RAIN: Phase 13.5 Emergency Rollback CP Repair - target-image-based rollback pre-gates (current/recovery gold). SoT local, no push.

This commit is contained in:
root 2026-08-31 12:54:03 +00:00
parent a7336c2585
commit d6394ef327

View file

@ -1305,6 +1305,101 @@ verify_candidate_image() {
exit 0 exit 0
} }
# ==== RAIN EMERGENCY ROLLBACK CP REPAIR: Target-spezifische image-only Verifikation ====
# ROLLBACK-PRE-GATES muessen das jeweilige ROLLBACK-ZIEL-IMAGE direkt verifizieren,
# NICHT den laufenden historical-service Container (der traegt bewusst ein anderes
# Image, z.B. Remediation). Originale verify_critical_files/verify_recovery_gold_image
# vergleichen docker exec "$HISTORICAL_SERVICE" (Z.916) -> falscher FAIL bei laufendem
# Remediation-Container. Diese Helper prüfen ausschliesslich ein festes Ziel-Image.
# KEIN freies CLI-Argument, KEIN latest, KEINE auto-discovery.
# 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
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]}"
if [ "$is" = "$gold" ]; then
printf ' MATCH %-22s image=%s gold=%s\n' "$k" "${is:0:12}" "${gold:0:12}"
else
printf ' FAIL %-22s image=%s gold=%s\n' "$k" "${is:0:12}" "${gold:0:12}"
allok=0
fi
done
[ "$allok" = "1" ] || die "verify: Critical-File-SHA-Mismatch im Ziel-IMAGE (FAIL CLOSED)"
}
# Runtime-Config eines ZIEL-IMAGES gegen erwartete Baseline. $1 = Target-ID/Tag.
# NUR Image-inspect, KEIN Container. FAIL CLOSED. ENV/secrets redigiert.
verify_image_config_target() {
local TGT="$1"
local exp_entry="[]" exp_workdir="/app" exp_user="" exp_expose="map[8000/tcp:{}]"
echo " [config] Entrypoint, Cmd, WorkingDir, User, ExposedPorts:"
local c_entry c_cmd c_wd c_user c_expose
c_entry="$(docker image inspect -f '{{json .Config.Entrypoint}}' "$TGT" 2>/dev/null)"
c_cmd="$(docker image inspect -f '{{json .Config.Cmd}}' "$TGT" 2>/dev/null)"
c_wd="$(docker image inspect -f '{{.Config.WorkingDir}}' "$TGT" 2>/dev/null)"
c_user="$(docker image inspect -f '{{.Config.User}}' "$TGT" 2>/dev/null)"
c_expose="$(docker image inspect -f '{{json .Config.ExposedPorts}}' "$TGT" 2>/dev/null)"
printf ' Entrypoint = %s (erwartet %s)\n' "${c_entry:-<nil>}" "$exp_entry"
printf ' Cmd = %s\n' "${c_cmd:-<nil>}"
printf ' WorkingDir = %s (erwartet %s)\n' "${c_wd:-<nil>}" "$exp_workdir"
printf ' User = %s (erwartet leer)\n' "${c_user:-<nil>}"
printf ' Exposed = %s (erwartet 8000)\n' "${c_expose:-<nil>}"
[ "$c_wd" = "$exp_workdir" ] || die "verify: WorkingDir unerwartet '$c_wd' (FAIL CLOSED)"
echo " [config] OK (ENV/secrets NICHT ausgegeben)"
}
# 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.
verify_gold_target_image() {
local TARGET_ID="$1"
echo " [target] Target-Image inspectable:"
docker image inspect "$TARGET_ID" >/dev/null 2>&1 || die "target FAIL: Ziel-Image nicht inspectable (FAIL CLOSED)"
echo " OK ($TARGET_ID)"
echo " [target] 63/63 Gold-Manifest + Critical SHAs + Config (IMAGE-ONLY, kein Container-Vergleich):"
local tmpc tmpdir tmpf gret
tmpc="rq-goldtgt-$(date +%s)"; tmpdir="/tmp/rq-goldtgt-copy-$$"
rm -rf "$tmpdir"; mkdir -p "$tmpdir"
docker create --name "$tmpc" --entrypoint /bin/true "$TARGET_ID" >/dev/null 2>&1 || die "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 "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"
classify_and_report "$tmpf"
gret="$G_RET"; rm -f "$tmpf"
if [ "$gret" != "ok" ]; then
rm -rf "$tmpdir"; die "target FAIL: Ziel-Image != Gold-Manifest (FAIL CLOSED)"
fi
echo " TARGET vs GOLD: 63 MATCH / 0 DIVERGED / 0 MISSING / 0 unerlaubte EXTRA"
echo " [target] Critical-Files (IMAGE-ONLY):"
verify_critical_files_image_only "$tmpdir/appapp"
rm -rf "$tmpdir"
verify_image_config_target "$TARGET_ID"
echo " VERDICT: TARGET GOLD OK (63/63, Critical SHAs + Config PASS, IMAGE-ONLY)"
return 0
}
# SEPARATER Current-Service-Safety-Check. Prueft, DASS der historical-service laeuft
# und healthy ist. Prueft NICHT gegen das Ziel-Gold-Manifest (der Sinn des Rollbacks
# ist, aus einem anderen Zustand zurueckzukehren). Intern, kein freies Argument.
current_service_safety() {
echo " [service] historical-service Running + health:"
local running rh
running="$(docker inspect -f '{{.State.Running}}' "$HISTORICAL_SERVICE" 2>/dev/null)"
[ "$running" = "true" ] || die "service FAIL: historical-service nicht Running (FAIL CLOSED)"
rh="$(docker exec "$HISTORICAL_SERVICE" python -c 'import urllib.request;print(urllib.request.urlopen("http://localhost:8000/health",timeout=5).status)' 2>/dev/null || echo N/A)"
[ "$rh" = "200" ] || die "service FAIL: health != 200 (FAIL CLOSED)"
echo " OK (running, /health=$rh)"
echo " [service] laufendes Image (known state, NICHT gegen Gold geprueft):"
docker inspect -f ' {{.Image}}' "$HISTORICAL_SERVICE" 2>/dev/null
}
# ---- recreate-candidate-safe (NUR historical-service; nur Red Queen) ---- # ---- recreate-candidate-safe (NUR historical-service; nur Red Queen) ----
recreate_candidate_safe() { recreate_candidate_safe() {
expect_zero "$@" expect_zero "$@"
@ -1343,8 +1438,10 @@ rollback_to_recovery_gold() {
local rt; rt="$(docker image inspect -f '{{.Id}}' "$RECOVERY_GOLD_TAG_FINAL" 2>/dev/null || true)" local rt; rt="$(docker image inspect -f '{{.Id}}' "$RECOVERY_GOLD_TAG_FINAL" 2>/dev/null || true)"
[ "$rt" = "$RECOVERY_GOLD_IMAGE_ID" ] || die "pre FAIL: Recovery-Tag != Recovery-ID (FAIL CLOSED)" [ "$rt" = "$RECOVERY_GOLD_IMAGE_ID" ] || die "pre FAIL: Recovery-Tag != Recovery-ID (FAIL CLOSED)"
echo " OK" echo " OK"
echo " [pre] verify-recovery-gold-image PASS:" echo " [pre] Current Service Safety (running + health, NICHT Gold-geprueft):"
( verify_recovery_gold_image >/dev/null 2>&1 ) && echo " OK" || die "pre FAIL: Recovery-Gold nicht verifiziert (FAIL CLOSED)" current_service_safety
echo " [pre] RECOVERY TARGET direkt verifizieren (IMAGE-ONLY, kein Container-Vergleich):"
verify_gold_target_image "$RECOVERY_GOLD_IMAGE_ID"
echo " [pre] historical-db healthy:" echo " [pre] historical-db healthy:"
ensure_db_run && echo " OK" ensure_db_run && echo " OK"
if [ "$RUN_MODE" = "DRY_RUN" ]; then if [ "$RUN_MODE" = "DRY_RUN" ]; then
@ -1372,11 +1469,10 @@ rollback_to_current_gold_anchor() {
echo " Ziel-Service : $HISTORICAL_SERVICE (NUR dieser; KEIN db, KEINE Volumes)" echo " Ziel-Service : $HISTORICAL_SERVICE (NUR dieser; KEIN db, KEINE Volumes)"
echo " Image : $GOLD_ANCHOR (fixed, KEIN latest, KEIN Tag erforderlich)" echo " Image : $GOLD_ANCHOR (fixed, KEIN latest, KEIN Tag erforderlich)"
echo " Preconditions:" echo " Preconditions:"
echo " [pre] Gold-Anker inspectable:" echo " [pre] Current Service Safety (running + health, NICHT Gold-geprueft):"
docker image inspect "$GOLD_ANCHOR" >/dev/null 2>&1 || die "pre FAIL: Gold-Anker nicht inspectable (FAIL CLOSED)" current_service_safety
echo " OK" echo " [pre] PRIMARY TARGET direkt verifizieren (IMAGE-ONLY, kein Container-Vergleich):"
echo " [pre] Gold-Anker 63/63 Gold-MATCH:" verify_gold_target_image "$GOLD_ANCHOR"
( verify_recovery_gold_image >/dev/null 2>&1 ) && echo " OK (Recovery-Gold 63/63; Gold-Anker separat verifiziert)" || die "pre FAIL: Recovery-Gold nicht verifiziert (FAIL CLOSED)"
echo " [pre] Recovery READY:" echo " [pre] Recovery READY:"
( verify_ready recovery >/dev/null 2>&1 ) || die "pre FAIL: Recovery-READY nicht PASS (FAIL CLOSED)" ( verify_ready recovery >/dev/null 2>&1 ) || die "pre FAIL: Recovery-READY nicht PASS (FAIL CLOSED)"
echo " OK" echo " OK"