> ## Documentation Index
> Fetch the complete documentation index at: https://prism.ntecdev.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> Notable changes to Prism — features, fixes, architectural shifts. Format: Keep a Changelog. Dated entries, newest first.

# Changelog

All notable changes to Prism are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Dates are UTC.

Prism started without semver-tagged releases — the project is a
single-tenant per-user install with ongoing migration. **As of `v0.2.0`
(2026-04-26)** the line is tagged. Entries are still grouped by date and
reference the commits + specs that landed; release tags surface above the
relevant date header. Operators upgrade by `git pull` + rebuilding the
backend image (and the optional standalone `session-manager` container, as
of v0.2.0).

<Note>
  This file is updated per commit arc, not per individual commit. Entries
  are curated for *what changed for operators, agents, or developers* —
  not a replay of every `git log` line. The git log is the source of
  truth for per-commit detail; this changelog is the human-readable arc.
</Note>

***

## 2026-05-13

### Architectural

* **SPEC-113 v0.3 — Bootstrap path resolution + immutable `$PRISM_ROOT` model (Texi architecture, Donna PO, Frank ratified).** Ring 2 methodology lives immutably at `$PRISM_ROOT/templates/` (`prism-base.md` + ptype overlay) and is **read at runtime** by agents — not copied into customer projects. `./PRISM.md` is no longer a customer project file; it exists only in the Prism repo itself, where `$PRISM_ROOT == $PROJECT_ROOT`. Customer dirs (`$PROJECT_ROOT/<name>`) get only the thin edge: `CLAUDE.md` / `AGENTS.md` Ring 1 replicas, `.prism/`, and `.agent/` in contributor mode. Upgrades touch `$PRISM_ROOT` only; never customer dirs. See [SPEC-113](/specs/spec-113-v0-3-bootstrap-path-resolution-and-scaffold-completeness) for the full contract, failure-mode names, and acceptance criteria. Motivated by postmortem `13b4a879` (DPA-Crawl bootstrap cascade).
* **SPEC-112 v0.3 — Client vs Contributor scaffold boundary (Texi architecture, Donna engineering / Plan #18 integration, Frank ratified).** Two install/scaffold modes. **Client** (default): customer install — `$PROJECT_ROOT` gets only `CLAUDE.md` / `AGENTS.md` Ring 1 replicas + `.prism/` dotfile metadata + editor MCP config. No `.agent/` tree, no roster files, no `start-all` launchers, no `PRISM.md`. **Contributor** (explicit opt-in via `scaffold_profile='contributor'`): adds the full `.agent/` tree, role/persona config, launch.local, roster compatibility files, `start-all` launchers, and contributor ptype/profile metadata. `prism_adopt` and `prism_create` default to client unless `scaffold_profile='contributor'` is passed (or an existing contributor tree is explicitly accepted, or `$PROJECT_ROOT == $PRISM_ROOT` for the Prism repo self-reference). See [SPEC-112](/specs/spec-112-v0-3-client-and-contributor-scaffold-boundary).
* **SPEC-114 v0.1 — Sage and Clara customer-care incident personas (Texi architecture, Donna PO + engineering, Frank greenlight 16:00Z).** Plan #19 v0.1 customer-care persona pair. **Sage** is the intake classifier — receives reporter signals and classifies as `info` / `incident` / `enhancement` / `recommendation` / `unclear`, routing incident-like cases to Clara. **Clara** (named for Clara Barton) is the incident manager — executes a five-tier init checklist (mandatory init reads → conditional reads by symptom → privacy gates → severity/scope/kind classification → output discipline). Clara is the gate that decides whether `prism_phone_home` fires under SPEC-115. v0.1 launches terminal-launched on server1 for observability; headless daemon mode deferred to a later phase. See [SPEC-114](/specs/spec-114-v0-1-sage-and-clara-customer-care-incident-personas).
* **SPEC-115 v0.1 — `prism_phone_home` verb + ET GitHub-issue transport (Texi architecture, Donna PO + backend, Frank greenlight 16:00Z).** Adds the controlled Prism-product phone-home channel before Prism Cloud exists. New `prism_phone_home` verb (rejects `scope='customer-internal'`; requires passed `redaction_report` + `operator_review.approved=true`); **ET** gitapp\_helper service validates the packet, applies idempotency dedupe, and transports through `gh` CLI to the **private invite-only `FrankTewksbury/Prism`** repository. Operator must be invited as a collaborator before phone-home can succeed (`phone_home_repo_access_denied` failure mode otherwise). Customer-internal incidents never leave the local environment; `mixed` scope phones home only the redacted Prism-product subset after operator review. v0.2 will swap `gh` CLI transport for a Prism-Bot GitHub App; v0.1 redaction + operator-review-before-send invariants must be preserved. See [SPEC-115](/specs/spec-115-v0-1-prism-phone-home-and-et-github-issue-transport).

### Added

* **PR #344 — BIOS template wording + `resolvePid` env fallback + offline-fallback gate (Donna, ships SPEC-113 v0.3).** `templates/CLAUDE.md` + `templates/AGENTS.md` bumped to **bios\_version 2.6.0**. Boot step 1 changes from `Read ./PRISM.md` to "Read Ring 2 methodology from `$PRISM_ROOT/templates/prism-base.md` + ptype overlay". Boot step 4 (offline fallback to `_ACTIVE_CONTEXT.md` / `_TODO.md`) is **gated on step 1 success** — prevents DPA-Crawl-class bootstrap cascade where Codex fell back to DFW global constitution and read offline context before `prism_start`. `mcp-node` `resolvePid` fallback order locked: explicit `pid` > explicit `name` > `PRISM_PROJECT_NAME` env > `PRISM_PROJECT_DIR` env > structured failure. Conflict detection: throws `env_project_mismatch` / `name_arg_mismatch_env_project` when explicit args disagree with launcher-injected env — defense in depth against the `identity_as_project_name` class. `templates/prism-application.md` A6 paragraph rewritten from *"PRISM.md composition writes to project root"* to *"Ring 2 methodology resolution reads at runtime from `$PRISM_ROOT/templates/`"*.

### Fixed

* **PR #343 — `coder.sh` launcher: remove `PRISM.md` from project-root sentinels (Lafonda).** Mac/POSIX launcher no longer treats `PRISM.md` as a project-root sentinel. Sentinel set is now `.prism/` directory + `AGENTS.md` / `CLAUDE.md` Ring 1 replicas + backend project metadata. Customer projects without `PRISM.md` (the new normal) now resolve correctly.
* **PR #342 — `coder.ps1` launcher: remove `PRISM.md` from project-root sentinels (Cherry).** Windows-side counterpart to PR #343. Cherry's Windows smoke (signal `913833f9`) confirms `coder.ps1` + BIOS 2.6.0 templates load correctly on a project root without `PRISM.md`.

***

## 2026-05-11

### Architectural

* **Marconi — in-memory signal-mesh switch shipped (SPEC-101 v0.4, Donna engineering driver, Texi ratified, Frank operator GO).** The signal-delivery hot path is now sender shim → Marconi → receiver shim, with Redis Stream + PG audit as an asynchronous fan-out off the hot path. The legacy synchronous-PG / Redis-pubsub forwarder is removed from `send_signal`. Live-verified on `server1` PROD 2026-05-11 01:03Z via signal `c8f4d0cb` landing in both `signal_queue` and `signal_queue_shadow` with `stage=model_acted_ack`. v0.4 target: **p99 `< 5ms`** on the same-instance hot path.
  * **Stage 0 — design gate** (PR #279). Loss budget, recovery invariants (R1/R2/R3/R5/R6/R8), and rollback procedures locked. Frank loss-budget signoff captured: *"if Redis is down, messages are delivered but never recorded at this stage of the build."*
  * **Stage 1 — process-local routing + registration cache** (PR #280). `backend/app/marconi/{_cache,routing,registration}.py` + flag-gated wiring. Flags `MARCONI_ROUTING_TABLE_READS` / `MARCONI_REGISTRATION_TABLE_READS`.
  * **Stage 2 — cache lifecycle: direct write-through hooks + startup warmup** (PR #282). Wires direct invalidation at every site that mutates session state (`controller_service.register/deregister/handoff_master/claim_master`, `KeyspaceReaper`, WS connect/disconnect). **No pubsub subscriber anywhere.**
  * **Stage 3 — in-memory audit queue + Redis Stream writer** (PR #293). Audit fan-out pipeline lands *before* the hot-path cutover so accepted signals continue producing parallel records during cutover. Flags `MARCONI_AUDIT_QUEUE_WRITE` / `MARCONI_REDIS_STREAM_WRITER`.
  * **Stage 3.5 — envelope enrichment for byte-perfect shadow comparison** (PR #295). Marconi-archived envelopes match legacy-PG envelopes byte-for-byte; shadow harness (PR #292) compares.
  * **Stage 4 — PG archiver** (PR #294 shadow, PR #299 PRIMARY flip). Consumer group on Marconi Cache Redis Stream with `XREADGROUP COUNT 1 BLOCK 0`; idempotent UPSERT into `signal_queue` / `signal_trace_events` / `signal_obligations`. **No batching** — the archiver optimizes for near-immediate audit persistence, not throughput batching. Flags `MARCONI_PG_ARCHIVER_SHADOW` / `MARCONI_PG_ARCHIVER_PRIMARY`.
  * **Stage 5 — hot-path inversion** (PR #283, flipped ON in PR #284). `send_signal` looks up the recipient's WS handle in Marconi's routing table and pushes directly. Synchronous PG INSERT removed from the hot path. Flags `MARCONI_HOT_PATH_SEND` / `MARCONI_OBLIGATIONS_MEMORY_PRIMARY`.
  * **Sign-coded result envelope** (PR #285, PR #286). `[stage=…]` banner the shim renders alongside the doorbell to make the delivery stage observable without a separate trace call. Possible stages: `model_acted_ack`, `surface_observed`, `pushed`, `queued_offline`.
  * **PR #299 — flip `MARCONI_PG_ARCHIVER_PRIMARY` ON.** Frank GO, no soak. PG archiver is the canonical writer to `signal_queue` / `signal_trace_events` / `signal_obligations`.

### Added

* **SPEC-100 — operator-invoked signal-mesh loopback diagnostic** (PR #276, Donna). `prism_channel_probe` and `prism_channel_probe_ack` verbs send a synthetic envelope from a sender shim through Marconi to a receiver shim and back, surfacing whether the sender's WS handle is healthy, whether the receiver's daemon is attached and forwarding, whether the surface strategy rendered the doorbell, and whether the receiver model called `prism_channel_probe_ack`. The loopback probe is what surfaced the legacy-path drift that motivated SPEC-101.
* **Channel-probe client-cap discovery + end-to-end probe verbs** (PR #287). Sender-side capability discovery before issuing a probe; full end-to-end verb wiring through MCP shim.
* **SPEC-104 — Marconi cache-first trace/read path.** Recent signal trace lookup now has a cache-first contract: `prism_signal_trace` defaults to Marconi Cache for real-time state, while `source="pg_audit"` is the explicit historical/reporting/reconciliation path. Responses expose source metadata such as `source`, `cache_hit`, `pg_audit_used`, `cache_stream_id`, and `trace_state`.

### Changed

* **Signal trace compatibility semantics.** A recent Marconi Cache miss no longer silently implies historical absence. Callers that need the old PG audit behavior must request `source="pg_audit"` or opt into history with `include_history=true`.

### Fixed

* **Rejected Marconi sends are auditable.** Sends to an unregistered recipient now enqueue a rejected audit envelope when Marconi Cache write-through is enabled, so misspelled-recipient attempts can be traced instead of returning IDs without a cache/audit path.
* **`marconi-archiver` — translate `publish_path` `pushed_to_marconi` → `pushed_to_ws`** (PR #300). Marconi emitted the internal label `pushed_to_marconi` in the API response while older shims and the dashboard expected `pushed_to_ws`. The archiver now normalizes the value at the boundary so external surfaces see a stable vocabulary.
* **SPEC-101 — remove `AUDIT_PENDING_MARCONI` + `ACK_DEFERRED_MARCONI` dead branches** (PR #301). Stage 5 cutover obsoleted these intermediate ack stages; cleanup landed post-deploy.
* **`channel-probe` — bump default ACK timeout 5s → 10s** (PR #290). Accommodates per-persona daemon turn-boundary holds; was tripping false-negative probe failures on busy receivers.
* **`launcher` — restore `--channels server:prism`** (PR #288). The flag name had drifted; channels-push delivery required the explicit flag.

### Docs

* **SPEC-101 v0.4 marconi audit flow lock** (PR #291). Operator architecture lock: Marconi switch (in-mem) → Marconi Cache (Redis Stream `marconi:signals:{tenant}`, MAXLEN \~7d) → PG audit. Pre-stamps Stage 5 status path so the code PR could mark it shipped without another architecture revision.
* **SPEC-101 v0.3.1 — operator architecture lock + Texi precision** (PR #281). Restored §3.6 in-memory audit queue as a named Marconi internal (replacing floating ring references); reordered stage ladder so audit fan-out lands BEFORE hot-path cutover.
* **SPEC-101 Stage 0 sub-doc + v0.2 scope reduction** (PR #279, updated v0.3+). Loss budget, recovery invariants, rollback. Local durable spool removed (Frank operator scope reduction).
* **SPEC-101 v0.1 — Marconi: in-memory signal mesh, zero backend on hot path** (PR #277). Initial draft, Texi concept ratification.
* **Signal Mesh + Marconi docs rewrite (Desiree).** [`/signal-mesh`](/signal-mesh) rewritten around the three-tier topology with 5 new mermaid diagrams (two flows, hot-path sequence, audit fan-out, surface delivery, recovery). New [`/marconi`](/marconi) explainer page covering in-memory tables, cache invalidation hooks, stage ladder, full §9 counter catalog, and failure-mode summary. Cross-doc consistency sweep across `index`, `vision`, `agent-surfaces`, and `metrics`. Amendment banners on the superseded signaling spec series (SPEC-034 / 037 / 044 / 045 / 052 / 058 / 070 / 071 / 091).

### Operability

* **OTel → Prometheus bridge RESTORED** (PR #298, Donna postmortem `3c1f986c`). `/metrics` endpoint now exposes the full `marconi_*` namespace plus the existing `signals_sent_total`, `system_signals_emitted_total`, `controller_*` counters. Trust Prometheus again. Memory `project_otel_prometheus_bridge_dead` updated to reflect restoration; PID label drift defect on `PeerLeft` path noted for follow-up.

***

## 2026-05-08

### Added

* **Memorules — SPEC-094 Phase 2 a-d (Donna, four-PR arc).** Memorules
  ship as the first concrete artifact of the persona-behavioral-memory
  layer. Persona-scoped behavioral records — what a persona has learned
  to do or not do across sessions — are now written, recalled, and
  audited through TriGraph-backed primitives instead of host-editor
  local files. *Working names pending legal clearance.*
  * **Phase 2a — Memorule schema + write service** (PR #235, 28 unit
    tests). New Cypher schema for `:Memorule` nodes carrying scope
    (persona / specialization / project / global), applicability rules,
    payload, source-of-record reference, and lifecycle status. Write
    service validates shape, deduplicates against existing recordings,
    and emits journal entries on first-write.
  * **Phase 2b — Memorule recall + applies\_to ranking** (PR #236,
    12 unit tests). `prism_remember` recall path scores Memorules by
    applicability — persona match > specialization match > project
    match > global — with deterministic tie-break.
  * **Phase 2c — `prism_remember kind='memorule'` contract expansion**
    (PR #237). The verb's payload contract grows to accept memorule-
    specific fields (rule body, applicability scope, supersedes
    pointer, invariants list).
  * **Phase 2d — Watson `:MemoruleAudit` emission** (PR #238, 10 unit
    tests). Every recall, write, supersede, and invalidate event emits
    a `:MemoruleAudit` entity into TriGraph for governance visibility.
    Closes the audit-trail gap raised in Texi's review.

* **Dewey (Recallgate) — SPEC-099 Phase 3 advisory wiring (Donna, four-PR
  arc).** Dewey is the memory-enforcement-gate archetype that ensures
  recall actually fires under stuckness. Phase 3 wires the advisory
  layer into existing verbs so prior memorules / postmortems /
  decisions surface at the moment of action. *Working names pending
  legal clearance.*
  * **Phase 3 — classifier + window + audit primitives** (PR #239).
    Backend service that classifies whether a given interaction
    *should* trigger recall, computes the lookback window, and emits
    audit primitives.
  * **Phase 3 — single-call advisory orchestrator** (PR #240). Wraps
    classifier + recall fetch + scoring into one MCP-callable entry
    point.
  * **Phase 3 — advisory live in `prism_todo create`** (PR #241).
    When an agent creates a TODO, the orchestrator fires and surfaces
    relevant prior memorules / postmortems / decisions in the
    response.
  * **Phase 3 — advisory wired into signal/ack** (PR #242). Signal
    acknowledgement path now consults the advisory to surface
    contextual memory at the point of action.

* **SPEC-096 v0.2 — wrap-time pre-flight (Donna, slices 1-4).** Catches
  ratified-but-uncommitted artifacts at `prism_wrap` and
  `prism_checkpoint` time so they don't get lost in the wrap delta —
  the recurrence pattern that surfaced in the 2026-05-08 dirty-tree
  incident (RCA `c3fb59b3`).
  * **v0.2 spec doc** (PR #225) — wrap-time pre-flight check for
    uncommitted ratified artifacts.
  * **Slice 1 — backend `wrap_preflight` service skeleton** (PR #228,
    53 unit tests). Pure backend; not yet wired into lifecycle.
  * **Slice 2 — mcp-node `wrap_preflight_shim` helper** (PR #231,
    17 tests). Surface-side helper for the MCP shim.
  * **Slice 3 — wire into delta endpoint + lifecycle** (PR #232).
    The check now actually fires inside `prism_wrap` and
    `prism_checkpoint`.
  * **Slice 4 — telemetry surface in `runtime_diagnostics`** (PR #234).
    Operators can watch `wrap_preflight` pass/fail rates over time.

* **Miranda (Wallbreak Protocol) — `method.wall-break.persistence`
  v0.2 published** (PR #222, Donna engineering author with Texi
  architecture `approved_with_required_revisions`, Candi governance
  acceptance). Canonical method fragment defining Prism's stuckness-
  and-persistence protocol — five trigger conditions, solution-table
  forcing, self-critique loop, wrap-time handoff gate, recall-on-task-
  start, and postmortem-on-structural-walls. Phase enforcement by
  social contract (Phase 1); SPEC-094 advisory integration (Phase 2)
  and `prism_stuck` verb (Phase 3) deferred until adoption gaps are
  observed. *Working names pending legal clearance.*

* **Plan #003 — memory-activation implementation plan (Miranda-style)**
  (PR #227, Donna). Sequences SPEC-094 Memorules + SPEC-099 Dewey
  (Recallgate) + SPEC-096 wrap-pre-flight into a coherent multi-phase
  execution arc.

* **Dashboard topology — host name on agent + service nodes** (Porsche,
  PRs #217 + #218). Topology surface displays the host machine for
  each agent and service node. PR #218 follows up by passing the
  host's `HOSTNAME` through to the dashboard container as
  `PRISM_MACHINE_ID` so the surface uses the same identifier as the
  rest of the stack.

* **Seed-input PDF archive at `docs/inputs/`** (PR #245). Project
  seed-input PDFs preserved under version control for ownership and
  timestamp record. Pairs with the legal/copyright initiative that
  shipped the visible Ntecdev / NtecLabs footer + per-page
  last-updated badge + `/legal` page (PR #223).

### Fixed

* **Signal — canonical PID label on `system_signals_emitted_total`**
  (PR #243). The metric was carrying drifting PID labels across
  cardinality boundaries; now stable. Part of the 2026-05-08
  signal-quality fix arc (RCA `c3fb59b3`).

* **Signal — zero subscribers treated as queued, agent → session
  fall-back** (PR #244). Zero-subscriber emits are no longer silently
  dropped; recipient resolution now falls back from agent identity
  to session id when the agent is offline. Same RCA arc as PR #243.

### Documentation

* **BIOS v2.5.0 — local auto-memory deprecated (SPEC-094 Phase 1)**
  (PR #216, Donna). `templates/CLAUDE.md` + `templates/AGENTS.md`
  carry the rule that Anthropic editor host auto-memory
  (`~/.claude/projects/*/memory/`, `MEMORY.md`, equivalent Codex /
  Cursor stores) is deprecated; all persistent memory operations
  route through Prism verbs (`prism_remember`, `prism_journal`,
  `semantic_recall`, etc.). Operator-locked rule — only the operator
  can re-enable, and only after SPEC-094 acceptance criteria pass.

* **SPEC-094 v0.4 + SPEC-099 v0.1 drafts** (PR #229, Donna). Memorules
  delta + Dewey (Recallgate) first drafts.

* **SPEC-094 v0.5 + SPEC-099 v0.2 — Texi revisions applied** (PR #230).

* **SPEC-099 — Texi second-pass text fix on ACK-only test case**
  (PR #233).

***

## 2026-05-07

### Added

* **SPEC-093 — Tenant → Org → Department → Project hierarchy (Donna,
  PR-A).** Four-level identity chain replaces the prior flat
  tenant/project model. Every Project now carries
  `tenant_id` + `org_id` + `department_id` + `pid`, with FK
  constraints enforcing the chain at insertion. Personal-install
  (LAN) backwards compatibility is preserved by **flat-LAN seeding**:
  when no Department is specified, a default Department is created
  under the default Org under the default Tenant — existing
  single-user installs continue to work without operator action.
  PR #211 — schema + service + bootstrap + migration; PR #210 —
  A5/A6/A7/A8 LAN dept-hierarchy smokes + upgrade rehearsal scaffold.

* **SPEC-090 — `prism_adopt` verb + `/projects/adopt` 409-idempotent
  surface** (PR #212, Donna). New MCP verb that registers an existing
  git repo into Prism without recreating PIDs. The 409-idempotent
  surface means re-running adopt against an already-registered
  project returns the existing PID rather than erroring or
  duplicating. Solves the bootstrap-second-machine and
  bootstrap-from-existing-clone cases.

### Fixed

* **CLI Windows statusline platform-split** (PR #215, Lafonda). Claude
  Code statusline now uses `bin/coder.ps1` via PowerShell on Windows;
  the existing `.sh` path stays for Unix. Closes the cross-platform
  parity gap that surfaced during the Windows install rehearsals.

***

## 2026-05-06

### Added

* **SPEC-087 cloud bring-up (Render Blueprint) — Phase 1 lands**
  (Donna). Phase 1 `OAUTH_REDIRECT_URI` + containerized Neo4j
  (Frank §7 amendment) lands in PR #188, completing the Render
  bring-up cascade that started 2026-05-04 with the v0.1 spec doc
  and Phase 0 Blueprint. Neo4j is now containerized inside the
  `prism-neo4j` pserv (no external Neo4j dependency); the OAuth
  redirect URI is threaded through env vars; the Blueprint applies
  cleanly against a fresh Render workspace. The cascade across PRs
  \#182–#188 — port-contract / dashboard-probe-URLs (#185), pserv
  dockerCommand + port bindings (#187), DATABASE\_URL dialect
  normalization (#186), neo4j plan upgrade `standard → pro_plus`
  per the full-capability sizing rule (#191) — is documented in the
  research postmortem (#189).

* **SPEC-089 — Cloud Customer Onboarding + Auth (Donna).** First
  customer-onboarding spec for the cloud surface.
  * **v0.1 spec doc** (PR #209). Covers the provider-agnostic
    identity model, the WorkOS auth provider integration, /whoami
    * /logout surfaces, and the parametric auth router design.
  * **Phase 1 — provider-agnostic user identity + auth-provider
    setting** (PR #194). Identity layer abstracted from any specific
    provider; `auth_provider` settings field selects which provider
    implementation to load.
  * **Phase 2 (slices 2.1 + 2.3) — `WorkOSProvider` + parametric
    auth router + `/whoami` + `/logout`** (PR #195). First concrete
    provider implementation; routes shape themselves around the
    configured provider.
  * **Phase 2 — WorkOS auth smoke + rollback-rehearsal scaffolds**
    (PR #196). End-to-end smoke for the WorkOS path; rollback
    rehearsal scaffolds for ops practice.

### Fixed

* **Auth — default Org seeded per Tenant in OAuth
  `provision_from_identity`** (PR #192). New Tenants no longer landed
  without an Org row, which later broke project creation that assumed
  `org_id` was always resolvable. The default-Org-per-Tenant invariant
  matches what SPEC-093 then formalized in the four-level hierarchy
  the next day.

* **Backend — `uvicorn --proxy-headers`** (PR #203). `request.base_url`
  now returns `https` behind the Render edge proxy, fixing OAuth
  redirect-URI generation that was emitting `http://` and breaking
  the WorkOS round-trip.

* **Signal — stale session resurrection loop stopped** (commit
  `fa73c35`). Fixed a path where dropped sessions could be resurrected
  by out-of-order signal frames, creating ghost rows that confused
  `prism_status`.

* **Signal — Redis pattern subscribers no longer count for push
  gating** (commit `37971ec`). Pattern-subscriber count was inflating
  the push-gating decision and causing zero-real-subscriber emits to
  be treated as live. Fixed to only count direct (non-pattern)
  subscribers.

* **Smoke — recall-tier3 payload shapes** (PR #199). Tier-3 recall
  payloads were drifting from the live shape after Phase 2 ran;
  smoke updated to track current contract.

* **Smoke — rollback per-key PUT, count-preservation guard,
  `SERVICE_ID` self-resolve** (PR #207). Render env-var sync now
  patches per-key, avoiding the wipe-on-PUT class postmortem
  `6b351dc6`; a count-preservation guard catches accidental list
  truncation; `SERVICE_ID` self-resolves from local context if not
  supplied.

* **Smoke — rollback poll `deploy.status=='live'`** (PR #208).
  Rollback smoke now polls for actual `live` status instead of
  returning on first `deploy_id` reply, surfacing honest SLO. Also
  adds `?limit=100` to the deploy-list query so older deploys aren't
  truncated.

* **Smoke — auth `https` assertion on `redirect_uri`** (PR #204).
  Smoke now asserts `https` on the redirect URI, catching the
  reverse-proxy regression that PR #203 fixed downstream.

### Documentation

* **Render bring-up cascade postmortem** (PR #189, Donna research
  note). Documents the PR #184–#188 cascade — what the Blueprint got
  right, what fell over, what surfaced via Render's native error
  reporting, and how the cascade was recovered. Useful reference for
  any future Cloud bring-up work.

***

## 2026-05-05

### Added

* **SPEC-082 v0.3 — Operator-Driven Master Handoff Verbs (Donna PO,
  Texi reviewed v0.1 → v0.2 → v0.3).** Two new MCP verbs that turn
  master election from "implicit first-registered wins, hope nobody
  preempts" into an explicit operator surface:

  * **`prism_master_handoff(pid, to_identity, to_session_id?)`** —
    cooperative transfer. Caller MUST be current master.
  * **`prism_master_claim(pid, to_identity, operator_id,
    operator_password, to_session_id?)`** — operator-authorized
    preempt. Caller proves authority via SPEC-038 §3.2 credentials;
    target identity is independent of caller. (The v0.1 contract had
    the claim verb promoting the caller — Texi's first finding flipped
    this so the motivation actually works.)

  Target resolution is a deterministic 6-step algorithm:
  `to_session_id` wins → otherwise candidate set is active rows for
  the identity within the heartbeat-freshness threshold → exactly-one
  fresh selects → multiple fresh rows returns `target_ambiguous` with
  a candidate `session_ids` list → no fresh rows returns
  `target_stale` → zero rows returns `target_not_registered`. Both
  verbs return `stale_master` on a CAS-lost concurrent race.

  State updates are Redis-first per SPEC-032: the Session Manager
  facade flips the master slot atomically, then the Postgres
  `controller_status` rows are reconciled. `prism_status` reflects
  the new master immediately on return. No `MasterChanged` enum was
  introduced — the existing `MasterPreempted` signal carries the
  delta (see below).

* **`prism_session_deregister(session_id)` — surgical cleanup verb.**
  Idempotent; releases both the Postgres controller row and the Redis
  hash + sessions-set entry for one session\_id. The lever for
  scrubbing stale rows that piled up before the leak fix landed.

* **`MasterPreempted` payload extension.** All emission paths now
  carry `previous_master_identity`, `previous_master_session_id`,
  `new_master_identity`, `new_master_session_id`, and a `reason`
  field (`"preempt"` or `"handoff"`). Operator-driven preempts also
  carry `by_operator=<operator_id>` so the previous master can
  attribute the flip in audit logs. Delivery contract unchanged —
  direct to the previous master per the existing system-signal
  semantics. \[PR #150 `b83d502`]

### Fixed

* **Controller-row Redis leak (postmortem `f7ff9a8e`, companion fix
  to SPEC-082).** The silent-reconnect path in
  `controller_service.register` (§2.2) and the cross-machine force-
  preempt path (§2.4) both released the prior session's Postgres row
  but left the Redis hash + sessions-set entry alive until the 90s
  TTL lapsed. Result: `prism_status` could report two active sessions
  for one identity for up to 90 seconds — the leak that surfaced
  2026-05-04 evening and triggered the SPEC-082 motivation in the
  first place. Both paths now release Redis state immediately;
  `prism_session_deregister` exists for surgical cleanup of any rows
  that pre-date the fix. \[PR #150 `b83d502`]

* **SPEC-082 split-flush ordering (postmortem `695d29b3`, hotfix to
  PR #150).** `handoff_master` and `claim_master` set the current
  master's `is_master=False` and the target's `is_master=True` in the
  same SQLAlchemy unit of work and issued a single `session.flush()`.
  Migration 016's partial UNIQUE constraint
  (`is_master=true AND released_at IS NULL` per project) forbids two
  active masters at any instant — and SQLAlchemy is free to reorder
  the target promotion ahead of the demotion inside one flush, which
  raised IntegrityError after the Redis `transfer_master` had already
  succeeded. Net effect: a Redis-vs-Postgres dual-plane split where
  `prism_status` reported one identity as master while the
  caller-must-be-master gate inside `handoff` recognised the other
  (the partial-UNIQUE diagnosis loop, surfaced by Texi diagnosis
  signal `7a948a45`). Fix is the same demote-flush-promote pattern
  `register()` already uses for preemption: demote the current master
  * flush, then promote the target + flush. `claim_master` also now
    demotes ALL active `is_master` rows in the project as
    defense-in-depth against zombie rows from prior outages. New test
    `test_spec082_hotfix_split_flush.py` pins the ordering by spying
    on `session.flush()`. \[PR #152 `0d0935a`]

* **SPEC-082 test infra — fakeredis Lua + asyncio\_mode.** The new
  SPEC-082 unit tests needed `fakeredis[lua]` (the Lua extra is
  required for the CAS scripts that drive `transfer_master`) and
  `asyncio_mode=auto` in `pytest.ini` so the async fixtures pick up
  the right loop without per-test markers. Pinned both in
  `requirements-dev.txt` and the pytest config so the suite runs
  green on a clean ephemeral container. \[PR #154 `92424be`]

***

## 2026-05-04

### Added

* **Plan #10 closure — Governance Foundation at Machine Speed.** Single
  ratification arc executed in approximately **3.5 hours** end-to-end.
  Stat block:

  | Metric                      | Value                           |
  | --------------------------- | ------------------------------- |
  | Artifacts ratified          | 6 (3 ADRs, 3 SPECs)             |
  | PRs merged                  | 9 (#114-#122)                   |
  | Production deploys          | 4 (avg 31.8s wall-time)         |
  | Smoke assertions passed     | 67                              |
  | RCAs (production incidents) | 0                               |
  | Concurrent agents           | 8                               |
  | Signals exchanged           | 80–120 (zero lost or misrouted) |

  Three feature flags shipped default-off:
  `PRISM_GOVERNANCE_RESOLVER_ENABLED`,
  `PRISM_CONSENSUS_PARALLELISM_ENABLED`,
  `PRISM_MEMORY_DOMAINS_ENABLED`. Default-off invariant maintained
  throughout — every implementation PR proved behavioral inertness of
  its gated path before merge. Server1 final state: image
  `fc4428729bf`, alembic migration 034 stable, `prism_status` GREEN.
  See [Plan #10 case study](/case-studies/plan-10-governance-foundation)
  for the full timeline, agent roster, and lane retrospectives.

* **Plan #10 Wave 3 ratified — SPEC-080 v0.2 (Donna PO, Texi review,
  Candi authored).** Tri-Graph Governance Recall + Capability Index.
  Graph-backed rule/capability lookup with source, citation,
  freshness, and supersession reporting. Advisory-only — never
  outranks Ring authority or live `prism_start` state. Texi's hardest
  review of the session: preventing graph recall from accidentally
  becoming a second authority system. Resolution anchored in concrete
  invariants — SPEC-020 Entity vs EntityState boundaries, type/edge
  registration before extractor writes, live `prism_start` state
  winning over graph projection, SPEC-065 telemetry isolation. v0.1
  → v0.2 fold of 12 binding amendments completed in approximately
  **4 minutes**. SPEC-080 Phase 2 verb (`prism_governance_lookup`)
  shipped same-day in PRs #126–#131 with a default-off
  `PRISM_GOVERNANCE_LOOKUP_ENABLED` flag and a one-shot
  `governance_backfill` CLI seeding 196 :Artifact / 48 :Decision /
  12 :SourceDocument+:InstructionSurface entities for PID-PGR01.

* **Plan #10 Wave 2 ratified — SPEC-078 v0.2 + SPEC-079 v0.2
  (Donna PO, Texi review, Candi authored).**
  * **SPEC-078 v0.2** — Consensus-First Parallelism + Method
    Fragments. Formalizes a 3-tier consensus workflow (low /
    medium / high-architectural). Introduces method-fragment schema
    with a proof fragment shipped same-session.
  * **SPEC-079 v0.2** — Memory Domain Contracts + CI Loop. Defines
    12 memory domains with read/write contracts that prevent
    cross-domain pollution. CI loop for continuous governance
    improvement.

* **Governance Foundation — Plan #10 Wave 1 ratified (Donna PO).** Three
  artifacts sealed in ADR #47 v0.2 → SPEC-077 v0.2 → ADR #48 v0.2 order:
  * **ADR #47 v0.2** — Governance Layer Precedence + Override Semantics
    (supersedes ADR #45 v0.1). Establishes the 5-layer narrower-wins
    model (global / tenant / org / project / user-session-runtime) with
    file-level `mandatory: true` frontmatter as the only override gate.
    Mandatory canonical org guardrails cannot be bypassed by a narrower
    layer.
  * **SPEC-077 v0.2** — Canonical Boot + Governance Resolution. Boot
    model (static BIOS → `prism_start` → governance block); 5-state
    per-layer classification (`found_nonempty` / `found_empty_stub` /
    `missing` / `skipped` / `invalid`); `PRISM_GOVERNANCE_RESOLVER_ENABLED`
    feature flag default-OFF for safe rollout.
  * **ADR #48 v0.2** — Persona / Identity / Specialization Field Model
    (supersedes ADR #46 v0.1). 5-field model (persona / identity /
    specialization / assignment / style); specialization is
    routing/memory/skill authority, **not** governance authority.

* **Launcher governance-var forwarding (Lafonda, PR #114
  `6f70b43`).** `bin/coder.sh` sources `$HOME/.prism/env` early
  (set -a / set +a wrap so vars auto-export to children) and
  forwards 8 governance vars (`PRISM_GOVERNANCE_LAYOUT`,
  `PRISM_GOVERNANCE_ROOT`, `PRISM_TENANT_SLUG`, `PRISM_ORG_SLUG`,
  `PRISM_PROJECT_SLUG`, `PRISM_CONFIG_PROFILE`, `PROJECT_ROOT`,
  `PRISM_ROOT`) to both Codex agent invocations + the Codex
  app-server background launch. Defensive `[ -f ]` test makes the
  source line a no-op when the env file doesn't exist yet, so the
  launcher patch is independently shippable without the install-verb
  work.

* **Install-lane governance helpers (Lafonda, PR #115
  `de33529`).** New module tree at `mcp-node/src/ops/governance/`:
  `slugValidator.ts` (`[a-z0-9-]+` validation, refuses silent
  normalization per Texi review), `envBlockWriter.ts` (atomic write
  to `$HOME/.prism/env` preserving operator extras + the
  operator-set `PRISM_GOVERNANCE_RESOLVER_ENABLED` toggle on re-run),
  `templateCopier.ts` (PRISM\_GLOBAL.md template-copy per SPEC-077
  §6, schema-version-compatible only). Install verbs do **not**
  auto-call `runGovernanceSetup()` yet — held for the
  post-ratification activation flip.

* **Runtime governance resolver + `prism_start` extension (Donna,
  PR #116 `1dcf5f3`).** New module
  `mcp-node/src/ops/governance/resolver.ts`: `resolveGovernance()`
  reads `$HOME/.prism/env`, classifies all 4 file-bearing layers per
  the SPEC-077 §4 5-state taxonomy, surfaces conflicts
  (`invalid_frontmatter` / `dual_org_sources` / `layout_collision` /
  `schema_version_mismatch` / `layer_mismatch` / `slug_mismatch`),
  reports mandatory guardrails, and composes the `gov:N/M` summary
  per §8. ORG dual-source resolution per ADR #47 §4: narrower-wins
  by default; canonical wins only when broader is `mandatory: true`.
  Default-OFF — when `PRISM_GOVERNANCE_RESOLVER_ENABLED` is not
  exactly `"true"`, the resolver returns `null` and the `prism_start`
  response shape exactly matches pre-SPEC-077 (no `governance` field).
  Resolver failures are non-fatal in lifecycle — caught + logged,
  treated as flag-off so bootstrap never crashes on a malformed env
  file. 27/27 smoke assertions pass.

* **BIOS templates bumped to `bios_version 2.4.0` (Donna, PR #116).**
  `templates/CLAUDE.md` + `templates/AGENTS.md` replace the old
  "Ring precedence: ORG.md > PRISM.md > this file" line with the
  SPEC-077 5-layer narrower-wins model + `mandatory: true` override
  semantics. Behavior unchanged when the resolver flag is off.
  Local CLAUDE.md / AGENTS.md replicas (and the Ring 2 PRISM.md
  composed file) refresh on next `prism_sync_bios` and re-compose.

### Documentation

* **`docs/architecture/governance-precedence.md`** (new, Desiree).
  Agent-facing reference for the 5-layer model: per-layer table,
  narrower-wins rule, `mandatory: true` override gate, ORG
  dual-source resolution, layout disambiguation, and the
  `prism_start` `governance` block shape. Cross-references SPEC-077
  v0.2 + ADR #47 v0.2 + ADR #48 v0.2 + the install-lane plan.

* **`docs/research/instruction-surface-inventory-2026-05-04.md`
  v0.1 → v0.2** (Desiree). Wave 1 ratification flip on the
  pending-state entries: SPEC-077 / ADR #47 / ADR #48 marked
  ratified; CLAUDE.md / AGENTS.md replicas note the 2.4.0 SOR vs
  2.3.0 local-replica drift expected until next `prism_sync_bios`;
  coder launcher entries flipped from "PR #114 in flight" to
  "PR #114 merged"; `templates/prism-base.md` reference bumped to
  v1.3.0.

* **`templates/prism-base.md` v1.2.0 → v1.3.0** (Desiree).
  Replaces the 3-tier ring-precedence callout with the same
  5-layer narrower-wins + `mandatory: true` text the BIOS templates
  carry at 2.4.0. PRISM.md replicas re-compose to pick up the new
  text; this repo's `PRISM.md` was hand-recomposed in the same PR
  so the project's own instructions match its own BIOS.

***

## 2026-05-03

### Added

* **Hybrid RAG fourth leg — temporal recency-decay (Donna, Plan #8 Phase 5).**
  `semantic_recall` fusion is now four-leg: vector + lexical + graph +
  temporal. Temporal scores each candidate as `exp(-age_days /
  half_life)` with `half_life=180d` and re-weights the existing fused
  candidate set rather than introducing new candidates — result-set
  composition is unchanged but recent-but-relevant memories rank ahead
  of stale ones at the same semantic score. `temporal_score` is exposed
  on each `RecallHit` and persisted into `memory_recall_events.rrf_weights.legs.temporal`.
  Closes the SPEC-059 §3.2 deferral. \[PR #103 `99561fb`, follow-up
  `71a43d1`]

* **Graph leg traverses the tri-graph for entity-anchored queries
  (Donna, Plan #8 Phase 4).** When the query mentions `SPEC-N` or
  `ADR-N` tokens, the graph leg now 1-hop bidirectional-walks all
  Semantic-layer typed edges (`:REFERENCES`, `:IMPLEMENTS`, `:EXTENDS`,
  `:RELATES_TO`, `:SUPERSEDES`, `:DEPENDS_ON`, `:SPECIFIES`,
  `:FORMALIZES`, `:RESOLVES`, `:DOCUMENTS`) and sums per-target edge
  counts. Queries without entity anchors fall back to the legacy
  `:MENTIONS` co-occurrence path, so existing callers see no behavior
  change. Closes the TRIGRAPH.md §8 Phase 5 deferral. \[PR #102
  `6771f46`]

* **Granular semantic edge taxonomy (Donna, Plan #8 Phase 3).** The
  spec / ADR / plan / retro / journal extractor now promotes section
  headings into typed edges instead of collapsing them all into
  `:REFERENCES`: `## Implements` → `:IMPLEMENTS`, `## Extends` →
  `:EXTENDS`, `## Supersedes` → `:SUPERSEDES`, `## Related` /
  `## See also` / `## Complements` / `## Informs` → `:RELATES_TO`. The
  `:CAN_REFERENCE` whitelist on Canonical type pairs gates every
  subtype the same way it gates `:REFERENCES`. Existing
  `scripts/backfill_spec_059_references.py` is idempotent and re-runs
  the extractor across the full corpus. \[PR #101 `c9bb9b4`]

* **Recent postmortems surface on bootstrap (Donna, Plan #8 Phase 2).**
  `prism_status` (consumed by `prism_start`) now returns up to 5
  most-recent unresolved postmortems for the calling persona, sorted
  `captured_at DESC`. Identity resolves from the session record first,
  falls back to the `X-Prism-Identity` header when no session exists.
  Implements SPEC-063 §3.5; raises failure-relevant memory salience
  *before* the agent has to remember to recall, addressing the
  filed-but-not-recalled failure mode that drove the 2026-05-01
  branch-confusion recurrence. \[PR #100 `ea752f4`, follow-up
  `37dd978`]

* **`rrf_weights` telemetry shape populated + `X-Prism-Identity`
  client header (Donna, Plan #8 Phase 1).** `memory_recall_events.rrf_weights`
  is no longer `None` — it carries `{k, top_n, legs.{vec,lex,graph,temporal}.{top_n_count, mean_score, fetched, ...}}`
  per recall, capturing observed leg contribution rather than static
  equal weights. The Node MCP shim now sends `X-Prism-Identity` on
  outbound HTTP calls so the backend can attribute every recall to a
  persona; expected to drive `captured_via='unknown'` rate from
  effectively 100% to \~5%. Unblocks the SPEC-059 §3.4 dashboard
  panels that were rendering empty. \[PR #99 `eb936d5`]

* **Dashboard `/memory` page — per-leg lift, per-agent recall,
  postmortem-rate trend (Porsche, PR #104).** Three panels behind a
  new "Memory" sidebar entry between Telemetry and Daemons. Per-leg
  lift is a stacked-area read of `memory_recall_events.rrf_weights.legs.{vec,lex,graph,temporal}.top_n_count`
  with `legImplemented` data-driven so the temporal series only
  appears once a non-zero contribution exists in the bucketed window.
  Recall-by-agent is per-`captured_via` stacked area with a separate
  `unknown` bucket. Postmortem trend is weekly stacked filings per
  persona with an unresolved-now overlay. \[PR #104 `7c12b1d`]

* **Dashboard `/memory` page — Phase 3-5 panels (Porsche, PR #105).**
  Second wave: a Graph Edge Inventory panel reads Neo4j directly
  (12-hour stacked-area buckets per edge type, plus current per-type
  totals in the legend), tracking the 11-edge taxonomy from PR #101
  including the backfill spike as honest signal. The per-leg lift
  chart gains a count↔mean\_score toggle: score view is a non-stacked
  line overlay (scores are not additive across legs) so the Phase 4
  graph-leg lift over `:MENTIONS` becomes visible as the graph line
  crosses above lex. \[PR #105 `b12fb99`]

* **Dashboard `/memory` page — cross-cycle KPI summary panel (Porsche,
  PR #106, Plan #8 Phase 6).** Top-of-page panel cross-tabs each Plan #8
  cycle's locked hypothesis text against live measurements with an
  explicit verdict tag — Cycle 1 (granular edge backfill) REJECTED with
  the corpus-discipline gap captured; Cycle 2 (graph-leg rewire) and
  Cycle 3 (temporal-score leg) MECHANISM VALIDATED with live
  `rrf_weights.legs.{graph,temporal}.mean_score` reads. The retrospective
  probe-set landed the same day (`5a8c739`,
  `docs/research/probe-set-2026-05-03.md`) upgrades Cycle 2's verdict to
  CONFIRMED on 4/4 query classes. Plan #8 final state captured in
  `docs/research/plan-9-closure-summary-2026-05-03.md`; tag
  `plan-8-truly-complete`. \[PR #106 `59aa11b`]

### Notes

The five memory-correction PRs (#99–#103) shipped as a single bundled
batch on 2026-05-03 evening rather than as five independent cycles.
The pre-registered single-variable-per-cycle protocol from the
closed-loop validation method was deliberately collapsed for this
correction push; treat phases 1–5 as one super-cycle with a lumped
pre/post measurement window beginning at the latest merge timestamp.
Per-cycle records live on `research/memory-system-improvements` under
`docs/research/cycles/cycle_0{1,2,3,4}_preregistration.md` and are
re-labelled as **retroactive validation records** (not pre-registered
hypotheses) per the Texi architecture note 3b594f61. Forward-going,
any memory-system retrieval/scoring change requires a frozen probe-set
as a precondition — fixed query text, expected targets, baseline
snapshot, scoring rule, threshold rule, no post-hoc query editing.

***

## 2026-04-28

### Added

* **`prism_deploy` MCP verb — SPEC-051 Phase 1 (Donna).** Replaces
  `bin/prism-deploy-server1.sh` (the highest-traffic agent script in
  the repo by token volume) with a structured-return MCP verb. Returns
  parsed JSON instead of dumping \~150–300 lines of rsync +
  docker-compose + cypher-shell scrollback into agent transcripts per
  call. New `mcp/deploy.py` with a `deploy(target, mode)` dispatcher +
  `deploy_server1` implementation; single ssh ControlMaster socket
  shared across the rsync, build, restart, health, and verify stages
  so the \~200ms handshake is paid once per verb call instead of per
  stage. `bin/prism-deploy-server1.sh` shrinks from 90 lines to a
  \~50-line thin wrapper invoking the verb (retained for non-agent
  callers — CI, ad-hoc operator runs). Hardcoded API key removed
  per the no-hardcoded-machine-values rule; verb sources from
  `~/.prism/credentials.personal.json` or env. `mcp/smoke_spec051_deploy.py`
  contract smoke against live server1 verifies
  `{ok:true, verify:{alembic:023, trigraph_types:14, trigraph_edges:37,
  type_count:14, api_health:true}, duration_s:7.69}`. Tracked by
  SPEC-051 + ADR-28. \[commit `b840d85`, PR #7]

* **`prism_backup` + `prism_clone_from` MCP verbs — SPEC-051 Phase 2
  (Donna).** Two more bash scripts ported per ADR-28 (TTY-as-the-line),
  plus a shared `mcp/ssh_session.py` helper extracted out of
  `mcp/deploy.py` so every server-touching verb amortizes the \~200ms
  SSH ControlMaster handshake. `mcp/backup.py` captures Prism creds +
  every editor MCP config known to `host_config` (cross-platform via
  `PRISM_APP_SUPPORT`) + Claude Code settings + shell rc files; writes
  `MANIFEST.txt` in the legacy `rel|size` format so `bin/prism-restore.sh`
  keeps working unchanged. `mcp/clone_from.py` orchestrates ssh +
  `pg_dump` → local docker compose up postgres → drop+recreate →
  `docker cp` + `pg_restore` → full stack up → verify (alembic, live
  vs. retired specs). Both registered as non-bootstrap-gated host-side
  operator verbs, async via `asyncio.to_thread`. `bin/prism-backup.sh`
  shrinks 95 → 30 lines; `scripts/clone-server1-to-local.sh` shrinks
  82 → 20 lines. Backup smoke verified end-to-end (7 files captured,
  2 skipped, manifest matches `prism-restore.sh` reader); clone smoke
  is preflight-only by default and gated behind `PRISM_CLONE_RUN=1`
  for the destructive full-cycle test (drops the local prism DB).
  `prism_deploy` re-verified post-refactor (alembic 023, 14 types,
  37 edges, 7.21s). Tracked by SPEC-051 + ADR-28.
  \[commit `d5ad49e`, PR #9]

***

## 2026-04-27

### Added

* **Signal mesh architecture page (Desiree).** New
  [`/signal-mesh`](/signal-mesh) doc consolidates the signal subsystem
  into a single architecture page: lifecycle, identity-targeted
  addressing, three planes (verb HTTP / data WebSocket / liveness HTTP),
  four delivery strategies (`ChannelsPush`, `AppServerInject`,
  `Piggyback`, `startup-drain`), the doorbell pattern, durability
  backstop on `signal_queue`, system signals
  (`PeerJoined`/`Left`/`MasterPreempted`), and the sender contract.
  Cross-refs SPEC-034/037/044/045/048 plus the Multi-Prism Controller
  and Agent Surfaces pages. Wired into the Architecture nav and the
  Project footer in `docs.json`. \[commit `637d786`]

* **`install-lan` doc — refreshed for SPEC-045 unified WebSocket
  data plane (Desiree).** Diagram edge label updated, Troubleshooting
  row on real-time signal delivery rewritten, and the stale "What LAN
  install does NOT cover" bullet about Redis exposure removed —
  real-time signal push works on LAN out of the box now via the
  authenticated WebSocket fan-out, so the old caveat misled readers.
  \[commit `637d786`]

### Changed

* **`bin/coder` launchers — Codex bootstrap prompt (Donna).** Mirrors
  the auto-bootstrap pattern shipped earlier for Claude Code. Both
  `bin/coder.sh` and `bin/coder.ps1` now append a positional bootstrap
  prompt on the `codex` invocation that instructs Codex to (1) call
  `prism_start(name=<project>, identity=<persona>)`, (2) display the
  Prism status card as the first line of the first substantive
  response, (3) fall back to the documented "MCP unavailable —
  operating in offline cache mode" string on bootstrap failure, and
  (4) summarise active context before proceeding. Closes the gap
  where Codex sessions launched via `coder` started cold without
  hitting `prism_start`. The Claude Code launchers got the equivalent
  prompt in PR #5 (still open at time of writing). \[commit `42aeabe`,
  PR #3]

### Fixed

* **ADR-27 Phase 1+2 — SM facade is honestly Redis-fronted (Donna).**
  Surfaced after a server1 restart on 2026-04-27 demoted Donna's stale
  registration in Redis and promoted Candi (mini1, registered 70s
  earlier). `prism_status` (Postgres-backed) reported Donna as master;
  `prism_whois` (Redis-backed) reported Candi. Two services held
  competing copies of the same runtime fact. ADR-27 ratifies the
  invariants — (1) the session manager owns *all* realtime state and
  reads come from Redis on the hot path, never Postgres; (2) storage
  backends stay hidden behind their owning service, with no cross-service
  Redis-client imports or `controller_registrations` reads for runtime
  data. **Phase 1** (164fd30): `controller_service.get_prism_status`
  switches from `_active_master` / `_active_peers` (Postgres) to
  `SessionStore.get_active_sessions(...)` (Redis); `last_heartbeat`
  populated from `registered_at` as a temporary proxy. **Phase 2**
  (0b88641): `SessionRecord` gains `last_seen_at`, `last_verb_at`, and
  a `freshness_at` convenience property; `refresh_ttl` HSETs
  `last_seen_at` on every heartbeat; new `stamp_verb_at` and
  `get_tenant_sessions(tenant_id)` SCAN aggregator;
  `controller_service.stamp_engagement` dual-writes Redis (hot path)
  and Postgres (audit) with non-fatal failure on either side;
  `signal_service._resolve_identity_to_session` drops its direct
  `ControllerRegistration` tiebreak query and orders entirely from
  `SessionRecord`; `persona_service.whois` drops the Postgres
  `GROUP BY` for tenant-wide `last_heartbeat` aggregation in favor of
  `SessionStore.get_tenant_sessions(...)`; `session_manager/api.py`
  rewritten to be honestly Redis-fronted (was a sham reading
  `ControllerRegistration` directly). \[commits `164fd30`, `0b88641`,
  PR #1]

* **SPEC-049 §3.4 — `manager_stream` keeps Starlette's headers cache
  in sync with injected `session_id` (Donna).** Cursor Bugbot flagged
  this on PR #1 (high severity). The previous implementation read
  `websocket.headers.raw`, which triggered Starlette's cached\_property
  on `WebSocket.headers` and materialised a Headers snapshot from the
  current `scope["headers"]`. The subsequent mutation of
  `scope["headers"]` to inject `x-prism-session-id` left the cache
  stale, so the legacy handler reading `websocket.headers[...]` saw the
  pre-injection value (missing) and closed the WebSocket on auth
  failure. Net effect: `/api/v1/sm/stream/{session_id}` (the SPEC-049
  §3.4 documented happy path used by `mcp/session_stream.py`) silently
  dropped the URL-form session\_id. Symptoms were masked because
  signals fell through to the SPEC-044 piggyback fallback, but the
  WS plane wasn't actually delivering live. Fix: read
  `websocket.scope["headers"]` directly without ever touching
  `.headers.raw`, then defensively clear the cached\_property slot so
  the legacy handler rebuilds from the mutated scope. No upstream
  framework behavior monkeypatched. \[commit `1d0a6f4`, PR #2]

***

## v0.2.0 — 2026-04-26

> First semver-tagged Prism release. Tag `v0.2.0` was pushed at commit
> `b0d1584` on `feat/spec-045-unified-stream`. Backend + the new
> standalone `session-manager` container both report version `0.2.0`.
>
> v0.2.0 is **non-breaking for running clients** — legacy
> `/api/v1/controller/*` and `/api/v1/personas/*` paths still respond.
> The breaking 410-Gone flip is a follow-up release after the agent
> fleet has restarted onto the SPEC-049 `/api/v1/sm/*` paths.
>
> Headline specs shipping in this release:
> SPEC-049 (Identity & Session Manager service),
> SPEC-045 (Unified WS stream),
> SPEC-046 (agent surface adapters),
> SPEC-040 (`hd_exec` handoff pickup),
> SPEC-042 (WIP type vocabulary),
> SPEC-043 (start-delta symmetry),
> SPEC-038 v0.2 (collision policy + operator-gated force),
> SPEC-044 (channel push idle-wake fix),
> SPEC-037/045 §5.1 (server-side `delivered_at`).

### Added

* **SPEC-049 — Identity & Session Manager Service (Donna's design +
  implementation).** Single-writer service that owns all identity,
  registration, election, and routing state under one process and one
  HTTP namespace, replacing the scattered `/controller/*` +
  `/personas/*` surface. New backend module `backend/session_manager/`
  ships in seven phases: (1a) module skeleton, (1b) session endpoints,
  (1c) personas endpoints + identity canonicalization, (1d) election
  endpoints (read-only in Phase 1), (1f) Redis-keyspace-event reaper
  retiring the old `ControllerSweepWorker`, (1g) WebSocket streaming
  under the manager + `service.delegate` cutover, (2) MCP client cuts
  over to `/sm/*` endpoints, (3) grep-guard + cleanup of legacy import
  surface, (4) standalone `session-manager` container that can run
  separately from the API gateway. Canonical paths now live under
  `/api/v1/sm/sessions`, `/api/v1/sm/personas`, `/api/v1/sm/elections`,
  `/api/v1/sm/ws`. The legacy `/controller/*` and `/personas/*` paths
  still respond in v0.2.0 for fleet-wide rolling restart compatibility;
  the 410-Gone flip will land in a follow-up release once all live
  agents have restarted onto the new MCP client.
  \[commits `eb8e1ee`..`45b0a0b`, release `b0d1584`]

* **SPEC-040 — `hd_exec` handoff pickup verb (Donna's implementation).**
  Symmetric to `prism_wrap`. After a session has wrapped (with
  `next_actions` recorded on the wrap delta), a successor agent can
  call `hd_exec` to pick up where the predecessor left off — the verb
  is a thin wrapper over `_prism_start_impl` with `delta_kind="pickup"`
  and predecessor metadata threaded through. Closes a two-year-old
  vapor entry in the lifecycle plan and pairs with SPEC-043's
  start-delta symmetry to make handoffs first-class events in the
  delta log. \[commit `e41528a`]

* **SPEC-042 — WIP Type Vocabulary (Donna's implementation).**
  Extensible, table-driven taxonomy for in-flight work concepts that
  predate a formal SPEC: `concept`, `research`, `spec`, `plan`,
  `general`. New table `wip_types` (alembic migration `023`), new
  MCP verb `prism_wip_types` to inspect the live vocabulary, and
  `prism_wip` writes now type-tag entries against this table.
  Catalog is data-driven so adding a new WIP kind no longer requires
  a code change. \[commit `3bfca51`]

* **SPEC-043 — Session-Start Delta Symmetry (Donna's implementation).**
  `prism_start` now emits a `delta_kind="start"` lifecycle delta on
  every bootstrap, mirroring the `wrap` delta on session close. Pairs
  with SPEC-040's `pickup` deltas to give the delta stream a complete
  three-event lifecycle (start → checkpoint(s) → wrap | pickup). Visible
  in `prism_start.recent_deltas` payload and via the WS event stream.
  \[commit `1d4ac66`]

* **SPEC-045 — Unified Session+Coordination Stream (Lola's design,
  Donna's implementation).** Closes the SPEC-044 LAN-reachability gap
  surfaced in live test: MCP-side Redis subscriber was unreachable from
  LAN clients because Redis `:6379` is docker-internal on server1.
  SPEC-045 consolidates the live data plane onto an authenticated
  WebSocket served by the existing FastAPI port (`/api/v1/session/ws`),
  with Bearer + `X-Prism-Session-Id` + `X-Prism-Identity` +
  `X-Prism-Project-Id` headers and server-side Redis pubsub fan-out
  to JSON envelopes. MCP gains `mcp/session_stream.py` (daemon thread
  * own asyncio loop, `websockets>=12.0`, exponential backoff
    1s→30s ±20% jitter, `Last-Event-Id` resume) and the SPEC-034 §8
    Redis subscriber is deleted. Same singleton API kept the
    `channel_bridge` + `ChannelsPushStrategy` callers untouched.
    Heartbeat stays out-of-band on its own HTTP POST endpoint per
    the telecom rule (D1). gRPC `CoordinationStream` service kept in
    place during rollout (D3); LAN-facing role retired, full retirement
    tracked as TODO #97. Single auth boundary, single LAN port.
    Live e2e (cross-identity Lola→Donna) verified \~300ms end-to-end.
    \[commit `2e1a344`]

* **SPEC-046 — Agent surface adapters (Donna's design + implementation).**
  Generalizes `mcp/strategies/` into a per-surface adapter layer that
  owns bootstrap + wrap hooks alongside signal-strategy selection. New
  `mcp/agent_surfaces/` package: `AgentSurface` Protocol +
  `BootstrapContext` / `WrapContext` dataclasses + `SURFACE_REGISTRY` +
  memoized `get_surface()` driven by `PRISM_AGENT_SURFACE`. Phase 1
  ships `ClaudeCodeSurface` (owns `channel_bridge.capture_session_lazy`
  * `reset_coalescing` on bootstrap, `reset_all` on wrap); Phase 2
    ships best-effort `ClaudeDesktopSurface`, `CursorSurface`,
    `CodexSurface`, and `OtherSurface` fallback. `prism_start` /
    `prism_wrap` no longer reference `channel_bridge` by name —
    surface-specific lifecycle hooks are the only caller. CodexSurface
    carries explicit polish-queue comments naming Texi as designated
    owner; placeholder fields flag where SPEC-035 App Server inject
    hooks land. `smoke_spec046_agent_surfaces.py` covers 18 checks
    across dispatch + Protocol conformance + bootstrap/wrap idempotency
    for every surface. SPEC-044 + SPEC-045 smokes re-run green.
    \[commit `1b7859a`]

* **SPEC-038 v0.2 — Identity-collision policy + operator-gated force
  preempt (Donna's implementation).** Three branches now formally
  distinguished in `controller_service.register`. (1) Same-machine
  reconnect (§2.2) — silent release-and-replace on terminal restart
  inside the 90s SessionStore TTL, `release_reason='reconnect'`;
  closes the UX gap where `/exit` + relaunch as the same identity
  got 409'd. (2) Cross-machine `identity_conflict` (§2.3) —
  structured 409 augmented with `same_machine: false` so the caller
  can tell honest collision from spoof. (3) Operator-gated force
  preempt (§2.4) — `force=true` now requires `operator_id` +
  `operator_password` matching the tenant's bcrypt hash; missing or
  invalid creds → HTTP 403 `force_denied`. Closes the v0.1 gap where
  any agent with the API key could silently preempt any session.
  Migration 022 adds `controller_registrations.process_pid` (PID
  fingerprint), `tenants.force_operator_id` + `force_password_hash`
  (bcrypt). New router `POST /api/v1/operator/force-credentials`;
  bcrypt-hash on receipt, plaintext never logged. New MCP verb
  `prism_set_force_credentials` (bootstrap-skipped). All 6 branches
  verified live against deployed server1.
  \[commit `4652772`]

* **SPEC-044 idle-wake fix (Donna).** `prism_start` now calls
  `channel_bridge.capture_session_lazy()` before returning, so a
  freshly-bootstrapped idle agent has the bridge ready when a signal
  arrives. Previously capture only fired on the next non-lifecycle
  verb (SPEC-041 skip), which left the doorbell silent for the exact
  "wake idle agent" use case SPEC-044 was written for.
  `channel_bridge.push_doorbell` now returns a status string
  (`"rang" | "coalesced" | "uncaptured" | "loop-closed"`) instead
  of `bool` — the prior bool collapsed `uncaptured` into `coalesced`
  and hid this bug for a release cycle.
  \[commit `4652772`]

### Changed

* **`bin/coder` launchers — Codex CLI 0.125.0 mapping.** The Codex
  agent path no longer invokes the stale `codex start` subcommand; it
  launches `codex` directly with `-C <project_dir>` and explicit flag
  mapping. Prism autonomy → Codex `--sandbox`
  (`safe-write`→`workspace-write`, `full-local`→`workspace-write`,
  `read`→`read-only`); Prism mode → Codex `--ask-for-approval`
  (`interactive`→`on-request`, `autonomous`→`never`); `-god` →
  `--dangerously-bypass-approvals-and-sandbox` and overrides the normal
  mapping. Launcher also exports
  `PRISM_AGENT_SURFACE` via `agent`→`surface` mapping
  (`claude`→`claude_code`, `codex`→`codex`, `cursor`→`cursor`) so
  Codex sessions stop registering as `other`. New
  `bin/smoke_coder_codex.sh` covers interactive/read,
  autonomous/safe-write, autonomous/full-local, and `-god` override.
  PowerShell port matches line-for-line; `pwsh` runtime verification
  pending. `-mode` and `-autonomy` are real CLI controls for Codex
  (versus Prism env metadata only for Claude Code, where `-god` adds
  `--dangerously-skip-permissions`).
  \[commit `56a9a17`]

### Fixed

* **SPEC-037 / SPEC-045 §5.1 — server-side `delivered_at` on WS push
  (Donna).** The signal envelope's `delivered_at` is now stamped at the
  WebSocket fan-out layer, not at the recipient client. Closes a
  reliability gap where a recipient that crashed mid-receive could
  re-deliver a signal that the sender already saw acknowledged. Pairs
  with the SPEC-037 piggyback drain to keep the "no signal lost,
  no signal duplicated" floor honest under WS reconnect.
  \[commit `eedee95`]

* **`prism_whois` — case-insensitive identity join + `is_master`
  propagation.** `prism_whois` no longer misses registrations whose
  `agent_identity` differs only in case from the query, and the
  `is_master` flag is now correctly populated from the live election
  table on the response. Both gaps had been masked by the long-running
  Donna/Lola single-master assumption; surfaced as the multi-agent fan-out
  on 2026-04-26 brought Candi, Texi, Desiree, and Lafonda online.
  \[commit `2971f01`]

***

## 2026-04-26 — post-`v0.2.0` tag

> Same-day commits that landed *after* the `v0.2.0` tag at `b0d1584`
> (16:15 EDT). Operators pinned to the exact tag will not see these;
> operators tracking `main` will.

### Added

* **SPEC-048 (draft) — Codex app-server signal injection (Texi).**
  Adds a fallback-safe `CodexAppServerInject` strategy plus the launcher
  plumbing it needs: `bin/coder.sh` and `bin/coder.ps1` gain
  `-codex-app-server`, `-codex-app-server-url`, and
  `-codex-app-server-port` flags (modes `off | local | auto`, default
  `auto`) which export `PRISM_CODEX_APP_SERVER_*` env into the child
  Codex process. The injection strategy is best-effort — if the local
  Codex app-server isn't reachable, signals fall back to the SPEC-037
  piggyback path rather than failing loudly. `CodexSurface` selects
  this strategy when the env signals an active app-server. Smoke at
  `mcp/smoke_spec048_codex_app_server.py`. Spec stays at status `draft`
  pending Frank's ratification of Texi's design.
  \[commit `edd1d67`]

### Fixed

* **Stream envelope — null-safe optional wire fields.**
  `wrap_signal_envelope` now coerces `signal_type` and `signal_id` to
  strings via a `_string_or(default, value)` helper before placing them
  in the SPEC-045 §4.2 envelope, so WebSocket clients never see `null`
  where the contract promises a `subtype`/`event_id` string. Defaults:
  `subtype="Unknown"`, `event_id=""`. Closes a wire-shape gap that
  surfaced when raw `signal_queue` rows occasionally carried null
  `signal_type` after legacy backfill. \[commit `2f48aa7`]

***

## 2026-04-25

### Added

* **SPEC-044 — Channel-Based Signal Push (Donna's implementation).**
  Wires the Prism MCP server as a Claude Code Channel so signal
  notifications push into idle sessions instead of waiting for the
  next verb response. New `_PrismFastMCP` subclass advertises the
  experimental `claude/channel` capability and captures the main MCP
  loop for cross-thread notification scheduling.
  `mcp/channel_bridge.py` lets the Redis subscriber thread fire
  `notifications/claude/channel` doorbells onto the main loop with
  one-outstanding coalescing per drain cycle.
  `ChannelsPushStrategy.deliver` buffers AND rings the doorbell — the
  piggyback path stays intact for the next-verb-call path. Launchers
  gain `--dangerously-load-development-channels server:prism`. `mcp`
  pinned to `~=1.27.0` because the FastMCP override touches private
  API. Three smoke files, 11 guards, all green.
  \[commit `0f9f937`]

* **SPEC-037 — Backend-side signal piggyback (Donna).** Eliminates the
  silent-loss class where a signal landed in `signal_queue` while the
  recipient's session existed but was idle. Server now drains
  pending signals onto the response of every bootstrapped verb the
  recipient calls, marking `delivered_at` + `delivery_method='piggyback'`
  in the same transaction. Closes the SPEC-034 honesty gap where the
  bridge reported `delivered=true` for queued-but-undelivered signals.
  \[commit `bd2367d`]

* **SPEC-039 — NYI runtime contract (Donna).** `mcp/nyi.py` adds the
  canonical `not_yet_implemented` payload + `@prism_nyi` decorator +
  `audit_nyi_markers()` scanner. `prism_audit` now surfaces every
  `PRISM-NYI` marker in source plus unmarked references in
  constitution markdown (Rings 1-2.5 only). Hybrid failure mode:
  tracked stubs are advisory, untracked stubs and constitution drift
  flip `exit_status` to `fail`. 20 unit tests.
  \[commit `f40d3a0`]

* **SPEC-041 + ADR-25 — Lifecycle / messaging separation (Donna).**
  `prism_start` no longer drains pending signals — the legacy
  startup-drain block is gone. New `_LIFECYCLE_VERBS` set in
  `mcp/server.py` special-cases `prism_start` / `prism_checkpoint` /
  `prism_wrap` inside `require_bootstrap` so they skip the SPEC-037
  piggyback drain too. `prism_wrap` docstring now points callers at
  `prism_signals_pending` for last-chance delivery. Single
  responsibility, race correctness, hd\_exec composition. ADR-25
  captures the principle. Supersedes the 2026-04-24 changelog claim
  that signals "drain on the target's next prism\_start when offline" —
  delivery is now via `prism_signals_pending` or piggyback on any
  non-lifecycle verb.
  \[commit `f40d3a0`]

### Changed

* **ADR-26 v2 — Auto-declare on first use (Donna, replacing v1
  strict-reject from the same day).** Live testing of strict-mode
  surfaced the wrong default for a solo-operator AI-FIRST workflow:
  the elicitation picker forced human-in-the-loop friction every
  time a fresh identity launched. Frank's design intent: typed
  identity not in registry → auto-declare and proceed. `prism_start`
  on whois miss for non-Bot identity now calls `create_persona` with
  `focus="(auto-declared on first launch)"` and `surface_hint` from
  `PRISM_AGENT_SURFACE`, then continues bootstrap. Elicitation
  picker preserved as a fallback only when auto-declare itself
  fails (rare: backend race or validation collision). Migration is
  implicit — orphans self-heal on relaunch.
  \[commit `c863227`]

  <Note>
    ADR number 26 was later filled in Postgres by an unrelated
    *Diagnostic Tool Library* decision (filed 2026-04-27, see
    [`/adrs/adr-026-diagnostic-tool-library-container-side-python-host-side-node`](/adrs/adr-026-diagnostic-tool-library-container-side-python-host-side-node)),
    so the published ADR-26 page is no longer the auto-declare design.
    The auto-declare design lives canonically in the memory namespace
    per Frank's standing field-collapse workaround — retrievable via
    `semantic_recall(query="persona auto-declare on first use")`.
    Commit `c863227` remains the implementation reference.
  </Note>

### Fixed

* **`prism_wrap` restored as MCP tool.** The SPEC-031 refactor
  extracted `_do_hygiene` out of `prism_wrap` but left `@mcp.tool()`
  * `@require_bootstrap` on the inner helper. Net effect:
    `_do_hygiene` was exposed as `mcp__prism___do_hygiene` (private API
    leak) and `prism_wrap` was unreachable from MCP — operators could
    not cleanly close a session, only checkpoint. Decorators swapped
    back; `_do_hygiene` is internal-only, called by both `prism_wrap`
    and `prism_checkpoint` which gate bootstrap at the outer layer.
    \[commit `a14f586`]

* **SPEC-036 v0.2 — Scaffold persona-leak fix (Lola's design,
  Donna's implementation).** `CLAUDE.md` boot reads `PRISM.md`
  (step 1) before `prism_start` (step 3), so a `persona: <name>`
  line in composed frontmatter was being absorbed as the agent's
  identity, racing with runtime identity resolution. Two failure
  modes confirmed in the field: offline mode (Lofanda on Candi's
  Windows install) adopted Lola for the entire session; online
  mode (Donna on mini2) greeted as Lola before self-correcting at
  step 3. Scaffolder now strips `persona:` from composed frontmatter
  and `_ACTIVE_CONTEXT.md`.
  \[commits `5bbb138`, `c439ebf`]

***

## 2026-04-24

### Added

* **SPEC-034 — Agent-to-Agent Signal Delivery (Lola's design, Donna's
  implementation).** Redis pub/sub transport + pluggable per-surface
  delivery strategies let Prism agents send structured signals
  (ReviewRequested, ReviewCompleted, TaskAssigned, Acknowledgment,
  StatusUpdate) to named peers without human relay. Signals persist
  in `signal_queue` (durability backstop) and drain on the target's
  next bootstrapped verb when offline. Broadcast support via `to="*"`.
  Full §3 Lola↔Donna round-trip verified end-to-end by
  `mcp/smoke_spec034_signal.py`. Deployed to server1.
  \[commits `f1c536f`, `d8abf06`, `186efec`, `8d9d0e3`]

* **SPEC-035 — Agent engagement signal + routing preference (Donna).**
  Active sessions advertise an `engagement` state on the controller
  registration (`active` vs `idle`), and `signal_service.send` now
  prefers an `active` peer over `idle` when the same identity has
  multiple registrations. Eliminates the racing-Donnas case where a
  signal addressed to "Donna" could land on whichever session
  registered last instead of the one currently doing work.
  \[commit `11537f7`]

* **SPEC-031 — `prism_checkpoint` verb + enriched `prism_status`
  payload (Lola's design, Donna's implementation).** `prism_checkpoint`
  runs all `prism_wrap` hygiene (delta flush, memory triggers,
  last\_wrapped\_\* write-back) without deregistering the caller from the
  controller — so agents can "save the game" between discrete work
  units without re-bootstrapping. `prism_status` response reshaped to
  SPEC-031 §4.2: project block, session block, agents flat list,
  top-N pending TODOs, `pending_nudges` stub (ready for SPEC-029),
  pre-formatted `status_line` matching the SPEC-021 Ring-0 format.
  `bios_version` bumped 2.1.0 → 2.2.0 with checkpoint-vs-wrap
  guidance in `CLAUDE.md` / `AGENTS.md`.
  \[commits `2a1bc33`, `9aad2d5`]

* **Persona registry + runtime identity (Lola's design).** Tenant-scoped
  `personas` table (alembic 020) records persistent agent identities
  decoupled from editor surfaces — surface stays baked (physical),
  identity is runtime-assigned. Two MCP verbs: `prism_persona_create`
  (bootstrap-gated) and `prism_whois` (NOT bootstrap-gated — needed to
  answer the "who should I be?" question *before* bootstrap completes).
  Redis cache at `prism:personas:{tenant_id}` with Postgres preload on
  backend lifespan; no TTL, write-through invalidation. `bin/coder --as <identity>` launcher flag exports `PRISM_AGENT_IDENTITY` to the child
  editor process without touching editor config. `prism_start` gained
  an `identity` arg and an identity-elicitation branch — when the env
  var is unset, start returns `{requires_elicitation: true,
  elicitation_kind: "identity", personas: [...]}` carrying the whois
  payload so the agent can prompt the operator and re-enter with the
  chosen identity. `install/install.py` no longer bakes identity
  defaults into the MCP env block.
  \[commit `09e53ae`]

* **MCP heartbeat auto-reregister on 410 Gone — session-drift recovery.**
  Previously, a stale-swept session (controller\_sweep\_worker marking
  `released_at=now` after ≥10min of no verb calls) would silently
  drop the agent off the routing table; the heartbeat loop would log
  the 410 and stop. Now the heartbeat mints a fresh session\_id, calls
  `POST /controller/register` with the same identity/surface/machine\_id,
  and fires a caller-supplied callback that updates the MCP's
  `_SESSION_IDS` and the HTTP client's session header. Agents
  self-heal without operator intervention.
  \[commit `d38ee76`]

* **`/metrics` Prometheus endpoint.** FastAPI now mounts
  `prometheus_client.make_asgi_app()` at `/metrics`. An always-on
  `PrometheusMetricReader` writes into `prometheus_client.REGISTRY`
  alongside the OTLP exporter (when `OTEL_EXPORTER_OTLP_ENDPOINT` is
  configured). SPEC-030 Phase 6 counters + SPEC-034 signal counters
  are now scrapeable without an external collector.
  \[commit `b89c710`]

* **SPEC-034 observability counters.** `signals_sent_total` (by pid +
  signal\_type + delivered|queued|broadcast outcome),
  `signals_drained_on_startup_total` (per drain call, quantity =
  signals delivered), `system_signals_emitted_total` (PeerJoined /
  PeerLeft / MasterPreempted × session|broadcast target). Mirrors
  SPEC-030 Phase 6 pattern; lazy import + try/except so metric bugs
  never fail the service.
  \[commit `920e42a`]

* **Cross-platform backup + restore scripts.** `bin/prism-backup.sh` +
  `bin/prism-restore.sh` (bash, Mac/Linux) and
  `bin/prism-backup.ps1` + `bin/prism-restore.ps1` (pwsh 7+, Windows)
  snapshot user-scoped Prism state (credentials, editor MCP configs,
  shell rc, PowerShell profile) into
  `~/.prism/backups/<TS>/`. Restore is interactive with per-file diff
  * confirm + pre-restore safety copies. Manifest format is
    identical cross-platform.
    \[commits `d3cc767`, `371ed88`]

### Changed

* **`prism install` — cross-platform industrialization (Lafonda +
  Donna, parallel).** Mode-aware install with
  `--mode local|lan|cloud` + interactive 1/2/3 prompt. Local mode
  preflights Docker (CLI + Compose v2 + daemon); LAN mode preflights
  `/health` + `/api/v1/projects` auth before touching any config.
  Cross-platform launcher setup: macOS/Linux chmod + symlink +
  managed `~/.zshrc` / `~/.bashrc` block; Windows `coder.cmd` shim
  * User PATH via `[Environment]::SetEnvironmentVariable`. Per-editor
    `PRISM_AGENT_IDENTITY` / `PRISM_AGENT_SURFACE` written into every
    MCP block (Lola for Claude Desktop, Donna for Claude Code, etc.).
    Auto-detect existing backend URL+key from editor configs before
    falling back to defaults — never clobbers a working install without
    `--force`. Backend preflight symmetric for both local and lan.
    MCP venv auto-provisioned on first run; Python dep preflight.
    `cmdUp` guards against accidental second-backend starts on LAN
    clients (installed mode must be `local`).
    \[commits `65d47c7`, `543440c`, `48f7b1d`, `9538981`]

* **`bin/coder.sh` self-sufficient + native `bin/coder.ps1` port.**
  Launcher derives `PRISM_ROOT` from script location and reads
  `PRISM_API_URL` / `PRISM_API_KEY` / `PROJECT_ROOT` from
  `~/.prism/credentials.personal.json`. Env vars still override;
  only `PRISM_API_KEY` hard-fails when absent from every source.
  PowerShell 7+ port matches the bash contract line-for-line. Drops
  the DFW-Helpers.ps1 dependency the legacy launcher carried.
  \[commit `678ffb3`]

* **`docs/INSTALL.md` restructured into Local / LAN / Cloud (deferred)
  client-install modes.** Separate preflight + cred-handling for each
  mode. Server-side install (backend host) moved to a trailing
  section pointing at `DEPLOY.md` + `bin/prism-server-install.sh`.
  \[commits `0e58558`, `a075a99`]

* \*\*`coder` launcher arc — single-dash long flags + identity un-baking
  * Windows PATH parity.\*\* Both `bin/coder.sh` and `bin/coder.ps1`
    flipped to single-dash long flags (`-as`, `-mode`, `-autonomy`,
    `-god`) so the bash/pwsh contracts match line-for-line. The TS CLI
    no longer bakes `PRISM_AGENT_IDENTITY` at install time, so
    `coder --as <Identity>` actually overrides instead of fighting the
    baked default. `coder.ps1` gained Windows User-PATH install via
    `[Environment]::SetEnvironmentVariable` so a fresh Windows install
    picks up `coder` without a manual PATH edit.
    \[commits `1726e64`, `b7a440b`, `b74424a`]

### Fixed

* **Lease conflict path — ASYNC SQLAlchemy gotcha.** `lease_service.request`'s
  `try: await session.flush() except IntegrityError:` pattern was
  hitting two stacked bugs on unique-violation: (1) `MissingGreenlet`
  during asyncpg's dialect-level exception translation (context lost);
  (2) `PendingRollbackError` from outer-transaction poisoning even
  with a savepoint. Rewrote using `postgres_insert.on_conflict_do_nothing(
  index_elements=[...], index_where=text("released_at IS NULL"))` against
  the partial UNIQUE index — no exception raised, no greenlet/rollback
  recovery needed, gRPC servicer correctly surfaces `ALREADY_EXISTS`
  instead of `UNKNOWN`.
  \[commit `6b2727f`]

* **`smoke_spec030_phase1` assertion shape.** Updated to match
  SPEC-031 §4.2's new `prism_status` payload (project block, session
  block, agents flat list, total\_agents, status\_line) instead of the
  pre-SPEC-031 `{pid, master, peers, caller, total_active}` fields.
  \[commit `6b2727f`]

* **`docker-compose.personal.yml` — Redis loopback bind.** Redis was
  internal-network-only; SPEC-034 MCP subscribers run host-side and
  need pub/sub access. Added `ports: ["127.0.0.1:6379:6379"]` with
  loopback-only binding (same posture as backend's 8765 mapping).
  Server LAN deployment intentionally unchanged — exposing server1's
  redis is a separate deployment decision with ufw/auth implications.
  \[commit `8d9d0e3`]

* **`backend-grpc` container healthcheck.** The Dockerfile's default
  HTTP `curl :8000/health` HEALTHCHECK could never pass in a gRPC-only
  container. Compose now overrides with a real `grpc_health_probe
  -addr=:50051` against a properly wired `grpc.health.v1.Health`
  service. No more perpetually-unhealthy backend-grpc confusing
  operators during real incidents.
  \[commit `b960f21`]

* **Prism self-host bootstrap + reserved-name error shape.** The
  `RESERVED_NAMESPACES` guard (last touched in the DFW→Prism rename at
  commit `7f26450`) was blocking the legitimate self-host seed — a
  fresh personal stack couldn't bootstrap its own `Prism` meta-project
  because the reserved-name check fired before the tenant had any rows
  to collide with. `bootstrap_service.bootstrap` now detects the
  self-host seed case (`name="prism"` **and** zero Prism-named
  projects in this tenant) and passes `allow_reserved_name=True`
  through to `scaffold_service.build_manifest`. The guard still blocks
  users naming their own project "Prism" on a tenant that already has
  one, plus the other reserved names (`tools`, `vault`, `shared`,
  `_framework`). Router now returns a structured HTTP 422 on
  reserved-name violation instead of a raw 500 + stack trace. Caught
  by Candi on mini2 during a clean `prism up` + `prism install --mode
  local` walk-through.
  \[commit `823521b`]

* **Windows-clone install flow — CRLF guardrails + missing `cli/dist/`
  note.** Windows Git's default `core.autocrlf=true` was silently
  corrupting `backend/docker-entrypoint.sh` on cold clone — the
  shebang became `#!/usr/bin/env bash\r`, the Linux kernel read
  `bash\r` as the interpreter name, and `backend-grpc` crash-looped
  with no clear error surface (`prism up` still reported the HTTP
  backend healthy on 8765). Two-layer defense: (1) repo-level
  `.gitattributes` forcing LF on shell / Dockerfile / Python /
  YAML / TOML / JSON / Markdown, keeping `*.ps1 / *.cmd / *.bat` at
  CRLF for native Windows tooling; (2) Dockerfile-level `sed -i
  's/\r$//'` on the entrypoint before `chmod`, so the image
  self-heals regardless of how the source reached the host.
  `docs/INSTALL.md` picked up a "Windows line-ending note"
  subsection, a `cli/dist/`-not-committed note (the `npm install`
  `prepare` script builds it — a fresh clone must run that before
  `prism ...` works), and two new troubleshooting rows covering the
  `env: 'bash\r'` symptom + the missing-dist module-not-found
  symptom. Caught by Candi during the 2026-04-24 Windows retest.
  \[commit `a5fbb9c`]

### Runtime verification

All four SPEC-030 smokes pass against server1 after this arc:
`smoke_spec030_phase1`, `smoke_spec030_leases`, `smoke_spec030_approvals`,
`smoke_spec030_phase3` — 100% green. SPEC-034 full §3 Lola↔Donna
round-trip verified via `smoke_spec034_signal.py` against localhost.

***

## 2026-04-23

### Added

* **SPEC-032 Phase A — Redis session plane dual-write (Option B).**
  MCP clients never connect to Redis directly — coordination flows
  through `SessionStore` on the backend (register/refresh\_ttl/release/
  get\_master/get\_active\_sessions/publish\_\*). CD-vs-CD tie handling +
  fakeredis integration tests. Hard prereq on backend startup:
  `session_store connected` log line or backend refuses to start.
  \[commits `642bc61`, `dc68c14`]

* **SPEC-030 Phase 5 — Approval flow + Phase 6 — Observability.**
  `approval_requests` table with CHECK-constrained statuses (pending|
  approved|rejected|timed\_out). Partial indexes on pending-only rows
  (hot path: push + timeout sweep + requester listing). Phase 6
  brings OTel `MeterProvider` + five controller counters (elections,
  stream events, lease grants, approval lifecycle, stream drops) +
  active-registrations observable gauge. OTLP exporter via gRPC;
  no-op when `OTEL_EXPORTER_OTLP_ENDPOINT` unset.
  \[commits `6c95f48`, `31f6fa8`]

### Fixed

* **Installer stamps per-editor identity vars.** Every editor's MCP
  block now carries `PRISM_AGENT_IDENTITY` + `PRISM_AGENT_SURFACE`
  so the controller can tell Lola (Claude Desktop) from Donna (Claude
  Code) from Cursor from Codex instead of all four registering as
  `unknown/other`. Caught during a multi-agent session where both
  Desktop + Code ended up unregisterable-as-peers.
  \[commit `ac47715`]

***

## Earlier

For entries older than 2026-04-23, see `git log --oneline` on `main`.
The project history from DFW → PrismGR → Prism is told narratively
in [History](/history). The changelog proper starts when the controller
plane (SPEC-030) + session plane (SPEC-032) shipped together, which
is the moment Prism became operationally stable enough to warrant
curated release notes.
