diff --git a/FABRIC-3.md b/FABRIC-3.md index d39e21e..7f80d14 100644 --- a/FABRIC-3.md +++ b/FABRIC-3.md @@ -160,9 +160,11 @@ decisions get added here, not to `FABRIC-2.md`. Follow the same discipline `FABR as a `compudynamics.c`-driven decision generically, not a `physics_hotwords_cache.c` (`DictEntry*`-hardcoded) reuse question specifically. -- [ ] Decide and implement unclean-removal handling (Section U's explicitly flagged open - question — never answered) — at minimum, detect a mid-flush disconnect via Milestone 2e's - disconnect signal and decide what state that leaves affected blocks in. +- [x] **CLOSED 2026-08-27 (`FABRIC-3.md` §F.10), scoped alongside `DETACH`.** Unclean removal + runs the exact same VM-kill path `EJECT` (M5) uses, minus the flush step — skipped outright + since the device is confirmed gone by the time the existing `bot_msc_detach_pending` signal + fires, not attempted-and-ignored. Design done; the actual wiring into `sk_repl_idle()`'s + existing detach handling is not yet written. ### From FABRIC-2.md §X, Milestone 4 — Drive/credential security @@ -687,7 +689,11 @@ decisions get added here, not to `FABRIC-2.md`. Follow the same discipline `FABR already-working, currently-unguarded retarget word — check re-verifies the attached drive live on every call; `installed=0` VMs stay freely targetable. -- [ ] Implement detach behavior on console disconnect or VM teardown. +- [ ] Implement detach behavior on console disconnect or VM teardown. **SCOPED 2026-08-27 + (`FABRIC-3.md` §F.10)**: a new `EJECT` word — flush via the existing `blk_vm_flush_all()`, + reset the console's active VM if bound to the departing VM, then `capsule_vm_kill()` (already + complete, no new teardown mechanism needed). Needs one small new piece of state `WIREBIND` + must own: which `VMUuid` is currently attached via the home-blocks USB path. ### From FABRIC-2.md §X, Milestone 6 — Kernel/capsule PKI signing chain @@ -1425,12 +1431,12 @@ graph TD BMAPWRITE["❌ Write block-map to drive (M3)"] BMAPREAD["❌ Read/validate block-map on insert (M3)"] MIGSM["🟡 Migration state machine (M3)
partially answered: ACL decides *when*"] - UNCLEAN["❌ Unclean-removal handling (M3)"] + UNCLEAN["✅ Unclean-removal handling (M3)
CLOSED 2026-08-27 (§F.10) — same kill path as EJECT, minus flush"] ACLKEY["❓ ACL 'bumps and holes' comparison
semantics — SAME GAP in M5 and D.4"] WIREBIND["❌ Wire hotplug → capsule_birth_baby() (M5)"] BINDSTEP["❌ Extend sk_repl_set_active_vm()
with key-match (M5) — SCOPED 2026-08-27 (§F.9)"] - DETACH["❌ Detach behavior (M5)"] + DETACH["❌ Detach behavior / new EJECT word (M5)
SCOPED 2026-08-27 (§F.10)"] EXPIRE["✅ EXPIRE reframed: session end
= VM detach via COOL (§B + D.2)"] RUNCAP["❌ Runtime capsule construction
from thumbdrive content — SCOPED 2026-08-27 (§F.6)"] @@ -1466,6 +1472,7 @@ graph TD ACLKEY --> BINDSTEP WIREBIND --> BINDSTEP BINDSTEP --> DETACH + UNCLEAN -.->|same kill path, no flush| DETACH DETACH -->|triggers| EXPIRE MSGSHAPE --> MSGMIGRATE @@ -1479,8 +1486,8 @@ graph TD classDef unblocked fill:#a83,stroke:#333,color:#fff classDef open fill:#666,stroke:#333,color:#fff classDef partial fill:#883,stroke:#333,color:#fff - class M6,PH8,EXPIRE,MSGSHAPE,HOTPLUG done - class W10,STALL,FIRSTTOUCH,BMAPWRITE,BMAPREAD,UNCLEAN,WIREBIND,BINDSTEP,DETACH,MINT,BMAPFMT,CERTVERIFY,RUNCAP blocked + class M6,PH8,EXPIRE,MSGSHAPE,HOTPLUG,UNCLEAN done + class W10,STALL,FIRSTTOUCH,BMAPWRITE,BMAPREAD,WIREBIND,BINDSTEP,DETACH,MINT,BMAPFMT,CERTVERIFY,RUNCAP blocked class ACLKEY,MSGMIGRATE,SSDSCOPE,ROUNDTRIP,POLYBLOCK open class MIGSM partial ``` @@ -1526,6 +1533,14 @@ finished). Dashed arrows = softer "gates/informs" relationships. (`mama_forth_words.c`) is a real, working, currently-unguarded retarget word. The only genuinely new code this node needs is a small cross-file accessor for the currently-attached device (mirroring `sk_repl_get_active_vm()`'s own precedent) plus the comparison itself. +- **`UNCLEAN` (M3) and `DETACH` (M5) were the same node wearing two names, in a different way + than `ACLKEY` was** (§F.10) — not identical questions asked twice, but one shared teardown + path (`capsule_vm_kill()`, already complete) forked by whether a flush is physically possible. + Scoping them together closed a long-open M3 item as a side effect of an M5 pass. +- **Found a real, pre-existing, independent bug while tracing `DETACH`** (§F.10): plain `KILL` + today never resets `g_repl_active_vm`, so killing the VM a console is currently `USE`'d onto + leaves a dangling pointer. Reported, not fixed — the new `EJECT`/hot-unplug call sites handle + this correctly, but the existing `KILL` word's own call sites do not. **Not yet done:** an ordered plan (which node to attack first, given the graph). Per Captain Bob's own framing, that's the next pass — "start asking and answering questions iteratively @@ -1924,6 +1939,70 @@ refusal message text (matching `USE`'s existing console-message style); whether this — `capsule_parity_log_birth_failed()` and friends, `parity.c` — but nothing says a security-relevant refusal here needs the same treatment; not decided). +### F.10 — `DETACH` (the last M5 node) — and `UNCLEAN` closed alongside it + +Traced `capsule_vm_kill()` (`capsule_birth.c:295-364`) fully before scoping, per D.2's own +framing that detach should reuse it: it's real and complete — evicts the Stadium patron cell +via `stadium_evict()`, `vm_cleanup()`+`sf_free()`s the VM, marks it `DEAD`, logs via +`capsule_parity_log_kill()`, is idempotent, and refuses to kill Hera. `DETACH` doesn't need a +new teardown mechanism, only a caller for this one plus the flush/bookkeeping around it. + +**Real finding: D.3's "flush before eject" only makes physical sense as a deliberate +pre-removal step — once a drive is physically pulled, there's nothing left to write to.** +That splits this into two genuinely different paths, and per direct instruction this pass +scopes **both**, closing the previously-separate, long-open `UNCLEAN` node (M3) at the same +time rather than leaving it for later: + +- **Graceful path (new `EJECT` word, no name conflict found):** the drive is still physically + present. Flush, then kill, then it's safe to remove. +- **Abrupt path (the existing `bot_msc_detach_pending` hot-unplug signal, already wired and + consumed in `sk_repl_idle()` for `blk_subsys_detach_device()`):** the drive is already gone. + No flush is attempted — data since the last flush is lost, which *is* correct unclean-removal + semantics, matching what every real OS does on a surprise USB yank. This is `UNCLEAN`'s + entire remaining scope, now closed as "same kill path as `EJECT`, minus the flush attempt." + +**One genuinely new, small piece of state required for both paths** (not a new mechanism, a +missing link): a way to know *which VM* corresponds to the currently home-blocks-attached +drive. `BINDSTEP`'s live re-verify-and-compare approach (§F.9) only works while the drive is +still physically present and readable — useless for the abrupt path, where the device is +already gone by the time the signal fires. Decided: `WIREBIND` (§F.5, itself not yet built) +must set a small tracked `VMUuid` — "the VM currently attached via the home-blocks USB path" — +at successful birth; both `EJECT` and the hot-unplug handler read and clear it. No new registry +machinery needed beyond this: `capsule_vm_registry_get(vm_id, &entry)` (already exists) resolves +the `VMUuid` to a live entry, and `capsule_vm_kill(entry.name)` (already exists, takes a name +not an ID — chain the two rather than adding a kill-by-id variant) does the rest. + +**Decisions made 2026-08-27:** + +1. **`EJECT` (graceful):** resolve the tracked attached-VM id → entry; `blk_vm_flush_all(vm)` + (already exists, same function `sk_repl_idle()`'s own periodic sync already calls) while the + VM is still alive; if the console's active VM (`sk_repl_get_active_vm()`) is this same VM, + reset it to Hera (`sk_repl_set_active_vm(NULL)`) *before* teardown — necessary, not + optional, see the dangling-pointer finding below; then `capsule_vm_kill(entry.name)`; clear + the tracked attached-VM state. `capsule_vm_kill()`'s own idempotency covers the case where + the VM was already gone by some other path — no extra guard needed. +2. **Hot-unplug (`UNCLEAN`, closed):** identical steps minus the flush — skipped outright, not + attempted-and-ignored-on-failure, since the device is confirmed gone by the time this signal + fires. Same active-VM reset-before-kill step applies. +3. **`W10`'s edge to `DETACH` in the §E graph is specifically about the `EJECT` path's flush + step** — the hot-unplug/`UNCLEAN` path never touches `WRITE(10)` at all (nothing is written + during an unclean removal), so `UNCLEAN` itself is *not* blocked on `WRITE(10)`, only + `EJECT` is. Worth keeping distinct now that both share one node. + +**Separate, already-existing bug found while tracing this, reported per standing instruction +(not fixed):** `capsule_vm_kill()` never touches `g_repl_active_vm`. Today, plain `KILL` on a +VM the console is currently `USE`'d onto leaves `g_repl_active_vm` dangling — the REPL would +fault on the next command dispatched through it. This is real and reachable *today*, +independent of `DETACH`/`EJECT`; decision 1 above only fixes it for the new `EJECT`/hot-unplug +call sites, not the pre-existing plain `KILL` word. + +**Not yet scoped (deferred within this node):** the exact new tracked-state variable's +name/location (logically `WIREBIND`'s to own, but `WIREBIND` itself isn't built yet); whether +`EJECT` needs a name argument or always targets "whatever's currently attached" (leaning toward +the latter, given the single-USB-device constraint confirmed in `F.8` means there's never more +than one candidate — not committed here); the console message text distinguishing a graceful +eject from an unclean one in the transcript. + ### D.5 — Scope expansion (2026-08-27): identity is common to every VM, not just users Surfaced while scoping `ACLKEY`, stated directly: *"the whole object is to deliver a