---
type: Concept
title: Repo-wide frontmatter format
description: The PMOS house convention — every tracked .md carries OKF-format YAML frontmatter with a required type + title + timestamp, so agents and the harness classify and route a file by reading it, not by guessing from its path.
tags: [frontmatter, okf, format, harness, aci, convention]
timestamp: 2026-07-24
---

# Repo-wide frontmatter format

**Situating context:** authored for the `repo-wide-md-frontmatter` initiative (proposed D63, amends
D03). The PM's framing — *"OKF is the format, not the folder"* — decouples the OKF **format** (which
this makes universal) from the `okf/` **bundle** (curated knowledge, unchanged) and from a file's
**location** (D04, unchanged). This concept is the reference for what the frontmatter lint enforces.

## The rule

Every **tracked** markdown file MUST open with a YAML frontmatter block declaring three **required**
keys:

| key | meaning | rule |
|---|---|---|
| `type` | what the file *is* | one of the registry values (below); enforced |
| `title` | human-readable name | non-empty string |
| `timestamp` | last-modified | valid ISO 8601 date (`YYYY-MM-DD`) or datetime |

Recommended-but-optional (OKF): `description`, `tags`, `resource`. Any pre-existing producer keys
(`initiative_id`, `eval_type`, `parent_KR`, `pass_threshold`, …) are preserved as optional extension
keys — never stripped.

```yaml
---
type: Concept
title: Repo-wide frontmatter format
timestamp: 2026-07-24
---
```

## Relationship to OKF and D03

OKF requires only `type`. PMOS layers `title` + `timestamp` as **required house keys**, applied
**repo-wide** (not just the `okf/` bundle). This is stricter than OKF's minimum but still
OKF-**conformant** — the spec is permissive about extra keys. It is the deliberate amendment D03
carries (type-only → richer set, repo-wide). The guardrail against required-key creep is this
registry: the enforced list of `type` values is closed and versioned.

## Reserved files (exempt)

OKF's reserved filenames carry **no** frontmatter and are skipped by both the migration and the lint:

- `index.md` — a directory listing for progressive disclosure
- `log.md` — a chronological update history

## The `type` registry

The enforced list lives in [`scripts/frontmatter_registry.txt`](/scripts/frontmatter_registry.txt) —
the single source `frontmatter_lint.py` reads. A `type:` value not in it fails CI (this catches typos).

- **OKF core:** `Concept`, `ADR`, `Template`, `Playbook`, `Reference`
- **Planning:** `Initiative`, `PRD`, `Story`, `Rubric`, `OKR`, `Radar`, `Checklist`, `Calibration`, `Research`, `Design`, `Prototype`
  - `Initiative` is the repo-native initiative record in [`planning/initiatives/`](/planning/initiatives/), read by the
    [Initiative Gate](/.github/scripts/initiative-gate.sh). Its D56 **work-type lane** rides a separate `lane:` key —
    not `type:` — so the lane vocabulary and this registry never collide.
  - `Story` is a **user-story document** — the decomposition of a spec into the units of work handed
    to whoever builds them. It is the one type in this registry that is also **checked positionally**
    (see below), and it may carry an optional `jira:` / `confluence:` / `google_doc:` key naming the
    tracker record it corresponds to. That key is a *declaration* the workspace displays, never a
    lookup: PMOS holds no credential and makes no outbound request.
- **Root governance:** `Decision`, `Principle`, `Debt`, `Discovery`, `Guide`

### When the path decides the type

Most types are the author's to choose. A few document kinds are identifiable from the path alone, and
for those `type ∈ registry` is not enough — the gate also checks the type is the RIGHT one, because a
file wearing a plausible wrong type passes the membership test forever.

**User stories** are the first such kind. A tracked `.md` whose filename ends `-user-stories.md`,
`-user-story.md` or `-stories.md`, **or** whose immediate parent directory is `stories/` (or
`user-stories/`), must declare `type: Story`. These are exactly the shapes the workspace's library
index calls a story (`ROLE_SUFFIXES` / `ROLE_BY_DIR` in `workspace/server.js`) — the board and the
gate are held to one definition of what a story is, rather than two that can drift.

The boundary is deliberately narrow: `planning/stories/archive/notes.md` is a note filed beside
stories, not a story, and `planning/research/story-mapping.md` is research. Both directions are
asserted in [`scripts/tests/frontmatter-story.sh`](/scripts/tests/frontmatter-story.sh).

**Retroactive repair (PMOS-self — the kit does not carry this tool).** Story files written before
`Story` existed declare something else — this repo's own test fixture declared `type: PRD`, because
nothing else was available. `python3 scripts/frontmatter_lint.py --fix` corrects them, and
`--root <dir>` points it at another repository so a product repo's stories can be repaired too. It
is bounded on purpose: story files only, the `type:` key only, and it never invents a `title` or a
`timestamp` — a story file missing those is reported rather than guessed at.

The linter and this registry are **not** in the create-pmos payload, and that is a decision rather
than an omission: the registry is *PMOS's* vocabulary, and enforcing it inside an adopter's
repository would fail files written to their own conventions. Whose vocabulary governs an adopter's
repo is its own question, and it deserves its own initiative. What adopters *do* get is the reading
half — the workspace draws a story on the artifacts row and shows the destination its frontmatter
declares, whatever `type:` the file carries.

### Extending the vocabulary

Add **one line** to `scripts/frontmatter_registry.txt` and a matching row above. A new `type` is a
visible, reviewable diff — never an ad-hoc value invented in a single file.

## Enforcement

- [`scripts/frontmatter_lint.py`](/scripts/frontmatter_lint.py) — deterministic, no secrets; asserts
  the three required keys (and `type` ∈ registry) on every non-reserved tracked `.md`. Run in
  `.github/workflows/frontmatter-ci.yml`.
- [`scripts/backfill_frontmatter.py`](/scripts/backfill_frontmatter.py) — the one-time migration:
  prepend-only, idempotent; reuses a file's already-declared `type`/`date` when present, else infers
  from path (`type`), the first H1 (`title`), and the last git-commit date (`timestamp`).
