# Contracts (libs/contracts)

`libs/contracts/` is the Zod contract surface: the single, typed source of truth
that every boundary in the platform validates against. It answers a narrow but
load-bearing question — when the BFF, a domain orchestration library, and a
Vitest spec all need to agree on what a "ritual check-in," a "grounded answer,"
or an "embodied-instruction demonstration request" _is_, where does that shape
live so the three of them cannot drift apart? The answer is one package,
`@oshun/contracts`, whose source tree groups its schemas into **23 first-level
domain directories** under `libs/contracts/src/` (the platform overview rounds
this to "two dozen"), each a barrel `index.ts`, most of them re-exported through
the package root `libs/contracts/src/index.ts`. The flagship is Tara: its
canonical contracts file `libs/contracts/src/tara/index.ts` is **2,688 lines**
of schemas, refinements, and shipped reference datasets, and it is wired into
the root barrel by a single `export * from './tara'` (`index.ts:25`).

What makes this tree the _contract_ layer rather than another library is a
property the build enforces and a grep can confirm: **it depends on nothing but
Zod.** Across all of `libs/contracts/src/`, production code imports `zod` (238
import sites) and _nothing else domain-shaped_ — there is not one
`from '@oshun/...'` import of a non-contracts package, no database driver, no
HTTP client (`package.json` lists exactly one dependency: `zod`). That purity is
the point: a schema can be imported by a UE-adjacent TypeScript client, by the
Fastify BFF, and by a `*.spec.ts` test alike without dragging in a Postgres
pool. The contracts surface is imported across 25+ areas of the monorepo —
`apps/isis` and `libs/isis` lead, then `apps/oshun` (the BFF), `apps/oya`,
`libs/oshun`, `libs/metis`, `libs/nyx`, `libs/shared`, and more — which is
exactly the write-once/compose-everywhere discipline the rest of the platform
follows. This page maps that surface; it is one slab of the stack described in
[the platform overview](./overview.md).

## What ships, honestly

The contract surface is **implemented and hard-tested**, not a scaffold. The
canonical conformance spec, `libs/contracts/src/contracts.spec.ts`, is a single
~352 KB file that calls `safeParse` **263 times** and `parse` **78 times**, and
— crucially — it asserts both the accepting and the rejecting case (every domain
has `expect(Schema.safeParse(good).success).toBe(true)` paired with
`...(bad).success).toBe(false)`), so the tests would fail against a schema that
accepted everything. That is the difference between a validator and a
shape-shaped placeholder.

Maturity is uneven across the 23 domains, and this page labels the differences
in the spirit of the repository's no-stub culture and the docs center's
_implemented / spec-only / provider-gated_ convention:

- **`common` is enormous and central** — **161 source modules** with **125
  colocated test files** (108 `.spec.ts` + 17 `.test.ts`). It is the shared
  vocabulary every other domain composes (primitives, consent, residency,
  evidence packs, grounded answers).
- **The V1 customer domains are deep** — `tara` (2,688 lines), `arete`,
  `nisaba`, `nyx`, `metis`, `veritas` each carry genuine domain logic
  (state-machine transition tables, `superRefine` invariants, shipped reference
  datasets), documented per-domain on the V1 feature pages.
- **`v3`, `v6`, `v9` are product-version namespaces** — sub-trees of many
  modules (`v3` alone re-exports `lilith`, `tara`, `saraswati`, `commons`,
  `consent`, `fixtures`, `registry`, `openapi`), namespaced under `V3Contracts`
  / `V6Contracts` / `V9Contracts` to keep their symbols from colliding with the
  V1 flat exports.
- **A handful are single-file barrels** — `atelier`, `identity`, `library`,
  `messaging`, `studio` are one `src` file each with no colocated spec, the
  smaller/younger surfaces of the tree. They are honest typed vocabularies, not
  fabricated depth, and this page does not imply otherwise.

One more honest seam: **not every wire shape lives in `contracts`.** The package
is the canonical _domain_ vocabulary, but individual BFF routes still define
local request DTOs inline when a shape is route-specific — e.g.
`RecordTurnRequestSchema` is a local `z.object` in
`apps/oshun/bff/src/routes/metis-tutor-memory.ts:40`, and `SubmitRunSchema` is
local to `agentic-runs-lifecycle.ts:26`. Contracts is the shared, cross-boundary
vocabulary; it is not a registry of every payload the platform parses.

## The shape of the tree — 23 domains

Enumerated so the count is verifiable, grouped by role:

| Group                          | Domains                                                                 |
| ------------------------------ | ----------------------------------------------------------------------- |
| **Shared vocabulary**          | `common` (161 modules)                                                  |
| **V1 customer domains**        | `tara`, `arete`, `nisaba`, `nyx`, `metis`, `veritas`                    |
| **Cross-cutting substrate**    | `events`, `llm`, `iris`, `aja`, `tts`, `living-scene`                   |
| **Agentic · studio · admin**   | `agent`, `studio`, `atelier`, `identity`, `library`, `messaging`, `oya` |
| **Product-version namespaces** | `v3`, `v6`, `v9`                                                        |

That is 1 + 6 + 6 + 7 + 3 = **23** directories, each with its own `index.ts`.
The groupings are descriptive, not a build boundary — the build boundary is the
dependency direction (contracts depends only on Zod; everything else depends on
contracts).

## The re-export pattern — one barrel, several resolution styles

`libs/contracts/src/index.ts` is the package root barrel, and it does **not**
treat all 23 domains identically. There are five resolution styles, and the
choice between them is driven by symbol-collision risk, because many domains
define the same common names (a `Vector3`, a `Capability`, a `Safety*` enum).

- **Flat `export *`** (7 domains: `common`, `events`, `llm`, `aja`, `arete`,
  `tara`, `nyx`). Their symbols land directly on the package root, so
  `import { CheckInSchema } from '@oshun/contracts'` works.
- **Flat _and_ namespaced** (4: `v3`, `v6`, `v9`, `iris`). Re-exported both with
  `export *` and as `export * as V3Contracts` (`index.ts:28-37`), so a caller
  can reach a V3 symbol flat or disambiguate it under `V3Contracts.*`.
- **Namespaced with selective prefixed names** (4: `living-scene`, `nisaba`,
  `metis`, `veritas`). These get an `export * as XContracts` _plus_ a curated
  list of individually re-exported, prefix-renamed symbols — e.g.
  `ClaimSchema as VeritasClaimSchema` (`index.ts:135`),
  `PassageSchema as NisabaPassageSchema` (`index.ts:81`) — so the common nouns
  ("Claim," "Passage," "Score") don't collide at the root.
- **Namespace-only** (2: `agent`, `oya`). Reachable _only_ as `AgentContracts.*`
  / `OyaContracts.*`. The Oya comment spells out the reason in code
  (`index.ts:176-179`): it is namespaced "to avoid top-level collisions with
  other domains (e.g. aja's Quaternion/Vector3, the many
  Safety*/Privacy*/Capability\* names)." That collision is real —
  `QuaternionSchema` is defined in `libs/contracts/src/aja/primitives.ts:17`.
- **Subpath-only — absent from the root barrel** (6: `atelier`, `identity`,
  `library`, `messaging`, `studio`, `tts`). These are _not_ re-exported from
  `index.ts` at all; you reach them only through their per-domain entry point,
  `@oshun/contracts/tts`, `@oshun/contracts/identity`, and so on.

Those per-domain entry points are declared twice, and both must agree. The
`package.json` `exports` map pins `"./tara"`, `"./common"`, `"./oya"`, the
nested `"./tts/providers"`, `"./tara/playback-rate"`, etc. to their source
files, and `tsconfig.base.json` carries the matching path mappings —
`"@oshun/contracts": ["libs/contracts/src/index.ts"]` (`:1421`),
`"@oshun/contracts/tara": ["libs/contracts/src/tara/index.ts"]` (`:1443`) — with
a catch-all `"@oshun/contracts/*": ["libs/contracts/src/*"]` (`:1451`)
backstopping any subpath the explicit list misses. (The two lists are _not_
perfectly in sync — `v9`, `agent`, and `living-scene` have no dedicated subpath
export and ride the catch-all and/or the root namespace instead — a real, minor
drift worth knowing when you import them.)

```mermaid
flowchart LR
  subgraph src["libs/contracts/src"]
    direction TB
    IDX["index.ts (root barrel)"]
    FLAT["flat export * · common, events, llm,\naja, arete, tara, nyx"]
    NS["export * as XContracts · v3/v6/v9, iris,\nliving-scene, nisaba, metis, veritas, agent, oya"]
    SUB["subpath-only · atelier, identity, library,\nmessaging, studio, tts"]
    IDX --- FLAT
    IDX --- NS
  end
  ROOT["@oshun/contracts"]
  PATH["@oshun/contracts/&lt;domain&gt;"]
  IDX --> ROOT
  FLAT -. flat names .-> ROOT
  NS -. namespaced .-> ROOT
  SUB --> PATH
  ROOT --> CONSUMERS["BFF · domain libs · specs · UE-adjacent TS"]
  PATH --> CONSUMERS
```

## A schema, end to end

The clearest way to see "the same shape in three places" is to follow one
schema. Take `CheckInSchema` from `libs/contracts/src/arete/index.ts:460`. It is
not a CRUD record — its `superRefine` (`:480-530`) encodes Arete's humane-habit
logic as machine-checked invariants:

- a `done` check-in _must_ report `engagementPercent === 100` (`:481`);
- a `partial` check-in _must_ be strictly between 0 and 100 (`:489-498`);
- a `skip`, `decline`, or `miss` _must_ be `0%` (`:500-509`);
- a `skip` or `decline` _must_ carry a user-visible `statusReason` (`:511-520`);
- and `streakTreatment` _must_ equal `getCheckInStreakTreatment(status)`
  (`:522-529`) — so a `decline` cannot silently break a streak the way a `miss`
  does. The humane-streak promise is enforced by the type, not by a downstream
  service remembering to be kind.

That one schema is consumed, unchanged, at three boundaries:

**1 · At the BFF boundary** — the gateway parses the request body and _fails
loud_ on a bad shape. The canonical pattern, from a contracts-sourced schema:

```ts
// apps/oshun/bff/src/routes/data-export.ts
import { CustomerDataExportCreateRequestSchema } from '@oshun/contracts'; // :4-7
const parsed = CustomerDataExportCreateRequestSchema.safeParse(
  request.body ?? {}
); // :84
```

and the fail-loud branch, from the agentic-run route
(`agentic-runs-lifecycle.ts:72-80`):

```ts
const parsed = SubmitRunSchema.safeParse(request.body);
if (!parsed.success) {
  reply.code(400).send({
    message: 'Agent run submission failed validation',
    reason: 'invalid_agent_run_submission',
    issues: parsed.error.issues, // the Zod issues, surfaced to the caller
  });
  return;
}
```

The route's own header comment states the contract-layer ethos plainly: it
"never fabricates a completion" (`:16`). A malformed payload becomes a `400`
with structured issues, not a half-built record three hops downstream.

**2 · In the domain library** — the same schemas validate at the service seam,
and here a failure `throw`s rather than returning a `400`. The Aja embodiment
adapter parses **both sides** of its boundary against the contracts surface,
importing from the per-domain subpath `@oshun/contracts/aja/index.js`
(`libs/oshun/embodiment-aja/src/canonical-adapter.ts:9`):

```ts
const request  = EmbodiedInstructionDemonstrationRequestSchema.parse(input);          // :45
return           EmbodiedInstructionDemonstrationResponseSchema.parse(await adapter…); // :46
```

The request _and_ the response are held to the same contract, so a misbehaving
backend is caught at the seam, not passed through. The pattern recurs across the
domain tree: `SourceSetSchema.parse(input)` in
`libs/oshun/evidence-sophia/src/source-set.ts:24`, and
`ContinuationTokenSchema.parse(token)` in
`libs/oshun/memory-iris/src/continuity/protocol.ts:52`.

**3 · In the Vitest spec** — `contracts.spec.ts` imports those exact symbols
from the package root barrel (`} from './index';`, `contracts.spec.ts:208`) and
the subpath where needed (`MemoryScopeKeySchema from './iris/index'`, `:209`),
then exercises each one against known-good and known-bad fixtures. Because the
spec imports the _same_ schema object the BFF and the domain lib import, a
change that would break a consumer breaks the test first.

```mermaid
sequenceDiagram
  participant Client
  participant BFF as BFF route
  participant Schema as @oshun/contracts schema
  participant Domain as domain lib
  Client->>BFF: POST body
  BFF->>Schema: Schema.safeParse(body)
  alt invalid
    Schema-->>BFF: { success:false, error.issues }
    BFF-->>Client: 400 + issues (fail loud)
  else valid
    Schema-->>BFF: { success:true, data }
    BFF->>Domain: typed data
    Domain->>Schema: Schema.parse(input) / parse(response)
    Note over Schema,Domain: throws on drift — same shape, both sides
  end
```

## "Validate at the boundary, fail loud" — what it means here

The contracts layer is **types, refinements, and canonical datasets** — it holds
_no behavior_. Its enforcement power comes entirely from a caller invoking
`parse`/`safeParse` at a boundary. That gives the platform two complementary
fail-loud modes:

- **`safeParse` at an untrusted edge** (the BFF parsing a client body) returns a
  discriminated `{ success, data | error }`, so the route can answer `400` with
  `error.issues` and never construct a partial entity.
- **`parse` at an internal seam** (a domain adapter validating a backend
  response) `throw`s a `ZodError`, surfacing drift as an exception rather than a
  silently-wrong object.

Underneath both, the `superRefine` guards make whole classes of illegal state
_unrepresentable_: Arete's check-in invariants above; Tara's mood taxonomy,
where a `distressLevel: 'high'` mood _must_ set `crisisHandoff.required: true`
(so you cannot ship a high-distress mood without a crisis route); and Tara's
`RitualSession`, whose `validateSessionTimeline` refinement rejects a session
whose declared `state` contradicts its own event log. These are documented in
depth on [the Tara feature page](../../V1/features/domain-tara.md); the point
for the platform is that the _rule_ lives in the shared schema, so every
consumer inherits it at once.

The honest limit: contracts cannot force a boundary to call it. A route that
forgets to `safeParse` is unguarded — the discipline is convention plus the
conformance spec, not a runtime that intercepts every payload. The platform's
answer is that the schema is the cheapest possible thing to reach for (`zod` is
the only dependency) and the spec makes its absence visible.

## `common`: the shared vocabulary the whole platform reads

`common` is the largest domain because it is the one every other domain — and
even parts of the shared infrastructure — composes. At the bottom sit the
primitives (`common/primitives.ts`): `UUIDSchema` (`z.string().uuid()`),
`SlugSchema` (a URL-safe regex), `TimestampSchema` (`z.string().datetime()`),
`PaginationRequestSchema`, and the `Timestamps` / `SoftDelete` mixins that every
entity spreads. Above those it carries the cross-domain models — consent,
evidence packs, grounded answers, citations, customer data export/deletion, and
the `canonical-audit-event` — that no single product should own.

`common` is also the one place the dependency direction is allowed to _reverse_:
two `libs/shared/` privacy packages read its rule tables.
`@oshun/data-residency` imports `OSHUN_DATA_RESIDENCY_RULES`
(`common/data-residency-rules.ts:402`) over the residency-zone vocabulary
`OshunResidencyZone` = `eu | uk | us | ca | latam | apac | global` (`:86`), and
evaluates a proposed cross-zone transfer against it. That is the single
sanctioned reach from infrastructure into the contract vocabulary — a residency
_zone_ is data, not a domain — and it is documented from the other side in
[Shared Libraries](./shared-libraries.md) and
[Persistence & Data](./persistence-data.md).

## `events`: two envelopes, one vocabulary

The `events` domain is worth separating from the runtime event bus it serves.
`events/envelope.ts` defines the **Zod validation envelope** —
`EventSourceSchema` enumerates 13 origins (`tara`, `isis`, `sophia`, `hathor`,
`bellona`, `yemaya`, `lilith`, `aphrodite`, `nyx`, `psyche`, `veritas`,
`concordia`, `system`), `EventPrioritySchema` the four levels — and
`events/index.ts` re-exports a typed payload schema per domain (`isis`,
`sophia`, `hathor`, … `concordia`). This is the _shape_ an event must satisfy.
The _transport_ — the `EventEnvelope` interface, Redis pub/sub delivery,
retry/dead-letter, signed outbound webhooks — lives in `@oshun/event-bus` under
`libs/shared/` and is documented in [Shared Libraries](./shared-libraries.md).
Contracts says what a `isis.asset.generated` payload _is_; the event bus carries
it. Keeping the two separate is what lets a producer validate a payload with
zero dependency on the bus implementation.

## How this foundation connects to the others

The contracts tree sits one layer above the shared infrastructure and one below
domain orchestration, and its edges are deliberate:

- **Down into shared infrastructure** — almost nothing; contracts depends only
  on Zod. The only reach is _inbound_: `@oshun/data-residency` and
  `@oshun/deletion-fanout` read `common`'s rule/zone tables. See
  [Shared Libraries](./shared-libraries.md).
- **Up into domain orchestration** — `libs/oshun/` parses at its seams against
  these schemas (`evidence-sophia`, `memory-iris`, `embodiment-aja` above). The
  domain libraries turn a validated shape into product behavior. See
  [Domain Orchestration](./oshun-domain-libraries.md).
- **Sideways into the BFF** — the gateway `safeParse`s request bodies against
  contracts request schemas (and its own route-local DTOs) before composing
  domain calls. See [BFF & Gateway](./bff-gateway.md).
- **Into persistence** — `common`'s `contract-prisma-alignment` module exists to
  keep the Zod shapes and the stored schema in step; the residency and deletion
  vocabularies the data layer enforces are contract tables. See
  [Persistence & Data](./persistence-data.md) and
  [Auth & Identity](./auth-identity.md) for the `identity` domain's consent and
  session shapes.

Because the contract is written once and imported everywhere, a change to what a
ritual, a claim, or a residency rule _is_ becomes a single, type-checked edit
that the BFF, the domain library, and the test all see at the same moment —
which is the entire reason the contract surface is a shared foundation rather
than 23 private copies.

## Related

- [The Shared Platform](./overview.md) — the platform framing and layered model
  this page fills in.
- [Shared Libraries](./shared-libraries.md) — the `@oshun/*` infrastructure,
  including the residency/deletion packages that read `common`'s rule tables.
- [Domain Orchestration](./oshun-domain-libraries.md) — `libs/oshun`, the
  service layer that parses against these schemas at its seams.
- [BFF & Gateway](./bff-gateway.md) — the tier that `safeParse`s request bodies
  against the contract surface.
- [Persistence & Data](./persistence-data.md) and
  [Auth & Identity](./auth-identity.md) — the residency, deletion, consent, and
  identity vocabularies these contracts define.
- [Tara — Rituals and Contemplative Practice](../../V1/features/domain-tara.md)
  — the flagship 2,688-line contracts file, documented end to end. </content>
  </invoke>
