Repository navigation
F4: canary-rollout engine operates on v<major>-<tier> channels (fallback-safe) [#657] #696
Description
Phase F4 of the major-scoped-channels epic #657. Make the canary-rollout engine operate on v<major>-<tier> channels — backward-compatible with the current live bare-tier tags (F5 migrates the live tags + consumers; F4 must NOT break the running fleet, which is still on <agent>/<tier>). Gated by the Canary-rollout Tests suite (#685) — keep scripts/lib/canary-rollout.sh pure.
The transition constraint (critical — same as F3)
The scheduled engine mutates production tags every 4h. Today those are <agent>/<tier>. F4 adds the ability to resolve/promote/autocut <agent>/v<M>-<tier>, but until F5 migrates the live tags, the engine must keep working on the bare-tier tags. Approach: resolution falls back — prefer <agent>/v<M>-<tier> when it exists, else the legacy <agent>/<tier>. On today's fleet (no v-tags yet) the engine behaves byte-identically. Verify: evaluate/autocut/promote against the current registry produce the SAME verdicts/moves as before F4.
Changes (scripts/canary-rollout.sh; pure bits in lib/)
- Channel resolution (
channel_commit+ thegit/ref/tags/<agent>/<tier>reads): given an agent + tier + major, resolve<agent>/v<major>-<tier>, falling back to<agent>/<tier>when the v-form tag is absent. Reuse F3'sring_pinned_major/ring_canonical_refconventions for the tag names (keep name-building consistent across ring-pins and the engine). autocut(_autocut_agent): the cut version's major (already computed via_autocut_bump— it supportsmajor) selects the target line. A major bump seeds<agent>/v<newmajor>-next(a fresh major line) rather than moving the existingnext; minor/patch move the current major'sv<M>-next. Immutable<agent>/vX.Y.Zrelease tags unchanged.cmd_promote/ promote-all: advance<agent>/v<M>-<tier>within a major line (a v2 candidate soaks v2-next→v2-ring0→…→v2-stable independently of v1-*).--override/--confirm/--allow-pre-existingsemantics unchanged.evaluate: report the major line under evaluation (e.g. "candidate (v2-next) = …").
Registry (standards/canary-rings.json, jq -a)
If a per-agent "current major" is needed for resolution, derive it from the latest <agent>/vX.Y.Z release tag (no new required field). Add an OPTIONAL major_line hint only if derivation is insufficient — prefer derivation to avoid schema churn.
Tests (tests/canary_rollout.bats, gated by #685)
- resolution prefers
v<M>-<tier>when present, falls back to bare<tier>when absent → on the current (bare-tier) fixture, verdicts are byte-identical to pre-F4 (the transition-safety guard). - autocut of a major bump seeds
v<newmajor>-next(fresh line), not a move of the old next. - autocut of minor/patch moves the current major's
v<M>-next. - promote advances within a major line; a v2 promotion never touches v1-* tags.
- evaluate prints the major line.
Out of scope
F5: live tag migration (<agent>/<tier>→<agent>/v<M>-<tier>), consumer re-pins, retirement of the bare-tier form. F4 makes the engine CAPABLE + fall-back-safe; it does not migrate live tags.
Acceptance
- Engine resolves/promotes/autocuts
v<M>-<tier>with fallback to bare-tier; zero behavior change on today's fleet (bats fixture proves byte-identical verdicts). - major bump seeds a fresh major line; promotion stays within a major.
lib/pure; bats green underCanary-rollout Tests; shellcheck clean.
Activity
don-petry commentedon Jul 13, 2026
Dev-Lead Implementation Plan
Issue: #696 — F4: canary-rollout engine operates on v- channels (fallback-safe) [#657]
Scope
Make the canary-rollout engine resolve/promote/autocut major-scoped
<agent>/v<M>-<tier>channel tags, preferring the v-form when it exists and falling back to the legacy bare<agent>/<tier>— so on today's (bare-tier) fleet the engine is byte-identical to pre-F4.Implementation Checklist
scripts/lib/canary-rollout.sh(pure): addmajor_component <version>andchannel_tag <agent> <tier> [major]name-builders (matching F3ring_canonical_refconvention)scripts/canary-rollout.sh: add_agent_current_major(derive major from highest<agent>/vX.Y.Zrelease),_channel_tag_commit,_looks_like_oid,_resolved_channel/_resolved_channel_tagscripts/canary-rollout.sh: rewritechannel_committo preferv<M>-<tier>then fall back to bare<tier>scripts/canary-rollout.sh:cmd_promotemoves the resolved frontier tag (v-form within a major line, else bare)scripts/canary-rollout.sh:_autocut_agent— major bump seeds a fresh<agent>/v<newmajor>-next; minor/patch move the resolved current-major next tag (bare on today's fleet)scripts/canary-rollout.sh:cmd_evaluatereports the major line under evaluation (e.g.candidate (v2-next) = …)Tests to Write (
tests/canary_rollout.bats, gated by #685)major_componentextracts MAJOR;channel_tagbuilds v-form + bare formevaluateverdict + labels are byte-identical to pre-F4 (fall-back to bare)v<M>-<tier>when that tag is presentv<newmajor>-next(fresh line), does not move the old nextv<M>-nextwhen the v-line existsTest Command
GH_TOKEN=dummy bats tests/canary_rollout.bats(CI:Canary-rollout Tests— shellcheck + bats)don-petry commentedon Jul 13, 2026
✅ F4 complete — canary-rollout engine now operates on
v<major>-<tier>channels (fallback-safe)Implemented per the Phase-1 plan. The engine is now capable of resolving, promoting, and autocutting major-scoped
<agent>/v<M>-<tier>channel tags while remaining fall-back-safe: it prefers the v-form tag when it exists, otherwise the legacy bare<agent>/<tier>. On today's bare-tier fleet (no v-tags) the engine is byte-identical to pre-F4 — the transition-safety guarantee.What changed
scripts/lib/canary-rollout.sh(pure core — kept side-effect-free):major_component <version>— MAJOR of a strictMAJOR.MINOR.PATCH, empty otherwise.channel_tag <agent> <tier> [major]— builds<agent>/v<M>-<tier>with a major (mirrors F3'sring_canonical_ref), else bare<agent>/<tier>.scripts/canary-rollout.sh(orchestrator / I/O glue):_looks_like_oid— hex guard so only a real commit id (never an absent-tag/{}sentinel) is preferred over the bare form._agent_current_major— derives the current major from the agent's highestvX.Y.Zrelease (no new required registry field — derivation only)._channel_tag_commit/_resolved_channel/_resolved_channel_tag— unified prefer-v-then-fallback resolution shared by commit lookup, promote, autocut, and display.channel_commit— now delegates to_resolved_channel(fall-back-safe).cmd_promote— advances the resolvedv<M>-<tier>within a major line (a v2 promotion never touches a v1 tag). Logical tier still drives the gate and is reported aspromoted_ring._autocut_agent— a MAJOR bump seeds a fresh<agent>/v<newmajor>-nextline; minor/patch advance the current major'sv<M>-next, falling back to barenextwhere no v-line exists yet.cmd_evaluate— reports the resolved major line (e.g.candidate (v2-next) = …, ring listing showsv2-stable).Test-first (TDD)
Added 10 tests to
tests/canary_rollout.bats(written red-first, confirmed failing before implementation): purechannel_tag/major_component; v-line resolution + evaluate label; promote-within-major; autocut MAJOR-seeds-v3-next, patch-advances-v2-next, patch-falls-back-to-bare; and a byte-identical-on-bare-fixture transition-safety guard. The shared_autocut_stubwas made faithful to the bare fleet (av<M>-nextchannel probe resolves absent), so its release-tag existence probe is unaffected.Verification
bats tests/canary_rollout.bats— 180/180 passing (170 pre-existing unchanged + 10 new).shellcheck --severity=warning -x scripts/canary-rollout.sh scripts/lib/canary-rollout.sh— clean..dev-lead/scripts/dev-lead-lint.sh— all checks passed.Out of scope (as specified)
F5 — live tag migration and consumer re-pins. F4 only makes the engine capable + safe; today's fleet behavior is unchanged.
feat: implement issue #696 — F4: canary-rollout engine operates on v<…