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 sharedhouse-user; per-user-scope the global habit/closure/offering stores — (DONE 2026-06-11, commit301f490eb7.) Everyx-oshun-user-idread 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_idcolumn +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 uselistForOwner. 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/observationsunauthenticated + store not user-scoped — every member's "private" field log is one global list — (DONE 2026-06-11, commit9722ac5305.) GET + POST now require the verified bearer;owner_user_idacross all three store tiers with strictlistForOwner(legacy unowned rows served to nobody; snapshot restore normalizes); canonicalv1_nyx_logged_observationdual-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 gainsgetAcademicIntegrityVerdictLearnerId; 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, commit002fbb47eb.) Both POSTs auth-gated +owner_user_idstamped across all three store tiers; the GET is strictlylistForOwner(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
tenantLivingScenesStoreis never enforced server-side — (DONE 2026-06-11, commita37ac70bc8.) 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 thex-oshun-nowclock 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'foroutput-gallery.retag— customer-reachable admin mutation — (DONE 2026-06-11, commite6c840fa00.) Per-tool scope requirements (toolScopeChecks) checked against the caller's REAL scopes; retag demands the same Isis workspace scope as the dedicated admin route; 403tool_scope_missingbefore any execution. (09 F1) - S8. Kill-switch
ExecutionTargetbuilt from client body — family/ agent/tenant switches evadable by request shaping — (DONE 2026-06-11, commite6c840fa00.) 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-declaredrootAgentId— 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.
recordExecutedRunblind upsert by client runId — any user can overwrite another user's persisted AgentRun + approval history — (DONE 2026-06-11, commite6c840fa00.) 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/invoicesunauthenticated + unscoped — dumps every customer's invoice ledger (purchaserUserId, plan, amounts, txids) — (DONE 2026-06-11, commitf705e547ba.) 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/messagewith 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
a37ac70bc8with 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-1077defaults to[], guarded byapps/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-600gives 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:558mounts<DailyPlanV2 />with no props whileuseRoutines()anduseRoutineRecommendations()inapps/oshun/web/src/lib/hooks/use-bff.tshave zero callers on Home. Pass the hook results asroutinesandrecommendation; a member with no routines keeps the existing empty state;HomeWorkspace.tsximports noSIMULATED_*export. (01 §1) Verify: newapps/oshun/web/src/components/home/__tests__/HomeDailyPlanLive.spec.tsxmocks 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 toplan: 'pro',verified: trueand the 7/18/5 demo stats when the session is silent. Default to'free',falseand zeros; keep the demo numbers behind an explicit demo-seed argument only. (01 §4) Verify: newlibs/oshun/auth/src/profile-store-defaults.spec.ts— a session with no plan or verified flag yields free, unverified, zero stats;npx vitest runon that file fromlibs/oshun/auth. - H1.c [V1.0] Give
stats.streakDaysandstats.activeDomainsa real writer or stop rendering them: they are read atapps/oshun/web/src/components/home/HeroBanner.tsx:461,1031andapps/oshun/web/src/components/ProfileSettingsPanel.tsx:263-265,961-963and nothing ever writes them. Derive the streak fromGET /v1/arete/streakand active domains from the member's real activity, or remove both tiles. (01 §4) Verify: newapps/oshun/web/src/components/home/__tests__/HeroBannerStreakSource.spec.tsx— the rendered streak equals the mocked/v1/arete/streakvalue and no streak line renders when the read fails. - H1.d [V1.0] Delete the fabricated Home activity strip:
FALLBACK_ACTIVITYinapps/oshun/web/src/components/home/ActivityFeed.tsx:77is the initial state (:441) and the error state (:477,507,539). Start empty, and on failure render the same unavailable presentation/activityuses (apps/oshun/web/src/components/CustomerShellOutageBanner.tsx) with no first-person entries. (01 §6) Verify: extendapps/oshun/web/src/components/home/__tests__/ActivityFeed.test.tsx— a 500 from/v1/activityrenders zero activity rows and the outage reason;grep -n FALLBACK_ACTIVITYreturns nothing in the component. - H1.e [V1.0] Stop swapping simulated recommendations into Home:
apps/oshun/web/src/components/home/MixedDomainRecommendationsLane.tsx:61,112servesSIMULATED_CROSS_RECOMMENDATIONSon error or on an empty payload, andapps/oshun/web/src/components/cross-domain/CrossDomainRecommendations.tsx:945,972defaultsuseDefaultFallbacktotrue. Empty payload renders the cold-start invitation, error renders a disclosed unavailable state, and the default flips tofalse. (01 §7) Verify: extendapps/oshun/web/src/components/home/__tests__/MixedDomainRecommendationsLane.test.tsx— empty and error responses each render zero recommendation cards and nosourceContextcopy. - H1.f [V1.0] Remove the fabricated Arete practice fallback:
FALLBACK_HOME_PRACTICEinapps/oshun/web/src/components/home/homeAretePracticeModels.ts:132-219(streaks 12/9/17, progress 68/54/73) renders while/v1/arete/practice/homeis loading or failing. Mirror the Veritas module (homeVeritasBriefingModels.ts): stage copy plus "practice data unavailable", zero numeric claims. (01 §16) Verify: extendapps/oshun/web/src/components/home/__tests__/HomeAretePracticeSection.test.tsx— withpractice: nullthe section renders no digit followed bydayor%. - H1.g [V1.0] Remove the fabricated Nyx perspective fallback:
FALLBACK_HOME_PERSPECTIVEinapps/oshun/web/src/components/home/homeNyxPerspectiveModels.ts:96-212asserts "Jupiter at opposition" tonight and a 42% observation in progress. The fallback claims no event and no continuation. (01 §17) Verify: extendapps/oshun/web/src/components/home/__tests__/HomeNyxPerspectiveSection.test.tsx— withperspective: nullthe textJupiterand any percentage are absent. - H1.h [V1.1] Apply the same defaults on mobile:
apps/oshun/mobile/src/auth/mobile-auth-context.tsx:241-243falls back toplan ?? 'pro',verified ?? trueand passesstats: undefined, which hands a new member the 7/18/5 demo stats. (01 §4) Verify: newapps/oshun/mobile/src/auth/mobile-auth-profile-defaults.spec.tsx(jest, run fromapps/oshun/mobile) — a fresh session yields free, unverified, zero stats.
- Already fixed (checked 2026-09-18): the daily-plan fixture default is gone
—
- 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-scopedGET /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:
createDefaultOshunBillingSnapshotinlibs/oshun/auth/src/billing-store.ts:979-1025seedscurrentPlan: 'pro', "Visa ending in 4242" and a paid 1200-cent invoice for every member. Seedfree, no payment methods, no invoices. (01 §3) Verify: newlibs/oshun/auth/src/billing-store-seed.spec.ts— the default snapshot has planfree, 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-71builds it onlocalStorageonly. Hydrate plan and renewal fromGET /v1/billing/subscription-managementand invoices fromGET /v1/payments/invoices; an unreachable BFF renders a disclosed unavailable state, never the seed. (01 §3) Verify: newapps/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:
handlePurchaseClickinapps/oshun/web/src/components/profile/BillingSection.tsx:128-142mutateslocalStorage, announces "active · renews" and callssyncProfilePlan(:1524), which flips the client plan thatapps/oshun/web/src/components/billing/ShellFeatureGate.tsx:46-47reads. Send the member to the crypto quote (/billing/crypto) or the Stripe checkout the fiat rail exposes, or show an honestnot_configuredstate; the client never writes its own plan. (01 §3) Verify: extendapps/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:
addBackupPaymentMethodinapps/oshun/web/src/components/profile/BillingSection.tsx:248-262invents 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 callsupsertPaymentMethodwith a literallast4. - H2.e [V1.0] Replace "Report failed charge": the button at
apps/oshun/web/src/components/profile/BillingSection.tsx:689callssimulateBillingFailure(:295), which injects a declined-card state into the member's own billing. Post a billing-category report toPOST /v1/feedback(apps/oshun/bff/src/routes/feedback.ts) instead and deletesimulateBillingFailure. (01 §22) Verify: same spec as H2.c — clicking the button sends one/v1/feedbackrequest and the billing snapshot is unchanged. - H2.f [V1.0] Build the member billing view:
apps/oshun/web/src/app/billing/page.tsxis 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: newapps/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:41uses the same seed andapps/oshun/mobile/app/(tabs)/profile.tsx:1233renders its invoice. (01 §3) Verify: newapps/oshun/mobile/src/profile/billing-store-hydration.spec.ts(jest) — a fresh member renders free with no invoice.
- Re-checked 2026-09-18: every finding still holds. The BFF has since gained
a Stripe fiat rail (
- H3. Web notifications center +
/v1/notificationsfabricate seven canonical notifications (fake billing alarm, fake safety review, fake data-export-ready) whenever the live feed is empty — (DONE 2026-06-11, commit5e0040e295.)buildCanonicalLaneNotificationsDELETED 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-1237now callsrequestMobileCapturePermission, which asks the OS throughapps/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 stopsyncNotificationsreturning 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: newapps/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 fromGET /v1/iris/adapter/reviewand/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: extendapps/oshun/mobile/app/(tabs)/profile.test.tsx— with an empty review response no entry mentioningRenatarenders 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:36callsbuildMobileHomeIrisHandoffState()with no arguments, which renders the fixture inapps/oshun/mobile/src/navigation/mobileIrisHandoff.ts:30,84(memory counts, "64% progress"). Lift the home tab's/v1/iris/mobile-handofffetch into a shared hook and render "no handoff waiting" when it is null. (01 §10) Verify: extendapps/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:48callsbuildMobileUniversalShareModel()with no requests, soapps/oshun/mobile/src/share/mobileUniversalSharing.ts:47,63-130invents an active link and an invite tolearner@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: extendapps/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:911builds state frombuildDefaultMobileCalendarSyncItems()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: extendapps/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 onexpo-cameraandexpo-image-picker(both inapps/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: extendapps/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.
- Already fixed (checked 2026-09-18): the capture "permission granted"
fiction —
- H5.
/aretehub is the last fixture room + all 8/arete/*depth pages serve fabricated intimate narratives — (DONE 2026-06-12, commit559bad49b5.) NewGET /v1/arete/roomover 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 honestareteUnavailable()fallback; AreteRoom discloses empty states. The 8 fixture depth pages carry the preview register viaapp/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.) NewAreteHabitCheckInControls(done/partial/skip/decline) on every/arete/habitsrow 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, commit8c435e26f6.) Coach chat starts empty and routes every turn through the real/v1/arete/coach/responsescomposer (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, commitf3ed8125a4.) Newapp/domains/layout.tsxmounts a path-resolvedDomainPreviewBannerForPathfor the WHOLE segment (all 55 deep pages + future ones); the duplicate room-level mount removed;DOMAIN_PREVIEW_SURFACESstays 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, commit1783477729.) Newapp/veritas/layout.tsxmounts 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/activityfabricates Nisaba/Metis timeline items for every member;/v1/metis/roomfabricates streak/minutes/assessment/tutor nudge unguarded in prod — (DONE 2026-06-12, commitba6e01061a.) Activity nisaba branch now reads the member's REAL notebooks; metis branch reads REAL tutor sessions + ingested outlines (newmetisIngestJobStore.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:courseProposalsstore 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, commitc705918aac.) 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.tsxhas no simulated progress, and a failed history read setshistoryUnavailable(: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-475delegates Nisaba and Metis toapp.domainAdapterslike 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-690still onlycommit()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: newapps/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 403step_up_required.
- Already fixed (checked 2026-09-18): forget-a-fact, pause and export reach
the server —
- 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, commit5e935fbf06.) Evidence queue carries an explicit renderedseedDataNotice(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-lessprovisionAccount+ tenant seat; 'entitlements' → persisted-plan flip); unwired kinds fail LOUD; row failures fail the op while recording honest partialcommitOutcomes(new contract field). Integrations registry: production boots EMPTY; dev seeds carry an auto-droppingseedDataNotice; the deadPlaceholderDataBanneris 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 carriesorigin: '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-1802binds in-process adapters by default (apps/oshun/bff/src/nyx/in-process-adapter.tsover the@oshun/domain-nyxephemeris and the durableapps/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 inapps/veritas/api/src/interfaces/http/routes/v1/oshun-facade.ts:247-752, mounted atapps/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.tstakes 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). Requirex-oshun-service-tokento equalVERITAS_OSHUN_FACADE_TOKEN, refuse in production when the variable is unset, and keep development open. (03 §1) Verify: newapps/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 ofcreateDomainServiceAdaptersthrough it. (03 §1) Verify: newapps/oshun/bff/src/adapters/veritas-oshun-facade-contract.spec.ts— all 17 operations parse throughnormalizeDomainServicePayloadwith no drift.
- Already fixed (checked 2026-09-18): Nyx, Nisaba and Metis no longer dial a
downstream service —
- 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:796callsmemoryBridge.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:259only GETs. Add grant and deny controls per category on/profile/memorythat POST withrole=settingsand re-hydrate, so durable memory becomes reachable. (06 §3) Verify: newapps/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
memoryConsentGrantedfrom 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: newapps/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) andRecallPipeline(libs/oshun/memory-iris/src/recall/pipeline.ts) have zero consumers inapps/. RouteGET /v1/iris/adapter/search(apps/oshun/bff/src/routes/iris.ts:650) and the assistant recall atassistant.ts:796through them so suppressed and sensitive categories never reach a turn. (06 §2) Verify: newapps/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 inapps/oshun/bff/src/adapters/iris-memory-adapters.ts. (06 §2) Verify: newapps/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 therole=adminreads ofapps/oshun/bff/src/routes/iris.tsso an operator inspection needs a reason, a time box and an audit event. (06 §2) Verify: newapps/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) andassessIrisProfileMemoryDecay(retention-compaction.ts) underlibs/oshun/memory-iris/src/. For each either wire one real caller in the BFF or mark the module@deprecatedwith a dated note naming the release it waits for. (06 §2) Verify:grep -rnfor each symbol overapps/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 renderGET /v1/iris/continuation-tokensas Home continuation cards. (06 §17) Verify: newapps/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/mobilehas only the indicatorsrc/components/AssistantMemoryState.tsxand 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: newapps/oshun/mobile/src/memory/MobileMemoryControls.spec.tsx(jest) — forget callsDELETE /v1/iris/adapter/memory/:id; consent calls the POST of T2.a.
- Already fixed (checked 2026-09-18): the bridge is bootstrapped and closed
per session —
- 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/personaand its route agree —GET /v1/persona/voice(apps/oshun/bff/src/routes/personas-consumer.ts:144-185) servesproviderId,voiceKind,defaultLocaleand a nullpreviewUrl, which is whatapps/oshun/web/src/app/profile/persona/page.tsxrenders. - T3.a [V1.0] Key Lilith policy packs by launch-roster ids:
apps/oshun/bff/src/adapters/lilith-persona-policy-adapters.ts:37,159,257knows onlytara-contemplative-guideandveritas-stoic-scholar, socheckPersonaTopic('lotus-mindful', …)finds no pack. Give every persona inlibs/oshun/persona-registry/src/launch-roster.tsa pack. (06 §10) Verify: newapps/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-84governspersona-zen-guideandpersona-stoic-guide, which no member can select. Re-key to the roster, and make a retired or deprecated persona disappear fromapps/oshun/bff/src/routes/personas-consumer.tsbrowse and select. (06 §10) Verify: extendapps/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 thepersonaHandoffbody field the BFF never reads. (06 §10) Verify: extendapps/oshun/web/src/components/assistant/__tests__/AssistantPanel.test.tsx(or a new.spec.tsxbeside it) — switching persona issues one/v1/personas/selectcall 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,339hardcodes "Guide One". Resolve the member's selected roster persona, or the roster default. (06 §10) Verify: newapps/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 withresolveEffectiveToneBand. (06 §11) Verify: newapps/oshun/bff/src/generation/tone-band-admit.spec.ts— acontemplative-strictrequest above the band's audacity cap is refused with a typed reason. - T3.f [V1.0] Enforce tone bands on sharing: call
enforceShareControlToneinapps/oshun/bff/src/routes/library-sharing.tsand the Living Scenes share create inapps/oshun/bff/src/routes/living-scenes.ts. (06 §11) Verify: newapps/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:
checkPublishLineageandemitLineageBinding(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.tskeep route). (06 §11) Verify: newapps/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:
speakTextbuilds a bareSpeechSynthesisUtterance(apps/oshun/web/src/components/assistant/AssistantPanel.tsx:4369) and never assigns a voice, rate or pitch. Map each roster persona to aspeechSynthesis.getVoices()profile as the V1.0 binding; the hosted TTS provider stays a deploy-time setting. (06 §16) Verify: newapps/oshun/web/src/components/assistant/__tests__/assistant-voice-binding.spec.ts— two personas produce utterances with differentvoiceorrate.
- Re-checked 2026-09-18: all five id universes are still disjoint and the
tone-band functions still have no caller outside
- 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-903appends every call totoolCalls, andapps/oshun/bff/src/agentic/runs-route.ts:662-664persists the run with its audit events. - Already fixed (checked 2026-09-18): meters are debited within a run —
governed-orchestrator.ts:269threadsconsumeBudget. - T4.a [V1.0] Connect approval to execution:
approve()moves a run toqueued(apps/oshun/bff/src/agentic/run-lifecycle-store.ts:730) and nothing consumes queued runs, while the execute route answers 409run_already_activefor 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 isqueued, refuse a gated run that is not, and stamp the result back onto the lifecycle record. (09 F4) Verify: extendapps/oshun/bff/src/agentic/runs-route.test.ts— submit, approve, execute reachescompleted; executing a gated run that is stillawaiting_approvalanswers 409. - T4.b [V1.0] Bind a server budget registry:
resolveBudgetsis an optional hook (apps/oshun/bff/src/agentic/runs-route.ts:404,648) thatapps/oshun/bff/src/server.ts:3807never passes, and the route still mergesbody.budgets(:649). Add a Postgres-backed per-tenant, per-agent, per-tool registry, pass it asresolveBudgets, persist meters across runs, and stop accepting client envelopes in production. (09 F5) Verify: extendapps/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 nobudgetsin either body.
- Already fixed (checked 2026-09-18): executed envelopes now carry
transcripts —
- 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) callsstore.authenticateApiKeyand 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 ontorequest.authContext) and mount it on the first real partner-facing route group; the SCIM routes of P16.a are the intended first consumer. Noteapps/oshun/bff/src/middleware/authz.tsis now a re-export shim; the middleware lives inlibs/shared/bff-kit/src/authz.ts. (10 §1) Verify: newlibs/shared/bff-kit/src/api-key-auth.spec.ts— missing key 401, wrong scope 403, revoked key 401, valid key populatesauthContext.tenantId. - T5.b [V1.0] Enforce the stored rate limit:
evaluateApiKeyRateLimit(libs/oshun/tenant-console/src/integrations/api-keys.ts:85) has no caller, sorateLimitPerMinuteis configuration nothing reads. Call it in the preHandler of T5.a with the window kept in the existing abuse-protection store and answer 429 withretry-after. (10 §1) Verify: same spec as T5.a — request N+1 inside a minute answers 429 with a positiveretry-after.
- Already fixed (checked 2026-09-18): a request can present a key —
- 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:
executeWebhookDeliveryandcreateWebhookDeliveryExecutorstill have no caller underapps/oshun/bff/src. - T6.a [V1.0] Wire a
fetch-basedWebhookHttpTransportandcreateWebhookDeliveryExecutor(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: newapps/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 failsSIGNING_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: newapps/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
WebhookEventand callsubscriptionsForEventfrom 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 onepayment.settledevent to a subscribed listener and none to an unsubscribed topic.
- Re-checked 2026-09-18:
- 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,runDryRunandevaluateAuthChallengeare still consumed only byapps/oshun/tenant-admin/src/app/roles/page.tsxandapps/oshun/tenant-admin/src/app/identity/page.tsx:115; the live check isscopes.includes(...)inlibs/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-templatesCRUD overlibs/oshun/tenant-console/src/roles/roles.ts(validateTemplateWithinApprovedBounds,transitionAssignment). (10 §3) Verify: newapps/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
effectiveCapabilitiesinto the scope set, so editing a template changes what the next session can do. (10 §3) Verify: newapps/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
runDryRunthat 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 withevaluateAuthChallenge(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 increateAuthPreHandlerfor step-up routes, answering 403step_up_required. (10 §8) Verify: newapps/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 answersstep_up_required.
- Re-checked 2026-09-18:
- 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-supporthas 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,advanceDunningStageanddegradedEntitlementOnLapse(libs/oshun/billing-support/src/dunning/dunning.ts:83,115,155) have no app caller. Drive them from the Stripeinvoice.payment_failedand subscription-lapse events inapps/oshun/bff/src/payments/fiat-routes.ts, and degrade the plan throughapps/oshun/bff/src/billing-support/plan-sync.tswhen the grace window ends. (11 §2) Verify: extendapps/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 tofree. - T8.b [V1.0] Give the crypto rail a billing period: settlement still
calls
updatePlanDurablywith 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 throughapps/oshun/bff/src/payments/quote-builder.ts, feed the offsets oflibs/oshun/payments-bridge/src/customer-surface/reminder-cadence.tsinto the reminder dispatcher, and apply the same lapse path as T8.a. (11 §3) Verify: newapps/oshun/bff/src/payments/crypto-renewal-worker.spec.ts— with a fixed clock, a plan past its period end with no settled renewal projects tofree; 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. AddPOST /v1/support/cases(session-stamped) over a Postgres store, assign queue and SLA withrouteCaseandslaForCase(libs/oshun/billing-support/src/support-cases/support-cases.ts:88,122), add an admin queue read and decide pair, and replace themailto:onapps/oshun/web/src/app/billing/page.tsxwith the intake. (11 §4) Verify: newapps/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,evaluateCaseSlaflags it after the deadline. - T8.d [V1.0] Stamp severity and SLA on moderation work: call
classifySeverityandslaForfrom@oshun/trust-safetywhen an item entersapps/oshun/bff/src/moderation/moderation-store.tsor the abuse-report store, and expose the breach state on the admin read. (11 §5) Verify: newapps/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
applyOffenseon 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/dashboardoverbuildSafetyDashboard, fed from the real moderation and abuse-report stores. (11 §5) Verify: newapps/oshun/bff/src/routes/admin-safety-dashboard.spec.ts— counts equal the rows in the stores; an empty deployment reads zeros.
- Already fixed (checked 2026-09-18):
- 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-botis 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 byapps/oshun/bff/src/telegram/deployment-parity.spec.ts; webhook registration is scripted ininfra/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_TOKENandOSHUN_TELEGRAM_WEBHOOK_SECRETin the Hetzner stack.env, and runsinfra/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 stagingshows the staging URL with no last error, and a/startsent to the bot is answered in the chat.blocked:external
- 2026-09-18: every code finding re-checked and found fixed — the BFF webhook
delivers through
- 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:
createSharedAnalyticsSinksis used only bytaraAnalytics.ts,studioWorkspaceMountTelemetry.tsandtransport.tsunderapps/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 inapps/oshun/web/src/lib/providers.tsx, besideExperimentationBootstrap. (13 F2) Verify: newapps/oshun/web/src/analytics/analytics-singleton-boot.spec.tsx— after the provider mounts,getOshunAnalytics().track(...)reaches the mockedPOST /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,notificationCenterTelemetryandnisabaStudyTelemetryinapps/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/analyticsprints 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. CalluseTaraAnalytics()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=*.tsxshows a component importer, or the file is gone.
- Re-checked 2026-09-18:
- 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/gradebooknow has a runtime consumer —apps/oshun/bff/src/metis/gradebook-store.ts(GradebookEmitter), used byapps/oshun/bff/src/routes/metis-integrity.ts:21.@metis/assessment,@metis/learningand@metis/tutoringstill have no consumer underapps/oshunorapps/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) intolibs/metis/tutoring/src/loop/adaptive-loop.tswith IRT item selection from@metis/assessment, and return the next item. (04 §5) Verify: newapps/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.tsin 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 theirnot_configuredseams until an LMS endpoint is provided. (04 §5) Verify: extend the specs besideapps/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.tson the call path with a per-endpoint kill switch. (04 §5) Verify: newapps/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.
- Partly fixed (checked 2026-09-18):
- 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/analyticsat: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: newapps/oshun/bff/src/routes/tara-single-plane.spec.ts— completing one sitting changes/v1/tara/history,/v1/tara/streakand/v1/tara/analyticstogether. - T12.b [V1.0] Arete streak:
/v1/arete/streakcomputes 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: newapps/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:1928andlogHabitCompletionatapps/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 atapps/oshun/bff/src/server.ts:1903). - Already fixed (checked 2026-09-18): the link routes somewhere —
apps/oshun/web/src/app/share/[token]/page.tsxexists 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-180ships four fixture collections and keeps all collection CRUD inlocalStorage(oshun.library.collections.v1), never calling/v1/library/collections. Reuse the saved-items sync pattern ofapps/oshun/web/src/lib/library/webLibraryStore.ts; drop the fixtures or gate them to signed-out guests. (05 §collections) Verify: newapps/oshun/web/e2e/library-collections-one-system.spec.ts— a collection created on/library/collections/newappears in the dashboard manager, and one created in the dashboard appears on/library/collectionsafter reload. - T13.b [V1.0] Make a granted share carry content:
GET /v1/library/shared/:collectionIdreturns 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: newapps/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:
setVisibilityandcreateShareLinkmake the first caller the owner of anycollectionId(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.
- Already fixed (checked 2026-09-18): share policies and links are durable —
- 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 fromartifact.score(apps/oshun/bff/src/routes/living-scenes.ts:1380-1385), so every scene is the same gradient. Derive per-segment tone, frame count fromdurationSecondsand phase fromperSegmentSeedsHex, and concatenate the segment runs into the APNG. (08 §3) Verify: newapps/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, anddecideCrisisAction(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 inlibs/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 inV1/TODOS.mdwith 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:57still reads the fixture route/v1/data-rights/consent, and nothing underapps/oshun/web/srccalls/v1/consent. - T15.a [V1.0] Point
/profile/dataat the real ledger: readGET /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: newapps/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.tsalready refuses those modalities without a granted flow. (11 §9) Verify: extendapps/oshun/bff/src/routes/personas-consumer.test.tsand 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/consentfromapps/oshun/bff/src/routes/domain-stubs.tsonce T15.a lands. 2026-09-18: depends on T15.a. (11 §9) Verify:grep -rn "data-rights/consent" apps/oshunreturns nothing.blocked:upstream
- Re-checked 2026-09-18:
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-142still reportsmissing-configin production andpreviewelsewhere without attempting a send. This is the same defect as the verification-code item ofdocs/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.tsdispatch the minted code throughdeliverWithEnvProviders(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; keepmissing-configonly when the environment has no email provider. (12 §3) Verify: newapps/oshun/bff/src/routes/channel-bindings-delivery.spec.tswith 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 yieldsstatus: '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-configand 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 ismissing-configand 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
- Re-checked 2026-09-18:
- 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
/quietwritesnotificationPreferencesStore(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 overwriteenabledwith 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: extendapps/oshun/bff/src/reminders/reminders-route.test.ts— after a Telegram/quieta reminder inside the window is suppressed; after the PATCH withenabled: falseit 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. DropuserOptInsto empty when the member's masterpushEnabledis off or the reminder's domain hasmodalities.pushorkinds.reminderoff (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:
toChannelSetinlibs/oshun/messaging-channels/src/v3-session-reminders.ts:308-313treats a missinguserOptInsas opted in. For every channel except in-app, absence means suppress. (12 §5) Verify: extendlibs/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-366adds templatecomponents, fed bydelivery.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 globalOSHUN_WHATSAPP_TEMPLATE. (12 §6) Verify: extendlibs/oshun/messaging-channels/src/whatsapp/whatsapp.test.tsanddelivery.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/whatsappin the BFF feedingingestWhatsAppStatusWebhookinto a suppression store the dispatcher consults, with Meta's signature check failing closed when no app secret is set. (12 §6) Verify: newapps/oshun/bff/src/routes/whatsapp-status-webhook.spec.ts— anopted_outstatus 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:106takesOSHUN_FCM_ACCESS_TOKENfrom the environment. Accept a service-account JSON, sign the JWT-bearer grant withnode:crypto, cache the access token and refresh before expiry, mirroring the APNs JWT intransports.ts. (12 §7) Verify: extendlibs/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
- Already fixed (checked 2026-09-18): WhatsApp sends carry the body —
- 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/srcorapps/oshun/mobile/srccalls/v1/reminders/inbox, andapps/oshun/bff/src/routes/notifications.tsdoes not merge it. - P4.a [V1.0] Merge
readReminderInbox(userId)into the/v1/notificationspayload as kindreminder, with the domain threaded through the dispatcher (the gap H3 recorded) and read state through the existing read-state store, soapps/oshun/web/src/components/NotificationsCenterPanel.tsxshows them with no client change beyond the new kind. (12 §8) Verify: newapps/oshun/bff/src/routes/notifications-reminder-inbox.spec.ts— a produced reminder appears once in/v1/notificationsfor 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
- Re-checked 2026-09-18: nothing under
- 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 durableapps/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-levelMapinapps/oshun/bff/src/nyx/event-action-state-store.ts:83, and in production the store isnullso the route answers 503 (nyx.ts:263-264). Write saves, follows and reminder routes through tonyxMemberStateStore(or make it the single authority) so the feature works in production and survives a restart. (03 §5) Verify: newapps/oshun/bff/src/routes/nyx-event-actions-durable.spec.ts— a reminder set through the PUT is returned bygetEventRemindersfor that member, is absent for another member, and is present after the store is re-opened.
- Partly fixed (checked 2026-09-18): the reminder worker reads
- 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=trueis refused in production —assertProductionDomainAdapterSafety(apps/oshun/bff/src/adapters/domain-adapter-runtime-safety.ts:11-29), called first increateDefaultDomainAdapters(apps/oshun/bff/src/app.ts:1763). - P6.a [V1.2] Give the cascade a customer read path:
/v1/veritas/driftand/v1/veritas/retractionsareguardedFixtureRoutes (apps/oshun/bff/src/routes/domain-stubs.ts:2628-2631). Replace them with reads over the cascade records written byapps/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: newapps/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
credibilityTierfromcomputeSourceQualityCompositeandbandFromComposite(libs/oshun/domain-veritas/src/source-quality/) over the real source records the facade reads. (03 §7) Verify: new spec besideapps/veritas/api/src/interfaces/http/routes/v1/oshun-facade.ts— a source with recorded assessments gets the band the composite yields; one with none readsunknown. - 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-authoringis used instead, delete the Veritas duplicate (this is the second machine P19 counted). (03 §7; 05 §lifecycle) Verify: newapps/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 fromauthContext.
- Already fixed (checked 2026-09-18):
- 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/verificationhas no BFF consumer. AfterbuildSophiaGroundedAnswer(apps/oshun/bff/src/routes/domain-stubs.ts) composes an answer, run claim extraction, independent retrieval and claim-to-source matching fromlibs/sophia/verification/src/, flag unsupported claims in the response, and bind any model call to OpenRouterdeepseek/deepseek-v4-flash-0731in tests. (07 §5) Verify: newapps/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
- Already fixed (checked 2026-09-18): the customer answer renders grounding
state, claim labels and abstention —
- 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-17is 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 staysqueueduntil an operator drains it. MirrorOSHUN_REMINDER_WORKER_INTERVAL_MSwith its own variable, off by default, holding the same lease pattern asapps/oshun/bff/src/reminders/cycle-lease.ts. (07 §7) Verify: extendapps/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:
bindGalleryStoreandbindGalleryContext(apps/oshun/web/src/app/studio/generation-gallery/loader.ts:43,47) are never called, so every member sees an empty gallery, and thelaunch-branch,send-to-editorial,send-to-asset-libraryandtakedownbuttons (GenerationGalleryClient.tsx:745-786) have no handler. Bind the loader to the member's released outputs inapps/oshun/bff/src/generation/output-catalog.tsand wire each button to its BFF route. (07 §4) Verify: newapps/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 haveapplyDecisioninapps/oshun/bff/src/isis/civitai-intake-store.tsinsert a normalized record on approve or conditional approve. (07 §6) Verify: newapps/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.
- Partly fixed (checked 2026-09-18): the managed-models registry no longer
shows seed models —
- 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
resolveShareabilitywithdefaultShareabilityGrants(reachForPrivacy(...))(apps/oshun/bff/src/routes/living-scenes.ts:1455), which grants every component the requested reach. Derive thetenant-policy,intentandlilith-policygrants from their real sources and give the rest the rationale "no restricting record". (08 §7) Verify: newapps/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
publicRedactedTextverbatim, andredactIntent(libs/yemaya/living-scenes-runtime/src/cue-privacy/intent-redaction.ts) has no BFF caller. RecomputeprivateIntentHashand overwrite the public layer withredactIntent(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 underapps/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: newapps/oshun/bff/src/routes/living-scenes-first-frame.spec.ts— the route returnsimage/pngwhose 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.tspasseswatermarkto the renderer; the short code is only a response header (living-scenes.ts:2402) and a DOM overlay. PassvisibleMark.shortCodesoembedWatermarkMarkruns 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]inapps/oshun/web/src/app/scene/[id]/page.tsx:199-262, so it keeps playing, andhonorsReducedMotionPreferenceis a hardcodedtrue(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 forrendition: 'reduced-motion'. 2026-09-18: uses the route of P9.c. (08 §14) Verify: newapps/oshun/web/e2e-inspect/scene-reduced-motion.spec.ts(chromium,workers=1,reducedMotion: 'reduce') — the animated image is not visible and the still is.
- Already fixed (checked 2026-09-18): share media is in the durable snapshot
(
- 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-118acceptsrole: 'assistant' | 'system'from the client and records content with noanalyzeMessageSafetyand no crisis-frame check. Forcerole: 'user'for client turns and mirror the assistant route's interception. (06 §12) Verify: extendapps/oshun/bff/src/psyche/realtime-route.test.ts— a client turn claimingassistantis stored asuser; 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,609returnsconnectionState: '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: newapps/oshun/bff/src/adapters/psyche-session-honesty.spec.ts— requesting a conference modality answers the typed not-built error; the text session carries nolatencyMsor 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 inrealtime-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 (
isCrisisFrameActiveForUserinapps/oshun/bff/src/routes/living-scenes.ts). - P11.a [V1.0] Compute the crisis flag on the server for memory:
lilithCrisisFrameon/v1/iris/adapter/search,plan-writeandrememberis whatever the client sends (apps/oshun/bff/src/routes/iris.ts:704,919). OR the hint withisCrisisFrameActiveForUser(userId). (06 §14) Verify: newapps/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 callisCrisisFrameActiveForUser. (06 §14) Verify: newapps/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:63hardcodesregion: 'global'and serves US numbers to everyone. Pass profile region andAccept-LanguageintorouteCrisis(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: newapps/oshun/bff/src/routes/safety-crisis-region.spec.ts— aGBcaller 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
planCheckIninto the reminder dispatcher (opt-in) and consultisInCooldownin the recommendations route. (11 §10) Verify: newapps/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
createMandatoryReportandselectLegalContacton frame resolution, producing an operator-visible record; with no legal contact configured for the jurisdiction the evaluation fails loud asnot_configuredand 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 yieldsnot_configured. - P11.f [V1.0] Extend Lilith message safety beyond the assistant:
analyzeMessageSafetyis called only fromapps/oshun/bff/src/routes/assistant.ts. Run it on Sophia question intake, and runvalidateDraftResponseon composed Sophia output before it is sent, reusing the assistant's interception envelope. (06 §19) Verify: newapps/oshun/bff/src/sophia/sophia-message-safety.spec.ts— a crisis-class question returns the interception envelope with resources instead of a composed answer.
- Partly fixed (checked 2026-09-18): Living Scenes now consults the frame on
the server (
- 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, mapfeature_specificandcontent_specificscopes to their route families, and answer 403account_restrictedwith the appeal link. (11 §7) Verify: newapps/oshun/bff/src/moderation/ban-enforcement.spec.ts— a banned member's mutation answers 403account_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,424readsmoderatorId,reviewerIdandissuedByfrom the body. Userequest.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: newapps/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.tsholds them in class fields with no sink. (11 §15) Verify: newapps/oshun/bff/src/moderation/moderation-store-durability.integration.spec.tsagainst 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.tsknowspending | confirmed | expired | cancelled, the settlement body carries no amount, and the plan flip is a directupdatePlanDurably(settlement-route.ts:302). AddpaidAmountAtomicto the webhook, classify settled, underpaid and overpaid through the bridge'sstate-mapper, and grant the plan from an entitlement-bus consumer as the fiat rail does. (11 §13) Verify: extendapps/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),evaluateRefundEligibilityfrom@oshun/billing-support, enqueue onColdSpendQueuefromlibs/oshun/payments-bridge, and an admin list-and-initiate route. Signing and broadcast stay with the operator's hardware wallet. (11 §14) Verify: newapps/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'sanalyticsflow, and drop events on the server when the bearer session resolves to a withdrawn flow. (11 §12) Verify: newapps/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.
- Already fixed (checked 2026-09-18): every settlement mints an
Ed25519-signed receipt and refuses to settle without a signer
(
- 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/srccallsuseTranslations;apps/oshun/web/src/i18n/messages/contains onlyen/;apps/oshun/web/src/i18n/request.ts:5hardcodes the default locale. - P14.a [V1.0] Make the switcher honest now: in
apps/oshun/web/src/components/LanguageSwitcher.tsxoffer 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: newapps/oshun/web/src/components/__tests__/LanguageSwitcher.spec.tsx— with onlyenpresent 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, mountNextIntlClientProvider, and externalize the shell-level strings (navigation, Home section headings, auth, settings) intomessages/en/shell.jsonwith a pseudo-locale proving nothing is hardcoded. (13 F5) Verify: newapps/oshun/web/e2e-inspect/i18n-pseudo-locale.spec.ts(chromium,workers=1) — under the pseudo-locale the shell chrome on/,/tara,/nyx,/arete,/nisabashows 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 markeddraft, 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
- Re-checked 2026-09-18: no component under
- 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/crashesand/v1/mobile/diagnosticsinapps/oshun/bff/src/routes/mobile-telemetry.ts:110,143. - P15.a [V1.0] Render the real status:
apps/oshun/web/src/app/status/page.tsxshows the hardcodedSTATUS_COMPONENTSand a "Specimen · fixture data" pill (apps/oshun/web/src/components/lilith/system-pages.tsx:602,658) whileGET /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: newapps/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/vitalsand/v1/web/crashes, none of which exist, and nothing calls its init functions. Add receivers on themobile-telemetry.tspattern and initialise from a client-root effect, or delete the directory and route errors throughPOST /v1/analytics/events. (13 F3) Verify: newapps/oshun/bff/src/routes/web-telemetry.spec.tsplus a web spec that a thrown render error produces one stored report; orls apps/oshun/web/src/observabilityfails andapp/error.tsxreports through the analytics ingest.
- Already fixed (checked 2026-09-18): mobile crashes and diagnostics have
receivers —
- 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/Usersand/scim/v2/Groupsinapps/oshun/bff/src/tenant-console/translating throughapplyScimSync(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: newapps/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 (
tidplustenant:admin:{tid}) from the SSO callback when the connection's tenant has the member, set the tenant-admin cookie on a/handoffcompletion route, and stop parsingtenant.tokens as dev tokens. (10 §9) Verify: newlibs/shared/bff-kit/src/tenant-session.spec.ts— withNODE_ENV=productiona signed tenant token authenticates and an unsignedtenant.token is refused. - P16.c [V1.0] Export real tenant data:
apps/oshun/bff/src/routes/admin-bulk-exports.ts:62takesrecordsfrom the request body. Add server-side sources perresourceKind(members, tenant-filtered audit events) and keep pasted records as an explicit custom mode. (10 §5) Verify: newapps/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.
- Re-checked 2026-09-18: all three findings still hold. Tenant session tokens
are still unsigned and gated with the dev tokens, now in
- 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:
submitReflectioninapps/oshun/web/src/components/lilith/TaraSitPlayer.tsx:391-403waits 300 ms and setssavedwith no request; offline it writes alocalStoragekey nothing reads;moodAfteris never sent. Add a reflection leg to the sitting routes inapps/oshun/bff/src/routes/domain-stubs.tscarrying{ reflectionText, moodAfter }, flush the offline queue on mount, and show "Saved" only after a 2xx. Work this child first. (02 §5) Verify: extendapps/oshun/web/src/components/lilith/TaraSitPlayer.test.tsx— with the BFF mocked to fail, the status is neversaved; 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 frompaused. (02 §12) Verify: extendapps/oshun/web/src/components/lilith/TaraSitPlayer.test.tsxwith fake timers — a 180-second sitting with no input reachescompleted. - P17.c [V1.0] Replace the Living Offerings fixture composer:
/arete/offeringrendersgetAreteOfferingFixture()(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, andvoiceSpeed(TaraSitPlayer.tsx:109,636-647) is read by nothing. Fetch guidance at opening, midpoint and closing with a silent skip on 503, and driveaudio.playbackRatefrom the slider. (02 §14) Verify: extendapps/oshun/web/src/components/lilith/TaraSitPlayer.test.tsx— the guidance URL is requested at the three progress points and the slider changesplaybackRate; 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/journalor/v1/arete/balanceBFF route exists, andAreteJournalReflectionWorkspace.tsxandAreteLifeBalance.tsxunderapps/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: newapps/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/reviewto the real week:getAreteReviewreturns the fixture (arete-depth.ts:381-385) whileGET /v1/arete/reviewcomputes highlights from real check-ins, soAreteReviewClosedurably 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: newapps/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:
getAreteHabitsreturnsgetAreteHabitsFixture()when the BFF read fails (arete-depth.ts:639-643). Return an unavailable shape the page renders as a quiet notice, astaraUnavailable()does. (02 §17) Verify: same spec directory — a failed read yields zero habits andunavailable: true.
- Already fixed (checked 2026-09-18): the offerings index shows the member's
kept and sent rows (
- 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 atapps/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:
buildNisabaRoomstill shipsCross-references: 4andSaved excerpts: 2as 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: extendapps/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_DETAILSinstead of fetching/v1/nisaba/passages/:id(apps/oshun/web/src/components/domains/NisabaSurface.tsx:567), a live but empty notebooks response is replaced byFALLBACK_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: newapps/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_PASSAGEcard (NisabaSurface.tsx:367,1358); wire/v1/nisaba/adapter/saved-passagesor drop the card. (04 §7) Verify: same spec file — with no saved passages the card is absent and no view claimsgrounded. - P18.d [V1.0] Lift the Nisaba preview banner: set
DOMAIN_PREVIEW_SURFACES.nisabatofalseinapps/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,651presents "Renata" marginalia anddayNumber: 109as the member's own, andapps/oshun/web/src/app/nisaba/notebooks/page.tsx:57,86swaps inNOTEBOOK_FIXTUREwith 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: newapps/oshun/web/src/lib/lilith-data/__tests__/nisaba-depth-honesty.spec.ts— no output containsRenata; 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 withsource_not_approvedunless the latest decision for the source is approved, and list which outputs count in the route header. (04 §8) Verify: newapps/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/tutorfrom the real session-memory store:getMetisTutorreturns the fixture transcript (apps/oshun/web/src/lib/lilith-data/metis-depth.ts:74-78) and no product surface posts turns toPOST /v1/metis/tutor-session-memories. List the member's sessions, post turns, and show honest empty otherwise. (04 §9) Verify: newapps/oshun/web/e2e/metis-tutor-live.spec.ts— a posted turn appears after reload; a new member sees no transcript.
- Already fixed (checked 2026-09-18): Nisaba member state and integrity
records are durable —
- 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:19advances throughtryEditorialAdvance. 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.tsis read only by its own routes. Have the communications publish path and the generation release leg refuse or delay a subject with an openembargoorblockedstream. (05 §embargo) Verify: newapps/oshun/bff/src/admin/release-stream-enforcement.spec.ts— publishing an embargoed subject beforeembargoLiftsAtis 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, indocs/audits/V1_RESIDUAL_AUDIT_2026-06-11/05-editorial-studio.mdunder a dated heading. (05 §census) Verify: the dated section exists and every family row carries agrepcommand 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
- Partly fixed (checked 2026-09-18): the canonical machine is consumed —
- 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-112useslatestApproval.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 besideapps/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: extendapps/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
pipelineIdagainstV1_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: extendapps/oshun/bff/src/agentic/run-lifecycle-store.test.ts— an unknown pipeline answers 400;arete.weekly_review_draftstarts inawaiting_approvalwith 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 unregisteredrootAgentId; this also makes the per-agent kill switches of S8 airtight. (09 F11) Verify: extendapps/oshun/bff/src/agentic/runs-route.test.ts— an unregistered agent id answers 400; a per-agent switch stops a registered one.
- Already fixed (checked 2026-09-18): approved runs read as approved —
- 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 (orfastify-type-provider-zod) inlibs/shared/bff-kit/src/and a lint rule that fails a new route file which readsrequest.bodywithout it. (13 F9) Verify: newlibs/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 withnpx vitest run <file>. - P21.c [V1.0] Validate analytics ingest with the library it ships
beside:
apps/oshun/bff/src/analytics/events-ingest.tshand-rolls type checks whilevalidateEventEnvelope(libs/oshun/analytics/src/validation.ts) and the event-name taxonomy go unused. (13 F9) Verify: extendapps/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.
- Re-counted 2026-09-18: 527 route files under
- 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
pushClientStatecall isapps/oshun/web/src/components/workspace/MultiPanelWorkspace.tsx:446andpullClientStatehas no caller. - P22.a [V1.0] Read the durable slot back on boot: call
pullClientState('workspace-layout')and merge byupdatedAtwhenMultiPanelWorkspacemounts. (01 §21) Verify: newapps/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-draftsslot to life: NisabaSurface persists notes, drafts, collections and plans tolocalStorageonly (apps/oshun/web/src/lib/nisaba-offline.ts). Push them throughpushClientState('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:264searches an in-file index with invented freshness and trending and makes no network call. UseGET /v1/searchthrough the typed BFF client, keep curated rows labelled as editorial, drop "trending" until the analytics ingest can supply it. (01 §19) Verify: newapps/oshun/mobile/app/(tabs)/explore-live-search.spec.tsx(jest) — a query issues one/v1/searchcall 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,60sends 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/continuityand the real consent read, use the session user, and include those items only when a real id exists. (01 §18) Verify: newapps/oshun/web/src/components/workspace/__tests__/irisMobileWorkspaceHandoff.spec.ts— with consent ungranted the packet says ungranted and carries no invented counts or items.
- Re-checked 2026-09-18: the only
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-assistantis gated byTestHarnessGate(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-25is 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
/stopis 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/voicehas 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.tsseals each event with an HMAC and the previous event's hash (sealEvent,:138-159),verifyIntegrity(:359) is returned byapps/oshun/bff/src/routes/admin-audit-log.ts:120,146, and production refuses to boot withoutOSHUN_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) andrunComplianceRetentionsweeps about thirty partitions with legal-hold checks (apps/oshun/bff/src/server.ts:957-1250, scheduled at:2075and: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). CaptureDate.now()after mount as the file already does forshellMountedAtMsRef. (01 §24) Verify: newapps/oshun/web/src/components/__tests__/ShellLayoutAccountSwitcherClock.spec.tsxwith 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.tsxserves a hand-written changelog frozen at v0.8–0.9 ("Dark theme polish",:82). Serve entries from the authored-communications store with achangelogkind, or show nothing. (01 §23) Verify: extendapps/oshun/web/src/components/activity/WhatsNewFeed.spec.tsxor 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:522hardcodessessions = 28) andCrossDomainHub. Confirm no page imports them, then remove the components and their tests. (01 §25) Verify:grep -rn "KpiGrid\|CrossDomainHub" apps/oshun/web/src/appprints nothing and the files are gone. - Q1.01.d [V1.1] Persist mobile preferences:
apps/oshun/mobile/src/profile/store.ts:21builds every profile, preference and privacy store oncreateMemoryKeyValueStorage(), so each setting resets on restart. Back them with the app's secure or async storage. (01 §26) Verify: newapps/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'inapps/oshun/web/src/components/home/homeMetisStudyContinuationModels.ts:53is the fallback label. (01 §28) Verify: extendapps/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/homehits 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.tsandcontent-filters.tsinapps/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, andTARA_TODAY_FIXTUREinapps/oshun/bff/src/routes/domain-stubs.tsdescribes a path that contradictsSITTING_PATH. Render the real duration and derive the fixture from the room builder. (02 §19) Verify: newapps/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 fromapps/oshun/bff/src/tara/room.ts. (02 §20) Verify: newapps/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 nohref; repoint the Arete hub habit links and "New ritual" at/arete/habitsand/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.tsxkeeps its empty model when the fetch fails, andapps/oshun/web/src/app/arete/offerings/page.tsxrenders a refused read as "No drafts in progress". Track a failed state and render a quiet could-not-load notice. (02 §23) Verify: newapps/oshun/web/src/components/domains/tara/TaraAnalyticsDashboard-failed.spec.tsxand 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/*/stateroutes that do not exist and has no caller. Delete it with thelastSyncedAtfields that imply a sync, or re-point it at/v1/client-state/:stateKey. (03 §9) Verify:grep -rn hydrateDomainStoresFromBff apps/oshun/web/srcprints 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" writeslocalStorageonly and never reaches the workspace evidence panel or achievements. AddPOSTandDELETE /v1/veritas/adapter/saved-articles/:articleIdover the adapter and write through fromVeritasArticleActions.tsx. (03 §10) Verify: newapps/oshun/bff/src/routes/veritas-saved-articles-write.spec.ts— a saved article appears in/v1/veritas/adapter/saved-articlesfor 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-20and the January-2024 TLE insatellites-service.ts:25. This service is off the V1.0 path (the BFF's Nyx adapter runs in-process); back/api/v1/eventswith the@oshun/domain-nyxsolver and make satellites fail closed asnot_configuredwithout a fresh TLE feed. (03 §11) Verify:npx vitest runon the service's events spec —getUpcomingEventsfor today's date returns future events; satellites without a feed answernot_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 thealignmentsthe compare view shows, which are curated text inapps/oshun/bff/src/nisaba/state.tstoday. The full Nisaba service is a recorded later-release deferral; this is the optional pre-step the report names. (04 §12) Verify: newapps/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: newapps/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,22leads 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: newapps/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.tsservesprogressPercent: 0and no route ever updates it. AddPOST /v1/nisaba/passages/:id/progress(or fold it into the client-state sync) and send it from the reader. (05 §reading progress) Verify: newapps/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:77promises "Drag and drop, reorder". Wire the ordering engine of@oshun/customer-curationinto 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:100andnew/LibraryNewCollectionForm.tsx:130link to/library?collectionId=…, which/libraryignores; 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
/savecaptures somewhere:GET /telegram/captureshas 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: newapps/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-curationannotations 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: newapps/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/authoringholds 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: newapps/oshun/bff/src/studio/authored-artifact-store.integration.spec.tsagainst 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,retrievalTextand the serveractivePersona, and no client reads them. Show a grounded-or-synthesized chip and the server's persona label inapps/oshun/web/src/components/assistant/AssistantPanel.tsx. (06 §22) Verify: extendapps/oshun/web/src/components/assistant/__tests__/AssistantPanel.test.tsx— a reply withresponseMode: '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) andcreateVoiceSafetyAssessmentreturnsclarity: 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: newapps/oshun/bff/src/adapters/lilith-persona-policy-honesty.spec.ts—lastCheckedis 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-858reports constants (9, 7, 12, 5, 6, 11 ms). Time the store call as the Psyche adapter does, or remove the field. (06 §25) Verify: newapps/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 unboundapps/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: newapps/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 preventsplay()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 andburnInAvailable: trueis 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: newapps/oshun/bff/src/routes/living-scenes-captions.spec.ts— the caption URL returnstext/vttwith the transcript's cues; no response containsoshun://. - 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: newapps/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-64now lists exactly the sets inTEMPLATE_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-120of the same spec file still namesshape-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: extendlibs/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:
costUsdandlatencySecondsare 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: extendcustomer-card.test.ts— no estimate depends ondomainId.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 inlibs/yemaya/blend-kernel/src/catalog/cinematographic-catalog.tshas no app consumer. Make compose-assist read the catalog and validate the catalog againstlibs/contracts/src/living-scene/technique.ts. (08 §19) Verify: newlibs/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/cardandPOST /v1/living-scenes/card/cueshave 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: newapps/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 inapps/oshun/bff/src/routes/living-scenes.ts. (08 §22) Verify: newapps/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
checkDeterminismover 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: newapps/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 throughapps/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-schemaScore, 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: newapps/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.tsxruns 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: newapps/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: extendapps/oshun/bff/src/agentic/runs-route.test.ts— the call over the bucket answersthrottledwith 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-controlsis already imported atapps/oshun/bff/src/agentic/run-lifecycle-store.ts:33) and accept amodeon submit that maps to a budget envelope once T4.b lands; list the engines still unused in the route header. (09 F13) Verify: extendapps/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.tsxcan 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: newapps/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: newapps/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,applyRedactionsandbuildCorrelationThreadinlibs/oshun/tenant-console/src/audit-explorer/audit-explorer.tsstill have no app caller, andapps/oshun/bff/src/admin/admin-audit-events-store.ts:13-15still 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: newapps/oshun/bff/src/admin/admin-audit-chain.spec.ts—grep -rn "firehose" apps/oshun/bff/src/admin/admin-audit-events-store.tsprints 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:
generateRawTokenmints API keys and webhook signing secrets and falls back toMath.random()under a "non-secret" annotation (apps/oshun/bff/src/admin/admin-integrations-registry-store.ts:160-165). UserandomBytes(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.tsprints nothing. - Q1.10.c [V1.0] Wire the two tenant-admin demo pages whose backends
exist:
apps/oshun/tenant-admin/src/app/status/page.tsxoverGET /v1/statusandhelp/page.tsxoverGET /v1/communications/help, dropping their demo label. (10 §12) Verify: newapps/oshun/tenant-admin/src/app/status/status-live.spec.tsxand itshelptwin — 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 ofapps/oshun/bff/src/routes/tenant-console-reads.ts, and render it on the tenant-admin integrations page. (10 §12) Verify: newapps/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:
tenantBffGetreturnsnullfor 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: newapps/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:
enforceResidencyinapps/oshun/bff/src/middleware/residency-guard.tshas no caller. Invoke it from the memory, export and deletion route families its header lists, with the deployment zone. (11 §16) Verify: extendapps/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
retentionDaysForinlibs/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 readOSHUN_RETENTION_RULESfromlibs/contracts/src/common/data-retention-rules.ts. (11 §17) Verify: newapps/oshun/bff/src/privacy/retention-sweep-worker.spec.tswith a fixed clock —grep -rn "retentionDaysFor" libs appsshows 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.tsdefines a second state machine beside the live@oshun/contractsplusapps/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 appsshows one implementation. - Q1.11.d [V1.0] Make
/profile/datathe privacy center: the real consent ledger, a link to memory controls, exports and deletions, and the residency display on one page; redirect/profile/notificationsto the settings panel. 2026-09-18: depends on T15.a. (11 §20) Verify: an e2e-inspect walk — every privacy control is reachable from/profile/databy 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-safetyacross 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: newlibs/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: newapps/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 indeliverDispatchedMessage. (12 §10) Verify: newapps/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 feedscomputeChannelDeliverability(libs/oshun/messaging-channels/src/deliverability.ts). Aggregate realMessageDeliveryResults per channel and show the summary on the admin messaging console. (12 §10) Verify: newapps/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, andapps/oshun/bff/src/channel-ops/digest-inputs-store.tshonestly 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: newapps/oshun/bff/src/notifications/digest-worker.spec.tswith 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.tsxmark 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: newapps/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 OpenTelemetryNodeTracerProvideratsrc/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), andinfra/hetzner/docker-compose.ymlhas no OTLP endpoint. Construct the tracer inapps/oshun/bff/src/server.tsbehind an env-gated OTLP exporter, add a server span per request, and propagatetraceparentto the domain adapters. This is the build task behind the "confirm OpenTelemetry" items ofdocs/releases/v1/specs/todos-p2.md§55. (13 F10) Verify: newapps/oshun/bff/src/__tests__/otel-tracing.spec.tswith 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 overGET /v1/admin/analytics/events. (13 F11) Verify: newapps/oshun/web/src/analytics/no-orphan-telemetry.spec.tsasserts 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. WirehomeLayoutV2ordomainSwitcheras the first real gate, or defer the bootstrap until a consumer exists. (13 F12) Verify: newapps/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:1253serves route names, volumes and error rates to anonymous callers. Require the observability admin scope or a static bearer fromOSHUN_METRICS_TOKEN, closed by default in production. (13 F13) Verify: newapps/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/eventsprunes 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: extendapps/oshun/bff/src/analytics/events-ingest.test.ts— 6000 anonymous events leave a signed-in member's 10 events readable.
- Already fixed (checked 2026-09-18) — 01 §27: the voice-input bars read the
microphone (
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.