forked from EduCraft/curriculum-project-hub
feat(model+spec): .cph-version 契约文件 + CphVersionMismatch 诊断(ADR-0016);bump 0.0.2
教研工程文件根放 .cph-version(内容=面向的 cph 版本)。cph 加载时比对自身版本
(CARGO_PKG_VERSION),不相容报 CphVersionMismatch error 并拒绝。当前判定为版本完全
相等(MVP);判定逻辑孤立在 versions_compatible 单谓词,后续可放宽为 semver 区间而
不动诊断分类/spec/CLI。
spec(7 类诊断,原 6 类):
- Diagnostic.lean: DiagKind 增 cphVersionMismatch(error 级),分类注释 6→7
- Pipeline.lean: load 阶段含 .cph-version 兼容性判定
- Courseware.lean: ADR 区间 →0016
实现:
- cph-diag: DiagCode::CphVersionMismatch("E-CPH-VERSION")
- cph-model: load() 解析 manifest 成功后 check_cph_version;versions_compatible
谓词(完全相等);CPH_VERSION const。missing .cph-version 暂跳过(迁移期 OPEN);
空文件报 error
- examples/TH-141、valid/mini fixture、KenKen 课各加 .cph-version=0.0.2
测试:cph-model 4 个版本门测试;全 workspace 53 passed;lake 25 jobs 绿。
负向验证:9.9.9 .cph-version → E-CPH-VERSION error + check 拒绝(exit 1)。
bump: workspace.package 0.0.1→0.0.2;cph --version 报 cph 0.0.2;README 同步
(含版本契约说明)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user