Fixes issue 589 (an agent session cannot ask the operator a question on Telegram and get the chosen answer back). Implements hq ADR 0332 (accepted: the knowledge base approved it and the operator approved it on 2026-10-12).
What. Two tools of the claude-code module:
claude_code_ask: headline (at most 60 characters), context in plain words, 2 to 4 options (label, what it means, exactly one recommended), why, on_expiry, by (who@node), expires_minutes (5 to 1430, 60 when absent). Kept in the new questions state (key q-<hex>) and published as an ask under this module's name on operator-channel. Every option is at the level approve and binds the digest of the question and of itself, so only a channel that proves who answered (Telegram) carries it. Waits a few seconds for the router's word, then answers with the key or the router's refusal (the content rule).
claude_code_answer: by key, says waiting / chosen (option, who, channel, when) / ended (expired, refused, cancelled, replaced) / not proven. Read from the router's own record and held to the kept question (asks.Warrant.For + Option.Performs). wait_seconds waits up to 60 s within one call.
At most two questions open at once (mesh-wide, counted in the questions bucket against the router's record), so one of the router's three open asks per asker is always left for a proposed instruction section (ADR 0274). The router's refusal on the decided subject is kept on the question, since a refused ask leaves no router record. The answer authorises nothing.
Second commit: a flake fixed in proposals.go.TestTheRoutersRefusalIsSaidAndDiscards failed on the build agent at the first head (decided ""): HeardEnd told the waiting Propose about the refusal before it wrote the discard, so a read right after found nothing. It now tells only after the write. The question path keeps the same order.
Why here, not a verb on operator-channel. A seat verb reached through a tool runner cannot tell which module or session called it; under this module's name the bus proves the asker, and the router stays the judge (ADR 0332 Considered Options).
Files. New questions.go, questions_test.go; proposal_tools.go registers the tools and routes the router's word on a q- ask to the questions; proposals.go (the order fix); module.json adds the questions state and the two tools; README. main.go is untouched (open #152 touches it; #178 touches proposals.go and README).
Tested.go test -race in modules/claude-code. MESH_CHECK_MODULES=claude-code MESH_GATE=<controller from mesh-controller main> sh merge-check.sh passes. Both required checks are green at e4e0ce7.
Design limits I chose (in the ADR): at most two questions open; expiry 5 to 1430 minutes, 60 by default; context at most 1500 bytes; one read waits at most 60 s.
Fixes issue 589 (an agent session cannot ask the operator a question on Telegram and get the chosen answer back). Implements hq ADR 0332 (accepted: the knowledge base approved it and the operator approved it on 2026-10-12).
**What.** Two tools of the claude-code module:
- `claude_code_ask`: headline (at most 60 characters), context in plain words, 2 to 4 options (label, what it means, exactly one `recommended`), `why`, `on_expiry`, `by` (who@node), `expires_minutes` (5 to 1430, 60 when absent). Kept in the new `questions` state (key `q-<hex>`) and published as an ask under this module's name on `operator-channel`. Every option is at the level approve and binds the digest of the question and of itself, so only a channel that proves who answered (Telegram) carries it. Waits a few seconds for the router's word, then answers with the key or the router's refusal (the content rule).
- `claude_code_answer`: by key, says waiting / chosen (option, who, channel, when) / ended (expired, refused, cancelled, replaced) / not proven. Read from the router's own record and held to the kept question (`asks.Warrant.For` + `Option.Performs`). `wait_seconds` waits up to 60 s within one call.
At most two questions open at once (mesh-wide, counted in the `questions` bucket against the router's record), so one of the router's three open asks per asker is always left for a proposed instruction section (ADR 0274). The router's refusal on the decided subject is kept on the question, since a refused ask leaves no router record. The answer authorises nothing.
**Second commit: a flake fixed in proposals.go.** `TestTheRoutersRefusalIsSaidAndDiscards` failed on the build agent at the first head (`decided ""`): `HeardEnd` told the waiting `Propose` about the refusal before it wrote the discard, so a read right after found nothing. It now tells only after the write. The question path keeps the same order.
**Why here, not a verb on `operator-channel`.** A seat verb reached through a tool runner cannot tell which module or session called it; under this module's name the bus proves the asker, and the router stays the judge (ADR 0332 Considered Options).
**Files.** New `questions.go`, `questions_test.go`; `proposal_tools.go` registers the tools and routes the router's word on a `q-` ask to the questions; `proposals.go` (the order fix); `module.json` adds the `questions` state and the two tools; README. `main.go` is untouched (open #152 touches it; #178 touches proposals.go and README).
**Tested.** `go test -race` in modules/claude-code. `MESH_CHECK_MODULES=claude-code MESH_GATE=<controller from mesh-controller main> sh merge-check.sh` passes. Both required checks are green at e4e0ce7.
**Design limits I chose** (in the ADR): at most two questions open; expiry 5 to 1430 minutes, 60 by default; context at most 1500 bytes; one read waits at most 60 s.
Design home: novox/hq#334.
mesh/merge-gate head 9446bec2f969 merged into trunk 9c9a76d220f8 — pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes …
A session that needs a decision could only ask in a terminal the operator may
not watch. claude_code_ask raises the question under this module's name at the
level approve, so only a channel that proves who answered carries it;
claude_code_answer reads the chosen option back by its key from the router's own
record, held to the question kept in the new questions state.
Deliverynovox/mesh-catalog@c4eebce598cb — delivered since 2026-10-12T21:33:38Z
Delivery plan — builds claude-code → ace, g14, novox, shanks; no bus step
tier 0: claude-code
ace: receives claude-code
g14: receives claude-code
novox: receives claude-code
shanks: receives claude-code
claude-code keeps data (ADR 0233): what it holds is backed up before it moves
grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine
Machines
claude-code on ace: passed — healthy 1 times over 40s
claude-code on the rest running it: sent
Transitions
2026-10-12 21:25 (new) → proposed (announced): novox/mesh-catalog#258's head announced
2026-10-12 21:27 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
2026-10-12 21:27 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-12 21:31 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
2026-10-12 21:31 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path — not a failed walk that carried a later commit for it, after which it waits for a walk of its own commit
2026-10-12 21:33 delivering → delivered (done): its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says
The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.
<!-- mesh-delivery:view -->
**Delivery** `novox/mesh-catalog@c4eebce598cb` — **delivered** since 2026-10-12T21:33:38Z
**Delivery plan** — builds claude-code → ace, g14, novox, shanks; no bus step
- tier 0: claude-code
- ace: receives claude-code
- g14: receives claude-code
- novox: receives claude-code
- shanks: receives claude-code
- claude-code keeps data (ADR 0233): what it holds is backed up before it moves
- grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine
**Machines**
- claude-code on ace: passed — healthy 1 times over 40s
- claude-code on the rest running it: sent
**Transitions**
- 2026-10-12 21:25 (new) → proposed (announced): novox/mesh-catalog#258's head announced
- 2026-10-12 21:27 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
- 2026-10-12 21:27 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-12 21:31 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
- 2026-10-12 21:31 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path — not a failed walk that carried a later commit for it, after which it waits for a walk of its own commit
- 2026-10-12 21:33 delivering → delivered (done): its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says
The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
Merge check at 9446bec2
Judged: head 9446bec2f969 merged into trunk 9c9a76d220f8; read beside it: mesh-controller 2f157148c6c8, mesh-controller-main 2f157148c6c8, mesh-host d8e785bb4b4b, mesh-tools 954e1a53eef9
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)
mesh/repo-check: FAIL — its merge-check.sh failed: --- FAIL: TestTheRoutersRefusalIsSaidAndDiscards (0.08s)
Delivery plan — builds claude-code → ace, g14, novox, shanks; no bus step
tier 0: claude-code
ace: receives claude-code
g14: receives claude-code
novox: receives claude-code
shanks: receives claude-code
claude-code keeps data (ADR 0233): what it holds is backed up before it moves
grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine
read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/questions_test.go
Run by the build seat on shanks as build-1791837914312821856 (builds --log build-1791837914312821856).
--- the gate: claude-code
note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here
claude-code: ok, 47 tool(s), keeps state servers, holdings, config, proposals, questions, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339)
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-12T20:39:25Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-12T20:39:25Z
modules the change moves:
- claude-code (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): claude-code
notes:
- modules/claude-code/README.md and modules/claude-code/cmd/claude-code/questions_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.003s
words: 12 retired words and 70 daemons' lines, none in the 45101 strings of 705 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.012s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 67, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.232s
testing modules/claude-code
[claude-code] removed /tmp/TestARemovalKeepsACopyAndLogsWhy2702621690/001/home/.claude/rules/hal-era.md from the home on the person's word, kept at /tmp/TestARemovalKeepsACopyAndLogsWhy2702621690/001/state/removed-from-home/2026-10-07/093000-instructions-hal-era/rules/hal-era.md: HAL is retired
[claude-code] removed /tmp/TestARemovalKeepsACopyAndLogsWhy2702621690/001/home/.claude/rules/hal-era.md from the home on the person's word, kept at /tmp/TestARemovalKeepsACopyAndLogsWhy2702621690/001/state/removed-from-home/2026-10-07/093000-instructions-hal-era-2/rules/hal-era.md: again
[claude-code] removed /tmp/TestARemovalKeepsACopyAndLogsWhy2702621690/001/home/.claude/skills/old from the home on the person's word, kept at /tmp/TestARemovalKeepsACopyAndLogsWhy2702621690/001/state/removed-from-home/2026-10-07/093000-skill-old/skills/old: superseded by the plugin's
[claude-code] removed /tmp/TestTheMemoryIsRemovedAndKept2414009028/001/home/.claude/CLAUDE.md from the home on the person's word, kept at /tmp/TestTheMemoryIsRemovedAndKept2414009028/001/state/removed-from-home/2026-10-07/093000-memory-CLAUDE/CLAUDE.md: the managed file says it now
[claude-code] removed /tmp/TestARemovalIsListedAndPutBack3469448569/001/home/.claude/rules/hal-era.md from the home on the person's word, kept at /tmp/TestARemovalIsListedAndPutBack3469448569/001/state/removed-from-home/2026-10-07/093000-instructions-hal-era/rules/hal-era.md: HAL is retired
[claude-code] removed /tmp/TestARemovalIsListedAndPutBack3469448569/001/home/.claude/skills/old from the home on the person's word, kept at /tmp/TestARemovalIsListedAndPutBack3469448569/001/state/removed-from-home/2026-10-07/093100-skill-old/skills/old: superseded
[claude-code] restored /tmp/TestARemovalIsListedAndPutBack3469448569/001/home/.claude/rules/hal-era.md from /tmp/TestARemovalIsListedAndPutBack3469448569/001/state/removed-from-home/2026-10-07/093000-instructions-hal-era/rules/hal-era.md, undoing its removal of 2026-10-07T09:30:00Z
[claude-code] restored /tmp/TestARemovalIsListedAndPutBack3469448569/001/home/.claude/skills/old from /tmp/TestARemovalIsListedAndPutBack3469448569/001/state/removed-from-home/2026-10-07/093100-skill-old/skills/old, undoing its removal of 2026-10-07T09:31:00Z
[claude-code] removed /tmp/TestACopyThatChangedIsNotPutBack2878857516/001/home/.claude/rules/r.md from the home on the person's word, kept at /tmp/TestACopyThatChangedIsNotPutBack2878857516/001/state/removed-from-home/2026-10-07/093000-instructions-r/rules/r.md: why
[claude-code] removed /tmp/TestACopyThatChangedIsNotPutBack2878857516/001/home/.claude/skills/s from the home on the person's word, kept at /tmp/TestACopyThatChangedIsNotPutBack2878857516/001/state/removed-from-home/2026-10-07/093000-skill-s/skills/s: why
[claude-code] proposal instr-86525fbad0e639e3 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-352d151a98f8f677 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-352d151a98f8f677 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b): the operator, via telegram (user id verified), chose Approve
[claude-code] proposal instr-b1028f2fa9112ba7 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-1a7b3f0c293b0b88 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-05e3ebe799b4de77 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-f59fc84434685434 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] REFUSED the answer to proposal instr-f59fc84434685434: the warrant answers an ask whose digest is "sha256:737f2b7735b4cbc8189b8e73c9c883c7cc42b32932b3993f12d7a2cd51d1e61f", and the ask instr-f59fc84434685434 kept here is sha256:e8ae531a12322d9d5e737b1c38a844dd41cce1cc6326b76b32846d81091ec9a3: it was not the ask the person was shown
[claude-code] proposal instr-5d4b7e49cce2a279 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] REFUSED the answer to proposal instr-5d4b7e49cce2a279: the proposal is not one this module makes: its text is not the text its digest names
[claude-code] proposal instr-54f7b7fe04db15a6 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] REFUSED the answer to proposal instr-54f7b7fe04db15a6: the warrant answers an ask whose digest is "sha256:efcd957bae152e5500de1e87ebd1772cb89d9605fa3de5a0d0619c54ffd8d222", and the ask instr-54f7b7fe04db15a6 kept here is sha256:cb67a23652b96a91be1fba45141d965d87b98005d3ceff65478e44645a68784d: it was not the ask the person was shown
[claude-code] proposal instr-f5932e1347f40adf (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] REFUSED the answer to proposal instr-f5932e1347f40adf: the warrant was given at 2026-10-11T04:12:00Z, after the ask instr-f5932e1347f40adf expired at 2026-10-11T03:02:00Z
[claude-code] proposal instr-94af098f45dfa419 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] REFUSED the answer to proposal instr-94af098f45dfa419: the option approve is approve, and the warrant says acknowledge
[claude-code] proposal instr-daeeb07846a05408 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] REFUSED the answer to proposal instr-daeeb07846a05408: the ask instr-daeeb07846a05408 ended chosen; no person chose
[claude-code] proposal instr-9f97a7cb6626224c (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] REFUSED the answer to proposal instr-9f97a7cb6626224c: the router's record is for claude-code's ask instr-0000000000000000
[claude-code] proposal instr-32d9e10c9cc797a2 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-32d9e10c9cc797a2 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b): the operator, via telegram (user id verified), chose Approve
[claude-code] proposal instr-2b5b8dcb16a3634f (remove mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-2b5b8dcb16a3634f (remove mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b): the operator, via telegram (user id verified), chose Approve
[claude-code] proposal instr-8d77b36791eb58d9 (add node builds, fingerprint e510 a5f5 820c e18f 38bb a043) asked of the operator
[claude-code] proposal instr-8d77b36791eb58d9 (add node builds, fingerprint e510 a5f5 820c e18f 38bb a043): the operator, via telegram (user id verified), chose Approve
[claude-code] proposal instr-8d77b36791eb58d9 (add node builds, fingerprint e510 a5f5 820c e18f 38bb a043): the operator, via telegram (user id verified), chose Approve
[claude-code] proposal instr-19e9ea3ee4c6d852 (add mesh x) could not be asked: permission denied
[claude-code] proposal instr-52100ec36ef64477 (add mesh long, fingerprint 7068 2608 e7b5 914a bbd5 1911) asked of the operator
[claude-code] proposal instr-b1dfd3c90cf99154 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-4e7063c855da59c2 (add mesh long, fingerprint 3eac c1e8 b4e2 2fe5 4f6c dc11) asked of the operator
[claude-code] REFUSED the answer to proposal instr-4e7063c855da59c2: the proposal is not one this module makes: its text is not the text its digest names
[claude-code] REFUSED the answer to proposal instr-aaaaaaaaaaaaaaaa: the proposal is not one this module makes: the text holds "@/": an @ after anything but a letter or a digit may be read in as a file, and a shared section refers to no file (novox/hq issue 340)
[claude-code] REFUSED the answer to proposal instr-aaaaaaaaaaaaaaaa: the proposal is not one this module makes: the text holds "@/": an @ after anything but a letter or a digit may be read in as a file, and a shared section refers to no file (novox/hq issue 340)
[claude-code] REFUSED the answer to proposal instr-bbbbbbbbbbbbbbbb: the proposal is not one this module makes: its name is not one
[claude-code] REFUSED the answer to proposal instr-cccccccccccccccc: the proposal is not one this module makes: its nodes are not listed once each, in order
[claude-code] REFUSED the answer to proposal instr-aaaaaaaaaaaaaaaa: the proposal is not one this module makes: the text holds "@/": an @ after anything but a letter or a digit may be read in as a file, and a shared section refers to no file (novox/hq issue 340)
[claude-code] REFUSED the answer to proposal instr-bbbbbbbbbbbbbbbb: the proposal is not one this module makes: its name is not one
[claude-code] REFUSED the answer to proposal instr-aaaaaaaaaaaaaaaa: the proposal is not one this module makes: the text holds "@/": an @ after anything but a letter or a digit may be read in as a file, and a shared section refers to no file (novox/hq issue 340)
[claude-code] REFUSED the answer to proposal instr-bbbbbbbbbbbbbbbb: the proposal is not one this module makes: its name is not one
[claude-code] REFUSED the answer to proposal instr-cccccccccccccccc: the proposal is not one this module makes: its nodes are not listed once each, in order
[claude-code] REFUSED the answer to proposal instr-dddddddddddddddd: the proposal is not one this module makes: the text is 16392 bytes, more than the 16384 a shared section may hold: every session reads all of it
[claude-code] proposal instr-2ee44e5fa257fb19 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-2ee44e5fa257fb19 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b): the operator, via telegram (user id verified), chose Approve
[claude-code] proposal instr-f66f8d0f20c772a1 (remove mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-0630ea942469d557 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-0556663ec4f2b7f2 (add node builds, fingerprint ff23 330d 0c95 f498 5bd5 2b98) asked of the operator
[claude-code] proposal instr-0556663ec4f2b7f2 (add node builds, fingerprint ff23 330d 0c95 f498 5bd5 2b98): the operator, via telegram (user id verified), chose Approve
[claude-code] proposal instr-c6f43bec51294229 (add node builds, fingerprint 8a35 b2e2 6ba0 15c6 6b1f 4570) asked of the operator
[claude-code] proposal instr-aafd34092e3643b3 (add mesh x, fingerprint 61eb b162 c522 567b aabe dfb1) asked of the operator
[claude-code] proposal instr-1bb558528d7cc50e (add mesh x, fingerprint 61eb b162 c522 567b aabe dfb1) asked of the operator
--- FAIL: TestTheRoutersRefusalIsSaidAndDiscards (0.08s)
proposals_test.go:677: decided ""
[claude-code] proposal instr-c0a873a17639bd95 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-2780666e55c0d389 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-2780666e55c0d389 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b): the operator, via telegram (user id verified), chose Approve
[claude-code] question q-21008bd92d9054a1 from the session fix-589 asked of the operator
[claude-code] question q-001b705412dfab9f from the session fix-589 asked of the operator
[claude-code] question q-d112c444b6bbba12 from the session fix-589 asked of the operator
[claude-code] REFUSED the answer to question q-d112c444b6bbba12: the warrant answers an ask whose digest is "sha256:64455f3b48410ad21ac4a7e13cd55e0c67c0ae3c34631ab418bd09a66c3e72d0", and the ask q-d112c444b6bbba12 kept here is sha256:1a6473422e94f1627094095ea78379a197d1e8acb45c18e158e0e2ebdca76d6b: it was not the ask the person was shown
[claude-code] question q-ca964671151f6cfa from the session fix-589 asked of the operator
[claude-code] REFUSED the answer to question q-ca964671151f6cfa: the warrant answers an ask whose digest is "sha256:0000", and the ask q-ca964671151f6cfa kept here is sha256:98b01be56699719a4d2285ed3846503fba604730eac8acba488c7481b9e53fe7: it was not the ask the person was shown
[claude-code] question q-7ba5314986a43c1c from the session fix-589 asked of the operator
[claude-code] REFUSED the answer to question q-7ba5314986a43c1c: the warrant was given at 2026-10-10T04:13:00Z, after the ask q-7ba5314986a43c1c expired at 2026-10-10T04:12:00Z
[claude-code] question q-00ece252afec9d0f from the session fix-589 asked of the operator
[claude-code] question q-26cfe751c59cbcaf from the session fix-589 asked of the operator
[claude-code] question q-862b87f2a477c92e from the session fix-589 asked of the operator
[claude-code] proposal instr-5993bbbf31fc5080 (add mesh x, fingerprint 61eb b162 c522 567b aabe dfb1) asked of the operator
[claude-code] question q-d28bb6e00e2b5edc from the session fix-589 asked of the operator
[claude-code] question q-9596ac7c4b0adbd7 from the session fix-589 asked of the operator
[claude-code] question q-0529b7b3471c1e98 from the session fix-589 asked of the operator
[claude-code] question q-f4282b0289fca2ec from the session fix-589 asked of the operator
[claude-code] removed /tmp/TestTheOperatorsHomeIsNotChangedThroughTheTools2375629080/003/home/.claude/rules/old.md from the home on the person's word, kept at /tmp/TestTheOperatorsHomeIsNotChangedThroughTheTools2375629080/003/state/removed-from-home/2026-10-12/204730-instructions-old/rules/old.md: the person asked
FAIL
FAIL claude-code/cmd/claude-code 2.906s
FAIL
--- what failed, picked from the whole of its output (the whole is in the build's log)
--- FAIL: TestTheRoutersRefusalIsSaidAndDiscards (0.08s)
proposals_test.go:677: decided ""
FAIL claude-code/cmd/claude-code 2.906s
**Merge check** at `9446bec2`
Judged: head `9446bec2f969` merged into trunk `9c9a76d220f8`; read beside it: mesh-controller `2f157148c6c8`, mesh-controller-main `2f157148c6c8`, mesh-host `d8e785bb4b4b`, mesh-tools `954e1a53eef9`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)
- `mesh/repo-check`: **FAIL** — its merge-check.sh failed: --- FAIL: TestTheRoutersRefusalIsSaidAndDiscards (0.08s)
**Delivery plan** — builds claude-code → ace, g14, novox, shanks; no bus step
- tier 0: claude-code
- ace: receives claude-code
- g14: receives claude-code
- novox: receives claude-code
- shanks: receives claude-code
- claude-code keeps data (ADR 0233): what it holds is backed up before it moves
- grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine
- read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/questions_test.go
Run by the build seat on shanks as `build-1791837914312821856` (`builds --log build-1791837914312821856`).
```
--- the gate: claude-code
note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here
claude-code: ok, 47 tool(s), keeps state servers, holdings, config, proposals, questions, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339)
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-12T20:39:25Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-12T20:39:25Z
modules the change moves:
- claude-code (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): claude-code
notes:
- modules/claude-code/README.md and modules/claude-code/cmd/claude-code/questions_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.003s
words: 12 retired words and 70 daemons' lines, none in the 45101 strings of 705 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.012s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 67, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.232s
testing modules/claude-code
[claude-code] removed /tmp/TestARemovalKeepsACopyAndLogsWhy2702621690/001/home/.claude/rules/hal-era.md from the home on the person's word, kept at /tmp/TestARemovalKeepsACopyAndLogsWhy2702621690/001/state/removed-from-home/2026-10-07/093000-instructions-hal-era/rules/hal-era.md: HAL is retired
[claude-code] removed /tmp/TestARemovalKeepsACopyAndLogsWhy2702621690/001/home/.claude/rules/hal-era.md from the home on the person's word, kept at /tmp/TestARemovalKeepsACopyAndLogsWhy2702621690/001/state/removed-from-home/2026-10-07/093000-instructions-hal-era-2/rules/hal-era.md: again
[claude-code] removed /tmp/TestARemovalKeepsACopyAndLogsWhy2702621690/001/home/.claude/skills/old from the home on the person's word, kept at /tmp/TestARemovalKeepsACopyAndLogsWhy2702621690/001/state/removed-from-home/2026-10-07/093000-skill-old/skills/old: superseded by the plugin's
[claude-code] removed /tmp/TestTheMemoryIsRemovedAndKept2414009028/001/home/.claude/CLAUDE.md from the home on the person's word, kept at /tmp/TestTheMemoryIsRemovedAndKept2414009028/001/state/removed-from-home/2026-10-07/093000-memory-CLAUDE/CLAUDE.md: the managed file says it now
[claude-code] removed /tmp/TestARemovalIsListedAndPutBack3469448569/001/home/.claude/rules/hal-era.md from the home on the person's word, kept at /tmp/TestARemovalIsListedAndPutBack3469448569/001/state/removed-from-home/2026-10-07/093000-instructions-hal-era/rules/hal-era.md: HAL is retired
[claude-code] removed /tmp/TestARemovalIsListedAndPutBack3469448569/001/home/.claude/skills/old from the home on the person's word, kept at /tmp/TestARemovalIsListedAndPutBack3469448569/001/state/removed-from-home/2026-10-07/093100-skill-old/skills/old: superseded
[claude-code] restored /tmp/TestARemovalIsListedAndPutBack3469448569/001/home/.claude/rules/hal-era.md from /tmp/TestARemovalIsListedAndPutBack3469448569/001/state/removed-from-home/2026-10-07/093000-instructions-hal-era/rules/hal-era.md, undoing its removal of 2026-10-07T09:30:00Z
[claude-code] restored /tmp/TestARemovalIsListedAndPutBack3469448569/001/home/.claude/skills/old from /tmp/TestARemovalIsListedAndPutBack3469448569/001/state/removed-from-home/2026-10-07/093100-skill-old/skills/old, undoing its removal of 2026-10-07T09:31:00Z
[claude-code] removed /tmp/TestACopyThatChangedIsNotPutBack2878857516/001/home/.claude/rules/r.md from the home on the person's word, kept at /tmp/TestACopyThatChangedIsNotPutBack2878857516/001/state/removed-from-home/2026-10-07/093000-instructions-r/rules/r.md: why
[claude-code] removed /tmp/TestACopyThatChangedIsNotPutBack2878857516/001/home/.claude/skills/s from the home on the person's word, kept at /tmp/TestACopyThatChangedIsNotPutBack2878857516/001/state/removed-from-home/2026-10-07/093000-skill-s/skills/s: why
[claude-code] proposal instr-86525fbad0e639e3 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-352d151a98f8f677 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-352d151a98f8f677 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b): the operator, via telegram (user id verified), chose Approve
[claude-code] proposal instr-b1028f2fa9112ba7 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-1a7b3f0c293b0b88 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-05e3ebe799b4de77 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-f59fc84434685434 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] REFUSED the answer to proposal instr-f59fc84434685434: the warrant answers an ask whose digest is "sha256:737f2b7735b4cbc8189b8e73c9c883c7cc42b32932b3993f12d7a2cd51d1e61f", and the ask instr-f59fc84434685434 kept here is sha256:e8ae531a12322d9d5e737b1c38a844dd41cce1cc6326b76b32846d81091ec9a3: it was not the ask the person was shown
[claude-code] proposal instr-5d4b7e49cce2a279 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] REFUSED the answer to proposal instr-5d4b7e49cce2a279: the proposal is not one this module makes: its text is not the text its digest names
[claude-code] proposal instr-54f7b7fe04db15a6 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] REFUSED the answer to proposal instr-54f7b7fe04db15a6: the warrant answers an ask whose digest is "sha256:efcd957bae152e5500de1e87ebd1772cb89d9605fa3de5a0d0619c54ffd8d222", and the ask instr-54f7b7fe04db15a6 kept here is sha256:cb67a23652b96a91be1fba45141d965d87b98005d3ceff65478e44645a68784d: it was not the ask the person was shown
[claude-code] proposal instr-f5932e1347f40adf (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] REFUSED the answer to proposal instr-f5932e1347f40adf: the warrant was given at 2026-10-11T04:12:00Z, after the ask instr-f5932e1347f40adf expired at 2026-10-11T03:02:00Z
[claude-code] proposal instr-94af098f45dfa419 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] REFUSED the answer to proposal instr-94af098f45dfa419: the option approve is approve, and the warrant says acknowledge
[claude-code] proposal instr-daeeb07846a05408 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] REFUSED the answer to proposal instr-daeeb07846a05408: the ask instr-daeeb07846a05408 ended chosen; no person chose
[claude-code] proposal instr-9f97a7cb6626224c (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] REFUSED the answer to proposal instr-9f97a7cb6626224c: the router's record is for claude-code's ask instr-0000000000000000
[claude-code] proposal instr-32d9e10c9cc797a2 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-32d9e10c9cc797a2 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b): the operator, via telegram (user id verified), chose Approve
[claude-code] proposal instr-2b5b8dcb16a3634f (remove mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-2b5b8dcb16a3634f (remove mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b): the operator, via telegram (user id verified), chose Approve
[claude-code] proposal instr-8d77b36791eb58d9 (add node builds, fingerprint e510 a5f5 820c e18f 38bb a043) asked of the operator
[claude-code] proposal instr-8d77b36791eb58d9 (add node builds, fingerprint e510 a5f5 820c e18f 38bb a043): the operator, via telegram (user id verified), chose Approve
[claude-code] proposal instr-8d77b36791eb58d9 (add node builds, fingerprint e510 a5f5 820c e18f 38bb a043): the operator, via telegram (user id verified), chose Approve
[claude-code] proposal instr-19e9ea3ee4c6d852 (add mesh x) could not be asked: permission denied
[claude-code] proposal instr-52100ec36ef64477 (add mesh long, fingerprint 7068 2608 e7b5 914a bbd5 1911) asked of the operator
[claude-code] proposal instr-b1dfd3c90cf99154 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-4e7063c855da59c2 (add mesh long, fingerprint 3eac c1e8 b4e2 2fe5 4f6c dc11) asked of the operator
[claude-code] REFUSED the answer to proposal instr-4e7063c855da59c2: the proposal is not one this module makes: its text is not the text its digest names
[claude-code] REFUSED the answer to proposal instr-aaaaaaaaaaaaaaaa: the proposal is not one this module makes: the text holds "@/": an @ after anything but a letter or a digit may be read in as a file, and a shared section refers to no file (novox/hq issue 340)
[claude-code] REFUSED the answer to proposal instr-aaaaaaaaaaaaaaaa: the proposal is not one this module makes: the text holds "@/": an @ after anything but a letter or a digit may be read in as a file, and a shared section refers to no file (novox/hq issue 340)
[claude-code] REFUSED the answer to proposal instr-bbbbbbbbbbbbbbbb: the proposal is not one this module makes: its name is not one
[claude-code] REFUSED the answer to proposal instr-cccccccccccccccc: the proposal is not one this module makes: its nodes are not listed once each, in order
[claude-code] REFUSED the answer to proposal instr-aaaaaaaaaaaaaaaa: the proposal is not one this module makes: the text holds "@/": an @ after anything but a letter or a digit may be read in as a file, and a shared section refers to no file (novox/hq issue 340)
[claude-code] REFUSED the answer to proposal instr-bbbbbbbbbbbbbbbb: the proposal is not one this module makes: its name is not one
[claude-code] REFUSED the answer to proposal instr-aaaaaaaaaaaaaaaa: the proposal is not one this module makes: the text holds "@/": an @ after anything but a letter or a digit may be read in as a file, and a shared section refers to no file (novox/hq issue 340)
[claude-code] REFUSED the answer to proposal instr-bbbbbbbbbbbbbbbb: the proposal is not one this module makes: its name is not one
[claude-code] REFUSED the answer to proposal instr-cccccccccccccccc: the proposal is not one this module makes: its nodes are not listed once each, in order
[claude-code] REFUSED the answer to proposal instr-dddddddddddddddd: the proposal is not one this module makes: the text is 16392 bytes, more than the 16384 a shared section may hold: every session reads all of it
[claude-code] proposal instr-2ee44e5fa257fb19 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-2ee44e5fa257fb19 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b): the operator, via telegram (user id verified), chose Approve
[claude-code] proposal instr-f66f8d0f20c772a1 (remove mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-0630ea942469d557 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-0556663ec4f2b7f2 (add node builds, fingerprint ff23 330d 0c95 f498 5bd5 2b98) asked of the operator
[claude-code] proposal instr-0556663ec4f2b7f2 (add node builds, fingerprint ff23 330d 0c95 f498 5bd5 2b98): the operator, via telegram (user id verified), chose Approve
[claude-code] proposal instr-c6f43bec51294229 (add node builds, fingerprint 8a35 b2e2 6ba0 15c6 6b1f 4570) asked of the operator
[claude-code] proposal instr-aafd34092e3643b3 (add mesh x, fingerprint 61eb b162 c522 567b aabe dfb1) asked of the operator
[claude-code] proposal instr-1bb558528d7cc50e (add mesh x, fingerprint 61eb b162 c522 567b aabe dfb1) asked of the operator
--- FAIL: TestTheRoutersRefusalIsSaidAndDiscards (0.08s)
proposals_test.go:677: decided ""
[claude-code] proposal instr-c0a873a17639bd95 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-2780666e55c0d389 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b) asked of the operator
[claude-code] proposal instr-2780666e55c0d389 (add mesh handoff-from-g14, fingerprint 6c06 0778 d360 23bd 6d1d cd1b): the operator, via telegram (user id verified), chose Approve
[claude-code] question q-21008bd92d9054a1 from the session fix-589 asked of the operator
[claude-code] question q-001b705412dfab9f from the session fix-589 asked of the operator
[claude-code] question q-d112c444b6bbba12 from the session fix-589 asked of the operator
[claude-code] REFUSED the answer to question q-d112c444b6bbba12: the warrant answers an ask whose digest is "sha256:64455f3b48410ad21ac4a7e13cd55e0c67c0ae3c34631ab418bd09a66c3e72d0", and the ask q-d112c444b6bbba12 kept here is sha256:1a6473422e94f1627094095ea78379a197d1e8acb45c18e158e0e2ebdca76d6b: it was not the ask the person was shown
[claude-code] question q-ca964671151f6cfa from the session fix-589 asked of the operator
[claude-code] REFUSED the answer to question q-ca964671151f6cfa: the warrant answers an ask whose digest is "sha256:0000", and the ask q-ca964671151f6cfa kept here is sha256:98b01be56699719a4d2285ed3846503fba604730eac8acba488c7481b9e53fe7: it was not the ask the person was shown
[claude-code] question q-7ba5314986a43c1c from the session fix-589 asked of the operator
[claude-code] REFUSED the answer to question q-7ba5314986a43c1c: the warrant was given at 2026-10-10T04:13:00Z, after the ask q-7ba5314986a43c1c expired at 2026-10-10T04:12:00Z
[claude-code] question q-00ece252afec9d0f from the session fix-589 asked of the operator
[claude-code] question q-26cfe751c59cbcaf from the session fix-589 asked of the operator
[claude-code] question q-862b87f2a477c92e from the session fix-589 asked of the operator
[claude-code] proposal instr-5993bbbf31fc5080 (add mesh x, fingerprint 61eb b162 c522 567b aabe dfb1) asked of the operator
[claude-code] question q-d28bb6e00e2b5edc from the session fix-589 asked of the operator
[claude-code] question q-9596ac7c4b0adbd7 from the session fix-589 asked of the operator
[claude-code] question q-0529b7b3471c1e98 from the session fix-589 asked of the operator
[claude-code] question q-f4282b0289fca2ec from the session fix-589 asked of the operator
[claude-code] removed /tmp/TestTheOperatorsHomeIsNotChangedThroughTheTools2375629080/003/home/.claude/rules/old.md from the home on the person's word, kept at /tmp/TestTheOperatorsHomeIsNotChangedThroughTheTools2375629080/003/state/removed-from-home/2026-10-12/204730-instructions-old/rules/old.md: the person asked
FAIL
FAIL claude-code/cmd/claude-code 2.906s
FAIL
--- what failed, picked from the whole of its output (the whole is in the build's log)
--- FAIL: TestTheRoutersRefusalIsSaidAndDiscards (0.08s)
proposals_test.go:677: decided ""
FAIL claude-code/cmd/claude-code 2.906s
```
mesh/merge-gate head e4e0ce761901 merged into trunk 9c9a76d220f8 — pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes …
mesh/repo-check head e4e0ce761901 merged into trunk 9c9a76d220f8 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
The refusal test failed on a loaded build agent: Propose returned on hearing the
refusal before HeardEnd had written the discard, so a reader right after saw
nothing decided. The question path keeps the same order.
Merge check at e4e0ce76
Judged: head e4e0ce761901 merged into trunk 9c9a76d220f8; read beside it: mesh-controller 2f157148c6c8, mesh-controller-main 2f157148c6c8, mesh-host d8e785bb4b4b, mesh-tools 954e1a53eef9
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)
mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
Delivery plan — builds claude-code → ace, g14, novox, shanks; no bus step
tier 0: claude-code
ace: receives claude-code
g14: receives claude-code
novox: receives claude-code
shanks: receives claude-code
claude-code keeps data (ADR 0233): what it holds is backed up before it moves
grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine
read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/questions_test.go
Run by the build seat on shanks as build-1791838311699019646 (builds --log build-1791838311699019646).
--- the gate: claude-code
note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here
claude-code: ok, 47 tool(s), keeps state servers, holdings, config, proposals, questions, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339)
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-12T20:48:25Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-12T20:48:25Z
modules the change moves:
- claude-code (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): claude-code
notes:
- modules/claude-code/README.md and modules/claude-code/cmd/claude-code/questions_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.003s
words: 12 retired words and 70 daemons' lines, none in the 45101 strings of 705 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.013s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 67, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.228s
testing modules/claude-code
ok claude-code/cmd/claude-code 3.724s
--- its merge-check-typescript.sh (typescript toolchain)
**Merge check** at `e4e0ce76`
Judged: head `e4e0ce761901` merged into trunk `9c9a76d220f8`; read beside it: mesh-controller `2f157148c6c8`, mesh-controller-main `2f157148c6c8`, mesh-host `d8e785bb4b4b`, mesh-tools `954e1a53eef9`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
**Delivery plan** — builds claude-code → ace, g14, novox, shanks; no bus step
- tier 0: claude-code
- ace: receives claude-code
- g14: receives claude-code
- novox: receives claude-code
- shanks: receives claude-code
- claude-code keeps data (ADR 0233): what it holds is backed up before it moves
- grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine
- read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/questions_test.go
Run by the build seat on shanks as `build-1791838311699019646` (`builds --log build-1791838311699019646`).
```
--- the gate: claude-code
note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here
claude-code: ok, 47 tool(s), keeps state servers, holdings, config, proposals, questions, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339)
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-12T20:48:25Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-12T20:48:25Z
modules the change moves:
- claude-code (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): claude-code
notes:
- modules/claude-code/README.md and modules/claude-code/cmd/claude-code/questions_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.003s
words: 12 retired words and 70 daemons' lines, none in the 45101 strings of 705 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.013s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 67, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.228s
testing modules/claude-code
ok claude-code/cmd/claude-code 3.724s
--- its merge-check-typescript.sh (typescript toolchain)
```
Independent review against ADR 0332 (accepted, version 8), issue 589.
WHAT I RAN (clone at this head, GOTMPDIR=/var/tmp)
go vet ./... clean; go test -race -count=1 ./... ok; gofmt clean.
merge-check.sh with MESH_CHECK_MODULES=claude-code and MESH_GATE built from mesh-controller main (2f15714): exit 0, "long-running resources without health: 67, as kept", claude-code tests ok.
Statuses at this head: mesh/merge-gate success (claude-code to ace, g14, novox, shanks, every machine composes), mesh/repo-check success, mesh/delivery success.
Mutations (each run against the full test file): cap set to 3, the router-ended skip in openQuestions removed, QuestionProblem removed from AskQuestion, the Ended write in HeardQuestionEnd removed, For removed (Option looked up by id instead), the 60 s clamp removed: each makes a test fail. Removing Performs alone, or the re-check of QuestionProblem on the kept question in ReadAnswer, makes no test fail; both are redundant behind For's ask digest (the digest covers every option's Binds), so this is defence in depth, not a gap.
proposals.go ordering fix: with a 200 ms sleep before writeApproved, the old ordering fails TestTheRoutersRefusalIsSaidAndDiscards (decided ""), the new ordering passes. Every path after the known-proposal check still records the end (read error, already decided, after the write), so no other behaviour changes. The fix is right.
CLAUSE BY CLAUSE
1 holds: published on asks.AskSubject(Module), no seat verb added. 2 holds: headline <= 60 runes, 2-4 options, exactly one recommended with why, on-expiry, 5 min to ProposalLasts (24 h - 10 min), 60 min default, who@node with the node checked against the runner's own. 3 holds as written (the router judges; a refusal is said in words). 4 holds: every option Approve, Binds = ActDigest of the whole question and the option. 5 holds: key after at most RouterAnswersWithin (8 s); waiting / chosen (id, label, who, channel, when) / ended / not proven; one call clamped to 60 s. 6 holds: warrant checked with asks.Warrant.For (same asker, same ask id, same ask digest, offered option, same level, warrant time not after expiry) and Option.Performs; tests cover a question rewritten after asking, another ask's digest, and a choice after expiry. 7, 9, 10 hold.
WHAT TO CHANGE
A secret is kept on the bus before the router refuses it. AskQuestion stores the question in the questions bucket (store.Create) and only then lets the router hold the words to the content rule. A context with a token is therefore written to a bucket every node reads, and nothing ever deletes a question. Propose avoids exactly this by running ProposedTextProblem's secretShaped patterns before Create, and node.go states that a secret only ever travels on a request. Run secretShaped (and invisible) over every text field (headline, context, why, on_expiry, each option's label and means) in QuestionProblem, so a secret-shaped question is refused with "Nothing was asked" and nothing is kept. Add one test: a context holding a token-shaped string is refused and store.m stays empty.
invisible() is applied to headline, context, why and on_expiry only. Option labels and means are shown on the phone too; include them.
SMALLER (fix now or open a follow-up issue)
3. The two-question cap is check-then-create, so it is not atomic across nodes: two sessions on two nodes asking at the same moment can both pass and open three questions, which takes the ask clause 8 keeps for a proposal. openQuestions also returns nil when store.Keys fails, so the cap fails open. Fail closed on a Keys error at least.
4. A question whose refusal was missed on the decided subject (the node was off) has no router record and counts as open until it expires, which can be up to 24 h minus 10 min; it holds a slot for that long.
5. Wording that the ADR's review removed has come back. The README says "a session reads a question at most once a minute" as a fact; ADR 0332 section 5 says nothing enforces that. The MostQuestions comment says "so a proposal always finds room"; the ADR says one ask is left for a proposal while two questions are open. The tool description says "Answers at once", where the code waits up to 8 s for the router.
6. by is optional in the schema and becomes "unnamed". ADR section 2 says a question carries the session's name. Mark by as required, or say in the description that a missing name is shown as unnamed.
7. Tests that are still missing: a warrant naming another asker or ask id, a warrant for an option that was not offered, and the exact edges (a 60 vs 61 character headline, expires_minutes 1430 vs 1431).
Nothing here is a reason to doubt the warrant checking; items 1 and 2 are what I would want before merging.
Verdict: request changes at e4e0ce76190169b2e75943a5063c79d651bcecdd
Independent review against ADR 0332 (accepted, version 8), issue 589.
WHAT I RAN (clone at this head, GOTMPDIR=/var/tmp)
- go vet ./... clean; go test -race -count=1 ./... ok; gofmt clean.
- merge-check.sh with MESH_CHECK_MODULES=claude-code and MESH_GATE built from mesh-controller main (2f15714): exit 0, "long-running resources without health: 67, as kept", claude-code tests ok.
- Statuses at this head: mesh/merge-gate success (claude-code to ace, g14, novox, shanks, every machine composes), mesh/repo-check success, mesh/delivery success.
- Mutations (each run against the full test file): cap set to 3, the router-ended skip in openQuestions removed, QuestionProblem removed from AskQuestion, the Ended write in HeardQuestionEnd removed, For removed (Option looked up by id instead), the 60 s clamp removed: each makes a test fail. Removing Performs alone, or the re-check of QuestionProblem on the kept question in ReadAnswer, makes no test fail; both are redundant behind For's ask digest (the digest covers every option's Binds), so this is defence in depth, not a gap.
- proposals.go ordering fix: with a 200 ms sleep before writeApproved, the old ordering fails TestTheRoutersRefusalIsSaidAndDiscards (decided ""), the new ordering passes. Every path after the known-proposal check still records the end (read error, already decided, after the write), so no other behaviour changes. The fix is right.
CLAUSE BY CLAUSE
1 holds: published on asks.AskSubject(Module), no seat verb added. 2 holds: headline <= 60 runes, 2-4 options, exactly one recommended with why, on-expiry, 5 min to ProposalLasts (24 h - 10 min), 60 min default, who@node with the node checked against the runner's own. 3 holds as written (the router judges; a refusal is said in words). 4 holds: every option Approve, Binds = ActDigest of the whole question and the option. 5 holds: key after at most RouterAnswersWithin (8 s); waiting / chosen (id, label, who, channel, when) / ended / not proven; one call clamped to 60 s. 6 holds: warrant checked with asks.Warrant.For (same asker, same ask id, same ask digest, offered option, same level, warrant time not after expiry) and Option.Performs; tests cover a question rewritten after asking, another ask's digest, and a choice after expiry. 7, 9, 10 hold.
WHAT TO CHANGE
1. A secret is kept on the bus before the router refuses it. AskQuestion stores the question in the questions bucket (store.Create) and only then lets the router hold the words to the content rule. A context with a token is therefore written to a bucket every node reads, and nothing ever deletes a question. Propose avoids exactly this by running ProposedTextProblem's secretShaped patterns before Create, and node.go states that a secret only ever travels on a request. Run secretShaped (and invisible) over every text field (headline, context, why, on_expiry, each option's label and means) in QuestionProblem, so a secret-shaped question is refused with "Nothing was asked" and nothing is kept. Add one test: a context holding a token-shaped string is refused and store.m stays empty.
2. invisible() is applied to headline, context, why and on_expiry only. Option labels and means are shown on the phone too; include them.
SMALLER (fix now or open a follow-up issue)
3. The two-question cap is check-then-create, so it is not atomic across nodes: two sessions on two nodes asking at the same moment can both pass and open three questions, which takes the ask clause 8 keeps for a proposal. openQuestions also returns nil when store.Keys fails, so the cap fails open. Fail closed on a Keys error at least.
4. A question whose refusal was missed on the decided subject (the node was off) has no router record and counts as open until it expires, which can be up to 24 h minus 10 min; it holds a slot for that long.
5. Wording that the ADR's review removed has come back. The README says "a session reads a question at most once a minute" as a fact; ADR 0332 section 5 says nothing enforces that. The MostQuestions comment says "so a proposal always finds room"; the ADR says one ask is left for a proposal while two questions are open. The tool description says "Answers at once", where the code waits up to 8 s for the router.
6. by is optional in the schema and becomes "unnamed". ADR section 2 says a question carries the session's name. Mark by as required, or say in the description that a missing name is shown as unnamed.
7. Tests that are still missing: a warrant naming another asker or ask id, a warrant for an option that was not offered, and the exact edges (a 60 vs 61 character headline, expires_minutes 1430 vs 1431).
Nothing here is a reason to doubt the warrant checking; items 1 and 2 are what I would want before merging.
mesh/merge-gate head 14999f1d3ecb merged into trunk 9c9a76d220f8 — pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes …
mesh/repo-check head 14999f1d3ecb merged into trunk 9c9a76d220f8 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
A question was kept on the bus before the router judged its words, so a token in
it reached a bucket every node reads: every word, the options' too, is now held
to the secret shapes and invisible characters before anything is kept, and a
refused question keeps none of its words. The cap was check-then-create and
failed open: each open question now holds one of two slot keys taken by create
or compare-and-set, refusing when a slot cannot be read. A question the router
never recorded frees its slot after two minutes instead of at expiry. by is
required; wording matches ADR 0332.
Merge check at 14999f1d
Judged: head 14999f1d3ecb merged into trunk 9c9a76d220f8; read beside it: mesh-controller 2f157148c6c8, mesh-controller-main 2f157148c6c8, mesh-host 8e95c1d9ed2d, mesh-tools 954e1a53eef9
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)
mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
Delivery plan — builds claude-code → ace, g14, novox, shanks; no bus step
tier 0: claude-code
ace: receives claude-code
g14: receives claude-code
novox: receives claude-code
shanks: receives claude-code
claude-code keeps data (ADR 0233): what it holds is backed up before it moves
grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine
read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/questions_test.go
Run by the build seat on shanks as build-1791839725811336259 (builds --log build-1791839725811336259).
--- the gate: claude-code
note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here
claude-code: ok, 47 tool(s), keeps state servers, holdings, config, proposals, questions, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339)
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-12T21:08:25Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-12T21:08:25Z
modules the change moves:
- claude-code (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): claude-code
notes:
- modules/claude-code/README.md and modules/claude-code/cmd/claude-code/questions_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.003s
words: 12 retired words and 70 daemons' lines, none in the 45125 strings of 705 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.013s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/paths 0.007s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 67, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.244s
testing modules/claude-code
ok claude-code/cmd/claude-code 3.772s
--- its merge-check-typescript.sh (typescript toolchain)
**Merge check** at `14999f1d`
Judged: head `14999f1d3ecb` merged into trunk `9c9a76d220f8`; read beside it: mesh-controller `2f157148c6c8`, mesh-controller-main `2f157148c6c8`, mesh-host `8e95c1d9ed2d`, mesh-tools `954e1a53eef9`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
**Delivery plan** — builds claude-code → ace, g14, novox, shanks; no bus step
- tier 0: claude-code
- ace: receives claude-code
- g14: receives claude-code
- novox: receives claude-code
- shanks: receives claude-code
- claude-code keeps data (ADR 0233): what it holds is backed up before it moves
- grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine
- read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/questions_test.go
Run by the build seat on shanks as `build-1791839725811336259` (`builds --log build-1791839725811336259`).
```
--- the gate: claude-code
note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here
claude-code: ok, 47 tool(s), keeps state servers, holdings, config, proposals, questions, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339)
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-12T21:08:25Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-12T21:08:25Z
modules the change moves:
- claude-code (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): claude-code
notes:
- modules/claude-code/README.md and modules/claude-code/cmd/claude-code/questions_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.003s
words: 12 retired words and 70 daemons' lines, none in the 45125 strings of 705 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.013s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/paths 0.007s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 67, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.244s
testing modules/claude-code
ok claude-code/cmd/claude-code 3.772s
--- its merge-check-typescript.sh (typescript toolchain)
```
go vet clean; go test -race -count=1 ok; gofmt clean. merge-check.sh with MESH_CHECK_MODULES=claude-code and MESH_GATE built from mesh-controller main (2f15714): exit 0, the count of resources without health is 67, as kept.
Statuses on this head: the latest mesh/merge-gate, mesh/repo-check and mesh/delivery are all success. The earlier pending entries were superseded.
I broke the code on purpose to see whether the tests notice. Each of these makes a test fail: the secret scan removed; the refused question no longer tombstoned; the two-minute release removed. One gap: replacing both Create and Update in takeSlot with a plain Put (no compare-and-set at all) still passes TestTheCapHoldsAcrossNodesAskingAtOnce, 20 of 20 runs under -race. The fake store's locks serialize the race, so that test does not prove the compare-and-set. A deterministic test would: a store whose Get hands back a stale revision once, so the ErrChanged path is taken.
RESOLVED FROM REVIEW 105
Secret-shaped words and invisible characters are checked in every field, options included, before anything is kept. Tested.
A refused question keeps no words (tombstone). Tested.
The cap now uses slot-1 and slot-2, taken by create or compare-and-set. Read errors refuse the question (fail closed). Tested.
5 and 6. The wording is fixed, and by is required.
The missing tests were added: another asker, another ask, an option not offered, and the 60-character and 1430-minute edges.
SLOT LEAKS: none found. A slot is reclaimed whenever its question is missing or unreadable, was not asked, has ended, has expired, is closed in the router's record, or (see below) has no router record after two minutes. So no slot outlives its question's expiry (at most 24 h minus 10 min). An unparseable slot value is reclaimed by compare-and-set. The one way questions stop is a persistent read error on the bus. It refuses every question, as intended, and recovers when reads work again.
WHAT TO CHANGE
The two-minute release can free the slot of a question the router does go on to hold, and it tells the session the question ended when it did not. The router takes asks from the seat's work queue and records each one before it acknowledges the work, so a restart loses none (messenger asks.go, the header and TakeAsk). When the router is down or backlogged for more than two minutes (its own rollout, for example), the ask waits in the queue and has no record yet. In that window:
(a) ReadAnswer says "ended ... nothing waits for the operator's answer". The session then does its on-expiry action. Later the router takes the ask, the operator is shown it on Telegram, and may answer a question the session has already given up on.
(b) takeSlot frees the slot, so a third question can be asked. Once the router catches up it holds three questions of this module, and a proposal is refused. That breaks clause 8 (one ask always left for a proposal).
Suggested fix: before releasing an unrecorded question, publish a cancel for it on asks.CancelSubject(Module). The router drops a cancel for an ask it does not know, but a cancel queued behind the ask closes it as cancelled when the router catches up. Then tombstone the question as "cancelled: the router had not recorded it two minutes after it was asked", so ReadAnswer's "ended" is true. Confirm that an ask and its cancel come off the same queue in order; if they do not, keep the slot and say "waiting: not yet recorded by the router" until the question expires. Add a test: a question with no record after two minutes is cancelled on the bus before its slot is taken.
Not blocking: in TestTheCapHoldsAcrossNodesAskingAtOnce, see the deterministic compare-and-set test suggested above.
Verdict: request changes at 14999f1d3ecbbca4ef086d51424895a17da0301f
Re-review against my review 105.
WHAT I RAN
- go vet clean; go test -race -count=1 ok; gofmt clean. merge-check.sh with MESH_CHECK_MODULES=claude-code and MESH_GATE built from mesh-controller main (2f15714): exit 0, the count of resources without health is 67, as kept.
- Statuses on this head: the latest mesh/merge-gate, mesh/repo-check and mesh/delivery are all success. The earlier pending entries were superseded.
- I broke the code on purpose to see whether the tests notice. Each of these makes a test fail: the secret scan removed; the refused question no longer tombstoned; the two-minute release removed. One gap: replacing both Create and Update in takeSlot with a plain Put (no compare-and-set at all) still passes TestTheCapHoldsAcrossNodesAskingAtOnce, 20 of 20 runs under -race. The fake store's locks serialize the race, so that test does not prove the compare-and-set. A deterministic test would: a store whose Get hands back a stale revision once, so the ErrChanged path is taken.
RESOLVED FROM REVIEW 105
1. Secret-shaped words and invisible characters are checked in every field, options included, before anything is kept. Tested.
2. A refused question keeps no words (tombstone). Tested.
3. The cap now uses slot-1 and slot-2, taken by create or compare-and-set. Read errors refuse the question (fail closed). Tested.
5 and 6. The wording is fixed, and by is required.
7. The missing tests were added: another asker, another ask, an option not offered, and the 60-character and 1430-minute edges.
SLOT LEAKS: none found. A slot is reclaimed whenever its question is missing or unreadable, was not asked, has ended, has expired, is closed in the router's record, or (see below) has no router record after two minutes. So no slot outlives its question's expiry (at most 24 h minus 10 min). An unparseable slot value is reclaimed by compare-and-set. The one way questions stop is a persistent read error on the bus. It refuses every question, as intended, and recovers when reads work again.
WHAT TO CHANGE
1. The two-minute release can free the slot of a question the router does go on to hold, and it tells the session the question ended when it did not. The router takes asks from the seat's work queue and records each one before it acknowledges the work, so a restart loses none (messenger asks.go, the header and TakeAsk). When the router is down or backlogged for more than two minutes (its own rollout, for example), the ask waits in the queue and has no record yet. In that window:
(a) ReadAnswer says "ended ... nothing waits for the operator's answer". The session then does its on-expiry action. Later the router takes the ask, the operator is shown it on Telegram, and may answer a question the session has already given up on.
(b) takeSlot frees the slot, so a third question can be asked. Once the router catches up it holds three questions of this module, and a proposal is refused. That breaks clause 8 (one ask always left for a proposal).
Suggested fix: before releasing an unrecorded question, publish a cancel for it on asks.CancelSubject(Module). The router drops a cancel for an ask it does not know, but a cancel queued behind the ask closes it as cancelled when the router catches up. Then tombstone the question as "cancelled: the router had not recorded it two minutes after it was asked", so ReadAnswer's "ended" is true. Confirm that an ask and its cancel come off the same queue in order; if they do not, keep the slot and say "waiting: not yet recorded by the router" until the question expires. Add a test: a question with no record after two minutes is cancelled on the bus before its slot is taken.
Not blocking: in TestTheCapHoldsAcrossNodesAskingAtOnce, see the deterministic compare-and-set test suggested above.
mesh/merge-gate head c4eebce598cb merged into trunk 9c9a76d220f8 — pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes …
mesh/repo-check head c4eebce598cb merged into trunk 9c9a76d220f8 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
Freeing the slot two minutes after asking could let a backlogged router still show
the question after the session read it as ended, and let a third question in. The
seat's queue offers work again after an error and the router drops a cancel of an
ask it has not recorded, so a cancel cannot take it back; once expired, the router
refuses it if taken late. The slot test now uses a stale revision, so a plain
write in place of the compare-and-set fails it.
Merge check at c4eebce5
Judged: head c4eebce598cb merged into trunk 9c9a76d220f8; read beside it: mesh-controller 961f2e82ea1c, mesh-controller-main 961f2e82ea1c, mesh-host 8e95c1d9ed2d, mesh-tools 954e1a53eef9
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)
mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
Delivery plan — builds claude-code → ace, g14, novox, shanks; no bus step
tier 0: claude-code
ace: receives claude-code
g14: receives claude-code
novox: receives claude-code
shanks: receives claude-code
claude-code keeps data (ADR 0233): what it holds is backed up before it moves
grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine
read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/questions_test.go
Run by the build seat on g14 as build-1791840311319059620 (builds --log build-1791840311319059620).
--- the gate: claude-code
note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here
claude-code: ok, 47 tool(s), keeps state servers, holdings, config, proposals, questions, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339)
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-12T21:22:43Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-12T21:22:43Z
modules the change moves:
- claude-code (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): claude-code
notes:
- modules/claude-code/README.md and modules/claude-code/cmd/claude-code/questions_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.002s
words: 12 retired words and 70 daemons' lines, none in the 45130 strings of 705 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.012s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/paths 0.004s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 67, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.197s
testing modules/claude-code
ok claude-code/cmd/claude-code 3.386s
--- its merge-check-typescript.sh (typescript toolchain)
**Merge check** at `c4eebce5`
Judged: head `c4eebce598cb` merged into trunk `9c9a76d220f8`; read beside it: mesh-controller `961f2e82ea1c`, mesh-controller-main `961f2e82ea1c`, mesh-host `8e95c1d9ed2d`, mesh-tools `954e1a53eef9`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
**Delivery plan** — builds claude-code → ace, g14, novox, shanks; no bus step
- tier 0: claude-code
- ace: receives claude-code
- g14: receives claude-code
- novox: receives claude-code
- shanks: receives claude-code
- claude-code keeps data (ADR 0233): what it holds is backed up before it moves
- grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine
- read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/questions_test.go
Run by the build seat on g14 as `build-1791840311319059620` (`builds --log build-1791840311319059620`).
```
--- the gate: claude-code
note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here
claude-code: ok, 47 tool(s), keeps state servers, holdings, config, proposals, questions, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339)
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-12T21:22:43Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-12T21:22:43Z
modules the change moves:
- claude-code (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): claude-code
notes:
- modules/claude-code/README.md and modules/claude-code/cmd/claude-code/questions_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.002s
words: 12 retired words and 70 daemons' lines, none in the 45130 strings of 705 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.012s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/paths 0.004s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 67, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.197s
testing modules/claude-code
ok claude-code/cmd/claude-code 3.386s
--- its merge-check-typescript.sh (typescript toolchain)
```
An ask and its cancel are not reliably ordered. The seat's work is handed back with NakWithDelay (mesh-controller internal/link receive_nats.go, Hold), so a requeued ask can come after its cancel. The messenger's closeAsk returns without doing anything for an ask it has not recorded. So a cancel cannot take back an unrecorded question, and the fallback was the right choice.
A question taken late after its expiry is refused. asks.Ask.Check refuses an ask when !Expires.After(now), and the router runs that check in TakeAsk before it records anything. claude-code frees the slot at !now.Before(Expires), the same boundary. Now a question with no router record keeps its slot. It reads "waiting: not yet recorded ... until it expires", and at expiry it reads ended with the on-expiry action. That closes both problems from review 107: no false "ended", and no third question while the router is catching up.
The tests fail when the code is broken. Putting back a 2-minute release fails TestAQuestionTheRouterHasNotRecordedHoldsItsSlotUntilItExpires. Replacing the compare-and-set with a plain write fails TestASlotIsTakenOnlyAtTheRevisionRead, whether only the Update or both Create and Update are replaced. That cross-node test is now deterministic.
go vet clean; go test -race ok; gofmt clean. merge-check.sh with MESH_CHECK_MODULES=claude-code and MESH_GATE built from mesh-controller main (2f15714): exit 0, the count of resources without health stays at 67. Statuses on this head: mesh/merge-gate success (claude-code to ace, g14, novox, shanks), mesh/repo-check success, mesh/delivery success.
Not blocking: if the clock on the node that asked runs ahead of the router's, a slot is freed a few seconds before the router would refuse the ask. In that window a third question could be asked, and that is all the gap allows. Also, the entry == nil in the following case of ReadAnswer's switch can no longer be reached and could be dropped.
Verdict: approve at c4eebce598cb45ed8885bcc44a8e9cd4cb7aeb37
Re-review against my review 107.
I checked both claims in the code.
1. An ask and its cancel are not reliably ordered. The seat's work is handed back with NakWithDelay (mesh-controller internal/link receive_nats.go, Hold), so a requeued ask can come after its cancel. The messenger's closeAsk returns without doing anything for an ask it has not recorded. So a cancel cannot take back an unrecorded question, and the fallback was the right choice.
2. A question taken late after its expiry is refused. asks.Ask.Check refuses an ask when !Expires.After(now), and the router runs that check in TakeAsk before it records anything. claude-code frees the slot at !now.Before(Expires), the same boundary. Now a question with no router record keeps its slot. It reads "waiting: not yet recorded ... until it expires", and at expiry it reads ended with the on-expiry action. That closes both problems from review 107: no false "ended", and no third question while the router is catching up.
The tests fail when the code is broken. Putting back a 2-minute release fails TestAQuestionTheRouterHasNotRecordedHoldsItsSlotUntilItExpires. Replacing the compare-and-set with a plain write fails TestASlotIsTakenOnlyAtTheRevisionRead, whether only the Update or both Create and Update are replaced. That cross-node test is now deterministic.
go vet clean; go test -race ok; gofmt clean. merge-check.sh with MESH_CHECK_MODULES=claude-code and MESH_GATE built from mesh-controller main (2f15714): exit 0, the count of resources without health stays at 67. Statuses on this head: mesh/merge-gate success (claude-code to ace, g14, novox, shanks), mesh/repo-check success, mesh/delivery success.
Not blocking: if the clock on the node that asked runs ahead of the router's, a slot is freed a few seconds before the router would refuse the ask. In that window a third question could be asked, and that is all the gap allows. Also, the `entry == nil` in the following case of ReadAnswer's switch can no longer be reached and could be dropped.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes issue 589 (an agent session cannot ask the operator a question on Telegram and get the chosen answer back). Implements hq ADR 0332 (accepted: the knowledge base approved it and the operator approved it on 2026-10-12).
What. Two tools of the claude-code module:
claude_code_ask: headline (at most 60 characters), context in plain words, 2 to 4 options (label, what it means, exactly onerecommended),why,on_expiry,by(who@node),expires_minutes(5 to 1430, 60 when absent). Kept in the newquestionsstate (keyq-<hex>) and published as an ask under this module's name onoperator-channel. Every option is at the level approve and binds the digest of the question and of itself, so only a channel that proves who answered (Telegram) carries it. Waits a few seconds for the router's word, then answers with the key or the router's refusal (the content rule).claude_code_answer: by key, says waiting / chosen (option, who, channel, when) / ended (expired, refused, cancelled, replaced) / not proven. Read from the router's own record and held to the kept question (asks.Warrant.For+Option.Performs).wait_secondswaits up to 60 s within one call.At most two questions open at once (mesh-wide, counted in the
questionsbucket against the router's record), so one of the router's three open asks per asker is always left for a proposed instruction section (ADR 0274). The router's refusal on the decided subject is kept on the question, since a refused ask leaves no router record. The answer authorises nothing.Second commit: a flake fixed in proposals.go.
TestTheRoutersRefusalIsSaidAndDiscardsfailed on the build agent at the first head (decided ""):HeardEndtold the waitingProposeabout the refusal before it wrote the discard, so a read right after found nothing. It now tells only after the write. The question path keeps the same order.Why here, not a verb on
operator-channel. A seat verb reached through a tool runner cannot tell which module or session called it; under this module's name the bus proves the asker, and the router stays the judge (ADR 0332 Considered Options).Files. New
questions.go,questions_test.go;proposal_tools.goregisters the tools and routes the router's word on aq-ask to the questions;proposals.go(the order fix);module.jsonadds thequestionsstate and the two tools; README.main.gois untouched (open #152 touches it; #178 touches proposals.go and README).Tested.
go test -racein modules/claude-code.MESH_CHECK_MODULES=claude-code MESH_GATE=<controller from mesh-controller main> sh merge-check.shpasses. Both required checks are green ate4e0ce7.Design limits I chose (in the ADR): at most two questions open; expiry 5 to 1430 minutes, 60 by default; context at most 1500 bytes; one read waits at most 60 s.
Design home: novox/hq#334.
Delivery
novox/mesh-catalog@c4eebce598cb— delivered since 2026-10-12T21:33:38ZDelivery plan — builds claude-code → ace, g14, novox, shanks; no bus step
Machines
Transitions
The commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
9446bec2Judged: head
9446bec2f969merged into trunk9c9a76d220f8; read beside it: mesh-controller2f157148c6c8, mesh-controller-main2f157148c6c8, mesh-hostd8e785bb4b4b, mesh-tools954e1a53eef9mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)mesh/repo-check: FAIL — its merge-check.sh failed: --- FAIL: TestTheRoutersRefusalIsSaidAndDiscards (0.08s)Delivery plan — builds claude-code → ace, g14, novox, shanks; no bus step
Run by the build seat on shanks as
build-1791837914312821856(builds --log build-1791837914312821856).Merge check at
e4e0ce76Judged: head
e4e0ce761901merged into trunk9c9a76d220f8; read beside it: mesh-controller2f157148c6c8, mesh-controller-main2f157148c6c8, mesh-hostd8e785bb4b4b, mesh-tools954e1a53eef9mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)Delivery plan — builds claude-code → ace, g14, novox, shanks; no bus step
Run by the build seat on shanks as
build-1791838311699019646(builds --log build-1791838311699019646).Verdict: request changes at
e4e0ce7619Independent review against ADR 0332 (accepted, version 8), issue 589.
WHAT I RAN (clone at this head, GOTMPDIR=/var/tmp)
CLAUSE BY CLAUSE
1 holds: published on asks.AskSubject(Module), no seat verb added. 2 holds: headline <= 60 runes, 2-4 options, exactly one recommended with why, on-expiry, 5 min to ProposalLasts (24 h - 10 min), 60 min default, who@node with the node checked against the runner's own. 3 holds as written (the router judges; a refusal is said in words). 4 holds: every option Approve, Binds = ActDigest of the whole question and the option. 5 holds: key after at most RouterAnswersWithin (8 s); waiting / chosen (id, label, who, channel, when) / ended / not proven; one call clamped to 60 s. 6 holds: warrant checked with asks.Warrant.For (same asker, same ask id, same ask digest, offered option, same level, warrant time not after expiry) and Option.Performs; tests cover a question rewritten after asking, another ask's digest, and a choice after expiry. 7, 9, 10 hold.
WHAT TO CHANGE
SMALLER (fix now or open a follow-up issue)
3. The two-question cap is check-then-create, so it is not atomic across nodes: two sessions on two nodes asking at the same moment can both pass and open three questions, which takes the ask clause 8 keeps for a proposal. openQuestions also returns nil when store.Keys fails, so the cap fails open. Fail closed on a Keys error at least.
4. A question whose refusal was missed on the decided subject (the node was off) has no router record and counts as open until it expires, which can be up to 24 h minus 10 min; it holds a slot for that long.
5. Wording that the ADR's review removed has come back. The README says "a session reads a question at most once a minute" as a fact; ADR 0332 section 5 says nothing enforces that. The MostQuestions comment says "so a proposal always finds room"; the ADR says one ask is left for a proposal while two questions are open. The tool description says "Answers at once", where the code waits up to 8 s for the router.
6. by is optional in the schema and becomes "unnamed". ADR section 2 says a question carries the session's name. Mark by as required, or say in the description that a missing name is shown as unnamed.
7. Tests that are still missing: a warrant naming another asker or ask id, a warrant for an option that was not offered, and the exact edges (a 60 vs 61 character headline, expires_minutes 1430 vs 1431).
Nothing here is a reason to doubt the warrant checking; items 1 and 2 are what I would want before merging.
Merge check at
14999f1dJudged: head
14999f1d3ecbmerged into trunk9c9a76d220f8; read beside it: mesh-controller2f157148c6c8, mesh-controller-main2f157148c6c8, mesh-host8e95c1d9ed2d, mesh-tools954e1a53eef9mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)Delivery plan — builds claude-code → ace, g14, novox, shanks; no bus step
Run by the build seat on shanks as
build-1791839725811336259(builds --log build-1791839725811336259).Verdict: request changes at
14999f1d3eRe-review against my review 105.
WHAT I RAN
RESOLVED FROM REVIEW 105
5 and 6. The wording is fixed, and by is required.
SLOT LEAKS: none found. A slot is reclaimed whenever its question is missing or unreadable, was not asked, has ended, has expired, is closed in the router's record, or (see below) has no router record after two minutes. So no slot outlives its question's expiry (at most 24 h minus 10 min). An unparseable slot value is reclaimed by compare-and-set. The one way questions stop is a persistent read error on the bus. It refuses every question, as intended, and recovers when reads work again.
WHAT TO CHANGE
(a) ReadAnswer says "ended ... nothing waits for the operator's answer". The session then does its on-expiry action. Later the router takes the ask, the operator is shown it on Telegram, and may answer a question the session has already given up on.
(b) takeSlot frees the slot, so a third question can be asked. Once the router catches up it holds three questions of this module, and a proposal is refused. That breaks clause 8 (one ask always left for a proposal).
Suggested fix: before releasing an unrecorded question, publish a cancel for it on asks.CancelSubject(Module). The router drops a cancel for an ask it does not know, but a cancel queued behind the ask closes it as cancelled when the router catches up. Then tombstone the question as "cancelled: the router had not recorded it two minutes after it was asked", so ReadAnswer's "ended" is true. Confirm that an ask and its cancel come off the same queue in order; if they do not, keep the slot and say "waiting: not yet recorded by the router" until the question expires. Add a test: a question with no record after two minutes is cancelled on the bus before its slot is taken.
Not blocking: in TestTheCapHoldsAcrossNodesAskingAtOnce, see the deterministic compare-and-set test suggested above.
Merge check at
c4eebce5Judged: head
c4eebce598cbmerged into trunk9c9a76d220f8; read beside it: mesh-controller961f2e82ea1c, mesh-controller-main961f2e82ea1c, mesh-host8e95c1d9ed2d, mesh-tools954e1a53eef9mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)Delivery plan — builds claude-code → ace, g14, novox, shanks; no bus step
Run by the build seat on g14 as
build-1791840311319059620(builds --log build-1791840311319059620).Verdict: approve at
c4eebce598Re-review against my review 107.
I checked both claims in the code.
The tests fail when the code is broken. Putting back a 2-minute release fails TestAQuestionTheRouterHasNotRecordedHoldsItsSlotUntilItExpires. Replacing the compare-and-set with a plain write fails TestASlotIsTakenOnlyAtTheRevisionRead, whether only the Update or both Create and Update are replaced. That cross-node test is now deterministic.
go vet clean; go test -race ok; gofmt clean. merge-check.sh with MESH_CHECK_MODULES=claude-code and MESH_GATE built from mesh-controller main (2f15714): exit 0, the count of resources without health stays at 67. Statuses on this head: mesh/merge-gate success (claude-code to ace, g14, novox, shanks), mesh/repo-check success, mesh/delivery success.
Not blocking: if the clock on the node that asked runs ahead of the router's, a slot is freed a few seconds before the router would refuse the ask. In that window a third question could be asked, and that is all the gap allows. Also, the
entry == nilin the following case of ReadAnswer's switch can no longer be reached and could be dropped.