Files
LithosAnanake/docs/03-architecture/word-acl/DESIGN.md
T
Robert Allan JamesandClaude Sonnet 5 cbe7b49a59 Documentation debt sweep: 5 of 6 items resolved, 1 confirmed accurate
- docs/lithosananke/ROADMAP.md + M7.1.md: fixed stale "Branch: lithosananke"
  (no such branch post-split), M7.1's "Design Complete" status (shipped
  and live, redirected to FABRIC*.md), the M8/success-criteria
  self-contradiction (OBSOLETE marking vs. unqualified live criterion),
  and the stale AHCI/SATA claim for M9 (real implementation is
  virtio_blk.c) -- also corrected BLOCK/BUFFER/UPDATE/FLUSH and block
  device abstraction to [x] since both are confirmed live in
  src/word_source/block_words.c and block_subsystem.c.
- Top-level ROADMAP.md: marked OBSOLETE (Captain Bob's call -- more than
  "stale," the architecture/branch topology/terminology it describes no
  longer exist), pointing to docs/lithosananke/ROADMAP.md and
  FABRIC*.md for current status.
- docs/03-architecture/word-acl/DESIGN.md: fixed the ACL Phase 7
  contradiction -- Phase 7 (LithosAnanke kernel parity) is independently
  verified complete per .claude/CLAUDE.md, not "remaining"; removed the
  stale lithosananke-branch-parity framing.
- VM-FLEET-ATTRACTOR-DESIGN-20260705.md's doe-campaign.4th "broken" claim:
  investigated, ran SMOKE-CAMPAIGN live (completes clean, fleet heat
  conserved) -- initially read as contradicting the claim, corrected
  directly by Captain Bob: a clean execution trace doesn't disprove the
  doc's actual argument (no real controlled-experimental-factor
  mechanism). Confirmed accurate, left untouched.
- Isabelle/HOL pipeline-metrics model/C-struct mismatch: confirmed a real
  proof-modeling gap (pm_last_accuracy_num/den has no analogue in the
  real PipelineGlobalMetrics struct), not stale prose -- tracked here
  rather than fixed, matching the .thy file's own scope boundary and
  this project's standing caution that each Isabelle gap needs its own
  subsystem model.

ACL-RWT DoE overhead re-measurement (the 6th item) intentionally not
started -- a full multi-architecture DoE campaign, not a doc-text fix,
holding for explicit confirmation given the scale.

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

405 lines
16 KiB
Markdown

# Word-Level ACL System
**Status:** Implemented through Phase 7 (LithosAnanke kernel parity) — independently verified
present in current `master` (`.claude/CLAUDE.md`'s Word-Level ACL System section). Phase 8
(PKI/thumbdrive minting) is the current open item.
**Target branch:** `master` — post-split, this repo's sole production line; there is no
separate `lithosananke` branch to reach parity with (see `.claude/CLAUDE.md`'s "On the branch
topology" note)
**Implementation files:** `capsules/ACL.4th`, `capsules/zuse.4th`, `src/word_source/acl_words.c`, `src/test_runner/modules/acl_words_test.c`
---
## Summary
### Overview
A word-level access control system for StarForth VMs, implemented as a
combination of a thin C infrastructure layer and a pure-FORTH policy capsule
(`ACL.4th`). The C layer provides four `DictEntry` fields for the hot path
(TTL counter + allow bit + mode + pin). All policy, all adaptive logic, all
inheritance rules live as colon definitions in `ACL.4th`. A bootstrap
superuser (`zuse.4th`) provides the sole authenticated escalation path.
---
### ACL Disposition — Three States
Each word in the dictionary has one of three enforcement dispositions:
| State | Behavior |
|-------|---------|
| `STRICT` | ACL re-checked on every execution |
| `TTL` | ACL cached; re-checked only when TTL counter reaches zero |
| `PINNED` | Current mode and decision frozen permanently; one-way ratchet |
---
### TTL — Statistically Adaptive
The TTL is not a fixed value. It is derived and continuously updated from
the existing SSM execution physics:
- **Heat** (`execution_heat`): hotter words earn longer TTLs; check cost
amortized over more executions
- **Rolling window**: sudden shift in caller pattern or access rate causes
TTL to shrink aggressively
- **Decay**: quiescent words pull their TTL back down so the next burst
re-validates early
- **Inference engine** (L5/L6): detects oscillating TTL and stabilizes it —
same CV threshold logic already used for window-width inference
`PINNED` is the asymptote: the adaptive process converges, you thumbtack
it, and the accumulator freezes permanently.
A FORTH security word selects enforcement mode per word:
```forth
' MY-WORD ACL-STRICT \ check every execution
' MY-WORD ACL-TTL-MODE \ use adaptive TTL
' MY-WORD ACL-PIN \ freeze — mode and decision immutable forever
```
---
### The Thumbtack / Pin Flag
`ACL-PIN ( xt -- )` is a one-way ratchet:
- Transitions: `STRICT → PINNED` or `TTL → PINNED` — never back
- Once pinned, `acl_mode` and `acl_ttl` are immutable for that VM context
- Any attempt to change a pinned word's ACL is silently ignored (or errors,
depending on policy)
- Kernel primitive words (`BIRTH`, `EXEC`, `BYE`, etc.) are pinned by Mama
at boot before the first `BIRTH` fires
---
### Inheritance at Birth
When a child VM is born:
```
acl_mode = parent->acl_mode \ policy propagates (STRICT or TTL)
acl_pinned = 0 \ always clear — child owns its own lock
acl_ttl = default \ reset; child has no execution history
acl_decision = ALLOW \ re-evaluated on first access
```
Key properties:
- **Pin is contextual, not viral.** A parent's pinned words do not force
the child to be pinned. The child inherits the mode as a starting point
and can tighten, relax, or re-pin freely.
- **Security lattice flows downward at birth only.** After birth, each VM's
ACL state is independent. A compromised child cannot bootstrap its way
back to Mama's pinned ACLs.
---
### Interpreter Hook — Two-Level Check
The C interpreter reads only two fields per `DictEntry`:
```c
if (vm->emergency_console) goto execute; /* 100% bypass — physical access */
if (entry->acl_ttl-- > 0) goto execute; /* TTL good — single decrement */
acl_recheck(vm, entry); /* TTL=0: call FORTH ACL-RECHECK */
if (!entry->acl_allow) goto reject;
```
**Hot path cost:** one decrement and a branch — essentially free.
**Cold path:** calls `ACL-RECHECK` in `ACL.4th`, which recomputes the
adaptive TTL, updates `acl_allow`, and resets the counter. The C side
never reasons about policy; it only reads the result.
---
### Two-Console Architecture
Two permanent, independent console layers exist at all times:
#### 1. Emergency Console (always present)
- `emergency_console` (`uint8_t`) flag in `VM` struct — C-only write; no
FORTH word sets it
- Checked first in the interpreter hot path — 100% ACL bypass
- `acl_recheck()` uses save/restore around it for re-entrancy protection
- `EMERGENCY_CONSOLE_ENABLED` Makefile build flag (default=1): when set to
0, strips interactive fallthrough; `vm_fault_handler()` weak symbol is
called instead (override for hardware reset, watchdog, debug probe)
- `BYE` returns to the emergency console
- Only `panic` kills the VM
- Prompt: `ok>`
#### 2. Zuse Console (omnipresent, awaiting authentication)
- `zuse_session` (`uint8_t`) flag in `VM` struct — C-only write; no FORTH
word sets it
- Prompt: `zuse)ok>`
- Full ACL bypass when a Zuse session is active
- Completely independent of `emergency_console` — setting one does not
affect the other
---
### Superuser: Zuse
Named for Konrad Zuse, pioneer of programmable computers. Zuse is the
bootstrap superuser: the sole entity that can own the zuse console and mint
user credentials.
- Defined in `capsules/zuse.4th`, loaded by `ACL.4th` at capsule boot
- Software-only for now (no thumbdrive); `zuse.4th` is a capsule citizen
from day one
- Zuse's words are pinned by `ACL-ZUSE-BOOT` at load time
- `ACL-BOOT` is called first, then `S" zuse.4th" EXEC` — both from within
`ACL.4th` itself (Block 2067)
- Future: replace with thumbdrive PKI (Ed25519 challenge-response);
`zuse.4th` becomes the bootstrapper that validates the physical drive
---
### CA Root
- Embedded in `ACL.4th` (Block 2066) as constants `ACL-CA-KEY-LO` /
`ACL-CA-KEY-HI`
- Capsule hash = root-of-trust fingerprint: any CA key change changes the
capsule hash and the birth protocol detects tampering
- Placeholder (zeros) until `tools/mkcapsule` embeds the real Ed25519 key
at build time
---
### Security Model / No-Security Condition
Security is opt-in via `init.4th`. The toggle is one commented-out line:
```forth
\ S" ACL.4th" EXEC
```
| `ACL.4th` | `zuse.4th` | Result |
|-----------|------------|--------|
| absent | absent | No security — open dev mode |
| present | absent | ACL enforced; emergency REPL locked until Zuse provisioned |
| present | present | Full lockdown; Zuse owns the zuse console |
Uncomment the line in `init.4th` to enable full lockdown. Leave it
commented out for development builds.
---
### Self-Activating Capsule
`ACL.4th` is self-activating — `init.4th` only needs one line:
```forth
S" ACL.4th" EXEC
```
Internal structure of `ACL.4th`:
- **Block 2066**: CA root placeholder constants (`ACL-CA-KEY-LO` /
`ACL-CA-KEY-HI`)
- **Block 2067**: Calls `ACL-BOOT` then `S" zuse.4th" EXEC`
`ACL-BOOT` stamps default ACL entries on all existing dictionary words.
After it runs, every subsequent `:` definition gets an ACL entry via the
defining-word hook. No word can exist without an ACL entry.
---
### Emergency Console — 100% Bypass
`vm->emergency_console` is set by the C layer when the physical `ok>` REPL
is active. It is checked first, before any ACL table lookup. Physical
presence always wins. The emergency console is never subject to ACL
constraints regardless of what `ACL.4th` defines.
The `EMERGENCY_CONSOLE_ENABLED=0` build flag strips the interactive
fallthrough for production or embedded builds, routing faults to
`vm_fault_handler()` instead.
---
### ACL.4th — Pure FORTH Implementation
The ACL table is a `CREATE`d FORTH array indexed by execution token (XT).
`'` (tick) gives the XT of any word; XT is just a cell value usable as a
table key.
Core words:
| Word | Stack | Description |
|------|-------|-------------|
| `ACL-ENTRY` | `( xt -- addr )` | O(1) table lookup by XT |
| `ACL-MODE@` | `( xt -- mode )` | read enforcement mode |
| `ACL-MODE!` | `( mode xt -- )` | set mode (no-op if pinned) |
| `ACL-PINNED?` | `( xt -- flag )` | test pin bit |
| `ACL-PIN` | `( xt -- )` | set pin — one-way, irreversible |
| `ACL-STRICT` | `( xt -- )` | set STRICT mode (no-op if pinned) |
| `ACL-TTL-MODE` | `( xt -- )` | set TTL mode (no-op if pinned) |
| `ACL-TTL@` | `( xt -- n )` | read current TTL counter |
| `ACL-TTL!` | `( n xt -- )` | write TTL counter |
| `ACL-ALLOW@` | `( xt -- flag )` | read cached allow/deny decision |
| `ACL-ALLOW!` | `( flag xt -- )` | write allow/deny decision |
| `ACL-INHERIT` | `( src dst -- )` | birth inheritance: copy mode, clear pin, reset ttl+decision |
| `ACL-RECHECK` | `( xt -- )` | adaptive TTL recomputation; updates allow + new TTL |
| `ACL-INIT-PRIMITIVES` | `( -- )` | bulk-initialize ACL entries for all existing dictionary words |
Example pin at boot in `init.4th`:
```forth
' BIRTH ACL-STRICT ' BIRTH ACL-PIN
' EXEC ACL-STRICT ' EXEC ACL-PIN
' BYE ACL-STRICT ' BYE ACL-PIN
```
---
### Bootstrap Sequence
```forth
\ In init.4th (one line, opt-in toggle — uncomment to enable):
S" ACL.4th" EXEC \ self-activating: calls ACL-BOOT then loads zuse.4th
\ ACL.4th Block 2067 does internally:
ACL-BOOT \ stamp default ACLs on all existing words
S" zuse.4th" EXEC \ load and pin Zuse's words
\ Subsequent capsule loads (e.g. doe.4th) get ACL entries via : hook
S" doe.4th" EXEC
```
Every `:` definition after `ACL-BOOT` runs creates its own ACL entry at
definition time via a hook in the defining word. No word is ever born
without an ACL entry.
---
### Capsule Namespace
| File | Role |
|------|------|
| `init.4th` | Mama VM personality (default); contains opt-in ACL toggle |
| `init-*.4th` | Alternate personalities |
| `doe.4th` | DoE experiment harness |
| `ACL.4th` | Word-level ACL subsystem — self-activating; loads `zuse.4th` |
| `zuse.4th` | Bootstrap superuser personality; words pinned by `ACL-ZUSE-BOOT` |
| `std-blob.4th` | Standard library layer (future) |
---
## Implementation Punch List
### Phase 1 — C Infrastructure (`master`) ✅ COMPLETE
- [x] Add `acl_ttl` (`uint32_t`) to `DictEntry` in `include/vm.h`
- [x] Add `acl_allow` (`uint8_t`) to `DictEntry`
- [x] Add `acl_mode` (`uint8_t`: STRICT=0 / TTL=1) to `DictEntry`
- [x] Add `acl_pinned` (`uint8_t`) to `DictEntry`
- [x] Default-initialize all four fields in `word_register()`:
`ttl=ACL_OPEN, allow=1, mode=TTL, pinned=0`
- [x] Add `emergency_console` (`uint8_t`) flag to `VM` struct
- [x] Add `zuse_session` (`uint8_t`) flag to `VM` struct
- [x] Insert two-level ACL check into interpreter loop in `vm.c`
- [x] Implement `acl_recheck()` in `vm.c` — calls FORTH `ACL-RECHECK` when
TTL=0; save/restore `emergency_console` for re-entrancy protection
- [x] Add `:` definition hook to create ACL entry for each newly defined word
### Phase 2 — C Primitive Words + `ACL.4th` Capsule (`master`) ✅ COMPLETE
- [x] 12 C primitive words in `src/word_source/acl_words.c`
- [x] `CREATE ACL-TABLE` sized to max dictionary entries
- [x] `ACL-ENTRY ( xt -- addr )` — XT-indexed O(1) lookup
- [x] Field accessors: `ACL-MODE@`, `ACL-MODE!`, `ACL-PINNED?`,
`ACL-TTL@`, `ACL-TTL!`, `ACL-ALLOW@`, `ACL-ALLOW!`
- [x] `ACL-PIN ( xt -- )` — one-way ratchet; no-op if already pinned
- [x] `ACL-STRICT ( xt -- )` — set STRICT mode; no-op if pinned
- [x] `ACL-TTL-MODE ( xt -- )` — set TTL mode; no-op if pinned
- [x] `ACL-INHERIT ( src-xt dst-xt -- )` — copy mode, clear pin,
reset ttl+decision
- [x] `ACL-RECHECK ( xt -- )` — recompute adaptive TTL from heat and rolling
window; update `acl_allow` and reset counter
- [x] `ACL-INIT-PRIMITIVES` — walk dictionary, create default entry per word
- [x] Pin privileged words: `BIRTH`, `EXEC`, `BYE`, and other Mama-only words
### Phase 3 — `capsules/ACL.4th` Self-Activating Capsule (`master`) ✅ COMPLETE
- [x] `ACL.4th` is self-activating: Block 2067 calls `ACL-BOOT` then
`S" zuse.4th" EXEC`
- [x] Block 2066: CA root placeholder constants (`ACL-CA-KEY-LO` /
`ACL-CA-KEY-HI`)
- [x] `ACL-BOOT` stamps default ACL entries on all existing dictionary words
- [x] `capsules/zuse.4th` loaded at end of `ACL.4th` capsule
- [x] Zuse's words pinned by `ACL-ZUSE-BOOT` at load time
### Phase 4 — `init.4th` Opt-In Toggle (`master`) ✅ COMPLETE
- [x] `init.4th` has one commented-out line as the opt-in toggle:
`\ S" ACL.4th" EXEC`
- [x] Uncomment to enable full lockdown; leave commented for dev/open mode
- [x] No other changes to `init.4th` required — `ACL.4th` is self-contained
### Phase 5 — POST Tests (`master`) ✅ COMPLETE
POST tests implemented in `src/test_runner/modules/acl_words_test.c`,
registered in `src/test_runner/test_runner.c`. 800/800 passing.
- [x] POST test: `ACL-PIN` is one-way — mode cannot change after pin set
- [x] POST test: inheritance — child entry has mode copied, pin cleared,
ttl and decision reset
- [x] POST test: emergency console bypass — `emergency_console=1` skips
ACL check entirely
- [x] POST test: STRICT mode — ACL re-evaluated on every execution
- [x] POST test: TTL hot path — TTL > 0 bypasses re-evaluation
- [x] POST test: adaptive accumulator — heat increase produces TTL increase
- [x] POST test: privileged words pinned at boot remain pinned after
`ACL-INIT-PRIMITIVES`
### Phase 6 — Isabelle/HOL Formal Verification (`master`) ✅ COMPLETE
Five `.thy` files in `proof/` alongside existing VM proofs:
- [x] `ACL_Pin_Monotone.thy` — pin bit is set-only; no operation clears it
once set
- [x] `ACL_Inherit_Clears_Pin.thy``ACL-INHERIT` always produces an entry
with `acl_pinned = 0` regardless of source entry state
- [x] `ACL_TTL_Bounded.thy` — TTL counter is bounded above by
`ACL-TTL-COMPUTE` output; cannot grow unboundedly
- [x] `ACL_Emergency_Bypass.thy` — when `emergency_console = 1` the
allow/deny decision is never consulted
- [x] `ACL_No_Escalation.thy` — a child VM cannot produce a pinned entry
with higher privilege than its inherited mode
### Phase 7 — LithosAnanke Parity (COMPLETE — independently verified)
There is no separate `lithosananke` branch to merge/port to (post-split, this repo's `master`
is the sole production line) — that framing is stale. Verified present directly in current
`master`: `acl_recheck()`/`zuse_session`/`emergency_console` wiring confirmed in
`src/starkernel/vm/vm_core.c`; the per-iteration `emergency_console = zuse_session ? 0 : 1`
assignment confirmed in `src/starkernel/repl.c`; the old `!vm->zuse_session` ACL-check bypass
confirmed absent from `src/vm.c`. See `.claude/CLAUDE.md`'s Word-Level ACL System section for
the full verification writeup.
- [x] Verify `ACL.4th` loads cleanly in kernel context (freestanding)
- [x] `ACL-BOOT` runs at kernel boot before first `BIRTH`
- [x] `vm->emergency_console` wired to kernel REPL active flag
- [x] `vm->zuse_session` wired to kernel Zuse console authentication path
- [x] Three-arch acceptance: amd64, aarch64, riscv64 boot to `ok>` with ACL
active and no regressions
- [x] Acceptance logs — this repo's standing convention commits every acceptance boot's serial
log under `logs/`, not a one-time Phase 7 action
### Phase 8 — PKI / Thumbdrive Authentication (FUTURE)
- [ ] Ed25519 challenge-response authentication for Zuse thumbdrive
- [ ] `tools/mkcapsule` embeds real Ed25519 CA key into `ACL.4th` Block 2066
at build time (replacing zero placeholder)
- [ ] User minting: admin creates thumbdrive with CA-signed certificate + home
block image
- [ ] Lose the drive → admin mints a new one; no software recovery path by
design
- [ ] `zuse.4th` becomes the bootstrapper that validates the physical drive
before granting Zuse session access