Merge pull request 'ADR 0081: decisions are in the chain — no orphan records' (#49) from process/decisions-in-the-chain into main
This commit was merged in pull request #49.
This commit is contained in:
@@ -17,6 +17,10 @@ What is enforced:
|
|||||||
"nothing, the capability existed" is an answer).
|
"nothing, the capability existed" is an answer).
|
||||||
research a known `status:`; a `graduated` overview says what it `became:`, and every
|
research a known `status:`; a `graduated` overview says what it `became:`, and every
|
||||||
target it names exists.
|
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
|
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
|
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)):
|
if not os.path.exists(os.path.join(ROOT, target)):
|
||||||
bad(path, "became names %s, which does not exist" % 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 = (
|
checked = (
|
||||||
len(glob.glob(os.path.join(ROOT, "03-DESIGN", "0*", "*.md")))
|
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, "04-ISSUES", "*", "00-report.md")))
|
||||||
+ len(glob.glob(os.path.join(ROOT, "01-RESEARCH", "*", "00-overview.md")))
|
+ len(glob.glob(os.path.join(ROOT, "01-RESEARCH", "*", "00-overview.md")))
|
||||||
|
+ len(records)
|
||||||
)
|
)
|
||||||
if failures:
|
if failures:
|
||||||
print("cycle: %d document(s) break the development cycle:" % len(failures))
|
print("cycle: %d document(s) break the development cycle:" % len(failures))
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
# Process — overview
|
# 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
|
playbook: trigger, who runs it, steps, outputs. Engineers and agents follow the same
|
||||||
playbooks; agents must not act outside them.
|
playbooks; agents must not act outside them.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# Playbook 05 — Constitution sync
|
# 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
|
**Trigger.** [`how-we-build.md`](../how-we-build.md) changed a rule that the mesh enforces at
|
||||||
runtime.
|
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.
|
||||||
@@ -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)*
|
- **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)
|
- **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)
|
- **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 -->
|
<!-- index:end -->
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ decisions:
|
|||||||
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
|
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
|
||||||
- 02-DECISIONS/0008-a-context-owns-its-store.md
|
- 02-DECISIONS/0008-a-context-owns-its-store.md
|
||||||
- 02-DECISIONS/0019-how-this-repository-works.md
|
- 02-DECISIONS/0019-how-this-repository-works.md
|
||||||
|
- 02-DECISIONS/0046-a-module-configuration-is-its-assignments-not-its-manifest.md
|
||||||
---
|
---
|
||||||
|
|
||||||
# The controller
|
# The controller
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ decisions:
|
|||||||
- 02-DECISIONS/0007-connectivity.md
|
- 02-DECISIONS/0007-connectivity.md
|
||||||
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
|
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
|
||||||
- 02-DECISIONS/0066-public-routing-is-name-agnostic.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
|
# Connectivity
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ updated: 2026-09-05
|
|||||||
decisions:
|
decisions:
|
||||||
- 02-DECISIONS/0024-model-access-is-a-provision.md
|
- 02-DECISIONS/0024-model-access-is-a-provision.md
|
||||||
- 02-DECISIONS/0009-modules-and-the-graph.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
|
# 14 — Model access
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ decisions:
|
|||||||
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
|
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
|
||||||
- 02-DECISIONS/0005-the-node-host.md
|
- 02-DECISIONS/0005-the-node-host.md
|
||||||
- 02-DECISIONS/0010-delivery.md
|
- 02-DECISIONS/0010-delivery.md
|
||||||
|
- 02-DECISIONS/0036-bootstrap-ends-at-a-usable-mesh.md
|
||||||
---
|
---
|
||||||
|
|
||||||
# Raising a mesh
|
# Raising a mesh
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ decisions:
|
|||||||
- 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md
|
- 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md
|
||||||
- 02-DECISIONS/0069-a-module-is-a-repository-and-a-path.md
|
- 02-DECISIONS/0069-a-module-is-a-repository-and-a-path.md
|
||||||
- 02-DECISIONS/0009-modules-and-the-graph.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
|
# Building a module
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ decisions:
|
|||||||
- 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md
|
- 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/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/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
|
# The module protocol
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ decisions:
|
|||||||
- 02-DECISIONS/0074-the-wire-is-specified-not-the-types.md
|
- 02-DECISIONS/0074-the-wire-is-specified-not-the-types.md
|
||||||
- 02-DECISIONS/0040-what-a-module-is.md
|
- 02-DECISIONS/0040-what-a-module-is.md
|
||||||
- 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.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
|
# Writing a module
|
||||||
|
|||||||
Reference in New Issue
Block a user