Skip to content

PR #472 Validation-Script Fixes — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Fix the three unresolved review defects in draft PR #472 (scripts/validate_issue_458.sh) so the new validation sections cannot report success while validating nothing, and so the SQS contract check actually matches the deployed log format.

Architecture: Extract the pure, decidable logic (outcome counters, summary rendering, exit-code policy, and partial-batch log-line classification) into a new sourceable library scripts/lib/validate_lib.sh. Unit-test that library with a dependency-free plain-bash runner (tests/scripts/validate_lib_test.sh) — no bats, no live AWS. Then refactor validate_issue_458.sh to source the library, count skips/inconclusives, surface incomplete coverage in the summary, replace the broken server-side CloudWatch filter with local classification, and rewrite the stale Gap #3 comment to match the code.

Tech Stack: Bash 3.2+ (macOS default), AWS CLI v2, plain-bash test harness. Mirrors the existing scripts/demo/lib.sh sourcing convention.

Implementation note (2026-06-02): .gitignore line 13 (lib/) ignores any lib/ directory, so the library was placed at scripts/validate_lib.sh (a file, like scripts/demo/lib.sh) rather than the scripts/lib/validate_lib.sh path written below — read all scripts/lib/validate_lib.sh references as scripts/validate_lib.sh, and the Setup mkdir as mkdir -p tests/scripts. Final unit-test count is 23 (the per-task "Ran N" numbers below are off by one from a draft miscount; the progression and behavior are unchanged).


Background: the three defects (from PR #472 reviews)

  1. Dead CloudWatch filter (gemini-code-assist + verified): Gap #3 check 2 uses --filter-pattern "failed=1", but lambdas/backtest-consumer/handler.py:65 logs f"{result.successful} succeeded, {result.skipped} skipped, {result.failed} failed" — i.e. 1 failed, never failed=1. The filter has never matched; the check is permanently inconclusive.
  2. False-positive passes (deveshmoper, CHANGES_REQUESTED): the happy-path and SQS sections can finish recording zero pass/fail assertions (data-collection down → SKIP_HAPPY_PATH=1; SKIP_SQS=1; inconclusive branches) yet the run still prints N passed, 0 failed and exits 0. Skips/inconclusives are invisible echo lines that never reach the summary or exit code.
  3. Stale Gap #3 comment (deveshmoper): the header comment describes "sends a deliberately malformed message", "capture the messageId", and "purge it from DLQ after the test" — none of which exists. The actual block is read-only.

File Structure

  • Create scripts/lib/validate_lib.sh — pure helpers: pass, fail, skip, inconclusive, render_summary, exit_code_for_results, is_partial_batch_failure_line. Side-effect-free (no AWS), so unit-testable.
  • Create tests/scripts/validate_lib_test.sh — dependency-free bash unit tests for the library.
  • Modify scripts/validate_issue_458.sh — source the library; remove inline pass/fail; convert bare SKIP/INCONCLUSIVE echoes to skip/inconclusive; fix Gap #3 check 2 to classify locally; rewrite the Gap #3 comment; use render_summary + exit_code_for_results.
  • Modify justfile — add a test-scripts recipe so the bash tests are runnable/CI-able.

Setup (do once before Task 1)

This work must land on the PR #472 branch, because sections [4/N]–[7/N] only exist there.

  • Step 0.1: Fetch and branch from the PR head

git fetch origin 05-14-test_validate_expand_smoke_script_with_happy-path_promote_rollback_sqs
git switch -c fix/pr472-validation-coverage \
    origin/05-14-test_validate_expand_smoke_script_with_happy-path_promote_rollback_sqs
Expected: a new local branch whose scripts/validate_issue_458.sh contains the [4/N][7/N] sections (grep -c '\[7/N\]' scripts/validate_issue_458.sh1).

  • Step 0.2: Confirm the defects are present

grep -n 'failed=1' scripts/validate_issue_458.sh        # the dead filter (defect 1)
grep -n 'purge it from DLQ' scripts/validate_issue_458.sh # the stale comment (defect 3)
grep -nc 'echo "  SKIP:' scripts/validate_issue_458.sh   # uncounted skips (defect 2)
Expected: each present. If any is missing, the branch is wrong — stop and re-check.

  • Step 0.3: Create the new directories

Neither scripts/lib nor tests/scripts exists in this worktree yet.

mkdir -p scripts/lib tests/scripts
Expected: both directories exist (ls -d scripts/lib tests/scripts).


Task 1: Outcome counters (skip / inconclusive)

Files: - Create: scripts/lib/validate_lib.sh - Create: tests/scripts/validate_lib_test.sh

  • Step 1: Write the failing test

Create tests/scripts/validate_lib_test.sh:

#!/usr/bin/env bash
# Dependency-free unit tests for scripts/lib/validate_lib.sh.
# Run: bash tests/scripts/validate_lib_test.sh   (no bats required)
set -uo pipefail

LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../scripts/lib" && pwd)/validate_lib.sh"
# shellcheck source=/dev/null
source "$LIB"

tests_run=0
tests_failed=0
check() {  # check "<desc>" "<actual>" "<expected>"
    local desc="$1" actual="$2" expected="$3"
    tests_run=$((tests_run + 1))
    if [[ "$actual" == "$expected" ]]; then
        echo "ok   - $desc"
    else
        echo "FAIL - $desc (expected '$expected', got '$actual')"
        tests_failed=$((tests_failed + 1))
    fi
}

# --- Task 1: counters ---
SKIP=0; skip "env down" >/dev/null;          check "skip increments SKIP" "$SKIP" "1"
SKIP=0; inconclusive "no data" >/dev/null;   check "inconclusive increments SKIP" "$SKIP" "1"
PASS=0; FAIL=0; SKIP=0
pass "a" >/dev/null; skip "b" >/dev/null
check "pass unaffected by skip" "$PASS" "1"
check "fail unaffected by skip" "$FAIL" "0"

echo ""
echo "Ran $tests_run tests, $tests_failed failed"
[[ $tests_failed -eq 0 ]]
  • Step 2: Run test to verify it fails

Run: bash tests/scripts/validate_lib_test.sh Expected: FAIL — the source "$LIB" line errors with "No such file or directory" (library not created yet), non-zero exit.

  • Step 3: Write minimal implementation

Create scripts/lib/validate_lib.sh:

#!/usr/bin/env bash
# Sourceable helpers for scripts/validate_issue_458.sh.
#
# Pure, side-effect-free logic (no AWS calls) lives here so it can be
# unit-tested by tests/scripts/validate_lib_test.sh without a deployed stack.
# Mirrors the scripts/demo/lib.sh sourcing convention.

# Outcome counters. Default to 0 unless the caller already set them.
PASS="${PASS:-0}"
FAIL="${FAIL:-0}"
SKIP="${SKIP:-0}"

pass() { echo "  PASS: $1"; PASS=$((PASS + 1)); }
fail() { echo "  FAIL: $1"; FAIL=$((FAIL + 1)); }

# skip / inconclusive record a NON-assertion outcome. They increment SKIP so
# the summary can report incomplete coverage instead of silently passing.
# Use skip() when a precondition makes a check impossible (e.g. a dependency
# is down); use inconclusive() when a check ran but could not decide.
skip()         { echo "  SKIP: $1"; SKIP=$((SKIP + 1)); }
inconclusive() { echo "  INCONCLUSIVE: $1"; SKIP=$((SKIP + 1)); }
  • Step 4: Run test to verify it passes

Run: bash tests/scripts/validate_lib_test.sh Expected: PASS — Ran 4 tests, 0 failed, exit 0.

  • Step 5: Commit
chmod +x scripts/lib/validate_lib.sh tests/scripts/validate_lib_test.sh
git add scripts/lib/validate_lib.sh tests/scripts/validate_lib_test.sh
git commit -m "test(validate): add validate_lib with counted skip/inconclusive outcomes"

Task 2: Summary rendering surfaces incomplete coverage

Files: - Modify: scripts/lib/validate_lib.sh - Modify: tests/scripts/validate_lib_test.sh

  • Step 1: Write the failing test

In tests/scripts/validate_lib_test.sh, insert this block immediately before the final echo "" summary block:

# --- Task 2: render_summary ---
PASS=2; FAIL=0; SKIP=1
out="$(render_summary)"
echo "$out" | grep -q "2 passed, 0 failed, 1 skipped/inconclusive" && r=yes || r=no
check "summary line includes skip count" "$r" "yes"
echo "$out" | grep -q "coverage is INCOMPLETE" && r=yes || r=no
check "summary warns when something was skipped" "$r" "yes"

PASS=3; FAIL=0; SKIP=0
out="$(render_summary)"
echo "$out" | grep -q "INCOMPLETE" && r=yes || r=no
check "no incomplete-coverage warning when nothing skipped" "$r" "no"
  • Step 2: Run test to verify it fails

Run: bash tests/scripts/validate_lib_test.sh Expected: FAIL — render_summary: command not found (function missing), the three new checks fail.

  • Step 3: Write minimal implementation

Append to scripts/lib/validate_lib.sh:

# Render the results summary, including skipped/inconclusive coverage. When any
# check was skipped or inconclusive, print a prominent warning so a reader does
# NOT mistake a partially-run validation for full coverage of the fix.
render_summary() {
    echo "  Results: ${PASS} passed, ${FAIL} failed, ${SKIP} skipped/inconclusive"
    if [[ "${SKIP}" -gt 0 ]]; then
        echo "  WARNING: ${SKIP} check(s) skipped or inconclusive — coverage is INCOMPLETE."
        echo "           Do NOT read this run as a full validation of the fix."
    fi
}
  • Step 4: Run test to verify it passes

Run: bash tests/scripts/validate_lib_test.sh Expected: PASS — Ran 7 tests, 0 failed.

  • Step 5: Commit
git add scripts/lib/validate_lib.sh tests/scripts/validate_lib_test.sh
git commit -m "feat(validate): summary surfaces incomplete coverage from skips"

Task 3: Exit-code policy

Files: - Modify: scripts/lib/validate_lib.sh - Modify: tests/scripts/validate_lib_test.sh

Policy (revised per GPT-5.5 review finding #1): a hard FAIL fails the run, and so does incomplete coverage — because deveshmoper's defect is precisely "a run that validated nothing still reports success and exits 0". Any skip/inconclusive therefore exits non-zero by default, so automation cannot mistake a partial run for a clean validation. An operator running interactively against a deliberately-partial dev env opts out with ALLOW_INCOMPLETE=1.

  • Step 1: Write the failing test

In tests/scripts/validate_lib_test.sh, insert before the final summary block:

# --- Task 3: exit_code_for_results ---
FAIL=0; SKIP=0; ALLOW_INCOMPLETE=0; exit_code_for_results; rc=$?
check "rc 0 when no failures and full coverage" "$rc" "0"
FAIL=2; SKIP=0; ALLOW_INCOMPLETE=0; exit_code_for_results; rc=$?
check "rc 1 when failures present" "$rc" "1"
FAIL=0; SKIP=3; ALLOW_INCOMPLETE=0; exit_code_for_results; rc=$?
check "rc 1 on incomplete coverage by default" "$rc" "1"
FAIL=0; SKIP=3; ALLOW_INCOMPLETE=1; exit_code_for_results; rc=$?
check "rc 0 on incomplete coverage when explicitly allowed" "$rc" "0"
  • Step 2: Run test to verify it fails

Run: bash tests/scripts/validate_lib_test.sh Expected: FAIL — exit_code_for_results: command not found, the four new checks fail.

  • Step 3: Write minimal implementation

Append to scripts/lib/validate_lib.sh:

# Exit-code policy. A hard FAIL always fails the run. Incomplete coverage (any
# skip/inconclusive) ALSO fails by default so an automated run cannot exit 0
# while having validated nothing — the exact false-positive PR #472 was flagged
# for. Set ALLOW_INCOMPLETE=1 to downgrade incomplete coverage to a pass for
# interactive runs against a partial dev environment.
exit_code_for_results() {
    [[ "${FAIL}" -gt 0 ]] && return 1
    if [[ "${SKIP}" -gt 0 && "${ALLOW_INCOMPLETE:-0}" != "1" ]]; then
        return 1
    fi
    return 0
}
  • Step 4: Run test to verify it passes

Run: bash tests/scripts/validate_lib_test.sh Expected: PASS — Ran 11 tests, 0 failed.

  • Step 5: Commit
git add scripts/lib/validate_lib.sh tests/scripts/validate_lib_test.sh
git commit -m "feat(validate): exit-code policy (skips do not fail the run)"

Task 4: Partial-batch log-line classifier (fixes the dead filter)

Files: - Modify: scripts/lib/validate_lib.sh - Modify: tests/scripts/validate_lib_test.sh

The consumer logs "{N} succeeded, {N} skipped, {N} failed". A line is a partial-batch failure iff the integer before failed is ≥ 1. CloudWatch filter patterns cannot express "failed ≥ 1", so we classify locally.

  • Step 1: Write the failing test

In tests/scripts/validate_lib_test.sh, insert before the final summary block:

The classifier matches the full consumer summary shape (finding #3 — a loose " failed" match would false-positive on unrelated lines like "job skipped, then 2 failed").

# --- Task 4: is_partial_batch_failure_line ---
is_partial_batch_failure_line "0 succeeded, 0 skipped, 1 failed" && r=yes || r=no
check "1 failed -> partial-batch failure" "$r" "yes"
is_partial_batch_failure_line "5 succeeded, 0 skipped, 0 failed" && r=yes || r=no
check "0 failed -> not a failure" "$r" "no"
is_partial_batch_failure_line "3 succeeded, 1 skipped, 2 failed" && r=yes || r=no
check "2 failed -> partial-batch failure" "$r" "yes"
is_partial_batch_failure_line "12 succeeded, 0 skipped, 10 failed" && r=yes || r=no
check "multi-digit 10 failed -> partial-batch failure" "$r" "yes"
is_partial_batch_failure_line "INFO 2026-06-02 0 succeeded, 0 skipped, 4 failed" && r=yes || r=no
check "log-prefixed summary still classified" "$r" "yes"
is_partial_batch_failure_line "Lambda init: cold start complete" && r=yes || r=no
check "unrelated line -> not a failure" "$r" "no"
is_partial_batch_failure_line "job skipped, then 2 failed" && r=yes || r=no
check "non-summary line ending in 'N failed' -> not a failure" "$r" "no"
is_partial_batch_failure_line "1 failed" && r=yes || r=no
check "bare '1 failed' (not the summary) -> not a failure" "$r" "no"
is_partial_batch_failure_line "" && r=yes || r=no
check "empty line -> not a failure" "$r" "no"
  • Step 2: Run test to verify it fails

Run: bash tests/scripts/validate_lib_test.sh Expected: FAIL — is_partial_batch_failure_line: command not found, the nine new checks fail.

  • Step 3: Write minimal implementation

Append to scripts/lib/validate_lib.sh:

# Return 0 (true) iff the given backtest-consumer log line is the batch summary
# AND its failed-count is >= 1. Matches the full shape emitted by
# lambdas/backtest-consumer/handler.py (with any leading log prefix):
#   "{N} succeeded, {N} skipped, {N} failed"
# Matching the whole shape (not just "<n> failed") avoids false positives on
# unrelated lines. Replaces the CloudWatch "failed=1" filter, which never
# matched this format and could not express "failed >= 1" anyway.
is_partial_batch_failure_line() {
    local line="$1"
    local failed
    failed=$(printf '%s\n' "$line" \
        | sed -nE 's/.*[0-9]+ succeeded, [0-9]+ skipped, ([0-9]+) failed.*/\1/p')
    [[ -n "$failed" && "$failed" -ge 1 ]]
}
  • Step 4: Run test to verify it passes

Run: bash tests/scripts/validate_lib_test.sh Expected: PASS — Ran 19 tests, 0 failed.

  • Step 5: Commit
git add scripts/lib/validate_lib.sh tests/scripts/validate_lib_test.sh
git commit -m "feat(validate): classify partial-batch failures by log content"

Task 4b: Per-section assertion guard

Global skip-counting is not enough (GPT-5.5 finding #2): a section could record no outcome at all (an early return/silent if-skip with no pass/fail/skip call) and stay invisible. section_begin + require_section_assertions make a zero-outcome section a hard FAIL. A skip counts as a recorded (visible) outcome, so a skip-only section is allowed here but still trips the incomplete-coverage exit gate from Task 3.

Files: - Modify: scripts/lib/validate_lib.sh - Modify: tests/scripts/validate_lib_test.sh

  • Step 1: Write the failing test

In tests/scripts/validate_lib_test.sh, insert before the final summary block:

# --- Task 4b: per-section assertion guard ---
PASS=0; FAIL=0; SKIP=0
section_begin; pass "did something" >/dev/null
require_section_assertions "demo" >/dev/null
check "section with a pass does not add a fail" "$FAIL" "0"

PASS=0; FAIL=0; SKIP=0
section_begin; skip "dep down" >/dev/null
require_section_assertions "demo" >/dev/null
check "section with only a skip does not add a fail" "$FAIL" "0"

PASS=0; FAIL=0; SKIP=0
section_begin
require_section_assertions "demo" >/dev/null
check "zero-outcome section becomes a fail" "$FAIL" "1"
  • Step 2: Run test to verify it fails

Run: bash tests/scripts/validate_lib_test.sh Expected: FAIL — section_begin: command not found, the three new checks fail.

  • Step 3: Write minimal implementation

Append to scripts/lib/validate_lib.sh:

# Per-section guard against a SILENT zero-outcome section (no pass/fail/skip at
# all) — the exact false-positive PR #472 was flagged for. Call section_begin()
# at the top of a validation section and require_section_assertions "<name>" at
# the end; a section that recorded nothing becomes a hard fail.
_SECTION_BASE=0
section_begin() { _SECTION_BASE=$((PASS + FAIL + SKIP)); }
require_section_assertions() {
    local name="$1"
    if [[ $((PASS + FAIL + SKIP)) -eq "${_SECTION_BASE}" ]]; then
        fail "${name}: section recorded ZERO outcomes — it validated nothing"
    fi
}
  • Step 4: Run test to verify it passes

Run: bash tests/scripts/validate_lib_test.sh Expected: PASS — Ran 22 tests, 0 failed.

  • Step 5: Commit
git add scripts/lib/validate_lib.sh tests/scripts/validate_lib_test.sh
git commit -m "feat(validate): per-section guard fails silent zero-outcome sections"

Task 5: Integrate the library into validate_issue_458.sh

This task wires the tested library into the real script and applies all three fixes. The script makes live AWS calls, so its "test" is a syntax/sourcing smoke check (the decidable logic is already covered by Tasks 1–4).

Files: - Modify: scripts/validate_issue_458.sh

  • Step 1: Write the failing smoke test

Create tests/scripts/validate_script_smoke_test.sh:

#!/usr/bin/env bash
# Smoke checks for scripts/validate_issue_458.sh that need NO AWS:
#   - the script parses (bash -n)
#   - it sources the shared library
#   - the dead "failed=1" filter is gone
#   - the stale "purge it from DLQ" comment is gone
#   - skips/inconclusives are counted (no bare 'echo "  SKIP:' / "  INCONCLUSIVE:')
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SCRIPT="$ROOT/scripts/validate_issue_458.sh"
rc=0
bash -n "$SCRIPT"                              || { echo "FAIL - bash -n"; rc=1; }
grep -q 'source .*validate_lib.sh' "$SCRIPT"   || { echo "FAIL - does not source validate_lib.sh"; rc=1; }
grep -q 'failed=1' "$SCRIPT"                    && { echo "FAIL - dead failed=1 filter still present"; rc=1; }
grep -q 'purge it from DLQ' "$SCRIPT"           && { echo "FAIL - stale DLQ-purge comment still present"; rc=1; }
grep -q 'echo "  SKIP:' "$SCRIPT"               && { echo "FAIL - uncounted bare SKIP echo still present"; rc=1; }
grep -q 'echo "  INCONCLUSIVE:' "$SCRIPT"       && { echo "FAIL - uncounted bare INCONCLUSIVE echo still present"; rc=1; }
[[ $rc -eq 0 ]] && echo "ok - validate_issue_458.sh smoke checks passed"
exit $rc

Run: bash tests/scripts/validate_script_smoke_test.sh Expected: FAIL — script does not yet source the library; failed=1, the stale comment, and bare SKIP/INCONCLUSIVE echoes are still present.

  • Step 2: Source the library and remove the inline helpers

In scripts/validate_issue_458.sh, replace the inline counter block:

PASS=0
FAIL=0

pass() { echo "  PASS: $1"; PASS=$((PASS + 1)); }
fail() { echo "  FAIL: $1"; FAIL=$((FAIL + 1)); }

with a source of the shared library (the library initializes PASS/FAIL/SKIP and defines pass/fail/skip/inconclusive):

# shellcheck source=scripts/lib/validate_lib.sh
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/validate_lib.sh"
  • Step 3: Count the happy-path skip and inconclusive outcomes

In the Gap #1 precondition block, replace:

        echo "  SKIP: data-collection service unhealthy (FunctionError=$DC_HEALTH); a real backtest cannot succeed."
        echo "  Note: this is an environmental issue, not a fix regression."
        echo ""
        # Skip the rest of section 4
        SKIP_HAPPY_PATH=1

with:

        skip "data-collection service unhealthy (FunctionError=$DC_HEALTH); a real backtest cannot succeed — environmental, not a fix regression."
        echo ""
        # Skip the rest of section 4
        SKIP_HAPPY_PATH=1

In the Gap #1 EnsureData-network branch, replace:

            echo "  INCONCLUSIVE: EnsureData failed with upstream service error ($(echo "$cause" | head -c 80)...)"
            echo "  This means data-collection /sync is unhealthy in $ENVIRONMENT — env issue, not a fix regression."
            echo "  The fix IS working: it propagated the upstream failure correctly through Catch."

with:

            inconclusive "EnsureData failed with upstream service error ($(echo "$cause" | head -c 80)...) — data-collection /sync unhealthy in $ENVIRONMENT; the fix propagated it correctly through Catch."
  • Step 4: Fix the Gap #3 check 2 to classify locally

Replace the dead-filter block:

    # Check 2: recent partial-batch failures in CloudWatch (last 1h)
    SINCE_TS=$(($(date +%s) - 3600))000
    # filter-log-events with --output text + length(@) can return multiple
    # paginated responses joined with newlines. Sum them.
    PARTIAL_BATCH_HITS=$(aws logs filter-log-events \
        --profile "$AWS_PROFILE" --region "$AWS_REGION" \
        --log-group-name "$CONSUMER_LOG_GROUP" \
        --start-time "$SINCE_TS" \
        --filter-pattern "failed=1" \
        --query "events | length(@)" --output text 2>/dev/null \
        | awk '{s+=$1} END {print s+0}' || echo "0")
    if [[ "$PARTIAL_BATCH_HITS" -gt 0 ]]; then
        pass "consumer logged $PARTIAL_BATCH_HITS partial-batch-failure events in last hour (contract is exercised)"
    else
        echo "  INCONCLUSIVE: no recent partial-batch failures in CloudWatch (last 1h)."
        echo "  This is normal in a quiet environment. Send a malformed message manually to verify."
    fi

with a fetch-then-classify block. The AWS call's exit code is captured first (finding #4) so a real error (missing log group, throttling, auth) surfaces as inconclusive-with-cause rather than collapsing into a silent "0 hits". The server-side filter is the broad term failed (every summary line contains it; finding #5 — avoid punctuation-sensitive patterns); the strict is_partial_batch_failure_line makes the actual decision locally:

    # Check 2: recent partial-batch failures in CloudWatch (last 1h).
    # CloudWatch filter patterns cannot express "failed >= 1", and the consumer
    # logs "<n> succeeded, <n> skipped, <n> failed" (never "failed=1"). Pull the
    # candidate lines with the broad term filter "failed", then classify each
    # locally with the strict is_partial_batch_failure_line().
    SINCE_TS=$(($(date +%s) - 3600))000
    LOG_EVENTS_RC=0
    LOG_MESSAGES=$(aws logs filter-log-events \
        --profile "$AWS_PROFILE" --region "$AWS_REGION" \
        --log-group-name "$CONSUMER_LOG_GROUP" \
        --start-time "$SINCE_TS" \
        --filter-pattern "failed" \
        --query "events[].message" --output text 2>"$TMPDIR_VALIDATE/logs_err.txt") || LOG_EVENTS_RC=$?

    if [[ "$LOG_EVENTS_RC" -ne 0 ]]; then
        # A real AWS error must NOT become a quiet "0 hits" — surface the cause.
        inconclusive "could not read consumer logs (aws rc=$LOG_EVENTS_RC): $(head -c 120 "$TMPDIR_VALIDATE/logs_err.txt")"
    else
        PARTIAL_BATCH_HITS=0
        while IFS= read -r line; do
            [[ -z "$line" ]] && continue
            is_partial_batch_failure_line "$line" && PARTIAL_BATCH_HITS=$((PARTIAL_BATCH_HITS + 1))
        done < <(printf '%s\n' "$LOG_MESSAGES" | tr '\t' '\n')
        if [[ "$PARTIAL_BATCH_HITS" -gt 0 ]]; then
            pass "consumer logged $PARTIAL_BATCH_HITS partial-batch-failure event(s) in last hour (contract is exercised)"
        else
            inconclusive "no partial-batch failures in CloudWatch (last 1h) — normal in a quiet env; send a malformed message manually to verify."
        fi
    fi
  • Step 5: Rewrite the stale Gap #3 header comment

Replace the Gap #3 header comment (the block beginning # Gap #3: SQS bad-message → DLQ contract and describing sending a message, capturing the messageId, and purging the DLQ) with an accurate read-only description:

    # ---------------------------------------------------------------------------
    # Gap #3: SQS partial-batch (ReportBatchItemFailures) contract — READ-ONLY.
    #
    # This section performs NO writes to the queue or DLQ. It verifies the
    # deployed contract two ways, without racing a fresh message:
    #   1. The EventSourceMapping has FunctionResponseTypes=ReportBatchItemFailures
    #      (a missing flag is a deploy regression).
    #   2. Recent backtest-consumer logs are scanned for partial-batch failures
    #      (failed >= 1), confirming the handler emits the partial-batch shape at
    #      runtime. Inconclusive (not a failure) when the environment is quiet.
    #
    # We deliberately do NOT send a message: BatchSize=1 + VisibilityTimeout=900s
    # + FIFO ordering means a fresh message can sit behind backlog for minutes —
    # that is queue shape, not fix correctness. The cleanup-isolation invariant
    # is covered directly by the unit tests in tests/unit/lambdas/.
    # ---------------------------------------------------------------------------
  • Step 5b: Guard the two flagged sections against zero-outcome

Wrap the happy-path (Gap #1) and SQS (Gap #3) sections so they cannot finish without a recorded outcome.

For the happy-path section, add section_begin immediately after its [4/N] Happy-path regression backtest... echo, and add require_section_assertions "happy-path" immediately before the echo "" that closes the section.

For the SQS section, the current code silently does nothing when SKIP_SQS=1. Add section_begin right after the [7/N] SQS partial-batch contract verification... echo, add require_section_assertions "SQS partial-batch contract" just before that section's closing fi, and give the disabled branch an explicit skip. The structure becomes:

if [[ "${SKIP_SQS:-0}" != "1" ]]; then
    echo "[7/N] SQS partial-batch contract verification..."
    section_begin
    # ... check 1 (EventSourceMapping flag) — records pass/fail ...
    # ... check 2 (CloudWatch classify) — records pass/inconclusive ...
    require_section_assertions "SQS partial-batch contract"
else
    skip "SQS partial-batch contract section disabled via SKIP_SQS=1"
fi
  • Step 6: Replace the summary + exit lines

Replace:

echo ""
echo "========================================================================"
echo "  Results: $PASS passed, $FAIL failed"
echo "========================================================================"

[[ $FAIL -eq 0 ]] || exit 1

with:

echo ""
echo "========================================================================"
render_summary
echo "========================================================================"

exit_code_for_results || exit 1
  • Step 7: Run the smoke test to verify it passes

Run: bash tests/scripts/validate_script_smoke_test.sh Expected: PASS — ok - validate_issue_458.sh smoke checks passed, exit 0.

  • Step 8: Re-run the library unit tests (no regressions)

Run: bash tests/scripts/validate_lib_test.sh Expected: PASS — Ran 22 tests, 0 failed.

  • Step 9: Commit
chmod +x tests/scripts/validate_script_smoke_test.sh
git add scripts/validate_issue_458.sh tests/scripts/validate_script_smoke_test.sh
git commit -m "fix(validate): count skips, fix partial-batch log check, correct Gap #3 comment

Resolves PR #472 review: skips/inconclusives now reach the summary and no
longer read as a clean pass; the dead 'failed=1' CloudWatch filter is replaced
by local classification of the real log format; the Gap #3 comment matches the
read-only code."

Task 6: Make the bash tests runnable via just

Files: - Modify: justfile

  • Step 1: Inspect the existing recipe style

Run: grep -nE '^test|^check' justfile | head Expected: shows the test/check recipe block to mirror.

  • Step 2: Add a test-scripts recipe

Append to justfile (match surrounding indentation/quoting style):

# Run dependency-free bash unit tests for shell scripts
test-scripts:
    bash tests/scripts/validate_lib_test.sh
    bash tests/scripts/validate_script_smoke_test.sh
  • Step 3: Run it

Run: just test-scripts Expected: both test files print their ok/Ran N tests, 0 failed lines and the recipe exits 0.

  • Step 4: Commit
git add justfile
git commit -m "chore(validate): add just test-scripts recipe for shell unit tests"

Final Verification

  • bash tests/scripts/validate_lib_test.shRan 22 tests, 0 failed
  • bash tests/scripts/validate_script_smoke_test.shok line, exit 0
  • bash -n scripts/validate_issue_458.sh → no output, exit 0
  • grep -c 'failed=1\|purge it from DLQ\|echo " SKIP:\|echo " INCONCLUSIVE:' scripts/validate_issue_458.sh0
  • shellcheck scripts/lib/validate_lib.sh scripts/validate_issue_458.sh if shellcheck is installed → no errors (warnings reviewed)
  • Push the branch and update PR #472; mark Ready for review and re-request from deveshmoper.

Self-Review notes

  • Defect 1 (dead filter): Task 4 (strict full-summary classifier, unit-tested incl. false-positive cases) + Task 5 Step 4 (wires it in, removes failed=1, captures the AWS rc, broadens the server-side filter). Smoke test asserts failed=1 is gone.
  • Defect 2 (false-positive passes): Task 1 (counted skip/inconclusive) + Task 2 (summary surfaces them) + Task 3 (incomplete coverage exits non-zero unless ALLOW_INCOMPLETE=1) + Task 4b (per-section guard hard-fails a silent zero-outcome section) + Task 5 Steps 3, 5b & 6 (convert bare echoes, wrap sections, explicit SKIP_SQS skip, use render_summary/exit_code_for_results). Smoke test asserts no bare SKIP/INCONCLUSIVE echoes remain.
  • Defect 3 (stale comment): Task 5 Step 5 rewrites it; smoke test asserts purge it from DLQ is gone. (Comment rewrite is documentation — per TDD-skill exceptions it has no unit test, but the smoke test guards against the stale text.)
  • No live-AWS coverage of Task 5 control flow is a known limit: the AWS-calling glue is verified only by bash -n + sourcing + grep guards. The decidable logic it depends on is fully unit-tested in Tasks 1–4b. A full e2e requires a deployed dev stack and is the manual smoke-run step the script itself performs.

Incorporated from GPT-5.5 review (codex, gpt-5.5, 2026-06-02)

All 7 findings accepted: #1 incomplete-coverage exit gate (Task 3), #2 per-section guard (Task 4b), #3 strict classifier + false-positive tests (Task 4), #4 capture AWS rc (Task 5 Step 4), #5 broaden server-side filter (Task 5 Step 4), #6 smoke rejects bare INCONCLUSIVE (Task 5 Step 1), #7 mkdir -p (Setup Step 0.3).