Disciplines · Audits

Living Scenes — Residual Audit (2026-06-11)

Realistic V1 polish vs V1.x: wiring the existing keep/share loop to the card (5), the header→store tenant-policy fix (6), score-derived rendering (3, the compositor already takes tone/dimensions/frames), server-side redaction (8),

2sections23 minread

On this page

Scope: V1/features.md lines 3609–4523. Evidence: current code only (read 2026-06-11, branch feat/hestia-epicure-embeddings). The 2026-06-10 ground-truth audit's 57 tasks (B4 honest materialization, D4 real APNG materialization + signed bytes + public media route) are confirmed landed and are NOT re-reported; this is the residual.

Pillar verdict (what exists / partial / absent)#

Spec pillar Verdict
Keep / share / revoke / tombstone / delete loop EXISTS (BFF, durable snapshot, tested) — but no product surface calls keep/share (finding 5)
Public viewer + password/unlock + embed + oEmbed + abuse report + transcripts EXISTS (real, wired end-to-end, operator triage inbox real)
Takedown cascade (7 dispositions, SLA, banners) EXISTS (route + lib executor + persisted overlay) — operator API only, no console UI; nightly SLA sweep orphaned
C2PA signing of real media bytes EXISTS (Ed25519 over the APNG actually served; prod fail-closed without key — DEPLOY)
Materialization (D4 APNG) PARTIAL — playable + signed, but score-independent (finding 3), non-durable, no re-render (finding 10)
Scene Score schema + contracts PARTIAL — runtime Score is far thinner than the spec segment schema (no style anchor / narration pin / grounding pin / persona / transitions per segment); contracts mirror exists
Conductor runtime ABSENT at runtime — pure-logic state machine exists in-lib, zero consumers; no session, no frame stream, no GPU dispatch, no segment adapters implemented (finding 4)
Live Direction channel PARTIAL→ABSENT — parse + verb policy real and wired (client-side), but cues apply to nothing; reconciliation + crisis classifier orphaned (findings 4, 21)
Blend kernel + technique catalog + compatibility scorer + per-technique evals ORPHANED@yemaya/blend-kernel (1,446 lines incl. the full 12-technique catalog) has zero app consumers (finding 19)
Customer composition surface + AI Compose Assist FIXTURE THEATER — client-only fixtures, fabricated assist suggestion + continuity pre-score, nothing persisted (finding 2)
Domain templates (5) PARTIAL — metadata + verb sets + tier gating exist; no template produces a scene; verb sets diverge from spec (finding 17)
AAA Scene Score Editor PARTIAL (demo) — rich UI over one hardcoded fixture score; no persistence; ignores its own API (finding 20)
Safety: PSE/seizure gate EXISTS on the render path (WCAG luminance-swing gate enforced on real frames)
Safety: crisis frame, cue pre-screen, tone bands PARTIAL→ABSENT — pre-share check is a 6-phrase English substring list; crisis state is a client header (findings 7, 16)
Determinism / render envelope / re-render-from-score / engine-upgrade re-bake ORPHANED — checkDeterminism, assertNodeParity, checkUpgradeAvailability, planRebake, rollback: zero consumers; no re-render endpoint (findings 3, 10, 23)
Provenance 4-channel redundancy PARTIAL — C2PA real; visible mark NOT in the media bytes; no audio (honestly n/a); attestation page real; channel-robustness eval orphaned (finding 12)
Shareability matrix TAUTOLOGY on live path — grants fabricated to equal the requested reach (finding 7)
Cue privacy / intent redaction UNENFORCED — redaction engine orphaned; "redacted" layer is client-supplied (finding 8)
Tenant entitlements + share policy CLIENT-SUPPLIED — policy parsed from request headers; admin store never consulted (finding 6)
Reduced-motion + captions + AD + transcript PARTIAL — transcripts real; captions/AD/stills are dead oshun:// metadata; viewer keeps animating under reduced-motion (findings 14, 15)
Latency/quality/backpressure budgets, dashboards ABSENT — no measurement anywhere; conductor backpressure logic orphaned (finding 24)
Eval sets / release gates / rollback ORPHANED ENGINES — no fixture corpora, no callers (finding 23)

Realistic V1 polish vs V1.x: wiring the existing keep/share loop to the card (5), the header→store tenant-policy fix (6), score-derived rendering (3, the compositor already takes tone/dimensions/frames), server-side redaction (8), first-frame route (9), media durability/re-render (10), honest download (11), embedded visible mark (12), and reduced-motion honesty (14) are all V1-shaped. Conductor/streaming, blend kernel execution, real Compose Assist (AgentRun), GPU segment generation, eval corpora, latency SLOs are V1.x.


1. Customer Living Scene card fakes Keep / Share / generation success#

Severity: P0-HONESTY Evidence: apps/oshun/web/src/components/studio/generation/LivingSceneCard.tsx:143-190 (startScene → notice "Scene queued with gallery delivery and provenance attached"; keepCurrentOffering → "Living Offering kept for this account with lifetime retention"; shareCurrentOffering → "Share queued … after Lilith re-check"); apps/oshun/web/src/app/studio/generation/living-scene/page.tsx:44 renders the card with NO onKeep/onShare/onSendToGallery callbacks and the component makes zero network calls. Spec promise: features.md 3634-3637 (keep, return, share), 4228-4235 (Keep default action, Share explicit second action). What the code actually does: button handlers set a notice string claiming a queued generation, a kept lifetime artifact, and a Lilith-checked share — while no session, no artifact, and no share exist. The real BFF keep/share routes are never called from any product surface. Fix sketch: wire the card's Keep/Share to POST /v1/living-scenes/artifacts/keep and /artifacts/:id/share (the e2e support file apps/oshun/web/e2e/support/living-scene.ts already shows the exact payloads); until then the notices must say "not available".

2. /studio/compose is fixture theater: fabricated Compose Assist + fabricated continuity score#

Severity: P0-HONESTY Evidence: apps/oshun/web/src/app/studio/compose/ComposeClient.tsx:31-93 (hardcoded segment library kept-lotus-001, nyx-anchor-aurora, metis-step-004; continuityPreScore: 0.92 hardcoded), :428-439 (assist panel shows the constant string "Suggested Segment: Metis lesson-step bridge · confidence 0.74-0.88"; acceptSuggestion inserts a hardcoded fixture segment), :449-450 (the fabricated 0.92 displayed as "Continuity pre-score" under "Promotion gates"). No fetch in the file. Same pattern on mobile (apps/oshun/mobile/app/compose.tsx). Spec promise: 3777-3790 (sources = user's kept scenes + template libraries + tenant imports), 3916-3943 (assist runs as Isis AgentRun with budgets, audit, gold-set), 3881-3914 (compatibility scorer produces the scores). What the code actually does: the only consumed parts of the runtime lib (validateCompositionDraft/promoteCompositionDraft/filterComposeAssistTechniqueSuggestions) run client-side over fixtures; the "AI suggestion", its confidence band, and the continuity pre-score are constants; "promotion" produces a local object that is never persisted, kept, or rendered. The user cannot tell any of this is canned. Fix sketch: load segments from GET /v1/living-scenes/artifacts; compute the pre-score with @yemaya/blend-kernel/compatibility/scorer (orphaned, ready); either wire a real assist AgentRun or remove the assist panel; label clearly until then.

3. Materialized share media is score-independent — every scene renders the identical gradient#

Severity: P0-STRUCT Evidence: apps/oshun/bff/src/routes/living-scenes.ts:671-689renderLivingSceneSegmentToPng({ tone: 'contemplative', width: 480, height: 270, frameCount: 24 }) takes nothing from artifact.score (no segments, tones, durations, seeds, narration, template) and hardcodes 'contemplative' for all five templates; libs/yemaya/rendering-pipelines/src/raster-kernel/living-scene-render.ts:118-148 is fully deterministic in those four inputs ⇒ byte-identical media for every artifact on the platform. Spec promise: 4183-4187 ("Score storage is the source of truth … re-render on demand reproduces the same artifact"), 3650-3660 (Render Envelope = reproducibility inputs: model hashes, seeds, per-segment seeds), 4032-4114 (per-template visual behavior). What the code actually does: the kept Score and Render Envelope (incl. per-segment seeds the keep route carefully Map-normalizes and persists) are dead weight at render time; a Nyx sky briefing, a Metis lesson, and a Tara arc all materialize to the same blue gradient. C2PA then signs media that has no derivation from the score it attests. Fix sketch: derive render inputs from the score — per-segment tone (the renderer already supports all 6 tones), per-segment frame counts from durationSeconds, phase offsets from perSegmentSeedsHex — and concatenate per-segment frame runs into the APNG. Pure plumbing; no new engine needed.

4. Conductor runtime + live direction execution are absent: a shelf of orphaned engines#

Severity: P0-STRUCT Evidence: zero non-test consumers (workspace-wide grep) for initialiseConductor/planPreWarm/advancePlayhead/setBackpressure/planReconnect (libs/yemaya/living-scenes-runtime/src/conductor/conductor.ts), reconcileCues (cues/reconciliation.ts), decideCrisisAction/checkLocaleParity (cues/crisis-classifier.ts); segment-adapter.ts defines SegmentAdapter ('hunyuan'|'wan'|'cogvideo'|'custom-on-comfy') with no concrete adapter implementation anywhere in the repo; the cue acceptance verdict claims application — customer-card.ts:407 returns notice "${verb} cue applied at the next scene boundary" with no scene, queue, or boundary in existence. Spec promise: 3704-3730 (Conductor streams a Score, pre-warm ≥2, carry-state, backpressure, reconnect, provider failover), 3665-3702 (cue queue, reconciliation precedence, sensitive-intention classifier on every voice/text cue, latency budgets). What the code actually does: cue handling ends at parse + verb-allowlist policy; the crisis classifier is never invoked on any live cue path (BFF /card/cuesevaluateLivingSceneCueparseCue+assertVerbAllowed only); accepted cues are discarded after the response. The conductor is well-tested pure logic with no caller. Fix sketch: V1.x for true streaming. For V1 honesty: change the accepted-cue notice to "cue accepted" (not "applied"), and run decideCrisisAction on every voice/text cue in evaluateLivingSceneCue (it's sitting right next to the parser).

5. The excellent keep/share/takedown loop has no product caller — API-only feature#

Severity: P1 Evidence: workspace grep — the only callers of POST /v1/living-scenes/artifacts/keep, POST /v1/living-scenes/artifacts/:id/share, POST /v1/living-scenes/shares/:id/revoke, GET /v1/living-scenes/artifacts are e2e fixtures (apps/oshun/web/e2e/support/living-scene.ts:114,128). No web/mobile surface lists kept offerings, creates shares, or revokes them. The operator takedown route (/v1/living-scenes/admin/artifacts/:id/takedown) likewise has no console UI. Spec promise: 4228-4252 (user keeps, returns to, shares, revokes from product surfaces). What the code actually does: a user has no way to keep a scene, see their kept offerings, mint a share link, or revoke one. Every public share in existence was created by a test. Fix sketch: a "My offerings" list view (GET artifacts → share/revoke buttons) closes the loop with routes that already exist and are tested; add a takedown form to the tenant console.

6. Tenant share policy is client-supplied request headers; the admin store is never enforced#

Severity: P0-SEC Evidence: apps/oshun/bff/src/routes/living-scenes.ts:1624-1651 (parseTenantSharePolicy reads x-oshun-living-scenes-share-allowed, -public-share-allowed, -policy-state etc. from the request), :1300-1316 (card tenant policy from headers), :1565-1577 (retention bounds from headers), :644-645 (mass-share window/threshold from x-oshun-share-window-seconds/x-oshun-share-burst-max — the sharer can lift their own spam gate), :707-708 (crisis label from x-oshun-living-scene-crisis-label), :1543-1549 (x-oshun-now time override). Meanwhile apps/oshun/bff/src/tenant-console/tenant-living-scenes-store.ts holds the "BFF source of truth" policy record (shareAllowed/shareScope/retention/branding) whose only consumer is the read-only admin console route (routes/admin-tenant-console.ts:57). Spec promise: 4358-4374 ("fail-closed enforcement: the share flow consults tenant policy at every step; ambiguous policy resolves to deny"), FERPA-class per-tenant share denial. What the code actually does: an authenticated member of a share-banned tenant simply omits the headers (defaults: shareAllowed=true, publicSharingAllowed=true) and shares publicly; the fail-closed branches only fire if the client volunteers the denying header. Two sources of truth: the stored policy the operator edits is decorative. Fix sketch: resolve policy server-side from tenantLivingScenesStore keyed by authContext.tenantId (headers at most as test overrides in non-prod); same for the mass-share thresholds and x-oshun-now.

7. Shareability matrix is a self-fulfilling tautology; crisis state rides a header#

Severity: P1 Evidence: apps/oshun/bff/src/routes/living-scenes.ts:706 passes defaultShareabilityGrants(reachForPrivacy(effectivePrivacyLevel)); libs/yemaya/living-scenes-runtime/src/personal-artifacts/personal-artifacts.ts:934-946 fabricates all nine component grants at exactly the requested reach with invented rationales ("music bed licensed for requested reach", "voice consent grants requested reach") — no consent record, rights metadata, or license is ever consulted. Crisis gating depends entirely on the optional x-oshun-living-scene-crisis-label header. Spec promise: 4299-4321 (per-component grants from the component's own consent record; resolution = min across components; crisis-touched ⇒ private-only regardless). What the code actually does: resolveShareability (real machinery, real crisis private-only rule) is fed inputs constructed so it can never restrict anything on the live path. The matrix "passes" for every share by construction. Fix sketch: derive grants from real sources where they exist (tenant policy → the tenant-policy component; score.intent presence → intent; Lilith check → lilith-policy; crisis from a server-side record not a header) and mark the rest honestly 'public'-by-absence with rationale "no restricting record".

8. Intent redaction is unenforced — the "public-redacted" layer is whatever the client sent#

Severity: P1 Evidence: redactIntent/buildIntentLayers (libs/yemaya/living-scenes-runtime/src/cue-privacy/intent-redaction.ts:87,133) have zero non-test consumers; the keep route accepts score.intent.publicRedactedText verbatim (routes/living-scenes.ts:1375-1390); score-schema.ts:286-323 validates only shape and that privateIntentHash is 64 hex chars — it never recomputes the hash over privateText nor checks the public layer was actually redacted. The public viewer then publishes publicRedactedText as the OG description (personal-artifacts.ts:1104-1110). Spec promise: 4279-4290 (a Lilith-governed redaction classifier writes the public layer; default-deny on share-with-private). What the code actually does: a buggy or malicious client can place the private intent (named persons, disclosures) into publicRedactedText and it goes straight to the public page and OG preview. The 204-line redaction engine with the category taxonomy sits unused. Fix sketch: in the keep route, recompute privateIntentHash (stableHash already there) and run redactIntent(privateText) server-side, overwriting the client's public layer.

9. OG/Twitter preview image and viewer stills point at a route that does not exist#

Severity: P1 Evidence: routes/living-scenes.ts:1007-1009,1121-1123 builds firstFrameUrl = ${origin}/api/living-scenes/<shortCode>/first-frame; repo-wide search: no first-frame handler in apps/oshun/web/src/app/api/** and no such BFF route. The viewer uses it for the OG image, Twitter card, motion-layer background, and the reduced-motion still (scene/[id]/page.tsx:43,49,239,251). Spec promise: 4259-4261 (OG/Twitter previews generated from the Score's first frame). What the code actually does: every share's social preview image and the viewer's background/reduced-motion still are 404s. Fix sketch: add GET /v1/living-scenes/public/:shortCode/first-frame that returns the first frame PNG of the stored APNG (one chunk-slice away), and point firstFrameUrl at it.

10. Share media is RAM-only and the spec's materialization lifecycle is orphaned#

Severity: P1 Evidence: routes/living-scenes.ts:174 shareMediaById = new Map<string, Buffer>() is excluded from captureLivingSceneShareSnapshot() (:228-237) — after a BFF restart the media route 404s media_not_materialized (:1084-1091) for every pre-restart share while the viewer still renders the share's C2PA manifest/media hash as playable. No re-render path exists despite the renderer being deterministic. The whole B4 lifecycle — newMaterialization/completeMaterialization/shouldEvict/evict/resolveShareRequest (envelope/materialize-on-share.ts) with its 30-day post-share TTL — has zero consumers; share.materialization is created once at state: 'pending' and never completed even though real bytes now exist at exactly that moment. Spec promise: 4184-4187 (MP4 cached 30 days post-share then evicted; re-render on demand reproduces bit-equivalently). What the code actually does: media lives forever in process memory, dies on restart, and the share's own materialization.state stays 'pending' (with the materialization-pending advisory note) even while the real APNG is being served. Fix sketch: persist media (or re-render on miss — it's deterministic given finding 3's fix) and call completeMaterialization with the APNG evidence at share-create time.

11. Download grant promises an MP4 that doesn't exist, with no download endpoint#

Severity: P1 Evidence: personal-artifacts.ts:640-668createDownloadGrant returns format: 'mp4' (hardcoded) for media that is APNG, plus c2paManifestId and visibleMarkShortCode implying intact embedded provenance; no route exchanges grantId for bytes. The viewer's "Download grant" button (scene/[id]/page.tsx:363-367) form-POSTs to the grants route and the browser lands on raw JSON. Spec promise: 4250-4252 (download grant produces an MP4 download with C2PA + visible mark

  • audio watermark intact). What the code actually does: mints grant metadata for a nonexistent file format with no redemption path — a result-shaped record with no result behind it. Fix sketch: format: 'apng', add GET .../download?grant=<id> serving the stored bytes with content-disposition: attachment (validating grant expiry), and handle the response client-side.

12. Visible provenance mark is never embedded in the media bytes#

Severity: P1 Evidence: the renderer supports burn-in (living-scene-render.ts:159,193-195 watermark?: { mark, placement }embedWatermarkMark), but neither the share path (routes/living-scenes.ts:672-677) nor the standalone render route (living-scenes/render-route.ts:62) passes one — despite server.ts:601-603 logging "watermarked … PNG frames" and lib comments claiming "the deterministic PNG render still carries a visible watermark" (personal-artifacts.ts:497-500). The shortCode badge is a DOM overlay only (scene/[id]/page.tsx:273-275). The channel-robustness eval (provenance/channel-robustness/channel-eval.ts evaluateAllChannels) has zero consumers. Spec promise: 4207-4210 (visible mark in a corner of the media, survives crops and recompression — one of the four redundant channels), 4219-4224 (per-channel survival measured, below-100% blocks release). What the code actually does: saving/re-sharing the APNG strips every embedded provenance channel (no visible mark in pixels, no audio to watermark); only the C2PA sidecar in the API response and the attestation page survive — and the survival eval that would have caught this never runs. Fix sketch: render the visibleMark.shortCode glyph via embedWatermarkMark into the frames before APNG assembly (re-render after createLivingSceneShare returns the mark, or precompute the mark from the share seed which is available before render).

13. Unauthenticated, un-rate-limited CPU-bound render endpoint#

Severity: P1 Evidence: living-scenes/render-route.ts:42POST /v1/living-scenes/render has no preHandler (contrast every other living-scenes route: authProtection or publicAbuseProtection); registered live in server.ts:603. Accepts up to 1920×1920×120 frames per request: ~440M pixels through per-pixel gradient + gaussian blur

  • PNG encode + base64 into a single JSON response. No global rate-limit plugin found in server.ts/app.ts. No product consumer calls this route (dead + exposed). Spec promise: none for an anonymous bulk-render API; 4268-4270 requires rate limiting on public surfaces. What the code actually does: lets any anonymous caller pin a CPU core for seconds and extract multi-hundred-MB responses in a loop. Fix sketch: add publicAbuseProtection (and/or auth), cap dimensions to the 480×270 profile actually used, or remove the route until something consumes it.

14. Reduced-motion is claimed but the animation keeps playing#

Severity: P1 Evidence: scene/[id]/page.tsx:196-200prefers-reduced-motion CSS hides [data-living-scene-motion-layer] (a static background div!) and shows the carousel div, but the animated APNG <img> (:263-272) sits in a third, always-visible container and keeps animating; the panel hardcodes data-honors-reduced-motion from viewerChrome.honorsReducedMotionPreference: true (personal-artifacts.ts:1056-1057, literally hardcoded true). Shares with rendition: 'reduced-motion' receive the identical animated media (routes/living-scenes.ts:672-689 ignores the rendition). Spec promise: 4430-4436 (reduced-motion render = still-image carousel, independent shareable artifact), 4449-4450 (accessibility release gate). What the code actually does: vestibular-safety users get the full animation plus a label saying reduced-motion is honored. Fix sketch: wrap the APNG <img> in the motion layer (so the CSS hides it) and show the first frame still in the reduced-motion branch; serve a 1-frame APNG for rendition: 'reduced-motion' shares.

15. Captions / audio descriptions / reduced-motion stills are dead oshun:// metadata#

Severity: P2 Evidence: personal-artifacts.ts:1010,1025,1036,1050stillFrameUrl, caption sidecarUrl, AD sidecarUrl, transcript downloadUrl all use the unresolvable oshun://living-scenes/... scheme; burnInAvailable: true hardcoded with no burn-in pipeline; no caption/AD media exists (the render is silent and visual-only). The viewer displays "Captions: en-US vtt · burn-in available" (scene/[id]/page.tsx:323-326). (Transcripts are the honest exception: real text served by the BFF transcript route.) Spec promise: 4437-4443 (sidecar caption track, per-segment AD, both ship in lockstep). What the code actually does: claims caption/AD coverage in user-visible UI while the URLs cannot resolve anywhere — the same oshun:// fabrication pattern B4 removed from materialization survives in the accessibility envelope. Fix sketch: generate a real VTT from the transcript lines (text exists), serve it beside the transcript route; report AD as unavailable until an AD track exists.

16. "Lilith pre-share re-check" is a six-phrase English substring blocklist#

Severity: P2 Evidence: personal-artifacts.ts:1088-1098containsBlockedSafetyFrame checks ['suicide','self-harm','kill myself','medical diagnosis','abuse disclosure','location of safety'] against segment display-names/parameters and cue args; it never inspects the rendered media, narration, or non-English text; no integration with the actual Lilith policy substrate or policy versioning beyond echoing a header string (routes/living-scenes.ts:709-710). Spec promise: 4271-4274 (fresh Lilith re-check on the materialized artifact at every outbound share), 3696-3699 (locale parity). What the code actually does: a keyword screen on metadata text, labeled as the Lilith re-check in the share record (lilithPreShareCheck). Fix sketch: route the score text through the BFF's real Lilith moderation seam (svc-moderation exists) and record its verdict + true policy version; keep the keyword screen as a fast pre-filter only.

17. Per-template cue verb sets diverge from the spec#

Severity: P2 Evidence: customer-card.ts:109-147 — Veritas omits shape-by (spec 4068-4070 grants shape-by(narrator-pace)); Metis omits morph and shift-style (spec 4092-4095 grants both — "the most expressive of the five"); Nyx omits recall (spec 4058 grants it) while gaining morph/shift-style the spec never lists for Nyx. Spec promise: 4058-4059, 4068-4070, 4092-4095. What the code actually does: enforces a different verb policy per template than the one specified — users of Metis lose exactly the pedagogical verbs the spec calls essential. Fix sketch: align TEMPLATE_ALLOWED_VERBS to the spec lists (3-line constant edit) and add a test asserting each template's set against the spec.

18. Customer card cost/latency "estimate" is string-length numerology#

Severity: P2 Evidence: customer-card.ts:304-309domainOffset = template.domainId.length % 3; costUsd = 0.42 + domainOffset * 0.08 + proClass; latencySeconds = 42 + domainOffset * 11 …. Rendered to users via estimateLivingSceneTemplate on the card. Spec promise: none specifically — but presenting a computed-looking costUsd derived from the length of the word "veritas" is a fabricated result on a live surface. What the code actually does: invents stable but meaningless cost/latency numbers. Fix sketch: replace with honest static per-template figures (or drop the fields) until a real cost model exists.

19. Technique catalog exists three times; the real one (blend-kernel) is fully orphaned#

Severity: P2 Evidence: libs/yemaya/blend-kernel/src/catalog/cinematographic-catalog.ts (422 lines, full CinematographicTechnique entries with kernel compositions, eval thresholds, allowlists) + catalog/tone-gating.ts + compatibility/scorer.ts + continuity-evals/per-technique.ts: zero consumers in any app (repo-wide grep for @yemaya/blend-kernel over apps/ returns nothing). The compose surface instead uses a second, independent allowlist embedded in living-scenes-runtime/src/compose-assist/compose-assist.ts:208-236 (TEMPLATE_TECHNIQUE_ALLOWLISTS), and libs/contracts/src/living-scene/technique.ts is a third (schema) copy. Spec promise: 3811-3871 (one versioned, contract-backed catalog feeding picker, assist, and promotion gates). What the code actually does: the catalog that satisfies the spec is dead code; the copy that runs is a reduced allowlist that can silently drift from it. Fix sketch: make compose-assist consume the blend-kernel catalog (delete the embedded allowlists), and validate the catalog against the contracts schema in a test.

20. AAA Scene Score Editor is a single-fixture demo that ignores its own API#

Severity: P2 Evidence: apps/yemaya/studio-web/src/score-editor/ScoreEditorPage.tsx (no fetch; all state from SCENE_SCORE_FIXTURE), core.ts:153-300 — one hardcoded score ("Evening Sea Walk") with hand-written transition eval numbers (fvd 48/61, flicker 0.06…) and carry-state scores; promotion (createPromotedVersion) mutates local state only. The entitlement-gated /v1/score-editor API (apps/yemaya/api/src/routes/score-editor.ts) serves only a static session descriptor and is not called by the editor. Spec promise: 4116-4146 (score versioning on every save, promotion through Isis environments, fixture-rehearsal against the workflow class's fixture set, replay of previously rendered sessions). What the code actually does: the timeline/inspector/scorecard/replay UI affordances exist and the gate logic (isTransitionGatePassed, canPromoteScore) is real, but there is exactly one score in the world, its eval numbers are authored by hand, and nothing persists. Fix sketch: V1: load/save scores via the yemaya API (a real store behind /v1/score-editor); keep the fixture as the seeded example. Real eval scoring is V1.x.

21. Dead BFF card/cue routes + duplicated entitlement logic client-side#

Severity: P2 Evidence: GET /v1/living-scenes/card and POST /v1/living-scenes/card/cues (routes/living-scenes.ts:492-540) have no callers anywhere (web/mobile/tests-of-product); the web card imports resolveLivingSceneCard/evaluateLivingSceneCue directly from the lib and evaluates tier/tenant/cue policy in the browser (LivingSceneCard.tsx:73,131-138), fed by headers parsed in the page (living-scene/page.tsx:55-70). Spec promise: 4028-4030 (card resolves templates from tier and tenant entitlements — an enforcement decision). What the code actually does: two parallel implementations of the same gate; the enforced one (server) is unreachable, the reachable one (client) is advisory by nature. Drift between them would be invisible. Fix sketch: have the card fetch /v1/living-scenes/card + post cues to /card/cues (routes already tested), keeping the lib calls as optimistic UI only — or delete the routes.

22. Customer-tier composition caps unenforced server-side#

Severity: P2 Evidence: free ≤6 segments/≤90s and paid ≤24/≤8min live only in client-side validateCompositionDraft (compose-assist.ts:454); the BFF keep route accepts any score of any size for any tier (routes/living-scenes.ts:554-590; score-schema.ts:143-176 checks structure only — no segment-count/duration/tier rule). Spec promise: 3791-3796 (tier caps; crisis frame disables free-tier composition). What the code actually does: caps are a UI suggestion; the persistence layer takes anything. Fix sketch: enforce the caps in the keep route from authContext tier + segment count/total duration.

23. Determinism / parity / engine-upgrade / rollback / eval-gate engines: zero callers, zero fixtures#

Severity: P2 Evidence: repo-wide grep — checkDeterminism (determinism-harness), assertNodeParity (n-node-parity), checkUpgradeAvailability/planRebake (engine-upgrade — the spec's "v2 available" one-tap re-bake has no surface), planRollback/isArtifactPlayableAfterRollback (release-gates/rollback), living-scenes evaluateReleaseGate (eval-gates): all NONE outside the lib's own tests. No per-template fixture corpus (spec ≥30/template) exists anywhere. Spec promise: 4179-4200 (determinism as release gate, upgrade migration), 4452-4478 (eval sets, gates, rollback). What the code actually does: the gate/parity/upgrade logic is real, typed, tested — and disconnected from any pipeline; nothing can pass or fail these gates in production. What's V1 vs V1.x: with finding 3 fixed, checkDeterminism over the compositor is a cheap real CI gate (V1); N-node parity, FVD-class continuity evals and 30-fixture corpora are V1.x. Fix sketch: wire checkDeterminism into a living-scenes CI test over the real render path; leave the rest explicitly recorded as V1.x.

24. Latency/quality budgets and backpressure: nothing measures anything#

Severity: P2 Evidence: no timing capture on any living-scenes route; conductor backpressure (conductor.ts:171) orphaned (finding 4); no P50/P95 dashboards, no SLO alerts, no per-provider metrics (there are no providers). Spec promise: 4397-4413 (first-frame P50 ≤400ms, segment latency, cue latency budgets, quality dashboards, SLO breach alerts). What the code actually does: budgets exist only as prose. With no streaming runtime this pillar is structurally V1.x; flagged so it is consciously deferred rather than assumed. Fix sketch: record per-route timings into the existing BFF observability when the runtime lands (V1.x).

25. Public viewer keyboard cue buttons are inert#

Severity: UX Evidence: scene/[id]/page.tsx:342-351 — cue verb <button type="button"> elements in a server component with no handlers, no form, no navigation; presented under "Accessibility" alongside data-assistive-tech-navigable="true". Spec promise: 4446-4448 (cue verbs available via keyboard). What the code actually does: renders verbs from the Cue Plan as buttons that do nothing on a static replay page (where cues have no meaning anyway). Fix sketch: render them as a read-only list ("cues used in this offering") until replay exists; don't ship buttons that no-op.

26. Creation→viewing→sharing journey is four disconnected islands#

Severity: UX Evidence: synthesis of findings 1, 2, 5: (a) the card claims to generate/keep/share but calls nothing; (b) compose promotes a PromotedCompositionScore type that cannot be kept (keep wants the unrelated score-schema.Score) — its output evaporates; (c) keep/share is reachable only by API; (d) the public viewer plays media unrelated to any score. No navigation links compose → card → offerings → share → viewer; /studio/compose and /studio/generation/living-scene are anon PUBLIC_PATHS (proxy.ts:42-43) detached from the authed product shell. Spec promise: 3611-3639 (one continuous customer promise from "I asked for an evening walk by the sea" to a kept, shareable offering). What the code actually does: each surface demos its own slice; no user can traverse the promised journey end to end. Fix sketch: one artifact spine — compose promotion emits a score-schema.Score, card Keep posts it, an offerings list links to share creation, share links to the viewer.

27. Provenance signing & unlock secrets — correctly deploy-bound (for the record)#

Severity: DEPLOY Evidence: routes/living-scenes.ts:1472-1500 (OSHUN_LIVING_SCENES_C2PA_SIGNING_KEY absent in prod ⇒ share route 503 fail-closed; deterministic dev key otherwise); living-scenes/scene-unlock-token.ts:104 (OSHUN_SCENE_UNLOCK_TOKEN_SECRET required in prod). The fal LTX enhancer remains release-gate-bound per D4 — not a finding. What deployment needs: set both env vars (32-byte hex signing key + unlock secret) and publish the signer public key for verification.


Severity counts#

Severity Count Findings
P0-SEC 1 6
P0-HONESTY 2 1, 2
P0-STRUCT 2 3, 4
P1 9 5, 7, 8, 9, 10, 11, 12, 13, 14
P2 10 15, 16, 17, 18, 19, 20, 21, 22, 23, 24
UX 2 25, 26
DEPLOY 1 27
Total 27