vignette/scripts/validate-public-runtime-offline-quiescence.py
2026-08-29 23:58:33 +09:00

183 lines
7.3 KiB
Python

#!/usr/bin/env python3
"""Validate a privacy-safe legacy bootstrap quiescence receipt."""
from __future__ import annotations
import argparse
import asyncio
import importlib.util
import json
import sys
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
INITIALIZER = SCRIPT_DIR / "initialize-public-runtime-upload-root.py"
async def _current_database_target_sha256() -> str:
import asyncpg
from app.config import settings
from public_runtime_database_identity import connected_database_target_sha256
connection = await asyncpg.connect(
settings.database_url, command_timeout=settings.db_command_timeout
)
try:
async with connection.transaction(isolation="repeatable_read", readonly=True):
return await connected_database_target_sha256(connection)
finally:
await connection.close()
def _load_initializer():
spec = importlib.util.spec_from_file_location(
"vignette_upload_initializer_contract", INITIALIZER
)
if spec is None or spec.loader is None:
raise RuntimeError("offline_quiescence_validator_unavailable")
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--receipt-path", required=True)
parser.add_argument("--expected-receipt-sha256", required=True)
parser.add_argument("--manifest-path", required=True)
parser.add_argument("--expected-manifest-sha256", required=True)
parser.add_argument("--stable-source-root", required=True)
parser.add_argument("--upload-root", required=True)
parser.add_argument("--expected-legacy-source-commit", required=True)
parser.add_argument("--expected-legacy-source-tree", required=True)
args = parser.parse_args()
module = _load_initializer()
try:
manifest_path = module._validated_regular_file(Path(args.manifest_path))
if module.sha256_file(manifest_path) != args.expected_manifest_sha256:
raise module.InitializationError("manifest_hash_drift")
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
offline = manifest.get("offline_quiescence") if isinstance(manifest, dict) else None
if not isinstance(offline, dict) or set(offline) != {
"receipt_sha256",
"database_target_sha256",
"source_root_sha256s",
"source_root_set_sha256",
"preserved_object_count",
"preserved_total_size_bytes",
"preserved_inventory_sha256",
"preserved_decode_valid_count",
"preserved_decode_invalid_count",
"required_decode_invalid_object_count",
"required_decode_invalid_reference_count",
"reference_count",
"unique_object_count",
"reference_set_sha256",
}:
raise module.InitializationError("manifest_offline_quiescence_missing")
if offline.get("receipt_sha256") != args.expected_receipt_sha256:
raise module.InitializationError("manifest_quiescence_receipt_drift")
proof = module.validate_offline_quiescence_receipt(
receipt_path=Path(args.receipt_path),
expected_receipt_sha256=args.expected_receipt_sha256,
stable_source_root=Path(args.stable_source_root),
upload_root=Path(args.upload_root),
expected_source_commit=args.expected_legacy_source_commit,
expected_source_tree=args.expected_legacy_source_tree,
expected_source_roots=None,
expected_source_root_sha256s=offline.get("source_root_sha256s"),
expected_source_root_set_sha256=offline.get(
"source_root_set_sha256"
),
expected_database_target_sha256=asyncio.run(
_current_database_target_sha256()
),
expected_preserved_object_count=offline.get(
"preserved_object_count"
),
expected_preserved_total_size_bytes=offline.get(
"preserved_total_size_bytes"
),
expected_preserved_inventory_sha256=offline.get(
"preserved_inventory_sha256"
),
expected_preserved_decode_valid_count=offline.get(
"preserved_decode_valid_count"
),
expected_preserved_decode_invalid_count=offline.get(
"preserved_decode_invalid_count"
),
expected_required_decode_invalid_object_count=offline.get(
"required_decode_invalid_object_count"
),
expected_required_decode_invalid_reference_count=offline.get(
"required_decode_invalid_reference_count"
),
expected_reference_count=offline.get("reference_count"),
expected_unique_object_count=offline.get("unique_object_count"),
expected_reference_set_sha256=offline.get("reference_set_sha256"),
)
if list(proof.source_root_sha256s) != offline.get(
"source_root_sha256s"
) or proof.source_root_set_sha256 != offline.get(
"source_root_set_sha256"
):
raise module.InitializationError("manifest_legacy_source_root_drift")
except (OSError, ValueError, module.InitializationError):
print(
json.dumps(
{"status": "failed", "reason": "offline_quiescence_contract_failed"},
ensure_ascii=True,
separators=(",", ":"),
)
)
return 1
except Exception:
print(
json.dumps(
{"status": "failed", "reason": "offline_quiescence_validation_unavailable"},
ensure_ascii=True,
separators=(",", ":"),
)
)
return 1
print(
json.dumps(
{
"status": "passed",
"receipt_sha256": proof.receipt_sha256,
"database_target_sha256": proof.database_target_sha256,
"source_root_set_sha256": proof.source_root_set_sha256,
"preserved_object_count": proof.preserved_object_count,
"preserved_total_size_bytes": proof.preserved_total_size_bytes,
"preserved_inventory_sha256": proof.preserved_inventory_sha256,
"preserved_decode_valid_count": (
proof.preserved_decode_valid_count
),
"preserved_decode_invalid_count": (
proof.preserved_decode_invalid_count
),
"required_decode_invalid_object_count": (
proof.required_decode_invalid_object_count
),
"required_decode_invalid_reference_count": (
proof.required_decode_invalid_reference_count
),
"reference_count": proof.reference_count,
"unique_object_count": proof.unique_object_count,
"reference_set_sha256": proof.reference_set_sha256,
"listener_absent": True,
"tunnel_absent": True,
},
ensure_ascii=True,
separators=(",", ":"),
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())