Files
curriculum-project-hub/docs/adr/0010-diagnostics-legal-lesson-and-check-pipeline.md
T
sjfhsjfh 81cf762a0c docs(adr): add ADR-0009 (export target = build) + ADR-0010 (diagnostics/legality/pipeline)
Lift business decisions made during the MVP build into the contract, per
constitution rule 5 (divergence points belong in the spec).

ADR-0009 — amends ADR-0005's render-matrix framing: an export target is a *build*
producing a typed Artifact (SingleFile | FileTree). build = map (per-kind, the
field-visibility lives here) + reduce (assemble, determined by artifact type:
SingleFile concatenates in lesson order then compiles — which is why @ref and
counters work). Build form = declarative schema + a run-shell hatch (where
ADR-0005's medium-only category (b) and npm/HTML builds live), not a Makefile.
typst compile is hidden backend mechanism, not part of a target. Defaults seeded
at project creation, overridable in the file — the heading-numbering bug
(一、 reused → 二、一、) is the worked example of that missing override layer.

ADR-0010 — backfills ADR-0005's deferred "legal lesson": the 7-class diagnostic
taxonomy with per-class meaning + severity; legal lesson = no error-level
diagnostic; the 5-phase pipeline (load→structural→schema→compile→coverage) with
compile gated on zero prior errors. External-facility diagnostics (TypstCompile,
DanglingReference, schema conformance) modeled as abstract predicates whose
verdict comes from an implementation oracle, not from Lean — keeps the contract
honest about what it does and doesn't decide.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 08:33:11 +08:00

7.8 KiB
Raw Blame History

ADR 0010: Checker Diagnostics, Legal Lesson, And The Check Pipeline

Status

Accepted — for the decisions below. Backfills the "full lesson legality" item ADR-0005 explicitly deferred ("the complete definition of a legal lesson; this ADR fixes only the one seed rule"). Builds on ADR-0005's seed (missing render ⇒ warning) and ADR-0009 (target = build). Some items deferred; see Open Questions.

Context

The checker is the product's moat — the thing that "stands in Lean's position" at runtime, validating an engineering file and emitting helpful fix hints. The MVP implementation grew a diagnostic taxonomy (seven codes), a notion of when a lesson is legal (publishable / buildable), and an ordered check pipeline — and then treated all three as "implementation detail." They are not. What each error class means, whether it blocks, what "legal" is, and in what order checks run with what gating are exactly the non-obvious domain decisions the constitution (rule 5) says must be pinned: the developer and the agent would otherwise each assume a different answer. This ADR lifts them into the contract.

A deliberate boundary runs through the taxonomy. Some checks the model can state structurally (a manifest part points at a folder that exists; a declared kind is known). Others are external facilities the semantic model cannot itself decide — does this typst source compile? do its cross-references resolve? does this data conform to its JSON Schema? Those are real, contractually-named diagnostics, but their verdict comes from an implementation oracle (the typst compiler, the schema validator), not from Lean. The contract names them and fixes their severity; it does not pretend to decide them.

Decision

The diagnostic taxonomy (seven classes)

Each diagnostic carries a code, a severity (error | warning, per ADR-0005 / Spec.Courseware.Severity — two-valued, no info/note), a message, an optional source span, and a fix hint. The MVP classes:

Code Meaning Severity Kind
PartPathMissing A manifest [[parts]] entry points at a folder that does not exist (or escapes the root via ..). error structural
UnknownKind A part declares a kind not in the known kind set (and: a part/element.toml kind mismatch). error structural
MissingContentFile A content field required by the kind's schema has no corresponding <field>.typ on disk. error structural/schema
SchemaViolation Instance data does not conform to its kind's JSON Schema (bad scalar type, missing required, stray key); also the fallback for a malformed manifest.toml/element.toml. error schema / external
DanglingReference A reference does not resolve — a relative import outside the import boundary, or an unresolved cross-reference. error external
TypstCompile The assembled typst source fails to compile (syntax error, unresolved @ref, etc.). error external
RenderIgnored A used kind has no render rule for a declared target; the element is omitted from that export. warning semantic

Six are error (blocking); RenderIgnored alone is a warning — the ADR-0005 seed rule, restated. The split between structural (model decides), schema/external (oracle decides), and semantic (the coverage rule) is the boundary described above.

RenderIgnored's severity is pinned to warning as a named contract fact (Lean: renderIgnoredSeverity), so "it does not block export" is greppable and alignable, not an inline literal.

A lesson is legal (valid, publishable) iff the check pipeline produces no error-severity diagnostic. Warnings do not affect legality — a lesson with RenderIgnored warnings is still legal and still exports (lossily). This is the complete legality predicate ADR-0005 deferred: legality is defined by the diagnostic set, not by a separate hand-written rulebook. New rules enter legality by adding diagnostics, keeping one source of truth.

External-facility diagnostics are oracle verdicts, not model judgments

TypstCompile, DanglingReference, and the schema-conformance face of SchemaViolation assert facts the semantic model cannot decide on its own: they depend on running the typst compiler and the schema validator. The contract therefore models them as abstract predicates (e.g. "this source compiles", "these references resolve", "this data conforms") parameterized over an implementation-provided oracle. The Lean spec states that such a diagnostic exists, what it means, and its severity — and explicitly marks that its truth value is supplied by the implementation, not computed in Lean. This keeps the contract honest (it does not embed a typst compiler) while still naming the diagnostic as part of the legality definition.

The check pipeline: ordered phases with compile gating

check runs five phases in a fixed order, collecting all diagnostics:

  1. load — parse manifest.toml + each element.toml. A hard failure (no parseable lesson) stops the pipeline; nothing downstream can run.
  2. structural — every part path exists, no .., kind is known, part/element kinds agree. Parts already flagged missing here are skipped downstream (no piling diagnostics on one root cause).
  3. schema — each present, known-kind part's data validated against its kind schema (scalars + required content files).
  4. typst compile — the external-facility phase. Gated: it runs only if phases 13 produced zero errors, because compiling known-broken input yields noise on top of the real cause. When it runs, it compiles each declared target (ADR-0009), deduping identical diagnostics across targets.
  5. render-coverage — the semantic warning rule; runs regardless of gating.

build --target T runs phases 13, refuses (no artifact) if any error, else compiles + exports T's artifact (ADR-0009). It does not run coverage — coverage describes export loss, informational for check, not a gate on one target's build.

The order and the gating are contract, because they determine which diagnostics a user sees (a syntax error behind a missing file is hidden until the file exists — deliberate). The per-phase algorithms are not pinned in Lean (depth ceiling, rule 5): the spec fixes the phases, their order, and the gate, not how each check is computed.

Consequences

  • "Is this lesson done / publishable?" has one precise answer: no error-level diagnostic. The CLI's exit code (1 on any error) is this predicate.
  • Adding a checker rule = adding a diagnostic with a severity; legality updates automatically, no separate legality rulebook to keep in sync.
  • The contract can name compile/reference/schema diagnostics as part of legality without embedding the oracles that decide them — the spec stays a semantic master, not a re-implementation.
  • The implementation's DiagCode enum, CheckReport, and phase sequence now have a contract to align to (by review, per the constitution — no CI gate); the alignment is documented at both ends.

Open Questions / Deferred

  • Additional codes. A dedicated ManifestMalformed (today folded into SchemaViolation) and finer schema/reference codes may be added; the taxonomy is open to growth, each addition a deliberate decision.
  • Cross-lesson diagnostics. Anything about a course (arrangement of lessons) or the question bank is out of scope — those models are still OPEN (ADR-0005).
  • Severity of future classes. Each new diagnostic's blocking/non-blocking status is decided when introduced; only the seven above are fixed here.
  • Warning sub-structure. Whether warnings ever need ordering/grouping or a third "info" level remains deferred (ADR-0005 kept Severity two-valued).