Reconcile: adopt initialization's consolidated HQ as canonical, re-home this session's new work #24
@@ -1,7 +1,9 @@
|
|||||||
# Checks
|
# Checks
|
||||||
|
|
||||||
```
|
```
|
||||||
python3 00-META/checks/records.py
|
python3 00-META/checks/records.py structure: links, citations, supersession, topics
|
||||||
|
python3 00-META/checks/index.py the reading order in 02-DECISIONS/README.md is current
|
||||||
|
python3 00-META/checks/index.py --write regenerate it
|
||||||
```
|
```
|
||||||
|
|
||||||
Non-zero exit on any problem, so it can be a gate rather than a report.
|
Non-zero exit on any problem, so it can be a gate rather than a report.
|
||||||
@@ -22,6 +24,8 @@ indistinguishable from one that cannot.
|
|||||||
| `live-citation` | a governing document citing a **superseded** record names its replacement in the same paragraph | `01-to-be/README.md` citing ADR 0022 as live guidance |
|
| `live-citation` | a governing document citing a **superseded** record names its replacement in the same paragraph | `01-to-be/README.md` citing ADR 0022 as live guidance |
|
||||||
| `supersession` | if A says it was superseded by B, B says it supersedes A | ADR 0012 never declared that it superseded 0011 |
|
| `supersession` | if A says it was superseded by B, B says it supersedes A | ADR 0012 never declared that it superseded 0011 |
|
||||||
| `numbering` | the number in the filename is the number in the heading | — |
|
| `numbering` | the number in the filename is the number in the heading | — |
|
||||||
|
| `topics` | every record names a topic the index knows | — |
|
||||||
|
| *(index.py)* | the written reading order matches what the records say | — |
|
||||||
|
|
||||||
## What is deliberately not checked
|
## What is deliberately not checked
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate the decision index, and check the written one still matches.
|
||||||
|
|
||||||
|
A number identifies a record and never changes, so the folder listing is creation order rather
|
||||||
|
than reading order. The index is what carries the path — and it is written rather than only
|
||||||
|
generated on demand, because a reader on a forge sees the folder and not a command.
|
||||||
|
|
||||||
|
The objection to a written index is that it drifts. That objection is answered by checking it
|
||||||
|
rather than by refusing to write one, which is `how-we-build` §5: a rule states how it is
|
||||||
|
checked.
|
||||||
|
|
||||||
|
python3 00-META/checks/index.py --write regenerate it
|
||||||
|
python3 00-META/checks/index.py fail if it is stale
|
||||||
|
"""
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
README = "02-DECISIONS/README.md"
|
||||||
|
START = "<!-- index:start -->"
|
||||||
|
END = "<!-- index:end -->"
|
||||||
|
|
||||||
|
# The reading order. Topics a record may belong to, in the order somebody would learn the system.
|
||||||
|
TOPICS = [
|
||||||
|
("the mesh", "What the mesh is"),
|
||||||
|
("the tiers", "Its tiers, from the bottom up"),
|
||||||
|
("what runs on it", "What runs on them, and how it gets there"),
|
||||||
|
("building it", "How it is built"),
|
||||||
|
("checking it", "How it is checked"),
|
||||||
|
("how we work", "How we work"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def field(text, name):
|
||||||
|
m = re.search(r'^%s:\s*(.+)$' % name, text, re.M)
|
||||||
|
return m.group(1).strip() if m else None
|
||||||
|
|
||||||
|
|
||||||
|
def records():
|
||||||
|
out = []
|
||||||
|
for path in sorted(glob.glob('02-DECISIONS/0*.md')):
|
||||||
|
text = io.open(path, encoding='utf-8').read()
|
||||||
|
heading = re.search(r'^# \d+\.\s*(.+)$', text, re.M)
|
||||||
|
out.append({
|
||||||
|
"file": os.path.basename(path),
|
||||||
|
"number": os.path.basename(path)[:4],
|
||||||
|
"title": heading.group(1).strip() if heading else "(no heading)",
|
||||||
|
"topic": field(text, "topic"),
|
||||||
|
"status": field(text, "status"),
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def render(rs):
|
||||||
|
known = {t for t, _ in TOPICS}
|
||||||
|
lines = [START, ""]
|
||||||
|
for topic, label in TOPICS:
|
||||||
|
rows = [r for r in rs if r["topic"] == topic]
|
||||||
|
if not rows:
|
||||||
|
continue
|
||||||
|
lines.append("### %s" % label)
|
||||||
|
lines.append("")
|
||||||
|
for r in rows:
|
||||||
|
mark = "" if r["status"] == "accepted" else " *(%s)*" % r["status"]
|
||||||
|
lines.append("- **%s** — [%s](%s)%s" % (r["number"], r["title"], r["file"], mark))
|
||||||
|
lines.append("")
|
||||||
|
stray = [r for r in rs if r["topic"] not in known]
|
||||||
|
if stray:
|
||||||
|
lines.append("### Unfiled")
|
||||||
|
lines.append("")
|
||||||
|
for r in stray:
|
||||||
|
lines.append("- **%s** — [%s](%s) — `topic:` is %r, which is not one of %s" % (
|
||||||
|
r["number"], r["title"], r["file"], r["topic"], ", ".join(sorted(known))))
|
||||||
|
lines.append("")
|
||||||
|
lines.append(END)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
text = io.open(README, encoding='utf-8').read()
|
||||||
|
wanted = render(records())
|
||||||
|
|
||||||
|
if START not in text or END not in text:
|
||||||
|
print("index: %s has no index markers (%s / %s)" % (README, START, END))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
current = text[text.index(START):text.index(END) + len(END)]
|
||||||
|
if "--write" in sys.argv:
|
||||||
|
if current == wanted:
|
||||||
|
print("index: already current")
|
||||||
|
return 0
|
||||||
|
io.open(README, 'w', encoding='utf-8').write(text.replace(current, wanted, 1))
|
||||||
|
print("index: written")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if current != wanted:
|
||||||
|
print("index: %s is stale. Regenerate it:\n"
|
||||||
|
" python3 00-META/checks/index.py --write" % README)
|
||||||
|
return 1
|
||||||
|
print("index: current")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -251,6 +251,25 @@ def check_supersession_symmetry(failures, records):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_topics(failures, records):
|
||||||
|
"""Every record names a topic the index knows.
|
||||||
|
|
||||||
|
The topic is what puts a record in the reading order, so a record without one — or with one
|
||||||
|
nobody defined — disappears from the index rather than appearing in the wrong place. That is
|
||||||
|
the quiet failure, so it is the one checked.
|
||||||
|
"""
|
||||||
|
known = {"the mesh", "the tiers", "what runs on it", "building it", "checking it",
|
||||||
|
"how we work"}
|
||||||
|
for number, record in sorted(records.items()):
|
||||||
|
topic = record["front"].get("topic")
|
||||||
|
if not topic:
|
||||||
|
failures.add("topics", rel(record["path"]),
|
||||||
|
"no topic, so it has no place in the reading order")
|
||||||
|
elif topic not in known:
|
||||||
|
failures.add("topics", rel(record["path"]),
|
||||||
|
"topic %r is not one of: %s" % (topic, ", ".join(sorted(known))))
|
||||||
|
|
||||||
|
|
||||||
def check_numbering(failures, records):
|
def check_numbering(failures, records):
|
||||||
"""The number in the filename is the number in the heading."""
|
"""The number in the filename is the number in the heading."""
|
||||||
for number, record in records.items():
|
for number, record in records.items():
|
||||||
@@ -273,6 +292,7 @@ def main():
|
|||||||
check_live_citations(failures, records)
|
check_live_citations(failures, records)
|
||||||
check_supersession_symmetry(failures, records)
|
check_supersession_symmetry(failures, records)
|
||||||
check_numbering(failures, records)
|
check_numbering(failures, records)
|
||||||
|
check_topics(failures, records)
|
||||||
print(f"records: {len(records)} decision records checked")
|
print(f"records: {len(records)} decision records checked")
|
||||||
return failures.report()
|
return failures.report()
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: the mesh
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-22
|
date: 2026-08-22
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: the mesh
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-02-25
|
date: 2026-02-25
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: the mesh
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-07-12
|
date: 2026-07-12
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: the tiers
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-28
|
date: 2026-08-28
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: the tiers
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-28
|
date: 2026-08-28
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: the tiers
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-28
|
date: 2026-08-28
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: the tiers
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-28
|
date: 2026-08-28
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: the tiers
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-26
|
date: 2026-08-26
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: what runs on it
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-28
|
date: 2026-08-28
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: what runs on it
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-28
|
date: 2026-08-28
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: building it
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-04-03
|
date: 2026-04-03
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: building it
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-23
|
date: 2026-08-23
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: building it
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-05-14
|
date: 2026-05-14
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: building it
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-06-04
|
date: 2026-06-04
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: building it
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-07-10
|
date: 2026-07-10
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: building it
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-28
|
date: 2026-08-28
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: checking it
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-24
|
date: 2026-08-24
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: checking it
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-24
|
date: 2026-08-24
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: how we work
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-28
|
date: 2026-08-28
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
@@ -77,11 +78,25 @@ settled. Everything else belongs in the design document, where the reasoning is
|
|||||||
**There is no ledger** — no separate document summarising, ranking or tracking decisions. A
|
**There is no ledger** — no separate document summarising, ranking or tracking decisions. A
|
||||||
chronological view is generated from frontmatter, which is what a ledger was actually for.
|
chronological view is generated from frontmatter, which is what a ledger was actually for.
|
||||||
|
|
||||||
**The numbering is the flow here too.** Records are ordered the way somebody would learn the
|
**A number identifies a record and never changes.** It is not a position, and it cannot be
|
||||||
system — what the mesh is, then its tiers from the bottom up, then what runs on them and how it
|
both — a position moves when the set changes, and an identity that moves is not one.
|
||||||
gets there, then how it is built, how it is checked, and how we work. **Not chronologically**: the
|
|
||||||
date is in the frontmatter and a consolidated record holds decisions taken across a week, so
|
That is not a preference. Records are referenced from **outside** this repository: code
|
||||||
ordering by age would order by an accident that no longer exists.
|
comments, commit messages, the knowledge base. Renumbering once cost 96 references across two
|
||||||
|
code repositories, and nothing in either would have failed to compile — the comments would
|
||||||
|
simply have pointed at the wrong reasoning, which is worse than a broken link because nothing
|
||||||
|
reports it.
|
||||||
|
|
||||||
|
**So the reading order lives in a generated index**, from each record's `topic:` — what the mesh
|
||||||
|
is, then its tiers from the bottom up, then what runs on them and how it gets there, then how it
|
||||||
|
is built, how it is checked, and how we work.
|
||||||
|
|
||||||
|
**And the index is written, not only generated on demand.** A reader looking at the folder on a
|
||||||
|
forge sees the folder, not a command. The objection to a written index is that it drifts, and
|
||||||
|
that is answered by **checking** it rather than by refusing to write one — which is §5's own
|
||||||
|
rule: a rule states how it is checked. A record with no topic, or a topic nobody defined, fails
|
||||||
|
the same check, because the quiet failure is a record that vanishes from the order rather than
|
||||||
|
appearing in the wrong place.
|
||||||
|
|
||||||
**The design layer is what you read.** These records explain *why* a thing is as it is. They are
|
**The design layer is what you read.** These records explain *why* a thing is as it is. They are
|
||||||
not a description of the system, and needing to read them to understand it would mean the design
|
not a description of the system, and needing to read them to understand it would mean the design
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: how we work
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-07-10
|
date: 2026-07-10
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: how we work
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-23
|
date: 2026-08-23
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: how we work
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-26
|
date: 2026-08-26
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
topic: how we work
|
||||||
status: accepted
|
status: accepted
|
||||||
date: 2026-08-26
|
date: 2026-08-26
|
||||||
deciders: jochen
|
deciders: jochen
|
||||||
|
|||||||
+59
-3
@@ -62,6 +62,62 @@ rather than guessing.
|
|||||||
|
|
||||||
## Index
|
## Index
|
||||||
|
|
||||||
The index is **generated, not maintained** — run the `hq-status` skill, which reads the
|
**A number identifies a record and never changes.** Records are referenced from outside this
|
||||||
frontmatter of every record. A hand-written index drifts from the folder it describes, and
|
repository — code comments, commit messages — so a number that moves invalidates them silently.
|
||||||
this one had already done so after a single addition.
|
Renumbering once cost 96 references across two code repositories, and that is why the numbers
|
||||||
|
are now fixed.
|
||||||
|
|
||||||
|
So the folder is in creation order, and **the reading order lives here.** It is generated from
|
||||||
|
each record's `topic:` and written, because a reader looking at the folder on a forge sees the
|
||||||
|
folder rather than a command. The objection to a written index is that it drifts — which is
|
||||||
|
answered by checking it rather than by refusing to write one:
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 00-META/checks/index.py --write regenerate
|
||||||
|
python3 00-META/checks/index.py fail if stale
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- index:start -->
|
||||||
|
|
||||||
|
### What the mesh is
|
||||||
|
|
||||||
|
- **0001** — [The mesh brokers capabilities; nodes host; agents think](0001-mesh-brokers-nodes-host-agents-think.md)
|
||||||
|
- **0002** — [Nodes communicate over a message broker, not over HTTP](0002-nodes-communicate-over-a-broker.md)
|
||||||
|
- **0003** — [An agent is a persistent employee, not an instance of a pool](0003-agents-are-persistent-employees.md)
|
||||||
|
|
||||||
|
### Its tiers, from the bottom up
|
||||||
|
|
||||||
|
- **0004** — [A node, and how it joins](0004-a-node-and-how-it-joins.md)
|
||||||
|
- **0005** — [The node host](0005-the-node-host.md)
|
||||||
|
- **0006** — [The substrate and the control plane](0006-the-substrate-and-the-control-plane.md)
|
||||||
|
- **0007** — [Connectivity](0007-connectivity.md)
|
||||||
|
- **0008** — [A context owns its store, exclusively](0008-a-context-owns-its-store.md)
|
||||||
|
|
||||||
|
### What runs on them, and how it gets there
|
||||||
|
|
||||||
|
- **0009** — [Modules and the graph](0009-modules-and-the-graph.md)
|
||||||
|
- **0010** — [Delivery](0010-delivery.md)
|
||||||
|
|
||||||
|
### How it is built
|
||||||
|
|
||||||
|
- **0011** — [Managed files are generated onto nodes and never edited there](0011-managed-files-are-generated-never-edited.md)
|
||||||
|
- **0012** — [The mesh creates no symlinks — a derived file is a copy](0012-the-mesh-creates-no-symlinks.md)
|
||||||
|
- **0013** — [Schema and state changes are numbered migrations, in the same language as the code](0013-schema-changes-are-numbered-migrations.md)
|
||||||
|
- **0014** — [No workspace — each module is a standalone package consuming published dependencies](0014-no-npm-workspace.md)
|
||||||
|
- **0015** — [Applications live in their own repository; the monorepo is for the mesh](0015-applications-live-in-their-own-repository.md)
|
||||||
|
- **0016** — [The lab](0016-the-lab.md)
|
||||||
|
|
||||||
|
### How it is checked
|
||||||
|
|
||||||
|
- **0017** — [A test defends a decision](0017-a-test-defends-a-decision.md)
|
||||||
|
- **0018** — [A picture of a system is read from the system, never from what asked for it](0018-a-picture-is-read-from-what-runs.md)
|
||||||
|
|
||||||
|
### How we work
|
||||||
|
|
||||||
|
- **0019** — [How this repository works](0019-how-this-repository-works.md)
|
||||||
|
- **0020** — [The mesh is governed by a constitution, injected where work is decided](0020-the-mesh-is-governed-by-a-constitution.md)
|
||||||
|
- **0021** — [HQ is the source of the mesh constitution](0021-hq-is-the-source-of-the-constitution.md)
|
||||||
|
- **0022** — [The constitution absorbs what is already enforced](0022-the-constitution-absorbs-what-is-enforced.md)
|
||||||
|
- **0023** — [The approval is the checkpoint, not the second pair of hands](0023-approval-is-the-checkpoint.md)
|
||||||
|
|
||||||
|
<!-- index:end -->
|
||||||
|
|||||||
Reference in New Issue
Block a user