#!/usr/bin/env bash
# Zero-dep regression tests for finish-branch.sh. Builds throwaway git repos (main + worktree, some
# with a bare origin) and asserts each gate FIRES on its bad input and PASSES on good input. Every
# assert encodes a real failure the primitive exists to catch — especially the irreversible ones
# (gated ff to main, cleanup ordering, asset-destruction guard). Run: bash test-finish-branch.sh
set -uo pipefail
LIB="$(cd "$(dirname "$0")" && pwd)/finish-branch.sh"
TEST_GATE_LOG=$(mktemp /tmp/fb-local-gate-log-XXXX)
TEST_GATE_BIN=$(mktemp /tmp/fb-local-gate-bin-XXXX)
cat > "$TEST_GATE_BIN" <<'EOF'
#!/usr/bin/env bash
printf 'called\n' >> "$FINISH_BRANCH_GATE_TEST_LOG"
while [[ $# -gt 0 && "$1" != "--" ]]; do shift; done
[[ ${1:-} == "--" ]] && shift
exec "$@"
EOF
chmod +x "$TEST_GATE_BIN"
export FINISH_BRANCH_LOCAL_GATE="$TEST_GATE_BIN" FINISH_BRANCH_GATE_TEST_LOG="$TEST_GATE_LOG"
PASS=0; FAIL=0
ok()  { PASS=$((PASS+1)); printf '  ok   %s\n' "$1"; }
bad() { FAIL=$((FAIL+1)); printf '  FAIL %s\n     %s\n' "$1" "$2"; }
jget() { printf '%s' "$1" | python3 -c "import sys,json;d=json.load(sys.stdin);print($2)"; }
run() { bash "$LIB" "$@"; }
gconf() { git -C "$1" config user.email t@t.t; git -C "$1" config user.name t; git -C "$1" config commit.gpgsign false; }

# Build: bare origin + main clone (seeded file.txt='base', pushed) + a worktree on branch 'feat'.
# Echoes "MAIN|WT|BRANCH". feat starts level with main.
mkproj() {
  local bare work wt
  bare=$(mktemp -d /tmp/fb-bare-XXXX); git -C "$bare" init -q --bare -b main
  work=$(mktemp -d /tmp/fb-work-XXXX); git -C "$work" init -q -b main; gconf "$work"
  printf 'wtree/\n' > "$work/.gitignore"   # mirror multideal: worktrees live in a gitignored path
  printf 'base\n' > "$work/file.txt"; printf 'keep\n' > "$work/other.txt"
  git -C "$work" add -A; git -C "$work" commit -q -m seed
  git -C "$work" remote add origin "$bare"; git -C "$work" push -q -u origin main 2>/dev/null
  wt="$work/wtree"; git -C "$work" worktree add -q "$wt" -b feat 2>/dev/null
  printf '%s|%s|%s' "$work" "$wt" "feat"
}
# commit on the branch worktree
wt_commit() { local wt=$1 f=$2 c=$3 m=$4; printf '%s\n' "$c" > "$wt/$f"; git -C "$wt" add -A; git -C "$wt" commit -q -m "$m"; }
# commit on main
main_commit() { local main=$1 f=$2 c=$3 m=$4; printf '%s\n' "$c" > "$main/$f"; git -C "$main" add -A; git -C "$main" commit -q -m "$m"; }
cleanproj() { local main=$1; rm -rf "$main"; }
optin() { git -C "$1" config harness.landQueue stage-a; }
advance_origin() {
  local main=$1 file=$2 content=$3 message=$4 bare tmp
  bare=$(git -C "$main" remote get-url origin)
  tmp=$(mktemp -d /tmp/fb-advance-XXXX)
  git clone -q "$bare" "$tmp/repo"; gconf "$tmp/repo"
  printf '%s\n' "$content" > "$tmp/repo/$file"
  git -C "$tmp/repo" add -A; git -C "$tmp/repo" commit -q -m "$message"
  git -C "$tmp/repo" push -q origin main 2>/dev/null
  rm -rf "$tmp"
}

echo "finish-branch tests:"

# ── preflight ──────────────────────────────────────────────────────────────────────────────
# 1. clean main → ready=true, no blockers
IFS='|' read -r M W B <<<"$(mkproj)"
j=$(run preflight "$M" "$B" "$W")
[[ "$(jget "$j" 'd["ready"]')" == "True" && "$(jget "$j" 'len(d["blockers"])')" == "0" ]] \
  && ok "preflight: clean main → ready" || bad "preflight clean" "$j"
cleanproj "$M"

# 2. dirty tracked file on main → dirty-main-worktree fires, ready=false
IFS='|' read -r M W B <<<"$(mkproj)"
printf 'contaminated\n' > "$M/file.txt"   # modified tracked, uncommitted
j=$(run preflight "$M" "$B" "$W")
[[ "$(jget "$j" 'd["ready"]')" == "False" \
   && "$(jget "$j" 'any(x["name"]=="dirty-main-worktree" for x in d["blockers"])')" == "True" ]] \
  && ok "preflight: dirty main fires" || bad "preflight dirty" "$j"
cleanproj "$M"

# 3. pending merge on main → pending-merge-on-main fires
IFS='|' read -r M W B <<<"$(mkproj)"
git -C "$M" checkout -q -b other; main_commit "$M" file.txt other-line "other"; git -C "$M" checkout -q main
main_commit "$M" file.txt main-line "main"
git -C "$M" merge other >/dev/null 2>&1   # conflicts → leaves MERGE_HEAD
j=$(run preflight "$M" "$B" "$W")
[[ "$(jget "$j" 'any(x["name"]=="pending-merge-on-main" for x in d["blockers"])')" == "True" ]] \
  && ok "preflight: pending merge fires" || bad "preflight pending-merge" "$j"
cleanproj "$M"

# ── sync-base ──────────────────────────────────────────────────────────────────────────────
# 4. branch already current with base → up-to-date
IFS='|' read -r M W B <<<"$(mkproj)"
j=$(run sync-base "$M" "$B" main "$W")
[[ "$(jget "$j" 'd["status"]')" == "up-to-date" ]] \
  && ok "sync-base: up-to-date when base is ancestor" || bad "sync-base up-to-date" "$j"
cleanproj "$M"

# 5. base advanced on a DIFFERENT file → clean merge into branch
IFS='|' read -r M W B <<<"$(mkproj)"
main_commit "$M" other.txt main-only-change "main advances other.txt"
wt_commit "$W" file.txt branch-change "branch edits file.txt"
j=$(run sync-base "$M" "$B" main "$W")
[[ "$(jget "$j" 'd["status"]')" == "clean" ]] \
  && ok "sync-base: clean merge on disjoint files" || bad "sync-base clean" "$j"
cleanproj "$M"

# 6. base and branch edit the SAME line → conflict, files listed, merge left in progress
IFS='|' read -r M W B <<<"$(mkproj)"
main_commit "$M" file.txt main-line "main edits file.txt"
wt_commit "$W" file.txt branch-line "branch edits file.txt"
j=$(run sync-base "$M" "$B" main "$W")
[[ "$(jget "$j" 'd["status"]')" == "conflict" \
   && "$(jget "$j" '"file.txt" in d["files"]')" == "True" \
   && -f "$W/.git" || -d "$W" ]] \
  && ok "sync-base: conflict surfaced, files listed" || bad "sync-base conflict" "$j"
# merge must be LEFT in progress for the agent (markers present)
git -C "$W" grep -q '^<<<<<<< ' -- file.txt 2>/dev/null \
  && ok "sync-base: conflict left in worktree for agent" || bad "sync-base leaves conflict" "no markers"
cleanproj "$M"

# ── land-merge ─────────────────────────────────────────────────────────────────────────────
# 7. happy: branch ahead, clean, tests pass → landed, main fast-forwarded to branch head
IFS='|' read -r M W B <<<"$(mkproj)"
wt_commit "$W" file.txt branch-feature "feat work"
bh=$(git -C "$W" rev-parse HEAD)
j=$(run land-merge "$M" "$B" main "$W" true)
[[ "$(jget "$j" 'd["status"]')" == "landed" \
   && "$(git -C "$M" rev-parse HEAD)" == "$bh" ]] \
  && ok "land-merge: gated ff lands verified head on main" || bad "land-merge happy" "$j / main=$(git -C "$M" rev-parse HEAD) bh=$bh"
cleanproj "$M"

# 8. tests FAIL → no land; main must be UNCHANGED (the core guarantee)
IFS='|' read -r M W B <<<"$(mkproj)"
m0=$(git -C "$M" rev-parse HEAD)
wt_commit "$W" file.txt branch-feature "feat work"
j=$(run land-merge "$M" "$B" main "$W" false)
[[ "$(jget "$j" 'd["status"]')" == "tests-failed" \
   && "$(git -C "$M" rev-parse HEAD)" == "$m0" ]] \
  && ok "land-merge: failing tests block ff, main untouched" || bad "land-merge tests-failed" "$j / main moved?"
cleanproj "$M"

# 9. conflict markers in a committed file (tree clean) → conflict-markers fires before tests
IFS='|' read -r M W B <<<"$(mkproj)"
printf 'a\n<<<<<<< HEAD\nb\n>>>>>>> x\n' > "$W/file.txt"; git -C "$W" add -A; git -C "$W" commit -q -m "oops committed markers"
m0=$(git -C "$M" rev-parse HEAD)
j=$(run land-merge "$M" "$B" main "$W" true)
[[ "$(jget "$j" 'd["status"]')" == "conflict-markers" \
   && "$(git -C "$M" rev-parse HEAD)" == "$m0" ]] \
  && ok "land-merge: conflict markers block ff" || bad "land-merge markers" "$j"
cleanproj "$M"

# 10. dirty worktree (unresolved index) → dirty-tree fires
IFS='|' read -r M W B <<<"$(mkproj)"
wt_commit "$W" file.txt branch-feature "feat work"
printf 'uncommitted edit\n' > "$W/file.txt"   # dirty, uncommitted
j=$(run land-merge "$M" "$B" main "$W" true)
[[ "$(jget "$j" 'd["status"]')" == "dirty-tree" ]] \
  && ok "land-merge: dirty worktree blocks ff" || bad "land-merge dirty-tree" "$j"
cleanproj "$M"

# 11. base not an ancestor (branch never synced, main advanced) → not-ff fires
IFS='|' read -r M W B <<<"$(mkproj)"
wt_commit "$W" file.txt branch-feature "feat work"
main_commit "$M" other.txt main-diverges "main advances independently"
j=$(run land-merge "$M" "$B" main "$W" true)
[[ "$(jget "$j" 'd["status"]')" == "not-ff" ]] \
  && ok "land-merge: non-ancestor base → not-ff (sync first)" || bad "land-merge not-ff" "$j"
cleanproj "$M"

# ── land-pr fail-closed gates (real gh PR creation not exercised — no GitHub throwaway) ──────
# 12. no origin remote → no-remote fires, main never touched
IFS='|' read -r M W B <<<"$(mkproj)"
git -C "$M" remote remove origin
wt_commit "$W" file.txt branch-feature "feat work"
j=$(run land-pr "$M" "$B" main "$W" true)
[[ "$(jget "$j" 'd["status"]')" == "no-remote" ]] \
  && ok "land-pr: missing remote fails closed" || bad "land-pr no-remote" "$j"
cleanproj "$M"

# Existing PRs must still run the gate and push the current branch head.
IFS='|' read -r M W B <<<"$(mkproj)"
wt_commit "$W" file.txt branch-feature "feat work"
GHBIN=$(mktemp -d /tmp/fb-ghbin-XXXX)
cat > "$GHBIN/gh" <<'EOF'
#!/usr/bin/env bash
case "$1 $2" in
  "auth status") exit 0;;
  "pr list") printf '42\n'; exit 0;;
  "pr create") exit 99;;
  *) exit 0;;
esac
EOF
chmod +x "$GHBIN/gh"
branch_head=$(git -C "$W" rev-parse HEAD)
out=$(PATH="$GHBIN:$PATH" run land-pr "$M" "$B" main "$W" true 2>/dev/null)
remote_head=$(git -C "$M" rev-parse --verify "origin/$B" 2>/dev/null || true)
[[ "$(printf '%s' "$out" | grep -c '"status":"pr-exists"')" == "1" \
   && "$(jget "$out" 'd["number"]')" == "42" \
   && "$remote_head" == "$branch_head" ]] \
  && ok "land-pr: existing PR branch is tested and updated" \
  || bad "land-pr existing PR update" "status=$out remote_head=$remote_head branch_head=$branch_head"
rm -rf "$GHBIN"; cleanproj "$M"

# 13. dirty worktree → dirty-tree fires before any push
IFS='|' read -r M W B <<<"$(mkproj)"
printf 'uncommitted\n' > "$W/file.txt"
j=$(run land-pr "$M" "$B" main "$W" true)
[[ "$(jget "$j" 'd["status"]')" == "dirty-tree" ]] \
  && ok "land-pr: dirty worktree fails closed" || bad "land-pr dirty-tree" "$j"
cleanproj "$M"

# ── cleanup ──────────────────────────────────────────────────────────────────────────────────
# gh pr list timeout fails closed with a diagnostic instead of continuing to tests/push.
IFS='|' read -r M W B <<<"$(mkproj)"
wt_commit "$W" file.txt branch-feature "feat work"
GHBIN=$(mktemp -d /tmp/fb-ghbin-XXXX)
cat > "$GHBIN/gh" <<'EOF'
#!/usr/bin/env bash
case "$1 $2" in
  "auth status") exit 0;;
  "pr list") sleep 2;;
esac
EOF
chmod +x "$GHBIN/gh"
j=$(PATH="$GHBIN:$PATH" FINISH_BRANCH_GH_TIMEOUT_SECONDS=1 run land-pr "$M" "$B" main "$W" true 2>/dev/null)
[[ "$(jget "$j" 'd["status"]')" == "pr-list-failed" \
   && "$(jget "$j" '"timed out" in d["detail"]')" == "True" ]] \
  && ok "land-pr: gh pr list timeout fails closed" || bad "land-pr pr-list timeout" "$j"
rm -rf "$GHBIN"; cleanproj "$M"

# Captured test gate is bounded and reports timeout without moving main.
IFS='|' read -r M W B <<<"$(mkproj)"
m0=$(git -C "$M" rev-parse HEAD)
wt_commit "$W" file.txt branch-feature "feat work"
j=$(FINISH_BRANCH_TEST_TIMEOUT_SECONDS=1 run land-merge "$M" "$B" main "$W" 'sleep 2' 2>/dev/null)
[[ "$(jget "$j" 'd["status"]')" == "tests-failed" \
   && "$(jget "$j" '"timed out" in d["detail"]')" == "True" \
   && "$(git -C "$M" rev-parse HEAD)" == "$m0" ]] \
  && ok "land-merge: captured test timeout fails closed" || bad "land-merge test timeout" "$j"
cleanproj "$M"

# 14. asset guard: worktree tmp/ holds files → assets-present, worktree NOT removed
IFS='|' read -r M W B <<<"$(mkproj)"
mkdir -p "$W/tmp"; printf 'expensive\n' > "$W/tmp/cache.bin"
j=$(run cleanup "$M" "$B" "$W")
[[ "$(jget "$j" 'd["status"]')" == "assets-present" && -d "$W" ]] \
  && ok "cleanup: asset guard blocks irreversible remove" || bad "cleanup assets-present" "$j / wt gone?"
cleanproj "$M"

# 15. happy: land then cleanup → worktree removed AND branch deleted, in order
IFS='|' read -r M W B <<<"$(mkproj)"
wt_commit "$W" file.txt branch-feature "feat work"
run land-merge "$M" "$B" main "$W" true >/dev/null
j=$(run cleanup "$M" "$B" "$W")
gone_wt=$([[ -d "$W" ]] && echo no || echo yes)
gone_br=$(git -C "$M" rev-parse --verify --quiet "$B" >/dev/null 2>&1 && echo no || echo yes)
[[ "$(jget "$j" 'd["status"]')" == "cleaned" && "$gone_wt" == "yes" && "$gone_br" == "yes" ]] \
  && ok "cleanup: worktree removed + branch deleted (ordered)" || bad "cleanup happy" "$j / wt_gone=$gone_wt br_gone=$gone_br"
cleanproj "$M"

# 16. --keep-branch (PR mode): worktree removed but branch KEPT
IFS='|' read -r M W B <<<"$(mkproj)"
wt_commit "$W" file.txt branch-feature "feat work"
j=$(run cleanup "$M" "$B" "$W" --keep-branch --no-push)
kept=$(git -C "$M" rev-parse --verify --quiet "$B" >/dev/null 2>&1 && echo yes || echo no)
[[ "$(jget "$j" 'd["status"]')" == "cleaned" && "$kept" == "yes" && ! -d "$W" ]] \
  && ok "cleanup: --keep-branch keeps branch (PR mode)" || bad "cleanup keep-branch" "$j / kept=$kept"
cleanproj "$M"

# 17. --assets-ok bypasses the guard even with assets present
IFS='|' read -r M W B <<<"$(mkproj)"
wt_commit "$W" file.txt branch-feature "feat work"
run land-merge "$M" "$B" main "$W" true >/dev/null
mkdir -p "$W/tmp"; printf 'x\n' > "$W/tmp/c"
j=$(run cleanup "$M" "$B" "$W" --assets-ok)
[[ "$(jget "$j" 'd["status"]')" == "cleaned" && ! -d "$W" ]] \
  && ok "cleanup: --assets-ok bypasses guard" || bad "cleanup assets-ok" "$j"
cleanproj "$M"

# ── drift (frozen-fact validation) ────────────────────────────────────────────────────────────
lastline() { printf '%s' "$1" | tail -1; }
# D1. merge-to-main + deploy anchor file present → drift ok (exit 0)
IFS='|' read -r M W B <<<"$(mkproj)"; touch "$M/deploy.yml"
if run drift --root "$M" --base main --mode merge-to-main --anchor "deploy:deploy.yml" >/dev/null 2>&1; then ok "drift: deploy anchor present → ok"; else bad "drift deploy ok" "exit!=0"; fi
cleanproj "$M"
# D2. deploy anchor file gone → exit 3, stage drift
IFS='|' read -r M W B <<<"$(mkproj)"
out=$(run drift --root "$M" --base main --mode merge-to-main --anchor "deploy:deploy.yml" 2>/dev/null); rc=$?
[[ $rc -eq 3 && "$(jget "$(lastline "$out")" 'd["stage"]')" == "drift" ]] && ok "drift: missing deploy signal → exit3" || bad "drift deploy missing" "rc=$rc $out"
cleanproj "$M"
# D3. pr + remote anchor matching origin → ok
IFS='|' read -r M W B <<<"$(mkproj)"; URL=$(git -C "$M" remote get-url origin)
if run drift --root "$M" --base main --mode pr --anchor "remote:$URL" >/dev/null 2>&1; then ok "drift: remote anchor matches → ok"; else bad "drift remote ok" "exit!=0"; fi
cleanproj "$M"
# D4. pr + remote anchor mismatched → exit 3
IFS='|' read -r M W B <<<"$(mkproj)"
out=$(run drift --root "$M" --base main --mode pr --anchor "remote:https://github.com/x/y" 2>/dev/null); rc=$?
[[ $rc -eq 3 ]] && ok "drift: remote moved → exit3" || bad "drift remote moved" "rc=$rc"
cleanproj "$M"
# D5. unknown mode → exit 3
IFS='|' read -r M W B <<<"$(mkproj)"
if run drift --root "$M" --base main --mode bogus 2>/dev/null; then bad "drift bad-mode exit0" ""; else ok "drift: unknown mode → exit3"; fi
cleanproj "$M"
# D6. base no longer resolves → exit 3
IFS='|' read -r M W B <<<"$(mkproj)"
if run drift --root "$M" --base nope --mode pr 2>/dev/null; then bad "drift bad-base exit0" ""; else ok "drift: base unresolved → exit3"; fi
cleanproj "$M"

# ── land (orchestrator) ─────────────────────────────────────────────────────────────────────
# L1. merge happy path: base diverged → sync(clean) → dep(marker) → gate(pass) → ff → cleanup. exit0.
IFS='|' read -r M W B <<<"$(mkproj)"; touch "$M/deploy.yml"
wt_commit "$W" b.txt x branchwork    # commit MESSAGE = branchwork (asserted via main log)
main_commit "$M" m.txt mainwork mw   # base ahead on a different file → clean merge
out=$(run land --root "$M" --base main --mode merge-to-main --anchor "deploy:deploy.yml" \
       --testcmd true --depcmd "touch $W/.dep_ran" -- feat "$W" 2>/dev/null); rc=$?
# worktree is removed on success, so assert the landed outcome via main's history, not the wt marker.
[[ $rc -eq 0 && "$(jget "$(lastline "$out")" 'd["stage"]')" == "landed" \
   && "$(git -C "$M" log --oneline | grep -c branchwork)" == "1" ]] \
  && ok "land merge: happy path lands to main" || bad "land merge happy" "rc=$rc $out"
cleanproj "$M"
# L2. dep refresh fails → exit 20 stage deps-refresh, main NOT advanced, gate never ran.
IFS='|' read -r M W B <<<"$(mkproj)"; touch "$M/deploy.yml"
wt_commit "$W" b.txt bw bw; main_commit "$M" m.txt mw mw
out=$(run land --root "$M" --base main --mode merge-to-main --anchor "deploy:deploy.yml" \
       --testcmd true --depcmd false -- feat "$W" 2>/dev/null); rc=$?
[[ $rc -eq 20 && "$(jget "$(lastline "$out")" 'd["stage"]')" == "deps-refresh" \
   && "$(git -C "$M" log --oneline | grep -c bw)" == "0" ]] \
  && ok "land merge: dep-fail → exit20, main untouched" || bad "land dep-fail" "rc=$rc $out"
cleanproj "$M"
# L3. gate fails → exit 20 stage land-merge, main NOT advanced.
IFS='|' read -r M W B <<<"$(mkproj)"; touch "$M/deploy.yml"
wt_commit "$W" b.txt bw bw; main_commit "$M" m.txt mw mw
out=$(run land --root "$M" --base main --mode merge-to-main --anchor "deploy:deploy.yml" \
       --testcmd false --depcmd "touch $W/.dep_ran" -- feat "$W" 2>/dev/null); rc=$?
[[ $rc -eq 20 && "$(jget "$(lastline "$out")" 'd["stage"]')" == "land-merge" \
   && -f "$W/.dep_ran" && "$(git -C "$M" log --oneline | grep -c bw)" == "0" ]] \
  && ok "land merge: gate-fail → exit20 (dep ran first), main untouched" || bad "land gate-fail" "rc=$rc $out"
cleanproj "$M"
# L4. PR mode, conflict markers in wt → land-pr fails closed BEFORE gh → exit20 stage land-pr, main untouched. (hermetic)
IFS='|' read -r M W B <<<"$(mkproj)"; URL=$(git -C "$M" remote get-url origin)
printf '<<<<<<< HEAD\nx\n>>>>>>> y\n' > "$W/conf.txt"; git -C "$W" add -A; git -C "$W" commit -q -m markers
out=$(run land --root "$M" --base main --mode pr --anchor "remote:$URL" --testcmd true -- feat "$W" 2>/dev/null); rc=$?
[[ $rc -eq 20 && "$(jget "$(lastline "$out")" 'd["stage"]')" == "land-pr" \
   && "$(git -C "$M" log --oneline | grep -c markers)" == "0" ]] \
  && ok "land pr: conflict-markers → exit20 land-pr, main untouched" || bad "land pr markers" "rc=$rc $out"
cleanproj "$M"
# L5. PR mode, clean branch + DIVERGED base + DIRTY main → no preflight-block, no base-sync (HEAD unchanged).
#     Reaches gh and fails at create (local remote ≠ GitHub) → exit20 land-pr. gh-guarded.
if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
  IFS='|' read -r M W B <<<"$(mkproj)"; URL=$(git -C "$M" remote get-url origin)
  wt_commit "$W" b.txt prwork pr
  main_commit "$M" m.txt ahead ahead; git -C "$M" push -q origin main 2>/dev/null
  printf 'wip\n' > "$M/file.txt"   # dirty main (must NOT block a PR)
  before=$(git -C "$W" rev-parse HEAD)
  out=$(run land --root "$M" --base main --mode pr --anchor "remote:$URL" --testcmd true -- feat "$W" 2>/dev/null); rc=$?
  after=$(git -C "$W" rev-parse HEAD)
  [[ "$before" == "$after" \
     && "$(printf '%s' "$out" | grep -c '"stage":"preflight"')" == "0" \
     && "$(jget "$(lastline "$out")" 'd["stage"]')" == "land-pr" \
     && "$(git -C "$M" log --oneline | grep -c prwork)" == "0" ]] \
    && ok "land pr: no preflight-block, no base-sync, main untouched" || bad "land pr invariants" "rc=$rc before=$before after=$after $out"
  cleanproj "$M"
else
  ok "land pr invariants (SKIPPED — gh not authed)"
fi

# L6. cwd-independence (REGRESSION): gh resolves repo + --fill reads local refs from CWD. The
#     orchestrator runs land-pr from the AGENT's cwd (any repo) — so land-pr MUST run every gh call
#     inside $main, else gh targets the wrong repo / "ambiguous argument base...branch". Hermetic:
#     stub gh to log "$1$2:$PWD"; origin = local bare (push works, slug non-empty, stub ignores -R);
#     invoke from an UNRELATED repo cwd; assert pr-list AND pr-create both ran with cwd == $main.
IFS='|' read -r M W B <<<"$(mkproj)"
wt_commit "$W" feat.txt change pr-work
GHBIN=$(mktemp -d /tmp/fb-ghbin-XXXX); GHLOG=$(mktemp /tmp/fb-ghlog-XXXX)
cat > "$GHBIN/gh" <<EOF
#!/usr/bin/env bash
printf '%s %s:%s\n' "\$1" "\$2" "\$PWD" >> "$GHLOG"
case "\$1 \$2" in
  "auth status") exit 0;;
  "pr list")     printf '' ; exit 0;;          # no existing PR
  "pr create")   printf 'https://example.invalid/pr/1\n'; exit 0;;
  *) exit 0;;
esac
EOF
chmod +x "$GHBIN/gh"
OTHER=$(mktemp -d /tmp/fb-other-XXXX); git -C "$OTHER" init -q -b main   # unrelated repo as caller cwd
out=$( cd "$OTHER" && PATH="$GHBIN:$PATH" bash "$LIB" land-pr "$M" "$B" main "$W" true 2>&1 )
createpwd=$(grep '^pr create:' "$GHLOG" | head -1 | sed 's/^pr create://')
listpwd=$(grep '^pr list:' "$GHLOG" | head -1 | sed 's/^pr list://')
[[ "$(printf '%s' "$out" | grep -c '"status":"pr-opened"')" == "1" \
   && "$createpwd" == "$M" && "$listpwd" == "$M" ]] \
  && ok "land-pr: gh runs in \$main (cwd-independent), not the caller's cwd" \
  || bad "land-pr cwd-independence" "status=$out create_pwd=$createpwd list_pwd=$listpwd main=$M"
rm -rf "$GHBIN" "$GHLOG" "$OTHER"; cleanproj "$M"

# L7. PR-mode cleanup must SURFACE assets-present (exit 20 stage cleanup), not swallow it → return 0.
#     Else the worktree leaks silently and --assets-ok is unreachable. Then prove the re-entry: same
#     command WITH --assets-ok cleans + returns 0. gh stubbed to pr-opened; asset = non-empty tmp/.
IFS='|' read -r M W B <<<"$(mkproj)"
wt_commit "$W" feat.txt change pr-work
mkdir -p "$W/tmp"; printf 'expensive\n' > "$W/tmp/cache.bin"   # gitignored asset only in the worktree
GHBIN=$(mktemp -d /tmp/fb-ghbin-XXXX)
cat > "$GHBIN/gh" <<'EOF'
#!/usr/bin/env bash
case "$1 $2" in
  "auth status") exit 0;; "pr list") printf ''; exit 0;;
  "pr create") printf 'https://example.invalid/pr/1\n'; exit 0;; *) exit 0;;
esac
EOF
chmod +x "$GHBIN/gh"
ANCH="remote:$(git -C "$M" remote get-url origin)"
out=$( PATH="$GHBIN:$PATH" bash "$LIB" land --root "$M" --base "$B" --mode pr --anchor "$ANCH" --testcmd true -- "$B" "$W" 2>&1 ); rc=$?
[[ $rc -eq 20 && "$(jget "$(lastline "$out")" 'd["stage"]')" == "cleanup" \
   && "$(printf '%s' "$out" | grep -c '"status":"pr-opened"')" == "1" && -d "$W" ]] \
  && ok "land pr: cleanup assets-present surfaced as exit20 (not swallowed), worktree preserved" \
  || bad "land pr cleanup surfacing" "rc=$rc wt_exists=$([[ -d $W ]] && echo y) $out"
# re-entry with --assets-ok → cleaned, exit 0, worktree removed (flag now reachable in PR mode)
out2=$( PATH="$GHBIN:$PATH" bash "$LIB" land --root "$M" --base "$B" --mode pr --anchor "$ANCH" --testcmd true -- "$B" "$W" --assets-ok 2>&1 ); rc2=$?
[[ $rc2 -eq 0 && ! -d "$W" ]] \
  && ok "land pr: --assets-ok re-entry cleans + exit0 (flag reachable)" \
  || bad "land pr assets-ok re-entry" "rc=$rc2 wt_exists=$([[ -d $W ]] && echo y) $out2"
rm -rf "$GHBIN"; cleanproj "$M"

# ── deploy-verify primitives ──────────────────────────────────────────────────────────────────
# DV1. deploy-preview: deploycmd writes URL to .ship-preview-url → deployed, url surfaced
IFS='|' read -r M W B <<<"$(mkproj)"
j=$(run deploy-preview "$W" 'echo https://prev-1.test > .ship-preview-url')
[[ "$(jget "$j" 'd["status"]')" == "deployed" && "$(jget "$j" 'd["url"]')" == "https://prev-1.test" ]] \
  && ok "deploy-preview: captures URL from .ship-preview-url" || bad "deploy-preview deployed" "$j"
cleanproj "$M"
# DV2. deploy-preview: deploy exits 0 but writes NO url → no-url (fail-closed)
IFS='|' read -r M W B <<<"$(mkproj)"
j=$(run deploy-preview "$W" 'true')
[[ "$(jget "$j" 'd["status"]')" == "no-url" ]] \
  && ok "deploy-preview: no URL written → no-url (fail-closed)" || bad "deploy-preview no-url" "$j"
cleanproj "$M"
# DV3. deploy-preview: deploy command itself fails → deploy-failed
IFS='|' read -r M W B <<<"$(mkproj)"
j=$(run deploy-preview "$W" 'false')
[[ "$(jget "$j" 'd["status"]')" == "deploy-failed" ]] \
  && ok "deploy-preview: nonzero deploy → deploy-failed" || bad "deploy-preview deploy-failed" "$j"
cleanproj "$M"
# DV4. e2e-gate: e2ecmd sees PREVIEW_URL and passes → passed
IFS='|' read -r M W B <<<"$(mkproj)"
j=$(run e2e-gate "$W" 'test "$PREVIEW_URL" = https://prev.test' https://prev.test)
[[ "$(jget "$j" 'd["status"]')" == "passed" ]] \
  && ok "e2e-gate: PREVIEW_URL plumbed, pass → passed" || bad "e2e-gate passed" "$j"
cleanproj "$M"
# DV5. e2e-gate: nonzero e2e → failed
IFS='|' read -r M W B <<<"$(mkproj)"
j=$(run e2e-gate "$W" 'false' https://prev.test)
[[ "$(jget "$j" 'd["status"]')" == "failed" ]] \
  && ok "e2e-gate: nonzero e2e → failed" || bad "e2e-gate failed" "$j"
cleanproj "$M"

# ── deploy-verify drift ─────────────────────────────────────────────────────────────────────
# DV6. deploy-verify + deploy anchor present → drift ok
IFS='|' read -r M W B <<<"$(mkproj)"; touch "$M/wrangler.toml"
if run drift --root "$M" --base main --mode deploy-verify --anchor "deploy:wrangler.toml" >/dev/null 2>&1; then ok "drift: deploy-verify anchor present → ok"; else bad "drift dv ok" "exit!=0"; fi
cleanproj "$M"
# DV7. deploy-verify + anchor gone → exit 3
IFS='|' read -r M W B <<<"$(mkproj)"
out=$(run drift --root "$M" --base main --mode deploy-verify --anchor "deploy:wrangler.toml" 2>/dev/null); rc=$?
[[ $rc -eq 3 ]] && ok "drift: deploy-verify missing anchor → exit3" || bad "drift dv missing" "rc=$rc"
cleanproj "$M"

# ── deploy-verify land orchestrator ─────────────────────────────────────────────────────────
# DV-L1. happy: deploy writes url → e2e (sees PREVIEW_URL) passes → promote merge-to-main → ff + cleanup. exit0.
IFS='|' read -r M W B <<<"$(mkproj)"; touch "$M/wrangler.toml"
wt_commit "$W" b.txt x branchwork
out=$(run land --root "$M" --base main --mode deploy-verify --anchor "deploy:wrangler.toml" \
       --testcmd true --deploycmd 'echo https://prev.test > .ship-preview-url' \
       --e2ecmd 'test "$PREVIEW_URL" = https://prev.test' --promote merge-to-main -- feat "$W" 2>/dev/null); rc=$?
[[ $rc -eq 0 && "$(jget "$(lastline "$out")" 'd["stage"]')" == "landed" \
   && "$(git -C "$M" log --oneline | grep -c branchwork)" == "1" ]] \
  && ok "land deploy-verify: deploy→e2e(pass)→promote lands to main" || bad "land dv happy" "rc=$rc $out"
cleanproj "$M"
# DV-L2. e2e FAILS → exit20 stage e2e-gate, main NOT advanced (NEVER promote an unverified build).
IFS='|' read -r M W B <<<"$(mkproj)"; touch "$M/wrangler.toml"
wt_commit "$W" b.txt x branchwork
out=$(run land --root "$M" --base main --mode deploy-verify --anchor "deploy:wrangler.toml" \
       --testcmd true --deploycmd 'echo https://prev.test > .ship-preview-url' \
       --e2ecmd 'false' --promote merge-to-main -- feat "$W" 2>/dev/null); rc=$?
[[ $rc -eq 20 && "$(jget "$(lastline "$out")" 'd["stage"]')" == "e2e-gate" \
   && "$(git -C "$M" log --oneline | grep -c branchwork)" == "0" ]] \
  && ok "land deploy-verify: e2e-fail → exit20, NOT promoted, main untouched" || bad "land dv e2e-fail" "rc=$rc $out"
cleanproj "$M"
# DV-L3. deploy yields no URL → exit20 stage deploy-preview, e2e NEVER runs (marker absent), main untouched.
IFS='|' read -r M W B <<<"$(mkproj)"; touch "$M/wrangler.toml"
wt_commit "$W" b.txt x branchwork
out=$(run land --root "$M" --base main --mode deploy-verify --anchor "deploy:wrangler.toml" \
       --testcmd true --deploycmd 'true' \
       --e2ecmd "touch $W/.e2e_ran" --promote merge-to-main -- feat "$W" 2>/dev/null); rc=$?
[[ $rc -eq 20 && "$(jget "$(lastline "$out")" 'd["stage"]')" == "deploy-preview" \
   && ! -f "$W/.e2e_ran" && "$(git -C "$M" log --oneline | grep -c branchwork)" == "0" ]] \
  && ok "land deploy-verify: no-url → exit20 deploy-preview, e2e never ran" || bad "land dv no-url" "rc=$rc e2e_ran=$([[ -f $W/.e2e_ran ]] && echo y) $out"
cleanproj "$M"
# DV-L4. missing --promote → usage fault (exit 3), nothing deployed.
IFS='|' read -r M W B <<<"$(mkproj)"; touch "$M/wrangler.toml"
wt_commit "$W" b.txt x branchwork
if run land --root "$M" --base main --mode deploy-verify --anchor "deploy:wrangler.toml" \
     --testcmd true --deploycmd 'echo x > .ship-preview-url' --e2ecmd true -- feat "$W" 2>/dev/null; then
  bad "land dv missing-promote exit0" ""
else ok "land deploy-verify: missing --promote → exit3 (fail-closed)"; fi
cleanproj "$M"
# DV-L5. promote=merge-to-main: base advanced → branch MUST be synced BEFORE deploy, so e2e verifies the
#        EXACT head that lands (never promote an unverified build). deploycmd ASSERTS the base file is
#        present in the worktree (proves sync ran first); only the fixed pre-deploy-sync ordering passes.
IFS='|' read -r M W B <<<"$(mkproj)"; touch "$M/wrangler.toml"
wt_commit "$W" b.txt x branchwork
main_commit "$M" base-only.txt y "base advances on disjoint file"
out=$(run land --root "$M" --base main --mode deploy-verify --anchor "deploy:wrangler.toml" \
       --testcmd true --deploycmd 'test -f base-only.txt && echo https://prev.test > .ship-preview-url' \
       --e2ecmd 'test "$PREVIEW_URL" = https://prev.test' --promote merge-to-main -- feat "$W" 2>/dev/null); rc=$?
[[ $rc -eq 0 && "$(jget "$(lastline "$out")" 'd["stage"]')" == "landed" \
   && "$(git -C "$M" log --oneline | grep -c branchwork)" == "1" ]] \
  && ok "land deploy-verify(mtm): synced BEFORE deploy → e2e verifies the landed head" || bad "land dv pre-sync" "rc=$rc $out"
cleanproj "$M"
# DV-L6. promote=merge-to-main: pre-deploy sync CONFLICTS → exit20 stage sync-base, deploy NEVER ran
#        (marker absent), main untouched. Verify gate is reached only on a current head.
IFS='|' read -r M W B <<<"$(mkproj)"; touch "$M/wrangler.toml"
main_commit "$M" file.txt main-line "main edits file.txt"
wt_commit "$W" file.txt branch-line "branch edits same line"
out=$(run land --root "$M" --base main --mode deploy-verify --anchor "deploy:wrangler.toml" \
       --testcmd true --deploycmd "touch $W/.deploy_ran; echo x > .ship-preview-url" \
       --e2ecmd true --promote merge-to-main -- feat "$W" 2>/dev/null); rc=$?
[[ $rc -eq 20 && "$(jget "$(lastline "$out")" 'd["stage"]')" == "sync-base" \
   && ! -f "$W/.deploy_ran" && "$(git -C "$M" log --oneline | grep -c branch-line)" == "0" ]] \
  && ok "land deploy-verify(mtm): pre-deploy sync conflict → exit20, deploy never ran" || bad "land dv pre-sync conflict" "rc=$rc deploy_ran=$([[ -f $W/.deploy_ran ]] && echo y) $out"
cleanproj "$M"

# ── Stage A serialized candidate landing ─────────────────────────────────────────────────────
# SA1. Remote main, not stale local main, is the integration base. Local main remains untouched.
IFS='|' read -r M W B <<<"$(mkproj)"; optin "$M"
local_main=$(git -C "$M" rev-parse main)
wt_commit "$W" source.txt source source-work
source_sha=$(git -C "$W" rev-parse HEAD)
advance_origin "$M" remote.txt remote remote-work
out=$(run land --root "$M" --base main --mode merge-to-main --testcmd true -- "$B" "$W" 2>/dev/null); rc=$?
git -C "$M" fetch -q origin main:refs/remotes/origin/main
[[ $rc -eq 0 && "$(git -C "$M" rev-parse main)" == "$local_main" \
   && "$(git -C "$M" show refs/remotes/origin/main:source.txt)" == "source" \
   && "$(git -C "$M" show refs/remotes/origin/main:remote.txt)" == "remote" \
   && "$(git -C "$M" merge-base --is-ancestor "$source_sha" refs/remotes/origin/main; echo $?)" == "0" \
   && ! -d "$W" \
   && "$(git -C "$M" rev-parse --verify --quiet "$B" >/dev/null 2>&1; echo $?)" != "0" ]] \
  && ok "stage-a: stale local main ignored; remote + source both land" \
  || bad "stage-a stale local main" "rc=$rc local=$(git -C "$M" rev-parse main) expected=$local_main wt=$([[ -d $W ]] && echo present) br=$(git -C "$M" rev-parse --verify --quiet "$B" 2>/dev/null || echo gone) $out"
cleanproj "$M"

# SA2. Two landers sharing one common-dir serialize the whole candidate gate and both land.
IFS='|' read -r M W B <<<"$(mkproj)"; optin "$M"
git -C "$W" branch -m feat-a; B=feat-a
wt_commit "$W" a.txt a land-a
W2=$(mktemp -d /tmp/fb-wt2-XXXX); rmdir "$W2"
git -C "$M" worktree add -q "$W2" -b feat-b main 2>/dev/null
wt_commit "$W2" b.txt b land-b
METRICS=$(mktemp -d /tmp/fb-metrics-XXXX)
cat > "$METRICS/gate.sh" <<'EOF'
#!/usr/bin/env bash
set -eu
d=$1
while ! mkdir "$d/mutex" 2>/dev/null; do sleep 0.01; done
current=0; [[ -f "$d/current" ]] && current=$(cat "$d/current")
current=$((current+1)); printf '%s\n' "$current" > "$d/current"
maximum=0; [[ -f "$d/maximum" ]] && maximum=$(cat "$d/maximum")
(( current > maximum )) && printf '%s\n' "$current" > "$d/maximum"
rmdir "$d/mutex"
sleep 1
while ! mkdir "$d/mutex" 2>/dev/null; do sleep 0.01; done
current=$(cat "$d/current"); printf '%s\n' "$((current-1))" > "$d/current"
rmdir "$d/mutex"
EOF
chmod +x "$METRICS/gate.sh"
bash "$LIB" land --root "$M" --base main --mode merge-to-main \
  --testcmd "bash '$METRICS/gate.sh' '$METRICS'" -- feat-a "$W" >"$METRICS/a.out" 2>&1 & p1=$!
bash "$LIB" land --root "$M" --base main --mode merge-to-main \
  --testcmd "bash '$METRICS/gate.sh' '$METRICS'" -- feat-b "$W2" >"$METRICS/b.out" 2>&1 & p2=$!
wait "$p1"; rc1=$?; wait "$p2"; rc2=$?
git -C "$M" fetch -q origin main:refs/remotes/origin/main
[[ $rc1 -eq 0 && $rc2 -eq 0 && "$(cat "$METRICS/maximum")" == "1" \
   && "$(git -C "$M" log --format=%s refs/remotes/origin/main | grep -c land-a)" == "1" \
   && "$(git -C "$M" log --format=%s refs/remotes/origin/main | grep -c land-b)" == "1" ]] \
  && ok "stage-a: parallel landers serialize (critical max=1) and both land" \
  || bad "stage-a parallel serialization" "rc1=$rc1 rc2=$rc2 max=$(cat "$METRICS/maximum" 2>/dev/null) a=$(cat "$METRICS/a.out") b=$(cat "$METRICS/b.out")"
rm -rf "$METRICS"; cleanproj "$M"

# SA3. Remote movement after attempt one discards the candidate and reruns the gate.
IFS='|' read -r M W B <<<"$(mkproj)"; optin "$M"
wt_commit "$W" source.txt source source-work
bare=$(git -C "$M" remote get-url origin)
ADV=$(mktemp -d /tmp/fb-churn-XXXX); git clone -q "$bare" "$ADV/repo"; gconf "$ADV/repo"
cat > "$ADV/gate.sh" <<'EOF'
#!/usr/bin/env bash
set -eu
state=$1 repo=$2
n=0; [[ -f "$state/count" ]] && n=$(cat "$state/count")
n=$((n+1)); printf '%s\n' "$n" > "$state/count"
if [[ $n -eq 1 ]]; then
  printf 'advanced\n' > "$repo/advanced.txt"
  git -C "$repo" add -A; git -C "$repo" commit -q -m remote-during-gate
  git -C "$repo" push -q origin main
fi
EOF
chmod +x "$ADV/gate.sh"
out=$(run land --root "$M" --base main --mode merge-to-main \
  --testcmd "bash '$ADV/gate.sh' '$ADV' '$ADV/repo'" -- "$B" "$W" 2>/dev/null); rc=$?
git -C "$M" fetch -q origin main:refs/remotes/origin/main
[[ $rc -eq 0 && "$(cat "$ADV/count")" == "2" \
   && "$(git -C "$M" show refs/remotes/origin/main:advanced.txt)" == "advanced" \
   && "$(git -C "$M" show refs/remotes/origin/main:source.txt)" == "source" ]] \
  && ok "stage-a: remote movement rebuilds candidate; gate reaches attempt 2" \
  || bad "stage-a remote rebuild" "rc=$rc attempts=$(cat "$ADV/count" 2>/dev/null) $out"
rm -rf "$ADV"; cleanproj "$M"

# SA4. Candidate merge conflict preserves source and rescue ref, and returns agent-action exit 20.
IFS='|' read -r M W B <<<"$(mkproj)"; optin "$M"
wt_commit "$W" file.txt source-line source-conflict
source_sha=$(git -C "$W" rev-parse HEAD)
advance_origin "$M" file.txt remote-line remote-conflict
out=$(run land --root "$M" --base main --mode merge-to-main --testcmd true -- "$B" "$W" 2>/dev/null); rc=$?
rescue=$(git -C "$M" for-each-ref --format='%(objectname)' refs/rescue/land/ | grep -F "$source_sha" || true)
[[ $rc -eq 20 && "$(jget "$(lastline "$out")" 'd["status"]')" == "candidate-conflict" \
   && -d "$W" && "$(git -C "$M" rev-parse "$B")" == "$source_sha" && "$rescue" == "$source_sha" ]] \
  && ok "stage-a: candidate conflict preserves source + rescue ref" \
  || bad "stage-a candidate conflict" "rc=$rc rescue=$rescue wt=$([[ -d $W ]] && echo present) $out"
cleanproj "$M"

# SA5. Source branch movement during candidate gate lands snapshot but retains moved branch/worktree.
IFS='|' read -r M W B <<<"$(mkproj)"; optin "$M"
wt_commit "$W" source.txt source source-snapshot
source_sha=$(git -C "$W" rev-parse HEAD)
MOVE=$(mktemp -d /tmp/fb-move-XXXX)
cat > "$MOVE/gate.sh" <<'EOF'
#!/usr/bin/env bash
set -eu
wt=$1 state=$2
if mkdir "$state/once" 2>/dev/null; then
  printf 'moved\n' > "$wt/moved.txt"
  git -C "$wt" add -A; git -C "$wt" commit -q -m source-moved
fi
EOF
chmod +x "$MOVE/gate.sh"
out=$(run land --root "$M" --base main --mode merge-to-main \
  --testcmd "bash '$MOVE/gate.sh' '$W' '$MOVE'" -- "$B" "$W" 2>/dev/null); rc=$?
moved_sha=$(git -C "$M" rev-parse "$B")
git -C "$M" fetch -q origin main:refs/remotes/origin/main
git -C "$M" merge-base --is-ancestor "$source_sha" refs/remotes/origin/main; snapshot_landed=$?
git -C "$M" merge-base --is-ancestor "$moved_sha" refs/remotes/origin/main; moved_landed=$?
[[ $rc -eq 0 && $snapshot_landed -eq 0 && $moved_landed -ne 0 && "$moved_sha" != "$source_sha" && -d "$W" ]] \
  && ok "stage-a: moved source retained; exact snapshot lands" \
  || bad "stage-a moved source" "rc=$rc snapshot_landed=$snapshot_landed moved_landed=$moved_landed wt=$([[ -d $W ]] && echo present) $out"
rm -rf "$MOVE"; cleanproj "$M"

# SA6. Config absent preserves legacy output and local-main fast-forward behavior byte-for-byte.
IFS='|' read -r M W B <<<"$(mkproj)"
wt_commit "$W" file.txt branch-feature legacy-land
expected='{"stage":"landed","status":"done","next":"none","detail":"merged to main, pushed, worktree removed, branch deleted"}'
out=$(run land --root "$M" --base main --mode merge-to-main --testcmd true -- "$B" "$W" 2>/dev/null); rc=$?
[[ $rc -eq 0 && "$out" == "$expected" && "$(git -C "$M" log --format=%s -1 main)" == "legacy-land" ]] \
  && ok "legacy: absent opt-in preserves exact output + local fast-forward" \
  || bad "legacy byte identity" "rc=$rc expected=$expected actual=$out"
cleanproj "$M"

# SA7. Deploy/e2e verified source tree cannot promote a different candidate tree.
IFS='|' read -r M W B <<<"$(mkproj)"; optin "$M"; touch "$M/wrangler.toml"
wt_commit "$W" source.txt source source-work
source_sha=$(git -C "$W" rev-parse HEAD)
advance_origin "$M" remote.txt remote remote-work
out=$(run land --root "$M" --base main --mode deploy-verify --anchor "deploy:wrangler.toml" \
  --testcmd true --deploycmd 'echo https://prev.test > .ship-preview-url' --e2ecmd true \
  --promote merge-to-main -- "$B" "$W" 2>/dev/null); rc=$?
git -C "$M" fetch -q origin main:refs/remotes/origin/main
git -C "$M" merge-base --is-ancestor "$source_sha" refs/remotes/origin/main; source_landed=$?
[[ $rc -eq 20 && "$(jget "$(lastline "$out")" 'd["status"]')" == "e2e-stale-base" && $source_landed -ne 0 && -d "$W" ]] \
  && ok "stage-a deploy-verify: differing candidate tree fails e2e-stale-base" \
  || bad "stage-a e2e stale base" "rc=$rc source_landed=$source_landed $out"
cleanproj "$M"

# SA8. Tracked source edits not represented by the snapshot block landing and destructive cleanup.
IFS='|' read -r M W B <<<"$(mkproj)"; optin "$M"
wt_commit "$W" source.txt committed source-work
remote_before=$(git -C "$M" ls-remote origin refs/heads/main | awk '{print $1}')
printf 'uncommitted\n' > "$W/source.txt"
out=$(run land --root "$M" --base main --mode merge-to-main --testcmd true -- "$B" "$W" 2>/dev/null); rc=$?
remote_after=$(git -C "$M" ls-remote origin refs/heads/main | awk '{print $1}')
[[ $rc -eq 20 && "$(jget "$(lastline "$out")" 'd["status"]')" == "source-dirty-tree" \
   && "$remote_after" == "$remote_before" && -d "$W" && "$(cat "$W/source.txt")" == "uncommitted" ]] \
  && ok "stage-a: dirty source blocks land and preserves worktree" \
  || bad "stage-a dirty source" "rc=$rc remote_before=$remote_before remote_after=$remote_after $out"
cleanproj "$M"

# SA9. Candidate push alone receives a matching 0600 authority nonce; rejection still removes it.
IFS='|' read -r M W B <<<"$(mkproj)"; optin "$M"
wt_commit "$W" source.txt source source-work
bare=$(git -C "$M" remote get-url origin)
common=$(git -C "$M" rev-parse --path-format=absolute --git-common-dir)
SEEN=$(mktemp /tmp/fb-authority-seen-XXXX); rm -f "$SEEN"
cat > "$bare/hooks/pre-receive" <<EOF
#!/usr/bin/env bash
set -eu
authority='$common/harness/land-authority'
[[ -f "\$authority" && "\$(stat -c %a "\$authority")" == 600 ]]
IFS= read -r expected < "\$authority"
[[ "\${HARNESS_LAND_TOKEN:-}" == "\$expected" ]]
printf 'seen\n' > '$SEEN'
exit 1
EOF
chmod +x "$bare/hooks/pre-receive"
out=$(run land --root "$M" --base main --mode merge-to-main --testcmd true -- "$B" "$W" 2>/dev/null); rc=$?
[[ $rc -eq 20 && "$(jget "$(lastline "$out")" 'd["status"]')" == "push-failed" \
   && "$(cat "$SEEN" 2>/dev/null)" == "seen" && ! -e "$common/harness/land-authority" && -d "$W" ]] \
  && ok "stage-a: scoped 0600 authority nonce removed after rejected push" \
  || bad "stage-a authority lifecycle" "rc=$rc seen=$(cat "$SEEN" 2>/dev/null) authority=$([[ -e $common/harness/land-authority ]] && echo present) $out"
rm -f "$SEEN"; cleanproj "$M"

# SA10. Push failure before merge preserves source branch/worktree; successful land cleans afterward.
IFS='|' read -r M W B <<<"$(mkproj)"; optin "$M"
wt_commit "$W" source.txt source source-work
source_sha=$(git -C "$W" rev-parse HEAD)
remote_before=$(git -C "$M" ls-remote origin refs/heads/main | awk '{print $1}')
bare=$(git -C "$M" remote get-url origin)
cat > "$bare/hooks/pre-receive" <<'EOF'
#!/usr/bin/env bash
exit 1
EOF
chmod +x "$bare/hooks/pre-receive"
out=$(run land --root "$M" --base main --mode merge-to-main --testcmd true -- "$B" "$W" 2>/dev/null); rc=$?
remote_after=$(git -C "$M" ls-remote origin refs/heads/main | awk '{print $1}')
rescue=$(git -C "$M" for-each-ref --format='%(objectname)' refs/rescue/land/ | grep -F "$source_sha" || true)
[[ $rc -eq 20 && "$(jget "$(lastline "$out")" 'd["status"]')" == "push-failed" \
   && "$remote_after" == "$remote_before" && -d "$W" \
   && "$(git -C "$M" rev-parse "$B")" == "$source_sha" && "$rescue" == "$source_sha" ]] \
  && ok "stage-a: push failure preserves source branch, worktree, and rescue ref" \
  || bad "stage-a push failure preservation" "rc=$rc remote_before=$remote_before remote_after=$remote_after rescue=$rescue wt=$([[ -d $W ]] && echo present) $out"
rm -f "$bare/hooks/pre-receive"
out=$(run land --root "$M" --base main --mode merge-to-main --testcmd true -- "$B" "$W" 2>/dev/null); rc=$?
git -C "$M" fetch -q origin main:refs/remotes/origin/main
[[ $rc -eq 0 && ! -d "$W" \
   && "$(git -C "$M" rev-parse --verify --quiet "$B" >/dev/null 2>&1; echo $?)" != "0" \
   && "$(git -C "$M" show refs/remotes/origin/main:source.txt)" == "source" ]] \
  && ok "stage-a: successful land cleans source after merge" \
  || bad "stage-a successful cleanup" "rc=$rc wt=$([[ -d $W ]] && echo present) br=$(git -C "$M" rev-parse --verify --quiet "$B" 2>/dev/null || echo gone) $out"
cleanproj "$M"

# SA11. worktree remove that prunes the branch ref is idempotent cleaned, not branch-delete-failed.
IFS='|' read -r M W B <<<"$(mkproj)"; optin "$M"
wt_commit "$W" source.txt source source-work
source_sha=$(git -C "$W" rev-parse HEAD)
ORIGPATH=$PATH
GITWRAP=$(mktemp -d /tmp/fb-gitwrap-XXXX)
cat > "$GITWRAP/git" <<EOF
#!/usr/bin/env bash
if [[ "\$1" == "-C" && "\$3" == "worktree" && "\$4" == "remove" ]]; then
  repo="\$2"; wt="\$5"
  br=\$(env PATH="$ORIGPATH" git -C "\$repo" worktree list --porcelain 2>/dev/null | awk -v p="\$wt" '
    \$1=="worktree"&&\$2==p{f=1; next} f&&\$1=="branch"{sub(/^refs\\/heads\\//,""); print; exit}')
  env PATH="$ORIGPATH" git "\$@" || exit \$?
  [[ -n "\$br" ]] && env PATH="$ORIGPATH" git -C "\$repo" update-ref -d "refs/heads/\$br" 2>/dev/null || true
  exit 0
fi
exec env PATH="$ORIGPATH" git "\$@"
EOF
chmod +x "$GITWRAP/git"
j=$(PATH="$GITWRAP:$PATH" bash -c 'source "'"$LIB"'"; _stage_a_cleanup "'"$M"'" "'"$B"'" "'"$W"'" "'"$source_sha"'" ""')
[[ "$(jget "$j" 'd["status"]')" == "cleaned" && ! -d "$W" \
   && "$(git -C "$M" rev-parse --verify --quiet "$B" >/dev/null 2>&1; echo $?)" != "0" ]] \
  && ok "stage-a cleanup: pruned branch after worktree remove → cleaned (idempotent)" \
  || bad "stage-a cleanup idempotent" "$j wt=$([[ -d $W ]] && echo present) br=$(git -C "$M" rev-parse --verify --quiet "$B" 2>/dev/null || echo gone)"
rm -rf "$GITWRAP"; cleanproj "$M"

# ── usage faults ──────────────────────────────────────────────────────────────────────────────
# 18. unknown subcommand → non-zero
if run bogus 2>/dev/null; then bad "usage: unknown subcommand non-zero" "exit 0"; else ok "usage: unknown subcommand → non-zero"; fi
# 19. not a git repo → non-zero (usage/env fault, not a JSON gate result)
if run preflight /tmp/fb-nope-$$ x /tmp/fb-nope2-$$ 2>/dev/null; then bad "usage: non-repo non-zero" "exit 0"; else ok "usage: non-repo → non-zero"; fi
[[ $(wc -l < "$TEST_GATE_LOG") -gt 0 ]] \
  && ok "test gate: routes through local-gate seam" \
  || bad "test gate remote routing" "local-gate seam was not called"
rm -f "$TEST_GATE_LOG" "$TEST_GATE_BIN"

echo
echo "PASS=$PASS FAIL=$FAIL"
[[ "$FAIL" -eq 0 ]]
