Skip to content

feat(harness): in-canvas Pause for the submit queue (plan #777) - #778

Merged
btipling merged 2 commits into
mainfrom
plan/queue-pause
Aug 22, 2026
Merged

feat(harness): in-canvas Pause for the submit queue (plan #777)#778
btipling merged 2 commits into
mainfrom
plan/queue-pause

Conversation

@btipling

Copy link
Copy Markdown
Owner

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. Pause holds 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_VERSION stays v20), no build.zig export-whitelist change, no wasm-int artifact rebuild. Cap-agnostic: paused is a boolean latch, MAX_ITEMS=16 / SUBMIT_CAP=262144 unchanged.

Goal Result
1 Pause (idle, incl. after Error/Stop) queue_paused latch + visible · paused TEAL state; FIFO unchanged
2 Next turn reads from the composer while paused Typed prompt + ▶/Ctrl+Enter sends that text; empty ▶ / empty Ctrl+Enter does not promote
3 Successful Ready does not auto-promote while paused tryPromoteQueuedcanPromote.paused blocks it; head stays
4 Unpause → drain by existing rules Resume clears the latch; next successful Ready / empty ▶ drains
5 Pause ≠ cancel/retry In-flight Busy / Stop / #756 retry path untouched; pause is a queue hold
6 Enqueue/edit/remove/Clear work while paused FIFO ops bypass the latch (tested)

Design (single seam)

The one promote primitive is bridge.tryPromoteQueued(editing)submit_queue.canPromote — the seam auto-promote (ui.zig shouldAutoPromote), idle empty-▶ (on_promote), and empty Ctrl+Enter already converge on. Adding paused to the canPromote args struct + a !args.paused guard blocks all three promote paths with one change while typed send (queueSubmitFromUi) bypasses it entirely. Auto-clear when the FIFO empties (mirrors shouldDropEditOnEmptyQueue) 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.zigcanPromote adds paused guard
  • native/harness/src/bridge.zigqueue_paused latch + isQueuePaused()/setQueuePausedFromUi() (pub fn, not export); wired into tryPromoteQueued; reset with queue lifecycle
  • native/harness/src/ui/queue_band.zig — header Pause/Resume toggle (n>0) + · paused label + shouldDropPauseOnEmptyQueue()
  • native/harness/src/ui.zig — empty-FIFO auto-clear of the latch
  • native/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: canPromote paused guard (pause holds even when every other gate is met; unpause re-arms)
  • bridge.test.zig (7 rows): defaults unpaused · set/read round-trip · tryPromoteQueued blocked 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_messages wipe the latch
  • queue_band.test.zig (3 rows): shouldDropPauseOnEmptyQueue truth table (paused+empty → true; not-paused / paused+non-empty → false)

Expected suite delta (baseline → with PR): bridge.test.zig 45→52, submit_queue.test.zig 16→17, queue_band.test.zig 69→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 new bridge rows.

Gates (all run in the cloud agent workspace)

Gate Result
zig build test-rich ✅ exit 0 (52 bridge / 17 submit_queue / 72 queue_band green, new rows confirmed)
zig build test-rich-invariants ✅ exit 0
zig build harness -Doptimize=Debug ✅ full Wasm compile, exit 0
zig fmt --check (touched .zig) ✅ clean
TS/vitest n/a — harness-only diff (no app//lib/ change); no new bridge export → no wasm-int artifact rebuild

Non-goals respected

No new bridge export / no build.zig whitelist 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 · paused state, auto-clears on empty FIFO. No DOM pause control (in-canvas queue-band chrome per feature-divide). No dual-chat regression.

Fixes #777

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).
@btipling btipling self-assigned this Aug 22, 2026
@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
invincible Ignored Ignored Aug 22, 2026 8:15am

Request Review

@btipling

Copy link
Copy Markdown
Owner Author

Implemented plan #777 (source #776) — in-canvas Pause for the Wasm submit queue.

Verification (cloud agent workspace, harness-only diff):

  • zig build test-rich ✅ exit 0 — new rows confirmed green: bridge 52 (7 pause rows), submit_queue 17 (paused guard), queue_band 72 (3 shouldDropPauseOnEmptyQueue)
  • zig build test-rich-invariants ✅ exit 0
  • zig build harness -Doptimize=Debug ✅ full Wasm compile, exit 0
  • zig fmt --check ✅ clean
  • No TS/vitest (no app//lib/ change); no new bridge export → no protocol bump (PROTOCOL_VERSION stays v20), no wasm-int artifact rebuild

Design: paused latch folded into submit_queue.canPromote via bridge.tryPromoteQueued (the single promote seam) holds every promote path — auto-promote on successful Ready + idle empty-▶ Play + empty Ctrl+Enter — while typed send and FIFO ops stay untouched. · paused TEAL toggle on the queue-band header; auto-clears when the FIFO empties; Wasm-ephemeral like the queue.

Stopped at merge-ready (no merge). Next: adversarial-review on this head, then an explicit merge request.

@btipling btipling left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial review — PR #778

Verdict: PASS WITH NOTES
Repo: btipling/invincible
Scope: mainplan/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.tryPromoteQueuedsubmit_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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

plan: pause the submit queue so the next turn reads from the composer

1 participant