Repository navigation
canary-rollout health gate goes blind on an ADR-0007 collapsed repo: it resolves runs by per-role workflow NAME, which the ingress replaces with jobs #1224
Description
Summary
scripts/canary-rollout.sh resolves an agent's runs by the workflow display name registered in standards/canary-rings.json:
wf="$(_agent_field "$agent" run_workflow)" # e.g. "Dev-Lead Agent"
gh run list --repo "$repo" --workflow "$wf" …ADR-0007 (combsmith/.github-private#1710) collapses a repo's per-role Class-1 caller stubs into one .github/workflows/agent-ingress.yml (name: Agent Ingress) with one job per role. After a collapse, those per-role workflows no longer exist in that repo — dev-lead becomes a job named dev-lead inside Agent Ingress.
So gh run list --repo <collapsed-repo> --workflow "Dev-Lead Agent" returns nothing there. The script's own comment (≈L504) notes that a missing workflow is a permanent error class, so this is not a transient miss: the health gate silently loses that repo's evidence.
Scope of the blast radius
The pilot repo is petry-projects/markets (combsmith/.github-private#1729, companion combsmith/markets#506). All five roles it collapses are registered agents:
| agent | registered run_workflow |
after collapse |
|---|---|---|
dev-lead |
Dev-Lead Agent |
job dev-lead in Agent Ingress |
pr-review-mention |
PR Review — Mention Trigger |
job in Agent Ingress |
pr-auto-review |
PR Auto-Review — Ready Check |
job in Agent Ingress |
pr-review |
PR Review Agent — Trigger |
job in Agent Ingress |
ci-failure-analyst |
CI Failure Analyst |
job in Agent Ingress |
markets is a ring member, so its runs are part of the promotion evidence for all five. A gate that sees zero runs there is deciding on incomplete evidence.
Why this is unscoped today
combsmith/.github-private#1727 fixed exactly this class — "retarget run attribution to job/role level so the collapse doesn't blind the fleet monitor" — but only inside .github-private (scripts/fleet_monitor.sh, scripts/lib/run-attribution.sh, scripts/pr_review_health.sh). It did not touch this repo.
Epic combsmith/.github-private#1723 states it is "scoped to the tooling + docs in .github-private and the FIRST repo's collapse", and #1729's only write to this repo is AC11(a) — publishing the canonical agent-ingress.yml template under standards/workflows/ for drift monitoring. Canary rollout is a different consumer of the same identity change, and no story covers it. Raised while reviewing #1729's package.
Acceptance criteria
canary-rollout.shresolves an agent's runs on a collapsed repo. Where the per-role workflow is absent but an ingress exists, attribute runs by the job/role inside it, the same way #1727 does for the fleet monitor. Reuse that logic rather than writing a second attribution path if it is importable; if it is not, say so and note the duplication deliberately.- Registry support for the ingress form.
canary-rings.jsoncarries whatever per-agent key the new resolution needs (for example an ingress job/role selector alongsiderun_workflow), documented in the file's notes the wayagent_ref_pathsandautocutalready are. - A blind gate fails loudly, never silently. If an agent's runs cannot be resolved for a ring member, that member is reported as unresolved and does not count as passing evidence. Today the permanent-error path just drops it.
- Mixed fleet works: collapsed and non-collapsed repos in the same ring resolve correctly in one evaluation, since fan-out is explicitly incremental.
- Tests in
tests/canary_rollout.bats: a collapsed repo (ingress with a role job, no per-role workflow) resolves; a non-collapsed repo still resolves by workflow name; an unresolvable member is reported unresolved rather than passing.
Sequencing
This does not block the pilot's package, but it should land before markets actually merges its collapse — otherwise the first collapsed repo silently degrades promotion evidence for five agents. combsmith/markets#506 is deliberately unlabelled pending ADR-0010 acceptance, so there is time.
Activity
don-petry commentedon Oct 2, 2026
Dev-Lead Implementation Plan
Issue: #1224 — canary-rollout health gate goes blind on an ADR-0007 collapsed repo
Scope
Teach
scripts/canary-rollout.shto find an agent's runs on a collapsed repo. When the per-role workflow is absent butAgent Ingressis present, it attributes runs by the role job inside the ingress. Any member it cannot attribute is reported as UNRESOLVED, and the gate fails closed (BLOCKED, triageUNRESOLVED) instead of silently dropping that member.Reuse decision (AC1)
.github-private'sscripts/lib/run-attribution.sh(#1727) is not importable here. The canary engine runs from a.githubcheckout, which has no.github-privatetree (the same constraint behind #613/#1069's inline autocut). So I'm deliberately duplicating its per-run role reduction, with a pointer comment back to the source:/Implementation Checklist
standards/canary-rings.json: top-levelingress(workflow: "Agent Ingress") plus_ingress_note. Per-agentingress_jobfor dev-lead, pr-review-mention, pr-auto-review, pr-review, ci-failure-analyst (the job keys match the markets collapse package §3)._repo_wf_runs_cached: add an opt-in strict mode that reports "workflow not found" (rc 3) separately from "empty". The default behaviour ([Fleet Monitor] petry-projects/.github — .github/workflows/canary-rollout.yml #747/[#811 re-split 1/2] canary-rollout: collapse run-list enumeration to a cached per-repo fetch #819) stays unchanged._ingress_attributed_runs: for each completed ingress run, read its jobs and reduce them to a per-role record (role conclusion +rolefield). Results are memoized per (repo, ingress) and shared by all agents._agent_run_json <agent> <repo> <since>, which resolves in this order:ingress_job;[], as today;_run_json "$repo" "$wf"to_agent_run_json:_tier_sample_cumulative_health_baseline_daily_suspect_class_counts_sample_decision_counts_blocker_evidence_frontier_state: if any member is unresolved, emit an::error::naming each one. A gate that would otherwise be clean (PROMOTE, SOAKING or AWAITING_CONFIRMATION) instead becomesBLOCKEDwith triageUNRESOLVED; real failures keep their own triage. Updateevaluate, thepromotemessage, and thesync-issuesblocker note/evidence to match.Tests to Write (
tests/canary_rollout.bats)dev-leadjob, noDev-Lead Agentworkflow): runs resolve by role, skipped roles are ignored, other roles' failures don't leak in.[]([Fleet Monitor] petry-projects/.github — .github/workflows/canary-rollout.yml #747 preserved).ingress_job, or an ingress run with no jobs) is reported UNRESOLVED, and_frontier_statedoes not PROMOTE.ingress_job, and_ingress_noteis documented.Test Command
bats tests/canary_rollout.bats+bash .dev-lead/scripts/dev-lead-lint.shNote on blast radius
In today's registry,
petry-projects/marketsis an explicit member only forapply-repo-settings. For the five collapsed agents it is reached only via*(stable), which the gate does not enumerate. So the blindness bites as soon as a collapsed repo is an explicit ring member (for example if TalkTerm or bmad-bgreat-suite collapse), not on markets' merge alone. The fix covers both.don-petry commentedon Oct 2, 2026
Dev-Lead: Progress (in progress — not yet durable)
Plan Execution (intended)
dev-lead-lint.shran clean in this workspace. That is provisional, not the pushed run.Files touched (this workspace, not yet pushed)
scripts/canary-rollout.sh:_agent_run_jsonresolves a member's runs in order: per-role workflow, then ingress role job, then non-consumer[], else UNRESOLVED._ingress_agent_runsand_run_jobs_jsonattribute ingress runs to the agent's role job. Each run's jobs are read once per sweep and shared by all agents._record_unresolvedand_unresolved_evidencerecord and list blind members._repo_wf_runs_cachedgains an opt-in strict mode that reports "not found" as rc 3._tier_sample,_cumulative_health,_baseline_daily,_suspect_class_counts,_sample_decision_counts,_blocker_evidence) now go through_agent_run_json._run_signatureand_run_decision_classare scoped to the role's own jobs._frontier_statenames any unresolved member in an::error::and holds an otherwise-clean gate asBLOCKEDwith triageUNRESOLVED.evaluateand the sync-issues blocker body/evidence handle this triage.standards/canary-rings.json:ingress(workflow: "Agent Ingress") plus_ingress_note;ingress_jobfor dev-lead, pr-review-mention, pr-auto-review, pr-review and ci-failure-analyst. The job keys come from §3 of the markets collapse package.tests/canary_rollout.bats: 12 new canary-rollout health gate goes blind on an ADR-0007 collapsed repo: it resolves runs by per-role workflow NAME, which the ingress replaces with jobs #1224 tests:[];ingress_job, or a job-less run;_frontier_statePROMOTE vs BLOCKED/UNRESOLVED;Notes
.github-private'sscripts/lib/run-attribution.sh(#1727) can't be imported from this checkout, so its role-reduction rules are duplicated deliberately, with a pointer comment.[]), which keeps [Fleet Monitor] petry-projects/.github — .github/workflows/canary-rollout.yml #747. Otherwise every ring member that doesn't run an agent would block.ingress_jobthat doesn't actually run there, it will show UNRESOLVED. That is the intended loud default.marketsis an explicit ring member only forapply-repo-settings. For the five collapsed agents it is reached only via*, which the gate does not enumerate. The fix applies once a collapsed repo is an explicit member.don-petry commentedon Oct 2, 2026
Dev-Lead: Implementation Complete — PR #1238
The implementation for issue #1224 is durable: commits are pushed and a pull request is open. This record is backed by verifiable artifacts you can confirm in one click.
1e0fbf61ba8c8592ca2b28eedeb45cab837dd9f6main: 3 file(s), +479/-55 linesReview happens on the PR. Acceptance criteria and test results are evidenced by the PR's diff and CI checks — not asserted here. If a later stage fails, this claim is retracted in place.
feat: implement issue #1224 — canary-rollout health gate goes blind o…
test(canary-rollout): call _cumulative_health with the #1220 candidat…
Merge main (#1240 tag-gap hold) into dev-lead/issue-1224
fix(canary-rollout): collapsed-repo job reads no longer hold the gate…
fix(canary-rollout): a truncated baseline is never an empty or all-ze…
fix(canary-rollout): pre-adoption and job-less cancelled ingress runs…
fix(canary-rollout): a failed job can no longer be masked by an actio…
docs(canary-rollout): _ingress_note states the job-less startup_failu…
fix(canary-rollout): legacy signature/decision reads keep their singl…
perf(canary-rollout): drop the per-run jq re-parse of the ingress pay…
fix(canary-rollout): run limit in runs-cache key; clearer unresolved …
fix(canary-rollout): FLAG_ERROR state line carries all 18 fields; add…
feat: implement issue #1224 — canary-rollout health gate goes blind o…
fix(canary-rollout): an agent with no ingress_job is not an ingress r…