Oshun Platform · Features

Scene Safety, Determinism, Provenance, and Cue Privacy

A focused page within the Oshun Platform Features documentation. The full map and every sibling page live in the Features hub.

12sections14 minread3tables

On this page

This page documents the governance spine of Living Scenes: the photosensitive-epilepsy (PSE) / strobe killer, the continuous tone-and-motion caps, the mid-stream crisis frame, the deterministic-render release gate, the four redundant provenance channels, and the two-layer (private / public-redacted) cue-privacy model. It serves the contemplative product, the AAA-Creator and operator authoring consoles, and trust-and-safety review; in the V1 stack it sits on top of the Yemaya generation substrate and the Lilith policy substrate. It is the most genuinely implemented part of the Living Scenes story because governance is decision logic, not media rendering. Most of what follows is real, tested code in libs/yemaya/living-scenes-runtime/src/ and libs/oshun/trust-safety/src/; where a piece is a fail-loud seam awaiting an upstream model or a real transcoder, this page says so plainly. See Living Scenes — Concept and Customer Promise for the customer framing and Lilith Persona Policy for the policy substrate this composes on.

The unit of safety: hard gates the user cannot turn off#

A Living Scene is a continuous AI-generated video that listens — the user shapes it live by voice, tap, or text. That responsiveness is exactly why the safety story is built as a set of hard, non-overridable gates rather than preferences. Two of those gates — the PSE/strobe killer and the crisis frame — cannot be relaxed by the user, the persona, the admin copilot, or any entitlement tier. The rest of the page works outward from those two.

Photosensitive-Epilepsy (PSE) / Strobe Detector — the hard kill#

detectPseStrobeHazard in libs/yemaya/living-scenes-runtime/src/safety/pse-strobe-detector.ts is a real flash-rate analysis after WCAG 2.3.1 / the Harding criteria (not, as an earlier draft of the features doc claimed, ITU-R BT.1702-2; that standard is not referenced in the code and was an imprecise citation). It runs on the live frame-luminance sequence and returns a PseStrobeResult:

ts
interface PseStrobeResult {
  safe: boolean; // false ⇒ caller MUST block (non-overridable)
  maxGeneralFlashesPerSecond: number;
  maxRedFlashesPerSecond: number;
  hazards: readonly string[]; // e.g. "general-flash 5/s exceeds 3/s"
}

The algorithm is concrete and load-bearing, so its constants are the safety contract:

Constant Value Meaning
LUMINANCE_FLASH_DELTA 0.1 A general flash needs an opposing-luminance swing ≥ 10% of max.
DARK_THRESHOLD 0.8 The darker side of the opposing pair must be below 0.8 (a dark-to-bright reversal).
RED_FLASH_DELTA 0.2 A red flash is the same test on the saturated-red fraction, swing ≥ 0.2.
FLASH_LIMIT_PER_SECOND 3 More than three general flashes or three red flashes in any one-second window is a hazard.

The detector walks the luminance series, counts a "flash" at each direction reversal whose swing from the previous significant extremum clears the delta (and, for general flashes, whose darker side is below DARK_THRESHOLD), then takes the maximum number of such flashes inside any sliding window of fps frames (one second). If either the general or red count exceeds three, safe is false.

Why a hard kill. The module header is explicit: when safe === false the caller must block the frame sequence — "no tier, flag, or operator setting may override it." This is the detector the V1 audit found missing entirely (a Living Scene could emit a flashing sequence with nothing to detect or block it), so the implementation is the genuine fix. In the live runtime the kill manifests as an immediate fade-to-still plus a plain operator-voice notice; the same detector is re-run in the nightly re-detection sweep (see the takedown disposition below). Actual pixel-level luminance/motion extraction from rendered frames is the one upstream piece — the detector consumes a PseFrame[] of relative luminance and red fraction; the renderer is responsible for producing those numbers.

Continuous tone, luminance, contrast, and motion caps#

Beyond the strobe killer, the runtime enforces per-persona-class budgets on luminance, contrast, and motion density, plus forbidden-frequency flicker bands and per-domain narrative-cadence bands. A Tara meditation budget is dramatically lower than a Veritas explainer budget; templates declare the cap, and the runtime enforces it. When the runtime intervenes, it emits a scene.policy-intervention event (see Scene Events below) tagging the interventionKindpse-strobe-cap, luminance-cap, motion-cap, tone-band-shift, or persona-cap — with the pre- and post-clamp values, so the intervention is observable rather than silent.

Live-cue policy gate and the prompt-injection defense#

Every voice or text cue is policy-checked before it is allowed to queue. The defense begins in the cue parser (libs/yemaya/living-scenes-runtime/src/cues/cue-parser.ts): raw customer text never reaches the segment generator. Instead, sanitizeForCueParse strips quotes, control characters (U+0000–U+001F and U+007F), code-fence markers, and HTML tags, collapses whitespace, and truncates to 240 characters; the sanitized head token is then matched against the small, deliberate CUE_VERBS_VOCABULARY (linger, advance, morph, recall, shift-style, shape-by, save-moment, abandon, kill). Unrecognized input returns an unrecognised verdict (it falls through to a Lilith pre-screen, never to the generator). For the voice channel, an ASR confidence below LOW_CONFIDENCE_THRESHOLD = 0.6 yields a low-confidence verdict so a misheard cue is not acted on. Tap cues are policy-free by construction. The constrained verb mapping — not a content filter — is the structural prompt-injection defense: there is no path by which arbitrary text becomes a generation prompt.

Mid-stream crisis frame and cross-surface cascade#

A crisis signal — detected in conversation, in a live cue, or in a kept-artifact replay viewer — supersedes persona, lineage, pedagogy, grounding, and any user/admin override. The decision shape lives in libs/yemaya/living-scenes-runtime/src/cues/crisis-classifier.ts:

  • LILITH_CRISIS_LABELS = self-harm-imminent, self-harm-ideation, harm-to-others, severe-dissociation, panic-acute, overwhelm, distress, safe.
  • decideCrisisAction maps the top label to a severity: the imminent set (self-harm-imminent, harm-to-others) → imminent; the severe set (self-harm-ideation, severe-dissociation, panic-acute) → severe; overwhelm / distressmoderate; safecontinue.
  • checkLocaleParity enforces that the imminent-class false-negative rate stays ≤ 0.02 (alongside a macro-F1 floor) per locale — a miss on the most dangerous class is the failure the parity gate exists to catch.

The module owns the taxonomy and the decision shape; the classifier model itself is downstream (the header is candid: "the actual classifier model is downstream; this module owns the decision shape + taxonomy mapping"), so any compatible classifier output can be plugged in.

When a frame is activated, the propagation is real and cross-surface. In libs/oshun/trust-safety/src/crisis/crisis-frame-cascade.ts, a single event constant LILITH_CRISIS_FRAME_ACTIVATED_EVENT = 'lilith.crisis_frame.activated' fans out to five surfacesCRISIS_FRAME_SURFACES = psyche, lilith-video, tara, iris, assistant. The published LilithCrisisFrameActivatedEvent carries two non-overridable directives, haltSynthesis: true and suspendMemoryWrites: true, so a per-surface projector cannot soften them. The domain stays free of the event bus (it talks to a CrisisFramePublishPort); crisis-frame-worker.ts binds the real Redis bus. consumeCrisisFrameActivation attempts each projector independently and records every outcome (projected | skipped | failed) so a recovery sweep can re-project failures — one surface failing to enter its frame never blocks the others. This is exactly the doc's "fade-to-still + plain operator voice + suspend Iris memory writes + open a safety-incident record," now with the named event and the five-surface fan-out (Iris memory-write suspend, Tara scheduling, video policy).

The crisis frame also reaches the scene event feed as a scene.crisis-frame event (see below), and any kept artifact whose render history touched a crisis frame is forced private-only by the shareability matrix.

Determinism, the Render Envelope, and re-render-from-score#

Determinism is a V1 release gate: the same Render Envelope must produce a pixel-equivalent video within tolerance, every time, on any approved GPU node. Score storage — Score + Render Envelope + Cue Plan — is the source of truth for a kept artifact, not the materialized MP4; the MP4 is materialized on first share (when channel-watermark bake-in matters) and re-rendered bit-equivalently on demand.

The gate is checkDeterminism in libs/yemaya/living-scenes-runtime/src/determinism/determinism-harness.ts, a pure decision function over per-(node, frame) SHA-256 digests:

ts
interface NodeFrameDigest {
  nodeId: string;
  frameNumber: number;
  sha256: string;
}

interface DeterminismReport {
  nodeIds: readonly string[];
  frameCount: number;
  mismatchedFrames: readonly number[]; // frames where node hashes disagree
  perNodeMismatchCount: ReadonlyMap<string, number>;
  pass: boolean; // every node within mismatchToleranceFrames
}

It groups digests by frame, flags any frame where the set of node hashes has more than one unique value, attributes the mismatch to each diverging node against the reference node, and fails if any node's mismatch count exceeds mismatchToleranceFrames. A run with fewer than two nodes auto-passes (there is nothing to cross-check). The actual rendering happens upstream; the harness is the release-gate decision. When any envelope component is later deprecated (model upgraded, blend kernel revised, locale assets updated), kept artifacts gain a "v2 available" notice with a one-tap re-bake; the original remains playable.

Provenance across channels — four redundant signals#

Provenance is carried in redundant channels so no single transcoding pipeline can erase it. The runtime does not export a single ProvenanceBundle type (a symbol by that name exists only in the unrelated libs/shared/audit-platform); provenance is split into separate, independently recoverable modules under libs/yemaya/living-scenes-runtime/src/provenance/.

Visible mark and the out-of-band attestation page#

provenance/visible/visible-mark.ts builds a VisibleMarkSpec — a minimal, contemplative-aesthetic corner glyph (bottom-right at normalized 0.92, 0.94, oshun-mono, opacity 0.45) carrying a 6-character short code drawn from a Crockford-style base32 alphabet with confusable characters (0/O/1/I/L) removed. The attestation URL is buildAttestationUrl({ origin, shortCode }), which constructs ${origin}/scene/${encodeURIComponent(shortCode)}. Note: the resolver key is the short code, not an artifact id, and the origin is injected — there is no hardcoded oshun.app in the builder. Prose that wrote the attestation page as the literal oshun.app/scene/<id> is therefore stale on both counts. The page re-asserts provenance even if every embedded mark has been stripped.

Inaudible audio watermark — a real keyed DSP#

provenance/audio-watermark/dsp.ts is a genuine spread-spectrum embed/extract, not just a payload encoder. Each ±1 chip is spread across a block of audio samples by a deterministic ±1 pseudo-noise (PN) sequence and added at low gain; extraction correlates each block against the same PN sequence:

  • pnSequence(seed, length) generates the PN sequence via xorshift32 — the seed is the watermark key; embed and extract must share it.
  • DEFAULT_CHIP_LENGTH = 128 samples per chip; DEFAULT_GAIN = 0.08.
  • embedWatermark requires chipBits.length * chipLength samples of headroom and throws otherwise; extractWatermark recovers 0 | 1 | -1 per chip (-1 = erasure below an optional correlation threshold).

The key guarantee: the payload is recoverable from the watermarked signal and robust to moderate additive noise, while a wrong PN key recovers nothing — correlation ≈ 0. That keyed-correlation property is what makes the audio mark hard to forge or strip blindly. (Note that the default deterministic Living Scene render is silent; where there is no audio track, the download grant honestly reports audioWatermarkIntact: 'not-applicable' rather than claiming a mark that is not there.)

C2PA Content Credentials — real Ed25519, tamper-evident#

provenance/c2pa/ed25519-signer.ts provides the genuine cryptographic signer the audit found faked. createEd25519C2paSigner({ privateKey, signerKeyId }) takes a 32-byte Ed25519 secret key (it throws if the length is wrong), keeps the private key inside the closure, and exposes signerKeyId, publicKeyHex, sign(bytes) → hex, and verify(bytes, hex) → boolean, all over the @noble/curves Ed25519 (EdDSA) primitive. createEd25519C2paVerifier(publicKeyHex) gives consumers a standalone verifier from the published public key. The signer backs buildC2paManifest in the sibling c2pa/sidecar.ts, which canonicalizes the media hash (mediaHashSha256, 64 hex chars), sorted assertions, and claim generator, then attaches a real signatureHex. The module header is explicit that this de-fabricates a prior faked signature; the result is a tamper-evident sidecar. Living Scene shares always carry two assertions: oshun.living_scene.artifact (artifact id, envelope hash, privacy level, rendition) and a c2pa.training-mining do-not-train preference. Real C2PA-into-MP4 / HLS-manifest muxing at materialization time is the upstream piece; the manifest itself is real and signed.

Channel-robustness eval — the release gate#

provenance/channel-robustness/channel-eval.ts measures per-channel survival across SHARE_CHANNELS = telegram, whatsapp, twitter-x, instagram, discord, email, generic-web (seven channels). For each channel, evaluateChannel records, per fixture, whether at least one of { visibleMark, audioWatermark, c2paSidecar } was recovered; a channel passes iff every fixture surfaced ≥ 1 signal. evaluateAllChannels rolls this up into perChannel, missingChannels, and allChannelsPass — which is true only when every channel is present and every channel passes. Below 100% recovery on a supported channel blocks release of the share path. Real channel transcoding (running fixtures through the actual platforms' recompression) is upstream; the harness is the gate decision over recorded recovery results.

Cue privacy, redaction, and sensitive-intention handling#

Every Score's intent slot carries two layers: a private layer and a public_redacted layer. cue-privacy/intent-redaction.ts owns the redaction pass over the concrete Lilith taxonomy of eight categories, each a real regex + replacement token:

LILITH_REDACTION_CATEGORIES Example pattern Replacement
named-person Dr Jane Smith, Mrs Smith (title + capitalized name) [NAMED-PERSON]
deceased my late mother [DECEASED]
medical-condition diagnosed with … [MEDICAL-CONDITION]
abuse-disclosure I was abused … [ABUSE-DISCLOSURE]
location-of-safety safe house at … [LOCATION-OF-SAFETY]
minor-identity my 7-year-old … [MINOR-IDENTITY]
financial-account account number … [FINANCIAL-ACCOUNT]
legal-status undocumented, asylum-seeker, on probation [LEGAL-STATUS]

redactIntent returns the publicRedactedText plus the set of categoriesHit; buildIntentLayers wraps that into an IntentLayer and computes a privateIntentHash by SHA-256 over `${intentId}\0${privateText}` using @noble/hashes (chosen over node:crypto so the module bundles for the browser 'use client' surfaces). evaluateCorpus computes per-category and macro precision/recall against a fixture corpus, so the taxonomy's quality is measurable rather than assumed.

Honest scope. The classifier header is candid: "Real-world deployment will swap in a model-backed classifier with the same signature; tests pin the categorical taxonomy." The shipped regexes are deterministic pattern-matchers — robust for the test taxonomy, but not an ML classifier. The seam is the safe kind: the signature is fixed and the corpus evaluator is real, so an upstream model can drop in without changing callers.

Default-deny and operator inspection. Sharing exports the public layer only; opting to share with the private layer requires per-share confirmation and is recorded in the artifact's audit log. The private intent layer and cue history are gated to the same authority class as Iris memory inspection (Iris Memory and Identity) — consented or elevated-authorization — while standard support and trust-and-safety reviewers see only the public-redacted layer (Review, Compliance, and Trust & Safety).

Backend resolver — and the contemplative hard guard#

Which generation backend serves a session is decided by resolveBackend in backend/backend-resolver.ts. There are two backends (BACKEND_IDS = chained-clip, phase-176-nous) and four entitlement tiers (ENTITLEMENT_TIERS = contemplative, curated-creator, aaa-creator, operator-admin). The selector:

  1. Hard guard: the contemplative tier never resolves to phase-176-nous, even if the workflow class declares it; it must support chained-clip and that backend must be able to serve under SLO, or the resolver throws.
  2. Higher tiers sort the class's supported backends by BACKEND_QUALITY_RANK (chained-clip 0, phase-176-nous 1) and pick the highest-rank backend that (a) is not phase-176-nous-while-the-phase176FeatureFlag-is-off and (b) passes the per-backend SLO check (canServeNow, finite non-negative headroom). If none can serve, it throws no-backend-can-serve.

The contemplative guard is the reason a meditation session can never be silently routed onto an experimental world-model backend.

Scene events — the observable governance feed#

Governance actions surface on the Psyche envelope as scene events (libs/oshun/embodiment-psyche/src/events/scene-events.ts, SCENE_EVENT_KINDS). Two are governance-specific:

  • scene.policy-interventioninterventionKindpse-strobe-cap, luminance-cap, motion-cap, tone-band-shift, persona-cap, with preValue / postValue and policyVersion.
  • scene.fallback-engagedfallbackKindreduced-motion-render, still-image-only, transcript-only, audio-with-still, pre-rendered-substitute; triggerprovider-degraded, policy-rejection, eval-gate-fail, shareability-restriction.

Plus scene.crisis-frame itself. verifyEventStream enforces a gap-free, monotonic sequence and per-session trace-id propagation, so the governance feed is auditable end to end. See Psyche Real-Time Runtime for the envelope these ride on.

Launch locales — a known divergence to reconcile#

"V1 launch locales" is referenced widely but is two different sets in code, and the cue-policy / crisis "locale parity across V1 launch locales" requirement is therefore ambiguous about which set it must hold over:

Set Source Members
OSHUN_LAUNCH_LOCALES (8) libs/oshun/i18n/src/index.ts en-US, es-US, fr-FR, de-DE, ar, he, ja-JP, pt-BR
V1_LAUNCH_LOCALES (10) libs/isis/workflow-classes/src/living-scene/locale-parity.ts en-US, en-GB, es-ES, es-419, fr-FR, de-DE, pt-BR, ja-JP, ar-EG, he-IL

The Living-Scenes locale-parity module uses the 10-locale set with regionalized tags and RTL_LOCALES = { ar-EG, he-IL }; the platform i18n module uses the 8-locale set with bare ar / he. The crisis-classifier locale-parity gate (imminent FNR ≤ 0.02) must hold over whichever set the deploy targets; until the two lists are unified, treat the 10-locale LS set as authoritative for Living-Scenes parity and flag the platform-vs-LS divergence in any cross-locale launch checklist. See §25.5 (locale parity) and §25.2 (crisis classifier) for the backlog framing.