Skip to content

spec(dag): unblock the REJECT checkpoint — a rejected review completes the workflow (A1) #294

Description

@LeXwDeX

Ticket 2/4 of the harness workstream (see #293). Primary source: docs/harness-review-2026-08-16.md (sections 一, A-1, A-3). No upstream blockers; independent of #293.

Problem Statement

When a review checkpoint returns REJECT, the harness terminalizes the workflow as failed — and failed workflows are immutable. That makes two of the four options in the Verdict Disposal Contract physically unreachable: additive extend (reopen requires status completed) and pause→replan→resume (replan rejects terminal workflows). The only surviving disposal is "start a new workflow", which discards accumulated evidence. This is why post-checkpoint replan has never been observed: it is structurally impossible, not a model-luck story.

Root cause chain (all evidence in the primary source):

  1. The review node with REJECT settles as a completed node (correct per policy: "a gate that successfully returns REVISE or REJECT is a completed node, not a failed node").
  2. The scheduling loop then detects unresolved review outcomes and fails the whole workflow.
  3. Reopen-by-extend is explicitly designed for the REJECT shape — its rationale comment names skipped dependents as non-executed so the graph "effectively ended at the checkpoint" — but only accepts completed workflows.

The harness contradicts its own policy: node-level rule says REJECT = completed; workflow-level behaviour says REJECT = failed.

Solution

Restore the designed-but-unreachable path with the smallest guard relocation:

  1. The scheduling loop stops failing a workflow over unresolved review outcomes; the graph completes naturally at the REJECT checkpoint (all remaining nodes conditional-skipped → checkpoint is a reporting leaf with zero executed dependents).
  2. The unresolved-review guard moves from the shared completion function to the tool-layer explicit complete action, where it belongs: an agent may not shortcut an unresolved review into "done", but a naturally reached checkpoint may complete so the parent can dispose of it.
  3. Wake already carries verdict + findings + required_actions (review blocks report to parent by default), so loudness is preserved through the checkpoint wake, not the terminal status string.
  4. Workflow status output surfaces outstanding review verdicts explicitly, instead of only in a failure-reason string.

User Stories

  1. As a parent orchestrator, I want a REJECT verdict to leave the workflow completed and reopenable, so that I can add a bounded correction wave without starting over.
  2. As a parent orchestrator, I want to be woken at the review checkpoint carrying the findings, so that I can act in the same turn as mandated by the Verdict Disposal Contract.
  3. As a parent orchestrator, I want status to list outstanding review verdicts, so that I never misread a completed workflow as an accepted one.
  4. As a maintainer, I want an explicit "complete while review unresolved" shortcut to keep failing, so that agents cannot launder a REJECT into done.
  5. As a maintainer, I want the node-level and workflow-level treatment of REJECT to agree, so that the harness no longer contradicts its stated policy.

Implementation Decisions

  1. Guard relocation, not removal: the unresolved-review check moves from the shared completion path to the explicit completion tool action; natural completion is unguarded, agent shortcuts stay guarded.
  2. The loop's terminal failure over unresolved reviews is deleted; completion at a conditional-skipped tail is the intended terminal shape.
  3. Review settlement, fingerprint echo, and correction-review lifecycle remain untouched — the review surface is invariant; only workflow-level disposal changes.
  4. Status reporting gains explicit unresolved-verdict surfacing (structured, not parsed from a reason string).
  5. Tests that pinned the old failing behaviour are identified and rewritten; review-lifecycle tests remain untouched and green.

Testing Decisions

  • The seam is the durable lifecycle boundary: build a graph with a review gate that settles REJECT, assert workflow status becomes completed (not failed), then assert additive reopen-extend with new node IDs is accepted, and assert the explicit-complete shortcut with unresolved review is still rejected.
  • Prior art: existing DAG lifecycle and review-audit regression tests; the reopen/extend path already has its own test surface around the reporting-leaf exception.

Out of Scope

  • Pause-on-REJECT with the orchestrator watchdog (variant A4): explicitly deferred until this ticket produces operational feel — then re-decide.
  • The verdict-disposal classifier wording: ticket 4/4, blocked by this ticket.
  • Config repository wording changes: ticket 3/4.

Further Notes

  • After this lands, the REJECT loop becomes observable for the first time — capture the experience before deciding on A4 (pause + watchdog).

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified and ready for an agent

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions