Skip to content

Repeat-finding seam escalation on round 1 (makes step-back reachable) + invariant-removal guard memories #280

Description

@randomm

Context

Verified findings (2026-07-26):

  1. Repeat findings are patched, never questioned. vipune accumulated 4 diverged copies of its SQL filter builder, 3 row mappers (two missing a validation the third performs), and 3 limit validations with 3 error types (all counts verified in /Users/janni/projects/vipune). A lens fix-loop patches each instance; the missing SEAM is never surfaced. Today nothing escalates same-shaped findings to a design-level question before the round-3 cap.

  2. BONUS BUG — step-back is unreachable. Verification found that NOTHING in the driver ever emits nextStep: "step-back": the 3-round lens cap routes to handoff (nextStep, work-driver.ts :473-475), and no other path emits it. runStepBack (:2735) — the SDD spec-gap analysis machinery from PR12 — is dead code in the live pipeline. This issue gives it its first real trigger; expect first-live-exercise bugs and test accordingly.

  3. Invariants die without record. When a constraint is removed (type widening — see the invariant-scan issue), the fact that it was load-bearing vanishes with it. The memory substrate (vipune) exists but is not triggered by the events that matter.

This work must ship as its own separate PR, independent of any other open issue. It reuses extension/src/invariant-scan.ts from the verify-full/widening-scan issue, so land that one first.

What to build

A. detectRepeatSeam(findings) (pure, ~35 LOC). Over one lens round's findings JSON: cluster by (lens, normalised title — lowercase, strip file-specific tokens); a cluster spanning ≥3 DISTINCT file paths = a missing-seam signal, not N defects.

B. Round-1 escalation routing. In runLens's ISSUES_FOUND handling: when detectRepeatSeam fires on round 1, instead of routing to lens-fix, emit step-back-triggered + cap-hit { cap: "repeat-finding-seam", nextStep: "step-back" } so the existing nextStep cap routing sends the cycle to runStepBack (the @explore SDD analysis → handoff with the proposed revision). Add the "repeat-finding-seam" cap literal to the workflow-state union (additive) + an explainCap branch explaining the operator-facing WHY ("same finding shape across N files is a missing seam — patching each instance would entrench the duplication; explore analysed which spec element under-specifies the shared behaviour").

  • Round ≥2 behaviour unchanged (fix loop continues; the cap still fires only at round 3).

C. Invariant-removal guard memories. When the widening scan (invariant-scan.ts) fires during lens prep: write ONE vipune guard memory per (file, symbol) via an injectable vipuneWriteFn (default: shell out to vipune add … --memory-type guard; follow the injection-seam pattern of verifyExecFn): "constraint → removed in issue #N; verify what now guarantees the old invariant before writing code that exploits the widened type." Emit a plumb-report noting the write. Dedupe: no duplicate write for the same (file, symbol) within a cycle.

  • One prompt line in inlineExplorePrompt: instruct explore to vipune search for guard memories touching the files it is about to analyse.

Env: PI_ENSEMBLE_SEAM_ESCALATION=0, PI_ENSEMBLE_INVARIANT_MEMORY=0 (default ON).

Acceptance criteria

  • detectRepeatSeam unit fixtures: 3 same-lens/same-title findings across 3 files → fires; across 2 files → does not; 3 different titles → does not; title normalisation tolerates file-specific tokens.
  • End-to-end (injected lensReviewFn returning a vipune-V4-shaped duplication cluster): round-1 cycle routes to step-back — step-back-triggered + cap-hit repeat-finding-seam events present, runStepBack's @explore dispatch fires (injected dispatchFn), handoff carries the SDD analysis. THIS IS STEP-BACK'S FIRST LIVE PATH — assert the full routing chain, not just the events.
  • Round-2 ISSUES_FOUND with the same cluster does NOT re-escalate (fix loop proceeds as today).
  • Widening-scan hit → exactly one guard memory write per (file, symbol) via injected vipuneWriteFn; content includes before/after and the issue number; plumb-report present; second hit same symbol → no duplicate write.
  • explainCap("repeat-finding-seam", …) produces the operator-facing explanation.
  • Both env kill-switches restore today's behaviour exactly.
  • tsc + biome + all 26 offline smokes green; schema changes additive only.
  • Docs: troubleshooting "Seam escalation — what repeat-finding-seam means" section; AGENTS.md §7 sentence noting step-back is now reachable.

Out of scope

  • Changing the round-3 cap or wall-clock caps.
  • Escalation triggers other than the ≥3-distinct-files rule (keep v1 deterministic and singular).
  • Automatic dedup/refactoring of the found seam (the escalation SURFACES it; the operator/spec revision decides).

Test plan

Offline: pure-fn fixtures + full-driver routing tests via injected lensReviewFn/dispatchFn/vipuneWriteFn in test-work-driver.ts. Live (nessie): seed a diff duplicating one helper across 3 files, run /work N, confirm round-1 step-back handoff names the seam instead of three per-file findings surviving to merge.


Addendum (2026-07-27 nessie post-mortem) — upstream evidence: plan-step under-decomposition

Fresh evidence for the seam problem this issue targets, from nessie #604: an 8.6-second plan dispatch collapsed the issue's 6 enumerated findings into ONE workstream declared with ONE path (src/cron/wiki_state.rs). The developer then sprawled to 11 files (26 edits on out-of-scope sweep_tools.rs alone), thrashed through ~17 consecutive failed cargo builds, and burned 10.5M tokens / 37 min before dying on a provider stall. The tree never compiled.

While the scope-fanout gate (#285) catches the sprawl downstream, the UPSTREAM failure is plan quality on multi-finding issues. In scope for this issue (or its immediate follow-up):

  • When the issue body enumerates N ≥ 3 discrete findings (numbered/checkbox list) and the plan returns 1 workstream, the plan prompt should be required to either (a) map each finding to a workstream explicitly, or (b) declare which findings are deferred out of this cycle. A plan that silently absorbs 6 findings into 1 path is the same missing-seam smell this issue's ≥3-files trigger detects at review time — catching it at plan time is one prompt-layer instruction + one deterministic count check.

Additional acceptance criteria

  • Plan prompt (inlinePlanPrompt) instructs per-finding workstream mapping for multi-finding issues.
  • Deterministic guard: issue body with ≥3 enumerated findings + single-workstream plan + single declared path → plumb-report warning naming the unmapped findings (warn, not halt, in v1).

Addendum (2026-07-27) — interaction with the parallel-work series (#287#290)

The plan-quality addendum (lines 48-60) is absorbed and superseded by the plan-decomposition issue (PR-C), which upgrades "warn, not halt" to one corrective re-dispatch and satisfies both addendum acceptance criteria — strike them here. The main body (repeat-finding seam escalation, dead step-back) is untouched and remains this issue's scope.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions