Disciplines · Audits

V1 Residual Audit — Veritas + Nyx (features.md 657–1055)

(DEPLOY: nothing new — once finding 1's facades land,

1section17 minread

On this page

Date: 2026-06-11 · Auditor slice 03 · Static read-only pass (no builds/tests run). Baseline: all 57 tasks of V1_SPEC_GROUND_TRUTH_AUDIT_2026-06-10.md landed; this report covers only what is residually broken/dishonest in current code.

Prior strengths confirmed and NOT re-litigated: the Nyx ephemeris tier is real (/v1/nyx/tonight, /v1/nyx/sky-almanac, /v1/nyx/events.ics, /v1/events/upcoming all compute from @oshun/domain-nyx, apps/oshun/bff/src/routes/domain-stubs.ts:1567–1670); the /veritas hub is honest (real briefing or honest-unavailable, apps/oshun/web/src/lib/lilith-data/veritas.ts:29–37); guardedFixtureRoute fails closed in production (domain-stubs.ts:840–856); the Veritas retraction-cascade engine is wired end-to-end on the write side (apps/oshun/bff/src/veritas/retraction-cascade-runtime.ts:48–113, started from server.ts:520–536). Adversarial grep over the BFF veritas/nyx route/adapter/store files returned zero stub-comment or Math.random hits.


1. The /v1/oshun/* facade does not exist for Veritas or Nyx — 35 BFF adapter operations are 502-by-construction#

Severity: P0-STRUCT

Evidence:

  • BFF expectations — apps/oshun/bff/src/adapters/domain-service-adapters.ts:1090–1305 (veritas) and :1306–1552 (nyx). Every method targets /v1/oshun/... on OSHUN_VERITAS_API_BASE_URL / OSHUN_NYX_API_BASE_URL (config/domain-service-discovery.ts:42–56).
  • Downstream reality — apps/veritas/api mounts only /api/v1/* (articles/claims/sources/stories/taxonomy/preferences/payments…, src/interfaces/http/routes/v1/index.ts; Postgres-backed, 503 fail-closed, e.g. articles.ts:95–98, 276–279). apps/nyx/api mounts only /api/v1/{objects,ephemeris,events,satellites} (src/app.ts:111–114). grep -rn "oshun" apps/{veritas,nyx}/api/src finds only @oshun/database imports — no /v1/oshun route exists in either service. Compare: apps/arete/api/src/routes/oshun-facade.ts and apps/tara/api/src/oshun-facade/ exist (C1).
  • Default adapters are the real HTTP ones (apps/oshun/bff/src/app.ts:1136–1149); every non-existent path → 404 → DomainServiceAdapterError → the route guarded() wrappers reply 502 (routes/veritas.ts:227–239, routes/nyx.ts:263–275).

Spec promise: features.md 659–684 (Veritas hub/briefing, save/follow, evidence inspection), 985–1007 + 1011–1032 (Nyx event model, save/follow/remind, highlights).

What the code actually does (blast radius, all 502/empty in any real deployment):

  • /v1/veritas/adapter/{availability,home-cards,continue-items,search,launch,saved-articles,trending-topics,bridge-contexts} (routes/veritas.ts:302–501) and /v1/veritas/briefing/home (:527–530) → the /veritas hub renders permanent honest-unavailable.
  • /v1/nyx/adapter/{availability,home-cards,continue-items,search,launch,nightly-highlights,object-search,saved-objects,event-reminders,observation-logs,bridge-moments} (routes/nyx.ts:314–655) and /v1/nyx/perspective/home (:782–818, 503 every time).
  • Assistant tool surface: 23 veritas/nyx ops (routes/assistant.ts:70–108); wearable (7), desktop (7); achievements — every Veritas and Nyx criterion metric degrades to 0 forever (routes/achievements.ts:163–199, honest-degrade but permanently zero); workspace evidence panel (apps/oshun/web/src/components/workspace/data/useWorkspaceLiveData.ts/v1/veritas/adapter/saved-articles) shows "unavailable".
  • Misleading availability: probeDomainHealth probes /readyz (veritas) / /health (nyx) (health/domain-service-health.ts:115–133) — both EXIST downstream and return ok, so /v1/{veritas,nyx}/adapter/availability reports the domain available (libs/oshun/domain-veritas/src/canonical-adapter.ts:75–77 maps availability from getHealth) while every data read 404s.

What a bridge router needs (per the C1 tara/arete pattern):

  • Veritas (17 endpoints): GET briefs/trending, GET articles/trending, GET articles/:id, GET articles/continue, GET articles/unread-count, GET articles/reading-stats, GET articles/engagement-stats, GET claims/top, GET claims/:id, GET topics/trending, GET topics/followed, POST+DELETE topics/:id/follow, GET categories, GET saved, POST+DELETE saved/:articleId. Catalog legs can map straight onto the existing Postgres routes (/api/v1/articles/trending, /api/v1/claims, /api/v1/categories, /api/v1/topics, /api/v1/sources/:id for credibility fields). Per-user legs (saved/followed/continue/unread/reading/engagement) have NO downstream store — needs a VeritasOshunStore (Drizzle/Postgres write-through like Tara's shadow-user bridge or Arete's redis facade store).
  • Nyx (18 endpoints): GET events/nightly, GET events/:id, GET events/continue, GET events/upcoming, GET objects/search, GET objects/:id, GET objects/visible, GET+POST observations, GET observations/stats, GET observations/search-activity, GET saved, POST+DELETE saved/:objectId, GET+POST reminders, POST reminders/:reminderId. Objects/ephemeris legs can use the real @nyx/messier + @nyx/ephemeris services already in apps/nyx/api. Events must NOT bridge to apps/nyx/api's events-service (see finding 11 — hardcoded 2024 samples); compute from @oshun/domain-nyx like /v1/nyx/tonight does. All per-user legs (observations/saved/reminders/continue/search-activity) have no downstream store — new facade store required.
  • Both must fail closed in production without {VERITAS,NYX}_OSHUN_FACADE_TOKEN matching x-oshun-service-token (BFF already sends OSHUN_DOMAIN_SERVICE_TOKEN, app.ts:1144–1148), plus contract round-trip tests booting the real routers through createDomainServiceAdapters (the C1 tests caught real drift day one).

Fix sketch: replicate the C1 bridge-router pattern: apps/veritas/api facade over the existing Postgres catalog + new per-user store; apps/nyx/api facade over Messier/ephemeris + @oshun/domain-nyx event solver + new per-user store; token fail-closed; contract tests.


Severity: P0-HONESTY

Evidence:

  • apps/oshun/web/src/lib/lilith-data/veritas-depth.ts:1–14 ("fixture today, BFF tomorrow") and e.g. :33–135 — invented bylines ("By M. Halvorsen"), invented sources ("Statnett · operator data", "NVE · regulator note"), invented confidence values (0.92), an entire fake Norway-grid investigation. All eight pages import it directly (src/app/veritas/{claim,counterclaims,mobile,provenance,retraction,source,story,topic}/page.tsx).
  • No label anywhere: grep -rn -i "preview|illustrative|example" src/app/veritas src/components/lilith/veritas.tsx → zero hits in 2,126 lines of broadsheet components; no layout.tsx under src/app/veritas/.
  • DomainPreviewBanner mounts only in DomainRouteExperience (src/components/DomainRouteExperience.tsx:579), which renders only for /domains/[domainId] — never for top-level /veritas/*.
  • Live nav into the fabrication: the honest /veritas hub's subnav links "Topic hubs" → /veritas/topic, "Sources" → /veritas/source, "Story updates" → /veritas/story (src/components/lilith/rooms.tsx:578–580), and the real lead's "Read sources" button defaults to /veritas/claim (src/components/lilith/VeritasArticleActions.tsx:28, rendered at rooms.tsx:681). A member reading a REAL grounded article clicks "Read sources" and lands on a FABRICATED claim page about a different, invented story — presented with full editorial authority.

Spec promise: features.md 659–666 (these surfaces are "presentational depth" of the Veritas domain), 678–681 (inspect-source interactions with real evidence bundles), 702–704 (trust display "never numerical scores presented without context" — these pages show invented confidence as if computed).

What the code actually does: the 06-10 headline "Veritas depth fabricates journalism" is fixed for /domains/* (D1 banner) and for the hub (honest briefing), but the eight top-level /veritas/* broadsheet pages — the exact pages that fabricate journalism — are outside the D1 register and reachable in two clicks from real content.

Fix sketch: either route these pages through the preview register (a /veritas/* layout mounting DomainPreviewBanner domainId="veritas"), or point the hub subnav + readSourcesHref at real surfaces (/domains/veritas?path=... claims workspace) until the depth pages are wired; minimally change readSourcesHref so a real article never links to invented sources.


3. D1 preview-banner coverage gap: the 30 static /domains/{veritas,nyx}/* tool pages never render the banner#

Severity: P0-STRUCT

Evidence: banner mounts once in DomainRouteExperience.tsx:579, used only by src/app/domains/[domainId]/page.tsx. The deep tools are separate static Next routes — 11 veritas (src/app/domains/veritas/{articles,bias,claims,fact-check,headlines,knowledge-graph,newsletter,nlp,research,story,topics}/page.tsx) + 19 nyx (src/app/domains/nyx/...) — grep -rln DomainPreviewBanner src/app/domains/zero hits; no shared layout.tsx exists under src/app/domains/{veritas,nyx}/. These pages render exactly the simulation-data components the registry exists for (VeritasArticleAnalyzer.tsx, NyxMoonTracker.tsx, NyxDashboardWidgets.tsx, … — 31 files import *-simulation-data). They are live-linked: NyxRoom subnav → /domains/nyx/satellites, /domains/nyx/events?filter=meteor, /domains/nyx/observation-log (rooms.tsx:815–817) and event rows → /domains/nyx/events/${id} (rooms.tsx:1030).

Spec promise: features.md 663–666 + 989–992 list these as the power-user surfaces; the D1 decision (audit doc line 507) promises "every /domains/* deep surface now carries an explicit Preview register" — the completion note's claim is false for the static subroutes, which is where the simulation data actually lives (DOMAIN_PREVIEW_SURFACES itself is correct; the mounting is what misses).

What the code actually does: /domains/veritas (shell) shows the banner; /domains/veritas/articles (the actual simulation UI) does not. Same for all nyx tools.

Fix sketch: add src/app/domains/veritas/layout.tsx and src/app/domains/nyx/layout.tsx (and tara/arete equivalents, out of slice) rendering <DomainPreviewBanner domainId=.../> above children — one file per domain, registry-driven exactly as designed.


4. GET /v1/nyx/observations is unauthenticated and the store is not user-scoped — every member's "private" field log is one global list#

Severity: P0-SEC

Evidence:

  • Route: apps/oshun/bff/src/routes/domain-stubs.ts:1628–1634app.get('/v1/nyx/observations', ...) with no preHandler at all returns domainStubsStore.observations.list(). The POST (:1600–1627) has only originGuard+csrfGuard — no auth, and never touches authContext.
  • Store: apps/oshun/bff/src/routes/domain-stubs-store.ts:119–162NyxObservationStore records {observationId, eventId, targetLabel, observedAtIso, conditions, note, ...} with no userId field; list() returns everything (Postgres or in-memory tier alike). Contrast: tara sittings/analytics in the same file read request.authContext?.userId and collectionsAuthGuard (domain-stubs.ts:1166–1181).
  • The page feeding it explicitly promises privacy: src/app/nyx/observation/page.tsx:44 renders right="private" and the docblock says "Nyx anchors it to your observation lifetime".

Spec promise: features.md 1030–1032 (save/remind is per-user) and the Nyx model generally treats observation logs as personal data (getObservationLogs({userId}) everywhere else, e.g. domain-service-adapters.ts:1381–1394).

What the code actually does: any session (or any direct BFF caller — no auth check) can read every user's observation notes; every write is anonymous and co-mingled. This is both a privacy leak and a third source of truth for observations (vs. the downstream /v1/oshun/observations per-user store of finding 1 and the web's localStorage useNyxStore.observations).

Fix sketch: require auth on GET+POST, add userId to StoredNyxObservation (+ Postgres column), scope list() per user; when the Nyx facade (finding 1) lands, fold this store into it so there is one observation log.


5. Nyx save/follow/remind is double-broken: the store users write is in-memory and never delivered; the delivery worker reads a store that doesn't exist#

Severity: P1

Evidence:

  • Write path: PUT /v1/nyx/event-actions/:eventIdnyxEventActionStateStore (routes/nyx.ts:683–756), which is a module-level Map (nyx/event-action-state-store.ts:83–95) — despite the NyxPersistedEventActionState naming, there is no durable-snapshot wiring in server.ts (grep nyxEventActionState server.ts → nothing) and no Postgres tier. Every saved event, followed event class, and reminder route (cadence/channels/reminderTime/offsetMinutesBeforePeak) evaporates on restart.
  • Delivery path: the reminder worker's Nyx producer (server.ts:650–653) calls app.domainAdapters.nyx.getEventReminders — the nonexistent downstream /v1/oshun/reminders (finding 1) — and produceNyxEventReminders swallows the throw per user ("fail-soft", reminders/nyx-reminder-bridge.ts:39–44). Net effect: no Nyx event reminder can ever be delivered, while the UI happily stores reminder config in the in-memory store the worker never reads.
  • Two sources of truth on the read side too: /v1/nyx/adapter/event-reminders (routes/nyx.ts:570–587) serves the downstream (502) store; /v1/nyx/event-actions serves the local one.

Spec promise: features.md 1030–1032 ("save event, follow event class, set reminder cadence … per-notification-channel routing") and 1027–1029 (calendar/reminder cadence per user preference).

What the code actually does: stores rich reminder config that nothing enforces; delivers from a store that 404s. Classic "stored config never enforced".

Fix sketch: wire nyxEventActionStateStore into the durable snapshot (like the C16 handoff store), and point produceNyxEventReminders at it (it has everything: reminderTime, routes, enabled) until the facade lands; when the facade lands, make the event-actions route write through to it.


6. The real retraction cascade has no customer-facing read path — /v1/veritas/{drift,retractions} are prod-503 fixtures and no surface shows cascade banners#

Severity: P1

Evidence:

  • Write side is real: POST /api/oshun/domains/veritas/sources/:id/retract persists via contract-persistence and publishes on Redis; the worker re-grounds Sophia answers and re-validates Metis lessons (veritas/retraction-cascade-runtime.ts:69–113).
  • Read side is a fixture: /v1/veritas/drift and /v1/veritas/retractions are guardedFixtureRoutes (domain-stubs.ts:1822–1827) — 503 in production — and they are the ONLY data source of the customer evidence page (src/app/veritas/evidence/page.tsx:66–70). So in production the page always renders "0 drift · 0 retracted" even after a real cascade fires.
  • No story-page/saved-claim/notebook banner exists: grep -rn "retraction" apps/oshun/web/src/components shows only the depth-fixture VeritasRetraction letter; nothing reads cascade records.

Spec promise: features.md 889–913 (retraction-cascade UX: story-page banner, per-saved-claim banner, notebook notice, severity gating, "appeal / correct us"), 732–736 (user-visible correction notes + story-update notifications).

What the code actually does: two sources of truth — real cascades in contract persistence, fabricated drift/retraction lists on the (dev-only) customer route; the spec's customer experience is unimplemented.

Fix sketch: replace the two fixture routes with reads over the cascade persistence (cascade id, trigger source, notice text, scope, timestamps — all persisted by createContractPersistenceRetractionPort), and surface a banner on /veritas briefing + saved items when a cascade touches them.


7. The Veritas editorial engines remain runtime-orphaned: source-quality composite, attestor lifecycle, counterclaim balance, topic-hub composer, editorial state machine, contradiction execution#

Severity: P1

Evidence: libs/oshun/domain-veritas/src/{source-quality/{composite,banding,domain-weights,rescoring,tenant-floor,vector-store},attestor/{credential-verification,conflict,probation,revocation,expiry,disagreement,audit},counterclaim/{surface,reader-preferences,audit},topic-hub/{composer,editorial-gating,refresh,locale-variant},editorial/state-machine,contradiction/{probe,execution,reviewer-routing,tenant-timebox,monitoring}} — full implementations with domain tests. Runtime consumers of @oshun/domain-veritas in the BFF: only domain-service-adapters.ts:51 (types), retraction-cascade-runtime.ts:18, cross-domain-recommendations.ts:14. grep -rln "computeSourceQualityComposite|bandFromComposite|EditorialStateMachine|composeTopicHub" apps libs (excluding the lib itself and tests) → only libs/sophia/verification/src/contradiction/contradiction-loop.ts (the Sophia half of the loop — itself unconsumed by any app route). No route, worker, or store operates source scoring, attestation, counterclaim surfacing, topic-hub gating, or the state machine. The credibilityTier/credibilityScore shown on the home briefing comes from the (nonexistent) downstream feed (routes/veritas.ts:588–613), not from the composite.

Spec promise: features.md 749–961 — the entire "Editorial Workflow, Source Verification, and Contradiction Detection" section, whose opening line says the schemas "are inert without the editorial loop that operates them" (751–753).

What the code actually does: the loop is still inert. This is the recorded systemic failure mode (audit doc lines 49–58); for Veritas, only the cascade slice got wired since.

Fix sketch: when the Veritas facade store lands (finding 1), compute credibilityTier from computeSourceQualityComposite+bandFromComposite over real source records instead of passing through a downstream field; add an admin lane route pair (catalog + verb) driving the editorial state machine + attestation records, following the studio lane-console pattern.


8. USE_MOCK_ADAPTERS=true serves fabricated journalism and sky data as real, with no production guard#

Severity: P1

Evidence: apps/oshun/bff/src/app.ts:1136–1139if (process.env.USE_MOCK_ADAPTERS === 'true') return createMockDomainServiceAdapters(); — no environment check. The mocks fabricate news ("AI breakthroughs in Q3", "New CRISPR therapy shows 94% efficacy…", adapters/mock-domain-service-adapters.ts:208–296) and Nyx events/logs, all flowing through the same routes that label themselves real. Contrast the repo's own posture: guardedFixtureRoute 503s in production (domain-stubs.ts:847–856).

Spec promise: features.md 686–707 (trust/confidence display is the product; fabricated verdicts under real chrome is the worst-case failure).

What the code actually does: one env var in a production deployment turns every Veritas/Nyx/tara/arete surface into convincing fabrications with no banner.

Fix sketch: in createDefaultDomainAdapters, refuse mocks when resolveOshunRuntimeEnvironment() is production/staging (throw at boot), mirroring the fixture guard.


9. Dead hydration path: hydrateDomainStoresFromBff calls four /v1/domains/*/state endpoints that don't exist, and nothing calls it#

Severity: P2

Evidence: apps/oshun/web/src/lib/stores/domain-stores.ts:606–636 fetches /v1/domains/{tara,arete,veritas,nyx}/state; no BFF route matches (grep -rn "domains/veritas/state" apps/oshun/bff/src → nothing; only /v1/domains/:domainId/bootstrap exists in routes/entitlements.ts:47). The function itself has zero callers (grep -rn hydrateDomainStoresFromBff src → definition only). The zustand stores are honest-empty localStorage, so no fabrication — but lastSyncedAt/syncFromBff imply a sync that can never happen.

Spec promise: features.md 677 / 1030 (per-user save/follow state — implies server truth, not per-browser).

What the code actually does: dead code advertising a BFF sync; useVeritasStore.savedClaims/followedTopics and useNyxStore.observations live only in the browser.

Fix sketch: delete hydrateDomainStoresFromBff (or re-point it at the E6 /v1/client-state/:stateKey channel with new slots), and route store mutations through the facade once finding 1 lands.


10. Veritas save/follow is three disconnected stores, and the web has no server mutation path at all#

Severity: P2

Evidence: (a) "Save to notebook" on the real lead writes localStorage webLibraryStore (VeritasArticleActions.tsx:37–55); (b) the workspace evidence panel reads server-side saved articles via /v1/veritas/adapter/saved-articles (useWorkspaceLiveData.ts) — a different store (downstream, currently 502); (c) useVeritasStore.savedClaims/followedTopics is a third (localStorage zustand). The BFF exposes no POST route for saveArticle/unsaveArticle/followTopic/unfollowTopicroutes/veritas.ts is GET-only; the adapter mutations (domain-service-adapters.ts:1198–1244) are reachable only through assistant tooling (routes/assistant.ts:82–85).

Spec promise: features.md 677 ("Save/follow controls for stories, claims, and topics"), 734–736 (story-update notifications "per-followed-topic" — impossible when follows never reach the server).

What the code actually does: a member's saves never appear in the workspace evidence panel or count toward veritas_articles_saved achievements; follows can never drive notifications.

Fix sketch: add POST/DELETE /v1/veritas/adapter/saved-articles/:articleId + topics/:topicId/follow BFF routes over the adapter mutations; make VeritasArticleActions write through (localStorage as offline cache), contingent on finding 1.


11. apps/nyx/api events + satellites services are hardcoded 2024 sample data — empty/garbage in 2026#

Severity: P2

Evidence: apps/nyx/api/src/services/events-service.ts:11–13// Sample Events Data — a fixed ASTRONOMICAL_EVENTS array of 2024 dates; getUpcomingEvents filters eventStart >= now (:262–276), so every query from 2026-06-11 returns [] forever (honest-empty, but the endpoint can never serve a real event). satellites-service.ts:11–25 — sample satellites with TLE epoch 24015.5 (Jan 2024); SGP4 propagation from a 2.5-year-stale ISS TLE produces meaningless pass predictions through the otherwise-real @nyx/realtime-satellites tracker. By contrast ephemeris-service.ts (real @nyx/ephemeris Meeus engine) and objects-service.ts (real @nyx/messier catalog) are genuine.

Spec promise: features.md 1024–1026 — prediction sources "each with version, license, attribution, freshness"; 1011–1016 event taxonomy.

What the code actually does: the only currently-running Nyx event source that is real is the BFF's own @oshun/domain-nyx solver. Any facade (finding 1) bridged onto apps/nyx/api's events-service would serve nothing.

Fix sketch: back /api/v1/events with the @oshun/domain-nyx lunar-phase/season solver + the source-backed featured meteor/eclipse table the BFF already uses; for satellites either ingest a live TLE feed (DEPLOY-credentialed) or fail closed with not_configured instead of stale-epoch predictions.


12. /v1/veritas/briefing/home and /v1/nyx/adapter/* error envelopes are inconsistent: the briefing handler 500s instead of its own 502/503 contract#

Severity: P2

Evidence: routes/veritas.ts:527–530Promise.all([getTrendingArticles, getTopClaims]) with no try/catch (unlike every /adapter/* route's guarded()); a downstream DomainServiceAdapterError propagates to Fastify's generic 500. The web treats any non-OK as unavailable so no fabrication results, but ops dashboards see 500s for a routine downstream outage and the route's own 503 leg (:533–539) is unreachable for the throw case.

Spec promise: n/a (robustness); features.md 668–675 implies a graceful briefing surface.

Fix sketch: wrap the two adapter calls in the same try → 502 domain_unavailable envelope used at routes/veritas.ts:227–239.


13. NyxRoom navigation sends members from the real sky into unlabeled simulations, with one mislabeled target#

Severity: UX

Evidence: rooms.tsx:815–817 — the real ephemeris hub's subnav: "Planets" → /domains/nyx/satellites (a satellite tracker, not planets — NyxSatelliteTracker.tsx, simulation-backed); "Meteors" → /domains/nyx/events?filter=meteor (simulation events); "Observations" → /domains/nyx/observation-log (simulation log) even though the member's REAL logging flow lives at /nyx/observation (and their real entries at GET /v1/nyx/observations). Combined with finding 3 (no banner on those pages), the register seam is invisible: real tonight-card → one click → fabricated observation history.

Spec promise: features.md 985–1007 (the hub and deep tools are one coherent domain surface).

Fix sketch: point "Observations" at /nyx/observation; rename "Planets" or point it at the solar-system/ephemeris view; banner-cover the rest (finding 3).


Severity counts#

Severity Count
P0-SEC 1
P0-HONESTY 1
P0-STRUCT 2
P1 4
P2 4
UX 1
DEPLOY 0
Total 13

(DEPLOY: nothing new — once finding 1's facades land, OSHUN_{VERITAS,NYX}_API_BASE_URL + OSHUN_DOMAIN_SERVICE_TOKEN + facade tokens are the existing deploy-tier requirement; live TLE feed in finding 11 would add one credential.)