Compare commits
41
Commits
09857b7228
..
v2.0.1
+1
-1
@@ -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
@@ -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
|
||||
(M0–M9 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.4–H.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 4–9) and
|
||||
intentional deferrals (10–11).
|
||||
|
||||
### 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 574–602, 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 4019–4020, next free range after `zuse.4th`'s 4016–4018, 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 10–11**: VM card multi-owner support, live-console
|
||||
`sudo`-style elevation, pre-signed capability tickets.
|
||||
|
||||
+96
-7
@@ -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,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.
|
||||
|
||||
|
||||
@@ -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.*
|
||||
|
||||
@@ -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@ ;
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 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 */
|
||||
@@ -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
Reference in New Issue
Block a user