fix(qa-gate): resolve per-plan VERIFICATION.md for single-plan phases#656
fix(qa-gate): resolve per-plan VERIFICATION.md for single-plan phases#656halindrome wants to merge 6 commits into
Conversation
A passing single-plan phase whose QA artifact was written with the
per-plan name `{NN}-{MM}-VERIFICATION.md` was read by the gate as
missing: `resolve-verification-path.sh` only resolved the phase-level
`{NN}-VERIFICATION.md`, the brownfield plain `VERIFICATION.md`, or
per-wave `{NN}-VERIFICATION-wave*.md` — never the per-plan name. The
gate then reported `qa_gate_writer=missing` / `qa_gate_result=missing`
and forced a spurious `QA_RERUN_REQUIRED` on a phase that already
passed.
Teach `phase_level_path()` to adopt a per-plan verification as a
last-resort fallback when exactly one `{NN}-{MM}-VERIFICATION.md`
exists and no phase-level, plain, or wave-level artifact is present.
Multiple per-plan files are ambiguous (the integration artifact is
authoritative) and fall through to the canonical phase-level name, so
multi-plan phases are unchanged. Fixing the shared resolver fixes every
consumer (`phase`/`current`/`authoritative`) and the QA gate in one
place.
Tests:
- resolve-verification-path.bats: single per-plan adopted; multiple
per-plan ambiguous -> phase-level; on-disk phase-level wins; wave
files win.
- qa-result-gate.bats: single-plan phase with only a per-plan
VERIFICATION.md routes PROCEED_TO_UAT.
The secondary `git mv -k` no-op on untracked artifacts noted in the
issue has no locus in the current tree (no `git mv` anywhere); it was a
live manual-recovery action, not committed code.
Closes swt-labs#653.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses the findings from the round-1 QA review (read-only adversarial
review of the per-plan VERIFICATION.md fix):
- The per-plan fallback glob `[0-9][0-9]` missed 3-digit plan numbers
(`{NN}-100-VERIFICATION.md` and beyond); the writer formats plan
numbers with `printf %02d`, so the 100th+ plan yields 3 digits and the
artifact was read as missing — the same swt-labs#653 symptom at a different
plan-number range. Widened to `[0-9][0-9]*` and replaced the
`ls -1 ... | grep -c .` parse with a `nullglob` array (exact entry
count, no `ls` parsing, no newline-in-filename ambiguity).
- Added coverage in resolve-verification-path.bats: the zero-artifact
fallthrough regression guard and a 3-digit per-plan adoption test.
- Added the missing trailing newline to resolve-verification-path.bats.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Round 2 QA review flagged the round-1 per-plan glob `[0-9][0-9]*` as
greedy: the trailing `*` also matched non-canonical names like
`{NN}-01a-VERIFICATION.md` and `{NN}-01-extra-VERIFICATION.md`, which
would then be adopted as the sole per-plan artifact. No writer in the
codebase emits such names (MM is always `printf %02d`, no suffix), so it
was latent rather than reachable — but loose.
Tighten it: keep the nullglob array as a cheap prefilter, then re-filter
with a strict regex `^{prefix}-[0-9][0-9]+-VERIFICATION\.md$` (>=2-digit,
all-numeric MM) so over-matches are rejected before the single-match
decision. 2- and 3-digit plan numbers are still adopted; non-numeric or
extra-segment names fall through to the canonical phase-level name.
Added a negative test asserting non-canonical per-plan names are not
adopted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Round 3 QA review came back fully clean — no findings at any severity. Verified: the round-2 regex re-filter closes the greedy-glob over-match; `phase_prefix` is provably digit-only (both derivation paths go through `printf %02d` / a `^[0-9]+$` guard), so its interpolation into the `[[ =~ ]]` ERE is injection-safe; the >=2-digit MM requirement, `set -euo pipefail` behavior, nullglob save/restore, basename handling, and precedence/ambiguity semantics all hold. shellcheck clean; both bats suites green (21 + 184, zero failures). Empty evidence commit per the QA review process (clean round). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
d414a7a to
97c29a4
Compare
|
QA Review — Round 1 (against base fix Addressed in |
|
QA Review — Round 2 (fresh review against Addressed in |
|
QA Review — Round 3 (fresh review against Clean round → empty evidence commit |
What
Teach
resolve-verification-path.shto resolve a per-plan verification artifact ({NN}-{MM}-VERIFICATION.md) as a last-resort fallback when a phase has exactly one such file and no phase-level ({NN}-VERIFICATION.md), plain (VERIFICATION.md), or per-wave ({NN}-VERIFICATION-wave*.md) artifact exists.Why
Closes #653. On a passing single-plan phase, the verification writer emits the per-plan name
{NN}-{MM}-VERIFICATION.md, but the QA gate's resolver only knew about the phase-level, plain, and wave names. The gate therefore readqa_gate_writer=missing/qa_gate_result=missingand forced a spuriousQA_RERUN_REQUIREDon a phase that had already passed — an extra QA cycle (cost + time) recoverable only by manually renaming the artifact.Fixing the shared resolver fixes every consumer (
phase/current/authoritative) and the QA gate in one place — a root-cause fix rather than patching the gate alone.The issue's secondary
git mv -kbug has no locus in the committed tree (there is nogit mvanywhere inscripts/,hooks/,commands/,references/, ortemplates/) — it was a one-off manual-recovery action during the live incident, not committed workflow code. Verified via repo-wide grep.How
phase_level_path()adopts a single per-plan{NN}-{MM}-VERIFICATION.mdonly when no higher-precedence artifact is present. Multiple per-plan files are ambiguous (the integration artifact is authoritative) and fall through to the canonical phase-level name, so multi-plan phases are unchanged.Testing
tests/resolve-verification-path.bats— single per-plan adopted; multiple per-plan → phase-level (ambiguous); on-disk phase-level wins; wave files win. EXIT=0, 0 failures.tests/qa-result-gate.bats— single-plan phase with only a per-planVERIFICATION.mdroutesPROCEED_TO_UAT. EXIT=0, 0 failures.bash -n+shellcheck -S warningonscripts/resolve-verification-path.sh— clean.testing/run-all.sh: zeronot okbats and zeroFAIL:contract/lint across the suite. (Theagent-shutdown-integration.batsintegration test passes cleanly in isolation — EXIT=0, 26 ok — but can hang under heavy local parallelism due to itssleep-based crashed-lock-holder simulation; unrelated to this change.)🤖 Generated with Claude Code