* fix(#2868): resume a phase stranded between its last plan and verification discover_and_group_plans exited unconditionally once every plan was filtered out, conflating "no plan work left" with "phase fully done". Those differ once a run can be interrupted between the final wave's SUMMARY and the verify step -- most often by a checkpoint plan that is retired but still writes a SUMMARY. The result was a phase that looked healthy from every index yet had no VERIFICATION.md, and whose recommended recovery command provably no-opped, because the only step that produces the artifact sits ten steps past that exit. The exit is now conditional. When the verification report is genuinely missing and no filter is active, the run reports the situation by name and continues at the tail gates instead of stopping. Two guards keep the normal paths untouched: - A filtered run (--gaps-only, or an explicit wave) finding nothing left in its own slice says nothing about whether the phase as a whole is done, so it exits exactly as before. Without this, --wave 1 on a finished first wave would jump to verification with later waves still outstanding. - A phase that already has its report exits as before too. The recovered path deliberately keeps the code-review and regression gates. The manual workaround this replaces skipped both, and that gap is the reason a real route exists rather than telling users to spawn the verifier by hand. Also acknowledges the emitted growth of the workflow file. As with #2830 it is appended to the fragment that already owns that path, since the linter hard-fails when two acknowledgment sources name the same one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(#2868): never treat a blocked-and-incomplete phase as finished Three findings from adversarial review, all fixed. BLOCKER -- the trigger conflated two different zero-runnable states. This step now has two skip rules: has_summary (the #2868 target) and, from #2830, a skip for plans whose blocked_by is non-empty. "All filtered" was therefore reachable with plans that never ran: plan A halts and is summarized, plan B is blocked by A and has no summary. The resume path fired, announced "All N plans are summarized" -- false -- skipped the wave steps so B was never dispatched, and jumped to the gates. B was silently abandoned, which is the same class of disappearance #2830 exists to prevent, reintroduced one layer up. The decision is now an explicit ordered three-way: a filtered run exits unchanged; any blocked-plan skip reports the phase as stuck on a halt and exits, routing to resolving the halt rather than to verification; only an all-summarized, unfiltered phase with a missing report resumes. MAJOR -- RESUME_TAIL_ONLY was set and never read anywhere in the workflow or its step fragments. Dead state implying enforcement that did not exist. Removed; the imperative at the decision point is what actually carries the control flow, so it now says so plainly. MAJOR -- the resume path skipped aggregate_results, which is the only step that runs the secure-phase threats-open gate. A phase with open threats would have advanced with no warning where a normal run always shows one. The path now enters at aggregate_results, verified to read only on-disk phase artifacts and independent queries, so it tolerates having executed no plans this run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(#2868): backfill changeset pr number Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
38 KiB
38 KiB