Files
clientflow_backend/scripts/rebuild_blif_flow_v2_projection.py

85 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""Rebuild additive BLIF Flow v2 projection tables with explicit safeguards."""
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Sequence
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
os.chdir(ROOT)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--production-shadow", action="store_true",
help="explicitly authorize projection-only shadow writes to database clientflow",
)
return parser
def validate_execution(*, production_shadow: bool, mode: str, database: str) -> None:
"""Validate CLI intent independently of DATABASE_URL inference."""
normalized_mode = str(mode or "").strip().lower()
if production_shadow:
if normalized_mode != "shadow":
raise RuntimeError("--production-shadow requires BLIF_FLOW_V2_MODE=shadow")
if database != "clientflow":
raise RuntimeError(
f"--production-shadow requires database 'clientflow', found {database!r}"
)
return
if database == "clientflow":
raise RuntimeError("production database requires explicit --production-shadow opt-in")
def main(argv: Sequence[str] | None = None) -> int:
# argparse handles --help and exits before any application/DB import below.
args = build_parser().parse_args(argv)
from sqlalchemy import text
from app.blif_flow_v2_projection_service import rebuild_blif_flow_v2_projection
from app.config import settings
from app.db import engine
mode = str(settings.blif_flow_v2_mode or "").strip().lower()
with engine.connect() as conn:
identity = conn.execute(text(
"SELECT current_database(), current_user, current_setting('transaction_read_only')"
)).one()
database, user, transaction_read_only = identity
validate_execution(
production_shadow=args.production_shadow, mode=mode, database=database,
)
print(json.dumps({
"database": database, "user": user, "blif_flow_v2_mode": mode,
"transaction_read_only": transaction_read_only,
"production_shadow_opt_in": args.production_shadow,
}, sort_keys=True), flush=True)
if args.production_shadow:
result = rebuild_blif_flow_v2_projection(
mode="shadow",
# Production is deliberately scoped to this invocation; the
# module-level default allowlist remains test-only.
allowed_databases=frozenset({"clientflow"}),
derive_expected_database="clientflow",
derive_expected_user=user,
expected_opportunity_count=None,
require_opportunities=True,
)
else:
result = rebuild_blif_flow_v2_projection()
print(json.dumps(result, indent=2, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())