> For the complete documentation index, see [llms.txt](https://faction-os.gitbook.io/faction-os-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://faction-os.gitbook.io/faction-os-docs/.spec_system/archive/sessions/phase22-session01-projection-contract/spec.md).

# Session Specification

**Session ID**: `phase22-session01-projection-contract` **Phase**: 22 - Projection Foundation **Status**: Not Started **Created**: 2026-07-04 **Base Commit**: 47f67eb86007df0d3fd829db4baa485fa7cdbbaf **Package**: apps/web **Package Stack**: TypeScript

***

## 1. Session Overview

This session creates the first `apps/web` game projection authority: a pure, versioned, serializable `gameProjection.ts` module with the complete neutral v1 aggregate shape and defensive browser-storage helpers. It does not wire the projection into the store, render new UI, add audio, or fold broad event behavior.

It is next because Phase 22 has no completed sessions, the candidate stub for Session 01 has no session prerequisites, and Sessions 02-08 depend on this contract before reducers, store folding, persistence scheduling, erasure coverage, and honesty regression tests can be implemented.

The output gives later sessions a stable API for projection types, constants, initial state, no-op reducer behavior, persistence parsing/loading/writing, and replay/mock persistence guards while preserving current source-of-truth boundaries for faction selection, scrolls, achievements, War Room, settings, Notice Board, replay, animation, audio, and prompts.

***

## 2. Objectives

1. Define the `GameProjection` v1 public contract, constants, context types, and serializable subtype interfaces in `apps/web/src/lib/gameProjection.ts`.
2. Implement `createInitialGameProjection(now?)` with the full neutral v1 shape and deterministic timestamp and season behavior.
3. Implement defensive parse, load, persist, and persistence-guard helpers that tolerate unavailable storage, malformed JSON, wrong versions, quota failures, and null storage.
4. Add pure Vitest coverage proving full shape, deterministic timestamps, JSON round trip, source-of-truth exclusions, malformed persisted input fallback, and same-reference no-op reducer behavior.

***

## 3. Prerequisites

### Required Sessions

* [x] Phase 21 - Bottom Rail Focused Surface Expansion completed according to `.spec_system/state.json` analysis.

### Required Tools Or Knowledge

* Node 26.2.0+ and npm 11.16.0 workspace toolchain.
* TypeScript and Vitest patterns from `apps/web/tests/replayPersist.test.ts`, `apps/web/tests/localErasure.test.ts`, and pure-helper tests under `apps/web/tests/`.
* Protocol `ServerEvent` type from `packages/protocol/src/events.ts`.

### Environment Requirements

* Local workspace dependencies installed.
* No provider credentials, hosted services, Cloudflare credentials, or local server runtime are required for this pure module and test session.

***

## 4. Scope

### In Scope (MVP)

* Web projection consumers need a single versioned aggregate contract in `apps/web/src/lib/gameProjection.ts` - create constants, context types, entity aliases, and the complete `GameProjection` interface.
* Later reducer and store sessions need a deterministic neutral baseline - implement `createInitialGameProjection(now?)` for sectors, legion, links, heroes, essence, buildings, War Tide, council, nemeses, alert focus, season, flare ceremony, and `lastEventAt`.
* Browser-local projection persistence needs defensive helpers - implement parse, load, persist, and `shouldPersistGameProjection` helpers that fail closed and swallow storage failures.
* Future reducers need a stable reducer API without accidental accumulation - expose `reduceGameProjection` as a pure no-op contract that returns the exact same reference until Session 02 expands reducer behavior.
* Validation needs focused proof - add `apps/web/tests/gameProjection.test.ts` for shape, determinism, parser fallback, storage behavior, JSON round trip, source-of-truth exclusions, and same-reference no-op reducer behavior.

### Out Of Scope (Deferred)

* Store folding, selectors, and `useGameStore.ts` integration - Reason: Session 05 owns store hydration, event folding, selectors, and reset memory behavior.
* Debounced persistence scheduling, lifecycle flushing, and reset storage removal - Reason: Session 06 owns scheduling and page lifecycle behavior.
* Browser-local erasure inventory updates - Reason: Session 08 owns `localErasure.ts` cleanup coverage for `factionos-game-v1`.
* Hero attention, Legion enemy, camp-link, XP, essence, War Tide, council, nemesis, fog, territory, and Dawn Report reducers - Reason: Sessions 02-04 and later phases own those slices.
* Rendering, battlefield UI, Quest Board UI, audio, toasts, ceremony, hosted persistence, protocol changes, and any second game state authority - Reason: Phase 22 foundation explicitly excludes presentation and parallel authority.

***

## 5. Technical Approach

### Architecture

Create `apps/web/src/lib/gameProjection.ts` as a pure TypeScript module that imports `ServerEvent` as a type only. The module owns the projection root type, nested serializable aggregate types, version and storage constants, persistence cadence constants, reducer and persistence context types, a deterministic initial-state factory, defensive storage helpers, and a no-op reducer shell.

The contract stays self-contained and side-effect free except for storage helper calls that accept optional `Storage | null`. Store integration, lifecycle listeners, scheduling timers, and UI reads remain outside this session so later sessions can wire one source of projection truth into `useGameStore.ts`.

### Design Patterns

* Pure helper module: Matches existing `apps/web/src/lib/*` pure-helper patterns and keeps reducer logic testable without React rendering.
* Type-only protocol import: Uses `ServerEvent` from `@factionos/protocol` without adding protocol events or duplicating protocol contracts.
* Defensive storage boundary: Mirrors replay persistence posture by treating missing, malformed, wrong-version, unsafe, quota-limited, or null storage as non-fatal.
* Stable public constants: Exposes storage key and cadence constants for later store lifecycle sessions instead of scattering magic values.

***

## 6. Deliverables

### Files To Create

| File                                    | Purpose                                                                                                                | Est. Lines |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------- |
| `apps/web/src/lib/gameProjection.ts`    | Versioned projection contract, initial-state factory, no-op reducer shell, and defensive persistence helpers.          | \~320      |
| `apps/web/tests/gameProjection.test.ts` | Pure Vitest coverage for the projection contract, storage helpers, serialization boundary, and reducer no-op behavior. | \~220      |

### Files To Modify

| File | Changes                                                                                                                                                                        | Est. Lines |
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- |
| None | Session 01 creates the contract and tests only; store, erasure, and documentation changes are deferred to later Phase 22 sessions unless tests expose a direct contract issue. | 0          |

***

## 7. Success Criteria

### Functional Requirements

* [ ] `createInitialGameProjection()` returns the full v1 shape with neutral future-owned fields and injected time.
* [ ] `season.id` is deterministic from the injected date using the `season:YYYY-MM-DD` shape.
* [ ] `parsePersistedGameProjection` returns a valid fresh projection for missing, malformed, unsupported-version, null, or unsafe input.
* [ ] `loadPersistedGameProjection` and `persistGameProjection` tolerate null, unavailable, malformed, quota-limited, and throwing storage without surfacing runtime errors.
* [ ] `shouldPersistGameProjection` returns false while replaying or while mock/drill mode is enabled.
* [ ] `reduceGameProjection` returns the exact same state reference for `connected` and unknown event inputs until later reducer sessions expand behavior.

### Testing Requirements

* [ ] `apps/web/tests/gameProjection.test.ts` covers full shape, deterministic timestamps, deterministic season id, malformed persisted input, storage round trip, JSON round trip, source-of-truth exclusions, and same-reference reducer behavior.
* [ ] `npm test -- apps/web/tests/gameProjection.test.ts` passes.
* [ ] `npm --workspace @factionos/web run typecheck` passes.

### Non-Functional Requirements

* [ ] Projection JSON contains aggregate projection state only and excludes selected faction, scroll inventory, achievement inventory, War Room membership, settings, Notice Board messages, visible hero slots, replay entries, toasts, animation state, audio state, raw prompts, broad paths, terminal output, file contents, provider payloads, tokens, and secrets.
* [ ] The module is deterministic under fixed inputs and has no timers, DOM rendering, audio, React, Zustand, or local server dependency.
* [ ] Storage helpers fail closed and do not throw when browser storage is absent or unreliable.

### Quality Gates

* [ ] All files ASCII-encoded.
* [ ] Unix LF line endings.
* [ ] Code follows project conventions.
* [ ] Primary user-facing surfaces are unchanged; this session adds no UI surface.

***

## 8. Implementation Notes

### Working Assumptions

* Phase 22 Session 01 is the next executable session: The analysis script reports `current_session: null`, Phase 22 status `not_started`, all Phase 22 candidates incomplete, and `session_01_projection_contract` has no dependencies. Planning can proceed without user arbitration because later stubs explicitly depend on Session 01.
* Package context is `apps/web`: The analysis script candidate package is `apps/web`, the Phase 22 PRD scope names `apps/web/src/lib/gameProjection.ts`, and the web README identifies `src/lib/` and `tests/` as the pure-helper and Vitest locations. Planning can proceed with a concrete package path.

### Conflict Resolutions

* Phase 22 implementation checklist says the projection module owns `reduceGameProjection`, while the Session 01 stub excludes broad event folding. The chosen interpretation is to export a pure reducer shell that returns the same state reference for `connected` and unknown inputs, leaving actual reducer slices to Sessions 02-04. This satisfies the Session 01 success criterion without stealing later session scope.

### Key Considerations

* The projection is derived, local browser state. It must not become the source of truth for existing settings, faction, scroll, achievement, War Room, Notice Board, replay, animation, audio, or raw prompt data.
* `GAME_PROJECTION_STORAGE_KEY` is `factionos-game-v1`; erasure inventory and reset removal are intentionally deferred to Sessions 06 and 08.
* Persistence helpers are synchronous defensive utilities only. Debounce and lifecycle ownership belong in the store session.

### Potential Challenges

* Large nested state shape: Keep factory helpers small and explicit so missing neutral fields are visible in tests.
* Storage type validation: Validate only the v1 root and known aggregate shape needed for a safe persisted projection; unsafe inputs should become a fresh projection rather than partial untrusted state.
* Serialization boundary drift: Tests should assert forbidden top-level names and representative blocked strings are absent from `JSON.stringify(createInitialGameProjection())`.

### Relevant Considerations

* \[P03] **Stable docs are the current contract**: Use `.spec_system/PRD/phase_22/PRD_phase_22.md`, package READMEs, and protocol types as current truth rather than archived source material.
* \[P20] **Broad privacy gates are release-critical**: Keep raw prompts, terminal output, file contents, provider payloads, broad paths, tokens, and secrets out of projection snapshots and tests.
* \[P08] **Full trusted erasure remains no-claim**: Adding a browser projection key does not imply broad trusted erasure; cleanup coverage is handled later and remains scoped.

### Behavioral Quality Focus

Checklist active: Yes Top behavioral risks for this session:

* Malformed or hostile persisted input could poison future projection state if parsing accepts unsafe shapes.
* Replay or mock mode could later persist drill-derived progress if the guard helper defaults incorrectly.
* The new aggregate could accidentally duplicate current source-of-truth or presentation state if serialization exclusions are not tested.

***

## 9. Testing Strategy

### Unit Tests

* Test `createInitialGameProjection` for the complete neutral v1 shape, injected timestamp usage, deterministic season id, and zero/null/empty-record defaults.
* Test `parsePersistedGameProjection`, `loadPersistedGameProjection`, `persistGameProjection`, and `shouldPersistGameProjection` with valid, missing, malformed, wrong-version, null-storage, throwing-storage, replaying, and mock-enabled cases.
* Test `reduceGameProjection` same-reference behavior for `connected` and unknown event inputs.

### Integration Tests

* None for this session. Store folding, selector reads, lifecycle scheduling, reset storage removal, and erasure coverage are later Phase 22 sessions.

### Runtime Verification

* Run `npm test -- apps/web/tests/gameProjection.test.ts`.
* Run `npm --workspace @factionos/web run typecheck`.

### Edge Cases

* Missing storage key, invalid JSON, non-object JSON, wrong `version`, unsupported nested field shapes, unavailable storage, `localStorage` throwing on `getItem`, `setItem`, or `removeItem`, null storage, JSON stringify failures, replaying persistence context, mock-enabled persistence context, and unknown events.

***

## 10. Dependencies

### Other Sessions

* Depends on: none within Phase 22; Phase 21 is complete.
* Depended by: `phase22-session02-hero-attention-reducer`, `phase22-session03-legion-enemy-reducer`, `phase22-session04-camp-link-boundaries`, `phase22-session05-store-folding-and-selectors`, `phase22-session06-persistence-lifecycle`, `phase22-session07-honesty-regression-tests`, and `phase22-session08-erasure-docs-and-validation`.

***

## Next Steps

Run the `implement` workflow step to begin implementation.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://faction-os.gitbook.io/faction-os-docs/.spec_system/archive/sessions/phase22-session01-projection-contract/spec.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
