From 90e4a368dc5199c7eb953bf5a4b09942c8498c0a Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 17 Sep 2026 22:36:33 +0200 Subject: [PATCH] ADR 0081: a decision nothing cites is not yet in the chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- 00-META/checks/cycle.py | 36 +++++++++++++ 00-META/process/00-overview.md | 6 ++- 00-META/process/05-constitution-sync.md | 3 ++ ...n-nothing-cites-is-not-yet-in-the-chain.md | 51 +++++++++++++++++++ 02-DECISIONS/README.md | 1 + 03-DESIGN/01-to-be/06-the-controller.md | 1 + 03-DESIGN/01-to-be/08-connectivity.md | 3 ++ 03-DESIGN/01-to-be/14-model-access.md | 2 + 03-DESIGN/01-to-be/17-raising-a-mesh.md | 1 + 03-DESIGN/01-to-be/18-building-a-module.md | 2 + 03-DESIGN/01-to-be/19-the-module-protocol.md | 1 + 03-DESIGN/01-to-be/20-writing-a-module.md | 1 + 12 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 02-DECISIONS/0081-a-decision-nothing-cites-is-not-yet-in-the-chain.md diff --git a/00-META/checks/cycle.py b/00-META/checks/cycle.py index d946c57..e808788 100644 --- a/00-META/checks/cycle.py +++ b/00-META/checks/cycle.py @@ -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)) diff --git a/00-META/process/00-overview.md b/00-META/process/00-overview.md index 1a50a69..50707c6 100644 --- a/00-META/process/00-overview.md +++ b/00-META/process/00-overview.md @@ -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. diff --git a/00-META/process/05-constitution-sync.md b/00-META/process/05-constitution-sync.md index acf67f9..084e651 100644 --- a/00-META/process/05-constitution-sync.md +++ b/00-META/process/05-constitution-sync.md @@ -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. diff --git a/02-DECISIONS/0081-a-decision-nothing-cites-is-not-yet-in-the-chain.md b/02-DECISIONS/0081-a-decision-nothing-cites-is-not-yet-in-the-chain.md new file mode 100644 index 0000000..6087d83 --- /dev/null +++ b/02-DECISIONS/0081-a-decision-nothing-cites-is-not-yet-in-the-chain.md @@ -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. diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 7b1b097..0b0ed56 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -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) diff --git a/03-DESIGN/01-to-be/06-the-controller.md b/03-DESIGN/01-to-be/06-the-controller.md index 28ea169..ea1e7cb 100644 --- a/03-DESIGN/01-to-be/06-the-controller.md +++ b/03-DESIGN/01-to-be/06-the-controller.md @@ -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 diff --git a/03-DESIGN/01-to-be/08-connectivity.md b/03-DESIGN/01-to-be/08-connectivity.md index 4b3c116..e5871da 100644 --- a/03-DESIGN/01-to-be/08-connectivity.md +++ b/03-DESIGN/01-to-be/08-connectivity.md @@ -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 diff --git a/03-DESIGN/01-to-be/14-model-access.md b/03-DESIGN/01-to-be/14-model-access.md index 5d0cf58..8dbd56b 100644 --- a/03-DESIGN/01-to-be/14-model-access.md +++ b/03-DESIGN/01-to-be/14-model-access.md @@ -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 diff --git a/03-DESIGN/01-to-be/17-raising-a-mesh.md b/03-DESIGN/01-to-be/17-raising-a-mesh.md index 0e02033..9d8f118 100644 --- a/03-DESIGN/01-to-be/17-raising-a-mesh.md +++ b/03-DESIGN/01-to-be/17-raising-a-mesh.md @@ -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 diff --git a/03-DESIGN/01-to-be/18-building-a-module.md b/03-DESIGN/01-to-be/18-building-a-module.md index 1bae04a..f5f3121 100644 --- a/03-DESIGN/01-to-be/18-building-a-module.md +++ b/03-DESIGN/01-to-be/18-building-a-module.md @@ -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 diff --git a/03-DESIGN/01-to-be/19-the-module-protocol.md b/03-DESIGN/01-to-be/19-the-module-protocol.md index 09b0731..633d451 100644 --- a/03-DESIGN/01-to-be/19-the-module-protocol.md +++ b/03-DESIGN/01-to-be/19-the-module-protocol.md @@ -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 diff --git a/03-DESIGN/01-to-be/20-writing-a-module.md b/03-DESIGN/01-to-be/20-writing-a-module.md index 55a17d2..d8af1b9 100644 --- a/03-DESIGN/01-to-be/20-writing-a-module.md +++ b/03-DESIGN/01-to-be/20-writing-a-module.md @@ -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