> 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/specs/phase24-session02-camp-mission-linkage/implementation-notes.md).

# Implementation Notes

**Session ID**: `phase24-session02-camp-mission-linkage` **Packages**: apps/web (primary), apps/server (secondary) **Started**: 2026-07-08 05:50 **Last Updated**: 2026-07-08 06:10

***

## Session Progress

| Metric              | Value      |
| ------------------- | ---------- |
| Tasks Completed     | 19 / 19    |
| Estimated Remaining | 0 hours    |
| Blockers            | 1 resolved |

***

## Task Log

### 2026-07-08 - Session Start

**Environment verified**:

* [x] Prerequisites confirmed (check-prereqs pass earlier this window)
* [x] Baseline web typecheck clean
* [x] Fixture patterns located in `questBoardStore.test.ts`, `suggestionManager.test.ts`, `suggestionRoutes.test.ts`

### Tasks T001-T002 - Grounding

**Completed**: 2026-07-08 05:52

**Notes**:

* `recordProjectionPendingLink` and `reduceMissionStart` verified complete in `gameProjection.ts` (dedup, trigger replacement, expiry pruning, verbatim + fallback binding, replay guard). `MAX_PENDING_LINKS` = 24.
* **Critical finding**: `normalizePendingLinkInput` REQUIRES a valid future `expiresAt`; omitting it makes recording a silent no-op. Added the exported `PENDING_LINK_TTL_MS` (2 minutes, section 5A) and the store helper passes accept-time + TTL.

### Tasks T003a/T003b - Server accept fallthrough

**Completed**: 2026-07-08 05:58

**Notes**:

* `acceptSuggestion` now falls through to current codebase issues when no idle suggestion matches. Returns an `IdleSuggestion`-shaped dispatch value (`prompt` = `suggestedPrompt`, `heroId` = "", `category` = "codebase\_issue", priority mapped from severity) with `changed: false` so acceptance never mutates or persists issue state. Dismissed issues 404 naturally because dismissal removes them from the store.
* Route unchanged: issue accepts flow through the same `SuggestionAcceptResponse` variants (send\_prompt when a hero resolves, prompt\_sent + compat intent otherwise).

**Files Changed**:

* `apps/server/src/managers/suggestionManager.ts` - fallthrough + `codebaseIssueAcceptDispatch` + `issueSeverityToPriority` helpers.

### Tasks T003-T011 - Web helpers and store wiring

**Completed**: 2026-07-08 06:05

**Notes**:

* `legionCampIdForIssueId` (sorted-camp-id deterministic lookup) in `legionCamps.ts`; `campMissionsFromLinks` inverse derivation + `PENDING_LINK_TTL_MS` in `gameProjection.ts`; `selectGameCampMissions` selector in the store.
* `recordQuestCampPendingLink(set, input)` mirrors the `applyEvent` guard shape: fold always, persist only when neither replaying nor mock; no-op recordings return `{}` from `set` (no reference churn).
* Accept wiring records only for `send_prompt` responses (hero known) on camp-backed cards; assign wiring uses the response hero for `send_prompt` and the explicitly assigned hero for `prompt_sent`. Camp id captured from the card before optimistic removal. Failure paths record nothing.
* `codebaseIssueToCard` now exposes `accept`/`assign` capabilities keyed by the issue id.

**Files Changed**:

* `apps/web/src/lib/legionCamps.ts`
* `apps/web/src/lib/gameProjection.ts`
* `apps/web/src/lib/questBoardModel.ts`
* `apps/web/src/store/useGameStore.ts`

**BQC Fixes**:

* Mutation safety: recording sits strictly inside the success branch after in-flight duplicate guards; dedup lives in the projection entry point.
* Contract alignment: both `SuggestionAcceptResponse` variants handled exhaustively via the `action` discriminant.

### Tasks T011b-T016 - Tests and gates

**Completed**: 2026-07-08 06:10

**Notes**:

* Server: 4 new tests (manager issue accept + no-mutation + dismissed 404 + idle precedence on id collision; route send\_prompt issue accept + dismissed 404). 22/22 pass.
* Web: `legionCampIdForIssueId` (3 tests), `campMissionsFromLinks` (3 tests), card capabilities assertion, and a camp-linkage describe in `questBoardStore.test.ts` (4 tests): accept records + `mission_start` binds + joint-assault selector; prompt\_sent/no-camp/failure record nothing; assign prompt\_sent records the assigned hero; replay/mock fold without persisting while normal mode persists (fake-timer flush).
* Full web project 2223/2223; full node project 1080 passed / 1 skipped after fixing a pre-existing README version-badge drift (see blocker).
* Biome, web+server typecheck, root format:check, ASCII/LF all clean.

**Files Changed**:

* `apps/server/tests/suggestionManager.test.ts`
* `apps/server/tests/suggestionRoutes.test.ts`
* `apps/web/tests/legionCamps.test.ts`
* `apps/web/tests/gameProjection.test.ts`
* `apps/web/tests/questBoard.test.ts`
* `apps/web/tests/questBoardStore.test.ts`
* `README.md` (version badge drift fix)

***

## Blockers & Solutions

### Blocker 1: No camp-backed accept path existed

**Description**: The server accept handler resolved ids against idle suggestions only, and codebase-issue cards exposed only `dismissIssue` - so the session as drafted would wire dead code (camps are built from codebase issues; idle suggestions map to no camp). **Impact**: All recording tasks (T007-T010) and the phase's linkage claim. **Resolution**: Extended the existing accept route's id resolution to fall through to current codebase issues (same route, same response contracts, no protocol change; acceptance dispatches without resolving the issue) and gave issue cards accept/assign capabilities. Documented in spec.md as a planning conflict resolution. **Time Lost**: \~20 minutes of grounding.

### Note: pre-existing README badge drift

`tests/packageMetadata.test.ts` failed on the pre-session tree (README badge 0.1.192 vs root package.json 0.1.194 - drift from the prior session's root version bump). Fixed by syncing the badge to 0.1.194.

***

## Design Decisions

### Decision 1: Accept of an issue does not dismiss it

**Context**: Idle-suggestion accept consumes the suggestion; issues are different - they exist until fixed and rescanned or explicitly banished. **Chosen**: Issue accept is a pure dispatch (`changed: false`); the camp survives until a verified kill (Session 04) or a clean rescan. This keeps Fun Law 1 intact: accepting a quest is not progress.

### Decision 2: No recording when the hero is unknown

**Context**: `prompt_sent` accept responses name no hero. **Chosen**: Plain accepts with `prompt_sent` record nothing; assigns use the explicitly chosen hero. Honesty over coverage - a guessed hero id could bind the wrong mission.

### Decision 3: `PENDING_LINK_TTL_MS` exported from gameProjection.ts

**Context**: Expiry is mandatory input; section 5A specifies accept+2min. **Chosen**: One named constant beside the pending-link logic so Session 07 regressions and the Phase 12 tuning sweep share it.


---

# 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/specs/phase24-session02-camp-mission-linkage/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.
