Issue 867. Builds ADR 0382 clauses 5 and 6 and the groups fields the controller reads (accepted, operator approved 2026-10-16).
mesh-delivery groups: each group line adds member_details {id, repository, state, number, head, base, open}; members unchanged.
modules/gitea: keeps per open pull request the siblings its latest verdict read (state file, bounded: merged or closed members dropped); a sibling's new head, unmerged close, or base move other than its own merge of the head read puts the verdict out of date (both statuses back to pending, nothing reruns); the merge tool refuses it and asks the check.
Merge tool: a verdict that read siblings merges only when mesh/delivery-group passed at every head read and each sibling head is approved (forge review at that head, or a ready mark naming it).
Statuses: beside mesh-host#114@<8> (+N more), or siblings unknown; the reason stays within 140 characters. A verdict with no siblings is said and merged exactly as before.
Tests: gitea 140 pass (type-checked), mesh-delivery go test ok, merge-check.sh with MESH_CHECK_MODULES="gitea mesh-delivery" exit 0 (gate ran). Review: independent Opus review, 2 rounds, APPROVE at this content (local 306174ba; tree d4211eec equal). Group: same branch name in mesh-controller and hq.
Issue 867. Builds ADR 0382 clauses 5 and 6 and the groups fields the controller reads (accepted, operator approved 2026-10-16).
- mesh-delivery `groups`: each group line adds `member_details` {id, repository, state, number, head, base, open}; `members` unchanged.
- modules/gitea: keeps per open pull request the siblings its latest verdict read (state file, bounded: merged or closed members dropped); a sibling's new head, unmerged close, or base move other than its own merge of the head read puts the verdict out of date (both statuses back to pending, nothing reruns); the merge tool refuses it and asks the check.
- Merge tool: a verdict that read siblings merges only when `mesh/delivery-group` passed at every head read and each sibling head is approved (forge review at that head, or a ready mark naming it).
- Statuses: `beside mesh-host#114@<8>` (+N more), or `siblings unknown`; the reason stays within 140 characters. A verdict with no siblings is said and merged exactly as before.
Tests: gitea 140 pass (type-checked), mesh-delivery go test ok, merge-check.sh with MESH_CHECK_MODULES="gitea mesh-delivery" exit 0 (gate ran). Review: independent Opus review, 2 rounds, APPROVE at this content (local 306174ba; tree d4211eec equal). Group: same branch name in mesh-controller and hq.
mesh/merge-gate head de9d29dc657d merged into trunk 62ec68355cd1 — pass: builds gitea, mesh-delivery → novox; no bus step; every machine composes with the …
mesh/repo-check head de9d29dc657d merged into trunk 62ec68355cd1 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh-delivery's groups names each member's pull request, head, base and whether it is open (member_details), so the controller can read a member's siblings beside its check. The forge's holder keeps the siblings each verdict read, puts that verdict out of date when a sibling gets a new head, closes, or its base moves by anything but its own merge, and the merge tool merges such a member only when mesh/delivery-group passed at the heads read and each sibling head is approved. The statuses name the siblings, or say they could not be learned.
Co-Authored-By: Claude <noreply@anthropic.com>
composed together: pass — every machine composes with the change as it did without (4 of 4 compose)
Machines
mesh-controller on every machine running it: sent
build-agent on every machine running it: sent
gitea on every machine running it: sent
mesh-delivery on every machine running it: sent
Transitions
2026-10-15 23:27 (new) → proposed (announced): novox/mesh-catalog#325's head announced
2026-10-15 23:31 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
2026-10-15 23:31 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-15 23:54 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
2026-10-15 23:54 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-15 23:59 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@de9d29dc657d` — **delivered** since 2026-10-15T23:59:23Z
**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/867-group-siblings`, in order: novox/hq@e4a55051cc3b → novox/mesh-controller@37fd73f3781e → novox/mesh-catalog@de9d29dc657d
- novox/mesh-controller@37fd73f3781e before novox/mesh-catalog@de9d29dc657d: built by
- composed together: pass — every machine composes with the change as it did without (4 of 4 compose)
**Machines**
- mesh-controller on every machine running it: sent
- build-agent on every machine running it: sent
- gitea on every machine running it: sent
- mesh-delivery on every machine running it: sent
**Transitions**
- 2026-10-15 23:27 (new) → proposed (announced): novox/mesh-catalog#325's head announced
- 2026-10-15 23:31 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
- 2026-10-15 23:31 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-15 23:54 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
- 2026-10-15 23:54 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-15 23:59 delivering → delivered (done): its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says
The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
Merge check at de9d29dc
Judged: head de9d29dc657d merged into trunk 62ec68355cd1; read beside it: mesh-controller 7543f64911c5, mesh-controller-main 7543f64911c5, mesh-host 198fd34ec497, mesh-tools 6b3e15042fee
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/holder_test.go
Run by the build seat on shanks as build-1792106877615889451 (builds --log build-1792106877615889451).
--- 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 (16.085864ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (56.12496ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.71994ms)
✔ a merged pull request whose branch was deleted is announced with its branch, not its pull ref (11.356337ms)
✔ a pull request's ready mark is read: the label and the head the newest listed ready comment names (21.609117ms)
✔ a ready comment by an account not listed is ignored, and said (0.668154ms)
✔ a ready comment older than the latest push, or than the label, never counts (42.100537ms)
[gitea] f cannot be read (gone); only mesh-tools, mesh-admin may mark a pull request ready
[gitea] f names no account that may mark a pull request ready; only mesh-tools, mesh-admin may
✔ a long thread hides nothing: every page of the timeline is read (14.911157ms)
✔ the accounts that may mark are the setting's, and only the mesh's own when it cannot be read (0.825805ms)
✔ the newest status of each context is kept, with what it said, when and by whom (19.483564ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.703844ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.309892ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (0.945136ms)
✔ a note line is appended once, as the forge's user, in its own repository (10.21299ms)
✔ a note that cannot be written is said, never read as written (0.513643ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (1.235047ms)
✔ a build machine taking the check is said on the head: building on which machine, since the local time (26.500886ms)
✔ the verdict heard while a 'building' write is in flight lands last and stays (5.18426ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ the verdict written while a start's read of the forge is in flight stays the newest status (1.862221ms)
✔ the verdict heard while a 'building' write still waits its turn: the 'building' is never written (0.650704ms)
✔ a start heard after the verdict writes nothing (0.546113ms)
✔ a start from before the check now pending (a run before a recheck, the stream's history) writes nothing (0.663133ms)
✔ an old run's verdict redelivered after a recheck's pending is not written over it (1.128456ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check the forge holds pending; not written
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ an old run's verdict in flight when the recheck comes lands before the recheck's pending (2.558306ms)
✔ a rechecked head says where its new run is: building on which machine (0.925786ms)
✔ the recheck's own verdict is written, even asked a little before the pending by another machine's clock (0.761495ms)
✔ after a restart, an old run's verdict is weighed against the check the forge's history holds open (0.583844ms)
✔ a recheck the forge does not take fails its caller, and the head is as it was (0.571843ms)
✔ the status tool writes the merge check's pending through the writer, and the mesh's other statuses as they were (3.782182ms)
✔ when a check was asked is read from its id; the opening of the check now pending from the gate's history (0.217521ms)
✔ a head this process did not announce (it restarted) is said only while the forge holds its gate pending (0.297072ms)
✔ a repository as the mesh clones it is told from another (1.399258ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (7.426694ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (49.095809ms)
✔ a protection rule covers a branch as the forge decides it: classes, alternatives, a plain name in any case (issue 539) (18.537709ms)
✔ gitea_api still writes what an author writes (5.924845ms)
✔ gitea_branch_protection_set only strengthens (14.676576ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (46.532664ms)
✔ gitea_merge_pull_request refuses a verdict judged against an earlier trunk, and asks the check once (136.596552ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (16.337076ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (13.61457ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (25.128497ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (52.81397ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (18.83017ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (4.226025ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (6.694879ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (13.52266ms)
✔ a repository owner or name is never another path (0.216441ms)
✔ the judges, without a forge (0.328672ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (2.482034ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (1.180567ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (4.704928ms)
✔ gitea_merge_pull_request merges a verdict that read siblings only with its group passed and each sibling head reviewed (85.09766ms)
✔ a verdict put out of date by a sibling's move is refused in its own words, and asked again once (22.441372ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (32.39731ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.456013ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (11.933501ms)
✔ a lookup the forge refuses is thrown, not read as no module (3.910843ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (5.07221ms)
✔ a new head beyond the newest 20 open pull requests is announced, exactly once (65.022651ms)
✔ a pass pages until it reaches back past its floor, and no further (24.746376ms)
✔ gitea_branch_protection_get answers every rule, read with the administrator account the tools' token cannot read with (28.176345ms)
✔ gitea_branch_protection_get answers the rule for a branch, null when it has none (8.37579ms)
✔ gitea_branch_protection_set still gets the forge's refusal, as the tools' account (4.337015ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (8.600661ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.301002ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (16.447497ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (1.197867ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.815704ms)
✔ no verdict sets warning on a required check; only a person's decision fails (1.285748ms)
✔ a commit status is set on the commit, under the merge check's context (37.204888ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.456363ms)
✔ a head announced is pending in the build queue, since the local time it was announced (0.277981ms)
✔ the vault's token is used, a setting of it is ignored and said once, without its value (3.282618ms)
[gitea] novox/mesh-controller#346: its verdict was judged beside mesh-host#114 at bbbbbbbbbbbb, it is now at dddddddddddd: out of date, nothing run (novox/hq ADR 0382)
[gitea] novox/mesh-controller#346: its verdict was judged beside mesh-host#114 was closed unmerged: out of date, nothing run (novox/hq ADR 0382)
[gitea] novox/mesh-controller#346: its verdict was judged beside mesh-host#114 merged onto cccccccccccc, its main is now eeeeeeeeeeee: out of date, nothing run (novox/hq ADR 0382)
[gitea] novox/mesh-controller#346: its verdict was judged beside mesh-host#114 was merged otherwise than its head bbbbbbbbbbbb onto cccccccccccc: out of date, nothing run (novox/hq ADR 0382)
[gitea] novox/mesh-controller#346: its verdict was judged beside mesh-host#114 was merged otherwise than its head bbbbbbbbbbbb onto cccccccccccc: out of date, nothing run (novox/hq ADR 0382)
[gitea] novox/mesh-host's main moved from 444444444444 to 333333333333: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
✔ both statuses name the siblings the verdict read, keeping the verdict word and the start of its reason within 140 characters (2.454524ms)
✔ a verdict that could not learn its siblings says so on the status and in the comment, and names none (0.464943ms)
✔ a verdict without siblings is said exactly as before (an older controller) (0.926965ms)
✔ the siblings a verdict names are kept per member, replaced by its next verdict, and removed by one that read none (1.006706ms)
✔ a sibling's new head puts both statuses of the member back to pending, with the reason, and runs nothing (1.888511ms)
✔ a sibling closed unmerged puts the member's verdict out of date (0.570654ms)
✔ a move of the sibling's base puts it out of date, naming both commits (0.417483ms)
✔ the sibling's own merge of the head read onto the base read does not, in either order of the two merges (0.595734ms)
✔ a merge of another head, or onto another base, is a move (0.877465ms)
✔ only the members whose verdict names the sibling are touched, and a member that moved on is dropped (0.605263ms)
✔ a trunk that moved tells the siblings' weighing; one first seen does not (0.627583ms)
✔ the group rule names every missing thing: the group status at each head read, and each sibling head's review (0.961736ms)
✔ a merged or closed member's entry is dropped, so a sibling's later push never re-pends its merged head (0.225851ms)
✔ a consumer that asks nothing, or write, publishes: the package team (1.68655ms)
✔ a consumer that asks to read joins the readers, who write no package (0.550583ms)
✔ a reader is made, put on the readers team, and taken off the package team (0.648244ms)
✔ a publisher is made and put on the package team, as before (0.243421ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (31.898427ms)
✔ second start: reuses the kept token, mints nothing (28.630108ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (29.224512ms)
✔ 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 (26.352665ms)
✔ 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 (29.149022ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (24.093001ms)
✔ concurrent first calls share one mint (13.81632ms)
✔ two tokens hold the name (two mints raced): both are deleted by their id, and one fresh token is minted (15.106248ms)
✔ two processes on one kept file, both without a token, mint once: the second waits for the first and reuses it (68.735123ms)
✔ a lock left by a process that died mid-mint is taken over, not waited on for ever (5.253691ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (15.22902ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (0.773395ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (20.813342ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (2.199943ms)
✔ nothing to mint with and no token: the client says what is missing (0.167421ms)
[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-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-STSuaA/state/token
✔ the tools register once there is a way to a token, and the first call mints it (26.348904ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-KEREDA/state/token
✔ a pull request can be closed, read and cleaned up through the tools (35.212197ms)
[gitea] no token kept — minting one
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-K32kWN/state/token
✔ the tools' token is minted for an account of their own, which is no administrator and writes through its team (61.26697ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-kxo2pF/state/token
✔ with the tools' token, the forge writes a status and refuses a protection edit — the forge itself, not a guard (40.742049ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-f962sL/state/token
✔ what a base's protection asks is read from the branch, which the tools' account may read, not from the rules' list it may not (33.390296ms)
[gitea] the kept token was minted with other scopes than the tools take now — minting one with them
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-ke1HUU/state/token
[gitea] deleted 1 token(s) named "mesh-tools" the admin account "mesh-admin" held: the tools act as "mesh-tools" now
✔ the administrator's token from before goes: a kept admin token is minted again for the tools' account, and the forge drops the old one (30.195897ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-VtgbrW/state/token
✔ a tools' account someone made an administrator is made a plain account again at the next mint (25.163397ms)
✔ the tools never act as the administrator: MESH_GITEA_TOOLS_USER naming it is refused (0.763774ms)
✔ the manifest hands the tools their account, and the merge-ready authors name it rather than the administrator (0.842975ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-9rRWys/state/token
✔ a repository check said as a warning on a head without merge-check.sh reads the base's protection from the branch (25.47873ms)
[gitea] no token kept — minting one
✔ a mint that fails leaves the administrator's old token in place: the tools are never left without one (14.959038ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-PwUUUv/state/token
[gitea] FAILED to delete the token(s) named "mesh-tools" the admin account "mesh-admin" holds — tried again at the next start: Gitea DELETE /users/mesh-admin/tokens/902 (named mesh-tools): 500 {"message":"the forge is not well"}
✔ an old token that cannot be deleted after the mint is said, the new token kept, and deleted at the next start (34.07227ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-wLduOF/state/token
✔ a team another process made between the look and the create is found, not a failure (24.960356ms)
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 25 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host's main moved from d28ca95ccccc to ffffffffffff: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host: whether a trunk moved could not be weighed, so it is weighed again on the next pass — the forge is away
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-controller's feat/57-config-and-provisioning moved from eeeeeeeeeeee to d28ca95ccccc: 1 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
✔ both statuses say the head and the trunk commit the verdict judged, first, whatever the summary's length (1.879841ms)
✔ a trunk's move puts an open head's merge check out of date, naming both commits, unless a check is on its way (1.280998ms)
✔ a push to a trunk puts every open pull request into it out of date — twenty-five of them — and none into another (5.496582ms)
✔ a trunk that cannot be read is weighed again on the next pass, never recorded half (0.491973ms)
✔ the trunk's move takes its turn after a verdict in flight, and weighs what the forge holds then (0.577154ms)
✔ a verdict judged against a trunk newer than the pass's first read is not put out of date (0.326412ms)
✔ a verdict that landed out of date keeps its account on the pull request (0.319112ms)
✔ a release branch's move puts every open pull request into it out of date, as a trunk's does (ADR 0358 §5) (0.540733ms)
✔ a signed event is published on the bus and acted on once (61.37253ms)
✔ an unsigned event, or one whose signature does not verify, is refused and published nowhere (33.735738ms)
✔ an event and the poll seeing the same head announce it once (0.428453ms)
✔ each forge event is named in the module's stream and trimmed (0.572233ms)
✔ the webhook is made a system webhook, which the forge sends every repository's events to (1.414138ms)
✔ default webhooks left at the receiver by an earlier start are replaced by one system webhook (1.442708ms)
✔ a repository's copy of a default webhook at the receiver is deleted, on every page, and said (4.626397ms)
✔ a forge that makes no system webhook is said, never taken as heard (0.685544ms)
✔ the receiver tells what it verified and what it refused (10.587711ms)
✔ a forge that never delivers is said loudly once, and unhealthy, when the poll finds what no event said (1.61927ms)
✔ a forge whose events stop after one delivery is said loudly once per silent spell, and refusals are unhealthy (0.663864ms)
✔ without a receiver by configuration the polls alone watch, healthy; a receiver that failed is unhealthy (0.435102ms)
✔ the polls look every 30 s until events are heard, every five minutes while they are, and 30 s again on a miss (0.276951ms)
ℹ tests 140
ℹ suites 0
ℹ pass 140
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 752.198168
**Merge check** at `de9d29dc`
Judged: head `de9d29dc657d` merged into trunk `62ec68355cd1`; read beside it: mesh-controller `7543f64911c5`, mesh-controller-main `7543f64911c5`, mesh-host `198fd34ec497`, mesh-tools `6b3e15042fee`
- `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/holder_test.go
Run by the build seat on shanks as `build-1792106877615889451` (`builds --log build-1792106877615889451`).
```
--- 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 (16.085864ms)
✔ every open pull request is read with what its base requires and what its head has (gitea_open_pulls) (56.12496ms)
✔ a pull request or issue number that is not one is refused before the forge is asked (1.71994ms)
✔ a merged pull request whose branch was deleted is announced with its branch, not its pull ref (11.356337ms)
✔ a pull request's ready mark is read: the label and the head the newest listed ready comment names (21.609117ms)
✔ a ready comment by an account not listed is ignored, and said (0.668154ms)
✔ a ready comment older than the latest push, or than the label, never counts (42.100537ms)
[gitea] f cannot be read (gone); only mesh-tools, mesh-admin may mark a pull request ready
[gitea] f names no account that may mark a pull request ready; only mesh-tools, mesh-admin may
✔ a long thread hides nothing: every page of the timeline is read (14.911157ms)
✔ the accounts that may mark are the setting's, and only the mesh's own when it cannot be read (0.825805ms)
✔ the newest status of each context is kept, with what it said, when and by whom (19.483564ms)
✔ a commit merges only when every required status is a success; a warning blocks (0.703844ms)
✔ no rule requires nothing; a rule that cannot be read says nothing, never yes (0.309892ms)
✔ a commit is contained in another when the compare finds nothing on it the other lacks (0.945136ms)
✔ a note line is appended once, as the forge's user, in its own repository (10.21299ms)
✔ a note that cannot be written is said, never read as written (0.513643ms)
✔ one view per pull request, found by its marker; only the mesh's statuses (1.235047ms)
✔ a build machine taking the check is said on the head: building on which machine, since the local time (26.500886ms)
✔ the verdict heard while a 'building' write is in flight lands last and stays (5.18426ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ the verdict written while a start's read of the forge is in flight stays the newest status (1.862221ms)
✔ the verdict heard while a 'building' write still waits its turn: the 'building' is never written (0.650704ms)
✔ a start heard after the verdict writes nothing (0.546113ms)
✔ a start from before the check now pending (a run before a recheck, the stream's history) writes nothing (0.663133ms)
✔ an old run's verdict redelivered after a recheck's pending is not written over it (1.128456ms)
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check the forge holds pending; not written
[gitea] novox/hq@a6b08de8: the verdict of build-1791680941000000000 is an older run's than the check now pending; not written
✔ an old run's verdict in flight when the recheck comes lands before the recheck's pending (2.558306ms)
✔ a rechecked head says where its new run is: building on which machine (0.925786ms)
✔ the recheck's own verdict is written, even asked a little before the pending by another machine's clock (0.761495ms)
✔ after a restart, an old run's verdict is weighed against the check the forge's history holds open (0.583844ms)
✔ a recheck the forge does not take fails its caller, and the head is as it was (0.571843ms)
✔ the status tool writes the merge check's pending through the writer, and the mesh's other statuses as they were (3.782182ms)
✔ when a check was asked is read from its id; the opening of the check now pending from the gate's history (0.217521ms)
✔ a head this process did not announce (it restarted) is said only while the forge holds its gate pending (0.297072ms)
✔ a repository as the mesh clones it is told from another (1.399258ms)
✔ gitea_api refuses the forge's administration, a branch's protection and the merge check's status, and sends nothing (7.426694ms)
✔ gitea_api refuses a file written to the default branch or one a rule guards, and writes one on a branch of its own (49.095809ms)
✔ a protection rule covers a branch as the forge decides it: classes, alternatives, a plain name in any case (issue 539) (18.537709ms)
✔ gitea_api still writes what an author writes (5.924845ms)
✔ gitea_branch_protection_set only strengthens (14.676576ms)
✔ gitea_merge_pull_request merges only a head whose required statuses all succeeded, and only that head (46.532664ms)
✔ gitea_merge_pull_request refuses a verdict judged against an earlier trunk, and asks the check once (136.596552ms)
✔ gitea_merge_pull_request merges only the head it is named, and refuses one that is not the head (16.337076ms)
✔ gitea_merge_pull_request is loud when the forge merged a head other than the one it was handed (13.61457ms)
✔ gitea_merge_pull_request says the merge happened when reading back what landed fails (25.128497ms)
✔ gitea_merge_pull_request reads back a fast-forward, and says it cannot read back a squash or a rebase (52.81397ms)
✔ gitea_delete_repo deletes only the tools' own user's repository, unguarded, untouched for 30 days, named in full (18.83017ms)
✔ gitea_api sends the path it judged, and refuses moving a pull request's base (4.226025ms)
✔ gitea_merge_pull_request merges only into a branch whose protection requires the checks (6.694879ms)
✔ a rule named for a branch is not made weaker than the glob rule that guards it now (13.52266ms)
✔ a repository owner or name is never another path (0.216441ms)
✔ the judges, without a forge (0.328672ms)
✔ gitea_api never acts as another user: sudo, in any case, and any query key it does not send are refused (2.482034ms)
✔ gitea_api never approves a pull request nor requests changes: those are the operator's (1.180567ms)
✔ no tool's caller text becomes another path or another user: `..` and `?sudo=` are refused before the forge (4.704928ms)
✔ gitea_merge_pull_request merges a verdict that read siblings only with its group passed and each sibling head reviewed (85.09766ms)
✔ a verdict put out of date by a sibling's move is refused in its own words, and asked again once (22.441372ms)
✔ every page of a pull request's files is read, though the forge caps a page at fifty (32.39731ms)
✔ a pass asks only the repositories that moved since the last look, every one before the first (0.456013ms)
✔ the directories above a merge's files that hold a module at the commit are said, the root never (11.933501ms)
✔ a lookup the forge refuses is thrown, not read as no module (3.910843ms)
✔ a renamed file is said under its old name too, as gone (novox/hq ADR 0267) (5.07221ms)
✔ a new head beyond the newest 20 open pull requests is announced, exactly once (65.022651ms)
✔ a pass pages until it reaches back past its floor, and no further (24.746376ms)
✔ gitea_branch_protection_get answers every rule, read with the administrator account the tools' token cannot read with (28.176345ms)
✔ gitea_branch_protection_get answers the rule for a branch, null when it has none (8.37579ms)
✔ gitea_branch_protection_set still gets the forge's refusal, as the tools' account (4.337015ms)
✔ the statuses asked for replace the rule's, and an empty list requires none (8.600661ms)
✔ a new rule refuses direct pushes unless asked; an administrator's override only when said (0.301002ms)
✔ each open pull request's new head is announced once, and nothing closed or merged (16.447497ms)
✔ a verdict is the head commit's status; an error is the forge's error, never a success (1.197867ms)
✔ each layer is its own status: the gate with the modules it judged, the repository's own check beside it (0.815704ms)
✔ no verdict sets warning on a required check; only a person's decision fails (1.285748ms)
✔ a commit status is set on the commit, under the merge check's context (37.204888ms)
✔ a change plan is the gate's result: said on the status and, when it builds something, as a comment (0.456363ms)
✔ a head announced is pending in the build queue, since the local time it was announced (0.277981ms)
✔ the vault's token is used, a setting of it is ignored and said once, without its value (3.282618ms)
[gitea] novox/mesh-controller#346: its verdict was judged beside mesh-host#114 at bbbbbbbbbbbb, it is now at dddddddddddd: out of date, nothing run (novox/hq ADR 0382)
[gitea] novox/mesh-controller#346: its verdict was judged beside mesh-host#114 was closed unmerged: out of date, nothing run (novox/hq ADR 0382)
[gitea] novox/mesh-controller#346: its verdict was judged beside mesh-host#114 merged onto cccccccccccc, its main is now eeeeeeeeeeee: out of date, nothing run (novox/hq ADR 0382)
[gitea] novox/mesh-controller#346: its verdict was judged beside mesh-host#114 was merged otherwise than its head bbbbbbbbbbbb onto cccccccccccc: out of date, nothing run (novox/hq ADR 0382)
[gitea] novox/mesh-controller#346: its verdict was judged beside mesh-host#114 was merged otherwise than its head bbbbbbbbbbbb onto cccccccccccc: out of date, nothing run (novox/hq ADR 0382)
[gitea] novox/mesh-host's main moved from 444444444444 to 333333333333: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
✔ both statuses name the siblings the verdict read, keeping the verdict word and the start of its reason within 140 characters (2.454524ms)
✔ a verdict that could not learn its siblings says so on the status and in the comment, and names none (0.464943ms)
✔ a verdict without siblings is said exactly as before (an older controller) (0.926965ms)
✔ the siblings a verdict names are kept per member, replaced by its next verdict, and removed by one that read none (1.006706ms)
✔ a sibling's new head puts both statuses of the member back to pending, with the reason, and runs nothing (1.888511ms)
✔ a sibling closed unmerged puts the member's verdict out of date (0.570654ms)
✔ a move of the sibling's base puts it out of date, naming both commits (0.417483ms)
✔ the sibling's own merge of the head read onto the base read does not, in either order of the two merges (0.595734ms)
✔ a merge of another head, or onto another base, is a move (0.877465ms)
✔ only the members whose verdict names the sibling are touched, and a member that moved on is dropped (0.605263ms)
✔ a trunk that moved tells the siblings' weighing; one first seen does not (0.627583ms)
✔ the group rule names every missing thing: the group status at each head read, and each sibling head's review (0.961736ms)
✔ a merged or closed member's entry is dropped, so a sibling's later push never re-pends its merged head (0.225851ms)
✔ a consumer that asks nothing, or write, publishes: the package team (1.68655ms)
✔ a consumer that asks to read joins the readers, who write no package (0.550583ms)
✔ a reader is made, put on the readers team, and taken off the package team (0.648244ms)
✔ a publisher is made and put on the package team, as before (0.243421ms)
✔ first start: mints with the admin account, keeps the token at 0600, asks for the tools' scopes only (31.898427ms)
✔ second start: reuses the kept token, mints nothing (28.630108ms)
✔ the forge rejects the kept token (its data was restored): minted afresh, once, and the call goes through (29.224512ms)
✔ 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 (26.352665ms)
✔ 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 (29.149022ms)
✔ the kept file is gone but the forge still holds a token by that name: replaced, not refused (24.093001ms)
✔ concurrent first calls share one mint (13.81632ms)
✔ two tokens hold the name (two mints raced): both are deleted by their id, and one fresh token is minted (15.106248ms)
✔ two processes on one kept file, both without a token, mint once: the second waits for the first and reuses it (68.735123ms)
✔ a lock left by a process that died mid-mint is taken over, not waited on for ever (5.253691ms)
✔ the admin account is refused: said plainly, nothing kept, and the next call fails the same way rather than crashing (15.22902ms)
✔ one process shares one source per kept file — the watcher and the tools never renew against each other (0.773395ms)
✔ a second process finds the token the first renewed, and reuses it instead of minting over it (20.813342ms)
✔ a configured token wins, and is reported rather than minted over when the forge rejects it (2.199943ms)
✔ nothing to mint with and no token: the client says what is missing (0.167421ms)
[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-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-STSuaA/state/token
✔ the tools register once there is a way to a token, and the first call mints it (26.348904ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-KEREDA/state/token
✔ a pull request can be closed, read and cleaned up through the tools (35.212197ms)
[gitea] no token kept — minting one
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-K32kWN/state/token
✔ the tools' token is minted for an account of their own, which is no administrator and writes through its team (61.26697ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-kxo2pF/state/token
✔ with the tools' token, the forge writes a status and refuses a protection edit — the forge itself, not a guard (40.742049ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-f962sL/state/token
✔ what a base's protection asks is read from the branch, which the tools' account may read, not from the rules' list it may not (33.390296ms)
[gitea] the kept token was minted with other scopes than the tools take now — minting one with them
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-ke1HUU/state/token
[gitea] deleted 1 token(s) named "mesh-tools" the admin account "mesh-admin" held: the tools act as "mesh-tools" now
✔ the administrator's token from before goes: a kept admin token is minted again for the tools' account, and the forge drops the old one (30.195897ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-VtgbrW/state/token
✔ a tools' account someone made an administrator is made a plain account again at the next mint (25.163397ms)
✔ the tools never act as the administrator: MESH_GITEA_TOOLS_USER naming it is refused (0.763774ms)
✔ the manifest hands the tools their account, and the merge-ready authors name it rather than the administrator (0.842975ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-9rRWys/state/token
✔ a repository check said as a warning on a head without merge-check.sh reads the base's protection from the branch (25.47873ms)
[gitea] no token kept — minting one
✔ a mint that fails leaves the administrator's old token in place: the tools are never left without one (14.959038ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-PwUUUv/state/token
[gitea] FAILED to delete the token(s) named "mesh-tools" the admin account "mesh-admin" holds — tried again at the next start: Gitea DELETE /users/mesh-admin/tokens/902 (named mesh-tools): 500 {"message":"the forge is not well"}
✔ an old token that cannot be deleted after the mint is said, the new token kept, and deleted at the next start (34.07227ms)
[gitea] no token kept — minting one
[gitea] the forge already holds a token named "mesh-tools" — replacing it
[gitea] minted a token for "mesh-tools" (write:repository, write:issue, read:user), kept at /tmp/gitea-tools-account-wLduOF/state/token
✔ a team another process made between the look and the create is found, not a failure (24.960356ms)
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 25 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host's main moved from d28ca95ccccc to ffffffffffff: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-host: whether a trunk moved could not be weighed, so it is weighed again on the next pass — the forge is away
[gitea] novox/mesh-host's main moved from a2c2c35bbbbb to d28ca95ccccc: 0 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
[gitea] novox/mesh-controller's feat/57-config-and-provisioning moved from eeeeeeeeeeee to d28ca95ccccc: 1 open pull request(s) into it put out of date, nothing run (novox/hq ADR 0323)
✔ both statuses say the head and the trunk commit the verdict judged, first, whatever the summary's length (1.879841ms)
✔ a trunk's move puts an open head's merge check out of date, naming both commits, unless a check is on its way (1.280998ms)
✔ a push to a trunk puts every open pull request into it out of date — twenty-five of them — and none into another (5.496582ms)
✔ a trunk that cannot be read is weighed again on the next pass, never recorded half (0.491973ms)
✔ the trunk's move takes its turn after a verdict in flight, and weighs what the forge holds then (0.577154ms)
✔ a verdict judged against a trunk newer than the pass's first read is not put out of date (0.326412ms)
✔ a verdict that landed out of date keeps its account on the pull request (0.319112ms)
✔ a release branch's move puts every open pull request into it out of date, as a trunk's does (ADR 0358 §5) (0.540733ms)
✔ a signed event is published on the bus and acted on once (61.37253ms)
✔ an unsigned event, or one whose signature does not verify, is refused and published nowhere (33.735738ms)
✔ an event and the poll seeing the same head announce it once (0.428453ms)
✔ each forge event is named in the module's stream and trimmed (0.572233ms)
✔ the webhook is made a system webhook, which the forge sends every repository's events to (1.414138ms)
✔ default webhooks left at the receiver by an earlier start are replaced by one system webhook (1.442708ms)
✔ a repository's copy of a default webhook at the receiver is deleted, on every page, and said (4.626397ms)
✔ a forge that makes no system webhook is said, never taken as heard (0.685544ms)
✔ the receiver tells what it verified and what it refused (10.587711ms)
✔ a forge that never delivers is said loudly once, and unhealthy, when the poll finds what no event said (1.61927ms)
✔ a forge whose events stop after one delivery is said loudly once per silent spell, and refusals are unhealthy (0.663864ms)
✔ without a receiver by configuration the polls alone watch, healthy; a receiver that failed is unhealthy (0.435102ms)
✔ the polls look every 30 s until events are heard, every five minutes while they are, and 30 s again on a miss (0.276951ms)
ℹ tests 140
ℹ suites 0
ℹ pass 140
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 752.198168
```
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.
Issue 867. Builds ADR 0382 clauses 5 and 6 and the groups fields the controller reads (accepted, operator approved 2026-10-16).
groups: each group line addsmember_details{id, repository, state, number, head, base, open};membersunchanged.mesh/delivery-grouppassed at every head read and each sibling head is approved (forge review at that head, or a ready mark naming it).beside mesh-host#114@<8>(+N more), orsiblings unknown; the reason stays within 140 characters. A verdict with no siblings is said and merged exactly as before.Tests: gitea 140 pass (type-checked), mesh-delivery go test ok, merge-check.sh with MESH_CHECK_MODULES="gitea mesh-delivery" exit 0 (gate ran). Review: independent Opus review, 2 rounds, APPROVE at this content (local 306174ba; tree
d4211eecequal). Group: same branch name in mesh-controller and hq.Delivery
novox/mesh-catalog@de9d29dc657d— delivered since 2026-10-15T23:59:23ZDelivery plan — builds gitea, mesh-delivery → novox; no bus step
Group
fix/867-group-siblings, in order: novox/hq@e4a55051cc → novox/mesh-controller@37fd73f378 → novox/mesh-catalog@de9d29dc65Machines
Transitions
The commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
de9d29dcJudged: head
de9d29dc657dmerged into trunk62ec68355cd1; read beside it: mesh-controller7543f64911c5, mesh-controller-main7543f64911c5, mesh-host198fd34ec497, mesh-tools6b3e15042feemesh/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 shanks as
build-1792106877615889451(builds --log build-1792106877615889451).ready at
de9d29dc65