accepted

    Generator emission of manual content-collection frontmatter

    Context and Problem Statement

    The 228 manual markdowns at docs/manual/ were generated by four parallel workers during the prior session (per commits 6328d03 / 0c3619d / fbf8d85 / 4c16c58 / b93deb2). Those workers emitted YAML frontmatter shaped for human readability, NOT for Astro content-collection Zod validation (which lands in ADR-0016).

    For regenerability to hold (per ADR-0015 §“Source of truth”), the generator must emit collection-valid frontmatter when re-emitting the manual from updated TTLs. Otherwise: regenerate → Zod validation fails at build → manual edits needed → regenerability broken.

    This ADR adds an opda-gen extension (opda-gen emit-manual or similar) that walks the source TTLs and emits the 228 markdowns with frontmatter matching the Zod schema declared in ADR-0016.

    Per the implementation programme plan §4, this is Phase 5 — gated on Phase 2 (ADR-0017 components, which declare the entry consumption contract) + Phase 3 (ADR-0018 plumbing, which declares any frontmatter the plugins inject).

    Implicit in ADR-0015 §“Bad” item (“docs/manual/ content needs frontmatter for Zod validation”) — this ADR owns that follow-up.

    Decision Drivers

    • The generator already runs at tools/opda-gen/ per ADR-0007 + ADR-0008 — the IA’s “docs-gen mode” is a natural extension.
    • Frontmatter shape must match ADR-0016’s Zod schema verbatim — the implementing worker reads that schema as the spec.
    • Mechanical extraction from TTLs:
      • tier ← which tier this entry belongs to (function of output path)
      • module ← which module TTL the entry derives from
      • kind ← entry type per the Zod enum (entity / scheme / exemplar / tier-readme / module-readme / cross-cutting / per-module-deployment / derived-profile / overlay-deployment / operations)
      • entityUri ← opda:<LocalName> for entity / scheme / exemplar entries (extracted from TTL owl:Class / skos:ConceptScheme / exemplar typing)
      • sourceTtl ← which TTL file holds the canonical source
      • sourceOdr ← dct:source URI from the TTL block
      • title ← rdfs:label @en for entity / scheme; H1 for the file otherwise
    • Round-trip discipline: opda-gen emit-manual should be idempotent — running twice produces byte-identical output (per the existing ci-byte-identity discipline).
    • Scope cap: frontmatter ONLY. This ADR does not change content. Content changes route through the IA spec amendment cycle.

    Considered Options

    • A — Extend opda-gen with emit-manual subcommand — chosen per programme plan.
    • B — Separate opda-manual-gen tool — duplicates the TTL-parsing infrastructure already in opda-gen.
    • C — Astro build-time script — couples regeneration to the Astro build; the generator pattern is to emit + commit ahead of build, not at build time.
    • D — Leave frontmatter manually edited per file — defeats regenerability; rejected per ADR-0015.

    Decision Outcome

    Option A — opda-gen emit-manual --tier <name> and opda-gen emit-manual (umbrella) subcommands shipped at tools/opda-gen/src/opda_gen/emitters/manual.py; CLI wired in tools/opda-gen/src/opda_gen/cli.py.

    G19a scope decision: option (c) — Tier READMEs, module READMEs, umbrella README, and VALIDATION-REPORT are excluded from generator scope. Their editorial body (including Phase 4’s “See also: Modelling section” blocks) is preserved intact. See implementation report §“Scope decision” for rationale.

    Consequences

    Positive:

    • All 184 in-scope manual markdowns now carry collection-valid frontmatter matching the ADR-0016 Zod schema.
    • opda-gen emit-manual is idempotent: second run produces zero changes.
    • Phase 4 “See also: Modelling section” blocks in 4 tier READMEs are fully preserved.
    • Astro build exits 0 with 386 pages and zero Zod validation errors post-emission.

    Neutral:

    • Tier READMEs and module READMEs remain without kind / tier / module frontmatter fields in the generated output; src/lib/manual.ts path-derived helpers remain the source of truth for those entries.
    • pyyaml is a new runtime dependency for the emitter; not yet declared in pyproject.toml (follow-up for validator).

    Negative / open items:

    • physical-ontology/<module>/classes.md and sibling files get kind: entity and a synthetic entityUri (e.g. opda:Classes) because they cover all classes in a module rather than a single entity. A future ADR could add kind: module-classes / kind: module-shapes enum values.

    Confirmation

    1. tools/opda-gen/src/opda_gen/emitters/manual.py exists with emit_manual(output_dir, *, tier=None) API — CONFIRMED
    2. opda-gen emit-manual CLI subcommand wired — CONFIRMED (verify via opda-gen --help)
    3. opda-gen emit umbrella runs emit-manual at the end of the existing pipeline — CONFIRMED
    4. opda-gen emit-manual --output docs/manual twice → second run: 0 files updated, 218 files skipped — CONFIRMED (idempotency)
    5. astro build after regeneration: exit 0, 386 pages, zero Zod validation errors — CONFIRMED
    6. 31 tests in tools/opda-gen/tests/test_manual.py covering per-tier emission, frontmatter fields, byte-identity, merge discipline; 158 total passing — CONFIRMED
    7. Implementation report: docs/adr/implementation-reports/ADR-0020-implementation.md — CONFIRMED

    Programme retirement

    ADR-0020 acceptance is the final gate in the manual-Astro-integration programme. When this validation passes:

    • ADR-0015 §Confirmation 10/10 green
    • ADRs 0016–0020 all status: accepted
    • Manual content regenerable from TTLs without manual frontmatter edits
    • Site builds + deploys

    Programme retires per docs/plan/manual-astro-integration.md §11.

    More Information

    ← Back to ADR Corpus  |  View source

    ADRs are MADR-format architecture decisions. A superseded ADR is replaced by a later record rather than edited in place.

    Comments

    Loading comments…