> 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/phase17-session02-server-notice-manager-persistence-and-context/spec.md).

# Session Specification

**Session ID**: `phase17-session02-server-notice-manager-persistence-and-context` **Phase**: 17 - Notice Board Coordination Parity **Status**: Completed **Created**: 2026-06-05 **Package**: apps/server **Package Stack**: TypeScript

***

## 1. Session Overview

This session replaces the current short-lived `NoticeBoard` server stub with a persistent, room-scoped Notice Board manager that later route, WebSocket, CLI, hook, and War Room sessions can depend on. Session 01 already expanded the shared protocol contracts; this pass consumes those contracts inside `apps/server` without implementing the canonical Express route family or relay behavior yet.

The current manager stores a 200-item in-memory list and only supports compatibility post, list, and resolve behavior. Phase 17 requires the recovered manager behavior: local JSON persistence, startup load, debounced saves, shutdown flush, a 500-message cap, room and target filtering, resolution, context generation, context cache invalidation, pruning, ingest dedupe, batch ingest, and automatic post dedupe.

All persistence remains local under `FACTIONOS_HOME` or the current server local-state root. Notice Board content is coordination data and may still be sensitive developer data, so this session must not introduce hosted transfer, raw prompt capture, terminal output capture, file-content storage, or stronger identity, auditability, erasure, or executor claims.

***

## 2. Objectives

1. Replace the in-memory Notice Board stub with a persistent parity manager.
2. Add room-scoped list, context, resolution, ingest, batch ingest, pruning, and auto-post behavior.
3. Preserve current `/notice`, `/notices`, and WebSocket compatibility callers through existing `post`, `list`, and `resolve` surfaces.
4. Add focused server tests proving persistence, filtering, context, resolution, pruning, and dedupe behavior.

***

## 3. Prerequisites

### Required Sessions

* [x] `phase16-session05-launch-review-and-documentation-handoff` - closes the prior public website phase and allows Phase 17 runtime work to begin.
* [x] `phase17-session01-protocol-notice-contract-parity` - provides expanded Notice Board protocol contracts, aliases, helpers, REST shapes, and event payload types.

### Required Tools/Knowledge

* Node 26.2.0 or newer with npm workspaces.
* TypeScript server manager and Vitest test patterns.
* `packages/protocol/src/notices.ts` Notice Board contracts and helpers.
* Quarantined historical manager evidence in `EXAMPLES/package-0.4.1/server/dist/noticeBoardManager.js`.

### 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.
* Use temporary test directories for `FACTIONOS_HOME` persistence tests.

***

## 4. Scope

### In Scope (MVP)

* Server runtime can persist Notice Board messages locally - store `notice-board.json` under `FACTIONOS_HOME` or the server local-state root with startup load, debounced save, atomic write, and shutdown flush behavior.
* Server manager can store canonical and compatibility notices - normalize posted notices through the protocol helpers while preserving `body`, `targets`, `postedAt`, and `severity` compatibility fields.
* Later route and WebSocket sessions can query room-scoped notices - implement `getMessages` with room, type, `since`, target session, expiration, include-resolved, offset, limit, newest-first ordering, and total count.
* Later hook sessions can request bounded context - implement `getContextForSession` with the recovered heading, same-author exclusion, unresolved filtering, target filtering, expiration filtering, last-fetch tracking, and five-second cache behavior.
* Later relay sessions can ingest remote notices safely - implement id-based `ingest`, `ingestBatch`, and `resolveForRoom` operations with duplicate protection and room boundaries.
* Automatic lifecycle post sessions can avoid spam - implement `autoPost` dedupe for same room, author session, and type inside a 30-second window.
* Existing compatibility callers keep compiling - retain `post`, `list`, and `resolve` methods used by `routes/heroes.ts`, `ws/handlers.ts`, and mock event generation.

### Out of Scope (Deferred)

* Canonical Express route implementation - Reason: Session 03 owns `/notice-board`, `/notice-board/context`, and resolve route handlers.
* WebSocket handler parity and broadcast forwarding - Reason: Session 03 owns runtime event emission and hydration changes.
* War Room relay forwarding and catch-up - Reason: Sessions 07 and 08 own Worker and local bridge convergence.
* Web cockpit rendering - Reason: Session 04 owns UI and store behavior.
* CLI and hook access - Reason: Sessions 05 and 06 own command and hook implementation.

***

## 5. Technical Approach

### Architecture

Keep `apps/server/src/managers/noticeBoard.ts` as the server-owned Notice Board state manager. Expand it from an in-memory array into a small persistent manager with constructor options for local state path, active room defaults, clock injection, debounce interval, context cache interval, and prune interval so tests can avoid global state. The manager should expose canonical methods for later sessions while keeping the current compatibility methods available.

Persistence should use a JSON store shaped around `{ messages: Notice[] }`. Loads should tolerate missing or malformed files by starting empty. Saves should create the parent directory, write to a temporary file, rename into place, and be debounced around the recovered 500 ms pattern. The server shutdown path should call a manager `destroy` or `flush` method so pending saves and prune timers are cleaned up.

Filtering and context behavior should operate on cloned Notice values to avoid leaking mutable internal state. Room-scoped methods should require a room code or use a local standalone room fallback. Compatibility list and post methods should continue returning the old shapes expected by current routes and WebSocket hydration until Session 03 rewires the canonical route family.

### Design Patterns

* Manager-owned state: keep persistence, filtering, pruning, and cache invalidation inside the manager rather than spreading it into routes.
* Protocol-backed normalization: import Notice Board helper contracts from `@factionos/protocol` instead of duplicating enum or alias logic.
* Atomic local writes: write temp files and rename to avoid partial local JSON state after process interruption.
* Timer lifecycle cleanup: clear debounce and prune timers on server stop.
* Fail-closed boundaries: ignore duplicate ingests, reject missing room resolution, and keep malformed persisted state from crashing startup.

### Technology Stack

* TypeScript in `apps/server`.
* Node `fs` and `path` APIs for local persistence.
* Vitest with fake timers and temporary directories.
* Package typecheck through `npm --workspace apps/server run typecheck`.
* Focused root Vitest runs for `apps/server/tests/noticeBoard.test.ts`.

***

## 6. Deliverables

### Files to Create

| File                                    | Purpose                                                           | Est. Lines |
| --------------------------------------- | ----------------------------------------------------------------- | ---------- |
| `apps/server/tests/noticeBoard.test.ts` | Persistent manager, filter, context, pruning, and dedupe coverage | \~320      |

### Files to Modify

| File                                      | Changes                                                                                                | Est. Lines |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------ | ---------- |
| `apps/server/src/managers/noticeBoard.ts` | Persistent Notice Board manager, canonical methods, compatibility methods, cache, pruning, and cleanup | \~360      |
| `apps/server/src/server.ts`               | Wire manager lifecycle cleanup into server shutdown                                                    | \~10       |

***

## 7. Success Criteria

### Functional Requirements

* [ ] Notices survive manager restart when using the same `FACTIONOS_HOME`.
* [ ] Missing or malformed `notice-board.json` does not crash server startup.
* [ ] Stored notices are capped at 500 and returned newest-first with accurate totals.
* [ ] Filters support room, type, `since`, target session, expiration, include-resolved, offset, and limit behavior.
* [ ] Targeted notices are hidden from unrelated sessions.
* [ ] Context output uses `## Notice Board (New Messages)` and contains only new unresolved relevant room messages.
* [ ] Resolution only applies inside the requested room and invalidates context cache.
* [ ] `ingest`, `ingestBatch`, and `autoPost` prevent duplicate notice spam.
* [ ] Current compatibility callers can still use `post`, `list`, and `resolve`.

### Testing Requirements

* [ ] Focused manager tests written and passing.
* [ ] Server package typecheck passing.
* [ ] Existing route and WebSocket tests do not regress.

### Non-Functional Requirements

* [ ] Notice persistence remains local-only and does not introduce hosted transfer, hosted identity, production auditability, real executors, or trusted unified erasure claims.
* [ ] Manager output does not add raw prompts, transcripts, terminal output, file contents, secrets, command bodies, absolute paths, logs, exports, replay buffers, scans, media drafts, diagnostics, backups, or quarantined historical content beyond explicitly posted coordination text and safe related-file references.
* [ ] Timer and file-handle lifecycle cleanup is deterministic on shutdown.

### Quality Gates

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

***

## 8. Implementation Notes

### Key Considerations

* `routes/heroes.ts` and `ws/handlers.ts` currently call `notices.post()` and `notices.list()`. Keep those methods compatible during this manager session.
* `server.ts` currently stops the mock generator and closes WebSocket/HTTP servers. Add Notice Board cleanup to that shutdown path.
* Existing local-state helpers use `FACTIONOS_HOME` with `~/.factionos` as the default. This session should follow that convention for `notice-board.json`.
* Full route query validation belongs to Session 03, but manager methods should already accept bounded typed query objects.

### Potential Challenges

* Timer tests can leak state: Use fake timers or constructor options so debounce and prune intervals are deterministic and cleaned up.
* Compatibility drift can break existing routes: Keep old method names and old alias fields while adding canonical methods.
* Context cache can hide invalidation bugs: Test post, ingest, batch ingest, resolve, prune, and load invalidation paths.
* Local JSON corruption can crash startup: Treat unreadable or malformed files as empty state and avoid logging raw local paths or payload bodies in errors.

### Relevant Considerations

* \[P03-packages/protocol] **Protocol leads cross-package work**: Consume the Session 01 Notice Board contracts instead of redefining notice vocabulary in `apps/server`.
* \[P03-apps/server] **Local server boundary must stay conservative**: Keep local state scoped, validated, rate-limit-compatible, and bounded.
* \[P06-apps/warroom+apps/web] **War Room federation is optional and redacted**: This manager prepares relay inputs but does not activate hosted identity, public collaboration safety, production auditability, certification, or full erasure.
* \[P07] **Redaction is boundary-specific**: Notice persistence, context output, future routes, WebSocket events, Worker relay, CLI, hooks, replay, export, logs, and diagnostics each need explicit minimization.
* \[P03] **Stable docs are the current contract**: Use current source and docs as truth; `EXAMPLES/` remains quarantined traceability evidence only.

### Behavioral Quality Focus

Checklist active: Yes Top behavioral risks for this session:

* Persistent local state can be partially written or left unsaved at shutdown.
* Context and list filters can leak room-scoped or targeted notices to the wrong session.
* Auto-post or relay ingest can duplicate messages and create coordination noise.

***

## 9. Testing Strategy

### Unit Tests

* Test startup load, missing file behavior, malformed file fallback, debounced save, atomic write, shutdown flush, and restart persistence.
* Test post normalization, compatibility aliases, list ordering, 500-message cap, room filtering, target filtering, expiration filtering, and include-resolved filtering.
* Test `resolveForRoom`, `ingest`, `ingestBatch`, context cache invalidation, context last-fetch behavior, and `autoPost` dedupe.

### Integration Tests

* Run existing route and WebSocket tests to confirm `post`, `list`, `resolve`, and hydration compatibility still work after the manager change.
* Run server package typecheck to confirm current route and WebSocket callers still compile against the expanded manager.

### Manual Testing

* Start the local server with a temporary `FACTIONOS_HOME`, post a compatibility notice through `/notice`, stop the server, restart it, and verify the notice persists through `/notices`.

### Edge Cases

* Empty room code, wrong room resolution, expired notices, stale non-critical notices, critical notices, duplicate ingests, duplicate auto-posts, targeted notices for unrelated sessions, malformed persisted JSON, and cache reuse inside the five-second window.

***

## 10. Dependencies

### External Libraries

* `nanoid`: existing server dependency for notice ids.
* Node `fs`, `path`, and `os`: local persistence primitives.
* `vitest`: existing workspace test runner.

### Internal Dependencies

* `@factionos/protocol`: Notice Board contracts, aliases, and helper types.
* `apps/server/src/server.ts`: lifecycle owner for manager shutdown cleanup.
* `apps/server/src/routes/heroes.ts`: compatibility `/notice` and `/notices` caller that must keep working.
* `apps/server/src/ws/handlers.ts`: compatibility hydration and `post_notice` caller that must keep working.

### Other Sessions

* **Depends on**: `phase17-session01-protocol-notice-contract-parity`
* **Depended by**: `phase17-session03-server-routes-and-websocket-parity`, `phase17-session04-web-store-and-cockpit-notice-board`, `phase17-session05-cli-notice-commands`, `phase17-session06-hook-context-and-provider-skill-parity`, `phase17-session07-war-room-worker-relay-and-catchup`, `phase17-session08-local-war-room-bridge-and-room-notice-convergence`, `phase17-session09-automatic-mission-lifecycle-posts`

***

## 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/phase17-session02-server-notice-manager-persistence-and-context/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.
