Operant Studio
OPER-207

Persist NEEDS_RESCOPE reason to ticket body + sticky label

Suggested
Justin Cooke

Body

---
feature_id: FEAT-studio-planning-validation
---

## Context

Planner rescope this session: OPER-184 through OPER-194 all NEEDS_RESCOPE with `labels: []` and no in-body trailer explaining why. The rescope reason (which of the 9 `PLANNING_REJECTION_REASONS` fired) is written to `PlanningValidation.verdict` + `.reasons` but there is no user-visible surface on the ticket itself. Determining the reason required reading `apps/web/src/lib/planning-validator.ts` source and correlating field state.

This is a discovery-cost problem: every rescope this week has cost the filer 5-10 minutes to figure out which reason fired, when the system already knows.

Contrast with admission failures — those DO get a body trailer (`<!-- admission-rejected -->` with the reason). Rescope failures should get the same treatment.

## What

Extend the planner rescope path (in `apps/web/src/lib/plan-validation-runner.ts` or the rescope call site) to:

1. On NEEDS_RESCOPE transition, append to Task.body:
   ```
   ---
   <!-- planner-rescope: <reason_code> -->
   **Planner rescope (<reason_code>):** <ADMISSION_ISSUE_MESSAGES[reason_code]>
   ```
2. Also add a sticky label `rescope:<reason_code>` (e.g., `rescope:missing_verify_cmd`) so the rescope queue is filterable by reason kind.
3. On successful re-admission (see OPER-193), strip both the trailer and the sticky label.
4. Idempotent: if the same reason fires again, no duplicate trailer or label.
5. If MULTIPLE reasons fire on the same rescope: append each as a bullet in a single trailer block; multiple sticky labels.

Downstream effect: the rescope queue UI at `/qa/live-fixes` becomes actionable — the filer can see WHY without opening the audit log.

## Acceptance Criteria

- [ ] Rescope path appends `<!-- planner-rescope: <reason> -->` trailer to Task.body
- [ ] Rescope path adds sticky label `rescope:<reason_code>`
- [ ] Successful re-admission strips trailer and label (interacts cleanly with OPER-193 auto-reheal)
- [ ] Multiple reasons on the same rescope: bulleted list in one trailer, multiple labels
- [ ] Idempotent: repeated rescopes with same reason do not duplicate trailer
- [ ] `/qa/live-fixes` (OPER-178) can filter by rescope reason via label
- [ ] Unit tests cover: single reason, multiple reasons, idempotent repeat, re-admission cleanup, label filter query

## Blocked-by

None. Complements OPER-193 (admission auto-reheal) — the two together close the ticket-flow-observability loop for the filer.

## Size / Band

S / assurance (append-to-body + label add on rescope; matches existing admission-rejected pattern).

## Discovered by

Session 2026.09.13 status check found ten NEEDS_RESCOPE tickets with no visible reason. Diagnostic cost: 15 minutes reading source to determine `missing_verify_cmd` was the root cause. Would have been zero if the trailer existed.

Attachments

Loading attachments…

Comments

Loading comments…