Skip to main content

easy-cheese-schemas

The machine-readable artifact contracts of easy-cheese, published as an installable package: run manifests, decompositions, curd blocks, PR plans, and the readiness gate, as attrs types with cattrs structuring and explicit schema-version tolerance.

pip install easy-cheese-schemas

Requires Python 3.11 or newer. cattrs floors at 3.10; 3.11+ skips the exceptiongroup dependency, and 3.10 reaches end of life in October 2026.

What this is, and what it is not

It is the artifact vocabulary: the types an external producer or consumer needs to write or read an easy-cheese document without reimplementing its field rules.

It is not the enforcement path — not yet. In v0.1 the types are derived from easy-cheese's existing hand-rolled validators (src/fanout/validate_*.py), which still run unchanged inside the repo. A conformance suite pins the two together: every fixture the validators accept must structure cleanly through these types, and every fixture they reject must fail to structure. Until the validators are migrated onto these types, treat that suite as the reason to trust them, and expect the derived-types arrangement to be retired rather than extended.

Also not published: easy-cheese's corpus and layout assumptions (paths.py) and the findings report grammar. Those are repo-internal and stay unversioned on purpose.

Not enforced in v0.1

The types check field shape and the collection invariants below, but a document that satisfies them can still be rejected by src/fanout/validate_*.py, which enforces cross-field rules these types do not yet carry. Structuring cleanly is therefore necessary, not sufficient. Do not read a clean Loaded as "easy-cheese will accept this".

Enforced here: required fields and their types, enum membership, curd file-disjointness, wiring DAG acyclicity and unknown W<n> references, wave size and curd surface floors, and the PR-plan shape rules.

Not enforced here, and checked only by the validators:

  • agent_resolution consistency — exactly one accepted attempt; the resolved agent matching that attempt; attempt and resolved power meeting the request's minimum_power; prompt-only permission enforcement implying degraded and a read-only request; an unknown resolved power implying degraded; a preferred-exact acceptance carrying a null fallback_reason (and a non-preferred one carrying a reason).
  • Phase-dependent requirementsphase of post_review_complete or pr_publish_complete requiring current_review / post_review.
  • Curd lifecycle — a curd with status: completed requiring review_context.

These are the accepted derivation gap described above, not oversights, and they are retired when the validators migrate onto these types.

Usage

load structures a raw mapping into one of the artifact types. It never raises: it returns a Loaded carrying the value (or None), the payload's provenance, and every problem it found, in the same where.key must be ... format easy-cheese's validators emit.

from easy_cheese_schemas import PrGroup, load

plan = {
    "schema_version": 1,
    "branch": "feat/publish",
    "title": "Publish the schemas package",
    "base": "main",
    "commits": ["9f2c1ab"],
}
strict = load(plan, PrGroup, strict=True)
print(strict.provenance, strict.problems)
print(strict.value)

# Written by a newer producer: unknown fields are ignored, the rest still parses.
newer = load(plan | {"schema_version": 2, "reviewers": ["ada"]}, PrGroup, strict=True)
print(newer.provenance, newer.value.branch)

# Lenient: gaps are reported, not raised, and the value is still usable.
lenient = load(plan | {"depends_on": None}, PrGroup, strict=False)
print(lenient.provenance, lenient.problems)
print(lenient.value)
Provenance.CURRENT ()
PrGroup(branch='feat/publish', title='Publish the schemas package', base='main', commits=['9f2c1ab'], body=None, depends_on=[])
Provenance.FUTURE feat/publish
Provenance.CURRENT ('PrGroup.body must be present; using default', 'PrGroup.depends_on must be a list, not NoneType')
PrGroup(branch='feat/publish', title='Publish the schemas package', base='main', commits=['9f2c1ab'], body=None, depends_on=[])

Problems accumulate; load reports every one it found in a single pass rather than stopping at the first, because the callers reporting them to a human need the whole list. problems is a tuple[str, ...]Loaded is frozen, and a mutable list on a frozen carrier is an invitation to edit the evidence.

Values are not type-coerced. A str where a list[str] belongs is a problem, not eight one-character paths; "no" where a bool belongs is a problem, not True. Reading a document is how you find out whether to trust it, so this layer reports the mismatch rather than papering over it.

One case returns a value and problems: a schema_version stamp that is not an integer is recorded as a problem and the payload is treated as UNSTAMPED, because an untrustworthy stamp is not a reason to discard an otherwise readable document. Callers that gate on value is not None should check problems too.

The schema_version contract

SCHEMA_VERSION = 1   # what this package writes and fully understands
MIN_READABLE = 1     # oldest stamp still readable; widens as the schema evolves
STAMP_KEY = "schema_version"

The real coupling is the data boundary, not the code boundary. A vendored bundle is internally consistent by construction — it ships its own copy of these types — but persisted artifacts in the durable corpus outlive the upgrade that rewrote the reader, and external producers are the entire point of publishing. So every payload carries its vintage, and a reader can tell what it is holding before it acts on it.

classify_stamp maps the stamp to one of five outcomes:

Provenance Stamp condition Meaning
CURRENT stamp == SCHEMA_VERSION Written against this exact schema.
PRIOR MIN_READABLE <= stamp < SCHEMA_VERSION One or more versions behind, still readable — the N-1 tolerance.
UNSTAMPED key absent (or present but not an int, which is also reported as a problem) No stamp. Normal for a hand-authored document, foreign for a manifest.
STALE stamp < MIN_READABLE Older than this reader supports.
FUTURE stamp > SCHEMA_VERSION Written by a newer easy-cheese.

FUTURE is not a rejection. Recognized fields parse, unknown fields are ignored, and the caller gets provenance=FUTURE so it can decide whether to act, warn, or refuse. Provenance is orthogonal to validity: a FUTURE payload can still be structurally invalid, and a CURRENT one can still collect problems.

Two-tier strictness by artifact class

load does not decide strictness — strict is the caller's keyword argument. The tiers are a convention about which default each artifact class deserves:

  • Markdown documents (specs, findings, handoffs) are hand-authored and hand-edited. A missing stamp is normal, so they are read lenient: documented defaults are filled in, every gap is recorded in problems, and the caller still gets a usable value.
  • JSON/YAML manifests are machine-written. A missing stamp means the payload is foreign, so they are read strict: full cattrs validation, with the raised exception group flattened into problems and value=None on failure. An explicit --lax escape exists at the CLI boundary for the case where a human is knowingly hand-repairing a manifest.

Stability policy

The package is semver'd, and the version governs the artifact contracts, not just the Python API. SCHEMA_VERSION and the package version move independently: a schema bump is always a major bump, but not every major bump is a schema bump.

  • Patch — no contract change. Bug fixes, problem-message wording, documentation.
  • Minor — additive only. New optional fields, new types, new exports. Every artifact a previous minor could write is still read as CURRENT, and every artifact a previous minor could read still parses.
  • Major — a breaking contract change: a removed or renamed field, a narrowed type, a field made required, or a raised MIN_READABLE. Accompanied by a SCHEMA_VERSION bump, so previously-written artifacts become PRIOR or STALE rather than silently misparsing.

The additive-only discipline is what makes FUTURE provenance safe, and it is a hard rule, not a preference. Because unknown fields are ignored, an older reader handed a newer artifact will silently drop anything it does not recognize. That is correct behavior when new fields are additive and optional, and it is data loss when they are not — the older reader would produce a value that looks complete and is not. So within a SCHEMA_VERSION, new fields must be additive and optional. Anything else waits for the major bump.

Pre-1.0 caveat: while the version is 0.x, minor bumps may break. The policy above binds from 1.0.

Releasing (repo owner)

A pending publisher must exist on PyPI before the first publish. Publishing uses PyPI Trusted Publishing (OIDC), so no long-lived token is stored in the repository — but that means PyPI has to be told, in advance, which workflow in which repository is allowed to publish. Create the pending publisher on PyPI with the project name, the repository, and the publishing workflow's filename. This is a one-time manual step and it cannot be automated: without it, the first release run fails at upload.

After that, releases are automatic — a version bump to pyproject.toml landing on main triggers the publish.

License

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

easy_cheese_schemas-1.0.0.tar.gz (74.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

easy_cheese_schemas-1.0.0-py3-none-any.whl (81.9 kB view details)

Uploaded Python 3

File details

Details for the file easy_cheese_schemas-1.0.0.tar.gz.

File metadata

  • Download URL: easy_cheese_schemas-1.0.0.tar.gz
  • Upload date:
  • Size: 74.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for easy_cheese_schemas-1.0.0.tar.gz
Algorithm Hash digest
SHA256 f833fcf4ceaa0dec939b605f4bc635bd34ddacf801e566b1195f3fdf51e6b52f
MD5 a3de5b7e88bb94331fed87eafc84060c
BLAKE2b-256 9d7491941e557aa09b19a7ac9fa0c1b13fecbfd996f7f01067d7c6e21115702e

See more details on using hashes here.

Provenance

The following attestation bundles were made for easy_cheese_schemas-1.0.0.tar.gz:

Publisher: publish-pypi.yml on paulnsorensen/easy-cheese

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file easy_cheese_schemas-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for easy_cheese_schemas-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 219df2ce294ac635396d56fe1950b726ed9ffd985599ddcf966939e413849c93
MD5 46a6f62fe0c94c99fed4ec0f13ec11a1
BLAKE2b-256 d16c48fe903c470fd0dc5ae8c6e801527c077c0d0cee32a2439286790f133643

See more details on using hashes here.

Provenance

The following attestation bundles were made for easy_cheese_schemas-1.0.0-py3-none-any.whl:

Publisher: publish-pypi.yml on paulnsorensen/easy-cheese

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.1.0

2 files

This release

1.0.0 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page