Phase 13.5 Recovery Archive Hotfix: Fix A (Reihenfolge-Bug nach docker image save) + cleanup-partial-recovery-archive Command

This commit is contained in:
root 2026-08-30 11:34:11 +00:00
parent a9d7da14f5
commit 9685b616cf

View file

@ -1957,11 +1957,14 @@ archive_image() { # $1 = role
asha="$(sha256sum "$base/image.tar" | awk '{print $1}')" asha="$(sha256sum "$base/image.tar" | awk '{print $1}')"
asize="$(stat -c '%s' "$base/image.tar")" asize="$(stat -c '%s' "$base/image.tar")"
# SHAs der Metadaten (zur Laufzeit, aus Build-Session / Host-SoT) # SHAs der Metadaten (zur Laufzeit, aus Build-Session / Host-SoT)
# HINWEIS: APP_MANIFEST_SHA_CUR / RUNTIME_CONFIG_SHA_CUR werden hier NICHT
# berechnet — app.manifest und runtime-config.txt existieren zu diesem Zeitpunkt
# noch nicht (werden erst unten erzeugt). sha256sum auf nicht-existente Datei
# wuerde unter set -euo pipefail mit EXIT=1 abbrechen (bewiesener Root Cause).
# Die korrekte Berechnung erfolgt NACH write_runtime_config/write_app_manifest.
HOST_SOT_RUNNER_SHA_CUR="$(sha256sum /opt/historical-v2/app/run_backfill_year.py 2>/dev/null | awk '{print $1}')" HOST_SOT_RUNNER_SHA_CUR="$(sha256sum /opt/historical-v2/app/run_backfill_year.py 2>/dev/null | awk '{print $1}')"
BUILD_INPUT_SHA_CUR="$(build_context_manifest 2>/dev/null | sha256sum | awk '{print $1}')" BUILD_INPUT_SHA_CUR="$(build_context_manifest 2>/dev/null | sha256sum | awk '{print $1}')"
WRAPPER_SHA_CUR="$(sha256sum /usr/local/bin/rq-historical | awk '{print $1}')" WRAPPER_SHA_CUR="$(sha256sum /usr/local/bin/rq-historical | awk '{print $1}')"
APP_MANIFEST_SHA_CUR="$(sha256sum "$base/app.manifest" 2>/dev/null | awk '{print $1}')"
RUNTIME_CONFIG_SHA_CUR="$(sha256sum "$base/runtime-config.txt" 2>/dev/null | awk '{print $1}')"
gen_archive_meta "$role" "$base" "$id" "$asize" "$asha" gen_archive_meta "$role" "$base" "$id" "$asize" "$asha"
write_runtime_config "$role" "$base" write_runtime_config "$role" "$base"
write_app_manifest "$role" "$base" "$id" write_app_manifest "$role" "$base" "$id"
@ -2040,6 +2043,81 @@ verify_archive_cmd() { # $1 = role (read-only)
verify_archive "$role" || die "VERIFY-ARCHIVE($role): FAIL (FAIL CLOSED)" verify_archive "$role" || die "VERIFY-ARCHIVE($role): FAIL (FAIL CLOSED)"
exit 0 exit 0
} }
# =============================================================================
# cleanup-partial-recovery-archive (Phase 13.5 · Recovery Archive Hotfix)
# Kontrollierter Cleanup eines NACHWEISLICH unvollstaendigen Recovery-Archive-
# Ordners. KEINE freie Pfadangabe. Nur der exakte Partial-Ordner fuer
# CURRENT_RECOVERY_FULL_IMAGE_ID. Verified Archive wird strikt DENY.
# Safety-Model (Punkt 9): Pfad exakt unter festem Root, Ordnername exakt aus
# CURRENT_RECOVERY_FULL_IMAGE_ID abgeleitet, kein freier Pfad, kein Symlink,
# kein Mountpoint, kein verified Archive, session.meta fehlt ODER INVALID/PARTIAL,
# CURRENT_RECOVERY_FULL_IMAGE_ID stimmt weiterhin, keine andere Recovery-ID.
# =============================================================================
cleanup_partial_recovery_archive() {
expect_zero "$@"
echo "=== CLEANUP-PARTIAL-RECOVERY-ARCHIVE ($RUN_MODE) ==="
# Aktive Recovery-Identitaet aus kontrolliertem State (Punkt 17). Kein d2db.
load_recovery_state
local sdir sid base
sdir="$(role_arch_dir recovery)" # = $ARCHIVE_ROOT/recovery
sid="$RECOVERY_ARCHIVE_ID" # exakt aus CURRENT_RECOVERY_FULL_IMAGE_ID abgeleitet
base="$sdir/$sid"
echo " TARGET=$base"
echo " CURRENT_RECOVERY_FULL_IMAGE_ID=$RECOVERY_GOLD_IMAGE_ID"
# --- Safety-Checks (alle FAIL CLOSED) ---
# 1. Root exakt unter festem Recovery Archive Root
[ "$sdir" = "$ARCHIVE_ROOT/recovery" ] || die "cleanup: Root nicht exakt $ARCHIVE_ROOT/recovery (FAIL CLOSED)"
# 2. Ordnername exakt 12 hex (aus FULL_IMAGE_ID abgeleitet, kein freier Pfad)
[[ "$sid" =~ ^[a-f0-9]{12}$ ]] || die "cleanup: Ordnername ungueltig '$sid' (FAIL CLOSED)"
# 3. Ziel existiert
[ -e "$base" ] || die "cleanup: Ziel existiert nicht $base (FAIL CLOSED)"
# 4. kein Symlink
[ ! -L "$base" ] || die "cleanup: Ziel ist Symlink (FAIL CLOSED)"
# 5. kein Mountpoint
if mountpoint -q "$base" 2>/dev/null; then die "cleanup: Ziel ist Mountpoint (FAIL CLOSED)"; fi
# 6. CURRENT_RECOVERY_FULL_IMAGE_ID stimmt weiterhin (Tag->ID Gate)
local tagid
tagid="$(docker image inspect -f '{{.Id}}' "$RECOVERY_GOLD_TAG_FINAL" 2>/dev/null || true)"
[ "$tagid" = "$RECOVERY_GOLD_IMAGE_ID" ] || die "cleanup: Tag->ID Gate FAIL ($tagid != $RECOVERY_GOLD_IMAGE_ID) (FAIL CLOSED)"
# 7. kein verified Archive (Punkt 10: verified Archive strikt DENY)
# verify_archive macht bei FAIL intern exit 2 -> in Subshell kapseln,
# damit ein INVALID/PARTIAL-Ergebnis das Skript nicht beendet.
local verify_rc=0
( verify_archive recovery ) >/dev/null 2>&1 || verify_rc=$?
if [ "$verify_rc" -eq 0 ]; then
die "cleanup: Archive ist VERIFIED (verify-recovery-archive PASS) — Cleanup strikt DENY (FAIL CLOSED)"
fi
# 8. Klassifikation: PARTIAL wenn session.meta fehlt; sonst INVALID (verify FAIL)
local classification
if [ ! -f "$base/session.meta" ]; then
classification="PARTIAL"
else
classification="INVALID"
fi
echo " CLASSIFICATION=$classification"
# 9. Nur PARTIAL/INVALID darf bereinigt werden (verified bereits oben DENY)
[ "$classification" = "PARTIAL" ] || [ "$classification" = "INVALID" ] || die "cleanup: Klassifikation ungueltig (FAIL CLOSED)"
# 10. keine andere Recovery-ID betroffen — nur exakt dieser sid
local other
for other in "$sdir"/*/; do
[ -d "$other" ] || continue
local oname="${other%/}"; oname="${oname##*/}"
[ "$oname" = "$sid" ] && continue
die "cleanup: andere Recovery-ID betroffen: $oname (FAIL CLOSED)"
done
if [ "$RUN_MODE" = "DRY_RUN" ]; then
echo " WOULD_REMOVE=$base"
echo "DRY_RUN_PASS: cleanup-partial-recovery-archive wuerde $base entfernen (keine Aktion)."
exit 0
fi
mode_or_die
# MUTATION: kontrollierter Cleanup des Partial-Ordners
rm -rf -- "$base" || die "cleanup: Entfernen fehlgeschlagen (FAIL CLOSED)"
[ ! -e "$base" ] || die "cleanup: Ziel nach Entfernen noch vorhanden (FAIL CLOSED)"
echo "CLEANUP-PARTIAL-RECOVERY-ARCHIVE OK: $base entfernt"
echo " Retry bereit: archive-recovery-image wird denselben CURRENT_RECOVERY_FULL_IMAGE_ID verwenden."
}
verify_ready_cmd() { # $1 = role (read-only) verify_ready_cmd() { # $1 = role (read-only)
local role="$1"; expect_zero "${@:2}" local role="$1"; expect_zero "${@:2}"
verify_ready "$role" || die "$(case $role in recovery) echo 'RECOVERY READY';; remediation) echo 'CANDIDATE READY';; esac): FAIL (FAIL CLOSED)" verify_ready "$role" || die "$(case $role in recovery) echo 'RECOVERY READY';; remediation) echo 'CANDIDATE READY';; esac): FAIL (FAIL CLOSED)"
@ -2395,8 +2473,8 @@ 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|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|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 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
@ -2806,6 +2884,7 @@ case "$SUBCMD" in
pin-remediation-image) pin_image remediation "$@" ;; pin-remediation-image) pin_image remediation "$@" ;;
verify-recovery-archive) verify_archive_cmd recovery "$@" ;; verify-recovery-archive) verify_archive_cmd recovery "$@" ;;
verify-remediation-archive) verify_archive_cmd remediation "$@" ;; verify-remediation-archive) verify_archive_cmd remediation "$@" ;;
cleanup-partial-recovery-archive) cleanup_partial_recovery_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 "$@" ;;
@ -2822,6 +2901,6 @@ case "$SUBCMD" in
verify-recovered-gold-build-source) verify_recovered_gold_build_source "$@";; verify-recovered-gold-build-source) verify_recovered_gold_build_source "$@";;
wrapper-sha) expect_zero "$@"; sha256sum /usr/local/bin/rq-historical ;; wrapper-sha) expect_zero "$@"; sha256sum /usr/local/bin/rq-historical ;;
*) die "Aufruf: ps|inspect|logs|run|code <K>|hashes|code-list|stage-dir|stage-check <K>|test <K>|search-writes|healthcheck|backup <K>|deploy <K>|rollback <K>|build-def|build-files|host-manifest|host-code <p>|container-manifest|container-code <p>|tree-diff|backup-sot|sot-session|recover-sot <K>|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|archive-recovery-image|archive-remediation-image|pin-recovery-image|pin-remediation-image|verify-recovery-archive|verify-remediation-archive|verify-recovery-pin|verify-remediation-pin|verify-recovery-ready|verify-remediation-ready|dry-restore-recovery-image-archive|restore-recovery-image-archive|verify-deployed-gold-source|dry-run recover-gold-build-source|recover-gold-build-source|verify-recovered-gold-build-source" *) die "Aufruf: ps|inspect|logs|run|code <K>|hashes|code-list|stage-dir|stage-check <K>|test <K>|search-writes|healthcheck|backup <K>|deploy <K>|rollback <K>|build-def|build-files|host-manifest|host-code <p>|container-manifest|container-code <p>|tree-diff|backup-sot|sot-session|recover-sot <K>|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|archive-recovery-image|archive-remediation-image|pin-recovery-image|pin-remediation-image|verify-recovery-archive|verify-remediation-archive|cleanup-partial-recovery-archive|verify-recovery-pin|verify-remediation-pin|verify-recovery-ready|verify-remediation-ready|dry-restore-recovery-image-archive|restore-recovery-image-archive|verify-deployed-gold-source|dry-run recover-gold-build-source|recover-gold-build-source|verify-recovered-gold-build-source"
;; ;;
esac esac