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

5.9 KiB

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.