# ADR 0016: The `.cph-version` Contract File ## Status Accepted — and implemented. The cph CLI refuses to process an engineering file whose `.cph-version` is not compatible with the running CLI, surfacing this as a 7th diagnostic class `cphVersionMismatch`. Builds on **ADR-0005** (the engineering file is the asset; its structure is declared), **ADR-0008** (the engineering file is a directory tree rooted at `manifest.toml`), and **ADR-0010/0012** (the diagnostic taxonomy and the "legal lesson = no error diagnostics" rule). ## Context As the cph tool and the authored curriculum engineering files evolve independently, a file authored against one version of the format may not be processable by a CLI built against another. Without a version contract, a mismatch surfaces as confusing downstream failures (a malformed-manifest diagnostic, a compile error, or a silently-wrong build) rather than as the real cause: "this file expects a different cph than the one you're running." The user's framing: the curriculum engineering-file workspace should carry a `.cph-version` file naming the cph version it targets; the CLI should **fail early** when that version is not compatible with itself. The compatibility rule can be refined over time, but for now it is the strictest possible: **exact version equality**. ## Decision ### A `.cph-version` file at the engineering-file root An engineering file's root (alongside `manifest.toml`) carries a `.cph-version` file whose entire content is the version string the file targets (e.g. `0.0.2`, no trailing newline required, whitespace trimmed). The CLI reads its own version from `CARGO_PKG_VERSION`. ### Compatibility is decided at `load` time and is a diagnostic The version check runs in the `load` phase (`cph-model::load`), before any structural/schema/compile work. An incompatible version produces a `cphVersionMismatch` **error** diagnostic — not a CLI-level early exit without a diagnostic. This keeps the failure inside the checker's normal diagnostic vocabulary: `cph check` reports it and exits 1 (which *is* an early refusal — load-phase errors halt the pipeline), and `cph build` refuses to run. Because it is an error diagnostic, an incompatible-version file is, by the contract, **not a legal lesson** (ADR-0010's "legal = no error diagnostics"). The diagnostic carries a hint pointing the user at both versions and at the file (`expected , found in .cph-version`). ### The 7th diagnostic class `cphVersionMismatch` The taxonomy grows from six to seven classes. `cphVersionMismatch` is **structural** (the loader decides it from a file read — no oracle needed), like `partPathMissing`/`unknownKind`; its severity is `error`. This is a real divergence point (the taxonomy is spec-pinned), so it lands in Lean (`Spec.Courseware.Check.Diagnostic.DiagKind`) and in `DiagCode`. ### The compatibility rule is exact equality, for now — and is replaceable The current rule is: **the file's `.cph-version` must exactly equal the CLI's version.** This is deliberately the strictest choice — it fails loudly on any drift, which is the right default while the format is young (0.0.x). The rule is isolated in one function (`cph_model::versions_compatible`), so it can be relaxed later to a semver range (e.g. "same major" or "compatible minor") **without touching the diagnostic class, the spec, or the CLI** — only that one predicate changes. The taxonomy and the "it's a `cphVersionMismatch` error" contract are stable across that evolution. ### A missing `.cph-version` is not (yet) an error If the file has no `.cph-version`, the check is skipped (no diagnostic) for now — existing files and the `mini`/`TH-141` fixtures predate the contract. Whether a missing file should itself be a `cphVersionMismatch` (or a softer warning) once the ecosystem migrates is left open. The KenKen lesson and this repo's examples carry the file as the migration's starting point. ## Consequences - A curriculum file now declares the cph version it was authored against; the CLI refuses incompatible versions with a clear diagnostic rather than a downstream mystery. - The diagnostic taxonomy is seven classes (was six). The Lean master, the `DiagCode` enum, and the severity mapping all gain `cphVersionMismatch`. - The compatibility predicate is the single place future semver relaxation lands — a deliberate seam. - `cph check`/`build` against the repo's own examples requires those examples' `.cph-version` to match the workspace version; the release commit updates them together. ## Open Questions / Deferred - **Missing-file policy.** Whether an absent `.cph-version` should become an error (forcing migration) or a warning — deferred until the contract is broadly adopted. - **Semver relaxation.** The exact equality rule is a placeholder; the real compatibility intervals (per ADR-0014-style "what's a breaking change") are settled when there is a real breaking change to justify loosening. - **Manifest vs file.** The version lives in a sibling file, not a `[project] cph-version = …` manifest key. This mirrors how a toolchain version is often a separate file (`.tool-versions`, `rust-toolchain`); whether it should migrate into the manifest is open.