#!/usr/bin/env bash
# v1.9.17 M1 F4(承 docs/V1916_RULINGS_36.md 裁1「F4 判据面守护改清单哈希
#   (`gate-face.manifest` + 语义目录兜底 + `manifest` 自举,**代路径前缀**)」):
#   **判据面守护** —— ① 同集(语义目录兜底)② 逐件哈希 ③ **自举** ④ 对照必红。
#
# ★★ 由(承分析档 §4.5.3 · 本档最重要之新机制):
#   PR 模式引入直推时代**不存在**之攻击面 —— **PR 可改门禁本身**
#   (把 `Dockerfile.supply-chain` 之 `cargo deny check` 改成 `|| true` 即「全绿合入」,
#     而 `L3` **无法察觉** —— 因其所验**正是该档**)。
#   ★ 「**代路径前缀**」之义: 旧法(`actual ∩ gate_face ≠ ∅` ⇒ C 级)唯**看路径** ⇒
#     ★ ① 改名可绕(改一档名即出集合)② 路径以外之**内容**改**无读数**。
#     ⇒ 新法 = **内容哈希** ⇒ ★ **改名亦红**(新名未登册)∧ **改一字节亦红**(哈希失配)。
#
# ★★ 判据(四条 · 逐条可判):
#   ① **同集(语义目录兜底)**: 判据面目录枚举之档集 ⇄ `manifest` 之键集 **逐名相符**(双向)
#      —— ★ 新档未登册 ⇒ 红(**具名**); ★ `manifest` 有键而档不在 ⇒ 红(**具名**)。
#   ② **逐件哈希**: `manifest` 逐件 `sha256sum -c` ⇒ `rc=0`(★ 改一字节 ⇒ 红**具名**)。
#   ③ **自举**: 锚档 `gate-face.manifest.sha256` 对 `manifest` 校验 `rc=0`
#      —— ★ 无此则**改 `manifest` 即破**(改哈希行即消红)。
#   ④ **对照**: 仓外副本面 —— 甲 不篡改必绿 ∧ 乙 改判据面一字节必红 ∧ 丙 改 `manifest` 一行必红
#      ∧ 丁 新增未登册档必红。★ 四向皆须咬合(承 [[control-comparison-must-be-able-to-flip]])。
#
# ★★ 自举之【双向锁】(本席之设计 · 并载):
#   `manifest` 改而锚不改 ⇒ 判据③ 红(锚所载之值 ≠ 真 `manifest` 哈希);
#   锚改而 `manifest` 不改 ⇒ 判据③ 红(同上)。
#   ⇒ ★ **单独改任一皆红**。
#   ★ 锚**不**入 `manifest` 之哈希集(★ 由: 若入则**自指** —— 锚之内容 = `manifest` 之哈希,
#     而 `manifest` 又载锚之哈希 ⇒ **无不动点**); ★ 锚之**存在**改由判据① 守(入同集之**双侧**)。
#   ★★ 并载(**照实 · 限**): 二者**同改**(且自洽)则可**伪造** —— ★ 此系**任何自指完整性**之
#     **固有限** ⇒ ★ 其治在**人**(§4.5.3 对策1「强制 C 级」)+ `git` 评审**看见 `manifest` 之 diff**,
#     非本器可独任。★ **禁**以本器之绿充「判据面未被动过」之全证。
#
# ★ 判据面【域】(★ 具名 · 逐条对合 §4.5.3 之清单 · **禁**通配「其余」):
#   `.xuanwu/**` · `scripts/tests/**` · `scripts/verify_release.sh` ·
#   `release/*/tools/**` · `botgate.toml` · `deny.toml` · `.github/workflows/**`
#
# 用法: bash scripts/tests/gate_face_manifest.sh [--sign] [<仓根>]
# 退出码: 0 判据全达 / 1 有判据不达 / 2 器之自败
set -uo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"

MODE=check
ROOT=""
for a in "$@"; do
    case "$a" in
        --sign) MODE=sign ;;
        -*) echo "器之自败: 未知旗标 $a" >&2; exit 2 ;;
        *) ROOT="$a" ;;
    esac
done
if [ -z "$ROOT" ]; then ROOT="$(cd "$HERE/../.." && pwd)"; fi
[ -d "$ROOT/.git" ] || { echo "器之自败: $ROOT 非 git 仓" >&2; exit 2; }
cd "$ROOT" || exit 2
SELF="$HERE/$(basename "$0")"

MANIFEST=".xuanwu/gate-face.manifest"
ANCHOR=".xuanwu/gate-face.manifest.sha256"

# ── 判据面全件(★ 域具名 · 排序去重 · ★ 锚**不在**内)─────────────────────────
face_files() {
    {
        git ls-files '.xuanwu/**'
        git ls-files 'scripts/tests/**'
        git ls-files 'scripts/verify_release.sh'
        git ls-files 'release/*/tools/**'
        git ls-files 'botgate.toml' 'deny.toml'
        git ls-files '.github/workflows/**'
    } | grep -vx "$MANIFEST" | grep -vx "$ANCHOR" | sort -u
}
# ★ 键集之**双侧** = 判据面全件 + **锚档**(★ 锚之【存在】由判据① 守)
# ★★ 锚**须**自 `face_files` 剔出(★ **本席当场自捕**): 若留 ⇒ 锚**入** `manifest` 之哈希集
#   ⇒ ★ **自指**(`manifest` 载锚之哈希,而锚之内容 = `manifest` 之哈希 ⇒ **无不动点**)
#   ⇒ 判据② 必红。★★ 该 bug **潜伏**于锚【未跟踪】之时(`git ls-files` 不列之 ⇒ 不入集),
#   `git add` 之后**现形** —— ★ 即本席自捕③(`git ls-files` 唯列已跟踪)之**镜像形**。

# ── 重签 ───────────────────────────────────────────────────────────────────
if [ "$MODE" = "sign" ]; then
    mkdir -p "$(dirname "$MANIFEST")"
    { face_files | while IFS= read -r f; do sha256sum "$f"; done; } > "$MANIFEST.tmp" \
        || { echo "器之自败: 生成失败" >&2; exit 2; }
    mv "$MANIFEST.tmp" "$MANIFEST"
    sha256sum "$MANIFEST" > "$ANCHOR"
    echo "已重签: $MANIFEST($(wc -l < "$MANIFEST") 条)· $ANCHOR($(cat "$ANCHOR"))"
    exit 0
fi

RC=0
echo "== gate-face-manifest · 面 =="
echo "  仓根 = $ROOT"
echo "  支 = $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '<无>')"
echo "  尖 = $(git rev-parse HEAD 2>/dev/null || echo '<无>')"
echo "  器 sha256 = $(sha256sum "$SELF" | awk '{print $1}')"
echo "  清单 = $MANIFEST · 锚 = $ANCHOR"

[ -f "$MANIFEST" ] || { echo "器之自败: 清单不在位(先 --sign)" >&2; exit 2; }
[ -f "$ANCHOR" ] || { echo "器之自败: 锚不在位(先 --sign)" >&2; exit 2; }

# ── ★★ 前置闸: 域内【未跟踪】之档(★ **本席当场自捕**)────────────────────────
# ★ 由: `face_files` 取 `git ls-files` ⇒ **唯列【已跟踪】之档** ⇒ ★ 未 `git add` 之新档
#   **不入册** ∧ 判据① **看不见**它(两侧皆无)⇒ ★★ **册不全而报绿** = 假绿。
#   ★ 并载(照实): 门禁面(CI checkout)之档**恒已跟踪** ⇒ 该险**唯本地**; ★ 然 `--sign`
#     若于未 `add` 时跑 ⇒ **签出之册缺件** ⇒ 该缺件**随提交入仓** ⇒ ★ 险**不再限本地**。
#   ★ 正形 = 本闸(未跟踪 ⇒ 器之自败 `rc=2` · **具名** ⇒ 禁静默)。
mapfile -t UNTRACKED < <(git ls-files --others --exclude-standard -- \
    .xuanwu scripts/tests release 2>/dev/null | grep -vx "$MANIFEST" | grep -vx "$ANCHOR" \
    | grep -E '^\.xuanwu/|^scripts/tests/|^scripts/verify_release\.sh$|^release/[^/]+/tools/|^botgate\.toml$|^deny\.toml$|^\.github/workflows/' | sort)
if [ "${#UNTRACKED[@]}" -gt 0 ]; then
    echo "器之自败: 判据面域内有【未跟踪】之档 ${#UNTRACKED[@]} 件 ⇒ 清单必不全(先 \`git add\` 再 --sign):" >&2
    printf '  ? %s\n' "${UNTRACKED[@]}" >&2
    exit 2
fi

ACT="$(mktemp)"; EXP="$(mktemp)"
trap 'rm -f "$ACT" "$EXP"' EXIT

# ── ① 同集(语义目录兜底)──────────────────────────────────────────────────
echo
echo "-- 判据① 同集: 判据面枚举 ⇄ manifest 键集(双向 · 双侧含锚)"
{ face_files; printf '%s\n' "$ANCHOR"; } | sort -u > "$ACT"
{ awk '{ $1=""; sub(/^ +/,""); print }' "$MANIFEST"; printf '%s\n' "$ANCHOR"; } | sort -u > "$EXP"
N_ACT="$(wc -l < "$ACT")"; N_EXP="$(wc -l < "$EXP")"
echo "  枚举档数 = $N_ACT · manifest 键数(含锚)= $N_EXP"
if diff -u "$ACT" "$EXP" > /dev/null 2>&1; then
    echo "  ✓ 判据① 逐名相符"
else
    echo "  ✗ 判据① 不同集(★ \`-\` = 枚举有而 manifest 无 ⇒ **未登册**; \`+\` = manifest 有而档不在 ⇒ **键腐**):" >&2
    diff -u "$ACT" "$EXP" | sed -n '3,$p' | sed 's/^/    /' >&2
    RC=1
fi

# ── ② 逐件哈希 ─────────────────────────────────────────────────────────────
echo
echo "-- 判据② 逐件 \`sha256sum -c\`"
if out="$(sha256sum -c "$MANIFEST" 2>&1)"; then
    echo "  ✓ 判据② $(wc -l < "$MANIFEST") 件哈希皆符"
else
    echo "  ✗ 判据② 哈希失配 · 具名:" >&2
    printf '%s\n' "$out" | grep -v ': OK$' | sed 's/^/    /' >&2
    RC=1
fi

# ── ③ 自举 ─────────────────────────────────────────────────────────────────
echo
echo "-- 判据③ 自举: 锚对 manifest 校验"
if out="$(sha256sum -c "$ANCHOR" 2>&1)"; then
    echo "  ✓ 判据③ 自举咬合($(printf '%s' "$out" | tr '\n' ' '))"
else
    echo "  ✗ 判据③ 自举破(★ 改 manifest 或改锚 而未重签): $out" >&2
    RC=1
fi

# ── ④ 对照(★ 仓外副本 · 四向)──────────────────────────────────────────────
# ★★ 递归闸(★ **本席当场自捕**): 对照面**调本器自身**(`bash "$SELF" "$F"`)⇒
#   若无闸则**无限递归**(对照面之对照面之……)—— 首版实测**挂起**(`timeout 400` ⇒ `rc=124`)。
#   ★ 正形 = 环境闸 `XW_GFM_NO_CONTROL=1`(对照面内之器**只跑判据①②③** · 不造对照)。
echo
if [ "${XW_GFM_NO_CONTROL:-0}" = "1" ]; then
    echo "-- 判据④ 对照: **本面内跳过**(XW_GFM_NO_CONTROL=1 · 防递归)"
    echo "== 判据 =="
    exit "$RC"
fi
echo "-- 判据④ 对照(仓外副本 · 四向皆须咬合)"
T="$(mktemp -d)"; trap 'rm -f "$ACT" "$EXP"; rm -rf "$T"' EXIT
F="$T/f"
mkdir -p "$F/scripts/tests" "$F/.xuanwu"
git -C "$F" init -q
git -C "$F" config user.email t@t; git -C "$F" config user.name t
printf '#!/bin/bash\necho leg-a\n' > "$F/scripts/tests/leg_a.sh"
printf '#!/bin/bash\necho leg-b\n' > "$F/scripts/tests/leg_b.sh"
git -C "$F" add -A >/dev/null 2>&1; git -C "$F" commit -qm init >/dev/null 2>&1
bash "$SELF" --sign "$F" >/dev/null 2>&1 || { echo "  ✗ 对照面重签失败(器之自败)" >&2; exit 2; }
chk() { XW_GFM_NO_CONTROL=1 bash "$SELF" "$F" 2>&1; }

chk > "$T/o0" 2>&1; r0=$?
echo "  甲 不篡改            rc=$r0(期 0)"

printf '#!/bin/bash\necho leg-a-TAMPERED\n' > "$F/scripts/tests/leg_a.sh"
chk > "$T/o1" 2>&1; r1=$?
echo "  乙 改判据面一字节    rc=$r1(期 ≠ 0)"
git -C "$F" checkout -q -- scripts/tests/leg_a.sh

sed -i '1s/./f/' "$F/$MANIFEST"
chk > "$T/o2" 2>&1; r2=$?
echo "  丙 改 manifest 一行  rc=$r2(期 ≠ 0)"
bash "$SELF" --sign "$F" >/dev/null 2>&1

printf '#!/bin/bash\necho new-leg\n' > "$F/scripts/tests/leg_c.sh"
git -C "$F" add -A >/dev/null 2>&1; git -C "$F" commit -qm add-c >/dev/null 2>&1
chk > "$T/o3" 2>&1; r3=$?
echo "  丁 新增未登册档      rc=$r3(期 ≠ 0)"

if [ "$r0" -eq 0 ] && [ "$r1" -ne 0 ] && [ "$r2" -ne 0 ] && [ "$r3" -ne 0 ]; then
    echo "  ✓ 判据④ 四向咬合"
else
    echo "  ✗ 判据④ 未咬合(r0=$r0 r1=$r1 r2=$r2 r3=$r3)—— 谓词无判别力" >&2; RC=1
fi
echo "  -- 具名抽验(乙/丙/丁 各取一行 · 证「红」皆**具名**而非仅 『rc』)"
grep -m1 'FAILED' "$T/o1" | sed 's/^/    乙: /' || true
grep -m1 '自举破' "$T/o2" | sed 's/^/    丙: /'
grep -m1 'leg_c\.sh' "$T/o3" | sed 's/^/    丁: /' || true

echo
echo "== 判据(★ 唯载【不变量】· 读数唯见上列各行 · 禁由本块推)=="
echo "  · 判据① = 判据面枚举 ⇄ manifest 键集 逐名相符(新档未登册 ⇒ 红具名);"
echo "  · 判据② = manifest 逐件 sha256sum -c rc=0(改一字节 ⇒ 红具名);"
echo "  · 判据③ = 自举: 锚对 manifest 校验 rc=0;"
echo "  · 判据④ = 四向对照(不篡改绿 ∧ 改判据面红 ∧ 改 manifest 红 ∧ 新增档红);"
echo "  · ★ 限 = manifest ∧ 锚**同改自洽**则可伪造(自指之固有限 · 治在人)。"
exit "$RC"