# Frontmatter Derivation — the doc graph

Load when: creating a doc that declares an edge (a feature, package or system doc, a UX doc, a behavioral checklist), updating one whose scope moved, or running a baseline/reconcile pass over many docs.

**A docs tree is a graph, and every edge in it is declared in frontmatter.** Two kinds of edge exist: **doc → code** (`owns:`), and **doc → doc** (`verifies:`, `applies:`). The SessionStart hook reads them all and injects the generated indexes; there is no hand-maintained map of either kind.

## The one rule that keeps the graph honest

**An edge is declared once, at the consuming end. Every inverse is generated, never authored.**

A parent that hand-lists its children is a second thing to forget, and the moment the two directions can disagree nothing says which is right — you have doubled the staleness surface rather than halving it. So a checklist names the feature it verifies; the feature never lists its checklists. A feature names the architecture doc it obeys; the architecture doc never lists its consumers. Both inverses are derived on demand and injected, exactly as the ownership index already is.

The consuming end is also the end that **knows**. A checklist author knows what they are verifying; an architecture doc has no idea who will obey it next year.

## When to run

- **Baseline / reconcile** — for every owning doc whose code paths aren't yet declared in its `owns:` frontmatter (the `maintenance` agent (baseline mode) calls this)
- **New owning doc** — when creating one via the [Feature](../templates/feature.md), [Package](../templates/package.md) or [System](../templates/system.md) template, project its `owns:` globs
- **Doc update** — when the documented scope changes, update the doc's `owns:` globs

## Which genres own code

**Three do, and no others: `feature`, `package`, `system`.** They layer by glob specificity over the same tree, which is what lets a repo be documented at three altitudes without subtracting anything by hand:

| Genre | Claims | Wins because |
| --- | --- | --- |
| `package` (`docs/architecture/packages/`) | `packages/{name}/**` — the whole tree, one glob | Nothing; it is the **residual** owner and loses every narrower claim, keeping what no one else takes |
| `system` (`docs/architecture/systems/`) | the subtrees and exact files one machine is made of | Its globs are narrower than the package glob — specificity |
| `feature` (`docs/features/`) | one capability's or one surface's globs | Same — specificity over the package glob |

Every other genre owns no code and carries no `owns:`. That includes the cross-cutting doctrine in `docs/architecture/` (and `docs/architecture/common/`), which describes patterns rather than regions — a surface declares it is bound by one through `applies:`, and the doctrine never claims the surface back. Checklists, acceptance docs, ADs, operations procedures, research and brainstorm records own nothing.

## How to derive the frontmatter

An owning doc's ownership frontmatter is `owns:` (required), `module:` (optional — feature docs only; a package is already a bounded unit and a system spans contexts by definition), `keywords:` (required), and `status:` (required). An optional `last-reviewed:` date (stamped whenever the doc is substantively reviewed or updated) gives the `maintenance` agent a staleness signal to rank its audits by — it is tooling metadata, never narrated in the body. `type:` (required — the genre, enum = the template names) and `rev:` (generator-written generation stamp) are the same class: external tooling reads them, sessions **preserve them on edits** and never narrate or inject them. The `maintenance` agent stamps `type:` on docs that predate the key. `slug:` completes the set on generated docs: the identity key, minted once at creation and never re-derived from a display name — in the repo the filename is the slug, and the `maintenance` agent re-aligns the two when they diverge.

**1. `owns:`** (the path scope)

`owns:` names the **regions whose truth this doc holds** — the directories where a change must be reconciled against this document before it ships. It is an authority claim a human makes, not an inventory of what the feature touches.

**A file the doc merely references is not a file the doc owns.** Reaching across a boundary into a shared module is a fact about the code, and it belongs to whoever owns that module's region. Transcribing those reach edges as `owns:` entries is how an index stops being a routing table and becomes a hand-maintained import graph: rows then track the dependency graph rather than the feature count, and the shared upstream file ends up filed under whichever downstream feature happened to name it first — a consumer recorded as the authority.

Prefer one region glob over many file globs. Ten paths say nothing about which is the entry point; one region says where the doc's writ runs.

| The doc governs                              | Glob                                 |
| -------------------------------------------- | ------------------------------------ |
| A feature's own directory                     | `src/feature/**`                     |
| A feature split across two directories        | One glob each                        |
| A single entry-point file, and nothing beside it | The exact file path                  |
| A whole workspace package, residually          | `packages/{name}/**` — one glob, on the package doc |
| A machine spanning packages                    | One glob or exact path per subtree it is made of, on the system doc |
| A shared file several features import         | **Nothing — leave it unowned**       |

**An unowned shared file is the system working, not a gap.** A shared module's authority is the contract it exposes, not any one consumer, so a library-references file or a common utility legitimately has no owning doc. Claiming it would make the index bigger and the routing less certain at once.

Glob each pattern against the codebase. At `status: active`, drop any that match nothing; at `status: draft`, projected globs may match nothing yet — confirm them against real code when the doc flips to `active`.

**2. `module:`** (feature docs only)

The single bounded-context label this feature belongs to, or omit it for a cross-cutting feature that belongs to no single context. A module is a label, not a folder — features sharing a `module:` value are what ties that module to code (a filter over the generated ownership index). Package and system docs omit it entirely.

**3. `keywords:` & `status:`**

`keywords:` are the discovery terms topic→doc lookup rides. `status:` is `active`, `draft`, or `deprecated` — it steers the model toward live docs and away from draft/deprecated ones.

## The doc → doc edges

A prose link is not an edge. It states that one doc mentions another; it never states **why**, so nothing can be derived from it — not who consumes a doctrine, not what to re-read before an edit, not what falls stale when a surface moves. A tree can carry hundreds of prose links and still have no graph. These two keys carry the relationships that matter; everything else stays prose.

**`verifies:`** — on a behavioral checklist (`docs/tests/{feature}.md`), the feature or UX doc(s) whose promises it proves. One value is the norm; several when a checklist spans a capability that two docs decide. A checklist carrying no `verifies:` is an orphan by construction: nothing implicates it when its subject changes, so it rots silently and the rot is undetectable.

**`applies:`** — on a feature or UX doc, the architecture docs it obeys. Not "docs I read" and not "docs I mention" — **obligation**: a doctrine this surface is bound by, such that a change to the doctrine implicates this doc. The narrow reading is the load-bearing one; a key that collects everything consulted degrades into a bibliography and stops meaning anything. A feature bound by nothing cross-cutting omits the key.

Cross-cutting *contracts* remain diagnosed by the `boundary` skill from source, never persisted — `applies:` records which doctrine a surface answers to, not which code it touches.

## What is not frontmatter, deliberately

**`Pinned:` stays a section-level line inside a checklist**, naming the suite tests that guard that section. It is already typed and already resolved mechanically by the `maintenance` agent. Hoisting it into frontmatter would flatten per-section detail into a doc-level list and duplicate what the sections already say — the exact double-declaration the one rule above forbids.

That edge has a property the others lack, and it is worth naming: **a `Pinned:` reference terminates.** A test file exists or it does not, so the edge is falsifiable by a resolver sweep with no judgement. An `owns:` glob is only falsifiable at the dangling end — a glob that resolves says nothing about whether the doc still describes what the code does. Terminal edges get resolver sweeps; non-terminal edges need a human or an agent to read both ends. Do not build a checker that pretends otherwise.

## Rules

- One canonical doc per path. A doc's `owns:` globs must not overlap another doc's at equal specificity — the narrower glob wins; equal-specificity overlap is an ownership conflict to resolve. Overlap at **unequal** specificity is the layering working as designed: a package doc's `packages/{name}/**` is meant to be outranked, and what survives every narrower claim is exactly what that doc describes.
- Use glob patterns for directories, explicit paths for individual files. At `active`, never list paths that don't exist in the codebase; a `draft`'s projected paths are confirmed at `active`.
- When a doc covers a deep feature with sub-specs (e.g., `editor/`), each sub-spec owns its own globs — don't duplicate a path across siblings.
- For baseline/reconcile with many docs, use parallel sub-agents to read source and propose `owns:` globs.
- **Never author an inverse.** No doc lists its verifiers, its consumers, or its dependents — those are generated. A hand-written "consumed by" table is a defect to delete, not a convention to follow.
- **Every edge value is a repo-relative doc path** (`docs/architecture/common/security.md`), never a bare filename and never a display title — the resolver is a path lookup, and a title changes without the file moving.
- **A dangling edge is a block, not a warning.** `verifies:` or `applies:` naming a doc that does not exist is a typo or a deletion; either way the graph is lying. Resolve at authoring time.
