Skip to content

fix(work-driver): a mis-parsed park reason can make the driver BUILD what the resolver said was already done #404

Description

@randomm

The defect

A resolver that parks an issue can have its work built anyway, with a fabricated justification attached to the PR.

Reproduced by execution against main (59d6075):

resolver emits:   ### PARK-REASON
                  already-implemented

PARK-REASON: regex (work-driver-intent.ts:138) requires a literal colon
   → no match
   → parkReason defaults to "underspecified"        (work-driver-intent.ts:159)
   → #397's override sees "underspecified" + a complete spec
        (work-driver-intent.ts:281)
   → verdict becomes proceed-with-assumptions
   → the driver BUILDS the already-implemented thing
   → and appends OVERRIDE_ASSUMPTION to the PR body, which reads:
       "its own spec named an intent, deliverables, acceptance criteria
        and confirmed evidence with no blocking open questions.
        The driver proceeded on the spec rather than the label."

That last part is what makes this worse than a plain crash: the override manufactures a confident, plausible rationale for doing precisely the wrong work, and puts it where a reviewer will read it as reasoning.

It nearly happened

Live run on randomm/nessie #650. The resolver emitted exactly that heading form with already-implemented, and its rationale said "There is no residual work on #650" — the issue is fixed by #657. .pi/work-state/650/spec.txt records parkReason: "underspecified".

It escaped only because that resolver wrote its deliverables as prose (**None for this issue.**) rather than bullets, so bullets() returned [] and specIsComplete was false. One bullet and /work would have rebuilt #657's work as a duplicate PR.

Root cause

"underspecified" is doing double duty at work-driver-intent.ts:159: it is both the null value (the token did not parse) and a real diagnosis (the issue genuinely says too little). Nothing downstream can tell them apart, and #397's override treats the null as the diagnosis.

The comment at :157-158 rationalises the default as "an unreadable reply is itself underspecified" — true of a garbled reply, false here, where the reply was well-formed and specific and only the delimiter drifted.

What to build

A. A defaulted reason must never license the override. Record whether parkReason was parsed or synthesised — a sibling field (parkReasonSource: "parsed" | "default"), or make parkReason optional and track the provenance separately — and gate the reconcileVerdict override at :281 on parsed. This is the fix; everything else is mitigation.

B. Tolerate the heading form. ### PARK-REASON ⏎ value, and the same for INTENT-VERDICT, since a real resolver emitted it. Keep the existing inline form working.

C. Say so when a value is synthesised. A trace at minimum; ideally the state file should show that the reason was invented rather than read, so this is diagnosable next time without executing the parser by hand.

Acceptance criteria

Out of scope

The wider policy-resolution refactor (replacing prose regexes with an LLM-judged, citation-verified seam) and the protected-doctrine boundary. Both are follow-ups; this issue is the bleeding.

This work must ship as its own separate PR, independent of any other open issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions