# V1 Residual Audit — Product Surfaces (features.md 258–449)

Date: 2026-06-11 · Auditor slice: V1 Product Promise + Customer Web + Customer
Mobile + Shared Consumer Shell + Public Web and Distribution. Method: static
source reading + grep only; every delegation chain followed to the bottom; all
06-10 ground-truth fixes (A–E phases) treated as landed and NOT re-reported.
Accepted postures (D1 preview register on registry-covered surfaces, C3
`guardedFixtureRoute` dev-only fixtures, E5/E7 retirements, env-gated
fail-closed providers) are excluded per the brief.

---

## P0-HONESTY — fabricated data / fabricated success on live paths

### 1. Home "Daily plan" is a permanent unlabeled simulation while a real routines backend sits unused

**Severity:** P0-HONESTY **Evidence:**

- `apps/oshun/web/src/components/home/HomeWorkspace.tsx:496` — `<DailyPlanV2 />`
  mounted with **no props**.
- `apps/oshun/web/src/components/routines/DailyPlanV2.tsx:1071-1072` —
  `const routines = propRoutines ?? SIMULATED_TODAY_ROUTINES; const recommendation = propRecommendation ?? SIMULATED_RECOMMENDATION;`
- `DailyPlanV2.tsx:172-260` — `SIMULATED_TODAY_ROUTINES` (`userId: 'user_sim'`,
  frozen `todayDate = '2026-05-24'`, "Quick Morning Start" with completion
  state) and `SIMULATED_RECOMMENDATION`.
- Real backend exists and is reachable:
  `apps/oshun/bff/src/routes/routines.ts:110-407` (`GET/POST /v1/routines`,
  templates, recommendations, executions) and typed hooks at
  `apps/oshun/web/src/lib/hooks/use-bff.ts:2226-2271` — **zero callers on
  home**. **Spec promise:** features.md:318-319 (redesigned dashboard reflecting
  the V1 home model); features.md:260 (coherent product, not fabrications).
  **What the code does:** every member's home shows the same fabricated routine
  list and progress bar with no fallback/preview disclosure of any kind — not
  even the "Curated …" pill the other home sections use. This is the C15 fixture
  pattern, alive on the highest-traffic surface, and the orphaned-engine pattern
  (tested backend pointed at nothing) at once. **Fix sketch:** Wire
  `DailyPlanV2` through
  `useUserRoutines()`/`useActiveRoutineExecution()`/`useRoutineRecommendations()`;
  honest empty state ("No routines yet — create one") when the member has none;
  delete `SIMULATED_TODAY_ROUTINES` from the live path (keep as test fixture
  only).

### 2. /profile + /settings fabricate "Domain statistics" and "Connected services"; the Connect/Manage button is dead

**Severity:** P0-HONESTY **Evidence:**

- `apps/oshun/web/src/components/ProfileSettingsPanel.tsx:106-143` —
  `DEMO_DOMAIN_STATS` (Tara 42 sessions/315 min/7-day streak, Veritas 89
  sessions, "Top action: Breathwork" …).
- `ProfileSettingsPanel.tsx:157-167` — `DEMO_SERVICES`: "Google — Connected · 2
  hours ago", "Apple Health — Connected · 30 min ago".
- `ProfileSettingsPanel.tsx:1105` — section subtitle computes "`199` total
  sessions" from the fixture; `:1120` renders the fixture grid; `:1448` "2
  connected"; `:1454` fixture list.
- `ProfileSettingsPanel.tsx:1495-1512` — the per-service `Connect`/`Manage`
  `<button>` has **no `onClick`** — a dead control.
- Mounted live at `app/profile/page.tsx:90` and `app/settings/page.tsx:35`.
  **Spec promise:** features.md:401-403 (profile/settings for account, billing
  entry, memory controls …); features.md:269-270 (make state visible where it
  affects user trust). **What the code does:** every user is told they have 199
  sessions of cross-domain history and two live OAuth integrations syncing their
  data. Both claims are fabricated for everyone; the integration buttons do
  nothing. **Fix sketch:** Delete both DEMO\_ blocks. Domain statistics: derive
  from the real `/v1/activity` + achievements reads the Activity surface already
  uses (or render the honest "no stats yet" note). Connected services: render
  the real calendar-connections client
  (`profile/calendar-connections-client.ts`) plus an honest "no integrations
  connected" empty state; remove the dead button or wire it.

### 3. Billing is a client-side simulation: every user seeds as paid "Pro" with a Visa and a paid invoice, and "purchase" fabricates success

**Severity:** P0-HONESTY (with a P1 entitlement-coherence edge) **Evidence:**

- `libs/oshun/auth/src/billing-store.ts:976-1025` —
  `createDefaultOshunBillingSnapshot()` seeds `currentPlan: 'pro'`,
  `accountStatus: 'active'`, payment method "Visa ending in 4242", and one
  **paid $12.00 invoice** dated now.
- `apps/oshun/web/src/profile/store.ts:58-61` — billing store built on
  `localStorage` with that seed and **no BFF fetcher** (compare the
  consent/export/deletion stores in the same file, which do have fetchers).
  `lib/auth-context.tsx:145` only `clear()`s it on sign-out — which re-seeds the
  same fiction (`billing-store.ts:929`).
- `apps/oshun/web/src/components/profile/BillingSection.tsx:113-135` —
  `handlePurchaseClick` → `oshunWebBillingStore.purchase(...)` (a localStorage
  mutation; no payment provider, no server call) then reports
  "`<plan> active · $X/mo · renews <date>`" and `syncProfilePlan(targetTier)`
  (`:1472-1474`) flips the client profile plan.
- `apps/oshun/web/src/components/billing/ShellFeatureGate.tsx:46-47` — client
  feature gates resolve entitlements from that client-mutable profile plan.
- `BillingSection.tsx:280-291,651` — "Report failed charge" button calls
  `simulateBillingFailure()` which _injects_ a fabricated decline into the local
  store instead of reporting anything.
- Same seeds on mobile: `apps/oshun/mobile/src/profile/store.ts:21-41`
  (in-memory storage, same `createDefaultOshunBillingSnapshot`), consumed by
  `app/(tabs)/profile.tsx` via
  `getLatestBillingInvoice`/`getDefaultPaymentMethod`.
- Two sources of truth: the **real** plan lives server-side in
  `customerAuthStateStore` (A6 entitlement middleware; C10 settlement flips it)
  and is never reconciled into this surface. **Spec promise:**
  features.md:401-402 (billing entry in profile/settings); features.md:260
  (coherent product). **What the code does:** a free user opens Billing and sees
  an active paid Pro subscription, a card on file, and a paid invoice — none
  real. Clicking "purchase" fabricates a successful subscription without moving
  money or touching the server plan, then unlocks client-side feature gates. The
  crypto path (quote→settle, C10) is real and entirely separate — the two
  billing stories contradict each other. **Fix sketch:** Give the billing store
  a BFF fetcher like its siblings: `GET /v1/billing` deriving plan from the
  session's persisted plan (A6) + real invoices from the C10 invoice store; seed
  honest-empty (free, no methods, no invoices). Purchase buttons route into the
  real payment flow (crypto quote) or an honest `not_configured` state. Delete
  `simulateBillingFailure` or rename/rewire it to a real support intake.

### 4. Fabricated profile-stats seed (7-day streak, 18 saved items, 5 active domains) reaches Home and Profile for every signed-in member

**Severity:** P0-HONESTY **Evidence:**

- `libs/oshun/auth/src/profile-store.ts:240-258` — `createDefaultOshunProfile`
  defaults `stats: { streakDays: 7, savedItems: 18, activeDomains: 5 }`,
  `plan: 'pro'`, `verified: true`.
- `apps/oshun/web/src/lib/auth-context.tsx:530-545` — `syncProfileStore` takes
  identity/plan from the real session but passes `stats: undefined` for a new
  user → fabricated defaults persist.
- Rendered: `components/home/HeroBanner.tsx:457,1003`
  ("`{streakDays} day practice streak`"),
  `components/ProfileSettingsPanel.tsx:307-309,950` ("Day streak" stat tile).
- Mobile mirror: `apps/oshun/mobile/src/auth/mobile-auth-context.tsx:223-235` —
  same pattern, plus fallbacks `plan ?? 'pro'`, `verified ?? true`.
- Only `savedItems` is ever reconciled to reality
  (`lib/library/webLibraryStore.ts:270-287`); `streakDays`/`activeDomains` have
  **no real writer anywhere**. **Spec promise:** features.md:318-319 (home
  reflects the V1 home model); the C2/C3 ground-truth fixes made the _Arete_
  streak real — this is a second, contradicting streak. **What the code does:**
  a brand-new member's hero banner congratulates them on a 7-day practice streak
  they don't have; the same fake number sits beside the now-real Arete streak
  from `/v1/arete/streak` — two sources of truth, one fabricated. **Fix
  sketch:** Seed stats at honest zero; derive streak from the real
  `/v1/arete/streak` (or the achievements stats adapters) and activeDomains from
  real domain activity; never default plan to 'pro' or verified to true when the
  session doesn't say so.

### 5. Web notifications center fabricates notifications whenever the live feed is empty — including a fake billing alarm

**Severity:** P0-HONESTY **Evidence:**

- `apps/oshun/web/src/components/NotificationsCenterPanel.tsx:1036-1038` —
  `setItems(mapped.length > 0 ? mapped : buildInitialNotifications())` on
  success-but-empty, and the same fixture in the `catch`.
- `NotificationsCenterPanel.tsx:207-260` — `buildInitialNotifications()`:
  "Cross-domain handoff is ready", and **"Billing review needs your confirmation
  — Support flagged a renewal mismatch on your Veritas upgrade"** with a deep
  link into the billing upgrade flow. **Spec promise:** features.md:399-400
  (notification center behavior for mixed-domain, admin, support … events);
  features.md:428-429 (honest loading/empty/degraded states). **What the code
  does:** a new user with zero notifications is shown fabricated unread items by
  design (not just on outage), one of which is a fake billing problem that
  funnels them toward the (also fake — finding 3) upgrade surface. No
  disclosure. **Fix sketch:** Empty feed → honest empty state ("You're all
  caught up"). On fetch failure → disclosed unavailable state. Delete
  `buildInitialNotifications` from the live path.

### 6. Home ActivityFeed serves fabricated first-person activity on outage

**Severity:** P0-HONESTY **Evidence:**
`apps/oshun/web/src/components/home/ActivityFeed.tsx:76-110` —
`FALLBACK_ACTIVITY`: "Completed morning breath sequence" (24 min ago), "Saved
'AI regulation frameworks' to reading queue", "Logged ISS visibility
observation", "Completed daily check-in"; served at `:465`, `:494`, `:526`
whenever `/v1/activity` errors or returns no items array, with only a small
notice ("Home kept its fallback entries"). **Spec promise:** features.md:395-396
(cross-domain activity timelines); features.md:284-285 (degrade safely);
features.md:428-429. **What the code does:** exactly the silent-fixture pattern
C15 deleted from `/activity` — the home strip was missed. The outage notice
exists but the _content_ remains first-person fabricated history with live "x
min ago" timestamps. **Fix sketch:** Replace the fixture fallback with the same
honest unavailable presentation `/activity` now uses (empty list +
`CustomerShellOutageBanner` reason). Delete `FALLBACK_ACTIVITY`.

### 7. Home mixed-domain recommendations swap in `SIMULATED_CROSS_RECOMMENDATIONS` on error _or_ empty, with fabricated personal context

**Severity:** P0-HONESTY **Evidence:**

- `apps/oshun/web/src/components/home/MixedDomainRecommendationsLane.tsx:61-62,109`
  — `isUsingFallback = isError || !hasLiveRecommendationPayload`; then
  `recommendations={isUsingFallback ? SIMULATED_CROSS_RECOMMENDATIONS : …}`.
  Disclosure is an 11px "Curated shell mix" pill.
- `components/cross-domain/CrossDomainRecommendations.tsx:124-186` — fixture
  items claim "**The centering session you finished this morning**", "**Your
  focus goal for the week**", "**The breathing reset you just completed**" as
  `sourceContext`.
- `CrossDomainRecommendations.tsx:944,957-962` — `useDefaultFallback = true` by
  default: ANY consumer passing an empty list silently gets the fixture.
- The fallback also bypasses `selectHomeMixedDomainRecommendationSlate` — the
  spec's suppression rules are not applied to what actually renders. **Spec
  promise:** features.md:324 (mixed-domain recommendations **with reason labels
  and suppression rules**). **What the code does:** a new user with genuinely no
  recommendations (empty payload — not an outage) is told a centering session
  "you finished this morning" points somewhere. Reason labels on fabricated
  reasons are worse than no recommendations. **Fix sketch:** Empty payload →
  honest empty/cold-start invitation (the cold-start module already exists in
  this file). Error → disclosed unavailable. Flip `useDefaultFallback` default
  to `false`. Keep the fixture only for the (orphaned) `CrossDomainHub` or
  delete it.

### 8. Mobile Activity tab seeds fabricated notifications and never syncs without a dev auth token

**Severity:** P0-HONESTY **Evidence:**

- `apps/oshun/mobile/app/(tabs)/activity.tsx:41-130` — `INITIAL_NOTIFICATIONS`:
  "Streak milestone reached — seven consecutive recovery sessions", "Weekly
  pattern detected — your strongest focus block appears between 9:00-11:00 AM",
  "Clear-sky window starts soon … in your location".
- `:244,252` — rendered as initial state. `:422-424` — `syncNotifications`
  **returns early if `!devAuthToken`**, so the fixtures stand with no
  disclosure. `:434-437` — on fetch failure the message says "Showing local
  fallback timeline" but the fallback content is fabricated personal insight.
  **Spec promise:** features.md:395-396 + 399-400 (activity timeline,
  notification center); the C16 fix covered mobile _home_ only. **What the code
  does:** the mobile activity timeline fabricates behavioral insights ("your
  strongest focus block") and location claims for everyone whose session token
  isn't present, and on error. **Fix sketch:** Initial state empty + loading;
  unauthenticated → sign-in prompt; failure → honest unavailable (no fixture
  timeline). Delete `INITIAL_NOTIFICATIONS` from the live path.

### 9. Mobile Profile tab: zero network — fabricated "Renata" Iris memories with working forget/export controls, fabricated linked sessions

**Severity:** P0-HONESTY **Evidence:**

- `apps/oshun/mobile/app/(tabs)/profile.tsx` — **0** hits for
  `fetch(`/`client.`/`api.` in 4,092 lines (verified by grep) except
  `listSessions` via the auth context.
- `:376-470` — `INITIAL_MOBILE_PROFILE_MEMORY_ENTRIES`: "Renata prefers a short
  breath session before breakfast…", "Renata is comparing Epictetus and Marcus
  Aurelius…", with fabricated provenance chains ("consent:routines granted by
  member", audit chain ids) and recall events. Every `setMobileMemoryEntries`
  call (`:1560-1755`) is a local mutation of this fiction; there is **no
  hydration from `/v1/iris/adapter/review`** (which C4 wired for web).
- `:267-291,663` — `INITIAL_LINKED_SESSIONS` ("iPhone 15 Pro", "Chrome on Mac",
  "Pixel Tablet" in SF/Austin) is the signed-out state (`:893-894` resets to it)
  and the pre-hydration state; the governance fact row reports "Sessions: 3
  active" from it (`:786-809`). **Spec promise:** features.md:401-403 (memory
  controls, sessions, devices in profile); features.md:269-270 (memory/consent
  state visible where it affects trust). C4's whole point was that fabricated
  memory + pre-granted consent is a trust-surface lie — the mobile mirror was
  missed. **What the code does:** mobile members "review", "forget", and
  "export" memories Iris never stored, about a person who doesn't exist; consent
  toggles flip local fiction; signed-out users see three fabricated active
  devices. **Fix sketch:** Hydrate memory controls from the same
  `/v1/iris/adapter/review` + `/consents` reads the web uses (typed client
  already has the BFF base); honest empty + ungranted-consent start. Linked
  sessions: empty + sign-in prompt when unauthenticated; on error show the
  error, never the fixture.

### 10. Mobile Library tab still ships the OLD fabricated Iris handoff (C16 fixed home only)

**Severity:** P0-HONESTY **Evidence:**

- `apps/oshun/mobile/app/(tabs)/library.tsx:15,36` —
  `buildMobileHomeIrisHandoffState()` (no args) feeds `MobileIrisHandoffCard`.
- `src/navigation/mobileIrisHandoff.ts:15-35,61-110` — `MOBILE_IRIS_CONTINUITY`
  fabricates memory counts
  (`coreBlockCount: 4, workingCount: 7, archivalCount: 18 …`), and the builder
  fabricates items: "Bhagavad Gita 2.47 — 64% progress", "Evidence reflection
  study path — 68%", `issuedAt` hardcoded `'2026-05-06T10:15:00.000Z'`.
- Contrast: `app/(tabs)/index.tsx` + `src/network/oshun-bff-client.ts:499-511`
  fetch/consume the REAL durable `/v1/iris/mobile-handoff` channel. **Spec
  promise:** features.md:339-341 + 411-412 (hand-off and continuity between
  desktop and mobile). **What the code does:** the library tab tells every
  member a desktop reading session is waiting to resume at 64%. Two sources of
  truth for the same fact (real channel on home, fiction on library), differing
  within one app. **Fix sketch:** Point the library tab's card at the same
  fetched handoff state home uses (lift the fetch into a shared hook); honest
  "no handoff waiting" when null. Delete `buildMobileHomeIrisHandoffState`'s
  fixture path.

### 11. Mobile "universal sharing" card fabricates active share links and invites; the shell-core engine has no real consumer

**Severity:** P0-HONESTY (plus orphaned-engine) **Evidence:**

- `apps/oshun/mobile/src/share/mobileUniversalSharing.ts:41-60` —
  `buildMobileUniversalShareModel()` defaults to
  `buildDefaultMobileUniversalShareRequests()`; summary renders "N/M universal
  links active".
- `:63-130` — fabricated requests: an active library share created 60s ago, an
  invite to `learner@example.edu` with fabricated rate-limit usage counters.
- Rendered on the live library tab: `app/(tabs)/library.tsx:49`
  (`buildMobileUniversalShareModel()` — no real requests passed).
- `libs/oshun/shell-core/src/universal-sharing.ts`
  (`buildConsumerShellShareCenter`) has **zero** runtime consumers besides this
  fixture wrapper (repo-wide grep) — the real per-link
  permission/expiry/revocation engine the spec describes is orphaned, while real
  sharing exists separately in `apps/oshun/bff/src/routes/library-sharing.ts`.
  **Spec promise:** features.md:406-407 (universal sharing, invite, referral
  flows with per-link permissions, attribution, expiry, revocation). **What the
  code does:** tells the member they have live outstanding share links and a
  pending email invite; "manage" affordances operate on fiction. Meanwhile the
  real library-share records (BFF) are a second, disconnected truth. **Fix
  sketch:** Feed `buildMobileUniversalShareModel` from the real library-sharing
  reads (`GET /v1/library/...share` family) or render the honest empty share
  center; delete the default fixture requests. Decide the shell-core engine's
  fate explicitly (wire it over the BFF share store or record an E5-style
  retirement note).

### 12. Mobile calendar sync is a simulation end-to-end: fabricated provider connections, static transport, success haptic on a no-op "Sync now"

**Severity:** P0-HONESTY **Evidence:**

- `apps/oshun/mobile/app/(tabs)/index.tsx:821-823` — home card state =
  `buildMobileCalendarSyncState({ issuedAt })`, no real inputs.
- `src/calendar/mobileCalendarSync.ts:164-220` —
  `buildDefaultMobileCalendarSyncItems()` fabricates scheduled rituals/reminders
  with stale May-2026 dates; `:321-346` builds fabricated Google/Apple/Outlook
  connector connections and inbound-change/conflict counts; `:588-602` —
  `createStaticCalendarTransport` is a test double **on the default live path**
  (`upsertEvent` echoes the event back as "synced").
- `app/(tabs)/index.tsx:1081-1094` — "Sync now" fires
  `triggerHapticIntent('success')` and navigates to an activity filter; no
  calendar API is called. Repo-wide grep: no `expo-calendar`, no provider OAuth
  anywhere in mobile.
- Contrast: web calendar connections are wired to real BFF routes
  (`apps/oshun/web/src/profile/calendar-connections-client.ts:67-85` →
  `routes/calendar-connect.ts`, `calendar-sync.ts`). **Spec promise:**
  features.md:371-373 (calendar integrations — Google, Apple, Outlook — with
  two-way sync where authorized). **What the code does:** claims three calendar
  providers are connected and syncing, shows reconciliation counts from a static
  double, and confirms "sync" with a success haptic without syncing anything.
  This is fabricated success, not an honest fail-closed seam. **Fix sketch:**
  Drive the card from the real BFF calendar-connection reads (same routes web
  uses); unconnected → honest "Connect a calendar" state; "Sync now" calls the
  real sync route or is removed. The deterministic reconciliation engine can
  stay — behind real connector/transport inputs only.

### 13. Mobile capture: "Request permission" fabricates an OS permission grant; no camera/mic/photo capture exists

**Severity:** P0-HONESTY **Evidence:**

- `apps/oshun/mobile/app/(tabs)/index.tsx:1126-1130` —
  `handleRequestCapturePermission` =
  `grantMobileCapturePermission(current, permission)` (a local state flip) +
  success haptic; `:1132-1143` — tapping a not-ready capture action also just
  flips the permission to granted locally.
- `src/capture/mobileCaptureIntake.ts` — pure model, no
  `expo-camera`/`expo-image-picker`/`expo-av`; repo grep shows the only real
  `requestPermissionsAsync` in mobile is notifications
  (`src/notifications/registration.ts:209`). **Spec promise:**
  features.md:374-376 (camera, microphone, photo-library capture with **explicit
  permissions** and per-domain redaction policies). **What the code does:**
  displays "granted" for OS permissions the OS was never asked for, then routes
  "capture" actions to ordinary screens — no capture pipeline exists behind the
  card. **Fix sketch:** Either wire real expo permission requests + a minimal
  capture→upload leg (the avatar upload asset-store seam from B13 is reusable),
  or remove the capture card from home and keep the redaction-policy model for
  V1.x. Never render a granted state the OS didn't return.

---

## P0-STRUCT — structural breaks

### 14. 54 standalone `/domains/*` deep pages bypass the D1 preview banner entirely — including every example the spec names

**Severity:** P0-STRUCT (explicitly requested check: surfaces not covered by
`DOMAIN_PREVIEW_SURFACES`) **Evidence:**

- The banner mounts in exactly one place:
  `apps/oshun/web/src/components/DomainRouteExperience.tsx:579`, used only by
  the catch-all `app/domains/[domainId]/page.tsx:91`.
- 54 concrete pages exist beside it
  (`find app/domains -mindepth 2 -name page.tsx`): `nyx/star-chart`,
  `veritas/fact-check`, `arete/journal`, `tara/teachers`,
  `veritas/claims/[claimId]`, `nyx/events/[eventId]`, … Each mounts its
  component directly (e.g. `app/domains/nyx/star-chart/page.tsx` →
  `NyxStarChart`; `app/domains/veritas/fact-check/page.tsx` →
  `VeritasFactChecker`) with **no `DomainPreviewBanner` and no shared
  `app/domains/layout.tsx`** (the only layouts are `nyx/renderer` and
  `nyx/widgets`, unrelated).
- Those components are exactly the simulation surfaces D1 labeled for:
  `components/domains/nyx/NyxStarChart.tsx:19` imports `nyx-simulation-data`;
  `components/domains/veritas/VeritasFactChecker.tsx:52` imports
  `veritas-simulation-data`; 18 components under `components/domains` import
  `*-simulation-data` (repo grep). **Spec promise:** features.md:303-309 names
  `/domains/arete/journal`, `/domains/veritas/fact-check`,
  `/domains/nyx/star-chart`, `/domains/tara/teachers` as the power-user layer —
  all four are unbannered standalone pages. **What the code does:** the registry
  says tara/veritas/nyx/arete/nisaba are "in preview", but the preview register
  renders only on the catch-all room; deep links and in-hub entry points land on
  the concrete pages, where fabricated user data/journalism/scholarship again
  carries full visual authority — the precise failure D1 was accepted to fix.
  **Fix sketch:** Add `app/domains/layout.tsx` that resolves the domain segment
  from the path and renders `<DomainPreviewBanner domainId={…}/>` above
  `children` (one file covers all 54 pages and future ones); keep the registry
  as the single switch.

### 15. Veritas/Nyx/Metis/Nisaba `/v1/oshun/*` facades were never built — home/feed stages for 4 of 6 domains fail by construction

**Severity:** P0-STRUCT **Evidence:**

- `apps/oshun/bff/src/adapters/domain-service-adapters.ts:1095-1380+` —
  veritas/nyx/metis/nisaba adapters dial `/v1/oshun/briefs/trending`,
  `/v1/oshun/events/nightly`, `/v1/oshun/courses/...`, `/v1/oshun/saved` …
  against `{veritas,nyx,metis,nisaba}BaseUrl`.
- Repo-wide grep: **no** `/v1/oshun` route and no `oshun-facade` module under
  `apps/veritas`, `apps/nyx`, `apps/metis`; Nisaba has no service at all. Only
  Tara (`apps/tara/api/src/oshun-facade/`) and Arete
  (`apps/arete/api/src/routes/oshun-facade.ts`) were built (C1: "Veritas/nyx
  repeat = follow-up of the same pattern" — the follow-up never happened).
- Consumers: `apps/oshun/bff/src/routes/feed-helpers.ts:113-200` — `/v1/home`
  (web home + C16 mobile home feed) and the feed/continue/favorites stages call
  `app.domainAdapters.veritas.getTrendingBriefs`, `nyx.getNightlyEvents`,
  `metis.getRecommendedCourses`, `nisaba.getDailyPassage` per request. **Spec
  promise:** features.md:265-266 (continuity across web/mobile/home), 364-365
  (mobile continuation cards for all six domains), 393-394 (Tara-led home
  ordering, blended exploration). **What the code does:** even with every dev
  service running, `domainStatus` for veritas/nyx/metis/nisaba in `/v1/home` is
  permanently `degraded` — the partial-failure envelope is honest, but four of
  six domains can never appear in the home/continue/favorites feed. The
  "blended" home is structurally Tara+Arete-only. **Fix sketch:** Repeat the C1
  facade pattern for veritas + nyx (both have real repositories), bridge
  nisaba's adapter to the in-BFF nisaba store it already mirrors (B6/E2 did this
  for daily-passage on another path — `getDailyPassage` should read the same
  store), and metis to its existing `/api/*` service; contract round-trip tests
  per C1.

---

## P1

### 16. Home Arete practice section fabricates streaks/progress on fallback, labeled only "Curated practice thread"

**Severity:** P1 **Evidence:**
`apps/oshun/web/src/components/home/homeAretePracticeModels.ts:131-217` —
`FALLBACK_HOME_PRACTICE`: goals with `streakDays: 12/9/17`,
`progress: 68/54/73`, continuation "Morning commitment loop — 58%",
`userId: 'demo-user'`; used whenever `input.practice == null` (loading or error)
via `:217`. Disclosure pill at `HomeAretePracticeSection.tsx:267-271` reads
"Curated practice thread" — which does not say the streak numbers aren't yours.
**Spec promise:** features.md:325-326 (Arete weekly review home module); the
C2/C3 work made Arete streaks real precisely so numbers like these would be
honest. **What the code does:** on any `/v1/arete/practice/home` failure (and
during load on slow links) the member sees specific fabricated streak/progress
numbers beside the _real_ streak elsewhere. Contrast the Veritas module in the
same directory (`homeVeritasBriefingModels.ts:110-126`), which was rewritten to
an explicit no-fabrication notice — the pattern exists; Arete (and Nyx, below)
weren't given it. **Fix sketch:** Mirror the Veritas fix: fallback carries stage
copy + "practice data unavailable" with zero numeric claims; delete the fixture
goals.

### 17. Home Nyx perspective fallback fabricates "Jupiter at opposition" tonight + a 42% observation continuation

**Severity:** P1 **Evidence:**
`apps/oshun/web/src/components/home/homeNyxPerspectiveModels.ts:95-140` —
`FALLBACK_HOME_PERSPECTIVE`: "Jupiter at opposition … reaches its closest point
to Earth this year", `windowStart/peakTime` frozen at 2026-03-27,
`magnitude: -2.8`, plus continuation "Track Jupiter through midnight — 42%,
lastOpenedAt …". Label on fallback: "Curated perspective"
(`HomeNyxPerspectiveSection.tsx:238`). **Spec promise:** features.md:325-326
(Nyx events home module); the B-phase ephemeris honesty work (real Meeus engine)
exists exactly so sky claims are true. **What the code does:** on outage,
asserts a specific astronomical event is happening tonight (it is dated ten
weeks ago) and that the member is mid-observation. Fabricated fact + fabricated
personal continuation. **Fix sketch:** Honest unavailable notice (no event
claim, no continuation); the real BFF route is ephemeris-backed, so the fallback
should claim nothing.

### 18. KNOWN RESIDUAL CONFIRMED — workspace→mobile handoff packet still fabricates the Iris continuity header (and more)

**Severity:** P1 **Evidence:**
`apps/oshun/web/src/components/workspace/irisMobileWorkspaceHandoff.ts:45-68` —
`WORKSPACE_IRIS_CONTINUITY`: static
`coreBlockCount: 4, workingCount: 7, archivalCount: 18, episodicCount: 5, semanticCount: 11`,
static `userId: 'oshun-v1-workspace-user'`, `consentState: 'granted'`
(fabricated consent — C4 made consent real and default-ungranted),
`lastUpdatedAt: '2026-05-06'`. Additionally `:141-163` fabricate a
`voice_session` (`sessionId: voice-${tab.id}`) and an `assistant_transcript`
(`conversationId: assistant-${tab.id}`) that don't exist. **Spec promise:**
features.md:339-341 (resumable assistant transcripts, hand-off between desktop
and mobile); features.md:269-270 (memory/consent state visible truthfully).
**What the code does:** the C16 transport is real (PUT to
`/v1/iris/mobile-handoff`), but the payload's memory counts, consent state, and
two of six items are fiction — mobile renders them as truth on resume. **Fix
sketch:** Fetch `/v1/iris/adapter/continuity` (and the real consent read) to
fill the header; include the voice/transcript items only when a real
session/conversation id exists; drop the static userId for the session user.

### 19. Mobile Explore search runs on a hardcoded in-file catalog, never `/v1/search`

**Severity:** P1 **Evidence:**
`apps/oshun/mobile/app/(tabs)/explore.tsx:217-585` — `SEARCH_INDEX` (in-file
items with fabricated freshness `publishedAtMs: NOW_MS - 2h` and
`popularityScore`s); `:586` `TRENDING_QUERIES` (fabricated "trending");
`:1339-1340` all search/filtering runs over this constant. **Zero** network
calls in the 3,065-line file (grep `client.|api.|fetch(` = 0). Web explore
meanwhile uses live `/v1/search`
(`components/explore/ExploreDashboard.tsx:1621`), including the member's real
saved items/notebooks (C7). **Spec promise:** features.md:327-329 (universal
search across … notebooks, collections, courses …); features.md:367-369 (mobile
command surface for search and jump-to-anything). **What the code does:** mobile
members search a fabricated catalog that can never contain their notebooks/saved
items, with fabricated trending and recency — a second source of truth diverging
from web search. **Fix sketch:** Point the explore search box at
`GET /v1/search` through the existing typed BFF client (auth + domain filters
already supported); keep the curated collections rows as labeled editorial
content; derive "trending" from the analytics ingest (C11) or drop the label.

### 20. Assistant panel: silent fixture session history + a fake tool-action progress bar

**Severity:** P1 **Evidence:**

- `apps/oshun/web/src/components/assistant/AssistantPanel.tsx:1884-1887` —
  `loadSessionHistory` catch → `setSessionHistory(SIMULATED_SESSION_HISTORY)`;
  `:651-664` four fabricated sessions with topics ("Evening meditation routine",
  "Weekly goal review").
- `:1545-1578` — when the message mentions "session"/"check-in", the panel
  renders "Starting … session..." / "Running daily check-in..." with a
  random-increment `setInterval` progress bar that always reaches
  `status: 'complete'` — no action is performed; the message is simply sent
  afterwards. **Spec promise:** features.md:320-323 (assistant dock with
  persistent context handoff); features.md:339-341 (resumable assistant
  transcripts). **What the code does:** outage shows fabricated past
  conversations as the member's; the progress theater claims an action ran and
  completed when nothing did (C9/C14 made real runs and replies possible — this
  UI predates them). **Fix sketch:** History failure → honest "history
  unavailable"; delete the keyword-triggered fake tool action (real
  suggested-actions already navigate/queue real work).

### 21. E6 cross-device workspace sync is write-only: nothing ever reads the durable slot back

**Severity:** P1 **Evidence:**
`apps/oshun/web/src/components/workspace/MultiPanelWorkspace.tsx:463` —
`pushClientState('workspace-layout', …)` on every tab change. Repo-wide grep:
`pullClientState` and `mergeByUpdatedAt` (`lib/client-state-sync.ts:33,50+`)
have **zero callers**. Tab restore still reads only `localStorage`
(`MultiPanelWorkspace.tsx:392`). The other three allow-listed slots
(onboarding-progress, recent-searches, study-plan-drafts) have neither push nor
pull (recent searches remain pure localStorage —
`UniversalSearchPanel.tsx:103-114`). **Spec promise:** features.md:337-338
(multi-tab support with per-tab continuity, stable deep-link state);
features.md:411-412 (cross-device continuity). **What the code does:** "another
device resumes the same tabs" (the E6 claim) cannot happen — the server copy is
written and never read. Stored state never consulted on the hot path. **Fix
sketch:** On workspace mount (signed-in), `pullClientState('workspace-layout')`
→ `mergeByUpdatedAt` with the local tabs before first render of restored tabs;
adopt the same 6-line pattern for recent-searches in `UniversalSearchPanel`.

### 22. "Report failed charge" injects a fabricated payment failure into the member's own billing state

**Severity:** P1 (subsumed by finding 3's rebuild, listed separately because the
control is actively harmful) **Evidence:**
`apps/oshun/web/src/components/profile/BillingSection.tsx:280-291` —
`simulateBillingFailure()` →
`recordPaymentFailure({ reason: 'Card was declined by issuer.' })` → notice
"Payment issue detected. Complete recovery actions to keep access active.";
wired to the customer-facing "Report failed charge" button (`:651`). **Spec
promise:** features.md:401-402 (billing entry); features.md:428-429 (honest
states). **What the code does:** a customer trying to report a real billing
problem instead _creates_ a fake declined-card state and is told their access is
at risk. The "Retry payment now" recovery then "completes" against the same
fiction. **Fix sketch:** Route the button to the real feedback/support intake
(B14's `POST /v1/feedback` exists) with a billing category; delete
`simulateBillingFailure`.

---

## P2

### 23. "What's New" feed is a stale hand-coded changelog that contradicts the product

**Severity:** P2 **Evidence:**
`apps/oshun/web/src/components/WhatsNewDropdown.tsx:32-86` — `CHANGELOG` frozen
at v0.8–0.9 / Feb 2026, entries like "Dark theme polish" (the shipped shell is
the cream Lilith system per E7). No fetch (grep: 0 network calls). **Spec
promise:** features.md:416-417 (in-app changelog, "what's new" feed). **What the
code does:** serves four-month-old, partially false product education; meanwhile
C8 built a real authored-communications store with localization that could carry
this feed. **Fix sketch:** Serve entries from the C8 communications store (new
`changelog` audience/kind) or at minimum refresh the static list and remove
false entries.

### 24. Account switcher computes "last active" against a frozen clock

**Severity:** P2 **Evidence:**
`apps/oshun/web/src/components/ShellLayout.tsx:225` —
`ACCOUNT_SWITCHER_NOW_MS = Date.parse('2026-05-07T12:00:00.000Z')`; fed to
`buildConsumerShellAccountSwitcher` at `:814`. E8 made the identities real but
left the frozen now, so relative recency labels/ordering are computed against
May 7 forever. **Spec promise:** features.md:413-414 (multi-account switcher
with explicit identity indicator). **Fix sketch:** Use `Date.now()` captured
client-side post-mount (the file already uses that pattern for
`shellMountedAtMsRef`).

### 25. Orphaned fixture dashboards still exported beside the live home: `KpiGrid` (hardcoded `sessions = 28`, seeded fake sparklines) and `CrossDomainHub`

**Severity:** P2 (orphans — no live consumer found; flagged so they don't get
re-mounted) **Evidence:** `apps/oshun/web/src/components/home/KpiGrid.tsx:523` —
`const sessions = 28;`; `:104-112` `generateSparkData` invents 14-day trends;
only its **skeleton** is consumed (`Skeleton.tsx:92`,
`ShellSurfaceStatePreview.tsx`).
`components/cross-domain/CrossDomainHub.tsx:108-125` renders
`SIMULATED_RITUALS`/`SIMULATED_CROSS_RECOMMENDATIONS`/`SIMULATED_CORRELATIONS`/`SIMULATED_ACHIEVEMENTS`;
zero consumers (repo grep). **Spec promise:** features.md:318-319 (the dashboard
reflects real model state). **Fix sketch:** Delete both (and `SIMULATED_*`
exports they anchor), or wire KpiGrid to the real profile/activity stats if it's
meant to return.

### 26. Mobile profile/preferences/privacy stores are memory-only — every setting resets on app restart

**Severity:** P2 **Evidence:** `apps/oshun/mobile/src/profile/store.ts:21` —
`createMemoryKeyValueStorage()` backs ALL mobile stores (profile, preferences,
privacy, data-rights, billing), unlike web's
`createWebStorageAdapter(localStorage)`. Notification topics, personalization,
privacy toggles configured in the profile tab do not survive a relaunch (no
AsyncStorage adapter anywhere in the file). **Spec promise:**
features.md:401-405 (profile/settings incl. notifications, personalization
preferences); features.md:265-266 (continuity). **Fix sketch:** Provide an
AsyncStorage/SecureStore-backed `KeyValueStorage` adapter (the auth context
already uses secure storage for tokens — same seam).

---

## UX / polish

### 27. Voice-input amplitude bars are random noise, not mic levels

**Severity:** UX **Evidence:**
`apps/oshun/web/src/components/assistant/AssistantPanel.tsx:1811-1815` —
`setInterval` fills 12 bars with `webRandomUnitInterval()` while real
SpeechRecognition runs. **Spec promise:** features.md:320-321 (assistant dock
quality bar). **Fix sketch:** Drive bars from a `AnalyserNode` over
`getUserMedia` (or replace with an indeterminate "listening" pulse that doesn't
imply level metering).

### 28. Metis home fallback says "Resume course" for a course the member never started

**Severity:** UX **Evidence:**
`apps/oshun/web/src/components/home/homeMetisStudyContinuationModels.ts:47-55` —
`FALLBACK_PRIMARY` CTA "Resume course" → `/courses/crs-002` with
`progressLabel: 'Course path ready'` when there is no continuation. **Spec
promise:** features.md:325-326 (Metis study continuation module). **Fix
sketch:** Cold-start copy should invite ("Start a course path"), not imply an
in-progress resume.

### 29. Adjacent-register inconsistency: home domain modules use three different degraded vocabularies

**Severity:** UX **Evidence:** On the same home screen, a degraded Veritas
module says "briefing is unavailable … no graded claim is shown" (honest,
`homeVeritasBriefingModels.ts:110-126`); a degraded Arete module says "Curated
practice thread" over fabricated streaks (finding 16); a degraded Nyx module
says "Curated perspective" over a fabricated event (finding 17); the
recommendations lane says "Curated shell mix" (finding 7). "Curated" is doing
dishonest work in three of the four. **Spec promise:** features.md:428-429
(consistent loading/degraded states); features.md:260 (coherent product). **Fix
sketch:** One shared degraded-module presentation (the Veritas pattern) across
all six home modules; reserve "Curated" for genuinely editorial content that
makes no personal/factual claims.

---

## DEPLOY

### 30. Smart-app-banner store URLs point at listings that don't exist (and the Apple URL pattern is malformed)

**Severity:** DEPLOY (store submission is a recorded deploy item) — with a small
P2 code edge **Evidence:** `apps/oshun/web/src/lib/mobile-store-links.ts:1-3` —
`https://apps.apple.com/app/oshun` (App Store URLs require
`/app/<name>/id<digits>`; this 404s structurally, not just pre-submission) and
the Play URL for an unsubmitted package; `SmartAppBanner.tsx:151,161` sends
users there. **Spec promise:** features.md:443-445 (store submission assets,
listing content); features.md:430+ (public-web-to-app conversion funnel). **Fix
sketch:** Env-gate the banner (`OSHUN_APP_STORE_URL`/`OSHUN_PLAY_STORE_URL`
unset → suppress the store CTA, keep the PWA install path), fixing the malformed
Apple pattern when the real id exists.

---

## Severity count

| Severity   | Count  |
| ---------- | ------ |
| P0-SEC     | 0      |
| P0-HONESTY | 13     |
| P0-STRUCT  | 2      |
| P1         | 7      |
| P2         | 4      |
| UX         | 3      |
| DEPLOY     | 1      |
| **Total**  | **30** |

Notes for the fixer: findings 3+4 (auth-client seeds) and 16+17+29 (home
fallback register) each share one root fix. Finding 14 is a one-file fix with
outsized honesty coverage (54 routes). Finding 15 is the largest build item (two
facades + two bridges) and is the C1 follow-up the audit already named.
