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.

A-26015: Frontmatter Validator Architecture — Config-Code Divergence and Resolution


title: Frontmatter Validator Architecture — Config-Code Divergence and Resolution authors:


A-26015: Frontmatter Validator Architecture — Config-Code Divergence and Resolution

Problem Statement

The hub config (conf.json) defines a complete type system: 10 document types, block composition (identity, discovery, lifecycle), a field registry of 16 fields, and a tag vocabulary. No script enforces this type system at the hub level.

Two domain scripts — check_adr.py and check_evidence.py — independently implement partial frontmatter validation against their spoke configs. Six of ten types (tutorial, guide, policy, package_spec, script_instruction, service) have zero validation.

The gap between what the config declares and what the scripts enforce grows with every type added to the registry. This is a config-code divergence problem, not a code-sharing problem.

Duplication inventory

Both domain scripts independently implement:

The duplication exists because validation logic was written before the hub config existed. ADR-26042 and the JSON migration (ADR-26054) came later. The scripts never refactored to derive from the hub type system.

Approach Evaluation

Assumptions under test

AssumptionStatusFalsification evidence
A library module reduces duplicationPlausibleDuplication moves from copy-paste to import+configure — net code decreases, but each domain script still decides which hub rules to enforce
All 10 types need frontmatter validation nowUnsupportedOnly ADR and evidence have governed files today; other types may not have frontmatter yet
Domain scripts will be refactored to delegatePlausibleBoth scripts are actively maintained, but refactoring carries regression risk
Type discovery from directory path is reliableVerifiedEvidence config maps artifact_types.*.directory_name; ADR files live in architecture/adr/; pattern holds for all current types
Pre-commit hook ordering is manageableVerified.pre-commit-config.yaml already orders 10+ hooks; one more is not a scaling problem

SVA constraint audit (ADR-26037)

ConstraintA (Library)B (Standalone)C (Module + CLI)
C1 Automation-FirstPassPassPass
C2 Vendor PortabilityPassPassPass
C3 Git-Native TraceabilityPassPassPass
C4 Proportional ComplexityWarning — wrapping duplication adds indirection without eliminating two-script boundaryPassPass
C5 Reuse Before InventionViolation — domain scripts independently interpret hub rules; hub type system is not the driverPass — config drives validationPass — config drives validation
C6 Bounded ScalabilityViolation — adding a type requires a new domain script, O(n) effortPass — O(1) via config entryPass — O(1) via config entry

Calibration note on A/C5: Approach A does reuse hub config, but through domain-script interpretation rather than direct hub-driven validation. The violation is that the hub type system is not authoritative — each script decides independently which rules to enforce. This is a weaker violation than ignoring the hub entirely, but still means hub and scripts can diverge silently.

WRC assessment (Claude Opus 4.6 analysis)

Approach A — Library Module

ComponentScoreRationale
E (Empirical)0.85Shared Python modules are standard deduplication
A (Adoption)0.80Common in pre-commit ecosystems
P_raw0.75Works for 2 scripts, 6 types uncovered
SVA penalties−0.20C5 (hub not authoritative), C6 (O(n) scaling)
P_final0.55

WRC = (0.85 × 0.35) + (0.80 × 0.25) + (0.55 × 0.40) = 0.72 — PoC-only.

Approach B — Standalone Validator

ComponentScoreRationale
E (Empirical)0.90Config-driven validators (eslint, pylint) are industry standard
A (Adoption)0.85Pre-commit + JSON Schema widespread
P_raw0.88All 10 types covered, duplication eliminated, vadocs-ready
SVA penalties0.00No violations
P_final0.88

WRC = (0.90 × 0.35) + (0.85 × 0.25) + (0.88 × 0.40) = 0.88 — Production-adaptable. Held back by medium migration effort and unresolved type discovery.

Approach C — Config-Driven Module with CLI Entry Point (generated)

Combines B’s config-driven architecture with incremental migration:

  1. Importable module: validate_frontmatter(path, repo_root) → list[Error]

  2. CLI entry point for pre-commit: python -m tools.scripts.check_frontmatter

  3. Domain scripts import module, replacing frontmatter logic incrementally

  4. CLI covers all types immediately, including 6 with no domain script

ComponentScoreRationale
E (Empirical)0.90Same config-driven pattern as B
A (Adoption)0.85Same industry backing as B
P_raw0.92Incremental migration, full coverage, clean vadocs.core boundary
SVA penalties0.00No violations
P_final0.92

WRC = (0.90 × 0.35) + (0.85 × 0.25) + (0.92 × 0.40) = 0.90 — Production-ready.

Methodology comparison

MethodologyWRCProsConsBest For
A: Library module0.72Low effort, minimal disruption6 types uncovered, C5/C6 violationsQuick dedup in small projects
B: Standalone validator0.88Full coverage, clean separationBig-bang migration, type discovery upfrontGreenfield projects
C: Module + CLI ★0.90Incremental migration, full coverage, vadocs-readySlightly more API surfaceEvolving projects with extraction roadmap

Key Insights

Root cause: config-code divergence

The duplication between domain scripts is a symptom. The root cause is that validation logic predates the hub config. Approach A treats the symptom (shared functions). Approach C treats the cause (hub config becomes authoritative).

Type discovery via frontmatter

The validator parses frontmatter first (generic operation), reads options.type from the result, then applies type-specific rules. No directory mapping needed — files are self-describing. Missing or unrecognized options.type is itself a validation error, not a discovery problem.

Scope boundary: full frontmatter ownership (WRC 0.91)

A follow-up SVA analysis evaluated three scope options:

The scope boundary becomes:

ConcernOwner
Frontmatter: field presence, format, allowed valuescheck_frontmatter.py
Structural: sections, section order, conditional sectionsdomain scripts
Naming: filename patterns, ID formatdomain scripts
Index: generation, glossary, sectioningcheck_adr.py
Auto-fix: status_correctionscheck_adr.py (mutates file, not validation)

Incremental migration eliminates the B risk

Approach B’s weakness is big-bang migration. Approach C derisks this: domain scripts delegate to the shared module one at a time. At any point, both old and new validation coexist. The CLI entry point provides immediate coverage for uncovered types regardless of migration progress.

vadocs extraction alignment

Approach C’s module boundary (validate_frontmatter() + resolve_type() + load_config_chain()) maps directly to vadocs.core in Phase 1.3. The CLI entry point becomes vadocs docs check-frontmatter. Domain scripts become thin wrappers calling vadocs.docs.check_adr(). The package boundary is designed from day one.

Pitfalls

  1. Type discovery ambiguity — files outside recognized directories with no type field must have an explicit fallback (skip with warning, not silent).

  2. Block inheritance — validator must enforce strict additive semantics: spokes can add fields to blocks, never remove.

  3. Pre-commit overlap during migration — temporary double-validation between check-frontmatter and domain scripts must not produce conflicting errors.

  4. conf.json as policy file — modifications to types or required change what passes validation; protect with review policy.

References