Extends the existing lexicon (which already covered heat/decay/inference vocabulary but predated Stadium work entirely) with patron, mass, density, K, cell, code field, Stadium, warehouse, utility -- all cited to their FABRIC.md DECIDED sections. Adds a Kconfig-knob-to-concept table with verified wiring status, flagging STADIUM_CAPACITY_TICK as dead (matches this session's §12 Q5 finding). Bumped to v1.1.
26 KiB
FABRIC-2.md — the Stadium, continued
Status: Living working document, opened 2026-08-12 as the successor to FABRIC.md
(now closed/archival — see its own header). This document does not repeat FABRIC.md's
design argument or history; it restates only outcomes, with pointers back to the section
that derived them. Read FABRIC.md for the "why," this document for the "what's left."
Provenance. Everything below came from a full, non-sampled read of the entire 7,595-line
FABRIC.md on 2026-08-12, looking for anything not yet resolved: unchecked punch-list
items, the architectural "### N.N Open" subsections in §1–24, the §25.7 "reported, not
scheduled" list, and any other "not yet"/"deferred"/"still open" language in the document.
Item numbers below are carried forward unchanged from FABRIC.md where one already existed,
for traceability — this is not a renumbering.
How to use this document going forward. New findings, new punch-list items, and new
decisions get added here, not to FABRIC.md. Follow the same discipline FABRIC.md §25.0
established (read it there — it isn't repeated here) for how work gets picked up, closed,
and recorded.
A. Blocked or scoped, not started
-
1.11 — Dirty-event granularity. Leaning region-based. Blocked on item 4.3 — settled as part of the console migration, not speculatively before it. Refs (FABRIC.md): §17.5, §23.2, §23.4 #1.
-
4.3 — Console. Umbrella item; settles 1.11 as part of the work. Nearly everything under it (4.3.1–4.3.7f, 4.4–4.4ac) is done — the parent stays open only because 4.4s below is still blocked and nothing has formally closed the umbrella. Refs (FABRIC.md): §17.5, §27.
-
4.4s —
(user)prompt segment. Scoped, blocked, not started. Extends 4.4's prompt format. Refs (FABRIC.md): §27.8, 4.4. -
4.6 — Artemis last. It works today; it is the thing that cannot be broken. Not started.
-
5.1 — Re-run the DoE on the new substrate. A green POST suite is not evidence that determinism holds under the Stadium migration — needs its own campaign. Not started.
-
5.2 — Isabelle/HOL. One datatype, one index space, one conservation theorem. Not started.
-
5.3 — Shrink the subsystem documents.
ARTEMIS.md,HERMES.md,CONSOLE.md,TRIPOD.mdneed trimming now thatFABRIC-2.md(viaFABRIC.md) is the design-of-record. Not started. Specific pending edit found within this item:TRIPOD.md's own Immediate Goal section still describes Hera spawning Hermes and Artemis automatically at boot — item 0.1 undid that behavior, and the doc was never updated to match.
B. Live and unmeasured — deferred while Tripod was pruned to Hera-alone, now genuinely live since item 4.2 restored Hermes
-
Fleet heat leak. Integer-truncation drift in
vm_physics_touch()(FABRIC.md §20.2) was explicitly noted as invisible with only Hera resident, and "becoming measurable the moment Phase 4 restores Hermes/Artemis." Phase 4 has now restored Hermes (item 4.2). This is the single most concrete open item carried into this document — real, live, and unmeasured.INVESTIGATED 2026-08-13 — §20.2's truncation claim does not hold; no live drift risk exists today. Full read of
capsule_vm_physics.c: every write toexecution_heat_q48goes through exactly one ofvm_physics_init()(birth),vm_physics_transfer()(:176-183), orvm_physics_retire()'s zero-out after transfer (:277) — confirmed via a repo-wide grep, nothing else touches the field.vm_physics_transfer()moves an identical amount offfromand ontotoin the same call (from -= moved; to += moved), unconditionally. The touch() fan-out (:334,share = (moved_total * heat) / others_total) truncates per-VM, sosum(shares) < moved_total— but eachshare, truncated or not, is what actually leaves its VM and what actually arrives at the touched VM, via the samevm_physics_transfer()call. A worked example: others_total=10 (A=5, B=5), moved_total=7 → share_A=share_B=3, sum is 6 not 7. Fleet sum before = 10+target; after = (2+2)+(target+6) = 10+target. Identical. The truncation is a rate-fidelity shortfall (the touched VM pulls in less than the physics model intended) — it cannot move the fleet-wide sum, because nothing is ever subtracted from one place without the identical amount landing at exactly one other. This matches the header's own contemporaneous claim (capsule_vm_physics.h:31-33): conservation is "held by construction: every state change is a balanced transfer." Git history rules out a since-fixed bug:vm_physics_transfer()and the touch() loop are unchanged since the file's creation (a5ed8c3, 2026-08-01), predating §20.2's correction (055e944, 2026-08-03) — the claim was made about the exact code read here, not an earlier version of it.The one path that can genuinely drop heat is
vm_physics_retire()'s guarded "no valid root" case (:271-275) — the dying VM's remainder has nowhere conservation-preserving to go if it IS the root, or its parent chain is broken. Both are structurally excluded today:capsule_vm_killrefuses to kill Hera, andparent_vm_idis set once at birth and never rewritten, so a well-formed chain can't corrupt itself. Real in principle, unreachable under current invariants — tracked separately in §C below rather than as a live measurement task.FABRIC.md §20.2 itself should eventually be corrected to match (currently still asserts the truncation causes monotonic drift); not done here to keep this document's discipline of not editing
FABRIC.mdfurther. -
Multi-VM heartbeat ownership. Whose
tick_target_nsdrives the one physical timer (FABRIC.md §26.5) has only one answer while Hera is the only VM; explicitly "not resolved for when Hermes/Artemis return." Hermes has returned (item 4.2). Same situation as the fleet heat leak above — a deferral whose triggering condition has since become true. Ruled: there is exactly one physical timer, so exactly one VM may write its re-arm period — never whichever VM'svm_tick()happened to run last. Hera is the fixed point everywhere else in this design (patron zero, sole capacity arbiter, sole birther/killer of VMs — see FABRIC.md §20.2, §26), so she is the sole owner here too. Every other VM's Loop #7 (vm_tick_inference_engine()) still adapts its ownvm->heartbeat.tick_target_nsexactly as before — that per-VM bookkeeping is correct and untouched — it simply never reaches the shared physical re-arm; only Hera's does. Done when:vm_tick_inference_engine()gates the physical-timer re-arm behindvm_uuid_is_hera(vm->stadium_vm_id), and a three-arch QEMU boot with Hermes live (item 4.2) is clean. Refs (FABRIC.md): §20.2, §26.5.DONE 2026-08-13.
vm_tick_inference_engine()(src/starkernel/vm/vm_runtime.c:766) now wraps the kernel-base re-arm block inif (vm_uuid_is_hera(vm->stadium_vm_id)), using the existingvm_uuid_is_hera()(src/starkernel/capsule/vm_uuid.c:51) already used the same way at the birth/kill call sites incapsule_birth.candcapsule_vm_physics.c. Three-arch acceptance boot, all clean took>, Hermes live and self-testing in every log:logs/20260813-075940/amd64,logs/20260813-080125/aarch64,logs/20260813-080257/riscv64. Committedbcc72d0.
C. Reported bugs and dead code, not yet fixed
-
vm_physics_retire()'s dropped-remainder case. (capsule_vm_physics.c:271-275) If the dying VM is itself the fleet root, or its parent chain is broken, its remaining heat has nowhere conservation-preserving to go and is zeroed instead of transferred — a genuine, if narrow, violation of thesum(execution_heat) == Q48_ONEinvariant. Found while investigating the "Fleet heat leak" item above, 2026-08-13.CORRECTION, same day: the "structurally excluded" framing this item originally had was wrong — the Hera case was reachable.
mama_word_kill()(mama_forth_words.c:520-530) calledvm_physics_retire(entry.vm_id)for any live-state VM found by name, with no Hera check, beforecapsule_vm_kill()'s own Hera guard (capsule_birth.c:313-316) ever ran.S" Hera" KILLtherefore hitvm_physics_retire()with Hera's own id; her self-referentialparent_vm_idmadevm_physics_find_root_id()return her own id immediately,rootcame backNULL, and the fleet's entireexecution_heat_q48sum was silently zeroed — with only the harmless-looking "cannot kill Hera" message as visible output, since that guard runs second. The broken-parent-chain half of this item remains genuinely unreachable (parent_vm_idis set once at birth and never rewritten); only the Hera half was live.DONE 2026-08-13. Fixed by excluding Hera from the retire call at the one call site, mirroring
capsule_vm_kill()'s existing guard:mama_forth_words.c'scapsule_vm_find_by_name_nocase(...) && entry.state == VM_STATE_LIVEcondition gained&& !vm_uuid_is_hera(entry.vm_id). No change tovm_physics_retire()orcapsule_vm_kill()itself. Three-arch acceptance boot, all clean took>:logs/20260813-083429/amd64,logs/20260813-083551/aarch64,logs/20260813-083738/riscv64. Committeddb66406. -
arch_mmu_init()— TODO, not accidental dead code. Declared (arch.h:73) and defined on all three architectures (amd64/arch.c:246,aarch64/arch.c:169,riscv64/arch.c:179) but never called anywhere — confirmed by repo-wide grep, 2026-08-13. Each definition is a real doc-commented no-op stub ("paging will be wired up in later milestones" / "MMU bring-up deferred" / "Sv39/Sv48 bring-up deferred"), and each comment describes it as satisfying "the commonarch_mmu_init()call site shared across all three ISAs" — a call site that does not exist anywhere in the tree today.vmm.c(M3) handles page-table management independently and does not need this hook to function. Captain Bob's call, 2026-08-13: leave the three stubs in place as scaffolding for a future per-arch MMU milestone rather than deleting or wiring in a no-op call site now — ruling recorded, closed as investigated/not-a-bug rather than left open. Found during item 4.3.5a. -
include/block_subsystem.h:168'sencodingfield (ASCII/UTF-8/binary) is dead metadata — nothing reads or writes it. (FABRIC.md §27.6) Investigated 2026-08-13, scope turned out wider than the field itself:blk_get_meta()/blk_set_meta()(src/block_subsystem.c:881,903) are the only public API for the wholeblk_meta_tstruct and have zero callers anywhere in the tree — not justencodingbutcontent_type,content_length,owner_id,permissions,acl_block,signature,entropy,hash, the chain fields, andapp_data[15]are all unused. The struct is a real fixed on-disk byte layout (packed 341 bytes/block intoMETA_REGION_OFFSET,src/block_subsystem.c:69-71), serialized viameta_to_slice()/meta_from_slice()during cache writeback/load — so it isn't free-standing dead code to delete, removing a field would shift every subsequent field's on-disk offset. Ruling (Captain Bob, 2026-08-13): flag and leave as-is — this metadata subsystem is scaffolding expected to get consumed once Artemis's design (content-typed/owned/ACL'd blocks) is completed, not a bug to fix now. -
tools/README.mddocuments afbtest.cexample that does not actually exist intools/— stale-doc discrepancy. Confirmed 2026-08-13, and the discrepancy is two-sided:git log --allshowsfbtest.cwas never added to the repo at any point — the table entry describes a tool that was documented but never built. Meanwhiletools/ttftest.c(added5f6cc05, item 4.3.7, TrueType parser core test) is a real tool that exists on disk but has no README table entry at all. Left unfixed pending explicit instruction (README edit would be a fix, not just an investigation). -
hotwords_cache_promote()has a NULL-write bug on a full cache. Confirmed 2026-08-13: the full-cache branch (src/physics_hotwords_cache.c:362-371) writeswordintocache->cache[cache->lru_index]unconditionally oncecache->cache_count >= HOTWORDS_CACHE_SIZE— it does not checkwordfor NULL on that path, unlike the slot-available branch below it, which does. A NULLwordwould silently evict a live cache entry and replace it with NULL. Confirmed unreachable in practice: all four call sites (physics_hotwords_cache.c:284,physics_execution_hooks.c:140,rolling_window_of_truth.c:787,src/starkernel/vm/vm_core.c:731) only callhotwords_cache_promote()after aDictEntry*NULL check has already passed. Ruling: flag and leave as-is, matching thearch_mmu_init()/block_subsystem precedent — real defect, no live trigger, not fixed without explicit instruction. -
heartbeat_trust()has zero callers — dead code. Stale claim, corrected 2026-08-13:heartbeat_trust()(src/starkernel/heartbeat.c:205) is called fromsrc/starkernel/kernel_main.c:913, printed as thetrust=0x...field of the M5 heartbeat boot-milestone diagnostic line, alongside tick count and variance. Not dead code — closed as investigated/not-a-bug. -
m5_time_trust/m5_varianceare declared and never used. Confirmed 2026-08-13: repo-wide grep for both names turns up only their own declarations (include/vm.h:315-316) — no reader, no writer, anywhere. Genuinely dead struct fields. Ruling: flag and leave as-is, same precedent as the other Section C dead-code items — not removed without explicit instruction. -
src/*.c.bakfiles (vm.c.bak,doe_metrics.c.bak,inference_engine.c.bak) remain tracked in git atsrc/top level. Repo hygiene debt, not intentional (also noted in.claude/CLAUDE.md). -
bump-z/bump-yMakefile targets referenceSTARFORTH_VERSION_MAJOR/MINOR/PATCH/STARFORTH_VERSION_STRINGfields that don't exist in the actual generatedinclude/version.h(also noted in.claude/CLAUDE.md). -
Kconfig/
menuconfighas never been exercised end-to-end. Every knob added so far (including item 4.1'sSTADIUM_WORD_HEAT_QUANTUM/STADIUM_WORD_COOL_RATE_Q48) has only ever been verified via itsMakefile.starkerneldefault. Nobody has runmake -f Makefile.starkernel menuconfig, changed a value, and confirmed it flows through to a build. Flagged by Captain Bob 2026-08-05.DONE 2026-08-13. The vendored
tools/kconfigbuild had never actually succeeded on this machine —bison/flexwere missing, installed with Captain Bob's go-ahead.mconfneeds an interactive TTY this environment doesn't have, so the exercise usedconfdirectly (same underlying enginemenuconfigcalls) rather than the ncurses frontend itself.make -f Makefile.starkernel ARCH=amd64 kernel_amd64_defconfiggeneratedbuild/amd64/.configcorrectly (CONFIG_SK_PARITY_DEBUGunset, matching Kconfig'sdefault n). Hand-editing that line toCONFIG_SK_PARITY_DEBUG=yand re-running the kernel build showed the real compile line forsrc/starkernel/vm/parity.cgained-DSK_PARITY_DEBUG=1(was=0), and the object compiled clean with it; reverting the line back to unset flipped the flag back to=0. Confirms themk/Kconfig.mkbridge andkconfig_boolmacro genuinely wire a.configchange through to-Dflags in both directions — this had never been verified before. One caveat found in passing, not itself a bug: invokingtools/kconfig/confdirectly (bypassing the Makefile) writes a stray.config+include/config//include/generated/at the repo root instead of underbuild/, since it doesn't pick up theKCONFIG_CONFIG/etc. env varsmk/Kconfig.mkexports — only matters if invokingconfby hand outside the Makefile targets, as done here; cleaned up before committing. No.config/build/artifacts committed — both are gitignored working state.
D. Design questions still genuinely open
-
§12 Q5 — the full eight-loop interference analysis has not been done. Its stated blocker ("no real time base on all three ISAs") is gone — Phase 0 completed that — so this is now unblocked and simply never picked back up.
ANALYSIS DONE 2026-08-13 — found a real violation of §22.4's required ordering, left open pending Captain Bob's call on how to handle it. Traced every loop's actual firing cadence from source (
src/starkernel/vm/vm_core.c,vm_runtime.c,capsule_vm_physics.c), in execution counts, not the nominal per-loop constant names:- Cadence 1 execution: Loop #1 (heat), Loop #2 (rolling-window record), Loop #4 (pipelining) — every word dispatch.
- Cadence
HEARTBEAT_CHECK_FREQUENCY= 256 executions: Loop #3 (background decay, batched),rolling_window_service(),dict_adaptive_optimization_pass(), L8 Jacquard vote, fleet-capacity-counter increment — all five in the samevm_heartbeat_run_cycle()call, zero separation between them. - Cadence
HEARTBEAT_INFERENCE_FREQUENCYticks × 256 = 256,000 executions: Loop #5 (window width) + Loop #6 (decay slope) + Loop #7 (adaptive heartrate) — all three merged into onevm_tick_inference_engine()call, zero separation between them.
Two real findings, not just tidiness:
- The fleet-capacity loop can fire faster than a VM's own heat loop, not slower —
the opposite of §22.4's "capacity must be the slower loop." §22.4 cites
vm_physics_heartbeat_tick()(capsule_vm_physics.c:454-459) by name as the existing precedent for the required 1000:1 separation. But that function is called from every live VM's ownvm_heartbeat_run_cycle()(vm_runtime.c:502), incrementing one shared globalfleet_heartbeat_tick_count, and firesvm_physics_tick()once that shared counter — fed by all VMs at once — reachesHEARTBEAT_INFERENCE_FREQUENCY(1000). A lone VM needs 1000 of its own 256-execution cycles to trip its own inference gate; withSTADIUM_MAX_VM_COUNT's real default of 4 (Hera + 2×Hermes + Artemis, Tripod's actual topology), the shared fleet counter can reach the same threshold up to ~4× sooner in wall-clock terms. So in a live multi-VM fleet, capacity arbitration can run more often in real time than any individual VM's own heat-inference loop, not less — exactly the ordering §22.4 calls "required, not preferred" because getting it backwards "produces a system that thrashes while every individual rule looks correct." - The fix §22.4 already specified was never actually built. §22.4's own resolution
text says the capacity-tick should get "its own named constant rather than literally
sharing
HEARTBEAT_INFERENCE_FREQUENCY... named and made a Kconfig symbol at implementation time (item 3.1)."STADIUM_CAPACITY_TICKexists inKconfig.kernelexactly as promised, default 1000 matching the cited precedent — but repo-wide grep confirms it is never read by any code path. Its only other appearance is a compile-time static assertion ininclude/starkernel/vm/stadium.h:118checking it's> 0, which gates nothing at runtime.vm_physics_heartbeat_tick()still literally sharesHEARTBEAT_INFERENCE_FREQUENCY, i.e. the code is still in the pre-implementation state §22.4 described as its starting precedent, not the resolved design it specified.
Also found in passing, minor and not a correctness issue:
vm_tick()'s own header comment (vm_runtime.c:104, "Aggregates all periodic optimization tasks (Loop #3 and Loop #5) into one place") is stale — Loop #3 is not called insidevm_tick(), it's called separately fromvm_heartbeat_run_cycle()on the faster 256-execution cadence, not the 256,000-execution onevm_tick()itself gates.Where this leaves Q5: within a single cadence tier (the 256-execution cluster, or the 256,000-execution cluster), several nominally-distinct loops share one gate with zero separation between them — apparently intentional unification (Phase 2's "Unified Inference Engine" merged #5/#6, L8 explicitly reads #1–#7's latest outputs each vote), not obviously a bug. Between tiers, separation is comfortably past the order-of-magnitude minimum (256:1 and 1000:1). The one place separation is required by name and is not actually present is the heat/capacity pair finding #1 above — that's the concrete, verified answer to "the risk is interference." Left open, not fixed, per this document's discipline — whether to wire
STADIUM_CAPACITY_TICKin for real (giving Hera her own slower cadence independent of per-VM tick counts) or something else is Captain Bob's call, not made here. -
§17.4 — the framebuffer utility's internal heat/decay dynamics are undesigned. Explicitly "Open, deferred": not a Stadium patron, but what physics (if any) governs it internally was never designed. Not blocking anything.
CHECKED 2026-08-13, still correctly not ripe — precondition refined, item left open. The actual FABRIC.md text (end of §17.5, not really §17.4 — that section number is a loose reference to the same "OPEN, deferred" paragraph) is explicit: "What those dynamics are is a question for when the framebuffer work actually happens... it should not be designed speculatively now." The console/framebuffer work (item 4.3/4.4 series) has since substantially shipped and is live, which looks at first glance like that precondition is now met. It isn't: grepped
src/starkernel/hal/framebuffer.c,vt100.c,console.c(1,800 lines total) forheat/decay/dirty— zero hits. Every write is immediate-mode (fb_scroll_rows(),fb_scroll_rect(), direct glyph blits at the point of writing); there is no damage list, no dirty-region tracking, nothing a per-region heat/decay scheme would even attach to yet. That's consistent with Section A's item 1.11 (dirty-event granularity) still being unstarted — this item's true prerequisite is 1.11, not "the framebuffer work" in general as originally framed here. Designing framebuffer-internal physics before the dirty-event mechanism they'd govern exists would be exactly the speculative work FABRIC.md warns against. Left open, framing corrected: blocked on item 1.11, not merely deferred. -
§23.4 #2 — cell-size validation against a real message shape. "Check that a typical message still fits in one cell" was never done — item 3.1's own done-note confirmed no message-patron struct existed yet at the time. This is now checkable: item 4.2 built Hermes's actual message/channel structures on the Stadium, so the 32-byte inline payload can finally be validated against something real instead of a hypothetical.
SETTLED 2026-08-13, but not as originally framed. Read Hermes v1's real message struct (
capsules/hermes/init.4th, blocks 4100/4105/4143, item 4.2):MSG-CELLSis 9 cells —TYPE, FROM, TO, PADDR, PLEN, STADIUM-CELL, SEQ, CH, ORIG-TYPE— at 8 bytes/cell (cell_tisint64_t/long,include/vm.h:71-75), i.e. 72 bytes, already over §23.3's whole proposed 64-byte cell before any payload. And the payload itself is not inline at all:MSG-SEND(:208-213) takes a caller-suppliedpaddr plen— wherever the caller already put the bytes (a string literal viaS", a block buffer) — and stores only the pointer and length in the struct. There is no fixed-size inline payload field to check a message against. So §23.4 #2's literal question ("does a typical message fit in the 32-byte inline payload") is moot, not answered: the shipped design didn't adopt §23.3's speculative 64-byte-cell/32-byte-inline-payload scheme for messages at all, it used a larger fixed control block plus out-of-line pointer/length indirection instead. Closing as settled-by-divergence — the real implementation went a different direction than the design question assumed, which is itself the answer; §23.3's cell-size numbers were never made load-bearing for Hermes messages and nothing here depends on reconciling them.
E. Documentation debt
-
Taxonomy / glossary. Captain Bob's own flag, 2026-08-04: the physics vocabulary (heat, mass, density, patron, Stadium, and Kconfig knob names built on it) needs an explicit glossary stating these are named analogies, so code, Kconfig, and documentation terminology don't drift apart over time. Never scoped into any phase. Real, acknowledged debt: "I guess that we didn't finish out FABRIC.md quite as much as we thought."
DONE 2026-08-13.
ONTOLOGY.mdalready existed and already did exactly this job for the original heat/decay/inference vocabulary (Section III's lexicon, Section 3.2's "avoid/deprecated terms" table) — it just predated the Stadium work entirely (confirmed: zero mentions of "Stadium," "patron," "mass," or "density" before this edit) and was never extended to cover it. Added new §IX "Stadium/Fabric Vocabulary" in the same format as the existing lexicon: a metaphor-mapping table, alphabetical definitions for Cell, Code field, Density, K, Mass, Mass-vs-heat-conservation, Patron, Reap, Stadium, Warehouse, and Utility (all cited back to theirFABRIC.mdDECIDED sections, not invented), plus a Kconfig-knob-to-concept table with verified, not assumed wiring status for everySTADIUM_*symbol — including flaggingSTADIUM_CAPACITY_TICKas dead, the same finding this document's own §12 Q5 closure made independently, now cross-referenced from the glossary too. BumpedONTOLOGY.mdto v1.1 with a version- history entry. No FABRIC.md/FABRIC-2.md content moved — this is a new, additive section in the document that already owned this job. -
ACL-RWT DoE overhead re-measurement. The measured overhead numbers in
.claude/CLAUDE.md("+0.0054%–+0.0088%") were all captured at-O0, before item 4.5 enabled real compiler optimization. Nobody has re-measured, or even confirmed the old and new numbers are comparable at all. Flagged in passing during item 4.5f, never formally scoped.