# KodeMed Wire Contracts (`schemas/`) This directory holds the **single source of truth** for every formal data contract a partner integrating with KodeMed touches. Each file is a JSON Schema (or XSD, for the COM/DLL XML variants) that gets: 1. Hand-edited here (versioned in git, this directory). 2. Validated mechanically by `scripts/test/test-schemas-uniformity.py` on every CI run. 3. Shipped to the downloads portal so partners can validate their integration code statically. ## Why this directory exists (#640) Before this directory existed, KodeMed had **one** formal schema published (`Documentation/spiges/do-d-14.04-SPIGES-2025-02.xsd`, the case-data XML) and **four** data formats that partners had to figure out from prose docs or by reading code: the HIS REST submission body, the webhook callback payload, the WebSocket envelope, and the COM `GetResults()` return value. Ricardo flagged this on 2026-05-14: > *"no me refiero a nuestro xsd, del formato de datos que los flujos"* …and on 2026-06-01 added the non-negotiable: > *"deben ser todos iguales, no que tengamos distinto formato de datos en > distintos flujos."* The whole point of this directory is to make field-name and field-type **drift across flows mechanically impossible**: every shared concept lives in `_common/`, and every flow schema must reference it via `$ref` — not inline. The uniformity test refuses any schema that re-declares a shared concept inline. ## Integration paths covered KodeMed runs in two integration modes; this contracts directory covers both: 1. **HIS partners (REST + webhook)** — a hospital information system POSTs a case to `/api/v1/coding/session` and gets a webhook back when the coding session closes. Both ends speak JSON. 2. **CodingClient stand-alone (WebSocket + COM + local file opener)** — the Windows Tray client (`KodeMed.CodingClient`) holds a persistent WebSocket to the server, exposes a COM API for legacy partners (Delphi / VB6 / .NET COM), and can open local SPIGES / BFS XML files directly without going through any HIS. All four data paths from that client share the same wrapped payload shape. The shared SPIGES XSD stays the canonical case-data format. This directory wraps it (and the BFS variant, which CodingClient can also open) into consistent envelopes for every other transport. ## Layout ``` schemas/ ├── README.md ← this file ├── _common/ ← shared building blocks — referenced via $ref │ ├── Identifier.schema.json ← server-generated UUID (sessionId, caseId, …) │ ├── OpaqueIdentifier.schema.json ← partner-generated string (instanceId, targetUserId) │ ├── Timestamp.schema.json │ ├── CaseData.schema.json │ ├── CodingResult.schema.json │ └── EventType.schema.json ├── his_rest/ ← HIS partner REST + webhook │ ├── SessionCreate-1.0.schema.json │ ├── SessionResponse-1.0.schema.json │ └── WebhookPayload-1.0.schema.json ├── websocket/ ← CodingClient ↔ Server full-duplex │ └── Message-1.0.schema.json ├── dll_com/ ← CodingClient COM API │ ├── GetResults-1.0.schema.json │ └── GetResults-1.0.xsd └── samples/ ← canonical payloads, used by tests ├── session-create.json ← valid: SessionCreate request ├── session-response.json ← valid: SessionResponse body ├── webhook-payload.json ← valid: outbound WebhookPayload ├── message.json ← valid: WebSocket envelope ├── get-results.json / .xml ← valid: COM GetResults (JSON + XML) ├── data/ ← real-world request bodies (CI-populated) ├── failing/ ← negative samples (MUST be rejected — R10) └── fixtures/ ← lock-in fixtures (dispatcher output ↔ schema, R14) ``` ## Uniformity rules (enforced by `scripts/test/test-schemas-uniformity.py`) 1. **One source of truth per concept.** Every shared concept (identifiers, timestamps, case data, result data, event types) lives in exactly one file under `_common/`. Re-declaring its shape inline in a flow schema fails the test. 2. **camelCase only.** All JSON property names use camelCase. Snake_case (`session_id`, `event_type`) is a drift bug the test catches — matches Spring Boot Jackson + the existing OpenAPI spec. 3. **UUID property suffix `Id` ⇒ `$ref _common/(Identifier|OpaqueIdentifier)`.** Any property name ending in `Id` references either `Identifier` (strict UUID for server-generated ids — `sessionId`, `caseId`, …) or `OpaqueIdentifier` (partner-controlled string for `instanceId`, `targetUserId`). Inline `type: string` declarations on a `*Id` property fail — every partner-visible id flows through one of those two canonical definitions. 4. **Timestamp property suffix `At` ⇒ ISO-8601 `format: date-time`.** Any property name ending in `At` (e.g. `expiresAt`, `occurredAt`) is type `string` with format `date-time`. 5. **EventType enum lives in one place.** Every schema that has an `eventType` property must `$ref` `_common/EventType.schema.json` and inherit the same enum values. 6. **Sample payloads round-trip.** Each schema has a canonical valid sample under `samples/`. The test validates every sample against its schema. Adding a field with no corresponding sample also fails the test (forces docs-by-example). 7. **Schema versioning.** Breaking changes ship a new `-MAJOR.MINOR.schema.json` file side-by-side; the deprecated version stays until partners migrate. The deprecation gate is documented in the file's `$comment`. ## How to add a new schema 1. Check `_common/` for an existing shared type. If one exists, `$ref` it; never re-declare inline. 2. If a new shared concept is needed, add it to `_common/` first, with a JSDoc-style description and a sample value. 3. Write the flow schema referencing `_common/` via `$ref`. 4. Add at least one canonical sample under `samples/`. 5. Run `bash scripts/test/run-tests.sh --module schemas` (or directly `python3 scripts/test/test-schemas-uniformity.py`). 6. If the test is RED, fix the schema until it is GREEN. Do not add a suppress / waiver — that defeats the whole point. ## Out of scope - The SPIGES XSD (`Documentation/spiges/do-d-14.04-SPIGES-2025-02.xsd`) — already published, stays as the canonical case-data XML format. The `_common/CaseData.schema.json` referenced from this dir wraps it (carries the XML as a `string` and the format discriminator) but does not re-declare any of its fields. - The internal Java + TypeScript domain models — those can be richer than the wire contract. The contracts here are only what crosses a partner trust boundary. - The SearchServer (#720) — a separate project with its own internal RPC contract, no partner-visible surface. ## See also - #640 — the original umbrella ticket for this directory. - `Documentation/KodeMed_HIS_REST_Integration_Spec.md` — partner-facing prose for the REST + webhook flow. - `Documentation/KodeMed_GetResults_Format.md` — partner-facing prose for the COM `GetResults()` return value. - `Documentation/spiges/do-d-14.04-SPIGES-2025-02.xsd` — case-data XML XSD.