Disciplines · Audits

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

compute) on a live surface.

6sections47 minread

On this page

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 contractapps/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)#

  • 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).
  • 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.
  • 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.
  • 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).
  • 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/globalfamily/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.
  • 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.
  • 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.
  • 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.
  • 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.

  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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)#

  • 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.
  • 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.
  • 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).
  • 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).
  • 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 isCrisisFrameActiveForUserloadActiveCrisisFrame, 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).
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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 AdminMobileReviewCards 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.
  • 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).
  • 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 UnavailableSectionNotes 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).
  • 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.

  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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 (DomainRouteExperienceNisabaSurface). 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)#

  • 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.
  • 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).
  • 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).
  • 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.
  • 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.
  • 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).
  • 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).
  • 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 OSHUNELEVENLABS* 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.