clio-schemas
The single source of truth for the record shapes shared across the CLIO
system. clio-agent, clio-relay, and gact-tui all speak the same wire
records — artifact versions/chains and transform provenance records. Historically
each service hand-wrote its own copy of these types and
they drifted. This package makes the shapes canonical: they are defined once as
pydantic v2 models here and exported to JSON
Schema. Python consumers import the models; TypeScript consumers generate
types from the JSON Schema shipped inside the package. Nobody hand-writes a
shared shape again.
Status: P2.1 records landed (version 0.2.0 / issue #1120). The bootstrap placeholders are gone.
ArtifactVersion,ArtifactRecord,ProvEdge,TransformRecord, their nested value models, and their enums are canonical.
Why the schemas are immutable package resources (design decision)
An exact clio-schemas pin, on its own, does not guarantee two services see
the same schema bytes: if each consumer regenerated JSON Schema from the
pydantic models under its own resolved pydantic version, a pydantic point release
could subtly change the output and drift would return through the back door.
So the canonical JSON Schemas are built once, committed, and shipped as
immutable package resources inside the wheel (clio_schemas/schemas/*.json),
alongside a canonical hash manifest (HASHES.json). Consumers copy those
committed bytes — they never regenerate. An exact pin therefore does determine
the bytes. Regenerating from the models is a repo-local developer command
(--regenerate), gated to the single locked pydantic version, so the
committed artifacts can only change deliberately, in this repo.
What's in the box
clio-schemas/
├── pyproject.toml # uv-compatible, py>=3.12, pydantic v2, hatchling
├── src/clio_schemas/
│ ├── __init__.py # public exports + __version__
│ ├── constants.py # LOCKED_PYDANTIC_VERSION, file names
│ ├── models.py # canonical models + ClioSchemaBase + registry
│ ├── export.py # copy / check / regenerate / verify
│ ├── py.typed # ships type information
│ └── schemas/ # COMMITTED immutable artifacts (in the wheel)
│ ├── artifact_version.json # per-model, self-contained
│ ├── artifact_record.json # per-model, self-contained
│ ├── transform_record.json # plus provenance/value model schemas
│ ├── index.json # aggregate: shared $defs emitted once
│ └── HASHES.json # canonical sha256 manifest
├── tools/ts-gen/
│ ├── schemas-to-ts.mjs # JSON Schema dir -> TS module graph (deterministic)
│ ├── tsconfig.check.json # isolated strict typecheck of generated TS
│ └── package.json / package-lock.json
├── scripts/check_version_bump.py # CI: schema change ⇒ version bump
├── tests/
│ ├── test_roundtrip.py # JSON/TS golden anti-drift tests
│ ├── test_legacy_parity.py # original clio-agent byte fixtures
│ ├── test_version_bump.py # unit tests for the bump-enforcement core
│ └── golden/ # committed golden TS directory
│ ├── _models.ts index.ts artifact_version.ts transform_record.ts ...
└── ci-drafts/ # workflow drafts for this repo + both consumers
The pipeline
pydantic models ──render (locked pydantic)──▶ committed *.json + HASHES.json ──ship in wheel──▶
consumer copies bytes ──schemas-to-ts.mjs──▶ TypeScript module graph
Every hop is deterministic: JSON is emitted with sorted keys and a stable
$defs order; the TypeScript generator uses a fixed banner (no timestamp),
sorted file order, and pinned formatting. The aggregate index.json carries all
models under a single shared $defs, so the generator emits each shared
definition (e.g. the ArtifactKind enum and nested IdentityEvidence) exactly
once — the generated module graph has no duplicate declarations.
Export command modes
# Consumer: copy the immutable committed schemas into ./schemas
uv run python -m clio_schemas.export --out schemas
# Consumer CI: verify a directory matches the committed bytes exactly
# (rejects stale, missing, AND unexpected/orphaned files)
uv run python -m clio_schemas.export --out schemas --check
# DEV: re-render the committed package resources from the models
# (refuses unless the installed pydantic == LOCKED_PYDANTIC_VERSION)
uv run python -m clio_schemas.export --regenerate
# REPO CI: assert committed artifacts are canonical (match models + hashes)
uv run python -m clio_schemas.export --verify
Quick start
uv sync --extra dev # install (locked pydantic)
uv run python -m clio_schemas.export --verify # artifacts are canonical
uv run pytest # golden round-trip + conformance
cd tools/ts-gen && npm ci && npm run check # generate TS + strict typecheck
Legacy extraction compatibility
The P2.1 artifact/provenance records use LegacyToleranceBase so their
clio-agent behavior remains byte- and validation-compatible: unknown keys are
ignored, ordinary Pydantic coercion remains enabled, immutable records stay
frozen, and ArtifactRecord stays mutable. Moving existing records to the
strict ClioSchemaBase contract would be a wire change; that coordinated
convergence is tracked in iowarp/clio-agent#1121.
How to add a model
- Define the pydantic v2 model in
src/clio_schemas/models.py, inheritingClioSchemaBase(strict wire semantics:extra="forbid",strict=True,frozen=True). Give every field aField(description=...)— descriptions flow into the JSON Schema and the generated TS doc comments. - Append the class to the
EXPORTED_MODELStuple in the same file. - Regenerate the committed artifacts and the golden TS, then bump the version:
uv run python -m clio_schemas.export --regenerate # updates schemas/ + HASHES.json cd tools/ts-gen && node schemas-to-ts.mjs \ --in ../../src/clio_schemas/schemas --out ../../tests/golden
- Bump
__version__insrc/clio_schemas/__init__.pyandversioninpyproject.tomlin lockstep (CI enforces a bump wheneverHASHES.jsonchanges), thenuv run pytest.
Versioning policy — exact-pin lockstep
Consumers pin an exact version (clio-schemas==X.Y.Z, not >=). A schema is
a contract between multiple services; a range would let two services resolve
different shapes and reintroduce drift. Therefore:
- Any change to a committed schema (detected via
HASHES.json) requires aclio-schemasversion bump — enforced mechanically in CI byscripts/check_version_bump.py, which compares the hash manifest against the merge base and fails if the version was not incremented. - All consumers (
clio-agent,clio-relay,gact-tui) update their pin to the new exact version in lockstep, in the same coordinated change. - The version lives in exactly two places that must agree:
pyproject.tomlversionandclio_schemas.__version__; CI verifies they match.
Wire evolution
Exact-pin lockstep is the bootstrap rule. As the real records land and the system runs mixed versions during rollouts, the intended evolution discipline is:
- Additive, reader-first staging. Additive changes (new optional field, new
enum member) ship to readers before writers: deploy the version that can
accept the new shape everywhere first, then deploy the writers that emit
it. Readers must ignore-or-tolerate unknown-but-optional additions during the
transition. Extracted P2.1 records preserve their legacy reader-tolerant
behavior through
LegacyToleranceBase; new records useClioSchemaBase. - N / N-1 compatibility. Once real schemas exist, adjacent versions are expected to interoperate: a service on version N and one on N-1 must be able to exchange the records they share for the duration of a rollout. Breaking (non-additive) changes require a two-step migration across at least one intermediate version, never a flag-day.
- Rollback ordering. Roll back in the reverse of deploy order: retire the writers of a new shape before the readers that understand it, so a rolled -back writer never emits a shape an already-rolled-back reader would reject.
Convergence of the extracted records on stricter validation is tracked by iowarp/clio-agent#1121 and requires a coordinated consumer migration.
How consumers regenerate
- clio-agent (Python): pin
clio-schemas==X.Y.Z,import clio_schemas. Its CI copies the committed schemas out of the installed package and--checks any vendored copy for exact-byte + file-set equality (seeci-drafts/clio-agent-schema-check.yml). - clio-relay (Python): same as clio-agent — import the models, pin exact.
- gact-tui (TypeScript): pin
clio-schemas==X.Y.Z, copy the shipped JSON Schema out of the package, and run the in-repo generator (tools/ts-gen, pinned via its lockfile) to produce.ts. Its CI regenerates into a clean dir and compares exact file sets + bytes so untracked/orphaned files are caught (seeci-drafts/gact-tui-ts-gen.yml).
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file clio_schemas-0.2.1.tar.gz.
File metadata
- Download URL: clio_schemas-0.2.1.tar.gz
- Upload date:
- Size: 37.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc04536410e7f220a623058205ca8a7c72d7e307b4427ed79102ee4d8911f950
|
|
| MD5 |
ea27b665931dee7b5b3e41d3962f10ee
|
|
| BLAKE2b-256 |
09f855288e50c25c764660c47da54dc28ae9c488a7f4558cd9ff87f2eb9eaba9
|
File details
Details for the file clio_schemas-0.2.1-py3-none-any.whl.
File metadata
- Download URL: clio_schemas-0.2.1-py3-none-any.whl
- Upload date:
- Size: 25.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1f46462298476b7d4d21b7f4af4f57824d4bee93c5702daf39123e6b4271c1e
|
|
| MD5 |
b7cfc5300e850517973f147503d9c249
|
|
| BLAKE2b-256 |
e14412e4685f17d8cfe72a18e433ac04fa61c16f76cbffd6754e624c6c1d4cf4
|