> 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/phase18-session03-suggestion-routes-and-websocket-parity/spec.md).

# Session Specification

**Session ID**: `phase18-session03-suggestion-routes-and-websocket-parity` **Phase**: 18 - Quest Board Suggestion Parity **Status**: Not Started **Created**: 2026-06-10 **Package**: apps/server **Package Stack**: TypeScript

***

## 1. Session Overview

This session wires the server-owned SuggestionManager from Session 02 into the runtime API surface. Session 01 already defined the shared suggestion contracts and Session 02 added persisted manager state, but the current server still returns unsupported-route responses for the historical `/suggestions/*` family and does not hydrate connected WebSocket clients with typed Quest Board state.

The work ships the first actionable Quest Board runtime layer: accepting idle suggestions, dismissing idle suggestions, dismissing codebase issues, reading summary counts, broadcasting canonical `suggestion_update` snapshots, and hydrating new WebSocket connections. It also preserves the current web store by continuing to emit string-based `idle_suggestion` compatibility frames until the web migration sessions land.

This session deliberately stops before idle lifecycle generation, scanner engines, scan trigger/status routes, on-demand analysis, project scan engines, and web typed cards. Routes and hydration consume existing manager state only; later sessions create more suggestion sources.

***

## 2. Objectives

1. Add the shipped suggestion route family for accept, suggestion dismiss, issue dismiss, and summary under both local and `/api` route prefixes.
2. Route every accepted or dismissed manager mutation through one broadcast path that emits `suggestion_update` plus `idle_suggestion` compatibility frames.
3. Hydrate newly connected WebSocket clients with the current `suggestion_update` snapshot in deterministic startup order.
4. Remove only shipped suggestion routes from unsupported-route behavior while leaving scan/analyze/project-scan engine routes planned.
5. Prove route, WebSocket, compatibility, validation, and unsupported-route behavior with focused server integration tests.

***

## 3. Prerequisites

### Required Sessions

* [x] `phase18-session01-protocol-suggestion-contract-parity` - provides `SuggestionSnapshot`, REST response aliases, `suggestion_update`, and `idle_suggestion` compatibility event types.
* [x] `phase18-session02-server-suggestion-manager-and-persistence` - provides `SuggestionManager`, persisted state, accept/dismiss APIs, summary counts, and `emitUpdate`.
* [x] `phase17-session03-server-routes-and-websocket-parity` - provides the route plus WebSocket parity pattern to mirror for manager-owned state.

### Required Tools/Knowledge

* Node 26.2.0 or newer with npm workspaces.
* Express 5 router and local middleware ordering in `apps/server/src/server.ts`.
* Current request validation envelope patterns in `apps/server`.
* Existing `Broadcaster`, `registerWsHandlers`, and hydration tests.
* Phase 18 PRD Appendix A evidence for route semantics only; do not import, copy, or transform historical code.

### Environment Requirements

* Work from the repository root.
* Use package-relative monorepo paths from the repo root.
* Keep all generated and edited files ASCII-only with Unix LF line endings.

***

## 4. Scope

### In Scope (MVP)

* Local API callers can accept an idle suggestion through `POST /suggestions/:suggestionId/accept` with validated IDs and compact error responses.
* Local API callers can dismiss an idle suggestion through `POST /suggestions/:suggestionId/dismiss`, backed by the manager dismissed suggestion ID set.
* Local API callers can dismiss a codebase issue through `POST /issues/:issueId/dismiss`, backed by the manager dismissed issue ID set.
* Local API callers can read `GET /suggestions/summary` for manager counts and severity buckets.
* Accepting a suggestion for an existing internal hero returns `action: "send_prompt"`, the target hero ID, the historical internal prompt endpoint, and the accepted prompt.
* Accepting a suggestion without an existing internal hero returns `action: "prompt_sent"` and emits the historical user-prompt compatibility message in the safest available current protocol shape.
* Every manager mutation caused by these routes broadcasts the canonical `suggestion_update` snapshot and a string `idle_suggestion` compatibility frame for current web-store consumers.
* Newly connected WebSocket clients receive one current `suggestion_update` snapshot during hydration.
* The shipped route paths work under both root and `/api` prefixes because they are mounted in the existing local route chain.
* Unsupported-route classification no longer claims shipped accept, dismiss, issue-dismiss, or summary routes are planned, while engine trigger/status routes remain planned until Sessions 07-08.
* Route and WebSocket integration tests cover success, validation, missing records, compatibility frames, hydrate, and dismissed items disappearing from snapshots.

### Out of Scope (Deferred)

* `/suggestions/analyze`, `/suggestions/scan`, `/suggestions/project-scan`, scan trigger routes, and scan status routes - *Reason: Sessions 07-08 own scan orchestration and engines.*
* Idle suggestion generation on hero-idle transitions - *Reason: Session 04 owns lifecycle generation, cooldowns, transcript context, and fallbacks.*
* Session summary generation and noise filtering - *Reason: Session 05 owns summaries and follow-up task generation.*
* Codebase scanner implementations - *Reason: Session 06 owns scanner logic.*
* Web Quest Board typed cards, action buttons, hero assignment, and keyboard shortcuts - *Reason: Sessions 09-10 own web consumption and actions.*
* Historical suggestion telemetry and entitlement gates - *Reason: Phase 18 explicitly excludes analytics capture and entitlement gating.*
* New real executor behavior for sending prompts to terminal agents - *Reason: real execution remains deferred; this session returns the prompt endpoint or emits compatibility intent only.*

***

## 5. Technical Approach

### Architecture

Create `apps/server/src/routes/suggestions.ts` as the Express router for the shipped suggestion route family. The router should receive the `SuggestionManager`, `Broadcaster`, and `HeroRoster`, validate path and body IDs with protocol-safe identifiers or route-local schemas, and map manager validation errors to the existing `invalid_request` response envelope. The router should keep small in-flight sets for accept and dismiss operations so duplicate triggers do not double-process the same suggestion or issue.

Create a small server-local broadcast helper, likely `apps/server/src/lib/suggestionBroadcast.ts`, so routes and WebSocket wiring do not duplicate snapshot construction or compatibility behavior. The helper should call the manager's canonical update path for `suggestion_update` and derive `idle_suggestion` frames from active idle suggestions for the current web store. It must not emit raw state files, absolute paths, provider payloads, or historical telemetry.

Update `apps/server/src/ws/handlers.ts` to accept the SuggestionManager in its context and send the current `suggestion_update` snapshot during connect-time hydration. The new hydration frame should be deterministic and tested. The existing client-message vocabulary stays unchanged; this session does not add client-to-server suggestion actions over WebSocket.

Update `apps/server/src/server.ts` to mount the suggestion router inside `mountLocalApiRoutes`, so the existing CORS, auth, fixed-window rate limit, and `express.json({ limit: "2mb" })` body cap cover both root and `/api` paths. Update `unsupportedRoutes.ts` to keep only unshipped suggestion engine routes classified as planned.

### Design Patterns

* Manager-owned state: route handlers call SuggestionManager and never mutate persisted state directly.
* Single broadcast path: one helper owns canonical and compatibility emission so route behavior and later engines cannot drift.
* Existing local API boundaries: suggestion routes inherit auth, CORS, rate limit, and body-size middleware instead of adding a parallel boundary.
* Compact validation envelopes: malformed inputs return stable `invalid_request` details without echoing raw request bodies.
* Compatibility without overclaiming executors: accept returns prompt routing information or prompt intent, but does not imply a real terminal executor is shipped.

### Technology Stack

* TypeScript in `apps/server`.
* Express 5 routers.
* Existing `@factionos/protocol` suggestion, REST, and event types.
* Existing `Broadcaster`, `HeroRoster`, and `SuggestionManager`.
* Vitest server integration tests with temporary state directories.

***

## 6. Deliverables

### Files to Create

| File                                            | Purpose                                                                                              | Est. Lines |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ---------- |
| `apps/server/src/routes/suggestions.ts`         | Suggestion accept, dismiss, issue-dismiss, summary route handlers and validation helpers             | \~260      |
| `apps/server/src/lib/suggestionBroadcast.ts`    | Canonical `suggestion_update` plus `idle_suggestion` compatibility emission helper                   | \~90       |
| `apps/server/tests/suggestionRoutes.test.ts`    | Route integration tests for accept, dismiss, summary, validation, `/api`, and unsupported boundaries | \~280      |
| `apps/server/tests/suggestionWebsocket.test.ts` | WebSocket hydrate, mutation broadcast, and compatibility-frame tests                                 | \~220      |

### Files to Modify

| File                                          | Changes                                                                      | Est. Lines |
| --------------------------------------------- | ---------------------------------------------------------------------------- | ---------- |
| `apps/server/src/server.ts`                   | Mount suggestion routes and pass suggestions into WebSocket handlers         | \~20       |
| `apps/server/src/ws/handlers.ts`              | Add SuggestionManager context and send `suggestion_update` hydrate frame     | \~25       |
| `apps/server/src/lib/unsupportedRoutes.ts`    | Reclassify only still-unshipped suggestion engine routes as planned          | \~20       |
| `apps/server/tests/unsupportedRoutes.test.ts` | Update planned suggestion route coverage after shipped paths mount           | \~35       |
| `apps/server/README_server.md`                | Document shipped suggestion routes and remove Session 02 unsupported wording | \~35       |

***

## 7. Success Criteria

### Functional Requirements

* [ ] `POST /suggestions/:suggestionId/accept` accepts active idle suggestions and removes them from active manager state.
* [ ] Accepting a suggestion for an existing hero returns `action: "send_prompt"` with `/internal-heroes/{heroId}/prompt`.
* [ ] Accepting a suggestion without a resolvable hero returns `action: "prompt_sent"` and emits a user-prompt compatibility intent.
* [ ] `POST /suggestions/:suggestionId/dismiss` and `POST /issues/:issueId/dismiss` persist dismissed IDs through the manager.
* [ ] Dismissed suggestions and issues do not reappear in emitted snapshots.
* [ ] `GET /suggestions/summary` returns counts and severity buckets from `SuggestionManager.getSummary`.
* [ ] New WebSocket clients receive the current `suggestion_update` snapshot during hydration.
* [ ] Every route mutation emits canonical `suggestion_update` and compatibility `idle_suggestion` frames.
* [ ] `/suggestions/analyze`, `/suggestions/scan`, and `/suggestions/project-scan` remain planned unsupported routes.

### Testing Requirements

* [ ] Route integration tests are written and passing.
* [ ] WebSocket hydrate and broadcast tests are written and passing.
* [ ] Unsupported-route regression tests are updated and passing.
* [ ] `npm --workspace apps/server run typecheck` passes.

### Non-Functional Requirements

* [ ] Suggestion routes inherit local auth, CORS, rate limit, and body-size caps from the existing server middleware chain.
* [ ] Validation errors do not echo raw request bodies, prompt text, provider payloads, absolute paths, or secrets.
* [ ] Broadcast frames use protocol-compatible shapes and avoid historical telemetry or entitlement claims.
* [ ] No hosted storage, analytics, provider transfer, or entitlement gate is introduced.

### Quality Gates

* [ ] All files ASCII-encoded.
* [ ] Unix LF line endings.
* [ ] Code follows project conventions.

***

## 8. Implementation Notes

### Key Considerations

* Keep this session as an integration layer over Session 02 manager state. Engines that create new suggestions come later.
* The current `ServerEvent` union includes `suggestion_update` and `idle_suggestion`, but not a first-class `user_prompt` event. If TypeScript exposes that gap during implementation, keep the fix minimal and compatibility-focused; do not broaden protocol scope beyond what the accept route needs.
* `unsupportedRoutes.ts` currently groups `/suggestions` under the planned task-queue family. The implementation should stop treating the whole prefix as unsupported once shipped handlers are mounted, while still classifying unshipped engine routes as planned.
* The current web store consumes only `idle_suggestion`; compatibility frames must continue until Sessions 09-10 migrate web state.

### Potential Challenges

* Historical accept semantics imply prompt delivery, but real executors remain deliberately unimplemented. Mitigation: return routing information for internal heroes and emit prompt intent for non-internal targets without claiming execution.
* Hydration order tests are strict. Mitigation: add the snapshot frame in one deterministic location and update tests to assert the new order.
* Dismissed IDs are persisted by the manager, but emitted snapshots still need to be derived after mutation. Mitigation: route handlers broadcast only after successful manager calls.

### Relevant Considerations

* \[P03-apps/server] **Local server boundary must stay conservative**: routes inherit validation, rate limits, body-size caps, auth, and CORS from the existing local boundary.
* \[P17] **Notice Board parity is a shared contract**: reuse the same manager-owned route and WebSocket discipline for suggestion state.
* \[P17-apps/server] **Manager-owned persistence and cleanup**: keep filtering, pruning, and persistence behind the SuggestionManager.
* \[P07] **Redaction is boundary-specific**: validation and unsupported-route responses must not echo raw prompts, secrets, local absolute paths, or broad request payloads.
* \[P03] **Stable docs are the current contract**: use the PRD and stable docs as the current truth; EXAMPLES are evidence only.

### Behavioral Quality Focus

Checklist active: Yes

Top behavioral risks for this session:

* Duplicate accept or dismiss triggers causing repeated prompt intents or inconsistent dismissed sets.
* WebSocket hydration and mutation broadcasts drifting from manager state.
* Validation errors leaking request bodies or raw prompt text.
* Compatibility `idle_suggestion` frames drifting from the canonical snapshot.

***

## 9. Testing Strategy

### Unit Tests

* Validate route-level parsing helpers for safe IDs, body shape, and compact invalid-request envelopes if helpers are exported for testing.

### Integration Tests

* Start `createFactionOsServer` with isolated suggestion and notice state directories.
* Seed suggestions and issues through the returned `handles.suggestions` manager.
* Exercise root and `/api` accept, dismiss, issue-dismiss, and summary routes.
* Connect WebSocket clients before and after route mutations to verify hydrate, canonical broadcast, compatibility broadcast, and dismissed item absence.
* Verify unsupported engine routes remain deterministic planned 501 responses.

### Manual Testing

* Run a local server, seed or trigger manager state, connect the web app, and confirm the current Quest Board still receives string suggestions through `idle_suggestion` while canonical snapshots are visible in WebSocket frames.

### Edge Cases

* Accepting an already accepted or missing suggestion returns a compact not-found or conflict response without a second broadcast.
* Dismissing an unknown suggestion or issue remains idempotent and records the dismissed ID according to manager behavior.
* Invalid path IDs, empty body objects, malformed hero IDs, and oversized body payloads return existing validation or middleware responses.
* Connected clients receive an empty but valid snapshot when no manager state exists.

***

## 10. Dependencies

### External Libraries

* No new runtime dependency is planned. Use existing server validation helpers and protocol normalizers unless implementation proves a route-local schema dependency is already available and necessary.

### Other Sessions

* **Depends on**: `phase18-session01-protocol-suggestion-contract-parity`, `phase18-session02-server-suggestion-manager-and-persistence`
* **Depended by**: `phase18-session04-idle-lifecycle-engine-parity`, `phase18-session07-scan-orchestration-and-quest-ignore-patterns`, `phase18-session08-on-demand-analysis-and-project-scan-engines`, `phase18-session09-web-quest-board-typed-cards`, `phase18-session10-quest-actions-and-keyboard-shortcuts`

***

## Next Steps

Run the implement workflow step to begin AI-led 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/phase18-session03-suggestion-routes-and-websocket-parity/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.
