Disciplines · Audits

V1 Residual Backlog — 2026-06-11

shared house-user; per-user-scope the global habit/closure/offering stores** — _(DONE 2026-06-11, commit 301f490eb7.)_ Every

7sections115 minread

On this page

Successor to V1_SPEC_GROUND_TRUTH_AUDIT_2026-06-10.md (all 57 tasks of which are complete). Built from a fresh 13-agent residual audit of V1/ against the post-backlog codebase; the full per-slice evidence reports live in docs/audits/V1_RESIDUAL_AUDIT_2026-06-11/ (01–13). ~250 findings: 12 P0-SEC, ~39 P0-HONESTY, ~25 P0-STRUCT, ~87 P1, ~51 P2, ~26 UX.

Status discipline (unchanged): every task starts [ ]. Mark [x] only after the code is written/verified in-session with the real commit hash recorded. One task → one verification → one mark → commit + push to BOTH the branch and main. Each task cites its evidence report section — read it before starting work.

2026-09-18 re-scope pass. Every open item was re-read against its cited report section and re-checked against the code as it stands today; the paths and line numbers in the children are today's. The 42 open items were finding-clusters, so each is now a roll-up line over child checkboxes, one per independently shippable finding: 224 children, each with a release tier from libs/oshun/navigation/src/release-scope.ts ([V1.0] 193, [V1.1] 14, [V1.2] 14, [later] 3) and a Verify: line. 45 dated plain bullets record findings, or legs of findings, that were found already fixed, with path:line evidence, instead of open boxes; no box was checked by this pass. 21 children carry a blocker tag (14 wait on another open child, 4 need the owner's credentials or hardware, 2 are owner decisions, 1 is a human review); the other 203 are actionable. Seven report findings that no backlog item had carried were attached to the nearest parent and marked "added 2026-09-18": 02 §5 (P17.a, a bright-line honesty defect), 01 §18 (P22.d), 06 §16 (T3.i), 06 §17 (T2.g), 06 §19 (P11.f), 06 §20 (T2.b) and 06 §21 (T2.h). Two things moved under the reports since June: V1/features.md was split into V1/features/*.md, so every "features.md NNNN" line reference in the reports is stale, and the BFF auth middleware now lives in libs/shared/bff-kit/src/authz.ts. A parent is closed only when every child under it is closed.

Report key: 01 product-surfaces · 02 tara-arete · 03 veritas-nyx · 04 nisaba-metis · 05 editorial-studio · 06 assistant-iris-psyche-lilith · 07 sophia-isis-generation-search · 08 living-scenes · 09 agentic-studio · 10 admin-tenant · 11 governance-privacy-payments · 12 messaging · 13 foundations-launch.


Phase S — Security (do first)#

  • S1. Tara/Arete identity: spoofable x-oshun-user-id → real auth; kill shared house-user; per-user-scope the global habit/closure/offering stores(DONE 2026-06-11, commit 301f490eb7.) Every x-oshun-user-id read deleted; mutations (sit-complete, habit propose, check-in, review close, offering keep) now require the verified bearer (collectionsAuthGuard); personalized reads (today/room/sittings/ ritual/streak/habits/review) resolve an OPTIONAL verified member and degrade to an honest anonymous baseline. Habits owner-scoped across all three store tiers (owner_user_id column + listForOwner; legacy unowned rows served to nobody; check-in 404s on another member's habit; canonical dual-write carries the real user). Review closures + offering decisions per-member AND durable (snapshot write-through — were volatile global maps). Search user-object candidates use listForOwner. Web sends the bearer on sit-completion + habit-proposal writes. Tests: 87 route/store tests green incl. 3 new isolation/401 suites; 24 live-DB tests green incl. new cross-owner leg; BFF + web typechecks green; the 1 failing TaraSitPlayer test is the pre-existing aria-pressed baseline (verified identical stashed). ORIG: every goal3 /v1/tara/*+/v1/arete/* route must resolve the real session user (401 without); per-user-scope tara completions/resume/ favorites and arete habits/check-ins/closures/offering decisions; add cross-user isolation tests. (02 §1–2)
  • S2. GET /v1/nyx/observations unauthenticated + store not user-scoped — every member's "private" field log is one global list(DONE 2026-06-11, commit 9722ac5305.) GET + POST now require the verified bearer; owner_user_id across all three store tiers with strict listForOwner (legacy unowned rows served to nobody; snapshot restore normalizes); canonical v1_nyx_logged_observation dual-write carries the real member (was literal 'house-user'); web form sends the bearer. 88 route/store tests + 24 live-DB tests green incl. new 401 + cross-member isolation legs; BFF + web typechecks green. ORIG: auth-gate all verbs; rebuild the store per-user with legacy migration; isolation tests. (03 §4)
  • S3. Metis integrity reads unscoped: any learner can read any verdict audit + open appeals on others' verdicts(DONE 2026-06-11, commit 90a9c0f394.) Bridge gains getAcademicIntegrityVerdictLearnerId; the route enforces caller===learner on audit reads + appeal opens and party-membership on workspace reads (operator scopes retain review access — the GET legs now accept admin:metis too, which they previously 403'd); mismatch answers the same 404 as an unknown id (no probing). 5 route tests green incl. the new three-leg foreign-learner test; BFF + lib typechecks green; 8 themis-wiring lib tests green. ORIG: enforce ownership on audit GET + appeal GET/POST via the verdict's subject user; operator scope bypass for staff. (04 §1)
  • S4. /v1/atelier/scenes + /v1/lilith-studio/scenes: global unscoped store, unauthenticated read AND write(DONE 2026-06-11, commit 002fbb47eb.) Both POSTs auth-gated + owner_user_id stamped across all three store tiers; the GET is strictly listForOwner (anonymous → honest empty); the fabricated "For J · 14 May" fixture scenes merged into every response are DELETED; canonical dual-writes carry the real member as sender/author (were 'house-user'/'oshun-studio-author'); both web scene forms send the bearer. 89 route/store + 24 live-DB tests green incl. new 401/anonymous-empty/cross-member legs; BFF + web typechecks green. ORIG: auth-gate; per-user scope; stamp the real session user on writes. (05 §"Atelier/Lilith-studio scenes")
  • S5. Generation job routes unauthenticated: enqueue / drain (fires paid provider calls) / read-any-job-by-id(DONE 2026-06-11, commit fd58b47b34.) All three legs now require the SAME Isis workspace scope as their siblings (list/retry) — operator-only rather than owner-scoped, because the spec's customer path is the curated-card route over the in-process enqueue ("raw provider machinery is never customer-facing"); no client anywhere polls the raw job-by-id route. New test: 401 anonymous + 403 wrong-scope on all three; 185 generation tests green; typecheck green. ORIG: session-gate enqueue + read; make drain operator-only. (07 §"Generation job lifecycle routes are unauthenticated")
  • S6. Living Scenes tenant share policy is client-supplied headers — the admin tenantLivingScenesStore is never enforced server-side(DONE 2026-06-11, commit a37ac70bc8.) Share policy (scope/embed), retention default/max, the mass-share window, the crisis label (now from the REAL crisis-frame runtime), the metis operator-override (now needs a real tenant-operator scope), and the x-oshun-now clock are ALL resolved server-side; the headers survive only behind a non-production test override env (OSHUN_LIVING_SCENES_HEADER_POLICY_OVERRIDES=1). New tests: spoofed allow-headers denied (403 denied_public_scope) for a tenant_internal_only tenant, tenant-internal still allowed, untenanted default preserved. All living-scenes suites green; typecheck green. ORIG: resolve the member's tenant server-side and read policy from the real store; ignore the headers on the live path. (08 §6)
  • S7. Agentic executor hardcodes tier: 'operator-admin' for output-gallery.retag — customer-reachable admin mutation(DONE 2026-06-11, commit e6c840fa00.) Per-tool scope requirements (toolScopeChecks) checked against the caller's REAL scopes; retag demands the same Isis workspace scope as the dedicated admin route; 403 tool_scope_missing before any execution. (09 F1)
  • S8. Kill-switch ExecutionTarget built from client body — family/ agent/tenant switches evadable by request shaping(DONE 2026-06-11, commit e6c840fa00.) family/provider/region resolve from server metadata keyed by tool (STUDIO_AGENT_TOOL_TARGETS); body step fields ignored; tenant comes only from the verified claim ('untenanted' fallback, never the body); the clock override is non-production only. NOTE: per-AGENT switches still match the caller-declared rootAgentId — making those airtight needs the runtime agent registry (09 F11, Phase P). 24 runs-route tests green incl. the reshaping-evasion leg. (09 F2)
  • S9. recordExecutedRun blind upsert by client runId — any user can overwrite another user's persisted AgentRun + approval history(DONE 2026-06-11, commit e6c840fa00.) The route 409s (run_id_conflict) on a runId owned by another member BEFORE executing; the store also refuses cross-owner overwrites defensively (typed 409 error); owner re-execution upsert preserved. New hijack test proves the victim's envelope survives intact. (09 F3)
  • S10. GET /v1/payments/invoices unauthenticated + unscoped — dumps every customer's invoice ledger (purchaserUserId, plan, amounts, txids)(DONE 2026-06-11, commit f705e547ba.) Auth-required; members see only their own invoices (anonymous quotes surface only to operators); admin:*/admin:studio retain the full view + tenant filter; the by-id route stays a capability URL (random UUID, DTO carries no purchaser). New 401/cross-member/owner/operator test legs; 84+6 tests green; typecheck green. (11 §1)
  • S11. Mobile assistant never reaches the BFF — client-side keyword template bypasses Lilith crisis policy, memory, persona, composer(DONE 2026-06-11, commit 7870c118b3.) The sheet now creates a real assistant session and POSTs every member turn (typed + seeded) to /v1/assistant/sessions/:id/message with the bearer; crisis interception envelopes render the policy statements + real resources inline; the keyword model survives ONLY as the explicitly-labeled offline fallback ("Offline tips — not a live assistant reply…"). New typed client methods (createAssistantSession/sendAssistantMessage) with bearer/URL tests + 4 container tests; 18 jest tests green; mobile typecheck green. (06 §6)
  • S12. Unauthenticated, un-rate-limited CPU-bound Living-Scenes render endpoint (1920×1920×120 frames)(DONE 2026-06-11, commit a37ac70bc8 with S6.) Session-gated; members clamped to 960×960×48 (Isis operators keep 1920/120); per-user in-flight cap (1) + 6/min rolling budget answering 429. Tests: 401 anonymous, member clamp vs operator envelope, budget 429 + per-user isolation. (08 §13)

Phase H — Honesty (result-faking on live paths)#

Work V1.0 children first, then V1.1, then V1.2.

  • H1. Home surface fabrications — roll-up of the fabricated-content findings on the signed-in Home (daily plan, profile stats, activity strip, recommendations, Arete and Nyx fallbacks); children below. (01 §1, §4, §6, §7, §16, §17)
    • Already fixed (checked 2026-09-18): the daily-plan fixture default is gone — apps/oshun/web/src/components/routines/DailyPlanV2.tsx:1070-1077 defaults to [], guarded by apps/oshun/web/src/components/home/__tests__/HomeNoFabricatedHistory.spec.tsx. What is left of 01 §1 is H1.a.
    • Already fixed (checked 2026-09-18): the web profile-stats seed — apps/oshun/web/src/lib/auth-context.tsx:589-600 gives a new member zero stats. What is left of 01 §4 is H1.b, H1.c and H1.h.
    • H1.a [V1.0] Feed Home's daily plan from the real routines backend: apps/oshun/web/src/components/home/HomeWorkspace.tsx:558 mounts <DailyPlanV2 /> with no props while useRoutines() and useRoutineRecommendations() in apps/oshun/web/src/lib/hooks/use-bff.ts have zero callers on Home. Pass the hook results as routines and recommendation; a member with no routines keeps the existing empty state; HomeWorkspace.tsx imports no SIMULATED_* export. (01 §1) Verify: new apps/oshun/web/src/components/home/__tests__/HomeDailyPlanLive.spec.tsx mocks the BFF boundary — one returned routine renders, an empty list renders the empty state, a failed read renders no routine.
    • H1.b [V1.0] Stop defaulting a member to a paid, verified profile: libs/oshun/auth/src/profile-store.ts:257-258 (toProfileFromSession) and :279-284 (createDefaultOshunProfile) fall back to plan: 'pro', verified: true and the 7/18/5 demo stats when the session is silent. Default to 'free', false and zeros; keep the demo numbers behind an explicit demo-seed argument only. (01 §4) Verify: new libs/oshun/auth/src/profile-store-defaults.spec.ts — a session with no plan or verified flag yields free, unverified, zero stats; npx vitest run on that file from libs/oshun/auth.
    • H1.c [V1.0] Give stats.streakDays and stats.activeDomains a real writer or stop rendering them: they are read at apps/oshun/web/src/components/home/HeroBanner.tsx:461,1031 and apps/oshun/web/src/components/ProfileSettingsPanel.tsx:263-265,961-963 and nothing ever writes them. Derive the streak from GET /v1/arete/streak and active domains from the member's real activity, or remove both tiles. (01 §4) Verify: new apps/oshun/web/src/components/home/__tests__/HeroBannerStreakSource.spec.tsx — the rendered streak equals the mocked /v1/arete/streak value and no streak line renders when the read fails.
    • H1.d [V1.0] Delete the fabricated Home activity strip: FALLBACK_ACTIVITY in apps/oshun/web/src/components/home/ActivityFeed.tsx:77 is the initial state (:441) and the error state (:477,507,539). Start empty, and on failure render the same unavailable presentation /activity uses (apps/oshun/web/src/components/CustomerShellOutageBanner.tsx) with no first-person entries. (01 §6) Verify: extend apps/oshun/web/src/components/home/__tests__/ActivityFeed.test.tsx — a 500 from /v1/activity renders zero activity rows and the outage reason; grep -n FALLBACK_ACTIVITY returns nothing in the component.
    • H1.e [V1.0] Stop swapping simulated recommendations into Home: apps/oshun/web/src/components/home/MixedDomainRecommendationsLane.tsx:61,112 serves SIMULATED_CROSS_RECOMMENDATIONS on error or on an empty payload, and apps/oshun/web/src/components/cross-domain/CrossDomainRecommendations.tsx:945,972 defaults useDefaultFallback to true. Empty payload renders the cold-start invitation, error renders a disclosed unavailable state, and the default flips to false. (01 §7) Verify: extend apps/oshun/web/src/components/home/__tests__/MixedDomainRecommendationsLane.test.tsx — empty and error responses each render zero recommendation cards and no sourceContext copy.
    • H1.f [V1.0] Remove the fabricated Arete practice fallback: FALLBACK_HOME_PRACTICE in apps/oshun/web/src/components/home/homeAretePracticeModels.ts:132-219 (streaks 12/9/17, progress 68/54/73) renders while /v1/arete/practice/home is loading or failing. Mirror the Veritas module (homeVeritasBriefingModels.ts): stage copy plus "practice data unavailable", zero numeric claims. (01 §16) Verify: extend apps/oshun/web/src/components/home/__tests__/HomeAretePracticeSection.test.tsx — with practice: null the section renders no digit followed by day or %.
    • H1.g [V1.0] Remove the fabricated Nyx perspective fallback: FALLBACK_HOME_PERSPECTIVE in apps/oshun/web/src/components/home/homeNyxPerspectiveModels.ts:96-212 asserts "Jupiter at opposition" tonight and a 42% observation in progress. The fallback claims no event and no continuation. (01 §17) Verify: extend apps/oshun/web/src/components/home/__tests__/HomeNyxPerspectiveSection.test.tsx — with perspective: null the text Jupiter and any percentage are absent.
    • H1.h [V1.1] Apply the same defaults on mobile: apps/oshun/mobile/src/auth/mobile-auth-context.tsx:241-243 falls back to plan ?? 'pro', verified ?? true and passes stats: undefined, which hands a new member the 7/18/5 demo stats. (01 §4) Verify: new apps/oshun/mobile/src/auth/mobile-auth-profile-defaults.spec.tsx (jest, run from apps/oshun/mobile) — a fresh session yields free, unverified, zero stats.
  • H2. Billing client-side fiction — roll-up: rebuild the member billing surface on the real persisted plan and invoices (composes with S10); children below. (01 §3, §22; 11 §21)
    • Re-checked 2026-09-18: every finding still holds. The BFF has since gained a Stripe fiat rail (apps/oshun/bff/src/payments/fiat-routes.ts), GET /v1/billing/subscription-management (apps/oshun/bff/src/routes/billing-subscription-management.ts:105) and the member-scoped GET /v1/payments/invoices (apps/oshun/bff/src/routes/domain-stubs.ts:3762); the web surface reads none of them.
    • H2.a [V1.0] Seed billing honestly empty: createDefaultOshunBillingSnapshot in libs/oshun/auth/src/billing-store.ts:979-1025 seeds currentPlan: 'pro', "Visa ending in 4242" and a paid 1200-cent invoice for every member. Seed free, no payment methods, no invoices. (01 §3) Verify: new libs/oshun/auth/src/billing-store-seed.spec.ts — the default snapshot has plan free, zero methods, zero invoices.
    • H2.b [V1.0] Give the web billing store a BFF fetcher like its siblings: apps/oshun/web/src/profile/store.ts:68-71 builds it on localStorage only. Hydrate plan and renewal from GET /v1/billing/subscription-management and invoices from GET /v1/payments/invoices; an unreachable BFF renders a disclosed unavailable state, never the seed. (01 §3) Verify: new apps/oshun/web/src/profile/billing-store-hydration.spec.ts — a mocked free member renders free with no invoices; a mocked settled invoice renders once; a 503 renders the unavailable state.
    • H2.c [V1.0] Route "purchase" into a real payment flow: handlePurchaseClick in apps/oshun/web/src/components/profile/BillingSection.tsx:128-142 mutates localStorage, announces "active · renews" and calls syncProfilePlan (:1524), which flips the client plan that apps/oshun/web/src/components/billing/ShellFeatureGate.tsx:46-47 reads. Send the member to the crypto quote (/billing/crypto) or the Stripe checkout the fiat rail exposes, or show an honest not_configured state; the client never writes its own plan. (01 §3) Verify: extend apps/oshun/web/src/components/profile/__tests__/BillingSection.test.tsx — clicking purchase performs a navigation or BFF call and leaves the profile plan unchanged until the server reports it.
    • H2.d [V1.0] Remove the fabricated backup card: addBackupPaymentMethod in apps/oshun/web/src/components/profile/BillingSection.tsx:248-262 invents a Visa 4242 or Mastercard 4444 with an expiry three years out. Remove the control, or hand off to the payment provider's own method-collection flow. (found while re-checking 01 §3) Verify: same spec as H2.c — no code path calls upsertPaymentMethod with a literal last4.
    • H2.e [V1.0] Replace "Report failed charge": the button at apps/oshun/web/src/components/profile/BillingSection.tsx:689 calls simulateBillingFailure (:295), which injects a declined-card state into the member's own billing. Post a billing-category report to POST /v1/feedback (apps/oshun/bff/src/routes/feedback.ts) instead and delete simulateBillingFailure. (01 §22) Verify: same spec as H2.c — clicking the button sends one /v1/feedback request and the billing snapshot is unchanged.
    • H2.f [V1.0] Build the member billing view: apps/oshun/web/src/app/billing/page.tsx is a static link card. Render the session plan, the member's invoice history and a receipt link per settled invoice (the by-id invoice DTO already carries the signed receipt, apps/oshun/bff/src/routes/domain-stubs.ts:3788-3795). (11 §21) Verify: new apps/oshun/web/e2e/billing-history.spec.ts (chromium, workers=1) — a member with one settled invoice sees it with a receipt link; a free member sees an honest empty history.
    • H2.g [V1.1] Apply H2.a and H2.b on mobile: apps/oshun/mobile/src/profile/store.ts:41 uses the same seed and apps/oshun/mobile/app/(tabs)/profile.tsx:1233 renders its invoice. (01 §3) Verify: new apps/oshun/mobile/src/profile/billing-store-hydration.spec.ts (jest) — a fresh member renders free with no invoice.
  • H3. Web notifications center + /v1/notifications fabricate seven canonical notifications (fake billing alarm, fake safety review, fake data-export-ready) whenever the live feed is empty(DONE 2026-06-11, commit 5e0040e295.) buildCanonicalLaneNotifications DELETED from the BFF (items now derive only from the real domain feed; admin/support/privacy/safety lanes honestly 0 until their stores are wired); the web panel's 13-item fabricated fallback DELETED — failed feed → disclosed outage notice, empty feed → honest empty state; old fixture content moved to the mocked network boundary in tests. NOTE: reminder-inbox wiring deferred to P4 (InAppMessage carries no domain — threading it through the dispatcher belongs with the inbox-UI task). 9 BFF + 44 web tests green; typechecks green. (01 §5; 12 §2)
  • H4. Mobile fabrications sweep — roll-up of the fabricated content on the native tabs; every child is V1.1 because the native apps ship in V1.1; children below. (01 §8–13)
    • Already fixed (checked 2026-09-18): the capture "permission granted" fiction — apps/oshun/mobile/app/(tabs)/index.tsx:1219-1237 now calls requestMobileCapturePermission, which asks the OS through apps/oshun/mobile/src/capture/mobileCapturePermissionsBridge.ts:68,88. What is left of 01 §13 is H4.g.
    • H4.a [V1.1] Activity tab: delete INITIAL_NOTIFICATIONS (apps/oshun/mobile/app/(tabs)/activity.tsx:95, initial state at :522,531) and stop syncNotifications returning early without a dev token (:744). Start empty and loading; signed out shows a sign-in prompt; a failed read shows an unavailable state with no timeline. (01 §8) Verify: new apps/oshun/mobile/app/(tabs)/activity-honest-states.spec.tsx (jest) — signed-out, failed and empty reads each render zero notification rows.
    • H4.b [V1.1] Profile tab memory controls: replace INITIAL_MOBILE_PROFILE_MEMORY_ENTRIES (apps/oshun/mobile/app/(tabs)/profile.tsx:405, state at :707,713) with hydration from GET /v1/iris/adapter/review and /v1/iris/adapter/consents, the reads the web page uses (apps/oshun/web/src/app/profile/memory/memory-state.ts:258-259); forget, pause and export call the same BFF routes as web. Closes 06 §21 for the read side. (01 §9) Verify: extend apps/oshun/mobile/app/(tabs)/profile.test.tsx — with an empty review response no entry mentioning Renata renders and consent shows ungranted.
    • H4.c [V1.1] Profile tab linked sessions: INITIAL_LINKED_SESSIONS (apps/oshun/mobile/app/(tabs)/profile.tsx:292) is the initial state (:700) and the signed-out state (:970). Start empty; signed out shows a sign-in prompt; an error shows the error. (01 §9) Verify: same spec file as H4.b — a signed-out render lists zero devices and the governance row reads zero sessions.
    • H4.d [V1.1] Library tab Iris handoff: apps/oshun/mobile/app/(tabs)/library.tsx:36 calls buildMobileHomeIrisHandoffState() with no arguments, which renders the fixture in apps/oshun/mobile/src/navigation/mobileIrisHandoff.ts:30,84 (memory counts, "64% progress"). Lift the home tab's /v1/iris/mobile-handoff fetch into a shared hook and render "no handoff waiting" when it is null. (01 §10) Verify: extend apps/oshun/mobile/src/navigation/mobileIrisHandoff.test.ts (jest) — a null handoff renders the empty card; the builder has no default fixture path.
    • H4.e [V1.1] Universal-share card: apps/oshun/mobile/app/(tabs)/library.tsx:48 calls buildMobileUniversalShareModel() with no requests, so apps/oshun/mobile/src/share/mobileUniversalSharing.ts:47,63-130 invents an active link and an invite to learner@example.edu. Feed it from the real library-sharing reads (apps/oshun/bff/src/routes/library-sharing.ts) or render the empty share center; delete the default requests. (01 §11) Verify: extend apps/oshun/mobile/src/share/mobileUniversalSharing.test.ts — no requests yields zero links and zero invites.
    • H4.f [V1.1] Calendar-sync card: apps/oshun/mobile/app/(tabs)/index.tsx:911 builds state from buildDefaultMobileCalendarSyncItems() and the static transport (apps/oshun/mobile/src/calendar/mobileCalendarSync.ts:164,358,588), and "Sync now" fires a success haptic without calling anything. Drive the card from the BFF calendar-connection routes the web uses (apps/oshun/web/src/profile/calendar-connections-client.ts); unconnected shows "Connect a calendar"; "Sync now" calls the real sync route or is removed. (01 §12) Verify: extend apps/oshun/mobile/src/calendar/mobileCalendarSync.test.ts (jest) — with no connections the state lists zero providers and the static transport is unreachable from the default path.
    • H4.g [V1.1] Capture card: permission is now real, but a ready capture action only routes to an ordinary screen (apps/oshun/mobile/app/(tabs)/index.tsx:1240-1275); no camera, microphone or photo capture exists. Build a minimal capture-to-upload leg on expo-camera and expo-image-picker (both in apps/oshun/mobile/package.json) over the avatar asset-store seam, or remove the capture actions from Home and keep the redaction-policy model. (01 §13) Verify: extend apps/oshun/mobile/src/capture/mobileCaptureIntake.test.ts — a ready action either produces an upload request or the card is absent; no action is labelled capture while only navigating.
  • H5. /arete hub is the last fixture room + all 8 /arete/* depth pages serve fabricated intimate narratives(DONE 2026-06-12, commit 559bad49b5.) New GET /v1/arete/room over the member's REAL habits/check-ins (trailing-week completion, aggregated week stats, humane-engine coach note, honestly-empty journal line); getArete() fetches it with an honest areteUnavailable() fallback; AreteRoom discloses empty states. The 8 fixture depth pages carry the preview register via app/arete/layout.tsx (hub/habits/offerings explicitly unlabeled). Full depth-page WIRING (streak/review pages → the real C2 endpoints) remains P17. 87 BFF + 14 web tests green; typechecks green. (02 §3–4)
  • H6. The check-in loop has no UI: C2's POST has zero callers; the only check-in UI is orphaned AND fakes its save(DONE 2026-06-12, commit f80147378e.) New AreteHabitCheckInControls (done/partial/skip/decline) on every /arete/habits row POSTing the real C2 endpoint with the bearer, rendering only the SERVER's recorded streak/recovery outcome; home "Check in" launch repointed at /arete/habits (was the coach chat); the orphaned fake-save DailyCheckInOverlay deleted with its tests. NOTE: the sit-player reflection fake-save sub-item is 02 §5 (H5's hub cluster /P17) — still open there. 2 new + 7 facade + 24 overlay tests green; typechecks green. (02 §6)
  • H7. /domains/* fake-result tools: AreteAICoach canned replies + fake typing; TaraSoundLibrary play/mixer/binaural UI with zero audio code(DONE 2026-06-12, commit 8c435e26f6.) Coach chat starts empty and routes every turn through the real /v1/arete/coach/responses composer (unreachable → disclosed, never simulated). Sound library plays REAL audio: ambient layers stream per-sound deterministic BFF WAVs; binaural = real stereo OscillatorNodes at catalog frequencies; bells = struck-bell additive synthesis; music catalog (no recordings in V1) discloses on play. The fabricated COACH_SESSIONS history remains in the banner-registered session-list views (labeled example data). 953 domain tests green; typecheck green. (02 §7–8)
  • H8. D1 banner hole: the preview banner mounts only in the [domainId] catch-all — 54+ standalone /domains/* static sub-routes (veritas/nyx/tara/arete/nisaba tools) bypass the register entirely(DONE 2026-06-11, commit f3ed8125a4.) New app/domains/layout.tsx mounts a path-resolved DomainPreviewBannerForPath for the WHOLE segment (all 55 deep pages + future ones); the duplicate room-level mount removed; DOMAIN_PREVIEW_SURFACES stays the single switch. 7 new tests over the spec-named deep pages; 81 domain-surface tests green; typecheck green. (01 §14; 02 §9; 03 §3)
  • H9. Veritas /veritas/{claim,source,story,topic,counterclaims,mobile, provenance,retraction} serve fabricated journalism unlabeled — and the real hub deep-links into them ("Read sources" → invented Norway-grid claim)(DONE 2026-06-12, commit 1783477729.) New app/veritas/layout.tsx mounts a path-resolved preview register on every depth sub-route (the real hub stays unlabeled; same DOMAIN_PREVIEW_SURFACES switch); the real lead's "Read sources" now anchors to the on-page evidence rail rendering the article's REAL claims/sources. 12 tests green; pre-existing lilith baselines verified stashed; typecheck green. (03 §2)
  • H10. /v1/activity fabricates Nisaba/Metis timeline items for every member; /v1/metis/room fabricates streak/minutes/assessment/tutor nudge unguarded in prod(DONE 2026-06-12, commit ba6e01061a.) Activity nisaba branch now reads the member's REAL notebooks; metis branch reads REAL tutor sessions + ingested outlines (new metisIngestJobStore.listForOwner), honest-empty otherwise, no fabricated grounded/verified badges or dead hrefs. Metis room: honest zero-state weekStats/resume/assessment; curated catalog progress 0; tutor nudge from the REAL durable tutor-session store or honest absence. NOTE for H11: nisaba activity now reflects the store, so the seeded fixture notebooks surface until H11 removes the seed. ALSO surfaced: courseProposals store is globally shared (any member's drafts shown to all as "drafted by you") — owner-scoping needed, tracked under Q1/P18 follow-up. 9+5+89 BFF + 19 web tests green; typechecks green. (04 §2–3)
  • H11. Nisaba per-user seed fabricates member-authored annotations/ notebooks + "verified" grounded-evidence views with invented citations on unbannered consumer routes; C7 search ranks them as the member's real objects(DONE 2026-06-12, commit e20f45cfaf.) All three seed families DELETED (3 member-authored annotations, 2 pre-built notebooks, 3 grounded/verified evidence views with invented citations + misquoted MN 118); members start honestly empty; passage annotation counts start 0 and stay real; grounded evidence 404s until the real pipeline runs; curated passages/labeled witness/compare lanes remain (dangling refs emptied). NOTE: the /v1/nisaba/room "third today" sub-item is 04 §6 (P1) and stays tracked under P18. 10+10+26 BFF + 48 web tests green; typecheck green. (04 §4; 05 §1)
  • H12. /studio/authoring: unlabeled fixture doc, fabricated Sophia/Lilith/Isis governance bindings, "autosave: saved" with zero persistence — also an orphan route(DONE 2026-06-12, commit c705918aac.) Explicit rehearsal register rendered (seeded example / in-memory only / illustrative bindings); autosave says "held in-memory (not persisted)"; readiness + AI-assist copy no longer claim live bindings or an upstream agent; route linked from the studio hub as "(rehearsal)". Real persisted artifact store + live bindings remain P19/T-scope. 6 tests green; typecheck green. (05 §authoring)
  • H13. Assistant fabrications — roll-up of the fabricated assistant, memory-control and safety surfaces; children below. (06 §4–§9)
    • Already fixed (checked 2026-09-18): forget-a-fact, pause and export reach the server — apps/oshun/web/src/app/profile/memory/memory-state.ts:418-424 (DELETE /v1/iris/adapter/memory/:id), :376-380 (PATCH …/pause), :431-432 (POST …/export).
    • Already fixed (checked 2026-09-18): the fabricated "Renata" crisis record — web reads GET /v1/safety/crisis-frames/mine (apps/oshun/web/src/app/profile/safety/ProfileSafetyJourney.tsx:526,605); mobile builds from the real frame (apps/oshun/mobile/src/safety/MobileProfileSafetyJourney.tsx:57-66,151).
    • Already fixed (checked 2026-09-18): the faked tool-progress bars and the four invented sessions — apps/oshun/web/src/components/assistant/AssistantPanel.tsx has no simulated progress, and a failed history read sets historyUnavailable (:4629-4634, rendered at :7343-7345).
    • Already fixed (checked 2026-09-18): the "Calculus I 34%" adapters — apps/oshun/bff/src/routes/assistant.ts:442-475 delegates Nisaba and Metis to app.domainAdapters like the other rooms.
    • H13.a [V1.0] Send the remaining memory clears to the server: category clear, scope clear, scope opt-out and the full clear with its seven-day grace in apps/oshun/web/src/app/profile/memory/ProfileMemoryControls.tsx:606-690 still only commit() a local view-model. Add bulk-delete and full-clear legs to the Iris adapter routes (apps/oshun/bff/src/routes/iris.ts), call them, then re-hydrate. (06 §4) Verify: new apps/oshun/bff/src/routes/iris-memory-bulk-clear.spec.ts — after a category clear the review read returns no entry of that category; a web spec asserts the toast appears only after a 2xx.
    • H13.b [V1.0] Replace the step-up theatre: the challenge is the constant 'iris-step-up' compared in the browser (apps/oshun/web/src/app/profile/memory/ProfileMemoryControls.tsx:78,407). Require the real session step-up the BFF already uses for device sessions (apps/oshun/bff/src/routes/auth-device-sessions.ts) on the destructive Iris routes and drop the client-side phrase. (06 §4) Verify: extend the H13.a BFF spec — a destructive call without a fresh step-up answers 403 step_up_required.
  • H14. Sophia operator evidence queue (AdminEvidenceQueuePanel) is a 100% hardcoded fixture presented as live operator data; customer gallery provenance inspector renders canned bundles instead of calling the live C6 provenance route(DONE 2026-06-12, commit 5e935fbf06.) Evidence queue carries an explicit rendered seedDataNotice (real wiring of the verification loops = P7); the gallery inspector now fetches the REAL C6 provenance for the SELECTED output with honest idle/loading/not-recorded/not-authorized/unreachable states — the canned Saraswati bundles are gone from the live surface (the fixture component's three remaining operator mounts are Q-register work). 14 gallery + 3 evidence tests green; typecheck green. (07 §2–3)
  • H15. Living Scene customer card fakes Keep/Share/generation success with zero network calls; /studio/compose is fixture theater(DONE 2026-06-12, commit 506edff5a6 — per the fix sketch's fallback.) All card actions now report honestly (delegated neutrally when a parent wires a callback; "not wired on this surface — nothing happened" otherwise; cue evaluation stays real and says so); compose (web + mobile) opens with an explicit rehearsal register over its constant segment library/assist/0.92 pre-score. REAL keep/share wiring needs a session-produced score — follows the conductor runtime (T14) + share surface cluster (P9). 28 web + 1 mobile tests green; typechecks green. (08 §1–2)
  • H16. Admin honesty: bulk-operation "staged commit" stamps a timestamp and applies nothing; integrations registry boots unlabeled fixture keys/webhooks/connectors with fabricated "healthy" probes(DONE 2026-06-12, commit 2678dfc6c3.) Commit now DISPATCHES every non-skip plan row to per-kind executors over the real stores ('users' → session-less provisionAccount + tenant seat; 'entitlements' → persisted-plan flip); unwired kinds fail LOUD; row failures fail the op while recording honest partial commitOutcomes (new contract field). Integrations registry: production boots EMPTY; dev seeds carry an auto-dropping seedDataNotice; the dead PlaceholderDataBanner is now mounted over it. 47 store/route tests + 21 contracts specs green; typechecks green. Rosters/content/taxonomy/persona_assignments executors remain fail-loud pending their store seams (P-phase). (10 §4, §6)
  • H17. T&S operator inbox interleaves real customer reports with unlabeled fabricated cases including a fake CSAM/NCMEC record(DONE 2026-06-12, commit 5a4a79b4ec.) Every served user report now carries origin: 'live' | 'seed' (stamped at the workspace read, added to the contract schema); the admin console renders "seeded example — not a customer report" on seed rows; the seeded image-moderation case fabricating a verified NCMEC CSAM hash match is replaced with a non-CSAM graphic-violence example, with a test asserting NO seeded case may ever carry csam/ncmec signals. 7 admin + 6 BFF + 50 contracts tests green; typechecks green. (11 §6)

Phase T — Structural (spec pillars unreachable)#

Work V1.0 children first, then V1.1, then V1.2.

  • T1. /v1/oshun/* facades for Veritas and Nyx — roll-up; the structural gap is closed by a different architecture than the one this item named, and two Veritas hardening findings remain; children below. (03 §1; 01 §15)
    • Already fixed (checked 2026-09-18): Nyx, Nisaba and Metis no longer dial a downstream service — apps/oshun/bff/src/app.ts:1779-1802 binds in-process adapters by default (apps/oshun/bff/src/nyx/in-process-adapter.ts over the @oshun/domain-nyx ephemeris and the durable apps/oshun/bff/src/nyx/nyx-member-stores.ts), so the 18 Nyx operations answer without a facade.
    • Already fixed (checked 2026-09-18): the Veritas facade exists — 17 /v1/oshun/* routes in apps/veritas/api/src/interfaces/http/routes/v1/oshun-facade.ts:247-752, mounted at apps/veritas/api/src/interfaces/http/server.ts:186, over the same Postgres tables as the native routes.
    • T1.a [V1.2] Gate the Veritas facade with the service token: apps/veritas/api/src/interfaces/http/routes/v1/oshun-facade.ts takes the member id from the query (:251,329,372,585) or the body (:642,664) and checks no credential, while the Arete facade fails closed (facadeAccessCheck, apps/arete/api/src/routes/oshun-facade.ts:180-195). Require x-oshun-service-token to equal VERITAS_OSHUN_FACADE_TOKEN, refuse in production when the variable is unset, and keep development open. (03 §1) Verify: new apps/veritas/api/src/interfaces/http/routes/v1/oshun-facade-token.spec.ts — wrong token 401, production with no token configured 401, matching token 200.
    • T1.b [V1.2] Add the Veritas contract round-trip test the Tara and Arete facades have (apps/oshun/bff/src/adapters/tara-oshun-facade-contract.test.ts, arete-oshun-facade-contract.test.ts): boot the real facade router and drive every Veritas method of createDomainServiceAdapters through it. (03 §1) Verify: new apps/oshun/bff/src/adapters/veritas-oshun-facade-contract.spec.ts — all 17 operations parse through normalizeDomainServicePayload with no drift.
  • T2. Iris memory loop — roll-up; bootstrap and per-turn recall are now wired, so what remains is the consent door, the unused recall and governance engines, and three findings of report 06 that no backlog item carried; children below. (06 §1–3; added 2026-09-18: 06 §17, §20, §21)
    • Already fixed (checked 2026-09-18): the bridge is bootstrapped and closed per session — apps/oshun/bff/src/routes/assistant.ts:2094 (bootstrapMemory) and :5621 (closeMemory).
    • Already fixed (checked 2026-09-18): recall is consulted per turn — apps/oshun/bff/src/routes/assistant.ts:796 calls memoryBridge.recallRelevantMemory.
    • Already fixed (checked 2026-09-18, 06 §18): the Iris export builds a real bundle (apps/oshun/bff/src/adapters/iris-memory-adapters.ts:1010) and the DSAR export includes memory (apps/oshun/bff/src/data-export/bundle-builder.ts:82).
    • T2.a [V1.0] Build the consent door: POST /v1/iris/adapter/consents (apps/oshun/bff/src/routes/iris.ts:564) has no client; apps/oshun/web/src/app/profile/memory/memory-state.ts:259 only GETs. Add grant and deny controls per category on /profile/memory that POST with role=settings and re-hydrate, so durable memory becomes reachable. (06 §3) Verify: new apps/oshun/web/e2e/profile-memory-consent.spec.ts (chromium, workers=1) — grant a category, reload, the category reads granted from the server; deny reverses it.
    • T2.b [V1.0] Make the Iris consent store the one source of truth for the assistant: session create still derives memoryConsentGranted from profile preferences (apps/oshun/bff/src/routes/assistant.ts:2082). Read it from the Iris adapter, and have the assistant-controls toggle POST the same consent route as T2.a. (06 §20) Verify: new apps/oshun/bff/src/routes/assistant-memory-consent.spec.ts — a member with a preference flag but no Iris grant gets a session with memory off.
    • T2.c [V1.0] Apply the privacy-aware recall filter: filterIrisPrivacyAwareRecall (libs/oshun/memory-iris/src/privacy-suppression.ts) and RecallPipeline (libs/oshun/memory-iris/src/recall/pipeline.ts) have zero consumers in apps/. Route GET /v1/iris/adapter/search (apps/oshun/bff/src/routes/iris.ts:650) and the assistant recall at assistant.ts:796 through them so suppressed and sensitive categories never reach a turn. (06 §2) Verify: new apps/oshun/bff/src/routes/iris-recall-suppression.spec.ts — a fact in a suppressed category is stored, then absent from both the search route and the assistant recall summary.
    • T2.d [V1.0] Surface write conflicts instead of overwriting: wire resolveIrisMemoryWriteConflict (libs/oshun/memory-iris/src/conflict-resolution.ts) into the adapter's remember path in apps/oshun/bff/src/adapters/iris-memory-adapters.ts. (06 §2) Verify: new apps/oshun/bff/src/adapters/iris-memory-conflict.spec.ts — two contradicting facts produce a recorded conflict with both versions retrievable.
    • T2.e [V1.0] Mount InspectionStateMachine (libs/oshun/memory-iris/src/admin-inspection/state-machine.ts) behind the role=admin reads of apps/oshun/bff/src/routes/iris.ts so an operator inspection needs a reason, a time box and an audit event. (06 §2) Verify: new apps/oshun/bff/src/routes/admin-iris-inspection.spec.ts — a read without an open inspection is refused; an expired inspection is refused; each granted read writes one audit event.
    • T2.f [V1.0] Census the remaining unused memory engines and decide each in code: InferenceEngine (inference/policy.ts), ActorHandleNamespace (multi-actor/namespace.ts) and assessIrisProfileMemoryDecay (retention-compaction.ts) under libs/oshun/memory-iris/src/. For each either wire one real caller in the BFF or mark the module @deprecated with a dated note naming the release it waits for. (06 §2) Verify: grep -rn for each symbol over apps/ returns a caller, or the module header carries the dated deferral; no third state.
    • T2.g [V1.0] Emit and read continuation tokens: the routes in apps/oshun/bff/src/routes/iris.ts (write, read, resume, notices over @oshun/memory-iris/continuity/protocol) have zero web or mobile callers. Write a checkpoint from the Tara sit player and the Nisaba reader, and render GET /v1/iris/continuation-tokens as Home continuation cards. (06 §17) Verify: new apps/oshun/web/e2e/continuation-token-resume.spec.ts — pause a sit, open Home in a second context, the continuation card resumes at the saved position.
    • T2.h [V1.1] Mobile memory controls: apps/oshun/mobile has only the indicator src/components/AssistantMemoryState.tsx and no screen that lists, forgets or consents. Build it over the same routes as web (composes with H4.b, which removes the fixture). (06 §21) Verify: new apps/oshun/mobile/src/memory/MobileMemoryControls.spec.tsx (jest) — forget calls DELETE /v1/iris/adapter/memory/:id; consent calls the POST of T2.a.
  • T3. Persona governance — roll-up: one canonical persona id space, policy packs and lifecycle bound to it, tone bands enforced, and one persona-surface finding of report 06 that no backlog item carried; children below. (06 §10–11; added 2026-09-18: 06 §16)
    • Re-checked 2026-09-18: all five id universes are still disjoint and the tone-band functions still have no caller outside libs/oshun/persona-policy-lilith.
    • Already fixed (checked 2026-09-18, 06 §15): /profile/persona and its route agree — GET /v1/persona/voice (apps/oshun/bff/src/routes/personas-consumer.ts:144-185) serves providerId, voiceKind, defaultLocale and a null previewUrl, which is what apps/oshun/web/src/app/profile/persona/page.tsx renders.
    • T3.a [V1.0] Key Lilith policy packs by launch-roster ids: apps/oshun/bff/src/adapters/lilith-persona-policy-adapters.ts:37,159,257 knows only tara-contemplative-guide and veritas-stoic-scholar, so checkPersonaTopic('lotus-mindful', …) finds no pack. Give every persona in libs/oshun/persona-registry/src/launch-roster.ts a pack. (06 §10) Verify: new apps/oshun/bff/src/adapters/lilith-persona-policy-roster.spec.ts — iterates the roster and asserts a pack resolves for every id.
    • T3.b [V1.0] Key the admin lifecycle store by roster ids and make it bite: apps/oshun/bff/src/admin/persona-lifecycle-store.ts:83-84 governs persona-zen-guide and persona-stoic-guide, which no member can select. Re-key to the roster, and make a retired or deprecated persona disappear from apps/oshun/bff/src/routes/personas-consumer.ts browse and select. (06 §10) Verify: extend apps/oshun/bff/src/routes/personas-consumer.test.ts — retire a roster persona through the admin store, browse omits it and select answers 409.
    • T3.c [V1.0] Replace the panel's private persona catalog: apps/oshun/web/src/design-system/assistant-persona-switching.ts:9-145 (oshun-navigator, zen-guide, stoic-scholar, …) is persisted client-side and the panel never calls /v1/personas/select. Browse and select through the server, and drop the personaHandoff body field the BFF never reads. (06 §10) Verify: extend apps/oshun/web/src/components/assistant/__tests__/AssistantPanel.test.tsx (or a new .spec.tsx beside it) — switching persona issues one /v1/personas/select call and the transcript label is the server's.
    • T3.d [V1.0] Give the Psyche runtime a roster default: apps/oshun/bff/src/adapters/psyche-embodiment-adapters.ts:288,328,339 hardcodes "Guide One". Resolve the member's selected roster persona, or the roster default. (06 §10) Verify: new apps/oshun/bff/src/adapters/psyche-persona-default.spec.ts — the session persona id is a member of the launch roster.
    • T3.e [V1.0] Enforce tone bands on generation: call enforceIsisGenerationDispatchTone (libs/oshun/persona-policy-lilith/src/tone-bands/enforcement.ts) in the Isis admit path (apps/oshun/bff/src/generation/jobs-route.ts), deriving the active band from surface and persona role with resolveEffectiveToneBand. (06 §11) Verify: new apps/oshun/bff/src/generation/tone-band-admit.spec.ts — a contemplative-strict request above the band's audacity cap is refused with a typed reason.
    • T3.f [V1.0] Enforce tone bands on sharing: call enforceShareControlTone in apps/oshun/bff/src/routes/library-sharing.ts and the Living Scenes share create in apps/oshun/bff/src/routes/living-scenes.ts. (06 §11) Verify: new apps/oshun/bff/src/routes/share-tone-band.spec.ts — a band that caps share reach to private refuses a public link.
    • T3.g [V1.0] Enforce tone bands in the Living Scenes conductor with enforceLivingScenesConductorTone. 2026-09-18: there is no conductor runtime to call it from until T14.c is decided. (06 §11) Verify: a conductor-path spec asserts the band's motion cap clamps an accepted cue. blocked:upstream
    • T3.h [V1.0] Enforce lineage policy at composition time: checkPublishLineage and emitLineageBinding (libs/oshun/persona-policy-lilith/src/sacred-symbols/lineage-binding.ts) have no caller. Call them where a composed scene or offering is kept (apps/oshun/bff/src/routes/living-scenes.ts keep route). (06 §11) Verify: new apps/oshun/bff/src/routes/living-scenes-lineage.spec.ts — a composition using a lineage-bound symbol without its binding is refused.
    • T3.i [V1.0] Make the selected voice pack change the voice: speakText builds a bare SpeechSynthesisUtterance (apps/oshun/web/src/components/assistant/AssistantPanel.tsx:4369) and never assigns a voice, rate or pitch. Map each roster persona to a speechSynthesis.getVoices() profile as the V1.0 binding; the hosted TTS provider stays a deploy-time setting. (06 §16) Verify: new apps/oshun/web/src/components/assistant/__tests__/assistant-voice-binding.spec.ts — two personas produce utterances with different voice or rate.
  • T4. Agentic approval→execution disconnect — roll-up; the executor was rebuilt as a governed orchestrator, so transcripts and in-run budget debits are done and the approval queue and the server budget registry remain; children below. (09 F4–F6)
    • Already fixed (checked 2026-09-18): executed envelopes now carry transcripts — libs/oshun/agentic-studio/src/runs/governed-orchestrator.ts:578-903 appends every call to toolCalls, and apps/oshun/bff/src/agentic/runs-route.ts:662-664 persists the run with its audit events.
    • Already fixed (checked 2026-09-18): meters are debited within a run — governed-orchestrator.ts:269 threads consumeBudget.
    • T4.a [V1.0] Connect approval to execution: approve() moves a run to queued (apps/oshun/bff/src/agentic/run-lifecycle-store.ts:730) and nothing consumes queued runs, while the execute route answers 409 run_already_active for any stored non-terminal run (apps/oshun/bff/src/agentic/runs-route.ts:497-503), so an approved run can never execute and "Run now" ends at "Released to Lilith's composer" (apps/oshun/web/src/components/lilith/AreteReviewDraftRun.tsx:54-55). Let execute accept a run whose lifecycle status is queued, refuse a gated run that is not, and stamp the result back onto the lifecycle record. (09 F4) Verify: extend apps/oshun/bff/src/agentic/runs-route.test.ts — submit, approve, execute reaches completed; executing a gated run that is still awaiting_approval answers 409.
    • T4.b [V1.0] Bind a server budget registry: resolveBudgets is an optional hook (apps/oshun/bff/src/agentic/runs-route.ts:404,648) that apps/oshun/bff/src/server.ts:3807 never passes, and the route still merges body.budgets (:649). Add a Postgres-backed per-tenant, per-agent, per-tool registry, pass it as resolveBudgets, persist meters across runs, and stop accepting client envelopes in production. (09 F5) Verify: extend apps/oshun/bff/src/agentic/runs-route.test.ts — a tenant cap of N units refuses the run that would cross it across two separate requests, with no budgets in either body.
  • T5. API keys accepted on real requests — roll-up; a key can now authenticate, but only against a probe route and with no rate limit; children below. (10 §1; 13 F8)
    • Already fixed (checked 2026-09-18): a request can present a key — GET /v1/integrations/auth-probe (apps/oshun/bff/src/routes/admin-integrations-registry.ts:46-110,625-629) calls store.authenticateApiKey and enforces tenant, scope, IP allowlist and expiry, and last-used now moves.
    • T5.a [V1.0] Extract the probe's key check into a reusable preHandler in libs/shared/bff-kit/src/ (createApiKeyAuthPreHandler, mapping key scopes onto request.authContext) and mount it on the first real partner-facing route group; the SCIM routes of P16.a are the intended first consumer. Note apps/oshun/bff/src/middleware/authz.ts is now a re-export shim; the middleware lives in libs/shared/bff-kit/src/authz.ts. (10 §1) Verify: new libs/shared/bff-kit/src/api-key-auth.spec.ts — missing key 401, wrong scope 403, revoked key 401, valid key populates authContext.tenantId.
    • T5.b [V1.0] Enforce the stored rate limit: evaluateApiKeyRateLimit (libs/oshun/tenant-console/src/integrations/api-keys.ts:85) has no caller, so rateLimitPerMinute is configuration nothing reads. Call it in the preHandler of T5.a with the window kept in the existing abuse-protection store and answer 429 with retry-after. (10 §1) Verify: same spec as T5.a — request N+1 inside a minute answers 429 with a positive retry-after.
  • T6. Outbound webhooks — roll-up: the delivery executor exists and nothing constructs an event or keeps a secret across a restart; children below. (10 §2, §11; 13 F7)
    • Re-checked 2026-09-18: executeWebhookDelivery and createWebhookDeliveryExecutor still have no caller under apps/oshun/bff/src.
    • T6.a [V1.0] Wire a fetch-based WebhookHttpTransport and createWebhookDeliveryExecutor (libs/oshun/tenant-console/src/integrations/webhook-delivery-execution.ts) in the BFF composition root, with the retry loop on an interval timer like the reminder worker and dead-letter rows readable by the admin panel. (10 §2) Verify: new apps/oshun/bff/src/admin/webhook-delivery-runtime.spec.ts — a local HTTP listener receives one signed POST whose signature verifies; a 500 response is retried on the backoff schedule and then dead-lettered.
    • T6.b [V1.0] Persist signing secrets: they live in an in-memory Map (apps/oshun/bff/src/admin/admin-integrations-registry-store.ts:168,351,398,515) excluded from the persisted state, so every pre-restart subscription fails SIGNING_SECRET_UNAVAILABLE. Store them wrapped with AES-GCM under an env-provided key in the durable state; with no key configured the store refuses to create a subscription rather than keep a plaintext copy. (10 §2, §11) Verify: new apps/oshun/bff/src/admin/webhook-secret-durability.spec.ts — export state, import into a fresh store, the simulator signs successfully; the persisted JSON contains no raw secret.
    • T6.c [V1.0] Publish real events: construct a WebhookEvent and call subscriptionsForEvent from the admin audit choke point (adminAuditEventsStore.record) for takedown, moderation decision, generation release and payment settlement. (10 §2) Verify: extend the T6.a spec — settling a test invoice delivers one payment.settled event to a subscribed listener and none to an unsubscribed topic.
  • T7. Roles and tenant auth policy feed live authorization — roll-up: the roles engine and the auth-challenge engine each have one demo-page consumer and the running system authorizes on literal scope strings; children below. (10 §3, §8)
    • Re-checked 2026-09-18: effectiveCapabilities, runDryRun and evaluateAuthChallenge are still consumed only by apps/oshun/tenant-admin/src/app/roles/page.tsx and apps/oshun/tenant-admin/src/app/identity/page.tsx:115; the live check is scopes.includes(...) in libs/shared/bff-kit/src/authz.ts:145.
    • T7.a [V1.0] Add a Postgres-backed role-template and role-assignment store with /v1/admin/role-templates CRUD over libs/oshun/tenant-console/src/roles/roles.ts (validateTemplateWithinApprovedBounds, transitionAssignment). (10 §3) Verify: new apps/oshun/bff/src/routes/admin-role-templates.spec.ts — a template outside approved bounds is refused; an assignment expires on its date; rows survive a store re-open.
    • T7.b [V1.0] Resolve roles into scopes at session mint: when a token is issued for a tenant member, expand their assignments with effectiveCapabilities into the scope set, so editing a template changes what the next session can do. (10 §3) Verify: new apps/oshun/bff/src/auth/role-scope-expansion.spec.ts — removing a capability from a template removes the matching scope from the next minted token and the route answers 403.
    • T7.c [V1.0] Expose the dry-run harness: a route over runDryRun that reports what a proposed template change would grant or revoke for named members, without applying it. (10 §3) Verify: same spec file as T7.a — the dry-run response lists the revoked capability and the store is unchanged.
    • T7.d [V1.0] Add tenantAuthPolicyStore (MFA requirement, step-up triggers, IP allowlist, session refresh) and evaluate it with evaluateAuthChallenge (libs/oshun/tenant-console/src/identity/federation-and-auth-policy.ts:76) in the SSO callback (apps/oshun/bff/src/auth/sso-login-routes.ts) and in createAuthPreHandler for step-up routes, answering 403 step_up_required. (10 §8) Verify: new apps/oshun/bff/src/auth/tenant-auth-policy.spec.ts — a login from outside the tenant's allowlist is refused; a step-up route without a fresh factor answers step_up_required.
  • T8. Billing and support runtime — roll-up; the billing library is no longer orphaned (the Stripe rail uses it), and dunning, crypto renewal, support cases and the trust-and-safety engines remain; children below. (11 §2–5)
    • Already fixed (checked 2026-09-18): @oshun/billing-support has runtime consumers — the Stripe fiat rail and plan projection (apps/oshun/bff/src/payments/fiat-routes.ts, fiat-stripe-store.ts, apps/oshun/bff/src/billing-support/plan-sync.ts, billing-store.ts) and the trial, family, cancel-flow and scholar-verification routes. Stripe subscriptions carry real periods (fiat-routes.ts:186-290).
    • 2026-09-18: this item was written before V1/TODOS.md §23.2 added Stripe as a second rail; T8.b is the crypto-rail half only.
    • T8.a [V1.0] Wire dunning: recordPaymentFailure, advanceDunningStage and degradedEntitlementOnLapse (libs/oshun/billing-support/src/dunning/dunning.ts:83,115,155) have no app caller. Drive them from the Stripe invoice.payment_failed and subscription-lapse events in apps/oshun/bff/src/payments/fiat-routes.ts, and degrade the plan through apps/oshun/bff/src/billing-support/plan-sync.ts when the grace window ends. (11 §2) Verify: extend apps/oshun/bff/src/payments/fiat-routes.test.ts — a failed-payment event opens a grace window, a later success clears it, expiry projects the plan to free.
    • T8.b [V1.0] Give the crypto rail a billing period: settlement still calls updatePlanDurably with no expiry (apps/oshun/bff/src/payments/settlement-route.ts:302), so one settled invoice is a lifetime plan. Stamp a period end at settlement, add a renewal worker that issues the next invoice through apps/oshun/bff/src/payments/quote-builder.ts, feed the offsets of libs/oshun/payments-bridge/src/customer-surface/reminder-cadence.ts into the reminder dispatcher, and apply the same lapse path as T8.a. (11 §3) Verify: new apps/oshun/bff/src/payments/crypto-renewal-worker.spec.ts — with a fixed clock, a plan past its period end with no settled renewal projects to free; a settled renewal extends the period.
    • T8.c [V1.0] Build support-case intake: no /v1/support* route exists and the operator workspace triages seeded cases. Add POST /v1/support/cases (session-stamped) over a Postgres store, assign queue and SLA with routeCase and slaForCase (libs/oshun/billing-support/src/support-cases/support-cases.ts:88,122), add an admin queue read and decide pair, and replace the mailto: on apps/oshun/web/src/app/billing/page.tsx with the intake. (11 §4) Verify: new apps/oshun/bff/src/routes/support-cases.spec.ts — a member's case lands in the routed queue with an SLA deadline, another member cannot read it, evaluateCaseSla flags it after the deadline.
    • T8.d [V1.0] Stamp severity and SLA on moderation work: call classifySeverity and slaFor from @oshun/trust-safety when an item enters apps/oshun/bff/src/moderation/moderation-store.ts or the abuse-report store, and expose the breach state on the admin read. (11 §5) Verify: new apps/oshun/bff/src/moderation/moderation-severity.spec.ts — a CSAM-class report is P0 with the P0 triage deadline; an overdue item reads breached.
    • T8.e [V1.0] Run the repeat-offender ladder: call applyOffense on every upheld decision and persist the per-user counter in Postgres; the resulting restriction is what P12.a enforces. (11 §5) Verify: same spec file as T8.d — three upheld decisions move a user from warn to restrict to suspend.
    • T8.f [V1.0] Add GET /v1/admin/safety/dashboard over buildSafetyDashboard, fed from the real moderation and abuse-report stores. (11 §5) Verify: new apps/oshun/bff/src/routes/admin-safety-dashboard.spec.ts — counts equal the rows in the stores; an empty deployment reads zeros.
  • T9. Telegram bot runway — roll-up; the code is done and only the owner's live registration is left; children below. (12 §1)
    • 2026-09-18: every code finding re-checked and found fixed — the BFF webhook delivers through deliverTelegramResponses (apps/oshun/bff/src/routes/telegram.ts:3,317-319); @oshun/telegram-bot is declared a library with no entrypoint (.github/workflows/deploy-hetzner.yml:21-23, infra/hetzner/README.md:43-45); both deployment planes are held in step by apps/oshun/bff/src/telegram/deployment-parity.spec.ts; webhook registration is scripted in infra/hetzner/scripts/telegram-webhook.sh. Verify and close once T9.a is done.
    • Owner decision 2026-09-18: Telegram is Eve's first live channel.
    • T9.a [V1.0] Go live: the owner creates the bot with BotFather, puts OSHUN_TELEGRAM_BOT_TOKEN and OSHUN_TELEGRAM_WEBHOOK_SECRET in the Hetzner stack .env, and runs infra/hetzner/scripts/telegram-webhook.sh register staging. 2026-09-18: the bot token is the owner's credential; no agent can mint one. (12 §1, §12) Verify: telegram-webhook.sh status staging shows the staging URL with no last error, and a /start sent to the bot is answered in the chat. blocked:external
  • T10. Analytics loop — roll-up: the shared transport is real and the live telemetry modules still do not use it; children below. (13 F1–F2)
    • Re-checked 2026-09-18: createSharedAnalyticsSinks is used only by taraAnalytics.ts, studioWorkspaceMountTelemetry.ts and transport.ts under apps/oshun/web/src/analytics/; initOshunAnalytics (libs/oshun/analytics/src/singleton.ts:19) has no caller; eleven modules still carry the "Temporary local sink" comment.
    • T10.a [V1.0] Initialise the singleton at shell boot: call initOshunAnalytics({ context, sinks: createSharedAnalyticsSinks() }) once in apps/oshun/web/src/lib/providers.tsx, beside ExperimentationBootstrap. (13 F2) Verify: new apps/oshun/web/src/analytics/analytics-singleton-boot.spec.tsx — after the provider mounts, getOshunAnalytics().track(...) reaches the mocked POST /v1/analytics/events.
    • T10.b [V1.0] Flip the live console-sink modules to the shared transport: publicAuthFunnelTelemetry, recommendationTelemetry, pwaLifecycleTelemetry, shellNavigationTelemetry, searchResultTelemetry, homeContinuationTelemetry, domainLaunchTelemetry, librarySaveTelemetry, activityReentryTelemetry, notificationCenterTelemetry and nisabaStudyTelemetry in apps/oshun/web/src/analytics/. The sink must honour the analytics consent of P13.d. (13 F1, F16) Verify: grep -rln "Temporary local sink" apps/oshun/web/src/analytics prints nothing; one spec per module family asserts an event reaches the mocked ingest.
    • T10.c [V1.0] Decide taraAnalytics.ts: it is wired to the real transport and has no importer. Call useTaraAnalytics() from the sit player and the Tara onboarding, or delete the module and its tests. (13 F1) Verify: grep -rn taraAnalytics apps/oshun/web/src --include=*.tsx shows a component importer, or the file is gone.
  • T11. Metis adaptive-learning spine — roll-up; Metis ships in V1.2, so every child is V1.2; children below. (04 §5)
    • Partly fixed (checked 2026-09-18): @metis/gradebook now has a runtime consumer — apps/oshun/bff/src/metis/gradebook-store.ts (GradebookEmitter), used by apps/oshun/bff/src/routes/metis-integrity.ts:21. @metis/assessment, @metis/learning and @metis/tutoring still have no consumer under apps/oshun or apps/metis.
    • T11.a [V1.2] Drive the adaptive loop from the tutor-memory route: feed each turn of POST /v1/metis/tutor-session-memories (apps/oshun/bff/src/routes/metis-tutor-memory.ts) into libs/metis/tutoring/src/loop/adaptive-loop.ts with IRT item selection from @metis/assessment, and return the next item. (04 §5) Verify: new apps/oshun/bff/src/routes/metis-adaptive-loop.spec.ts — a run of correct answers raises the selected item difficulty; the P95 of the selection step is under 600 ms on the dev server.
    • T11.b [V1.2] Persist mastery transitions: record every band change from libs/metis/learning/src/mastery/transitions.ts in Postgres with the evidence that caused it. (04 §5) Verify: same spec file as T11.a — a transition row exists after the band changes and none when it does not.
    • T11.c [V1.2] Emit gradebook entries on assignment completion through the file-export and xAPI adapters of @metis/gradebook; LTI-AGS and Caliper stay behind their not_configured seams until an LMS endpoint is provided. (04 §5) Verify: extend the specs beside apps/oshun/bff/src/metis/gradebook-store.ts — completing an assignment yields one entry and one xAPI statement; re-completion is idempotent.
    • T11.d [V1.2] Bring-your-own-model safety harness: add a tenant model-endpoint registry and put libs/shared/inbound-integrations/src/byom.ts on the call path with a per-endpoint kill switch. (04 §5) Verify: new apps/oshun/bff/src/metis/byom-model-harness.spec.ts — an endpoint that fails the sandbox checks is never called; an armed kill switch refuses the next call.
  • T12. Tara and Arete data planes — roll-up: one system of record per fact, the facade being the architecture chosen in C1; children below. (02 §10–11)
    • Re-checked 2026-09-18: both planes still serve the same facts. S1 closed the identity half of 02 §11.
    • T12.a [V1.0] Tara progress: a sit completion is written to the goal3 store (POST /v1/tara/sittings/:id/complete, apps/oshun/bff/src/routes/domain-stubs.ts:1634, read by /v1/tara/analytics at :1699) while /v1/tara/{streak,history,continue} read the facade (apps/oshun/bff/src/routes/tara.ts:175-250). Forward the completion to the facade's progress write and keep goal3 as a write-through cache, or read both from one store. (02 §10) Verify: new apps/oshun/bff/src/routes/tara-single-plane.spec.ts — completing one sitting changes /v1/tara/history, /v1/tara/streak and /v1/tara/analytics together.
    • T12.b [V1.0] Arete streak: /v1/arete/streak computes from goal3 check-ins (apps/oshun/bff/src/routes/domain-stubs.ts:1731) and the reminder worker computes at-risk streaks from the facade (apps/oshun/bff/src/server.ts:3483). Make both read one source. (02 §10) Verify: new apps/oshun/bff/src/routes/arete-single-streak.spec.ts — after a check-in the route and the worker's producer report the same streak.
    • T12.c [V1.0] Habit completions: two write paths, the check-in at apps/oshun/bff/src/routes/domain-stubs.ts:1928 and logHabitCompletion at apps/oshun/bff/src/routes/routines.ts:76. Converge them and record the boundary in each route header. (02 §10) Verify: same spec file as T12.b — a completion logged through the routines route counts in /v1/arete/streak.
  • T13. Collections — roll-up: one durable collection system with a share link that shows something; children below. (05 §collections/share; 01 §21 read-back)
    • Already fixed (checked 2026-09-18): share policies and links are durable — wireDurableLibrarySharePolicies (apps/oshun/bff/src/library/share-policy-store.ts:524-529, wired at apps/oshun/bff/src/server.ts:1903).
    • Already fixed (checked 2026-09-18): the link routes somewhere — apps/oshun/web/src/app/share/[token]/page.tsx exists and calls the resolve endpoint (:100). What it can show is T13.b.
    • T13.a [V1.0] Back the dashboard's collections with the server: apps/oshun/web/src/components/library/LibraryDashboard.tsx:114-180 ships four fixture collections and keeps all collection CRUD in localStorage (oshun.library.collections.v1), never calling /v1/library/collections. Reuse the saved-items sync pattern of apps/oshun/web/src/lib/library/webLibraryStore.ts; drop the fixtures or gate them to signed-out guests. (05 §collections) Verify: new apps/oshun/web/e2e/library-collections-one-system.spec.ts — a collection created on /library/collections/new appears in the dashboard manager, and one created in the dashboard appears on /library/collections after reload.
    • T13.b [V1.0] Make a granted share carry content: GET /v1/library/shared/:collectionId returns only {collectionId, granted, tier, via} (apps/oshun/bff/src/routes/library-sharing.ts:231-264). On grant include the collection label and its items, filtered by the granted tier. (05 §share) Verify: new apps/oshun/bff/src/routes/library-sharing-content.spec.ts — a view-tier token returns label and items; a revoked token returns neither.
    • T13.c [V1.0] Check ownership before the first policy: setVisibility and createShareLink make the first caller the owner of any collectionId (apps/oshun/bff/src/library/share-policy-store.ts:232-244) without consulting the collections store. Refuse unless the caller owns the collection. (05 §share) Verify: same spec file as T13.b — a second member cannot create a policy for the first member's collection id.
  • T14. Living Scenes rendering and conductor — roll-up: score-driven rendering first, honest cue handling, and an owner decision on conductor scope; children below. (08 §3–4)
    • Re-checked 2026-09-18: all findings still hold.
    • T14.a [V1.0] Render from the score: the share path calls renderLivingSceneSegmentToPng({ tone: 'contemplative', … }) with nothing taken from artifact.score (apps/oshun/bff/src/routes/living-scenes.ts:1380-1385), so every scene is the same gradient. Derive per-segment tone, frame count from durationSeconds and phase from perSegmentSeedsHex, and concatenate the segment runs into the APNG. (08 §3) Verify: new apps/oshun/bff/src/routes/living-scenes-score-render.spec.ts — two different scores produce different media hashes; the same score renders byte-identical twice.
    • T14.b [V1.0] Make cue handling honest and safe: the accepted-cue notice says "cue applied at the next scene boundary" (libs/yemaya/living-scenes-runtime/src/customer-card/customer-card.ts:407) although nothing applies it, and decideCrisisAction (libs/yemaya/living-scenes-runtime/src/cues/crisis-classifier.ts) is never run. Say "cue accepted", and classify every voice or text cue before acceptance. (08 §4) Verify: extend the customer-card specs in libs/yemaya/living-scenes-runtime/src/customer-card/ — the notice no longer says applied; a crisis-class cue is refused with the crisis action.
    • T14.c [V1.0] Decide conductor scope for V1.0: minimal cue-to-segment application, or defer the streaming conductor (libs/yemaya/living-scenes-runtime/src/conductor/conductor.ts, zero callers) to a later release. 2026-09-18: the report recommends deferral; the owner has not decided. (08 §4) Verify: the decision is recorded in V1/TODOS.md with its date, and T3.g is re-scoped to match. blocked:governance
  • T15. Consent ledger customer surface — roll-up: the real ledger has no door, and voice and avatar consent cannot be granted in the product; children below. (11 §9)
    • Re-checked 2026-09-18: apps/oshun/web/src/app/profile/data/page.tsx:57 still reads the fixture route /v1/data-rights/consent, and nothing under apps/oshun/web/src calls /v1/consent.
    • T15.a [V1.0] Point /profile/data at the real ledger: read GET /v1/consent (apps/oshun/bff/src/routes/consent.ts) and add grant and withdraw controls per flow that post to the per-flow verbs. (11 §9) Verify: new apps/oshun/web/e2e/profile-consent-ledger.spec.ts — withdraw the analytics flow, reload, it reads withdrawn; the audit read lists the transition.
    • T15.b [V1.0] Mount a first-use consent prompt on the persona voice and avatar picker, since apps/oshun/bff/src/routes/personas-consumer.ts already refuses those modalities without a granted flow. (11 §9) Verify: extend apps/oshun/bff/src/routes/personas-consumer.test.ts and add a web spec — selecting a voice persona without consent shows the prompt; granting it makes the select succeed.
    • T15.c [V1.0] Delete the fixture route /v1/data-rights/consent from apps/oshun/bff/src/routes/domain-stubs.ts once T15.a lands. 2026-09-18: depends on T15.a. (11 §9) Verify: grep -rn "data-rights/consent" apps/oshun returns nothing. blocked:upstream

Phase P — P1 wiring register (work after S/H/T; full detail in reports)#

Work V1.0 children first, then V1.1, then V1.2.

  • P1. Channel-binding verification codes — roll-up: the bind route never sends the code, so no verified binding can exist and every email, SMS and WhatsApp reminder is suppressed; children below. (12 §3)
    • Re-checked 2026-09-18: apps/oshun/bff/src/routes/channel-bindings.ts:133-142 still reports missing-config in production and preview elsewhere without attempting a send. This is the same defect as the verification-code item of docs/audits/V1_REMAINING_WORK_IMPLEMENTATION_CHECKLIST_2026-06-08.md (its §2.1 note near line 925); close both together.
    • P1.a [V1.0] Send the email code: in apps/oshun/bff/src/routes/channel-bindings.ts dispatch the minted code through deliverWithEnvProviders (libs/oshun/messaging-channels/src/provider-config-env.ts:231), the path the signup verification already uses (apps/oshun/bff/src/auth/verification-email-sender.ts). Report the transport's real outcome; keep missing-config only when the environment has no email provider. (12 §3) Verify: new apps/oshun/bff/src/routes/channel-bindings-delivery.spec.ts with the SMTP provider pointed at local Mailpit (:1025, inbox API on :8025) — binding an address puts one message containing the code in Mailpit, and verifying with it yields status: 'verified'.
    • P1.b [V1.0] Send the SMS and WhatsApp codes through the same call, with a transport double at the provider boundary in tests; with no Twilio or Meta configuration the route answers missing-config and stores nothing it would claim as sent. (12 §3) Verify: same spec file as P1.a — the double receives one send per bind; with the env cleared the response is missing-config and the double is never called.
    • P1.c [V1.0] Prove one live SMS and one live WhatsApp verification. 2026-09-18: needs the owner's Twilio and Meta Cloud credentials in ~/.config/oshun/env-master.env; none exist there. (12 §12) Verify: a real handset receives the code and the binding reads verified. blocked:external
  • P2. Notification preferences the dispatcher never reads — roll-up: quiet hours, master push, per-domain and per-kind toggles are stored and not enforced, and the planner defaults to opted-in; children below. (12 §4–5)
    • Re-checked 2026-09-18: all three findings still hold.
    • P2.a [V1.0] Make one store authoritative for quiet hours: Telegram /quiet writes notificationPreferencesStore (apps/oshun/bff/src/telegram/effects-adapter.ts:60-66) and the preferences PATCH writes the same place, but the dispatcher and the preferences read both overwrite enabled with the consumer-profile flag (apps/oshun/bff/src/reminders/reminders-route.ts:668-671, apps/oshun/bff/src/routes/notifications-preferences.ts:121-123,305). Pick one, and have every writer and reader use it. (12 §4) Verify: extend apps/oshun/bff/src/reminders/reminders-route.test.ts — after a Telegram /quiet a reminder inside the window is suppressed; after the PATCH with enabled: false it is delivered.
    • P2.b [V1.0] Enforce the send-side toggles: enrichReminderWithLivePolicy (apps/oshun/bff/src/reminders/reminders-route.ts:662-692) reads quiet hours, bindings and the crisis frame and nothing else. Drop userOptIns to empty when the member's master pushEnabled is off or the reminder's domain has modalities.push or kinds.reminder off (apps/oshun/bff/src/notifications/preferences-store.ts). (12 §5) Verify: same test file — with push off a push reminder is suppressed and an in-app one is still delivered.
    • P2.c [V1.0] Default external channels to off: toChannelSet in libs/oshun/messaging-channels/src/v3-session-reminders.ts:308-313 treats a missing userOptIns as opted in. For every channel except in-app, absence means suppress. (12 §5) Verify: extend libs/oshun/messaging-channels/src/v3-session-reminders.test.ts — a plan with no opt-ins dispatches in-app only.
  • P3. WhatsApp and FCM delivery legs — roll-up; message content now reaches WhatsApp, and the template registry, status ingestion and the FCM token remain; children below. (12 §6–7)
    • Already fixed (checked 2026-09-18): WhatsApp sends carry the body — libs/oshun/messaging-channels/src/transports.ts:358-366 adds template components, fed by delivery.ts:533.
    • P3.a [V1.0] Route WhatsApp sends through planWhatsAppTemplateDelivery (libs/oshun/messaging-channels/src/whatsapp/index.ts): per-locale and per-use-case template approval, the 24-hour session window and the cost-ledger entry, replacing the single global OSHUN_WHATSAPP_TEMPLATE. (12 §6) Verify: extend libs/oshun/messaging-channels/src/whatsapp/whatsapp.test.ts and delivery.test.ts — an unapproved locale is refused before any HTTP call; an open session window selects the session path.
    • P3.b [V1.0] Ingest WhatsApp delivery statuses: add POST /v1/webhooks/whatsapp in the BFF feeding ingestWhatsAppStatusWebhook into a suppression store the dispatcher consults, with Meta's signature check failing closed when no app secret is set. (12 §6) Verify: new apps/oshun/bff/src/routes/whatsapp-status-webhook.spec.ts — an opted_out status suppresses the next send to that number; a bad signature answers 401.
    • P3.c [V1.0] Mint the FCM token instead of reading a one-hour one: libs/oshun/messaging-channels/src/provider-config-env.ts:106 takes OSHUN_FCM_ACCESS_TOKEN from the environment. Accept a service-account JSON, sign the JWT-bearer grant with node:crypto, cache the access token and refresh before expiry, mirroring the APNs JWT in transports.ts. (12 §7) Verify: extend libs/oshun/messaging-channels/src/transports.test.ts — with a fixed clock the second send inside the hour reuses the token and a send after expiry mints a new one; the token endpoint is a test double.
    • P3.d [V1.1] Prove one live FCM push and one live WhatsApp template send. 2026-09-18: needs the owner's Firebase service account and Meta Cloud credentials; the FCM leg also needs the native app of V1.1. (12 §12) Verify: a device receives the push and a handset receives the template message with the session title in it. blocked:external
  • P4. In-app reminder inbox has no reader — roll-up: reminders deliver into a store no member can see (composes with H3); children below. (12 §8)
    • Re-checked 2026-09-18: nothing under apps/oshun/web/src or apps/oshun/mobile/src calls /v1/reminders/inbox, and apps/oshun/bff/src/routes/notifications.ts does not merge it.
    • P4.a [V1.0] Merge readReminderInbox(userId) into the /v1/notifications payload as kind reminder, with the domain threaded through the dispatcher (the gap H3 recorded) and read state through the existing read-state store, so apps/oshun/web/src/components/NotificationsCenterPanel.tsx shows them with no client change beyond the new kind. (12 §8) Verify: new apps/oshun/bff/src/routes/notifications-reminder-inbox.spec.ts — a produced reminder appears once in /v1/notifications for its recipient only, and marking it read persists.
    • P4.b [V1.1] Show the same items on the mobile activity tab once H4.a has removed its fixtures. 2026-09-18: depends on H4.a. (12 §8) Verify: the H4.a spec gains a case where a reminder row renders from the BFF payload. blocked:upstream
  • P5. Nyx save, follow and remind — roll-up; the delivery worker now reads a durable store, and the route members write through still does not; children below. (03 §5)
    • Partly fixed (checked 2026-09-18): the reminder worker reads app.domainAdapters.nyx.getEventReminders (apps/oshun/bff/src/server.ts:3490), which is now the in-process adapter over the durable apps/oshun/bff/src/nyx/nyx-member-stores.ts.
    • P5.a [V1.0] Make the member's writes land where the worker reads: PUT /v1/nyx/event-actions/:eventId (apps/oshun/bff/src/routes/nyx.ts:803-818) uses the module-level Map in apps/oshun/bff/src/nyx/event-action-state-store.ts:83, and in production the store is null so the route answers 503 (nyx.ts:263-264). Write saves, follows and reminder routes through to nyxMemberStateStore (or make it the single authority) so the feature works in production and survives a restart. (03 §5) Verify: new apps/oshun/bff/src/routes/nyx-event-actions-durable.spec.ts — a reminder set through the PUT is returned by getEventReminders for that member, is absent for another member, and is present after the store is re-opened.
  • P6. Veritas retraction cascade and editorial engines — roll-up; Veritas ships in V1.2, so every child is V1.2; children below. (03 §6–8)
    • Already fixed (checked 2026-09-18): USE_MOCK_ADAPTERS=true is refused in production — assertProductionDomainAdapterSafety (apps/oshun/bff/src/adapters/domain-adapter-runtime-safety.ts:11-29), called first in createDefaultDomainAdapters (apps/oshun/bff/src/app.ts:1763).
    • P6.a [V1.2] Give the cascade a customer read path: /v1/veritas/drift and /v1/veritas/retractions are guardedFixtureRoutes (apps/oshun/bff/src/routes/domain-stubs.ts:2628-2631). Replace them with reads over the cascade records written by apps/oshun/bff/src/veritas/retraction-cascade-runtime.ts, and show a banner on the Veritas briefing and on saved items a cascade touches. (03 §6) Verify: new apps/oshun/bff/src/routes/veritas-retraction-read.spec.ts — after a retraction is posted the read returns it with its scope; before, it returns an honest empty list in production mode.
    • P6.b [V1.2] Compute credibility instead of passing it through: derive credibilityTier from computeSourceQualityComposite and bandFromComposite (libs/oshun/domain-veritas/src/source-quality/) over the real source records the facade reads. (03 §7) Verify: new spec beside apps/veritas/api/src/interfaces/http/routes/v1/oshun-facade.ts — a source with recorded assessments gets the band the composite yields; one with none reads unknown.
    • P6.c [V1.2] Add an admin lane (catalog read plus verb route) that drives EditorialStateMachine (libs/oshun/domain-veritas/src/editorial/state-machine.ts) and attestation records, following the studio lane-console pattern; if the canonical machine of @oshun/studio-authoring is used instead, delete the Veritas duplicate (this is the second machine P19 counted). (03 §7; 05 §lifecycle) Verify: new apps/oshun/bff/src/routes/admin-veritas-editorial-lane.spec.ts — an illegal transition is refused with the machine's reason; a legal one is persisted with the actor from authContext.
  • P7. Sophia verification loops — roll-up; the customer answer surface now shows grounding, and the fact-check, contradiction and unsupported-claim loops still run nowhere; children below. (07 §5, §UX)
    • Already fixed (checked 2026-09-18): the customer answer renders grounding state, claim labels and abstention — apps/oshun/web/src/app/sophia/SophiaAskForm.tsx:41-99.
    • P7.a [V1.0] Run the loops on the live answer path: @sophia/verification has no BFF consumer. After buildSophiaGroundedAnswer (apps/oshun/bff/src/routes/domain-stubs.ts) composes an answer, run claim extraction, independent retrieval and claim-to-source matching from libs/sophia/verification/src/, flag unsupported claims in the response, and bind any model call to OpenRouter deepseek/deepseek-v4-flash-0731 in tests. (07 §5) Verify: new apps/oshun/bff/src/sophia/verification-loop.spec.ts — an answer containing a claim with no supporting passage in the corpus returns that claim flagged unsupported; a supported claim is not flagged.
    • P7.b [V1.0] Feed the operator queue from the loops: persist flagged claims and contradictions to Postgres and serve them to the evidence-queue panel, replacing the seeded cases and their notice in apps/oshun/web/src/lib/lilith-data/operator-depth.ts:355-361. 2026-09-18: depends on P7.a. (07 §5) Verify: the panel lists a case produced by the P7.a spec scenario and the seed notice is gone. blocked:upstream
  • P8. Generation worker, customer gallery and model registry — roll-up of three unconnected generation seams; children below. (07 §4, §6–7)
    • Partly fixed (checked 2026-09-18): the managed-models registry no longer shows seed models — apps/oshun/bff/src/isis/managed-models-store.ts:1-17 is an honest empty projection. The approval-to-registry write is still missing (P8.c).
    • P8.a [V1.0] Add an opt-in interval worker for generation jobs: the only drain is the manual POST /v1/generation/jobs/process (apps/oshun/bff/src/generation/jobs-route.ts:708), so a customer submission stays queued until an operator drains it. Mirror OSHUN_REMINDER_WORKER_INTERVAL_MS with its own variable, off by default, holding the same lease pattern as apps/oshun/bff/src/reminders/cycle-lease.ts. (07 §7) Verify: extend apps/oshun/bff/src/generation/jobs-route.test.ts — with the interval set and a provider double bound, an enqueued job reaches a terminal state with no manual drain; with it unset nothing runs.
    • P8.b [V1.0] Bring the customer generation gallery to life: bindGalleryStore and bindGalleryContext (apps/oshun/web/src/app/studio/generation-gallery/loader.ts:43,47) are never called, so every member sees an empty gallery, and the launch-branch, send-to-editorial, send-to-asset-library and takedown buttons (GenerationGalleryClient.tsx:745-786) have no handler. Bind the loader to the member's released outputs in apps/oshun/bff/src/generation/output-catalog.ts and wire each button to its BFF route. (07 §4) Verify: new apps/oshun/web/e2e/studio-generation-gallery.spec.ts — a released output for the signed-in member is listed; takedown removes it after confirmation; a member with none sees the empty state.
    • P8.c [V1.0] Write approved Civitai intake into a real registry: bind ManagedModelRegistryStore (apps/oshun/bff/src/isis/managed-models-store.ts) to a Postgres table and have applyDecision in apps/oshun/bff/src/isis/civitai-intake-store.ts insert a normalized record on approve or conditional approve. (07 §6) Verify: new apps/oshun/bff/src/isis/managed-models-registry.spec.ts — an approved intake appears in the registry browse with its licence fields; a rejected one does not.
  • P9. Living Scenes share surface — roll-up; media now survives a restart and the download grant has an endpoint, and five share-surface findings remain; children below. (08 §7–14)
    • Already fixed (checked 2026-09-18): share media is in the durable snapshot (apps/oshun/bff/src/routes/living-scenes.ts:296-303, restored at :589).
    • Already fixed (checked 2026-09-18): the download grant redeems — GET /v1/living-scenes/public/:shortCode/download-grants/:grantId/media (living-scenes.ts:2063) serves the stored bytes in the materialized format (:1940).
    • S12 closed 08 §13; S6 closed the header-borne crisis label of 08 §7.
    • P9.a [V1.0] Stop the shareability matrix passing by construction: the share route feeds resolveShareability with defaultShareabilityGrants(reachForPrivacy(...)) (apps/oshun/bff/src/routes/living-scenes.ts:1455), which grants every component the requested reach. Derive the tenant-policy, intent and lilith-policy grants from their real sources and give the rest the rationale "no restricting record". (08 §7) Verify: new apps/oshun/bff/src/routes/living-scenes-shareability.spec.ts — a tenant policy of internal-only caps a public request to tenant reach.
    • P9.b [V1.0] Enforce intent redaction on the server: the keep route stores the client's publicRedactedText verbatim, and redactIntent (libs/yemaya/living-scenes-runtime/src/cue-privacy/intent-redaction.ts) has no BFF caller. Recompute privateIntentHash and overwrite the public layer with redactIntent(privateText). (08 §8) Verify: same spec file as P9.a — a kept score whose client-supplied public text contains a personal name is stored and served redacted.
    • P9.c [V1.0] Serve the first frame: the viewer's OG image, Twitter card and reduced-motion still point at /api/living-scenes/<shortCode>/first-frame (apps/oshun/bff/src/routes/living-scenes.ts:1853,1969,2133) and no handler exists in the BFF or under apps/oshun/web/src/app. Add a public route that returns the first PNG frame of the stored APNG and point the URLs at it. (08 §9) Verify: new apps/oshun/bff/src/routes/living-scenes-first-frame.spec.ts — the route returns image/png whose bytes are the APNG's first frame; an unknown code answers 404.
    • P9.d [V1.0] Burn the visible provenance mark into the pixels: neither the share path nor apps/oshun/bff/src/living-scenes/render-route.ts passes watermark to the renderer; the short code is only a response header (living-scenes.ts:2402) and a DOM overlay. Pass visibleMark.shortCode so embedWatermarkMark runs before APNG assembly. (08 §12) Verify: same spec file as P9.c — decoding a frame of a shared scene finds the mark region differing from an unmarked render of the same score.
    • P9.e [V1.0] Honour reduced motion: the animated APNG sits outside [data-living-scene-motion-layer] in apps/oshun/web/src/app/scene/[id]/page.tsx:199-262, so it keeps playing, and honorsReducedMotionPreference is a hardcoded true (libs/yemaya/living-scenes-runtime/src/personal-artifacts/personal-artifacts.ts:1066). Move the image inside the motion layer, show the first-frame still in the reduced-motion branch, and serve a one-frame APNG for rendition: 'reduced-motion'. 2026-09-18: uses the route of P9.c. (08 §14) Verify: new apps/oshun/web/e2e-inspect/scene-reduced-motion.spec.ts (chromium, workers=1, reducedMotion: 'reduce') — the animated image is not visible and the still is.
  • P10. Psyche dialogue surface — roll-up: a realtime recorder with no policy, a client-assignable assistant role and a session route that fabricates a connected runtime; children below. (06 §12–13)
    • Re-checked 2026-09-18: all findings still hold; no web or mobile client connects to the surface.
    • P10.a [V1.0] Put member turns under policy: apps/oshun/bff/src/psyche/realtime-route.ts:110-118 accepts role: 'assistant' | 'system' from the client and records content with no analyzeMessageSafety and no crisis-frame check. Force role: 'user' for client turns and mirror the assistant route's interception. (06 §12) Verify: extend apps/oshun/bff/src/psyche/realtime-route.test.ts — a client turn claiming assistant is stored as user; a crisis-class turn returns the interception envelope and is not stored as ordinary content.
    • P10.b [V1.0] Stop fabricating a connected session: apps/oshun/bff/src/adapters/psyche-embodiment-adapters.ts:296,503,526,580,609 returns connectionState: 'connected', join URLs, latencies and evaluation scores for transports the capability route reports as not built. Fail closed for unbuilt modalities, return only the real text-session envelope, delete the invented numbers. (06 §13) Verify: new apps/oshun/bff/src/adapters/psyche-session-honesty.spec.ts — requesting a conference modality answers the typed not-built error; the text session carries no latencyMs or score field.
    • P10.c [V1.0] Give the surface one client or take it off the public router: connect the assistant panel through PsycheSessionBridge (libs/oshun/shell-assistant/src/psyche-session-bridge.ts, zero app consumers), or mount the realtime route behind a flag that is off in production with a dated header note. (06 §12) Verify: either a web spec shows a panel turn arriving in the dialogue store, or the route answers 404 in production mode in realtime-route.test.ts.
  • P11. Crisis residuals — roll-up: server-side crisis suppression for memory and generation, region-aware resources, the post-crisis machinery, and one Lilith-reach finding of report 06 that no backlog item carried; children below. (06 §14; 11 §10; added 2026-09-18: 06 §19)
    • Partly fixed (checked 2026-09-18): Living Scenes now consults the frame on the server (isCrisisFrameActiveForUser in apps/oshun/bff/src/routes/living-scenes.ts).
    • P11.a [V1.0] Compute the crisis flag on the server for memory: lilithCrisisFrame on /v1/iris/adapter/search, plan-write and remember is whatever the client sends (apps/oshun/bff/src/routes/iris.ts:704,919). OR the hint with isCrisisFrameActiveForUser(userId). (06 §14) Verify: new apps/oshun/bff/src/routes/iris-crisis-suppression.spec.ts — with an armed frame and no client flag, remember writes nothing and search returns nothing.
    • P11.b [V1.0] Consult the frame before generation and before a Tara session starts: neither the Isis admit path (apps/oshun/bff/src/generation/jobs-route.ts) nor the Tara routes call isCrisisFrameActiveForUser. (06 §14) Verify: new apps/oshun/bff/src/generation/crisis-frame-admit.spec.ts — with an armed frame enqueue answers the stillness refusal and no job is created.
    • P11.c [V1.0] Make crisis resources region- and language-aware: apps/oshun/bff/src/routes/safety.ts:63 hardcodes region: 'global' and serves US numbers to everyone. Pass profile region and Accept-Language into routeCrisis (libs/oshun/trust-safety/src/crisis/crisis.ts) over a region-keyed catalog, and fall back to an international directory rather than to US numbers. (11 §10) Verify: new apps/oshun/bff/src/routes/safety-crisis-region.spec.ts — a GB caller gets UK resources, an unmapped region gets the international fallback and no US-only number.
    • P11.d [V1.0] Wire the post-crisis machinery: on frame resolution persist planCheckIn into the reminder dispatcher (opt-in) and consult isInCooldown in the recommendations route. (11 §10) Verify: new apps/oshun/bff/src/safety/post-crisis.spec.ts — resolving a frame schedules one check-in for an opted-in member and none otherwise; recommendations are empty during cooldown.
    • P11.e [V1.0] Evaluate reporting obligations: call createMandatoryReport and selectLegalContact on frame resolution, producing an operator-visible record; with no legal contact configured for the jurisdiction the evaluation fails loud as not_configured and never claims a report was filed. (11 §10) Verify: same spec file as P11.d — a jurisdiction with a configured contact yields a pending report record; one without yields not_configured.
    • P11.f [V1.0] Extend Lilith message safety beyond the assistant: analyzeMessageSafety is called only from apps/oshun/bff/src/routes/assistant.ts. Run it on Sophia question intake, and run validateDraftResponse on composed Sophia output before it is sent, reusing the assistant's interception envelope. (06 §19) Verify: new apps/oshun/bff/src/sophia/sophia-message-safety.spec.ts — a crisis-class question returns the interception envelope with resources instead of a composed answer.
  • P12. Moderation enforcement and integrity — roll-up: bans that restrict nothing, actor identity taken from the request body, and a store that forgets on restart; children below. (11 §7–8, §15)
    • Re-checked 2026-09-18: all three findings still hold.
    • P12.a [V1.0] Enforce bans: nothing outside the moderation files reads banStatus. Consult it in a preHandler on mutating customer routes, map feature_specific and content_specific scopes to their route families, and answer 403 account_restricted with the appeal link. (11 §7) Verify: new apps/oshun/bff/src/moderation/ban-enforcement.spec.ts — a banned member's mutation answers 403 account_restricted; reads still work; lifting the ban restores access.
    • P12.b [V1.0] Take actor identity from the session: apps/oshun/bff/src/routes/admin-moderation.ts:233,351,424 reads moderatorId, reviewerId and issuedBy from the body. Use request.authContext.userId, refuse an appeal resolution by the moderator who made the decision (libs/aja/content-moderation/src/moderation-workflow.ts), and require a second reviewer for permanent bans and P0 classes. (11 §8) Verify: new apps/oshun/bff/src/routes/admin-moderation-actor.spec.ts — a body-supplied actor id is ignored; self-review answers 409; a permanent ban stays pending until a second operator approves.
    • P12.c [V1.0] Persist the moderation queue, appeals and bans in Postgres: apps/oshun/bff/src/moderation/moderation-store.ts holds them in class fields with no sink. (11 §15) Verify: new apps/oshun/bff/src/moderation/moderation-store-durability.integration.spec.ts against local Postgres (oshun_dev) — a decision, an appeal and a ban survive re-opening the store.
  • P13. Payments residuals — roll-up; signed receipts now exist, and the crypto invoice contract, refunds and analytics consent remain; children below. (11 §11–14)
    • Already fixed (checked 2026-09-18): every settlement mints an Ed25519-signed receipt and refuses to settle without a signer (apps/oshun/bff/src/payments/settlement-route.ts:31,46,198-205, settlement-receipt.ts), and the by-id invoice read serves it (apps/oshun/bff/src/routes/domain-stubs.ts:3788-3795).
    • Already fixed (checked 2026-09-18, 11 §19): the settlement HMAC is computed over the raw body (settlement-route.ts:155).
    • 2026-09-18: written before the Stripe rail existed. The fiat rail already publishes through FiatEntitlementEmitter (apps/oshun/bff/src/payments/fiat-routes.ts:529); P13.a brings the crypto rail to the same contract.
    • P13.a [V1.0] Bring the crypto invoice to the spec contract: apps/oshun/bff/src/payments/invoice-store.ts knows pending | confirmed | expired | cancelled, the settlement body carries no amount, and the plan flip is a direct updatePlanDurably (settlement-route.ts:302). Add paidAmountAtomic to the webhook, classify settled, underpaid and overpaid through the bridge's state-mapper, and grant the plan from an entitlement-bus consumer as the fiat rail does. (11 §13) Verify: extend apps/oshun/bff/src/payments/settlement-route.test.ts — an underpaid settlement grants nothing and flags the invoice; an exact one grants through the bus consumer.
    • P13.b [V1.0] Build the refund request path that needs no chain access: POST /v1/payments/invoices/:id/refund-request (session-scoped, destination captured per asset rules), evaluateRefundEligibility from @oshun/billing-support, enqueue on ColdSpendQueue from libs/oshun/payments-bridge, and an admin list-and-initiate route. Signing and broadcast stay with the operator's hardware wallet. (11 §14) Verify: new apps/oshun/bff/src/payments/refund-request.spec.ts — an eligible invoice produces one queued unsigned transaction; an ineligible one answers the typed reason; another member's invoice answers 404.
    • P13.c [V1.0] Operator co-signs and broadcasts a queued refund. 2026-09-18: needs the owner's hardware wallet and a funded node; no agent can do it. (11 §14) Verify: a test-net refund appears on chain and the queue row records the txid. blocked:external
    • P13.d [V1.0] Enforce analytics consent: the web transport posts unconditionally (apps/oshun/web/src/analytics/transport.ts) and the ingest consults nothing (apps/oshun/bff/src/analytics/events-ingest.ts). Gate the shared sink on the member's analytics flow, and drop events on the server when the bearer session resolves to a withdrawn flow. (11 §12) Verify: new apps/oshun/bff/src/analytics/events-ingest-consent.spec.ts — a withdrawn member's batch is accepted with zero events stored; a web spec asserts no POST after withdrawal.
  • P14. Localization — roll-up: eight declared languages and right-to-left support over an app that renders English only; children below. (13 F5)
    • Re-checked 2026-09-18: no component under apps/oshun/web/src calls useTranslations; apps/oshun/web/src/i18n/messages/ contains only en/; apps/oshun/web/src/i18n/request.ts:5 hardcodes the default locale.
    • P14.a [V1.0] Make the switcher honest now: in apps/oshun/web/src/components/LanguageSwitcher.tsx offer only locales that have a message catalog, show the rest as coming later and disabled, and do not flip the document direction for a locale with no catalog. (13 F5) Verify: new apps/oshun/web/src/components/__tests__/LanguageSwitcher.spec.tsx — with only en present every other option is disabled and selecting Arabic is impossible.
    • P14.b [V1.0] Thread the stored locale into apps/oshun/web/src/i18n/request.ts, mount NextIntlClientProvider, and externalize the shell-level strings (navigation, Home section headings, auth, settings) into messages/en/shell.json with a pseudo-locale proving nothing is hardcoded. (13 F5) Verify: new apps/oshun/web/e2e-inspect/i18n-pseudo-locale.spec.ts (chromium, workers=1) — under the pseudo-locale the shell chrome on /, /tara, /nyx, /arete, /nisaba shows no unbracketed English string from the externalized set.
    • P14.c [V1.0] Draft catalogs for es, fr, de, pt, ja, ar and he from the English catalog with the test-bound model (OpenRouter deepseek/deepseek-v4-flash-0731), each file marked draft, and keep those locales disabled in the switcher. Quote the spend and ask before running it. 2026-09-18: depends on P14.b. (13 F5) Verify: a key-parity check (apps/oshun/web/src/i18n/catalog-parity.spec.ts) passes for every drafted locale. blocked:upstream
    • P14.d [V1.0] A fluent reviewer signs off each drafted catalog, and right-to-left layout is reviewed for ar and he, before its locale is enabled. 2026-09-18: a human act. (13 F5) Verify: each enabled locale's catalog header names its reviewer and date. blocked:human
  • P15. Status page and client observability — roll-up; mobile crash reports now land, and the public status page and the web observability directory remain; children below. (13 F3–F4, F6)
    • Already fixed (checked 2026-09-18): mobile crashes and diagnostics have receivers — POST /v1/mobile/crashes and /v1/mobile/diagnostics in apps/oshun/bff/src/routes/mobile-telemetry.ts:110,143.
    • P15.a [V1.0] Render the real status: apps/oshun/web/src/app/status/page.tsx shows the hardcoded STATUS_COMPONENTS and a "Specimen · fixture data" pill (apps/oshun/web/src/components/lilith/system-pages.tsx:602,658) while GET /v1/status (apps/oshun/bff/src/routes/customer-communications.ts) has no consumer. Fetch it in the server component and keep the specimen behind a development query parameter. (13 F6) Verify: new apps/oshun/web/e2e/status-page-live.spec.ts — an operator-authored incident appears on /status; with the BFF down the page says status is unavailable instead of showing the specimen.
    • P15.b [V1.0] Decide the web observability directory: apps/oshun/web/src/observability/ posts to /v1/web/telemetry/*, /v1/web/errors, /v1/web/vitals and /v1/web/crashes, none of which exist, and nothing calls its init functions. Add receivers on the mobile-telemetry.ts pattern and initialise from a client-root effect, or delete the directory and route errors through POST /v1/analytics/events. (13 F3) Verify: new apps/oshun/bff/src/routes/web-telemetry.spec.ts plus a web spec that a thrown render error produces one stored report; or ls apps/oshun/web/src/observability fails and app/error.tsx reports through the analytics ingest.
  • P16. Tenant console — roll-up: no SCIM endpoint, no production login for tenant admins, and an export that packages pasted JSON; children below. (10 §5, §7, §9)
    • Re-checked 2026-09-18: all three findings still hold. Tenant session tokens are still unsigned and gated with the dev tokens, now in libs/shared/bff-kit/src/authz.ts:216-229,515.
    • P16.a [V1.0] Expose SCIM 2.0: add /scim/v2/Users and /scim/v2/Groups in apps/oshun/bff/src/tenant-console/ translating through applyScimSync (libs/oshun/tenant-console/src/identity/scim.ts) onto the tenant member store, authenticated per tenant with the API-key preHandler of T5.a. 2026-09-18: depends on T5.a. (10 §7) Verify: new apps/oshun/bff/src/tenant-console/scim-route.spec.ts — create, update and deactivate a user and sync a group; a key for another tenant answers 403. blocked:upstream
    • P16.b [V1.0] Give tenant admins a production login: mint signed HS256 tenant sessions (tid plus tenant:admin:{tid}) from the SSO callback when the connection's tenant has the member, set the tenant-admin cookie on a /handoff completion route, and stop parsing tenant. tokens as dev tokens. (10 §9) Verify: new libs/shared/bff-kit/src/tenant-session.spec.ts — with NODE_ENV=production a signed tenant token authenticates and an unsigned tenant. token is refused.
    • P16.c [V1.0] Export real tenant data: apps/oshun/bff/src/routes/admin-bulk-exports.ts:62 takes records from the request body. Add server-side sources per resourceKind (members, tenant-filtered audit events) and keep pasted records as an explicit custom mode. (10 §5) Verify: new apps/oshun/bff/src/routes/admin-bulk-exports-sources.spec.ts — a members export for tenant A contains A's members only and its manifest verifies.
  • P17. Tara and Arete remainder — roll-up; the first child is a bright-line honesty defect that H6 handed to this item and the 2026-06-11 text never listed; children below. (02 §12–17; added 2026-09-18: 02 §5)
    • Already fixed (checked 2026-09-18): the offerings index shows the member's kept and sent rows (buildAreteOfferingsIndex, apps/oshun/bff/src/routes/domain-stubs.ts:2111-2124), the decision log became per-member and durable under S1, coach "Approve" posts to /v1/arete/coach/decision (apps/oshun/web/src/components/lilith/AreteCoachActions.tsx:43), and the real-teacher names are gone from the Tara hub.
    • P17.a [V1.0] Stop the sit player faking a saved reflection: submitReflection in apps/oshun/web/src/components/lilith/TaraSitPlayer.tsx:391-403 waits 300 ms and sets saved with no request; offline it writes a localStorage key nothing reads; moodAfter is never sent. Add a reflection leg to the sitting routes in apps/oshun/bff/src/routes/domain-stubs.ts carrying { reflectionText, moodAfter }, flush the offline queue on mount, and show "Saved" only after a 2xx. Work this child first. (02 §5) Verify: extend apps/oshun/web/src/components/lilith/TaraSitPlayer.test.tsx — with the BFF mocked to fail, the status is never saved; with success the stored completion has the text and the mood.
    • P17.b [V1.0] Stop the player pausing a still meditator: DRIFT_IDLE_SECONDS = 90 (TaraSitPlayer.tsx:33,324-331) fires while the session is running, so no sit over 90 seconds completes without a tap. Treat running playback as presence, or count drift only from paused. (02 §12) Verify: extend apps/oshun/web/src/components/lilith/TaraSitPlayer.test.tsx with fake timers — a 180-second sitting with no input reaches completed.
    • P17.c [V1.0] Replace the Living Offerings fixture composer: /arete/offering renders getAreteOfferingFixture() (apps/oshun/web/src/lib/lilith-data/arete-depth.ts:584-588, recipient "J"). Compose from the member's input and generate through the Living Scenes render route once T14.a makes it score-driven. 2026-09-18: depends on T14.a. (02 §13) Verify: a web spec — the composer starts empty, and keeping a composed offering shows it on /arete/offerings. blocked:upstream
    • P17.d [V1.0] Play voice guidance and make the speed slider real: GET /v1/tara/sessions/:id/guidance (apps/oshun/bff/src/tara/ambient-audio-routes.ts) has no web caller, and voiceSpeed (TaraSitPlayer.tsx:109,636-647) is read by nothing. Fetch guidance at opening, midpoint and closing with a silent skip on 503, and drive audio.playbackRate from the slider. (02 §14) Verify: extend apps/oshun/web/src/components/lilith/TaraSitPlayer.test.tsx — the guidance URL is requested at the three progress points and the slider changes playbackRate; a 503 leaves the sit running.
    • P17.e [V1.0] Reach the Arete journal and balance backends from the web: the facade serves both, no /v1/arete/journal or /v1/arete/balance BFF route exists, and AreteJournalReflectionWorkspace.tsx and AreteLifeBalance.tsx under apps/oshun/web/src/components/domains/arete/ make no network call. Add thin BFF routes over the Arete adapter and wire the editor and the assessment submit; fixture entries become the empty state. (02 §15) Verify: new apps/oshun/bff/src/routes/arete-journal-balance.spec.ts — a posted entry is read back for its author only; a web spec shows it after reload.
    • P17.f [V1.0] Wire /arete/review to the real week: getAreteReview returns the fixture (arete-depth.ts:381-385) while GET /v1/arete/review computes highlights from real check-ins, so AreteReviewClose durably closes the fixture week. Read the route, and disable the close action with an honest note when there is no live week. (02 §16) Verify: new apps/oshun/web/src/lib/lilith-data/__tests__/arete-review-live.spec.ts — the week label comes from the BFF payload; with no check-ins the close control is disabled.
    • P17.g [V1.0] Remove the habits fixture fallback: getAreteHabits returns getAreteHabitsFixture() when the BFF read fails (arete-depth.ts:639-643). Return an unavailable shape the page renders as a quiet notice, as taraUnavailable() does. (02 §17) Verify: same spec directory — a failed read yields zero habits and unavailable: true.
  • P18. Nisaba and Metis remainder — roll-up; member state is now durable, Nisaba children are V1.0 and Metis children are V1.2; children below. (04 §6–11)
    • Already fixed (checked 2026-09-18): Nisaba member state and integrity records are durable — wireDurableNisabaStudyWorkspaces (apps/oshun/bff/src/nisaba/state.ts:1292, wired at apps/oshun/bff/src/server.ts:1929) and the integrity snapshot (apps/oshun/bff/src/metis/academic-integrity-appeal-store.ts:1592).
    • Already fixed (checked 2026-09-18): the Nisaba room is built around the reader's actual daily passage (apps/oshun/bff/src/nisaba/room.ts:147-168).
    • P18.a [V1.0] Finish the Nisaba room: buildNisabaRoom still ships Cross-references: 4 and Saved excerpts: 2 as constants (apps/oshun/bff/src/nisaba/room.ts:177-178), and the no-reader default is the Bhikkhu Bodhi translation of SN 22.59 (:38-39), which is not public domain. Count from the real stores with honest zeros, and replace the default with a public-domain rendering that carries its attribution. (04 §6) Verify: extend apps/oshun/bff/src/nisaba/room.test.ts — a member with no saved excerpts reads 0; the default passage citation names a public-domain edition.
    • P18.b [V1.0] Stop NisabaSurface substituting fixtures in live mode, part one: compare targets render SUPPLEMENTAL_PASSAGE_DETAILS instead of fetching /v1/nisaba/passages/:id (apps/oshun/web/src/components/domains/NisabaSurface.tsx:567), a live but empty notebooks response is replaced by FALLBACK_NOTEBOOK_LIST_RESPONSE (:706,1175), and a BFF error shows the embedded fixture labelled "Cached Nisaba workspace" (:2834). Fetch real bodies, render honest empty, label the error as unavailable. (04 §7) Verify: new apps/oshun/web/src/components/domains/__tests__/NisabaSurfaceLiveHonesty.spec.tsx — empty notebooks render the empty state; an error renders no passage body; a compare target shows the fetched body.
    • P18.c [V1.0] Part two: remove the fixture grounded views and the unconditional SAVED_PASSAGE card (NisabaSurface.tsx:367,1358); wire /v1/nisaba/adapter/saved-passages or drop the card. (04 §7) Verify: same spec file — with no saved passages the card is absent and no view claims grounded.
    • P18.d [V1.0] Lift the Nisaba preview banner: set DOMAIN_PREVIEW_SURFACES.nisaba to false in apps/oshun/web/src/components/domains/DomainPreviewBanner.tsx. 2026-09-18: only after P18.b and P18.c. (04 §7) Verify: the existing domain-surface banner tests are updated and pass with Nisaba unbannered. blocked:upstream
    • P18.e [V1.0] Make the Nisaba depth pages honest about the reader: apps/oshun/web/src/lib/lilith-data/nisaba-depth.ts:39,651 presents "Renata" marginalia and dayNumber: 109 as the member's own, and apps/oshun/web/src/app/nisaba/notebooks/page.tsx:57,86 swaps in NOTEBOOK_FIXTURE with timestamps computed from today whenever the BFF refuses. Render the member-shaped regions from the real stores or empty; keep the curated apparatus. (04 §10) Verify: new apps/oshun/web/src/lib/lilith-data/__tests__/nisaba-depth-honesty.spec.ts — no output contains Renata; a refused notebooks read yields an unavailable state with zero notebooks.
    • P18.f [V1.2] Make source approval gate generation: nothing on the ingest or generation path reads apps/oshun/bff/src/metis/metis-byom-decision-store.ts. For high-stakes outputs refuse with source_not_approved unless the latest decision for the source is approved, and list which outputs count in the route header. (04 §8) Verify: new apps/oshun/bff/src/metis/byom-approval-gate.spec.ts — a rejected source cannot produce a course outline; an approved one can.
    • P18.g [V1.2] Drive /metis/tutor from the real session-memory store: getMetisTutor returns the fixture transcript (apps/oshun/web/src/lib/lilith-data/metis-depth.ts:74-78) and no product surface posts turns to POST /v1/metis/tutor-session-memories. List the member's sessions, post turns, and show honest empty otherwise. (04 §9) Verify: new apps/oshun/web/e2e/metis-tutor-live.spec.ts — a posted turn appears after reload; a new member sees no transcript.
  • P19. Editorial lifecycle, embargo and the unused-engine census — roll-up; one of the two lifecycle machines now has a consumer; children below. (05 §lifecycle/embargo/census)
    • Partly fixed (checked 2026-09-18): the canonical machine is consumed — apps/oshun/bff/src/agentic/autonomy-bindings/editorial-publisher.ts:19 advances through tryEditorialAdvance. The Veritas duplicate is handled in P6.c.
    • P19.a [V1.0] Enforce embargo and blocked streams: apps/oshun/bff/src/admin/admin-editorial-release-streams-store.ts is read only by its own routes. Have the communications publish path and the generation release leg refuse or delay a subject with an open embargo or blocked stream. (05 §embargo) Verify: new apps/oshun/bff/src/admin/release-stream-enforcement.spec.ts — publishing an embargoed subject before embargoLiftsAt is refused; after it, allowed.
    • P19.b [V1.0] Re-run the census and write the recommendation: for each family with no consumer in @oshun/studio-authoring (creator-roles, asset-metadata, templates, localization-workflow, versioning, taxonomy-curation, collaboration) and @oshun/customer-curation (collections, annotations, share-cards, version-awareness), record today's importer count and recommend wire-for-V1.0 or defer, in docs/audits/V1_RESIDUAL_AUDIT_2026-06-11/05-editorial-studio.md under a dated heading. (05 §census) Verify: the dated section exists and every family row carries a grep command and its result.
    • P19.c [V1.0] The owner records build or defer per family. 2026-09-18: a scope decision; the report notes that no recorded deferral covers these spec sections. (05 §census) Verify: each family has a dated decision line in V1/TODOS.md. blocked:governance
  • P20. Agentic remainder — roll-up; the console field misread is fixed, and the registry, catalog, pipelines and evaluation findings remain; children below. (09 F7–F11)
    • Already fixed (checked 2026-09-18): approved runs read as approved — apps/oshun/bff/src/routes/admin-agentic-operations.ts:111-112 uses latestApproval.verdict.
    • P20.a [V1.0] Give gold sets and champion-challenger something to operate on: the admin store has promote and rollout verbs and no create verb, production seeds are empty, and nothing reads trafficSharePct. Add create and ingest routes, and have run dispatch consult the active experiment for agent-version selection; or label the panels read-only and record the deferral in the route header. (09 F8) Verify: extend the specs beside apps/oshun/bff/src/admin/admin-agentic-operations-store.ts — a created experiment at 100% share selects the challenger version on the next run.
    • P20.b [V1.0] Validate tool plans against the contract catalog and check grants: the execute route accepts any id in the injected four-tool registry and V1_TOOL_CATALOG (libs/contracts/src/agent/tools.ts) has no runtime reader. Unknown id answers 400, known but unimplemented answers 503, and admission resolves a server-side grant keyed by agent, tool and scope. (09 F9) Verify: extend apps/oshun/bff/src/agentic/runs-route.test.ts — the three outcomes above, plus a revoked grant refusing a previously allowed tool.
    • P20.c [V1.0] Validate pipelineId against V1_PIPELINES (libs/oshun/agentic-studio/src/pipelines/v1-pipelines.ts) at submit and attach the pipeline's declared approval gate and budget cap to the run. (09 F10) Verify: extend apps/oshun/bff/src/agentic/run-lifecycle-store.test.ts — an unknown pipeline answers 400; arete.weekly_review_draft starts in awaiting_approval with its cap.
    • P20.d [V1.0] Mount a read-only agent registry over libs/oshun/agentic-studio/src/registry/ seeded with the agents the product invokes, and make submit and execute refuse an unregistered rootAgentId; this also makes the per-agent kill switches of S8 airtight. (09 F11) Verify: extend apps/oshun/bff/src/agentic/runs-route.test.ts — an unregistered agent id answers 400; a per-agent switch stops a registered one.
  • P21. Contracts under the routes — roll-up: bind the contract library to request and response payloads and make OpenAPI drift checking two-way; children below. (13 F9, F15)
    • Re-counted 2026-09-18: 527 route files under apps/oshun/bff/src/routes, 43 import @oshun/contracts; the OpenAPI document lists about 108 /v1/ paths.
    • P21.a [V1.0] Add a parseWith(schema) helper (or fastify-type-provider-zod) in libs/shared/bff-kit/src/ and a lint rule that fails a new route file which reads request.body without it. (13 F9) Verify: new libs/shared/bff-kit/src/parse-with.spec.ts; the rule fires on a fixture file in the ESLint rule's own test.
    • P21.b [V1.0] Migrate the first tranche: every mutation route in the auth, profile, consent, payments and library families validates its body and shapes its response through a contracts schema; record the before and after counts in the item when done. (13 F9) Verify: grep -L "@oshun/contracts" over that tranche's route files prints nothing, and each family's existing route tests pass per file with npx vitest run <file>.
    • P21.c [V1.0] Validate analytics ingest with the library it ships beside: apps/oshun/bff/src/analytics/events-ingest.ts hand-rolls type checks while validateEventEnvelope (libs/oshun/analytics/src/validation.ts) and the event-name taxonomy go unused. (13 F9) Verify: extend apps/oshun/bff/src/analytics/events-ingest.test.ts — an event name outside the taxonomy is rejected with the taxonomy's reason.
    • P21.d [V1.0] Add the inverse OpenAPI assertion to apps/oshun/bff/src/__tests__/contract/openapi-coverage.test.ts: enumerate the app's registered routes and fail for any that is neither documented nor on an explicit waiver list with a reason. (13 F15) Verify: the test fails when a new undocumented route is registered and passes once it is documented or waived.
  • P22. Workspace and cross-device read-back — roll-up: sync is write-only, plus one workspace handoff finding of report 01 that no backlog item carried; children below. (01 §19, §21; 04 §13; added 2026-09-18: 01 §18)
    • Re-checked 2026-09-18: the only pushClientState call is apps/oshun/web/src/components/workspace/MultiPanelWorkspace.tsx:446 and pullClientState has no caller.
    • P22.a [V1.0] Read the durable slot back on boot: call pullClientState('workspace-layout') and merge by updatedAt when MultiPanelWorkspace mounts. (01 §21) Verify: new apps/oshun/web/e2e/workspace-layout-cross-device.spec.ts — a layout saved in one browser context is restored in a second context for the same member.
    • P22.b [V1.0] Bring the study-plan-drafts slot to life: NisabaSurface persists notes, drafts, collections and plans to localStorage only (apps/oshun/web/src/lib/nisaba-offline.ts). Push them through pushClientState('study-plan-drafts', …) on persist and pull and merge in the hydration effect. (04 §13) Verify: same spec pattern — a study plan written in one context appears in a second.
    • P22.c [V1.1] Point mobile Explore at real search: apps/oshun/mobile/app/(tabs)/explore.tsx:264 searches an in-file index with invented freshness and trending and makes no network call. Use GET /v1/search through the typed BFF client, keep curated rows labelled as editorial, drop "trending" until the analytics ingest can supply it. (01 §19) Verify: new apps/oshun/mobile/app/(tabs)/explore-live-search.spec.tsx (jest) — a query issues one /v1/search call and renders its results; offline shows an unavailable state.
    • P22.d [V1.0] Stop the workspace-to-mobile handoff packet inventing its header: apps/oshun/web/src/components/workspace/irisMobileWorkspaceHandoff.ts:46,50,60 sends a static user id, consentState: 'granted' and fixed memory counts, and adds voice-session and transcript items that do not exist. Fill the header from /v1/iris/adapter/continuity and the real consent read, use the session user, and include those items only when a real id exists. (01 §18) Verify: new apps/oshun/web/src/components/workspace/__tests__/irisMobileWorkspaceHandoff.spec.ts — with consent ungranted the packet says ungranted and carries no invented counts or items.

Phase Q — P2 + UX polish register (after P; see reports for detail)#

Work V1.0 children first, then V1.1, then V1.2.

  • Q1. P2 and UX polish sweep — roll-up of the lower-severity findings of all thirteen reports, one child per finding, grouped by report number (Q1.<report>.<letter>); children below. (reports 01–13, P2 and UX sections)
    • Already fixed (checked 2026-09-18) — 01 §27: the voice-input bars read the microphone (analyser.getByteFrequencyData, apps/oshun/web/src/components/assistant/AssistantPanel.tsx:4204-4205).
    • Already fixed — 03 §12: the Veritas home briefing degrades to its own 503 (apps/oshun/bff/src/routes/veritas.ts:758-770).
    • Already fixed — 04 §16: the Nisaba and Metis adapters run in-process (apps/oshun/bff/src/app.ts:1788-1802), so their reads and the Sophia retriever no longer dial a missing service.
    • Already fixed — 06 §24: assistant sessions are durable (wireDurableAssistantSessions, apps/oshun/bff/src/server.ts:225). 06 §26: /welcome/test-assistant is gated by TestHarnessGate (apps/oshun/web/src/app/welcome/test-assistant/layout.tsx).
    • Already fixed — 07 (lineage seeds): apps/oshun/bff/src/isis/output-lineage-store.ts:1-25 is an honest empty projection; what is left is Q1.07.a.
    • Already fixed — 11 §19 (HMAC over the raw body): see P13. 12 §9: Telegram /stop is enforced in the reminder cycle (apps/oshun/bff/src/reminders/reminders-route.ts:737-739, apps/oshun/bff/src/telegram/delivery-stop.spec.ts), and /voice has a fail-closed TTS provider (libs/oshun/messaging-channels/src/telegram/tts-provider.ts).
    • Already fixed — 10 §10 (audit tamper evidence): apps/oshun/bff/src/admin/admin-audit-events-store.ts seals each event with an HMAC and the previous event's hash (sealEvent, :138-159), verifyIntegrity (:359) is returned by apps/oshun/bff/src/routes/admin-audit-log.ts:120,146, and production refuses to boot without OSHUN_ADMIN_AUDIT_INTEGRITY_KEY (apps/oshun/bff/src/admin/admin-audit-governance.ts:26-36). What is left is Q1.10.a.
    • Already fixed — 11 §17 (retention schedules): canonical rules live in libs/contracts/src/common/data-retention-rules.ts (conversation history 30 days) and runComplianceRetention sweeps about thirty partitions with legal-hold checks (apps/oshun/bff/src/server.ts:957-1250, scheduled at :2075 and :2957). What is left is Q1.11.b.
    • Already fixed — 12 §10 (channel audit envelopes): the Telegram webhook persists them (recordTelegramAuditResponses, apps/oshun/bff/src/routes/telegram.ts:720-728).
    • Pointers, not tasks: "webhook secrets volatile" (10 §11) is T6.b; "no billing view" (11 §21) is H2.f; 02 §22 and 06 §27 resolve with P17, T2.a, T3.c and T15.a; 08 §24 (latency budgets) waits for the conductor decision T14.c.
    • Q1.01.a [V1.0] Unfreeze the account switcher clock: ACCOUNT_SWITCHER_NOW_MS = Date.parse('2026-05-07…') (apps/oshun/web/src/components/ShellLayout.tsx:301, used at :1100). Capture Date.now() after mount as the file already does for shellMountedAtMsRef. (01 §24) Verify: new apps/oshun/web/src/components/__tests__/ShellLayoutAccountSwitcherClock.spec.tsx with fake timers — an account active one hour ago reads as an hour, not months.
    • Q1.01.b [V1.0] Replace the stale "What's New": apps/oshun/web/src/components/WhatsNewDropdown.tsx serves a hand-written changelog frozen at v0.8–0.9 ("Dark theme polish", :82). Serve entries from the authored-communications store with a changelog kind, or show nothing. (01 §23) Verify: extend apps/oshun/web/src/components/activity/WhatsNewFeed.spec.tsx or add a dropdown spec — entries come from the mocked communications read; an empty read renders no list.
    • Q1.01.c [V1.0] Delete the unused fixture dashboards beside Home: KpiGrid (apps/oshun/web/src/components/home/KpiGrid.tsx:522 hardcodes sessions = 28) and CrossDomainHub. Confirm no page imports them, then remove the components and their tests. (01 §25) Verify: grep -rn "KpiGrid\|CrossDomainHub" apps/oshun/web/src/app prints nothing and the files are gone.
    • Q1.01.d [V1.1] Persist mobile preferences: apps/oshun/mobile/src/profile/store.ts:21 builds every profile, preference and privacy store on createMemoryKeyValueStorage(), so each setting resets on restart. Back them with the app's secure or async storage. (01 §26) Verify: new apps/oshun/mobile/src/profile/store-persistence.spec.ts (jest) — a preference written, the store re-created over the same storage, the preference read back.
    • Q1.01.e [V1.2] Stop Home saying "Resume course" to a member who never started one: ctaLabel: 'Resume course' in apps/oshun/web/src/components/home/homeMetisStudyContinuationModels.ts:53 is the fallback label. (01 §28) Verify: extend apps/oshun/web/src/components/home/__tests__/HomeMetisStudyContinuationSection.test.tsx — with no continuation the label invites starting, not resuming.
    • Q1.01.f [V1.0] Give the Home modules one degraded vocabulary: after H1.e–g land, every module uses the Veritas pattern (says what is unavailable, claims nothing) and "Curated" is kept for editorial content that makes no personal or factual claim. 2026-09-18: depends on H1.e, H1.f, H1.g. (01 §29) Verify: grep -rn "Curated" apps/oshun/web/src/components/home hits only editorial modules; one spec renders all modules degraded and asserts the shared presentation. blocked:upstream
    • Q1.02.a [V1.0] Decide the unused Tara content plumbing: use-tara-content.ts, tara-cache.ts, tara-monitoring.ts and content-filters.ts in apps/oshun/web/src/lib/tara/ have no importer. Point the hooks' query functions at the real /v1/tara/* facade routes and adopt them in the deep components, or delete the four modules. (02 §18) Verify: each module has a component importer, or the four files and their tests are gone.
    • Q1.02.b [V1.0] Fix the Tara hub copy: the eyebrow is the literal "Today's sit · 12 min" (apps/oshun/web/src/components/lilith/rooms.tsx:102) beside a Begin link that computes the real minutes, and TARA_TODAY_FIXTURE in apps/oshun/bff/src/routes/domain-stubs.ts describes a path that contradicts SITTING_PATH. Render the real duration and derive the fixture from the room builder. (02 §19) Verify: new apps/oshun/web/src/components/lilith/rooms-tara-duration.spec.tsx — a 180-second sitting shows 3 min in both places.
    • Q1.02.c [V1.0] Refuse unknown sitting ids: POST /v1/tara/sittings/:id/complete (apps/oshun/bff/src/routes/domain-stubs.ts:1634) records any string, which inflates streaks. Answer 404 unless the id is in the catalog exported from apps/oshun/bff/src/tara/room.ts. (02 §20) Verify: new apps/oshun/bff/src/routes/tara-sitting-catalog-guard.spec.ts — a made-up id answers 404 and writes nothing; a catalog id records once.
    • Q1.02.d [V1.0] Give the dead Tara hub tabs destinations: "Courses", "Teachers" and "Library" (apps/oshun/web/src/components/lilith/rooms.tsx:72-74) have no href; repoint the Arete hub habit links and "New ritual" at /arete/habits and /arete/habits/new. (02 §21) Verify: apps/oshun/web/e2e-inspect/ keyboard walk — each tab is a link that lands on a 200 page.
    • Q1.02.e [V1.0] Tell failure from emptiness: apps/oshun/web/src/components/domains/tara/TaraAnalyticsDashboard.tsx keeps its empty model when the fetch fails, and apps/oshun/web/src/app/arete/offerings/page.tsx renders a refused read as "No drafts in progress". Track a failed state and render a quiet could-not-load notice. (02 §23) Verify: new apps/oshun/web/src/components/domains/tara/TaraAnalyticsDashboard-failed.spec.tsx and a page spec for the offerings index — a rejected fetch renders the notice and not the empty-state copy.
    • Q1.03.a [V1.0] Remove the dead hydration path: hydrateDomainStoresFromBff (apps/oshun/web/src/lib/stores/domain-stores.ts:622) calls four /v1/domains/*/state routes that do not exist and has no caller. Delete it with the lastSyncedAt fields that imply a sync, or re-point it at /v1/client-state/:stateKey. (03 §9) Verify: grep -rn hydrateDomainStoresFromBff apps/oshun/web/src prints nothing, or a spec shows a real round trip.
    • Q1.03.b [V1.2] Give Veritas saves a server path: topic follow now has routes (apps/oshun/bff/src/routes/veritas.ts:405,424) but there is no save-article mutation, so "Save to notebook" writes localStorage only and never reaches the workspace evidence panel or achievements. Add POST and DELETE /v1/veritas/adapter/saved-articles/:articleId over the adapter and write through from VeritasArticleActions.tsx. (03 §10) Verify: new apps/oshun/bff/src/routes/veritas-saved-articles-write.spec.ts — a saved article appears in /v1/veritas/adapter/saved-articles for that member only.
    • Q1.03.c [later] Replace the 2024 sample data in the standalone Nyx service: apps/nyx/api/src/services/events-service.ts:10-20 and the January-2024 TLE in satellites-service.ts:25. This service is off the V1.0 path (the BFF's Nyx adapter runs in-process); back /api/v1/events with the @oshun/domain-nyx solver and make satellites fail closed as not_configured without a fresh TLE feed. (03 §11) Verify: npx vitest run on the service's events spec — getUpcomingEvents for today's date returns future events; satellites without a feed answer not_configured.
    • Q1.03.d [V1.0] Fix the Nyx hub navigation: "Planets" points at the satellite tracker and "Observations" at the simulated log (apps/oshun/web/src/components/lilith/rooms.tsx:1059-1061) although the member's real log lives at /nyx/observation. Point Observations at the real route and rename or re-target Planets. (03 §13) Verify: a rooms spec asserts the two hrefs.
    • Q1.04.a [later] Give the Nisaba scholarship engines their first real consumer: run the collation engine (libs/nisaba/criticism/src/collation-engine.ts) over the corpus compare pairs on the server to produce the alignments the compare view shows, which are curated text in apps/oshun/bff/src/nisaba/state.ts today. The full Nisaba service is a recorded later-release deferral; this is the optional pre-step the report names. (04 §12) Verify: new apps/oshun/bff/src/nisaba/compare-collation.spec.ts — alignments for one compare pair equal the engine's output.
    • Q1.04.b [V1.0] Make the illustrative manuscript witness match its passage: the witness for the Dhammapada I.1–2 passage transcribes a breath-mindfulness text (Anapanassatim bhikkhave…, apps/oshun/bff/src/nisaba/state.ts:1378). Use the Pali of Dhammapada I.1–2 from a public-domain edition with its attribution, or bind the witness to a passage it matches. (04 §15) Verify: new apps/oshun/bff/src/nisaba/witness-coherence.spec.ts — the witness transcription shares its opening words with the passage's source text.
    • Q1.04.c [V1.2] Align the Metis assistant seeds with the six-discipline focus: apps/oshun/bff/src/routes/metis-search-seeds.ts:14,22 leads with Calculus and Data Structures and links to /courses/... routes that do not exist. Use the room's curated catalog as the one source and link only to routes that exist. (04 §14) Verify: new apps/oshun/bff/src/routes/metis-search-seeds.spec.ts — every seed href resolves against the web route manifest and every seed discipline is one of the six.
    • Q1.05.a [V1.0] Let reading progress change: apps/oshun/bff/src/nisaba/state.ts serves progressPercent: 0 and no route ever updates it. Add POST /v1/nisaba/passages/:id/progress (or fold it into the client-state sync) and send it from the reader. (05 §reading progress) Verify: new apps/oshun/bff/src/routes/nisaba-reading-progress.spec.ts — posting 40 then reading the passage returns 40 for that member and 0 for another.
    • Q1.05.b [V1.0] Make the collections copy true: apps/oshun/web/src/app/library/collections/page.tsx:77 promises "Drag and drop, reorder". Wire the ordering engine of @oshun/customer-curation into the dashboard's item list, or soften the copy. (05 §copy) Verify: the sentence is gone, or an e2e spec reorders two items and the order persists.
    • Q1.05.c [V1.0] Fix the collection deep links: apps/oshun/web/src/app/library/collections/page.tsx:100 and new/LibraryNewCollectionForm.tsx:130 link to /library?collectionId=…, which /library ignores; the working route is /library/collection/[collectionId]. 2026-09-18: a BFF collection id only resolves once T13.a lands. (05 §deep links) Verify: an e2e spec — clicking a collection on the index opens that collection. blocked:upstream
    • Q1.05.d [V1.0] Show Telegram /save captures somewhere: GET /telegram/captures has no reader in web, mobile or the Mini App. Feed captures into the saved-items store, or add a Telegram lane to the library. (05 §telegram) Verify: new apps/oshun/bff/src/telegram/captures-in-library.spec.ts — a capture appears in the member's saved items and in universal search.
    • Q1.05.e [V1.0] Lift annotations out of Nisaba: move the per-user store to one keyed by surface and artifact id using the @oshun/customer-curation annotations engine, keep the Nisaba routes working over it, and mount it on the Tara transcript reader. Veritas and Metis readers follow in V1.2. (05 §annotations) Verify: new apps/oshun/bff/src/routes/shared-annotations.spec.ts — an annotation on a Tara transcript and one on a Nisaba passage are listed together for their author and export together.
    • Q1.05.f [V1.0] Persist the authored document and carry its id through the studio lanes: /studio/authoring holds its document in memory (H12) and the review, comments and localization consoles evaluate hand-pasted JSON. Add a Postgres artifact store for authored documents and let each lane accept an artifact id as its default payload. (05 §authoring, §lanes) Verify: new apps/oshun/bff/src/studio/authored-artifact-store.integration.spec.ts against local Postgres (oshun_dev) — a saved document is re-opened by id and submitted to the approval-workflow route by id.
    • Q1.06.a [V1.0] Render the disclosure the server already sends: the assistant route emits responseMode, retrievalText and the server activePersona, and no client reads them. Show a grounded-or-synthesized chip and the server's persona label in apps/oshun/web/src/components/assistant/AssistantPanel.tsx. (06 §22) Verify: extend apps/oshun/web/src/components/assistant/__tests__/AssistantPanel.test.tsx — a reply with responseMode: 'synthesized' shows the chip.
    • Q1.06.b [V1.0] Unfreeze the Lilith adapter and stop inventing voice scores: DEFAULT_NOW = '2026-03-22…' stamps health forever (apps/oshun/bff/src/adapters/lilith-persona-policy-adapters.ts:36,122) and createVoiceSafetyAssessment returns clarity: 0.94, naturalness: 0.92 (:477-478). Use the live clock, and answer { assessed: false, reason: 'no-audio-analysis-provider' } until a real scorer exists. (06 §23) Verify: new apps/oshun/bff/src/adapters/lilith-persona-policy-honesty.spec.tslastChecked is within a second of now and the assessment carries no numeric score.
    • Q1.06.c [V1.0] Measure or drop the health latencies: apps/oshun/bff/src/adapters/iris-memory-adapters.ts:846-858 reports constants (9, 7, 12, 5, 6, 11 ms). Time the store call as the Psyche adapter does, or remove the field. (06 §25) Verify: new apps/oshun/bff/src/adapters/iris-health-latency.spec.ts — two health reads with a slowed store differ, or the field is absent.
    • Q1.07.a [V1.0] Show real lineage in the operator console: released outputs record lineage in apps/oshun/bff/src/generation/output-catalog.ts, and the console reads the unbound apps/oshun/bff/src/isis/output-lineage-store.ts. Bind the console to the catalog's lineage, with ownership, so a takedown can compute its cascade. (07 lineage) Verify: new apps/oshun/bff/src/isis/output-lineage-binding.spec.ts — releasing a derived output makes the parent-to-child edge appear in the console read.
    • Q1.07.b [V1.0] Verify the watermark before narration plays: no customer playback component checks the provenance bundle's watermark hash. Add the check at playback entry and block or flag on mismatch. (07 watermark) Verify: new apps/oshun/web/src/components/studio/generation/NarrationCard-watermark.spec.tsx — a tampered hash prevents play() and shows the flag; an intact one plays.
    • Q1.08.a [V1.0] Replace the dead accessibility URLs: captions, audio description, stills and transcript links use an unresolvable oshun://living-scenes/… scheme and burnInAvailable: true is hardcoded (libs/yemaya/living-scenes-runtime/src/personal-artifacts/personal-artifacts.ts:1019-1059). Generate a real VTT from the transcript lines and serve it beside the transcript route; report audio description as unavailable. (08 §15) Verify: new apps/oshun/bff/src/routes/living-scenes-captions.spec.ts — the caption URL returns text/vtt with the transcript's cues; no response contains oshun://.
    • Q1.08.b [V1.0] Make the pre-share re-check a real one: containsBlockedSafetyFrame (personal-artifacts.ts:414-420) is a six-phrase English substring list recorded as the Lilith re-check. Send the score text through the BFF's Lilith moderation seam, record its verdict and true policy version, and keep the list as a pre-filter. (08 §16) Verify: new apps/oshun/bff/src/routes/living-scenes-preshare-check.spec.ts — a non-English crisis phrase the list misses is caught, and the share record names the policy version used.
    • Q1.08.c [V1.0] Reconcile the Nyx cue verbs inside the spec: the finding (code diverges from the spec's per-template verbs) is overtaken, because the canonical table in V1/features/domain-templates-and-editor.md:58-64 now lists exactly the sets in TEMPLATE_ALLOWED_VERBS (libs/yemaya/living-scenes-runtime/src/customer-card/customer-card.ts:108-147). What is left is that the Nyx prose at :119-120 of the same spec file still names shape-by, which the table and the code do not grant. Make prose and table agree, and pin the table with a test. (08 §17) Verify: extend libs/yemaya/living-scenes-runtime/src/customer-card/customer-card.test.ts — each template's allowed set equals the row of the spec table, asserted literally; the spec file has one answer for Nyx.
    • Q1.08.d [V1.0] Remove the invented cost estimate: costUsd and latencySeconds are computed from the length of the domain id (customer-card.ts:304-309). Use honest static per-template figures with their source, or drop the fields. (08 §18) Verify: extend customer-card.test.ts — no estimate depends on domainId.length.
    • Q1.08.e [V1.0] Use one technique catalog: compose-assist embeds its own allowlists (libs/yemaya/living-scenes-runtime/src/compose-assist/compose-assist.ts) while the full catalog in libs/yemaya/blend-kernel/src/catalog/cinematographic-catalog.ts has no app consumer. Make compose-assist read the catalog and validate the catalog against libs/contracts/src/living-scene/technique.ts. (08 §19) Verify: new libs/yemaya/living-scenes-runtime/src/compose-assist/technique-catalog-parity.spec.ts — every allowlisted technique id exists in the catalog and the catalog parses under the contract schema.
    • Q1.08.f [V1.0] Use or delete the server card routes: GET /v1/living-scenes/card and POST /v1/living-scenes/card/cues have no caller while the web card evaluates tier and cue policy in the browser. Fetch the card and post cues through the routes, keeping the library calls as optimistic UI. (08 §21) Verify: new apps/oshun/web/src/components/studio/generation/LivingSceneCard-server-policy.spec.tsx — a cue the server refuses is shown refused even if the client evaluation allowed it.
    • Q1.08.g [V1.0] Enforce composition caps on the server: free tier up to 6 segments and 90 seconds, paid up to 24 and 8 minutes exist only in the client's validateCompositionDraft; the keep route takes any score. Check them from the session tier in apps/oshun/bff/src/routes/living-scenes.ts. (08 §22) Verify: new apps/oshun/bff/src/routes/living-scenes-tier-caps.spec.ts — a free member keeping a 7-segment score answers 403 with the cap.
    • Q1.08.h [V1.0] Wire determinism as a CI gate: run checkDeterminism over the real render path once T14.a lands. 2026-09-18: depends on T14.a; N-node parity, continuity evals and the 30-fixture corpora stay later-release. (08 §23) Verify: new apps/oshun/bff/src/routes/living-scenes-determinism.spec.ts — a living-scenes spec fails when two renders of one score differ. blocked:upstream
    • Q1.08.i [V1.0] Stop shipping buttons that do nothing: the public viewer renders cue verbs as handler-less <button>s (apps/oshun/web/src/app/scene/[id]/page.tsx:357). Render them as a read-only list of cues used. (08 §25) Verify: an axe run through apps/oshun/web/e2e-inspect/ (chromium, workers=1) on a share page reports zero serious or critical violations and no inert button.
    • Q1.08.j [V1.0] Join the four islands into one journey: compose promotion emits a score-schema Score, the card's Keep posts it, the offerings list links to share creation, and the share links to the viewer, inside the signed-in shell. 2026-09-18: depends on T14.a and P9. (08 §26) Verify: new apps/oshun/web/e2e/living-scene-journey.spec.ts (chromium, workers=1) — one e2e spec walks compose, keep, share and view for a signed-in member. blocked:upstream
    • Q1.08.k [later] Give the AAA Scene Score Editor a store: apps/yemaya/studio-web/src/score-editor/ScoreEditorPage.tsx runs on one fixture score and never calls /v1/score-editor. Load and save scores through the Yemaya API and keep the fixture as the seeded example. (08 §20) Verify: new apps/yemaya/api/src/routes/score-editor-store.spec.ts — a saved score is re-opened with its version incremented.
    • Q1.09.a [V1.0] Engage throttles: every governed step runs with throttle: null (libs/oshun/agentic-studio/src/runs/governed-orchestrator.ts:599). Resolve a server-side per-tool and per-tenant throttle config as kill switches are resolved, and persist its state between requests. (09 F12) Verify: extend apps/oshun/bff/src/agentic/runs-route.test.ts — the call over the bucket answers throttled with a retry-after.
    • Q1.09.b [V1.0] Put the cheapest unused run controls to work: expose operator pause and cancel over the lifecycle store (@oshun/agentic-studio/runs/run-controls is already imported at apps/oshun/bff/src/agentic/run-lifecycle-store.ts:33) and accept a mode on submit that maps to a budget envelope once T4.b lands; list the engines still unused in the route header. (09 F13) Verify: extend apps/oshun/bff/src/agentic/run-lifecycle-store.test.ts — pause then cancel reach their states with audit events.
    • Q1.09.c [V1.0] Give the operator console its actions: apps/oshun/admin/src/components/AgenticOperationsPanel.tsx can only arm kill switches. Add per-run pause, cancel and mark-reviewed, filters by family, tenant and approval state, and a per-run drill-down that shows the now-populated tool calls. 2026-09-18: uses the routes of Q1.09.b. (09 F14) Verify: new apps/oshun/admin/src/components/AgenticOperationsPanel-actions.spec.tsx — a run row opens its tool-call transcript; pause calls the route.
    • Q1.09.d [V1.0] Close the one customer agent surface's loop: after T4.a, add a completion notification and a run-history read to apps/oshun/web/src/components/lilith/AreteReviewDraftRun.tsx, and record the wider invocation matrix as a later release in its header. 2026-09-18: depends on T4.a. (09 F15) Verify: new apps/oshun/web/src/components/lilith/AreteReviewDraftRun-history.spec.tsx — a completed run shows its draft and appears in history. blocked:upstream
    • Q1.10.a [V1.0] Retire the second, unused audit-integrity engine and correct the comment that points at it: the live store already seals every event into an HMAC hash chain and verifies it on read (see the note above), while verifyAuditChain, applyRedactions and buildCorrelationThread in libs/oshun/tenant-console/src/audit-explorer/audit-explorer.ts still have no app caller, and apps/oshun/bff/src/admin/admin-audit-events-store.ts:13-15 still describes a firehose feed into that explorer which does not exist. Either mount the library's redaction on the tenant-scoped audit read (apps/oshun/bff/src/routes/tenant-console-reads.ts) or mark the module deprecated with a dated note, and fix the comment. (10 §10) Verify: new apps/oshun/bff/src/admin/admin-audit-chain.spec.tsgrep -rn "firehose" apps/oshun/bff/src/admin/admin-audit-events-store.ts prints nothing, and the library module either has a BFF importer with a spec or carries the dated deprecation.
    • Q1.10.b [V1.0] Remove the weak fallback from the secret generator: generateRawToken mints API keys and webhook signing secrets and falls back to Math.random() under a "non-secret" annotation (apps/oshun/bff/src/admin/admin-integrations-registry-store.ts:160-165). Use randomBytes(24).toString('base64url') or throw, and fix the annotation. (10 §13) Verify: grep -n "Math.random" apps/oshun/bff/src/admin/admin-integrations-registry-store.ts prints nothing.
    • Q1.10.c [V1.0] Wire the two tenant-admin demo pages whose backends exist: apps/oshun/tenant-admin/src/app/status/page.tsx over GET /v1/status and help/page.tsx over GET /v1/communications/help, dropping their demo label. (10 §12) Verify: new apps/oshun/tenant-admin/src/app/status/status-live.spec.tsx and its help twin — each renders the mocked BFF payload and an honest unavailable state.
    • Q1.10.d [V1.0] Add GET /v1/tenant-console/integrations, filtering the integrations registry snapshot to the session tenant in the style of apps/oshun/bff/src/routes/tenant-console-reads.ts, and render it on the tenant-admin integrations page. (10 §12) Verify: new apps/oshun/bff/src/routes/tenant-console-integrations.spec.ts — tenant A never sees tenant B's keys or webhooks.
    • Q1.10.e [V1.0] Stop the tenant cockpit blaming the network for a permission error: tenantBffGet returns null for any non-ok response (apps/oshun/tenant-admin/src/lib/tenant-bff.ts:44-46), so a 403 reads as "backend unreachable". Return the status and render a scope-mismatch message; link the cockpit from the operator surfaces. (10 §14) Verify: new apps/oshun/tenant-admin/src/lib/tenant-bff.spec.ts — a 403 renders the permission message, a network error renders the unreachable one.
    • Q1.11.a [V1.0] Call the residency guard: enforceResidency in apps/oshun/bff/src/middleware/residency-guard.ts has no caller. Invoke it from the memory, export and deletion route families its header lists, with the deployment zone. (11 §16) Verify: extend apps/oshun/bff/src/middleware/residency-guard.spec.ts — an export request for an out-of-zone artifact answers 403 and writes the audit event.
    • Q1.11.b [V1.0] Remove the second retention table: enforcement exists (see the note above), so retentionDaysFor in libs/oshun/privacy/src/export-deletion/deletion.ts, which still has no app consumer, is now a rival source of truth for the same numbers. Delete it or make it read OSHUN_RETENTION_RULES from libs/contracts/src/common/data-retention-rules.ts. (11 §17) Verify: new apps/oshun/bff/src/privacy/retention-sweep-worker.spec.ts with a fixed clock — grep -rn "retentionDaysFor" libs apps shows no independent table of day counts; a spec asserts the two agree if the function survives.
    • Q1.11.c [V1.0] Keep one consent taxonomy: libs/oshun/privacy/src/consent/consent.ts defines a second state machine beside the live @oshun/contracts plus apps/oshun/bff/src/consent/state.ts. Collapse the duplicate, and for the unconsumed compliance, breach and DSAR-intake modules either mount a route or mark them deferred in their headers. (11 §18) Verify: grep -rn transitionConsent libs apps shows one implementation.
    • Q1.11.d [V1.0] Make /profile/data the privacy center: the real consent ledger, a link to memory controls, exports and deletions, and the residency display on one page; redirect /profile/notifications to the settings panel. 2026-09-18: depends on T15.a. (11 §20) Verify: an e2e-inspect walk — every privacy control is reachable from /profile/data by keyboard. blocked:upstream
    • Q1.11.e [V1.0] Give the four governance consoles one severity vocabulary and cross-links: adopt P0–P3 from @oshun/trust-safety across the admin workspaces, the moderation console, the abuse inbox (apps/oshun/bff/src/routes/admin-abuse-reports.ts, S1–S3 today) and takedowns, and link items by subject user and content id. (11 §22) Verify: new libs/contracts/src/governance-severity.spec.ts — all four payload schemas share the severity enum; an item view links to the same subject in the other stores.
    • Q1.12.a [V1.0] Cascade a Telegram unlink: unlinkTelegram (apps/oshun/bff/src/telegram/user-state-store.ts:957) marks the link revoked and nothing else. Invalidate the member's Mini App sessions and purge their scheduled Telegram reminders. (12 §9) Verify: new apps/oshun/bff/src/telegram/unlink-cascade.spec.ts — after unlink a previously minted Mini App token is refused and no Telegram reminder is dispatched.
    • Q1.12.b [V1.0] Ingest email bounces and complaints: ingestEmailWebhookEvent (libs/oshun/messaging-channels/src/email/index.ts) has no route. Add the provider event webhook and check the suppression list in deliverDispatchedMessage. (12 §10) Verify: new apps/oshun/bff/src/routes/email-events-webhook.spec.ts — a hard bounce suppresses the next send to that address.
    • Q1.12.c [V1.0] Record deliverability: a per-member delivery record exists for the transparency route (apps/oshun/bff/src/channel-ops/message-delivery-store.ts), but no delivery result feeds computeChannelDeliverability (libs/oshun/messaging-channels/src/deliverability.ts). Aggregate real MessageDeliveryResults per channel and show the summary on the admin messaging console. (12 §10) Verify: new apps/oshun/bff/src/reminders/deliverability-recording.spec.ts — two sends and one failure yield the expected per-channel rate.
    • Q1.12.d [V1.0] Build the digest or remove its settings: members can set digest frequency and delivery time, and nothing produces one. The engine file the report named (apps/oshun/bff/src/notifications/digest.ts) is gone, and apps/oshun/bff/src/channel-ops/digest-inputs-store.ts honestly reads an empty projection with a do-not-send verdict. Compose the projection from the owning stores and send by email on a worker tick, or remove the digest controls. (12 §10) Verify: new apps/oshun/bff/src/notifications/digest-worker.spec.ts with a fixed clock and Mailpit (:1025/:8025) — a daily digest for a member with two unread items arrives once; or the settings UI has no digest section.
    • Q1.12.f [V1.0] Stop the settings promising more than is delivered: in apps/oshun/web/src/components/profile/NotificationPreferences.tsx mark toggles the send path does not enforce as not yet active, and remove each mark as P2.b and Q1.12.d land. (12 §11) Verify: new apps/oshun/web/src/components/profile/__tests__/NotificationPreferences-inactive-marks.spec.tsx — an unenforced toggle carries the mark; the mark list is driven by one exported constant.
    • Q1.13.a [V1.0] Turn request tracing on in the BFF: the canonical package exists (libs/shared/tracing, a real OpenTelemetry NodeTracerProvider at src/tracer.ts:405) and the BFF already imports it for Eve trace context in the assistant and workbench paths, but nothing constructs the tracer at boot, requests get correlation-id logging only (libs/shared/bff-kit/src/tracing.ts), and infra/hetzner/docker-compose.yml has no OTLP endpoint. Construct the tracer in apps/oshun/bff/src/server.ts behind an env-gated OTLP exporter, add a server span per request, and propagate traceparent to the domain adapters. This is the build task behind the "confirm OpenTelemetry" items of docs/releases/v1/specs/todos-p2.md §55. (13 F10) Verify: new apps/oshun/bff/src/__tests__/otel-tracing.spec.ts with an in-memory exporter — one request produces a server span and a child span for the adapter call sharing a trace id.
    • Q1.13.b [V1.0] Prune or wire the unused telemetry modules: 47 of the modules in apps/oshun/web/src/analytics/ had no importer on 2026-06-11. Re-count, delete those still unused with their tests, and add one admin analytics view over GET /v1/admin/analytics/events. (13 F11) Verify: new apps/oshun/web/src/analytics/no-orphan-telemetry.spec.ts asserts every non-test module in the directory has an importer outside it; the admin view renders mocked facets.
    • Q1.13.c [V1.0] Make a feature flag gate something, or stop fetching flags on every navigation: no component calls useFeatureFlag. Wire homeLayoutV2 or domainSwitcher as the first real gate, or defer the bootstrap until a consumer exists. (13 F12) Verify: new apps/oshun/web/src/experimentation/first-flag-gate.spec.tsx — flipping the flag changes the rendered Home layout; or the network panel shows no flag fetch per navigation.
    • Q1.13.d [V1.0] Close /metrics: apps/oshun/bff/src/app.ts:1253 serves route names, volumes and error rates to anonymous callers. Require the observability admin scope or a static bearer from OSHUN_METRICS_TOKEN, closed by default in production. (13 F13) Verify: new apps/oshun/bff/src/__tests__/metrics-auth.spec.ts — anonymous 401, correct bearer 200.
    • Q1.13.e [V1.0] Stop anonymous floods evicting real telemetry: POST /v1/analytics/events prunes one shared ring of 5000 (apps/oshun/bff/src/analytics/events-ingest.ts:38,79). Partition retention by origin, or limit by event volume rather than request count. (13 F14) Verify: extend apps/oshun/bff/src/analytics/events-ingest.test.ts — 6000 anonymous events leave a signed-in member's 10 events readable.

DEPLOY register additions (provisioning, not code)#

Store-listing URLs (smart-app-banner targets malformed/dead) · metis adapter downstream service · channel creds as before. See per-report DEPLOY sections.


Execution order#

S1–S12 strictly first (S1 unblocks honest Tara/Arete data for H5/H6). Then H (bright-line honesty), then T (structural; T1 early — it converts dozens of degraded surfaces), then P, then Q. UX cohesion items inside H/T tasks should be fixed with the task that touches the surface.