From 9b0c6741e7e5d5c9d8f43ef0bc7fe70a86115fad Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 10 Oct 2026 21:53:43 +0200 Subject: [PATCH] ADR 0286: a tool or verb marked in its manifest runs only on the operator's approval of that one call (issue 417) Graduates research 039. The tool runner (and the controller for its own verbs) asks the operator through the existing operator-channel asks and runs the exact call once on the warrant, reusing the act digest and warrant check of ADR 0259 and the shown-in-shape form of ADRs 0274 and 0277, so risky agent calls stop resting on the agent's restraint. Amends to-be 46 and hands it off. --- 00-META/glossary.md | 4 + .../00-overview.md | 6 +- ...wers-it-and-the-asker-acts-on-a-warrant.md | 5 + ...hat-cannot-become-root-without-a-person.md | 4 + ...l-a-call-to-it-needs-a-persons-approval.md | 5 + ...nly-where-no-agent-runs-as-its-operator.md | 3 + ...-that-one-call-with-its-exact-arguments.md | 298 ++++++++++++++++++ 03-DESIGN/00-as-is/13-the-console.md | 6 +- .../46-the-conversation-with-the-operator.md | 40 ++- 9 files changed, 366 insertions(+), 5 deletions(-) create mode 100644 02-DECISIONS/0286-a-tool-or-verb-marked-in-its-manifest-runs-only-on-the-operators-approval-of-that-one-call-with-its-exact-arguments.md diff --git a/00-META/glossary.md b/00-META/glossary.md index b3eac862..e49d56cd 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -527,6 +527,10 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272. effect only on the operator's warrant, bound to the exact thing shown: a shared instruction section (ADR 0274), a trusted setting's layer ([ADR 0277](../02-DECISIONS/0277-a-trusted-setting-is-proposed-through-a-verb-and-set-on-the-operators-warrant-and-a-modules-own-secret-is-asked-for-and-typed-at-the-desk.md)). A proposal changes nothing by itself; declined, expired or refused, it is discarded. +- **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](../02-DECISIONS/0286-a-tool-or-verb-marked-in-its-manifest-runs-only-on-the-operators-approval-of-that-one-call-with-its-exact-arguments.md)). - **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 diff --git a/01-RESEARCH/039-a-tool-that-needs-a-persons-approval/00-overview.md b/01-RESEARCH/039-a-tool-that-needs-a-persons-approval/00-overview.md index 1ca91419..ed45259e 100644 --- a/01-RESEARCH/039-a-tool-that-needs-a-persons-approval/00-overview.md +++ b/01-RESEARCH/039-a-tool-that-needs-a-persons-approval/00-overview.md @@ -1,8 +1,10 @@ --- -status: active +status: graduated initiated: 2026-10-08 touches: [] -became: [] +became: + - 02-DECISIONS/0286-a-tool-or-verb-marked-in-its-manifest-runs-only-on-the-operators-approval-of-that-one-call-with-its-exact-arguments.md + - 03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md --- # 039 — A tool or verb that needs a person's approval diff --git a/02-DECISIONS/0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md b/02-DECISIONS/0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md index e92ded70..195c7c3a 100644 --- a/02-DECISIONS/0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md +++ b/02-DECISIONS/0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md @@ -278,6 +278,11 @@ met, and when. It carries no secret. chose an act, not that only the operator could have caused it. Said here so no reader takes the warrant for that second guarantee. + > **The mechanism changed — 2026-10-10, by [ADR 0286](0286-a-tool-or-verb-marked-in-its-manifest-runs-only-on-the-operators-approval-of-that-one-call-with-its-exact-arguments.md).** A tool or verb marked `needs-approval` in its manifest now runs + > only on a warrant for that one call, with its exact arguments, checked by the tool runner that serves it (or + > by the controller, for its own verbs). For a marked act the warrant is the only way; for the others this + > bullet stands. `mesh-delivery.release` is among the first marked. + ### 7. Who the operator is on a channel: linking - **An answer from an account that is not the operator's** is refused, and the operator is told on the linked diff --git a/02-DECISIONS/0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md b/02-DECISIONS/0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md index 2efc5c0c..5ce0cf78 100644 --- a/02-DECISIONS/0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md +++ b/02-DECISIONS/0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md @@ -321,6 +321,10 @@ found around it and are closed on the mesh's main line: was not measured; where one does, this is also a way back to root on the control node. ADR 0268 §4 closes it once a call to such a tool needs a person's approval (research 039); until then, withholding `execute` on every node is the operator's choice, at the terminal. + + > **The mechanism changed — 2026-10-10, by [ADR 0286](0286-a-tool-or-verb-marked-in-its-manifest-runs-only-on-the-operators-approval-of-that-one-call-with-its-exact-arguments.md).** A call to a tool marked `needs-approval` runs only on the + > operator's warrant for that call. `execute` is marked as ADR 0268 §4 decided; the desk's two tools are split + > into a form that runs a command, which is marked, and one that does not, as issue 417 tracks. - **Tool servers already on the bus** keep rendering until their own change lands (issue 340, "still open"). Text written straight into the bus state needs a tool runner's credential, which the agent account cannot read. diff --git a/02-DECISIONS/0268-the-login-shells-execute-is-withheld-on-the-control-node-until-a-call-to-it-needs-a-persons-approval.md b/02-DECISIONS/0268-the-login-shells-execute-is-withheld-on-the-control-node-until-a-call-to-it-needs-a-persons-approval.md index 0a3d68a2..1e51355d 100644 --- a/02-DECISIONS/0268-the-login-shells-execute-is-withheld-on-the-control-node-until-a-call-to-it-needs-a-persons-approval.md +++ b/02-DECISIONS/0268-the-login-shells-execute-is-withheld-on-the-control-node-until-a-call-to-it-needs-a-persons-approval.md @@ -79,6 +79,11 @@ Option 4. or verb declares that a call needs a person's approval, and the mesh refuses the call without a warrant from ADR 0259's flow. The control-node may then serve `execute` again, under the mark. The other nodes keep it unmarked until then. + + > **The mechanism changed — 2026-10-10, by [ADR 0286](0286-a-tool-or-verb-marked-in-its-manifest-runs-only-on-the-operators-approval-of-that-one-call-with-its-exact-arguments.md).** The mark exists as a decision: `needs-approval`, set on + > `execute` in the seat's definition, enforced by the tool runner of each node. It is set once the reads of §5 + > that have no tool yet (a file, an account) have one on every node, which issue 418 tracks. What §4 decided + > stands. 5. **Narrow read tools replace the common reads first,** each a tool of the module that owns the area, so that an approval is asked for an act and not for a read. Otherwise nearly every approval is for a read, the operator learns to approve without reading, and approval is worth nothing. The reads diff --git a/02-DECISIONS/0272-the-operator-reaches-the-controller-with-mesh-cli-on-every-node-and-a-node-is-the-terminal-only-where-no-agent-runs-as-its-operator.md b/02-DECISIONS/0272-the-operator-reaches-the-controller-with-mesh-cli-on-every-node-and-a-node-is-the-terminal-only-where-no-agent-runs-as-its-operator.md index e428bf15..20dfa106 100644 --- a/02-DECISIONS/0272-the-operator-reaches-the-controller-with-mesh-cli-on-every-node-and-a-node-is-the-terminal-only-where-no-agent-runs-as-its-operator.md +++ b/02-DECISIONS/0272-the-operator-reaches-the-controller-with-mesh-cli-on-every-node-and-a-node-is-the-terminal-only-where-no-agent-runs-as-its-operator.md @@ -76,6 +76,9 @@ the original. ([ADR 0234](0234-the-mesh-holds-a-conversation-with-its-operator.md), ADR 0259). Not now. It is the route for a node whose agents must share the operator's account, and it waits for the approval flow (research 039). + > **The mechanism changed — 2026-10-10, by [ADR 0286](0286-a-tool-or-verb-marked-in-its-manifest-runs-only-on-the-operators-approval-of-that-one-call-with-its-exact-arguments.md).** The approval flow for one call exists as a decision: a + > marked tool or verb runs only on the operator's warrant for that call. This option's choice stands. + ### Which account on which node is the terminal 1. **The operator's account on any node.** Rejected. On every node but the control-node, and on the control-node diff --git a/02-DECISIONS/0286-a-tool-or-verb-marked-in-its-manifest-runs-only-on-the-operators-approval-of-that-one-call-with-its-exact-arguments.md b/02-DECISIONS/0286-a-tool-or-verb-marked-in-its-manifest-runs-only-on-the-operators-approval-of-that-one-call-with-its-exact-arguments.md new file mode 100644 index 00000000..2f326fd4 --- /dev/null +++ b/02-DECISIONS/0286-a-tool-or-verb-marked-in-its-manifest-runs-only-on-the-operators-approval-of-that-one-call-with-its-exact-arguments.md @@ -0,0 +1,298 @@ +--- +topic: what runs on it +status: accepted +date: 2026-10-10 +deciders: jochen +reconstructed: false +extends: 02-DECISIONS/0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md +--- + +# 286. A tool or verb marked in its manifest runs only on the operator's approval of that one call, with its exact arguments + +## Context + +[Research 039](../01-RESEARCH/039-a-tool-that-needs-a-persons-approval/00-overview.md) asked whether a module's +tool or a seat's verb can declare that calling it needs a person's approval, so that the mesh refuses the call +without one. The operator's words for this decision, 2026-10-10: "risky agent actions ask them first on +Telegram, showing exactly what will be done, and a yes covers that one action only." + +What the mesh has, on 2026-10-10: + +- **Nothing refuses a call for want of a person.** Any tool runner may call any tool on any node + ([ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md)), and the runner + cannot tell who called: an agent session and the operator reach the same address the same way + ([issue 340](../04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/00-report.md)). + [ADR 0259](0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md) §6 + says it outright: the acts a warrant authorises are still verbs an agent may call directly, "until research + 039 … withholds them from every caller without a warrant". Some modules end a tool's description with "(a)" + for one that acts and "(r)" for one that reads; that is prose, and nothing reads it. +- **The login shell's `execute` is root on a node in one call.** It runs any command as the operator account, + which has passwordless sudo. [ADR 0268](0268-the-login-shells-execute-is-withheld-on-the-control-node-until-a-call-to-it-needs-a-persons-approval.md) + withholds it on the control-node until a call to it needs a person's approval, and keeps it on the other + three nodes, where `mesh_search` finds it on 2026-10-10. [ADR 0266](0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md) + names it, with the desk's `node-terminal-emulator.open` and `rofi_run`, as the way an agent on the control-node + still becomes root on another node. +- **The path from an ask to a warrant is built and used.** The router (`operator-channel.asks`) held nine asks + on 2026-10-10: eight chosen "the operator, via telegram (user id verified), chose Approve", five of them the + agent module's instruction proposals ([ADR 0274](0274-an-agent-proposes-a-shared-instruction-section-and-every-node-takes-it-only-on-the-operators-warrant-which-it-proves-itself.md)) + and three the controller's settings proposals ([ADR 0277](0277-a-trusted-setting-is-proposed-through-a-verb-and-set-on-the-operators-warrant-and-a-modules-own-secret-is-asked-for-and-typed-at-the-desk.md)), + the agent module's five shown as "Part shown". The shared library (mesh-sdk, Go) holds the parts an asker needs: an + option's `binds`, the digest of an **act** over named fields in a canonical encoding (`ActDigest`), a + warrant's check against the ask the asker kept (`Warrant.For`), and the check that the act about to be + performed is the one bound (`Option.Performs`). The controller's verb `warranted` records a warrant in the + hand-act log from the router's own record. The tool runner is TypeScript, and the digest exists in Go only. +- **The reads `execute` carried.** ADR 0268 §5 lists them. On 2026-10-10 `mesh_search` finds a tool for a + container's logs (`docker_logs`, on all four nodes), a unit's state and journal (`node-service-manager`), + the listening sockets (`netcheck_listening`) and the hosts entries (`node-hostname.entries`), and none for + reading or searching a file, and none for looking up an account. Issue 418, in the issue tracker, was opened + for those two. + +The record was searched first ("tool needs a person's approval warrant call refused", "approval"): research +039, ADRs 0259, 0266, 0268, 0272, 0274 and 0277, and nothing that marks a tool. + +### Against GENESIS + +- **Effect — "when something genuinely needs a decision, you are asked — with the context, not a log line."** + The call that needs the operator is put to them as what it will do, on the phone, and only that call. +- **Failure must be loud.** A call refused for want of an approval says so, and says it did nothing. An + approval nothing can carry is refused at once, never left waiting. +- **The mesh owns the truth.** The mark is the tool's own declaration in the catalogue, issued to the runner by + the controller; a module's code cannot drop it. + +## Considered Options + +### Where the call is refused + +1. **In the MCP client** (a permission prompt in the agent's session). Rejected: a client is no boundary, since + any tool runner may call any tool, and an agent controls its own client. +2. **In each module's own code**, as ADR 0274 and ADR 0277 do for their proposals. Rejected as the general way: + every module would rebuild the same check, and a module that forgets is a hole nobody sees. Those two stay + as they are, because their act is not a call but a proposal kept until approved. +3. **In the tool runner that serves the call, for every module it carries, and in the controller for its own + verbs, with one shared check.** Chosen. Every call to a module's tool or a node seat's verb passes through + the runner on the node that serves it (ADR 0175). + +### What one approval covers + +1. **One agent session, or a span of time.** Rejected by the operator's words: "a yes covers that one action + only". A wider approval is fewer taps and more reach for whoever holds the session. +2. **One call, with its exact arguments, on one node, once, soon after the choice.** Chosen. + +### Per tool, or per argument + +1. **The mark on an argument's value**, a rule in the manifest saying which values need a person. Rejected: a + rule written wrong lets harm through unasked, and nobody reads it on the phone. +2. **The tool decides from its arguments.** Rejected: the module's code runs before the check, which is what + the mark exists to stop. +3. **The mark is on the tool or the verb, and covers every call to it.** Chosen. A tool that is harmless for + some arguments and not for others is split by its module into two tools, and the second is marked. + +### How the call waits + +1. **The call blocks until the operator answers.** Rejected: an answer can take an hour, longer than any client + waits, and a call held open holds the runner. +2. **The runner performs the call itself on hearing the warrant**, as ADR 0274's nodes do. Rejected for a call: + the caller wants the call's answer, and nobody would receive it. +3. **The call answers at once that it was asked and did nothing; the same call made again after the approval + runs, once.** Chosen. + +### Who asks + +1. **The tool runner under a name of its own.** Rejected: a new asker, a new grant, and an ask that names the + runner rather than the module whose act it is. +2. **The module whose tool is called, through the runner that carries it.** Chosen. ADR 0259 §3 already grants + a runner the asks of each module it carries that uses the `operator-channel` seat. For its own verbs the + controller asks as `mesh-controller`, as it does today. + +### What the operator reads + +1. **Every argument whole, always.** Rejected: the router's content rule refuses a path, an address or a + secret's shape in anything that leaves the mesh, and a command often carries a path. +2. **A fingerprint only.** Rejected: nobody approves what they cannot read. +3. **Every argument whole where every part of it may leave the mesh; else its shape with markers, the + fingerprint, and where it is read whole.** Chosen, as ADR 0277 §2 shows a setting. + +## Decision + +### 1. The approval mark + +A module's manifest gains **`needs-approval`**: a list of its tools, and of the seat verbs it serves, each of +whose calls needs the operator's approval. A seat's definition may mark one of its verbs the same way, and then +every holder's verb is marked. The marks add up: a holder cannot unmark a verb its seat marks. + +The controller refuses a manifest whose mark names a tool or verb the module does not serve. It issues the +mark in the membership it gives each tool runner for the module, and **the runner reads the mark there and +never from the bundle**, so a bundle cannot drop it. A module with a mark is composed as a user of the +`operator-channel` seat, so its grants to ask, to hear its warrants and to read its own asks are the ones ADR +0259 §3 already composes. No new kind of grant. + +**A runner that does not honour the mark is not issued a marked tool.** A runner declares that it honours it +(`approval-mark/1`) when it joins; on a node whose runner does not, the controller withholds every marked tool +and verb, and the runner's status names them. A node is never left serving a marked tool unasked because its +runner is older than this record. + +### 2. The act that is approved + +The call is turned into an **act** (ADR 0259 §6), digested with the shared library's `ActDigest`: + +- `address`, the address as called; `node`, the node that serves it; `module` and `tool` (or `seat` and + `verb`); +- `build`, the digest of the bundle the runner serves it from, so a call approved before the module moved is + asked again rather than run by code the operator did not approve; +- `arg.` for each argument given: a string as itself, any other value as its JSON in RFC 8785's canonical + form. An argument left out is not in the act, so the same call made again must leave it out too. + +### 3. The first call asks, and does nothing + +On a call to a marked tool with no approval for its act, the runner: + +1. refuses at once, asking nothing, a call whose arguments hold anything shaped like a secret (a secret never + goes to a phone; a marked tool takes its secrets another way), or whose arguments are more than 16 KiB; +2. publishes **one ask** under the module's name, unless an open ask for the same act exists, in which case it + asks nothing new; +3. waits up to 5 seconds for the router's word, as ADR 0274 §5 (its note of 2026-10-10) has it, and answers the call. + +**The ask:** + +- **headline**: "Run on ?", shortened to "Run ?" when the names make it longer than the router + allows, and ending "Part shown" whenever the phone does not show every argument whole; +- **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; then **every argument, one per line, "name: value"**, whole where + every part of it may leave the mesh, else in its shape with ‹path›, ‹address› or ‹withheld› in place of what + may not (ADR 0277 §2); the act's **fingerprint** (the first 24 hexadecimal digits of its digest, in groups of + four); and, when a part is not shown, where the whole is read: the runner's tool `approvals` on that node, + through the mesh MCP server or `mesh-cli`, whose fingerprint must be the one on the phone. 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"; +- **two options, Approve and Decline, both at the level approve**, so only a channel that proves who answered + carries either (Telegram today; the desk answers only `acknowledge`, + [ADR 0258](0258-a-desk-click-performs-only-an-acknowledgement-and-an-answer-is-a-one-time-ticket.md)). Each + binds the act's digest and its answer; +- **who may answer**: the operator, the only role; +- **expiry**: 60 minutes after it is asked. It never defaults; +- **about**: the act's digest, so the same call made again while it is open asks nothing new. + +The runner keeps the waiting call (the ask's id, the act and its whole arguments) in its own record, and its +tool **`approvals`** lists the calls asked on that node in the last day, newest first, each with its state and +fingerprint, and given an ask's id, its whole arguments and the act's digest. + +### 4. The same call again runs, once, on the warrant + +When a call to a marked tool comes and its act has an ask that was chosen, the runner, before anything runs: + +1. reads **the router's own record** of the ask, under the module's name (ADR 0259 §3), never a record of its + own or the caller's; +2. composes the ask again from the act and holds the warrant to it with the shared check (`Warrant.For`): the + same asker and ask, the ask's digest the router took, a person's choice, an option the ask offered, at its + level, before the ask expired; +3. requires the option chosen to be **Approve**, and the act about to be performed to be the one it bound + (`Option.Performs`): the same node, build and every argument; +4. requires the call to come **within 30 minutes of the choice**; +5. claims the ask as used in **the node's own file**, written whole and synced to the disk before it replaces + the old one, and kept for 48 hours. A warrant claimed before is refused, so a call runs once however often it + is made, and a warrant replayed into the bus runs nothing. + +Then it runs the call, answers with the tool's own answer and a line naming the approval ("run on the operator's +approval via telegram at 19:21, ask "), and asks the controller's `warranted` to record it in the hand-act +log, again every ten minutes for a week while the controller has not taken it (ADR 0274 §6). + +A record of the runner's that points at another ask, or a waiting call written into the bus by anyone else, +runs nothing: every check above is against the router's record and the call actually made. + +### 5. What the caller is told + +The call that does not run says so first, as an error, never as a result: "Not run: this needs the operator's +approval. Asked on telegram at 19:21 (ask , fingerprint …); make the same call again once it is approved, +before 20:21." Then, for the same call made again: still waiting; declined (the same act is not asked again for +10 minutes, and the call says so); expired; approved too long ago (asked again); already run; or refused, with +the router's words. `operator-channel.asks` shows the same ask. No answer ever reads as done when nothing ran. + +### 6. Fail closed + +The call is refused, runs nothing and says why, when: no module holds the operator channel; the router refuses +the ask (no ready channel can carry an approval, or the module already has three open asks); the ask cannot be +published; the router's record cannot be read; the node's own file cannot be read, in which case no marked call +runs on that node until it can; or the mark cannot be read from the membership. + +**The controller's terminal is not exempt.** A marked tool called through `mesh-cli` asks like any other call, +because the runner cannot tell who called. A call refused by any check here is recorded in the runner's log +with the ask's id and fingerprint and never the arguments, which may hold what may not be logged. + +### 7. The controller's own verbs + +The controller serves its own verbs and enforces the same mark on them, with the same shared check, asking as +`mesh-controller` and claiming each warrant once in its own record. Its verbs are marked in its own manifest. + +### 8. What is marked first + +- **`node-login-shell.execute`, on every node, in the seat's definition**, as ADR 0268 §4 decided, once the reads + of ADR 0268 §5 that have no tool yet (reading or searching a file, looking up an account) have one on every + node, tracked by issue 418. Until then the control-node keeps withholding it and the others serve it unmarked, + as ADR 0268 has it. Whether the control-node then serves it again, under the mark, stays the operator's + setting. +- **`mesh-delivery.release`**, the glossary's "a person letting a held delivery go on". The controller's own + Release on a condition already goes through an ask, so the operator sees no new kind of question. + +The rest of research 039's candidates, and the split of `rofi_run` and `node-terminal-emulator.open` into a +form that opens an entry or a window and a form that runs a command, which is marked, are tracked by issue 417. +Each is a change to its module's manifest, reviewed like any other. + +### 9. What this changes in the earlier records + +- **ADR 0259 §6**, "a warrant is not yet the only way to these acts": for a marked tool or verb it is now the + only way. The others stay as §6 says. +- **ADR 0268 §4**: the mark is this record's; §4 holds as decided, with §8 above saying when. +- **ADR 0266**, "what still remains": the remote path to root through `execute` closes on each node as the mark + reaches it; the desk's two tools close with their split. +- **ADR 0272**, the second factor (its option 4): a per-call approval on a verified channel now exists to build + that route on. Which node is the terminal is unchanged. + +## Consequences + +- An agent can still call anything, and a marked call does nothing until the operator taps Approve on the phone, + having read the tool, the node and every argument, or its shape and fingerprint. The tap runs that call once. +- A call the module, the controller, the bus or a replayed record could have caused alone now needs the router's + record of the operator's choice. +- **What got harder:** + - an agent that needs a marked tool waits for the operator, and makes the call twice; + - a call whose arguments carry a path is shown on the phone in shape only, with "Part shown"; reading exactly + what will run then means `approvals` at a terminal or the desk. "Showing exactly what will be done" holds + on the phone only for arguments that may leave the mesh; + - each module with a mark asks against its own three open asks, so a fourth waiting call from one module is + refused until one is answered; + - a tool whose risk depends on its arguments is split into two tools; + - a node whose runner predates the mark does not serve marked tools until its runner is updated; + - the tool runner, which is TypeScript, needs the act's digest, which exists in Go only, so the shared + library carries it in both, held to the same vectors. + +## How it is checked + +| Rule | Checked by | +|---|---| +| A mark naming nothing served is refused; the mark reaches the runner from the membership | the controller's tests: a manifest marking an unknown tool is refused; a seat's mark reaches every holder's membership; the membership carries it whatever the bundle lists | +| A runner that does not honour the mark is not issued a marked tool | the controller's test: a membership for a runner without `approval-mark/1` leaves out the marked tool and names it | +| The first call asks once and runs nothing | the runner's test: a marked tool's bundle is not called; one ask is published under the module's name, passing `Check`, Approve and Decline both at approve, each binding the act's digest; the same call again asks nothing new; the answer is an error saying "Not run" | +| The same call runs once on Approve, and only that call | the runner's test against a real bus: after the router's record says Approve, the call runs once; again, it is refused as already run; with one argument changed, another build, another node, after 30 minutes, on Decline, at the level acknowledge, nobody choosing, another ask's warrant, or a record of the runner's pointing at another ask, nothing runs | +| A warrant replayed or a file lost runs nothing | the runner's test: a used claim kept across a restart; an unreadable file refuses every marked call | +| Fail closed | the runner's test: no holder of the operator channel, the router's refusal, a fourth ask, a secret in an argument and more than 16 KiB are each refused, nothing asked or run where so stated, each said | +| The digest is the same in both languages | the shared library's conformance vectors, run in Go and TypeScript | +| The controller's verbs | the controller's test: a marked verb of its own runs only on a warrant, once | +| The arguments are never logged | the runner's test: its log over an ask and a run holds the ask's id and fingerprint and no argument | +| Live, after rollout | an agent calls a marked tool on a node; the phone shows "Run on ?" with the arguments and Approve and Decline; the call made again after Approve runs once, a third call is refused, and `hand-acts` shows the warrant | + +## References + +- [Research 039](../01-RESEARCH/039-a-tool-that-needs-a-persons-approval/00-overview.md) — the effort this + graduates +- [ADR 0259](0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md) — the + ask, the warrant, the act's digest +- [ADR 0274](0274-an-agent-proposes-a-shared-instruction-section-and-every-node-takes-it-only-on-the-operators-warrant-which-it-proves-itself.md), + [ADR 0277](0277-a-trusted-setting-is-proposed-through-a-verb-and-set-on-the-operators-warrant-and-a-modules-own-secret-is-asked-for-and-typed-at-the-desk.md) + — the proposals this reuses: the shape shown, the fingerprint, the warrant proven from the router's record, + the `warranted` verb +- [ADR 0266](0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md), + [ADR 0268](0268-the-login-shells-execute-is-withheld-on-the-control-node-until-a-call-to-it-needs-a-persons-approval.md), + [ADR 0272](0272-the-operator-reaches-the-controller-with-mesh-cli-on-every-node-and-a-node-is-the-terminal-only-where-no-agent-runs-as-its-operator.md) + — the records that wait on this one +- [to-be 46](../03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md) §10 — the design +- Issue 417 (the build) and issue 418 (the reads that come first), in the issue tracker diff --git a/03-DESIGN/00-as-is/13-the-console.md b/03-DESIGN/00-as-is/13-the-console.md index 9d2e8564..d68f484f 100644 --- a/03-DESIGN/00-as-is/13-the-console.md +++ b/03-DESIGN/00-as-is/13-the-console.md @@ -2,7 +2,7 @@ layer: as-is status: implemented code: [mesh-catalog modules/mesh-console, mesh-tools src/mesh.ts, mesh-tools src/http.ts, mesh-tools src/runtime.ts, mesh-controller internal/broker, mesh-controller cmd/mesh-controller/check.go, mesh-controller cmd/mesh-controller/seatverbs.go] -updated: 2026-09-30 +updated: 2026-10-10 decisions: - 02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md - 02-DECISIONS/0095-the-control-plane-is-the-way-to-ask-a-module.md @@ -31,6 +31,10 @@ mesh records rather than rolls out — and 62 tools from the rest. `tools/call` reaches any tool by `.`, listed or not. The mesh MCP server's grant is `*`, so what it may call is every tool on the mesh; its account may publish nothing else and subscribes nothing. +No call is refused for want of a person's approval: a tool runner serves every call it is issued, whoever made +it, and cannot tell an agent from the operator (ADR 0259 §6, issue 340). Only the agent module's instruction +proposals and the controller's settings proposals wait for the operator's warrant, each in its own code. + ## The mesh's own verbs *Since 2026-09-30 evening ([ADR 0154](../../02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md)).* diff --git a/03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md b/03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md index 705fa4f3..790a1b09 100644 --- a/03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md +++ b/03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md @@ -1,9 +1,10 @@ --- layer: to-be -status: designed -code: [] +status: in-progress +code: [mesh-controller, mesh-tools, mesh-sdk, mesh-catalog] updated: 2026-10-10 decisions: + - 02-DECISIONS/0286-a-tool-or-verb-marked-in-its-manifest-runs-only-on-the-operators-approval-of-that-one-call-with-its-exact-arguments.md - 02-DECISIONS/0277-a-trusted-setting-is-proposed-through-a-verb-and-set-on-the-operators-warrant-and-a-modules-own-secret-is-asked-for-and-typed-at-the-desk.md - 02-DECISIONS/0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md - 02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md @@ -444,6 +445,40 @@ announced on every channel as break-glass. `retire approve` takes **`expect`**, and refuses if the set now differs. `conditions silence` stays callable; a silence an agent sets is said on the away channel, with its why. +### A call that needs the operator's approval + +*Added 2026-10-10, [ADR 0286](../../02-DECISIONS/0286-a-tool-or-verb-marked-in-its-manifest-runs-only-on-the-operators-approval-of-that-one-call-with-its-exact-arguments.md).* +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`. + +``` + agent ──call──► tool runner (serving node) ──ask, under the module's name──► router ──► Telegram + ▲ │ "Not run: asked, ask " │ Approve + │ ▼ ▼ + └──same call──► runner reads the router's record ◄────────────── warrant (decided.) + │ Warrant.For + Option.Performs on the act; within 30 min; claimed once + ▼ + runs the call once ──► mesh-controller.warranted (hand-act log) +``` + +- **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 on + ?", 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", 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 and until when. +- **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); 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, 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), + and `mesh-delivery.release`. + ### The hand-act Gains **`via`** (kind and holder), **`requested-by`** (agent principal or condition key), **`ask`** (the @@ -562,6 +597,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 set, issued in the membership, withheld from a runner without `approval-mark/1` (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` and `mesh-delivery.release` marked (catalogue, the controller's seat set) | 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 `hand-acts` shows the warrant | **Switching phase 5a on: the operator's steps, in order** (amended 2026-10-09, ADR 0259). Each line is typed with `nox` (`mesh-cli`) on the control-node, from the operator's own account, which is the controller's terminal