OPER-56
Generalized reap-streak parker (guardrail)
Body
```yaml ticket_id: OPER-56 size: M has_migration: false phi_in_scope: false verification_cmd: "pnpm --filter dispatcher test -- workflow-reaper" target_branch: main module: dispatcher ``` ## Agent directive `@claude` — Implement the generalized reap-streak parker per the spec below. Open a PR titled `feat(dispatcher): generalized reap-streak parker (OPER-56)` against `main`. Behind feature flag; default off. Follow `CLAUDE.md`. **Do not exceed 500 LOC total.** --- ## 1. Summary Generalize the workflow-reaper streak parker to count any consecutive non-merge outcome (`failure` | `success_nopr` | `success_closed`) rather than only `success_nopr`. Parks the ticket at threshold 5 with a diagnostic label describing the dominant failure mode. Behind feature flag. ## 2. Motivation On 2026-07-26 eleven Portico tickets (POR-321, POR-122, POR-124, POR-129, POR-133, POR-134, POR-202, POR-242, POR-245, POR-248, POR-251) accumulated 55–110 dispatch cycles each without merging. Zero of them tripped the current guardrail (`workflow-reaper.ts:78`, threshold 3, `success_nopr` only) because their failure modes were `success_closed` (size gate) or `failure`. This ticket closes that gap. See `/home/user/workspace/decisions/cancelled-11-fix-plan-2026-07-26.md` and `/home/user/workspace/decisions/oper-56-guardrail-proposal-2026-07-26.md` for full context. ## 3. Acceptance criteria - [ ] `packages/dispatcher/src/workflow-reaper-streak.ts` exports `getConsecutiveNonMergeCount(taskId)` reading from `DispatchRun.outcome` (if the enum is already granular enough — verify first). - [ ] `parkTaskIfStreakExceeded(taskId)` flips status to `BOUNCED` and labels the ticket with a dominant-mode string when the count hits 5. - [ ] Dominant-mode label is one of: - `reap:oversize-close-streak` — dominated by `success_closed` - `reap:no-pr-streak` — dominated by `success_nopr` - `reap:failure-streak` — dominated by `failure` - `reap:mixed-streak` — no single mode ≥3 of 5 - [ ] Fast path retained: 3 consecutive `success_nopr` still parks immediately (backward compatible with `DEFAULT_MAX_CONSECUTIVE_SUCCESS_NOPR`). - [ ] Feature flag `reaper.generalized_streak` gates the new behavior. Default off. - [ ] Only counts outcomes from the last 24h (older decay out). - [ ] Slack notification to `#operant-studio-ops` on every park with the 5 outcome tags in order + workflow-run links. - [ ] Backtest script `packages/dispatcher/scripts/backtest-reaper.ts` logs what would have been parked without actually parking. - [ ] Vitest unit tests covering: - 5 consecutive `success_closed` → park with `reap:oversize-close-streak`. - 5 consecutive `failure` → park with `reap:failure-streak`. - 3 consecutive `success_nopr` → park with `reap:no-pr-streak` (fast path, threshold 3). - Merge in the middle resets the counter. - Non-merge outcomes older than 24h don't count. - Feature flag off → no park. - [ ] `pnpm lint && pnpm typecheck && pnpm --filter dispatcher test -- workflow-reaper` green. ## 4. Files in scope ### Create - `packages/dispatcher/src/workflow-reaper-streak.ts` - `packages/dispatcher/src/workflow-reaper-streak.test.ts` - `packages/dispatcher/scripts/backtest-reaper.ts` ### Modify - `packages/dispatcher/src/workflow-reaper.ts` — integrate new streak logic; keep `DEFAULT_MAX_CONSECUTIVE_SUCCESS_NOPR = 3` fast path. ### Do NOT touch - The DispatchRun schema unless the outcome enum is not granular enough (verify first; stop and comment if it isn't). ## 5. Constraints & stop conditions - Do not exceed 500 LOC total. - Do not park any ticket while the feature flag is off. - Stop and comment if the `DispatchRun.outcome` enum doesn't already distinguish `success_nopr` / `failure` / `success_closed` / `success_merged` — the ticket depends on that granularity being present. - Do not migrate or delete existing BOUNCED tickets. ## 6. Dependencies - **Blocked by:** none (defense-in-depth; ship independently of POR-492/493/494). - **Blocks:** OPER-57 (dependency validator, separate ticket) — not created yet. ## 7. Out of scope - Fixing the underlying failure modes (planner underspec, phantom deps). Those are separate tickets. - Changing dispatcher throughput (still 1 claim/tick). - Deleting or migrating existing BOUNCED tickets. ## 8. PR Description ``` ## Summary Generalizes workflow-reaper streak parker to count any consecutive non-merge outcome (failure | success_nopr | success_closed) rather than just success_nopr. Parks at threshold 5 with a diagnostic dominant-mode label. Behind feature flag. ## Acceptance criteria status - [x] getConsecutiveNonMergeCount / parkTaskIfStreakExceeded - [x] Dominant-mode labels (4 variants) - [x] Fast path 3x success_nopr retained - [x] Feature flag reaper.generalized_streak (default off) - [x] 24h decay window - [x] Slack notification on park - [x] Backtest script - [x] Vitest coverage - [x] pnpm lint && typecheck && test green ## Test output <paste output> ## Risk level low (guarded by feature flag; default off; backtest before enable) ## Rollout note Ship off. Backtest for 1 week. Enable in staging for 1 week. Enable prod. Rollback if false-positive rate > 5% of merges per week. ## Out of scope - Planner underspec fixes - Dispatcher throughput - Phantom-dependency validation (separate ticket) ```
Attachments
Loading attachments…
Comments
Loading comments…