#!/usr/bin/env bash
# Harness for scripts/record-audit.py (grounding-audit Phase 1 extractor).
# Read-only over throwaway fixture trees; asserts contradiction detection, the
# counted/not-counted surface rules, reference integrity, census classes, and
# the 0/1/2 exit contract. All runs use --no-git (fixtures are not git repos).
set -u
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT="$HERE/../../scripts/record-audit.py"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
PASS=0; FAIL=0
ok()   { PASS=$((PASS+1)); }
bad()  { FAIL=$((FAIL+1)); echo "  FAIL: $1"; }

assert_exit() { # <desc> <want-rc> <cmd...>
  local desc="$1" want="$2"; shift 2
  "$@" >/dev/null 2>&1; local got=$?
  [ "$got" -eq "$want" ] && ok || bad "$desc (want exit $want, got $got)"
}
assert_report() { # <desc> <fixed-substring> <report-file>
  local desc="$1" want="$2" f="$3"
  grep -qF -- "$want" "$f" && ok || bad "$desc (want '$want' in $f)"
}
assert_not_report() { # <desc> <fixed-substring> <report-file>
  local desc="$1" want="$2" f="$3"
  grep -qF -- "$want" "$f" && bad "$desc (must NOT contain '$want')" || ok
}

# ---- fixture A: contradictions + broken refs --------------------------------
A="$WORK/a"; mkdir -p "$A/docs/audit" "$A/runbooks" "$WORK/mem"
cat > "$A/docs/design-decisions.md" <<'EOF'
# decisions
D-001 something ADOPTED.
EOF
cat > "$A/docs/readiness.md" <<'EOF'
The outer plan reads 5 add / 2 in-place / 6 destroy today.
Earlier note: outer plan UNCHANGED (5/0/6, create-only content).
Re-confirm 5-add/(0-or-2)-change/6-destroy-empty before apply.
Stage 3 is COMPLETE per this doc. Pin: OpenTofu v1.12.3.
See docs/absent.md and D-999 for details. This gate is blocking.
EOF
cat > "$A/docs/notes.md" <<'EOF'
Stage 3 is HELD per this doc. The binary is tofu 1.12.4.
MAAS `thing` (10.12.4.100) must not become a version row.
Egress through the OPNsense edge. On it: MAAS 3.7.2 region+rack.
The exit contract is 0/1/2 for the ROUTER gate poll.
Stage 3 remains OPEN per the workflow doc.
That earlier ruling was SUPERSEDED by a later amendment (prose, legal).
EOF
cat > "$A/docs/vocab.md" <<'EOF'
**Status:** DEPLOY-READY.
**Status:** SUPERSEDED.
| G4 | verify pair | TO RUN |
EOF
cat > "$A/docs/changelog-20260101-old.md" <<'EOF'
Plan: 7 to add, 2 to change, 7 to destroy.
EOF
cat > "$A/docs/audit/quote.md" <<'EOF'
Charter quotes the defect: plan 9/9/9 seen once.
EOF
mkdir -p "$A/docs/archive"
cat > "$A/docs/archive/old-changelog.md" <<'EOF'
Archived history: the plan read 8/8/8 back then.
EOF
cat > "$A/README.md" <<'EOF'
Nothing here.
EOF
cat > "$WORK/mem/mem-note.md" <<'EOF'
Memory claim: plan 1/1/1 recorded here.
EOF

REPORT_A="$WORK/a-report.md"
assert_exit "fixture A -> exit 1 (HOLD)" 1 \
  python3 "$SCRIPT" --repo "$A" --memory-dir "$WORK/mem" --out "$REPORT_A" --no-git
assert_report "plan contradiction group"        "CONTRADICTION: plan"           "$REPORT_A"
assert_report "wordy plan value present"        "5/2/6"                          "$REPORT_A"
assert_report "compact plan value present"      "5/0/6"                          "$REPORT_A"
assert_report "hyphen or-form value present"    "5/0-or-2/6"                     "$REPORT_A"
assert_report "tofu-output plan value present"  "7/2/7"                          "$REPORT_A"
assert_report "version contradiction (tofu)"    "CONTRADICTION: version \`tofu\`" "$REPORT_A"
assert_report "stage-status contradiction"      "CONTRADICTION: status \`stage-3\`" "$REPORT_A"
assert_report "broken path ref detected"        "docs/absent.md"                 "$REPORT_A"
assert_report "orphan D-number detected"        "D-999"                          "$REPORT_A"
assert_report "unreferenced changelog listed"   "docs/changelog-20260101-old.md" "$REPORT_A"
assert_report "memory file in census"           "memory/mem-note.md"             "$REPORT_A"
assert_report "gate assertion captured"         "blocking"                       "$REPORT_A"
assert_not_report "IP fragment is not a MAAS version row" "| 10.12 |"            "$REPORT_A"
assert_report     "cross-product line credits MAAS"   "| version | maas | 3.7.2 |" "$REPORT_A"
assert_not_report "cross-product bleed (opnsense=maas ver)" "| opnsense | 3.7.2 |" "$REPORT_A"
assert_not_report "exit-code triple not a plan row (ROUTER!=outer)" "0/1/2"      "$REPORT_A"
# audit-class and memory rows must not create counted groups: 9/9/9 and 1/1/1
# appear only as not-counted rows, never as a CONTRADICTION value list entry.
assert_not_report "audit-quoted 9/9/9 not a counted value" "\`9/9/9\`"           "$REPORT_A"
assert_not_report "memory 1/1/1 not a counted value"       "\`1/1/1\`"           "$REPORT_A"
# docs/archive/ excluded from counting (operator-ruled 2026-07-19, Batch 6.1)
assert_not_report "archived 8/8/8 not a counted value"     "\`8/8/8\`"           "$REPORT_A"
assert_report     "archived file still inventoried"        "docs/archive/old-changelog.md" "$REPORT_A"

# ---- vocab scan (Batch 0.4: legal token set, Option A + H1) -----------------
assert_report "vocab section present"          "Status-token vocabulary scan"    "$REPORT_A"
assert_report "legal OPEN classified"          "| OPEN | legal |"                "$REPORT_A"
assert_report "OPEN feeds stage-3 contradiction" "| OPEN | docs/notes.md:5"      "$REPORT_A"
assert_report "DEPLOY-READY flagged illegal"   "| DEPLOY-READY | illegal/status-pos |" "$REPORT_A"
assert_report "TO RUN in table row flagged"    "| TO RUN | illegal/status-pos |" "$REPORT_A"
assert_report "event word in Status line flagged" "| SUPERSEDED | event/status-pos |" "$REPORT_A"
# prose event word is legitimate row-history vocabulary -- never a vocab row
assert_not_report "prose SUPERSEDED not flagged" "| SUPERSEDED | event/status-pos | docs/notes.md" "$REPORT_A"

# ---- fixture B: clean -> exit 0 ---------------------------------------------
B="$WORK/b"; mkdir -p "$B/docs"
cat > "$B/docs/one.md" <<'EOF'
The outer plan reads 5 add / 0 change / 6 destroy. Pin: OpenTofu v1.12.3.
**Status:** READY.
EOF
REPORT_B="$WORK/b-report.md"
assert_exit "clean fixture -> exit 0 (vocab flag is report-only)" 0 \
  python3 "$SCRIPT" --repo "$B" --out "$REPORT_B" --no-git
assert_report "clean summary zero groups" "CONTRADICTION GROUPS (counted surfaces): 0" "$REPORT_B"
assert_report "vocab defect flagged without changing exit" "| READY | illegal/status-pos |" "$REPORT_B"
assert_not_report "no event rows in B" "| SUPERSEDED |" "$REPORT_B"

# ---- preconditions ----------------------------------------------------------
assert_exit "absent repo -> exit 2" 2 \
  python3 "$SCRIPT" --repo "$WORK/nope" --out "$WORK/x.md" --no-git
assert_exit "bad memory dir -> exit 2" 2 \
  python3 "$SCRIPT" --repo "$B" --memory-dir "$WORK/nope" --out "$WORK/x.md" --no-git

# ---- determinism: identical reruns produce identical bytes ------------------
R1="$WORK/d1.md"; R2="$WORK/d2.md"
python3 "$SCRIPT" --repo "$A" --memory-dir "$WORK/mem" --out "$R1" --no-git >/dev/null 2>&1
python3 "$SCRIPT" --repo "$A" --memory-dir "$WORK/mem" --out "$R2" --no-git >/dev/null 2>&1
if cmp -s "$R1" "$R2"; then ok; else bad "non-deterministic report output"; fi

TOTAL=$((PASS+FAIL))
echo "record-audit: $PASS/$TOTAL PASS"
[ "$FAIL" -eq 0 ]
