Date: 2026-06-11 · Read-only static audit. Baseline: all 57 tasks of
V1_SPEC_GROUND_TRUTH_AUDIT_2026-06-10 are landed (verified in current code where
relevant: C1 facades exist and are adapter-wired; C2 check-in + humane streak
engine is real; C3 domain-stub reads are real-or-honest; D2 ambient WAV +
env-gated guidance routes exist and the hub player plays the ambient loop).
Everything below is RESIDUAL — what still falls short of the spec promise or of
product polish. Accepted states (D1 banner on /domains/{tara,arete} roots,
explicitly recorded V1.x deferrals, env-gated fail-closed providers) are not
re-flagged except where the acceptance itself has a hole.
1. Tara/Arete practice records keyed by a spoofable header, defaulting every member to one shared house-user#
Severity: P0-SEC
Evidence:
apps/oshun/bff/src/routes/domain-stubs.ts:1037-1056—GET /v1/tara/todayandGET /v1/tara/roomresolve identity as(request.headers['x-oshun-user-id'] as string) ?? 'house-user'and never consultauthContext.domain-stubs.ts:1130—POST /v1/tara/sittings/:id/complete(the flagship completion write) does the same: header-only,'house-user'fallback, no auth preHandler (only[originGuard, csrfGuard]).domain-stubs.ts:1062-1065, 1086-1089, 1207-1210, 1252-1255, 1319-1322, 1429-1432— sittings/ritual/streak/habits/check-in/review preferauthContext?.userIdbut none of these routes registers an auth preHandler, sorequest.authContextis never populated (it is set only bycreateAuthPreHandler,middleware/authz.ts:120); they all fall through to the header →'house-user'.- Nothing anywhere injects the header:
grep -rn "x-oshun-user-id"acrossapps/oshunhits only domain-stubs.ts itself.bff-fetch.tsforwards bearer+cookies only;TaraSitPlayer.tsx:194-202POSTs completion withcredentials:'include'and no bearer.
Spec promise: features.md 497–503 (session state per learner), 520–526 (first-class per-user events), 575–585 (per-member check-in/streak records).
What the code actually does: every member reads and writes ONE shared practice
record. One member's sit completions advance everyone's /tara room "todaysSit"
and streak; arete check-ins, streaks, review highlights and ritual continuation
are likewise shared. Worse, any unauthenticated caller can write completions/
check-ins for any victim user id by setting x-oshun-user-id (reads too:
GET /v1/arete/streak with a victim header returns their record). The
"per-member" logic C2/C3 built is real, but the member identity feeding it is
fabricated.
Fix sketch: register the auth preHandler on all /v1/tara/* + /v1/arete/*
domain-stub routes, take userId from authContext.userId only, delete the
x-oshun-user-id header path (or restrict it to a service-token-authenticated
internal caller), and have the web player send the bearer on its POSTs.
2. Cross-user global stores: habits, review closures, and Living-Offering decisions are shared by all members (and two of them are volatile)#
Severity: P0-SEC
Evidence:
apps/oshun/bff/src/routes/domain-stubs.ts:1248-1284, 1396-1422—GET/POST /v1/arete/habitsread/writedomainStubsStore.habitswith no user key;PgHabit(domain-stubs-postgres.ts:39) has NOuserIdcolumn (only check-ins do). Every member's proposed habit — label and "whyItMatters" text, which is sensitive by nature ("Quit drinking…") — is listed to every other member, unauthenticated.domain-stubs.ts:1467-1473—GET /v1/arete/review/closedreturnsareteReviewStore.list()(all closures, all users).domain-stubs.ts:1500-1506+arete/arete-offering-store.ts:26-59—GET /v1/arete/offerings/sentreturns every member's offering records includingrecipientnames; the store is an in-memoryMap(restart wipes it), as isarete-review-store.
Spec promise: features.md 651–657 ("the private intent layer never leaves the originating user"; offerings "kept in their gallery", per-person).
What the code actually does: personal decisions (who you sent an offering to, which weeks you closed, which habits you keep) are a single global list served to any caller, and the offering/review records evaporate on restart.
Fix sketch: add userId (from authContext) to goal3_stub_habit and to both
stores' records; scope every list/read by it; move the two in-memory stores to
the goal3 Postgres/file tier like habits/check-ins.
3. /arete consumer hub is still a fully fabricated room — the one hub left on a fixture#
Severity: P0-HONESTY
Evidence: apps/oshun/web/src/lib/lilith-data/arete.ts:14-16 — getArete()
returns getAreteFixture() unconditionally: "Sit, three minutes · 31-day streak
· Done today", week heatmaps, weekStats 22/28, a fabricated coach note ("Your
reading is at risk on Sundays…"), a fabricated journal entry ("The barista
smiled at me…"). Rendered by AreteRoom (components/lilith/rooms.tsx:341-564)
with no preview label (the D1 banner covers /domains/* only). Contrast:
lilith-data/tara.ts fetches /v1/tara/room and renders an honest unavailable
room on failure; rooms.tsx:61,568,805,1114 show tara/veritas/nyx/nisaba all
default to *Unavailable() while AreteRoom alone defaults to the fixture.
Spec promise: features.md 567–571 (consumer hub showing the member's habits, streaks, coaching) + the 06-10 headline "fixture hubs" which this slice's fixes were meant to close.
What the code actually does: presents invented personal data as the member's own
on the flagship /arete page, while the real plumbing this audit cycle built
(/v1/arete/habits with per-member overlay, /v1/arete/streak computed from
real check-ins) sits one fetch away, unconsumed by this page.
Fix sketch: build /v1/arete/room in the BFF (habits list + computed streak +
honest-empty coach note/evening prompt) exactly as /v1/tara/room was done;
getArete() fetches it and returns an honest areteUnavailable() on failure.
4. All eight /arete/* depth pages serve fixtures with intimate fabricated narratives; the real streak/review endpoints go unconsumed#
Severity: P0-HONESTY
Evidence: apps/oshun/web/src/lib/lilith-data/arete-depth.ts — every getter
except getAreteHabits resolves to its fixture: getAreteStreak (44-61,
fabricated day 109 / 47-day streak + generateStreakHeatmap() arithmetic
pattern (i*31)%11), getAreteRecovery (65-95, "Sunday was your daughter's
recital"), getAretePlan, getAreteGoal, getAreteReview (192-287, a full
fabricated week: "Six of seven mornings, you sat", per-day journal lines),
getAreteCoaching (291-349, a letter addressed to "Renata" with fabricated
pattern confidences 0.84/0.71/0.62), getAretePatterns, getAreteOffering.
Pages:
app/arete/{streak,recovery,plan,goal,review,coaching,patterns, offering}/page.tsx.
Meanwhile the BFF's REAL GET /v1/arete/streak (domain-stubs.ts:1198, C2's
humane engine) and GET /v1/arete/review (domain-stubs.ts:1427, real weekly
highlights) have ZERO web consumers (grep "/v1/arete" apps/oshun/web/src →
only offerings/habits/coach/review- close/practice-home).
Spec promise: features.md 597–650 (humane streak surface, recovery prompts from real missed cadences, weekly review computed from "habit/goal/routine logs, mood, energy, journal entries", coaching summary from observed patterns).
What the code actually does: /arete/streak shows a 47-day streak the engine
never computed; /arete/recovery invents the member's last three days;
/arete/coaching greets the member as "Renata". These are consumer routes
outside DOMAIN_PREVIEW_SURFACES — only /arete/streak and /arete/recovery
carry a "Specimen · Arete mobile" masthead; plan/goal/review/coaching/patterns/
offering carry no register at all. Two of the pages mount REAL mutation
components against the fiction (see finding 16).
Fix sketch: wire getAreteStreak → /v1/arete/streak and getAreteReview →
/v1/arete/review now (both exist); for the rest, either wire to honest-empty
BFF reads or stamp an explicit preview register on the page until wired.
5. TaraSitPlayer reflection "save" fabricates success; mood-after and post-completion reflections are silently discarded#
Severity: P0-HONESTY
Evidence: apps/oshun/web/src/components/lilith/TaraSitPlayer.tsx:299-316 —
submitReflection online path is
await new Promise(r => setTimeout(r, 300)); setReflectionStatus('saved') → UI
prints "Saved · attached to today" (line 765). No request is made anywhere. The
offline path writes tara:reflection:{id} to localStorage and prints "will sync
when you're back online" (768-770) — no sync mechanism exists (the only repo
references to tara:reflection:/tara:completion: are the two writes, lines
207/303; the comment at 204 "localStorage queue picks it up on next online tick"
describes a reader that does not exist). moodAfter (687-697) is captured and
never sent. The completion POST (184-223) includes reflectionText but fires
exactly once at the completion transition — before the reflection textarea has
been typed into — and completionPostedRef blocks any re-send; the comment at
219-221 ("Future edits … are sent via the existing submitReflection flow") is
false.
Spec promise: features.md 524–526 ("Completion events: emit on completion with duration, modality, mood-after, journal capture"), 545–547 (save partial-session reflection), 535 (export-to-journal-entry with reflection capture).
What the code actually does: tells the member their reflection is "attached to today" while dropping it (and their mood) on the floor.
Fix sketch: make submitReflection POST to a real endpoint (extend
/v1/tara/sittings/:id/complete with an amend leg, or add
/v1/tara/sittings/:id/reflection) carrying { reflectionText, moodAfter };
add a mount-time flusher for the two localStorage queues; only show "Saved"
after a 2xx.
6. The C2 check-in engine has zero UI consumers; the only check-in UI is an orphaned component with a simulated save#
Severity: P0-HONESTY
Evidence:
POST /v1/arete/habits/:habitId/check-in(domain-stubs.ts:1289) — no caller inapps/oshun/web/srcorapps/oshun/mobile/src(grep: only partnership check-ins hit the network).components/domains/arete/DailyCheckInOverlay.tsx:127-151— the actual check-in flow:// SIMULATED BFF save, fake 600ms delay,console.info, then closes as if saved. And the component itself is orphaned — nothing imports it.- The home CTA
checkIn: buildLaunch('/check-in')(routes/arete.ts:474,551) lands on/domains/arete?path=/check-in, whichAreteSurface.tsx:530maps to the COACH CHAT view — there is no check-in view. The real-data/arete/habitspage (components/lilith/arete.tsx:2255, zero fetch/POST calls in the file) is display-only: no done/partial/skip/ decline control exists anywhere.
Spec promise: features.md 575–595 (check-in schema and status semantics as the core daily loop), 569 ("Mobile daily check-in").
What the code actually does: C2 built a durable, well-tested check-in record and humane fold engine that no member can ever reach. The product still has no way to check in — the 06-10 headline "no check-ins" is only half-closed (backend yes, product no).
Fix sketch: add done/partial/skip/decline buttons to the /arete/habits rows
(client component POSTing the C2 endpoint with bearer), repoint the home "Check
in" launch at it, and either wire DailyCheckInOverlay to the same endpoint or
delete it.
7. AreteAICoach (/domains/arete/coach) fabricates an AI coach — the B8 fix covered the other coach component only#
Severity: P0-HONESTY
Evidence: components/domains/arete/AreteAICoach.tsx:97-139 — every user
message gets the SAME canned reply ("That is a thoughtful observation. Let me
reflect that back to you…") after a fake setTimeout 1.5s "typing" animation,
seeded from a fabricated COACH_SESSIONS history (arete-extended-simulation).
Zero network calls in the file. B8 fixed AreteCoach.tsx (now POSTs
/v1/arete/coach/responses, line 401) — but /domains/arete/coach/page.tsx
renders AreteAICoach, the unfixed twin, on a static route with no preview
banner (finding 9).
Spec promise: features.md 631–635 (coaching summary/invitation grounded in observed patterns); CLAUDE.md zero-tolerance for fabricated results.
What the code actually does: simulates an AI conversation and presents it as coaching.
Fix sketch: have AreteAICoach's ChatView call the same
POST /v1/arete/coach/responses composer AreteCoach uses (same process, no
creds needed), start from an empty session, and drop COACH_SESSIONS.
8. TaraSoundLibrary is a silent fake player — play/pause/mixer/binaural UI with no audio code at all#
Severity: P0-HONESTY
Evidence: components/domains/tara/TaraSoundLibrary.tsx (2,562 lines) — play
buttons, "Binaural beat player with frequency display" (line 14), mixer layers,
an animated equalizer (isPlaying, line 1239) — and not one AudioContext,
OscillatorNode, or new Audio in the file (grep: zero). Rendered at
/domains/tara/sounds (static route, no banner — finding 9). Meanwhile the BFF
can already synthesize real ambient WAVs (tara/ambient-audio.ts, D2).
Spec promise: features.md 484–487 (sound modality: singing-bowl, drone, mantra), 529–531 (background-sound preferences).
What the code actually does: pressing play animates bars and produces silence — fabricated playback on a customer route.
Fix sketch: feed the library from the D2 synthesizer (an ambient.wav variant
per soundscape id — the planner is already deterministic per session id) or a
WebAudio oscillator graph for the binaural/drone presets; until then, banner the
route.
9. The D1 preview banner covers only the /domains/[domainId] root — all 19 static sub-routes render simulation data with no register#
Severity: P0-HONESTY
Evidence: DomainPreviewBanner is imported solely by
components/DomainRouteExperience.tsx:4 (rendered at line 579), whose only
route consumer is app/domains/[domainId]/page.tsx. Next.js static routes win
over the dynamic segment, so
/domains/arete/{affirmations,balance,coach, gamification,goals,habits,journal,plan-review,progress,seven-habits,time, vision}
and
/domains/tara/{analytics,collections,courses/[id],programs, search,sounds,teachers,teachers/[id]}
never mount it (e.g. app/domains/arete/habits/page.tsx renders
AreteHabitSystem bare). Those pages render exactly the components D1 was
about: AreteHabitSystem.tsx:2709 (useState(sampleHabits)),
AreteGoalSystem/AreteJournalSystem (sampleJournalEntries etc.),
TaraCollections.tsx:128,254 (SIMULATED_COLLECTIONS/PROGRAMS),
TaraCourseDetail, TaraTeacherProfile,
AreteJournalReflectionWorkspace.tsx:97-149 (fabricated entries for user-1),
plus findings 7 and 8. The D1 closure note ("mounted once … every /domains/*
deep surface now carries an explicit register") is false for the sub-routes.
Spec promise: D1's own accepted contract — fabricated data must never be mistakable for the member's own.
What the code actually does: the banner-as-accepted-state holds only on the two root URLs; one click deeper, the same simulated personal data renders with full visual authority.
Fix sketch: mount <DomainPreviewBanner domainId=…/> in a shared layout for
app/domains/tara/* and app/domains/arete/* (one layout.tsx per domain
folder), keyed to the same registry.
10. Two parallel data planes for the same Tara/Arete facts — goal3 stores vs the C1 facades — and surfaces mix them#
Severity: P0-STRUCT
Evidence:
- Tara progress: web hub writes/reads BFF goal3
taraCompletions(domain-stubs.ts:1049-1161), while/v1/tara/{streak,history,continue, favorites,recommended}(routes/tara.ts:175-250) read the facade →apps/tara/apiDrizzle tables, which never see the hub's completions. - Arete streak:
/v1/arete/streakcomputes from goal3 check-ins (domain-stubs.ts:1198); the reminder worker computes at-risk streaks fromapp.domainAdapters.arete.getStreakStats— the facade's apps/arete/api-side completions (server.ts:645); the web/arete+/arete/streakpages show a third value (the fixture, findings 3-4). - Habit completions have two write paths: C2 check-ins (goal3) and
routes/routines.ts:74-75→ facadelogHabitCompletion(apps/arete/api).
Spec promise: features.md 503–506 (one continuation state), 597–607 (one humane streak the member can trust).
What the code actually does: the streak a member could be nudged about (reminders, facade plane) and the streak the API computes from their check-ins (goal3 plane) and the streak the page shows (fixture) are three independent numbers that can never agree.
Fix sketch: pick one system of record per fact (the C1 facade is the
architecture the user chose) and make the other a write-through: hub sit
completions forward to the facade's progress write; /v1/arete/streak reads the
facade (or the facade reads goal3 check-ins) — then delete the loser.
11. Even inside the goal3 plane, the Tara loop is broken end-to-end: completions write to house-user, analytics reads the real userId#
Severity: P0-STRUCT
Evidence: POST /v1/tara/sittings/:id/complete records under the header-or-
'house-user' id (domain-stubs.ts:1130, no auth, web sends no header/bearer);
GET /v1/tara/analytics (domain-stubs.ts:1166-1191, auth-required) reads
pg.taraCompletions.listForUser(authContext.userId). The two ids can never
match for a real member.
Spec promise: features.md 461–463 (dashboard reflecting the member's practice); B9's "computed from real rows".
What the code actually does: a member completes sits forever and their analytics
dashboard remains the honest-empty state forever, while /tara's room progress
(read via the same house-user key) shows everyone the shared aggregate. The B9
dashboard is honest but unfeedable.
Fix sketch: same as finding 1 — authContext on the write path; the analytics read then works as-built.
12. The sit player pauses itself mid-meditation: 90s without pointer/keyboard input triggers "drift" while RUNNING#
Severity: P1
Evidence: TaraSitPlayer.tsx:31 DRIFT_IDLE_SECONDS = 90; tick loop 225-256
sets state='drifted' (stopping the timer and the ambient audio, 151-161) when
Date.now() - lastInteractionRef ≥ 90s; lastInteractionRef updates only on
play/pause/scrub/keyboard (258-296). Every sitting in the catalog is ≥180s
(tara/room.ts:42-55).
Spec promise: features.md 500-501 — drift is "idle beyond the per-modality
drift_idle_seconds threshold", i.e. detecting an ABANDONED session; sitting
still IS the practice.
What the code actually does: no sit can complete unless the member pokes the screen at least every 89 seconds; in practice the flagship flow always ends in "The session paused itself" at 1:30, and the real-progress overlay (room/streak) is unreachable through honest use.
Fix sketch: while state==='started' and ambient audio is playing, treat
playback as presence (refresh lastInteractionRef on timeupdate), or count
drift only from paused; per-modality thresholds belong in the sitting catalog.
13. Living Offerings: the spec's flagship Arete feature is a fixture composer + a 3-field volatile decision log, and the index can never show what you kept#
Severity: P1
Evidence: /arete/offering renders getAreteOfferingFixture()
(arete-depth.ts:389-427 — recipient "J", canned timeline/score/sharing rows);
AreteOfferingActions POSTs /v1/arete/offerings/keep which stores
{recipient, occasion, action} in the in-memory global store (finding 2) — no
scene, no score, no watermark, no shareability matrix, no re-render artifact.
The index /arete/offerings/page.tsx:46 reads GET /v1/arete/offerings — a
guardedFixtureRoute (domain-stubs.ts:1423, 503 in production) — NOT
/v1/arete/offerings/sent where keep/send writes land. A member who keeps a
draft then opens the index sees "No drafts in progress" (prod) or fixture rows
that aren't theirs (dev).
Spec promise: features.md 651–657 — intention → 4-8 min Living Scene, watermarked, kept in gallery, re-renderable forever, shareability matrix.
What the code actually does: composer is fiction; the only durable thing is a recipient string in RAM; the kept/sent lifecycle is split across two endpoints that never meet.
Fix sketch: point the index at /v1/arete/offerings/sent (merged with drafts)
so the loop a member can already perform is visible; the real scene/score
pipeline should ride the existing Living-Scenes render route (BFF
living-scenes/render-route.ts already does contemplative arcs) as the
generation backend.
14. Voice layer: guidance endpoint orphaned, voice-speed slider wired to nothing, and the hub claims "narrated" sessions by real teachers that don't exist#
Severity: P1
Evidence:
GET /v1/tara/sessions/:id/guidance(tara/ambient-audio-routes.ts:113-154, env-gated ElevenLabs, fail-closed — correctly built) has ZERO web consumers (grepguidance+/v1/tarain web: none). Even with creds provisioned, no member ever hears it.TaraSitPlayer.tsx:85,541-555— the "Voice speed · 0.85–1.25×" slider setsvoiceSpeedwhich is read by nothing (no voice element exists; the ambient loop'splaybackRateis untouched). A dead control implementing the spec's most specific audio knob.rooms.tsx:160hub prints "{teacher} · narrated";tara/room.ts:57-62hardcodes real public figures (Sam Harris, Tara Brach, J. Goldstein, Pema Chödrön) with fabricated sitting counts (142/96/71/34 — the catalog has 12 sittings);TARA_TODAY_FIXTUREclaims "Sam Harris reads twelve short essays". No narration audio exists anywhere in the product, and the deep teacher directory uses a disjoint fictional roster (tara-simulation-data.ts: Sarah Chen et al.), so the hub teachers link to nothing (see finding 21).
Spec promise: features.md 529-531 (voice speed with quality-preserving resample, ambient/voice mix), 469 (named teachers), D2's voice-guidance leg.
What the code actually does: ships the synthesis seam but not the experience; advertises narration and real-teacher content the player cannot produce (content/licensing risk on top of the honesty gap).
Fix sketch: fetch /guidance?phase=opening|midpoint|closing in the player at
the matching progress points (graceful skip on 503), drive
audio.playbackRate = voiceSpeed (preservesPitch defaults true), and replace
the real-people teacher fixture with the platform's own voice persona until
licensed content exists.
15. Arete balance + journal backends exist in the facade but no web surface can reach them; the web UIs are client-local fixtures#
Severity: P1
Evidence: facade balance endpoints (domain-service-adapters.ts:1794-1807,
/v1/oshun/balance + /latest; journal endpoints likewise per C1) are consumed
only by achievement stat counters (routes/achievements.ts:211-214). No
/v1/arete/balance or /v1/arete/journal BFF customer route exists. The web
components — AreteBalance.tsx (1,557 lines), AreteLifeBalance.tsx (892),
AreteJournalReflectionWorkspace.tsx (1,270, fabricated user-1 entries),
AreteJournalSystem.tsx (sampleJournalEntries) — make zero network calls.
Spec promise: features.md 570-572 ("Web plan/review workspace, journaling, reflection"), 626-630 (inputs include journal entries, mood, energy).
What the code actually does: a member cannot write a journal entry or a balance
assessment anywhere in the Oshun web product, while the storage and API for both
sit finished in apps/arete/api.
Fix sketch: add thin BFF routes proxying the arete adapter's journal/balance
read+write; wire AreteJournalReflectionWorkspace's editor and
AreteLifeBalance's assessment submit to them; fixture entries become the empty
state.
16. Real durable actions are mounted on fictional subjects — closures, offerings, and "approved adjustments" record decisions about content that doesn't exist#
Severity: P1
Evidence:
components/lilith/arete.tsx:1345—AreteReviewClosedurably records closure ofdata.weekLabel= the FIXTURE "week of 28 apr — 4 may" (arete-depth.ts:198) for every member, forever.arete.tsx:2230—AreteOfferingActionssends "to J" (fixture recipient).AreteCoachActions.tsx:41-60— "Approve" prints "two adjustments queued for the next review" but writes only a localStorage flag; the "adjustments" are fixture text; nothing is queued anywhere, and the claim survives only on that browser.
Spec promise: features.md 628-630 (plan adjustments saved with user approval); the codebase's own fail-loud-over-fake rule.
What the code actually does: honest stores, fictional referents — the durable record says the member closed a week they never lived and approved adjustments that were never proposed.
Fix sketch: these resolve automatically once findings 3-4 wire the pages to real
data; until then gate the action components behind a real weekLabel/ recipient
(render disabled with an honest "no live week yet" note), and make "Approve"
POST to a real adjustments record or say what it actually does.
17. getAreteHabits falls back to a fabricated 3-habit fixture when the BFF is unreachable#
Severity: P1
Evidence: lilith-data/arete-depth.ts:444-502 — on !remote,
getAreteHabitsFixture() returns invented habits ("Morning grounding sit ·
7-day streak · kept yesterday morning") rendered by /arete/habits as the
member's own. Tara's equivalent (tara.ts:16-18) returns an honest unavailable
register instead.
Spec promise: project rule "Always wire real data — never fixtures/fallbacks"; the C3 convention (honest zeros / unavailable on failure).
What the code actually does: a transient BFF failure silently swaps the member's real habit list for fiction — indistinguishable in the UI.
Fix sketch: return an areteHabitsUnavailable() shape (empty list + an
unavailable: true flag the page renders as a quiet notice), mirroring
taraUnavailable().
18. Orphaned Tara content plumbing: four library modules with zero consumers#
Severity: P2
Evidence: lib/tara/use-tara-content.ts (10 exported hooks over simulated data;
header says "swap queryFn when a real BFF endpoint is available"),
tara-cache.ts (imports its query keys; nothing imports tara-cache),
tara-monitoring.ts, content-filters.ts — no importer outside the chain (grep
across components/app: only content-types and tara-simulation-data are used,
by the simulated components themselves).
Spec promise: n/a (dead code; the modules exist to make simulation look like infrastructure).
What the code actually does: maintains a parallel React-Query layer that no surface uses — a trap for future wiring (someone will "wire" the hooks and get simulation).
Fix sketch: delete the four modules, or convert use-tara-content queryFns to
the real /v1/tara/* facade routes (which exist and are unconsumed —
finding 10) and adopt them in the deep components.
19. Tara hub copy inconsistencies: hardcoded "12 min" eyebrow and a second, contradictory path universe in /v1/tara/today#
Severity: P2
Evidence: rooms.tsx:110 — eyebrow literal "Today's sit · 12 min" regardless of
todaysSit.durationSeconds (the Begin link beside it computes the real minutes
— for the first sit they read "12 min" and "Begin · 3 min" together).
domain-stubs.ts:131-145 — TARA_TODAY_FIXTURE claims
pathLabel 'path IV', sessionIndex 3, pathTotal 21 vs the room's single
12-sitting path; consumed by lilith-studio/tara.
Spec promise: features.md 461-463 (coherent session/path model).
Fix sketch: render the real duration in the eyebrow; align TARA_TODAY_FIXTURE
with the SITTING_PATH source of truth (or derive it from buildTaraRoom).
20. /v1/tara/sittings/:id/complete accepts arbitrary sitting ids#
Severity: P2
Evidence: domain-stubs.ts:1123-1147 — any non-empty string is recorded as a
completion; no validation against TARA_SITTING_CATALOG/SITTING_PATH.
Spec promise: features.md 497-503 (session state is per-catalog-session).
What the code actually does: garbage ids inflate the streak (streak counts distinct days of any completion) and pollute analytics' per-sitting grouping.
Fix sketch: 404 unless the id is in the shared catalog module (export the id set
from tara/room.ts).
21. Dead and misrouted hub controls (Tara + Arete)#
Severity: UX
Evidence:
rooms.tsx:67-75— Tara hubLSubNavtabs "Courses / Teachers / Library" have nohref;LSubNavrenders href-less items ascursor:pointerspans (design-system/lilith/shells.tsx:179-189) — three dead tabs, while real destinations exist (/domains/tara/collections,/domains/tara/teachers,/library).rooms.tsx:269-278— each hub teacher row ends in an "↗" that is aspan, not a link (and no teacher page exists for the real-person roster — finding 14).rooms.tsx:443-462— Arete hub habit rows and "+ New ritual" deep-link to/domains/arete/habits?habit=…/?wizard=loop, butapp/domains/arete/habits/page.tsxpasses onlyonBack; both params are ignored, and the destination is the unbannered SIMULATOR while the real wired pages (/arete/habits,/arete/habits/new) exist one path over.
Spec promise: features.md 453-456 / 560-566 (hub → deep-tool continuity).
Fix sketch: give the three tabs hrefs; make teacher rows link to the teacher
directory; repoint hub habit links + "New ritual" at /arete/habits and
/arete/habits/new.
22. Register whiplash: three disagreeing datasets within one click of /arete#
Severity: UX
Evidence: /arete (fixture: best streak "Sit · 31 days", coach note) → subnav
"Habits" → /domains/arete/habits (simulation: "Morning meditation · 22d", no
banner) vs /arete/habits (real BFF rows, likely empty) vs /arete/streak
(fixture: 47 days, "day 109"). Four surfaces, four answers to "what's my
streak?", none of them the C2-computed truth.
Spec promise: features.md 597-607 (one humane streak); D1's register contract.
Fix sketch: findings 3, 4, 9 and 21 collectively resolve this; the acceptance
test should be "every visible streak number traces to /v1/arete/streak".
23. Silent error-states: analytics dashboard and offerings index render success-shaped emptiness on failure#
Severity: UX
Evidence: TaraAnalyticsDashboard.tsx:893-913 — fetch failure keeps
EMPTY_MODEL with no "couldn't load" notice (honest-empty and error are
indistinguishable; with finding 11, every member sees it forever).
app/arete/offerings/page.tsx:46-49 — a 503 from the prod-guarded endpoint
collapses to offerings: [] → "No drafts in progress", presented as the
member's true state.
Spec promise: product polish — distinct empty/loading/error registers.
Fix sketch: track a failed flag alongside loading; render a one-line quiet
"couldn't reach your practice history — try again" register distinct from the
genuine empty state.
24. DEPLOY register (not code defects; fail-closed as designed)#
Severity: DEPLOY
- Voice guidance:
OSHUN_ELEVENLABS_API_KEY+OSHUN_ELEVENLABS_VOICE_ID(ambient-audio-routes.ts:54-63) — 503voice_guidance_not_configureduntil provisioned (but see finding 14: provisioning alone changes nothing for members today). - C1 facades:
OSHUN_{TARA,ARETE}_API_BASE_URL+OSHUN_DOMAIN_SERVICE_TOKEN/{TARA,ARETE}_OSHUN_FACADE_TOKEN(app.ts:1136-1149, domain-service-discovery.ts) — non-dev boot throws without explicit origins; facades fail closed without the token. - goal3 Postgres tier:
OSHUN_STUBS_DATABASE_URL/DATABASE_URL— without it, streak/check-in/completion/analytics 503 (correctly) and room/sittings serve the fresh-path default.
Severity counts#
| Severity | Count | Findings |
|---|---|---|
| P0-SEC | 2 | 1, 2 |
| P0-HONESTY | 7 | 3, 4, 5, 6, 7, 8, 9 |
| P0-STRUCT | 2 | 10, 11 |
| P1 | 6 | 12, 13, 14, 15, 16, 17 |
| P2 | 3 | 18, 19, 20 |
| UX | 3 | 21, 22, 23 |
| DEPLOY | 1 | 24 |
| Total | 24 |