Files
tech-log-frontend/docs/superpowers/plans/2026-08-01-scope-owned-server-state.md
T

100 lines
5.9 KiB
Markdown

# Scope-Owned Server State Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Prevent previous-account query and optimistic state from remaining renderable or writable after an authentication scope transition.
**Architecture:** The scope runtime is a fail-closed lifecycle authority. A bootstrap generation store owns a QueryClient and its concrete invalidation coordinator, swaps them only after the previous generation is closed, and exposes a stable invalidation facade plus a subscribable generation snapshot to React.
**Tech Stack:** TypeScript 7, React 19, TanStack Query 5, Vitest 4, Testing Library.
## Global Constraints
- FENCED is synchronous and no previous-account UI may render after it.
- Mandatory participant or cache cleanup failure never publishes READY.
- Snapshot subscriber defects never prevent reset scheduling.
- Every READY generation owns a different QueryClient identity.
- Late work from an old generation cannot write through the stable coordinator.
- Preserve existing public application ports and dirty-worktree changes.
---
### Task 1: Fail-closed scope lifecycle
**Files:**
- Modify: `src/contracts/server-state-scope.ts`
- Modify: `src/adapters/query-cache/server-state-scope-runtime.ts`
- Modify: `tests/unit/server-state-scope-runtime.test.ts`
**Interfaces:**
- Adds phase `FAILED` and lifecycle event `{ kind: "FAILED"; generation: number }`.
- Adds dependency callback `activateNextGeneration(): void | Promise<void>` executed after old local reset and before READY.
- [ ] Add failing tests for a throwing snapshot listener, participant failure, reset failure, and next-generation activation failure.
- [ ] Run `corepack pnpm exec vitest run tests/unit/server-state-scope-runtime.test.ts` and confirm each new behavior fails for the intended reason.
- [ ] Notify listeners from a stable snapshot with per-listener exception isolation.
- [ ] Continue all cleanup participants, remember the first mandatory failure, and always attempt local reset.
- [ ] Close old identities after cleanup, then publish FAILED if any mandatory step failed.
- [ ] Call `activateNextGeneration` only after successful reset and publish READY only after it succeeds.
- [ ] Re-run the focused unit test and confirm all lifecycle cases pass.
### Task 2: Render fence surface synchronously
**Files:**
- Modify: `src/presentation/adapters/query/server-state-scope-provider.tsx`
- Create: `tests/component/server-state-scope-provider.test.tsx`
**Interfaces:**
- `ServerStateScopeProvider` subscribes to both snapshot and phase through one store notification.
- Props add optional `transitionFallback?: ReactNode`; default is `null`.
- Existing `useServerStateScope()` continues returning `CacheScopeSnapshot` for feature hooks.
- [ ] Add a failing component test that renders private child data, triggers a deferred reset, and expects the child to disappear in the same `act()` turn.
- [ ] Add a failing test that FAILED never remounts children.
- [ ] Run the new component test and confirm old children remain with the current provider.
- [ ] Subscribe in the provider and render children only for READY; render the supplied fallback for FENCED/FAILED and nothing after DISPOSED.
- [ ] Re-run the test and confirm no old-data frame is observable.
### Task 3: QueryClient generation store and stable coordinator facade
**Files:**
- Create: `src/bootstrap/server-state-generation-store.ts`
- Modify: `src/bootstrap/runtime-adapters.ts`
- Modify: `src/bootstrap/runtime-application.tsx`
- Modify: `tests/unit/runtime-adapters.test.ts`
- Modify: `tests/component/runtime-application.test.tsx`
**Interfaces:**
- `ServerStateGenerationSnapshot = { generation: number; queryClient: QueryClient; coordinator: QueryInvalidationCoordinator; crossContextStatus(): CrossContextInvalidationStatus }`.
- Store methods: `getSnapshot()`, `subscribe(listener)`, `activateNext()`, `resetCurrent()`, `dispose()`.
- A stable `QueryInvalidationCoordinator` facade delegates only to the current snapshot and fences delegates by generation.
- [ ] Add a failing unit test that captures the initial QueryClient, triggers session transition, completes reset, and expects a different current QueryClient.
- [ ] Add a failing test that an old captured coordinator cannot invalidate after activation.
- [ ] Add a component test that QueryClientProvider receives and renders against the new generation.
- [ ] Implement generation factory ownership in bootstrap; create cross-context transport and concrete coordinator per generation.
- [ ] Pass `resetCurrent` and `activateNext` to the scope runtime in the required order.
- [ ] Make `RuntimeApplication` subscribe to the generation store and key the QueryClient provider by generation.
- [ ] Dispose the current generation and store exactly once during application shutdown.
- [ ] Run runtime-adapter and runtime-application tests until all pass.
### Task 4: Conditional validator and cleanup ordering
**Files:**
- Modify: `src/bootstrap/runtime-adapters.ts`
- Modify: `tests/unit/runtime-adapters.test.ts`
**Interfaces:**
- Conditional validator clearing becomes an ordered mandatory scope participant rather than a general snapshot subscriber.
- [ ] Add a failing order test proving validator clear occurs after admission fence and before cache disposal.
- [ ] Register the validator closer with an explicit order and remove the generic subscription.
- [ ] Re-run runtime-adapter tests and verify each closer executes once.
### Task 5: Verification
- [ ] Run scope runtime, provider, runtime-adapter, runtime-application, and application-query focused suites.
- [ ] Run `corepack pnpm check:types` and `corepack pnpm lint`.
- [ ] Run the complete non-browser suite.
- [ ] Record browser-only follow-up separately; do not claim it passed without Playwright evidence.