Phase 13.5: Authorized Remediation Divergence Verify Fix - verify-runner-remediation-image nutzt gemeinsame authorized_gold_delta-Policy (exakt 1 autorisierte Runner-Divergenz, alles andere FAIL CLOSED); Candidate-vs-Host-SoT direkter Vergleich; verify_critical_files runner-Flag

This commit is contained in:
Rain Ocampo 2026-08-29 23:56:14 +00:00
parent 6194868100
commit 8187a4bc1d

View file

@ -526,10 +526,14 @@ gen_host_manifest() {
}
# read-only: Critical-Files-SHAs Recovery-Image + laufender Gold-Container + Gold-Baseline (Punkt 11)
# $2 (optional) = "runner" -> erlaubt die EINE autorisierte Runner-Divergenz im IMAGE:
# run_backfill_year.py image=RUNNER_REMEDIATION_SHA (Container bleibt Gold).
# Nur fuer verify-runner-remediation-image; alle anderen Aufrufer bleiben strikt 0-Divergenz.
verify_critical_files() {
# $1 = Basisverzeichnis mit app/ (kopierter Recovery-Tree) ODER leer -> nur Container+Baseline
local tmpc tmpdir k path imgsha csha
local base_dir="${1:-}"
local allow_runner="${2:-}"
local img_shas=""
if [ -n "$base_dir" ]; then
# Recovery-Image-SHAs aus kopiertem Tree. Der Tree ist prefixlos (docker cp
@ -549,6 +553,12 @@ verify_critical_files() {
is=$(echo "$img_shas" | grep -oE "${k}:[a-f0-9]+|${k}:MISSING" | cut -d: -f2)
img="$is"
fi
# Autorisierte Runner-Divergenz: NUR run_backfill_year.py, NUR im Image, NUR auf RUNNER_REMEDIATION_SHA.
if [ "$allow_runner" = "runner" ] && [ "$k" = "run_backfill_year.py" ] \
&& [ "$img" = "$RUNNER_REMEDIATION_SHA" ] && [ "$csha" = "$gold" ]; then
printf ' AUTH %-22s image=%s container=%s gold=%s (autorisierte Runner-Divergenz)\n' "$k" "${img:0:12}" "${csha:0:12}" "${gold:0:12}"
continue
fi
if [ "$csha" = "$gold" ] && { [ -z "$base_dir" ] || [ "$img" = "$gold" ]; }; then
printf ' MATCH %-22s container=%s gold=%s\n' "$k" "${csha:0:12}" "${gold:0:12}"
else
@ -1106,41 +1116,55 @@ remediation_guards() {
echo " [guard O] kein freier Parameter/User-Input:"
echo " OK (keine Argumente akzeptiert)"
}
# Read-only Build-Input-Check: nur app/run_backfill_year.py darf vom Gold-Manifest
# divergieren (und zwar exakt auf RUNNER_REMEDIATION_SHA). alles andere -> FAIL.
verify_remediation_build_input() {
local hm gold d=0 m=0 x=0 mat=0 extra_ok=0 runner_seen=0
hm="$(mktemp)"; gen_host_manifest > "$hm"
# ---- GEMEINSAME AUTHORIZED-GOLD-DELTA-POLICY (Phase 13.5) ----
# Einzige autorisierte Divergenz gegen GOLD_MANIFEST:
# app/run_backfill_year.py ALT=RUNNER_GOLD_SHA -> NEU=RUNNER_REMEDIATION_SHA
# (exakte Pfad- UND SHA-Bindung; KEINE generische "1 Divergenz ok"-Lockerung).
# Setzt globale Zaehler AD_MATCH/AD_AUTH/AD_UNAUTH/AD_MISSING/AD_EXTRA und
# AD_RET (ok|fail). fail wenn UNAUTH>0 || MISSING>0 || EXTRA>0.
# Wird von verify_remediation_build_input UND verify_runner_remediation_image
# verwendet -> EINE Policy, keine unterschiedlichen Policies.
authorized_gold_delta() {
local new="$1" gold goldrel goldsha nsha nrel found
AD_MATCH=0; AD_AUTH=0; AD_UNAUTH=0; AD_MISSING=0; AD_EXTRA=0
while read -r goldsha goldrel; do
goldrel="${goldrel#./}"; [ -n "$goldrel" ] || continue
local nsha
nsha="$(awk -v r="$goldrel" '$2==r{print $1}' "$hm")"
if [ -z "$nsha" ]; then m=$((m+1))
elif [ "$nsha" = "$goldsha" ]; then mat=$((mat+1))
nsha="$(awk -v r="$goldrel" '$2==r{print $1}' "$new")"
if [ -z "$nsha" ]; then AD_MISSING=$((AD_MISSING+1))
elif [ "$nsha" = "$goldsha" ]; then AD_MATCH=$((AD_MATCH+1))
else
if [ "$goldrel" = "app/run_backfill_year.py" ]; then
runner_seen=1
if [ "$nsha" = "$RUNNER_REMEDIATION_SHA" ]; then extra_ok=1; else die "BUILD-INPUT FAIL: Runner divergiert auf unerwartete SHA $nsha"; fi
if [ "$goldrel" = "app/run_backfill_year.py" ] \
&& [ "$goldsha" = "$RUNNER_GOLD_SHA" ] \
&& [ "$nsha" = "$RUNNER_REMEDIATION_SHA" ]; then
AD_AUTH=$((AD_AUTH+1))
else
echo " DIVERGED(UNERWARTET) $goldrel"; d=$((d+1))
AD_UNAUTH=$((AD_UNAUTH+1))
fi
fi
done < "$GOLD_MANIFEST"
while read -r nsha nrel; do
nrel="${nrel#./}"
local found
found="$(awk -v r="$nrel" '$2==r{print $1}' "$GOLD_MANIFEST")"
if [ -z "$found" ]; then
case "$nrel" in
app/app/backfill.py|app/app/test_idempotency.py|app/eligibility.py) ;;
*) echo " EXTRA(UNERLAUBT) $nrel"; x=$((x+1)) ;;
*) AD_EXTRA=$((AD_EXTRA+1)) ;;
esac
fi
done < "$hm"
done < "$new"
AD_RET="ok"
[ $AD_UNAUTH -eq 0 ] && [ $AD_MISSING -eq 0 ] && [ $AD_EXTRA -eq 0 ] || AD_RET="fail"
}
# Read-only Build-Input-Check: nur app/run_backfill_year.py darf vom Gold-Manifest
# divergieren (und zwar exakt auf RUNNER_REMEDIATION_SHA). alles andere -> FAIL.
verify_remediation_build_input() {
local hm
hm="$(mktemp)"; gen_host_manifest > "$hm"
authorized_gold_delta "$hm"
rm -f "$hm"
echo " Gold $((mat+d+m)): MATCH=$mat DIVERGED=$d MISSING=$m ; unerlaubte EXTRA=$x ; autorisierte Runner-Divergenz=$extra_ok"
[ "$runner_seen" = "1" ] && [ "$extra_ok" = "1" ] || die "BUILD-INPUT FAIL: autorisierte Runner-Divergenz fehlt (FAIL CLOSED)"
[ $d -eq 0 ] && [ $m -eq 0 ] && [ $x -eq 0 ] || die "BUILD-INPUT FAIL: unerwartete Divergenz (FAIL CLOSED)"
echo " Gold $((AD_MATCH+AD_AUTH+AD_UNAUTH+AD_MISSING)): MATCH=$AD_MATCH AUTHORIZED_DIVERGED=$AD_AUTH UNAUTHORIZED_DIVERGED=$AD_UNAUTH MISSING=$AD_MISSING ; unerlaubte EXTRA=$AD_EXTRA"
[ "$AD_AUTH" = "1" ] || die "BUILD-INPUT FAIL: autorisierte Runner-Divergenz fehlt (FAIL CLOSED)"
[ "$AD_RET" = "ok" ] || die "BUILD-INPUT FAIL: unerwartete Divergenz (FAIL CLOSED)"
return 0
}
# ---- build-runner-remediation (DRY_RUN = kein Build; KEIN Overwrite) ----
@ -1202,13 +1226,32 @@ verify_runner_remediation_image() {
tmpf="$(mktemp)"
( cd "$tmpdir/appapp" && find . -type f -name '*.py' ! -path '*/__pycache__/*' -printf '%P\n' 2>/dev/null | sort \
| while IFS= read -r rel; do [ -f "$rel" ] && sha256sum "$rel"; done | awk '{print $1" app/"$2}' ) > "$tmpf"
classify_and_report "$tmpf"; local gret="$G_RET"; rm -f "$tmpf"
if [ "$gret" != "ok" ]; then
rm -rf "$tmpdir"; die "verify: Image != Gold-Manifest (unzulaessige Divergenz) (FAIL CLOSED)"
fi
echo " Image vs GOLD (63 MATCH / 0 DIVERGED / 0 MISSING / 0 unerlaubte EXTRA)"
# [Gold-Delta] Image vs GOLD_MANIFEST: exakt 1 autorisierte Runner-Divergenz erlaubt.
authorized_gold_delta "$tmpf"
echo " Image vs GOLD: MATCH=$AD_MATCH AUTHORIZED_DIVERGED=$AD_AUTH UNAUTHORIZED_DIVERGED=$AD_UNAUTH MISSING=$AD_MISSING unerlaubte EXTRA=$AD_EXTRA"
[ "$AD_AUTH" = "1" ] || { rm -rf "$tmpdir"; die "verify: Gold-Delta != exakt 1 autorisierte Runner-Divergenz (FAIL CLOSED)"; }
[ "$AD_RET" = "ok" ] || { rm -rf "$tmpdir"; die "verify: Image != Gold-Manifest (unzulaessige Divergenz) (FAIL CLOSED)"; }
# [Candidate vs Host-SoT] Image-App-Manifest == Host-SoT-App-Manifest (direkter
# Manifest-zu-Manifest-Vergleich, OHNE Gold-Bezug; vollstaendig MATCH).
# Host-only-Legacy-Dateien (im Image via .dockerignore ausgeschlossen) sind erlaubt.
local hmf cmiss cextra
hmf="$(mktemp)"; gen_host_manifest > "$hmf"
cmiss=0; cextra=0
while read -r hsha hrel; do
hrel="${hrel#./}"; [ -n "$hrel" ] || continue
case "$hrel" in
app/app/backfill.py|app/app/test_idempotency.py|app/eligibility.py) continue ;;
esac
isha="$(awk -v r="$hrel" '$2==r{print $1}' "$tmpf")"
if [ -z "$isha" ]; then cmiss=$((cmiss+1))
elif [ "$isha" != "$hsha" ]; then cextra=$((cextra+1)); fi
done < "$hmf"
rm -f "$hmf"
echo " Candidate vs Host-SoT: MATCH (direkt) MISSING=$cmiss DIVERGED=$cextra"
[ "$cmiss" = "0" ] && [ "$cextra" = "0" ] || { rm -rf "$tmpdir"; die "verify: Candidate != Host-SoT (FAIL CLOSED)"; }
rm -f "$tmpf"
echo " [Critical-Files] $REMEDIATION_TAG vs gold:"
verify_critical_files "$tmpdir/appapp"
verify_critical_files "$tmpdir/appapp" runner
rm -rf "$tmpdir"
echo " [Runner] Image vs Host-SoT persistent:"
local ir; ir="$(docker run --rm --entrypoint /bin/sh "$imgid" -c "sha256sum /app/app/run_backfill_year.py 2>/dev/null" | awk '{print $1}')" || ir=""
@ -1230,7 +1273,7 @@ verify_runner_remediation_image() {
local srunner; srunner="$(grep '^HOST_SOT_RUNNER_SHA=' "$REMEDIATION_SESSION_META" | cut -d= -f2)"
[ "$srunner" = "$RUNNER_REMEDIATION_SHA" ] || die "verify: Session-Runner != erwartet (FAIL CLOSED)"
echo " OK ($srunner)"
echo "VERDICT: REMEDIATION IMAGE OK (63 MATCH / 0 DIVERGED / 0 MISSING / 0 unerlaubte EXTRA; Runner=Host-SoT; Critical-SHAs + Config PASS)"
echo "VERDICT: REMEDIATION IMAGE OK (Candidate==Host-SoT; Gold-Delta exakt 1 autorisierte Runner-Divergenz; Critical-SHAs + Config PASS)"
exit 0
}
# ---- recreate-runner-remediation-safe (NUR historical-service; nur Red Queen) ----