diff --git a/architecture/runtime-features/notifications.md b/architecture/runtime-features/notifications.md new file mode 100644 index 0000000..aa7729b --- /dev/null +++ b/architecture/runtime-features/notifications.md @@ -0,0 +1,601 @@ +# DCM Data Model — Notification Model + + +**Document Status:** ✅ Complete +**Document Type:** Architecture Reference + +> **Foundation Document Reference** +> +> This document is a detailed reference for a specific domain of the DCM architecture. +> The three foundational abstractions — Data, Provider, and Policy — are defined in +> [udlm/foundations/foundations.md](https://github.com/croadfeldt/udlm/blob/main/foundations/foundations.md). All concepts in this document map to one or +> more of those three abstractions. +> See also: [Provider Contract](https://github.com/croadfeldt/udlm/blob/main/contracts/provider-contract.md) | [Policy Contract](https://github.com/croadfeldt/udlm/blob/main/contracts/policy-contract.md) +> +> **This document maps to: PROVIDER + POLICY** +> +> Provider: notification service. Policy: audience resolution and subscription rules + + +**Related Documents:** [Webhooks, Messaging, and External Integration](webhooks-messaging.md) | [Entity Relationships](https://github.com/croadfeldt/udlm/blob/main/entities/entity-relationships.md) | [Resource/Service Entities](https://github.com/croadfeldt/udlm/blob/main/entities/resource-service-entities.md) | [Auth Providers](https://github.com/croadfeldt/udlm/blob/main/governance/auth-providers.md) | [Universal Audit](https://github.com/croadfeldt/udlm/blob/main/observability/universal-audit.md) + +--- + + +> **See [Event Catalog](https://github.com/croadfeldt/udlm/blob/main/contracts/event-catalog.md)** — authoritative source for all DCM event types and payload schemas. + +## 1. Purpose + +The DCM Notification Model defines a **unified, configurable notification pipeline** that routes event notifications to all parties with a stake in a changed resource — not just the original requestor. The audience for any notification is derived from the **entity relationship graph**, not from who submitted the original request. + +This document defines: +- The notification service — the ninth DCM provider type +- The event taxonomy — a closed vocabulary of notification-worthy events +- The audience resolution model — how the relationship graph determines who gets notified +- The subscription model — how actors declare their notification preferences +- The notification payload structure — the unified envelope all notification services receive +- The delivery pipeline — from event trigger through audience resolution through provider delivery + +Outbound webhooks are one delivery channel within this model, implemented via the notification service. + +--- + +## 2. Design Principles + +**Relationship graph determines audience.** When a resource changes, DCM traverses the entity relationship graph to find all stakeholders. A VLAN decommission notifies every VM attached to that VLAN, regardless of which Tenant owns each VM. The graph is the source of truth for notification scope. + +**Delivery mechanism is configurable, not prescribed.** DCM generates and routes notifications. How they are delivered — email, Slack, PagerDuty, ServiceNow, webhook, SMS — is the concern of a notification service. Organizations register the notification service(s) that fit their operations. + +**Three notification tiers.** Some notifications are mandatory and non-suppressable (security, sovereignty violations, audit chain breaks). Some are Tenant-default (all resource lifecycle events in a Tenant). Some are actor-subscription (specific events on specific resources). All three compose without conflict. + +**Audience role shapes the notification.** The same event produces different notifications for an owner ("your resource changed") versus a stakeholder ("a resource you depend on changed") versus an approver ("your approval is required"). The audience role is part of the notification envelope. + +**Delivery is audited.** Every notification dispatch is an audit record. Delivery failures are tracked and escalated per policy. + +--- + +## 3. The notification service + +The notification service is the ninth formal DCM provider type. It handles the translation from DCM's unified notification envelope to the delivery channel's native format, and it handles delivery, retry, and delivery confirmation. + +### 3.1 Provider Types Table Update + +| # | Type | Purpose | +|---|------|---------| +| 1 | Service Provider | Realizes resources | +| 2 | Information Provider | Serves authoritative external data | +| 3 | composite service definition | Composes multiple providers | +| 4 | data store | Persists DCM state | +| 5 | event routing service | Event streaming and messaging | +| 6 | External Policy Evaluator | External policy logic | +| 7 | credential management service | Resolves secrets | +| 8 | Auth Provider | Authenticates identities | +| **9** | **notification service** | **Delivers notifications via configured channels** | + +### 3.2 notification service Registration + +```yaml +service_provider_registration: + artifact_metadata: + uuid: + handle: "org/notifications/slack-provider" + version: "1.0.0" + status: active + owned_by: { display_name: "Platform Engineering" } + + provider_type: notification + display_name: "Slack notification service" + description: "Delivers DCM notifications to configured Slack channels" + + # Delivery channels this provider supports + delivery_channels: + - channel_type: slack + config_schema_ref: # JSON Schema for channel config + supports_threading: true + supports_urgency_routing: true # different channels per urgency level + - channel_type: webhook + config_schema_ref: + + # Sovereignty declaration — same model as all providers + sovereignty_declaration: + data_residency_guarantee: EU + operating_jurisdictions: [DE, FR, NL] + + # Delivery guarantees this provider offers + delivery_guarantees: + at_least_once: true + idempotency_key: notification_uuid + max_delivery_latency: PT30S # for critical urgency + retry_policy: + max_attempts: 7 + backoff: exponential + initial_interval: PT5S + max_interval: PT1H + on_exhaustion: dead_letter + + # Health check endpoint (on the provider) + health_endpoint: https://notif-provider.corp.example.com/health + + # Callback endpoint (DCM calls this to submit notifications) + delivery_endpoint: https://notif-provider.corp.example.com/deliver +``` + +### 3.3 Multiple notification services + +Organizations may register multiple notification services — one for Slack, one for PagerDuty, one for ServiceNow tickets. Notification subscriptions declare which provider to use for delivery. The Notification Router in DCM routes each notification to the correct provider based on the subscription's `service_provider_uuid`. + +--- + +## 4. The Event Taxonomy + +The notification event taxonomy is a **closed vocabulary** — a finite, versioned set of event types that DCM can generate notifications for. Events are grouped by category. Subscriptions reference these event types by name. + +### 4.1 Request Lifecycle Events + +| Event Type | Trigger | Default Audience | +|-----------|---------|-----------------| +| `request.acknowledged` | Request received, Intent State created | Owner | +| `request.requires_approval` | Policy requires human review before dispatch | Owner, Approvers | +| `request.approved` | Intent State PR merged; proceeding to assembly | Owner | +| `request.dispatched` | Requested State committed; dispatched to provider | Owner | +| `request.completed` | Provider confirmed realization; Realized State written | Owner | +| `request.failed` | Request failed at any stage | Owner | +| `request.cancelled` | Consumer cancelled; request terminated | Owner | +| `request.gating_rejected` | Gating policy rejected the request | Owner, Policy Owner | + +### 4.2 Resource Lifecycle Events + +| Event Type | Trigger | Default Audience | +|-----------|---------|-----------------| +| `entity.realized` | Entity first realized by provider | Owner, Stakeholders (depth 1) | +| `entity.state_changed` | Entity lifecycle state transition | Owner, Stakeholders (required) | +| `entity.ttl_warning` | TTL expires within declared warning window | Owner | +| `entity.ttl_expired` | TTL reached; expiry action triggered | Owner, Stakeholders (required) | +| `entity.suspended` | Entity entered SUSPENDED state | Owner, Stakeholders (required) | +| `entity.resumed` | Entity exited SUSPENDED state | Owner, Stakeholders (required) | +| `entity.decommissioning` | Decommission initiated | Owner, Stakeholders (all) | +| `entity.decommissioned` | Entity fully decommissioned | Owner, Stakeholders (all) | +| `entity.decommission_deferred` | Decommission blocked by active stakes | Owner, Stakeholders (required) | +| `entity.ownership_transferred` | Ownership moved to a different Tenant | Previous Owner, New Owner | +| `entity.pending_review` | Entity entered PENDING_REVIEW state | Owner, Platform Admin | + +### 4.3 Drift and Discovery Events + +| Event Type | Trigger | Default Audience | +|-----------|---------|-----------------| +| `drift.detected` | Discovered State differs from Realized State | Owner | +| `drift.severity_escalated` | Drift severity increased | Owner, Platform Admin | +| `drift.resolved` | Drift resolved (REVERT or UPDATE_DEFINITION) | Owner | +| `drift.escalated` | Drift escalated to human review | Owner, Platform Admin, SRE | +| `unsanctioned_change.detected` | Change detected with no corresponding Requested State record | Owner, Security Team, Platform Admin | + +### 4.4 Provider Update Events + +| Event Type | Trigger | Default Audience | +|-----------|---------|-----------------| +| `provider_update.submitted` | Provider submitted an update notification | Owner | +| `provider_update.requires_approval` | Provider update requires consumer approval | Owner (approval required) | +| `provider_update.approved` | Provider update approved; Realized State updated | Owner | +| `provider_update.rejected` | Provider update rejected; becomes drift | Owner, Provider Team | +| `provider_update.auto_approved` | Provider update auto-approved by pre-authorization policy | Owner (informational) | + +### 4.5 Dependency and Relationship Events + +| Event Type | Trigger | Default Audience | +|-----------|---------|-----------------| +| `dependency.state_changed` | A required dependency's state changed | Owner of dependent entity | +| `stakeholder.resource_decommissioning` | A shared resource the actor stakes is decommissioning | All stakeholders | +| `allocation.pool_capacity_low` | Allocation pool capacity below threshold | Pool Owner, Platform Admin | +| `allocation.released` | Allocation decommissioned; capacity returned to pool | Pool Owner | +| `cross_tenant_auth.expiring` | Cross-tenant authorization expiring | Both Tenant Admins | +| `cross_tenant_auth.revoked` | Cross-tenant authorization revoked while allocation active | Both Tenant Admins, Affected Resource Owners | + +### 4.6 Governance Events + +| Event Type | Trigger | Default Audience | +|-----------|---------|-----------------| +| `policy.activated` | Policy moved to active status | Platform Admin, Policy Owner | +| `policy.deactivated` | Policy deactivated | Platform Admin, Policy Owner | +| `external_policy_evaluation.trust_elevated` | External Policy Evaluator mode level elevated | Platform Admin, Security Team | +| `profile.changed` | Active deployment profile changed | Platform Admin, All Tenant Admins | +| `catalog_item.deprecated` | Catalog item deprecated | All consumers with active resources of that type | + +### 4.7 Security and System Events (Mandatory — Non-Suppressable) + +| Event Type | Trigger | Audience | +|-----------|---------|---------| +| `audit.chain_integrity_alert` | Hash chain verification failure detected | Security Team, Platform Admin | +| `sovereignty.violation` | Resource in violation of sovereignty constraints | Platform Admin, Security Team, Resource Owner | +| `sovereignty.migration_required` | Provider sovereignty change requires entity migration | Platform Admin, Resource Owner | +| `federation.tunnel_degraded` | DCM-to-DCM federation tunnel health degraded | Platform Admin, SRE | +| `auth.provider_failover` | Auth Provider failed over to secondary | Platform Admin | +| `rehydration.blocked` | Concurrent rehydration attempt rejected | Requesting Actor, Platform Admin | +| `security.unsanctioned_provider_write` | Attempted write to Realized Store without Requested State ref | Security Team, Platform Admin | + +--- + +## 5. Audience Resolution — The Relationship Graph Model + +### 5.1 The Fundamental Rule + +**The audience for a notification is every entity with a stake in the changed resource, resolved by traversing the relationship graph from the changed entity.** + +The notification system does not maintain a separate subscriber list per entity. It derives the audience at event time by traversing the relationship graph. This means the audience is always current — adding a new VM attachment to a VLAN automatically includes that VM's owner in future VLAN notifications, without any subscription update required. + +### 5.2 Audience Resolution Algorithm + +``` +Event fires on entity E (e.g., VLAN-100 decommissioning) + │ + ▼ Step 1: Resolve direct owner + │ entity.owned_by_tenant_uuid → Tenant Admin and resource owner actors + │ Audience role: owner + │ + ▼ Step 2: Traverse relationship graph + │ For each relationship on entity E: + │ Check: is this relationship type notification-relevant for this event type? + │ Check: does the relationship's stake_strength meet the minimum for this event? + │ If yes: resolve the related entity's owner → add to audience + │ Audience role: stakeholder + │ + ▼ Step 3: Check for approval requirements + │ Does this event require approval from a specific actor? + │ If yes: add approver to audience + │ Audience role: approver + │ + ▼ Step 4: Apply mandatory system audiences + │ Security events: always include Security Team and Platform Admin + │ Governance events: always include Policy Owner and Platform Admin + │ (These cannot be filtered out by subscription preferences) + │ + ▼ Step 5: Apply actor subscription overrides + │ Actors with explicit subscriptions to this event type → include/exclude per subscription + │ (Subscriptions can add additional audience; they cannot remove mandatory audiences) + │ + ▼ Step 6: Deduplicate and resolve contact details + │ Same actor via multiple paths → one notification with all audience_roles listed + │ Resolve each actor to their configured notification channels + │ + ▼ Step 7: Route to notification service(s) + One notification per actor per configured channel + Notification envelope includes audience_role +``` + +### 5.3 Relationship Notification Relevance + +The Resource Type Specification declares which relationship types are notification-relevant and for which events: + +```yaml +resource_type_spec: + fqn: Network.VLAN + notification_rules: + - event_type: entity.decommissioning + notify_relationships: + - relationship_type: attached_to # source direction (VMs attached to this VLAN) + min_stake_strength: required # only required stakes get notified + traversal_depth: 1 # direct relationships only + audience_role: stakeholder + - relationship_type: attached_to + min_stake_strength: optional # optional stakes get informational notice + traversal_depth: 1 + audience_role: observer + + - event_type: entity.state_changed + notify_relationships: + - relationship_type: attached_to + min_stake_strength: required + traversal_depth: 1 + audience_role: stakeholder +``` + +**Traversal depth:** `1` means direct relationships only. `2` means relationships of related entities. In most cases `1` is correct — deeper traversal is reserved for critical security events that affect the entire graph. + +### 5.4 Cross-Tenant Notification + +Notification traversal follows relationship graphs across Tenant boundaries. If a VM in AppTeam Tenant has a `required` stake in a VLAN owned by NetworkOps Tenant, and the VLAN is decommissioned, AppTeam receives a stakeholder notification — even though the VLAN belongs to a different Tenant. + +Cross-tenant notifications are governed by the same sovereignty rules as cross-tenant data access: +- Notification content is limited to what the receiving Tenant is authorized to know +- The notification identifies the changed resource but does not expose the owning Tenant's configuration details +- Sovereignty checks apply to notification delivery (a notification about an EU-sovereign resource cannot be delivered to a US-based endpoint) + +```yaml +notification_sovereignty_check: + # Before delivering cross-tenant notification: + check: + - receiver_tenant_sovereignty_compatible: true + - notification_content_authorized_for_receiver: true + - delivery_endpoint_jurisdiction_compatible: true + on_failure: redact_and_deliver # or: suppress_with_audit | block_with_alert +``` + +--- + +## 6. Notification Subscriptions + +### 6.1 Three Subscription Tiers + +**Tier 1 — Mandatory System Notifications (non-suppressable):** +Security events, sovereignty violations, audit chain breaks. Always delivered to the declared system audiences (Security Team, Platform Admin) regardless of any subscription configuration. No actor or policy can suppress these. + +**Tier 2 — Tenant Default Notifications:** +Configured by Tenant admins for all resources in their Tenant. Establishes the baseline notification behavior — which events trigger notifications, which channels to use, and which urgency mapping to apply. + +```yaml +tenant_notification_defaults: + tenant_uuid: + service_provider_uuid: + + default_channel_config: + channel_type: slack + workspace: "corp" + urgency_routing: + critical: "#platform-incidents" + high: "#platform-alerts" + medium: "#platform-notifications" + low: "#platform-digest" # batched hourly + + # Which event categories are enabled by default for all resources in this Tenant + enabled_event_categories: + request_lifecycle: [request.completed, request.failed, request.gating_rejected] + resource_lifecycle: [entity.state_changed, entity.ttl_warning, entity.decommissioning] + drift: [drift.detected, unsanctioned_change.detected] + provider_update: [provider_update.requires_approval, provider_update.rejected] + dependency: [stakeholder.resource_decommissioning, cross_tenant_auth.revoked] + + # Urgency defaults per event type + urgency_overrides: + unsanctioned_change.detected: critical + drift.detected: high + entity.ttl_warning: medium + request.completed: low +``` + +**Tier 3 — Actor-Level Subscriptions:** +Individual actors subscribe to specific events on specific resources or resource types. Most useful for service accounts (CI/CD pipelines, monitoring tools) that need targeted event feeds. + +```yaml +actor_notification_subscription: + subscription_uuid: + actor_uuid: + service_provider_uuid: + + channel_config: + channel_type: pagerduty + service_id: "payments-api-on-call" + escalation_policy_id: "payments-prod" + + subscriptions: + # Subscribe to all drift events on VMs in AppTeam Tenant + - scope: + tenant_uuid: + resource_type: Compute.VirtualMachine + events: [drift.detected, unsanctioned_change.detected] + urgency_override: high + + # Subscribe to decommission of a specific VLAN I depend on + - scope: + entity_uuid: + events: [entity.decommissioning, entity.decommissioned] + urgency_override: critical +``` + +### 6.2 Subscription Composition Rules + +When multiple subscription tiers match for the same actor and event: +- Mandatory system notifications always fire (cannot be suppressed) +- Tenant defaults fire unless the actor's subscription explicitly opts out for that event type +- Actor subscriptions can add additional channels or override urgency — they do not suppress Tenant defaults unless the subscription explicitly declares `suppress_tenant_default: true` +- Deduplication: if the same notification would be delivered to the same actor via two channels from two subscription matches, deliver once per channel (not once per subscription match) + +--- + +## 7. Notification Payload — The Unified Envelope + +Every notification delivered to a notification service uses this unified envelope. The notification service translates it to the delivery channel's native format. + +```yaml +notification: + # Identity + notification_uuid: # idempotency key + correlation_id: # links to the audit record for the triggering event + generated_at: + + # The event + event_type: entity.decommissioning # from the closed taxonomy + event_uuid: # the triggering event's UUID + urgency: + + # The subject entity + entity: + uuid: + handle: + resource_type: Network.VLAN + display_name: "VLAN-100 (EU-WEST Production)" + tenant_uuid: + tenant_display_name: "NetworkOps" + + # Audience context + audience: + actor_uuid: + actor_display_name: "Jane Smith" + audience_role: + # stakeholder: explains WHY this actor is in the audience + stakeholder_reason: + via_entity_uuid: # "because your VM-A is attached to this VLAN" + via_relationship_type: attached_to + via_entity_display_name: "VM-A (payments-api-server-01)" + + # What changed + context: + previous_state: OPERATIONAL + new_state: DECOMMISSIONING + change_summary: "VLAN-100 decommission initiated by NetworkOps team" + changed_fields: [] + changed_by: + actor_uuid: + actor_display_name: "Bob Jones (NetworkOps)" + effective_at: + + # Action required (if any) + requires_action: false + action: + type: null # approve | acknowledge | migrate | release_stake + description: null + action_url: null + deadline: null + + # Deep links + links: + entity_url: "https://dcm.corp.example.com/resources/" + event_url: "https://dcm.corp.example.com/audit/" + related_entities: + - uuid: + display_name: "VM-A (payments-api-server-01)" + url: "https://dcm.corp.example.com/resources/" +``` + +### 7.1 Urgency Mapping + +| Urgency | Meaning | Typical delivery target | +|---------|---------|------------------------| +| `critical` | Immediate action required; outage or security risk imminent | On-call pager, incident channel | +| `high` | Action required; significant impact if not addressed | Alert channel, SRE queue | +| `medium` | Action recommended; non-urgent but should not be ignored | Notification channel, daily digest | +| `low` | Informational; no action required | Digest, async channel | + +Default urgency per event type is declared in the event taxonomy. Tenant defaults and actor subscriptions may override upward or downward. + +--- + +## 8. The Delivery Pipeline + +``` +Event fires (e.g., VLAN-100 enters DECOMMISSIONING state) + │ + ▼ Stage 1: Audit record written (Stage 1 Commit Log — synchronous) + │ ENTITY_STATE_CHANGED audit record committed + │ event_uuid assigned + │ + ▼ Stage 2: Notification Router evaluates + │ Load entity relationship graph for VLAN-100 + │ Run audience resolution algorithm (Section 5.2) + │ Result: [AppTeam, DevTeam, OpsTeam] as stakeholders; [NetworkOps] as owner + │ + ▼ Stage 3: Subscription resolution + │ For each audience member: + │ Resolve Tier 1 mandatory notifications + │ Apply Tier 2 Tenant defaults + │ Apply Tier 3 actor subscriptions + │ Determine: which notification service(s); which channel config; urgency + │ + ▼ Stage 4: Notification envelope generation + │ One envelope per audience member per delivery + │ Audience role set correctly (owner / stakeholder / approver / observer) + │ Stakeholder reason populated for non-owners + │ + ▼ Stage 5: Route to notification service(s) + │ POST to provider delivery endpoint with notification envelope + │ Provider translates to delivery channel (Slack, PagerDuty, email, etc.) + │ Provider returns delivery_uuid and status + │ + ▼ Stage 6: Delivery confirmation + │ Provider reports: delivered | failed | queued + │ Delivery record written to Notification Delivery Store + │ NOTIFICATION_DISPATCHED audit record written (async) + │ + ▼ Stage 7: Failure handling + On provider delivery failure: + Retry per provider's declared retry policy + On exhaustion: dead_letter to platform admin + On critical urgency exhaustion: escalate immediately + NOTIFICATION_DELIVERY_FAILED audit record written +``` + +### 8.1 Notification Delivery Store + +A lightweight store (not the Audit Store) tracking delivery status per notification: + +```yaml +notification_delivery_record: + delivery_uuid: + notification_uuid: + actor_uuid: + service_provider_uuid: + channel_type: slack + status: + dispatched_at: + delivered_at: + failure_reason: + retry_count: 2 +``` + +--- + +## 9. Provider Update Notification Integration + +Provider Update Notifications (doc 06, Section 7a) integrate with the notification model at two points: + +**When provider submits update notification:** +- `provider_update.submitted` fires → Owner notified (informational) + +**When provider update requires consumer approval:** +- `provider_update.requires_approval` fires → Owner notified (action required) +- `action.type: approve` +- `action.action_url` points to `/api/v1/resources/{uuid}/provider-notifications/{uuid}:approve` +- `action.deadline` set per policy (default PT24H — if no response, escalate) + +**On resolution:** +- Approved → `provider_update.approved` fires → Owner notified; Stakeholders notified of state change via `entity.state_changed` +- Rejected → `provider_update.rejected` fires → Owner notified; becomes drift event → `drift.detected` fires + +--- + +## 10. Relationship to Webhooks and Message Bus + +### 10.1 Webhooks as a Notification Channel + +Outbound webhooks (doc 18) are now **one delivery channel type within the notification service model** rather than a parallel mechanism. A notification service with `channel_type: webhook` delivers notifications to configured HTTP endpoints using the unified notification envelope. + +The webhook registration model (doc 18, Section 3.2) is implemented as actor-level subscriptions (Section 6.1, Tier 3) with a webhook-type notification service. + +### 10.2 Message Bus as Notification Infrastructure + +The event routing service (doc 18, Section 5) is the **internal transport** for the notification pipeline. The Notification Router publishes notification events to the Message Bus. notification services subscribe to their assigned topics. This decouples event generation from delivery and enables high-throughput notification processing. + +``` +DCM Event → Notification Router → Message Bus → notification service subscription +``` + +The Message Bus is infrastructure — not a notification channel. Consumers do not subscribe to the Message Bus directly for notifications; they use the subscription model (Section 6.1). + +--- + +## 11. System Policies + +| Policy | Rule | +|--------|------| +| `NOT-001` | The audience for every notification is derived from the entity relationship graph at event time. DCM does not maintain static subscriber lists per entity. | +| `NOT-002` | Mandatory system notifications (Tier 1: security, sovereignty, audit chain) are never suppressable by any subscription configuration or policy. | +| `NOT-003` | Cross-tenant notifications carry only information the receiving Tenant is authorized to see. Sovereignty checks apply to notification delivery endpoints. | +| `NOT-004` | Every notification dispatch is an audit record. Delivery failures are tracked. Critical urgency delivery exhaustion triggers immediate escalation to Platform Admin. | +| `NOT-005` | Provider Update Notifications that require consumer approval carry `action.type: approve` and `action.deadline`. If the deadline passes without resolution, the notification escalates per policy. | +| `NOT-006` | Notification traversal depth is bounded. Resource Type Specifications declare the maximum traversal depth for each event type. Default: depth 1 (direct relationships only). | +| `NOT-007` | A notification service must be registered and active before notifications can be delivered. DCM does not have a built-in delivery channel — at minimum a webhook-type notification service must be configured for external delivery. | +| `NOT-008` | The notification event taxonomy is a closed vocabulary. Custom event types are not supported. New event types require a DCM registry proposal following standard governance. | + +--- + +## 12. Related Concepts + +- **notification service** — the ninth DCM provider type; handles translation and delivery +- **Notification Router** — DCM control plane component that resolves audiences and routes to providers +- **Audience Resolution** — deriving notification recipients from the entity relationship graph +- **Notification Subscription** — actor or Tenant declaration of notification preferences +- **Notification Delivery Store** — lightweight store tracking delivery status +- **Provider Update Notification** — formal provider mechanism for reporting authorized state changes (see doc 06, Section 7a) +- **Outbound Webhook** — one delivery channel type within the notification service model + +--- + +*Document maintained by the DCM Project. For questions or contributions see [GitHub](https://github.com/dcm-project).* diff --git a/architecture/runtime-features/webhooks-messaging.md b/architecture/runtime-features/webhooks-messaging.md new file mode 100644 index 0000000..7067119 --- /dev/null +++ b/architecture/runtime-features/webhooks-messaging.md @@ -0,0 +1,825 @@ +# DCM Data Model — Webhooks, Messaging, and External Integration + + +**Document Status:** ✅ Complete +**Related Documents (updated):** [Notification Model](notifications.md) | [Entity Relationships](https://github.com/croadfeldt/udlm/blob/main/entities/entity-relationships.md) + +> **Foundation Document Reference** +> +> This document is a detailed reference for a specific domain of the DCM architecture. +> The three foundational abstractions — Data, Provider, and Policy — are defined in +> [udlm/foundations/foundations.md](https://github.com/croadfeldt/udlm/blob/main/foundations/foundations.md). All concepts in this document map to one or +> more of those three abstractions. +> See also: [Provider Contract](https://github.com/croadfeldt/udlm/blob/main/contracts/provider-contract.md) | [Policy Contract](https://github.com/croadfeldt/udlm/blob/main/contracts/policy-contract.md) +> +> **This document maps to: PROVIDER** +> +> The Provider abstraction — Message Bus and webhook delivery channels + + +**Related Documents:** [Universal Audit Model](https://github.com/croadfeldt/udlm/blob/main/observability/universal-audit.md) | [Deployment and Redundancy](deployment-redundancy.md) | [Authentication and Authorization](https://github.com/croadfeldt/udlm/blob/main/governance/auth-providers.md) | [Policy Organization](../governance-enforcement/policy-profiles.md) + +--- + + +> **See [Event Catalog](https://github.com/croadfeldt/udlm/blob/main/contracts/event-catalog.md)** — authoritative source for all DCM event types and payload schemas. + +## 1. Purpose + +DCM communicates with the outside world through three complementary mechanisms: + +- **Outbound Webhooks** — DCM pushes event notifications to external HTTP endpoints +- **Inbound Webhooks** — External systems push requests, queries, and events to DCM HTTP endpoints +- **event routing services** — DCM integrates with external message buses for persistent, high-throughput bidirectional event streaming + +All three mechanisms are authenticated, authorized, and audited identically to any other DCM API call. There is no privileged back-channel. Every integration is a registered DCM actor subject to full Policy Engine evaluation. + +**Webhooks are optional and policy-governed.** The active Profile sets defaults — `fsi` and `sovereign` profiles may require webhook or message bus coverage for audit events. `minimal` and `dev` profiles make them fully optional. + +--- + +## 2. Ingress and Egress — The Universal Actor Model + +### 2.1 The `ingress` Block + +Every request entering DCM carries an immutable `ingress` block set by the DCM ingress layer before Policy Engine evaluation. It is never consumer-declarable and never modifiable by policies — policies may only read it. + +```yaml +ingress: + # HOW it arrived + surface: + protocol: + authenticated_via: + authorized_via: policy_engine + + # WHO sent it — fully resolved actor context + actor: + uuid: + type: + display_name: "Jane Smith" + identity_source: + auth_provider_uuid: # which Auth Provider authenticated + auth_provider_type: + + # Resolved DCM roles and scope (at authentication time) + roles: [sre] + tenant_scope: [] + groups: [] + permissions: [request.submit, query.entity_state] + + # Authorization chain + authorized_by: + method: + authorizing_entity_uuid: + authorization_timestamp: + expiry: + + # Session context (human actors) + session_uuid: + session_started_at: + mfa_verified: + + # External identity (federated actors) + external_identity: + provider: + subject: "uid=jsmith,cn=users,cn=accounts,dc=corp,dc=example,dc=com" + claims: + email: jsmith@corp.example.com + display_name: "Jane Smith" + department: Engineering + cost_center: CC-1234 + ldap_groups: [cn=dcm-sre,...] + sid: "S-1-5-21-..." # AD Security Identifier + + # Rate limit tracking + rate_limit_bucket: + + # Surface-specific detail + webhook_registration_uuid: # if surface: webhook_inbound + (optional infrastructure)_uuid: # if surface: message_bus_inbound + message_offset: # if surface: message_bus_inbound + scheduler_job_uuid: # if surface: scheduler + parent_request_uuid: # if surface: policy_engine|rehydration + source_ip: +``` + +### 2.2 The `egress` Block + +All outbound calls from DCM carry DCM's authenticated identity: + +```yaml +egress: + surface: + protocol: + actor: + uuid: + type: dcm_internal + component: + authenticated_via: + credential_ref: + service_provider_uuid: + secret_path: + originating_request_uuid: + originating_actor_uuid: +``` + +### 2.3 Policy Engine Use Cases — Ingress/Egress Fields + +The ingress block enables a rich class of governance rules: + +```yaml +# Require specific auth for sensitive operations +policy: "If action == decommission AND ingress.actor.mfa_verified == false THEN gate" + +# Block legacy API keys from production Tenants +policy: "If tenant.profile == prod AND ingress.actor.identity_source == static_api_key THEN gate" + +# Require enterprise auth for security resources +policy: "If resource_type IN [Network.FirewallRule] AND ingress.actor.auth_provider_type NOT IN [oidc, freeipa, active_directory] THEN gate" + +# Enrich from OIDC claims +policy: "If ingress.actor.external_identity.claims.department EXISTS THEN inject: business_context.department" + +# Block message bus inbound from non-service-accounts +policy: "If ingress.surface == message_bus_inbound AND ingress.actor.type != webhook_service_account THEN gate" + +# Sovereignty check on inbound message bus +policy: "If ingress.surface == message_bus_inbound AND (optional infrastructure).jurisdiction != tenant.sovereignty_zone THEN gate" +``` + + +### 2.5 Ingress API vs Consumer API — Relationship Clarification + +These two terms refer to different architectural layers: + +**Ingress API (infrastructure layer):** +The network-level entry point for all inbound requests to the DCM control plane. It handles: +- TLS termination +- Authentication token validation +- Setting the immutable `ingress` block on every request (surface, actor, timestamp, mfa_verified) +- Rate limiting at the network level +- Routing to the appropriate internal component (Consumer API handlers, Provider API handlers, Admin API handlers) + +The Ingress API is infrastructure — it is not directly defined in any consumer-facing specification. + +**Consumer API (application layer):** +The logical REST API surface that consumers interact with, as defined in the [Consumer API Specification](../../docs/specifications/consumer-api-spec.md). The Consumer API is *served through* the Ingress API. When a consumer calls `POST /api/v1/requests`, that request enters through the Ingress API (which sets the ingress block) and is then handled by the Consumer API component. + +**Other APIs served through the Ingress API:** +- **Provider API** — the callback and notification endpoints that Service Providers call (`/api/v1/provider/...`) +- **Admin API** — platform administration operations (`/api/v1/admin/...`) +- **Webhook Inbound** — external systems calling DCM (`/api/v1/webhooks/...`) + +**The Ingress API is one, the Consumer API is one of several logical surfaces routed through it.** + +--- + +### 2.6 Consumer Rate Limiting and Quota Model + +Consumer-side rate limiting and resource quotas are enforced by Gating policies — not hardcoded limits. This keeps quota enforcement consistent with DCM's policy-driven model. + +**Request rate limiting (per actor):** + +```yaml +rate_limit_policy: + type: gating + handle: "system/quotas/api-rate-limit" + trigger: request.initiated + conditions: + - field: ingress.actor_uuid + rate_window: PT1M + max_requests: 60 # configurable per Tenant policy + action: reject + rejection_code: 429 + rejection_message: "Rate limit exceeded. Retry after PT1M." +``` + +**Resource quotas (per Tenant per resource type):** + +```yaml +quota_policy: + type: gating + handle: "tenant/payments/vm-quota" + trigger: request.initiated + conditions: + - field: request.resource_type + equals: Compute.VirtualMachine + - field: tenant.active_entity_count + resource_type: Compute.VirtualMachine + operator: gte + value: 100 # max 100 concurrent VMs for this Tenant + action: reject + rejection_message: "VM quota exceeded (100). Request a quota increase via the Admin API." +``` + +**Quota increase process:** Tenants request quota increases through the standard request process. A quota change request produces a Requested State record, goes through policy evaluation, and requires Platform Admin approval for significant increases. + +**Profile-governed defaults:** + +| Profile | Default API rate limit | Default resource quota | +|---------|----------------------|----------------------| +| minimal | 10 req/min | Unlimited | +| dev | 60 req/min | Unlimited | +| standard | 60 req/min | Policy-declared | +| prod | 120 req/min | Policy-declared | +| fsi | 60 req/min | Strict policy-declared | +| sovereign | 30 req/min | Strict policy-declared | + + +### 2.4 System Policies + +| Policy | Rule | +|--------|------| +| `ING-008` | All DCM requests must carry an ingress block. The ingress block is set by the DCM ingress layer and is immutable — policies may read but not modify it. | +| `ING-009` | The ingress block must include a fully resolved actor context: uuid, type, identity_source, roles, tenant_scope, auth_provider_uuid, and authorized_by chain. | +| `ING-010` | All egress calls from DCM must carry DCM's authenticated identity. Unauthenticated egress is rejected. | +| `ING-011` | Authentication enforcement is profile-governed. Standard and above reject unauthenticated requests. Minimal and dev support lightweight authenticated modes. There is no anonymous access in any profile. | +| `ING-012` | Webhook and message bus inbound surfaces always require authentication regardless of active Profile. | +| `ING-013` | Rate limiting is enforced per registered actor. Exceeding rate limits returns 429 Too Many Requests. | + +--- + +## 3. Outbound Webhooks + +> **⚠️ Architecture Update — Notification Model Supersedes Outbound Webhooks** +> +> The outbound webhook model described in Section 3 has been one delivery channel within the Notification Model. Outbound webhooks are now one delivery channel type within the notification service model rather than a parallel mechanism. +> +> **For new implementations:** Use the notification service subscription model (doc 23, Section 6) with a webhook-type notification service. +> +> +> The key improvement in the new model: audience is derived from the **entity relationship graph**, not from a manually maintained subscriber list. A webhook subscription for VLAN drift events will now automatically include all VMs attached to that VLAN as audience context. + +### 3.1 Concept + +DCM pushes event notifications to registered external HTTP endpoints. Outbound webhooks are **optional and policy-governed** — the active Profile and Policy Groups determine which events require external notification. + +### 3.2 Webhook Registration + +```yaml +webhook_registration: + artifact_metadata: + uuid: + handle: "org/webhooks/payments-drift-alerts" + version: "1.0.0" + status: active + owned_by: + display_name: "Payments Platform Team" + notification_endpoint: + + name: "Payments Platform Drift Alerts" + description: "Notifies payments team of drift detection events" + + # SCOPE + scope: + type: + tenant_uuid: + cross_tenant_authorization_uuid: # if cross_tenant + + # EVENT SUBSCRIPTIONS + event_subscriptions: + - event_type: drift.detected + schema_version: "1.0" + adapter: true # DCM transforms newer schemas to 1.0 + filter: + tenant_uuid: + resource_types: [Compute.VirtualMachine, Storage.Block] + - event_type: request.realized + schema_version: "1.0" + - event_type: entity.state_transition + schema_version: "1.0" + filter: + to_states: [DEGRADED, FAILED] + + # ENDPOINT + endpoint: + url: https://alerts.payments.corp.example.com/dcm/events + sovereignty_check: true # verify endpoint jurisdiction before delivery + + # AUTHENTICATION + authentication: + mode: + secret_ref: + service_provider_uuid: + secret_path: "dcm/webhooks/payments-drift/hmac-secret" + rotation_policy: + automatic: true + interval: P90D + transition_window: P7D + notify_before: P14D + + # RELIABILITY + retry_policy: + max_attempts: 7 + backoff: exponential + initial_interval: PT5S + max_interval: PT1H + timeout_per_attempt: PT10S + on_exhaustion: dead_letter # dead_letter | discard | escalate + + # HEALTH + health: + failure_threshold: 10 + suspension_notification: true + auto_deactivate_after: P30D + status: active + + # SCHEMA COMPATIBILITY + schema_adapter: + enabled: true + # DCM maintains forward-compatibility adapters per schema version + # Consumer stays on declared schema_version indefinitely + deprecation_notice_days: 90 +``` + +### 3.3 Event Taxonomy + +> The table below is the event taxonomy for webhook subscriptions. + +The event taxonomy maps onto the Universal Audit action vocabulary. All are versioned registry entries: + +| Category | Events | +|----------|--------| +| Entity lifecycle | `entity.created`, `entity.modified`, `entity.state_transition`, `entity.deleted`, `entity.expired`, `entity.rehydrated` | +| Group | `group.member_added`, `group.member_removed`, `group.created`, `group.deleted` | +| Relationship | `relationship.created`, `relationship.released` | +| Policy | `policy.activated`, `policy.deactivated`, `policy.evaluated` (fail/gate only), `policy.shadow_result` | +| Provider | `provider.healthy`, `provider.degraded`, `provider.unhealthy`, `provider.registered`, `provider.deregistered` | +| Audit/security | `audit.chain_break`, `audit.forward_failed` | +| Drift | `drift.detected`, `drift.resolved`, `drift.escalated` | +| Request | `request.submitted`, `request.approved`, `request.rejected`, `request.realized`, `request.failed` | +| Rehydration | `rehydration.started`, `rehydration.completed`, `rehydration.paused`, `rehydration.interrupted` | +| Authorization | `authorization.granted`, `authorization.revoked` | +| Webhook | `webhook.secret_rotated`, `webhook.suspended`, `webhook.schema_deprecated` | + +### 3.4 Payload Format + +```yaml +webhook_payload: + # Envelope + event_uuid: # idempotency key + event_type: drift.detected + event_schema_version: "1.0" + timestamp: # from Stage 1 Commit Log — authoritative + dcm_version: + + # Subject + subject: + entity_uuid: + entity_type: infrastructure_resource + entity_handle: + tenant_uuid: + + # Delta + delta: + drifted_fields: + - field: cpu_count + realized_value: 4 + discovered_value: 8 + drift_severity: significant + + # Links + links: + self: + audit_record: +``` + +### 3.5 Delivery Guarantees + +- **At-least-once** — not exactly-once; consumers must be idempotent using `event_uuid` +- **Per-entity ordering** — events for a given `entity_uuid` delivered in Commit Log sequence order +- **Cross-entity ordering** — not guaranteed; use `timestamp` for actual occurrence time +- **Sovereignty-aware** — delivery blocked if endpoint jurisdiction incompatible with Tenant sovereignty (WHK-004) + +--- + +## 4. Inbound Webhooks + +### 4.1 Concept + +DCM exposes authenticated HTTP endpoints that external systems call to submit requests, queries, and events. Inbound webhooks are subject to full Policy Engine evaluation — identical to any other API call. + +### 4.2 Inbound Endpoints + +| Endpoint | Purpose | +|----------|---------| +| `POST /webhooks/inbound/request` | Submit a service request | +| `POST /webhooks/inbound/query` | Query entity state or catalog | +| `POST /webhooks/inbound/event` | Push an event (provider state change, CI/CD signal) | +| `POST /webhooks/inbound/ingestion` | Push brownfield ingestion data | +| `POST /webhooks/inbound/data` | Push enrichment or information data | + +### 4.3 Webhook Actor Registration + +Every inbound webhook caller must be registered as a **webhook actor** — a service account in the DCM identity model: + +```yaml +webhook_actor: + artifact_metadata: + uuid: + handle: "actors/webhook/cicd-pipeline-prod" + status: active + + name: "CI/CD Pipeline Production" + actor_type: webhook_service_account + + # Authentication + authentication: + mode: hmac_sha256 + secret_ref: + service_provider_uuid: + secret_path: "dcm/webhooks/inbound/cicd-pipeline/hmac" + + # Authorization + role: consumer + tenant_scope: [] + permitted_operations: + - request.submit + - query.entity_state + - query.catalog + + # Rate limiting + rate_limit: + requests_per_minute: 60 + burst: 10 + + # Audit identity + audit_identity: + display_name: "CI/CD Pipeline (Production)" + system: "jenkins-prod-01" +``` + +### 4.4 Response Model + +- **Queries** — synchronous response with result +- **Requests and events** — `202 Accepted` + `request_uuid`; caller polls status or registers outbound webhook for completion notification + +--- + +## 5. event routing service + +### 5.1 Concept + +A **event routing service** is the sixth DCM provider type — a persistent, high-throughput integration with an external message bus for bidirectional event streaming. Where webhooks are point-to-point HTTP calls, a event routing service is a durable pub/sub connection. + +### 5.2 Registration + +```yaml +(optional infrastructure)_registration: + artifact_metadata: + uuid: + handle: "providers/messagebus/corporate-kafka" + version: "1.0.0" + status: active + + name: "Corporate Kafka Cluster" + provider_type: message_bus + + # Direction + direction: + + # Protocol + protocol: + + # Connection + connection: + brokers: [kafka-1.corp:9093, kafka-2.corp:9093, kafka-3.corp:9093] + credentials_ref: + service_provider_uuid: + secret_path: "dcm/providers/messagebus/corporate-kafka/credentials" + tls: + mode: mtls + ca_cert_ref: + service_provider_uuid: + secret_path: "dcm/providers/messagebus/corporate-kafka/ca-cert" + + # Outbound — DCM publishes to external bus + outbound: + topic_mapping: + entity.state_transition: "dcm.entities.state" + drift.detected: "dcm.drift.alerts" + request.realized: "dcm.requests.completed" + audit.chain_break: "dcm.security.alerts" + schema_version: "1.0" + delivery_guarantee: at_least_once + + # Inbound — DCM consumes from external bus + inbound: + consumer_group: "dcm-inbound-prod" + topic_mapping: + "cicd.deployment.completed": request.submit + "cmdb.discovery.update": ingestion.push + "itsm.change.approved": request.approve + # Inbound messages processed as authenticated API calls + actor_identity_uuid: + # Same Policy Engine evaluation as inbound webhooks + + # Sovereignty + operational_sovereignty: + jurisdiction: eu-west + certifications: [ISO-27001, GDPR-compliant] + + # Health + health_check: + interval_seconds: 30 + on_unhealthy: alert +``` + +### 5.3 Architecture + +``` +DCM internal Message Bus (internal pub/sub backbone) + │ + ├── Webhook Delivery Service ──────→ External HTTP endpoints (outbound webhooks) + │ + └── Message Bus Bridge Service ────→ External message bus (event routing service) + ←─── External message bus (inbound) +``` + +The internal Message Bus is never exposed directly. All external event integration goes through either the Webhook Delivery Service or the Message Bus Bridge Service — both of which handle authentication, authorization, sovereignty checks, and schema transformation. + +--- + +## 6. Git PR Ingress — Distributed Git Request Mechanism + +### 6.1 Concept + +DCM supports **git_pr_merge** as a twelfth ingress surface — enabling teams to submit DCM resource definitions as Pull Requests to a DCM-watched Git repository. This is the native workflow for infrastructure-as-code teams: open a PR, get it reviewed by humans and DCM's policy engine simultaneously, merge to execute. + +**Why Git PR ingress matters:** +- GitOps teams work in Git — their deployment workflow is already PR-based +- Security and compliance teams review infrastructure changes the same way they review code +- The PR itself is the human review record; DCM's audit trail captures the automated processing +- Rollback is a Git revert — natural and familiar +- Multi-team approval workflows use existing Git branch protection rules +- The PR diff shows exactly what changes — field-level visibility + +### 6.2 The Git Request Watcher + +A dedicated control plane component — the **Git Request Watcher** — monitors designated repositories via webhooks (preferred) or polling. It is policy-governed: which repositories it watches, which branches trigger processing, and which resource types may be submitted via Git PR. + +### 6.3 Request Repository Structure + +``` +dcm-requests/ ← DCM-watched request repository + {tenant-uuid}/ + pending/ + {resource-handle}/ + request.yaml ← Standard DCM resource definition + realized/ + {resource-handle}/ + realized.yaml ← DCM writes realized state here on success + failed/ + {resource-handle}/ + request.yaml ← Moved here on failure with error detail +``` + +### 6.4 Git Actor Identity Resolution + +**Authentication is always required.** Git PR ingress actors must be resolved to DCM actors through the registered Auth Provider — DCM trusts the Git server's authentication assertion, not user-declared Git configuration. Anyone can set their local `git config user.email` to anything; DCM ignores self-declared identity. + +**The trust chain:** +``` +Git server authenticates user (SSH key, OAuth token, password) + │ Git server's authentication is trusted — not user's claimed identity + ▼ +DCM Git Request Watcher receives PR merge webhook + │ Webhook payload contains: actor.login, actor.auth_method, actor.external_id + │ All verified by the Git server + ▼ +Auth Provider resolution (same path as web UI login for the same user): + ├── OIDC/OAuth: Git server OAuth subject → OIDC Auth Provider userinfo lookup + ├── LDAP/AD: Git server username → LDAP lookup → DCM actor + ├── SSH key: Git server key fingerprint → DCM SSH key registry → DCM actor + └── Service account: Git service account → registered webhook actor + ▼ +Fully resolved DCM actor — same roles, groups, tenant scope as any other user +``` + +**Resolution methods:** + +```yaml +git_actor_resolution: + # Method 1: OIDC/OAuth (recommended — Git server uses same IdP as DCM) + method: oidc_subject_lookup + auth_provider_uuid: + + # Method 2: LDAP/AD (enterprise — Git server authenticates via corporate directory) + method: ldap_username_lookup + auth_provider_uuid: + + # Method 3: SSH key fingerprint + method: ssh_key_fingerprint + # Keys registered in DCM SSH key registry, linked to actor UUIDs + + # Method 4: Service account (automated workflows) + method: webhook_service_account + # Git service account mapped to registered webhook actor +``` + +**Identity resolution failure — explicit rejection:** + +When DCM cannot map the merge actor to a DCM actor, the PR is rejected with an actionable comment. Never silently ignored. + +``` +❌ DCM Identity Resolution Failed + +DCM could not map the merge actor "jsmith" to a DCM actor identity. + +Possible causes: + • Your Git account is not linked to a DCM actor via the corporate Auth Provider + • Your DCM actor account has been suspended or deactivated + +To resolve: + • Contact your platform administrator: https://dcm.corp.example.com/actors/git-identity-setup + +This PR will not be processed until identity resolution succeeds. +``` + +### 6.5 The ingress Block for Git PR + +```yaml +ingress: + surface: git_pr_merge # or: git_pr_open (for shadow validation) + protocol: https + authenticated_via: oidc # or: ldap_direct_bind, active_directory, ssh_key + actor: + uuid: + type: human + display_name: "Jane Smith" + identity_source: oidc + auth_provider_uuid: + roles: [consumer] + tenant_scope: [] + groups: [] + # Groups and tenant scope: SAME mappings as web UI login for this user + external_identity: + provider: github # or: gitlab, gitea, freeipa, active_directory + subject: # verified by Git server + git_username: jsmith + git_verified_email: jsmith@corp.com # from Git server record — not git config + mfa_verified: true # from Auth Provider session record + git_context: + repository: https://git.corp.example.com/dcm-requests/payments + pr_number: 142 + pr_url: https://git.corp.example.com/payments/pulls/142 + merge_commit: + base_branch: main + pr_author: jsmith + pr_reviewers: [platform-team, security-team] + pr_approved_by: [, ] + # Approved by: DCM resolves Git reviewer identities via same Auth Provider +``` + +### 6.6 PR Lifecycle — DCM Processing Flow + +``` +1. Developer creates resource definition YAML (standard DCM request format) + │ +2. Opens PR against dcm-requests/{tenant-uuid}/pending/ + │ +3. DCM Git Watcher detects PR (git_pr_open event) + │ +4. DCM resolves PR author → DCM actor via Auth Provider + │ Failure → post rejection comment; stop processing + │ +5. DCM validates actor tenant scope against target Tenant + │ Failure → post rejection comment; stop processing + │ +6. Shadow policy evaluation (same nine-step assembly — dry run) + │ Results posted as PR review comments: + │ "✅ Schema valid" + │ "✅ All policies pass" + │ "⚠️ Will be placed in eu-west-1 per sovereignty policy" + │ "❌ Gating Policy: VM size exceeds quota — reduce cpu_count" + │ +7. Human review and approval (standard Git PR workflow) + │ Branch protection enforces required reviewers + │ Policy may declare: require DCM-defined approvers in git_context.pr_approved_by + │ +8. PR merged to main (git_pr_merge event) + │ ingress_surface: git_pr_merge + │ Actor re-verified at merge time (not assumed from PR open time) + │ +9. DCM processes as standard nine-step assembly (real — not shadow) + │ +10. DCM posts realization result as PR comment + status check + │ "✅ VM payments-prod-01 realized — UUID: " + │ "❌ Realization failed — audit record: " + │ +11. DCM commits realized state to realized/ directory (optional) + Git history = full lifecycle record +``` + +### 6.7 Policy Engine Use Cases — Git PR Ingress + +```yaml +# Require PR approval before processing production resources +policy: + type: gating + rule: > + If ingress.surface == git_pr_merge + AND tenant.profile IN [prod, fsi, sovereign] + AND ingress.git_context.pr_approved_by NOT CONTAINS required_approvers + THEN gate: "PR requires approval from platform admin and security owner" + +# Require MFA for Git PR merges in production Tenants +policy: + type: gating + rule: > + If ingress.surface == git_pr_merge + AND tenant.profile IN [prod, fsi, sovereign] + AND ingress.actor.mfa_verified == false + THEN gate: "MFA required for Git PR merges in production Tenants" + +# Restrict resource types submittable via Git PR +policy: + type: gating + rule: > + If ingress.surface == git_pr_merge + AND resource_type NOT IN [Compute.VirtualMachine, Storage.Block] + THEN gate: "Only compute and storage resources may be submitted via Git PR" + +# Require actor to be in authorized Git team for target Tenant +policy: + type: gating + rule: > + If ingress.surface == git_pr_merge + AND ingress.actor.groups NOT CONTAINS tenant.authorized_git_groups + THEN gate: "Git PR author is not in an authorized group for this Tenant" + +# Post shadow results as PR comments (transformation) +policy: + type: transformation + placement_phase: pre + rule: > + If ingress.surface IN [git_pr_merge, git_pr_open] + THEN inject: git_feedback.post_as_pr_comment = true +``` + +### 6.8 Updated Ingress Surface Taxonomy + +```yaml +ingress_surface_taxonomy: + - web_ui # DCM's own web interface + - consumer_api # Direct REST API call + - webhook_inbound # Inbound webhook call + - message_bus_inbound # Inbound message bus + - provider_callback # Provider reporting back + - policy_engine # Policy-generated sub-request + - scheduler # Scheduled/timed trigger + - rehydration # Rehydration-generated + - ingestion # Ingestion pipeline + - dcm_internal # DCM system-generated + - operator_cli # Command line interface + - git_pr_merge # Git PR merge → execute ← new + - git_pr_open # Git PR open → shadow validation only ← new +``` + +### 6.9 System Policies — Git PR Ingress + +| Policy | Rule | +|--------|------| +| `GIT-001` | DCM supports git_pr_merge and git_pr_open as ingress surfaces. Git PR ingress is subject to full Policy Engine evaluation identical to API ingress. | +| `GIT-002` | DCM trusts the Git server's authentication assertion — not user-declared Git configuration. DCM resolves the Git server's verified identity through the registered Auth Provider to produce a fully-resolved DCM actor with the same role, group, and tenant scope mappings as any other user authenticated via the same Auth Provider. | +| `GIT-003` | Unresolvable Git actor identities are rejected with an actionable PR comment. PRs are never silently ignored. | +| `GIT-004` | The resolved Git PR actor must have the target Tenant UUID in their tenant_scope. PRs targeting Tenants outside the actor's scope are rejected — same enforcement as API tenant scope checks. | +| `GIT-005` | DCM posts shadow policy evaluation results as PR review comments on git_pr_open. Gating Policy failures should be surfaced via repository branch protection integration. | +| `GIT-006` | PR approval status may be declared as an authorization requirement by policy. DCM checks declared reviewer approvals against the PR's actual approval record before processing a merged PR. | +| `GIT-007` | The Git Request Watcher component is policy-governed: which repositories it monitors, which branches trigger processing, and which resource types may be submitted via Git PR are declared via Policy Group. | +| `GIT-008` | Actor identity is re-verified at merge time — not assumed from PR open time. A user whose DCM actor is suspended between PR open and merge will be rejected at merge. | + +--- + + + +| Policy | Rule | +|--------|------| +| `WHK-001` | Events for a given entity_uuid are delivered in Commit Log sequence order. Cross-entity ordering is not guaranteed. | +| `WHK-002` | Webhook delivery uses at-least-once semantics. Consumers must be idempotent using event_uuid as the deduplication key. | +| `WHK-003` | Outbound webhook authentication must be declared at registration. Supported: hmac_sha256 (default), mtls, bearer_token. Unauthenticated webhooks are rejected. | +| `WHK-004` | Webhook endpoints that would deliver data outside a Tenant's sovereignty boundary are subject to sovereignty checks. | +| `WHK-005` | Webhook governance is policy-driven. Profiles set defaults — fsi/sovereign profiles may require webhook coverage for audit events via Policy Group. | +| `WHK-006` | Webhook registrations must declare the event schema version expected. DCM supports current and N-1 schema versions simultaneously. | +| `WHK-007` | Platform-scoped webhooks require Platform Admin role and are audit-logged as CONFIG_CHANGE. | +| `WHK-008` | Cross-tenant webhooks require a valid cross_tenant_authorization record (XTA-001). | +| `WHK-009` | Inbound webhook callers must be registered as webhook actors with explicit role, tenant scope, and permitted operations. Unregistered callers are rejected with 401. | +| `WHK-010` | Inbound webhook calls are subject to full Policy Engine evaluation — identical to any other API call. No bypass. | +| `WHK-011` | All inbound webhook calls are recorded in the audit trail with the webhook actor as the immediate actor. | +| `WHK-012` | Inbound webhook endpoints return 202 Accepted + request_uuid for async operations. | +| `WHK-013` | Rate limiting is enforced per registered webhook actor. Exceeding rate limits returns 429 Too Many Requests. | +| `WHK-014` | All credential references in webhook and message bus configurations must resolve through a registered credential management service. | + +--- + +*Document maintained by the DCM Project. For questions or contributions see [GitHub](https://github.com/dcm-project).*