feat(spec): build out System layer and element schema model

Picture the whole framework, then pin the element-kind language layer.

System platform layer — rebuild the semantics likec4 can't draw (ADR-0001..0004):
- Prelude: opaque Identifiers carrier (ProjectId/RunId/SessionId/Principal)
- System/Run: RunState + Terminal subset (set completeness left OPEN, no invented pending)
- System/Lock: lock owner=run (typed), LockTable exclusivity, WellFormed invariant
  (a lock holder must be a non-terminal run — couples Lock and Run)
- System/Permission: read<edit<manage role lattice, capability derivation,
  can_mono monotonicity theorem; force-release sits outside the lattice (admin-only)
- System/Audit: intentionally thin (mostly plumbing, OPEN)

Courseware product layer — split the single Lesson file into focused modules and
fix doc tautology: Primitives / Element / Lesson / Render / Diagnostic, plus
QuestionBank and Course skeletons (core relations OPEN, not invented).
Diagnostic upgrades WarnsIgnored into a severity-tagged checker rule.

Element schema + rich-content model (ADR-0006, ADR-0007):
- docs/adr/0006: kind schema is declarative JSON Schema; field types are built-in
  scalars plus a `content` extension; a `content` value is typst source taken as
  module body; rich content must carry a VirtualPath (else click-to-jump and
  relative import break — verified against typst source); import boundary =
  within the engineering file + @package
- docs/adr/0007: on-disk form is a real directory tree (agent/grep friendly);
  VirtualPath = real relative path; layout conventions deferred
- spec/Courseware/RichContent: prose anchor for rich content (opaque VPath,
  RichContentRef); ElementData kept abstract — JSON/typst internals are
  implementation detail, not contract

lake build green (18 jobs), no sorry, toolchain v4.31.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 00:31:14 +08:00
parent 4a828e07b9
commit f1dce07789
18 changed files with 740 additions and 107 deletions
+10 -104
View File
@@ -1,115 +1,21 @@
import Spec.Courseware.Element
/-!
# Lesson —— 课工程文件的母本模型(单节课)
# Lesson —— 单节课工程文件
本文件固定 ADR-0005 的骨架共识:一个**工程文件 = 一节课(lesson)**,
lesson 是**有类型 element 实例的有序序列**;element 实例 = kind 标签 + 符合该
kind schema 的数据;export target 是对 lesson 的投影;渲染由一份
**(kind × target) 配置矩阵**承载。
本轮**只承诺关系结构**。所有尚未决定的基元(schema 怎么表示、kind 怎么定义、
render rule 的载荷长什么样)都收口进 `Primitives`,以抽象类型留白(`OPEN`),
契约因此对未定之物**零承诺**,同时仍 type-check 通过。完整的"合法 lesson"判定
亦未定;本轮只落 checker 的第一颗种子(缺渲染 ⇒ warning)。
ADR-0005:一个工程文件 = 一节课,是 element 实例的**有序序列**。课程/单元不是工程
文件,而是 lesson 的编排(见 `Spec.Courseware.Course`,OPEN)。
-/
namespace Spec.Courseware
/--
契约基元载体(`PINNED` 关系结构 / `OPEN` 基元留白,`ADR-0005`)。
一节课(`PINNED`, ADR-0005)。
把本轮**尚未决定**的基元收口到一处,使 lesson 模型在其上参数化。每个字段都是
一个 `OPEN` 点:契约只声明它**存在**、以及它在关系中扮演的角色,绝不假设其内部
表示。具体表示待后续各自的 ADR 落定后再 refine
用 `List` 而非 `Multiset`/`Finset`,因为 **element 的先后次序承载教学语义**(讲义/
PPT 式的内容流,先讲定义再举例不同于反过来)。**不建模时长**:ADR-0005 决定 lesson
是内容编排而非时间轴,故这里没有"每块多少分钟"这类字段——序是结构,时长不是
-/
structure Primitives where
/--
element 的**种类标识**(`PINNED` 开放宇宙 / `OPEN` 表示,`ADR-0005`)。
kind 构成一个**开放、可扩展的宇宙**(stdlib default + 远期第三方),因此**刻意
用一个抽象类型,而非封闭 `inductive`/枚举**——把"kind 集合不封闭"这条 PINNED
直接编码进类型。`KindId` 的具体表示是 `OPEN`。
-/
KindId : Type
/--
某 kind 的**合法数据**类型(`PINNED` 依赖关系 / `OPEN` schema 表示,`ADR-0005`)。
以 `KindId` 为索引的**依赖类型**,精确表达"element 数据必须**符合该 kind 的
schema**":不同 kind 的合法数据类型不同。kind 对框架的最小契约**就是**这份
schema(渲染不在其中)。schema 自身**怎么表示**是 `OPEN`,故此处只给出
`KindId → Type` 这一依赖,不触碰其内部。
-/
ElementData : KindId Type
/--
**导出目标标识**(`PINNED` 角色 / `OPEN` 表示,`ADR-0005`)。
一个 target(学生讲义 / 教师教案 / PPT / 第三方平台 archive / html 教具平台…)
是对同一 lesson 的一种投影。target 同样是开放集合;其具体表示 `OPEN`。
-/
TargetId : Type
/--
**渲染规则的载荷**(`OPEN` 表示,`ADR-0005`)。
"某 kind 在某 target 下如何呈现"的那条规则的**内容**。其表示完全 `OPEN`(typst
只是渲染后端之一,不是定义语言)——本轮只关心规则**在不在**(见 `RenderConfig`),
不关心它**是什么**。
-/
RenderRule : Type
variable (P : Primitives)
/--
**element 实例**:一个 kind 标签 + 符合该 kind schema 的数据(`PINNED`,`ADR-0005`)。
`data` 的类型由 `kind` 决定(`P.ElementData kind`),从类型上保证"数据符合该 kind
的 schema"——构造一个 `Element` 时不可能给出与其 kind 不匹配的数据。
注:ADR-0005 的 (a) 类信息(逐字稿、重点圈划等"语义性但 target-specific"的字段)
属于具体 kind 的 schema 范畴,故落在 `P.ElementData` 内,不在此通用结构上出现;
(b) 类信息(html 交互、build/npm)不进 element。
-/
structure Element where
/-- 该 element 的种类(`PINNED`,`ADR-0005`)。 -/
kind : P.KindId
/-- 符合 `kind` schema 的数据(`PINNED`,`ADR-0005`)。 -/
data : P.ElementData kind
/--
**lesson = element 实例的有序序列**(`PINNED`,`ADR-0005`)。
用 `List` 编码"有序":**次序是有意义的**。**时长不建模**——lesson 是内容编排
(讲义/PPT 式的内容流),不是时间轴。课程/单元不是 lesson,而是 lesson 的编排
(`OPEN`,见 ADR-0005 Deferred),不在本文件。
-/
abbrev Lesson := List (Element P)
/--
**渲染配置矩阵**:(kind × target) → 可选的渲染规则(`PINNED`,`ADR-0005`)。
这份矩阵**住在工程文件里**:框架给好 default,文件可逐项 override。`rule k t = none`
表示该 (kind, target) 对**尚无渲染规则**(此处只建模规则的**有无**,不建模其内容)。
-/
structure RenderConfig where
/-- (kind, target) ↦ 该对是否配了渲染规则及其载荷(`PINNED`,`ADR-0005`)。 -/
rule : P.KindId P.TargetId Option P.RenderRule
/--
kind `k` 在 target `t` 下**已配渲染规则**(`PINNED`,`ADR-0005`)。
即矩阵在该 (kind, target) 处非空。"覆盖"= 有规则可用;`covers` 为假即"缺渲染"。
-/
def RenderConfig.covers {P : Primitives} (c : RenderConfig P) (k : P.KindId) (t : P.TargetId) : Prop :=
(c.rule k t).isSome
/--
**checker 种子规则**:lesson 在 target `t` 下**存在被忽略的 element**(`PINNED`,`ADR-0005`)。
成立 ⟺ lesson 里**存在**某 element,其 kind 在 `t` 下没有渲染规则。
ADR-0005 PIN 住该情形的**严重级别 = warning,而非 error**:它**不阻断**导出,只提示
"这些 element 在该 target 下被忽略了"。这是 checker(产品里"站在 Lean 位置"的那个
东西)的第一颗诊断种子;完整的"合法 lesson"判定仍 `OPEN`。
-/
def WarnsIgnored (l : Lesson P) (c : RenderConfig P) (t : P.TargetId) : Prop :=
e l, ¬ c.covers e.kind t
abbrev Lesson (P : Primitives) := List (Element P)
end Spec.Courseware