Project-Template/docs/DOC_TRUST_MAP.md

12 KiB

Doc Trust Map — which document to believe

Status: Current
Owner: <who maintains this>
Last reviewed: <YYYY-MM-DD>
Governs: docs/**
Review trigger: Any doc added, deleted or moved; any change to which doc owns a subject
Fires on: added, deleted, moved

Write this file last, once the rest exists, and describe what is actually here rather than what a template said should be. Its whole value is being accurate about the others.

Work items are not in this tree

The sequence of work, the open defects, and the things blocking a release all live in this repository's issue tracker, not in docs/. Milestones are batches; issues are deliverables; severity labels are P0, P1, P2 and release-blocker.

This section exists to stop the next contributor starting a fresh markdown to-do list. A list of things to do in two places is two records that will disagree, and nothing will say which one is right.

Question Answer lives in
What are we building, and for whom? docs/planning/PROJECT_PLAN.md
What is the sequence of work? milestones in the tracker
What is left in this batch? open issues under that milestone
What is broken right now? issues labelled P0 / P1 / P2
What makes a release wrong? issues labelled release-blocker
What is the next action? the nextAction field on the project at privacyllc.dev — the newest entry in docs/history/DEVELOPMENT_LOG.md says what it was then
What is blocking us? the tracker, for the work; the blockers table at privacyllc.dev, for the stakeholder-facing version
What do I do when a piece of work is finished? docs/WORK_CYCLE.md
What watches this in production, and what do I do when it breaks? docs/OPERATIONS.md
What happened, and when? docs/history/DEVELOPMENT_LOG.md
Did QA pass, and what does the tester think? docs/qa/ClaudeReport.md
What did QA actually reach? docs/qa/ClaudeQACoverage.md
How is it built? docs/architecture/
How do I release, back up, or check this? scripts/
Which script do I run, and can it stop me? docs/TOOLS.md — the signpost; docs/architecture/README.md has the table
What runs before a commit? .githooks/
What stops an agent reporting work it did not verify? docs/architecture/scripts/verify-before-done.sh, and docs/TOOLS.md for why it is a second layer rather than the layer
How do I write a check that will actually catch something? docs/architecture/GUARDS.md

Next action and blockers are recorded at the end of every piece of work, not when somebody asks. docs/WORK_CYCLE.md holds that procedure and the reason each step is in it — including which of these channels updates itself and which two only ever change because a person changed them.

Folder layout

Folder Contents
docs/planning/ PROJECT_PLAN — the vision. Not the schedule; that is the tracker.
docs/qa/ ClaudeQAPlan (playbook), ClaudeQACoverage (what each pass reached), ClaudeReport (the verdict)
docs/architecture/ How it is built: module boundaries, data shapes, reference manuals
docs/design/ What it should feel like: product decisions, UI plans, copy
docs/security/ SECURITY — threat model, secret handling, what is out of scope
docs/history/ DEVELOPMENT_LOG (dated, append-only), HISTORY (the narrative), BATCH_LEDGER (archived, superseded by the tracker)
docs/data/ the three branding marks privacyllc.dev renders for this project
docs/ root this map; WORK_CYCLE — what to do when a piece of work is finished; TOOLS — where the scripts are and which can stop you; OPERATIONS — what watches production, (only for a deployed service)

README.md stays at the repository root; it is the landing page and moving it breaks that. Everything else lives under docs/.

Source-of-truth ladder

When two sources disagree, believe them in this order:

  1. Verified code behaviour — read the source, run the tests
  2. The issue tracker — for anything about state of work: what is open, what closed, when, and by which commit
  3. Specialist docs — authoritative for their own subject only
  4. History and logs — a record of then, not a description of now

A document that describes work as done is describing what was true when it was written. The tracker is describing now.

The status header

Every document in this tree opens with one, immediately after its H1 — and so do the documents at the repository root, which scripts/doc-triggers.py reads alongside this tree. The root is read one level deep only, so a vendored copy of this template cannot enrol its documents as governing the project holding it.

Status: Current | Draft | Superseded | Archived
Owner: <who maintains this>
Last reviewed: <YYYY-MM-DD>
Governs: <paths or subject this document is authoritative for>
Review trigger: <the change that should send someone back to this file>
Fires on: <optional — added, deleted, moved, changed>

Exactly those four status words. They are not a suggestion: a checker reads them, and a document whose status falls outside the list is reported as having an unknown one rather than being quietly accepted. It is also what separates a document from a template for one: project-readme-template.md carries Status: <Current | Draft | Superseded | Archived> and a Governs: describing the README of whichever project copies it, so doc-triggers.py does not treat it as governing anything here — and says so when it would otherwise have fired, rather than dropping it quietly. Historical is the word this convention used to have and deliberately dropped — Archived covers it, and a vocabulary a script enforces beats one described in prose, which drifts the moment somebody writes something adjacent to it. Draft earns its place by naming a real state the old list had no word for: a document being written, which is neither current nor superseded.

Review trigger is the line that matters. "Last reviewed" ages on its own and a reader cannot tell a current document from an abandoned one by looking at it; a trigger names the event that should bring somebody back, which is what stops a document becoming confidently wrong. It is also checked: the header is read from the first sixteen lines of the file, and one carrying Status without Review trigger is reported as incomplete — that combination looks finished and is not.

Governs is a comma-separated list, and an entry may explain itself after the glob with a spaced dash — docs/data/** — the assets privacyllc.dev renders for this project. scripts/doc-triggers.py cuts the entry there and reads the globs from the left of it. Use that form and no other. A gloss in parentheses, or after a colon, is not recognised: the whole entry becomes the glob, matches nothing, and the document is silently never fired — not reported as skipped either, because it still looks like a path. Three documents here sat in exactly that state from the first commit, docs/data/img/README.md among them, so editing the branding assets never once prompted the document that specifies their names and sizes. A trailing / on a glob means the directory and everything in it, which is how this file's .githooks/ entry reaches a project's installed hooks.

Fires on is optional, and only for the case where Governs: is much broader than the trigger. Governs: says where a document is authoritative; the trigger says which kinds of change to that place should bring somebody back, and scripts/doc-triggers.py can only read the first. This file is the extreme of the gap: it governs docs/**, the broadest glob here, while its trigger is one of the narrowest — any doc added, deleted or moved. Without the declaration it fires on every edit to every document forever, and is right to by the only rule the tool has. A prompt that always fires is one people stop reading, and it takes the true positives with it.

The words are added, deleted, moved and changed, comma-separated. Omit the line unless it is genuinely needed — absent means fire on every kind, which is what almost every document wants. It is deliberately declared rather than inferred from the trigger prose: the obvious heuristic — existence verbs and no change verb — was tried against the documents here and misread this one, whose trigger ends "any change to which doc owns a subject". That clause is about which document owns a subject, not about a file being edited, and nothing mechanical separates it from a trigger that does mean modification. A wrong guess is silent in the expensive direction, so an unreadable or missing declaration fires on everything and only a deliberate line ever quietens a document.

Two markers: does this apply, and who earned it

This tree is copied into every project, and some of what it carries will not apply to all of them. A static site has no refunds; a library has no uptime. Rather than shipping several templates, entries that are not universal say so inline, with two markers on different axes:

Marker Answers Example
(only where money moves), (only for a deployed service) does this project have to do this at all? a payments QA pass; the operations runbook
(precautionary) was this rule earned here, or borrowed from outside? rate limits; secrets in a shipped bundle

They are independent, and conflating them loses the distinction. A rule can be universally applicable and precautionary — rate limiting applies to everything here and has never yet bitten us. Another can be narrowly conditional and thoroughly proven elsewhere — money flowing backwards applies only to projects that take money, and is the single most common defect in the audits it came from. One word cannot say both, and the reader needs both: applicability tells them whether to keep the rule, provenance tells them whether to argue with it.

If it does not apply, delete it. That is docs/qa/ClaudeQAPlan.md's rule about passes — a pass that never applies is noise; a pass that is always skipped is a lie — and it holds for a checklist row and for a whole document just as well. A project that is not a deployed service deletes OPERATIONS.md rather than carrying its headings unanswered forever, because an empty runbook reads as one nobody wrote rather than one that never applied.

Deleting is safe here precisely because this file is the trust map: what a project keeps is what a project meant to keep.

Declaring a document deliberately absent

A repository may decide it will not keep one of these documents in git — QA and security notes held outside the repository on purpose, say. Say so here, one line per path, anywhere in this file:

Exempt: <the path> — <why, in a few words>

Write the real path only when you mean it. Outside a fenced block, an Exempt: line naming a real document is not an example — it is a declaration, and the checker will report that document as deliberately absent in every repository that copied the file. Keep illustrations fenced, and use placeholders anyway, as the form above does.

Fenced blocks are skipped, but only since 2026-08-12. Before that the parser read every line, which meant that documenting this feature used it: the example a writer naturally reaches for is a real required path, because an illustration with a fake one teaches nothing. A draft of this very section carried Exempt: docs/security/SECURITY.md inside a fence and would have marked that document deliberately absent everywhere it was copied. It is recorded here rather than quietly fixed because the shape recurs — a parser that cannot tell a description of a thing from the thing itself.

The declaration lives in this file because the decision does. Holding the list somewhere else would be two records of one fact, and the other one would never hear that this repository changed its mind.

docs/data/ and docs/data/img/ cannot be exempted. A declaration naming either is refused rather than honoured, because the Command Center renders what is in them — an exemption would produce a project card with no icon and nothing explaining why, which is the failure the check exists to catch.