Designs 34 and 46: sessions wait on any event, and the approval mark gates calls and marked waits, delivered with design 47's milestone G #540
+5
-1
@@ -1117,7 +1117,11 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272, 0383, 03
|
||||
- **approval mark** — a tool's or verb's declaration in its manifest (`needs-approval`) that each call to it runs
|
||||
only on the operator's warrant for that one call, with its exact arguments; the tool runner that serves it, or
|
||||
the controller for its own verbs, asks and checks
|
||||
(ADR 0286).
|
||||
(ADR 0286). It covers a module's events and streams too (the operator, 2026-11-11; design 34 every-event §32): a
|
||||
module may mark one of its events, a restricted stream is marked whole, and a `mesh_wait` on a marked event runs
|
||||
only on the operator's warrant for that one wait, with the exact names it waits on and its bound, which the tool
|
||||
runner asks and checks before it subscribes. The tool runner reads every mark from the catalog half on the bus
|
||||
(design 46 7-approval-mark §5).
|
||||
- **operator message / input** — what arrives on `intake`, once the router has checked the sender
|
||||
against its own list of the operator's identities: a **trusted** one is an operator message,
|
||||
addressed to an agent by `@name` or thread or else to the **responder** (`@mesh`, the router's own
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
---
|
||||
layer: to-be
|
||||
design: 34
|
||||
milestone: every-event
|
||||
updated: 2026-11-11
|
||||
code: [mesh-sdk, mesh-delivery, mesh-tools, mesh-catalog modules/claude-code]
|
||||
decisions:
|
||||
- "ADR 0670"
|
||||
- "ADR 0671"
|
||||
- "ADR 0286"
|
||||
- "ADR 0367"
|
||||
- "ADR 0043"
|
||||
- "ADR 0259"
|
||||
- "ADR 0332"
|
||||
- "ADR 0363"
|
||||
- "ADR 0376"
|
||||
- "ADR 0489"
|
||||
- "ADR 0571"
|
||||
- "ADR 0578"
|
||||
- "ADR 0614"
|
||||
---
|
||||
|
||||
# Design 34, milestone every-event — the tool runner hears every event
|
||||
|
||||
**What it builds.** A session's `mesh_wait` waits on any event of any seat or module, by its name, with no list to
|
||||
keep: the tool runner's bus account is granted the event subjects of the whole mesh, every module's own stream
|
||||
included (ADR 0671 §1), and the list of families in the tool runner's manifest, the manifest validator's rule about it
|
||||
and the grants part's computation of subjects from it go (ADR 0670). The whole shape is in the design's main file, [§3b](../34-the-console.md#3b-waiting-on-the-bus); this
|
||||
part holds every line the milestone builds, cited as "design 34 every-event §n".
|
||||
|
||||
**Where.** Four repositories and hq, one delivery group: mesh-sdk (the shared event subjects; the field, type and
|
||||
membership entry that go), mesh-delivery (the grants part, seat `mesh-grants`; the manifest validator in the indexer),
|
||||
mesh-tools (the tool runner, module `node-tools`) and mesh-catalog (the claude-code module's Stop hook, §20), with hq
|
||||
for the playbook and agent lines (§24). Only these reference the list today: no other manifest carries the key
|
||||
`waits`. The milestone is built on each repository's main as it stands. Design 47's milestone G edits the grants
|
||||
part's account composition and the tool runner's manifest and manifest test too; its lead rebases it onto this
|
||||
milestone's heads when G's develop restarts.
|
||||
|
||||
**The approval mark on events.** *A marked event waits for the operator's warrant* (§32 to §37) writes the operator's
|
||||
answers of 2026-11-11: a wait respects the approval mark, and the gate that asks and checks it (design 46's milestone
|
||||
7, [`7-approval-mark.md`](../46-milestones/7-approval-mark.md)) is built in design 47's milestone G and delivered in one
|
||||
group with G and this milestone ("Build the gate in G"). So §25's grant of a restricted stream's subjects and the gate
|
||||
that holds its mark reach the machines together.
|
||||
|
||||
## The event subjects, in mesh-sdk
|
||||
|
||||
1. **One place names the mesh's event subjects.** mesh-sdk's package `buscompose` gains a file `events.go` with three
|
||||
exports, used by the grants part (§6) and the tool runner (§13), so neither spells the event namespace itself:
|
||||
- `EveryEvent`, the subjects the tool runner's account hears: `mesh.seat.*.event.>` and `mesh.mod.*.event.>`, in
|
||||
that order (ADR 0670 §1);
|
||||
- `EventSubjects(name)`, the subjects one event name lands on. A name is `<emitter>.<event>`, optionally followed
|
||||
by further tokens (`operator-channel.decided.claude-code`); each token matches `[A-Za-z0-9_-]+`. It answers four
|
||||
subjects: `mesh.seat.<emitter>.event.<rest>`, the same followed by `.>`, `mesh.mod.<emitter>.event.<rest>`, and
|
||||
the same followed by `.>`, where `<rest>` is every token after the first; given a namespace (`seat` or `module`,
|
||||
§13) it answers only that namespace's two. It refuses, with an error naming the rule, a name of fewer than two
|
||||
tokens, an empty token, or a token outside that form (so `*` and `>` are refused);
|
||||
- `EventName(subject)`, the inverse for a heard subject: for `mesh.seat.<emitter>.event.<rest>` or
|
||||
`mesh.mod.<emitter>.event.<rest>` it answers the name `<emitter>.<rest>` and its namespace; any other subject is
|
||||
answered as not an event.
|
||||
|
||||
Why four subjects: a name does not say whether its emitter is a seat or a module, and both namespaces are heard
|
||||
(§6); and a seat's event that names its asker or kind after the event (ADR 0259 §3), or a module's that names its
|
||||
module and branch, is heard by the event's name alone, the `filter` narrowing it by body. How it is checked: one
|
||||
table-driven unit test of the three, in `events_test.go`, one of whose rows holds that every subject
|
||||
`EventSubjects` answers for its cases is covered by a subject of `EveryEvent`.
|
||||
2. **The manifest loses `waits`.** `catalogue.Manifest.Waits` and its comment go from `go/catalogue/manifest.go`;
|
||||
the comment on `ToolRunner` loses "and the only module that may declare `waits`". `go/catalogue/waits_test.go` is
|
||||
deleted, and one row is added to the decoder's existing table of unknown fields: a manifest
|
||||
`{"module":"node-tools","version":"1","tool-runner":true,"waits":["a.b"]}` decodes with `UnknownField()` naming
|
||||
`waits` (ADR 0670 §2). The decoder itself is unchanged: a stored manifest that still carries `waits` is read
|
||||
without it (ADR 0578 §3).
|
||||
3. **The baseline of manifest reads stays as generated.** `go/catalogue/manifest_reads_baseline.txt` keeps
|
||||
`word:waits`: it is the contract of the engines built before any stated what it reads (ADR 0614 §4), which did read
|
||||
`waits`. In `own_manifest_test.go` the list of baseline entries this contract no longer reads (today the two
|
||||
renamed seats) gains `word:waits`, its comment naming ADR 0670 §2 as the record that retired the word.
|
||||
4. **The wait types go.** `go/buscompose/waits.go` (the type `Wait`) is deleted and `Users.Waits` goes from
|
||||
`go/buscompose/users.go`; `Membership.Waits` and the type `WaitIssued` go from `go/membership/membership.go`
|
||||
(ADR 0670 §2). Nothing replaces them: no membership names what its account hears.
|
||||
|
||||
## The grant, in the grants part (seat `mesh-grants`)
|
||||
|
||||
5. **What the grants part computed from the list goes.** `internal/buscompose/waits.go` is deleted whole (`Wait`,
|
||||
`WaitSubject`, `waitGrants`, `waitsIssuedFor`); `Declared.Waits` goes from `internal/buscompose/users.go`, and with
|
||||
it the term `len(d.Waits) > 0` of `runnerServes`; `m.Waits = waitsIssuedFor(d)` goes from `membership.go`, and the
|
||||
alias `WaitIssued` from `sdk_membership.go`; `waitsOf` and the field `Waits:` of `declaredFor` go from
|
||||
`internal/grants/records.go` (ADR 0670 §2). `safeSubject` in `nats.go` stays: the bus-state grants use it.
|
||||
6. **The tool runner's runtime hears every event subject.** In `internal/buscompose/nats.go`, where a runtime's
|
||||
permissions are composed from the modules it carries, the call of `waitGrants` is replaced by: when one of the
|
||||
carried modules says `tool-runner` (`Declared.ToolRunner`), `buscompose.EveryEvent` (§1) is appended to the
|
||||
runtime account's subscribe list. Nothing else is added for it: no publish, no durable consumer, no queue, no other
|
||||
subject; a runtime carrying no tool runner gets neither subject (ADR 0670 §1, §3; ADR 0043 §2, §3, §5, §8 as ADR
|
||||
0670 §3 now reads them). The grants part writes them into the bus's user list as every grant (ADR 0571 §9); they
|
||||
are read with `mesh-grants.grants_user_list`, and composed at once with `mesh-grants.grants_compose_now`.
|
||||
7. **The tool runner's membership names no events.** The grants part's first composition after its delivery rewrites
|
||||
every tool runner's membership in its state `memberships` (key `<node>.node-tools`, ADR 0571 §10) without the key
|
||||
`waits`, since the field is gone (§4); every other part of it is unchanged. It is read with the grants part's
|
||||
reading of memberships and rewritten at once with `mesh-grants.grants_compose_now`.
|
||||
8. **A composition test holds the grant.** In `internal/buscompose/buscompose_test.go`, one table-driven test composes
|
||||
a runtime carrying the tool runner and one other module, and a runtime carrying the other module alone, and holds:
|
||||
the first's subscribe list contains `mesh.seat.*.event.>` and `mesh.mod.*.event.>`; no subscribe subject of either
|
||||
is wider than those (none of `>`, `mesh.>`, `mesh.*.>`, `mesh.seat.>`, `mesh.mod.>`, nor any `$JS.` subject beyond
|
||||
what the carried modules' own consumes give); the first's publish list holds no subject of another module's or
|
||||
seat's events; the first derives no durable consumer beyond its carried modules' `consumes`; and the second has
|
||||
neither subject (ADR 0670 §1, §3).
|
||||
9. **The real bus holds it too.** The grants part's existing real-bus test (`cmd/mesh-grants/composed_integration_test.go`)
|
||||
gains cases, no new test: the tool runner's account, as composed, subscribes
|
||||
`mesh.mod.mesh-delivery-view.event.delivered` and receives an event published there; it is refused a publish on
|
||||
that subject; it is refused a subscription on `mesh.seat.channel.accept.show.<kind>` and on
|
||||
`mesh.seat.intake.proof.code.<kind>` (an accept and a proof are never events, ADR 0670 §1); and another module's
|
||||
account is refused a subscription on an event it does not consume (ADR 0670 §1, §3).
|
||||
|
||||
## The manifest validator, in the indexer
|
||||
|
||||
10. **The rule about the list goes.** `waitProblems` and its call in `judge/rules.go` are deleted, and with them the
|
||||
regular expression `waitToken` where nothing else uses it; the table rows that judged `waits`
|
||||
(`judge/rules_test.go`, the `waits` cases) are deleted, and one row is added: a manifest that carries `waits` is
|
||||
refused by the manifest validator as the checker runs it (`Shelf`), naming the unknown field `waits`, and set aside
|
||||
to wait as the indexer runs it (`ShelfWaiting`), as any field it does not know (ADR 0578 §1); the tool runner's
|
||||
manifest without it is accepted (ADR 0670 §2). Only revisions taken in after the indexer's delivery are judged so:
|
||||
a manifest the validator accepted before keeps its verdict (ADR 0578 §1).
|
||||
|
||||
## The tool runner (module `node-tools`)
|
||||
|
||||
11. **Its manifest's changes, every one.** `node-tools/module.json` drops the key `waits` and its five entries (ADR
|
||||
0670 §2); gains `operator-channel` in `uses`, for the asks of marked waits (§35; design 46 7-approval-mark §7); gains
|
||||
in `requires` the state `approvals` of design 46 7-approval-mark §13; and gains the stream `node-tools-findings` and
|
||||
the `consumes` of `mesh-conditions.findings-asked` that design 47 G §387 writes. Its `version` stays `2`, a label
|
||||
no reader judges. Nothing else in it changes.
|
||||
12. **The publication time is kept.** `bus.Heard` gains `Published`, read from the event's header `x-time`, which
|
||||
mesh-sdk's emit sets (`go/process/emit.go`) and the status view's own emit too; `HearFirst` reads it from the
|
||||
message's headers, and leaves it empty for an event that carries none.
|
||||
13. **`mesh_wait` takes any event name.** `console.Surface.waitAnswer` (`internal/console/wait.go`) no longer reads the
|
||||
membership. Each name of `subjects` becomes its subjects through `buscompose.EventSubjects` (§1); a refused name is
|
||||
answered as a failure quoting that error. An optional argument `from`, `seat` or `module`, narrows every name to
|
||||
that namespace, for an emitter whose name is both a seat's and a module's. A call naming more than 16 names is
|
||||
refused, saying the bound (each name is four subscriptions for the call; 16 names is 64, far above what a watcher
|
||||
names). The same subject named twice is subscribed once. The map from subject to name goes: the heard event is
|
||||
named by `buscompose.EventName` of the subject it arrived on. The timeout, the filter and the timed-out answer are
|
||||
unchanged (ADR 0367 §1). The refusal of names outside a list, and its words naming the manifest, go (ADR 0670 §2).
|
||||
14. **A misspelt name is refused, not waited on for ever.** Before it subscribes, `waitAnswer` refuses a name whose
|
||||
emitter is neither a seat nor a module of the catalog half the tool runner holds from G (its first view by
|
||||
`mesh-catalog.revision`, followed on `catalog-revision`; design 47 G §196), naming that source and its revision.
|
||||
A name whose emitter is known but whose event no seat definition and no module's entry in the catalog half
|
||||
declares (its `emits` and its streams' events, design 46 7-approval-mark §5) is waited on,
|
||||
and its timed-out answer says that no declaration names that event. While the catalog half is not yet read (the
|
||||
first views' state, `mesh_first_views`), names are not checked, and the answer says `unchecked` with why; a
|
||||
marked wait is then held as §34 says.
|
||||
15. **The answer names the heard event.** A heard event is answered as today, `heard` with `subject`, `body` and
|
||||
`at`, where `subject` is now the heard event's whole name (`mesh-delivery-view.delivered`,
|
||||
`operator-channel.decided.claude-code`), with two new fields: `from`, `seat` or `module`, the namespace it
|
||||
arrived on, and `published`, the event's own time (§12), absent when it carries none. The `read` line stays (an
|
||||
event only wakes you, ADR 0367 §4); for a name beginning `operator-channel.decided` it says instead that the
|
||||
operator's answer is read only with `claude_code_answer`, never from the event's body (ADR 0670 §5; ADR 0332 §5,
|
||||
§6).
|
||||
16. **A refused subscription is said, and a wait is never a hot loop.** `HearFirst` subscribes every subject of the
|
||||
call; a subscription the bus refuses is noted, and the others go on waiting. When every subscription of the call is
|
||||
refused, it waits out the call's timeout (or the caller's going) and then answers the refusal, so a watcher that
|
||||
loops on it asks once a timeout, never at once again. Its refusal words become: "the bus refused this account a
|
||||
subscription on <subjects>: the grants part grants this machine's tool runner every event subject (novox/hq ADR
|
||||
0670 §1), so its grant is not composed yet; read the state from its owner meanwhile"; no words name a wait grant or
|
||||
ADR 0367 §2. A heard event's answer and a timed-out answer list under `refused` any subject the bus refused while
|
||||
others stood.
|
||||
17. **The tool's description says what it hears.** `waitTool`'s description of `subjects` says: one or more event
|
||||
names, `<seat or module>.<event>`, such as `mesh-delivery-view.delivered`; an event whose subject goes on after
|
||||
the name (an asker, a kind, a module) is heard by the name too; the filter narrows by the body's fields; `from`
|
||||
narrows to a seat's or a module's. Its own description keeps "an event only wakes you" and adds that an operator's
|
||||
answer is read only with `claude_code_answer`. The words "only those the mesh granted this machine's tool runner"
|
||||
go.
|
||||
18. **The hearing serves `mesh_wait` alone.** `HearFirst` has one caller, `waitAnswer`. The modules the runtime carries
|
||||
share its account, so the bus no longer bounds which events their code could hear; the runtime still gives a
|
||||
carried module's code only what its membership names, and this is accepted under ADR 0670 §1, every session
|
||||
already hearing every event. How it is checked: the code review of every change to the tool runner reads
|
||||
`HearFirst`'s callers.
|
||||
19. **What goes from the tool runner's code, and its tests.** The type `bus.WaitIssued` (`internal/bus/wait.go`) and
|
||||
`Membership.Waits` (`internal/bus/bus.go`) go. In `console/manifest_waits_test.go` the test of the five families
|
||||
becomes a test that the manifest has no key `waits`, its comment citing ADR 0670 §2; its tests of what the tool
|
||||
runner consumes and of the runner's own secrets stay. In `console/wait_test.go`: `waitingRuntime` issues no waits,
|
||||
and the answering test waits on `mesh-delivery-view.delivered` and on `operator-channel.decided` with no membership
|
||||
naming either, its rows holding `subject`, `from`, `published` and `read` (a `decided.<asker>` event heard by
|
||||
`operator-channel.decided`); `TestMeshWaitRefusesAnEventOutsideTheGrantedFamilies` becomes the table of refusals:
|
||||
a name with `*`, with `>`, of one token, 17 names, an emitter no source knows (§14), and a bus-refused subscription
|
||||
answered after the timeout with §16's words; `TestMeshWaitWithNoFamiliesIssuedWaitsOnNothing` is deleted. In
|
||||
`bus/wait_test.go`, `TestTheBusRefusesAWaitOutsideTheGrant` stays as §16's refusal test, with a timeout of a
|
||||
fraction of a second, its comment citing ADR 0670 §1. The manifest test also holds each change of §11: no `waits`,
|
||||
`operator-channel` in `uses`, the state `approvals` in `requires` with its options, and the finding stream. No test
|
||||
function is added: the module stays within its budget.
|
||||
|
||||
## The claude-code module's Stop hook
|
||||
|
||||
20. **The hook no longer doubts the grant.** In mesh-catalog `modules/claude-code/cmd/claude-code/stop.go`,
|
||||
`watcherAdvice` says for a wait with an event: "mesh_wait on <event> and reread <read> after every answer, or start
|
||||
a watcher that does", with no condition on the tool runner's grant; its comment cites ADR 0670 §2 beside ADR 0489
|
||||
§3. Its row in `stop_test.go` holds the new words.
|
||||
|
||||
## Events carry no secret
|
||||
|
||||
21. **The prepare lists every event.** The milestone file's prepare (playbook 07, step 0) gains a section *Events and
|
||||
their fields*: every event each module declares (its `emits` and its streams' events) and each seat's definition
|
||||
declares, read from the module catalog: the catalog revision of the change's branch (`mesh-catalog.revision`),
|
||||
which carries the seat definitions too, and nothing else, no manifest and no bus read, so the events G and design
|
||||
46's milestone 7 add are listed (the finding streams of design 47 G §383 among them), each a holder's event with
|
||||
its subject form; and every event mesh-sdk publishes on a module's behalf (the announcement's `states` on
|
||||
`mesh.mod.<module>.event.states`, found by the callers of its subject builders); each with the fields of its body
|
||||
as the emitting code builds it, by file and line (ADR 0670 §4). *Why:* the operator, 2026-11-11: "only the indexer
|
||||
uses the manifest to build the module catalog".
|
||||
22. **The code review confirms none carries a secret, before the grant is delivered.** The milestone's code review
|
||||
(playbook 07, step 2) reads that list again at the group's heads, and confirms that no event's body carries a
|
||||
credential, a token that grants an act, code, or a message marked secret; an intake `choice`'s one-use token is not
|
||||
such a token (ADR 0259 §3, §5). The list is the module catalog's (§21); the review reads no bus. An event found
|
||||
carrying a secret holds the milestone's delivery and goes to the operator as a design question
|
||||
(ADR 0670 §4).
|
||||
23. **The stream events are read too.** §21's list also names every event kept in a module's own stream
|
||||
(`mesh.stream.<module>.event.<event>`, ADR 0363 §2), the seat holders' events published there among them, and
|
||||
whether its stream is restricted (its declaration carries a `grant`, ADR 0363 §4). The mail stream's events are
|
||||
named in it as heard with their content: a mail's own text, a sign-in link or a code someone sent by mail among
|
||||
it, is the operator's mail and not a secret in §22's sense, which is a credential, token or code the mesh itself
|
||||
issues or keeps (ADR 0671 §3, which narrows ADR 0670 §4).
|
||||
24. **From then on every change is asked the same.** In this milestone's hq pull request: playbook 07's review step
|
||||
gains the question, for any change that adds an event or a field to one, "does it carry a secret?"; and the watcher
|
||||
lines that describe the list (`.claude/agents/mesh-watcher.md`, playbook 07's *Following and reporting*: "the
|
||||
families a watcher may wait on are those the mesh granted the tool runner") say instead that `mesh_wait` hears any
|
||||
event by name (ADR 0670 §2, §4).
|
||||
|
||||
## Stream events
|
||||
|
||||
*By ADR 0671: the operator chose every module stream, mail too, on 2026-11-11.*
|
||||
|
||||
25. **The tool runner also hears every module stream, a restricted one included.** `buscompose.EveryEvent` (§1)
|
||||
gains a third subject, `mesh.stream.*.event.>`, after the two of §1, and the grants part's composition (§6) appends
|
||||
it with them. Nothing else is added for it: no durable consumer, no grant to acknowledge, no membership word, no
|
||||
publish, and no subscribe deny of a restricted stream (ADR 0671 §1). A restricted stream's grant value (ADR 0363
|
||||
§4) decides who consumes it, not who hears it, so changing or withdrawing that value changes nothing in the tool
|
||||
runner's grant (ADR 0671 §2); a module that consumes a restricted stream is composed as today (ADR 0376 §2 as ADR
|
||||
0671 §2 reads it). The bus does not hold the approval mark on a restricted stream; the tool runner's own gate does,
|
||||
before it subscribes for a wait (§32 to §37; the operator, 2026-11-11), and that gate is delivered in the same
|
||||
group as this grant (design 46 7-approval-mark §23). *Why:* ADR 0286 §8 has the bus hold a mark, and the bus
|
||||
cannot grant a subscription for one approved wait alone, so the operator chose the tool runner's gate.
|
||||
26. **A name hears its stream form.** `EventSubjects` (§1) answers six subjects, adding
|
||||
`mesh.stream.<emitter>.event.<rest>` and the same followed by `.>`; `from` (§13) takes `stream` too; `EventName`
|
||||
answers a stream subject with namespace `stream`. A seat's holder event kept in its holder's stream is named by its
|
||||
holder module (`mesh-delivery-checker.verdict`). The bound of §13 stays 16 names (96 subscriptions).
|
||||
27. **The tests hold it.** §8's test holds exactly the three subjects for the runtime carrying the tool runner, beside
|
||||
a fixture module that declares a restricted stream, and that no durable consumer and no subject to acknowledge on
|
||||
that stream are derived for the tool runner's runtime (ADR 0671 §1). §9's real-bus test gains cases: the tool
|
||||
runner's account, as composed, receives an event published on a fixture module's stream, and one published on a
|
||||
fixture restricted stream whose grant value has no entry for the tool runner, and still receives one after that
|
||||
value is changed and the accounts are composed again; it is refused a durable consumer and an acknowledgement on
|
||||
that restricted stream; and a module whose `consumes` names the restricted stream with no entry in its grant value
|
||||
gets no durable consumer and no read grant on it (ADR 0671 §1, §2).
|
||||
|
||||
## Delivery
|
||||
|
||||
28. **In G's delivery group, no order named beyond the module graph's.** The members are pushed and checked in design
|
||||
47 milestone G's group, with design 46's milestone 7, whose edges (design 46 7-approval-mark §23) order the gate
|
||||
before the seat definition that marks `execute`; this milestone adds no order of its own between its members, and no
|
||||
transitional field is kept (the operator, 2026-11-03: a change is designed as
|
||||
it should be, its delivery is the delivery system's): a part built with the new mesh-sdk reads a stored manifest of
|
||||
the tool runner that still carries `waits` without it (ADR 0578 §3), and a node-engine is given a want only for a
|
||||
manifest whose words it reads (ADR 0614 §4).
|
||||
29. **What `mesh_wait` answers between the deliveries.** On a node whose tool runner is new and whose grant is not yet
|
||||
composed, the subjects the old grant names are still heard and the rest are listed under `refused` (§16); on a
|
||||
node whose tool runner is older and whose membership the new grants part rewrote, the old runner answers at once
|
||||
that it was issued no event families. Both are loud. A watcher reads its owner after every answer (ADR 0367 §4) and
|
||||
ends at a call failed twice (its definition), so neither becomes a loop; a laptop asleep through the window gets
|
||||
its new tool runner when it wakes, and its watchers read their owners meanwhile in the same way.
|
||||
30. **Going back is going forward** (design 46 7-approval-mark §24 for the gate's part). A fault met after the delivery is fixed by a new change delivered the same way.
|
||||
Reverting the group instead reverts every member, the indexer first, since the manifest validator delivered here
|
||||
sets aside the old tool runner's manifest carrying `waits` (§10); until the revert reaches a node, `mesh_wait`
|
||||
there answers as §29 says.
|
||||
|
||||
## Nothing built ahead of the group
|
||||
|
||||
31. **No tool is built ahead of the group.** The extension of the nats module's `nats_streams` this line held, for
|
||||
§22's bus read, is not built: the secrets review reads the module catalog alone (§21, §22). *Why:* the operator,
|
||||
2026-11-11: "only the indexer uses the manifest to build the module catalog". *How it is checked:* the milestone's
|
||||
prepare names no member outside the group.
|
||||
|
||||
## A marked event waits for the operator's warrant
|
||||
|
||||
*By the operator's answers of 2026-11-11: "can it also respect any 'requires approval' config on the topics?", and
|
||||
"Build the gate in G".* These lines use the gate design 46 7-approval-mark builds for a marked call (its §8 to §19),
|
||||
delivered in the same group. *Why they depart from the records, said once for the section:* ADR 0286 §1 marks tools
|
||||
and verbs, and the operator extended the mark to a module's events and streams; ADR 0671 §1 and §3 have every session
|
||||
hear every module stream, mail included, with no exception, and the operator narrowed that to a wait the operator
|
||||
approves, for a marked event or a restricted stream.
|
||||
|
||||
32. **A module marks an event, and a restricted stream is marked whole.** A module's manifest `needs-approval` (ADR
|
||||
0286 §1) may also name one of its own events, by the name its `emits` or one of its `streams`' `events` gives it,
|
||||
with `*` and `**` as there. Every stream a module declares with a `grant` (ADR 0363 §4; `ModuleStream.Restricted`)
|
||||
is marked whole, every event it carries, with no entry in `needs-approval`; a stream without a `grant` is not, a
|
||||
work-queue one included. A seat's definition marks no event. The indexer's manifest validator refuses, as the
|
||||
checker runs it, a manifest whose `needs-approval` names an event that neither its `emits` nor its streams'
|
||||
`events` carry, naming the event, as ADR 0286 §4 refuses a mark on a tool the module does not serve (design 46
|
||||
7-approval-mark §4's table gains the row). A module that marks only events needs neither `operator-channel` in
|
||||
`uses` nor `approval-mark/1`: the tool runner asks for the wait, not the module.
|
||||
33. **The tool runner reads the marks from the catalog half on the bus.** The tool runner derives each module's marked
|
||||
subjects from the catalog half it holds (§14; the catalog graph entry carries `needs-approval` and each stream's
|
||||
`grant`, design 46 7-approval-mark §5): for a marked event `<event>` of module `<module>`,
|
||||
`mesh.mod.<module>.event.<event>` and `mesh.stream.<module>.event.<event>`, each event pattern as the bus spells it
|
||||
(`WirePattern`); for a stream with a `grant`, every subject of `ModuleStream.Subjects()`; and each of these also
|
||||
followed by `.>`, since an event may name its node or asker after its name (design 47 G §383) and
|
||||
`ModuleStream.Subjects()` answers only the patterns the stream stores. It derives them again on every revision it
|
||||
hears, and no module's name is written in its code; it is design 46 7-approval-mark §5's derivation, extended to
|
||||
events, following the running version as that line says. A mark added while a wait waits applies from the next
|
||||
call, which that call's answer says. *Why:*
|
||||
ADR 0286 §5 had the mark issued in a membership; no controller issues one now, a membership names no event (ADR
|
||||
0670 §2), and the catalog half, which the indexer alone builds from the manifests, keeps the mark out of any bundle
|
||||
as §5 required; from G the catalog half is where the tool runner reads which modules exist (design 47 G §196), and
|
||||
no part but the indexer reads a manifest (the operator, 2026-11-11).
|
||||
34. **Which wait is marked.** A wait is marked when any subject it subscribes (§13, §26, after `from`) overlaps a
|
||||
marked subject of §33, that is, some subject matches both, a wildcard on either side. mesh-sdk's `events.go` (§1)
|
||||
gains `Overlaps(a, b)` for it. Before the catalog half is read (`mesh_first_views`), a wait with any subject
|
||||
outside `mesh.seat.` waits, within its own timeout, for the first view and is then judged; nothing is asked while
|
||||
the marks are unread, and a wait still unjudged when its timeout ends is refused then, saying the marks are not
|
||||
yet read, since a mark that cannot be read runs nothing (ADR 0286 §40; design 46 7-approval-mark §19). A wait
|
||||
`from: seat` is never marked.
|
||||
35. **A marked wait runs only on the operator's warrant for that one wait.** The call is an act as design 46
|
||||
7-approval-mark §8 makes one: the address `mesh_wait`, the node, the module `node-tools` and the tool `mesh_wait`,
|
||||
its build (the digest of the tool runner's own artifact), and every
|
||||
argument given (`subjects`, `from`, `filter`, `timeout`), so the warrant binds the exact names, the filter and the
|
||||
bound. The tool runner refuses, asks, judges and claims it as that part's §9, §10, §11 and §13 to §17 do for a
|
||||
marked tool, asking on the seat `operator-channel` under its own module's name, `node-tools`, whose manifest gains
|
||||
`operator-channel` in `uses` for it (that part's §7). The ask's headline is "Wait on <names> on <node>?"
|
||||
(shortened as that part's §10 says), and its explanation names every event name one per line, the filter, and the
|
||||
bound as the timeout in minutes, then the act's fingerprint. On a warrant it accepts, it subscribes, and the wait's
|
||||
timeout runs from then. One warrant is one wait (that part's §15): a watcher looping on a marked event is approved
|
||||
call by call. Its answer carries the line naming the approval and the warrant is recorded through
|
||||
`mesh-hand-acts.warranted` (that part's §16), and `mesh_approvals` (that part's §18) lists the waits asked beside
|
||||
the calls. *Why:* the operator, 2026-11-11: a marked wait runs only on the operator's warrant for that one wait,
|
||||
shown with the exact names it waits on and its bound.
|
||||
36. **A refused marked wait is said, and never a hot loop.** A marked wait with no warrant the tool runner accepts
|
||||
subscribes to nothing. Its first call publishes the ask at once; every such call answers, as an error, design 46
|
||||
7-approval-mark §12's or §17's words with "this wait" for "the call", when its timeout ends or its caller goes, as
|
||||
§16 answers a refused subscription, or as soon as the router's record of its ask says it was chosen: the wait hears
|
||||
the warrant event `operator-channel.decided.node-tools` for its ask's id and then reads the record, an event only
|
||||
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. 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
|
||||
7-approval-mark §12's words after its timeout; a wait with the most timeout a wait takes (ten minutes, ADR 0367
|
||||
§1), its ask chosen at its second minute, answers at the choice, and the same call made then runs; the same wait
|
||||
with a warrant bound to its exact names, filter and timeout subscribes and answers the event published after it,
|
||||
so the restricted stream's event is heard only after the warrant; with a warrant for other names, another filter
|
||||
or another timeout it is refused; the same names `from: seat` ask nothing; a wait named `<module>.<event>.<node>`
|
||||
under a marked `<module>.<event>` is marked; and before the catalog half is read, a marked wait asks nothing, is
|
||||
judged when the first view comes within its timeout, and is refused at its timeout when it does not. The warrant is
|
||||
written on the test bus as the router's record. `Overlaps` gains rows in §1's table (`mesh.stream.mail.event.mail.>` overlaps
|
||||
`mesh.stream.mail.event.mail.*.message`; `mesh.mod.mail.event.x` does not overlap `mesh.stream.mail.event.x`), and
|
||||
§32's refusal a row in the indexer's `judge/rules_test.go`.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- **A watcher wakes on an event no manifest names** (the operator's acceptance; live, read by tool call): at the first
|
||||
delivery after every node holds the new tool runner, a watcher's `mesh_wait` on `mesh-delivery-view.delivered`
|
||||
answers the status view's `delivered`, its `at` less than a second after its `published`, with no rereading on a
|
||||
timer; the tool runner (`node-tools`) is delivered to every node, and the grants part and the indexer to theirs,
|
||||
read with `mesh-delivery.show` and each node's `node-engine.composition` (§6, §13, §15).
|
||||
- **The grant is exactly what the record names** (unit, and integration on a real bus): the composition test of §8
|
||||
and the real-bus cases of §9 pass.
|
||||
- **The list is gone** (unit): the manifest validator as the checker runs it refuses a manifest carrying `waits`, the
|
||||
indexer sets it aside, and the tool runner's manifest is accepted without it (§2, §10, §11).
|
||||
- **`mesh_wait` refuses loudly and never loops** (unit): §19's table of refusals passes.
|
||||
- **No event carries a secret** (the code review): the milestone file's *Events and their fields* is complete and the
|
||||
code review's confirmation at the group's heads is written in it before the grants part is delivered (§21, §22).
|
||||
- **An answer is never taken from an event** (the code review, and unit): in the members nothing reads an operator's
|
||||
answer from a `decided` event's body, and `mesh_wait`'s answer for one says to read it with `claude_code_answer`
|
||||
(§15).
|
||||
- **A stream event wakes a watcher** (live, read by tool call): a watcher's `mesh_wait` on
|
||||
`mesh-delivery-checker.verdict` wakes on the checker's next verdict (§25, §26; ADR 0671 §1).
|
||||
- **The grant does not follow a restricted stream's grant value** (integration on a real bus): §27's cases pass
|
||||
(ADR 0671 §2).
|
||||
- **A marked wait runs only on the operator's warrant** (unit): §37's rows pass.
|
||||
- **A wait on mail, as the operator sees it** (live, read by tool call and on the phone): a session's `mesh_wait` on
|
||||
`mesh-mail.mail`, the mail module's restricted stream, asks "Wait on mesh-mail.mail on <node>?" on the phone with
|
||||
its bound, and hears nothing before; it ends refused when its timeout ends or as soon as the operator chooses; the
|
||||
same call made after the Approve hears the next mail, and the next call asks again (§32 to §36).
|
||||
@@ -1,9 +1,21 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: implemented
|
||||
code: [mesh-tools, mesh-host, mesh-controller]
|
||||
updated: 2026-10-17
|
||||
status: in-progress
|
||||
code: [mesh-tools, mesh-host, mesh-controller, mesh-sdk, mesh-delivery, mesh-catalog modules/claude-code]
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0670"
|
||||
- "ADR 0671"
|
||||
- "ADR 0286"
|
||||
- "ADR 0043"
|
||||
- "ADR 0259"
|
||||
- "ADR 0332"
|
||||
- "ADR 0571"
|
||||
- "ADR 0363"
|
||||
- "ADR 0376"
|
||||
- "ADR 0489"
|
||||
- "ADR 0578"
|
||||
- "ADR 0614"
|
||||
- "ADR 0197"
|
||||
- "ADR 0195"
|
||||
- "ADR 0175"
|
||||
@@ -119,6 +131,64 @@ listed once per machine does not hold on the live mesh MCP server — postgres a
|
||||
optional, answered by whichever instance replies. The address replaces that statement rather than
|
||||
repairing it.
|
||||
|
||||
## 3b. Waiting on the bus
|
||||
|
||||
*By ADR 0367, as ADR 0670 and ADR 0671 change it.* Beside the five discovery tools of §3a and `mesh_runtimes`, the mesh MCP
|
||||
server offers a seventh, `mesh_wait`: a session names one or more events, an optional `filter` of body fields and a
|
||||
`timeout` (four minutes when absent, ten at most), and is answered the first matching event or told it timed out
|
||||
(ADR 0367 §1). A session calls it in a loop from a background command (ADR 0367 §3), and after every answer or time-out
|
||||
reads the state once from the verb that owns it: an event only wakes it (ADR 0367 §4).
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
E[a seat's, a module's or a module stream's event<br/>mesh.seat.<seat>.event.… / mesh.mod.<module>.event.… / mesh.stream.<module>.event.…] -->|heard for the call| TR[the tool runner's account]
|
||||
G[seat mesh-grants] -->|grants the three event subjects| TR
|
||||
M[the catalog half, seat mesh-catalog] -->|the marked events and restricted streams| TR
|
||||
TR -->|a marked wait only on the operator's warrant| OC[seat operator-channel]
|
||||
TR -->|mesh_wait answers it by name| S[a session]
|
||||
S -->|then reads the state| O[the verb that owns it]
|
||||
```
|
||||
|
||||
**The tool runner hears every event of the mesh, and no list says which** (ADR 0670 §1, §2). The grants part (seat
|
||||
`mesh-grants`) grants the account of the runtime carrying the tool runner a plain subscription on the event subjects
|
||||
of every seat and every module, and on nothing else of theirs: no accept, proof, call, work queue or bucket, and no
|
||||
publish. `mesh_wait` subscribes when called and closes the subscription when it answers, so the account keeps no
|
||||
durable consumer and no queue of its own (kept from ADR 0367 §2), and any event name is accepted: a new event is
|
||||
heard from its first publication, with no change to a manifest. The tool runner's manifest names no events, the
|
||||
manifest validator has no rule about them, and no membership lists them. A name whose emitter no seat and no module
|
||||
bears is refused, so a misspelt wait does not run for ever.
|
||||
|
||||
**Who hears.** Whoever may call the mesh MCP server: any process on the machine, through its loopback endpoint (§2),
|
||||
which is the session there. The tool runner's standing grant of every event is accepted under ADR 0670 §1: what a
|
||||
session hears through it, it may hear.
|
||||
|
||||
**Module streams too, mail included** (ADR 0671 §1, §2). The tool runner also hears the events kept in a module's own
|
||||
stream (ADR 0363 §2), the seat holders' events published there among them, a restricted stream such as mail's
|
||||
included. A restricted stream's grant value decides who consumes it, never who hears it, so the tool runner's hearing
|
||||
needs no `consumes` and no entry in that value, and keeps no consumer of it.
|
||||
|
||||
**A marked event is heard only on the operator's warrant** (the operator, 2026-11-11). The approval mark (ADR 0286) covers a module's events and streams as well as its tools and
|
||||
verbs: a module may mark one of its events in `needs-approval`, and a restricted stream is marked whole. The tool
|
||||
runner reads the marks from the catalog half on the bus (seat `mesh-catalog`), and a `mesh_wait` on a marked event, or on any subject
|
||||
of a marked stream, runs only on the operator's warrant for that one wait, asked on the seat `operator-channel` with
|
||||
the exact names it waits on and its bound, and checked before the tool runner subscribes; without one it is refused
|
||||
and says so. The bus grants the subjects, and the tool runner's own gate holds the mark, as the operator chose; the
|
||||
gate is design 46's milestone 7, built in design 47's milestone G and delivered in one group with G and milestone
|
||||
every-event, so the subjects and the gate reach the machines together. This departs from ADR 0286 §1 and §8 (tools and
|
||||
verbs only, held by the bus) and from ADR 0671 §1 and §3 (every module stream heard with no exception) by the
|
||||
operator's answer. An unmarked event is heard with no ask.
|
||||
|
||||
**What that makes audible, and what still bounds it.** Every session now hears the operator channel's `decided` of
|
||||
every asker and the intake bench's events. So no event of a seat or a module carries a secret: no credential, token or
|
||||
code the mesh itself issues or keeps, and no message marked secret, which the code review asks of every change that
|
||||
adds an event or a field (ADR 0670 §4 as ADR 0671 §3 narrows it). A mail's own content is the operator's mail and is
|
||||
heard as it is, a sign-in link or a code someone sent by mail among it (ADR 0671 §3). And a heard event never answers anything: the operator's answer to an ask is read only
|
||||
with `claude_code_answer`, from the router's record and held to the question's digest (ADR 0670 §5, ADR 0332 §5,
|
||||
§6), and an asker acts only on a warrant from its own `decided` subject through its own consumer (ADR 0259 §6).
|
||||
|
||||
What is built for this, line by line, is milestone every-event's part,
|
||||
[`34-milestones/every-event.md`](34-milestones/every-event.md).
|
||||
|
||||
## 4. Where it runs
|
||||
|
||||
On whichever machines an operator sits at, by assignment. It is not on the control node by default and
|
||||
@@ -145,6 +215,14 @@ address gets a refused connection, which is the truthful answer.
|
||||
| a call through the mesh MCP server's endpoint reaches a module over the bus and the answer is the module's own, unshaped | ADR 0035, a surface decides nothing |
|
||||
| on the live mesh: the mesh MCP server assigned to a workstation answers `tools/list` on loopback and a call to the forge returns repositories | the exit of work-order step 3 |
|
||||
| the composed filter for a machine carrying the mesh MCP server opens no port for it | ADR 0144 |
|
||||
| on a real bus, `mesh_wait` answers the first matching event within a second of its publication, and after every answer a session reads the owner's state | ADR 0367 §1, §4 |
|
||||
| live: a watcher's `mesh_wait` on the status view's `delivered`, an event no manifest names, wakes on it with no rereading on a timer | ADR 0670 §2, milestone every-event |
|
||||
| a composition test: the tool runner's runtime account is granted exactly `mesh.seat.*.event.>`, `mesh.mod.*.event.>` and `mesh.stream.*.event.>` beside what its carried modules declare, with no publish on another's events, no durable consumer for them and no wider subject; on a real bus it is refused a publish on an event subject, and another module's account a subscription on an event it does not consume | ADR 0670 §1, §3; ADR 0671 §1 |
|
||||
| on a real bus, the tool runner's account hears a restricted stream's event with no entry in its grant value, still after the value changes, and is refused a durable consumer and an acknowledgement on it | ADR 0671 §1, §2 |
|
||||
| the tool runner's gate refuses a marked wait with no warrant and accepts one with a warrant bound to its exact names, filter and timeout; on the test bus a restricted stream's event is heard only after the warrant | the operator, 2026-11-11; ADR 0286 |
|
||||
| the manifest validator, as the checker runs it, refuses a manifest that carries `waits` as an unknown field, and accepts the tool runner's without it | ADR 0670 §2 |
|
||||
| the milestone's prepare lists every event with its body's fields, and its code review confirms none carries a secret before the grant is delivered; every later change adding an event or a field is asked the same in its review | ADR 0670 §4 |
|
||||
| nothing takes an operator's answer from a `decided` event's body; `claude_code_answer` is its one reader | ADR 0670 §5 |
|
||||
|
||||
## What shipped, 2026-09-30
|
||||
|
||||
@@ -163,6 +241,15 @@ on the live mesh; and a module registered from the catalog by hand recorded its
|
||||
than as a path on the git seat, because `--self` takes the forge path form — the rebuild-on-merge still
|
||||
matched it by URL.
|
||||
|
||||
## Milestones
|
||||
|
||||
| Milestone | What it builds | State |
|
||||
|---|---|---|
|
||||
| the mesh MCP server | §1 to §5: the module, its loopback endpoint, its discovery (ADR 0152) | delivered 2026-09-30 (above) |
|
||||
| found by address | §3a: five announced tools and addresses (ADR 0195, ADR 0197) | delivered; [as-is 13](../00-as-is/13-the-console.md) |
|
||||
| waiting on the bus | §3b's `mesh_wait` on a list of five families in the tool runner's manifest (ADR 0367) | delivered (issue 745); [as-is 13](../00-as-is/13-the-console.md) |
|
||||
| every-event | §3b as ADR 0670 and ADR 0671 change it: the tool runner hears every event, module streams included, a marked one on the operator's warrant, and the list goes; one delivery group of mesh-sdk, mesh-delivery, mesh-tools and mesh-catalog, with hq | designed; its part is [`34-milestones/every-event.md`](34-milestones/every-event.md) |
|
||||
|
||||
## What this does not settle
|
||||
|
||||
- **The mesh's own verbs on the bus.** Design 33's third family: these now answer directly, through
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
---
|
||||
layer: to-be
|
||||
design: 46
|
||||
milestone: 7-approval-mark
|
||||
updated: 2026-11-11
|
||||
code: [mesh-sdk, mesh-contracts, mesh-delivery, mesh-tools, mesh-catalog modules/zsh, mesh-catalog modules/gitea]
|
||||
decisions:
|
||||
- "ADR 0286"
|
||||
- "ADR 0259"
|
||||
- "ADR 0627"
|
||||
- "ADR 0656"
|
||||
- "ADR 0666"
|
||||
- "ADR 0668"
|
||||
- "ADR 0595"
|
||||
- "ADR 0597"
|
||||
- "ADR 0310"
|
||||
- "ADR 0268"
|
||||
- "ADR 0564"
|
||||
---
|
||||
|
||||
# Design 46, milestone 7 — a call that needs the operator's approval
|
||||
|
||||
**What it builds.** A tool or a seat verb marked in its manifest or its seat's definition runs only on the operator's
|
||||
approval of that one call, with its exact arguments (ADR 0286). The first call asks the operator on the seat
|
||||
`operator-channel` and runs nothing; the same call made again within 30 minutes of the operator's Approve runs once.
|
||||
The tool runner (module `node-tools`) asks, judges and claims; it reads the marks from the catalog half on the bus; and
|
||||
on a machine whose tool runner does not honour the mark the grants part withholds every marked subject. The shape is in
|
||||
the design's main file,
|
||||
[§10, *A call that needs the operator's approval*](../46-the-conversation-with-the-operator.md#a-call-that-needs-the-operators-approval);
|
||||
this part holds every line the milestone builds, cited as "design 46 7-approval-mark §n".
|
||||
|
||||
**When.** It is built and delivered in design 47's milestone G, in G's delivery group, with design 34's milestone
|
||||
every-event, whose marked waits (design 34 every-event §32 to §37) use this gate (the operator, 2026-11-11: "Build the
|
||||
gate in G").
|
||||
|
||||
**Where.** mesh-sdk (the manifest field, the seat definition's field, the per-machine state's keys), mesh-contracts
|
||||
(the login shell's seat definition), mesh-delivery (the indexer's manifest validator and catalog graph entry, the grants
|
||||
part's deny), mesh-tools (the tool runner's gate, its state, its tools, and the mesh MCP server's `mesh_describe` in
|
||||
it) and mesh-catalog (the zsh module, which holds the login shell, and the forge module, §21). Nothing is built in a controller: no controller issues memberships today, bus permissions are the grants part's
|
||||
(seat `mesh-grants`), the hand-act log is the seat `mesh-hand-acts`, and the asker's logic is operator-channel's client
|
||||
in mesh-contracts (ADR 0627 §1).
|
||||
|
||||
**What changes from ADR 0286 as written.** Each line says in its *Why* where something built since ADR 0286
|
||||
(2026-10-10) changes how a clause is met: the mark is read from the catalog half and not from a membership (§5), the
|
||||
digest is Go alone (§8), the claim is kept in the runner's bus state and not a file (§13), `warranted` is a kept call
|
||||
and not a retry on a timer (§16), and a fourth open ask is offered as G makes it (§17). The controller's own verbs (ADR
|
||||
0286 §43) are not built: no controller serves verbs.
|
||||
|
||||
**The bound of the gate, a known limit.** The gate holds for every call that reaches the tool runner's handler. It does
|
||||
not hold for a process that reads the tool runner's bus credential (the file `broker` in its state directory,
|
||||
`own-secrets`) and speaks to the bus itself: that account may subscribe a marked subject, a restricted stream's among
|
||||
them, on a machine whose runner honours the mark. Read on the workstation on 2026-11-11 with `node-reads.stat`, the
|
||||
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: 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.
|
||||
|
||||
## The mark
|
||||
|
||||
1. **A module marks its tools and verbs in `needs-approval`.** mesh-sdk's `catalogue.Manifest` gains `NeedsApproval`,
|
||||
the manifest key `needs-approval`: a list of strings, each a tool the module serves (`<tool>`) or a seat verb it
|
||||
serves (`<seat>.<verb>`), and, under design 34 every-event §32, one of its events. Absent is no mark. *Why:* ADR
|
||||
0286 §1, §49 (each marking is a manifest change, reviewed like any other). *How it is checked:* unit, a row in the
|
||||
decoder's table test in `go/catalogue`; the manifest reads baseline gains `word:needs-approval`.
|
||||
2. **A seat's definition marks a verb for every holder.** mesh-sdk's `seatdef` reads a verb's `needs-approval`, a
|
||||
boolean, in a seat definition (`seats/<seat>/seat.json` in mesh-contracts); the seat definitions the catalog half
|
||||
carries (design 47 G §194) carry it on the verb. *Why:* ADR 0286 §2. *How it is checked:* unit, a fixture row in
|
||||
`seatdef`'s table test.
|
||||
3. **Marks add up.** A verb is marked when its seat's definition or its holder's `needs-approval` marks it; a holder
|
||||
cannot unmark what its seat marks. *Why:* ADR 0286 §3. *How it is checked:* unit, a row of §5's table.
|
||||
4. **The manifest validator refuses a mark it cannot honour.** The indexer's manifest validator (`judge/rules.go`)
|
||||
refuses, as the checker runs it, and sets aside to wait, as the indexer runs it, a manifest that: marks a tool its
|
||||
`tools` does not list, or a verb its `claims` do not serve (ADR 0286 §4); marks a tool or a verb, or holds a seat
|
||||
verb its seat marks, without `operator-channel` in `uses` (§7); marks a tool or a verb while every artifact of kind
|
||||
`bundle` it builds is run by one of its `process` resources (`artifact`), so its own program answers its tools and
|
||||
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: a bundle whose marks name a tool it does not
|
||||
register is not loaded, so no tool of that module is served on that node at all, 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
|
||||
under ADR 0286 §4, §40, the refusal kept by lead-47 on 2026-11-11). *How it is checked:* unit, one row per refusal
|
||||
in `judge/rules_test.go`, rows that accept the zsh and forge manifests of §21, and a row of §22's mark table for
|
||||
`mark-unserved`.
|
||||
|
||||
## Where the marks are read, and where they are withheld
|
||||
|
||||
5. **From the catalog half on the bus, never from a bundle.** The indexer's catalog graph entry of a module (design 47
|
||||
G §174) gains its `needs-approval`, its `emits`, and for each stream it declares its events and whether it carries a
|
||||
`grant`; no part but the indexer reads a manifest (the operator, 2026-11-11: "only the indexer uses the manifest to
|
||||
build the module catalog"); the seat
|
||||
definitions of the catalog half carry the verb marks (§2). The tool runner, which from G takes its first view of the
|
||||
catalog half by `mesh-catalog.revision` and follows `catalog-revision` (design 47 G §196), derives from it, for each
|
||||
module it serves, its marked tools and verbs, in a package of its own, `node-tools/internal/approval`, again on
|
||||
every revision it hears. No module's name is written in its code. The marks follow the running version: the runner
|
||||
applies the marks of the module's version it serves, and, while the newest revision's entry for that module
|
||||
differs, the union of both, so a mark is never lost before or after the module moves. A mark added while a call
|
||||
waits applies from the next call, which the answer of §12 says. *Why:* ADR 0286 §5 had the controller issue the mark
|
||||
in a membership so that a bundle could not drop it; no controller issues memberships now, and the judged catalog
|
||||
half is the same guarantee: the bundle never carries the mark (ADR 0656 §6, one source for which modules exist).
|
||||
The union keeps ADR 0286 §3's "marks add up" across a version change. *How it is checked:* unit, a table test in
|
||||
`approval` derives the marks from fixture catalog halves, §3's row, a version change and the union among them.
|
||||
6. **A machine whose tool runner does not honour the mark serves no marked tool or verb.** The tool runner states
|
||||
`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`, 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, the marks read from the catalog half as §5 has them:
|
||||
`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. The tool runner's version on a node is read from the
|
||||
same assignment half revision, its entry for `node-tools` on that node (the module version placed there, design 47
|
||||
G §175), against the version the node-engine's `running.<node>.node-tools` last said running (design 47 G §309);
|
||||
readings that revision names from before that version ran are older. 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. 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
|
||||
`mesh.seat.operator-channel.event.decided.<module>` and the read of the router's record under `<module>.` in the
|
||||
seat's state `asks`, as for every user (ADR 0259 §3). The runner passes the client its own publish and read on that
|
||||
runtime's account (`Client.Publish`, `Client.Record`), never the bundle's. *Why:* ADR 0286 §6. *How it is checked:*
|
||||
unit, a row in the grants part's composition table for a fixture module with a mark.
|
||||
|
||||
## The act, and the first call
|
||||
|
||||
8. **The act is digested with operator-channel's client.** For a call of a marked tool or verb, the tool runner
|
||||
builds an `operatorchannelclient.Act` with `address` (as called), `node` (the node that serves it), `module` and
|
||||
`tool`, or `seat` and `verb`, `build` (the digest of the bundle it serves the module from), and `arg.<name>` for each
|
||||
argument given, a string as itself and any other value as its JSON in RFC 8785's canonical form; an argument left
|
||||
out is not in the act. Its digest is `operatorchannelclient.ActDigest`. *Why:* ADR 0286 §10, §11; the digest is in
|
||||
mesh-contracts since ADR 0627 §1. The TypeScript twin ADR 0286 named is not built: the tool runner is Go, and no
|
||||
TypeScript asker computes an act. *How it is checked:* unit, rows in `approval` for the act's fields, an omitted
|
||||
argument, a number, an object and a changed build each giving another digest.
|
||||
9. **A call refused without asking.** On a call with no approval for its act, the runner refuses at once, asking
|
||||
nothing, a call whose arguments hold anything `outward.CheckSecret` refuses (mesh-sdk), or whose arguments are more
|
||||
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, 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 of its own `State` it writes and reads (empty for
|
||||
every asker today), is `<node>.`; never before the router's record it reads through `Record` and `Where`, whose key
|
||||
stays `<module>.<id>` in the seat state `asks`; 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>?",
|
||||
"Run <tool>?" when that is longer than the router allows, ending "Part shown" whenever an argument is not shown
|
||||
whole; the explanation: the first line of the tool's own description, the node, that it was called through the
|
||||
mesh's tools, which cannot tell who called, every argument one per line as "name: value", and the act's
|
||||
fingerprint, its digest's first 24 hexadecimal digits in groups of four. *Why:* ADR 0286 §13, §15, §16, §20 to
|
||||
§23; the client checks the ask (`Check`) before it publishes. *How it is checked:* unit, a row in `approval` holds
|
||||
the published ask's fields against a fixture call, and a second identical call publishes nothing.
|
||||
11. **An argument is shown whole only where it may leave the mesh.** Each argument is shown whole where
|
||||
`outward.Check` passes it, else in its shape with ‹path›, ‹address› or ‹withheld› in place of what it refuses; the
|
||||
arguments are shown whole when they are at most 2000 bytes and 40 lines, otherwise their first 20 lines and 1200
|
||||
bytes, marked "NOT SHOWN IN FULL", and the explanation then says that the whole is read with `mesh_approvals`
|
||||
(§18) on that node, whose fingerprint must be the one on the phone. *Why:* ADR 0286 §17 to §19. *How it is
|
||||
checked:* unit, rows of §10's test with a path, an address and a long argument.
|
||||
12. **The first call answers "Not run", or that the router has not taken it.** The runner waits up to 5 seconds for
|
||||
the router's record of the ask (§14) and answers the call as an error, never as a result. With the record: "Not
|
||||
run: this needs the operator's approval. Asked on <channel> at <time> (ask <id>, fingerprint …); make the same call
|
||||
again within 30 minutes of the approval; the ask expires at <time>." With no record within the 5 seconds: "Not
|
||||
run, asked; not yet taken by the router (ask <id>, fingerprint …); make the same call again later." The ask stays
|
||||
in the seat's stream of accepts, a work queue the router takes it from when it runs, and the `act.` key stays, so
|
||||
the next call reads the router's record of the same ask and asks nothing new. A mark added since the caller last
|
||||
called is named in the answer ("marked since <revision>"). *Why:* ADR 0286 §14, §37; the caller calls again, as
|
||||
the operator chose on 2026-11-11 ("Call again (as designed)") over a held call that runs on the Approve and returns
|
||||
its outcome (issue 417, 2026-10-16). *How it is checked:* unit,
|
||||
rows of §22's table hold both answers; a row with no router holds that a second call asks nothing new.
|
||||
|
||||
## The runner's record, and the same call again
|
||||
|
||||
13. **The runner keeps its asks and claims in a state of its own, per machine.** The tool runner's manifest asks in
|
||||
`requires` for a state `approvals`, template `state`, `per-machine`, `ttl-seconds` 172,800 (48 hours), cap 64 MiB.
|
||||
Its keys: `<node>.ask.<id>`, the asked call as the client keeps it (the ask, the act, the whole arguments, when
|
||||
asked, whether acted on, and `recorded` once `warranted` answered, §16), and `<node>.act.<digest>`, the id of the
|
||||
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, 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, saying that the fill and the cap are every machine's together. 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; this is written only while the runner runs, so a machine asleep through that hour keeps
|
||||
only the `call-unanswered` history of §16, which is accepted.
|
||||
*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 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.`
|
||||
names an ask, the runner reads the router's own record of that ask under the module's name (`Client.Where`, the
|
||||
seat state `asks`, key `<module>.<id>`), never a record of its own or the caller's; it composes the ask again from
|
||||
the act and holds the warrant to it with `operatorchannelclient.For` (the same asker and ask, the ask's digest, a
|
||||
person's choice, an option offered, at its level, before it expired); it requires the option to be `approve` and
|
||||
the act to be the one it bound (`operatorchannelclient.Performs`: the same node, build and every argument); and it
|
||||
requires the call within 30 minutes of the choice. It judges the newest ask it keeps for the act. *Why:* ADR 0286
|
||||
§26 to §29, §32. *How it is checked:* unit, rows of §22's table: approved, approved for other arguments, approved
|
||||
31 minutes ago, declined, expired, another asker's warrant.
|
||||
15. **One approval runs one call.** Before it runs, the runner marks `<node>.ask.<id>` acted on by compare-and-set
|
||||
(`Client.Take`); a warrant marked before is refused, so one approval runs one call however often the call is made,
|
||||
and a warrant replayed onto the bus runs nothing. After "already run", "expired" or "approved too long ago" the next
|
||||
identical call asks anew, and the used mark stays. *Why:* ADR 0286 §30, §31, §33, §36. *How it is checked:* unit, a
|
||||
row of §22's table makes the approved call twice and runs it once; two calls at the same moment run it once.
|
||||
16. **The call runs, and its warrant is recorded, across a restart too.** Once the checks pass, the runner runs the
|
||||
call and answers with the tool's own answer and a line naming the approval ("run on the operator's approval via
|
||||
<channel> at <time>, ask <id>"). It then calls `mesh-hand-acts.warranted` with `asker` (the module) and `ask` (the
|
||||
id) as a kept call (ADR 0668 §1, §2: the verb takes it once per ask), and on its answer writes `recorded` on
|
||||
`<node>.ask.<id>`. At each start the runner calls `warranted` again for every acted, unrecorded ask its keys hold,
|
||||
within their 48 hours, each named in its `findings-stated`'s `pending` until it is made (design 47 G §384). One
|
||||
unanswered after five minutes is the runner's finding `<node>.<ask id>.call-unanswered` in its state `findings`,
|
||||
said on its stream `node-tools-findings` (design 47 G §383, §387), which the condition keeper hears; it clears when
|
||||
`warranted` answers. *Why:* ADR 0286 §34, §35; the hand-act log is the seat `mesh-hand-acts`, whose `warranted`
|
||||
reads the router's own record, and its "again every ten minutes for a week" is ADR 0668's kept call now, kept
|
||||
across a restart by the `recorded` mark (ADR 0668 §3, ADR 0564). *How it is checked:* integration, §22's whole test
|
||||
reads the hand-act recorded once, also when the runner is restarted after the run and before `warranted` answers.
|
||||
17. **What a later call is told.** For the same call made again before it can run, the caller is told, as an error:
|
||||
still waiting; declined (the same act is not asked again for 10 minutes, and the answer says so); expired,
|
||||
approved too long ago or already run (each asks again, §15); not yet taken by the router (§12); or refused, with the
|
||||
router's words. An ask the router holds back as the asker's fourth open one is still waiting, and
|
||||
`operator-channel.asks` lists it (ADR 0666 §12, as G builds it). *Why:* ADR 0286 §38, §39; ADR 0286 §40's refusal
|
||||
of a fourth open ask was the router's before G, which now offers a fourth and holds the oldest back. *How it is
|
||||
checked:* unit, rows of §22's table.
|
||||
18. **The tools read and steer what was asked.** The mesh MCP server on each machine gains the tool `mesh_approvals`:
|
||||
with no argument, the calls asked on that machine in the last day, newest first, each with its state and
|
||||
fingerprint, read from §13's state, and the marks it applies, each module's marked tools and verbs with their
|
||||
source (its module's entry in the catalog half or its seat's definition) and the catalog revision read; given an ask's id, its whole
|
||||
arguments and the act's digest; given `cancel` and an ask's id, it takes that open ask back (`Client.Cancel`),
|
||||
recorded first in the hand-act log through `mesh-hand-acts.hand-act` with who and why, and refused when the record
|
||||
cannot be written. `mesh_describe` answers, for a marked tool or verb, `needs-approval` with its source. *Why:*
|
||||
ADR 0286 §25 (the runner's `approvals`), named as the mesh MCP server's own tools are; a tool reads everything a
|
||||
module holds and does by hand what it does by itself (the operator's instruction of 2026-10-25). *How it is
|
||||
checked:* unit, rows in the mesh MCP server's tool table test: the list, the marks with their sources, an ask read
|
||||
whole, a cancel recorded and one refused without a record, and `mesh_describe` of a marked tool.
|
||||
|
||||
## Failing closed
|
||||
|
||||
19. **Nothing runs when a part of the check is missing, and a stuck gate is said.** A call of a marked tool is
|
||||
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, 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 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 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.
|
||||
|
||||
## Marked first
|
||||
|
||||
21. **`node-login-shell.execute` is marked in its seat's definition.** mesh-contracts' `seats/node-login-shell/seat.json`
|
||||
marks the verb `execute` with `needs-approval`; the zsh module's manifest (mesh-catalog `modules/zsh`), which holds
|
||||
the seat, gains `operator-channel` in `uses`. The control-node keeps withholding `execute`, and whether it serves
|
||||
it again under the mark stays the operator's setting. *Why:* ADR 0286 §44, §45: the reads it carried have their
|
||||
tools on every node (the seat `node-reads`, ADR 0310; issue 418 resolved 2026-10-12, answering on every node since
|
||||
2026-10-16). *How it is checked:* live, as the acceptance says.
|
||||
- **The forge's two acts on a trunk's protection are marked.** The forge module (mesh-catalog `modules/gitea`)
|
||||
gains a tool `gitea_branch_protection_loosen`, marked in its `needs-approval`, which alone performs the two acts
|
||||
`gitea_branch_protection_set_full` refuses today: removing a trunk's protection, and allowing a force-push onto
|
||||
a trunk; `gitea_branch_protection_set_full` keeps refusing both and its refusal names the new tool. The manifest
|
||||
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; the operator, 2026-11-11: "Mark them in G". The
|
||||
principle: loosening a trunk's protection runs only on the operator's own warrant for that one act, never on a
|
||||
session's word. Its bound: a process that reads the forge module's own credential speaks to the forge directly
|
||||
and goes around it, the same known limit as issue 1966 note 193; the prepare reads that credential's mode and
|
||||
owner on the forge's node with `<node>/node-reads.stat` and writes them in the milestone file. *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
|
||||
|
||||
22. **The tests.** The tool runner's tests in `node-tools/internal/approval` are one table-driven unit test of the
|
||||
refusals and answers (§9, §12, §14, §15, §17, §19) and one of the marks (§5), on the tool runner's test bus
|
||||
(`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. The client's `KeyPrefix` (§10) has a row in mesh-contracts'
|
||||
client test: every key of its own `State` written and read carries it, an empty prefix writes today's keys, and
|
||||
with a prefix set `Where` still reads `<module>.<id>`. *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
|
||||
validator and catalog graph entry that read `needs-approval` before any manifest or seat definition carrying it (a
|
||||
reader edge, design 47 G §89); and the grants part that composes the deny of §6 before the seat definition marking
|
||||
`execute` (an edge of the seat definition's readers), so no machine serves `execute` unasked between the two.
|
||||
*Why:* the operator, 2026-11-11, "Build the gate in G"; the operator, 2026-11-03, a change is designed as it should
|
||||
be and its delivery is the delivery system's. *How it is checked:* the delivery plan names both edges.
|
||||
24. **Going back is going forward.** A fault met after the delivery is fixed by a new change delivered the same way.
|
||||
Reverting instead reverts G's whole group in the reverse of §23's order: the seat definition's mark first, then the
|
||||
grants part, then the validator. The `approvals` state is then declared by no running module: it is an orphan
|
||||
`orphans` lists, its keys aging out within 48 hours, dropped through ADR 0595's `drop` on the operator's word.
|
||||
*Why:* design 47 G's revert rule; ADR 0595 §4, §5. *How it is checked:* the delivery plan of a revert names the
|
||||
order.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- **A marked call is asked on the phone, does nothing, and runs once on the Approve** (live, read by tool call):
|
||||
`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
|
||||
`acceptance-approval-mark`, created by the acceptance through the forge's `gitea_create_repo` and kept afterwards,
|
||||
never deleted, with full protection set on its trunk by `gitea_branch_protection_set_full`,
|
||||
`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).
|
||||
- **A marked wait as the operator sees it** (live): design 34 every-event's acceptance of a wait on a mail event.
|
||||
- **The mark cannot be lost on the way** (unit): §5's and §8's tables pass; a bundle carries no mark.
|
||||
- **It fails closed** (unit): §19's rows pass.
|
||||
- **The validator refuses a mark nothing enforces** (unit): §4's rows pass.
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-controller, mesh-tools, mesh-sdk, mesh-catalog]
|
||||
updated: 2026-11-10
|
||||
code: [mesh-controller, mesh-tools, mesh-sdk, mesh-catalog, mesh-contracts, mesh-delivery]
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0668"
|
||||
- "ADR 0667"
|
||||
- "ADR 0666"
|
||||
- "ADR 0347"
|
||||
@@ -17,6 +18,11 @@ decisions:
|
||||
- "ADR 0253"
|
||||
- "ADR 0234"
|
||||
- "ADR 0227"
|
||||
- "ADR 0627"
|
||||
- "ADR 0597"
|
||||
- "ADR 0595"
|
||||
- "ADR 0310"
|
||||
- "ADR 0268"
|
||||
---
|
||||
|
||||
# 46 — The conversation with the operator
|
||||
@@ -561,44 +567,27 @@ said on the away channel, with its why.
|
||||
|
||||
### A call that needs the operator's approval
|
||||
|
||||
*Added 2026-10-10, ADR 0286.*
|
||||
A module's manifest marks a tool, or a seat verb it serves, as **`needs-approval`**; a seat's definition may mark
|
||||
a verb for every holder. The controller issues the mark in the runner's membership, never from the bundle, and
|
||||
withholds a marked tool from a runner that does not declare `approval-mark/1`, by leaving its subject out of that
|
||||
runner's bus permissions.
|
||||
*Added 2026-10-10, ADR 0286; amended 2026-11-11 to the mesh as built and as design 47's milestone G leaves it.*
|
||||
A module's manifest marks a tool, or a seat verb it serves, in **`needs-approval`**; a seat's definition may mark
|
||||
a verb for every holder. The tool runner reads the marks from the catalog half on the bus (seat `mesh-catalog`), never from
|
||||
the bundle, and on a machine whose tool runner does not state that it honours the mark the grants part withholds every
|
||||
marked subject from its account.
|
||||
|
||||
```
|
||||
agent ──call──► tool runner (serving node) ──ask, under the module's name──► router ──► Telegram
|
||||
agent ──call──► tool runner (serving node) ──ask, under the module's name──► seat operator-channel ──► Telegram
|
||||
▲ │ "Not run: asked, ask <id>" │ Approve
|
||||
│ ▼ ▼
|
||||
└──same call──► runner reads the router's record ◄────────────── warrant (decided.<module>)
|
||||
│ Warrant.For + Option.Performs on the act; within 30 min; claimed once
|
||||
│ For + Performs on the act; within 30 min; claimed once in its own state
|
||||
▼
|
||||
runs the call once ──► mesh-controller.warranted (hand-act log)
|
||||
runs the call once ──► seat mesh-hand-acts, warranted
|
||||
```
|
||||
|
||||
- **The act** is the address, the node, the module and tool, the bundle's build and every argument given, digested
|
||||
with the shared library's `ActDigest` (in Go and TypeScript, one set of vectors).
|
||||
- **The first call asks and does nothing.** One ask per act (its `about` is the act's digest): "Run <tool> on
|
||||
<node>?", the tool's first line, every argument whole where it may leave the mesh or in its shape with
|
||||
‹path›, ‹address› or ‹withheld› and "Part shown" (whole when at most 2000 bytes and 40 lines, else the
|
||||
first 20 lines and 1200 bytes), the fingerprint, and where the whole is read (the runner's
|
||||
`approvals` on that node). Approve and Decline, both at approve; expiry 60 minutes; never a default. The call
|
||||
answers as an error: "Not run", with the ask's id, that the call runs within 30 minutes of the approval, and
|
||||
when the ask expires.
|
||||
- **The same call again** runs once on Approve, read from the router's record, within 30 minutes of the choice,
|
||||
claimed in the node's own file (kept 48 hours, one writer per node); the runner judges the newest ask it keeps
|
||||
for the act, and after "already run", "expired" or "approved too long ago" the next identical call asks anew;
|
||||
then `warranted` records the hand-act. Anything else is
|
||||
refused and said: waiting, declined (not asked again for 10 minutes), expired, already run, or the router's
|
||||
refusal.
|
||||
- **Fail closed:** a secret's shape in an argument or more than 16 KiB of arguments, no operator channel, a
|
||||
fourth open ask (until design 47's milestone G is delivered, after which a fourth is offered and the oldest held
|
||||
back, §6), an unpublished ask, an unreadable router record, node file or mark: nothing runs.
|
||||
- **The controller** enforces the same mark on its own verbs, asking as `mesh-controller`.
|
||||
- **Marked first:** `node-login-shell.execute` on every node once the reads it carried have tools (issue 418).
|
||||
`mesh-delivery.release` waits until a warrant the controller holds passes the mark of a verb it calls (issue
|
||||
417), so a Release on a condition is not asked twice.
|
||||
The first call asks and does nothing; the same call made again within 30 minutes of the operator's Approve runs once;
|
||||
everything else is refused and said, and anything the check cannot read runs nothing. `node-login-shell.execute` is
|
||||
marked first. Every line of it is in milestone 7's part,
|
||||
[`46-milestones/7-approval-mark.md`](46-milestones/7-approval-mark.md), built and delivered with design 47's milestone
|
||||
G (the operator, 2026-11-11).
|
||||
|
||||
### The hand-act
|
||||
|
||||
@@ -718,7 +707,7 @@ Each phase ends at its own *done when*. Owning repositories from [`repos.md`](..
|
||||
| **5a — Asks on the bus** (ADR 0259) | the kinded bench, caller-named verbs, `proof` subjects and the records users read (controller); a bundle's seat traffic, compare-and-set state and its own account (tool runner); the asker's and channel's types (shared library); the router's asks, tokens, identities, judging, warrants, expiry and linking with its hour (messenger); `telegram` and `desk-channel`; the controller asking for its conditions' actions and acting on warrants | mesh-controller, mesh-tools, mesh-sdk, mesh-catalog | a held delivery released from the phone; the hand-act reads "the operator, via telegram, user id verified"; a desk Silence goes through an ask |
|
||||
| **5 — Authorise, with TOTP** | `authorises` in the verb table; the router's checks for the code; direct calls refused, break-glass; `retire approve` takes `expect`; the hand-act fields; the TOTP seed as the router's state, enrolled at a terminal with ten recovery codes; `factor recover`, `factor status`, the local-only break-glass enrolment; no P2 tier before recovery codes exist; optional key enrolment; the self-check probe for the away channel; agents' grants lose the authorising verbs | mesh-controller; mesh-catalog (agents' grants, the desk's code prompt) | one controller test per refusal passes; at the desk, approve needs a code; a recovery drill re-enrols with a recovery code, and is announced |
|
||||
| **6 — Telegram live** | the `telegram` module (built in 5a) with replies and forced-reply codes, under its own account on the control node; the operator's two bots configured; the watcher assigned off the control node; the dead-man ping | mesh-catalog | the self-check says the away channel carries every tier; rehearsals: an approve and a destroy answered on the phone, with the hand-acts read |
|
||||
| **7 — A call that needs approval** (ADR 0286) | `needs-approval` in the manifest and the seat list, issued in the membership, withheld from a runner without `approval-mark/1` in its bus permissions (controller); the act's digest in TypeScript beside Go, with shared vectors (shared library); the check, the ask, the runner's `approvals`, the node's used file and `warranted` (tool runner); the controller's own verbs; `execute` marked (the controller's seat list) once issue 418's reads exist | mesh-controller, mesh-sdk, mesh-tools, mesh-catalog | a marked call on a node is asked on the phone, does nothing, runs once when made again after Approve, is refused the third time and asked again, and `hand-acts` shows the warrant |
|
||||
| **7 — A call that needs approval** (ADR 0286), delivered with design 47's milestone G | the mark in the manifest and the seat definition, read by the tool runner from the bus; the manifest validator's refusals; the capability `approval-mark/1`; the act, the ask, the check, the claim in the runner's own state, `warranted` and `mesh_approvals`; `execute` marked. Its part is [`46-milestones/7-approval-mark.md`](46-milestones/7-approval-mark.md) | mesh-sdk, mesh-contracts, mesh-delivery, mesh-tools, mesh-catalog | its part's acceptance |
|
||||
| **8 — No ask hidden** (ADR 0666 §12), delivered with design 47's milestone G | a fourth ask offered and the asker's oldest held back, offered again when one ends; `asks` lists the held-back asks; the router's tool `ask-again`; the pipeline runner's `approval` asks and their `about` | mesh-catalog (the router) | a fourth ask and a declined approval each reach the operator; `asks` shows the held-back one |
|
||||
|
||||
**Switching phase 5a on: the operator's steps, in order** (amended 2026-10-09, ADR 0259). Each line is typed with
|
||||
|
||||
Reference in New Issue
Block a user