Repository navigation
[Phase 2] Cross-repo remediation PR driver (DRY_RUN-gated, idempotent) #1150
Copy link
Copy link
Closed
Labels
dev-leadFor dev-lead agent pickupFor dev-lead agent pickupinitiativeEpic / initiative tracking issueEpic / initiative tracking issue
Description
Story
As a fleet platform maintainer,
I want a DRY_RUN-aware network driver that consumes the remediation plan and, per consumer repo, creates a branch, writes the canonical stub bytes, and opens exactly one PR,
so that detected drift is auto-corrected as an auditable, deterministic proposal instead of manual per-issue toil.
Acceptance Criteria
- Given the Phase-1 plan (from fleet_stub_drift.json), for each remediable {repo, stub_file}: fetch the canonical bytes from CANONICAL_STUB_REPO/standards/workflows/.yml (the source whose SHA drift was measured against), create a branch on the consumer repo, write the content via the contents API PUT, and open ONE PR per repo grouping all that repo's drifted stubs.
- DRY_RUN-aware: when DRY_RUN is truthy the driver logs the intended branch/PUT/PR per repo and makes zero write API calls (mirrors seed-repo-template.sh _is_dry/_seed_repo). DRY_RUN is the default in this story.
- Idempotent: if a remediation branch or an open remediation PR already exists for the repo, the driver skips re-creating it (mirrors seed-repo-template.sh's existing-PR check via gh pr list --head).
- The PR is opened against the consumer repo's default branch as a proposal — never a direct push to the default branch — with a deterministic title/body citing the canonical source and the drift.
- Post-write, the driver asserts per repo that the resulting file blob SHA equals the canonical SHA (byte-identity ⇒ drift closed) and fails that repo on mismatch rather than opening a misleading PR; the fleet-level verification summary is Phase 3's responsibility.
- bats tests stub gh and assert: dry-run makes no writes, the branch+PUT+PR sequence for a drifted repo, the idempotent skip, and one-PR-per-repo grouping; shellcheck passes.
- Fail-closed blast-radius bound: the driver refuses to run live (DRY_RUN=false) unless an explicit repo scope is supplied — with no scope it stays dry-run and emits a ::warning::. This guarantees a bound even if the driver is invoked before the Phase-3 pilot scope / REMEDIATE_MAX_REPOS ceiling lands.
Tasks / Subtasks
- Add the network driver main() to scripts/fleet_stub_remediate.sh consuming fleet_stub_drift.json, grouping entries by repo so each repo yields one branch and one PR. (AC: test issue from agent #1)
- Reuse the branch (git/refs POST from the default-branch head sha) + contents-PUT (with the existing-file sha when overwriting) + gh pr create pattern from seed-repo-template.sh _seed_repo/_put_file. (AC: test issue from agent #1, Add @claude delegation, auto-merge, and rebase handling #3, Optimize review: small-PR and incremental fast paths #4)
- Fetch canonical bytes with gh api repos/${CANONICAL_STUB_REPO}/contents/${canonical_path} --jq .content piped to base64 -d (same source as the SHA compare). (AC: test issue from agent #1, feat: add Copilot engine support via REVIEW_ENGINE toggle #5)
- Gate all writes behind _is_dry; default DRY_RUN=true; log intended mutations in dry-run. (AC: Go-live improvements for PR review agent #2)
- After PUT, verify the written blob SHA equals the canonical SHA and fail the repo on mismatch. (AC: feat: add Copilot engine support via REVIEW_ENGINE toggle #5)
- Add bats tests/fleet_stub_remediate.bats with gh stubbed on PATH; run shellcheck. (AC: feat: feature ideation framework — adversarial proposer/challenger/synthesizer #6)
- Require an explicit repo scope for live operation: when DRY_RUN=false and no scope is set, refuse to write and stay dry-run with a ::warning:: (fail-closed). (AC: Workflow should skip no-op PRs in MAX_PRS count #7)
Dev Notes
- Reuse the cross-repo PR machinery in shape from seed-repo-template.sh: default_branch lookup, git/ref/heads base sha, git/refs POST to create the branch, _put_file (contents PUT with the existing file sha when overwriting), gh pr list --head to detect an already-open PR, and a single gh pr create. [Source: scripts/seed-repo-template.sh]
- The canonical content fetch mirrors fleet_monitor.sh's canonical SHA read (repos/${CANONICAL_STUB_REPO}/contents/${canonical_path}); fetch .content and base64 -d for the bytes. Because the stub is deployed verbatim, PUTting these bytes makes the consumer blob SHA equal the canonical SHA — closing drift deterministically. [Source: scripts/fleet_monitor.sh]
- DRY_RUN convention: follow _is_dry()/DRY_RUN from seed-repo-template.sh and the repo's dry-run-first canaries so the first wiring is inert. [Source: scripts/seed-repo-template.sh, AGENTS.md]
- Blast-radius: enforce at most one PR per consumer repo per run (group by repo). The per-run max-repos ceiling and pilot scoping land in Phase 3.
- Supply-chain posture (idea's adversarial review): PRs are proposals through the normal PR-review pipeline, content is deterministic and byte-identical to canon, and the Phase-1 allowlist protects customized files. Encode 'never push to default branch' as an AC.
- Testing: bats with a gh shim on PATH asserting the API call sequence, matching how the repo's cross-repo scripts are exercised. [Source: tests/fleet_stub_drift.bats, AGENTS.md]
Project Structure Notes
The network driver lives in the same scripts/fleet_stub_remediate.sh added in Phase 1 (pure helpers + a source-guarded main()), matching how seed-repo-template.sh co-locates pure helpers with a network main().
References
- scripts/seed-repo-template.sh
- scripts/fleet_monitor.sh
- scripts/fleet_stub_drift.sh
- AGENTS.md
Likely target surface
scripts/fleet_stub_remediate.sh (network main)tests/fleet_stub_remediate.bats (new)
Story prepared by the BMAD Scrum Master (Bob) for epic #1148. Status: ready-for-dev.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
dev-leadFor dev-lead agent pickupFor dev-lead agent pickupinitiativeEpic / initiative tracking issueEpic / initiative tracking issue
Activity
don-petry commentedon Jul 15, 2026
Dev-Lead Implementation Plan
Issue: #1150 — [Phase 2] Cross-repo remediation PR driver (DRY_RUN-gated, idempotent)
Scope
Add a DRY_RUN-gated, idempotent, fail-closed network driver to
scripts/fleet_stub_remediate.shthat consumes the Phase-1 remediation plan (fromfleet_stub_drift.json), and per consumer repo fetches the canonical stub bytes, creates a branch, PUTs the content, verifies byte-identity (blob SHA == canonical SHA), and opens exactly one PR against the repo's default branch.Implementation Checklist
scripts/fleet_stub_remediate.sh: addDRY_RUN(defaulttrue) +_is_dryhelper (mirror seed-repo-template.sh)._canonical_bytes/_canonical_shahelpers (mirror fleet_monitor.sh canonical read:contents/<path>→.content|base64 -dand.sha)._put_file(contents PUT with existing-file sha when overwriting) — reuse seed-repo-template.sh shape._scope_list,_in_scope) readingREMEDIATE_REPOS+--repoflag(s)._remediate_reponetwork function: idempotent open-PR skip, branch create-or-reuse, per-stub fetch+PUT+SHA-verify, onegh pr create. Fail the repo on SHA mismatch (no misleading PR).main()as the network driver: build plan via existing purebuild_remediation_plan, group entries by repo (one branch + one PR per repo), apply the fail-closed scope gate, iterate repos.build_remediation_plan,resolve_canonical_path,remediation_allowlisted) unchanged; source-guard preserved.Tests to Write (append to
tests/fleet_stub_remediate.bats)gh pr createsequence (asserted via aghshim on PATH logging calls).DRY_RUN=falsewith no repo scope stays dry-run, emits a::warning::, and makes no writes.Test Command
bats tests/fleet_stub_remediate.batsandbash .dev-lead/scripts/dev-lead-lint.shdon-petry commentedon Jul 15, 2026
Dev-Lead: Implementation Complete
Plan Execution
DRY_RUN(defaulttrue) +_is_drytoscripts/fleet_stub_remediate.sh(AC Go-live improvements for PR review agent #2)._canonical_bytes/_canonical_sha/_blob_sha(canonical read mirrors fleet_monitor.sh) (AC test issue from agent #1, feat: add Copilot engine support via REVIEW_ENGINE toggle #5)._put_file(contents PUT with existing-file SHA) reusing seed-repo-template.sh shape (AC test issue from agent #1)._scope_list,_in_scope) readingREMEDIATE_REPOS+--repo(AC Workflow should skip no-op PRs in MAX_PRS count #7)._remediate_repo: open-PR idempotent skip, branch create-or-reuse, per-stub fetch+PUT+SHA-verify, onegh pr createagainst the default branch (AC test issue from agent #1, Add @claude delegation, auto-merge, and rebase handling #3, Optimize review: small-PR and incremental fast paths #4, feat: add Copilot engine support via REVIEW_ENGINE toggle #5).main()+run_remediation: builds plan via the purebuild_remediation_plan, groups by repo (one branch/one PR each), applies the fail-closed scope gate (AC test issue from agent #1, Workflow should skip no-op PRs in MAX_PRS count #7).build_remediation_plan,resolve_canonical_path,remediation_allowlisted) unchanged; source-guard preserved.Test Results
shellcheck --severity=warning -xclean;.dev-lead/scripts/dev-lead-lint.sh→ all checks passed. No regressions infleet_stub_drift.bats/template_stub_drift.bats.Files Changed
scripts/fleet_stub_remediate.sh: added the co-located Phase-2 network driver (helpers +_remediate_repo+run_remediation+ rewrittenmain()); pure plan-builder section untouched.tests/fleet_stub_remediate.bats: added agh-shim harness (setup/teardown) and 8 network-driver tests covering dry-run, live branch+PUT+PR, idempotent skip, one-PR-per-repo grouping, SHA byte-identity verify, and the fail-closed scope guard.Notes
gh pr list --headcheck.--repo/REMEDIATE_REPOSscope; with none the driver stays dry-run and emits a::warning::. The per-runREMEDIATE_MAX_REPOSceiling and pilot scoping land in Phase 3.tests/fleet_stub_remediate.batsis already registered inlint.yml's bats list (from Phase 1) — no workflow wiring change needed.feat: implement issue #1150 — [Phase 2] Cross-repo remediation PR dri…