Merge after #164 — this branch is built on feat/a-node-names-the-account-its-agents-run-as (it relies on #164's command verb being read-only, which closes module add through a verb, and uses its terminalRefusal). Until #164 merges, this diff also shows #164's seven commits; the two commits of this PR are the last two.
The route
An agent makes a repository of its own (or forks novox/mesh-catalog or novox/mesh-host), commits a module.json naming sudo (granting itself NOPASSWD) or mesh-host, and calls mesh-controller.build on it. The outcome was registered under that name from the agent's repository: RegisterModule upserts by name and overwrites source, and the trunk rule (ADR 0238) checked the trunk of the repository built — the agent's own. The next push made the agent root on every node.
The fix
Take-in (takeIn, which every outcome reaches whoever hears it and whichever verb asked it — build, rebuild, replay --register, assign with build, a plan, a merge): an outcome registers a module only from the module's registered repository; a module new to the catalogue only from a repository the catalogue already builds another module from (how a merge adds one, issue 300). Anything else only when the build request was kept as asked at the controller's terminal: new column build_request.at_terminal (migration 0084), set only when the asking process was started neither by a verb nor a seat call (MESH_VERB and MESH_CALLER both unset — runVerb sets both).
Ask (buildOneAsked, buildAndShow): through a verb, a build of a repository the catalogue builds no module from is not asked at all — the build node would run what the agent wrote.
Rollback (PreviousBuild): a failed gate puts back only a build of the failed build's repository. A refused build is still recorded, and a fork carries the commit the module was registered at.
Sources compare as one repository whether spelled as a seat path or as the URL composed from it (live sources are URLs, http://…:20000/novox/mesh-catalog.git).
Tests (each fails before, passes after)
TestABuildFromAnAgentsRepositoryIsNotRegisteredAsAModuleTheMeshHolds: own repo naming sudo, a fork of the catalogue, a fork of the node-engine — refused, catalogue unchanged, build recorded.
TestANewModuleFromARepositoryTheCatalogueDoesNotBuildFromIsNotRegistered (also an outcome no request was kept for).
Still working: TestAtTheTerminalAnotherRepositoryIsRegistered (move, new external module), TestADeliveryFromTheRegisteredRepositoryIsRegistered (a plan's rebuild, a merge adding a module).
Nine existing tests registered a brand-new module into an empty catalogue from an outcome nobody asked for; they now keep that first build as the operator's at the terminal (asTheOperator), and the gate test's outcome echoes its seat as a real one does.
What changes for the operator
A build from a new repository, or a module moved to another repository, is done at the controller's terminal (mesh-controller build …). A rebuild, a delivery, build --behind/--on and a merge adding a module to the catalogue repository work as before.
merge-check.sh run on this head (which contains current main).
**Merge after #164** — this branch is built on `feat/a-node-names-the-account-its-agents-run-as` (it relies on #164's `command` verb being read-only, which closes `module add` through a verb, and uses its `terminalRefusal`). Until #164 merges, this diff also shows #164's seven commits; the two commits of this PR are the last two.
## The route
An agent makes a repository of its own (or forks `novox/mesh-catalog` or `novox/mesh-host`), commits a `module.json` naming `sudo` (granting itself NOPASSWD) or `mesh-host`, and calls `mesh-controller.build` on it. The outcome was registered under that name from the agent's repository: `RegisterModule` upserts by name and overwrites `source`, and the trunk rule (ADR 0238) checked the trunk of *the repository built* — the agent's own. The next push made the agent root on every node.
## The fix
- **Take-in** (`takeIn`, which every outcome reaches whoever hears it and whichever verb asked it — `build`, `rebuild`, `replay --register`, `assign` with build, a plan, a merge): an outcome registers a module only from the module's registered repository; a module new to the catalogue only from a repository the catalogue already builds another module from (how a merge adds one, issue 300). Anything else only when the build request was kept as **asked at the controller's terminal**: new column `build_request.at_terminal` (migration 0084), set only when the asking process was started neither by a verb nor a seat call (`MESH_VERB` and `MESH_CALLER` both unset — `runVerb` sets both).
- **Ask** (`buildOneAsked`, `buildAndShow`): through a verb, a build of a repository the catalogue builds no module from is not asked at all — the build node would run what the agent wrote.
- **Rollback** (`PreviousBuild`): a failed gate puts back only a build of the failed build's repository. A refused build is still recorded, and a fork carries the commit the module was registered at.
Sources compare as one repository whether spelled as a seat path or as the URL composed from it (live sources are URLs, `http://…:20000/novox/mesh-catalog.git`).
## Tests (each fails before, passes after)
- `TestABuildFromAnAgentsRepositoryIsNotRegisteredAsAModuleTheMeshHolds`: own repo naming `sudo`, a fork of the catalogue, a fork of the node-engine — refused, catalogue unchanged, build recorded.
- `TestANewModuleFromARepositoryTheCatalogueDoesNotBuildFromIsNotRegistered` (also an outcome no request was kept for).
- `TestAVerbAsksNoBuildOfARepositoryTheCatalogueDoesNotBuildFrom`.
- `TestARollbackNeverPutsBackABuildFromAnotherRepository`.
- Still working: `TestAtTheTerminalAnotherRepositoryIsRegistered` (move, new external module), `TestADeliveryFromTheRegisteredRepositoryIsRegistered` (a plan's rebuild, a merge adding a module).
Nine existing tests registered a brand-new module into an empty catalogue from an outcome nobody asked for; they now keep that first build as the operator's at the terminal (`asTheOperator`), and the gate test's outcome echoes its seat as a real one does.
## What changes for the operator
A build from a new repository, or a module moved to another repository, is done at the controller's terminal (`mesh-controller build …`). A rebuild, a delivery, `build --behind/--on` and a merge adding a module to the catalogue repository work as before.
`merge-check.sh` run on this head (which contains current main).
On the control node every agent ran as the operator's account, which has
passwordless sudo, so an agent could become root without a person (hq ADR
0266). A node now names an agent account at the controller's terminal only;
the agent's module declares it never to become root, the node-engine judges
that, and the self-check (DA) raises agent-can-become-root while it does not
hold, so ADR 0259's router can rest on it.
The generic command verb ran node account and node agent-account, so an agent
could name itself the operator account and have the next send grant it root
(hq ADR 0266 review). Refuse every node subcommand but list and show through
any verb; refuse the operator account as the agent account in both
directions and well-known service accounts as an agent account; and count a
verdict heard more than 15 minutes ago as not judged, so stopping the
node-engine cannot freeze a healthy one. Re-pin mesh-host to its review head.
Review found a chain through the command verb: set the operator's key to one
the caller holds, rotate secrets so they are sealed to it too, read the sealed
copies, open them. Whoever may call a verb includes agents (hq ADR 0266), so
command now runs an allow list of reading forms, and operator, identity,
token, broker, api, licence and every secret command but rotate are refused
through any verb.
A flag before the subcommand (plans --json go <id>) still acts, so the
generic command verb judges every word of a plans line, retry included
(hq ADR 0266). The rest of this change, places and accesses at the
terminal and one line per setting, is issue 339's on main (#168, #170,
#172), which now does it for every process a verb runs; this branch's
--through-verb flag and its copy of those rules go.
The engine's pull request now keeps main's placement guard (issue 339) and adds
only the agent-home rule; the pin follows it so the two are judged together
(hq ADR 0266). Move it to the engine's merged commit before this merges.
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
node agent-account <node> <account> [home] stored a home that nothing used: the
account was created at /home/<account> while its files went to the named home
(hq ADR 0266). The engine reads a user's home only when it creates the account,
so an existing one is never moved.
build-agent keeps data (ADR 0233): what it holds is backed up before it moves
route-proxy provides route: its consumers are sent again after it
route-proxy keeps data (ADR 0233): what it holds is backed up before it moves
Transitions
2026-10-09 10:43 (new) → proposed (announced): novox/mesh-controller#174's head announced
2026-10-09 10:48 proposed → checked (checked): the verdict names this commit
2026-10-09 10:48 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-09 11:06 ready → held (hold): something holds it for a person: its group's composed check did not pass for the heads that merged, or it merged and no walk was opened for it
2026-10-09 11:35 held → covered (covered): covered: a later merge that contains it was delivered: novox/mesh-controller@eca6390d6f (merged as 33dc85d8 into main, walk plan-1791544480657912626): build-agent, mesh-controller, route-proxy
The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.
<!-- mesh-delivery:view -->
**Delivery** `novox/mesh-controller@95e7a7120ab0` — **covered** since 2026-10-09T11:35:27Z
**Delivery plan** — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
- tier 0: mesh-controller
- tier 1: build-agent
- tier 2: route-proxy
- ace: receives build-agent, route-proxy
- g14: receives build-agent
- novox: receives mesh-controller, build-agent, route-proxy
- shanks: receives build-agent
- build-agent keeps data (ADR 0233): what it holds is backed up before it moves
- route-proxy provides route: its consumers are sent again after it
- route-proxy keeps data (ADR 0233): what it holds is backed up before it moves
**Transitions**
- 2026-10-09 10:43 (new) → proposed (announced): novox/mesh-controller#174's head announced
- 2026-10-09 10:48 proposed → checked (checked): the verdict names this commit
- 2026-10-09 10:48 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-09 11:06 ready → held (hold): something holds it for a person: its group's composed check did not pass for the heads that merged, or it merged and no walk was opened for it
- 2026-10-09 11:35 held → covered (covered): covered: a later merge that contains it was delivered: novox/mesh-controller@eca6390d6fe4 (merged as 33dc85d8 into main, walk plan-1791544480657912626): build-agent, mesh-controller, route-proxy
The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
build-agent keeps data (ADR 0233): what it holds is backed up before it moves
route-proxy provides route: its consumers are sent again after it
route-proxy keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on shanks as build-1791539342120430846 (builds --log build-1791539342120430846).
--- the gate: build-agent, mesh-controller, route-proxy
mesh-controller: ok, 52 tool(s); WARNING: controller 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-09T08:07:39Z 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-09T08:07:39Z
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 3 module(s) in 3 tier(s): build-agent, mesh-controller, route-proxy
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-controller/cmd/mesh-builder 1.043s
ok github.com/novox/mesh-controller/cmd/mesh-controller 202.518s
ok github.com/novox/mesh-controller/examples/objectstore-provisioner 1.030s
ok github.com/novox/mesh-controller/examples/postgres-provisioner 1.037s
ok github.com/novox/mesh-controller/examples/redis-provisioner 1.021s
ok github.com/novox/mesh-controller/examples/route-proxy 1.188s
ok github.com/novox/mesh-controller/internal/artifacts 1.069s
ok github.com/novox/mesh-controller/internal/broker 2.769s
ok github.com/novox/mesh-controller/internal/builder 1.945s
ok github.com/novox/mesh-controller/internal/catalogue 5.013s
ok github.com/novox/mesh-controller/internal/conditions 2.507s
ok github.com/novox/mesh-controller/internal/envfile 1.041s
ok github.com/novox/mesh-controller/internal/facts 1.051s
ok github.com/novox/mesh-controller/internal/identity 4.738s
ok github.com/novox/mesh-controller/internal/inventory 86.348s
ok github.com/novox/mesh-controller/internal/lease 17.696s
ok github.com/novox/mesh-controller/internal/licences 4.309s
ok github.com/novox/mesh-controller/internal/licences/adapters 1.032s
ok github.com/novox/mesh-controller/internal/link 53.556s
ok github.com/novox/mesh-controller/internal/lint 2.152s
ok github.com/novox/mesh-controller/internal/outward 1.066s
ok github.com/novox/mesh-controller/internal/overlay 1.051s
ok github.com/novox/mesh-controller/internal/secrets 1.526s
ok github.com/novox/mesh-controller/internal/store 3.455s
ok github.com/novox/mesh-controller/internal/testbus 1.218s
ok github.com/novox/mesh-controller/internal/token 1.015s
**Merge check** at `c816da16`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, mesh-controller, route-proxy)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed
**Delivery plan** — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
- tier 0: mesh-controller
- tier 1: build-agent
- tier 2: route-proxy
- ace: receives build-agent, route-proxy
- g14: receives build-agent
- novox: receives mesh-controller, build-agent, route-proxy
- shanks: receives build-agent
- build-agent keeps data (ADR 0233): what it holds is backed up before it moves
- route-proxy provides route: its consumers are sent again after it
- route-proxy keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on shanks as `build-1791539342120430846` (`builds --log build-1791539342120430846`).
```
--- the gate: build-agent, mesh-controller, route-proxy
mesh-controller: ok, 52 tool(s); WARNING: controller 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-09T08:07:39Z 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-09T08:07:39Z
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 3 module(s) in 3 tier(s): build-agent, mesh-controller, route-proxy
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-controller/cmd/mesh-builder 1.043s
ok github.com/novox/mesh-controller/cmd/mesh-controller 202.518s
ok github.com/novox/mesh-controller/examples/objectstore-provisioner 1.030s
ok github.com/novox/mesh-controller/examples/postgres-provisioner 1.037s
ok github.com/novox/mesh-controller/examples/redis-provisioner 1.021s
ok github.com/novox/mesh-controller/examples/route-proxy 1.188s
ok github.com/novox/mesh-controller/internal/artifacts 1.069s
ok github.com/novox/mesh-controller/internal/broker 2.769s
ok github.com/novox/mesh-controller/internal/builder 1.945s
ok github.com/novox/mesh-controller/internal/catalogue 5.013s
ok github.com/novox/mesh-controller/internal/conditions 2.507s
ok github.com/novox/mesh-controller/internal/envfile 1.041s
ok github.com/novox/mesh-controller/internal/facts 1.051s
ok github.com/novox/mesh-controller/internal/identity 4.738s
ok github.com/novox/mesh-controller/internal/inventory 86.348s
ok github.com/novox/mesh-controller/internal/lease 17.696s
ok github.com/novox/mesh-controller/internal/licences 4.309s
ok github.com/novox/mesh-controller/internal/licences/adapters 1.032s
ok github.com/novox/mesh-controller/internal/link 53.556s
ok github.com/novox/mesh-controller/internal/lint 2.152s
ok github.com/novox/mesh-controller/internal/outward 1.066s
ok github.com/novox/mesh-controller/internal/overlay 1.051s
ok github.com/novox/mesh-controller/internal/secrets 1.526s
ok github.com/novox/mesh-controller/internal/store 3.455s
ok github.com/novox/mesh-controller/internal/testbus 1.218s
ok github.com/novox/mesh-controller/internal/token 1.015s
```
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh-host #61 dropped the two-minute limit inside the search's 15-minute
bound, asks pacman once, and calls a walk with errors incomplete; the pin
follows it so the two are judged together (hq ADR 0266). Move it to the
engine's merged commit before this merges.
Follow-ups of the review (commit 80ad37e; branch rebased on #164's re-pin d951f22):
Protected trunk, refused not raised. Through any verb, a registration also needs the source on the mesh's forge with its trunk (the branch the module follows, else the default branch) covered by a rule that refuses direct pushes, requires at least one status, and lets no admin merge past one. Asked of gitea.gitea_branch_protection_get (glob rules included). Unreadable forge, or a source not on the forge, means refused. I chose refusing over a condition: a registered module is what the next push sends. I checked every repository built in the last 400 builds (13 repositories); all already pass this rule.
Repository identity. The forge's repository id (via gitea_api GET /repos/o/r) is recorded at registration (module.source_repo_id, in migration 0084). A different id under the same name (deleted and made again) is refused, also for a new module beside one registered from that repository. Existing modules record their id at their next registration; until then there is no id to compare (trust on first use).
Terminal is never the serving controller.serve sets MESH_SERVED_BY_THE_CONTROLLER=1 in its own environment, so it and everything it starts (verbs, children) never read as the terminal. A terminal request counts only when its repository and path match the outcome's.
Tests, each failing with its check removed: TestATrunkTheForgeDoesNotProtectRegistersNothing, TestARepositoryMadeAgainUnderItsNameIsNotTheModulesSource, TestATerminalRequestCoversOnlyWhatItAsked, TestTheServingControllerIsNeverTheTerminal. Merge order: #164, then this, then #175 (stacked: #175 adds migration 0085).
Follow-ups of the review (commit 80ad37e; branch rebased on #164's re-pin d951f22):
- **Protected trunk, refused not raised.** Through any verb, a registration also needs the source on the mesh's forge with its trunk (the branch the module follows, else the default branch) covered by a rule that refuses direct pushes, requires at least one status, and lets no admin merge past one. Asked of `gitea.gitea_branch_protection_get` (glob rules included). Unreadable forge, or a source not on the forge, means refused. I chose refusing over a condition: a registered module is what the next push sends. I checked every repository built in the last 400 builds (13 repositories); all already pass this rule.
- **Repository identity.** The forge's repository id (via `gitea_api GET /repos/o/r`) is recorded at registration (`module.source_repo_id`, in migration 0084). A different id under the same name (deleted and made again) is refused, also for a new module beside one registered from that repository. Existing modules record their id at their next registration; until then there is no id to compare (trust on first use).
- **Terminal is never the serving controller.** `serve` sets `MESH_SERVED_BY_THE_CONTROLLER=1` in its own environment, so it and everything it starts (verbs, children) never read as the terminal. A terminal request counts only when its repository and path match the outcome's.
Tests, each failing with its check removed: `TestATrunkTheForgeDoesNotProtectRegistersNothing`, `TestARepositoryMadeAgainUnderItsNameIsNotTheModulesSource`, `TestATerminalRequestCoversOnlyWhatItAsked`, `TestTheServingControllerIsNeverTheTerminal`. **Merge order: #164, then this, then #175** (stacked: #175 adds migration 0085).
build-agent keeps data (ADR 0233): what it holds is backed up before it moves
route-proxy provides route: its consumers are sent again after it
route-proxy keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on novox as build-1791541655866717096 (builds --log build-1791541655866717096).
--- the gate: build-agent, mesh-controller, route-proxy
mesh-controller: ok, 52 tool(s); WARNING: controller 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-09T08:07:39Z 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-09T08:07:39Z
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 3 module(s) in 3 tier(s): build-agent, mesh-controller, route-proxy
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-controller/cmd/mesh-builder 1.044s
ok github.com/novox/mesh-controller/cmd/mesh-controller 228.974s
ok github.com/novox/mesh-controller/examples/objectstore-provisioner 1.020s
ok github.com/novox/mesh-controller/examples/postgres-provisioner 1.029s
ok github.com/novox/mesh-controller/examples/redis-provisioner 1.021s
ok github.com/novox/mesh-controller/examples/route-proxy 1.121s
ok github.com/novox/mesh-controller/internal/artifacts 1.059s
ok github.com/novox/mesh-controller/internal/broker 2.875s
ok github.com/novox/mesh-controller/internal/builder 2.833s
ok github.com/novox/mesh-controller/internal/catalogue 5.420s
ok github.com/novox/mesh-controller/internal/conditions 2.467s
ok github.com/novox/mesh-controller/internal/envfile 1.031s
ok github.com/novox/mesh-controller/internal/facts 1.029s
ok github.com/novox/mesh-controller/internal/identity 5.085s
ok github.com/novox/mesh-controller/internal/inventory 92.362s
ok github.com/novox/mesh-controller/internal/lease 17.671s
ok github.com/novox/mesh-controller/internal/licences 4.711s
ok github.com/novox/mesh-controller/internal/licences/adapters 1.012s
ok github.com/novox/mesh-controller/internal/link 54.418s
ok github.com/novox/mesh-controller/internal/lint 2.222s
ok github.com/novox/mesh-controller/internal/outward 1.037s
ok github.com/novox/mesh-controller/internal/overlay 1.023s
ok github.com/novox/mesh-controller/internal/secrets 1.500s
ok github.com/novox/mesh-controller/internal/store 3.401s
ok github.com/novox/mesh-controller/internal/testbus 1.207s
ok github.com/novox/mesh-controller/internal/token 1.015s
**Merge check** at `80ad37ec`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, mesh-controller, route-proxy)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed
**Delivery plan** — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
- tier 0: mesh-controller
- tier 1: build-agent
- tier 2: route-proxy
- ace: receives build-agent, route-proxy
- g14: receives build-agent
- novox: receives mesh-controller, build-agent, route-proxy
- shanks: receives build-agent
- build-agent keeps data (ADR 0233): what it holds is backed up before it moves
- route-proxy provides route: its consumers are sent again after it
- route-proxy keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on novox as `build-1791541655866717096` (`builds --log build-1791541655866717096`).
```
--- the gate: build-agent, mesh-controller, route-proxy
mesh-controller: ok, 52 tool(s); WARNING: controller 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-09T08:07:39Z 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-09T08:07:39Z
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 3 module(s) in 3 tier(s): build-agent, mesh-controller, route-proxy
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-controller/cmd/mesh-builder 1.044s
ok github.com/novox/mesh-controller/cmd/mesh-controller 228.974s
ok github.com/novox/mesh-controller/examples/objectstore-provisioner 1.020s
ok github.com/novox/mesh-controller/examples/postgres-provisioner 1.029s
ok github.com/novox/mesh-controller/examples/redis-provisioner 1.021s
ok github.com/novox/mesh-controller/examples/route-proxy 1.121s
ok github.com/novox/mesh-controller/internal/artifacts 1.059s
ok github.com/novox/mesh-controller/internal/broker 2.875s
ok github.com/novox/mesh-controller/internal/builder 2.833s
ok github.com/novox/mesh-controller/internal/catalogue 5.420s
ok github.com/novox/mesh-controller/internal/conditions 2.467s
ok github.com/novox/mesh-controller/internal/envfile 1.031s
ok github.com/novox/mesh-controller/internal/facts 1.029s
ok github.com/novox/mesh-controller/internal/identity 5.085s
ok github.com/novox/mesh-controller/internal/inventory 92.362s
ok github.com/novox/mesh-controller/internal/lease 17.671s
ok github.com/novox/mesh-controller/internal/licences 4.711s
ok github.com/novox/mesh-controller/internal/licences/adapters 1.012s
ok github.com/novox/mesh-controller/internal/link 54.418s
ok github.com/novox/mesh-controller/internal/lint 2.222s
ok github.com/novox/mesh-controller/internal/outward 1.037s
ok github.com/novox/mesh-controller/internal/overlay 1.023s
ok github.com/novox/mesh-controller/internal/secrets 1.500s
ok github.com/novox/mesh-controller/internal/store 3.401s
ok github.com/novox/mesh-controller/internal/testbus 1.207s
ok github.com/novox/mesh-controller/internal/token 1.015s
```
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
Only the engine's own test changed; the pin follows the head so the two are
judged together (hq ADR 0266). Move it to the engine's merged commit before
this merges.
An agent could make a repository of its own, or fork one the mesh builds
from, commit a module.json naming sudo or mesh-host, and ask the build verb
for it: the outcome was registered under that name, and the next push made
whoever wrote it root on every node (novox/hq ADR 0266 §7, the review of
2026-10-09). The trunk rule checked the trunk of the repository built, which
was the agent's.
The take-in, which every outcome reaches whichever verb asked it, now
registers a module only from its registered repository, and a new module only
from a repository the catalogue already builds from (a merge adding one);
anything else only when the build request was kept as asked at the
controller's terminal (migration 0084). Through a verb, a build of a
repository the catalogue builds nothing from is not asked at all.
A build refused for its repository is still recorded, and a fork carries the
commit the module was registered at: the rollback's search for the previous
build would have found it and registered it by the back door.
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/delivery covered: a later merge that contains it was delivered: novox/mesh-controller@eca6390d6fe4 (merged as 33dc85d8 into main, walk plan-17915444…
A source repository's name is not its identity, and a trunk anyone may push
to makes the trunk rule mean nothing (the review of 2026-10-09). Through any
verb a module now registers only from a repository on the mesh's forge whose
trunk refuses direct pushes, requires a status and lets no administrator
merge past one, asked of the forge's own tools; and only from the repository
by the forge's id, recorded at registration (migration 0084), so one deleted
and made again under the name is refused. The serving controller marks its
environment, so nothing it runs or starts reads as the terminal, and a
terminal request covers only the repository and path it asked.
build-agent keeps data (ADR 0233): what it holds is backed up before it moves
route-proxy provides route: its consumers are sent again after it
route-proxy keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on ace as build-1791542632864709183 (builds --log build-1791542632864709183).
--- the gate: build-agent, mesh-controller, route-proxy
mesh-controller: ok, 52 tool(s); WARNING: controller 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-09T08:07:39Z 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-09T08:07:39Z
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 3 module(s) in 3 tier(s): build-agent, mesh-controller, route-proxy
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-controller/cmd/mesh-builder 1.035s
ok github.com/novox/mesh-controller/cmd/mesh-controller 176.206s
ok github.com/novox/mesh-controller/examples/objectstore-provisioner 1.026s
ok github.com/novox/mesh-controller/examples/postgres-provisioner 1.030s
ok github.com/novox/mesh-controller/examples/redis-provisioner 1.021s
ok github.com/novox/mesh-controller/examples/route-proxy 1.131s
ok github.com/novox/mesh-controller/internal/artifacts 1.054s
ok github.com/novox/mesh-controller/internal/broker 2.545s
ok github.com/novox/mesh-controller/internal/builder 1.828s
ok github.com/novox/mesh-controller/internal/catalogue 4.370s
ok github.com/novox/mesh-controller/internal/conditions 2.402s
ok github.com/novox/mesh-controller/internal/envfile 1.031s
ok github.com/novox/mesh-controller/internal/facts 1.035s
ok github.com/novox/mesh-controller/internal/identity 4.099s
ok github.com/novox/mesh-controller/internal/inventory 72.311s
ok github.com/novox/mesh-controller/internal/lease 17.671s
ok github.com/novox/mesh-controller/internal/licences 3.740s
ok github.com/novox/mesh-controller/internal/licences/adapters 1.027s
ok github.com/novox/mesh-controller/internal/link 51.041s
ok github.com/novox/mesh-controller/internal/lint 2.015s
ok github.com/novox/mesh-controller/internal/outward 1.101s
ok github.com/novox/mesh-controller/internal/overlay 1.083s
ok github.com/novox/mesh-controller/internal/secrets 1.471s
ok github.com/novox/mesh-controller/internal/store 3.272s
ok github.com/novox/mesh-controller/internal/testbus 1.242s
ok github.com/novox/mesh-controller/internal/token 1.015s
**Merge check** at `95e7a712`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, mesh-controller, route-proxy)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed
**Delivery plan** — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
- tier 0: mesh-controller
- tier 1: build-agent
- tier 2: route-proxy
- ace: receives build-agent, route-proxy
- g14: receives build-agent
- novox: receives mesh-controller, build-agent, route-proxy
- shanks: receives build-agent
- build-agent keeps data (ADR 0233): what it holds is backed up before it moves
- route-proxy provides route: its consumers are sent again after it
- route-proxy keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on ace as `build-1791542632864709183` (`builds --log build-1791542632864709183`).
```
--- the gate: build-agent, mesh-controller, route-proxy
mesh-controller: ok, 52 tool(s); WARNING: controller 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-09T08:07:39Z 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-09T08:07:39Z
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 3 module(s) in 3 tier(s): build-agent, mesh-controller, route-proxy
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-controller/cmd/mesh-builder 1.035s
ok github.com/novox/mesh-controller/cmd/mesh-controller 176.206s
ok github.com/novox/mesh-controller/examples/objectstore-provisioner 1.026s
ok github.com/novox/mesh-controller/examples/postgres-provisioner 1.030s
ok github.com/novox/mesh-controller/examples/redis-provisioner 1.021s
ok github.com/novox/mesh-controller/examples/route-proxy 1.131s
ok github.com/novox/mesh-controller/internal/artifacts 1.054s
ok github.com/novox/mesh-controller/internal/broker 2.545s
ok github.com/novox/mesh-controller/internal/builder 1.828s
ok github.com/novox/mesh-controller/internal/catalogue 4.370s
ok github.com/novox/mesh-controller/internal/conditions 2.402s
ok github.com/novox/mesh-controller/internal/envfile 1.031s
ok github.com/novox/mesh-controller/internal/facts 1.035s
ok github.com/novox/mesh-controller/internal/identity 4.099s
ok github.com/novox/mesh-controller/internal/inventory 72.311s
ok github.com/novox/mesh-controller/internal/lease 17.671s
ok github.com/novox/mesh-controller/internal/licences 3.740s
ok github.com/novox/mesh-controller/internal/licences/adapters 1.027s
ok github.com/novox/mesh-controller/internal/link 51.041s
ok github.com/novox/mesh-controller/internal/lint 2.015s
ok github.com/novox/mesh-controller/internal/outward 1.101s
ok github.com/novox/mesh-controller/internal/overlay 1.083s
ok github.com/novox/mesh-controller/internal/secrets 1.471s
ok github.com/novox/mesh-controller/internal/store 3.272s
ok github.com/novox/mesh-controller/internal/testbus 1.242s
ok github.com/novox/mesh-controller/internal/token 1.015s
```
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.
Merge after #164 — this branch is built on
feat/a-node-names-the-account-its-agents-run-as(it relies on #164'scommandverb being read-only, which closesmodule addthrough a verb, and uses itsterminalRefusal). Until #164 merges, this diff also shows #164's seven commits; the two commits of this PR are the last two.The route
An agent makes a repository of its own (or forks
novox/mesh-catalogornovox/mesh-host), commits amodule.jsonnamingsudo(granting itself NOPASSWD) ormesh-host, and callsmesh-controller.buildon it. The outcome was registered under that name from the agent's repository:RegisterModuleupserts by name and overwritessource, and the trunk rule (ADR 0238) checked the trunk of the repository built — the agent's own. The next push made the agent root on every node.The fix
takeIn, which every outcome reaches whoever hears it and whichever verb asked it —build,rebuild,replay --register,assignwith build, a plan, a merge): an outcome registers a module only from the module's registered repository; a module new to the catalogue only from a repository the catalogue already builds another module from (how a merge adds one, issue 300). Anything else only when the build request was kept as asked at the controller's terminal: new columnbuild_request.at_terminal(migration 0084), set only when the asking process was started neither by a verb nor a seat call (MESH_VERBandMESH_CALLERboth unset —runVerbsets both).buildOneAsked,buildAndShow): through a verb, a build of a repository the catalogue builds no module from is not asked at all — the build node would run what the agent wrote.PreviousBuild): a failed gate puts back only a build of the failed build's repository. A refused build is still recorded, and a fork carries the commit the module was registered at.Sources compare as one repository whether spelled as a seat path or as the URL composed from it (live sources are URLs,
http://…:20000/novox/mesh-catalog.git).Tests (each fails before, passes after)
TestABuildFromAnAgentsRepositoryIsNotRegisteredAsAModuleTheMeshHolds: own repo namingsudo, a fork of the catalogue, a fork of the node-engine — refused, catalogue unchanged, build recorded.TestANewModuleFromARepositoryTheCatalogueDoesNotBuildFromIsNotRegistered(also an outcome no request was kept for).TestAVerbAsksNoBuildOfARepositoryTheCatalogueDoesNotBuildFrom.TestARollbackNeverPutsBackABuildFromAnotherRepository.TestAtTheTerminalAnotherRepositoryIsRegistered(move, new external module),TestADeliveryFromTheRegisteredRepositoryIsRegistered(a plan's rebuild, a merge adding a module).Nine existing tests registered a brand-new module into an empty catalogue from an outcome nobody asked for; they now keep that first build as the operator's at the terminal (
asTheOperator), and the gate test's outcome echoes its seat as a real one does.What changes for the operator
A build from a new repository, or a module moved to another repository, is done at the controller's terminal (
mesh-controller build …). A rebuild, a delivery,build --behind/--onand a merge adding a module to the catalogue repository work as before.merge-check.shrun on this head (which contains current main).Delivery
novox/mesh-controller@95e7a7120ab0— covered since 2026-10-09T11:35:27ZDelivery plan — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
Transitions
33dc85d8into main, walk plan-1791544480657912626): build-agent, mesh-controller, route-proxyThe commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
c816da16mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, mesh-controller, route-proxy)mesh/repo-check: PASS — its merge-check.sh passedDelivery plan — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
Run by the build seat on shanks as
build-1791539342120430846(builds --log build-1791539342120430846).c816da16fato80ad37ec16Follow-ups of the review (commit 80ad37e; branch rebased on #164's re-pin
d951f22):gitea.gitea_branch_protection_get(glob rules included). Unreadable forge, or a source not on the forge, means refused. I chose refusing over a condition: a registered module is what the next push sends. I checked every repository built in the last 400 builds (13 repositories); all already pass this rule.gitea_api GET /repos/o/r) is recorded at registration (module.source_repo_id, in migration 0084). A different id under the same name (deleted and made again) is refused, also for a new module beside one registered from that repository. Existing modules record their id at their next registration; until then there is no id to compare (trust on first use).servesetsMESH_SERVED_BY_THE_CONTROLLER=1in its own environment, so it and everything it starts (verbs, children) never read as the terminal. A terminal request counts only when its repository and path match the outcome's.Tests, each failing with its check removed:
TestATrunkTheForgeDoesNotProtectRegistersNothing,TestARepositoryMadeAgainUnderItsNameIsNotTheModulesSource,TestATerminalRequestCoversOnlyWhatItAsked,TestTheServingControllerIsNeverTheTerminal. Merge order: #164, then this, then #175 (stacked: #175 adds migration 0085).Merge check at
80ad37ecmesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, mesh-controller, route-proxy)mesh/repo-check: PASS — its merge-check.sh passedDelivery plan — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
Run by the build seat on novox as
build-1791541655866717096(builds --log build-1791541655866717096).80ad37ec16to95e7a7120aMerge check at
95e7a712mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, mesh-controller, route-proxy)mesh/repo-check: PASS — its merge-check.sh passedDelivery plan — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
Run by the build seat on ace as
build-1791542632864709183(builds --log build-1791542632864709183).