# Canonical Data Contracts

```mermaid
classDiagram
  class CaseSkeleton {
    +entities
    +domains
    +constraints
    +uniqueSolution
  }
  class NarrativeCase {
    +suspects
    +timeline
    +clues
    +redHerrings
  }
  class GateReport {
    +solvable
    +unique
    +fair
    +assetComplete
  }
  class CaseBundle {
    +manifest
    +provenance
    +signature
  }
  CaseSkeleton --> NarrativeCase : constrains dressing
  NarrativeCase --> GateReport : evaluated as
  GateReport --> CaseBundle : permits only on pass
  CaseBundle *-- CaseSkeleton
  CaseBundle *-- NarrativeCase
```

The skeleton remains embedded in the shipped bundle so presentation cannot erase
the proof object that constrained it. A gate report is an authorization
boundary: only a fully passing report permits the narrative and its provenance
to become a signed delivery bundle.

V8's whole thesis — _the LLM proposes; a constraint solver disposes_ — only
holds if there is a precise, typed boundary between the two layers, and that
boundary is a set of **canonical data contracts**: the typed shapes for a case —
its solvable skeleton, its clues, its suspects and alibis, its proven solution,
and the gated, signed bundle that ships — that every stage of the pipeline
agrees on. Because the skeleton is machine-checked and the surface is
regenerated, the contract is what lets a constraint solver, a release-gate
suite, an LLM prose writer, and a C2PA signer all touch the same case without
any one of them being able to redefine what "solved," "fair," or "presented to
the player" means. The solver cannot fabricate a unique solution; the gate suite
cannot pass a case that solves on a withheld clue; the bundle cannot ship an
asset it did not really produce. Each refusal is enforced at a contract seam.
This page is the contract-side companion to the V8 architecture set; the
monolith hub is [../V8_ARCHITECTURE.md](../V8_ARCHITECTURE.md).

The defining structural fact — and the first thing to get right — is **where the
contracts live**. They are _not_ in the central Zod contracts surface: there is
no `libs/contracts/src/v8` directory (verified absent), and
[the platform contracts page](../../platform/contracts.html) lists product
namespaces for `v3`, `v6`, and `v9` but **none for v8**. V8's canonical shapes
are instead **co-located in the domain libraries themselves**, as plain
TypeScript `interface`/`type` declarations rather than `zod` schemas. The
self-contained, end-to-end-tested spine is the three `libs/v8/*` packages this
page is built around: `@oshun/v8-case-csp` (the constraint shapes + the prover),
`@oshun/v8-case-gates` (the case shape + the seven release gates), and
`@oshun/v8-case-bundle` (the forged, signed delivery bundle). That divergence
from V3/V6/V9 is deliberate, not an oversight, and the section below states it
plainly.

## What ships, honestly

Three things are true at once, and a reader is owed all three.

**Implemented and hard-tested (real today).** The three `libs/v8/*` packages are
genuine, composed, and exercised by specs that assert _correctness_, not just
shape. `@oshun/v8-case-csp` is a real finite-domain constraint solver —
backtracking search with node consistency, minimum-remaining-values ordering,
and forward checking (`csp-solver.ts:212`, `solveCsp`) — whose spec anchors
against the canonical **Zebra puzzle** answer, a known-correct result a stub
could not fake (the doc-comment at `csp-solver.ts:12-16` states this, and
`csp-solver.spec.ts` proves it). The mystery layer's spec drives a
four-dimension murder case to a single named solution and **rejects** the
under-determined, over-constrained, and wrong-culprit variants
(`mystery-skeleton.spec.ts:100-175`). The gate suite spec asserts the registered
gate ids are _exactly_ `g1:fairness … g7:safety` and walks each block path
(`case-gates.spec.ts:30-114`). The bundle spec C2PA-signs each generated asset,
verifies it, and confirms a **tampered** digest or uri fails verification
(`case-bundle.spec.ts:123-167`). These contracts compose three real shared
platform packages — `@oshun/content-release-gates` (`ReleaseGateService`,
`gateFromEvalScore`, `gateFromManifestCheck`), `@oshun/content-quality-judge`
(`createGroundingGate`), and `@oshun/content-signing` (the Ed25519
signer/verifier + `sha256Hex`) — so the gating and signing are not forked
per-product.

**Provider-gated (fail-loud seams, not stubs).** A case's _surface_ — its prose,
suspect portraits, evidence images, music, voice-over — needs generation
providers. Those are injected boundaries, and their absence is reported, never
faked. With no prose writer wired, `forgeCaseBundle` throws
`CaseBundleNotConfiguredError` (`case-bundle.ts:241-243`) rather than inventing
a case; a media provider that does not support an asset kind yields a
`not-configured` asset slot carrying an honest `reason`, never fabricated bytes
(`case-bundle.ts:259-267`). The bundle's own doc-comment names the live
providers (Stability/Flux/Hunyuan3D/Meshy/Suno/ElevenLabs) as the `[~]`
remainder while the solvability proof, C2PA stamping, and gating are real
(`case-bundle.ts:11-15`).

**Spec-vs-code drift, named honestly.** The architecture monolith's §3 sketches
a _different_ contract vocabulary — `CaseSpec`, `CaseGroundTruth`, the
`MysterySession` IR, and the `FV5*` V5 compile target — and sites them in
`libs/yemaya/case-engine`. Those richer shapes _are_ real, but they live in a
**second, independent package**, `@yemaya/case-contracts`
(`libs/yemaya/case-contracts/src/`), and they are consumed by the §4 subsystem
family (`@yemaya/case-engine`, `case-verifier`, …), not by the `libs/v8/*` trio.
The two families share no imports in either direction (verified). The section
[The two contract tracks](#the-two-contract-tracks-reconciled) reconciles them
so no reader is misled about which "ground truth" or "alibi" a given file means.
In particular, the first-class `suspects` and `Alibi` _types_ the title implies
are named in `@yemaya/case-contracts`; in the tested `libs/v8/*` trio a suspect
and an alibi are modelled _structurally_, and that mapping is made explicit
below.

## The case-bundle contract

The terminal contract — what the pipeline actually emits — is the `CaseBundle`
(`libs/v8/case-bundle/src/case-bundle.ts:156`). It is the join of everything the
earlier stages produced into one gated, provenance-stamped object:

```ts
interface CaseBundle {
  readonly case: V8Case; // the gated detective case
  readonly uniqueness: CaseUniquenessResult; // the CSP verdict (proof, not claim)
  readonly assets: readonly CaseAssetSlot[]; // generated | not-configured, per request
  readonly evaluation: V8CaseGateResult; // the seven-gate report
  readonly blocked: boolean; // evaluation.status === 'blocked'
  readonly provenance: CaseBundleProvenance;
}
```

Two design choices in this shape carry the honesty of the whole product. First,
**`assets` is a discriminated union, not an array of URLs** (`CaseAssetSlot`,
`case-bundle.ts:85-95`): a slot is either
`{ status: 'generated', request, manifest }` or
`{ status: 'not-configured', request, reason }`. A consumer cannot read a `uri`
off a slot without first proving it generated — the type forces the
absent-provider case to be handled. Second, **`provenance` is a real audit
record** (`CaseBundleProvenance`, `:146`): it carries `uniqueSolutionProven`, a
`solutionHash` (the sha256 of the proven solution), the `generatedAssetCount`,
the explicit `notConfiguredKinds`, and the signer key id + public key PEM. The
`solutionHash` is computed over the actual proven assignment
(`case-bundle.ts:288`), so the provenance binds to _the case that was solved_,
not to a label asserting it was.

Each generated asset carries a `C2paAssetManifest` (`:72-83`): the
`contentSha256` of the produced bytes, the signature algorithm, the signer key
id, and an Ed25519 `signatureBase64` over a **canonical** manifest payload
(stable key order, `manifestPayload` at `:166`). `verifyCaseAssetManifest`
(`:215`) recomputes that canonical payload and checks the signature, so altering
any signed field — digest, uri, kind — flips verification to `false`; the spec
proves exactly this tamper detection (`case-bundle.spec.ts:147-167`). The forge
order itself is a contract invariant: `forgeCaseBundle` (`:236`) proves
uniqueness _first_ and throws `CaseNotSolvableError` before any writer or media
provider is called, and the spec asserts no prose was generated for an
unsolvable skeleton (`case-bundle.spec.ts:169-193`). The full forge is the
subject of
[./architectural-thesis-and-pipeline.md](./architectural-thesis-and-pipeline.md).

## The constraint, clue, suspect & alibi shapes

The case's _skeleton_ — the half that must be machine-checked — is the
`MysterySkeleton` (`libs/v8/case-csp/src/mystery-skeleton.ts:69`):

```ts
interface MysterySkeleton {
  readonly dimensions: Readonly<Record<string, readonly string[]>>; // each solution axis → its finite domain
  readonly groundTruth: Readonly<Record<string, string>>; // the intended answer: one value per dimension
  readonly clues: readonly MysteryClue[];
}
```

This is the structural answer to "suspects, alibis, the solution." A **suspect
is a value in the `culprit` dimension's domain**; the **solution is the
`groundTruth`** assignment over every dimension; and an **alibi is a clue** — an
`is-not` constraint that eliminates a suspect, or an `allowed`-tuple constraint
that ties a suspect to a place. A `MysteryClue` (`:60`) pairs a `clueId`, a
`visibility` of `'presented' | 'withheld'` (only presented clues prove
solvability), and a `MysteryConstraint` — the load-bearing discriminated union
(`:33-58`) with six kinds: `is`, `is-not`, `one-of`, `if-then`, `same`, and the
general `allowed` escape hatch (an explicit set of permitted tuples over a scope
of dimensions). That last kind is the honest way to express an arbitrary fair
deduction "without hand-coding a predicate the data can't see" (`:48-58`); the
spec uses it to encode suspect→location access and suspect→motive maps
(`mystery-skeleton.spec.ts:46-75`).

Underneath sits a domain-agnostic CSP layer (`csp-solver.ts`): `CspVariable`
(`:23`), the `CspConstraint` union (`:40` — `equals`, `not-equals`, `in`,
`all-different`, `implies`, and a general `relation`), and `CspModel` (`:55`).
`compileMysteryToCsp` (`mystery-skeleton.ts:136`) lowers a skeleton to a model,
**including only presented clues by default** — the model the player can
actually solve from. `proveUniqueSolution` (`csp-solver.ts:356`) finds up to two
solutions and returns a `UniquenessResult` (`:343`): `unsatisfiable` (0),
`unique` (1, with the witness), or `under-determined` (≥2). The mystery layer
wraps this into the contract every later stage reads, `CaseUniquenessResult`
(`:211`):

```ts
interface CaseUniquenessResult {
  readonly uniqueSolutionProven: boolean; // the single flag the G2 gate consumes
  readonly status: UniquenessStatus; // 'unique' | 'under-determined' | 'unsatisfiable'
  readonly solutionCount: 0 | 1 | 2;
  readonly solution?: Readonly<Record<string, string>>;
  readonly matchesGroundTruth: boolean;
  readonly reason?: string; // an honest one-line failure cause
}
```

The subtle, important field is `matchesGroundTruth`: `proveCaseUniqueness`
(`:239`) sets `uniqueSolutionProven` true _only_ when the presented clues admit
exactly one solution **and** that solution equals the author's `groundTruth`. A
case whose visible clues uniquely prove a _different_ culprit than the author
intended is a real generation bug, and the prover surfaces it (`reason` mentions
"ground truth") rather than asserting the case solved — the spec drives
precisely this butler-vs-doctor mismatch (`mystery-skeleton.spec.ts:154-174`).
The deep treatment of the solver and prover is
[./clew-minos-palimpsest-symbolic-core.md](./clew-minos-palimpsest-symbolic-core.md).

## The gate verdict shapes

Between the skeleton and the bundle sits the _gated case_, `V8Case`
(`libs/v8/case-gates/src/index.ts:52`), and its clue, `V8Clue` (`:42`). These
are deliberately a different shape from the CSP `MysteryClue`: where the CSP
clue carries a logical `constraint`, the gate clue carries `text`, a
`presentedBeforeReveal` flag, and an optional `redHerring` marker — the
_player-facing_ facts a fair-play checker needs. `V8Case` adds the
`solutionClueIds` (the clue ids the deductive chain depends on), the
`uniqueSolutionProven` flag (set from the CSP proof, never by hand), and three
0–100 scores (`voiceDistinctivenessScore`, `proseScore`, `safetyScore`).

`evaluateV8Case` (`:183`) registers seven gates on the shared
`ReleaseGateService` and returns a `V8CaseGateResult` (`:173`): a `status` of
`'cleared' | 'blocked'`, the full `ReleaseReport`, and the `blockedGateIds`. The
seven gates (`buildV8CaseGates`, `:99`) are the contract's fairness invariants
made executable:

| Gate                | Invariant (the contract refuses to ship a case that…)                             | Source of verdict                                   |
| ------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------- |
| `g1:fairness`       | …solves on a clue never presented to the player (every `solutionClueId` is shown) | manifest check (`:110`)                             |
| `g2:solvability`    | …has no proven unique solution (the §I.2 CSP flag)                                | manifest check over `uniqueSolutionProven` (`:123`) |
| `g3:clue-grounding` | …cites a clue absent from the case (invented evidence)                            | shared `createGroundingGate` (`:130`)               |
| `g4:voice`          | …has indistinct suspect voices (below `minVoice`, default 70)                     | `gateFromEvalScore` (`:139`)                        |
| `g5:misdirection`   | …has zero red herrings, or more than `maxRedHerrings` (an unfair maze)            | manifest check (`:145`)                             |
| `g6:prose`          | …reads below the prose bar (`minProse`, default 70)                               | `gateFromEvalScore` (`:159`)                        |
| `g7:safety`         | …falls below the safety bar (`minSafety`, default 90)                             | `gateFromEvalScore` (`:165`)                        |

`g3` is the one that reuses platform machinery most directly: its retriever
(`caseClueRetriever`, `:87`) reports a solution clue id `supported` iff it
exists in the case's clue set and `unsupported-claim` otherwise, so a solution
that points at a non-existent "phantom-clue" is blocked as invented evidence
(`case-gates.spec.ts:71-77`). The thresholds are overridable via
`V8CaseGateConfig` (`:70`). The clearest contract guarantee here is the
**cleared-iff-all-seven** rule: `status` is `'cleared'` only when the report
passed, and `blockedGateIds` lists every failing gate — the bundle's `blocked`
flag is just `evaluation.status === 'blocked'` (`case-bundle.ts:296`).

## Referential integrity across the shapes

The contracts are only canonical because their references resolve, and the
wiring between the three `libs/v8/*` packages is one acyclic chain — skeleton →
proof → case → bundle — with each edge a real value, not a duplicated literal:

- **CSP proof → gate flag.** `V8Case.uniqueSolutionProven` is never authored; it
  is `CaseUniquenessResult.uniqueSolutionProven` carried forward.
  `provenUniquenessFlag` (`mystery-skeleton.ts:290`) exists precisely so a
  caller can set it from the proof without `@oshun/v8-case-gates` importing the
  CSP package (composition at the call site, the pattern the buildable-lib rules
  require). The mystery spec proves the end-to-end path: a CSP-proven case
  clears `g2`, an under-determined one is blocked by it
  (`mystery-skeleton.spec.ts:253-272`).
- **Solution clues ⊆ case clues.** `g1:fairness` checks every `solutionClueId`
  resolves to a `presentedBeforeReveal` clue; `g3:clue-grounding` checks every
  `solutionClueId` exists at all. Between them, a `solutionClueIds` entry that
  dangles (no such clue) or that points at a withheld clue cannot pass.
- **Asset slot ⊆ requested assets.** Every `CaseAssetSlot` carries its
  originating `CaseAssetRequest`, and `forgeCaseBundle` walks
  `draft.assetRequests` one-to-one (`case-bundle.ts:258`), so the bundle's asset
  list is exactly the writer's request list — each either fulfilled-and-signed
  or honestly not-configured.
- **Manifest digest → real bytes.** `C2paAssetManifest.contentSha256` is the
  sha256 of the bytes the provider actually returned (`stampAsset`, `:184`); the
  signature is over the canonical payload of those fields, so provenance binds
  to content, not to a promise of it.

The one referential boundary that does _not_ exist is the cross-family one: the
`libs/v8/*` trio and `@yemaya/case-contracts` do not reference each other. That
is intentional — the tested vertical is self-contained — but it is also the seam
a reader must hold in mind, which is the next section.

## The two contract tracks, reconciled

V8 ships **two** real contract families, and they assign familiar names to
differently-shaped objects. Telling them apart is exactly the kind of drift this
page exists to flag (the V9 lesson-artifact contract page makes the same honest
move for its dual gate schemes).

**Track A — the tested vertical (`libs/v8/*`, `scope:v8`).** `MysterySkeleton` /
`MysteryClue` / `MysteryConstraint` (case-csp), `V8Case` / `V8Clue`
(case-gates), `CaseBundle` / `CaseAssetSlot` / `C2paAssetManifest`
(case-bundle). Plain TS interfaces, no Zod, composing the shared platform
gate/sign packages. This is the track with the clean
skeleton→prove→gate→sign→bundle test wiring, and the one this page centres on.

**Track B — the §3/§4 subsystem shapes (`@yemaya/case-contracts`).** This
package realizes the architecture monolith's §3 vocabulary almost verbatim, as
plain TS interfaces across ten interface modules (plus a barrel, a generated V5
target, and a test): `CaseSpec` (`case-spec.ts:18`), `CaseGroundTruth`
(`ground-truth.ts:156`) — which, unlike the CSP skeleton, names
`suspects: CharacterRef[]`, `alibis: Alibi[]`, and
`constraints: ConstraintClause[]` as first-class fields — and a real `Alibi`
(`ground-truth.ts:95`) whose `status` is
`'verified' | 'flawed' | 'unverifiable'` with the culprit's flawed alibi
carrying exactly one breaking `flawFactId`. Its `ir.ts` does what §3.3 instructs
— it **re-exports the existing validator's** `MysterySession`/`Clue`/`Solution`
types rather than redefining them (`ir.ts:40-54`), and `v5-target.ts` holds the
`FV5*` compile target with a real `deductionKindFromConfidence` mapping
(`v5-target.ts:31`). So when the title says "suspects" and "alibis," the _named_
types live here; the _proven_ logic lives in Track A. The two are parallel
realizations of one design, not rival implementations of one interface — and
**neither lives in `libs/contracts/src/v8`, which does not exist**.

The practical rule for a reader: if you are looking at the constraint solver, a
seven-gate report, or a C2PA-stamped bundle, you are in Track A (`libs/v8/*`);
if you are looking at `CaseSpec` difficulty bands, a named `Alibi`, the
`MysterySession` IR, or the V5 `FV5*` structs, you are in Track B
(`@yemaya/case-contracts`). The same word — "ground truth" — means a flat
`Record<dimension, value>` in the first and a `CaseGroundTruth` object with
suspects and alibis in the second.

## Connections

- The pipeline that fills these contracts, stage by stage:
  [./architectural-thesis-and-pipeline.md](./architectural-thesis-and-pipeline.md)
  — the forge order, the repair loop, and why solvability is proven before
  generation.
- The symbolic core that produces the `CaseUniquenessResult` and the proof the
  `g2` gate stands on:
  [./clew-minos-palimpsest-symbolic-core.md](./clew-minos-palimpsest-symbolic-core.md).
- Why these shapes are co-located in the domain libs rather than the central Zod
  surface — and what _does_ live in `libs/contracts/` (v3/v6/v9, not v8):
  [the platform contracts page](../../platform/contracts.html).
- The architecture hub and the honest build status:
  [../V8_ARCHITECTURE.md](../V8_ARCHITECTURE.md).
