Compare commits

...
Author SHA1 Message Date
jochen 4507de7b65 ADR 0277, progressive insight: a proposal's values are whole on a channel that proves who answers (issue 383)
mesh/delivery ready: it delivers once merged
mesh/merge-gate out of date: judged against a trunk it does not name, the trunk is now a0e51c061b5e; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against a trunk it does not name, the trunk is now a0e51c061b5e; judged again on a recheck or when it is merged
The operator could not read what they were approving on the phone: the values were masked as
‹path› and ‹address›, and the fingerprint and a how-to paragraph made the message a wall of
text. On a channel that proves who answers the exact values are shown whole and only a value
shaped like a secret is withheld; the masking stays for conditions and for channels that prove
nothing; the fingerprint and the how-to are the Details answer's. To-be 46 takes the amendment
to the content rule and to exact-render.
2026-10-10 15:51:24 +02:00
mesh-admin 1ea678c6bd Merge pull request 'ADR 0277: 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' (#278) from decisions/0277-a-trusted-setting-is-proposed-and-set-on-the-operators-warrant into main 2026-10-10 12:50:19 +00:00
mesh-admin 76bb721cf4 Merge pull request 'Issue 362 resolved: ADR 0276 gains the own-path batch rule and a progressive insight, to-be 45 gains S18 and S19' (#277) from issues/362-resolved into main 2026-10-10 12:43:19 +00:00
jochen 3539446885 ADR 0277: the migrations are 0090 and 0091, and the code lands in two pull requests
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-10 14:42:47 +02:00
jochen 4d92d3872b ADR 0277: 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
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
The operator does not want to run manual commands on the control node. Issue 339 made every trusted
setting the terminal's alone, and a module's own secret the terminal's or the give verb's: each a line
an agent could only hand the operator to type. ADR 0274's pattern is extended: an agent proposes, the
operator approves on a channel that proves who answered, the controller sets the layer on the warrant
bound to the exact values shown. A secret is asked for by a verb and typed at the desk, bounded, the
prompt naming who asked. The designs 44, 52, 46 and 13, issue 339, ADR 0259 and 0266 and the glossary
are amended with dated notes.
2026-10-10 14:24:06 +02:00
jochen 952c984c34 Issue 362 resolved: ADR 0276 gains the own-path batch rule and a progressive insight, to-be 45 gains S18 and S19
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-10 14:14:56 +02:00
13 changed files with 396 additions and 12 deletions
+7 -1
View File
@@ -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
@@ -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.
@@ -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 <node> <module> <name> [--at <desk>]`, 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;
@@ -164,6 +164,18 @@ either, because **delivery group** already names deliveries that share a head br
repository exist and that none names its merge, or that the controller holds none) and listed by
`stalled` at once, so probe D14 raises it for the operator.
> **Decided during the build, 2026-10-10** (on the operator's delegation). Decision 2 batches every merge of the
> window together, and decision 4 starts a walk on the controller's own path without mesh-delivery's word. A
> batch holding both a merge on that path and a catalogue merge would therefore start the catalogue's deliveries
> without their word: their turn in a delivery group, their holds, and the order of decision 5 would be skipped.
> So **a merge on the controller's own path never shares a batch with a merge that waits for the delivery's
> word.** Such a merge goes into a batch of its own, with its own window; the open catalogue batch closes and is
> cut by the normal rules, and queues behind the own-path walk like any other. Catalogue merges heard during the
> own-path batch's window join the next catalogue batch. An own-path walk being cut folds no catalogue walk that
> waits for its word: that walk's merges are batched again behind it, and the walk names that batch as the one
> that took it over, so no catalogue delivery ever skips its turn. Checked by the controller's test
> `TestAMergeOnTheControllersPathNeverSharesABatch`.
## Consequences
- **A merge waits up to 90 seconds before it is planned**, and up to 10 minutes in a busy period. A walk
@@ -181,6 +193,11 @@ either, because **delivery group** already names deliveries that share a head br
restart-safe; the walk's record names several repositories and commits instead of one, and every reader of
it (`plans`, S3, S16, `plan-moved`, mesh-delivery) changes with it. The store gains the batch and the
merges a walk answers, as a numbered migration and in the consolidated schema.
> **Progressive insight — 2026-10-10.** The controller has no consolidated schema file: a fresh install runs its
> numbered migrations from the first, embedded in the build, so the numbered migration (0089) is the whole of
> the schema change. The bullet above said "as a numbered migration and in the consolidated schema"; that second
> half describes mesh-delivery's kind of store, not the controller's. The decision stands.
- **The replay of issue 362** in the lab raises a mesh, merges two commits of one repository 18 seconds
apart, and passes when one walk delivers both without a person; it fails on the commit before the change.
@@ -0,0 +1,281 @@
---
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 <id>` shows it whole, with the same fingerprint.
> **Progressive insight — 2026-10-10.** Issue 383 in the issue tracker: the first proposals reached the
> operator's phone as "+ shares: media=‹path›", and the operator said they could not see what they were being
> asked to approve: a mount point, a share name or a private address is the thing being approved. Option 1 was
> rejected on the fact that the content rule refuses a path or an address "in any message that leaves the
> mesh"; the operator decided, and the rule now reads, that on a channel that proves who answers
> (`verified-sender`, Telegram today) the exact values are shown WHOLE, and only a value shaped like a secret
> is withheld there — the ask carries its explanation twice (the SDK's `Whole` beside `Explanation`), the
> router shows the whole only on such a channel and holds it to the secret rule alone, and refuses the ask as
> a whole when a secret's shape is left in it. Option 3 stands as decided for every channel that proves
> nothing (the desk) and for every condition's words. The decision stands: the operator reads the exact
> change and approves it; what changed is where the values may be read whole.
### 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 <module> <values | --clear>
[--node <node>] [--replace]` and `settings proposals [<id>]`, 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 <id>`, 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;
> **Progressive insight — 2026-10-10.** Issue 383: on the phone this read as one wall of text. The
> explanation is now the change alone — one line per key as above, then "Proposed by <who> at <time>" — and
> the router adds what every ask says (who asks, what each answer does, when it expires); on a channel that
> proves who answers the values are whole, as the insight above says. The fingerprint, where the whole is
> read and that the layer is set at once moved to the **Details** answer the ask's own message offers (the
> SDK's `Details`), shown line by line under the content rule. The telegram channel shows every message
> formatted, never reworded: the headline bold, a key line and a fingerprint in monospace, its markup escaped.
- **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 <id>, proposed by <who>".
`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 <node>`.
### 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 <node> <module> <name> [--at <desk>]`): 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 (migration 0090) and a table of secret asks (migration 0091).
## 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` |
| On a channel that proves who answers the values are whole and only a secret's shape is withheld; elsewhere they keep their shape; the message carries no fingerprint and no how-to (the insight of 2026-10-10) | the controller's `TestAProposalShowsItsValuesWholeOnlyWhereTheSenderIsProvenAndNeverASecret` (the masked and the whole change differ in exactly the masked lines); the router's `TestAnAsksWholeWordsAreShownWhereTheSenderIsProvenAndMaskedElsewhere` and `TestAnAskWhoseWholeWordsCarryASecretIsRefused`; the telegram channel's `TestAMessageIsRenderedInHTMLWithItsMarkupEscaped` |
| 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 <node>?" 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`,
migrations 0090 and 0091; mesh-catalog `modules/rofi` (the prompt names who asked)
@@ -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.
@@ -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/0090-a-layer-says-who-set-it.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 <module> <values | --clear>
[--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 [<id>]` 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
@@ -4,6 +4,7 @@ status: in-progress
code: [mesh-controller, mesh-host, mesh-tools, mesh-catalog, mesh-sdk, mesh-lab]
updated: 2026-10-10
decisions:
- 02-DECISIONS/0276-merges-are-assembled-in-a-rolling-window-and-each-batch-is-delivered-by-one-walk-with-one-commit-per-repository.md
- 02-DECISIONS/0271-a-send-held-for-the-buss-planned-step-is-told-to-the-operator-at-once-and-a-change-that-needs-it-says-so-before-it-merges.md
- 02-DECISIONS/0265-a-verb-that-reads-is-answered-on-the-serving-controllers-own-connection-and-every-connection-says-what-it-is.md
- 02-DECISIONS/0264-a-message-a-consumer-gives-up-on-is-kept-until-a-person-delivers-it-again-or-drops-it.md
@@ -238,6 +239,8 @@ signal and asserts its condition. `doctor signals` shows, for every row, the age
| S15 | a hand act with a cause already recorded | hand-act log | each act | the second within 14 days; clears when fewer than two remain within 14 days | `healer-wanted` (names the cause, and the healer that was not enough where one answers it) | warning | — |
| S16 | a walk waiting for its delivery's word is let go | mesh-delivery, through `deliver` (ADR 0239) | each merge whose walk waits | 30 min; urgent after 4 h | `waiting` (names the walk and `plans go`) | warning | — |
| S17 | a send held for the bus's planned step is told to a person (ADR 0271) | controller's plan | each send refused because it would replace the bus outside its step | none: the first refusal; cleared once the bus's machine has been sent the build the mesh holds | `bus-step-waiting`, for the operator (names the machines, what waits, the bus build from and to, since when and the `mesh-controller.bus` call); S3 leaves such a walk out | warning | — |
| S18 | a batch of merges is cut into its walk (ADR 0276) | controller's merge window | each batch | a minute past `merge-window-at-most` while no walk is open: the controller failed to cut it | `batch-not-cut` (names the batch and what it holds) | warning | — |
| S19 | a batch waiting behind an open walk is cut when it ends (ADR 0276) | controller's merge window | each batch closed while a walk is open | that walk's bound, a tier's bound for each of its tiers, from when the batch closed | `batch-behind-walk` (names the walk) | warning | — |
The bus advisories (S9) cost one read-only subscription: the server already publishes them. The
controller translates each into a condition naming the thing in the mesh's words, as the refused reply
@@ -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
@@ -236,6 +237,7 @@ withdrawn from routing and reported until it passes. A new word is a new version
| | `operator-first` | the operator can write unprompted | a simulated message yields a `message` envelope |
| trusting | `verified-sender` | the answer came from the operator's own account, through a holder no agent shares | a choice from an identity not on the list is dropped and reported; placement checked |
| | `exact-render` | the ask is shown as the router rendered it | rendered text equals the router's, byte for byte |
| | | *Amended 2026-10-10 (issue 383):* a channel may format the router's words (Telegram: the title bold, a key line and a fingerprint in monospace) and escapes its markup; it never rewords them | the words, formatting aside, equal the router's byte for byte — what a tap on the message is compared by |
| | `code-factor` | a typed code reaches the router by request and reply, never judged by the holder | a code never appears in an event; it is deleted from the conversation where the service allows |
| | `key-factor` | a security key's assertion over the router's challenge reaches the router | the challenge is the router's; the signature is checked by the router |
@@ -302,6 +304,13 @@ is not exempted.
- **A refusal is said to the sender** — the `notify` call or the ask is refused naming
the offending part, so the asker can rephrase or move the specific into a reference — and raises
`channel-refused`. The offending part is never sent to a channel.
- *Amended 2026-10-10, [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)'s progressive insight (issue 383):* **an ask may carry its explanation
whole** (`Whole` beside `Explanation`), its paths and addresses as they are: what the operator approves must be
readable where they approve it. The router shows the whole only on an ask's own message, on a kind declaring
`verified-sender` that was given the ask's answers, and holds it to the secret rule alone; a secret's shape in
it refuses the ask as a whole. The rule above holds for everything else: every condition's words, and every
kind that proves nothing. An ask may also say what its **Details** answer shows (`Details`), held to the rule
line by line.
### References
@@ -380,6 +389,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 |
@@ -130,6 +130,11 @@ controller's `plan-moved` event, never inferred from time.
- **One walk at a time.** Merges that arrive while a walk is open join the next batch. A walk that started
is never superseded. A walk still waiting for its word when the next batch is cut is folded into the new
walk, which answers every merge the folded one answered.
- **The controller's own path.** A merge that moves a module whose walk waits for nobody's word (the controller,
the node-engine, the tool runner, the bus, mesh-delivery) never shares a batch with one that waits for
mesh-delivery's: it has a batch of its own, with its own window, and the catalogue's batch queues behind its
walk like any other. An own-path walk folds no waiting catalogue walk; that walk's merges are batched again
behind it, so no catalogue delivery skips its turn (ADR 0276, decided during the build, 2026-10-10).
- **A late merge.** A merge heard after a later merge of its repository and branch was put in a walk is
answered by that walk: the assembling batch, the open walk, or, when that walk was delivered, it ends
`covered` at once.
+13 -5
View File
@@ -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:<uuid>@<mountpoint>`, 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 <node> mounts smb-credentials
--from <file>`. The process writes one credentials file per source, readable by root only. A source
--from <file>` 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 <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=<the old value>` 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
@@ -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
@@ -1,8 +1,9 @@
---
status: located
status: resolved
opened: 2026-10-10
located-in: [mesh-catalog modules/mesh-delivery (holder.go deliveryWalking), mesh-controller cmd/mesh-controller (upgrades.go SourceMoved, release_plan.go laterOnTheBranch)]
fixed-by:
fixed-by: mesh-catalog#171, mesh-controller#197
replay: R362
amended-design: 03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md
---
@@ -65,3 +66,14 @@ The operator decided the model that removes this shape, not only its bookkeeping
commit per repository, one walk at a time. The bookkeeping that tied a walk to an ended delivery is repaired as
well, and every wait of this shape has a bound and a condition. It is designed in to-be 47, *The merge window
and the batch*.
## Resolved
2026-10-10. mesh-delivery's side is mesh-catalog#171 (merged): a walk is tied to every live delivery whose
merge it answers, never to one that has ended, and a waiting walk no live delivery holds is listed by `stalled`
at once. The controller's side is mesh-controller#197: the merge window and the batch, one walk per batch with
one commit per repository, the walk naming every merge it answers, and the bounds of decision 9. The replay
R362 is registered in mesh-lab's replays register (mesh-lab#75), proved to fail on the commit before the
mesh-delivery fix and pass on it. The build raised one rule the record had not settled, decided during the
build and written into ADR 0276: a merge on the controller's own path never shares a batch with a catalogue
merge.