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: Taxonomy Audit and Composable Block Design

A-26008: Common Frontmatter Standard: Taxonomy Audit and Composable Block Design

Problem Statement

A-26005 proposed a comprehensive document type taxonomy (~20 types, 7 categories) with 8 universal frontmatter fields for every governed document. Before codifying this into ADR-26042 (Common Frontmatter Standard), a systematic audit compared the taxonomy against actual repository contents to determine: which types exist, what has governance, what belongs outside the type system, and whether the “8 universal fields” model holds.

The audit revealed that the taxonomy was over-specified and the flat field model doesn’t fit all document types. This analysis captures the corrected type registry, the misc/ boundary principle, the Jupytext two-block parsing problem, and proposes a composable block frontmatter schema as the architecture for ADR-26042.

Key Insights

The misc/ Boundary — What Is Not a Document Type

The most important finding is architectural: misc/ is the repo’s untyped scratch space. Files live in misc/ precisely because they do not belong to any governed type. A-26005 incorrectly treated several misc/ items as document types (plans, announcements, insights, tech debt). They are not.

The misc/ directory contains:

SubdirectoryContentsWhy It’s Untyped
in_progress/Draft content mirroring the ai_system/ chapter structure (~40 files across 6 chapter dirs, plus drafts/, mlops/, security/)Staging area — files graduate to ai_system/ when ready, gaining frontmatter and type governance at that point
pr/tg_channel_ai_learning/50 Telegram channel postsExternal communication — published to Telegram, the repo is staging, not the archive
pr/ (root)PR images, draft template, READMEService files for the promotional workflow
plan/Implementation plans (active + implemented/ archive)Ephemeral work items — consumed and discarded between context switches
plan/techdebt.mdTechnical debt registerService file — tracking mechanism, not a reference document
gherkin_tmp/Temporary analysis filesScratch work
todo.mdPlain text scratch padNo structure, no frontmatter — by design
insights.mdCaptured learningsScratch — absorbed into formal docs and deleted

Key principle: No persistent artifact (ADR, analysis, guide) should ever reference a misc/ file, because misc/ files are ephemeral by nature. This is already documented in CLAUDE.md: “Never link from persistent artifacts to ephemeral files (misc/plan/, misc/todo.md, evidence/sources/).”

Governed Document Types — The Actual Registry

After excluding misc/ and non-document files, the repo has 11 document types: 10 content types across governed directories, plus 1 service type for infrastructure files.

Content Types (10)
Typetype: valueDirectoryFile CountCurrent Governance
ADRadrarchitecture/adr/41Full (schema + sections + lifecycle)
Analysisanalysisevidence/analyses/8 (incl. this file)Full
Retrospectiveretrospectiveevidence/retrospective/0Full (config exists, no files yet)
Sourcesourceevidence/sources/8Full
Tutorialtutorialai_system/*/~7Partial (Jupytext sync)
Guideguidetools/docs/git/5None (link checks only)
Script Instructionscript_instructiontools/docs/scripts_instructions/16Partial (existence check)
Package Specpackage_specarchitecture/packages/3None
Manifestomanifestoarchitecture/1None (singleton)
Policypolicysecurity/0None (empty directory)
Service Type (1)

Service files are .md files that live in governed directories but are not authored content — they are infrastructure supporting the type system itself. In UNIX terms, these are device files (/dev/): they look like regular files but serve a system function.

FileLocationRoleNotes
CHANGELOGCHANGELOGgeneratedAuto-generated by generate_changelog.py
ADR Indexarchitecture/adr/adr_index.mdgeneratedAuto-generated by check_adr.py --fix
ADR Templatearchitecture/adr/adr_template.mdtemplateBlueprint for creating ADR instances
README filesVarious directoriesnavigationDirectory-level orientation

Service files carry only the identity block (title, type) — they do not compose the discovery or lifecycle blocks. Templates may optionally add version for change tracking.

Governance Gap

Of 10 content types, only 4 have full validation (schema + sections + lifecycle): adr, analysis, retrospective, source. These 4 types are governed by adr_config.yaml and evidence.config.yaml, which independently define their own required fields, date formats, and tag vocabularies — the config fragmentation documented in misc/plan/techdebt.md TD-001. The remaining 6 types (tutorial, guide, script_instruction, package_spec, manifesto, policy) have no frontmatter schema validation. This is the governance gap ADR-26042 must close.

Aspirational Types from A-26005 (Deferred)

These types appear in the A-26005 taxonomy but have no files and no config. They should be registered only when the first file is created:

TypeA-26005 CategoryStatus
session_historyRUNTIMEPlanned for Phase 2 (agent runtime)
release_notesOPERATIONALNot started
api_referenceREFERENCENot started
handbookLEARNINGAmbiguous — tutorials may already serve this role

The id Field — Type-Specific, Not Universal

A-26005’s VRL model treats id as the primary key for every document. The audit shows this doesn’t hold universally.

Some document types cross-reference each other by ID in frontmatter fields:

The id field exists to serve these cross-references. Types that cross-reference each other (ADR, analysis, retrospective, source) need IDs. Types that are referenced only via file path in markdown links (tutorials, guides, manifesto, policies) do not.

Additional reasons id is not universal:

Decision: id stays type-specific. Types with frontmatter cross-references define id in their type-specific extensions.

The Jupytext Two-Block Frontmatter Problem

Tutorial files (.md paired with .ipynb via Jupytext) have a non-standard frontmatter structure that conflicts with the common frontmatter standard.

Three Observed Patterns

Pattern A — Old style (pre-ADR-26023, e.g., choosing_model_size.md, algebra_gemm_engineering_standard.md):

---                              ← Block 1: Jupytext config
jupytext:
  formats: md:myst,ipynb
  text_representation: ...
kernelspec: ...
---
# H1 Title                      ← Title outside frontmatter
+++
---                              ← Block 2: free-form metadata (NOT valid YAML)
Owner: Vadim Rudakov
Version: 1.0.0
Birth: 2025-10-19
Last Modified: 2026-02-05
---
+++
Content starts here...

Pattern B — Post-ADR-26023 (e.g., yaml_frontmatter_for_ai_enabled_engineering.md):

---                              ← Block 1: Jupytext config
jupytext:
  formats: md:myst,ipynb
kernelspec: ...
---
# H1 Title                      ← Title outside frontmatter
---                              ← Block 2: proper YAML frontmatter
title: YAML Frontmatter for AI-Enabled Engineering
author: Vadim Rudakov, rudakow.wadim@gmail.com
date: 2026-02-08
options:
  version: 0.3.0
  birth: 2026-02-05
---
+++
Content starts here...

Pattern C — Non-Jupytext files (ADRs, evidence, guides, etc.):

---                              ← Block 1: content frontmatter (only block)
id: 26039
title: "pgvector as Ecosystem Database Standard"
date: 2026-03-08
status: proposed
tags: [architecture, context_management, devops]
---
Content starts here...
The Parsing Problem

A standard parse_frontmatter() reads the first ---...--- block. For Patterns A and B, this returns Jupytext config (jupytext:, kernelspec:), not content metadata. The actual content frontmatter is Block 2, positioned after the H1 title.

This means:

  1. Validation scripts must detect Jupytext-paired files — by checking for jupytext: key in Block 1, or by checking for a sibling .ipynb file

  2. For Jupytext files, parse Block 2 — skip Block 1 (Jupytext config) and extract the second ---...--- block

  3. Pattern A files need migration — their Block 2 is free-form text (Owner: ...), not valid YAML. These must be converted to proper YAML frontmatter during the lazy migration

  4. MyST renderer handles this correctly — the two-block structure is a MyST convention (ADR-26023 §Two-Block Jupytext Structure), so the published site works. Only validation scripts are affected

Impact on ADR-26042

The frontmatter standard must specify:

ADR-26023 Reconciliation

ADR-26023 established 5 content frontmatter fields: title, author/authors, date, options.version, options.birth. The common frontmatter core proposed by A-26005 has 8 fields: title, description, type, date, birth, version, tags, token_size.

Key differences to resolve:

FieldADR-26023A-26005 ProposalResolution Direction
dateTop-level (MyST renders), semantics vary by typeTop-level, “last meaningful update”Semantic unification — date means “last meaningful update” for all types. See semantic shift below
authorTop-level (MyST renders)Not in common core (type-specific)Keep as optional — not all types have authors (sources have model)
versionUnder options.versionTop-levelMove to top-level — validation scripts need direct access
birthUnder options.birthTop-levelMove to top-level, auto-maintained by pre-commit hook (behavioral change from human-maintained)
descriptionNot in ADR-26023RequiredNew field — the critical agent context management mechanism
typeNot in ADR-26023RequiredNew field — self-describing documents
token_sizeNot in ADR-26023Required (auto-maintained)New field — context budget planning
tagsNot in ADR-26023 (ADRs have it)RequiredPromote to universal — domain classification for all types
The date Semantic Shift — Critical Migration Issue

Currently, date has inconsistent semantics across types:

The composable block model resolves this by splitting the two meanings:

Migration impact for ADRs: The current date values in 41 ADRs are birth dates. During migration, these values must be copied to birth, and date must be set to the actual last-modified date. This is a data migration, not just a field rename.

Moving version and birth from options.* to top-level, and changing birth from human-maintained to auto-maintained, are breaking changes relative to ADR-26023. ADR-26042 should formally supersede ADR-26023’s field placement while preserving its MyST-native naming principle.

Composable Block Frontmatter Schema

The audit reveals that a flat “8 required fields for everything” model doesn’t work — service files need only 2 fields, sources don’t need version or birth. Instead of inheritance with exclusions (subtract what doesn’t apply), the schema uses composable blocks — additive only, like Lego. Each type declares which blocks it composes. No exclusions, no overrides.

This follows the specialization principle from OASIS DITA (Darwin Information Typing Architecture, §2.1.3 Specialization): types only widen the base contract (add fields/constraints), never narrow it (remove fields). See A-26005 §DITA Specialization for the detailed analysis of this model applied to the repo’s document types.

Block Definitions

identity block — “What is this file?” (every governed .md/.ipynb, no exceptions):

FieldPurposeMaintenance
titleDocument identityHuman
typeSelf-describing — the file type byteHuman (set once)

discovery block — “Should I read it?” (enables agent progressive disclosure):

FieldPurposeMaintenance
descriptionThe document’s elevator pitch (~20 tokens)Human
tagsDomain classification — semantic namespaceHuman (validated against vocabulary)
token_sizeContext budget cost — i_sizeAuto (pre-commit hook)

This mirrors the SKILL.md progressive disclosure pattern documented in A-26009: metadata (~100 tokens) lets the agent decide whether to load the full content (~5K tokens).

lifecycle block — “How current is it?” (production traceability):

FieldPurposeMaintenance
dateLast meaningful update — i_mtimeAuto (pre-commit hook on content change)
birthCreation date — i_ctimeAuto (pre-commit hook on new file)
versionProduction traceability — SemVer per AVPHuman bump, auto-validated (must change when content changes)
Type Composition

Each type declares which blocks it uses, plus type-specific fields:

blocks:
  identity:   [title, type]
  discovery:  [description, tags, token_size]
  lifecycle:  [date, birth, version]

types:
  adr:
    blocks: [identity, discovery, lifecycle]
    required: [id, status]
    optional: [superseded_by]

  analysis:
    blocks: [identity, discovery, lifecycle]
    required: [id, status]
    optional: [sources, produces]

  retrospective:
    blocks: [identity, discovery, lifecycle]
    required: [id, status, severity]
    optional: [produces]

  source:
    blocks: [identity, discovery]          # No lifecycle — ephemeral
    required: [id, model]
    optional: [extracted_into]

  tutorial:
    blocks: [identity, discovery, lifecycle]

  guide:
    blocks: [identity, discovery, lifecycle]

  script_instruction:
    blocks: [identity, discovery, lifecycle]

  package_spec:
    blocks: [identity, discovery, lifecycle]

  manifesto:
    blocks: [identity, discovery, lifecycle]

  policy:
    blocks: [identity, discovery, lifecycle]

  service:
    blocks: [identity]                     # Minimal
    optional: [role, version]              # version here is standalone,
                                           # not governed by lifecycle block rules

Key properties:

Rejected Ideas

References