#!/usr/bin/env python3 """Apply the Catalog-owned Account approval notification binding Seed module.""" from __future__ import annotations import argparse import json import os from pathlib import Path import subprocess import sys import time from typing import Any SCRIPT_DIR = Path(__file__).resolve().parent REPO_ROOT = SCRIPT_DIR.parent RUNTIME_DIR = REPO_ROOT / "deployments" / "offline" / "runtime" if str(RUNTIME_DIR) not in sys.path: sys.path.insert(0, str(RUNTIME_DIR)) import apply_account_approval_notify_seed as account_notify_seed # noqa: E402 import deploy_offline_env # noqa: E402 MODULE_ID = "account_approval_notify" INVOCATION_ID = "staging.account_notify.post_credits_approval" SUMMARY_FILENAME = "summary.json" SUMMARY_FORMAT = "deploy-seed-script-v1" PLANNED_COUNTS = {"process_actions": 5} class AccountNotifySeedAdapterError(RuntimeError): """Raised when the scripted Seed adapter contract is violated.""" def parse_args(argv: list[str] | None = None) -> argparse.Namespace: parser = argparse.ArgumentParser( description="Apply Account approval notification binding Seed data." ) parser.add_argument("--source-root", required=True) parser.add_argument("--module-id", required=True) parser.add_argument("--invocation-id", required=True) parser.add_argument("--data-source", action="append", required=True) parser.add_argument("--selection-json", required=True) parser.add_argument("--output-dir", required=True) parser.add_argument("--dry-run", action="store_true") return parser.parse_args(argv) def _selection(raw: str) -> dict[str, Any]: try: selection = json.loads(raw) except json.JSONDecodeError as exc: raise AccountNotifySeedAdapterError("selection must be valid JSON") from exc if selection != {}: raise AccountNotifySeedAdapterError( "Account approval notification Seed does not accept a selection" ) return selection def _seed_file(source_root: Path, data_sources: list[str]) -> Path: if len(data_sources) != 1: raise AccountNotifySeedAdapterError( "Account approval notification Seed requires one data source" ) expected = ( source_root / "deployments" / "offline" / "runtime" / "seeds" / "account-approvals-notify.seed.json" ).resolve(strict=True) actual = Path(data_sources[0]).expanduser().resolve(strict=True) if actual != expected: raise AccountNotifySeedAdapterError( "data source is not the reviewed Account approval notification Seed" ) return actual def _write_summary(path: Path, payload: dict[str, Any]) -> None: path.write_text( json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8", ) def run_adapter(args: argparse.Namespace) -> tuple[dict[str, Any], Path]: started = time.perf_counter() source_root = Path(args.source_root).expanduser().resolve(strict=True) if args.module_id != MODULE_ID: raise AccountNotifySeedAdapterError( "module id is not the Account approval notification Seed module" ) if args.invocation_id != INVOCATION_ID: raise AccountNotifySeedAdapterError( "invocation id is not the reviewed Account approval notification Seed route" ) _selection(args.selection_json) seed_file = _seed_file(source_root, args.data_source) seed = json.loads(seed_file.read_text(encoding="utf-8")) errors = account_notify_seed.validate(seed) if errors: raise AccountNotifySeedAdapterError( "Account approval notification Seed validation failed: " + "; ".join(errors) ) if len(seed.get("processActions") or []) != PLANNED_COUNTS["process_actions"]: raise AccountNotifySeedAdapterError( "Account approval notification Seed count does not match the contract" ) output_dir = Path(args.output_dir).expanduser().resolve() output_dir.mkdir(parents=True, exist_ok=False) applied_counts = {"process_actions": 0} verification_status = "planned" if not args.dry_run: completed = subprocess.run( [ sys.executable, str(RUNTIME_DIR / "apply_account_approval_notify_seed.py"), "--seed-file", str(seed_file), ], cwd=source_root, env=dict(os.environ), check=False, ) if completed.returncode != 0: raise AccountNotifySeedAdapterError( f"Account approval notification Seed applier exited {completed.returncode}" ) applied_counts = dict(PLANNED_COUNTS) deploy_offline_env.refresh_runtime_metadata_cache( dict(os.environ), reason="staging account approval notify binding seed", ) verification_status = "skipped" summary = { "schema_version": 1, "format": SUMMARY_FORMAT, "module_id": MODULE_ID, "dry_run": args.dry_run, "apply_engine": "direct_sql", "planned_counts": dict(PLANNED_COUNTS), "applied_counts": applied_counts, "coverage_complete": applied_counts == PLANNED_COUNTS if not args.dry_run else True, "verification_status": verification_status, "duration_ms": round((time.perf_counter() - started) * 1000), } summary_path = output_dir / SUMMARY_FILENAME _write_summary(summary_path, summary) print(json.dumps(summary, ensure_ascii=False, indent=2, sort_keys=True)) return summary, summary_path def main(argv: list[str] | None = None) -> int: args = parse_args(argv) try: run_adapter(args) except (OSError, RuntimeError, deploy_offline_env.DeployError) as exc: print(f"Account approval notification Seed adapter failed: {exc}", file=sys.stderr) return 2 return 0 if __name__ == "__main__": raise SystemExit(main())