> 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/phase23-session03-projection-folding/spec.md).

# Session Specification

**Session ID**: `phase23-session03-projection-folding` **Phase**: 23 - Legion I - Scanner Camps **Status**: Not Started **Created**: 2026-07-05 **Base Commit**: 4fd7a073dd7a41799aaa26595067f31156b5526c **Package**: apps/web **Package Stack**: TypeScript

***

## 1. Session Overview

This session replaces the current `suggestion_update` camp no-op in `apps/web/src/lib/gameProjection.ts` with honest scanner camp reconciliation. The reducer will fold current `CodebaseIssue` snapshots through the existing `reconcileLegionCamps` helper from Session 01 and update only the aggregate `GameProjection` camp and sector fields.

It is next because Phase 23 has completed the pure reconciliation and ranking foundation, and the active phase dependency table marks Session 03 as the next unstarted executable session. This work unlocks the store persistence, Quest Board focus, and battlefield rendering sessions without creating a second game-state authority.

The implementation stays reducer-scoped. It does not add storage keys, protocol events, Quest Board filtering, battlefield UI, Banish behavior, mission links, rewards, camp damage, XP, loot, banners, achievements, or hosted state.

***

## 2. Objectives

1. Wire `suggestion_update` codebase issue snapshots into `reconcileLegionCamps` inside `apps/web/src/lib/gameProjection.ts`.
2. Update projection camps and sectors only when reconciliation changes the aggregate projection state.
3. Preserve replay, rewards, pending-link, camp-link, and unrelated projection field isolation.
4. Extend `apps/web/tests/gameProjection.test.ts` for scanner camp folding, replay no-op behavior, alert focus priority, and aggregate-only privacy.

***

## 3. Prerequisites

### Required Sessions

* [x] `phase23-session01-pure-camp-reconciliation` - provides `reconcileLegionCamps`, severity weights, sector coalescing, stable cells, tiers, entrenchment, and aggregate-only output.
* [x] `phase22-session01-projection-contract` - provides the versioned `GameProjection`, sector, camp, alert focus, and reducer boundaries.
* [x] `phase22-session05-store-folding-and-selectors` - provides normal web event folding through the single projection authority.
* [x] `phase22-session08-erasure-docs-and-validation` - validates replay, mock, persistence, and browser-local erasure guardrails that this session must preserve.

### Required Tools Or Knowledge

* TypeScript reducer patterns in `apps/web/src/lib/gameProjection.ts`.
* Pure Legion camp helper API in `apps/web/src/lib/legionCamps.ts`.
* Existing `ServerEvent` and `CodebaseIssue` contracts from `@factionos/protocol`.
* Vitest patterns in `apps/web/tests/gameProjection.test.ts`.

### Environment Requirements

* Node 26.2.0 or newer and npm 11.16.0.
* Root workspace dependencies installed.
* Commands run from the repository root.

***

## 4. Scope

### In Scope (MVP)

* The web projection reducer can reconcile current scanner issues from `suggestion_update` into `GameProjection.legion.camps` and `GameProjection.sectors`.
* The reducer rejects replayed `suggestion_update` events before camp reconciliation.
* Mock-enabled reducer calls may fold camps in memory while persistence remains guarded by the existing store-level `shouldPersistGameProjection` path.
* `lastEventAt` changes only when camp or sector reconciliation changes the projection state.
* `alertFocus` is recomputed after camp changes, with existing flare, gate, siege, ram, and skirmisher priority above `newCamp`.
* Focused tests prove issue grouping, HP, tiers, stable sector data, entrenchment, removal, replay no-op, no reward or link side effects, alert priority, and field isolation.

### Out Of Scope (Deferred)

* Store persistence assertions - Reason: Session 04 owns persistence, replay, mock-mode, reset, local erasure, and no-new-storage-key regression coverage.
* Quest Board dismissal, inspection, or Banish UI - Reason: Session 05 owns camp focus and issue dismissal through existing Quest Board actions.
* Battlefield camp rendering and Champion Mode product copy - Reason: Session 06 owns `CampLayer`, accessible buttons, and visible camp limits.
* Golden Age, scan-not-run, and scan-failed presentation - Reason: Session 07 owns scan lifecycle and clean-state battlefield UI.
* Camp links, pending links, mission links, camp damage, XP, loot, banners, achievements, new protocol fields, hosted state, or trusted-erasure claims - Reason: Phase 23 explicitly forbids these behaviors for scanner camps.

***

## 5. Technical Approach

### Architecture

Keep `gameProjection.ts` as the single derived game-state authority. Import `reconcileLegionCamps` from `./legionCamps` and change `reduceSuggestionUpdate` to accept the `suggestion_update` event plus reducer context. Replayed snapshots return the original state before reconciliation.

For normal and mock-enabled non-replay events, pass `event.codebaseIssues`, `state.legion.camps`, `state.sectors`, and a normalized snapshot timestamp into `reconcileLegionCamps`. Use `event.updatedAt` when valid and fall back to the reducer context timestamp when the event timestamp cannot be used.

Apply the reconciled camps and sectors only when they differ from the current projection records. Because the pure helper returns fresh records and reports new or changed camp ids but does not fully report removed camps or sector-only changes, add reducer-local equality checks for camp records and sector records. When no camp or sector value changes, return the original state.

When reconciliation changes state, update `legion.camps`, `sectors`, and `lastEventAt`, then run the existing alert-focus finalization path. Do not touch `pendingLinks`, `campLinks`, heroes, essence, buildings, War Tide, council objectives, nemeses, season, attention, or flare ceremony.

### Design Patterns

* Snapshot reconciliation: current scanner issue snapshots own the full camp census, so absent issues remove stale camps.
* Reducer-local change detection: keep identity stable for no-op snapshots and set `lastEventAt` only for real camp or sector changes.
* Aggregate-only privacy boundary: store issue ids, safe sector labels, HP, tiers, counters, cells, and timestamps only.
* Existing priority composition: reuse `finalizeProjectionUpdate` and `recomputeAlertFocus` so new camps remain below flares, gates, siege, rams, and skirmishers.

***

## 6. Deliverables

### Files To Create

| File | Purpose                                                     | Est. Lines |
| ---- | ----------------------------------------------------------- | ---------- |
| None | This session modifies existing reducer and test files only. | 0          |

### Files To Modify

| File                                    | Changes                                                                                                                                                            | Est. Lines |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- |
| `apps/web/src/lib/gameProjection.ts`    | Import camp reconciliation, replace `suggestion_update` no-op, add reducer-local record equality, update camps/sectors/lastEventAt/alert focus only on change.     | \~90       |
| `apps/web/tests/gameProjection.test.ts` | Replace no-op snapshot assertions and add focused reducer tests for scanner camp reconciliation, replay, mock in-memory folding, alert focus, and field isolation. | \~260      |

***

## 7. Success Criteria

### Functional Requirements

* [ ] A `suggestion_update` with three codebase issues in two sectors creates two projection camps in the normal reducer flow.
* [ ] HP uses the Session 01 severity weights: error = 3, warning = 2, info = 1.
* [ ] Camp tiers change at the documented thresholds.
* [ ] Sector ids, labels, and cells are stable across identical snapshots.
* [ ] Large issue sets coalesce into bounded sectors instead of one camp per issue.
* [ ] Repeated identical snapshots increment `entrenchedScans`, changed membership resets it, and removed issues remove camps.
* [ ] Replayed `suggestion_update` events do not mutate camps.
* [ ] Mock-enabled non-replay snapshots can fold camps in memory without changing persistence guard semantics.
* [ ] New camps do not steal alert focus from flares, gates, siege, rams, or skirmishers.

### Testing Requirements

* [ ] Focused reducer tests cover scanner camp creation, HP, tiers, sector stability, sector coalescing, entrenchment, changed membership, removals, replay no-op, mock in-memory folding, and alert priority.
* [ ] Regression tests prove no XP, loot, banners, achievements, camp damage, pending links, camp links, or unrelated projection fields change during camp reconciliation.
* [ ] Privacy tests prove projection JSON does not contain raw issue messages, suggested prompts, event bodies, terminal output, provider payloads, file contents, absolute paths, environment values, or secrets.
* [ ] Focused test command passes for `apps/web/tests/gameProjection.test.ts`.

### Non-Functional Requirements

* [ ] No new protocol event, storage key, hosted dependency, or trusted-erasure claim is introduced.
* [ ] Reducer output is deterministic across identical inputs.
* [ ] No-op snapshots preserve original object identity where no aggregate camp or sector value changes.

### Quality Gates

* [ ] All files ASCII-encoded.
* [ ] Unix LF line endings.
* [ ] Code follows project conventions.
* [ ] `npm --workspace @factionos/web run typecheck` passes.

***

## 8. Implementation Notes

### Working Assumptions

* Package context is `apps/web`: the analysis script reports Session 03 as an `apps/web` candidate, and every deliverable targets the web projection reducer or web tests.
* Session 03 depends on Session 01, not Session 02: the Phase 23 dependency table says projection folding depends on the reconciliation helper only. Session 02 is complete and useful context, but its Champion Mode selectors are not required for reducer folding.
* `event.updatedAt` is the scanner snapshot timestamp for reducer folding when valid. Planning can proceed because the existing `SuggestionUpdateEvent` factory and Phase 23 PRD both identify snapshot update time, and the reducer can fall back to `ctx.now` if the event timestamp is invalid.
* Mock-enabled behavior is reducer-local in this session: the reducer may fold camps in memory while Session 04 proves store persistence does not write mock state.

### Conflict Resolutions

* The master PRD planned-phase table still labels Phase 23 as Not Started, while `.spec_system/PRD/phase_23/PRD_phase_23.md`, state analysis, and completed session records show Phase 23 is in progress with Sessions 01 and 02 complete. The chosen interpretation is to trust the analysis script and live Phase 23 PRD for workflow state.
* Current `gameProjection.test.ts` says suggestion snapshots are no-progress no-ops for links, camps, and rewards, while Phase 23 requires camp reconciliation. The chosen interpretation is to change only the camp and sector part of that assertion: snapshots still create no progress, rewards, links, or rich copied issue detail.
* Phase 22 intentionally left `suggestion_update` camp folding as a no-op, but Phase 23 explicitly replaces that no-op with snapshot-owned reconciliation. The chosen implementation is reducer folding only, preserving no protocol, storage, hosted, reward, camp-damage, or mission-link expansion.
* `reconcileLegionCamps` returns new records and reports new or changed camps, but not every reducer no-op condition. The chosen reducer implementation adds local record equality so `lastEventAt` and object identity change only when projection state actually changes.

### Key Considerations

* Keep `gameProjection.ts` as the single derived game-state authority.
* Keep Quest Board as the owner of rich issue titles, messages, prompts, and actions; projection stores only aggregate ids and safe labels.
* Preserve replay and mock honesty. Replayed scanner traffic cannot mint camps, and mock in-memory camps do not imply persisted progress.
* Keep this session free of product UI changes; user-facing inspection, Banish, battlefield rendering, scan lifecycle, and Golden Age presentation belong to later sessions.

### Potential Challenges

* Reconciliation can accidentally update `lastEventAt` for identical snapshots: compare current and reconciled records before returning a new projection.
* New camp alert focus can override higher-priority attention if finalization bypasses existing focus helpers: reuse `finalizeProjectionUpdate`.
* Privacy can regress if tests inspect only camp shape and not serialized projection JSON: keep explicit forbidden-snippet assertions.

### Relevant Considerations

* \[P22-apps/web] **Game projection is the single derived authority**: Fold camps through `gameProjection.ts` rather than adding a parallel authority.
* \[P22-apps/web] **Derived projection storage is narrow**: This session adds no storage key and keeps camp fields aggregate-only.
* \[P22-apps/web] **Do not persist replay or drill progress**: Replay returns no-op; mock behavior remains in-memory and persistence is left to the existing guard.
* \[P22-apps/web] **Do not promote dormant camp links**: Scanner snapshots do not write `pendingLinks` or `campLinks`.
* \[P18-apps/server] **Quest Board state is durable local evidence**: Rich issue details remain in Quest Board, not projection state.
* \[P07] **Redaction is boundary-specific**: The reducer and tests must keep raw commands, terminal output, file contents, provider payloads, secrets, and broad absolute paths out of broad projection surfaces.

### Behavioral Quality Focus

Checklist active: Yes Top behavioral risks for this session:

* External scanner snapshots are reducer input, so `suggestion_update` handling must use the declared `ServerEvent` contract and explicit replay rejection.
* Reducer state changes can create false progress signals, so tests must prove no rewards, links, camp damage, XP, banners, achievements, or unrelated fields change.
* Reopenable replay and mock contexts can create durable drift, so replay no-op and mock in-memory behavior must be tested separately from Session 04 persistence assertions.

***

## 9. Testing Strategy

### Unit Tests

* Add focused reducer tests in `apps/web/tests/gameProjection.test.ts` for camp creation, HP, tiers, stable sector ids/labels/cells, bounded large-repo coalescing, entrenchment increment/reset, removal, replay no-op, mock in-memory folding, and alert priority.

### Integration Tests

* Use existing `reduceGameProjection` and `makeSuggestionUpdate` test helpers to exercise the real event reducer path with typed `suggestion_update` events.

### Runtime Verification

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

### Edge Cases

* Empty codebase issue snapshots remove stale camps.
* Invalid or missing issue timestamps use the snapshot timestamp fallback.
* Replayed snapshots return the same projection object.
* Existing flares, gates, siege, rams, and skirmishers keep alert priority over new camps.
* Serialized projection output excludes raw issue text, prompts, absolute paths, event bodies, terminal output, provider payloads, file contents, environment values, and secrets.

***

## 10. Dependencies

### Other Sessions

* Depends on: `phase23-session01-pure-camp-reconciliation`.
* Depended by: `phase23-session04-store-persistence-boundaries`, `phase23-session05-quest-board-camp-focus`, `phase23-session06-battlefield-camp-layer`.

***

## 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/phase23-session03-projection-folding/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.
