Stage O-B: Hera drains her own mail at the idle point, not on the ~1 s idle beat (FABRIC-3.9.0 §2.5)

- sk_vm_idle_point(may_drain_self): with no frame live and mail pending,
  Hera drains one message and returns to the poll loop. Readline passes
  n == 0 (the mid-edit gate), headless 1, the key read 0.
- Removed sk_repl_idle()'s timer-paced drain of Hera's queue and the two
  flags that guarded it (g_mama_interpreting, g_idle_pump_active) -- the
  idle point's frames check already covers that re-entry hazard.

Probe (reverted), all three ISAs: admission -> drain for mail to an idle
Hera 0-1 beats (was up to 100); a message arriving mid-edit is held until
the line is entered (2080-3587 beats), never printed into typed text; an
event queued during a busy line drains when it ends; BLK-ATTACH at boot 1
beat. Acceptance passes; parity, dict_hash set and relay ledger unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-24 22:33:33 -04:00
co-authored by Claude Opus 5.5
parent fe55038e9a
commit ee4c401d1c
16 changed files with 55062 additions and 65 deletions
+58 -1
View File
@@ -1,6 +1,6 @@
# FABRIC-3.9.8.md — Observers and participants: execution plan and log
**Status: IN PROGRESS.** Done: O-A, the stack-convention fix, S-1a, S-1b, S-1c, M-0, M-A, the dead-VM departure fix and M-B (§2). The design and
**Status: IN PROGRESS.** Done: O-A, the stack-convention fix, S-1a, S-1b, S-1c, M-0, M-A, the dead-VM departure fix, M-B and O-B (§2). The design and
its rulings (O-1 to O-4, the discard, and §7/§8's M- and S- rulings) are in `FABRIC-3.9.0.md`.
Each stage:
@@ -681,3 +681,60 @@ Evidence is named **before** each stage runs.
- The relay ledger shows `held=2048`: every event's heat was released.
- Idle log growth is 0, there are no faults, and QEMU exits cleanly. Clean builds on all
three ISAs plus the hosted build.
- [x] **Stage O-B — Hera acts on her own mail the moment she is idle (`FABRIC-3.9.0.md` §2.5).**
2026-09-24. Captain Bob: *"go ahead with O-B."*
**The change:**
- **`sk_vm_idle_point(int may_drain_self)`** (`switch.c`): when Hera is running with no frame
live and has mail, it drains one message, then returns to the caller's poll loop, which calls
it again.
- The existing `frames.count != 0` check already refuses whenever a line, word, `KEY`/
`EXPECT`/`QUERY` or drain of hers is in progress. So the drain can't re-enter her
interpreter mid-parse, which is the hazard the old flags guarded.
- **The callers** (`repl.c`):
- readline passes **`n == 0`**, keeping the mid-edit gate: a drained payload never prints
into a line being typed;
- the headless wait passes 1;
- the blocking key read passes 0, since a key read is someone mid-interaction, and it is
always inside a word anyway.
- **Removed:** `sk_repl_idle()`'s timer-paced drain of Hera's queue (one message per
`SK_IDLE_BEAT_INTERVAL` = 100 beats, about 1 s), and with it the two flags that guarded it
(`g_mama_interpreting`, `g_idle_pump_active`).
- Nothing else read them. The dispatch's set/restore of `g_mama_interpreting` is gone too.
- `sk_repl_idle()` keeps its USB and block-sync servicing, unchanged.
**Evidence.** A probe (reverted) stamped each message to Hera with the beat count at
admission, and printed the gap when she drained it. On the same boots, run from the console:
- `wa` defines `PINGH`, which sends `72 EMIT` to Hera. Both strings are built in `wa`'s own
`PAD`, because an `S"` can't nest inside the `S"` that carries the job.
- Jobs `: PZW 30000 0 DO LOOP PINGH ; PZW` (idle case) and `3000000` iterations (mid-edit
case).
Logs: `logs/20260924-213049/amd64/`, `-214100/aarch64/`, `-214803/riscv64/`.
| Case | amd64 | aarch64 | riscv64 |
|---|---|---|---|
| **Idle at the prompt**, 3 runs: admission → drain | **0, 0, 0 beats** | 0, 1, 0 | 1, 0, 0 |
| **Mid-edit:** the message arrives while `1 2` is typed but not entered; ` + .` Enter about 40 s later | drained only after the line ran (`3 ok`, then `H`), **3587 beats** | drained at the entered line's first word boundary (`H3 ok`), 2080 beats | same as aarch64, 2613 beats |
| **Busy:** `EV-BORN` queued while Hera runs `WORKER-BIRTH` | 14 beats, the moment the line ended | 1 | 0 |
| **`BLK-ATTACH`** (type 9) at boot, from Artemis | 1 beat | 1 | 1 |
- Before this stage, a message to an idle Hera waited for the next idle beat: up to 100
beats. Now it is 0–1.
- The gate held on every ISA: nothing drained while the line was being edited.
- Once Enter is pressed the message goes at the first clean boundary. That is either Hera's
existing in-word drain checkpoint (FABRIC-3.8 Stage B) at the entered line's first word
boundary, or the idle point after the line. Which comes first is timing, so the order of `H`
and `3` differs by ISA. Either way it is a line boundary, never inside typed text.
- The amd64 log also holds a first pass of the script whose job texts used a bare
`DO … LOOP` in interpret mode (`DO: compile-only`). That was a script error, corrected in the
same boot, and all the figures above come from the corrected pass.
**Acceptance** on the clean tree (`logs/20260924-215452/amd64/`, `-220741/aarch64/`,
`-222049/riscv64/`):
- Boot parity and the `dict_hash` set are unchanged from M-B (546 words,
`0x99b01134c3d3049c`).
- The O-A checks pass (40/40/48, `HHH`, `3` mid-stream).
- The relay and `BLK-ATTACH` flows are unchanged: mint → WIREBIND → `USE` → `3`, with the
ledger at the standard `held=2048 pulled=258048 returned=255263 consumed=737`.
- Idle log growth is 0, there are no faults, and QEMU exits cleanly.