You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
DOM (boot driver extract, SessionStore) · harness (Wasm = assertion surface only, no Zig) · Vercel backend (in-process persist seam + envelope; no route change)
Reusability impact
none — config-only CI (GITHUB_TOKEN / existing fetch-harness). No origin URL, droplet, or World secret hard-bind
Production mutate?
no
Cloud ops path
N/A for Production data/secret/env. CI ships: new GHA int-durable (ubuntu-latest, PR + main)
Living docs
AGENTS.md tests bullet: npm run test:int + npm test--project flags. No issue numbers. Other surfaces N/A
Review notes (2026-08-27)
Field
Value
Reviewed
2026-08-27
Verdict
HANDOFF-READY
Reviewer
plan-review (mode=fix)
Majors landed: (1) vitest run runs all projects — npm test must pass --project default --project tenancy or the int project is collected. (2) it.fails would swallow a missing-wasm throw — fail-closed load is a green test; only contract expects are it.fails. (3) Do not add lib/turnBoot.ts — extract into existing modules (sessionRepository / turnAttach). Minors: GHA permissions: actions: read; AGENTS.md timeless (no issue numbers).
Source issue #860 is this issue. Body below is the implementable plan. Product patches for #859 stay on later PRs.
Summary
Ship a process-level integration suite that reproduces #859 as failing desired-contract tests before any persist/attach product patch. Wasm is the operator-visible ring after hydrate, not the SUT. The SUT is persist + envelope + boot attach + C15 Send remap. One PR, one review, merge. Default npm test stays green. npm run test:int stays green because of it.fails, not because the contract holds.
next start, auth cookie, local Workflow World, Vercel World, workflows-smoke extension.
DO droplet / invincible-do-1 / self-hosted runner (Zig compile only).
Rewriting turnLoop.test.ts (it locks{ deltas } as persist content until a product PR).
React mount of HarnessHost.
Layer 2/3 (HTTP POST /api/turns, preview Vercel smoke).
Forbidden wiring: dual DOM chat · secrets in Wasm · weakening C15 · /api/agent fallback · wrapping runAgentStream in one 'use step' · Wasm/protocol change · nested 'use step' · continue-on-error on the int job · it.skip when wasm is missing · laptop-only Production ops · phase/issue theater in docs/*
Intent lock
In scope: vitest project int + int/** helpers/tests; behavior-preserving extract of boot/attach spec from HarnessHost so the driver does not mount React; GHA int-durable.yml; one AGENTS.md line.
Out of scope: anything that makes a #859 row pass (except the extract remaining behavior-identical to main).
Forbidden: dual DOM chat · secrets in Wasm · laptop-only Production ops · phase/issue theater in product docs
Architectural decisions
Decision
Options considered
Choice
Why
Runtime
Playwright next start / wasmtime / Node WebAssembly.instantiate
harness.wasm imports dvui. Node already instantiates it (lib/harnessChat.wasm-int.test.ts). wasmtime would be a new product and would not have caught pointer clobber / C15 remap
F5
Document reload / new tab folklore / new JS heap
New JS heap (new wasm instance + load envelope/blob + hydrate + attach spec)
E19 decideAttachClass is heap state (heapApplied), not a document. Matches #857
MemoryBlobTranscriptStore + MemorySessionStore + real createTurnPersistSeam / persistStep
Real persist + parser. Fake the model and tools only. Do not stub the persist seam or parseCloudSessionSnapshot
HTTP
Drive POST /api/turns / in-process persist
In-process persist + parser + boot
Layer 2 (next start + Workflow World) is a later issue. Local World is the false-green workflows-smoke forbids
Host driver
New lib/turnBoot.ts / mount React / extract into existing modules
bootCloudSnapshot in lib/sessionRepository.ts (next to parseCloudSessionSnapshot / overlayEnvelopeMeta; mirrors inner getEnvelope two-step, no HTTP). coldAttachFromSnapshot in lib/turnAttach.ts (next to decideAttachClass). HarnessHostkeepsconst kickColdAttach = useCallback
lib/sessionBoot.ts is URL/mint/adopt identity — do not overload it. Do not add a third boot module. Extract is behavior-preserving of today's boot (parse fail → keep local; overlay never runs). Do not overlay envelope running on parse error — that is the #859 product fix
Red ratchet
continue-on-error / it.skip / wrap the whole file in it.fails / split load vs contract
Greenit('loads harness.wasm') fail-closed (throw, never skip). it.fails only around desired-contract expects, tagged by #859 row. beforeAll / loadBridge() stay outsideit.fails
If loadBridge throws inside it.fails, missing wasm looks like the expected failure (false green). Product PR that fixes a row must drop that it.fails in the same PR or unexpected-pass reds CI
Gate vs npm test
Exclude int/** from default only / separate config file / explicit --project on npm test
Vitest project int + default exclude: ['int/**']and"test": "… && vitest run --project default --project tenancy". npm run test:int = vitest run --project int (no wrapper script)
Vitest 3 vitest run with test.projects runs every project. Excluding int/** from default is not enough — the int project would still run under naked vitest run
CI runner
DO self-hosted / ubuntu-latest
ubuntu-latest only. Fetch mainharness-wasm via existing scripts/fetch-harness-artifact.mjs
DO is Zig compile (docs/runner.md). Fork PRs never run there. App int is trusted Node
Artifact SHA
Wait for this PR's GITHUB_SHA / compile Zig here / latest main harness-wasm
Latest main artifact. Unset HARNESS_COMMIT_SHA. HARNESS_WAIT_MS=0. HARNESS_REQUIRE=1
This PR does not touch Zig. fetch-harness already ignores harness-wasm-pr-*. Waiting on the PR SHA would fail-closed or stall
No int project. npm test = di-gate + drizzle-journal-gate + vitest run
test:int script
package.json
absent
vitest
package.jsonvitest: ^3.0.5
it.fails is available
wasm-int already in default suite
**/*.{test,spec} includes lib/harnessChat.wasm-int.test.ts
Leave it there. Int suite is additive
fetch-harness token
scripts/fetch-harness-artifact.mjs
HARNESS_ARTIFACT_TOKEN || GH_TOKEN || GITHUB_TOKEN. Artifact name harness-wasm (main only)
Existing GHA
.github/workflows/*
Nonpm test / wasm-int GHA today. build-harness is Zig on self-hosted; workflows-smoke is dispatch + Vercel World. Neither is this job
HARNESS_ARTIFACT_TOKEN
AGENTS.md infra Done (Vercel prebuild)
GHA job uses default GITHUB_TOKEN (same repo). No new secret
DO runner
AGENTS.md / docs/runner.md
Zig compile only. Out of scope
pushSessionToBridge
lib/harnessChat.ts (exported)
Hydrate path for heap B ring
Design
Driver shape (heap A → drop → heap B)
heap A (originating tab)
MemoryBlobTranscriptStore + MemorySessionStore
createTurnPersistSeam / persistStep (real)
optional: loadBridge() + runHarnessTurn with fake model
→ host-shaped envelope upsert (turnStatus: running, turnRunId)
→ drop the reader (AbortController) before persistStep // rows 5–6
→ or let persistStep run // row 1 fixture
heap B (F5 = new JS heap)
new wasm instance (rows that assert the ring)
bootCloudSnapshot({ local, envelopeMeta, blobJson })
// mirrors inner getEnvelope: parseCloudSessionSnapshot(blob)
// ok → overlayEnvelopeMeta(parsed, envelopeMeta)
// fail → action error, snapshot = local (TODAY)
pushSessionToBridge(wasmB, snapshot)
coldAttachFromSnapshot(snapshot)
decideSendAttach({ turnStatus, turnRunId, heapApplied: null, ... })
assert inv_message_*_at
Fake model: deterministic tool_start / text_delta / done (second prompt for the turn-2 user line). Fake sandbox tools. Do not mock persist or the parser.
getEnvelope stays unexported. The int driver calls parseCloudSessionSnapshot + overlayEnvelopeMetadirectly with in-memory envelope + blob bytes — the same two-step, no fetch.
Behavior-preserving extract (existing modules — not lib/turnBoot.ts)
HarnessHostkickColdAttachremains a useCallback that calls coldAttachFromSnapshot(sessionRef.current) then runPrompt('', { attach }). Do not rename the hook; lib/turnAttach.test.ts source-scan stays green. Green unit tests of today's extract semantics live in lib/sessionRepository.test.ts + lib/turnAttach.test.ts (parse fail keeps local; coldAttachFromSnapshot none when completed).
Do not change overlay-on-error, envelope-wins, or Send remap. That is #859 product work.
Test matrix (desired contract; must fail on main today)
persistStep / createTurnPersistSeam.persist writes a blob; parseCloudSessionSnapshot(blob)
not null. Envelope GET/bootCloudSnapshot hydrates latest user line + assistant. checkpointPointer is either unnecessary or boot-readable
no
int/persist-parse.int.test.ts
2
3 stale local
Heap A leaves envelope turnStatus: running + turnRunId. Heap B local is completed / missing turn 2. Blob may be {deltas} (unreadable)
coldAttachFromSnapshot / decideAttachClass → cold, not none. Ring is not stuck on turn-1-only
yes
int/f5-attach.int.test.ts
3
C15 last click
Heap B Send while envelope live, local completed
decideSendAttach → GET-attach, not { kind: 'none' } (POST). A true second POST while live still 409s (C15 preserved — unit already exists; do not weaken)
yes
int/f5-attach.int.test.ts
4
5 quota
Polyfill localStorage that throws QuotaExceededError on a later LocalStorageSessionStore.save of running + turn-2 user
Failure is not silent. Boot must not trust a truncated snapshot as if the save succeeded. No Chromium
no
int/quota.int.test.ts
5
4 stream-drop
Abort after headers, no SSE done
Envelope stays running (overlap #849). Heap B attaches. Empty-EOF does not spin (decideHotResume already refuses; assert it)
yes
int/f5-attach.int.test.ts
6
2 persist not continuous
Mid-turn abort
Blob/envelope have the user line + whatever host persist actually wrote. Heap-B ring is not live interleaving. Documents the hole; may stay it.fails until a later continuous-persist issue
yes
int/f5-attach.int.test.ts
Row 1 first in the PR. It is the one-file test that would have blocked B13. No Wasm.
Realistic fixture for rows 2–3–5 (incident shape): turn 1 persistStep completed ({ deltas } on transcriptPointer); turn 2 started (envelope running + turnRunId); reader aborted; heap B local is turn-1 completed.
"test": "… && vitest run --project default --project tenancy" (required). "test:int": "vitest run --project int". Optionally prefix test:int with node scripts/di-gate.mjs &&. Not a wrapper around vitest
lib/sessionRepository.ts
Add bootCloudSnapshot. Unit-test today's semantics (parse fail keeps local) in lib/sessionRepository.test.ts — green
lib/turnAttach.ts
Add coldAttachFromSnapshot. Unit-test in lib/turnAttach.test.ts — green
app/harness/HarnessHost.tsx
Hook body calls coldAttachFromSnapshot. Behavior-identical
int/loadBridge.ts
Same fail-closed loader as wasm-int (dvui stub + WebAssembly.instantiate). Duplicate OK; do not churn the green wasm-int file unless a one-line import is trivial
int/stores.ts
Wire MemoryBlobTranscriptStore + MemorySessionStore + createTurnPersistSeam like prod
int/driver.ts
Heap A/B: bootCloudSnapshot, hydrate, attach spec, ring readback
int/persist-parse.int.test.ts
Matrix row 1 it.fails
int/f5-attach.int.test.ts
Rows 2, 3, 5, 6 it.fails
int/quota.int.test.ts
Row 4 it.fails
.github/workflows/int-durable.yml
See Cloud ops
AGENTS.md
One line under Working rules / tests: npm run test:int is the durable-turn int project; excluded from npm test; fail-closed wasm; it.fails until the matching #859 product PR
Do not rewrite turnLoop.test.ts. Do not add docs/*.
it.fails convention
it('loads harness.wasm (fail-closed)',async()=>{constbridge=awaitloadBridge();// throw if missing — NOT it.failsexpect(bridge.protocolVersion()).toBeGreaterThanOrEqual(11);});it.fails('#859 row 1: worker persist blob parses as SessionSnapshot',async()=>{expect(parseCloudSessionSnapshot(JSON.parse(blobContent),sessionId)).not.toBeNull();});
Wasm rows: await loadBridge() (or beforeAll) above the it.fails body. Only the desired-contract expects sit inside it.fails.
When the product PR makes the expect pass, it.fails fails (unexpected pass). That PR drops it.fails (becomes it) in the same commit. Never continue-on-error. Never it.skip.
Cloud ops path
Production mutate: N/A. No migrate / backfill / seed / env cutover / secret dual-store.
CI (not a human Production button, but the cloud operator path for the lock):
Item
Lock
Primary operator surface
GitHub Actions → int-durable on pull_request + pushmain (not dispatch-only). Origin required check is GitHub branch-protection UI — out of this PR; shipping the workflow is enough
Workflow to add
.github/workflows/int-durable.yml
runs-on
ubuntu-latest only. Never self-hosted / invincible-do-1 / vars.RUNNER_LABELS
actions/checkout@v4 → actions/setup-node@v4 (Node 22) → npm ci → npm run fetch-harness → npm run test:int
Wasm supply
scripts/fetch-harness-artifact.mjs. Token: default GITHUB_TOKEN (already in GHA). Do not set HARNESS_COMMIT_SHA. HARNESS_WAIT_MS=0. HARNESS_REQUIRE=1. Artifact name harness-wasm from main. Fail-closed if missing
Secrets (names only)
none new. Do not add VERCEL_TOKEN / World / DATABASE_URL. Do not require HARNESS_ARTIFACT_TOKEN (that's Vercel prebuild)
permissions
contents: read + actions: read (artifact list/download). No write, no id-token
Guards
timeout-minutes: 15; concurrency group int-durable-${{ github.ref }} cancel-in-progress; never continue-on-error
Explicit non-paths
notbuild-harness; notworkflows-smoke; not DO; notnpm test (would exclude int anyway); not personal laptop npm as the prove
After job
none (no deploy, no env flip)
Living docs plan
Surface
Change
Notes
docs/*
N/A
No operator product behavior change. Session-model / runner / feature-divide stay with H23–H25. Do not narrate #860 / phases
AGENTS.md
Update Working rules tests bullet: npm test = di-gate + drizzle-journal-gate + vitest run --project default --project tenancy; npm run test:int = vitest run --project int; fail-closed wasm; it.fails only on persist/boot contract rows
Timeless. Do not cite issue numbers
README.md
N/A
Visitors do not run this suite
SECURITY.md
N/A
No new secret, no runner-policy change (ubuntu-latest, same-repo)
.env.example
N/A
No new env. Reuses GITHUB_TOKEN / fetch-harness
Caps table
No existing cap is raised or lowered. No product cap added.
Cap / ceiling
Value
Rationale
Code location
HARNESS_SESSION_MAX_BODY_BYTES
unchanged
Not touched
lib/sessionCloudCaps.ts
TURN_STREAM_CURSOR_MAX
unchanged
Not touched
lib/sessionCloudCaps.ts
wasm-int fail-closed supply
throw, never it.skip
Reuse
int/loadBridge.ts (same rule as lib/harnessChat.wasm-int.test.ts)
NEW int project testTimeout
30000 ms
Wasm instantiate + two heaps; generous NEW; not a product/transport cap
vitest.config.ts project int
GHA timeout-minutes
15
Generous NEW job budget
.github/workflows/int-durable.yml
Quota test uses a tiny fake store cap as a test double (not a product cap, not HARNESS_SESSION_MAX_BODY_BYTES). Do not change the ~5MB browser folklore into a shipped constant.
Implementation order
vitest.config.ts project int + default exclude int/** + package.jsontest gains --project default --project tenancy and test:int. Confirm vitest run --project default --project tenancy collect does not pick up int/**. Naked vitest run would — that is why npm test must pass the flags.
int/stores.ts + int/persist-parse.int.test.ts (row 1 it.fails). Prove: persistStep content → parseCloudSessionSnapshot is null on main, wrapped in it.fails. No wasm in this file.
bootCloudSnapshot + coldAttachFromSnapshot + thin HarnessHost call. Default-suite unit tests: parse fail keeps local (green). lib/turnAttach.test.ts source-scan still green.
int/quota.int.test.ts (row 4 it.fails) against real LocalStorageSessionStore + throwing polyfill.
.github/workflows/int-durable.yml (same PR — do not defer CI).
AGENTS.md one line.
Agent workspace: npm test green, npm run typecheck green, npm run test:int green via it.fails.
Testing
#
Case
Layer
Type
Command / method
1
Row 1 pointer clobber
API/persist
integration (it.fails)
npm run test:int
2
Row 2 stale local → cold attach
DOM + harness
integration (it.fails)
npm run test:int
3
Row 3 Send → GET-attach
DOM + harness
integration (it.fails)
npm run test:int
4
True double-POST still 409
API
unit (already exists)
npm test — do not weaken
5
Row 4 quota not silent
DOM
integration (it.fails)
npm run test:int
6
Row 5 stream-drop keeps running
DOM + harness
integration (it.fails)
npm run test:int
7
Row 6 persist hole documented
DOM + harness
integration (it.fails)
npm run test:int
8
Missing wasm throws
harness
integration (green, not it.fails)
npm run test:int without public/harness/harness.wasm → non-zero, not skip, not swallowed
9
Default suite unchanged
—
unit + wasm-int
npm test (--project default --project tenancy) green (no int/**)
10
Extract semantics
DOM
unit
bootCloudSnapshot parse-fail keeps local; coldAttachFromSnapshot none on completed — green in default suite
11
kickColdAttach source-scan
DOM
unit
lib/turnAttach.test.ts still finds the hook
12
typecheck
—
gate
npm run typecheck
13
GHA job
GHA
operator
int-durable on the PR; ubuntu-latest; fetch-harness + test:int
Minimum locked for DoD: rows 1, 2, 3, 9, 12, 13. Rows 4–6 ship in the same PR (source lock) but 1–3 are the B13/F5/C15 ratchet.
Definition of done
Vitest project int + npm run test:int (vitest run --project int). npm test = di-gate + drizzle-journal-gate + vitest run --project default --project tenancy (does not collect int/**; still green on main).
Matrix row 1 ships and is it.fails on main: worker persist blob is not a SessionSnapshot today.
Matrix rows 2–3 ship (real wasm, two heaps): stale local completed + envelope running → attach, not none; Send → GET not POST. it.fails on main.
Matrix row 4: quota-bounded store, silent save is a failed contract. it.fails on main.
Extract is behavior-preserving (bootCloudSnapshot / coldAttachFromSnapshot in existing modules; nolib/turnBoot.ts). HarnessHost keeps const kickColdAttach = useCallback; lib/turnAttach.test.ts still green.
Fail-closed wasm supply: a greenit('loads harness.wasm') throws if missing (never it.skip; never inside it.fails).
GHA int-durable on ubuntu-latest (same-repo PR + main). Not self-hosted. Not continue-on-error. Fetches main harness-wasm.
No Playwright, no wasmtime, no next start, no Vercel World, no product persist/attach patch, no C15 weaken, no /api/agent fallback, no Wasm/protocol change, no nested 'use step'.
npm run typecheck + default vitest run green. npm run test:int green because of it.fails, not because the contract holds.
Cloud ops: Production mutate N/A. CI workflow shipped in this PR.
Living docs:AGENTS.md tests bullet (commands + --project flags + fail-closed / it.fails split). Timeless — no issue numbers. Other surfaces N/A.
Plan header
plan/int-durable-860GITHUB_TOKEN/ existing fetch-harness). No origin URL, droplet, or World secret hard-bindint-durable(ubuntu-latest, PR + main)AGENTS.mdtests bullet:npm run test:int+npm test--projectflags. No issue numbers. Other surfaces N/AReview notes (2026-08-27)
Majors landed: (1)
vitest runruns all projects —npm testmust pass--project default --project tenancyor theintproject is collected. (2)it.failswould swallow a missing-wasm throw — fail-closed load is a green test; only contract expects areit.fails. (3) Do not addlib/turnBoot.ts— extract into existing modules (sessionRepository/turnAttach). Minors: GHApermissions: actions: read; AGENTS.md timeless (no issue numbers).Source issue #860 is this issue. Body below is the implementable plan. Product patches for #859 stay on later PRs.
Summary
Ship a process-level integration suite that reproduces #859 as failing desired-contract tests before any persist/attach product patch. Wasm is the operator-visible ring after hydrate, not the SUT. The SUT is persist + envelope + boot attach + C15 Send remap. One PR, one review, merge. Default
npm teststays green.npm run test:intstays green because ofit.fails, not because the contract holds.Goals
parseCloudSessionSnapshot(desired). On main:it.failscompleted+ enveloperunning→ cold attach, notnone; Send → GET not POST.it.failson mainrunning; mid-turn blob is not live interleaving.it.failson mainintexcluded fromnpm test. New GHAint-durableon ubuntu-latestharness.wasmthrows, neverit.skip(same aslib/harnessChat.wasm-int.test.ts)Non-goals / out of scope
it.failsin the same PR as the fix.next start, auth cookie, local Workflow World, Vercel World,workflows-smokeextension.invincible-do-1/ self-hosted runner (Zig compile only).turnLoop.test.ts(it locks{ deltas }as persist content until a product PR).HarnessHost.POST /api/turns, preview Vercel smoke)./api/agentfallback · wrappingrunAgentStreamin one'use step'· Wasm/protocol change · nested'use step'·continue-on-erroron the int job ·it.skipwhen wasm is missing · laptop-only Production ops · phase/issue theater indocs/*Intent lock
In scope: vitest project
int+int/**helpers/tests; behavior-preserving extract of boot/attach spec fromHarnessHostso the driver does not mount React; GHAint-durable.yml; oneAGENTS.mdline.Out of scope: anything that makes a #859 row pass (except the extract remaining behavior-identical to main).
Forbidden: dual DOM chat · secrets in Wasm · laptop-only Production ops · phase/issue theater in product docs
Architectural decisions
next start/ wasmtime / NodeWebAssembly.instantiateWebAssembly.instantiate, reuse wasm-int dvui stubharness.wasmimportsdvui. Node already instantiates it (lib/harnessChat.wasm-int.test.ts). wasmtime would be a new product and would not have caught pointer clobber / C15 remapdecideAttachClassis heap state (heapApplied), not a document. Matches #857fetch/ Vercel Blob+Redis / in-memory Blob + envelopeMemoryBlobTranscriptStore+MemorySessionStore+ realcreateTurnPersistSeam/persistStepparseCloudSessionSnapshotPOST /api/turns/ in-process persistnext start+ Workflow World) is a later issue. Local World is the false-greenworkflows-smokeforbidslib/turnBoot.ts/ mount React / extract into existing modulesbootCloudSnapshotinlib/sessionRepository.ts(next toparseCloudSessionSnapshot/overlayEnvelopeMeta; mirrors innergetEnvelopetwo-step, no HTTP).coldAttachFromSnapshotinlib/turnAttach.ts(next todecideAttachClass).HarnessHostkeepsconst kickColdAttach = useCallbacklib/sessionBoot.tsis URL/mint/adopt identity — do not overload it. Do not add a third boot module. Extract is behavior-preserving of today's boot (parse fail → keep local; overlay never runs). Do not overlay enveloperunningon parse error — that is the #859 product fixcontinue-on-error/it.skip/ wrap the whole file init.fails/ split load vs contractit('loads harness.wasm')fail-closed (throw, never skip).it.failsonly around desired-contract expects, tagged by #859 row.beforeAll/loadBridge()stay outsideit.failsloadBridgethrows insideit.fails, missing wasm looks like the expected failure (false green). Product PR that fixes a row must drop thatit.failsin the same PR or unexpected-pass reds CInpm testint/**from default only / separate config file / explicit--projectonnpm testint+ defaultexclude: ['int/**']and"test": "… && vitest run --project default --project tenancy".npm run test:int=vitest run --project int(no wrapper script)vitest runwithtest.projectsruns every project. Excludingint/**from default is not enough — theintproject would still run under nakedvitest runubuntu-latestonly. Fetch mainharness-wasmvia existingscripts/fetch-harness-artifact.mjsGITHUB_SHA/ compile Zig here / latest mainharness-wasmHARNESS_COMMIT_SHA.HARNESS_WAIT_MS=0.HARNESS_REQUIRE=1harness-wasm-pr-*. Waiting on the PR SHA would fail-closed or stallLayer placement
lib/workflows/persistStep.ts,lib/agent/turnPersistSeam.ts,lib/agent/turnWorkerPersist.ts'use workflow'entry, no Worldlib/sessionRepository.tsparseCloudSessionSnapshot/overlayEnvelopeMeta/bootCloudSnapshot; innergetEnvelopetwo-step mirrored (not HTTP)lib/sessionBoot.tsstays identity-onlylib/turnAttach.tsdecideAttachClass/decideSendAttach/coldAttachFromSnapshot;app/harness/HarnessHost.tsxkickColdAttach(hook stays)int/loadBridge.ts+inv_message_*_atlib/sessionStore.tsLocalStorageSessionStore.saveemptycatchlocalStorage, not ~5MB folklore.github/workflows/int-durable.ymlCurrent baseline (live code)
Grounded on
main@a10dad73b6c21e2f3badab5d45562d9fb20862a7.{ deltas }lib/workflows/persistStep.tscontent = JSON.stringify({ deltas: args.deltas })meta.transcriptPointeron that bodylib/agent/turnPersistSeam.tscreateTurnPersistSeam→persistTranscriptSegment{ id, updatedAt, messages[] }lib/sessionRepository.tsparseCloudSessionSnapshot{ deltas }→nullgetEnvelopeincreateHttpSessionRepository(~L637)overlayEnvelopeMeta. Parse fail →{ action: 'error', message: 'Invalid transcript body.' }— overlay never runs,checkpointPointerunreadoverlayEnvelopeMetacan copy envelopeturnStatuslib/sessionRepository.tsoverlayEnvelopeMetaapp/harness/HarnessHost.tsxkickColdAttach(~L371):if (s.turnStatus !== 'running' || !s.turnRunId) return;thenattach: { runId, startIndex: 0, dedup: true }lib/turnAttach.test.ts(const kickColdAttach = useCallback)lib/turnAttach.tsdecideSendAttach→decideAttachClass;turnStatus !== 'running'→{ kind: 'none' }sendAgentStreamlib/harnessChat.wasm-int.test.tsloadBridgelib/sessions/blobStores.tsMemoryBlobTranscriptStore;lib/sessions/memorySessionStore.tsMemorySessionStoreturnPersistSeam.test.ts/turnWorkerPersist.test.tslib/sessionStore.tsLocalStorageSessionStore.savecatch { /* quota / private mode — ignore */ }vitest.config.tsdefault+tenancyintproject.npm test= di-gate + drizzle-journal-gate +vitest runtest:intscriptpackage.jsonpackage.jsonvitest: ^3.0.5it.failsis available**/*.{test,spec}includeslib/harnessChat.wasm-int.test.tsscripts/fetch-harness-artifact.mjsHARNESS_ARTIFACT_TOKEN||GH_TOKEN||GITHUB_TOKEN. Artifact nameharness-wasm(main only).github/workflows/*npm test/ wasm-int GHA today.build-harnessis Zig on self-hosted;workflows-smokeis dispatch + Vercel World. Neither is this jobHARNESS_ARTIFACT_TOKENGITHUB_TOKEN(same repo). No new secretdocs/runner.mdpushSessionToBridgelib/harnessChat.ts(exported)Design
Driver shape (heap A → drop → heap B)
Fake model: deterministic
tool_start/text_delta/done(second prompt for the turn-2 user line). Fake sandbox tools. Do not mock persist or the parser.getEnvelopestays unexported. The int driver callsparseCloudSessionSnapshot+overlayEnvelopeMetadirectly with in-memory envelope + blob bytes — the same two-step, nofetch.Behavior-preserving extract (existing modules — not
lib/turnBoot.ts)bootCloudSnapshot({ id, local, envelopeMeta, blobJson })parseCloudSessionSnapshot(blobJson, id). Null →{ action: 'error', message: 'Invalid transcript body.', snapshot: local }(host keeps local; no overlay, nocheckpointPointerfetch). Ok →{ action: 'ok', snapshot: overlayEnvelopeMeta(parsed, envelopeMeta) }coldAttachFromSnapshot(s)s.turnStatus === 'running' && s.turnRunId→{ runId, startIndex: 0, dedup: true }; elsenull. Exact predicate ofkickColdAttachHarnessHostkickColdAttachremains auseCallbackthat callscoldAttachFromSnapshot(sessionRef.current)thenrunPrompt('', { attach }). Do not rename the hook;lib/turnAttach.test.tssource-scan stays green. Green unit tests of today's extract semantics live inlib/sessionRepository.test.ts+lib/turnAttach.test.ts(parse fail keeps local;coldAttachFromSnapshotnone whencompleted).Do not change overlay-on-error, envelope-wins, or Send remap. That is #859 product work.
Test matrix (desired contract; must fail on main today)
persistStep/createTurnPersistSeam.persistwrites a blob;parseCloudSessionSnapshot(blob)null. Envelope GET/bootCloudSnapshothydrates latest user line + assistant.checkpointPointeris either unnecessary or boot-readableint/persist-parse.int.test.tsturnStatus: running+turnRunId. Heap B local iscompleted/ missing turn 2. Blob may be{deltas}(unreadable)coldAttachFromSnapshot/decideAttachClass→ cold, notnone. Ring is not stuck on turn-1-onlyint/f5-attach.int.test.tscompleteddecideSendAttach→ GET-attach, not{ kind: 'none' }(POST). A true second POST while live still 409s (C15 preserved — unit already exists; do not weaken)int/f5-attach.int.test.tslocalStoragethat throwsQuotaExceededErroron a laterLocalStorageSessionStore.saveofrunning+ turn-2 userint/quota.int.test.tsdonerunning(overlap #849). Heap B attaches. Empty-EOF does not spin (decideHotResumealready refuses; assert it)int/f5-attach.int.test.tsit.failsuntil a later continuous-persist issueint/f5-attach.int.test.tsRow 1 first in the PR. It is the one-file test that would have blocked B13. No Wasm.
Realistic fixture for rows 2–3–5 (incident shape): turn 1
persistStepcompleted ({ deltas }ontranscriptPointer); turn 2 started (enveloperunning+turnRunId); reader aborted; heap B local is turn-1completed.Files
vitest.config.tsint:include: ['int/**/*.int.test.ts'],environment: 'node',testTimeout: 30000. Defaultexclude+=int/**package.json"test": "… && vitest run --project default --project tenancy"(required)."test:int": "vitest run --project int". Optionally prefixtest:intwithnode scripts/di-gate.mjs &&. Not a wrapper around vitestlib/sessionRepository.tsbootCloudSnapshot. Unit-test today's semantics (parse fail keeps local) inlib/sessionRepository.test.ts— greenlib/turnAttach.tscoldAttachFromSnapshot. Unit-test inlib/turnAttach.test.ts— greenapp/harness/HarnessHost.tsxcoldAttachFromSnapshot. Behavior-identicalint/loadBridge.tsWebAssembly.instantiate). Duplicate OK; do not churn the green wasm-int file unless a one-line import is trivialint/stores.tsMemoryBlobTranscriptStore+MemorySessionStore+createTurnPersistSeamlike prodint/driver.tsbootCloudSnapshot, hydrate, attach spec, ring readbackint/persist-parse.int.test.tsit.failsint/f5-attach.int.test.tsit.failsint/quota.int.test.tsit.fails.github/workflows/int-durable.ymlAGENTS.mdnpm run test:intis the durable-turn int project; excluded fromnpm test; fail-closed wasm;it.failsuntil the matching #859 product PRDo not rewrite
turnLoop.test.ts. Do not adddocs/*.it.failsconventionWasm rows:
await loadBridge()(orbeforeAll) above theit.failsbody. Only the desired-contractexpects sit insideit.fails.When the product PR makes the expect pass,
it.failsfails (unexpected pass). That PR dropsit.fails(becomesit) in the same commit. Nevercontinue-on-error. Neverit.skip.Cloud ops path
Production mutate: N/A. No migrate / backfill / seed / env cutover / secret dual-store.
CI (not a human Production button, but the cloud operator path for the lock):
int-durableonpull_request+pushmain(not dispatch-only). Origin required check is GitHub branch-protection UI — out of this PR; shipping the workflow is enough.github/workflows/int-durable.ymlruns-onubuntu-latestonly. Never self-hosted /invincible-do-1/vars.RUNNER_LABELSif:skip whenpull_request.head.repo.full_name != github.repository(public-repo policy; forks cannot read originharness-wasm)actions/checkout@v4→actions/setup-node@v4(Node 22) →npm ci→npm run fetch-harness→npm run test:intscripts/fetch-harness-artifact.mjs. Token: defaultGITHUB_TOKEN(already in GHA). Do not setHARNESS_COMMIT_SHA.HARNESS_WAIT_MS=0.HARNESS_REQUIRE=1. Artifact nameharness-wasmfrom main. Fail-closed if missingVERCEL_TOKEN/ World /DATABASE_URL. Do not requireHARNESS_ARTIFACT_TOKEN(that's Vercel prebuild)permissionscontents: read+actions: read(artifact list/download). No write, noid-tokentimeout-minutes: 15; concurrency groupint-durable-${{ github.ref }}cancel-in-progress; nevercontinue-on-errorbuild-harness; notworkflows-smoke; not DO; notnpm test(would exclude int anyway); not personal laptop npm as the proveLiving docs plan
docs/*AGENTS.mdnpm test= di-gate + drizzle-journal-gate +vitest run --project default --project tenancy;npm run test:int=vitest run --project int; fail-closed wasm;it.failsonly on persist/boot contract rowsREADME.mdSECURITY.md.env.exampleGITHUB_TOKEN/ fetch-harnessCaps table
No existing cap is raised or lowered. No product cap added.
HARNESS_SESSION_MAX_BODY_BYTESlib/sessionCloudCaps.tsTURN_STREAM_CURSOR_MAXlib/sessionCloudCaps.tsit.skipint/loadBridge.ts(same rule aslib/harnessChat.wasm-int.test.ts)testTimeout30000msvitest.config.tsprojectinttimeout-minutes15.github/workflows/int-durable.ymlQuota test uses a tiny fake store cap as a test double (not a product cap, not
HARNESS_SESSION_MAX_BODY_BYTES). Do not change the ~5MB browser folklore into a shipped constant.Implementation order
vitest.config.tsprojectint+ default excludeint/**+package.jsontestgains--project default --project tenancyandtest:int. Confirmvitest run --project default --project tenancycollect does not pick upint/**. Nakedvitest runwould — that is whynpm testmust pass the flags.int/stores.ts+int/persist-parse.int.test.ts(row 1it.fails). Prove:persistStepcontent →parseCloudSessionSnapshotisnullon main, wrapped init.fails. No wasm in this file.bootCloudSnapshot+coldAttachFromSnapshot+ thinHarnessHostcall. Default-suite unit tests: parse fail keeps local (green).lib/turnAttach.test.tssource-scan still green.int/loadBridge.ts+int/driver.ts+int/f5-attach.int.test.ts(rows 2, 3, 5, 6it.fails).int/quota.int.test.ts(row 4it.fails) against realLocalStorageSessionStore+ throwing polyfill..github/workflows/int-durable.yml(same PR — do not defer CI).AGENTS.mdone line.npm testgreen,npm run typecheckgreen,npm run test:intgreen viait.fails.Testing
it.fails)npm run test:intit.fails)npm run test:intit.fails)npm run test:intnpm test— do not weakenit.fails)npm run test:intrunningit.fails)npm run test:intit.fails)npm run test:intit.fails)npm run test:intwithoutpublic/harness/harness.wasm→ non-zero, not skip, not swallowednpm test(--project default --project tenancy) green (noint/**)bootCloudSnapshotparse-fail keeps local;coldAttachFromSnapshotnone oncompleted— green in default suitekickColdAttachsource-scanlib/turnAttach.test.tsstill finds the hooknpm run typecheckint-durableon the PR; ubuntu-latest; fetch-harness +test:intMinimum locked for DoD: rows 1, 2, 3, 9, 12, 13. Rows 4–6 ship in the same PR (source lock) but 1–3 are the B13/F5/C15 ratchet.
Definition of done
int+npm run test:int(vitest run --project int).npm test= di-gate + drizzle-journal-gate +vitest run --project default --project tenancy(does not collectint/**; still green on main).it.failson main: worker persist blob is not aSessionSnapshottoday.completed+ enveloperunning→ attach, not none; Send → GET not POST.it.failson main.it.failson main.running; mid-turn persist hole documented.it.failson main.bootCloudSnapshot/coldAttachFromSnapshotin existing modules; nolib/turnBoot.ts).HarnessHostkeepsconst kickColdAttach = useCallback;lib/turnAttach.test.tsstill green.it('loads harness.wasm')throws if missing (neverit.skip; never insideit.fails).int-durableon ubuntu-latest (same-repo PR + main). Not self-hosted. Notcontinue-on-error. Fetches mainharness-wasm.next start, no Vercel World, no product persist/attach patch, no C15 weaken, no/api/agentfallback, no Wasm/protocol change, no nested'use step'.npm run typecheck+ defaultvitest rungreen.npm run test:intgreen because ofit.fails, not because the contract holds.AGENTS.mdtests bullet (commands +--projectflags + fail-closed /it.failssplit). Timeless — no issue numbers. Other surfaces N/A.it.failswork on later PRs.Risks & mitigations
it.failsit.failssilently ignored / jobcontinue-on-error/it.failsswallows missing wasmit.fails. Forbidcontinue-on-error. Product PR that fixes a row must dropit.failsor CI reds on unexpected passnpm testcollectsint/**exclude: ['int/**']plusvitest run --project default --project tenancy. Verify collect before mergeGITHUB_SHAand times outHARNESS_WAIT_MS=0, do not setHARNESS_COMMIT_SHA; latest mainharness-wasmif:skip (existing public-repo policy)testTimeout30s; job timeout 15m; reuse wasm-int stub (already works in Node)lib/turnAttach.test.tssource-scan breaksHarnessHostGITHUB_TOKEN. No origin URLOpen questions
None. In-scope engineering choices are locked above. Origin branch-protection "required check" is operator UI, not a plan blocker.
Phase map
N/A — single issue, one PR. Do not split into phase children.
Corrections / refinements vs source #860
AGENTS.mdone agent-rule line (skill: agent rules changed).docs/*still N/Aint-durable)getEnvelopekickColdAttachextractcoldAttachFromSnapshotinlib/turnAttach.ts; hook stays inHarnessHost(source-scan). Nolib/turnBoot.tstest:intvitest run --project int— no wrapper script (AGENTS.md vitest rule)vitest runvs projectsint/**from defaultnpm testto--project default --project tenancy(vitest runs every project otherwise)it.failsvs missing wasmit.failsonly on contract expectslib/turnBoot.tsbootCloudSnapshot→sessionRepository.ts;coldAttachFromSnapshot→turnAttach.ts.sessionBoot.tsstays identity/mintcontents: read+actions: readReferences
lib/harnessChat.wasm-int.test.ts(bug: tool-run count never increments in the Wasm UI (nothing, then all tools) #433 / plan plan: live tool-count increment in the Wasm harness (implements #433) #448)scripts/workflows-smoke.test.ts