#!/usr/bin/env python3 """Apply the Catalog-owned EXP HubSpot owner binding Seed module.""" from __future__ import annotations import argparse import json import os from pathlib import Path 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 deploy_offline_env # noqa: E402 MODULE_ID = "exp_hubspot_owner_bindings" INVOCATION_ID = "exp.hubspot_owner_bindings.post_baseline" SUMMARY_FILENAME = "summary.json" SUMMARY_FORMAT = "deploy-seed-script-partition-v1" PLANNED_COUNTS = {"binding_mappings": 52} class ExpOwnerBindingsAdapterError(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 EXP HubSpot owner 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 ExpOwnerBindingsAdapterError("selection must be valid JSON") from exc if selection != {}: raise ExpOwnerBindingsAdapterError( "EXP HubSpot owner binding 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 ExpOwnerBindingsAdapterError( "EXP HubSpot owner binding Seed requires one data source" ) expected = ( source_root / "deployments" / "offline" / "runtime" / "seeds" / "exp-hubspot-owner-bindings.json" ).resolve(strict=True) actual = Path(data_sources[0]).expanduser().resolve(strict=True) if actual != expected: raise ExpOwnerBindingsAdapterError( "data source is not the reviewed EXP HubSpot owner binding 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 ExpOwnerBindingsAdapterError( "module id is not the EXP HubSpot owner binding Seed module" ) if args.invocation_id != INVOCATION_ID: raise ExpOwnerBindingsAdapterError( "invocation id is not the reviewed EXP HubSpot owner binding Seed route" ) _selection(args.selection_json) seed_file = _seed_file(source_root, args.data_source) runtime_script_dir = seed_file.parent.parent mappings = deploy_offline_env.load_exp_hubspot_owner_binding_bootstrap( runtime_script_dir ) if len(mappings) != PLANNED_COUNTS["binding_mappings"]: raise ExpOwnerBindingsAdapterError( "EXP HubSpot owner binding 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 = {"binding_mappings": 0} outcome_counts = {"resolved_bindings": 0, "unresolved_mappings": 0} verification_status = "planned" if not args.dry_run: outcome_counts = deploy_offline_env.bootstrap_exp_hubspot_owner_bindings( dict(os.environ), runtime_script_dir, source_root, ) applied_counts = dict(PLANNED_COUNTS) verification_status = "passed" summary = { "schema_version": 1, "format": SUMMARY_FORMAT, "module_id": MODULE_ID, "dry_run": args.dry_run, "apply_engine": "rest_api", "planned_counts": dict(PLANNED_COUNTS), "applied_counts": applied_counts, "outcome_counts": outcome_counts, "coverage_complete": ( sum(outcome_counts.values()) == PLANNED_COUNTS["binding_mappings"] 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"EXP HubSpot owner binding Seed adapter failed: {exc}", file=sys.stderr) return 2 return 0 if __name__ == "__main__": raise SystemExit(main())