ADR 0274: hold the review's findings — invisible text, any @, the record judged at decide, older approvals never undo later ones
This commit is contained in:
+40
-14
@@ -107,8 +107,8 @@ The agent module (`claude-code`) gains two tools:
|
||||
name and the text, or `remove` for a section approved before. It refuses what issue 340 refuses and more (§5),
|
||||
records the **proposal** in the module's `proposals` state (the action, the scope, the nodes, the name, the text,
|
||||
the text's SHA-256 digest, the node it came from and when), and publishes one ask on the `operator-channel` seat
|
||||
under the module's own name. It answers with the proposal's id, its fingerprint (the digest's first twelve
|
||||
hexadecimal digits) and the question the operator was asked. **It changes nothing on any node.**
|
||||
under the module's own name. It answers with the proposal's id, its fingerprint (the digest's first 24
|
||||
hexadecimal digits, in groups of four) and the question the operator was asked. **It changes nothing on any node.**
|
||||
- **`claude_code_proposals`** lists the proposals, newest first: what each does, where it reaches, its
|
||||
fingerprint, and what this node made of it (waiting, approved, declined, discarded at expiry, refused). Given an
|
||||
id it answers that proposal's whole text and digest. It also lists the sections approved on this node.
|
||||
@@ -121,12 +121,13 @@ proposal too (§4).
|
||||
One ask per proposal, composed from the proposal alone, so that every node composes the same one:
|
||||
|
||||
- **headline**: "Add Claude instructions '<name>'?" ("Replace", "Remove"), shortened to "Add Claude instructions?"
|
||||
when the name makes it longer than the router allows;
|
||||
when the name makes it longer than the router allows, and ending "Part shown" whenever the phone does not show
|
||||
the whole text;
|
||||
- **explanation**, first line: "Add instructions '<name>' to every Claude session on the mesh?", or "on <the
|
||||
nodes>"; then the node it was proposed from and when, the text's lines, bytes and fingerprint, and that every new
|
||||
session there reads it as the mesh's own word until it is removed the same way; then **the whole text** when it is
|
||||
at most 2000 bytes and 40 lines and every line may leave the mesh, or else **its first lines** (at most 20 lines
|
||||
and 1200 bytes), each line that may carry a path, an address or a long word replaced by a marker, and the line
|
||||
at most 2000 bytes and 40 lines and every line may leave the mesh, or else "NOT SHOWN IN FULL here" and **its first
|
||||
lines** (at most 20 lines and 1200 bytes), each line that may carry a path, an address or a long word replaced by a marker, and the line
|
||||
that shows it all: `mesh-cli ask claude-code claude_code_proposals '{"id": "<id>"}'` at the controller's
|
||||
terminal, whose fingerprint must be the one on the phone;
|
||||
- **two options, Approve and Decline, both at the level approve**, as the controller's rehearsal asks: only a
|
||||
@@ -149,17 +150,30 @@ proposals, and once a minute:
|
||||
|
||||
1. reads the router's **own record** of the ask, under the module's name. The bus lets only the router write that
|
||||
record, and lets the module read only its own asks (ADR 0259 §3). An ask still open is left for the next look;
|
||||
2. composes the ask again from the proposal, and holds the warrant to it with the SDK's own check: the same asker
|
||||
2. judges the proposal as the module would have made it (§5's refusals, its name, scope, nodes, where it came from,
|
||||
its action, and the text against its digest), because anything holding a tool runner's credential can write a
|
||||
record and ask for it under the module's name; then composes the ask again from the proposal, and holds the
|
||||
warrant to it with the SDK's own check: the same asker
|
||||
and ask, **the ask's digest the router took**, a person's choice, an option the ask offered, at that option's
|
||||
level, given before the ask expired. Then the chosen option's bound act must be this proposal's act, and the
|
||||
text's digest must be the digest the proposal names. An ask published under the module's name with other words,
|
||||
a proposal changed after it was asked, a warrant for another ask, or an answer at another level is refused and
|
||||
said, and nothing is applied;
|
||||
said, and nothing is applied. A refusal is judged again on every look rather than kept, so an honest approval
|
||||
refused for a reason that passes (a node still on an older version of the module) is taken once it holds;
|
||||
3. on **Approve**, writes the section into **its own file** in the module's state directory, if the proposal reaches
|
||||
this node (the mesh scope, or a node scope naming it), and renders. On **Decline**, an expiry, a cancel, a
|
||||
replacement or the router's refusal it applies nothing. Either way **it records the proposal as decided and never
|
||||
decides it again**: a warrant heard twice, or replayed into the bus, changes nothing;
|
||||
4. takes closed asks oldest warrant first, so two approvals of the same section end with the later one.
|
||||
4. takes closed asks oldest warrant first, and keeps, for each section, when the last change applied to it was
|
||||
approved: **an approval older than that is never applied**, so an earlier approval read late, or replayed into the
|
||||
bus, never undoes a later replacement or removal.
|
||||
|
||||
A section is its scope, its nodes and its name together: a proposal of the same name for other nodes is another
|
||||
section, and replaces or removes nothing of the first.
|
||||
|
||||
**A record that cannot be read is never taken for an empty one.** The node's own file holding the approved sections
|
||||
and every decision is written whole, synced to the disk before it takes the old one's place; when it cannot be
|
||||
read, the node decides nothing, proposes nothing and renders no approved section, and says why.
|
||||
|
||||
The managed instruction file renders the approved sections under their own heading, "Instructions the operator
|
||||
approved", each with where it was proposed from and when it was approved, after the mesh's own text, the
|
||||
@@ -169,7 +183,9 @@ instructions table and the terminal's sections.
|
||||
away for longer than that, or joins later, cannot prove an approval made before, and **does not render it**: its
|
||||
`claude_code_proposals` says which approvals it could not prove, and the operator proposes the section again or sets
|
||||
it at the terminal. That is the cost of option 3 and is accepted: an approval the node cannot prove is the case this
|
||||
decision exists to refuse.
|
||||
decision exists to refuse. The same holds the other way: **a removal whose record is deleted from the bus before a
|
||||
node read it is not seen there**, so that node keeps the section; the node's list shows what it renders, and the
|
||||
operator proposes the removal again.
|
||||
|
||||
### 4. A removal needs the same approval
|
||||
|
||||
@@ -181,8 +197,13 @@ is what renders. A section set at the terminal is changed there, and a proposal
|
||||
### 5. What is refused, before anything is asked
|
||||
|
||||
- **What issue 340 refuses, kept**: a shell line (`` !`…` `` or a block opened with ```` ```! ````); front matter of
|
||||
any kind, naming `hooks`, `mcpServers`, `permissionMode` or `allowed-tools` when it holds one; and an `@`
|
||||
reference, which the instruction file reads in as a file;
|
||||
any kind, naming `hooks`, `mcpServers`, `permissionMode` or `allowed-tools` when it holds one; and **any `@` at the
|
||||
text's start or after anything but a letter or a digit** (any whitespace, Unicode's included, or punctuation),
|
||||
which the instruction file may read in as a file. Wider than the vendor's pattern on purpose;
|
||||
- **what a reader cannot see and a session reads**: a control character but a line break and a tab, a format
|
||||
character (zero-width characters, bidirectional overrides, the tag block), private use, a noncharacter, and any
|
||||
space or separator but the plain space. The operator approves what was shown, so nothing unshown may ride along;
|
||||
- a heading of level one to three, which would read as one of the managed file's own sections;
|
||||
- anything shaped like a secret (a key block, a known token's shape, a value given to a secret's name), because the
|
||||
text is read by every session and shown on a phone;
|
||||
- an empty text, one that is not text, or **more than 16 KiB**: every session reads all of it, every time;
|
||||
@@ -196,8 +217,8 @@ is what renders. A section set at the terminal is changed there, and a proposal
|
||||
|
||||
### 6. The warrant is recorded in the hand-act log
|
||||
|
||||
The controller gains the verb **`warranted`** (asker, ask, what was done). It reads **the router's own record** of
|
||||
that asker's ask, never the caller's account of it, and records who chose, through which channel, with which proofs
|
||||
The controller gains the verb **`warranted`** (asker, ask, and nothing else). It reads **the router's own record**
|
||||
of that asker's ask, never the caller's account of it, and records who chose, through which channel, with which proofs
|
||||
and which answer, under the verb `warrant` that the hand-act log already counts as a person's decision, not a repair.
|
||||
It records once per ask, under an id the ask decides, however many of the module's instances ask. It refuses an ask
|
||||
still open, one that ended without a person's choice, and another asker's. Every node that decided an Approve or a
|
||||
@@ -242,11 +263,16 @@ the ask's explanation, which carries it.
|
||||
| A removal needs the same approval; a deleted record removes nothing | the module's test: deleting the proposal record leaves the section rendered; proposing the removal leaves it; the approved removal takes it out |
|
||||
| A node scope reaches only its nodes | the module's test: a section approved for one node renders there and not on another |
|
||||
| Fail closed when no channel proves its sender, or the bus refuses the ask | the module's test: with no verified-sender channel the proposal is refused naming the terminal's line, and nothing is kept or asked; an ask the bus refuses leaves the proposal never asked and never active |
|
||||
| What a reader cannot see, or the file would read in, is refused | the module's test: an `@` after a vertical tab, a no-break space, an ideographic space, a line separator or a bracket; tag characters, a zero-width space, a bidirectional override, an escape, a carriage return, private use, and headings of level one to three are each refused; an e-mail-like word and a fourth-level heading are taken |
|
||||
| A record written straight into the bus is judged before its approval applies | the module's test: a record with an import, a heading in its name, its nodes out of order, or too long, asked for under the module's name and approved, is refused and renders nothing |
|
||||
| An older approval never undoes a later one | the module's test: a node that reads a removal before the addition it removes leaves the addition superseded and the section absent |
|
||||
| An unreadable record is never taken for an empty one | the module's test: over an unreadable record nothing is decided, proposed or written |
|
||||
| A section for some nodes is its own | the module's test: the same name for other nodes is an addition, and a removal for them is refused |
|
||||
| Issue 340's refusals hold | the module's test: a shell line, a shell block, front matter (with `hooks` or `allowed-tools`, or any), an `@` reference, a key block, a value given to a token's name, more than 16 KiB, a NUL, the home scope, `all` at the node scope and a name the terminal sets are each refused and nothing is asked |
|
||||
| A long text is shown in part and read whole at the terminal | the module's test: a line with a path is not on the phone, the explanation names the terminal's line and the fingerprint, and `claude_code_proposals` with the id answers the whole text and digest |
|
||||
| The text is never logged | the module's test: the module's log over a proposal and its approval holds the id and not a line of the text |
|
||||
| The tools are served | the module's test through its real tool dispatch: propose, list, and the registering tool's refusal naming the proposal tool |
|
||||
| The warrant is recorded once, from the router's record | the controller's tests: the entry carries who chose, via which channel and the proofs from the router's warrant; an ask still open, ended without a choice, another asker's or another ask's, or the controller's own is refused; the entry is written once however often it is asked, against a real bus; the verb's line is not taken for a repair and is not the terminal's alone |
|
||||
| The warrant is recorded once, from the router's record | the controller's tests: the entry carries who chose, via which channel and the proofs from the router's warrant; an ask still open, ended without a choice, another asker's or another ask's, or the controller's own is refused; the entry is written once however often it is asked, against a real bus; a word of the caller's own is refused; the verb's line is not taken for a repair and is not the terminal's alone |
|
||||
| Live, after rollout | an agent proposes a hand-over for every node; the operator's phone shows "Add instructions '…' to every Claude session on the mesh?" with Approve and Decline; after Approve a new session on another node reads the text, and `hand-acts` shows the warrant |
|
||||
|
||||
## References
|
||||
|
||||
@@ -509,9 +509,9 @@ learned, the state of a piece of work: each is a named section, read first in ev
|
||||
|
||||
| Step | What happens |
|
||||
|---|---|
|
||||
| propose | `claude_code_instructions_propose` (scope `mesh`, or `node` and the nodes; a name; the text, or `remove`) refuses what issue 340 refuses and a secret's shape, more than 16 KiB, a name the terminal sets, and at once when no channel proves its sender; keeps the proposal in the `proposals` state; publishes one ask under the module's name on `operator-channel` |
|
||||
| propose | `claude_code_instructions_propose` (scope `mesh`, or `node` and the nodes; a name; the text, or `remove`) refuses what issue 340 refuses (any `@` after a non-letter included), what a reader cannot see, headings of level one to three, a secret's shape, more than 16 KiB, a name the terminal sets, and at once when no channel proves its sender; keeps the proposal in the `proposals` state; publishes one ask under the module's name on `operator-channel` |
|
||||
| ask | composed from the proposal alone: "Add instructions '<name>' to every Claude session on the mesh?", the text whole when short and sayable, else its first lines, its size and fingerprint and the terminal line that shows all of it; Approve and Decline both at the level approve, each binding the proposal's act; a day to answer |
|
||||
| prove | every node reads the router's own record of the ask under the module's name, composes the ask again, holds the warrant to it (the ask's digest, the option's bound act, the text's digest, in time), and only then writes the section into its own `approved.json`; decided once per node, oldest warrant first |
|
||||
| prove | every node reads the router's own record of the ask under the module's name, composes the ask again, judges the record as the module would have made it, holds the warrant to it (the ask's digest, the option's bound act, the text's digest, in time), and only then writes the section into its own `approved.json`; decided once per node, oldest warrant first, never older than the section's last applied change |
|
||||
| render | the managed instruction file gains "Instructions the operator approved", after the terminal's sections, from the node's own file, never from the bus |
|
||||
| remove | a proposal with `remove`, approved the same way; a deleted bus record removes nothing |
|
||||
| record | each node that decided an answer asks the controller's `warranted`, which reads the router's record and writes the hand-act log once per ask |
|
||||
|
||||
Reference in New Issue
Block a user