Add app credentials, account-scoped idempotency and per-app terminal notifications. Consolidate the unreleased migration baseline, preserve rule descriptions as AI context, and regenerate provider contracts with regression coverage.
104 lines
3.6 KiB
Python
104 lines
3.6 KiB
Python
"""Generate response and signed webhook examples from provider serializers."""
|
|
|
|
import argparse
|
|
import asyncio
|
|
import json
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
|
|
import httpx
|
|
|
|
from ai_platform.infrastructure.webhook import OmniWebhook
|
|
from ai_platform.lead_scoring.application.notifications import Notification
|
|
from ai_platform.lead_scoring.application.repository import ScoringRunRecord
|
|
from ai_platform.lead_scoring.application.run_use_cases import to_run_detail
|
|
from ai_platform.lead_scoring.application.scoring import calculate_run
|
|
from ai_platform.presentation.http.routes import to_detail_json, to_json
|
|
from ai_platform.presentation.http.run_responses import RunDetail, RunSummary
|
|
from ai_platform.presentation.http.schemas import RunRequest
|
|
|
|
FIXTURES = Path(__file__).resolve().parents[1] / "contracts/lead-scoring/v2/fixtures"
|
|
NOW = datetime(2026, 9, 28, tzinfo=UTC)
|
|
SECRET = "test-only-webhook-secret-32-characters-long"
|
|
RUN_ID = "22222222-2222-4222-8222-222222222222"
|
|
EVENT_ID = "11111111-1111-4111-8111-111111111111"
|
|
|
|
|
|
def write_json(name: str, value: dict, *, check: bool) -> None:
|
|
path = FIXTURES / name
|
|
expected = json.dumps(value, ensure_ascii=False, indent=2) + "\n"
|
|
if check:
|
|
if not path.exists() or path.read_text() != expected:
|
|
raise SystemExit(f"Contract fixture drift: {name}; regenerate provider examples")
|
|
else:
|
|
path.write_text(expected)
|
|
|
|
|
|
async def main(*, check: bool = False) -> None:
|
|
request = RunRequest.model_validate_json(
|
|
(FIXTURES / "run-request.json").read_text()
|
|
).model_dump(mode="json")
|
|
rule_score, breakdown = calculate_run(request)
|
|
row = ScoringRunRecord(
|
|
id=RUN_ID,
|
|
request_id=request["request_id"],
|
|
app_id="omni-local",
|
|
account_id=request["account_id"],
|
|
status="completed",
|
|
fingerprint="0" * 64,
|
|
profile_version="profile-example",
|
|
request=request,
|
|
rule_score=rule_score,
|
|
ai_score=None,
|
|
final_score=rule_score,
|
|
degraded=False,
|
|
result_mode="rules_only",
|
|
error_code=None,
|
|
breakdown=breakdown,
|
|
created_at=NOW,
|
|
completed_at=NOW,
|
|
ai_confidence=None,
|
|
ai_reasoning=None,
|
|
ai_evidence=None,
|
|
)
|
|
write_json(
|
|
"run-summary.json",
|
|
RunSummary.model_validate(to_json(row)).model_dump(mode="json"),
|
|
check=check,
|
|
)
|
|
write_json(
|
|
"run-detail.json",
|
|
RunDetail.model_validate(to_detail_json(to_run_detail(row))).model_dump(mode="json"),
|
|
check=check,
|
|
)
|
|
event = Notification(
|
|
EVENT_ID, RUN_ID, row.app_id, row.account_id, row.request_id, "completed", NOW, 0
|
|
)
|
|
|
|
def capture(request: httpx.Request) -> httpx.Response:
|
|
write_json(
|
|
"webhook-delivery.json",
|
|
{
|
|
"method": request.method,
|
|
"content_type": request.headers["Content-Type"],
|
|
"timestamp": request.headers["X-Webhook-Timestamp"],
|
|
"body": request.content.decode(),
|
|
"signature": request.headers["X-Webhook-Signature"],
|
|
},
|
|
check=check,
|
|
)
|
|
return httpx.Response(202)
|
|
|
|
async with httpx.AsyncClient(transport=httpx.MockTransport(capture)) as client:
|
|
assert await OmniWebhook(
|
|
client, "https://omni.example.invalid/events", SECRET, lambda: NOW
|
|
).send(event)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"--check", action="store_true", help="Reject drift without writing artifacts"
|
|
)
|
|
asyncio.run(main(check=parser.parse_args().check))
|