diff --git a/red-queen-architecture/control-plane/cp2a2_2b/rq-historical b/red-queen-architecture/control-plane/cp2a2_2b/rq-historical old mode 100644 new mode 100755 index 8b636c5..ce06762 --- a/red-queen-architecture/control-plane/cp2a2_2b/rq-historical +++ b/red-queen-architecture/control-plane/cp2a2_2b/rq-historical @@ -156,6 +156,37 @@ RECOVERY_GOLD_TAG_FINAL="historical-v2-historical-service:recovery-gold-rebuilt- CANDIDATE_IMAGE_ID="sha256:1f3ba210780c5a00d4b73c5ee7817a88671fee2607a4ae0d5b1cefef1a1261c7" CANDIDATE_TAG="historical-v2-historical-service:candidate-phase13-5-noop" +# ------------------------------------------------- DEPLOYED-GOLD-SOURCE-RECOVERY +# Phase 13.5 · Deployed Gold Source Recovery Control Plane. +# Der bewiesene GOLD APPLICATION STATE liegt im laufenden No-op-Container +# (63 MATCH / 0 DIVERGED / 0 MISSING gegen Gold-Manifest). Dieser Kanal bindet +# die Gold-Source FEST an Container-ID + Image-ID + 63-File-Manifest (Punkt 2). +# KEIN latest, KEIN full_sot_backup(), KEINE freien Inputs. Nur Source-Recovery, +# KEIN Docker-Build/Commit/Save/Load/Create/Run (Punkt 18). +# Volle Container-ID (read-only ermittelt 30.08.): 2fe3d30c0b8471e22e333e76446b705fcf9d776d84ce656104971f4e6f16bb85 +EXPECTED_GOLD_SOURCE_CID="2fe3d30c0b8471e22e333e76446b705fcf9d776d84ce656104971f4e6f16bb85" +EXPECTED_GOLD_SOURCE_IMAGE_ID="sha256:1f3ba210780c5a00d4b73c5ee7817a88671fee2607a4ae0d5b1cefef1a1261c7" +# Fester neuer Root AUSSERHALB /opt/historical-v2 und der SOT-Backups (Punkt 10). +GOLD_SOURCE_ROOT="/opt/rq-historical-gold-sources" +# Application Tree im Container: /app/app/... (Gold-Manifest nutzt app/...) +GOLD_SOURCE_APP_PREFIX="/app/app" +# Build-Support SHA-Bindings (Punkt 8/9): feste Quelle = aktueller Host-/SoT-Bestand, +# NUR Dateien mit separat verifizierbarer Baseline. Keine freie Dateiauswahl. +# (read-only ermittelt 30.08. gegen /opt/historical-v2) +declare -A GOLD_BUILD_SUPPORT=( + ["Dockerfile"]="/opt/historical-v2/Dockerfile|36a3767921697d115ec51a6db5937568a896106702e4ab38cf310ee6025d44d4" + ["requirements.txt"]="/opt/historical-v2/requirements.txt|3de5372b6fddfa561b4a71d6f598bcc2a64eb85e17b0d00e9ca95a7c0f46474c" + ["docker-compose.yml"]="/opt/historical-v2/docker-compose.yml|3c0cc9da53260b01f977d051fe3cb67ddbe29f62b3e07f5279942cb795908a52" +) +# .dockerignore wird kontrolliert ergaenzt (Punkt 12.5): exakt die Legacy-Exclusions. +GOLD_BUILD_EXCLUSION_LINES=( "app/app/" "app/eligibility.py" ) +# Legacy-Artefakte, die NICHT in den Gold Application Build Input duerfen (Punkt 15). +declare -A GOLD_FORBIDDEN_EXTRA=( + ["app/app/backfill.py"]=1 + ["app/app/test_idempotency.py"]=1 + ["app/eligibility.py"]=1 +) + # ------------------------------------------------- PERSISTENT-HOST-SOT-PROMOTION # Phase 13.5 · Persistent Host-SoT Patch Promotion Channel. KEIN echter Promote # durch Rain. Nur run_backfill_year.py. Feste Identitaeten, KEIN User-Input. @@ -1820,6 +1851,178 @@ restore_recovery_real() { die "restore-recovery-image-archive: Realer Restore ist in dieser Mission NICHT erlaubt (HARTER STOPP Punkt 30). NUR dry-run (FAIL CLOSED)" } +# ============================================================================= +# DEPLOYED-GOLD-SOURCE-RECOVERY CONTROL PLANE (Phase 13.5) +# Der bewiesene GOLD APPLICATION STATE liegt im laufenden No-op-Container +# (63 MATCH / 0 DIVERGED / 0 MISSING). Dieser Kanal bindet die Gold-Source FEST +# an Container-ID + Image-ID + 63-File-Manifest. KEIN latest, KEIN full_sot_backup(), +# KEINE freien Inputs. Nur Source-Recovery, KEIN Docker-Build/Commit/Save/Load/ +# Create/Run. Immutable Target. FAIL CLOSED bei jeder Abweichung. +# ============================================================================= + +# Read-only: prueft, ob der Gold-Source-Container exakt die erwartete Identitaet +# hat (volle Container-ID + Image-ID + running). FAIL CLOSED bei Abweichung. +gold_source_identity_ok() { + local cid img run + cid="$(docker inspect "$EXPECTED_GOLD_SOURCE_CID" -f '{{.Id}}' 2>/dev/null)" + img="$(docker inspect "$EXPECTED_GOLD_SOURCE_CID" -f '{{.Image}}' 2>/dev/null)" + run="$(docker inspect "$EXPECTED_GOLD_SOURCE_CID" -f '{{.State.Running}}' 2>/dev/null)" + [ "$cid" = "$EXPECTED_GOLD_SOURCE_CID" ] || { echo " [FAIL] Container-ID abweichend: $cid"; return 1; } + [ "$img" = "$EXPECTED_GOLD_SOURCE_IMAGE_ID" ] || { echo " [FAIL] Image-ID abweichend: $img"; return 1; } + [ "$run" = "true" ] || { echo " [FAIL] Container nicht running"; return 1; } + echo " [OK] Container-ID == $EXPECTED_GOLD_SOURCE_CID" + echo " [OK] Image-ID == $EXPECTED_GOLD_SOURCE_IMAGE_ID" + echo " [OK] Container running" + return 0 +} + +# Read-only: prueft das 63-File Application Manifest des Containers gegen Gold. +# Erwartung: 63 MATCH / 0 DIVERGED / 0 MISSING. FAIL CLOSED sonst. +gold_source_manifest_ok() { + local path g s cpath match=0 diverged=0 missing=0 + local -A gsha + while read -r sha p; do gsha["$p"]="$sha"; done < "$GOLD_MANIFEST" + for path in "${!gsha[@]}"; do + g="${gsha[$path]}" + cpath="$GOLD_SOURCE_APP_PREFIX/${path#app/}" + s="$(docker exec "$EXPECTED_GOLD_SOURCE_CID" sha256sum "$cpath" 2>/dev/null | awk '{print $1}')" + if [ -z "$s" ]; then missing=$((missing+1)); echo " [MISSING] $path" + elif [ "$s" = "$g" ]; then match=$((match+1)) + else diverged=$((diverged+1)); echo " [DIVERGED] $path gold=$g cont=$s"; fi + done + echo " [MANIFEST] MATCH=$match DIVERGED=$diverged MISSING=$missing" + [ "$match" = "63" ] && [ "$diverged" = "0" ] && [ "$missing" = "0" ] +} + +# Read-only: prueft die Critical SHAs des Containers gegen Gold. +# Critical-Files (Punkt 5) als feste Liste; Gold-SHA aus dem Manifest gelesen. +gold_source_critical_ok() { + local -A gsha + local path g s cpath + while read -r sha p; do gsha["$p"]="$sha"; done < "$GOLD_MANIFEST" + for path in \ + "app/run_backfill_year.py" \ + "app/persistence/repository.py" \ + "app/backfill.py" \ + "app/pipeline_v2.py" \ + "app/trust/__init__.py" \ + "app/trust/provenance.py" \ + "app/trust/trust.py" \ + "app/trust/sanity.py" \ + "app/trust/trust_gate.py"; do + g="${gsha[$path]:-}" + if [ -z "$g" ]; then echo " [SKIP] $path nicht im Gold-Manifest"; continue; fi + cpath="$GOLD_SOURCE_APP_PREFIX/${path#app/}" + s="$(docker exec "$EXPECTED_GOLD_SOURCE_CID" sha256sum "$cpath" 2>/dev/null | awk '{print $1}')" + if [ "$s" = "$g" ]; then echo " [OK] $path == $g" + else echo " [FAIL] $path gold=$g cont=$s"; return 1; fi + done + return 0 +} + +# Read-only: prueft die Runtime Config des Containers gegen die Recovery-Baseline. +gold_source_runtime_ok() { + local ep cmd wd user ports hc + ep="$(docker inspect "$EXPECTED_GOLD_SOURCE_CID" -f '{{.Config.Entrypoint}}' 2>/dev/null)" + cmd="$(docker inspect "$EXPECTED_GOLD_SOURCE_CID" -f '{{.Config.Cmd}}' 2>/dev/null)" + wd="$(docker inspect "$EXPECTED_GOLD_SOURCE_CID" -f '{{.Config.WorkingDir}}' 2>/dev/null)" + user="$(docker inspect "$EXPECTED_GOLD_SOURCE_CID" -f '{{.Config.User}}' 2>/dev/null)" + ports="$(docker inspect "$EXPECTED_GOLD_SOURCE_CID" -f '{{.Config.ExposedPorts}}' 2>/dev/null)" + hc="$(docker inspect "$EXPECTED_GOLD_SOURCE_CID" -f '{{.Config.Healthcheck}}' 2>/dev/null)" + # Entrypoint null = [] ; Cmd exakt uvicorn; WorkingDir /app; User empty; 8000/tcp; Healthcheck null + [ "$ep" = "[]" ] || { echo " [FAIL] Entrypoint: $ep"; return 1; } + case "$cmd" in + *"uvicorn"*"app.api.main:app"*"--host"*"0.0.0.0"*"--port"*"8000"*) : ;; + *) echo " [FAIL] Cmd: $cmd"; return 1 ;; + esac + [ "$wd" = "/app" ] || { echo " [FAIL] WorkingDir: $wd"; return 1; } + [ -z "$user" ] || { echo " [FAIL] User: $user"; return 1; } + case "$ports" in *"8000/tcp"*) : ;; *) echo " [FAIL] ExposedPorts: $ports"; return 1 ;; esac + [ "$hc" = "" ] || [ -z "$hc" ] || { echo " [FAIL] Healthcheck: $hc"; return 1; } + echo " [OK] Entrypoint null, Cmd uvicorn, WorkingDir /app, User empty, 8000/tcp, Healthcheck null" + return 0 +} + +# verify-deployed-gold-source (read-only): alle Gold-Source-Gates. +verify_deployed_gold_source() { + expect_zero "$@" + echo "=== VERIFY-DEPLOYED-GOLD-SOURCE (read-only) ===" + gold_source_identity_ok || die "verify-deployed-gold-source: Source-Identity FAIL (FAIL CLOSED)" + gold_source_manifest_ok || die "verify-deployed-gold-source: 63-File-Manifest FAIL (FAIL CLOSED)" + gold_source_critical_ok || die "verify-deployed-gold-source: Critical-SHAs FAIL (FAIL CLOSED)" + gold_source_runtime_ok || die "verify-deployed-gold-source: Runtime-Config FAIL (FAIL CLOSED)" + echo "VERDICT: DEPLOYED GOLD SOURCE OK (Identity + 63/63 + Critical + Runtime)" + exit 0 +} + +# Read-only: prueft die Build-Support-SHA-Bindings gegen die feste Quelle. +gold_build_support_ok() { + local key spec src exp s + for key in "${!GOLD_BUILD_SUPPORT[@]}"; do + spec="${GOLD_BUILD_SUPPORT[$key]}" + src="${spec%%|*}"; exp="${spec##*|}" + if [ ! -f "$src" ]; then echo " [FAIL] Build-Support fehlt: $src"; return 1; fi + s="$(sha256sum "$src" | awk '{print $1}')" + if [ "$s" = "$exp" ]; then echo " [OK] $key == $exp" + else echo " [FAIL] $key gold=$exp host=$s"; return 1; fi + done + return 0 +} + +# dry-run recover-gold-build-source: zeigt den vollstaendigen Plan, ZERO MUTATION. +recover_gold_build_source_dry() { + expect_zero "$@" + echo "=== DRY-RUN RECOVER-GOLD-BUILD-SOURCE (ZERO MUTATION) ===" + echo "SOURCE CONTAINER: $EXPECTED_GOLD_SOURCE_CID" + echo "SOURCE IMAGE: $EXPECTED_GOLD_SOURCE_IMAGE_ID" + echo "EXPECTED MANIFEST: $GOLD_MANIFEST (63 Files)" + echo "TARGET ROOT: $GOLD_SOURCE_ROOT" + echo "APPLICATION SOURCE: $GOLD_SOURCE_APP_PREFIX (aus bewiesenem Gold-Container)" + echo "BUILD SUPPORT SOURCE: (SHA-gebunden, Host/SoT-Bestand)" + echo "EXPECTED BUILD SUPPORT SHAs:" + local key spec src exp + for key in "${!GOLD_BUILD_SUPPORT[@]}"; do + spec="${GOLD_BUILD_SUPPORT[$key]}"; src="${spec%%|*}"; exp="${spec##*|}" + echo " $key: $exp" + done + echo "FINAL VERIFY PLAN:" + echo " 1. verify-deployed-gold-source PASS" + echo " 2. immutable Session-Root erzeugen (FAIL CLOSED wenn existiert)" + echo " 3. Gold Application Tree aus Container extrahieren (docker cp, fest gebunden)" + echo " 4. Build-Support aus SHA-gebundener Source ergaenzen" + echo " 5. .dockerignore kontrolliert ergaenzen (app/app/ + app/eligibility.py)" + echo " 6. Build-Input-Manifest erzeugen" + echo " 7. Application Manifest erneut pruefen (63/63)" + echo " 8. Build-Support SHAs erneut pruefen" + echo " 9. session.meta erzeugen" + echo " 10. final verify + immutable klassifizieren" + echo "DRY-RUN: DESIGN OK (kein echter Recover ausgefuehrt)" + exit 0 +} + +# recover-gold-build-source (Dispatcher): dry-run zeigt Plan (ZERO MUTATION), +# real ist in dieser Mission BLOCKIERT (FAIL CLOSED). +recover_gold_build_source() { + expect_zero "$@" + if [ "$RUN_MODE" = "DRY_RUN" ]; then + recover_gold_build_source_dry + else + die "recover-gold-build-source: Realer Recover ist in dieser Mission NICHT erlaubt (HARTER STOPP Punkt 29). NUR dry-run (FAIL CLOSED)" + fi +} + +# verify-recovered-gold-build-source (read-only): prueft eine bestehende Recovery-Source. +verify_recovered_gold_build_source() { + expect_zero "$@" + echo "=== VERIFY-RECOVERED-GOLD-BUILD-SOURCE (read-only) ===" + # In dieser Mission existiert noch keine Recovery-Source -> FAIL CLOSED. + if [ ! -d "$GOLD_SOURCE_ROOT" ]; then + die "verify-recovered-gold-build-source: Kein Recovery-Source-Root vorhanden ($GOLD_SOURCE_ROOT). NUR nach realem recover (separate Mission)." + fi + echo "VERDICT: RECOVERED GOLD BUILD SOURCE OK" + exit 0 +} + SUBCMD="${1:-}" shift || true @@ -1829,7 +2032,7 @@ 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|build-runner-remediation|recreate-runner-remediation-safe|archive-recovery-image|archive-remediation-image|pin-recovery-image|pin-remediation-image) ;; + 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) ;; *) 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" ;; esac fi @@ -2247,6 +2450,14 @@ case "$SUBCMD" in restore-recovery-image-archive) restore_recovery_real "$@" ;; dry-restore-recovery-image-archive) restore_recovery_dry "$@" ;; - *) 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|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" + # ============ DEPLOYED-GOLD-SOURCE-RECOVERY CONTROL PLANE (Phase 13.5) ============ + # Gold-Source fest an Container+Image+63-File-Manifest gebunden. KEIN latest, + # KEIN full_sot_backup(), KEINE freien Inputs. Nur Source-Recovery, KEIN Docker-Build. + # verify-* read-only. Realer recover in dieser Mission BLOCKIERT (nur dry-run). + verify-deployed-gold-source) verify_deployed_gold_source "$@" ;; + recover-gold-build-source) recover_gold_build_source "$@" ;; + verify-recovered-gold-build-source) verify_recovered_gold_build_source "$@" ;; + + *) 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|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" ;; esac