Phase 13.5: Deploy verified partial-archive control-plane patch (cleanup-partial-remediation-archive + active remediation image state validation)

This commit is contained in:
root 2026-08-30 20:39:13 +00:00
parent 3e65c36cce
commit ce58a067ee

View file

@ -276,7 +276,9 @@ declare -A ARCHIVE_SUB=( [recovery]="recovery" [remediation]="candidates" )
# aus Build-Session (41e357a1..., aktuell im Store fehlend). # aus Build-Session (41e357a1..., aktuell im Store fehlend).
RECOVERY_ARCHIVE_ID="" # zur Laufzeit aus State gesetzt (load_recovery_state) RECOVERY_ARCHIVE_ID="" # zur Laufzeit aus State gesetzt (load_recovery_state)
# Remediation-Archiv-Session-ID: kurze ID der Build-Session-Remendation (41e357a1...). # Remediation-Archiv-Session-ID: kurze ID der Build-Session-Remendation (41e357a1...).
REMEDIATION_ARCHIVE_ID="41e357a12b90" # (RAIN Hotfix) Statische REMEDIATION_ARCHIVE_ID ENTFERNT — historische 41e357a12b90
# darf keine aktive Archive-Identity sein. Aktive ID ausschliesslich aus der aktuellen,
# inspectable Build-Session ableiten (remediation_session / remediation_full_image_id).
# Remediation-Archiv-Referenz (aus Build-Session falls vorhanden, sonst leer): # Remediation-Archiv-Referenz (aus Build-Session falls vorhanden, sonst leer):
# wird zur Laufzeit per remediation_full_image_id() aufgeloest. # wird zur Laufzeit per remediation_full_image_id() aufgeloest.
# Pin-Referenz-Ziel (Schicht A): exakt diese Image-ID muss der Pin-Container referenzieren. # Pin-Referenz-Ziel (Schicht A): exakt diese Image-ID muss der Pin-Container referenzieren.
@ -1453,11 +1455,18 @@ rollback_runner_patch() {
# KEIN echter Build/Recreate durch Rain. Red Queen fuehrt nach Freigabe aus. # KEIN echter Build/Recreate durch Rain. Red Queen fuehrt nach Freigabe aus.
# Helfer: Read-only-Session-Bestand ("Build-Session vorhanden?") — muss zusaetzlich # Helfer: Read-only-Session-Bestand ("Build-Session vorhanden?") — muss zusaetzlich
# FULL_IMAGE_ID tragen, da Verify/Recreate nur der gespeicherten Image-ID vertrauen. # FULL_IMAGE_ID tragen, da Verify/Recreate nur der gespeicherten Image-ID vertrauen.
remediation_session() { # 0 wenn giltige Build-Session mit FULL_IMAGE_ID vorhanden remediation_session() { # 0 NUR wenn AKTUELL erfolgreicher Build-State mit inspectable Image existiert
# (RAIN Hotfix §9/§10) Eine historische/stale FULL_IMAGE_ID darf KEINE aktive Identity
# sein. Nur wenn das referenzierte Image im Docker-Store tatsaechlich inspectable ist,
# ist die Session "aktiv und aktuell". Sonst -> NO ACTIVE REMEDIATION IMAGE STATE.
[ -f "$REMEDIATION_SESSION_META" ] || return 1 [ -f "$REMEDIATION_SESSION_META" ] || return 1
grep -q '^FULL_IMAGE_ID=sha256:[a-f0-9]\{64\}$' "$REMEDIATION_SESSION_META" || return 1 grep -q '^FULL_IMAGE_ID=sha256:[a-f0-9]\{64\}$' "$REMEDIATION_SESSION_META" || return 1
[ "$(stat -c '%F' "$REMEDIATION_SESSION_META")" = "regular file" ] || return 1 [ "$(stat -c '%F' "$REMEDIATION_SESSION_META")" = "regular file" ] || return 1
[ -L "$REMEDIATION_SESSION_META" ] && return 1 [ -L "$REMEDIATION_SESSION_META" ] && return 1
local rid
rid="$(grep '^FULL_IMAGE_ID=' "$REMEDIATION_SESSION_META" | cut -d= -f2)"
# NO ACTIVE fallback: historische ID ist nur Evidence. Image muss inspectable sein.
docker image inspect "$rid" >/dev/null 2>&1 || return 1
return 0 return 0
} }
remediation_full_image_id() { # FULL_IMAGE_ID aus Session (echo) — nur wenn rem_remediation_session OK remediation_full_image_id() { # FULL_IMAGE_ID aus Session (echo) — nur wenn rem_remediation_session OK
@ -2390,12 +2399,25 @@ cleanup_partial_remediation_staging() {
die "cleanup: Remediation Image existiert (FAIL CLOSED)" die "cleanup: Remediation Image existiert (FAIL CLOSED)"
fi fi
echo " B) Kein Remediation Image" echo " B) Kein Remediation Image"
# C) Kein Remediation Archive # C) VALID-Archive-Semantik (RAIN Hotfix §4): NICHT nackte Directory-Existence.
local arch_dir="$ARCHIVE_ROOT/candidates" # VALID -> strikt DENY (gueltiges Archive blockiert Staging-Cleanup).
if [ -d "$arch_dir" ] && [ -n "$(ls -A "$arch_dir" 2>/dev/null)" ]; then # PARTIAL -> erst sanktionierten cleanup-partial-remediation-archive ausfuehren,
die "cleanup: Remediation Archive existiert (FAIL CLOSED)" # dann Retry (Lifecycle-getrennt, bevorzugte Variante).
fi # NO -> kein Blocker, Staging-Cleanup faehrt fort.
echo " C) Kein Remediation Archive" # INVALID/UNKNOWN -> FAIL CLOSED.
local cls
cls="$(remediation_archive_classification)"
case "$cls" in
VALID) die "cleanup: VALID Remediation Archive existiert — Cleanup strikt DENY (FAIL CLOSED)" ;;
PARTIAL)
echo " C) PARTIAL_ARCHIVE_PRESENT=YES"
echo " PARTIAL_ARCHIVE_CLASSIFICATION=SAFE_ORPHAN"
die "cleanup: PARTIAL Remediation Archive vorhanden — zuerst cleanup-partial-remediation-archive, dann Retry (Lifecycle-getrennt)" ;;
INVALID) die "cleanup: Archive-Zustand INVALID — FAIL CLOSED, kein automatischer Umgang" ;;
UNKNOWN) die "cleanup: Archive-Zustand UNKNOWN — FAIL CLOSED, kein automatischer Umgang" ;;
NO) echo " C) Remediation Archive = NO (kein VALID-Archive-Blocker)" ;;
*) die "cleanup: ungueltige Archive-Klassifikation (FAIL CLOSED)" ;;
esac
# D) Kein Remediation Pin # D) Kein Remediation Pin
if docker ps -a --format '{{.Names}}' | grep -q "rq-historical-pin-phase13-5-runner-remediation"; then if docker ps -a --format '{{.Names}}' | grep -q "rq-historical-pin-phase13-5-runner-remediation"; then
die "cleanup: Remediation Pin existiert (FAIL CLOSED)" die "cleanup: Remediation Pin existiert (FAIL CLOSED)"
@ -2457,6 +2479,147 @@ cleanup_partial_remediation_staging() {
echo " Retry bereit: build-runner-remediation wird neues Staging materialisieren." echo " Retry bereit: build-runner-remediation wird neues Staging materialisieren."
} }
# =============================================================================
# cleanup-partial-remediation-archive (Phase 13.5 · RAIN Hotfix)
# Kontrollierter Cleanup eines NACHWEISLICH unvollstaendigen / verwaisten
# Remediation-Archive-Ordners. KEINE freie Pfadangabe, KEINE freie Session.
# Zielidentitaet vollstaendig aus kontrolliertem State / Build-Session abgeleitet.
# Sicher entfernbar NUR klar klassifizierbare PARTIAL-Orphans. Valid/Invalid/
# Unknown/aktive Zustände -> FAIL CLOSED.
# =============================================================================
# --- Archive-Zustands-Klassifikation (Mission §2) ---
# A) NO_ARCHIVE : kein Archive-Ziel vorhanden.
# B) PARTIAL_ARCHIVE : Verzeichnis vorhanden, aber mind. ein Pflichtartefakt fehlt
# oder ist unvollstaendig (z.B. image.tar fehlt/leer).
# C) VALID_ARCHIVE : alle Pflichtartefakte da UND konsistent gebunden UND verify PASS.
# D) INVALID_ARCHIVE : vollstaendig erscheinend, aber SHA/Metadata/Binding falsch.
# E) UNKNOWN_ARCHIVE_STATE : nicht sicher klassifizierbar.
# Fail-closed: VALID blockiert Cleanup; PARTIAL separat bereinigbar; INVALID/UNKNOWN FAIL.
remediation_archive_classification() { # echo: NO|PARTIAL|VALID|INVALID|UNKNOWN
local sdir base img meta manifest runtime
sdir="$(role_arch_dir remediation)" # = $ARCHIVE_ROOT/candidates
local sid
if remediation_session; then
local ridsess; ridsess="$(remediation_full_image_id)"
sid="${ridsess#sha256:}"; sid="${sid:0:12}"
else
sid=""
fi
echo " REMEDIATION_ARCHIVE_BASE=$sdir" >&2
[ -d "$sdir" ] || { echo "NO"; return; }
if [ -n "$sid" ]; then
base="$sdir/$sid"
else
local found="" cnt=0
for d in "$sdir"/*/; do
[ -d "$d" ] || continue
cnt=$((cnt+1)); found="${d%/}"; found="${found##*/}"
done
if [ "$cnt" = "0" ]; then echo "NO"; return; fi
base="$sdir/$found"
fi
[ -d "$base" ] || { echo "NO"; return; }
img="$base/image.tar"; meta="$base/session.meta"; manifest="$base/app.manifest"; runtime="$base/runtime-config.txt"
# --- VALID-Pruefung (Mission §3) ---
local ok=1
[ -f "$img" ] && [ ! -L "$img" ] && [ -s "$img" ] || ok=0
[ -f "$meta" ] && [ ! -L "$meta" ] || ok=0
[ -f "$manifest" ] && [ ! -L "$manifest" ] || ok=0
[ -f "$runtime" ] && [ ! -L "$runtime" ] || ok=0
if [ "$ok" = "1" ]; then
local want got
want="$(awk -F'=' '$1=="ARCHIVE_SHA256"{print $2}' "$meta" 2>/dev/null)"
got="$(sha256sum "$img" 2>/dev/null | awk '{print $1}')"
if [ -n "$want" ] && [[ "$want" =~ ^[a-f0-9]{64}$ ]] && [ "$got" = "$want" ]; then
local role
role=remediation
if ( verify_archive remediation ) >/dev/null 2>&1; then echo "VALID"; return; fi
echo "INVALID"; return
fi
echo "INVALID"; return
fi
# --- PARTIAL vs UNKNOWN (RAIN Hotfix: fail-closed-Semantik) ---
# PARTIAL nur wenn klar unvollstaendig/orphan: image.tar fehlt oder ist leer.
# (NICHT bei blosser Anomalie — Symlink/widerspruechliche Metadata ist UNKNOWN.)
if [ ! -f "$img" ] || [ ! -s "$img" ]; then
echo "PARTIAL"; return
fi
# Sonst: image.tar vorhanden+valide, aber Metadata/Manifest/Runtime unvollstaendig
# oder widerspruechlich -> nicht sicher klassifizierbar -> UNKNOWN (fail closed).
echo "UNKNOWN"; return
}
# cleanup-partial-remediation-archive — dry-run + real (Mission §5/§6/§7)
cleanup_partial_remediation_archive() {
expect_zero "$@"
echo "=== CLEANUP-PARTIAL-REMEDIATION-ARCHIVE ($RUN_MODE) ==="
local sdir sid base
sdir="$(role_arch_dir remediation)" # = $ARCHIVE_ROOT/candidates
if remediation_session; then
local ridsess; ridsess="$(remediation_full_image_id)"
sid="${ridsess#sha256:}"; sid="${sid:0:12}"
else
sid=""
fi
if [ -n "$sid" ]; then
base="$sdir/$sid"
else
local found="" cnt=0
for d in "$sdir"/*/; do
[ -d "$d" ] || continue
cnt=$((cnt+1)); found="${d%/}"; found="${found##*/}"
done
if [ "$cnt" = "0" ]; then die "cleanup-partial-remediation-archive: kein Archive-Ordner (FAIL CLOSED)"; fi
if [ "$cnt" != "1" ]; then die "cleanup-partial-remediation-archive: mehrere Candidate-Ordner — Ziel nicht eindeutig (FAIL CLOSED)"; fi
sid="$found"; base="$sdir/$sid"
fi
echo " TARGET=$base"
# --- SAFETY GATES (Mission §6) ---
[ "$sdir" = "$ARCHIVE_ROOT/candidates" ] || die "cleanup-partial: Root != $ARCHIVE_ROOT/candidates (FAIL CLOSED)"
[[ "$sid" =~ ^[a-f0-9]{12}$ ]] || die "cleanup-partial: Ordnername ungueltig '$sid' (FAIL CLOSED)"
[ -e "$base" ] || { echo " PARTIAL_ARCHIVE=NO (kein Ziel)"; echo "DRY_RUN_PASS: nichts zu entfernen."; exit 0; }
[ ! -L "$base" ] || die "cleanup-partial: Ziel ist Symlink (FAIL CLOSED)"
if mountpoint -q "$base" 2>/dev/null; then die "cleanup-partial: Ziel ist Mountpoint (FAIL CLOSED)"; fi
if mountpoint -q "$sdir" 2>/dev/null; then die "cleanup-partial: Parent ist Mountpoint (FAIL CLOSED)"; fi
if grep -qE " $base( |$)" /proc/mounts 2>/dev/null; then die "cleanup-partial: Ziel ist Bind/Volume Mount (FAIL CLOSED)"; fi
if ps aux 2>/dev/null | grep -E "docker save|docker build|docker tag" | grep -v grep | grep -q "$sdir"; then
die "cleanup-partial: Build-/Save-Prozess aktiv (FAIL CLOSED)"
fi
if docker ps --format '{{.Names}}' 2>/dev/null | grep -qE "build|remediation"; then
die "cleanup-partial: Build-Container aktiv (FAIL CLOSED)"
fi
# --- KLASSIFIKATION (Mission §2/§7) ---
local cls
cls="$(remediation_archive_classification)"
echo " CLASSIFICATION=$cls"
case "$cls" in
VALID) die "cleanup-partial: Archive ist VALID/verify PASS — Cleanup strikt DENY (FAIL CLOSED)" ;;
INVALID) die "cleanup-partial: Archive INVALID (voll, aber SHA/Metadata/Binding falsch) — FAIL CLOSED, kein Cleanup" ;;
UNKNOWN) die "cleanup-partial: Archive-Zustand UNKNOWN — FAIL CLOSED, kein Cleanup" ;;
PARTIAL) : ;;
NO) echo " PARTIAL_ARCHIVE=NO"; echo "DRY_RUN_PASS: kein partielles Archive."; exit 0 ;;
*) die "cleanup-partial: ungueltige Klassifikation (FAIL CLOSED)" ;;
esac
echo " PARTIAL_ARCHIVE=CONFIRMED"
echo " IMAGE_TAR=$( [ -f "$base/image.tar" ] && [ -s "$base/image.tar" ] && echo PRESENT || echo ABSENT )"
echo " ACTIVE_REMEDIATION_IMAGE=$( if remediation_session && docker image inspect "$(remediation_full_image_id)" >/dev/null 2>&1; then echo PRESENT; else echo NONE; fi )"
echo " ACTIVE_PIN=$( docker ps -a --format '{{.Names}}' 2>/dev/null | grep -Eq 'rq-historical-pin-phase13-5-runner-remediation' && echo PRESENT || echo NONE )"
echo " ACTIVE_CONTAINER=$( docker ps --format '{{.Names}}' 2>/dev/null | grep -q 'remediation' && echo PRESENT || echo NONE )"
echo " VALID_ARCHIVE=NO"
if [ "$RUN_MODE" = "DRY_RUN" ]; then
echo " ACTION=WOULD_REMOVE_PARTIAL_REMEDIATION_ARCHIVE"
echo "DRY_RUN_PASS: cleanup-partial-remediation-archive wuerde $base entfernen (keine Aktion)."
echo " ZERO MUTATION."
exit 0
fi
mode_or_die
rm -rf -- "$base" || die "cleanup-partial: Entfernen fehlgeschlagen (FAIL CLOSED)"
[ ! -e "$base" ] || die "cleanup-partial: Ziel nach Entfernen noch vorhanden (FAIL CLOSED)"
echo "CLEANUP-PARTIAL-REMEDIATION-ARCHIVE OK: $base entfernt"
echo " Retry bereit: cleanup-partial-remediation-staging ist nun logisch freigegeben."
}
# restore-recovery-image-archive (DESIGN; realer Restore NICHT in dieser Mission ausfuehren) # restore-recovery-image-archive (DESIGN; realer Restore NICHT in dieser Mission ausfuehren)
restore_recovery_dry() { # dry-run restore_recovery_dry() { # dry-run
expect_zero "$@" expect_zero "$@"
@ -2806,7 +2969,7 @@ if [ "$SUBCMD" = "dry-run" ]; then
SUBCMD="${1:-}" SUBCMD="${1:-}"
shift || true shift || true
case "$SUBCMD" in 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|build-runner-remediation|recreate-runner-remediation-safe|archive-recovery-image|archive-remediation-image|pin-recovery-image|pin-remediation-image|cleanup-partial-recovery-archive|cleanup-partial-remediation-staging|recover-gold-build-source) ;; 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|archive-recovery-image|archive-remediation-image|pin-recovery-image|pin-remediation-image|cleanup-partial-recovery-archive|cleanup-partial-remediation-staging|cleanup-partial-remediation-archive|recover-gold-build-source) ;;
*) die "dry-run nur mit build-historical|recreate-historical|snapshot-recovery|build-recovery-gold|build-runner-remediation|recreate-runner-remediation-safe|archive-recovery-image|archive-remediation-image|pin-recovery-image|pin-remediation-image|cleanup-partial-recovery-archive zulaessig" ;; *) die "dry-run nur mit build-historical|recreate-historical|snapshot-recovery|build-recovery-gold|build-runner-remediation|recreate-runner-remediation-safe|archive-recovery-image|archive-remediation-image|pin-recovery-image|pin-remediation-image|cleanup-partial-recovery-archive zulaessig" ;;
esac esac
fi fi
@ -3219,6 +3382,7 @@ case "$SUBCMD" in
verify-remediation-archive) verify_archive_cmd remediation "$@" ;; verify-remediation-archive) verify_archive_cmd remediation "$@" ;;
cleanup-partial-recovery-archive) cleanup_partial_recovery_archive "$@" ;; cleanup-partial-recovery-archive) cleanup_partial_recovery_archive "$@" ;;
cleanup-partial-remediation-staging) cleanup_partial_remediation_staging "$@" ;; cleanup-partial-remediation-staging) cleanup_partial_remediation_staging "$@" ;;
cleanup-partial-remediation-archive) cleanup_partial_remediation_archive "$@" ;;
verify-recovery-pin) verify_pin_cmd recovery "$@" ;; verify-recovery-pin) verify_pin_cmd recovery "$@" ;;
verify-remediation-pin) verify_pin_cmd remediation "$@" ;; verify-remediation-pin) verify_pin_cmd remediation "$@" ;;
verify-recovery-ready) verify_ready_cmd recovery "$@" ;; verify-recovery-ready) verify_ready_cmd recovery "$@" ;;