Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions scripts/resolve-verification-path.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,38 @@ phase_level_path() {
fi
fi

# Per-plan single-plan-phase fallback (issue #653): when no phase-level, plain,
# or wave-level artifact exists on disk but the writer produced exactly one
# per-plan {NN}-{MM}-VERIFICATION.md (the single-plan-phase case), adopt it so
# the QA gate does not see the phase as unverified and force a spurious re-run.
# Only a single match is adopted — multiple per-plan files are ambiguous and
# fall through to the canonical phase-level name (the integration artifact).
if [ -n "$phase_prefix" ]; then
# Match per-plan names with a >=2-digit, all-numeric MM component to mirror
# the writer's `printf %02d` plan numbering (resolve-artifact-path.sh): so
# 3-digit plan numbers (100+) are matched, but non-canonical names such as
# `{NN}-01a-VERIFICATION.md` or `{NN}-01-extra-VERIFICATION.md` are not.
# A nullglob array gives an exact entry count with no `ls` parsing and no
# newline-in-filename ambiguity; the glob's `*` is greedy, so a strict regex
# re-filter rejects its over-matches before the single-match decision.
local -a plan_files=() _plan_matches=()
local _nullglob_restore _pf _pf_base
_nullglob_restore=$(shopt -p nullglob 2>/dev/null || true)
shopt -s nullglob
_plan_matches=( "$PHASE_DIR/${phase_prefix}-"[0-9][0-9]*"-VERIFICATION.md" )
eval "$_nullglob_restore" 2>/dev/null || true
for _pf in "${_plan_matches[@]}"; do
_pf_base=$(basename "$_pf")
if [[ "$_pf_base" =~ ^${phase_prefix}-[0-9][0-9]+-VERIFICATION\.md$ ]]; then
plan_files+=("$_pf")
fi
done
if [ "${#plan_files[@]}" -eq 1 ]; then
printf '%s\n' "${plan_files[0]}"
return 0
fi
fi

if [ -n "$canonical_name" ]; then
echo "$PHASE_DIR/$canonical_name"
return 0
Expand Down
27 changes: 27 additions & 0 deletions tests/qa-result-gate.bats
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,33 @@ VERIF
[[ "$output" == *"qa_gate_writer=write-verification.sh"* ]]
}

@test "single-plan phase with only a per-plan {NN}-{MM}-VERIFICATION.md → PROCEED_TO_UAT (issue #653)" {
# Regression: a passing single-plan phase whose verification artifact was
# written with the per-plan name must not be read as missing and forced to
# re-run. Before the fix the gate auto-resolved the (absent) phase-level
# 01-VERIFICATION.md and reported qa_gate_writer=missing → QA_RERUN_REQUIRED.
: > "$PHASE_DIR/01-01-PLAN.md"
{
echo "---"
echo "phase: 01"
echo "result: PASS"
echo "passed: 33"
echo "failed: 0"
echo "total: 33"
echo "plans_verified: [01-01]"
echo "writer: write-verification.sh"
echo "---"
} > "$PHASE_DIR/01-01-VERIFICATION.md"

run bash "$SCRIPT" "$PHASE_DIR"

[ "$status" -eq 0 ]
[[ "$output" == *"qa_gate_routing=PROCEED_TO_UAT"* ]]
# Confirm the per-plan artifact was actually read (not a missing-file false pass)
[[ "$output" == *"qa_gate_writer=write-verification.sh"* ]]
[[ "$output" == *"qa_gate_result=PASS"* ]]
}

@test "brownfield fallback to plain VERIFICATION.md" {
# Create a phase dir WITHOUT numbered prefix
BROWNFIELD_DIR="$TEST_DIR/legacy-phase"
Expand Down
122 changes: 121 additions & 1 deletion tests/resolve-verification-path.bats
Original file line number Diff line number Diff line change
Expand Up @@ -307,4 +307,124 @@ EOF

[ "$status" -eq 0 ]
[ -z "$output" ]
}
}

# Issue #653: single-plan phase where the writer produced a per-plan
# {NN}-{MM}-VERIFICATION.md must resolve to that artifact, not a synthetic
# phase-level name the gate would read as missing.
@test "resolve-verification-path phase adopts the sole per-plan verification (issue #653 single-plan phase)" {
cat > "$PHASE_DIR/01-01-VERIFICATION.md" <<'EOF'
---
result: PASS
plans_verified: [01-01]
---
EOF

run bash "$SCRIPTS_DIR/resolve-verification-path.sh" phase "$PHASE_DIR"

[ "$status" -eq 0 ]
[ "$output" = "$PHASE_DIR/01-01-VERIFICATION.md" ]
}

@test "resolve-verification-path phase does NOT adopt per-plan files when more than one exists (ambiguous -> phase-level)" {
cat > "$PHASE_DIR/01-01-VERIFICATION.md" <<'EOF'
---
result: PASS
---
EOF
cat > "$PHASE_DIR/01-02-VERIFICATION.md" <<'EOF'
---
result: PASS
---
EOF

run bash "$SCRIPTS_DIR/resolve-verification-path.sh" phase "$PHASE_DIR"

[ "$status" -eq 0 ]
[ "$output" = "$PHASE_DIR/01-VERIFICATION.md" ]
}

@test "resolve-verification-path phase prefers an on-disk phase-level artifact over a per-plan one" {
cat > "$PHASE_DIR/01-VERIFICATION.md" <<'EOF'
---
result: PASS
---
EOF
cat > "$PHASE_DIR/01-01-VERIFICATION.md" <<'EOF'
---
result: FAIL
---
EOF

run bash "$SCRIPTS_DIR/resolve-verification-path.sh" phase "$PHASE_DIR"

[ "$status" -eq 0 ]
[ "$output" = "$PHASE_DIR/01-VERIFICATION.md" ]
}

@test "resolve-verification-path phase prefers wave files over a per-plan one" {
cat > "$PHASE_DIR/01-VERIFICATION-wave1.md" <<'EOF'
---
result: PASS
---
EOF
cat > "$PHASE_DIR/01-01-VERIFICATION.md" <<'EOF'
---
result: FAIL
---
EOF

run bash "$SCRIPTS_DIR/resolve-verification-path.sh" phase "$PHASE_DIR"

[ "$status" -eq 0 ]
[ "$output" = "$PHASE_DIR/01-VERIFICATION-wave1.md" ]
}

# Regression guard: a numbered phase with NO verification artifact of any kind
# must fall through the per-plan block and return the synthetic phase-level
# name (the pre-existing behavior the #653 block must not disturb).
@test "resolve-verification-path phase returns the synthetic phase-level name when no artifact exists" {
run bash "$SCRIPTS_DIR/resolve-verification-path.sh" phase "$PHASE_DIR"

[ "$status" -eq 0 ]
[ "$output" = "$PHASE_DIR/01-VERIFICATION.md" ]
}

# Issue #653 (round 1 hardening): the writer's `printf %02d` plan numbering
# yields 3-digit names for the 100th+ plan, so the per-plan fallback must adopt
# a sole {NN}-{MMM}-VERIFICATION.md, not just 2-digit MM.
@test "resolve-verification-path phase adopts a sole 3-digit per-plan verification (100+ plan numbers)" {
cat > "$PHASE_DIR/01-100-VERIFICATION.md" <<'EOF'
---
result: PASS
plans_verified: [01-100]
---
EOF

run bash "$SCRIPTS_DIR/resolve-verification-path.sh" phase "$PHASE_DIR"

[ "$status" -eq 0 ]
[ "$output" = "$PHASE_DIR/01-100-VERIFICATION.md" ]
}

# Issue #653 (QA round 2): the per-plan glob is greedy; a strict regex re-filter
# must reject non-canonical names whose MM segment is not purely numeric (e.g. a
# trailing letter or an extra `-segment-`), so they are NOT mistaken for the sole
# per-plan artifact. Such names fall through to the canonical phase-level name.
@test "resolve-verification-path phase does NOT adopt a non-canonical per-plan name (over-match guard)" {
cat > "$PHASE_DIR/01-01a-VERIFICATION.md" <<'EOF'
---
result: PASS
---
EOF
cat > "$PHASE_DIR/01-01-extra-VERIFICATION.md" <<'EOF'
---
result: PASS
---
EOF

run bash "$SCRIPTS_DIR/resolve-verification-path.sh" phase "$PHASE_DIR"

[ "$status" -eq 0 ]
[ "$output" = "$PHASE_DIR/01-VERIFICATION.md" ]
}
Loading