Domain · Specifications

OpenAPI Domain — Technical Specifications

The openapi domain is the @oshun/openapi library at libs/openapi/.

23sections36 minread

On this page

Technical specification for @oshun/openapi: the centralized OpenAPI 3.1 specification registry, spec loaders, validators, breaking-change and drift detectors, code generators, typed API clients, and the per-domain spec-file inventory. Status: IMPLEMENTED — this document is grounded in the source under libs/openapi/.


Scope and Status#

The openapi domain is the @oshun/openapi library at libs/openapi/. It is a build-time / CI tooling library (package.json declares "private": true, version 0.1.0, "type": "module"). It is not loaded by production services at runtime — domain API servers consume the generated TypeScript types and the generated typed clients, not the YAML spec files.

The domain has three concerns:

  1. Spec storage — OpenAPI 3.1 YAML spec files under src/specs/, one per domain, plus the V2 companion spec under v2/.
  2. A typed registrySPEC_PATHS, SPEC_REGISTRY, and the ApiDomain union in src/utils/registry.ts catalog every spec with metadata.
  3. Tooling — fourteen tsx scripts under scripts/ that validate specs, detect breaking changes, detect spec-to-generated-type drift, generate TypeScript types, generate three families of specs from canonical Zod contracts, generate typed API clients, and build HTML documentation.

A second, unrelated directory exists at the repository root: openapi/. It holds two legacy standalone spec files (openapi/isis/openapi.yaml, openapi/lilith/openapi.yaml) plus two .backup-* files for the Lilith spec. These root-level files are not referenced by @oshun/openapi's registry, loaders, or scripts; the canonical specs all live under libs/openapi/src/specs/.


Package Structure#

The annotated tree below shows every file of significance. The scripts/ directory contains fourteen tsx scripts that can each be run directly via pnpm run <script-name>; the src/ directory holds the library code that scripts and consumers import.

text
libs/openapi/
├── package.json                 # @oshun/openapi, v0.1.0, private, ESM
├── project.json                 # Nx project: build + lint + test + 9 nx:run-commands targets
├── tsconfig.json                # base config, references lib + spec tsconfigs
├── tsconfig.lib.json             # build config, excludes specs/tests/generated
├── tsconfig.spec.json            # test config
├── vitest.config.ts              # vitest: node env, src/** + scripts/** spec globs, v8 coverage
├── nyx/
│   └── README.md                 # pointer doc for the Phase 21 Nyx API contract
├── v2/
│   └── companion.yaml            # V2 Companion Public API spec (served outside src/specs)
├── docs/                         # generated HTML docs output (9 domain subdirs + index.html)
├── scripts/                      # 14 tsx tooling scripts
│   ├── validate.ts               # OpenAPI 3.1 structural validator
│   ├── diff.ts                   # breaking-change detector vs a git base ref
│   ├── drift-check.ts            # YAML ↔ generated-TS drift detector (exports detectDrift)
│   ├── drift-check.spec.ts       # vitest suite for detectDrift
│   ├── generate.ts               # orchestrating type generator (runs all sub-generators)
│   ├── generate-docs.ts          # Redoc HTML documentation generator
│   ├── generate-calliope-spec.ts # Calliope spec generator from @calliope/core Zod schemas
│   ├── generate-oshun-v1-specs.ts    # Oshun V1 domain + BFF spec generator from Zod contracts
│   ├── generate-oshun-v1-api-clients.ts # Oshun V1 typed client package generator
│   ├── generate-v3-spec.ts       # V3 spec generator from @oshun/contracts/v3
│   ├── generate-v3-clients.ts    # V3 typed tenant client generator
│   └── validate-v3-spec.ts       # V3 spec validation via oas3-validator
└── src/
    ├── index.ts                  # public API barrel
    ├── openapi.spec.ts           # vitest suite for registry + loader + generators
    ├── utils/
    │   ├── loader.ts             # load / merge / resolve spec files
    │   └── registry.ts           # SpecMetadata, ApiDomain, SPEC_PATHS, SPEC_REGISTRY, helpers
    ├── oshun-v1/
    │   └── spec-builder.ts       # builds Oshun V1 specs + client resources from Zod contracts
    ├── v3-clients/               # generated V3 typed clients (do not edit by hand)
    │   ├── index.ts
    │   ├── base.ts               # V3 fetch transport + V3ApiError
    │   ├── lilith-platform.ts
    │   ├── tara-studio.ts
    │   ├── saraswati-stage.ts
    │   ├── lilith-commons.ts
    │   └── __tests__/client.spec.ts
    ├── generated/                # generated TypeScript types (do not edit by hand)
    │   ├── index.ts              # re-exports all domain type modules
    │   ├── lilith.ts   yemaya.ts   isis.ts     sophia.ts   hathor.ts
    │   ├── bellona.ts  nyx.ts      calliope.ts concordia.ts metis.ts
    │   ├── oshun-bff.ts  main.ts
    └── specs/                    # OpenAPI 3.1 YAML spec files
        ├── main.yaml             # legacy consolidated "AI Wisdom Platform API" spec
        ├── v3.yaml               # generated V3 contract spec
        ├── lilith/lilith-api.yaml
        ├── yemaya/yemaya-api.yaml
        ├── isis/isis-api.yaml
        ├── sophia/sophia-api.yaml
        ├── hathor/hathor-api.yaml
        ├── bellona/bellona-api.yaml
        ├── nyx/nyx-api.yaml
        ├── nyx/nyx-v1-contracts.yaml
        ├── calliope/calliope-api.yaml
        ├── concordia/concordia-api.yaml
        ├── tara/tara-v1-contracts.yaml
        ├── arete/arete-v1-contracts.yaml
        ├── veritas/veritas-v1-contracts.yaml
        ├── nisaba/nisaba-v1-contracts.yaml
        ├── metis/metis-v1-contracts.yaml
        └── oshun-bff/oshun-bff-v1-contracts.yaml

Note: the src/specs/ directory does not contain per-feature spec files like lilith/chat.yaml, yemaya/projects.yaml, isis/generation.yaml, or sophia/documents.yaml. Each implemented domain has a single <domain>-api.yaml spec; the Oshun V1 domains additionally have a generated <domain>-v1-contracts.yaml. The SPEC_PATHS constant declares several fine-grained and shared/* keys (see below) that have no backing file on disk yet — those are forward declarations, not present specs.


Public API#

src/index.ts exports three groups of symbols. Everything a downstream consumer or script needs comes through this barrel — direct imports into src/utils/ or src/oshun-v1/ are possible but not required.

Loader utilities (from ./utils/loader)#

These five functions handle all file I/O for spec documents. Consumers never need to read raw YAML themselves.

Export Kind Purpose
loadSpec function Async-read a YAML spec and parse it to an OpenAPIV3_1.Document
loadSpecSync function Synchronous variant for build scripts and generators
getSpecPath function Resolve a relative spec path to an absolute filesystem path
listSpecs function Recursively list all .yaml/.yml spec files
mergeSpecs function Merge a base document with additional documents into one

Registry and metadata (from ./utils/registry)#

These exports give programmatic access to the spec catalog. The two const objects are the primary sources of truth; the functions are convenience wrappers over them.

Export Kind Purpose
SPEC_PATHS const object Typed map (as const) of every spec file's relative path
SPEC_REGISTRY const object Record<string, SpecMetadata> of every registered spec
getSpecsByDomain function Filter SPEC_REGISTRY values by ApiDomain
getSpecsByTag function Filter SPEC_REGISTRY values by membership in tags
getDomainPaths function Return all relative spec paths for a SPEC_PATHS key
extractEndpoints function Flatten a document's paths into endpoint descriptors
extractSchemas function Flatten a document's components.schemas into descriptors
SpecMetadata type (export) Per-spec metadata interface
ApiDomain type (export) Union of all Oshun API domain identifiers

Oshun V1 spec builder (from ./oshun-v1/spec-builder)#

These exports are used exclusively by the generation scripts. Application code does not call them at runtime.

Export Kind Purpose
buildOshunV1SpecArtifacts function Build all V1 domain-service specs plus the BFF spec
getOshunV1ApiDomains function Return the frozen list of the six V1 API domains
getOshunV1ClientResources function Derive typed-client resource descriptors for a V1 domain
getOshunV1DomainSpecPath function Return the relative spec path for a V1 domain
getOshunV1SpecFilePaths function Return the relative spec paths of every V1 artifact
JsonObject type (export) Recursive plain-JSON object type used for spec documents
OshunV1ApiDomain type (export) Union of the six V1 API domains
OshunV1ClientResource type (export) A single REST resource a generated V1 client exposes
OshunV1SpecArtifact type (export) A generated spec: id, filePath, title, document

Re-exported third-party types#

OpenAPIV3_1, OpenAPIV3, OpenAPI, and IJsonSchema are re-exported from the openapi-types package for consumer convenience.

Beyond the barrel, package.json declares additional subpath exports: ./generated and ./generated/* (the generated type modules), ./v3-clients and ./v3-clients/* (the generated V3 clients), ./v2/* (the raw V2 YAML), and ./specs/* (the raw domain YAML files).


SpecMetadata Type#

Every entry in SPEC_REGISTRY conforms to this interface, defined in src/utils/registry.ts. The fields together provide everything needed to describe an API in a developer portal or to locate its spec file on disk.

Field Type Required Meaning
name string yes Human-readable display name of the API
version string yes Spec version string (e.g. 0.1.0, 1.0.0, 3.0.0)
description string yes One-line summary of the API surface
specPath string yes Relative path to the spec file (a SPEC_PATHS value)
domain ApiDomain yes Owning domain identifier
tags string[] yes Free-form classification tags for cross-cutting queries
basePath string yes URL base path the API is mounted under
deprecated boolean no Marks a spec as deprecated; omitted when not deprecated

ApiDomain Union#

ApiDomain is a TypeScript string-literal union — not a runtime enum. It enumerates every Oshun API domain that can own a spec. Using a union (rather than an enum) means TypeScript checks domain strings at compile time without any runtime overhead. Each member carries an inline comment in the source explaining its scope:

Value Scope
tara Meditation, ritual, and guided-session APIs
arete Habits, goals, routines, and coaching APIs
veritas Stories, claims, sources, and evidence APIs
lilith Consciousness Experience — end-user APIs
yemaya Creative Studio — production APIs
isis Generative Factory — generation APIs
sophia Knowledge Engine — search / research APIs
hathor World Builder — worldbuilding APIs
bellona Engine Bridge — engine integration APIs
calliope AI artist lifecycle and creative management
nyx Astronomy — celestial objects, ephemeris, events, satellites
nisaba Passages, manuscripts, annotations, notebooks, and study APIs
metis Educational platform — learner, admin, tutoring, assessment, build
v2 V2 fighting-game companion and public API surface
v3 V3 Lilith metaverse, Tara Studio, Saraswati Stage, and Commons APIs
oshun-bff Oshun backend-for-frontend APIs
shared Cross-domain shared APIs

SPEC_PATHS Reference#

SPEC_PATHS is declared as const, so its keys and string values are typed literals — this prevents path typos in loaders, validators, and generators. Paths are relative to src/specs/ (except v2/companion.yaml, which the loader resolves against libs/openapi/v2/, and metis.api, which points at an external repository path). The object is intentionally structured to mirror the domain hierarchy: top-level keys are domain names, nested objects hold per-feature or per-contract keys.

typescript
const SPEC_PATHS = {
  main: 'main.yaml',
  lilith: { chat, lectures, tts, consciousness, mythology, webrtc },
  yemaya: { projects, assets, collaboration, members, reviews },
  isis: { generation, workflows, models, jobs },
  sophia: { documents, search, knowledge, citations },
  hathor: { worlds, elements, narratives, simulations },
  bellona: { sessions, builds, exports },
  calliope: { api: 'calliope/calliope-api.yaml' },
  tara: { v1Contracts: 'tara/tara-v1-contracts.yaml' },
  arete: { v1Contracts: 'arete/arete-v1-contracts.yaml' },
  veritas: { v1Contracts: 'veritas/veritas-v1-contracts.yaml' },
  nyx: {
    api: 'nyx/nyx-api.yaml',
    v1Contracts: 'nyx/nyx-v1-contracts.yaml',
    objects,
    ephemeris,
    events,
    satellites,
  },
  nisaba: { v1Contracts: 'nisaba/nisaba-v1-contracts.yaml' },
  metis: {
    api: 'services/metis/openapi/metis.openapi.json',
    v1Contracts: 'metis/metis-v1-contracts.yaml',
  },
  v2: { companion: 'v2/companion.yaml' },
  v3: { contracts: 'v3.yaml' },
  oshunBff: { v1Contracts: 'oshun-bff/oshun-bff-v1-contracts.yaml' },
  shared: { auth, users, health, notifications },
} as const;

Two important facts about SPEC_PATHS:

  • Forward-declared keys. The fine-grained lilith.*, yemaya.*, isis.*, sophia.* (including citations), hathor.*, bellona.*, nyx.objects/ephemeris/events/satellites, and the entire shared.* group name files that are not present on disk. The registry.ts source comments this region "to be created during domain migrations." They keep the typed key set stable for tools that will consume those specs once the files exist.
  • metis.api is an external path. It points at services/metis/openapi/metis.openapi.json — a ~1 MB JSON spec owned by the Metis service, not a YAML file under src/specs/. The type generator reads it as an external spec (see Code Generation).

SPEC_REGISTRY Reference#

SPEC_REGISTRY is a Record<string, SpecMetadata>. It currently registers 20 specs, each identified by a short string key. The table below lists every registry key with its core metadata fields; the tags section below the table explains how getSpecsByTag groups them.

Key name domain version basePath
main Oshun Platform API lilith 0.1.0 /v1
auth Authentication API shared 0.1.0 /v1/auth
users Users API shared 0.1.0 /v1/users
health Health API shared 0.1.0 /v1/health
v3-contracts Oshun V3 Contract API v3 3.0.0 /api/v3
calliope Calliope API calliope 1.0.0 /v1
oshun-v1-tara Tara V1 Contract API tara 1.0.0 /api/v1/tara
oshun-v1-arete Arete V1 Contract API arete 1.0.0 /api/v1/arete
oshun-v1-veritas Veritas V1 Contract API veritas 1.0.0 /api/v1/veritas
nyx Nyx Astronomy API nyx 1.0.0 /api/v1
oshun-v1-nyx Nyx V1 Contract API nyx 1.0.0 /api/v1/nyx
nyx-objects Celestial Objects API nyx 1.0.0 /api/v1/objects
nyx-ephemeris Ephemeris API nyx 1.0.0 /api/v1/ephemeris
nyx-events Astronomical Events API nyx 1.0.0 /api/v1/events
nyx-satellites Satellite Tracking API nyx 1.0.0 /api/v1/satellites
oshun-v1-nisaba Nisaba V1 Contract API nisaba 1.0.0 /api/v1/nisaba
metis Metis API metis 0.1.0 /api
oshun-v1-metis Metis V1 Contract API metis 1.0.0 /api/v1/metis
v2-companion V2 Companion Public API v2 0.1.0 /api/v2
oshun-v1-bff Oshun BFF V1 Contract API oshun-bff 1.0.0 /api/oshun

Tags drive cross-cutting queries via getSpecsByTag. Each spec can belong to multiple tag groups; the tag values are free-form strings:

  • main: core, platform
  • auth: auth, security; users: users, profiles; health: health, observability
  • v3-contracts: v3, contracts, lilith, tara, saraswati, commons
  • calliope: artist, music, social, concerts, analytics
  • The oshun-v1-* specs all carry oshun-v1 and contracts plus a domain tag and a theme tag (tara+rituals, arete+coaching, veritas+evidence, nyx+astronomy, nisaba+study, metis+learning, bff).
  • nyx: astronomy, ephemeris, satellites, celestial; nyx-objects: astronomy, objects, catalog; nyx-ephemeris: astronomy, ephemeris, calculations; nyx-events: astronomy, events, calendar; nyx-satellites: satellites, tracking, iss
  • metis: education, tutoring, assessment, admin, analytics, build
  • v2-companion: v2, companion, public-api, replays, notifications

No registry entry currently sets deprecated.


Registry Helper Functions#

All four helpers live in src/utils/registry.ts and provide typed access to the registry without callers needing to iterate SPEC_REGISTRY or SPEC_PATHS directly.

getSpecsByDomain(domain: ApiDomain): SpecMetadata[]#

Returns every SPEC_REGISTRY value whose domain equals the argument. Used by the developer portal to build per-domain documentation sections. Domains with no registered spec (for example isis — its isis-api.yaml exists on disk but is not registered) correctly return an empty array.

getSpecsByTag(tag: string): SpecMetadata[]#

Returns every SPEC_REGISTRY value whose tags array contains the argument. Enables cross-cutting views such as "every contracts spec" or "every spec tagged astronomy."

getDomainPaths(domain: keyof typeof SPEC_PATHS): string[]#

Returns all relative spec paths under a SPEC_PATHS key. If the key maps to a plain string (e.g. main), it returns a single-element array; if it maps to a nested object, it returns Object.values() of that object.

extractEndpoints(spec: OpenAPIV3_1.Document)#

Walks spec.paths. For each path item and each of the seven HTTP methods (get, post, put, patch, delete, head, options), it emits one descriptor. Path items that are null/undefined are skipped. Used by the breaking-change detector and by portal endpoint indexes.

typescript
{ method: string;        // uppercased, e.g. "GET"
  path: string;          // the path template
  operationId?: string;
  tags?: string[];
  summary?: string; }

extractSchemas(spec: OpenAPIV3_1.Document)#

Walks spec.components.schemas. For each schema it emits a descriptor. The type handling explicitly accounts for OpenAPI 3.1 allowing type to be a string array (e.g. ["string", "null"]), joining array members with |.

typescript
{ name: string;
  type: string;          // OpenAPI 3.1 type, joined with "|" if an array;
                         // defaults to "object" when type is absent
  description?: string; }

Spec Loader (src/utils/loader.ts)#

The loader resolves spec paths against two roots, keeping the calling code free of path-construction logic:

  • SPECS_DIRlibs/openapi/src/specs/ (the default root)
  • V2_SPECS_DIRlibs/openapi/v2/ (for v2-prefixed paths)

Path resolution — resolveSpecFilePath#

The internal resolver applies three rules: absolute paths pass through unchanged; a path equal to v2 or starting with v2/ resolves against the package root (libs/openapi/); anything else resolves against SPECS_DIR.

loadSpec(specPath): Promise<OpenAPIV3_1.Document>#

Asynchronously reads the resolved file UTF-8 and parses it with the yaml package's YAML.parse, returning the result cast to OpenAPIV3_1.Document.

loadSpecSync(specPath): OpenAPIV3_1.Document#

Same as loadSpec using fs.readFileSync — for build scripts and generators that need blocking I/O.

getSpecPath(relativePath): string#

Public wrapper over resolveSpecFilePath; returns the absolute filesystem path for a relative spec path.

listSpecs(): Promise<string[]>#

Recursively scans SPECS_DIR for .yaml/.yml files, returning each path relative to SPECS_DIR. It then separately scans V2_SPECS_DIR and appends those files with a v2/-prefixed relative path. Both roots are guarded with fs.existsSync.

mergeSpecs(base, ...specs): OpenAPIV3_1.Document#

Merges a base document with any number of additional documents into a single unified document. This is the mechanism for producing a combined API reference when the developer portal needs all domains browsable in one document. Merge semantics:

  • paths — shallow-merged; a later spec's path entry overrides an earlier one of the same key.
  • components — five sub-maps are individually shallow-merged: schemas, responses, parameters, requestBodies, securitySchemes.
  • tags — deduplicated by tag.name; a tag whose name is already present is not re-added.

Domain Spec-File Inventory#

The src/specs/ directory holds the OpenAPI 3.1 YAML files below. Endpoint and schema counts are taken from the actual files; all are openapi: 3.1.0. The counts for generated specs show "(Zod-generated)" in the schema column because those schemas are derived programmatically from Zod contracts and therefore do not have a meaningful fixed count to cite here — they grow as contracts are added.

Spec file info.title Version Endpoints (path entries) Component schemas
main.yaml AI Wisdom Platform API 0.1.0 ~694 ~993
v3.yaml Oshun V3 Contract API 3.0.0 ~60 ~31
lilith/lilith-api.yaml Lilith API — Consciousness Experience 1.0.0 ~12 ~25
yemaya/yemaya-api.yaml Yemaya API — Creative Studio 1.0.0 ~10 ~24
isis/isis-api.yaml Isis API — Generative Factory 1.0.0 ~17 ~34
sophia/sophia-api.yaml Sophia API — Knowledge Engine 1.0.0 ~45 ~61
hathor/hathor-api.yaml Hathor API — Worldbuilding & Narrative 1.0.0 ~126 ~65
bellona/bellona-api.yaml Bellona API — Engine Bridge 1.0.0 ~23 ~32
nyx/nyx-api.yaml Nyx Astronomy API 1.0.0 ~15 ~23
calliope/calliope-api.yaml Calliope API — Autonomous AI Artist Platform 1.0.0 ~13 (Zod-generated)
concordia/concordia-api.yaml Concordia API — Cooperative Mediation 0.1.0 ~11 ~52
tara/tara-v1-contracts.yaml Tara V1 Contract API 1.0.0 ~12 (Zod-generated)
arete/arete-v1-contracts.yaml Arete V1 Contract API 1.0.0 ~20 (Zod-generated)
veritas/veritas-v1-contracts.yaml Veritas V1 Contract API 1.0.0 ~20 (Zod-generated)
nyx/nyx-v1-contracts.yaml Nyx V1 Contract API 1.0.0 ~8 (Zod-generated)
nisaba/nisaba-v1-contracts.yaml Nisaba V1 Contract API 1.0.0 ~26 (Zod-generated)
metis/metis-v1-contracts.yaml Metis V1 Contract API 1.0.0 ~22 (Zod-generated)
oshun-bff/oshun-bff-v1-contracts.yaml Oshun BFF V1 Contract API 1.0.0 ~148 (Zod-generated)
v2/companion.yaml V2 Companion Public API 0.1.0 ~12 (hand-authored)

The specs fall into three authoring categories:

  1. Hand-authored YAMLmain.yaml, the seven <domain>-api.yaml files for lilith/yemaya/isis/sophia/hathor/bellona/nyx, concordia-api.yaml, and v2/companion.yaml. These are edited directly.
  2. Generated from Zod via the V1 spec builder — the six <domain>-v1-contracts.yaml files plus oshun-bff-v1-contracts.yaml. Each carries the header # This file is generated by libs/openapi/scripts/generate-oshun-v1-specs.ts.
  3. Generated from Zod via dedicated generatorscalliope-api.yaml (header # This file is auto-generated by libs/openapi/scripts/generate-calliope-spec.ts) and v3.yaml (header # Generated by libs/openapi/scripts/generate-v3-spec.ts. Do not edit by hand.).

Concordia spec endpoints#

concordia/concordia-api.yaml (Phase 179) is hand-authored at version 0.1.0. Its base path is /v1/concordia and it defines 11 path entries / 11 operations covering the full cooperative mediation lifecycle from case creation to audit export:

Path Operation
/v1/concordia/cases createConcordiaCase
/v1/concordia/cases/{caseId}/parties addConcordiaParty
/v1/concordia/cases/{caseId}/intake submitConcordiaIntake
/v1/concordia/cases/{caseId}/issues createConcordiaIssue
/v1/concordia/cases/{caseId}/preference-queries createPreferenceQuery
/v1/concordia/cases/{caseId}/search-runs startSearchRun
/v1/concordia/cases/{caseId}/pareto-frontier getParetoFrontier
/v1/concordia/cases/{caseId}/drafts createSettlementDraft
/v1/concordia/cases/{caseId}/reviews createReviewDecision
/v1/concordia/cases/{caseId}/execute executeSettlement
/v1/concordia/audit/{caseId} getCaseAudit

Its tag set is Cases, Intake, Search, Drafts, Review, Execute, Audit, covering case intake, party-isolated preference inference, Pareto search, settlement drafting, human review, settlement execution, and audit export.


Oshun V1 Spec Builder (src/oshun-v1/spec-builder.ts)#

This module deterministically generates OpenAPI 3.1 documents and typed-client descriptors directly from the canonical Zod contracts in @oshun/persistence, combined with domain metadata from @oshun/domain-registry. It is the single source of the V1 contract specs and clients. The separation between this module and the scripts that call it means the generation logic is independently testable.

Inputs#

The builder draws from three workspace libraries:

  • V1_OBJECT_PERSISTENCE_CONTRACTS and V1_ENUM_PERSISTENCE_CONTRACTS from @oshun/persistence — arrays of ObjectPersistenceContract / EnumPersistenceContract, each carrying kind, domain, contractName, schemaName, modelName, tableName, and a Zod schema.
  • DOMAIN_REGISTRY from @oshun/domain-registry — supplies each domain's displayName, shellNarrative.summary, and bff-base-path.
  • ContractPersistenceDomain — the union tara | arete | veritas | nyx | nisaba | metis | cross-cutting.

Exported types#

The builder exports four types that the generation scripts and the public barrel re-export:

  • OshunV1ApiDomain = Exclude<ContractPersistenceDomain, 'cross-cutting'> — the six domains that get their own service spec.
  • OshunV1SpecArtifact{ id: string; filePath: string; title: string; document: JsonObject }.
  • OshunV1ClientResource{ domain: OshunV1ApiDomain; contractName: string; propertyName: string; schemaName: string; collectionPath: string; itemPath: string }.
  • JsonObject — a recursive plain-JSON object type.

Constants#

Three module-level constants control the shape of every generated document:

  • OSHUN_V1_API_DOMAINS — the frozen tuple ['tara', 'arete', 'veritas', 'nyx', 'nisaba', 'metis'].
  • CONTRACT_VERSION'1.0.0', the info.version for every generated spec.
  • JSON_SCHEMA_DIALECT'https://json-schema.org/draft/2020-12/schema', emitted as the document's jsonSchemaDialect.

Exported functions#

Function Returns
getOshunV1ApiDomains() the six-element OSHUN_V1_API_DOMAINS tuple
buildOshunV1SpecArtifacts() one OshunV1SpecArtifact per domain plus one BFF artifact (7)
getOshunV1SpecFilePaths() the filePath of every artifact
getOshunV1DomainSpecPath(d) `${d}/${d}-v1-contracts.yaml`
getOshunV1ClientResources(d) one OshunV1ClientResource per object contract in domain d

Generated document shape#

buildDomainServiceSpec(domain) produces one document per V1 domain. buildBffSpec() produces the combined BFF document. Both documents share the same component structure described in "Generated components" below.

Each per-domain service spec contains:

  • openapi: '3.1.0', jsonSchemaDialect, an info block titled <DisplayName> V1 Contract API.
  • A single server whose url is /api/v1/<domain>.
  • One tag named after the domain, described by the registry's shellNarrative.summary.
  • Document-level security [{ bearerAuth: [] }].
  • paths and components generated by buildContractPaths and buildComponents.

The BFF spec combines all six domains into one document. For each domain it generates paths under that domain's bff-base-path (e.g. /api/oshun/domains/tara), plus a /api/oshun/shared group derived from the cross-cutting contracts. The server url is /api/oshun. BFF schema names are domain-prefixed (see component naming below).

Generated paths per object contract — buildContractPaths#

Each object contract yields a five-operation REST resource. The collection path is <basePath>/<pluralized-kebab contractName>, and the item path appends /{sourceRecordId}. Operation IDs are stems built from an optional Bff prefix, the PascalCase domain, and the contract name (singular or pluralized):

HTTP Operation ID stem Summary Success Error responses ($ref)
GET collection list<Plural> List <Contract> records 200 401, 403, 500
POST collection create<Stem> Create <Contract> 201 400, 401, 403, 409, 500
GET item get<Stem> Get <Contract> 200 401, 403, 404, 500
PUT item upsert<Stem> Upsert <Contract> 200 400, 401, 403, 404, 409, 500
DELETE item tombstone<Stem> Tombstone <Contract> 200 401, 403, 404, 500

The collection GET accepts LimitParam, CursorParam, and IncludeTombstonesParam; the item path declares SourceRecordIdParam at the path level. POST/PUT request bodies reference the contract schema; the DELETE response references TombstoneResponse.

Generated components — buildComponents#

Every generated document's components block is built from the same set of reusable pieces, ensuring that every V1 domain spec and the BFF spec share the same wire-format conventions for authentication, pagination, error handling, and tombstoning:

  • securitySchemes.bearerAuthtype: http, scheme: bearer, bearerFormat: JWT.
  • parameters — four reusable parameters:
    • SourceRecordIdParam — required path string, minLength: 1.
    • LimitParam — optional query integer, minimum 1, maximum 250, default 50.
    • CursorParam — optional query string.
    • IncludeTombstonesParam — optional query boolean, default false.
  • responses — six reusable error responses, each application/json with the ErrorResponse schema: BadRequest, Unauthorized, Forbidden, NotFound, Conflict, InternalError.
  • schemas — three fixed envelope schemas plus per-contract schemas:
    • ErrorResponseobject, additionalProperties: false, required error + message, optional requestId and free-form details.
    • PageInfoobject, required limit (integer 1–250) + hasMore (boolean), optional cursor and nextCursor.
    • TombstoneResponseobject, required sourceRecordId + tombstonedAt (date-time), optional tombstoneReason.
    • For each object contract: <SchemaName> (the Zod schema converted to JSON Schema), <SchemaName>ListResponse ({ data: [...], page: PageInfo }), and <SchemaName>MutationResponse ({ data: <SchemaName> }).
    • For each enum contract: <SchemaName> (the Zod enum converted).

Component naming — componentSchemaName#

Schema name generation uses two modes to keep per-domain and BFF schemas unambiguous:

  • plain (used by per-domain service specs) — keeps contractName unchanged, e.g. RitualTemplate.
  • prefixed (used by the BFF spec) — prepends the PascalCase domain name, e.g. TaraRitualTemplate, or uses Shared for cross-cutting contracts, e.g. SharedEvidencePack.

Zod-to-JSON-Schema conversion — zodToJsonSchema#

Each Zod schema is converted with z.toJSONSchema(schema, { io: 'input', unrepresentable: 'any' }). The result is post-processed by stripSchemaDialect, which recursively removes every $schema key so the JSON Schema embeds cleanly into the OpenAPI components block.

Pluralization / casing helpers#

The builder includes several string-transformation helpers to produce consistent resource paths, operation IDs, and client property names:

toPascalCase, toKebabCase, pluralizeKebab, pluralizePascal, lowerFirst, and operationPrefix. Pluralization handles the y → ies rule (when not preceded by a vowel) and the s/x/ch/sh → +es rule.


Validation — scripts/validate.ts#

validate.ts is a self-contained OpenAPI 3.1 structural validator. It does not depend on an external schema library; it implements field-level checks directly. This means it can be fast and have zero external runtime dependencies beyond the yaml package already used by the loaders. CLI flag: --domain-only restricts scanning to the recognized domain directories.

Files scanned#

The validator recursively collects .yaml/.yml files from src/specs/ and from v2/. With --domain-only, at the root level it only descends into the directories in DOMAIN_DIRS (isis, sophia, hathor, bellona, yemaya, lilith, calliope, nyx, concordia, tara, arete, veritas, nisaba, metis, v2, oshun-bff) and skips root-level spec files such as main.yaml and v3.yaml.

Checks performed#

Each file is YAML-parsed (a parse failure is a hard error) and run through five check groups, each accumulating errors (which make a spec invalid) and warnings (advisory only):

validateRequiredFields

  • openapi present and starting with 3. (else error).
  • info present, with info.title and info.version (errors); missing info.description is a warning.
  • At least one of paths or webhooks is non-empty (OpenAPI 3.1 allows either) — else error.

validatePaths

  • Each path key must start with /.
  • Path-template parameters ({name}) must be unique within a path; duplicates are errors. Non-identifier parameter names are warnings.
  • Per operation (the eight HTTP methods): missing operationId is a warning; non-identifier operationId is a warning; duplicate operationId across the document is an error. Missing summary/description and missing tags are warnings.
  • responses must be present (error) with at least one 2xx (warning if not). Response codes must be default or match [1-5]\d\d (else error).
  • Every {param} in the path must be defined in path-level or operation-level parameters, including resolution of $ref parameters via resolveParameterRef — an undefined path parameter is an error.

validateSchemas

  • A schema with no type, allOf, oneOf, anyOf, $ref, or enum is a warning.
  • An object schema with neither properties nor additionalProperties is a warning.
  • A required field name that is not present in properties is an error.

validateSecurity

  • Missing securitySchemes is a warning.
  • Any operation-level security requirement naming an undefined scheme is an error.

validateRefs

  • Recursively walks the document; any $ref of the form #/components/schemas/<Name> whose <Name> is not a defined schema is an error (broken reference detection).

Output and exit code#

The validator prints a per-file report and a summary (total specs, valid, invalid, total errors, total warnings). It exits 1 if any spec is invalid and 0 otherwise. Empty path lists fail with "No paths or webhooks defined."

npm scripts: validate runs validate.ts --domain-only; validate:all runs validate.ts over every file. The Nx target openapi:validate instead runs the external @redocly/cli lint over src/specs/.


Breaking-Change Detection — scripts/diff.ts#

diff.ts compares the current specs against a base git ref to detect changes that would break API consumers. It is the mechanism by which the platform enforces backward compatibility on every PR.

CLI#

  • --base=<ref> — the git reference to compare against; default origin/main.
  • --check — fail (exit 1) when breaking changes are found.

npm scripts: diff (report only) and diff:check (--check).

Specs compared#

A fixed list DOMAIN_SPECS of seven domain specs is compared: lilith/lilith-api.yaml, yemaya/yemaya-api.yaml, isis/isis-api.yaml, sophia/sophia-api.yaml, hathor/hathor-api.yaml, bellona/bellona-api.yaml, concordia/concordia-api.yaml. For each, the base version is retrieved via git show <base>:libs/openapi/src/specs/<file> and the current version from disk. A spec that exists only on disk is reported as a new spec (no comparison); a spec that exists only in the base is reported as an entirely removed spec (a breaking change).

Breaking changes detected#

The BreakingChange.type union has five members. detectBreakingChanges flags the following conditions — note that path comparison is template-aware (renaming a path parameter does not count as a breaking change):

type Condition
path-removed a base path has no current match
operation-removed a base operation (get/post/put/patch/delete) is gone
parameter-required an optional parameter became required, or a required parameter was removed
response-removed a 2xx success response code was removed from an operation
schema-breaking (declared in the union; reserved for schema-level breaks)

Removal of non-2xx (error) response codes is explicitly not treated as breaking. normalizePathTemplate rewrites {anything} to {} and findPathByWireTemplate matches paths whose only difference is parameter naming — so renaming a path parameter is not flagged.

Non-breaking changes#

detectNonBreakingChanges reports newly added paths and newly added operations on existing paths. These are surfaced in the report to give reviewers a complete picture of what changed.

Output and exit code#

A per-spec report plus a summary (specs compared, new specs, removed specs, total breaking and non-breaking changes). With --check, the script exits 1 when any spec has breaking changes, and prints guidance to bump the major version or run a deprecation period.


Drift Detection — scripts/drift-check.ts#

drift-check.ts detects the most common contract-drift symptom: a spec YAML was edited but its generated TypeScript was not regenerated (or vice versa). Catching this in CI prevents a scenario where the TypeScript types engineers import no longer match the spec the API actually implements. The module exports detectDrift and supporting types so it can be unit-tested; drift-check.spec.ts is its vitest suite.

CLI#

  • --check — exit 1 when drift is detected.
  • --json — emit a machine-readable DriftReport instead of the human report.

npm scripts: drift (report) and drift:check (--check).

Domain map#

DOMAIN_MAPS pairs each generated TypeScript module with its source YAML (11 pairs): lilith, yemaya, isis, sophia, hathor, bellona, concordia, nyx all map to <domain>/<domain>-api.yaml; calliope maps to calliope/calliope-api.yaml; metis maps to metis/metis-v1-contracts.yaml; oshun-bff maps to oshun-bff/oshun-bff-v1-contracts.yaml.

Checks performed per domain#

For each pair, the YAML is parsed into a SpecModel (paths, operationIds, components.schemas) and the generated .ts is parsed by regex into the same model. detectDrift then emits a DriftEntry for any of four categories:

  1. paths — every YAML path must appear in the generated paths type. YAML {param} placeholders are normalized to {string} to correlate against what openapi-typescript emits as `${string}`. Both missing and extra paths are reported.
  2. operations — every YAML operationId must appear as an operations['<id>'] reference in the generated TS.
  3. schemas — every YAML component schema must appear in the generated TS, either as components['schemas']['<Name>'] or as a direct declaration inside the schemas: block.
  4. header — the generated file must carry an Auto-generated from: <yaml> comment still pointing at the matching YAML; a missing or mismatched header is rename drift.

If a YAML or generated file is entirely absent, that itself is reported as a drift entry.

DriftReport / API surface#

  • detectDrift(domains?, options?)options.specsDir and options.generatedDir override the roots (used by the test suite to point at temp fixture trees).
  • DriftReport{ domains: number; driftCount: number; entries: DriftEntry[] }.
  • The human formatter prints up to five missing/extra items per category and an "... and N more" tail.

Code Generation#

The generation pipeline is the bridge between spec sources (hand-authored YAML and canonical Zod contracts) and the TypeScript artifacts that application code actually imports. There are two directions: specs generated from Zod contracts, and TypeScript types generated from specs.

scripts/generate.ts — type generation orchestrator#

The generate script is the umbrella generator. It runs in order:

  1. Invoke generate-oshun-v1-specs.ts to regenerate the V1 + BFF YAML specs.
  2. Invoke generate-oshun-v1-api-clients.ts to regenerate the V1 typed client packages.
  3. Invoke generate-calliope-spec.ts to regenerate calliope-api.yaml.
  4. For every .yaml/.yml under src/specs/ and v2/, run openapi-typescript to emit a .ts module into src/generated/.
  5. For each external spec in EXTERNAL_SPECS, do the same.

Output naming: a v2/ spec becomes v2-<name>.ts; a <name>-api.yaml becomes <dirname>.ts (so lilith/lilith-api.yamllilith.ts); any other file keeps its base name.

EXTERNAL_SPECS are two specs owned by other packages, generated with an explicit output name:

  • services/metis/openapi/metis.openapi.jsonmetis.ts
  • apps/oshun/bff/openapi/oshun-bff.openapi.yamloshun-bff.ts

A missing external spec is skipped with a warning rather than failing the run.

The Nx target openapi:gen runs generate.ts directly.

src/generated/ — generated type modules#

src/generated/index.ts is a barrel that export * as <domain> for: lilith, yemaya, isis, sophia, hathor, bellona, nyx, calliope, concordia, oshunBff, metis. It also re-exports each module's paths type under a <Domain>Paths alias (LilithPaths, YemayaPaths, …, MetisPaths). The directory additionally contains main.ts. Every generated file carries a "DO NOT EDIT" header. Domain service code imports these types instead of hand-writing request/response shapes.

scripts/generate-calliope-spec.ts#

Generates src/specs/calliope/calliope-api.yaml from the CalliopeOpenApiComponentSchemas record exported by @calliope/core (a Record<string, z.ZodTypeAny>). Each Zod schema is converted with z.toJSONSchema(..., { io: 'input', unrepresentable: 'any' }) and stripped of $schema keys. The script then assembles a complete document by hand:

  • info titled "Calliope API - Autonomous AI Artist Platform", version 1.0.0, with contact "Oshun Platform Team" and license "MIT".
  • Three servers: production https://api.oshun.io/calliope, staging https://staging-api.oshun.io/calliope, local http://localhost:3010.
  • Six tags: Artists, Songs, Eras, Social, Concerts, Analytics.
  • Document-level bearerAuth security.
  • 13 path entries covering artist CRUD, nested song / era / social-post / concert CRUD, and three analytics read endpoints (overview, streaming, engagement).
  • components with a bearerAuth HTTP-bearer-JWT scheme; reusable path parameters (ArtistIdParam, SongIdParam, EraIdParam, PostIdParam, ConcertIdParam, all UUID strings) and query parameters (PageParam, LimitParam with maximum 100 / default 20, WindowFromParam, WindowToParam date-times); four reusable responses (BadRequest, Unauthorized, NotFound, InternalError); and the Zod-derived schemas.

The serialized output is prefixed with the header # This file is auto-generated by libs/openapi/scripts/generate-calliope-spec.ts and written with yaml's stringify({ lineWidth: 0 }).

Check mode (--check, the generate:calliope:check script): instead of writing, the script compares the freshly built content to the file on disk and exits 1 if they differ — enforcing that the committed spec is current.

scripts/generate-oshun-v1-specs.ts#

Calls buildOshunV1SpecArtifacts() and, for each artifact, serializes the document with yaml's stringify({ lineWidth: 110 }), prefixes the header # This file is generated by libs/openapi/scripts/generate-oshun-v1-specs.ts., then pipes it through prettier (--stdin-filepath oshun-v1.yaml) before writing under src/specs/. In --check mode it reports any spec that is missing or out of date and exits 1. npm scripts: generate:oshun-v1-specs and generate:oshun-v1-specs:check.

scripts/generate-oshun-v1-api-clients.ts#

Generates one typed API-client package per V1 domain. The package list (CLIENT_PACKAGES) is derived from getOshunV1ApiDomains(); each entry yields package name @<domain>/api-client, project name <domain>-api-client, root libs/<domain>/api-client, and scope tag scope:<domain>. The metis package has preserveExistingScaffold: true, so for Metis only src/client.ts, src/client.test.ts, and src/generated/openapi.ts are (re)written — its package.json, project.json, tsconfigs, vitest.config.ts, and index.ts are left untouched; all other domains get the full scaffold regenerated.

Per package the generator writes:

  • package.json, project.json, three tsconfigs, vitest.config.ts, src/index.ts (full scaffold; skipped for Metis).
  • src/client.ts — a typed client with a ResourceClient<Record, List, Mutation> per object contract, exposing list, create, get, upsert, tombstone. Includes an injectable FetchLike, a configurable base URL and headers, query-string assembly (limit, cursor, includeTombstones), and an OshunApiClientError thrown on non-OK responses.
  • src/client.test.ts — a vitest spec asserting the five operations issue the expected GET/POST/GET/PUT/DELETE calls to the contract routes.
  • src/generated/openapi.ts — the domain spec run through openapi-typescript into a temp directory and copied in.

All files are formatted via prettier --stdin-filepath. --check mode reports out-of-date or missing files and exits 1. npm scripts: generate:oshun-v1-clients and generate:oshun-v1-clients:check.

scripts/generate-v3-spec.ts#

Generates src/specs/v3.yaml by calling buildV3OpenApiDocument() from @oshun/contracts/v3, serializing with yaml's stringify({ aliasDuplicateObjects: false, lineWidth: 100 }), and prefixing the header # Generated by libs/openapi/scripts/generate-v3-spec.ts. Do not edit by hand. It accepts an optional positional target which must be v3 (any other value throws). --check mode compares to disk and throws if stale. The generated V3 spec is titled "Oshun V3 Contract API", version 3.0.0, and carries four tags: lilith-platform, tara-studio, saraswati-stage, lilith-commons. npm scripts: generate:v3-spec and generate:v3-spec:check.

scripts/generate-v3-clients.ts#

Generates the four typed V3 tenant clients plus base.ts and index.ts into src/v3-clients/. Each client is scoped to one V3 tenant — the tenant list (TENANT_FILES) maps each V3ClientTenant to a file name, factory function, and interface name:

Tenant File Factory Interface
lilith-platform lilith-platform.ts createLilithPlatformClient LilithPlatformClient
tara-studio tara-studio.ts createTaraStudioClient TaraStudioClient
saraswati-stage saraswati-stage.ts createSaraswatiStageClient SaraswatiStageClient
lilith-commons lilith-commons.ts createLilithCommonsClient LilithCommonsClient

For each tenant, getV3ContractsForTenant(tenant) from @oshun/contracts/v3 supplies contract descriptors; the generator emits a get<Contract>(id) and create<Contract>(input) method per contract, each issuing a v3JsonRequest to /api/v3/<tenant>/<routeSegment>. --check mode reports any out-of-date file. npm scripts: generate:v3-clients and generate:v3-clients:check. generate:v3 / generate:v3:check run the V3 spec and client generators together; the matching Nx targets are generate:v3 and generate:v3:check.

src/v3-clients/ — generated V3 clients#

This directory is "do not edit by hand" — it is fully regenerated by generate-v3-clients.ts. The transport layer and all four tenant clients live here.

  • base.ts exports the transport: the V3FetchResponse and V3Fetch interfaces, the V3ClientOptions config (baseUrl, optional fetch, optional headers), the V3ApiError class (carrying status, code, requestId, body), and v3JsonRequest<T>(options, path, init). v3JsonRequest falls back to globalThis.fetch, sets accept: application/json (and content-type when a body is present), reads the body according to content type, and throws a V3ApiError — including the x-request-id response header — on a non-OK response.
  • index.ts re-exports base and the four tenant client modules.
  • Each tenant file (e.g. lilith-platform.ts) exports an interface and a factory. LilithPlatformClient covers nine V3 contracts: LilithSession, AvatarBinding, Presence, Room, Venue, SpatialTranscript, Report3D, ProvenanceBundle3D, EmbodiedConsent.

scripts/validate-v3-spec.ts#

Validates src/specs/v3.yaml against every contract in V3_CONTRACT_REGISTRY (from @oshun/contracts/v3) using the oas3-validator package. For each contract descriptor it constructs the collection path /api/v3/<clientTenant>/<routeSegment> and the item path <collection>/{id}, then asserts that the descriptor's fixture validates both as a POST request body to the collection and as a 200 GET response body from the item path. Any validator error array is thrown. npm script: validate:v3; Nx target: validate:v3.


Documentation Generation — scripts/generate-docs.ts#

generate-docs.ts builds a static HTML documentation site from the domain specs using Redoc (loaded from cdn.redoc.ly at page runtime). The output is a deployable static site that gives external and internal developers a searchable, schema-aware view of every documented API.

Documented domains#

A fixed DOMAINS record configures nine domains with a title and description: isis, sophia, hathor, bellona, yemaya, lilith, calliope, nyx, concordia. For each, the script reads the first .yaml/.yml file in src/specs/<domain>/, extracts info.title/version/description, and:

  • writes docs/<domain>/index.html — a Redoc page embedding the spec JSON, with a shared dark navbar linking all nine domains and a custom Redoc theme (indigo #6366f1 primary, Inter / JetBrains Mono typography);
  • copies the spec to docs/<domain>/openapi.yaml for download.

It also writes docs/index.html — a landing page with one card per domain (title, version badge, description) and a "Getting Started" section (authentication via JWT bearer tokens, production/staging base URLs, the default 1000-requests-per-minute rate limit, JSON response format).

Output location#

All output goes to libs/openapi/docs/. The committed docs/ tree currently holds index.html plus per-domain index.html + openapi.yaml for all nine configured domains.

npm scripts: generate:docs runs the generator; docs runs it and then npx serve docs for local preview.


Dependencies#

The package.json dependency list below is the exact source of truth for what this library imports. Runtime dependencies are loaded at generation time (during builds and CI); dev dependencies are invoked by scripts.

json
"dependencies": {
  "@calliope/core": "workspace:*",
  "@oshun/contracts": "workspace:*",
  "@oshun/domain-registry": "workspace:*",
  "@oshun/persistence": "workspace:*",
  "openapi-types": "^12.1.3",
  "yaml": "^2.4.1",
  "zod": "catalog:"
},
"devDependencies": {
  "oas3-validator": "1.0.0",
  "openapi3-ts": "0.11.0",
  "openapi-typescript": "^7.10.1",
  "tsx": "^4.7.0",
  "vite-tsconfig-paths": "catalog:"
},
"peerDependencies": { "typescript": "catalog:" }

Each dependency serves a specific role in the generation and validation pipeline:

  • @calliope/core — source of CalliopeOpenApiComponentSchemas for the Calliope spec generator.
  • @oshun/contracts — source of buildV3OpenApiDocument, V3_CONTRACT_REGISTRY, getV3ContractsForTenant, and the V3 fixtures used by the V3 spec/client generators and validator (via the /v3 subpath).
  • @oshun/domain-registry — source of DOMAIN_REGISTRY (display names, shell narratives, BFF base paths) for the Oshun V1 spec builder.
  • @oshun/persistence — source of the V1 object/enum persistence contracts and the ContractPersistenceDomain type.
  • openapi-types — TypeScript types for OpenAPI documents; OpenAPIV3_1 is re-exported.
  • yaml — YAML parse/stringify across loaders and every generator.
  • zod — used by the spec builder and Calliope generator for z.toJSONSchema.
  • oas3-validator — runtime validation engine for validate-v3-spec.ts.
  • openapi-typescript — converts specs to TypeScript types in generate.ts and the V1 client generator.
  • tsx — runs every script.

Nx Project Targets#

project.json defines the project (@oshun/openapi, tags scope:oshun, type:lib) with ten targets. The build target compiles the library and copies spec YAML files as assets; the remaining targets all invoke tsx scripts via nx:run-commands.

Target Executor Action
build @nx/js:tsc Compile to dist/libs/openapi, copying src/specs and v2 YAML as assets
lint @nx/eslint:lint Lint the project
test @nx/vite:test Run vitest (passWithNoTests: true)
openapi:validate nx:run-commands npx @redocly/cli lint libs/openapi/src/specs/
openapi:gen nx:run-commands tsx libs/openapi/scripts/generate.ts
generate:oshun-v1-specs[:check] nx:run-commands Delegates to the matching pnpm script
generate:oshun-v1-clients[:check] nx:run-commands Delegates to the matching pnpm script
generate:v3[:check] nx:run-commands Delegates to the matching pnpm script
validate:v3 nx:run-commands Delegates to the validate:v3 pnpm script

The build target's TS config is tsconfig.lib.json, which includes src/**/*.ts and scripts/**/*.ts but excludes test files and src/generated/**/*.ts.


npm Script Reference#

All scripts are invokable via pnpm run <script> from the libs/openapi/ directory. The *:check variants are the ones CI invokes — they never write files, only compare and exit with an error code.

Script Command Purpose
validate tsx scripts/validate.ts --domain-only Validate domain specs only
validate:all tsx scripts/validate.ts Validate every spec including root-level
diff tsx scripts/diff.ts Report breaking changes vs origin/main
diff:check tsx scripts/diff.ts --check Fail CI on breaking changes
drift tsx scripts/drift-check.ts Report spec↔generated-type drift
drift:check tsx scripts/drift-check.ts --check Fail CI on drift
generate:calliope tsx scripts/generate-calliope-spec.ts Generate the Calliope spec from Zod
generate:calliope:check tsx scripts/generate-calliope-spec.ts --check Verify the Calliope spec is current
generate:oshun-v1-specs tsx scripts/generate-oshun-v1-specs.ts Generate the V1 + BFF specs from Zod
generate:oshun-v1-specs:check … --check Verify the V1 + BFF specs are current
generate:oshun-v1-clients tsx scripts/generate-oshun-v1-api-clients.ts Generate the V1 typed client packages
generate:oshun-v1-clients:check … --check Verify the V1 client packages are current
generate:v3-spec tsx scripts/generate-v3-spec.ts Generate v3.yaml from @oshun/contracts/v3
generate:v3-spec:check … --check Verify v3.yaml is current
generate:v3-clients tsx scripts/generate-v3-clients.ts Generate the four V3 tenant clients
generate:v3-clients:check … --check Verify the V3 clients are current
generate:v3 generate:v3-spec && generate:v3-clients Generate V3 spec + clients together
generate:v3:check generate:v3-spec:check && generate:v3-clients:check Verify V3 spec + clients
validate:v3 tsx scripts/validate-v3-spec.ts Validate v3.yaml against V3 contract fixtures
generate tsx scripts/generate.ts Orchestrate all generators + type generation
generate:docs tsx scripts/generate-docs.ts Build the Redoc HTML documentation site
docs generate:docs && npx serve docs Build and serve the docs locally
build tsc Compile the library
clean rm -rf dist docs Remove build and docs output

CI Integration#

The *:check scripts are the CI gates. They all share the same contract: build the expected artifact, compare it to what is committed, and exit 1 on any difference. A consistent CI configuration runs these five steps in order:

  1. validate:all — every spec must be a structurally valid OpenAPI 3.1 document.
  2. diff:check — no breaking changes versus the base branch in the seven tracked domain specs.
  3. drift:check — no spec↔generated-type drift in the eleven mapped domains.
  4. generate:calliope:check, generate:oshun-v1-specs:check, generate:oshun-v1-clients:check, generate:v3:check — every generated spec and client is in sync with its canonical Zod source.
  5. validate:v3 — the V3 spec accepts every V3 contract fixture.

This enforces the single-source-of-truth rule: generated specs and clients must be regenerated and committed alongside any change to their Zod source.


Tests#

Two vitest suites run under the test target (vitest.config.ts includes src/** and scripts/** spec globs, node environment, v8 coverage). Together they cover the registry, loader, spec builder, drift detector, and the generated V3 clients.

  • src/openapi.spec.ts — exercises SPEC_PATHS values, SPEC_REGISTRY entries (main, auth, calliope, metis, V2, V3, and every generated oshun-v1-* entry), getSpecsByDomain / getSpecsByTag / getDomainPaths, extractEndpoints / extractSchemas, the V2 companion spec loaded via loadSpecSync / listSpecs, and buildOshunV1SpecArtifacts / getOshunV1ClientResources (asserting concrete contract paths and schema names such as RitualTemplate and CourseBuild).
  • scripts/drift-check.spec.ts — exercises detectDrift against temporary fixture trees: a zero-drift case and cases covering missing paths, missing operations, missing schemas, header drift, a missing YAML, and a missing generated file.
  • src/v3-clients/__tests__/client.spec.ts — exercises the generated V3 clients via createTaraStudioClient: a typed happy-path POST, and V3ApiError propagation for 4xx and 5xx responses including code/requestId/body metadata.

Integration Points#

The table below shows every external consumer of this library and how they use it. The boundary is clean: this library provides contracts and tooling only; it has no knowledge of business logic.

Consumer Usage
Domain API servers Import generated types from @oshun/openapi/generated and the generated V1/V3 typed clients
CI pipeline Runs validate:all, diff:check, drift:check, and the *:check generation gates
Developer portal generate:docs output (docs/) deployed as the documentation site
@calliope/core Supplies CalliopeOpenApiComponentSchemas consumed by the Calliope spec generator
@oshun/contracts (/v3) Supplies buildV3OpenApiDocument, V3_CONTRACT_REGISTRY, V3 fixtures for V3 generation
@oshun/persistence Supplies the V1 object/enum persistence contracts the V1 spec builder generates from
@oshun/domain-registry Supplies DOMAIN_REGISTRY metadata for V1 spec titles, tags, and BFF base paths
Metis service Owns services/metis/openapi/metis.openapi.json, consumed as an external spec by generate.ts
Oshun BFF app Owns apps/oshun/bff/openapi/oshun-bff.openapi.yaml, consumed as an external spec
V2 public API apps/oshun/web V2 handlers and route adapters consume libs/openapi/v2/companion.yaml

V2 Companion public API binding#

The V2 Companion Public API is the one external HTTP surface that contracts against this library at runtime. It is split across two directories in the apps/oshun/web Next.js app:

  • apps/oshun/web/api/v2/ holds the framework-agnostic handler layer — public-api.ts defines V2_PUBLIC_API_BASE_PATH = '/api/v2', the V2_PUBLIC_API_ENDPOINTS map, and the typed handlers (getV2PlayerStats, getV2PlayerHistory, getV2Replay, createV2CoachStream, subscribeV2Notifications), all read-only with gameplayAuthority: false.
  • apps/oshun/web/src/app/api/v2/ holds the thin Next.js App Router adapters (player/[playerId]/stats/route.ts, replay/[replayId]/route.ts, match/[matchId]/coach-stream/route.ts, notification/subscribe/route.ts, and openapi/route.ts), which delegate to those handlers and re-serve the spec.

Both layers are pinned to the same contract: @oshun/openapi resolves the v2-companion spec from libs/openapi/v2/companion.yaml (registered with basePath: '/api/v2'), and the openapi/route.ts adapter serves that exact document via SPEC_PATHS.v2.companion, so the published OpenAPI description and the live /api/v2 endpoints can never drift apart.


Acceptance Criteria#

The openapi domain enforces these properties through its *:check scripts and test suites. These criteria are the machine-verifiable definition of "the domain is healthy."

  1. Every spec under src/specs/ and v2/ is a structurally valid OpenAPI 3.1 document (validate:allopenapi version, required info fields, paths or webhooks, unique operation IDs, no broken #/components/schemas refs, defined path parameters, defined security schemes).
  2. No tracked domain spec introduces a removed path/operation/2xx response or a parameter that became required or was removed, versus the base branch (diff:check).
  3. Each generated TypeScript module's paths, operation IDs, schemas, and Auto-generated from: header stay in sync with its source YAML (drift:check).
  4. calliope-api.yaml, the six V1 contract specs, oshun-bff-v1-contracts.yaml, v3.yaml, and the V1 and V3 typed clients are byte-identical to a fresh regeneration from their canonical Zod sources (generate:*:check).
  5. The V3 spec validates every V3 contract fixture as both request and response bodies (validate:v3).
  6. SPEC_PATHS, SPEC_REGISTRY, the registry helpers, and the V1 spec builder produce the documented values (src/openapi.spec.ts).