From 80e8550f9b50a05fdd5a1ded79b06be5c7699307 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 29 Oct 2026 22:38:23 +0100 Subject: [PATCH] Bind offline recovery to retained PostgreSQL and application image IDs Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a17f16-d99c-7ea3-95f0-8212897fba6b --- scripts/restore-paired-snapshot.py | 4 +++- scripts/verify-restored-application.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/scripts/restore-paired-snapshot.py b/scripts/restore-paired-snapshot.py index 644114e..28ead89 100644 --- a/scripts/restore-paired-snapshot.py +++ b/scripts/restore-paired-snapshot.py @@ -81,6 +81,8 @@ def content_fingerprint(archive: Path, destination: Path) -> dict[str, str]: def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('snapshot', type=Path) + parser.add_argument('--postgres-image', default='postgres:16', + help='Local PostgreSQL image/digest; use the retained kit image ID offline') parser.add_argument('--keep-instance', action='store_true', help='Keep the isolated internal network/database for a content rehearsal') args = parser.parse_args() @@ -95,7 +97,7 @@ def main() -> int: name = 'csoc-recovery-' + time.strftime('%Y%m%d%H%M%S', time.gmtime()) network = name + '-net' work = Path(tempfile.mkdtemp(prefix=name + '-', dir='/tmp')) - image = run(['docker', 'image', 'inspect', 'postgres:16', + image = run(['docker', 'image', 'inspect', args.postgres_image, '--format', '{{.Id}}']).decode().strip() if not re.fullmatch(r'sha256:[a-f0-9]{64}', image): raise Refused('unresolved_postgres_image') diff --git a/scripts/verify-restored-application.py b/scripts/verify-restored-application.py index 43e81f0..656a9dc 100644 --- a/scripts/verify-restored-application.py +++ b/scripts/verify-restored-application.py @@ -12,6 +12,11 @@ parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--restore-receipt", type=Path, required=True) parser.add_argument("--snapshot", type=Path, required=True) parser.add_argument("--output", type=Path, required=True) +parser.add_argument( + "--kit-manifest", + type=Path, + help="Bind offline image IDs to the independently verified kit manifest", +) args = parser.parse_args() if args.output.exists() or args.output.is_symlink(): raise SystemExit("fresh output required") @@ -19,6 +24,14 @@ state = json.loads(args.restore_receipt.read_text()) capture = json.loads((args.snapshot / "receipt.json").read_text()) assert capture["status"] == "captured" and capture["app_resumed"] and capture["writers_quiesced"] image = capture["image"] +if args.kit_manifest: + kit = json.loads(args.kit_manifest.read_text()) + assert kit["schema"] == "platform.coulomb-recovery-kit-manifest.v1" + assert kit["application_image"] == capture["image"] + assert kit["captured_at"] == capture["started_at"] + assert kit["postgres_image_id"] == state["postgres_image_id"] + image = kit["application_image_id"] + assert re.fullmatch(r"sha256:[a-f0-9]{64}", image) if not re.fullmatch(r"csoc-recovery-\d{14}", state["database_container"]): raise SystemExit("isolated recovery container required") if state["network"] != state["database_container"] + "-net":