Date: 2026-06-11 · Auditor slice 04 · Baseline: V1_SPEC_GROUND_TRUTH_AUDIT_2026-06-10.md (all 57 tasks landed)
Evidence rule honored: every claim below is from current source read this
session; comments/tests/docs were not trusted as proof. Recorded V1.x deferrals
(full Nisaba service, Sefaria/CDLI), D1-bannered /domains/* illustrative data,
and env-cred-gated providers are NOT re-reported as findings except where the
residual goes beyond the recorded deferral.
What was verified GOOD first (no finding):
- B2 metis ingest is real.
apps/oshun/bff/src/metis/ingest-pipeline.tsderives a deterministic study outline (paragraph segmentation, stopworded key-term frequency, prompt synthesis, 200-wpm stats) from actual provided text; URL/PDF-without-bytes jobs are recorded honestawaiting_source_content(ingest-job-store.ts), owner-scoped GET, durable viawireDurableMetisIngestJobs(server.ts:410), wired to the web form (apps/oshun/web/src/app/metis/ingest/). No fabricated queued jobs remain. - D6 corpus is genuine.
apps/oshun/bff/src/nisaba/public-domain-corpus.ts— 9 real public-domain quotations (Müller 1881, Carter 1758, Long 1862, Legge 1891, KJV 1611) with real translator attribution;buildCorpusPassage(nisaba/state.ts:872-906) is honest-zero on progress/saved/lastReadAt; stats derived from actual text. - The nisaba consumer route surface (
routes/nisaba.ts, 2,567 lines) is real store-backed CRUD with auth + scope + pagination + revision-keyed caching; notebooks/annotations create/update flows are real and owner-scoped. - Themis adjudication/appeal HTTP surface (
routes/metis-integrity.ts) wires the realMetisThemisCanonicalBridge; tutor-session-memory route wires the real@metis/models/session-memoryengine and is snapshot-durable (server.ts:422). BYOM operator decisions are durable (server.ts:407) and UI-wired (MetisByomDecisionPanel.tsx:80). /metis/sessionfalls back honestly to empty lessons (app/metis/session/page.tsx:48-51);/nisabaand/metishubs fetch the BFF server-side with honest*Unavailable()fallbacks (lib/lilith-data/nisaba.ts,metis.ts).
1. Metis academic-integrity reads have no ownership check — any learner can read any verdict audit and any appeal workspace#
Severity: P0-SEC
Evidence: apps/oshun/bff/src/routes/metis-integrity.ts:199-229 —
getWorkspaceHandler and getAuditHandler call
requireScope(request, reply, hasMetisScope, 'metis') and then discard the
returned userId; academicIntegrityAppealStore.getWorkspace(appealId) /
getAuditRecord(verdictId) (metis/academic-integrity-appeal-store.ts:48-54)
take only the id — no appellant/respondent/learner binding is ever compared to
the caller. openAppealHandler (:131-166) likewise lets any domain:metis
holder open an appeal against any verdictId, including another learner's.
Spec promise: features.md 1498-1509 — appeals belong to the learner ("learner can contest"); 1300-1301 / 1506-1509 — the audit record carries detection signals, classifier output, evidence excerpts, decision rationale; 1566-1568 — "per-tenant visibility rules govern who sees what... evidence packs are linkable only to operators with the right scope."
What the code actually does: every authenticated consumer holds domain:metis
(it is a standard domain scope), so any member who learns or guesses a
verdictId/appealId can read another learner's integrity evidence excerpts
and file appeals on their verdicts. Adjudicate/resolve are operator-gated
correctly; the read/open legs are not.
Fix sketch: persist learnerId/appellantId on verdicts and appeals; in the
two GET handlers and openAppeal, require
authContext.userId === record.learnerId (or operator scope). Return 404 on
mismatch to avoid id-probing.
2. /v1/activity fabricates Nisaba and Metis timeline items for every member, unconditionally, in production#
Severity: P0-HONESTY
Evidence: apps/oshun/bff/src/routes/activity.ts:559-660 (nisaba branch) and
:662-712 (metis branch) inside fetchDomainTimelineItems. Unlike the tara
branch (which gates on a real attemptDomainCall), the nisaba and metis
branches unconditionally return hardcoded items: "Notebook handoff... 8
annotations", a passage card for
'Bhagavad Gita 2.47' / 'compassion-across-lineages' / 'Comparative Study Edition'
— ids and edition from the pre-D6 fixture corpus that no longer exists in any
store — and a metis card claiming "Calculus I recovery path · 24 lessons · 34%
complete · tutoring support attached" with
grounding: { state: 'grounded', citationCount: 8 } and
provenance: { state: 'verified' }. Action hrefs point at
/library/passage/compassion-across-lineages (no such passage),
/courses/crs-002 (no /courses route exists in apps/oshun/web/src/app —
verified by ls), and /domains/metis?...path=/assessments/calc-checkpoint-1
(lands on the Metis launch-readiness preview gate, not a mastery check).
Spec promise: features.md 1068-1070 / 1182-1184 — "First-class shell recognition across ... activity timeline" means the member's real Nisaba/Metis activity; the platform-wide rule (and the 06-10 audit's C-series convention) is honest-empty per-user reads.
What the code actually does: every member's Activity timeline asserts they completed reading, annotating (8 annotations), and 34% of a calculus course they never opened, stamped "verified" and "grounded" — fabricated provenance/grounding metadata on a first-class authenticated shell surface with no preview register.
Fix sketch: mirror the tara branch — derive nisaba items from
nisabaConsumerStateStore (real notebooks/annotations, honest empty otherwise)
and metis items from the real course-proposal + ingest-job stores; delete the
fabricated grounded/verified badges and the dead hrefs.
3. /v1/metis/room serves fabricated per-user learning state — unguarded in production#
Severity: P0-HONESTY
Evidence: apps/oshun/bff/src/metis/room.ts:90-103 — buildMetisRoom always
returns
weekStats: { lessonsRead: '4 · 6', assessments: '2 · 3', tutorMinutes: '34', streak: '11 days' },
resume: { sectionNumber: 4, ... minutesToday: 14 }, an openAssessment "due
Sun", curated courses with per-user progress: 0.34/0.18/0.42, and
tutorNudge: '"You marked Discourses I.4 yesterday — would you like to walk through the second half together?"'.
The route (routes/domain-stubs.ts:1694-1707) is not wrapped in
guardedFixtureRoute (unlike /v1/metis/lessons at :1673) and the web hub
renders it as the member's schoolroom
(apps/oshun/web/src/lib/lilith-data/metis.ts:14-19, fetched by /metis).
Spec promise: features.md 1223-1224 — "Learner-facing progress, mastery, study recommendations, continuity" (the learner's real ones); 1186-1190 — honest empty/loading/degraded states are first-class.
What the code actually does: a brand-new member opens /metis and is told they
have an 11-day streak, studied 14 minutes today, are in week 2 of Stoic ethics,
have an assessment due Sunday, and that the tutor remembers them marking
Discourses I.4 yesterday — none of which happened. Only the drafted-course
overlay is real (C3). The tutor-nudge is fabricated memory in quoted tutor voice
— the exact pattern C4 purged from Iris.
Fix sketch: compute weekStats/resume from real stores (ingest jobs, course
proposals, tutor-session memories — all exist and are durable); replace
openAssessment/tutorNudge with honest null states the MetisRoom component
already needs for its unavailable variant; keep the curated catalog but at
progress: 0 / no week cursor until real play state exists.
4. Nisaba per-user seed fabricates member-authored annotations, notebooks, and "grounded/verified" evidence views with invented citations — on unbannered consumer routes#
Severity: P0-HONESTY
Evidence: apps/oshun/bff/src/nisaba/state.ts:908-1635 —
buildSeedRecord(userId) gives EVERY member, at first touch: (a) three
annotations authored as the member
(author: { userId, displayName: deriveDisplayName(userId) }, lines 1524-1580)
with notes they never wrote and timestamps 2026-03-22/23; (b) two pre-built
notebooks ("Attention notebook", "Cross-tradition compare", lines 1586-1624)
presented as theirs; (c) grounded evidence views (lines 1074-1460) with
groundingStatus: 'grounded', fabricated processingTimeMs: 1480/1845,
claimChecks with status: 'verified' and support counts, and invented
citations: excerpts attributed to "Majjhima Nikaya, MN 118" that are not MN
118 quotations ("Attend to the breath as it lengthens and shortens...", "When
attention wanders, return without violence" — paraphrases presented as
scripture, the exact thing the D6 corpus header at public-domain-corpus.ts:5-7
forbids) plus a nonexistent commentary "Practices of Recollection, M. Anand,
2024, https://oshun.app/sources/recollection" with reliabilityScore: 0.78 and
scholarlyConsensus: 'moderate'. Served by /v1/nisaba/notebooks,
/v1/nisaba/passages/:id/ annotations,
/v1/nisaba/passages/:id/grounded-evidence (routes/nisaba.ts) — consumed by the
/nisaba hub pages and NisabaSurface. D6 fixed reading-state and labeled the
manuscript witness ("Illustrative example — not a real repository holding",
state.ts:920) but left these.
Spec promise: features.md 1115-1117 (annotations are the member's, with visibility rules); 1131-1134 ("assistant-assisted explanation whose every interpretive claim exposes inspectable Sophia citations" — i.e., real ones); C-series convention: new member starts honestly empty.
What the code actually does: fabricated user authorship (C4's "identical seeded
Nisaba-study memories for every user" pattern, surviving here), and
result-faking grounding: 'verified' claim checks and 'grounded' status from a
pipeline that never ran, citing a fabricated source and misquoted scripture. The
annotation/notebook counts (annotationCount: 2, state.ts:1043) are internally
consistent with the seeds, which makes the fabrication more convincing, not
less.
Fix sketch: start members at zero annotations/notebooks/evidence views (the
stores already handle empty); move the three curated annotations to
clearly-attributed editorial content if wanted ("Oshun editors", not the
member); either delete the seeded evidence views or re-ground them through the
real Sophia extractive composer over the corpus with honest processingTimeMs
and citations that quote only the actual public-domain bodies.
5. The Metis adaptive-learning spine remains fully orphaned — IRT, mastery bands, adaptive loop, gradebook emission, BYOM-model safety harness have zero runtime consumers#
Severity: P0-STRUCT
Evidence: import sweep over apps/ (excluding tests/node_modules): zero imports
of @metis/assessment (IRT 1PL/2PL/3PL in
libs/metis/assessment/src/irt-models/, adaptive selection in adaptive/),
@metis/learning (mastery transitions
libs/metis/learning/src/mastery/ transitions.ts, personalization signals),
@metis/tutoring (the adaptive loop
libs/metis/tutoring/src/loop/adaptive-loop.ts), @metis/gradebook
(emission.ts + LTI-AGS / xAPI / Caliper / file-export adapters),
libs/shared/inbound-integrations/src/byom.ts (the tenant-model sandbox; the
only @oshun/inbound-integrations consumers are calendar/SSO/LMS files), and
libs/metis/integrations/src/byom (applyByomSourceApprovalDecision). The only
@metis/* runtime consumers anywhere are: models/session-memory (BFF
tutor-memory store + apps/metis/web), integrations/themis-canonical-wiring
(BFF integrity store), integrations/oshun-continuity-bridge (assistant
handoff), integrations/cross-domain-flows and llm-client (apps/metis/web).
Spec promise: features.md 1363-1370 — "The loop is launch-blocking: Metis ships to institutions, and that contract turns on its integrity"; 1372-1402 (mastery transition model), 1433-1465 (adaptive loop, P95 ≤ 600 ms), 1546-1571 (gradebook contract: "Without this layer Metis cannot ship to schools"), 1515-1544 (BYOM safety surface).
What the code actually does: the engines exist and are real (this is the same state the 06-10 audit flagged), but no item turn anywhere produces evidence, no mastery band ever transitions at runtime, no GradebookEntry is ever emitted to any LMS adapter, and no BYOM tenant-model endpoint can be onboarded, sandboxed, or kill-switched — there is no registry, no call path, no enforcement. The tutor-session-memory route folds understanding into a mastery posterior (real) but nothing connects it to item delivery, next-item selection, or gradebook emission.
Fix sketch: this is the largest残 piece of launch-blocking scope. Minimum
credible wiring: drive adaptive-loop from the tutor-memory route's turn input;
persist MasteryTransition records; emit GradebookEntry through the existing
file-export/xAPI adapters on assignment completion. BYOM-model onboarding needs
a tenant endpoint registry + the existing sandbox harness on the call path — or
an explicit recorded deferral, which does not currently exist for this (unlike
the Nisaba service).
6. /v1/nisaba/room is a third "today" with fabricated marginalia counts and a non-public-domain translation#
Severity: P1
Evidence: apps/oshun/bff/src/nisaba/room.ts — the room passage is hardcoded
"Anattalakkhaṇa Sutta, SN 22.59 ... trans. Bhikkhu Bodhi" (lines 29-33) with six
body paragraphs of that translation, while the store's daily passage everywhere
else (E2's "one today") is nisaba-passage-attention (Dhammapada I.1-2).
buildNisabaRoom(realAnnotationCount) overlays the member's annotation count
for the Dhammapada passage (routes/nisaba.ts:1935-1941) under the SN 22.59
display, and ships fabricated marginalia counts: "Cross-references: 4", "Saved
excerpts: 2" (room.ts:86-88 — the comment admits "default to the curated counts
until those stores are surfaced"). The Bhikkhu Bodhi SN translation (Wisdom
Publications, 2000) is not public domain, unlike every D6 corpus text.
Spec promise: features.md 1131-1134 (one daily passage per plan/interest); 1141-1143 ("exports are gated on the source edition's rights metadata"); D6's recorded principle: genuine public-domain quotation with real attribution.
What the code actually does: /nisaba (hub) shows a different "today" than
/v1/nisaba/ passages/daily, /v1/nisaba/adapter/daily-passage, and
NisabaSurface (E2 unified those two, the room stayed out); the annotation count
shown belongs to a passage the member isn't looking at; and the platform serves
a copyrighted translation verbatim on its most-visible Nisaba surface while
claiming a public-domain-only corpus posture.
Fix sketch: build the room from
nisabaConsumerStateStore.getDailyPassage(userId) (real body, sections from
paragraphs, real annotation/saved counts, honest 0 cross-references); if the
Anattalakkhaṇa Sutta is wanted, use a public-domain rendering and add it to the
corpus with real attribution.
7. NisabaSurface still mixes fixture data into live mode — the nisaba preview label CANNOT be lifted yet#
Severity: P1
Evidence: apps/oshun/web/src/components/domains/NisabaSurface.tsx — (a)
buildDerivedPassage (:11328-11359): opening a compare target renders
SUPPLEMENTAL_PASSAGE_DETAILS[target.passageId] (:549) instead of fetching the
real passage; for the REAL corpus id nisaba-passage-speech (Enchiridion
XXXIII, tr. Carter in the BFF) the fixture body is "Hatred is not quieted by
hatred..." with tradition: 'Buddhist',
sourceName: 'Annotated Practice Reader' — the member sees the Enchiridion
reference/title over the wrong tradition and an invented body, in live mode;
defaults fabricate annotationCount ?? 4, progressPercent ?? 20. (b)
:1135-1137 — a live-but-EMPTY notebooks response is replaced by
FALLBACK_NOTEBOOK_LIST_RESPONSE ("Morning grounding notebook", 11 items),
defeating the honest-empty contract even when the BFF answered. (c) :1224-1227
— groundedViews falls back to fixture views with
groundingStatus: 'grounded'. (d) :1273-1276 —
buildNisabaSavedPassageCard(SAVED_PASSAGE...) renders a fixture saved passage
("Dhammapada 1.5", 9 annotations, saved 2026-03-23) unconditionally, even in
surfaceState === 'live', while the real /v1/nisaba/adapter/saved-passages
endpoint goes unused. (e) :1133 + :2720-2727 — on a BFF error the
fixture daily (Gita 2.47, progressPercent: 62, saved: true,
annotationCount: 12 at :589-620) is shown under the label "Cached Nisaba
workspace" — nothing was cached; that is the embedded fixture. Mitigations that
DO exist: DOMAIN_PREVIEW_SURFACES.nisaba === true
(DomainPreviewBanner.tsx:32) keeps the banner on, and the surface carries a
live/fallback/curated status label; real BFF hooks (daily passage, annotations
read/create/update, notebooks, manuscript) are wired and primary.
Spec promise: features.md 1060-1063 (the /domains/nisaba power-user surface),
1118-1121 (split-view compare renders the editions), plus D1's own bar:
"fabricated data must never be mistakable for their own" and the banner
registry's instruction to flip nisaba to false only once wired.
What the code actually does: the spine is live, but compare bodies, empty-state notebooks, grounded views, the saved-passage card, and the error path still substitute fabricated content — including misattributed real-corpus ids — so the surface is not yet honest enough to drop the banner.
Verdict on the open D1 question: keep nisaba: true. Lift it only after:
compare targets fetch /v1/nisaba/passages/:id for real bodies; live-empty
notebooks render honest empty; fixture grounded views and SAVED_PASSAGE card
removed (wire adapter/saved-passages or drop the card); error state labeled as
unavailable, not "Cached".
8. BYOM source-approval decisions gate nothing — stored-config-never-enforced#
Severity: P1
Evidence: apps/oshun/bff/src/metis/metis-byom-decision-store.ts (real,
durable, audited decision records) — but the only consumers of the store are its
own admin route (routes/admin-metis-byom-decision.ts) and the operator panel.
Nothing in the generation path reads the decision state: /v1/metis/ingest
(domain-stubs.ts:1756) derives an outline with no approval check, and the lib
that maps decisions to canonical bundle states
(libs/metis/integrations/src/byom, named in the store's own header) has zero
runtime imports.
Spec promise: features.md 1208-1209 — "Source approval before high-stakes course generation, tutoring, or publication."
What the code actually does: an operator can record
approve/reject/request-changes verdicts that are faithfully persisted and
listed, and ingestion/generation proceeds identically regardless. Note the
naming hazard: this /metis/byom is bring-your-own-material; the spec's
"BYOM safety surface" (1515-1544) is bring-your-own-model and is entirely
unimplemented at runtime (finding 5).
Fix sketch: have ingest-to-course / study-guide generation look up
listForSource(sourceId) and refuse (fail-loud source_not_approved) for
high-stakes outputs when the latest decision is not 'approved'; document which
outputs count as high-stakes.
9. Tutor runtime: real session-memory engine has no producer; learner-facing tutor surfaces render fixture transcripts and fixture KPIs#
Severity: P1
Evidence: POST /v1/metis/tutor-session-memories (routes/metis-tutor-memory.ts)
folds turns into a real mastery posterior and is durable — but a repo-wide grep
finds no UI or runtime producer; only e2e specs call it. Meanwhile the
member-facing /metis/tutor page (app/metis/tutor/page.tsx:14) renders
getMetisTutorFixture() (lib/lilith-data/metis-depth.ts) — a hardcoded
transcript including turns in the member's own voice ("I keep getting tangled on
apatheia..."); /metis/byom renders fixture materials including "My Discourses
notes (last semester)" with rights "mine"; /metis/assessment a fixture rubric;
and /operator/metis (app/operator/metis/page.tsx:15) fixture operational
KPIs — "Tutor turns · 24h: 1,820" and "OneRoster · in sync ✓" — with no register
label on any of these pages.
Spec promise: features.md 1259-1279 (tutor runtime modes with logged switching), 1186-1190 (honest states), 1193-1197 (admin dashboards inspect real telemetry).
What the code actually does: two sources of truth — a real engine wired to HTTP with nothing feeding it, and product surfaces feeding members (and operators) fabricated conversations, uploads, and an "in sync" OneRoster claim. The operator KPI fixture is the most hazardous: it asserts operational health that was never measured.
Fix sketch: drive /metis/tutor from the real session-memory store (list the
member's sessions, honest empty state otherwise) and post turns to the existing
route; replace /operator/metis KPIs with real counts (tutor-memory store,
ingest store, OneRoster apply store all exist) or honest "not measured"; label
any remaining presentational content as such.
10. Nisaba depth pages and notebooks index: unlabeled fixture user-data, silent fixture fallback#
Severity: P1
Evidence: (a) lib/lilith-data/nisaba-depth.ts ("fixture today, BFF tomorrow",
:3) — every
/nisaba/{scholar,compare,manuscript,lexicon,graph,notebook,plan,daily} page
renders fixtures: scholar marginalia include
'Renata · 6 May · early morning · the wet stone.' (:39 — the same fabricated
"Renata" persona C4 purged from memory controls) presented as reader marginalia;
getNisabaDailyFixture (:570-592) fabricates dayNumber: 109 and "Tuesday, 6
May" study-day state (and cites "trans. Farquharson 1944", a translation whose
US public-domain status is doubtful). No page carries a preview/illustrative
register (checked app/nisaba/scholar/page.tsx, daily/page.tsx). These are
NOT covered by D1's banner (that covers /domains/* only). (b)
app/nisaba/notebooks/page.tsx:70-71 — the real /v1/nisaba/notebooks read
silently swaps to NOTEBOOK_FIXTURE whenever the BFF refuses (including auth
failures and outages), with dynamically fabricated
updatedAtIso: Date.now() - 32d timestamps and "N held" masthead counts; the
only acknowledgment is a code comment ("for layout review").
Spec promise: features.md 1057-1060 records these as "presentational depth" — but presentational copy is one thing; first-person reader marginalia, day counters, and notebook counts presented in the member's own register without a label is the "mistakable for their own" hazard D1 exists for.
What the code actually does: a signed-in member on /nisaba/scholar sees
marginalia attributed to a reading life they don't have; on /nisaba/notebooks
during a BFF blip they see notebooks they don't own, dated relative to today.
Fix sketch: smallest honest fix — render the depth pages' user-shaped regions
(marginalia, day counter, notebook list) from the real stores or honest-empty,
keeping the curated scholarly apparatus; for notebooks, show the existing
empty/unavailable state instead of NOTEBOOK_FIXTURE. Alternatively extend the
D1 banner mechanism to these routes.
11. Nisaba consumer state and integrity verdicts are not durable — member annotations/notebooks and academic-integrity evidence vanish on restart#
Severity: P1
Evidence: nisabaConsumerStateStore is a bare in-memory Map
(apps/oshun/bff/src/nisaba/state.ts:431-432, 859) with no wireDurable* call
in server.ts (verified: the durable wiring list includes metis
ingest/byom/tutor-sessions but nothing for nisaba). Likewise
academicIntegrityAppealStore holds verdicts/appeals only inside the in-memory
bridge (metis/academic-integrity-appeal-store.ts:32), unwired to any snapshot
store.
Spec promise: features.md 1115-1117 ("durable re-anchoring" of annotations), 1121 (reading resumes across devices); 1496-1497 ("evidence preserved beyond routine retention" for severe violations).
What the code actually does: a member's real created annotations, notebooks, and
notebook items — and every adjudicated integrity verdict, appeal, and audit
record — are lost on every BFF deploy. The codebase's own createSnapshotSink
pattern (used one directory over for metis stores) is the established remedy and
was simply not applied here.
Fix sketch: add wireDurableNisabaConsumerState / wireDurableIntegrityCases
snapshot sinks mirroring wireDurableTutorSessions (explicit Map serialization,
write-through on mutation).
12. Nisaba scholarship engines remain orphaned (collation/philology/languages) — consistent with the V1.x deferral, but the only "consumer" is a coverage assertion#
Severity: P2
Evidence: zero imports of @nisaba/philology, @nisaba/criticism (the
Needleman-Wunsch collation engine at
libs/nisaba/criticism/src/collation-engine.ts + block-alignment +
transposition detection), @nisaba/languages (28 ancient-script modules),
@nisaba/editions, @nisaba/canon etc. anywhere outside libs/nisaba/. The
single runtime touch of the whole 23-library Nisaba estate is
NisabaSurface.tsx:5/123 importing assertNisabaWebReadingWorkspaceCoverage —
a static registry self-check
(libs/nisaba/workspace/src/web-reading-workspace.ts:451) that validates the
lib's own constant table, not any product behavior.
Spec promise: features.md 1095-1114 (manuscript/edition schema, critical apparatus, lexicon/ morphology lookup) — but the full Nisaba service is a recorded V1.x deferral, so this is logged as residual state, not a violation.
What the code actually does: nothing — the engines are pointed at nothing, exactly as on 06-10. No fabrication is involved; the in-repo state is honest. Flagged so the V1.x service work knows the runtime gap is unchanged and so nobody mistakes the workspace-coverage import for integration.
Fix sketch (optional, pre-V1.x): the cheapest real consumer is the compare lane
— run the collation engine over the corpus's compare pairs server-side to
generate the alignments the compare view already displays (currently curated
text in state.ts).
13. Cross-device study-state sync is write-only and the study-plan-drafts slot is dead — Nisaba study artifacts remain device-local#
Severity: P2
Evidence: NisabaSurface persists notes/drafts/collections/study-plans/density to
localStorage only (lib/nisaba-offline.ts:14-18, surface hydration at
NisabaSurface.tsx:1855-1869). The E6 sync lib declares 'study-plan-drafts'
as a ClientStateKey (lib/client-state-sync.ts:26) but a repo-wide grep finds
zero pushClientState('study-plan-drafts'...) producers and zero
pullClientState callers of any key anywhere — the only push is
workspace-layout (MultiPanelWorkspace.tsx:463), which itself is never pulled
back.
Spec promise: features.md 1120-1121 — "reading position is a ContinuationToken surface so study resumes mid-passage across devices"; 1127-1130 (study plan learner tracking).
What the code actually does: a member's Nisaba study notes, collections, and plans exist on one browser; the durable per-user client-state route exists (E6) but the read-back leg is missing, so even the one synced slot is write-only. (Cross-slice note: the pull gap affects all E6 consumers, not just Nisaba — flagged here because the dead key is a study-plan key.)
Fix sketch: push the Nisaba study-plan/notes payloads through
pushClientState('study-plan-drafts',...) on persist, and call
pullClientState + mergeByUpdatedAt during the surface's hydration effect.
14. Metis search/assistant seeds contradict the six-discipline spec focus and point at routes that don't exist#
Severity: UX
Evidence: routes/metis-search-seeds.ts:9-33 — the assistant/search seed
catalog leads with "Calculus I", "Data Structures and Algorithms ... for
technical interviews and production systems", "Introduction to Machine
Learning"; consumed by the assistant at routes/assistant.ts:285-292 (falls
back to the FULL seed list when no filter matches). crs-002 hrefs from these
and sibling fixtures point at /courses/crs-002 (routes/library.ts:613-628,
routes/cross-domain-recommendations.ts:617,703, routes/assistant.ts:161) —
there is no /courses route in apps/oshun/web/src/app — and at
/domains/metis?... deep paths that land on the MetisShellEntryPreview launch
gate, not the promised content. The metis room's curated catalog (PHL/AST/GRK,
room.ts:51-63) tells a third, different catalog story.
Spec promise: features.md 1166-1178 — Metis "centers heavily on six core disciplines — philosophy, religion, psychology, neuroscience, anthropology, and astronomy ... technical foundations included as scaffolding rather than as the headline curriculum."
What the code actually does: the assistant recommends an interview-prep STEM catalog the spec explicitly de-centers, three surfaces disagree about what courses exist, and the recommended links dead-end.
Fix sketch: replace the seed list with the room's six-discipline curated catalog
(one source of truth), and route hrefs to surfaces that exist (/metis,
/domains/metis entry, or drop the link).
15. Nisaba manuscript witness text does not match its passage — labeled illustrative, but incoherent#
Severity: UX
Evidence: nisaba/state.ts:916-1028 — the (correctly labeled) illustrative
manuscript witness for nisaba-passage-attention (Dhammapada I.1-2, "Mind
Precedes All States") carries a transcription/ translation of the Ānāpānasati
breath-mindfulness text ("Anapanassatim bhikkhave bhavetham...") and folio
regions glossing breath practice, while its own summary claims it keeps the lane
connected to "the exact excerpt behind the translated passage." The web fixture
duplicates the mismatch (NisabaSurface.tsx:400+, witness attached to "Bhagavad
Gita 2.47").
Spec promise: features.md 1095-1100, 1118-1120 — manuscript image alongside its transcription of the passage being read.
What the code actually does: the scholarly showcase teaches the wrong lesson — the witness "behind" the translation has no textual relationship to it.
Fix sketch: either bind the witness to a breath-text passage added to the corpus, or write an illustrative witness whose transcription is the actual Pali of Dhammapada I.1-2.
16. /v1/metis/adapter/* is permanently unavailable (no service implements the adapter contract) and the Sophia/Telegram retriever can never return evidence#
Severity: DEPLOY
Evidence: all eleven /v1/metis/adapter/* reads (routes/metis.ts) and
/v1/metis/{recommended, continue,search} delegate to
app.domainAdapters.metis dialing oshun.domain.metis.adapter paths at
OSHUN_METIS_API_BASE_URL (config/domain-service-discovery.ts:74-81,
adapters/domain-service-adapters.ts:2056+); the only Metis service in the repo
(apps/metis/api-gateway) serves /api/v1/*, not that contract — no
implementation of the adapter surface exists anywhere (repo-wide grep). Same for
nisaba's /v1/oshun/* adapter paths (domain-service-adapters.ts:1894+) — and
the BFF's Sophia retriever (telegram/sophia-grounder-bridge.ts:55-103, wired
at app.ts:1126) depends solely on domainAdapters.nisaba.searchLibrary, so it
always catches, returns zero citations, and abstains.
Spec promise: features.md 1182-1190 (Metis shell recognition / learner reads); 1212-1214 (source-aware tutor answers with citation inspection).
What the code actually does: everything fails honest (502 / abstention) — no
fabrication — but these are not env-cred gaps: there is no deployable
implementation to point the env vars at. The Nisaba service half is the recorded
V1.x deferral (not re-found); the Metis half has no recorded deferral, and the
assistant's grounding keystone always abstains even though the BFF holds a real,
searchable 9-passage corpus locally in nisabaConsumerStateStore.
Fix sketch: short-term, back searchLibrary (and the nisaba adapter daily/saved
reads, per the E2 pattern) with the local consumer store so grounding has real
evidence; for Metis, either implement the /v1/oshun/* adapter facade inside
apps/metis/api-gateway or record the deferral explicitly.
Severity counts#
| Severity | Count | Findings |
|---|---|---|
| P0-SEC | 1 | 1 |
| P0-HONESTY | 3 | 2, 3, 4 |
| P0-STRUCT | 1 | 5 |
| P1 | 6 | 6, 7, 8, 9, 10, 11 |
| P2 | 2 | 12, 13 |
| UX | 2 | 14, 15 |
| DEPLOY | 1 | 16 |
| Total | 16 |
D1 open question answered: DOMAIN_PREVIEW_SURFACES.nisaba must stay true
(finding 7 lists the exact wiring needed to lift it).