Fixes novox/hq issue 446: a pull request's pending check said only that the mesh was mapping its head, for the whole queue wait and build.
Cause. The gitea module writes mesh/merge-gate pending as "the mesh is mapping this head onto its module graph" when it announces a head. Nothing wrote that status again until the controller's checked verdict. A controller check takes about 10 minutes, so readers took the forge for lagging (issue 423).
The gitea module is the one writer of mesh/merge-gate. The first version of this PR had mesh-delivery write "building on …". The review reproduced a race in that version: mesh-delivery read the gate as pending, the gitea module wrote the verdict, and then mesh-delivery wrote "building". The forge keeps only the newest status per context. A pass then read as pending, which blocks the merge until the next push or recheck. A failure hid under "building". Two writers cannot order their writes, so mesh-delivery is left untouched and only the gitea module writes this status.
Change (modules/gitea only).
On announcement, the status reads waiting in the build queue since 03:08, in local time (pendingWords, pulls.ts).
The module now consumes the build seat's node-build-agent.started event (ADR 0157; manifest consumes, the same seat-watch path as mesh-controller.checked). The event's head must be one the module announced and its repository must match. Then the status reads building on shanks since 03:09.
gate.ts (GateWriter) runs every write of a head's gate on one per-head promise chain. A verdict is marked the moment checked is heard, before it waits its turn. A "building" write still queued, or taking its turn after that mark, is dropped, so the verdict always lands last. A failed verdict write still fails the event handler, so the event is redelivered.
A start counts only for the current check. A start from before the pending words were last set is ignored (one minute of clock slack). That covers a run before a recheck and old events replayed from the stream. If the process has restarted and does not remember the head, it reads the forge's gate inside the chain, writes only while that gate is pending, and checks the verdict mark again after the read.
Not in this change: "behind N". The queue position lives only in the controller. Its queue verb answers in text only (queue is not in jsonVerbs), and no event marks an ask being queued. Showing the position needs a controller change: queue answering as data.
Tests (test/gate.test.ts). The started event is shaped like the one hq#309's check produced on 2026-10-11 (builds --log build-1791680941048622697). The tests cover:
a normal start, which writes "building on …";
the verdict arriving while a "building" write is in flight;
the verdict arriving while a start's read of the forge is in flight (the reproduced race);
the verdict arriving while a "building" write still waits its turn;
a start after the verdict, which writes nothing;
a start from before a recheck, which writes nothing;
a restarted process;
telling repositories apart.
Against the old shape (a second writer that reads the gate, then writes, with no chain and no verdict mark), the race test and two others fail, and five pass.
What was run: merge-check-typescript.sh with MESH_CHECK_MODULES=gitea and TOOLCHAIN_MODULES set to a scratch folder assembled by copying typescript, @types/node and undici-types from mesh-tools' node_modules and @novox/mesh-sdk from the gitea module's node_modules (nothing installed). Type-check is clean and 64 of 64 tests pass. Also: the catalogue's words check, and go vet plus go test -race in mesh-delivery, which is unchanged from main.
Reviewers' checklist: the consumer filter for a seat event; the per-head chain in gate.ts; that a verdict write failing still rejects the checked handler; local time on novox.
Fixes novox/hq issue 446: a pull request's pending check said only that the mesh was mapping its head, for the whole queue wait and build.
**Cause.** The gitea module writes `mesh/merge-gate` pending as "the mesh is mapping this head onto its module graph" when it announces a head. Nothing wrote that status again until the controller's `checked` verdict. A controller check takes about 10 minutes, so readers took the forge for lagging (issue 423).
**The gitea module is the one writer of `mesh/merge-gate`.** The first version of this PR had mesh-delivery write "building on …". The review reproduced a race in that version: mesh-delivery read the gate as pending, the gitea module wrote the verdict, and then mesh-delivery wrote "building". The forge keeps only the newest status per context. A pass then read as pending, which blocks the merge until the next push or recheck. A failure hid under "building". Two writers cannot order their writes, so mesh-delivery is left untouched and only the gitea module writes this status.
**Change (modules/gitea only).**
- On announcement, the status reads `waiting in the build queue since 03:08`, in local time (`pendingWords`, `pulls.ts`).
- The module now consumes the build seat's `node-build-agent.started` event (ADR 0157; manifest `consumes`, the same seat-watch path as `mesh-controller.checked`). The event's head must be one the module announced and its repository must match. Then the status reads `building on shanks since 03:09`.
- `gate.ts` (`GateWriter`) runs every write of a head's gate on one per-head promise chain. A verdict is marked the moment `checked` is heard, before it waits its turn. A "building" write still queued, or taking its turn after that mark, is dropped, so the verdict always lands last. A failed verdict write still fails the event handler, so the event is redelivered.
- A start counts only for the current check. A start from before the pending words were last set is ignored (one minute of clock slack). That covers a run before a recheck and old events replayed from the stream. If the process has restarted and does not remember the head, it reads the forge's gate inside the chain, writes only while that gate is pending, and checks the verdict mark again after the read.
**Not in this change: "behind N".** The queue position lives only in the controller. Its `queue` verb answers in text only (`queue` is not in `jsonVerbs`), and no event marks an ask being queued. Showing the position needs a controller change: `queue` answering as data.
**Tests** (`test/gate.test.ts`). The `started` event is shaped like the one hq#309's check produced on 2026-10-11 (`builds --log build-1791680941048622697`). The tests cover:
- a normal start, which writes "building on …";
- the verdict arriving while a "building" write is in flight;
- the verdict arriving while a start's read of the forge is in flight (the reproduced race);
- the verdict arriving while a "building" write still waits its turn;
- a start after the verdict, which writes nothing;
- a start from before a recheck, which writes nothing;
- a restarted process;
- telling repositories apart.
Against the old shape (a second writer that reads the gate, then writes, with no chain and no verdict mark), the race test and two others fail, and five pass.
What was run: `merge-check-typescript.sh` with `MESH_CHECK_MODULES=gitea` and `TOOLCHAIN_MODULES` set to a scratch folder assembled by copying typescript, @types/node and undici-types from mesh-tools' node_modules and @novox/mesh-sdk from the gitea module's node_modules (nothing installed). Type-check is clean and 64 of 64 tests pass. Also: the catalogue's words check, and `go vet` plus `go test -race` in mesh-delivery, which is unchanged from main.
Reviewers' checklist: the consumer filter for a seat event; the per-head chain in `gate.ts`; that a verdict write failing still rejects the `checked` handler; local time on novox.
The forge's holder set mesh/merge-gate pending as "the mesh is mapping this head onto its module graph" and nothing
changed it until the verdict, so a ten-minute controller check read as a stalled forge (issue 423). The holder now
says the head waits in the build queue since the local time it was announced; mesh-delivery follows the build
seat's started event and says the machine building it and since when, never over a verdict the forge holds.
Deliverynovox/mesh-catalog@1cf56688c1d5 — failed since 2026-10-11T01:31:04Z
Covered: covered after its failure: a later walk that contains it was delivered: plan-1791682027628755615 (novox/mesh-catalog at 87b0d3a5 into main, done, contains its merge 87b0d3a5)
Delivery plan — builds gitea → novox; no bus step
tier 0: gitea
novox: receives gitea
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
Machines
build-agent on ace: passed — carried by plan-1791681844507629611
mesh-delivery on novox: passed — carried by plan-1791682028101285377
gitea on novox: passed — carried by release-1791683247815947138
gitea on the rest running it: sent
Transitions
2026-10-11 01:24 (new) → proposed (announced): novox/mesh-catalog#202's head announced
2026-10-11 01:26 proposed → checked (checked): the verdict names this commit
2026-10-11 01:26 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-11 01:30 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
2026-10-11 01:30 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-11 01:31 delivering → failed (failed): its walk failed, carrying its own commit: a gate on a first machine (what it carried put back), a build, a machine
The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.
<!-- mesh-delivery:view -->
**Delivery** `novox/mesh-catalog@1cf56688c1d5` — **failed** since 2026-10-11T01:31:04Z
**Covered**: covered after its failure: a later walk that contains it was delivered: plan-1791682027628755615 (novox/mesh-catalog at 87b0d3a5 into main, done, contains its merge 87b0d3a5)
**Delivery plan** — builds gitea → novox; no bus step
- tier 0: gitea
- novox: receives gitea
- 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
**Machines**
- build-agent on ace: passed — carried by plan-1791681844507629611
- mesh-delivery on novox: passed — carried by plan-1791682028101285377
- gitea on novox: passed — carried by release-1791683247815947138
- gitea on the rest running it: sent
**Transitions**
- 2026-10-11 01:24 (new) → proposed (announced): novox/mesh-catalog#202's head announced
- 2026-10-11 01:26 proposed → checked (checked): the verdict names this commit
- 2026-10-11 01:26 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-11 01:30 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
- 2026-10-11 01:30 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-11 01:31 delivering → failed (failed): its walk failed, carrying its own commit: a gate on a first machine (what it carried put back), a build, a machine
The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: gitea, mesh-delivery)
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, mesh-delivery → novox; no bus step
tier 0: gitea, mesh-delivery
novox: receives gitea, mesh-delivery
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
read by no module's build: modules/mesh-delivery/cmd/mesh-delivery/issue446_test.go
Run by the build seat on novox as build-1791681276173259052 (builds --log build-1791681276173259052).
--- the gate: gitea, mesh-delivery
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)
mesh-delivery: ok, 1 tool(s), invokes seat:mesh-controller.delivery-plan, seat:mesh-controller.delivery-order, seat:mesh-controller.delivery-check, seat:mesh-controller.deliver, seat:mesh-controller.delivery-stop, seat:mesh-controller.delivery-walks, gitea.gitea_note_append, gitea.gitea_delivery_view, gitea.gitea_commit_status, gitea.gitea_commit_statuses, gitea.gitea_contains, gitea.gitea_open_pulls, keeps state deliveries, groups
long-running resources without health: 1
files asking for a setting without saying whether it is trusted: 0
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-11T01:09:19Z 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-11T01:09:19Z
modules the change moves:
- gitea (changed)
- mesh-delivery (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, mesh-delivery
notes:
- modules/mesh-delivery/cmd/mesh-delivery/issue446_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.005s
words: 12 retired words and 70 daemons' lines, none in the 39456 strings of 655 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.013s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 69, as kept
testing modules/gitea
ok gitea/cmd/npm-registry 1.825s
testing modules/mesh-delivery
ok mesh-delivery/cmd/mesh-delivery 2.021s
--- 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 (9.51983ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (62.659984ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.825093ms)
✔ the newest status of each context is kept, with what it said, when and by whom (24.348982ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.672742ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.30929ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (1.028923ms)
✔ a note line is appended once, as the forge's user, in its own repository (9.738731ms)
✔ a note that cannot be written is said, never read as written (0.475081ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.634601ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (4.96203ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (66.153602ms)
✔ gitea_api still writes what an author writes (8.500708ms)
✔ gitea_branch_protection_set only strengthens (17.749228ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (34.975685ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (14.26334ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (10.796023ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (20.314614ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (45.358067ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (23.605851ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (3.443388ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (7.384205ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (13.004157ms)
✔ a repository owner or name is never another path (0.22097ms)
✔ the judges, without a forge (0.283851ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (1.476653ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (2.650086ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (5.01884ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (32.959111ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.371921ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (13.62429ms)
✔ a lookup the forge refuses is thrown, not read as no module (5.054781ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (6.333663ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (7.333726ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.260621ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (10.568473ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (1.110922ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.759262ms)
✔ no verdict sets warning on a required check; only a person's decision fails (1.049532ms)
✔ a commit status is set on the commit, under the merge check's context (39.786395ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.43926ms)
✔ a head announced is pending in the build queue, since the local time it was announced (0.22792ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (52.270252ms)
✔ second start: reuses the kept token, mints nothing (20.396104ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (120.068897ms)
✔ 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 (21.580097ms)
✔ 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 (20.343463ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (16.899336ms)
✔ concurrent first calls share one mint (11.203664ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (11.920145ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (0.899602ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (20.574944ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (2.758936ms)
✔ nothing to mint with and no token: the client says what is missing (0.22698ms)
[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-4z8HJj/state/token
✔ the tools register once there is a way to a token, and the first call mints it (9.024599ms)
[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-4UNZAj/state/token
✔ a pull request can be closed, read and cleaned up through the tools (20.735504ms)
ℹ tests 56
ℹ suites 0
ℹ pass 56
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 510.556983
**Merge check** at `4254d173`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: gitea, mesh-delivery)
- `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, mesh-delivery → novox; no bus step
- tier 0: gitea, mesh-delivery
- novox: receives gitea, mesh-delivery
- 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
- read by no module's build: modules/mesh-delivery/cmd/mesh-delivery/issue446_test.go
Run by the build seat on novox as `build-1791681276173259052` (`builds --log build-1791681276173259052`).
```
--- the gate: gitea, mesh-delivery
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)
mesh-delivery: ok, 1 tool(s), invokes seat:mesh-controller.delivery-plan, seat:mesh-controller.delivery-order, seat:mesh-controller.delivery-check, seat:mesh-controller.deliver, seat:mesh-controller.delivery-stop, seat:mesh-controller.delivery-walks, gitea.gitea_note_append, gitea.gitea_delivery_view, gitea.gitea_commit_status, gitea.gitea_commit_statuses, gitea.gitea_contains, gitea.gitea_open_pulls, keeps state deliveries, groups
long-running resources without health: 1
files asking for a setting without saying whether it is trusted: 0
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-11T01:09:19Z 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-11T01:09:19Z
modules the change moves:
- gitea (changed)
- mesh-delivery (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, mesh-delivery
notes:
- modules/mesh-delivery/cmd/mesh-delivery/issue446_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.005s
words: 12 retired words and 70 daemons' lines, none in the 39456 strings of 655 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.013s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 69, as kept
testing modules/gitea
ok gitea/cmd/npm-registry 1.825s
testing modules/mesh-delivery
ok mesh-delivery/cmd/mesh-delivery 2.021s
--- 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 (9.51983ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (62.659984ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.825093ms)
✔ the newest status of each context is kept, with what it said, when and by whom (24.348982ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.672742ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.30929ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (1.028923ms)
✔ a note line is appended once, as the forge's user, in its own repository (9.738731ms)
✔ a note that cannot be written is said, never read as written (0.475081ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.634601ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (4.96203ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (66.153602ms)
✔ gitea_api still writes what an author writes (8.500708ms)
✔ gitea_branch_protection_set only strengthens (17.749228ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (34.975685ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (14.26334ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (10.796023ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (20.314614ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (45.358067ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (23.605851ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (3.443388ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (7.384205ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (13.004157ms)
✔ a repository owner or name is never another path (0.22097ms)
✔ the judges, without a forge (0.283851ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (1.476653ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (2.650086ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (5.01884ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (32.959111ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.371921ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (13.62429ms)
✔ a lookup the forge refuses is thrown, not read as no module (5.054781ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (6.333663ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (7.333726ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.260621ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (10.568473ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (1.110922ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.759262ms)
✔ no verdict sets warning on a required check; only a person's decision fails (1.049532ms)
✔ a commit status is set on the commit, under the merge check's context (39.786395ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.43926ms)
✔ a head announced is pending in the build queue, since the local time it was announced (0.22792ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (52.270252ms)
✔ second start: reuses the kept token, mints nothing (20.396104ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (120.068897ms)
✔ 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 (21.580097ms)
✔ 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 (20.343463ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (16.899336ms)
✔ concurrent first calls share one mint (11.203664ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (11.920145ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (0.899602ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (20.574944ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (2.758936ms)
✔ nothing to mint with and no token: the client says what is missing (0.22698ms)
[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-4z8HJj/state/token
✔ the tools register once there is a way to a token, and the first call mints it (9.024599ms)
[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-4UNZAj/state/token
✔ a pull request can be closed, read and cleaned up through the tools (20.735504ms)
ℹ tests 56
ℹ suites 0
ℹ pass 56
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 510.556983
```
mesh/delivery covered after its failure: a later walk that contains it was delivered: plan-1791682027628755615 (novox/mesh-catalog at 87b0d3a5 into main,…
mesh-delivery read the gate as pending and then wrote "building on", and the gitea module could write the verdict in
between: the forge keeps the newest status, so a pass read pending and a failure hid under "building". The gitea
module now hears the build seat's started itself and puts every write of a head's gate on one chain, marking a
verdict the moment it is heard; a start from before the pending words were last set is not the current check's.
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: gitea)
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 → novox; no bus step
tier 0: gitea
novox: receives gitea
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
Run by the build seat on g14 as build-1791681861835740730 (builds --log build-1791681861835740730).
--- the gate: gitea
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)
long-running resources without health: 1
files asking for a setting without saying whether it is trusted: 0
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-11T01:18:19Z 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-11T01:18:19Z
modules the change moves:
- gitea (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): gitea
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.004s
words: 12 retired words and 70 daemons' lines, none in the 39448 strings of 655 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.008s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/paths 0.005s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 69, as kept
testing modules/gitea
ok gitea/cmd/npm-registry 1.723s
--- 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 (7.438242ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (37.533356ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.121759ms)
✔ the newest status of each context is kept, with what it said, when and by whom (13.397284ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.648319ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.280337ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (0.791829ms)
✔ a note line is appended once, as the forge's user, in its own repository (12.167333ms)
✔ a note that cannot be written is said, never read as written (0.416914ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.56918ms)
✔ a build machine taking the check is said on the head: building on which machine, since the local time (7.766589ms)
✔ the verdict heard while a 'building' write is in flight lands last and stays (2.545645ms)
✔ the verdict written while a start's read of the forge is in flight stays the newest status (0.65983ms)
✔ the verdict heard while a 'building' write still waits its turn: the 'building' is never written (0.37755ms)
✔ a start heard after the verdict writes nothing (0.25557ms)
✔ a start from before the check now pending (a run before a recheck, the stream's history) writes nothing (0.444124ms)
✔ a head this process did not announce (it restarted) is said only while the forge holds its gate pending (0.251944ms)
✔ a repository as the mesh clones it is told from another (0.945778ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (3.438404ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (40.254721ms)
✔ gitea_api still writes what an author writes (9.595899ms)
✔ gitea_branch_protection_set only strengthens (17.77256ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (29.11436ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (14.464781ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (9.936509ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (19.487513ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (39.018999ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (19.092911ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (3.581132ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (4.938643ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (12.296084ms)
✔ a repository owner or name is never another path (0.358434ms)
✔ the judges, without a forge (0.470485ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (3.236334ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (2.033342ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (3.92056ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (23.878817ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.322256ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (9.710804ms)
✔ a lookup the forge refuses is thrown, not read as no module (4.044483ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (5.694925ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (5.086541ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.249329ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (7.614483ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (0.752625ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.457771ms)
✔ no verdict sets warning on a required check; only a person's decision fails (0.66999ms)
✔ a commit status is set on the commit, under the merge check's context (24.451134ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.461919ms)
✔ a head announced is pending in the build queue, since the local time it was announced (0.198343ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (22.696495ms)
✔ second start: reuses the kept token, mints nothing (17.779282ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (23.446305ms)
✔ 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 (15.90042ms)
✔ 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 (17.029592ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (15.235761ms)
✔ concurrent first calls share one mint (8.76198ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (9.562195ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (0.749869ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (16.005708ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (2.178565ms)
✔ nothing to mint with and no token: the client says what is missing (0.14899ms)
[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-UFzpB5/state/token
✔ the tools register once there is a way to a token, and the first call mints it (7.530286ms)
[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-lFKwz6/state/token
✔ a pull request can be closed, read and cleaned up through the tools (18.873999ms)
ℹ tests 64
ℹ suites 0
ℹ pass 64
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 370.037063
**Merge check** at `1cf56688`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: gitea)
- `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 → novox; no bus step
- tier 0: gitea
- novox: receives gitea
- 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
Run by the build seat on g14 as `build-1791681861835740730` (`builds --log build-1791681861835740730`).
```
--- the gate: gitea
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)
long-running resources without health: 1
files asking for a setting without saying whether it is trusted: 0
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-11T01:18:19Z 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-11T01:18:19Z
modules the change moves:
- gitea (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): gitea
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.004s
words: 12 retired words and 70 daemons' lines, none in the 39448 strings of 655 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.008s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/paths 0.005s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 69, as kept
testing modules/gitea
ok gitea/cmd/npm-registry 1.723s
--- 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 (7.438242ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (37.533356ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.121759ms)
✔ the newest status of each context is kept, with what it said, when and by whom (13.397284ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.648319ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.280337ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (0.791829ms)
✔ a note line is appended once, as the forge's user, in its own repository (12.167333ms)
✔ a note that cannot be written is said, never read as written (0.416914ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.56918ms)
✔ a build machine taking the check is said on the head: building on which machine, since the local time (7.766589ms)
✔ the verdict heard while a 'building' write is in flight lands last and stays (2.545645ms)
✔ the verdict written while a start's read of the forge is in flight stays the newest status (0.65983ms)
✔ the verdict heard while a 'building' write still waits its turn: the 'building' is never written (0.37755ms)
✔ a start heard after the verdict writes nothing (0.25557ms)
✔ a start from before the check now pending (a run before a recheck, the stream's history) writes nothing (0.444124ms)
✔ a head this process did not announce (it restarted) is said only while the forge holds its gate pending (0.251944ms)
✔ a repository as the mesh clones it is told from another (0.945778ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (3.438404ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (40.254721ms)
✔ gitea_api still writes what an author writes (9.595899ms)
✔ gitea_branch_protection_set only strengthens (17.77256ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (29.11436ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (14.464781ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (9.936509ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (19.487513ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (39.018999ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (19.092911ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (3.581132ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (4.938643ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (12.296084ms)
✔ a repository owner or name is never another path (0.358434ms)
✔ the judges, without a forge (0.470485ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (3.236334ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (2.033342ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (3.92056ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (23.878817ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.322256ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (9.710804ms)
✔ a lookup the forge refuses is thrown, not read as no module (4.044483ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (5.694925ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (5.086541ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.249329ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (7.614483ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (0.752625ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.457771ms)
✔ no verdict sets warning on a required check; only a person's decision fails (0.66999ms)
✔ a commit status is set on the commit, under the merge check's context (24.451134ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.461919ms)
✔ a head announced is pending in the build queue, since the local time it was announced (0.198343ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (22.696495ms)
✔ second start: reuses the kept token, mints nothing (17.779282ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (23.446305ms)
✔ 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 (15.90042ms)
✔ 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 (17.029592ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (15.235761ms)
✔ concurrent first calls share one mint (8.76198ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (9.562195ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (0.749869ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (16.005708ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (2.178565ms)
✔ nothing to mint with and no token: the client says what is missing (0.14899ms)
[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-UFzpB5/state/token
✔ the tools register once there is a way to a token, and the first call mints it (7.530286ms)
[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-lFKwz6/state/token
✔ a pull request can be closed, read and cleaned up through the tools (18.873999ms)
ℹ tests 64
ℹ suites 0
ℹ pass 64
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 370.037063
```
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes novox/hq issue 446: a pull request's pending check said only that the mesh was mapping its head, for the whole queue wait and build.
Cause. The gitea module writes
mesh/merge-gatepending as "the mesh is mapping this head onto its module graph" when it announces a head. Nothing wrote that status again until the controller'scheckedverdict. A controller check takes about 10 minutes, so readers took the forge for lagging (issue 423).The gitea module is the one writer of
mesh/merge-gate. The first version of this PR had mesh-delivery write "building on …". The review reproduced a race in that version: mesh-delivery read the gate as pending, the gitea module wrote the verdict, and then mesh-delivery wrote "building". The forge keeps only the newest status per context. A pass then read as pending, which blocks the merge until the next push or recheck. A failure hid under "building". Two writers cannot order their writes, so mesh-delivery is left untouched and only the gitea module writes this status.Change (modules/gitea only).
waiting in the build queue since 03:08, in local time (pendingWords,pulls.ts).node-build-agent.startedevent (ADR 0157; manifestconsumes, the same seat-watch path asmesh-controller.checked). The event's head must be one the module announced and its repository must match. Then the status readsbuilding on shanks since 03:09.gate.ts(GateWriter) runs every write of a head's gate on one per-head promise chain. A verdict is marked the momentcheckedis heard, before it waits its turn. A "building" write still queued, or taking its turn after that mark, is dropped, so the verdict always lands last. A failed verdict write still fails the event handler, so the event is redelivered.Not in this change: "behind N". The queue position lives only in the controller. Its
queueverb answers in text only (queueis not injsonVerbs), and no event marks an ask being queued. Showing the position needs a controller change:queueanswering as data.Tests (
test/gate.test.ts). Thestartedevent is shaped like the one hq#309's check produced on 2026-10-11 (builds --log build-1791680941048622697). The tests cover:Against the old shape (a second writer that reads the gate, then writes, with no chain and no verdict mark), the race test and two others fail, and five pass.
What was run:
merge-check-typescript.shwithMESH_CHECK_MODULES=giteaandTOOLCHAIN_MODULESset to a scratch folder assembled by copying typescript, @types/node and undici-types from mesh-tools' node_modules and @novox/mesh-sdk from the gitea module's node_modules (nothing installed). Type-check is clean and 64 of 64 tests pass. Also: the catalogue's words check, andgo vetplusgo test -racein mesh-delivery, which is unchanged from main.Reviewers' checklist: the consumer filter for a seat event; the per-head chain in
gate.ts; that a verdict write failing still rejects thecheckedhandler; local time on novox.Delivery
novox/mesh-catalog@1cf56688c1d5— failed since 2026-10-11T01:31:04ZCovered: covered after its failure: a later walk that contains it was delivered: plan-1791682027628755615 (novox/mesh-catalog at
87b0d3a5into main, done, contains its merge87b0d3a5)Delivery plan — builds gitea → novox; no bus step
Machines
Transitions
The commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
4254d173mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: gitea, mesh-delivery)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, mesh-delivery → novox; no bus step
Run by the build seat on novox as
build-1791681276173259052(builds --log build-1791681276173259052).Merge check at
1cf56688mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: gitea)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 → novox; no bus step
Run by the build seat on g14 as
build-1791681861835740730(builds --log build-1791681861835740730).