novox/hq issue 438 (the watch counts pending as checked). The mesh-delivery and gitea half of the fix. The controller half is novox/mesh-controller#213, on the same branch name, and it merges first:
after: novox/mesh-controller
What was wrong. mesh-delivery's watch for unchecked heads (unchecked.go, from issue 347, #159) counted a head as checked when it carried any merge-check context, pending included, because gitea_open_pulls gave only the contexts' names. A head left with a pending mesh/merge-gate and no verdict, or never given mesh/repo-check, waited unable to merge, and the watch never said it.
What changes.
gitea, gitea_open_pulls: each pull request also carries checks: the head's statuses by context, the newest of each, with state, description and updated_at (new headChecks, the same forge read as commitStatuses). statuses is unchanged.
mesh-delivery, which heads count as checked: a head is checked only when every merge check its base requires has answered. Success, failure, error and warning all count; a failure is a verdict the pull request shows itself.
A head with no merge check at all is said as before: after 10 minutes, state unannounced, words unchanged.
A head with a required check pending, or never set while another one is set, is said after the bound below, as a stalled line in the new state unanswered. Such a head was announced (the controller set its status), so unannounced would be false.
The line names each check and since when, in says and in a new waiting field for the controller's words, for example mesh/merge-gate pending since 2026-10-11T00:11:39Z, mesh/repo-check never set.
Times are UTC, RFC 3339, as the module's other lines give them; a forge time with an offset is converted.
When the clock restarts. The clock runs from this owner's first sight of the head waiting. It restarts only on a newer ask: a required check set pending for the first time, set pending again (a re-ask), or back to pending after it answered. A check that answers never restarts it, so the head is said one hour after first sight on whatever is still unanswered.
Announced heads are still skipped: their delivery has its own bound.
Rollout order between gitea and mesh-delivery does not matter: a gitea that sends no checks is read by statuses alone, exactly as before (tested).
Why the controller goes first: without mesh-controller#213, an unanswered line would fall into the generic delivery words ("Delivery of X unanswered") and offer a Stop that mesh-delivery.stop refuses.
The bound: 1 hour, Bounds[Proposed].For. It is the delivery table's stated bound for "its check was asked and not answered" (mesh-delivery.table, state proposed), so an unannounced head is said after the same time an announced one would be. It is longer than the controller lets one check run (checkTimeout and the builder's CheckTimeout, 45 minutes, after which the check ends as an error verdict). So a slow check, a queued one, or a status read a moment behind (issue 423) is not said.
The word.unanswered is proposed for the glossary and is not in it yet. It comes from the table's own words for the proposed state's bound; unheld already means a waiting walk no delivery holds.
What I checked.
Real shapes: I read gitea_commit_statuses on mesh-controller #212 and #211 (merge-gate pending, "the mesh is mapping this head onto its module graph"; repo-check missing) and mesh-catalog #194 (both answered). The tests decode that JSON.
Tests in issue438_test.go:
A head with a check started and never answered is not said at 59 minutes and is said at 61 minutes, naming both checks in says and waiting.
A check answering at 50 minutes does not restart the clock: the head is said at 61 minutes on what is left.
A re-asked check restarts its bound.
A time with an offset is said in UTC.
Answered heads (success and failure), and an hq head whose only pending check is one its base does not require, are never said.
A forge without checks is read as before.
Before the fix: the tests that say a head, keep the clock, or give UTC each failed on the code before them; the others passed there, as they should.
go vet ./... and go test ./... -count=1 in modules/mesh-delivery pass, and checks/words passes.
gitea:test/announcing.test.ts asserts checks. I ran it under Node's test runner after compiling it with tsc in a scratch copy. It passes with the fix and fails without it.
What I did not verify.
I did not run the full merge-check-typescript.sh in the toolchain image. gitea's token and guard tests need the SDK, which my scratch copy lacked.
Whether any open pull request today has a required check pending with no delivery held. If one does, it will be said within an hour of this rolling out.
An announced delivery in ready or rejected whose required check is still pending or missing is still not said: those states have no bound. I have not seen this happen, and it is outside this issue.
novox/hq issue 438 (the watch counts pending as checked). The mesh-delivery and gitea half of the fix. The controller half is novox/mesh-controller#213, on the same branch name, and it merges first:
after: novox/mesh-controller
**What was wrong.** mesh-delivery's watch for unchecked heads (`unchecked.go`, from issue 347, #159) counted a head as checked when it carried any merge-check context, pending included, because `gitea_open_pulls` gave only the contexts' names. A head left with a pending `mesh/merge-gate` and no verdict, or never given `mesh/repo-check`, waited unable to merge, and the watch never said it.
**What changes.**
- **gitea, `gitea_open_pulls`:** each pull request also carries `checks`: the head's statuses by context, the newest of each, with `state`, `description` and `updated_at` (new `headChecks`, the same forge read as `commitStatuses`). `statuses` is unchanged.
- **mesh-delivery, which heads count as checked:** a head is checked only when every merge check its base requires has answered. Success, failure, error and warning all count; a failure is a verdict the pull request shows itself.
- A head with no merge check at all is said as before: after 10 minutes, state `unannounced`, words unchanged.
- A head with a required check pending, or never set while another one is set, is said after the bound below, as a `stalled` line in the new state `unanswered`. Such a head was announced (the controller set its status), so `unannounced` would be false.
- The line names each check and since when, in `says` and in a new `waiting` field for the controller's words, for example `mesh/merge-gate pending since 2026-10-11T00:11:39Z`, `mesh/repo-check never set`.
- Times are UTC, RFC 3339, as the module's other lines give them; a forge time with an offset is converted.
- **When the clock restarts.** The clock runs from this owner's first sight of the head waiting. It restarts only on a newer ask: a required check set pending for the first time, set pending again (a re-ask), or back to pending after it answered. A check that answers never restarts it, so the head is said one hour after first sight on whatever is still unanswered.
- Announced heads are still skipped: their delivery has its own bound.
- **Rollout order between gitea and mesh-delivery does not matter:** a gitea that sends no `checks` is read by `statuses` alone, exactly as before (tested).
- **Why the controller goes first:** without mesh-controller#213, an `unanswered` line would fall into the generic delivery words ("Delivery of X unanswered") and offer a Stop that `mesh-delivery.stop` refuses.
**The bound: 1 hour, `Bounds[Proposed].For`.** It is the delivery table's stated bound for "its check was asked and not answered" (`mesh-delivery.table`, state `proposed`), so an unannounced head is said after the same time an announced one would be. It is longer than the controller lets one check run (`checkTimeout` and the builder's `CheckTimeout`, 45 minutes, after which the check ends as an error verdict). So a slow check, a queued one, or a status read a moment behind (issue 423) is not said.
**The word.** `unanswered` is proposed for the glossary and is not in it yet. It comes from the table's own words for the proposed state's bound; `unheld` already means a waiting walk no delivery holds.
**What I checked.**
- **Real shapes:** I read `gitea_commit_statuses` on mesh-controller #212 and #211 (merge-gate `pending`, "the mesh is mapping this head onto its module graph"; repo-check missing) and mesh-catalog #194 (both answered). The tests decode that JSON.
- **Tests in `issue438_test.go`:**
- A head with a check started and never answered is not said at 59 minutes and is said at 61 minutes, naming both checks in `says` and `waiting`.
- A check answering at 50 minutes does not restart the clock: the head is said at 61 minutes on what is left.
- A re-asked check restarts its bound.
- A time with an offset is said in UTC.
- Answered heads (success and failure), and an hq head whose only pending check is one its base does not require, are never said.
- A forge without `checks` is read as before.
- **Before the fix:** the tests that say a head, keep the clock, or give UTC each failed on the code before them; the others passed there, as they should.
- `go vet ./...` and `go test ./... -count=1` in modules/mesh-delivery pass, and `checks/words` passes.
- **gitea:** `test/announcing.test.ts` asserts `checks`. I ran it under Node's test runner after compiling it with tsc in a scratch copy. It passes with the fix and fails without it.
**What I did not verify.**
- I did not run the full `merge-check-typescript.sh` in the toolchain image. gitea's token and guard tests need the SDK, which my scratch copy lacked.
- Whether any open pull request today has a required check pending with no delivery held. If one does, it will be said within an hour of this rolling out.
- An announced delivery in `ready` or `rejected` whose required check is still pending or missing is still not said: those states have no bound. I have not seen this happen, and it is outside this issue.
The watch for unchecked heads counted any merge-check status as checked, pending
included, so a head left with a pending mesh/merge-gate, or never given
mesh/repo-check, waited unable to merge with nothing saying so. gitea_open_pulls
now gives each head status with its state and time, and the watch says a head
whose required check stays pending or unset past the delivery table's bound for
a check asked and not answered.
composed together: pass — every machine composes with the change as it did without (4 of 4 compose)
Machines
gitea on novox: passed — carried by release-1791679778890968653
mesh-delivery on novox: passed — carried by release-1791679778890968653
mesh-controller on novox: passed — judged with gitea: healthy 3 times over 3m39s
gitea on the rest running it: sent
mesh-controller on the rest running it: sent
mesh-delivery on the rest running it: sent
Transitions
2026-10-11 00:25 (new) → proposed (announced): novox/mesh-catalog#195's head announced
2026-10-11 00:27 proposed → checked (checked): the verdict names this commit
2026-10-11 00:27 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-11 00:49 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 00:49 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 00:54 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@450892498d1d` — **delivered** since 2026-10-11T00:54:02Z
**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
**Group** `fix/438-a-check-that-never-answered-is-said`, in order: novox/mesh-controller@671e350f56e3 → novox/mesh-catalog@450892498d1d
- novox/mesh-controller@671e350f56e3 before novox/mesh-catalog@450892498d1d: declared
- composed together: pass — every machine composes with the change as it did without (4 of 4 compose)
**Machines**
- gitea on novox: passed — carried by release-1791679778890968653
- mesh-delivery on novox: passed — carried by release-1791679778890968653
- mesh-controller on novox: passed — judged with gitea: healthy 3 times over 3m39s
- gitea on the rest running it: sent
- mesh-controller on the rest running it: sent
- mesh-delivery on the rest running it: sent
**Transitions**
- 2026-10-11 00:25 (new) → proposed (announced): novox/mesh-catalog#195's head announced
- 2026-10-11 00:27 proposed → checked (checked): the verdict names this commit
- 2026-10-11 00:27 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-11 00:49 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 00:49 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 00:54 delivering → delivered (done): its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says
The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: 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/issue438_test.go
Run by the build seat on novox as build-1791677834286174910 (builds --log build-1791677834286174910).
--- 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 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-11T00:14:44Z 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-11T00:14:44Z
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 2 module(s) in 1 tier(s): gitea, mesh-delivery
notes:
- modules/mesh-delivery/cmd/mesh-delivery/issue438_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 39421 strings of 654 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.016s
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 2.257s
testing modules/mesh-delivery
ok mesh-delivery/cmd/mesh-delivery 1.980s
--- 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.513485ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (63.849787ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.904933ms)
✔ the newest status of each context is kept, with what it said, when and by whom (19.129042ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.681812ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.30581ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (0.871572ms)
✔ a note line is appended once, as the forge's user, in its own repository (8.642794ms)
✔ a note that cannot be written is said, never read as written (0.44607ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.731651ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (4.105817ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (51.200986ms)
✔ gitea_api still writes what an author writes (6.737911ms)
✔ gitea_branch_protection_set only strengthens (21.761047ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (35.068469ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (16.122827ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (12.905181ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (25.129742ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (42.172991ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (20.360924ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (4.698598ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (6.479551ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (13.189742ms)
✔ a repository owner or name is never another path (0.212541ms)
✔ the judges, without a forge (0.28845ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (3.600886ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (1.546963ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (7.814603ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (31.747593ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.410521ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (11.536679ms)
✔ a lookup the forge refuses is thrown, not read as no module (4.717527ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (6.959782ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (5.487799ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.35551ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (13.437993ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (0.757312ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.605421ms)
✔ no verdict sets warning on a required check; only a person's decision fails (0.957821ms)
✔ a commit status is set on the commit, under the merge check's context (40.597307ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.661422ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (39.827746ms)
✔ second start: reuses the kept token, mints nothing (23.64774ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (22.777488ms)
✔ 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 (25.886663ms)
✔ 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.774532ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (19.450862ms)
✔ concurrent first calls share one mint (11.6859ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (14.269194ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (0.931232ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (18.15034ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (2.615844ms)
✔ nothing to mint with and no token: the client says what is missing (0.225821ms)
[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-GrpScy/state/token
✔ the tools register once there is a way to a token, and the first call mints it (9.682036ms)
[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-pSLzh1/state/token
✔ a pull request can be closed, read and cleaned up through the tools (20.399144ms)
ℹ tests 55
ℹ suites 0
ℹ pass 55
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 506.762776
**Merge check** at `9d766b67`
- `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/issue438_test.go
Run by the build seat on novox as `build-1791677834286174910` (`builds --log build-1791677834286174910`).
```
--- 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 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-11T00:14:44Z 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-11T00:14:44Z
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 2 module(s) in 1 tier(s): gitea, mesh-delivery
notes:
- modules/mesh-delivery/cmd/mesh-delivery/issue438_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 39421 strings of 654 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.016s
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 2.257s
testing modules/mesh-delivery
ok mesh-delivery/cmd/mesh-delivery 1.980s
--- 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.513485ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (63.849787ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.904933ms)
✔ the newest status of each context is kept, with what it said, when and by whom (19.129042ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.681812ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.30581ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (0.871572ms)
✔ a note line is appended once, as the forge's user, in its own repository (8.642794ms)
✔ a note that cannot be written is said, never read as written (0.44607ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.731651ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (4.105817ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (51.200986ms)
✔ gitea_api still writes what an author writes (6.737911ms)
✔ gitea_branch_protection_set only strengthens (21.761047ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (35.068469ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (16.122827ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (12.905181ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (25.129742ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (42.172991ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (20.360924ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (4.698598ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (6.479551ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (13.189742ms)
✔ a repository owner or name is never another path (0.212541ms)
✔ the judges, without a forge (0.28845ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (3.600886ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (1.546963ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (7.814603ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (31.747593ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.410521ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (11.536679ms)
✔ a lookup the forge refuses is thrown, not read as no module (4.717527ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (6.959782ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (5.487799ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.35551ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (13.437993ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (0.757312ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.605421ms)
✔ no verdict sets warning on a required check; only a person's decision fails (0.957821ms)
✔ a commit status is set on the commit, under the merge check's context (40.597307ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.661422ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (39.827746ms)
✔ second start: reuses the kept token, mints nothing (23.64774ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (22.777488ms)
✔ 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 (25.886663ms)
✔ 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.774532ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (19.450862ms)
✔ concurrent first calls share one mint (11.6859ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (14.269194ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (0.931232ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (18.15034ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (2.615844ms)
✔ nothing to mint with and no token: the client says what is missing (0.225821ms)
[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-GrpScy/state/token
✔ the tools register once there is a way to a token, and the first call mints it (9.682036ms)
[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-pSLzh1/state/token
✔ a pull request can be closed, read and cleaned up through the tools (20.399144ms)
ℹ tests 55
ℹ suites 0
ℹ pass 55
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 506.762776
```
Review of #195: the clock restarted on any change in what a head waits on, so a
check answering pushed the line back by the time it took; it now restarts only on
a newer ask. A head whose check started was announced, so its line no longer says
unannounced; pending times are given in UTC like the module's other lines.
The clock restarts only on a newer ask. The watch now remembers when each required check that is pending was set so (pending), and starts the bound again only when a required check is pending on a time it did not see before: set pending for the first time, set anew (a re-ask), or back to pending after it answered (askedAgain). A check that answers never restarts it. New test TestACheckThatAnswersDoesNotStartTheBoundAgain: merge-gate answers at 50 min, repo-check never set, said at 61 min; it failed on 9d766b6 (not said).
Label. A head whose check started is now State: unanswered (Unanswered), not unannounced. No word in the delivery table or the glossary fits a head announced but held by no delivery here; unheld already means a waiting walk no delivery holds, so I did not reuse it. unanswered comes from the table's own words for the proposed state's bound ("its check was asked and not answered"); the glossary has no entry for it. Pending times are given in UTC RFC 3339, as the module's other lines are (TestAPendingTimeIsSaidInUTC, which failed on 9d766b6 with a +02:00 time).
A follow-up this needs: mesh-controller's stalledWords (plain_words.go) has words only for unannounced. An unanswered line falls through to the generic delivery words ("Delivery of X unanswered for 1h, past its limit") and offers a Stop that mesh-delivery.stop would refuse, because no delivery exists. That needs its own mesh-controller pull request.
go vet ./..., go test ./... -count=1 (modules/mesh-delivery) and checks/words pass.
Review findings addressed in 1f48a29:
1. **The clock restarts only on a newer ask.** The watch now remembers when each required check that is pending was set so (`pending`), and starts the bound again only when a required check is pending on a time it did not see before: set pending for the first time, set anew (a re-ask), or back to pending after it answered (`askedAgain`). A check that answers never restarts it. New test `TestACheckThatAnswersDoesNotStartTheBoundAgain`: merge-gate answers at 50 min, repo-check never set, said at 61 min; it failed on 9d766b6 (not said).
2. **Label.** A head whose check started is now `State: unanswered` (`Unanswered`), not `unannounced`. No word in the delivery table or the glossary fits a head announced but held by no delivery here; `unheld` already means a waiting walk no delivery holds, so I did not reuse it. `unanswered` comes from the table's own words for the proposed state's bound ("its check was asked and not answered"); the glossary has no entry for it. Pending times are given in UTC RFC 3339, as the module's other lines are (`TestAPendingTimeIsSaidInUTC`, which failed on 9d766b6 with a +02:00 time).
A follow-up this needs: mesh-controller's `stalledWords` (plain_words.go) has words only for `unannounced`. An `unanswered` line falls through to the generic delivery words ("Delivery of X unanswered for 1h, past its limit") and offers a Stop that `mesh-delivery.stop` would refuse, because no delivery exists. That needs its own mesh-controller pull request.
`go vet ./...`, `go test ./... -count=1` (modules/mesh-delivery) and `checks/words` pass.
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/issue438_test.go
Run by the build seat on ace as build-1791678142985605292 (builds --log build-1791678142985605292).
--- 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 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-11T00:14:44Z 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-11T00:14:44Z
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 2 module(s) in 1 tier(s): gitea, mesh-delivery
notes:
- modules/mesh-delivery/cmd/mesh-delivery/issue438_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.002s
words: 12 retired words and 70 daemons' lines, none in the 39422 strings of 654 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.011s
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.683s
testing modules/mesh-delivery
ok mesh-delivery/cmd/mesh-delivery 1.675s
--- 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.364593ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (38.422975ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.213413ms)
✔ the newest status of each context is kept, with what it said, when and by whom (18.34248ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.526249ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.262924ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (0.675871ms)
✔ a note line is appended once, as the forge's user, in its own repository (5.711523ms)
✔ a note that cannot be written is said, never read as written (0.368032ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.477328ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (4.602655ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (41.943889ms)
✔ gitea_api still writes what an author writes (7.299349ms)
✔ gitea_branch_protection_set only strengthens (13.221186ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (29.658412ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (13.353467ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (10.955313ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (20.226063ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (42.518729ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (20.022428ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (4.161338ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (6.85953ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (13.650816ms)
✔ a repository owner or name is never another path (0.209915ms)
✔ the judges, without a forge (0.307679ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (2.734684ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (3.730175ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (4.740044ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (22.211525ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.35107ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (10.083025ms)
✔ a lookup the forge refuses is thrown, not read as no module (3.893993ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (5.264561ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (7.000348ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.329851ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (11.559941ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (0.759609ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.661274ms)
✔ no verdict sets warning on a required check; only a person's decision fails (3.163851ms)
✔ a commit status is set on the commit, under the merge check's context (28.271392ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.466567ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (31.888856ms)
✔ second start: reuses the kept token, mints nothing (16.081928ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (20.235138ms)
✔ 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 (22.956316ms)
✔ 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.864189ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (14.96785ms)
✔ concurrent first calls share one mint (10.193703ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (11.745532ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (0.837735ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (18.186495ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (2.252968ms)
✔ nothing to mint with and no token: the client says what is missing (0.180289ms)
[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-7hIc1N/state/token
✔ the tools register once there is a way to a token, and the first call mints it (8.239024ms)
[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-7UoO5H/state/token
✔ a pull request can be closed, read and cleaned up through the tools (19.235881ms)
ℹ tests 55
ℹ suites 0
ℹ pass 55
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 358.816685
**Merge check** at `1f48a294`
- `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/issue438_test.go
Run by the build seat on ace as `build-1791678142985605292` (`builds --log build-1791678142985605292`).
```
--- 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 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-11T00:14:44Z 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-11T00:14:44Z
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 2 module(s) in 1 tier(s): gitea, mesh-delivery
notes:
- modules/mesh-delivery/cmd/mesh-delivery/issue438_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.002s
words: 12 retired words and 70 daemons' lines, none in the 39422 strings of 654 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.011s
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.683s
testing modules/mesh-delivery
ok mesh-delivery/cmd/mesh-delivery 1.675s
--- 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.364593ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (38.422975ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.213413ms)
✔ the newest status of each context is kept, with what it said, when and by whom (18.34248ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.526249ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.262924ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (0.675871ms)
✔ a note line is appended once, as the forge's user, in its own repository (5.711523ms)
✔ a note that cannot be written is said, never read as written (0.368032ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.477328ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (4.602655ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (41.943889ms)
✔ gitea_api still writes what an author writes (7.299349ms)
✔ gitea_branch_protection_set only strengthens (13.221186ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (29.658412ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (13.353467ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (10.955313ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (20.226063ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (42.518729ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (20.022428ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (4.161338ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (6.85953ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (13.650816ms)
✔ a repository owner or name is never another path (0.209915ms)
✔ the judges, without a forge (0.307679ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (2.734684ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (3.730175ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (4.740044ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (22.211525ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.35107ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (10.083025ms)
✔ a lookup the forge refuses is thrown, not read as no module (3.893993ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (5.264561ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (7.000348ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.329851ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (11.559941ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (0.759609ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.661274ms)
✔ no verdict sets warning on a required check; only a person's decision fails (3.163851ms)
✔ a commit status is set on the commit, under the merge check's context (28.271392ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.466567ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (31.888856ms)
✔ second start: reuses the kept token, mints nothing (16.081928ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (20.235138ms)
✔ 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 (22.956316ms)
✔ 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.864189ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (14.96785ms)
✔ concurrent first calls share one mint (10.193703ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (11.745532ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (0.837735ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (18.186495ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (2.252968ms)
✔ nothing to mint with and no token: the client says what is missing (0.180289ms)
[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-7hIc1N/state/token
✔ the tools register once there is a way to a token, and the first call mints it (8.239024ms)
[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-7UoO5H/state/token
✔ a pull request can be closed, read and cleaned up through the tools (19.235881ms)
ℹ tests 55
ℹ suites 0
ℹ pass 55
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 358.816685
```
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/issue438_test.go
Run by the build seat on ace as build-1791678355601030024 (builds --log build-1791678355601030024).
--- 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 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-11T00:24:56Z 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-11T00:24:56Z
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 2 module(s) in 1 tier(s): gitea, mesh-delivery
notes:
- modules/mesh-delivery/cmd/mesh-delivery/issue438_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.002s
words: 12 retired words and 70 daemons' lines, none in the 39423 strings of 654 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.011s
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.692s
testing modules/mesh-delivery
ok mesh-delivery/cmd/mesh-delivery 1.671s
--- 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.230648ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (41.665113ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.420963ms)
✔ the newest status of each context is kept, with what it said, when and by whom (10.737335ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.426923ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.233699ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (0.601983ms)
✔ a note line is appended once, as the forge's user, in its own repository (5.492068ms)
✔ a note that cannot be written is said, never read as written (0.311265ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.46789ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (5.095542ms)
✔ 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.367053ms)
✔ gitea_api still writes what an author writes (6.076169ms)
✔ gitea_branch_protection_set only strengthens (13.903038ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (30.757619ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (13.325311ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (10.319057ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (22.832715ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (43.266975ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (21.016215ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (4.142422ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (4.893844ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (12.896937ms)
✔ a repository owner or name is never another path (0.205527ms)
✔ the judges, without a forge (0.309251ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (3.157528ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (2.293754ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (3.537524ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (20.336758ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.334619ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (11.075291ms)
✔ a lookup the forge refuses is thrown, not read as no module (3.320404ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (3.999342ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (5.75837ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.331333ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (7.180315ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (0.880425ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.697822ms)
✔ no verdict sets warning on a required check; only a person's decision fails (0.934197ms)
✔ a commit status is set on the commit, under the merge check's context (26.762985ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.410352ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (28.958716ms)
✔ second start: reuses the kept token, mints nothing (13.763805ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (15.919061ms)
✔ 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.574986ms)
✔ 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.09968ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (17.735637ms)
✔ concurrent first calls share one mint (9.858412ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (11.677273ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (1.367783ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (17.395647ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (2.278625ms)
✔ nothing to mint with and no token: the client says what is missing (0.180389ms)
[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-8hd45e/state/token
✔ the tools register once there is a way to a token, and the first call mints it (8.468648ms)
[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-1T4CZm/state/token
✔ a pull request can be closed, read and cleaned up through the tools (19.183158ms)
ℹ tests 55
ℹ suites 0
ℹ pass 55
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 355.862037
**Merge check** at `45089249`
- `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/issue438_test.go
Run by the build seat on ace as `build-1791678355601030024` (`builds --log build-1791678355601030024`).
```
--- 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 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-11T00:24:56Z 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-11T00:24:56Z
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 2 module(s) in 1 tier(s): gitea, mesh-delivery
notes:
- modules/mesh-delivery/cmd/mesh-delivery/issue438_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.002s
words: 12 retired words and 70 daemons' lines, none in the 39423 strings of 654 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.011s
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.692s
testing modules/mesh-delivery
ok mesh-delivery/cmd/mesh-delivery 1.671s
--- 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.230648ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (41.665113ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.420963ms)
✔ the newest status of each context is kept, with what it said, when and by whom (10.737335ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.426923ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.233699ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (0.601983ms)
✔ a note line is appended once, as the forge's user, in its own repository (5.492068ms)
✔ a note that cannot be written is said, never read as written (0.311265ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (0.46789ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (5.095542ms)
✔ 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.367053ms)
✔ gitea_api still writes what an author writes (6.076169ms)
✔ gitea_branch_protection_set only strengthens (13.903038ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (30.757619ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (13.325311ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (10.319057ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (22.832715ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (43.266975ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (21.016215ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (4.142422ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (4.893844ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (12.896937ms)
✔ a repository owner or name is never another path (0.205527ms)
✔ the judges, without a forge (0.309251ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (3.157528ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (2.293754ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (3.537524ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (20.336758ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.334619ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (11.075291ms)
✔ a lookup the forge refuses is thrown, not read as no module (3.320404ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (3.999342ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (5.75837ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.331333ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (7.180315ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (0.880425ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.697822ms)
✔ no verdict sets warning on a required check; only a person's decision fails (0.934197ms)
✔ a commit status is set on the commit, under the merge check's context (26.762985ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.410352ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (28.958716ms)
✔ second start: reuses the kept token, mints nothing (13.763805ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (15.919061ms)
✔ 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.574986ms)
✔ 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.09968ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (17.735637ms)
✔ concurrent first calls share one mint (9.858412ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (11.677273ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (1.367783ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (17.395647ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (2.278625ms)
✔ nothing to mint with and no token: the client says what is missing (0.180389ms)
[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-8hd45e/state/token
✔ the tools register once there is a way to a token, and the first call mints it (8.468648ms)
[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-1T4CZm/state/token
✔ a pull request can be closed, read and cleaned up through the tools (19.183158ms)
ℹ tests 55
ℹ suites 0
ℹ pass 55
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 355.862037
```
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.
novox/hq issue 438 (the watch counts pending as checked). The mesh-delivery and gitea half of the fix. The controller half is novox/mesh-controller#213, on the same branch name, and it merges first:
after: novox/mesh-controller
What was wrong. mesh-delivery's watch for unchecked heads (
unchecked.go, from issue 347, #159) counted a head as checked when it carried any merge-check context, pending included, becausegitea_open_pullsgave only the contexts' names. A head left with a pendingmesh/merge-gateand no verdict, or never givenmesh/repo-check, waited unable to merge, and the watch never said it.What changes.
gitea_open_pulls: each pull request also carrieschecks: the head's statuses by context, the newest of each, withstate,descriptionandupdated_at(newheadChecks, the same forge read ascommitStatuses).statusesis unchanged.unannounced, words unchanged.stalledline in the new stateunanswered. Such a head was announced (the controller set its status), sounannouncedwould be false.saysand in a newwaitingfield for the controller's words, for examplemesh/merge-gate pending since 2026-10-11T00:11:39Z,mesh/repo-check never set.checksis read bystatusesalone, exactly as before (tested).unansweredline would fall into the generic delivery words ("Delivery of X unanswered") and offer a Stop thatmesh-delivery.stoprefuses.The bound: 1 hour,
Bounds[Proposed].For. It is the delivery table's stated bound for "its check was asked and not answered" (mesh-delivery.table, stateproposed), so an unannounced head is said after the same time an announced one would be. It is longer than the controller lets one check run (checkTimeoutand the builder'sCheckTimeout, 45 minutes, after which the check ends as an error verdict). So a slow check, a queued one, or a status read a moment behind (issue 423) is not said.The word.
unansweredis proposed for the glossary and is not in it yet. It comes from the table's own words for the proposed state's bound;unheldalready means a waiting walk no delivery holds.What I checked.
gitea_commit_statuseson mesh-controller #212 and #211 (merge-gatepending, "the mesh is mapping this head onto its module graph"; repo-check missing) and mesh-catalog #194 (both answered). The tests decode that JSON.issue438_test.go:saysandwaiting.checksis read as before.go vet ./...andgo test ./... -count=1in modules/mesh-delivery pass, andchecks/wordspasses.test/announcing.test.tsassertschecks. I ran it under Node's test runner after compiling it with tsc in a scratch copy. It passes with the fix and fails without it.What I did not verify.
merge-check-typescript.shin the toolchain image. gitea's token and guard tests need the SDK, which my scratch copy lacked.readyorrejectedwhose required check is still pending or missing is still not said: those states have no bound. I have not seen this happen, and it is outside this issue.Delivery
novox/mesh-catalog@450892498d1d— delivered since 2026-10-11T00:54:02ZDelivery plan — builds gitea, mesh-delivery → novox; no bus step
Group
fix/438-a-check-that-never-answered-is-said, in order: novox/mesh-controller@671e350f56 → novox/mesh-catalog@450892498dMachines
Transitions
The commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
9d766b67mesh/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-1791677834286174910(builds --log build-1791677834286174910).Review findings addressed in
1f48a29:pending), and starts the bound again only when a required check is pending on a time it did not see before: set pending for the first time, set anew (a re-ask), or back to pending after it answered (askedAgain). A check that answers never restarts it. New testTestACheckThatAnswersDoesNotStartTheBoundAgain: merge-gate answers at 50 min, repo-check never set, said at 61 min; it failed on9d766b6(not said).State: unanswered(Unanswered), notunannounced. No word in the delivery table or the glossary fits a head announced but held by no delivery here;unheldalready means a waiting walk no delivery holds, so I did not reuse it.unansweredcomes from the table's own words for the proposed state's bound ("its check was asked and not answered"); the glossary has no entry for it. Pending times are given in UTC RFC 3339, as the module's other lines are (TestAPendingTimeIsSaidInUTC, which failed on9d766b6with a +02:00 time).A follow-up this needs: mesh-controller's
stalledWords(plain_words.go) has words only forunannounced. Anunansweredline falls through to the generic delivery words ("Delivery of X unanswered for 1h, past its limit") and offers a Stop thatmesh-delivery.stopwould refuse, because no delivery exists. That needs its own mesh-controller pull request.go vet ./...,go test ./... -count=1(modules/mesh-delivery) andchecks/wordspass.Merge check at
1f48a294mesh/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 ace as
build-1791678142985605292(builds --log build-1791678142985605292).Merge check at
45089249mesh/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 ace as
build-1791678355601030024(builds --log build-1791678355601030024).