fix(panel): a second press ends the gesture it replaces
Found by reading the arming path back after the G5 checkpoint, not by a failing row. `arm_accepted_gesture` overwrote an already-armed latch, so a dropped `Up` --- one lost to an outbox that closed under a stall --- was followed by the next press silently discarding the first gesture's record, without counting it as a cancellation. Inert on this base, where records are only counted. Once `panel-pointer-replay` attaches a child release to each record, the discarded one leaves a button held down with nothing left to release it, and the arming code that decides this is this slice's. Mutation: restore the plain overwrite -> the new row alone. Also records three CI-red observations from the slice-completion gate, which is the first entry on this lane with a MEASURED confound instead of the standing uncontrolled one. Three wall-clock-deadline rows red in one run --- criterion_1 by 0.12%, a PTY lifecycle race, and a 5s child -exit deadline --- all green in isolation, the last in 0.15s against its 5s deadline. `uptime` during the run went 14.02 -> 28.35, from an unrelated turso test suite on the same machine with one binary at 693% CPU. Not a controlled experiment, but the same evidence U9's synthetic -load control was meant to produce, and it points at load. Two process traps are recorded with them, because both were made here. The Bash tool caps a command at ten minutes and SIGTERMs it, which the gate reports as `FAILED (exit 143)` on whatever stage was running and which reads exactly like a real failure. And `pkill -f <pattern>` kills the invoking shell when the pattern appears in its own command line, so the intended target survives while the operator believes it died --- and here `pkill -f "cargo test"` would have destroyed an unrelated project's build. Identify by PID. Verified: `cargo fmt --check`; `cargo clippy --workspace --all-targets -- -D warnings`; the four §5b G5 rows. The full protocol gate follows on a quieter machine; the run described above is not evidence for this tree and is recorded as an observation only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
parent
419b078c0f
commit
bf4ffe0995
|
|
@ -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
|
||||
<pattern>` 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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue