diff --git a/FABRIC.md b/FABRIC.md index 010be05..d38dd27 100644 --- a/FABRIC.md +++ b/FABRIC.md @@ -1078,7 +1078,7 @@ that matters. The total stays fixed; only the partition shifts. Decide how big the Stadium is at runtime. Do not decide what an entry is, or how entries are addressed, at runtime. -### 17.7 Word-level heat conservation — LEANING, one question open, blocks item 4.1 +### 17.7 Word-level heat conservation — DECIDED 2026-08-05, blocks item 4.1 until implemented Item 4.1 (hot words onto the Stadium, §25.5) needs word patrons to carry a Stadium `heat` share. The obvious move — `q48_from_u64(execution_heat)`, a pure representation change @@ -1108,15 +1108,51 @@ resolves four of the five questions this section left open: today; the counter keeps incrementing and decaying exactly as today. Stadium `heat` is not part of `dict_hash` and item 4.1 does not need to add it there. No baseline discontinuity, no "before vs. after" comparison problem — there is nothing to reconcile. -3. **What transfers, from whom, on every dispatch — and its cost.** **Still open.** This is - real work, not dissolved by the reframing above: on a kernel-build word dispatch, some - Q48.16 amount has to move toward the touched word's cell from the rest of the VM's - resident pool — the same shape as `vm_physics_transfer()`'s clamped subtract/add at the - fleet level, or the proportional redistribution at `capsule_vm_physics.c:334`, but nobody - has picked the exact rule or measured its cost on this path yet. This sits in both the - hosted and kernel dispatch primitive today (`physics_execution_heat_increment()`), but - under the resolution below the *new* transfer is kernel-only, alongside — not replacing — - that existing increment. Still blocks item 4.1's code. +3. ~~**What transfers, from whom, on every dispatch — and its cost.**~~ **RESOLVED + 2026-08-05 — a reservoir, not a fan-out, keeping the transfer O(1).** + `vm_physics_touch()`'s proportional pull across every other live VM (`capsule_vm_physics.c + :281-355`) is O(n) over the fleet and is explicitly justified there only because fleet + touches are rare — the file's own comment (`:47-49`) contrasts "dozens to low hundreds" of + fleet touches against word executions "in the millions." Copying that shape for words is + not viable. + + Instead, each VM's inner Stadium gets one additional scalar — the **reservoir** — holding + whatever heat is not currently claimed by a resident patron. All word-heat transfers are + two-party, against the reservoir, mirroring Hera's structural role at the fleet level (a + single fixed point that absorbs and donates) rather than the fleet's peer-to-peer fan-out: + - **Touch** (dispatch of an already-resident word): pull a fixed Q48.16 quantum from the + reservoir into the word's cell, clamped at what the reservoir holds. O(1). + - **Cooling** (Loop #3's decay shape, redirected): return heat from the cell to the + reservoir instead of letting it vanish. O(1) per word, same as today's independent decay. + - **Eviction:** the cell's *remaining* heat must flow back to the reservoir before the cell + returns to the free list (item 3.7) — otherwise conservation breaks on every reap. + - **Quantum size:** a Kconfig constant, not an inferred rate. The fleet needed a + statistical estimator (`VMFleetWindow`, `vm_physics_tick()`) because its touches are rare + and irregular; words already have a simpler precedent — `execution_heat`'s existing + per-dispatch increment is a flat `+1`, not tick-scaled. Mirroring that shape avoids a + second estimator. Actual tuning is DoE work (item 5.1), not decided here. + + **Admission is the starter grant, by explicit choice (2026-08-05) — `execution_heat` + plays no role.** A non-resident word has no cell, so its density is 0 and it can never win + item 3.5's "denser than the least-dense resident" comparison on its own. Two shapes were + weighed: (A) gate admission attempts on `execution_heat`'s existing threshold crossing — + free, since the increment already happens, but makes `execution_heat` the promotion + governor in kernel builds, directly against the "one governor per build" rule below; or + (B) every dispatch of a non-resident word requests a fixed starter quantum from the + reservoir and is admitted iff that quantum's density beats the current least-dense + resident — `execution_heat` stays fully inert, matching the rule as already committed. + **Chosen: (B).** + + **Accepted cost, not a new gap:** `stadium_admit()` (item 3.5, `DONE`) already documents + its own two full-array scans as O(N), deferred until the free list lands (`FABRIC.md + :2971-2974`, "not worth a workaround for code with no caller yet"). Option B calls that + O(N) path on every dispatch of every non-resident word — the same "check on every touch" + shape the old hot-words cache already used (`hotwords_cache_lookup()` in the word-lookup + path, §17.3), but each check costs O(N) here instead of the old cache's O(1) + threshold-plus-round-robin-write. This is deliberately paid for now, in exchange for + keeping `execution_heat` inert, and is expected to resolve the same way item 3.5's own + scans do — when the free list (item 3.7's per-VM free-list heads) replaces the full-array + scan. Not a new item; it collapses into item 3.5's already-recorded debt. 4. ~~**What happens to `dict_hash` and parity comparisons that predate this change.**~~ **RESOLVED by #2 above** — nothing predates a change that isn't being made to the hashed field. @@ -1124,14 +1160,22 @@ resolves four of the five questions this section left open: The Stadium engine (§18) already owns a conserved heat wire per cell; item 4.1 populates that existing wire for the word patron kind. It is not a new loop and needs no name. -**What sums to what, and admission semantics — settled by code already written, not -re-derived here:** per-VM Stadium, summing to 1.0 across every patron resident in that VM's -quota (words, blocks, ACLs, messages together, not a word-only sub-pool) — matching §21.4's -"K conserved here, independently" and the `stadium.h:64` field comment. No reset on -admit/evict: the sum stays invariant across *any* call per §19.1's own conservation check, so -admission is itself a transfer, not a reset — mirroring `capsule_vm_physics.c`'s VM-birth -pattern, where a new patron starts at 0 (or a starter share) and is topped up by transfer, -never by manufactured heat. +**What sums to what, and admission semantics — settled by code already written, plus the +reservoir above:** per-VM Stadium, one pool per VM (words, blocks, ACLs, messages together, +not a word-only sub-pool) — matching §21.4's "K conserved here, independently" and the +`stadium.h:64` field comment. **Correction to this section's 2026-08-05 earlier wording:** +the invariant is not "residents sum to `Q48_ONE`" — the reservoir holds whatever residents +haven't claimed, so the correct invariant is + +> Σ(resident patron heat) + reservoir == `Q48_ONE` + +checked the same way `vm_physics_conserved()` checks the fleet sum, epsilon-bounded. No reset +on admit/evict: the total stays invariant across *any* call, so admission and eviction are +transfers against the reservoir, never a reset — mirroring `capsule_vm_physics.c`'s VM-birth +pattern (a new patron starts at 0, topped up by transfer) with the reservoir playing Hera's +role: at VM-Stadium-quota-grant time, before any word patron is resident, the reservoir holds +the VM's entire share, exactly as Hera holds the fleet's entire `Q48_ONE` before any other VM +is born. **One governor per build — states explicitly what closes the §11/§25.5 "never two live heat mechanisms" gap:** @@ -1146,20 +1190,24 @@ mechanisms" gap:** This is the same per-build split §17.3 already ruled for the cache itself; word-level heat conservation follows it rather than inventing a second shape. -**Open, to settle before item 4.1 writes any transfer code:** + **Open, deferred honestly rather than blocking:** -- The exact per-dispatch transfer rule (pull from a proportional share of current residents? - from a designated "unclaimed" pool representing non-resident dictionary words? something - else) and its measured cost on the dispatch path, kernel build only. +- The quantum size (Kconfig constant, §17.7 bullet 3 above) has no value yet — tuning is DoE + work (item 5.1), not invented here, same treatment as `STADIUM_MEMORY_PERCENT` (item 3.2). - Whether `rolling_window_seed_hotwords_cache()`'s POST warm-start (`rolling_window_of_truth.c:786`) needs a Stadium-side counterpart to seed word patrons' - initial `heat` distribution — noted here as the natural seeding site, not designed. + initial `heat` distribution from the reservoir — noted here as the natural seeding site, + not designed. Item 4.1 may ship without it; POST warm-start of the *old* cache is unaffected + either way since it writes `execution_heat`, which stays untouched. +- `stadium_admit()`'s O(N) scans (item 3.5's own recorded debt) are accepted cost for word + admission under Option B, not re-litigated here. They resolve when the free list (item 3.7) + supersedes the full-array scan — tracked at item 3.5, not a new item. -**Nothing in this section authorizes touching the per-dispatch transfer rule, or wiring -`stadium_admit`/`stadium_dispatch` for words, before the open bullet above reads DECIDED. -`execution_heat`'s current increment/decay behaviour and `dict_hash` are explicitly out of -scope for item 4.1 — this section's earlier ban on touching them no longer applies, because -nothing here proposes touching them.** +**This section now authorizes item 4.1 to wire word patrons onto the Stadium using: the +reservoir-based O(1) touch/cool transfer, Option B's starter-grant admission (no +`execution_heat` involvement), and the corrected invariant above. `execution_heat`'s current +increment/decay behaviour and `dict_hash` remain explicitly out of scope — nothing in item +4.1 touches either.** ---