feat(harness): in-canvas Pause for the submit queue (plan #777) - #778
Conversation
After a turn error, the operator wants to continue the failed work from the composer without the next send consuming a parked queue item. Add an operator-initiated Pause latch to the Wasm submit queue so the next turn reads directly from the composer; parked items stay in the FIFO and only drain after the operator unpauses. Wasm-internal only: no new bridge export, no protocol bump, no build.zig whitelist change, no cap change, no wasm-int artifact rebuild. - submit_queue.zig: add `paused: bool` to the `canPromote` args struct + a `!args.paused` guard. `canPromote` is the one seam auto-promote, idle empty-▶ Play and empty Ctrl+Enter converge on via `bridge.tryPromoteQueued`, so one guard blocks goals 2 and 3 while typed send stays untouched. - bridge.zig: `queue_paused` latch + `isQueuePaused()`/`setQueuePausedFromUi()` (pub fn, not export); pass the live read into `tryPromoteQueued`'s `canPromote` args; reset to false on `reset()` and `inv_clear_messages()` (Wasm-ephemeral). - ui/queue_band.zig: `shouldDropPauseOnEmptyQueue()` predicate (mirrors `shouldDropEditOnEmptyQueue`) + header Pause/Resume toggle (visible when n>0) with a TEAL-accent `· paused` label. Toggle only flips the latch — it never cancels an in-flight turn or a row edit (pause is a hold, not danger; EMBER never). - ui.zig: auto-clear `queue_paused` the frame the FIFO empties (drain / Clear / remove), so a stale pause can never block later promotes. - Tests: canPromote paused guard (submit_queue), tryPromoteQueued blocked while paused / auto-promote held / unpause restores / FIFO ops unaffected during pause / reset+clear wipe the latch (bridge), shouldDropPauseOnEmptyQueue truth table (queue_band). - Living docs: docs/harness-limits.md Submit queue **Pause** row + AGENTS.md behavior one-liner (protocol stays v20, no export/cap). Gates: zig build test-rich (exit 0), test-rich-invariants (exit 0), harness -Doptimize=Debug (exit 0), zig fmt --check clean. No TS/vitest (harness-only).
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
Implemented plan #777 (source #776) — in-canvas Pause for the Wasm submit queue. Verification (cloud agent workspace, harness-only diff):
Design: Stopped at merge-ready (no merge). Next: |
btipling
left a comment
There was a problem hiding this comment.
Adversarial review — PR #778
Verdict: PASS WITH NOTES
Repo: btipling/invincible
Scope: main ← plan/queue-pause · 9 files · in-canvas submit-queue Pause latch (plan #777, source #776)
HEAD: ba087f34c861430a53ba9eb62c367e24497a1abf
Lenses run: L1, L3, L4, L6, L8, L9 (skip: L2 no secrets/API/workflows; L5 no poll/alloc/history growth; L7 no clone bind)
AGENTS.md read: yes · docs/feature-divide.md read (in-canvas queue-band chrome; no DOM pause control) — divide holds
Attack assumed the pause latch was a no-op or leaked a promote path. It is not: the only promote primitive is bridge.tryPromoteQueued → submit_queue.canPromote, and this PR adds .paused = queue_paused there. Idle empty-▶ (on_promote) and empty Ctrl+Enter already converge on that call; typed send is queueSubmitFromUi and never consults canPromote. shouldAutoPromote can still fire on a successful Ready while paused; tryPromoteQueued then refuses the pop (goal 3 test). Stop / #756 retry / Continue-at-head are untouched. FIFO ops (enqueue/edit/remove/Clear) do not read the latch. reset() / inv_clear_messages wipe it. Empty-FIFO auto-clear (shouldDropPauseOnEmptyQueue) prevents a stale hold after Clear/remove-last. No new export, protocol stays v20, no cap change. Palette is TEAL (teal_accent / teal_surface); EMBER is not used. Feature-divide one-liner: a multi-turn chat still completes from the Wasm composer; pause is queue-band chrome, not a React control.
No Blocker or Major survived self-refutation on this SHA.
Findings
| Sev | Lens | Finding | Break scenario | Refutation attempt | Confidence |
|---|---|---|---|---|---|
| Nit | L8 | Submit queue When it runs still says the head starts on idle empty-▶ / empty Ctrl+Enter with no pause carve-out. The new Pause row is the next cell, but that sentence is the promote contract from #760. | Operator reads only “When it runs”, taps empty ▶ while paused, nothing happens, thinks Play is broken. | Defender: “Pause row is immediately below.” Then the When-it-runs cell should say “unless paused.” | high |
| Nit | L8 | Living docs this PR added (docs/harness-limits.md Pause row; AGENTS.md v20 cell) name plan #777. The plan’s living-docs lock was timeless / no phase-issue artifacts. |
Next reader treats the row as archaeology. | Defender: “one plan id.” The rest of the Submit-queue table is timeless. | high |
Residual risk
canPromote.paused defaults false (fail-open if a future caller omits the field). Production has one caller (tryPromoteQueued) and it passes queue_paused. Header adds a 64 px Pause + 56 px Clear + optional · paused / · full on a TOUCH_H=40 row; at ~390 px the {n} queued label has expand = .horizontal and should shrink, but there is no chrome layout test for the new button. Unpause does not immediately promote (next successful Ready or empty-▶) — documented. Pause while Busy is allowed (n>0); it does not cancel the in-flight turn. docs/feature-divide.md still says bridge protocol v19 (pre-existing, out of this plan). Live operator smoke of pause → typed continue → success → held → Resume → empty-▶ was not run here. zig → harness.wasm is pending on this SHA (32561341144).
Merge guidance
PASS WITH NOTES. Safe to merge from this attack; the leftover Nits are docs wording, not a promote leak. Do not --approve from this skill.
What was not attacked
Live DO runner beyond the pending build-harness; in-browser Pause at 390 px; host auto-continue vs a paused non-empty queue (still gated on queuedCount() > 0); keymap chord for pause (none planned); help overlay copy.
What
Add an operator-initiated Pause latch to the Wasm submit queue (plan #777, source #776). After a turn error the operator wants to continue the failed work from the composer — a typed correction / smaller retry — without the next send consuming a parked queue item.
Pauseholds every promote path (auto-promote on a successful Ready + idle empty-▶ Play + empty Ctrl+Enter), while the FIFO contents and the typed-send path stay untouched: the next turn reads directly from the composer. The operator flips a visible in-canvas Pause/Resume toggle in the queue band to unpause; only then do parked items drain by the existing rules.Wasm-internal only — no new bridge export, no protocol bump (
PROTOCOL_VERSIONstays v20), nobuild.zigexport-whitelist change, no wasm-int artifact rebuild. Cap-agnostic:pausedis a boolean latch,MAX_ITEMS=16 /SUBMIT_CAP=262144 unchanged.queue_pausedlatch + visible· pausedTEAL state; FIFO unchangedtryPromoteQueued→canPromote.pausedblocks it; head staysResumeclears the latch; next successful Ready / empty ▶ drainsDesign (single seam)
The one promote primitive is
bridge.tryPromoteQueued(editing)→submit_queue.canPromote— the seam auto-promote (ui.zigshouldAutoPromote), idle empty-▶ (on_promote), and empty Ctrl+Enter already converge on. Addingpausedto thecanPromoteargs struct + a!args.pausedguard blocks all three promote paths with one change while typed send (queueSubmitFromUi) bypasses it entirely. Auto-clear when the FIFO empties (mirrorsshouldDropEditOnEmptyQueue) so a stale hold can never block later promotes. Same host-testable seam family as the v19/v20 promote-gate locks.Files
native/harness/src/submit_queue.zig—canPromoteaddspausedguardnative/harness/src/bridge.zig—queue_pausedlatch +isQueuePaused()/setQueuePausedFromUi()(pub fn, not export); wired intotryPromoteQueued; reset with queue lifecyclenative/harness/src/ui/queue_band.zig— header Pause/Resume toggle (n>0) +· pausedlabel +shouldDropPauseOnEmptyQueue()native/harness/src/ui.zig— empty-FIFO auto-clear of the latchnative/harness/src/{submit_queue,bridge,queue_band}.test.zig— new rows (below)docs/harness-limits.md,AGENTS.md— timeless Pause rows (protocol stays v20, no export/cap)Tests
Zig host-unit additions (pure suites, run via
zig build test-rich):submit_queue.test.zig:canPromotepaused guard (pause holds even when every other gate is met; unpause re-arms)bridge.test.zig(7 rows): defaults unpaused · set/read round-trip ·tryPromoteQueuedblocked while paused (non-empty, all gates otherwise met) · auto-promote held after successful Ready (goal 3) · unpause restores drain (goal 4) · FIFO ops (enqueue/edit/remove/Clear) keep working while paused (goal 6) ·reset()+inv_clear_messageswipe the latchqueue_band.test.zig(3 rows):shouldDropPauseOnEmptyQueuetruth table (paused+empty → true; not-paused / paused+non-empty → false)Expected suite delta (baseline → with PR):
bridge.test.zig45→52,submit_queue.test.zig16→17,queue_band.test.zig69→72. The dvui-layout chrome tests (composer_layout, etc.) are unchanged — the pause no-op lives at the bridge seam they mirror, covered by the newbridgerows.Gates (all run in the cloud agent workspace)
zig build test-richzig build test-rich-invariantszig build harness -Doptimize=Debugzig fmt --check(touched .zig)app//lib/change); no new bridge export → no wasm-int artifact rebuildNon-goals respected
No new bridge export / no
build.zigwhitelist change / no protocol bump / no cap change. Pause is a queue hold, not a user-confirmation gate (standing no-user-gates stance) — optional operator hold, transparent· pausedstate, auto-clears on empty FIFO. No DOM pause control (in-canvas queue-band chrome per feature-divide). No dual-chat regression.Fixes #777