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.
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:
commonis 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,veritaseach carry genuine domain logic (state-machine transition tables,superRefineinvariants, shipped reference datasets), documented per-domain on the V1 feature pages. v3,v6,v9are product-version namespaces — sub-trees of many modules (v3alone re-exportslilith,tara,saraswati,commons,consent,fixtures,registry,openapi), namespaced underV3Contracts/V6Contracts/V9Contractsto keep their symbols from colliding with the V1 flat exports.- A handful are single-file barrels —
atelier,identity,library,messaging,studioare onesrcfile 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, soimport { CheckInSchema } from '@oshun/contracts'works. - Flat and namespaced (4:
v3,v6,v9,iris). Re-exported both withexport *and asexport * as V3Contracts(index.ts:28-37), so a caller can reach a V3 symbol flat or disambiguate it underV3Contracts.*. - Namespaced with selective prefixed names (4:
living-scene,nisaba,metis,veritas). These get anexport * as XContractsplus 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 asAgentContracts.*/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 —QuaternionSchemais defined inlibs/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 fromindex.tsat 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.)
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
donecheck-in must reportengagementPercent === 100(:481); - a
partialcheck-in must be strictly between 0 and 100 (:489-498); - a
skip,decline, ormissmust be0%(:500-509); - a
skipordeclinemust carry a user-visiblestatusReason(:511-520); - and
streakTreatmentmust equalgetCheckInStreakTreatment(status)(:522-529) — so adeclinecannot silently break a streak the way amissdoes. 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:
// 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):
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 throws 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):
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.
"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:
safeParseat an untrusted edge (the BFF parsing a client body) returns a discriminated{ success, data | error }, so the route can answer400witherror.issuesand never construct a partial entity.parseat an internal seam (a domain adapter validating a backend response)throws aZodError, 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; 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 and
Persistence & Data.
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.
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-residencyand@oshun/deletion-fanoutreadcommon's rule/zone tables. See Shared Libraries. - Up into domain orchestration —
libs/oshun/parses at its seams against these schemas (evidence-sophia,memory-iris,embodiment-ajaabove). The domain libraries turn a validated shape into product behavior. See Domain Orchestration. - Sideways into the BFF — the gateway
safeParses request bodies against contracts request schemas (and its own route-local DTOs) before composing domain calls. See BFF & Gateway. - Into persistence —
common'scontract-prisma-alignmentmodule 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 and Auth & Identity for theidentitydomain'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 — the platform framing and layered model this page fills in.
- Shared Libraries — the
@oshun/*infrastructure, including the residency/deletion packages that readcommon's rule tables. - Domain Orchestration —
libs/oshun, the service layer that parses against these schemas at its seams. - BFF & Gateway — the tier that
safeParses request bodies against the contract surface. - Persistence & Data and Auth & Identity — the residency, deletion, consent, and identity vocabularies these contracts define.
- Tara — Rituals and Contemplative Practice — the flagship 2,688-line contracts file, documented end to end.