#!/bin/sh
# fleet claim-honesty gate  --  DOS reason: CLAIM_UNWITNESSED
#
# Runs `dos review` over exactly the commits this push would add to the trunk
# (origin/<trunk>..HEAD) and surfaces any RESIDUAL — a commit whose subject makes
# a claim (`feat`/`fix`/`test`/...) that the diff could NOT witness (a `test():`
# touching zero _test.go files, a `fix():` touching zero source, ...). This is fak
# applying its OWN thesis to its OWN development: don't believe the commit message,
# verify the claim against the file set git itself recorded.
# The echoed review surface is residual-first: RESIDUAL is the operator work-list,
# CLEARED is the near-zero-attention band for claims the kernel already witnessed.
#
# Why a git hook (not just a CLAUDE.md line): git runs it BELOW the agent layer, so
# the same floor binds Claude Code, Codex, and a human at the push boundary — the
# cross-agent enforcement a doc sentence cannot give. It is the push-time complement
# to the pre-commit leak-scan guard, and the local stand-in for the `dos review`
# CI gate (docs/358) while CI is billing-blocked on the private repo.
#
# Mode  (env FLEET_REVIEW_GUARD):  block (default) | warn | off
#   block  refuse the push until the residual is witnessed/amended  (exit 1)
#   warn   allow the push, print the residual + how to fix it      (exit 0)
#   off    do nothing
# Default is BLOCK: false "done" is refused at the ship seam, while `dos review`
# still renders the residual band first so human attention goes only to claims the
# witness could not clear. Soften with FLEET_REVIEW_GUARD=warn while settling an
# intentional false positive.
#
# Escape hatch:  FLEET_ALLOW_RESIDUAL=1 git push   always allows (e.g. an
# intentional data-artifact commit whose `test()` subject is a known false-positive;
# prefer fixing the subject — a JSON result drop is a `docs(...)`/`chore(...)`, not a `test`).
#
# Fail-open by design: if the `dos` CLI or the trunk ref is unavailable, the hook
# says so and allows the push — a missing verifier must never wedge the trunk.

mode="${FLEET_REVIEW_GUARD:-block}"

# Only gate pushes of the trunk itself; a topic branch / tag push is not the shared
# floor this guard protects.
branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)" || exit 0
# Trunk is `main`; `master` stays allowlisted as the legacy alias (the private
# repo still trunks on master, and these hooks are shared across both copies).
case "$branch" in
	main|master) : ;;
	*) exit 0 ;;
esac

root="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0

# The architest tier table must not drift between pushes. GitHub Actions can report
# TestEveryPackageDeclaresTier red after a direct push, but the local push boundary is
# the earliest in-repo place to refuse the same recurrence (#1145). Use `fak hygiene`
# instead of `go test` so native Windows clones avoid the OS test-binary block noted in
# AGENTS.md. As with the claim review below, a missing runner fails open rather than
# wedging a fresh clone.
tier_mode="${FLEET_TIER_GUARD:-block}"
if [ "$tier_mode" != "off" ] && [ "${ALLOW_TIER_DRIFT:-0}" != "1" ]; then
	if command -v fak >/dev/null 2>&1; then
		tier_out="$(fak hygiene --root "$root" --gates TIER_DECLARED 2>&1)"
		tier_status=$?
	elif command -v go >/dev/null 2>&1; then
		tier_out="$(cd "$root" && go run ./cmd/fak hygiene --root "$root" --gates TIER_DECLARED 2>&1)"
		tier_status=$?
	else
		tier_out="neither 'fak' nor 'go' is on PATH"
		tier_status=2
	fi

	if [ "$tier_status" -eq 1 ]; then
		if [ "$tier_mode" = "block" ]; then
			echo "TIER_DECLARED (blocked): architest tier table drift would red the trunk." >&2
			echo "$tier_out" >&2
			echo "  fix: add the missing tier row or remove the stale row in internal/architest/architest_test.go." >&2
			echo "  override once: ALLOW_TIER_DRIFT=1 git push   (intentional false-positive only)" >&2
			exit 1
		else
			echo "TIER_DECLARED (advisory): architest tier table drift detected." >&2
			echo "$tier_out" >&2
		fi
	elif [ "$tier_status" -ne 0 ]; then
		echo "TIER_DECLARED (warn): tier drift gate could not run; push allowed." >&2
		echo "$tier_out" >&2
	fi
fi

[ "$mode" = "off" ] && exit 0
[ "${FLEET_ALLOW_RESIDUAL:-0}" = "1" ] && exit 0

# Need the dos CLI. Resolve it from PATH; if absent, fail open with a note.
dos_bin="$(command -v dos 2>/dev/null)"
if [ -z "$dos_bin" ]; then
	echo "CLAIM_UNWITNESSED (warn): 'dos' CLI not on PATH; claim-honesty review skipped." >&2
	exit 0
fi

# The range this push would ADD to the trunk: prefer this branch's own upstream,
# then origin/main, then the legacy origin/master. If none is known (fresh clone,
# detached remote), fall back to the last 20 commits rather than failing.
if git rev-parse --verify --quiet "origin/$branch" >/dev/null 2>&1; then
	range="origin/$branch..HEAD"
elif git rev-parse --verify --quiet origin/main >/dev/null 2>&1; then
	range="origin/main..HEAD"
elif git rev-parse --verify --quiet origin/master >/dev/null 2>&1; then
	range="origin/master..HEAD"
else
	range="HEAD~20..HEAD"
fi

# Nothing to review (push adds no commits) -> clean.
if [ -z "$(git rev-list "$range" 2>/dev/null)" ]; then
	exit 0
fi

out="$("$dos_bin" review --workspace "$root" "$range" 2>&1)"
status=$?

# exit 0 clean / 1 a RESIDUAL exists / 2 unreadable range (docs/358).
if [ "$status" -eq 0 ]; then
	exit 0
fi
if [ "$status" -eq 2 ]; then
	echo "CLAIM_UNWITNESSED (warn): dos review could not read $range; push allowed." >&2
	echo "$out" >&2
	exit 0
fi

# status == 1: a residual exists.
if [ "$mode" = "block" ]; then
	echo "CLAIM_UNWITNESSED (blocked): a commit in $range claims something its diff does not witness." >&2
	echo "$out" >&2
	echo "  fix: amend the subject to match what the diff does (a result-artifact drop is docs/chore, not test)," >&2
	echo "       or add the witnessing file; then re-push." >&2
	echo "  override once: FLEET_ALLOW_RESIDUAL=1 git push   (intentional false-positive only)" >&2
	exit 1
else
	echo "CLAIM_UNWITNESSED (advisory): a commit in $range claims something its diff does not witness." >&2
	echo "$out" >&2
	echo "  fix the subject to match the diff, or add the witnessing file. This hook blocks by default; current mode is FLEET_REVIEW_GUARD=warn." >&2
	exit 0
fi
