Hold a retired word's plural to the word, so alerts and rollouts no longer pass

words.py matched a retired word only when nothing followed it, so the
plural of every retired word passed. The pattern now takes s or es on the
last word; 14 uses in 9 documents are reworded, two of them 'the hosts'
for the node-engines.
This commit is contained in:
jochen
2026-10-07 21:14:36 +02:00
parent 921ff3e6c4
commit 350f7dcdc2
10 changed files with 26 additions and 18 deletions
+4 -1
View File
@@ -78,5 +78,8 @@ of the tools' retired words (`retired-words`) with the glossary.
It failed on something real before it passed: 581 uses of retired words in 63 documents on its first run, besides the glossary itself and research 034 —
"the host" for the node-engine in 30 designs, "control plane" in 10, the glossary's own entry for the tool runner —
and two glossary contradictions (the "console", the deprecated broker's seat), fixed in the change that
added it. Homonyms (*plan*, *gate*, *tier*, *ask*, *store*, *record*, *check*) are not checked by it: a
added it. A retired word's plural (`s` or `es` on its last word) is matched as the word: until 2026-10-07
it was not, so "alerts", "rollouts" and "the hosts" (the node-engines) passed while their singulars
failed; the plural failed on 14 uses in 9 documents before it passed. A verb that reads like a plural
("a node hosts") is a finding too, and is reworded. Homonyms (*plan*, *gate*, *tier*, *ask*, *store*, *record*, *check*) are not checked by it: a
word list cannot tell one sense from another, so they are reviewed.
+8 -3
View File
@@ -20,7 +20,8 @@ What is enforced:
checked document. Running prose is what is left once code blocks, code spans, block
quotes, text in quotation marks, struck-through text, link targets, HTML comments and
frontmatter are taken out: a quotation keeps the words it quotes, a link target is a file
name, and an identifier lives in a code span.
name, and an identifier lives in a code span. A word's plural is the word: "alerts" fails
as "alert" does.
unique no head word (a bolded word opening a glossary entry) heads two entries, and no head
word is also struck through on a *Not:* line.
tools list with `--list tools` it prints the words retired in the tools' descriptions, which is the
@@ -96,9 +97,13 @@ def glossary():
def pattern(word):
"""A whole-word, case-insensitive match; a space in the word matches a space, a line break or a hyphen."""
"""A whole-word, case-insensitive match; a space in the word matches a space, a line break or a hyphen.
The plural is the word too: its last part may end in `s` or `es` ("alerts", "control planes"), since
a retired word does not come back by being counted. Anything else joined on is another word.
"""
parts = [re.escape(p) for p in word.split()]
return re.compile(r"(?i)(?<![\w-])" + r"[\s-]+".join(parts) + r"(?![\w-])")
return re.compile(r"(?i)(?<![\w-])" + r"[\s-]+".join(parts) + r"(?:e?s)?(?![\w-])")
def blank(match):
+1 -1
View File
@@ -331,7 +331,7 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240.
- **condition** — one open fact about something the mesh owns that is wrong, with a key and a severity;
raised and cleared only by observation
([ADR 0231](../02-DECISIONS/0231-a-healer-acts-on-what-observation-raised-and-only-observation-says-it-worked.md)).
A wrapped dashboard's alerts are its own; the mesh's notion is a condition.
What a wrapped dashboard raises is its own; the mesh's notion is a condition.
*Not:* ~~alert~~ (hq)
- **self-check** — the controller's own examination of the mesh, run on demand and on a schedule
(to-be 45 §4). Its verb is `doctor`, an identifier; prose says self-check.
+2 -2
View File
@@ -20,7 +20,7 @@ One relational database holds the bindings. Its content divides cleanly:
| Holds | Describes |
|---|---|
| Node records | Which nodes exist, and each node's own properties — its name in the mesh, whether it carries a public name, its identity text |
| Assignments | Which node hosts which module, at which selection, and whether it starts automatically |
| Assignments | Which node runs which module, at which selection, and whether it starts automatically |
| Overrides | Per-node, per-module values that take precedence over anything the manifest generates |
| Mesh settings | Values every node reads — where the broker is, where the forge is, where the registry is |
| Grants | Which consumer holds which resource from which provider, with the credential |
@@ -87,7 +87,7 @@ down.
## Reaching a capability on another node
A node hosts some capabilities and can reach the rest.
A node holds some capabilities and can reach the rest.
At startup, a node asks every peer what it hosts. For anything hosted elsewhere it creates a
local stand-in that forwards over the broker. For anything hosted both locally and elsewhere
+1 -1
View File
@@ -55,7 +55,7 @@ each one earning its place by the test above rather than by being ours:
| **connectivity** | private network, resolution, exposure, filtering, certificates — **specified in full in [`08-connectivity.md`](08-connectivity.md)** | who peers with whom; which node is reachable |
| **provisioning** | resource grants between modules | consumer and provider may be on different nodes |
| **delivery** | source to artifact to node | it targets nodes |
| **observability** | health, logs, metrics, alerts | *unreachable for a week* is nobody else's to notice |
| **observability** | health, logs, metrics, conditions | *unreachable for a week* is nobody else's to notice |
| **identity** | agents, humans, services, authorisation | credentials follow an agent's node bindings and modality |
| **api** | the one interface every surface speaks to | — it is an interface, not a context |
+3 -3
View File
@@ -454,7 +454,7 @@ An operator's own names, unrelated to the mesh, live in `/etc/hosts`'s kept regi
the `node-hostname` seat's holder and changed through its tools; the controller holds none of them
([ADR 0199](../../02-DECISIONS/0199-a-module-that-answers-names-declares-its-zone-and-a-nodes-hosts-file-is-one-modules.md)).
*Checked by the holder's configuration carrying one forwarding rule per declared zone, and by a push
leaving the hosts file's operator region byte for byte.*
leaving the `hosts` file's operator region byte for byte.*
*What follows describes the per-node resolver this replaces — how it was built and why the roles were
split. The split stands; the serving role's scope is what moved.*
@@ -474,7 +474,7 @@ composed from the serving node, the rule above holds without exception. The publ
module's node's, which is where the operator put it.
**The mesh writes the data and runs no daemon.** One wildcard per machine, from the same set that
writes the hosts file. A resolver is third-party software and runs *on* the mesh rather than being
writes the `hosts` file. A resolver is third-party software and runs *on* the mesh rather than being
*of* it: the mesh has no business shipping one, choosing which one, or knowing its configuration
language. Swapping dnsmasq for unbound changes that module and nothing in the controller.
@@ -1113,7 +1113,7 @@ The list is worth having in one place, because it is most of the argument:
2026-10-05:* no node holds `node-dns-resolver` any more, and
[ADR 0220](../../02-DECISIONS/0220-what-a-machine-asks-needs-its-uplink-held-and-the-retired-resolver-pieces-go.md) deletes the seat. What stood here before: *"Not built: every node still runs
`node-dns-resolver`. The migration's four steps are in the record, in order."* Nor did it
stay true that *"the workstation moves to the one resolver only once"* zones and the hosts file's
stay true that *"the workstation moves to the one resolver only once"* zones and the `hosts` file's
holder ([ADR 0199](../../02-DECISIONS/0199-a-module-that-answers-names-declares-its-zone-and-a-nodes-hosts-file-is-one-modules.md))
exist: every node, the workstation included, asks the one resolver, and every node holds
`node-hosts-file`.
+2 -2
View File
@@ -102,7 +102,7 @@ machine*.
That is not sufficient here, and the shortfall is concrete rather than theoretical:
- the controller node hosts **two** sessions, which must be able to hold **different**
- the controller node runs **two** sessions, which must be able to hold **different**
licences — a per-machine binding cannot express it at all;
- *this node's session uses the personal licence, the mesh's uses the company one* is the ordinary
case, not an exotic one.
@@ -192,7 +192,7 @@ here so the shape is not rediscovered.
## Consequences
**Two sessions on one node, and no ambiguity.** The controller node hosts its own node session
**Two sessions on one node, and no ambiguity.** The controller node runs its own node session
and the mesh session. ADR 0004's *one per node* forbids ambiguity about who answers when a **node**
is addressed; these answer to different addresses.
+2 -2
View File
@@ -638,7 +638,7 @@ left to be re-derived under time pressure:
| 3 | the client library | it is what a module calls to emit, and it is where the subject is derived. Until it lands, a locally-named event is published under the local name itself |
| 4 | the catalogue | every manifest and every module's code, renamed together. Safe only once the runtime derives |
| 5 | the controller | **it refuses an old-style event name outright**, so landing it before the catalogue makes every unconverted module unregisterable |
| 6 | the hosts | last, because nothing else waits on them |
| 6 | the node-engine | last, because nothing else waits on them |
Two properties make the sequence safe rather than merely ordered, and both are pinned by tests. A
name already in the old form passes through the derivation untouched, so a module nobody has
@@ -692,7 +692,7 @@ healthy while reacting to nothing.
> shutting it down ends the path that reaches this installation's machines from a workstation.
> The rollout is driven from the node, or before the broker stops — a sequencing constraint on
> 5.2, not an afterthought.
- [x] 5.5 **the AMQP transport is deleted from the controller and the hosts**. One bus, nothing
- [x] 5.5 **the AMQP transport is deleted from the controller and the node-engines**. One bus, nothing
to select ([ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md)).
**Done 2026-09-28.** The controller's old consume loop, build request, tool call, management
@@ -697,7 +697,7 @@ warning, not compared), and SMART read under an array.
**Done when:** on a lab mesh, a broken build of the controller, the node-engine and the tool runner —
one that starts and does nothing, one that crashes, one that cannot reach the bus — is each rolled
back with no hand, the mesh ends on the previous build, and a condition and a message say so. Live: the
next three core rollouts each record a health verdict.
next three core builds to roll out each record a health verdict.
**Built 2026-10-06** ([ADR 0236](../../02-DECISIONS/0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md),
the operator's "start phase 4"): in mesh-controller (migration 0073), the probes H-controller, H-engine,
@@ -713,7 +713,7 @@ so the *done when*; container state in the node-engine's report, without which a
crash-loops after its compose applied is seen only through what it breaks; *(designed 2026-10-07 by
[ADR 0240](../../02-DECISIONS/0240-a-module-says-how-it-is-healthy-and-the-node-engine-judges-it.md) as
[to-be 48](48-a-module-says-how-it-is-healthy.md), whose phases track it)* composing `not-reversible` for
a build whose migration cannot be undone; the next three core rollouts' verdicts.
a build whose migration cannot be undone; the verdicts of the next three core builds to roll out.
### Phase 5 — Checks before merge, and the replays
+1 -1
View File
@@ -80,7 +80,7 @@ ADR 0006 named seven contexts of the controller. Each is a domain now, under the
| connectivity | Connectivity |
| provisioning | Provisioning |
| delivery | Change and delivery — owned by the `mesh-delivery` module, not by the controller (ADR 0239) |
| observability | Health and repair: the mesh built conditions, not alerts, and half of the domain is repair |
| observability | Health and repair: the mesh built conditions, not "alerts", and half of the domain is repair |
| identity | Identity and access, with the licences store the seven did not name |
Module, Core, Data and Operator and conversation are new: the manifest, the controller itself, the data