Compare commits

...
41 Commits
Author SHA1 Message Date
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 4cd18734ee FABRIC-3.md §H.12: 22-item implementation punch list
One coding task per item across 7 phases, each gated by the mandatory
3-arch QEMU boot acceptance test. Two corrections found while grounding
this against live code: capsule_birth.c's generic admission path already
admits every VM as a Stadium patron (just hardcoded unpinned), so no new
stadium_birth_hermes()/_artemis() functions are needed -- the real task
is making that path pin Hera/Hermes/Artemis specifically. VMIdentity
(vm_identity.h) already exists fully built; Session.identity reuses it
directly rather than inventing a new type.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 05:50:00 -04:00
Robert Allan JamesandClaude Opus 5 89d814f9f3 FABRIC-3.md §H: close all three remaining design gaps
Gap #1: closed set of four cards (VM/word/block/message), no new cards
for now. Gap #2: creator-ceiling enforcement is a birth-time snapshot
copy, not a live ACL-RECHECK-time check -- reversed mid-conversation once
the live-check approach's cross-VM dictionary-lookup problem surfaced;
final rationale is child program stability (a program developed against
one ACL set must not have it silently changed by later parent changes).
Gap #3: Zuse's eligibility list persists in the existing growable
metadata-fence mechanism as a simple owner_pubkey[32] list, no extra
per-entry metadata.

Every design-level question in this refactor is now closed. What remains
is pure implementation (5 items) and two intentional deferrals.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 05:43:22 -04:00
Robert Allan JamesandClaude Opus 5 d86b6de9cd FABRIC-3.md §H.11: refreshed gap analysis, end of session
Full sweep across the whole session-refactor design pass -- 7 of the
original 10 §H.9 gaps are now closed. Compiles the current accurate
picture: one genuinely open design question (more cards beyond the four
named?), two small undecided pieces (creator-ceiling enforcement location,
Zuse eligibility-list storage), five pure implementation gaps (design
fully decided, nothing coded), and two explicitly-deferred non-goals
(VM card multi-owner, elevation trigger alternates). Also updates H.10's
stale "elevation trigger not yet usable" note now that H.7 confirmed the
messaging substrate is already real.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 19:04:48 -04:00
Robert Allan JamesandClaude Opus 5 91dfb0a079 FABRIC-3.md §H: close gap #8 -- EXPIRE/D.2 relationship reconciled
No actual conflict between D.2 and H.1. D.2 (2026-08-27, "session ending =
VM detach, reuse COOL") was scoped to user sessions, written before
Hera/Hermes/Artemis were framed as having their own persistent sessions.
D.2 is a special case of H.1's broader pinned/non-pinned model, fully
subsumed: it describes exactly how the non-pinned branch behaves. Pinned
sessions have no D.2-style "ending" at all -- permanent by construction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 19:00:32 -04:00
Robert Allan JamesandClaude Opus 5 26b91f1ca4 FABRIC-3.md §H: close gap #7 -- private-topic traffic stays ungated
Checked MSG-SEND directly -- no ACL check anywhere in it today, on common
or a private topic alike. Confirmed: private-topic traffic stays ungated;
trust is established once at the handshake (CH-REQUEST/CH-ACCEPT/
CH-CONFIRM) and never re-checked per-message afterward. The message
card's scope is now final: it gates the single CH-REQUEST call, nothing
else in the messaging path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 18:57:06 -04:00
Robert Allan JamesandClaude Opus 5 b15339ca1f FABRIC-3.md §H: close gap #6 -- CH is the topic, and H.7/H.8 are real code
Checked capsules/common/messaging.4th directly instead of assuming: the
H.7 messaging protocol isn't design vision, it's already substantially
built -- COMMON-CH, CH-REQUEST, CH-ACCEPT, CH-CONFIRM, CH-CLOSE, and
local ACK/NACK (MSG-ACK-LAST/MSG-NACK-LAST/MSG-NACKED) all exist and work
today. CH already is the topic, 1:1, no new representation needed.
Upgrades H.7/H.8's framing from "protocol groundwork, not yet real" to
"the mechanism exists, the ACL gate on top of it is the remaining work" --
the message card is now a concrete wiring task, not a hypothetical one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 18:55:09 -04:00
Robert Allan JamesandClaude Opus 5 33286cb3a2 FABRIC-3.md §H: close gap #3 -- word card IS the existing ACL system
Checked capsules/ACL.4th directly: both existing modes (STRICT, TTL)
already default to allow=1, matching the word card's default-permissive
baseline. Since each VM has its own dictionary, existing per-word ACL
state is already scoped per-session for free. Confirmed: the word card is
the existing acl_ttl/acl_allow/acl_mode/acl_pinned system reused as-is,
not a new parallel structure. Surfaces one new follow-on gap: creator-
ceiling enforcement at VM birth time isn't designed anywhere yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 18:52:32 -04:00
Robert Allan JamesandClaude Opus 5 fdecb91908 FABRIC-3.md §H: close gap #2 -- parent/name session fields confirmed
Both fields (parent VMUuid, name) confirmed as-is with no adjustment. All
five session fields (vm_id, pinned, parent, name, identity) are now
confirmed, none still model-proposed-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 18:50:23 -04:00
Robert Allan JamesandClaude Opus 5 de513483e3 FABRIC-3.md §H: close gap #1 -- non-pinned session mapping
Confirmed: an ephemeral user VM's session lands as a non-pinned,
COOL-subject Stadium patron -- synthesized from D.2's earlier finding
(session ending = VM detach, reuse COOL) rather than a fresh decision.
Thumbdrive detach is the explicit trigger. No exception case for a
temporarily-pinned user VM.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 18:48:25 -04:00
Robert Allan JamesandClaude Opus 5 197ff03963 FABRIC-3.md: BMAPFMT gets FORTH wrappers, design fully closed
Decided: yes, FORTH wrappers for the block-card ACL primitives, mirroring
the word-level ACL split (raw C accessors, policy composed in FORTH).
zuse_cert_seed's C-only precedent doesn't apply -- that's key material,
block ACL fields are ordinary ACL state like DictEntry's. BMAPFMT's
field/API design (owner_fp, acl_allow, acl_ttl, flags bits, FORTH
wrappers) is now fully decided across this and the prior three passes --
only the actual code edit remains.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 18:36:52 -04:00
Robert Allan JamesandClaude Opus 5 ce1618dcd8 FABRIC-3.md: BMAPFMT flags bit values decided -- CLAIMED/MIGRATING/STALE
blk_meta_t's existing unused flags field (uint64_t) uses orthogonal bits,
not a mutually-exclusive enum: bit 0 = CLAIMED, bit 1 = MIGRATING (serves
MIGSM), bit 2 = STALE (serves UNCLEAN), 61 bits reserved. BMAPFMT's field
design (owner_fp, acl_allow, acl_ttl, and now these flags bits) is fully
decided; only the actual code edit to block_subsystem.h/.c remains.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 18:29:05 -04:00
Robert Allan JamesandClaude Opus 5 a05e6d60ba FABRIC-3.md: BMAPFMT gets a TTL -- blocks support temporary elevation too
Resolves part of section F.4's deferred item #1 (allow-list/grant shape
beyond the single fast-deny bit): blk_meta_t's acl_reserved[7] becomes a
4-byte acl_ttl (mirroring DictEntry's countdown shape) plus 3 bytes still-
open slack, reusing the same ACL-TTL/Zuse-eligibility-list mechanism as
the word card. Kept lean -- no acl_mode/acl_pinned mirror, since a block
isn't pinned/strict the way a word is. Updated both F.4 (the field design)
and H.6 (the block card) to match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 18:26:27 -04:00
Robert Allan JamesandClaude Opus 5 316de60671 FABRIC-3.md §H.10: close pin-authority choke point -- session owns both directions
Decided: session_set_pinned()/session_is_pinned() (or equivalent) are the
sole read AND write path for pin state -- nothing, including existing
Stadium code, touches STADIUM_FLAG_PIN on the patron header directly
anymore. Not just a write-side guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 18:22:43 -04:00
Robert Allan JamesandClaude Opus 5 a93d4fa2cd FABRIC-3.md §H.10: verify dictionaries are per-VM, not shared
Checked the load-bearing assumption underneath the word card's
creator-ceiling invariant and the H.5 elevation trigger, both of which
live on DictEntry ACL fields -- confirmed each VM gets its own separate
memory/dictionary buffer at birth (vm_bootstrap.c:181), so DictEntry ACL
state is already naturally scoped per-session. No conflict, no redesign.
Also notes two smaller, lower-risk open items: pin-authority sync between
Session and Stadium's flag, and the elevation trigger's dependency on
not-yet-real Hermes messaging.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 18:20:13 -04:00
Robert Allan JamesandClaude Opus 5 ba97623349 FABRIC-3.md §H: close gap #4 -- temporary Zuse elevation trigger decided
Trigger: live message-based request (ELEVATE-REQUEST on common or a
private topic, Zuse's session grants/NACKs, ACL-ALLOW!/ACL-TTL! write on
receipt), gated by a Zuse-held eligibility list keyed by owner_pubkey.
Grounded against capsules/ACL.4th directly -- ACL-TTL is a recheck-cadence
cache, not a grant mechanism; reuse means calling its ACL-ALLOW!/ACL-TTL!
primitives from new Zuse-triggered code, not new C primitives. Live-console
sudo-style grant and pre-signed capability tickets remain deferred, not
rejected, alternates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 18:15:44 -04:00
Robert Allan JamesandClaude Opus 5 1349e783aa FABRIC-3.md §H: session refactor capture -- struct shape + identity/ACL cards
Design capture from today's session-refactor discussion, back to Hera:
session-as-Stadium-patron struct shape (references the patron by VMUuid,
pin-authoritative, identity embedded), and the identity ACL "stack of
cards" model with all four named dimensions scoped so far (VM, word,
block, message) plus the PubSub/topics messaging-protocol groundwork the
message card depends on. Closes with a numbered gap-analysis list (§H.9)
of everything still explicitly open.

Capture-only, matching this document's own established discipline -- no
struct written, no code changed. Mirrors §D's Tripod-vision capture in
style and structure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 18:08:35 -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 58c59e87e5 Initial commit
Signed-off-by: Robert Allan James <robert.allan.james@gmail.com>
2026-09-01 12:07:32 -04:00
Robert Allan James d2a0305703 Record driving deadlines for #1 (provisional expiry) and #2 (December deliverable)
Clarified 2026-08-29 with the author: "no hurry" is wrong for #1 and #2.

  - #1 (Hosted StarForth): the provisional will expire before conversion. Its
    priority claim is time-boxed (provisional filed ~Dec 2025, window ~Dec
    2026); if the non-provisional isn't filed claiming benefit before the
    window lapses the priority is lost and the same subject matter can't be
    re-staked by refiling thereafter. Hard clock regardless of December scope.
  - #2 (full StarshipOS): the December deliverable that keeps the flagship
    covered; pending counsel confirmation it may also be the conversion
    vehicle for #1's provisional.

Open point to resolve: is #2 (or the December delivery) the conversion vehicle
for #1, or is #1 converted by a filing separate from #2? Decision record only;
no filing made. Authoritative form in ROADMAP.md; FABRIC-3.md tracks it.
2026-08-29 10:36:19 -04:00
Robert Allan James 31dfc5de63 Record IP framing of the three products: 3 patent applications + 3 marks (StarForth, StarshipOS/LithosAnanke, Compudynamics)
The three-product split is treated as three patent applications, each matched
to a trademark (decided 2026-08-29):

  - Patent 1 + mark StarForth                  -> hosted/interpreted runtime
  - Patent 2 + marks StarshipOS (canonical spelling; noted as "StarshopOS"
    in the decision words) + LithosAnanke       -> full standalone OS
  - Patent 3 + mark Compudynamics               -> Zynq steady-state machinery
    with sealed executions, HOL-proven, anchored by the physics-adaptive runtime

Flagged open for counsel (not assumed/filed): the repo already carries a
"Patent pending" USPTO provisional filed Dec 2025 for the Compudynamics
physics-adaptive runtime (docs/patent/). Whether patent 3 is a continuation/
refinement of that provisional, and whether the three product applications are
additive to or fold it in, must be reconciled. Records intent only; no legal
text drafted or filed. Authoritative form in ROADMAP.md; FABRIC-3.md tracks it.
2026-08-29 10:32:04 -04:00
Robert Allan James 936d046ca6 Record the FPGA three-product split: Hosted StarForth / full StarshipOS / HOL-proven sealed-execution hardware
The Zynq FPGA is not a fourth platform to port to — it is the pivot that
forces the project into three distinct products, each with its own host,
delivery, and proof character (decided 2026-08-29):

  1. Hosted StarForth - the existing hosted/interpreted StarForth runtime
     (3-arch acceptance-tested), delivered as a portable embedded runtime.
  2. A full StarshipOS - the standalone OS built on LithosAnanke
     (LithosAnanke -> StarshipOS), a self-booting OS on general silicon
     (SER5/RasPi/Milk-V line).
  3. Hardware steady-state machinery with sealed executions, HOL-proven -
     the FPGA-native product: hardware-enforced sealed executions and
     steady-state machinery machine-checked in a proof assistant (HOL);
     delivery is the bitstream + HOL proof artifacts, not just an OS port.

The coupling is the point: product 3 is born on the FPGA, and its existence
is what cleanly separates 1 from 2 from 3. Product 1 ships hosted on an OS,
product 2 as an OS on general silicon, product 3 as proven hardware. Per-
product gates and the even-major line carrying the three as separate tracks
are scoped at v2.5.0 close / during the coloring-in phase. Complements, and
does not retract, the existing hardware bare-metal release policy.

Authoritative form in ROADMAP.md; FABRIC-3.md tracks the same horizon.
2026-08-29 10:30:09 -04:00
Robert Allan James 8e94522d10 Record trajectory beyond v2.5.0: Zynq FPGA next, after a "coloring in" hardening phase
The next big milestone after the three-board bare-metal cut (v2.5.0) is
transferring the battle-tested amd64/aarch64/riscv64 story to a Zynq (AMD
Xilinx) FPGA SoC — configurable silicon with soft/hard CPU cores, PL fabric,
and a non-standard memory map, a genuinely larger step than any prior board
(expected on a new even-major line). Between v2.5.0 and the Zynq sits a
"coloring in" period: hardening that thickens the shape of what exists rather
than adding silicon (USB BOT/xHCI + block robustness, live-entropy and
Zuse-cert hardening on real ASICs, SMP/multi-core + IRQ routing from the HAL
notes, driver breadth) so the FPGA carries a production-honest shape forward.

Authoritative form in ROADMAP.md "Beyond v2.5.0"; FABRIC-3.md tracks the same
horizon in the post-release section and re-points G.6's Next at the
v2.2.0/v2.4.0/v2.5.0 cadence.
2026-08-29 10:28:22 -04:00
Robert Allan James 25276ae359 Decide board-by-board hardware rollout: v2.2.0 (SER5 amd64) / v2.4.0 (RasPi5 aarch64) / v2.5.0 (all three)
Real silicon arrives incrementally (Beelink SER5 in hand now; RasPi 5 + Milk-V
orderable around Mon 2026-08-31), so the hardware release is split per board
in hand, each an even-minor LTS point-in-time cut on the same line:

  - v2.2.0  amd64 bare metal  — Beelink SER5 (two 16GB sticks: one boots the
                                thumbdrive image, one mints a real Zuse user)
  - v2.4.0  aarch64 bare metal — Raspberry Pi 5
  - v2.5.0  all three bare     — adds Milk-V (riscv64)

Each closes only when its board's items are proven live (boot to ok>, block-
fence Zuse true, backend entropy non-deterministic), never build-only.

Records the decision in the authoritative Release Versioning Policy
(docs/lithosananke/ROADMAP.md, "Board-by-board hardware rollout, decided
2026-08-29") and re-maps G.4/G.5 in FABRIC-3.md onto the new cadence, noting
v2.0.0 was cut (tag v2.0.0).
2026-08-29 10:22:36 -04:00
Robert Allan James 28de700645 v2.0.1: G.4 amd64 RDRAND backend behind rng_get_bytes() (SER5 entropy)
First real per-arch RNG backend, added to the v2.0.0 unified entry point in
src/starkernel/rng/rng.c, #if-guarded to amd64: CPUID.01H:ECX[30] RDRAND
detection + inline-asm rdrand draws feeding rdrand_fill() (whole-byte
emission from the low end; a partial final draw is discarded -- throwing
away entropy is always safe).

Probe order honors the release policy: virtio-rng is tried first, so the
QEMU path stays on virtio-rng unchanged; RDRAND is the fallback only real
hardware (which has no virtio-rng device) reaches. QEMU-verified both ways
on amd64: with virtio-rng present -> "rng: backend = virtio-rng" (unchanged);
with virtio-rng absent and RDRAND exposed (-cpu max) -> "rng: backend =
rdrand" + "entropy: ready" + Zuse attach confirmed. rdrand_fill()'s exact
logic host-proven: fills 32-byte/16-byte buffers and yields differing draws
run-to-run (non-deterministic). aarch64/riscv64 builds unaffected (guarded
off). riscv64 Zkr and aarch64 peripheral-RNG backends remain parked for their
real boards.

FABRIC-3.md G.4 amd64 slice marked BUILT + QEMU-verified.
2026-08-29 10:18:07 -04:00
Robert Allan James 19bc90c97d v2.0.1: document G.6 generic thumbdrive image milestone in FABRIC-3.md 2026-08-29 10:12:58 -04:00
Robert Allan James bd266942ce v2.0.1: generic GPT/FAT32 UEFI bootable thumbdrive image (thumbdrive goal)
New `make -f Makefile.starkernel thumbdrive` goal builds a generic
UEFI-bootable GPT disk image (disk image -> GPT + one FAT32 "STARKERNEL"
EFI System partition with EFI/BOOT/BOOT<ARCH>.EFI + startup.nsh) that can be
written directly to a USB thumbdrive with dd and boots on any real amd64 UEFI
firmware (Beelink SER5 path) as well as under QEMU. The monolithic loader
embeds the whole kernel, so the ESP needs only the UEFI fallback boot path.

Verified under QEMU by attaching the image as a USB mass-storage device (not
cdrom): OVMF BDS auto-selected "UEFI QEMU QEMU USB HARDDRIVE" (Boot0002),
then kernel booted normally -- LithosAnanke v2.0.0, POST 1012/0/0 + ok>,
rng: backend = virtio-rng + entropy: ready, Artemis ready, and the USB BOT/
xHCI storage path enumerated (READ CAPACITY10 -> MSC device ready -> READ10
CSW PASS). This mirrors the real-hardware SER5 flow: firmware boots the USB
thumbdrive's ESP, and the OS's own storage rides the same USB BOT/xHCI path.

Works for all three arches via the arch-mapped EFI boot name.
2026-08-29 10:12:43 -04:00
Robert Allan James 2efd7fe7e7 v2.0.0: QEMU release — bump LITHOS_VERSION to 2.0.0
The §G v2.0.0 QEMU release-gate punch list is complete (G.1 xHCI stall
recovery, G.2 unified rng_get_bytes() entropy entry point, G.3 NVRAM
de-scoped). Bump the kernel version to 2.0.0 (even major = LTS, per the
Release Versioning Policy: X.0.0 = QEMU release, X.5.0 = hardware bare-metal
release). Update README and .claude/CLAUDE.md version references and the
Makefile.starkernel version-roadmap comment.

Verified: all three arches (amd64/aarch64/riscv64) build clean with v2.0.0
embedded; QEMU amd64 boot shows "LithosAnanke v2.0.0", POST 1012/0/0 + ok>,
rng: backend = virtio-rng + entropy: ready, and Zuse identity confirmed from
the attached thumbdrive.
2026-08-29 10:09:37 -04:00
133 changed files with 443016 additions and 98 deletions
+1 -1
View File
@@ -211,7 +211,7 @@ Output: `build/<arch>/kernel/starkernel_loader.efi` + `build/<arch>/kernel/stark
Two independently tracked version strings flow into the generated `include/version.h`:
`VERSION` (`Makefile.starkernel` — the embedded StarForth engine version, currently `3.1.0`;
note this does **not** auto-sync with the standalone StarForth repo's own version) and
`LITHOS_VERSION` (`Makefile.starkernel` — the kernel version, currently `1.5.4`).
`LITHOS_VERSION` (`Makefile.starkernel` — the kernel version, currently `2.0.1`).
### Build configuration (Kconfig — real, wired, not vestigial)
+689 -24
View File
@@ -1771,24 +1771,45 @@ pubkey-based model — flagged, not silently reused.
resolves via the drive's own identity record. */
uint8_t acl_allow; /* cached fast-deny bit, checked first -- vm.c:611-624's exact
pattern, applied to a block instead of a word. */
uint8_t acl_reserved[7]; /* explicitly undecided -- deliberate slack per "flexibility
until we understand the recipe," not a placeholder to fill
reflexively. */
uint32_t acl_ttl; /* countdown, same shape as DictEntry's acl_ttl -- decided
2026-09-02 (FABRIC-3.md §H.6/§H.9 item 5): blocks need
temporary elevation too, same ACL-TTL-reuse mechanism as the
word card (§H.5). Kept lean -- "lean, TTL only" -- no
acl_mode/acl_pinned mirror, a block isn't "pinned"/"strict"
the way a word is. */
uint8_t acl_reserved[3]; /* still genuinely undecided -- deliberate slack per
"flexibility until we understand the recipe," not a
placeholder to fill reflexively. */
uint64_t reserved_future; /* untouched budget, same reasoning. */
```
`flags` (already existing, already generic) does double duty as the **state** field from
Step 1 — no new field, just future-defined bit values (`CLAIMED`/`MIGRATING`/`STALE`/etc.).
Everything else in `blk_meta_t` (`checksum`, timestamps, `content_type`, hash, chain links,
`app_data[15]`) is untouched.
`flags` (already existing, already generic, `uint64_t` on `blk_meta_t`, confirmed unused —
zero callers) does double duty as the **state** field from Step 1. **Bit values decided
2026-09-02**: orthogonal bits, not a mutually-exclusive enum — a block can be both
`CLAIMED` and `MIGRATING` at once. Starting set: bit 0 = `CLAIMED`, bit 1 = `MIGRATING`
(serves `MIGSM`), bit 2 = `STALE` (serves `UNCLEAN` — interrupted flush), remaining 61 bits
reserved. Everything else in `blk_meta_t` (`checksum`, timestamps, `content_type`, hash,
chain links, `app_data[15]`) is untouched.
4. **Allocation granularity**: claims quantize to whole devblocks (3 Forth blocks), matching
the existing packing — a cell never needs to describe partial-devblock ranges.
**Not yet scoped (deferred within this node):** the actual allow-list/grant shape beyond the
single fast-deny bit (lands in `acl_reserved`, once designed); the specific `flags` bit
values for each state; whether `blk_get_meta()`/`blk_set_meta()` need new FORTH word wrappers
or stay C-only like `zuse_cert_seed`'s "no FORTH access" precedent; the actual repurposing
edit to `block_subsystem.h`/`.c` itself (this pass produced the field design, not the code
change).
**Resolved 2026-09-02:** blocks need temporary elevation, reusing the same `ACL-TTL`
mechanism as the word card, gated the same way by Zuse's eligibility list (§H.5) — see the
`acl_ttl` field added above. Also resolved same day: the `flags` bit values above.
**Decided 2026-09-02: yes, FORTH wrappers.** Mirrors the existing word-level ACL split
exactly — raw C accessors (e.g. `BLK-ACL-ALLOW@`/`!`, `BLK-ACL-TTL@`/`!`, `BLK-OWNER@`), actual
elevation/policy logic composed in FORTH on top, presumably a new capsule alongside `ACL.4th`
— per this project's own standing hard rule ("ACL policy belongs in `ACL.4th`, never in C...
Compose in FORTH first"). `zuse_cert_seed`'s "no FORTH access" is a different case, not a
counter-precedent: that field is private signing-key material, walled off from FORTH for
key-material-specific reasons — block ACL fields (`owner_fp`, `acl_allow`, `acl_ttl`, `flags`
bits) are ordinary ACL state, same category as `DictEntry`'s `acl_allow`/`acl_ttl` (which
already have FORTH accessors), not key material.
**Only remaining open item on this whole node**: the actual code edit to
`block_subsystem.h`/`.c` plus the new FORTH-wrapper primitives and policy capsule — field/API
design is now fully decided across this pass and the two before it (`acl_ttl`, `flags` bit
values, this FORTH-wrapper decision), nothing implemented yet.
### F.5 — `WIREBIND` (breadcrumb only — followed into `RUNCAP` instead, 2026-08-27)
@@ -3292,6 +3313,14 @@ author expects to have money for the SBCs — RasPi 6, Milk-V — within about a
v2.0.0 cut). Nothing
is deferred that QEMU alone could already prove out; only what genuinely needs real silicon.
**v2.0.0 was CUT 2026-08-29 (annotated tag `v2.0.0`, the three-arch QEMU release).** With
real boards arriving incrementally (Beelink SER5 in hand now; RasPi 5 + Milk-V orderable
around Mon 2026-08-31), the hardware rollout is split per board in hand into **v2.2.0
(amd64/SER5), v2.4.0 (aarch64/RasPi 5), v2.5.0 (all three, adds Milk-V riscv64)** — each an
even-minor LTS point-in-time cut on the same line. Authoritative form in `ROADMAP.md`
"Release Versioning Policy": **Board-by-board hardware rollout, decided 2026-08-29**. The
G.2/G.4/G.5 items below are re-mapped onto that cadence in each section's completion notes.
Everything about the state being shipped by v2.0.0 is unchanged by this versioning: the
current tree is a complete, deterministic, three-architecture OS that boots UEFI under QEMU
(M0M9 core milestones complete, M7.1 Capsules live, M9 Block I/O live, terminal/REPL I/O
@@ -3413,21 +3442,59 @@ there.
primitive) returns genuinely non-deterministic bytes (two boots differ), the Zuse mint
path seeded from it produces a valid distinct cert per boot when bleached, and QEMU
behavior is unchanged.
- **v2.0.1 — amd64 RDRAND backend BUILT + QEMU-verified 2026-08-29 (SER5 slice).** The first
real per-arch backend lands in `src/starkernel/rng/rng.c`, behind the v2.0.0 unified
entry point, `#if`-guarded to amd64: CPUID.01H:ECX[30] detection + inline-asm `rdrand`
draws feeding `rdrand_fill()` (whole-byte emission, partial draw discarded — throwing
away entropy is always safe). Probe order honors the policy: virtio-rng is tried first,
so the QEMU path stays on virtio-rng unchanged; RDRAND is the fallback that only real
hardware (which has no virtio-rng) reaches. QEMU-verified both ways on amd64: with
virtio-rng present → `rng: backend = virtio-rng` (unchanged); with virtio-rng absent and
RDRAND exposed (`-cpu max`) → `rng: backend = rdrand` + `entropy: ready` + Zuse attach
confirmed. `rdrand_fill()`'s exact logic host-proven to fill 32-byte/16-byte buffers and
produce differing draws run-to-run (non-deterministic). aarch64/riscv64 builds unaffected
(guarded off). Still parked for their real boards: riscv64 Zkr (RNDR), aarch64 peripheral
RNG. Next: prove RDRAND live on the real SER5 (v2.2.0), then add Zkr (v2.5.0/Milk-V) and the
aarch64 peripheral RNG (v2.4.0/RasPi 5) as those boards reach v2.2.0-level closure.
#### G.5 [v2.5.0] Real-machine boot validation (SER5 / RasPi 6 / Milk-V)
#### G.5 [v2.2.0 / v2.4.0 / v2.5.0] Real-machine boot validation (SER5 / RasPi 5 / Milk-V)
Flash `starkernel.iso` to real media and boot each real board in hand, confirming the same
acceptance story QEMU keeps green: POST `1012/0/0` + `ok>`, block-fence Zuse load true to the
already-minted Artemis image, and the G.4 RNG backend working live. The board pool is
SER5 (if still in hand), RasPi 6 (aarch64), Milk-V (riscv64) — the goal is at least one
board per architecture, but each board that boots is a separate, recorded data point.
This is the genuine transfer proof that the zero-degradation QEMU claim holds on real
silicon — the thing v2.0.0 cannot honestly claim.
Boot the generic thumbdrive image (`make -f Makefile.starkernel ARCH=<arch> thumbdrive`) on
each real board in hand, confirming the same acceptance story QEMU keeps green: POST
`1012/0/0` + `ok>`, block-fence Zuse load true to the already-minted Artemis image, and the
corresponding G.4 RNG backend working live. **Mapping onto the board-by-board cadence
(decided 2026-08-29):** v2.2.0 = SER5 (amd64, in hand now — two 16 GB sticks available, one
to boot the image, one to mint a real Zuse identity on real hardware); v2.4.0 = RasPi 5
(aarch64, orderable around Mon 2026-08-31); v2.5.0 = adds Milk-V (riscv64), the full
three-arch bare-metal cut. Each board that boots is a separate, recorded data point, and
each milestone closes only when its board's items are proven live (not build-only).
- **Exit criterion:** each board in hand cold-boots to `ok>` with Arena conservation
- **Exit criterion (per milestone):** the board cold-boots to `ok>` with Arena conservation
(43691/21845/65536), Zuse cert loads from the fence (or mints fresh on bleached media),
and its G.4 RNG backend returns non-deterministic bytes live. v2.5.0 does not close on any
board's G.4/G.x item being build-only.
and its G.4 RNG backend returns non-deterministic bytes live. A milestone does not close on
any of its board's G.4/G.x items being build-only.
#### G.6 [v2.0.1] Generic UEFI-bootable thumbdrive image (SER5 path — **BUILT + VERIFIED 2026-08-29**)
First concrete v2.0.1/SER5 work item, taken up because the Beelink SER5 is in hand and a
"generic thumbdrive bootable OS" is the target. The new `make -f Makefile.starkernel
thumbdrive` goal builds a generic GPT/FAT32 disk image (one "STARKERNEL" EFI System
partition with `EFI/BOOT/BOOT<ARCH>.EFI` + `startup.nsh`) writable to a USB stick with
`dd` and bootable on any real amd64 UEFI firmware as well as under QEMU. The monolithic
loader embeds the entire kernel, so the ESP needs only the UEFI fallback boot path —
this is what makes the image "as generic as possible."
- **Verified** by attaching the image to QEMU as a **USB mass-storage device** (not cdrom):
OVMF BDS auto-selected `Boot0002 "UEFI QEMU QEMU USB HARDDRIVE"`, then the kernel booted
normally — v2.0.0 banner, POST `1012/0/0` + `ok>`, `rng: backend = virtio-rng` +
`entropy: ready`, Artemis ready, and the USB BOT/xHCI storage path enumerated
(READ CAPACITY10 → MSC device ready → READ10 CSW PASS). This mirrors the real SER5 flow:
firmware boots the thumbdrive's ESP, and the OS's own storage rides the same USB BOT/xHCI
path. Works for all three arches via the arch-mapped EFI boot name.
- **Next (v2.0.1→v2.2.0):** G.5 real-board proof on the SER5 (in hand) — boot the thumbdrive
image, live RDRAND entropy, real Zuse mint on a second stick — then extend per arch to
v2.4.0 (RasPi 5) and v2.5.0 (Milk-V). Authoritative cadence in `ROADMAP.md`
"Board-by-board hardware rollout".
---
@@ -3447,3 +3514,601 @@ the punch lists above explicitly delimit what is excluded from each milestone:
- Dirty-event granularity (1.11, blocked on 4.3) and the §17.4 framebuffer heat/decay design.
- Re-run the DoE on the new substrate (§A 5.1).
- First-touch allocation first-call-free.
**Beyond v2.5.0 — Zynq FPGA, then a coloring-in period, decided 2026-08-29.** The next big
milestone after the three-board bare-metal cut (v2.5.0) is transferring the story to a
**Zynq FPGA** (configurable silicon — soft/hard cores, PL fabric, non-standard memory map) —
a genuinely larger step than any prior board, expected on a new even-major line. Between
v2.5.0 and starting the Zynq sits a **"coloring in"** hardening phase: making v2.5.0's
real-hardware story production-honest (USB BOT/xHCI + block robustness; live-entropy and
Zuse-cert hardening on real ASICs; SMP/multi-core + IRQ routing from the HAL notes; driver
breadth) so the FPGA carries a thickened, not thin, shape forward.
**The FPGA creates a three-product split, decided 2026-08-29.** The Zynq is the pivot that
forces the project into three distinct products, each with its own host, delivery, and proof
character: (1) **Hosted StarForth** — the existing hosted/interpreted StarForth runtime;
(2) **A full StarshipOS** — the standalone OS on general silicon (LithosAnanke → StarshipOS);
(3) **Hardware steady-state machinery with sealed executions, HOL-proven** — the FPGA-native
product: hardware-enforced sealed executions and steady-state machinery machine-checked in a
proof assistant (HOL); delivery is the bitstream + HOL proof artifacts. Product 3 is born on
the FPGA and its existence is what cleanly separates 1 from 2 from 3. Full form in
`ROADMAP.md` "Release Versioning Policy → The FPGA creates a three-product split".
**IP framing, decided 2026-08-29.** The three products are treated as three patent
applications with a matching mark each: Patent 1 + **StarForth** (hosted runtime); Patent 2 +
**StarshipOS + LithosAnanke** (full standalone OS); Patent 3 + **Compudynamics** (Zynq
steady-state/sealed-execution HOL-proven hardware, anchored by the physics-adaptive runtime).
Open for reconciliation with counsel: the repo already has a **"Patent pending"** USPTO
provisional (filed Dec 2025) for the Compudynamics physics-adaptive runtime (`docs/patent/`);
patent 3 and the three product applications must be positioned relative to it (continuation?
additive?) — recorded as intent here, nothing filed or drafted. **Driving deadlines
(clarified 2026-08-29):** #1's provisional will **expire before conversion** (filed ~Dec
2025, window ~Dec 2026 — priority lost if unconverted; the subject matter can't be re-staked
by refiling after lapse), so #1 is on a hard clock; **#2 (full StarshipOS) is the December
deliverable** that keeps the flagship covered — and, pending counsel confirmation, may be the
conversion vehicle for #1. Authoritative form in `ROADMAP.md` "IP framing of the three
products".
---
## H. Session refactor, back to Hera — session struct shape + identity/ACL card model (capture, 2026-09-02)
Captain Bob announced a major refactor reaching all the way back to Hera's own boot sequence:
formally introduce **session** as a first-class concept at the root of the system, rather than
bolted on later. This sharpens and extends D.2's 2026-08-27 correction ("a session is any VM
client running in the Stadium fabric... Zuse is a player like any other") into a concrete
struct shape and, from there, into a full identity/ACL model. **Capture-only pass, matching
this document's own established discipline** ("capture EVERYTHING first then we'll build a
plan," D's own intro) — nothing here is scoped into implementable items yet.
### H.1 — Session = Stadium patron, admission restated
- Hera defines what a session *is* (the concept/type itself). At boot, Hera registers
**herself** into her own session first. When Hera births a child VM (Hermes, then Artemis),
she also stands up a session for that child at birth time.
- **A session is a Stadium patron** — registering a session = admitting a patron to the
Stadium, not a new parallel bookkeeping structure. This is the same correction D.2 already
made ("a session IS a VM"), now traced down to the concrete admission mechanism already
live in `src/starkernel/vm/stadium.c`: `StadiumPatronHeader` (64 bytes — identity, heat,
ttl, link, contains, mass, flags [bit 0 = `STADIUM_FLAG_PIN`], behaviour, 32-byte inline
payload) and `stadium_birth_hera()`, which already admits Hera as patron zero, pinned, via
`stadium_admit(vm_uuid_hera(), &candidate)`. No `stadium_birth_hermes()`/`_artemis()`
equivalent exists yet — Hera is the only admitted patron today.
- **Pinned** sessions (Hermes, Artemis — the VMs Hera births at boot) are patrons exempt from
the normal departure path (heat decay / `COOL`): they never leave, permanently.
- Core unifying principle, in the user's own words: **"a process and a virtual machine are
going to be exactly the same thing"** inside this OS — VM identity, process identity, and
session become one concept, not three.
- **A session is a Stadium patron** *(Captain Bob, confirming): "Oh, yeah. God. This is...
this becomes a patron of the stadium, I believe, session, that is."* Pinned patrons are
exempt from the normal departure path (heat decay / `COOL`) — they never leave.
- **Resolved 2026-09-02**: yes, a non-pinned session (an ordinary Stadium patron subject to
`COOL`) is where an ephemeral user VM's session lands. Synthesized from D.2's earlier
finding rather than a fresh decision — D.2 (2026-08-27) already established "ending a
session is a VM detach... far closer to the existing, real, working `COOL`/
`capsule_vm_kill()` path" for user sessions tied to a thumbdrive; thumbdrive detach is the
explicit trigger, not just passive heat decay while idle. Pinned system VMs (Hera, Hermes,
Artemis) are permanently exempt from `COOL` regardless of idle state; non-pinned user VMs
are exempt from nothing. No exception case (e.g. a user VM temporarily pinned while
actively attached) — ruled out, confirmed as-is.
- **`EXPIRE`/D.2 relationship, resolved 2026-09-02**: no actual conflict between D.2 and this
section — D.2 was scoped to *user* sessions (thumbdrive attach/detach), written before
Hera/Hermes/Artemis were framed as having their own persistent sessions. **D.2 is a special
case of H.1, fully subsumed**: D.2 describes exactly how the non-pinned branch behaves
(detach triggers `COOL`), matching the mapping above. Pinned sessions simply have no
D.2-style "ending" at all — permanent by construction, the question D.2 was answering
doesn't apply to them.
### H.2 — Session struct shape
Confirmed: **a session is a NEW structure, separate from `StadiumPatronHeader`, that
references the patron by `VMUuid`** — not the patron header itself, and not indexed by
Stadium cell index. Gets its own header file, same precedent as `VMUuid`/planned-`VMIdentity`
(never grow `struct VM` or `StadiumPatronHeader` with inline fields for this).
Confirmed session fields so far:
- `vm_id` (`VMUuid`) — the patron this session references.
- `pinned`**session is authoritative**; Stadium's `STADIUM_FLAG_PIN` bit follows the
session's value, not the other way around.
- `parent` (`VMUuid`) — who birthed this session (Hera → Hermes/Artemis). **Confirmed
as-is 2026-09-02.**
- `name` — canonical human-readable name (feeds `console.c`'s `g_active_vm_name` prefix,
doesn't replace the console-binding mechanism itself). **Confirmed as-is 2026-09-02.**
- `identity` — lives directly embedded in the session struct (confirmed). Contents: the
ACL "stack of cards" model, H.3 onward.
**All five session fields are now confirmed**, none still model-proposed-only. **User's own
framing, explicit and important**: "the floor was, who knows? We're gonna be revisiting this
part of it around and around for a while." Confirmed-now doesn't mean locked-forever — treat
this field list as a live, expected-to-churn working draft, just no longer an open gap.
### H.3 — Identity model: ACL "stack of cards"
Identity carries the VM's/word's ACL as a **stack of cards, each with pinholes punched through
it** — user's own framing: "a stack of cards... got pinholes going through it, and those
pinholes drop all the way down through and give the permission for whatever we're
work[ing on]... a set of graduated sieves." Confirmed accurate by the user.
**Each card is one permission dimension**, not a redundant extra layer of the same check —
named dimensions: VM, word, block, message. A compound action (e.g. "execute word X, which
touches block Z and sends message M") passes through the word card, the block card, and the
message card at once — one card per relevant axis. **Confirmed: an action only consults the
cards for the dimensions it actually touches** — a pure VM-level operation only checks the VM
card, not the full stack every time.
**Closed set, resolved 2026-09-03**: VM, word, block, and message are the complete set — "closed
set, no new cards, naturally, subject to change." Not permanently frozen, but not open-ended
by default either — a fifth card (e.g. a "console" dimension, given the D.2b pentagon names
Console as a full peer node) would be a deliberate future decision, not something implicitly
already on the table.
**Word-ACL reconciliation resolved 2026-09-02.** Checked `capsules/ACL.4th` directly: both
existing modes (`STRICT` — "always allow, TTL stays 0 (recheck always)"; `TTL` — "compute TTL
from heat; set allow=1") already default to `allow=1` — no path in the current system denies
by default, already matching the word card's "default-permissive baseline" almost exactly.
Since each VM has its own dictionary (H.10), the existing per-word ACL state is already
naturally scoped per-session, for free. **Confirmed: the word card is not a new parallel
structure — it's the existing `acl_ttl`/`acl_allow`/`acl_mode`/`acl_pinned` system, wholesale,
reused as-is.** The stack-of-cards framing is new vocabulary for what already exists, not a
new mechanism.
**The one genuinely new piece, not present in today's system at all**: creator-ceiling
enforcement. Nothing today caps a newly-birthed VM's word-ACL state against its parent's.
**Mechanism decided 2026-09-03, after a mid-conversation reversal.** First proposed: hook the
ceiling check into the existing `ACL-RECHECK` cold path (checked later, no birth-time copy) —
initially agreed, but this runs into a real problem: each VM has an entirely separate
`DictEntry` dictionary (H.10), so `ACL-RECHECK` running inside a child would need a live
cross-VM lookup back to the parent's own dictionary state, never designed. On seeing this, the
user reconsidered: **"perhaps a fast copy might be cheaper and just as effective."**
**Final: birth-time snapshot, no live sync, ever.** The child's `acl_allow`/`acl_mode`/
`acl_pinned`/`acl_ttl` are copied from the parent once, at birth — no ongoing reference back
to the parent afterward, which solves the cross-VM lookup problem outright. The child's own
ACL state can still grow/shrink dynamically after birth (the word card's "additive/subtractive"
framing still holds); it just starts from the snapshot rather than staying synced to the
parent's *current* state. **Explicit rationale, user's own words**: "if something was
developed with a particular set of ACLs, it should remain at that — otherwise parent changes
break the child's program." Program stability for the child, not administrative convenience,
is why live-sync was rejected.
### H.4 — VM card
Narrow by design: a single base gate ("who owns / can-touch this VM at all") that other, finer
checks sit on top of. Does not itself enumerate operations (birth/kill, console attach,
retarget, quota grant, etc.) — those are finer-grained checks built elsewhere.
- **Zuse is entirely outside the card stack**: "forget Zeus [Zuse]. Zeus will always have 100
percent authority" — unconditional, not modeled as an always-allowed path *through* the VM
card the way the earlier `ACLKEY` pass (§F.2) had framed it. Zuse bypasses the card stack
entirely, full stop.
- **Single owner, for now**: exactly one `owner_pubkey` per VM at this gate (match or no
match), not a list of authorized identities — "single owner for now," multi-owner
explicitly deferred, not in scope.
- Matches the `VMIdentity{owner_pubkey[32], installed}` shape already decided in the earlier
`ACLKEY` pass (§F.2) — the VM card is effectively that ownership check, non-Zuse case only.
### H.5 — Word card
- **Default-permissive baseline**: "will have to pretty much admit any word" — not a
default-deny gate that has to explicitly enumerate everything.
- **Dynamic, not fixed**: ACLs at this card are "additive/subtractive dynamic entities" — the
permission set can grow or shrink over the session's lifetime, not a static list decided
once at creation.
- **Hard ceiling invariant**: "they can never exceed the creator's permissions" — a session's
word permissions can never exceed what its own creator/parent had. Delegation is capped by
the parent's own permission set — presumably where H.2's `parent` field earns its keep.
- **Escape hatch, mechanism confirmed reused**: the only way to temporarily exceed that
creator-ceiling is "some kind of temporary Zuse power" — **confirmed reuse of the existing,
already-built-and-measured `ACL-TTL` mechanism** (`acl_ttl` field on `DictEntry`), not a new
parallel temporary-grant mechanism.
- **Mechanism grounded, checked directly against `capsules/ACL.4th`**: the existing `ACL-TTL`
machinery is a recheck-cadence cache, not itself a grant mechanism — the real C-exposed
primitives are `ACL-ALLOW!`/`ACL-TTL!` (`ACL-RECHECK` recomputes TTL from execution heat and
sets `allow`). Reusing it for elevation means Zuse-triggered code calls these same
primitives directly, setting `allow=1` with a TTL that counts down to expiry.
- **Trigger, decided 2026-09-02: live message-based request, kept deliberately simple.**
Three options were weighed — message-based request/grant, a live-console `sudo`-style
command, and a pre-signed capability ticket decoupling grant-time from use-time — user
picked the first: "live request, keep it simple for now." Rides the H.7 messaging protocol:
the requesting session posts a request (working name `ELEVATE-REQUEST`) on `common` or a
private topic with Zuse, Zuse's session responds grant/`NACK`, and the
`ACL-ALLOW!`/`ACL-TTL!` write happens on receipt. Fits the standing "nothing is done until
it's real Hermes messages" completion criterion (D.1). The other two options are explicitly
deferred, not rejected — "keep it simple for now" scopes this decision to the live-request
path only.
- **Eligibility gate on top of the trigger, user-proposed**: "maybe zuse keeps a list of users
that can be granted zuse caps?" — confirmed as a gating layer, not a replacement for the
trigger. Zuse checks this list (keyed by `owner_pubkey`, matching H.4's single-owner
VM-card shape) before honoring any elevation request.
- **Storage decided 2026-09-03**: persists across reboots, living in Zuse's already-built
growable metadata fence at the top of Artemis's block device (the same mechanism that
already persists Zuse's own `zuse_cert_devblock_t` identity — see Phase 8/Milestone 6).
Shape: a simple growable list of `owner_pubkey[32]` entries, no extra per-entry metadata —
"simple list, no extra metadata, unless we find a reason this won't work" (provisional
lean-by-default, not a permanent ban on adding fields later). Not yet designed: the actual
record format added to the metadata fence; how entries get added (presumably a Zuse-only
FORTH word, unbuilt); exact relationship to the still-unbuilt `MINT`-a-second-identity flow.
### H.6 — Block card
**Confirmed: reuses the already-decided `BMAPFMT` design** from §F.4 — distributed per-block
ownership, not a new mechanism. Repurposes the dormant, fully-wired, zero-caller `blk_meta_t`
struct: 8-byte owner-pubkey fingerprint + 1-byte fast-deny `acl_allow`, with deliberate
reserved slack in the 40-byte block ("flexibility until we understand the recipe"). No
separate centralized block-ownership table (makes `homeblocks_sig_t`'s reserved
`blockmap_offset`/`blockmap_devblocks` fields unnecessary, already flagged for cleanup).
**Extended 2026-09-02**: blocks need temporary elevation too, same `ACL-TTL`-reuse mechanism
and Zuse-eligibility-list gating as the word card (H.5) — `acl_reserved` in §F.4's layout
became a 4-byte `acl_ttl` (mirroring `DictEntry`'s countdown shape) plus 3 bytes still-open
slack, kept lean rather than fully mirroring `DictEntry`'s `acl_mode`/`acl_pinned` too
("lean, TTL only" — a block isn't "pinned"/"strict" the way a word is).
**`flags` bit values decided 2026-09-02**: `blk_meta_t`'s existing `flags` field (`uint64_t`,
doubling as block state) uses orthogonal bits, not a mutually-exclusive enum. Starting set:
bit 0 = `CLAIMED`, bit 1 = `MIGRATING` (serves `MIGSM`), bit 2 = `STALE` (serves `UNCLEAN`),
remaining 61 bits reserved.
**FORTH-wrapper question decided 2026-09-02: yes.** Mirrors the word-level ACL split — raw C
accessors (`BLK-ACL-ALLOW@`/`!`, `BLK-ACL-TTL@`/`!`, `BLK-OWNER@`), policy composed in FORTH on
top. `zuse_cert_seed`'s C-only precedent doesn't apply — that's key material, block ACL fields
are ordinary ACL state like `DictEntry`'s.
`BMAPFMT`'s field/API design (TTL + flags bits + FORTH wrappers) is now fully complete but
**the code change was never made** — this refactor is presumably where it finally lands.
### H.7 — Messaging protocol: not groundwork, already real code (verified 2026-09-02)
Originally captured as design vision — **upgraded 2026-09-02** after checking
`capsules/common/messaging.4th` directly instead of assuming. The protocol described below
isn't a future design, it's already substantially built:
- **PubSub, decided.** Terminology is **"topics"** — the existing code's own term is
"channel"/`CH`. **Resolved: `CH` already *is* the topic, 1:1, no new representation
needed** — "topic" is just today's newly-adopted vocabulary for what the code already calls
a channel.
- **Every VM always subscribes to one well-known "common" topic.** Real: `COMMON-CH`, the
actual variable holding the one canonical common channel, owned by Hermes and wired into
`hermes/init.4th` at boot (`CH-ADD-MBR` adds members 0 and 1).
- **Handshake flow, real functions**: `CH-REQUEST ( type from to paddr plen -- )` — checks
`COMMON-CH` is `CH-OPEN`, sends via `MSG-SEND` — is literally "post a message on `common`
requesting a commune." `CH-ACCEPT ( -- ch|0 )` — the receiving side allocates a new private
channel, marks it `CH-NEGOTIATING`. `CH-CONFIRM ( ch -- )` transitions
`CH-NEGOTIATING``CH-OPEN`. `CH-CLOSE ( ch -- )` tears a channel down (`CH-CLOSING`).
- **ACK/NACK, real**: `MSG-ACK-LAST`/`MSG-NACK-LAST`/`MSG-NACKED` (253) — "purely local, no
VM-EXEC indirection" per the code's own comment — a general reliability mechanism applied to
every message on any topic, not specific to the handshake step.
### H.8 — Message card
**Scope confirmed narrow and one-sided**: gates only "who's allowed to post on `common`
requesting a commune" — the *initiating* side of the H.7 handshake, i.e. who's allowed to
call the real `CH-REQUEST` function above. The receiving VM's decision to accept/ACK (i.e.
`CH-ACCEPT`) is **not** gated by this card.
**Private-topic traffic gating, resolved 2026-09-02**: checked `MSG-SEND`
(`capsules/common/messaging.4th:213`, the underlying primitive both `CH-REQUEST` and any
private-topic traffic use) directly — no ACL check anywhere in it today, purely mechanical.
**Confirmed: private-topic traffic stays ungated.** Trust is established once at the
handshake, and nothing re-checks permission on every subsequent message within an
already-open private topic — consistent with this card's own narrow scope and the elevation
trigger's "keep it simple for now."
**The message card's full, final scope**: gates only the single call to `CH-REQUEST`.
Nothing else in the messaging path is ACL-gated by design.
**Implementation-gap note (2026-09-02)**: since `CH-REQUEST` is real, already-callable code
with no ACL gate in front of it today, this card's job is a concrete, well-targeted wiring
task — add the gate check to the real function — rather than a hypothetical one waiting on
future messaging infrastructure. Not yet built.
### H.9 — Gap analysis (as of 2026-09-02)
Explicitly open items surfaced during this capture pass, none decided yet:
1. **CLOSED 2026-09-02 — non-pinned session mapping (H.1).** Yes — an ephemeral user VM's
session lands as a non-pinned, `COOL`-subject Stadium patron, thumbdrive detach as the
explicit trigger (synthesized from D.2's earlier finding). No exception case for a
temporarily-pinned user VM.
2. **CLOSED 2026-09-02 — `parent` and `name` session fields (H.2).** Confirmed as-is, no
adjustment. All five session fields now confirmed (still subject to the general
"revisiting... around and around" caveat, but no longer an open gap specifically).
3. **CLOSED 2026-09-02 — word-level ACL reconciliation (H.3).** The word card *is* the
existing `acl_ttl`/`acl_allow`/`acl_mode`/`acl_pinned` system, wholesale, no new parallel
structure. New follow-on gap surfaced by this closure: creator-ceiling enforcement at VM
birth time isn't designed anywhere yet (nothing today caps a child's word-ACL state against
its parent's).
4. **CLOSED 2026-09-02 — temporary-elevation invocation path (H.5).** Trigger decided: a live
message-based request (`ELEVATE-REQUEST` on `common` or a private topic, Zuse's session
grants/`NACK`s, write happens on receipt via `ACL-ALLOW!`/`ACL-TTL!`), gated by a
Zuse-held eligibility list keyed by `owner_pubkey`. Still open underneath this closure: the
eligibility list's exact representation/storage, and the two explicitly-deferred (not
rejected) alternate triggers — live-console `sudo`-style grant, and pre-signed capability
tickets.
5. **`BMAPFMT` code change (H.6).** Field design was finished in the §F.4 pass but the actual
code edit to `blk_meta_t` was never made — a real, concrete implementation gap, not just an
open question.
6. **CLOSED 2026-09-02 — topic/`CH` reconciliation (H.7).** `CH` already *is* the topic, 1:1.
Bigger than expected: this check also upgraded all of H.7/H.8 from "protocol vision" to
"already real code" (`COMMON-CH`, `CH-REQUEST`, `CH-ACCEPT`, `CH-CONFIRM`, `CH-CLOSE`,
`MSG-ACK-LAST`/`MSG-NACK-LAST` all exist and work today in `capsules/common/messaging.4th`).
The message card's gate is now a concrete wiring task onto a real function, not a
hypothetical one waiting on future infrastructure.
7. **CLOSED 2026-09-02 — private-topic traffic gating (H.8).** Confirmed ungated — `MSG-SEND`
has no ACL check today; trust is established once at the handshake, nothing re-checks
permission afterward. The message card's scope is now final: it gates `CH-REQUEST` alone.
8. **CLOSED 2026-09-02 — the `EXPIRE`/D.2 relationship.** No conflict — D.2 is a special case
of H.1, fully subsumed (D.2 describes the non-pinned branch specifically; pinned sessions
have no D.2-style "ending" at all).
9. **Every other card beyond the four named** (VM, word, block, message) — the model list is
confirmed as the *named-so-far* set, not stated as closed/exhaustive.
10. **No implementation has started.** Every item above, and every card definition in
H.4H.8, is design capture only — no struct has been written, no `stadium_birth_hermes()`/
`_artemis()` exists, no `Session` header exists. Standard for this document's own
discipline (see D's intro), but worth stating plainly given the size of this section.
### H.10 — Verification pass: "are we painting ourselves into a corner?" (2026-09-02)
Asked directly by Captain Bob. One load-bearing assumption underneath H.3/H.5 got checked
against the live code rather than left as an inference.
**The risk**: the word card's creator-ceiling invariant and the H.5 elevation trigger both
live on `DictEntry` (`acl_allow`/`acl_ttl`), which only works per-session if each VM has its
own separate dictionary — a shared/global dictionary would make a single `acl_allow` bit
unable to mean "yes for session A, no for session B" on the same word.
**Verified true, no conflict, no redesign needed.** `struct VM` (`include/vm.h:410-412`) owns
its own `uint8_t* memory` buffer and its own `DictEntry* latest` chain; `vm_bootstrap.c:181`
(`vm->memory = (uint8_t*)vm_host_alloc(vm, VM_MEMORY_SIZE, ...)`) allocates a fresh,
independent memory/dictionary buffer on every VM birth. Hera, Hermes, Artemis, and any future
child each get their own separate dictionary — `DictEntry` ACL fields are already naturally
scoped per-session. H.3's stack-of-cards model and H.5's word card/elevation trigger stand as
decided, unchanged by this check.
Two smaller, lower-risk items flagged in the same pass:
- **CLOSED 2026-09-02 — pin-authority choke point (H.2).** Session.pinned is authoritative
over Stadium's `STADIUM_FLAG_PIN` bit. Decided: **full choke point at the session level,
both directions** — both writing and reading pin state go exclusively through session-owned
functions (e.g. `session_set_pinned()` / `session_is_pinned()`); nothing, including existing
Stadium code, reads `STADIUM_FLAG_PIN` directly off the patron header anymore. Session is
the sole authority for both write and read, not just the write path.
- **Elevation trigger, updated 2026-09-02: closer to usable than first thought.** Rides Hermes
messaging — at the time this risk was flagged, assumed to not be the real implementation
yet. §H.7's later verification found the messaging substrate (`CH-REQUEST`/`CH-ACCEPT`/
`CH-CONFIRM`/ACK-NACK) is already real, working code. What's still missing is only the ACL
gate itself (H.8) and the `ELEVATE-REQUEST` message type/handler — smaller gaps than
originally flagged, not a missing substrate.
### H.11 — Gap analysis, refreshed (2026-09-02, end of session)
Full sweep across the whole design pass, not just a re-list of §H.9's original ten items — 7
of them are now closed (1, 2, 3, 4, 6, 7, 8). New sub-gaps surfaced while closing the old
ones. This supersedes §H.9 as the current accurate picture; §H.9 itself is left intact above
as historical record (each item already carries its own closure annotation inline).
**Genuinely open design question (never asked):**
1. **CLOSED 2026-09-03.** Closed set — VM, word, block, message are the complete four, no new
cards for now (see H.3).
**Small design pieces, now resolved 2026-09-03:**
2. **CLOSED.** Creator-ceiling enforcement (§H.3) is a birth-time snapshot copy, not a live
`ACL-RECHECK`-time check — decided after a mid-conversation reversal once the live-check
approach's cross-VM lookup problem surfaced.
3. **CLOSED.** Zuse's eligibility list persists in the existing metadata-fence mechanism, as a
simple `owner_pubkey[32]` list, no extra metadata (§H.5).
**Pure implementation gaps — design fully decided, nothing coded yet:**
4. `stadium_birth_hermes()`/`_artemis()` don't exist (§H.1).
5. The `Session` struct + its own header don't exist (§H.2).
6. `BMAPFMT`'s code edit to `block_subsystem.h`/`.c` (§F.4/§H.6 — field/API design fully
closed).
7. The message card's ACL gate isn't wired onto the real `CH-REQUEST` (§H.8).
8. Creator-ceiling enforcement itself — the birth-time copy logic (§H.3, design now fully
decided as of item 2's closure above, code not written).
9. The eligibility-list's actual metadata-fence record format + the Zuse-only FORTH word to
add entries (§H.5, design now fully decided as of item 3's closure above, code not
written).
**Explicitly deferred, not rejected (intentional non-goals for now):**
10. VM card multi-owner support (§H.4).
11. Elevation trigger alternates — live-console `sudo` path, pre-signed capability tickets
(§H.5).
**Remaining genuinely open design question, after this pass**: none — every design-level
question from this list is now closed. What's left is implementation (items 49) and
intentional deferrals (1011).
### H.12 — Implementation punch list (2026-09-03)
One coding task per item, not a concept per item — each followed by the mandatory
amd64/aarch64/riscv64 boot acceptance test (this document's only valid acceptance criterion,
see the top-level CLAUDE.md). Grounded against live code, not the earlier design captures'
assumptions — two corrections surfaced while building this list, both noted inline below.
**Correction 1**: `stadium_birth_hermes()`/`_artemis()` (§H.11 item 4) don't need writing from
scratch. `capsule_birth.c`'s generic admission path (~lines 574602, used for every VM birth
today) already admits every born VM as a Stadium patron — it's just hardcoded to admit them
**unpinned** (`vm_patron.flags = 0`, with a comment explicitly noting "unpinned... unlike
Hera"). The real task is making that existing path pin Hera/Hermes/Artemis specifically,
leaving ordinary/user VMs unpinned as they already correctly are.
**Correction 2**: `VMIdentity` (`include/starkernel/vm_identity.h`) already exists, fully
built — `owner_pubkey[32]` + `installed` + `acl_caps` (capability bitmask), the VM-card shape
from H.4, plus more. `Session.identity` should be a `VMIdentity`, not a new type. Its own doc
comment confirms Hera/Hermes/Artemis have no installed identity yet ("before D.5's per-VM-
identity work lands") — wiring real identities into them is part of this refactor's remaining
work, not new invention.
**Phase 1 — Session struct + pin-authority choke point**
- [x] **1. DONE 2026-09-03.** `include/starkernel/session.h`: `Session{vm_id (VMUuid), pinned
(int), parent (VMUuid), name (fixed buffer, `SESSION_NAME_BUF`=64), identity (VMIdentity,
embedded)}`. Type only, no logic. No callers yet, so this acceptance run only confirms the
header itself is syntactically clean and doesn't break the build. Verified 3-arch boot to
`ok>` (amd64/aarch64/riscv64).
- [x] **2. DONE 2026-09-03, one deviation from the original wording.**
`src/starkernel/vm/session.c` + `session_boot_init()`/`session_find(VMUuid)`/
`session_register(...)`. Not a fixed-size array as originally written here — found
`stadium.c`'s own `StadiumVMQuota` table had already been moved off a fixed array to a
`kmalloc`'d-at-boot, budget-sized one (same "population isn't knowable in advance"
reasoning), so `session.c` mirrors that current precedent instead: `session_boot_init()`
sizes the slot table from `stadium_max_vm_count()`, must run after `stadium_boot_init()`.
Added `session.c` to `Makefile.starkernel`'s explicit `LOADER_EXTRA_SRCS`/
`KERNEL_EXTRA_SRCS` list (not a wildcard build). No callers yet. Verified 3-arch boot to
`ok>`.
- [x] **3. DONE 2026-09-03, one addition found necessary.** `session_set_pinned()`/
`session_is_pinned()` implemented — the pin-authority choke point (H.2/H.10).
`session_is_pinned()` answers from `Session.pinned` directly (the authoritative copy, no
Stadium re-derivation); `session_set_pinned()` writes both `Session.pinned` and the
mirrored `STADIUM_FLAG_PIN` bit on the session's own patron header, so Stadium's own
internal eviction/admission logic (which must stay self-contained, no call back into
session.c) keeps seeing a correct bit. **Addition**: `Session` needed a `stadium_cell`
field (index into `stadium_cells()`) that wasn't in the original §H.2 field list — the
choke point can't reach the right patron header without it. Necessary plumbing, not a new
session-level concept, so not treated as reopening §H.2's design. Also moved
`STADIUM_FLAG_PIN`'s `#define` from a `stadium.c`-private constant to `stadium.h` (public)
so `session.c` can reference it without duplicating the definition. Verified 3-arch boot to
`ok>`.
- [x] **4. DONE 2026-09-03.** Rewired `stadium_birth_hera()` to admit unpinned then register
through `session_register(vm_uuid_hera(), vm_uuid_hera(), "Hera")` (self-referential
parent, matching `capsule_run.h`'s `parent_vm_id == vm_id` root convention) +
`session_set_pinned(vm_uuid_hera(), 1)` — Hera is session zero. `stadium_admit()` confirmed
to have no admission-time-special pin handling (just copies the candidate header), so
admit-unpinned-then-pin-after is safe. Soft-fail (logged, non-fatal) if `session_register()`
fails — Hera's actual Stadium admission already succeeded and 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()`. Verified 3-arch boot to `ok>`, no
soft-fail message logged on any arch (registration succeeded), Hermes/Artemis births
unaffected.
**Phase 2 — Pin Hermes/Artemis (Correction 1 above)**
- [x] **5. DONE 2026-09-03, one bug found and fixed via probe.** `capsule_birth.c`'s generic
admission block now registers a session for every born VM and pins it if
`is_fleet_foundation` (Hera/Hermes/Artemis). Admits unpinned then pins after, same ordering
as step 4 (`stadium_admit()` confirmed to have no admission-time pin handling). **Bug found
by step 6's probe**: the fleet-foundation name check first used
`vm_name_eq_nocase(capsule_name, "Hermes")`, exact match — but `capsule_name` is actually
`"hermes:init.4th"`/`"artemis:init.4th"` (the real "namespace:filename" convention), never
a bare name, so the check silently never matched. Fixed with a new
`vm_name_prefix_eq_nocase()` helper matching everything before a literal `:`. 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.
- [x] **6. DONE 2026-09-03.** Confirmed via a temporary probe (`console_println` after
`session_set_pinned()`, captured once then reverted per this project's probe convention):
first run showed `hermes:init.4th pinned=0` / `artemis:init.4th pinned=0` (the bug above),
second run after the fix showed `pinned=1` for both. Probe code fully reverted before
commit — only its finding (the `:`-suffixed capsule-name format) remains, in step 5's doc
comment and this entry.
**Phase 3 — Session fields wired at birth**
- [x] **7. DONE 2026-09-03, larger than one line — a real signature-threading pass.**
`Session.parent` now comes from the actual birthing VM's own `stadium_vm_id`, not a
hardcoded `vm_uuid_hera()`. This meant adding a `VMUuid parent` parameter to
`capsule_birth_baby()` and, one level up, to `capsule_console_birth()` and
`capsule_runcap_birth()` (neither had a `VM *` in their own signature, but every one of
their callers did) — traced all 6 real call sites across `mama_forth_words.c` (4: `BIRTH`,
`CAPSULE-BIRTH`, `CONNECT-ARTEMIS`, `CONNECT-HERMES`, plus `RUNCAP-TEST`/`PAIR-TEST` = 6
total) and `capsule_wirebind.c` (2: console + user birth in
`capsule_wirebind_try_attach()`), confirmed each has a real `VM *` (`vm`/`mama_vm`) in
scope, and passed `vm->stadium_vm_id` through at every one. Two functions
(`mama_word_connect_artemis`/`_hermes`) had their `vm` parameter marked
`__attribute__((unused))`, now genuinely used — attribute removed. User explicitly chose
this option (full threading) over leaving the earlier `vm_uuid_hera()` hardcode in place.
- [x] **8. Already satisfied by step 5.** `session_register(vm_id, parent, capsule_name)`
already passes the capsule's own name string — nothing further needed.
- [x] **9. Confirmed by code inspection.** `session_register()` (step 2) zeroes `identity`
unconditionally — `installed` reads 0 by construction, matching `VMIdentity`'s own
documented default. No behavior change, as expected.
Verified 3-arch boot to `ok>` (amd64/aarch64/riscv64) for step 7's actual code changes,
which also exercises 8 and 9 unchanged.
**Phase 4 — Creator-ceiling enforcement (H.3, birth-time snapshot)**
- [x] **10. DONE 2026-09-03.** `dictionary_snapshot_acl_from_parent(child, parent)` added as a
static helper in `capsule_birth.c`: walks `child->latest`'s link chain, looks up each name
in `parent` via `vm_find_word()` (the same C-level lookup `FIND` itself uses — not a
modification to `FIND`, per the standing "never modify `FIND`" rule), copies
`acl_allow`/`acl_mode`/`acl_pinned`/`acl_ttl` onto the child's matching entry when found.
Baby-specific words the parent doesn't have are left untouched — nothing to cap them
against.
- [x] **11. DONE 2026-09-03.** Called once via `vm_find_entry_ptr(parent)` (the same internal
registry lookup `capsule_birth_mama()` already uses for Hera's own entry) to get the
parent's live `VM*`, right after `dict_hash`/parity logging — deliberately *after*, not
before: the snapshot depends on the parent's *current* ACL state, which can vary run-to-run
once Zuse-granted elevations exist, so applying it before the hash would make
`birth_dict_hash` no longer a pure function of capsule content, breaking the "same capsule
booted twice produces the same dict hash" determinism invariant this codebase relies on
elsewhere. Verified 3-arch boot to `ok>` (amd64/aarch64/riscv64).
**Phase 5 — `BMAPFMT` (§F.4/§H.6, independent, can run any time)**
- [x] **12. DONE 2026-09-03, one pre-existing bug found along the way.** `blk_meta_t`'s old
40-byte `owner_id`/`permissions`/`acl_block`/`signature[2]` replaced with
`owner_fp[8]`/`acl_allow`/`acl_ttl` (u32)/`acl_reserved[3]`/`reserved_future`, matching
§F.4/§H.6's decided layout exactly. **Verified via a real standalone `offsetof`/`sizeof`
compile, not hand math**, per this step's own instruction: found `sizeof(blk_meta_t)` was
already **344**, not the `341` its own `BLK_META_PER_BLOCK` constant and "341-byte slice"
comment claimed — a pre-existing inaccuracy, not introduced by this edit (ordinary trailing
struct-alignment padding after the final `uint8_t padding[5]`). Harmless in practice:
`BLK_META_PER_BLOCK` has **zero callers anywhere in the codebase**, consistent with
`blk_meta_t` itself being dormant. After this edit's own alignment changes (a `uint32_t`
and a `uint64_t` field each force a few bytes of compiler-inserted padding after preceding
`uint8_t` fields), the real new size is **336** bytes (8 smaller than before, not the
"same 40-byte budget" framing implied — natural alignment, not a bug). Added a
`_Static_assert(sizeof(blk_meta_t) == 336, ...)`, matching `blk_volume_meta_t`'s own
existing precedent in this same file, and fixed the now-doubly-stale "341-byte slice"
comment to point at the assert instead of repeating the wrong number. Did not touch
`BLK_META_PER_BLOCK` itself — out of scope, unused, not what this step asked for.
Verified 3-arch boot to `ok>` (amd64/aarch64/riscv64).
- [x] **13. DONE 2026-09-03.** `BLK_FLAG_CLAIMED`/`BLK_FLAG_MIGRATING`/`BLK_FLAG_STALE`
(bits 0/1/2, `1ull << n`) defined in `block_subsystem.h` right above `blk_meta_t`, `flags`
field comment updated to point at them. Verified 3-arch boot to `ok>`
(amd64/aarch64/riscv64).
- [x] **14. DONE 2026-09-03.** `blk_owner_fp_get`/`_set`, `blk_acl_allow_get`/`_set`,
`blk_acl_ttl_get`/`_set`, `blk_flags_get`/`_set` added — thin read-modify-write wrappers
over the existing `blk_get_meta()`/`blk_set_meta()` (which already own caching/dirty-
tracking; these add no new state). FORTH wrappers (step 15) call these, not
`blk_get_meta()`/`blk_set_meta()` directly, mirroring the word-level ACL system's own
C-primitive/FORTH-policy split. Verified 3-arch boot to `ok>` (amd64/aarch64/riscv64).
- [x] **15. DONE 2026-09-03, live-tested.** `BLK-ACL-ALLOW@`/`!`, `BLK-ACL-TTL@`/`!`,
`BLK-OWNER@` registered in `block_words.c` (shared/vendored source — verified with both a
hosted sanity build and the 3-arch kernel boot). `BLK-OWNER@` packs the 8-byte fingerprint
into one cell (`cell_t` is `int64_t`, 8 bytes — exact fit, raw bit reinterpretation). No
`BLK-OWNER!` — only the five words step 15 itself listed; setting ownership stays a
controlled, C-only operation (MINT/birth), not a general FORTH write. **Live-tested via QMP
keystrokes on the running riscv64 instance**: `1 BLK-ACL-ALLOW@` executed cleanly (`ok`, no
`UNKNOWN WORD` error) against a real block. Verified 3-arch boot to `ok>`
(amd64/aarch64/riscv64).
- [x] **16. DONE 2026-09-03, live-tested — Phase 5 complete.** `capsules/block-acl.4th`
(blocks 40194020, next free range after `zuse.4th`'s 40164018, verified against
`BLOCK_MAP.md` — CLAUDE.md's block-namespace rule) defines `BLK-ACL-CHECK ( block# --
allow? )`: a real, working fast-deny check mirroring `vm.c:611-624`'s pattern applied to a
block instead of a word. First touch lazily claims the block (`allow=1`,
`TTL=BLK-ACL-BASE-TTL`=256, same base value as `ACL.4th`'s own), matching the word card's
default-permissive baseline — not a stub, genuinely does something on every call. No
automatic TTL-decrement hot path exists for blocks yet (words decrement per-dispatch in
`vm.c`; blocks have no equivalent loop) — out of this step's scope, the fast-deny gate
itself is real and complete regardless. Passed `mkcapsule --lint` cleanly. Loaded via a new
`S" block-acl.4th" EXEC` line in `init.4th`'s Block 2049, right after `ACL.4th`'s own —
confirmed `ACL.4th`'s own activation messages identical before/after this addition (nothing
broken). **Live-tested via QMP keystrokes**: `1 BLK-ACL-CHECK` executed cleanly (`ok`, no
`UNKNOWN WORD`) on the running amd64 instance. Verified 3-arch boot to `ok>`
(amd64/aarch64/riscv64).
**Phase 5 (`BMAPFMT`) is now fully complete** — field layout, `flags` bits, C accessors,
FORTH wrappers, and a real policy word all landed and verified.
**Phase 6 — Zuse eligibility list (H.5)**
- [ ] **17.** Extend the metadata-fence record format with a new growable
`owner_pubkey[32]`-list record type.
- [ ] **18.** Implement read/add/membership-check functions in C.
- [ ] **19.** Add a Zuse-only FORTH word to add an entry, gated by `zuse_session`.
**Phase 7 — Message card gate + `ELEVATE-REQUEST` (H.8)**
- [ ] **20.** Add the initiator-only ACL gate at `CH-REQUEST`'s entry point
(default-permissive baseline, real hook point established).
- [ ] **21.** Define `ELEVATE-REQUEST` and a minimal real handler checking the eligibility
list, granting via `ACL-ALLOW!`/`ACL-TTL!` on match.
- [ ] **22.** Add the FORTH entrypoint a session actually calls to send one.
**Excluded, per H.11's deferred items 1011**: VM card multi-owner support, live-console
`sudo`-style elevation, pre-signed capability tickets.
+96 -7
View File
@@ -71,12 +71,16 @@ MAKEFLAGS += -j$(NPROC)
endif
# Version
# Roadmap:
# Roadmap (per docs/lithosananke/ROADMAP.md "Release Versioning Policy" and
# FABRIC-3.md §G — X.0.0 = QEMU release, X.5.0 = hardware bare-metal release):
# v1.0.x — serial-only production (released)
# v1.5.x — framebuffer VT100 terminal/console milestone (current)
# v2.0.0 — StarForth SDK release; v2.x.x development begins from there
# v1.5.x — framebuffer VT100 terminal/console milestone (released)
# v2.0.0 — QEMU release (even major = LTS): three-arch QEMU story complete
# v2.0.1 — SER5 hardware-track line: RDRAND backend + generic thumbdrive image goal
# v2.2.0 — amd64 bare-metal (Beelink SER5) — see ROADMAP "Board-by-board rollout"
# v2.5.0 — hardware bare-metal release: real per-arch RNG + real-board boot
VERSION ?= 3.1.0
LITHOS_VERSION ?= 1.5.4
LITHOS_VERSION ?= 2.0.1
# ==============================================================================
# BUILD PATHS
@@ -524,7 +528,8 @@ LOADER_EXTRA_SRCS := \
$(KERNEL_SRC)/vm/q48_stubs.c \
$(KERNEL_SRC)/vm/stadium.c \
$(KERNEL_SRC)/vm/stadium_words.c \
$(KERNEL_SRC)/vm/stadium_blocks.c
$(KERNEL_SRC)/vm/stadium_blocks.c \
$(KERNEL_SRC)/vm/session.c
KERNEL_EXTRA_SRCS := $(LOADER_EXTRA_SRCS)
@@ -554,6 +559,7 @@ KERNEL_OBJS := \
.PHONY: all clean clean-kernel kernel kernel-all
.PHONY: qemu qemu-esp qemu-gdb
.PHONY: thumbdrive iso-usb
.PHONY: info help
# ==============================================================================
@@ -999,8 +1005,89 @@ else ifeq ($(ARCH),riscv64)
echo "=== Serial log: $$LOG ==="; \
echo "=== Extracting DOE CSV ==="; \
bash scripts/extract_doe.sh $$LOG $$CSV || true; \
cp $$CSV $(DOE_LATEST_DIR)/riscv64.csv 2>/dev/null || true
endif
cp $$CSV $(DOE_LATEST_DIR)/riscv64.csv 2>/dev/null || true
endif
# thumbdrive — build a generic UEFI-bootable GPT/FAT32 disk image that can be
# written directly to a USB thumbdrive (dd) and boots on real amd64 UEFI
# hardware (e.g. Beelink SER5) as well as under QEMU. This is the "generic
# thumbdrive bootable OS" image for the v2.5.0/SER5 bare-metal path: the
# monolithic loader embeds the whole kernel, so the ESP needs only the UEFI
# fallback boot path EFI/BOOT/BOOT<ARCH>.EFI. Storage on real hardware is the
# USB BOT/xHCI path (already live), not virtio.
thumbdrive: all
@mkdir -p $(BUILD_DIR)
@if ! which sgdisk >/dev/null 2>&1; then echo "Error: sgdisk not found. Install gdisk (apt-get install gdisk)."; exit 1; fi
@if ! which mkfs.fat >/dev/null 2>&1; then echo "Error: mkfs.fat not found. Install dosfstools (apt-get install dosfstools)."; exit 1; fi
@case "$(ARCH)" in \
amd64) BOOTNAME="BOOTX64.EFI" ;; \
aarch64) BOOTNAME="BOOTAA64.EFI" ;; \
riscv64) BOOTNAME="BOOTRISCV64.EFI" ;; \
*) echo "Error: no thumbdrive EFI boot name for ARCH=$(ARCH)"; exit 1 ;; \
esac; \
# Geometry: 128 MiB disk (262144 sectors), GPT partition 1 spans sectors \
# 2048..262110 (the GPT last-usable sector for this disk size), i.e. \
# 260063 sectors. The FAT32 ESP image MUST match the partition size \
# exactly; a larger ESP overruns the disk (GPT grows corrupt) or \
# spills past the partition end, both of which made earlier builds \
# unbootable on hardware. \
DISK=$(BUILD_DIR)/starkernel-thumbdrive.img; \
ESP_SECTORS=260063; \
rm -f $$DISK $(BUILD_DIR)/thumbdrive-esp.img; \
dd if=/dev/zero of=$(BUILD_DIR)/thumbdrive-esp.img bs=512 count=$$ESP_SECTORS 2>/dev/null; \
mkfs.fat -F 32 -n "STARKERNEL" $(BUILD_DIR)/thumbdrive-esp.img >/dev/null 2>&1; \
mmd -i $(BUILD_DIR)/thumbdrive-esp.img ::/EFI; \
mmd -i $(BUILD_DIR)/thumbdrive-esp.img ::/EFI/BOOT; \
mcopy -i $(BUILD_DIR)/thumbdrive-esp.img $(LOADER_EFI) ::/EFI/BOOT/$$BOOTNAME; \
printf 'FS0:\\EFI\\BOOT\\%s\r\n' "$$BOOTNAME" > $(BUILD_DIR)/thumbdrive-startup.nsh; \
mcopy -i $(BUILD_DIR)/thumbdrive-esp.img $(BUILD_DIR)/thumbdrive-startup.nsh ::/startup.nsh; \
dd if=/dev/zero of=$$DISK bs=1M count=128 2>/dev/null; \
sgdisk -n 1:2048:0 -t 1:ef00 -c 1:"EFI System" $$DISK >/dev/null 2>&1; \
dd if=$(BUILD_DIR)/thumbdrive-esp.img of=$$DISK bs=512 seek=2048 conv=notrunc 2>/dev/null; \
rm -f $(BUILD_DIR)/thumbdrive-esp.img $(BUILD_DIR)/thumbdrive-startup.nsh; \
echo "Thumbdrive image: $$DISK"; \
echo "Write to a USB stick with: dd if=$$DISK of=/dev/sdX bs=4M status=progress"
# iso-usb — build a PURE UEFI isohybrid ISO for the Iso Image Writer workflow
# (GNOME Disks "Restore Disk Image...", or dd). Writes a single .iso to a USB
# stick as a raw image; the ISO carries a clean GPT with an EFI System
# Partition holding EFI/BOOT/BOOT<ARCH>.EFI, so real UEFI firmware (e.g.
# Beelink SER5) scans the ESP and boots it. No GRUB, no isolinux, no MBR boot
# code — the protective MBR template is zeroed (partition metadata only).
# Also still boots under QEMU via -cdrom. This is the novice path: one ISO,
# picked with a GUI, written to the stick.
iso-usb: all
@mkdir -p $(BUILD_DIR)
@if ! which xorriso >/dev/null 2>&1; then echo "Error: xorriso not found. Install xorriso (apt-get install xorriso)."; exit 1; fi
@if ! which mformat >/dev/null 2>&1 || ! which mmd >/dev/null 2>&1 || ! which mcopy >/dev/null 2>&1; then echo "Error: mtools not found. Install mtools (apt-get install mtools)."; exit 1; fi
@case "$(ARCH)" in \
amd64) BOOTNAME="BOOTX64.EFI" ;; \
aarch64) BOOTNAME="BOOTAA64.EFI" ;; \
*) echo "Error: iso-usb EFI boot name only defined for amd64/aarch64 yet"; exit 1 ;; \
esac; \
ISODIR=$(BUILD_DIR)/iso-usb; \
rm -rf $$ISODIR; mkdir -p $$ISODIR; \
dd if=/dev/zero of=$$ISODIR/efi.img bs=512 count=8192 2>/dev/null; \
mformat -i $$ISODIR/efi.img :: >/dev/null 2>&1; \
mmd -i $$ISODIR/efi.img ::/EFI; mmd -i $$ISODIR/efi.img ::/EFI/BOOT; \
mcopy -i $$ISODIR/efi.img $(LOADER_EFI) ::/EFI/BOOT/$$BOOTNAME; \
printf 'FS0:\\EFI\\BOOT\\%s\r\n' "$$BOOTNAME" > $$ISODIR/startup.nsh; \
mcopy -i $$ISODIR/efi.img $$ISODIR/startup.nsh ::/startup.nsh; \
head -c 432 /dev/zero > $$ISODIR/protmbr.bin; \
ISO=$(BUILD_DIR)/starkernel-iso-usb.iso; \
rm -f $$ISO; \
xorriso -as mkisofs \
-V STARKERNEL -r -J \
-isohybrid-mbr $$ISODIR/protmbr.bin \
-eltorito-alt-boot -e efi.img -no-emul-boot \
-isohybrid-gpt-basdat \
-o $$ISO $$ISODIR >/dev/null 2>&1; \
rm -rf $$ISODIR; \
echo "Iso Image Writer ISO (pure UEFI): $$ISO"; \
echo " In GNOME Disks, pick the ISO with 'Restore Disk Image...' and select your USB stick."; \
echo " Or: dd if=$$ISO of=/dev/sdX bs=4M status=progress"
# qemu-esp — interactive dev boot from FAT directory (no disk image rebuild,
# no auto-kill/timeout/DOE-injection logic — stays up until you quit it
@@ -1134,6 +1221,8 @@ help:
@echo " qemu — clean boot, serial tee'd live + logs/<session>/<arch>/ + DOE CSV → doe/ (all arches)"
@echo " qemu-esp — quick boot from FAT directory (amd64, aarch64)"
@echo " qemu-gdb — boot with GDB stub on :1234"
@echo " thumbdrive — build generic GPT/FAT32 UEFI disk image (write to a USB stick with dd; real-hardware/SER5 path)"
@echo " iso-usb — build UEFI isohybrid ISO for Iso Image Writer / GNOME Disks 'Restore Disk Image...' (novice path; real-hardware/SER5)"
@echo ""
@echo "QEMU display:"
@echo " QEMU_DISPLAY=gtk — framebuffer window backend for 'qemu' goal (default: gtk)"
+1 -1
View File
@@ -1,4 +1,4 @@
# LithosAnanke v1.5.4
# LithosAnanke v2.0.1
**UEFI-bootable FORTH microkernel.** Boots from firmware, initialises memory and interrupts, then runs the StarForth VM as its sole userspace runtime. No libc. No OS. Just stone and necessity.
+9 -6
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-08-29T13:51:54Z -->
<!-- Generated by mkcapsule --manifest 2026-09-03T11:42:42Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
@@ -10,6 +10,7 @@
|---------|----------------|----------|--------|
| `ACL.4th` | 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4015 | `0xd781d22148ff171d` | yes |
| `artemis:init.4th` | 4110, 4111, 4112, 4113, 4122, 4123, 4124, 4125, 4126, 4127, 4128, 4129, 4130, 4131, 4132, 4133, 4134, 4135, 4136, 4137, 4138, 4139, 4140, 4141, 4160, 4161, 4162, 4163, 4164, 4165, 4166, 4167, 4168, 4169, 4170, 4171, 4172, 4173, 4174, 4177, 4178, 4179, 4180, 4181, 4182, 4851, 4852, 4853, 4854 | `0xfe6e570467368153` | yes |
| `block-acl.4th` | 4019, 4020 | `0xf5eab0544b962dfa` | yes |
| `common:messaging.4th` | 5003, 5004, 5005, 5006, 5007, 5008, 5009, 5010, 5011, 5012, 5013, 5014, 5015, 5016, 5017, 5018, 5019, 5020, 5021, 5022, 5023, 5024, 5025, 5026, 5027, 5028, 5029, 5030, 5031, 5032, 5033, 5034, 5035, 5036, 5037, 5038 | `0x892fd1c86d8c175e` | yes |
| `common:msg.4th` | 4055 | `0x850a0382344ea6c4` | yes |
| `doe-campaign.4th` | 4060, 4061, 4062, 4063, 4064, 4065 | `0x3d4549142d91ec20` | yes |
@@ -33,7 +34,7 @@
| `init-l8-temporal.4th` | 4830, 4831 | `0x51abd4c138246651` | yes |
| `init-l8-transition.4th` | 4840, 4841, 4842 | `0xbcc1a81976f0a4c9` | yes |
| `init-l8-volatile.4th` | 4810, 4811, 4812, 4813 | `0x98caabbbd92abac4` | yes |
| `init.4th` | 2049, 2050, 2057 | `0x9ac6523d24b80cd0` | yes |
| `init.4th` | 2049, 2050, 2057 | `0xa94a7b63db15021b` | yes |
| `lib.4th` | 4050 | `0x4b216635c359ef73` | yes |
| `process.4th` | 4300, 4301 | `0x781afc1dbd0294f7` | yes |
| `sdk.4th` | 5109, 5110, 5111, 5112, 5113, 5114, 5115 | `0x008fdbbb62c94a3a` | yes |
@@ -45,9 +46,9 @@
| LBN | Capsule | xxHash64 | Status |
|-----|---------|----------|--------|
| 2049 | `init.4th` | `0x9ac6523d24b80cd0` | ok |
| 2050 | `init.4th` | `0x9ac6523d24b80cd0` | ok |
| 2057 | `init.4th` | `0x9ac6523d24b80cd0` | ok |
| 2049 | `init.4th` | `0xa94a7b63db15021b` | ok |
| 2050 | `init.4th` | `0xa94a7b63db15021b` | ok |
| 2057 | `init.4th` | `0xa94a7b63db15021b` | ok |
| 2064 | `init-l8-omni.4th` | `0x5979e314d6452045` | ok |
| 2065 | `init-l8-omni.4th` | `0x5979e314d6452045` | ok |
| 2066 | `init-l8-omni.4th` | `0x5979e314d6452045` | ok |
@@ -107,6 +108,8 @@
| 4016 | `zuse.4th` | `0x490ded9be257a90b` | ok |
| 4017 | `zuse.4th` | `0x490ded9be257a90b` | ok |
| 4018 | `zuse.4th` | `0x490ded9be257a90b` | ok |
| 4019 | `block-acl.4th` | `0xf5eab0544b962dfa` | ok |
| 4020 | `block-acl.4th` | `0xf5eab0544b962dfa` | ok |
| 4050 | `lib.4th` | `0x4b216635c359ef73` | ok |
| 4055 | `common:msg.4th` | `0x850a0382344ea6c4` | ok |
| 4060 | `doe-campaign.4th` | `0x3d4549142d91ec20` | ok |
@@ -353,4 +356,4 @@
None.
---
*32 capsule(s) scanned. Re-run `mkcapsule --manifest <dir>` to refresh.*
*33 capsule(s) scanned. Re-run `mkcapsule --manifest <dir>` to refresh.*
+25
View File
@@ -0,0 +1,25 @@
Block 4019
( block-acl.4th - Block-Level Access Control BMAPFMT )
( C prims: BLK-ACL-ALLOW@ BLK-ACL-ALLOW! BLK-ACL-TTL@ )
( BLK-ACL-TTL! BLK-OWNER@ )
( Policy words this file: BLK-ACL-CHECK )
( Mirrors ACL.4th's C-primitive/FORTH-policy split, )
( applied to blocks instead of words. )
( FABRIC-3.md H.12 step 16, 2026-09-03. )
256 CONSTANT BLK-ACL-BASE-TTL
Block 4020
( BLK-ACL-CHECK block# -- allow? )
( Fast-deny check, vm.c 611-624's pattern applied to )
( a block. First touch: default-permissive claim -- )
( allow=1, TTL=BLK-ACL-BASE-TTL -- matching the word )
( card's own default. No automatic TTL decrement loop )
( exists for blocks yet -- words decrement per word )
( dispatch, blocks have no equivalent hot path -- but )
( this is a real, working fast-deny gate either way. )
: BLK-ACL-CHECK ( block# -- allow? )
DUP BLK-ACL-TTL@ 0= IF
DUP BLK-ACL-BASE-TTL SWAP BLK-ACL-TTL!
DUP 1 SWAP BLK-ACL-ALLOW!
THEN
BLK-ACL-ALLOW@ ;
+1
View File
@@ -14,6 +14,7 @@ Block 2049
: VM-PARENT ( -- id ) 0 ;
: VM-CHILDREN ( -- ) ." (none)" CR ;
S" ACL.4th" EXEC
S" block-acl.4th" EXEC
S" lib.4th" EXEC
S" fabric.4th" EXEC
S" font.4th" EXEC
BIN
View File
Binary file not shown.
+108
View File
@@ -30,6 +30,114 @@ Applied to the two planned releases: the **v2.0.0** cut (even major, so LTS) is
release; **v2.5.0** is the hardware bare-metal release that transfers v2.0.0's QEMU story to
real boards. See `FABRIC-3.md` §G for the release-gate punch lists.
**Board-by-board hardware rollout, decided 2026-08-29 (extends the above as boards come
online).** Real silicon is arriving incrementally (Beelink SER5 in hand now; RasPi 5 + Milk-V
orderable around Mon 2026-08-31), so the hardware release is split per real board in hand,
each its own even-minor cut on the same line (each `X.Y.0` here is an LTS point-in-time cut,
not a separate dev line):
- **v2.2.0 — amd64 bare metal.** Beelink SER5 (in hand). Gate: the generic GPT/FAT32
thumbdrive image (`make -f Makefile.starkernel ARCH=amd64 thumbdrive`) flashes to and boots
on the real SER5 via its real UEFI; POST + `ok>`; the amd64 **RDRAND** backend
(`rng: backend = rdrand`) serves live entropy; and a real Zuse identity is minted on a
second thumbdrive in real hardware and re-attaches. Two 16 GB sticks available: one to boot
the image, one to mint the Zuse user.
- **v2.4.0 — aarch64 bare metal.** Raspberry Pi 5 (orderable ~Mon 2026-08-31). Gate: boots
on the real board, aarch64 peripheral-RNG backend live, Zuse mint/attach on real media.
- **v2.5.0 — all three bare metal.** Adds Milk-V (riscv64) to the above; the Zkr (RNDR)
backend live. This is the full "same story on all three arches on real silicon" cut.
**Beyond v2.5.0 — Zynq FPGA is the next big milestone, after a hardening phase.** Once the
three general-purpose boards close, the next major transfer is to a **Zynq (AMD Xilinx)
FPGA** SoC — the step where the battle-tested amd64/aarch64/riscv64 story rides on
configurable silicon. That is a genuinely bigger milestone than any single prior board: an
FPGA demands its platform be carried forward rather than ported with trivial
architecture-delta work, and it reshapes the hardware story (soft/hard CPU cores, PL fabric,
non-standard memory map, custom peripherals). Expect a new even-major line for it once the
coloring-in period below lands.
**The FPGA creates a three-product split, decided 2026-08-29.** The Zynq is not just a fourth
platform to port to — it is the pivot that forces the project to separate into three
distinct products, each with its own host, delivery, and proof character. This is the neat
partition the FPGA's configurable-silicon nature makes possible and demands:
1. **Hosted StarForth** — the hosted/interpreted StarForth product that already exists
(3-arch acceptance-tested; runs StarForth hosted on an OS). Its delivery is the Forth
+ VM + capsule semantics as a portable, embeddable interpreted runtime.
2. **A full StarshipOS** — the standalone operating system built on LithosAnanke
(LithosAnanke → StarshipOS). Its delivery is a self-booting OS on general-purpose
silicon (the SER5/RasPi/Milk-V line already covers this).
3. **Hardware steady-state machinery with sealed executions, HOL-proven** — the
FPGA-native product: hardware-enforced sealed executions and steady-state machinery
whose guarantees are machine-checked in a proof assistant (HOL). This is the product only
configurable silicon can honor — secret/hardware-boundary enforcement and formally
verified behavior carried in silicon rather than software. Its delivery is the bitstream +
the HOL proof artifacts (not just a port of the OS).
The coupling is the point: the FPGA is where product 3 is born, and product 3's existence is
what cleanly separates 1 from 2 from 3. Product 1 ships hosted on an OS; product 2 ships as
an OS on general silicon; product 3 ships as proven hardware. Anything that straddles those
boundaries after this decision is a conscious product-line choice, not an accident of history.
Scoping of each product's concrete gates, and the new even-major line that carries products 1
through 3 as separate tracks, is defined at v2.5.0 close / during the coloring-in phase.
(This complements, and does not retract, the existing "hardware bare-metal release" policy
above.)
**IP framing of the three products — 3 patent applications + 3 marks, decided 2026-08-29.**
The three products are treated as **three patent applications**, each matched to a trademark,
with one caveat about the pre-existing provisional:
- **Patent 1 + mark StarForth.** The hosted/interpreted StarForth runtime product.
- **Patent 2 + marks StarshipOS + LithosAnanke.** The full standalone OS
(LithosAnanke → StarshipOS). (Noted as "StarshopOS" in the 2026-08-29 decision words;
canonical repo spelling and mark is **StarshipOS** — confirmed as the literal mark
to register.)
- **Patent 3 + mark Compudynamics.** The Zynq hardware steady-state machinery with sealed
executions, HOL-proven — anchored by the physics-adaptive runtime.
**Driving deadlines (why "no hurry" is wrong for #1 and #2), clarified 2026-08-29.** Two
clocks bind the near term, independent of product #3 and the FPGA:
- **#1 — the provisional will expire before conversion.** Its priority claim is time-boxed:
if the non-provisional isn't filed claiming benefit before the provisional's window lapses
(provisional filed ~Dec 2025 → window ~Dec 2026), the provisional's priority is lost and
the same subject matter cannot be re-staked by refiling thereafter. So #1 is on a hard
clock regardless of the December scope.
- **#2 — full StarshipOS is the December deliverable.** This is what "keeps the flagship
covered": the standalone-OS product (and, pending confirmation below, possibly the vehicle
that converts #1's provisional) must deliver by December.
- **Open point to resolve with counsel:** is **#2's** application (or the December delivery)
the **conversion vehicle** for #1's provisional — i.e., does it claim benefit from the
#1 provisional and thereby secure what would otherwise lapse — or is #1 converted by a
filing separate from #2? Captured as a decision record; no filing made.
**Relationship to the existing provisional (must be reconciled, not assumed).** The repo
already carries a **"Patent pending"** USPTO **provisional filed December 2025** for the
physics-grounded self-adaptive runtime (the Compudynamics adaptive runtime), sourced in
`docs/patent/` and referenced in `README.md`. Compudynamics is therefore already staked as
the brand of that foundation. Open points to resolve with counsel before any filing, so
nothing is invented or double-filed: is patent 3 the continuation/refinement of that
provisional (its HOL hardware realization) or a fresh application? Are the three product
applications additive to, or folding in, the Dec-2025 provisional? This record states intent;
it does not file, claim, or draft legal text.
**Before the FPGA — "coloring in", decided 2026-08-29.** The period between the all-three
bare-metal cut (v2.5.0) and starting the Zynq is finishing/hardening work that thickens the
shape of what already exists rather than adding new silicon. This is not idle time; it is
the point where v2.5.0's real-hardware story is made production-honest before the FPGA asks
to carry it further. Concrete items to define during it (draft scope, to be firmed at v2.5.0
close): Zynq-preflight robustness of the USB BOT/xHCI and block paths; live-entropy and
Zuse-cert hardening on the three real boards; SMP/multi-core bring-up and IRQ routing on real
ASICs (parked in the HAL notes); driver set expansion beyond the three boards before
committing an FPGA port; and whatever v2.5.0's real-board validation surfaces. As boards
land and the v2.5.0 gates close, this list is edited down to the concrete coloring-in punch
list, and the Zynq becomes the even-major target after it.
QEMU remains the zero-degradation reference on every line; each `X.Y.0` must reproduce the
QEMU acceptance story (`POST 1012/0/0` + `ok>`, block-fence Zuse true, backend entropy
non-deterministic) on its board before it closes.
---
## Milestone Overview
+81 -8
View File
@@ -199,6 +199,16 @@ typedef struct {
_Static_assert(sizeof(blk_volume_meta_t) == 4096,
"blk_volume_meta_t must be exactly one 4 KiB devblock");
/* blk_meta_t.flags bit values -- FABRIC-3.md §F.4/§H.6/§H.12 step 13,
* decided 2026-09-02/03. Orthogonal bits, not a mutually-exclusive enum:
* a block can be both CLAIMED and MIGRATING at once. Grounded in the only
* states §F.4 actually motivated by a real need (MIGSM/UNCLEAN, two
* then-currently-blocked graph nodes) plus CLAIMED/STALE, the names
* already used loosely in that pass's own prose. 61 bits remain reserved. */
#define BLK_FLAG_CLAIMED (1ull << 0) /* owned, per BMAPFMT's owner_fp */
#define BLK_FLAG_MIGRATING (1ull << 1) /* mid-migration; serves MIGSM */
#define BLK_FLAG_STALE (1ull << 2) /* interrupted flush; serves UNCLEAN */
/* Per-1 KiB block metadata (packed into top 1 KiB region of each 4 KiB sector). */
typedef struct {
/* Core integrity (16 bytes) */
@@ -210,7 +220,7 @@ typedef struct {
uint64_t modified_time; /* Unix timestamp (last write) */
/* Block status (16 bytes) */
uint64_t flags; /* Status flags */
uint64_t flags; /* Status flags -- BLK_FLAG_* bits above */
uint64_t write_count; /* Number of writes (wear leveling) */
/* Content identification (32 bytes) */
@@ -223,11 +233,37 @@ typedef struct {
uint64_t entropy[4]; /* 256-bit entropy/random seed */
uint64_t hash[4]; /* SHA-256 (optional) */
/* Security & ownership (40 bytes) */
uint64_t owner_id; /* User/process ID */
uint64_t permissions; /* rwx-style permissions */
uint64_t acl_block; /* Block number containing ACL (0=none) */
uint64_t signature[2]; /* 128-bit signature */
/* Security & ownership -- FABRIC-3.md §F.4/§H.6/§H.12 step 12, decided
* 2026-08-27/2026-09-03: BMAPFMT repurposes this slot rather than
* building a separate on-drive block-map table (distributed
* ownership/ACL, travels with the block itself). Replaces the old
* owner_id/permissions/acl_block/signature[2] fields, which predated
* and directly conflicted with both the anti-POSIX principle and
* VMIdentity's pubkey-based model. Not the same 40-byte budget the
* old fields occupied -- natural alignment padding (uint32_t acl_ttl
* and uint64_t reserved_future each force a few bytes of compiler-
* inserted padding after the preceding uint8_t fields) makes this
* section's real footprint smaller; verified below via
* _Static_assert on the whole struct's actual sizeof(), not trusted
* by hand (see the blk_volume_meta_t padding-bug lesson this project
* already learned once). */
uint8_t owner_fp[8]; /* truncated fingerprint of owner's VMIdentity
* pubkey -- cheap per-block; full pubkey
* resolves via the drive's own identity
* record. */
uint8_t acl_allow; /* cached fast-deny bit, checked first --
* vm.c:611-624's exact pattern, applied to a
* block instead of a word. */
uint32_t acl_ttl; /* countdown, same shape as DictEntry's
* acl_ttl -- blocks support temporary
* elevation too, same ACL-TTL-reuse
* mechanism and Zuse-eligibility-list gating
* as the word card (§H.5). */
uint8_t acl_reserved[3]; /* still genuinely undecided -- deliberate
* slack per "flexibility until we understand
* the recipe," not a placeholder to fill
* reflexively. */
uint64_t reserved_future; /* untouched budget, same reasoning. */
/* Link/chain support (32 bytes) */
uint64_t prev_block; /* Previous in chain (0=none) */
@@ -238,10 +274,29 @@ typedef struct {
/* Application-specific (120 bytes) */
uint64_t app_data[15]; /* 15×64-bit app-defined fields */
/* Padding to reach 341-byte slice */
uint8_t padding[5];
uint8_t padding[5]; /* trailing slack, unrelated to any exact size target --
* the old "341-byte slice" comment here was already
* inaccurate before FABRIC-3.md §H.12 step 12's edit
* (sizeof(blk_meta_t) was 344, not 341, due to
* ordinary trailing struct-alignment padding after
* this array -- harmless since BLK_META_PER_BLOCK,
* the only thing that constant would matter to, has
* zero callers anywhere in this codebase). Verify
* this struct's real size with the _Static_assert
* below, not by re-deriving it from this comment. */
} blk_meta_t;
/* Verified via offsetof()/sizeof(), not trusted by hand -- see the
* blk_volume_meta_t padding-bug lesson this project already learned once
* (a hand-summed struct padding formula hid a real 4-byte alignment gap).
* 336, not the BLK_META_PER_BLOCK/"341-byte slice" figure this struct's
* own comments have long claimed -- that mismatch predates this assert and
* is harmless today (see padding[5]'s own comment above), but this assert
* now makes any future drift in either direction fail the build instead of
* silently mismatching a constant nothing currently checks against it. */
_Static_assert(sizeof(blk_meta_t) == 336,
"blk_meta_t size changed -- update this assert and check BLK_META_PER_BLOCK");
/* Error codes */
enum {
BLK_OK = 0,
@@ -358,6 +413,24 @@ int blk_get_meta(uint32_t block_num, blk_meta_t *meta);
int blk_set_meta(uint32_t block_num, const blk_meta_t *meta);
/* BMAPFMT field accessors -- FABRIC-3.md §F.4/§H.6/§H.12 step 14. Thin
* read-modify-write wrappers over blk_get_meta()/blk_set_meta() (which
* already own the caching/dirty-tracking), one per new blk_meta_t field.
* FORTH wrappers (BLK-ACL-ALLOW@/! etc., §H.12 step 15) call these, not
* blk_get_meta()/blk_set_meta() directly -- same C-primitive/FORTH-policy
* split as the existing word-level ACL system. */
int blk_owner_fp_get(uint32_t block_num, uint8_t out_fp[8]);
int blk_owner_fp_set(uint32_t block_num, const uint8_t fp[8]);
int blk_acl_allow_get(uint32_t block_num, uint8_t *out_allow);
int blk_acl_allow_set(uint32_t block_num, uint8_t allow);
int blk_acl_ttl_get(uint32_t block_num, uint32_t *out_ttl);
int blk_acl_ttl_set(uint32_t block_num, uint32_t ttl);
int blk_flags_get(uint32_t block_num, uint64_t *out_flags);
int blk_flags_set(uint32_t block_num, uint64_t flags);
int blk_is_allocated(uint32_t block_num);
int blk_mark_allocated(uint32_t block_num);
+9
View File
@@ -127,6 +127,14 @@ CapsuleRunResult capsule_birth_mama(
* @param descs Capsule descriptor array
* @param names Capsule name entry array (parallel to descs)
* @param arena Capsule payload arena
* @param parent Who is birthing this VM (FABRIC-3.md §H.12 step 7) --
* the caller's own VMUuid (e.g. vm->stadium_vm_id for
* a FORTH word handler), recorded on the new VM's
* Session.parent. Every current call site has one in
* scope, directly or one level up; traced live rather
* than assumed (checked all 6 call sites across
* mama_forth_words.c/capsule_console.c/
* capsule_runcap.c/capsule_wirebind.c).
* @param skip_pki_sig 0 for every build-time capsule (the normal case --
* checked against the compile-time-baked signature
* array via capsule_get_signatures()). Non-zero only
@@ -154,6 +162,7 @@ CapsuleRunResult capsule_birth_baby(
const CapsuleDesc *descs,
const CapsuleNameEntry *names,
const uint8_t *arena,
VMUuid parent,
int skip_pki_sig,
VMUuid *out_vm_id,
void **out_vm_ctx
+3 -1
View File
@@ -35,11 +35,13 @@
* "CaptBob"); sk_repl_dispatch_line() looks for a
* live "<name>~user" counterpart to decide
* whether a given active VM is a console.
* @param parent Who is birthing this VM (FABRIC-3.md §H.12 step 7)
* -- passed straight through to capsule_birth_baby().
* @param out_vm_id Output: assigned VM ID.
* @param out_vm_ctx Output: new VM context (may be NULL).
* @return CAPSULE_RUN_OK on success, error code otherwise.
*/
CapsuleRunResult capsule_console_birth(const char *console_name,
CapsuleRunResult capsule_console_birth(const char *console_name, VMUuid parent,
VMUuid *out_vm_id, void **out_vm_ctx);
#endif /* __STARKERNEL__ */
+3
View File
@@ -58,6 +58,8 @@ struct blkio_dev;
* @param vm_name Symbolic name for the new VM (becomes both the
* capsule's own single directory entry name and the
* VM registry name).
* @param parent Who is birthing this VM (FABRIC-3.md §H.12 step 7) --
* passed straight through to capsule_birth_baby().
* @param out_vm_id Output: assigned VM ID.
* @param out_vm_ctx Output: new VM context (may be NULL if not needed).
* @return CAPSULE_RUN_OK on success, error code otherwise.
@@ -66,6 +68,7 @@ CapsuleRunResult capsule_runcap_birth(
struct blkio_dev *dev,
const homeblocks_sig_t *sig,
const char *vm_name,
VMUuid parent,
VMUuid *out_vm_id,
void **out_vm_ctx
);
+18
View File
@@ -128,6 +128,24 @@ void console_puts(const char *s);
*/
void console_println(const char *s);
/**
* console_ensure_line_start - Make sure the next console_putc() starts at
* the beginning of a fresh output line, emitting a newline if output is
* currently mid-line (e.g. dangling behind a re-anchored prompt).
* No-op if already at line start. Mirrors console_putc()'s dual
* serial+framebuffer behavior (a bare '\n' reaches both).
*/
void console_ensure_line_start(void);
/**
* console_tx_count - Monotonic count of console_putc() calls delivered to
* either output (serial and/or framebuffer). In use by the REPL to detect
* that an idle bottom half (heartbeat, USB attach/detach) wrote to the
* console while the top-level prompt was showing, so it can re-anchor the
* prompt afterward. Never decreases.
*/
uint64_t console_tx_count(void);
/**
* Read a single character from serial console (non-blocking)
* Returns -1 if no character available
+11 -5
View File
@@ -100,16 +100,22 @@ void fb_draw_orientation_test(void);
* --------------------------------------------------------------------- */
/**
* Scroll the framebuffer up by `char_rows` character rows (each 16 px).
* The vacated rows at the bottom are filled with bg.
* Scroll the whole framebuffer up by `pixel_rows` pixel rows. The vacated
* rows at the bottom are filled with bg. Takes an explicit pixel-row count
* (not a hardcoded 8x16-cell assumption) so callers with a non-8x16 cell
* height (e.g. TTF mode, 24px) pass their own cell height directly --
* same convention fb_scroll_rect() below already uses, for the same reason
* (a caller-computed char_rows * fixed-16px assumption drifts out of sync
* with the text model's own row height in TTF mode, and that drift
* compounds with every scroll).
*/
void fb_scroll_rows(uint32_t char_rows, uint32_t bg);
void fb_scroll_rows(uint32_t pixel_rows, uint32_t bg);
/**
* Scroll a sub-rectangle of the framebuffer up by `pixel_rows` pixel rows
* (FABRIC.md item 4.4t: box-confined REPL scrolling). Unlike fb_scroll_rows()
* (whole-framebuffer, fixed 16px-row assumption), this is bounded to
* [x, x+w) x [y, y+h) and takes an explicit pixel-row count so callers with
* (whole-framebuffer), this is bounded to
* [x, x+w) x [y, y+h). Both take an explicit pixel-row count so callers with
* a non-8x16 cell height (e.g. TTF mode) pass their own cell height directly.
* Pixels outside the rect are untouched. The vacated rows at the bottom of
* the rect are filled with bg.
+12 -4
View File
@@ -120,12 +120,20 @@ int sk_console_key_available(void);
* uses internally, so a mid-word EXPECT behaves identically to typing at
* "ok>" itself.
*
* @param buf Destination buffer
* @param size Buffer capacity, including the NUL terminator
* @param active_vm VM whose idle dispatch runs while waiting
* @param buf Destination buffer
* @param size Buffer capacity, including the NUL terminator
* @param active_vm VM whose idle dispatch runs while waiting
* @param reanchor_prompt Nonzero to re-print the "ok> " prompt whenever
* an idle bottom half (heartbeat, USB attach/detach) writes
* to the console while this readline blocks at a bare,
* untyped prompt -- keeps the top-level prompt as the last
* thing shown once the chatter dies down. Callers whose
* prompt line is their own (shim.c's fgets(), i.e.
* QUERY/EXPECT/ACCEPT) pass 0 so "ok> " never gets stamped
* onto their mid-word input context.
* @return number of characters placed in buf, not counting the NUL
*/
int sk_console_readline(char *buf, int size, VM *active_vm);
int sk_console_readline(char* buf, int size, VM* active_vm, int reanchor_prompt);
#ifdef __cplusplus
}
+169
View File
@@ -0,0 +1,169 @@
/*
StarForth Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* session.h - Per-VM session (FABRIC-3.md §H, decided 2026-09-02/03)
*
* A session is a Stadium patron (FABRIC-3.md §H.1) -- registering a session
* IS admitting a patron to the Stadium, not a new parallel bookkeeping
* structure. This struct is the piece that sits ALONGSIDE the patron,
* referencing it by VMUuid rather than being indexed by Stadium cell index
* or grown as inline fields on StadiumPatronHeader/struct VM (deliberately
* its own header, mirroring VMUuid's/VMIdentity's own precedent -- standing
* instruction: give real-shaped data its own header and integrate as a
* field, don't grow existing structs ad hoc).
*
* Fields (FABRIC-3.md §H.2, all five confirmed 2026-09-02/03):
* vm_id -- the patron this session references.
* pinned -- session is AUTHORITATIVE over Stadium's STADIUM_FLAG_PIN
* bit (§H.10): the sole read/write path for pin state is
* session_set_pinned()/session_is_pinned() below, nothing
* else (including existing Stadium code) may touch
* STADIUM_FLAG_PIN directly.
* parent -- who birthed this session (Hera -> Hermes/Artemis, etc).
* name -- canonical human-readable name; feeds console.c's
* g_active_vm_name prefix, does not replace the
* console-binding mechanism itself.
* identity -- embedded VMIdentity (FABRIC-3.md §H.4's VM card is
* effectively VMIdentity's existing ownership check; reused
* directly here, not reinvented).
*
* Explicit user framing (2026-09-02), still true: "we're gonna be
* revisiting this part of it around and around for a while" -- treat this
* shape as a live working draft, not permanently locked.
*/
#ifndef STARKERNEL_SESSION_H
#define STARKERNEL_SESSION_H
#ifdef __STARKERNEL__
#include <stdint.h>
#include <stddef.h>
#include "starkernel/vm_uuid.h"
#include "starkernel/vm_identity.h"
/* Matches console.c's CONSOLE_VM_NAME_BUF precedent -- same order of
* magnitude for the same kind of data (a short human-readable VM name). */
#define SESSION_NAME_BUF 64
/* Sentinel meaning "no Stadium cell recorded yet" -- same shape as
* STADIUM_CELL_NONE (stadium.c), duplicated here rather than pulled in via
* stadium.h to avoid this header depending on Stadium's internal cell-index
* type. Session's own callers set stadium_cell after their own
* stadium_admit() call returns a real index (§H.12 step 3 doc). */
#define SESSION_STADIUM_CELL_NONE ((size_t)-1)
typedef struct {
VMUuid vm_id; /* the patron this session references */
int pinned; /* authoritative over STADIUM_FLAG_PIN; see
* session_set_pinned()/session_is_pinned() */
VMUuid parent; /* who birthed this session */
char name[SESSION_NAME_BUF]; /* canonical human-readable name */
VMIdentity identity; /* embedded, not referenced -- see vm_identity.h */
size_t stadium_cell; /* index of this session's own patron cell in
* stadium_cells() -- SESSION_STADIUM_CELL_NONE
* until the caller that admits this session's
* patron (stadium_admit()'s return value) sets it.
* session_set_pinned()/session_is_pinned() need
* this to reach the right patron header; added
* §H.12 step 3, not part of the original H.2 field
* list -- necessary plumbing, not a new session-
* level concept, so not itself renegotiated. */
} Session;
/*
* session_boot_init - Boot-time allocation, mirroring stadium_boot_init()'s
* own kmalloc-sized-from-budget shape rather than a fixed compile-time
* array (stadium.c's own StadiumVMQuota table was moved off a fixed array
* for the same reason -- population isn't knowable in advance). Must run
* after stadium_boot_init() (session slot count is sized from
* stadium_max_vm_count()) and before the first session is registered.
* No callers yet (§H.12 step 2) -- wiring into the boot sequence happens
* in a later punch-list step.
*
* @return 0 on success, -1 if kmalloc failed or stadium_max_vm_count() is 0
* (Stadium not yet initialized).
*/
int session_boot_init(void);
/*
* session_find - Look up a session by the VMUuid of the patron it
* references. Linear scan, same shape as stadium.c's own
* quota_slot_for_vm() -- the population this searches is small (one entry
* per VM, not per word/block).
*
* @return Pointer to the live session, or NULL if none is registered for
* vm_id.
*/
Session *session_find(VMUuid vm_id);
/*
* session_register - Register a new session for vm_id. identity starts
* zeroed (VMIdentity's own documented default: installed=0, "no lock,
* allow freely" -- §H.12 Correction 2). pinned starts 0 (unpinned); use
* session_set_pinned() separately to pin, keeping this function's job to
* "create the session record" only, not "create and also decide pin
* policy" -- callers (e.g. the capsule-birth admission path, §H.12 phase 2)
* decide pinning themselves.
*
* @param vm_id The patron this session references. Must not already have
* a registered session (session_find(vm_id) must be NULL).
* @param parent Who birthed this session (vm_uuid_hera() for Hera's own
* self-registration -- self-referential, matching the
* existing parent_vm_id convention documented in
* capsule_run.h).
* @param name Copied into the new session's name buffer, truncated to
* SESSION_NAME_BUF - 1 if longer.
* @return Pointer to the new session, or NULL if the slot table is full,
* not yet initialized, or vm_id is already registered.
*/
Session *session_register(VMUuid vm_id, VMUuid parent, const char *name);
/*
* session_set_pinned / session_is_pinned - The pin-authority choke point
* (FABRIC-3.md §H.2/§H.10, decided 2026-09-02: "full choke point at the
* session level, both directions"). Session is authoritative for every
* EXTERNAL reader -- nothing else, including existing Stadium code, reads
* or writes STADIUM_FLAG_PIN on a patron header directly anymore.
*
* session_is_pinned() answers from the session's own `pinned` field
* directly (the authoritative copy) -- it does not re-derive the answer
* from Stadium. session_set_pinned() writes both: the session's own
* `pinned` field (authoritative) AND the mirrored STADIUM_FLAG_PIN bit on
* the session's own patron header (stadium_cells()[session->stadium_cell]),
* so the Stadium engine's own internal eviction/admission logic -- which
* must stay self-contained and cannot call back into session.c -- keeps
* seeing a correct, in-sync bit.
*
* Both no-op (return 0 / do nothing) if vm_id has no registered session, or
* if stadium_cell is still SESSION_STADIUM_CELL_NONE (patron not admitted
* yet) for the set path.
*/
void session_set_pinned(VMUuid vm_id, int pinned);
int session_is_pinned(VMUuid vm_id);
#endif /* __STARKERNEL__ */
#endif /* STARKERNEL_SESSION_H */
+10
View File
@@ -46,6 +46,16 @@
* conflating "contains Hera" with "contains nothing." */
#define STADIUM_CONTAINS_NONE ((uint32_t)-1)
/* `flags` bit 0 -- pinned, exempt from eviction/reap. Moved here from a
* stadium.c-private #define (FABRIC-3.md §H.12 step 3) so session.c's pin-
* authority choke point (session_set_pinned()/session_is_pinned()) can
* write/read this same bit without a duplicate definition. Session is
* authoritative for every EXTERNAL reader (FABRIC-3.md §H.10) -- this bit
* on the raw patron header stays a mirrored copy purely for the Stadium
* engine's own internal eviction/admission logic (stadium.c), which must
* stay self-contained and not call back into session.c. */
#define STADIUM_FLAG_PIN 0x01u
/*
* StadiumPatronHeader - one member of the closed two-valued cell union
* (FABRIC.md §3). Nine wires: identity, heat, TTL, pin (a bit in `flags`),
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More