Skip to content

Documentation Architecture Standard

This standard governs the shape of the docs corpus — which documents must exist for a subsystem and how they relate — as the complement to the Documentation Standard, which governs how any single page is written. It exists because a corpus can be full of individually excellent pages and still have no front door: the granular docs are all present, yet nothing ties a multi-service capability together, so a reader (human or agent) cannot get from “what is vulnerability management” to the pieces without already knowing they exist. This standard defines the documentation layers, and makes the overarching overview hub a required, enforced layer rather than an optional nicety. It is the information-architecture half of Prime Directive 9 (docs as code).

The problem this solves — good granular docs, no front door

Section titled “The problem this solves — good granular docs, no front door”

The pitlab corpus already governs the granular layers well: per-service pages auto-generate from the Alloy probe inventory (ADR-0059), alert runbooks are owned by their service (ADR-0064), and every decision gets an ADR (Prime Directive 9). What had no owner, no trigger, and no gate was the overarching layer — the page that ties a capability spanning several services into one navigable whole. The vulnerability-management pipeline (Trivy + Dependency-Track + vulnscan) ran for months as four separate docs with no hub; the gap was found by a human noticing, not by any check firing. This standard closes that structural hole.

Documentation layers — the Diátaxis-informed model

Section titled “Documentation layers — the Diátaxis-informed model”

pitlab docs are organised into five evergreen layers, adapted from the Diátaxis documentation framework (tutorial / how-to / reference / explanation), plus one Epic-scoped design-time layer (the design doc — see design docs capture intended state). Each layer answers a different question and carries a distinct kind tag; a subsystem is not fully documented until every layer that applies to it exists.

LayerAnswerskind tagExample
Overview hub“What is this capability, end to end, and where are its parts?”overviewvulnerability-management
Service page“What is this one service — endpoints, dashboard, deps, alerts?”reference (auto-generated)dtrack
Operational runbook“How do I run / fix / operate this?”runbookvulnscan
Decision record“Why is it built this way?”adrADR-0101
Standard / policy“What rule governs this class of thing?”standard / policythis page
Design doc (Epic-scoped, design-time)“What did we agree to build for this Epic, and did the build match?”designdesigns
reciprocal linksreciprocal linksOverview hub (kind:overview)explanation + navigationthe front doorService pages (reference)auto-generated per serviceOperational runbooks(how-to)ADRs (decision / why)Standards / policiesCode the source of truth
reciprocal linksreciprocal linksOverview hub (kind:overview)explanation + navigationthe front doorService pages (reference)auto-generated per serviceOperational runbooks(how-to)ADRs (decision / why)Standards / policiesCode the source of truth

An overview hub is mandatory for any capability spanning two or more services

Section titled “An overview hub is mandatory for any capability spanning two or more services”

The core rule: a capability, pipeline, or subsystem that spans two or more services or components owes exactly one kind: overview hub page. A single-service concern does not — its service page plus any runbook already form its front door. The test is cross-component: if understanding the thing requires assembling facts from two or more separate docs, those docs need a hub above them. Vulnerability management (three components), the incident lifecycle (capture + distillation + recall), backup/DR, and the observability stack are all cross-service and each owe a hub.

An overview-kind page is a navigation-and-explanation hub, not a runbook — keep operational detail in the runbooks it links. It must contain, at minimum:

  • A lead blurb (2–4 sentences: what the capability is, end to end, and why it exists), per the Documentation Standard.
  • Exactly one topology Mermaid diagram showing the components and how work flows between them — the diagram is the fastest way a reader grasps the whole.
  • A “Where to look” section linking every granular doc beneath it (service pages, runbooks, ADRs, and the governing code paths), each with self-describing anchor text.
  • Reciprocal corpus wiring — the hub links down to its parts with wikilinks, and at least the primary runbook links back up to the hub, so neither is an island (Documentation Standard cross-linking rule).

Landing pages carry overview too, but only named capability hubs are CI-shape-gated

Section titled “Landing pages carry overview too, but only named capability hubs are CI-shape-gated”

The overview kind spans two sub-types, and the CI gate enforces the shape only on the one it can identify unambiguously:

  • A named capability-hub content page (operations/vulnerability-management.md, operations/incident-lifecycle.md) is a front door over a multi-service capability. It carries the full shape above and the CI lint gates on it.
  • A landing / navigation page — any folder index.md (a MkDocs section or domain landing page), the tags.md index, or a generated catalog (services/index.md) — is overview because it is a navigation page, but an index.md serves double duty (folder navigation and possibly a hub), so CI cannot assume it is a capability hub. These are exempt from the CI shape check. The lint keys the exemption off the generated tag and the index.md / tags.md basename (it runs over the assembled build tree, where section landings are <section>/index.md).

A domain landing page (dns/index.md, claude-config/index.md) that is a genuine hub should still adopt the full shape — a topology diagram and reciprocal links make it a real front door, and several already do — but because it is an index.md, that expectation is enforced by the /wrapup judgment check rather than the CI lint. Put a capability hub in a named content page, not an index.md, when you want it CI-gated. The hub pattern already existed organically (the dns/ and claude-config/ domain hubs); what this standard adds is making it required for cross-service capabilities, shaped consistently, and enforced — not the concept itself.

Placement — hubs live in operations or a domain landing page

Section titled “Placement — hubs live in operations or a domain landing page”

An overview hub for an operational capability lives in operations/ (e.g. operations/vulnerability-management.md, operations/incident-lifecycle.md), sitting alongside the runbooks it indexes. A hub for a whole domain may instead be that domain’s landing page. Either way it carries kind: overview so it is classifiable, gate-checkable, and distinguishable from the runbooks around it — the overview kind is not decorative, it is the marker the gate keys on.

Do not name a hub after a build-time generated page. Some operations/ filenames are owned by doc generators, not by hand-authored content: generate_schedule_docs.py renders operations/maintenance.md and operations/backup.md (the kind: generated “Backup Architecture” schedule page), and the service-deps, Cloudflare, and docker-stacks generators own operations/services.md, operations/access.md, and docker/stacks.md. Because the build assembles committed docs over the generated tree, a committed hub at one of these paths would silently clobber the generated page (or vice-versa). Give the hub a distinct name and link the generated page from its “Where to look” section rather than colliding — this is why the backup/DR hub is operations/backup-dr.md, not operations/backup.md (ADR-0113).

Adding a new top-level docs section requires wiring it into the build assemble step

Section titled “Adding a new top-level docs section requires wiring it into the build assemble step”

A new top-level section under docs/ (a new nav tab such as requirements/, designs/, policy/) is not published by MkDocs alone. The docs pipeline builds from an assembled tree, and each hand-authored section is copied into that tree by an explicit per-section cp -r line in the assemble step of pitlab-docs/pipelines/docs.yml. Omit that line and the section renders into the nav but its files never enter the build tree, so every page under it 404s on the live site. Creating a section is therefore four coordinated edits, and a clean local mkdocs build passes with only the first three — the fourth only reds in CI:

#EditWhat breaks if omitted
1docs/<section>/ dir with an index.md (kind: overview section landing) and a .pages nav filethe section has no landing page or nav ordering
2Add <section> to docs/root.pagesthe section is absent from the top-level nav
3If it introduces a new doc genre, add the kind to mkdocs.yml extra.tag_vocabulary.kindthe pre-push tag-validation hook (hooks/wikilinks.py) fails the build on the unregistered kind
4Add mkdir -p "${DOCS_BUILD}/docs/<section>" + cp -r /home/pit/pitlab-docs/docs/<section>/. "${DOCS_BUILD}/docs/<section>/" to the assemble step in pipelines/docs.ymlvalidate_docs_published.py fails the build — the section is in nav but missing from the assembled tree, i.e. a silent live-site 404

Edit 4 is the easily-missed one and has caught the corpus twice — the designs section and the requirements section (ADR-0242) each shipped nav-wired but un-assembled and reded the docs pipeline. It fails loudly at build time rather than silently on the live site — that is exactly validate_docs_published.py’s job, turning a would-be 404 into a red run — so the residual cost is one wasted pipeline cycle, not a broken site. The section landing index.md is a nav scaffold and is exempt from the overview-hub shape lint (per Landing pages carry overview too).

The unit of a standard is the domain — decisions roll into domain standards, not new micro-standards

Section titled “The unit of a standard is the domain — decisions roll into domain standards, not new micro-standards”

Where the overview-hub rule above governs how a multi-service capability is fronted, this rule governs the granularity of the standards layer itself: the unit of a standard is the domain. A new decision — a new pipeline gate, a new exposure rule, a new retention tier — rolls into the standard for its domain and cites its ADR; it does not spawn a new micro-standard doc. This is the deeper fix the overview hub only fronted: the corpus grew ~38 standards authored per-decision, so compose-by-citation and an overview hub papered a front door over genuine fragmentation. The remedy is to minimise doc count — consolidate each over-fragmented cluster into one domain-unit standard whose internal rule sections are self-contained, subject-named, and keep every conformance checklist and ## Enforcement table verbatim, with the per-decision ADRs preserved as rationale-of-record and cited inline. The consolidation and the full 38→domain remap are recorded in ADR-0252.

The stopping rule is deterministic, so it prevents re-sprawl without re-litigation: a subsystem standard rolls into its domain; a cross-cutting meta standard (Documentation, this standard, Standard-Enforcement, Tagging, Token-Optimization, ADO Work-Item, Autonomous Remediation Authority) and a single-subsystem platform standard (Home Assistant, Desktop App Design) stay standalone and are cited by the domain docs, never absorbed. Genuine composition — a domain doc citing a meta standard — is legitimate; what is disallowed is a new per-decision micro-standard when a domain home already exists.

The registry ansible/scripts/doc_gen/standards_taxonomy.yml is the single source of truth for every standard’s domain assignment (or its meta/platform standalone status) and is the low-cardinality domain vocabulary (RULE 10). It is enforced at commit time by validate_standards_taxonomy.py: every *-standard.md under standards/ must be a registered domain doc, a member still pending its domain merge, or a whitelisted standalone — an un-domained new standard reds the docs pipeline. A new standard doc cannot appear without a commit, and CI re-inspects every commit, so per the Standard-Enforcement Standard two-layer rule the commit-time gate is the complete machine enforcement — no scheduled reconciler is owed (a standard cannot drift into existence out-of-band). The judgement “should this decision be a new standard or a section in an existing domain standard?” is not machine-inferable and is the paired review backstop.

Design docs capture intended state and are reconciled against as-built (the design-time layer)

Section titled “Design docs capture intended state and are reconciled against as-built (the design-time layer)”

A design doc (kind: design) is the agreed, pre-implementation design for one Epic’s worth of work, produced by the /grillme interview and persisted at docs/designs/<epic-id>-<slug>.md. It is distinct from the evergreen layers above on two axes: it is Epic-scoped (it describes a change, not a standing capability) and time-bound (it is deliberately updated from intended to as-built, not frozen as history). Its purpose is to give delivery a durable intent anchor and to close the design → ADRs → as-built traceability loop — the enterprise pattern of architecture conformance. Governed by ADR-0233 (the lifecycle) and this layer model.

A design doc carries the shape /grillme authors: a lead blurb, a Prior art section (required — see below), a decision tree (Mermaid), a decisions table (decision → choice → rationale → rejected alternatives), the NFR resolution, a compliance & control-mapping table (CPS 234/230, NIST CSF 2.0, ISO 27001, internal standards), the minimal delivery plan, and an initially-empty Deviations (as-delivered) section.

Its front-matter status moves through the delivery lifecycle:

  • proposed — stamped when /grillme writes the doc, before implementation begins.
  • as-delivered — stamped by /wrapup Check O at Epic close, which walks each design decision (implemented / deviated / dropped), requires an ADR for every deviation, and fills the Deviations table (designed → built → why → governing ADR). This is the design-level mirror of the per-ADR reconciliation (/wrapup Check G).

Relationship to the other layers. An overview hub is an evergreen front door over a standing capability; a design doc is a one-off record of a change’s intended design, and once its Epic ships the hub (if the capability warranted one) is where readers go, not the design doc. An ADR records a single decision and its rationale as immutable history; a design doc records the whole intended design for the Epic and is updated to as-built. Deviations discovered at close become (or cite) ADRs — so the two layers interlock rather than duplicate.

A design doc’s Prior art section is required — it records the two-store sweep that proved the design was not already made

Section titled “A design doc’s Prior art section is required — it records the two-store sweep that proved the design was not already made”

Every design doc carries a Prior art section, and it is not optional. It is the written record of /grillme’s Layer 0 sweep — the blocking gate that runs before the first design layer opens — and it must state four things: the WIQL terms swept across the ADO board in several mechanism-class vocabularies (the subsystem, the skill/command, the artifact, the problem, the symptom — never one keyword); the docs_search phrasings run against designs/, adr/ and standards/ (at least two differently-phrased queries, because the hybrid retriever’s dense half is phrasing-sensitive and one query is not a search); every candidate Epic and doc examined, with a one-line verdict on why it does or does not cover the goal; and, where the overlap was partial, which part was folded into existing work instead of being redesigned.

The section exists because this estate keeps two memory stores, and searching only one is half a search. The docs corpus records what the estate knows; the ADO board records what it is doing, has planned, and has already decided. On 2026-08-16 a full session re-derived analysis that had been grilled, approved and documented two days earlier as Epic #2436 (Blocked-action deferral for /go) and Epic #2441 (Agent permissions as governed config) — both tagged grilled, both carrying design docs — because the board had been queried exactly once, on a vocabulary that happened to miss. A correct-but-narrow negative was generalised into “no open Epic covers this”. That is empty-is-not-absence applied to a store nobody was treating as memory, and it fails in the most expensive direction: a missed doc costs a half-fact, a missed Epic costs duplicated design work and an approved decision reversed by accident.

An unrecorded sweep is indistinguishable from a skipped one, which is why the record — not merely the searching — is the requirement. A design doc with no Prior art section is a design whose novelty was assumed, and it is non-conforming. Where a sweep genuinely finds nothing, that is itself the finding and is written down as such, naming the vocabularies tried; an honest recorded gap is cheap, and an unstated one is unfalsifiable. Where an existing Epic already covers the goal, /grillme stops and resumes that Epic instead — so the Prior art section of a design doc that was written is also the evidence that this stop condition was correctly not met.

A design doc’s shape is not CI-gated — enforced at authoring and reconciled at close

Section titled “A design doc’s shape is not CI-gated — enforced at authoring and reconciled at close”

Unlike an overview hub, a design doc is not shape-checked by a CI lint. Its shape is enforced at authoring (/grillme produces it deterministically) and its intent-vs-built truth is reconciled at Check O; the pre-push tag-validation hook already gates the kind: design tag against the vocabulary. A dedicated structural validator (analogous to validate_overview_hubs.py) is a deliberate future option, not built — the same proportionality reasoning as the missing-hub registry above: there is no observed failure mode (malformed or missing design docs) to justify the extra pipeline step, and two authoring/close gates already cover it. If malformed design docs are observed in practice, a validate_design_docs.py shape lint is the recorded escalation path.

The gate — CI structural lint plus a wrap-up judgment check

Section titled “The gate — CI structural lint plus a wrap-up judgment check”

Conformance is enforced at two points, mirroring the Pillar-2 dashboard model (a CI check for structure, a wrap-up check for the judgment call):

  • CI structural lint (ansible/scripts/doc_gen/validate_overview_hubs.py, blocking in the docs pipeline) — every page tagged kind: overview must contain a Mermaid diagram, a “Where to look” (or equivalent links) section, and outbound wikilinks; a hub that is a bare page or an island fails the build. This enforces the shape of hubs that exist.
  • Wrap-up check (/wrapup) — the judgment “did this session stand up or materially change a capability spanning ≥2 services, and does it have a conforming hub?” is made at wrap-up, where the same completeness calls (dashboards, alerts, docs) already live. Detecting a missing hub is a human/agent judgment, not something CI can infer reliably — so it is prompted, not automated.

Detecting missing hubs by registry is a deliberate future option, not built yet

Section titled “Detecting missing hubs by registry is a deliberate future option, not built yet”

A fully automated “this capability has no hub” detector would need a declarative registry of capabilities (the same one-inventory-many-renderers pattern the Alloy probe inventory uses for dashboards, ADR-0059). That is heavier than the current gap warrants: the wrap-up judgment check plus the structural lint catch the realistic failure mode (a new capability shipped without its hub) at proportionate cost. A capability registry is recorded as the escalation path if hubs are still found missing despite the wrap-up check.

The one standing machine floor is validate_overview_hubs.py, the blocking structural lint in the docs pipeline (pitlab-docs pipeline, “Validate overview hubs” step) that runs on every docs deploy over the assembled build tree. It enforces the shape of a page tagged kind: overview — a topology Mermaid diagram, a “Where to look” links section (or ≥3 outbound links), and at least one outbound wikilink — and fails the build red if any is missing. Everything above the shape (whether a capability even has a hub, information-architecture and placement judgement, reciprocal back-links from the runbooks) is not CI-inferable and is decided by the /wrapup judgment check and /code-review docs dimension. All machine rows are deploy-time, so Dead-man is n/a throughout.

ObligationClassLayerMechanismDead-man
A capability spanning ≥2 services/components owes exactly one kind: overview hub page (detecting a missing hub)review/wrapup judgment check “did this session stand up or change a ≥2-service capability, and does it have a conforming hub?” — detecting absence is not CI-inferable (§ “Detecting missing hubs by registry is a deliberate future option”)n/a
Hub carries a lead blurb (2–4 sentences: what the capability is end to end, and why)review/code-review docs dimension against the Documentation Standard blurb rule — validate_overview_hubs.py does not check blurb textn/a
Hub contains exactly one topology Mermaid diagram of the components and work flowmachinedeployvalidate_overview_hubs.py (docs pipeline “Validate overview hubs” step, blocking) requires a ```mermaid block; the “exactly one” refinement is advisoryn/a
Hub has a “Where to look” section linking every granular doc beneath itmachinedeployvalidate_overview_hubs.py requires a “where to look” heading OR ≥3 outbound linksn/a
Hub links down to its parts with outbound wikilinks and is not an islandmachinedeployvalidate_overview_hubs.py requires ≥1 wikilink on the overview pagen/a
Reciprocal wiring — at least the primary runbook links back up to the hubreview/code-review docs dimension / /wrapup cross-linking check — the lint runs only on the overview page and cannot see the runbook’s uplinkn/a
A domain landing page (index.md) that is a genuine hub adopts the full hub shapereview/wrapup judgment check — index.md/tags.md/generated catalogs are exempt from the CI shape lint (_is_scaffold in validate_overview_hubs.py)n/a
A hub carries kind: overview and lives in operations/ or a domain landing pagereview/code-review docs dimension — the kind: overview tag is the marker the lint keys on, but placement and tagging are not machine-gatedn/a
Do not name a hub after a build-time generated page (operations/maintenance.md, operations/backup.md, operations/services.md, operations/access.md, docker/stacks.md)review/code-review docs dimension — a committed hub at a generator-owned path silently clobbers the generated page; not machine-gated (ADR-0113)n/a
A new top-level docs/ section is wired into the pipeline assemble step (present in the built tree, not just nav)machinedeployvalidate_docs_published.py (docs pipeline “Validate all docs are published” step, blocking) fails the build if a source doc is in nav but missing from the assembled tree — the § “Adding a new top-level docs section” edit 4 backstopn/a
An Epic delivered through /grillme owes exactly one kind: design doc at designs/<epic>-<slug>.mdreview/grillme persists it before delivery; /wrapup Check O confirms it exists and reconciles it at Epic close (ADR-0233)n/a
A design doc is stamped status: as-delivered with its Deviations section filled at Epic closereview/wrapup Check O — the intent-vs-built reconciliation is a semantic judgment, not CI-inferablen/a
A design doc’s page shape (blurb, Prior art, decision tree, control mapping, delivery plan, Deviations)review/grillme authors the shape deterministically and /wrapup Check O reconciles it at Epic close; a CI shape lint is a recorded future option, not built (§ “A design doc’s shape is not CI-gated”)n/a
A design doc carries a Prior art section recording the Layer 0 two-store sweep — the board WIQL terms across several mechanism-class vocabularies, ≥2 differently-phrased docs_search queries, and a per-hit verdictreview/grillme Layer 0 is a blocking gate: the sweep is recorded before any design layer opens and carried into the design doc, and /wrapup Check O reconciles the doc at Epic close. Whether a sweep was adequate (were the right vocabularies tried?) is a judgement no lint can make — a section can be present and vacuous (§ “A design doc’s Prior art section is required”)n/a
kind: design is a registered kind tagmachinepre-pushtag-validation hook (hooks/wikilinks.py) against mkdocs.yml extra.tag_vocabulary.kindn/a
Every *-standard.md is a registered domain doc, a pending member, or a whitelisted meta/platform standalone (the domain-unit rule) — an un-domained new standard reds the buildmachinedeployvalidate_standards_taxonomy.py (docs pipeline “Enforce standards taxonomy” step, blocking) resolves every standard under standards/ against standards_taxonomy.yml; a new standard cannot appear without a commit, so per the two-layer rule this commit-time check is complete — no scheduled reconciler (ADR-0252)n/a
A new decision rolls into its domain standard (citing its ADR) rather than spawning a new micro-standardreview/wrapup docs-as-code check + /code-review docs dimension — “new standard vs a section in an existing domain standard” is a judgement the taxonomy gate cannot make (it checks registration, not whether a doc should have been created)n/a