0083: Version markdown metadata and delete the transition
Status: accepted (2026-08-19) · Scope: product tier (docs CLI and
host documentation metadata)
Context
Section titled “Context”ADR 0013 made leading YAML frontmatter the only lifecycle-status source. The
rest of the frontmatter stayed loosely typed. Most host documents use a
date-only last_updated, date has more than one meaning, and freshness
commands still use Git edit age. Wiki pages, runbooks, and synchronized pages
have related fields but no shared timestamp semantics.
The host approved a v2 contract with real creation and update timestamps plus profile-specific fields for lifecycle state, runbook reviews, wiki provenance, and upstream sources. Those fields need executable maintenance rules. A larger header that depends on authors remembering every timestamp would become stale quickly.
Alternatives considered
Section titled “Alternatives considered”Add only created_at and rename last_updated
Section titled “Add only created_at and rename last_updated”This would fix names and precision but leave status changes, runbook reviews, and upstream timestamps mixed into one update field. Sweeps would still answer the wrong question after an unrelated edit.
Derive all timestamps from Git and store none
Section titled “Derive all timestamps from Git and store none”Git is useful for migration and repair, but a rendered wiki snapshot does not always have repository history. Git also cannot distinguish a spelling fix from a status transition or procedure review.
Keep permanent v1 and v2 readers
Section titled “Keep permanent v1 and v2 readers”This would repeat the ambiguity removed by ADR 0013. Every consumer would keep two field names and two meanings, and newly written v1 files could survive indefinitely.
Use a versioned schema with temporary migration support
Section titled “Use a versioned schema with temporary migration support”This gives the transition a measurable end. Validation and maintenance can land before the bulk rewrite, while the final gate proves that compatibility is safe to delete.
Decision
Section titled “Decision”Harnery defines schema: harnery-doc/v2 with quoted RFC 3339 UTC timestamps.
created_at is immutable and updated_at records substantive document
changes. Lifecycle documents also carry status_changed_at; runbooks carry
reviewed_at and review_due_at; resolved issues carry resolved_at; synced
pages keep upstream time in source_updated_at.
Document type selects a validation profile. The shared fields cover stable
owner ids, summaries, normalized tags, typed relationships, and list-based
access grants. Profile validators enforce their enums and cross-field rules.
Unknown keys fail unless they use the temporary x_ namespace.
The docs CLI owns three permanent operations:
metadata validaterequires v2 for every managed document and reports valid, invalid, legacy, and missing metadata.metadata syncupdates semantic timestamps and provides a staged check for hooks and CI.docs newcreates valid documents with current timestamps and required profile fields.
The dry-run-first migrator used Git history only for the bounded corpus rewrite. It was deleted at cutover along with the dual readers, legacy aliases, fallback freshness rules, strict-mode transition flag, and the older bold-header migrator. Migration code is not a permanent compatibility surface.
Consequences
Section titled “Consequences”Freshness checks can measure status age and review age directly instead of using the latest Git edit as a proxy. Wiki and synchronized content can carry timestamps outside a Git checkout without confusing document updates with source-system updates.
The migration will stop on missing owners, ambiguous summaries, conflicting dates, or uncertain source timestamp semantics. That creates some manual triage, but it avoids manufacturing history or ownership.
Compatibility removal is the last feature phase. The cutover proof includes a strict audit of every managed repository, a search for legacy field names and transition markers, and tests that reject v1 metadata through normal readers.
Result
Section titled “Result”The 2026-08-19 host cutover migrated 598 existing documents. The final strict audit covered 600 documents across the host and its tracked repositories. It reported 600 valid documents and zero invalid, legacy, missing, warning, or error results.
Harnery now rejects v1 metadata unconditionally. The --require-v2 flag, bold
header migrator, v1-to-v2 migrator, status aliases, and dual-read branches are
gone.
A review of that cutover found the discovery rules themselves were too narrow,
so the 600-document pass had measured an incomplete corpus. Audits carried a
declared type and a validation profile but no path rule, issue records outside
docs/issues/ matched nothing, and the rules allowed a single directory segment
before docs/, which hid every document in a repository nested deeper than
that. A file carrying the v2 marker with an unknown type was also dropped from
the audit rather than reported as invalid, so a typo silently exempted a
document. Discovery now anchors on docs/<kind>/ at any depth, covers audits,
and validates any v2-marked file regardless of its type. That brought 69 more
documents into scope; the strict audit covers 670 documents and all 670 are
valid. The committed JSON Schema and generated reference tables share the type
enum with the runtime validator, and check:docs-metadata-v2 catches generated
artifact drift.