> 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-session04-camp-link-boundaries/implementation-notes.md).

# Implementation Notes

**Session ID**: `phase22-session04-camp-link-boundaries` **Package**: apps/web **Started**: 2026-07-05 10:35 **Last Updated**: 2026-07-05 11:21

***

## Session Progress

| Metric              | Value     |
| ------------------- | --------- |
| Tasks Completed     | 20 / 20   |
| Estimated Remaining | 0 minutes |
| Blockers            | 0         |

***

## Task Log

Task entries are listed newest first.

### Task T020 - Run focused checks and encoding validation

**Started**: 2026-07-05 11:15 **Completed**: 2026-07-05 11:21 **Duration**: 6 minutes

**Notes**:

* Ran the required focused reducer suite, package typecheck, Biome check, ASCII check, LF check, and whitespace check.
* Confirmed no BQC fixes were needed beyond the implemented validation, duplicate prevention, contract alignment, replay guard, and no-progress paths.

**Files Changed**:

* `apps/web/src/lib/gameProjection.ts` - final checked reducer source.
* `apps/web/tests/gameProjection.test.ts` - final checked reducer tests.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task and completion checklist complete.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded final verification evidence.

**Verification**:

* Command/check: `npm test -- apps/web/tests/gameProjection.test.ts`
  * Result: PASS - 1 test file passed; 24 tests passed.
  * Evidence: Focused reducer suite passed with camp-link and no-progress coverage.
* Command/check: `npm --workspace @factionos/web run typecheck`
  * Result: PASS - `tsc -b --noEmit` exited 0.
  * Evidence: Web package typecheck passed.
* Command/check: `npm exec -- biome check apps/web/src/lib/gameProjection.ts apps/web/tests/gameProjection.test.ts`
  * Result: PASS - Biome checked 2 files with no fixes applied.
  * Evidence: Changed source and test files passed Biome.
* Command/check: ASCII/LF validation over changed source, test, and session artifacts
  * Result: PASS - no non-ASCII bytes and no CRLF line endings found.
  * Evidence: `grep -nP '[^\x00-\x7F]'` and `rg -nU $'\r'` returned no matches.
* Command/check: `git diff --check`
  * Result: PASS - no whitespace errors.
  * Evidence: Command exited 0 with no output.
* UI product-surface check: N/A - reducer/test/spec artifacts only; no rendered user-facing files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T019 - Test suggestion\_update no-progress snapshots

**Started**: 2026-07-05 11:14 **Completed**: 2026-07-05 11:15 **Duration**: 1 minute

**Notes**:

* Added idle suggestion and codebase issue snapshot tests proving `suggestion_update` is inert for camp links and progress.
* Verified snapshot prompts are not copied into projection state.

**Files Changed**:

* `apps/web/tests/gameProjection.test.ts` - added snapshot no-progress tests.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `npm test -- apps/web/tests/gameProjection.test.ts`
  * Result: PASS - 1 test file passed; 24 tests passed.
  * Evidence: `treats suggestion snapshots as no-progress no-ops for links, camps, and rewards` returns the same state and verifies links, camps, XP, banners, essence, and War Tide remain unchanged.
* Command/check: `rg -n "Idle snapshot prompt|Codebase issue prompt|suggestion snapshots" apps/web/tests/gameProjection.test.ts`
  * Result: PASS - idle and codebase snapshot no-progress assertions are present.
  * Evidence: Test confirms snapshot prompts are not persisted into the projection.
* UI product-surface check: N/A - reducer test work only; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T018 - Test terminal clearing and unrelated-link preservation

**Started**: 2026-07-05 11:13 **Completed**: 2026-07-05 11:14 **Duration**: 1 minute

**Notes**:

* Added tests for `mission_complete`, failed `mission_update`, and cancelled `mission_update` clearing.
* Verified unrelated pending links, unrelated camp links, camp state, hero rewards, essence, and War Tide remain unchanged.

**Files Changed**:

* `apps/web/tests/gameProjection.test.ts` - added terminal clearing and preservation assertions.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `npm test -- apps/web/tests/gameProjection.test.ts`
  * Result: PASS - 1 test file passed; 24 tests passed.
  * Evidence: `clears mission camp links on completion and terminal updates without progress mutation` verifies terminal clearing and preservation of unrelated state.
* Command/check: `rg -n "clears mission camp links|mission-beta|mission-gamma|state: \"cancelled\"" apps/web/tests/gameProjection.test.ts`
  * Result: PASS - clearing scenarios are present in the focused reducer test file.
  * Evidence: Test asserts non-terminal update returns the same state and terminal updates clear only linked missions.
* UI product-surface check: N/A - reducer test work only; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T017 - Test mission\_start binding and rejection cases

**Started**: 2026-07-05 11:12 **Completed**: 2026-07-05 11:13 **Duration**: 1 minute

**Notes**:

* Added focused tests for exact prompt priority, same-hero fallback, duplicate mission start no-op, wrong-hero rejection, expired-link rejection, and replay no-op.

**Files Changed**:

* `apps/web/tests/gameProjection.test.ts` - added mission-start camp-link binding tests.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `npm test -- apps/web/tests/gameProjection.test.ts`
  * Result: PASS - 1 test file passed; 24 tests passed.
  * Evidence: `binds mission starts by exact prompt before same-hero fallback` covers exact binding, fallback binding, wrong-hero rejection, expired-link rejection, and replay no-op.
* Command/check: `rg -n "mission-exact|mission-fallback|mission-wrong-hero|mission-expired|mission-replay" apps/web/tests/gameProjection.test.ts`
  * Result: PASS - all binding and rejection assertions are present.
  * Evidence: Test file contains the expected mission cases.
* UI product-surface check: N/A - reducer test work only; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T016 - Test recordProjectionPendingLink behavior

**Started**: 2026-07-05 11:11 **Completed**: 2026-07-05 11:12 **Duration**: 1 minute

**Notes**:

* Added tests for valid input, invalid fields, duplicate exact links, expired input, sorted insertion, trigger refresh, expiry pruning, unsafe id rejection, and serialization round-trip.

**Files Changed**:

* `apps/web/tests/gameProjection.test.ts` - added `recordProjectionPendingLink` test coverage.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `npm test -- apps/web/tests/gameProjection.test.ts`
  * Result: PASS - 1 test file passed; 24 tests passed.
  * Evidence: `recordProjectionPendingLink` describe block covers valid, invalid, duplicate, expired, sorted, refreshed, pruned, unsafe, and serialized behavior.
* Command/check: `npm exec -- biome check apps/web/src/lib/gameProjection.ts apps/web/tests/gameProjection.test.ts`
  * Result: PASS - Biome checked 2 files with no fixes applied.
  * Evidence: Test additions meet formatter/linter expectations.
* UI product-surface check: N/A - reducer test work only; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T015 - Preserve serializable aggregate shape and authority boundaries

**Started**: 2026-07-05 11:10 **Completed**: 2026-07-05 11:11 **Duration**: 1 minute

**Notes**:

* Kept all changes in the pure web reducer and focused reducer tests.
* Did not add UI, protocol, server, audio, persistence, REST, WebSocket, or second authority surfaces.
* Parser accepts only bounded, sorted, duplicate-free pending links and safe `campLinks`.

**Files Changed**:

* `apps/web/src/lib/gameProjection.ts` - kept camp-link state serializable and parser-guarded.
* `apps/web/tests/gameProjection.test.ts` - added parse round-trip and pending-link serialization assertions.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `npm test -- apps/web/tests/gameProjection.test.ts`
  * Result: PASS - 1 test file passed; 24 tests passed.
  * Evidence: `recordProjectionPendingLink` test verifies persisted parse round trip and pending-link field shape.
* Command/check: `rg -n "recordProjectionPendingLink|isPendingLinkArray|isRecordOf\\(value.campLinks" apps/web/src/lib/gameProjection.ts`
  * Result: PASS - exported helper and parser guards are present in the reducer only.
  * Evidence: Changed authority remains in `apps/web/src/lib/gameProjection.ts` and tests; no protocol/server/UI paths were edited.
* UI product-surface check: N/A - no user-facing files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T014 - Preserve replay and snapshot anti-farming behavior

**Started**: 2026-07-05 11:09 **Completed**: 2026-07-05 11:10 **Duration**: 1 minute

**Notes**:

* `mission_start` returns the same state during replay and validates mission id, hero id, prompt, and active expiry before binding.
* `suggestion_update` snapshots stay no-op and do not create links, camps, rewards, or persisted snapshot prompt copies.

**Files Changed**:

* `apps/web/src/lib/gameProjection.ts` - added replay guard, schema-like input validation, expiry pruning, and snapshot no-op.
* `apps/web/tests/gameProjection.test.ts` - added replay, wrong-hero, expired-link, and snapshot no-progress assertions.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `npm test -- apps/web/tests/gameProjection.test.ts`
  * Result: PASS - 1 test file passed; 24 tests passed.
  * Evidence: Tests verify replay mission starts are no-op, wrong heroes do not bind, expired links are pruned without camp links, and suggestion snapshots cannot mutate progress fields.
* Command/check: `npm --workspace @factionos/web run typecheck`
  * Result: PASS - `tsc -b --noEmit` completed successfully.
  * Evidence: Protocol event narrowing and validation helper wiring typecheck cleanly.
* UI product-surface check: N/A - pure reducer/test work; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T013 - Add explicit suggestion\_update no-progress reducer case

**Started**: 2026-07-05 11:08 **Completed**: 2026-07-05 11:09 **Duration**: 1 minute

**Notes**:

* Added a `suggestion_update` switch case and a scoped comment documenting deferred camp coalescing.
* Snapshot events remain reconciliation-only and cannot create links or progress.

**Files Changed**:

* `apps/web/src/lib/gameProjection.ts` - added explicit no-progress `suggestion_update` reducer case.
* `apps/web/tests/gameProjection.test.ts` - added idle and codebase snapshot no-op assertions.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `npm test -- apps/web/tests/gameProjection.test.ts`
  * Result: PASS - 1 test file passed; 24 tests passed.
  * Evidence: `treats suggestion snapshots as no-progress no-ops for links, camps, and rewards` verifies `suggestion_update` returns the same state and does not store snapshot prompts.
* Command/check: `rg -n "case \"suggestion_update\"|reduceSuggestionUpdate|Camp coalescing remains deferred" apps/web/src/lib/gameProjection.ts`
  * Result: PASS - explicit no-progress reducer and scoped comment are present.
  * Evidence: Switch case routes `suggestion_update` to a deliberate state-preserving no-op.
* UI product-surface check: N/A - pure reducer/test work; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T012 - Wire failed and cancelled mission\_update clearing

**Started**: 2026-07-05 11:07 **Completed**: 2026-07-05 11:08 **Duration**: 1 minute

**Notes**:

* Added terminal `mission_update` handling for `failed` and `cancelled` states only.
* Non-terminal updates are explicit no-ops, and terminal clears do not mutate progress-like fields.

**Files Changed**:

* `apps/web/src/lib/gameProjection.ts` - wired failed/cancelled `mission_update` through `withoutMissionCampLink`.
* `apps/web/tests/gameProjection.test.ts` - added active, failed, and cancelled update assertions.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `npm test -- apps/web/tests/gameProjection.test.ts`
  * Result: PASS - 1 test file passed; 24 tests passed.
  * Evidence: Terminal update assertions clear `mission-beta` and `mission-gamma`; active update returns the same state.
* Command/check: `rg -n "case \"mission_update\"|reduceMissionUpdate|state: \"failed\"|state: \"cancelled\"" apps/web/src/lib/gameProjection.ts apps/web/tests/gameProjection.test.ts`
  * Result: PASS - terminal update reducer and tests are present.
  * Evidence: Reducer checks `patch.state` and only clears for `failed` or `cancelled`.
* UI product-surface check: N/A - pure reducer/test work; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T011 - Wire mission\_complete camp-link clearing

**Started**: 2026-07-05 11:06 **Completed**: 2026-07-05 11:07 **Duration**: 1 minute

**Notes**:

* `mission_complete` clears only the linked mission id from `campLinks`.
* Completion does not change pending links, camps, hero XP, banners, kills, essence, or War Tide.

**Files Changed**:

* `apps/web/src/lib/gameProjection.ts` - wired `mission_complete` through `withoutMissionCampLink`.
* `apps/web/tests/gameProjection.test.ts` - added completion clearing and no-progress assertions.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `npm test -- apps/web/tests/gameProjection.test.ts`
  * Result: PASS - 1 test file passed; 24 tests passed.
  * Evidence: `clears mission camp links on completion and terminal updates without progress mutation` verifies `mission-alpha` is removed while unrelated links and progress fields remain unchanged.
* Command/check: `rg -n "case \"mission_complete\"|reduceMissionComplete|makeMissionComplete|XP|warTide" apps/web/src/lib/gameProjection.ts apps/web/tests/gameProjection.test.ts`
  * Result: PASS - completion reducer and no-progress test assertions are present.
  * Evidence: Completion path only calls `withoutMissionCampLink`.
* UI product-surface check: N/A - pure reducer/test work; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T010 - Wire mission\_start same-hero fallback binding

**Started**: 2026-07-05 11:05 **Completed**: 2026-07-05 11:06 **Duration**: 1 minute

**Notes**:

* Added deterministic same-hero fallback after exact prompt lookup misses.
* Fallback only uses active pending links after expiry pruning and rejects wrong-hero missions.

**Files Changed**:

* `apps/web/src/lib/gameProjection.ts` - added same-hero fallback resolution path.
* `apps/web/tests/gameProjection.test.ts` - added fallback, wrong-hero, and expired-link assertions.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `npm test -- apps/web/tests/gameProjection.test.ts`
  * Result: PASS - 1 test file passed; 24 tests passed.
  * Evidence: `binds mission starts by exact prompt before same-hero fallback` verifies fallback links `mission-fallback` to `camp-fallback`, wrong-hero input returns the same state, and expired pending links do not bind.
* Command/check: `rg -n "fallbackIndex|mission-fallback|mission-wrong-hero|mission-expired" apps/web/src/lib/gameProjection.ts apps/web/tests/gameProjection.test.ts`
  * Result: PASS - fallback implementation and test cases are present.
  * Evidence: Resolver uses first sorted same-hero active link only after exact lookup.
* UI product-surface check: N/A - pure reducer/test work; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T009 - Wire mission\_start exact hero and prompt binding

**Started**: 2026-07-05 10:58 **Completed**: 2026-07-05 11:05 **Duration**: 7 minutes

**Notes**:

* Wired `mission_start` to match active pending links by exact `heroId + prompt` before fallback.
* Consumes only the matched pending link and writes a sorted `campLinks[missionId]` entry.

**Files Changed**:

* `apps/web/src/lib/gameProjection.ts` - added `mission_start` reducer case and exact-match binding path.
* `apps/web/tests/gameProjection.test.ts` - added exact binding assertion for `camp-exact`.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `npm test -- apps/web/tests/gameProjection.test.ts`
  * Result: PASS - 1 test file passed; 24 tests passed.
  * Evidence: `binds mission starts by exact prompt before same-hero fallback` verifies exact prompt binding creates `{"mission-exact":"camp-exact"}` and leaves only `camp-fallback` pending.
* Command/check: `rg -n "case \"mission_start\"|selectPendingLinkForMissionStart|camp-exact" apps/web/src/lib/gameProjection.ts apps/web/tests/gameProjection.test.ts`
  * Result: PASS - reducer switch, resolver, and exact assertion are present.
  * Evidence: Source exact-match resolver is wired through `reduceGameProjection`.
* UI product-surface check: N/A - pure reducer/test work; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T008 - Add typed Vitest event builders

**Started**: 2026-07-05 10:54 **Completed**: 2026-07-05 10:58 **Duration**: 4 minutes

**Notes**:

* Added typed builders for `mission_start`, `mission_complete`, `mission_update`, and `suggestion_update`.
* Added typed idle suggestion and codebase issue builders for no-progress snapshot tests.

**Files Changed**:

* `apps/web/tests/gameProjection.test.ts` - added typed protocol event builders and imported `recordProjectionPendingLink`.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded implementation evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `npm exec -- biome check apps/web/tests/gameProjection.test.ts`
  * Result: PASS - Biome checked the test file with no fixes applied.
  * Evidence: Test builder additions meet formatting/lint rules.
* Command/check: `rg -n "makeMission(Start|Complete|Update)|makeSuggestionUpdate|makeIdleSuggestion|makeCodebaseIssue" apps/web/tests/gameProjection.test.ts`
  * Result: PASS - all typed builders are present.
  * Evidence: Builder definitions exist for mission lifecycle and suggestion snapshot event families.
* UI product-surface check: N/A - test helper work only; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T007 - Add deterministic camp-link clearing helpers

**Started**: 2026-07-05 10:53 **Completed**: 2026-07-05 10:54 **Duration**: 1 minute

**Notes**:

* Added `withoutMissionCampLink` and terminal mission reducers that remove only the matching `campLinks` entry.
* Kept completion and failed/cancelled update paths isolated from XP, banners, kills, essence, War Tide, pending links, and camp state.

**Files Changed**:

* `apps/web/src/lib/gameProjection.ts` - added mission complete and terminal mission update clearing helpers.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded implementation evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `rg -n "withoutMissionCampLink|reduceMissionComplete|reduceMissionUpdate" apps/web/src/lib/gameProjection.ts`
  * Result: PASS - terminal clearing helpers and reducers are present.
  * Evidence: Clearing removes `campLinks[missionId]` only and returns the original state when no matching mission link exists.
* Command/check: `npm --workspace @factionos/web run typecheck`
  * Result: PASS - package typecheck completed successfully with terminal clearing helpers.
  * Evidence: `@factionos/web` TypeScript build accepted the reducer additions.
* UI product-surface check: N/A - pure reducer helper work; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T006 - Add deterministic mission-start link resolution helpers

**Started**: 2026-07-05 10:52 **Completed**: 2026-07-05 10:53 **Duration**: 1 minute

**Notes**:

* Added mission-link input normalization for mission id, hero id, and verbatim prompt.
* Added deterministic resolver that checks exact `heroId + prompt` before same-hero fallback over sorted active pending links.
* Added selected-link consumption helper for single-link removal.

**Files Changed**:

* `apps/web/src/lib/gameProjection.ts` - added mission-start camp-link resolution helpers.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded implementation evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `rg -n "normalizeMissionLinkInput|selectPendingLinkForMissionStart|withoutPendingLinkAtIndex|reduceMissionStart" apps/web/src/lib/gameProjection.ts`
  * Result: PASS - mission-start helper stack is present.
  * Evidence: Resolver first searches exact `heroId` and `prompt`, then first same-hero active link after sorting/pruning.
* Command/check: `npm --workspace @factionos/web run typecheck`
  * Result: PASS - package typecheck completed successfully with mission-start helpers.
  * Evidence: `@factionos/web` TypeScript build accepted the helper signatures.
* UI product-surface check: N/A - pure reducer helper work; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T005 - Export pending-link recorder with duplicate prevention

**Started**: 2026-07-05 10:51 **Completed**: 2026-07-05 10:52 **Duration**: 1 minute

**Notes**:

* Added exported `recordProjectionPendingLink` for future real camp callers.
* Preserves the original state reference for invalid input, expired input without existing expired links, and exact duplicate links.
* Replaces same camp/hero/prompt triggers rather than appending duplicates.

**Files Changed**:

* `apps/web/src/lib/gameProjection.ts` - exported pending-link recording helper and input type.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded implementation evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `rg -n "recordProjectionPendingLink|haveSamePendingLinkTrigger|withPendingLinks" apps/web/src/lib/gameProjection.ts`
  * Result: PASS - exported recorder and duplicate-trigger/reference-preserving helpers are present.
  * Evidence: `recordProjectionPendingLink` prunes expired links, rejects invalid input, returns unchanged state for exact duplicates, and filters same-trigger entries before inserting the new sorted link.
* Command/check: `npm --workspace @factionos/web run typecheck`
  * Result: PASS - package typecheck completed successfully with the exported helper.
  * Evidence: `@factionos/web` TypeScript build accepted the new export.
* UI product-surface check: N/A - pure reducer helper work; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T004 - Add bounded pending-link validation and expiry helpers

**Started**: 2026-07-05 10:43 **Completed**: 2026-07-05 10:51 **Duration**: 8 minutes

**Notes**:

* Added bounded pending-link id, prompt, expiry, equality, sorting, trigger-key, and expiry pruning helpers.
* Tightened persisted pending-link parsing to require safe ids, valid expiry timestamps, sorted bounded arrays, and duplicate-trigger rejection.

**Files Changed**:

* `apps/web/src/lib/gameProjection.ts` - added pending-link validation, sorting, equality, expiry, and parser helpers.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded implementation evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `npm --workspace @factionos/web run typecheck`
  * Result: PASS - `tsc -b --noEmit` completed successfully after reducer helper changes.
  * Evidence: TypeScript accepted the updated reducer and helper signatures.
* Command/check: `npm exec -- biome check apps/web/src/lib/gameProjection.ts`
  * Result: PASS - Biome checked 1 source file with no fixes applied after formatting adjustment.
  * Evidence: Source formatting/linting is clean for the changed reducer file.
* Command/check: `rg -n "MAX_PENDING_LINKS|normalizePendingLink|sortPendingLinks|pruneExpiredPendingLinks" apps/web/src/lib/gameProjection.ts`
  * Result: PASS - helper constants and validation/sorting/expiry functions are present.
  * Evidence: Matches show bounded helper definitions and parser integration in `gameProjection.ts`.
* UI product-surface check: N/A - pure reducer helper work; no UI files changed.
* UI craft check: N/A - no rendered surface changed.

***

### Task T003 - Inspect mission and suggestion event contracts

**Started**: 2026-07-05 10:40 **Completed**: 2026-07-05 10:43 **Duration**: 3 minutes

**Notes**:

* Confirmed `mission_start` carries a full `Mission` with `id`, `heroId`, and verbatim `prompt`.
* Confirmed `mission_update` supplies `missionId` plus `Partial<Mission>` and terminal state comes from `patch.state`.
* Confirmed `suggestion_update` is a typed `SuggestionSnapshot` with idle suggestions and codebase issues, not a camp accept signal.

**Files Changed**:

* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded protocol inspection evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `sed -n '1,210p' packages/protocol/src/events.ts`; `sed -n '360,420p' packages/protocol/src/events.ts`; `sed -n '600,705p' packages/protocol/src/events.ts`; `cat packages/protocol/src/missions.ts`; `sed -n '1,180p' packages/protocol/src/suggestions.ts`; `sed -n '540,980p' packages/protocol/src/suggestions.ts`
  * Result: PASS - inspected event union and the mission/suggestion payload contracts used by the reducer.
  * Evidence: Mission lifecycle events and `SuggestionUpdateEvent extends SuggestionSnapshot` are available without protocol changes.
* UI product-surface check: N/A - protocol inspection only; no UI files changed.
* UI craft check: N/A - protocol inspection only; no rendered surface changed.

***

### Task T002 - Inspect the current projection reducer and camp-link fields

**Started**: 2026-07-05 10:36 **Completed**: 2026-07-05 10:40 **Duration**: 4 minutes

**Notes**:

* Confirmed `GameProjectionPendingLink`, `pendingLinks`, and `campLinks` already exist in the serializable v1 shape.
* Confirmed reducer helpers use pure copy-on-write, sorted record updates, parser guards, and `finalizeProjectionUpdate`.

**Files Changed**:

* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded reducer inspection evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `sed -n '1,260p' apps/web/src/lib/gameProjection.ts`; `sed -n '261,560p' apps/web/src/lib/gameProjection.ts`; `sed -n '561,940p' apps/web/src/lib/gameProjection.ts`; `sed -n '941,1280p' apps/web/src/lib/gameProjection.ts`; `sed -n '1281,1775p' apps/web/src/lib/gameProjection.ts`
  * Result: PASS - inspected reducer shape, parser guards, existing copy-on-write helpers, alert-focus logic, persistence functions, and reducer switch.
  * Evidence: `pendingLinks` and `campLinks` are shape/parser fields only; no existing mission or suggestion reducer cases are wired.
* UI product-surface check: N/A - reducer inspection only; no UI files changed.
* UI craft check: N/A - reducer inspection only; no rendered surface changed.

***

### Task T001 - Verify Session 03 validation evidence and dependency scope

**Started**: 2026-07-05 10:35 **Completed**: 2026-07-05 10:36 **Duration**: 1 minute

**Notes**:

* Confirmed Session 03 validation passed and left no unresolved blocker for Session 04.
* Confirmed Session 03 scope stayed in the web reducer/test surface this session depends on.

**Files Changed**:

* `.spec_system/specs/phase22-session04-camp-link-boundaries/implementation-notes.md` - recorded validation evidence.
* `.spec_system/specs/phase22-session04-camp-link-boundaries/tasks.md` - marked task complete.

**Verification**:

* Command/check: `cat .spec_system/specs/phase22-session03-legion-enemy-reducer/validation.md`
  * Result: PASS - report shows `Result: PASS`, 20/20 tasks complete, focused reducer tests passed, full Vitest passed, typecheck passed, and Biome passed.
  * Evidence: Session 03 validation ledger reports focused reducer suite 20 passed, full suite 3179 passed / 1 skipped, package and workspace typecheck passed, and no blockers.
* UI product-surface check: N/A - setup evidence inspection only; no UI files changed.
* UI craft check: N/A - setup evidence inspection only; no rendered surface changed.

***

### 2026-07-05 - Session Start

**Environment verified**:

* [x] Prerequisites confirmed by `.spec_system/scripts/check-prereqs.sh --json --env --package apps/web`
* [x] Tools available: jq 1.7, git 2.43.0, npm 11.16.0
* [x] Directory structure ready for `apps/web`
* [x] Database not applicable for reducer-only session

***


---

# 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-session04-camp-link-boundaries/implementation-notes.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.
