# 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 <cli-version>, found <file-version> 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.
