Follow-up to the review of mesh-controller #186 (hq issue 353, hq #253). The controller needs to know whether the bus enforces its grant to ask, not only whether the list was sent: on 2026-10-09 the grant was written at 17:55:56 and enforced from the reload at 17:56:05.
What changes
nats_user_can keeps its answer (allowed, by) and adds in_force, file_written, server_loaded, why.
in_force is true only when the user list on the machine allows it and the server's config_load_time (from /varz) is at or after the list's last write (stat -c %y inside the container, to the nanosecond; checked against the pinned image, nats:2.11.17-alpine).
Anything not known is not in force, said in why: the write time unreadable, /varz unreadable, or a server that gives no load time.
Reviewers' checklist
Read only: docker exec … stat, and /varz, which the tools already read. Nothing changes the bus.
Fails closed: every unknown is in_force: false.
Test TestAGrantIsInForceOnlyWhenTheServerReloadedAfterTheListWasWritten (six cases). Mutation: taking the load-time comparison out fails the case "reloaded before the write".
Checks.MESH_GATE=<controller at main 7477346> MESH_CHECK_MODULES=nats sh merge-check.sh passes locally on the merge result (this branch contains main at ac7400d).
Order. This first, assigned and live on the machine holding the bus. Then mesh-controller's follow-up (fix/353-the-asker-judges-the-controllers-own-grant), which asks for in_force and asks the operator nothing while the bus module does not say it (an older module is not a yes). Do not merge without review.
Follow-up to the review of mesh-controller #186 (hq issue 353, hq #253). The controller needs to know whether the bus **enforces** its grant to ask, not only whether the list was sent: on 2026-10-09 the grant was written at 17:55:56 and enforced from the reload at 17:56:05.
**What changes**
- `nats_user_can` keeps its answer (`allowed`, `by`) and adds `in_force`, `file_written`, `server_loaded`, `why`.
- `in_force` is true only when the user list on the machine allows it **and** the server's `config_load_time` (from `/varz`) is at or after the list's last write (`stat -c %y` inside the container, to the nanosecond; checked against the pinned image, `nats:2.11.17-alpine`).
- Anything not known is not in force, said in `why`: the write time unreadable, `/varz` unreadable, or a server that gives no load time.
**Reviewers' checklist**
- Read only: `docker exec … stat`, and `/varz`, which the tools already read. Nothing changes the bus.
- Fails closed: every unknown is `in_force: false`.
- Test `TestAGrantIsInForceOnlyWhenTheServerReloadedAfterTheListWasWritten` (six cases). Mutation: taking the load-time comparison out fails the case "reloaded before the write".
**Checks.** `MESH_GATE=<controller at main 7477346> MESH_CHECK_MODULES=nats sh merge-check.sh` passes locally on the merge result (this branch contains main at ac7400d).
**Order.** This first, assigned and live on the machine holding the bus. Then mesh-controller's follow-up (`fix/353-the-asker-judges-the-controllers-own-grant`), which asks for `in_force` and asks the operator nothing while the bus module does not say it (an older module is not a yes). Do not merge without review.
Deliverynovox/mesh-catalog@4fdb047cc80f — delivered since 2026-10-12T12:14:06Z
Delivery plan — builds nats, sent nowhere; merging this needs a person's bus upgrade; 1 wait(s) for a person
tier 0: nats
novox: waits for a person: nats
merging this needs a person's bus upgrade: nats is never sent by an ordinary send, and until mesh_call mesh-controller.bus {"upgrade": "true", "why": "…", "reversible" or "irreversible": "true"} runs, nothing else is sent to novox either — unless the new build turns out the same as the one running there (issue 280)
nats provides mesh-bus: its consumers are sent again after it
nats keeps data (ADR 0233): what it holds is backed up before it moves
Transitions
2026-10-12 10:13 (new) → proposed (announced): novox/mesh-catalog#166's head announced
2026-10-12 10:16 proposed → checked (checked): the verdict names this commit
2026-10-12 10:16 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-12 12:13 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 12:13 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 12:14 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@4fdb047cc80f` — **delivered** since 2026-10-12T12:14:06Z
**Delivery plan** — builds nats, sent nowhere; merging this needs a person's bus upgrade; 1 wait(s) for a person
- tier 0: nats
- novox: waits for a person: nats
- merging this needs a person's bus upgrade: nats is never sent by an ordinary send, and until `mesh_call mesh-controller.bus {"upgrade": "true", "why": "…", "reversible" or "irreversible": "true"}` runs, nothing else is sent to novox either — unless the new build turns out the same as the one running there (issue 280)
- nats provides mesh-bus: its consumers are sent again after it
- nats keeps data (ADR 0233): what it holds is backed up before it moves
**Transitions**
- 2026-10-12 10:13 (new) → proposed (announced): novox/mesh-catalog#166's head announced
- 2026-10-12 10:16 proposed → checked (checked): the verdict names this commit
- 2026-10-12 10:16 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-12 12:13 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 12:13 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 12:14 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/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: nats)
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 nats, sent nowhere; merging this needs a person's bus upgrade; 1 wait(s) for a person
tier 0: nats
novox: waits for a person: nats
merging this needs a person's bus upgrade: nats is never sent by an ordinary send, and until mesh_call mesh-controller.bus {"upgrade": "true", "why": "…", "reversible" or "irreversible": "true"} runs, nothing else is sent to novox either — unless the new build turns out the same as the one running there (issue 280)
nats provides mesh-bus: its consumers are sent again after it
nats keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on novox as build-1791567752070504502 (builds --log build-1791567752070504502).
--- the gate: nats
nats: ok, 9 tool(s), keeps jetstream (valuable), snapshots (rebuildable); WARNING: server stay(s) up and say(s) not how it is ready — judged by liveness alone, refused from 2026-11-18 (ADR 0240 rule 8)
long-running resources without health: 1
files asking for a setting without saying whether it is trusted: 0
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 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-09T17:36:02Z 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-09T17:36:02Z
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): nats
--- 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 36212 strings of 627 files
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: 70, as kept
testing modules/nats
ok nats-tools/cmd/nats-tools 6.348s
--- its merge-check-typescript.sh (typescript toolchain)
**Merge check** at `2a2759ab`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: nats)
- `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 nats, sent nowhere; merging this needs a person's bus upgrade; 1 wait(s) for a person
- tier 0: nats
- novox: waits for a person: nats
- merging this needs a person's bus upgrade: nats is never sent by an ordinary send, and until `mesh_call mesh-controller.bus {"upgrade": "true", "why": "…", "reversible" or "irreversible": "true"}` runs, nothing else is sent to novox either — unless the new build turns out the same as the one running there (issue 280)
- nats provides mesh-bus: its consumers are sent again after it
- nats keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on novox as `build-1791567752070504502` (`builds --log build-1791567752070504502`).
```
--- the gate: nats
nats: ok, 9 tool(s), keeps jetstream (valuable), snapshots (rebuildable); WARNING: server stay(s) up and say(s) not how it is ready — judged by liveness alone, refused from 2026-11-18 (ADR 0240 rule 8)
long-running resources without health: 1
files asking for a setting without saying whether it is trusted: 0
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 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-09T17:36:02Z 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-09T17:36:02Z
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): nats
--- 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 36212 strings of 627 files
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: 70, as kept
testing modules/nats
ok nats-tools/cmd/nats-tools 6.348s
--- its merge-check-typescript.sh (typescript toolchain)
```
mesh/merge-gate pass: builds nats, sent nowhere; merging this needs a person's bus upgrade; 1 wait(s) for a person; every machine composes with the change …
nats_user_can read the user list on the machine. A list written is not a list the bus enforces until
the server reloads it: on 2026-10-09 the controller asked between the write and the reload's effect.
The answer now carries in_force — allowed in the file, and the server's config_load_time after the
file's write — with why when it is not, and anything not known is not in force.
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: nats)
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 nats, sent nowhere; merging this needs a person's bus upgrade; 1 wait(s) for a person
tier 0: nats
novox: waits for a person: nats
merging this needs a person's bus upgrade: nats is never sent by an ordinary send, and until mesh_call mesh-controller.bus {"upgrade": "true", "why": "…", "reversible" or "irreversible": "true"} runs, nothing else is sent to novox either — unless the new build turns out the same as the one running there (issue 280)
nats provides mesh-bus: its consumers are sent again after it
nats keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on novox as build-1791800028730561806 (builds --log build-1791800028730561806).
--- the gate: nats
nats: ok, 9 tool(s), keeps jetstream (valuable), snapshots (rebuildable); WARNING: server stay(s) up and say(s) not how it is ready — judged by liveness alone, refused from 2026-11-18 (ADR 0240 rule 8)
long-running resources without health: 1
files asking for a setting without saying whether it is trusted: 0
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 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-12T10:08: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-12T10:08:43Z
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): nats
--- 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 42354 strings of 682 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: 69, as kept
testing modules/nats
ok nats-tools/cmd/nats-tools 6.138s
--- its merge-check-typescript.sh (typescript toolchain)
**Merge check** at `4fdb047c`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: nats)
- `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 nats, sent nowhere; merging this needs a person's bus upgrade; 1 wait(s) for a person
- tier 0: nats
- novox: waits for a person: nats
- merging this needs a person's bus upgrade: nats is never sent by an ordinary send, and until `mesh_call mesh-controller.bus {"upgrade": "true", "why": "…", "reversible" or "irreversible": "true"}` runs, nothing else is sent to novox either — unless the new build turns out the same as the one running there (issue 280)
- nats provides mesh-bus: its consumers are sent again after it
- nats keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on novox as `build-1791800028730561806` (`builds --log build-1791800028730561806`).
```
--- the gate: nats
nats: ok, 9 tool(s), keeps jetstream (valuable), snapshots (rebuildable); WARNING: server stay(s) up and say(s) not how it is ready — judged by liveness alone, refused from 2026-11-18 (ADR 0240 rule 8)
long-running resources without health: 1
files asking for a setting without saying whether it is trusted: 0
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 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-12T10:08: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-12T10:08:43Z
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): nats
--- 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 42354 strings of 682 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: 69, as kept
testing modules/nats
ok nats-tools/cmd/nats-tools 6.138s
--- its merge-check-typescript.sh (typescript toolchain)
```
Verdict: approve, at head 4fdb047 (rebased onto main da296ca; the old head was 2a2759a, which was 200 commits behind main; main changed nothing under modules/nats, and the rebase was clean).
Independent review for hq issue 353.
What I checked:
The change touches only modules/nats/cmd/nats-tools (main.go, monitor.go, tools.go, tools_test.go). The Dockerfile, entrypoint.sh, module.json and build.on are untouched, so the server stays nats 2.11.17-alpine at the same pinned digest. Only the nats-tools bundle changes.
It reads only: docker exec ... stat -c %y on the user list, and /varz. It changes nothing on the bus.
It fails closed. An unknown write time, an unreadable /varz, or a server that gives no config_load_time each give in_force false, with the reason in why. allowed and by are unchanged, so the old answer stays readable.
The comparison is correct for how the list arrives. The node-engine writes the file only when its content differs (applyFile, contentSame) and writes it atomically into the mounted directory. The entrypoint sends SIGHUP within 5 s of a digest change. So in_force turns true at most about 5 s after a real change.
go test -race ./... in modules/nats passes. Mutation: replacing the load-time comparison with true fails "reloaded before the write", as the PR says.
Checks at 4fdb047: mesh/merge-gate pass, mesh/repo-check pass, mesh/delivery ready.
What merging this costs (for whoever merges it):
mesh-delivery.what-if says "merging this needs a person's bus upgrade: nats is never sent by an ordinary send, and until mesh-controller.bus upgrade runs, nothing else is sent to novox either". So after the merge, every delivery to the control node waits until the operator runs the bus upgrade. That is the operator's decision. The merge should happen only when they are ready to run it soon after.
The upgrade can be undone: revert this commit and run a bus upgrade again. No stream or stored data changes. I did not verify whether the upgrade recreates the server container when only the tools bundle changed.
Minor, not blocking:
Suppose the list changes and changes back inside one 5-second poll. Its digest then equals the last one, so the server is not reloaded, but the file's write time is newer than the last load. in_force then stays false until the next real change. It fails closed and is rare. I note it because the controller (#188) holds every ask while in_force is false.
Verdict: approve, at head 4fdb047 (rebased onto main da296ca; the old head was 2a2759a, which was 200 commits behind main; main changed nothing under modules/nats, and the rebase was clean).
Independent review for hq issue 353.
What I checked:
- The change touches only modules/nats/cmd/nats-tools (main.go, monitor.go, tools.go, tools_test.go). The Dockerfile, entrypoint.sh, module.json and build.on are untouched, so the server stays nats 2.11.17-alpine at the same pinned digest. Only the nats-tools bundle changes.
- It reads only: `docker exec ... stat -c %y` on the user list, and /varz. It changes nothing on the bus.
- It fails closed. An unknown write time, an unreadable /varz, or a server that gives no config_load_time each give in_force false, with the reason in why. `allowed` and `by` are unchanged, so the old answer stays readable.
- The comparison is correct for how the list arrives. The node-engine writes the file only when its content differs (applyFile, contentSame) and writes it atomically into the mounted directory. The entrypoint sends SIGHUP within 5 s of a digest change. So in_force turns true at most about 5 s after a real change.
- `go test -race ./...` in modules/nats passes. Mutation: replacing the load-time comparison with `true` fails "reloaded before the write", as the PR says.
- Checks at 4fdb047: mesh/merge-gate pass, mesh/repo-check pass, mesh/delivery ready.
What merging this costs (for whoever merges it):
- mesh-delivery.what-if says "merging this needs a person's bus upgrade: nats is never sent by an ordinary send, and until `mesh-controller.bus upgrade` runs, nothing else is sent to novox either". So after the merge, every delivery to the control node waits until the operator runs the bus upgrade. That is the operator's decision. The merge should happen only when they are ready to run it soon after.
- The upgrade can be undone: revert this commit and run a bus upgrade again. No stream or stored data changes. I did not verify whether the upgrade recreates the server container when only the tools bundle changed.
Minor, not blocking:
- Suppose the list changes and changes back inside one 5-second poll. Its digest then equals the last one, so the server is not reloaded, but the file's write time is newer than the last load. in_force then stays false until the next real change. It fails closed and is rare. I note it because the controller (#188) holds every ask while in_force is false.
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.
Follow-up to the review of mesh-controller #186 (hq issue 353, hq #253). The controller needs to know whether the bus enforces its grant to ask, not only whether the list was sent: on 2026-10-09 the grant was written at 17:55:56 and enforced from the reload at 17:56:05.
What changes
nats_user_cankeeps its answer (allowed,by) and addsin_force,file_written,server_loaded,why.in_forceis true only when the user list on the machine allows it and the server'sconfig_load_time(from/varz) is at or after the list's last write (stat -c %yinside the container, to the nanosecond; checked against the pinned image,nats:2.11.17-alpine).why: the write time unreadable,/varzunreadable, or a server that gives no load time.Reviewers' checklist
docker exec … stat, and/varz, which the tools already read. Nothing changes the bus.in_force: false.TestAGrantIsInForceOnlyWhenTheServerReloadedAfterTheListWasWritten(six cases). Mutation: taking the load-time comparison out fails the case "reloaded before the write".Checks.
MESH_GATE=<controller at main 7477346> MESH_CHECK_MODULES=nats sh merge-check.shpasses locally on the merge result (this branch contains main atac7400d).Order. This first, assigned and live on the machine holding the bus. Then mesh-controller's follow-up (
fix/353-the-asker-judges-the-controllers-own-grant), which asks forin_forceand asks the operator nothing while the bus module does not say it (an older module is not a yes). Do not merge without review.Delivery
novox/mesh-catalog@4fdb047cc80f— delivered since 2026-10-12T12:14:06ZDelivery plan — builds nats, sent nowhere; merging this needs a person's bus upgrade; 1 wait(s) for a person
mesh_call mesh-controller.bus {"upgrade": "true", "why": "…", "reversible" or "irreversible": "true"}runs, nothing else is sent to novox either — unless the new build turns out the same as the one running there (issue 280)Transitions
The commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
2a2759abmesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: nats)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 nats, sent nowhere; merging this needs a person's bus upgrade; 1 wait(s) for a person
mesh_call mesh-controller.bus {"upgrade": "true", "why": "…", "reversible" or "irreversible": "true"}runs, nothing else is sent to novox either — unless the new build turns out the same as the one running there (issue 280)Run by the build seat on novox as
build-1791567752070504502(builds --log build-1791567752070504502).2a2759abe9to4fdb047cc8Merge check at
4fdb047cmesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: nats)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 nats, sent nowhere; merging this needs a person's bus upgrade; 1 wait(s) for a person
mesh_call mesh-controller.bus {"upgrade": "true", "why": "…", "reversible" or "irreversible": "true"}runs, nothing else is sent to novox either — unless the new build turns out the same as the one running there (issue 280)Run by the build seat on novox as
build-1791800028730561806(builds --log build-1791800028730561806).Verdict: approve, at head
4fdb047(rebased onto main da296ca; the old head was2a2759a, which was 200 commits behind main; main changed nothing under modules/nats, and the rebase was clean).Independent review for hq issue 353.
What I checked:
docker exec ... stat -c %yon the user list, and /varz. It changes nothing on the bus.allowedandbyare unchanged, so the old answer stays readable.go test -race ./...in modules/nats passes. Mutation: replacing the load-time comparison withtruefails "reloaded before the write", as the PR says.4fdb047: mesh/merge-gate pass, mesh/repo-check pass, mesh/delivery ready.What merging this costs (for whoever merges it):
mesh-controller.bus upgraderuns, nothing else is sent to novox either". So after the merge, every delivery to the control node waits until the operator runs the bus upgrade. That is the operator's decision. The merge should happen only when they are ready to run it soon after.Minor, not blocking: