ADR 0081: a decision nothing cites is not yet in the chain

Decisions were the one link the cycle checks skipped, and measuring found 19 of 70 records
orphaned — the credential flow and the module-runtime cluster among them, which is how a
stale premise about a settled decision survived in working memory. cycle.py now refuses an
accepted record nothing cites; the 19 got true homes (design frontmatter, the playbook that
implements 0021, META for the process records). The overview names the practice: spec-driven
development with provenance.

https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
2026-09-17 22:36:33 +02:00
parent 1162f7fe25
commit 90e4a368dc
12 changed files with 107 additions and 1 deletions
+36
View File
@@ -17,6 +17,10 @@ What is enforced:
"nothing, the capability existed" is an answer).
research a known `status:`; a `graduated` overview says what it `became:`, and every
target it names exists.
decisions every accepted record is REACHABLE from the cycle: cited by a design doc's
frontmatter, a research overview, an issue report, a 00-META document, or another
record's extends/supersedes chain. A decision nothing points at is one nobody will
find by following pointers -- which is how records go stale in people's heads.
Deliberately NOT enforced: `resolved` issues may leave `located-in` empty (a symptom that
turned out not to be a defect has no owner), and as-is docs need no decisions (they
@@ -135,10 +139,42 @@ def main():
if not os.path.exists(os.path.join(ROOT, target)):
bad(path, "became names %s, which does not exist" % target)
# ---- decisions -------------------------------------------------------------------
records = {}
for path in sorted(glob.glob(os.path.join(ROOT, "02-DECISIONS", "[0-9]*.md"))):
front = frontmatter(path)
records[os.path.basename(path)] = (path, (front or {}).get("status"))
cited = set()
sources = (glob.glob(os.path.join(ROOT, "03-DESIGN", "*", "*.md"))
+ glob.glob(os.path.join(ROOT, "01-RESEARCH", "*", "00-overview.md"))
+ glob.glob(os.path.join(ROOT, "04-ISSUES", "*", "00-report.md"))
+ glob.glob(os.path.join(ROOT, "00-META", "**", "*.md"), recursive=True))
for path in sources:
text = open(path, encoding="utf-8").read()
if os.sep + "03-DESIGN" + os.sep in path:
# A design doc's governing citations live in frontmatter; a prose mention is
# commentary, not a home.
m = re.match(r"^---\n(.*?)\n---", text, re.S)
text = m.group(1) if m else ""
for m in re.finditer(r"([0-9]{4}-[^\s\)\],#]+\.md)", text):
cited.add(os.path.basename(m.group(1)))
for name in records:
front = frontmatter(records[name][0]) or {}
for key in ("extends", "supersedes", "superseded-by"):
v = front.get(key)
if isinstance(v, str) and v:
cited.add(os.path.basename(v))
for name, (path, status) in records.items():
if status == "accepted" and name not in cited:
bad(path, "an accepted decision nothing in the cycle cites -- give it a home in a "
"design doc's decisions:, a research became:, an issue, or 00-META")
checked = (
len(glob.glob(os.path.join(ROOT, "03-DESIGN", "0*", "*.md")))
+ len(glob.glob(os.path.join(ROOT, "04-ISSUES", "*", "00-report.md")))
+ len(glob.glob(os.path.join(ROOT, "01-RESEARCH", "*", "00-overview.md")))
+ len(records)
)
if failures:
print("cycle: %d document(s) break the development cycle:" % len(failures))
+5 -1
View File
@@ -1,6 +1,10 @@
# Process — overview
How work moves through HQ, and who may do what. Every other document in this folder is a
How work moves through HQ, and who may do what. In industry terms this is **spec-driven
development, with provenance**: the decision is the why, the design doc is the spec, `code:`
names the implementation, and the lab beds are the conformance tests — and unlike the common
form, the chain itself is checked ([ADR 0080](../../02-DECISIONS/0080-the-development-cycle-is-checked.md),
[0081](../../02-DECISIONS/0081-a-decision-nothing-cites-is-not-yet-in-the-chain.md)). Every other document in this folder is a
playbook: trigger, who runs it, steps, outputs. Engineers and agents follow the same
playbooks; agents must not act outside them.
+3
View File
@@ -1,5 +1,8 @@
# Playbook 05 — Constitution sync
Implements [ADR 0021](../../02-DECISIONS/0021-hq-is-the-source-of-the-constitution.md): HQ is
the source of the constitution, and the knowledge-base page is derived, never edited.
**Trigger.** [`how-we-build.md`](../how-we-build.md) changed a rule that the mesh enforces at
runtime.
@@ -0,0 +1,51 @@
---
topic: how we work
status: accepted
date: 2026-09-17
deciders: jochen
reconstructed: false
extends: 0080-the-development-cycle-is-checked.md
---
# 81. A decision nothing cites is not yet in the chain
## Context
[ADR 0080](0080-the-development-cycle-is-checked.md) made the development cycle checked — but its
checks covered designs, issues and research, not the decisions themselves. Measuring showed why
that matters: 19 of 70 records were cited by nothing — no design doc's `decisions:`, no research
`became:`, no issue, no other record. Among them sat load-bearing decisions (the credential flow,
the module-runtime cluster), and the cost had already been paid once in practice: a stale premise
about an orphaned decision survived in working memory precisely because no pointer led to the
record that had settled it.
## Decision
Every **accepted** decision must be reachable from the cycle: cited by a design doc's frontmatter
(`decisions:` — a governing citation, not a prose mention), a research overview, an issue report,
a `00-META` document, or another record's `extends`/`supersedes` chain.
[`cycle.py`](../00-META/checks/cycle.py) refuses orphans. Proposed records are exempt — a record
under consideration has no home yet — and superseded records are reachable through their
supersession chain by construction.
The 19 orphans were given true homes in the same change: the module-runtime cluster
(0044–0049, 0053–0055) into the connectivity, controller, protocol, writing and model-access
designs; the build decisions (0072, 0076) into the building design; 0021 into the playbook that
implements it; the process records were already reachable once `00-META` counted as a source.
Taken together with 0080, the practice has a name the industry will recognise:
**spec-driven development, with provenance** — a decision is the *why*, the design doc is the
spec, `code:` names the implementation, and the lab beds are the conformance tests. What the
common form leaves implicit, the cycle makes checked: the spec itself must trace to a decision,
and the decision must be findable from the work it governs.
## Consequences
Following pointers now reaches every accepted decision, so a cleared session (or a person) can
trust the frontmatter graph as the whole map. The check is reachability, not truth: a citation
placed wrongly still lies, and reading remains the job.
## References
- [ADR 0080](0080-the-development-cycle-is-checked.md) — the cycle this completes.
- [`00-META/process/00-overview.md`](../00-META/process/00-overview.md) — the flow.
+1
View File
@@ -166,5 +166,6 @@ python3 00-META/checks/index.py fail if stale
- **0032** — [The local account owns the mesh; a surface delegates to a module](0032-the-local-account-owns-the-mesh.md) *(superseded)*
- **0034** — [The local account owns the mesh, and a web application's login is not that](0034-the-local-account-owns-the-mesh.md)
- **0080** — [The development cycle is checked, not trusted](0080-the-development-cycle-is-checked.md)
- **0081** — [A decision nothing cites is not yet in the chain](0081-a-decision-nothing-cites-is-not-yet-in-the-chain.md)
<!-- index:end -->
+1
View File
@@ -10,6 +10,7 @@ decisions:
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
- 02-DECISIONS/0008-a-context-owns-its-store.md
- 02-DECISIONS/0019-how-this-repository-works.md
- 02-DECISIONS/0046-a-module-configuration-is-its-assignments-not-its-manifest.md
---
# The controller
+3
View File
@@ -17,6 +17,9 @@ decisions:
- 02-DECISIONS/0007-connectivity.md
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
- 02-DECISIONS/0066-public-routing-is-name-agnostic.md
- 02-DECISIONS/0029-a-network-is-a-shape-because-an-action-cannot-be-undone.md
- 02-DECISIONS/0044-a-public-name-is-provisioned-like-any-capability.md
- 02-DECISIONS/0045-a-machine-firewall-is-the-sum-of-what-it-listens-on.md
---
# Connectivity
+2
View File
@@ -8,6 +8,8 @@ updated: 2026-09-05
decisions:
- 02-DECISIONS/0024-model-access-is-a-provision.md
- 02-DECISIONS/0009-modules-and-the-graph.md
- 02-DECISIONS/0054-model-usage-is-recorded-at-two-grains.md
- 02-DECISIONS/0055-model-access-is-answered-by-a-licence-or-a-node.md
---
# 14 — Model access
+1
View File
@@ -11,6 +11,7 @@ decisions:
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
- 02-DECISIONS/0005-the-node-host.md
- 02-DECISIONS/0010-delivery.md
- 02-DECISIONS/0036-bootstrap-ends-at-a-usable-mesh.md
---
# Raising a mesh
@@ -11,6 +11,8 @@ decisions:
- 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md
- 02-DECISIONS/0069-a-module-is-a-repository-and-a-path.md
- 02-DECISIONS/0009-modules-and-the-graph.md
- 02-DECISIONS/0072-two-graphs-and-the-build-chain.md
- 02-DECISIONS/0076-the-sdk-is-a-published-package.md
---
# Building a module
@@ -11,6 +11,7 @@ decisions:
- 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md
- 02-DECISIONS/0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md
- 02-DECISIONS/0042-the-shape-of-an-event-on-the-wire.md
- 02-DECISIONS/0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md
---
# The module protocol
@@ -10,6 +10,7 @@ decisions:
- 02-DECISIONS/0074-the-wire-is-specified-not-the-types.md
- 02-DECISIONS/0040-what-a-module-is.md
- 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md
- 02-DECISIONS/0053-a-step-that-runs-on-a-schedule.md
---
# Writing a module