bbfd9103f4e53ce95a9d16f79977ae4d43f8d2a4
723
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bbfd9103f4 |
Stage C: cut over BLK-ATTACH-EVENT alone -- FABRIC-3.6.md task 3.8
The reply leg (Artemis -> Hera ack) that used to flow through common:messaging.4th's MSG-SEND/MSG-TICK now goes through kernel-Hermes's sk_hermes_send_one()/sk_hermes_drain_checkpoint() instead -- FORTH Hermes never sees a BLK-ATTACH-EVENT message again (SXXXIV.2's partition rule). The request leg was never real FORTH messaging traffic to begin with (a direct VM-EXEC, no type tag, forced by Hera's own inability to load common:messaging.4th), so it is untouched. New KH-BLK-ATTACH-SEND (repl.c) wraps sk_hermes_send_one(), reached from capsules/artemis/init.4th's HERA-BLK-ATTACH-REQ. Delivery reuses task 3.4's already-wired sk_hermes_drain_checkpoint(); BLK-ATTACH-ACK itself is unchanged, just reached by a different layer. SK_HERMES_MSG_TYPE_BLK_ATTACH deliberately reuses BLK-ATTACH-EVENT's own value (9) to document this as a cutover of the same message, not a new one. Two real bugs found on the way, both recorded in FABRIC-3.6.md's findings log: - A popped FORTH CREATE-buffer address was raw-cast to a host pointer instead of going through vm_ptr() -- silently read all-zero memory, no crash, no error, just a message that arrived and did nothing. Fixed; the rule and its exception (repl.c's own dev-addr is legitimately a raw pointer, formatted that way by its own pushing code) are written up for the next FORTH-facing C word. - A separate, genuine hang on the very first live exercise of this path, never reproduced across ten subsequent boots. Reported, not chased -- not blocking, per the task's own check being otherwise fully satisfied. Also found live: log_message() is invisible in this build's actual serial-log capture at every level -- settled on a single console_println in the real drain target instead, one line per real USB attach, not a hot-path. Final acceptance (logs/20260922-105501, -105758, -110304, disk images reset before each): dict_hash identical across all three architectures for every VM, zero UNKNOWN WORD, mkcapsule --lint clean, real ledger+stadium_conserved(Artemis)=true evidence on every boot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
f37aa0fb17 |
Channel-open policy hook -- FABRIC-3.6.md task 3.7
Added HERMES-CHANNEL-OPEN? ( req-hi req-lo -- allow? ) at capsules/ACL.4th block 4008 (default: approve everything) -- the one word policy authors edit. sk_hermes_channel_open_policy(VM*, VMUuid) (kernel_hermes.h/.c) is the C-side query that calls it via plain word-dispatch against the target VM's own dictionary/stack, never vm_interpret() (avoids task 3.4's input-buffer cursor hazard entirely) and never decides the answer itself. Fails closed: no policy word, a policy error, or stack underflow all deny, matching CLAUDE.md's posture that absence of policy must never mean "always allow." Two real bugs found and fixed before this was called done: missing current_executing_entry assignment before calling the word's func pointer (colon words silently no-op without it, vm_core.c:730 -- no crash, just a wrong answer); and a second FAIL with debug instrumentation still in place whose precise cause isn't reconstructable, since no intermediate commit exists for that attempt. Self-test proves the task's check four ways against the same unchanged C function: default approve, live redefinition to deny (zero C change), restore, and a VM with no ACL.4th loaded at all (fail closed). A fifth check wires the result into task 3.6's sk_hermes_channel_respond() end to end: a denied policy produces a NACK and no channel, ledger/stadium_conserved() holding throughout. Scope, per Captain Bob's ruling: closes with the query built and proven; sk_hermes_channel_respond() still takes a caller-supplied approved bool rather than calling the policy internally. Wiring a real channel-open call site to only this query is deferred to whichever later task first needs a live decision. dict_hash identical across amd64/aarch64/riscv64 for every VM, zero UNKNOWN WORD, mkcapsule --lint clean (38 files, 0 violations). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
205a49ecd0 |
ACK/NACK and private-channel negotiation -- FABRIC-3.6.md task 3.6
Extracted sk_hermes_send_one() from sk_hermes_publish()'s own
per-subscriber body -- one code path for both point-to-point and
fan-out delivery, so the ledger can never diverge between them.
Point-to-point addressing turned out to be load-bearing, not
incidental: sk_hermes_publish()'s fan-out sets msg->to to whichever
member it is iterating, so a negotiation message "published" to the
common channel would spuriously reach every member, not just the real
target (checked with advisor() before building the naive version).
"Over the common channel" means every VM is reachable from birth (task
3.2), not that the exchange itself fans out -- messaging.4th's own
CH-REQUEST carried an explicit `to` for the same reason.
sk_hermes_channel_request/respond/close build the mechanics: request ->
grant (creates a private channel, subscribes both parties, sends
CH_GRANT + one ACK) or NACK ("a deny is a NACK", SXLV.1 -- no separate
type); close authorized by membership alone. The grant/deny decision is
a plain caller-supplied `approved` bool -- task 3.7 replaces the call
site that produces it with a real ACL.4th query, not this signature.
Self-test covers the task's own three checks plus a sibling advisor()
flagged: an approved respond() whose channel creation itself fails
(table exhausted) must still fall through to NACK, not a silent false
grant or half-open channel -- verified by exhausting the whole channel
table and confirming the fallback.
Bug found and fixed before this was called done: the first draft
dropped a message via pending_pop() alone, without releasing it first,
leaking its Stadium heat and failing the self-test's own ledger
baseline check (logs/20260922-065946/amd64/, kept as audit trail).
Fixed and re-verified PASS on all three architectures.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
8a2ee0fdad |
Payload bound and chunking -- FABRIC-3.6.md task 3.5
sk_hermes_publish() now enforces SK_HERMES_CHUNK_MAX_PAYLOAD (1024) on every message's payload_len uniformly, chunked or not -- closing the gap task 3.3 explicitly parked. A chunk carrier is [SkHermesChunkHeader][content slice], slice capped at 1024 - sizeof(header) rather than 1024 itself, so every message on the wire satisfies the same one-block bound vm_interpret()'s own drain limit already requires -- a future chunk-aware drain never has to special-case a carrier that can't be handed to vm_interpret() as-is. Deliberately no chunking-sender API: building one would need kernel-Hermes to own chunk-buffer memory with a real lifetime it has no way to track (kept alive until every subscriber drains it). Sending is a loop pattern a caller writes with sk_hermes_chunk_count() + sk_hermes_publish(), demonstrated by this task's own self-test. sk_hermes_reassemble() is pure and memory-agnostic: validates msg_id agreement, exact seq coverage, and per-chunk slice sizes before a single memcpy, with the total length computed once and checked against the caller's buffer once -- never order-dependent on which chunk happens to overflow. Verified live on all three architectures: a 1024-byte payload as one message, a 1025-byte send refused outright with the ledger untouched, and a 3000-byte payload split into 3 chunks, drained, and reassembled byte-exact against the original. dict_hash unmoved and identical across architectures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
2b1ba031a5 |
Drain at the outermost checkpoint -- FABRIC-3.6.md task 3.4
sk_hermes_drain_checkpoint() interprets one queued payload per checkpoint (ruled: one message per checkpoint), reusing sk_vm_at_outermost_interpret() and placed before the switch-signal block in vm_core.c's existing cooperative checkpoint (sk_vm_context_switch() doesn't return until switched back to, so drain must come first or it silently never runs on a switching checkpoint). Amends FABRIC-3.5.md SXLIII.5, caught by advisor() before writing the naive version: "recursive drain is prevented for free" via g_vm_interpret_depth is true but only for same-message re-drain -- it doesn't cover the separate same-VM reentrancy hazard FABRIC-3.md SXX already named for Hera specifically (VMCallState saves rsp/exit_colon/ ecw_nesting only, never input_buffer/input_length/input_pos). Draining calls vm_interpret() on the same vm whose own vm_interpret() call is still paused mid-word at the checkpoint; without saving and restoring the cursor by hand, the enclosing REPL line or LOAD block would be silently truncated. sk_hermes_drain_checkpoint() snapshots and restores input_buffer/input_length/input_pos/mode/error/abort_requested around the call. Not a divergence from the ruling -- cursor preservation is the implementer's own obligation inside the ruled mechanism. Gated behind a system-wide pending-total counter so the common no-message-in-flight case costs one integer read per word dispatch, not a stadium_max_vm_count()-sized queue scan (also flagged by advisor() as a real hot-path cost, not deferred). Verified live on all three architectures: a self-test publishes a real payload to Hermes, proves the depth gate via VM-EXEC-ing an existing harmless colon word into Hermes (genuine nested vm_interpret(), depth 2, must not drain), then drains directly from genuinely-outermost context and confirms exactly one clean drain. dict_hash unmoved and identical across architectures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
1f6343bc03 |
Publish path, no dispatch -- FABRIC-3.6.md task 3.3
sk_hermes_publish() allocates one SkHermesMessage per channel member (heat-cost ruling 2026-09-21: one message per subscriber, funded by the publisher's own reservoir) and enqueues each onto a new per-subscriber SkHermesPendingQueue -- found-or-created lazily by vm_id, sized from stadium_max_vm_count() like the channel/switch tables. Best-effort across subscribers: a failed allocation or full queue skips and rolls back just that one subscriber, not the whole publish -- the natural reading of "ledger and stadium_conserved() hold across N publishes to M subscribers" (the task's own check), not a separate ruling. Dispatches nothing -- sk_hermes_pending_count()/peek()/pop() are the read/drain primitives task 3.4's real checkpoint-driven drain will build on; this task's own self-test uses them directly since no checkpoint hook exists yet. Verified live on all three architectures: pending-queue table sized 50/202/50 slots (tracking the channel table's own per-arch sizing), a synthetic publish self-test (2 publishes to 3 subscribers) confirms exact per-subscriber delivery counts, and ledger/stadium_conserved() invariants hold both mid-publish and after manually draining every queue back to baseline. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
a4afdfa591 |
Channel table + common channel, inert (B1) -- FABRIC-3.6.md task 3.2
Adds SkHermesChannel: a channel is an index into a boot-time, stadium_max_vm_count()-sized table (same sizing pattern task 3.1 established for the switch table -- no separate numeric rule was ruled for this table, so task 3.1's bound is extended directly, flagged as such rather than restated as a new ruling). No name field, mirroring messaging.4th's own nameless CH-ARENA. The common channel (index 0) is created at boot and permanent. Hera subscribes explicitly in kernel_main.c (she is the one VM never born through capsule_birth_baby()); every other VM -- Tripod fleet and future WIREBIND identities alike -- subscribes inside capsule_birth_baby() itself, the single choke point every other birth already passes through. Inert: no publish, no dispatch, no ACK/NACK, no ACL hook (tasks 3.3, 3.6, 3.7). Verified live on all three architectures: channel table sized to 50/202/50 slots (matching switch-signal's own per-arch sizing), common-channel fleet self-test confirms all four Tripod members are members, and a synthetic create/subscribe/unsubscribe/ destroy round-trip against a private topic passes, including refusing to destroy the common channel. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
c19ef365fe |
Dynamic switch table (B2) -- FABRIC-3.6.md task 3.1
Replaces the fixed SK_SWITCH_MAX_SLOTS=16 compile-time array with a boot-time, RAM-derived allocation via a new sk_vm_switch_signal_boot_init(), kmalloc'd to stadium_max_vm_count() entries -- the same pattern session_boot_init() already established for Stadium-derived sizing. Every switch-signal participant is a Stadium VM, so this reuses that bound directly rather than deriving a separate one. Verified live on all three architectures: switch table sized to 50 slots (amd64), 202 slots (aarch64), 50 slots (riscv64) -- all well past the old fixed cap. All three boot to [zuse@Hera] ok> cleanly; dict_hash for Hermes/Hestia identical across architectures, unmoved from pre-task values. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
66ea4a5e74 |
Record task 3.0 rulings (FABRIC-3.5.md §XLVI); close FABRIC-3.6.md task 3.0
Captain Bob ruled all five §XLV.4 sub-items plus task 3.3's heat-cost design point: ACK on channel-open+delivery only; the channel-open ACL hook is a new word in ACL.4th; switch-table sizing mirrors Stadium's stadium_max_vm_count_val; chunks carry (msg_id, seq, is_last); drain one message per outermost-interpret checkpoint; publish costs one message per subscriber. Tasks 3.1-3.7 may now be written precisely. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
f2f7111dff |
Draft Phase 3 task breakdown (3.0-3.11), awaiting review -- FABRIC-3.6.md
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
303b0c7edf |
Record B1/B2/B4 rulings (FABRIC-3.5.md §XLV); clear Phase 3 blockers in FABRIC-3.6.md
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
feace42397 |
Add diagnostic scan cross-check of the Hermes counters -- FABRIC-3.6.md task 2.8
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
2a2bf6eb35 |
Stage B proof: add per-VM consumed term to stadium_conserved -- FABRIC-3.6.md task 2.7
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
313ffc89e6 |
Add exact-equality Hermes ledger self-audit -- FABRIC-3.6.md task 2.6
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
d11eb2e5db |
Add sk_hermes_decay(), ledgering decay into consumed -- FABRIC-3.6.md task 2.5
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
493d410028 |
Add the four ledger counters (held/pulled/returned/consumed) -- FABRIC-3.6.md task 2.4
FABRIC-3.5.md SXL.4's ledger: held == pulled - returned - consumed,
epsilon zero. Added sk_hermes_ledger() (an accessor, not a mutator)
plus four static counters in kernel_hermes.c.
Each counter has exactly one increment/decrement site: held/pulled
both move at sk_hermes_alloc()'s single success path, after every
refusal branch has already returned; held/returned both move at
sk_hermes_release()'s single success path. consumed is declared and
always reads 0 -- its one increment site doesn't exist yet, and won't
until task 2.5 gives decay something to record.
sk_hermes_release() now reads the Stadium cell's live header.heat
immediately before calling stadium_evict(), rather than assuming the
original pulled amount -- stadium_evict() zeroes the header as part of
freeing the cell and its own return value is a success code, not the
credited amount, so this is the only point the true remaining heat is
available. Today this always equals the original Q.SLOT pull; once
task 2.5's decay exists, this is what keeps returned correct without
touching this function again.
Self-test (kernel_main.c) extended: snapshots the ledger before
running so it checks its own deltas, verifies held/pulled grow by
exactly got_n * Q_SLOT on allocation with returned/consumed untouched,
then verifies held returns to its starting value and returned grows by
the same amount on release, and checks the audit invariant itself as a
bonus (task 2.6 formalizes this properly).
Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, dict_hash unmoved. All three print PASS with
identical final ledger: held=0 pulled=65536 returned=65536 consumed=0.
No compiler warnings.
Authorized by Captain Bob ("keep going with rhe 6.5 document").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
2c1dc1753a |
Correct sk_hermes_alloc() to admit a real Stadium patron; add sk_hermes_release() -- FABRIC-3.6.md tasks 2.2 (amended) + 2.3
Real finding, caught before building release on a foundation that
couldn't support it: task 2.2's first cut of sk_hermes_alloc() pulled
reservoir heat but never admitted a real Stadium-floor patron -- just a
local in_use flag. FABRIC-3.5.md SXXXIII.4 item 1 says MSG-FREE-NODE
returns heat "via STADIUM-EVICT", which only means something if
allocation admitted something. SXL.4's own invariant, Sigma(resident
patron heat) + reservoir + consumed == Q48_ONE, cannot balance if held
heat is invisible to every term while held. Flagged to Captain Bob
before proceeding; authorized to correct 2.2 in the same pass as
building 2.3 on top of the fix.
sk_hermes_alloc() now calls stadium_admit() with heat = the pulled
amount, behaviour = STADIUM_BEHAVIOUR_DELIVER (matching messaging.4th's
own SB-DELIVER STADIUM-ADMIT exactly), and identity = the message's own
slot index (matching the FORTH precedent -- caught live in the first
boot of this fix that omitting this made stadium_dispatch()'s existing
DELIVER diagnostic print msg_idx=0 for every message instead of a
distinct value). The returned cell index is stored in the message's
own stadium_cell field. Stadium-floor refusal (independent of reservoir
affordability) rolls back the pull the same way the other refusal
paths already do.
sk_hermes_release() -- the function task 2.3 actually asks for -- calls
stadium_evict() on that cell, which itself returns the departing
patron's remaining heat to its owning VM's reservoir, matching
MSG-FREE-NODE's exact shape. Release does not touch the reservoir
directly.
Self-test (kernel_main.c) extended: keeps every allocated message's
pointer, allocates to exhaustion as before, releases all of them, and
checks the reservoir returns to precisely its starting value.
"Undecayed" is true by construction (no decay/TTL logic exists yet,
task 2.5) -- exact restoration, not approximate.
Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, dict_hash unmoved from task 2.2. All three print
identical PASS arithmetic: reservoir0=65536, reservoir_after_alloc=0,
reservoir_final=65536. No compiler warnings.
stadium_dispatch()'s DELIVER-case console output (one line per
eviction) is pre-existing instrumentation, not new -- confirmed real
and load-bearing per stadium.c's own comment, verbose but expected.
Authorized by Captain Bob ("yes").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
9d129fdb1c |
Add sk_hermes_alloc(), the heat-coupled allocator -- FABRIC-3.6.md task 2.2, item 28
The piece FABRIC-3.5.md SXXXIII.6 calls "what remains genuinely hard,"
built and proven first per its own recommendation. Added
src/starkernel/vm/kernel_hermes.c (wired into Makefile.starkernel's
LOADER_EXTRA_SRCS -- this repo lists vm/*.c files explicitly, no glob)
and sk_hermes_alloc()'s declaration in kernel_hermes.h.
Checks stadium_reservoir_peek(vm_id) >= SK_HERMES_Q_SLOT before
touching the reservoir at all -- refusal this way needs no rollback,
since nothing was pulled -- with an explicit rollback path
(stadium_reservoir_push) kept defensively for the pull-then-short case,
though nothing in this single-core kernel is expected to reach it.
SK_HERMES_Q_SLOT = Q48_ONE / SK_HERMES_MSG_MAX (2048), deliberately
simpler than messaging.4th's own formula, which reserves a Q.1/3 floor
for COMMON-CH's own Stadium heat -- kernel-Hermes has no such object
(SXXXIII.4/SXXXIII.5's flat membership list carries no heat of its
own), so there is nothing left for that floor to protect.
Self-test in kernel_main.c, same diagnostic-only synthetic-VM pattern
as the existing Stadium quota grant self-test (lo=3, distinct from
that test's lo=1): reads back the actual granted reservoir rather than
assuming a number, derives expected_n from it, allocates to refusal,
and checks the refusal lands at exactly expected_n, the reservoir
doesn't move on the refused attempt (rollback proven, not assumed),
and the final reservoir is exactly reservoir0 minus got_n times
Q_SLOT.
Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, dict_hash unmoved from task 2.1 (pure C, no FORTH
touched). All three print identical self-test arithmetic: reservoir0=
65536 Q_SLOT=2048 expected_n=32 got_n=32 reservoir_after=0. No compiler
warnings.
Noted, not fixed: Q_SLOT's divisor and SK_HERMES_MSG_MAX are the same
32, so reservoir and arena exhaustion land at exactly the same count by
construction -- this test can't distinguish which refusal reason
fired, only that refusal is correct and rolls back correctly.
Deliberately not evidence for stadium_conserved(): allocating alone
(no release yet, task 2.3) leaves pulled heat held off the Stadium
floor, so the two-term check would correctly read false right now if
run mid-hold. That's expected, not a bug -- Stage B (task 2.7) is
defined as "before and after the alloc/free cycle," not "continuously
during." This task's self-test checks reservoir arithmetic directly
instead.
Authorized by Captain Bob ("Yes continue").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
f10fa7ae83 |
Add kernel-Hermes message/membership structures -- FABRIC-3.6.md task 2.1, Phase 2 begins
Phase 2, task 2.1 only: type definitions, wired to nothing, drawing no
heat -- no allocator, no protocol logic, no registration anywhere.
FABRIC-3.5.md SXXII.4: Phase 2 structures come first and prove nothing
until the allocator is built on top (task 2.2 onward, each its own
commit).
Added include/starkernel/vm/kernel_hermes.h:
SkHermesMessage -- field-for-field mirror of messaging.4th's live
9-cell MSG-* layout (type/from/to/payload addr+len/Stadium cell
index/seq/channel/orig-type), per SXXXIII.4 item 1 ("roughly half the
file is accessors that become struct fields"), plus an explicit
in_use flag for task 2.2's allocator. Deliberately no separate heat
field: per SXL.4, a message's heat IS the Stadium cell it occupies,
not a value copied alongside it -- one source of truth for the
conservation invariant stadium_conserved() (task 0.7) checks.
SkHermesMembership -- one flat broadcast membership list, SXXXIII.4/
SXXXIII.5's recommended replacement for messaging.4th's 28-word channel
abstraction (traced to exactly one live caller, CH-ADD-MBR). Item 27
(negotiation vs. broadcast, Phase 3 blocker B1) is not answered by this
structure and isn't meant to be -- a flat list is correct either way.
Genuinely wired to nothing: no .c file, no Makefile change, no include
from any compiled source. Syntax-checked standalone (gcc -std=c99
-Wall -Wextra -Werror -fsyntax-only) before touching the real build.
Boot byte-identical to task 1.9's baseline on amd64 (same dict_hash
triple, zero UNKNOWN WORD). Did not repeat aarch64/riscv64 -- the file
compiles into no object on any architecture, so there is no mechanism
by which it could diverge.
Authorized by Captain Bob ("yes").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
1e75bb8039 |
Assert Hestia's headless invariant -- FABRIC-3.6.md task 1.9, Phase 1 closed
Audited first: neither capsules/hestia/init.4th nor her birth block in
kernel_main.c references g_wirebind_attached_username, CONSOLE-ATTACH,
MINT, or any proxy-minting mechanism -- the invariant already held
structurally, by absence. Stated it explicitly anyway, per the task:
added a comment at Hestia's birth site quoting FABRIC-3.5.md SXVIII.6's
invariant verbatim, warning future edits not to add console/wirebind/
proxy code there without re-reading it first.
Verified live with the actual no-thumbdrive boot
(ARCH=<arch> qemu ZUSEDISK=), not the default. All four VMs born
successfully on all three architectures, zero UNKNOWN WORD, and zero
ok> occurrences anywhere in any of the three full logs -- genuinely
silent, matching sk_repl_headless_wait()'s own documented "no banner,
no prompt, no input surface at all." Watched each log's line count
post-birth for 8-10s to confirm it stayed flat rather than eventually
printing something late.
Confirmed no regression on the standard (with-thumbdrive) path on
amd64: dict_hash identical to task 1.8's baseline. Did not repeat that
check on aarch64/riscv64 -- the change is a comment only, cannot
diverge by compiler, and the headless invariant itself was already
proven identically on all three.
Phase 1 is now fully closed (tasks 1.1-1.9). Tripod is Hera/Artemis/
Hestia plus Hermes (retained through Phase 1-3 per SXXXIV.4); Hestia
owns the drawing fabric exclusively; headless-until-login intact with
Hestia in the fleet. Phase 2 is next.
Authorized by Captain Bob ("yes").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
6c6293cd52 |
Move PLOT/FB-WIDTH/FB-HEIGHT registration to Hestia only -- FABRIC-3.6.md task 1.8
Real fix for task 0.8's finding. register_framebuffer_words() was
called unconditionally from register_forth79_words() (word_registry.c),
itself called unconditionally from vm_init_with_host() -- the generic
per-VM bootstrap every VM goes through, with no way to know a VM's
name at that point.
Removed the unconditional call. Added a name-gated call instead in
capsule_birth_baby() (capsule_birth.c), beside the existing
is_fleet_foundation check -- the one place in the birth path where
capsule_name and the newly-allocated VM* are both in scope together:
Hestia gets register_framebuffer_words(), nobody else does.
Positively verified live, exactly as the task's own check demands:
FB-WIDTH via VM-EXEC returns UNKNOWN WORD in Hermes and Artemis, 1280
in Hestia. Confirmed at the fundamental level too: Hera's own base
PARITY:M7.1a word count dropped from 531 to 528 -- exactly the three
words removed -- identical across all three architectures.
Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD during boot. mkcapsule --lint capsules/ clean, 38
files / 0 violations (pure C change, no capsule content touched). No
compiler warnings.
Side effect on the vendored hosted build, expected and not a
regression: capsule_birth.c is kernel-only, so the hosted starforth
binary has no Hestia concept and now never registers these words at
all -- confirmed live. framebuffer_words.c's own top comment already
calls this surface "kernel-only, no-op on hosted builds," so the prior
stub registration was already vestigial. Ran a plain `make` sanity
build per CLAUDE.md's own stated purpose for that target; regenerated
lfs/amd64/starforth included here rather than left stale.
Authorized by Captain Bob ("yes").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
5afb33049f |
Move fabric.4th + font.4th to hestia/init.4th -- FABRIC-3.6.md tasks 1.6+1.7 (merged)
Tasks 1.6 and 1.7 are not independent, and the punchlist's split was
wrong: font.4th calls G-LINE/G-ELLIPSE, which are fabric.4th's own
words. Confirmed live before committing to an approach -- removed only
fabric.4th's EXEC from init.4th, left font.4th's in place, booted
amd64: Hera's boot floods with UNKNOWN WORD: 'G-LINE'/'G-ELLIPSE' the
moment font.4th loads (logs/20260919-171047/amd64/, kept as evidence).
Reverted that partial state, asked Captain Bob how to proceed given
neither task can independently pass its own three-arch-boot check, and
was told to use best practices.
Moved both together, in their original relative order, into a new
capsules/hestia/init.4th block 4988 -- a deliberate, documented
deviation from "one task, one commit," not a bundling of convenience.
Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD. Hestia's dict_hash identical across all three
architectures. Verified the shrink/grow live, not just inferred from
hash movement: HERE reads 20008 in Hera, 63040 in Hestia post-move.
CART-PLOT in Hera is UNKNOWN WORD; the identical call routed into
Hestia via VM-EXEC reaches the word and fails on a stack underflow
instead, proof it exists there since an unknown word can't underflow.
mkcapsule --lint capsules/ clean, 38 files / 0 violations. MANIFEST.md
updated: init.4th's block 2049 entry no longer lists fabric.4th/
font.4th; hestia/init.4th's entry gains block 4988.
Authorized by Captain Bob ("Use best practices.").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
487769e18a |
Register Hestia for switch signals -- FABRIC-3.6.md task 1.5
Added a fourth capsule_vm_find_by_name_nocase("Hestia", ...) +
sk_vm_switch_signal_register(...) block in src/starkernel/kernel_main.c,
same shape as the existing Hermes/Artemis blocks, placed after all four
fleet members are confirmed born -- the existing comment on this block
already states why: no critical-section protection during setup, so
registering earlier risks the signal firing mid-birth.
Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, hashes identical to task 1.4's baseline (this is
pure C runtime state, doesn't touch any FORTH dictionary). No compiler
warnings.
Took FABRIC-3.5.md SXXXV.0's "invisible by default" warning literally
rather than trusting a clean boot log alone: SXXVIII.2's own recorded
switch-storm signature is "QEMU pinned near 100% CPU, serial log frozen
solid," not an error message. Confirmed normal wall-clock boot time on
all three (~30s) and, since TCG itself always shows ~100% CPU
regardless of guest workload, watched each serial log's line count at
the idle prompt for 5-10s and confirmed it stopped growing rather than
flooding or silently stalling.
Authorized by Captain Bob ("yes").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
10e2d654e5 |
Birth Hestia in kernel_main.c -- FABRIC-3.6.md task 1.4
Added a birth block immediately after Hermes's own, same shape:
S" Hestia" BIRTH followed by a registry-lookup confirmation. Fleet is
now Hera/Hermes/Artemis/Hestia, four VMs, through Phase 1-3
(FABRIC-3.5.md SXXXIV.4) until Phase 4 retires Hermes.
Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD. Registry shows all four (BIRTH: Hermes live, BIRTH:
Hestia live, PARITY:BIRTH for all three non-Hera VMs). Hestia's
dict_hash identical across all three architectures (0x31cab513929eea89).
Hera/Hermes hashes unchanged from task 1.3; Artemis's vm_id shifted
(now the 4th birth instead of 3rd -- sequence-derived, not identity-
derived, so expected) but its dict_hash is unchanged and still
identical across arches. No compiler warnings.
Noted, not a regression: Hestia's birth log shows the same
"( Unterminated comment" HADES warning Artemis's birth has shown since
task 0.0's first baseline.
Authorized by Captain Bob ("yes").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
ff00de9a63 |
Add Hestia to is_fleet_foundation -- FABRIC-3.6.md task 1.3
Fourth vm_name_prefix_eq_nocase(capsule_name, "Hestia") check alongside
Hera/Hermes/Artemis in src/starkernel/capsule/capsule_birth.c's
is_fleet_foundation local -- Hermes retained, per FABRIC-3.5.md
SXXXIV.4 (he stays live and fleet-foundation through Phase 1-3). The
flag's only consequence is session_set_pinned(vm_id, 1) for whichever
VM name matches.
Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, byte-identical to the pre-change baseline -- expected,
since nothing births anything named "Hestia" yet (task 1.4), so the
added name never matches. Hermes confirmed still present in the check
and still born normally in all three logs.
Authorized by Captain Bob ("yes").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
4f7cbe78fc |
Create capsules/hestia/init.4th -- FABRIC-3.6.md task 1.2
The third Tripod leg's first real file (FABRIC-3.5.md SII/SIV). Blocks
4986-4987 of the 4986-4996 allocated in task 1.1 (
|
||
|
|
b624133a28 |
capsules/MANIFEST.md: allocate Hestia's block range 4986-4996 -- FABRIC-3.6.md task 1.1
Documentation only, no capsule file created yet (that's task 1.2).
Checked against actual current occupancy rather than trusting
MANIFEST.md's own stale blanket "4853+ OPEN" line: fabric.4th already
occupies 4900-4924 and font.4th 4925-4985 (both standalone capsule
files EXEC'd by init.4th, block-numbered independently of it -- tasks
1.6/1.7 relocate which capsule EXECs them, not their own ranges), and
4997 is the console proxy's hardcoded Block 4997 string literal
(capsule_console.c:27-29). Allocated 4986-4996, the gap between the
two, avoiding 4997 as the task requires.
Split the Unassigned Ranges table's single blanket line into an
explicit claim for 4986-4996 plus a corrected "OPEN" line that excludes
the ranges actually in use. Recorded in MANIFEST.md rather than
tools/capsule-reserved.txt -- that file is for blocks owned by
non-capsule infrastructure per its own header comment; a real capsule
allocation belongs in MANIFEST.md alongside every other infrastructure
capsule's entry.
mkcapsule --lint capsules/ clean, 37 files / 0 violations, unchanged.
Authorized by Captain Bob ("YES").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
eed2a9dfb5 |
FABRIC-3.6.md task 0.8: PLOT/FB-WIDTH/FB-HEIGHT reachability audit -- Phase 0 closed
Read-only audit, no code changed. Finding: reachable from every VM
today, not confined to one table, contrary to item 33's premise.
FORTH level matches expectation: fabric.4th/font.4th are EXEC'd only
from capsules/init.4th (Hera). C level does not: register_framebuffer_
words() (src/word_source/framebuffer_words.c:60-65) is called
unconditionally from register_forth79_words() (src/word_registry.c:
139), itself called unconditionally from vm_init()
(src/starkernel/vm/vm_bootstrap.c:263) -- the generic per-VM bootstrap
every VM goes through, no identity check.
Verified live rather than trusting the source trace alone: booted
amd64 and ran `S" FB-WIDTH ." S" Hermes" VM-EXEC` and the same against
Artemis -- both returned 1280, not UNKNOWN WORD. Neither loads
fabric.4th, so the raw C primitive itself is answering.
Not fixed here, per the task's own read-only scope. Gives task 1.8 a
concrete starting state: its own check ("a non-Hestia VM calling PLOT
gets UNKNOWN WORD") currently fails, and register_framebuffer_words()'s
call site will need to become conditional or move out of the universal
bootstrap -- not just the FORTH-level relocation tasks 1.6/1.7 already
plan for.
Phase 0 gate met across tasks 0.2-0.7 (three-arch boot, stadium_
conserved() true, zero UNKNOWN WORD, repeatedly). Phase 0 is closed;
Phase 1 (Hestia, messaging untouched) is next.
Authorized by Captain Bob ("Continue.").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
380f0a09c9 |
Add stadium_conserved() -- FABRIC-3.6.md task 0.7, item 41
Boolean analogue of vm_physics_conserved(), for the Stadium per-VM
quota invariant rather than fleet-wide execution heat
(FABRIC-3.5.md SXXXIX.4). int stadium_conserved(VMUuid vm_id), in
src/starkernel/vm/stadium.c alongside stadium_resident_sum()/
stadium_reservoir_peek() that it's built from, declared in
include/starkernel/vm/stadium.h.
Implements the two-term form -- resident_sum(vm_id) +
reservoir_peek(vm_id) == Q48_ONE -- not the three-term form SXL.4
rules for the eventual system. That ruling's `consumed` term is a
Phase 2 kernel-Hermes ledger deliverable that doesn't exist yet:
nothing draws on any VM's Stadium quota today (task 2.2 is literally
where that wiring gets built), so consumed is honestly zero right now.
Folding it in as a placeholder would be inventing Phase 2 state ahead
of it existing -- the doc comment says so explicitly, so whoever
builds Phase 2's ledger extends this function rather than working
around it.
Wired into the existing per-VM boot diagnostic
(stadium_words_print_boot_diagnostics(), kernel_main.c:810, Hera
only -- the sole existing call site) rather than adding a new one,
printing CONSERVED/DRIFTED the same shape vm_physics_status() already
uses.
Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, all print "Stadium conservation: CONSERVED" with
identical resident_sum=47641 reservoir=17895 sum=65536=Q48_ONE. No
compiler warnings on either edited file (forced recompile checked).
Authorized by Captain Bob ("Yes.").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
9886ad5315 |
tools/capsule-reserved.txt: return freed block ranges -- FABRIC-3.6.md task 0.6
Added 4055-4059 (former common/msg.4th) and 4300-4399 (former
process.4th) as reserved, each noted as freed by this reshuffle's
strip rather than owned by non-capsule infrastructure -- the file's
usual purpose (Artemis's own block usage, etc.). Framed explicitly as
lifted, not permanent, once someone deliberately wants a range back,
per FABRIC-3.5.md SSXVIII.4/XXII.4: freed ranges should be returned
here rather than silently available for a future capsule to reclaim
without anyone noticing.
mkcapsule --lint capsules/ clean, 37 files / 0 violations.
check_reserved_conflicts() -- the hard build-gate that actually reads
this file (tools/mkcapsule.c:974) -- passes clean on a real
`make -f Makefile.starkernel ARCH=amd64 all`, confirming the new
entries don't collide with anything currently baked. Registry/
documentation only, no capsule content touched, so no 3-arch boot run
for this task.
All of Phase 0's strips and documentation corrections (0.1-0.6) are
now done. stadium_conserved() (0.7) and the PLOT/FB-WIDTH/FB-HEIGHT
registration audit (0.8) remain.
Authorized by Captain Bob ("Go for it.").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
e233d09fa1 |
capsules/MANIFEST.md: correct blocks 4055 and 2049 -- FABRIC-3.6.md task 0.5
Block 2049's justification claimed init.4th loads compudynamics,
common:msg, fleet-k and process. Read the live file: it loads none of
these. compudynamics.4th/fleet-k.4th were already deleted (9323f776,
2026-07-05); common:msg.4th/process.4th are this reshuffle's own
Category A strips (tasks 0.2/0.3, a0e97258/aafcce43) and were never
EXEC'd from this block even before that -- the manifest entry was
already wrong prior to this pass, just not yet caught.
Removed the standalone common/msg.4th (former block 4055) and
process.4th (former blocks 4300-4301) sections, since both files no
longer exist, and folded them into "Deleted capsules (historical)"
alongside the existing compudynamics.4th/fleet-k.4th entry -- same
convention, same section. Noted that common/msg.4th's own entry had
claimed it was an immutable ABI "every messaging VM loads at birth",
a claim FABRIC-2.md:2773 had already flagged stale before this
correction landed. Updated the Unassigned Ranges table so 4055-4059
and 4300-4399 read as former-file ranges rather than "extension space"
for files that no longer exist.
Documentation only -- no capsule content touched, mkcapsule --lint
capsules/ still clean (37 files, 0 violations). Verified every
remaining ### `*.4th` section in the manifest names a file actually
present on disk.
Authorized by Captain Bob ("Keep going.").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
bcc678e354 |
Strip SPAWN-EVENT from messaging.4th -- FABRIC-3.6.md task 0.4
Dead per task 0.1's reachability check (
|
||
|
|
aafcce4320 |
Strip capsules/process.4th and its EVENT-EMIT/-WAIT/-DRAIN -- FABRIC-3.6.md task 0.3
process.4th is dead per task 0.1's reachability check (
|
||
|
|
a0e9725883 |
Strip capsules/common/msg.4th -- FABRIC-3.6.md task 0.2
Dead per task 0.1's reachability check (
|
||
|
|
156d1642de |
FABRIC-3.6.md task 0.1: Category A reachability established
Checked the three §XXII.2 routes for capsules/common/msg.4th,
capsules/process.4th, and the SPAWN-EVENT constant, never by grep
count alone: a boot-path trace of every capsule init.4th loads at
birth, a tools/experiments/docs invocation search, and confirmation
that mere presence in the baked capsule directory doesn't make a name
reachable if nothing constructs it at runtime. All three are dead by
every route -- zero EXEC sites, zero callers of their exported words.
Two findings recorded, neither changing the punchlist's plan:
tools/hermes_smoke.sh calls EVENT-EMIT/EVENT-WAIT/EVENT-DRAIN directly,
a caller FABRIC-3.5.md SXXXIII.3 missed when it said the only other
reference was MANIFEST.md. Doesn't make them live -- the script is
already broken on its own terms (references capsules/core/init.4th and
build/amd64/standard/starforth, neither exists; calls the pre-rename
CD-INIT word). Task 0.3's plan to strip these three words alongside
process.4th stands.
PAUSE-EVENT/RESUME-EVENT/KILL-EVENT are exactly as dead-by-name as
SPAWN-EVENT -- process.4th's own calls pass bare numeric literals, never
the constant names. Task 0.4 only names SPAWN-EVENT; flagged for
whoever picks up Category A stripping next rather than expanding this
task's scope.
Investigation only, no code changed.
Authorized by Captain Bob ("begin.").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
4544877a36 |
FABRIC-3.6.md task 0.0: three-ISA baseline smoke test, PASS
amd64/aarch64/riscv64 all boot clean to [zuse@Hera] ok>, zero UNKNOWN
WORD, and dict_hash identical across all three: Hera (PARITY:M7.1a)
0x6824fe5993239838, Hermes 0x062252c4da6858da, Artemis
0xed80117724c26f36. This is the gate task 0.0 exists for -- every later
task's acceptance in this document assumes this baseline is known-good.
Found and worked around a real confound along the way: disk/artemis.img
is deliberately shared across all three ISAs' qemu targets (FABRIC-3.md
SXXXV.2), so a same-order rerun has run 2 and 3 silently resume run 1's
already-formatted disk instead of formatting their own. The first
attempt (logs/20260919-124835 amd64, logs/20260919-124952 aarch64,
both kept for the record) shows exactly this: Artemis's dict_hash
diverges between the two runs even though Hera's and Hermes's do not,
because only Artemis's birth path branches on disk state. Restored
disk/artemis.img and disk/thumbdrives/zuse-thumb-ident.img to their
committed blank state before each of the three reruns that produced
the clean, matching baseline above, and recorded the finding in
FABRIC-3.6.md so a later task doesn't mistake the same confound for a
real architecture divergence.
capsules/BLOCK_MAP.md's timestamp header is regenerated by the build,
per .claude/CLAUDE.md's documented behavior for that generated file.
Authorized by Captain Bob ("begin", "clean new disk",
"document, commit, and push").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
89d886b7f7 |
CLAUDE.md: correct four stale claims, add reshuffle pointer; record as FABRIC-3.5 §XLIV
Authorized by Captain Bob. Each claim re-verified against source immediately before editing rather than from this session's notes. Theory files: 23 becomes 52, all listed in proof/ROOT, with a pointer to COVERAGE.md's own statement that the deliverable is the verified boundary rather than a green build. LITHOS_VERSION: 2.0.1 becomes 2.0.0, noting §I.2 rolled it back because 2.0.1 names the SER5 hardware-track line and claims progress not yet verified, and that the policy is semantic rather than sequential. ACL pinning carried two mutually inconsistent rules, neither matching the code: policy never in C with no vm_find_word plus field assignment, and separately that kernel-only words should be pinned in a kernel-specific capsule. kernel_main.c:771-782 pins BIRTH and CAPSULE-BIRTH exactly the forbidden way, deliberately, and ACL.4th's block-4005 comment explains why -- so ACL.4th stays host-portable. Now stated as the rule plus its one sanctioned exception. The mkcapsule block rule was described as a 1024-byte budget verified with wc -c. Reading tools/mkcapsule.c shows validate_forth_blocks enforces 64 chars by 16 lines and a block number in [2048,5120). 64 times 16 is 1024, which is where the figure came from, but the enforcement is per-line: eight lines of 128 chars is 1024 bytes and still fails. Note that FABRIC-3 §XXXII.2's own correction of this claim was itself incomplete, fixing the number while missing the line-length rule. Adds a WORK IN PROGRESS pointer directing a fresh session to FABRIC-3.6.md's START HERE, since CLAUDE.md is what auto-loads. It states no reshuffle code exists and that the file's current Tripod descriptions stay correct until the reshuffle lands, deliberately not pre-writing the post-reshuffle state. FABRIC-3.6.md: trap 5 rewritten, since it told a fresh session to distrust four claims that are now fixed; it now names MANIFEST.md and FABRIC-0 §25.7 as the documents that still drift. Also removes a quoted commit id from the handoff that will always be stale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP |
||
|
|
6233dcaef5 |
FABRIC-3.6.md: correct the handoff for execution on a real toolchain
Captain Bob confirmed execution happens on his PC with the toolchain installed, not in a container. Two lines of the handoff would have misdirected the session they exist for. Task 0.0 said it must be run by Captain Bob on a machine with the real toolchain. On the intended machine the session can run it itself, and the old wording would have had it wait for a human who was expecting it to proceed. Now it says to run it, after verifying the toolchain is actually present, and to stop and say so rather than improvise a partial test if anything is missing. Adds a note that the unchecked state is not-yet-attempted rather than a prior failure, since a fresh reader could reasonably assume the latter. The working-directory warning was written around this container's own layout and named a path that will not exist on the target machine. Replaced with the check that actually matters -- git remote -v must show the Gitea repo -- keeping the container case as a parenthetical rather than the headline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP |
||
|
|
ca5443cb1a |
FABRIC-3.6.md: correct the handoff -- GitHub is not part of this project
The GitHub reference came from this session's own harness, not from LithosAnanke: the session was started in /home/user/StarForth, a separate empty repo wired to github.com/rajames440/StarForth, and the task instructions named that as the build target. The mirror claim came from another GitHub repo's own description, which is itself stale -- per Captain Bob, GitHub has been removed from everything and is not a mirror. Both statements are wrong and were about to mislead exactly the audience the handoff exists for, so they are replaced rather than softened. The underlying hazard is real and kept in accurate form: a session launched the same way will land in a directory that is not this project and be told to build there. The note now says that plainly -- nothing outside Gitea is this project, and a directory that is not a clone of admin/LithosAnanake is the wrong place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP |
||
|
|
eb6cad2330 |
FABRIC-3.6.md: add START HERE session handoff
Written so a cold session told "go build it" can begin without re-deriving anything. Records where the code actually is -- the Gitea instance, not the empty GitHub StarForth repo that cost this session time at the outset, and not the read-only mirror -- plus the branch, its head, and that no code has been written yet. States the two-document split and the rule that 3.5 is authoritative and a task proving a ruling wrong is a finding to record rather than a divergence to make quietly. Carries the five traps this project's own history proves are real, since a fresh session would otherwise walk into each: silent failure is the dominant mode and a green boot is weak evidence; grep cannot establish capsule reachability, having put six live capsules at zero references; fleet_conserved cannot see Stadium heat, so a leaking allocator leaves it reporting fine; dict_hash changing is expected while diverging is the stop condition; and CLAUDE.md carries four known-stale claims to trust the code over. Names the three blocked decisions and the standing prohibitions -- no branches, no stashing, no fixing in passing, no bundling, nothing without authorization. No credentials committed; the handoff names the host and repo only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP |
||
|
|
2032974f3a |
FABRIC-3.6.md: add pre-flight -- three-ISA baseline as task 0.0, and why it gates
Records the branch state execution starts from: head
|
||
|
|
3a4e5cff07 |
FABRIC-3.5.md §XLIII: B3 settled -- the target drains its own queue at its own checkpoint
The last genuine design question in the reshuffle, and once again the mechanism already exists, built for the structurally identical problem and corrected twice in the field. Delivery must execute the payload inside the target, but §XXXII retired the pump so kernel-Hermes may not VM-EXEC into anyone. The payload must wait somewhere the target consumes under its own power, at a moment when doing so is safe -- and safe is the hard part, since interpreting mid-word, mid-unwind or nested inside someone else's dispatch is the reentrancy class this codebase has been bitten by repeatedly. vm_core.c already has all of it: sk_vm_at_outermost_interpret() as the predicate, a per-word checkpoint in execute_colon_word() gated on it, a defer-don't-lose discipline for the nested case whose own comment explains that a nested word does not own the stack it is running on, and placement before the error and unwind checks so it only acts when the VM is in a clean resumable state. That is the exact predicate, placement and deferral semantics delivery needs, because the switcher had to answer the same question. Ruling: kernel-Hermes enqueues and publishes the fact, dispatching nothing; the target drains its own queue in its own context at its own outermost checkpoint. One published fact, two independent consumers -- the switcher for eligibility, the VM itself for drain -- and neither dispatches into anyone. Notes this is §XX's proven pattern generalized: the pump's defect was never draining but draining from outside, and Hera was special-cased into safety. Retire the pump and every VM does what she already does, so the special case disappears by becoming universal. Also notes recursive drain is prevented for free, since interpreting increments the same depth counter that defines the boundary. Names three constraints rather than leaving them to be discovered: INPUT_BUFFER_SIZE is 1025 so a payload above 1024 bytes cannot be interpreted in one drain, starvation is real but is the switcher's existing risk rather than a new one, and draining one message per checkpoint rather than the whole queue keeps the work bounded. FABRIC-3.6.md: B3 cleared, B4 added for the payload bound. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP |
||
|
|
dd255eac58 |
FABRIC-3.6.md: open the execution log; FABRIC-3.5.md §XLII records the split
Agreed to move the punchlist to its own document, on the series' own rule rather than preference. FABRIC-1 closed at 4,420 lines because "the still-open work [was] hard to find" among hundreds of resolved ones; FABRIC-3.5 stands at 4,452 with 40+ open items in the same shape. Annotating 25 tasks there, commit by commit, would bury the design record it exists to be. The split is strict and stated firmly, because §XXXI found this series' documents losing track of each other. 3.5 holds the design record and stays authoritative on conflict; 3.6 holds the work. Rulings are cited in 3.6, never restated, since duplication is how two documents begin to disagree -- and a task that proves a ruling wrong is recorded as a finding there and amended here, never silently diverged. 3.6 restores the checkbox convention. §XXXI.2 found the carry-forward discipline was mechanically auditable through FABRIC-2 and broke at FABRIC-3, which has no checkboxes, after which "is anything still open" stopped being a grep and became a reading exercise -- which is how six design items went quiet without anyone deciding to drop them. The next document in the series does not repeat the defect the gap analysis found in it. §XLI stays in 3.5 as the source 3.6 instantiates: the phase structure, why Phase 2 sits early, and the three Phase 3 blockers with their justification. What moved is the checklist, not the argument. 3.6 also carries an explicit out-of-scope section so deliberately excluded work is not absorbed by an executing session. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP |
||
|
|
91108c23c9 |
FABRIC-3.5.md §XLI: the build punchlist -- phases 0-2 buildable, phase 3 blocked
Answers the question asked: yes for the first three phases, no for the fourth, and the reason is three named things rather than general caution. Phase 0 is preparation with no behaviour change -- Category A reachability established per §XXII.2's three routes rather than by grep, the strips themselves, MANIFEST corrected as its files go, freed blocks returned, stadium_conserved() added, and the framebuffer registration audited. Phase 1 is Hestia with messaging untouched, sequenced per §XXXIV.4 so Hermes is retained and is_fleet_foundation holds four names. Phase 2 is the allocator and its audit, inert, ending in the Stage B proof that §XXXIX.4 corrected: the ledger plus stadium_conserved(), with fleet_conserved explicitly not evidence. Twenty-five tasks across those three phases, each with its own check, which also discharges item 34. Phase 2 is deliberately reachable early, since §XXXIII named it the only genuinely hard part and it should fail cheaply with nothing built on top. Phase 3 is not buildable pending item 27 (channels: negotiation or one membership), item 32 (SK_SWITCH_MAX_SLOTS is 16 and §XXXII made the switcher the sole mover, so whether this is a constant bump or a table redesign changes the phase's shape), and §XXXII.5.1, the delivery hand-off, which is the last genuine design question in the reshuffle and sits on the critical path. Records a caution: phases 0-2 can run without answering those, which is a feature, but it means arriving at a working audited allocator with the cutover still undesigned. Better to settle the hand-off before phase 2 finishes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP |
||
|
|
91173854cb |
FABRIC-3.5.md §XL: item 40 settled -- preserve the consumption model, ledger the sink
New evidence found while settling this reverses §XXXVIII's framing. messaging.4th block 5025, immediately above MSG-REAP, states outright that K reap only fires at heat=0 so the freed contribution is 0, and anticipates that a force-reap would require explicit K redistribution. The zero-return at natural reap is a documented design note, not an oversight: heat is modelled as consumed over a message's life rather than held as a refundable deposit. So §XXXVIII's trace stands exactly -- decay drains the field, reap returns nothing, the eviction guard is bypassed -- but its interpretation does not. The word leak is withdrawn, along with its recommendation to change the economics as part of this reshuffle. Two further roles for message heat turned up and both corroborate the model: MSG-NACK-LAST halves it, so a rejected message ages twice as fast, a penalty in the same currency; and delivery order does not consult it at all, so changing it cannot perturb sequencing. Ruling: kernel-Hermes preserves the economy exactly and additionally records what it consumes, making the Stadium invariant read patron heat plus reservoir plus consumed equals Q48_ONE. That is what makes item 41's stadium_conserved() possible at all -- without a consumed term a checker over a designed sink reports non-conservation as normal operation, which forces a tolerance that hides real bugs. §XXXVII.3's counter is therefore vindicated rather than deleted, and for a better reason than it was introduced for. Declines to separate reservation from age now, despite it being the better architecture, on scope, measurement comparability, and the fact that this subsystem yielded two new surprises while a third was being settled. Names but does not answer the real question: nothing replenishes a reservoir, so a long-lived VM's send capacity declines monotonically, and batched pumping accelerates it with fleet size. Fourth instance of latent at Tripod scale, visible at fleet scale. Filed as outside this reshuffle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP |
||
|
|
3843e1d685 |
FABRIC-3.5.md §XXXIX: item 39 settled -- the accountings are not coupled, and §XXXIV.6 watched the wrong one
Traced both directions. capsule_vm_physics.c has zero references to stadium and does not include its header; it has zero references to reservoir. stadium.c's single vm_physics mention is a comment. vm_physics_fleet_heat_sum() sums execution_heat_q48, whose only writers are initialise, transfer between two VMs, and redistribute on death. Message allocation cannot move it. They are not even the same shape of invariant. StadiumVMQuota.reservoir's own comment states Stadium conservation is per-VM -- resident patron heat plus that VM's reservoir equals Q48_ONE each -- while vm-physics K is fleet-wide across all VMs. Two scopes, two disjoint data sets, sharing only the Q48.16 format, the constant, and the word heat. That shared vocabulary is what made them look like one system. Only one has a checker. vm_physics_conserved() is the sole *_conserved() function in the kernel; the Stadium side has a documented invariant, a human-readable diagnostic print, and MSG-K covering just the messaging slice. So §XXXVIII's leak is invisible to every automated check -- not because checks disagree, but because nothing is looking. Corrects §XXXIV.6 in the dangerous direction: it made fleet_conserved the tripwire for Stage B, but a kernel-Hermes allocator leaking every reservation would leave it reporting a serene 1. That is §XXXV.0's signature exactly, built into the plan, and item 39 existed to catch it before it mattered. Stage B instead verifies kernel-Hermes's own ledger and the Stadium per-VM invariant, and should add the missing stadium_conserved() as its first act. Two earlier rulings need consequent care. §XV.4's K must be qualified, since the heat a dying arbiter holds is Stadium heat rather than the verified fleet K -- substance stands, citation was wrong. And the self-audit is promoted from safety net to sole instrument, which argues for an independent Stadium checker rather than the arbiter policing itself alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP |
||
|
|
b4bd2413d5 |
FABRIC-3.5.md §XXXVIII: item 38 settled -- decayed heat does not return, and a reaped message returns nothing
Read the path end to end. Message heat is Stadium cell heat, since MSG-HEAT@/! route through MSG-STADIUM-CELL@ STADIUM-HEAT@/!, so MSG-COOL-ONE decays header->heat in the cell itself. stadium_evict returns whatever remains to the owner's reservoir, and its own comment says why: otherwise every reap leaks heat and the sum drifts below Q48_ONE. But MSG-REAP fires exactly when heat reaches zero, so at eviction time the field is already zero. So the eviction guard that exists to stop reaps leaking is bypassed completely for every aged-out message, and the whole Q.SLOT pulled to send it is destroyed. That is worse than §XXXVII.6 anticipated, which expected partial loss. The root cause is one field carrying two incompatible meanings: an activity metric, which should decay, and a reservation currency, which must not. Decaying a budget token destroys budget. Whether that is intentional is arguable -- pay to send, forfeit if uncollected is a coherent backpressure economy -- but nothing replenishes reservoirs, so the forfeit is permanent against a finite pool. States a precision boundary rather than overclaiming: there are two heat accountings, Stadium and VM-physics, and this leak is in the Stadium one. Whether it is visible to VM-CONSERVED? was not established, so that is filed as its own item to settle before §XXXIV.6's tripwire is relied on. Recommends separating reservation from age as two fields, which costs nothing in greenfield C, removes the leak, keeps reaping working and preserves backpressure in an honest form. Notes the consequence for §XXXVII: separating the fields restores §XXXVI.3's original invariant and deletes the decayed counter §XXXVII.3 had to invent. Both earlier sections were right about the destination and wrong about the obstacle -- it was never decay, it was decay applied to the wrong field. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP |
||
|
|
cbed0c5f02 |
FABRIC-3.5.md §XXXVII: item 36 settled -- audit epsilon is zero, after correcting §XXXVI's invariant
Checking the numbers first showed §XXXVI.3's proposed invariant cannot hold. MSG-COOL-ONE multiplies a message's heat by Q-DECAY and writes it back, returning the difference to nothing, so heat held diverges from heat pulled with no bug involved -- that is Loop #3's decay working. §XXXVI's ruling that the sinking condition is a failed self-audit stands; its arithmetic is corrected in place. The numbers settle the epsilon on their own. Q48_ONE is 65536, VM_PHYSICS_EPSILON_Q48 is 3277, and Q.SLOT is about 929 today or 1365 once channels are retired. So the existing epsilon is three and a half messages' worth of heat -- loose enough to hide the exact failure the audit exists to catch. It is not wrong, it was sized for fleet heat, which is genuinely noisy; it must not be reused here. Corrected invariant: held equals pulled minus returned minus decayed. All four terms are exact integers, Q.SLOT is a compile-time constant, and decay's delta is computable at the moment it is applied. Nothing is a measurement, so there is no noise to tolerate and epsilon is zero -- any divergence is a bug and the audit fires on the first unit. The one change required is the whole trick: today decay is an invisible overwrite, so kernel-Hermes must record what it destroys. One counter converts an unauditable quantity into an exact one, which is why the epsilon can be zero rather than guessed. Cadence is running counters rather than recomputation, since MSG-TOTAL-HEAT scans the whole arena and as a fleet-wide kernel structure that is §XXII's O(N) wall a third time, after the pump and the switch slot table. The check becomes one comparison of four integers, O(1), on every mutation. A scan stays useful for verifying the counters themselves, in Stage B and diagnostics rather than the hot path. Flags without resolving that if decayed heat is destroyed and eviction returns only what remains, fleet heat drifts downward over a long run -- the same shape F0 §25.7 warned of for a different, since-fixed cause. States the limit of the trace honestly: STADIUM-EVICT's return semantics were not read, so this is reported, not established. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP |
||
|
|
3a593055de |
FABRIC-3.5.md §XXXVI: item 31 settled -- kernel-Hermes sinks when its own heat accounting fails
§XXXV.1 found §VIII's latch had no trigger, since kernel-Hermes is not a Stadium patron and §VII's ladder cannot reach it. §VIII survives with a trigger that is concrete, cheap and already precedented twice here. Enumerates what can actually go wrong and finds only one state between healthy and gone: corrupted internal state, still executing but unable to route correctly and possibly unaware. kmalloc failure and budget exhaustion are refusals, which is the economy working; a panic is not sinking because it is already gone. Keeps §VIII rather than retiring it, for a concrete reason: the window buys a flush. block_subsystem.c carries dirty RAM blocks a hard halt loses, and §XV.4 already ruled payloads may vanish, so the window's value is that dirty blocks reach the disk. The sinking condition is a failed self-audit: the sum of heat held in live messages must equal total pulled minus total returned. An arbiter that has lost track of the heat it holds is exactly one that will break K for everyone else, and this is the earliest honest moment it can know. Not a new mechanism -- MSG-K already audits the FORTH messaging layer's own heat, and vm_physics_conserved() is the C shape verbatim. Notes the honest adaptation, that MSG-K is per-VM and sums a reservoir kernel-Hermes does not have, so the idea ports rather than the code. Records the resulting property: K is both the thing §XV.4 says must survive a death and the quantity whose violation announces it. States the accepted loss rather than engineering around it. Panics raise nothing and flush nothing, because the corruption that caused the panic may be in the block layer and flushing corrupt data over good data is worse than losing it. The audit narrows the window of silent loss without closing it. Bounds §VII explicitly, since §XXI.3's table reads as universal: rung 2 is a from-scratch BIRTH and BIRTH births VMs, so kernel-Hermes has a two-state life, correct or stopped. The bound was always implicit in authority flowing down the birth graph -- a thing never born has no place on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP |
||
|
|
cdf9cb24aa |
FABRIC-3.5.md §XXXV: pre-mortem -- two new design gaps, one hypothesis weakened
Assume failure six months on and work backwards, grounded in this project's own failure record rather than generic risk. Puts the dominant failure mode first, with four independent instances: things here fail without saying anything -- an identical serial log for a switch storm and a healthy idle REPL, a console-name mismatch that quietly never relays, 6 of 9 identities silently not attaching, and FORTH silently dropping a definition that references an undefined word. The principal finding is a design gap rather than a risk. §VIII's sinking latch was conceived when Hermes was a Stadium patron that could degrade. Kernel-Hermes is not admitted -- stadium_admit() runs only on birth -- so it has no heat, no TTL, no eviction and no compudynamic death. Its real failure modes are a panic, which halts instantly and gives the fleet no window at all, or allocation refusal, which is degraded but not dying and would be wrong to latch irreversibly. §VII's ladder does not apply either, since rung 2 is a from-scratch BIRTH and BIRTH births VMs. So the failure story for the one component that cannot emit SOS is undesigned, and retiring §VIII is a legitimate option rather than a tidiness problem. Second finding: SK_SWITCH_MAX_SLOTS is 16, which was reasonable when the switcher was one of two ways a VM got control. §XXXII made it the only one, so 16 becomes a hard ceiling on the fleet the moment that ruling lands, against §XXII's own note that the fleet is headed toward hundreds of VMs. This is the pump story repeating one layer up, findable on paper this time. Records a hypothesis that weakened on inspection rather than pushing it: K verification is vm_physics_conserved() in capsule_vm_physics.c, exposed as VM-CONSERVED?, independent of the DoE, which only reports it. The capability survives a DoE rewrite; only the continuous per-tick record does not, so Stage B needs its own deterministic check loop. Also flags that Hestia's ownership is conventional and therefore checkable -- the framebuffer primitives must be registered to her alone -- and that the Category A strip should precede the DoE rewrite while the campaign that would catch a bad strip still runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP |