OPER-155
Support `target_repo:` frontmatter in team-repo-routing (retire noop-PR convention) — D-27 next action 1
Band
LARGE
Suggested
Justin Cooke
Body
feature_id: FEAT-studio-dispatcher decision_memo: docs/pdt/2026.09.09-d27-multi-repo-team-routing.md filing_template_version: 1 blocked_by: OPER-148 ## Context PDT memo D-27 (`docs/pdt/2026.09.09-d27-multi-repo-team-routing.md`) approved Option A: add optional `target_repo:` frontmatter to Ledger tickets and route dispatch to the declared repo when present. Retires the noop-bookkeeping-PR convention that D-26 (`merged-pr-precheck.ts` provenance guard, OPER-148) is patching around. **Current state.** `packages/dispatcher/src/team-repo-routing.ts:23-27` hardcodes exactly one target repo per team. All dispatch call sites resolve via `targetRepoForTeam(teamId)` -- 8 sites in `packages/dispatcher`, 3 in `apps/web`. Cross-repo work (OPER-* tickets whose code lands in `portico`) is bridged by opening a noop bookkeeping PR on the team's default repo, which is the exact seam D-26 exposes as a premature-DONE bug source. **Desired state.** A ticket may optionally declare `target_repo: portico` (or any value on a team-scoped allowlist) in its body frontmatter. Dispatch routes to that repo; admission rejects unknown values; the D-26 provenance guard becomes tighter by also checking the merged PR's repo matches the declared `target_repo:`. **Ren's DISSENT mitigation** (from D-27): admission validates against a per-team allowlist; typo -> `unknown_target_repo` rejection code with a specific reason string. **Jordan's DISSENT mitigation**: OPER-146 (D-25 helper) is being amended in parallel to accept `target_repo?` as a typed parameter so agents can't silently regress. **Marisol's DISSENT mitigation**: admission test proves a PHI-band ticket declaring `target_repo: portico` still requires a PHI-band reviewer -- routing is orthogonal to review policy. Two-way door per D-27: if it goes wrong, delete the parser call and ignore the frontmatter -- everything reverts to today's behavior. Do NOT retire the noop-PR fallback in this ticket. That's a separate follow-up after OPER-148 provenance guard reaches Beta AND at least one Operant-team ticket has round-tripped through target_repo-declared dispatch + provenance-verified DONE. ## Acceptance Criteria - New helper `taskTargetRepo(task, teamId)` in `packages/dispatcher/src/team-repo-routing.ts` returns the `target_repo:` frontmatter override when parseable AND on the team's allowlist; otherwise falls back to the team default from `TEAM_TARGET_REPO`. - New constant `TEAM_TARGET_REPO_ALLOWLIST: Record<string, ReadonlyArray<string>>` maps each teamId to the set of repos it is permitted to dispatch to. Operant team's allowlist includes at least `operant-studio` and `portico`; Portico team's allowlist includes at least `portico`; Roster team's allowlist includes at least `roster`. The team-default repo is always a member of its own allowlist. - All 11 call sites of `targetRepoForTeam` migrated to `taskTargetRepo(task, teamId)` (or route through a helper that resolves via the task). Enumerated in writeSets: `github-dispatch.ts`, `backend.ts:784`, `tick.ts:185`, `workflow-reaper.ts:275/480/655`, `workflow-reaper-streak.ts:242`, `workflow-reaper-closed-park-notify.ts:24`, `merged-pr-precheck.ts` (multiple), `sensor-status-advanced-without-pr.ts:239`, `sensor-clean-pr-not-merging.ts:294`, `ticket-intake-dedup.ts:517`. - `apps/web/src/lib/ticket-admission.ts` validates any parsed `target_repo:` value against the per-team allowlist. Unknown value -> admission rejection with code `unknown_target_repo` and a reason string that names both the offered value and the allowlist for the team. Absent frontmatter is NOT a rejection -- team default is fine. - `packages/dispatcher/src/merged-pr-precheck.ts` (already being provenance-guarded by OPER-148) additionally checks that the merged PR's repo matches the ticket's declared `target_repo:` when present. Mismatch -> no DONE flip, same fail-safe as OPER-148. - Unit tests cover: (a) Operant-team ticket with `target_repo: portico` dispatches to portico; (b) Operant-team ticket without frontmatter dispatches to operant-studio (default); (c) Operant-team ticket with `target_repo: porticoo` (typo) rejected at admission with `unknown_target_repo`; (d) Operant-team ticket with `target_repo: piperdx` rejected (not on allowlist); (e) merged-PR precheck: matching target_repo + matching DispatchRun branch -> DONE flip; (f) merged-PR precheck: PR is on the correct repo but does not match DispatchRun branch -> no flip (OPER-148 provenance holds); (g) merged-PR precheck: PR merged on the wrong repo (target_repo declared portico, merged PR on operant-studio) -> no flip; (h) admission test: PHI-band ticket declaring `target_repo: portico` still requires a PHI-band reviewer (Marisol dissent mitigation). - Documentation: append a section to `OPERANT-BOILERPLATE.md` in `operant-health/operant-studio` describing the `target_repo:` frontmatter contract, the per-team allowlist, and the interaction with the D-25 filing helper. ## Non-goals - Retiring the noop-PR-on-DONE-fallback convention -- explicitly deferred per D-27 next action 3 (separate ticket, dependent on this + OPER-148 reaching Beta). - Schema change to store target_repo as a Task column -- Kira's dissent explicitly declined this; body-frontmatter parse is fine at v0.1. - Multi-repo dispatch for a single ticket (e.g. "do half on studio, half on portico") -- one target_repo per ticket; work that spans two repos must be two tickets, chained via `blocked_by:`. ## Related - Sequenced after OPER-148 (D-26 provenance guard) merges so the mismatch-checks in merged-pr-precheck.ts are additive on top of a working provenance base. If OPER-148 is still in flight when this ticket claims, wait. - Sibling of OPER-146 amendment (D-27 next action 2) -- helper gains `target_repo?` typed parameter so agents can't route around admission. - Unblocks the noop-PR-retirement ticket (D-27 next action 3, not yet filed). <!-- readmit-request 2026.09.09 -->
Attachments
Loading attachments…
Comments
Loading comments…