Files
ThothII/.superpowers/sdd/predeploy-fix-report.md

930 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Pre-deployment Fix Wave Report
Date: 2026-07-14
Worktree: `/home/chirone/ThothII/.worktrees/activity-log-cte-layout`
Base: `e5366d14a6da8fb331d94be60b8929cefb1fe3e0`
## Outcome
All three reviewed findings are implemented in one coherent backend/frontend wave:
1. Resume leaves the prior selection, Zustand state, document panel, and EventSource untouched
until `POST /resume` succeeds. Cold Resume changes state and reconnects only after backend
clear/rebind; already-active same-session Resume preserves the existing binding; failure is a
no-op apart from the fixed toast.
2. SSE uses monotonically increasing per-session ids, cursor-filtered replay, native and manual
reconnect cursors, id continuity across `hub.clear`, and descriptor-id pending-gate
idempotence at both backend and frontend layers.
3. Generic Pi system events and readiness errors are projected through explicit public
allowlists. Sentinel URLs, paths, tokens, stderr, commands, and extra fields do not reach HTTP
or SSE.
No harness, workflow, persistence, model, CTE viewer, CTE card, or shared Card file changed.
## Interfaces
- Frontend `resumeSession(id)` now returns
`Promise<{ id: string; alreadyActive: boolean }>` via `ResumeSessionResult`.
- Backend successful Resume always returns the same shape:
- running/waiting runtime: `{ id, alreadyActive: true }`
- validated cold runtime: `{ id, alreadyActive: false }`
- `SseHub.publish(sessionId, event, data): number` returns the assigned SSE id.
- `SseHub.subscribe(sessionId, send, { afterId, pending })` calls
`send(event, data, id)` for replay/live frames with `id > afterId`.
- `GET /sessions/:id/events` accepts native `Last-Event-ID` and manual
`?lastEventId=<integer>`; when both are valid it uses the greater cursor.
- Every emitted SSE frame is `id: <n>\nevent: <name>\ndata: <json>\n\n`.
- Public readiness failure is exactly:
`Session services are not ready. Check configuration and connectivity, then try again.`
- Generic Pi system events are exactly `{ type: "system_event", event }`, and `event` must be a
non-empty string.
## Files
Backend production:
- `backend/src/bridge/session-bridge.ts`
- `backend/src/routes/sessions.ts`
- `backend/src/sse/sse-hub.ts`
Backend tests:
- `backend/test/routes-sessions.test.ts`
- `backend/test/session-bridge.test.ts`
- `backend/test/sse-hub.test.ts`
- `backend/test/sse-route.test.ts` (new)
Frontend production/support:
- `frontend/src/api/sessions.ts`
- `frontend/src/api/types.ts`
- `frontend/src/shell/AppShell.tsx`
- `frontend/src/store/sessionStore.ts`
- `frontend/src/stream/useSessionStream.ts`
- `frontend/src/test/fakeEventSource.ts`
Frontend tests:
- `frontend/src/api/sessions.test.ts`
- `frontend/src/shell/AppShell.session-mgmt.test.tsx`
- `frontend/src/store/sessionStore.test.ts`
- `frontend/src/stream/useSessionStream.test.tsx`
## TDD RED/GREEN evidence
### 1. Backend Resume result and client-boundary allowlists
RED command:
```text
cd backend && npx vitest run test/routes-sessions.test.ts test/session-bridge.test.ts
```
RED output (exit 1):
```text
Test Files 2 failed (2)
Tests 6 failed | 35 passed (41)
expected { id: 's1' } to deeply equal { id: 's1', alreadyActive: false }
expected raw readiness URL/token/path to equal the fixed public message
expected three raw generic system events to equal [{ type: 'system_event', event: 'session_exit' }]
```
GREEN command:
```text
cd backend && npx vitest run test/routes-sessions.test.ts test/session-bridge.test.ts
```
GREEN output (exit 0):
```text
✓ test/session-bridge.test.ts (14 tests)
✓ test/routes-sessions.test.ts (27 tests)
Test Files 2 passed (2)
Tests 41 passed (41)
```
### 2. Backend exact-once SseHub and route framing
RED command:
```text
cd backend && npx vitest run test/sse-hub.test.ts test/sse-route.test.ts
```
RED output (exit 1):
```text
Test Files 2 failed (2)
Tests 7 failed (7)
expected [undefined, undefined, undefined] to deeply equal [1, 2, 3]
expected unconditional replay not to contain "one" / "two"
expected one buffered pending gate, received replay plus a second pending emission
```
GREEN command:
```text
cd backend && npx vitest run test/sse-hub.test.ts test/sse-route.test.ts
```
GREEN output (exit 0):
```text
✓ test/sse-hub.test.ts (4 tests)
✓ test/sse-route.test.ts (3 tests)
Test Files 2 passed (2)
Tests 7 passed (7)
```
### 3. Frontend cursor tracking and gate idempotence
RED command:
```text
cd frontend && npx vitest run src/stream/useSessionStream.test.tsx src/store/sessionStore.test.ts
```
RED output (exit 1):
```text
Test Files 2 failed (2)
Tests 2 failed | 28 passed (30)
expected /sessions/s1/events to be /sessions/s1/events?lastEventId=7
expected duplicate gate pendingWidget to remain null, received gate-1
```
GREEN command:
```text
cd frontend && npx vitest run src/stream/useSessionStream.test.tsx src/store/sessionStore.test.ts
```
GREEN output (exit 0):
```text
✓ src/store/sessionStore.test.ts (23 tests)
✓ src/stream/useSessionStream.test.tsx (7 tests)
Test Files 2 passed (2)
Tests 30 passed (30)
```
### 4. Frontend typed Resume and AppShell ordering/preservation
Typed API RED command:
```text
cd frontend && npx tsc -b
```
Typed API RED output (exit 1):
```text
src/api/sessions.test.ts(43,9): error TS2322: Type 'void' is not assignable to type
'{ id: string; alreadyActive: boolean; }'.
```
Lifecycle RED command:
```text
cd frontend && npx vitest run src/api/sessions.test.ts src/shell/AppShell.session-mgmt.test.tsx
```
Lifecycle RED output (exit 1):
```text
✓ src/api/sessions.test.ts (9 tests)
❯ src/shell/AppShell.session-mgmt.test.tsx (15 tests | 4 failed)
Test Files 1 failed | 1 passed (2)
Tests 4 failed | 20 passed (24)
already-active same-session Resume created two EventSources instead of one
deferred cold Resume closed the document panel before POST completion
failed same-session Resume closed the prior EventSource
failed Resume with no active session opened an EventSource
```
GREEN commands:
```text
cd frontend && npx vitest run src/api/sessions.test.ts src/shell/AppShell.session-mgmt.test.tsx
cd frontend && npx tsc -b
```
GREEN output (exit 0):
```text
✓ src/api/sessions.test.ts (9 tests)
✓ src/shell/AppShell.session-mgmt.test.tsx (15 tests)
Test Files 2 passed (2)
Tests 24 passed (24)
TypeScript: no output, exit 0
```
The AppShell cold-reconnect test additionally proves that the old source accepts an event while
Resume is pending, the replacement URL carries `lastEventId=8`, the replacement receives one
post-resume transcript/activity row, and two deliveries of the same descriptor id yield one gate.
## Affected verification
Backend command:
```text
cd backend && npx vitest run test/routes-sessions.test.ts test/session-bridge.test.ts \
test/sse-hub.test.ts test/sse-route.test.ts test/health.test.ts test/e2e-f1.test.ts
```
Output (exit 0):
```text
Test Files 6 passed (6)
Tests 52 passed (52)
```
Backend typecheck:
```text
cd backend && npx tsc --noEmit -p .
```
Output: no output, exit 0.
Frontend command:
```text
cd frontend && npx vitest run src/api/sessions.test.ts src/store/sessionStore.test.ts \
src/stream/useSessionStream.test.tsx src/shell/AppShell.session-mgmt.test.tsx \
src/shell/CentralStatus.test.tsx src/shell/ModelActivityPanel.test.tsx \
src/shell/f1-loop.test.tsx src/shell/AppShell.new-session.test.tsx
```
Output (exit 0):
```text
Test Files 8 passed (8)
Tests 74 passed (74)
```
Frontend typecheck:
```text
cd frontend && npx tsc -b
```
Output: no output, exit 0.
## Full verification
Backend full suite:
```text
cd backend && npx vitest run
```
```text
Test Files 22 passed (22)
Tests 177 passed (177)
```
Frontend full suite:
```text
cd frontend && npx vitest run
```
```text
Test Files 43 passed (43)
Tests 271 passed (271)
```
Backend production build:
```text
cd backend && npm run build
> tsc -p tsconfig.json
exit 0
```
Frontend production build:
```text
cd frontend && npm run build
> tsc -b && vite build
✓ 4835 modules transformed.
✓ built in 8.25s
exit 0
```
## Integrated re-review closure (2026-07-15)
This section supersedes the earlier cold same-session assertion that the replacement URL carries
`lastEventId=8`. That behavior was correct only while the backend process and its in-memory id
sequence survived. A restarted backend begins a fresh sequence, so a successful cold Resume now
explicitly discards the browser's cursor before replacing the EventSource.
All four integrated re-review findings are closed:
1. `AppShell` passes a dedicated cursor-reset epoch to `useSessionStream`. A cold same-session
Resume increments it only after `alreadyActive: false`; a high cursor such as `901` is omitted
from the replacement URL and fresh low-id events/gates are consumed. An already-active
same-session Resume still preserves its source, cursor, and store.
2. `useSessionStream` no longer mutates the cursor ref during render. Effect setup resets cursor
state on session/reset-epoch changes, callbacks are guarded by a captured active-source
identity, and cleanup clears only its own active identity. A queued event from the replaced
source cannot write the new store or poison its next reconnect URL.
3. Backend Resume is serialized per session and rechecks runtime state inside the lock. Manifest,
readiness, and reopen validation precede the transport commit. Idle/failed replacement creates
and binds the new runtime before `hub.clear`, which occurs synchronously immediately before the
first `Resuming session` publish. Reopen/create failure returns exactly
`Session could not be resumed. Check configuration and connectivity, then try again.`, keeps the
prior hub buffer/subscribers attached, and does not expose exception sentinels. Concurrent calls
perform one cold start and the waiter returns `alreadyActive: true`.
4. `SseHub.forget(id)` removes subscribers, buffered events, and the last id. Permanent session
DELETE invokes it after disk deletion; ordinary close and Resume continue to use `clear`, which
preserves the id sequence.
### Re-review files
Production:
- `backend/src/pi/pi-process-manager.ts`
- `backend/src/routes/sessions.ts`
- `backend/src/sse/sse-hub.ts`
- `frontend/src/shell/AppShell.tsx`
- `frontend/src/stream/useSessionStream.ts`
Tests/support:
- `backend/test/pi-process-manager.test.ts`
- `backend/test/routes-sessions.test.ts`
- `backend/test/sse-hub.test.ts`
- `frontend/src/shell/AppShell.session-mgmt.test.tsx`
- `frontend/src/stream/useSessionStream.test.tsx`
- `frontend/src/test/fakeEventSource.ts`
### Re-review TDD RED/GREEN evidence
Frontend RED command:
```text
cd frontend && npx vitest run src/stream/useSessionStream.test.tsx \
src/shell/AppShell.session-mgmt.test.tsx
```
RED output (exit 1):
```text
Test Files 2 failed (2)
Tests 3 failed | 21 passed (24)
reset epoch: expected the old source to close, received false
cold same-session: expected /sessions/s1/events, received ?lastEventId=901
stale source: expected an empty transcript, received "stale session one"
```
Frontend GREEN command:
```text
cd frontend && npx vitest run src/stream/useSessionStream.test.tsx \
src/shell/AppShell.session-mgmt.test.tsx
cd frontend && npx tsc -b
```
GREEN output (exit 0):
```text
Test Files 2 passed (2)
Tests 24 passed (24)
TypeScript: no output, exit 0
```
Backend RED command:
```text
cd backend && npx vitest run test/sse-hub.test.ts test/routes-sessions.test.ts
```
RED output (exit 1):
```text
Test Files 2 failed (2)
Tests 8 failed | 28 passed (36)
three Resume ordering assertions observed clear before reopen/create
reopen and create sentinels escaped as raw HTTP 500 responses
the concurrent waiter cold-started again instead of returning alreadyActive: true
SseHub.forget was absent and DELETE did not invoke permanent cleanup
```
Backend GREEN command:
```text
cd backend && npx vitest run test/sse-hub.test.ts test/routes-sessions.test.ts
cd backend && npx tsc --noEmit -p .
```
GREEN output (exit 0):
```text
Test Files 2 passed (2)
Tests 36 passed (36)
TypeScript: no output, exit 0
```
The failure tests publish a post-failure probe through the same hub and prove that a subscriber
attached before either reopen or create rejection still receives it. The concurrency test overlaps
two same-id requests behind a deferred reopen and proves one manifest/readiness/reopen/create/clear
sequence.
### Initial re-review verification (before independent-review hardening)
```text
cd backend && npx vitest run
Test Files 22 passed (22)
Tests 182 passed (182)
cd frontend && npx vitest run
Test Files 43 passed (43)
Tests 273 passed (273)
cd backend && npm run build
> tsc -p tsconfig.json
exit 0
cd frontend && npm run build
> tsc -b && vite build
✓ 4835 modules transformed.
✓ built in 8.46s
exit 0
```
`git diff --check` produced no output (exit 0). The frontend build retains its pre-existing
large-chunk warning; no new build or type errors were introduced.
Final whitespace verification:
```text
git diff --check
no output, exit 0
```
## Self-review
- Resume sequencing: reopen and runtime binding precede backend `clear` and HTTP success; frontend
state mutation and cursor-reset epoch follow it. Failure catch only emits fixed UI copy.
- Already active: same-session returns before reset/generation/manifest repaint; different session
resets the single-session store and binds the new id only after success.
- SSE exact-once: ids are transport identity, not content hashes; replay is strictly `id > cursor`;
`clear` retains the counter; pending gate matching uses only descriptor id.
- Cursor behavior: hook tracks `MessageEvent.lastEventId`, carries it only to an ordinary same-id
generation, and resets it on session-id/cold-runtime epoch change. Native EventSource reconnect
remains supported by the route header.
- Gate defense: the Zustand set survives pending clear but resets with the session store.
- Client boundary: raw `ensure.error` is unused in public responses; generic Pi system events are
reconstructed rather than spread; frontend type mirrors the two-field event.
- Scope: `git diff` contains no CTE/Card/harness/workflow/persistence/model changes. Four pre-existing
modified `.superpowers/sdd/{progress,task-2-report,task-3-report,task-4-report}.md` files are user
work and are excluded from staging.
## Remaining concerns
- The 200-event SSE ring limit remains intentional. A brand-new page can reconstruct only retained
backlog; an in-memory same-session reconnect is exact-once from its cursor.
- Per-session sequence counters remain in backend memory after `clear` by design so later in-process
cold same-id Resume cannot reuse ids. Permanent DELETE removes the counter via `forget`.
- The Delete-then-Resume adversarial route test proves the deleted session is not resurrected but
currently receives the runner's generic HTTP 500 when `sessionShow` can no longer find it. A
future API cleanup can normalize that missing-session response to 404 or 409.
- Frontend tests still print pre-existing MSW unhandled-request and React ref/`act` warnings even
though all 276 tests pass. The frontend production build still reports pre-existing large chunk
warnings. Neither warning class was introduced or expanded by this change.
- No live Pi/DWH smoke was run; this wave changes only REST/SSE/frontend lifecycle boundaries and
is covered by fake-Pi, live Fastify SSE, component, full-suite, typecheck, and production-build
gates.
## Independent-review hardening
The required independent review was run repeatedly against the uncommitted diff. Its first pass
found four Important lifecycle edges beyond the integrated findings: queued old-runtime callbacks,
post-spawn construction cleanup, concurrent frontend Resume completions, and the passive-effect
commit window. Its second pass confirmed those fixes and identified one remaining Important
retention issue in the new runtime-identity map. The final pass reported no Critical, Important, or
Minor findings and assessed the diff ready to merge.
The resulting hardening is:
- Runtime bridge callbacks are gated by the bound runtime identity. Replacement, close, and DELETE
invalidate the old identity, so queued old events cannot publish or call `failSession`. An active
runtime removed by the manager can still publish its complete public failure sequence; after the
terminal unmanaged `agent_end`, its binding is released and later events are rejected.
- `PiProcessManager` kills the spawned child and removes any registered map entry if either
spawn-boundary stderr setup or later RPC/bridge/map initialization throws.
- Resume completion compares against synchronously maintained current active-session identity.
Concurrent `alreadyActive: false` then `alreadyActive: true` results preserve the cold source,
cursor, store, and replayed gate.
- Stream source replacement uses a layout effect. A deterministic later-layout-effect test delivers
a queued old event inside the former commit-to-passive-cleanup window and proves it is ignored.
- Cursor tests cover both a restarted backend's fresh low ids and an in-process hub's preserved high
ids followed by a cursor-bearing ordinary reconnect.
### Hardening TDD RED/GREEN evidence
Backend identity/construction RED command:
```text
cd backend && npx vitest run test/pi-process-manager.test.ts test/routes-sessions.test.ts
```
```text
Test Files 2 failed (2)
Tests 3 failed | 69 passed (72)
post-spawn reader initialization did not kill the child
replaced and deleted runtime callbacks still called failSession/published
```
Additional spawn-boundary and terminal-release RED checks:
```text
cd backend && npx vitest run test/pi-process-manager.test.ts \
-t "spawn boundary initialization"
Tests 1 failed | 38 skipped (39)
cd backend && npx vitest run test/routes-sessions.test.ts -t "terminal sequence"
Tests 1 failed | 34 skipped (35)
```
Frontend concurrency/layout RED command:
```text
cd frontend && npx vitest run src/stream/useSessionStream.test.tsx \
src/shell/AppShell.session-mgmt.test.tsx
```
```text
Test Files 2 failed (2)
Tests 2 failed | 25 passed (27)
the later-layout-effect event wrote "commit-window stale text"
the false→true completion pair erased pending gate "cold-gate"
```
Final focused GREEN commands:
```text
cd backend && npx vitest run test/pi-process-manager.test.ts \
test/routes-sessions.test.ts test/sse-hub.test.ts
cd backend && npx tsc --noEmit -p .
Test Files 3 passed (3)
Tests 79 passed (79)
TypeScript: no output, exit 0
cd frontend && npx vitest run src/stream/useSessionStream.test.tsx \
src/shell/AppShell.session-mgmt.test.tsx
cd frontend && npx tsc -b
Test Files 2 passed (2)
Tests 27 passed (27)
TypeScript: no output, exit 0
```
### Final full verification after review hardening
```text
cd backend && npx vitest run
Test Files 22 passed (22)
Tests 188 passed (188)
cd frontend && npx vitest run
Test Files 43 passed (43)
Tests 276 passed (276)
cd backend && npm run build
> tsc -p tsconfig.json
exit 0
cd frontend && npm run build
> tsc -b && vite build
✓ 4835 modules transformed.
✓ built in 8.47s
exit 0
```
The final frontend run retains the repository's pre-existing MSW/ref/`act` warnings, and the build
retains the pre-existing large-chunk warning. No test, typecheck, or build failures remain.
## Stale-bootstrap, lifecycle-lock, and competing-Resume hardening
Date: 2026-07-15
Base: `08b1f4909e8eb7538156cecc2e7a6cafb46ddfc7`
This follow-up closes asynchronous identity/order and multi-client transport gaps found in the
pre-deployment review:
- `PiProcessManager.teardownIfCurrent(id, runtime)` makes teardown an identity-checked operation.
Bootstrap re-checks identity after configuration/retrieval and before both the public
`Starting model` event and model start. Its failure continuation acquires the same session
lifecycle lock, claims only its own runtime identity, and holds serialization through persisted
failure and the public terminal sequence. A continuation left behind by Close or DELETE cannot
target a replacement or recreate forgotten SSE state.
- The former Resume-only promise tail is now a per-session lifecycle lock shared by Resume, Close,
and DELETE. Each route reads the current runtime inside the lock immediately before replacement
or removal and uses identity-checked teardown. Deferred route tests prove both orderings:
Resume then Close/Delete finishes removed with no post-removal bootstrap event; Close then Resume
creates only after Close completes; DELETE then Resume cannot recreate a deleted session.
- AppShell assigns each Resume invocation a monotonic token and records the latest target. A
completion for a different, superseding session id cannot reset the store, select a source, close
the panel, or repaint phase from a late manifest. Same-id invocations are per-target single-flight
operations through the POST and local binding commit: repeated pre-commit clicks update the
shared operation's latest token but issue no second POST or commit path. The operation becomes
joinable again before its manifest fetch, whose repaint remains token/id/selection guarded. Start
new, Stop, streamed session exit, and active-session deletion invalidate pending Resume work.
This prevents stale-source preservation and reverse/non-Resume intent overwrite without allowing
a slow manifest to suppress a later explicit rebind.
- `SseHub` subscriber registrations now carry idempotent transport-close callbacks. `clear` and
`forget` snapshot and actively close every response before discarding runtime transport state;
callback-driven unsubscription during that iteration is safe. The SSE route ends its response so
native EventSource reconnects with `Last-Event-ID`. Post-clear events retain monotonic ids and are
buffered for replay; `forget` additionally resets the id state.
Production files:
- `backend/src/pi/pi-process-manager.ts`
- `backend/src/routes/sessions.ts`
- `backend/src/sse/sse-hub.ts`
- `frontend/src/shell/AppShell.tsx`
Regression tests:
- `backend/test/pi-process-manager.test.ts`
- `backend/test/routes-sessions.test.ts`
- `backend/test/sse-hub.test.ts`
- `backend/test/sse-route.test.ts`
- `frontend/src/shell/AppShell.session-mgmt.test.tsx`
### TDD RED/GREEN evidence
Runtime identity API RED:
```text
cd backend && npx vitest run test/pi-process-manager.test.ts -t "identity-checked teardown"
Test Files 1 failed (1)
Tests 1 failed | 39 skipped (40)
TypeError: mgr.teardownIfCurrent is not a function
```
Runtime identity API GREEN:
```text
Test Files 1 passed (1)
Tests 1 passed | 39 skipped (40)
```
Deferred bootstrap RED:
```text
cd backend && npx vitest run test/routes-sessions.test.ts \
-t "stale bootstrap|bootstrap that"
Test Files 1 failed (1)
Tests 6 failed | 35 skipped (41)
close/delete + replacement: stale continuation removed the replacement runtime
delete without replacement: stale continuation called failSession after forget
```
Deferred bootstrap GREEN:
```text
Test Files 1 passed (1)
Tests 6 passed | 35 skipped (41)
```
Shared lifecycle ordering RED:
```text
cd backend && npx vitest run test/routes-sessions.test.ts \
-t "Resume followed|Close followed|Delete followed"
Test Files 1 failed (1)
Tests 4 failed | 41 skipped (45)
All four deferred assertions observed the competing route settle before the first lifecycle
operation released.
```
Bootstrap plus lifecycle GREEN:
```text
cd backend && npx vitest run test/routes-sessions.test.ts \
-t "Resume followed|Close followed|Delete followed|stale bootstrap|bootstrap that"
Test Files 1 passed (1)
Tests 10 passed | 35 skipped (45)
```
Competing frontend Resume RED:
```text
cd frontend && npx vitest run src/shell/AppShell.session-mgmt.test.tsx \
-t "competing Resume|stale Resume manifest"
Test Files 1 failed (1)
Tests 2 failed | 16 skipped (18)
reverse POST completion opened a second, stale EventSource
late s1 manifest repainted the selected s3 phase from F3 to F7
```
Competing and same-id Resume GREEN:
```text
cd frontend && npx vitest run src/shell/AppShell.session-mgmt.test.tsx \
-t "competing Resume|stale Resume manifest|false then true"
Test Files 1 passed (1)
Tests 3 passed | 15 skipped (18)
```
### Independent-review hardening RED/GREEN
The first final review reported no Critical findings and three Important edge cases: bootstrap
could start during an in-progress Close; bootstrap-owned failure was persisted twice; and an older
same-id result could overwrite newer state. The integrated reviewer also required non-Resume
navigation to invalidate pending Resume work. The final main review tightened the same-ID contract
to true single-flight so a second same-target click cannot preserve a dead pre-restart source.
Backend review RED:
```text
cd backend && npx vitest run test/routes-sessions.test.ts \
-t "Close suppresses|bootstrap failure persists once"
Test Files 1 failed (1)
Tests 2 failed | 45 skipped (47)
deferred configure started Pi while closeSession was still pending
bootstrap/public failure called failSession twice
```
Backend review GREEN:
```text
Test Files 1 passed (1)
Tests 2 passed | 45 skipped (47)
```
Same-id single-flight RED:
```text
cd frontend && npx vitest run src/shell/AppShell.session-mgmt.test.tsx \
-t "share one cold request"
Test Files 1 failed (1)
Tests 1 failed | 18 skipped (19)
two concurrent same-ID invocations issued two cold POSTs (three total including initial activation)
```
Non-Resume invalidation RED:
```text
cd frontend && npx vitest run src/shell/AppShell.session-mgmt.test.tsx \
-t "starting a new question invalidates"
Test Files 1 failed (1)
Tests 1 failed | 19 skipped (20)
the late Resume opened an EventSource after Start new returned to the landing state
```
Frontend review GREEN:
```text
cd frontend && npx vitest run src/shell/AppShell.session-mgmt.test.tsx \
-t "share one cold request|competing Resume|stale Resume manifest|starting a new question invalidates"
Test Files 1 passed (1)
Tests 4 passed | 15 skipped (19)
```
Post-commit single-flight lifetime RED:
```text
cd frontend && npx vitest run src/shell/AppShell.session-mgmt.test.tsx \
-t "releases same-id single-flight"
Test Files 1 failed (1)
Tests 1 failed | 19 skipped (20)
s1 committed and waited on its manifest; after s3 superseded it, a new s1 Resume reused the old
operation and issued no second s1 POST (expected 2, received 1).
```
Same-id and manifest lifetime GREEN:
```text
cd frontend && npx vitest run src/shell/AppShell.session-mgmt.test.tsx -t "same-id|manifest"
Test Files 1 passed (1)
Tests 4 passed | 16 skipped (20)
cd frontend && npx tsc -b
no output, exit 0
```
Multi-client SSE disconnect RED:
```text
cd backend && npx vitest run test/sse-hub.test.ts test/sse-route.test.ts
Test Files 2 failed (2)
Tests 3 failed | 6 passed (9)
clear/forget invoked zero of two registered close callbacks, and two live HTTP SSE responses timed
out instead of reaching EOF after clear.
```
Multi-client SSE disconnect GREEN:
```text
cd backend && npx vitest run test/sse-hub.test.ts test/sse-route.test.ts
Test Files 2 passed (2)
Tests 9 passed (9)
cd backend && npx tsc --noEmit -p .
no output, exit 0
```
The Hub tests use two subscribers whose close callbacks immediately unsubscribe themselves, proving
safe snapshot iteration and exactly-once closure. The live-route test opens two HTTP streams, proves
both receive EOF on clear, publishes a new event and gate, then reconnects after id 1 and replays
exactly ids 2 and 3. The forget test closes both subscribers and proves the next id resets to 1.
Close now removes the observed runtime identity before awaiting persistence. Failure persistence is
claimed once per runtime and lifecycle-serialized; bootstrap's public `session_failed` cannot start
a duplicate. A per-target in-flight map owns the only same-ID POST and commit while its mutable
latest token keeps s1→s2→s1 ordering correct; it is removed immediately after the binding commit,
before awaiting the independently guarded manifest. One shared invalidation helper is called when
active deletion, streamed exit, Start new, or Stop begins.
### Focused verification
```text
cd backend && npx vitest run test/routes-sessions.test.ts test/pi-process-manager.test.ts \
test/sse-hub.test.ts test/sse-route.test.ts
Test Files 4 passed (4)
Tests 96 passed (96)
cd backend && npx tsc --noEmit -p .
no output, exit 0
cd frontend && npx vitest run src/shell/AppShell.session-mgmt.test.tsx \
src/shell/AppShell.new-session.test.tsx src/stream/useSessionStream.test.tsx
Test Files 3 passed (3)
Tests 36 passed (36)
cd frontend && npx tsc -b
no output, exit 0
```
### Full verification
```text
cd backend && npx vitest run
Test Files 22 passed (22)
Tests 202 passed (202)
cd frontend && npx vitest run
Test Files 43 passed (43)
Tests 280 passed (280)
cd backend && npm run build
> tsc -p tsconfig.json
exit 0
cd frontend && npm run build
> tsc -b && vite build
✓ 4835 modules transformed.
✓ built in 8.47s
exit 0
```
The frontend suite/build retain the previously documented MSW, React ref/`act`, experimental type
stripping, and large-chunk warnings. No warning class was introduced by this wave. No harness,
workflow, persistence, SQL/CTE viewer, model-selection, or deployment file changed. The four
pre-existing modified `.superpowers/sdd/{progress,task-2-report,task-3-report,task-4-report}.md`
files remain excluded from staging.
### Final independent-review verdict
After the multi-client transport fix, the independent reviewer reported no Critical, Important, or
Minor findings. Its own focused verification passed 96 backend transport/lifecycle tests, 31
frontend Resume/stream tests, both TypeScript checks, and `git diff --check`. Final assessment:
**Ready to deploy: Yes.**