Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f60fdd263f |
+1
-7
@@ -279,9 +279,7 @@ 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) — 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
|
||||
verb, so a change that is the terminal's alone is taken (issue 339, issue 340). 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)).
|
||||
@@ -503,10 +501,6 @@ 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
|
||||
|
||||
-7
@@ -508,13 +508,6 @@ 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.
|
||||
|
||||
|
||||
-5
@@ -262,11 +262,6 @@ 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;
|
||||
|
||||
-17
@@ -164,18 +164,6 @@ 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
|
||||
@@ -193,11 +181,6 @@ 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.
|
||||
|
||||
|
||||
-281
@@ -1,281 +0,0 @@
|
||||
---
|
||||
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,9 +7,8 @@ code:
|
||||
- mesh-controller examples/postgres-provisioner
|
||||
- mesh-controller internal/inventory/given.go
|
||||
- mesh-controller cmd/mesh-controller/given.go
|
||||
updated: 2026-10-10
|
||||
updated: 2026-10-06
|
||||
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
|
||||
@@ -180,10 +179,3 @@ 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,10 +2,9 @@
|
||||
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, 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
|
||||
- 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
|
||||
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
|
||||
---
|
||||
@@ -30,21 +29,6 @@ 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,7 +4,6 @@ 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
|
||||
@@ -239,8 +238,6 @@ 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,9 +2,8 @@
|
||||
layer: to-be
|
||||
status: designed
|
||||
code: []
|
||||
updated: 2026-10-10
|
||||
updated: 2026-10-08
|
||||
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
|
||||
@@ -237,7 +236,6 @@ 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 |
|
||||
|
||||
@@ -304,13 +302,6 @@ 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
|
||||
|
||||
@@ -389,7 +380,6 @@ 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,11 +130,6 @@ 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.
|
||||
|
||||
@@ -5,9 +5,8 @@ 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-10
|
||||
updated: 2026-10-08
|
||||
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
|
||||
@@ -104,14 +103,8 @@ 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>` 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
|
||||
--from <file>`. 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
|
||||
@@ -213,10 +206,9 @@ 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**: 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.
|
||||
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.
|
||||
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,12 +36,6 @@ 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
|
||||
|
||||
+2
-14
@@ -1,9 +1,8 @@
|
||||
---
|
||||
status: resolved
|
||||
status: located
|
||||
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: mesh-catalog#171, mesh-controller#197
|
||||
replay: R362
|
||||
fixed-by:
|
||||
amended-design: 03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md
|
||||
---
|
||||
|
||||
@@ -66,14 +65,3 @@ 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.
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
status: open
|
||||
opened: 2026-10-09
|
||||
located-in: []
|
||||
fixed-by:
|
||||
amended-design:
|
||||
---
|
||||
|
||||
# 375. An agent cannot put a commit on the forge
|
||||
|
||||
## Symptom
|
||||
|
||||
On 2026-10-09, in an agent session on the operator's laptop, local times:
|
||||
|
||||
- An agent finished a change to this repository's connectivity design on a branch, committed it in a
|
||||
worktree, and ran `merge-check.sh`; all four checks passed.
|
||||
- `git push` was not possible: this repository's clone on the laptop has its push URL set to the literal
|
||||
`DISABLED`. The laptop's clones of the mesh's code repositories push normally. Who set it, when, and
|
||||
why is not recorded anywhere this session could find.
|
||||
- The forge's tools have no verb that pushes a branch or makes a commit. `gitea_api` refused
|
||||
`POST /repos/{owner}/{repo}/diffpatch`, the forge's endpoint that applies a patch to a branch: *not a
|
||||
write gitea_api makes* ([ADR 0266](../../02-DECISIONS/0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md)).
|
||||
- The one file write `gitea_api` allows is the contents endpoint, which takes the **whole file**,
|
||||
base64-encoded. The changed design document is about 90 KB, so the call would carry about 121 KB of
|
||||
base64 that the agent must reproduce character for character. That is not a workable path.
|
||||
|
||||
The change waited for the operator to push it by hand, and so did this report.
|
||||
|
||||
*2026-10-10.* The push URL is no longer disabled, so `git push` over ssh works from the laptop again, with
|
||||
the operator's key. Nobody recorded why it was disabled or why it was restored. The forge's tools are
|
||||
unchanged: still 35, still no write that puts a commit on a branch.
|
||||
|
||||
## Why it is an issue
|
||||
|
||||
- **The mesh's own rule for a change cannot be followed by an agent.** Every change goes through a branch,
|
||||
a pull request and a person's approval. An agent off the control node can make the branch locally and
|
||||
open the pull request through `gitea_create_pull_request`, but it cannot put the commits between the two
|
||||
on the forge.
|
||||
- **Where no tool covers a need, none is worked around** ([ADR 0245](../../02-DECISIONS/0245-a-verb-says-what-it-replaces-and-the-agent-is-guarded-from-working-round-the-mesh.md)).
|
||||
The only ways round are pushing to an explicit URL past the disabled one, or retyping a file as base64.
|
||||
The first undoes a setting someone made on purpose. The second risks a commit that silently differs
|
||||
from what was checked.
|
||||
- **The control node already has an answer the laptop does not.** On the control node the agent account
|
||||
holds a forge key of its own, so its pushes are its own (ADR 0266). An agent on any other machine has
|
||||
no equivalent.
|
||||
|
||||
## Other gaps in the forge's tools
|
||||
|
||||
Found by listing the forge module's 35 tools on 2026-10-09. Only the first one blocks work. The others
|
||||
can be done through `gitea_api`, which costs a raw call and a large answer each time.
|
||||
|
||||
| Gap | Today | Checked |
|
||||
|---|---|---|
|
||||
| **No push, commit or patch on a branch** | blocked, as above | yes |
|
||||
| **No commit history or compare** | `GET …/commits` through `gitea_api`: about 100 lines of JSON for one commit, its author and committer printed in full each time. `gitea_contains` answers only whether one commit contains another. | yes |
|
||||
| **No reading a pull request's reviews or line comments** | `gitea_list_comments` returns the conversation only. Reviews and comments on lines need `GET …/pulls/{n}/reviews` through `gitea_api`. An agent asked to address review feedback does not see it unless it knows to ask there. No *approve* tool is right, since approval is a person's. | yes |
|
||||
| **No creating a branch** | `POST …/branches` through `gitea_api` | no |
|
||||
| **No editing an issue** — title, body, labels | only close and reopen exist | no |
|
||||
| **No listing a directory** | `gitea_get_file` reads one file | no |
|
||||
| **No code search across repositories** | none | no |
|
||||
|
||||
*Checked* says whether this session called the raw endpoint and saw it answer. The rows marked *no* are
|
||||
read from the tool list and the forge's API, not tried.
|
||||
|
||||
## Open
|
||||
|
||||
- Who disabled the push URL on the laptop's clone of this repository and who restored it, and whether
|
||||
disabling it was meant to keep agents from pushing here or was a step in something else.
|
||||
- Whether an agent off the control node should push with a forge key of its own, as on the control node,
|
||||
or whether the forge module should take a commit as a patch and write it under the agent's account.
|
||||
Today an agent off the control node pushes with the operator's key, so its pushes are not its own.
|
||||
Either needs a decision record.
|
||||
Reference in New Issue
Block a user