int: two-heap durable-turn suite (locks #859) - #861
Conversation
Add vitest project int (excluded from npm test via --project flags) with it.fails contracts for persist blob parse, F5 attach, Send remap, quota, and stream-drop. Extract bootCloudSnapshot / coldAttachFromSnapshot without changing today's boot. GHA int-durable on ubuntu-latest. Fixes #860
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
Opened from plan-review + implement-plan on #860. Not merged. Verification in the agent workspace: Next: adversarial-review on this PR, then an explicit merge request (merge commit, not squash). |
GITHUB_TOKEN is not a default runner env var, so fetch-harness failed closed under HARNESS_REQUIRE=1. Pass github.token and lock it with a yaml source-scan.
CI follow-up (
|
btipling
left a comment
There was a problem hiding this comment.
Adversarial review — PR #861
Verdict: CONCERNS
Repo: btipling/invincible
Scope: main ← plan/int-durable-860 · 17 files · int suite + behavior-preserving boot/attach extract + GHA int-durable
Lenses run: L1, L2, L4, L6, L7, L8 (skip L3: no dual-chat / Wasm product path change beyond thin kickColdAttach call; skip L5: two Wasm instantiations in an opt-in project, not a poll/alloc storm; skip L9: no palette/UX)
AGENTS.md read: yes (tests bullet, vitest --project rule, no-wrapper). docs/feature-divide.md + SECURITY.md runner policy read.
Findings
| Sev | Lens | Finding | Break scenario | Refutation attempt | Confidence |
|---|---|---|---|---|---|
| Major | L1/L6 | bootCloudSnapshot (lib/sessionRepository.ts) is a test-only twin. Production getEnvelope still inlines parseCloudSessionSnapshot + overlayEnvelopeMeta and does not call the extract. Int rows 2–3/5 lock F5 via bootFromMemory → bootCloudSnapshot, not the host boot path (bootCloudSession → repo.get → inner getEnvelope → keep-local on error → kickColdAttach). |
Product PR implements overlay-on-error (or envelope-wins) only in bootCloudSnapshot, drops the matching it.fails. npm run test:int goes green. Operator F5 still hits inner getEnvelope, gets { action: 'error', message: 'Invalid transcript body.' }, keeps stale local completed, Send still POSTs into a live run → C15 409. The ratchet never moved the bug. |
Plan said getEnvelope stays unexported and the driver mirrors the two-step with Memory stores. That is not a reason to leave two copies of the two-step. coldAttachFromSnapshot was wired into HarnessHost; boot was not. Wiring getEnvelope through bootCloudSnapshot is behavior-preserving (error path still discards the dummy local). Defender “int is in-process on purpose” fails: the extract exists so the product fix has one seam. |
high |
| Major | L4/L6 | package.json test:changed is still vitest run --changed with no --project flags. The plan already proved vitest 3 runs every test.projects entry unless --project is passed; npm test was fixed, the fast gate was not. AGENTS.md still documents npm run test:changed = vitest run --changed. |
Change lib/turnAttach.ts / lib/sessionRepository.ts (the #859 product PRs). vitest run --changed collects int/f5-attach.int.test.ts (imports those modules). beforeAll loadBridge() throws without harness.wasm → fast gate red, or runs it.fails rows as if they were the default green suite. Inverse: a broken default-suite test in a touched file can be masked if someone “fixes” collect by accident. Same class of hole as naked vitest run collecting int. |
“--changed only runs related tests, so int stays quiet on unrelated PRs.” False for the PRs this suite exists to serve: they touch persist/boot/attach. “Agent workspace always has wasm.” AGENTS.md forbids assuming that for the default green gate; that is why npm test got --project. CI int-durable is a different job. |
high |
| Major | L6 | int/quota.int.test.ts it.fails asserts an impossible conjunction: threw === true and loaded.turnStatus === 'running' with turn-2 text. Today both fail (save swallows → remainder is completed) so it.fails is green. |
Product PR makes LocalStorageSessionStore.save rethrow quota (the “not silent” fix) and drops it.fails. threw becomes true; load() is still the previous completed snapshot (throw means the running write did not land). Test stays red. The only way both expects pass is “throw and persist running,” which is not a coherent save API. The quota row cannot be dropped as specified. |
Plan text: “Failure is not silent. Boot must not trust a truncated snapshot as if the save succeeded.” That is two contracts: (1) save does not swallow; (2) remainder is truncated / boot must not treat it as a successful running save. They were jammed into one it.fails. “Boot” is never called; only store.load(). |
high |
| Major | L6 | int/f5-attach.int.test.ts green row 5 (green): mid-turn abort does not flip envelope to completed never aborts, never folds, never calls decideDetach / fail-fold. It markEnvelopeRunning then reads the same row back. |
A later change to the real abort/detach fold (D18 classifyTurnFailure / persist-clear of turnStatus) can start flipping envelope to completed on reader drop. This green test stays green. Operators still F5 into a completed envelope and kickColdAttach no-ops. The PR body claims “envelope stays running after abort.” That is not what the test proves. |
Plan allowed a host-shaped envelope upsert as the stream-drop fixture. A fixture is not a lock of abort semantics. #849 / D18 already have unit tests; this row’s only unique claim is “abort does not complete the envelope,” and that SUT is not invoked. |
high |
| Minor | L6 | it.fails bodies still include fixture I/O (persistTurn1, bootFromMemory, first store.save). Plan pulled loadBridge out of it.fails for this exact false-green (throw = expected fail). Persist/boot setup was left inside. |
persistStep / Memory LWW / setPersistSeamResolver throws (seam not wired, clock conflict). Every contract it.fails stays green. Missing-wasm was the named case; persist throw is the same mechanism. |
Mitigated if a green test runs the same fixture in the same file (a throw fails that row). No such smoke exists today next to persist-parse / quota. | high |
| Minor | L8 | Test titles mix plan-matrix numbers with #859 row N. quota.int.test.ts and f5-attach.int.test.ts both claim #859 row 5 (issue table: quota=5, stream-drop=4). |
Product PR “drop the matching it.fails for row 5” drops the wrong file, or keeps a still-red stream-drop wrapped as if quota were fixed. |
Titles are not runtime. Still a ratchet protocol bug on a PR whose DoD is “drop matching it.fails in the same PR as the fix.” |
high |
| Nit | L1 | HarnessHost.kickColdAttach takes spec from coldAttachFromSnapshot then ignores spec.startIndex / spec.dedup (attach: { runId: spec.runId, startIndex: 0, dedup: true }). |
Later change to ColdAttachSpec (e.g. startIndex from envelope cursor) updates the extract + int tests; host keeps 0. Same doppelganger class as boot, bounded by today’s literal type. |
Type is currently { startIndex: 0; dedup: true }. Drift is future. lib/turnAttach.test.ts source-scans the hardcoded string, which cements the drift. |
medium |
Residual risk
Even after the Majors: this is still an in-process Memory Blob/envelope driver, not GET /envelope + signed Blob URL + live /api/turns SSE. it.fails rows 2-ring / 6 assert TURN2_USER / LIVE_ASSISTANT that the fixture never persisted — they will stay red until a later continuous-persist issue rewrites the fixture (plan allowed that for row 6). Fork PRs skip int-durable; making it a required check will block forks unless GitHub is taught to allow skipped. Latest-main harness-wasm (WAIT_MS=0) can lag protocol; load test fail-closes, which is correct but will red this job on an expired/missing artifact.
Merge guidance
- CONCERNS: do not merge until Majors are fixed (or explicitly accepted). DI/cost gate did not fire (no extra
PGlite/ live HTTP). - Required before merge: (1)
getEnvelopecallsbootCloudSnapshot; (2)test:changed(+ AGENTS.md) gets--project default --project tenancy; (3) split the quota ratchet into a droppableit.fails(not silent) + a green truncated-remainder row; (4) green abort row must invoke detach/abort classification, not echomarkEnvelopeRunning.
What was not attacked
Live DO self-hosted runner, prod AI Gateway, Vercel Workflows World, Playwright/next start, real browser F5 + quota ~5MB, fork-PR token surface (job skipped). CI int-durable at 375e794 was read: fetch-harness sha=efb2d39 wait disabled — using latest artifact, 4 files / 9 passed.
Wire getEnvelope through bootCloudSnapshot so the int extract is the production two-step. Pass attach spec through kickColdAttach. Give test:changed the same --project flags as npm test. Split quota into a green truncated-remainder row and an it.fails not-silent expect. Green stream-drop row classifies F5 as detach. persistTurn1 fixture smoke so setup throw cannot hide inside it.fails.
Adversarial-review follow-up (
|
| Finding | Fix |
|---|---|
bootCloudSnapshot was a test-only twin |
getEnvelope now calls it. Dummy local discarded on parse fail — host still keeps local. Source-scan in lib/sessionRepository.test.ts. |
test:changed collected int |
vitest run --changed --project default --project tenancy. AGENTS.md + scripts/int-durable.gha.test.ts lock. |
Quota it.fails impossible conjunction |
Green: truncated remainder stays completed. it.fails: save must throw (droppable). |
Green abort echoed markEnvelopeRunning |
Green row asserts B8 completed → host running LWW, then decideDetach is detach (not cancel) and shouldSetHostTurnNote('running') is false. |
it.fails swallowed fixture throw |
persistTurn1 fixture smoke in persist-parse (green). |
Dual #859 row 5 titles |
Issue-table numbers: quota=5, stream-drop=4, F5=3, persist-hole=2. |
kickColdAttach ignored spec fields |
attach: spec. Source-scan updated. |
Verified here: npm run typecheck 0; npm run test:int 4 files / 11 passed (7 it.fails + 4 green); default-project changed files 125 passed; scripts/di-gate.mjs OK.
Product PRs for #859 still drop the matching it.fails in the same PR as the fix — quota drop is now only “save throws.”
btipling
left a comment
There was a problem hiding this comment.
Adversarial review — PR #861
Verdict: CONCERNS
Repo: btipling/invincible
Scope: main ← plan/int-durable-860 @ 1b68157 · 17 files · int suite + boot/attach extract + GHA int-durable (re-review after the first CONCERNS follow-up)
Lenses run: L1, L2, L4, L6, L7, L8 (skip L3: no dual-chat / Wasm product path; skip L5: two Wasm instantiations in an opt-in project; skip L9: no palette/UX)
AGENTS.md read: yes. docs/feature-divide.md + SECURITY.md runner policy read.
Prior review at 375e794 (CONCERNS) was addressed in 1b68157 for: getEnvelope→bootCloudSnapshot, test:changed --project, quota split, abort classify, fixture smoke, row titles, attach: spec. Those do not survive this pass. A new host-composition hole does.
Findings
| Sev | Lens | Finding | Break scenario | Refutation attempt | Confidence |
|---|---|---|---|---|---|
| Major | L1/L6 | F5 / C15 int rows (int/f5-attach.int.test.ts) call coldAttachFromSnapshot(boot.snapshot) and decideSendAttach({ turnRunId: boot.snapshot.turnRunId, turnStatus: boot.snapshot.turnStatus }). bootFromMemory returns bootCloudSnapshot's error snapshot (the real local). Production does not. getEnvelope maps parse fail to CloudGetResult { action: 'error'; status; message } — no snapshot (lib/sessionRepository.ts · dummy local discarded). bootCloudSession on error never calls onAdopt (keep local). kickColdAttach / Send read sessionRef (HarnessHost.tsx). |
Product PR implements envelope-wins only in bootCloudSnapshot (overlay running onto parse-fail snapshot, maybe keep action: 'error'), drops the matching it.fails. npm run test:int unexpected-passes then goes green. Operator F5: blob still { deltas } → getEnvelope error → keep stale completed → kickColdAttach no-op → Send POSTs → C15 409. Same doppelganger class as the last review; wiring getEnvelope through the extract did not close it because the host never consumes the error snapshot. Even action: 'ok' + overlay of dummy { updatedAt: 0, messages: [] } loses shouldAdoptBootServer LWW to a newer completed local. |
“getEnvelope now calls bootCloudSnapshot; one seam.” False: int reads BootCloudResult.snapshot on error; CloudGetResult cannot carry that field. “Row 1 persist-schema-first makes parse succeed, so getEnvelope returns ok.” #859 explicitly wants row 3 to unblock attach before blob schema unify; the int fixture is unreadable { deltas } + markEnvelopeRunning. “LWW is out of scope for int.” Host F5 is keep-local + LWW; a lock that skips both will green a product patch the operator never feels. |
high |
| Minor | L6 | #859 row 3: heap-B ring is not stuck on turn-1-only asserts TURN2_USER in the ring. persistTurn1 never writes turn-2 (B13 { deltas } + turn-1 checkpoint only). Duplicate of row 2 persist-hole. |
Product PR “drop matching it.fails for row 3” (envelope-wins / attach) converts this to it(). Test stays red — not because attach failed, because the fixture has no turn-2 user line. Inverse: they leave it wrapped and think row 3 is still broken after attach actually works. |
Titles were already cleaned once (quota vs stream-drop). This one still mixes attach DoD with continuous-persist DoD. Not runtime-wrong today (it.fails is green-as-fail). |
high |
| Minor | L4 | scripts/int-durable.gha.test.ts locks token, actions: read, ubuntu-latest, no self-hosted, no continue-on-error, no SHA pin. It does not lock the same-repo if: (head.repo.full_name == github.repository). |
A later edit drops the if:. Fork PRs run int-durable on ubuntu-latest, fail-closed at fetch-harness (HARNESS_REQUIRE=1, fork token cannot read origin harness-wasm). Not runner RCE. Plan required the skip so forks stay skipped, not red. |
Fork-on-GitHub-hosted is standard untrusted CI, not L2 Blocker. Still a plan-locked guard with no source-scan. | high |
Residual risk
Still an in-process Memory Blob/envelope driver, not signed Blob URL + live /api/turns SSE. Row 2 LIVE_ASSISTANT / turn-2 user expects stay red until continuous persist rewrites the fixture (allowed). Fork PRs skip the job; a required check will block forks unless GitHub is taught to allow skipped. Latest-main harness-wasm (WAIT_MS=0) can lag protocol; the green load test fail-closes, which is correct. setPersistSeamResolver is process-global; int project default isolate contains it today.
Merge guidance
- CONCERNS: do not merge until the Major is fixed (or explicitly accepted). DI/cost gate did not fire (no extra
PGlite/ live HTTP). - Required before merge: F5/C15 int rows must compose host restore (
CloudGetResulterror → keep local → LWWshouldAdoptBootServeron ok →coldAttachFromSnapshot/decideSendAttachon that snapshot), in a production helperHarnessHostactually calls. Envelope-wins then has one droppable seam and cannot green by overlaying a snapshotgetEnvelopethrows away. - Minors: retitle/move the turn-2 ring expect off row 3; lock the same-repo
if:in the yaml source-scan.
What was not attacked
Live DO self-hosted runner, prod AI Gateway, Vercel Workflows World, Playwright/next start, real browser F5 + quota ~5MB, fork-PR token surface (job skipped). CI durable-turn int at this head was green (33s).
Adversarial re-review CONCERNS: int F5/C15 rows read bootCloudSnapshot's error snapshot, which getEnvelope discards. Envelope-wins in the extract would green it.fails while kickColdAttach still no-ops. snapshotAfterCloudGet is the host seam (error → keep local, ok → LWW). HarnessHost.onAdopt and int F5 rows both use it. Ignore a running snapshot on action error. Retitle the turn-2 ring expect as row 2. Lock same-repo if: on int-durable.yml.
Adversarial-review follow-up (
|
| Finding | Fix |
|---|---|
F5/C15 int rows read bootCloudSnapshot error snapshot |
snapshotAfterCloudGet in lib/sessionBoot.ts: non-ok → keep local (ignore any error snapshot); ok → LWW. HarnessHost.onAdopt and int/f5-attach.int.test.ts both use it. Unit test: a running snapshot on action: 'error' still returns completed local. Source-scan: getEnvelope error return has no snapshot:; host + int file must call the helper. |
| Row 3 ring expect was a persist-hole lock | Retitled #859 row 2: F5 ring is not turn-1-only (worker transcript, not attach). |
yaml same-repo if: unlocked |
scripts/int-durable.gha.test.ts source-scan. |
Verified here: tsc --noEmit 0; npm run test:int 4 files / 11 passed (7 it.fails + 4 green); default-project changed files 149 passed; scripts/di-gate.mjs OK.
Envelope-wins for #859 now has to change snapshotAfterCloudGet (and getEnvelope if it should surface overlay). Overlay-only inside bootCloudSnapshot can no longer drop the F5 it.fails.
btipling
left a comment
There was a problem hiding this comment.
Adversarial review — PR #861
Verdict: CONCERNS
Repo: btipling/invincible
Scope: main ← plan/int-durable-860 @ 85e2073 · 19 files · int suite + boot/attach extract + GHA int-durable (third pass after two CONCERNS follow-ups)
Lenses run: L1, L2, L4, L6, L7, L8 (skip L3: no dual-chat / Wasm product path beyond thin kickColdAttach + onAdopt restore; skip L5: two Wasm instantiations in an opt-in project, not a poll/alloc storm; skip L9: no palette/UX)
AGENTS.md read: yes (tests bullet, vitest --project rule, no-wrapper). docs/feature-divide.md + SECURITY.md runner policy read.
Prior reviews at 375e794 and 1b68157 (both CONCERNS) were addressed in 1b68157 / 85e2073 for: getEnvelope→bootCloudSnapshot, test:changed --project, quota split, abort classify, fixture smoke, row titles, attach: spec, snapshotAfterCloudGet onAdopt+int, yaml same-repo scan. Those specific rows do not survive this pass. A new host-composition hole does.
Findings
| Sev | Lens | Finding | Break scenario | Refutation attempt | Confidence |
|---|---|---|---|---|---|
| Major | L1/L6 | F5/C15 int rows still compose BootCloudResult, not host CloudGetResult. int/driver.ts bootFromMemory returns bootCloudSnapshot (error includes snapshot: input.local). int/f5-attach.int.test.ts restoredAfterBoot passes that object to snapshotAfterCloudGet. Production does not. getEnvelope (lib/sessionRepository.ts) maps parse fail to { action: 'error'; status; message } — no snapshot (source-scan in lib/sessionRepository.test.ts locks the strip). bootCloudSession on error never calls onAdopt (keep local). HarnessHost.onAdopt only runs on action: 'ok' and hardcodes { action: 'ok', snapshot: serverSnap }. snapshotAfterCloudGet is typed { action: string; snapshot?: SessionSnapshot }, so BootCloudResult type-checks. |
Product PR implements envelope-wins as: (1) bootCloudSnapshot parse-fail returns overlayEnvelopeMeta(input.local, envelopeMeta) still action: 'error'; (2) snapshotAfterCloudGet honors got.snapshot on non-ok when it is running. Drops the matching it.fails. npm run test:int unexpected-passes then goes green. Default suite stays green: the getEnvelope source-scan still finds the no-snapshot error return. Operator F5: blob still { deltas } → getEnvelope error → bootCloudSession skips onAdopt → sessionRef stays stale completed → kickColdAttach no-op → Send POSTs → C15 409. Same doppelganger class as the last two reviews; snapshotAfterCloudGet did not close it because the int driver never applies the host GET mapping, and onAdopt never sees the error path the helper's docstring claims to match. Dummy local in getEnvelope vs real local in bootFromMemory is the same split: overlay-on-error of input.local is a different snapshot in int than anything repo.get can return. |
“snapshotAfterCloudGet is the attach/Send restore seam; both host and int call it.” False: host calls it only on the ok path with a CloudGetResult. Int calls it with a BootCloudResult whose error carries the snapshot getEnvelope throws away. “Unit test ignores a running snapshot on action error.” That test is in the default suite; a product PR that teaches snapshotAfterCloudGet to honor error snapshots will update it in the same PR as dropping it.fails — the getEnvelope strip stays. “Envelope-wins must also change getEnvelope (docstring).” Comments are not a ratchet. The last follow-up even hedged “and getEnvelope if it should surface overlay.” The int rows must fail until the mapping BootCloudResult → CloudGetResult (the strip) moves. |
high |
Residual risk
Still an in-process Memory Blob/envelope driver, not signed Blob URL + live /api/turns SSE. Row 2 LIVE_ASSISTANT / turn-2 user expects stay red until continuous persist rewrites the fixture (plan allowed). Fork PRs skip the job; a required check will block forks unless GitHub is taught to allow skipped. Latest-main harness-wasm (WAIT_MS=0) can lag protocol; the green load test fail-closes, which is correct. setPersistSeamResolver is process-global; int project default isolate contains it today. Naked node_modules/vitest/vitest.mjs run (merge-pr / AGENTS.md local-binary line) now collects the int project; default already fail-closes without wasm via lib/harnessChat.wasm-int.test.ts, so this is extra time + it.fails in the “full suite,” not a new supply gate.
Merge guidance
- CONCERNS: do not merge until the Major is fixed (or explicitly accepted). DI/cost gate did not fire (Memory stores, no extra
PGlite/ live HTTP). - Required before merge: extract the host GET mapping (
BootCloudResult→CloudGetResult, error has no snapshot) into a production helpergetEnvelopeactually returns.bootFromMemorymust return thatCloudGetResult.snapshotAfterCloudGetmust takeCloudGetResult(soBootCloudResultcannot type-check). Envelope-wins then cannot green F5 by overlaying a snapshotgetEnvelopediscards; it has to change the mapping both host and int use. - No remaining minors that independently block.
What was not attacked
Live DO self-hosted runner, prod AI Gateway, Vercel Workflows World, Playwright/next start, real browser F5 + quota ~5MB, fork-PR token surface (job skipped). CI durable-turn int at 85e2073 was green (29s).
Adversarial re-review CONCERNS at 85e2073: bootFromMemory returned BootCloudResult (error includes snapshot). Envelope-wins in bootCloudSnapshot + snapshotAfterCloudGet could drop it.fails while getEnvelope still discarded the snapshot and kickColdAttach no-op'd. cloudGetFromBoot is the host GET mapping. getEnvelope and the int driver both use it. snapshotAfterCloudGet takes CloudGetResult so BootCloudResult cannot type-check as the restore input.
Adversarial-review follow-up (
|
| Finding | Fix |
|---|---|
F5/C15 int rows composed BootCloudResult (error includes snapshot) |
cloudGetFromBoot in lib/sessionRepository.ts: error → { action: 'error', status: 0, message } no snapshot; ok → snapshot through. getEnvelope and int/driver.ts bootFromMemory both return it. snapshotAfterCloudGet now takes CloudGetResult (BootCloudResult cannot type-check). Unit: overlay running on error is discarded. Source-scan: getEnvelope calls cloudGetFromBoot; driver does not return bootCloudSnapshot(. |
Envelope-wins for #859 now has to change cloudGetFromBoot (so host GET and int stay one mapping) and snapshotAfterCloudGet. Overlay-only inside bootCloudSnapshot cannot drop the F5 it.fails.
Verified here: tsc --noEmit 0; npm run test:int 4 files / 11 passed (7 it.fails + 4 green); default-project sessionRepository + sessionBoot + turnAttach 144 passed; scripts/di-gate.mjs OK.
btipling
left a comment
There was a problem hiding this comment.
Adversarial review — PR #861
Verdict: CONCERNS
Repo: btipling/invincible
Scope: main ← plan/int-durable-860 @ 11ae1d6 · 19 files · int suite + boot/attach extract + GHA int-durable (fourth pass after three CONCERNS follow-ups)
Lenses run: L1, L2, L4, L6, L7, L8 (skip L3: no dual-chat / Wasm product path beyond thin kickColdAttach + onAdopt restore; skip L5: two Wasm instantiations in an opt-in project, not a poll/alloc storm; skip L9: no palette/UX)
AGENTS.md read: yes (tests bullet, vitest --project rule, no-wrapper). docs/feature-divide.md + SECURITY.md runner policy read.
Prior reviews at 375e794, 1b68157, and 85e2073 (all CONCERNS) were addressed in 1b68157 / 85e2073 / 11ae1d6 for: getEnvelope→bootCloudSnapshot, test:changed --project, quota split, abort classify, fixture smoke, row titles, attach: spec, snapshotAfterCloudGet onAdopt+int, yaml same-repo scan, cloudGetFromBoot mapping. Those specific rows do not survive this pass. A new host-composition hole does.
Findings
| Sev | Lens | Finding | Break scenario | Refutation attempt | Confidence |
|---|---|---|---|---|---|
| Major | L1/L6 | F5/C15 int rows still compose a wider restore than the host. (1) int/f5-attach.int.test.ts restoredAfterBoot calls snapshotAfterCloudGet(local, boot) on every CloudGetResult, including action: 'error'. Production bootCloudSession (lib/sessionBoot.ts) on error/disabled never calls onAdopt — HarnessHost therefore never runs snapshotAfterCloudGet on parse-fail. The helper's own docstring tells the #859 PR to change this function and cloudGetFromBoot for envelope-wins. (2) int/driver.ts bootFromMemory passes the real completed local into bootCloudSnapshot. Production getEnvelope (lib/sessionRepository.ts) passes dummy { id, updatedAt: 0, messages: [] }. |
Bypass A (docstring path). Product PR: cloudGetFromBoot puts envelope running / turnRunId on the error result; snapshotAfterCloudGet overlays those onto local when action !== 'ok'. Drop the matching it.fails. npm run test:int unexpected-passes then goes green. Default suite source-scans still pass (getEnvelope error has no snapshot:; cloudGetFromBoot error strip can keep status/message and add sibling fields). Operator F5: blob still { deltas } → getEnvelope error → bootCloudSession skips onAdopt → sessionRef stays stale completed → kickColdAttach no-op → Send POSTs → C15 409. Bypass B (parse-fail → ok). Product PR: bootCloudSnapshot parse-fail returns { action: 'ok', snapshot: overlayEnvelopeMeta(input.local, envelopeMeta) }; snapshotAfterCloudGet adopts a running non-empty server snapshot (keeps the existing empty-mint LWW guard). Int overlay is the real local (has turn-1 messages) → adopt → cold attach. Host overlay is the dummy (empty, updatedAt: 0) → empty-mint guard keeps completed local → restored === local early-return skips kickColdAttach. Same C15 409. |
“snapshotAfterCloudGet already returns local on non-ok, so int matches skip-onAdopt.” True today. False the moment the product PR does what the docstring tells it to do — teach that helper to overlay on error. Skip-onAdopt is a different function (bootCloudSession) that int never calls. “cloudGetFromBoot is the mapping both host and int use.” It is the GET mapping. Restore happens after GET. Host restore is if (ok) onAdopt → snapshotAfterCloudGet; int restore is snapshotAfterCloudGet on the raw result. “Equal updatedAt means overlay of real local still loses LWW, so bypass B cannot green.” Bypass B includes the LWW change (running + non-empty). That change is the path of least resistance: shouldAdoptServer already refuses empty mint over dialogue, so “adopt running if the overlay has messages” greens int and leaves host on dummy-empty. “Unit tests lock error has no snapshot.” They lock snapshot: on the error object, not sibling envelope fields, and they do not lock parse-fail staying action: 'error' once bootCloudSnapshot is allowed to return ok. |
high |
Residual risk
Still an in-process Memory Blob/envelope driver, not signed Blob URL + live /api/turns SSE. Row 2 LIVE_ASSISTANT / turn-2 user expects stay red until continuous persist rewrites the fixture (plan allowed). Fork PRs skip the job; a required check will block forks unless GitHub is taught to allow skipped. Latest-main harness-wasm (WAIT_MS=0) can lag protocol; the green load test fail-closes, which is correct. setPersistSeamResolver is process-global; int project default isolate contains it today. Naked node_modules/vitest/vitest.mjs run (merge-pr / AGENTS.md local-binary line) now collects the int project; default already fail-closes without wasm via lib/harnessChat.wasm-int.test.ts, so this is extra time + it.fails in the “full suite,” not a new supply gate.
Merge guidance
- CONCERNS: do not merge until the Major is fixed (or explicitly accepted). DI/cost gate did not fire (Memory stores, no extra
PGlite/ live HTTP). - Required before merge: (1) One restore helper for all
CloudGetResultactions (snapshotAfterRepoGet: non-ok →local; ok → existing LWW). Int F5/C15 rows call that, notsnapshotAfterCloudGeton a raw GET. (2)bootCloudSessionmust invoke a host callback on error/disabled (onGetMiss) that runs the same helper — today a no-op (restored === local); envelope-wins on parse-fail then activates +kickColdAttachon both paths. (3)getEnvelopeandbootFromMemorymust pass the same dummy local intobootCloudSnapshot(extractgetEnvelopeParseLocal). Overlay-of-input.localthen loses empty-mint LWW on both sides; envelope-wins has to merge turn carriers onto the caller's local in the restore helper. - No remaining minors that independently block.
What was not attacked
Live DO self-hosted runner, prod AI Gateway, Vercel Workflows World, Playwright/next start, real browser F5 + quota ~5MB, fork-PR token surface (job skipped). CI durable-turn int at 11ae1d6 was green (29s).
Adversarial re-review CONCERNS at 11ae1d6: int F5/C15 rows called snapshotAfterCloudGet on error CloudGetResults (bootCloudSession never does — onAdopt is ok-only) and bootFromMemory passed the real local into bootCloudSnapshot (getEnvelope passes an empty mint). Envelope-wins in snapshotAfterCloudGet or overlay-of-input.local would drop it.fails while kickColdAttach still no-op'd. snapshotAfterRepoGet is the restore seam for every GET action. Int F5 rows and HarnessHost onAdopt/onGetMiss use it. bootFromMemory uses getEnvelopeParseLocal. onGetMiss is a no-op today (result is local).
Adversarial-review follow-up (
|
| Finding | Fix |
|---|---|
F5/C15 int rows composed a wider restore than the host (error CloudGetResult into snapshotAfterCloudGet; real local into bootCloudSnapshot) |
snapshotAfterRepoGet (lib/sessionBoot.ts): non-ok → local; ok → existing LWW. Int F5 rows and HarnessHost.onAdopt both use it. bootCloudSession onGetMiss runs the same helper on error/disabled (today a no-op). getEnvelope and int/driver.ts both pass getEnvelopeParseLocal (empty mint). Source-scan: F5 must not call snapshotAfterCloudGet; driver must not pass opts.local; host must wire onGetMiss. |
Envelope-wins for #859 now has to change snapshotAfterRepoGet (so host onGetMiss and int stay one restore). Overlay-only inside bootCloudSnapshot / teaching snapshotAfterCloudGet to honor error snapshots cannot drop the F5 it.fails.
Verified here: tsc --noEmit 0; npm run test:int 4 files / 11 passed (7 it.fails + 4 green); default-project sessionRepository + sessionBoot + turnAttach + yaml 156 passed; scripts/di-gate.mjs OK.
btipling
left a comment
There was a problem hiding this comment.
Adversarial review — PR #861
Verdict: PASS WITH NOTES
Repo: btipling/invincible
Scope: main ← plan/int-durable-860 @ b067a4d · 19 files · int suite + boot/attach extract + GHA int-durable (fifth pass after four CONCERNS follow-ups)
Lenses run: L1, L2, L4, L6, L7, L8 (skip L3: no dual-chat / Wasm product path beyond thin kickColdAttach + onAdopt/onGetMiss restore; skip L5: two Wasm instantiations in an opt-in project, not a poll/alloc storm; skip L9: no palette/UX)
AGENTS.md read: yes (tests bullet, vitest --project rule, no-wrapper). docs/feature-divide.md + SECURITY.md runner policy read.
Prior reviews at 375e794, 1b68157, 85e2073, and 11ae1d6 (all CONCERNS) were addressed in 1b68157 / 85e2073 / 11ae1d6 / b067a4d for: getEnvelope→bootCloudSnapshot, test:changed --project, quota split, abort classify, fixture smoke, row titles, attach: spec, snapshotAfterCloudGet onAdopt+int, yaml same-repo scan, cloudGetFromBoot mapping, snapshotAfterRepoGet + onGetMiss + dummy getEnvelopeParseLocal. Those specific host-composition holes do not survive this pass. Attack did not breach a new Major.
Findings
| Sev | Lens | Finding | Break scenario | Refutation attempt | Confidence |
|---|---|---|---|---|---|
| Minor | L1 | bootCloudSession onGetMiss (lib/sessionBoot.ts) is (got) => void — no GET target id. HarnessHost overlays onto sessionRef.current and activates with id: local.id. |
Operator has local session B loaded, URL pins ?s=A. A's blob is unreadable, A's envelope is running. Product #859 overlay in snapshotAfterRepoGet (the path this PR's comments teach) applies A's turnRunId onto B, then activateSession({ ...restored, id: B }). Cold-attach subscribes B's ring to A's run. Same-id F5 (#859) is fine; pin-of-other-id + parse-fail is the hole this seam just added. |
“Error keeps local (#411 never-blank); overlay is same-id only.” True today (snapshotAfterRepoGet returns local on non-ok, so the id mix cannot fire). False the moment the product PR does what the docstring says. onAdopt already receives the GET id; onGetMiss should too, and the host must refuse overlay when local.id !== id. |
high |
| Minor | L1 | Parse-error path always onUrlUpdate(null) after onGetMiss (lib/sessionBoot.ts · bootCloudSession). activateSession does not pin ?s=. |
Product overlay makes onGetMiss activateSession + kickColdAttach. Int F5/C15 go green (snapshotAfterRepoGet only). Host attaches then unpins. Share/reload without that tab's localStorage (quota row, other device) mints instead of pinning the live run. Ok-path onAdopt still pins. The comment “envelope-wins only changes snapshotAfterRepoGet” is a false product map for URL identity. |
“kind: 'local' means keep local and clear a stale ?s= that points at an unreadable row.” Correct today (onGetMiss is a no-op). After envelope-wins the row is the live run — unpin is the opposite of ok-path adopt. Int cannot catch this: it never calls bootCloudSession. |
high |
| Nit | L8 | AGENTS.md tests bullet (touched this PR) says vitest 3 runs every test.projects entry unless --project is passed, then still tells agents to invoke node_modules/vitest/vitest.mjs run with no flags. merge-pr follows that line, so the full merge-green suite collects int/ (it.fails + extra wasm). Goal 4 isolated int from npm test only. |
merge-pr of a later commit that breaks int/loadBridge or unexpected-passes an it.fails does fail, which is good; merge-pr of this PR stays green (7 expected fails). Confusion: operators treat naked vitest run as the default green gate and now see it.fails in it. Not a supply-gate (lib/harnessChat.wasm-int.test.ts already fail-closes). |
“Plan only required --project on npm test.” True. The bullet this PR edited is still internally contradictory. |
high |
Residual risk
Still an in-process Memory Blob/envelope driver, not signed Blob URL + live /api/turns SSE. Row 2 LIVE_ASSISTANT / TURN2_USER expects stay red after envelope-wins (fixture never persisted turn 2) until continuous persist rewrites them (plan allowed). Fork PRs skip the job; a required check will block forks unless GitHub is taught to allow skipped. Latest-main harness-wasm (WAIT_MS=0) can lag protocol; the green load test fail-closes, which is correct. setPersistSeamResolver is process-global; int project default isolate contains it today. CloudGetResult error has no envelope fields yet — envelope-wins must thread liveness through cloudGetFromBoot and snapshotAfterRepoGet; overlay-only inside bootCloudSnapshot still cannot drop the F5 it.fails.
Merge guidance
- PASS WITH NOTES: safe to merge from this attack; Minors should land on this branch (or the first #859 product PR) so envelope-wins cannot pin the wrong session or unpin
?s=after a successful restore. - DI/cost gate did not fire (Memory stores, no extra
PGlite/ live HTTP). - Required for a clean #859 follow-through: (1)
onGetMiss(got, id)+ host refuse whenlocal.id !== id; (2)onGetMissreturn'adopted'sobootCloudSessionpinslocalIdinstead ofonUrlUpdate(null).
What was not attacked
Live DO self-hosted runner, prod AI Gateway, Vercel Workflows World, Playwright/next start, real browser F5 + quota ~5MB, fork-PR token surface (job skipped). CI durable-turn int at b067a4d was green (23s).
Adversarial PASS WITH NOTES at b067a4d: envelope-wins in snapshotAfterRepoGet would overlay a pinned session's run onto a different local (onGetMiss had no GET id) and then onUrlUpdate(null) would unpin after a successful restore. onGetMiss(got, id): host refuses when local.id !== id. Return 'adopted' so bootCloudSession pins the GET id instead of clearing ?s=. Today snapshotAfterRepoGet still returns local on error (no-op, still unpins). AGENTS.md notes naked vitest collects the int project.
Adversarial-review follow-up (
|
| Finding | Fix |
|---|---|
onGetMiss had no GET target id |
(got, id). Host refuses when local.id !== id (pinned ?s=A cannot paint A's run onto local B). |
Parse-error path always onUrlUpdate(null) after onGetMiss |
Return 'adopted' when restore actually changed the snapshot; bootCloudSession then pins the GET id. Today snapshotAfterRepoGet still returns local on error — still unpins. |
Naked vitest run collects int |
AGENTS.md tests bullet: merge-pr / local-binary collects every project; extra wasm + it.fails, not a second supply gate. |
Verified here: tsc --noEmit 0; default-project sessionBoot + turnAttach + sessionRepository + yaml 158 passed; scripts/di-gate not re-run (no I/O constructors).
Envelope-wins for #859 still changes snapshotAfterRepoGet (and threads envelope liveness through cloudGetFromBoot). Overlay-only inside bootCloudSnapshot cannot drop the F5 it.fails. The host will now refuse a cross-session overlay and keep ?s= when restore adopts.
Summary
Process-level integration suite that locks #859 as failing desired-contract tests before any persist/attach product patch. Wasm is the ring assertion surface; SUT is persist + envelope + boot attach + C15 Send remap.
Fixes #860
Refs #859 · Refs #794
What landed
int(int/**/*.int.test.ts).npm testis nowvitest run --project default --project tenancyso the int project is not collected (vitest 3 runs every project otherwise).npm run test:int— green because ofit.fails, not because the contract holds.bootCloudSnapshot(lib/sessionRepository.ts),coldAttachFromSnapshot(lib/turnAttach.ts).HarnessHostkeepsconst kickColdAttach = useCallback. Nolib/turnBoot.ts. No persist/attach product patch..github/workflows/int-durable.yml—ubuntu-latest, same-repo PR + main,actions: read, latest mainharness-wasm, not self-hosted, notcontinue-on-error.AGENTS.mdtests bullet:--projectflags, fail-closed wasm load,it.failsonly on contract expects. Timeless (no issue numbers).Test matrix
int/load.int.test.tsint/persist-parse.int.test.tsit.failsint/f5-attach.int.test.tsit.fails+ one green (envelope staysrunningafter abort)int/quota.int.test.tsit.failslib/sessionRepository.test.ts+lib/turnAttach.test.tsVerification (agent workspace)
npm run typechecknpm run test:intit.fails+ 2 green)int/vitest run --changed --project default --project tenancyscripts/di-gate.mjsscripts/drizzle-journal-gate.mjsnpm run buildExpected CI: new required-path job int-durable (not a GitHub required check until origin UI). Default
testcheck if any is unchanged aside fromnpm testargv.Out of scope (locked)
No Playwright, wasmtime,
next start, Vercel World, C15 weaken,/api/agentfallback, Wasm/protocol change, nested'use step', product persist schema / envelope-wins / quota visibility.Product PRs for #859 drop the matching
it.failsin the same PR as the fix.