Answer round 2 of the domain review of the approval mark

A stuck state recovers as a kept call, a node with no or stale readings is
denied, the caller-named subjects are withheld too, and the per-machine key
form and the client's prefix are named, so no path serves a marked tool
unasked; the forge's loosening is marked by the operator's answer.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 02:51:24 +01:00
parent 636b2be7ac
commit bddf6ce95a
2 changed files with 53 additions and 23 deletions
@@ -339,7 +339,9 @@ approves, for a marked event or a restricted stream.
waking it, and answers at once, so the same call made again runs within the warrant's 30 minutes. *Why:* ADR 0286
§14 answers within 5 seconds, which a watcher's loop would turn into a call every few seconds for the ask's hour;
ending at the choice keeps a long wait from spending the warrant's window. An unmarked wait is heard as §13 to §16
say, with no ask.
say, with no ask. Every machine's marked waits ask under the one asker `node-tools`, so they share its open asks
and the router's holding back of the oldest beyond three (ADR 0666 §12, as G builds it), and the wait reads the
router's record through `Client.Where` under `node-tools`.
37. **The tests hold it.** Unit, as the budget counts them (`meshtest` runs its bus in the test's process): §19's table
of refusals in `console/wait_test.go` gains rows, no test function: a wait on a fixture module's marked event, and
one on a fixture stream with a `grant`, each with no warrant, subscribes nothing, asks once and answers design 46
@@ -53,7 +53,9 @@ them, on a machine whose runner honours the mark. Read on the workstation on 202
file is mode 0600, owned by the operator's account, in a directory of mode 0755: every process running as the
operator's account, every module on the machine's shared account and every agent session among them, can read it and
hear a restricted stream without a warrant. This is the same known limit as design 47 G §385 (modules running as the
machine's account, issue 1966 note 193, whose fix carries its check), and this milestone does not change it. The
machine's account, issue 1966 note 193, whose fix carries its check), and this milestone does not change it: that a
restricted stream is heard only on a warrant holds against every caller once that fix runs modules and sessions on
accounts that cannot read the file, and until then against callers that go through the tool runner. The
prepare reads the file's mode and owner on every node with `<node>/node-reads.stat` and writes them in the milestone
file; a node where the file is readable beyond the operator's account is a finding of the prepare.
@@ -78,8 +80,10 @@ file; a node where the file is readable beyond the operator's account is a findi
no tool runner loads or gates them (a module such as the forge's, whose tools bundle no process runs, is not
refused); or names in `needs-approval` one name that is both one of its tools and one of its events. A tool mark
is checked against `tools` where the manifest lists its tools; where only its bundle names them, the tool runner
checks each marked tool against the tools the bundle registers at load, and a mark naming none is its finding
`<node>.<module>.mark-unserved` (said as §16's), cleared when the bundle serves it or the mark goes. Each refusal names the entry and the rule. *Why:* a mark
checks each marked tool against the tools the bundle registers at load: a bundle whose marks name a tool it does not
register is not loaded, failing closed rather than serving unmarked what the manifest meant to mark, and that is its
finding `<node>.<module>.mark-unserved` (said as §16's), cleared when a bundle that serves it, or a manifest without
the mark, is loaded. Each refusal names the entry and the rule. *Why:* a mark
nothing enforces would be a call that runs unasked while saying it is guarded; ADR 0286 §43 had the controller
enforce its own verbs, and no other own-process server has a gate, so such a mark is refused rather than ignored;
and an entry that names two things is read one way by one reader and another way by the next (the design's choices
@@ -106,18 +110,25 @@ file; a node where the file is readable beyond the operator's account is a findi
`approval-mark/1` among its call capabilities (`runtime.CallCapabilities`), which its announcement carries and its
node's node-engine takes into the node's record; the assignment half carries it among the capability readings
(design 47 G §194). The grants part, composing a runtime's account, reads that runtime's node's readings: where the
tool runner does not state `approval-mark/1`, it composes a subscribe deny of every marked subject of every module
the runtime carries (`mesh.mod.<module>.tool.<tool>`, the same followed by `.*`, and for a marked verb
`mesh.seat.<seat>.tool.<verb>`, the same followed by `.<node>`), which the bus's deny outranks its allow of
`mesh.mod.<module>.tool.>` for. `Permissions` gains `SubscribeDeny`, written into the user list as the server's
tool runner does not state `approval-mark/1`, where the node has no readings, or where its readings are older than
the tool runner's version on that node (failing closed as §19), it composes a subscribe deny of every marked
subject of every module the runtime carries: `mesh.mod.<module>.tool.<tool>` and the same followed by `.*`; the
caller-named subject mesh-sdk's `process.Call` sends on first, `mesh.mod.<module>.call.<tool>.>`; and for a marked
verb `mesh.seat.<seat>.tool.<verb>`, the same followed by `.<node>`, and `mesh.seat.<seat>.call.<verb>.<node>.>`.
The bus's deny outranks its allow of `mesh.mod.<module>.tool.>` for each. Going back to an older tool runner on a
node leaves the window until its readings update, in which the old deny or none stands; that window is accepted. `Permissions` gains `SubscribeDeny`, written into the user list as the server's
subscribe deny, as `PublishDeny` is for publishing. The withheld tools and verbs are named in the grants part's read
of that account (`mesh-grants.grants_user_list`, a field `withheld` with each subject and "its tool runner does not
state approval-mark/1"), and in the node-engine's `composition` of the module. *Why:* ADR 0286 §7, §8, §9: the bus
enforces the mark; the runner's status of ADR 0286 §7 is read from the grants part's account read, since a runner
that does not honour the mark is a version that cannot say so. *How it is checked:* unit, the grants part's
composition table: with readings lacking the capability the deny of each marked subject is composed and named in
`withheld`, with it no deny; integration, the grants part's real-bus test: an account composed with the deny is
refused delivery of a call on a marked subject while it holds `mesh.mod.<module>.tool.>`.
that does not honour the mark is a version that cannot say so. A call that reaches no responder because its
subject is withheld is answered by the mesh MCP server naming `withheld` and the grants part's read. *How it is
checked:* unit, the grants part's composition table: readings lacking the capability, no readings, and readings
older than the runner each compose the deny of every subject above, the caller-named ones and a marked seat verb's
`.<node>` form among them, each named in `withheld`; readings with the capability compose no deny; integration, the
grants part's real-bus test: an account composed with the deny is refused delivery of a call on a marked subject
while it holds `mesh.mod.<module>.tool.>`, and so is a call sent through `process.Call` on the caller-named
subject.
7. **The asker's grants are a user's of `operator-channel`.** Because the manifest says `operator-channel` in `uses`
(§4), the grants part composes, on the account of the runtime carrying the module, the publish of
`mesh.seat.operator-channel.accept.ask.<module>` and `.cancel.<module>`, the hearing of its warrants on
@@ -141,7 +152,9 @@ file; a node where the file is readable beyond the operator's account is a findi
than 16 KiB. *Why:* ADR 0286 §12; mesh-sdk's `outward` is the content rule ADR 0310 §4 moved there. *How it is
checked:* unit, two rows of §22's table.
10. **One ask, under the module's name.** Otherwise the runner asks through an `operatorchannelclient.Client` whose
`Module` is the marked module and whose `State` is the runner's state of §13, unless an ask for the same act is
`Module` is the marked module, whose `State` is the runner's state of §13, and whose new field `KeyPrefix`, which
the client in mesh-contracts gains and puts before every key it writes and reads (empty for every asker today), is
`<node>.`; unless an ask for the same act is
open there (§13's `act.` key), in which case it asks nothing new. The ask: `about` the act's digest; `who`
`operator`; `expires` 60 minutes after; no `on-expiry`; two options, `approve` (label Approve) and `decline` (label
Decline), both at level `approve`, each binding the act's digest; the headline "Run <tool> on <node>?",
@@ -178,12 +191,18 @@ file; a node where the file is readable beyond the operator's account is a findi
newest ask for that act. The runner on each machine is the one writer of its machine's keys; a key is deleted by
its time to live, never by hand. A per-machine state reaches today only the key named for its machine; mesh-sdk's
per-machine form and the grants part's `stateAccess` gain the keys below it, `<node>.>`, for every per-machine
state: one more subject per per-machine state on each account, and every module's per-machine keys keep their
form. A write the bus refuses (the cap reached, or any other) fails closed under §19 and is said in the answer.
state, a bundle's membership (`stateIssuedFor`, ADR 0260 §18) too, in the same form: one more subject per
per-machine state on each account, and every module's per-machine keys keep their form; mesh-sdk's rebuild of it
rides in G's group. The cap is the bucket's, shared by every machine's keys. A write the bus refuses (the cap
reached, or any other) fails closed under §19 and is said in the answer; `mesh_approvals` reports the bucket's fill
against its cap. A run still unrecorded (§16) an hour before its key's 48 hours end is written in the runner's log
as an error naming the ask.
*Why:* ADR 0286 §24, §30 kept the waiting call in the runner's record and claimed a warrant in the node's own file,
kept 48 hours, by one writer per node; the client built since keeps asks and marks a warrant acted on by
compare-and-set in a module's bus state (ADR 0259 §10, ADR 0627 §1), and a per-machine state (ADR 0595) is one
writer per machine, so the file is not built. The cap and the key form are the design's choices. *How it is
writer per machine, so the file is not built. The keys below the machine's own depart from ADR 0260 §16 (a
per-machine state reaches only its machine's key), the design's choice following the operator's answers of
2026-11-11 (the gate built in G); the cap and the key form are the design's choices too. *How it is
checked:* unit, the manifest test of `node-tools` holds the entry; the grants part's composition table holds the
`<node>.>` reach and no other node's; integration, §22's whole test reads the keys.
14. **The same call again is judged on the router's record.** When a call of a marked tool comes and `<node>.act.`
@@ -237,15 +256,18 @@ file; a node where the file is readable beyond the operator's account is a findi
refused, runs nothing and says why when: no module holds `operator-channel` (read from the assigner's first view
`mesh-delivery-assigner.views.seat-holders`, which the tool runner consumes; the seat list names no holders); the
router refuses the ask; the ask cannot be published; the router's record cannot be read; §13's state cannot be read
or written, in which case no marked call runs on that machine until it can; or the marks are not yet read. Before
or written, in which case no marked call runs on that machine until it can, the failed read or write being made
again as a kept call (ADR 0668 §1) until it succeeds; or the marks are not yet read. Before
the first view of the catalog half is read, a call waits for it within its 5 seconds and is then judged, asking
nothing while the marks are unread; still unread, it is refused, naming `mesh_first_views`. When §13's state has
been unreadable or unwritable for five minutes, the runner raises its finding `<node>.approvals.approvals-unreadable`
(said on `node-tools-findings` as §16's), and clears it at the next read and write that succeed. Each refusal is
(said on `node-tools-findings` as §16's), and clears it when the kept call succeeds; its `findings-stated` at a start
judges it anew from a read of the state (design 47 G §384). Each refusal is
written in the runner's log with the ask's id and fingerprint, never the arguments. *Why:* ADR 0286 §40, §42; a
gate that refuses every call for a reason nobody hears is a silent fault, and the finding and its five minutes are
the design's choice. *How it is checked:* unit, one row of §22's table per case, the holder read from a fixture
`views.seat-holders` with none and with one, and the finding raised after five minutes and cleared.
`views.seat-holders` with none and with one; and a row where the state fails, the finding is raised after five
minutes, the state recovers with no further call, and the finding clears.
20. **The terminal is not exempt.** A marked tool called through `mesh-cli` or any caller asks as every call does: the
runner cannot tell who called. *Why:* ADR 0286 §41. *How it is checked:* the code review reads that the gate sits
on the call's handler, before the bundle, and that no path to a bundle passes around it.
@@ -265,9 +287,9 @@ file; a node where the file is readable beyond the operator's account is a findi
gains `operator-channel` in `uses`. No other tool or verb is marked in this milestone. *Why:* the forge's own
refusal says these two acts are the operator's and waited for this mark (issue 417, 2026-10-28); a mark is on a
whole tool (ADR 0286 §1), so the acts move to a tool of their own, as ADR 0286 §48 splits a tool into an unmarked
and a marked form, and tightening a protection stays unasked; marked now pending the operator's answer of
2026-11-11. *How it is checked:* the forge module's tool test: `set_full` refuses both acts naming the new
tool; the new tool performs each; and live, a loosening asked on the phone runs once on the Approve.
and a marked form, and tightening a protection stays unasked; the operator, 2026-11-11: "Mark them in G". *How it
is checked:* the forge module's tool test: `set_full` refuses both acts naming the new tool; the new tool
performs each; and live, as the acceptance says.
## Tests, delivery and going back
@@ -276,7 +298,8 @@ file; a node where the file is readable beyond the operator's account is a findi
(`meshtest`), the router's record and the warrant written there as the router writes them; and one integration
test that starts the runner against the real bus, with no store, with a fixture marked module: a call, the
operator's Approve written as the router's record, the call again, the run, the hand-act, `mesh_approvals`, and a
restart between the run and `warranted`'s answer. *Why:* the operator's test budget (one whole test over many small
restart between the run and `warranted`'s answer. The client's `KeyPrefix` (§10) has a row in mesh-contracts'
client test: every key written and read carries it, and an empty prefix writes today's keys. *Why:* the operator's test budget (one whole test over many small
ones). *How it is checked:* the check counts the module's tests against its budget.
23. **In G's delivery group, ordered by the module graph.** The members are pushed and checked in design 47 milestone
G's group, with design 34's milestone every-event. The orders are the module graph's: the indexer's manifest
@@ -298,6 +321,11 @@ file; a node where the file is readable beyond the operator's account is a findi
`node-login-shell.execute` on a node other than the control-node answers "Not run", the ask is on
`operator-channel.asks`, and after the operator's Approve the same call runs once, a third identical call is refused
as already run, and `mesh-hand-acts.hand-acts` names the warranted act (§10, §12, §14 to §16, §21).
- **The forge's loosening is asked, and the protection is put back** (live, read by tool call): on a scratch repository
of the forge made for this acceptance, `acceptance-approval-mark`, with full protection on its trunk,
`gitea_branch_protection_loosen` removing the trunk's protection answers "Not run" and asks on the phone; after the
Approve the same call runs once; then `gitea_branch_protection_set_full` sets the full protection again and
`gitea_branch_protection_get` reads it back as it was (§21).
- **No machine serves `execute` unasked** (live, read by tool call): before this acceptance passes, on every node
`execute` is either asked as above or absent, read with each node's `node-engine.composition` and
`mesh-grants.grants_user_list`'s `withheld` (§6).