Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Common Frontmatter Standard

ADR-26042: Common Frontmatter Standard

Date

2026-04-14 (accepted — enforcement of options.type as mandatory field)

Status

accepted

Supersedes ADR-26023.

Context

As of March 2026, the repository has 10 governed document types (9 content + 1 service) across multiple directories, but frontmatter governance is fragmented. Only 4 types (ADR, analysis, retrospective, source) have full schema validation — each defined independently in adr_config.yaml and evidence.config.yaml. The remaining 5 content types (tutorial, guide, script instruction, package spec, policy) have no frontmatter validation at all.

ADR-26023 established MyST-native field names (title, author, date, options.version, options.birth) for content articles but did not define a type system or cover non-article types. The options.* nesting hides fields from validation scripts. Meanwhile, evidence.config.yaml defines its own common_required_fields as a stopgap, duplicating field definitions across configs.

A systematic audit in A-26008 revealed three structural problems:

  1. No type self-identification — validation scripts infer type from directory path rather than from the document itself.

  2. Flat field model fails — a single required-fields list cannot serve both service files (2 fields) and full content types (10+ fields).

  3. date semantics are inconsistent — ADRs treat date as birth date; analyses treat it as last-modified; ADR-26023 defines it as last-modified. The same field means different things depending on type.

The audit proposed a composable block architecture following the DITA specialization principle: types compose blocks additively, never removing fields. This eliminates the need for exclusion rules while accommodating the full range from minimal service files to rich content types.

Decision

We adopt a composable block frontmatter schema as the universal standard for all governed documents. Every .md and Jupytext-paired .ipynb file in a governed directory carries frontmatter composed from three blocks. Types declare which blocks they use; blocks are only added, never subtracted.

1. Composable Blocks

Identity block — present in every governed document:

FieldPurposeMaintenanceMyST-native?
titleDocument identityHumanYes
typeSelf-describing file typeHuman (set once)No (options.type)
authorsResponsible party (owner, not necessarily the writer — documents may be AI-generated under human control)HumanYes

Discovery block — enables agent progressive disclosure:

FieldPurposeMaintenanceMyST-native?
descriptionBrief elevator pitchHumanYes
tagsDomain classificationHuman (validated against vocabulary)Yes
token_sizeContext budget costAuto (pre-commit hook)No (options.token_size)

Lifecycle block — production traceability:

FieldPurposeMaintenanceMyST-native?
dateLast meaningful updateAuto (pre-commit hook)Yes
birthCreation dateAuto (pre-commit hook on new file)No (options.birth)
versionSemVer traceabilityHuman bump, auto-validated — if date changed, version must changeNo (options.version)

Fields marked “MyST-native” in the tables above are top-level YAML keys rendered by the MyST site. All other fields go under options.* — invisible to MyST but accessible to validation scripts and RAG pipelines. The authoritative list of which fields are MyST-native is the block tables, not this prose.

This preserves ADR-26023’s principle of leveraging MyST frontmatter natively while extending it with ecosystem-specific metadata.

2. Type Composition

Each type declares which blocks it composes, plus type-specific required and optional fields:

TypeBlocksType-Specific RequiredType-Specific Optional
adridentity + discovery + lifecycleid, statussuperseded_by
analysisidentity + discovery + lifecycleid, statussources, produces
retrospectiveidentity + discovery + lifecycleid, status, severityproduces
sourceidentity + discoveryid, modelextracted_into
tutorialidentity + discovery + lifecycle——
guideidentity + discovery + lifecycle——
script_instructionidentity + discovery + lifecycle——
package_specidentity + discovery + lifecycle——
policyidentity + discovery + lifecycle——
serviceidentity—role (generated/template/navigation), version

Sources skip lifecycle because they are ephemeral (three-commit workflow). Service files skip both discovery and lifecycle because they are infrastructure, not authored content. The manifesto type from A-26008 is absorbed into policy — it is a singleton document that shares the same governance needs.

3. date Semantic Unification

All types adopt the same date semantics: last meaningful update. The birth field captures creation date. This resolves the inconsistency where ADRs treated date as birth date while other types treated it as last-modified.

4. id Is Type-Specific

The id field is not universal. Only types that cross-reference each other via frontmatter fields carry id: ADR, analysis, retrospective, source. Types referenced solely via file path links (tutorials, guides, etc.) do not need synthetic IDs.

5. Jupytext Dual-Block Parsing

Validation scripts must support Jupytext dual-block files, parsing the content frontmatter block (Block 2, after the H1 title) rather than the Jupytext config block (Block 1).

6. Centralized Configuration

The block definitions, type registry, and shared tag vocabulary are codified in the project’s centralized governance config directory (ADR-26036). The shared config (conf.yaml) is the SSoT for what fields every type must have. Doc type configs (<doc_type>.conf.yaml) retain domain-specific operational rules and inherit common vocabulary via parent_config. See A-26010 for the evidence base behind the scope-isolated directory pattern.

Important: Strict Additive Inheritance

Between ecosystem projects, the hub project’s config provides defaults. Spoke projects’ configs extend them:

  1. Hub fields are immutable — spokes cannot remove, rename, or change semantics of hub-defined fields.

  2. Spokes add — type-specific fields extend the hub schema.

  3. Spokes tighten — a spoke may promote a hub optional field to required for its type, but never the reverse.

  4. Spokes that override hub semantics are invalid and rejected by validation.

7. Migration

Automated, in two phases: (A) ADRs first — one-shot script to fix the date → birth semantic shift, since lazy migration would leave 41 files with silently wrong semantics; (B) all other governed files — auto-fix script analogous to check_adr.py --fix, adding missing fields per the type registry. Human-authored fields (description) get TODO placeholders for later review.

Consequences

Positive

Negative / Risks

Alternatives

References

Participants

  1. Vadim Rudakov

  2. Claude (claude-opus-4-6, AI Engineering Advisor)