Repository navigation
[Phase 5] DRY_RUN soak, threshold tuning without overfitting, and go/no-go to enable label/close #1206
Description
Story
As a maintainer,
I want progress spam-pr-guard through the fleet canary rings — spam-pr-guard/next → ring0 → ring1 → stable — using the standard #548 graduated dwell/sample gate, with require_confirmation: true on ring1→stable as the human go/no-go to enable label/close, tuning thresholds via standards/spam-pr-guard.json without overfitting the frozen regression fixtures,
so that label and close are enabled only after the scorer is validated on real traffic with a bounded false-positive rate, keeping legitimate first-timers safe, and the enable gate ships via the fleet pattern rather than a one-off DRY_RUN flip.
Reframed from "DRY_RUN soak + go/no-go code flip" to "canary ring progression" — the fleet pattern already encodes dwell-and-sample soak at every ring boundary (scripts/canary-rollout.sh, graduated gate per #548). The dry_run input is a stub-level default (ON initially), overridable per-stub, and flipped at a specific cut-standards-release.sh cut once require_confirmation clears on ring1→stable.
Acceptance Criteria
- Ring progression.
spam-pr-guardpasses the standard#548graduated gate at each ring boundary (next → ring0: dwell_hours 4 + sample_fraction_permille 250 clamped 3..15;ring0 → ring1: dwell_hours 8 + waive_sample;ring1 → stable: dwell_hours 12 + sample_min 1 +require_confirmation: true). Promotion is driven byscripts/canary-rollout.sh promote-allon its scheduled cadence; a human runsscripts/canary-rollout.sh promote spam-pr-guard --confirmonce to clear thering1→stableconfirmation gate (dispatch audit-logged; no state store). - DRY_RUN defaults, per ring. While on
nextandring0, the stub's effectivewith.dry_runstaystrue— the staged action logs the intended action to the step summary and makes zero write API calls. The flip is a stub-default change (with.dry_run: false) that ships via the nextscripts/cut-standards-release.sh cutAFTER the--confirmonring1→stable. Individual consumers may override via their own stub'swith:block. - Soak evidence. The soak log is reviewed against at least 10 real outside-PR events on
next+ring0; the review is captured as an evidence comment on the Phase 5 canary-confirm issue thatscripts/canary-rollout.sh sync-issuesfiles forspam-pr-guard(labeledneeds-human, auto-closes on promote or candidate change — seecanary-rings.json § _confirm_note). - Fixture discipline. Thresholds are tuned only via
standards/spam-pr-guard.json; the frozen regression fixtures (#1976-shaped → high; legitimate-first-time → low with the raw-score assertion from [Phase 2] Deterministic spam-PR scoring script and config #1203; security-sensitive → escalate) are never edited to force a score — they are the immutable baseline and must still pass at whatever thresholds are chosen. - Held-out validation. Threshold changes are validated on held-out real DRY_RUN events (from the
next/ring0soak) distinct from the bats fixtures, so tuning cannot overfit or reward-hack the fixture set. - Metric gate before
--confirm. The legitimate-first-time counterfactual scores below the medium threshold at the enabled values, and the observed false-positive rate on legitimate PRs is under 2% onnext+ring0BEFORE thering1→stable--confirm. These metrics are recorded on the canary-confirm issue. - Decisions recorded on the epic. The decision on whether high-score PRs auto-close vs label-only, and any close delay (discussion open Q1), is recorded on the epic Org standard: automated spam / low-effort outside-PR handling (spam-pr-guard) #1200 at
--confirmtime rather than silently baked into earlier code. - Anti-reward-hacking guard. A CI guard (or ack-gate label, mirroring
.github/workflows/test-deletion-guard.yml) fails any PR that modifies a frozen baseline fixture undertests/fixtures/spam-pr-guard/without an explicit maintainer ack, so thresholds cannot be tuned by quietly editing the fixtures to pass.
Tasks / Subtasks
- Verify
scripts/canary-rollout.sh promote-allcyclesspam-pr-guardthroughnext → ring0 → ring1under the standard#548gate (AC: Addressing PR comments #1) - Capture the ~2-week
next+ring0soak log against >= 10 real events on the canary-confirm issue (AC: Add coding standards & principles section to AGENTS.md #3) - Tune thresholds via
standards/spam-pr-guard.jsonwhile keeping the regression fixtures frozen; validate on held-out real events (AC: Add workflow, environment, and orchestration guidance #4, Add stacked PR strategy and Epic-level workflow guidance #5) - Confirm the counterfactual stays below medium and FP rate < 2% before
--confirm(AC: feat: add Structured Logging and CQRS standards #6) - Record the auto-close-vs-label decision on the epic (AC: docs: document branch protection rules, rulesets, and auto-merge policy #7)
- Run
scripts/canary-rollout.sh promote spam-pr-guard --confirm(dispatch; audit-logged) to clearring1→stable(AC: Addressing PR comments #1) - Cut
standards/vX.Y.Zwith thewith.dry_run: falsedefault once--confirmclears, so the stable default ships to all stub consumers (AC: Add multi-agent isolation strategy using git worktrees #2) - Add the anti-reward-hacking CI guard against silent edits to
tests/fixtures/spam-pr-guard/(AC: Evolve Agentic Development: Integrate Claude Agent Teams with BMAD Workflow #8)
Dev Notes
- The soak IS the canary ring progression; the fleet pattern does not have a separate "DRY_RUN phase" distinct from
next/ring0. Seestandards/canary-rings.json § _confirm_notefor howrequire_confirmationholds aring1→stabletransition and howsync-issuessurfaces evidence. --confirmis NOT--override: it advances ONLY a reliability-clean AWAITING_CONFIRMATION state and can never bypass a BLOCKED gate. Reliability gates (next → ring0 → ring1) still clear unattended on the standard#548graduated gate.- The anti-reward-hacking guard mirrors
.github/workflows/test-deletion-guard.yml(which gatestests/deletions behind an ack-test-deletion label): extend the same shape to cover EDITS of the frozentests/fixtures/spam-pr-guard/fixtures, since tuning against the fixtures rather than held-out events is the reward-hacking failure mode. - Held-out validation = the real
next/ring0events, which are distinct from the training fixtures — the guard against overfitting the fixtures. Capture a summary on the canary-confirm issue so the--confirmdecision has a durable record. - Discussion open Q1 (auto-close vs label-only) is decided at
--confirmtime, not baked into the scorer or workflow earlier; enhancement recommends auto-close with a clear reopen comment plus a delay, but this remains the recorded human decision on Org standard: automated spam / low-effort outside-PR handling (spam-pr-guard) #1200. - The
dry_runflip is a stub-default change (editstandards/workflows/spam-pr-guard.ymlto setwith: { dry_run: false }) that ships viascripts/cut-standards-release.sh cut vX.Y.Z→standards/v1-stable. A consumer that opts out of the flip can override in their own stub'swith:block.
Project Structure Notes
Primarily an operational/decision story (dev-lead:hands-off): config threshold edits in standards/spam-pr-guard.json, a recorded --confirm decision, a one-time stub-default change, and the CI guard. No new scripts; touches only the config file, the stub default, and canary-rings.json if any gate knobs need tweaking.
References
scripts/canary-rollout.sh(promote / promote-all / promote --confirm / sync-issues; AC Addressing PR comments #1, Add coding standards & principles section to AGENTS.md #3)standards/canary-rings.json § _confirm_note+.agents["pr-auto-review"]as acorrectnessprior artstandards/standards-versioning.md(how thewith.dry_run: falsedefault ships; AC Add multi-agent isolation strategy using git worktrees #2).github/workflows/test-deletion-guard.yml(anti-reward-hacking guard pattern; AC Evolve Agentic Development: Integrate Claude Agent Teams with BMAD Workflow #8)AGENTS.md § Fleet stub-drift remediation loop- Org standard for automatically handling spam / low-effort outside PRs #1198
Likely target surface
standards/spam-pr-guard.json(threshold tuning edits)standards/workflows/spam-pr-guard.yml(one-shotwith.dry_run: falsedefault change at--confirmtime).github/workflows/spam-pr-guard-tests.yml(or a sibling fixture-ack-gate workflow for AC Evolve Agentic Development: Integrate Claude Agent Teams with BMAD Workflow #8)
Story prepared by the BMAD Scrum Master (Bob) for epic #1200. Realigned from one-off DRY_RUN flip to fleet canary ring progression + require_confirmation go/no-go. Status: inert pending Phase 3 landing + first promote-all cycle.
Activity
don-petry commentedon Sep 29, 2026
dev-lead is withholding action on this item.
It is labeled
dev-lead:hands-off(deliberately kept off-limits to dev-lead (e.g. changes to the dev-lead automation itself)), so dev-lead will not pick it up while that label is present. This notice is posted once so the withhold is visible rather than looking like a stalled run.To re-enable automated pickup: remove the
dev-lead:hands-offlabel.