diff --git a/docs/active-work.md b/docs/active-work.md index b7a2b47..f4e1e74 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -287,7 +287,12 @@ from #171 and #215. half (G1–G4), outbound family selection, inbound family gating, the GPU's negotiated family enum, and the nine family-gate rows (G6a–b, G7a–b, G8). **Full `--protocol` gate green at `3c06176`.** - In progress since, uncommitted: G5's accepted-gesture latch. + Then three more: G5a's trigger and its latch, the wheel exemption + with exhaustion and mapped coalescing, and the receiver rows. + **Every row this slice owns is now witnessed** — G0, G1–G4a, G5a plus + latch substrate, G6–G8, G9a–c, G10/G10a–c, G11a, G13a–b, G14a–b, G15 + — with G4b, G5b–e/g/i–p, G6c, G7c, G11b and G12a–b left to the replay + lane per §5b's split table. - **PROTOCOL-BEARING — v25, and it runs alone.** `ADVERTISED_PROTOCOL_VERSION` stays pinned at **20**. - **Why it exists.** A `PanelPointer` names a cell and nothing on the @@ -465,6 +470,35 @@ from #171 and #215. (2026-08-15, plain `cargo test --lib`, not a gate step). Isolated rerun green. Same budget as the occurrence above, now seen in two different selectors on one branch in one day. + - **Three wall-clock-deadline rows red in ONE run, under a MEASURED + foreign load** (2026-08-16, log `20260816T063330Z`). Steps + `04-lib-crdt` and `09-sweep-crdt`. Fragments: + `criterion_1_end_of_line_typing_completes_sub_frame_per_keystroke` + — "per-keystroke orchestrator time **1.001196ms** exceeds 1ms", + **0.12% over**, the smallest margin any budget on this lane has + failed by; `m6_1_pty_mode_lifecycle_started_then_exited` — "must + observe stdout 'done'"; and + `ctrl_c_on_launcher_group_does_not_reach_spawned_daemon` — "child + did not exit within 5s". All three green in isolation, the last in + **0.15s against its 5s deadline**, a 33x margin. + - **This is the first entry on this lane with a NAMED confound rather + than the standing uncontrolled one.** `uptime` during the run: + load average **14.02 → 28.35**, from an unrelated `turso` test + suite on the same machine (`./verify_task_state.sh + turso-without-rowid`, target dir `/opt/target`, one test binary at + **693% CPU**). It is not a controlled experiment, but it is the + same evidence U9's synthetic-load control was meant to produce, and + it points at load. U9 stays owed; its value is now lower. + - **Two process traps this cost, both worth carrying forward.** The + Bash tool caps a command at 10 minutes and SIGTERMs it, which the + gate reports as `FAILED (exit 143)` on whatever stage was running — + indistinguishable from a real failure in the summary line. Run the + protocol gate under `setsid` and watch the log. And `pkill -f + ` kills the invoking shell when the pattern appears in its + own command line, so the intended target survives and the operator + believes it died. **Both mistakes were made here**; the second + nearly killed an unrelated project's build, because + `pkill -f "cargo test"` would have matched it. Identify by PID. - **Three consecutive full-gate runs, three DIFFERENT unrelated failures** (composition budget in `04-lib-crdt`, then composition again in `10-sweep-crdt`, then this in `08-sweep`), against a diff diff --git a/docs/bottom-panel-framing.md b/docs/bottom-panel-framing.md index 685d3e5..a00da80 100644 --- a/docs/bottom-panel-framing.md +++ b/docs/bottom-panel-framing.md @@ -2497,6 +2497,47 @@ Two consequences are recorded rather than fixed: later and leaving that one out would be an inconsistency inside a single function rather than a clean deferral. +#### What the slice actually landed, row by row + +Recorded because the split table above states the *intent*, and a +reader auditing this later needs the *outcome* — including the two +places where writing the witnesses changed the design. + +**Landed here:** G0 (wire shapes, positions, field order, accumulated +pins), G1–G4a (the authoritative key, document and terminal halves), +G5a plus the latch substrate, G6a–b/G7a–b/G8a–e (both directions, all +four wrong-family quadrants, forged identity), G9a/G9b/G9c, G10 and +G10a–c, G11a, G13a–b, G14a–b, G15. + +**Still the replay lane's**, unchanged: G4b, G5b–e, G5g, G5i–p, G6c, +G7c, G11b, G12a–b. + +Two corrections the mutation pass forced, both of which had passed +their first review as written: + +- **G10a proved nothing about zero.** Asserted with a generation + already held, zero is also *lower* than it, so the nondecreasing + clause did the refusing and deleting the zero check left the row + green. Zero is only isolable before any authority exists — which is + also the case the framing names, a sender that never initialised the + field. Split into its own row with that setup. +- **G11a's exhaustion latch had no proven job.** The overflow path + already returns before storing the ceiling snapshot, so the next read + re-takes the changed arm and the band stays down without any latch. + Measured, the two are alternatives: only removing both resurrects the + band. The latch is kept, and now earns its place through `peek`, + which honours it so the peek and the authoritative read agree that an + exhausted session has no key rather than reporting the ceiling. + +One witness-shape note, since it recurs: the two G10b rows call +`panel_mapping_is_current` directly. A wheel has **no +dispatcher-visible effect on this base** — a document panel focuses on +`Down` only, and no panel pointer coordinate is consumed anywhere — so +a row asserting focus for a wheel would be green whatever the gate did. +Each row carries a press leg alongside, which does have an effect, to +show the predicate is wired into the production arm rather than merely +correct in isolation. + ### Coherence impact (`COHERENCE.md` §20) - **Journey steps touched: 5** (document-panel editing and selection) diff --git a/src/daemon.rs b/src/daemon.rs index 2a93ddf..d338222 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -7189,6 +7189,60 @@ mod tests { ); } + /// §5b — SUBSTRATE: a second accepted press ENDS the gesture it + /// replaces rather than overwriting it. + /// + /// Found by reading the arming path back, not by a failing row: a + /// dropped `Up` — one lost to a closed outbox under a stall — is + /// followed by the next press, and a plain overwrite discards the + /// first gesture's record without counting it. Inert on this base, + /// where records are only counted; once replay attaches a child + /// release to each record, the discarded one leaves a button held + /// down with nothing left to release it. + #[test] + fn g5_substrate_a_second_press_ends_the_gesture_it_replaces() { + let fid = FrontendId(777); + let (mut editor, mut semantic_states, mut render, _document, panel, epochs) = + panel_session_at(PROTOCOL_VERSION, fid); + let buffer_id = editor.core.borrow().windows[&panel].buffer_id; + let generation = stamped_generation(&semantic_states, fid); + let press = || mapped_pointer(fid, epochs, buffer_id, generation); + + dispatch_panel_event( + &mut editor, + fid, + PROTOCOL_VERSION, + &mut semantic_states, + &mut render, + press(), + ); + assert!(semantic_states[&fid].has_accepted_gesture()); + assert_eq!( + semantic_states[&fid].panel_gesture_cancellations(), + 0, + "fixture: the first press cancels nothing" + ); + + // The `Up` never arrives; the next press does. + dispatch_panel_event( + &mut editor, + fid, + PROTOCOL_VERSION, + &mut semantic_states, + &mut render, + press(), + ); + assert!( + semantic_states[&fid].has_accepted_gesture(), + "the new gesture is armed" + ); + assert_eq!( + semantic_states[&fid].panel_gesture_cancellations(), + 1, + "and the one it displaced was ENDED, not dropped on the floor" + ); + } + /// §5b — SUBSTRATE for the framing's G5p: per-frontend gesture /// ownership. /// diff --git a/src/semantic_render.rs b/src/semantic_render.rs index 286b972..2f429f4 100644 --- a/src/semantic_render.rs +++ b/src/semantic_render.rs @@ -728,6 +728,13 @@ impl SemanticRenderState { /// per §5b's split table, because the release it denies does not /// exist on this branch. pub fn arm_accepted_gesture(&mut self, gesture: AcceptedPanelGesture) { + // A second accepted press while one is already armed means the + // first gesture's release never arrived — a dropped `Up`, or an + // outbox that closed under a stall. END it rather than + // overwrite it: overwriting discards the record silently, and + // once replay attaches effects to that record the child is left + // holding a button down with nothing left to release it. + self.cancel_accepted_gesture(); self.accepted_gesture = Some(gesture); }