forked from EduCraft/curriculum-project-hub
ecc92c9a87
上游 0029/0030 与本地 filelib 侧同号 ADR 相撞,cph 两份改到本地空闲号段, 代码锚点引用一并跟随。
168 lines
9.0 KiB
Markdown
168 lines
9.0 KiB
Markdown
# ADR 0037: Batch & Combined Export
|
|
|
|
## Status
|
|
|
|
Accepted. **Extends/refines ADR-0009 and ADR-0011** (export target = a build
|
|
producing a typed artifact) by adding **two** export dimensions that today have
|
|
no home: (1) building **multiple targets of one lesson** in one batch, and (2)
|
|
**combining multiple lessons into one** artifact — a 讲义合集 / course bundle.
|
|
It does **not** redefine the SingleFile vs FileTree artifact distinction
|
|
(ADR-0011) or the single-`typstCompile`-step MVP; it adds the *collection*
|
|
semantics on top.
|
|
|
|
## Context
|
|
|
|
Today `cph build --target T` builds exactly one target `T` of one engineering
|
|
file into one artifact. Two real needs fall outside that:
|
|
|
|
1. **A lesson's multiple versions.** A lesson already declares several targets
|
|
(student handout, teacher plan, slides, script). Producing all of them is
|
|
today N separate `cph build` invocations with no shared invocation, ordering,
|
|
or failure summary. Teachers preparing a lesson want "build the whole lesson
|
|
in all needed forms" as one action.
|
|
|
|
2. **Combining lessons into one deliverable.** ADR-0005 deferred "course =
|
|
arrangement of lessons" — a course/unit is *not* an engineering file; it is
|
|
an arrangement of lessons "modeled elsewhere". The elsewhere is empty. A real
|
|
deliverable is a **讲义合集 / course bundle** — several lessons ordered into
|
|
one document (e.g. "期中复习合集", a term bundle, a topic compilation). This
|
|
spans multiple engineering files and currently has no model and no CLI path.
|
|
|
|
Both are product-plain features (the 老师端 exports; a bundle is what a teacher
|
|
hands a class), not architectural speculation.
|
|
|
|
## Decision
|
|
|
|
### The existing single-lesson single-target build is the atomic unit
|
|
|
|
ADR-0009/0011's model — a target is a build over one lesson producing one
|
|
`Artifact` via ordered `Step`s — is unchanged and remains the *unit*. Nothing
|
|
below replaces it; the new semantics are **aggregations over that unit**.
|
|
|
|
### Dimension 1 — Batch: build a set of targets of one lesson
|
|
|
|
`cph build` on a lesson gains the ability to produce **several targets in one
|
|
invocation**, as one batched operation:
|
|
|
|
- The lesson root `manifest.toml` `[targets.*]` already enumerates the declared
|
|
targets and their order (ADR-0008/0011). Building "all declared targets" is the
|
|
default batch: each declared target builds to its own artifact
|
|
(`build/<target>.{pdf,md}`), in declaration order.
|
|
- A batched build is **non-transactional and independent per target**: each
|
|
target is a separate build with its own artifact, own diagnostics, own
|
|
exit/result. One target failing (e.g. teacher plan PDF) does not block the rest
|
|
(student PDF), matching the per-target independent-failure stance of
|
|
ADR-0009's "missing render for a used kind ⇒ warning, non-blocking".
|
|
- The batch emits a **summary**: a per-target ledger (ok/failed + its artifact
|
|
or error), and a **non-zero aggregate exit if any target failed** to produce
|
|
its artifact. A target that fails to produce its artifact is a real defect
|
|
(this repo's fail-fast stance — don't paper over bugs), distinct from
|
|
ADR-0009's "missing render rule ⇒ warning" (a policy-level skip, non-error).
|
|
"Don't block the rest" still holds: every target is attempted, but a single
|
|
failure makes the aggregate non-zero so CI/observability catch it. This is the
|
|
CLI's job; it is the natural "build the whole lesson" affordance.
|
|
|
|
### Dimension 2 — Combined: arrange multiple lessons into one artifact
|
|
|
|
A **course bundle** is a new, lightweight, second kind of engineering-file-adjacent
|
|
unit: an **ordered arrangement of lessons** (ADR-0005's deferred "course =
|
|
arrangement of lessons" finally given a concrete export home).
|
|
|
|
#### Bundle carrier: a directory containing `bundle.toml`
|
|
|
|
A bundle is a **directory containing `bundle.toml`**. A directory gives the
|
|
bundle a stable root for relative lesson paths and a home for build output
|
|
(echoing ADR-0007's "engineering file = directory"). The `bundle.toml` carries:
|
|
|
|
- `[info]` — the bundle's own title/author (of the 合集);
|
|
- `[targets.*]` — the bundle's build configuration, reusing ADR-0011's build
|
|
mechanism;
|
|
- an ordered `lessons` array — each entry: a lesson path (relative to the
|
|
bundle root, pointing at each engineering-file root, each a self-contained
|
|
directory tree per ADR-0007) plus optional per-lesson per-target overrides.
|
|
|
|
A bundle target produces an **ordered concatenation/assembly of the lessons'
|
|
artifacts or content** into one `Artifact`, reusing the ADR-0011 artifact ADT:
|
|
|
|
- `SingleFile` — a combined document (讲义合集): the lessons' content assembled
|
|
in order into one compiled document, with the existing cross-reference /
|
|
`@label` machinery working because it is one compiled document (the same
|
|
reason ADR-0011 gives for why SingleFile concatenation works at all).
|
|
- `FileTree` — each lesson to its own file plus a generated index (ADR-0011's
|
|
third-party-archive case, now with a first-class multi-lesson trigger).
|
|
|
|
A bundle target's steps are the **ordered typed steps** of ADR-0011, but the
|
|
"map" now operates at the level of whole lessons rather than a single lesson's
|
|
parts: a step like `assembleLessons` (ordered inclusion of each lesson's
|
|
content/artifact) plus the existing `typstCompile`/`shell` steps for assembly
|
|
and any post-processing. Concretely the framework provides a
|
|
`typstCompile`-style step that pulls each listed lesson's content in order into
|
|
one document (mirroring how a single lesson's template pulls its parts).
|
|
|
|
#### Renumbering in a `SingleFile` bundle: template-resident, default reset per lesson
|
|
|
|
Numbering is presentation, which ADR-0011 already owns to the template file (not
|
|
the manifest), so the **bundle target's template decides** whether auto-counters
|
|
reset at lesson boundaries; the framework ships a helper to reset counters at a
|
|
lesson boundary. The **recommended default resets auto-counters at each lesson
|
|
boundary**: lessons are authored self-contained, so an internal "例题3" means
|
|
that lesson's 例题3; cross-lesson continuation would silently break author
|
|
references. A genuine "全书 continuous numbering" is an explicit template
|
|
override. `@label` cross-references stay global (resolved by label name,
|
|
independent of counters); only auto-increment counters reset.
|
|
|
|
#### Bundles do not nest (MVP)
|
|
|
|
A bundle references lessons only, not other bundles, until a real need appears —
|
|
mirrors the tree-nesting simplicity and keeps the first bundle target minimal.
|
|
|
|
### Invariant: lessons stay self-contained; combination is export-time only
|
|
|
|
Opening the door to combining lessons must **not** open the door to cross-lesson
|
|
imports inside a lesson's own content (ADR-0006's import boundary: within one
|
|
engineering file plus `@package`, never into a sibling lesson). A bundle is
|
|
allowed to *assemble already-authored lessons at export time* — reading their
|
|
content for the combined artifact — but no lesson's rich content may `import`
|
|
another lesson's internals as part of *its own* authoring. Combination is a
|
|
**projection over self-contained lessons**, exactly as a render target is a
|
|
projection over a lesson. This keeps each engineering file independently
|
|
checkable, buildable, and movable, and avoids reintroducing cross-file coupling
|
|
ADR-0006 explicitly rejected.
|
|
|
|
### CLI surface
|
|
|
|
- `cph build <lesson>` → all declared targets (the batch default).
|
|
- `cph build <lesson> --target student --target teacher` → the named subset
|
|
(the multi-version batch), in the given order.
|
|
- `cph bundle <bundle-path> --target <name>` → build one combined bundle target
|
|
(`SingleFile` merged doc or `FileTree`), giving the multi-lesson merge.
|
|
(`cph build` on a bundle root is the batch-of-bundle-targets equivalent.)
|
|
|
|
## Consequences
|
|
|
|
- **One lesson, many versions** is one command with a per-target ledger — the
|
|
natural 老师端 "导出全部版本" action, and any single failure surfaces as a
|
|
non-zero aggregate.
|
|
- **Course = arrangement of lessons** gets a concrete, export-focused home (the
|
|
bundle), discharging the ADR-0005 deferred item without inventing a full
|
|
course-authoring model.
|
|
- The **artifact/distinction and build-step machinery (ADR-0011) is reused** — a
|
|
bundle target is just a build whose inputs are whole lessons, not a new
|
|
parallel export engine.
|
|
- **Self-containment stays** (ADR-0006/0007): each lesson remains independently
|
|
checkable and buildable; the bundle only reads them for assembly. A lesson and
|
|
a bundle can version/evolve independently.
|
|
- The teacher surface can offer "export all versions" (batch) and "compile into
|
|
a 合集" (combined) as two concrete, productisible actions.
|
|
|
|
## Open Questions / Deferred
|
|
|
|
- **Bundle-of-bundles / nesting** — no nesting in MVP; re-open only when a real
|
|
need appears.
|
|
- **Dedup/caching across targets and lessons** — none in MVP, consistent with
|
|
ADR-0011's "no caching in MVP".
|
|
- **Exact counter-reset semantics in a `SingleFile` bundle** — the default
|
|
(reset per lesson) is decided; the precise mechanism (which counters, how the
|
|
template override is expressed) is settled with the first bundle template
|
|
implementation.
|