Skip to content

F21 (#815): persisted backend submit queue mirror + reload re-arm + bounded drain - #901

Merged
btipling merged 9 commits into
mainfrom
f21/persisted-queue-drain
Aug 31, 2026
Merged

F21 (#815): persisted backend submit queue mirror + reload re-arm + bounded drain#901
btipling merged 9 commits into
mainfrom
f21/persisted-queue-drain

Conversation

@btipling

Copy link
Copy Markdown
Owner

backend-agents F21 — persisted backend submit queue + drain

Plan #815 · parent umbrella #794 · branch f21/persisted-queue-drain

Ships F21 as-built under the operator's ship-it directive (2026-08-30): the Wasm/protocol surface is untouched — no new inv_* export, no Zig change, no cap change. The runtime Wasm submit FIFO (v18 depth / v20 insert-front / v21 preserve / promote gate) stays exactly as-is; this PR adds what the host can own without touching it: the persisted mirror, reload re-arm, and a bounded drop budget for failed drain starts. The plan's enqueue-visibility question is resolved as a documented residual on #815 (living comment), not a gate.

What landed

Piece Where Notes
Persisted mirror helpers lib/turnQueue.ts (new) sanitizeQueue (fail-closed: drop blanks/over-cap items, depth cap, empty = unset), queueAppend, removeQueuedText (first-match), queueRestoreHead (defer path), queueClear, rearmQueueFromMirror (reverse-order v20 inserts, only when the Wasm queue is empty, stop on any insert reject)
Mirror carrier SessionSnapshot.queue?: string[] Rides the existing transcript blob — localStorage JSON locally, transcript-object body on the envelope+Blob carrier (trimForCloudPut folds it, parseCloudSessionSnapshot restores it sanitized, overlayEnvelopeMeta deliberately leaves it alone — meta is not its carrier). Never a reserved meta key, no new route, no new server surface
Submit seam app/harness/HarnessHost.tsx runPrompt Submit-while-live appends to the mirror before the turn runs; the same call's reconcile removes it when the durable start is reached — result.ok OR the failure blended x-workflow-run-id (the post-headers accepted-POST blend). Keying removal on the durable start (not the terminal) means a crash between accept and terminal can never double-run a drained prompt on reload
Drain failure semantics same reconcile Failed start before any durable begin (pre-header network / 5xx / subscribe-fail / 409 / 429) → restore the head (queueRestoreHead persist + Wasm queuedInsertFront) and count a failed attempt; give-up at TURN_QUEUE_DRAIN_MAX_ATTEMPTS = 5 drops the item with a painted Error row, never silent
Reload hydration hydrateRingWindow After every ring rebuild, re-arms the Wasm FIFO from the mirror (reverse-order queuedInsertFront); skips when the Wasm queue is non-empty (never double-enqueues); fail-closed on insert reject (items stay in the mirror). inv_queued_count parity holds; removal keying makes reload-never-double-STARTS hold too
Caps lib/turnQueue.ts TURN_QUEUE_MAX_ITEMS = 16 (exact parity with Wasm submit_queue.MAX_ITEMS), TURN_QUEUE_TEXT_MAX_CHARS = 5,000 (the queue is for small follow-up prompts — big prompts belong on the transcript per the #794 post-mortem), TURN_QUEUE_DRAIN_MAX_ATTEMPTS = 5 (new, generous; no existing cap raised/lowered)

Explicitly not changed

  • native/harness/** (Wasm/Zig), bridge protocol, all inv_* exports.
  • /api/turns route and its 401/400/429/409/403/503 gates; drain still POSTs through the same runPromptrunHarnessTurnsendTurnStream funnel the composer uses (≤1 child in flight by construction — the drain IS a normal host submit).
  • Reserved meta, C15/D17/E19/E20 paths, queue_paused/promote-gate semantics.

Residuals (documented on #815, not blockers)

  1. Wasm-internal band enqueues (enqueueFromUi, band edit/remove/Clear) are not host-observable without an additive export (protocol v22); they drain at runtime but are not crash-safe persisted. Fix path (one additive REQUIRED export, v18/v19/v20/v21 precedent) is written up on the living comment.
  2. The legacy roll-forward one-shot PUT /api/sessions/:id drops unknown body fields, so the cloud mirror rides the envelope+Blob carrier (and localStorage), not the roll-forward record.

Gates

  • vitest default project: 2430/2430 green (includes the new lib/turnQueue.test.ts + F21 rows in lib/sessionRepository.test.ts).
  • npm run typecheck: clean.

Closes #815 (with the enqueue-visibility residual recorded on the issue for a future protocol-v22 follow-up).

…ounded drain

backend-agents F21 — persisted backend submit queue + drain, as-built under the
operator ship-it override (Wasm untouched, no protocol bump, no new inv_* export).

- lib/turnQueue.ts (new): sanitize/append/remove/restore-head/clear helpers for
  the persisted mirror + reverse-order rearmQueueFromMirror (never double-enqueues;
  fail-closed on insert reject) + caps: TURN_QUEUE_MAX_ITEMS=16 (Wasm MAX_ITEMS
  parity), TURN_QUEUE_TEXT_MAX_CHARS=5000 (small prompts are the product case),
  TURN_QUEUE_DRAIN_MAX_ATTEMPTS=5 (drop-with-paint budget).
- SessionSnapshot.queue?: string[] — the mirror rides the EXISTING transcript blob
  (localStorage JSON locally; transcript-object body on the envelope+Blob carrier).
  Never a reserved meta key; no new route/server surface. Sanitized fail-closed on
  every read (trimForCloudPut fold, parseCloudSessionSnapshot restore, local load).
- HarnessHost: submit-while-live appends to the mirror; the same runPrompt call's
  reconcile removes it when the durable start is reached (result.ok OR the failure
  blended x-workflow-run-id — crash between accept and terminal cannot double-run a
  drained prompt on reload). Failed starts restore the head (persist + Wasm band
  queuedInsertFront) and count attempts; give-up drops with a painted Error row.
  hydrateRingWindow re-arms the FIFO from the mirror after ring rebuilds.
- Known residual (documented on the plan issue): Wasm-internal band enqueues are
  not host-observable without a protocol bump; they drain at runtime but are not
  crash-safe persisted. Roll-forward one-shot PUT drops unknown body fields, so the
  cloud mirror rides the envelope+Blob carrier.

Gates: vitest default project 2430/2430 green; tsc --noEmit clean.

Plan: #815 (backend-agents F21). Parent umbrella: #794.
@vercel

vercel Bot commented Aug 30, 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 31, 2026 5:10am

Request Review

@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 #901

Verdict: CONCERNS
Repo: btipling/invincible
Scope: mainf21/persisted-queue-drain · 6 files · persisted submit-queue mirror + reload re-arm
Lenses run: L1, L2, L3, L5, L6, L8 (skip L4/L7/L9: no CI/workflow/deploy, no reusability bind, no palette/layout)
AGENTS.md read: yes (feature-divide yes — host/bridge/agent loop; SECURITY.md N/A — no workflow/secret/runner/API route)

Wasm-internal enqueue residual is not a finding (documented operator override on #815). Attack is against the as-built contract this PR actually claims.

Findings

Sev Lens Finding Break scenario Refutation attempt Confidence
Major L1 app/harness/HarnessHost.tsx runPrompt reconcile strips the drained prompt from session.queue only after runHarnessTurn returns. Durable start already happened: lib/harnessChat.ts onTurnStartedapplyCtx.patchSessiononSessionPatchpersistTurn writes turnRunId + running with the in-flight prompt still in the mirror. The PR lock ("keying removal on the durable start, not the terminal") is not implemented. 1. Host-known follow-up B is in the mirror. 2. Drain-start POST of B is accepted (x-workflow-run-id set, onTurnStarted persisted). 3. F5 before the stream done. 4. hydrateRingWindow re-arms B into the Wasm FIFO; kickColdAttach resumes the live run. 5. That run completes → v19 auto-promote pops B → poll POSTs B again. C15 409 does not apply (first run already finished). Tools/sandbox writes re-run. "Removal after result.ok || resultRunId is keying on durable start." That check runs after the terminal of runHarnessTurn, not at header-accept. Mid-turn onSessionPatch already saved the unstripped queue. No test locks "crash between accept and terminal cannot double-run." high
Major L1 Transcript-blob carrier is not copy-forwarded by worker persist. lib/agent/turnPersistSeam.ts buildThisRunChunk emits {id, updatedAt, messages, prev?, depth?} and drops queue. Cloud GET reconstructs from that head (flattenReconstructedBody spreads the worker chunk). mergeAdoptedUsage is { ...server, usage: server.usage ?? local.usage } — server wins. 1. Cloud session, follow-up B persisted on the host PUT blob. 2. Worker B7 writes a this-run chunk (no queue), advances meta.transcriptPointer. 3. F5. 4. Local first-paint re-arms from localStorage. 5. bootCloudSession onAdopt (fresh heap, inflightRef false) hydrates the worker snapshot without queue and wipes the mirror. Band empty. "Host terminal PUT after the turn re-uploads the full snapshot with queue; idle F5 is fine." True after a completed host persist. Mid-turn F5 (the crash this PR exists to survive) GETs the worker head. Overlay-meta is deliberately not the carrier, so envelope adopt cannot save it. high
Major L6 The two reload invariants live only in HarnessHost.tsx (and the worker chunk builder). lib/turnQueue.test.ts proves helpers in isolation; sessionRepository.test.ts proves fold/parse of a blob that already has queue. Nothing proves (a) strip-at-durable-start vs strip-at-terminal, (b) worker copy-forward, (c) adopt does not clobber a local mirror. Claimed lib/sessionStore.test.ts local round-trip is not in the diff. Delete the reconcile block (or move it after persistTurn). Helper tests stay green. Double-run and cloud-wipe still ship. "HarnessHost is historically source-locked, not unit-rendered." Prior PRs still added *.test.ts source-locks for the invariant (detachTurn.test.ts, turnAttach.test.ts). This PR's safety claims have no equivalent. L6 on the same root as the L1 rows. high

Residual risk

Host-known-only persist still means composer-while-Busy (enqueueFromUi) dies on F5 — accepted residual. Failed-start queuedInsertFront does not auto-promote (Error + promote-gate false); retries are Play / later-success, not a poll tick, despite the drainAttemptsRef comment. queueClear is dead. Duplicate sendWhileRunning texts are skipped via includes while queueAppend allows duplicates — bounded.

Merge guidance

CONCERNS: do not merge until both Majors are fixed:

  1. Strip the drained prompt from the mirror at durable start (optimistic removeQueuedText + persist before runHarnessTurn on the non-attach path, restore-head only when the POST never accepted). Mid-turn onSessionPatch must never carry the in-flight prompt.
  2. Copy-forward sanitizeQueue(prior.queue) onto worker this-run chunks in buildThisRunChunk so a B7 pointer advance cannot drop the carrier.
  3. Tests for (1) as a HarnessHost source-lock + (2) as a persist-seam row. Optional: localStorage round-trip in sessionStore.test.ts (claimed, missing).

What was not attacked

Live DO runner, prod Gateway, Wasm FIFO internals (untouched by design), /api/turns gates, protocol v18–v21, roll-forward one-shot PUT drop of unknown fields (documented residual #2).

…er chunks

Adversarial-review #901 Majors:

- HarnessHost: persist removeQueuedText before runHarnessTurn on the
  non-attach drain path so onTurnStarted/onSessionPatch cannot save the
  in-flight prompt. Restore-head only when the POST never accepted.
  Crash between accept and terminal can no longer re-arm a double-POST.
- turnPersistSeam buildThisRunChunk: copy-forward sanitizeQueue(prior.queue)
  so a B7 this-run chunk cannot drop the transcript-blob carrier (cloud
  adopt was wiping a localStorage re-arm).

Tests: HarnessHost source-lock, persist-seam copy-forward, localStorage
round-trip. typecheck clean; affected default-project rows green.

@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 #901

Verdict: CONCERNS
Repo: btipling/invincible
Scope: mainf21/persisted-queue-drain · 9 files · persisted submit-queue mirror + reload re-arm (HEAD 6e103b2, after the prior-review follow-up)
Lenses run: L1, L2, L3, L5, L6, L8 (skip L4/L7/L9: no CI/workflow/deploy, no reusability bind, no palette/layout)
AGENTS.md read: yes (feature-divide yes — host/bridge/agent loop; SECURITY.md N/A — no workflow/secret/runner/API route)

Prior-review Majors on 400df2d (strip-after-terminal, worker drop of queue) landed in 6e103b2. This attack is against HEAD. Wasm-internal enqueue residual is not a finding (documented operator override on #815).

Findings

Sev Lens Finding Break scenario Refutation attempt Confidence
Major L1 lib/agent/turnPersistSeam.ts buildThisRunChunk copy-forwards prior.queue verbatim. Host drain-start persist(removeQueuedText) is fire-and-forget coalesced PUT (HarnessHost persistrepo.put; one in-flight PUT per session). lib/workflows/persistStep.ts this-run content is {id, messages}never queue — so fromContent is always undefined and the worker cannot see the host strip. After a just-completed turn the channel is still inflight with the terminal snapshot that still lists the drained prompt; the strip sits in pending while POST /api/turns starts the next worker. B7 reads the pre-strip blob and writes this-run {queue: [B, C]}. flattenReconstructedBody keeps the head field; mergeAdoptedUsage is {...server, usage: server.usage ?? local.usage} — server wins. 1. Cloud envelope session. Follow-up B is in the mirror. 2. Turn A terminals; host PUT of {queue:[B,C]} still in flight. 3. Promote drains B; host removeQueuedText is pending behind that PUT. 4. Worker B7 of B copy-forwards [B,C] onto the this-run chunk and advances transcriptPointer. 5. F5. 6. bootCloudSession onAdopt hydrates the worker snapshot with B still queued; hydrateRingWindow re-arms B; kickColdAttach finishes the live run. 7. That run completes → v19 auto-promote pops B → poll POSTs B again. C15 409 does not apply (first run already finished). Tools/sandbox writes re-run. "Strip-before-runHarnessTurn plus onTurnStarted persist already saved [C]." Those writes share the same coalesced channel and cannot overtake an in-flight terminal PUT from the previous turn. "Copy-forward was the #901 Major fix." It closed drop; it re-introduced stale include of the in-flight prompt. Local-only F5 is fine (sync writeLocalSession); cloud adopt is the path this PR exists to survive. high
Major L6 The new persist-seam row locks the wrong invariant. copy-forwards session.queue onto worker this-run chunks persists ['follow-up B','follow-up C'] onto a second chunk whose user text is turn-2 user — i.e. copy-forward with no drain of this-run's prompt. Nothing proves: this-run user text matching a mirrored item is removed; a coalesced-PUT race cannot re-arm an in-flight prompt; mergeAdoptedUsage does not clobber a stripped local with a stale server superset. HarnessHost source-lock only checks removeQueuedText appears before runHarnessTurn in the file text. Change buildThisRunChunk to strip this-run's first user text from the copy-forwarded queue. Existing F21 rows stay green. Cloud double-run still ships. "Helper tests + source-lock were the asked-for L6 of the prior review." They proved the previous patch, not the cloud race this commit left open. Same root as the L1 row. high

Residual risk

Host-known-only persist still means composer-while-Busy (enqueueFromUi) dies on F5 — accepted residual. Optimistic strip-before-POST can lose a queued prompt on F5 in the strip-persist-but-POST-never-accepted window (local; bounded; inverse of double-run). drainAttemptsRef is in-memory and the comment ("re-promotes on a later poll tick") is false: failed-start queuedInsertFront does not auto-promote (Error + promote-gate false); retries are Play / later success. Roll-forward one-shot PUT still drops unknown body fields (documented residual #2). Duplicate sendWhileRunning texts skipped via includes while queueAppend allows duplicates — bounded.

Merge guidance

CONCERNS: do not merge until the Major is fixed:

  1. Worker this-run chunks must copy-forward prior.queue minus this-run's first user prompt (removeQueuedText semantics). persistStep content is {id, messages} — the first role:user row is the drained prompt. A B7 that beats the host strip PUT then writes [C] not [B,C].
  2. Persist-seam test: prior {queue:['follow-up B','follow-up C']}, this-run content user follow-up B → chunk.queue equals ['follow-up C']. Keep the existing omit-queue copy-forward row for the non-matching user-text case.

What was not attacked

Live DO runner, prod Gateway, Wasm FIFO internals (untouched by design), /api/turns gates, protocol v18–v21, envelope LWW of a late host strip PUT vs worker this-run messages (pre-existing host/worker persist race; B8 overlay).

Adversarial-review #901 HEAD Majors:

- turnPersistSeam buildThisRunChunk: copy-forward prior.queue minus this-run's
  first user text (removeQueuedText semantics). persistStep content is
  `{id, messages}` so fromContent never carries the host strip, and a coalesced
  PUT cannot beat B7 — verbatim copy-forward re-armed the in-flight prompt on
  cloud F5 and double-POSTed after the live run completed.
- queueWithoutText helper shared with removeQueuedText.

Tests: persist-seam drain-strip + last-item unset; queueWithoutText unit rows.
typecheck clean; affected default-project rows green.

@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 #901

Verdict: CONCERNS
Repo: btipling/invincible
Scope: mainf21/persisted-queue-drain · 9 files · persisted submit-queue mirror + reload re-arm (HEAD b07397d, after prior-review follow-ups)
Lenses run: L1, L2, L3, L5, L6, L8 (skip L4/L7/L9: no CI/workflow/deploy, no reusability bind, no palette/layout)
AGENTS.md read: yes (feature-divide yes — host/bridge/agent loop; SECURITY.md N/A — no workflow/secret/runner/API route)

Prior-review Majors on 400df2d / 6e103b2 (strip-after-terminal, worker drop of queue, verbatim copy-forward of the in-flight prompt) landed in HEAD. This attack is against b07397d. Wasm-internal enqueue residual is not a finding (documented operator override on #815).

Findings

Sev Lens Finding Break scenario Refutation attempt Confidence
Major L1 lib/harnessChat.ts preserveQueue = attaching && rawPrompt.trim().length > 0 is false for kickColdAttach (empty prompt, dedup: true). HarnessHost.hydrateRingWindow re-arms the Wasm FIFO from session.queue, then kickColdAttachrunHarnessTurn('', {attach, dedup:true})pushSessionToBridge({preserveQueue:false})inv_clear_messages wipes the FIFO it just re-armed. Test 2h (F5 cold attach still clears the submit FIFO) locks the wipe. The comment even says kickColdAttach "may clear the (empty) queue" — F21 made that queue non-empty. 1. Host-known follow-ups B,C are in the mirror; turn A is still running. 2. F5 / switch-back / boot. 3. hydrateRingWindow clearMessages + rearmQueueFromMirror restores [B,C] into Wasm. 4. kickColdAttach microtask cold-attaches with ''. 5. Cold-attach hydrate uses preserveQueue:false → FIFO empty. 6. Attach completes → v19 auto-promote sees an empty band → B/C never drain. Canvas shows no queue chips. Mirror still has them (idle F5 later re-arms) — the reload this PR exists to survive does not restore a drainable FIFO while a turn is live. "preserveQueue:false on empty prompt was #857: F5 queue is stale from the previous session." hydrateRingWindow already clearMessages then re-arms this session's mirror; the stale FIFO is gone before kick. #857 test 2h is now a F21 regression lock. "sendWhileRunning already sets preserveQueue true." That path is non-empty prompt; kickColdAttach is the empty-prompt F5 path. Local-only idle F5 (no running turn) is fine (kickColdAttach no-ops). high
Major L6 Same root as L1. lib/turnQueue.test.ts source-lock only asserts removeQueuedText appears before runHarnessTurn. Persist-seam rows lock copy-forward/strip on the blob. Nothing proves: empty-prompt cold attach keeps a just-re-armed FIFO; hydrateRingWindow then kickColdAttach is not a wipe. Test 2h asserts the opposite (expect(exp.__queue).toEqual([])). Change preserveQueue to attaching (drop the empty-prompt conjunct). Current F21 helper tests + source-lock stay green. Test 2h fails (it wants the wipe). The F5-running re-arm still ships broken until 2h is inverted. "HarnessHost is source-locked, not unit-rendered." This invariant lives in runHarnessTurn (already has a named 2h row) — the lock is present and wrong for F21. L6 on the same root as the L1 row. high
Minor L1 Give-up paints MessageKind.Error via bridge.pushMessage only. runHarnessTurn validation uses appendMessage + push so the Error is in SessionSnapshot.messages. F21 give-up does not, then persistTurn(folded) saves the stripped queue without the row. Drain fails 5 times → give-up paints Error, drops the item. F5 rebuilds from snapshot: no Error row, item gone. The "never silent" paint does not survive reload. "Paint is for the moment of drop, not a durable transcript row." The PR lock is "drop-with-paint, never silent." Other host Errors (validation) persist. Bounded: only the explanation is lost, not a double-run. high

Residual risk

Host-known-only persist still means composer-while-Busy (enqueueFromUi) dies on F5 — accepted residual. Optimistic strip-before-POST can lose a queued prompt on F5 in the strip-persist-but-POST-never-accepted window (local; inverse of double-run). Worker firstUserText strip is exact-match: a follow-up whose text equals this-run's user line is also dropped from the blob (short prompts like ok / continue). drainAttemptsRef is in-memory and does not auto-promote after a failed start (Error + promote-gate false; retries are Play / later success) despite the "poll tick" comment. Roll-forward one-shot PUT still drops unknown body fields (documented residual #2). Duplicate sendWhileRunning texts skipped via includes while queueAppend allows duplicates — bounded. queueClear is unused (Clear/New mint a fresh session).

Merge guidance

CONCERNS: do not merge until the Major is fixed:

  1. runHarnessTurn must preserveQueue on every attach, including kickColdAttach's empty prompt. The F5/switch stale-FIFO wipe is already hydrateRingWindow's clearMessages before re-arm; cold-attach must use inv_clear_ring so the just-re-armed FIFO survives. Invert test 2h: empty-prompt cold attach keeps the FIFO.
  2. Optional Minor: appendMessage the give-up Error onto reconciled before persistTurn so F5 is not silent.

What was not attacked

Live DO runner, prod Gateway, Wasm FIFO internals (untouched by design), /api/turns gates, protocol v18–v21, envelope LWW of a late host PUT vs worker this-run messages (pre-existing host/worker persist race; B8 overlay), origin roll-forward carrier (BLOB_READ_WRITE_TOKEN still unset).

…rate

Adversarial-review #901 HEAD Majors:

- runHarnessTurn: preserveQueue on every attach, including kickColdAttach's
  empty prompt. hydrateRingWindow already clearMessages + re-arms from the
  persisted mirror; inv_clear_messages on the subsequent cold-attach hydrate
  wiped that FIFO so a running-turn F5 never restored a drainable band.
- Invert test 2h (was an #857 lock of the wipe).
- Give-up Error is appendMessage'd onto the snapshot so F5 is not silent.

Tests: inverted 2h + source-locks. typecheck clean; affected default-project
rows green.

@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 #901

Verdict: CONCERNS
Repo: btipling/invincible
Scope: mainf21/persisted-queue-drain · 11 files · persisted submit-queue mirror + reload re-arm (HEAD 5d1787c, after prior-review follow-ups)
Lenses run: L1, L2, L3, L5, L6, L8 (skip L4/L7/L9: no CI/workflow/deploy, no reusability bind, no palette/layout)
AGENTS.md read: yes (feature-divide yes — host/bridge/agent loop; SECURITY.md N/A — no workflow/secret/runner/API route)

Prior-review Majors on 400df2d / 6e103b2 / b07397d (strip-after-terminal, worker drop of queue, verbatim copy-forward of the in-flight prompt, kickColdAttach FIFO wipe) landed in HEAD. This attack is against 5d1787c. Wasm-internal enqueue residual is not a finding (documented operator override on #815).

Findings

Sev Lens Finding Break scenario Refutation attempt Confidence
Major L1 app/harness/HarnessHost.tsx hydrateRingWindow always clearMessages (wipe FIFO) then rearmQueueFromMirror. That helper is also the live ring snap: Load-earlier and the needSnap path that runs before runPrompt(pending) of a just-promoted head. Promote has already popped the head; the mirror still lists it (strip is inside runPrompt, after this hydrate). Rearm puts the in-flight prompt back into the FIFO. 1. Host-known follow-ups B,C are in the mirror and the Wasm band. 2. Turn A Ready → v19 auto-promote pops B (pending_submit=B, FIFO [C]). 3. Operator is on a historical ring window (Load earlier) or that Load-earlier just ran so needSnap is true. 4. Poll: hydrateRingWindow wipes [C], rearms from the still-unstripped mirror → FIFO [B,C]. 5. runPrompt(B) strips the mirror to [C] and POSTs B. 6. B completes → auto-promote pops B again → poll POSTs B a second time. C15 409 does not apply (first run already finished). Tools/sandbox writes re-run. Last-item case is the same: promote of sole head leaves FIFO empty, rearm restores it, same double-POST. "queuedCount()>0 skips re-arm." After clearMessages the count is 0 — the skip never fires on this path. "Strip-before-runHarnessTurn already dropped B." Strip runs inside runPrompt, after the hydrate. "Load-earlier should rebuild the ring." v21 inv_clear_ring (preserveQueue) is the live-session ring replace; F5/adopt/switch are the cold wipe+rearm. Folding re-arm into a helper used for both is the bug. high
Major L6 Same root as L1. lib/turnQueue.test.ts source-lock only asserts removeQueuedText appears before runHarnessTurn and preserveQueue = attaching. Persist-seam rows lock blob copy-forward. Test 2h locks cold-attach keep. Nothing proves: Load-earlier / needSnap do not re-arm; live ring snaps use preserveQueue (not clearMessages). hydrateRingWindow is one function for cold boot and live paging. Change Load-earlier/needSnap to pass a live policy (preserveQueue, no re-arm). Current F21 helper tests + source-lock stay green. The Load-earlier drain double-POST still ships. "HarnessHost is source-locked, not unit-rendered." Prior PRs still added named locks for the invariant (detachTurn.test.ts, turnAttach.test.ts, this PR's 2h invert). The new re-arm has no call-site lock. L6 on the same root as the L1 row. high

Residual risk

Host-known-only persist still means composer-while-Busy (enqueueFromUi) dies on F5 — accepted residual. Optimistic strip-before-POST can lose a queued prompt on F5 in the strip-persist-but-POST-never-accepted window (local; inverse of double-run). Worker firstUserText strip is exact-match: a follow-up whose text equals this-run's user line is also dropped from the blob (retry-same-prompt / ok / continue). drainAttemptsRef is in-memory, does not auto-promote after a failed start (Error + promote-gate false; retries are Play / later success) despite the comment claiming "re-promotes on a later poll tick". Duplicate sendWhileRunning texts skipped via includes while queueAppend allows duplicates — bounded. Roll-forward one-shot PUT still drops unknown body fields (documented residual #2). queueClear is unused (Clear/New mint a fresh session). TURN_QUEUE_MAX_ITEMS is pinned separately from HARNESS_QUEUE_MAX_ITEMS (no equality lock).

Merge guidance

CONCERNS: do not merge until the Major is fixed:

  1. Split cold vs live hydrate. Cold (boot / adopt / switch): inv_clear_messages + rearmQueueFromMirror. Live (Load-earlier / needSnap before pending submit): inv_clear_ring (preserveQueue: true) and do not re-arm — a just-promoted head is already out of the band and still in the mirror until runPrompt strips it. Do not key re-arm on queuedCount()===0 after a live clear: that is exactly the post-promote empty-FIFO case.
  2. Tests: queueHydratePlan('live'){preserveQueue:true, rearm:false}; queueHydratePlan('cold'){preserveQueue:false, rearm:true}. Source-lock that Load-earlier and the needSnap hydrate pass 'live'; boot/adopt stay cold.

What was not attacked

Live DO runner, prod Gateway, Wasm FIFO internals (untouched by design), /api/turns gates, protocol v18–v21, envelope LWW of a late host PUT vs worker this-run messages (pre-existing host/worker persist race; B8 overlay), origin roll-forward carrier (BLOB_READ_WRITE_TOKEN still unset).

Adversarial-review #901 HEAD Majors:

- hydrateRingWindow was wipe+rearm for every caller. Load-earlier and the
  needSnap path before runPrompt(pending) are live ring snaps: promote has
  already popped the head, the mirror still lists it until drain-start strip,
  and re-arm put it back into the FIFO so the next Ready double-POSTed it.
- Split via queueHydratePlan: cold (boot/adopt/switch) still clearMessages
  + rearm; live uses inv_clear_ring and never rearms. Do not key re-arm on
  queuedCount()===0 after a live clear (that is the last-item promote case).

Tests: queueHydratePlan unit rows + HarnessHost source-lock for live vs cold
call sites. typecheck clean; affected default-project rows green.

@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 #901

Verdict: CONCERNS
Repo: btipling/invincible
Scope: mainf21/persisted-queue-drain · 11 files · persisted submit-queue mirror + reload re-arm (HEAD 265d860, after prior-review follow-ups)
Lenses run: L1, L2, L3, L5, L6, L8 (skip L4/L7/L9: no CI/workflow/deploy, no reusability bind, no palette/layout)
AGENTS.md read: yes (feature-divide yes — host/bridge/agent loop; SECURITY.md N/A — no workflow/secret/runner/API route)

Prior-review Majors on 400df2d / 6e103b2 / b07397d / 5d1787c (strip-after-terminal, worker drop of queue, verbatim copy-forward of the in-flight prompt, kickColdAttach FIFO wipe, live ring snap re-arm) landed in HEAD. This attack is against 265d860. Wasm-internal enqueue residual is not a finding (documented operator override on #815).

Findings

Sev Lens Finding Break scenario Refutation attempt Confidence
Major L6 F21 rewrote app/harness/HarnessHost.tsx runPrompt / hydrateRingWindow call sites and did not update the existing HarnessHost source-locks that pin those strings. lib/detachTurn.test.ts and lib/hostQuotaError.test.ts still assert persistTurn(next, next.turnStatus !== 'running') and hydrateRingWindow(b, session, nextStart); (no 'live'). New F21 rows in lib/turnQueue.test.ts stay green. vitest run --project default lib/detachTurn.test.ts lib/hostQuotaError.test.ts3 failed (reproduced on HEAD 265d860). merge-pr full vitest gate is red. PR cannot merge. "Rename-only; the #870 paint-skip invariant still holds as persistTurn(reconciled, …)." The production skip is intact. The lock is not — merge-pr / npm test is the contract, and the claimed default-project green is false. Leaving the #870 locks pointing at a dead next means a later paint-skip regression on the epoch path would not fail those rows. The Load-earlier regex no longer proves paintQuotaAfterRebuild still follows the live hydrate. high

Residual risk

Host-known-only persist still means composer-while-Busy (enqueueFromUi) dies on F5 — accepted residual. Optimistic strip-before-POST can lose a queued prompt on F5 in the strip-persist-but-POST-never-accepted window (local; inverse of double-run). Worker firstUserText strip is exact-match: a follow-up whose text equals this-run's user line is also dropped from the blob. drainAttemptsRef is in-memory; the comment ("re-promotes on a later poll tick") is still false — failed-start setFailLifecycle arms promote-gate false + Error, so retries are Play / later success. setFailLifecycle may insert CONTINUE_TURN_PROMPT when siblings remain in the FIFO (pre-existing #759; F21 then queuedInsertFronts the restored head in front of it). Duplicate sendWhileRunning texts skipped via includes while queueAppend allows duplicates — bounded. Roll-forward one-shot PUT still drops unknown body fields (documented residual #2). queueClear is unused. TURN_QUEUE_MAX_ITEMS is pinned separately from HARNESS_QUEUE_MAX_ITEMS (no equality lock).

Merge guidance

CONCERNS: do not merge until the Major is fixed:

  1. Update the existing HarnessHost source-locks to the F21 symbols: persistTurn(reconciled, reconciled.turnStatus !== 'running') in lib/detachTurn.test.ts and lib/hostQuotaError.test.ts; Load-earlier / needSnap regexes must allow hydrateRingWindow(..., 'live') and still require the following paintQuotaAfterRebuild (the #870 wipe-then-paint invariant).
  2. Re-run those two files (not only lib/turnQueue.test.ts) so default-project is actually green.

What was not attacked

Live DO runner, prod Gateway, Wasm FIFO internals (untouched by design), /api/turns gates, protocol v18–v21, envelope LWW of a late host PUT vs worker this-run messages (pre-existing host/worker persist race; B8 overlay), origin roll-forward carrier (BLOB_READ_WRITE_TOKEN still unset).

Adversarial-review #901 HEAD Major:

- persistTurn(next, ...) became persistTurn(reconciled, ...) on the epoch path;
  detachTurn + hostQuotaError source-locks still asserted the dead `next`.
- Load-earlier / needSnap hydrates now pass kind 'live'; the #870 wipe-then-paint
  regex still required hydrateRingWindow(... nextStart); with no extra arg.

The production paint-skip / paintQuotaAfterRebuild invariants were intact; the
locks were not. Default-project rows were red (3 failed).

Tests: updated locks. typecheck N/A (tests only).

@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 #901

Verdict: CONCERNS
Repo: btipling/invincible
Scope: mainf21/persisted-queue-drain · 13 files · persisted submit-queue mirror + reload re-arm (HEAD 06613f4, after prior-review follow-ups)
Lenses run: L1, L2, L3, L5, L6, L8 (skip L4/L7/L9: no CI/workflow/deploy, no reusability bind, no palette/layout)
AGENTS.md read: yes (feature-divide yes — host/bridge/agent loop; SECURITY.md N/A — no workflow/secret/runner/API route)

Prior-review Majors on 400df2d / 6e103b2 / b07397d / 5d1787c / 265d860 (strip-after-terminal, worker drop of queue, verbatim copy-forward of the in-flight prompt, kickColdAttach FIFO wipe, live ring snap re-arm, stale #870 source-locks) landed in HEAD. This attack is against 06613f4. Wasm-internal enqueue residual is not a finding (documented operator override on #815). Roll-forward PUT drop of unknown fields is not a finding (documented residual #2; same-id boot LWW keeps a newer-or-equal local mirror).

Findings

Sev Lens Finding Break scenario Refutation attempt Confidence
Major L1 lib/agent/turnPersistSeam.ts firstUserText exact-matches the this-run user row against session.queue. Production persistStep content is checkpointToSnapshotMessages(fold.checkpoint), and that checkpoint's user content is turnWorkflow userMessage = POST /api/turns parsed.prompt = host formatPromptWithHistory(session.messages, prompt) (lib/harnessChat.ts apiPromptsendTurnStream). After the first turn, that string is the folded blob (Previous conversation…\nUser: …\nUser: follow-up B\n\nAssistant:), not the raw queue item. queueWithoutText no-ops; copy-forward keeps the in-flight prompt on the worker head. Worker updatedAt is newer than the host strip PUT → shouldAdoptServer adopts the worker snapshot. 1. Cloud envelope session. Turn A completes; host-known follow-up B is in the mirror. 2. Drain POSTs B; host removeQueuedText persists [C]. 3. Workflow userMessage is the history fold, not B. B7 copy-forwards [B,C] onto the this-run chunk and advances transcriptPointer. 4. F5. 5. bootCloudSession onAdopt hydrates the worker snapshot with B still queued; hydrateRingWindow re-arms B; kickColdAttach finishes the live run. 6. That run completes → v19 auto-promote pops B → poll POSTs B again. C15 409 does not apply (first run already finished). Tools/sandbox writes re-run. "The persist-seam row already strips when this-run user is follow-up B." That is not the production shape — runHarnessTurn sends apiPrompt (folded) on every drain after the first turn, and a queue implies prior history. "Host strip-before-runHarnessTurn already saved [C]." Worker overlay clock is later; same-id adopt is server-wins on updatedAt. "firstUserText was the #901 HEAD Major fix." It closed bare-prompt copy-forward; every real follow-up drain is a fold. high
Major L6 Same root as L1. copy-forward drops this-run user prompt from the queue persists a this-run user row whose text equals the queued item. Nothing proves: a formatPromptWithHistory user row still strips B; a coalesced-PUT race cannot re-arm an in-flight prompt when the checkpoint user is the fold. Helper queueWithoutText unit rows stay green either way. Change firstUserText to extract the last User: line of a history fold (or pass the raw prompt into persist). Existing F21 persist-seam rows stay green. Cloud double-run still ships. "Helper tests + source-lock were the asked-for L6 of the prior review." They proved the previous patch against a synthetic {role:user,text:'follow-up B'} body, not the apiPrompt the host actually POSTs. L6 on the same root as the L1 row. high

Residual risk

Host-known-only persist still means composer-while-Busy (enqueueFromUi) dies on F5 — accepted residual. Optimistic strip-before-POST can lose a queued prompt on F5 in the strip-persist-but-POST-never-accepted window (local; inverse of double-run). drainAttemptsRef is in-memory; the comment ("re-promotes on a later poll tick") is still false — failed-start setFailLifecycle arms promote-gate false + Error, so retries are Play / later success. setFailLifecycle may insert CONTINUE_TURN_PROMPT when siblings remain in the FIFO (pre-existing #759; F21 then queuedInsertFronts the restored head in front of it). Duplicate sendWhileRunning texts skipped via includes while queueAppend allows duplicates — bounded. Roll-forward one-shot PUT still drops unknown body fields (documented residual #2). queueClear is unused. TURN_QUEUE_MAX_ITEMS is pinned separately from HARNESS_QUEUE_MAX_ITEMS (no equality lock). docs/session-model.md blob shape still omits queue and says extra keys are ignored.

Merge guidance

CONCERNS: do not merge until the Major is fixed:

  1. Worker this-run chunks must strip the raw drained prompt, not the history-folded userMessage. firstUserText should treat a formatPromptWithHistory blob as "last User: line before the trailing Assistant:" (bare prompt stays exact-match). A B7 of follow-up B then writes [C], not [B,C].
  2. Persist-seam test: prior {queue:['follow-up B','follow-up C']}, this-run content user = formatPromptWithHistory([turn-1 user, turn-1 assistant], 'follow-up B') → chunk.queue equals ['follow-up C']. Keep the existing bare-prompt strip row.

What was not attacked

Live DO runner, prod Gateway, Wasm FIFO internals (untouched by design), /api/turns gates, protocol v18–v21, envelope LWW of a late host PUT vs worker this-run messages (pre-existing host/worker persist race; B8 overlay), origin roll-forward carrier (BLOB_READ_WRITE_TOKEN still unset).

… strip

Adversarial-review #901 HEAD Majors:

- turnPersistSeam firstUserText exact-matched the this-run user row against
  session.queue. Production persistStep content is the workflow userMessage,
  which runHarnessTurn POSTs as formatPromptWithHistory (apiPrompt) — after
  the first turn that is the folded blob, not the raw queue item. Copy-forward
  kept the in-flight prompt on the worker head; cloud F5 adopted it and
  double-POSTed after the live run completed.
- queueTextFromUserContent takes the last User: line of a history fold
  (bare prompts pass through). Persist-seam row uses a real fold.

Tests: queueTextFromUserContent unit rows + persist-seam folded-userMessage
strip. typecheck clean; affected default-project rows green.

@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 #901

Verdict: CONCERNS
Repo: btipling/invincible
Scope: mainf21/persisted-queue-drain · 13 files · persisted submit-queue mirror + reload re-arm (HEAD fca3b10, after prior-review follow-ups)
Lenses run: L1, L2, L3, L5, L6, L8 (skip L4/L7/L9: no CI/workflow/deploy, no reusability bind, no palette/layout)
AGENTS.md read: yes (feature-divide yes — host/bridge/agent loop; SECURITY.md N/A — no workflow/secret/runner/API route)

Prior-review Majors on 400df2d / 6e103b2 / b07397d / 5d1787c / 265d860 / 06613f4 (strip-after-terminal, worker drop of queue, verbatim copy-forward of the in-flight prompt, kickColdAttach FIFO wipe, live ring snap re-arm, stale #870 source-locks, history-fold userMessage unwrap) landed in HEAD. This attack is against fca3b10. Wasm-internal enqueue residual is not a finding (documented operator override on #815). Roll-forward PUT drop of unknown fields is not a finding (documented residual #2).

Findings

Sev Lens Finding Break scenario Refutation attempt Confidence
Major L1 mergeAdoptedUsage is {...server, usage: server.usage ?? local.usage} — whole-snapshot server-wins except usage. F21's host-known persist path is queueAppend (send-while-running) then a coalesced repo.put. Worker B7 of the live turn copy-forwards prior.queue from before that PUT lands, stamps a newer overlay updatedAt (max(Date.now(), stored+1)), and advances transcriptPointer. Boot/F5 shouldAdoptBootServer + mergeAdoptedUsage hydrates the worker snapshot without the just-appended follow-up. Inverse of the drain-strip race: lost prompt, not double-run. onAdopt skipping while inflightRef is true does not help — F5 is a fresh heap. 1. Envelope session. Turn A running; host inflight false (503 / D18). 2. Operator Send B; queueAppend writes local [B] (updatedAt=T1) and coalesces a PUT. 3. Worker B7 of A reads prior blob (PUT not landed / older pointer), copy-forwards empty-or-stale queue (no B), writes this-run chunk, overlay clock T2>T1. 4. F5. 5. localStorage first-paint has [B]; bootCloudSession onAdopt LWW-adopts the worker snapshot; hydrateRingWindow rearms empty. B never drains. "queueAppend PUT lands and becomes prior, so the next B7 copy-forwards [B]." Only if the PUT beats B7. Mid-turn B7 is on every tool batch; overlay clock is monotonically newer than the append. Previous #901 Majors already proved coalesced host PUTs lose this race in the other direction. "mergeAdoptedUsage is usage-only by design." That is why a new transcript-body field needs the same field-level merge usage got — server-wins on the whole snapshot is how a missing/stale queue clobbers local. "Local-only F5 is fine." True; this PR's cloud adopt path is the one it claims to survive. high
Major L6 Same root as L1. Persist-seam rows lock copy-forward and this-run strip on the worker chunk. mergeAdoptedUsage tests only usage. Nothing proves: same-id adopt of a worker head that omitted a local queueAppend keeps B; same-id adopt of a stale-long server queue still strips the in-flight last user so B cannot re-arm. Helper sanitizeQueue / queueAppend rows stay green either way. Change mergeAdoptedUsage to keep local extras (union) and strip queueTextFromUserContent(last user) of the adopted messages. Existing F21 rows stay green. Cloud F5 still drops a host-known follow-up that lost the PUT race. "Helper tests + source-lock were the asked-for L6 of prior reviews." They proved strip/copy-forward on the worker blob, not the adopt merge that actually hydrates F5. L6 on the same root as the L1 row. high
Minor L6 TURN_QUEUE_MAX_ITEMS is pinned separately from HARNESS_QUEUE_MAX_ITEMS (both 16) with no equality lock. Drift → rearm of a 17th Wasm item is dropped by sanitize, or a 16-cap mirror cannot restore a raised Wasm FIFO. Change one constant. F21 helper tests stay green. Reload re-arm silently truncates. "Comment says pinned to avoid a cycle." A test-only import of both constants is cycle-free and is how other dual-pins are locked. high
Minor L8 docs/session-model.md still describes the Blob head as {id, updatedAt, messages, prev?, depth?} and says "Extra keys are ignored." F21's carrier is an extra key (queue). A later persist change that believes the doc will drop the mirror (the original #901 worker-drop Major). New-hire / agent implements flatten/persist from the doc, omits queue. Cloud adopt wipes the mirror again. "Code + tests are the contract." Living-docs rule in AGENTS.md: docs/session-model.md is the session restore truth. Residual listed on the last review, still unfixed. high

Residual risk

Host-known-only persist still means composer-while-Busy (enqueueFromUi) dies on F5 — accepted residual. Optimistic strip-before-POST can lose a queued prompt on F5 in the strip-persist-but-POST-never-accepted window (local; inverse of double-run). drainAttemptsRef is in-memory; the comment ("re-promotes on a later poll tick") is still false — failed-start setFailLifecycle arms promote-gate false + Error, so retries are Play / later success. setFailLifecycle may insert CONTINUE_TURN_PROMPT when siblings remain in the FIFO (pre-existing #759; F21 then queuedInsertFronts the restored head in front of it). Duplicate sendWhileRunning texts skipped via includes while queueAppend allows duplicates — bounded. Roll-forward one-shot PUT still drops unknown body fields (documented residual #2). queueClear is unused (Clear/New mint a fresh session). queueTextFromUserContent takes the last \nUser: line, so a follow-up whose body itself contains that marker can fail the worker strip (paste-a-transcript; bounded).

Merge guidance

CONCERNS: do not merge until the Major is fixed:

  1. Same-id adopt must field-merge queue, not whole-snapshot server-wins. Union server+local (server order, then local copies not already present, cap TURN_QUEUE_MAX_ITEMS), then queueWithoutText of queueTextFromUserContent(last user row on the adopted messages) so a stale-long server queue cannot re-arm an in-flight prompt and a worker head that missed queueAppend cannot drop it.
  2. Tests on mergeAdoptedUsage: (a) server queue omitted, local ['follow-up B']['follow-up B']; (b) server ['follow-up B','follow-up C'], local ['follow-up C'], last user is a formatPromptWithHistory fold of follow-up B['follow-up C']. Keep existing usage rows.

Minors: lock TURN_QUEUE_MAX_ITEMS === HARNESS_QUEUE_MAX_ITEMS; name queue on the Blob shape in docs/session-model.md (stop saying extra keys are ignored).

What was not attacked

Live DO runner, prod Gateway, Wasm FIFO internals (untouched by design), /api/turns gates, protocol v18–v21, envelope LWW of a late host PUT vs worker this-run messages (pre-existing host/worker persist race; B8 overlay), origin roll-forward carrier (BLOB_READ_WRITE_TOKEN still unset / NEXT_PUBLIC_HARNESS_CARRIER_ENVELOPE default off).

…rip in-flight)

Adversarial-review #901 HEAD Majors:

- mergeAdoptedUsage was whole-snapshot server-wins except usage. Worker B7 of
  the live turn copy-forwards prior.queue from before a coalesced queueAppend
  PUT lands, stamps a newer overlay clock, and boot/F5 adopt wiped the
  host-known follow-up (inverse of the drain-strip race).
- Union server+local (server order, then local copies not already present),
  then queueWithoutText of queueTextFromUserContent(last user) so a stale-long
  server queue cannot re-arm an in-flight drain.

Minors: lock TURN_QUEUE_MAX_ITEMS === HARNESS_QUEUE_MAX_ITEMS; name queue on
the Blob shape in docs/session-model.md; drainAttemptsRef comment no longer
claims poll-tick auto-promote.

Tests: mergeQueues unit rows + mergeAdoptedUsage adopt rows + source-lock.
typecheck clean; affected default-project rows green.

@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 #901

Verdict: PASS WITH NOTES
Repo: btipling/invincible
Scope: mainf21/persisted-queue-drain · 14 files · persisted submit-queue mirror + reload re-arm (HEAD 3ea0958, after prior-review follow-ups)
Lenses run: L1, L2, L3, L5, L6, L8 (skip L4/L7/L9: no CI/workflow/deploy, no reusability bind, no palette/layout)
AGENTS.md read: yes (feature-divide yes — host/bridge/agent loop; SECURITY.md N/A — no workflow/secret/runner/API route)

Prior-review Majors on 400df2d / 6e103b2 / b07397d / 5d1787c / 265d860 / 06613f4 / fca3b10 (strip-after-terminal, worker drop of queue, verbatim copy-forward of the in-flight prompt, kickColdAttach FIFO wipe, live ring snap re-arm, stale #870 source-locks, history-fold userMessage unwrap, whole-snapshot server-wins adopt) landed in HEAD. This attack is against 3ea0958. Wasm-internal enqueue residual is not a finding (documented operator override on #815). Roll-forward PUT drop of unknown fields is not a finding (documented residual #2).

Affected default-project files re-run on HEAD: 459/459 green (turnQueue, sessionRepository, sessionStore, turnPersistSeam, detachTurn, hostQuotaError, harnessChat).

Findings

Sev Lens Finding Break scenario Refutation attempt Confidence
Minor L6 Production cloud GET is reconstructTranscriptChainflattenReconstructedBody (spreads headBody) → parseCloudSessionSnapshot. F21 locks copy-forward on the raw worker chunk and mergeAdoptedUsage on already-parsed snapshots. Nothing proves the GET flatten keeps queue. Change flattenReconstructedBody to copy only {id, updatedAt, messages} (the pre-F21 documented shape). Persist-seam + merge rows stay green. New-device / empty-local ?s= pin adopts a snapshot with no queue; hydrateRingWindow rearms empty; host-known follow-ups never drain. Same-id local extras still save a same-heap F5 — the miss is cloud-only restore. "Spread keeps extra keys today." True on HEAD. The GET path is the only remaining untested carrier hop; a whitelist flatten is the original worker-drop Major in a different function. high
Minor L8 lib/sessionRepository.ts trimForCloudPut still comments Empty mirror omits the field (absent = clear on adopt-restore). HEAD field-merge is union + strip (mergeQueues); omit is not a clear. New-hire / agent treats omit-as-clear, reverts mergeAdoptedUsage to whole-snapshot server-wins or skips the union. A coalesced queueAppend that lost the B7 race is wiped on F5 again. "Code + merge tests are the contract." Living-docs / in-code comments are how the last worker-drop Major was supposed to stay dead. docs/session-model.md was updated; this comment was not. high

Residual risk

Host-known-only persist still means composer-while-Busy (enqueueFromUi) dies on F5 — accepted residual. Optimistic strip-before-POST can lose a queued prompt on F5 in the strip-persist-but-POST-never-accepted window (local; inverse of double-run). mergeQueues / worker firstUserText exact-match strip also drops a follow-up whose text equals the live last user (ok / continue / retry-same-prompt). drainAttemptsRef is in-memory (reload starts a fresh 5). setFailLifecycle may insert CONTINUE_TURN_PROMPT when siblings remain; F21 then queuedInsertFronts the restored head in front of it (pre-existing #759). Duplicate sendWhileRunning texts skipped via includes while queueAppend allows duplicates — bounded. queueTextFromUserContent takes the last \nUser: line, so a follow-up that itself contains that marker can fail the worker strip (paste-a-transcript; bounded). queueClear is unused (Clear/New mint a fresh session).

Merge guidance

PASS WITH NOTES: the double-run / lost-queueAppend family did not breach on 3ea0958 with current evidence. Minors should land before or with merge — they are the two remaining untested/stale-comment hops on the carrier this PR exists to keep.

What was not attacked

Live DO runner, prod Gateway, Wasm FIFO internals (untouched by design), /api/turns gates, protocol v18–v21, envelope LWW of a late host PUT vs worker this-run messages (pre-existing host/worker persist race; B8 overlay), origin roll-forward carrier (BLOB_READ_WRITE_TOKEN still unset / NEXT_PUBLIC_HARNESS_CARRIER_ENVELOPE default off).

Adversarial-review #901 HEAD Minors:

- flattenReconstructedBody spreads the worker head; a whitelist flatten to
  {id, updatedAt, messages} would drop the F21 carrier on cloud GET (new-device
  / empty-local ?s= pin) while persist-seam and merge rows stayed green.
- trimForCloudPut comment claimed omit = clear on adopt-restore; same-id adopt
  field-merges via mergeQueues.

Tests: flatten keeps queue; GET flatten+parse restores it. typecheck N/A
(comment + tests).
@btipling
btipling merged commit 9d4ffaa into main Aug 31, 2026
3 checks passed
@btipling
btipling deleted the f21/persisted-queue-drain branch August 31, 2026 05:46
btipling added a commit that referenced this pull request Aug 31, 2026
v22 is reserved by the concurrent #815/#901 enqueue-visibility residual.
This PR's reasoning-effort picker is additive protocol v23.

Conflict: persist the F21 reconciled snapshot (queue restore/give-up),
then fold live getSelectedReasoning() the same way as selectedModel.
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.

backend-agents F21: persisted backend submit queue + drain

1 participant