Write the operator's answers into milestone G's part: findings heard as events, invokers chosen, the shared account a known limit

The condition keeper read other modules' findings states, which only their owner may reach; a node-engine
restarted with the full seat list never cleared its own finding, because only its process remembered writing it.
Observers now say each finding as an event and state all they own at each start and on the keeper's ask, so the
keeper's view is rebuilt from events alone. The `invokers` form is the operator's choice, and the tool runner's
shared account stays as it is in G, named as a known limit.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 02:20:24 +01:00
parent 9bcd0ad197
commit 9f9cb58f54
4 changed files with 103 additions and 21 deletions
+5 -3
View File
@@ -916,12 +916,14 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283, 0564, 0668.
(ADR 0283).
- **probe** — one look at one thing's health, run by whoever owns the verdict; what a probe returns is a
**finding**, before it becomes a condition. A finding is kept as a key `<scope>.<id>.<kind>` in its observer's
bucket `findings` until it is no longer observed (ADR 0564 §2).
bucket `findings` until it is no longer observed (ADR 0564 §2); from design 47's milestone G the observer also says
each as its own event, and states every finding it owns at its start, so the condition keeper hears findings and
reads no observer's bucket (ADR 0596 §2; design 47 G §383, §384).
- **node health report** — each node-engine's own report of its node's health, a key that node's engine writes
and the watchman reads (ADR 0564, as its progressive insight of 2026-10-29 has it); its layout is mesh-sdk's.
- **observer** — a part that writes findings for the condition keeper: the watchman, the self-check and the
healers (ADR 0564 §2), and each caller of a kept call, for its `call-unanswered` (ADR 0668 §3). An observer never
writes a condition.
healers (ADR 0564 §2), and each caller of a kept call, for its `call-unanswered` (ADR 0668 §3); from design 47's
milestone G, every module that writes findings (design 47 G §383). An observer never writes a condition.
- **condition keeper** — the holder of the seat `mesh-conditions`: the one writer of the
open conditions, raising each from an observer's finding and clearing it when the finding goes, and saying
`condition-raised`, `condition-changed` and `condition-cleared` (ADR 0564 §2, §3). The controller keeps them
@@ -3,7 +3,7 @@ layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-10
updated: 2026-11-11
decisions:
- "ADR 0667"
- "ADR 0666"
@@ -229,7 +229,7 @@ and an estimated 2 mesh-sdk publishes a day, each rebuilding about 120 artifacts
| the builder | `reserved`: one key per module and `MAJOR.MINOR` (§369) | old `state` section | until its `version-released`, a release of another `MAJOR.MINOR` of the module, or `builder_reserved_drop` | a few KiB; at the cap the builder reserves nothing and says why |
| the assigner | `written`: the last source revision and end count per module and watched branch (§352) | old `state` section | until the branch is unwatched | a few KiB |
| the indexer and the assigner | `inputs`: a `run` ask's input by write time and sha256 (§354) | old `state` section | until the ask's `step-ended`, at most a day | 64 MiB; an estimated 300 asks a day of under 100 KiB each is under 30 MiB; at the cap the asker refuses the ask loudly, naming the store |
| the runner and the assigner | `findings` (§355) | old `state` section | an entry deleted when its finding clears | a few KiB |
| the runner and the assigner | `findings` (§355), read by their own read tools alone; the keeper hears the findings as events (§383) | old `state` section | an entry deleted when its finding clears | a few KiB |
| the runner | `aged`: three step-log sequences (§330) | old `state` section | never deleted | bytes |
| the teller | `widens-said` (§335) | the template `state` | until the branch is unwatched | a few KiB; the template's |
| each build module | `steps` (§326) | the template `state` | one key per artifact, dropped by `steps` with `drop` | under 1 MiB; the template's |
+93 -15
View File
@@ -3,12 +3,13 @@ layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-10
updated: 2026-11-11
decisions:
- "ADR 0668"
- "ADR 0667"
- "ADR 0666"
- "ADR 0656"
- "ADR 0636"
- "ADR 0635"
- "ADR 0625"
- "ADR 0619"
@@ -21,6 +22,8 @@ decisions:
- "ADR 0563"
- "ADR 0548"
- "ADR 0363"
- "ADR 0289"
- "ADR 0253"
---
# 47, milestone G: the answers to the builders', the reviewers' and the operator's questions
@@ -147,7 +150,9 @@ record it follows from, and replaces the builder's own choice where one was made
`holder:<seat>@same-node` (the holder of that seat on the node of the verb's holder). A definition whose `callers`
today is text is given a list in this milestone: "any caller whose role grants it" becomes `invokers`, "a person"
`persons`, and every other text the names the design gives that verb. Why: ADR 0666 §20 makes the definition the
grants part's one source of caller limits, in a form it reads. How it is checked: unit (definition reader), each
grants part's one source of caller limits, in a form it reads; `invokers`, which ADR 0666 §20's list does not name,
is the operator's choice for the verbs whose text said "any caller whose role grants it" (Telegram, 2026-11-11 02:13
CET: "Any declaring module"). How it is checked: unit (definition reader), each
form accepted and any other entry refused; the grants part's table test grants each form.
- §337 **The bus module's restore points.** The verbs the holder of `node-backup` on the bus module's node calls to take
and restore a module's kept data on the bus name `holder:node-backup@same-node`. Why: only that holder takes them
@@ -251,10 +256,12 @@ record it follows from, and replaces the builder's own choice where one was made
composition step runs on parts built from the heads, in the sandbox (ADR 0666 §17). How it is checked: whole
(assigner), a composition step's `run` is answered and its input read by digest.
- §355 **The assigner says an unanswered `manifest-reads`.** The assigner, the one caller of the node-engine's
`manifest-reads` from G, writes `call-unanswered` into its own findings state, and the condition keeper's reads gain
it in the same change (ADR 0668 §3). The assigner and the runner each declare that state, `findings`, in their old
`state` section, both being foundation parts. How it is checked: whole (assigner, runner, condition keeper), a node-engine
that never answers raises `call-unanswered` after five minutes, and the keeper raises it.
`manifest-reads` from G, writes `call-unanswered` as its own finding and says it as an observer does (§383, §384),
so the condition keeper hears it with no change of its own (ADR 0668 §3; the operator, 2026-11-11). The assigner and
the runner each declare their own state `findings` in their old `state` section, both being foundation parts, read
by their own read tools alone. How it is checked: whole (assigner, runner, condition keeper), a node-engine that
never answers makes the assigner say `finding-written` for `call-unanswered` after five minutes, and the keeper
raises it from that event.
- §356 **Two acting parts in different steps make no cycle.** The assigner's cycle check counts waits within one step only
(*A wait holds only within its step*). How it is checked: the unit rows there.
- §357 **Which provider a requirement points to.** In the assignment graph, by design 27's order: the named seat's
@@ -285,15 +292,17 @@ record it follows from, and replaces the builder's own choice where one was made
- §364 **The node-engine claims its seat.** The node-engine's manifest claims the node seat `node-engine` (ADR 0563), and
the indexer and the node-engine's own guard read its holder from the seat list, keeping no module name. How it is
checked: the code review reads that neither fixes a module name (§7).
- §365 **The condition keeper reads its sources from the catalog.** The catalog graph entry carries each kept data a
manifest declares (its name and its section or template); the keeper takes a first view of the catalog half and
reads a module's `findings` wherever its entry declares a state `findings`, and the node-engine's holder from the
seat list. It joins the readers of *Every other reader of which modules exist*, reading every watched branch's newest catalog
revision, so a module watched on any branch is a source. A condition of a module that is in no branch's revision is
cleared, said with the reason `module left the catalog`. Before its first view the keeper keeps raising from the
sources it held, and says `view-waits` after five minutes (*A part with no view says it waits*). Why: no part fixes a name (§7),
and the keeper reads findings (ADR 0564 §2). How it is checked: whole (keeper), a fixture module declaring `findings`
is read with no code change.
- §365 **The condition keeper finds its sources in the catalog.** The catalog graph entry carries the events each
manifest declares in its `streams`; the keeper takes a first view of the catalog half, and its sources are the
modules whose entry declares the finding events of §383, the node-engine among them, its holder read from the seat
list. It joins the readers of *Every other reader of which modules exist*, reading every watched branch's newest
catalog revision, so a module watched on any branch is a source. A condition of a module that is in no branch's
revision is cleared, said with the reason `module left the catalog`. Before its first view the keeper keeps the
conditions it holds and applies no finding event, which waits on its durable consumer, and says `view-waits` after
five minutes (*A part with no view says it waits*). It reads no module's `findings` state (§383). Why: no part fixes
a name (§7), and the keeper hears findings and reads no other module's state (ADR 0596 §2, §4; the operator,
2026-11-11). How it is checked: whole (keeper), a fixture module declaring the finding events is heard with no code
change; the catalog's validation finds no `findings` in the keeper's manifest `reads`.
### From round 1 of the review by architectural domain (2026-11-10)
@@ -478,3 +487,72 @@ record it follows from, and replaces the builder's own choice where one was made
module's highest released is published late (a retry of an older reservation), the catalog records it `released
late`, says no `version-released` for it, and the runner starts no rebuild on its `package-published` (§53). Why:
a rebuild against an older part would move a dependent back; the design's choice, round 2 of the review by architectural domain, 2026-11-10. How it is checked: test 102.
### The operator's answers to milestone G's design questions (2026-11-11)
*ADR 0596 §1, §2, §4, ADR 0564 §2, §12, §13, ADR 0668 §1, §3, ADR 0636 §1, ADR 0666 §16, ADR 0363 §2. The operator,
2026-11-11, through lead-47: the condition keeper reads no other module's state and hears each module's findings as
events, and a module states its findings from its own truth; the shared tool-runner account stays as it is in G.*
- §383 **An observer says its findings as events; the keeper reads no observer's state.** Every module that writes
findings (those the keeper reads today and those G adds: the watchman, the self-check, the healers, the runner, the
assigner, the indexer, the builder, the forge bridge and the node-engine among them) is an observer, and says each
change of a finding as an event of its own, declared in its manifest's `streams` and published by its account alone
on `mesh.stream.<module>.event.<event>.<node>`, the node it runs on in the subject (§260, ADR 0363 §2):
`finding-written` when a finding is raised or what it says changes (only then, ADR 0289 §1, §4), and
`finding-cleared` when it no longer observes it. `finding-written` carries `observer` (the module), `node`, `scope`,
`id` and `kind` (the key `<scope>.<id>.<kind>` of ADR 0564 §2), `severity`, `subject`, `evidence`, ADR 0253's words
(`headline`, `explanation`), `since` and `at`; `finding-cleared` carries `observer`, `node`, `scope`, `id`, `kind`,
`at` and `why`. mesh-sdk's finding writer (`go/views`' `FindingWriter`) says them for every part, beside the key it
writes in the part's own state `findings`, which stays the owner's and is read only by the owner's read tool. The
keeper hears them on one durable consumer of its own: its manifest's `consumes` names them with the emitter `*`
(`*.finding-written`, `*.finding-cleared`, `*.findings-stated`), a form that takes that event from every module
whose manifest declares it, and the grants part grants the subscription per declaring module, derived from the
manifests as every grant is (§59); the manifest validator takes `*` only as the whole emitter before a declared event
name. The keeper takes a finding only from a source its catalog view names (§365), raises or changes a condition on
`finding-written` and clears it on `finding-cleared`, save while ADR 0564 §13 holds it; a condition's looks are
those of its last event, and the observer's read tool shows its newest. What exists today goes in the same change:
every `findings` entry of the keeper's manifest `reads`, its `consumes` of the assigner's and the indexer's
`views.findings` and its `invokes` of `assigner_read_findings` and `indexer_read_findings`; the assigner and the
indexer declare no `views.findings`, which no reader follows any more; the read tools stay for persons and sessions.
No ordering edge is needed: a source still on its old version says nothing, its conditions stand as they are, and
§384's bound names it. Why: only its owner reaches a module's state, and a part hears another's events (ADR 0596 §2,
§4); the operator, 2026-11-11. The subjects, the fields, the writer and the `*` emitter are the design's choice.
How it is checked: unit (mesh-sdk's writer), a finding written and one cleared each say their event with these
fields after the key; unit (manifest validator), `*.finding-written` accepted, `*.>` and `*` before an undeclared
event refused; whole (keeper), a fixture observer's `finding-written` raises a condition and its `finding-cleared`
clears it, and the same event from an account that is not the source's is refused by the bus; the catalog's
validation finds no `findings` in the keeper's `reads`.
- §384 **An observer states every finding it owns at its start and when the keeper asks; the keeper's view is
rebuilt from those events alone.** At each start, and on each ask of the keeper, an observer judges every finding it
owns anew from its own truth, the cause each kind watches (the seat list it holds, a call still unanswered, a wait
still standing), never from what a process remembers and never from its `findings` state, and says `findings-stated`
on `mesh.stream.<module>.event.findings-stated.<node>`, carrying `observer`, `node`, `at`, `asked` (the ask it
answers, none at a start) and `findings`, every finding it observes now with `finding-written`'s fields; a finding
whose bound has not passed since the start (a wait of under five minutes) is not among them. The keeper sets its view
of that observer on that node to exactly that set: it raises what is new and clears each condition of that observer
and node the set does not name, with the reason `not stated at <observer>'s start` or `not stated when asked`; for an
observer whose catalog entry claims only seats held once for the mesh, the set replaces its findings of every node,
so a holder moved to another machine leaves none behind. The keeper asks with its seat's event `findings-asked`
(`mesh.seat.mesh-conditions.event.findings-asked`, carrying `ask`, `at`, `by` and, to ask one, `module`), declared in
the seat's definition and heard by every observer through mesh-sdk's writer, which declares it in the observer's
`consumes`. It asks after its first view (§365) at each of its starts, and on `resync` (ADR 0564 §12), which so reads
no state. An observer that states nothing within five minutes of an ask while its machine is present is the keeper's
own finding `findings-unstated`, naming the module and the node, cleared by its next `findings-stated`; its conditions
stand meanwhile, and an observer on a machine away is not named. A set larger than one bus message goes in the
observer's sent store (§59). The node-engine's `seat-list-unread` (ADR 0636 §1) is stated so: a node-engine started
with the full list states no `seat-list-unread`, and the keeper clears it at that start. Why: the node-engine's
seat-list waiter kept only in its process that it had written its finding, so a node-engine restarted with the full
list never cleared it, and two machines said they waited for the seat list for a day while both held it (issue 1966);
the operator, 2026-11-11: the fix is NATS-native, from each observer's own truth. The event, its fields, the bound,
the reasons and the replacement for an observer held once are the design's choice. How it is checked: whole (the
node-engine, and each observer through mesh-sdk's writer), a module restarted after raising a finding whose cause has
gone clears it at its start, the node-engine's `seat-list-unread` among them; whole (keeper), a `findings-asked` is
answered by a fixture observer's `findings-stated`, a condition the set does not name is cleared with its reason, and
an observer that states nothing raises `findings-unstated` after five minutes; unit (mesh-sdk's writer), a start
states only the findings observed now.
- §385 **One tool-runner account for every module of a machine stays as it is.** On a machine where one tool runner
serves every module's tools on one bus account, any tools bundle there may read any sent store (§59) that account is
granted, and call `merge-pull` or the runner's `run` as a session; G changes neither that account nor its grants, a
known limit. Why: the operator, 2026-11-11; modules running as the machine's account are issue 1966's (note 193),
whose fix carries its check. How it is checked: none in G; the limit is named on issue 1966.
+3 -1
View File
@@ -3,7 +3,7 @@ layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-10
updated: 2026-11-11
decisions:
- "ADR 0668"
- "ADR 0667"
@@ -59,7 +59,9 @@ decisions:
- "ADR 0398"
- "ADR 0391"
- "ADR 0363"
- "ADR 0289"
- "ADR 0283"
- "ADR 0253"
- "ADR 0240"
- "ADR 0235"
- "ADR 0233"