From 619486810091dfded9768942c66ed631c53a6fff Mon Sep 17 00:00:00 2001 From: Rain Ocampo Date: Sat, 29 Aug 2026 23:01:57 +0000 Subject: [PATCH] Phase 13.5: Remediation Build/Deploy Control Plane - build-runner-remediation, verify-runner-remediation-image, recreate-runner-remediation-safe, verify-remediation-build-input; Full/Partial SoT-Backup-Klassifikation (Blocker A) --- .../control-plane/cp2a2_2b/rq-historical | 324 ++++++++++++++++-- 1 file changed, 299 insertions(+), 25 deletions(-) diff --git a/red-queen-architecture/control-plane/cp2a2_2b/rq-historical b/red-queen-architecture/control-plane/cp2a2_2b/rq-historical index dc799d7..ed06c0a 100644 --- a/red-queen-architecture/control-plane/cp2a2_2b/rq-historical +++ b/red-queen-architecture/control-plane/cp2a2_2b/rq-historical @@ -169,6 +169,24 @@ EXPECTED_RUNNER_PATCH_SHA256="6cca3c55bb559d75be2f58e59c231003902f124a54ada25582 # Fester Backup-Root fuer die Promotion-Session (root-owned, nicht world-readable). PROMOTE_BACKUP_ROOT="/opt/rq-historical-sot-backups/pre-runner-remediation" +# ------------------------------------------------- REMEDIATION BUILD/DEPLOY-KANAL +# Phase 13.5 · Controlled Runner-Remediation Build/Deploy Channel. +# KEIN echter Build/Recreate durch Rain; nur Kanal. Red Queen fuehrt nach Freigabe aus. +# Fester Remediation-Tag (Punkt 3) — KEIN Overwrite, KEIN force-retag, KEIN latest. +REMEDIATION_TAG="historical-v2-historical-service:candidate-phase13-5-runner-remediation" +# Build-Session-Basis (root-owned; enthaelt nach Build session.meta mit FULL_IMAGE_ID). +REMEDIATION_SESSION_DIR="/opt/rq-historical-build-sessions/phase13-5-runner-remediation" +REMEDIATION_SESSION_META="$REMEDIATION_SESSION_DIR/session.meta" +# Fester Build-Context/Dockerfile (Punkt 8,10) — alles hardcoded, KEIN User-Input. +REMEDIATION_BUILD_CONTEXT="/opt/historical-v2" +REMEDIATION_BUILD_DOCKERFILE="/opt/historical-v2/Dockerfile" +# Expected Runner-SHAs (Punkt 10/11): ALT = Gold (vor Remediation), NEU = persistent (nach). +RUNNER_GOLD_SHA="70d3abb1a92a871e28cc6fb95b9a26944b55dd8df1b72db80642d6881b7b0f20" +RUNNER_REMEDIATION_SHA="6cca3c55bb559d75be2f58e59c231003902f124a54ada25582970524599f0752" +# Recovery-Gold (immutable Baseline) — Referenz fuer Expected-Gold-Delta. +# Candidate/Recovery duerfen code-identisch sein, aber verschiedene Image-IDs haben (KEIN Fehler). +REM_NOOP_IMAGE_ID="sha256:1f3ba210780c5a00d4b73c5ee7817a88671fee2607a4ae0d5b1cefef1a1261c7" + # RUN_MODE (Mutation Guard): READ_ONLY | DRY_RUN | MUTATING RUN_MODE=MUTATING @@ -188,12 +206,44 @@ expect_zero() { [ $# -eq 0 ] || die "unerlaubtes Argument '$*'"; } # MODE ist global: MUTATING (Default) | DRY_RUN (keine Mutationen) mode_or_die() { [ "$RUN_MODE" != "DRY_RUN" ] || die "DRY_RUN-Modus, aber mutierender Pfad erreicht (FAIL CLOSED)"; } -sot_bestandscheck() { # gültiges vollständiges backup-sot vorhanden? - local latest bdir - latest="$(ls -1t "$SOT_BACKUP_ROOT" 2>/dev/null | head -1)" - [ -n "$latest" ] || die "kein backup-sot vorhanden (FAIL CLOSED)" - bdir="$SOT_BACKUP_ROOT/$latest" - [ -f "$bdir/historical-v2.tar" ] && [ -s "$bdir/SHA256.manifest" ] || die "backup-sot unvollständig (FAIL CLOSED)" +# ---- Full/Partial-SoT-Backup-Klassifikation (Phase 13.5 Remediation, Blocker A) ---- +# Ein Partial-Backup (z.B. pre-runner-remediation mit nur session.meta + +# run_backfill_year.py) darf NIEMALS als Full-SoT-Snapshot gelten. +# Ein gueltiger FULL SOT SNAPSHOT muss zwingend haben: +# historical-v2.tar (regular, kein Symlink, nicht leer) +# SHA256.manifest (regular, kein Symlink, nicht leer) +# und optional session.meta / provenance. Read-only. +is_full_sot_backup() { # $1 = Backup-Verzeichnis -> 0 wenn gueltiger Full-Snapshot + local bdir="$1" tt mt + [ -d "$bdir" ] || return 1 + [ -f "$bdir/historical-v2.tar" ] || return 1 + [ -s "$bdir/historical-v2.tar" ] || return 1 + [ -f "$bdir/SHA256.manifest" ] || return 1 + [ -s "$bdir/SHA256.manifest" ] || return 1 + [ -L "$bdir/historical-v2.tar" ] && return 1 + [ -L "$bdir/SHA256.manifest" ] && return 1 + tt=$(stat -c '%F' "$bdir/historical-v2.tar" 2>/dev/null) + mt=$(stat -c '%F' "$bdir/SHA256.manifest" 2>/dev/null) + [ "$tt" = "regular file" ] || return 1 + [ "$mt" = "regular file" ] || return 1 + return 0 +} +# Neuester VALIDIERTER Full-Snapshot (liest KEIN pre-runner-remediation-partial). +full_sot_backup() { + local d latest="" + for d in "$SOT_BACKUP_ROOT"/*/; do + [ -d "$d" ] || continue + if is_full_sot_backup "${d%/}"; then + # alphabetisch = chronologisch (Namen tragen yyyymmdd_hhmmss-Prefix -> neuester zuletzt) + latest="${d%/}" + fi + done + [ -n "$latest" ] || return 1 + echo "$latest" +} +sot_bestandscheck() { # gueltiger vollstaendiger (FULL) backing-sot vorhanden? + local bdir + bdir="$(full_sot_backup)" || die "kein gueltiger FULL SOT BACKUP vorhanden (FAIL CLOSED)" echo "$bdir" } verify_recovery_ok() { # 13/13 MATCH? @@ -612,13 +662,14 @@ verify_recovery_gold_image() { # Image des historical-service explizit auf einen festen Tag und deaktiviert build. # --no-deps verhindert DB-Recreate, --no-build verhindert Build. write_recreate_override() { - # $1 = fester Image-Tag (CANDIDATE_TAG oder RECOVERY_GOLD_TAG_FINAL) + # $1 = fester Image-Tag (CANDIDATE_TAG oder RECOVERY_GOLD_TAG_FINAL oder REMEDIATION_TAG) local tag="$1" ovf ovf="/tmp/rq-recreate-override-$$.yml" cat > "$ovf" </dev/null || die "Guard E FAIL: kein gueltiger FULL SOT BACKUP (FAIL CLOSED)" + echo " OK ($(full_sot_backup))" + echo " [guard F] Recovery-Gold inspectable:" + docker image inspect "$RECOVERY_GOLD_IMAGE_ID" >/dev/null 2>&1 || die "Guard F FAIL: Recovery-Image nicht inspectable (FAIL CLOSED)" + echo " OK" + echo " [guard G] Recovery-Tag -> Recovery-ID:" + local rt; rt="$(docker image inspect -f '{{.Id}}' "$RECOVERY_GOLD_TAG_FINAL" 2>/dev/null || true)" + [ "$rt" = "$RECOVERY_GOLD_IMAGE_ID" ] || die "Guard G FAIL: Recovery-Tag != Recovery-ID (FAIL CLOSED)" + echo " OK" + echo " [guard H] verify-recovery-gold-image PASS:" + ( verify_recovery_gold_image >/dev/null 2>&1 ) || die "Guard H FAIL: Recovery-Gold nicht verifiziert (FAIL CLOSED)" + echo " OK" + echo " [guard I] No-op-Candidate unveraendert:" + local ni; ni="$(docker image inspect -f '{{.Id}}' "$CANDIDATE_TAG" 2>/dev/null || true)" + [ "$ni" = "$CANDIDATE_IMAGE_ID" ] && [ "$CANDIDATE_IMAGE_ID" = "$REM_NOOP_IMAGE_ID" ] || die "Guard I FAIL: No-op-Candidate veraendert (FAIL CLOSED)" + echo " OK ($ni)" + echo " [guard J] Remediation-Tag existiert NOCH NICHT (KEIN Overwrite):" + if docker image inspect "$REMEDIATION_TAG" >/dev/null 2>&1; then + die "Guard J FAIL: Remediation-Tag existiert bereits — KEIN Ueberschreiben, kein force-retag (FAIL CLOSED)" + fi + echo " OK (Tag frei)" + echo " [guard K] historical-db healthy:" + ensure_db_run || die "Guard K FAIL: historical-db nicht Running (FAIL CLOSED)" + echo " OK" + echo " [guard L] kein Runner/Backfill laeuft:" + docker ps --format '{{.Names}}' | grep -qiE 'runner|backfill' && die "Guard L FAIL: Runner/Backfill laeuft (FAIL CLOSED)" || echo " OK" + echo " [guard M] Production Safety geschlossen (isolierter POC-Stack):" + echo " OK (eigene db, eigenes Netz, GET-only API, kein Produktivnetz)" + echo " [guard N] Dockerfile/Context exakt erwartet:" + dockerfile_expected && echo " OK" + 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" + 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)) + 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 + else + echo " DIVERGED(UNERWARTET) $goldrel"; d=$((d+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)) ;; + esac + fi + done < "$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)" + return 0 +} +# ---- build-runner-remediation (DRY_RUN = kein Build; KEIN Overwrite) ---- +build_runner_remediation() { + expect_zero "$@" + echo "=== BUILD-RUNNER-REMEDIATION ($RUN_MODE) ===" + echo " Build Context : $REMEDIATION_BUILD_CONTEXT (fix)" + echo " Dockerfile : $REMEDIATION_BUILD_DOCKERFILE (fix)" + echo " Ziel-Tag : $REMEDIATION_TAG (fix, KEIN latest, KEIN Overwrite)" + echo " Guards:" + remediation_guards + echo " [build-input] REMEDIATION_BUILD_INPUT (Build-Context-Manifest):" + build_context_manifest | while IFS= read -r line; do echo " $line"; done + if [ "$RUN_MODE" = "DRY_RUN" ]; then + echo "DRY_RUN_PASS: build-runner-remediation wuerde docker build -f $REMEDIATION_BUILD_DOCKERFILE -t $REMEDIATION_TAG $REMEDIATION_BUILD_CONTEXT (keine Aktion)." + exit 0 + fi + mode_or_die + echo " [build] docker build -f $REMEDIATION_BUILD_DOCKERFILE -t $REMEDIATION_TAG $REMEDIATION_BUILD_CONTEXT" + docker build -f "$REMEDIATION_BUILD_DOCKERFILE" -t "$REMEDIATION_TAG" "$REMEDIATION_BUILD_CONTEXT" || die "build-runner-remediation fehlgeschlagen (FAIL CLOSED)" + local newid now + newid="$(docker image inspect -f '{{.Id}}' "$REMEDIATION_TAG" 2>/dev/null)" + [ -n "$newid" ] || die "build: Image-ID nicht ermittelbar (FAIL CLOSED)" + echo " FULL_IMAGE_ID=$newid" + now="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + # Build-Session root-owned anlegen (einmaliges Schreiben der Image-ID) + mkdir -p "$REMEDIATION_SESSION_DIR" || die "build: Session-Dir nicht anlegbar (FAIL CLOSED)" + cat > "$REMEDIATION_SESSION_META" </dev/null 2>&1 || die "verify: Image-ID nicht inspectable (FAIL CLOSED)" + echo " [Binding] Remediation-Tag -> FULL Image-ID:" + local rt; rt="$(docker image inspect -f '{{.Id}}' "$REMEDIATION_TAG" 2>/dev/null || true)" + [ "$rt" = "$imgid" ] || die "verify: Remediation-Tag ($rt) != gespeicherte Image-ID ($imgid) (FAIL CLOSED)" + echo " OK" + echo " [Host-SoT App-Manifest vs Image] (Candidate == Host-SoT):" + local tmpc tmpdir tmpf + tmpc="rq-rem-verify-$(date +%s)"; tmpdir="/tmp/rq-rem-copy-$$" + rm -rf "$tmpdir"; mkdir -p "$tmpdir" + docker create --name "$tmpc" --entrypoint /bin/true "$imgid" >/dev/null 2>&1 || die "verify: 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 "verify: /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"; 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)" + echo " [Critical-Files] $REMEDIATION_TAG vs gold:" + verify_critical_files "$tmpdir/appapp" + 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="" + local hr; hr="$(sha256sum /opt/historical-v2/app/run_backfill_year.py | awk '{print $1}')" + [ "$ir" = "$hr" ] && [ "$ir" = "$RUNNER_REMEDIATION_SHA" ] || die "verify: Runner-Image-SHA ($ir) != Host-SoT ($hr) (FAIL CLOSED)" + echo " Image=$ir Host=$hr (erwartet $RUNNER_REMEDIATION_SHA)" + echo " [Expected Gold Delta] GENAU EINE fachliche Divergenz = app/run_backfill_year.py:" + echo " ALT(Gold)=$RUNNER_GOLD_SHA NEU(Remediation)=$RUNNER_REMEDIATION_SHA" + echo " [Runtime Config] $REMEDIATION_TAG:" + local c_entry c_cmd c_wd c_user c_expose + c_entry="$(docker image inspect -f '{{json .Config.Entrypoint}}' "$imgid" 2>/dev/null)" + c_cmd="$(docker image inspect -f '{{json .Config.Cmd}}' "$imgid" 2>/dev/null)" + c_wd="$(docker image inspect -f '{{.Config.WorkingDir}}' "$imgid" 2>/dev/null)" + c_user="$(docker image inspect -f '{{.Config.User}}' "$imgid" 2>/dev/null)" + c_expose="$(docker image inspect -f '{{json .Config.ExposedPorts}}' "$imgid" 2>/dev/null)" + printf ' Entrypoint=%s Cmd=%s WorkDir=%s User=%s Expose=%s\n' "${c_entry:-null}" "${c_cmd:-null}" "${c_wd:-}" "${c_user:-}" "${c_expose:-}" + [ "$c_wd" = "/app" ] || die "verify: WorkingDir unerwartet (FAIL CLOSED)" + echo " [Build-Input] Session-Host-Runner == persistent Host-Runner:" + 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)" + exit 0 +} +# ---- recreate-runner-remediation-safe (NUR historical-service; nur Red Queen) ---- +recreate_runner_remediation_safe() { + expect_zero "$@" + echo "=== RECREATE-RUNNER-REMEDIATION-SAFE ($RUN_MODE) ===" + echo " Ziel-Service : $HISTORICAL_SERVICE (NUR dieser; KEIN db, KEIN Compose-Down, KEINE Volumes)" + remediation_session || die "recreate: keine gueltige Build-Session (zuerst build-runner-remediation) (FAIL CLOSED)" + local imgid; imgid="$(remediation_full_image_id)" + echo " Image : $REMEDIATION_TAG -> $imgid (explizit, KEIN latest)" + echo " Pre-Gates:" + recreate_pre_gates + echo " [gate N] Remediation-Tag -> FULL Image-ID:" + local rt; rt="$(docker image inspect -f '{{.Id}}' "$REMEDIATION_TAG" 2>/dev/null || true)" + [ "$rt" = "$imgid" ] || die "Gate N FAIL: Remediation-Tag != FULL Image-ID (FAIL CLOSED)" + echo " OK" + echo " [gate O] verify-runner-remediation-image PASS:" + ( verify_runner_remediation_image >/dev/null 2>&1 ) || die "Gate O FAIL: Remediation-Image nicht verifiziert (FAIL CLOSED)" + echo " OK" + echo " Baseline vor Recreate:" + recreate_baseline + if [ "$RUN_MODE" = "DRY_RUN" ]; then + echo "DRY_RUN_PASS: wuerde docker compose -f $COMPOSE_FILE -f up -d --no-deps --no-build $HISTORICAL_SERVICE (Image=$REMEDIATION_TAG, gespeicherte ID=$imgid). KEINE Aktion." + exit 0 + fi + mode_or_die + local ovf; ovf="$(write_recreate_override "$REMEDIATION_TAG")" + docker compose -f "$COMPOSE_FILE" -f "$ovf" up -d --no-deps --no-build "$HISTORICAL_SERVICE" || { rm -f "$ovf"; die "recreate-runner-remediation-safe fehlgeschlagen (FAIL CLOSED)"; } + rm -f "$ovf" + echo " Post-Gates:" + recreate_post_gates "$imgid" + echo "RECREATE-RUNNER-REMEDIATION-SAFE OK. Container laeuft mit Remediation-Image (gespeicherte ID)." +} + # ---- build-historical (NUR Build, kein recreate; DRY_RUN = kein Bau) ---- build_historical() { expect_zero "$@" @@ -1100,8 +1375,8 @@ if [ "$SUBCMD" = "dry-run" ]; then SUBCMD="${1:-}" shift || true case "$SUBCMD" in - build-historical|recreate-historical|snapshot-recovery|build-recovery-gold|pin-current-candidate|recreate-candidate-safe|rollback-to-recovery-gold|promote-runner-patch|rollback-runner-patch) ;; - *) die "dry-run nur mit build-historical|recreate-historical|snapshot-recovery|build-recovery-gold zulaessig" ;; + build-historical|recreate-historical|snapshot-recovery|build-recovery-gold|pin-current-candidate|recreate-candidate-safe|rollback-to-recovery-gold|promote-runner-patch|rollback-runner-patch|build-runner-remediation|recreate-runner-remediation-safe) ;; + *) die "dry-run nur mit build-historical|recreate-historical|snapshot-recovery|build-recovery-gold|build-runner-remediation|recreate-runner-remediation-safe zulaessig" ;; esac fi @@ -1374,22 +1649,19 @@ case "$SUBCMD" in echo " session: $ts" ;; sot-session) expect_zero "$@" - # Zeigt die aktuelle (neueste) Recovery-Session-Backup-ID - latest="$(ls -1t "$SOT_BACKUP_ROOT" 2>/dev/null | head -1)" - [ -n "$latest" ] || die "kein backup-sot vorhanden" - echo "SESSION: $latest" - echo "PFAD: $SOT_BACKUP_ROOT/$latest" + # Zeigt den neuesten VALIDIERTEN FULL SOT SNAPSHOT (Partial-Backups wie + # pre-runner-remediation werden NICHT als Full-Snapshot gezaehlt; Blocker A). + bdir="$(full_sot_backup)" || die "kein gueltiger FULL SOT BACKUP vorhanden (FAIL CLOSED)" + echo "SESSION: $(basename "$bdir")" + echo "PFAD: $bdir" ;; recover-sot) req_ok; [ $# -eq 1 ] || die "recover-sot braucht genau EINEN Key (recover-sot-list)" key="$1" spec="${RECOVERY[$key]:-}" [ -n "$spec" ] || die "unbekannter Recovery-Key '$key' (recover-sot-list)" IFS='|' read -r csrc htarget gold <<< "$spec" - # 1. gültiges vollständiges backup-sot vorhanden? - latest="$(ls -1t "$SOT_BACKUP_ROOT" 2>/dev/null | head -1)" - [ -n "$latest" ] || die "recover-sot: kein backup-sot vorhanden (FAIL CLOSED)" - bdir="$SOT_BACKUP_ROOT/$latest" - [ -f "$bdir/historical-v2.tar" ] && [ -s "$bdir/SHA256.manifest" ] || die "recover-sot: Backup unvollständig (FAIL CLOSED)" + # 1. gueltiger vollstaendiger (FULL) backup-sot vorhanden? (Partial ausgeschlossen) + bdir="$(full_sot_backup)" || die "recover-sot: kein gueltiger FULL SOT BACKUP vorhanden (FAIL CLOSED)" # 2. Source = historical-service fixed (req_ok bereits geprueft) # 3/4. Source regular file, kein Symlink if ! docker exec "$CONTAINER" sh -c "[ -f '$csrc' ]" 2>/dev/null; then die "recover-sot: Source kein File im Container: $csrc"; fi @@ -1452,10 +1724,8 @@ case "$SUBCMD" in [ "$allok" = "1" ] && echo "VERIFY-SOT-RECOVERY: ALLE MATCH" || { echo "VERIFY-SOT-RECOVERY: FEHLER"; exit 2; } ;; restore-sot) expect_zero "$@" - # Restore des eindeutig markierten aktuellen Recovery-Session-Backups - latest="$(ls -1t "$SOT_BACKUP_ROOT" 2>/dev/null | head -1)" - [ -n "$latest" ] || die "restore-sot: kein backup-sot vorhanden" - bdir="$SOT_BACKUP_ROOT/$latest" + # Restore aus dem neuesten validerungspflichtigen Session-Backup (FULL) + bdir="$(full_sot_backup)" || die "restore-sot: kein gueltiger FULL SOT BACKUP vorhanden (FAIL CLOSED)" [ -f "$bdir/$SOT_SESSION_MARKER" ] || die "restore-sot: kein markiertes Session-Backup (FAIL CLOSED)" [ -f "$bdir/historical-v2.tar" ] || die "restore-sot: tar fehlt (FAIL CLOSED)" echo "RESTORE-SOT: $bdir" @@ -1475,6 +1745,10 @@ case "$SUBCMD" in rollback-to-recovery-gold) rollback_to_recovery_gold "$@" ;; promote-runner-patch) promote_runner_patch "$@" ;; rollback-runner-patch) rollback_runner_patch "$@" ;; + build-runner-remediation) build_runner_remediation "$@" ;; + verify-runner-remediation-image) verify_runner_remediation_image "$@" ;; + recreate-runner-remediation-safe) recreate_runner_remediation_safe "$@" ;; + verify-remediation-build-input) expect_zero "$@"; verify_remediation_build_input ;; build-historical) build_historical "$@" ;; create-build-exclusion) expect_zero "$@" # Build-Pollution-Exclusion implementieren (Punkt 5-8): .dockerignore anlegen/ersetzen @@ -1502,6 +1776,6 @@ case "$SUBCMD" in recreate-historical) recreate_historical "$@" ;; verify-gold-reproduction) verify_gold_reproduction "$@" ;; - *) die "Aufruf: ps|inspect|logs|run|code |hashes|code-list|stage-dir|stage-check |test |search-writes|healthcheck|backup |deploy |rollback |build-def|build-files|host-manifest|host-code

|container-manifest|container-code

|tree-diff|backup-sot|sot-session|recover-sot |recover-sot-list|verify-sot-recovery|restore-sot|snapshot-recovery|dry-run snapshot-recovery|verify-recovery-snapshot|build-recovery-gold|dry-run build-recovery-gold|verify-recovery-gold-image|pin-current-candidate|dry-run pin-current-candidate|verify-candidate-image|recreate-candidate-safe|dry-run recreate-candidate-safe|rollback-to-recovery-gold|dry-run rollback-to-recovery-gold|promote-runner-patch|dry-run promote-runner-patch|rollback-runner-patch|dry-run rollback-runner-patch|build-historical|dry-run build-historical|recreate-historical|dry-run recreate-historical|verify-gold-reproduction|create-build-exclusion|show-build-exclusion" + *) die "Aufruf: ps|inspect|logs|run|code |hashes|code-list|stage-dir|stage-check |test |search-writes|healthcheck|backup |deploy |rollback |build-def|build-files|host-manifest|host-code

|container-manifest|container-code

|tree-diff|backup-sot|sot-session|recover-sot |recover-sot-list|verify-sot-recovery|restore-sot|snapshot-recovery|dry-run snapshot-recovery|verify-recovery-snapshot|build-recovery-gold|dry-run build-recovery-gold|verify-recovery-gold-image|pin-current-candidate|dry-run pin-current-candidate|verify-candidate-image|recreate-candidate-safe|dry-run recreate-candidate-safe|rollback-to-recovery-gold|dry-run rollback-to-recovery-gold|promote-runner-patch|dry-run promote-runner-patch|rollback-runner-patch|dry-run rollback-runner-patch|build-runner-remediation|dry-run build-runner-remediation|verify-runner-remediation-image|recreate-runner-remediation-safe|dry-run recreate-runner-remediation-safe|verify-remediation-build-input|build-historical|dry-run build-historical|recreate-historical|dry-run recreate-historical|verify-gold-reproduction|create-build-exclusion|show-build-exclusion" ;; esac