#!/usr/bin/env bash
# validate-compliance-artifacts.test.sh — fixture-based tests for #232
# hardenings (regex tightening, no-RTM-row skip, SUPERSEDED handling).
#
# Builds a throwaway git repo per case, populates compliance/ files,
# runs validate-compliance-artifacts.sh against it, asserts on the exit
# code and selected log lines.
#
# Usage:
#   ./scripts/validate-compliance-artifacts.test.sh
#
# The script is hermetic: it does NOT touch the host repo; everything
# runs inside a mktemp'd directory that's torn down at the end. Suitable
# for CI invocation, no extra deps beyond bash + git + grep.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
VALIDATOR="$SCRIPT_DIR/validate-compliance-artifacts.sh"
[ -x "$VALIDATOR" ] || chmod +x "$VALIDATOR"

PASS=0
FAIL=0

# --- helpers ---

# Build a fresh git fixture under $1 and cd into it. Initial commit on
# a `base` branch, then a single feature commit on `feature` whose body
# the caller seeds via the second argument.
make_fixture() {
  local dir="$1" feat_msg="$2"
  rm -rf "$dir"
  mkdir -p "$dir"
  cd "$dir"
  git init -q --initial-branch=base
  git config user.email "test@example.com"
  git config user.name "test"
  mkdir -p compliance/pending-releases compliance/approved-releases compliance/superseded-releases compliance/evidence
  printf '# RTM\n\n| ID | Description | Status |\n| --- | --- | --- |\n' > compliance/RTM.md
  git add . && git commit -q -m "init"
  git checkout -q -b feature
  echo "feature change" > feature.txt
  git add . && git commit -q -m "feat: feature commit

$feat_msg"
}

# Run the validator against the current fixture's HEAD vs base. Captures
# stdout to $OUT_FILE and exit code to $EXIT_CODE_VAR.
run_validator() {
  set +e
  OUT_FILE=$(mktemp)
  bash "$VALIDATOR" base > "$OUT_FILE" 2>&1
  LAST_EXIT=$?
  set -e
}

assert_grep() {
  local desc="$1" pattern="$2" want_match="$3"
  if grep -qE "$pattern" "$OUT_FILE"; then
    found=1
  else
    found=0
  fi
  if [ "$found" = "$want_match" ]; then
    echo "  PASS: $desc"
    PASS=$((PASS + 1))
  else
    echo "  FAIL: $desc (want match=$want_match, got=$found, pattern=$pattern)"
    echo "  --- output ---"
    sed 's/^/  /' "$OUT_FILE"
    echo "  ---"
    FAIL=$((FAIL + 1))
  fi
}

assert_exit() {
  local desc="$1" want="$2"
  if [ "$LAST_EXIT" = "$want" ]; then
    echo "  PASS: $desc (exit=$LAST_EXIT)"
    PASS=$((PASS + 1))
  else
    echo "  FAIL: $desc (want=$want, got=$LAST_EXIT)"
    echo "  --- output ---"
    sed 's/^/  /' "$OUT_FILE"
    echo "  ---"
    FAIL=$((FAIL + 1))
  fi
}

WORKDIR=$(mktemp -d -t validate-compliance-test-XXXX)
trap 'rm -rf "$WORKDIR"' EXIT

# --- case 1: REQ-0XX placeholder is ignored (\d{3,} regex) ---

echo "Case 1: REQ-0XX placeholder doesn't create phantom REQ-0"
make_fixture "$WORKDIR/case1" "Ref: REQ-0XX/test-scope.md (placeholder for sub-REQs)"
run_validator
assert_grep "no phantom REQ-0 surfaced" '^Requirements found in PR commits: REQ-0\b' 0
assert_grep "skip-validation message present" 'No REQ-XXX references found' 1
assert_exit "validator exits 0 on placeholder-only commit body" 0
cd "$WORKDIR"

# --- case 2: REQ-099 mentioned but absent from RTM → INFO + skip ---

echo "Case 2: forward-reference REQ skipped when RTM row absent"
make_fixture "$WORKDIR/case2" "Ref: REQ-099 (this is a forward-reference, no RTM row)"
run_validator
assert_grep "INFO line emitted for forward-reference" 'INFO: REQ-099 is referenced.*no RTM row' 1
assert_grep "no ERROR for missing evidence dir" 'ERROR: Evidence directory missing.*REQ-099' 0
assert_exit "validator exits 0 when only forward-references present" 0
cd "$WORKDIR"

# --- case 3: SUPERSEDED RTM status is accepted ---

echo "Case 3: SUPERSEDED RTM status passes"
make_fixture "$WORKDIR/case3" "Ref: REQ-030"
# RTM with SUPERSEDED status; full evidence + ticket present
{
  echo '# RTM'
  echo
  echo '| ID | Description | Status |'
  echo '| --- | --- | --- |'
  echo '| REQ-030 | Replaced by REQ-031 | SUPERSEDED by REQ-031 |'
} > compliance/RTM.md
mkdir -p compliance/evidence/REQ-030
echo "scope" > compliance/evidence/REQ-030/test-scope.md
echo "plan" > compliance/evidence/REQ-030/test-plan.md
echo "summary" > compliance/evidence/REQ-030/test-execution-summary.md
touch compliance/pending-releases/RELEASE-TICKET-REQ-030.md
git add . && git commit -q --amend --no-edit
run_validator
assert_grep "RTM SUPERSEDED accepted" 'OK: RTM status is SUPERSEDED' 1
assert_exit "validator exits 0 with SUPERSEDED RTM status" 0
cd "$WORKDIR"

# --- case 4: SUPERSEDED ticket location is accepted ---

echo "Case 4: superseded-releases/ ticket location accepted"
make_fixture "$WORKDIR/case4" "Ref: REQ-031"
{
  echo '# RTM'
  echo
  echo '| ID | Description | Status |'
  echo '| --- | --- | --- |'
  echo '| REQ-031 | Successor of REQ-030 | SUPERSEDED by REQ-032 |'
} > compliance/RTM.md
mkdir -p compliance/evidence/REQ-031
echo "scope" > compliance/evidence/REQ-031/test-scope.md
echo "plan" > compliance/evidence/REQ-031/test-plan.md
echo "summary" > compliance/evidence/REQ-031/test-execution-summary.md
# Only the superseded location — neither pending- nor approved-releases.
touch compliance/superseded-releases/RELEASE-TICKET-REQ-031.md
git add . && git commit -q --amend --no-edit
run_validator
assert_grep "ticket in superseded-releases/ accepted" 'OK: Release ticket exists' 1
assert_grep "no missing-ticket ERROR" 'ERROR: Release ticket missing' 0
assert_exit "validator exits 0 with SUPERSEDED ticket location" 0
cd "$WORKDIR"

# --- case 7: duplicate ticket in pending + approved → ERROR + exit 1 ---
#
# Regression for devaudit-installer#193: a stale pending copy left behind
# after close-out (carried back by a stale-branch merge) must be caught
# here with an actionable message, not silently pass and poison the
# evidence-completeness gate downstream (#192).

echo "Case 7: duplicate ticket in pending + approved directories fails"
make_fixture "$WORKDIR/case7" "Ref: REQ-077"
{
  echo '# RTM'
  echo
  echo '| ID | Description | Status |'
  echo '| --- | --- | --- |'
  echo '| REQ-077 | Duplicate ticket test | TESTED - PENDING SIGN-OFF |'
} > compliance/RTM.md
mkdir -p compliance/evidence/REQ-077
echo "scope" > compliance/evidence/REQ-077/test-scope.md
echo "plan" > compliance/evidence/REQ-077/test-plan.md
echo "summary" > compliance/evidence/REQ-077/test-execution-summary.md
# Ticket in BOTH pending and approved — the duplicate
touch compliance/pending-releases/RELEASE-TICKET-REQ-077.md
touch compliance/approved-releases/RELEASE-TICKET-REQ-077.md
git add . && git commit -q --amend --no-edit
run_validator
assert_grep "duplicate-ticket ERROR emitted" 'ERROR: RELEASE-TICKET-REQ-077 exists in more than one release directory' 1
assert_grep "no OK for duplicate ticket" 'OK: Release ticket exists' 0
assert_exit "validator exits 1 on duplicate ticket" 1
cd "$WORKDIR"

# --- case 5: bare-filename reference resolves to file at depth ≥2 ---
#
# Regression for the broken `compgen -G "**/$TF"` search: bash globstar
# is off by default, so `**` only matched depth-1 paths. A test plan
# referencing a bare filename (e.g. `inventory-service.list-by-kind.test.ts`)
# whose actual file lived at `__tests__/services/...` was reported as
# missing even though it existed. Fix uses `find -name` instead.

echo "Case 5: bare-filename reference resolves to depth-2 test file"
make_fixture "$WORKDIR/case5" "Ref: REQ-200"
{
  echo '# RTM'
  echo
  echo '| ID | Description | Status |'
  echo '| --- | --- | --- |'
  echo '| REQ-200 | Bare-filename ref test | IN PROGRESS |'
} > compliance/RTM.md
mkdir -p compliance/evidence/REQ-200
echo "scope" > compliance/evidence/REQ-200/test-scope.md
# Plan references the file by BARE filename (no directory prefix).
{
  echo '# Test Plan — REQ-200'
  echo
  echo '| AC | Test | Notes |'
  echo '| --- | --- | --- |'
  echo '| AC1 | `foo-service.list-by-kind.test.ts` | unit test |'
} > compliance/evidence/REQ-200/test-plan.md
# Actual test file lives at depth 2.
mkdir -p __tests__/services
echo "// stub" > __tests__/services/foo-service.list-by-kind.test.ts
echo "summary" > compliance/evidence/REQ-200/test-execution-summary.md
touch compliance/pending-releases/RELEASE-TICKET-REQ-200.md
git add . && git commit -q --amend --no-edit
run_validator
assert_grep "depth-2 bare filename resolved" 'OK: All test files referenced in test-plan.md exist' 1
assert_grep "no missing-test ERROR" 'ERROR: Test file referenced in test-plan.md not found' 0
assert_exit "validator exits 0 with depth-2 bare-filename reference" 0
cd "$WORKDIR"

# --- case 6: a future REQ mentioned only in commit prose is ignored ---
# Regression for the META-JOBS REQ-002 false positive: scraping the whole
# commit body pulled in `REQ-002` from "target close: REQ-002" and then
# ERRORed on its missing evidence dir even though REQ-002 hadn't started.
# Only `[REQ-XXX]` subject tags and `Ref:` lines count as under-change.
echo "Case 6: future REQ mentioned only in prose is not validated"
make_fixture "$WORKDIR/case6" "Implements the access-control boundary.

Dependency advisories accepted under R-001; target close: REQ-002.

Ref: REQ-001"
# REQ-002 HAS an RTM row (the trap) but no evidence dir; REQ-001 is the real
# Ref but has no RTM row, so it INFO-skips. Old code would ERROR on REQ-002.
{
  echo '# RTM'
  echo
  echo '| ID | Description | Status |'
  echo '| --- | --- | --- |'
  echo '| REQ-002 | Dependency hardening (not started) | PLANNED |'
} > compliance/RTM.md
git add . && git commit -q -m "chore: seed RTM with future REQ-002 row"
run_validator
assert_grep "REQ-002 not pulled in from prose" 'Requirements found in PR commits:.*REQ-002' 0
assert_grep "no evidence-dir ERROR for prose-only REQ-002" 'ERROR: Evidence directory missing.*REQ-002' 0
assert_exit "validator exits 0 when future REQ is only prose-mentioned" 0
cd "$WORKDIR"

# --- case 8: tracked REQ without test-execution-summary fails ---
#
# Regression for devaudit-installer#341: tracked releases must not pass
# validation without the per-release test report that satisfies the
# portal's Test Reports gate.

echo "Case 8: tracked REQ missing test-execution-summary fails"
make_fixture "$WORKDIR/case8" "Ref: REQ-341"
{
  echo '# RTM'
  echo
  echo '| ID | Description | Status |'
  echo '| --- | --- | --- |'
  echo '| REQ-341 | Missing test summary | TESTED - PENDING SIGN-OFF |'
} > compliance/RTM.md
mkdir -p compliance/evidence/REQ-341
echo "scope" > compliance/evidence/REQ-341/test-scope.md
echo "plan" > compliance/evidence/REQ-341/test-plan.md
touch compliance/pending-releases/RELEASE-TICKET-REQ-341.md
git add . && git commit -q --amend --no-edit
run_validator
assert_grep "missing-summary ERROR emitted" 'ERROR: Test execution summary missing: compliance/evidence/REQ-341/test-execution-summary.md' 1
assert_exit "validator exits 1 when tracked REQ lacks test-execution-summary" 1
cd "$WORKDIR"

# --- summary ---

# --- case 9: bundled release context missing from canonical artefacts fails ---
echo "Case 9: bundled release artefacts without context headings fail"
make_fixture "$WORKDIR/case9" "Ref: REQ-344"
{
  echo '# RTM'
  echo
  echo '| ID | Description | Status |'
  echo '| --- | --- | --- |'
  echo '| REQ-344 | Bundled release context missing | TESTED - PENDING SIGN-OFF |'
} > compliance/RTM.md
mkdir -p compliance/evidence/REQ-344
echo "scope" > compliance/evidence/REQ-344/test-scope.md
echo "plan" > compliance/evidence/REQ-344/test-plan.md
echo "summary" > compliance/evidence/REQ-344/test-execution-summary.md
echo "security" > compliance/evidence/REQ-344/security-summary.md
echo "ai note" > compliance/evidence/REQ-344/ai-use-note.md
cat > compliance/pending-releases/RELEASE-TICKET-REQ-344.md <<'EOF'
# Release Ticket — REQ-344

## Summary
Tracked release without bundle section.
EOF
echo "## Bundled Changes" > compliance/pending-releases/BUNDLED-CHANGES-REQ-344.md
git add . && git commit -q --amend --no-edit
run_validator
assert_grep "missing bundled manifest is reported" "ERROR: Bundled release evidence exists but BUNDLED-CHANGES-REQ-344.json is missing" 1
assert_grep "missing structured fields on bundled evidence are reported" "ERROR: Bundled release evidence is missing bundled field '\\*\\*Core tracked release:\\*\\*'" 1
assert_grep "missing structured fields on ticket are reported" "ERROR: Release ticket is missing bundled field '\\*\\*Core tracked release:\\*\\*'" 1
assert_grep "missing bundled section on ticket is reported" "ERROR: Bundled release evidence exists but the release ticket is missing" 1
assert_grep "missing bundled section on summary is reported" "ERROR: Bundled release evidence exists but test-execution-summary.md is missing" 1
assert_grep "missing bundled section on security summary is reported" "ERROR: Bundled release evidence exists but security-summary.md is missing" 1
assert_grep "missing bundled section on ai note is reported" "ERROR: Bundled release evidence exists but ai-use-note.md is missing" 1
assert_exit "validator exits 1 when bundled artefacts lack required headings" 1
cd "$WORKDIR"

# --- case 10: bundled release context present in canonical artefacts passes ---
echo "Case 10: bundled release artefacts with context headings pass"
make_fixture "$WORKDIR/case10" "Ref: REQ-345"
{
  echo '# RTM'
  echo
  echo '| ID | Description | Status |'
  echo '| --- | --- | --- |'
  echo '| REQ-345 | Bundled release context present | TESTED - PENDING SIGN-OFF |'
} > compliance/RTM.md
mkdir -p compliance/evidence/REQ-345
echo "scope" > compliance/evidence/REQ-345/test-scope.md
echo "plan" > compliance/evidence/REQ-345/test-plan.md
cat > compliance/evidence/REQ-345/test-execution-summary.md <<'EOF'
# Test Execution Summary — REQ-345

## Bundled Release Context
- **Core tracked release:** REQ-345
- **Absorbed predecessor releases:** None
- **Absorbed non-release work:** housekeeping syncs only
- **Why bundled here:** housekeeping consolidation
- **Evidence impact:** core REQ proof unchanged; gate evidence covers full develop state
- **Reviewer impact:** reviewer is approving the tracked REQ plus absorbed housekeeping context
- **Security / risk impact:** None beyond core REQ
- **Reference:** compliance/pending-releases/BUNDLED-CHANGES-REQ-345.md
EOF
cat > compliance/evidence/REQ-345/security-summary.md <<'EOF'
# Security Summary — REQ-345

## Bundled Release Context
- **Core tracked release:** REQ-345
- **Absorbed predecessor releases:** None
- **Absorbed non-release work:** housekeeping syncs only
- **Why bundled here:** housekeeping consolidation
- **Evidence impact:** no extra security evidence beyond the shared gate outputs
- **Reviewer impact:** reviewer should treat the shared gate results as covering the absorbed housekeeping work too
- **Security / risk impact:** None beyond core REQ
- **Reference:** compliance/pending-releases/BUNDLED-CHANGES-REQ-345.md
EOF
cat > compliance/evidence/REQ-345/ai-use-note.md <<'EOF'
# AI Use Record — REQ-345

## Bundled Release Context
- **Core tracked release:** REQ-345
- **Absorbed predecessor releases:** None
- **Absorbed non-release work:** housekeeping syncs only
- **Why bundled here:** housekeeping consolidation
- **Evidence impact:** no AI-generated evidence split beyond the shared bundle context
- **Reviewer impact:** reviewer should read the bundle note as part of the approval scope
- **Security / risk impact:** None beyond core REQ
- **Reference:** compliance/pending-releases/BUNDLED-CHANGES-REQ-345.md
EOF
cat > compliance/pending-releases/RELEASE-TICKET-REQ-345.md <<'EOF'
# Release Ticket — REQ-345

## Summary
Tracked release with bundle section.

## Bundled Changes
- **Core tracked release:** REQ-345
- **Absorbed predecessor releases:** None
- **Absorbed non-release work:** housekeeping syncs only
- **Why bundled here:** housekeeping consolidation
- **Evidence impact:** gate evidence and release reports include the absorbed housekeeping changes
- **Reviewer impact:** approval scope is the tracked REQ plus the absorbed housekeeping context
- **Security / risk impact:** None beyond core REQ
- **Reference:** compliance/pending-releases/BUNDLED-CHANGES-REQ-345.md
EOF
cat > compliance/pending-releases/BUNDLED-CHANGES-REQ-345.md <<'EOF'
## Bundled Changes

- **Core tracked release:** `REQ-345`
- **Bundle manifest:** `BUNDLED-CHANGES-REQ-345.json`
- **Manifest hash:** `sha256:test-bundle-hash`
- **Absorbed predecessor releases:** None
- **Absorbed non-release work:** housekeeping syncs only
- **Why bundled here:** housekeeping consolidation
- **Evidence impact:** gate evidence and release reports include the absorbed housekeeping changes
- **Reviewer impact:** approval scope is the tracked REQ plus the absorbed housekeeping context
- **Security / risk impact:** None beyond core REQ
- **Reference:** commit range `abc123..HEAD`

### Absorbed Non-Release Work

- `abc123` chore: sync templates
EOF
cat > compliance/pending-releases/BUNDLED-CHANGES-REQ-345.json <<'EOF'
{
  "schemaVersion": 1,
  "approvalRelease": { "version": "REQ-345" },
  "coreRelease": { "version": "REQ-345" },
  "members": [],
  "nonReleaseWorkItems": [],
  "manifestHash": "sha256:test-bundle-hash"
}
EOF
git add . && git commit -q --amend --no-edit
run_validator
assert_grep "bundled manifest accepted" "OK: Bundled release manifest present: compliance/pending-releases/BUNDLED-CHANGES-REQ-345.json" 1
assert_grep "bundled ticket section accepted" "OK: Release ticket documents bundled release context" 1
assert_grep "bundled test summary section accepted" "OK: test-execution-summary.md documents bundled release context" 1
assert_grep "bundled security summary section accepted" "OK: security-summary.md documents bundled release context" 1
assert_grep "bundled ai note section accepted" "OK: ai-use-note.md documents bundled release context" 1
assert_grep "structured bundled evidence accepted" "OK: Bundled release evidence carries the required structured fields" 1
assert_exit "validator exits 0 when bundled artefacts carry required headings" 0
cd "$WORKDIR"

# --- case 11: prose-only ai-use-note.md triggers a portal-parseability warning ---
#
# Regression for devaudit#799: the portal's AI Contributors panel expects
# YAML frontmatter (ai_contributors:) or a legacy "AI Tool Used: <tool>"
# line. A note with neither renders fine in the evidence list but fails
# to parse into structured contributor data — as happened for real on
# wawagardenbar-app REQ-098 and REQ-095, neither of which had either
# marker.

echo "Case 11: prose-only ai-use-note.md triggers a portal-parseability warning"
make_fixture "$WORKDIR/case11" "Ref: REQ-350"
{
  echo '# RTM'
  echo
  echo '| ID | Description | Status |'
  echo '| --- | --- | --- |'
  echo '| REQ-350 | Prose-only AI note | TESTED - PENDING SIGN-OFF |'
} > compliance/RTM.md
mkdir -p compliance/evidence/REQ-350
cat > compliance/evidence/REQ-350/ai-use-note.md <<'EOF'
# REQ-350 — AI use note

## What the AI did

- Implemented the feature end to end.
EOF
git add . && git commit -q --amend --no-edit
run_validator
assert_grep "prose-only note flagged" "WARNING: ai-use-note.md has no YAML frontmatter \(ai_contributors:\), legacy 'AI Tool Used:' line, or '\*\*Tool:\*\*' field" 1
cd "$WORKDIR"

# --- case 12: ai-use-note.md with YAML frontmatter is accepted silently ---

echo "Case 12: ai-use-note.md with YAML frontmatter is accepted silently"
make_fixture "$WORKDIR/case12" "Ref: REQ-351"
{
  echo '# RTM'
  echo
  echo '| ID | Description | Status |'
  echo '| --- | --- | --- |'
  echo '| REQ-351 | Structured AI note | TESTED - PENDING SIGN-OFF |'
} > compliance/RTM.md
mkdir -p compliance/evidence/REQ-351
cat > compliance/evidence/REQ-351/ai-use-note.md <<'EOF'
---
ai_contributors:
  - tool: "Claude Sonnet 5"
    version: "claude-sonnet-5"
    session_id: "abc123"
    date_range: "2026-08-01 to 2026-08-01"
    commits: ["abc1234"]
---

# AI Use Record — REQ-351

## Summary
Implemented the feature end to end.
EOF
git add . && git commit -q --amend --no-edit
run_validator
assert_grep "structured note not flagged" "WARNING: ai-use-note.md has no YAML frontmatter" 0
cd "$WORKDIR"

# --- case 13: prose ai-use-note.md with a **Tool:** field is accepted silently ---
#
# Regression for devaudit-installer#653 / devaudit#799: the portal's parser
# was widened to accept a "**Tool:**" bold field in an otherwise free-form
# note, not just YAML frontmatter or the legacy line. This check must not
# flag notes that use the format Step 5 now explicitly requires.

echo "Case 13: prose ai-use-note.md with a **Tool:** field is accepted silently"
make_fixture "$WORKDIR/case13" "Ref: REQ-352"
{
  echo '# RTM'
  echo
  echo '| ID | Description | Status |'
  echo '| --- | --- | --- |'
  echo '| REQ-352 | Prose AI note with Tool field | TESTED - PENDING SIGN-OFF |'
} > compliance/RTM.md
mkdir -p compliance/evidence/REQ-352
cat > compliance/evidence/REQ-352/ai-use-note.md <<'EOF'
# REQ-352 — AI use note

**Tool:** Claude Code
**Model:** claude-sonnet-5

## What the AI did

- Implemented the feature end to end.
EOF
git add . && git commit -q --amend --no-edit
run_validator
assert_grep "prose note with Tool field not flagged" "WARNING: ai-use-note.md has no YAML frontmatter" 0
cd "$WORKDIR"

# --- case 14: @e2e-deferred annotation must not trip the E2E-named-in-plan check ---
#
# Regression for devaudit-installer#745: \be2e\b matched inside the literal
# "@e2e-deferred:" annotation (@ and - are non-word chars, so \b is satisfied
# on both sides of "e2e"), so a plan that correctly followed
# Implementation_Plan_TEMPLATE.md's documented negative-case convention was
# treated as if it named Playwright/E2E as a verification method, then
# ERRORed for having no tagged e2e/ spec — penalizing the exact thing the
# template asks for.

echo "Case 14: @e2e-deferred annotation is not mistaken for an E2E verification claim"
make_fixture "$WORKDIR/case14" "Ref: REQ-360"
{
  echo '# RTM'
  echo
  echo '| ID | Description | Status |'
  echo '| --- | --- | --- |'
  echo '| REQ-360 | Backend-only, no UI surface | TESTED - PENDING SIGN-OFF |'
} > compliance/RTM.md
mkdir -p compliance/evidence/REQ-360
cat > compliance/evidence/REQ-360/implementation-plan.md <<'EOF'
# Implementation Plan — REQ-360

## 4. E2E test coverage

`@e2e-deferred: no UI-facing files touched by this REQ's diff (services/financial-report-service.ts only) — no e2e/visual-regression surface exists to test.`
EOF
echo "scope" > compliance/evidence/REQ-360/test-scope.md
echo "plan" > compliance/evidence/REQ-360/test-plan.md
echo "summary" > compliance/evidence/REQ-360/test-execution-summary.md
touch compliance/pending-releases/RELEASE-TICKET-REQ-360.md
git add . && git commit -q --amend --no-edit
run_validator
assert_grep "no E2E-verification-method ERROR for a deferred plan" 'ERROR: implementation-plan.md names Playwright/E2E as a verification method' 0
assert_exit "validator does not fail on a correctly-deferred plan" 0
cd "$WORKDIR"

# Regression guard: a plan that genuinely names Playwright/E2E as its
# verification method, with no tagged spec, must still be caught.
echo "Case 15: plan naming Playwright with no tagged spec still ERRORs"
make_fixture "$WORKDIR/case15" "Ref: REQ-361"
{
  echo '# RTM'
  echo
  echo '| ID | Description | Status |'
  echo '| --- | --- | --- |'
  echo '| REQ-361 | UI change, verified via Playwright | TESTED - PENDING SIGN-OFF |'
} > compliance/RTM.md
mkdir -p compliance/evidence/REQ-361
cat > compliance/evidence/REQ-361/implementation-plan.md <<'EOF'
# Implementation Plan — REQ-361

## 4. E2E test coverage

Verified via Playwright end-to-end tests covering the new checkout flow.
EOF
echo "scope" > compliance/evidence/REQ-361/test-scope.md
echo "plan" > compliance/evidence/REQ-361/test-plan.md
echo "summary" > compliance/evidence/REQ-361/test-execution-summary.md
touch compliance/pending-releases/RELEASE-TICKET-REQ-361.md
git add . && git commit -q --amend --no-edit
run_validator
assert_grep "E2E-verification-method ERROR still fires for a real claim" 'ERROR: implementation-plan.md names Playwright/E2E as a verification method' 1
assert_exit "validator fails when Playwright is named but no spec tags it" 1
cd "$WORKDIR"

echo
echo "=== validate-compliance-artifacts.test.sh: $PASS passed, $FAIL failed ==="
[ "$FAIL" -eq 0 ]
