FABRIC.md — reconcile §1-24, add §25 punch list

Reconciliation. §1-15 were written before any code was read and had drifted
from §16-24 in twenty places. Each superseded claim is now marked in place
rather than rewritten -- §19.4 and §19.5 quote the original §4 wording
directly, and the retracing trail matters more than a tidy read.

Two real gaps surfaced during the reread, neither previously recorded:

- §3's wire table was missing `mass`. Density is heat / mass, so mass has to
  live in the entry, and §3 is the table an implementer would work from. It is
  now an eighth wire.
- §8 asserts a per-patron exclusivity primitive -- "one sits in a car, the only
  exclusive thing in the room, no global lock" -- that nothing in §16-24
  defines. §21 covers ISR-vs-mainline concurrency, which is a different
  question. Marked OPEN at the source and carried as punch list item 1.1.

Also corrected: §9's admission table is complete for all five patron kinds with
both `?` marks closed; §12's six questions are all resolved; §5's third
category is supplied; §15's five-line summary had two wrong lines; §4, §6, §7,
§17.4 and §17.6 status markers now match reality.

§25 is the punch list, with its operating instructions first: one item at a
time, no jumping ahead, no scope increase, no fabricating anything unverified,
stop when blocked. Every checked item gets its own commit carrying both the
change and this file with that box ticked, so the document is never a claim
about work not in the branch.

Phase 0 is the substrate -- Hera alone plus real timers and IRQ return paths on
all three ISAs. Nothing else can start until it is done.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-03 10:27:49 -04:00
co-authored by Claude Opus 5
parent 7c436351a0
commit 7fe0133d07
+566 -58
View File
@@ -1,8 +1,22 @@
# FABRIC.md — DRAFT
# FABRIC.md — the Stadium
**Status:** Draft for review. Captures the design session of 3 August 2026.
**Nothing here is committed.** Sections are marked **DECIDED**, **LEANING**, or **OPEN** so
you can argue with it rather than inherit it.
**Status:** Living working document. Started 3 August 2026 and appended to as work proceeds.
Sections are marked **DECIDED**, **LEANING**, or **OPEN** so they can be argued with rather
than inherited.
**How to read it.** §115 are the original design argument, written before any code was
examined. §16 onward are findings and decisions made against the actual tree, in the order
they happened. **Where the two disagree, the later section wins** — earlier text is left
standing, with a pointer, because §19.4 and §19.5 quote it directly and because retracing
the reasoning matters more than a tidy read.
**On the name.** The thing described here is the **Stadium**. §115 call it "the arena",
which was the working name until it collided with `src/starkernel/vm/arena.c` — the
PMM-backed VM page allocator, an unrelated structure. Read "arena" as "Stadium" throughout
the early sections; the word was not swept, to keep the original text quotable.
**§25 is the punch list.** It is the authoritative statement of what is done and what is
not. Read its instructions before doing any work against this document.
---
@@ -23,6 +37,11 @@ message has.
**DECIDED.** Direction is not optional. The remaining question is effort, not validity.
> **Updated by §20 and §17.5.** There are five patron kinds, not four — VMs are the fifth
> and were already implemented (§20.1). And the Console's patron is the dirty *event*, not
> the cell (§17.5); "Console heats dirty cells" above is the reading that §9 flagged as
> suspect and §17.5 resolved.
---
## 2. The arena
@@ -49,17 +68,24 @@ One structure. No variants, no type field, no subclassing.
| Wire | Meaning |
|---|---|
| identity | handle or name |
| heat | current thermal state |
| TTL | remaining lifetime |
| identity | handle or name**never a content hash while resident** (§24.4) |
| heat | conserved share of 1.0, moved by traffic (§19.1) |
| TTL | remaining lifetime — messages and ACLs only (§17.1) |
| pin | invariance flag (opposite of TTL, not an extension of it) |
| link | index into the arena, not a pointer |
| code field | what to do when this entry is worked |
| payload | inline if small, by reference if large |
| link | index into the Stadium, not a pointer |
| code field | behaviour tag from a closed enumeration (§18.3) |
| **mass** | **cells this patron occupies — its footprint (§19.2)** |
| payload | carried in the patron's own cells; large patrons are simply heavy (§23.1) |
Fixed-size cells. Links are indices, so the arena stays an array — no fragmentation,
Fixed-size cells. Links are indices, so the Stadium stays an array — no fragmentation,
and tractable for Isabelle later.
> **Amended by §19.2 and §23.1.** `mass` is an eighth wire, added when density was
> defined — density is heat ÷ mass, so mass has to live in the entry. And the original
> payload rule ("inline if small, by reference if large") was dissolved rather than
> answered: a large patron occupies more cells, which is what mass already measures.
> By-reference is reserved for things outside the Stadium, which are not patrons.
**The code field is the entire type system.** A block's code field migrates. A message's
delivers. A VM's ticks. The engine never asks what kind of thing it is holding; it
heats, ranks, reaps, and calls the code field.
@@ -67,7 +93,7 @@ heats, ranks, reaps, and calls the code field.
> If you find yourself wanting a type field so the engine can branch on entry kind, the
> design has gone wrong. The code field already answers that question.
**DECIDED**, except payload threshold — see Open Questions.
**DECIDED**, including the payload question — dissolved in §23.1.
---
@@ -93,7 +119,21 @@ Consequences:
TTL expiry stays unconditional: entries leave at their own time, unscheduled, nobody's
decision. Pinning remains the separate, opposite mechanism — invariance, not longevity.
**LEANING.** The causality is right; the density formulation needs a concrete definition.
**DECIDED as amended by §19.** The density formulation this section called for is supplied
there. Three specific amendments, and the original wording above is left intact because
§19.4 and §19.5 quote it:
- **"Density generates heat" is backwards** (§19.5). Traffic confers heat; density is
heat ÷ mass, derived downstream. One word was carrying two meanings.
- **The third bullet is struck, not repaired** (§19.4). "A crowded entry is harder to
reach" does not translate — a hot entry is *easier* to reach, which is what a cache is
for. The conclusion survives via the second bullet: heat is zero-sum, so popularity is
self-limiting by conservation.
- **The first bullet is now true rather than aspirational.** Ranking reads density, which
§19.2 makes a number.
The line about TTL and pinning is correct but incomplete — §17.1 shows there are three
departure mechanisms, not two: TTL, heat decay, and pin.
---
@@ -108,7 +148,18 @@ This section exists because forcing everything in is how this design turns into
- **Devices are beside the arena.** The framebuffer is the building's lighting, not an
occupant. Console's dirty *events* are arena entries; the pixels are not.
**DECIDED.** Three sharp edges, nothing forced.
**DECIDED**, and completed by §17.5, which supplies the third edge this section counted but
did not list, and sharpens the second:
| Category | Relation | Example |
|---|---|---|
| Warehouse | beneath | Artemis, and the dictionary (§17.3) |
| Stadium | the floor | patrons |
| Utility | beside | framebuffer, and devices generally |
"The building's lighting" undersells the framebuffer — it reads as part of the structure.
§17.5 calls it **the power company**: external infrastructure the building consumes. Not
the Stadium, not the basement of the Stadium, a third thing.
---
@@ -123,7 +174,10 @@ The engine cannot be a VM service, because VMs live inside the thing it manages.
Structurally the same move as minting Zuse's certificate at first boot: a root that
cannot be produced by the mechanism it grounds.
**LEANING.** Order is right; the allocation mechanism is unspecified.
**DECIDED.** The order was right, and the allocation mechanism this section left unspecified
is now given: one global array of fixed-size cells, sized at boot from the memory budget,
addressed by index (§17.6b, §22.3). Step 1 above allocates that array before any VM exists;
step 2 makes Hera the first patron in it (§20).
---
@@ -132,10 +186,17 @@ cannot be produced by the mechanism it grounds.
Hera's job becomes arena distribution. This is not a new responsibility — allocating a
VM's share *is* birthing it, and lifecycle is already what Hera is for.
**OPEN:** whether a VM's share is a hard bound or an elastic one that can grow and shrink
under pressure, with capacity transferring between VMs as a conserved operation Hera
arbitrates. Elastic is more powerful and more work. Under elasticity, birth sizes the
*rest* volume rather than a cap — a more forgiving thing to have to guess right.
~~**OPEN:**~~ **RESOLVED in §22 — elastic.** Whether a VM's share is a hard bound or an
elastic one that can grow and shrink under pressure, with capacity transferring between VMs
as a conserved operation Hera arbitrates. Elastic is more powerful and more work. Under
elasticity, birth sizes the *rest* volume rather than a cap — a more forgiving thing to have
to guess right.
§22 takes the elastic option. §19's density definition turns it into a negative feedback
loop that runs itself — capacity flows down the density gradient — so it costs less than
this section anticipated. The layout that makes it cheap is a single global cell pool with
per-VM quotas held as counts (§22.3), and capacity must move on a slower loop than heat
(§22.4).
---
@@ -155,6 +216,16 @@ field re-entering through a metaphor. They are one entry shape differing only in
code field — a car's lifetime is the show, a person's is a visit; a car's code field is
*be attended to*, a person's is *move and attend*.
> **OPEN — the one thing in this section the design has not addressed.** *"Occasionally one
> sits in a car, which is the only exclusive thing in the room, scoped to a single object,
> no global lock."* That sentence asserts an exclusivity primitive: per-patron, not global.
> Nothing in §1624 defines it. §21 addresses ISR-versus-mainline concurrency, which is a
> different question.
>
> It matters because a patron reaped while someone is "sitting in" it is a use-after-free
> under another name, and §22.3 gave up physical fault containment, which raises the stakes
> rather than lowering them. Tracked as a punch-list item in §25.
---
## 9. The admission test
@@ -165,21 +236,28 @@ both of which must have a non-forced answer:
1. **What does heat mean for this thing?**
2. **What is its reap event?**
| Type | Heat means | Reap is | Verdict |
|---|---|---|---|
| Block | accessed often | migration | passes |
| Message | delivery urgency | delivery | passes |
| VM | runs often | execution / death by cooling | passes |
| ACL | checked often | ? | check |
| Screen cell | ? | redraw, which removes nothing | **suspect** |
**COMPLETE.** Run against every candidate; all five patron kinds pass, and the two `?` marks
are closed:
Screen cells are the one to resolve first. A cell never expires — it is a fixed grid
position always present. If cells are permanent arena entries, most of the arena is inert
and permanently pinned. The likely correct read is that the arena entry is the **dirty
event**, not the cell: transient, honest TTL, and the grid stays outside where it belongs.
| Type | Heat means | Governed by | Reap is | Verdict |
|---|---|---|---|---|
| Block | accessed often | heat decay | migration back to Artemis | passes |
| Message | delivery urgency | TTL | delivery | passes |
| VM | runs often | heat decay | death by cooling | passes (§20) |
| Word | executed often | heat decay | cooling off the floor | passes (§17.3) |
| ACL | checked often | TTL | **expiry** (§17.1) | passes |
| ~~Screen cell~~ | — | — | — | **not a patron** (§17.5) |
Ten minutes on paper. Either it confirms the design or it finds the one case that breaks
it, before any code moves.
Screen cells were the suspect case and the suspicion was correct. A cell never expires — it
is a fixed grid position always present, so cells-as-entries would leave most of the arena
inert and permanently pinned. §17.5 confirms the reading anticipated here: **the patron is
the dirty event, not the cell.** The grid stays outside, and the event turns out to be a
message with a different destination rather than a sixth kind.
Words were added to the table by §17.3 — the original list omitted them because §1 treated
the dictionary as the *reference implementation* rather than as a population of patrons.
Ten minutes on paper. It confirmed the design and caught one case, which is what it was for.
---
@@ -188,6 +266,12 @@ it, before any code moves.
**FABRIC.md first, then Hermes native on the fabric, then measure, then Console, then
Artemis last.**
> **Amended by §16.5 and §21.2.** This ordering is still right for the *subsystems*, but it
> is not the first work. A substrate floor sits beneath all of it: Hera alone, real timer
> interrupts and a real IRQ return path on all three ISAs (§16.1), and compudynamics driven
> from that tick. None of the sequencing below can begin until that exists, because the
> engine has nothing to run on. §25 carries the actual order.
Reasoning:
- Hermes is unfinished, which is lucky. Finishing it the old way and refactoring later
@@ -230,7 +314,25 @@ writing code.
---
## 12. Open questions
## 12. Open questions — all six now closed
**Status as of §24. Every question in this section has been answered or dissolved.** The
original text is kept below because several later sections quote it.
| | Question | Outcome | Where |
|---|---|---|---|
| Q1 | Payload threshold | **dissolved** — large patrons are simply heavy | §23.1 |
| Q2 | Entry header size | **sized** — 64-byte cell, ~32-byte header (constants to validate) | §23.3 |
| Q3 | Screen cell or dirty event | **event**; the grid is not a patron | §17.5 |
| Q4 | Per-VM share hard or elastic | **elastic**, via quota over one pool | §22 |
| Q5 | Loop coupling / timescales | **partly** — capacity must move slower than heat | §22.4 |
| Q6 | One region or nested per VM | **nested**, two levels | §21 |
Q5 is marked *partly* deliberately: §22.4 fixes the one ordering that matters (capacity
slower than heat) but the full eight-loop interference analysis has not been done, and
§16.1 notes it cannot be until a real time base exists on all three ISAs.
---
1. Payload threshold — what size goes inline versus by reference.
2. Arena entry header size. Cardinality spans orders of magnitude (dozens of VMs,
@@ -323,19 +425,25 @@ the build, and it is not finished until it is correct.
## 15. The whole thing in five lines
- The arena holds the live crowd. Storage is the warehouse. Devices are the building.
- The Stadium holds the live crowd. Storage is the warehouse. Devices are the utility.
- One entry shape. The code field is the only difference between kinds.
- Heat is density, conferred by traffic. Nobody decides.
- Departure is unconditional. Pinning is invariance, not longevity.
- The kernel opens the hall. Hera walks in first.
- Traffic confers heat. Heat is conserved at 1.0. Density is heat per cell. Ranking reads
density.
- Departure is TTL, or cooling, or never. Pinning is invariance, not longevity.
- The kernel opens the hall. Hera walks in first, and cannot be asked to leave.
*(Amended from the original five by §17.5, §19.5, §17.1 and §20.5 #3. The earlier third
line — "heat is density, conferred by traffic" — conflated two quantities; the earlier
fourth — "departure is unconditional" — knew only one mechanism.)*
---
## 16. Substrate findings — 2026-08-03
Naming: the arena is now called the **Stadium**, because `src/starkernel/vm/arena.c`
already owns "arena" for the PMM-backed VM page allocator — an unrelated concept. Sections
115 above still say arena and have not been reconciled.
already owns "arena" for the PMM-backed VM page allocator — an unrelated concept. §115
still use the old word; see the note in the header. Their *substance* has since been
reconciled against §1624, with each superseded claim marked in place.
Four findings from reading the tree. The first three change what step one costs. The
fourth changes what the engine is allowed to be.
@@ -375,7 +483,9 @@ riscv64 timing numbers before and after that fix are not comparable.
### 16.3 The dictionary is already a Stadium
§1 claims the dictionary is the reference implementation. It is stronger than that.
`DictEntry` today carries six of the seven wires in §3:
`DictEntry` today carries six of the eight wires in §3 — the two it lacks are `mass`, which
§19.2 added later, and a behaviour tag, since its code field is a raw function pointer
rather than an enumerated tag (§18.3):
| §3 wire | Already in `DictEntry` |
|---|---|
@@ -441,10 +551,14 @@ ground) sits above that floor, not below it.
## 17. Patrons
An occupant of the Stadium is a **patron**. Blocks, words, ACLs and messages are all
patrons. The word is doing real work: it names the category without implying a class
An occupant of the Stadium is a **patron**. Blocks, words, ACLs, messages **and VMs** are
all patrons. The word is doing real work: it names the category without implying a class
hierarchy, and it keeps the metaphor honest — patrons attend, they are not the building.
VMs were omitted when this section was written and added by §20, which found they were
already implemented as the outer level. **Five kinds, not four** — the counts elsewhere in
§17 predate that and should be read accordingly.
**DECIDED.**
### 17.1 Four patrons die four different ways — and that is not a type field
@@ -479,6 +593,7 @@ Mapped per patron:
| ACL | TTL | expiry |
| Block | heat decay | **migration back to Artemis** — evicted, not destroyed |
| Word | heat decay | cooling off the floor (see §17.3) |
| VM | heat decay | death by cooling (see §20); Hera is pinned (§20.5 #3) |
#### Two measures, one clock
@@ -580,9 +695,11 @@ direction stated differently earlier the same day, and it deserves longer than a
### 17.4 Open
1. **What is a word's TTL, concretely?** Heat decay already cools words, but decay-to-cold
and TTL-expiry are not obviously the same clock. Either they unify or §17.3 needs a
second mechanism, which would be a bad sign.
1. ~~**What is a word's TTL, concretely?**~~ **DISSOLVED — a word has no TTL.** The premise
was wrong. §17.1 (as corrected) establishes TTL and heat decay as two distinct
mechanisms, not one clock read two ways: words are governed by **heat decay**, and only
messages and ACLs carry a TTL. Nothing needed unifying and no second mechanism was
required.
2. ~~**Is the hot-word set bounded today?**~~ **RESOLVED — yes, hard bounded.**
`DictEntry *cache[HOTWORDS_CACHE_SIZE]` (`include/physics_hotwords_cache.h:168`) is a
fixed array inside the struct, with `HOTWORDS_CACHE_SIZE = 32` (`:84`). Nothing is
@@ -601,12 +718,19 @@ direction stated differently earlier the same day, and it deserves longer than a
NULL into `cache[lru_index]`. Unreachable today — every caller passes a non-NULL entry
from the bucket search — but the NULL check reads as though it prevents this, and does
not.
3. **ACL reap** — §9 still marks this `?`. ACL entries carry `acl_ttl` in `DictEntry`
already, so this is likely the easiest of the four to close, and it should be closed on
paper alongside the others rather than left dangling.
4. Does a patron ever change what it is? A block that is written becomes a new block by
content-addressing. A word that is redefined is a new word. If nothing mutates in place,
that is worth stating explicitly — it removes a whole class of proof obligation in §13.
3. ~~**ACL reap**~~ **RESOLVED — TTL expiry.** ACL entries already carry `acl_ttl` in
`DictEntry`, so they fall under the TTL mechanism in §17.1 alongside messages. §9's `?`
is closed.
4. ~~Does a patron ever change what it is?~~ **RESOLVED in §24 — and the question was
slightly wrong.** Full immutability is not available: FORTH blocks mutate in place by
definition (`BLOCK` / `UPDATE` / `FLUSH`), while words already behave the opposite way,
redefinition creating a new entry. The kinds genuinely disagree.
What actually mattered was never payload but **mass and identity**. §24.2 states the
invariant: identity never changes during a residency; mass never changes as a side effect
of use; header fields mutate freely; payload contents may mutate provided size and
identity do not. That gives §13 the enumerable mass function it needed without demanding
immutability nothing could deliver.
### 17.5 The framebuffer is not a patron — it is a utility
@@ -644,7 +768,10 @@ That gives three categories, all principled, none of them exceptions:
| Stadium | the floor | patrons |
| Utility | beside | framebuffer, and devices generally |
#### What is live is the dirty event — and it is not a fifth patron kind
#### What is live is the dirty event — and it is not a new patron kind
*(Written when the taxonomy had four kinds; §20 has since added VMs as the fifth. The point
stands unchanged — the dirty event adds nothing to the taxonomy at all.)*
Run §9's two questions on it:
@@ -714,7 +841,10 @@ capacity is determined. Deriving that capacity from available memory rather than
constant costs nothing and gives up nothing. Cells stay uniform, links stay indices, the
region stays contiguous.
**LEANING toward (b).**
**SUPERSEDED by §22.3 — the answer is (b) with a refinement.** The Stadium is one global
array of cells sized at boot, and per-VM shares are **quotas held as counts** rather than
separate regions. That keeps (b)'s properties while making (c)'s elasticity trivial, so the
two are no longer alternatives.
#### Why (d) is expensive — by this document's own argument
@@ -750,9 +880,11 @@ capacity transferring between VMs as a conserved operation Hera arbitrates."* El
the Stadium level and elasticity at the per-VM level are the same question asked at two
scales, and they should be answered together rather than separately.
**OPEN**, and coupled to §7 and to §12 Q6 (one Stadium or nested per VM). Note that if Q6
resolves to nested-per-VM, (c) becomes considerably more attractive — capacity transfer
between VMs is the whole point of that arrangement, and a fixed per-VM bound would waste it.
~~**OPEN**~~ **RESOLVED — and the prediction here was right.** Q6 did resolve to nested
(§21), and (c) did become the attractive option (§22). But §22.3 found a cheaper route to
it than resizing a contiguous region: with quotas held as counts over one shared cell pool,
elasticity costs arithmetic on two integers and the denominator never moves at the level
that matters. The total stays fixed; only the partition shifts.
#### The rule this reduces to
@@ -825,11 +957,15 @@ us to.
So a fixed enum with fixed dispatch is mandatory, not a concession to practicality. But
there are two things one could enumerate, and only one of them preserves §3:
| | Enumerate | Engine asks | Cost of a fifth patron |
| | Enumerate | Engine asks | Cost of a new patron kind |
|---|---|---|---|
| ✗ | patron **kinds**`BLOCK`, `WORD`, `ACL`, `MESSAGE` | "what are you?" | touch the engine |
| ✗ | patron **kinds**`BLOCK`, `WORD`, `ACL`, `MESSAGE`, `VM` | "what are you?" | touch the engine |
| ✓ | **behaviours**`MIGRATE`, `DELIVER`, `EXPIRE`, `COOL` | nothing; calls `dispatch(tag)` | none |
This was not hypothetical. VMs were added as a patron kind by §20 *after* this section was
written, and cost the engine nothing — a VM's behaviour tag is `COOL`, the same tag a word
carries. Under the rejected column it would have been an engine change.
Both are closed, both are fixed at build time, both are equally provable. Only the second
keeps the engine ignorant of its contents, which is the property §3 exists to protect. Two
patrons may share a tag; a new patron that migrates costs zero engine changes.
@@ -1508,3 +1644,375 @@ Firmness of each figure:
If identity can be elided for some kinds, 8 bytes of a 32-byte header is a large saving.
This must not become a per-kind branch (§18.3), so it is only worth doing if it can be
expressed uniformly.
---
## 24. Mutation, identity, and mass stability
§17.4 #4 and §19.6 #2 ask whether patrons mutate in place. The question as posed does not
survive contact with the patron kinds, and the version that does is cheaper.
### 24.1 Full immutability is not available
FORTH-79 blocks are mutable by definition: `BLOCK` returns a buffer, writes go into it,
`UPDATE` marks it dirty, `FLUSH` writes it back. In a block editor that is a mutation per
keystroke. A rule that every write produces a new patron would mean a new patron per
keystroke.
Words already behave the opposite way. Redefinition creates a **new** dictionary entry
rather than editing the existing one — which is why `vm_dict_resolve_in_bucket()` resolves
in reverse-insertion order, newest visible definition winning.
The kinds genuinely disagree. Forcing them to agree would be §11's exception trap approached
from the other side.
### 24.2 The concern was never payload — it was mass and identity
§19.6 #2 asks this for density stability: if mass changes underfoot, density changes and
ranking is meaningless. §13 asks it because in-place mutation is what makes proofs
expensive.
Neither concern is about payload bytes. A block's contents can change entirely and it is
still 1024 bytes at the same handle.
So the invariant is narrower than immutability and costs almost nothing:
| Tier | Rule |
|---|---|
| **Identity** | never changes for the life of the residency |
| **Mass** | never changes *as a side effect of use* |
| **Header** — heat, TTL, flags, link | mutates freely; this is the engine's work |
| **Payload contents** | may mutate in place, provided size and identity do not |
**DECIDED.**
### 24.3 VMs appear to violate the mass rule, and do not
A VM's mass is elastic by §22 — that is the point of elasticity. But it changes only through
Hera's arbitrated transfer, on the slow loop of §22.4. So the rule is not that mass is
constant:
> **Mass changes only through an arbitrated transfer, never through traffic.**
Traffic moves heat and nothing else. Density is therefore stable between transfers, which is
what ranking requires, and §13 gets a mass function that changes at known, enumerable points
rather than continuously.
### 24.4 A resident patron's identity is its handle, not its content hash
This follows from tier 1 and is worth stating because it is easy to get backwards.
A block's identity while resident is its **handle** — its LBN. Its content hash is computed
at **migration**, for the warehouse. If identity were the content hash, editing a resident
block would change its identity mid-residency and break tier 1 immediately.
§3 already permits this: identity is *"handle or name"*. It never said hash.
Content-addressing therefore stays where it belongs — at the warehouse boundary, which is
already how Artemis and the capsule model behave. The Stadium does not do content
addressing; the warehouse does.
### 24.5 The rule this reduces to
> **No patron may grow or shrink while resident. If it needs to be a different size, it is
> a different patron.**
No per-kind branching, no exception, and it holds for all five patron kinds.
### 24.6 Open
1. **What happens to a resident block whose content changes, at migration time?** Its new
content hash differs from the one it arrived with. The warehouse sees a new block; the
Stadium saw one continuous residency. That is coherent, but the hand-off needs stating —
particularly whether the old hash is retained anywhere for audit.
2. **Does redefining a word while its old definition is resident leave two patrons?** The
dictionary keeps both entries by design. If both are hot, both are on the floor, both
have mass. That is probably correct — they are genuinely two different words — but it
should be confirmed rather than discovered.
---
# 25. The punch list
**This section is authoritative for what is done and what is not.** Sections 124 are the
design. This is the work.
---
## 25.0 How to implement this punch list
**Read this subsection every time before touching an item. Do not skip it because it was
read earlier in the session.**
### The rules
1. **One item at a time.** Take the lowest-numbered unchecked item whose prerequisites are
met. Finish it completely. Do not begin a second item while one is in progress.
2. **Do not jump ahead.** Do not start a later item because it seems easy, related, or
convenient. Do not do "while I'm in here" work. If a later item looks like it should be
reordered, say so and wait for an answer — do not reorder unilaterally.
3. **Do not increase scope.** Do exactly what the item says. If the item says "write the
trap entry," write the trap entry — not the trap entry plus a refactor of the file it
lives in. Anything you notice that is not in the item gets **reported**, not fixed.
This includes obvious bugs. Report them; they get their own item if they warrant one.
4. **Do not fabricate, confabulate, or conflate.** If you do not know how something works,
read it. If you cannot determine it by reading, **stop and say so**. Never invent a
function, a register name, a constant, a FORTH word, or an API that you have not
verified exists in this tree or in the relevant hardware manual. Never guess at a value
and present it as known. Never merge two things that are similar into one thing that is
neither. A wrong answer stated confidently has cost this project git resets before.
5. **When blocked, stop.** Report exactly what is blocking, what was tried, and what is
needed. Do not work around it silently. Do not substitute a different approach and
carry on.
6. **Acceptance is not optional and not negotiable.** Each item states *Done when*. An item
is not done until that exact condition is met and observed. Not "should work," not
"compiles cleanly" unless that is what the item says. If acceptance requires the
three-architecture QEMU boot, then all three have booted and their logs exist.
7. **Report failures honestly.** If a test fails, say it failed and show the output. If a
step was skipped, say it was skipped and why. Never describe partial work as complete.
### The commit discipline
Every checked-off item gets **its own commit**, and that commit contains:
- the code or document change for that item, and
- this file, with that item's checkbox changed from `[ ]` to `[x]`.
Nothing else. One item, one commit. The punch list and the tree move together, so the
document is never a claim about work that is not in the branch.
Commit message format:
```
<area>: <what the item did> e.g. riscv64: real trap entry with SRET return
Punch list §25 item <id> complete.
<one or two lines on what was actually verified, not what was intended>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
```
### Standing constraints from `.claude/CLAUDE.md`
These override anything convenient:
- **Never create a branch without explicit permission.** Work on the branch you are on.
- **Never stash.** If the tree is dirty, report it and wait.
- **Never apply a fix that was not requested.** Report it instead.
- **Acceptance for any kernel change is the three-architecture QEMU boot.** There is no
other test. The hosted `make` build is compile-sanity only.
- **One QEMU instance at a time, foreground, `clean` before `qemu`.** Concurrent runs
corrupt the timing signal.
- **Read `experiments/bare_metal/README.md` in full before editing any `.4th` file**, and
verify capsule edits with `mkcapsule --lint` rather than counting bytes by hand.
### When an item is genuinely wrong
The design is not sacred. If implementing an item shows the design is wrong, **stop, report
what the code demonstrated, and propose the amendment.** Amend the relevant section of this
document first, get agreement, then continue. Do not implement something you believe is
wrong because it is written down, and do not silently implement something different.
---
## 25.1 Phase 0 — Substrate
*Nothing in later phases can start until Phase 0 is complete. The engine has nothing to run
on until there is a tick on all three architectures (§16.1, §16.5).*
- [ ] **0.1 — Prune `capsules/init.4th` to Hera alone.**
Remove the Artemis and Hermes births, the `common:msg.4th` and `process.4th` loads, and
blocks 20502059's fleet test scaffolding. Adjust `BOOT-BANNER`, `VM-TREE`, `VM-CHILDREN`.
Leave `capsules/hermes/` and `capsules/artemis/` untouched on disk.
*Done when:* all three architectures boot to the prompt with Hera alone, no Hermes or
Artemis in the banner, and the three logs exist under `logs/`.
*Refs:* the surviving blocks are 2057, 2049, 2050.
- [ ] **0.2 — riscv64: real trap entry.**
Replace the one-way `riscv64_trap_entry` in `arch/riscv64/isr.S` with save / dispatch /
restore / `sret`. Route `scause` bit 63 + cause 5 to the timer path; everything else keeps
falling through to the existing fatal handler.
*Done when:* riscv64 boots to the prompt unchanged, having taken and returned from at
least one trap. Exceptions still halt with the same diagnostic as before.
- [ ] **0.3 — riscv64: SBI timer and real time base.**
Arm the timer via the SBI TIME extension. Switch the time base from `rdcycle` to the
`time` CSR and take its frequency from the device tree `timebase-frequency`, with a named
fallback constant — not a bare magic number (§16.2).
*Done when:* `heartbeat_ticks()` advances on riscv64 and the tick interval matches the
configured rate within measurement noise. Verify the SBI extension is present before
relying on it; if it is absent, stop and report rather than falling back silently.
- [ ] **0.4 — aarch64: determine the exception level at runtime.**
Read `CurrentEL` in `timer_init()` and select the `CNTP_*_EL0` or `CNTHP_*_EL2` register
set accordingly. Do not hardcode either.
*Done when:* the boot log states which EL was detected, on real QEMU output.
- [ ] **0.5 — aarch64: IRQ vector split.**
Split `irq_spx` out of the shared fatal handler in `arch/aarch64/isr.S`: save `x0``x30`,
`ELR_EL1`, `SPSR_EL1`, call a C handler, restore, `eret`. The other fifteen vectors are
unchanged. Note the 128-byte slot limit — the save sequence will not fit inline and must
branch to a trampoline.
*Done when:* aarch64 boots to the prompt having taken and returned from at least one IRQ.
- [ ] **0.6 — aarch64: minimal GICv2.**
Enable the distributor and CPU interface, set the priority mask, enable the timer PPI,
acknowledge via `IAR` / `EOIR`. **Read the base addresses and the PPI INTID from the
device tree — do not take them from memory or from this document.**
*Done when:* the timer interrupt is delivered and acknowledged. Scope is one interrupt;
a general GIC driver is out of scope and must not be written.
- [ ] **0.7 — aarch64: arm the generic timer.**
`apic_timer_start()` / `apic_timer_stop()` using the register set chosen in 0.4, re-armed
each tick.
*Done when:* `heartbeat_ticks()` advances on aarch64 at the configured rate.
- [ ] **0.8 — Converge the three architectures on one tick path.**
One `heartbeat_tick()` call site per architecture; ISR does counter and flag only;
the engine runs in the mainline off the pending flag. Reuse the existing
`adaptive_check_accumulator` / `adaptive_pending` pattern rather than inventing a second.
*Done when:* all three architectures drive the same bottom half, and no loop math runs in
interrupt context.
*Refs:* §18.4, §21.2.
- [ ] **0.9 — Write the concurrency constraint at the mutex stub.**
Add a comment at `src/starkernel/vm/host/shim.c:415` stating that the no-op is correct
only while nothing in interrupt context mutates shared structure, and that making it a
real spinlock would deadlock a single hart.
*Done when:* the comment is in place. This is a documentation item; no behaviour changes.
*Refs:* §21.2, §21.5 #1.
- [ ] **0.10 — Phase 0 acceptance.**
Full three-architecture QEMU run. Confirm: boots to prompt on all three; tick count
non-zero on all three; TIME-TRUST and variance sane on riscv64 after 0.3; amd64 output
unchanged from its pre-branch behaviour.
**Then boot one architecture twice and confirm the parity dict hash is identical across
runs.** If it drifts, something is firing on wall time and Phase 0 is not complete.
*Done when:* all of the above observed, logs committed.
*Refs:* §16.4, §18.5.
---
## 25.2 Phase 1 — Design questions to settle on paper
*These need answers, not code. Each one is settled by amending the relevant section of this
document and committing that amendment as its own item.*
- [ ] **1.1 — Exclusive access ("sitting in a car").** §8 asserts a per-patron exclusivity
primitive that is not a global lock. Nothing defines it. Decide what it is, what it
blocks, and what happens if a patron is selected for reaping while held.
*Refs:* §8. **This is the largest unresolved design question.**
- [ ] **1.2 — The resting floor.** Whether a VM's quota has a floor, and whether it is the
mass of its pinned patrons (derived) or a constant (tuned). *Refs:* §22.5 #1.
- [ ] **1.3 — What triggers a capacity transfer.** Hera arbitrates; on what signal, and how
often. Should read the density gradient, not a schedule. *Refs:* §22.5 #2.
- [ ] **1.4 — The heat/capacity timescale ratio.** The ordering is fixed (capacity slower);
the ratio is not. *Refs:* §22.4, §22.5 #3.
- [ ] **1.5 — The outer bound.** The outer Stadium's capacity, and the behaviour at the
bound: birth refused, or coldest VM reaped. *Refs:* §20.5 #1, §22.5 #4.
- [ ] **1.6 — A VM's mass: allocated share, or one cell.** *Refs:* §20.4, §20.5 #2.
- [ ] **1.7 — Rule out recursion beyond two levels** — deliberately, not by omission.
*Refs:* §20.5 #4.
- [ ] **1.8 — Block content change at migration.** A resident block whose content changed
has a different hash on the way out. State the hand-off. *Refs:* §24.6 #1.
- [ ] **1.9 — Redefined words as two resident patrons.** Confirm both may be on the floor.
*Refs:* §24.6 #2.
- [ ] **1.10 — Identity elision.** Whether identity can be dropped for some kinds without a
per-kind branch. Optimisation; may be closed as "no". *Refs:* §23.4 #3.
- [ ] **1.11 — Dirty-event granularity.** Leaning region-based. **Deferred to console work**
— do not settle speculatively. *Refs:* §17.5, §23.2, §23.4 #1.
---
## 25.3 Phase 2 — Prepare the existing physics
- [ ] **2.1 — Restate heat transfer on tick count.**
`vm_physics_touch()` scales transfers by wall-clock elapsed time
(`capsule_vm_physics.c:272`). Restate it on tick count.
*Done when:* no wall-clock value influences heat, and the double-boot reproducibility
check from 0.10 still passes.
*Refs:* §18.5, §19.6 #3.
- [ ] **2.2 — Bound the VM registry.**
The registry is a `kmalloc`-backed unbounded list, which makes fleet K an identity that
cannot fail (§20.2). Give it the hard bound decided in 1.5.
*Done when:* the population is bounded and `VM-CONSERVED?` can, in principle, fail.
*Refs:* §2, §20.2.
---
## 25.4 Phase 3 — Stadium core
*Blocked on Phase 0 complete, and on items 1.11.7.*
- [ ] **3.1 — Cell and header.** Define the entry with all eight wires (§3). Validate the
64-byte cell by counting real bytes; adjust and record if it does not fit. *Refs:* §23.3.
- [ ] **3.2 — Boot-time allocation.** One global cell array, sized from the memory budget,
before any VM exists. *Refs:* §6, §17.6, §22.3.
- [ ] **3.3 — Behaviour enumeration and dispatch.** Closed tag set fixed at build time.
Enumerate behaviours, never patron kinds. *Refs:* §13, §18.3.
- [ ] **3.4 — Density ranking.** Heat ÷ mass, read not computed. *Refs:* §19.2, §19.3.
- [ ] **3.5 — Admission and eviction.** Admit if denser than the least dense resident.
*Refs:* §19.3.
- [ ] **3.6 — Hera as patron zero, pinned.** Assert at the eviction site; selecting Hera is
a panic, not a filtered candidate. *Refs:* §20.5 #3.
---
## 25.5 Phase 4 — Migrate the subsystems
*One subsystem at a time, converted completely. Never two live heat mechanisms at once
(§11).*
- [ ] **4.1 — Hot words onto the Stadium.** Replaces the round-robin eviction with density
ranking. Measurable before and after via `stats.evictions` / `stats.promotions`.
*Refs:* §17.3.
- [ ] **4.2 — Hermes native on the Stadium.** The proving ground; produces the effort
number. *Refs:* §10.
- [ ] **4.3 — Console.** Settles 1.11 as part of the work. *Refs:* §17.5.
- [ ] **4.4 — Artemis last.** It works today; it is the thing that cannot be broken.
*Refs:* §10.
---
## 25.6 Phase 5 — Verification and measurement
- [ ] **5.1 — Re-run the DoE on the new substrate.** A green POST suite is not evidence that
K holds; those are different claims. *Refs:* §10.
- [ ] **5.2 — Isabelle/HOL.** One datatype, one index space, one conservation theorem.
*Refs:* §13, §22.3.
- [ ] **5.3 — Shrink the subsystem documents.** `ARTEMIS.md`, `HERMES.md`, `CONSOLE.md`,
`TRIPOD.md` should each reduce to roughly three lines. Any that grows is fighting the
design. `TRIPOD.md` also needs its Immediate Goal rewritten — it currently requires Hera
to spawn Hermes and Artemis at boot, which 0.1 undoes. *Refs:* §11.
---
## 25.7 Reported, not scheduled
*Found while reading. Not fixed, not assigned. They become items only if Captain Bob says
so.*
- `hotwords_cache_promote()` writes NULL into the ring if `word` is NULL and the cache is
full (`physics_hotwords_cache.c:363-364`). Unreachable today.
- `heartbeat_trust()` is exported and has zero callers.
- `m5_time_trust` and `m5_variance` (`include/vm.h:315-316`) are declared and never used.
- `src/*.c.bak` files are tracked in git at the `src/` top level.
- The `bump-z` / `bump-y` targets in the hosted `Makefile` reference version macros that do
not exist in the generated `include/version.h`.