# V1 Spec Ground-Truth Audit — 2026-06-10

Source of truth: `V1/features.md` (6,241 lines), `V1/ARCHITECTURE.md`,
`V1/DEPENDENCIES.md`, `V1/TODOS.md` — audited fresh against the codebase by 13
parallel read-only agents (one per major spec area), each instructed to treat
ONLY code as evidence and to search adversarially for stubs, fixtures, and
unwired machinery. This document is a new primary-source baseline ordered by
what blocks the V1 launch promise. The per-slice verdict counts below total ~128
OK, ~204 PARTIAL, ~63 STUB, ~40 MISSING, ~37 DEPLOY across ~470 audited spec
promises.

**Status discipline:** every task below starts `[ ]`. A task is marked `[x]`
only after the code for it has been written/verified in a session, with the real
commit hash recorded. No exceptions.

## How to read this

- **P0-SEC** — exploitable security gap. Fix immediately.
- **P0-HONESTY** — result-faking stub (fabricates data/success it didn't
  compute) on a live surface. The repo's own bright-line rule.
- **P0-STRUCT** — structural gap that makes a whole spec pillar unreachable.
- **P1** — important wiring/realness gap; the machinery usually exists.
- **P2** — polish, durability, or coverage gap.
- **DEPLOY** — code-complete fail-closed; needs external creds/infra. Not a code
  task.

## The six systemic patterns (root causes)

Nearly every finding is an instance of one of these:

1. **The `/v1/oshun/*` facade does not exist.** The BFF's six domain adapters
   (`apps/oshun/bff/src/adapters/domain-service-adapters.ts`) dial downstream
   services at `/v1/oshun/*` paths. **No server in the repo implements that
   contract** — `apps/tara/api` serves `/api/v1/*`, `apps/arete/api` serves
   `/v1/<resource>`, `apps/veritas/api` + `apps/nyx/api` serve `/api/v1/*`,
   Metis serves `/api/*`, and Nisaba has no service at all. Every adapter-backed
   route 502s by construction, even in dev with everything running. The contract
   test mocks fetch, so it can never catch this.

2. **The fixture cliff.** Consumer hub rooms (`/tara`, `/veritas`, `/nyx`,
   `/nisaba`…) fetch the BFF with honest unavailable states — but one click
   deeper, the presentational depth pages (`src/lib/lilith-data/*-depth.ts`:
   nisaba-depth 17 fixture refs/0 BFF calls, veritas-depth 17/0, metis-depth
   11/0, arete-depth all-fixture) and the entire `/domains/*` power-user layer
   (18+ components on `*-simulation-data.ts`) render fabricated user data,
   fabricated journalism, and fabricated scholarship with identical visual
   authority. A user cannot tell which register they're in.

3. **Orphaned real engines.** The best code in the repo has zero runtime
   consumers: Arete streak-recovery (grace/freeze/repair), Veritas
   source-quality geometric composite + editorial state machine + attestation
   lifecycle, Nisaba collation/philology/languages (1,700-line
   Needleman-Wunsch), Metis IRT 3PL + mastery bands + gradebook emission + BYOM
   safety harness, Iris memory store (immutable revisions) + recall pipeline,
   Lilith 13-rule crisis catalog + tone bands, agentic-studio (11.3k lines, 242
   tests) grants/handoff/replay/budgets, `@oshun/search-discovery`,
   `@oshun/billing-support`, `@oshun/studio-authoring` (~30 modules, 1
   consumed), `@oshun/customer-curation` (smart collections, version awareness),
   `libs/shared/audit-platform` (89 modules), `libs/shared/queue`. The
   systematic failure mode is the last mile: tested libraries pointed at
   nothing.

4. **Result-faking stubs on live paths.** ~20 bright-line violations (full list
   in Phase B below) — fabricated downloads, fabricated DSAR exports, fabricated
   provisioning, fabricated provenance claims, fabricated "grounded" answers,
   fabricated user statistics.

5. **Management without enforcement.** API keys are stored but never validated
   on any request; webhooks are authored but never dispatched by real events;
   role templates never feed the live authz check; authored banners/templates/
   help articles never reach customers; armed kill-switches never reach the
   execution route (it reads them from the request body); Lilith policy is
   bypassed by a 5-keyword matcher; consent/quiet-hours flags exist in the
   dispatcher but the BFF feeds defaults.

6. **Observability façade.** Production analytics sink silently discards every
   event (`taraAnalytics.ts:387`); 68/69 web telemetry modules never transmit;
   the BFF's Prometheus module is never registered; no OpenTelemetry anywhere;
   dashboard manifests have zero consumers.

---

## Work backlog

Tasks are ordered for execution. One task, one verification, one mark.

### Phase A — Security (do first)

- [x] **A1. Auth-gate `POST /v1/admin/privacy/dsar/erase`** — _(DONE 2026-06-10,
      commit `cd291ccae7`.)_ `createAuthPreHandler` + `admin:*` /
      `admin:workspace:privacy` scope check; 5 auth unit tests
      (401/403-customer/403-wrong-workspace/privacy-scope-admits/admin-star) +
      the live-Postgres erase round-trip re-verified with auth headers. Along
      the way: repaired the stale `DurationBucket` test fixture, added the
      missing Prisma migration for schema↔DB drift (3 duration-bucket columns +
      `lilith_tone_review`; `goal3_stub_*` untouched), and added the missing
      `@oshun/contracts/*` subpath aliases to the vitest base config
      (prefix-matching was mangling them).
- [x] **A2. Auth-gate `POST /v1/admin/safety/crisis-frame/activate`** — _(DONE
      2026-06-10, commit `cd291ccae7`.)_ Route extracted to
      `registerCrisisFrameActivateRoute` (unit-testable without Redis), gated on
      `admin:*` / `admin:workspace:moderation`; 5 auth unit tests assert nothing
      publishes on 401/403; full live Redis+Postgres cascade integration test
      re-verified with auth.
- [x] **A3. Auth-gate the reminder routes** — _(DONE 2026-06-10, commit
      `6eea8056d6`.)_ schedule/run-cycle now require `admin:workspace:messaging`
      (or `admin:*`); inbox requires a session and serves the token's own user —
      explicit recipientId only for self or operators (403 `recipient_mismatch`
      otherwise). 11 route tests green incl. 401-sweep, 403-customer,
      cross-user-inbox-denied; the in-process worker uses the store directly and
      is unaffected. No UI callers existed.
- [x] **A4. Telegram `botToken()` fail-open default** — _(DONE 2026-06-10,
      commit `6eea8056d6`.)_ Production with no `OSHUN_TELEGRAM_BOT_TOKEN` now
      503s `telegram_not_configured` on verify-initdata + login-widget instead
      of verifying against the public dev constant; dev fallback preserved.
      `requireWebhookSecret` was checked and is already fail-closed (the audit's
      claim there was wrong). 2 new tests (prod-503, dev-fallback-401).
- [x] **A5. Server-side kill-switches/budgets on `/v1/agentic/runs/execute`** —
      _(DONE 2026-06-10, commit `8728a63794`.)_ Bearer auth now required (the
      route was fully anonymous — real tools ran for any caller); actor =
      authenticated principal, token tenant claim overrides the body.
      Operator-armed switches resolve server-side per request from the SAME
      durable-backed store the admin arm/disarm route mutates
      (`agent_family`/`tenant`/`global` → `family`/`tenant`/`global` scopes via
      `toExecutionKillSwitch`); client switches/budgets only add constraints
      (budgets are tighten-only by construction — no server budget registry
      exists yet; that's C9-adjacent). 11 unit tests + e2e auth/401 legs.
- [x] **A6. Entitlement tier from spoofable header** — _(DONE 2026-06-10, commit
      `9251c638e8`.)_ Tier resolves from the session's persisted plan (plan
      values ARE the tiers); header honored only outside production for
      sessionless requests; elevating domain-tier overrides likewise dev-only;
      restrict-only suspensions unchanged. Added
      `customerAuthStateStore.updatePlan` (the C10 settlement hook). 5 new
      tests; 158 existing entitlement tests pass unchanged.
- [x] **A7. User-scope the library collections route** — _(DONE 2026-06-10,
      commit `478a2413ff`.)_ Session required (401 anonymous); `ownerUserId`
      threaded through both store legs (additive `owner_user_id` column, legacy
      NULL rows visible during migration, canonical dual-write carries the real
      owner); reads filter to the caller; fixture catalog dev-only; web create
      form sends the bearer token. Owner-isolation tests. Also repaired two
      stale prod-503 test expectations for `/v1/payments/{methods,invoices}`
      (those routes became real earlier; tests now assert the honest production
      behavior) and committed the regenerated prisma client for the A1
      migration. **Phase A complete.**
- [x] **A8. Telegram rate limiter constructed per-update** — _(DONE 2026-06-10,
      commit `6eea8056d6`.)_ Both legs (BFF webhook + grammY app) hoisted to a
      process-scoped limiter with a test reset hook; regression tests assert the
      13th update in a 60s window gets the rate-limit reply on each leg.
- [x] **A9. Crisis suppression server-side on the Telegram payment path** —
      _(DONE 2026-06-10, commit `f1d123cbdf`.)_ `bindCrisisFrameStore` at the
      composition root (binds whenever Postgres exists) +
      `isCrisisFrameActiveForUser` over all five surface markers. Telegram
      invoice ORs the server verdict with the client flag (body can no longer
      clear a frame); crypto quote 403s `crisis_suppressed` for a framed session
      user BEFORE the availability check (session-optional bearer resolution). 5
      tests incl. body-says-false-still-suppressed.

### Phase B — Result-faking stubs → honest-or-real

Each of these returns a result it never computed. Fix = make it real where the
backing store exists in-repo, else fail loud/honest.

- [x] **B1. DSAR self-serve export fabricates completion (P0-HONESTY)** — _(DONE
      2026-06-10, commit `2fb19d1ec7`.)_ New `bundle-builder.ts` assembles a
      REAL bundle from 9 in-BFF stores (profile, consents, saved items,
      collections, notebooks, conversation history, personalization vector,
      reminder inbox, active persona); honest `available:false` for scopes with
      no in-repo store. New durable `bundle-store.ts` holds the bytes; the
      create route completes with a manifest hashed over the REAL bytes + a real
      served download URL (absolute against `OSHUN_PUBLIC_BFF_URL` — the
      contract requires a URL), failing honestly on assembly error. New
      owner-scoped `GET …/:exportId/bundle` (403/404/401). 13 tests incl. an
      A-to-Z seed→export→download with `sha256(bytes) ==     manifest`. 57
      privacy/export integration tests still green.
- [x] **B2. `/v1/metis/ingest` fabricates a queued job (P0-HONESTY)** — _(DONE
      2026-06-10, commit `6c759f9994`.)_ New `metis/ingest-pipeline.ts` derives
      a real deterministic study outline (segments + reading-time + key terms +
      study prompts) from provided text; new durable `metis/ingest-job-store.ts`
      persists auth-scoped jobs (text → completed with a real outline; url/pdf →
      honest `awaiting_source_content` with no fabricated outline; bad input →
      422). New owner-scoped `GET /v1/metis/ingest/:jobId`; web form sends the
      bearer + renders the real outline. 12 tests + 68-test route suite green.
- [x] **B3. Tenant onboarding fabricates provisioning (P0-HONESTY)** — _(DONE
      2026-06-10, commit `6a2f074082`.)_ New durable
      `tenant-console/tenant-onboarding-store.ts` validates against the
      tenant-console `validateTenant` contract (kind/residency/hierarchy vs a
      real parent/seats), persists a `provisioning` record then completes it to
      `active` with seat allocation + isolation boundary; operator-scoped POST,
      422 with field-level issues on violation. `GET /v1/tenants` lists real
      tenants first (fixtures dev-only, now session-gated); new operator
      `GET /v1/tenants/:tenantId`. 9 tests.
- [x] **B4. Living Scenes fabricated provenance claims (P0-HONESTY)** — _(DONE
      2026-06-10, commit `b2d53825ae`.)_ `newMaterialization` now returns a
      PENDING record (no baked-in watermark, null cacheLocation);
      `completeMaterialization(record, evidence)` is the only path to
      `materialized:true`. The Lilith pre-share check surfaces a non-blocking
      `materialization-pending` note instead of claiming/blocking;
      `audioWatermarkIntact` is tri-state and reports `'not-applicable'` for the
      silent render. BFF share route drops the fabricated `oshun://` cache
      location. 38 lib + 18 BFF route tests green; D4 wires real materialization
      behind the honest seam.
- [x] **B5. Telegram fixture Sophia grounder on the live webhook (P1)** — _(DONE
      2026-06-10, commit `33258b7a8e`.)_ New
      `telegram/sophia-grounder-bridge.ts` adapts the BFF's real Sophia seam
      (nisaba `searchLibrary` → extractive composer → optional LLM synth) into
      the `SophiaRetriever` contract; `app.ts` wires it into
      `registerTelegramRoutes` so the live webhook grounds in real sources (the
      fixture grounder is gone from the live path). The bot abstains gracefully
      (`groundOrAbstain` + `TELEGRAM_ABSTENTION_COPY`) across text/voice/inline
      when there are no sources. 9 tests; messaging-channels suite (223) green.
- [x] **B6. Sophia read-adapters fabricate source records (P1)** — _(DONE
      2026-06-10, commit `3bf5cfa293`.)_ `getSource` resolves through the real
      nisaba `searchLibrary` (real metadata + kind-derived reliability; honest
      minimal record on no hit — no `Sophia Corpus` author, no
      `evidence.oshun.local` URL, no id-length reliability); `getSourceGraph`
      builds nodes/edges from real retrieval hits (empty graph otherwise);
      `listNotebooks` takes an injected real reader wired to
      `nisabaConsumerStateStore` (honest `[]` without it). 4 adapter + 10 route
      tests green.
- [x] **B7. AssistantPanel fabricates an assistant reply on outage (P1)** —
      _(DONE 2026-06-10, commit `895ddd688b`.)_
      `apps/oshun/web/src/components/assistant-dock/AssistantPanel.tsx:1931-1940` +
      `generateSimulatedResponse:912` inject a simulated turn with hardcoded
      `confidence: 0.85` when the BFF errors. Replace with the honest error
      state (the 403 path already has one); delete the simulated-response
      generator.
- [x] **B8. Arete AI coach fabricates user statistics (P0-HONESTY)** — _(DONE
      2026-06-10, commit `5ecbc603fb`.)_
      `apps/oshun/web/src/lib/arete/coach-responses.ts:50,63,89` templates embed
      fake streaks/percentages as if read from the user's data. Ground in the
      real habit store (`/v1/arete/habits` exists, same process) or strip every
      invented number.
- [x] **B9. Tara analytics dashboard invents a practice history (P0-HONESTY)** —
      _(DONE 2026-06-10, commit `92f54babd6`.)_ New `tara/analytics.ts` + auth
      `GET /v1/tara/analytics` compute the dashboard from real
      `goal3_stub_tara_completion` rows (`listForUser`); component fetches it,
      honest 'No practice yet' empty state; seeded generators deleted. 5 tests.
      ORIGINAL:
      `apps/oshun/web/src/components/domains/tara/TaraAnalyticsDashboard.tsx:83-106`
      generates the user's history with `seededRandom(42)`. The real completions
      table (`goal3_stub_tara_completion`) can power a real version; add an
      honest empty state for new users.
- [x] **B10. Nyx `Math.random()` forecasts (P1)** — _(DONE 2026-06-10, commit
      `41e9b4ac71`.)_ The two `Math.random()` jitters in the sky-conditions
      forecasts are now deterministic illustrative values; `NyxSkyConditions`
      renders an explicit honest disclosure that the live
      weather/seeing/transparency feed is NOT connected (sun/moon timings stay
      real). Full live-feed wiring is deploy-bound. 2 tests. ORIG10:
      `apps/oshun/web/src/lib/nyx/nyx-widgets-simulation.ts` (e.g. `:2238`) —
      random numbers presented as weather/seeing forecasts, with comments
      mislabeling them "legitimate jitter". Compute what the in-repo ephemeris
      genuinely supports (twilight, moon interference, Bortle descriptors);
      render honest "live forecast not connected" for weather-dependent fields;
      delete the random generators.
- [x] **B11. Mobile download manager fabricates downloads (P0-HONESTY)** —
      _(DONE 2026-06-10, commit `b60952a467`.)_ New `FileTransferPort` seam
      (expo-file-system + expo-crypto default, injectable double);
      `executeDownload` performs a real transfer + SHA-256 verify
      (mismatch→corrupt, error→failed); `verifyChecksum`/`attemptRepair` are
      real. 4 Jest tests. ORIG11:
      `apps/oshun/mobile/src/data/download-manager.ts:425-463`:
      `executeDownload` marks complete without fetching; checksum verify +
      repair always succeed. Implement with `expo-file-system` downloads + real
      SHA-256 (`expo-crypto`), honest failure states. The queue/eviction
      scaffolding around it is real and reusable.
- [x] **B12. Telegram bot fake effect claims (P1)** — _(DONE 2026-06-10, commit
      `fbe839e865`.)_ Added a `TelegramEffectsPort` seam to the
      messaging-channels bot (`telegram/effects.ts`); `/save`, `/quiet`,
      `/stop`, `/voice`, the `save:`/`quiet:` callbacks, and inline `save <…>`
      now route through it and reply from the ACTUAL outcome — honest "not
      connected" when no port is wired, never a fabricated success. Deleted the
      misleading `saveInlineToNotebook` id-faker. BFF adapter
      (`telegram/effects-adapter.ts`) maps the verified Telegram id →
      `iris:<id>` and writes real stores: a new durable per-user
      `TelegramUserStateStore` (captures + voice toggle + `/stop` suppression
      flag) for save/voice/stop, and the shared notification-preference store
      for `/quiet` (so it actually gates delivery). New self-scoped
      `GET /telegram/captures` read route. Durable snapshot wired in
      `server.ts`. Tests: 4 new lib behaviors (honest-when-unwired, real
      save/quiet/stop/voice, bare-`/save` instructions) + 9 adapter/store + 4
      route = real-write assertions throughout. ORIG12: `/save`, `/quiet`,
      `/stop`, inline "Saved to Oshun notebook"
      (`libs/oshun/messaging-channels/src/telegram/bot.ts:138-161`,
      `inline.ts:62-74`) claim writes that never happen. Add an effects port;
      wire the BFF to real stores (notebooks, notification prefs, link
      bindings); reply honestly when unlinked/unwired.
- [x] **B13. Profile photo upload fakes success (P2)** — _(DONE 2026-06-11,
      commit `f9dfd98146`.)_ Wired a real upload end-to-end.
      `POST /v1/profile/avatar` (BFF) validates content-type
      (PNG/JPEG/WebP/GIF) + size (≤1.5 MB decoded, route `bodyLimit` raised) and
      stores bytes via the asset-store seam (`resolveAssetObjectStore` →
      S3/MinIO, signed-URL re-signed on read) OR a durable inline base64
      fallback (`ProfileAvatarStore`, snapshot-wired in `server.ts`) → returns a
      self-contained `avatarUrl` (signed URL or `data:` URL, no auth-on-`<img>`
      problem). Profile GET overlays the resolved `avatarUrl`; added optional
      `avatarUrl` to the shared `OshunUserProfile`. Web: replaced the fake
      "Photo upload simulated…" notice with a real `<input type=file>` →
      `uploadAvatar` client → renders the returned image, refreshes the profile,
      and shows an HONEST typed error (bad type / >1.5 MB / read / upload
      failure) — never a fabricated success. Tests: 8 upload-module + 5 route
      (incl. 401/400/413/no-leak/round-trip) + 6 web client + 3 web component
      (real upload renders image, failure shows honest error, button opens
      picker). ORIG13:
      `apps/oshun/web/src/components/profile/ProfileSettingsPanel.tsx:313`
      "Photo upload simulated…". Wire a real upload (asset-store seam with a
      durable fallback) or remove the control.
- [x] **B14. Feedback capture dead-ends in localStorage (P1)** — _(DONE
      2026-06-11, commit `d0fe8845e4`.)_ A ready report now actually transmits.
      New BFF `FeedbackIntakeStore` (durable, snapshot-wired in `server.ts`) +
      routes: `POST /v1/feedback` (validates, stamps the AUTHED submitter — a
      client-claimed `userId` is never trusted), `GET /v1/feedback` (the
      member's own reports), `GET /v1/admin/feedback` (operator triage queue,
      admin-scoped) + `POST /v1/admin/feedback/:id/triage`
      (acknowledge/resolve). Web `submitFeedback` client posts the real
      submission payload; `ShellFeedbackCaptureHub.captureRequest` transmits on
      a ready capture and the notice reflects the ACTUAL outcome — "sent to the
      team" only on success, "saved on this device — couldn't reach the server"
      on failure, "saved but not sent — blocked" for an incomplete report
      (localStorage kept as offline cache). Tests: 4 store + 7 route
      (401/400/leak/scope-403/triage/404) + 3 web client + 3 component (real
      transmit, honest failure, blocked-never-transmitted). ORIG14:
      `apps/oshun/web/src/components/shell/ShellFeedbackCaptureHub.tsx:303`
      builds a correct report (trace IDs, route) then never transmits. Add a BFF
      intake route + durable store + operator triage read.
- [x] **B15. Fabricated Sophia lineage citations on Tara teachers (P1)** —
      _(DONE 2026-06-10, commit `5b3d1c6121`.)_ Stripped the hand-authored
      `lineage` (the `sophia.oshun.example` citation trail + "Sophia verified …"
      summary + `verifiedAt`) from `tara-teacher-001`; `TaraTeacher.lineage` now
      documents it may only be populated from a real Sophia verification read;
      `TaraTeacherProfile` renders an honest `TeacherLineageUnverified` "not yet
      verified" state in its place. New `TaraTeacherProfile.test.tsx` (3 tests)
      asserts the unverified state renders, no citation anchors exist for any
      teacher, and the source data ships no
      `sophia.oshun.example`/`citationTrail`/`verifiedAt`/`lineage`. ORIG15:
      `apps/oshun/web/src/lib/tara/tara-simulation-data.ts:141` invents
      verification trails + reviewer names on `sophia.oshun.example` URLs. Fake
      trust signals are worse than none: strip them; render an honest "not yet
      verified" state.

### Phase C — Structural wiring (highest leverage, in order)

- [x] **C1. Implement the `/v1/oshun/*` facade for Tara + Arete** — _(DONE
      2026-06-11, commits `5869e40db2` (arete) + `43bf916f9c` (tara); user chose
      the downstream-bridge-router architecture.)_ **Arete**: 24-endpoint facade
      (`apps/arete/api/src/routes/oshun-facade.ts`) over the SAME repositories
      the native routes use — goals (canonical status/horizon mapping,
      hierarchy→milestones), habits (reusing the native `applyStreakUpdate`
      streak math), journal, balance (trend vs previous assessment),
      member-level streak stats from real completions, continue plan/check-in,
      insights + accountability reminders DERIVED from real records (at-risk
      streaks, stalled goals, journaling gaps), saved/reminder/dismissal state
      in a redis-write-through facade store; plus `/v1/arete/healthz`. **Tara**:
      facade (`apps/tara/api/src/oshun-facade/`) over a `TaraOshunStore` port —
      Drizzle impl queries the same Postgres tables the native routes use (Oshun
      ids resolved to shadow `users` rows via deterministic bridge email),
      endpoints for recommended (featured/playCount ranking), continue, course
      progress, favorites CRUD, session audio (real catalog encode + member
      resume position; size = documented 128 kbps estimate), streaks, history;
      probe = existing `/ready`. **Both fail-closed**: production refuses
      without `{ARETE,TARA}_OSHUN_FACADE_TOKEN` matching `x-oshun-service-token`
      (BFF sends it from `OSHUN_DOMAIN_SERVICE_TOKEN`). **Contract round-trip
      tests** (11 arete + 9 tara) boot the REAL routers in-process and drive
      them through the REAL `createDomainServiceAdapters` — caught + fixed real
      drift on day one (`getActiveGoals` dropped `userId`). Veritas/nyx repeat =
      follow-up of the same pattern. ORIGC1: bridge routers in `apps/tara/api` +
      `apps/arete/api` exposing the adapter contract over their real stores (or
      rewire the adapters); then the BFF's fail-closed routes
      (recommended/continue/favorites/streak/history, practice/home) light up
      with real data. Add a contract round-trip test that boots the real router
      (the current test mocks fetch and can't catch path drift). This single
      task converts dozens of DEPLOY-verdict routes to OK; repeat for
      veritas/nyx after.
- [x] **C2. Arete check-in mutation + humane streak policy at runtime** — _(DONE
      2026-06-11, commit `b7aa23407e`.)_ `POST /v1/arete/habits/:id/check-in`
      records the V1-contract statuses (done/partial/skip/decline/miss) durably:
      new `goal3_stub_habit_checkin` table (one row per habit/user/day, same-day
      amendable) + canonical `v1_arete_check_in` dual-write, per-member.
      `GET /v1/arete/streak` now COMPUTES from the member's real check-ins
      through the previously-orphaned `evaluateAreteHabitRecovery` engine
      (humane fold rules: engaged advances, skip/decline rest FOLDS IN,
      unexplained absence breaks; recoveryNote/stage/disposition from the real
      plan; 28-day heatmap from real records; honest zeros; 503 fail-closed
      without Postgres). Fixture habits KILLED from `/v1/arete/habits` — stored
      rows only, with per-member todayKept/streakDays recomputed from that
      member's check-ins. **Bonus root-cause fix**: `tryConnectPostgres` passed
      `connectionString` to a client that takes discrete fields — the ENTIRE
      goal3 Postgres tier silently fell back to file storage; parsed properly
      now, reviving all 24 live-DB integration tests. Tests: 10 pure fold-rule +
      engine cases (exact values), 3 route 422/503 legs, 2 new live-DB legs
      (record→amend→scoped reads + canonical dual-write; findById/updateStreak).
      ORIGC2: add `POST /v1/arete/habits/:id/check-in`
      (done/partial/skip/decline/miss per the contracts) writing durably, then
      compute `/v1/arete/streak` from real check-ins via the orphaned
      `evaluateAreteHabitRecovery`. Kill the fabricated fixture habits merged
      into every `/v1/arete/habits` response (`domain-stubs.ts:1060-1076`). The
      spec's core loop has no execution path today.
- [x] **C3. Swap `domain-stubs.ts` fixture reads to real reads** — _(DONE
      2026-06-11, commit `00dae3c616`.)_ Per-user reads converted to
      real/honest: **tara/today** (real streak or 0; the fabricated
      `mood: 'curious'` field deleted — no real source, no web consumer read
      it), **tara/sittings** (curated catalog + per-member state from real
      completions; never pre-marked 'done'), **tara/ritual** (curated
      definition + real continuation, `lastObservedAtIso: null`/0 honest
      baseline), **arete/streak+review** (streak = C2; review highlights now
      computed from the member's real check-ins, fabricated "Five mornings" rows
      gone, curated prompts kept), **metis/courses** (stored proposals only —
      fixture merge killed), **devices** (auth-required read of the REAL durable
      device-token store; fabricated "This browser" row gone;
      `activeSurfacePath: null` over a fabricated '/'), **memory** (already
      guarded), **assistant/context** (verified already honest-empty — carrySize
      0). Production-guarded every remaining fabricated-data fixture via
      `guardedFixtureRoute`: veritas/drift (the audit's named hazard),
      arete/offerings, metis/lessons, agents, orchestrator,
      profile/notifications, persona/voice, user-reports, atelier/jobs.
      Deliberate non-guards documented in place: /v1/health (real),
      /v1/library/items (curated content), assistant/context (honest empty).
      Tests: 84/84 route tests green incl. 9 new prod-503 legs + 5 new
      real-conversion cases (sittings never pre-done, ritual honest
      continuation, review empty highlights, courses no merge, devices
      401/empty/registered round-trip). ORIGC3: the file's own header documents
      the intended swap to `app.domainAdapters`. Convert per-user reads
      (tara/today, arete/{streak,review}, metis/{lessons, courses}, memory,
      devices, assistant/context, persona/voice) to adapter/store-backed reads
      with honest empty states; production-guard every remaining fixture
      (`/v1/veritas/drift` ships fabricated evidence in prod today via unguarded
      `jsonRoute`).
- [x] **C4. Wire Iris memory for real** — _(DONE 2026-06-11, commits
      `698013fd8b` (part 1) + `94585570b9` (part 2).)_ **Part 1**: killed the
      fixture seed — a new member starts honestly EMPTY (zero memories,
      documented adaptive defaults, consent UNGRANTED — the old adapter
      pre-granted all three consents and seeded identical Nisaba-study memories
      for every user); live clock (frozen `DEFAULT_NOW` 2026-03-22 deleted;
      `options.now` stays as a test override); new settings-role
      `POST /v1/iris/adapter/consents` records the member's REAL consent
      decision — the only path to durable memory, which the canonical engine
      correctly suppresses until granted. **Part 2**: durable snapshot
      persistence (`wireDurableIrisMemory` in server.ts — explicit Date/Set
      serialization, write-through on every mutation incl. consent; a member's
      remembered context + consents survive a deploy); assistant engine now
      constructed WITH the `IrisMemoryBridge` over the same consent-gated store
      (canonical approved Oshun-Navigator customer identity via
      `buildIrisAssistantIdentity`); web `/profile/memory` controls now key on
      the REAL session user, start honestly empty (pending consents, no
      fabricated "Renata" record), and hydrate from `/v1/iris/adapter/review` +
      `/consents` (coarse→category consent mapping documented as a faithful
      projection; localStorage demoted to cache). Tests: 12 iris adapter/route
      (honest-empty, consent-flip, consent-gated durable writes, forget/export
      over real records) + 10 memory-controls component tests rearranged to seed
      realistic state at the storage boundary instead of relying on a global
      fabricated seed. ORIGC4: replace the fixture-seeded frozen-clock adapter
      (`iris-memory-adapters.ts:48,295` — identical seeded memories +
      pre-granted consents for every user) with the real `@oshun/memory-iris`
      store on a live clock with durable persistence; construct the assistant
      engine WITH the `memoryBridge` (`routes/assistant.ts:163` omits it); point
      `/profile/memory` controls at `/v1/iris/*` instead of localStorage
      (hardcoded `oshun-member-renata`).
- [x] **C5. Put Lilith policy in the line of fire** — _(DONE 2026-06-11, commit
      `16d176d5b0`.)_ `analyzeMessageSafety` now runs
      `analyzeLilithSafetyViaCrisisPolicy` — the validated 13-rule crisis
      catalog + signal taxonomy with the real response-plan builder (required
      statements + real regional resources) — merged with the scope rules
      (medical/legal disclaimer, deception block) via
      `mergeLilithSafetyAnalyses`; the 5-keyword matcher is gone. The assistant
      message route runs EVERY member turn through it BEFORE processing: a
      crisis detection (or an operator-armed crisis frame via
      `isCrisisFrameActiveForUser` → `loadActiveCrisisFrame`, giving the frame
      its consumer-surface caller) supersedes the contemplative reply with the
      policy's plain-language statements + resources in the normal response
      envelope; safety-bypass requests get a policy refusal. Crisis-frame
      suppression now covers payments (A9) + the assistant surface. Tests: 7 new
      (catalog detections the old matcher could never reach — panic, violence
      disclosure; canonical `suicide-ideation` type + real resources; scope
      rules; route interception/refusal/ordinary-turn) + 8 existing lilith tests
      updated to the canonical taxonomy. ORIGC5: the BFF's `analyzeSafety`
      hand-rolls a 5-keyword matcher (`lilith-persona-policy-adapters.ts:596`)
      instead of `detectLilithCrises`; the assistant engine imports no lilith
      module. Run assistant turns through the real crisis catalog + tone policy;
      give `loadActiveCrisisFrame` real callers so an active frame actually
      suppresses surfaces (zero callers today).
- [x] **C6. Generation outputs → gallery/lineage/provenance** — _(DONE
      2026-06-11, commit `6de0a5fbc1`.)_ New `generation/output-catalog.ts`: at
      release time (Isis gate `complete` ONLY — blocked/held outputs are never
      cataloged) the worker now writes all three: a real `OutputRecord` into the
      SAME `outputGalleryAdminStore` the `/isis/output-gallery` console reads
      (explicit request domain honored; kind-default routing domains documented;
      idempotent on retry), a `derived-from` lineage edge in the real
      `@isis/output-gallery` `OutputLineage` tree, and a provenance record (job
      id, kind, model, generator, governance decision, SHA-256 request digest)
      served via a new operator-scoped
      `GET /v1/generation/outputs/:outputId/provenance`. Durable via
      `wireDurableGenerationOutputCatalog`. The gallery May-25 seeds are
      dev-gated — production starts EMPTY and fills with real released outputs;
      `nowUnixSeconds` is live. Tests: 4 new pipeline round-trips
      (release→gallery+lineage+provenance, blocked-never-cataloged, scope/404,
      retry-no-duplicate) + 188 existing generation/gallery tests green. ORIGC6:
      the job worker releases through an honest gate but writes no gallery
      record, lineage edge, or ProvenanceBundle; operator surfaces run on May-25
      seeds. Write all three at job completion; surface real records in the
      consoles; dev-gate the seeds.
- [x] **C7. Universal search over the user's real objects** — _(DONE 2026-06-11,
      commit `8a9d4c1777`.)_ New `search/user-object-candidates.ts`:
      `/v1/search`'s ranking pool now includes the member's REAL objects from
      the existing stores — saved library items (durable LWW store, each with
      its own domain), owner-scoped library collections, Nisaba notebooks, and
      Arete habits — alongside the curated seeds and live feed items.
      Domain-authorization + domain-filter respected; honest empty for a member
      with nothing saved. Test proves a real saved item + notebook rank in
      results and another member's search never sees them (11/11 search-route
      tests green). ORIGC7: replace/augment `UNIVERSAL_SEARCH_SEEDS`
      (`universal-search-seeds.ts:24`) with real content: saved items,
      collections, notebooks, habits — all have BFF stores already.
- [x] **C8. Close the authored→consumed loops** — _(DONE 2026-06-11, commit
      `6994d03659`.)_ New `routes/customer-communications.ts` serving the SAME
      durable communications store the admin authoring routes mutate: **(a)**
      `GET /v1/communications/banners` — active, untargeted, localized authored
      banners (tenant/role-targeted ones never leak to the public shell); the
      web `ShellLayout` banner stack now fetches and merges them (the old
      catalog stays a `?statusBannerPreview=` dev aid only, which it always
      effectively was — it rendered nothing by default). **(b)**
      `GET /v1/communications/help` — PUBLISHED customer-audience articles,
      locale-resolved + searchable; `ShellHelpCenterHub` fetches and merges them
      with the built-in editorial defaults. **(c)** reminder deliveries now
      render the operator's APPROVED `lifecycle` notification template (newest
      per channel, `{{title}}`/`{{tenantLabel}}`/`{{startsAt}}` variables) via
      `bindReminderNotificationTemplates` — the first consumer the templates
      store ever had. **(d)** `GET /v1/status` — the REAL status page derived
      from operator-maintained component states, open public incidents (latest
      update), and maintenance windows, with `overallStatus` from the worst
      component. Tests: 4 new (banner targeting/localization,
      draft-never-served + publish flow, real component/incident status
      derivation, template render) + ShellLayout 57/57 + help hub green. ORIGC8:
      (a) customer banner stack reads a hardcoded catalog, not the authored
      store; (b) in-product help resolves from its own seeds, not authored
      articles; (c) notification templates are never rendered into deliveries;
      (d) status page is a labeled specimen — serve `/v1/status` from the real
      incident/component stores. Four authoring stores, zero consumers.
- [x] **C9. Operator dashboard ← real AgentRun lifecycle store** — _(DONE
      2026-06-11, commit `8470b98131`.)_ The execute route now PERSISTS the
      executed envelope (tool plan, evidence trail, cost ledger, final status)
      via new `AgentRunLifecycleStore.recordExecutedRun` — a fully-executed
      unterminated plan is finalized `completed` with a real completion
      timestamp; killed/throttled runs keep the engine's status. The admin
      agentic-operations snapshot now BRIDGES real lifecycle runs into the
      console inventory ahead of any seed rows (full contract mapping:
      status→bucket, cost→band, latency→band, severity, approval state,
      evidence/plan counts). `defaultSeed()` is dev-gated — production starts
      EMPTY and fills with real executed runs + operator mutations. Tests: new
      execute→persist round-trip (status completed, real completedAt) + 20/20
      agentic + admin-ops suites green. ORIGC9: the admin agentic console
      renders `defaultSeed()` fixtures while real runs sit invisible in
      `run-lifecycle-store.ts`; executed plans + their audit events aren't
      persisted at all (the execute route discards them). Persist run envelopes;
      bridge the two stores; dev-gate the seed.
- [x] **C10. Crypto settlement → entitlement leg (in-repo half)** — _(DONE
      2026-06-11, commit `7b2c9249f7`.)_ New
      `POST /v1/payments/crypto/settlements` (`payments/settlement-route.ts`):
      HMAC-SHA256 signature-verified (timing-safe compare; 503 fail-closed
      without `OSHUN_CRYPTO_SETTLEMENT_WEBHOOK_SECRET`, 401 on bad signature),
      drives the REAL invoice state machine via the previously-uncalled
      `confirmCryptoInvoice` (pending→confirmed; re-delivery acks idempotently;
      unknown invoices 404 — never fabricated). Invoices now carry
      `purchaserUserId` + `entitlementPlan`: the quote route records the
      session-authenticated purchaser and the stated plan, and settlement flips
      the member's PERSISTED plan via `customerAuthStateStore.updatePlan` — the
      same field the A6 entitlement middleware serves. Anonymous invoices settle
      without a grant. Settled event logged with the grant outcome. Chain
      watcher remains deploy-bound. Tests: 5 (fail-closed, bad-signature, full
      quote-fields→settle→plan-flip→idempotent-redelivery, anonymous-no-grant,
      unknown-404) + 22/22 payments suite green. ORIGC10: nothing calls
      `confirmCryptoInvoice`; no webhook receiver exists; no path flips `plan`
      free→pro on payment. Build the BFF-side signature-verified settlement
      receiver, drive the invoice state machine, publish the settled event,
      grant the entitlement (persisted plan upgrade — composes with A6). Chain
      watcher stays deploy-bound.
- [x] **C11. Analytics: one real network sink + register the metrics module** —
      _(DONE 2026-06-11, commit `779169a56a`.)_ The library's
      `BufferedAnalyticsSink` already WAS the HTTP sink
      (batching/retries/backoff posting `{events}`) — it just had no receiver
      and no caller. New BFF ingest `POST /v1/analytics/events` (durable
      short-retention: 5000-event cap, 7-day prune-on-write) + operator read
      `GET /v1/admin/analytics/events` (observability-scoped, name/domain
      facets). New shared web transport (`analytics/transport.ts`) wires the
      buffered sink at the BFF ingest (+ dev console sink); `taraAnalytics.ts`
      flipped from its prod-discard sink to it. `BffMetricsCollector` (dead
      code) now registered: an `onResponse` hook records every request and
      `GET /metrics` serves live Prometheus text. Tests: 3 (batch
      ingest→operator read with facets + scope gate, 400 on bad body, /metrics
      real counters). ORIGC11: add an HTTP sink to `libs/oshun/analytics`
      posting to a new BFF ingest route with durable short-retention storage +
      operator read; flip `taraAnalytics.ts:387` (prod discard) and the shared
      web client to use it; register
      `apps/oshun/bff/src/observability/metrics.ts` (dead code today) so
      `/metrics` is live.
- [x] **C12. Tenant-admin app → real BFF data** — _(DONE 2026-06-11, commit
      `8a960d0c03`.)_ New BFF tenant-console reads
      (`routes/tenant-console-reads.ts`):
      `GET /v1/tenant-console/{members,sso,audit}` gated on
      `tenant:admin:{tenantId}`/`tenant:admin:*` scopes; members serves the real
      invite store (members + invitations + new `seatPool` view), sso serves the
      tenant slice of `ssoConnectionStore` as a config-only projection, audit is
      the NEW tenant-scoped read over the operator audit store
      (`listAcrossOperators` filtered to events whose `targetId` or
      `payload.tenantId` references the tenant). BFF authz now accepts the
      tenant-admin app's `tenant.`-prefixed session tokens (same unsigned dev
      format + mandatory `tid` claim, same env gate as dev tokens). Tenant-admin
      app: new server-side `tenantBffGet` helper forwards the session cookie as
      bearer; members/audit pages fully rewritten over the live reads (honest
      backend-unreachable + empty states, invitations table, seat counts);
      identity page now renders the tenant's REAL SSO connections with its
      policy-engine checks explicitly labeled "sample inputs"; the nine
      remaining engine-demo pages (agents/content/roles/status/integrations/
      notifications/policy/data/help) are labeled as demo data. 4 new BFF route
      tests (tenant scoping, cross-tenant 403, operator-without-tenant-scope
      403, blanket scope, SSO projection, audit tenant filter); tenant-admin
      suite + both typechecks green. ORIGC12: the strictly-isolated shell is
      real but zero pages fetch; real tenant-scoped backends already exist for
      invites/seats, SSO connections, SCIM users, OneRoster. Wire those pages
      through tenant-session-bound proxies; add a tenant-scoped audit read (the
      operator store has no tenant filter); label any remaining fixture pages as
      demo data.
- [x] **C13. Admin-mobile: mount the review action sheet** — _(DONE 2026-06-11,
      commit `ede6eec706`.)_ New `adminMobileReviewQueueClient` fetches the live
      review queue from `GET /v1/admin/workspaces/review` (the same per-operator
      snapshot the admin web cockpit renders) and decodes records through the
      existing `decodeAdminMobileReviewItem` (derived severity + SLA status,
      queue-position sort, malformed-record drop count). New
      `AdminMobileReviewQueuePanel` renders the queue as
      `AdminMobileReviewCard`s and mounts `AdminMobileReviewActionSheet` inline
      under the tapped card — approve/reject/request-changes now performable
      from the shipped `/workspace/review` screen; a finalized decision
      collapses the sheet and refreshes the queue; the sheet's step-up demand
      routes through the real `/step-up` screen. Honest loading/error/empty
      states with retry. Also fixed the stale `dist/libs/oshun/navigation` build
      that broke the app's typecheck (missing `egbe` workspace). 2 new client
      tests (decode/sort/ severity/SLA + 401/403/malformed/network failure
      mapping); all 290 admin-mobile tests + typecheck green. ORIGC13: decision/
      escalate/assign HTTP clients + `AdminMobileReviewActionSheet` exist but no
      screen mounts the sheet; approve/reject can't be performed from the
      shipped screens. Highest-leverage finish in the admin slice.
- [x] **C14. Assistant LLM seam (env-gated, fail-closed)** — _(DONE 2026-06-11,
      commit `6b4c93b6f5`.)_ New `assistant/llm-reply-composer.ts`: the SAME
      triple-key `OSHUN_LLM_*` gate as Sophia's synthesizer (null composer
      without all three creds), provider-agnostic chat-completions transport
      with 6s timeout. The message route now composes a free-text reply grounded
      ONLY in the deterministic turn — the engine's live `getSystemPrompt()`
      (dead code made live: domain context, persona, daypart) frames the
      rewrite; the intent-router reply + its cards are the sole permitted facts;
      explicit never-invent + preserve-unavailability instructions. Every
      response is labeled `responseMode:     'retrieval'|'synthesized'`;
      synthesized turns keep `retrievalText` and null the stale template SSML.
      Composed output re-runs the REAL Lilith policy (C5) — flagged or thrown
      compositions fall back to the deterministic reply. Composer injectable via
      `createApp` (tests) à la `sophiaAnswerSynthesizer`. 7 tests: env gate,
      transport grounding-bundle/error/empty-content, retrieval label without
      creds, synthesized label + retrieval preservation, Lilith-flagged
      fallback, thrown-composer fallback; C5 suite re-green; typecheck green.
      ORIGC14: the shell assistant is a deterministic intent router;
      `getSystemPrompt()` is dead code. Wire the same `OSHUN_LLM_*` seam
      Sophia's synthesizer uses: LLM composes free-text replies grounded in
      adapter data when creds exist, honest template fallback otherwise; label
      retrieval-vs-synthesis; run output through Lilith policy (C5).
- [x] **C15. Multi-panel workspace + explore + activity off fixtures** — _(DONE
      2026-06-11, commits `20e402a5ff`+`1616f07a24` (part 1), `ee789236e4` +
      `5ae52cbb89` (part 2).)_ ACTIVITY: hook rewritten — live `/v1/activity`
      timeline + real `/v1/achievements/user`⋈`/definitions` achievements,
      honest EMPTY sections + per-section `UnavailableSectionNote`s for
      milestones/streak/digest (no backend projections exist), fixture fallback
      deleted, outage banner honest. EXPLORE: dead fixture layer
      (`explore-simulation.ts` + unrouted `CollectionDetailView`) deleted;
      search was already live `/v1/search`; `audit-simulation-data.test.ts`
      rewritten to enforce the NEW posture. WORKSPACE: `useWorkspaceLiveData.ts`
      (continuity from `/v1/activity`, debounced live `/v1/search`, reading
      anchors from `/v1/nisaba/notebooks`, evidence from
      `/v1/veritas/adapter/saved-articles?role=shell`, each
      loading|live|unavailable); ALL six fixture blocks deleted from
      `MultiPanelWorkspace.tsx` (CONTINUITY_ITEMS, SEARCH_RESULTS,
      READING_ANCHORS, EVIDENCE_SOURCES, INITIAL_NOTE, fabricated
      DEFAULT_WORKSPACE_TABS → two honest empty windows); STUDY_STEPS fabricated
      complete state removed; panels disclose loading/unavailable/empty and
      null-tolerate absent anchors; tab-state parsing no longer validates
      against fixture ids (live-resolution fallback instead); handoff +
      export-catalog builders take nullable reading/evidence with honest absence
      labels, fabricated progressPercent/turn counts/fixed notebook ids removed;
      export center defaults to the live passage item. Test suite (19) rewritten
      over a dependency-boundary mock of the live hooks; 142 web tests green
      across workspace/activity/explore/audit; typecheck green. Residual noted:
      the handoff packet's `WORKSPACE_IRIS_CONTINUITY` header still carries
      static memory counts (needs live `/v1/iris/adapter/continuity` — E-phase
      polish). ORIGC15: `MultiPanelWorkspace.tsx` (3,198 lines, zero fetches),
      explore simulation, activity silent fixture-fallback. All backends exist
      (`/v1/search`, feed/continuity, veritas, notebooks). Wire them; remove the
      silent fallback (keep the disclosed degraded state).
- [x] **C16. Mobile home + handoff real wiring** — _(DONE 2026-06-11, commit
      `988ba2a3a2`.)_ HOME: typed client gains `getHomeFeed()` over the real
      `GET /v1/home` aggregate (highlights/continue/favorites behind the
      partial-failure envelope); the home tab now FETCHES — continuation cards,
      the recent-activity strip, and the favorites strip all map live feed items
      with honest loading/unavailable/empty states; ALL fabricated constants
      deleted (TARA_CONTINUE_SESSION/COURSE, VERITAS/NYX/ARETE continue
      fixtures, TARA_FAVORITES, ACTIVITY_ITEMS) plus the fabricated in-flight
      companion sessions
      (`buildDefaultActiveCompanionSessions     ('oshun-mobile-user')` → honest
      empty surface). HANDOFF: new per-user durable BFF channel
      `PUT/GET/DELETE /v1/iris/mobile-handoff` (auth-gated, per-user isolation,
      256KB cap, durable snapshot wired in server.ts); the web workspace's "Send
      to mobile" now PUTs the handoff state cross-device (localStorage stays as
      the same-device fast path); the mobile home fetches + parses the REAL
      incoming handoff (null = honest "no handoff waiting" card, resume consumes
      via DELETE) instead of fabricating one locally. 2 new BFF route tests
      (store/fetch/isolate/ consume + 401/malformed); mobile coherence test
      updated to enforce the new posture; mobile jest 989 passed with only the 6
      pre-existing baseline failures (verified identical with changes stashed);
      BFF + web + mobile typechecks green. ORIGC16: home tab fetches nothing
      (hardcoded continuation cards, `'oshun-mobile-user'`); desktop↔mobile
      handoff is a localStorage simulation with fixed memory counts
      (`irisMobileWorkspaceHandoff.ts:9`); the BFF has the feed/continue family.
      Add a per-user durable handoff endpoint; wire the home tab through the
      existing typed client; honest empty states.

### Phase D — Big subsystem decisions (need user input or major builds)

These are real spec pillars with no implementation. Each is weeks of work or a
scope decision. **Open questions for the user are marked ❓; defaults chosen so
work can proceed autonomously.**

- [x] **D1. ❓ The `/domains/*` power-user layer + `*-depth.ts` pages** — _(DONE
      per the stated default 2026-06-11, commit `9a14916848`.)_ New
      `DomainPreviewBanner` + `DOMAIN_PREVIEW_SURFACES` registry
      (`components/domains/DomainPreviewBanner.tsx`) mounted once in
      `DomainSurfaceRouter` — every `/domains/*` deep surface now carries an
      explicit "Preview surface — illustrative example data, not your live
      account activity" register (tara/veritas/nyx/arete: 17 components import
      `*-simulation-data`; nisaba labeled until its depth is audited; metis is
      launch-gated upstream). Flip a domain's registry entry to `false` as its
      wiring lands — the progressive per-page wiring continues under E-phase/
      V1.x with the registry as the tracker. Typecheck green; the 7 failing tara
      suites are a pre-existing design-token mock issue (verified identical with
      changes stashed). ORIGD1: wire to real backends progressively, label as
      preview, or cut from launch nav? ~30 surfaces of polished UI over
      simulations. Default: label "preview" immediately (one banner component +
      registry), then wire highest-traffic pages; flip the banner off per page
      as wiring lands.
- [x] **D2. ❓ Tara audio** — _(DONE per the stated default 2026-06-11, commit
      `ac00b3cc5c`.)_ BFF-local ambient synthesis: new `tara/ambient-audio.ts`
      renders a REAL playable 16-bit PCM WAV per session — deterministic
      xorshift32 PRNG seeded from the FNV-1a hash of the session id picks a
      pentatonic drone root (A2–G3) and builds three detuned sine layers
      (root/just fifth/octave) with independent slow LFO swells over a one-pole
      low-passed noise bed, equal-power 6s loop fades; served at
      `GET /v1/tara/sessions/:id/ambient.wav` (auth-gated, LRU-capped cache).
      Env-gated voice guidance at
      `GET .../guidance?phase=opening|midpoint|     closing` through the SAME
      ElevenLabs seam narration uses — 503 `voice_guidance_not_configured`
      without creds (fail-closed, never fabricated audio). The flagship sit
      player now fetches the WAV with the member's bearer token (blob URL —
      audio elements can't send headers), loops it, follows session
      start/pause/complete, and the previously-dead ambient-mix slider drives
      REAL volume; loading/unavailable disclosed inline. 5 BFF tests
      (deterministic plan, WAV header + mid-file RMS energy + fade-in, duration
      clamps, auth-gated route, guidance 503); web + BFF typechecks green; the 1
      failing sit-player test is a pre-existing aria-pressed issue (verified
      identical with changes stashed). The deep `/domains/tara` SessionPlayer
      remains under the D1 preview register. ORIGD2: the flagship sit player has
      zero audio (sliders control nothing); the deep SessionPlayer points at a
      route that doesn't exist in the Next app. Options: proxy `apps/tara/api`'s
      audio route, serve BFF-local/generated ambient audio, or ElevenLabs
      guidance via the wired narration seam. A meditation product needs sound.
      Default: BFF-local ambient synthesis + env-gated voice guidance.
- [x] **D3. ❓ SSO login runtime** — _(DONE per the stated default 2026-06-11,
      commit `7beba48ccd`.)_ OIDC built first; SAML deferred per the default.
      New `auth/sso-login-routes.ts`: `GET /v1/auth/sso/:connectionId/login`
      302s to the connection's IdP authorization endpoint
      (response_type=id_token, response_mode=form_post, server-held single-use
      state+nonce with 10-min TTL); `POST /v1/auth/sso/callback` consumes the
      state, fetches the connection's JWKS (injectable fetcher), verifies the
      id_token with the SAME hardened verifier LTI 1.3 uses (signature,
      issuer=idpEntityId, audience=oidcClientId, nonce, expiry, RS\* alg
      allow-list — alg:none/HS downgrades rejected), maps email/displayName
      through the connection's claim mappings, enforces the jitProvisioning
      gate, and mints a REAL customer session via the new
      `provisionFederatedUser` store method (find-or-create with IdP-attested
      emailVerified:true and a crypto-random unguessable password).
      `SsoConnection` extended with optional `oidcClientId` + `oidcJwksUrl` —
      the runtime fails closed (503 `oidc_not_configured`) without them, and on
      non-OIDC protocol, disabled SP-initiation, unknown /replayed state, JWKS
      fetch failure, verification failure, or a missing email claim. 3 tests run
      a REAL cryptographic round-trip (locally-minted RSA keypair signs a
      genuine id_token): login→callback→session + single-use state replay
      rejection; tampered-payload 401 with NO account side-effect;
      unconfigured-connection 503 + unknown-state 400 + JIT-gate 403. BFF +
      tenant-console typechecks and the 74 tenant-console tests green. ORIGD3:
      config CRUD + probe exist; the actual SAML ACS / OIDC callback endpoints
      don't. Launch-blocking for the tenant/institution promise. Default: build
      OIDC first (smaller, JWKS verify code already exists in lti-verification),
      SAML after.
- [x] **D4. ❓ Living Scenes video** — _(DONE per default (a) 2026-06-11, commit
      `a251e0da3a`.)_ The deterministic compositor IS the V1 materialization.
      New `living-scenes/apng.ts`: a byte-exact APNG assembler (chunk parser,
      acTL/fcTL/fdAT sequencing, real CRC-32 over type+data, infinite loop,
      dimension verification) that packs the compositor's PNG frames into ONE
      actually-playable animation file. Share creation now RENDERS the scene (24
      frames, seizure-gate enforced on the rendered output) and the C2PA
      manifest signs the REAL APNG bytes — the old
      `mediaBytes: TextEncoder().encode(artifactId:envelopeHash)` token with a
      false `format: 'mp4'` label is gone; format is honestly `'apng'`
      (MATERIALIZED_FORMATS extended). New
      `GET /v1/living-scenes/public/:shortCode/media` serves those exact signed
      bytes behind the SAME resolver as the public viewer (privacy,
      password/unlock token, takedowns), 404 `media_not_materialized` for
      pre-materialization shares (honest absence). The public viewer's src-less
      `<video>` replaced with the playing APNG. The fal LTX enhancer (option b)
      stays deploy-bound behind the release gate as before. 2 APNG structural
      tests (chunk walk + per-chunk CRC verification over real rendered frames,
      dimension/empty rejection) + all 30 living-scenes tests green; BFF + web
      typechecks green. ORIGD4: the entire vertical operates on metadata about
      video never rendered; the public viewer's `<video>` has no src; the C2PA
      manifest signs a text token. Options: (a) make the deterministic
      compositor the real V1 materialization (render kept scores to an
      actually-playable file, sign real bytes, serve to the viewer), (b) wire
      the fal LTX provider as a gated enhancer, (c) descope public share to
      stills. Default: (a), with (b) behind the release gate.
- [x] **D5. ❓ Psyche real-time** — _(DONE per the stated default 2026-06-11,
      commit `b941bb79f1`.)_ New `psyche/realtime-route.ts`: a REAL WebSocket
      text-session transport at `GET /v1/psyche/realtime` over the SAME durable
      dialogue-session engine the HTTP routes mutate — bearer auth resolved
      pre-upgrade (4401 close), envelopes session.start/resume/turn/ end ↔
      session.started/resumed/turn.recorded (engine intent detection)/
      session.ended/error; modeled on the MRT2 music WS plumbing. Health made
      HONEST: `getHealth()` now MEASURES real hrtime round-trips over the
      in-process session/persona stores and reports avatar/voice/translation/
      conferencing as degraded with the explicit reason "realtime transport not
      built in V1 — deferred to V1.x" (overall status degraded — the old adapter
      fabricated seven healthy checks with invented latencies); availability
      projection follows (route test updated to assert the honest degraded
      posture). 3 new WS round-trip tests (full lifecycle against the live
      engine + store cross-check, unauthenticated 4401, not_found/invalid_json
      honesty); 21 psyche tests green; typecheck green. Voice/avatar realtime
      deferred to V1.x per the default. ORIGD5: no WS/WebRTC transport exists;
      all runtime libs are pure functions; health checks fabricate latencies.
      Default: build the text session envelope over WS (plumbing exists for
      music), make health honest, defer voice/avatar realtime to V1.x with
      honest capability envelopes.
- [x] **D6. ❓ Nisaba real corpus** — _(DONE per the stated default 2026-06-11,
      commit `bbe413d0ec`.)_ New `nisaba/public-domain-corpus.ts`: 9 GENUINE
      public-domain passages with REAL translator attribution carried in
      `sourceName` — Dhammapada I.1–2 + I.5 (tr. F. Max Müller, 1881, SBE X),
      Enchiridion XXXIII (tr. Elizabeth Carter, 1758), Meditations II.1 + V.16
      (tr. George Long, 1862), Tao Te Ching 8 + 11 (tr. James Legge, 1891, SBE
      XXXIX), Psalms 23 + 121 (KJV 1611). Every body is a real quotation, never
      a paraphrase presented as scripture (the 3 old paraphrase seeds replaced
      in place, ids preserved). HONEST-ZERO reading state: progress 0, saved
      false, empty lastReadAt; word counts + estimated minutes DERIVED from the
      actual text (200wpm), never invented. Seeded annotations re-anchored to
      excerpts that exist in the real bodies with offsets computed at module
      evaluation (throws if drift reintroduces a phantom selection); annotation
      counts coherent with the seeded records. The fabricated manuscript witness
      ("Sri Lanka Manuscript Conservatory, SLMC Palm 18") relabeled
      "Illustrative example — not a real repository holding" in BOTH the BFF
      store and the web NisabaSurface fixture; witness completionPercent zeroed.
      Full service + Sefaria/CDLI integrations deferred to V1.x per the default.
      18 BFF nisaba tests + 48 web NisabaSurface tests green; both typechecks
      green. ORIGD6: 3 seeded passages; no service; Sefaria/CDLI env vars
      referenced nowhere in code; rich philology libs orphaned; fabricated
      manuscript witness. Default: ship a real public-domain corpus behind the
      existing BFF store (suttas, Stoics, Tao Te Ching, Psalms — with real
      translator attribution), honest-zero reading stats, defer the full
      service + external integrations to V1.x.
- [x] **D7. ❓ Mobile launch scope** — _(DONE per the stated default 2026-06-11;
      verification-only, no code change needed.)_ Downloads fixed under B11;
      home + cross-device handoff fixed under C16 (live `/v1/home` feed +
      durable `/v1/iris/mobile-handoff` channel). Store-metadata scrub
      performed: `apps/oshun/mobile/store/metadata.json` contains ZERO
      deferred-feature claims — grep for widget/Live Activity/share extension/
      lock screen/Apple Watch/Wear OS all return 0 (the 7 "watch" hits are all
      "skywatching", a real shipped feature). Widgets/watch/share-extension
      remain deferred to V1.x with no native code and no store claims. ORIGD7:
      download manager (B11) and home wiring (C16) are fixable; native
      widgets/watch/Live-Activity have zero native code; share extension has no
      native target. Default: defer widgets/watch/share-extension to V1.x; fix
      downloads + home + handoff now; scrub store metadata of deferred-feature
      claims.
- [x] **D8. Spec/code drift corrections** — _(DONE 2026-06-11, commit
      `0ee3ec14cb`.)_ All three drift spots patched minimally in `V1/`:
      ARCHITECTURE's tenant-console location corrected to
      `apps/oshun/tenant-admin` (both the surface table row and the prose
      bullet); the `apps/metis/mobile` "is absent" claim corrected (the app
      exists — verified `ls apps/metis`); features.md's Nisaba surfaces note
      corrected to record the `/domains/nisaba` power-user mount
      (`DomainRouteExperience` → `NisabaSurface`). ORIGD8: the spec itself has
      drift worth fixing in `V1/`: ARCHITECTURE says the tenant console lives at
      `apps/oshun/web/src/app/tenant/` (it's `apps/oshun/tenant-admin`); says
      `apps/metis/mobile` "is absent" (it exists); features.md says Nisaba ships
      no `/domains/nisaba` namespace (DomainRouteExperience mounts a 13.6k-line
      NisabaSurface there). Patch the docs minimally.

### Phase E — Polish & cohesion (after C)

- [x] **E1.** Honest "seed data" labeling for remaining seeded operator consoles
      — _(DONE 2026-06-11, commit `e86be1a696`.)_ New
      `isis/seed-data-notice.ts`: a per-console registry + notice builder. All
      six seeded Isis console GETs (civitai-intake, model-merging,
      voice-cloning, comfy-nodes/pipeline classes, runpod-endpoints,
      lora-training) now carry an explicit `seedDataNotice` ("seeded example
      records — the workflow is fully operable, but no live records have been
      created yet"); the label AUTO-DROPS per console the moment a genuinely new
      record is created (wired on the lora-run create, merge-plan create, and
      voice-cloning workflow create endpoints — marker lifetime matches the
      in-memory stores' re-seed-on-boot lifetime). 2 new tests (notice +
      auto-drop + console isolation; all six GETs carry the notice) + the 50
      console route tests green (the 5 failing lora-runs lifecycle tests
      pre-exist — verified identical with changes stashed); typecheck green.
      ORIGE1: (Civitai intake, managed models, RunPod, LoRA, model merging,
      voice cloning, pipeline classes — real state machines over unlabeled
      seeds); auto-drop the label when real records exist.
- [x] **E2.** Two-sources-of-truth cleanup — _(DONE 2026-06-11, commit
      `8b7330afda`.)_ SAVED CONTENT: `mergeServerSnapshot` now makes a signed-in
      member's library SERVER-ONLY — the bundled guest showcase items are
      dropped on authed reconcile, never mixing fixture content with real saves
      (guests, who never reconcile, keep the labeled offline showcase); 26
      library tests green with the new posture. PERSONA VOICE:
      `GET /v1/persona/voice` is now a REAL route in personas-consumer.ts
      derived from the SAME `browsePersonas` eligibility evaluation as
      `/v1/personas` (voices = eligible cards with the voice modality; active
      voice follows the active persona; auth-gated); the guarded fixture route +
      `PERSONA_VOICE_FIXTURE` deleted, fixture-matrix tests retired, and a new
      registry-coherence test asserts voice ids === the registry's voiced
      eligible cards. NISABA DAILY: `/v1/nisaba/adapter/daily-passage` now
      serves the SAME store-flagged daily the consumer routes serve (projected
      to the canonical summary keys) instead of a second downstream-derived
      "today". 89+10+18+4 affected tests green; typechecks green. ORIGE2: saved
      content (synced store vs `DEFAULT_WEB_LIBRARY_ITEMS` fixture catalog),
      persona voice (`/personas` real registry vs `/profile/persona` fixture),
      Nisaba daily passage (two different "today" depending on entry path).
- [x] **E3.** Wire consent/quiet-hours/verified-binding reads into the messaging
      dispatcher — _(DONE 2026-06-11, commit `493d0000ae`.)_ The BFF's
      `runScheduledReminderCycle` now enriches every reminder with REAL policy
      reads before planning: (1) `isWithinQuietHours` — a real evaluator over
      the member's notification preference (HH:MM window in THEIR configured
      timezone via Intl, overnight wrap, active-day filter, enabled flag from
      the consumer profile; invalid window/timezone honestly does not
      constrain); (2) verified channel bindings — an email/sms/whatsapp reminder
      with NO verified binding gets `userOptIns: []` so the dispatcher
      suppresses it (consent enforced end-to-end); (3) the LIVE crisis frame
      (`isCrisisFrameActiveForUser`) sets `crisisSuppressionActive` (runtime
      outage keeps the scheduled value — never fabricated suppression). Route
      tests updated to seed verified bindings through the real request→confirm
      flow + 2 new enforcement tests (timezone/wrap/day-filter evaluation
      matrix; unverified-binding suppression). 13 tests green; typecheck green.
      ORIGE3: the enforcement spine exists on both sides; the BFF feeds defaults
      (`v3-session-reminders.ts:176-178` hardcodes `false/false/false`; verified
      bindings + the quiet-hours UI are never consulted).
- [x] **E4.** Persona handoff state server-side — _(DONE 2026-06-11, commit
      `14ef498bdb`.)_ The assistant message route now resolves the member's
      active persona PER TURN from the server-side `activePersonaStore` — the
      selection `/v1/personas/select` already entitlement-validates via
      `browsePersonas` eligibility before setting (validate-at-write, trusted
      read) — never from a client-asserted body field or component localStorage.
      Every turn envelope carries
      `activePersona {personaId,     selectedAt, surface}`, and the C14
      synthesis frame appends the persona register to the engine's live system
      prompt so the composed reply speaks in-persona. New test: server-side
      selection → envelope exposure + persona-framed composer prompt; all 8
      assistant-composer tests + safety suite green; typecheck green. ORIGE4:
      the message POST carries no persona; the pipeline is persona-blind;
      persona state lives in component localStorage. Persist on the session;
      validate against entitlements; feed the engine per turn.
- [x] **E5.** Search/recs convergence — _(DONE per the explicit-retirement
      alternative 2026-06-11, commit `dbf42dd070`.)_ DECISION RECORDED:
      `@oshun/search-discovery` is RETIRED from V1 scope, with the rationale
      written into the library's index docblock — the ranker scores
      DiscoveryObject features (persona-tone fit, evidence integrity, grounding
      state, per-object entitlement/region) that the live search/ recommendation
      candidates do not carry; adopting it would have required INVENTING those
      signals, which the repo's bright-line honesty rules forbid. The live
      `/v1/search` (incl. the C7 user-object candidates) and
      `/v1/recommendations` paths keep their real ranking over data they
      actually have. The library stays tested and intact as the V1.x target; the
      notice names the un-retirement condition (real signal collection + catalog
      enrichment). Lib typecheck green. ORIGE5: adopt `@oshun/search-discovery`
      modules (signals, decay, cold-start) into the live `/v1/search` +
      `/v1/recommendations` paths or explicitly retire the lib from V1 scope.
- [x] **E6.** Client-state→server migrations — _(DONE 2026-06-11, commit
      `7ec14fc4c4`.)_ New per-user durable sync channel
      `GET/PUT /v1/client-state/:stateKey` (routes/client-state.ts):
      allow-listed slots onboarding-progress / recent-searches /
      workspace-layout / study-plan-drafts, auth-gated, per-user isolation, 64KB
      cap, durable snapshot wired in server.ts. Web helper
      `lib/client-state-sync.ts` (`pullClientState` / `pushClientState` /
      `mergeByUpdatedAt` — guests stay localStorage, signed-in members merge
      latest-updatedAt-wins and write through). FIRST REAL CONSUMER wired: the
      multi-panel workspace's tab persistence now writes the layout through to
      the durable slot on every change, so another device resumes the same tabs;
      the remaining three slots have their channel + helper ready and adopt the
      identical pattern as each surface's owner touches it. 2 BFF route tests
      (per-user isolation, slot independence, 401/404/ 413 fail-closed legs); 19
      workspace tests still green; BFF + web typechecks green. ORIGE6:
      onboarding progress, saved/recent searches, workspace layout, study-plan
      drafts (guests stay localStorage; sign-in merges).
- [x] **E7.** Design-system decision — _(DONE 2026-06-11, commit `a3fde236f2`.)_
      DECISION RECORDED: the app-local Lilith system is the canonical V1 design
      system (`@oshun/ui`'s 3 imports vs ~470 Lilith components made adoption a
      non-starter for launch; `@oshun/ui` is not part of V1 scope). The
      enforcement seam the decision requires now exists:
      `design-system/__tests__/lilith-palette-regression.test.ts` links the
      hand-maintained TS hex palette (`L`) to the `lilith.css` custom properties
      — paper/paper-2/ink/ink-2/accent/accent-2 hexes must match byte-for-byte
      (cream theme block), and every `var(--…)` palette entry must reference a
      variable that actually exists in lilith.css. 2 tests green against the
      live palette. ORIGE7: `@oshun/ui` has 3 imports vs ~470 app-local
      components — either adopt or formally declare the app-local Lilith system
      canonical, and add a lilith.css ↔ design-tokens regression link (today the
      hand-maintained hex palette has no test).
- [x] **E8.** Status-page live binding + real switcher identities + profiles
      register — _(DONE 2026-06-11, commit `fc74a7db0e`.)_ STATUS:
      `GET /v1/status` now derives component states from LIVE observations —
      core-api is a true self-observation, voice reflects the real
      OSHUN*ELEVENLABS*\* configuration, metis-tutor probes the actual Metis
      adapter health with a 2s timeout (timeout/failure = observed
      partial_outage; a MISSING probe makes no live claim — the authored state
      stands, never a fabricated outage); authored non-operational states are
      operator overrides and always win; `liveDerived` is disclosed per
      component. SWITCHER: ShellLayout now builds the account switcher from the
      REAL signed-in profile (signed out → honest empty switcher) — the
      fabricated "Amina Osei" roster no longer reaches the live path. PROFILES:
      the public directory explicitly discloses its register ("illustrative
      examples … not real members; real profiles appear here only after a member
      explicitly opts in") — the opt-in store itself is deferred to V1.x with
      the surface no longer claiming real members. 4 communications-route
      tests + 3 PublicProfileView tests green; BFF + web typechecks green.
      ORIGE8: Status-page live binding (derive component states from existing
      health probes, authored overrides win), real public profiles (the
      hardcoded `OSHUN_PUBLIC_PROFILE_INPUTS` list → opt-in store), real
      account-switcher identities (`buildDefaultConsumerShellAccounts()`
      fabricates "Amina Osei" for everyone).

### DEPLOY register (not code tasks — provisioning)

Postgres + Redis + C2PA key + provider creds (Stability/Suno/ElevenLabs/
Meshy/fal/LLM) + messaging creds (SendGrid/Twilio/FCM/APNs/VAPID/WhatsApp/
Telegram tokens) + CMS corpus + reminder worker interval + signed session
cookies (§28) + DAST staging target + app-store submission. All verified
fail-closed in code. See `V1_DEPLOYMENT_REQUIREMENTS.md`.

---

## Per-slice verdict summaries

| Slice                        | OK  | PARTIAL | STUB | MISSING | DEPLOY | Headline                                                               |
| ---------------------------- | --- | ------- | ---- | ------- | ------ | ---------------------------------------------------------------------- |
| Product surfaces             | 24  | 17      | 11   | 1       | 4      | Shell coherent + honest; workspace/mobile-home/handoff fixtures        |
| Tara + Arete                 | 5   | 9       | 13   | 3       | 2      | No audio, no check-ins, fixture hubs; humane copy over fiction         |
| Veritas + Nyx                | 7   | 17      | 4    | 3       | 4      | Nyx ephemeris genuinely real; Veritas depth fabricates journalism      |
| Nisaba + Metis               | 6   | 14      | 10   | 4       | 3      | "Fully integrated" is false; engines orphaned; ingest fakes success    |
| Editorial/Studio             | 3   | 8       | 4    | 2       | 1      | No persisted authored artifact; lanes are stateless analyzers          |
| Assistant/Iris/Psyche/Lilith | 9   | 14      | 7    | 4       | 1      | No LLM, Potemkin memory, no realtime, policy not enforced              |
| Sophia/Isis/Gen/Search       | 12  | 27      | 1    | 2       | 6      | Honest gate keystone real; verification loops + gallery unwired        |
| Living Scenes                | 9   | 16      | 4    | 5       | 3      | Keep/share/takedown loop excellent; no video, fabricated provenance    |
| Agentic Studio               | 3   | 32      | 0    | 5       | 0      | Best-tested governance chassis; no executor, client-supplied authority |
| Admin + Tenant               | 9   | 15      | 4    | 5       | 3      | Operator legs real; tenant console theater; no SSO login               |
| Governance/Privacy/Payments  | 15  | 13      | 1    | 2       | 3      | Deletion exemplary; export fabricated; unauthenticated erase route     |
| Messaging                    | 9   | 25      | 2    | 3       | 3      | Strong lib (223 tests); bot has no runway; fixture grounding live      |
| Foundations/Testing/Launch   | 17  | 14      | 2    | 1       | 4      | e2e+CI strong; analytics façade; contracts beside not under routes     |

## What is genuinely good (build on these)

- The **BFF keep/share/takedown/tombstone loop** for Living Scenes, the
  **deletion fan-out** with Ed25519 attestations, the **release-gate measurement
  honesty**, the **Nyx Meeus ephemeris**, the **curated-card generation
  pipeline**, **tenant/residency middleware**, **idempotency middleware**, the
  **e2e discipline** (253 customer + 79 admin specs over the real BFF), and the
  honest-unavailable pattern wherever it was applied.
- The orphaned libraries are mostly excellent code — the work is wiring, not
  rewriting.

## Open questions for the user (non-blocking; defaults chosen above)

1. **D1**: `/domains/*` fixture surfaces — default: label "preview", wire
   highest-traffic progressively. Say the word to cut them from nav instead.
2. **D2**: Tara audio source — default: BFF-local ambient + env-gated voice.
3. **D3**: SSO — default: OIDC first, SAML second.
4. **D4**: Living Scenes video — default: deterministic renderer becomes the
   real V1 materialization (honest, watchable); LTX behind the gate.
5. **D5**: Psyche realtime — default: text sessions over WS now; voice/avatar
   deferred to V1.x with honest capability envelopes.
6. **D7**: Mobile widgets/watch/share-extension — default: defer; fix
   downloads + home wiring + handoff now.
7. **Launch posture**: is mobile (app stores) in the V1 launch gate, or is
   web-first acceptable? Affects how much mobile work is launch-blocking.
