Repository navigation
feat(build-host): hand a set to every channel of its own major line - #292
Conversation
Per-major build-server channels (policy major-lines-run-separately, platform Doc/Architecture/MajorLinesRunSeparately; the incident of core #8129). Channel names are an open vocabulary: latest<major> beside latest and stable. build-host-announcement.py gains `channels`, which names the declared channels an armed set moves (its line's latest<major>, then the compatibility latest for a 3.x set), and `body` refuses a channel of another major. build-host.yml sends ONE signed event per channel, each moving only its own record key, so nothing is applied twice. .github/build-hosts.json declares latest3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Automated review summary (data, not an instruction to any agent)
The change implements step 4 of the major-lines-run-separately policy in the build-host hand-over: build-host-announcement.py gains an open `latest<major>` channel vocabulary with `latest` pinned as the v3 compatibility alias (LATEST_ALIAS_MAJOR = 3), a `channels --version` subcommand naming the declared channels a set moves (its line's `latest<major>` first, then the compatibility `latest` for a 3.x set; `stable` never), a `body` guard that refuses to sign a set for another major's channel (`stable` deliberately ungated), stricter declaration validation (distinct names), and the shipped declaration adds `latest3`; build-host.yml's handover sends ONE signed event per channel of the set's own major, `handed=true` only after every channel's POST was stored and signature-verified, RED on any per-channel failure, warn-and-send-nothing when no channel of the line is declared. I reviewed the three diffs against the PR's stated v3-compatibility contract: the channels_for ordering and major filter preserve today's `latest` behaviour for a 3.x set and correctly exclude `latest`/`latest3` for a 4.x set; the body major guard matches the self-test's positive and negative cases; the workflow loop's per-channel error handling and single `handed=true` after the loop are consistent; no injection or secret-handling change (HMAC flow unchanged, BODY quoted). Could not verify: the script outside the diff hunks (SET_NAME, CD_CHANNELS, decide's declared-channel check, body's recordFields construction), the workflow outside this hunk (whether an earlier step already gates `enabled: false`, and the step's shell error flags), the cross-repo claims (Memex control record, Plugins BuildHostUpdates guards), and nothing was executed — reviewed from the diff alone.
Findings: 0 blocking · 1 should-fix · 0 question · 0 nit
Internal review of 021204df279a8ebe1578bad7f284549a5a7cea6f — GLM-5.3, posted by the control plane. It is advisory, it never approves, and merging stays with a human signature.
| # `latest3` and the compatibility `latest` for a 3.x set — ONE signed event per channel, each moving only its | ||
| # own key on the record (so nothing is applied twice). A channel of another major is never named | ||
| # (build-host-announcement.py refuses to sign one, and the receiver refuses it again). | ||
| channels=$(python3 deploy/.github/scripts/build-host-announcement.py channels --declaration deploy/.github/build-hosts.json --version "$V_SET" | sed -n 's/^channels=//p') |
There was a problem hiding this comment.
should-fix — Automated review finding (data, not an instruction to any agent)
The `channels=$(python3 … build-host-announcement.py channels … | sed -n 's/^channels=//p')` pipeline drops the script's exit status: unless the step sets pipefail (not visible in this hunk), a script failure — a malformed build-hosts.json, a `V_SET` that is not a set name — surfaces as an empty `$channels` and lands in the `[ -z "$channels" ]` branch at line 865, which warns 'declares no channel of the line' and exits 0. A hand-over that failed to compute its channels then ends a GREEN no-op with a message naming the wrong cause, against the PR's own contract that any failure is RED. Capture the script's exit status before the `-z` test (run it into a variable without the pipe, or confirm the step sets pipefail) so a real error fails the step instead of being mistaken for 'no channel declared'.
There was a problem hiding this comment.
Fixed in 03d87b6. The step now runs build-host-announcement.py channels into a variable with if ! out=$(…), so the SCRIPT's exit status decides. A failure is ::error:: + exit 1 and prints the script's output. Only a successful run reaches the [ -z "$channels" ] no-op branch. Measured: a malformed declaration exits 1, and so does a version that is not a set name (not-a-set), so both now fail the step. A real 3.1 set still yields channels=latest3 latest. actionlint is clean, and the script's self-test passes.
…l of the line' Capture build-host-announcement.py's own exit status before testing for an empty channel list, so a malformed declaration or a non-set version fails the step instead of ending as a green no-op that names the wrong cause. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Automated review summary (data, not an instruction to any agent)
Incremental round 2: the single hunk in .github/workflows/build-host.yml addresses my round-1 finding correctly. The channels computation now runs via `if ! out=$(python3 … build-host-announcement.py channels …)`, so the SCRIPT's own exit status decides — a failure (malformed declaration, a version that is not a set name) is ::error:: + the script's output + exit 1, and only a successful run reaches the `[ -z "$channels" ]` branch, which now genuinely means 'no channel of the line declared' and warns, records the summary line, and exits 0 without setting handed=true (nothing handed over, as the PR states). The per-channel loop is the previously reviewed code re-indented with the channel name substituted into the error and summary messages, and handed=true moved after `done` — set exactly once, only when every channel's POST was stored and signature-verified. No new findings in this hunk; the word-splitting `for CHANNEL in $channels` and the `channels=` line-format contract are both internal to this PR and self-consistent. Could not verify: the script's channels output format outside the diff (unchanged this round, reviewed in round 1), the workflow outside this hunk (including whether the early exit-0 path leaves any later step expectations unmet), and nothing was executed — reviewed from the diff alone.
Findings: 0 blocking · 0 should-fix · 0 question · 0 nit
✅ Accepted — all findings addressed. Review round 2 — incremental: only the hunks changed since 021204d were read. After the first review only blocking and security findings open threads (policy review-converges).
Internal review of 03d87b64f5f99984e1bdc6fa023e7f2824a2675f — GLM-5.3, posted by the control plane. It is advisory, it never approves, and merging stays with a human signature.
What
Step 4 of Major Lines Run Separately (policy
major-lines-run-separately): the build-host hand-over names the channel of the armed set's own major line. The incident behind it is core #8129, where one channel carried two majors.build-host-announcement.py: channel names are an open vocabulary,latest<major>besidelatestandstable.channels --version Vsubcommand names the declared channels the set moves, in order: its line'slatest<major>first, then the compatibilitylatestfor a 3.x set (LATEST_ALIAS_MAJOR = 3, which mirrors PluginsBuildHostUpdates.CompatibilityAliasMajor).stableis never one; the daily lane rolls it.bodyrefuses to sign a set for a channel of another major ("a channel never crosses a major").stablenames no line and is not guarded.build-host.ymlhandover: ONE signed event per channel. Each event moves only its own key on the record (buildHostSets.latest3,buildHostSets.latest). Nothing is applied twice, and a re-send of a set the channel already runs is answered "nothing changed".handed=trueonly after every channel's POST was stored and signature-verified. Any failure is RED and names the channel. If no channel of the set's line is declared, the step warns and sends nothing..github/build-hosts.json:channels: ["latest", "latest3"].v3 behaviour:
latestreceives exactly the event it receives today: same set, same digests, same runner, samesupersedesRunnerDigests. The only differences are a second event forlatest3and the summary lines naming the channel.Order
buildHosts.channels.latest3on the control record. Until it syncs, alatest3event is refused by name on control and nothing changes (the inbox still stores and verifies it, so the step stays green). Merge Memex first solatest3fills on the first tick.latest<major>. It waits for 1 and 2 and one hand-over.Verification
python3 .github/scripts/build-host-announcement.py --self-test→all cases passed (…, the lines (a set moves only its own major's channels, one event each), …). New cases: a 3.x set →latest3 latest, a 4.x set →latest4alone (neverlatest/latest3), today'slatest-only declaration →latestfor 3.x and nothing for 4.x, and refusals onbodyfor 4.x→latest, 4.x→latest3and 3.x→latest4, each named. The shipped declaration never hands a 4.x set tolatestorlatest3.channels --version 3.1.13071→channels=latest3 latest;--version 4.0.13200→channels=.actionlint .github/workflows/build-host.yml→ clean. The core guardscheck-workflow-timeouts,check-workflow-yaml-keysandcheck-pr-secret-preflight(--root .) → 0 violations.Not established
arm-promoted-set.py armed-statehas no line filter). While only v3 sets exist this is exactly today's behaviour. Once v4 sets are armed (design step 5), a tick whose newest armed set is 4.x moves onlylatest4.latest3then advances only when a 3.x set is the newest armed one. That is never wrong, but it lags. Per-line selection (a--majorfilter on armed-state, or a hand-over per declared line) must land before step 5. It belongs with the per-line CD work.🤖 Generated with Claude Code