#!/bin/sh
# Unit test for the source-only mirror packer (ci/publish/mirror-public.sh, ADR-0008).
# Asserts the two invariants the public mirror depends on: (1) the enumerated exclusions —
# every file carrying release/migration plumbing or the private-home name — are absent from
# the snapshot tree; (2) the leak guard hard-fails a tree that smuggles a private-home
# reference in content OR filename. Plan mode only: no network, no refs created.
# [unit->REQ-DIST-SOURCE-MIRROR]
set -u
ROOT=$(CDPATH= cd "$(dirname "$0")/.." && pwd)
cd "$ROOT" || exit 1
SCRIPT="$ROOT/ci/publish/mirror-public.sh"
rc=0
fail() { printf 'FAIL: %s\n' "$1"; rc=1; }

# 1. Plan mode on HEAD passes and its snapshot tree drops every enumerated exclusion.
out=$(sh "$SCRIPT" HEAD 2>&1) || { fail "plan mode on HEAD should pass"; printf '%s\n' "$out"; }
tree=$(printf '%s\n' "$out" | sed -n 's/^snapshot tree: \([0-9a-f]*\).*/\1/p')
[ -n "$tree" ] || fail "plan output must name the snapshot tree"
if [ -n "$tree" ]; then
  listing=$(git ls-tree -r --name-only "$tree")
  for p in adapter/claude-spt.toml ci/publish docs/RELEASE-RUNBOOK.md \
           docs/adr/0008-development-relocates-to-a-private-home.md AGENTS.md \
           traceable-reqs.toml docs/TRACEABILITY.md README.md \
           docs/plans/MIGRATION-RELEASE-PLAN.md adapter/strings/skills/setup.md \
           plugin/sptc/skills/setup/SKILL.md; do
    if printf '%s\n' "$listing" | grep -q "^$p"; then
      fail "excluded path leaked into snapshot tree: $p"
    else
      echo "ok   excluded from snapshot: $p"
    fi
  done
fi

# 1b. THE WORKING TREE, not just HEAD. The guard packs a committish, which is correct for what it
# does — but that makes it blind to work not yet committed, so running gates before `git add` guards
# a tree that does not contain the new file and the leak lands in the commit anyway (field: a plan
# file naming the private home shipped in a commit whose gates had "passed", 2026-08-04). Gates
# before the commit are not gates on what the commit contains. Build a tree from the working state —
# tracked edits AND untracked files, .gitignore honoured — in a throwaway index and guard THAT.
TMPIDX=$(mktemp "${TMPDIR:-/tmp}/sptc-mirrortest-wt.XXXXXX") || { fail mktemp; exit "$rc"; }
export GIT_INDEX_FILE="$TMPIDX"
git read-tree "HEAD^{tree}" 2>/dev/null
git add -A 2>/dev/null
wt=$(git write-tree 2>/dev/null)
unset GIT_INDEX_FILE
rm -f "$TMPIDX"
if [ -n "$wt" ]; then
  if out=$(sh "$SCRIPT" "$wt" 2>&1); then
    echo "ok   working tree (uncommitted included) is leak-clean"
  else
    fail "leak guard fires on the WORKING TREE — a private-home reference is staged or untracked"
    # -A2: the guard prints the offending PATH on the line AFTER its LEAK header, and a bare
    # `grep leak` drops exactly that line — leaving a failure that says something leaked but
    # not what. Half an instrument reads like a whole one right up until you need it.
    printf '%s\n' "$out" | grep -i -A2 leak
  fi
else
  fail "could not build a working-tree snapshot to guard"
fi

# 2. Leak guard fires on planted CONTENT (a blob naming the private home) — build the poisoned
# tree in a throwaway index; the worktree and real index stay untouched.
TMPIDX=$(mktemp "${TMPDIR:-/tmp}/sptc-mirrortest-idx.XXXXXX") || { fail mktemp; exit "$rc"; }
export GIT_INDEX_FILE="$TMPIDX"
git read-tree "HEAD^{tree}"
blob=$(printf 'points at BigscreenVR private home\n' | git hash-object -w --stdin)
git update-index --add --cacheinfo "100644,$blob,leak-probe.txt"
poisoned=$(git write-tree)
unset GIT_INDEX_FILE
rm -f "$TMPIDX"
if out=$(sh "$SCRIPT" "$poisoned" 2>&1); then
  fail "leak guard must fail a tree with planted private-home content"
else
  case "$out" in *LEAK*leak-probe.txt*|*leak-probe.txt*) echo "ok   content leak guard fires (names the file)";;
    *) fail "leak failure must name the offending file"; printf '%s\n' "$out";; esac
fi

# 3. Leak guard fires on a planted FILENAME even with clean content.
TMPIDX=$(mktemp "${TMPDIR:-/tmp}/sptc-mirrortest-idx2.XXXXXX") || { fail mktemp; exit "$rc"; }
export GIT_INDEX_FILE="$TMPIDX"
git read-tree "HEAD^{tree}"
blob=$(printf 'clean content\n' | git hash-object -w --stdin)
git update-index --add --cacheinfo "100644,$blob,notes-spt-bs.md"
poisoned=$(git write-tree)
unset GIT_INDEX_FILE
rm -f "$TMPIDX"
if sh "$SCRIPT" "$poisoned" >/dev/null 2>&1; then
  fail "leak guard must fail a tree with a private-home-named file"
else
  echo "ok   filename leak guard fires"
fi

# 4. The root JIT-plan CLASS is excluded as a PREDICATE, not as a list of the plans that happen
#    to exist today. Both halves are planted with names that appear NOWHERE in EXCLUDES, because
#    a class rule is only worth anything on the case nobody enumerated - which is precisely the
#    case that went unguarded from d2355ed until 2026-08-28.
TMPIDX=$(mktemp "${TMPDIR:-/tmp}/sptc-mirrortest-idx3.XXXXXX") || { fail mktemp; exit "$rc"; }
export GIT_INDEX_FILE="$TMPIDX"
git read-tree "HEAD^{tree}"
# 4a. A never-before-seen ROOT plan, carrying the private-home literal a real plan cites freely.
#     If the class predicate holds it is stripped before the leak guard ever sees it.
leaky=$(printf 'cites the BigscreenVR board by coordinate\n' | git hash-object -w --stdin)
git update-index --add --cacheinfo "100644,$leaky,FUTURE-UNNAMED-PLAN.md"
# 4b. A never-before-seen plan under docs/plans/, which is PUBLIC product and must SURVIVE.
#     Guards the opposite error: `*-PLAN.md` without `:(glob)` matches at any depth and would
#     silently unpublish all of docs/plans/ while still reporting a clean mirror.
clean=$(printf 'ordinary public plan content\n' | git hash-object -w --stdin)
git update-index --add --cacheinfo "100644,$clean,docs/plans/FUTURE-UNNAMED-PLAN.md"
planted=$(git write-tree)
unset GIT_INDEX_FILE
rm -f "$TMPIDX"
snap=$(sh "$SCRIPT" "$planted" 2>/dev/null | awk '/^snapshot tree:/{print $3}')
if [ -z "$snap" ]; then
  fail "planted root plan must be EXCLUDED, not tripped as a leak (no snapshot produced)"
else
  # Assert the rig actually planted, so neither branch below can pass vacuously on an empty tree.
  if git ls-tree -r --name-only "$planted" | grep -qxF 'FUTURE-UNNAMED-PLAN.md'; then
    if git ls-tree -r --name-only "$snap" | grep -qxF 'FUTURE-UNNAMED-PLAN.md'; then
      fail "an unenumerated ROOT *-PLAN.md survived into the snapshot"
    else
      echo "ok   root plan class excludes an unenumerated plan"
    fi
    if git ls-tree -r --name-only "$snap" | grep -qxF 'docs/plans/FUTURE-UNNAMED-PLAN.md'; then
      echo "ok   docs/plans/*-PLAN.md is NOT swept up by the root class"
    else
      fail "the root class over-reached and unpublished docs/plans/"
    fi
  else
    fail "test rig did not plant the probe file"
  fi
fi

exit "$rc"
