FABRIC.md: resolve items 1.8, 1.9, 1.10, 1.12

Punch list §25 items 1.8, 1.9, 1.10, 1.12 complete.

1.8 -- block content hash is computed once, at the migration boundary,
as part of the MIGRATE code field; audit retention of the old hash is
an Artemis-layer question, out of scope for the Stadium (§5).

1.9 -- confirmed factually (not decided): vm_dict_resolve_in_bucket()
keeps both old and new definitions resident with newest-wins shadowing,
no GC on redefinition.

1.10 -- closed as no. Identity elision either reintroduces the
per-kind branch §18.3 forbids, or breaks lookups (name, LBN) that
words and blocks already depend on outside the Stadium. Stays a fixed,
always-present 8-byte field for every kind.

1.12 -- linked, not contiguous. Forced by §22.3's already-decided
disjoint per-VM free list, which gives no adjacency guarantee.
4-byte next-index, 60 usable bytes per continuation cell, 18
continuation cells for a 1024-byte block. Completes §23.3's sizing
table and unblocks item 3.1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-04 10:42:33 -04:00
co-authored by Claude Sonnet 5
parent 4fd9420ecf
commit f2eb877691
+95 -36
View File
@@ -1910,22 +1910,24 @@ cheaper than repacking one later.
256 KB per VM is comfortable against QEMU's `-m 1024`, and the outer Stadium's capacity
(§20.5 #1) then follows from how many VMs the machine is willing to host.
#### Why the continuation cell cannot be sized yet
#### The continuation cell — RESOLVED by item 1.12, linked
An earlier draft stated a 1024-byte block is "17 cells: 1 header + 16 payload." **That figure
assumes continuation cells are contiguous and carry nothing but bytes.** Neither is
established:
**RESOLVED 2026-08-04.** An earlier draft stated a 1024-byte block is "17 cells: 1 header +
16 payload." That figure assumed continuation cells are contiguous and carry nothing but
bytes — an assumption this section settles rather than leaves open.
- §22.3 allocates cells from a per-VM free list, so a patron's cells are **not necessarily
adjacent**. If they are not, each continuation cell needs a link to the next — which is
4 bytes off its payload, making it 60 usable, and a 1024-byte block 18 continuation cells
rather than 16.
- Alternatively, allocation could guarantee contiguous runs for multi-cell patrons, keeping
continuation cells pure payload at the cost of reintroducing the fragmentation §3 avoids.
**Linked, not contiguous — forced by what §22.3 already decided, not a fresh 50/50 choice.**
§22.3's per-VM free list (DECIDED) draws cells individually, with no adjacency guarantee.
Guaranteeing contiguous runs for multi-cell patrons would mean changing that allocator to
find runs rather than pop a free-list head, which reintroduces exactly the fragmentation §3
and §13 already ruled out by choosing fixed-size, index-linked cells in the first place. The
allocator that exists says linked; contiguous would require an allocator that does not.
These are different designs with different costs, and the choice determines both the mass of
every large patron and whether §22.3's free list stays a simple list. **OPEN — settle before
item 3.1.** The "17 cells" figure should not be relied on until it is.
**Consequence for sizing:** each continuation cell carries a 4-byte next-index alongside its
payload, leaving **60 bytes usable** out of the 64-byte cell. A 1024-byte block needs **18
continuation cells** at 60 usable bytes each, not 16 at a full 64 — the same figure this
section's earlier draft flagged as the linked-form cost without yet choosing it. §23.3's
sizing table is now complete on this row.
Firmness of each figure:
@@ -1945,25 +1947,43 @@ Firmness of each figure:
1. **Dirty-event granularity** (§23.2) — confirm region-based when console work begins.
2. **Cell size validation.** Build the header for real, count the bytes, and check that a
typical message still fits in one cell with the behaviour tag and flags included.
3. **Is `identity` needed at all for every patron kind?** For a word it is a name; for a
block a handle (§24.4); for a message possibly nothing — its identity could be its index.
If identity can be elided for some kinds, 8 bytes of a 32-byte header is a large saving.
3. ~~**Is `identity` needed at all for every patron kind?**~~ **RESOLVED by item 1.10
(§25.2), 2026-08-04 — no, it cannot be elided.** For a word it is a name; for a block a
handle (§24.4); for a message possibly nothing — its identity could be its index. 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.
**It cannot be, and the reason is a genuine conflict, not just difficulty.** Two ways to
elide it, both blocked:
- **Elide it only for kinds that don't need it** (messages) while keeping it for kinds
that do (words, blocks). This is exactly the per-kind branch §18.3 forbids — the
engine, or something reading the header, would have to know a message's header is
shaped differently than a word's, which reintroduces the type-field problem §3 exists
to prevent.
- **Elide it everywhere, uniformly.** This breaks the kinds that genuinely need it: a
word is resolved by name, not by Stadium position — `vm_dict_resolve_in_bucket()`
looks up by name — and a block is resolved by LBN, not by Stadium position either. The
Stadium index is not a substitute for either; they are different addressing schemes
serving different lookups.
So the saving is not reachable without violating either §18.3's uniformity requirement or
a lookup mechanism a patron kind already depends on outside the Stadium. `identity` stays
a fixed, always-present 8-byte header field for every kind, whether or not a given kind's
own logic makes use of it.
**Larger than it first appeared.** §3 now declares cells a closed two-valued union —
header or continuation. Whatever distinguishes the two occupies header space and
interacts directly with any identity elision: a scheme that reuses the identity field as
the discriminator, for instance, would couple the two decisions. Settle the
header/continuation encoding first; identity elision is downstream of it.
header/continuation encoding first; identity elision is downstream of it. (Settled by
item 1.12 — see §23.4 #4 — reinforcing that identity stays untouched by that encoding.)
4. **The continuation-cell encoding.** Whether a multi-cell patron's cells are contiguous
(continuation cells are pure payload; allocation must find runs, reintroducing
fragmentation) or linked (continuation cells carry a next-index, costing 4 bytes of
payload each and changing every large patron's mass). §22.3's per-VM free list does not
currently guarantee adjacency, so the linked form is the default unless allocation
changes. **This gates item 3.1** — the cell structure cannot be built without it, and
§23.3's sizing table cannot be completed without it either.
4. ~~**The continuation-cell encoding.**~~ **RESOLVED by item 1.12 — linked.** See §23.3's
"The continuation cell" subsection: forced by §22.3's already-decided disjoint free list,
not a fresh choice. 4-byte next-index, 60 usable bytes per continuation cell, 18
continuation cells for a 1024-byte block. Item 3.1 is unblocked on this item.
---
@@ -2041,14 +2061,31 @@ 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.
1. ~~**What happens to a resident block whose content changes, at migration time?**~~
**RESOLVED by item 1.8 (§25.2), 2026-08-04.** 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, and the hand-off is: the new hash is computed **exactly
once, at the migration boundary**, as part of the block's `MIGRATE` code field —
consistent with §24.4 (a resident block's identity is its handle/LBN, never its content
hash) and §17.2 (migration is the block's departure event, not destruction). Nothing
about a resident block's identity changes mid-residency regardless of how many times its
content mutates; the hash is a warehouse-side fact computed only when the block actually
leaves.
**Whether the old hash is retained anywhere for audit is out of scope here.** §5 draws
this boundary already: the warehouse is beneath the Stadium, and the Stadium does not do
content addressing — the warehouse does. Audit retention is an Artemis-layer policy
question, not a Stadium one, and inventing an answer for it here would cross that
boundary rather than respect it.
2. ~~**Does redefining a word while its old definition is resident leave two patrons?**~~
**RESOLVED by item 1.9 (§25.2), 2026-08-04 — confirmed, yes.** Not a design choice, a
factual check: `vm_dict_resolve_in_bucket()` (`dictionary_management.c:257`) walks a
bucket chain and returns the newest match — "the newest visible definition wins
(FORTH-79 shadowing)" (`:266`). Nothing in the redefinition path unlinks or frees the
superseded `DictEntry`; it stays in the bucket, merely shadowed. So if both the old and
new definitions are hot, both are correctly on the floor, both have mass, both are
ranked independently. This is the right behaviour, not an artefact to work around — they
are genuinely two different words with two different execution histories.
---
@@ -2422,20 +2459,35 @@ document and committing that amendment as its own item.*
> VM-birth time — a birth that would exceed the configured depth is refused. Full argument
> in §20.5 #4.
- [ ] **1.8 — Block content change at migration.** A resident block whose content changed
- [x] **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.
> **RESOLVED 2026-08-04.** New hash computed exactly once, at the migration boundary, as
> part of the block's `MIGRATE` code field — the resident identity (handle/LBN) never
> changes mid-residency. Whether the old hash is retained for audit is an Artemis-layer
> question, out of scope for the Stadium per §5's boundary. Full argument in §24.6 #1.
- [x] **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
> **RESOLVED 2026-08-04 — confirmed, yes.** Factual, not a design choice:
> `vm_dict_resolve_in_bucket()` keeps both entries resident with newest-wins shadowing, no
> GC on redefinition. Correct behaviour, not an artefact. Full argument in §24.6 #2.
- [x] **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.
> **RESOLVED 2026-08-04 — closed as no.** Eliding it per-kind reintroduces the type-field
> branch §18.3 forbids; eliding it uniformly breaks lookups words and blocks already
> depend on outside the Stadium (name, LBN). Neither path is reachable without violating
> an existing constraint. `identity` stays a fixed, always-present 8-byte field for every
> kind. Full argument in §23.4 #3.
- [ ] **1.11 — Dirty-event granularity.** Leaning region-based. **Blocked on item 4.3**
it is settled as part of the console migration, not speculatively before it (C5).
*Refs:* §17.5, §23.2, §23.4 #1.
- [ ] **1.12 — The continuation-cell encoding.** Contiguous (continuation cells are pure
- [x] **1.12 — The continuation-cell encoding.** Contiguous (continuation cells are pure
payload; allocation must find runs, reintroducing fragmentation) or linked (each
continuation cell carries a next-index, costing 4 bytes of payload and changing every
large patron's mass). §22.3's per-VM free list guarantees no adjacency, so linked is the
@@ -2443,6 +2495,13 @@ document and committing that amendment as its own item.*
was never a schedulable item until now (C4). Settling it completes §23.3's sizing table.
*Refs:* §23.4 #4, §23.3, §22.3. **Prerequisite of 3.1.**
> **RESOLVED 2026-08-04 — linked.** Forced, not chosen: §22.3's disjoint per-VM free list
> gives no adjacency guarantee, and guaranteeing contiguity would reintroduce the
> fragmentation §3/§13 already ruled out. 4-byte next-index, 60 usable bytes per
> continuation cell, 18 continuation cells for a 1024-byte block. §23.3's sizing table is
> complete; item 3.1 is unblocked on this item. Full argument in §23.3's "The continuation
> cell" subsection.
---
## 25.3 Phase 2 — Prepare the existing physics