phase13.5: fix remediation recreate Gate O runner allowance

This commit is contained in:
root 2026-08-31 16:13:40 +00:00
parent d6394ef327
commit b61acd3f08

View file

@ -1131,10 +1131,16 @@ recreate_pre_gates() {
# Alle echten Remediation-Gates bleiben FAIL CLOSED. Kein freies Image, keine freie ID.
remediation_recreate_pre_gates() {
load_recovery_state
echo " [gate A] verify-recovery-gold-image PASS:"
( verify_recovery_gold_image >/dev/null 2>&1 ) && echo " OK" || die "Gate A FAIL: Recovery-Gold nicht verifiziert (FAIL CLOSED)"
echo " [gate B] verify-candidate-image PASS:"
( verify_candidate_image >/dev/null 2>&1 ) && echo " OK" || die "Gate B FAIL: Candidate nicht verifiziert (FAIL CLOSED)"
echo " [gate A] verify-gold-target-image (RECOVERY/GOLD TARGET-State, image-only) PASS:"
# Phase 13.5 Pre-Gate-A-Fix: verifiziere das RECOVERY/GOLD-ZIEL (Target-State) direkt,
# NICHT den laufenden historical-service Source-State. Ein Remediation-Recreate muss
# auch moeglich sein, wenn der aktuelle Service bereits Remediation traegt. Der fix
# wiederverwendet den bereits geprueften image-basierten Helper verify_gold_target_image
# (63/63 Gold + Critical SHAs + Config, via Temp-Container aus dem ZIEL-Image).
# RECOVERY_GOLD_IMAGE_ID ist fest aus Recovery-State (load_recovery_state), keine freie ID/kein latest.
( verify_gold_target_image "$RECOVERY_GOLD_IMAGE_ID" >/dev/null 2>&1 ) && echo " OK" || die "Gate A FAIL: Recovery/Gold-Ziel nicht verifiziert (FAIL CLOSED)"
echo " [gate B] verify-candidate-target-image (REMEDIATION/CANDIDATE TARGET-State, image-only 62+1) PASS:"
( verify_candidate_target_image "$(remediation_full_image_id)" >/dev/null 2>&1 ) && echo " OK" || die "Gate B FAIL: Candidate/Remediation-Ziel nicht verifiziert (FAIL CLOSED)"
echo " [gate C] Recovery Image inspectable:"
docker image inspect "$RECOVERY_GOLD_IMAGE_ID" >/dev/null 2>&1 || die "Gate C FAIL (FAIL CLOSED)"
echo " OK"
@ -1316,12 +1322,17 @@ verify_candidate_image() {
# Critical-File-SHAs eines ZIEL-IMAGES gegen Gold. NUR Image, KEIN Container-Vergleich.
# $1 = Basisverzeichnis mit app/ (kopierter Target-Tree). FAIL CLOSED.
verify_critical_files_image_only() {
local base_dir="$1" allok=1 k path is gold
local base_dir="$1" allok=1 k path is gold allow_runner="${2:-}"
for k in "${!CRITICAL_SHA[@]}"; do
path="${CRITICAL_PATH[$k]#app/}"
is="MISSING"
[ -f "$base_dir/$path" ] && is="$(sha256sum "$base_dir/$path" | awk '{print $1}')"
gold="${CRITICAL_SHA[$k]}"
# 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" ] && [ "$is" = "$RUNNER_REMEDIATION_SHA" ]; then
printf ' AUTH %-22s image=%s gold=%s (autorisierte Runner-Divergenz)\n' "$k" "${is:0:12}" "${gold:0:12}"
continue
fi
if [ "$is" = "$gold" ]; then
printf ' MATCH %-22s image=%s gold=%s\n' "$k" "${is:0:12}" "${gold:0:12}"
else
@ -1353,6 +1364,46 @@ verify_image_config_target() {
echo " [config] OK (ENV/secrets NICHT ausgegeben)"
}
# Phase 13.5 Gate-B-Fix: image-only 62+1 Candidate-Target-Verifikation.
# Prueft das REMEDIATION-CANDIDATE-ZIEL-IMAGE direkt (feste ID aus Session), NICHT den
# laufenden historical-service Container. Erlaubt exakt 1 autorisierte Runner-Divergenz
# (run_backfill_year.py = RUNNER_REMEDIATION_SHA). repository.py MUSS Gold bleiben.
# Wiederverwendet: authorized_gold_delta, verify_critical_files_image_only, verify_image_config_target.
# KEIN freies Image / latest / autodiscovery. FAIL CLOSED bei jeder Abweichung.
verify_candidate_target_image() {
local TARGET_ID="$1"
echo " [candidate-target] Remediation-Candidate-Image inspectable:"
docker image inspect "$TARGET_ID" >/dev/null 2>&1 || die "candidate-target FAIL: Ziel-Image nicht inspectable (FAIL CLOSED)"
echo " OK ($TARGET_ID)"
echo " [candidate-target] 62+1 Gold-Manifest (1 autorisierter Runner-Overlay) + Critical SHAs + Config (IMAGE-ONLY):"
local tmpc tmpdir tmpf
tmpc="rq-candtgt-$(date +%s)"; tmpdir="/tmp/rq-candtgt-copy-$$"
rm -rf "$tmpdir"; mkdir -p "$tmpdir"
docker create --name "$tmpc" --entrypoint /bin/true "$TARGET_ID" >/dev/null 2>&1 || die "candidate-target: Temp-Container Fehler (FAIL CLOSED)"
docker cp "$tmpc:/app/app" "$tmpdir/appapp" >/dev/null 2>&1 || { docker rm -f "$tmpc" >/dev/null 2>&1; rm -rf "$tmpdir"; die "candidate-target: /app/app nicht lesbar (FAIL CLOSED)"; }
docker rm -f "$tmpc" >/dev/null 2>&1 || true
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"
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"; rm -f "$tmpf"; die "candidate-target: Gold-Delta != exakt 1 autorisierte Runner-Divergenz (FAIL CLOSED)"; }
[ "$AD_RET" = "ok" ] || { rm -rf "$tmpdir"; rm -f "$tmpf"; die "candidate-target: Image != Gold-Manifest (unzulaessige Divergenz) (FAIL CLOSED)"; }
# [repository hard gate] app/persistence/repository.py im Image MUSS Gold sein (8fff892f...)
local irepo; irepo="$(awk '$2=="app/persistence/repository.py"{print $1}' "$tmpf")"
[ "$irepo" = "8fff892f52a612f464b5be21c66ef7787a47f2d7d049f815ef87837553c2a228" ] \
|| { rm -rf "$tmpdir"; rm -f "$tmpf"; die "candidate-target: repository.py im Image != Gold (8fff892f...) (FAIL CLOSED)"; }
echo " repository.py = Gold (8fff892f...) OK"
rm -f "$tmpf"
echo " [Critical-Files] Candidate-Target vs gold (image-only, autorisierte Runner-Divergenz erlaubt):"
verify_critical_files_image_only "$tmpdir/appapp" runner
rm -rf "$tmpdir"
echo " [Config] Candidate-Target Runtime Config:"
verify_image_config_target "$TARGET_ID"
echo "VERDICT: CANDIDATE TARGET OK (62 Gold-MATCH + 1 autorisierter Runner-Overlay; repository.py Gold; Critical-SHAs + Config PASS)"
return 0
}
# Kompletter image-only Gold-Verify eines festen ZIEL-IMAGES.
# $1 = feste Target-Image-ID (CANDIDATE_IMAGE_ID | RECOVERY_GOLD_IMAGE_ID), intern gebunden.
# inspectable -> 63/63 Gold-Manifest -> Critical SHAs -> Config. KEIN Container-Vergleich.
@ -1925,8 +1976,8 @@ verify_runner_remediation_image() {
echo " HOST_SOT_DRIFT=DETECTED (repository.py $hrepo vs Gold 8fff892f...)"
echo " HOST_SOT_DRIFT=NON-BLOCKING_FOR_REMEDIATION_BUILD"
rm -f "$tmpf"
echo " [Critical-Files] $REMEDIATION_TAG vs gold:"
verify_critical_files "$tmpdir/appapp" runner
echo " [Critical-Files] $REMEDIATION_TAG vs gold (image-only, autorisierte Runner-Divergenz erlaubt):"
verify_critical_files_image_only "$tmpdir/appapp" runner
rm -rf "$tmpdir"
echo " [Runner] Image == autorisierter Overlay (6cca3c55...):"
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=""