# Oshun V1 — Deployment Requirements (the non-code half of "production ready")

Status: **updated 2026-06-02** (supersedes the 2026-05-30 framing); **payments
addendum 2026-07-04** — the fiat + app-store billing rails and the exact
core-runtime/settlement env-var names are now in §7a and copy-pasteable in the
repo-root `.env.example` (previously only prose here). The in-repo
correctness/wiring gaps that could be closed by code **are closed** (see
`V1_PRODUCTION_READINESS_GAP_ANALYSIS_2026-06-02.md`): the studio surface is
real, the hardcoded-adapter stub class is eliminated, five generation journeys
are wired end-to-end, login→ATO + social→achievement triggers fire, OneRoster +
reminder-delivery are durable/autonomous. "Production ready" additionally
requires the **deploy-time** inputs below — credentials, per-tenant config,
content, a running model, object storage, a knowledge corpus — that are not
source code. Each maps to the in-repo code that consumes it and the env that
activates it. §8 lists features whose **in-repo wiring is intentionally absent**
because it needs infrastructure/providers/product-contracts that don't exist yet
(not a code-completable gap).

Every wired feature **fails closed** without its input (no fabricated sends,
images, audio, addresses); the inputs below _activate_ the real path.

---

## 1. Core data + event infra (required for the cascades, durability, idempotency)

| Input    | Env var                                               | In-repo code that consumes it                                                                                                                                                                                                                                        | Default without it          |
| -------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| Postgres | `OSHUN_V1_DATABASE_URL` / `OSHUN_ADMIN_DATABASE_URL`  | `createAdminSnapshotStore`, durable admin/state/cascade/living-scene-share stores, contract persistence; the per-user **saved-library-items** store (4.2 — cross-device saves + the 25-item free cap's source of truth) rides this same snapshot store, no new creds | in-memory (lost on restart) |
| Redis    | `OSHUN_REDIS_URL` / `OSHUN_BFF_IDEMPOTENCY_REDIS_URL` | event-bus cascades (veritas/crisis), `RedisBffIdempotencyStore`                                                                                                                                                                                                      | per-process only            |

Verified end-to-end against live Postgres + Redis (integration tests gated on
the env vars).

## 2. Living Scenes generation

- **Works today:** `POST /v1/living-scenes/render` produces real, watermarked,
  seizure-gated PNG frames from the deterministic CPU compositor
  (`renderLivingSceneSegmentToPng`) — **no external model needed**.
- **AI video (built 2026-06-09):** `video` + `video-from-image` are now V1
  curated cards wired through the generation-job pipeline behind the Isis
  release gate (§8). The provider is the in-repo fal.ai-hosted LTX-Video client
  (`@isis/ai-providers` video-generation, real `fetch` + poll), activated by
  `OSHUN_VIDEO_FAL_KEY`. Fail-closed without a key; even credentialed, every
  clip stays gate-**blocked** until a deploy-bound video safety-scan +
  watermark + C2PA step lands (no in-repo video scanner). The deterministic
  render path above is unaffected.
- **Alternative video backend (optional):** the in-repo ComfyUI/RunPod client
  (`@yemaya/comfyui-integration` provider-manager, real `fetch`-based submit/
  poll) is also ready for a self-hosted GPU endpoint + model weights, flowing
  through the same watermark + PSE pipeline and the fail-closed Isis gate
  (`/v1/isis/generate`, currently `notConfiguredProviderExecutor`). The
  curated-card fal path above is the wired default.

## 3. Multi-tenant LMS (LTI 1.3 / SCORM)

- **Works today:**
  `POST /v1/tenant/lms/{lti/login,lti/launch,scorm/launch,scorm/sessions/:id/runtime}`
  are mounted; the id_token JWS verifier, OIDC login, and SCORM 1.2/2004 RTEs
  are implemented and tested.
- **Deploy input:** per-tenant connector registrations in `OSHUN_LMS_CONNECTORS`
  (JSON array of `LmsConnectorDescriptor`: issuer, clientId, deploymentIds,
  jwksUrl, authorizationUrl, tokenUrl, scopes). Each is validated on boot
  (invalid skipped — but **the skip reason is now retained**, not discarded).
  With none configured the routes 404 honestly. The platform's live JWKS
  endpoint is fetched over HTTPS at launch time.
- **Operability (admin-gated):** `GET /v1/admin/lms/connectors` lists the live
  connectors AND any skipped at boot with their exact validation `issues` (no
  more silent skips); `POST /v1/admin/lms/connectors/validate` dry-runs a
  candidate descriptor (no persistence) and returns the structured
  `ConnectorValidationResult`. Both require `admin:*`/`admin:studio`; the GET
  summary never echoes connector secrets.
- **Per-provider `OSHUN_LMS_CONNECTORS` examples** (copy-pasteable; production
  requires HTTPS jwks/auth/token URLs, `assignment-and-grade` requires all three
  AGS scopes, `names-and-roles` requires the context-membership scope; validate
  any candidate first via `POST /v1/admin/lms/connectors/validate` or the
  `/operator/lms` dry-run form):

  **Canvas (LTI 1.3 + AGS):**

  ```json
  [
    {
      "connectorId": "canvas-prod",
      "tenantId": "tenant-1",
      "kind": "lms",
      "providerKey": "canvas",
      "environment": "production",
      "status": "enabled",
      "lti13": {
        "issuer": "https://canvas.example.edu",
        "clientId": "oshun-tool",
        "deploymentIds": ["dep-1"],
        "jwksUrl": "https://canvas.example.edu/api/lti/security/jwks",
        "authorizationUrl": "https://canvas.example.edu/api/lti/authorize_redirect",
        "tokenUrl": "https://canvas.example.edu/login/oauth2/token",
        "permittedMessageTypes": ["LtiResourceLinkRequest"],
        "advantageServices": ["assignment-and-grade"],
        "serviceScopes": [
          "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem",
          "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly",
          "https://purl.imsglobal.org/spec/lti-ags/scope/score"
        ]
      }
    }
  ]
  ```

  **Moodle (LTI 1.3 + names-and-roles):**

  ```json
  [
    {
      "connectorId": "moodle-prod",
      "tenantId": "tenant-2",
      "kind": "lms",
      "providerKey": "moodle",
      "environment": "production",
      "status": "enabled",
      "lti13": {
        "issuer": "https://moodle.example.org",
        "clientId": "oshun-moodle-tool",
        "deploymentIds": ["2"],
        "jwksUrl": "https://moodle.example.org/mod/lti/certs.php",
        "authorizationUrl": "https://moodle.example.org/mod/lti/auth.php",
        "tokenUrl": "https://moodle.example.org/mod/lti/token.php",
        "permittedMessageTypes": [
          "LtiResourceLinkRequest",
          "LtiDeepLinkingRequest"
        ],
        "advantageServices": ["names-and-roles"],
        "serviceScopes": [
          "https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly"
        ]
      }
    }
  ]
  ```

  **SCORM-only (legacy packages, no LTI):**

  ```json
  [
    {
      "connectorId": "legacy-scorm",
      "tenantId": "tenant-3",
      "kind": "lms",
      "providerKey": "generic",
      "environment": "production",
      "status": "enabled",
      "scorm": {
        "enabled": true,
        "acceptedVersions": ["1.2", "2004"],
        "maxPackageBytes": 10000000,
        "launchBaseUrl": "https://lms.oshun.app/scorm",
        "featureClass": "legacy-fallback"
      }
    }
  ]
  ```

## 4. Messaging (multi-channel)

- **Works today:** in-app notifications (the notifications subsystem) need no
  external provider. The dispatcher + delivery seam + idempotent reminder cycle
  (`runReminderCycle`) and worker loop (`runReminderWorkerTick`) are implemented
  and tested; missing credentials yield a `missing-config` result (never a fake
  send).
- **Deploy input (per external channel):**
  - Email (SendGrid): `OSHUN_SENDGRID_API_KEY`, `OSHUN_MESSAGING_EMAIL_FROM`
  - SMS (Twilio): `OSHUN_TWILIO_ACCOUNT_SID`, `OSHUN_TWILIO_AUTH_TOKEN`,
    `OSHUN_TWILIO_FROM`
  - Push (FCM / Android): `OSHUN_FCM_ACCESS_TOKEN`, `OSHUN_FCM_PROJECT_ID`
  - Push (APNs / iOS): `OSHUN_APNS_TEAM_ID`, `OSHUN_APNS_KEY_ID`,
    `OSHUN_APNS_PRIVATE_KEY` (the .p8 PEM), `OSHUN_APNS_BUNDLE_ID` (optional
    `OSHUN_APNS_SANDBOX=true`). The transport signs a real ES256 provider JWT
    and POSTs to the APNs HTTP/2 device endpoint; `push-apns` is routed to APNs,
    never FCM (wired 2026-06-02).
  - WhatsApp (Meta): `OSHUN_WHATSAPP_ACCESS_TOKEN`,
    `OSHUN_WHATSAPP_PHONE_NUMBER_ID`, `OSHUN_WHATSAPP_TEMPLATE`,
    `OSHUN_WHATSAPP_LANGUAGE`
  - Slack: `OSHUN_SLACK_BOT_TOKEN`; Discord: `OSHUN_DISCORD_BOT_TOKEN` A channel
    is sent only when ALL its vars are present
    (`buildMessageProviderConfigFromEnv`). NOTE: an HTTP/2-capable fetch must
    back the APNs transport at deploy (Node global `fetch` is HTTP/1.1; APNs
    requires HTTP/2) — inject it as `fetchImpl`.
  - **Activation visibility:** `GET /v1/admin/messaging/configured-channels`
    (admin-scoped) reports which channels are active in this deployment —
    channel NAMES only, never secret values (`configuredChannelsFromEnv`). Every
    var above is documented in `.env.example` (Messaging / Notifications block).
- **Reminder DELIVERY (autonomous):** set `OSHUN_REMINDER_WORKER_INTERVAL_MS`
  (>0) to start the in-process delivery worker (`startReminderDeliveryWorker` →
  `runScheduledReminderCycle` on a timer; in-app + env transports, idempotent on
  `deliveredIds`). Disabled by default (fail-closed). Run on a **single instance
  / behind leader election** — duplicate instances waste work but never
  double-send. No external cron needed.
- **Reminder PRODUCTION (now wired in-repo, 2026-06-02):** two real producers
  now query live domain state — no external slice-feeder needed for streaks:
  - **BFF-scoped (known users):** `startReminderDeliveryWorker`'s `produce` hook
    sweeps the consumer-profile known-user set, reads each user's real streak
    stats via the Arete adapter, and schedules an in-app streak reminder for any
    at-risk daily streak (deduped). Active whenever the delivery worker is on.
  - **Arete all-users sweep:** `apps/arete/api` `POST /v1/reminders/streak-due`
    (admin-only) runs `produceStreakLossReminders` over
    `HabitRepository.listHabitsAtRiskOfStreakLoss(date)` (active daily habits,
    live streak, no check-in today) and returns one reminder per at-risk habit
    with a deadline at the **end of the member's own local day**. Point a daily
    cron at it; delivery transport (email/push) is the §4 env injection above.
  - _Remaining external slices:_ due-assignment / upcoming-session producers
    still need a domain-service bulk-enumeration feed (the streak path is
    built).

## 5. Curated hub content

- **Works today:** all five domain rooms (`/veritas`, `/nyx`, `/tara`, `/metis`,
  `/nisaba`) are served from the BFF as the source of truth with real-data
  overlays (grounded briefing, ephemeris, sitting completions, course proposals,
  annotation counts) and honest "unavailable" fallbacks.
- **Deploy/content input:** the curated editorial copy (meditations, passages,
  course catalog) currently shipped as the BFF default content set is
  replaceable by an editorial/CMS content source feeding the same room
  endpoints.

## 6. Provenance signing (Living Scenes C2PA)

- **Deploy input:** the Ed25519 signing key (`OSHUN_LIVING_SCENES_C2PA_*`). With
  a dev fallback in non-prod; fail-closed in production. The signing +
  verification code is real.

## 7. Payments (crypto settlement) — updated 2026-06-10

- **Works today (real code, wired):** the BFF routes are backed by
  **`@oshun/payments-bridge`** through the composition root
  (`apps/oshun/bff/src/payments/`): `POST /v1/payments/crypto/quote` validates →
  disclosure-gates → provisions a real per-charge receiving target → builds the
  `PaymentsBridgeInvoiceDto` via the lib's paywall builders → signs an Ed25519
  receipt → persists to the **snapshot-durable invoice ledger**;
  `GET /v1/payments/methods` serves the REAL asset catalog (`active` reflects
  whether a runtime is bound); `GET /v1/payments/invoices` lists the ledger;
  `GET /v1/payments/invoices/:id` re-serves an issued invoice's **full DTO**
  (retained at issuance) — the web `billing/crypto` paywall's fetch
  (`invoice-fetcher-wiring.ts` sets the loader's fetcher at server boot; only a
  still-`pending` invoice renders as payable). **Do NOT wire
  `@aphrodite/crypto-payments`** — its rate fetch is mocked and its EVM path
  simulated; the bridge is the real stack.
- **Why it stays fail-closed without deploy inputs:** a quote must hand the
  customer a real, non-custodial **receiving address** and a **live,
  spread-checked rate** — fabricating either loses customer funds. With no bound
  runtime, `POST .../quote` → `503 payments_not_configured` in **every**
  environment (no address is ever issued), and the web paywall renders the
  honest "issuance pending" notice.
- **Deploy input (binds the runtime via `bindPaymentsRuntime`):**
  - **Receipt signer**: `OSHUN_PAYMENTS_RECEIPT_ED25519_PRIVATE_KEY` (32-byte
    hex) + `OSHUN_PAYMENTS_RECEIPT_AUDIT_KEY_ID` (both in `.env.example`) —
    every issued invoice carries a verifiable Ed25519 receipt.
  - **Live rate feed**: API access for the 3-source median aggregator (Kraken +
    CoinGecko + a Uniswap v3 TWAP RPC endpoint); <2 reachable sources →
    fail-closed, no fabricated rate.
  - **Settlement provisioner** (the `InvoiceTargetProvisioner` port): BTC/LN via
    BTCPay Server or OpenNode (API key + store id; the in-repo
    `BitcoinPaymentProcessor` is real), EVM/Solana/TON/Monero via `@aje/*`
    wallet derivation (operator xpub/derivation config) or a non-custodial PSP.
    A chain with no provisioner is omitted from the catalog (fail-loud), never
    served with a placeholder address.
  - **Webhook secrets**: the BTCPay/OpenNode/PSP signing secrets for the
    settlement webhook (signature-verified, fail-closed) that flips an invoice
    to `confirmed` and grants entitlements at the per-asset
    `requiredConfirmations`.
  - **Settlement webhook secret (exact var):** the receiver reads
    `OSHUN_CRYPTO_SETTLEMENT_WEBHOOK_SECRET` — an HMAC-SHA256 shared secret
    verified over `${timestamp}.${rawBody}` (±300s replay window). Unset →
    `POST /v1/payments/crypto/settlements` fails closed (503).

## 7a. Payments — fiat rail + app-store IAP (added 2026-07-04; not in the 2026-06-02 baseline)

The baseline §7 documented only the crypto settlement rail. Two more payment
rails have since been wired into the BFF, each fail-closed on the same
honest-seam pattern (no credential → typed 503, never a fabricated
charge/entitlement). None of these vars were in the 2026-06-02 `.env.example`;
all are now present.

- **Fiat — Stripe Billing + Apple Pay / Google Pay** (`@oshun/fiat-payments`,
  `apps/oshun/bff/src/payments/fiat-routes.ts`, `/v1/payments/fiat/*`):
  - `STRIPE_SECRET_KEY` — the only required Stripe secret (API version + base
    URL are pinned in-code). Unset → `503 stripe_not_configured`.
  - `STRIPE_PUBLISHABLE_KEY` — additionally required for the Google Pay
    tokenization config route.
  - `STRIPE_WEBHOOK_SECRET` (+ optional `STRIPE_WEBHOOK_SECRET_PREVIOUS` for
    zero-downtime rotation) — the raw-byte signature-verified webhook; unset →
    the webhook 503s and Stripe events are refused. A verified
    `checkout.session.completed` / `customer.subscription.*` event binds the
    member, mints the local subscription from the event's real periods, and
    syncs the entitlement plan the gate reads (revocations flip it back to
    free).
  - `APPLE_PAY_MERCHANT_ID` activates the Apple Pay config + merchant-validation
    routes (the live Apple merchant-identity session transport is a separate
    deploy-bound seam → `503 apple_merchant_identity_not_configured` until
    injected). `GOOGLE_PAY_MERCHANT_ID` (+ the publishable key) activates Google
    Pay.
- **App-store IAP — Apple StoreKit 2 + Google Play** (`@oshun/billing-support`,
  `apps/oshun/bff/src/routes/billing-app-store.ts`, `/v1/billing/app-store/*`):
  the signed-transaction / server-notification verifiers are injectable seams
  that fail **loud** (`503 apple_verifier_not_configured` /
  `play_verifier_not_configured`) until a deploy injects the **Apple root-CA
  chain** + the **Play Developer API service account** — deploy-bound trust
  roots, not env vars. The env only sets identity + the RTDN gate:
  `OSHUN_APPLE_BUNDLE_ID`, `OSHUN_PLAY_PACKAGE_NAME` (both default
  `com.oshun.app`), and `OSHUN_PLAY_RTDN_PUSH_TOKEN` (the Pub/Sub push shared
  secret; unset → the RTDN endpoint 503s). Every admission/notification
  recomputes + persists the entitlement tier the middleware reads (renewals
  extend; REVOKED/EXPIRED downgrade to free).
- **Core BFF runtime vars referenced in §1** —
  `OSHUN_ADMIN_DATABASE_URL`/`OSHUN_V1_DATABASE_URL`,
  `OSHUN_BFF_IDEMPOTENCY_REDIS_URL`/`OSHUN_REDIS_URL`, `OSHUN_BFF_JWT_SECRET` (+
  `OSHUN_BFF_FORCE_SIGNED_TOKENS`/`OSHUN_BFF_ALLOW_DEV_TOKENS`),
  `OSHUN_DOMAIN_SERVICE_TOKEN`/`TARA_`/`ARETE_OSHUN_FACADE_TOKEN`,
  `OSHUN_SCIM_BEARER_TOKEN`, the public origins +
  `OSHUN_DEPLOYMENT_RESIDENCY_ZONE` — are now copy-pasteable in the repo-root
  `.env.example` (previously only prose in §1). The BFF
  `apps/oshun/bff/.env.example` has also been corrected to the canonical
  `OSHUN_*` names (its earlier `AUTH_JWKS_URL` / `SERVICE_JWT_SECRET` /
  `REDIS_URL` entries were read by no code).

## 8. AI generation providers (wired 2026-06-02 — fail-closed without creds)

The generation-job pipeline (`/v1/generation/jobs`, fed by the curated-card,
music, and nyx-3d front-doors + the agentic studio) now resolves **real
providers from credentials**; each kind fails closed (the job records
`provider_not_configured`) until its key is set:

| Kind                         | Env var(s)                                                                                                                               | Provider                                                                       | Customer journey                                                   |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------ |
| `illustration` + `explainer` | `OSHUN_STABILITY_API_KEY` (`OSHUN_STABILITY_MODEL?`)                                                                                     | Stability SD3.5                                                                | curated illustration / grounded explainer card                     |
| `music`                      | `OSHUN_AUDIO_MUSIC_RUNPOD_ENDPOINT` + `OSHUN_RUNPOD_API_KEY`                                                                             | open ACE-Step 1.5 (ComfyUI/RunPod)                                             | `/v1/isis/music/generate` (ambient-audio)                          |
| `sky-briefing`               | `OSHUN_MESHY_API_KEY`                                                                                                                    | Meshy text-to-3D                                                               | `/v1/isis/nyx-3d/generate`                                         |
| `narration`                  | `OSHUN_ELEVENLABS_API_KEY` + `OSHUN_ELEVENLABS_VOICE_ID`                                                                                 | ElevenLabs TTS                                                                 | curated narration card (inline base64 audio)                       |
| `caption-dub`                | `OSHUN_DUBBING_ELEVENLABS_API_KEY` (or `OSHUN_ELEVENLABS_API_KEY`) **+ `OSHUN_ASSET_STORE_*`** (`OSHUN_DUBBING_API_BASE?`)               | ElevenLabs Dubbing (voice-cloning dub per target language)                     | curated caption-dub card (per-locale stored outputs + signed URLs) |
| `accessibility-pass`         | `OSHUN_ASSET_STORE_*` (contrast, in-repo) **+ optional `OSHUN_A11Y_ML_API_BASE`/`_API_KEY`** (`_VISION_MODEL?`, `_TRANSCRIPTION_MODEL?`) | in-repo WCAG contrast + OpenAI-compatible vision alt-text / Whisper transcript | curated accessibility-pass card                                    |
| `video` + `video-from-image` | `OSHUN_VIDEO_FAL_KEY` (`OSHUN_VIDEO_MODEL?`, default `ltx-video-2.0`)                                                                    | fal.ai-hosted LTX-Video (text-to-video + image-to-video)                       | curated `video` / `video-from-image` cards                         |

All **eight** V1 curated-card kinds (illustration, narration, ambient-audio →
`music`, explainer, caption-dub, accessibility-pass, `video`,
`video-from-image`) are now wired through the generation-job pipeline (admit →
enqueue → worker, fail-closed without a provider) — and as of 2026-06-10 **all
eight ship a concrete in-repo provider client activated by credentials**
(Stability, open ACE-Step, Meshy, ElevenLabs TTS, fal LTX-Video, ElevenLabs
Dubbing, the in-repo WCAG contrast pass + an OpenAI-compatible vision/Whisper
a11y service). (Image generation also has the in-repo ComfyUI/RunPod path of
§2.) **Caption-dub activates on an ElevenLabs key + the asset store** (resolves
the `sourceAssetId`, dubs per primary-subtag locale via submit → poll →
download, persists each dubbed output back to the store under
`caption-dub/{dubbingId}/{lang}.{ext}` with a signed download URL, and scans the
dubbed transcript for the gate's safety score). **Even when credentialed it
stays fail-closed at the gate:** ElevenLabs dubbing clones the source speakers'
voices, so every measurement carries the canonical `cloned-voice-used`
human-review trigger (released only with an assigned reviewer), and
provenance/watermark are honestly absent until the deploy-bound signing/
watermark steps land. **Video ships its provider but stays fail-closed at the
gate even when credentialed:** there is no in-repo video safety-scanner /
watermarker / C2PA signer, so its measurement carries `safetyScanScore=null` +
`provenanceManifestPresent=false` + watermark absent → every in-repo clip is
honestly **blocked** (specific missing-signal reasons, never a fabricated pass)
until those deploy-bound steps land — the same honest posture as music +
sky-briefing. **Accessibility-pass releases directly** (its no-`url` analysis
report uses the `releaseKind:'analysis'` exemption — there is no releasable
media asset): WCAG contrast needs only the asset store; alt-text + transcript
additionally need `OSHUN_A11Y_ML_*` and honestly report
`requires-external-provider` without it.

**Isis release gate (wired into the worker 2026-06-02 — a provider contract):**
every produced output is now passed through `evaluateGenerationRelease` before
it is marked `complete`/released. A provider executor's result MUST carry a
`releaseMeasurement` (safety-scan score ≥ 0.9, C2PA `provenanceManifestPresent`,
per-kind watermark-coverage + quality floors, policy/rights/shape clear) — and
optionally `releaseAdmissions` (template/model-provenance). The worker maps the
gate decision to a job status: `complete` (allow) / `needs_review` (review) /
`blocked` (block, denied admission, or **no/invalid measurement** —
fail-closed). **A credential alone does NOT activate the customer path.**
Setting (e.g.) `OSHUN_STABILITY_API_KEY` activates the provider _call_ — it does
not release the output. Release happens only when the executor emits a
`releaseMeasurement`; an output with no governance evidence lands
`blocked`/`governance_measurement_absent` (fail-closed), never auto-released.
The **measurement bridge** is therefore the keystone, not the credential.

That bridge now exists: `buildReleaseMeasurement(signals)`
(`apps/oshun/bff/src/generation/provider-measurement.ts`) maps a provider's real
governance signals (safety-scan score, watermark coverage, C2PA-present flag,
quality score, output kind) into the canonical envelope — fail-closed (an absent
floor signal blocks; it never substitutes a passing constant). **Per-provider
signal extraction is now wired for every adapter:** image
(`adaptImageProviderGenerate`), narration, music, sky-briefing, and both video
adapters each call `buildReleaseMeasurement` with their real governance signals,
and all nine worker executors forward the resulting measurement verbatim (the
invariant is complete — `image-executor.ts:68` is the pattern). Where a real
output-content signal exists it is used (image moderation via
`OSHUN_IMAGE_MODERATION_*`; narration moderates the spoken text via
`OSHUN_TEXT_MODERATION_*`); where no in-repo scanner exists (instrumental music,
3D geometry, video) the signal is honestly `null` and the output **blocks** at
the gate — never a fabricated passing constant. The remaining piece that lets a
credentialed media job actually reach the customer is therefore deploy-bound:
the real safety-scan + watermark + C2PA-signing services (and, for image/audio/
video, persisting the processed bytes to the asset store), not in-repo code.

**Activating a deployed governance step (the exact env flags):** once a real
watermark / C2PA / synthetic-voice-disclosure step IS deployed for a media kind,
tell the release gate it exists via the per-kind OPERATOR ATTESTATION flags now
in `.env.example` — `OSHUN_WATERMARK_{IMAGE,AUDIO,VIDEO}_CONFIGURED`,
`OSHUN_PROVENANCE_{IMAGE,AUDIO,VIDEO}_CONFIGURED`, and
`OSHUN_SYNTHESIS_DISCLOSURE_AUDIO_CONFIGURED`. Each is fail-closed (unset ⇒ that
kind stays blocked); a flag is a deploy PROMISE that the step is really running,
not a bypass (`media-releasability.ts`). The canonical price book both payment
rails price against — and re-verify settlements against before granting
entitlements — is `OSHUN_PLAN_PRICE_BOOK_JSON` (malformed ⇒ throws at boot).

## 9. Remaining feature gaps — need infrastructure/execution engines (NOT code-completable in-repo)

**Scope:** All **eight** V1 curated cards (illustration, narration,
ambient-audio, explainer, caption-dub, accessibility-pass, and — added
2026-06-09 — `video` + `video-from-image`) are now wired through the generation
pipeline (§8). **General-3D** is still **not** a V1 curated card (the general-3D
_studio_ surface is an operator tool — verified real in the studio audit;
customer 3D is the wired Nyx sky-briefing). Video, formerly listed here as
out-of-scope, is now an in-scope curated card with a concrete provider (fal
LTX-Video); it remains gate-blocked until a deploy-bound video
safety-scanner/watermarker/C2PA signer is provided (see §8).

The items below need an external provider/backend/data capability bound at the
deployable boundary — the application code (executor, route, store, seam) is in
place; the external capability is not source code.

| Feature                                                 | What it needs (beyond credentials)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `caption-dub` / `accessibility-pass` **infrastructure** | the in-repo clients now exist (§8: ElevenLabs Dubbing; vision alt-text + Whisper transcripts). What remains deploy-bound: the **`OSHUN_ASSET_STORE_*` object store itself** (S3/MinIO bucket + creds — resolves the `sourceAssetId`, persists dubbed outputs), the dubbing/a11y **API keys**, the **text-moderation endpoint** (dub transcript safety scan), a **human-review rota** (every dub triggers `cloned-voice-used`), and the media **provenance/watermark steps** shared with narration/video                                                                                                                                                                  |
| Sophia `/answer` **prose synthesis** (enhancement only) | grounding is now real: `/v1/sophia/{answer,grounded}` retrieve query-relevant citations from the Nisaba library (the in-repo corpus) and `/v1/sophia/sources/grounding` scores provided sources. The V1 criterion ("grounding visible where evidence matters") is met by the real citations. Only synthesized _prose_ over the evidence needs an LLM — an enhancement, not the grounding feature. **The synthesizer seam is built + wired** (`createOpenAiCompatibleSynthesizer`, temp 0, JSON mode, honesty-sanitized, fail-soft to extractive); activate it by setting `OSHUN_LLM_API_BASE` + `OSHUN_LLM_API_KEY` + `OSHUN_LLM_MODEL` (any OpenAI-compatible endpoint) |
| reminder PRODUCTION — publisher event feeds             | all four reminder kinds now have an in-repo BFF-projection producer + durable store + Redis event consumer (§4): streaks (Arete `listHabitsAtRiskOfStreakLoss`), due-assignment (`lms.assignment.upserted`), upcoming-session (`session.upserted`), content-drop (`content.drop.scheduled`). The remaining deploy-bound piece is the **owning services emitting those per-recipient events** onto the shared Redis bus (the BFF projects them; it never invents the audience). Absent the events, the stores stay empty and produce nothing (honest)                                                                                                                     |

(payment → chargeback — previously listed here — is now wired: the Telegram
invoice handler screens the proposed transaction through the real scorer into
`ChargebackScreeningStore`, surfaced on the admin chargeback route. The Isis
governance gate and APNs push transport — previously gaps — are also now wired;
see §8 and §4.)

(upload → model-governance enforcement — previously listed here as "no in-BFF
upload flow" — is now wired (2026-06-09):
`POST /v1/admin/isis/model-governance/ upload` validates a full per-right
3D-model license matrix → registers it into the (snapshot-durable) license
registry → `admit()`s at the boundary, recording the commercial + IP verdict.
The Isis release gate on generation output (§8) remains the separate, always-on
output-side model-governance.)

(Cross-domain achievement auto-award — previously listed here — is now wired via
a debounced read-time refresh on the achievements read routes.)

---

### Summary

This session closed the cleanly-completable in-repo correctness/wiring gaps (20
verified slices: achievements correctness + auto-award + read-time refresh + the
stub-class elimination, **all six curated-card journeys** + sky-briefing wired
through the generation pipeline, **Sophia grounded retrieval over the Nisaba
library**, three native event triggers (login→ATO, social→achievement,
payment→chargeback), OneRoster + reminder-delivery durability/autonomy). As of
2026-06-10 all eight curated-card kinds ship a concrete in-repo provider
activated by credentials — caption-dub (ElevenLabs Dubbing + asset store +
transcript safety scan) and accessibility-pass (in-repo WCAG contrast +
OSHUN*A11Y_ML*\* vision/Whisper) included (§8/§9). Going live requires
provisioning the deploy-time inputs in §1–§8 — data stores, generation-provider
keys, per-tenant LMS config, an optional AI video model + GPU endpoint, the
editorial content source, the signing key, and the reminder worker interval. The
features in §9 additionally require infrastructure (object store, knowledge
corpus), provider selection, or new product contracts before their in-repo
wiring is non-speculative. None of §1–§9 is closable by further in-repo code
alone at the verified, zero-stub bar.
