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.
Lead Scoring: Omni app-auth integration
Provider baseline: omni-ai-platform-app-auth, revision 2, 2026-09-30. openapi.json is generated from backend routes; fixtures/ contains examples, not a second validation schema. The existing v2 directory is retained without legacy compatibility. Cross-service acceptance remains pending until Omni verifies the same artifacts.
Authentication and identity
POST/GET use Authorization: Bearer <app_api_key> for one Omni installation/environment. AI Platform resolves a stable app_id, requires active state and lead_scoring permission. Missing/invalid/inactive/revoked credentials return 401; missing permission returns 403 on both routes. Omni owns user/account/EMS authorization; AI Platform does not register accounts.
POST /v1/lead-scoring/runs has exactly request_id, account_id, subject_ref, snapshot, scoring_config, ai_profile. Account ID is a nonblank string of 1–255 characters, preserved unchanged. Subject must match the snapshot EMS lead ID. Idempotency-Key represents the body request UUID. Self-supplied app/tenant/callback fields return 422.
Each scoring_config.rules[] may include description: an optional string of at most 99 Unicode characters. Omitted or null descriptions preserve legacy normalized requests and replay fingerprints. Empty/whitespace descriptions are accepted but omitted from AI context. Nonblank descriptions are stored with the request, included in its fingerprint and sent as supplemental JSON context in the AI scoring pass, not as evidence or instructions. Rule matching, points and weights are unchanged; rule IDs, conditions, points and weights are not sent to AI. The request fixture includes a described rule and a legacy rule without this field.
Deduplication is (app_id, account_id, request_id): 202 and Location for new admission, 200 same canonical body/run, 409 changed body. Summary/detail carry app/account/request/run identity. GET another app's run returns 404. Key rotation preserves ownership/replay. Missing callback configuration blocks new admission with 503 after replay lookup; DB or scheduling unavailability also returns 503.
Generate OpenAPI and serializer-derived summary/detail/signature examples with make contract; uv run --no-sync python scripts/export_contract_fixtures.py --check rejects fixture drift without modifying artifacts. Validate examples in tests/test_run_contract.py and tests/test_notifications.py.
Credentials and configuration
Operator CLI issues, rotates or revokes keys: uv run --no-sync ai-platform create-app <app_id>, rotate-app-key <app_id>, revoke-app-key <app_id>. Raw key is output only for issuance/rotation and must be transferred securely to Omni backend. Admin Web lists status and revokes access without receiving raw keys. Old tenant provisioning endpoints and provisioning-secret settings are removed.
Omni backend configuration: LEAD_SCORING_API_URL, AI_PLATFORM_APP_ID (expected identity), AI_PLATFORM_APP_API_KEY. Provider configuration: AI_PLATFORM_APP_WEBHOOKS, JSON mapping app ID to {"url":"https://omni.example.invalid/api/v1/internal/lead-scoring/notifications","signing_secret":"<server-secret-at-least-32-characters>"}. Each app has its own URL/secret. Only development loopback HTTP is allowed; other environments require HTTPS. URL/secret/key never belong in browser-visible configuration. X-Tenant-VKey retains its existing name and RAM-only AIHub role.
Terminal notification
Webhook contains exactly event_id, app_id, account_id, request_id, run_id, status (completed/failed), occurred_at (UTC ISO 8601). It contains no scores, snapshot or evidence. Event ID/body remain stable across retry; occurred_at is terminal event time, not send time.
X-Webhook-Timestamp is UTC ISO 8601 for this delivery attempt. X-Webhook-Signature is sha256= plus lowercase hex HMAC-SHA256 over UTF-8 bytes timestamp + "." + raw_request_body. Verify raw bytes before parsing, compare constant-time, reject timestamps outside ±5 minutes and body above 8192 bytes. fixtures/webhook-delivery.json uses the test-only secret test-only-webhook-secret-32-characters-long to fix exact bytes/signature.
Omni commits the immutable source request before POST and durably accepts callback before ACK 202; an exact duplicate returns 200, conflicting identity/body returns 409. Dedupe (app_id,event_id), bind run atomically even when callback precedes admission response, GET with app key and compare app/account/request/run/subject to the stored source before EMS writeback. Account authorization, enabled/EMS rechecks and latest-run ordering belong to Omni.
Provider retries network/non-2xx up to five attempts, then keeps a dead event. uv run --no-sync ai-platform redeliver-notification <event_id> retries the same event without rescoring. A crash may repeat a previously ACKed event. Webhook transport rejects redirects. GET is the result source of truth.
Migration and verification boundary
The unreleased schema is consolidated in 0001_initial, creating the current app-auth schema directly. Databases with the old local revisions need separate reconciliation or an explicitly approved rebuild; the baseline does not automatically stamp, reset or map legacy data. Provider tests do not prove Omni request persistence, callback receiver or EMS writeback. Run the shared validation checklist on both applications with mock AI/EMS before enabling real integration.