OPER-174
writeSet-path validation at admission — reject phantom paths that can never ship
Band
SMALL
Suggested
Justin Cooke
Body
--- feature_id: FEAT-studio-planning-validation band: SMALL target_repo: operant-health/operant-studio --- ## Context Reconciliation of OPER-146 (2026.09.09) surfaced a new failure class: **Phantom-writeSet**. The ticket declared four writeSet paths at admission time. Three matched real paths in the monorepo and shipped correctly across PRs #436/#437/#438/#439/#463. The fourth — `packages/agent-runtime/src/ledger-client.ts` — did not exist anywhere in the repo. There is no `packages/agent-runtime/` directory (the actual package is `packages/ledger-client/src/client.ts`, a different filename in a different location). Because the phantom path can never be satisfied by any PR, any coverage-based DONE check on this ticket has a permanent gap. This is silent: Ledger's Premature-DONE reconciler (OPER-448) sees 3-of-4 coverage and either flips DONE early (its current bug) or, once fixed, refuses to flip DONE indefinitely. Both outcomes are wrong for the same root reason: the declared path was fabricated at planning time and never validated against the actual repo tree. This is distinct from Premature-DONE (partial coverage of real paths); it is admission-time contract corruption. Fixing OPER-448 does not detect it, and no downstream gate can distinguish "shipped path missing coverage" from "phantom path that could never ship." Concretely observed on OPER-146: - declared: `packages/agent-runtime/src/ledger-client.ts` - actual monorepo has: `packages/ledger-client/src/client.ts`, `packages/ledger-client/src/index.ts`, `packages/ledger-client/src/types.ts` - no PR in the OPER-146 stack touched any file in `packages/ledger-client/` Related evidence: reconciliation + phantom-writeSet correction comments on OPER-146 (Ledger cuid cmttws4zg0084jo04jevjmcxx). ## Acceptance Criteria Gate specification: 1. **Validation trigger.** The admission workflow MUST validate every declared writeSet path at ticket admission time, before returning BACKLOG status. Applies to both initial POST and PATCH events that mutate the writeSets array. 2. **Path resolution rules.** A writeSet path is valid if it satisfies one of: - **Exists in main.** The exact path resolves to a file (not directory) via the GitHub Contents API against the target_repo's default branch. - **Explicit new-file flag.** The path is prefixed with `new:` (e.g. `new:apps/web/src/lib/new-feature.ts`). This declares the file will be created by the ticket's PRs and skips existence checks. Directory-level glob `new:apps/web/src/lib/**` allowed for scaffolding tickets. - **Explicit new-directory flag.** The path ends in `/**` and the parent directory exists in main (e.g. `packages/ledger-client/**` valid if `packages/ledger-client/` exists). 3. **Rejection semantics.** If any declared writeSet path fails validation, admission MUST reject with status `admission-rejected` and label the ticket `phantom-writeset`. The rejection response body MUST list every failing path with the exact validation error (`path-not-found-in-main`, `parent-dir-missing`, `expected-file-got-directory`, etc.). 4. **Correction path.** A rejected ticket can be re-admitted after PATCH corrects the writeSets. The `phantom-writeset` label MUST be cleared on successful re-admission (the same label-clearing sweep OPER-168 handles for `admission-rejected` — extend that sweep to cover this label too). 5. **Escape hatch.** For tickets whose paths depend on runtime tree inspection (rare), a top-level frontmatter flag `skip_writeset_validation: true` MUST bypass validation entirely. Requires justification comment on the ticket; the gate MUST log usage as a metric so the fleet can audit whether the escape is over-used. 6. **Observability.** Every admission run MUST log: (a) each declared writeSet path, (b) whether it resolved, (c) which validation rule matched or failed. Feed into existing admission observability parser. 7. **Test.** Add integration tests covering: - happy path (all paths exist) - phantom leaf (path with real parent but wrong filename) - phantom directory (path whose parent directory does not exist) - explicit `new:` prefix (accepted without existence check) - `**` glob with real parent (accepted) - `**` glob with phantom parent (rejected) - `skip_writeset_validation: true` (accepted and logged) ## Non-goals - Does NOT validate that the paths are *the right* paths for the work — only that they exist or are declared as new - Does NOT check writeSets against target_repo trees other than the default branch - Does NOT retroactively re-validate historical tickets (a separate cleanup pass, if wanted, is out of scope here) ## Effort Single Ledger ticket family. Depends on OPER-168's label-clearing sweep landing first for the label-hygiene half of the fix; the validation logic itself can ship independently and start rejecting new tickets immediately.
Attachments
Loading attachments…
Comments
Loading comments…