forked from EduCraft/curriculum-project-hub
chore(spec+checker): cleanup pass — trim docs, reorg Courseware, covers-as-data, fold DanglingReference (ADR-0012)
Spec母本噪音清理 + 一处 spec↔impl 对齐 + 一处契约自洽修正。 Part A — spec doc 瘦身:每条 doc 收到"语义点 + 标签 + ADR ref + 承载性 why", 把跨文件复读的方法论(element-kind 开放性对比、likec4 画不出、分歧点测试)上移到 module header。OPEN 框架保持清晰(RunState/Capability 完整性仍明示须 surface)。 Part B — Courseware/ 由 10 文件平铺重组为 Model/ Export/ Check/ Open/ 四子命名空间 (namespace Spec.Courseware 不变,零引用改动)+ 四个子 aggregator。lake build 绿(24 jobs)。 Part C — 渲染覆盖落为数据(ADR-0011 对齐):去掉 cph-check 里硬编码的 COVERED_TARGETS,改读 TargetConfig.covers。cph-model 新增 covers: Option<Vec<String>> (None=未声明,由 cph-check 默认为全部 known kinds;显式 [] 表示不覆盖任何 kind)。 新增 3 个覆盖行为测试。 ADR-0012 — DanglingReference 退役(诊断 7→6 类):其两种情形(未解析 @ref、越界/缺失 相对 import)都是 typst 编译期失败,归 typstCompile。同步移除 Oracle.refsResolve (被 compiles 蕴含),Legal 少一个合取项。impl 删去从未被发射的 DiagCode::DanglingReference, 闭合 named-but-unemitted 的 spec↔impl 缝。ADR-0010 加修订指针。 OPEN 点(RunState/Capability 完整性、QuestionBank、Course)按既定纪律保持 OPEN,本次 只改善其框架措辞,不决策。 验证:spec lake build 绿;cargo test 全绿(含新增覆盖测试);clippy 静默; TH-141 check 0 errors/0 warnings 无回归。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,35 +0,0 @@
|
||||
/-!
|
||||
# Artifact —— export target 的产物(ADR-0009 / 0011)
|
||||
|
||||
ADR-0009:一个 export target 是**一次 build**,产出一个**有类型的产物**(artifact)。
|
||||
ADR-0011 进一步钉死:产物不是一个光秃的标签,而是**带字段的 ADT**——"产物到底指什么"
|
||||
(单个文件落在哪 / 一棵树产出哪些文件)是不好猜的领域语义,必须写进字段 + doc,而非
|
||||
抹成 `singleFile | fileTree` 两个空构造子。
|
||||
|
||||
路径/glob 在此用 `String` 承载并由 doc 赋义(它们就是文本:一个相对路径、一个 glob
|
||||
模式),不复刻文件系统类型——契约只钉"这个字段是什么",不建模路径代数。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
/--
|
||||
export 产物(`PINNED` 带字段 ADT, ADR-0011)。
|
||||
|
||||
- `singleFile (filepath)` —— 产物是**单个文件**,落在 `filepath`(相对工程根的路径)。
|
||||
讲义 PDF、教案 PDF 即此。`filepath` 说清"这一份产物写到哪"。
|
||||
- `fileTree (root) (outputs)` —— 产物是 `root` 目录下、匹配 `outputs` **glob** 的**一组
|
||||
文件**(第三方平台 archive 即此)。用 glob 而非显式清单:比逐个枚举轻,又让消费方/
|
||||
checker 知道"这次 build 该产出哪些文件"、可据以校验产物完整性。
|
||||
|
||||
**为什么字段进契约**:产物形状决定 `cph build` 吐一个文件还是一个目录、消费方期待什么;
|
||||
也决定 reduce/assemble 怎么折叠(`singleFile` 把有序片段拼成一份再编译——这也是交叉引用
|
||||
`@ref` 与 例题N 计数器能工作的前提;`fileTree` 每 part 落一文件)。后端/格式(哪个 PDF
|
||||
引擎、哪种 markdown 方言)仍 OPEN(ADR-0009),不在字段里。
|
||||
-/
|
||||
inductive Artifact where
|
||||
/-- 单文件产物,落在 `filepath`(相对工程根)。 -/
|
||||
| singleFile (filepath : String)
|
||||
/-- 多文件产物:`root` 目录下匹配 `outputs` glob 的文件集。 -/
|
||||
| fileTree (root : String) (outputs : String)
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -0,0 +1,9 @@
|
||||
import Spec.Courseware.Check.Diagnostic
|
||||
import Spec.Courseware.Check.Pipeline
|
||||
|
||||
/-!
|
||||
# Courseware.Check —— checker 语义
|
||||
|
||||
诊断分类与合法 lesson(`Diagnostic`)、检查管线的阶段与序(`Pipeline`)。决策出处
|
||||
ADR-0010。
|
||||
-/
|
||||
@@ -0,0 +1,100 @@
|
||||
import Spec.Courseware.Model.Lesson
|
||||
import Spec.Courseware.Export.Render
|
||||
|
||||
/-!
|
||||
# Diagnostic —— checker 诊断:分类、严重级别、合法 lesson
|
||||
|
||||
产品里"站在 Lean 位置"的 rule-based checker,语义在此沉淀(ADR-0010,经 ADR-0012
|
||||
修订)。它对 lesson 提诊断,每条有**分类**(`DiagKind`)与**严重级别**(`Severity`)。
|
||||
本模块:钉级别类型(二分);钉 6 类诊断各自的含义与级别,并把"**合法 lesson = 无
|
||||
error 级诊断**"建成判定(ADR-0005 deferred 的"完整合法判定"的回填);对**模型外设施**
|
||||
型诊断(typst 编过否、数据合 schema 否)用**抽象谓词 + `Oracle` 实现边界**表示——契约
|
||||
说"存在这条诊断、什么意思、什么级别",真值由实现提供,不在 Lean 内计算(不内嵌 typst
|
||||
编译器)。引用解析(`@ref`、相对 import)不另设诊断:它们都是 typst 编译期失败,归
|
||||
`typstCompile`(ADR-0012)。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
/-- 诊断严重级别(`PINNED` 二分, ADR-0005)。`error` 阻断(产物不合法),`warning` 不
|
||||
阻断(产物仍可导出,只是有损)。更细级别(info/hint)未决策,故只二分。 -/
|
||||
inductive Severity where
|
||||
| warning
|
||||
| error
|
||||
|
||||
/-- 诊断**分类**(`PINNED` 6 类, ADR-0010,经 ADR-0012 修订为 6 类)。按"谁来判定"分
|
||||
三层:**结构型**(模型自身可判):`partPathMissing`/`unknownKind`;**schema/外部设施型**
|
||||
(靠实现 oracle):`missingContentFile`/`schemaViolation`/`typstCompile`;**语义型**:
|
||||
`renderIgnored`。 -/
|
||||
inductive DiagKind where
|
||||
/-- manifest 的 part 指向不存在的文件夹(或经 `..` 逃出根)。结构型。 -/
|
||||
| partPathMissing
|
||||
/-- part 声明了未知 kind(含 part 与 element.toml 的 kind 不一致)。结构型。 -/
|
||||
| unknownKind
|
||||
/-- kind schema 要求的某 `content` 字段缺对应 `<field>.typ`。结构/schema 型。 -/
|
||||
| missingContentFile
|
||||
/-- 实例数据不合其 kind 的 JSON Schema;亦作 manifest/element.toml 畸形的兜底。 -/
|
||||
| schemaViolation
|
||||
/-- 拼装出的 typst 源编译失败:语法错、未解析的交叉引用 `@ref`、越界或缺失的相对
|
||||
`import`/`include`(后两者即旧 `danglingReference` 的两种情形——typst 在编译期
|
||||
检出,故归此类,ADR-0012)。外部设施型。 -/
|
||||
| typstCompile
|
||||
/-- 某被用到的 kind 在某声明的 target 下无渲染规则,该 element 被忽略。语义型。 -/
|
||||
| renderIgnored
|
||||
|
||||
/-- 每类诊断的**严重级别**(`PINNED`, ADR-0010)。五类 `error`(阻断);**唯
|
||||
`renderIgnored` 为 `warning`**——ADR-0005 种子规则"缺渲染 ⇒ warning,不阻断导出"。
|
||||
钉成全函数使"哪类阻断"成为可引用、可对齐的事实(实现侧 `DiagCode` 级别据此对齐)。 -/
|
||||
def DiagKind.severity : DiagKind → Severity
|
||||
| .partPathMissing => .error
|
||||
| .unknownKind => .error
|
||||
| .missingContentFile => .error
|
||||
| .schemaViolation => .error
|
||||
| .typstCompile => .error
|
||||
| .renderIgnored => .warning
|
||||
|
||||
/-- 缺渲染诊断的级别 = **warning**(`PINNED`, 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。 -/
|
||||
def renderIgnored (l : Lesson P) (c : RenderConfig P) (t : P.TargetId) : Prop :=
|
||||
∃ e ∈ l, ¬ c.covers e.kind t
|
||||
|
||||
/-!
|
||||
## 模型外设施型诊断:抽象谓词 + 实现边界(ADR-0010)
|
||||
|
||||
`typstCompile`/`schemaViolation`(的 schema-合规面)断言的是模型自身无法判定的事实——
|
||||
要跑 typst 编译器、schema 校验器。契约把它们建成**抽象谓词**,真值由实现提供的 oracle
|
||||
给出。下面用 `Oracle` 收口这些判定:它不是要在 Lean 里实现 checker,而是把"这些事实
|
||||
来自模型外"显式化、类型化。
|
||||
-/
|
||||
|
||||
/-- **实现侧判定 oracle**(`PINNED` 实现边界, ADR-0010)。每个字段是一个谓词,真值由
|
||||
实现(checker)提供。结构型诊断(part 路径、未知 kind)不入此 oracle——那些模型自身可判。 -/
|
||||
structure Oracle (l : Lesson P) (c : RenderConfig P) where
|
||||
/-- target `t` 下拼装源可编译(否 ⇒ `typstCompile`)。**含引用解析**:源能编译即蕴含
|
||||
其 `@ref`、相对 import 全部解析(ADR-0012 已把引用诊断并入 `typstCompile`)。 -/
|
||||
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` 的可枚举性。 -/
|
||||
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)
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -0,0 +1,52 @@
|
||||
import Spec.Courseware.Check.Diagnostic
|
||||
|
||||
/-!
|
||||
# Pipeline —— checker 检查管线的阶段与序(ADR-0010)
|
||||
|
||||
checker 的 `check` 按**固定顺序**跑五个阶段,逐阶段收集诊断;`compile` 阶段有**门控**。
|
||||
顺序与门控是契约——它决定用户看到哪些诊断(藏在缺文件背后的语法错,在文件补齐前不
|
||||
显示,这是有意的)。每阶段的**算法**不进 Lean(宪法第 5 条深度上限):只钉**阶段、序、
|
||||
门控**。阶段对应上游模块:`load`←`cph-model`;`structural`←part 路径/未知 kind;
|
||||
`schema`←`cph-schema`;`compile`←`cph-typst`(模型外设施);`coverage`←`renderIgnored`。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
/-- 检查管线的**阶段**(`PINNED` 5 阶段, ADR-0010)。
|
||||
|
||||
- `load` —— 解析 manifest + 各 element.toml。硬失败则**停**整条管线。
|
||||
- `structural` —— part 路径存在、无 `..`、kind 已知且一致。此处判缺的 part 后续跳过。
|
||||
- `schema` —— 每个"存在且 kind 已知"的 part 按其 kind schema 校验。
|
||||
- `compile` —— 模型外设施阶段(typst 编译)。**门控:仅当前序零 error 才跑**。
|
||||
- `coverage` —— 语义型 warning(`renderIgnored`);**不**受门控,总跑。 -/
|
||||
inductive Phase where
|
||||
| load
|
||||
| structural
|
||||
| schema
|
||||
| compile
|
||||
| coverage
|
||||
deriving DecidableEq
|
||||
|
||||
/-- 管线阶段的**执行序**(`PINNED`, ADR-0010)。`order p` 越小越先跑。序是契约:
|
||||
`compile`(3)排在 `structural`(1)/`schema`(2)之后,正因前者门控于后者无 error。 -/
|
||||
def Phase.order : Phase → Nat
|
||||
| .load => 0
|
||||
| .structural => 1
|
||||
| .schema => 2
|
||||
| .compile => 3
|
||||
| .coverage => 4
|
||||
|
||||
/-- 某阶段是否**受"前序零 error"门控**(`PINNED`, ADR-0010)。唯 `compile` 受门控:藏在
|
||||
结构/schema 错背后的编译错,在前者修好前不显示——有意降噪。 -/
|
||||
def Phase.gated : Phase → Bool
|
||||
| .compile => true
|
||||
| _ => false
|
||||
|
||||
/-- 管线在 `load` 硬失败时**停**(`PINNED`, ADR-0010)。`load` 拿不到可解析 lesson 时,
|
||||
无 lesson 可喂下游,整条管线终止——唯一会截断后续所有阶段的情形(区别于 `compile`
|
||||
门控只跳过自己)。 -/
|
||||
def Phase.haltsPipelineOnFailure : Phase → Bool
|
||||
| .load => true
|
||||
| _ => false
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -1,16 +0,0 @@
|
||||
import Spec.Courseware.Lesson
|
||||
|
||||
/-!
|
||||
# Course —— 课程编排(骨架,规则 OPEN)
|
||||
|
||||
ADR-0005:工程文件的粒度是**单节课**;course / 单元**不是**工程文件,而是 lesson
|
||||
的**编排**。
|
||||
|
||||
但"编排"的具体规则未决策:course 是 lesson 的有序列表,还是带层级(单元 → 课)的
|
||||
树?编排里 lesson 是被引用还是被包含?跨 lesson 有无约束(目标覆盖、前后置)?这些
|
||||
都是 `OPEN`。
|
||||
|
||||
按宪法第 2 条,本模块**不臆造**编排结构——不建 `Course := List Lesson`(那会偷偷
|
||||
承诺"扁平有序、无层级")。只在此 surface:课程编排待专门 ADR。本文件当前不引入任何
|
||||
承诺性声明。
|
||||
-/
|
||||
@@ -1,144 +0,0 @@
|
||||
import Spec.Courseware.Lesson
|
||||
import Spec.Courseware.Render
|
||||
|
||||
/-!
|
||||
# Diagnostic —— checker 诊断:分类、严重级别、合法 lesson
|
||||
|
||||
产品里"站在 Lean 位置"的那个 rule-based checker,语义在此沉淀(ADR-0010)。它对 lesson
|
||||
提诊断,每条诊断有**分类**(`DiagKind`)与**严重级别**(`Severity`)。本模块:
|
||||
|
||||
1. 钉级别类型(二分,ADR-0005)。
|
||||
2. 钉 7 类诊断各自的含义与级别(ADR-0010),并把"**合法 lesson = 无 error 级诊断**"
|
||||
建成判定——这是 ADR-0005 deferred 的"完整合法判定"的回填。
|
||||
3. 对**模型外设施**型诊断(typst 能否编过、引用悬否、数据合 schema 否),用**抽象谓词 +
|
||||
显式实现边界**表示:契约说"存在这样一条诊断、它什么意思、什么级别",但其**真值由
|
||||
实现(checker)提供**,不在 Lean 内计算——保持契约诚实(不内嵌 typst 编译器)。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
/--
|
||||
诊断的严重级别(`PINNED` 二分, ADR-0005)。
|
||||
|
||||
`error` 阻断(产物不合法),`warning` 不阻断(产物仍可导出,只是有损/有忽略)。更细的
|
||||
级别(info/hint)未决策,故只二分。
|
||||
-/
|
||||
inductive Severity where
|
||||
| warning
|
||||
| error
|
||||
|
||||
/--
|
||||
诊断的**分类**(`PINNED` 7 类, ADR-0010)。每类含义见下;级别见 `DiagKind.severity`。
|
||||
|
||||
按"谁来判定"分三层(ADR-0010 的边界):
|
||||
- **结构型**(模型自身可判):`partPathMissing` / `unknownKind`。
|
||||
- **schema / 外部设施型**(判定靠实现 oracle):`missingContentFile` / `schemaViolation`
|
||||
/ `danglingReference` / `typstCompile`。
|
||||
- **语义型**(coverage 规则):`renderIgnored`。
|
||||
-/
|
||||
inductive DiagKind where
|
||||
/-- manifest 的 part 指向不存在的文件夹(或经 `..` 逃出根)。结构型。 -/
|
||||
| partPathMissing
|
||||
/-- part 声明了未知 kind(含 part 与 element.toml 的 kind 不一致)。结构型。 -/
|
||||
| unknownKind
|
||||
/-- kind schema 要求的某 `content` 字段缺对应 `<field>.typ`。结构/schema 型。 -/
|
||||
| missingContentFile
|
||||
/-- 实例数据不合其 kind 的 JSON Schema(标量类型错/缺必填/多余键);亦作 manifest/
|
||||
element.toml 畸形的兜底。schema / 外部设施型。 -/
|
||||
| schemaViolation
|
||||
/-- 引用不解析:越界的相对 import,或悬空的交叉引用。外部设施型。 -/
|
||||
| danglingReference
|
||||
/-- 拼装出的 typst 源编译失败(语法错、`@ref` 未解析…)。外部设施型。 -/
|
||||
| typstCompile
|
||||
/-- 某被用到的 kind 在某声明的 target 下无渲染规则,该 element 被该 target 忽略。语义型。 -/
|
||||
| renderIgnored
|
||||
|
||||
/--
|
||||
每类诊断的**严重级别**(`PINNED`, ADR-0010)。
|
||||
|
||||
六类为 `error`(阻断);**唯 `renderIgnored` 为 `warning`**——即 ADR-0005 的种子规则
|
||||
"缺渲染 ⇒ warning,不阻断导出"。把级别钉成一个全函数,使"哪类阻断、哪类不阻断"成为
|
||||
契约里可引用、可对齐的事实(实现侧 `DiagCode` 的级别据此对齐)。
|
||||
-/
|
||||
def DiagKind.severity : DiagKind → Severity
|
||||
| .partPathMissing => .error
|
||||
| .unknownKind => .error
|
||||
| .missingContentFile => .error
|
||||
| .schemaViolation => .error
|
||||
| .danglingReference => .error
|
||||
| .typstCompile => .error
|
||||
| .renderIgnored => .warning
|
||||
|
||||
/-- 缺渲染诊断的级别 = **warning**(`PINNED`, ADR-0005/0010,**非 error**)。
|
||||
|
||||
保留为具名常量,使"它是 warning 不是 error"可被实现侧 grep 对齐(实现里同名常量
|
||||
`RENDER_IGNORED_SEVERITY` 引用本定义)。等价于 `DiagKind.severity .renderIgnored`。 -/
|
||||
def renderIgnoredSeverity : Severity := DiagKind.renderIgnored.severity
|
||||
|
||||
variable (P : Primitives)
|
||||
|
||||
/--
|
||||
**缺渲染诊断**:lesson 在 target `t` 下存在无法渲染的 element(`PINNED`, ADR-0005/0009)。
|
||||
|
||||
成立 ⟺ 存在某 element,其 kind 在 `t` 下未配渲染规则(`covers` 为假,见 `Render`)。这条
|
||||
是 checker 的**语义型**诊断,级别由 `renderIgnoredSeverity` 钉为 warning。`covers` 已随
|
||||
ADR-0009 下沉到 target 的 build 规格,但本判定式不变。
|
||||
-/
|
||||
def renderIgnored (l : Lesson P) (c : RenderConfig P) (t : P.TargetId) : Prop :=
|
||||
∃ e ∈ l, ¬ c.covers e.kind t
|
||||
|
||||
/-!
|
||||
## 模型外设施型诊断:抽象谓词 + 实现边界(ADR-0010)
|
||||
|
||||
`typstCompile` / `danglingReference` / `schemaViolation`(的 schema-合规面)断言的是
|
||||
**模型自身无法判定**的事实——要跑 typst 编译器、要跑 schema 校验器。契约因此把它们建成
|
||||
**抽象谓词**,真值由一个**实现提供的 oracle** 给出。Lean 只说"这条诊断存在、什么意思、
|
||||
什么级别",**不**在 Lean 内计算其真假。
|
||||
|
||||
下面用一个 `Oracle` 结构收口这些实现侧判定。它**不是**要在 Lean 里实现 checker,而是
|
||||
把"这些事实来自模型外"这件事显式化、类型化:谁想谈论一节 lesson 合不合法,就得先有一个
|
||||
oracle 提供这些外部判定。
|
||||
-/
|
||||
|
||||
/--
|
||||
**实现侧判定 oracle**(`PINNED` 实现边界, ADR-0010)。
|
||||
|
||||
收口那些"模型外设施"才能回答的判定。每个字段是一个谓词,**真值由实现(checker)提供**:
|
||||
|
||||
- `compiles` —— 该 lesson 在 target `t` 下拼装出的 typst 源能否编译通过(否 ⇒ `typstCompile`)。
|
||||
- `refsResolve` —— 该 lesson 的引用(相对 import / 交叉引用)是否全部解析(否 ⇒ `danglingReference`)。
|
||||
- `dataConforms` —— 每个 element 的数据是否合其 kind schema(否 ⇒ `schemaViolation`)。
|
||||
- `contentFilesPresent` —— schema 要求的 `content` 文件是否齐备(否 ⇒ `missingContentFile`)。
|
||||
|
||||
把它们作为参数,正是"显式实现边界":契约能据此**定义**合法性,却不假装自己能算出这些
|
||||
外部事实。结构型诊断(part 路径、未知 kind)不入此 oracle——那些模型自身可判。
|
||||
-/
|
||||
structure Oracle (l : Lesson P) (c : RenderConfig P) where
|
||||
/-- target `t` 下拼装源可编译。 -/
|
||||
compiles : P.TargetId → Prop
|
||||
/-- 引用全部解析。 -/
|
||||
refsResolve : Prop
|
||||
/-- 每个 element 数据合 schema。 -/
|
||||
dataConforms : Prop
|
||||
/-- schema 要求的 content 文件齐备。 -/
|
||||
contentFilesPresent : Prop
|
||||
|
||||
/--
|
||||
**合法 lesson**(`PINNED`, ADR-0010;回填 ADR-0005 deferred 的"完整合法判定")。
|
||||
|
||||
一节 lesson 合法 ⟺ 检查管线产出**零条 error 级诊断**。warning(如 `renderIgnored`)
|
||||
不影响合法性——带 warning 的 lesson 仍合法、仍可(有损)导出。
|
||||
|
||||
这里把"无 error 级诊断"展开为:模型外设施判定(经 `Oracle`)全部为真,**且**每个声明的
|
||||
target 都编译通过。结构型诊断(part 路径、未知 kind、kind 不一致)由 `cph-model` 在加载
|
||||
期判定;能走到这一步谈合法性,意味着已加载成功,故此处聚焦 schema/外部设施层。注意
|
||||
`renderIgnored` 是 warning,**不**进合法性合取——这正是 ADR-0005 种子规则的体现。
|
||||
|
||||
`targets` 是该工程文件声明的 target 列表(`RenderConfig.spec t` 非 `none` 者);为保持
|
||||
本层抽象、不绑定 `TargetId` 的可枚举性,这里用"对所有满足前提的 t"的全称式表达。
|
||||
-/
|
||||
def Legal (l : Lesson P) (c : RenderConfig P) (o : Oracle P l c) : Prop :=
|
||||
o.refsResolve ∧ o.dataConforms ∧ o.contentFilesPresent ∧
|
||||
(∀ t : P.TargetId, (c.spec t).isSome → o.compiles t)
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -1,32 +0,0 @@
|
||||
import Spec.Courseware.Primitives
|
||||
|
||||
/-!
|
||||
# Element —— 课程内容的原子单位
|
||||
|
||||
ADR-0005:element 实例 = 一个 kind 标签 + 符合该 kind schema 的数据。本模块把这条
|
||||
编码成一个依赖结构,使"数据必须匹配其 kind"成为类型层面的事实而非运行时校验。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
variable (P : Primitives)
|
||||
|
||||
/--
|
||||
element 实例(`PINNED`, ADR-0005)。
|
||||
|
||||
`data` 的类型 `P.ElementData kind` **由 `kind` 决定**——这意味着无法构造一个数据与
|
||||
其 kind 不符的 element:schema 合规由类型系统而非 checker 保证。
|
||||
|
||||
ADR-0005 的 (a) 类信息(逐字稿、重点圈划等"语义性但只在某些 target 浮现"的字段)
|
||||
属于具体 kind 的 schema,落在 `P.ElementData` 内,不出现在这个通用结构上;(b) 类
|
||||
信息(html 交互、build/npm)按 ADR-0005 不进 element。交互教具是一种"重型 kind",
|
||||
其 `ElementData` 只承载题目/配置数据,重实现在模型之外——它在此结构里与例题、定理
|
||||
同构,仅 `kind` 不同。
|
||||
-/
|
||||
structure Element where
|
||||
/-- 该 element 的 kind。 -/
|
||||
kind : P.KindId
|
||||
/-- 符合 `kind` schema 的数据(类型随 `kind` 而变)。 -/
|
||||
data : P.ElementData kind
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -0,0 +1,8 @@
|
||||
import Spec.Courseware.Export.Artifact
|
||||
import Spec.Courseware.Export.Render
|
||||
|
||||
/-!
|
||||
# Courseware.Export —— export target = artifact + 有序 typed steps
|
||||
|
||||
产物 ADT(`Artifact`)、build 规格与渲染覆盖(`Render`)。决策出处 ADR-0009 / 0011。
|
||||
-/
|
||||
@@ -0,0 +1,23 @@
|
||||
/-!
|
||||
# Artifact —— export target 的产物(ADR-0009 / 0011)
|
||||
|
||||
ADR-0009:一个 export target 是**一次 build**,产出一个**有类型的产物**。ADR-0011
|
||||
钉死:产物是**带字段的 ADT**——"产物到底指什么"(单文件落在哪 / 一棵树产出哪些文件)
|
||||
是不好猜的领域语义,必须写进字段 + doc,而非抹成两个空构造子。路径/glob 用 `String`
|
||||
承载并由 doc 赋义(它们就是文本),不复刻文件系统类型。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
/-- export 产物(`PINNED` 带字段 ADT, ADR-0011)。产物形状决定 `cph build` 吐文件还是
|
||||
目录、reduce 怎么折叠(`singleFile` 把有序片段拼成一份再编译——交叉引用 `@ref`、例题
|
||||
计数器能工作的前提;`fileTree` 每 part 落一文件)。后端/格式仍 OPEN(ADR-0009)。 -/
|
||||
inductive Artifact where
|
||||
/-- 单文件产物,落在 `filepath`(相对工程根)。讲义/教案 PDF 即此。 -/
|
||||
| singleFile (filepath : String)
|
||||
/-- 多文件产物:`root` 目录下匹配 `outputs` **glob** 的文件集(第三方平台 archive
|
||||
即此)。用 glob 而非显式清单:轻,又让消费方/checker 知道该产出哪些文件、可校验
|
||||
完整性。 -/
|
||||
| fileTree (root : String) (outputs : String)
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -0,0 +1,59 @@
|
||||
import Spec.Courseware.Model.Primitives
|
||||
import Spec.Courseware.Export.Artifact
|
||||
|
||||
/-!
|
||||
# Render —— export target = artifact + 有序 typed steps(ADR-0009 / 0011)
|
||||
|
||||
ADR-0009:export target 是一次 build,产出一个有类型的 `Artifact`。ADR-0011 钉死 build
|
||||
的**形状**:一个 target 是 `artifact` + 一串**有序 typed step**。
|
||||
|
||||
- `typstCompile template` —— 把**模板文件**(如 `exports/student.typ`)编译成产物。它是
|
||||
*typed* 而非裸 shell,正因框架要把 **manifest 注入**模板(经 `--input manifest=…`),
|
||||
裸字符串表达不了这个 wiring。presentation(编号、样式)住模板里,不在 manifest。
|
||||
- `shell run` —— 逃生口,给难以声明的步骤(ADR-0005 的 (b) 类 medium-only)。
|
||||
|
||||
**渲染覆盖**:ADR-0011 废止了 per-target `RenderRule` 载荷——渲染的"how"已移进模板。
|
||||
契约只保留覆盖声明 `covers`(该 target 渲染哪些 kind),供种子诊断用。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
variable (P : Primitives)
|
||||
|
||||
/-- 一个 build **step**(`PINNED` typed, ADR-0011;可扩展)。MVP 仅一个 `typstCompile`;
|
||||
`steps` 是 list 因为 FileTree / 第三方 build 会需多步。刻意不把模板内部、shell 命令的
|
||||
解析结构写进来(实现细节, ADR-0011 OPEN)。 -/
|
||||
inductive Step where
|
||||
/-- 编译模板文件 `template`(相对工程根)成产物;框架注入 manifest。typed 的理由:
|
||||
注入这件事裸 shell 写不出。 -/
|
||||
| typstCompile (template : String)
|
||||
/-- shell 逃生口:执行命令 `run`(ADR-0005 (b) 类落这)。MVP 不实现,先建结构。 -/
|
||||
| shell (run : String)
|
||||
|
||||
/-- 一个 export target 的 build 规格(`PINNED` artifact + 有序 steps, ADR-0011)。 -/
|
||||
structure TargetSpec where
|
||||
/-- 产物(带字段, ADR-0011)。决定 build 折叠成单文件还是文件树。 -/
|
||||
artifact : Artifact
|
||||
/-- **有序** build steps。按序执行;MVP 仅一个 `typstCompile`。 -/
|
||||
steps : List Step
|
||||
/-- **覆盖声明**:`covers k` 表示此 target 渲染 kind `k`。ADR-0011 把旧
|
||||
`renders : KindId → Option RenderRule` 降级后的产物——契约只声明"渲染哪些 kind"
|
||||
(种子诊断 `renderIgnored` 用),"how"由 `steps` 的模板实现。 -/
|
||||
covers : P.KindId → Prop
|
||||
|
||||
/-- 渲染配置(`PINNED` target-中心, ADR-0009/0011)。`spec t = none` 表示 target `t`
|
||||
未声明(不导出);`some s` 给出其 build 规格。 -/
|
||||
structure RenderConfig where
|
||||
/-- target ↦ 该 target 的 build 规格(未声明则 `none`)。 -/
|
||||
spec : P.TargetId → Option (TargetSpec P)
|
||||
|
||||
/-- kind `k` 在 target `t` 下**被渲染**(`PINNED`, ADR-0009/0011;承接 ADR-0005)。成立
|
||||
⟺ `t` 已声明(`spec t = some s`)**且** `s.covers k`。为假即"此 kind 在此 target 下不
|
||||
被渲染"——checker 据此报 warning(见 `Diagnostic.renderIgnored`)。`P` 隐式以便点记法。 -/
|
||||
def RenderConfig.covers {P : Primitives} (c : RenderConfig P)
|
||||
(k : P.KindId) (t : P.TargetId) : Prop :=
|
||||
match c.spec t with
|
||||
| none => False
|
||||
| some s => s.covers k
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -1,21 +0,0 @@
|
||||
import Spec.Courseware.Element
|
||||
|
||||
/-!
|
||||
# Lesson —— 单节课工程文件
|
||||
|
||||
ADR-0005:一个工程文件 = 一节课,是 element 实例的**有序序列**。课程/单元不是工程
|
||||
文件,而是 lesson 的编排(见 `Spec.Courseware.Course`,OPEN)。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
/--
|
||||
一节课(`PINNED`, ADR-0005)。
|
||||
|
||||
用 `List` 而非 `Multiset`/`Finset`,因为 **element 的先后次序承载教学语义**(讲义/
|
||||
PPT 式的内容流,先讲定义再举例不同于反过来)。**不建模时长**:ADR-0005 决定 lesson
|
||||
是内容编排而非时间轴,故这里没有"每块多少分钟"这类字段——序是结构,时长不是。
|
||||
-/
|
||||
abbrev Lesson (P : Primitives) := List (Element P)
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -0,0 +1,11 @@
|
||||
import Spec.Courseware.Model.Primitives
|
||||
import Spec.Courseware.Model.RichContent
|
||||
import Spec.Courseware.Model.Element
|
||||
import Spec.Courseware.Model.Lesson
|
||||
|
||||
/-!
|
||||
# Courseware.Model —— 工程文件的内容模型
|
||||
|
||||
留白基元(`Primitives`)、富内容锚点(`RichContent`)、原子单位(`Element`)、单节课
|
||||
(`Lesson`)。决策出处 ADR-0005 / 0006。
|
||||
-/
|
||||
@@ -0,0 +1,24 @@
|
||||
import Spec.Courseware.Model.Primitives
|
||||
|
||||
/-!
|
||||
# Element —— 课程内容的原子单位
|
||||
|
||||
ADR-0005:element 实例 = 一个 kind 标签 + 符合该 kind schema 的数据。本模块把它编码成
|
||||
依赖结构,使"数据必须匹配其 kind"成为类型层面的事实而非运行时校验。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
variable (P : Primitives)
|
||||
|
||||
/-- element 实例(`PINNED`, ADR-0005)。`data : P.ElementData kind` 由 `kind` 决定——
|
||||
无法构造数据与 kind 不符的 element,schema 合规由类型系统保证。ADR-0005 的 (a) 类
|
||||
字段(逐字稿、重点圈划)落在具体 kind 的 `ElementData` 内,不出现在此通用结构上;
|
||||
交互教具等"重型 kind"在此与例题、定理同构,仅 `kind` 不同,重实现在模型之外。 -/
|
||||
structure Element where
|
||||
/-- 该 element 的 kind。 -/
|
||||
kind : P.KindId
|
||||
/-- 符合 `kind` schema 的数据(类型随 `kind` 而变)。 -/
|
||||
data : P.ElementData kind
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -0,0 +1,16 @@
|
||||
import Spec.Courseware.Model.Element
|
||||
|
||||
/-!
|
||||
# Lesson —— 单节课工程文件
|
||||
|
||||
ADR-0005:一个工程文件 = 一节课,是 element 实例的**有序序列**。课程/单元不是工程
|
||||
文件,而是 lesson 的编排(见 `Spec.Courseware.Course`,OPEN)。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
/-- 一节课(`PINNED`, ADR-0005)。用 `List` 因为 **element 次序承载教学语义**(先讲
|
||||
定义再举例 ≠ 反过来);**不建模时长**——ADR-0005 决定 lesson 是内容编排而非时间轴。 -/
|
||||
abbrev Lesson (P : Primitives) := List (Element P)
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -0,0 +1,34 @@
|
||||
/-!
|
||||
# Primitives —— Courseware 契约的留白基元
|
||||
|
||||
课程工程文件模型(ADR-0005)依赖一组基元:element kind 怎么标识、某 kind 的数据
|
||||
schema 是什么、export target 怎么标识。收口成载体 `Primitives`,让模型在其上参数化
|
||||
——契约谈得了 element / lesson / 渲染**之间的关系**,而把每个基元的**内部表示**留给
|
||||
实现。注意:某基元语义已 PINNED(如 schema 形态由 ADR-0006 钉死)与其表示进 Lean
|
||||
是两回事——JSON Schema / typst 的内部结构属实现细节,不入 Lean,故基元在此仍以抽象
|
||||
类型承载。富内容的 prose 母本见 `Courseware.RichContent`。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
/-- Courseware 契约基元载体(关系 `PINNED`, ADR-0005;各基元表示留给实现, ADR-0006)。 -/
|
||||
structure Primitives where
|
||||
/-- element kind 标识(`PINNED` **开放宇宙**, ADR-0005;表示 `OPEN`)。刻意用抽象
|
||||
类型而非 `inductive`:ADR-0005 决定 kind 是开放可扩展宇宙(stdlib + 第三方),
|
||||
封闭枚举会违背它——此处开放是**已决策的**(区别于 `RunState` 的"尚未封闭")。 -/
|
||||
KindId : Type
|
||||
/-- 某 kind 的合法数据类型(`PINNED` 依赖关系, ADR-0005;schema 形态 `PINNED`
|
||||
ADR-0006,表示仍抽象)。以 kind 为索引:`ElementData k` 即"符合 `k` schema 的
|
||||
数据"。schema 形态(声明式 JSON Schema + `content` 叶子 = typst 源)是 ADR-0006
|
||||
钉死的,但属 JSON/typst 内部结构、实现细节,不进 Lean;契约只锚定"数据符合
|
||||
kind schema"这条关系,故此处仍是抽象类型。 -/
|
||||
ElementData : KindId → Type
|
||||
/-- export target 标识(`PINNED` 角色, ADR-0005;表示 `OPEN`)。一个 target 是一次
|
||||
build,产出对 lesson 的一种投影(讲义/教案/PPT/平台 archive…),见 `Render`。 -/
|
||||
TargetId : Type
|
||||
|
||||
-- 注:原 `RenderRule : Type` 已随 ADR-0011 移除。渲染的"how"不再是契约层 per-target
|
||||
-- 载荷,而由 `Render.TargetSpec.steps` 里 `typstCompile` step 引用的**模板文件**承载;
|
||||
-- 契约只保留覆盖声明 `TargetSpec.covers`(该 target 渲染哪些 kind),供种子诊断用。
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -0,0 +1,28 @@
|
||||
/-!
|
||||
# RichContent —— 富内容(ADR-0006 的 prose 母本)
|
||||
|
||||
ADR-0006:element schema 的"叶子"可以是 `content` 类型,其值是**一段 typst 源**,
|
||||
语义取该源作为 module 求值后的 body content。关键约束(均 ADR-0006,源自 typst 源码
|
||||
事实):富内容**不可无主**——typst 的源必须有 `FileId`,否则 span 脱锚、相对 import
|
||||
报"cannot access file system from here"。故每段富内容是 World 里的一等文件,坐落在
|
||||
一个**虚拟路径**上;相对 import 限本工程路径结构内 + `@package`(不跨工程)。
|
||||
|
||||
本模块只立 prose 锚点 + 最小抽象签名:typst 的 `Content`/`Module` 内部结构、JSON
|
||||
Schema 形状属实现细节,不进 Lean,只承诺"富内容由一个虚拟路径定位"这一条关系。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
/-- 富内容在工程文件路径结构中的**虚拟路径**(`OPEN` 表示, ADR-0006)。把一段富内容
|
||||
定位为 World 里的一等文件(span 可解析、相对 import 可锚定)。落盘后即真实相对路径
|
||||
(ADR-0007),不在本层承诺,故 opaque。 -/
|
||||
opaque VPath : Type
|
||||
|
||||
/-- 对一段富内容的**引用**:它坐落在某个虚拟路径上(`PINNED` 关系, ADR-0006)。
|
||||
刻意**不**建模源文本、不建模求值出的 `Content`(那是实现侧的事);只钉"富内容经由一个
|
||||
`VPath` 定位",作为 `Primitives.ElementData` 里 `content` 叶子的语义锚点。 -/
|
||||
structure RichContentRef where
|
||||
/-- 该富内容所在的虚拟路径(ADR-0006;落盘后为真实相对路径, ADR-0007)。 -/
|
||||
vpath : VPath
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -0,0 +1,9 @@
|
||||
import Spec.Courseware.Open.QuestionBank
|
||||
import Spec.Courseware.Open.Course
|
||||
|
||||
/-!
|
||||
# Courseware.Open —— 留白骨架(核心关系 OPEN)
|
||||
|
||||
题库与 element 的关系(`QuestionBank`)、课程编排规则(`Course`)。两者均为已 surface
|
||||
但未决策的分歧点,按宪法第 2 条不臆造,待专门 ADR 落定。
|
||||
-/
|
||||
@@ -0,0 +1,10 @@
|
||||
/-!
|
||||
# Course —— 课程编排(骨架,规则 OPEN)
|
||||
|
||||
ADR-0005:工程文件的粒度是**单节课**;course / 单元**不是**工程文件,而是 lesson 的
|
||||
**编排**。但"编排"的具体规则未决策:有序列表还是带层级(单元 → 课)的树?lesson 被
|
||||
引用还是被包含?跨 lesson 有无约束(目标覆盖、前后置)?这些都是 `OPEN`。
|
||||
|
||||
按宪法第 2 条本模块**不臆造**编排结构——不建 `Course := List Lesson`(那会偷偷承诺
|
||||
"扁平有序、无层级")。只在此 surface:课程编排待专门 ADR。本文件当前不引入任何承诺性声明。
|
||||
-/
|
||||
@@ -0,0 +1,10 @@
|
||||
/-!
|
||||
# QuestionBank —— 题库(骨架,核心关系 OPEN)
|
||||
|
||||
题库是与工程文件并列的资产(类比 DAW 的 sample library):有自身结构的实体,又是最
|
||||
典型的可复用单元,lesson 会引用它。但**题库与 element 的关系尚未决策**,且用户明确
|
||||
指出"纯引用可能不够"——element 内联题目数据 / lesson 持指向题库条目的引用 / 两者并存?
|
||||
|
||||
这是一个 `OPEN` 分歧点。按宪法第 2 条本模块**不替它选解**——不建 `QuestionRef` 也不建
|
||||
内联结构,只在此 surface。待专门 ADR 落定后再填。本文件当前不引入任何承诺性声明。
|
||||
-/
|
||||
@@ -1,72 +0,0 @@
|
||||
import Spec.Courseware.Diagnostic
|
||||
|
||||
/-!
|
||||
# Pipeline —— checker 检查管线的阶段与序(ADR-0010)
|
||||
|
||||
checker 的 `check` 按**固定顺序**跑五个阶段,逐阶段收集诊断;`compile` 阶段有**门控**。
|
||||
顺序与门控是契约——它决定用户看到哪些诊断(藏在缺文件背后的语法错,在文件补齐前不显示,
|
||||
这是**有意**的)。每阶段的**算法**不进 Lean(宪法第 5 条深度上限):本模块只钉**阶段、序、
|
||||
门控**,不形式化每步怎么算。
|
||||
|
||||
阶段与上游模块的对应:`load` ← `cph-model`;`structural` ← part 路径/未知 kind;`schema`
|
||||
← `cph-schema`;`compile` ← `cph-typst`(模型外设施,见 `Diagnostic.Oracle`);`coverage`
|
||||
← `Diagnostic.renderIgnored`。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
/--
|
||||
检查管线的**阶段**(`PINNED` 5 阶段, ADR-0010)。
|
||||
|
||||
- `load` —— 解析 manifest + 各 element.toml。硬失败(无可解析 lesson)则**停**整条管线。
|
||||
- `structural` —— part 路径存在、无 `..`、kind 已知、part 与 element kind 一致。此处已被
|
||||
判缺的 part,在后续阶段**跳过**(不在同一根因上堆诊断)。
|
||||
- `schema` —— 每个"存在且 kind 已知"的 part 的数据按其 kind schema 校验(标量 + content 文件)。
|
||||
- `compile` —— 模型外设施阶段(typst 编译)。**门控:仅当 `load`/`structural`/`schema` 零
|
||||
error 才跑**(否则在已知坏输入上编译只是噪声)。跑时对每个声明的 target 编译(ADR-0009)。
|
||||
- `coverage` —— 语义型 warning 规则(`renderIgnored`);**不**受门控,总跑。
|
||||
-/
|
||||
inductive Phase where
|
||||
| load
|
||||
| structural
|
||||
| schema
|
||||
| compile
|
||||
| coverage
|
||||
deriving DecidableEq
|
||||
|
||||
/--
|
||||
管线阶段的**执行序**(`PINNED`, ADR-0010)。`order p` 越小越先跑。
|
||||
|
||||
序是契约:`compile`(3)排在 `structural`(1)/`schema`(2)之后,正因前者门控于后者无 error。
|
||||
`coverage`(4)排最后但不门控(见 `gated`)。
|
||||
-/
|
||||
def Phase.order : Phase → Nat
|
||||
| .load => 0
|
||||
| .structural => 1
|
||||
| .schema => 2
|
||||
| .compile => 3
|
||||
| .coverage => 4
|
||||
|
||||
/--
|
||||
某阶段是否**受"前序零 error"门控**(`PINNED`, ADR-0010)。
|
||||
|
||||
唯 `compile` 受门控:前序(load/structural/schema)出任何 error 则跳过它——藏在结构/schema
|
||||
错背后的编译错,在前者修好前不显示,这是有意的降噪。其余阶段不门控:`load` 是入口,
|
||||
`structural`/`schema` 是门控的**依据**(自身得先跑),`coverage` 产 warning 故无条件跑。
|
||||
-/
|
||||
def Phase.gated : Phase → Bool
|
||||
| .compile => true
|
||||
| _ => false
|
||||
|
||||
/--
|
||||
管线在 `load` 硬失败时**停**(`PINNED`, ADR-0010)。
|
||||
|
||||
`load` 拿不到可解析的 lesson(`cph-model` 返回 `none`)时,没有 lesson 可喂给下游,
|
||||
整条管线就此终止——这是唯一会"截断"后续所有阶段的情形(区别于 `compile` 的门控只跳过
|
||||
自己)。
|
||||
-/
|
||||
def Phase.haltsPipelineOnFailure : Phase → Bool
|
||||
| .load => true
|
||||
| _ => false
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -1,50 +0,0 @@
|
||||
/-!
|
||||
# Primitives —— Courseware 契约的留白基元
|
||||
|
||||
课程工程文件模型(ADR-0005)依赖一组基元:element kind 怎么标识、某 kind 的数据
|
||||
schema 是什么、export target 怎么标识、渲染规则的载荷长什么样。
|
||||
|
||||
把它们收口成一个载体 `Primitives`,让 lesson 模型在其上参数化——契约因此能谈
|
||||
element / lesson / 渲染**之间的关系**,而把每个基元的**内部表示**留给实现。注意:
|
||||
"某基元的语义已 PINNED"与"其表示进 Lean"是两回事——schema 的**形态**已由 ADR-0006
|
||||
钉死(声明式 JSON Schema + 富内容叶子 = typst content),但那是 JSON / typst 的内部
|
||||
结构、属实现细节,**不入 Lean**;Lean 只锚定"数据符合 kind schema"这条关系,故这些
|
||||
基元在此仍以抽象类型承载。富内容这一新概念的 prose 母本见 `Courseware.RichContent`。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
/--
|
||||
Courseware 契约基元载体(关系 `PINNED`, ADR-0005;各基元表示留给实现, ADR-0006)。
|
||||
-/
|
||||
structure Primitives where
|
||||
/--
|
||||
element kind 的标识(`PINNED` 开放宇宙, ADR-0005;表示 `OPEN`)。
|
||||
|
||||
**刻意用抽象类型而非 `inductive`**:ADR-0005 决定 kind 是开放可扩展宇宙(stdlib
|
||||
default + 第三方),封闭枚举会直接违背它。这与 `RunState` 的"未封闭"不同——此处
|
||||
开放是**已决策的**。
|
||||
-/
|
||||
KindId : Type
|
||||
/--
|
||||
某 kind 的合法数据类型(`PINNED` 依赖关系, ADR-0005;schema 形态 `PINNED` ADR-0006,
|
||||
表示仍抽象)。
|
||||
|
||||
以 kind 为索引:不同 kind 的合法数据类型不同。这正是 ADR-0005"kind 的最小契约
|
||||
= 数据 schema"的载体——`ElementData k` 即"符合 `k` 之 schema 的数据"。
|
||||
|
||||
schema 的**形态**已由 ADR-0006 定:声明式 **JSON Schema**,字段类型 = 内置标量 +
|
||||
一个 `content` 扩展类型;`content` 字段的值是一段 **typst 源**(canonical 存为带
|
||||
虚拟路径的源,语义取其 module body)。但这些是 JSON / typst 的内部结构——**实现
|
||||
细节,非分歧点**,不进 Lean。故 `ElementData` 在此**仍是抽象类型**:契约只承诺
|
||||
"存在一个由 kind 决定的合法数据类型",不复刻 JSON Schema / typst Content 的形状。
|
||||
-/
|
||||
ElementData : KindId → Type
|
||||
/-- export target 的标识(`PINNED` 角色, ADR-0005;表示 `OPEN`)。一个 target 是一次 build,产出对 lesson 的一种投影(讲义/教案/PPT/平台 archive…),见 `Render`。 -/
|
||||
TargetId : Type
|
||||
|
||||
-- 注:原有 `RenderRule : Type` 已随 ADR-0011 移除。渲染的"how"不再是契约层的 per-target
|
||||
-- 载荷,而是 `Render.TargetSpec.steps` 里 `typstCompile` step 所引用的**模板文件**承载;
|
||||
-- 契约只保留覆盖声明 `TargetSpec.covers`(该 target 渲染哪些 kind),供种子诊断用。
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -1,17 +0,0 @@
|
||||
import Spec.Courseware.Element
|
||||
|
||||
/-!
|
||||
# QuestionBank —— 题库(骨架,核心关系 OPEN)
|
||||
|
||||
题库是整个方案里与工程文件并列的资产(讨论里类比 DAW 的 sample library):有自身
|
||||
结构的被建模实体,又是最典型的可复用单元,lesson 会引用它。
|
||||
|
||||
但**题库与 element 的关系尚未决策**,且用户明确指出"纯引用可能不够":
|
||||
- element 内联题目数据(如交互教具那样),还是
|
||||
- lesson 持一个指向题库条目的**引用**,还是
|
||||
- 两者并存?
|
||||
|
||||
这是一个 `OPEN` 分歧点。按宪法第 2 条,本模块**不替它选解**——不建 `QuestionRef`
|
||||
也不建内联结构,只在此 surface 该问题。待专门的 ADR 落定后再填。本文件当前只承载
|
||||
这条说明,不引入任何承诺性声明。
|
||||
-/
|
||||
@@ -1,88 +0,0 @@
|
||||
import Spec.Courseware.Primitives
|
||||
import Spec.Courseware.Artifact
|
||||
|
||||
/-!
|
||||
# Render —— export target = artifact + 有序 typed steps(ADR-0009 / 0011)
|
||||
|
||||
ADR-0009:export target 是**一次 build**,产出一个有类型的 `Artifact`。ADR-0011 钉死
|
||||
build 的**形状**:一个 target 是 `artifact` + 一串**有序的 typed step**。每个 step 是一个
|
||||
**有类型的操作**:
|
||||
|
||||
- `typstCompile template` —— 把**模板文件**(如 `exports/student.typ`)编译成产物。它是
|
||||
*typed* 而非裸 shell,正因框架要把 **manifest 注入**模板(经 `--input manifest=…`),
|
||||
裸 `typst compile` 字符串表达不了这个 wiring。presentation(numbly 编号、样式)住在
|
||||
模板里,**不**在 manifest。
|
||||
- `shell run` —— 逃生口,给难以声明的步骤(ADR-0005 的 (b) 类 medium-only:HTML 交互、
|
||||
npm build 落这)。
|
||||
|
||||
MVP 每个 target 只有一个 `typstCompile` step,但 `steps` 是 **list**,因为 `FileTree` /
|
||||
第三方 build 会需要多步。
|
||||
|
||||
**渲染覆盖**:ADR-0011 废止了 per-target 的 `RenderRule` 载荷——渲染的"how"已移进模板。
|
||||
契约只保留**覆盖声明** `covers : KindId → Prop`(该 target 渲染哪些 kind),供种子诊断用;
|
||||
模板是其实现。`RenderConfig.covers`/`renderIgnored` 据此,ADR-0005 种子规则不变。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
variable (P : Primitives)
|
||||
|
||||
/--
|
||||
一个 build **step**(`PINNED` typed, ADR-0011;可扩展)。
|
||||
|
||||
- `typstCompile (template)` —— 编译模板文件(相对工程根的路径)成产物;框架把 manifest
|
||||
注入它。**typed** 的理由:注入这件事裸 shell 写不出。
|
||||
- `shell (run)` —— 逃生口,执行 `run` 命令(ADR-0005 的 (b) 类落这)。MVP 不实现,先建结构。
|
||||
|
||||
**刻意不**把模板内部、shell 命令的解析结构写进来:那是实现细节(ADR-0011 OPEN)。此处只钉
|
||||
"step 是有类型操作,目前两型,可扩展"这一骨架。
|
||||
-/
|
||||
inductive Step where
|
||||
/-- 编译模板文件 `template`(相对工程根)成产物;框架注入 manifest。 -/
|
||||
| typstCompile (template : String)
|
||||
/-- shell 逃生口:执行命令 `run`。 -/
|
||||
| shell (run : String)
|
||||
|
||||
/--
|
||||
一个 export target 的 build 规格(`PINNED` artifact + 有序 steps, ADR-0011)。
|
||||
|
||||
- `artifact` —— 产物(`Courseware.Artifact`,带字段)。决定 build 折叠成单文件还是文件树。
|
||||
- `steps` —— **有序** step 列表。按序执行;MVP 仅一个 `typstCompile`。
|
||||
- `covers` —— **覆盖声明**:`covers k` 表示此 target **渲染** kind `k`。这是 ADR-0011 把
|
||||
旧 `renders : KindId → Option RenderRule` 降级后的产物——契约只声明"渲染哪些 kind"
|
||||
(种子诊断 `renderIgnored` 用),渲染的"how"由 `steps` 的模板实现,不再是契约层载荷。
|
||||
-/
|
||||
structure TargetSpec where
|
||||
/-- 产物(带字段, ADR-0011)。 -/
|
||||
artifact : Artifact
|
||||
/-- 有序 build steps。 -/
|
||||
steps : List Step
|
||||
/-- 覆盖声明:此 target 是否渲染该 kind。 -/
|
||||
covers : P.KindId → Prop
|
||||
|
||||
/--
|
||||
渲染配置(`PINNED` target-中心, ADR-0009/0011)。
|
||||
|
||||
`spec t = none` 表示 target `t` **未声明**(此工程文件不导出该 target);`some s` 给出该
|
||||
target 的 build 规格 `s`(artifact + steps + 覆盖声明)。
|
||||
-/
|
||||
structure RenderConfig where
|
||||
/-- target ↦ 该 target 的 build 规格(未声明则 `none`)。 -/
|
||||
spec : P.TargetId → Option (TargetSpec P)
|
||||
|
||||
/--
|
||||
kind `k` 在 target `t` 下**被渲染**(`PINNED`, ADR-0009/0011;承接 ADR-0005 `covers`)。
|
||||
|
||||
成立 ⟺ target `t` 已声明(`spec t = some s`)**且**其覆盖声明含 `k`(`s.covers k`)。
|
||||
`covers` 为假即"此 kind 在此 target 下不被渲染"——checker 据此报 warning(见
|
||||
`Spec.Courseware.Diagnostic.renderIgnored`)。语义与 ADR-0005 连续,只是判定基底从
|
||||
"有无 RenderRule"换成"覆盖声明是否含此 kind"(ADR-0011 废 RenderRule 载荷)。`P` 隐式
|
||||
以便点记法。
|
||||
-/
|
||||
def RenderConfig.covers {P : Primitives} (c : RenderConfig P)
|
||||
(k : P.KindId) (t : P.TargetId) : Prop :=
|
||||
match c.spec t with
|
||||
| none => False
|
||||
| some s => s.covers k
|
||||
|
||||
end Spec.Courseware
|
||||
@@ -1,39 +0,0 @@
|
||||
/-!
|
||||
# RichContent —— 富内容(ADR-0006 的 prose 母本)
|
||||
|
||||
ADR-0006 给 element schema 的"叶子"定了形:一个字段可以是 `content` 类型,其值是
|
||||
**一段 typst 源**,语义上取该源作为 module 求值后的 **body content**。
|
||||
|
||||
关键约束(均 ADR-0006,源自 typst 源码事实):富内容**不可无主**——typst 的源必须
|
||||
有 `FileId`,否则 span 脱锚(`span.id() = none`,点击跳转失效)且相对 `import` 报
|
||||
"cannot access file system from here"。故每段富内容是 World 里的一等文件,坐落在一个
|
||||
**虚拟路径**上;相对 import 限本工程路径结构内 + `@package`(不跨工程)。
|
||||
|
||||
本模块只为这个概念立一个 prose 锚点 + 一个**最小抽象签名**。typst 的 `Content` /
|
||||
`Module` 内部结构、JSON Schema 的形状都是**实现细节、非分歧点**,不进 Lean——这里
|
||||
不复刻它们,只承诺"富内容由一个虚拟路径定位"这一条 ADR-0006 钉死的关系。
|
||||
-/
|
||||
|
||||
namespace Spec.Courseware
|
||||
|
||||
/--
|
||||
富内容在工程文件路径结构中的**虚拟路径**(`OPEN` 表示, ADR-0006)。
|
||||
|
||||
它把一段富内容定位为 World 里的一等文件(span 可解析、相对 import 可锚定)。其具体
|
||||
表示——以及它落到磁盘后就是真实相对路径(ADR-0007)——不在本层承诺,故 opaque。
|
||||
-/
|
||||
opaque VPath : Type
|
||||
|
||||
/--
|
||||
对一段富内容的**引用**:它坐落在某个虚拟路径上(`PINNED` 关系, ADR-0006)。
|
||||
|
||||
契约层面,一段 `content` 字段值由其虚拟路径标识(canonical 存储是该路径下的 typst
|
||||
源文本,而非序列化的 `Content`——后者不可反序列化)。这里**刻意不**建模源文本、
|
||||
不建模求值出的 `Content`:那是实现侧的事。此结构只钉死"富内容经由一个 `VPath`
|
||||
定位"这一条关系,作为 `Primitives.ElementData` 里 `content` 叶子的语义锚点。
|
||||
-/
|
||||
structure RichContentRef where
|
||||
/-- 该富内容所在的虚拟路径(ADR-0006;落盘后为真实相对路径, ADR-0007)。 -/
|
||||
vpath : VPath
|
||||
|
||||
end Spec.Courseware
|
||||
Reference in New Issue
Block a user