Ariadne · Features

Cross-Cutting Concerns, the Pipeline & Localization

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

7sections12 minread1diagram

On this page

Most of V8's feature set is about producing something — a ground truth solved, a case proven, a suspect brought to life, a pack compiled into the shipping game. This page is about the connective tissue: the single composition that runs all of those subsystems in order, and the disciplines that wrap every minted case no matter which subsystem produced it. Ariadne's promise is not "infinite content" — anyone can generate infinite text — it is fairness you can trust, reproducibly and within budget. That promise only survives contact with a live service if five things hold for every case: it reproduces bit-for-bit from a seed, it is minted under a bounded bill, nothing unsafe or unfair can reach a player, its display text can localize without ever touching the deduction logic, and an operator can see how it played. In V8 those are not slideware values — they are small, real, tested TypeScript packages threaded through one orchestrator.

The concrete surface is one composition and one spine. The composition is runPipeline (apps/v8/loom-service/src/pipeline.ts:127), which ties Clew → Minos → Anansesɛm → Loom → Daedalus → Theseus into a single mint-to-publish call. The spine is @yemaya/case-pipeline (libs/yemaya/case-pipeline/src/index.ts), which exports nine Stage-11 primitives — budget, journal, determinism, provenance, safety, HITL, multiplayer arbitration, telemetry, and a load harness — plus the localization/accessibility pass in @yemaya/case-localization. The case gates themselves are the shared platform suite, @oshun/v8-case-gates (libs/v8/case-gates), composed on @oshun/content-release-gates rather than forked. This is the feature-side view of those cross-cutting concerns; the architecture companion is ../architecture/oracle-director-cross-cutting-and-deployment.md, and the hub for the whole set is ../V8_features.md.

What ships, honestly#

The cross-cutting spine is real, and most of it is wired into the run-time mint. These suites run green here, against the actual code: case-pipeline/src/pipeline.test.ts (21/21), the Loom service's §12.4 acceptance apps/v8/loom-service/src/pipeline.test.ts (5/5), case-localization (8/8), case-director (33/33 across five files), the platform case-gates.spec.ts (7/7), and the C2PA case-bundle.spec.ts (5/5). The logic is domain-specific, not CRUD: an FNV-1a-64 reproducibility key over a Map-aware canonical IR, a clamp-to-headroom budget that degrades instead of overrunning, a three-tier mint governor, a fail-loud keyword safety scanner over a curated lexicon, a leader-arbitrated co-op writeback, and a drift detector over a real solve-rate/quality baseline.

Three honesty notes matter, in the same spirit as the rest of the V8 set.

  • Most of the spine is on the run-time path; two pieces are tested but not yet threaded. runPipeline threads five primitives 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. But the three-tier BudgetGovernor is exercised in tests (pipeline.test.ts:83) while runPipeline uses the per-case budget today, and the reproduce determinism check ships as a CLI (apps/v8/loom-service/src/reproduce-cli.ts) rather than inside the mint.
  • Localization, accessibility, HITL, and multiplayer arbitration are real libraries with their own green tests, not yet called from the runtime apps. @yemaya/case-localization is implemented and tested but unimported by apps/v8 — its live translator is the injected boundary, so V8 localization is a real-but-deferred motion, not a missing capability and not a fake one.
  • Where something needs money, a model, or a binary, it is a fail-loud seam, not a fabrication. A missing safety scanner refuses rather than passing content through unscanned (safety.ts:48); a non-source locale with no translator refuses to emit untranslated text (localize.ts:70); an absent media provider in the bundle forge yields a not-configured slot, never a fabricated asset. Honest "planned/gated" beats fake "shipped."

The end-to-end pipeline: one CaseSpec, eleven stages, eight gates#

A case is born from a CaseSpec and dies or ships at decideRelease. The whole arc is one function — runPipeline(spec, deps) — and reading it is the fastest way to understand how the subsystems compose. It runs, in order: Clew (generateCase builds ground truth + derives clues), Minos (verifyCase proves G1–G3 — uniqueness, deductive completeness, fair-play), Anansesɛm (realizeNarrative writes the prose/dialogue surface), Loom (realizeAssets, optional — a text-only degraded pack when no asset realizer is injected), Daedalus (compileCase lowers the IR to V5 FV5* structs — first a draft so Theseus has game data to play), Theseus (checkInGameSolvable = G4; judgeCase is diagnostic), G5 independently calibrated human-aligned judge evidence, then the inline G6 safety and G7 canon-consistency checks, then G8 preregistered human-quality launch evidence, then decideRelease. Only on an all-eight-green decision does Daedalus re-compile for publish with the Python validate-cold-cases.py build gate enforced, after which recordProvenance and the telemetry session are written (pipeline.ts:127248).

Two details make this a real composition rather than a call list. First, the budget is reserved and spent at the stages that costreserve('tokens', 6000, 'clew') then spend('tokens', 4000, 'clew') around generation, reserve('asset-jobs', 8, 'loom') before realization (pipeline.ts:139, :162) — so the bill is accounted per stage, not estimated after the fact. Second, G6 is fail-loud by composition: requireSafetyClear throws on flagged or unscanned content, and the pipeline wraps it so any throw simply leaves g6 = false and blocks publish (pipeline.ts:199205) — a refusal, never a silent pass.

flowchart TD SPEC["CaseSpec (seed, cell, difficulty)"] --> CLEW subgraph forge["runPipeline — apps/v8/loom-service/src/pipeline.ts"] direction TB CLEW["Clew · generateCase<br/>ground truth + clue derivation"] MINOS["Minos · verifyCase<br/>G1 uniqueness · G2 completeness · G3 fair-play"] ANAN["Anansesɛm · realizeNarrative<br/>prose · dialogue · banter"] LOOM["Loom · realizeAssets (optional)<br/>text-only degraded pack otherwise"] DRAFT["Daedalus · compileCase (DRAFT)<br/>FV5* structs · skip python gate"] THE["Theseus · G4 in-game solvable · diagnostic judge"] G5["G5 calibrated human-aligned judge evidence"] G67["G6 safety (fail-loud) · G7 canon"] G8["G8 preregistered human-quality launch evidence"] DEC{"decideRelease — all 8 green?"} PUB["Daedalus · compileCase (PUBLISH)<br/>v2 manifest + validate-cold-cases.py"] CLEW --> MINOS --> ANAN --> LOOM --> DRAFT --> THE --> G5 --> G67 --> G8 --> DEC DEC -- "no" --> BLK["422 + blockingReasons · nothing cached"] DEC -- "yes" --> PUB end PUB --> PROV["recordProvenance + telemetry session"] subgraph spine["@yemaya/case-pipeline — Stage-11 spine (wraps every stage)"] BUD["budget reserve/spend"] JRN["journal (monotonic seq)"] TEL["telemetry + drift"] SAFE["safety seam (G6)"] PROVX["provenance (C2PA)"] end forge --- BUD & JRN & TEL & SAFE & PROVX

The Loom relay and the offline acceptance run#

At run time the UE5 client never holds provider keys — it reaches the pipeline through the Loom relay (apps/v8/loom-service/src/server.ts:40): POST /v8/cases/mint runs runPipeline and returns the compiled pack only if all eight ReleaseDecision gates pass (HTTP 200 with the manifest, or 422 with the blocking reasons; nothing is cached on a block). The decisive proof that this composition is real and not aspirational is buildOfflineDeps (deps.ts:44): the zero- credential path that wires the deterministic Clew core, a text-only asset pack, the local KeywordSafetyScanner, a createCaseBudget of 200k tokens / 50 asset- jobs, and an in-memory StageJournal + TelemetryAggregator. The §12.4 acceptance test drives exactly that — it mints case seed 5 and asserts result.publish === true with all of G1–G8 green, deterministically, with no provider keys (pipeline.test.ts:28). The production deps swap in a live LLM proposer, the real Isis asset realizer, the V7 Sekhmet scanner, and canon-graph retrieval behind the same interfaces.

Trustworthy by construction: determinism, cost, governance#

Determinism and replay#

The reproducibility contract is that the same (seed, canon, model-versions) reproduces a case bit-for-bit at the IR level. This is both a provenance guarantee and a cost lever — a popular case is minted and gated once, then served from cache many times. canonicalize (determinism.ts:36) produces a structurally-stable, JSON-safe view: Maps become key-sorted [key, value] pairs, Sets sort, object keys sort, and the wall-clock VOLATILE_KEYS (createdAt/updatedAt/analyzedAt/atIso) are dropped — so timestamp noise is excluded from the IR identity. reproKeyOf (:119) folds the seed, the provider-sorted model versions, and the canon hash into an FNV-1a-64 key (fnv1a64, :99). The teeth are in assertReproducible (:188): it re-compareIrs two runs and throws with the first divergent IR path (e.g. clues[0][1].description) if they disagree — a real replay-failure signal, not a swallowed boolean. That assertion ships as the reproduce CLI, which regenerates a case twice and exits non-zero on divergence (reproduce-cli.ts:52).

Cost discipline and the three-tier governor#

createCaseBudget (budget.ts:170) implements the contracts' CaseBudget surface over @yemaya/budget-management's ComputeBudgetManager, tracking two resources — tokens and asset-jobs. Its load-bearing invariant is no silent overrun: reserve (:78) is a soft hold computed against ceiling − committed − heldTotal, so two stages reserving in parallel cannot jointly over-commit, and spend (:104) clamps the commit to remaining headroom — an over-budget spend records only what fits and tags the commit (clamped from N). Over budget means upstream degrades (fewer bespoke assets), never a quiet overshoot. Above per-case budgets sits the BudgetGovernor (:215), which 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(playerId) (:290) answers whether the Oracle may admit one more mint under the player+global envelopes — the exact signal the mint-ahead queue honours. The governor is tested (pipeline.test.ts:83 drives a spend that fits per-case but blows the global ceiling, and a throttle that refuses minting two jobs with one left); the honest note is that runPipeline uses the per-case budget today, with the three-tier envelope as the production wrap.

Governance and safety: two gate framings, one fail-loud seam#

V8 has two distinct gate contracts. The runtime Loom pipeline uses the eight-gate ReleaseDecision, ordered by the pipeline stage that produces each verdict (Minos uniqueness → … → Sekhmet safety → canon → preregistered human-quality launch evidence). The platform suite @oshun/v8-case-gates is a different numbering, ordered by kind of check, and registers seven real GateDefinitions on the shared @oshun/content-release-gates service — composed, not forked. buildV8CaseGates (case-gates/src/index.ts:99) builds G1 fair-play, G2 solvability, G3 clue-grounding (which reuses the shared @oshun/content-quality-judge createGroundingGate so "every cited clue exists" is the same grounding machine the rest of the platform uses), G4 voice- distinctiveness, G5 bounded misdirection, G6 prose, and G7 safety; evaluateV8Case (:183) returns cleared only when all seven pass. That suite earns its keep in the C2PA bundle forge (@oshun/v8-case-bundle), which proves uniqueness before any generation (throwing CaseNotSolvableError), Ed25519-signs every produced asset through the shared @oshun/content-signing signer, and runs the assembled case through evaluateV8Case.

The safety seam itself is the clearest expression of "fail loud beats fake success." requireSafetyClear (safety.ts:48) throws in two cases — when no scanner is configured (refusing unscanned content) and when the scanner reports flags. The offline KeywordSafetyScanner (:123) is a genuine word-boundary scan over a curated abuse / hate-slur / real-person-defamation / self-harm / doxxing lexicon (:82), never always-clear; production routes the same seam to V7 Sekhmet's multimodal classifiers.

HITL, provenance, and multiplayer canon#

Three more disciplines round out the governance envelope. HITLInMemoryHitlQueue (hitl.ts:61) — is a real pending/resolved queue for the three escalation reasons (repair-escalation, commissioned, flagged-content), each item carrying the hidden CaseGroundTruth and the Minos verifier-report id so a reviewer sees what the machine saw. ProvenancerecordProvenance (provenance.ts) — binds a published case to its repro key and captures every asset's claim handle, generator, digest, hashScope, and signed state from the manifest (not a sample), with minted/reused counts. Loom's current locator digest remains explicitly unsigned until a downstream byte signer upgrades it; runPipeline records that honest boundary on every mint. Multiplayer canonarbitrateWriteback (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 (real, deferred from the run time)#

Localization in V8 has one non-negotiable rule: the deduction logic is locale- invariant. localizeStrings (localize.ts:63) translates only display strings — each carrying a stable id from the writers' room — and never the symbolic IR (clue logic, solution, constraints), so a case solves identically in every locale. The source locale (en-US) is the identity; any other locale requires a translator and refuses to emit untranslated text claiming to be localized (:70) — the fail-loud seam where a live MT/LLM provider plugs in, with a real deterministic PseudoLocaleTranslator (:36) covering the offline i18n path. assertLocaleInvariant (:84) throws if two bundles cover different string-id sets — i.e. if localization dropped or invented a string, which would mean it touched the logic.

The accessibility pass derives everything from real case structure, never invented: generateCaptions (accessibility.ts:27) emits a speaker-tagged, sequence-ordered caption per VO line; assignEvidenceCues (:68) gives each clue category a distinct shape + fill pattern so evidence is distinguishable without colour; generateHints (:87) builds progressive hints straight from the solution's deduction path — each level reveals exactly one more step — so a stuck player is helped without being handed the answer; and dyslexiaFriendly (:103) segments at clause boundaries without changing the words. The honest boundary, stated plainly: @yemaya/case-localization is green (8/8) but unimported by apps/v8 — it is a real library not yet on the runtime path, and the live translator is the injected provider boundary. This is the same "deliberately deferred motion" posture V9 takes with its own P3 localization.

Observability: journal, telemetry, drift, load#

An operator's view of V8 is built from three real primitives. The journal (StageJournal.emit, journal.ts:88) writes each stage transition to both a durable JournalStore and a live EventBus, stamping a strictly-increasing seq per (caseId, seed) run; replayRun (:122) returns the run in seq order for audit. The in-memory default satisfies both interfaces; Postgres + Redis are the production swaps. Telemetry (TelemetryAggregator, telemetry.ts:63) records how shipped cases actually play — solve-rate, the Brilliant/Good/Doubtful/Wrong rating distribution, a weighted mean-quality, and a ten-bucket 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 — it refuses to fall back to wall-clock Date.now (:4251) — and throws if a minCasesPerSecond SLA is missed.

How a case refuses to ship#

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

  • Non-deterministic regenerationassertReproducible throws with the first divergent IR path (determinism.ts:188); the reproduce CLI exits non-zero.
  • Mint budget exhausted → an over-ceiling spend clamps to headroom rather than overrunning (budget.ts:114); the BudgetGovernor denies and names the blocking tier (:256).
  • Unscanned or flagged contentrequireSafetyClear throws, leaving G6 red and blocking publish (safety.ts:48, pipeline.ts:199).
  • Untranslatable localelocalizeStrings refuses untranslated text when no translator is configured (localize.ts:70); a logic-touching localization trips assertLocaleInvariant.
  • Competing co-op writebacksarbitrateWriteback commits exactly the leader's outcome with a deterministic tiebreak (multiplayer.ts:38).
  • Any gate reddecideRelease blocks publish, the Loom relay returns 422 with the blocking reasons, and nothing is cached (server.ts:46).