Fixes issue 624 (the lab's runs stop at setup with EALLOWREMOTE), under ADR 0346 (proposed in the knowledge base; design 27, hq #345).
Merge only after mesh-controller #306 is delivered. A controller without it hands the placeholder through as text. The lab then rejects that text (publicRootOf: not a URL) and falls back as it does today, so nothing breaks, but nothing is fixed either. module check with main's controller accepts the gitea manifest, so the gate alone will not hold the order.
What
gitea: serves.npm-package-registry gains public-root: https://${bound:route:name-web}/api/packages/novox/npm/. That is the forge route's composed public name (label git + the providing machine's public domain), the same host the forge writes every tarball at. The README says what a registry consumer is told.
lab:
publicRootOf(binding) reads serves.public-root from the lab's npm-package-registry binding (MESH_LAB_FORGE_BINDING). It takes only an https URL whose path is exactly the npm path, with no user, query, fragment, whitespace or control character.
npmRegistry(forge, get, publicRoot) keeps every check of #275: the raw string is checked for whitespace and control characters, then parsed; no user or password, no query or fragment; the path is under the npm path; the registry is built from the parsed origin, never the raw string.
It now compares the tarball's whole origin (scheme, host and port) against the public root's or the forge setting's own. Before, it compared the hostname only, against the forge's.
lab_run and lab_check both use it. When it falls back, the problem also says why the binding gave no root.
Other consumers of npm-package-registry
In the catalogue: build-agent and lab (and gitea provides it).
No manifest says ${bound:npm-package-registry:…} in a file. build-agent's manifest test only requires such a file to be only-when-bound.
mesh-builder reads the binding's serves into a map[string]any and uses only scheme, port and npm-path (cmd/mesh-builder), so the new key changes nothing for build-agent.
Tests
modules/lab/test/runs.test.ts, new:
a tarball is taken at the public root, and refused at another port (:8443), another scheme (http:), another host, without a public root, and at the forge's host on another port;
publicRootOf takes an https root at the npm path, and refuses a missing key, http, another path, a query, a user, an embedded newline, a leftover placeholder, and an unreadable or absent binding.
The #275 fallback cases still pass, with their messages updated to the origin check.
On main the file fails at import (publicRootOf), and the port case fails: main compares hostnames only.
23 of 23 lab tests and 89 of 89 gitea tests pass; merge-check-typescript.sh type-checks both.
merge-check.sh with MESH_CHECK_MODULES=lab,gitea and MESH_GATE built from mesh-controller #306 passes: every manifest through module check, and gitea's Go tests.
after: novox/mesh-controller#306
Fixes issue 624 (the lab's runs stop at setup with EALLOWREMOTE), under ADR 0346 (proposed in the knowledge base; design 27, hq #345).
**Merge only after mesh-controller #306 is delivered.** A controller without it hands the placeholder through as text. The lab then rejects that text (`publicRootOf`: not a URL) and falls back as it does today, so nothing breaks, but nothing is fixed either. `module check` with main's controller accepts the gitea manifest, so the gate alone will not hold the order.
## What
- **gitea**: `serves.npm-package-registry` gains `public-root`: `https://${bound:route:name-web}/api/packages/novox/npm/`. That is the forge route's composed public name (label `git` + the providing machine's public domain), the same host the forge writes every tarball at. The README says what a registry consumer is told.
- **lab**:
- `publicRootOf(binding)` reads `serves.public-root` from the lab's npm-package-registry binding (`MESH_LAB_FORGE_BINDING`). It takes only an https URL whose path is exactly the npm path, with no user, query, fragment, whitespace or control character.
- `npmRegistry(forge, get, publicRoot)` keeps every check of #275: the raw string is checked for whitespace and control characters, then parsed; no user or password, no query or fragment; the path is under the npm path; the registry is built from the parsed origin, never the raw string.
- It now compares the tarball's whole origin (scheme, host and port) against the public root's or the forge setting's own. Before, it compared the hostname only, against the forge's.
- `lab_run` and `lab_check` both use it. When it falls back, the problem also says why the binding gave no root.
## Other consumers of npm-package-registry
In the catalogue: build-agent and lab (and gitea provides it).
- No manifest says `${bound:npm-package-registry:…}` in a file. build-agent's manifest test only requires such a file to be `only-when-bound`.
- mesh-builder reads the binding's `serves` into a `map[string]any` and uses only `scheme`, `port` and `npm-path` (cmd/mesh-builder), so the new key changes nothing for build-agent.
## Tests
- `modules/lab/test/runs.test.ts`, new:
- a tarball is taken at the public root, and refused at another port (`:8443`), another scheme (`http:`), another host, without a public root, and at the forge's host on another port;
- `publicRootOf` takes an https root at the npm path, and refuses a missing key, http, another path, a query, a user, an embedded newline, a leftover placeholder, and an unreadable or absent binding.
- The #275 fallback cases still pass, with their messages updated to the origin check.
- On main the file fails at import (`publicRootOf`), and the port case fails: main compares hostnames only.
- 23 of 23 lab tests and 89 of 89 gitea tests pass; `merge-check-typescript.sh` type-checks both.
- `merge-check.sh` with `MESH_CHECK_MODULES=lab,gitea` and `MESH_GATE` built from mesh-controller #306 passes: every manifest through `module check`, and gitea's Go tests.
Deliverynovox/mesh-catalog@1faee6e27976 — delivered since 2026-10-13T20:11:10Z
Delivery plan — builds gitea, lab → novox, shanks; no bus step
tier 0: gitea, lab
novox: receives gitea
shanks: receives lab
gitea provides npm-package-registry, git: its consumers are sent again after it
gitea keeps data (ADR 0233): what it holds is backed up before it moves
lab 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 lab is judged on its first machine
Machines
route-proxy on ace: passed — carried by plan-1791919354338134213
lab on shanks: passed — carried by release-1791922091860830040
gitea on novox: passed — healthy 1 times over 20s
gitea on the rest running it: sent
lab on the rest running it: sent
Transitions
2026-10-13 18:33 (new) → proposed (announced): novox/mesh-catalog#280's head announced
2026-10-13 18:37 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
2026-10-13 18:37 checked → rejected (refused): the gate failed or could not run, or the repository's own check failed
2026-10-13 19:39 rejected → proposed (recheck): judged again against trunk faebc3569a (it was rejected against trunk 9c166398f2)
2026-10-13 19:39 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
2026-10-13 19:39 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-13 20:07 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
2026-10-13 20:07 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-13 20:11 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@1faee6e27976` — **delivered** since 2026-10-13T20:11:10Z
**Delivery plan** — builds gitea, lab → novox, shanks; no bus step
- tier 0: gitea, lab
- novox: receives gitea
- shanks: receives lab
- gitea provides npm-package-registry, git: its consumers are sent again after it
- gitea keeps data (ADR 0233): what it holds is backed up before it moves
- lab 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 lab is judged on its first machine
**Machines**
- route-proxy on ace: passed — carried by plan-1791919354338134213
- lab on shanks: passed — carried by release-1791922091860830040
- gitea on novox: passed — healthy 1 times over 20s
- gitea on the rest running it: sent
- lab on the rest running it: sent
**Transitions**
- 2026-10-13 18:33 (new) → proposed (announced): novox/mesh-catalog#280's head announced
- 2026-10-13 18:37 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
- 2026-10-13 18:37 checked → rejected (refused): the gate failed or could not run, or the repository's own check failed
- 2026-10-13 19:39 rejected → proposed (recheck): judged again against trunk faebc3569acf (it was rejected against trunk 9c166398f204)
- 2026-10-13 19:39 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
- 2026-10-13 19:39 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-13 20:07 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
- 2026-10-13 20:07 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-13 20:11 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 9e2c1531
Judged: head 9e2c1531e426 merged into trunk 4d15a155e634; read beside it: mesh-controller 6f1a05366c1d, mesh-controller-main 6f1a05366c1d, mesh-host 0e92ef3d1b21, mesh-tools 954e1a53eef9
mesh/merge-gate: FAIL — 4 problem(s) the change brings; the first: a machine (f35): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry" (modules: gitea, lab)
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 gitea, lab → novox, shanks; no bus step
tier 0: gitea, lab
novox: receives gitea
shanks: receives lab
gitea provides npm-package-registry, git: its consumers are sent again after it
gitea keeps data (ADR 0233): what it holds is backed up before it moves
lab 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 lab is judged on its first machine
Run by the build seat on ace as build-1791903246423675253 (builds --log build-1791903246423675253).
--- the gate: gitea, lab
gitea: ok, keeps data (valuable); 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)
lab: ok, keeps work (cache); WARNING: runtime-env 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: 1
files asking for a setting without saying whether it is trusted: 1
2 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 2 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T14:52:41Z 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: FAIL — 4 problem(s) the change brings; the first: a machine (f35): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
judged by controller development build against the facts of 2026-10-13T14:52:41Z
fails:
- a machine (f35): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
- a machine (yvbpsm): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
- 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): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
- holds mesh-dns-resolver (idt): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
modules the change moves:
- gitea (changed)
- lab (changed)
machines:
- a machine (f35): does not compose; the change adds 0 resource(s) and removes 399
- holds mesh-dns-resolver (idt): does not compose; the change adds 0 resource(s) and removes 732
- 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): does not compose; the change adds 0 resource(s) and removes 554
- a machine (yvbpsm): does not compose; the change adds 0 resource(s) and removes 377
a merge would rebuild 2 module(s) in 1 tier(s): gitea, lab
mesh-controller: the change fails the merge gate
--- 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 46625 strings of 717 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/secrets 0.013s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
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: 66, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.222s
testing modules/gitea
ok gitea/cmd/forge 1.442s
ok gitea/cmd/npm-registry 1.825s
? gitea/internal/forgehttp [no test files]
modules/lab is TypeScript: merge-check-typescript.sh type-checks and tests it
--- its merge-check-typescript.sh (typescript toolchain)
type-checking modules/gitea (TypeScript)
testing modules/gitea (TypeScript)
✔ a pull request opened after the margin is looked at, by its own update time (8.282764ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (43.880612ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.326212ms)
✔ a merged pull request whose branch was deleted is announced with its branch, not its pull ref (8.22137ms)
✔ the newest status of each context is kept, with what it said, when and by whom (24.42021ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.501092ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.370336ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (0.642968ms)
✔ a note line is appended once, as the forge's user, in its own repository (7.623757ms)
✔ a note that cannot be written is said, never read as written (0.323338ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.492055ms)
✔ a build machine taking the check is said on the head: building on which machine, since the local time (12.846ms)
✔ the verdict heard while a 'building' write is in flight lands last and stays (5.887653ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ the verdict written while a start's read of the forge is in flight stays the newest status (0.78748ms)
✔ the verdict heard while a 'building' write still waits its turn: the 'building' is never written (0.359406ms)
✔ a start heard after the verdict writes nothing (0.382719ms)
✔ a start from before the check now pending (a run before a recheck, the stream's history) writes nothing (0.367651ms)
✔ an old run's verdict redelivered after a recheck's pending is not written over it (0.658427ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check the forge holds pending; not written
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ an old run's verdict in flight when the recheck comes lands before the recheck's pending (1.326983ms)
✔ a rechecked head says where its new run is: building on which machine (0.57495ms)
✔ the recheck's own verdict is written, even asked a little before the pending by another machine's clock (0.484541ms)
✔ after a restart, an old run's verdict is weighed against the check the forge's history holds open (0.41472ms)
✔ a recheck the forge does not take fails its caller, and the head is as it was (0.379423ms)
✔ the status tool writes the merge check's pending through the writer, and the mesh's other statuses as they were (4.657393ms)
✔ when a check was asked is read from its id; the opening of the check now pending from the gate's history (0.179207ms)
✔ a head this process did not announce (it restarted) is said only while the forge holds its gate pending (0.265218ms)
✔ a repository as the mesh clones it is told from another (0.202881ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (4.96023ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (42.730991ms)
✔ a protection rule covers a branch as the forge decides it: classes, alternatives, a plain name in any case (issue 539) (11.066747ms)
✔ gitea_api still writes what an author writes (6.238783ms)
✔ gitea_branch_protection_set only strengthens (16.137025ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (40.610466ms)
✔ gitea_merge_pull_request refuses a verdict judged against an earlier trunk, and asks the check once (139.538701ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (16.962227ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (12.490241ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (24.87751ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (52.063499ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (20.933007ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (3.489948ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (6.827781ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (11.271701ms)
✔ a repository owner or name is never another path (0.384002ms)
✔ the judges, without a forge (0.341191ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (2.407173ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (1.171281ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (4.798037ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (21.620821ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.344217ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (10.258047ms)
✔ a lookup the forge refuses is thrown, not read as no module (3.057144ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (4.000357ms)
✔ a new head beyond the newest 20 open pull requests is announced, exactly once (53.051284ms)
✔ a pass pages until it reaches back past its floor, and no further (17.664364ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (6.764703ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.285627ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (13.327466ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (0.937211ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.725934ms)
✔ no verdict sets warning on a required check; only a person's decision fails (1.045866ms)
✔ a commit status is set on the commit, under the merge check's context (27.286527ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.430329ms)
✔ a head announced is pending in the build queue, since the local time it was announced (0.219884ms)
✔ the vault's token is used, a setting of it is ignored and said once, without its value (2.269996ms)
✔ a consumer that asks nothing, or write, publishes: the package team (1.094096ms)
✔ a consumer that asks to read joins the readers, who write no package (0.350399ms)
✔ a reader is made, put on the readers team, and taken off the package team (0.561806ms)
✔ a publisher is made and put on the package team, as before (0.162445ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (28.965261ms)
✔ second start: reuses the kept token, mints nothing (15.582083ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (20.610661ms)
✔ a token kept with write:admin by an earlier build is minted again without it at the next start, and the forge drops the old one (18.675886ms)
✔ a kept token lacking a scope the tools need: the forge refuses the call for the scope, the token is re-minted with the whole list, and the call goes through (18.522167ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (16.698459ms)
✔ concurrent first calls share one mint (9.462252ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (11.070835ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (0.854476ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (16.203729ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (1.172804ms)
✔ nothing to mint with and no token: the client says what is missing (0.267533ms)
[gitea] no tools — no Gitea token, and nothing to mint one with — set MESH_GITEA_TOKEN, or MESH_GITEA_ADMIN_USER, MESH_GITEA_ADMIN_PASSWORD_FILE, MESH_GITEA_STATE_DIR
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-admin" (write:repository, write:issue, read:user), kept at /tmp/gitea-jr91ec/state/token
✔ the tools register once there is a way to a token, and the first call mints it (8.270672ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-admin" (write:repository, write:issue, read:user), kept at /tmp/gitea-hNtGR5/state/token
✔ a pull request can be closed, read and cleaned up through the tools (19.583601ms)
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 25 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host's main moved from d28ca95ccccc to ffffffffffff: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host: whether a trunk moved could not be weighed, so it is weighed again on the next pass — the forge is away
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
✔ both statuses say the head and the trunk commit the verdict judged, first, whatever the summary's length (1.35205ms)
✔ a trunk's move puts an open head's merge check out of date, naming both commits, unless a check is on its way (1.151023ms)
✔ a push to a trunk puts every open pull request into it out of date — twenty-five of them — and none into another (5.740368ms)
✔ a trunk that cannot be read is weighed again on the next pass, never recorded half (0.92082ms)
✔ the trunk's move takes its turn after a verdict in flight, and weighs what the forge holds then (0.58534ms)
✔ a verdict judged against a trunk newer than the pass's first read is not put out of date (2.742884ms)
✔ a verdict that landed out of date keeps its account on the pull request (0.327105ms)
ℹ tests 89
ℹ suites 0
ℹ pass 89
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 574.46039
type-checking modules/lab (TypeScript)
testing modules/lab (TypeScript)
✔ a checkout with a lock file at its root is installed there (2.631014ms)
✔ a checkout whose package lives one directory down is installed there, as mesh-tools' node-tools (0.727448ms)
✔ a package.json with no lock file is named, never installed (0.443443ms)
✔ a run installs and builds mesh-tools in node-tools, where its lock file is (9.80162ms)
✔ a run installs a checkout with its lock file at the root there, and builds only when asked (3.995138ms)
✔ a run fails at its setup, naming the checkout, when nothing of it can be installed (9.838449ms)
✔ a run checks out mesh-tools-go and says where its checkouts are (0.648408ms)
✔ the forge credential is the bound account and its sealed secret, for the forge's own origin (1.395472ms)
✔ a run clones through git's credential store, and its script carries no secret (0.808249ms)
✔ git presents the stored credential to the forge, and only when one is given (19.617366ms)
✔ a stopped script removes the credential, whatever ends it (11.161585ms)
✔ the script removes the credential as soon as its clones are done (0.320262ms)
✔ credentials a crashed run left are removed, but not the running one's (0.765869ms)
✔ the lab asks the package registry for read only (0.302488ms)
✔ each check gets a fresh directory of its own (0.379292ms)
﹣ what earlier checks left is cleared, and what cannot be is named rather than tripped over (0.054663ms) # running as root, which no directory mode stops from removing anything
✔ leftover checks are cleared, whoever runs the check (0.72884ms)
✔ the lab's tools bundle names its bound credential and binding in its env (0.140083ms)
✔ a run names the package registry by the address the registry gives its tarballs (16.9651ms)
✔ a registry that does not give an address the lab takes leaves the forge's, and the reason is said (4.488204ms)
✔ a run's npm configuration names the registry it was given, and the forge's own address only without one (0.265328ms)
✔ a tarball is taken at the forge's public root, and at no other port or scheme of its host (1.215083ms)
✔ the public root is read from the binding, and only an https root at the npm path is taken (1.56492ms)
ℹ tests 23
ℹ suites 0
ℹ pass 22
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1
ℹ todo 0
ℹ duration_ms 174.986679
**Merge check** at `9e2c1531`
Judged: head `9e2c1531e426` merged into trunk `4d15a155e634`; read beside it: mesh-controller `6f1a05366c1d`, mesh-controller-main `6f1a05366c1d`, mesh-host `0e92ef3d1b21`, mesh-tools `954e1a53eef9`
- `mesh/merge-gate`: **FAIL** — 4 problem(s) the change brings; the first: a machine (f35): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry" (modules: gitea, lab)
- `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 gitea, lab → novox, shanks; no bus step
- tier 0: gitea, lab
- novox: receives gitea
- shanks: receives lab
- gitea provides npm-package-registry, git: its consumers are sent again after it
- gitea keeps data (ADR 0233): what it holds is backed up before it moves
- lab 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 lab is judged on its first machine
Run by the build seat on ace as `build-1791903246423675253` (`builds --log build-1791903246423675253`).
```
--- the gate: gitea, lab
gitea: ok, keeps data (valuable); 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)
lab: ok, keeps work (cache); WARNING: runtime-env 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: 1
files asking for a setting without saying whether it is trusted: 1
2 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 2 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T14:52:41Z 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: FAIL — 4 problem(s) the change brings; the first: a machine (f35): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
judged by controller development build against the facts of 2026-10-13T14:52:41Z
fails:
- a machine (f35): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
- a machine (yvbpsm): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
- 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): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
- holds mesh-dns-resolver (idt): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
modules the change moves:
- gitea (changed)
- lab (changed)
machines:
- a machine (f35): does not compose; the change adds 0 resource(s) and removes 399
- holds mesh-dns-resolver (idt): does not compose; the change adds 0 resource(s) and removes 732
- 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): does not compose; the change adds 0 resource(s) and removes 554
- a machine (yvbpsm): does not compose; the change adds 0 resource(s) and removes 377
a merge would rebuild 2 module(s) in 1 tier(s): gitea, lab
mesh-controller: the change fails the merge gate
--- 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 46625 strings of 717 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/secrets 0.013s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
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: 66, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.222s
testing modules/gitea
ok gitea/cmd/forge 1.442s
ok gitea/cmd/npm-registry 1.825s
? gitea/internal/forgehttp [no test files]
modules/lab is TypeScript: merge-check-typescript.sh type-checks and tests it
--- its merge-check-typescript.sh (typescript toolchain)
type-checking modules/gitea (TypeScript)
testing modules/gitea (TypeScript)
✔ a pull request opened after the margin is looked at, by its own update time (8.282764ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (43.880612ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.326212ms)
✔ a merged pull request whose branch was deleted is announced with its branch, not its pull ref (8.22137ms)
✔ the newest status of each context is kept, with what it said, when and by whom (24.42021ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.501092ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.370336ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (0.642968ms)
✔ a note line is appended once, as the forge's user, in its own repository (7.623757ms)
✔ a note that cannot be written is said, never read as written (0.323338ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.492055ms)
✔ a build machine taking the check is said on the head: building on which machine, since the local time (12.846ms)
✔ the verdict heard while a 'building' write is in flight lands last and stays (5.887653ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ the verdict written while a start's read of the forge is in flight stays the newest status (0.78748ms)
✔ the verdict heard while a 'building' write still waits its turn: the 'building' is never written (0.359406ms)
✔ a start heard after the verdict writes nothing (0.382719ms)
✔ a start from before the check now pending (a run before a recheck, the stream's history) writes nothing (0.367651ms)
✔ an old run's verdict redelivered after a recheck's pending is not written over it (0.658427ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check the forge holds pending; not written
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ an old run's verdict in flight when the recheck comes lands before the recheck's pending (1.326983ms)
✔ a rechecked head says where its new run is: building on which machine (0.57495ms)
✔ the recheck's own verdict is written, even asked a little before the pending by another machine's clock (0.484541ms)
✔ after a restart, an old run's verdict is weighed against the check the forge's history holds open (0.41472ms)
✔ a recheck the forge does not take fails its caller, and the head is as it was (0.379423ms)
✔ the status tool writes the merge check's pending through the writer, and the mesh's other statuses as they were (4.657393ms)
✔ when a check was asked is read from its id; the opening of the check now pending from the gate's history (0.179207ms)
✔ a head this process did not announce (it restarted) is said only while the forge holds its gate pending (0.265218ms)
✔ a repository as the mesh clones it is told from another (0.202881ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (4.96023ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (42.730991ms)
✔ a protection rule covers a branch as the forge decides it: classes, alternatives, a plain name in any case (issue 539) (11.066747ms)
✔ gitea_api still writes what an author writes (6.238783ms)
✔ gitea_branch_protection_set only strengthens (16.137025ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (40.610466ms)
✔ gitea_merge_pull_request refuses a verdict judged against an earlier trunk, and asks the check once (139.538701ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (16.962227ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (12.490241ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (24.87751ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (52.063499ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (20.933007ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (3.489948ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (6.827781ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (11.271701ms)
✔ a repository owner or name is never another path (0.384002ms)
✔ the judges, without a forge (0.341191ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (2.407173ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (1.171281ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (4.798037ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (21.620821ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.344217ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (10.258047ms)
✔ a lookup the forge refuses is thrown, not read as no module (3.057144ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (4.000357ms)
✔ a new head beyond the newest 20 open pull requests is announced, exactly once (53.051284ms)
✔ a pass pages until it reaches back past its floor, and no further (17.664364ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (6.764703ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.285627ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (13.327466ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (0.937211ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.725934ms)
✔ no verdict sets warning on a required check; only a person's decision fails (1.045866ms)
✔ a commit status is set on the commit, under the merge check's context (27.286527ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.430329ms)
✔ a head announced is pending in the build queue, since the local time it was announced (0.219884ms)
✔ the vault's token is used, a setting of it is ignored and said once, without its value (2.269996ms)
✔ a consumer that asks nothing, or write, publishes: the package team (1.094096ms)
✔ a consumer that asks to read joins the readers, who write no package (0.350399ms)
✔ a reader is made, put on the readers team, and taken off the package team (0.561806ms)
✔ a publisher is made and put on the package team, as before (0.162445ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (28.965261ms)
✔ second start: reuses the kept token, mints nothing (15.582083ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (20.610661ms)
✔ a token kept with write:admin by an earlier build is minted again without it at the next start, and the forge drops the old one (18.675886ms)
✔ a kept token lacking a scope the tools need: the forge refuses the call for the scope, the token is re-minted with the whole list, and the call goes through (18.522167ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (16.698459ms)
✔ concurrent first calls share one mint (9.462252ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (11.070835ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (0.854476ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (16.203729ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (1.172804ms)
✔ nothing to mint with and no token: the client says what is missing (0.267533ms)
[gitea] no tools — no Gitea token, and nothing to mint one with — set MESH_GITEA_TOKEN, or MESH_GITEA_ADMIN_USER, MESH_GITEA_ADMIN_PASSWORD_FILE, MESH_GITEA_STATE_DIR
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-admin" (write:repository, write:issue, read:user), kept at /tmp/gitea-jr91ec/state/token
✔ the tools register once there is a way to a token, and the first call mints it (8.270672ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-admin" (write:repository, write:issue, read:user), kept at /tmp/gitea-hNtGR5/state/token
✔ a pull request can be closed, read and cleaned up through the tools (19.583601ms)
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 25 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host's main moved from d28ca95ccccc to ffffffffffff: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host: whether a trunk moved could not be weighed, so it is weighed again on the next pass — the forge is away
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
✔ both statuses say the head and the trunk commit the verdict judged, first, whatever the summary's length (1.35205ms)
✔ a trunk's move puts an open head's merge check out of date, naming both commits, unless a check is on its way (1.151023ms)
✔ a push to a trunk puts every open pull request into it out of date — twenty-five of them — and none into another (5.740368ms)
✔ a trunk that cannot be read is weighed again on the next pass, never recorded half (0.92082ms)
✔ the trunk's move takes its turn after a verdict in flight, and weighs what the forge holds then (0.58534ms)
✔ a verdict judged against a trunk newer than the pass's first read is not put out of date (2.742884ms)
✔ a verdict that landed out of date keeps its account on the pull request (0.327105ms)
ℹ tests 89
ℹ suites 0
ℹ pass 89
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 574.46039
type-checking modules/lab (TypeScript)
testing modules/lab (TypeScript)
✔ a checkout with a lock file at its root is installed there (2.631014ms)
✔ a checkout whose package lives one directory down is installed there, as mesh-tools' node-tools (0.727448ms)
✔ a package.json with no lock file is named, never installed (0.443443ms)
✔ a run installs and builds mesh-tools in node-tools, where its lock file is (9.80162ms)
✔ a run installs a checkout with its lock file at the root there, and builds only when asked (3.995138ms)
✔ a run fails at its setup, naming the checkout, when nothing of it can be installed (9.838449ms)
✔ a run checks out mesh-tools-go and says where its checkouts are (0.648408ms)
✔ the forge credential is the bound account and its sealed secret, for the forge's own origin (1.395472ms)
✔ a run clones through git's credential store, and its script carries no secret (0.808249ms)
✔ git presents the stored credential to the forge, and only when one is given (19.617366ms)
✔ a stopped script removes the credential, whatever ends it (11.161585ms)
✔ the script removes the credential as soon as its clones are done (0.320262ms)
✔ credentials a crashed run left are removed, but not the running one's (0.765869ms)
✔ the lab asks the package registry for read only (0.302488ms)
✔ each check gets a fresh directory of its own (0.379292ms)
﹣ what earlier checks left is cleared, and what cannot be is named rather than tripped over (0.054663ms) # running as root, which no directory mode stops from removing anything
✔ leftover checks are cleared, whoever runs the check (0.72884ms)
✔ the lab's tools bundle names its bound credential and binding in its env (0.140083ms)
✔ a run names the package registry by the address the registry gives its tarballs (16.9651ms)
✔ a registry that does not give an address the lab takes leaves the forge's, and the reason is said (4.488204ms)
✔ a run's npm configuration names the registry it was given, and the forge's own address only without one (0.265328ms)
✔ a tarball is taken at the forge's public root, and at no other port or scheme of its host (1.215083ms)
✔ the public root is read from the binding, and only an https root at the npm path is taken (1.56492ms)
ℹ tests 23
ℹ suites 0
ℹ pass 22
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1
ℹ todo 0
ℹ duration_ms 174.986679
```
modules/lab/tools/runs.ts publicRootOf (73-93): takes only an https URL, with no user, password, query or fragment, whose path is exactly the npm path, and no whitespace or control character. An unfilled ${bound:...} is refused (tested).
npmRegistry (115-160): a tarball is taken only when its whole origin (scheme, host, port) equals the public root's or the forge setting's. Every #275 check is kept: whitespace and control characters, user and password, query and fragment, and the npm-path prefix. The registry is rebuilt from the parsed origin alone. This is tighter than #275, which compared the host and ignored the port.
I probed it by hand. :443, upper case and https:\ normalise to the trusted origin, so they are accepted, correctly. A trailing-dot host, a backslash-@ trick, a %2f@ user and blob: are all refused.
The lab tests pass at the head: 23 of 23. Run against main's runs.ts, 3 of them fail: the #275 refusal table, the public-root origin test and the publicRootOf test.
build-agent (mesh-controller cmd/mesh-builder/main.go:461-486, 588-606) reads serves as a free map and only reads scheme, port and npm-path, so the new key changes nothing for it.
Nit, not blocking: runs.ts:129. If the forge setting were not http(s), for example file:///x, own.origin is "null". A file: tarball's origin is also "null", so it matches, and the registry becomes "null/api/...". The forge setting is the operator's, so this cannot be reached from the registry's answer. Refusing an opaque origin ("null") in trusted would close it.
Statuses read at review time: mesh/delivery failure (14:56Z, "rejected: the gate failed ..."), which is the expected failure under today's controller. mesh/merge-gate and mesh/repo-check are pending, "out of date: judged against trunk 4d15a155e6, the trunk is now e209d46faf3d". Ask mesh-delivery.recheck after #306 is live, and merge only on green.
Verdict: approve at 9e2c1531, to merge only after mesh-controller #306 has merged and its walk is done.
Independent review (issue 624, ADR 0346).
What I checked:
- modules/gitea/module.json: public-root is https://${bound:route:name-web}/api/packages/novox/npm/. gitea contributes route under the local name web, so this is its own name and #306's CheckServes accepts it.
- modules/lab/tools/runs.ts publicRootOf (73-93): takes only an https URL, with no user, password, query or fragment, whose path is exactly the npm path, and no whitespace or control character. An unfilled ${bound:...} is refused (tested).
- npmRegistry (115-160): a tarball is taken only when its whole origin (scheme, host, port) equals the public root's or the forge setting's. Every #275 check is kept: whitespace and control characters, user and password, query and fragment, and the npm-path prefix. The registry is rebuilt from the parsed origin alone. This is tighter than #275, which compared the host and ignored the port.
- I probed it by hand. :443, upper case and https:\\ normalise to the trusted origin, so they are accepted, correctly. A trailing-dot host, a backslash-@ trick, a %2f@ user and blob: are all refused.
- The lab tests pass at the head: 23 of 23. Run against main's runs.ts, 3 of them fail: the #275 refusal table, the public-root origin test and the publicRootOf test.
- build-agent (mesh-controller cmd/mesh-builder/main.go:461-486, 588-606) reads serves as a free map and only reads scheme, port and npm-path, so the new key changes nothing for it.
Nit, not blocking: runs.ts:129. If the forge setting were not http(s), for example file:///x, own.origin is "null". A file: tarball's origin is also "null", so it matches, and the registry becomes "null/api/...". The forge setting is the operator's, so this cannot be reached from the registry's answer. Refusing an opaque origin ("null") in trusted would close it.
Statuses read at review time: mesh/delivery failure (14:56Z, "rejected: the gate failed ..."), which is the expected failure under today's controller. mesh/merge-gate and mesh/repo-check are pending, "out of date: judged against trunk 4d15a155e634, the trunk is now e209d46faf3d". Ask mesh-delivery.recheck after #306 is live, and merge only on green.
The forge writes every tarball at its public name and the lab reaches it at
a private address, so npm 12 refused every locked @novox package. The public
name now comes from the mesh's own composition of the forge's route, through
the lab's binding, and the lab compares scheme, host and port.
mesh/merge-gate head 1faee6e27976 merged into trunk faebc3569acf — pass: builds gitea, lab → novox, shanks; no bus step; every machine composes with the ch…
mesh/repo-check head 1faee6e27976 merged into trunk faebc3569acf — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
Merge check at 1faee6e2
Judged: head 1faee6e27976 merged into trunk 9c166398f204; read beside it: mesh-controller 9f3aa91e812b, mesh-controller-main 9f3aa91e812b, mesh-host 0e92ef3d1b21, mesh-tools 954e1a53eef9
mesh/merge-gate: FAIL — 4 problem(s) the change brings; the first: a machine (f35): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry" (modules: gitea, lab)
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 gitea, lab → novox, shanks; no bus step
tier 0: gitea, lab
novox: receives gitea
shanks: receives lab
gitea provides npm-package-registry, git: its consumers are sent again after it
gitea keeps data (ADR 0233): what it holds is backed up before it moves
lab 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 lab is judged on its first machine
Run by the build seat on novox as build-1791916408401279391 (builds --log build-1791916408401279391).
--- the gate: gitea, lab
gitea: ok, keeps data (valuable); 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)
lab: ok, keeps work (cache); WARNING: runtime-env 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: 1
files asking for a setting without saying whether it is trusted: 1
2 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 2 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T17:37:27Z 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: FAIL — 4 problem(s) the change brings; the first: a machine (f35): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
judged by controller development build against the facts of 2026-10-13T17:37:27Z
fails:
- a machine (f35): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
- a machine (yvbpsm): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
- 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): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
- holds mesh-dns-resolver (idt): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
modules the change moves:
- gitea (changed)
- lab (changed)
machines:
- a machine (f35): does not compose; the change adds 0 resource(s) and removes 399
- holds mesh-dns-resolver (idt): does not compose; the change adds 0 resource(s) and removes 732
- 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): does not compose; the change adds 0 resource(s) and removes 554
- a machine (yvbpsm): does not compose; the change adds 0 resource(s) and removes 377
a merge would rebuild 2 module(s) in 1 tier(s): gitea, lab
mesh-controller: the change fails the merge gate
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.009s
words: 12 retired words and 70 daemons' lines, none in the 46600 strings of 717 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.015s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/secrets 0.015s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
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: 66, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.233s
testing modules/gitea
ok gitea/cmd/forge 1.510s
ok gitea/cmd/npm-registry 1.818s
? gitea/internal/forgehttp [no test files]
modules/lab is TypeScript: merge-check-typescript.sh type-checks and tests it
--- its merge-check-typescript.sh (typescript toolchain)
type-checking modules/gitea (TypeScript)
testing modules/gitea (TypeScript)
✔ a pull request opened after the margin is looked at, by its own update time (11.890195ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (62.976247ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (2.954668ms)
✔ a merged pull request whose branch was deleted is announced with its branch, not its pull ref (15.273695ms)
✔ the newest status of each context is kept, with what it said, when and by whom (31.235028ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.67224ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.34326ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (2.214389ms)
✔ a note line is appended once, as the forge's user, in its own repository (31.829728ms)
✔ a note that cannot be written is said, never read as written (0.513529ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.72338ms)
✔ a build machine taking the check is said on the head: building on which machine, since the local time (18.603173ms)
✔ the verdict heard while a 'building' write is in flight lands last and stays (8.390157ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ the verdict written while a start's read of the forge is in flight stays the newest status (1.27211ms)
✔ the verdict heard while a 'building' write still waits its turn: the 'building' is never written (0.51767ms)
✔ a start heard after the verdict writes nothing (0.797669ms)
✔ a start from before the check now pending (a run before a recheck, the stream's history) writes nothing (0.56111ms)
✔ an old run's verdict redelivered after a recheck's pending is not written over it (3.481479ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check the forge holds pending; not written
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ an old run's verdict in flight when the recheck comes lands before the recheck's pending (3.625999ms)
✔ a rechecked head says where its new run is: building on which machine (0.927929ms)
✔ the recheck's own verdict is written, even asked a little before the pending by another machine's clock (0.69901ms)
✔ after a restart, an old run's verdict is weighed against the check the forge's history holds open (0.80926ms)
✔ a recheck the forge does not take fails its caller, and the head is as it was (0.550129ms)
✔ the status tool writes the merge check's pending through the writer, and the mesh's other statuses as they were (15.466125ms)
✔ when a check was asked is read from its id; the opening of the check now pending from the gate's history (0.266649ms)
✔ a head this process did not announce (it restarted) is said only while the forge holds its gate pending (0.38342ms)
✔ a repository as the mesh clones it is told from another (0.28785ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (7.851197ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (67.240715ms)
✔ a protection rule covers a branch as the forge decides it: classes, alternatives, a plain name in any case (issue 539) (30.070249ms)
✔ gitea_api still writes what an author writes (9.666326ms)
✔ gitea_branch_protection_set only strengthens (23.015792ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (44.674173ms)
✔ gitea_merge_pull_request refuses a verdict judged against an earlier trunk, and asks the check once (156.379671ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (18.839793ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (16.339974ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (28.334559ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (59.591098ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (21.228222ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (4.886408ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (6.602578ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (13.867054ms)
✔ a repository owner or name is never another path (0.50119ms)
✔ the judges, without a forge (0.6264ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (1.695099ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (3.182499ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (4.112359ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (38.496675ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.53468ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (18.702633ms)
✔ a lookup the forge refuses is thrown, not read as no module (5.125858ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (6.293268ms)
✔ a new head beyond the newest 20 open pull requests is announced, exactly once (108.90593ms)
✔ a pass pages until it reaches back past its floor, and no further (34.386068ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (30.510988ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.37181ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (22.485392ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (0.98996ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.75262ms)
✔ no verdict sets warning on a required check; only a person's decision fails (1.05753ms)
✔ a commit status is set on the commit, under the merge check's context (64.044846ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.5796ms)
✔ a head announced is pending in the build queue, since the local time it was announced (0.23947ms)
✔ the vault's token is used, a setting of it is ignored and said once, without its value (8.704487ms)
✔ a consumer that asks nothing, or write, publishes: the package team (1.532369ms)
✔ a consumer that asks to read joins the readers, who write no package (0.46669ms)
✔ a reader is made, put on the readers team, and taken off the package team (0.49913ms)
✔ a publisher is made and put on the package team, as before (0.20277ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (28.542969ms)
✔ second start: reuses the kept token, mints nothing (17.078074ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (23.426491ms)
✔ a token kept with write:admin by an earlier build is minted again without it at the next start, and the forge drops the old one (19.479803ms)
✔ a kept token lacking a scope the tools need: the forge refuses the call for the scope, the token is re-minted with the whole list, and the call goes through (18.434833ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (16.906523ms)
✔ concurrent first calls share one mint (10.662347ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (12.343455ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (1.073999ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (18.650983ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (2.838759ms)
✔ nothing to mint with and no token: the client says what is missing (0.21116ms)
[gitea] no tools — no Gitea token, and nothing to mint one with — set MESH_GITEA_TOKEN, or MESH_GITEA_ADMIN_USER, MESH_GITEA_ADMIN_PASSWORD_FILE, MESH_GITEA_STATE_DIR
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-admin" (write:repository, write:issue, read:user), kept at /tmp/gitea-1DKCMu/state/token
✔ the tools register once there is a way to a token, and the first call mints it (9.385357ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-admin" (write:repository, write:issue, read:user), kept at /tmp/gitea-S3RNoq/state/token
✔ a pull request can be closed, read and cleaned up through the tools (21.836122ms)
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 25 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host's main moved from d28ca95ccccc to ffffffffffff: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host: whether a trunk moved could not be weighed, so it is weighed again on the next pass — the forge is away
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
✔ both statuses say the head and the trunk commit the verdict judged, first, whatever the summary's length (2.029399ms)
✔ a trunk's move puts an open head's merge check out of date, naming both commits, unless a check is on its way (1.3092ms)
✔ a push to a trunk puts every open pull request into it out of date — twenty-five of them — and none into another (4.491978ms)
✔ a trunk that cannot be read is weighed again on the next pass, never recorded half (0.40437ms)
✔ the trunk's move takes its turn after a verdict in flight, and weighs what the forge holds then (0.42781ms)
✔ a verdict judged against a trunk newer than the pass's first read is not put out of date (0.26615ms)
✔ a verdict that landed out of date keeps its account on the pull request (0.238779ms)
ℹ tests 89
ℹ suites 0
ℹ pass 89
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 713.244892
type-checking modules/lab (TypeScript)
testing modules/lab (TypeScript)
✔ a checkout with a lock file at its root is installed there (3.011069ms)
✔ a checkout whose package lives one directory down is installed there, as mesh-tools' node-tools (0.93218ms)
✔ a package.json with no lock file is named, never installed (0.57298ms)
✔ a run installs and builds mesh-tools in node-tools, where its lock file is (108.917591ms)
✔ a run installs a checkout with its lock file at the root there, and builds only when asked (5.282728ms)
✔ a run fails at its setup, naming the checkout, when nothing of it can be installed (13.465246ms)
✔ a run checks out mesh-tools-go and says where its checkouts are (0.773869ms)
✔ the forge credential is the bound account and its sealed secret, for the forge's own origin (0.64107ms)
✔ a run clones through git's credential store, and its script carries no secret (0.77704ms)
✔ git presents the stored credential to the forge, and only when one is given (11.349896ms)
✔ a stopped script removes the credential, whatever ends it (36.935587ms)
✔ the script removes the credential as soon as its clones are done (0.408229ms)
✔ credentials a crashed run left are removed, but not the running one's (0.77678ms)
✔ the lab asks the package registry for read only (0.33823ms)
✔ each check gets a fresh directory of its own (0.36773ms)
﹣ what earlier checks left is cleared, and what cannot be is named rather than tripped over (0.0509ms) # running as root, which no directory mode stops from removing anything
✔ leftover checks are cleared, whoever runs the check (0.73492ms)
✔ the lab's tools bundle names its bound credential and binding in its env (0.1439ms)
✔ a run names the package registry by the address the registry gives its tarballs (18.069253ms)
✔ a registry that does not give an address the lab takes leaves the forge's, and the reason is said (4.457299ms)
✔ a run's npm configuration names the registry it was given, and the forge's own address only without one (0.386269ms)
✔ a tarball is taken at the forge's public root, and at no other port or scheme of its host (1.24274ms)
✔ the public root is read from the binding, and only an https root at the npm path is taken (1.57045ms)
✔ a forge setting that is not http(s) trusts no origin of its own (0.527789ms)
ℹ tests 24
ℹ suites 0
ℹ pass 23
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1
ℹ todo 0
ℹ duration_ms 318.720518
**Merge check** at `1faee6e2`
Judged: head `1faee6e27976` merged into trunk `9c166398f204`; read beside it: mesh-controller `9f3aa91e812b`, mesh-controller-main `9f3aa91e812b`, mesh-host `0e92ef3d1b21`, mesh-tools `954e1a53eef9`
- `mesh/merge-gate`: **FAIL** — 4 problem(s) the change brings; the first: a machine (f35): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry" (modules: gitea, lab)
- `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 gitea, lab → novox, shanks; no bus step
- tier 0: gitea, lab
- novox: receives gitea
- shanks: receives lab
- gitea provides npm-package-registry, git: its consumers are sent again after it
- gitea keeps data (ADR 0233): what it holds is backed up before it moves
- lab 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 lab is judged on its first machine
Run by the build seat on novox as `build-1791916408401279391` (`builds --log build-1791916408401279391`).
```
--- the gate: gitea, lab
gitea: ok, keeps data (valuable); 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)
lab: ok, keeps work (cache); WARNING: runtime-env 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: 1
files asking for a setting without saying whether it is trusted: 1
2 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 2 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T17:37:27Z 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: FAIL — 4 problem(s) the change brings; the first: a machine (f35): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
judged by controller development build against the facts of 2026-10-13T17:37:27Z
fails:
- a machine (f35): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
- a machine (yvbpsm): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
- 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): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
- holds mesh-dns-resolver (idt): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry"
modules the change moves:
- gitea (changed)
- lab (changed)
machines:
- a machine (f35): does not compose; the change adds 0 resource(s) and removes 399
- holds mesh-dns-resolver (idt): does not compose; the change adds 0 resource(s) and removes 732
- 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): does not compose; the change adds 0 resource(s) and removes 554
- a machine (yvbpsm): does not compose; the change adds 0 resource(s) and removes 377
a merge would rebuild 2 module(s) in 1 tier(s): gitea, lab
mesh-controller: the change fails the merge gate
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.009s
words: 12 retired words and 70 daemons' lines, none in the 46600 strings of 717 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.015s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/secrets 0.015s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
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: 66, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.233s
testing modules/gitea
ok gitea/cmd/forge 1.510s
ok gitea/cmd/npm-registry 1.818s
? gitea/internal/forgehttp [no test files]
modules/lab is TypeScript: merge-check-typescript.sh type-checks and tests it
--- its merge-check-typescript.sh (typescript toolchain)
type-checking modules/gitea (TypeScript)
testing modules/gitea (TypeScript)
✔ a pull request opened after the margin is looked at, by its own update time (11.890195ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (62.976247ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (2.954668ms)
✔ a merged pull request whose branch was deleted is announced with its branch, not its pull ref (15.273695ms)
✔ the newest status of each context is kept, with what it said, when and by whom (31.235028ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.67224ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.34326ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (2.214389ms)
✔ a note line is appended once, as the forge's user, in its own repository (31.829728ms)
✔ a note that cannot be written is said, never read as written (0.513529ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.72338ms)
✔ a build machine taking the check is said on the head: building on which machine, since the local time (18.603173ms)
✔ the verdict heard while a 'building' write is in flight lands last and stays (8.390157ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ the verdict written while a start's read of the forge is in flight stays the newest status (1.27211ms)
✔ the verdict heard while a 'building' write still waits its turn: the 'building' is never written (0.51767ms)
✔ a start heard after the verdict writes nothing (0.797669ms)
✔ a start from before the check now pending (a run before a recheck, the stream's history) writes nothing (0.56111ms)
✔ an old run's verdict redelivered after a recheck's pending is not written over it (3.481479ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check the forge holds pending; not written
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ an old run's verdict in flight when the recheck comes lands before the recheck's pending (3.625999ms)
✔ a rechecked head says where its new run is: building on which machine (0.927929ms)
✔ the recheck's own verdict is written, even asked a little before the pending by another machine's clock (0.69901ms)
✔ after a restart, an old run's verdict is weighed against the check the forge's history holds open (0.80926ms)
✔ a recheck the forge does not take fails its caller, and the head is as it was (0.550129ms)
✔ the status tool writes the merge check's pending through the writer, and the mesh's other statuses as they were (15.466125ms)
✔ when a check was asked is read from its id; the opening of the check now pending from the gate's history (0.266649ms)
✔ a head this process did not announce (it restarted) is said only while the forge holds its gate pending (0.38342ms)
✔ a repository as the mesh clones it is told from another (0.28785ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (7.851197ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (67.240715ms)
✔ a protection rule covers a branch as the forge decides it: classes, alternatives, a plain name in any case (issue 539) (30.070249ms)
✔ gitea_api still writes what an author writes (9.666326ms)
✔ gitea_branch_protection_set only strengthens (23.015792ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (44.674173ms)
✔ gitea_merge_pull_request refuses a verdict judged against an earlier trunk, and asks the check once (156.379671ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (18.839793ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (16.339974ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (28.334559ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (59.591098ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (21.228222ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (4.886408ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (6.602578ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (13.867054ms)
✔ a repository owner or name is never another path (0.50119ms)
✔ the judges, without a forge (0.6264ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (1.695099ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (3.182499ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (4.112359ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (38.496675ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.53468ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (18.702633ms)
✔ a lookup the forge refuses is thrown, not read as no module (5.125858ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (6.293268ms)
✔ a new head beyond the newest 20 open pull requests is announced, exactly once (108.90593ms)
✔ a pass pages until it reaches back past its floor, and no further (34.386068ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (30.510988ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.37181ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (22.485392ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (0.98996ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.75262ms)
✔ no verdict sets warning on a required check; only a person's decision fails (1.05753ms)
✔ a commit status is set on the commit, under the merge check's context (64.044846ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.5796ms)
✔ a head announced is pending in the build queue, since the local time it was announced (0.23947ms)
✔ the vault's token is used, a setting of it is ignored and said once, without its value (8.704487ms)
✔ a consumer that asks nothing, or write, publishes: the package team (1.532369ms)
✔ a consumer that asks to read joins the readers, who write no package (0.46669ms)
✔ a reader is made, put on the readers team, and taken off the package team (0.49913ms)
✔ a publisher is made and put on the package team, as before (0.20277ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (28.542969ms)
✔ second start: reuses the kept token, mints nothing (17.078074ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (23.426491ms)
✔ a token kept with write:admin by an earlier build is minted again without it at the next start, and the forge drops the old one (19.479803ms)
✔ a kept token lacking a scope the tools need: the forge refuses the call for the scope, the token is re-minted with the whole list, and the call goes through (18.434833ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (16.906523ms)
✔ concurrent first calls share one mint (10.662347ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (12.343455ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (1.073999ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (18.650983ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (2.838759ms)
✔ nothing to mint with and no token: the client says what is missing (0.21116ms)
[gitea] no tools — no Gitea token, and nothing to mint one with — set MESH_GITEA_TOKEN, or MESH_GITEA_ADMIN_USER, MESH_GITEA_ADMIN_PASSWORD_FILE, MESH_GITEA_STATE_DIR
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-admin" (write:repository, write:issue, read:user), kept at /tmp/gitea-1DKCMu/state/token
✔ the tools register once there is a way to a token, and the first call mints it (9.385357ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-admin" (write:repository, write:issue, read:user), kept at /tmp/gitea-S3RNoq/state/token
✔ a pull request can be closed, read and cleaned up through the tools (21.836122ms)
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 25 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host's main moved from d28ca95ccccc to ffffffffffff: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host: whether a trunk moved could not be weighed, so it is weighed again on the next pass — the forge is away
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
✔ both statuses say the head and the trunk commit the verdict judged, first, whatever the summary's length (2.029399ms)
✔ a trunk's move puts an open head's merge check out of date, naming both commits, unless a check is on its way (1.3092ms)
✔ a push to a trunk puts every open pull request into it out of date — twenty-five of them — and none into another (4.491978ms)
✔ a trunk that cannot be read is weighed again on the next pass, never recorded half (0.40437ms)
✔ the trunk's move takes its turn after a verdict in flight, and weighs what the forge holds then (0.42781ms)
✔ a verdict judged against a trunk newer than the pass's first read is not put out of date (0.26615ms)
✔ a verdict that landed out of date keeps its account on the pull request (0.238779ms)
ℹ tests 89
ℹ suites 0
ℹ pass 89
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 713.244892
type-checking modules/lab (TypeScript)
testing modules/lab (TypeScript)
✔ a checkout with a lock file at its root is installed there (3.011069ms)
✔ a checkout whose package lives one directory down is installed there, as mesh-tools' node-tools (0.93218ms)
✔ a package.json with no lock file is named, never installed (0.57298ms)
✔ a run installs and builds mesh-tools in node-tools, where its lock file is (108.917591ms)
✔ a run installs a checkout with its lock file at the root there, and builds only when asked (5.282728ms)
✔ a run fails at its setup, naming the checkout, when nothing of it can be installed (13.465246ms)
✔ a run checks out mesh-tools-go and says where its checkouts are (0.773869ms)
✔ the forge credential is the bound account and its sealed secret, for the forge's own origin (0.64107ms)
✔ a run clones through git's credential store, and its script carries no secret (0.77704ms)
✔ git presents the stored credential to the forge, and only when one is given (11.349896ms)
✔ a stopped script removes the credential, whatever ends it (36.935587ms)
✔ the script removes the credential as soon as its clones are done (0.408229ms)
✔ credentials a crashed run left are removed, but not the running one's (0.77678ms)
✔ the lab asks the package registry for read only (0.33823ms)
✔ each check gets a fresh directory of its own (0.36773ms)
﹣ what earlier checks left is cleared, and what cannot be is named rather than tripped over (0.0509ms) # running as root, which no directory mode stops from removing anything
✔ leftover checks are cleared, whoever runs the check (0.73492ms)
✔ the lab's tools bundle names its bound credential and binding in its env (0.1439ms)
✔ a run names the package registry by the address the registry gives its tarballs (18.069253ms)
✔ a registry that does not give an address the lab takes leaves the forge's, and the reason is said (4.457299ms)
✔ a run's npm configuration names the registry it was given, and the forge's own address only without one (0.386269ms)
✔ a tarball is taken at the forge's public root, and at no other port or scheme of its host (1.24274ms)
✔ the public root is read from the binding, and only an https root at the npm path is taken (1.57045ms)
✔ a forge setting that is not http(s) trusts no origin of its own (0.527789ms)
ℹ tests 24
ℹ suites 0
ℹ pass 23
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1
ℹ todo 0
ℹ duration_ms 318.720518
```
Verdict: approve at 1faee6e2. Merge it only after mesh-controller #306 is merged and its walk is done, and then only on a green merge gate.
Re-review after review 222.
The head changed from 9e2c1531 to 6f7d067 + 1faee6e2. 6f7d067 carries the same change as before; the only tree difference is main's own claude-code commits.
1faee6e2 closes the runs.ts:129 nit. The forge setting's own origin is trusted only when its scheme is http: or https:. A file: forge (origin "null") or an ftp: forge now trusts nothing of its own. The new test covers both cases.
Lab tests at the head: 24 of 24 pass.
Statuses read now: mesh/repo-check is a success ("head 1faee6e279 merged into trunk 9c166398f2 — pass"). mesh/merge-gate is a failure ("fail: builds gitea, lab → novox, shanks; ... 4 problem(s) the change brings"), as expected under today's controller, which does not fill ${bound:...} in serves. mesh/delivery is a failure. After #306 is live, ask mesh-delivery.recheck. If the gate is still red, read its 4 problems before merging.
Verdict: approve at 1faee6e2. Merge it only after mesh-controller #306 is merged and its walk is done, and then only on a green merge gate.
Re-review after review 222.
- The head changed from 9e2c1531 to 6f7d067 + 1faee6e2. 6f7d067 carries the same change as before; the only tree difference is main's own claude-code commits.
- 1faee6e2 closes the runs.ts:129 nit. The forge setting's own origin is trusted only when its scheme is http: or https:. A file: forge (origin "null") or an ftp: forge now trusts nothing of its own. The new test covers both cases.
- Lab tests at the head: 24 of 24 pass.
Statuses read now: mesh/repo-check is a success ("head 1faee6e27976 merged into trunk 9c166398f204 — pass"). mesh/merge-gate is a failure ("fail: builds gitea, lab → novox, shanks; ... 4 problem(s) the change brings"), as expected under today's controller, which does not fill ${bound:...} in serves. mesh/delivery is a failure. After #306 is live, ask mesh-delivery.recheck. If the gate is still red, read its 4 problems before merging.
Merge check at 1faee6e2
Judged: head 1faee6e27976 merged into trunk faebc3569acf; read beside it: mesh-controller bff5b7f0e4f8, mesh-controller-main bff5b7f0e4f8, mesh-host 0e92ef3d1b21, mesh-tools 954e1a53eef9
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: gitea, lab)
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 gitea, lab → novox, shanks; no bus step
tier 0: gitea, lab
novox: receives gitea
shanks: receives lab
gitea provides npm-package-registry, git: its consumers are sent again after it
gitea keeps data (ADR 0233): what it holds is backed up before it moves
lab 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 lab is judged on its first machine
Run by the build seat on shanks as build-1791920231330576030 (builds --log build-1791920231330576030).
--- the gate: gitea, lab
gitea: ok, keeps data (valuable); 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)
lab: ok, keeps work (cache); WARNING: runtime-env 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: 1
files asking for a setting without saying whether it is trusted: 1
2 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 2 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T19:37:05Z 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-13T19:37:05Z
modules the change moves:
- gitea (changed)
- lab (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 2 module(s) in 1 tier(s): gitea, lab
--- 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 46775 strings of 718 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/secrets 0.012s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
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: 66, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.220s
testing modules/gitea
ok gitea/cmd/forge 1.488s
ok gitea/cmd/npm-registry 1.808s
? gitea/internal/forgehttp [no test files]
modules/lab is TypeScript: merge-check-typescript.sh type-checks and tests it
--- its merge-check-typescript.sh (typescript toolchain)
type-checking modules/gitea (TypeScript)
testing modules/gitea (TypeScript)
✔ a pull request opened after the margin is looked at, by its own update time (11.115367ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (53.848441ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.527757ms)
✔ a merged pull request whose branch was deleted is announced with its branch, not its pull ref (10.58568ms)
✔ the newest status of each context is kept, with what it said, when and by whom (16.352845ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.670698ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.298354ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (0.757969ms)
✔ a note line is appended once, as the forge's user, in its own repository (13.622995ms)
✔ a note that cannot be written is said, never read as written (0.468995ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.80283ms)
✔ a build machine taking the check is said on the head: building on which machine, since the local time (13.735235ms)
✔ the verdict heard while a 'building' write is in flight lands last and stays (6.494303ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ the verdict written while a start's read of the forge is in flight stays the newest status (1.232044ms)
✔ the verdict heard while a 'building' write still waits its turn: the 'building' is never written (0.603667ms)
✔ a start heard after the verdict writes nothing (0.631598ms)
✔ a start from before the check now pending (a run before a recheck, the stream's history) writes nothing (0.549856ms)
✔ an old run's verdict redelivered after a recheck's pending is not written over it (0.8969ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check the forge holds pending; not written
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ an old run's verdict in flight when the recheck comes lands before the recheck's pending (3.043954ms)
✔ a rechecked head says where its new run is: building on which machine (1.054652ms)
✔ the recheck's own verdict is written, even asked a little before the pending by another machine's clock (0.813049ms)
✔ after a restart, an old run's verdict is weighed against the check the forge's history holds open (0.636177ms)
✔ a recheck the forge does not take fails its caller, and the head is as it was (0.591527ms)
✔ the status tool writes the merge check's pending through the writer, and the mesh's other statuses as they were (5.25242ms)
✔ when a check was asked is read from its id; the opening of the check now pending from the gate's history (0.218962ms)
✔ a head this process did not announce (it restarted) is said only while the forge holds its gate pending (0.296373ms)
✔ a repository as the mesh clones it is told from another (0.235013ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (7.410923ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (49.853905ms)
✔ a protection rule covers a branch as the forge decides it: classes, alternatives, a plain name in any case (issue 539) (14.645226ms)
✔ gitea_api still writes what an author writes (7.198281ms)
✔ gitea_branch_protection_set only strengthens (16.189693ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (45.014311ms)
✔ gitea_merge_pull_request refuses a verdict judged against an earlier trunk, and asks the check once (143.864031ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (17.385007ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (12.645363ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (24.328145ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (52.049141ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (19.944146ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (4.030006ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (6.684546ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (11.249608ms)
✔ a repository owner or name is never another path (0.326873ms)
✔ the judges, without a forge (0.333123ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (2.493068ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (1.178163ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (3.772513ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (25.55318ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.397565ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (11.651863ms)
✔ a lookup the forge refuses is thrown, not read as no module (3.757103ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (4.617403ms)
✔ a new head beyond the newest 20 open pull requests is announced, exactly once (47.401658ms)
✔ a pass pages until it reaches back past its floor, and no further (22.93576ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (6.378782ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.275523ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (9.656449ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (0.980781ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.8855ms)
✔ no verdict sets warning on a required check; only a person's decision fails (1.233094ms)
✔ a commit status is set on the commit, under the merge check's context (39.522438ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.591647ms)
✔ a head announced is pending in the build queue, since the local time it was announced (0.289604ms)
✔ the vault's token is used, a setting of it is ignored and said once, without its value (3.245237ms)
✔ a consumer that asks nothing, or write, publishes: the package team (1.426257ms)
✔ a consumer that asks to read joins the readers, who write no package (0.371074ms)
✔ a reader is made, put on the readers team, and taken off the package team (0.512905ms)
✔ a publisher is made and put on the package team, as before (0.205892ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (36.013518ms)
✔ second start: reuses the kept token, mints nothing (18.805434ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (21.292752ms)
✔ a token kept with write:admin by an earlier build is minted again without it at the next start, and the forge drops the old one (23.896931ms)
✔ a kept token lacking a scope the tools need: the forge refuses the call for the scope, the token is re-minted with the whole list, and the call goes through (16.980033ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (20.167659ms)
✔ concurrent first calls share one mint (11.682612ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (13.647295ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (1.215983ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (20.2995ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (1.582928ms)
✔ nothing to mint with and no token: the client says what is missing (0.190162ms)
[gitea] no tools — no Gitea token, and nothing to mint one with — set MESH_GITEA_TOKEN, or MESH_GITEA_ADMIN_USER, MESH_GITEA_ADMIN_PASSWORD_FILE, MESH_GITEA_STATE_DIR
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-admin" (write:repository, write:issue, read:user), kept at /tmp/gitea-FZNXLg/state/token
✔ the tools register once there is a way to a token, and the first call mints it (9.564758ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-admin" (write:repository, write:issue, read:user), kept at /tmp/gitea-pLIFlg/state/token
✔ a pull request can be closed, read and cleaned up through the tools (18.675331ms)
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 25 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host's main moved from d28ca95ccccc to ffffffffffff: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host: whether a trunk moved could not be weighed, so it is weighed again on the next pass — the forge is away
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
✔ both statuses say the head and the trunk commit the verdict judged, first, whatever the summary's length (1.627838ms)
✔ a trunk's move puts an open head's merge check out of date, naming both commits, unless a check is on its way (1.002631ms)
✔ a push to a trunk puts every open pull request into it out of date — twenty-five of them — and none into another (4.543412ms)
✔ a trunk that cannot be read is weighed again on the next pass, never recorded half (0.384704ms)
✔ the trunk's move takes its turn after a verdict in flight, and weighs what the forge holds then (0.447515ms)
✔ a verdict judged against a trunk newer than the pass's first read is not put out of date (0.285664ms)
✔ a verdict that landed out of date keeps its account on the pull request (0.267283ms)
ℹ tests 89
ℹ suites 0
ℹ pass 89
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 606.03411
type-checking modules/lab (TypeScript)
testing modules/lab (TypeScript)
✔ a checkout with a lock file at its root is installed there (2.61315ms)
✔ a checkout whose package lives one directory down is installed there, as mesh-tools' node-tools (0.858329ms)
✔ a package.json with no lock file is named, never installed (0.541506ms)
✔ a run installs and builds mesh-tools in node-tools, where its lock file is (9.315686ms)
✔ a run installs a checkout with its lock file at the root there, and builds only when asked (5.466552ms)
✔ a run fails at its setup, naming the checkout, when nothing of it can be installed (13.621935ms)
✔ a run checks out mesh-tools-go and says where its checkouts are (0.84876ms)
✔ the forge credential is the bound account and its sealed secret, for the forge's own origin (0.83047ms)
✔ a run clones through git's credential store, and its script carries no secret (1.034271ms)
✔ git presents the stored credential to the forge, and only when one is given (12.628734ms)
✔ a stopped script removes the credential, whatever ends it (13.617885ms)
✔ the script removes the credential as soon as its clones are done (0.347084ms)
✔ credentials a crashed run left are removed, but not the running one's (0.731938ms)
✔ the lab asks the package registry for read only (0.340064ms)
✔ each check gets a fresh directory of its own (0.381924ms)
﹣ what earlier checks left is cleared, and what cannot be is named rather than tripped over (0.05953ms) # running as root, which no directory mode stops from removing anything
✔ leftover checks are cleared, whoever runs the check (0.773199ms)
✔ the lab's tools bundle names its bound credential and binding in its env (0.145002ms)
✔ a run names the package registry by the address the registry gives its tarballs (18.113296ms)
✔ a registry that does not give an address the lab takes leaves the forge's, and the reason is said (4.42521ms)
✔ a run's npm configuration names the registry it was given, and the forge's own address only without one (0.326324ms)
✔ a tarball is taken at the forge's public root, and at no other port or scheme of its host (1.133153ms)
✔ the public root is read from the binding, and only an https root at the npm path is taken (1.645509ms)
✔ a forge setting that is not http(s) trusts no origin of its own (0.480616ms)
ℹ tests 24
ℹ suites 0
ℹ pass 23
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1
ℹ todo 0
ℹ duration_ms 203.018819
**Merge check** at `1faee6e2`
Judged: head `1faee6e27976` merged into trunk `faebc3569acf`; read beside it: mesh-controller `bff5b7f0e4f8`, mesh-controller-main `bff5b7f0e4f8`, mesh-host `0e92ef3d1b21`, mesh-tools `954e1a53eef9`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: gitea, lab)
- `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 gitea, lab → novox, shanks; no bus step
- tier 0: gitea, lab
- novox: receives gitea
- shanks: receives lab
- gitea provides npm-package-registry, git: its consumers are sent again after it
- gitea keeps data (ADR 0233): what it holds is backed up before it moves
- lab 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 lab is judged on its first machine
Run by the build seat on shanks as `build-1791920231330576030` (`builds --log build-1791920231330576030`).
```
--- the gate: gitea, lab
gitea: ok, keeps data (valuable); 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)
lab: ok, keeps work (cache); WARNING: runtime-env 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: 1
files asking for a setting without saying whether it is trusted: 1
2 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 2 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T19:37:05Z 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-13T19:37:05Z
modules the change moves:
- gitea (changed)
- lab (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 2 module(s) in 1 tier(s): gitea, lab
--- 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 46775 strings of 718 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/secrets 0.012s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
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: 66, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.220s
testing modules/gitea
ok gitea/cmd/forge 1.488s
ok gitea/cmd/npm-registry 1.808s
? gitea/internal/forgehttp [no test files]
modules/lab is TypeScript: merge-check-typescript.sh type-checks and tests it
--- its merge-check-typescript.sh (typescript toolchain)
type-checking modules/gitea (TypeScript)
testing modules/gitea (TypeScript)
✔ a pull request opened after the margin is looked at, by its own update time (11.115367ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (53.848441ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.527757ms)
✔ a merged pull request whose branch was deleted is announced with its branch, not its pull ref (10.58568ms)
✔ the newest status of each context is kept, with what it said, when and by whom (16.352845ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.670698ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.298354ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (0.757969ms)
✔ a note line is appended once, as the forge's user, in its own repository (13.622995ms)
✔ a note that cannot be written is said, never read as written (0.468995ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.80283ms)
✔ a build machine taking the check is said on the head: building on which machine, since the local time (13.735235ms)
✔ the verdict heard while a 'building' write is in flight lands last and stays (6.494303ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ the verdict written while a start's read of the forge is in flight stays the newest status (1.232044ms)
✔ the verdict heard while a 'building' write still waits its turn: the 'building' is never written (0.603667ms)
✔ a start heard after the verdict writes nothing (0.631598ms)
✔ a start from before the check now pending (a run before a recheck, the stream's history) writes nothing (0.549856ms)
✔ an old run's verdict redelivered after a recheck's pending is not written over it (0.8969ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check the forge holds pending; not written
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ an old run's verdict in flight when the recheck comes lands before the recheck's pending (3.043954ms)
✔ a rechecked head says where its new run is: building on which machine (1.054652ms)
✔ the recheck's own verdict is written, even asked a little before the pending by another machine's clock (0.813049ms)
✔ after a restart, an old run's verdict is weighed against the check the forge's history holds open (0.636177ms)
✔ a recheck the forge does not take fails its caller, and the head is as it was (0.591527ms)
✔ the status tool writes the merge check's pending through the writer, and the mesh's other statuses as they were (5.25242ms)
✔ when a check was asked is read from its id; the opening of the check now pending from the gate's history (0.218962ms)
✔ a head this process did not announce (it restarted) is said only while the forge holds its gate pending (0.296373ms)
✔ a repository as the mesh clones it is told from another (0.235013ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (7.410923ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (49.853905ms)
✔ a protection rule covers a branch as the forge decides it: classes, alternatives, a plain name in any case (issue 539) (14.645226ms)
✔ gitea_api still writes what an author writes (7.198281ms)
✔ gitea_branch_protection_set only strengthens (16.189693ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (45.014311ms)
✔ gitea_merge_pull_request refuses a verdict judged against an earlier trunk, and asks the check once (143.864031ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (17.385007ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (12.645363ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (24.328145ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (52.049141ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (19.944146ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (4.030006ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (6.684546ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (11.249608ms)
✔ a repository owner or name is never another path (0.326873ms)
✔ the judges, without a forge (0.333123ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (2.493068ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (1.178163ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (3.772513ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (25.55318ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.397565ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (11.651863ms)
✔ a lookup the forge refuses is thrown, not read as no module (3.757103ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (4.617403ms)
✔ a new head beyond the newest 20 open pull requests is announced, exactly once (47.401658ms)
✔ a pass pages until it reaches back past its floor, and no further (22.93576ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (6.378782ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.275523ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (9.656449ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (0.980781ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.8855ms)
✔ no verdict sets warning on a required check; only a person's decision fails (1.233094ms)
✔ a commit status is set on the commit, under the merge check's context (39.522438ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.591647ms)
✔ a head announced is pending in the build queue, since the local time it was announced (0.289604ms)
✔ the vault's token is used, a setting of it is ignored and said once, without its value (3.245237ms)
✔ a consumer that asks nothing, or write, publishes: the package team (1.426257ms)
✔ a consumer that asks to read joins the readers, who write no package (0.371074ms)
✔ a reader is made, put on the readers team, and taken off the package team (0.512905ms)
✔ a publisher is made and put on the package team, as before (0.205892ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (36.013518ms)
✔ second start: reuses the kept token, mints nothing (18.805434ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (21.292752ms)
✔ a token kept with write:admin by an earlier build is minted again without it at the next start, and the forge drops the old one (23.896931ms)
✔ a kept token lacking a scope the tools need: the forge refuses the call for the scope, the token is re-minted with the whole list, and the call goes through (16.980033ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (20.167659ms)
✔ concurrent first calls share one mint (11.682612ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (13.647295ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (1.215983ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (20.2995ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (1.582928ms)
✔ nothing to mint with and no token: the client says what is missing (0.190162ms)
[gitea] no tools — no Gitea token, and nothing to mint one with — set MESH_GITEA_TOKEN, or MESH_GITEA_ADMIN_USER, MESH_GITEA_ADMIN_PASSWORD_FILE, MESH_GITEA_STATE_DIR
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-admin" (write:repository, write:issue, read:user), kept at /tmp/gitea-FZNXLg/state/token
✔ the tools register once there is a way to a token, and the first call mints it (9.564758ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-admin" (write:repository, write:issue, read:user), kept at /tmp/gitea-pLIFlg/state/token
✔ a pull request can be closed, read and cleaned up through the tools (18.675331ms)
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 25 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host's main moved from d28ca95ccccc to ffffffffffff: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host: whether a trunk moved could not be weighed, so it is weighed again on the next pass — the forge is away
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
✔ both statuses say the head and the trunk commit the verdict judged, first, whatever the summary's length (1.627838ms)
✔ a trunk's move puts an open head's merge check out of date, naming both commits, unless a check is on its way (1.002631ms)
✔ a push to a trunk puts every open pull request into it out of date — twenty-five of them — and none into another (4.543412ms)
✔ a trunk that cannot be read is weighed again on the next pass, never recorded half (0.384704ms)
✔ the trunk's move takes its turn after a verdict in flight, and weighs what the forge holds then (0.447515ms)
✔ a verdict judged against a trunk newer than the pass's first read is not put out of date (0.285664ms)
✔ a verdict that landed out of date keeps its account on the pull request (0.267283ms)
ℹ tests 89
ℹ suites 0
ℹ pass 89
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 606.03411
type-checking modules/lab (TypeScript)
testing modules/lab (TypeScript)
✔ a checkout with a lock file at its root is installed there (2.61315ms)
✔ a checkout whose package lives one directory down is installed there, as mesh-tools' node-tools (0.858329ms)
✔ a package.json with no lock file is named, never installed (0.541506ms)
✔ a run installs and builds mesh-tools in node-tools, where its lock file is (9.315686ms)
✔ a run installs a checkout with its lock file at the root there, and builds only when asked (5.466552ms)
✔ a run fails at its setup, naming the checkout, when nothing of it can be installed (13.621935ms)
✔ a run checks out mesh-tools-go and says where its checkouts are (0.84876ms)
✔ the forge credential is the bound account and its sealed secret, for the forge's own origin (0.83047ms)
✔ a run clones through git's credential store, and its script carries no secret (1.034271ms)
✔ git presents the stored credential to the forge, and only when one is given (12.628734ms)
✔ a stopped script removes the credential, whatever ends it (13.617885ms)
✔ the script removes the credential as soon as its clones are done (0.347084ms)
✔ credentials a crashed run left are removed, but not the running one's (0.731938ms)
✔ the lab asks the package registry for read only (0.340064ms)
✔ each check gets a fresh directory of its own (0.381924ms)
﹣ what earlier checks left is cleared, and what cannot be is named rather than tripped over (0.05953ms) # running as root, which no directory mode stops from removing anything
✔ leftover checks are cleared, whoever runs the check (0.773199ms)
✔ the lab's tools bundle names its bound credential and binding in its env (0.145002ms)
✔ a run names the package registry by the address the registry gives its tarballs (18.113296ms)
✔ a registry that does not give an address the lab takes leaves the forge's, and the reason is said (4.42521ms)
✔ a run's npm configuration names the registry it was given, and the forge's own address only without one (0.326324ms)
✔ a tarball is taken at the forge's public root, and at no other port or scheme of its host (1.133153ms)
✔ the public root is read from the binding, and only an https root at the npm path is taken (1.645509ms)
✔ a forge setting that is not http(s) trusts no origin of its own (0.480616ms)
ℹ tests 24
ℹ suites 0
ℹ pass 23
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1
ℹ todo 0
ℹ duration_ms 203.018819
```
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.
after: novox/mesh-controller#306
Fixes issue 624 (the lab's runs stop at setup with EALLOWREMOTE), under ADR 0346 (proposed in the knowledge base; design 27, hq #345).
Merge only after mesh-controller #306 is delivered. A controller without it hands the placeholder through as text. The lab then rejects that text (
publicRootOf: not a URL) and falls back as it does today, so nothing breaks, but nothing is fixed either.module checkwith main's controller accepts the gitea manifest, so the gate alone will not hold the order.What
serves.npm-package-registrygainspublic-root:https://${bound:route:name-web}/api/packages/novox/npm/. That is the forge route's composed public name (labelgit+ the providing machine's public domain), the same host the forge writes every tarball at. The README says what a registry consumer is told.publicRootOf(binding)readsserves.public-rootfrom the lab's npm-package-registry binding (MESH_LAB_FORGE_BINDING). It takes only an https URL whose path is exactly the npm path, with no user, query, fragment, whitespace or control character.npmRegistry(forge, get, publicRoot)keeps every check of #275: the raw string is checked for whitespace and control characters, then parsed; no user or password, no query or fragment; the path is under the npm path; the registry is built from the parsed origin, never the raw string.lab_runandlab_checkboth use it. When it falls back, the problem also says why the binding gave no root.Other consumers of npm-package-registry
In the catalogue: build-agent and lab (and gitea provides it).
${bound:npm-package-registry:…}in a file. build-agent's manifest test only requires such a file to beonly-when-bound.servesinto amap[string]anyand uses onlyscheme,portandnpm-path(cmd/mesh-builder), so the new key changes nothing for build-agent.Tests
modules/lab/test/runs.test.ts, new::8443), another scheme (http:), another host, without a public root, and at the forge's host on another port;publicRootOftakes an https root at the npm path, and refuses a missing key, http, another path, a query, a user, an embedded newline, a leftover placeholder, and an unreadable or absent binding.publicRootOf), and the port case fails: main compares hostnames only.merge-check-typescript.shtype-checks both.merge-check.shwithMESH_CHECK_MODULES=lab,giteaandMESH_GATEbuilt from mesh-controller #306 passes: every manifest throughmodule check, and gitea's Go tests.Delivery
novox/mesh-catalog@1faee6e27976— delivered since 2026-10-13T20:11:10ZDelivery plan — builds gitea, lab → novox, shanks; no bus step
Machines
Transitions
faebc3569a(it was rejected against trunk9c166398f2)The commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
9e2c1531Judged: head
9e2c1531e426merged into trunk4d15a155e634; read beside it: mesh-controller6f1a05366c1d, mesh-controller-main6f1a05366c1d, mesh-host0e92ef3d1b21, mesh-tools954e1a53eef9mesh/merge-gate: FAIL — 4 problem(s) the change brings; the first: a machine (f35): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry" (modules: gitea, lab)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 gitea, lab → novox, shanks; no bus step
Run by the build seat on ace as
build-1791903246423675253(builds --log build-1791903246423675253).Verdict: approve at
9e2c1531, to merge only after mesh-controller #306 has merged and its walk is done.Independent review (issue 624, ADR 0346).
What I checked:
Nit, not blocking: runs.ts:129. If the forge setting were not http(s), for example file:///x, own.origin is "null". A file: tarball's origin is also "null", so it matches, and the registry becomes "null/api/...". The forge setting is the operator's, so this cannot be reached from the registry's answer. Refusing an opaque origin ("null") in trusted would close it.
Statuses read at review time: mesh/delivery failure (14:56Z, "rejected: the gate failed ..."), which is the expected failure under today's controller. mesh/merge-gate and mesh/repo-check are pending, "out of date: judged against trunk
4d15a155e6, the trunk is now e209d46faf3d". Ask mesh-delivery.recheck after #306 is live, and merge only on green.9e2c1531e4to1faee6e279Merge check at
1faee6e2Judged: head
1faee6e27976merged into trunk9c166398f204; read beside it: mesh-controller9f3aa91e812b, mesh-controller-main9f3aa91e812b, mesh-host0e92ef3d1b21, mesh-tools954e1a53eef9mesh/merge-gate: FAIL — 4 problem(s) the change brings; the first: a machine (f35): nothing could be sent to it with this change — build-agent has a file that says ${bound:route:name-web}, and build-agent does not require "route". It may name "artifact-store" or "npm-package-registry" (modules: gitea, lab)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 gitea, lab → novox, shanks; no bus step
Run by the build seat on novox as
build-1791916408401279391(builds --log build-1791916408401279391).Verdict: approve at
1faee6e2. Merge it only after mesh-controller #306 is merged and its walk is done, and then only on a green merge gate.Re-review after review 222.
9e2c1531to6f7d067+1faee6e2.6f7d067carries the same change as before; the only tree difference is main's own claude-code commits.1faee6e2closes the runs.ts:129 nit. The forge setting's own origin is trusted only when its scheme is http: or https:. A file: forge (origin "null") or an ftp: forge now trusts nothing of its own. The new test covers both cases.Statuses read now: mesh/repo-check is a success ("head
1faee6e279merged into trunk9c166398f2— pass"). mesh/merge-gate is a failure ("fail: builds gitea, lab → novox, shanks; ... 4 problem(s) the change brings"), as expected under today's controller, which does not fill ${bound:...} in serves. mesh/delivery is a failure. After #306 is live, ask mesh-delivery.recheck. If the gate is still red, read its 4 problems before merging.Merge check at
1faee6e2Judged: head
1faee6e27976merged into trunkfaebc3569acf; read beside it: mesh-controllerbff5b7f0e4f8, mesh-controller-mainbff5b7f0e4f8, mesh-host0e92ef3d1b21, mesh-tools954e1a53eef9mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: gitea, lab)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 gitea, lab → novox, shanks; no bus step
Run by the build seat on shanks as
build-1791920231330576030(builds --log build-1791920231330576030).