docs(spec): 重写 spec/ 与根 README 的语言与取舍

按新文风(简洁书面中文)重写 spec/ 全部 Lean doc 注释、spec/README,
并顺根 README。核心:讲清产品逻辑、去伪术语、去 ADR 黑话、DRY。

语言:砍钉死/留痕/实现侧/将就/脑补/刻意等伪术语;短句;不复述文件系统
能看到的东西;typst 考据移出 spec 指向 ADR。

内容取舍(动结构):
- System 层大改:删 can_mono 形式化定理、Capability 9 项枚举与
  requiredRole 映射、RunState 6 构造子;Audit.lean 删除并入 System 顶部。
  Hub 未建的部分一律 prose 占位,只留 Lock 的 owner=run 与 WellFormed。
- 澄清两个"检查":产品 checker(LLM 判不了合法性,checker 真跑工具补这块)
  vs 开发时 spec↔impl 一致性检查(无自动闸门)。Oracle 重新定位为
  "checker 得委托外部工具才能判的事实",不是"Lean 没写形式化"。
- spec/README 补取舍判据 checklist(自顶向下逐步细化、不在 Lean 里验证实现)。
- 根 README 去 DRY:删硬编码版本号、cache 路径细节;宪法第 3 条吸收
  "人/coding assistant 核对"修正;第 5 条与 spec/README 判据去重。

保留:Export/Render 执行语义、Info 的 raw→canonical 设计模式(产品语义,
只顺文风不砍结构);renderIgnoredSeverity(实现对齐依赖)。

lake build 通过(24 jobs)。

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-25 03:22:59 +08:00
parent 4c697904e6
commit 73e9d258d6
24 changed files with 381 additions and 455 deletions
+59 -47
View File
@@ -2,30 +2,39 @@ import Spec.Courseware.Model.Lesson
import Spec.Courseware.Export.Render
/-!
# Diagnostic —— checker 诊断:分类、严重级别、合法 lesson
# Diagnostic —— 产品 checker 诊断
产品里"站在 Lean 位置"的 rule-based checker,语义在此沉淀(ADR-0010,经 ADR-0012
修订)。它对 lesson 提诊断,每条有**分类**(`DiagKind`)与**严重级别**(`Severity`)。
本模块:钉级别类型(二分);钉 7 类诊断各自的含义与级别,并把"**合法 lesson = 无
error 级诊断**"建成判定(ADR-0005 deferred 的"完整合法判定"的回填);对**模型外设施**
型诊断(typst 编过否、数据合 schema 否)用**抽象谓词 + `Oracle` 实现边界**表示——契约
说"存在这条诊断、什么意思、什么级别",真值由实现提供,不在 Lean 内计算(不内嵌 typst
编译器)。引用解析(`@ref`、相对 import)不另设诊断:它们都是 typst 编译期失败,归
`typstCompile`(ADR-0012)。版本契约诊断 `cphVersionMismatch`(ADR-0016)是第 7 类。
这个 codebase 是要拿去卖的产品。LLM 辅助操作提效是它的核心卖点之一——但 LLM 判不了
一节课合不合法:它不能真的去跑 typst 编译器、不能可靠地断言一段数据合不合 schema、
也不能可靠地检查文件齐不齐。所以产品里有一个 rule-based checker 来做这件事:它真跑
工具、给确定性的诊断,补上 LLM 判不了的这块。这个 checker 的语义在这里(ADR-0010,
经 ADR-0012、ADR-0016 修订)。它对 lesson 提诊断,每条有一个分类(`DiagKind`)和一
个严重级别(`Severity`)。
注意区分两个"检查":这里的 checker 是**产品功能**——用户把教研工程文件喂给 `cph`,
检查这个工程文件合不合法。它和"开发时 spec 与实现是否一致"是两回事,后者没有自动
闸门,靠核对。
本模块钉三件事:严重级别二分;7 类诊断各自的含义和级别;"合法 lesson = 无 error 级
诊断"这条判定。其中有些诊断是 LLM 判不了、得 checker 真跑工具才能判的(typst 编不编
得过、数据合不合 schema、content 文件齐不齐)——这些用抽象谓词加 `Oracle` 表示:契约
说"存在这条诊断、什么意思、什么级别",真值由 checker 给(契约不在 Lean 里内嵌 typst
编译器去算)。引用解析(`@ref`、相对 import)不单列:它们都是 typst 编译期失败,归
`typstCompile`(ADR-0012)。版本契约 `cphVersionMismatch`(ADR-0016)是第 7 类。
-/
namespace Spec.Courseware
/-- 诊断严重级别(`PINNED` 二分, ADR-0005)。`error` 阻断(产物不合法),`warning` 不
阻断(产物仍可导出,只是有损)。更细级别(info/hint)未决策,故只二分。 -/
/-- 诊断严重级别(ADR-0005)。`error` 阻断(产物不合法),`warning` 不阻断(产物仍可导出,
只是有损)。更细级别(info/hint)未决策,故只二分。 -/
inductive Severity where
| warning
| error
/-- 诊断**分类**(`PINNED` 7 类, ADR-0010,经 ADR-0012 修订为 6 类,经 ADR-0016 增至 7
类)。按"谁来判定"分三层:**结构型**(模型自身可判):`partPathMissing`/`unknownKind`/
`cphVersionMismatch`;**schema/外部设施型**(靠实现 oracle):
`missingContentFile`/`schemaViolation`/`typstCompile`;**语义型**:`renderIgnored`。 -/
/-- 诊断分类(ADR-0010,经 ADR-0012 折并为 6 类ADR-0016 增至 7 类)。按 checker 怎么判分三层:
结构型(checker 自己按结构判):`partPathMissing`/`unknownKind`/`cphVersionMismatch`;
schema/外部工具型(得真跑工具):`missingContentFile`/`schemaViolation`/`typstCompile`;
语义型:`renderIgnored`。 -/
inductive DiagKind where
/-- manifest 的 part 指向不存在的文件夹(或经 `..` 逃出根)。结构型。 -/
| partPathMissing
@@ -36,21 +45,19 @@ inductive DiagKind where
/-- 实例数据不合其 kind 的 JSON Schema;亦作 manifest/element.toml 畸形的兜底。 -/
| schemaViolation
/-- 拼装出的 typst 源编译失败:语法错、未解析的交叉引用 `@ref`、越界或缺失的相对
`import`/`include`(后两者即旧 `danglingReference` 的两种情形——typst 在编译期
检出,故归此类,ADR-0012)。外部设施型。 -/
`import`/`include`后两者 typst 在编译期检出,故归此类(ADR-0012)。外部工具型。 -/
| typstCompile
/-- 某被用到的 kind 在某声明的 target 下无渲染规则,该 element 被忽略。语义型。 -/
| renderIgnored
/-- 工程文件的 `.cph-version` 与 CLI(cph)版本不相容(ADR-0016)。**结构型**(加载期
判)。工程文件根的 `.cph-version` 声明它所面向的 cph 版本;CLI 加载时按**兼容性判
定**比对自身版本(实现侧 `CARGO_PKG_VERSION`),不相容即产此类。**当前判定为版本
完全相等才相容**(MVP;后续可放宽为 semver 区间,判定逻辑可逐步改而不动本分类)。
`error` 级——版本不相容的工程文件不应被该 CLI 处理。 -/
/-- 工程文件的 `.cph-version` 与 CLI(cph)版本不相容(ADR-0016)。结构型(加载期判)。
工程文件根的 `.cph-version` 声明它所面向的 cph 版本;CLI 加载时比对自身版本,不相容
即产此类。当前判定为版本完全相等才相容(MVP;后续可放宽为 semver 区间,判定逻辑可
逐步改而不动本分类)。`error` 级——版本不相容的工程文件不应被该 CLI 处理。 -/
| cphVersionMismatch
/-- 每类诊断的**严重级别**(`PINNED`, ADR-0010)。六类 `error`(阻断);**
`renderIgnored` 为 `warning`**——ADR-0005 种子规则"缺渲染 ⇒ warning,不阻断导出"。
钉成全函数使"哪类阻断"成为可引用、可对齐的事实(实现侧 `DiagCode` 级别据此对齐)-/
/-- 每类诊断的严重级别(ADR-0010)。六类 `error`(阻断);唯 `renderIgnored` 为 `warning`
——ADR-0005 种子规则"缺渲染 ⇒ warning,不阻断导出"。钉成全函数使"哪类阻断"成为可
引用、可对齐的事实-/
def DiagKind.severity : DiagKind Severity
| .partPathMissing => .error
| .unknownKind => .error
@@ -60,46 +67,51 @@ def DiagKind.severity : DiagKind → Severity
| .renderIgnored => .warning
| .cphVersionMismatch => .error
/-- 缺渲染诊断的级别 = **warning**(`PINNED`, ADR-0005/0010,**非 error**)。具名常量,
使"它是 warning"可被实现 grep 对齐(实现里同名常量 `RENDER_IGNORED_SEVERITY` 引用
本定义)。等价于 `DiagKind.renderIgnored.severity`。 -/
/-- 缺渲染诊断的级别 = warning(ADR-0005/0010,非 error)。具名常量,使"它是 warning"
可被实现 grep 对齐(实现里同名常量 `RENDER_IGNORED_SEVERITY` 引用本定义)。
等价于 `DiagKind.renderIgnored.severity`。 -/
def renderIgnoredSeverity : Severity := DiagKind.renderIgnored.severity
variable (P : Primitives)
/-- **缺渲染诊断**:lesson 在 target `t` 下存在无法渲染的 element(`PINNED`,
ADR-0005/0009)。成立 ⟺ 存在某 element,其 kind 在 `t` 下 `covers` 为假。语义型诊断,
级别 warning。 -/
/-- 缺渲染诊断:lesson 在 target `t` 下存在无法渲染的 element(ADR-0005/0009)。成立 ⟺
存在某 element,其 kind 在 `t` 下 `covers` 为假。语义型诊断,级别 warning。 -/
def renderIgnored (l : Lesson P) (c : RenderConfig P) (t : P.TargetId) : Prop :=
e l, ¬ c.covers e.kind t
/-!
## 模型外设施型诊断:抽象谓词 + 实现边界(ADR-0010)
## 要真跑工具才能判的诊断:抽象谓词 + Oracle
`typstCompile`/`schemaViolation`(的 schema-合规面)断言的是模型自身无法判定的事实——
要跑 typst 编译器、schema 校验器。契约把它们建成**抽象谓词**,真值由实现提供的 oracle
给出。下面用 `Oracle` 收口这些判定:它不是要在 Lean 里实现 checker,而是把"这些事实
来自模型外"显式化、类型化
诊断分两类(按 checker 怎么判):有些 checker 自己按工程文件结构就能判(part 路径
在不在、kind 知不知道、某 kind 在某 target 下有没有被覆盖);有些 checker 自己也判
不了,得真跑外部工具——typst 编不编得过(要跑 typst 编译器)、数据合不合 schema(要
跑 schema 校验器)、content 文件齐不齐(要看磁盘)。后者就是 `Oracle` 收口的
`Oracle` 把这些"得 checker 委托外部工具才能判"的事实建成抽象谓词,真值由 checker 给。
它不是要在 Lean 里实现 checker,而是把"这几件事 checker 自己算不了、得委托出去"显式
表达、类型化。它只收 Legal 需要的、得委托外部工具的事实;checker 自己能判的(如
`renderIgnored`)不进 Oracle。
(这两类 checker 都判得了;但 LLM 两类都判不了——这正是产品里要有个 rule-based
checker 的理由,见本模块顶部。)
-/
/-- **实现侧判定 oracle**(`PINNED` 实现边界, ADR-0010)。每个字段是一个谓词,真值由
实现(checker)提供。结构型诊断(part 路径、未知 kind)不入此 oracle——那些模型自身可判-/
/-- checker 委托外部工具才能判的那些事实(ADR-0010)。每个字段是一个谓词,真值由
checker 提供。checker 自己按结构就能判的诊断(part 路径、未知 kind)不入此 oracle。 -/
structure Oracle (l : Lesson P) (c : RenderConfig P) where
/-- target `t` 下拼装源可编译(否 ⇒ `typstCompile`)。**含引用解析**:源能编译即蕴含
`@ref`、相对 import 全部解析(ADR-0012 已把引用诊断并入 `typstCompile`)。 -/
/-- target `t` 下拼装源可编译(否 ⇒ `typstCompile`)。含引用解析:源能编译即蕴含
`@ref`、相对 import 全部解析(ADR-0012)。 -/
compiles : P.TargetId Prop
/-- 每个 element 数据合 schema(否 ⇒ `schemaViolation`)。 -/
dataConforms : Prop
/-- schema 要求的 content 文件齐备(否 ⇒ `missingContentFile`)。 -/
contentFilesPresent : Prop
/-- **合法 lesson**(`PINNED`, ADR-0010;回填 ADR-0005 deferred 的"完整合法判定")。
合法 ⟺ 检查管线产出**零条 error 级诊断**。展开为:模型外设施判定(经 `Oracle`)全为真,
**且**每个声明的 target 都编译通过。结构型诊断由 `cph-model` 在加载期判定;能走到这步
谈合法性意味着已加载成功,故此处聚焦 schema/外部设施层。引用解析不单列——已被
`compiles` 蕴含(ADR-0012)。`renderIgnored` 是 warning,**不**进合取——ADR-0005 种子
规则的体现。`targets` 用全称式表达以不绑定 `TargetId` 的可枚举性。 -/
/-- 合法 lesson(ADR-0010)。合法 ⟺ 检查管线产出零条 error 级诊断。展开为:得委托外部
工具的判定(经 `Oracle`)全为真,且每个声明的 target 都编译通过。checker 自己按结构
能判的诊断(part 路径、未知 kind)在加载期已判;能走到这步谈合法性意味着那些已过,
故此处聚焦 schema/外部工具层。`renderIgnored` 是 warning,不进合取(ADR-0005 种子
规则)。`targets` 用全称式表达以不绑定 `TargetId` 的可枚举性。 -/
def Legal (l : Lesson P) (c : RenderConfig P) (o : Oracle P l c) : Prop :=
o.dataConforms o.contentFilesPresent
( t : P.TargetId, (c.spec t).isSome o.compiles t)
+16 -19
View File
@@ -1,26 +1,24 @@
import Spec.Courseware.Check.Diagnostic
/-!
# Pipeline —— checker 检查管线的阶段与序(ADR-0010)
# Pipeline —— 检查管线的阶段与序
checker 的 `check` 按**固定顺序**跑五个阶段,逐阶段收集诊断;`compile` 阶段有**门控**。
顺序与门控是契约——它决定用户看到哪些诊断(藏在缺文件背后的语法错,在文件补齐前不
显示,这是有意的)。每阶段的**算法**不进 Lean(宪法第 5 条深度上限):只钉**阶段、序、
门控**。阶段对应上游模块:`load`←`cph-model`;`structural`←part 路径/未知 kind;
`schema`←`cph-schema`;`compile`←`cph-typst`(模型外设施);`coverage`←`renderIgnored`。
checker 的 `check` 按固定顺序跑五个阶段,逐阶段收集诊断;`compile` 阶段有门控。顺序和
门控是契约——它决定用户看到哪些诊断(藏在缺文件背后的语法错,在文件补齐前不显示,这是
有意的)。每阶段的算法不进 Lean(宪法第 5 条深度上限):只钉阶段、序、门控。
-/
namespace Spec.Courseware
/-- 检查管线的**阶段**(`PINNED` 5 阶段, ADR-0010)。
/-- 检查管线的阶段(ADR-0010)。
- `load` —— 解析 manifest + 各 element.toml。**含 `.cph-version` 兼容性判定**
(ADR-0016:工程根 `.cph-version` 与 CLI 版本不相容 ⇒ `cphVersionMismatch` error)。
硬失败(无法解析 lesson)则**停**整条管线。
- `load` —— 解析 manifest + 各 element.toml。含 `.cph-version` 兼容性判定(ADR-0016:
工程根 `.cph-version` 与 CLI 版本不相容 ⇒ `cphVersionMismatch` error)。硬失败(无法
解析 lesson)则整条管线。
- `structural` —— part 路径存在、无 `..`、kind 已知且一致。此处判缺的 part 后续跳过。
- `schema` —— 每个"存在且 kind 已知"的 part 按其 kind schema 校验。
- `compile` —— 模型外设施阶段(typst 编译)。**门控:仅当前序零 error 才跑**
- `coverage` —— 语义型 warning(`renderIgnored`);**不**受门控,总跑。 -/
- `compile` —— 跑外部工具的阶段(typst 编译)。门控:仅当前序零 error 才跑。
- `coverage` —— 语义型 warning(`renderIgnored`);受门控,总跑。 -/
inductive Phase where
| load
| structural
@@ -29,8 +27,8 @@ inductive Phase where
| coverage
deriving DecidableEq
/-- 管线阶段的**执行序**(`PINNED`, ADR-0010)。`order p` 越小越先跑。序是契约:
`compile`(3)排在 `structural`(1)/`schema`(2)之后,正因前者门控于后者无 error。 -/
/-- 管线阶段的执行序(ADR-0010)。`order p` 越小越先跑。序是契约:`compile`(3)排在
`structural`(1)/`schema`(2)之后,正因前者门控于后者无 error。 -/
def Phase.order : Phase Nat
| .load => 0
| .structural => 1
@@ -38,15 +36,14 @@ def Phase.order : Phase → Nat
| .compile => 3
| .coverage => 4
/-- 某阶段是否**受"前序零 error"门控**(`PINNED`, ADR-0010)。唯 `compile` 受门控:藏在
结构/schema 错背后的编译错,在前者修好前不显示——有意降噪。 -/
/-- 某阶段是否受"前序零 error"门控(ADR-0010)。唯 `compile` 受门控:藏在结构/schema 错
背后的编译错,在前者修好前不显示——有意降噪。 -/
def Phase.gated : Phase Bool
| .compile => true
| _ => false
/-- 管线在 `load` 硬失败时**停**(`PINNED`, ADR-0010)。`load` 拿不到可解析 lesson 时,
无 lesson 可喂下游,整条管线终止——唯一会截断后续所有阶段的情形(区别于 `compile`
门控只跳过自己)。 -/
/-- 管线在 `load` 硬失败时停(ADR-0010)。`load` 拿不到可解析 lesson 时,无 lesson 可喂
下游,整条管线终止——唯一会截断后续所有阶段的情形(区别于 `compile` 门控只跳过自己)。 -/
def Phase.haltsPipelineOnFailure : Phase Bool
| .load => true
| _ => false