# Policy Notification Contract · Risk Profile Lifecycle and Threshold Changes ## Purpose - Provide a stable payload/transport contract for notifying downstream systems when risk profiles are created, updated, activated/deactivated, or when scoring thresholds change. - Unblocks `POLICY-RISK-69-001` by supplying the “notifications contract” referenced in sprint planning. ## Event Types - `policy.profile.created` — new profile draft created. - `policy.profile.activated` — profile version activated for a tenant/scope. - `policy.profile.deactivated` — profile version retired or superseded. - `policy.profile.threshold_changed` — risk thresholds updated (any level). - `policy.profile.override_added` / `override_removed` — override lifecycle changes. - `policy.profile.simulation_ready` — simulation results available for consumption. ## Transport - Primary: Notifications service topic `notifications.policy.profiles` (tenant-scoped). - Alt: Webhook delivery using POST with `X-Stella-Tenant` and HMAC-SHA256 signature header `X-Stella-Signature` (hex digest over body with shared secret). - Idempotency: `event_id` is a UUIDv7; consumers must de-duplicate. ## Payload Schema (JSON) ```json { "event_id": "018f9a2e-8f7d-7fbb-9db4-9f9a3d9c4caa", "event_type": "policy.profile.threshold_changed", "emitted_at": "2025-12-07T12:00:00Z", "tenant_id": "tenant-123", "profile_id": "risk-profile-core", "profile_version": "3.2.0", "change_reason": "Updated high/critical thresholds per policy board decision", "actor": { "type": "user", "id": "alice@example.com" }, "thresholds": { "info": 0.1, "low": 0.25, "medium": 0.5, "high": 0.75, "critical": 0.9 }, "effective_scope": { "tenants": ["tenant-123"], "projects": ["proj-a", "proj-b"], "purl_patterns": ["pkg:npm/*"], "cpe_patterns": ["cpe:2.3:*:vendor:*:product:*:*:*:*:*:*:*"], "tags": ["prod", "pci"] }, "hash": { "algorithm": "sha256", "value": "b6c1d6c618a01f9fef6db7e6d86e3c57b1a2cc77ce88a7b7d8e8ac4c28e0a1df" }, "links": { "profile_url": "https://policy.example.com/api/risk/profiles/risk-profile-core", "diff_url": "https://policy.example.com/api/risk/profiles/risk-profile-core/diff?from=3.1.0&to=3.2.0", "simulation_url": "https://policy.example.com/api/risk/simulations/results/018f9a2e-8f7d-7fbb-9db4-9f9a3d9c4caa" }, "trace": { "trace_id": "4f2d1b7c6a9846a5b9a72f4c3ed1f2c1", "span_id": "9c4caa8f7d7fbb9d" } } ``` ## Validation Rules - `emitted_at` is UTC ISO-8601; ordering is deterministic by `(emitted_at, event_id)`. - `tenant_id` is required; `projects` optional but recommended for multi-project scopes. - `hash.value` MUST be the SHA-256 of the serialized risk profile bundle that triggered the event. - `links.*` SHOULD point to the canonical Policy Engine endpoints; omit if not reachable in air-gap. - Webhook delivery MUST include `X-Stella-Signature` = `hex(HMAC_SHA256(shared_secret, raw_body))`. ## CLI Consumption (sample output) Example consumption for downstream automation (captured from `policy notify tail`): ``` $ stella policy notify tail --topic notifications.policy.profiles --tenant tenant-123 --limit 1 event_id: 018f9a2e-8f7d-7fbb-9db4-9f9a3d9c4caa event_type: policy.profile.threshold_changed profile_id: risk-profile-core@3.2.0 thresholds: info=0.10 low=0.25 medium=0.50 high=0.75 critical=0.90 scope.tenants: tenant-123 scope.projects: proj-a, proj-b hash.sha256: b6c1d6c618a01f9fef6db7e6d86e3c57b1a2cc77ce88a7b7d8e8ac4c28e0a1df links.profile_url: https://policy.example.com/api/risk/profiles/risk-profile-core ``` ## Versioning - Version 1.0 frozen with this document; additive fields require minor version bump (`event_schema_version` header optional, default `1.0`). - Breaking changes require new event types or topic.