105 lines
3.6 KiB
Python
105 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timedelta, timezone
|
|
from pathlib import Path
|
|
|
|
from sqlalchemy import create_engine, inspect, select
|
|
from sqlalchemy.orm import Session
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from scripts.event_bus_smoke import run_smoke_check
|
|
from services.shared.db import DATABASE_URL, _normalize_database_url
|
|
from services.shared.sql_models import EventOutboxRow
|
|
|
|
|
|
def _engine_for(database_url: str | None):
|
|
return create_engine(_normalize_database_url(database_url or DATABASE_URL), future=True)
|
|
|
|
|
|
def _parse_iso(value: str | None) -> datetime | None:
|
|
if not value:
|
|
return None
|
|
try:
|
|
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
except ValueError:
|
|
return None
|
|
|
|
|
|
def evaluate_track8(
|
|
*,
|
|
database_url: str | None = None,
|
|
require_bus_enabled: bool = False,
|
|
base_url: str | None = None,
|
|
failed_age_seconds: int = 300,
|
|
) -> list[str]:
|
|
issues: list[str] = []
|
|
engine = _engine_for(database_url)
|
|
inspector = inspect(engine)
|
|
tables = set(inspector.get_table_names())
|
|
required_tables = {"event_outbox", "event_inbox", "reporting_event_log"}
|
|
missing = sorted(required_tables - tables)
|
|
if missing:
|
|
issues.append(f"Missing required tables: {', '.join(missing)}")
|
|
|
|
if require_bus_enabled and os.getenv("EVENT_BUS_ENABLED", "0").strip() not in {"1", "true", "yes", "on"}:
|
|
issues.append("EVENT_BUS_ENABLED is not enabled in the current environment")
|
|
|
|
with Session(engine) as session:
|
|
failed_rows = session.execute(
|
|
select(EventOutboxRow).where(EventOutboxRow.status == "failed")
|
|
).scalars().all()
|
|
stale_cutoff = datetime.now(timezone.utc) - timedelta(seconds=failed_age_seconds)
|
|
stale_failed = [
|
|
row for row in failed_rows if (_parse_iso(row.updated_at) or _parse_iso(row.created_at) or stale_cutoff) < stale_cutoff
|
|
]
|
|
if stale_failed:
|
|
issues.append(f"Found {len(stale_failed)} failed outbox events older than {failed_age_seconds}s")
|
|
|
|
pending_rows = session.execute(
|
|
select(EventOutboxRow).where(EventOutboxRow.status == "pending")
|
|
).scalars().all()
|
|
if len(pending_rows) > 500:
|
|
issues.append(f"Outbox backlog too high: {len(pending_rows)} pending events")
|
|
|
|
if not (ROOT / "contracts" / "events" / "ivr.completed.json").exists():
|
|
issues.append("Missing contracts/events/ivr.completed.json")
|
|
|
|
if base_url:
|
|
smoke = run_smoke_check(base_url=base_url, database_url=database_url)
|
|
if not smoke["passed"]:
|
|
issues.append("Event bus smoke check failed")
|
|
|
|
return issues
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Validate Track 8 event-bus readiness.")
|
|
parser.add_argument("--database-url", default=None)
|
|
parser.add_argument("--require-bus-enabled", action="store_true")
|
|
parser.add_argument("--base-url", default=None)
|
|
parser.add_argument("--failed-age-seconds", type=int, default=300)
|
|
args = parser.parse_args()
|
|
|
|
issues = evaluate_track8(
|
|
database_url=args.database_url,
|
|
require_bus_enabled=args.require_bus_enabled,
|
|
base_url=args.base_url,
|
|
failed_age_seconds=args.failed_age_seconds,
|
|
)
|
|
if issues:
|
|
print("[FAIL] Track 8 validation failed:")
|
|
for issue in issues:
|
|
print(f"- {issue}")
|
|
raise SystemExit(1)
|
|
print("[PASS] Track 8 validation checks passed")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|