docs(adr): cph 的 nested-manifest / batch-export 改号 0036/0037

上游 0029/0030 与本地 filelib 侧同号 ADR 相撞,cph 两份改到本地空闲号段,
代码锚点引用一并跟随。
This commit is contained in:
2026-08-06 00:51:33 +08:00
parent e878b46701
commit ecc92c9a87
27 changed files with 100 additions and 100 deletions
+20 -20
View File
@@ -1,4 +1,4 @@
//! `cph-model` — load the ADR-0029 nested outline manifest into an in-memory
//! `cph-model` — load the ADR-0036 nested outline manifest into an in-memory
//! lesson.
//!
//! This crate is the **loader**, not the full checker. A lesson's structure is
@@ -12,7 +12,7 @@
//! pipeline validate and include content from — unaffected by nesting.
//! - [`Lesson::outline`] — the **full rendering-order sequence**: elements
//! interleaved with section headings, at the depth-first traversal position
//! they open at (ADR-0029). This is what the augmented manifest (built by
//! they open at (ADR-0036). This is what the augmented manifest (built by
//! `cph-typst`) walks to hand the template a rendering order that includes
//! headings.
//!
@@ -34,7 +34,7 @@ use std::path::{Component, Path, PathBuf};
use cph_diag::{DiagCode, Diagnostic, Severity};
use serde::{Deserialize, Serialize};
/// An in-memory lesson loaded from an engineering file (ADR-0029).
/// An in-memory lesson loaded from an engineering file (ADR-0036).
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct Lesson {
/// `[project]` from the root manifest (id, name).
@@ -43,12 +43,12 @@ pub struct Lesson {
pub info: Info,
/// The ordered **element** sequence — the lesson's element order (ADR-0005).
/// Contains only leaves; containers never appear here (a container
/// "contributes no element of its own", ADR-0029). Index-stable: an
/// "contributes no element of its own", ADR-0036). Index-stable: an
/// [`OutlineEntry::Element`] names a position in this `Vec` by index.
pub parts: Vec<Part>,
/// The full depth-first rendering order: elements (by index into `parts`)
/// interleaved with section headings, at the position they open in the
/// tree (ADR-0029). Consumed by the augmented-manifest builder so a
/// tree (ADR-0036). Consumed by the augmented-manifest builder so a
/// template can render headings in their real position; `cph-check`'s
/// structural/schema/coverage phases do not need it (they use `parts`).
pub outline: Vec<OutlineEntry>,
@@ -77,7 +77,7 @@ impl Lesson {
}
}
/// One entry in the lesson's full rendering-order sequence (ADR-0029).
/// One entry in the lesson's full rendering-order sequence (ADR-0036).
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum OutlineEntry {
/// An element at this position: `part_index` into [`Lesson::parts`].
@@ -88,7 +88,7 @@ pub enum OutlineEntry {
depth: u32,
},
/// A section container opens here. Contributes no element; it is a
/// heading, not a part (ADR-0029). `depth` is the section's nesting depth
/// heading, not a part (ADR-0036). `depth` is the section's nesting depth
/// (1 = a section directly under the engineering-file root).
Section {
/// The container's declared kind (MVP: always `"section"`; validated
@@ -378,17 +378,17 @@ pub struct Info {
/// One element leaf, plus its loaded `element.toml` descriptor.
///
/// `path` is the element folder's path relative to the engineering-file root,
/// regardless of how deeply nested it is in the outline tree (ADR-0029) — the
/// regardless of how deeply nested it is in the outline tree (ADR-0036) — the
/// loader accumulates full root-relative paths during the depth-first walk, so
/// every downstream consumer (schema validation, augmented-manifest include
/// paths) keeps working against a root-relative path exactly as before ADR-0029.
/// paths) keeps working against a root-relative path exactly as before ADR-0036.
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct Part {
/// Declared kind from the containing outline entry (ADR-0006). This is the
/// **declared** kind, which may disagree with the `element.toml` kind (a
/// disagreement is reported as [`DiagCode::UnknownKind`], but the declared
/// kind — not the descriptor's — is what `cph-check`'s known-kind check
/// tests, matching pre-ADR-0029 behavior).
/// tests, matching pre-ADR-0036 behavior).
pub kind: String,
/// Element folder path, root-relative, kept verbatim (forward/back slashes
/// as the OS provides) for diagnostics/display.
@@ -418,7 +418,7 @@ pub struct ElementDescriptor {
// --- raw deserialization shapes (mirror the on-disk TOML) ---------------------
/// One `manifest.toml`, root or container (ADR-0029). Root-only tables
/// One `manifest.toml`, root or container (ADR-0036). Root-only tables
/// (`project`/`info`/`targets`) and the container-only `group` table coexist in
/// one shape; [`load`]/[`load_children`] enforce which is expected where and
/// flag misplacement rather than rejecting parse outright (non-fatal, like
@@ -471,14 +471,14 @@ impl RawAuthor {
}
}
/// A container's optional `[group]` table (ADR-0029): presentation metadata for
/// A container's optional `[group]` table (ADR-0036): presentation metadata for
/// a section, kept minimal per the ADR's recommended default.
#[derive(Debug, Deserialize)]
struct RawGroup {
title: Option<String>,
}
/// One `children` entry (ADR-0029): a `kind` + a **parent-relative** `path`.
/// One `children` entry (ADR-0036): a `kind` + a **parent-relative** `path`.
/// The loader resolves on disk whether the named folder is a leaf
/// (`element.toml`) or a container (`manifest.toml`) — `kind` is the declared
/// label, cross-checked against the leaf's `element.toml` kind (unchanged from
@@ -600,7 +600,7 @@ pub fn load(root: &Path) -> (Option<Lesson>, Vec<Diagnostic>) {
}
};
// The root is the implicit top container (ADR-0029): it must not declare
// The root is the implicit top container (ADR-0036): it must not declare
// `[group]` (that is container-only presentation metadata).
if raw.group.is_some() {
diags.push(
@@ -625,7 +625,7 @@ pub fn load(root: &Path) -> (Option<Lesson>, Vec<Diagnostic>) {
.map(|(name, value)| parse_target(name, value, &mut diags))
.collect();
// Walk the outline tree depth-first (ADR-0029), starting at the root's own
// Walk the outline tree depth-first (ADR-0036), starting at the root's own
// children. A direct child section of the root opens at depth 1.
let mut parts = Vec::new();
let mut outline = Vec::new();
@@ -652,7 +652,7 @@ pub fn load(root: &Path) -> (Option<Lesson>, Vec<Diagnostic>) {
}
/// Recursively load one container's ordered `children` into `parts`/`outline`
/// (ADR-0029). `container_dir` is the container's own absolute directory;
/// (ADR-0036). `container_dir` is the container's own absolute directory;
/// `rel_prefix` is that container's own root-relative path (empty for the
/// engineering-file root). `next_section_depth` is the depth a **direct**
/// section child of this container would open at (1 for the root's children).
@@ -884,7 +884,7 @@ fn read_container_manifest(dir: &Path, diags: &mut Vec<Diagnostic>) -> Option<Ra
/// Record a broken child as a placeholder leaf (empty descriptor) so that
/// order/count stays observable even when the folder could not be resolved to
/// either a leaf or a container. Mirrors the pre-ADR-0029 behavior for a
/// either a leaf or a container. Mirrors the pre-ADR-0036 behavior for a
/// missing part folder.
fn push_broken_leaf(
parts: &mut Vec<Part>,
@@ -1498,9 +1498,9 @@ fn has_parent_traversal(path: &Path) -> bool {
path.components().any(|c| matches!(c, Component::ParentDir))
}
// --- bundle: an ordered arrangement of lessons (ADR-0030) --------------------
// --- bundle: an ordered arrangement of lessons (ADR-0037) --------------------
/// A **bundle** (ADR-0030): a directory carrying `bundle.toml`, which arranges
/// A **bundle** (ADR-0037): a directory carrying `bundle.toml`, which arranges
/// an ordered list of already-authored, self-contained lessons into one export
/// unit. Discharges ADR-0005's deferred "course = arrangement of lessons" for
/// the export purpose, without inventing a full course-authoring model.
@@ -1540,7 +1540,7 @@ pub struct BundleLesson {
/// `bundle.toml` (kept verbatim for diagnostics/display).
pub path: PathBuf,
/// Which of that lesson's own declared targets to render into the bundle
/// (ADR-0030: the bundle assembles a lesson's already-authored content, it
/// (ADR-0037: the bundle assembles a lesson's already-authored content, it
/// does not re-target it). Defaults to the lesson's first declared target,
/// or `"student"` if the lesson declares none.
pub target: String,
+1 -1
View File
@@ -1,4 +1,4 @@
//! Integration tests for `cph_model::load_bundle` (ADR-0030): an ordered
//! Integration tests for `cph_model::load_bundle` (ADR-0037): an ordered
//! arrangement of self-contained lessons, loaded from `bundle.toml`.
use std::path::PathBuf;
+2 -2
View File
@@ -1,5 +1,5 @@
//! Integration tests for `cph_model::load`, driven by static fixtures under
//! `tests/fixtures/`. The fixtures double as documentation of the ADR-0029
//! `tests/fixtures/`. The fixtures double as documentation of the ADR-0036
//! on-disk format (a nested outline manifest; supersedes ADR-0008's flat
//! `[[parts]]`).
@@ -111,7 +111,7 @@ fn nested_sections_flatten_depth_first_with_correct_depths() {
"nested fixture must have no diagnostics, got: {diags:?}"
);
// DFS pre-order element sequence (ADR-0029): containers contribute no
// DFS pre-order element sequence (ADR-0036): containers contribute no
// element of their own.
let paths: Vec<_> = lesson.parts.iter().map(|p| p.path.clone()).collect();
assert_eq!(