What the Validator Checks
Why docs are validated
The handbook is plain Markdown in a Git repo, so nothing structurally stops a doc from shipping with a missing owner_team, a broken heading ladder, or an unsafe HTML tag. The validator is what keeps the rules in Fill the Frontmatter, the heading conventions, and The Audio Layer from being merely advisory. It enforces only what is mechanically checkable — style, tone, and clarity stay the reviewer's job.
The rules themselves are explained, with their reasoning, in the docs they belong to. This page is the consolidated lookup: every check in one place, and what each one does to your save.
Errors versus warnings
Every check produces one of two severities, and the difference is the whole contract:
- Errors block the save. The doc is not written until you fix them. An invalid doc can never land.
- Warnings surface but pass through. They flag likely problems (a too-short description, a heading that smells like two topics) without stopping you. Use judgement.
Where validation runs
The same validator code runs in three places, so the rules are identical everywhere:
-
In the editor, as you write — issues appear live in the validation gutter. This is guidance, not a gate.
-
In the API worker, at save time — this is the real chokepoint. The worker is the sole writer to the content repo, so it re-runs the validator on every save and rejects the commit if there are any errors. When that happens the API returns:
{"error": "Document has 2 validation errors and was not saved","code": "VALIDATION_FAILED","issues": [ ]} -
In CI, as a backstop — when the validation rules themselves change, a workflow re-validates the entire content repo to catch any already-committed doc the new rules would now reject.
One exception: the audio narration rules are skipped at the save gate, because the .audio.txt companion is a separate file written separately. They run in the editor and in CI only.
The rule catalog
Frontmatter rules
The YAML metadata block at the top of every doc.
| Rule | Severity | What it flags |
|---|---|---|
frontmatter/missing | Error | The doc has no YAML frontmatter block at all. |
frontmatter/required-field | Error | A required field is absent: title, description, doc_type, owner_team, status, or created. |
frontmatter/enum | Error | doc_type or status holds a value outside its allowed set. |
frontmatter/owner-team-format | Error | owner_team is an email instead of a team slug. |
frontmatter/owner-team-unknown | Error | owner_team is not a slug in the team registry. |
frontmatter/date-format | Error | created or last_reviewed is not an ISO YYYY-MM-DD date. |
frontmatter/future-review | Error | last_reviewed is dated in the future. |
frontmatter/sidebar-label-forbidden | Error | sidebar_label is present; the sidebar must show the title. |
frontmatter/description-short | Warning | description is under 20 characters — too short to be useful in search. |
frontmatter/description-long | Warning | description is over 200 characters — keep it to one sentence. |
Heading rules
Heading levels and hygiene in the body.
| Rule | Severity | What it flags |
|---|---|---|
headings/no-body-h1 | Error | An H1 appears in the body; the page H1 is rendered from title. |
headings/min-h2 | Error | The page has no H2; section boundaries are required for audio and search. |
headings/no-level-skip | Error | A heading jumps a level, such as H2 straight to H4. |
headings/trailing-punctuation | Warning | A heading ends with punctuation. |
headings/no-formatting | Warning | A heading contains backtick, bold, or italic markup. |
headings/single-topic | Warning | A heading likely combines two topics (contains a comma or the word "and"). |
Body markdown rules
Code blocks, images, HTML, JSX, admonitions, and embeds.
| Rule | Severity | What it flags |
|---|---|---|
markdown/code-block-language | Error | A fenced code block has no language tag (use text for plain output). |
markdown/image-alt | Error | An image has empty alt text. |
markdown/directive-allowlist | Error | A ::: directive is not a known admonition (note, tip, info, warning, danger). |
markdown/jsx-allowlist | Error | A JSX component is not on the registered list (Tabs, TabItem, Details). |
markdown/forbidden-html | Error | A forbidden HTML tag such as script, an inline event handler, or a style attribute was used. |
markdown/video-src | Error | A video tag is missing src or points outside /api/videos/. |
markdown/iframe-src | Error | An iframe is missing src or its host is not YouTube or Loom. |
markdown/inline-html | Warning | An HTML tag outside the allow-list was used. |
Audio narration rules
Checked only when a companion .audio.txt exists — in the editor and CI, not at the save gate.
| Rule | Severity | What it flags |
|---|---|---|
audio/missing-marker | Error | A body heading has no matching ##/### marker in the narration file. |
audio/orphan-marker | Error | A narration marker matches no H2/H3 heading in the doc. |