Channel-open policy hook -- FABRIC-3.6.md task 3.7
Added HERMES-CHANNEL-OPEN? ( req-hi req-lo -- allow? ) at capsules/ACL.4th block 4008 (default: approve everything) -- the one word policy authors edit. sk_hermes_channel_open_policy(VM*, VMUuid) (kernel_hermes.h/.c) is the C-side query that calls it via plain word-dispatch against the target VM's own dictionary/stack, never vm_interpret() (avoids task 3.4's input-buffer cursor hazard entirely) and never decides the answer itself. Fails closed: no policy word, a policy error, or stack underflow all deny, matching CLAUDE.md's posture that absence of policy must never mean "always allow." Two real bugs found and fixed before this was called done: missing current_executing_entry assignment before calling the word's func pointer (colon words silently no-op without it, vm_core.c:730 -- no crash, just a wrong answer); and a second FAIL with debug instrumentation still in place whose precise cause isn't reconstructable, since no intermediate commit exists for that attempt. Self-test proves the task's check four ways against the same unchanged C function: default approve, live redefinition to deny (zero C change), restore, and a VM with no ACL.4th loaded at all (fail closed). A fifth check wires the result into task 3.6's sk_hermes_channel_respond() end to end: a denied policy produces a NACK and no channel, ledger/stadium_conserved() holding throughout. Scope, per Captain Bob's ruling: closes with the query built and proven; sk_hermes_channel_respond() still takes a caller-supplied approved bool rather than calling the policy internally. Wiring a real channel-open call site to only this query is deferred to whichever later task first needs a live decision. dict_hash identical across amd64/aarch64/riscv64 for every VM, zero UNKNOWN WORD, mkcapsule --lint clean (38 files, 0 violations). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
205a49ecd0
commit
f37aa0fb17
+61
-1
@@ -984,9 +984,69 @@ ruling]** cannot be written precisely until Captain Bob settles the named sub-it
|
||||
task touches no capsule). Artemis's own `dict_hash` differs run-to-run as already
|
||||
documented (task 3.1's own finding) -- shared `disk/artemis.img` state accumulated across
|
||||
this session's many prior boots, not an architecture divergence.
|
||||
- [ ] **3.7** — **Channel-open policy hook** **[needs ruling 3.0b]**. Kernel-Hermes asks
|
||||
- [x] **3.7** — **Channel-open policy hook** **[needs ruling 3.0b]**. Kernel-Hermes asks
|
||||
`ACL.4th`, never decides in C, never gates on `zuse_session`. *Check:* a denied open is
|
||||
denied by FORTH policy, with the C unchanged when the policy changes.
|
||||
2026-09-22 · `logs/20260922-075138/amd64/`, `logs/20260922-075649/aarch64/`,
|
||||
`logs/20260922-080212/riscv64/` (the successful runs; `logs/20260922-074119/amd64/` and
|
||||
`logs/20260922-074628/amd64/` are two genuine prior attempts that hit real bugs, caught by
|
||||
the self-test itself before this task was called done -- kept, not deleted, per the
|
||||
never-delete-logs convention) — all three reach `[zuse@Hera] ok>`, zero `UNKNOWN WORD`,
|
||||
`mkcapsule --lint capsules/` clean (38 files, 0 violations — one block added inside the
|
||||
existing `ACL.4th`, no file added). `dict_hash` identical across all three architectures
|
||||
for every VM (Hera `0xbb2b51e7595464ce`, Hermes `0xc95ef2d92fa0781f`, Hestia
|
||||
`0xfbde9fe105fd3b3d`, Artemis `0x7f18214489036b39`).
|
||||
|
||||
Added `HERMES-CHANNEL-OPEN? ( req-hi req-lo -- allow? )` at `capsules/ACL.4th` block 4008
|
||||
(default: approve everything) — the one word policy authors edit; `sk_hermes_channel_open_policy(VM *target_vm, VMUuid requester)`
|
||||
(`kernel_hermes.h`/`.c`) is the C-side query that calls it, and never itself decides. Uses
|
||||
plain word-dispatch (`vm_find_word()` + the entry's own `func` pointer) against the
|
||||
*target* VM's own dictionary and data stack, not `vm_interpret()` — deliberately, since
|
||||
`HERMES-CHANNEL-OPEN?` is a fixed known name, and this avoids task 3.4's input-buffer
|
||||
cursor-preservation hazard entirely (this touches the target's data stack only, never its
|
||||
input buffer). **Fails closed, not open**: no policy word present (e.g. a VM that never
|
||||
loaded `ACL.4th`), a policy-word error, or stack underflow all return "denied," matching
|
||||
`.claude/CLAUDE.md`'s own posture that absence of policy must never mean "always allow."
|
||||
A broken policy word's own error state is cleared on the target VM before returning, so it
|
||||
cannot leak into whatever else that VM is doing.
|
||||
|
||||
**Two real bugs found and fixed before this was called done, not a clean first pass:**
|
||||
1. (`logs/20260922-074119/`) The first draft called `entry->func(target_vm)` directly
|
||||
without setting `target_vm->current_executing_entry` first — colon words dispatch
|
||||
through `execute_colon_word()`, which reads its own body address from that field and
|
||||
silently no-ops if it's `NULL` (`vm_core.c:730`). Every call silently did nothing,
|
||||
leaving the pushed requester args untouched on the stack rather than erroring — no
|
||||
crash, no obvious symptom, just a wrong answer, which is why it needed the self-test
|
||||
(not a crash) to catch. Fixed by setting `current_executing_entry` before the call and
|
||||
restoring the caller's own value after (in case the target VM is ever called into
|
||||
mid-dispatch elsewhere later).
|
||||
2. (`logs/20260922-074628/`) Still `FAIL`, with debug instrumentation left in from
|
||||
diagnosing bug 1 (`entry=... func=...`, `error=0 dsp=1`/`error=0 dsp=2` printed, then
|
||||
`FAIL`). **No intermediate commit exists for this attempt, so its precise defect is
|
||||
not reconstructable now** — the log records the symptom and nothing more. Debug prints
|
||||
were removed; the third attempt (`075138`/`075649`/`080212`) passed clean on all three
|
||||
architectures with no debug output.
|
||||
|
||||
Self-test in `kernel_main.c` proves the task's check four ways against the **same
|
||||
unchanged C function**: (a) Hera's default `HERMES-CHANNEL-OPEN?` approves; (b)
|
||||
redefining that same word live on Hera to deny flips the answer with zero C change; (c)
|
||||
restoring the approve-default flips it back; (d) Hermes — who never loads `ACL.4th` at
|
||||
all (grep-confirmed: only `init.4th`/`ACL.4th`/`zuse.4th`/`block-acl.4th` reference it) —
|
||||
is correctly refused closed, not silently approved, proving the fail-closed behaviour on a
|
||||
VM with no policy at all. A fifth check wires the policy result straight into task 3.6's
|
||||
`sk_hermes_channel_respond()` end to end: a denied policy really produces a NACK and no
|
||||
channel, with the ledger audit and `stadium_conserved()` holding before and after —
|
||||
exactly task 3.6's own deny-path shape, reused rather than re-derived.
|
||||
|
||||
**Not yet done, by scope**: the actual call site inside `sk_hermes_channel_respond()`'s
|
||||
caller still takes a plain `approved` bool (task 3.6's own signature, unchanged, as
|
||||
promised) — this task builds and proves the query function itself; wiring it as the
|
||||
*only* source of that bool at a real call site is deferred to whichever later task first
|
||||
needs a live (not self-test-only) channel-open decision, consistent with every other
|
||||
Phase 3 task before Stage C being self-test-only. `dict_hash` for Hermes/Hestia unmoved
|
||||
from task 3.6's values, as expected (neither VM loads `ACL.4th`'s changed block); Hera's
|
||||
own hash moved (she does) and Artemis's tracks the same disk-state artifact already
|
||||
documented at task 3.1.
|
||||
- [ ] **3.8** — **Stage C: cut over `BLK-ATTACH-EVENT` alone** (§XXXIV.3). One layer owns it;
|
||||
FORTH Hermes still routes every other type. *Check:* the real Hera↔Artemis attach path
|
||||
works end to end on all three ISAs; ledger and `stadium_conserved()` true before/after;
|
||||
|
||||
Reference in New Issue
Block a user