# Oracle Director, Cross-Cutting Concerns & Deployment

This is the operations page of the V8 (Ariadne) set — the part of the
self-authoring detective universe that decides _which_ minted case a player
meets next, the disciplines that wrap _every_ case regardless of which subsystem
produced it, and the shape of the thing you actually deploy. The throughline is
the same solve-first thesis viewed from the run-time side: once Clew has
proposed a case and Minos has proved it fair and solvable, somebody has to keep
a player in their flow channel, keep the bill bounded, keep every generated
frame content-credentialed, keep a co-op session's canon coherent, and keep the
whole thing reproducible from a seed. In V8 those are first-class, tested code —
an Elo-style player model, a water-mark mint queue that throttles against a live
budget, a clamp-to-headroom budget governor, an FNV-1a reproducibility key, a
fail-loud safety seam, a leader-arbitrated canon writeback — composed beside the
eight generation subsystems rather than hand-waved in a slide.

The concrete surface is three focused packages plus two HTTP services. The
**Oracle** is `@yemaya/case-director`: the drama manager that models the player,
paces difficulty, runs the mint-ahead queue, and places cases in the world
(Ariadne §10). The **cross-cutting spine** is `@yemaya/case-pipeline`: budget,
journal, determinism, provenance, safety, HITL, multiplayer arbitration,
telemetry, and a load harness (Stage 11). The **localization/accessibility**
pass is `@yemaya/case-localization`. The **deployment** is two `node:http`
services — `@v8/loom-service` (the orchestrator BFF the UE5 client reaches) and
`@v8/minos-asp-sidecar` (the constraint solver) — plus the
`@v8/daedalus-compiler` build tool. This page is the deep companion to the
monolith's §4.9 (Oracle), §5 (cross-cutting), and §6 (deployment shape); the
section hub is [../V8_ARCHITECTURE.md](../V8_ARCHITECTURE.md).

## What ships, honestly

The monolith describes an Oracle that reaches all the way into a running V5/V6
open world — seeding ambient mysteries onto live NPC schedules, keeping the
Bureau Case Board warm, minting on demand at run time. The honest split is that
the **direction _logic_ is real and tested, while its wiring into the live UE5
world and the loom-service run time is the open boundary.**

- **The Oracle is real and green, but it is a library, not yet a wired
  runtime.** `@yemaya/case-director` is a fully-implemented package — a genuine
  online-rating player model, streak-aware pacing with anti-repetition, a
  budget-throttled mint queue, deterministic placement, and a seasonal calendar
  — with **33 passing tests across five files**, green when run directly with
  vitest. But **no runtime app imports it yet**: the Loom service's
  `POST /v8/cases/mint` mints from a `CaseSpec` the _caller_ supplies
  (`apps/v8/loom-service/src/server.ts:40`); the Oracle is precisely the
  component that _would_ produce those specs and manage the warm queue. Treat
  the player model, pacer, and queue as tested decision logic, not a director
  already steering a shipped game.
- **The cross-cutting spine is real, and most of it is wired into the run
  pipeline.** `@yemaya/case-pipeline` exports nine primitives, and `runPipeline`
  (`apps/v8/loom-service/src/pipeline.ts:127`) threads five through every mint:
  the per-case **budget** (reserve/spend at each stage), the **journal** (a
  monotonic event per stage), **telemetry**, **provenance**, and the fail-loud
  **safety** gate — behaviour covered by 21 tests in `pipeline.test.ts`. Two
  pieces are real-and-tested but _not_ on the runtime path: the three-tier
  `BudgetGovernor` (§11.1) is exercised in tests (`pipeline.test.ts:83`) but
  `runPipeline` uses the per-case budget, and the `reproduce` determinism check
  ships as a **CLI** (`reproduce-cli.ts`) rather than inside the mint.
- **Localization, accessibility, HITL, multiplayer arbitration, and the
  difficulty-calibration feedback are real libraries with their own tests, not
  yet called from the runtime apps.** `@yemaya/case-localization` is green (**8
  tests**) but unimported by `apps/v8`; the calibration the monolith says feeds
  the Oracle's player model (§9.4/§10) is a real function
  (`calibrateDifficulty`, `libs/yemaya/case-eval/src/calibration.ts:24`) whose
  drift is computed but whose write-back wire to the model is described, not
  coded.
- **Where something needs money, a model, or a binary, it is a fail-loud seam,
  not a fake.** A missing safety scanner _refuses_ rather than passing content
  through unscanned (`requireSafetyClear`, `safety.ts:48`); a non-source locale
  with no translator _refuses_ to emit untranslated text (`localizeStrings`,
  `localize.ts:70`); the Minos sidecar honestly reports whether `clingo` is
  present and falls back to the in-process solver
  (`minos-asp-sidecar/src/server.ts:30`). Honest "planned/gated" beats fake
  "shipped."

## The Oracle: open-world case director

The Oracle's job (monolith §4.9/§10) is to keep one player in the **flow
channel** — cases that are neither trivial nor impossible — while honoring their
taste, avoiding repetition, weaving their open story threads back in, and never
letting the mint bill run away. Its docstring states the remit precisely: model
each player (§10.1), keep a warm queue against a budget (§10.2), decide where
each case surfaces (§10.3), pace difficulty/theme (§10.4), and schedule the
seasonal drops that replace V5's authored packs (§10.5)
(`libs/yemaya/case-director/src/index.ts:1`).

### The player model is a real online rating

`updateFromOutcome` (`player-model.ts:139`) is the load-bearing function, and it
is a genuine Elo-style update, not a moving average of a label. Each finished
case carries an `actual` score in `[0,1]` — `wrong`→0, `partial`→0.45,
`correct`→0.8, `brilliant`→1 (`OUTCOME_SCORE`, `:23`) — and the model nudges the
skill estimate by `K · (actual − expected)`. The `expected` term is the Elo
logistic (`expectedScore`, `:118`): each difficulty maps to an implied rating
(`intro` 0.25 … `fiendish` 0.9, `DIFFICULTY_RATING`, `:111`), so a player whose
skill matches the band is expected to score 0.5. The `K`-factor _decays_ with
evidence (`learningRate`, `:130`): `0.12 + 0.4/(1+n)` runs from 0.52 on a
player's first case to a ~0.12 asymptote, so provisional ratings find a player
fast and settled ones don't tank on one bad night. `recommendedDifficulty`
(`:181`) reads the estimate back into a band, and the model also keeps
theme/cell taste counters and weight-sorted **open threads** (e.g. "wrongly
accused Dockhand Mara, now freed") that pacing weaves into the next case.

### Pacing: flow nudge, anti-repetition, canon weaving

`nextCaseSpec` (`pacing.ts:159`) reconciles three pressures into one
deterministic `CaseSpec`. First, **difficulty tracks skill but is nudged by the
recent streak**: `pacedDifficulty` (`:93`) bumps one band up after a run of
strong solves (stretch) and one down after a run of weak ones (relief). Second,
**anti-repetition**: `themeCandidates` (`:118`) and `cellCandidates` (`:139`)
drop every theme and cell used in the last _K_ cases from the pool — falling
back to the unfiltered pool only if exclusion would empty it, with the player's
tasted themes ordered first so favourites recur. Third, **canon weaving**: the
player's strongest open thread is written into
`castConstraints.priorOutcomeHooks` (`:180`) so the new case calls back to it.
Everything stochastic is driven by a seeded RNG, so a
`(playerId, cell, counter)` triple reproduces the same spec — the replay
requirement begins here.

### The mint-ahead queue throttles against a live budget

`MintQueue` (`mint-queue.ts:83`) keeps _N_ verified cases warm per
`(player, cell)` lane so a player who walks up to the Case Board never waits on
the pipeline. `refill` (`:131`) is real water-mark scheduling: while a lane is
below its high-water mark it **reserves the per-request cost before emitting**
(`DEFAULT_MINT_COST` = 4000 tokens, 3 asset-jobs, `:32`), and the first
reservation the budget cannot grant stops the refill short — the lane is left
partially full and `budgetLimited` is set (`:141`). The Oracle thus throttles
its own mint rate against the remaining envelope rather than blindly minting.
The queue codes against the `CaseBudget` _interface_ and accepts any conforming
budget: it ships a simple `InMemoryCaseBudget` (`:219`) for tests, but in
production the same lane logic runs over the three-tier governor below.

### Placement and the seasonal cadence

`placeCase` (`placement.ts:86`) decides where a verified case surfaces, in
priority order: a **commissioned** case (asked for via the Commission Studio) is
always honoured directly and never demoted; otherwise, if an active world
location matches the spec's required location and has a present NPC to carry the
hook, the case is seeded **ambient** into the living world; else it falls back
to the **bureau Case Board**. The selection is deterministic given the spec plus
the world snapshot. `buildSeasonCalendar` (`seasonal.ts:86`) replaces V5's
authored weekly cold-case packs with a generated drop calendar: `weeks` dated
drops seven days apart, each a deterministic `CaseSpec`, difficulty escalating
across the season (`seasonalDifficulty`, `:59`) and the cell rotating week to
week — and it never reads a clock, so a season is reproducible from its id. The
honest boundary: `placeCase` reads a `WorldState` interface; the _live_
NPC-schedule injection and the in-engine Case Board surface that consume its
decision are the UE5 plugin, the spec/external remainder.

## Cross-cutting concerns (Stage 11)

The cross-cutting spine is `@yemaya/case-pipeline`, whose index enumerates the
Stage-11 disciplines as named primitives (`case-pipeline/src/index.ts:1`). Each
is real, and the ones the mint touches are wired into `runPipeline`.

### Budget and the three-tier mint governor

`createCaseBudget` (`budget.ts:170`) composes the contracts' `CaseBudget`
surface over `@yemaya/budget-management`'s `ComputeBudgetManager`. Its
load-bearing invariant is **no silent overrun**: `spend` (`:104`) clamps a
commit to the remaining headroom so committed spend can never exceed the ceiling
— over-budget means upstream _degrades_ (fewer bespoke assets), never a quiet
overshoot — while `reserve` (`:78`) is a soft hold two parallel stages cannot
jointly push past the ceiling. Above per-case budgets, `BudgetGovernor` (`:215`)
enforces **per-case ∧ per-player ∧ global** ceilings at once: `authorize`
(`:242`) admits a spend only if it fits all three and names the first failing
tier, and `throttle` (`:290`) answers whether the Oracle may admit one more mint
under the player+global envelopes — the exact signal the mint queue honours. The
governor is tested (`pipeline.test.ts:83`), but `runPipeline` uses the per-case
budget today; the three-tier envelope is the production wrap.

### Determinism and replay

A case is reproducible from `(seed, model-versions, canon-snapshot-hash)` — both
a provenance guarantee and a cost lever (a popular case is minted and gated
once, served many times). `canonicalize` (`determinism.ts:36`) produces a
structurally-stable, JSON-safe view — `Map`s become key-sorted pair arrays,
`Set`s sort, and wall-clock fields are dropped — so the IR identity excludes
timestamp noise. `reproKeyOf` (`:119`) folds the seed, provider-sorted model
versions, and canon hash into an FNV-1a-64 key; `assertReproducible` (`:188`)
re-runs the engine and throws with the **first divergent IR path** if two runs
disagree (a real replay-failure signal, not a swallowed boolean). This ships as
the `reproduce` CLI (`reproduce-cli.ts`), which regenerates a case twice and
proves the IR bit-for-bit identical or exits non-zero.

### Provenance, safety, journal, HITL, and multiplayer

- **Provenance metadata.** `recordProvenance` (`provenance.ts`) binds a
  published case to its repro key and captures **every** asset's claim handle,
  generator, content hash, `hashScope`, and signed state from the manifest (not
  a sample), with minted/reused counts. Loom currently emits
  `hashScope: 'locator'` and `signed: false`; only a downstream signer that
  fetches the media bytes may upgrade those fields. `runPipeline` calls the
  recorder on every mint. The generated field boundary is
  [Asset Provenance Fields](./asset-provenance-fields.generated.md).
- **Safety (G6).** `requireSafetyClear` (`safety.ts:48`) is fail-loud by
  construction — it _throws_ both when no scanner is configured (refusing
  unscanned content) and when the scanner reports flags. The offline default
  `KeywordSafetyScanner` (`:123`) is a genuine word-boundary scan over a curated
  abuse/defamation/self-harm lexicon, never always-clear; the pipeline wraps it
  so any throw leaves `g6 = false` and blocks publish (`pipeline.ts:199`).
- **Journal.** `StageJournal.emit` (`journal.ts:88`) writes each stage
  transition to a durable store _and_ a live bus, stamping a strictly-increasing
  `seq` per `(caseId, seed)` run, and `replayRun` (`:122`) returns the run in
  `seq` order for audit. The in-memory default satisfies both interfaces;
  Postgres + Redis are the production swaps.
- **HITL & multiplayer.** `InMemoryHitlQueue` (`hitl.ts:61`) is a real
  pending/resolved queue for the three escalation reasons (repair escalations,
  commissioned cases, flagged content), each item carrying the hidden
  `CaseGroundTruth` so a reviewer sees what the machine saw.
  `arbitrateWriteback` (`multiplayer.ts:38`) resolves competing co-op outcomes:
  the **session leader's** proposal wins, with a deterministic seq-then-playerId
  tiebreak so the committed canon is reproducible regardless of network arrival
  order.

### Localization and accessibility

`localizeStrings` (`localize.ts:63`) keeps the **deduction logic
locale-invariant** — only display strings (each with a stable id from the
writers' room) are translated, never the symbolic IR — so a case solves
identically in every locale, and `assertLocaleInvariant` (`:84`) fails if a
locale bundle drops or invents a string id. A live translator is an injectable
fail-loud seam; `PseudoLocaleTranslator` (`:36`) covers the offline i18n path.
The accessibility pass derives everything from real case structure:
`generateCaptions` (`accessibility.ts:27`) emits a speaker-tagged caption per VO
line; `assignEvidenceCues` (`:68`) gives each clue category a distinct **shape +
fill pattern** so evidence is distinguishable without colour; and
`generateHints` (`:87`) builds **progressive** hints straight from the
solution's deduction path — each level reveals one more step — so a stuck player
is helped without being handed the answer.

### Telemetry and drift

`TelemetryAggregator` (`telemetry.ts:63`) records how shipped cases actually
play — solve-rate, the Brilliant/Good/Doubtful/Wrong rating distribution, an
abandon-point histogram — and `driftAlert` (`:136`) fires when solve-rate or
mean quality regress past a tolerance below a baseline, so a bad cohort of
generated cases is caught before it spreads (`runPipeline` records a session per
mint, `pipeline.ts:248`). The companion load harness `mintThroughput`
(`loadtest.ts:67`) measures sustained cases/second at real concurrency against a
**monotonic** clock (refusing a wall-clock fallback), throwing if a
`minCasesPerSecond` SLA is missed.

## Deployment shape

V8 deploys as TypeScript over the shared Oshun infrastructure: three small
`node:http`/Nest-style services and the subsystem libraries they compose. The
client never holds provider keys — it reaches everything through the Loom relay
(monolith §6).

```mermaid
flowchart TD
    UE["V5/V6 UE5 client<br/>(V8_Ariadne_CaseClient plugin · spec)"]
    UE -->|"POST /v8/cases/mint · GET /v8/cases/:id"| LOOM

    subgraph svc["apps/v8 services (TS / node:http)"]
      LOOM["@v8/loom-service :4010<br/>runPipeline · buildOfflineDeps"]
      MINOS["@v8/minos-asp-sidecar :4070<br/>POST /solve · clingo? → DPLL fallback"]
      DAED["@v8/daedalus-compiler (CLI)<br/>case → FV5* + cold_cases_manifest.json"]
    end
    LOOM -->|"verify G1-G3"| MINOS
    LOOM -->|"publish"| DAED

    subgraph oracle["Oracle (lib · feeds specs · not yet wired)"]
      ORC["@yemaya/case-director<br/>player model · mint queue · placement"]
    end
    ORC -. "CaseSpec + warm queue" .-> LOOM

    subgraph xcut["@yemaya/case-pipeline (Stage 11 spine)"]
      BUD["budget + governor"]
      JRN["journal (seq)"]
      PROV["provenance (C2PA)"]
      SAFE["safety G6 (fail-loud)"]
      TEL["telemetry + drift"]
    end
    LOOM --- BUD & JRN & PROV & SAFE & TEL

    subgraph infra["Infra seams (in-memory default → prod swap)"]
      PG[("Postgres+pgvector<br/>journal store · canon")]
      RDS[("Redis<br/>event bus")]
      OBJ[("MinIO / S3<br/>generated assets")]
    end
    JRN -. store .-> PG
    JRN -. bus .-> RDS
    PROV -. assets .-> OBJ
```

**The services.** `@v8/loom-service` is the orchestrator BFF
(`createLoomServer`, `server.ts:25`): `POST /v8/cases/mint` runs the full
pipeline and returns the compiled pack _only_ if all eight `ReleaseDecision`
gates pass (HTTP 200 vs 422 with blocking reasons), `GET /v8/cases/:id` fetches
a minted pack, and `/health` reports liveness (default port 4010). Its
`buildOfflineDeps` (`deps.ts:44`) is the zero-credential path the §12.4
acceptance run exercises — the deterministic Clew core, a text-only asset pack,
the local `KeywordSafetyScanner`, and in-memory budget/journal/telemetry — with
the production deps swapping in a live LLM proposer, the real Isis asset
realizer, the V7 Sekhmet scanner, and canon-graph retrieval.
`@v8/minos-asp-sidecar` (`server.ts:18`) is the constraint solver as a
dependency-light container: `POST /solve` returns the verdict and `/health`
reports whether a `clingo` backend is available, falling back to the in-process
DPLL solver for an identical verdict (default port 4070).
`@v8/daedalus-compiler` is the build-time CLI that compiles a green case into
V5's `FV5*` Mind-Palace structs plus a `cold_cases_manifest.json`.

**The infrastructure seams.** Every stateful dependency is an interface with a
real in-memory default and a documented production swap: the journal's
`JournalStore`/`EventBus` default to one `InMemoryJournal` and swap to
**Postgres + Redis**; the per-case budget defaults to an in-memory manager;
generated assets land in **MinIO/S3**; the canon graph is **Postgres +
pgvector** with an optional **Neo4j** overlay. Build targets are honest about
scope — the service `build` is a `tsc --noEmit` typecheck and the services
self-execute under `node`; the heavy infra (the clingo image, ACE/Inworld
run-time interrogation with the new `OSHUN_INWORLD_*` / `OSHUN_NVIDIA_ACE_*`
credentials, and the `V8_Ariadne_CaseClient` UE plugin) is the external
remainder the monolith enumerates as tasks. The deeper compile target and eval
path are on
[./daedalus-compiler-and-theseus-eval.md](./daedalus-compiler-and-theseus-eval.md).

## Failure modes and refusals

The layer is built to _refuse_ rather than fabricate, and each refusal is
specific and located:

- **Mint budget exhausted** → `MintQueue.refill` stops short with
  `budgetLimited: true` (`mint-queue.ts:141`); the lane is left partially warm.
  An over-ceiling `spend` clamps to headroom rather than overrunning
  (`budget.ts:114`), and the `BudgetGovernor` denies and names the blocking tier
  (`:256`).
- **Non-deterministic regeneration** → `assertReproducible` throws with the
  first divergent IR path (`determinism.ts:188`); the `reproduce` CLI exits
  non-zero.
- **Unscanned or flagged content** → `requireSafetyClear` throws, leaving G6 red
  and blocking publish (`safety.ts:48`, `pipeline.ts:199`).
- **Untranslatable locale** → `localizeStrings` refuses untranslated text when
  no translator is configured (`localize.ts:70`); a logic-touching localization
  trips `assertLocaleInvariant` (`:84`).
- **Competing co-op writebacks** → `arbitrateWriteback` commits exactly the
  leader's outcome with a deterministic tiebreak (`multiplayer.ts:38`).
- **No clingo binary** → the Minos sidecar reports `clingo: false` and falls
  back to DPLL for the same verdict (`minos-asp-sidecar/src/server.ts:30`).

## Related

- The section hub: [../V8_ARCHITECTURE.md](../V8_ARCHITECTURE.md)
- [./architectural-thesis-and-pipeline.md](./architectural-thesis-and-pipeline.md)
  — the solve-first thesis and the eleven-stage pipeline whose stages the
  journal and budget thread through
- [./daedalus-compiler-and-theseus-eval.md](./daedalus-compiler-and-theseus-eval.md)
  — the compile-to-V5 target and the Theseus playtester whose difficulty
  calibration feeds the Oracle's player model
- [./canonical-data-contracts.md](./canonical-data-contracts.md) — the
  `CaseSpec` the Oracle emits and the `CaseGroundTruth` the HITL queue carries
- [../../platform/overview.html](../../platform/overview.html) — the shared
  Oshun platform (budget management, content signing, release gates) V8 composes
  rather than re-implements
