diff --git a/00-META/glossary.md b/00-META/glossary.md index 3af03f11..ec3c50e9 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -279,7 +279,9 @@ and the operator act. **Recorded by** the controller's `identity` and `licences` - **proof** — what an authorising answer carries: a verified sender, a one-time code, a security key's touch (ADR 0234). How much proof a kind of ask needs is its **assurance level**. - **controller's terminal** — a command line the controller runs as typed by its operator, not asked through a - verb, so a change that is the terminal's alone is taken (issue 339, issue 340). In the controller's code it is a + verb, so a change that is the terminal's alone is taken (issue 339, issue 340) — or, since + [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), + proposed through a verb and set on the operator's warrant. In the controller's code it is a line run without `MESH_VERB`. The operator reaches it with `mesh-cli` from their own account on a node where no agent can run as that account: the control-node, and a node whose agents run under an account of their own ([ADR 0272](../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)). @@ -501,6 +503,10 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272. - **warrant** — the router's record that a person chose one option of one ask: the ask, the option, who chose (the operator, as a channel kind's identity), through which channel, the proofs and when, published for that asker alone. The only thing an asker acts on (ADR 0259). +- **proposal** — a change an agent, or anyone the bus admits, puts forward through a verb and that takes + 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. - **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/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 3dcc63cc..e92ded70 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 @@ -508,6 +508,13 @@ with the router not knowing its own machine, it approved. Now: machine's launcher, carried by that machine's runtime, may answer its prompt; for any other module's secret that remains the accepted risk of a desk where agents run as the operator. + > **The mechanism changed — 2026-10-10, by [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 same prompt is opened by the + > controller's `secret-ask` verb from anywhere on the mesh, with the desk the module's machine unless named, + > and `give` composes its line. It is bounded (one open ask per secret, three an hour, read from the store + > before the prompt opens) and the prompt names who asked, from the bus's word on the caller alone. What + > stands: the value is typed at the desk, sealed to the one call, never in an argument, a log or an event; + > a trusted party's secret is still the terminal's. + **Where an ask can be answered is said by the router**, from the channels that qualify: "Answer on Telegram, or from the mesh MCP server." The asker's words never name a channel. 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 e059863c..2efc5c0c 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 @@ -262,6 +262,11 @@ here: which seals to the machine that uses the secret, never to a key the caller chooses. **The `token` verb is refused too**, because it handed the joining token to its caller. Enrolling a machine is now done at the controller's terminal, by the operator; + + > **The mechanism changed — 2026-10-10, by [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).** Beside `secret rotate`, one other + > `secret` line is run through a verb: `secret ask [--at ]`, which carries no + > value and answers none — the operator types it at the desk's hidden prompt. And `settings proposals` + > joins the generic verb's reads; `settings propose` is the settings verb's. Everything else stands. - `claude_code_add_api_key` read a caller's file as the operator, sealed it away and deleted it. It now takes only a file under the agent's own home, reached through no symbolic link, read as the agent, at most 4 KiB, holding an Anthropic API key. The licence manager refuses a key of another shape too; diff --git a/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 b/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 new file mode 100644 index 00000000..47857018 --- /dev/null +++ b/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 @@ -0,0 +1,260 @@ +--- +topic: what runs on it +status: accepted +date: 2026-10-10 +deciders: jochen +reconstructed: false +extends: 02-DECISIONS/0274-an-agent-proposes-a-shared-instruction-section-and-every-node-takes-it-only-on-the-operators-warrant-which-it-proves-itself.md +--- + +# 277. A trusted setting is proposed through a verb and set on the operator's warrant, and a module's own secret is asked for and typed at the desk + +## Context + +[Issue 339](../04-ISSUES/339-a-verb-can-place-a-directory-at-the-machines-own/00-report.md) found that any caller +of the controller's `settings` verb could have the node-engine hand a system directory to an account of the +caller's choice, and its fix made every **trusted setting** the controller's terminal's alone: `places` and +`accesses`, every key a provider serves, every setting a trusted file asks for, and the whole layer of a module +with a trusted mergeable file ([issue 340](../04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/00-report.md)). +The controller refuses them through any verb: "set at the controller's terminal only … whoever may call a verb +includes agents". A module's own secret issued outside the mesh is given the same way: `secret accept` at the +terminal, or the desk prompt the `give` verb opens ([ADR 0259](0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md) +§10, step 7). + +That was right for the hole it closed and wrong for the operator's day. On 2026-10-10 the operator said: "you +should modify the settings yourself, I don't want to run manual commands on novox". The case at hand: the +`mounts` module's `sources` on the gaming machine's client, a trusted setting (a mount point on the machine, +asked for by a trusted file), and its `smb-credentials`, a secret issued outside the mesh. Each was a line the +agent could only hand the operator to type at the control node. + +[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) +established the pattern that removes the terminal from the loop without giving the decision to an agent: an +agent **proposes**, the operator **approves** on a channel that proves who answered (Telegram, placed where no +agent can become root), and the act is performed on the **warrant** — the router's record of the choice, bound +to the exact thing shown. The controller already acts on warrants for its own conditions (Release, Stop, +Restart, a rehearsal), claims each once, and holds the act to the digest the option bound. + +The record was searched first ("settings propose warrant", "trusted setting approve", "secret ask desk +prompt"): nothing beside ADR 0274, issue 339's rule, ADR 0259 §10 and the operator's direction. + +**Measured in the controller's code on 2026-10-10.** The `settings set` command is the one place a layer is +written; the terminal rule is keyed on the verb named in the process's environment. The `asked` bucket on the +bus is written by the controller alone, so a record there, unlike the agent module's `proposals` state (ADR +0274 §3), proves who kept it. A layer carries when it was set and not who set it. The router's content rule +refuses a path, an address or a secret's shape in any ask's words, and `sources` holds both a path and an +address. The desk's prompt (`node-launcher.secret`) is written by the launcher from the module's, the secret's +and the machine's names, says the controller asks, and takes no words of the caller's; the bus lets only the +controller ask it. Nothing bounded how often it could be asked. + +### Against GENESIS + +- **Failure must be loud.** A proposal nothing can carry to the operator is refused at once, in words, never + left waiting for an answer that cannot come. +- **The mesh owns the truth.** The layer is the mesh's record, set by the controller on a warrant it checked, + never a value an agent wrote; who approved it is kept beside it. +- **Effect — "when something genuinely needs a decision, you are asked, with the context".** The operator reads + the module, the machine and the exact change on their phone, and taps. + +## Considered Options + +### Who may set a trusted setting + +1. **The controller's terminal alone** (issue 339's rule). Safe, and kept as one of two ways. Alone, every + placement and every mount is a trip to the control node, and an agent can only describe what to type. +2. **Let verbs set trusted settings outright**, trusting the caller. Rejected: an agent would then decide what + root mounts and owns on a machine, which is the hole issue 339 closed. +3. **An agent proposes through a verb; the operator approves on a channel that proves who answered; the + controller sets the layer on the warrant.** Chosen, beside 1. + +### Who performs the set + +1. **The verb's process, on hearing the warrant.** Rejected: a verb's process is short-lived and does not hear + the router's word; the serving controller is the asker that holds its asks and claims each once. +2. **The serving controller, in-process, as `settings set` at the terminal does.** Chosen. The same judgement + (the module's definition, a line break, a port per machine, a removal meant or refused), the same history, + the same words. + +### What the operator reads + +1. **The exact values, always.** Rejected: the router's content rule refuses a path, an address or a secret's + shape in any message that leaves the mesh, and a mount's source is both a path and an address. +2. **A fingerprint only.** Rejected: nobody approves what they cannot read. +3. **Every key with its exact new value where every word may leave the mesh; where not, the value in its own + shape with the part that may not leave replaced by a marker (‹address›, ‹path›, ‹withheld›), the layer's + fingerprint, and where the whole is read.** Chosen. "recalbox=smb://host/share@/mnt/recalbox" is shown as + "recalbox=‹address›@‹path›", and `settings proposals ` shows it whole, with the same fingerprint. + +### Where the record of the set is kept + +1. **A seat event of the controller's own**, beside the hand-act log. Rejected for now: nothing consumes it, and + the installer's first user list in the node-engine's repository names every controller event one by one, so + it would cost a node-engine change and rollout for a fact without a reader. +2. **The hand-act log (on the bus), the layer's own record of who approved it, and the router's edit of the + ask on every channel to its outcome.** Chosen. The warrant is recorded under the verb `warrant`, which the + hand-act log counts as a person's decision; `settings` says "approved by the operator via telegram at …". + +### How a module's own secret is asked for + +1. **Through a verb's argument, or on Telegram.** Rejected: a verb's caller may be an agent, and a value typed + on Telegram passes a bot's server. A secret's value is typed where no agent and no outside party reads it. +2. **The desk prompt, as `give` opens it, asked for by a verb from anywhere, bounded, naming who asked.** + Chosen. The value is typed at the desk, sealed to the one call, sealed again to the module's machine, and + never in an argument, a log or an event. + +## Decision + +### 1. A trusted setting may be proposed through a verb, by anyone the bus admits + +The controller's `settings` verb gains three arguments: `propose` (with `values`, or `clear`, and `replace` as +for a set), `proposals` and `proposal`. The command lines are `settings propose +[--node ] [--replace]` and `settings proposals []`, the operator's as `nox settings propose …`. A +proposal is for any key, the trusted ones included; `settings set` through a verb still refuses a trusted +setting (issue 339), and its refusal now names the proposal as the way. + +A proposal **changes nothing**. It is kept in the controller's own asks on the bus (the `asked` bucket, which +the controller alone writes), with the module, the machine, the values whole, whether the layer is cleared or +a removal meant, the layer as it stood when proposed, who proposed it (the bus's word on the caller) and when, +and the digests of the values and of the layer they replace. It is judged as `settings set` judges before the +operator is asked, so no ask is raised for a layer the mesh would refuse, and a removal not marked `--replace` +is refused as [ADR 0217](0217-no-change-to-a-machine-takes-effect-unseen.md) refuses it at the terminal. + +### 2. The ask + +One ask per proposal, published under the controller's name on the `operator-channel` seat: + +- **headline**: "Set mounts on shanks?" ("Clear …?", "… on the whole mesh?"), shortened when the names make + it longer than the router allows; +- **explanation**: the module and the machine; who proposed it and when; then the change against the layer as + it stands — "+ key: value" added, "~ key: value (was: old)" changed, "- key (was: old)" removed, and the + count of keys unchanged — each value exact where every word of it may leave the mesh, else in its shape + with ‹address›, ‹path› or ‹withheld› in place of what may not; the layer's **fingerprint** (the first 24 + hexadecimal digits of its digest, in groups of four); when a part is not shown, where the whole is read + (`settings proposals `, through the mesh MCP server or `mesh-cli`), whose fingerprint must be the one on + the phone; and that, approved, the layer is set at once and the machine takes it at its next push; +- **two options, Approve and Decline, both at the level approve**, as the controller's rehearsal asks, so only a + channel that proves who answered carries either. Each binds the digest of its act: the proposal's id, the + answer, the module, the machine, the digest of the exact values, the digest of the layer they replace, + whether the removal is meant, whether it is a clear, who proposed it and when; +- **expiry**: a day less ten minutes after it was proposed; at expiry the proposal is discarded. An ask that + authorises never defaults (ADR 0259); +- **about**: the module and the machine, so a newer proposal for the same layer replaces an older one still + open, and the older is discarded. + +### 3. The serving controller sets the layer on the warrant, and on nothing else + +The controller acts on the warrant as on any other of its asks (ADR 0259 §6): only for an ask it holds, only +on a warrant whose ask digest is the one it kept, claimed once by compare-and-set so a warrant heard twice, or +replayed, changes nothing; only for an option the ask offered, at that option's level, before the ask expired. +Then, before anything is set: + +1. the act about to be performed must be the one the chosen option bound, and **the values kept in the + record must digest to the digest in that act**: a record whose values, or whose layer-as-it-stood, changed + after the ask is refused and nothing is set; +2. the layer must still be the one the operator was shown the change against (to-be 46 §10, step 7): a layer + changed since, at the terminal or on another warrant, refuses the act, and the proposal is made again; +3. the layer is set, or cleared, exactly as `settings set` and `settings clear` do at the terminal, with the + same judgement and the same history kept, and **who approved it is kept beside the layer**: "approved by + the operator, as telegram identity …, via telegram (user id verified) at …, ask , proposed by ". + `settings` says it back, so a layer the operator approved on their phone is told from one typed; a layer + set at the terminal says so, and one set through a verb names the verb. + +On Decline nothing is set and the proposal is recorded as declined; on expiry, a cancel, a replacement or +the router's refusal nothing is set and the proposal is recorded as ended. Either way the warrant, or the +end, is recorded in the hand-act log under the verb `warrant`, a person's decision and never a repair, with +who chose, through which channel and with which proofs. **The push afterwards stays a separate act**, as it +is for a layer set at the terminal: the proposal's answer says `push `. + +### 4. Fail closed + +A proposal is refused at once, in words, naming the terminal's line instead, when no module holding the +operator channel is assigned, when the bus does not yet hold the controller's grant to ask, when the ask could +not be published, or when the ask's words would carry what may not leave the mesh and the change could not be +shown without it. Published, the verb waits a few seconds for the router's word: a refusal (no channel can +carry an approval) is said to the caller, and the proposal is recorded as ended. The router caps open asks at +three per asker, the controller's conditions among them; a proposal that would be the fourth is refused before +it is asked, and the same change proposed twice is refused naming the first. + +### 5. A module's own secret is asked for, and typed at the desk + +The controller gains the verb **`secret-ask`** (`secret ask [--at ]`): the operator +is asked for the module's own secret in the desk's hidden prompt, on the module's machine or the one `at` +names, exactly as the `give` verb has it since ADR 0259 §10 — `give` now composes the same line. The value is +typed at the desk, sealed to the one call's key before it leaves that machine, opened in the controller, sealed +to the module's machine as `secret accept` seals it, and forgotten: **never in a verb's argument, never on a +channel, never in a log, an answer or an event**. It is refused, before anybody is asked to type, for a secret +the mesh issues itself (the module's bus account, one the mesh may make) and for a module that runs as an +account of its own, whose value is typed at the controller's terminal (ADR 0259 §10, N1-give). + +**Bounded, and the prompt names who asked.** Every ask is recorded in the store before the prompt opens, with +who asked (the bus's word on the caller), the secret, the machine, the desk and how it ended: one open ask per +secret, and three an hour, read from that record, so an agent cannot keep a prompt in front of the operator +until they type. The prompt names the module, the secret, the machine and who asked: the caller as the bus +named it, cut to a name's characters by the controller and refused by the launcher in any other shape, never a +word the caller chose. Given, the value is recorded in the hand-act log with who asked, and announced on every +channel as every given secret is. + +### 6. What this changes in the earlier records + +- **Issue 339's rule** ("set at the controller's terminal only, never through a verb") gains a second way: a + trusted setting is set at the terminal, or on the operator's warrant. The refusal through a verb stands and + names the proposal. +- **ADR 0259 §10, step 7** (the desk prompt opened by `give`): the same prompt is opened by `secret-ask` from + anywhere, bounded, naming who asked; `give` composes its line. +- **ADR 0266** (every `secret` subcommand but `rotate` refused through a verb): `secret ask` is the one other + line a verb runs, carrying no value and answering none. +- **ADR 0217 §1** stands: a proposal that removes a key is refused unless `--replace` says the removal is + meant, and the removal is in the ask's words. +- **ADR 0262** stands: `settings` shows every value and where it came from, and now who set the layer. + +## Consequences + +- An agent sets a trusted setting with one call and one tap on the operator's phone, and asks for a secret + with one call and the operator's typing at their desk. No line is typed at the control node for either. +- No caller of a verb can set a trusted setting: what it writes is a proposal, and the controller sets the + layer only on a warrant bound to the exact values the operator was shown. +- **What got harder:** + - every trusted setting is a question to the operator, among at most three open asks of the controller's; + - a value with a path or an address is read in part on the phone and whole with `settings proposals`; + - a layer changed between the proposal and the approval refuses the act, and the proposal is made again; + - a secret is asked for at most three times an hour, and a prompt the operator did not start now names who + did start it, so they decline it; + - the store gains a column on the settings layers and a table of secret asks (migration 0089). + +## How it is checked + +| Rule | Checked by | +|---|---| +| A proposal changes nothing, and asks at the level approve with the exact change | the controller's test: the ask passes the SDK's check, offers Approve and Decline both at the level approve, each binding an act digest; the explanation carries each key with its value or its shape, the layer's fingerprint and the "was" of a changed key, and passes the content rule; the kept record is the controller's own, and no layer is set (`TestAProposalAsksAtTheLevelApproveWithTheExactChange`, `TestAClearIsProposedAndShowsWhatItRemoves`) | +| What the phone is shown keeps a value's shape and passes the content rule | `TestAValueIsShownInItsShapeAndPassesTheContentRule` | +| A layer the mesh would refuse, or a silent removal, is not asked | `TestAProposalTheMeshWouldRefuseIsNotAsked` | +| The layer is set once, on the operator's approval, with who approved it; a replay changes nothing | `TestTheLayerIsSetOnceOnTheOperatorsApproval`: the warrant heard twice sets once, no verb is called, the hand-act reads `warrant` with the answer and the values' digest, and counts as a person's decision | +| Decline, expiry, cancel, replacement and the router's refusal discard | `TestDeclineExpiryAndRefusalDiscardAProposal`, each outcome, and an approval after the end refused | +| The warrant binds the exact values | `TestAWarrantSetsOnlyTheExactValuesTheOperatorWasShown`: the values or the layer-as-it-stood changed in the record, the action's digest changed, another ask's digest, the level acknowledge, after expiry, nobody chose, another asker's — each sets nothing | +| Set on the real store as at the terminal, judged, the history kept, who approved it said, a stale proposal refused | `TestSetOnAWarrantJudgesTheLayerAndKeepsWhoApprovedIt`: the layer set and `settings show` says who approved it; the same proposal again refused for a changed layer; a removal not meant refused; a line break refused; a clear keeps who cleared it with the history | +| A layer says who set it, at the terminal and through a verb | `TestALayerSaysWhoSetIt` | +| Fail closed | `TestAProposalFailsClosedWhenNothingCanCarryIt`: no router, no grant, a publish that fails, the router's refusal — each refused in words naming the terminal's line, nothing kept or marked unsent | +| Bounded | `TestAProposalIsBounded` (the same change twice; a fourth open ask), `TestOpenProposalsCountTowardTheAsksHeldOpen` | +| A trusted setting through the settings verb is still refused, naming the proposal | `TestATrustedSettingThroughAVerbNamesTheProposal`, and issue 339's tests unchanged | +| The verb composes propose, proposals and proposal; the generic command proposes nothing | `TestTheSettingsVerbComposesProposeAndProposals` | +| The secret's value is never in an argument, a log or an event | `TestASecretGivenAtTheDeskIsSealedAndSaidNowhere` (unchanged), `TestASecretAskIsNeverASecretRead` (a value on the line, a file, recover and export each refused) | +| The prompt names who asked from the bus's word alone, in a name's characters | `TestThePromptNamesWhoAskedFromTheBussWordAlone`; the launcher's `TestThePromptIsWrittenHereFromNamesAndSaysTheControllerAsks` (a caller's name taken, a sentence refused) | +| One open ask per secret, three an hour, and every end recorded | `TestASecretAskIsBoundedAndItsEndRecorded`, and on the store `TestASecretAskIsOneAtATimeAndFewAnHour` | +| The `give` and `secret-ask` verbs run the one desk line, and no other `secret` line passes a verb | `TestTheGiveVerbRunsTheDeskPathAndTheControllerMayAskTheDesk`, `TestOnlyTheGiveLinePassesTheTerminalRuleForSecrets` | +| Live, after rollout | an agent proposes `sources` for mounts on the gaming machine's client; the operator's phone shows "Set mounts on ?" with the key, its value in shape, the fingerprint, Approve and Decline; after Approve `settings` shows the layer with "approved by the operator … via telegram", and `hand-acts` shows the warrant. An agent asks for `smb-credentials` there; a prompt on that desk names mounts, the secret and who asked; the operator types; the secret is sealed to that machine's mounts | + +## References + +- [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) + — the pattern this record extends to settings and secrets +- [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, and the desk prompt (§10) +- [Issue 339](../04-ISSUES/339-a-verb-can-place-a-directory-at-the-machines-own/00-report.md) — the rule this + record widens +- [ADR 0217](0217-no-change-to-a-machine-takes-effect-unseen.md), [ADR 0262](0262-a-preference-has-a-default-in-its-manifest-and-the-operators-own-value-has-none.md) + — a layer replaced whole, and what `settings` says +- [ADR 0266](0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md) + — the terminal-only commands +- [to-be 44](../03-DESIGN/01-to-be/44-no-change-takes-effect-unseen.md) §1, [to-be 52](../03-DESIGN/01-to-be/52-shares-and-mounts.md) + §2, [to-be 46](../03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md) §10 — the designs amended +- mesh-controller `cmd/mesh-controller/proposals.go`, `desk_secret.go`, `internal/inventory/secret_ask.go`, + migration 0089; mesh-catalog `modules/rofi` (the prompt names who asked) diff --git a/03-DESIGN/01-to-be/13-credentials-and-their-rotation.md b/03-DESIGN/01-to-be/13-credentials-and-their-rotation.md index 20049e77..44af1cb8 100644 --- a/03-DESIGN/01-to-be/13-credentials-and-their-rotation.md +++ b/03-DESIGN/01-to-be/13-credentials-and-their-rotation.md @@ -7,8 +7,9 @@ code: - mesh-controller examples/postgres-provisioner - mesh-controller internal/inventory/given.go - mesh-controller cmd/mesh-controller/given.go -updated: 2026-10-06 +updated: 2026-10-10 decisions: + - 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/0001-mesh-brokers-nodes-host-agents-think.md - 02-DECISIONS/0009-modules-and-the-graph.md - 02-DECISIONS/0085-a-secret-is-a-provision.md @@ -179,3 +180,10 @@ named and its age; the last is replaced when a person asks `secret rotate`. A pa delivers is unchanged ([ADR 0092](../../02-DECISIONS/0092-an-operator-delivers-a-pair-credential.md)). *How it is checked:* the tests ADR 0228 names, and a live `rotate` of a given at-start secret through the mesh MCP server. + +*Amended 2026-10-10 by [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).* An outside party's value is also **asked for**: the +controller's `secret-ask` verb, from anywhere on the mesh, opens the desk's hidden prompt on the module's +machine (or the one named), naming the module, the secret and who asked; the operator types the value there, +sealed to the one call and then to the machine, never in an argument, a log or an event. One open ask per +secret, three an hour; refused for a secret the mesh makes itself and for a module running as an account of its +own, whose value stays the terminal's. diff --git a/03-DESIGN/01-to-be/44-no-change-takes-effect-unseen.md b/03-DESIGN/01-to-be/44-no-change-takes-effect-unseen.md index 7ab102fc..91b655a9 100644 --- a/03-DESIGN/01-to-be/44-no-change-takes-effect-unseen.md +++ b/03-DESIGN/01-to-be/44-no-change-takes-effect-unseen.md @@ -2,9 +2,10 @@ layer: to-be status: in-progress code: - - mesh-controller: cmd/mesh-controller/unseen.go, cmd/mesh-controller/push.go (--all, --move, the hold), cmd/mesh-controller/held_back.go (the hold in a named push's cascade), cmd/mesh-controller/plan.go (--diff), cmd/mesh-controller/modules.go (settings show, --replace), cmd/mesh-controller/seatverbs.go and internal/catalogue/verbs.go (the verbs' arguments), internal/inventory/unseen.go, internal/inventory/catalogue.go (the history kept on set and clear), internal/inventory/migrations/0078-what-a-change-replaces.sql -updated: 2026-10-08 + - mesh-controller: cmd/mesh-controller/unseen.go, cmd/mesh-controller/push.go (--all, --move, the hold), cmd/mesh-controller/held_back.go (the hold in a named push's cascade), cmd/mesh-controller/plan.go (--diff), cmd/mesh-controller/modules.go (settings show, --replace), cmd/mesh-controller/seatverbs.go and internal/catalogue/verbs.go (the verbs' arguments), internal/inventory/unseen.go, internal/inventory/catalogue.go (the history kept on set and clear), internal/inventory/migrations/0078-what-a-change-replaces.sql, cmd/mesh-controller/proposals.go and internal/inventory/migrations/0089-a-layer-says-who-set-it-and-a-secret-ask-is-bounded.sql (a proposal set on a warrant, who set a layer) +updated: 2026-10-10 decisions: + - 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/0217-no-change-to-a-machine-takes-effect-unseen.md - 02-DECISIONS/0030-data-outlives-the-mesh-that-declared-it.md --- @@ -29,6 +30,21 @@ That stays. Around it: - **Keeping the previous layer.** Each set and each clear records the layer it replaced and when, so the previous value is read back with `settings show --history`, not from a backup. +*Amended 2026-10-10 by [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).* + +- **Who set it is kept with the layer.** A layer says who set it: at the controller's terminal by whom, + through which verb, or approved by the operator on which channel and when (ask id, proposed by whom). + `settings show` says it after the layer; the history copy keeps it. +- **A trusted setting is proposed and set on a warrant.** `settings propose + [--node] [--replace]` (the verb's `propose`) keeps a proposal in the controller's own asks and asks the + operator on a channel that proves who answers: the module, the machine, every key with its exact new value + (in its shape where a path or an address may not leave the mesh), the "was" of a changed key, the removed + keys, and the layer's fingerprint. Nothing changes until Approve, on which the serving controller sets the + layer as a set at the terminal would — judged the same, the removal meant or refused the same, the history + kept — only when the record's values still digest to what the option bound and the layer is still the one + shown. Decline, expiry or the router's refusal discard it. `settings proposals []` lists them and shows + one whole. The push afterwards is a separate act. + ## 2. A push says what it will change - **What was sent is kept.** Each send records a summary of the declaration it sent the machine, beside 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 d6169859..705fa4f3 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 @@ -2,8 +2,9 @@ layer: to-be status: designed code: [] -updated: 2026-10-08 +updated: 2026-10-10 decisions: + - 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 - 02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md @@ -380,6 +381,7 @@ the factors are the router's state. | approve or reject a waiting retirement set (`retire approve` / `retire reject`) | approve | | confirm a kept binding moves (`pin`, while `binding-kept` names it) | approve | | end a stuck plan; send a machine its declaration by hand; reset a bus consumer; retry a healer | approve | +| set or clear a trusted setting an agent proposed (`settings propose`, [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)) — bound to the exact values shown; the push stays a separate act | approve | | silence a condition | acknowledge | | delete a retired consumer's data, one or by age (`cleanup delete`) | destroy | | change the operator's identities, the away channel, or a factor's enrolment | destroy | diff --git a/03-DESIGN/01-to-be/52-shares-and-mounts.md b/03-DESIGN/01-to-be/52-shares-and-mounts.md index 08897485..138af67c 100644 --- a/03-DESIGN/01-to-be/52-shares-and-mounts.md +++ b/03-DESIGN/01-to-be/52-shares-and-mounts.md @@ -5,8 +5,9 @@ code: - mesh-controller internal/catalogue (the seats node-nfs-server and node-mounts) - mesh-catalog modules/nfs-server - mesh-catalog modules/mounts -updated: 2026-10-08 +updated: 2026-10-10 decisions: + - 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/0263-a-share-is-a-provision-a-node-mounts-it-on-demand-and-only-nodes-that-ask-get-it.md - 02-DECISIONS/0051-shared-data-is-the-operators.md - 02-DECISIONS/0092-an-operator-delivers-a-pair-credential.md @@ -103,8 +104,14 @@ The glossary holds the three words. `name=disk:@`, each optionally `:ro`. `none` (the default) is none. - **Its secret `smb-credentials`.** One section per SMB source, `[name]` with `username=` and `password=`, issued outside the mesh and given with `secret accept mounts smb-credentials - --from `. The process writes one credentials file per source, readable by root only. A source + --from ` at the controller's terminal — or asked for by an agent with the controller's `secret-ask` + verb, on which a hidden prompt opens at that machine's desk naming the module, the secret and who asked, + and the operator types it there ([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)). The process writes one credentials file per source, readable by root only. A source with no section is said with that remedy and is not mounted. +- **Its settings are trusted settings** (a trusted file asks for them), so a verb may not set them outright: + an agent proposes them (`settings propose mounts … --node `) and the operator approves on their + phone, where the ask shows each source in its shape ("recalbox=‹address›@‹path›") and the layer's + fingerprint; the layer is set on the warrant and pushed after (ADR 0277). - **What it refuses.** A mount point at, under or above any data the mesh keeps on this machine (the controller's `data` verb, asked over the bus; until it is read nothing new is set up), a system path, a mount point where something else is mounted or a folder that is not empty, and a share this machine @@ -206,9 +213,10 @@ folders, none refused for a reason not understood. export goes; the mesh's stays. *Undo:* `zfs set sharenfs=` by a person, recorded as a hand-act. 7. **The downloads**, the same way: read-only first, then read-write. -8. **The gaming device**: the operator gives `smb-credentials` with `secret accept`; the source is added - to `sources`; `node-mounts.list` says it `absent` while the device is off, and it mounts on access - when it is on. Then its fstab line is adopted. +8. **The gaming device**: an agent asks for `smb-credentials` with `secret-ask`, and the operator types it at + that machine's desk (or gives it with `secret accept` at the terminal); the agent proposes the source in + `sources`, and the operator approves it on their phone; `node-mounts.list` says it `absent` while the + device is off, and it mounts on access when it is on. Then its fstab line is adopted. 9. **The laptop**, read-only, granted read-only on the home server. **Root.** Both modules' tools act as root through `sudo -n`, and the `sudo` module grants the operator diff --git a/04-ISSUES/339-a-verb-can-place-a-directory-at-the-machines-own/00-report.md b/04-ISSUES/339-a-verb-can-place-a-directory-at-the-machines-own/00-report.md index b196a271..415f36ed 100644 --- a/04-ISSUES/339-a-verb-can-place-a-directory-at-the-machines-own/00-report.md +++ b/04-ISSUES/339-a-verb-can-place-a-directory-at-the-machines-own/00-report.md @@ -36,6 +36,12 @@ break adds a line of the caller's own to such a file. ## Why it is an issue +> **The mechanism changed — 2026-10-10, by [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).** The fix below made every trusted +> setting the controller's terminal's alone. That stands as one of two ways: a trusted setting is now also +> **proposed** through the `settings` verb by anyone the bus admits and **set by the controller only on the +> operator's warrant**, answered on a channel that proves who answered, bound to the exact values shown. The +> refusal through a verb stands, and names the proposal. + These settings were made for the operator. They say where an adopted machine keeps a module's data. But the mesh has one way to write any setting, the `settings` verb, and whoever may call a verb includes every agent session on a machine that runs the mesh MCP server. The generic `command` verb reaches the same