Commit Graph
202 Commits
Author SHA1 Message Date
Robert Allan JamesandClaude Sonnet 5 70dc8beba4 FABRIC-2.md §I.9 follow-on: blinking | cursor instead of static block
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Captain Bob asked for the framebuffer cursor to render as a blinking
vertical bar rather than the previous static solid-block glyph.

vt100_draw_cursor() (hal/vt100.c) now fills a thin bar (cell_w()/8, min
1px, full cell height) at the cursor's left edge instead of the whole
cell -- an I-beam shape. vt100_erase_cursor() is unchanged (clearing the
whole cell already safely covers the narrower bar).

Blinking is new in repl.c: sk_console_readline()'s idle branch toggles the
cursor on/off every SK_CURSOR_BLINK_INTERVAL (50 ticks, 500ms at 100Hz)
via alternating console_fb_draw_cursor()/console_fb_erase_cursor() calls,
independent of the heartbeat/idle-beat mechanism the §I.9 fix just touched
(deliberately not reused, to avoid recoupling to that path). Runs
regardless of n, so it blinks whether sitting at a bare prompt or paused
mid-edit. Every deterministic draw site (initial prompt, prompt reanchor,
backspace, character echo) now goes through a new helper, sk_cursor_show(),
which resets the blink cycle to "on" and redraws -- typing always shows a
solid cursor, never mid-blink.

Verified via the mandatory foreground 3-arch QEMU acceptance boot: amd64
(logs/20260905-021054, extensive live interactive typing including
multi-line : / ; word definitions and error cases, prompts stayed
correctly attached throughout), aarch64 (logs/20260905-021551), riscv64
(logs/20260905-022324) -- all three reached (zuse) ok> and shut down
cleanly via BYE.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 02:24:51 -04:00
Robert Allan JamesandClaude Sonnet 5 8edb95b65d FABRIC-2.md §I.9: fix the terminal phantom-linebreak defect
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
console_ensure_line_start() (hal/console.c) used to emit its newline
immediately via a path that deliberately skipped the tx-byte counter, so
that sk_repl_idle()'s unconditional per-beat call to it (repl.c, ~1s idle
heartbeat) could force a real newline the REPL's own prompt-reanchor logic
never noticed -- the prompt was never reprinted, and the next real
keystroke echoed onto the now-blank line, indistinguishable from Enter
having already been pressed at a bare prompt. Root-caused in the previous
commit (704573b); this commit applies the fix per explicit go-ahead.

Fix: defer the newline instead of emitting it eagerly.
console_ensure_line_start() now only sets a flag (g_pending_line_close);
the newline is realized -- for real, and counted by g_console_tx_count
like any other output -- on the next actual console_putc() call, or
silently discarded via the new console_cancel_deferred_line_start() if the
caller decides nothing was actually printed. sk_repl_idle() captures
tx_before_idle right after its console_ensure_line_start() call and cancels
the deferred newline at both of its exit points when console_tx_count()
hasn't moved. A beat with nothing to report now leaves the console
untouched; a beat that does print still closes the dangling prompt line
first, properly counted this time. The pre-existing n > 0 mid-edit gate in
sk_console_readline() is untouched -- independent purpose, not the bug.

Verified via the mandatory foreground 3-arch QEMU acceptance boot
(clean qemu, amd64 -> aarch64 -> riscv64, one at a time): all three reached
(zuse) ok>, all echoed the first real input on the same log line as the
prompt rather than a fresh line, all shut down cleanly via BYE. amd64's log
additionally shows live human backspace-correction still glued to the same
prompt line. Logs: logs/20260905-015854 (amd64), logs/20260905-020248
(aarch64), logs/20260905-020552 (riscv64); logs/20260905-015813 is a
foreground-rule-violation retry killed and redone correctly, kept per this
project's "never delete logs/" convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 02:08:06 -04:00
Robert Allan JamesandClaude Sonnet 5 704573bdfc FABRIC-2.md §I.9: root-cause the terminal defect, not fixed (identify-only)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
No code changed. This is a closure annotation on an existing open item
where it lives (FABRIC-2.md, marked archival by CLAUDE.md, but §I's punch
list landed there after the FABRIC series rename and genuinely still
lives there) -- not new design content in an archival doc.

Used QMP send-key to inject a real keypress into a running headless QEMU
guest -- no physical keyboard needed, overturning this item's own
"no way to observe the live QEMU GTK window from this environment"
assumption. A first pass (amd64 + aarch64, real first keypress after a
fresh boot, pre/post log captures) actually reproduced the defect but
misread it as clean: both logs showed the echoed key on a bare new
"[Hera]" line instead of appended to the still-visible "(zuse) ok> "
line, which looks like normal REPL output unless you know the prompt
should still be there. QMP screendump on aarch64 confirmed it visually.

Root cause traced to two specific interacting lines, not guessed:
console_ensure_line_start() (hal/console.c:312) emits its newline via
the tx-count-exempt console_putc_inner(), by design, so idle chatter
doesn't spam the REPL's prompt reanchor. But sk_repl_idle() (repl.c:178)
calls it unconditionally on every ~1s idle beat, including at a bare
prompt with nothing typed (n==0, repl.c:610) -- forcing a real but
tx-count-invisible newline that the reanchor check (repl.c:633) never
notices, so the prompt is never reprinted. The next real keystroke
echoes onto the now-blank line with a lazily-emitted "[VMName] " prefix,
indistinguishable from Enter having already been pressed. Not a
first-keypress race -- it fires on any ~1s+ human pause at a bare
prompt, i.e. nearly always, matching "consistent and reproducible."

repl.c's own comment at lines 598-609 already half-diagnosed this
exact failure mode and gates it for n>0 (mid-edit); the gap is the
identical n==0 case (bare prompt) was treated as harmless. Likely fix
location noted, not designed here.

Verified reproducible on both amd64 (i8042) and aarch64 (virtio-input)
input paths; riscv64 shares aarch64's virtio-input code path. Evidence:
three fresh boot logs plus before/after screendump PNGs
(evidence/aarch64/qemu-screenshot-20260905-013835-i9-*.png). No 3-arch
acceptance boot run for this commit -- no code changed, and these
logs/screenshots are themselves the evidentiary artifact, not a
generic regression check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 01:47:53 -04:00
Robert Allan JamesandClaude Sonnet 5 dbaead0af2 xhci: route driver chatter through log_message(), silence at default log level
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
xhci.c and repl.c's USB attach/detach path printed every xHCI command
submission, completion, and BOT transfer step unconditionally via
console_println()/console_puts() -- floods the serial log on every boot,
regardless of whether anyone is debugging the USB stack.

Converted every "xhci:"-prefixed line to log_message() with a level
chosen by what it reports, not blanket debug:
- LOG_ERROR: allocation/mapping failures, timeouts, command failures,
  CSW signature/tag mismatches, CSW FAILED/PHASE ERROR, "not implemented"
  refusals, every "deferred ... setup failed" path
- LOG_WARN: dropped/skipped conditions (command ring busy, tracked-port
  range exceeded), unrecognized media (bad version/CRC), TUR retry
- LOG_DEBUG: routine progress (command submitted, succeeded, transfer
  completed, port connected) and expected outcomes (recognized/blank
  media)

Default log level is LOG_INFO, so the LOG_DEBUG chatter that was the
actual complaint is now silent by default and re-enabled with
--log-level=debug; LOG_ERROR/LOG_WARN stay visible so real faults aren't
buried.

xhci_log_hex32() now routes through log_message(LOG_DEBUG, ...) instead
of console_puts()/console_println() directly -- kept its own zero-padded
8-digit hex formatting rather than switching to log_message()'s %x
(which has no width control), since register values lining up in the
log is the reason this helper exists. console.h dropped from xhci.c,
no longer used directly.

Verified functionally unchanged, not just "still boots": all three
architectures reach zuse)ok>, log_message()-instrumented lines are gone
from the default-level log (grep -c xhci == 0 on all three, versus dozens
before), and the USB thumbdrive path still works end to end --
"Zuse: identity confirmed from attached thumbdrive" appears on all three
boots exactly as before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 01:25:50 -04:00
Robert Allan JamesandClaude Sonnet 5 1bd041e84d FABRIC-3.md §IV.3 item 5: pci_init() DTB path re-scoped, doc-only, nothing implementable this pass
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Re-examined the Pi 5 RP1 PCIe access re-scoping (§IV.3 item 5) to see what
was actually buildable before real hardware arrives (2026-09-17), and
found nothing safely implementable this pass -- recorded three corrections:

1. The "vmm_map_range() needed for native boot" secondary finding is
   wrong as stated: arch_mmu_init() is a stub on both non-amd64 ISAs,
   vmm.c's load_cr3() is a no-op outside __x86_64__, and riscv64's satp
   is explicitly left at Bare (this session's own §V.3 item 7 fix). No
   non-amd64 ISA ever turns its own MMU on today, so VA==PA and there is
   nothing to map. Reframed as a future concern conditional on MMU
   bring-up, not a current prerequisite.

2. No silent-wrong-answer risk exists today: with no ACPI on native Pi 5
   boot, pci_init()'s MCFG parse already fails cleanly and aarch64 takes
   no ECAM fallback, rather than misreading RP1's 37 KB indirect window
   as flat ECAM.

3. The "make config-space dispatch runtime instead of compile-time" half
   of the re-scoped fix, considered on its own, isn't separable:
   portio_read32() and siblings use outl/inl inline asm that cannot exist
   in an aarch64/riscv64 translation unit, so any case referencing them
   stays #ifdef-gated regardless -- an enum wearing the same compile-time
   selection. It only becomes real dispatch once a second non-amd64
   mechanism is actually compiled in.

Broadcom indirect access itself remains unimplemented: no QEMU model
exists for "brcm,bcm2712-pcie" (zero branches would ever execute before
real hardware, unlike the guard-exercised GIC/PLIC DTB fixes), and
config-space access alone is insufficient for RP1 to enumerate without
the real driver's controller bring-up (link training, PERST, window
setup) -- building only the index/data window would compile and boot
while silently never working.

No code changed. 3-arch acceptance run anyway per convention: all three
reach zuse)ok> with identical virtio-blk/xHCI PCI device discovery to
the pre-change baseline (commit 9b6de5d), confirming no regression from
a change that touched no source.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 01:06:24 -04:00
Robert Allan JamesandClaude Sonnet 5 9b6de5d6c7 riscv64: PLIC base address DTB-discovered, QEMU-virt constant as fallback (§V.3 item 3)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
plic_init() now takes boot_info->dtb (threaded through apic_init()) and
tries fdt_find_node_by_compatible(dtb, "sifive,plic-1.0.0") ->
fdt_find_prop_in_node(..., "reg", ...) before falling back to the
QEMU-virt-specific constant it previously hardcoded unconditionally.
Reuses the node-scoped DTB lookup primitive built for the aarch64 GIC
base fix unchanged. s_plic_base is now a runtime uintptr_t, same shape
as apic.c's s_gicd_base/s_gicc_base.

This system's QEMU/UEFI riscv64 firmware does not forward a DTB to the
guest (timer.c's own timebase-frequency read falls back too, confirmed
in this boot's own log), so only the no-DTB fallback branch is exercised
here -- the success branch (a real DTB with a matching PLIC node) stays
unverified until real Milk-V Mars hardware. FABRIC-3.md's first-drafted
claim that the success branch would run (based on a stale comment in
plic.c's own pre-fix header) was checked against the actual log and
corrected before this commit.

3-arch acceptance: amd64/aarch64 don't compile these files, so their
runs are non-regression on untouched files only. riscv64's own boot log
confirms the fallback path prints exactly as designed and boot reaches
zuse)ok> unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 00:56:25 -04:00
Robert Allan JamesandClaude Sonnet 5 90ee8deb6d riscv64: skip satp Bare-mode switch when already Bare (§V.3 item 7 audit fix)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
arch_early_init() unconditionally cleared satp on every boot, justified
only by behavior observed under QEMU/EDK2 firmware (satp.MODE=10/Sv57,
kernel identity-mapped within it). That reasoning never applied to the
native U-Boot+OpenSBI boot path, where satp is conventionally already 0
at S-mode handoff -- the unconditional clear was likely a harmless no-op
there, but on an unverified assumption.

Fix: read satp.MODE first and return early when it's already 0 (nothing
to switch away from, no safety argument needed). The unconditional
csrw/sfence pair still runs unchanged for the confirmed QEMU/EDK2 case.
No Sv39/Sv48/Sv57 page-table walker built -- out of proportion to this
finding's severity.

3-arch acceptance: amd64/aarch64 don't compile this file, so their runs
are non-regression on untouched files only. riscv64's own boot log
confirms satp.MODE = 0xa at entry, so the mode != 0 branch ran and
"satp cleared -- Bare mode, explicit" printed exactly as before the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 00:17:35 -04:00
Robert Allan JamesandClaude Sonnet 5 350287850f arch/aarch64: fix VBAR_EL2 stale comment + hardcode HVC PSCI conduit
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
FABRIC-3.md §IV.3 item 7's code audit found two more findings; fixed both
per direct instruction naming them specifically.

Comments only, no behavior change:
interrupts.c's arch_interrupts_init() doc comment (and its matching
runtime EL2 console message) claimed VBAR_EL2 was "a known gap... not yet
wired." Checked against isr.S's aarch64_install_vectors() and found that
claim stale: the assembly already branches on aarch64_current_el() and
writes vbar_el2/vbar_el1 correctly, threading the same answer through
el2_mode_flag for the IRQ trampoline's ELR/SPSR selection too. There was
no gap to close -- only the comment was wrong. Also fixed a same-vintage
one-word staleness in arch.c's aarch64_install_vectors extern comment
("installs VBAR_EL1" -> EL-aware), caught while touching this.

Real behavior change, gated to preserve existing QEMU behavior:
arch_cold_reset() hardcoded the PSCI conduit to HVC, a documented
QEMU-specific workaround (QEMU's AAVMF has no genuine EL3 to answer SMC).
Real Pi 5 hardware's ATF means EL3 exists there; confirmed further this
pass that bcm2712.dtsi's own /psci node declares method="smc" directly,
not just inferred from ATF's presence.

New aarch64_psci_conduit_init(dtb) in arch.c: fdt_valid(dtb) ->
fdt_find_node_by_compatible(dtb, "arm,psci-1.0") ->
fdt_find_prop_in_node(..., "method", ...) -- sets a module-static
s_psci_use_smc flag to 1 only when method reads exactly "smc"; every
other outcome (no DTB, no PSCI node, method="hvc", property absent)
leaves it at its default 0/HVC, preserving this system's existing QEMU
behavior exactly. arch_cold_reset() now branches on that flag between
smc #0/hvc #0. Called once from apic_init() (arch/aarch64/apic.c), the
one point in boot with boot_info->dtb already in hand -- collocated with
its consumer in arch.c rather than defined in apic.c, since
arch_cold_reset() has no boot_info of its own by the time it runs (called
from deep in VM execution, via BYE).

Three-arch QEMU acceptance run exercised both new guard branches: the
aarch64 boot log shows "PSCI: no DTB -- using HVC (QEMU virt-machine
default)" immediately before the existing GIC discovery lines, then
boots clean to zuse)ok> -- confirming the QEMU/UEFI path is unchanged.
The SMC success branch itself stays unverified until real Pi 5 hardware
runs BYE.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 00:06:41 -04:00
Robert Allan JamesandClaude Sonnet 5 dc9445abec apic.c (aarch64): fix GIC base address for real Pi 5 hardware
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
FABRIC-3.md §IV.3 item 7's code audit found GICD_BASE_PA/GICC_BASE_PA
hardcoded to QEMU virt-machine constants (0x08000000/0x08010000),
self-documented as a deliberate exception because QEMU's aarch64 UEFI
firmware never forwards a DTB. That premise doesn't hold on the native
Pi 5 boot path (rpi5_native_boot.c), which does have a real DTB and calls
this same, unmodified apic_init() -- which ignored boot_info entirely and
always programmed the QEMU addresses. Real BCM2712 GIC-400 is at
0x10_7fff9000 (confirmed against bcm2712.dtsi's axi/gicv2 nodes), a
completely different region of the address space. Fixed per direct
instruction, naming this finding specifically.

apic_init() now calls gic_bases_from_dtb() first: fdt_valid(dtb) ->
fdt_find_node_by_compatible(dtb, "arm,gic-400") ->
fdt_find_prop_in_node(..., "reg", ...), reading the first two
2-address-cell/2-size-cell entries (GICD, then GICC -- the standard
arm,gic-400 binding order). Falls back to the QEMU constants on any
failure (no DTB, no matching node), so the existing QEMU/UEFI path is
unchanged. GICD_BASE_PA/GICC_BASE_PA became s_gicd_base/s_gicc_base
(module-static uintptr_t, no longer compile-time constants on this path)
-- every MMIO call site (apic_init, apic_spi_enable, apic_read_iar,
apic_eoi_intid) now reads through them.

Three-arch QEMU acceptance run exercised the guard itself, not just
compiled it: the aarch64 boot log shows "GICv2: no DTB GIC node -- using
QEMU virt-machine defaults" followed by the unchanged "distributor+CPU
interface enabled, PPI 30" line, then boots clean to zuse)ok>. The
success branch (a real DTB with a matching node) stays unverified until
real Pi 5 hardware -- this build never has a devicetree to exercise it
against.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 23:54:39 -04:00
Robert Allan JamesandClaude Sonnet 5 0f798256a0 FABRIC-3.md: aarch64/riscv64 boot-path code audit pass (§IV.3/§V.3 item 7)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Reviewed arch/aarch64/{apic,arch,timer,interrupts}.c and
arch/riscv64/{apic,plic,arch,interrupts,timer}.c for the same class of
QEMU-virt-vs-real-hardware assumption §III item 6's amd64 audit looked for.
Report only, per this project's "identify, don't fix unless asked" rule --
no source files changed.

Findings, aarch64:
- Severe, confirmed live: apic.c's GICD/GICC base addresses are hardcoded
  QEMU-virt constants, self-documented as a deliberate exception because
  QEMU's aarch64 firmware never forwards a DTB. That premise no longer
  holds -- the native Pi 5 boot path (item 2) receives a real DTB and
  calls the same, unmodified kernel_main(), whose M4 sequence calls
  apic_init(boot_info) unconditionally (kernel_main.c:413); apic_init()
  still ignores boot_info entirely. Real BCM2712 GIC-400 is at
  0x10_7fff9000, confirmed against bcm2712.dtsi -- a different region of
  the address space entirely from the hardcoded 0x08000000. With the MMU
  off at this point in boot, this blocks reaching ok> on real hardware as
  the code stands.
- Doc-only correction: interrupts.c's own comment claims VBAR_EL2 is never
  installed ("a known gap"). Checked against isr.S and found stale -- the
  actual implementation already branches on aarch64_current_el() and
  installs vbar_el2/vbar_el1 correctly. No functional gap; the comment
  describes one the code already closed.
- arch_cold_reset() hardcodes the PSCI conduit to HVC, a QEMU-specific
  workaround (QEMU's AAVMF has no genuine EL3). The Pi 5's real ATF means
  EL3 firmware exists there, making SMC the conventional real-hardware
  conduit -- no runtime detection exists.

Findings, riscv64:
- plic.c's QEMU-virt-hardcoded PLIC_BASE is already tracked (§V.3 item 3);
  this pass confirms rather than rediscovers it.
- arch_early_init()'s satp clear is justified entirely by behavior
  observed under QEMU's EDK2 firmware; the native boot path (U-Boot+
  OpenSBI, no EDK2) doesn't share that observation, though the action is
  likely still safe since OpenSBI's handoff conventionally leaves satp=0
  already. Lowest-severity finding in the set.
- Clean: the SBI timer path and arch_cold_reset()'s SBI SRST call are both
  genuinely hardware-independent -- named as the portable pattern amd64's
  i8042-pulse reset and aarch64's hardcoded-HVC PSCI call both lack.

Doc-only change. Three-arch QEMU acceptance (amd64/aarch64/riscv64, in
order) run to confirm non-regression only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 23:31:38 -04:00
Robert Allan JamesandClaude Sonnet 5 6a6ff9353f FABRIC-3.md: amd64 boot-path code audit pass (§III item 6)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Reviewed src/starkernel/boot/uefi_loader.c and every file in arch/amd64/
(arch.c, apic.c, ioapic.c, interrupts.c, i8042.c, timer.c) for anything that
assumes SER5-specific hardware rather than standard UEFI/ACPI. Report only,
per this project's "identify, don't fix unless asked" rule -- no source
files changed.

Verified clean: uefi_loader.c's COM1 presence probe, its two-pass ACPI
2.0-then-1.0 GUID preference, the GetMemoryMap/ExitBootServices golden
path, and GOP BltOnly degradation. apic.c/ioapic.c derive LAPIC mode,
APIC timer frequency, and I/O APIC base/GSI at runtime, not from hardcoded
constants.

Four findings recorded (not fixed):
1. HPET base (timer.c) hardcoded at 0xFED00000, never cross-checked against
   the ACPI "HPET" table -- the same bug class this file's own comment
   already documents and fixed for the PM_TMR port.
2. LAPIC base always 0xFEE00000; apic_init()'s own doc comment flags MADT
   relocation as unimplemented. Narrower than that suggests: ioapic.c's
   parser already reads MadtHeader.local_apic_address, just never plumbs
   it to apic.c. MADT type 5 (Local APIC Address Override) isn't parsed
   at all.
3. i8042 PS/2 controller (i8042.c) and the legacy 8259 PIC (interrupts.c)
   are poked unconditionally with no ACPI FADT presence check, unlike this
   same file's own raw_serial_init() COM1 probe.
4. arch_cold_reset() (confirmed live via BYE, mama_forth_words.c:1437)
   pulses the i8042 reset line instead of using the FADT's standards-defined
   RESET_REG/RESET_VALUE, which doesn't depend on i8042 existing at all.

None are certain to bite on the real SER5 -- the conventions assumed hold
on the large majority of PC-compatible x86_64 systems -- but the decision
in FABRIC-3.md §III requires arguing genericity from standards compliance,
not from "it booted," and these are the concrete gaps.

Doc-only change. Three-arch QEMU acceptance (amd64/aarch64/riscv64, in
order) run to confirm non-regression only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 23:15:09 -04:00
Robert Allan JamesandClaude Sonnet 5 ecdb32e0d6 boot_media/rpi5/config.txt: Pi 5 native boot config (FABRIC-3.md §IV.3 item 6)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Researched against the official raspberrypi.com config.txt reference and one
real, working Pi 5 bare-metal project's own checked-in config.txt
(leopoldch/BatMetal), not assumed from general Pi knowledge:

- kernel=kernel_2712.img, os_check=0, device_tree=bcm2712-rpi-5-b.dtb
- enable_uart=1 added (correction to the item's original text): costs
  nothing when unused, and is the only diagnostic channel that would survive
  a framebuffer failure on the untested mailbox path (rpi5_native_boot.c).
- arm_64bit=1 dropped (correction to the item's original text): confirmed
  inert on Pi 5 by official docs.
- dtparam=pciex1 researched, deliberately omitted with rationale recorded --
  community-reported only, not needed until item 5's re-scoped RP1/PCIe work.
- os_check=0 vs. BatMetal's own config (which omits it) recorded as an open,
  unresolved discrepancy rather than silently picked.

boot_media/rpi5/README.md notes the one remaining gap this exposes:
kernel_2712.img names a file nothing currently builds (item 1's
separate-image build target is still outstanding future work).

Doc-and-config only, no compiled code changed. Three-arch QEMU acceptance
(amd64/aarch64/riscv64, in order) run to confirm non-regression only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 23:05:39 -04:00
Robert Allan JamesandClaude Sonnet 5 11c93773d0 FABRIC-3.md: pci_init() DTB path item was wrong -- re-scope, don't build the ECAM version
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Investigated before writing code, per this document's own discipline, and
found the original item 5 text's assumption doesn't hold: it framed this
as "swap the ACPI MCFG lookup for a DTB one, same flat-ECAM access
underneath." Confirmed against three primary sources that this is wrong:

- bcm2712-rpi-5-b.dts: RP1 sits under pcie2 ("brcm,bcm2712-pcie"),
  reg = <0x10 0x00120000 0x00 0x9310> -- a ~37KB window, far too small
  for a flat 256MB ECAM region.
- pcie-brcmstb.c (the real Linux driver for this compatible string):
  brcm_pcie_map_bus() computes a standard ECAM-shaped offset but accesses
  it through an IDX_ADDR/DATA_ADDR indirect index+window pair, not a
  direct MMIO read. A real third config-access mechanism, not a bigger
  version of the DTB-lookup task.

Re-scoped item 5 (§IV.3) to record the real shape: pci.c's cfg_read32/
cfg_write32 family dispatches today via #ifdef ARCH_AMD64 vs. everything
else (flat ECAM) -- but QEMU aarch64 (flat ECAM, real ACPI MCFG) and Pi 5
aarch64 (Broadcom indirect windowing) are the same build, so this needs a
runtime dispatch change to a file all three architectures currently boot
through, not a small addition. Also flagged: pci_init()'s vmm_map_range()
for the ECAM window is amd64-only today (aarch64/riscv64 rely on UEFI's
identity map); native boot has no UEFI identity map at all, so any PCIe
work here needs its own explicit mapping regardless of access mechanism.

Also corrected §V.3 item 5's now-stale "shares the same code" cross-
reference -- the Mars's JH7110 PCIe controller needs its own primary-
source check, not an assumed-shared implementation with RP1's.

Not implemented this pass -- recording the real shape is the deliverable;
building the runtime-dispatch version is a materially larger, separate
task. Doc-only change; 3-arch boot verified per the project's standing
rule, proving only that nothing regressed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 22:47:18 -04:00
Robert Allan JamesandClaude Sonnet 5 7187d68082 rpi5_native_boot.c: carve /reserved-memory out of the Pi 5 memory map
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Previously deferred (rpi5_native_boot.c's own header comment flagged this
as needing interval-splitting logic written blind against hardware not
yet in hand) -- revisited by fetching bcm2712-ds.dtsi directly rather than
assuming reserved-memory was empty or absent. It has one static child
(atf@0, ARM Trusted Firmware's own region) and one dynamic child
(linux,cma, size/alloc-ranges only, no fixed reg) -- the dynamic one is
skipped, nothing fixed to carve and no allocator this early to service it
against anyway.

Adds two fdt.c primitives: fdt_find_node_by_name() (reserved-memory has
neither compatible nor device_type per DT spec) and fdt_next_child_node()
-- one exported symbol, not the two-primitive general sibling-walker
originally sketched, collapsed after review since the only real use here
is "iterate one node's direct children."

collect_reserved_ranges() reads each child's own #address-cells/
#size-cells with a fallback to root's only if absent -- confirmed
necessary, not just defensive: reserved-memory's own declared <2>/<1>
genuinely differs from root's <2>/<2>. emit_region_with_carveouts()
clips a sorted reserved-range list against each RAM region, emitting
alternating EfiConventionalMemory gaps and EfiReservedMemoryType
carve-outs (insertion sort, no libc qsort in freestanding).
RPI5_MAX_MEMMAP_ENTRIES is the exact worst-case count, recomputed rather
than estimated -- the rpi5_mailbox.c buffer-size bug is the standing
lesson for this pattern.

Compile-only-verified; nothing in the existing UEFI/QEMU path calls
rpi5_native_boot(), so this cannot be exercised until real hardware.
Verified 3-arch boot to ok>/zuse)ok>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 22:24:57 -04:00
Robert Allan JamesandClaude Sonnet 5 67e3fb7459 rpi5_native_boot.c: Pi 5 DTB->BootInfo constructor (FABRIC-3.md §IV.3 item 2)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
rpi5_native_boot() populates the existing BootInfo struct from the
devicetree instead of UEFI protocols, then calls the existing, unmodified
kernel_main() -- the crux of why most of M1-M9 stays shared between the
UEFI and native boot paths. native_rpi5_entry.S now tail-calls into it
instead of halting.

memory_map is built from /memory's own reg, honoring the *root* node's
#address-cells/#size-cells (confirmed against bcm2712.dtsi's actual root
node -- <2>/<2> -- not assumed; a wrong cell width here would compile and
boot clean in QEMU while silently corrupting the real memory map on real
silicon). Required a new fdt_find_node_by_device_type() since /memory is
identified by device_type = "memory" per DT spec, not compatible.

args comes from /chosen's bootargs fed into the existing
cmdline_parse_ascii() (confirmed pure C99 with no UEFI coupling before
reusing it). framebuffer comes from the already-built
rpi5_mailbox_get_framebuffer() at a fixed 1920x1080x32 default -- no EDID
query exists in this codebase, flagged rather than guessed past.

Deliberately scoped out, not silently skipped: /reserved-memory is not
parsed. Carving reserved sub-ranges out of /memory's span needs
interval-splitting logic that would be written blind against hardware not
yet in hand -- exactly the kind of code that hides a bug until real
silicon. pmm.c's Pass 3 only ever clears pages this file lists as
EfiConventionalMemory, so the gap is "less usable RAM than optimal," never
"reserved RAM wrongly marked free." Left as its own future item.

Also fixes a real link failure this work surfaced: boot/cmdline.c was only
in LOADER_SRCS_BASE (the .efi target), not KERNEL_SRCS_BASE (the separate
.elf target arch/aarch64/*.c also wildcards into) -- added it there too.

Compile-only-verified; nothing in the existing UEFI/QEMU path calls
rpi5_native_boot(), so this cannot be exercised until real hardware.
Verified 3-arch boot to ok>/zuse)ok>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 14:42:32 -04:00
Robert Allan JamesandClaude Sonnet 5 281de9547c native_rpi5_entry.S: Pi 5 native (non-UEFI) boot entry stub (FABRIC-3.md §IV.3 item 1)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
rpi5_native_start masks x0 down to the documented 32-bit DTB-pointer range
(the firmware's own entry protocol leaves the upper 32 bits unspecified),
stores it into g_rpi5_dtb_ptr for the still-open DTB->BootInfo constructor
(item 2) to read, then switches sp to a dedicated 2 MiB BSS stack -- this
path has no EDK2 boot stack to inherit, unlike every other entry path in
this codebase.

Intentionally halts (wfe/b loop) afterward rather than tail-calling into
item 2's constructor, which doesn't exist yet -- no stub function pretending
to be more than it is.

Not yet linked at the real 0x80000 load address; that needs its own linker
script/build target, not scoped into this item. Compiles and links into the
existing ARCH=aarch64 QEMU/UEFI acceptance build as dead code (ELF kernel
build's KERNEL_ASM wildcards every *.S in arch/aarch64/; nothing there
branches to it), same as rpi5_dtb.c/rpi5_mailbox.c before it.

Verified 3-arch boot to ok>/zuse)ok>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 14:12:42 -04:00
Robert Allan JamesandClaude Sonnet 5 a32b0ebcbe rpi5_mailbox.c: wire the VideoCore mailbox message protocol (FABRIC-3.md §IV.3 item 3)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
New rpi5_mailbox_get_framebuffer() sends one property-tag request buffer
(phys size, virt size, depth, pixel order, virtual offset, allocate-buffer,
get-pitch) over the register layout rpi5_dtb.c already discovers, populating
an Rpi5FramebufferInfo kept in exact field-for-field sync with uefi.h's
FramebufferInfo so console.c/vt100.c/framebuffer.c need no downstream changes
once this is wired into a real entry stub.

Register offsets (+0x00/+0x18 MBOX0 read/status, +0x20/+0x38 MBOX1
write/status) confirmed against a Pi-5-specific bare-metal reference,
independently cross-checked against this codebase's own rpi5_dtb.c
translated base address.

Caught and fixed a real buffer-overflow bug before compiling: the static
request buffer was sized 32 words against an actual 35-word requirement for
the 7-tag sequence, recomputed exactly rather than re-estimated; resized to
40 words.

Two things flagged as genuinely unverified against real hardware (not
guessed past the comment): the allocate-buffer tag's request-size field
value, and whether its response address needs classic bus-alias masking on
Pi 5 specifically.

Compile-only-verified -- no caller yet (that's the still-open entry-stub/DTB
constructor items). Verified 3-arch boot to ok>/zuse)ok>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 14:00:32 -04:00
Robert Allan JamesandClaude Sonnet 5 ca52ce8243 rpi5_dtb.c: wire fdt.c's node-scoped lookup into the Pi 5 UART/mailbox addresses
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
New include/starkernel/rpi5_dtb.h / src/starkernel/arch/aarch64/rpi5_dtb.c:
rpi5_uart_base()/rpi5_mailbox_base(), each finding their peripheral by
compatible string ("arm,pl011" / "brcm,bcm2835-mbox") via fdt_find_node_by_
compatible() then reading its "reg" via fdt_find_prop_in_node().

Found and fixed a real translation gap before it could have silently
produced a wrong address: confirmed directly against bcm2712.dtsi
(raspberrypi/linux) that both peripherals live under one "soc"
simple-bus node whose ranges property adds a fixed 0x10_0000_0000
offset to every child reg value. fdt.c's reader deliberately doesn't
apply ranges translation generally (not a general devicetree
library); this file applies that one, fixed, SoC-wide offset
explicitly, documented with the exact devicetree excerpt that
confirmed it.

Compile-only verification -- no caller wired in yet, these two
functions are what the still-open entry-stub and mailbox-framebuffer-
driver punch-list items will call. Verified 3-arch boot to ok>
(amd64/aarch64/riscv64, each in the foreground; rpi5_dtb.o confirmed
built on aarch64, the only arch that compiles this file).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 13:36:08 -04:00
Robert Allan JamesandClaude Sonnet 5 5e46f18fd9 fdt.c: node-scoped lookup extension (FABRIC-3.md SSIV.3/SSV.3 shared item)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
Adds fdt_find_node_by_compatible() and fdt_find_prop_in_node() to the
minimal FDT reader -- the extension fdt.h's own header comment already
flagged as a known future need ("item 0.6 will need node-scoped reg
lookups"), now with real consumers: the Pi 5's UART/mailbox register
addresses (native boot, no ACPI) and the Milk-V Mars's real PLIC base
address (currently hardcoded to QEMU-virt's own value).

fdt_find_node_by_compatible() matches any entry in a node's
NUL-separated "compatible" list, first match in document order.
fdt_find_prop_in_node() scopes to that one node's own direct
properties only -- stops at the first child node or the node's own
end, per the DT spec's ordering guarantee that a node's properties
always precede its children. Same minimal, non-tree-building,
single-linear-scan-per-call style as the existing reader; no new
state, no allocation.

Compile-only verification -- no caller wired in yet, this is the
shared primitive both boards' own punch-list items will call once
built. Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in
the foreground).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 13:05:42 -04:00
Robert Allan JamesandClaude Sonnet 5 8717416d36 FABRIC-3.md: version correction -- LITHOS_VERSION back to 2.0.0, plus a rename-gap fix
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
LITHOS_VERSION 2.0.1 was premature: per this project's own versioning
policy, 2.0.1 claims SER5 hardware-track progress (RDRAND backend +
thumbdrive image) that was never actually verified on real hardware --
that verification is FABRIC-3.md's own open topic. Reset to 2.0.0
(still a QEMU-only release, correctly). Verified 3-arch boot shows
"LithosAnanke v2.0.0" in each serial log directly, not assumed from the
Makefile edit alone.

Also closes a real gap found in today's earlier FABRIC-series rename:
Makefile.starkernel, Kconfig.kernel, scripts/bleach_zuse_img.sh, four
proof/*.thy files, and isr.S were never swept -- the original file list
only matched *.md/*.c/*.h/*.4th, silently skipping every other
extension. Fixed with the same safe placeholder substitution.
.claude/settings.local.json's historical permission-grant log and
ClaudeEXPORT/'s frozen export were deliberately left untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 11:53:44 -04:00
Robert Allan JamesandClaude Sonnet 5 fcba528273 FABRIC-3.md: Task 1 closed -- master fast-forwarded to v2.0.1, verified
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
master (d2a0305) was a strict ancestor of v2.0.1 (72c14cb) -- pure
fast-forward, no real merge needed. Fast-forwarded local master,
pushed to origin (58c59e8..72c14cb). Verified on a genuinely clean
master tree rather than inferring from the fast-forward: hosted build
clean, 3-arch kernel acceptance (amd64/aarch64/riscv64, each in the
foreground) all reached ok>/zuse)ok> and shut down cleanly, identical
to v2.0.1's own last acceptance pass. master and v2.0.1 are now
identical on both local and origin.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 11:37:29 -04:00
Robert Allan JamesandClaude Sonnet 5 b031b802e3 Rename FABRIC series: FABRIC.md->0, FABRIC-2.md->1, FABRIC-3.md->2, FABRIC-4.md unchanged
FABRIC.md -> FABRIC-0.md
FABRIC-2.md -> FABRIC-1.md
FABRIC-3.md -> FABRIC-2.md (the current/living document)
FABRIC-4.md unchanged (new #3 to follow separately)

Every cross-reference repo-wide updated to match, including doc-comment
citations inside kernel source (.c/.h) files -- done via an ordered
placeholder substitution (FABRIC-3.md->placeholder2, FABRIC-2.md->
placeholder1, FABRIC.md->placeholder0, then placeholders resolved to
final names) in a single pass per file to avoid double-shifting
already-renamed references.

One line in capsules/font.4th grew past the 64-char block-format limit
as a side effect of the longer filename; shortened it and reverified
with mkcapsule --lint (34/34 pass) before rebuilding.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the
foreground) after the fix; logs and DoE CSVs from this session's
verification runs included per this repo's own audit-artifact
convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 11:22:51 -04:00
Robert Allan JamesandClaude Sonnet 5 eeceec21a5 FABRIC-3.md §I.5: Milestone 7 trust tiers (QEMU-vs-real-hardware), closing it
Closes the contributor-capsule/trust-tier punch-list item. Decided
direction: QEMU-vs-real-hardware conditional enforcement.

Found before building on that decision: the obvious mechanism (expose
TimerInfo.vm_mode) only works on amd64 -- aarch64 and riscv64 both had
vm_mode hardcoded to 1 unconditionally, meaning they'd always report
"running under QEMU" even on real hardware. Built real detection for
both instead of shipping that: aarch64 checks the ACPI RSDP's OEM ID
for QEMU's "BOCHS " SeaBIOS-heritage signature; riscv64 checks the
devicetree root compatible property for "qemu". Confirmed vm_mode was
otherwise unread anywhere else in either file first -- zero risk to
existing timing behavior.

CAPSULE_FLAG_CONTRIB (mkcapsule.c: FLAG_CONTRIB) path-matches on
capsules/contrib/, mirroring FLAG_MAMA_INIT's exact-match pattern.
contrib_capsule_refused() (capsule_birth.c) enforces: no additional
check under QEMU (same WARN-only as everything else); on real hardware,
a contrib capsule additionally requires CAPSULE_SIG_OK, since it has no
other provenance to fall back on. Wired into capsule_birth_baby() and
capsule_run_experiment().

Also updates §I.7 (Milestone 9): its stated precondition (Milestone 7
closing) is now met, flagged as stale rather than treated as a green
light to design networking from nothing.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the
foreground) -- compile/boot verification only; the real-hardware
enforcement branch is unverifiable from this environment, same as all
of §I.6. logs and DoE CSVs from this session's verification runs
included per this repo's own audit-artifact convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 11:04:25 -04:00
Robert Allan JamesandClaude Sonnet 5 5567d03c12 FABRIC-3.md §I.4: magic-number content-type detection, closing Milestone 6
Closes both remaining Milestone 6 (PKI) punch-list items.

Signature-status column: doc-only closure -- the underlying need was
already redirected to capsules/BLOCK_MAP.md's real Signed column
(2026-08-26); checking the box off as moot-as-worded rather than
leaving an accurate-but-permanently-unchecked marker.

Magic-number content-type detection (Section U item 14): built in
tools/mkcapsule.c. detect_content_type() classifies a file's actual
leading bytes (TTF/OpenType sfnt tags, DER's 0x30 SEQUENCE tag, or a
printable-ASCII/TAB/CR/LF heuristic for text) against
expected_type_from_ext()'s .4th/.md/.der/.ttf mapping; process_file()
warns on mismatch, never refuses -- same WARN-first rollout this
project already used for capsule signing. Verified against every real
capsule in the repo (38 files) with zero false positives. This is the
shared primitive Milestone 7's contrib-capsule validation can reuse
next.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the
foreground); logs and DoE CSVs from this session's verification runs
included per this repo's own audit-artifact convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 10:45:57 -04:00
Robert Allan JamesandClaude Sonnet 5 4018fe8b04 FABRIC-3.md §I.2: FIRSTTOUCH + migration state machine (blk_meta_relocate_devblock)
Closes the block-subsystem punch-list item -- built exactly to §F.11's
already-decided algorithm after re-verifying it against current
blk_meta_t (a 2026-09-03 re-scoping note had wrongly claimed the chain
fields no longer existed; they do, untouched by BMAPFMT).

blk_firsttouch_claim(): one linear scan of Artemis's own device
(new blk_get_first_disk_range(), correctly bounding the scan instead of
the global multi-device LBN space), scattered-chain claim via
prev_block/next_block/chain_length, owner_fp stamped on every member
devblock, fails outright with no partial claim.

blk_meta_relocate_devblock(): the real migration primitive -- bridges
the existing FORTH-block-granularity blk_subsys_relocate_block() up to
devblock granularity (BLK_PACK_RATIO=3, corrected mid-design), running
it 3x and transferring blk_meta_t ownership fields. The "migration
state machine" turned out to be just the 2 states BLK_FLAG_MIGRATING
already reserved; the real design work was the trigger. Two were
scoped in conversation (overflow onto Artemis; heat-based wear
leveling); heat/wear-leveling is built and wired into sk_repl_idle()
via blk_meta_t.write_count. Overflow is deliberately left open,
precisely scoped (needs a slot-lookup-by-device-pointer call site
threaded from WIREBIND) rather than guessed at.

Also flagged, not fixed: BMAPFMT's owner_fp/CLAIMED and the pre-existing
BAM allocator are two parallel, unreconciled accounting systems --
FIRSTTOUCH/relocate only touch the former.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the
foreground); logs and DoE CSVs from this session's verification runs
included per this repo's own audit-artifact convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 10:33:47 -04:00
Robert Allan JamesandClaude Sonnet 5 1d468a65b1 FABRIC-3.md §I.1: (user) console prompt segment, closing the 4.4s->4.3->1.11 chain
Extends the REPL prompt to "[VM name] (user) ok>" (e.g. "[Hera]
(zuse) ok>") per the locked FABRIC.md §4.4s spec, unblocked by this
session's own §I.3/§I.8 identity-tracking work. Adds
capsule_wirebind_attached_username() alongside the existing tracked
VMUuid, and a new sk_print_prompt() helper (repl.c) that all three
prompt call sites now go through -- checks Zuse first, then a WIREBIND
user, prints nothing when neither is attached.

Closes 4.4s, 4.3 (console umbrella), and formally settles 1.11
(dirty-event granularity) as region-based per FABRIC-2.md's own "no
independent path" ruling -- a decision closure only, not an
implementation, so §17.4 (framebuffer heat/decay physics) stays open,
re-scoped precisely: blocked on the dirty-region-tracking mechanism
existing, not on 1.11's decision.

Documents a reported-but-unreproduced terminal defect (§I.9) as a new
punch-list item -- investigated the readline/keyboard-bridge code
paths, found nothing conclusive, needs a live repro with a serial log
before it's actionable.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the
foreground); logs and DoE CSVs from this session's verification runs
included per this repo's own audit-artifact convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 09:42:26 -04:00
Robert Allan JamesandClaude Sonnet 5 60d9c2520e FABRIC-3.md §I.3/§I.8: WIREBIND EJECT/detach + EXPIRE re-scoped as logout
Closes §I.3 (Milestone 5 remainder): WIREBIND now tracks which VM is
attached via the home-blocks USB path, and a new EJECT word plus the
existing hot-unplug signal both flush/reset-console/kill through it
(FABRIC-3.md §F.10).

Closes §I.8 (EXPIRE/ACL), re-scoped: the original "admit the zuse
session as a Stadium patron and reap on TTL" plan was invalidated a
second time -- Zuse authenticates directly onto Hera, who is patron
zero and permanently pinned, so there is no patron for a reap sweep to
ever find. Built instead as a detach-triggered logout
(capsule_zuse_boot_logout()), the same trigger EJECT/hot-unplug use for
regular WIREBIND users, so neither identity is a special case. Required
a companion fix: install_and_activate() used to skip re-running
ACL-ZUSE-BOOT whenever the cert was already installed, which made a
logout permanent for the rest of the boot; the outer re-attach gate now
checks zuse_session (clears on logout) instead of zuse_cert_installed
(a deliberate permanent one-way ratchet, left untouched).

Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the
foreground) after both steps; logs and DoE CSVs from this session's
verification runs included per this repo's own audit-artifact
convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 09:21:01 -04:00
Robert Allan JamesandClaude Sonnet 5 cb6e079a73 §H.12 step 20: CH-REQUEST initiator-only gate (MY-CH-ID)
Added VARIABLE MY-CH-ID to messaging.4th (fail-closed -1 default) and
set it per-VM in hermes/init.4th and artemis/init.4th. CH-REQUEST now
refuses if the caller-supplied 'from' doesn't match the calling VM's
own id, closing a real spoofing gap found while implementing this.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgooKd5hJNtTYqB6CyK5f9
2026-09-03 12:09:29 -04:00
Robert Allan JamesandClaude Sonnet 5 7d53344875 §H.12 step 19: ZUSE-ELIGIBILITY-ADD word, no gating (corrected mid-step)
Plain FORTH word wrapping zuse_eligibility_add() unconditionally. Two
wrong first attempts (C-level zuse_session check, then a FORTH wrapper
checking it) both corrected: zuse_session isn't a special axis needing
its own gate anywhere -- Zuse's authority is the absence of any ACL
restricting her, not a flag any word checks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgooKd5hJNtTYqB6CyK5f9
2026-09-03 11:38:07 -04:00
Robert Allan JamesandClaude Sonnet 5 f4615cf605 §H.12 step 18: zuse_eligibility.c -- read/add/membership-check
is_member() (fail-closed) and add() (idempotent, chains new devblocks
onto the tail as needed) over the item-17 eligibility-list devblock
chain, mirroring capsule_zuse_boot.c's magic/version/CRC-64 validation
convention. No callers yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgooKd5hJNtTYqB6CyK5f9
2026-09-03 11:24:24 -04:00
Robert Allan JamesandClaude Opus 5 46d89dd5be §H.12 step 16: block-acl.4th policy capsule -- Phase 5 (BMAPFMT) complete
capsules/block-acl.4th (blocks 4019-4020, next free range per
BLOCK_MAP.md): BLK-ACL-CHECK (block# -- allow?), a real fast-deny check
mirroring vm.c:611-624's pattern for blocks instead of words. First
touch lazily claims the block (allow=1, TTL=256, same base as ACL.4th's
own), matching the word card's default-permissive baseline. Not a stub --
genuinely does something on every call.

Loaded via a new EXEC line in init.4th right after ACL.4th's own;
confirmed ACL.4th's own activation is unaffected. Passed mkcapsule
--lint. Live-tested via QMP keystrokes: 1 BLK-ACL-CHECK executed cleanly.

Phase 5 (BMAPFMT) is now fully complete -- field layout, flags bits, C
accessors, FORTH wrappers, and a real policy word.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 07:44:28 -04:00
Robert Allan JamesandClaude Opus 5 405c713c4a §H.12 step 15: FORTH wrappers for BMAPFMT block-ACL fields
BLK-ACL-ALLOW@/!, BLK-ACL-TTL@/!, BLK-OWNER@ registered in block_words.c.
BLK-OWNER@ packs the 8-byte owner fingerprint into one cell (cell_t is
int64_t). No BLK-OWNER! -- ownership stays a controlled C-only operation.

Live-tested via QMP keystrokes on a running instance: 1 BLK-ACL-ALLOW@
executed cleanly against a real block. Verified 3-arch boot to ok>
(amd64/aarch64/riscv64) plus a hosted sanity build (shared source).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 07:36:57 -04:00
Robert Allan JamesandClaude Opus 5 15e6836ca3 §H.12 step 14: C accessors for BMAPFMT block-ACL fields
blk_owner_fp_get/_set, blk_acl_allow_get/_set, blk_acl_ttl_get/_set,
blk_flags_get/_set -- thin read-modify-write wrappers over the existing
blk_get_meta()/blk_set_meta() (caching/dirty-tracking already owned
there). Sets up the C-primitive layer FORTH wrappers (step 15) will call,
mirroring the word-level ACL system's own split.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 07:29:01 -04:00
Robert Allan JamesandClaude Opus 5 c19cc07ee3 §H.12 step 13: blk_meta_t flags bit constants
BLK_FLAG_CLAIMED/BLK_FLAG_MIGRATING/BLK_FLAG_STALE (bits 0/1/2), matching
the decided §F.4/§H.6 layout. Orthogonal bits, not a mutually-exclusive
enum.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 07:23:27 -04:00
Robert Allan JamesandClaude Opus 5 edd7effb5a §H.12 step 12: BMAPFMT field layout in blk_meta_t
Replaced the old 40-byte owner_id/permissions/acl_block/signature[2]
with owner_fp[8]/acl_allow/acl_ttl (u32)/acl_reserved[3]/reserved_future,
matching the decided §F.4/§H.6 layout.

Found a pre-existing bug via a real offsetof/sizeof compile check
(not hand math, per this step's own instruction): sizeof(blk_meta_t)
was already 344, not the 341 its own BLK_META_PER_BLOCK constant and
"341-byte slice" comment claimed -- harmless since that constant has
zero callers anywhere. New size after this edit's own alignment
padding is 336. Added a _Static_assert matching blk_volume_meta_t's
existing precedent, and fixed the stale comment to point at it.
BLK_META_PER_BLOCK itself untouched -- unused, out of scope.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 07:18:41 -04:00
Robert Allan JamesandClaude Opus 5 a268abe925 §H.12 steps 10-11: creator-ceiling enforcement, birth-time ACL snapshot
dictionary_snapshot_acl_from_parent(child, parent): walks the child's
dictionary, copies acl_allow/acl_mode/acl_pinned/acl_ttl from the
parent's matching word (by name, via vm_find_word() -- FIND's own
lookup, not modified) onto the child's entry. One-time snapshot at
birth, no live sync, matching H.3's decided rationale (a program
developed against one ACL set must not have it silently changed by
later parent changes).

Called once, after dict_hash/parity logging rather than before -- the
snapshot depends on the parent's current ACL state, which can vary
run-to-run once Zuse elevations exist, so applying it earlier would
break the "same capsule twice produces the same dict hash" determinism
invariant.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 06:44:58 -04:00
Robert Allan JamesandClaude Opus 5 ed86a759e1 §H.12 steps 7-9: thread real parent VMUuid through the birth call chain
Session.parent now comes from the actual birthing VM's own
stadium_vm_id, not a hardcoded vm_uuid_hera(). Added a VMUuid parent
parameter to capsule_birth_baby() and, one level up, to
capsule_console_birth()/capsule_runcap_birth() (neither had a VM* in
their own signature, but every caller did). Updated all 6 real call
sites: BIRTH, CAPSULE-BIRTH, CONNECT-ARTEMIS, CONNECT-HERMES,
RUNCAP-TEST, PAIR-TEST (mama_forth_words.c) and the console+user birth
pair in capsule_wirebind_try_attach() (capsule_wirebind.c). Two
functions had their vm parameter marked __attribute__((unused)), now
genuinely used -- attribute removed.

Steps 8 (Session.name from capsule name) and 9 (identity defaults to
installed=0) were already satisfied by step 5's existing
session_register() call and its identity-zeroing -- confirmed by
inspection, no further code needed.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 06:38:32 -04:00
Robert Allan JamesandClaude Opus 5 09998af999 §H.12 steps 5-6: pin Hera/Hermes/Artemis in generic capsule-birth admission
capsule_birth.c's generic admission block now registers a session for
every born VM (session_register) and pins it (session_set_pinned) when
the birthing capsule is Hera/Hermes/Artemis.

Bug found and fixed via a temporary probe (written, run, captured,
reverted): the fleet-foundation name check first used an exact-match
comparison against "Hermes"/"Artemis", but capsule_name is actually
"hermes:init.4th"/"artemis:init.4th" (the real namespace:filename
convention) -- the check silently never matched, both would-be-pinned
VMs stayed unpinned. Fixed with a new vm_name_prefix_eq_nocase() helper
matching everything before a literal ':'. Probe confirmed pinned=0 before
the fix, pinned=1 after, on all relevant VMs.

Session.parent is hardcoded to vm_uuid_hera() for now (every birth
through this path is Hera-initiated today); step 7 generalizes this to
the actual birthing VM's own id.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64) on the final,
probe-free code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 06:26:50 -04:00
Robert Allan JamesandClaude Opus 5 67793ea4a1 §H.12 step 4: Hera registers as session zero; punch list to checkboxes
Rewired stadium_birth_hera() to admit unpinned then register through
session_register()/session_set_pinned() instead of setting
STADIUM_FLAG_PIN directly on the candidate header. Self-referential
parent (vm_uuid_hera(), vm_uuid_hera()), matching capsule_run.h's
parent_vm_id == vm_id root convention. Soft-fail, non-fatal, if
session_register() fails -- Hera's actual Stadium admission is what the
patron-zero invariant is about. Wired session_boot_init() into
kernel_main.c right after stadium_boot_init(), before stadium_birth_hera().

Also converted §H.12's punch list from bold "DONE" markers to this
document's established - [ ]/- [x] checkbox convention (already used
throughout §A), for consistency.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64), no soft-fail message
on any arch, Hermes/Artemis births unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 06:15:53 -04:00
Robert Allan JamesandClaude Opus 5 a621131ef6 §H.12 step 3: session_set_pinned/session_is_pinned pin-authority choke point
session_is_pinned() reads Session.pinned directly (authoritative, no
Stadium re-derivation); session_set_pinned() writes both Session.pinned
and the mirrored STADIUM_FLAG_PIN bit on the session's own patron cell,
keeping Stadium's internal eviction/admission logic (which must stay
self-contained) in sync without it calling back into session.c.

Added Session.stadium_cell (index into stadium_cells()) -- necessary
plumbing not in the original H.2 field list; the choke point can't reach
the right patron header without it. Moved STADIUM_FLAG_PIN from a
stadium.c-private #define to stadium.h (public) so session.c can
reference it without a duplicate definition.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 06:10:11 -04:00
Robert Allan JamesandClaude Opus 5 6d9fe3f515 §H.12 step 2: session-slot table, session_find/session_register
src/starkernel/vm/session.c: kmalloc'd-at-boot slot table sized from
stadium_max_vm_count() (mirrors stadium.c's own StadiumVMQuota, not a
fixed compile-time array as originally planned -- that table was already
moved off a fixed array for the same "population isn't knowable in
advance" reason). session_boot_init()/session_find()/session_register()
implemented for real, no stubs; session_register() zeroes identity and
leaves pinned=0, matching VMIdentity's own documented default and
deferring pin policy to callers. Added session.c to Makefile.starkernel's
explicit source lists. No callers yet.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 06:03:14 -04:00
Robert Allan JamesandClaude Opus 5 668e36bb17 §H.12 step 1: add Session struct (type only)
include/starkernel/session.h: vm_id (VMUuid), pinned (int, authoritative
over Stadium's STADIUM_FLAG_PIN per H.10), parent (VMUuid), name (fixed
64-byte buffer), identity (embedded VMIdentity, reusing the existing
type rather than inventing a new one). No logic yet, no callers -- next
step wires the session-slot array and register/find functions.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 05:57:01 -04:00
Robert Allan JamesandClaude Opus 5 f81e9c92bc Fix framebuffer console: scroll drift causes progressive line overlap in TTF mode
fb_scroll_rows() hardcoded the pixel distance it physically shifts the
framebuffer by as char_rows * 16 * scale -- the bitmap-font (font_8x16.c)
cell height -- regardless of which glyph mode vt100.c actually had active.
In TTF mode (the REPL's default, cell height 24px via VT100_TTF_CELL_H_PX)
this meant every scroll_up(1) call physically shifted the framebuffer by
only 16px while the text model (g_vt.rows, py_of()) placed each row 24px
apart. That 8px-per-scroll shortfall compounds with every subsequent
scroll: a few scrolls barely show it, but enough scrolls -- or scrolling
quickly, which is just many scrolls in a short span -- accumulates into
visible pixel overlap between rows, with newer lines drawn on top of the
tail end of older ones.

fb_scroll_rect() (the box-confined scroll added later for 4.4t) already
carried a doc comment calling this out explicitly, describing its own
explicit pixel_rows parameter as the fix for fb_scroll_rows()'s "fixed
16px-row assumption" -- fb_scroll_rows() itself was just never updated to
match.

Fixed by changing fb_scroll_rows()'s parameter from an implicit char_rows
count to an explicit pixel_rows count (matching fb_scroll_rect()'s
existing convention), and having its one caller (vt100.c's scroll_up())
pass lines * cell_h() -- the real active cell height -- instead of a raw
line count for the callee to guess at.

Verified: booted amd64 to the REPL (TTF mode active per sk_repl()'s own
console_fb_enable_ttf() call), let boot chatter + WORDS output scroll the
screen through thousands of accumulated scroll_up() calls, then measured
every visible line's y-position via a QMP screendump. Spacing held at a
perfectly consistent 24px (TTF cell height) top to bottom with zero drift
-- the old hardcoded-16px bug could not have produced that after this many
scrolls. Re-verified boot to ok> on all three architectures
(amd64/aarch64/riscv64) per repo acceptance policy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 17:25:06 -04:00
Robert Allan JamesandClaude Opus 5 c14324f498 Fix framebuffer console: idle heartbeat corrupts in-progress input line
sk_repl_idle() (called every ~1s from sk_console_readline()'s idle loop)
opens with console_ensure_line_start(), which unconditionally forces a
newline whenever the console isn't at a line boundary -- including
mid-edit, after characters have been typed and echoed but before Enter.
This fired on every elapsed SK_IDLE_BEAT_INTERVAL regardless of whether
sk_repl_idle() had anything to print, visually snapping the in-progress
input line to a fresh empty line -- indistinguishable from Enter having
been pressed. Most noticeable on the space key since it's the most common
key hit during a pause.

Gate the idle beat on n == 0 (no in-progress edit), mirroring the n > 0
guard the prompt reanchor logic just below already uses. Deferring the
xhci/block-sync idle service by at most one more interval while a line
is being edited is within its own documented "coarse cadence, cheap
early-exit" tolerance.

Verified: reproduced via QMP send-key against a live amd64 QEMU boot
(multi-character line typed with pauses across several idle intervals
stayed intact after the fix, where it previously broke on each interval).
Re-verified boot to ok> on all three architectures (amd64/aarch64/riscv64)
per repo acceptance policy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 17:12:21 -04:00
Robert Allan James daf9021ea5 Initial commit
Signed-off-by: Robert Allan James <robert.allan.james@gmail.com>
2026-09-01 12:09:20 -04:00
Robert Allan James 5689c397fc Bug-fix sweep: repl reentrancy, virtio/blocksys bounds, identity CRCs, LOG_LINE_MAX
Code review fixes, all compile clean (hosted gcc + aarch64/riscv64 kernel flags):

- repl.c (H1): reentrancy guards on the MSG-TICK idle pump. sk_repl_idle()
  now defers when Hera is mid-interpret (g_mama_interpreting) or when its
  own vm_interpret is on the stack (g_idle_pump_active), so a blocking
  KEY/EXPECT/QUERY inside a dispatched line can no longer re-enter the
  interpreter and clobber the in-flight input buffer.
- virtio_rng.c: clamp device-returned used_len to VRNG_BUF_SIZE before the
  caller's data_buf copy, closing a device-controlled OOB read.
- block_subsystem.c: first-write path now keys off created_time==0 instead
  of dead magic==0 so fresh blocks get a real created_time stamp; first_free/
  last_allocated fixed to absolute Forth LBNs (set in blk_compute_fresh_geometry
  from slot->start_lbn, no longer the wrong physical-BAM-index values from
  compute_totals_from_B); physical-bounds guard on blk_meta_zone_read/write
  prevents unsigned underflow on a corrupt fence >= device size.
- capsule_zuse_boot.c / capsule_wirebind.c: identity seed validated magic ->
  version -> CRC-64 (compute_crc64 over offsetof(crc)) before trusting it,
  so a corrupt/format-mismatched record is refused, never loaded.
- log.h / starkernel/log.h: unused LOG_LINE_MAX 256 renamed LOG_MSG_LINE_MAX
  to lift the include-order collision with vm.h's LOG_LINE_MAX 64; stale
  include-order comments dropped (kernel_main.c, shim.c, capsule_birth.c).
- FABRIC-3.md: three stale-doc carry-forward items closed [x] with cbe7b49
  notes.

Real KEY/?TERMINAL/QUERY/EXPECT bodies (console WIP):
- repl.h/repl.c: sk_console_getkey()/sk_console_key_available()/
  sk_console_readline() public bodies; non-destructive peek buffers the
  found byte so a following KEY returns it.
- shim.c: getchar()/fgetc()/fgets()/sf_terminal_ready() routed through the
  real console paths instead of stubs; sf_terminal_ready() in platform_io.h
  with sf_terminal_ready() implemented for the hosted build (linux/io.c,
  POSIX select on fd 0) wired into Makefile.
- io_words.c: ?TERMINAL now returns actual terminal-readiness, not constant 0.

Artifacts: minted disk/artemis.img + rebuilt lfs kernel; BLOCK_MAP.md,
doe csv + qemu log regenerated.
2026-08-28 23:28:10 -04:00
Robert Allan JamesandClaude Sonnet 5 849b83b727 Zuse default-attach: xHCI initial-port-scan fix, ZUSEDISK wiring, mismatched-marker resync
xhci_bringup() now scans for already-connected ports at bring-up
(xhci_scan_ports_for_already_connected()), not just later hotplug events,
so a USB device present on the QEMU command line at launch is detected.
Makefile.starkernel attaches disk/zuse.img on the xhci0 bus by default in
all three arch qemu targets (ZUSEDISK=, empties for a bare boot).

capsule_mint_identity() gained a drive_known_blank param to skip a fully
redundant second homeblocks_sig_check() when the caller already confirmed
HOMEBLOCKS_SIG_BLANK itself.

Root-caused what looked like a hang after the drive attached: Artemis's
fence still carried a genesis marker from before a mid-session reformat,
while the reformatted disk/zuse.img read back BLANK -- a mismatched pair
capsule_zuse_boot_try_attach() correctly declined to act on, leaving the
boot idling at a plain ok> with nothing left to log (indistinguishable
from a hang under slow TCG). Fixed by zeroing both disk/artemis.img and
disk/zuse.img at their original sizes, giving a matched blank pair.
Verified full three-architecture acceptance: amd64 fresh genesis-mint,
aarch64/riscv64 clean reload against the same now-minted images.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KBjfeLPo71sUQ8zC7V7P5m
2026-08-28 21:42:14 -04:00
Robert Allan JamesandClaude Sonnet 5 21bca315ff Phase C: distributed messaging capsule + idle-loop pump
Extract the messaging vocabulary (arenas, MSG-*/CH-*/MBR-* words) out of
capsules/hermes/init.4th into a new shared capsules/common/messaging.4th
that Hermes and Artemis each load at birth, giving every VM its own
private MSG-ARENA/CH-ARENA instead of only Hermes having one. Hermes
stays the owner of the one real, canonical COMMON-CH; Artemis subscribes
into it via VM-EXEC at her own birth, and Hermes proactively subscribes
Hera (idx 0) since Hera always exists first.

Hera does NOT get her own copy: register_child_vm_words()'s own doc
comment explains why the STADIUM-* primitives common:messaging.4th
depends on are deliberately never registered in her dictionary (keeps
her dict_hash off item 4.1's baseline). Confirmed live by loading it
into her dictionary anyway first -- every colon-definition referencing
an unregistered Stadium primitive was silently dropped (MSG-HEAT@/!,
CH-HEAT@/!, MSG-COOL-ALL, MSG-TICK all missing after boot). Reverted
that path; she orchestrates via BIRTH/VM-EXEC/VM-CALL instead.

Added capsule_vm_registry_get_by_index() (capsule_birth.c/.h) for
registry enumeration by birth-order position, and a pump in repl.c's
existing idle hook that walks every live VM once per idle beat and
VM-EXECs MSG-TICK into each one except Hera's own entry.

Verified clean (no UNKNOWN WORD / VM-EXEC errors after birth) on all
three architectures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
2026-08-28 13:02:23 -04:00
Robert Allan JamesandClaude Sonnet 5 6f5605d479 Phase 8: zuse cert storage moved out of the dictionary (fuse-blow install)
Found that a pinned CONSTANT is not actually tamper-proof: ACL-PIN only
blocks redefinition, not a >BODY-then-store on the word's existing data
field. Moves the Zuse cert value into C-only VM struct fields
(zuse_cert_lo/hi + zuse_cert_installed fuse bit) with a one-time
vm_zuse_cert_install() and read-only ZUSE-CERT-LO@/HI@/INSTALLED? FORTH
accessors, closing the tamper path structurally instead of by convention.
Deletes the now-insecure ZUSE-CERT-LO/HI CONSTANT words from zuse.4th.

vm_zuse_cert_install() has no caller yet -- the real mint flow (Milestone
6 CA, the MINT word) is still open; this is storage + accessors only, not
a stand-in mint. Documented in FABRIC-3.md. Verified: hosted build clean,
mkcapsule --lint clean (31/31), clean boot to ok> on amd64/aarch64/riscv64
with Stadium conservation intact and no panics.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
2026-08-26 13:58:22 -04:00
Robert Allan JamesandClaude Sonnet 5 28c1b12c7f Wire homeblocks_sig_check() into USB attach: the warn half (Phase 8)
Wired into sk_repl_idle()'s USB hotplug attach handler, right between
blkio_usb_open_msc() succeeding and blk_subsys_attach_device() -- logs
a distinct message per outcome (recognized / blank-or-foreign /
bad-version / bad-crc / read-error).

The "refuse" half is deliberately not implemented -- there is nothing
real to gate yet. blkio_usb.c has no SCSI WRITE(10) support at all, so
there is no write path today to refuse; attach currently only enables
read-only access, which is also the general-purpose USB block I/O
path this repo already relies on for unrelated testing, not
exclusively a home-blocks identity workflow. Refusing attach on blank
media would break that legitimate use without protecting anything
real -- same "don't build ahead of a real caller" reasoning EXPIRE's
deferral used. Refuse belongs on the write path once WRITE(10) exists.

sig_start_fblock is hardcoded to 0 at the call site -- correct for
today's unpartitioned raw test/real media (no GPT parser exists yet),
flagged in the comment as the one place that changes once a real
GPT-partition-relative lookup exists, isolated from
homeblocks_sig.c's own location-agnostic check logic.

Verified live: hot-attached disk/usb-thumbdrive-test.img (blank media)
through a running amd64 instance's QMP socket (blockdev-add +
device_add usb-storage) -- captured exactly 4 real TUR+READ10 BOT
cycles (matching the header's 4 forth-block span) followed by the
correct "not recognized" warning, then normal attach completing
successfully afterward (no regression). Conservation intact, no
panic. Clean zero-warning compile and clean boot on all three
architectures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
2026-08-26 07:14:51 -04:00