Skip to content

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

Closed

Description

@don-petry

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

  1. canary-rollout.sh resolves 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.
  2. Registry support for the ingress form. canary-rings.json carries whatever per-agent key the new resolution needs (for example an ingress job/role selector alongside run_workflow), documented in the file's notes the way agent_ref_paths and autocut already are.
  3. 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.
  4. Mixed fleet works: collapsed and non-collapsed repos in the same ring resolve correctly in one evaluation, since fan-out is explicitly incremental.
  5. 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

  1. don-petry commented on Oct 2, 2026

    @don-petry
    ContributorAuthor

    Dev-Lead Implementation Plan

    Issue: #1224 — canary-rollout health gate goes blind on an ADR-0007 collapsed repo

    Scope

    Teach scripts/canary-rollout.sh to find an agent's runs on a collapsed repo. When the per-role workflow is absent but Agent Ingress is 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, triage UNRESOLVED) instead of silently dropping that member.

    Reuse decision (AC1)

    .github-private's scripts/lib/run-attribution.sh (#1727) is not importable here. The canary engine runs from a .github checkout, which has no .github-private tree (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:

    • role = job-name segment before /
    • worst-outcome precedence when one role has several jobs
    • a role whose jobs were all skipped did not run
    • a run with no jobs cannot be attributed, so it is flagged loudly

    Implementation Checklist

    • standards/canary-rings.json: top-level ingress (workflow: "Agent Ingress") plus _ingress_note. Per-agent ingress_job for 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.
    • New _ingress_attributed_runs: for each completed ingress run, read its jobs and reduce them to a per-role record (role conclusion + role field). Results are memoized per (repo, ingress) and shared by all agents.
    • New _agent_run_json <agent> <repo> <since>, which resolves in this order:
      • the per-role workflow;
      • if that is absent, the ingress plus ingress_job;
      • if both are absent, a non-consumer: [], as today;
      • if the ingress is present but the role can't be attributed: UNRESOLVED, recorded to a flag file.
    • Callers switch from _run_json "$repo" "$wf" to _agent_run_json:
      • _tier_sample
      • _cumulative_health
      • _baseline_daily
      • _suspect_class_counts
      • _sample_decision_counts
      • _blocker_evidence
    • Failed-step signatures and decision classes are scoped to the role's own jobs, so another role's failure in the same ingress run can't mask or trip this agent's benign/suspect matching.
    • _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 becomes BLOCKED with triage UNRESOLVED; real failures keep their own triage. Update evaluate, the promote message, and the sync-issues blocker note/evidence to match.

    Tests to Write (tests/canary_rollout.bats)

    • Collapsed repo (ingress with a dev-lead job, no Dev-Lead Agent workflow): runs resolve by role, skipped roles are ignored, other roles' failures don't leak in.
    • Non-collapsed repo still resolves by workflow name, and the ingress is never queried.
    • A repo with neither workflow is still a non-consumer [] ([Fleet Monitor] petry-projects/.github — .github/workflows/canary-rollout.yml #747 preserved).
    • Unresolvable member (ingress present, agent has no ingress_job, or an ingress run with no jobs) is reported UNRESOLVED, and _frontier_state does not PROMOTE.
    • Mixed ring: one collapsed and one legacy member in a single evaluation both contribute.
    • Registry: the five collapsed agents carry ingress_job, and _ingress_note is documented.

    Test Command

    bats tests/canary_rollout.bats + bash .dev-lead/scripts/dev-lead-lint.sh

    Note on blast radius

    In today's registry, petry-projects/markets is an explicit member only for apply-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.

  2. don-petry commented on Oct 2, 2026

    @don-petry
    ContributorAuthor

    Dev-Lead: Progress (in progress — not yet durable)

    Provisional. Nothing is pushed yet. The automation posts the authoritative completion record (with PR number + head SHA) after the push. Treat the notes below as intent, not delivered results.

    Plan Execution (intended)

    • Tests were written first and confirmed red. The implementation then followed the posted plan.
    • The local bats suite and dev-lead-lint.sh ran clean in this workspace. That is provisional, not the pushed run.

    Files touched (this workspace, not yet pushed)

    • scripts/canary-rollout.sh:
      • New functions:
        • _agent_run_json resolves a member's runs in order: per-role workflow, then ingress role job, then non-consumer [], else UNRESOLVED.
        • _ingress_agent_runs and _run_jobs_json attribute ingress runs to the agent's role job. Each run's jobs are read once per sweep and shared by all agents.
        • _record_unresolved and _unresolved_evidence record and list blind members.
      • Changed functions:
        • _repo_wf_runs_cached gains an opt-in strict mode that reports "not found" as rc 3.
        • The gate callers (_tier_sample, _cumulative_health, _baseline_daily, _suspect_class_counts, _sample_decision_counts, _blocker_evidence) now go through _agent_run_json.
        • _run_signature and _run_decision_class are scoped to the role's own jobs.
      • Gate behaviour: _frontier_state names any unresolved member in an ::error:: and holds an otherwise-clean gate as BLOCKED with triage UNRESOLVED. evaluate and the sync-issues blocker body/evidence handle this triage.
    • standards/canary-rings.json:
      • top-level ingress (workflow: "Agent Ingress") plus _ingress_note;
      • ingress_job for 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:
      • collapsed repo resolves by job;
      • legacy repo resolves by name and never queries the ingress;
      • neither workflow present stays [];
      • unresolved: no ingress_job, or a job-less run;
      • another role's failure can't leak into this agent's count;
      • mixed ring;
      • _frontier_state PROMOTE vs BLOCKED/UNRESOLVED;
      • blocker body and evidence;
      • registry keys.

    Notes

    • Reuse (AC1): .github-private's scripts/lib/run-attribution.sh (#1727) can't be imported from this checkout, so its role-reduction rules are duplicated deliberately, with a pointer comment.
    • Not unresolved:
    • False-positive risk: if a collapsed repo is an explicit member for an agent without ingress_job that doesn't actually run there, it will show UNRESOLVED. That is the intended loud default.
    • Blast radius today: markets is an explicit ring member only for apply-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.
  3. don-petry commented on Oct 2, 2026

    @don-petry
    ContributorAuthor

    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.

    Review 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    dev-leadFor dev-lead agent pickup

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions