Skip to content

fix: one bounded recovery review pass after a receipt-invalidating write (2.6.3) - #758

Open
apackeer wants to merge 1 commit into
v2from
fix/issue-755-review-budget-recovery
Open

fix: one bounded recovery review pass after a receipt-invalidating write (2.6.3)#758
apackeer wants to merge 1 commit into
v2from
fix/issue-755-review-budget-recovery

Conversation

@apackeer

Copy link
Copy Markdown
Contributor

Problem

Fixes #755. Refs #742.

A stage that declares a reviewer deadlocks when a declared produces[] artifact is written after the terminal review receipt:

  • freshReviewReceipts invalidates the receipt (fingerprint mismatch, or a later ARTIFACT_CREATED/ARTIFACT_UPDATED row), so completion refuses: no fresh REVIEW_COMPLETED is recorded... Invoke the reviewer and record the verdict.
  • The review budget scan (reviewAttemptSummary) never resets on that invalidation, so a new request refuses: review request 2 exceeds this stage's review budget (1)... do not re-invoke the reviewer; quote its findings at the approval gate.
  • --retry-pending and --verdict also refuse (the pair was already matched).

The two guards instruct each other's forbidden action - a circular loop with no tool path forward. #698/#702 added the review-freeze PreToolUse hook to prevent the invalidating write, but Kiro IDE cannot run it (IDE hook payloads carry empty tool_input, so a targeted pre-write freeze has no path to inspect) - it is the only harness without that backstop, which is where #755 was hit. The gate-revision backstop does not cover this either: it requires the write to land after gate-open and after a HUMAN_TURN.

Change

One bounded, deterministic recovery pass, plus refusal messages that name the real way out:

  • core/tools/aidlc-lib.ts - freshReviewReceipts now reports stageStale / unitStale: a terminal receipt existed in the current attempt window but was invalidated. Set only for terminal verdicts with a usable recorded fingerprint (or an event-clear of an actually-set verdict); cleared when a later terminal receipt lands; floored by the same attempt boundaries as everything else.
  • core/tools/aidlc-log.ts - the request path permits exactly ONE recovery REVIEW_REQUESTED at ordinal budget+1 when the scope is stale, nothing is pending, and the budget was consumed by real passes (requestCount > budget proves the recovery slot is already spent, so no extra bookkeeping). The emitted row carries Recovery: stale-receipt. A second invalidation is refused with guidance to stop editing past receipts and present the situation at the human gate; the --retry-pending refusal in the stale state now names the recovery command with the correct ordinal. --single rows are excluded (invisible to the fresh-receipt scan by design).
  • core/tools/aidlc-state.ts - completion refusals distinguish an invalidated receipt from a review that never ran, per-unit refusals list invalidated vs never-reviewed units, both with the recovery instruction and the human Request Changes reset.
  • Prose/doc alignment in stage-protocol section 12a and docs/reference/04-stage-protocol.md (the budget-enforcement paragraph gains the bounded exception).

Nothing is weakened for intact receipts: the ordinal contract, the advisory single pass, the adversarial cap, and every existing refusal string are unchanged when no terminal receipt was invalidated. Wave review_state plumbing is deliberately untouched; a stale per-unit receipt still surfaces as outstanding/iteration 1 and the out-of-sequence refusal self-corrects the ordinal.

Tests

  • NEW tests/unit/t291-review-receipt-recovery.test.ts - end-to-end reproduction of the [Bug]: Workflow stucks at "Intent-capture" stage (scope Enterprise) #755 wedge through the real CLIs: request+verdict, invalidating write, stale-specific completion refusal, recovery pair, successful approval, and the bounded second invalidation.
  • tests/unit/t271-review-iteration-ceiling.test.ts - recovery cases: advisory wedge (retry-pending guidance, out-of-sequence self-correction, recovery row + JSON marker, retry during pending recovery, recovery-spent refusal) and the adversarial per-unit variant; the existing intact-receipt budget assertions are unchanged.
  • tests/unit/t115.test.ts, tests/unit/t263-reviewer-terminal-ordering.test.ts - assertions for the write-after-receipt scenario moved to the new stale-specific message.

Verification

  • bun run typecheck, bun run check (dist parity + lint), bun scripts/package.ts --check: clean.
  • Full smoke+unit+integration tier: 321 files, 319 pass. The 2 reds are pre-existing/environmental, not from this branch: t238-user-stories-mob.sdk (known red on clean v2 at 74a51a1) and t145-packaging-parity (box-level bun module-resolution artifact: the identical Cannot find package 'smol-toml' failure reproduces with the pristine v2 emit.ts copied to /tmp; every input to that test is byte-identical to v2 on this branch).
  • Post-rebase (onto 4569754, re-bumped 2.6.2 -> 2.6.3): targeted t66/t68/t115/t205/t263/t264/t271/t278/t291 all pass.

Field workaround (pre-fix dists)

A wedged session recovers today by choosing Request Changes at the approval gate: the GATE_REJECTED resets both the receipt-freshness floor and the review budget, after which the revision path re-runs the section 12a review.

…ite (2.6.3)

A terminal review receipt voided by a later write to a declared produces[]
artifact used to deadlock the stage: completion demanded a fresh receipt while
the review budget refused another pass, and the two refusals pointed at each
other. The engine now exposes the invalidated-receipt state from the fresh-
receipt scan, permits exactly one recovery REVIEW_REQUESTED at the next
ordinal when the budget was consumed and nothing is pending, and rewrites the
completion, retry-pending, and budget refusals to name the actual recovery
path (recovery pass first, human Request Changes at the gate once it is
spent). Intact receipts keep their existing budgets; ordinary over-budget
requests are still refused.

Wedge reported from Kiro IDE (#755), where the review-freeze hook cannot run,
and from Copilot (#742). Regression coverage: t271 recovery cases and the new
t291 end-to-end reproduction; t115/t263 assertions moved to the stale-specific
message.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant