docs: reorganise -- current reference gathered, finished work archived, superseded and dead ideas marked as such

docs/CLAUDE.md Phase 2, 2026-10-14.  All moves by git mv; every archived
file carries a header saying what it is, why, and where to look instead.

- docs/reference/ (new): roadmap and versioning policy, M7.1, word-ACL
  design, kernel args, hosted acceptance test, dictionary, primitives,
  ontology, L8 table -- previously spread over docs/, docs/lithosananke/
  and docs/03-architecture/.
- docs/working/archive/historical/: finished work (session plan, test-gap
  audit, birthing plan, fix write-ups, the January architecture draft).
- docs/working/archive/superseded/: the Hermes-VM Tripod docs and July
  Hermes designs (-> FABRIC-3.5/3.6), the StarForth roadmap, every working
  doc already flagged superseded or obsolete.
- docs/working/archive/dead/: the hal_* target contract (never adopted),
  L4Re, metrics-and-knobs.
- Papers and proposals into docs/working/papers/.
- References rewritten across the repo (not logs/, not docs/patent/);
  CLAUDE.md's documentation section rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-14 16:03:56 -04:00
co-authored by Claude Opus 5.5
parent 1a4b163861
commit 7aed588f78
116 changed files with 305 additions and 175 deletions
-726
View File
@@ -1,726 +0,0 @@
# LithosAnanke / StarForth — Word Dictionary Reference
**Status:** Living reference document, generated 2026-08-12. Keep it up to date as words
are added, removed, or re-registered — see "Keeping this current" at the bottom.
**Scope: core C primitives only.** This covers every word registered via `register_word()`
(and the one file that uses `vm_create_word()` directly, `physics_pipelining_diagnostic_words.c`)
across `src/word_source/*.c` and `src/starkernel/capsule/mama_forth_words.c` — roughly 470
words. It does **not** cover the ~330 FORTH-defined words living inside `.4th` capsules
(`ACL.4th`, `zuse.4th`, `doe.4th`, workload capsules, etc.) — those are experiment/workload/
policy definitions layered on top of this vocabulary, not the language itself, and change
per-capsule rather than per-build. If a capsule-word reference is wanted later, it belongs in
a separate document — the two have very different lifecycles.
**Source of truth.** Every entry below was extracted directly from source: the word name as
passed to `register_word()`, the stack effect and description from the doc comment
immediately above the implementing function (or inferred from the function body where no
comment exists, marked `(inferred)`). Registration order within each file is preserved
(rather than alphabetized) because related words are grouped together at the call site.
**Two name collisions exist in the live dictionary.** FORTH dictionary lookup walks the
definition chain newest-first (`vm->latest` backward via `->link`), so when two files
register the same name, **the file registered later in `register_forth79_words()`
(`src/word_registry.c`) wins** — the earlier registration becomes permanently unreachable by
name (though still present in the chain). Both cases are noted at point of use below:
- `[`, `]`, `STATE` — registered by `dictionary_manipulation_words.c` (Module 13), then again
by `defining_words.c` (Module 17). **`defining_words.c`'s versions are live.**
- `MOD`, `/MOD`, `*/`, `*/MOD` — registered by `arithmetic_words.c` (Module 4), then again by
`mixed_arithmetic_words.c` (Module 6). **`mixed_arithmetic_words.c`'s versions are live.**
---
## Stack (`stack_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `DROP` | `( n -- )` | Removes the top stack item. |
| `DUP` | `( n -- n n )` | Duplicates the top stack item. |
| `?DUP` | `( n -- n n \| n -- 0 )` | Duplicates the top item only if it is non-zero. |
| `SWAP` | `( n1 n2 -- n2 n1 )` | Exchanges the top two stack items. |
| `OVER` | `( n1 n2 -- n1 n2 n1 )` | Copies the second stack item to the top. |
| `ROT` | `( n1 n2 n3 -- n2 n3 n1 )` | Rotates the top three stack items, moving the third to the top. |
| `-ROT` | `( n1 n2 n3 -- n3 n1 n2 )` | Reverse-rotates the top three stack items. |
| `DEPTH` | `( -- n )` | Pushes the current number of items on the data stack. |
| `PICK` | `( n -- stack[n] )` | Copies the nth stack item (0-indexed from top, after popping `n`) to the top. |
| `ROLL` | `( n -- )` | Moves the nth stack item to the top, shifting intermediate items down. |
## Return Stack (`return_stack_words.c`)
FORTH-79 compliance, stated in this file's own header: only `>R`/`R>`/`R@` are provided — no
direct return-stack addressing (`RP!`/`RP@`), which FORTH-79 forbids.
| Word | Stack Effect | Description |
|---|---|---|
| `>R` | `( x -- )` | Moves x from the data stack to the return stack. |
| `R>` | `( -- x )` | Moves x from the return stack back to the data stack. |
| `R@` | `( -- x )` | Copies the top of the return stack to the data stack without removing it. |
## Memory (`memory_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `@` | `( addr -- n )` | Fetches a cell from VM memory at `addr`. |
| `!` | `( n addr -- )` | Stores cell `n` at VM memory address `addr`. |
| `C@` | `( addr -- c )` | Fetches a byte from VM memory at `addr`. |
| `C!` | `( c addr -- )` | Stores byte `c` at VM memory address `addr`. |
| `+!` | `( n addr -- )` | Adds `n` to the cell currently at `addr`. |
| `-!` | `( n addr -- )` | Subtracts `n` from the cell currently at `addr`. |
| `2@` | `( addr -- x_low x_high )` | Fetches two consecutive cells starting at `addr`. |
| `2!` | `( x_low x_high addr -- )` | Stores two consecutive cells starting at `addr`. |
| `FILL` | `( addr len c -- )` | Fills `len` bytes starting at `addr` with byte value `c`. |
| `MOVE` | `( addr1 addr2 len -- )` | Copies `len` bytes from `addr1` to `addr2`, handling overlap correctly. |
| `ERASE` | `( addr len -- )` | Zeroes `len` bytes starting at `addr`. |
| `CELLS` | `( n -- n' )` | Multiplies `n` by the cell size in bytes. |
## Arithmetic (`arithmetic_words.c`)
`MOD`, `/MOD`, `*/`, `*/MOD` below are **shadowed** — see the collision note at the top of
this document; `mixed_arithmetic_words.c`'s versions are the ones actually live.
| Word | Stack Effect | Description |
|---|---|---|
| `+` | `( n1 n2 -- n3 )` | Adds `n1` and `n2`. |
| `-` | `( n1 n2 -- n3 )` | Subtracts `n2` from `n1`. |
| `*` | `( n1 n2 -- n3 )` | Multiplies `n1` by `n2`. |
| `/` | `( n1 n2 -- n3 )` | Divides `n1` by `n2`; errors on division by zero. |
| `MOD` *(shadowed)* | `( n1 n2 -- n3 )` | `n1` modulo `n2`; errors on division by zero. |
| `/MOD` *(shadowed)* | `( n1 n2 -- n3 n4 )` | Pushes `n1 MOD n2` (remainder) then `n1 / n2` (quotient). |
| `*/` *(shadowed)* | `( n1 n2 n3 -- n4 )` | Computes `n1 * n2 / n3` using a 64-bit intermediate to avoid overflow. |
| `*/MOD` *(shadowed)* | `( n1 n2 n3 -- n4 n5 )` | Computes `n1 * n2 / n3`, pushing remainder then quotient, via a 64-bit intermediate. |
| `1+` | `( n -- n+1 )` | Adds 1. |
| `1-` | `( n -- n-1 )` | Subtracts 1. |
| `2+` | `( n -- n+2 )` | Adds 2. |
| `2-` | `( n -- n-2 )` | Subtracts 2. |
| `2*` | `( n -- n*2 )` | Multiplies by 2 (left shift). |
| `2/` | `( n -- n/2 )` | Divides by 2 (right shift). |
| `ABS` | `( n -- \|n\| )` | Absolute value. |
| `NEGATE` | `( n -- -n )` | Two's-complement negation. |
| `MIN` | `( n1 n2 -- n3 )` | Pushes the smaller of `n1` and `n2`. |
| `MAX` | `( n1 n2 -- n3 )` | Pushes the larger of `n1` and `n2`. |
## Logical & Comparison (`logical_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `AND` | `( n1 n2 -- n3 )` | Bitwise AND. |
| `OR` | `( n1 n2 -- n3 )` | Bitwise OR. |
| `XOR` | `( n1 n2 -- n3 )` | Bitwise XOR. |
| `NOT` | `( flag -- flag )` | FORTH-79 logical NOT: 0 → true (-1), non-zero → false (0). |
| `INVERT` | `( n1 -- n2 )` | Bitwise complement (FORTH-83 extension). |
| `LSHIFT` | `( x1 u -- x2 )` | Logical left shift by `u` bits. |
| `RSHIFT` | `( x1 u -- x2 )` | Logical right shift by `u` bits. |
| `0=` | `( n -- flag )` | True if `n` is zero. |
| `0<` | `( n -- flag )` | True if `n` is negative. |
| `0>` | `( n -- flag )` | True if `n` is positive. |
| `0<>` | `( n -- flag )` | True if `n` is non-zero. |
| `=` | `( n1 n2 -- flag )` | True if equal. |
| `<>` | `( n1 n2 -- flag )` | True if not equal. |
| `<` | `( n1 n2 -- flag )` | True if `n1 < n2` (signed). |
| `>` | `( n1 n2 -- flag )` | True if `n1 > n2` (signed). |
| `>=` | `( n1 n2 -- flag )` | True if `n1 >= n2` (signed). |
| `<=` | `( n1 n2 -- flag )` | True if `n1 <= n2` (signed). |
| `U<` | `( u1 u2 -- flag )` | True if `u1 < u2` (unsigned). |
| `U>` | `( u1 u2 -- flag )` | True if `u1 > u2` (unsigned). |
| `WITHIN` | `( n low high -- flag )` | True if `low <= n < high`. |
| `TRUE` | `( -- flag )` | Pushes -1. |
| `FALSE` | `( -- flag )` | Pushes 0. |
## Mixed Arithmetic (`mixed_arithmetic_words.c`)
`MOD`, `/MOD`, `*/`, `*/MOD` here are the **live** versions — see the collision note at the
top of this document.
| Word | Stack Effect | Description |
|---|---|---|
| `M+` | `( d_high d_low n -- d_high' d_low' )` | Adds single-cell signed `n` to double-cell `d`, with carry propagation. |
| `M-` | `( d_high d_low n -- d_high' d_low' )` | Subtracts single-cell signed `n` from double-cell `d`, with carry propagation. |
| `M*` | `( n1 n2 -- d_low d_high )` | Multiplies two single-cell signed integers into a double-cell result (TOS = high cell). |
| `M/MOD` | `( d_high d_low n -- remainder quotient )` | Divides double-cell dividend `d` by single-cell divisor `n`; TOS = quotient. |
| `MOD` | `( n1 n2 -- remainder )` | Computes `n1 % n2`; errors on division by zero. |
| `/MOD` | `( n1 n2 -- remainder quotient )` | Divides `n1` by `n2`, leaving both remainder and quotient (TOS = quotient). |
| `*/` | `( n1 n2 n3 -- quotient )` | Computes `(n1 * n2) / n3` using a wider intermediate to avoid overflow. |
| `*/MOD` | `( n1 n2 n3 -- remainder quotient )` | Computes `(n1 * n2) / n3`, leaving both remainder and quotient. |
## Double-Precision (`double_words.c`)
A double is represented on the stack as `dlow dhigh` (high cell on top).
| Word | Stack Effect | Description |
|---|---|---|
| `S>D` | `( n -- d )` | Converts a single-precision number to double, sign-extending into the high cell. |
| `D+` | `( d1 d2 -- d3 )` | Double-precision addition with carry propagation. |
| `D-` | `( d1 d2 -- d3 )` | Double-precision subtraction with borrow propagation. |
| `DNEGATE` | `( d1 -- d2 )` | Double-precision two's-complement negation. |
| `DABS` | `( d1 -- d2 )` | Double-precision absolute value. |
| `DMAX` | `( d1 d2 -- d3 )` | Maximum of two double-precision numbers. |
| `DMIN` | `( d1 d2 -- d3 )` | Minimum of two double-precision numbers. |
| `D<` | `( d1 d2 -- flag )` | True if `d1 < d2` (double-precision signed compare). |
| `D=` | `( d1 d2 -- flag )` | True if `d1 = d2`. |
| `2DROP` | `( d -- )` | Drops a double (two cells). |
| `2DUP` | `( d -- d d )` | Duplicates a double. |
| `2SWAP` | `( d1 d2 -- d2 d1 )` | Swaps two doubles. |
| `2OVER` | `( d1 d2 -- d1 d2 d1 )` | Copies the second double to the top. |
| `2ROT` | `( d1 d2 d3 -- d2 d3 d1 )` | Rotates three doubles. |
| `2>R` | `( d -- ) ( R: -- d )` | Moves a double to the return stack (nesting-aware: preserves the resume IP above it when inside a colon-word executor). |
| `2R>` | `( -- d ) ( R: d -- )` | Moves a double from the return stack back to the data stack. |
| `2R@` | `( -- d ) ( R: d -- d )` | Copies a double from the return stack without removing it. |
| `D0=` | `( d -- flag )` | True if the double is zero. |
| `D0<` | `( d -- flag )` | True if the double is negative. |
| `D2*` | `( d1 -- d2 )` | Double-precision left shift by 1 (multiply by 2), carrying across cells. |
| `D2/` | `( d1 -- d2 )` | Double-precision arithmetic right shift by 1 (divide by 2), preserving sign, carrying across cells. |
## Format & Numeric Output (`format_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `.` | `( n -- )` | Prints `n` in the current base, followed by a space. |
| `.R` | `( n width -- )` | Prints `n` right-justified in a field of `width` characters. |
| `U.` | `( u -- )` | Prints `u` as unsigned, followed by a space. |
| `U.R` | `( u width -- )` | Prints `u` as unsigned, right-justified in a field of `width`. |
| `D.` | `( d -- )` | Prints double-cell `d` as signed decimal (or `DOUBLE-OVERFLOW` if it doesn't fit a single cell). |
| `D.R` | `( d width -- )` | Prints double-cell `d` right-justified in a field of `width`. |
| `.S` | `( -- )` | Prints the entire data stack contents non-destructively, prefixed with the depth. |
| `?` | `( addr -- )` | Fetches and prints the cell at `addr`. |
| `DUMP` | `( addr u -- )` | Prints a hex/ASCII memory dump of `u` bytes starting at `addr`. |
| `<#` | `( -- )` | Begins pictured numeric output conversion; resets the hold buffer. |
| `#` | `( ud \| n -- ud2 )` | Converts one digit of the number (in the current base) into the hold buffer. |
| `#S` | `( ud \| n -- 0 0 )` | Converts all remaining digits of the number into the hold buffer. |
| `#>` | `( [ud] -- addr u )` | Ends pictured numeric conversion; pushes the address and length of the formatted string. |
| `HOLD` | `( c -- )` | Prepends character `c` to the pictured-numeric hold buffer. |
| `SIGN` | `( n -- )` | If `n` is negative, holds a `-` character for pictured output. |
| `BASE` | `( -- addr )` | Pushes the VM address of the `BASE` variable (current numeric conversion radix). |
| `DECIMAL` | `( -- )` | Sets `BASE` to 10. |
| `HEX` | `( -- )` | Sets `BASE` to 16. |
| `OCTAL` | `( -- )` | Sets `BASE` to 8. |
## String & Text Processing (`string_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `COUNT` | `( addr1 -- addr2 u )` | Converts a counted string (length-prefixed) at `addr1` into an address/length pair. |
| `EXPECT` | `( addr u -- )` | Reads a line from stdin into the buffer at `addr` (max `u` bytes) and updates `SPAN` with the actual length read. |
| `SPAN` | `( -- addr )` | Pushes the address of the `SPAN` variable, holding the character count from the last `EXPECT`/`QUERY`. |
| `QUERY` | `( -- )` | Reads a line from stdin into the terminal input buffer (TIB), resetting `>IN` to 0 and updating `SPAN`. |
| `TIB` | `( -- addr )` | Pushes the address of the terminal input buffer. |
| `WORD` | `( c -- addr )` | Parses the next word from the input stream delimited by character `c`, returning the address of a counted-string scratch buffer. |
| `(s")` | `( -- c-addr u )` | Runtime for compiled `S"` — reads an inline length-prefixed string from the threaded-code stream and pushes its address/length, advancing past it. |
| `S"` | `( "ccc<quote>" -- c-addr u )` | Parses a quoted string literal. Interpret mode: stores it at `HERE` and pushes address/length. Compile mode: compiles `(s")` plus the inline string data. Immediate. |
| `>IN` | `( -- addr )` | Pushes the address of the `>IN` input-stream-position variable. |
| `SOURCE` | `( -- addr u )` | Pushes the address and length of the current input source (the TIB). |
| `BL` | `( -- c )` | Pushes the ASCII code for space (32). |
| `[']` | `( -- xt )` | Parses the next word from the input stream and pushes (or compiles as a literal) its execution token. Immediate. |
| `LITERAL` | `( -- )` | Placeholder/no-op in this implementation. |
| `[LITERAL]` | `( -- )` | Placeholder/no-op in this implementation (delegates to `LITERAL`). |
| `CONVERT` | `( d1 addr1 -- d2 addr2 )` | Converts digit characters starting at `addr1` into a running double-precision accumulator `d1`→`d2` (simplified, base 10 only), returning the address just past the converted digits. |
| `NUMBER` | `( addr -- n flag )` | Converts a counted string at `addr` to a number (base 10 only); flag is 1 on success, 0 on failure. |
| `ENCLOSE` | `( addr c -- addr1 n1 n2 n3 )` | Parses a delimited field: `n1` = leading-delimiter count, `n2` = end offset of the field, `n3` = offset past trailing delimiters. |
| `-TRAILING` | `( addr u -- addr u' )` | Trims trailing ASCII spaces from a string (direct or counted-string form auto-detected), returning the trimmed length. |
| `CMOVE` | `( addr1 addr2 u -- )` | Copies `u` bytes from `addr1` to `addr2`, ascending address order (correct for overlap when `addr2 >= addr1`). |
| `CMOVE>` | `( addr1 addr2 u -- )` | Copies `u` bytes from `addr1` to `addr2`, descending address order (correct for overlap when `addr2 < addr1`). |
| `COMPARE` | `( addr1 u1 addr2 u2 -- n )` | Lexicographically compares two strings (direct or counted-string form), returns -1/0/+1. |
| `SEARCH` | `( addr1 u1 addr2 u2 -- addr3 u3 flag )` | Finds the first occurrence of string 2 within string 1; returns the matching tail and a found flag. |
| `SCAN` | `( addr u char -- addr' u' )` | Finds the first occurrence of `char` in the string, returning the tail starting there (or end-of-string if not found). |
| `SKIP` | `( addr u char -- addr' u' )` | Skips leading occurrences of `char` in the string, returning the remaining tail. |
| `BLANK` | `( addr u -- )` | Fills `u` bytes at `addr` with ASCII space (auto-detects counted-string form). |
## I/O & Terminal (`io_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `EMIT` | `( c -- )` | Outputs the character `c` to the terminal. |
| `CR` | `( -- )` | Outputs a newline character. |
| `KEY` | `( -- c )` | Reads one character from the terminal and pushes it. |
| `?TERMINAL` | `( -- flag )` | Pushes whether input is available (stub implementation, always pushes false). |
| `TYPE` | `( addr u -- )` | Outputs `u` characters from VM memory starting at `addr`. |
| `SPACE` | `( -- )` | Outputs a single space character. |
| `SPACES` | `( n -- )` | Outputs `n` space characters. |
| `(do-string)` | `( -- )` | Runtime helper compiled by `."` — reads an inline length-prefixed string from the threaded code and prints it, advancing IP past it. Not meant to be called directly. |
| `."` | `( "ccc<quote>" -- )` | Immediate. Interpretation: parses and prints a string up to the closing `"` directly. Compilation: compiles `(do-string)` plus the inline string data. |
## Block & Mass Storage (`block_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `BLOCK` | `( u -- addr )` | Returns the VM address of block `u`'s content, loading it if needed (does not mark dirty). |
| `BUFFER` | `( u -- addr )` | Returns the VM address of block `u`'s buffer without reading its content; marks it dirty. |
| `UPDATE` | `( -- )` | Marks the currently-loaded block (per `SCR`) dirty and syncs it to the underlying block-subsystem buffer. |
| `BLK-CONFIRM-FORMAT` | `( lbn -- )` | Commits the low-level disk container format for the slot owning block `lbn`; until called, writes to that disk slot are refused. Must be called only by the disk's owner after classifying its content as safe. |
| `SAVE-BUFFERS` | `( -- )` | Writes back every dirty block window slot to the block subsystem. |
| `EMPTY-BUFFERS` | `( -- )` | Invalidates all block window slots and zeroes all user blocks, without writing back. |
| `FLUSH` | `( -- )` | Same as `SAVE-BUFFERS` — syncs and invalidates all buffers. |
| `LOAD` | `( u -- )` | Sets `SCR` to `u` and interprets block `u`'s content as FORTH source. |
| `LIST` | `( u -- )` | Sets `SCR` to `u` and prints the block's content formatted as 16 lines of 64 characters with line numbers. |
| `THRU` | `( u1 u2 -- )` | Executes `LOAD` on every block from `u1` to `u2` inclusive. |
| `SCR` | `( -- addr )` | Pushes the VM address of the `SCR` (current block) variable. |
| `-->` | `( -- )` | Continues interpretation on the next sequential block after the current `SCR`. |
## Dictionary & Compilation (`dictionary_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `HERE` | `( -- addr )` | Returns the dictionary pointer. |
| `ALIGN` | `( -- )` | Aligns the dictionary pointer to a cell boundary. |
| `ALLOT` | `( n -- )` | Allocates `n` bytes in the dictionary; `n` may be negative to deallocate. |
| `,` | `( n -- )` | Compiles a single cell value into the dictionary at `HERE`. |
| `C,` | `( c -- )` | Compiles a single byte value into the dictionary at `HERE`. |
| `2,` | `( d -- )` | Compiles a double-cell value into the dictionary at `HERE`, low cell first. |
| `PAD` | `( -- addr )` | Returns the VM address of the 512-byte scratch text buffer at top of memory. |
| `SP!` | `( sp -- )` | Sets the data stack pointer; can only shrink the stack, never grow it. |
| `SP@` | `( -- sp )` | Returns the current data stack-pointer index (top is 0). |
| `LATEST` | `( -- addr )` | Returns the VM address near the most recent compiled definition (end of dictionary). |
## Dictionary Manipulation (`dictionary_manipulation_words.c`)
`[`, `]`, `STATE` below are **shadowed** — see the collision note at the top of this
document; `defining_words.c`'s versions are the ones actually live.
| Word | Stack Effect | Description |
|---|---|---|
| `[` *(shadowed)* | `( -- )` | Enters interpretation mode. |
| `]` *(shadowed)* | `( -- )` | Enters compilation mode. |
| `STATE` *(shadowed)* | `( -- addr )` | Pushes the address of this file's own `state_variable` (a separate static, not the VM's `vm->state_addr` used by `defining_words.c`'s `STATE`). |
| `SMUDGE` | `( -- )` | Compile-only: intended to toggle the hidden/smudge bit of the latest word (function body is a stub/placeholder in the current source). |
| `HIDDEN` | `( -- )` | Compile-only: sets the hidden flag on the most recently defined word. |
| `>BODY` | `( xt -- addr )` | Converts an execution token to its data-field (body) address. |
| `>NAME` | `( xt -- addr )` | Converts an execution token to the address of its name field. |
| `NAME>` | `( addr -- xt )` | Converts a name-field address back to its execution token, by linear search of the dictionary. |
| `>LINK` | `( addr -- addr )` | Pushes the address of a dictionary entry's link field. |
| `LINK>` | `( addr -- addr )` | Dereferences a link-field address to get the next dictionary entry. |
| `CFA` | `( addr -- xt )` | Returns the code-field address of an entry — in this implementation, the entry pointer itself. |
| `LFA` | `( addr -- addr )` | Alias for `>LINK` — returns the link-field address. |
| `NFA` | `( addr -- addr )` | Returns the name-field address of a dictionary entry. |
| `PFA` | `( addr -- addr )` | Returns the parameter-field (body) address of a dictionary entry. |
| `TRAVERSE` | `( addr n -- addr )` | Moves forward or backward across a name field by `n`. |
| `INTERPRET` | `( -- )` | Sets interpret mode; actual interpretation is driven by the VM's own `vm_interpret()`, not this word's body. |
| `FIND` | `( "name" -- xt \| 0 )` | Parses the next word and pushes its dictionary entry pointer, or 0 if not found (a miss is not an error). |
| `'` | `( "name" -- xt )` | FORTH-79 tick: parses the next word and pushes its execution token; errors if not found. Non-immediate — the compiler compiles a call to it in compile mode. |
## Vocabulary System (`vocabulary_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `VOCABULARY` | `( -- )` | Creates a new vocabulary; executing the created word selects itself as CONTEXT. |
| `DEFINITIONS` | `( -- )` | Sets CURRENT to CONTEXT, so new words are defined into the CONTEXT vocabulary. |
| `CONTEXT` | `( -- addr )` | Returns the VM address of the cell holding the CONTEXT vocabulary pointer. |
| `CURRENT` | `( -- addr )` | Returns the VM address of the cell holding the CURRENT vocabulary pointer. |
| `FORTH` | `( -- )` | Makes FORTH the CONTEXT vocabulary. |
| `ORDER` | `( -- )` | Prints the current search order (CONTEXT then FORTH) and CURRENT. |
| `(FIND)` | `( addr -- addr flag )` | Primitive finder: searches CONTEXT then FORTH vocabularies for the counted string at `addr`. |
## System & Environment (`system_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `(` | `( -- )` | Begins a comment; skips input up to the matching `)`, honoring nested parens. Immediate. |
| `\` | `( -- )` | Line comment; skips input to the end of the current line. Immediate. |
| `COLD` | `( -- )` | Performs a cold start: resets VM state and rewinds `HERE` toward the base dictionary. |
| `WARM` | `( -- )` | Performs a warm restart: resets stacks/mode/error without rewinding `HERE`. |
| `BYE` | `( -- )` | Halts the VM, signalling the REPL to stop. (Overridden for Hera — see Kernel: Capsule Birth & Tripod Fleet below.) |
| `REBOOT` | `( addr len -- )` | Sets boot arguments from the given string and performs a cold reset (kernel target) or prints a hosted-stub message and halts. Interpret-only. |
| `SAVE-SYSTEM` | `( -- )` | Writes a trivial snapshot of VM memory (up to `HERE`) to `forth_system.img`. |
| `WORDS` | `( -- )` | Lists every word name currently in the dictionary, 8 per line, with a total count. |
| `VLIST` | `( -- )` | Lists every dictionary entry with its name, address, and flags byte. |
| `SEE` | `( "name" -- )` | Decompiles and prints the named word's definition (threaded code, or `<primitive>` for C words). |
| `PAGE` | `( -- )` | Clears the terminal screen via an ANSI escape sequence. |
| `EXECUTE` | `( xt -- )` | Executes the word whose execution token is on the stack. |
| `NOP` | `( -- )` | Does nothing. |
| `79-STANDARD` | `( -- flag )` | Prints and pushes whether FORTH-79 standard-compliance mode is active (-1) or not (0). |
| `QUIT` | `( -- )` | Clears the return stack and returns to the interpreter, without treating it as an error. Immediate. |
| `ABORT` | `( -- )` | Clears both stacks and returns to the interpreter; not treated as an error condition. |
| `(ABORT")` | `( flag addr len -- )` | Runtime helper for `ABORT"`: if `flag` is nonzero, prints the message at `addr len` and performs `ABORT` semantics. |
| `ABORT"` | `( flag -- )` | Parses a message up to the next `"`; if `flag` is nonzero (interpret mode) or at run time (compiled), prints it and aborts. Immediate. |
## Line Editor (`editor_words.c`)
Non-standard (not FORTH-79).
| Word | Stack Effect | Description |
|---|---|---|
| `L` | `( u -- )` | Prints line `u` (0-15) of the current SCR block. |
| `S` | `( c-addr len u -- )` | Sets line `u` of the current SCR block from a buffer, padding with spaces to 64 chars. |
| `SHOW` | `( -- )` | Prints the whole 16x64 current screen with line numbers. |
| `EDIT` | `( u -- )` | Enters a tiny interactive line-editor shell (stdin/stdout) on block `u`. |
## Defining Words (`defining_words.c`)
`[`, `]`, `STATE` here are the **live** versions — see the collision note at the top of this
document.
| Word | Stack Effect | Description |
|---|---|---|
| `:` | `( "name" -- )` | Begins a colon definition; parses the name and enters compile mode. Immediate. |
| `;` | `( -- )` | Ends a colon definition, compiling an implicit `EXIT` and returning to interpret mode. Immediate. |
| `CREATE` | `( "name" -- )` | Defines a new word whose runtime pushes its data-field address (FORTH-79: allocates no data itself). |
| `VARIABLE` | `( "name" -- )` | Defines a new word that allocates one cell (initialized to 0) and whose runtime pushes that cell's address. |
| `CONSTANT` | `( n "name" -- )` | Defines a new word whose runtime pushes the fixed value `n`. |
| `IMMEDIATE` | `( -- )` | Marks the most recently defined word as IMMEDIATE (executes during compilation instead of being compiled in). |
| `STATE` | `( -- addr )` | Pushes the VM address of the `STATE` variable (0 = interpreting, nonzero = compiling). |
| `[` | `( -- )` | Enters interpret state (sets `STATE` to 0). Immediate. |
| `]` | `( -- )` | Enters compile state (sets `STATE` to -1). |
| `FORGET` | `( "name" -- )` | Removes the named word and every word defined after it from the dictionary, rewinding `HERE`. |
| `COMPILE` | `( "word" -- )` | Legacy immediate word: parses the next word and compiles a call to it. Immediate. |
| `[COMPILE]` | `( "word" -- )` | Compiles the next word even if it is itself IMMEDIATE. Immediate. |
| `LIT` | `( -- n )` | Runtime primitive: fetches the next cell from threaded code (via the return-stack IP) and pushes it as a literal. |
| `LITERAL` | `( n -- )` | Compiles `n` as a literal into the current definition (via `LIT`). Immediate. |
| `does_rt` | `( -- )` *(inferred)* | Internal helper: patches the just-created child word to use the `DOES>` runtime and records the `DOES>` body address in its parameter field. Not meant to be called directly from FORTH. |
| `DOES>` | `( -- )` | Finalizes a defining word's CREATE-part and compiles the following code as the DOES>-body run by every word the defining word subsequently creates. Immediate, compile-only. |
| `DEFER` | `( "name" -- )` | Creates a deferred word with an empty execution-token slot; errors if executed before `IS` sets it. |
| `IS` | `( xt "name" -- )` | Stores execution token `xt` into a word previously created with `DEFER`. |
| `DEFER@` | `( "name" -- xt )` | Fetches the execution token currently stored in a deferred word. |
## Control Flow (`control_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `(BRANCH)` | `( -- )` | Runtime: unconditional branch — reads a relative byte offset from the inline thread and adjusts the return-stack IP by it. |
| `(0BRANCH)` | `( f -- )` | Runtime: conditional branch — pops a flag; if zero, branches by the inline relative offset, otherwise skips it. |
| `(?DO)` | `( limit index -- )` | Runtime for `?DO` — if index equals limit the loop body is skipped entirely (branches past it); otherwise pushes (limit, index) onto the return stack as the loop frame. |
| `(DO)` | `( limit index -- )` | Runtime for `DO` — unconditionally pushes (limit, index) onto the return stack as the loop frame. |
| `(LOOP)` | `( -- )` | Runtime for `LOOP` — increments the loop index; if still less than the limit, branches back to the loop body, else drops the loop frame and falls through. |
| `(+LOOP)` | `( n -- )` | Runtime for `+LOOP` — adds `n` to the loop index; continues looping while the (signed-direction-aware) index hasn't crossed the limit, else exits. |
| `(LEAVE)` | `( -- )` | Runtime for `LEAVE` — forces the current loop to exit at the next `LOOP`/`+LOOP` by setting index equal to limit. |
| `IF` | `( f -- )` *(compile-time)* | Compiles a conditional branch (`0BRANCH`) with a placeholder target, pushed for later patching by `ELSE`/`THEN`. Immediate. |
| `ELSE` | `( -- )` *(compile-time)* | Patches the matching `IF`'s branch to land here, compiles an unconditional branch past the else-clause, pushed for `THEN` to patch. Immediate. |
| `THEN` | `( -- )` *(compile-time)* | Patches the matching `IF` or `ELSE` branch to land here, closing the conditional. Immediate. |
| `BEGIN` | `( -- )` *(compile-time)* | Marks the start of a loop (target for `UNTIL`/`AGAIN`/`REPEAT`). Immediate. |
| `WHILE` | `( f -- )` *(compile-time)* | Compiles a conditional exit branch inside a `BEGIN...REPEAT` loop, patched by `REPEAT`. Immediate. |
| `REPEAT` | `( -- )` *(compile-time)* | Compiles an unconditional branch back to `BEGIN` and patches `WHILE`'s exit branch to land after it. Immediate. |
| `AGAIN` | `( -- )` *(compile-time)* | Compiles an unconditional branch back to the matching `BEGIN` (infinite loop unless exited via `LEAVE`/`EXIT`). Immediate. |
| `UNTIL` | `( f -- )` *(compile-time)* | Compiles a conditional branch back to the matching `BEGIN`; loop repeats while the flag is false. Immediate. |
| `?DO` | `( limit index -- )` *(compile-time)* | Compiles the `(?DO)` runtime call plus loop-frame bookkeeping for `LOOP`/`LEAVE`. Immediate. |
| `DO` | `( limit index -- )` *(compile-time)* | Compiles the `(DO)` runtime call plus loop-frame bookkeeping for `LOOP`/`LEAVE`. Immediate. |
| `LOOP` | `( -- )` *(compile-time)* | Compiles the `(LOOP)` runtime call, patches the back-edge to `DO`/`?DO` and any pending `LEAVE` sites. Immediate. |
| `+LOOP` | `( n -- )` *(compile-time)* | Compiles the `(+LOOP)` runtime call, patches the back-edge to `DO`/`?DO` and any pending `LEAVE` sites. Immediate. |
| `LEAVE` | `( -- )` *(compile-time)* | Compiles the runtime leave-flag plus a forward branch out of the enclosing loop, patched at the next `LOOP`/`+LOOP`. Immediate. |
| `I` | `( -- i )` | Pushes the index of the innermost active `DO`/`?DO` loop. |
| `J` | `( -- j )` | Pushes the index of the next-outer `DO`/`?DO` loop (requires nested loops). |
| `UNLOOP` | `( -- )` | Discards the current loop's (limit, index) frame from the return stack without branching — used before an early `EXIT` from inside a loop. |
| `EXIT` | `( -- )` | Marks the current colon definition for a one-shot early return. |
| `CASE` | `( n -- n )` *(compile-time)* | Marks the start of a `CASE...OF...ENDOF...ENDCASE` selector statement. Immediate. |
| `OF` | `( n1 n2 -- \| n1 )` *(compile-time)* | Compiles a compare-and-branch (`OVER = 0BRANCH DROP`) for one `CASE` clause. Immediate. |
| `ENDOF` | `( -- )` *(compile-time)* | Ends one `OF` clause: patches its branch, compiles a jump to `ENDCASE`. Immediate. |
| `ENDCASE` | `( n -- )` *(compile-time)* | Ends the `CASE` statement: compiles a `DROP` for the selector and patches all `ENDOF` branches to land here. Immediate. |
## StarForth Extensions (`starforth_words.c`)
Each word is registered twice — once into the root vocabulary, once again into the
`STARFORTH` vocabulary context (`ENTROPY@`/`ENTROPY!` only registered the second time).
Listed once below.
| Word | Stack Effect | Description |
|---|---|---|
| `ENTROPY@` | `( addr -- n )` | Fetches the execution-heat counter for the `DictEntry` at `addr` (validated against the live dictionary chain). |
| `ENTROPY!` | `( n addr -- )` | Sets the execution-heat counter for the `DictEntry` at `addr`. |
| `WORD-ENTROPY` | `( -- )` | Prints execution-heat statistics (per word, total, average) for every word in the dictionary. |
| `RESET-ENTROPY` | `( -- )` | Resets every word's execution-heat counter and compudynamics metadata (temperature, latency, last-active) to zero. |
| `TOP-WORDS` | `( n -- )` | Prints the `n` most frequently executed words, sorted descending by execution heat. |
| `(-` | `( -- )` | Comment word: consumes input up to a balanced closing `)` — used for `init.4th` metadata markers. |
| `INIT` | `( -- )` | Reads `./capsules/core/init.4th`, remaps and copies its numbered blocks sequentially starting at block 1, executes them via `LOAD`, then zeroes the init blocks for userspace reuse. |
| `VERSION` | `( -- )` | Prints the StarForth version string (`STARFORTH_VERSION_FULL`). |
| `SEED` | `( n -- )` | Seeds the internal PRNG (LCG) with `n` (forces non-zero state). |
| `RANDOM` | `( lo hi -- n )` | Returns a pseudo-random integer in the inclusive range `[lo, hi]`. |
| `WAIT` | `( n -- )` | Waits `n` heartbeat ticks (calls `vm_tick()` n times) — architecture-independent, no wall-clock dependency. |
| `ZUSE-AUTHENTICATE` | `( -- )` | Sets `vm->zuse_session = 1` — C-only superuser bypass, no FORTH-level authentication logic. |
## Word-Level ACL (`acl_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `ACL-MODE@` | `( xt -- mode )` | Fetches the ACL enforcement mode of the word `xt`: 0 = STRICT (permanent), 1 = TTL (expires after countdown). |
| `ACL-PINNED?` | `( xt -- flag )` | Pushes -1 if the word `xt` is pinned (its ACL fields are immutable), 0 otherwise. |
| `ACL-TTL@` | `( xt -- n )` | Fetches the current TTL countdown of the word's ACL entry. |
| `ACL-ALLOW@` | `( xt -- flag )` | Fetches the cached allow/deny decision for the word: -1 = allowed, 0 = denied. |
| `ACL-HEAT@` | `( xt -- heat )` | Fetches the execution-heat counter for the word (used to calibrate TTL). |
| `ACL-WORD-ID` | `( xt -- n )` | Fetches the word's stable `word_id`, assigned at registration and usable as a persistent table index. |
| `ACL-MODE!` | `( mode xt -- )` | Sets the ACL enforcement mode of word `xt`. No-op if the word is pinned. |
| `ACL-TTL!` | `( n xt -- )` | Sets the TTL countdown on the word's ACL entry (clamped to `[0, UINT32_MAX]`). No-op if pinned. |
| `ACL-ALLOW!` | `( flag xt -- )` | Sets the cached allow/deny decision for word `xt`. No-op if pinned. |
| `ACL-PIN` | `( xt -- )` | Sets `acl_pinned` on word `xt`. One-way ratchet — can never be cleared once set. |
| `ACL-INHERIT` | `( src dst -- )` | Copies `acl_mode` from `src` to `dst`, then clears `dst`'s pin, resets its TTL to 0, and sets `acl_allow` to 1 (optimistic default). |
| `ACL-INIT-PRIMITIVES` | `( -- )` | Walks the entire dictionary and resets ACL fields (TTL=0, allow=1, mode=TTL) on every unpinned entry. |
## Compudynamics: Benchmark (`physics_benchmark_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `BENCH-DICT-LOOKUP` | `( iterations -- )` | Benchmarks dictionary lookup performance over a set of common words for the given iteration count; warns if iterations < 10,000 for statistical validity. |
| `PHYSICS-CACHE-STATS` | `( -- )` | Displays detailed hot-words cache statistics and current cache contents. |
| `PHYSICS-TOGGLE-CACHE` | `( -- )` | Enables/disables the hot-words cache at runtime for A/B testing. |
| `PHYSICS-RESET-STATS` | `( -- )` | Resets cache and pipeline-metrics statistics for a clean before/after comparison. |
| `PHYSICS-BUILD-INFO` | `( -- )` | Prints the current hot-words cache build configuration and runtime state. |
| `PHYSICS-BAYESIAN-REPORT` | `( addr_baseline -- )` | Generates a Bayesian inference report comparing current cache stats against a baseline. |
## Compudynamics: Pipelining Diagnostics (`physics_pipelining_diagnostic_words.c`)
Registers via `vm_create_word()` directly rather than `register_word()` — the only file in
the tree that does this (verified by a `vm_create_word(vm, "` sweep of the whole
`word_source/` directory).
| Word | Stack Effect | Description |
|---|---|---|
| `PIPELINING-SHOW-STATS` | `( c-addr u -- )` | Displays word-to-word transition metrics (prefetch attempts/hits/misses, latency saved) for the named word. |
| `PIPELINING-SHOW-TOP-TRANSITIONS` | `( c-addr u n -- )` | Displays the top `n` words that typically follow the named word, ranked by transition count. |
| `PIPELINING-RESET-ALL` | `( -- )` | Clears transition metrics from every word in the dictionary. |
| `PIPELINING-ENABLE` | `( -- )` | Reports whether pipelining metrics collection is enabled (compile-time flag; this word does not toggle it). |
| `PIPELINING-STATS` | `( -- )` | Displays aggregate pipelining statistics (transitions, prefetch hit rate) across the whole dictionary. |
| `PIPELINING-ANALYZE-WORD` | `( c-addr u -- )` | Comprehensive analysis of one word's transition predictability (entropy/distribution spread) plus prefetch net benefit. |
## Compudynamics: Freeze/Decay Control (`physics_freeze_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `FREEZE-WORD` | `( caddr u -- )` | Sets `WORD_FROZEN` on the named word so its execution heat stops decaying; silently succeeds if not found. |
| `UNFREEZE-WORD` | `( caddr u -- )` | Clears `WORD_FROZEN` on the named word, letting heat decay resume normally. |
| `FROZEN?` | `( caddr u -- flag )` | Pushes true (-1) if the named word is frozen, false (0) otherwise or if not found. |
| `HEAT!` | `( heat caddr u -- )` | Directly writes `execution_heat` for the named word (diagnostics; bypasses normal accumulation/decay). |
| `HEAT@` | `( caddr u -- heat )` | Pushes the current `execution_heat` of the named word (0 if not found). |
| `SHOW-HEAT` | `( caddr u -- )` | Prints the named word's heat plus frozen/pinned status to stdout. |
| `ALL-HEATS` | `( -- )` | Prints a table of every dictionary word sorted by descending execution heat. |
| `DECAY-RATE@` | `( -- rate )` | Pushes the compile-time base heat decay rate (Q48.16) per microsecond used by Loop #3. |
| `FREEZE-CRITICAL` | `( -- )` | Freezes a hard-coded list of 21 system-critical words (`DUP`, `DROP`, `SWAP`, `IF`, `EXECUTE`, etc.) so they never decay out of the hot-words cache. |
## Compudynamics: General Diagnostics (`physics_diagnostic_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `PHYSICS-WORD-METRICS` | `( -- )` | Displays temperature, execution heat, latency, mass, and derived thermal pressure for the most recently executed word. |
| `PHYSICS-CALC-KNOBS` | `( -- )` | Calculates and displays recommended priority/sampling/stack-limit/affinity adjustments based on the most recent word's thermal pressure. |
| `PHYSICS-BURN` | `( n -- )` | Executes the most recently executed word `n` times, printing thermal feedback progress and a final summary. |
| `PHYSICS-SHOW-FEEDBACK` | `( -- )` | Displays the full feedback-loop demonstration (metrics → math → knobs → effect) for the most recent word. |
## Dictionary Heat Optimization (`dictionary_heat_diagnostic_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `HEAT-PERCENTILES` | `( -- 25th 50th 75th )` | Pushes the three heat percentile thresholds used by heat-aware dictionary lookup. |
| `LOOKUP-STRATEGY@` | `( -- strategy )` | Pushes the current lookup strategy: 0 = naive (newest-first), 1 = heat-aware. |
| `LOOKUP-STRATEGY!` | `( strategy -- )` | Sets the lookup strategy; only 0 or 1 accepted, other values silently discarded. |
| `REORG-BUCKETS` | `( -- )` | Forces an immediate re-sort of dictionary lookup buckets by heat and refreshes percentile thresholds. |
| `SHOW-HEAT-OPTIMIZATION` | `( -- )` | Prints a summary of lookup strategy, percentile thresholds, and resulting hot/warm/cool zones. |
| `COMPARE-LOOKUPS` | `( iterations -- )` | Micro-benchmarks naive vs. heat-aware dictionary lookup over six common words and prints the speedup/slowdown. |
## Log Level Control (`log_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `LOG-ERROR` | `( -- n )` | Pushes the `LOG_ERROR` level constant. |
| `LOG-WARN` | `( -- n )` | Pushes the `LOG_WARN` level constant. |
| `LOG-INFO` | `( -- n )` | Pushes the `LOG_INFO` level constant. |
| `LOG-TEST` | `( -- n )` | Pushes the `LOG_TEST` level constant. |
| `LOG-DEBUG` | `( -- n )` | Pushes the `LOG_DEBUG` level constant. |
| `LOG-LEVEL!` | `( n -- )` | Sets the active log filter level (clamped to `[LOG_ERROR, LOG_DEBUG]`). |
| `LOG-LEVEL@` | `( -- n )` | Pushes the current log filter level. |
| `(do-log-error)` | `( -- )` | Runtime for compiled `LOG-ERROR"` — emits the inline string at ERROR level. |
| `(do-log-warn)` | `( -- )` | Runtime for compiled `LOG-WARN"` — emits the inline string at WARN level. |
| `(do-log-info)` | `( -- )` | Runtime for compiled `LOG-INFO"` — emits the inline string at INFO level. |
| `(do-log-test)` | `( -- )` | Runtime for compiled `LOG-TEST"` — emits the inline string at TEST level. |
| `(do-log-debug)` | `( -- )` | Runtime for compiled `LOG-DEBUG"` — emits the inline string at DEBUG level. |
| `LOG-ERROR"` | `( "ccc<quote>" -- )` | Logs a quoted string literal at ERROR level (interpret: immediate; compile: compiles the runtime + inline data). Immediate. |
| `LOG-WARN"` | `( "ccc<quote>" -- )` | Logs a quoted string literal at WARN level. Immediate. |
| `LOG-INFO"` | `( "ccc<quote>" -- )` | Logs a quoted string literal at INFO level. Immediate. |
| `LOG-TEST"` | `( "ccc<quote>" -- )` | Logs a quoted string literal at TEST level. Immediate. |
| `LOG-DEBUG"` | `( "ccc<quote>" -- )` | Logs a quoted string literal at DEBUG level. Immediate. |
| `LOG-ERROR-STR` | `( c-addr u -- )` | Logs a stack-supplied string (e.g. from `S"`) at ERROR level. |
| `LOG-WARN-STR` | `( c-addr u -- )` | Logs a stack-supplied string at WARN level. |
| `LOG-INFO-STR` | `( c-addr u -- )` | Logs a stack-supplied string at INFO level. |
| `LOG-TEST-STR` | `( c-addr u -- )` | Logs a stack-supplied string at TEST level. |
| `LOG-DEBUG-STR` | `( c-addr u -- )` | Logs a stack-supplied string at DEBUG level. |
## Q48.16 Fixed-Point Math (`q48_words.c`)
`cell_t` reinterpreted as `uint64_t`; 1.0 = 65536, resolution ≈ 0.0000153.
| Word | Stack Effect | Description |
|---|---|---|
| `Q.+` | `( q1 q2 -- q_sum )` | Q48.16 addition. |
| `Q.-` | `( q1 q2 -- q_diff )` | Q48.16 subtraction. |
| `Q.*` | `( q1 q2 -- q_prod )` | Q48.16 multiplication. |
| `Q./` | `( q1 q2 -- q_quot )` | Q48.16 division; returns `q=0` on division by zero. |
| `Q.ABS` | `( q -- \|q\| )` | Absolute value. |
| `Q.NEG` | `( q -- -q )` | Negation. |
| `Q.LOG` | `( q -- ln_q )` | Natural log via Newton-Raphson approximation. |
| `Q.EXP` | `( q -- e^q )` | Exponential via Taylor series approximation. |
| `Q.SQRT` | `( q -- sqrt_q )` | Square root via Newton-Raphson approximation. |
| `Q.SIN` | `( q -- sin_q )` | Sine (radians, any magnitude) via Taylor series. |
| `Q.COS` | `( q -- cos_q )` | Cosine (radians, any magnitude) via Taylor series. |
| `Q.FROM-INT` | `( n -- q )` | Converts integer to Q48.16 (`n << 16`). |
| `Q.TO-INT` | `( q -- n )` | Converts Q48.16 to integer, truncating (`q >> 16`). |
| `Q.1` | `( -- 65536 )` | Pushes 1.0 in Q48.16. |
| `Q.0` | `( -- 0 )` | Pushes 0.0 in Q48.16. |
| `Q.SCALE` | `( -- 65536 )` | Pushes the scale factor (alias of `Q.1`). |
| `Q.=` | `( q1 q2 -- flag )` | -1 if equal, else 0. |
| `Q.<` | `( q1 q2 -- flag )` | -1 if `q1 < q2`, else 0. |
| `Q.>` | `( q1 q2 -- flag )` | -1 if `q1 > q2`, else 0. |
| `Q.0=` | `( q -- flag )` | -1 if `q = 0`, else 0. |
| `Q.MAX` | `( q1 q2 -- q_max )` | Maximum of two Q48.16 values. |
| `Q.MIN` | `( q1 q2 -- q_min )` | Minimum of two Q48.16 values. |
| `Q.PRINT` | `( q -- )` | Prints as `"integer.frac"` to the console. |
Note: `q48_16_words.c` (despite the similar filename) registers no FORTH words at all — it's
a pure C support library (multiply/divide with wide intermediates) consumed by other files
like `ttf_words.c`, not a word-registration file.
## SSM Inference + Jacquard (`inference_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `Q.VARIANCE` | `( addr u -- q )` | Computes the variance of `u` uint64 cells starting at `addr`, result as Q48.16. |
| `INFER-DECAY-SLOPE` | `( addr u -- q )` | Computes the exponential decay slope of `u` cells at `addr` via linear regression, as Q48.16. |
| `INFER-WINDOW-WIDTH` | `( addr u -- n )` | Computes the optimal window width from the variance inflection point of `u` cells at `addr`. |
| `WINDOW-DIVERSITY` | `( -- u )` | Pushes the rolling window's diversity (entropy) metric. |
| `INFER-RUN` | `( -- )` | Runs the full inference engine against this VM's rolling window and dictionary heat stats, updating `vm->last_inference_outputs`. |
| `INFER-WINDOW@` | `( -- u )` | Fetches the last computed adaptive window width. |
| `INFER-DECAY@` | `( -- q )` | Fetches the last computed adaptive decay slope (Q48.16). |
| `INFER-VARIANCE@` | `( -- q )` | Fetches the last computed window variance (Q48.16). |
| `INFER-FIT@` | `( -- q )` | Fetches the last computed slope fit quality (Q48.16). |
| `INFER-EARLY-EXIT@` | `( -- flag )` | Pushes 1 if the last `INFER-RUN` used the ANOVA early-exit path. |
| `L8-MODE` | `( -- n )` | Pushes the current L8 Jacquard mode (0-15). |
| `L8-UPDATE` | `( entropy_q cv_q temporal_q stability_q -- )` | Feeds four Q48.16 metrics into the L8 Jacquard mode selector's update function. |
| `L8-APPLY` | `( -- )` | Applies the current L8 mode to `vm->ssm_config` (legacy 16-mode path). |
| `L8-TABLE-FORCE` | `( config_idx -- )` | Forces the adaptive 128-config table onto `config_idx`, as if the bandit's own selection had picked it, and applies it immediately. |
| `BAYES-CACHE-MEAN` | `( -- q )` | Pushes the Bayesian mean latency estimate for hot-word cache hits (Q48.16). |
| `BAYES-CACHE-LOWER` | `( -- q )` | Pushes the 95% credible lower bound for cache-hit latency. |
| `BAYES-CACHE-UPPER` | `( -- q )` | Pushes the 95% credible upper bound for cache-hit latency. |
| `BAYES-BUCKET-MEAN` | `( -- q )` | Pushes the Bayesian mean latency estimate for dictionary bucket searches. |
| `BAYES-BUCKET-LOWER` | `( -- q )` | Pushes the 95% credible lower bound for bucket-search latency. |
| `BAYES-BUCKET-UPPER` | `( -- q )` | Pushes the 95% credible upper bound for bucket-search latency. |
## DEFER / Late Binding (`defer_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `DEFER` | `( -- )` | Parses a name and creates a deferred word with an unset (0) data field; calling it before `IS` sets `vm->error`. |
| `IS` | `( xt -- )` | Parses a name (which must have been created by `DEFER`) and stores `xt` as its target action, enabling late binding. |
| `DEFER@` | `( -- xt )` | Parses a name (must be `DEFER`-created) and pushes its current target execution token. |
## Console Fabric — Framebuffer (`framebuffer_words.c`)
Kernel-only (`__STARKERNEL__`); no-ops on hosted builds. Raw hardware-boundary access —
FABRIC-0.md item 4.3.3.
| Word | Stack Effect | Description |
|---|---|---|
| `PLOT` | `( x y color -- )` | Raw raster pixel write at (x,y), top-left origin, Y-down. No Cartesian awareness. |
| `FB-WIDTH` | `( -- n )` | Pushes the framebuffer width in pixels (0 on hosted builds). |
| `FB-HEIGHT` | `( -- n )` | Pushes the framebuffer height in pixels (0 on hosted builds). |
## Console Fabric — Keyboard (`keyboard_words.c`)
| Word | Stack Effect | Description |
|---|---|---|
| `KBD-SCAN` | `( -- c -1 \| 0 )` | Pops one raw scancode off the amd64 i8042 ring buffer if available; pushes 0 if empty or on other architectures. |
| `KBD-DEBUG` | `( -- isr_count spurious_count )` | Pushes the i8042 interrupt and spurious-interrupt counters (amd64 diagnostic). |
| `VKBD-EVENT` | `( -- code value -1 \| 0 )` | Pops one decoded EV_KEY event off the virtio-input ring buffer (riscv64/aarch64); pushes 0 if empty. |
| `VKBD-DEBUG` | `( -- isr_count )` | Pushes the virtio-input interrupt counter (riscv64/aarch64 diagnostic). |
| `KEY-EVENT` | `( -- keycode pressed -1 \| 0 )` | Cross-architecture converged key event: pops one keycode/pressed pair in the Linux input-keycode namespace, translated from whichever backend (i8042 or virtio-input) is live. |
| `ALT+TAB` | `( -- )` | Programmatic equivalent of the physical Alt+Tab interception — toggles the framebuffer graphics/text overlay state. |
## Console Fabric — TrueType Text (`ttf_words.c`)
Kernel-only (`__STARKERNEL__`); no-op on hosted builds. FABRIC-0.md item 4.3.7e.
| Word | Stack Effect | Description |
|---|---|---|
| `TTF-TEXT` | `( c-addr u x y size color -- )` | Renders a UTF-8 string via the TrueType rasterizer at Cartesian position (x,y) (origin bottom-left of the framebuffer) with the given point size and color. |
## Console Fabric — Scrollback (`scroll_words.c`)
Kernel-only (`__STARKERNEL__`); no-op on hosted builds. FABRIC-0.md item 4.4q (boot-mode
scrollback added by 4.4ac).
| Word | Stack Effect | Description |
|---|---|---|
| `SCROLL-BACK` | `( n -- )` | Moves the REPL framebuffer scrollback view back `n` lines. |
| `SCROLL-FWD` | `( n -- )` | Moves the REPL framebuffer scrollback view forward `n` lines, toward live. |
## Kernel: Capsule Birth & Tripod Fleet (`mama_forth_words.c`)
Kernel-only (`__STARKERNEL__`). Implements the MAMA vocabulary — capsule birth protocol and
Tripod fleet control (`src/starkernel/capsule/mama_forth_words.c`). Two registration
functions exist: `register_mama_forth_words()` gives Hera (Mama) her full word set,
registered identically into both the FORTH and MAMA vocabularies; `register_child_vm_words()`
gives child VMs (Hermes/Artemis/etc.) a smaller subset, since they aren't bootstrapped
through the same path.
**Hera's word set:**
| Word | Stack Effect | Description |
|---|---|---|
| `BYE` | `( -- )` | Hera-only override: reaps all child VMs, then cold-restarts the machine. (Children instead get the standard `system_word_bye` from `system_words.c`, which just halts back to the parent's REPL.) |
| `CONNECT-HERMES` | `( -- )` | Enters Hermes's REPL, birthing her first if not already live/stopped (idempotent). |
| `CONNECT-ARTEMIS` | `( -- )` | Enters Artemis's REPL, birthing her first if not already live/stopped (idempotent). |
| `BIRTH` | `( c-addr u -- )` | Births a named child VM from its capsule (name → `name:init.4th`). Idempotent if already live; refuses to re-birth Hera. |
| `KILL` | `( c-addr u -- )` | Destroys a named VM unconditionally, fanning its heat out to survivors first. Hera cannot be killed. Idempotent. |
| `START` | `( c-addr u -- )` | Enters a named VM's REPL loop synchronously; caller blocks until the target halts via STOP/BYE. Cannot start a LIVE/DEAD/STILLBORN VM. |
| `STOP` | `( -- )` | Self-stop: sets `vm->halted` so the current VM's REPL loop exits on its next iteration. Registered in every VM including children. |
| `USE` | `( c-addr u -- )` | Redirects system-wide REPL input to a named VM without touching the C call stack; `USE Hera` resets to default dispatch. |
| `EXEC` | `( c-addr u -- )` | Executes a named capsule in the current VM — same path as Mama's own init auto-run. |
| `CAPSULE-COUNT` | `( -- n )` | Pushes the number of capsules in the capsule directory. |
| `CAPSULE@` | `( idx -- desc )` | Pushes the capsule descriptor address at index `idx` (0 if out of bounds). |
| `CAPSULE-HASH@` | `( desc -- hash )` | Pushes the content hash field from a capsule descriptor. |
| `CAPSULE-FLAGS@` | `( desc -- flags )` | Pushes the flags field from a capsule descriptor. |
| `CAPSULE-LEN@` | `( desc -- len )` | Pushes the payload length field from a capsule descriptor. |
| `CAPSULE-BIRTH` | `( capsule-id -- vm-id-hi vm-id-lo )` | Births a baby VM from a production (p) capsule by index; returns the new VM's 128-bit ID as a double, or all-ones on failure. |
| `CAPSULE-RUN` | `( capsule-id -- )` | Runs an experiment (e) capsule on Mama by index. |
| `MAMA-VM-ID` | `( -- 0 0 )` | Pushes Mama's (Hera's) own VM ID as a double — always zero. |
| `VM-COUNT` | `( -- n )` | Pushes the number of registered VMs. |
| `VM-CONSERVED?` | `( -- flag )` | FORTH boolean: true if the fleet heat sum is within epsilon of `Q.1` (conservation check). |
| `VM-PHYSICS-STATUS` | `( -- )` | Prints the fleet compudynamics diagnostic report. |
| `VM-STEP` | `( c-addr u -- )` | Gives one REPL turn (prompt, read one line, execute, return) to a named VM — the Compudynamics context-switch primitive. |
| `VM-EXEC` | `( cmd-caddr cmd-u vm-name-caddr vm-name-u -- )` | Injects and immediately executes a command string in a named VM, no readline/blocking. |
| `VM-CALL` | `( cmd-caddr cmd-u vm-name-caddr vm-name-u -- n )` | Like `VM-EXEC` but pops one cell from the target's stack afterward and pushes it onto the caller's stack (cross-VM query primitive). |
| `CAPSULE-TEST` | `( -- )` | Prints a diagnostic message confirming the capsule system is active. |
**Additional words in `register_child_vm_words()` only (not in Hera's own dictionary — this
keeps Hera's `dict_hash` off item 4.1's baseline):**
| Word | Stack Effect | Description |
|---|---|---|
| `STADIUM-ADMIT` | `( identity heat behaviour -- cell \| -1 )` | Admits a mass-1 patron into the calling VM's own Stadium quota; -1 if behaviour tag is invalid or admission is refused. |
| `STADIUM-EVICT` | `( cell -- flag )` | Reaps the patron header at `cell`; true (-1) on success, false (0) if refused (out of range, not resident, pinned, or contains-gated). |
| `STADIUM-RES@` | `( -- heat )` | Read-only peek at the calling VM's own reservoir balance (Q48.16). |
| `STADIUM-RES-PULL` | `( qty -- heat )` | Pulls up to `qty` (Q48.16) from the calling VM's own reservoir; returns the amount actually pulled (never more, never negative). |
| `STADIUM-RES-PUSH` | `( heat -- )` | Credits heat (Q48.16) back into the calling VM's own reservoir. |
| `STADIUM-HEAT@` | `( cell -- heat )` | Reads a resident cell's own heat; 0 if not resident or not owned by the calling VM. |
| `STADIUM-HEAT!` | `( new-heat cell -- )` | Writes a resident cell's own heat, atomically reconciling the reservoir delta; silently refused if not owned by the calling VM or reservoir can't cover an increase. |
| `STADIUM-WORD-HEAT` | `( -- heat )` | Sum of heat held by the calling VM's own word-execution residents (item 4.1's cells) — closes the term application-level conservation checks (e.g. Hermes's `HERMES-K`) need. |
## Hosted-Build-Only Lifecycle Stubs (`lifecycle_words_hosted.c`)
Compiled only when `__STARKERNEL__` is **not** defined. In the hosted build there is no
capsule blob or kernel VM registry, so these five just consume their arguments and log
intent — real behavior lives in `mama_forth_words.c` (`BIRTH`/`USE`) plus separate
kernel-side `KILL`/`PAUSE`/`RESUME` words for the kernel build.
| Word | Stack Effect | Description |
|---|---|---|
| `BIRTH` | `( c-addr u -- )` | Hosted stub: logs "BIRTH name (hosted)"; no real VM lifecycle in the hosted build. |
| `KILL` | `( c-addr u -- )` | Hosted stub: logs "KILL name (hosted)". |
| `PAUSE` | `( c-addr u -- )` | Hosted stub: logs "PAUSE name (hosted)". |
| `RESUME` | `( c-addr u -- )` | Hosted stub: logs "RESUME name (hosted)". |
| `USE` | `( c-addr u -- )` | Hosted stub: logs "USE name (hosted)". |
---
## Keeping this current
This document was built by reading every `register_word()` (and the one `vm_create_word()`)
call site directly, plus the doc comment above each implementing function. When words are
added, removed, or re-registered:
1. Update the relevant category table above — keep registration order within a file, not
alphabetical.
2. If a new file registers a name that collides with an existing one, add it to the collision
note at the top of this document and mark both entries (`*(shadowed)*` / live) at point of
use, the same way `[`/`]`/`STATE` and `MOD`/`/MOD`/`*/`/`*/MOD` are handled above —
determine the winner from `register_forth79_words()`'s call order in `src/word_registry.c`
(later registration wins; search is newest-first via `vm->latest`/`->link`).
2. If a new `word_source/*.c` file is added, give it its own `##` section, placed near
related categories.
3. Word counts aren't tracked as a running total in this document on purpose — they drift
too easily and the categories are the useful unit, not a single number. If a total is
needed, `grep -rc 'register_word(' src/word_source/*.c src/starkernel/capsule/mama_forth_words.c`
plus a check for `vm_create_word(vm, "` (the one alternate registration path found so
far) gets close, but always verify against this document rather than trusting either
grep alone — see the `physics_pipelining_diagnostic_words.c` case above for why.
-571
View File
@@ -1,571 +0,0 @@
# M7.1: Init Capsule Architecture
**Status:** Shipped and live, not just designed — this document predates the actual
capsule/Tripod/Stadium work; see `FABRIC-0.md`/`FABRIC-1.md`/`FABRIC-2.md` for real status
(per `.claude/CLAUDE.md`'s standing redirect)
**Branch:** `master` (post-split; the old `lithosananke` branch no longer exists in this repo)
**Prerequisite:** M7 (VM Parity Validation)
## The Immutable Law
> **Exactly ONE production INIT `(p)` defines a baby VM.**
>
> No INIT is shared implicitly.
> No base INIT exists.
> Any similarity between VMs is the result of deliberate reuse by Mama.
This rule is non-negotiable. Everything in this document exists to enforce it.
---
## 1. DOMAIN vs PERSONALITY
### 1.1 DOMAIN (Mama-only Concept)
**DOMAIN** is a *construction and selection space* that exists **only in Mama FORTH**.
DOMAIN is used to:
- Classify *kinds* of VMs (e.g., HTTP, storage, control-plane)
- Compose and curate block sets
- Run experiments `(e)`
- Decide which block set is promoted to production `(p)`
DOMAIN:
- Is **never** executed by babies
- Is **never** visible to babies
- Is **not** part of VM truth
- Leaves **no** runtime trace in the baby
DOMAIN answers: *"What kind of thing is Mama trying to create?"*
### 1.2 PERSONALITY (Baby-only Reality)
**PERSONALITY** is the *result* of Mama's work.
A baby VM receives:
- **Exactly one `(p)` INIT**
- Executed exactly once at birth
- Which defines its entire worldview
PERSONALITY:
- Defines role, capabilities, invariants, vocabulary
- Is immutable for the lifetime of the VM
- Is the only truth the VM knows
PERSONALITY answers: *"What am I?"*
**The baby has no knowledge of DOMAIN.**
---
## 2. What is an Init Capsule?
An INIT capsule is **not**:
- A shared base
- A layered inheritance mechanism
- A runtime-selectable script
- A file or "boot code"
An INIT capsule **is**:
- An **ordered, immutable block sequence**
- A **truth-bearing unit**
- Executed **exactly once** at VM birth
- The **sole source of identity** for that VM
- **Referentially addressable** — by content hash, not name
- **Observationally enumerable** — Mama can list all capsules
Think: ROM image + provenance, not "config."
---
## 3. Architecture Decision
### Option 1: One Big Memory Blob (Indexed)
Single contiguous arena with descriptor table + payload region.
### Option 2: Individual Named Blobs
Separate addressable entities with symbolic names.
### Decision: Hybrid
**Physically:** Option 1 — single arena, indexed by ID + offset
**Logically:** Option 2 — capsules declared symbolically at build time
At runtime: **symbols do not exist.** Only facts (hashes, offsets, lengths).
> Names lie. Hashes don't.
---
## 4. Data Structures
### 3.1 CapsuleDesc (64 bytes, cache-line aligned)
```c
typedef struct {
uint64_t magic; /* 0x00: 'CAPS' | ver | hashAlg | reserved */
uint64_t capsule_id; /* 0x08: == content_hash (content-addressed) */
uint64_t content_hash; /* 0x10: hash of payload bytes */
uint64_t offset; /* 0x18: byte offset into payload arena */
uint64_t length; /* 0x20: payload length in bytes */
uint32_t flags; /* 0x28: state + policy bits */
uint32_t owner_vm; /* 0x2C: 0 = mama, else child VM ID */
uint64_t birth_count; /* 0x30: how many VMs born from this */
uint64_t created_ns; /* 0x38: monotonic timestamp at registration */
} CapsuleDesc; /* 0x40 = 64 bytes */
```
### 3.2 Magic Field Packing
```
bits 0..31 : 'CAPS' (0x53504143 little-endian)
bits 32..39 : version (0 for v0)
bits 40..47 : hashAlg (enum CapsuleHashAlg)
bits 48..63 : reserved (zero)
```
### 3.3 CapsuleDirHeader
```c
typedef struct {
uint64_t magic; /* 'CAPD' | ver | reserved */
uint64_t arena_base; /* phys or virt base of payload arena */
uint64_t arena_size; /* bytes */
uint32_t desc_count; /* current number of descriptors */
uint32_t desc_capacity; /* max (fixed at compile time for Phase A) */
uint64_t dir_hash; /* hash of descriptor table (for parity) */
} CapsuleDirHeader;
```
### 3.4 CapsuleRunRecord (for DoE logging)
```c
typedef struct {
uint64_t run_id; /* sequential run identifier */
uint32_t vm_id; /* which VM executed this */
uint32_t reserved;
uint64_t capsule_id; /* which capsule was run */
uint64_t capsule_hash; /* hash at time of execution */
uint64_t pre_dict_hash; /* dictionary state before run */
uint64_t post_dict_hash; /* dictionary state after run */
uint64_t started_ns; /* monotonic start time */
uint64_t ended_ns; /* monotonic end time */
uint32_t result_code; /* execution result */
uint32_t flags; /* run flags (mode, etc.) */
} CapsuleRunRecord;
```
---
## 5. Content Addressing
**Rule:** `capsule_id` MUST be derived from `content_hash`.
For v0: `capsule_id == content_hash` (identity)
This ensures:
- No sequential ID collisions
- No "what's the salt" questions
- Facts only, no oracles
---
## 6. Hash Algorithm
**v0 Choice:** xxHash64
- Deterministic across hosted + kernel
- Easy to implement freestanding
- Fast
- Good enough collision resistance for practice
**Future-proofing:** The `hashAlg` field in magic allows upgrade to SHA-256 or BLAKE3 without breaking history.
```c
typedef enum {
CAPSULE_HASH_XXHASH64 = 0,
CAPSULE_HASH_SHA256 = 1,
CAPSULE_HASH_BLAKE3 = 2,
} CapsuleHashAlg;
```
---
## 7. Flags
### 6.1 State Flags
```c
#define CAPSULE_FLAG_ACTIVE 0x00000001 /* eligible for use */
#define CAPSULE_FLAG_REVOKED 0x00000002 /* birth-blocked forever */
#define CAPSULE_FLAG_DEPRECATED 0x00000004 /* eligible but discouraged */
#define CAPSULE_FLAG_PINNED 0x00000008 /* immune to GC */
```
### 6.2 Mode Flags (Production vs Experiment)
```c
#define CAPSULE_FLAG_PRODUCTION 0x00000010 /* (p) truth-bearing */
#define CAPSULE_FLAG_EXPERIMENT 0x00000020 /* (e) workload only */
```
**Invariant:** Exactly one of (p) or (e) must be set.
```c
#define CAPSULE_MODE_VALID(f) \
((((f) & CAPSULE_FLAG_PRODUCTION) != 0) ^ \
(((f) & CAPSULE_FLAG_EXPERIMENT) != 0))
```
If both or neither are set: **invalid capsule.**
### 6.3 Revocation Semantics
A revoked capsule:
- Remains enumerable (for audit)
- Remains hashable (for provenance)
- Is birth-blocked (mama refuses to bind it)
**REVOKED always overrides ACTIVE.**
---
## 8. Eligibility Rules
### 7.1 Birth Eligibility
A VM may be born **only** from a (p) capsule:
```c
#define BIRTH_ELIGIBLE(flags) \
(((flags) & CAPSULE_FLAG_PRODUCTION) && \
((flags) & CAPSULE_FLAG_ACTIVE) && \
!((flags) & CAPSULE_FLAG_REVOKED))
```
If mama attempts to bind an (e) capsule at birth: **hard refusal.**
No warnings. No fallback. No exceptions.
### 7.2 DoE/Runtime Eligibility
**(e) capsules exist ONLY in Mama's domain.** Baby VMs never see them, never execute them, never know they exist.
Mama may execute:
- **(e) capsules** freely on herself (subject to policy)
- **(p) capsules** only as birth capsules for babies (never as workloads)
This prevents the deadly mistake:
> "Let's just rerun a production init as an experiment."
No. That turns truth into a variable.
### 7.3 Twins, Triplets, and Variants
**Twins/Triplets/Variants are not special constructs.** They are simply:
- Multiple VMs born from the **same (p) capsule** (twins)
- VMs born from **similar (p) capsules** (variants)
Any similarity between VMs is the result of:
1. Mama deliberately choosing the same (p) capsule for birth
2. Mama creating similar (p) capsules from shared block sets
There is no implicit inheritance. There is no base class. If two VMs share behavior, it's because Mama *made* them share it — not because of any hidden mechanism.
---
## 9. Parity Integration
Birth becomes a parity checkpoint:
```
PARITY:BIRTH vm_id=42 capsule_id=0xA13F mode=p capsule_hash=0x... dict_hash=0x...
```
DoE runs are logged separately:
```
PARITY:RUN vm_id=42 run_id=17 capsule_id=0xBEEF mode=e pre_dict=0x... post_dict=0x...
```
### 8.1 Truth Chain
For any VM, you can prove:
1. Which capsule created it (birth_capsule_id)
2. What the capsule contained (content_hash)
3. What dictionary state resulted (dict_hash)
4. What workloads were run after birth (CapsuleRunRecord log)
This is **provenance**. This is **science**. This is **patent-grade**.
---
## 10. Arena Placement
### Phase A: Static (M7.1 - M8)
- Descriptor table + payload arena live in `.rodata` or dedicated section
- Built by tooling (or compiled-in)
- Zero allocator dependency
- Deterministic
```c
/* In kernel image */
extern const CapsuleDirHeader capsule_directory;
extern const CapsuleDesc capsule_descriptors[];
extern const uint8_t capsule_arena[];
```
### Phase B: Dynamic (Future)
- Mama manages RAM arena
- Runtime capsule registration
- Optional persistence via memory-mapped block store
**Key:** `CapsuleDesc` doesn't care where bytes come from. It only cares about `offset`/`length` within "the arena."
---
## 11. Capacity
### Phase A: Fixed Array
```c
#define MAX_CAPSULES 256
```
`CapsuleDirHeader` includes `desc_count` and `desc_capacity`.
Growable arrays can come later without interface changes.
---
## 12. Who Writes Descriptors?
### Phase A (Static)
- **Build tooling** generates descriptors
- Kernel boots with sealed capsule directory
- Mama reads and binds, never writes
### Phase B (Dynamic)
- **Mama** appends new capsules to RAM arena
- Only mama writes descriptors at runtime
- **Children never write descriptors, ever**
---
## 13. VM Birth Protocol
1. Mama selects **one production INIT `(p)`** (by content hash)
2. Mama validates eligibility:
- `CAPSULE_FLAG_PRODUCTION` set
- `CAPSULE_FLAG_ACTIVE` set
- `CAPSULE_FLAG_REVOKED` not set
3. Mama allocates a new VM
4. INIT blocks are **copied** into the VM execution window (RAM blocks 0–2047)
5. INIT blocks are **executed sequentially**
6. Execution window is **cleared**
7. VM begins life with its **PERSONALITY fully imprinted**
8. Mama logs parity checkpoint:
```
PARITY:BIRTH vm_id=N capsule_id=X mode=p capsule_hash=H dict_hash=D
```
9. Mama increments `capsule.birth_count`
**There is:**
- No shared INIT
- No implicit base
- No post-birth INIT execution
- No runtime selection
**The baby VM cannot:**
- Enumerate sibling capsules
- Mutate the arena
- Know its own capsule ID (optional: can query mama)
- See or know about DOMAIN
---
## 14. DoE Execution Protocol
1. Mama selects workload capsule(s)
2. Mama validates eligibility:
- `CAPSULE_FLAG_EXPERIMENT` set
- `CAPSULE_FLAG_ACTIVE` set
- `CAPSULE_FLAG_REVOKED` not set
3. For each workload:
a. Record `pre_dict_hash`
b. Execute capsule on target VM
c. Record `post_dict_hash`
d. Log run record:
```
PARITY:RUN vm_id=N run_id=R capsule_id=X mode=e pre_dict=P post_dict=Q
```
**DoE runs do not change the VM's birth truth.** They are experiments, not identity.
---
## 15. Validation Function
```c
typedef enum {
CAPSULE_VALID = 0,
CAPSULE_ERR_BAD_MAGIC,
CAPSULE_ERR_BAD_VERSION,
CAPSULE_ERR_BAD_HASH_ALG,
CAPSULE_ERR_BOUNDS,
CAPSULE_ERR_MODE_INVALID,
CAPSULE_ERR_REVOKED_ACTIVE,
CAPSULE_ERR_HASH_MISMATCH,
} CapsuleValidateResult;
CapsuleValidateResult capsule_validate(
const CapsuleDesc *desc,
const uint8_t *arena_base,
uint64_t arena_size,
int verify_hash /* if true, recompute and compare */
);
```
Checks:
1. Magic signature and version
2. Hash algorithm is known
3. `offset + length <= arena_size`
4. Exactly one of (p)/(e) is set
5. REVOKED and ACTIVE not both set (REVOKED wins, but warn)
6. Optional: recompute hash and compare to `content_hash`
---
## 16. File Deliverables
| File | Purpose |
|------|---------|
| `include/starkernel/capsule.h` | CapsuleDesc, CapsuleDirHeader, flags, enums |
| `include/starkernel/capsule_run.h` | CapsuleRunRecord for DoE logging |
| `src/starkernel/capsule/capsule_validate.c` | Validation function |
| `src/starkernel/capsule/capsule_birth.c` | Birth protocol implementation |
| `src/starkernel/capsule/capsule_run.c` | DoE run logging |
---
## 17. Summary
| Principle | Enforcement |
|-----------|-------------|
| One truth per VM | Birth only accepts (p) capsules |
| DOMAIN is Mama-only | Construction concept, never visible to babies |
| PERSONALITY is baby-only | Executed INIT result, immutable identity |
| (e) capsules are Mama-only | Never executed by or visible to baby VMs |
| Content-addressed | `capsule_id == content_hash` |
| Names don't exist at runtime | Symbolic resolution at build time only |
| Revocation is permanent | REVOKED overrides ACTIVE, capsule stays enumerable |
| DoE doesn't change truth | Workloads are (e), logged separately |
| Mama sees all | Children see one capsule, read-only |
| No implicit sharing | Twins exist because Mama chose same capsule |
| Provenance is mandatory | PARITY:BIRTH and PARITY:RUN logged |
---
## 18. Next Steps
1. **Implement `capsule.h`** — structs, flags, macros
2. **Implement `capsule_validate()`** — freestanding, no libc
3. **Build tooling** — generate static capsule directory from `.4th` files
4. **Integrate with birth** — extend `sk_vm_bootstrap` to accept capsule pointer
5. **Parity logging** — extend parity subsystem for BIRTH/RUN records
---
## 19. Gap Analysis (Resolved)
### 18.1 Payload Encoding
**Decision:** Raw UTF-8 FORTH source for Phase A.
- Simple, debuggable, portable
- Hash computed over raw bytes
- Tokenization/compilation is premature optimization
- Future phases may add `CAPSULE_FLAG_TOKENIZED` if needed
### 18.2 VM ID Allocation
Mama maintains a VM registry:
```c
typedef struct {
uint32_t vm_id; /* assigned at birth, immutable */
uint64_t birth_capsule_id;
uint64_t birth_timestamp_ns;
uint32_t state; /* EMBRYO, LIVE, DEAD */
uint32_t flags;
} VMRegistryEntry;
```
- VM ID 0 is reserved (mama)
- IDs are sequential (1, 2, 3, ...)
- ID is never reused within a session
### 18.3 Birth Failure Handling
If init execution fails:
1. VM never transitions to LIVE state
2. VM is marked STILLBORN
3. Parity log records failure:
```
PARITY:BIRTH_FAILED vm_id=N capsule_id=X error=E partial_dict_hash=H
```
4. Partial state is **not** observable by other VMs
5. Mama may retry with same or different capsule
### 18.4 Run Log Storage
`CapsuleRunRecord` entries stored in:
**Phase A:** Fixed ring buffer in kernel BSS
```c
#define MAX_RUN_RECORDS 1024
CapsuleRunRecord run_log[MAX_RUN_RECORDS];
uint32_t run_log_head;
```
**Phase B:** Append-only log, optionally persisted to block device
### 18.5 Atomicity of birth_count
**Rule:** Only mama increments `birth_count`.
Since mama is single-threaded (or serialized), no atomic operations needed in Phase A. Phase B (SMP mama) would require `__atomic_fetch_add`.
### 18.6 xxHash64 Implementation
Freestanding xxHash64 required:
```
include/starkernel/xxhash64.h
src/starkernel/hash/xxhash64.c
```
Public domain implementation (~200 lines). Must be:
- No libc dependency
- Deterministic across platforms
- Seedable (seed = 0 for capsule hashing)
---
## 20. Implementation Checklist
- [ ] `include/starkernel/capsule.h` — structs, flags, macros
- [ ] `include/starkernel/capsule_run.h` — CapsuleRunRecord
- [ ] `include/starkernel/xxhash64.h` — hash function
- [ ] `src/starkernel/hash/xxhash64.c` — freestanding xxHash64
- [ ] `src/starkernel/capsule/capsule_validate.c` — validation
- [ ] `src/starkernel/capsule/capsule_birth.c` — birth protocol
- [ ] `src/starkernel/capsule/capsule_run.c` — DoE run logging
- [ ] `tools/mkcapsule.c` — build tool to generate capsule directory
- [ ] Extend parity subsystem for BIRTH/BIRTH_FAILED/RUN records
- [ ] VM registry in mama
---
*This design will still make sense 20 years from now.*
-195
View File
@@ -1,195 +0,0 @@
# LithosAnanke — StarForth Kernel Branch
> **⚠ PARTIALLY STALE (flagged 2026-10-09).** This page predates the repo split and the
> Tripod/kernel reshuffle. There is no `lithosananke` branch in this repo — `master` is the
> sole production line — and the VM fleet is now **Hera / Artemis / Hestia** (the Hermes VM
> was removed; messaging moved into the kernel as kernel-Hermes). M7.1 is **live**, not
> merely designed. For authoritative status see `.claude/CLAUDE.md` and
> `docs/fabric/FABRIC-2.md` / `FABRIC-3.md` / `FABRIC-3.5.md` / `FABRIC-3.6.md`; for the
> milestone plan see `ROADMAP.md` (this file is an orientation page).
**Line:** `master` (sole production line post-split)
**Status:** M7 Complete (VM Parity) · M7.1 Live (capsules, Tripod, Stadium, word-level ACL Phases 1–7)
---
## What is LithosAnanke?
LithosAnanke is the kernel branch of StarForth — a UEFI-bootable microkernel with the StarForth VM as its native execution engine.
**Etymology:** *Lithos* (stone/foundation) + *Ananke* (necessity/inevitability)
The name reflects the project's nature: a necessary foundation for StarshipOS.
---
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ UEFI Firmware │
│ • ExitBootServices → handoff to kernel │
└─────────────────────┬───────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────┐
│ LithosAnanke Boot (M0) │
│ • UEFI loader collects BootInfo │
│ • Memory map, ACPI tables, framebuffer │
└─────────────────────┬───────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────┐
│ LithosAnanke HAL (M1-M6) │
│ • PMM: Physical Memory Manager (bitmap) │
│ • VMM: Virtual Memory Manager (4-level paging) │
│ • IDT: Interrupt Descriptor Table │
│ • APIC: Timer + IRQ routing │
│ • Heap: kmalloc/kfree │
│ • Console: Serial UART │
└─────────────────────┬───────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────┐
│ StarForth VM (M7+) │
│ • 295 FORTH-79 words │
│ • Parity validation (hash=0x684bbf2fa1d96d55) │
│ • Physics-driven adaptive runtime │
│ • Init capsule system (M7.1) │
└─────────────────────────────────────────────────────────────┘
```
---
## Current Milestones
| Milestone | Description | Status |
|-----------|-------------|--------|
| M0 | UEFI Boot + Serial Output | Complete |
| M1 | Physical Memory Manager | Complete |
| M2 | Virtual Memory Manager | Complete |
| M3 | IDT + Interrupts | Complete |
| M4 | APIC Timer | Complete |
| M5 | Timer Calibration | Complete |
| M6 | Kernel Heap | Complete |
| **M7** | **VM Parity Validation** | **Complete** |
| **M7.1** | **Capsules · Tripod · Stadium · ACL** | **Live** (see FABRIC docs) |
| M8 | REPL (keyboard input) | Superseded — see FABRIC-0 §25.5 item 4.4 |
| M9 | Block Storage / Block I/O | Live (virtio-blk + block subsystem) |
---
## Build & Run
```bash
# Build kernel with VM integration (loader + kernel)
make -f kernel/Makefile ARCH=amd64 STARFORTH_ENABLE_VM=1
# Run in QEMU
make -f kernel/Makefile ARCH=amd64 STARFORTH_ENABLE_VM=1 qemu
# Other architectures
make -f kernel/Makefile ARCH=aarch64 qemu
make -f kernel/Makefile ARCH=riscv64 qemu
```
**Output artifacts:**
- `build/amd64/kernel/starkernel_loader.efi` — UEFI loader
- `build/amd64/kernel/starkernel_kernel.elf` — Kernel binary
---
## Documentation
### Core Design
| Document | Description |
|----------|-------------|
| [M7.1.md](M7.1.md) | Init Capsule Architecture |
| [ROADMAP.md](ROADMAP.md) | Milestone roadmap |
| [amd64-isr-fix/README.md](amd64-isr-fix/README.md) | APIC timer ISR fix + kernel 3-arch acceptance test |
| [hosted-acceptance-test/README.md](hosted-acceptance-test/README.md) | Hosted VM 3-arch acceptance test (amd64 native, aarch64/riscv64 under QEMU user-mode) |
### HAL (Hardware Abstraction Layer)
| Document | Description |
|----------|-------------|
| [hal/overview.md](hal/overview.md) | HAL architecture and principles |
| [hal/interfaces.md](hal/interfaces.md) | HAL interface specifications |
| [hal/lithosananke-integration.md](hal/lithosananke-integration.md) | Kernel-specific HAL implementation |
| [hal/platform-implementations.md](hal/platform-implementations.md) | Platform implementation guide |
| [hal/migration-plan.md](hal/migration-plan.md) | Migration from hosted to kernel |
---
## Key Concepts
### One Truth Per VM
Every VM has exactly one birth capsule — its identity and provenance.
```
PARITY:BIRTH vm_id=42 capsule_id=0xA13F mode=p capsule_hash=0x... dict_hash=0x...
```
### DOMAIN vs PERSONALITY
**DOMAIN** is a construction concept that exists **only in Mama FORTH**. It's how Mama classifies, composes, and selects capsules. DOMAIN is never visible to baby VMs.
**PERSONALITY** is the result of executing a production `(p)` INIT capsule. It's what the baby VM *is* — its vocabulary, invariants, capabilities. PERSONALITY is immutable and defines the VM's entire worldview.
### Production (p) vs Experiment (e)
- **(p) capsules** are truth-bearing — they can birth VMs
- **(e) capsules** are Mama-only workloads — experiments that never touch babies
A baby VM never sees, executes, or knows about (e) capsules.
### Content-Addressed Capsules
Capsules are identified by content hash, not names. Names lie. Hashes don't.
### Mama FORTH
The root VM that holds all truths, manages child VMs, and enforces the birth protocol. Mama:
- Maintains the capsule directory
- Selects (p) capsules for birth
- Executes (e) capsules for experimentation
- Logs all births and runs for provenance
---
## Directory Structure
```
src/starkernel/
├── boot/ # UEFI loader, ELF loader
├── hal/ # Console, host services
├── memory/ # PMM, VMM, kmalloc
├── arch/amd64/ # x86_64 specific (APIC, IDT, timer)
├── vm/ # VM integration
│ ├── arena.c # VM memory arena
│ ├── parity.c # Parity validation
│ ├── bootstrap/ # sk_vm_bootstrap
│ └── host/ # Host services shim
└── kernel_main.c # Entry point
```
---
## Contributing
The contributing rules are:
1. Maintain parity validation (M7 baseline)
2. Pass the three-arch QEMU boot test (`ARCH={amd64,aarch64,riscv64}`)
3. Not break the hosted VM build (plain `make` at the repo root)
---
## References
- [StarForth README](../../README.md) — Main project
- [FORTH-79 Standard](http://www.forth.org/forth-79.html)
- [OSDev Wiki](https://wiki.osdev.org/)
- [UEFI Specification](https://uefi.org/specifications)
-587
View File
@@ -1,587 +0,0 @@
# LithosAnanke Roadmap
**Branch:** `master` (post-split: this repo's `master` is the sole LithosAnanke production
line; the old `lithosananke` branch belonged to the pre-split combined monorepo and no
longer exists here — see `.claude/CLAUDE.md`'s "On the branch topology" note)
**Current:** M7.1 in progress — see `FABRIC-0.md`/`FABRIC-1.md`/`FABRIC-2.md` for real status
---
## Release Versioning Policy (ratified 2026-09-19, FABRIC-3.5.md §XXX — supersedes the
2026-08-28 policy below)
This is the standing release-versioning policy for LithosAnanke. It is authoritative; the
FABRIC documents track implementation against it, and any permanent change to the policy
belongs here.
**Superseded 2026-09-19.** The original 2026-08-28 policy (`X.0.0` = QEMU release, `X.5.0` =
hardware bare-metal release, **even** major = LTS) used the minor field to carry a milestone
meaning and had the LTS parity backwards relative to the ratified policy below. It is kept
struck through immediately after this note for historical traceability, not as current rule.
- **Major: ODD = LTS line** (non-breaking fixes only — backports, never redesigns). **EVEN =
working line** (breaking changes land here). The line you are on tells you whether breakage
is possible at all — not the major number's parity signaling "supported vs. not" the way the
retired policy read it.
- **Minor** — release within that line. No longer carries a milestone meaning (`X.5.0` no
longer means anything special) — milestones are roadmap entries now, not arithmetic.
- **Patch** — working builds within a release.
- **`LITHOS_VERSION` (kernel) and `VERSION` (embedded StarForth engine) are independent and do
not auto-sync** — do not infer a relationship if they ever coincide.
- **Engine `VERSION` bumps at tag time by what actually changed:** major = any
FORTH-79-visible word semantics change; minor = words added/removed/relocated; patch =
build-only, no dictionary-visible change.
```
v1.0.x — serial-only production (released, historical encoding)
v1.5.x — framebuffer VT100 console milestone (released, historical encoding)
v2.0.0 — QEMU release: three-arch QEMU story complete (released)
v2.0.1 — SER5 hardware-track line (historical; superseded by this policy)
v2.1.0 — Tripod/kernel reshuffle: Hermes into the kernel, Tripod = Hera/Artemis/Hestia
v2.2.0 — amd64 bare-metal bring-up (Beelink SER5)
v2.x — further working releases; riscv64 / aarch64 board bring-up
v3.0.0 — FIRST LTS, when and only when all 5 criteria below are met
```
**LTS criteria (a tag may take an odd major only when all five hold):** real-hardware boot
demonstrated on at least the amd64 reference board, logs committed as audit artifacts; no
known-reproducible silent-failure defect open; ACL Phase 8 closed (or explicitly scoped out in
writing); the `proof/` boundary restated and not contracted relative to the prior LTS; a soak —
architecture unchanged for at least one full release cycle. The first tag meeting all five
takes the odd major — no judgement call.
See `FABRIC-3.5.md` §XXX for the full ratified policy text and reasoning, and `FABRIC-2.md` §G
for the release-gate punch lists this ladder replaced the arithmetic reading of.
<details>
<summary>Historical: the retired 2026-08-28 policy (kept for traceability only)</summary>
- ~~`X.0.0` — QEMU release. A whole-number minor in the `X.0` position is the QEMU-only
release: the point where the three-architecture (amd64/aarch64/riscv64) story under QEMU
is complete and defensible on its own terms.~~
- ~~`X.5.0` — Hardware bare-metal release. Real-hardware-only work gates `X.5.0`, not
`X.0.0`.~~
- ~~Even major numbers are LTS.~~ **Wrong — the ratified policy is odd = LTS.**
</details>
**Board-by-board hardware rollout, decided 2026-08-29 (extends the above as boards come
online; corrected 2026-09-22 for the ratified §XXX policy — these are working-line minor
releases, not LTS cuts, since v2.x is the even/working line).** 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 minor cut on the v2.x
working line (v2.1.0, the Tripod/kernel reshuffle, landed first — see the ladder above):
- **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
```
M0 UEFI Boot ████████████████████ COMPLETE
M1 PMM ████████████████████ COMPLETE
M2 VMM ████████████████████ COMPLETE
M3 IDT ████████████████████ COMPLETE
M4 APIC ████████████████████ COMPLETE
M5 Timer ████████████████████ COMPLETE
M6 Heap ████████████████████ COMPLETE
M7 VM Parity ████████████████████ COMPLETE
M7.1 Capsules ████████████████░░░░ LIVE — see FABRIC-0.md/FABRIC-1.md/FABRIC-2.md
M8 REPL ░░░░░░░░░░░░░░░░░░░░ OBSOLETE — see FABRIC-0.md §25.5 item 4.4
M9 Block I/O ░░░░░░░░░░░░░░░░░░░░ PLANNED
M10 Networking ░░░░░░░░░░░░░░░░░░░░ FUTURE
```
---
## Phase 1: Boot Foundation (M0-M6)
### M0: UEFI Boot + Serial Output
**Goal:** Kernel prints "LithosAnanke booting..." to serial
**Deliverables:**
- [x] UEFI loader (`uefi_loader.c`)
- [x] BootInfo handoff (memory map, ACPI, framebuffer)
- [x] ExitBootServices
- [x] Serial console output
**Validation:**
```
StarKernel UEFI Loader
Loading...
RAW SERIAL UP
```
---
### M1: Physical Memory Manager
**Goal:** Track and allocate physical page frames
**Deliverables:**
- [x] Bitmap allocator
- [x] Parse UEFI memory map
- [x] `pmm_alloc_pages()` / `pmm_free_pages()`
- [x] Statistics reporting
**Validation:**
```
PMM initialized.
PMM statistics:
Total pages: 249450
Free pages : 247921
```
---
### M2: Virtual Memory Manager
**Goal:** 4-level paging, identity + higher-half mapping
**Deliverables:**
- [x] PML4 → PDPT → PD → PT setup
- [x] CR3 switch
- [x] `vmm_map()` / `vmm_unmap()`
- [x] Self-test
**Validation:**
```
VMM initialized (mapped RAM, CR3 switched)
VMM self-test: mapped OK at 0xffff800000000000
```
---
### M3: Interrupt Descriptor Table
**Goal:** CPU exceptions and IRQ handling
**Deliverables:**
- [x] 256-entry IDT
- [x] ISR stubs (asm)
- [x] Exception handlers (div-by-zero, page fault, etc.)
- [x] IRQ routing framework
**Validation:**
```
IDT installed.
```
---
### M4: APIC Timer
**Goal:** Local APIC initialization and timer IRQs
**Deliverables:**
- [x] Local APIC enable
- [x] APIC timer configuration
- [x] Spurious interrupt vector
**Validation:**
```
APIC: init...
APIC enabled (SIVR=0xFF).
APIC: init done
```
---
### M5: Timer Calibration
**Goal:** Accurate time measurement
**Deliverables:**
- [x] TSC frequency detection
- [x] HPET calibration (when available)
- [x] PM Timer fallback
- [x] Relative vs. absolute trust levels
**Validation:**
```
Timer: init...
Timer: trust=1 (0=NONE,1=REL,2=ABS), TSC=0 Hz
Timer: init done
```
---
### M6: Kernel Heap
**Goal:** `kmalloc()` / `kfree()` working
**Deliverables:**
- [x] Heap initialization
- [x] Allocation tracking
- [x] Statistics
**Validation:**
```
Kernel heap initialized.
Heap statistics:
Total bytes: 16777176
Free bytes: 16777176
```
---
## Phase 2: VM Integration (M7)
### M7: VM Parity Validation
**Goal:** StarForth VM boots with reproducible dictionary hash
**Deliverables:**
- [x] VMHostServices abstraction
- [x] VM arena allocation (5 MB)
- [x] FORTH-79 word registration (295 words)
- [x] Parity checkpoint logging
- [x] Heartbeat thread start
**Validation:**
```
VM: bootstrap parity...
[HAL][host] VMHostServices table registered
VM arena allocated: 0xffff900000000000 (5 MB)
Registering FORTH-79 Standard word set...
PARITY:M7.1a word_count=295 here=0x30 latest_id=294 hash=0x684bbf2fa1d96d55
PARITY:OK
VM: parity bootstrap complete
Starting heartbeat...
APIC Timer: started
```
**Commit:** `6f350bc` — M7: StarForth VM integration with parity validation
---
## Phase 3: Capsule Architecture (M7.1)
### M7.1: Init Capsule System
**Goal:** Content-addressed, immutable init capsules for VM birth
**Status:** Design Complete (see [M7.1.md](M7.1.md)) — this section predates the actual
capsule/Tripod/Stadium work and is not current. **See `FABRIC-0.md`/`FABRIC-1.md` for real
status** (per `.claude/CLAUDE.md`'s standing redirect) — Tripod (Hera/Hermes/Artemis),
word-level ACL Phases 1–7, and the Stadium physics-economy migration are all live, not just
designed. Most recently: item 4.6 (Artemis's block-heat arena migrated to Stadium
admission-on-allocate) verified passing its 30-rep stress campaign on all three
architectures 2026-08-18, after fixing a quota-grant ordering bug (`capsule_birth.c`) that
had made every admission fail; see `FABRIC-1.md` Section H. The same pass also fixed a
long-standing aarch64-only crash on `BYE` (wrong PSCI conduit — SMC has nothing to answer it
under AAVMF/no-EL3 boot; switched to HVC), see `FABRIC-1.md` Section I.
**Core Concepts:**
| Concept | Description |
|---------|-------------|
| **DOMAIN** | Mama-only construction space — never visible to babies |
| **PERSONALITY** | Baby-only identity — result of executing (p) INIT |
| **(p) Production** | Truth-bearing capsules that birth VMs |
| **(e) Experiment** | Mama-only workloads for DoE |
**Birth Protocol:**
1. Mama selects one production `(p)` capsule by content hash
2. Mama validates eligibility (ACTIVE, PRODUCTION, not REVOKED)
3. Mama allocates new VM
4. INIT blocks copied to execution window (RAM blocks 0–2047)
5. INIT blocks executed sequentially
6. Execution window cleared
7. VM begins life with PERSONALITY imprinted
8. Mama logs `PARITY:BIRTH vm_id=N capsule_id=X mode=p ...`
9. Mama increments `capsule.birth_count`
**Deliverables:**
- [ ] `CapsuleDesc` struct (64 bytes, cache-aligned)
- [ ] `CapsuleDirHeader` struct
- [ ] xxHash64 implementation (freestanding)
- [ ] `capsule_validate()` function
- [ ] Birth protocol (`PARITY:BIRTH` logging)
- [ ] DoE run logging (`CapsuleRunRecord`)
- [ ] `mkcapsule` build tool
**Key Design Decisions:**
- Content-addressed: `capsule_id == content_hash`
- (p) Production vs (e) Experiment modes
- One truth per VM — no shared/implicit base INITs
- Mama holds all truths — (e) capsules never touch babies
- Twins/variants are just VMs with same/similar capsules
---
## Phase 4: Interactive Forth (M8)
> **⚠ OBSOLETE (2026-08-11).** This entire M8 section is superseded by
> `FABRIC-0.md` §25.5 item 4.4 (REPL). The console/REPL design has moved past
> what's scoped here — do not use this section as a source for REPL or
> CANVAS design decisions. Kept in place for history only.
### M8: REPL + Interactive Forth
**Status:** Scoped 2026-08-11 (this section). Substrate (interrupt-driven keyboard
input, glyph rendering) already complete via Stadium's Console fabric work
(FABRIC-0.md §25.5 items 4.3.5–4.3.7f) — see "What's already done" below. This
scoping pass covers what's left: wiring the existing keyboard-event stream into
the REPL's character input path.
**Goal:** Type Forth at the kernel *keyboard* (not just serial), get `ok` prompt.
The REPL already works interactively over serial today (`sk_repl_run()`,
`src/starkernel/repl.c`) — that is not what this milestone adds. What's missing
is a second input source feeding the same loop.
**What's already done, read from the code before scoping this, not assumed:**
- Interrupt-driven keyboard input on all three architectures (amd64 i8042,
aarch64/riscv64 virtio-keyboard-pci + GIC/PLIC), converged behind one shared
interface: `KEY-EVENT ( -- keycode pressed -1 | 0 )` in
`src/word_source/keyboard_words.c`, `keycode` in the Linux input keycode
namespace. FABRIC-0.md items 4.3.5–4.3.5f, verified live on all three
architectures with real keypresses.
- Glyph rendering, both paths: the stroke-drawn font (`TEXT`, capsule-based,
113-glyph v1 repertoire, FABRIC-0.md 4.3.6–4.3.6g) and TrueType (`TTF-TEXT`,
proportional spacing via real `hmtx` advance widths, FABRIC-0.md 4.3.7–4.3.7f).
Neither is required for the REPL text itself, though — see the VT100 finding
below.
- **The REPL's on-screen text already renders independently of both of the
above.** `console_fb_init()` (`src/starkernel/hal/console.c`) calls
`vt100_init()`, which mirrors every `console_putc()`/`console_puts()` call to
the framebuffer via the baked-in `font_8x16.c` raster font. This is *already
live* — the REPL prompt and its output appear on screen today, serial-driven,
no CANVAS/glyph-capsule dependency. This confirms §27.6's mockup: a fixed
VT100 REPL strip (already working) plus a separate large CANVAS region above
it (still unsized — see the last deliverable below).
**What's actually missing, confirmed by reading the code, not assumed:**
`src/starkernel/repl.c`'s `sk_readline()` reads characters via
`console_getc()` (`src/starkernel/hal/console.c`), which is serial-UART-only —
it never looks at `KEY-EVENT`. Separately, the FORTH-79 words `KEY`/`?TERMINAL`
(`src/word_source/io_words.c:84-108`, shared/vendored source) are dead in the
kernel build: `KEY` calls libc `getchar()`, which the kernel shim
(`src/starkernel/vm/host/shim.c:1206`) hardcodes to return `-1` (EOF);
`?TERMINAL` is a literal stub that always returns false. Neither path has ever
been wired to a real keyboard.
**Deliverables:**
- [ ] Minimal scancode→ASCII layout: a small lookup table (US QWERTY,
unshifted/shifted rows only — no dead keys, no locale switching) driven
by `KEY-EVENT`'s Linux-namespace keycodes, plus shift-state tracking
(`KEY_LEFTSHIFT`/`KEY_RIGHTSHIFT` press/release) since `KEY-EVENT` itself
carries no modifier state today (confirmed by reading
`keyboard_words.c` — deferred at 4.3.5f, not implemented anywhere).
- [ ] Merge keyboard into `console_getc()`'s poll: extend it (or add a second
function `sk_readline()` polls alongside it) to also drain `KEY-EVENT`
and translate presses to ASCII via the layout table above. **Serial stays
a co-equal input source, not replaced** — the entire acceptance/DoE
harness injects FORTH commands over the serial socket
(`socat ... UNIX-CONNECT:$SERIAL_SOCK`) and greps the serial log for
`ok>`; breaking that path breaks `make qemu`, `DOE_INJECT`, and every
screendump verification technique used throughout 4.3.x. Both sources
feed the same byte stream into the existing `sk_readline()` — no new
line-editing code needed, backspace/echo already work
(`sk_readline()`, `repl.c:83-141`).
- [ ] Wire `KEY`/`?TERMINAL` (`io_words.c`) to the same merged source under
`#ifdef __STARKERNEL__`, for FORTH-79 compliance — any capsule calling
these directly (not just the REPL) should get real input too, not a
permanently-EOF stub. Small addition once the merge above exists.
- [ ] CANVAS viewport sizing (§27.6's explicit deferral to this milestone): the
large graphics region above the VT100 REPL strip needs an actual
scrollable framebuffer viewport size decided (e.g. 640×480, per the
original mockup) — layout/presentation, not input plumbing; do this
last, after keyboard input works, since it doesn't block interactivity.
**Explicitly out of scope for this milestone (minimal bar, matches "Line
editing (backspace, minimal)"):** extended (`0xE0`-prefixed) i8042 scancodes,
virtio-input autorepeat (`value=2`) as actual character repeat, arrow-key
cursor movement/history, non-US keyboard layouts. Deferred to a later item if
ever needed — not blocking "type Forth at the kernel, get `ok`."
**Validation:**
```
LithosAnanke v0.3.0
ok 1 2 + .
3 ok
```
(via a real keypress on all three architectures, same live-keypress
verification standard FABRIC-0.md 4.3.5f already established — not a
serial-injected synthetic test, though serial injection must still work
identically afterward.)
---
## Phase 5: Persistence (M9)
### M9: Block Storage
**Goal:** Read/write blocks to disk
**Deliverables:**
- [x] Block device driver — implemented via `virtio_blk.c` (virtio block device), not the
AHCI/SATA driver originally scoped here; see `.claude/CLAUDE.md`'s M9 note.
- [x] Block device abstraction — `block_subsystem.c`'s unified LBN address space, backend-agnostic
across RAM/RAMDRIVE/DISK/USB via the `blkio_dev` vtable.
- [x] `BLOCK` / `BUFFER` / `UPDATE` / `FLUSH` words — live, `src/word_source/block_words.c`.
- [ ] Persistent dictionary — no evidence found of word definitions surviving reboot; not
independently verified as done, left unchecked rather than assumed from the rest of this
list being live.
---
## Future Milestones
### M10: Networking
- VirtIO-net driver
- TCP/IP stack (minimal)
- DHCP client
### M11: Process Model
- Forth tasks
- Scheduling
- IPC
### M12: Self-Hosting
- Compile Forth on LithosAnanke
- Edit/assemble/link cycle
---
## Validation Commands
```bash
# Build
make -f kernel/Makefile ARCH=amd64 STARFORTH_ENABLE_VM=1
# Run QEMU
make -f kernel/Makefile ARCH=amd64 STARFORTH_ENABLE_VM=1 qemu
# Clean
make -f kernel/Makefile ARCH=amd64 clean-kernel
```
---
## Branch Relationship
```
master (post-split: sole LithosAnanke production line — the old
lithosananke/master two-branch topology no longer exists in this repo)
└── M0–M7 complete, M7.1 live (capsules / Tripod / Stadium / ACL)
```
---
## Success Criteria
LithosAnanke is successful when:
1. **M7 Parity** — VM dictionary hash reproducible across boots
2. **M7.1 Capsules** — Birth protocol enforced, provenance logged
3. ~~**M8 REPL** — Interactive Forth at bare metal~~ — superseded, this criterion is now met
via Stadium's Console fabric work (`FABRIC-0.md` §25.5 item 4.4), not a standalone M8; see
the OBSOLETE banner above Phase 4
4. **M9 Persistence** — State survives reboot
---
*The foundation is laid. The necessity is clear.*
-978
View File
@@ -1,978 +0,0 @@
# LithosAnanke System Architecture
**Version**: 0.1 (Draft)
**Date**: 2026-01-27
**Status**: Architectural Design
---
## 1. EXECUTIVE SUMMARY
LithosAnanke is a microkernel-style operating system where all system services run as isolated Virtual Machines (VMs) communicating exclusively through message passing. User identity is tied to physical media (thumbdrives), with cryptographically signed capability grants enforced at VM birth.
**Core Principles:**
- Everything is a VM, everything is a capsule
- All communication through messaging backbone (no exceptions)
- All storage through block I/O VM (no exceptions)
- Physical possession = authentication
- Cryptographic signing = capability enforcement
- One admin key, no recovery, no backdoors
---
## 2. VM HIERARCHY
### 2.1 Overview
```
┌─────────────────────────────────────────────────────────────────┐
│ Mama VM │
│ (births and deaths ONLY) │
└─────────────────────────────────────────────────────────────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────────┐ ┌───────────────┐ ┌───────────────────┐
│ Messaging VM │ │ Block I/O VM │ │ User Session │
│ (all comms) │ │ (all storage)│ │ VMs... │
└───────────────────┘ └───────────────┘ └───────────────────┘
```
### 2.2 Mama VM
**Role:** Root VM, births and deaths other VMs. Nothing else.
**Responsibilities:**
- Execute Mama init capsule at boot
- Birth system VMs (Messaging, Block I/O)
- Birth user session VMs on thumbdrive insertion
- Death user session VMs on thumbdrive removal
- Maintain VM registry
**Does NOT do:**
- Message routing (Messaging VM does this)
- Block I/O (Block I/O VM does this)
- User interaction (User VMs do this)
**Capsule:** `mama-init.4th` with flag `CAPSULE_FLAG_MAMA_INIT`
### 2.3 Messaging Backbone VM
**Role:** All inter-VM communication routes through this VM. No exceptions.
**Responsibilities:**
- Route messages between VMs
- Enforce message-level ACLs
- Queue messages for sleeping/busy VMs
- Handle hardware interrupts (keyboard, USB events)
- Notify Mama of hardware events (thumbdrive insertion)
**Capabilities Required:**
- `CAP_ROUTE_MESSAGES` - Can route between any VMs
- `CAP_HARDWARE_IRQ` - Can receive hardware interrupts
- `CAP_VM_ENUMERATE` - Can query VM registry
**Capsule:** `messaging.4th` with flag `CAPSULE_FLAG_PRODUCTION`
### 2.4 Block I/O VM
**Role:** All storage access routes through this VM. No exceptions.
**Responsibilities:**
- Physical block I/O to all storage devices
- Map user virtual blocks to physical blocks
- Enforce block-level ACLs
- Manage Block Allocation Maps (BAM)
- Handle storage device hotplug
**Capabilities Required:**
- `CAP_BLOCK_PHYSICAL` - Direct hardware access to storage
- `CAP_BLOCK_MAP` - Can create/modify block mappings
- `CAP_ACL_ENFORCE` - Can check ACLs on block operations
**Capsule:** `block-io.4th` with flag `CAPSULE_FLAG_PRODUCTION`
### 2.5 User Session VMs
**Role:** Isolated user environment with REPL.
**Created when:** User inserts authenticated thumbdrive
**Destroyed when:** Thumbdrive removed or user executes BYE
**Each user session gets:**
- Own VM instance
- Own heap (memory arena)
- Own RAM blocks (0-2047)
- Own disk blocks mapped to thumbdrive (2048+)
- Capabilities as granted by signed capability block
**Capsule:** `user-shell.4th` or user-specified custom capsule
---
## 3. BLOCK SUBSYSTEM ARCHITECTURE
### 3.1 User's Logical View (Deterministic)
Every user sees the identical logical layout:
```
┌─────────────────────────────────────────────────────────────┐
│ Blocks 0-2047: RAM Drive │
│ - Fast, volatile working space │
│ - 2 MB total (2048 × 1024 bytes) │
│ - Cleared on session end │
├─────────────────────────────────────────────────────────────┤
│ Blocks 2048+: Physical Storage (Thumbdrive) │
│ - Persistent across sessions │
│ - User's "home" blocks │
│ - Block 2048 typically contains startup script │
└─────────────────────────────────────────────────────────────┘
```
**Guarantees:**
- Blocks 0-2047 are ALWAYS RAM (fast, volatile)
- Blocks 2048+ are ALWAYS physical storage (persistent)
- This layout is identical for every user on every machine
- User FORTH code is portable (same logical addresses everywhere)
### 3.2 Physical Storage Layout (Thumbdrive)
```
┌─────────────────────────────────────────────────────────────┐
│ Devblock 0: Volume Header (4 KiB) │
│ - Magic, version, geometry │
│ - BAM location pointers │
│ - Reserved range definitions │
├─────────────────────────────────────────────────────────────┤
│ Devblocks 1..B: Physical BAM │
│ - 1 bit per 1 KiB block │
│ - Tracks allocated/free status │
├─────────────────────────────────────────────────────────────┤
│ Devblocks B+1..X: System Region │
│ - NOT VISIBLE to user (no logical block maps here) │
│ - Identity sector (fingerprint source data) │
│ - Signed capability grants │
│ - ACL tables (variable size, chained blocks) │
│ - Audit log (grows over time) │
│ - Startup block pointer │
├─────────────────────────────────────────────────────────────┤
│ Devblocks X+1..end: User Block Region │
│ - Mapped to user's logical blocks 2048+ │
│ - User's FORTH code, data, definitions │
│ - Fully under user control (within ACL limits) │
└─────────────────────────────────────────────────────────────┘
```
### 3.3 Virtual BAM
Each user session has a **Virtual BAM** that maps only their accessible blocks:
```
User's Virtual BAM Physical Reality
───────────────── ─────────────────
Block 0 ─────────────────► RAM buffer[0]
Block 1 ─────────────────► RAM buffer[1]
...
Block 2047 ─────────────────► RAM buffer[2047]
Block 2048 ─────────────────► Thumbdrive devblock X+1
Block 2049 ─────────────────► Thumbdrive devblock X+2
...
```
**Key property:** System region devblocks have NO entry in the virtual BAM. From the user's FORTH perspective, they don't exist. No block number maps to them.
### 3.4 Block I/O Flow
User VM requests a block read:
```
User VM Messaging VM Block I/O VM
│ │ │
│ ─── MSG(READ,2048) ───► │ │
│ │ ─── MSG(READ) ─────► │
│ │ │
│ │ ├─ Check ACL
│ │ ├─ Map virtual→physical
│ │ ├─ Physical I/O
│ │ │
│ │ ◄─── MSG(DATA) ───── │
│ ◄─── MSG(DATA) ──────── │ │
│ │ │
```
**No direct hardware access** from user VMs. All I/O is mediated.
### 3.5 System Region Uses
The system region (invisible to user) stores variable-size metadata:
| Data | Size | Notes |
|------|------|-------|
| Identity sector | ~256 bytes | Source data for fingerprint |
| Fingerprint | 32-64 bytes | SHA-256 or SHA-512 hash |
| Capability grants | Variable | Signed by admin |
| ACL tables | Variable | Block-level permissions |
| Group memberships | Variable | For shared access |
| Audit log | Grows | Tracks significant events |
| Startup pointer | 8 bytes | Which block to LOAD at init |
| Custom metadata | Variable | Application-specific |
**Variable-size data** uses block chaining (existing `prev_block`, `next_block` fields in `blk_meta_t`).
---
## 4. USER IDENTITY MODEL
### 4.1 Physical Capability Model
**Identity IS the thumbdrive.** No passwords, no usernames.
```
┌─────────────────────────────────────────────────────────────┐
│ User's Thumbdrive │
├─────────────────────────────────────────────────────────────┤
│ Contains: │
│ - Identity data → SHA fingerprint │
│ - Signed capability grants │
│ - User's blocks (code, data) │
│ - Startup configuration │
├─────────────────────────────────────────────────────────────┤
│ Properties: │
│ - Fully portable (works on any LithosAnanke system) │
│ - Self-contained (no central user database) │
│ - Physical possession = authentication │
└─────────────────────────────────────────────────────────────┘
```
### 4.2 Fingerprint Generation
The SHA fingerprint uniquely identifies the user:
```
Fingerprint = SHA-256(identity_sector_data)
```
**Identity sector contents** (stored in system region):
- UUID (random, generated at user creation)
- Creation timestamp
- Optional: hardware serial numbers, user metadata
**The fingerprint:**
- Is computed by the kernel at thumbdrive insertion
- Is matched against the signed capability grant
- Cannot be forged (would need admin signature)
### 4.3 Session Lifecycle
```
┌─────────────────────────────────────────────────────────────┐
│ 1. THUMBDRIVE INSERTION │
├─────────────────────────────────────────────────────────────┤
│ • Hardware interrupt → Messaging VM │
│ • Messaging VM notifies Mama │
│ • Block I/O VM reads system region │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. IDENTITY VERIFICATION │
├─────────────────────────────────────────────────────────────┤
│ • Compute SHA fingerprint from identity sector │
│ • Read signed capability grant from system region │
│ • Verify signature against fused admin public key │
│ • If invalid: reject, log attempt, stop │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. VM BIRTH │
├─────────────────────────────────────────────────────────────┤
│ • Mama selects base capsule (user-shell or custom) │
│ • Mama births new VM │
│ • VM allocated: heap, RAM blocks (0-2047) │
│ • Disk blocks (2048+) mapped to thumbdrive │
│ • Capabilities granted: capsule ∩ signed_grant │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. INITIALIZATION │
├─────────────────────────────────────────────────────────────┤
│ • Base capsule executes (defines PERSONALITY) │
│ • User's startup block LOADed (e.g., 2048 LOAD) │
│ • Custom words, aliases, preferences applied │
│ • REPL ready for user input │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 5. ACTIVE SESSION │
├─────────────────────────────────────────────────────────────┤
│ • User interacts via REPL │
│ • All I/O through Block I/O VM │
│ • All comms through Messaging VM │
│ • ACLs enforced on every operation │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 6. SESSION END (thumbdrive removal OR BYE) │
├─────────────────────────────────────────────────────────────┤
│ • Hardware interrupt (removal) or explicit BYE │
│ • Messaging VM notifies Mama │
│ • Mama deaths the user VM │
│ • RAM blocks freed │
│ • Heap freed │
│ • Block mappings removed │
│ • Session over, no trace remains │
└─────────────────────────────────────────────────────────────┘
```
### 4.4 User Startup Customization
Users can customize their environment like `.bashrc`:
```forth
\ Block 2048 - User's startup script (example)
\ Load my personal library
2049 LOAD
\ Define aliases
: LS DIR ;
: PWD BLK@ . ;
\ Set preferences
DECIMAL
64 BASE-DEFAULT !
\ Greeting
." Welcome back, Captain!" CR
```
**Constraints:**
- Startup runs AFTER base capsule
- Can define words, load blocks, set variables
- CANNOT exceed capability grants
- CANNOT access blocks outside mapped range
- CANNOT send unauthorized messages
---
## 5. SECURITY MODEL
### 5.1 Root of Trust: Admin Fusing
**One-time, irreversible provisioning:**
```
┌─────────────────────────────────────────────────────────────┐
│ PROVISIONING MODE (First Boot Only) │
├─────────────────────────────────────────────────────────────┤
│ 1. System detects unprovisioned state │
│ 2. Generate Ed25519 keypair │
│ • Private key: 32 bytes │
│ • Public key: 32 bytes │
│ 3. FUSE public key to system │
│ • Written to NVRAM/secure storage │
│ • Cannot be changed after this │
│ 4. Create admin thumbdrive │
│ • Private key stored in system region │
│ • Root capability grant (all permissions) │
│ • Self-signed (bootstrap) │
│ 5. Exit provisioning mode FOREVER │
│ • Provisioning flag set │
│ • Cannot re-enter this mode │
└─────────────────────────────────────────────────────────────┘
```
**After fusing:**
- Public key is immutable (fused to system)
- Private key exists ONLY on admin thumbdrive
- No copies, no backups, no recovery mechanism
- Lose admin thumbdrive = cannot provision new users
### 5.2 Admin Thumbdrive
**The single point of administrative authority:**
```
Admin Thumbdrive (System Region)
├── Admin identity sector
├── Root capability grant (self-signed at provisioning)
├── Private signing key (Ed25519)
└── Admin blocks (tools, scripts)
Admin Capabilities:
├── CAP_SIGN_GRANTS - Can sign capability grants
├── CAP_CREATE_USERS - Can provision new thumbdrives
├── CAP_REVOKE_USERS - Can revoke capabilities
├── CAP_SYSTEM_CONFIG - Can modify system settings
├── CAP_ALL_BLOCKS - Can access any block
└── CAP_* - All capabilities
```
**Admin can:**
- Create new user thumbdrives
- Sign capability grants
- Revoke user capabilities (by not re-signing)
- Access any user's blocks (for recovery, audit)
- Modify system configuration
**Admin CANNOT:**
- Create another admin (no delegation)
- Recover lost admin thumbdrive
- Change the fused public key
### 5.3 Capability Grant Structure
```
┌─────────────────────────────────────────────────────────────┐
│ Capability Grant Block │
├─────────────────────────────────────────────────────────────┤
│ Header: │
│ Magic: "CPGR" (0x43504752) │
│ Version: 1 │
│ Grant ID: unique identifier │
│ Created: timestamp │
│ Expires: timestamp (0 = never) │
├─────────────────────────────────────────────────────────────┤
│ Identity: │
│ Fingerprint: SHA-256 (32 bytes) │
│ Must match computed fingerprint at auth time │
├─────────────────────────────────────────────────────────────┤
│ Capabilities (bitmap + parameters): │
│ CAP_BLOCK_READ - Can read blocks │
│ CAP_BLOCK_WRITE - Can write blocks │
│ CAP_BLOCK_RANGE - Allowed block range (start, end) │
│ CAP_MSG_SEND - Can send messages │
│ CAP_MSG_RECEIVE - Can receive messages │
│ CAP_MSG_TARGETS - Allowed message targets (VM list) │
│ CAP_CAPSULE_LOAD - Can request capsule loading │
│ CAP_CUSTOM_CAPSULE - Can use custom init capsule │
│ CAP_GROUP_MEMBER - Group membership list │
│ CAP_RESOURCE_QUOTA - CPU, memory, block limits │
├─────────────────────────────────────────────────────────────┤
│ Signature: │
│ Ed25519 signature over all above fields │
│ Signed by admin private key │
│ 64 bytes │
└─────────────────────────────────────────────────────────────┘
```
### 5.4 Capability Verification Flow
```
Thumbdrive inserted
│
▼
┌───────────────────────────────────────┐
│ 1. Read identity sector │
│ 2. Compute fingerprint = SHA-256(id) │
└───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ 3. Read capability grant block │
│ 4. Extract grant.fingerprint │
│ 5. Compare: computed == grant.fp? │
│ NO → Reject (identity mismatch) │
└───────────────────────────────────────┘
│ YES
▼
┌───────────────────────────────────────┐
│ 6. Extract grant.signature │
│ 7. Verify: Ed25519_verify( │
│ fused_public_key, │
│ grant_data, │
│ signature) │
│ FAIL → Reject (tampered/forged) │
└───────────────────────────────────────┘
│ PASS
▼
┌───────────────────────────────────────┐
│ 8. Check expiry timestamp │
│ EXPIRED → Reject │
└───────────────────────────────────────┘
│ VALID
▼
┌───────────────────────────────────────┐
│ 9. Grant capabilities to new VM │
│ effective_caps = grant.caps │
└───────────────────────────────────────┘
```
### 5.5 Capability Enforcement Points
**At VM Birth:**
- Capabilities extracted from signed grant
- Stored in VM registry entry
- Cannot be modified after birth
**At Message Send:**
- Messaging VM checks: Does sender have CAP_MSG_SEND?
- Messaging VM checks: Is target in CAP_MSG_TARGETS?
- Reject if not authorized
**At Block Access:**
- Block I/O VM checks: Does requestor have CAP_BLOCK_READ/WRITE?
- Block I/O VM checks: Is block in CAP_BLOCK_RANGE?
- Reject if not authorized
**At Capsule Load:**
- Mama checks: Does requestor have CAP_CAPSULE_LOAD?
- Mama checks: Does requestor have CAP_CUSTOM_CAPSULE (if custom)?
- Reject if not authorized
### 5.6 Effective Capabilities
Final capabilities are the intersection:
```
effective_caps = capsule.declared ∩ grant.allowed ∩ system.policy
```
**capsule.declared:** What the init capsule requests
**grant.allowed:** What the signed grant permits
**system.policy:** System-wide limits (optional)
A user cannot gain capabilities not in their signed grant, even if the capsule requests them.
### 5.7 Revocation
**Capability revocation works by:**
1. Admin creates new grant with reduced capabilities
2. Admin re-signs the grant
3. New grant replaces old on thumbdrive
4. Next session uses new (reduced) capabilities
**Immediate revocation:**
- Admin can maintain a revocation list
- Checked at session start
- Fingerprint on revocation list = reject
**No online revocation checking** - this is a standalone system. Revocation requires physical access to update the thumbdrive or revocation list.
### 5.8 Attack Surface Analysis
**Attacks PREVENTED:**
| Attack | Prevention |
|--------|------------|
| Password guessing | No passwords exist |
| Credential theft | Must steal physical device |
| Privilege escalation | Capabilities signed, immutable |
| Block range escape | Virtual BAM has no mapping |
| Direct hardware access | All I/O through service VMs |
| Message spoofing | Source VM verified by Messaging VM |
| Capability forging | Ed25519 signature required |
| Admin impersonation | Admin private key on single device |
| Recovery mode exploit | No recovery mode exists |
| Backup compromise | No backups exist |
**Attacks NOT PREVENTED:**
| Attack | Notes |
|--------|-------|
| Physical theft of thumbdrive | Physical security is user's responsibility |
| Physical theft of admin drive | Total compromise, must reflash |
| Cold boot attack | RAM contents may be recoverable |
| Evil maid (hardware tampering) | Outside scope, requires secure boot |
| Side channel timing | Not addressed in current design |
### 5.9 Security Boundaries
```
┌─────────────────────────────────────────────────────────────┐
│ KERNEL SPACE │
│ • Mama VM │
│ • VM scheduler │
│ • Fused public key │
│ • Hardware abstraction │
└─────────────────────────────────────────────────────────────┘
│
│ Capsule-defined boundary
▼
┌─────────────────────────────────────────────────────────────┐
│ PRIVILEGED VM SPACE │
│ • Messaging VM (CAP_ROUTE_MESSAGES) │
│ • Block I/O VM (CAP_BLOCK_PHYSICAL) │
└─────────────────────────────────────────────────────────────┘
│
│ Capability-checked boundary
▼
┌─────────────────────────────────────────────────────────────┐
│ USER VM SPACE │
│ • User session VMs │
│ • Constrained by signed capability grants │
│ • Cannot access other user's blocks │
│ • Cannot send unauthorized messages │
└─────────────────────────────────────────────────────────────┘
```
---
## 6. MESSAGING BACKBONE
### 6.1 Message Structure
```
┌─────────────────────────────────────────────────────────────┐
│ Message Header │
├─────────────────────────────────────────────────────────────┤
│ msg_id: uint64 Unique message identifier │
│ source_vm: uint32 Sender VM ID │
│ target_vm: uint32 Recipient VM ID │
│ msg_type: uint16 Message type code │
│ flags: uint16 Delivery flags │
│ payload_len: uint32 Payload size in bytes │
│ timestamp: uint64 Send timestamp (monotonic) │
├─────────────────────────────────────────────────────────────┤
│ Message Payload │
│ (variable length, message-type specific) │
└─────────────────────────────────────────────────────────────┘
```
### 6.2 Message Types
**System Messages:**
- `MSG_VM_BIRTH` - Mama→Messaging: new VM created
- `MSG_VM_DEATH` - Mama→Messaging: VM terminated
- `MSG_HARDWARE_EVENT` - Messaging→Mama: USB insert/remove
**Block I/O Messages:**
- `MSG_BLOCK_READ` - Request block read
- `MSG_BLOCK_WRITE` - Request block write
- `MSG_BLOCK_DATA` - Block data response
- `MSG_BLOCK_ACK` - Write acknowledgment
- `MSG_BLOCK_ERROR` - I/O error response
**User Messages:**
- `MSG_USER_DEFINED` - Application-specific messages
### 6.3 Message Routing
```
┌─────────────────────────────────────────────────────────────┐
│ Messaging VM │
├─────────────────────────────────────────────────────────────┤
│ Inbound Queue ──► │ Route │ ──► Per-VM Outbound Queues │
│ │ │ ├── Mama Queue │
│ │ │ ├── Block I/O Queue │
│ │ │ ├── User VM 1 Queue │
│ │ │ ├── User VM 2 Queue │
│ │ │ └── ... │
├─────────────────────────────────────────────────────────────┤
│ Routing Logic: │
│ 1. Verify source VM exists and is alive │
│ 2. Check source has CAP_MSG_SEND │
│ 3. Check target is in source's CAP_MSG_TARGETS │
│ 4. Enqueue to target's outbound queue │
│ 5. Wake target VM if sleeping │
└─────────────────────────────────────────────────────────────┘
```
### 6.4 Synchronous vs Asynchronous
**Asynchronous (default):**
- Sender posts message and continues
- Message queued for recipient
- Response comes as separate message
**Synchronous (CALL pattern):**
- Sender posts message and blocks
- Waits for response message
- Useful for RPC-style operations (e.g., block read)
---
## 7. BOOT SEQUENCE
### 7.1 Full Boot Flow
```
┌─────────────────────────────────────────────────────────────┐
│ PHASE 0: Hardware Initialization │
├─────────────────────────────────────────────────────────────┤
│ • UEFI boot │
│ • Memory map from firmware │
│ • Jump to kernel entry │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PHASE 1: Kernel Initialization │
├─────────────────────────────────────────────────────────────┤
│ • Console init (serial UART) │
│ • PMM init (physical memory manager) │
│ • VMM init (virtual memory manager) │
│ • Heap init (kernel malloc) │
│ • IDT init (interrupt descriptor table) │
│ • APIC init (interrupt controller) │
│ • Timer init (APIC timer, TSC calibration) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PHASE 2: Provisioning Check │
├─────────────────────────────────────────────────────────────┤
│ • Check provisioning flag in NVRAM │
│ • If NOT provisioned: enter provisioning mode │
│ └── Generate keypair, fuse, create admin drive, exit │
│ • If provisioned: load fused public key, continue │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PHASE 3: Mama VM Creation │
├─────────────────────────────────────────────────────────────┤
│ • Allocate Mama heap │
│ • Create Mama VM (VM ID 0) │
│ • Load mama-init capsule │
│ • Execute Mama init │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PHASE 4: System VM Birth │
├─────────────────────────────────────────────────────────────┤
│ • Mama births Messaging VM from messaging.4th capsule │
│ • Mama births Block I/O VM from block-io.4th capsule │
│ • System services now running │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PHASE 5: Device Enumeration │
├─────────────────────────────────────────────────────────────┤
│ • USB controller initialization │
│ • Storage device discovery │
│ • Messaging VM registers for hardware events │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PHASE 6: Ready for Users │
├─────────────────────────────────────────────────────────────┤
│ • System idle, waiting for thumbdrive insertion │
│ • Console shows: "Insert thumbdrive to login" │
│ • Or admin thumbdrive for administration │
└─────────────────────────────────────────────────────────────┘
```
### 7.2 Capsule Boot Order
```
Capsule Directory (in kernel or on system storage)
├── mama-init.4th ← Loaded first, by kernel
├── messaging.4th ← Birthed by Mama, second
├── block-io.4th ← Birthed by Mama, third
├── user-shell.4th ← Birthed on user login
└── (other capsules) ← As needed
```
---
## 8. IMPLEMENTATION PRIORITIES
Based on dependencies:
### 8.1 Critical Path
```
1. USB Driver (XHCI)
└── Need to detect thumbdrives
2. Block I/O Kernel Port
└── Need to read thumbdrive system region
3. SHA-256 in Kernel
└── Need to compute fingerprints
4. Ed25519 in Kernel
└── Need to verify signatures
5. Messaging Backbone VM
└── Need for all inter-VM communication
6. Block I/O VM
└── Need for all storage access
7. Capability Enforcement
└── Need to check grants at every operation
8. User Session VM
└── Finally can have interactive users
```
### 8.2 Estimated Complexity
| Component | LOC Estimate | Complexity |
|-----------|--------------|------------|
| XHCI driver | ~2500 | High |
| USB mass storage | ~800 | Medium |
| Block subsystem port | ~500 | Medium (reuse existing) |
| SHA-256 | ~400 | Low (well-known algorithm) |
| Ed25519 | ~600 | Medium (or use library) |
| Capability grant parser | ~300 | Low |
| Messaging VM | ~1000 | Medium |
| Block I/O VM | ~800 | Medium |
| Capability enforcement | ~500 | Medium |
| User session management | ~600 | Medium |
| Admin provisioning | ~400 | Medium |
**Total: ~8,400 LOC** for core security infrastructure
---
## 9. DATA STRUCTURES SUMMARY
### 9.1 VM Registry Entry (Extended)
```c
typedef struct {
uint32_t vm_id; // Unique VM identifier
uint32_t state; // VMState enum
uint64_t birth_capsule_id; // Which capsule birthed this
uint64_t birth_timestamp_ns; // When born
uint64_t birth_dict_hash; // Dictionary hash at birth
// Security fields (new)
uint8_t fingerprint[32]; // SHA-256 of user identity
uint64_t capability_bitmap; // Granted capabilities
uint32_t block_range_start; // First allowed block
uint32_t block_range_end; // Last allowed block
uint32_t msg_target_count; // Number of allowed targets
uint32_t msg_targets[16]; // Allowed message target VMs
// Resource tracking
uint64_t heap_base; // VM's heap start
uint64_t heap_size; // VM's heap size
uint64_t ram_blocks_base; // RAM block allocation
uint32_t flags;
uint32_t reserved;
} VMRegistryEntry;
```
### 9.2 Capability Bitmap
```c
// Capability flags
#define CAP_BLOCK_READ (1ULL << 0) // Can read blocks
#define CAP_BLOCK_WRITE (1ULL << 1) // Can write blocks
#define CAP_MSG_SEND (1ULL << 2) // Can send messages
#define CAP_MSG_RECEIVE (1ULL << 3) // Can receive messages
#define CAP_CAPSULE_LOAD (1ULL << 4) // Can request capsule load
#define CAP_CUSTOM_CAPSULE (1ULL << 5) // Can use custom capsule
#define CAP_SIGN_GRANTS (1ULL << 6) // Can sign capability grants (admin)
#define CAP_CREATE_USERS (1ULL << 7) // Can provision users (admin)
#define CAP_REVOKE_USERS (1ULL << 8) // Can revoke capabilities (admin)
#define CAP_SYSTEM_CONFIG (1ULL << 9) // Can modify system config (admin)
#define CAP_ALL_BLOCKS (1ULL << 10) // Can access any block (admin)
#define CAP_HARDWARE_IRQ (1ULL << 11) // Can receive hardware interrupts
#define CAP_ROUTE_MESSAGES (1ULL << 12) // Can route messages (messaging VM)
#define CAP_BLOCK_PHYSICAL (1ULL << 13) // Direct hardware I/O (block VM)
#define CAP_VM_ENUMERATE (1ULL << 14) // Can query VM registry
#define CAP_BLOCK_MAP (1ULL << 15) // Can create block mappings
#define CAP_ACL_ENFORCE (1ULL << 16) // Can enforce ACLs
// Convenience
#define CAP_USER_DEFAULT (CAP_BLOCK_READ | CAP_BLOCK_WRITE | \
CAP_MSG_SEND | CAP_MSG_RECEIVE)
#define CAP_ADMIN_ALL (0xFFFFFFFFFFFFFFFFULL)
```
### 9.3 Signed Capability Grant (On-disk)
```c
typedef struct __attribute__((packed)) {
// Header (16 bytes)
uint32_t magic; // "CPGR" = 0x43504752
uint16_t version; // Grant format version
uint16_t flags; // Grant flags
uint64_t grant_id; // Unique grant identifier
// Timestamps (16 bytes)
uint64_t created_ns; // Creation timestamp
uint64_t expires_ns; // Expiry (0 = never)
// Identity (32 bytes)
uint8_t fingerprint[32]; // SHA-256 that must match
// Capabilities (32 bytes)
uint64_t capability_bitmap; // Granted capabilities
uint32_t block_range_start; // First allowed block (logical)
uint32_t block_range_end; // Last allowed block (logical)
uint32_t msg_target_count; // Number of target VMs
uint32_t msg_targets[4]; // Allowed targets (expandable)
// Quotas (16 bytes)
uint32_t max_heap_mb; // Maximum heap size
uint32_t max_ram_blocks; // Maximum RAM blocks
uint32_t max_disk_blocks; // Maximum disk blocks
uint32_t reserved;
// Signature (64 bytes)
uint8_t signature[64]; // Ed25519 signature
} CapabilityGrant; // Total: 176 bytes
```
---
## 10. GLOSSARY
| Term | Definition |
|------|------------|
| **Mama VM** | Root VM that births and deaths all other VMs |
| **Baby VM** | Any VM birthed by Mama |
| **Capsule** | Immutable init block sequence defining VM personality |
| **PERSONALITY** | VM's immutable identity after birth (from capsule) |
| **DOMAIN** | Mama's construction space (never visible to babies) |
| **Fingerprint** | SHA-256 hash of identity sector, identifies user |
| **Capability Grant** | Signed document specifying user's permissions |
| **Fused Key** | Admin public key permanently written at provisioning |
| **Virtual BAM** | Per-user block allocation map (subset view) |
| **System Region** | Thumbdrive area invisible to user (metadata, ACLs) |
| **Admin Thumbdrive** | Single device with signing authority |
---
## 11. OPEN QUESTIONS
1. **Multiple simultaneous users?** Can multiple thumbdrives be inserted at once, with multiple concurrent sessions?
2. **Shared blocks between users?** Can users share specific blocks with each other? (Would require group capabilities)
3. **Network authentication?** Future extension for remote login?
4. **Audit log rotation?** System region audit log grows forever - need rotation policy?
5. **Capability delegation?** Can a user grant subset of their capabilities to another? (Currently: no)
6. **Time-limited sessions?** Automatic logout after idle timeout?
---
## 12. REVISION HISTORY
| Version | Date | Changes |
|---------|------|---------|
| 0.1 | 2026-01-27 | Initial architecture draft |
---
**Document Status:** Draft - Pending Review
**Authors:** Claude (AI Assistant), Robert A. James
**Next Steps:**
1. Review and refine security model
2. Prototype USB detection
3. Implement Ed25519/SHA-256 in kernel
4. Design message format in detail
5. Implement Block I/O VM capsule
-97
View File
@@ -1,97 +0,0 @@
# APIC Timer ISR Fix — amd64
**Date**: 2026-06-03
**Branch**: `claude/starforth-architecture-review-oubN0`
**Commits**: `fb0c256`, `901f827`
---
## Problem
The LithosAnanke amd64 kernel was triple-faulting on the first APIC timer interrupt,
causing QEMU to exit immediately after printing `ok>`. The adaptive heartbeat never
fired and `Heartbeat: N ticks` never appeared in serial logs.
## Root Cause
The kernel is compiled with `-fPIC -fvisibility=hidden` (required for UEFI PE
relocation). Assembly symbols `isr_stub_table` and `isr_stub0` in `isr.S` were
declared `.global` with **default visibility**. The C compiler therefore accessed
them through GOT indirection. However, the linker — building a final non-shared
binary — collapsed the GOT references to point directly at the symbols themselves.
The net effect: "load from GOT[`isr_stub_table`]" loaded the 8-byte instruction
bytes of `isr_stub0` (`0x006a90eb006a006a`) rather than its address. Both
`link_time_addr` and `runtime_addr` in `arch_interrupts_init()` became the same
wrong value (instruction bytes), so `reloc_offset = 0` and all 256 IDT entries
received garbage handler addresses. IDT[32] was set to `0x6a006afffffe91e9`
(non-canonical), which triggered #GP → double fault → triple fault on the first
timer delivery.
The bug was confirmed by disassembling `arch_interrupts_init()` in the loader PE:
the compiler emitted a `mov` (load from memory) for `&isr_stub0` instead of a
`lea` (compute address), reading the function's instruction bytes.
## Fix
Two changes, one line each:
**`src/starkernel/arch/amd64/isr.S`** — Added `.hidden` directives for all symbols
referenced from C:
```asm
.hidden isr_stub_table
.hidden isr_stub0
.hidden isr_stub32
.hidden isr_stub_default
.hidden isr_common_entry
```
**`src/starkernel/arch/amd64/interrupts.c`** — Added visibility attributes to the
`extern` declarations:
```c
extern void *isr_stub_table[IDT_ENTRIES] __attribute__((visibility("hidden")));
extern void isr_stub0(void) __attribute__((visibility("hidden")));
```
With hidden visibility, the compiler uses direct RIP-relative `lea` instead of GOT
indirection, yielding correct runtime addresses for all 256 IDT entries.
## Result
| Criterion | Before | After |
|---|---|---|
| IDT[32] handler | `0x6a006afffffe91e9` (corrupt) | `0x3d90251c` (valid canonical address) |
| First timer interrupt | #GP → triple fault → QEMU exit | ISR fires cleanly, returns via `iretq` |
| `PARITY:OK` | ✅ (unaffected) | ✅ |
| `Init: init.4th OK` | ✅ (unaffected) | ✅ |
| `ok>` REPL | ✅ reached, then immediate crash | ✅ runs indefinitely |
| `Heartbeat: N ticks` | Never | Every second at 100 Hz, TIME-TRUST ≈ 0.999 |
## Secondary Fix
`repl_print_dec32`, `repl_lapic_ccr`, `repl_print_hex32`, and `idle_iters` in
`repl.c` are x86-64 specific (LAPIC MMIO, `pushfq`). They were unguarded, causing
`-Werror=unused-function` / `unused-variable` failures on aarch64 (clang) and
riscv64 (gcc). All moved inside `#ifdef ARCH_AMD64`.
## Acceptance Testing
All three architectures must boot to `ok>` with `PARITY:OK` and `Init: init.4th OK`.
amd64 additionally shows `Heartbeat: N ticks TIME-TRUST≈0.999` repeating at 1 Hz.
```bash
make -f Makefile.starkernel ARCH=amd64 clean qemu
make -f Makefile.starkernel ARCH=aarch64 clean qemu
make -f Makefile.starkernel ARCH=riscv64 clean qemu
```
Verify each log:
```bash
grep "PARITY:OK" logs/qemu-<arch>-*.log
grep "Init: init.4th OK" logs/qemu-<arch>-*.log
grep "ok>" logs/qemu-<arch>-*.log
grep "Heartbeat:" logs/qemu-amd64-*.log | head -5
```
@@ -1,178 +0,0 @@
# Hosted StarForth — 3-Architecture Acceptance Test
**Date**: 2026-07-24 (original), corrected 2026-08-02
**Branch**: `master`
**Commits**: `edced063` (original block-subsystem fix), `4485c38` (doc +
3 clang-surfaced bug fixes), `e287334` (asm register-reuse fix), this pass
(`riscv64-clang` Makefile target)
---
## What this verifies
The **hosted** StarForth VM (`make`, not `Makefile.starkernel`) is a static
binary. This test confirms it starts cleanly — reaches the `ok>` REPL prompt
with no errors — on all three architectures the project targets:
- **amd64** — native execution on the build host
- **aarch64** — cross-compiled, run under QEMU user-mode emulation
- **riscv64** — cross-compiled, run under QEMU user-mode emulation
This is distinct from the kernel (`Makefile.starkernel`) 3-arch acceptance
test documented in
[`../amd64-isr-fix/README.md`](../amd64-isr-fix/README.md), which boots a
full UEFI bare-metal image under system-mode QEMU. This test only exercises
the hosted Linux binary, using QEMU **user-mode** emulation to run a
foreign-architecture static binary directly on the x86_64 host — no kernel,
no UEFI firmware, no system emulation.
---
## Prerequisites
Cross toolchains, QEMU user-mode emulation, and clang (needed for the
riscv64 leg — see below):
```bash
sudo apt-get install -y \
gcc-aarch64-linux-gnu \
gcc-riscv64-linux-gnu \
clang-18 lld-18 \
qemu-user
```
`qemu-user` alone is enough — the binaries built below are statically
linked (`-static`), so the non-static `qemu-<arch>` emulators run them
directly with no sysroot. (The `qemu-user-static` package, if you see it
referenced elsewhere, provides statically-linked *emulators*; it's not
required just because the *guest* binary is static.)
---
## Invocation note
None of the three binaries accept a `-c "<script>"` flag — earlier
revisions of this doc showed one, but `src/cli.c`/`src/main.c` have never
had one in this repo's history. Pipe the script via stdin with `-s`
(script/silent mode) instead. Startup always runs the full internal POST
test suite before the REPL takes stdin, so expect substantial test-suite
output before your script's result appears at the very end.
---
## amd64 — native
```bash
make clean && make
echo "1 2 + . BYE" | ./build/amd64/standard/starforth -s
```
Expect `3 Goodbye!` at the end of the output, then a clean exit. No
emulation needed — this is the build host's native architecture.
---
## aarch64 — cross-compile + QEMU user-mode
```bash
make rpi4-cross
echo "1 2 + . BYE" | qemu-aarch64 ./build/raspi/fastest/starforth -s
```
`make rpi4-cross` cross-compiles with `aarch64-linux-gnu-gcc`
(`ARCH=raspi`, `TARGET=fastest`, `-static`); see `Makefile:547-554`. The
output binary is statically linked, so `qemu-aarch64` can execute it
directly with no sysroot.
---
## riscv64 — cross-compile + QEMU user-mode
```bash
make riscv64-clang
echo "1 2 + . BYE" | qemu-riscv64 ./build/riscv64/fastest/starforth -s
```
`make riscv64-clang` cross-compiles with `clang-18` (`ARCH=riscv64`,
`TARGET=fastest`, `-static`); see `Makefile:566-579`. **This leg must use
clang, not GCC**: `riscv64-linux-gnu-gcc` fails to build this tree
(`nanosleep` visibility failure under `-std=c99`, unrelated to any project
change — root cause not yet chased down, see
`docs/working/archive/session-logs/2026-07-24-punch-list.md` item #3). The
target's `CFLAGS` deliberately does not reuse the Makefile's
`$(BASE_CFLAGS)` (which hardcodes `-std=c99`) — clang needs `-std=c11
-pthread` here instead, plus `-fuse-ld=lld` in `LDFLAGS` (folding it into
`CC` makes clang reject it as an unused argument during `-c`/compile-only
invocations under `-Werror`).
---
## Pass criteria
For each architecture:
1. Build completes with no errors.
2. `blk_subsys_init` succeeds — no `"Failed to initialize block subsystem"`
error. (This was broken on every architecture, including amd64, prior to
`edced063` — see below.)
3. The internal POST suite runs (substantial log output — expected, not a
failure by itself) and the piped `1 2 + . BYE` script prints `3 Goodbye!`
at the end.
---
## Background: the block-subsystem bug this depends on
Before `edced063`, the hosted VM could not start on **any** architecture on
this branch. `src/main.c` allocated a hardcoded `blk_ram[1024 * 1024]`
buffer, stale relative to `BLK_RAM_BLOCKS=2080` (`include/block_subsystem.h`)
— a deliberate redesign that moved the RAM/ramdrive boundary to physical
block 2080 (user-visible LBN 2048), per `BLK_FORTH_SYS_RESERVED=32`. The
kernel-side buffer in `src/starkernel/vm/bootstrap/sk_vm_bootstrap.c` already
derived its size correctly from the shared constant; `main.c` was the one
remaining hardcoded holdout. The fix:
```c
#include "block_subsystem.h"
...
static uint8_t blk_ram[BLK_RAM_BLOCKS * BLK_FORTH_SIZE]; /* was: blk_ram[1024 * 1024] */
```
This is why the acceptance test above is meaningful on this branch
specifically: it did not pass before this fix, on any of the three
architectures.
---
## Background: the riscv64 clang build (2026-08-02 correction)
The riscv64 command above was broken in this doc from 2026-07-24 until this
correction — `docs/working/archive/session-logs/2026-07-24-punch-list.md`
(item #2) recorded that the working clang invocation had been found and
fixed in the old pre-split monorepo's `master` branch's `.claude/CLAUDE.md`,
but the correction was never copied into this doc. Verifying it end-to-end
for this correction surfaced that the clang build also needs three small
source fixes from that same punch list (item #1), previously ported to the
old monorepo's `master` but not to this line:
- `src/math_portable.c` — `-100LL << 16` is UB (shifting a negative value)
under `-Wshift-negative-value`; changed to `-(100LL << 16)`.
- `src/physics_pipelining_metrics.c` — removed dead `q48_mul_q48()`
(`-Wunused-function` under clang; GCC doesn't catch this by default).
- `src/word_source/editor_words.c` — removed dead `set_scr()` (same reason).
The punch list's highest-priority item — a genuine SIGSEGV-causing
register-reuse hazard in `include/vm_asm_opt_riscv64.h`'s `vm_pop_asm`/
`vm_rpop_asm` (clang's register allocator can pick the same register for a
memory operand referenced both before and after a write-back, corrupting
the result; GCC happens not to hit it) was *not* required just to get this
acceptance test's `-O3` build to compile and pass, but has since been
fixed too (commit `e287334`) — and turned out not to be merely a latent
risk: it was silently corrupting 10 `CASE.*` control-flow tests under this
exact build (`955`/`10 failed` → `965`/`0 failed`, `ALL IMPLEMENTED TESTS
PASSED!`, with nothing else changed).
Item #3 of the same punch list — no `make` target for this leg, only the
long manual invocation — is also resolved as of this pass: `make
riscv64-clang` wires in the same recipe verified above directly into the
Makefile.
-323
View File
@@ -1,323 +0,0 @@
# Kernel Command-Line Arguments (KernelArgs)
**Branch:** `lithosananke`
**Status:** Design complete — awaiting implementation
## Motivation
Memory sizing and runtime behavior are currently compile-time constants:
```c
#define KERNEL_HEAP_SIZE (2ULL * 1024 * 1024 * 1024) /* kernel_main.c */
.space 0x200000 /* kernel_entry.S */
```
This means tuning stack depth or heap size for a given machine requires a
full rebuild. The JVM model (`-Xmx`, `-Xss`) is the right analogy: bake in
safe defaults, allow runtime overrides via boot-time command line. The same
mechanism also lets the kernel self-start the DoE and control log verbosity
without relying on external socat injection from the Makefile scripts.
## UEFI Mechanism
`EFI_LOADED_IMAGE_PROTOCOL->LoadOptions` is a UCS-2 string passed to the
EFI application by the firmware or boot manager. It is available to the
loader before `ExitBootServices()` at no extra cost — the loader already
holds `loaded_image` (see `uefi_loader.c:160`). We convert it to ASCII
(simple 8-bit truncation; all valid switches are ASCII) and parse it into
a `KernelArgs` struct that travels to the kernel via `BootInfo`.
## New Files
| File | Purpose |
|------|---------|
| `include/starkernel/kernel_args.h` | `KernelArgs` struct + default macros |
| `src/starkernel/boot/cmdline.c` | UCS-2→ASCII conversion + tokeniser + parser |
| `include/starkernel/cmdline.h` | Parser API (`cmdline_parse`) |
## Changed Files
| File | Change |
|------|--------|
| `include/starkernel/uefi.h` | Add `KernelArgs args` + `void *kernel_stack_base` + `uint64_t kernel_stack_size` to `BootInfo` |
| `src/starkernel/boot/uefi_loader.c` | Call `cmdline_parse(loaded_image->LoadOptions, ...)`, allocate dynamic stack, fill `BootInfo` |
| `src/starkernel/arch/amd64/kernel_entry.S` | Read `boot_info->kernel_stack_base`; fall back to BSS stack if zero |
| `src/starkernel/kernel_main.c` | Use `boot_info->args.heap_size`; honour `run_doe` and `log_level` |
| `Makefile.starkernel` | Pass `--doe` via OVMF boot entry args; remove socat EXEC-DOE injection |
## `KernelArgs` Struct
```c
/* include/starkernel/kernel_args.h */
#define KARGS_DEFAULT_STACK_SIZE (2ULL * 1024 * 1024) /* 2 MB */
#define KARGS_DEFAULT_HEAP_SIZE (2ULL * 1024 * 1024 * 1024) /* 2 GB */
#define KARGS_DEFAULT_LOG_LEVEL 1 /* info */
#define KARGS_DEFAULT_RUN_DOE 0
typedef struct {
/* Memory */
uint64_t stack_size; /* --stack=<size> e.g. 2M 4M 8M */
uint64_t heap_size; /* --heap=<size> e.g. 2G 4G */
/* Runtime behaviour */
int run_doe; /* --doe flag, default off */
int log_level; /* --log-level=<level> debug=0 info=1 warn=2 error=3 */
/* Extension: raw unparsed cmdline preserved verbatim (null-terminated) */
char raw[512];
} KernelArgs;
```
All fields have explicit defaults. Unknown switches are ignored and
appended to `raw` so future kernels can act on them while old kernels boot
safely.
## `BootInfo` Changes
Two fields are added **directly** to `BootInfo` (not nested inside
`KernelArgs`) so `kernel_entry.S` can read them at fixed, known offsets
without any C header dependency:
```c
typedef struct {
EFI_MEMORY_DESCRIPTOR *memory_map;
UINTN memory_map_size;
UINTN memory_map_descriptor_size;
EFI_RUNTIME_SERVICES *runtime_services;
void *acpi_table;
FramebufferInfo framebuffer;
UINT8 uefi_boot_services_exited;
/* NEW — kernel stack (loader-allocated; zero = use BSS fallback) */
void *kernel_stack_base; /* low address of allocation */
uint64_t kernel_stack_size;
/* NEW — all other parsed arguments */
KernelArgs args;
} BootInfo;
```
Keeping `kernel_stack_base` / `kernel_stack_size` at the top-level avoids
having to compute a nested struct offset inside assembly.
## Assembly Trampoline (`kernel_entry.S`)
```asm
kernel_main:
/* rdi = BootInfo* */
mov rax, [rdi + BOOT_INFO_STACK_BASE_OFFSET]
test rax, rax
jnz .dynamic_stack
lea rax, [rip + g_kernel_stack_top] /* BSS fallback */
jmp .stack_ready
.dynamic_stack:
mov rcx, [rdi + BOOT_INFO_STACK_SIZE_OFFSET]
add rax, rcx /* top = base + size */
.stack_ready:
and rax, -16
sub rax, 8
mov rsp, rax
xor rbp, rbp
jmp kernel_main_impl
```
`BOOT_INFO_STACK_BASE_OFFSET` and `BOOT_INFO_STACK_SIZE_OFFSET` are defined
as integer constants in a small `boot_info_offsets.h` that is included by
both C (to `_Static_assert` correctness) and the assembly file.
## Parser (`cmdline.c`)
```c
void cmdline_parse(const CHAR16 *load_options, BootInfo *boot_info);
```
Steps:
1. Convert UCS-2 → ASCII into a local 512-byte buffer (truncate at 511).
2. Copy verbatim into `boot_info->args.raw`.
3. Fill defaults into `boot_info->args`.
4. Tokenise on spaces; for each token:
- `--stack=<N>[KMG]` → `boot_info->kernel_stack_size` + `args.stack_size`
- `--heap=<N>[KMG]` → `args.heap_size`
- `--doe` → `args.run_doe = 1`
- `--log-level=<s>` → `args.log_level` (debug/info/warn/error → 0-3)
- anything else → silently ignored (forward-compat)
5. Allocate stack via `BS->AllocatePages(AllocateAnyPages, EfiLoaderData, pages)`.
Store result in `boot_info->kernel_stack_base`.
Size suffix parser handles `K`/`M`/`G` (case-insensitive); bare numbers are
treated as bytes.
## Loader Integration
In `uefi_loader.c`, after the existing `HandleProtocol` call for
`loaded_image` and before `ExitBootServices`:
```c
cmdline_parse(loaded_image->LoadOptions, &boot_info);
```
`boot_info` is already on the loader stack and passed to the kernel entry;
no structural change needed.
## `kernel_main.c` Integration
```c
/* Heap — use runtime size if provided */
uint64_t heap_sz = boot_info->args.heap_size
? boot_info->args.heap_size
: KARGS_DEFAULT_HEAP_SIZE;
kmalloc_init(heap_sz);
/* DoE — self-start if requested */
#ifdef STARFORTH_ENABLE_VM
if (boot_info->args.run_doe) {
vm_interpret(mama, "EXEC-DOE");
}
#endif
```
Log level feeds into the existing `LOG-DEBUG` / `LOG-INFO` etc. word
infrastructure via a new `sk_set_log_level(int)` call.
## Makefile Integration
The `qemu` target currently injects `EXEC-DOE` via socat after detecting
`ok>`. With `--doe` supported natively:
```makefile
# Pass --doe via OVMF boot entry OptionalData
QEMU_KERNEL_ARGS ?= --doe --log-level=info --stack=2M --heap=2G
```
The ISO build embeds these in the UEFI boot entry so the kernel receives
them from firmware. The socat injection dance is removed. The `SK_CMD`
compile-time flag remains available for one-off testing.
## Passing Args in QEMU
OVMF reads boot entry `OptionalData` from the UEFI variable store. The
cleanest approach for the Makefile: use a small Python/shell script to
write the boot entry with the desired args into `OVMF_VARS.fd` before
launching QEMU. Alternatively, `efibootmgr` syntax baked into the ISO's
`startup.nsh` script works with the OVMF shell.
## `REBOOT` FORTH Word
### Purpose
Allows a running kernel to set new boot flags and immediately cold-reset.
The canonical use case:
```forth
S" --doe --log-level=debug" REBOOT
```
On the next boot the loader finds the one-shot NVRAM variable, uses it
instead of `LoadOptions`, and clears it so subsequent boots revert to
normal.
### Stack effect
```
REBOOT ( addr len -- )
```
Takes a counted string (same convention as `S"`) containing the new
argument line.
### Interpret-only guard
`REBOOT` is a normal (non-immediate) word that checks compilation state at
the top of its body. Attempting to include it in a `:` definition produces
a `-14` throw (`interpretation semantics only`) **at definition time**, not
at run time — which is the correct FORTH-standard behaviour.
```c
static void word_reboot(VM *vm) {
if (vm->compiling) {
vm_throw(vm, -14); /* interpretation semantics only */
return;
}
...
}
```
### NVRAM one-shot mechanism
When `REBOOT` executes it:
1. Pops `addr` / `len` from the data stack.
2. Validates length (`0 < len < KERNEL_ARGS_CMDLINE_MAX`); throws `-11` on
out-of-range.
3. Converts the string to UCS-2.
4. Reads `StarForthRebootTries` from NVRAM (default 0 if absent).
5. If `tries >= REBOOT_MAX_TRIES` (default **3**):
- Clears both `StarForthBootArgs` and `StarForthRebootTries` variables.
- Prints warning to serial: `REBOOT: max tries reached — dropping to REPL`.
- Returns normally (does **not** reset); execution continues at the REPL.
6. Otherwise: writes `StarForthBootArgs` and increments `StarForthRebootTries`,
then calls `RuntimeServices->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL)`.
This call does not return.
On the next boot, the loader checks for `StarForthBootArgs`:
- If found: copies value, **clears `StarForthBootArgs`** (one-shot), uses it
for parsing. `StarForthRebootTries` is left untouched by the loader — it
is only cleared by a successful REPL session (see below) or by the
max-tries guard above.
On reaching the REPL successfully, `kernel_main` clears `StarForthRebootTries`
to zero, resetting the counter for the next `REBOOT` invocation. This means
a clean interactive boot always resets the escape-hatch state.
### `SetVariable` failure path
If any `SetVariable` call returns a non-`EFI_SUCCESS` status:
- Print warning: `REBOOT: NVRAM write failed (<status>) — dropping to REPL`.
- Do **not** attempt `ResetSystem`.
- Return normally; execution continues at the REPL.
No infinite retry loop. One attempt, one warning, one escape to REPL.
### Hosted-VM stub
On the Linux-hosted build `RuntimeServices` is unavailable. The stub logs
the would-be args and sets `vm->halted = 1`:
```c
#ifndef STARFORTH_KERNEL
hal_console_puts("[REBOOT] hosted stub — would reboot with: ");
hal_console_write((const char *)VM_ADDR(vm, addr), (size_t)len);
hal_console_puts("\n");
vm->halted = 1;
#endif
```
### Registration
Registered alongside `BYE` / `WARM` / `COLD` in `system_words.c`:
```c
REGISTER_WORD("REBOOT", word_reboot, WORD_NORMAL);
```
---
## Extension Points
Adding a new switch requires:
1. A field in `KernelArgs` with a default macro.
2. One `else if` branch in `cmdline_parse()`.
3. One read site in the kernel (usually `kernel_main.c`).
No changes to `BootInfo`, `kernel_entry.S`, or the loader calling code.
## Invariants
- If `LoadOptions` is NULL or empty, all defaults apply — identical to
current behaviour. Existing QEMU launch commands continue to work.
- The BSS stack (`g_kernel_stack`) remains in the binary as a fallback.
It can be removed in a future cleanup once dynamic allocation is proven.
- `KernelArgs.raw` is always null-terminated; max 511 chars.
- Unknown arguments never cause a boot failure.
-18
View File
@@ -1,18 +0,0 @@
# docs/lithosananke/kernel-args/
Design doc for kernel command-line arguments (`KernelArgs`).
- **[DESIGN.md](DESIGN.md)** — Design for runtime-configurable memory sizing
and behavior (heap size, stack depth, DoE self-start, log verbosity)
currently baked in as compile-time constants. Covers the UEFI
`EFI_LOADED_IMAGE_PROTOCOL->LoadOptions` mechanism, the `KernelArgs`
struct, `BootInfo` changes, the assembly trampoline
(`kernel_entry.S`), the `cmdline.c` parser, loader/`kernel_main.c`/
Makefile integration, passing args under QEMU, and the `REBOOT` FORTH
word (including its NVRAM one-shot mechanism and hosted-VM stub). Status:
design complete, awaiting implementation.
## See also
- [`docs/lithosananke/README.md`](../README.md) — kernel branch overview.
- [Project root](../../../README.md)
@@ -1,183 +0,0 @@
# Kernel Stack Trampoline Fix — riscv64
**Date**: 2026-08-02
**Branch**: `master`
**Commit**: `7366275`
---
## Problem
The riscv64 kernel crashed partway through VM bootstrap during the 2026-08-01
DoE campaign run, well after amd64 and aarch64 had both booted cleanly to
`ok>` in the same session. The crash was a wild jump: execution landed at an
address nowhere inside the kernel's own linked image, then almost immediately
faulted trying to load from a small, near-null address.
```
Registering FORTH-79 Standard word set...
Registering FORTH-79 arithmetic words...
FORTH-79 Standard word set registration complete
*** EXCEPTION (riscv64) ***
scause = 0x000000000000000d
sepc = 0x00000000be27c3c2
stval = 0x00000000000074d8
```
`scause = 0xd` is a supervisor-mode load page fault. `sepc` (~0xbe27c3c2) sat
near the top of QEMU's 1 GB RAM window — nowhere close to the kernel's own
`.text`/`.data`/`.bss` footprint (`0x400000`–`0xcdf9a0`, confirmed via `nm`/
`readelf` on the exact build that produced the log). `stval` (0x74d8, ~30 KB)
stayed fixed across rebuilds even as `sepc` drifted — the signature of a
corrupted return address rather than a fixed logic bug at one source line.
## Investigation
Two dead ends were ruled out before the real cause surfaced:
1. **ELF relocation / buffer overlap in the split-build loader.** The
codebase has a `!MONOLITHIC_BUILD` path (`elf_load_kernel()` in
`elf_loader.c`) that loads `kernel.elf` from the ESP into a UEFI
`AllocateAnyPages` buffer. This looked like a plausible overlap candidate
at first, but `Makefile.starkernel` defaults to `MONOLITHIC=1`, meaning
`kernel_main` is linked directly into the loader and that code path never
runs. Confirmed by checking the built binary for the investigation's own
diagnostic strings — absent, because the compilation unit was built with
`-DMONOLITHIC_BUILD` and the code was preprocessed out.
2. **`RAW_LOG()` silently no-ops on every non-amd64 arch.** All of
`load_kernel_from_esp()`'s narration (and an early ConOut-based
diagnostic) never reached the captured serial log, on riscv64, in any
build. `RAW_LOG` was defined only for `ARCH_AMD64`; everywhere else it
expanded to `((void)0)`. This made straightforward diagnosis impossible
until a real raw-UART writer was added for riscv64 (see Secondary Fix).
With real output available, `console_println()` checkpoints were bisected
through `vm_init_with_host()` and `sk_vm_bootstrap_parity()`. Every checkpoint
inside `vm_init_with_host()` printed successfully, including the very last
line of the function — but the checkpoint immediately after its *return*
never appeared. Re-running with a trivial, unrelated local-variable addition
shifted the crash earlier and changed the exception type entirely (from a
load fault to an instruction-fetch fault at `sepc == stval`, i.e. the CPU
tried to *execute* an unmapped address). A crash whose location and even
category shift under unrelated code changes is the standard signature of
stack corruption, not a fixed bug at a specific call site.
## Root Cause
`kernel_main.c` documented the gap directly, once read closely:
> On amd64, `kernel_entry.S` switches the stack from UEFI's default to a
> 2 MiB zero-initialised BSS stack and tail-calls this function as
> `kernel_main_impl`. On aarch64 and riscv64 the assembly trampoline is not
> yet implemented and the UEFI loader calls `kernel_main` directly.
amd64 has carried a dedicated stack-switch trampoline
(`src/starkernel/arch/amd64/kernel_entry.S`) since early on, with its own
comment explaining why: *"UEFI hands control to kernel_main() running on a
small UEFI-provided stack (typically 128 KB–256 KB). The FORTH interpreter +
DOE experiment loop can easily exceed that depth."* riscv64 never had the
equivalent. It ran the entire VM bootstrap — 27 chained word-registration
modules, physics/SSM init, Tripod capsule birth — directly on whatever small
stack EDK2's riscv64 firmware handed the loader, overflowing it and
corrupting a return address. aarch64 has the same theoretical exposure but
happens not to trip it, apparently because its firmware's default boot stack
is larger — incidental, not a guarantee.
## Fix
**`src/starkernel/arch/riscv64/kernel_entry.S`** (new) — RISC-V stack-switch
trampoline mirroring amd64's:
```asm
.section .bss
.align 4
.global g_kernel_stack
g_kernel_stack:
.space 0x200000
g_kernel_stack_top:
.global g_kernel_stack_top
.section .text
.extern kernel_main_impl
.global kernel_main
kernel_main:
ld t0, BOOT_INFO_KERNEL_STACK_BASE_OFFSET(a0)
bnez t0, .Ldynamic_stack
la t0, g_kernel_stack_top
j .Lstack_ready
.Ldynamic_stack:
ld t1, BOOT_INFO_KERNEL_STACK_SIZE_OFFSET(a0)
add t0, t0, t1
.Lstack_ready:
andi t0, t0, -16
mv sp, t0
mv s0, zero
tail kernel_main_impl
```
**`src/starkernel/kernel_main.c`** — riscv64 now builds `kernel_main_impl`
(invoked via the trampoline) instead of `kernel_main` directly, same pattern
as amd64:
```c
#if defined(__x86_64__) || defined(__riscv)
void kernel_main_impl(BootInfo *boot_info) {
#else
void kernel_main(BootInfo *boot_info) {
#endif
```
**`Makefile.starkernel`** — wires the new file into the riscv64 loader build:
```makefile
ifeq ($(ARCH),riscv64)
LOADER_ASM += $(KERNEL_SRC)/arch/$(ARCH)/kernel_entry.S
endif
```
## Secondary Fix
`RAW_LOG()` in `src/starkernel/boot/uefi_loader.c` was a no-op on every arch
except amd64, silencing all of the loader's own diagnostic messages on
riscv64 (and aarch64). Added a real raw-UART writer for riscv64, targeting
QEMU virt's 16550-compatible `uart8250` at MMIO `0x10000000` (matches
`Domain0 Region03` in the OpenSBI boot banner) — the same byte-polling
protocol as the existing amd64 port-I/O version, just MMIO instead of `outb`/
`inb`. Existing and future `RAW_LOG()` call sites in the loader now actually
produce output on riscv64.
## Result
| Criterion | Before | After |
|---|---|---|
| `sepc` after word registration | Wild address outside kernel image (`0xbe27c3c2`-ish, drifting per build) | N/A — no longer occurs |
| Exception | Load or instruction-fetch page fault, non-deterministic between builds | None |
| `[Hera] ok>` (riscv64) | Never reached | ✅ reached, Tripod fleet (Hera/Hermes/Artemis) runs to completion |
| `PASS: E2E msg flow` | Never logged | ✅ |
| `PARITY:MAMA_INIT` / `Init: Mama birth OK` | Never logged | ✅ |
| Loader `RAW_LOG()` output on riscv64 | Silent no-op | ✅ visible in serial log |
## Acceptance Testing
All three architectures must boot to `ok>`, run the Tripod fleet, and reach
`[Hera] ok>`. Per repo convention, run in this exact order, one QEMU instance
at a time, always `clean` before `qemu`:
```bash
make -f Makefile.starkernel ARCH=amd64 clean qemu
make -f Makefile.starkernel ARCH=aarch64 clean qemu
make -f Makefile.starkernel ARCH=riscv64 clean qemu
```
Verify each log:
```bash
grep "ok>" logs/<session>/<arch>/qemu-<arch>-*.log
grep "PASS: E2E msg flow" logs/<session>/<arch>/qemu-<arch>-*.log
grep "PARITY:MAMA_INIT" logs/<session>/<arch>/qemu-<arch>-*.log
grep "EXCEPTION" logs/<session>/<arch>/qemu-<arch>-*.log # must be empty
```
All three architectures confirmed clean on 2026-08-02 (see
`logs/20260802-064050/riscv64/`, `logs/20260802-064424/amd64/`,
`logs/20260802-064656/aarch64/`, committed alongside this fix).