docs(groundskeeper): draw the plan-phase refusal, and gate the reference against the engine
Two mermaid diagrams in the component README where the prose already earns them: the include-to-token resolution chain, and the run lifecycle with the exit taxonomy and the human between plan and apply. First mermaid in this repo; GitLab renders it natively in blob and merge request views, and lint-templates.py extracts only yaml/yml fences so the new blocks are invisible to it.
The lifecycle diagram is a correction as much as an addition. The draft drew the fail-closed exit only off the apply path, which would have told a reader that a plan-mode run is immune. It is not: every keeper reads through Api.get_all, Api.rest and Api.graphql during build_plan(), none of those reads is guarded by read_only, and build_plan() runs before both the cap check and the mode branch, so a refusal there returns 2 without reaching either. The new edge says so. It does not reuse the existing fail-closed node, whose "journal the partial" label is true of an apply that stopped midway and false of a plan-phase refusal that never reached apply_plan().
tests/groundskeeper/validate-readme.py gates the keeper reference tables against the engine. The README documents 57 keys across 8 sections by hand and nothing compared them to the schema the engine validates, so a renamed or added manifest field drifts silently into wrong public documentation. The checker reads the engine's own constants rather than transcribing them, and it is section-scoped rather than substring-matched: a key added to one keeper often already appears in the README under another, so a bare "appears somewhere" check passes exactly the drift this is for.
Both directions are fatal. A README-only key is the worse one, because a reader copies it from the published table and load_manifest then refuses the whole manifest on an unattended schedule. lint-templates.py rule 9 already enforces the same bidirectional exactness for input tables.
Verified able to fail, five ways: a documented key removed, a documented key the engine rejects, a new engine key, a new engine key whose name already appears three times elsewhere in the README (the case a substring check passes), and the reference reformatted out of tables, which reports a vacuous parse rather than a pass it did not verify. Those five are now 13 assertions in run.sh, so the checker's redness is itself gated. Suite is 270 passing, up from 256. lint-templates 22/22 with 23 advisories, verified identical with these changes stashed.
Wired into the existing validate:groundskeeper job, whose rules:changes already covers both the engine and the README. No CHANGELOG entry: this gives a consumer pinning a tag nothing new, and the precedent is consistent, including the 241-line README expansion that added none.
Squash is off deliberately: the diagram work and the new gate are separate concerns and are worth keeping as separate commits.