From fb2c929dbb095979a0fd4aad315dcbaf1beb00df Mon Sep 17 00:00:00 2001 From: Chris Roadfeldt Date: Tue, 28 Jul 2026 00:26:20 -0500 Subject: [PATCH] [DCM-04] Schemas Republished from croadfeldt/dcm; see dcm-project/dcm#108. Co-Authored-By: Claude Opus 4.8 --- schemas/jsonschema/dcm-common.json | 437 +++ schemas/jsonschema/dcm-entities.json | 414 +++ schemas/jsonschema/dcm-events.json | 2761 +++++++++++++++++ schemas/jsonschema/dcm-policies.json | 652 ++++ schemas/jsonschema/dcm-providers.json | 513 +++ .../resource-type-spec-template.json | 364 +++ schemas/openapi/AEP-CONFORMANCE.md | 48 + schemas/openapi/dcm-admin-api.yaml | 2007 ++++++++++++ schemas/openapi/dcm-consumer-api.yaml | 2663 ++++++++++++++++ schemas/openapi/dcm-operator-api.yaml | 621 ++++ .../openapi/dcm-provider-callback-api.yaml | 868 ++++++ schemas/sql/001-initial.sql | 591 ++++ 12 files changed, 11939 insertions(+) create mode 100644 schemas/jsonschema/dcm-common.json create mode 100644 schemas/jsonschema/dcm-entities.json create mode 100644 schemas/jsonschema/dcm-events.json create mode 100644 schemas/jsonschema/dcm-policies.json create mode 100644 schemas/jsonschema/dcm-providers.json create mode 100644 schemas/jsonschema/resource-type-spec-template.json create mode 100644 schemas/openapi/AEP-CONFORMANCE.md create mode 100644 schemas/openapi/dcm-admin-api.yaml create mode 100644 schemas/openapi/dcm-consumer-api.yaml create mode 100644 schemas/openapi/dcm-operator-api.yaml create mode 100644 schemas/openapi/dcm-provider-callback-api.yaml create mode 100644 schemas/sql/001-initial.sql diff --git a/schemas/jsonschema/dcm-common.json b/schemas/jsonschema/dcm-common.json new file mode 100644 index 0000000..a8e5510 --- /dev/null +++ b/schemas/jsonschema/dcm-common.json @@ -0,0 +1,437 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://dcm-project.io/schemas/common/v1", + "title": "DCM Common Types", + "description": "Shared primitive and composite types reused across all DCM schemas.", + "$defs": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "RFC 4122 UUID v4" + }, + "handle": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-_/]*[a-z0-9]$", + "minLength": 3, + "maxLength": 256, + "description": "Human-readable stable identifier. Format: //. Lowercase, hyphens, underscores, and slashes only." + }, + "semver": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "description": "Semantic version string (MAJOR.MINOR.PATCH[-prerelease][+build])" + }, + "iso8601_datetime": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 datetime with timezone (e.g., 2026-03-29T14:30:00Z)" + }, + "iso8601_duration": { + "type": "string", + "pattern": "^P(?:\\d+Y)?(?:\\d+M)?(?:\\d+W)?(?:\\d+D)?(?:T(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$", + "description": "ISO 8601 duration (e.g., P90D, PT24H, P1Y6M)" + }, + "resource_type_fqn": { + "type": "string", + "pattern": "^[A-Z][a-zA-Z0-9]+\\.[A-Z][a-zA-Z0-9]+$", + "description": "Fully-qualified resource type name. Format: . (e.g., Compute.VirtualMachine, Network.VLAN)" + }, + "data_classification": { + "type": "string", + "enum": [ + "public", + "internal", + "confidential", + "restricted", + "phi", + "pci", + "sovereign", + "classified" + ], + "description": "Data classification level. Ordered: public < internal < confidential < restricted < phi | pci < sovereign < classified. Note: phi, sovereign, and classified are immutable once set (ACC-003)." + }, + "deployment_posture": { + "type": "string", + "enum": [ + "homelab", + "dev", + "standard", + "prod", + "fsi", + "sovereign" + ], + "description": "DCM deployment profile posture" + }, + "lifecycle_status": { + "type": "string", + "enum": [ + "developing", + "proposed", + "active", + "deprecated", + "retired" + ], + "description": "Artifact lifecycle status" + }, + "country_code": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "ISO 3166-1 alpha-2 country code" + }, + "actor_ref": { + "type": "object", + "required": [ + "actor_uuid", + "actor_type" + ], + "additionalProperties": false, + "properties": { + "actor_uuid": { + "$ref": "#/$defs/uuid" + }, + "actor_type": { + "type": "string", + "enum": [ + "human", + "system_component", + "policy", + "provider", + "scheduled_job" + ] + }, + "display_name": { + "type": "string", + "maxLength": 256 + } + } + }, + "artifact_metadata": { + "type": "object", + "description": "Standard metadata present on every DCM Data artifact.", + "required": [ + "uuid", + "handle", + "version", + "status", + "created_at", + "updated_at", + "created_by" + ], + "additionalProperties": false, + "properties": { + "uuid": { + "$ref": "#/$defs/uuid" + }, + "handle": { + "$ref": "#/$defs/handle" + }, + "version": { + "$ref": "#/$defs/semver" + }, + "status": { + "$ref": "#/$defs/lifecycle_status" + }, + "created_at": { + "$ref": "#/$defs/iso8601_datetime" + }, + "updated_at": { + "$ref": "#/$defs/iso8601_datetime" + }, + "created_by": { + "$ref": "#/$defs/actor_ref" + }, + "created_via": { + "type": "string", + "enum": [ + "pr", + "api", + "migration", + "system" + ] + }, + "owned_by": { + "type": "object", + "required": [ + "display_name" + ], + "additionalProperties": false, + "properties": { + "display_name": { + "type": "string", + "maxLength": 256 + }, + "email": { + "type": "string", + "format": "email" + } + } + }, + "tags": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Arbitrary key-value tags for search and organisation" + } + } + }, + "field_provenance": { + "type": "object", + "description": "Field-level provenance metadata. Every data field on a DCM artifact may carry this as a sibling _provenance key.", + "required": [ + "source_type", + "recorded_at" + ], + "additionalProperties": false, + "properties": { + "source_type": { + "type": "string", + "enum": [ + "layer", + "consumer_input", + "policy_transformation", + "provider_reported", + "information_provider", + "system_default", + "operator_override" + ] + }, + "source_ref": { + "$ref": "#/$defs/uuid", + "description": "UUID of the source layer, policy, or provider that set this value" + }, + "recorded_at": { + "$ref": "#/$defs/iso8601_datetime" + }, + "recorded_by": { + "$ref": "#/$defs/actor_ref" + }, + "basis": { + "type": "string", + "maxLength": 512, + "description": "Human-readable rationale for this value" + }, + "confidence_score": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Confidence band for information-provider-sourced values (0.0\u20131.0)" + }, + "overridable": { + "type": "boolean", + "default": true + } + } + }, + "sovereignty_declaration": { + "type": "object", + "required": [ + "operating_jurisdictions", + "data_residency_zones" + ], + "additionalProperties": false, + "properties": { + "operating_jurisdictions": { + "type": "array", + "items": { + "$ref": "#/$defs/country_code" + }, + "minItems": 1 + }, + "data_residency_zones": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "description": "Zone IDs where data physically resides" + }, + "regulatory_frameworks": { + "type": "array", + "items": { + "type": "string" + }, + "description": "e.g., GDPR, HIPAA, FedRAMP" + }, + "sub_processors": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "jurisdictions" + ], + "properties": { + "name": { + "type": "string" + }, + "jurisdictions": { + "type": "array", + "items": { + "$ref": "#/$defs/country_code" + } + }, + "purpose": { + "type": "string" + } + } + }, + "description": "Third parties with access to data handled by this provider" + }, + "inter_zone_agreements": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Legal frameworks governing cross-zone data transfers (e.g., EU-US DPF)" + } + } + }, + "accreditation_ref": { + "type": "object", + "required": [ + "accreditation_uuid", + "framework", + "status" + ], + "additionalProperties": false, + "properties": { + "accreditation_uuid": { + "$ref": "#/$defs/uuid" + }, + "framework": { + "type": "string", + "description": "e.g., iso_27001, soc2_type2, fedramp_moderate, hipaa_baa, pci_dss" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "revoked" + ] + }, + "valid_until": { + "$ref": "#/$defs/iso8601_datetime" + }, + "certificate_ref": { + "type": "string", + "format": "uri" + } + } + }, + "pagination": { + "type": "object", + "description": "Standard pagination envelope for list responses", + "required": [ + "items", + "pagination" + ], + "properties": { + "items": { + "type": "array" + }, + "pagination": { + "type": "object", + "required": [ + "total", + "limit", + "offset" + ], + "additionalProperties": false, + "properties": { + "total": { + "type": "integer", + "minimum": 0 + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 1000 + }, + "offset": { + "type": "integer", + "minimum": 0 + }, + "next_cursor": { + "type": "string" + }, + "prev_cursor": { + "type": "string" + } + } + } + } + }, + "error_response": { + "type": "object", + "required": [ + "error" + ], + "additionalProperties": false, + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "message", + "request_id" + ], + "additionalProperties": false, + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code (e.g., RESOURCE_NOT_FOUND, POLICY_DENIED)" + }, + "message": { + "type": "string", + "description": "Human-readable error description" + }, + "request_id": { + "$ref": "#/$defs/uuid", + "description": "Correlation ID for this request; present in all audit records" + }, + "rule_uuid": { + "$ref": "#/$defs/uuid", + "description": "UUID of the governing policy rule (present when code is POLICY_DENIED or GOVERNANCE_DENIED)" + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "required": [ + "field", + "issue" + ], + "properties": { + "field": { + "type": "string" + }, + "issue": { + "type": "string" + } + } + }, + "description": "Field-level validation errors (present on 422)" + } + } + } + } + }, + "resource_type_ref": { + "description": "Resource type reference \u2014 accepts either a Fully Qualified Name (e.g., 'Compute.VirtualMachine') or a Resource Type Registry UUID. DCM resolves either form to the canonical resource_type_uuid + resource_type_name pair during request assembly. FQN is recommended for consumer-facing usage as it is stable across deployments. UUID is accepted for programmatic use where the UUID is obtained from the catalog.", + "oneOf": [ + { + "$ref": "#/$defs/resource_type_fqn", + "description": "FQN form: 'Category.TypeName' (e.g., 'Compute.VirtualMachine')" + }, + { + "type": "string", + "format": "uuid", + "description": "UUID form: Resource Type Registry UUID from the service catalog" + } + ] + } + } +} \ No newline at end of file diff --git a/schemas/jsonschema/dcm-entities.json b/schemas/jsonschema/dcm-entities.json new file mode 100644 index 0000000..b03bcfd --- /dev/null +++ b/schemas/jsonschema/dcm-entities.json @@ -0,0 +1,414 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://dcm-project.io/schemas/entities/v1", + "title": "DCM Entity Schemas", + "description": "JSON Schema definitions for all three DCM entity types: Infrastructure Resource, Composite Resource, and Process Resource. See data-model/01-entity-types.md.", + + "$defs": { + + "entity_lifecycle_state": { + "type": "string", + "enum": [ + "REQUESTED", + "PENDING", + "PROVISIONING", + "REALIZED", + "OPERATIONAL", + "DEGRADED", + "MAINTENANCE", + "SUSPENDED", + "DECOMMISSIONING", + "DECOMMISSIONED", + "FAILED", + "PENDING_REVIEW", + "TIMEOUT_PENDING", + "LATE_REALIZATION_PENDING", + "INDETERMINATE_REALIZATION", + "COMPENSATION_IN_PROGRESS", + "COMPENSATION_FAILED", + "PENDING_EXPIRY_ACTION", + "INGESTED", + "ENRICHING", + "PROMOTED" + ], + "description": "Lifecycle state for Infrastructure Resource and Composite Resource entities. DECOMMISSIONED is the only terminal state for infrastructure resources; FAILED is terminal for process resources. PENDING_REVIEW requires human resolution. TIMEOUT_PENDING through COMPENSATION_FAILED are recovery states (Section 49.8). INGESTED through PROMOTED are ingestion states (Section 20.2)." + }, + + "process_lifecycle_state": { + "type": "string", + "enum": ["REQUESTED", "INITIATED", "EXECUTING", "COMPLETED", "FAILED", "CANCELLED"], + "description": "Lifecycle state for Process Resource entities. COMPLETED, FAILED, and CANCELLED are terminal. No SUSPENDED or PENDING_REVIEW states." + }, + + "ownership_model": { + "type": "string", + "enum": ["whole_allocation", "allocation", "shareable"], + "description": "whole_allocation: consumer owns entity outright. allocation: entity carved from a pool owned by another Tenant. shareable: consumer holds a stake; resource owner retains ownership." + }, + + "on_expiry_action": { + "type": "string", + "enum": ["decommission", "suspend", "notify", "escalate"] + }, + + "drift_status": { + "type": "string", + "enum": ["clean", "drifted", "unknown"] + }, + + "drift_severity": { + "type": "string", + "enum": ["minor", "significant", "critical"] + }, + + "billing_state": { + "type": "string", + "enum": ["billable", "non_billable", "reduced_rate"] + }, + + "composition_visibility": { + "type": "string", + "enum": ["opaque", "transparent", "selective"], + "description": "opaque: consumers see composite only. transparent: consumers see composite and all constituents. selective: policy declares which constituents are visible." + }, + + "composite_health": { + "type": "string", + "enum": ["healthy", "degraded", "failed"] + }, + + "constituent_role": { + "type": "string", + "enum": ["primary", "supporting", "optional"] + }, + + "rehydration_constraints": { + "type": "object", + "additionalProperties": false, + "properties": { + "min_auth_level": { + "type": "string", + "description": "Minimum authority tier name required to authorize rehydration" + }, + "allow_delegated_rehydration": { + "type": "boolean", + "default": false, + "description": "Whether a Tenant admin (not the original requester) may authorize rehydration" + } + } + }, + + "rehydration_history_entry": { + "type": "object", + "required": ["rehydrated_at", "previous_provider_uuid", "new_provider_uuid", "authorized_by"], + "additionalProperties": false, + "properties": { + "rehydration_uuid": { "$ref": "dcm-common.json#/$defs/uuid" }, + "rehydrated_at": { "$ref": "dcm-common.json#/$defs/iso8601_datetime" }, + "previous_provider_uuid": { "$ref": "dcm-common.json#/$defs/uuid" }, + "new_provider_uuid": { "$ref": "dcm-common.json#/$defs/uuid" }, + "authorized_by": { "$ref": "dcm-common.json#/$defs/actor_ref" }, + "reason": { "type": "string" } + } + }, + + "pending_review_record": { + "type": "object", + "required": ["trigger", "detected_at", "resolution_options"], + "additionalProperties": false, + "properties": { + "trigger": { + "type": "string", + "enum": [ + "rehydration_sovereignty_conflict", + "cross_tenant_authorization_revoked", + "ownership_transfer_conflict" + ] + }, + "detected_at": { "$ref": "dcm-common.json#/$defs/iso8601_datetime" }, + "conflict_detail": { "type": "string" }, + "resolution_options": { + "type": "array", + "items": { + "type": "string", + "enum": ["re_authorize", "release", "escalate", "manual_override"] + } + } + } + }, + + "relationship": { + "type": "object", + "required": ["relationship_uuid", "relation", "target_entity_uuid", "created_at"], + "additionalProperties": false, + "properties": { + "relationship_uuid": { "$ref": "dcm-common.json#/$defs/uuid" }, + "relation": { + "type": "string", + "enum": [ + "requires", + "contains", + "references", + "peer", + "allocated_from", + "constituent_of", + "has_constituent" + ] + }, + "target_entity_uuid": { "$ref": "dcm-common.json#/$defs/uuid" }, + "target_entity_type": { "$ref": "dcm-common.json#/$defs/resource_type_fqn" }, + "created_at": { "$ref": "dcm-common.json#/$defs/iso8601_datetime" }, + "metadata": { "type": "object", "additionalProperties": true } + } + }, + + "infrastructure_resource_entity": { + "type": "object", + "title": "Infrastructure Resource Entity", + "description": "A realized physical or virtual infrastructure resource. Persists after provisioning. Owned by exactly one Tenant. Subject to drift detection and TTL management. See data-model/01-entity-types.md Section 2.1.", + "required": [ + "artifact_metadata", + "entity_type", + "resource_type", + "resource_type_spec_version", + "lifecycle_state", + "owned_by_tenant_uuid", + "created_by_actor_uuid", + "ownership_model" + ], + "additionalProperties": true, + "properties": { + + "artifact_metadata": { "$ref": "dcm-common.json#/$defs/artifact_metadata" }, + + "entity_type": { + "type": "string", + "const": "infrastructure_resource" + }, + + "resource_type": { "$ref": "dcm-common.json#/$defs/resource_type_fqn" }, + "resource_type_spec_version": { "$ref": "dcm-common.json#/$defs/semver" }, + "lifecycle_state": { "$ref": "#/$defs/entity_lifecycle_state" }, + + "owned_by_tenant_uuid": { "$ref": "dcm-common.json#/$defs/uuid" }, + "created_by_actor_uuid": { "$ref": "dcm-common.json#/$defs/uuid" }, + "ownership_model": { "$ref": "#/$defs/ownership_model" }, + + "allocated_from_pool_uuid": { + "oneOf": [{ "$ref": "dcm-common.json#/$defs/uuid" }, { "type": "null" }], + "description": "UUID of the pool entity this was carved from. Null unless ownership_model is 'allocation'." + }, + "allocation_ref_uuid": { + "oneOf": [{ "$ref": "dcm-common.json#/$defs/uuid" }, { "type": "null" }] + }, + "shared_resource_uuid": { + "oneOf": [{ "$ref": "dcm-common.json#/$defs/uuid" }, { "type": "null" }], + "description": "UUID of the shared resource this is a stake in. Null unless ownership_model is 'shareable'." + }, + + "provider_uuid": { + "oneOf": [{ "$ref": "dcm-common.json#/$defs/uuid" }, { "type": "null" }] + }, + "provider_entity_id": { + "oneOf": [{ "type": "string", "maxLength": 512 }, { "type": "null" }], + "description": "The provider's own identifier for this resource (e.g., 'vm-12345'). Separate from the DCM UUID." + }, + "provider_entity_id_history": { + "type": "array", + "items": { + "type": "object", + "required": ["provider_entity_id", "valid_from"], + "properties": { + "provider_entity_id": { "type": "string" }, + "valid_from": { "$ref": "dcm-common.json#/$defs/iso8601_datetime" }, + "valid_to": { "$ref": "dcm-common.json#/$defs/iso8601_datetime" } + } + } + }, + + "ttl": { + "oneOf": [{ "$ref": "dcm-common.json#/$defs/iso8601_duration" }, { "type": "null" }] + }, + "ttl_expires_at": { + "oneOf": [{ "$ref": "dcm-common.json#/$defs/iso8601_datetime" }, { "type": "null" }] + }, + "on_expiry": { "$ref": "#/$defs/on_expiry_action" }, + "billing_state": { "$ref": "#/$defs/billing_state" }, + + "rehydration_constraints": { "$ref": "#/$defs/rehydration_constraints" }, + "rehydration_history": { + "type": "array", + "items": { "$ref": "#/$defs/rehydration_history_entry" } + }, + + "last_discovered_at": { + "oneOf": [{ "$ref": "dcm-common.json#/$defs/iso8601_datetime" }, { "type": "null" }] + }, + "drift_status": { "$ref": "#/$defs/drift_status" }, + "last_drift_severity": { + "oneOf": [{ "$ref": "#/$defs/drift_severity" }, { "type": "null" }] + }, + + "pending_review_record": { + "oneOf": [{ "$ref": "#/$defs/pending_review_record" }, { "type": "null" }], + "description": "Present only when lifecycle_state is PENDING_REVIEW." + }, + + "relationships": { + "type": "array", + "items": { "$ref": "#/$defs/relationship" } + } + }, + "if": { + "properties": { "lifecycle_state": { "const": "PENDING_REVIEW" } } + }, + "then": { + "required": ["pending_review_record"], + "properties": { + "pending_review_record": { "not": { "type": "null" } } + } + } + }, + + "composite_resource_entity": { + "type": "object", + "title": "Composite Resource Entity", + "description": "Produced by a Composite Service registration aggregating multiple constituent Infrastructure Resource Entities into a higher-order service. The composite is a first-class entity with its own UUID. See data-model/01-entity-types.md Section 2.2 and data-model/30-composite-service-model.md.", + "required": [ + "artifact_metadata", + "entity_type", + "resource_type", + "resource_type_spec_version", + "lifecycle_state", + "owned_by_tenant_uuid", + "composition_visibility", + "constituents" + ], + "additionalProperties": true, + "properties": { + + "artifact_metadata": { "$ref": "dcm-common.json#/$defs/artifact_metadata" }, + + "entity_type": { + "type": "string", + "const": "composite_resource" + }, + + "resource_type": { "$ref": "dcm-common.json#/$defs/resource_type_fqn" }, + "resource_type_spec_version": { "$ref": "dcm-common.json#/$defs/semver" }, + "lifecycle_state": { "$ref": "#/$defs/entity_lifecycle_state" }, + "owned_by_tenant_uuid": { "$ref": "dcm-common.json#/$defs/uuid" }, + + "composition_visibility": { "$ref": "#/$defs/composition_visibility" }, + "composite_health": { "$ref": "#/$defs/composite_health" }, + + "constituents": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["constituent_entity_uuid", "role", "required_for_composite_operational"], + "additionalProperties": false, + "properties": { + "constituent_entity_uuid": { "$ref": "dcm-common.json#/$defs/uuid" }, + "role": { "$ref": "#/$defs/constituent_role" }, + "required_for_composite_operational": { "type": "boolean" }, + "constituent_lifecycle_state": { "$ref": "#/$defs/entity_lifecycle_state" } + } + } + }, + + "provider_uuid": { + "oneOf": [{ "$ref": "dcm-common.json#/$defs/uuid" }, { "type": "null" }], + "description": "The Service Provider that registered the Composite Service this composite was instantiated from." + }, + + "ttl": { "oneOf": [{ "$ref": "dcm-common.json#/$defs/iso8601_duration" }, { "type": "null" }] }, + "ttl_expires_at": { "oneOf": [{ "$ref": "dcm-common.json#/$defs/iso8601_datetime" }, { "type": "null" }] }, + "on_expiry": { "$ref": "#/$defs/on_expiry_action" }, + "billing_state": { "$ref": "#/$defs/billing_state" }, + + "relationships": { + "type": "array", + "items": { "$ref": "#/$defs/relationship" } + } + } + }, + + "process_resource_entity": { + "type": "object", + "title": "Process Resource Entity", + "description": "An ephemeral execution: automation job, playbook, pipeline, or workflow. Does not persist after reaching a terminal state. Must declare max_execution_time. See data-model/01-entity-types.md Section 2.3.", + "required": [ + "artifact_metadata", + "entity_type", + "resource_type", + "resource_type_spec_version", + "lifecycle_state", + "owned_by_tenant_uuid", + "created_by_actor_uuid", + "max_execution_time" + ], + "additionalProperties": true, + "properties": { + + "artifact_metadata": { "$ref": "dcm-common.json#/$defs/artifact_metadata" }, + + "entity_type": { + "type": "string", + "const": "process_resource" + }, + + "resource_type": { "$ref": "dcm-common.json#/$defs/resource_type_fqn" }, + "resource_type_spec_version": { "$ref": "dcm-common.json#/$defs/semver" }, + "lifecycle_state": { "$ref": "#/$defs/process_lifecycle_state" }, + "owned_by_tenant_uuid": { "$ref": "dcm-common.json#/$defs/uuid" }, + "created_by_actor_uuid": { "$ref": "dcm-common.json#/$defs/uuid" }, + + "max_execution_time": { "$ref": "dcm-common.json#/$defs/iso8601_duration" }, + "started_at": { "oneOf": [{ "$ref": "dcm-common.json#/$defs/iso8601_datetime" }, { "type": "null" }] }, + "completed_at": { "oneOf": [{ "$ref": "dcm-common.json#/$defs/iso8601_datetime" }, { "type": "null" }] }, + "execution_timeout_at": { "oneOf": [{ "$ref": "dcm-common.json#/$defs/iso8601_datetime" }, { "type": "null" }] }, + + "affected_entity_uuids": { + "type": "array", + "items": { "$ref": "dcm-common.json#/$defs/uuid" }, + "description": "UUIDs of all Infrastructure Resource Entities modified during this process. Mandatory if any modifications were made." + }, + + "provider_uuid": { "oneOf": [{ "$ref": "dcm-common.json#/$defs/uuid" }, { "type": "null" }] }, + "provider_job_id": { "oneOf": [{ "type": "string", "maxLength": 512 }, { "type": "null" }] }, + + "exit_status": { + "oneOf": [ + { "type": "string", "enum": ["success", "failure", "timeout", "cancelled"] }, + { "type": "null" } + ] + }, + "execution_log_ref": { + "oneOf": [{ "$ref": "dcm-common.json#/$defs/uuid" }, { "type": "null" }], + "description": "Reference to the log store entry for this process execution." + } + } + }, + + "dcm_entity": { + "oneOf": [ + { "$ref": "#/$defs/infrastructure_resource_entity" }, + { "$ref": "#/$defs/composite_resource_entity" }, + { "$ref": "#/$defs/process_resource_entity" } + ], + "discriminator": { + "propertyName": "entity_type", + "mapping": { + "infrastructure_resource": "#/$defs/infrastructure_resource_entity", + "composite_resource": "#/$defs/composite_resource_entity", + "process_resource": "#/$defs/process_resource_entity" + } + } + } + + }, + + "$ref": "#/$defs/dcm_entity" +} diff --git a/schemas/jsonschema/dcm-events.json b/schemas/jsonschema/dcm-events.json new file mode 100644 index 0000000..fcff8e4 --- /dev/null +++ b/schemas/jsonschema/dcm-events.json @@ -0,0 +1,2761 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://dcm-project.io/schemas/events/v1", + "title": "DCM Event Schemas", + "description": "JSON Schema definitions for all DCM event types (101 event payloads across 22 domains). The base envelope wraps every event. Event-specific payload fields are in the payload object. See data-model/33-event-catalog.md.", + "$defs": { + "urgency": { + "type": "string", + "enum": [ + "critical", + "high", + "medium", + "low", + "info" + ] + }, + "event_subject": { + "type": "object", + "additionalProperties": false, + "properties": { + "entity_uuid": { + "oneOf": [ + { + "$ref": "dcm-common.json#/$defs/uuid" + }, + { + "type": "null" + } + ] + }, + "entity_type": { + "oneOf": [ + { + "$ref": "dcm-common.json#/$defs/resource_type_fqn" + }, + { + "type": "null" + } + ] + }, + "entity_handle": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "tenant_uuid": { + "oneOf": [ + { + "$ref": "dcm-common.json#/$defs/uuid" + }, + { + "type": "null" + } + ] + }, + "actor_uuid": { + "oneOf": [ + { + "$ref": "dcm-common.json#/$defs/uuid" + }, + { + "type": "null" + } + ] + } + } + }, + "event_links": { + "type": "object", + "additionalProperties": false, + "properties": { + "self": { + "type": "string", + "format": "uri" + }, + "audit_record": { + "type": "string", + "format": "uri" + } + } + }, + "score_driver": { + "type": "object", + "required": [ + "signal", + "contribution" + ], + "additionalProperties": false, + "properties": { + "signal": { + "type": "string" + }, + "contribution": { + "type": "integer", + "minimum": 0, + "maximum": 100 + } + } + }, + "base_event_envelope": { + "type": "object", + "description": "Common envelope present on every DCM event. Consumers must implement idempotency using event_uuid (at-least-once delivery).", + "required": [ + "event_uuid", + "event_type", + "event_schema_version", + "timestamp", + "dcm_version", + "dcm_instance_uuid", + "subject", + "urgency", + "payload" + ], + "properties": { + "event_uuid": { + "$ref": "dcm-common.json#/$defs/uuid", + "description": "Idempotency key \u2014 stable across retries" + }, + "event_type": { + "type": "string", + "description": "Fully qualified event type (e.g. request.submitted)" + }, + "event_schema_version": { + "type": "string", + "pattern": "^\\d+\\.\\d+$", + "description": "Increments on breaking payload schema changes" + }, + "timestamp": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime", + "description": "From the DCM Commit Log \u2014 authoritative time source" + }, + "dcm_version": { + "$ref": "dcm-common.json#/$defs/semver" + }, + "dcm_instance_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "subject": { + "$ref": "#/$defs/event_subject" + }, + "urgency": { + "$ref": "#/$defs/urgency" + }, + "payload": { + "type": "object" + }, + "links": { + "$ref": "#/$defs/event_links" + } + } + }, + "empty_payload": { + "type": "object", + "additionalProperties": false, + "properties": {} + }, + "request_submitted_payload": { + "type": "object", + "required": [ + "request_uuid", + "catalog_item_uuid", + "resource_type" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "catalog_item_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "catalog_item_handle": { + "type": "string" + }, + "resource_type": { + "$ref": "dcm-common.json#/$defs/resource_type_fqn" + }, + "submitted_fields": { + "type": "object", + "additionalProperties": true + } + } + }, + "request_intent_captured_payload": { + "type": "object", + "required": [ + "request_uuid", + "entity_uuid" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "request_layers_assembled_payload": { + "type": "object", + "required": [ + "request_uuid" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "layers_applied": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "request_policies_evaluated_payload": { + "type": "object", + "required": [ + "request_uuid", + "risk_score", + "routing_tier" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "risk_score": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "routing_tier": { + "type": "string" + }, + "score_drivers": { + "type": "array", + "items": { + "$ref": "#/$defs/score_driver" + } + } + } + }, + "request_requires_approval_payload": { + "type": "object", + "required": [ + "request_uuid", + "approval_uuid", + "required_tier", + "required_tier_gravity", + "risk_score" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "approval_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "required_tier": { + "type": "string" + }, + "required_tier_gravity": { + "type": "string", + "enum": [ + "none", + "routine", + "elevated", + "critical" + ] + }, + "risk_score": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "window_expires_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "dcmgroup_uuid": { + "oneOf": [ + { + "$ref": "dcm-common.json#/$defs/uuid" + }, + { + "type": "null" + } + ] + } + } + }, + "request_approved_payload": { + "type": "object", + "required": [ + "request_uuid", + "approval_uuid" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "approval_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "approved_by": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "request_placement_complete_payload": { + "type": "object", + "required": [ + "request_uuid", + "provider_uuid" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "placement_score": { + "type": "number" + } + } + }, + "request_dispatched_payload": { + "type": "object", + "required": [ + "request_uuid", + "provider_uuid" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "request_realized_payload": { + "type": "object", + "required": [ + "request_uuid", + "provider_uuid", + "outcome" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "outcome": { + "type": "string", + "enum": [ + "realized", + "failed", + "degraded" + ] + }, + "failure_reason": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "realized_fields": { + "type": "object", + "additionalProperties": true + } + } + }, + "request_failed_payload": { + "type": "object", + "required": [ + "request_uuid", + "failure_stage", + "reason" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "failure_stage": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "provider_uuid": { + "oneOf": [ + { + "$ref": "dcm-common.json#/$defs/uuid" + }, + { + "type": "null" + } + ] + }, + "recoverable": { + "type": "boolean" + } + } + }, + "request_cancelled_payload": { + "type": "object", + "required": [ + "request_uuid" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "cancelled_by": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "cancellation_stage": { + "type": "string" + } + } + }, + "request_gating_rejected_payload": { + "type": "object", + "required": [ + "request_uuid", + "policy_handle", + "enforcement_class", + "rejection_reason" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "policy_handle": { + "type": "string" + }, + "policy_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "enforcement_class": { + "type": "string", + "enum": [ + "compliance", + "operational" + ] + }, + "rejection_reason": { + "type": "string" + }, + "risk_score": { + "type": "integer", + "minimum": 0, + "maximum": 100 + } + } + }, + "request_progress_updated_payload": { + "type": "object", + "required": [ + "request_uuid", + "stage", + "progress_pct" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "stage": { + "type": "string" + }, + "progress_pct": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "message": { + "type": "string" + } + } + }, + "request_compound_assembled_payload": { + "type": "object", + "required": [ + "request_uuid", + "constituent_request_uuids" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "constituent_request_uuids": { + "type": "array", + "items": { + "$ref": "dcm-common.json#/$defs/uuid" + } + }, + "composite_service_ref": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "request_dependencies_resolved_payload": { + "type": "object", + "required": [ + "request_uuid", + "dependency_uuids" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "dependency_uuids": { + "type": "array", + "items": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + } + }, + "entity_realized_payload": { + "type": "object", + "required": [ + "request_uuid", + "provider_uuid" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "realized_fields": { + "type": "object", + "additionalProperties": true + }, + "composite_entity": { + "type": "boolean" + } + } + }, + "entity_state_changed_payload": { + "type": "object", + "required": [ + "previous_state", + "new_state", + "triggered_by" + ], + "additionalProperties": false, + "properties": { + "previous_state": { + "type": "string" + }, + "new_state": { + "type": "string" + }, + "triggered_by": { + "type": "string", + "enum": [ + "ttl", + "decommission", + "consumer", + "policy", + "provider", + "system" + ] + }, + "reason": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + } + }, + "entity_modified_payload": { + "type": "object", + "required": [ + "modified_fields" + ], + "additionalProperties": false, + "properties": { + "modified_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "modification_type": { + "type": "string", + "enum": [ + "consumer_update", + "provider_update", + "policy_transformation" + ] + } + } + }, + "entity_ttl_warning_payload": { + "type": "object", + "required": [ + "ttl_expires_at", + "expiry_action" + ], + "additionalProperties": false, + "properties": { + "ttl_expires_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "expiry_action": { + "type": "string", + "enum": [ + "decommission", + "suspend", + "notify", + "escalate" + ] + }, + "warning_window": { + "$ref": "dcm-common.json#/$defs/iso8601_duration" + } + } + }, + "entity_ttl_expired_payload": { + "type": "object", + "required": [ + "ttl_expired_at", + "expiry_action" + ], + "additionalProperties": false, + "properties": { + "ttl_expired_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "expiry_action": { + "type": "string", + "enum": [ + "decommission", + "suspend", + "notify", + "escalate" + ] + } + } + }, + "entity_decommissioning_payload": { + "type": "object", + "required": [ + "initiated_at" + ], + "additionalProperties": false, + "properties": { + "initiated_by": { + "oneOf": [ + { + "$ref": "dcm-common.json#/$defs/uuid" + }, + { + "type": "null" + } + ] + }, + "initiated_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "reason": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "stakes_resolved": { + "type": "boolean" + }, + "credential_revocation_status": { + "type": "string", + "enum": [ + "complete", + "partial", + "pending" + ] + } + } + }, + "entity_decommissioned_payload": { + "type": "object", + "required": [ + "decommissioned_at", + "provider_uuid" + ], + "additionalProperties": false, + "properties": { + "decommissioned_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "entity_decommission_deferred_payload": { + "type": "object", + "required": [ + "reason", + "blocking_relationships" + ], + "additionalProperties": false, + "properties": { + "reason": { + "type": "string" + }, + "blocking_relationships": { + "type": "array", + "items": { + "type": "string" + } + }, + "retry_after": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + } + } + }, + "entity_suspended_payload": { + "type": "object", + "required": [ + "suspended_at" + ], + "additionalProperties": false, + "properties": { + "suspended_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "suspended_by": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "reason": { + "type": "string" + } + } + }, + "entity_resumed_payload": { + "type": "object", + "required": [ + "resumed_at" + ], + "additionalProperties": false, + "properties": { + "resumed_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "resumed_by": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "entity_expired_payload": { + "type": "object", + "required": [ + "expired_at", + "expiry_action_taken" + ], + "additionalProperties": false, + "properties": { + "expired_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "expiry_action_taken": { + "type": "string", + "enum": [ + "decommission", + "suspend", + "notify", + "escalate" + ] + } + } + }, + "entity_ownership_transferred_payload": { + "type": "object", + "required": [ + "from_tenant_uuid", + "to_tenant_uuid", + "transferred_at" + ], + "additionalProperties": false, + "properties": { + "from_tenant_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "to_tenant_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "transferred_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "authorized_by": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "entity_pending_review_payload": { + "type": "object", + "required": [ + "trigger", + "detected_at", + "resolution_options" + ], + "additionalProperties": false, + "properties": { + "trigger": { + "type": "string", + "enum": [ + "rehydration_sovereignty_conflict", + "cross_tenant_authorization_revoked", + "ownership_transfer_conflict" + ] + }, + "detected_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "conflict_detail": { + "type": "string" + }, + "resolution_options": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "drift_detected_payload": { + "type": "object", + "required": [ + "drift_uuid", + "severity", + "unsanctioned" + ], + "additionalProperties": false, + "properties": { + "drift_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "severity": { + "type": "string", + "enum": [ + "minor", + "significant", + "critical" + ] + }, + "unsanctioned": { + "type": "boolean" + }, + "field_count": { + "type": "integer", + "minimum": 1 + }, + "critical_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "drift_severity_escalated_payload": { + "type": "object", + "required": [ + "drift_uuid", + "previous_severity", + "new_severity" + ], + "additionalProperties": false, + "properties": { + "drift_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "previous_severity": { + "type": "string", + "enum": [ + "minor", + "significant", + "critical" + ] + }, + "new_severity": { + "type": "string", + "enum": [ + "minor", + "significant", + "critical" + ] + }, + "age": { + "$ref": "dcm-common.json#/$defs/iso8601_duration" + } + } + }, + "drift_escalated_payload": { + "type": "object", + "required": [ + "drift_uuid", + "escalated_to" + ], + "additionalProperties": false, + "properties": { + "drift_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "escalated_to": { + "type": "string" + }, + "age": { + "$ref": "dcm-common.json#/$defs/iso8601_duration" + } + } + }, + "drift_resolved_payload": { + "type": "object", + "required": [ + "drift_uuid", + "resolution" + ], + "additionalProperties": false, + "properties": { + "drift_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "resolution": { + "type": "string", + "enum": [ + "remediated", + "accepted", + "provider_corrected" + ] + }, + "resolved_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + } + } + }, + "provider_registered_payload": { + "type": "object", + "required": [ + "provider_uuid", + "provider_type", + "display_name" + ], + "additionalProperties": false, + "properties": { + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "provider_type": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "approval_method": { + "type": "string" + } + } + }, + "provider_health_changed_payload": { + "type": "object", + "required": [ + "provider_uuid", + "previous_status", + "new_status" + ], + "additionalProperties": false, + "properties": { + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "previous_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "unhealthy" + ] + }, + "new_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "unhealthy" + ] + }, + "consecutive_failures": { + "type": "integer" + } + } + }, + "provider_deregistered_payload": { + "type": "object", + "required": [ + "provider_uuid", + "reason" + ], + "additionalProperties": false, + "properties": { + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "reason": { + "type": "string" + }, + "affected_entity_count": { + "type": "integer" + } + } + }, + "provider_update_submitted_payload": { + "type": "object", + "required": [ + "provider_uuid", + "entity_uuid", + "update_uuid", + "proposed_fields" + ], + "additionalProperties": false, + "properties": { + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "update_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "proposed_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "provider_update_decision_payload": { + "type": "object", + "required": [ + "provider_uuid", + "entity_uuid", + "update_uuid", + "decision" + ], + "additionalProperties": false, + "properties": { + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "update_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "decision": { + "type": "string", + "enum": [ + "APPROVED", + "AUTO_APPROVED", + "REJECTED", + "REQUIRES_APPROVAL" + ] + }, + "reason": { + "type": "string" + } + } + }, + "rehydration_started_payload": { + "type": "object", + "required": [ + "source_provider_uuid", + "target_provider_uuid" + ], + "additionalProperties": false, + "properties": { + "source_provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "target_provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "authorized_by": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "rehydration_completed_payload": { + "type": "object", + "required": [ + "new_provider_uuid", + "rehydrated_at" + ], + "additionalProperties": false, + "properties": { + "new_provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "rehydrated_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + } + } + }, + "rehydration_blocked_payload": { + "type": "object", + "required": [ + "reason" + ], + "additionalProperties": false, + "properties": { + "reason": { + "type": "string" + }, + "sovereignty_gap": { + "type": "boolean" + } + } + }, + "policy_lifecycle_payload": { + "type": "object", + "required": [ + "policy_uuid", + "policy_handle", + "policy_type" + ], + "additionalProperties": false, + "properties": { + "policy_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "policy_handle": { + "type": "string" + }, + "policy_type": { + "type": "string" + }, + "domain": { + "type": "string" + } + } + }, + "policy_shadow_result_payload": { + "type": "object", + "required": [ + "policy_uuid", + "request_uuid", + "shadow_decision" + ], + "additionalProperties": false, + "properties": { + "policy_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "shadow_decision": { + "type": "string", + "enum": [ + "would_allow", + "would_deny" + ] + }, + "enforcement_class": { + "type": "string" + } + } + }, + "credential_lifecycle_payload": { + "type": "object", + "required": [ + "credential_uuid", + "credential_type" + ], + "additionalProperties": false, + "properties": { + "credential_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "credential_type": { + "type": "string" + }, + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "expires_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + } + } + }, + "approval_decision_recorded_payload": { + "type": "object", + "required": [ + "approval_uuid", + "decision", + "required_tier" + ], + "additionalProperties": false, + "properties": { + "approval_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "decision": { + "type": "string", + "enum": [ + "approve", + "reject", + "abstain" + ] + }, + "required_tier": { + "type": "string" + }, + "decisions_so_far": { + "type": "integer" + }, + "quorum_reached": { + "type": "boolean" + } + } + }, + "approval_quorum_reached_payload": { + "type": "object", + "required": [ + "approval_uuid", + "outcome" + ], + "additionalProperties": false, + "properties": { + "approval_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "outcome": { + "type": "string", + "enum": [ + "approved", + "rejected" + ] + } + } + }, + "approval_window_expiring_payload": { + "type": "object", + "required": [ + "approval_uuid", + "expires_at" + ], + "additionalProperties": false, + "properties": { + "approval_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "expires_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "decisions_so_far": { + "type": "integer" + }, + "quorum_needed": { + "type": "integer" + } + } + }, + "approval_expired_payload": { + "type": "object", + "required": [ + "approval_uuid" + ], + "additionalProperties": false, + "properties": { + "approval_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "expired_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + } + } + }, + "tier_registry_proposed_payload": { + "type": "object", + "required": [ + "change_uuid", + "proposed_by", + "blocking_items" + ], + "additionalProperties": false, + "properties": { + "change_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "proposed_by": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "blocking_items": { + "type": "integer" + } + } + }, + "tier_registry_impact_assessed_payload": { + "type": "object", + "required": [ + "change_uuid", + "impact_report_uuid", + "blocking_items" + ], + "additionalProperties": false, + "properties": { + "change_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "impact_report_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "blocking_items": { + "type": "integer" + }, + "security_degradations": { + "type": "integer" + }, + "broken_references": { + "type": "integer" + } + } + }, + "tier_registry_degradation_detected_payload": { + "type": "object", + "required": [ + "change_uuid", + "degradation_item_uuid", + "tier_name" + ], + "additionalProperties": false, + "properties": { + "change_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "degradation_item_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "tier_name": { + "type": "string" + }, + "old_gravity": { + "type": "string" + }, + "new_gravity": { + "type": "string" + } + } + }, + "tier_registry_activated_payload": { + "type": "object", + "required": [ + "change_uuid", + "activated_by", + "activated_at" + ], + "additionalProperties": false, + "properties": { + "change_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "activated_by": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "activated_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "tier_count": { + "type": "integer" + } + } + }, + "audit_integrity_payload": { + "type": "object", + "required": [ + "store_uuid", + "detected_at" + ], + "additionalProperties": false, + "properties": { + "store_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "detected_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "affected_records": { + "type": "integer" + }, + "break_location": { + "type": "string" + } + } + }, + "audit_forward_failed_payload": { + "type": "object", + "required": [ + "target_store_uuid", + "failure_reason" + ], + "additionalProperties": false, + "properties": { + "target_store_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "failure_reason": { + "type": "string" + }, + "retry_count": { + "type": "integer" + } + } + }, + "dependency_state_changed_payload": { + "type": "object", + "required": [ + "dependency_entity_uuid", + "previous_state", + "new_state" + ], + "additionalProperties": false, + "properties": { + "dependency_entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "previous_state": { + "type": "string" + }, + "new_state": { + "type": "string" + }, + "impact": { + "type": "string", + "enum": [ + "none", + "degraded", + "failed" + ] + } + } + }, + "stakeholder_resource_decommissioning_payload": { + "type": "object", + "required": [ + "resource_uuid", + "decommission_at", + "stakeholder_action_required" + ], + "additionalProperties": false, + "properties": { + "resource_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "decommission_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "stakeholder_action_required": { + "type": "boolean" + } + } + }, + "allocation_released_payload": { + "type": "object", + "required": [ + "pool_uuid", + "allocation_uuid" + ], + "additionalProperties": false, + "properties": { + "pool_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "allocation_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "released_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + } + } + }, + "allocation_pool_capacity_low_payload": { + "type": "object", + "required": [ + "pool_uuid", + "available_pct" + ], + "additionalProperties": false, + "properties": { + "pool_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "available_pct": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "available_units": { + "type": "integer" + } + } + }, + "ingestion_transitional_created_payload": { + "type": "object", + "required": [ + "provider_uuid", + "discovered_resource_count" + ], + "additionalProperties": false, + "properties": { + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "discovered_resource_count": { + "type": "integer" + } + } + }, + "ingestion_enriched_payload": { + "type": "object", + "required": [ + "entity_uuid", + "enriched_fields" + ], + "additionalProperties": false, + "properties": { + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "enriched_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "ingestion_promotion_approved_payload": { + "type": "object", + "required": [ + "entity_uuid", + "target_tenant_uuid" + ], + "additionalProperties": false, + "properties": { + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "target_tenant_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "approved_by": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "governance_catalog_item_deprecated_payload": { + "type": "object", + "required": [ + "catalog_item_uuid", + "sunset_date" + ], + "additionalProperties": false, + "properties": { + "catalog_item_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "sunset_date": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "successor_type": { + "type": "string" + }, + "migration_guide_url": { + "type": "string", + "format": "uri" + } + } + }, + "governance_profile_changed_payload": { + "type": "object", + "required": [ + "previous_posture", + "new_posture" + ], + "additionalProperties": false, + "properties": { + "previous_posture": { + "type": "string" + }, + "new_posture": { + "type": "string" + }, + "changed_by": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "governance_policy_trust_elevated_payload": { + "type": "object", + "required": [ + "policy_evaluator_ref", + "previous_trust", + "new_trust" + ], + "additionalProperties": false, + "properties": { + "policy_evaluator_ref": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "previous_trust": { + "type": "string" + }, + "new_trust": { + "type": "string" + } + } + }, + "security_unsanctioned_provider_write_payload": { + "type": "object", + "required": [ + "provider_uuid", + "entity_uuid", + "field_paths" + ], + "additionalProperties": false, + "properties": { + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "field_paths": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "sovereignty_violation_payload": { + "type": "object", + "required": [ + "violation_type", + "entity_uuid", + "provider_uuid" + ], + "additionalProperties": false, + "properties": { + "violation_type": { + "type": "string" + }, + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "detail": { + "type": "string" + } + } + }, + "sovereignty_migration_required_payload": { + "type": "object", + "required": [ + "entity_uuid", + "current_provider_uuid", + "reason" + ], + "additionalProperties": false, + "properties": { + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "current_provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "reason": { + "type": "string" + }, + "deadline": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + } + } + }, + "federation_tunnel_degraded_payload": { + "type": "object", + "required": [ + "peer_dcm_uuid", + "tunnel_status" + ], + "additionalProperties": false, + "properties": { + "peer_dcm_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "tunnel_status": { + "type": "string", + "enum": [ + "degraded", + "down" + ] + }, + "affected_routes": { + "type": "integer" + } + } + }, + "auth_provider_failover_payload": { + "type": "object", + "required": [ + "failed_provider_uuid", + "failover_provider_uuid" + ], + "additionalProperties": false, + "properties": { + "failed_provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "failover_provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "failover_reason": { + "type": "string" + } + } + }, + "rehydration_paused_payload": { + "type": "object", + "description": "Rehydration paused \u2014 awaiting approval or resource availability.", + "required": [ + "entity_uuid", + "rehydration_uuid", + "pause_reason" + ], + "additionalProperties": false, + "properties": { + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "rehydration_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "pause_reason": { + "type": "string" + } + } + }, + "rehydration_interrupted_payload": { + "type": "object", + "description": "Rehydration interrupted \u2014 failure or conflict during rehydration.", + "required": [ + "entity_uuid", + "rehydration_uuid", + "interrupt_reason" + ], + "additionalProperties": false, + "properties": { + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "rehydration_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "interrupt_reason": { + "type": "string" + }, + "recovery_action": { + "type": "string" + } + } + }, + "audit_integrity_break_payload": { + "type": "object", + "description": "Audit Merkle-tree integrity break detected \u2014 potential tamper or data corruption. Urgency: critical, non-suppressable (EVT-007).", + "required": [ + "entity_uuid", + "expected_hash", + "actual_hash", + "chain_sequence" + ], + "additionalProperties": false, + "properties": { + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "expected_hash": { + "type": "string" + }, + "actual_hash": { + "type": "string" + }, + "chain_sequence": { + "type": "integer" + } + } + }, + "itsm_record_created_payload": { + "type": "object", + "description": "ITSM record created by ITSM Action Policy. See data-model/42-itsm-integration.md.", + "required": [ + "itsm_provider_uuid", + "record_type", + "record_id", + "entity_uuid" + ], + "additionalProperties": false, + "properties": { + "itsm_provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "record_type": { + "type": "string", + "enum": [ + "change_request", + "incident", + "cmdb_ci", + "service_request" + ] + }, + "record_id": { + "type": "string" + }, + "record_url": { + "type": "string", + "format": "uri" + }, + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "itsm_record_updated_payload": { + "type": "object", + "description": "Existing ITSM record updated.", + "required": [ + "itsm_provider_uuid", + "record_type", + "record_id" + ], + "additionalProperties": false, + "properties": { + "itsm_provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "record_type": { + "type": "string" + }, + "record_id": { + "type": "string" + }, + "updated_fields": { + "type": "object", + "additionalProperties": true + } + } + }, + "itsm_record_failed_payload": { + "type": "object", + "description": "ITSM record creation or update failed.", + "required": [ + "itsm_provider_uuid", + "action", + "error_message" + ], + "additionalProperties": false, + "properties": { + "itsm_provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "action": { + "type": "string" + }, + "error_message": { + "type": "string" + }, + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "accreditation_verified_payload": { + "type": "object", + "description": "Accreditation verified against external source. See data-model/47-accreditation-monitor.md.", + "required": [ + "accreditation_uuid", + "verification_tier", + "external_status" + ], + "additionalProperties": false, + "properties": { + "accreditation_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "verification_tier": { + "type": "string", + "enum": [ + "external_registry", + "document_currency", + "contract_webhook", + "expiry_only" + ] + }, + "external_status": { + "type": "string" + }, + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "accreditation_status_changed_payload": { + "type": "object", + "description": "Accreditation status changed \u2014 may require review (ACM-002).", + "required": [ + "accreditation_uuid", + "previous_status", + "new_status" + ], + "additionalProperties": false, + "properties": { + "accreditation_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "previous_status": { + "type": "string" + }, + "new_status": { + "type": "string" + }, + "change_source": { + "type": "string", + "enum": [ + "external_registry", + "document_check", + "contract_event", + "manual", + "expiry" + ] + } + } + }, + "accreditation_registry_mismatch_payload": { + "type": "object", + "description": "External registry reports status that differs from DCM record.", + "required": [ + "accreditation_uuid", + "dcm_status", + "registry_status" + ], + "additionalProperties": false, + "properties": { + "accreditation_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "dcm_status": { + "type": "string" + }, + "registry_status": { + "type": "string" + }, + "registry_url": { + "type": "string", + "format": "uri" + } + } + }, + "accreditation_verification_stale_payload": { + "type": "object", + "description": "Accreditation verification has exceeded stale_after threshold without successful check.", + "required": [ + "accreditation_uuid", + "last_verified_at", + "stale_after" + ], + "additionalProperties": false, + "properties": { + "accreditation_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "last_verified_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "stale_after": { + "$ref": "dcm-common.json#/$defs/iso8601_duration" + }, + "failure_count": { + "type": "integer" + } + } + }, + "accreditation_document_expired_payload": { + "type": "object", + "description": "Accreditation supporting document has exceeded max_age threshold.", + "required": [ + "accreditation_uuid", + "document_date", + "max_age" + ], + "additionalProperties": false, + "properties": { + "accreditation_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "document_date": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "max_age": { + "$ref": "dcm-common.json#/$defs/iso8601_duration" + } + } + }, + "accreditation_contract_event_payload": { + "type": "object", + "description": "Contract management system reported a change (BAA signed, amended, or terminated).", + "required": [ + "accreditation_uuid", + "contract_action" + ], + "additionalProperties": false, + "properties": { + "accreditation_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "contract_action": { + "type": "string", + "enum": [ + "signed", + "amended", + "terminated" + ] + }, + "contract_system": { + "type": "string" + }, + "contract_id": { + "type": "string" + } + } + }, + "accreditation_expiry_approaching_payload": { + "type": "object", + "description": "Accreditation approaching declared expiry date (P90D warning per default).", + "required": [ + "accreditation_uuid", + "expires_at", + "days_remaining" + ], + "additionalProperties": false, + "properties": { + "accreditation_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "expires_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "days_remaining": { + "type": "integer" + } + } + }, + "request_scheduled_payload": { + "type": "object", + "description": "Request accepted with deferred dispatch. See data-model/37-scheduled-requests.md.", + "required": [ + "request_uuid", + "entity_uuid", + "schedule" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "schedule": { + "type": "object", + "additionalProperties": true + } + } + }, + "request_schedule_cancelled_payload": { + "type": "object", + "description": "Scheduled request cancelled before dispatch.", + "required": [ + "request_uuid", + "entity_uuid", + "cancelled_by" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "cancelled_by": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "request_schedule_deadline_missed_payload": { + "type": "object", + "description": "Scheduled request passed not_after deadline without dispatch. Terminal FAILED (SCH-005).", + "required": [ + "request_uuid", + "entity_uuid", + "not_after" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "not_after": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + } + } + }, + "request_pending_dependency_payload": { + "type": "object", + "description": "Request waiting for dependency in a request group. See data-model/38-request-dependency-graph.md.", + "required": [ + "request_uuid", + "group_uuid", + "depends_on_request_uuid" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "group_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "depends_on_request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "wait_for": { + "type": "string", + "enum": [ + "acknowledged", + "approved", + "dispatched", + "realized" + ], + "default": "realized" + } + } + }, + "request_dependency_met_payload": { + "type": "object", + "description": "Dependency condition satisfied \u2014 dependent request proceeding.", + "required": [ + "request_uuid", + "group_uuid", + "dependency_request_uuid" + ], + "additionalProperties": false, + "properties": { + "request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "group_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "dependency_request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "injected_fields": { + "type": "object", + "additionalProperties": true + } + } + }, + "request_group_completed_payload": { + "type": "object", + "description": "All requests in a dependency group have reached terminal state successfully.", + "required": [ + "group_uuid", + "total_requests", + "completed_count" + ], + "additionalProperties": false, + "properties": { + "group_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "total_requests": { + "type": "integer" + }, + "completed_count": { + "type": "integer" + } + } + }, + "request_group_failed_payload": { + "type": "object", + "description": "Request group failed \u2014 dependency failure or group timeout.", + "required": [ + "group_uuid", + "failure_reason" + ], + "additionalProperties": false, + "properties": { + "group_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "failure_reason": { + "type": "string", + "enum": [ + "dependency_failed", + "group_timeout" + ] + }, + "failed_request_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "cancelled_count": { + "type": "integer" + } + } + }, + "dcm_event": { + "type": "object", + "description": "A DCM event. The base envelope is always present. The payload object schema is determined by event_type.", + "allOf": [ + { + "$ref": "#/$defs/base_event_envelope" + } + ], + "unevaluatedProperties": false + }, + "subscription_created_payload": { + "type": "object", + "description": "New subscription created (PENDING state). See data-model/50-subscription-lifecycle.md.", + "required": [ + "subscription_uuid", + "tenant_uuid", + "catalog_item_uuid", + "tier" + ], + "additionalProperties": false, + "properties": { + "subscription_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "tenant_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "catalog_item_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "tier": { + "type": "string" + }, + "consumption_model": { + "type": "string" + } + } + }, + "subscription_activated_payload": { + "type": "object", + "required": [ + "subscription_uuid", + "provider_uuid" + ], + "additionalProperties": false, + "properties": { + "subscription_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "managed_entity_count": { + "type": "integer" + } + } + }, + "subscription_suspended_payload": { + "type": "object", + "required": [ + "subscription_uuid", + "suspended_by", + "reason" + ], + "additionalProperties": false, + "properties": { + "subscription_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "suspended_by": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "reason": { + "type": "string" + } + } + }, + "subscription_resumed_payload": { + "type": "object", + "required": [ + "subscription_uuid" + ], + "additionalProperties": false, + "properties": { + "subscription_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "resumed_by": { + "$ref": "dcm-common.json#/$defs/uuid" + } + } + }, + "subscription_renewed_payload": { + "type": "object", + "required": [ + "subscription_uuid", + "new_terms_version", + "new_expires_at" + ], + "additionalProperties": false, + "properties": { + "subscription_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "new_terms_version": { + "type": "string" + }, + "new_expires_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "previous_terms_version": { + "type": "string" + } + } + }, + "subscription_renewal_failed_payload": { + "type": "object", + "required": [ + "subscription_uuid", + "failure_reason" + ], + "additionalProperties": false, + "properties": { + "subscription_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "failure_reason": { + "type": "string" + } + } + }, + "subscription_tier_changed_payload": { + "type": "object", + "required": [ + "subscription_uuid", + "previous_tier", + "new_tier" + ], + "additionalProperties": false, + "properties": { + "subscription_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "previous_tier": { + "type": "string" + }, + "new_tier": { + "type": "string" + } + } + }, + "subscription_update_applied_payload": { + "type": "object", + "required": [ + "subscription_uuid", + "update_uuid", + "entity_uuid", + "channel" + ], + "additionalProperties": false, + "properties": { + "subscription_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "update_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "entity_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "channel": { + "type": "string" + }, + "auto_applied": { + "type": "boolean" + } + } + }, + "subscription_update_rejected_payload": { + "type": "object", + "required": [ + "subscription_uuid", + "update_uuid", + "rejected_by" + ], + "additionalProperties": false, + "properties": { + "subscription_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "update_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "rejected_by": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "reason": { + "type": "string" + } + } + }, + "subscription_expiry_approaching_payload": { + "type": "object", + "required": [ + "subscription_uuid", + "expires_at", + "days_remaining" + ], + "additionalProperties": false, + "properties": { + "subscription_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "expires_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "days_remaining": { + "type": "integer" + }, + "auto_renew": { + "type": "boolean" + } + } + }, + "subscription_expired_payload": { + "type": "object", + "required": [ + "subscription_uuid", + "grace_period_ends" + ], + "additionalProperties": false, + "properties": { + "subscription_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "grace_period_ends": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + }, + "managed_entity_count": { + "type": "integer" + } + } + }, + "subscription_cancelled_payload": { + "type": "object", + "required": [ + "subscription_uuid", + "cancelled_by" + ], + "additionalProperties": false, + "properties": { + "subscription_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "cancelled_by": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "reason": { + "type": "string" + }, + "grace_period_ends": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + } + } + }, + "subscription_decommissioned_payload": { + "type": "object", + "required": [ + "subscription_uuid", + "entities_decommissioned" + ], + "additionalProperties": false, + "properties": { + "subscription_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "entities_decommissioned": { + "type": "integer" + } + } + }, + "override_first_approval_payload": { + "type": "object", + "properties": { + "override_request_uuid": { + "type": "string", + "format": "uuid" + }, + "approver_uuid": { + "type": "string", + "format": "uuid" + }, + "approver_role": { + "type": "string" + }, + "awaiting_second": { + "type": "boolean" + } + }, + "required": [ + "override_request_uuid", + "approver_uuid" + ] + }, + "override_approved_payload": { + "type": "object", + "properties": { + "override_request_uuid": { + "type": "string", + "format": "uuid" + }, + "request_uuid": { + "type": "string", + "format": "uuid" + }, + "approvers": { + "type": "array", + "items": { + "type": "object" + } + }, + "compensating_controls": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "override_request_uuid", + "request_uuid" + ] + }, + "override_rejected_payload": { + "type": "object", + "properties": { + "override_request_uuid": { + "type": "string", + "format": "uuid" + }, + "rejector_uuid": { + "type": "string", + "format": "uuid" + }, + "reason": { + "type": "string" + } + }, + "required": [ + "override_request_uuid", + "rejector_uuid" + ] + }, + "override_expired_payload": { + "type": "object", + "properties": { + "override_request_uuid": { + "type": "string", + "format": "uuid" + }, + "timeout_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "override_request_uuid" + ] + }, + "request_policy_blocked_payload": { + "type": "object", + "properties": { + "request_uuid": { + "type": "string", + "format": "uuid" + }, + "blocking_details": { + "type": "array", + "items": { + "type": "object" + } + }, + "resolution_options": { + "type": "object" + }, + "timeout_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "request_uuid", + "blocking_details", + "resolution_options" + ] + }, + "request_resolution_chosen_payload": { + "type": "object", + "properties": { + "request_uuid": { + "type": "string", + "format": "uuid" + }, + "action": { + "type": "string", + "enum": [ + "modify", + "request_override", + "cancel", + "escalate" + ] + }, + "modifications": { + "type": "object" + }, + "justification": { + "type": "string" + } + }, + "required": [ + "request_uuid", + "action" + ] + }, + "request_modified_resubmit_payload": { + "type": "object", + "properties": { + "request_uuid": { + "type": "string", + "format": "uuid" + }, + "modified_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "previous_blocking_policy": { + "type": "string" + } + }, + "required": [ + "request_uuid", + "modified_fields" + ] + }, + "override_requested_payload": { + "type": "object", + "properties": { + "override_request_uuid": { + "type": "string", + "format": "uuid" + }, + "request_uuid": { + "type": "string", + "format": "uuid" + }, + "blocking_policy_handle": { + "type": "string" + }, + "consumer_justification": { + "type": "string" + }, + "required_approval_type": { + "type": "string", + "enum": [ + "single", + "dual" + ] + }, + "eligible_approver_roles": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "override_request_uuid", + "request_uuid", + "blocking_policy_handle" + ] + } + }, + "$ref": "#/$defs/dcm_event" +} \ No newline at end of file diff --git a/schemas/jsonschema/dcm-policies.json b/schemas/jsonschema/dcm-policies.json new file mode 100644 index 0000000..6e1cdcd --- /dev/null +++ b/schemas/jsonschema/dcm-policies.json @@ -0,0 +1,652 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://dcm-project.io/schemas/policies/v1", + "title": "DCM Policy Schemas", + "description": "JSON Schema definitions for all seven DCM policy types and their output schemas. See data-model/B-policy-contract.md and data-model/14-policy-profiles.md.", + + "$defs": { + + "policy_type": { + "type": "string", + "enum": [ + "gating", + "validation", + "transformation", + "recovery", + "orchestration_flow", + "governance_matrix_rule", + "lifecycle", + "itsm_action" + ] + }, + + "concern_type": { + "type": "string", + "enum": [ + "security", + "compliance", + "operational", + "recovery_posture", + "zero_trust_posture", + "data_authorization_boundary", + "orchestration_flow" + ] + }, + + "policy_domain": { + "type": "string", + "enum": ["system", "platform", "tenant", "resource_type", "entity"] + }, + + "payload_type": { + "type": "string", + "enum": [ + "request.initiated", + "request.layers_assembled", + "request.policy_evaluated", + "request.placement_resolved", + "request.dispatched", + "resource.state_changed", + "resource.drift_detected", + "provider.registered", + "provider.health_changed", + "contribution.submitted", + "contribution.approved" + ], + "description": "Closed vocabulary of DCM pipeline events that policies can match against." + }, + + "match_condition": { + "type": "object", + "required": ["field", "operator"], + "additionalProperties": false, + "properties": { + "field": { + "type": "string", + "description": "Dot-notation path into the payload (e.g., request.resource_type, resource.lifecycle_state)" + }, + "operator": { + "type": "string", + "enum": ["equals", "not_equals", "in", "not_in", "minimum", "maximum", "contains", "matches"] + }, + "value": { + "description": "Comparison value. Type must match the referenced field." + } + } + }, + + "model_a_match": { + "type": "object", + "title": "Model A Match — Payload type and field conditions", + "description": "Used by pipeline policies: Validation, Transformation, Recovery, Orchestration Flow.", + "required": ["payload_type"], + "additionalProperties": false, + "properties": { + "payload_type": { "$ref": "#/$defs/payload_type" }, + "conditions": { + "type": "array", + "items": { "$ref": "#/$defs/match_condition" } + }, + "condition_logic": { + "type": "string", + "enum": ["all", "any"], + "default": "all" + } + } + }, + + "subject_match": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": ["human_actor", "system_component", "provider", "peer_dcm"] + }, + "identity": { "type": "object", "additionalProperties": true }, + "tenant": { "type": "object", "additionalProperties": true } + } + }, + + "data_match": { + "type": "object", + "additionalProperties": false, + "properties": { + "classification": { "$ref": "dcm-common.json#/$defs/data_classification" }, + "resource_type": { "$ref": "dcm-common.json#/$defs/resource_type_fqn" }, + "field_paths": { + "type": "object", + "required": ["mode"], + "additionalProperties": false, + "properties": { + "mode": { "type": "string", "enum": ["allowlist", "blocklist"] }, + "paths": { "type": "array", "items": { "type": "string" } } + } + }, + "capability": { "type": "string" } + } + }, + + "target_match": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { "type": "string", "enum": ["provider", "peer_dcm", "data_store"] }, + "sovereignty_zone": { + "type": "object", + "properties": { + "match": { "type": "string", "description": "Zone ID or pattern" } + } + }, + "accreditation_held": { + "type": "object", + "properties": { + "includes": { "type": "array", "items": { "type": "string" } } + } + }, + "trust_posture": { "type": "string", "enum": ["verified", "vouched", "provisional"] } + } + }, + + "context_match": { + "type": "object", + "additionalProperties": false, + "properties": { + "profile": { + "type": "object", + "properties": { + "deployment_posture": { "$ref": "dcm-common.json#/$defs/deployment_posture" } + } + }, + "zero_trust_posture": { + "type": "object", + "properties": { + "minimum": { + "type": "string", + "enum": ["none", "boundary", "full", "hardware_attested"] + } + } + }, + "federated": { "type": "boolean" } + } + }, + + "model_b_match": { + "type": "object", + "title": "Model B Match — Four-axis boundary conditions", + "description": "Used by boundary policies: Governance Matrix Rules.", + "additionalProperties": false, + "properties": { + "subject": { "$ref": "#/$defs/subject_match" }, + "data": { "$ref": "#/$defs/data_match" }, + "target": { "$ref": "#/$defs/target_match" }, + "context": { "$ref": "#/$defs/context_match" } + } + }, + + "base_policy_artifact": { + "type": "object", + "description": "Fields common to all policy types.", + "required": [ + "artifact_metadata", + "policy_type", + "concern_type", + "domain", + "enforcement" + ], + "properties": { + "artifact_metadata": { "$ref": "dcm-common.json#/$defs/artifact_metadata" }, + "policy_type": { "$ref": "#/$defs/policy_type" }, + "concern_type": { "$ref": "#/$defs/concern_type" }, + "domain": { "$ref": "#/$defs/policy_domain" }, + "enforcement": { + "type": "string", + "enum": ["hard", "soft"], + "description": "hard: cannot be relaxed by any downstream rule. soft: downstream rules may tighten but not relax." + }, + "shadow_mode": { + "type": "boolean", + "default": false, + "description": "When true, policy evaluates but does not enforce. Results written to Validation Store only." + }, + "description": { "type": "string" }, + "rationale": { "type": "string", "description": "Why this policy exists; links to regulatory or operational requirement" } + } + }, + + "gating_output": { + "type": "object", + "title": "Validation Policy Output Schema (compliance/operational enforcement_class)", + "description": "Binary allow/deny decision. Compliance enforcement_class: always fail-safe (deny on error). Operational enforcement_class: contributes risk_score_contribution to aggregate score.", + "required": ["decision", "enforcement_class"], + "additionalProperties": false, + "properties": { + "decision": { + "type": "string", + "enum": ["allow", "deny"] + }, + "enforcement_class": { + "type": "string", + "enum": ["compliance", "operational"], + "description": "compliance: boolean deny, fail-safe, for regulatory mandates. operational: contributes weighted risk score." + }, + "risk_score_contribution": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Required when enforcement_class is 'operational'. Weight contributed to aggregate risk score." + }, + "reason": { + "type": "string", + "description": "Human-readable reason for the decision. Required when decision is 'deny'." + }, + "rule_ref": { + "type": "string", + "description": "Reference to the specific rule within this policy that produced the decision." + } + }, + "if": { "properties": { "enforcement_class": { "const": "operational" } } }, + "then": { "required": ["risk_score_contribution"] } + }, + + "validation_output": { + "type": "object", + "title": "Validation Output Schema", + "description": "Field-level validation result. Pass/fail with field-specific detail.", + "required": ["result"], + "additionalProperties": false, + "properties": { + "result": { + "type": "string", + "enum": ["pass", "fail"] + }, + "field_results": { + "type": "array", + "items": { + "type": "object", + "required": ["field", "result"], + "additionalProperties": false, + "properties": { + "field": { "type": "string" }, + "result": { "type": "string", "enum": ["pass", "fail"] }, + "message": { "type": "string" }, + "value_received": { }, + "constraint_violated": { "type": "string" } + } + } + }, + "message": { "type": "string" } + } + }, + + "transformation_mutation": { + "type": "object", + "required": ["field", "operation"], + "additionalProperties": false, + "properties": { + "field": { "type": "string", "description": "Dot-notation field path to modify" }, + "operation": { + "type": "string", + "enum": ["set", "append", "remove", "redact", "default_if_absent"] + }, + "value": { "description": "New value for set/append/default_if_absent operations" }, + "provenance_basis": { + "type": "string", + "description": "Recorded as the provenance basis for the transformed field value" + } + } + }, + + "transformation_output": { + "type": "object", + "title": "Transformation Output Schema", + "description": "Data enrichment or mutation. Produces an ordered list of field mutations applied to the request payload.", + "required": ["mutations"], + "additionalProperties": false, + "properties": { + "mutations": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/transformation_mutation" } + }, + "description": { "type": "string" } + } + }, + + "recovery_output": { + "type": "object", + "title": "Recovery Output Schema", + "description": "Recovery action to take when a trigger condition is detected.", + "required": ["trigger", "action"], + "additionalProperties": false, + "properties": { + "trigger": { + "type": "string", + "enum": [ + "realization_failed", + "provider_timeout", + "provider_unavailable", + "drift_detected_critical", + "drift_detected_significant", + "accreditation_gap", + "sovereignty_conflict", + "dependency_failed", + "process_timeout", + "health_check_failed" + ], + "description": "Closed vocabulary of conditions that activate this recovery policy." + }, + "action": { + "type": "string", + "enum": [ + "retry", + "requeue", + "rehydrate", + "notify_and_wait", + "escalate", + "revert_to_requested", + "discard", + "alert_only" + ], + "description": "Closed vocabulary of recovery actions." + }, + "params": { + "type": "object", + "additionalProperties": true, + "description": "Action-specific parameters (e.g., max_retries, retry_interval, escalation_target)" + }, + "max_retries": { "type": "integer", "minimum": 0 }, + "retry_interval": { "$ref": "dcm-common.json#/$defs/iso8601_duration" }, + "timeout": { "$ref": "dcm-common.json#/$defs/iso8601_duration" } + } + }, + + "orchestration_step": { + "type": "object", + "required": ["step_id", "step_type"], + "additionalProperties": false, + "properties": { + "step_id": { "type": "string" }, + "step_type": { + "type": "string", + "enum": [ + "provider_dispatch", + "policy_evaluation", + "placement_resolution", + "layer_assembly", + "approval_gate", + "notification", + "dependency_wait", + "parallel_group" + ] + }, + "description": { "type": "string" }, + "depends_on": { + "type": "array", + "items": { "type": "string" }, + "description": "step_ids that must complete before this step executes" + }, + "params": { "type": "object", "additionalProperties": true }, + "on_failure": { "$ref": "#/$defs/recovery_output" } + } + }, + + "orchestration_flow_output": { + "type": "object", + "title": "Orchestration Flow Output Schema", + "description": "Named, ordered workflow. Declares the explicit step sequence for a request type. Evaluated by the Request Orchestrator.", + "required": ["flow_name", "ordered", "steps"], + "additionalProperties": false, + "properties": { + "flow_name": { "type": "string" }, + "ordered": { "type": "boolean", "const": true }, + "steps": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/orchestration_step" } + }, + "timeout": { "$ref": "dcm-common.json#/$defs/iso8601_duration" } + } + }, + + "governance_matrix_decision": { + "type": "string", + "enum": ["ALLOW", "DENY", "DENY_REQUEST", "STRIP_FIELD", "REDACT", "REQUIRE_APPROVAL"] + }, + + "governance_matrix_rule_output": { + "type": "object", + "title": "Governance Matrix Rule Output Schema", + "description": "Cross-boundary access control decision. Uses four-axis match (Model B). Produces an ALLOW/DENY/STRIP/REDACT decision. See data-model/27-governance-matrix.md.", + "required": ["decision"], + "additionalProperties": false, + "properties": { + "decision": { "$ref": "#/$defs/governance_matrix_decision" }, + "field_permissions": { + "type": "object", + "additionalProperties": false, + "description": "Per-field decisions when decision is STRIP_FIELD or REDACT.", + "properties": { + "mode": { "type": "string", "enum": ["allowlist", "blocklist"] }, + "fields": { + "type": "array", + "items": { + "type": "object", + "required": ["field_path", "action"], + "properties": { + "field_path": { "type": "string" }, + "action": { + "type": "string", + "enum": ["STRIP_FIELD", "REDACT", "DENY_REQUEST", "ALLOW"] + } + } + } + } + } + }, + "reason": { "type": "string" }, + "rule_ref": { "type": "string" } + } + }, + + "lifecycle_event_trigger": { + "type": "string", + "enum": [ + "entity.created", + "entity.state_changed", + "entity.relationship_added", + "entity.relationship_removed", + "entity.decommissioned", + "entity.ttl_expiring", + "entity.ownership_transferred" + ] + }, + + "lifecycle_policy_output": { + "type": "object", + "title": "Lifecycle Policy Output Schema", + "description": "Declarative rules governing entity lifecycle transitions and relationship events.", + "required": ["trigger", "action"], + "additionalProperties": false, + "properties": { + "trigger": { "$ref": "#/$defs/lifecycle_event_trigger" }, + "condition": { "type": "string", "description": "Optional condition expression on trigger payload" }, + "action": { + "type": "string", + "enum": [ + "notify", + "gate", + "cascade_decommission", + "release_relationship", + "escalate", + "enforce_retention" + ] + }, + "params": { "type": "object", "additionalProperties": true } + } + }, + + "gating_policy": { + "allOf": [ + { "$ref": "#/$defs/base_policy_artifact" }, + { + "type": "object", + "required": ["match", "output"], + "properties": { + "policy_type": { "const": "gating" }, + "match": { "$ref": "#/$defs/model_a_match" }, + "output": { "$ref": "#/$defs/gating_output" } + } + } + ] + }, + + "validation_policy": { + "allOf": [ + { "$ref": "#/$defs/base_policy_artifact" }, + { + "type": "object", + "required": ["match", "output"], + "properties": { + "policy_type": { "const": "validation" }, + "match": { "$ref": "#/$defs/model_a_match" }, + "output": { "$ref": "#/$defs/validation_output" } + } + } + ] + }, + + "transformation_policy": { + "allOf": [ + { "$ref": "#/$defs/base_policy_artifact" }, + { + "type": "object", + "required": ["match", "output"], + "properties": { + "policy_type": { "const": "transformation" }, + "match": { "$ref": "#/$defs/model_a_match" }, + "output": { "$ref": "#/$defs/transformation_output" } + } + } + ] + }, + + "recovery_policy": { + "allOf": [ + { "$ref": "#/$defs/base_policy_artifact" }, + { + "type": "object", + "required": ["match", "output"], + "properties": { + "policy_type": { "const": "recovery" }, + "match": { "$ref": "#/$defs/model_a_match" }, + "output": { "$ref": "#/$defs/recovery_output" } + } + } + ] + }, + + "orchestration_flow_policy": { + "allOf": [ + { "$ref": "#/$defs/base_policy_artifact" }, + { + "type": "object", + "required": ["match", "output"], + "properties": { + "policy_type": { "const": "orchestration_flow" }, + "match": { "$ref": "#/$defs/model_a_match" }, + "output": { "$ref": "#/$defs/orchestration_flow_output" } + } + } + ] + }, + + "governance_matrix_rule_policy": { + "allOf": [ + { "$ref": "#/$defs/base_policy_artifact" }, + { + "type": "object", + "required": ["match", "output"], + "properties": { + "policy_type": { "const": "governance_matrix_rule" }, + "match": { "$ref": "#/$defs/model_b_match" }, + "output": { "$ref": "#/$defs/governance_matrix_rule_output" } + } + } + ] + }, + + "lifecycle_policy": { + "allOf": [ + { "$ref": "#/$defs/base_policy_artifact" }, + { + "type": "object", + "required": ["output"], + "properties": { + "policy_type": { "const": "lifecycle" }, + "output": { "$ref": "#/$defs/lifecycle_policy_output" } + } + } + ] + }, + + "itsm_action_policy_output": { + "type": "object", + "description": "ITSM Action Policy output — side-effect policy that fires on DCM events and triggers ITSM actions. Non-blocking by default (ITSM-002). See data-model/42-itsm-integration.md.", + "required": ["itsm_provider_uuid", "action", "action_payload"], + "additionalProperties": false, + "properties": { + "itsm_provider_uuid": { "$ref": "dcm-common.json#/$defs/uuid" }, + "action": { "type": "string", "enum": ["create_change_request", "update_change_request", "close_change_request", "create_incident", "update_incident", "close_incident", "create_cmdb_ci", "update_cmdb_ci", "retire_cmdb_ci", "create_service_request"] }, + "action_payload": { "type": "object", "additionalProperties": true, "description": "Template payload — supports {{ field }} expressions resolved at evaluation time" }, + "store_reference_on_entity": { "type": "boolean", "default": true, "description": "Store ITSM record reference in entity business data (ITSM-004)" }, + "block_until_created": { "type": "boolean", "default": false, "description": "If true, block pipeline until ITSM record is created. Requires block_timeout (ITSM-005)." }, + "block_timeout": { "$ref": "dcm-common.json#/$defs/iso8601_duration", "description": "Required when block_until_created is true. Pipeline never permanently stalled." }, + "on_failure": { "type": "string", "enum": ["warn_and_continue", "fail_request"], "default": "warn_and_continue" } + } + }, + + "itsm_action_policy": { + "allOf": [ + { "$ref": "#/$defs/base_policy_artifact" }, + { + "type": "object", + "required": ["output"], + "properties": { + "policy_type": { "const": "itsm_action" }, + "output": { "$ref": "#/$defs/itsm_action_policy_output" } + } + } + ] + }, + + "dcm_policy": { + "oneOf": [ + { "$ref": "#/$defs/gating_policy" }, + { "$ref": "#/$defs/validation_policy" }, + { "$ref": "#/$defs/transformation_policy" }, + { "$ref": "#/$defs/recovery_policy" }, + { "$ref": "#/$defs/orchestration_flow_policy" }, + { "$ref": "#/$defs/governance_matrix_rule_policy" }, + { "$ref": "#/$defs/lifecycle_policy" }, + { "$ref": "#/$defs/itsm_action_policy" } + ], + "discriminator": { + "propertyName": "policy_type", + "mapping": { + "gating": "#/$defs/gating_policy", + "validation": "#/$defs/validation_policy", + "transformation": "#/$defs/transformation_policy", + "recovery": "#/$defs/recovery_policy", + "orchestration_flow": "#/$defs/orchestration_flow_policy", + "governance_matrix_rule":"#/$defs/governance_matrix_rule_policy", + "lifecycle": "#/$defs/lifecycle_policy", + "itsm_action": "#/$defs/itsm_action_policy" + } + } + } + + }, + + "$ref": "#/$defs/dcm_policy" +} diff --git a/schemas/jsonschema/dcm-providers.json b/schemas/jsonschema/dcm-providers.json new file mode 100644 index 0000000..813ab39 --- /dev/null +++ b/schemas/jsonschema/dcm-providers.json @@ -0,0 +1,513 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://dcm-project.io/schemas/providers/v1", + "title": "DCM Provider Schemas", + "description": "JSON Schema for DCM Provider registration and capability declarations. A provider is not a fixed type — it declares capabilities (verb × domain) and occupies non-exclusive capability categories (ADR-PROV-002; croadfeldt/udlm capability-discovery.md); the legacy labels service/information/auth/peer_dcm/process are convenience profiles. Composite services are a Data concept orchestrated by the Control Plane, not a provider type. Two policy evaluation modes: Internal (DCM evaluates via OPA) and External (external provider evaluates).", + "$defs": { + "provider_type": { + "type": "string", + "enum": [ + "service", + "information", + "auth", + "peer_dcm", + "process" + ], + "description": "DCM provider types. Service providers handle full resource lifecycle including credentials and notifications via resource_type declarations. Auth providers enable pluggable multi-IdP authentication with tenant routing. Process providers execute ephemeral workflows." + }, + "provider_status": { + "type": "string", + "enum": [ + "SUBMITTED", + "VALIDATING", + "PENDING_APPROVAL", + "ACTIVE", + "SUSPENDED", + "DEREGISTERING", + "DEREGISTERED", + "FORCED_DEREGISTERED" + ] + }, + "trust_posture": { + "type": "string", + "enum": [ + "verified", + "vouched", + "provisional" + ] + }, + "provider_health_response": { + "type": "object", + "required": [ + "status", + "version" + ], + "additionalProperties": false, + "properties": { + "status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "unhealthy" + ] + }, + "version": { + "type": "string" + }, + "capabilities_available": { + "type": "array", + "items": { + "type": "string" + } + }, + "details": { + "type": "object", + "additionalProperties": true, + "description": "Provider-specific detail. DCM treats as opaque." + } + } + }, + "certificate_declaration": { + "type": "object", + "required": [ + "pem", + "ca_chain", + "rotation_interval" + ], + "additionalProperties": false, + "properties": { + "pem": { + "type": "string", + "description": "PEM-encoded provider certificate" + }, + "ca_chain": { + "type": "string", + "description": "PEM-encoded CA chain" + }, + "rotation_interval": { + "$ref": "dcm-common.json#/$defs/iso8601_duration", + "description": "How frequently the provider rotates this certificate" + }, + "expires_at": { + "$ref": "dcm-common.json#/$defs/iso8601_datetime" + } + } + }, + "base_provider_registration": { + "type": "object", + "title": "Base Provider Registration", + "description": "Fields required in every provider registration regardless of type.", + "required": [ + "artifact_metadata", + "provider_type_id", + "display_name", + "sovereignty_declaration", + "health_endpoint", + "certificate", + "capability_extension" + ], + "properties": { + "artifact_metadata": { + "$ref": "dcm-common.json#/$defs/artifact_metadata" + }, + "provider_type_id": { + "$ref": "#/$defs/provider_type" + }, + "display_name": { + "type": "string", + "maxLength": 256 + }, + "description": { + "type": "string" + }, + "status": { + "$ref": "#/$defs/provider_status" + }, + "sovereignty_declaration": { + "$ref": "dcm-common.json#/$defs/sovereignty_declaration" + }, + "accreditations": { + "type": "array", + "items": { + "$ref": "dcm-common.json#/$defs/accreditation_ref" + } + }, + "health_endpoint": { + "type": "string", + "format": "uri" + }, + "health_poll_interval": { + "$ref": "dcm-common.json#/$defs/iso8601_duration", + "default": "PT5M" + }, + "failure_threshold": { + "type": "integer", + "minimum": 1, + "default": 3, + "description": "Consecutive failed health checks before status transitions to DEGRADED" + }, + "certificate": { + "$ref": "#/$defs/certificate_declaration" + }, + "trust_score": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "DCM-computed trust score (0.0\u20131.0). Not set by provider; updated by DCM based on health, accreditation, and governance matrix checks." + }, + "capability_extension": { + "type": "object", + "description": "Provider-type-specific capability declaration. Schema determined by provider_type_id." + } + } + }, + "service_provider_capabilities": { + "type": "object", + "title": "Service Provider Capability Extension", + "description": "Capabilities for a Service Provider \u2014 realizes physical or virtual infrastructure resources.", + "required": [ + "offered_resource_types", + "services_endpoint" + ], + "additionalProperties": false, + "properties": { + "offered_resource_types": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "dcm-common.json#/$defs/resource_type_fqn" + }, + "description": "Resource types this provider can realize (e.g., Compute.VirtualMachine)" + }, + "services_endpoint": { + "type": "string", + "format": "uri" + }, + "discovery_endpoint": { + "type": "string", + "format": "uri" + }, + "reserve_query_endpoint": { + "type": "string", + "format": "uri" + }, + "max_concurrent_realizations": { + "type": "integer", + "minimum": 1 + }, + "realization_timeout_default": { + "$ref": "dcm-common.json#/$defs/iso8601_duration" + }, + "supports_suspension": { + "type": "boolean", + "default": false + }, + "supports_rehydration": { + "type": "boolean", + "default": false + }, + "cost_metadata": { + "type": "object", + "additionalProperties": true, + "description": "Provider-declared cost data consumed by the Cost Analysis component" + } + } + }, + "information_provider_capabilities": { + "type": "object", + "title": "Information Provider Capability Extension", + "required": [ + "authority_scope", + "interaction_model" + ], + "additionalProperties": false, + "properties": { + "authority_scope": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + }, + "description": "Field paths or resource type domains this provider has authority over (e.g., Network.VLAN.vlan_id)" + }, + "interaction_model": { + "type": "string", + "enum": [ + "push", + "pull", + "both" + ], + "description": "push: provider pushes updates to DCM. pull: DCM queries provider. both: supports either." + }, + "push_endpoint": { + "type": "string", + "format": "uri", + "description": "DCM endpoint where provider pushes updates (if interaction_model is push or both)" + }, + "pull_endpoint": { + "type": "string", + "format": "uri", + "description": "Provider endpoint DCM queries (if interaction_model is pull or both)" + }, + "write_back_endpoint": { + "type": "string", + "format": "uri", + "description": "Provider endpoint for DCM-initiated write-back operations" + }, + "schema_endpoint": { + "type": "string", + "format": "uri", + "description": "Where DCM retrieves the provider's extended field schema" + }, + "confidence_model": { + "type": "string", + "enum": [ + "declared", + "computed" + ], + "default": "declared" + }, + "max_staleness": { + "$ref": "dcm-common.json#/$defs/iso8601_duration" + } + } + }, + "composite_service_capabilities": { + "type": "object", + "title": "Composite Service Capability Extension", + "description": "Declares the catalog-level composite services this provider registers — multi-resource catalog items composed of declared constituent resource types. See data-model/30-composite-service-model.md.", + "required": [ + "offered_composite_types", + "constituent_providers" + ], + "additionalProperties": false, + "properties": { + "offered_composite_types": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "dcm-common.json#/$defs/resource_type_fqn" + } + }, + "constituent_providers": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "provider_uuid", + "role" + ], + "properties": { + "provider_uuid": { + "$ref": "dcm-common.json#/$defs/uuid" + }, + "role": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": true + } + } + } + }, + "composition_endpoint": { + "type": "string", + "format": "uri" + }, + "compensation_strategy": { + "type": "string", + "enum": [ + "rollback_all", + "rollback_failed", + "notify_and_wait", + "best_effort" + ], + "description": "What to do if a constituent fails mid-composition" + }, + "composition_visibility": { + "type": "string", + "enum": [ + "opaque", + "transparent", + "selective" + ] + } + } + }, + "peer_dcm_capabilities": { + "type": "object", + "title": "Peer DCM Capability Extension", + "description": "Another DCM instance participating in a federation. Enables cross-instance request routing, audit correlation, and drift detection.", + "required": [ + "dcm_api_endpoint", + "trust_posture" + ], + "additionalProperties": false, + "properties": { + "dcm_api_endpoint": { + "type": "string", + "format": "uri" + }, + "federation_endpoint": { + "type": "string", + "format": "uri" + }, + "audit_endpoint": { + "type": "string", + "format": "uri" + }, + "trust_posture": { + "$ref": "#/$defs/trust_posture" + }, + "dcm_version": { + "$ref": "dcm-common.json#/$defs/semver" + }, + "federation_scope": { + "type": "string", + "enum": [ + "full", + "read_only", + "audit_only", + "resource_routing_only" + ], + "description": "What this peer DCM is authorized to do in the federation" + }, + "sovereignty_declaration": { + "$ref": "dcm-common.json#/$defs/sovereignty_declaration" + } + } + }, + "capability_extension": { + "description": "Provider-type-specific capability declaration. Schema determined by provider_type.", + "oneOf": [ + { + "$ref": "#/$defs/service_provider_capabilities" + }, + { + "$ref": "#/$defs/information_provider_capabilities" + }, + { + "$ref": "#/$defs/composite_service_capabilities" + }, + { + "$ref": "#/$defs/auth_provider_capabilities" + }, + { + "$ref": "#/$defs/peer_dcm_capabilities" + }, + { + "$ref": "#/$defs/process_provider_capabilities" + } + ] + }, + "provider_registration": { + "allOf": [ + { + "$ref": "#/$defs/base_provider_registration" + } + ], + "unevaluatedProperties": false + }, + "process_provider_capabilities": { + "type": "object", + "description": "Capabilities for process providers \u2014 ephemeral workflow execution (software install, backup, migration, compliance scan).", + "required": [ + "supported_process_types" + ], + "additionalProperties": false, + "properties": { + "supported_process_types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "FQN process resource types this provider can execute" + }, + "max_concurrent_executions": { + "type": "integer" + }, + "timeout_default": { + "type": "string", + "description": "ISO 8601 duration" + }, + "idempotent": { + "type": "boolean", + "description": "Whether re-execution is safe" + }, + "automation_platform": { + "type": "string", + "description": "e.g., AAP, Tekton, ArgoWorkflows" + } + } + }, + "auth_provider_capabilities": { + "type": "object", + "description": "Capabilities for auth providers \u2014 authentication, identity, and MFA services. Multiple auth providers support tenant-routed authentication.", + "required": [ + "auth_modes", + "token_format" + ], + "additionalProperties": false, + "properties": { + "auth_modes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "oidc", + "saml", + "ldap", + "kerberos", + "mtls_cert" + ] + }, + "description": "Supported authentication protocols" + }, + "token_format": { + "type": "string", + "enum": [ + "jwt", + "opaque", + "saml_assertion" + ] + }, + "mfa_methods": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "totp", + "webauthn", + "hardware_token", + "push", + "sms" + ] + }, + "description": "Supported MFA methods" + }, + "rbac_model": { + "type": "string", + "enum": [ + "group_claims", + "role_claims", + "attribute_based" + ] + }, + "token_lifetime_seconds": { + "type": "integer" + }, + "supports_session_revocation": { + "type": "boolean" + }, + "federation_capable": { + "type": "boolean", + "description": "Can federate with peer auth providers" + } + } + } + }, + "$ref": "#/$defs/base_provider_registration" +} diff --git a/schemas/jsonschema/resource-type-spec-template.json b/schemas/jsonschema/resource-type-spec-template.json new file mode 100644 index 0000000..189996e --- /dev/null +++ b/schemas/jsonschema/resource-type-spec-template.json @@ -0,0 +1,364 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://dcm-project.io/schemas/resource-types/template/v1", + "title": "DCM Resource Type Extension Schema Template", + "description": "Template and authoring guide for Resource Type Extension schemas. Service Providers publish one schema per resource type they offer to the Resource Type Registry. DCM uses these schemas to validate request fields, populate the Service Catalog field explorer, and enforce field constraints. See data-model/05-resource-type-hierarchy.md and data-model/20-registry-governance.md.", + + "$defs": { + + "resource_type_spec": { + "type": "object", + "title": "Resource Type Specification", + "description": "The top-level object published to the Resource Type Registry for each resource type.", + "required": [ + "fqn", + "version", + "entity_type", + "ownership_model", + "display_name", + "description", + "registry_tier", + "spec_schema", + "lifecycle" + ], + "additionalProperties": false, + "properties": { + + "fqn": { + "type": "string", + "pattern": "^[A-Z][a-zA-Z0-9]+\\.[A-Z][a-zA-Z0-9]+$", + "description": "Fully-qualified resource type name. Format: .. Examples: Compute.VirtualMachine, Network.VLAN, Database.PostgreSQLInstance, Process.AnsiblePlaybook" + }, + + "version": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$", + "description": "Semantic version. Major version increments when the spec_schema has breaking changes (removed required fields, type changes). Minor for new optional fields. Patch for description/metadata updates." + }, + + "entity_type": { + "type": "string", + "enum": ["infrastructure_resource", "composite_resource", "process_resource"], + "description": "Which DCM entity type resources of this type produce. Determines lifecycle state machine, drift detection, TTL eligibility." + }, + + "ownership_model": { + "type": "string", + "enum": ["whole_allocation", "allocation", "shareable"], + "description": "whole_allocation: requesting Tenant owns the resource outright. allocation: resource is carved from a pool (declare pool_resource_type). shareable: multiple consumers reference the same resource." + }, + + "pool_resource_type": { + "type": "string", + "description": "Required when ownership_model is 'allocation'. FQN of the pool resource type this allocation comes from (e.g., if this type is Network.IPAddress, pool_resource_type might be Network.IPAddressPool)." + }, + + "allocatable_pool": { + "type": "boolean", + "default": false, + "description": "True if resources of this type act as allocation pools. When true, this resource type produces pool entities from which allocation entities are carved." + }, + + "display_name": { "type": "string", "maxLength": 64 }, + "description": { "type": "string", "maxLength": 1024 }, + "icon": { "type": "string", "format": "uri", "description": "URL to SVG icon for Service Catalog display" }, + "documentation": { "type": "string", "format": "uri", "description": "URL to operator documentation for this resource type" }, + + "registry_tier": { + "type": "string", + "enum": ["dcm_system", "verified_community", "organization"], + "description": "dcm_system: built-in DCM types. verified_community: reviewed by DCM project. organization: local to this DCM deployment." + }, + + "provider_uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the Service Provider that realizes this resource type." + }, + + "spec_schema": { + "type": "object", + "description": "JSON Schema for the resource-type-specific fields. These fields appear in the `spec` object on create/update requests and in the `realized_fields` on entities. Must be a valid JSON Schema.", + "$ref": "#/$defs/resource_spec_schema" + }, + + "constraint_visibility": { + "type": "string", + "enum": ["full", "partial", "none"], + "default": "full", + "description": "How much constraint detail is visible to consumers in the Service Catalog. full: all constraints shown. partial: labels shown, not values. none: field exists but constraints hidden." + }, + + "lifecycle": { + "type": "object", + "description": "Lifecycle configuration for this resource type.", + "required": ["supports_suspension", "supports_rehydration"], + "additionalProperties": false, + "properties": { + "supports_suspension": { "type": "boolean" }, + "supports_rehydration": { "type": "boolean" }, + "default_ttl": { "type": "string", "description": "ISO 8601 duration. If set, resources get this TTL by default." }, + "max_ttl": { "type": "string", "description": "ISO 8601 duration. Maximum allowed TTL." }, + "on_expiry_default": { "type": "string", "enum": ["decommission", "suspend", "notify", "escalate"] } + } + }, + + "drift_detection": { + "type": "object", + "description": "Drift detection configuration for this resource type.", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean", "default": true }, + "field_criticality": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": ["critical", "significant", "minor"] + }, + "description": "Map of field_path → criticality. Fields not listed default to 'minor'. Criticality × change magnitude = drift severity." + } + } + }, + + "relationships": { + "type": "object", + "description": "Permitted relationship types for resources of this type.", + "additionalProperties": false, + "properties": { + "requires": { + "type": "array", + "items": { "type": "string" }, + "description": "Resource type FQNs this resource type requires (hard dependencies)" + }, + "supports_references_to": { + "type": "array", + "items": { "type": "string" }, + "description": "Resource type FQNs this resource type may reference (soft dependencies)" + } + } + }, + + "catalog_metadata": { + "type": "object", + "description": "Service Catalog presentation metadata.", + "additionalProperties": false, + "properties": { + "category": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } }, + "cost_unit": { "type": "string", "description": "What the cost is denominated in (e.g., 'per vCPU/month')" }, + "typical_provisioning_time": { "type": "string", "description": "ISO 8601 duration estimate" } + } + }, + + "deprecated": { + "type": "object", + "description": "Present if this resource type is deprecated.", + "additionalProperties": false, + "properties": { + "since_version": { "type": "string" }, + "sunset_at": { "type": "string", "format": "date-time" }, + "successor_type": { "type": "string", "description": "FQN of the replacement resource type" }, + "migration_guide":{ "type": "string", "format": "uri" } + } + } + } + }, + + "resource_spec_schema": { + "type": "object", + "description": "The JSON Schema for resource-type-specific fields. This is an embedded JSON Schema document. It defines what fields appear in the `spec` object of create/update requests and in the `realized_fields` of entities.", + "required": ["type", "properties"], + "properties": { + "type": { "const": "object" }, + "properties": { "type": "object" }, + "required": { "type": "array", "items": { "type": "string" } }, + "additionalProperties": { "type": "boolean", "default": false } + } + }, + + "field_descriptor": { + "type": "object", + "description": "A field in the resource spec_schema. Extends standard JSON Schema with DCM-specific metadata.", + "properties": { + "type": { "type": "string" }, + "description": { "type": "string" }, + "x-dcm-editable": { + "type": "boolean", + "default": false, + "description": "Whether this field can be changed on a REALIZED/OPERATIONAL resource via PATCH /resources/{uuid}" + }, + "x-dcm-provider-only": { + "type": "boolean", + "default": false, + "description": "Field is populated by the provider on realization and cannot be set by the consumer on request" + }, + "x-dcm-immutable": { + "type": "boolean", + "default": false, + "description": "Field cannot be changed after initial request (not editable, not updated by provider)" + }, + "x-dcm-display": { + "type": "string", + "enum": ["visible", "hidden", "admin_only"], + "default": "visible", + "description": "Service Catalog visibility for this field" + }, + "x-dcm-constraint-basis": { + "type": "string", + "description": "Human-readable explanation of why this constraint exists (displayed in Service Catalog)" + }, + "x-dcm-example": { + "description": "Example value shown in Service Catalog field explorer" + } + } + } + + }, + + "type": "object", + "title": "Resource Type Extension Schema — Example (Compute.VirtualMachine)", + "description": "Example showing how a Service Provider would define the Resource Type Spec for Compute.VirtualMachine. Replace all fields with your resource type specifics.", + + "properties": { + "resource_type_spec": { "$ref": "#/$defs/resource_type_spec" } + }, + + "examples": [ + { + "resource_type_spec": { + "fqn": "Compute.VirtualMachine", + "version": "1.2.0", + "entity_type": "infrastructure_resource", + "ownership_model": "whole_allocation", + "display_name": "Virtual Machine", + "description": "A virtual machine instance on the organization's virtualization infrastructure.", + "registry_tier": "organization", + "provider_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + + "spec_schema": { + "type": "object", + "required": ["cpu_cores", "memory_gb", "os_image"], + "additionalProperties": false, + "properties": { + + "cpu_cores": { + "type": "integer", + "minimum": 1, + "maximum": 128, + "description": "Number of vCPUs", + "x-dcm-editable": true, + "x-dcm-display": "visible", + "x-dcm-constraint-basis": "Maximum set by hypervisor NUMA topology", + "x-dcm-example": 4 + }, + + "memory_gb": { + "type": "integer", + "minimum": 1, + "maximum": 1024, + "description": "RAM in gigabytes", + "x-dcm-editable": true, + "x-dcm-display": "visible", + "x-dcm-example": 8 + }, + + "os_image": { + "type": "string", + "description": "OS image identifier", + "x-dcm-editable": false, + "x-dcm-immutable": true, + "x-dcm-display": "visible", + "x-dcm-example": "rhel-9-approved-2026-03" + }, + + "os_image_version": { + "type": "string", + "description": "Current patched OS version", + "x-dcm-provider-only": true, + "x-dcm-display": "visible" + }, + + "network_segment_uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the Network.Segment resource to attach to", + "x-dcm-editable": false, + "x-dcm-immutable": true + }, + + "ip_address": { + "type": "string", + "format": "ipv4", + "description": "Assigned IP address (provider-assigned)", + "x-dcm-provider-only": true, + "x-dcm-display": "visible" + }, + + "hostname": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$", + "description": "VM hostname. If not provided, generated by the provider.", + "x-dcm-editable": false, + "x-dcm-example": "prod-app-01" + }, + + "storage_volumes": { + "type": "array", + "items": { + "type": "object", + "required": ["size_gb"], + "properties": { + "size_gb": { "type": "integer", "minimum": 10 }, + "type": { "type": "string", "enum": ["ssd", "hdd", "nvme"] }, + "mount_point": { "type": "string" } + } + }, + "x-dcm-editable": true, + "description": "Additional storage volumes. Root volume is always included." + }, + + "hypervisor_host": { + "type": "string", + "description": "Hypervisor host where the VM was placed", + "x-dcm-provider-only": true, + "x-dcm-display": "admin_only" + } + } + }, + + "lifecycle": { + "supports_suspension": true, + "supports_rehydration": true, + "default_ttl": null, + "on_expiry_default": "notify" + }, + + "drift_detection": { + "enabled": true, + "field_criticality": { + "cpu_cores": "significant", + "memory_gb": "significant", + "os_image_version": "critical", + "ip_address": "critical", + "network_segment_uuid":"critical", + "hostname": "significant", + "storage_volumes": "significant", + "hypervisor_host": "minor" + } + }, + + "relationships": { + "requires": ["Network.Segment"], + "supports_references_to": ["Network.IPAddressPool", "Storage.Volume"] + }, + + "catalog_metadata": { + "category": "Compute", + "tags": ["virtual-machine", "compute", "vm"], + "cost_unit": "per vCPU/month", + "typical_provisioning_time": "PT3M" + } + } + } + ] +} diff --git a/schemas/openapi/AEP-CONFORMANCE.md b/schemas/openapi/AEP-CONFORMANCE.md new file mode 100644 index 0000000..8a0430a --- /dev/null +++ b/schemas/openapi/AEP-CONFORMANCE.md @@ -0,0 +1,48 @@ +# DCM OpenAPI — AEP conformance + +DCM's public APIs adopt the **[API Enhancement Proposals](https://aep.dev/)** (AEP) — resource-oriented +design, the standard methods (Get/List/Create/Update/Delete), and the RFC 9457 error model — per +**ADR-AEP-001** (croadfeldt/udlm). Conformance is checked by the AEP **Spectral** OpenAPI ruleset. + +## Run the linter locally + +``` +npm install @stoplight/spectral-cli @aep_dev/aep-openapi-linter +npx spectral lint "schemas/openapi/*.yaml" --ruleset .spectral.yaml +``` + +CI runs the same lint on every PR that touches `schemas/openapi/**` (`.github/workflows/lint-openapi.yml`). +It is **advisory** today (`continue-on-error: true`) while the baseline below is burned down; flip it to +blocking once the error count reaches zero. + +## Baseline (2026-07-08, first run) + +| Spec | Errors | Warnings | Notes | +|------|--------|----------|-------| +| `dcm-consumer-api.yaml` | 144 | 236 | | +| `dcm-operator-api.yaml` | 32 | 24 | | +| `dcm-provider-callback-api.yaml` | 22 | 25 | | +| `dcm-admin-api.yaml` | — | — | **P0: invalid YAML — does not parse / lint (see below)** | + +### Top rule categories (across the three parseable specs) + +| Count | Rule(s) | Category | +|------:|---------|----------| +| ~110 | `aep-158-*` (next-page-token, max-page-size, page-token) | **Pagination** — List methods need `page_size`/`page_token` params + `next_page_token` in the response | +| ~90 | `aep-131/132/133/135-*` (operation-id, request-body, response-body) | **Standard methods** — Get/List/Create/Update/Delete operationId + body conventions | +| ~62 | `aep-142-time-field-*` | **Time fields** — timestamp fields must be named `*_time` | +| 77 | `operation-description` | Every operation needs a description | +| 35 | `oas3-schema` | Structural OpenAPI validity (non-AEP) | +| ~7 | `aep-193-error-response-schema` | **Errors** — responses should be RFC 9457 Problem Details | +| misc | `aep-140-uri-property-naming`, `aep-136-operation-id` (LRO) | naming / long-running operations | + +## Remediation plan (ratchet the baseline down) + +1. **P0 — fix `dcm-admin-api.yaml` structural corruption.** It has **two `components:` blocks** (~line 1535 and ~line 1838) and duplicated/misplaced `/api/v1/admin/overrides…` path blocks nested under `components:`; the YAML is invalid (fails to parse at ~line 1846). Repair: consolidate to one `components:`, move the misplaced path blocks under `paths:`, de-duplicate. Then it can lint. +2. **RFC 9457 errors (`aep-193`).** Replace the bespoke `Error` / `OperatorError` / `ProviderError` schemas with a shared `ProblemDetails` schema (`type/status/title/detail/instance` + extension members), `application/problem+json` — consistent with the UDLM error model (croadfeldt/udlm `contracts/error-model.md`, ADR-AEP-001). +3. **Pagination (`aep-158`).** Give every List method `page_size` + `page_token` parameters and `next_page_token` in the response. +4. **Standard methods (`aep-131/132/133/135`).** Normalize operationIds and request/response bodies to the AEP method shapes. +5. **Time fields (`aep-142`).** Rename timestamp fields to the `*_time` suffix. +6. **Descriptions.** Add operation descriptions. + +Flip CI to blocking after step 4; steps 5–6 are polish. diff --git a/schemas/openapi/dcm-admin-api.yaml b/schemas/openapi/dcm-admin-api.yaml new file mode 100644 index 0000000..d3b72a4 --- /dev/null +++ b/schemas/openapi/dcm-admin-api.yaml @@ -0,0 +1,2007 @@ +openapi: "3.1.0" + +info: + title: DCM Admin API + version: "1.0.0" + description: | + The DCM Admin API is used by platform engineers, SREs, and system administrators to manage + the DCM control plane itself. All endpoints require platform admin or higher authority. + + **Key principles:** + - Requires `verified` or `authorized` tier authority for most operations + - Mutating operations against the governance model (tier registry, profiles) require + `authorized` tier with a declared DCMGroup quorum + - All actions produce audit records + - Destructive or degrading operations have explicit confirmation steps + + + + **AEP Alignment:** This API follows [AEP](https://aep.dev) conventions: + custom methods use colon syntax (`POST /resources/{name}:suspend`), + async operations return an `Operation` resource (AEP-136 LRO), + and list pagination uses `page_size`/`page_token` parameters. + + contact: + name: DCM Project + url: https://github.com/dcm-project + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0 + +servers: + - url: https://{dcm-host}/ + description: DCM Control Plane + variables: + dcm-host: + default: dcm.example.com + +security: + - BearerAuth: [] + +tags: + - name: health + description: DCM control plane health and readiness + - name: tenants + description: Tenant lifecycle management + - name: actors + description: Actor session and risk management + - name: providers + description: Provider registration approval and management + - name: accreditations + description: Provider accreditation review + - name: discovery + description: Brownfield discovery scheduling and monitoring + - name: drift + description: Drift orphan management and recovery decisions + - name: quotas + description: Tenant quota management + - name: search + description: Search index operations + - name: bootstrap + description: Bootstrap credential management + - name: scoring + description: Risk scoring model configuration + - name: approvals + description: Platform-level approval management + - name: tier-registry + description: Authority tier registry management + +paths: + + # ─── HEALTH ─────────────────────────────────────────────────────────────── + + /livez: + get: + tags: [health] + operationId: liveness + summary: Kubernetes-style liveness probe + security: [] + responses: + "200": { description: Control plane process is alive } + "503": { description: Control plane unhealthy } + + /readyz: + get: + tags: [health] + operationId: readiness + summary: Kubernetes-style readiness probe + security: [] + responses: + "200": { description: Control plane ready to serve requests } + "503": { description: Control plane not ready (stores unavailable, bootstrap incomplete, etc.) } + + /metrics: + get: + tags: [health] + operationId: metrics + summary: Prometheus metrics endpoint + security: [] + responses: + "200": + description: Prometheus text format metrics + content: + text/plain: + schema: { type: string } + + /api/v1/admin/health: + get: + tags: [health] + operationId: getAdminHealth + summary: Detailed control plane health including component and provider status + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/AdminHealthResponse" } + + # ─── TENANTS ────────────────────────────────────────────────────────────── + + /api/v1/admin/tenants: + get: + tags: [tenants] + operationId: listTenants + summary: List all Tenants + parameters: + - { $ref: "#/components/parameters/page_size" } + - { $ref: "#/components/parameters/page_token" } + - name: status + in: query + schema: { type: string, enum: [active, suspended, decommissioned] } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/TenantList" } + post: + tags: [tenants] + operationId: createTenant + summary: Create a new Tenant + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/TenantCreate" } + responses: + "201": + content: + application/json: + schema: { $ref: "#/components/schemas/Tenant" } + "409": { description: Tenant handle already exists } + + /api/v1/admin/tenants/{tenant_uuid}:suspend: + post: + tags: [tenants] + operationId: suspendTenant + summary: Suspend a Tenant (blocks all new requests; active resources remain) + parameters: + - { $ref: "#/components/parameters/tenant_uuid" } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [reason] + properties: + reason: { type: string } + responses: + "200": { description: Tenant suspended } + "409": { description: Tenant already suspended or decommissioned } + + /api/v1/admin/tenants/{tenant_uuid}:reinstate: + post: + tags: [tenants] + operationId: reinstateTenant + summary: Reinstate a suspended Tenant + parameters: + - { $ref: "#/components/parameters/tenant_uuid" } + responses: + "200": { description: Tenant reinstated } + + /api/v1/admin/tenants/{tenant_uuid}: + delete: + tags: [tenants] + operationId: decommissionTenant + summary: Decommission a Tenant (must have zero active resources) + parameters: + - { $ref: "#/components/parameters/tenant_uuid" } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [reason, confirmation] + properties: + reason: { type: string } + confirmation: { type: string, const: "DECOMMISSION", description: "Must be the string 'DECOMMISSION'" } + responses: + "200": + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: { $ref: "#/components/schemas/Operation" } + "409": { description: Tenant has active resources — cannot decommission } + + /api/v1/admin/tenants/{tenant_uuid}/quotas: + get: + tags: [quotas] + operationId: getTenantQuotas + summary: Get quota configuration for a Tenant + parameters: + - { $ref: "#/components/parameters/tenant_uuid" } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/TenantQuotas" } + + /api/v1/admin/tenants/{tenant_uuid}/quotas/{resource_type}: + put: + tags: [quotas] + operationId: setTenantQuota + summary: Set or update quota for a specific resource type on a Tenant + parameters: + - { $ref: "#/components/parameters/tenant_uuid" } + - name: resource_type + in: path + required: true + schema: { type: string } + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/QuotaUpdate" } + responses: + "200": { description: Quota updated } + + # ─── ACTORS ─────────────────────────────────────────────────────────────── + + /api/v1/admin/actors/{actor_uuid}:revoke-sessions: + post: + tags: [actors] + operationId: revokeActorSessions + summary: Revoke all active sessions for an actor (emergency session revocation) + parameters: + - { $ref: "#/components/parameters/actor_uuid" } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [reason] + properties: + reason: { type: string } + responses: + "200": + content: + application/json: + schema: + type: object + properties: + sessions_revoked: { type: integer } + + /api/v1/admin/actors/{actor_uuid}/sessions: + get: + tags: [actors] + operationId: getActorSessions + summary: List all active sessions for an actor + parameters: + - { $ref: "#/components/parameters/actor_uuid" } + responses: + "200": + content: + application/json: + schema: + type: object + properties: + sessions: { type: array, items: { type: object } } + + /api/v1/admin/actors/{actor_uuid}/risk-history: + get: + tags: [actors, scoring] + operationId: getActorRiskHistory + summary: Get risk score history for an actor (full detail for admin) + parameters: + - { $ref: "#/components/parameters/actor_uuid" } + - { $ref: "#/components/parameters/page_size" } + - { $ref: "#/components/parameters/page_token" } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/RiskHistory" } + + /api/v1/admin/actors/{actor_uuid}/risk-history:reset: + post: + tags: [actors] + operationId: resetActorRiskHistory + summary: Reset risk score history for an actor (requires verified tier) + parameters: + - { $ref: "#/components/parameters/actor_uuid" } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [reason] + properties: + reason: { type: string } + responses: + "200": { description: Risk history reset } + + # ─── PROVIDERS ──────────────────────────────────────────────────────────── + + /api/v1/admin/location-types: + get: + tags: [locations] + operationId: listLocationTypes + summary: List registered location types (standard and custom) + security: + - bearerAuth: [] + responses: + "200": + description: All registered location types + content: + application/json: + schema: + type: object + properties: + location_types: + type: array + items: { type: object, additionalProperties: true } + post: + tags: [locations] + operationId: registerCustomLocationType + summary: Register a custom location type + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: { type: object, additionalProperties: true } + responses: + "200": + description: Operation initiated + content: + application/json: + schema: { $ref: "#/components/schemas/Operation" } + + /api/v1/admin/locations: + get: + tags: [locations] + operationId: adminListLocations + summary: List all location nodes (admin — no entitlement filter) + parameters: + - { name: level, in: query, schema: { type: string } } + - { name: page_size, in: query, schema: { type: integer, default: 100 } } + - { name: page_token, in: query, schema: { type: string } } + security: + - bearerAuth: [] + responses: + "200": + description: All location nodes + content: + application/json: + schema: { type: object, additionalProperties: true } + + /api/v1/admin/locations/{location_uuid}: + patch: + tags: [locations] + operationId: updateLocationCapacity + summary: Update mutable location fields (e.g., rack_units_available) + parameters: + - { name: location_uuid, in: path, required: true, schema: { type: string, format: uuid } } + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: { type: object, additionalProperties: true } + responses: + "200": + description: Location updated + content: + application/json: + schema: { type: object, additionalProperties: true } + "404": { $ref: "#/components/responses/NotFound" } + + + /api/v1/admin/providers: + get: + tags: [providers] + operationId: listProviders + summary: List all registered providers + parameters: + - { $ref: "#/components/parameters/page_size" } + - { $ref: "#/components/parameters/page_token" } + - name: provider_type + in: query + schema: { type: string } + - name: status + in: query + schema: { type: string } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/ProviderList" } + + /api/v1/admin/providers/pending: + get: + tags: [providers] + operationId: listPendingProviders + summary: List providers awaiting approval + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/ProviderList" } + + /api/v1/admin/providers/{provider_uuid}:approve: + post: + tags: [providers] + operationId: approveProvider + summary: Approve a provider registration + parameters: + - { $ref: "#/components/parameters/provider_uuid" } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [reason] + properties: + reason: { type: string } + conditions: { type: string, description: "Any conditions attached to approval" } + external_reference: { type: string } + responses: + "200": { description: Provider approved and activated } + "409": { description: Provider not in PENDING_APPROVAL state } + + /api/v1/admin/providers/{provider_uuid}:reject: + post: + tags: [providers] + operationId: rejectProvider + summary: Reject a provider registration + parameters: + - { $ref: "#/components/parameters/provider_uuid" } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [reason] + properties: + reason: { type: string } + responses: + "200": { description: Provider rejected } + + /api/v1/admin/providers/{provider_uuid}:suspend: + post: + tags: [providers] + operationId: suspendProvider + summary: Suspend a provider (no new requests routed; existing resources unaffected) + parameters: + - { $ref: "#/components/parameters/provider_uuid" } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [reason] + properties: + reason: { type: string } + duration: { type: string, description: "ISO 8601 duration; null = indefinite" } + responses: + "200": { description: Provider suspended } + + # ─── ACCREDITATIONS ─────────────────────────────────────────────────────── + + /api/v1/admin/accreditations: + get: + tags: [accreditations] + operationId: listAccreditations + summary: List provider accreditations (pending, active, expiring soon) + parameters: + - name: status + in: query + schema: { type: string, enum: [pending, active, expired, expiring_soon] } + - { $ref: "#/components/parameters/page_size" } + - { $ref: "#/components/parameters/page_token" } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/AccreditationList" } + + /api/v1/admin/accreditations/{accreditation_uuid}:approve: + post: + tags: [accreditations] + operationId: approveAccreditation + summary: Approve a submitted accreditation + parameters: + - { name: accreditation_uuid, in: path, required: true, schema: { type: string, format: uuid } } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [reason] + properties: + reason: { type: string } + external_reference: { type: string } + responses: + "200": { description: Accreditation approved } + + /api/v1/admin/accreditations/{accreditation_uuid}: + delete: + tags: [accreditations] + operationId: revokeAccreditation + summary: Revoke an active accreditation + parameters: + - { name: accreditation_uuid, in: path, required: true, schema: { type: string, format: uuid } } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [reason] + properties: + reason: { type: string } + responses: + "200": { description: Accreditation revoked; affected entities notified } + + # ─── DISCOVERY ──────────────────────────────────────────────────────────── + + /api/v1/admin/discovery:trigger: + post: + tags: [discovery] + operationId: triggerDiscovery + summary: Trigger an immediate discovery cycle for one or all providers + requestBody: + content: + application/json: + schema: + type: object + properties: + provider_uuid: { type: string, format: uuid, description: "Omit to trigger all active providers" } + scope: { type: string, enum: [full, targeted], default: full } + responses: + "202": + content: + application/json: + schema: + type: object + properties: + discovery_job_uuid: { type: string, format: uuid } + + /api/v1/admin/discovery/jobs/{discovery_job_uuid}: + get: + tags: [discovery] + operationId: getDiscoveryJob + summary: Get status of a discovery job + parameters: + - { name: discovery_job_uuid, in: path, required: true, schema: { type: string, format: uuid } } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/DiscoveryJobStatus" } + + # ─── DRIFT / ORPHANS ────────────────────────────────────────────────────── + + /api/v1/admin/orphans: + get: + tags: [drift] + operationId: listOrphans + summary: List discovered entities with no matching Requested State record + parameters: + - { $ref: "#/components/parameters/page_size" } + - { $ref: "#/components/parameters/page_token" } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/OrphanList" } + + /api/v1/admin/orphans/{orphan_candidate_uuid}/resolve: + post: + tags: [drift] + operationId: resolveOrphan + summary: Resolve an orphan candidate (ingest, ignore, or decommission) + parameters: + - { name: orphan_candidate_uuid, in: path, required: true, schema: { type: string, format: uuid } } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [resolution] + properties: + resolution: { type: string, enum: [ingest, ignore, decommission] } + target_tenant_uuid: { type: string, format: uuid, description: "Required for 'ingest'" } + reason: { type: string } + responses: + "200": { description: Orphan resolved } + + /api/v1/admin/recovery-decisions/pending: + get: + tags: [drift] + operationId: listPendingRecoveryDecisions + summary: List platform-level recovery decisions awaiting admin resolution + responses: + "200": + content: + application/json: + schema: + type: object + properties: + decisions: { type: array, items: { type: object } } + + /api/v1/admin/recovery-decisions/{recovery_decision_uuid}: + post: + tags: [drift] + operationId: resolveRecoveryDecision + summary: Resolve a platform-level recovery decision + parameters: + - { name: recovery_decision_uuid, in: path, required: true, schema: { type: string, format: uuid } } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [decision, reason] + properties: + decision: { type: string, enum: [approve, reject, escalate] } + reason: { type: string } + responses: + "200": { description: Decision recorded } + + # ─── SEARCH INDEX ───────────────────────────────────────────────────────── + + /api/v1/admin/search-index:rebuild: + post: + tags: [search] + operationId: rebuildSearchIndex + summary: Trigger a full search index rebuild + responses: + "202": + content: + application/json: + schema: + type: object + properties: + job_uuid: { type: string, format: uuid } + + /api/v1/admin/search-index/status: + get: + tags: [search] + operationId: getSearchIndexStatus + summary: Get current search index status and last rebuild time + responses: + "200": + content: + application/json: + schema: + type: object + properties: + status: { type: string } + entity_count: { type: integer } + last_rebuilt: { type: string, format: date-time } + + # ─── BOOTSTRAP ──────────────────────────────────────────────────────────── + + /api/v1/admin/bootstrap:rotate-credential: + post: + tags: [bootstrap] + operationId: rotateBootstrapCredential + summary: Rotate the bootstrap credential (zero-day trust credential rotation) + description: | + Rotates the bootstrap credential used for initial DCM trust establishment. + Requires the current credential to be presented and records the rotation in + the Audit Store. New credential is returned once; cannot be retrieved again. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [current_credential_ref, reason] + properties: + current_credential_ref: { type: string } + reason: { type: string } + responses: + "200": + content: + application/json: + schema: + type: object + properties: + new_credential: { type: string, description: "New bootstrap credential. Shown once." } + rotated_at: { type: string, format: date-time } + + # ─── SCORING ────────────────────────────────────────────────────────────── + + /api/v1/admin/profiles/{profile_name}/scoring: + get: + tags: [scoring] + operationId: getProfileScoring + summary: Get scoring model configuration for a profile + parameters: + - { $ref: "#/components/parameters/profile_name" } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/ScoringConfiguration" } + + patch: + tags: [scoring] + operationId: updateProfileScoring + summary: Update scoring configuration for a profile (threshold adjustments) + parameters: + - { $ref: "#/components/parameters/profile_name" } + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/ScoringConfigurationUpdate" } + responses: + "200": { description: Scoring configuration updated } + "422": { description: Configuration violates SMX-008 hard constraint (auto max_score ≤ 50) } + + /api/v1/admin/profiles/{profile_name}/scoring/overrides: + post: + tags: [scoring] + operationId: addScoringOverride + summary: Add a per-policy enforcement class override for a profile + parameters: + - { $ref: "#/components/parameters/profile_name" } + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/ScoringOverride" } + responses: + "201": { description: Override added } + + /api/v1/admin/scoring/audit: + get: + tags: [scoring] + operationId: getScoringAudit + summary: Get scoring evaluation audit records for review + parameters: + - { $ref: "#/components/parameters/page_size" } + - { $ref: "#/components/parameters/page_token" } + - name: actor_uuid + in: query + schema: { type: string, format: uuid } + responses: + "200": + content: + application/json: + schema: + type: object + properties: + records: { type: array, items: { type: object } } + + # ─── APPROVALS ──────────────────────────────────────────────────────────── + + /api/v1/admin/approvals/pending: + get: + tags: [approvals] + operationId: listPlatformApprovals + summary: List all platform-level approvals pending admin action + parameters: + - { $ref: "#/components/parameters/page_size" } + - { $ref: "#/components/parameters/page_token" } + - name: tier + in: query + schema: { type: string } + responses: + "200": + content: + application/json: + schema: + type: object + properties: + approvals: { type: array, items: { $ref: "#/components/schemas/ApprovalRecord" } } + + /api/v1/admin/approvals/{approval_uuid}: + get: + tags: [approvals] + operationId: getApproval + summary: Get full detail for an approval record including all decisions + parameters: + - { name: approval_uuid, in: path, required: true, schema: { type: string, format: uuid } } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/ApprovalDetail" } + + /api/v1/admin/approvals/{approval_uuid}:vote: + post: + tags: [approvals] + operationId: recordAdminApprovalVote + summary: Record a platform admin approval vote + parameters: + - { name: approval_uuid, in: path, required: true, schema: { type: string, format: uuid } } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [decision, reason] + properties: + decision: { type: string, enum: [approve, reject, abstain] } + reason: { type: string } + external_reference: { type: string } + responses: + "200": { description: Vote recorded; approval gate re-evaluated } + + # ─── AUTHORITY TIER REGISTRY ────────────────────────────────────────────── + + /api/v1/admin/tier-registry/changes: + post: + tags: [tier-registry] + operationId: proposeTierRegistryChange + summary: Propose a change to the authority tier registry + description: | + Submits a proposed updated tier list. DCM computes the tier impact diff immediately — + comparing the proposed ordered list to the current one and classifying every changed + tier as SECURITY_DEGRADATION, SECURITY_UPGRADE, BROKEN_REFERENCE, PROFILE_GAP, or + STALE_WEIGHT. Returns the impact report UUID for inspection. + The change cannot activate until all SECURITY_DEGRADATION and BROKEN_REFERENCE items + are explicitly accepted. See 32-authority-tier-model.md Section 7. + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/TierRegistryChangeProposal" } + responses: + "202": + content: + application/json: + schema: + type: object + properties: + change_uuid: { type: string, format: uuid } + impact_report_uuid: { type: string, format: uuid } + blocking_items: { type: integer, description: "Number of SECURITY_DEGRADATION or BROKEN_REFERENCE items" } + status: { type: string, enum: [pending_review, ready_to_activate] } + + get: + tags: [tier-registry] + operationId: listTierRegistryChanges + summary: List tier registry change proposals + parameters: + - name: status + in: query + schema: { type: string, enum: [pending_review, ready_to_activate, activated, rejected] } + - { $ref: "#/components/parameters/page_size" } + - { $ref: "#/components/parameters/page_token" } + responses: + "200": + content: + application/json: + schema: + type: object + properties: + changes: { type: array, items: { type: object } } + + /api/v1/admin/tier-registry/changes/{change_uuid}/impact: + get: + tags: [tier-registry] + operationId: getTierRegistryImpact + summary: Get the full impact report for a proposed tier registry change + parameters: + - { name: change_uuid, in: path, required: true, schema: { type: string, format: uuid } } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/TierImpactReport" } + + /api/v1/admin/tier-registry/changes/{change_uuid}:accept-degradation: + post: + tags: [tier-registry] + operationId: acceptTierDegradation + summary: Accept a specific SECURITY_DEGRADATION item (requires verified tier) + description: | + Accepts a single SECURITY_DEGRADATION item identified in the impact report. + The accepting actor must be at `verified` tier or above and must provide a reason + describing what compensating controls justify the degradation. + The change cannot activate until ALL SECURITY_DEGRADATION items are accepted. + parameters: + - { name: change_uuid, in: path, required: true, schema: { type: string, format: uuid } } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [degradation_item_uuid, reason, compensating_controls] + properties: + degradation_item_uuid: { type: string, format: uuid } + reason: { type: string, minLength: 20, description: "Why this degradation is acceptable" } + compensating_controls: { type: string, minLength: 20, description: "What compensating controls are in place" } + responses: + "200": { description: Degradation accepted; impact report updated } + "403": { description: Actor does not meet required tier (verified or above)" } + "409": { description: Item already accepted or change not in pending_review state } + + /api/v1/admin/tier-registry/changes/{change_uuid}:activate: + post: + tags: [tier-registry] + operationId: activateTierRegistryChange + summary: Activate a proposed tier registry change + description: | + Activates the proposed tier list as the new authoritative authority tier registry. + Returns 409 if any SECURITY_DEGRADATION or BROKEN_REFERENCE items remain unaccepted. + Impact report is stored in the Audit Store at activation time. + Requires `authorized` tier. + parameters: + - { name: change_uuid, in: path, required: true, schema: { type: string, format: uuid } } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [reason] + properties: + reason: { type: string } + responses: + "200": { description: Tier registry updated; new ordered list now active } + "403": { description: Actor does not meet required tier (authorized)" } + "409": + description: Blocking items remain unaccepted + content: + application/json: + schema: + type: object + properties: + blocking_items: { type: array, items: { type: object } } + +# ─── COMPONENTS ──────────────────────────────────────────────────────────────── + + # ── Workload Analysis (Admin — aggregate view) ──────────────────────────────── + /api/v1/admin/workload-analysis: + get: + tags: [workload-analysis] + operationId: listWorkloadProfiles + summary: List all workload profiles across all tenants (admin aggregate view) + description: | + Returns workload profiles platform-wide. Useful for capacity planning, + workload-type distribution reporting, and migration readiness assessment. + Filtered by archetype, confidence, or resource type. + parameters: + - {name: archetype, in: query, schema: {type: string, enum: [web_server, database, batch_processor, message_broker, api_gateway, cache, storage, monitoring, unknown]}} + - {name: confidence, in: query, schema: {type: string, enum: [high, medium, low, undetermined]}} + - {name: resource_type, in: query, schema: {type: string}, description: "FQN e.g. Compute.VirtualMachine"} + - {name: tenant_uuid, in: query, schema: {type: string, format: uuid}} + - {name: containerization_score_min, in: query, schema: {type: integer, minimum: 1, maximum: 10}} + - {name: page_size, in: query, schema: {type: integer, default: 50}} + - {name: page_token, in: query, schema: {type: string}} + security: [{bearerAuth: []}] + responses: + "200": + description: Workload profile list + content: + application/json: + schema: + type: object + properties: + items: + type: array + items: + type: object + properties: + entity_uuid: {type: string, format: uuid} + tenant_uuid: {type: string, format: uuid} + resource_type: {type: string} + workload_archetype: {type: string} + confidence: {type: string} + containerization_score: {type: integer} + analyzed_at: {type: string, format: date-time} + total_count: {type: integer} + archetype_distribution: + type: object + description: Count per archetype across all profiles in result set + additionalProperties: {type: integer} + next_page_token: {type: string} + "401": {$ref: "#/components/responses/Unauthorized"} + "403": {$ref: "#/components/responses/Forbidden"} + + # ── Accreditation Monitor ───────────────────────────────────────────────────── + /api/v1/admin/accreditations/{accreditation_uuid}:verify: + post: + tags: [accreditation] + operationId: triggerAccreditationVerification + summary: Trigger immediate external verification of an accreditation + parameters: + - {name: accreditation_uuid, in: path, required: true, schema: {type: string, format: uuid}} + security: [{bearerAuth: []}] + requestBody: + content: + application/json: + schema: + type: object + properties: + override_reason: {type: string, description: "Required when manually overriding last_verified_at in air-gapped mode"} + responses: + "200": + description: Operation initiated + content: + application/json: + schema: {$ref: "#/components/schemas/Operation"} + "404": {$ref: "#/components/responses/NotFound"} + + /api/v1/admin/accreditations/{accreditation_uuid}:configure-webhook: + post: + tags: [accreditation] + operationId: configureAccreditationWebhook + summary: Configure a contract management webhook for Tier 3 accreditation verification + description: | + Used for BAA and DoD IL accreditations. Registers the contract management + system (DocuSign, Ironclad, etc.) to send lifecycle events to DCM when + the underlying contract is signed, amended, or terminated. + parameters: + - {name: accreditation_uuid, in: path, required: true, schema: {type: string, format: uuid}} + security: [{bearerAuth: []}] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [contract_system, contract_id] + properties: + contract_system: {type: string, enum: [docusign, ironclad, agiloft, custom]} + contract_id: {type: string, description: ID in the contract management system} + webhook_secret: {type: string, description: HMAC secret for webhook authentication} + responses: + "200": + description: Webhook configured; webhook_url returned for registration in contract system + content: + application/json: + schema: + type: object + properties: + accreditation_uuid: {type: string, format: uuid} + webhook_url: {type: string, format: uri, description: "Register this URL in your contract management system"} + webhook_secret_set: {type: boolean} + + /api/v1/admin/accreditations/{accreditation_uuid}/contract-event: + post: + tags: [accreditation] + operationId: receiveAccreditationContractEvent + summary: Inbound webhook — receive contract lifecycle event from contract management system + description: | + Called by contract management systems (DocuSign, Ironclad, etc.) when + the underlying BAA or authorization contract changes state. + Authenticated via HMAC signature using the webhook_secret. + parameters: + - {name: accreditation_uuid, in: path, required: true, schema: {type: string, format: uuid}} + security: [{webhookHmac: []}] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [contract_event_type, contract_id, effective_date] + properties: + contract_event_type: {type: string, enum: [signed, amended, terminated, renewal_due, renewed]} + contract_id: {type: string} + effective_date: {type: string, format: date-time} + details: {type: object, additionalProperties: true} + responses: + "200": + description: Event received and processed + content: + application/json: + schema: + type: object + properties: + accreditation_uuid: {type: string, format: uuid} + dcm_action_taken: {type: string, enum: [activated, pending_review, revoked, none]} + "401": {$ref: "#/components/responses/Unauthorized"} + "404": {$ref: "#/components/responses/NotFound"} + + # ── Maintenance Windows ─────────────────────────────────────────────────────── + /api/v1/admin/maintenance-windows: + get: + tags: [scheduling] + operationId: listMaintenanceWindows + summary: List declared maintenance windows + parameters: + - {name: status, in: query, schema: {type: string, enum: [active, upcoming, expired]}} + - {name: page_size, in: query, schema: {type: integer, default: 50}} + - {name: page_token, in: query, schema: {type: string}} + security: [{bearerAuth: []}] + responses: + "200": + description: Maintenance window list + content: + application/json: + schema: + type: object + properties: + items: {type: array, items: {$ref: "#/components/schemas/MaintenanceWindow"}} + next_page_token: {type: string} + post: + tags: [scheduling] + operationId: createMaintenanceWindow + summary: Declare a new maintenance window + security: [{bearerAuth: []}] + requestBody: + required: true + content: + application/json: + schema: {$ref: "#/components/schemas/MaintenanceWindowCreate"} + responses: + "200": + description: Maintenance window created + content: + application/json: + schema: {$ref: "#/components/schemas/MaintenanceWindow"} + "422": {$ref: "#/components/responses/UnprocessableEntity"} + + /api/v1/admin/maintenance-windows/{window_uuid}: + get: + tags: [scheduling] + operationId: getMaintenanceWindow + summary: Get maintenance window details including scheduled requests in queue + parameters: + - {name: window_uuid, in: path, required: true, schema: {type: string, format: uuid}} + security: [{bearerAuth: []}] + responses: + "200": + description: Maintenance window detail + content: + application/json: + schema: {$ref: "#/components/schemas/MaintenanceWindow"} + "404": {$ref: "#/components/responses/NotFound"} + patch: + tags: [scheduling] + operationId: updateMaintenanceWindow + summary: Update maintenance window schedule or description + parameters: + - {name: window_uuid, in: path, required: true, schema: {type: string, format: uuid}} + security: [{bearerAuth: []}] + requestBody: + required: true + content: + application/json: + schema: {$ref: "#/components/schemas/MaintenanceWindowPatch"} + responses: + "200": + description: Maintenance window updated + content: + application/json: + schema: {$ref: "#/components/schemas/MaintenanceWindow"} + delete: + tags: [scheduling] + operationId: deleteMaintenanceWindow + summary: Delete a maintenance window (cancels queued requests if policy dictates) + parameters: + - {name: window_uuid, in: path, required: true, schema: {type: string, format: uuid}} + security: [{bearerAuth: []}] + responses: + "200": + description: Deleted; queued_request_disposition indicates what happened to waiting requests + content: + application/json: + schema: + type: object + properties: + window_uuid: {type: string, format: uuid} + queued_requests_affected: {type: integer} + queued_request_disposition: {type: string, enum: [cancelled, reassigned, held]} + + /api/v1/admin/maintenance-windows/{window_uuid}/scheduled-requests: + get: + tags: [scheduling] + operationId: listWindowScheduledRequests + summary: List requests queued for this maintenance window + parameters: + - {name: window_uuid, in: path, required: true, schema: {type: string, format: uuid}} + - {name: page_size, in: query, schema: {type: integer, default: 50}} + - {name: page_token, in: query, schema: {type: string}} + security: [{bearerAuth: []}] + responses: + "200": + description: Queued requests + content: + application/json: + schema: + type: object + properties: + items: {type: array, items: {type: object, additionalProperties: true}} + next_page_token: {type: string} + + # ── Federation Management ──────────────────────────────────────────────────── + /api/v1/admin/federation/peers: + get: + tags: [federation] + operationId: listFederationPeers + summary: List all registered federation peer DCM instances + parameters: + - {name: trust_posture, in: query, schema: {type: string, enum: [verified, vouched, untrusted]}} + - {name: status, in: query, schema: {type: string, enum: [active, suspended, pending]}} + - {name: page_size, in: query, schema: {type: integer, default: 50}} + - {name: page_token, in: query, schema: {type: string}} + security: [{bearerAuth: []}] + responses: + "200": + description: List of federation peer registrations + content: + application/json: + schema: + type: object + properties: + items: {type: array, items: {$ref: "#/components/schemas/FederationPeer"}} + next_page_token: {type: string} + "401": {$ref: "#/components/responses/Unauthorized"} + "403": {$ref: "#/components/responses/Forbidden"} + post: + tags: [federation] + operationId: registerFederationPeer + summary: Register a new federation peer DCM instance + security: [{bearerAuth: []}] + requestBody: + required: true + content: + application/json: + schema: {$ref: "#/components/schemas/FederationPeerRegistration"} + responses: + "200": + description: Operation initiated — peer registration pending trust verification + content: + application/json: + schema: {$ref: "#/components/schemas/Operation"} + "422": {$ref: "#/components/responses/UnprocessableEntity"} + + /api/v1/admin/federation/peers/{peer_uuid}: + get: + tags: [federation] + operationId: getFederationPeer + summary: Get federation peer details and current trust status + parameters: + - {name: peer_uuid, in: path, required: true, schema: {type: string, format: uuid}} + security: [{bearerAuth: []}] + responses: + "200": + description: Federation peer record + content: + application/json: + schema: {$ref: "#/components/schemas/FederationPeer"} + "404": {$ref: "#/components/responses/NotFound"} + delete: + tags: [federation] + operationId: deregisterFederationPeer + summary: Deregister a federation peer (graceful tunnel teardown) + parameters: + - {name: peer_uuid, in: path, required: true, schema: {type: string, format: uuid}} + security: [{bearerAuth: []}] + responses: + "200": + description: Operation initiated + content: + application/json: + schema: {$ref: "#/components/schemas/Operation"} + + /api/v1/admin/federation/peers/{peer_uuid}:set-trust-posture: + post: + tags: [federation] + operationId: setFederationPeerTrustPosture + summary: Set the trust posture for a federation peer + parameters: + - {name: peer_uuid, in: path, required: true, schema: {type: string, format: uuid}} + security: [{bearerAuth: []}] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [trust_posture, reason] + properties: + trust_posture: {type: string, enum: [verified, vouched, untrusted]} + reason: {type: string, description: Justification for trust posture change} + responses: + "200": + description: Trust posture updated + content: + application/json: + schema: {$ref: "#/components/schemas/FederationPeer"} + + /api/v1/admin/federation/peers/{peer_uuid}:suspend: + post: + tags: [federation] + operationId: suspendFederationPeer + summary: Suspend federation tunnel (stop routing; preserve registration) + parameters: + - {name: peer_uuid, in: path, required: true, schema: {type: string, format: uuid}} + security: [{bearerAuth: []}] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [reason] + properties: + reason: {type: string} + responses: + "200": + description: Peer suspended + content: + application/json: + schema: {$ref: "#/components/schemas/FederationPeer"} + + /api/v1/admin/federation/peers/{peer_uuid}/routed-requests: + get: + tags: [federation] + operationId: listFederationRoutedRequests + summary: List requests routed through this federation peer + parameters: + - {name: peer_uuid, in: path, required: true, schema: {type: string, format: uuid}} + - {name: status, in: query, schema: {type: string}} + - {name: page_size, in: query, schema: {type: integer, default: 50}} + - {name: page_token, in: query, schema: {type: string}} + security: [{bearerAuth: []}] + responses: + "200": + description: Federated request list + content: + application/json: + schema: + type: object + properties: + items: {type: array, items: {type: object, additionalProperties: true}} + next_page_token: {type: string} + + /api/v1/admin/federation/config: + get: + tags: [federation] + operationId: getFederationConfig + summary: Get this DCM instance's federation configuration (identity, capabilities, trust policy) + security: [{bearerAuth: []}] + responses: + "200": + description: Federation configuration + content: + application/json: + schema: {$ref: "#/components/schemas/FederationConfig"} + patch: + tags: [federation] + operationId: updateFederationConfig + summary: Update federation configuration (enable/disable federation, set scope) + security: [{bearerAuth: []}] + requestBody: + required: true + content: + application/json: + schema: {$ref: "#/components/schemas/FederationConfigPatch"} + responses: + "200": + description: Configuration updated + content: + application/json: + schema: {$ref: "#/components/schemas/FederationConfig"} + + # ── Subscription Administration (doc 50) ──────────────────────────── + + /api/v1/admin/subscriptions: + get: + operationId: adminListSubscriptions + summary: List all subscriptions across tenants + tags: [Subscription Administration] + parameters: + - {name: tenant_uuid, in: query, schema: {type: string, format: uuid}} + - {name: lifecycle_state, in: query, schema: {type: string}} + - {name: provider_uuid, in: query, schema: {type: string, format: uuid}} + - {name: page_size, in: query, schema: {type: integer, default: 100}} + - {name: page_token, in: query, schema: {type: string}} + responses: + "200": + description: Paginated subscription list across tenants + content: + application/json: + schema: {$ref: "#/components/schemas/ResourceList"} + + /api/v1/admin/subscriptions/expiring: + get: + operationId: adminListExpiringSubscriptions + summary: List subscriptions approaching expiry + tags: [Subscription Administration] + parameters: + - {name: within_days, in: query, schema: {type: integer, default: 30}} + - {name: page_size, in: query, schema: {type: integer, default: 100}} + - {name: page_token, in: query, schema: {type: string}} + responses: + "200": + description: Expiring subscriptions + content: + application/json: + schema: {$ref: "#/components/schemas/ResourceList"} + + /api/v1/admin/subscriptions/{subscription_uuid}:force-cancel: + post: + operationId: adminForceCancelSubscription + summary: Admin force cancellation — bypasses consumer approval + tags: [Subscription Administration] + parameters: + - {name: subscription_uuid, in: path, required: true, schema: {type: string, format: uuid}} + requestBody: + content: + application/json: + schema: + type: object + required: [reason] + properties: + reason: {type: string} + skip_grace_period: {type: boolean, default: false} + responses: + "200": + description: Force cancellation initiated + content: + application/json: + schema: {$ref: "#/components/schemas/Operation"} + + + /api/v1/admin/overrides: + get: + summary: List pending override requests + operationId: listOverrideRequests + parameters: + - name: status + in: query + schema: + type: string + enum: [pending, approved, rejected, expired] + - name: role + in: query + schema: + type: string + responses: + '200': + description: Override requests + /api/v1/admin/overrides/{request_uuid}: + get: + summary: Get override request details + operationId: getOverrideRequest + parameters: + - name: request_uuid + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Override request details + /api/v1/admin/overrides/{request_uuid}/approve: + post: + summary: Approve override request + operationId: approveOverride + parameters: + - name: request_uuid + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [justification, role] + properties: + justification: + type: string + role: + type: string + compensating_controls: + type: array + items: + type: string + responses: + '200': + description: Override approved + /api/v1/admin/overrides/{request_uuid}/reject: + post: + summary: Reject override request + operationId: rejectOverride + parameters: + - name: request_uuid + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [reason] + properties: + reason: + type: string + responses: + '200': + description: Override rejected + +components: + + securitySchemes: + BearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + + parameters: + + tenant_uuid: + name: tenant_uuid + in: path + required: true + schema: { type: string, format: uuid } + + provider_uuid: + name: provider_uuid + in: path + required: true + schema: { type: string, format: uuid } + + actor_uuid: + name: actor_uuid + in: path + required: true + schema: { type: string, format: uuid } + + profile_name: + name: profile_name + in: path + required: true + schema: { type: string, enum: [homelab, dev, standard, prod, fsi, sovereign] } + + limit: + name: page_size + in: query + schema: { type: integer, minimum: 1, maximum: 1000, default: 50 } + + cursor: + name: page_token + in: query + schema: { type: string } + + schemas: + + + + MaintenanceWindow: + type: object + properties: + window_uuid: {type: string, format: uuid} + handle: {type: string, description: "Stable reference used in schedule.window_id"} + display_name: {type: string} + description: {type: string} + schedule: + type: object + description: Cron expression or recurrence rule for this window + properties: + rrule: {type: string, description: "RFC 5545 RRULE e.g. FREQ=WEEKLY;BYDAY=SA;BYHOUR=2"} + duration: {type: string, description: "ISO 8601 duration e.g. PT4H"} + timezone: {type: string, description: "IANA timezone e.g. UTC, America/New_York"} + status: {type: string, enum: [active, suspended, expired]} + next_opens_at: {type: string, format: date-time} + next_closes_at: {type: string, format: date-time} + queued_request_count: {type: integer} + created_at: {type: string, format: date-time} + owned_by_actor_uuid: {type: string, format: uuid} + + MaintenanceWindowCreate: + type: object + required: [handle, display_name, schedule] + properties: + handle: {type: string, pattern: "^[a-z][a-z0-9-]{2,63}$"} + display_name: {type: string} + description: {type: string} + schedule: + type: object + required: [rrule, duration, timezone] + properties: + rrule: {type: string} + duration: {type: string} + timezone: {type: string} + + MaintenanceWindowPatch: + type: object + properties: + display_name: {type: string} + description: {type: string} + status: {type: string, enum: [active, suspended]} + schedule: + type: object + properties: + rrule: {type: string} + duration: {type: string} + timezone: {type: string} + + FederationPeer: + type: object + properties: + peer_uuid: {type: string, format: uuid} + display_name: {type: string} + dcm_instance_uuid: {type: string, format: uuid} + endpoint: {type: string, format: uri} + trust_posture: {type: string, enum: [verified, vouched, untrusted]} + status: {type: string, enum: [active, suspended, pending, deregistered]} + sovereignty_declarations: {type: array, items: {type: string}} + registered_at: {type: string, format: date-time} + last_heartbeat_at: {type: string, format: date-time} + routed_request_count: {type: integer} + + FederationPeerRegistration: + type: object + required: [display_name, endpoint, public_key_pem] + properties: + display_name: {type: string} + endpoint: {type: string, format: uri, description: mTLS endpoint of the peer DCM instance} + public_key_pem: {type: string, description: Public key for mTLS identity verification} + initial_trust_posture: {type: string, enum: [verified, vouched, untrusted], default: untrusted} + scope_declaration: + type: object + properties: + resource_types_accessible: {type: array, items: {type: string}} + sovereignty_constraints: {type: array, items: {type: string}} + + FederationConfig: + type: object + properties: + federation_enabled: {type: boolean} + this_instance_uuid: {type: string, format: uuid} + this_instance_display_name: {type: string} + this_instance_endpoint: {type: string, format: uri} + default_trust_posture: {type: string, enum: [verified, vouched, untrusted]} + peer_count: {type: integer} + active_peer_count: {type: integer} + + FederationConfigPatch: + type: object + properties: + federation_enabled: {type: boolean} + default_trust_posture: {type: string, enum: [verified, vouched, untrusted]} + this_instance_display_name: {type: string} + + Operation: + type: object + description: | + AEP-136 Long-Running Operation. Returned by async operations instead of 202 Accepted. + Poll GET {operation.name} until done is true. + The operation.name is a stable resource path: /api/v1/operations/{uuid} + required: [name, done] + additionalProperties: false + properties: + name: + type: string + description: "Stable resource path for this operation. Poll this URL for status." + example: /api/v1/operations/a1b2c3d4-e5f6-7890-abcd-ef1234567890 + done: + type: boolean + description: True when the operation has reached a terminal state (success or error) + default: false + metadata: + type: object + description: Operation-specific progress metadata + additionalProperties: false + properties: + stage: + type: string + description: Current pipeline stage + progress_pct: + type: integer + minimum: 0 + maximum: 100 + resource_uuid: + type: string + format: uuid + description: UUID of the resource being created/modified (set as soon as assigned) + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + response: + type: object + description: Present when done is true and the operation succeeded. Contains the result resource. + additionalProperties: true + error: + type: object + description: Present when done is true and the operation failed. + additionalProperties: false + properties: + code: { type: string } + message: { type: string } + details: { type: array, items: { type: object } } + + + Error: + type: object + required: [error] + properties: + error: + type: object + required: [code, message, request_id] + properties: + code: { type: string } + message: { type: string } + request_id: { type: string, format: uuid } + rule_uuid: { type: string, format: uuid } + fields: { type: array, items: { type: object } } + + AdminHealthResponse: + type: object + properties: + status: { type: string, enum: [healthy, degraded, unhealthy] } + version: { type: string } + uptime: { type: string } + components: + type: array + items: + type: object + properties: + name: { type: string } + status: { type: string } + detail: { type: string } + providers: + type: object + properties: + total: { type: integer } + healthy: { type: integer } + degraded: { type: integer } + unavailable: { type: integer } + + Tenant: + type: object + properties: + uuid: { type: string, format: uuid } + handle: { type: string } + display_name: { type: string } + status: { type: string } + created_at: { type: string, format: date-time } + updated_at: { type: string, format: date-time } + resource_count: { type: integer } + compliance_domains: { type: array, items: { type: string } } + + TenantCreate: + type: object + required: [handle, display_name] + properties: + handle: { type: string } + display_name: { type: string } + compliance_domains: { type: array, items: { type: string } } + + TenantList: + type: object + properties: + items: { type: array, items: { $ref: "#/components/schemas/Tenant" } } + pagination: { type: object } + + TenantQuotas: + type: object + properties: + tenant_uuid: { type: string, format: uuid } + quotas: + type: array + items: + type: object + properties: + resource_type: { type: string } + page_size: { type: integer } + used: { type: integer } + remaining: { type: integer } + + QuotaUpdate: + type: object + required: [limit] + properties: + page_size: { type: integer, minimum: 0 } + reason: { type: string } + + ProviderList: + type: object + properties: + items: + type: array + items: + type: object + properties: + uuid: { type: string, format: uuid } + display_name: { type: string } + provider_type_id: { type: string } + status: { type: string } + trust_score: { type: number } + health_status: { type: string } + accreditations: { type: array, items: { type: object } } + pagination: { type: object } + + AccreditationList: + type: object + properties: + items: + type: array + items: + type: object + properties: + accreditation_uuid: { type: string, format: uuid } + provider_uuid: { type: string, format: uuid } + framework: { type: string } + status: { type: string } + expires_at: { type: string, format: date-time } + pagination: { type: object } + + DiscoveryJobStatus: + type: object + properties: + discovery_job_uuid: { type: string, format: uuid } + status: { type: string, enum: [running, completed, failed] } + started_at: { type: string, format: date-time } + completed_at: { type: string, format: date-time } + providers_queried: { type: integer } + entities_discovered: { type: integer } + drift_records_created: { type: integer } + orphans_detected: { type: integer } + + OrphanList: + type: object + properties: + items: + type: array + items: + type: object + properties: + orphan_candidate_uuid: { type: string, format: uuid } + provider_uuid: { type: string, format: uuid } + resource_type: { type: string } + discovered_at: { type: string, format: date-time } + provider_entity_id: { type: string } + pagination: { type: object } + + ScoringConfiguration: + type: object + properties: + profile: { type: string } + approval_routing: + type: array + items: + type: object + properties: + tier: { type: string } + max_score: { type: integer } + smx008_auto_cap: + type: integer + description: "Hard cap on auto-approve max_score (≤ 50 always enforced)" + + ScoringConfigurationUpdate: + type: object + properties: + approval_routing: + type: array + items: + type: object + required: [tier, max_score] + properties: + tier: { type: string } + max_score: { type: integer, minimum: 0, maximum: 100 } + + ScoringOverride: + type: object + required: [policy_uuid, enforcement_class_override] + properties: + policy_uuid: { type: string, format: uuid } + enforcement_class_override: { type: string, enum: [compliance, operational] } + reason: { type: string } + + RiskHistory: + type: object + properties: + actor_uuid: { type: string, format: uuid } + records: + type: array + items: + type: object + properties: + score: { type: integer } + routing_tier: { type: string } + evaluated_at: { type: string, format: date-time } + request_uuid: { type: string, format: uuid } + stored_tier_weight: { type: integer } + + ApprovalRecord: + type: object + properties: + approval_uuid: { type: string, format: uuid } + subject_type: { type: string } + subject_uuid: { type: string, format: uuid } + required_tier: { type: string } + required_quorum: { type: integer } + decisions: + type: array + items: + type: object + properties: + actor_uuid: { type: string, format: uuid } + decision: { type: string } + reason: { type: string } + recorded_at: { type: string, format: date-time } + expires_at: { type: string, format: date-time } + created_at: { type: string, format: date-time } + status: { type: string, enum: [open, approved, rejected, expired] } + + ApprovalDetail: + allOf: + - { $ref: "#/components/schemas/ApprovalRecord" } + - type: object + properties: + subject_detail: { type: object } + stored_tier_weights: + type: object + description: "Tier weights recorded at approval creation time (ATM-008)" + additionalProperties: { type: integer } + + TierRegistryChangeProposal: + type: object + required: [proposed_tier_list, reason] + properties: + proposed_tier_list: + type: array + minItems: 1 + items: + type: object + required: [name, decision_gravity] + properties: + name: { type: string, description: "Stable tier name (e.g., auto, reviewed, verified, authorized)" } + decision_gravity: { type: string, enum: [none, routine, elevated, critical] } + description: { type: string } + dcm_gate: { type: string } + organization_provides: { type: string } + dcmgroup_required: { type: boolean } + dcmgroup_uuid: { type: string, format: uuid } + quorum_threshold: { type: integer, minimum: 1 } + reason: { type: string } + + TierImpactReport: + type: object + properties: + impact_report_uuid: { type: string, format: uuid } + change_uuid: { type: string, format: uuid } + computed_at: { type: string, format: date-time } + blocking_items: { type: integer } + items: + type: array + items: + type: object + properties: + item_uuid: { type: string, format: uuid } + classification: + type: string + enum: [SECURITY_DEGRADATION, SECURITY_UPGRADE, BROKEN_REFERENCE, PROFILE_GAP, STALE_WEIGHT] + tier_name: { type: string } + old_position: { type: integer } + new_position: { type: integer } + old_gravity: { type: string } + new_gravity: { type: string } + affected_items: + type: array + items: + type: object + properties: + item_type: { type: string } + item_uuid: { type: string, format: uuid } + description: { type: string } + accepted: { type: boolean } + accepted_by: { type: string, format: uuid } + accepted_reason: { type: string } diff --git a/schemas/openapi/dcm-consumer-api.yaml b/schemas/openapi/dcm-consumer-api.yaml new file mode 100644 index 0000000..0565b6e --- /dev/null +++ b/schemas/openapi/dcm-consumer-api.yaml @@ -0,0 +1,2663 @@ +openapi: 3.1.0 +info: + title: DCM Consumer API + version: 1.0.0 + description: 'The DCM Consumer API provides the interface used by application teams, + Tenant owners, + + and automated tooling to interact with the DCM control plane. All interactions + are + + authenticated, Tenant-scoped, and governed by the Policy Engine. + + + **Key principles:** + + - All requests require a valid session token (Bearer) obtained via `/api/v1/auth/token` + + - All responses are Tenant-scoped — actors only see entities they are authorized + to see + + - Policy denials return 403 with a `rule_uuid` identifying the governing rule + + - All mutating operations produce an audit record + + - List endpoints support cursor-based pagination via `page_size` and `page_token` + query parameters + + + **API Versioning:** DCM uses URL path versioning (`/api/v1/`). Version discovery + is available + + at `/.well-known/dcm-api-versions`. See `34-api-versioning-strategy.md`. + + + + + **AEP Alignment:** This API follows [AEP](https://aep.dev) conventions: + + custom methods use colon syntax (`POST /resources/{name}:suspend`), + + async operations return an `Operation` resource (AEP-136 LRO), + + and list pagination uses `page_size`/`page_token` parameters. + + ' + contact: + name: DCM Project + url: https://github.com/dcm-project + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0 +servers: +- url: https://{dcm-host}/ + description: DCM Control Plane + variables: + dcm-host: + description: Hostname of the DCM control plane deployment + default: dcm.example.com +security: +- BearerAuth: [] +tags: +- name: discovery + description: API version discovery and migration guides +- name: authentication + description: Session management, token issuance, and introspection +- name: catalog + description: Service catalog browsing and search +- name: requests + description: Service request submission and lifecycle +- name: resources + description: Resource entity management and lifecycle operations +- name: drift + description: Drift detection, acknowledgement, and remediation +- name: groups + description: Resource group management +- name: approvals + description: Approval workflow for pending decisions +- name: cost + description: Cost estimation and attribution +- name: notifications + description: Notification inbox management +- name: webhooks + description: Outbound webhook subscription management +- name: search + description: Cross-entity search +- name: audit + description: Audit trail access +- name: contributions + description: Federated contribution submission (policies, resource groups) +- name: credentials + description: Credential retrieval and rotation +paths: + /.well-known/dcm-api-versions: + get: + tags: + - discovery + operationId: getApiVersions + summary: List supported API versions + security: [] + responses: + '200': + description: Supported API versions and deprecation status + content: + application/json: + schema: + type: object + properties: + versions: + type: array + items: + type: object + properties: + version: + type: string + status: + type: string + enum: + - current + - supported + - deprecated + - sunset + sunset_date: + type: string + format: date + /api/v1/migration-guide: + get: + tags: + - discovery + operationId: getMigrationGuide + summary: Get migration guide for API version transitions + security: [] + parameters: + - name: from + in: query + required: true + schema: + type: string + example: '1' + - name: to + in: query + required: true + schema: + type: string + example: '2' + responses: + '200': + description: Migration guide document + content: + application/json: + schema: + type: object + properties: + from_version: + type: string + to_version: + type: string + breaking_changes: + type: array + items: + type: object + guidance: + type: string + /api/v1/auth/token: + post: + tags: + - authentication + operationId: createSession + summary: Authenticate and obtain a session token + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - grant_type + properties: + grant_type: + type: string + enum: + - password + - client_credentials + - oidc_code + username: + type: string + password: + type: string + format: password + client_id: + type: string + client_secret: + type: string + format: password + code: + type: string + redirect_uri: + type: string + format: uri + responses: + '200': + description: Session token issued + content: + application/json: + schema: + $ref: '#/components/schemas/SessionTokenResponse' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /api/v1/auth/sessions: + get: + tags: + - authentication + operationId: listSessions + summary: List active sessions for the authenticated actor + responses: + '200': + content: + application/json: + schema: + type: object + properties: + sessions: + type: array + items: + $ref: '#/components/schemas/SessionSummary' + description: List active sessions + delete: + tags: + - authentication + operationId: revokeAllSessions + summary: Revoke all sessions for the authenticated actor (except the current + session) + responses: + '204': + description: All other sessions revoked + /api/v1/auth/sessions/{session_uuid}: + delete: + tags: + - authentication + operationId: revokeSession + summary: Revoke a specific session + parameters: + - $ref: '#/components/parameters/session_uuid' + responses: + '204': + description: Session revoked + '404': + $ref: '#/components/responses/NotFound' + /api/v1/auth:introspect: + post: + tags: + - authentication + operationId: introspectToken + summary: Introspect a token and return actor identity and roles + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - token + properties: + token: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TokenIntrospection' + '401': + $ref: '#/components/responses/Unauthorized' + /api/v1/auth/session: + delete: + tags: + - auth + operationId: logoutCurrentSession + summary: Logout current session + description: Terminates the calling actor's current session and invalidates + the bearer token. + security: + - bearerAuth: [] + responses: + '204': + description: Session terminated + '401': + $ref: '#/components/responses/Unauthorized' + /api/v1/catalog: + get: + tags: + - catalog + operationId: listCatalogItems + summary: List catalog items available to the authenticated actor (RBAC-filtered) + parameters: + - $ref: '#/components/parameters/page_size' + - $ref: '#/components/parameters/page_token' + - name: category + in: query + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CatalogItemList' + /api/v1/catalog/{catalog_item_uuid}: + get: + tags: + - catalog + operationId: getCatalogItem + summary: Get full schema and details for a catalog item including field constraints + parameters: + - $ref: '#/components/parameters/catalog_item_uuid' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CatalogItem' + '404': + $ref: '#/components/responses/NotFound' + /api/v1/catalog/search: + get: + tags: + - catalog + operationId: searchCatalog + summary: Search catalog by keyword, resource type, or tag + parameters: + - name: q + in: query + required: true + schema: + type: string + minLength: 1 + - $ref: '#/components/parameters/page_size' + - $ref: '#/components/parameters/page_token' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CatalogItemList' + /api/v1/requests: + post: + tags: + - requests + operationId: submitRequest + summary: Submit a service request + description: 'Submits a resource request. The request is stored as an Intent + State artifact, processed + + through layer assembly and policy evaluation, placed with a provider, and + dispatched. + + Returns immediately with a request_uuid for status polling or SSE streaming. + + ' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ServiceRequest' + responses: + '200': + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + '400': + $ref: '#/components/responses/BadRequest' + '403': + $ref: '#/components/responses/Forbidden' + '422': + $ref: '#/components/responses/UnprocessableEntity' + get: + tags: + - requests + operationId: listRequests + summary: List requests submitted by the authenticated actor + parameters: + - $ref: '#/components/parameters/page_size' + - $ref: '#/components/parameters/page_token' + - name: status + in: query + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RequestList' + /api/v1/requests/{request_uuid}/status: + get: + tags: + - requests + operationId: getRequestStatus + summary: Poll request status and pipeline stage + parameters: + - $ref: '#/components/parameters/request_uuid' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RequestStatus' + '404': + $ref: '#/components/responses/NotFound' + /api/v1/requests/{request_uuid}/stream: + get: + tags: + - requests + operationId: streamRequestStatus + summary: Stream real-time request status updates (Server-Sent Events) + parameters: + - $ref: '#/components/parameters/request_uuid' + responses: + '200': + description: SSE stream of RequestStatus events + content: + text/event-stream: + schema: + type: string + /api/v1/requests/{request_uuid}: + delete: + tags: + - requests + operationId: cancelRequest + summary: Cancel a pending request (before provider dispatch) + parameters: + - $ref: '#/components/parameters/request_uuid' + responses: + '204': + description: Request cancelled + '409': + description: Request already dispatched — cannot cancel + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /api/v1/request-groups: + post: + tags: + - requests + operationId: createRequestGroup + summary: Submit a group of related requests (bulk or dependency-ordered) + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RequestGroup' + responses: + '200': + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + /api/v1/request-groups/{group_uuid}: + get: + tags: + - requests + operationId: getRequestGroup + summary: Get status of all requests in a group + parameters: + - $ref: '#/components/parameters/group_uuid' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RequestGroupStatus' + /api/v1/resources: + get: + tags: + - resources + operationId: listResources + summary: List resources owned by the authenticated actor's Tenant + parameters: + - $ref: '#/components/parameters/page_size' + - $ref: '#/components/parameters/page_token' + - name: resource_type + in: query + schema: + type: string + - name: lifecycle_state + in: query + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/ResourceList' + /api/v1/resources/{entity_uuid}: + get: + tags: + - resources + operationId: getResource + summary: Get full entity detail for a resource + parameters: + - $ref: '#/components/parameters/entity_uuid' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/ResourceEntity' + '404': + $ref: '#/components/responses/NotFound' + patch: + tags: + - resources + operationId: updateResource + summary: Update editable fields on a realized resource (delta only) + parameters: + - $ref: '#/components/parameters/entity_uuid' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ResourceUpdate' + responses: + '200': + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + '400': + $ref: '#/components/responses/BadRequest' + '403': + $ref: '#/components/responses/Forbidden' + delete: + tags: + - resources + operationId: decommissionResource + summary: Initiate resource decommission + parameters: + - $ref: '#/components/parameters/entity_uuid' + responses: + '200': + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + '409': + description: Resource has active dependents — cannot decommission + /api/v1/resources/{entity_uuid}:suspend: + post: + tags: + - resources + operationId: suspendResource + summary: Suspend an OPERATIONAL resource + parameters: + - $ref: '#/components/parameters/entity_uuid' + responses: + '200': + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + '409': + description: Resource not in OPERATIONAL state + /api/v1/resources/{entity_uuid}:resume: + post: + tags: + - resources + operationId: resumeResource + summary: Resume a SUSPENDED resource + parameters: + - $ref: '#/components/parameters/entity_uuid' + responses: + '200': + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + '409': + description: Resource not in SUSPENDED state + /api/v1/resources/{entity_uuid}:rehydrate: + post: + tags: + - resources + operationId: rehydrateResource + summary: Rehydrate a resource to a new provider or context + parameters: + - $ref: '#/components/parameters/entity_uuid' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RehydrateRequest' + responses: + '200': + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + '403': + $ref: '#/components/responses/Forbidden' + /api/v1/resources/{entity_uuid}:extend-ttl: + post: + tags: + - resources + operationId: extendTtl + summary: Extend or modify the TTL of a resource + parameters: + - $ref: '#/components/parameters/entity_uuid' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - new_ttl + properties: + new_ttl: + type: string + description: ISO 8601 duration or datetime + responses: + '200': + description: TTL updated + /api/v1/resources/expiring: + get: + tags: + - resources + operationId: listExpiringResources + summary: List resources expiring within a time window + parameters: + - name: within + in: query + schema: + type: string + description: ISO 8601 duration (e.g., P30D) + - $ref: '#/components/parameters/page_size' + - $ref: '#/components/parameters/page_token' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/ResourceList' + /api/v1/resources/{entity_uuid}:transfer: + post: + tags: + - resources + operationId: initiateOwnershipTransfer + summary: Initiate ownership transfer to another Tenant + parameters: + - $ref: '#/components/parameters/entity_uuid' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - target_tenant_uuid + - reason + properties: + target_tenant_uuid: + type: string + format: uuid + reason: + type: string + responses: + '200': + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + /api/v1/resources/transfers/{transfer_uuid}:accept: + post: + tags: + - resources + operationId: acceptOwnershipTransfer + summary: Accept an incoming ownership transfer + parameters: + - name: transfer_uuid + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Transfer accepted; ownership updated + /api/v1/resources/transfers/{transfer_uuid}:reject: + post: + tags: + - resources + operationId: rejectOwnershipTransfer + summary: Reject an incoming ownership transfer + parameters: + - name: transfer_uuid + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Transfer rejected + /api/v1/resources:bulk-decommission: + post: + tags: + - resources + operationId: bulkDecommission + summary: Decommission multiple resources in dependency-safe order + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - entity_uuids + properties: + entity_uuids: + type: array + items: + type: string + format: uuid + minItems: 1 + dry_run: + type: boolean + default: false + responses: + '200': + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + /api/v1/resources/{entity_uuid}/relationships: + get: + tags: + - resources + operationId: getResourceRelationships + summary: Get all relationships for a resource entity + parameters: + - $ref: '#/components/parameters/entity_uuid' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + relationships: + type: array + items: + type: object + /api/v1/resources/{entity_uuid}/provider-notifications: + get: + tags: + - resources + operationId: getProviderNotifications + summary: List pending provider-initiated state change notifications requiring + approval + parameters: + - $ref: '#/components/parameters/entity_uuid' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + notifications: + type: array + items: + type: object + /api/v1/resources/{entity_uuid}/provider-notifications/{notification_uuid}:approve: + post: + tags: + - resources + operationId: approveProviderNotification + summary: Approve a provider-initiated state change + parameters: + - $ref: '#/components/parameters/entity_uuid' + - name: notification_uuid + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Provider notification approved + /api/v1/resources/{entity_uuid}/recovery-decisions: + get: + tags: + - resources + operationId: getRecoveryDecisions + summary: List pending recovery decisions for a resource (for notify_and_wait + policies) + parameters: + - $ref: '#/components/parameters/entity_uuid' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + decisions: + type: array + items: + type: object + /api/v1/resources/{entity_uuid}/recovery-decisions/{recovery_decision_uuid}: + post: + tags: + - resources + operationId: resolveRecoveryDecision + summary: Resolve a pending recovery decision + parameters: + - $ref: '#/components/parameters/entity_uuid' + - name: recovery_decision_uuid + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - decision + properties: + decision: + type: string + enum: + - approve + - reject + - escalate + reason: + type: string + responses: + '200': + description: Decision recorded + /api/v1/resources/{entity_uuid}/audit: + get: + tags: + - audit + operationId: getResourceAudit + summary: Get audit trail for a resource entity + parameters: + - $ref: '#/components/parameters/entity_uuid' + - $ref: '#/components/parameters/page_size' + - $ref: '#/components/parameters/page_token' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AuditList' + /api/v1/resources/{entity_uuid}/drift: + get: + tags: + - drift + operationId: getResourceDrift + summary: Get current drift records for a resource + parameters: + - $ref: '#/components/parameters/entity_uuid' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DriftRecordList' + /api/v1/resources/{entity_uuid}/drift/{drift_uuid}:acknowledge: + post: + tags: + - drift + operationId: acknowledgeDrift + summary: Acknowledge a drift record (suppress notification without resolving) + parameters: + - $ref: '#/components/parameters/entity_uuid' + - name: drift_uuid + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Drift acknowledged + /api/v1/resources/{entity_uuid}/drift/{drift_uuid}:accept: + post: + tags: + - drift + operationId: acceptDrift + summary: Accept the drifted state as the new intended state (update Requested + State) + parameters: + - $ref: '#/components/parameters/entity_uuid' + - name: drift_uuid + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Drifted state accepted; Requested State updated + /api/v1/resources/{entity_uuid}/drift/{drift_uuid}:revert: + post: + tags: + - drift + operationId: revertDrift + summary: Revert the resource to its Requested State (dispatch remediation) + parameters: + - $ref: '#/components/parameters/entity_uuid' + - name: drift_uuid + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + /api/v1/groups: + get: + tags: + - groups + operationId: listGroups + summary: List groups the authenticated actor's Tenant has access to + responses: + '200': + content: + application/json: + schema: + type: object + properties: + groups: + type: array + items: + type: object + /api/v1/groups/{group_uuid}: + get: + tags: + - groups + operationId: getGroup + summary: Get group detail and member list + parameters: + - name: group_uuid + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + content: + application/json: + schema: + type: object + /api/v1/groups/{group_uuid}/members: + post: + tags: + - groups + operationId: addGroupMember + summary: Add an entity to a group + parameters: + - name: group_uuid + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - entity_uuid + properties: + entity_uuid: + type: string + format: uuid + responses: + '201': + description: Entity added to group + /api/v1/groups/{group_uuid}/members/{entity_uuid}: + delete: + tags: + - groups + operationId: removeGroupMember + summary: Remove an entity from a group + parameters: + - name: group_uuid + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/entity_uuid' + responses: + '204': + description: Entity removed from group + /api/v1/approvals/pending: + get: + tags: + - approvals + operationId: listPendingApprovals + summary: List approval decisions pending the authenticated actor's action + parameters: + - $ref: '#/components/parameters/page_size' + - $ref: '#/components/parameters/page_token' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + approvals: + type: array + items: + $ref: '#/components/schemas/ApprovalRecord' + /api/v1/approvals/{approval_uuid}: + post: + tags: + - approvals + operationId: recordApprovalDecision + summary: Record an approval decision (approve/reject/abstain) + description: 'Designed to be called by both humans via the UI and by external + systems (ITSM, Slack bots, + + CI/CD pipelines) that have been authorized to record decisions on behalf of + the organization. + + DCM enforces the gate and records the audit trail; the deliberation process + is the + + organization''s responsibility. + + ' + parameters: + - name: approval_uuid + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - decision + - reason + properties: + decision: + type: string + enum: + - approve + - reject + - abstain + reason: + type: string + minLength: 1 + external_reference: + type: string + description: ITSM ticket ID, Jira issue key, etc. + responses: + '200': + description: Decision recorded; approval gate re-evaluated + '403': + $ref: '#/components/responses/Forbidden' + '409': + description: Approval already in terminal state + /api/v1/cost/estimate: + post: + tags: + - cost + operationId: estimateCost + summary: Get cost estimate before submitting a request + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ServiceRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CostEstimate' + /api/v1/resources/{entity_uuid}/cost: + get: + tags: + - cost + operationId: getResourceCost + summary: Get cost actuals and attribution for a resource + parameters: + - $ref: '#/components/parameters/entity_uuid' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CostActuals' + /api/v1/operations/{operation_uuid}: + get: + tags: + - operations + operationId: getOperation + summary: Poll a Long-Running Operation for status (AEP-136) + description: 'Returns the current state of an async operation. Poll until `done` + is `true`. + + + **Note:** `operation_uuid == request_uuid`. All async operations initiated + via + + POST /api/v1/requests, PATCH /api/v1/resources/{uuid}, etc., return an Operation + + whose UUID is the same as the request UUID. + + + Two polling views are available: + + - This endpoint: AEP-standard (done, metadata, response/error) + + - GET /api/v1/requests/{uuid}/status: DCM-native rich view (pipeline_stage, + full history) + + + Both reflect the same underlying operation state. + + ' + parameters: + - name: operation_uuid + in: path + required: true + description: The operation UUID (same as the request UUID) + schema: + type: string + format: uuid + security: + - bearerAuth: [] + responses: + '200': + description: Operation status (check `done` field for completion) + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + '404': + $ref: '#/components/responses/NotFound' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /api/v1/quota: + get: + tags: + - cost + operationId: getQuota + summary: Get quota status for the authenticated actor's Tenant + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/QuotaStatus' + /api/v1/notifications: + get: + tags: + - notifications + operationId: listNotifications + summary: List notifications in the authenticated actor's inbox + parameters: + - name: unread_only + in: query + schema: + type: boolean + - $ref: '#/components/parameters/page_size' + - $ref: '#/components/parameters/page_token' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + notifications: + type: array + items: + type: object + /api/v1/notifications/{notification_uuid}/read: + post: + tags: + - notifications + operationId: markNotificationRead + summary: Mark a notification as read + parameters: + - name: notification_uuid + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Marked as read + /api/v1/notifications:read-all: + post: + tags: + - notifications + operationId: markAllNotificationsRead + summary: Mark all notifications as read + responses: + '204': + description: All notifications marked as read + /api/v1/webhooks: + get: + tags: + - webhooks + operationId: listWebhooks + summary: List outbound webhook subscriptions for the authenticated actor + responses: + '200': + content: + application/json: + schema: + type: object + properties: + webhooks: + type: array + items: + $ref: '#/components/schemas/WebhookSubscription' + post: + tags: + - webhooks + operationId: createWebhook + summary: Create an outbound webhook subscription + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookSubscription' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookSubscription' + /api/v1/webhooks/{webhook_uuid}: + delete: + tags: + - webhooks + operationId: deleteWebhook + summary: Delete a webhook subscription + parameters: + - name: webhook_uuid + in: path + required: true + schema: + type: string + format: uuid + responses: + '204': + description: Webhook deleted + # ── Workload Analysis ───────────────────────────────────────────────────────── + /api/v1/resources/{entity_uuid}/workload-profile: + get: + tags: [workload-analysis] + operationId: getWorkloadProfile + summary: Get the Workload Analysis profile for a resource + description: | + Returns the most recent WorkloadProfile entity for the given resource. + WorkloadProfiles are created automatically during brownfield ingestion + and can be refreshed on demand via the :analyze custom method. + parameters: + - {name: entity_uuid, in: path, required: true, schema: {type: string, format: uuid}} + security: [{bearerAuth: []}] + responses: + "200": + description: Workload profile + content: + application/json: + schema: {$ref: "#/components/schemas/WorkloadProfile"} + "404": {$ref: "#/components/responses/NotFound"} + "403": {$ref: "#/components/responses/Forbidden"} + + /api/v1/resources/{entity_uuid}/workload-profile:analyze: + post: + tags: [workload-analysis] + operationId: analyzeWorkload + summary: Trigger re-analysis of a resource's workload profile + description: | + Initiates a new Workload Analysis pass for the resource. The existing + WorkloadProfile is superseded when the new analysis completes. Useful + when a resource's role has changed since initial classification. + parameters: + - {name: entity_uuid, in: path, required: true, schema: {type: string, format: uuid}} + security: [{bearerAuth: []}] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [reason] + properties: + reason: {type: string, description: Why re-analysis is needed} + include_mta: {type: boolean, default: true, description: Include MTA containerization assessment} + responses: + "200": + description: Operation initiated — poll for completion + content: + application/json: + schema: {$ref: "#/components/schemas/Operation"} + "404": {$ref: "#/components/responses/NotFound"} + + /api/v1/search: + get: + tags: + - search + operationId: search + summary: Cross-entity search (resources, groups, catalog items) + parameters: + - name: q + in: query + required: true + schema: + type: string + minLength: 1 + - name: types + in: query + schema: + type: array + items: + type: string + style: form + explode: false + - $ref: '#/components/parameters/page_size' + - $ref: '#/components/parameters/page_token' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/SearchResults' + /api/v1/audit/correlation/{correlation_id}: + get: + tags: + - audit + operationId: getAuditByCorrelation + summary: Get all audit records for a correlation ID (cross-resource trace) + parameters: + - name: correlation_id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AuditList' + /api/v1/contribute/policy: + post: + tags: + - contributions + operationId: contributePolicy + summary: Submit a policy contribution via the federated contribution pipeline + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PolicyContribution' + responses: + '200': + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + /api/v1/contribute/resource-group: + post: + tags: + - contributions + operationId: contributeResourceGroup + summary: Submit a resource group contribution + requestBody: + required: true + content: + application/json: + schema: + type: object + responses: + '200': + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + /api/v1/contribute: + get: + tags: + - contributions + operationId: listContributions + summary: List contributions submitted by the authenticated actor + responses: + '200': + content: + application/json: + schema: + type: object + properties: + contributions: + type: array + items: + type: object + /api/v1/contribute/{contribution_uuid}: + delete: + tags: + - contributions + operationId: withdrawContribution + summary: Withdraw a pending contribution + parameters: + - name: contribution_uuid + in: path + required: true + schema: + type: string + format: uuid + responses: + '204': + description: Contribution withdrawn + '409': + description: Contribution already activated — cannot withdraw + /api/v1/resources/{entity_uuid}/credentials: + get: + tags: + - credentials + operationId: listResourceCredentials + summary: List credentials associated with a resource (metadata only — no values) + parameters: + - $ref: '#/components/parameters/entity_uuid' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + credentials: + type: array + items: + $ref: '#/components/schemas/CredentialSummary' + /api/v1/credentials/{credential_uuid}/value: + get: + tags: + - credentials + operationId: getCredentialValue + summary: Retrieve the current credential value (audited; step-up auth may be + required) + parameters: + - name: credential_uuid + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CredentialValue' + '403': + $ref: '#/components/responses/Forbidden' + /api/v1/credentials/{credential_uuid}:rotate: + post: + tags: + - credentials + operationId: rotateCredential + summary: Initiate credential rotation + parameters: + - name: credential_uuid + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + # ── Subscription Management (doc 50) ────────────────────────────────── + + /api/v1/subscriptions: + get: + operationId: listSubscriptions + summary: List tenant subscriptions (paginated) + tags: [Subscription Management] + parameters: + - { name: page_size, in: query, schema: { type: integer, default: 100 } } + - { name: page_token, in: query, schema: { type: string } } + - { name: lifecycle_state, in: query, schema: { type: string } } + responses: + '200': + description: Paginated subscription list + content: + application/json: + schema: + $ref: '#/components/schemas/SubscriptionList' + post: + operationId: createSubscription + summary: Create a subscription (enters request pipeline) + tags: [Subscription Management] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SubscriptionCreate' + responses: + '200': + description: Subscription creation initiated. Poll operation for status. + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + + /api/v1/subscriptions/{subscription_uuid}: + get: + operationId: getSubscription + summary: Get subscription details + tags: [Subscription Management] + parameters: + - { name: subscription_uuid, in: path, required: true, schema: { type: string, format: uuid } } + responses: + '200': + description: Subscription details + content: + application/json: + schema: + $ref: '#/components/schemas/Subscription' + patch: + operationId: updateSubscription + summary: Update subscription (tier change, auto_renew toggle) + tags: [Subscription Management] + parameters: + - { name: subscription_uuid, in: path, required: true, schema: { type: string, format: uuid } } + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SubscriptionUpdate' + responses: + '200': + description: Update initiated + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + + /api/v1/subscriptions/{subscription_uuid}:cancel: + post: + operationId: cancelSubscription + summary: Cancel subscription (starts grace period) + tags: [Subscription Management] + parameters: + - { name: subscription_uuid, in: path, required: true, schema: { type: string, format: uuid } } + requestBody: + content: + application/json: + schema: + type: object + properties: + reason: { type: string } + responses: + '200': + description: Cancellation initiated + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + + /api/v1/subscriptions/{subscription_uuid}:renew: + post: + operationId: renewSubscription + summary: Manually renew subscription + tags: [Subscription Management] + parameters: + - { name: subscription_uuid, in: path, required: true, schema: { type: string, format: uuid } } + responses: + '200': + description: Renewal initiated + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + + /api/v1/subscriptions/{subscription_uuid}:suspend: + post: + operationId: suspendSubscription + summary: Suspend subscription + tags: [Subscription Management] + parameters: + - { name: subscription_uuid, in: path, required: true, schema: { type: string, format: uuid } } + requestBody: + content: + application/json: + schema: + type: object + properties: + reason: { type: string } + responses: + '200': + description: Suspension initiated + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + + /api/v1/subscriptions/{subscription_uuid}:resume: + post: + operationId: resumeSubscription + summary: Resume suspended subscription + tags: [Subscription Management] + parameters: + - { name: subscription_uuid, in: path, required: true, schema: { type: string, format: uuid } } + responses: + '200': + description: Resume initiated + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + + /api/v1/subscriptions/{subscription_uuid}/entities: + get: + operationId: listSubscriptionEntities + summary: List managed entities under this subscription + tags: [Subscription Management] + parameters: + - { name: subscription_uuid, in: path, required: true, schema: { type: string, format: uuid } } + - { name: page_size, in: query, schema: { type: integer, default: 100 } } + - { name: page_token, in: query, schema: { type: string } } + responses: + '200': + description: Paginated entity list + content: + application/json: + schema: + $ref: '#/components/schemas/ResourceList' + + /api/v1/subscriptions/{subscription_uuid}/updates: + get: + operationId: listSubscriptionUpdates + summary: List pending and applied updates + tags: [Subscription Management] + parameters: + - { name: subscription_uuid, in: path, required: true, schema: { type: string, format: uuid } } + - { name: status, in: query, schema: { type: string, enum: [PENDING, APPROVED, REJECTED, APPLIED, FAILED] } } + - { name: page_size, in: query, schema: { type: integer, default: 100 } } + - { name: page_token, in: query, schema: { type: string } } + responses: + '200': + description: Paginated update list + content: + application/json: + schema: + $ref: '#/components/schemas/SubscriptionUpdateList' + + /api/v1/subscriptions/{subscription_uuid}/updates/{update_uuid}:approve: + post: + operationId: approveSubscriptionUpdate + summary: Approve a pending provider-originated update + tags: [Subscription Management] + parameters: + - { name: subscription_uuid, in: path, required: true, schema: { type: string, format: uuid } } + - { name: update_uuid, in: path, required: true, schema: { type: string, format: uuid } } + responses: + '200': + description: Update approved + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + + /api/v1/subscriptions/{subscription_uuid}/updates/{update_uuid}:reject: + post: + operationId: rejectSubscriptionUpdate + summary: Reject a pending provider-originated update + tags: [Subscription Management] + parameters: + - { name: subscription_uuid, in: path, required: true, schema: { type: string, format: uuid } } + - { name: update_uuid, in: path, required: true, schema: { type: string, format: uuid } } + requestBody: + content: + application/json: + schema: + type: object + properties: + reason: { type: string } + responses: + '200': + description: Update rejected + content: + application/json: + schema: + $ref: '#/components/schemas/Operation' + + + /api/v1/requests/{request_uuid}/resolution: + get: + summary: Get policy block details and resolution options + operationId: getResolutionOptions + parameters: + - name: request_uuid + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: Blocking details and resolution options + /api/v1/requests/{request_uuid}:resolve: + post: + summary: Resolve a policy-blocked request + operationId: resolveBlockedRequest + parameters: + - name: request_uuid + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [action] + properties: + action: + type: string + enum: [modify, request_override, cancel, escalate] + modifications: + type: object + justification: + type: string + compensating_controls: + type: array + items: + type: string + context: + type: string + responses: + '200': + description: Resolution action accepted + +components: + securitySchemes: + BearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: Session token obtained from POST /api/v1/auth/token + parameters: + entity_uuid: + name: entity_uuid + in: path + required: true + schema: + type: string + format: uuid + description: UUID of the resource entity + request_uuid: + name: request_uuid + in: path + required: true + schema: + type: string + format: uuid + catalog_item_uuid: + name: catalog_item_uuid + in: path + required: true + schema: + type: string + format: uuid + group_uuid: + name: group_uuid + in: path + required: true + schema: + type: string + format: uuid + session_uuid: + name: session_uuid + in: path + required: true + schema: + type: string + format: uuid + limit: + name: page_size + in: query + schema: + type: integer + minimum: 1 + maximum: 1000 + default: 50 + cursor: + name: page_token + in: query + schema: + type: string + description: Opaque page token from previous response (use next_page_token from + response) + responses: + BadRequest: + description: Invalid request syntax + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Unauthorized: + description: Missing or invalid authentication token + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Forbidden: + description: Authenticated but not authorized. rule_uuid identifies the governing + policy. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + NotFound: + description: Resource not found or not visible to authenticated actor + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + UnprocessableEntity: + description: Request syntax valid but semantically invalid (e.g., field validation + failure) + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + schemas: + Operation: + type: object + description: "AEP-136 Long-Running Operation. Returned by async operations instead\ + \ of 202 Accepted.\nPoll GET {operation.name} until done is true.\nThe operation.name\ + \ is a stable resource path: /api/v1/operations/{uuid}\nNote: operation_uuid\ + \ == request_uuid. Two polling endpoints are available:\n - GET /api/v1/operations/{uuid}\ + \ — AEP-standard thin view (done, metadata, response/error)\n - GET /api/v1/requests/{uuid}/status\ + \ — DCM-native rich view (pipeline_stage, full status history)\nBoth endpoints\ + \ reflect the same underlying operation state.\n" + required: + - name + - done + additionalProperties: false + properties: + name: + type: string + description: Stable resource path for this operation. Poll this URL for + status. + example: /api/v1/operations/a1b2c3d4-e5f6-7890-abcd-ef1234567890 + done: + type: boolean + description: True when the operation has reached a terminal state (success + or error) + default: false + metadata: + type: object + description: Operation-specific progress metadata + additionalProperties: false + properties: + stage: + type: string + description: Current pipeline stage + progress_pct: + type: integer + minimum: 0 + maximum: 100 + resource_uuid: + type: string + format: uuid + description: UUID of the resource being created/modified (set as soon + as assigned) + request_uuid: + type: string + format: uuid + description: The DCM request UUID. operation_uuid == request_uuid. Use + GET /api/v1/requests/{request_uuid}/status for the full DCM-native + pipeline view. + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + response: + type: object + description: Present when done is true and the operation succeeded. Contains + the result resource. + additionalProperties: true + error: + type: object + description: Present when done is true and the operation failed. + additionalProperties: false + properties: + code: + type: string + message: + type: string + details: + type: array + items: + type: object + Error: + type: object + required: + - error + properties: + error: + type: object + required: + - code + - message + - request_id + properties: + code: + type: string + message: + type: string + request_id: + type: string + format: uuid + rule_uuid: + type: string + format: uuid + description: Present on POLICY_DENIED and GOVERNANCE_DENIED + fields: + type: array + items: + type: object + properties: + field: + type: string + issue: + type: string + SessionTokenResponse: + type: object + required: + - access_token + - token_type + - expires_in + - session_uuid + properties: + access_token: + type: string + token_type: + type: string + const: bearer + expires_in: + type: integer + description: Seconds until expiry + session_uuid: + type: string + format: uuid + refresh_token: + type: string + SessionSummary: + type: object + properties: + session_uuid: + type: string + format: uuid + created_at: + type: string + format: date-time + expires_at: + type: string + format: date-time + ip_address: + type: string + user_agent: + type: string + current: + type: boolean + TokenIntrospection: + type: object + properties: + active: + type: boolean + actor_uuid: + type: string + format: uuid + actor_type: + type: string + display_name: + type: string + tenant_uuids: + type: array + items: + type: string + format: uuid + roles: + type: array + items: + type: string + groups: + type: array + items: + type: string + expires_at: + type: string + format: date-time + CatalogItem: + type: object + properties: + uuid: + type: string + format: uuid + resource_type: + description: Resource type — FQN string (e.g., 'Compute.VirtualMachine') + or Registry UUID. DCM resolves either form internally. + oneOf: + - type: string + pattern: ^[A-Z][a-zA-Z0-9]+\.[A-Z][a-zA-Z0-9]+$ + description: FQN form (recommended) + - type: string + format: uuid + description: UUID form + display_name: + type: string + description: + type: string + version: + type: string + status: + type: string + provider_uuid: + type: string + format: uuid + field_schema: + type: object + description: 'JSON Schema for the request body fields. Each field includes + a constraint block with type: range|enum|pattern|layer_reference|layer_reference_list. + For layer_reference constraints, allowed_values is resolved at render + time from active Reference Data Layer instances of the declared layer_type.' + cost_estimate: + type: object + dependencies: + type: array + items: + type: object + CatalogItemList: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/CatalogItem' + pagination: + type: object + ServiceRequest: + type: object + required: + - catalog_item_uuid + - fields + properties: + catalog_item_uuid: + type: string + format: uuid + fields: + type: object + description: Resource-type-specific field values + additionalProperties: true + group_uuid: + type: string + format: uuid + description: Assign to a resource group on creation + scheduled_at: + type: string + format: date-time + description: Defer request execution to this time + depends_on: + type: array + items: + type: string + format: uuid + description: Request UUIDs that must complete first + dry_run: + type: boolean + default: false + description: Evaluate policy and placement without submitting + RequestAccepted: + type: object + properties: + request_uuid: + type: string + format: uuid + status: + type: string + stream_url: + type: string + format: uri + RequestStatus: + type: object + properties: + request_uuid: + type: string + format: uuid + status: + type: string + enum: + - ACKNOWLEDGED + - ASSEMBLING + - AWAITING_APPROVAL + - APPROVED + - DISPATCHED + - PROVISIONING + - COMPLETED + - FAILED + - CANCELLED + - SCHEDULED + - PENDING_DEPENDENCY + description: "Consumer request lifecycle status. COMPLETED/FAILED/CANCELLED are terminal." + pipeline_stage: + type: string + entity_uuid: + type: string + format: uuid + description: Set once realization begins + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + error: + $ref: '#/components/schemas/Error' + RequestList: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/RequestStatus' + pagination: + type: object + RequestGroup: + type: object + required: + - requests + properties: + requests: + type: array + items: + $ref: '#/components/schemas/ServiceRequest' + minItems: 1 + ordered: + type: boolean + default: false + description: Execute in declared order with dependency waiting + group_name: + type: string + RequestGroupAccepted: + type: object + properties: + group_uuid: + type: string + format: uuid + request_uuids: + type: array + items: + type: string + format: uuid + RequestGroupStatus: + type: object + properties: + group_uuid: + type: string + format: uuid + status: + type: string + requests: + type: array + items: + $ref: '#/components/schemas/RequestStatus' + ResourceEntity: + type: object + description: Resource entity as returned by the Consumer API (may be field-filtered + by governance matrix) + properties: + uuid: + type: string + format: uuid + entity_type: + type: string + resource_type: + type: string + lifecycle_state: + type: string + owned_by_tenant_uuid: + type: string + format: uuid + provider_uuid: + type: string + format: uuid + drift_status: + type: string + billing_state: + type: string + ttl_expires_at: + type: string + format: date-time + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + fields: + type: object + additionalProperties: true + description: Resource-type-specific realized fields + relationships: + type: array + items: + type: object + ResourceList: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/ResourceEntity' + pagination: + type: object + ResourceUpdate: + type: object + required: + - fields + properties: + fields: + type: object + additionalProperties: true + description: Editable fields and their new values (delta only) + reason: + type: string + RehydrateRequest: + type: object + properties: + target_provider_uuid: + type: string + format: uuid + reason: + type: string + DriftRecord: + type: object + properties: + drift_uuid: + type: string + format: uuid + entity_uuid: + type: string + format: uuid + detected_at: + type: string + format: date-time + severity: + type: string + enum: + - minor + - significant + - critical + unsanctioned: + type: boolean + status: + type: string + enum: + - open + - acknowledged + - resolved + - escalated + field_differences: + type: array + items: + type: object + DriftRecordList: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/DriftRecord' + pagination: + type: object + ApprovalRecord: + type: object + properties: + approval_uuid: + type: string + format: uuid + subject_type: + type: string + subject_uuid: + type: string + format: uuid + required_tier: + type: string + required_quorum: + type: integer + decisions_so_far: + type: integer + expires_at: + type: string + format: date-time + created_at: + type: string + format: date-time + CostEstimate: + type: object + properties: + estimated_monthly_cost: + type: number + currency: + type: string + breakdown: + type: array + items: + type: object + confidence: + type: string + enum: + - high + - medium + - low + CostActuals: + type: object + properties: + entity_uuid: + type: string + format: uuid + period_start: + type: string + format: date-time + period_end: + type: string + format: date-time + total_cost: + type: number + currency: + type: string + breakdown: + type: array + items: + type: object + QuotaStatus: + type: object + properties: + tenant_uuid: + type: string + format: uuid + quotas: + type: array + items: + type: object + WebhookSubscription: + type: object + required: + - endpoint_url + - events + properties: + webhook_uuid: + type: string + format: uuid + readOnly: true + endpoint_url: + type: string + format: uri + events: + type: array + items: + type: string + description: Event types from the DCM Event Catalog + secret: + type: string + description: HMAC-SHA256 signing secret for signature verification + active: + type: boolean + default: true + created_at: + type: string + format: date-time + readOnly: true + SearchResults: + type: object + properties: + results: + type: array + items: + type: object + pagination: + type: object + AuditRecord: + type: object + properties: + audit_uuid: + type: string + format: uuid + entity_uuid: + type: string + format: uuid + event_type: + type: string + actor: + type: object + recorded_at: + type: string + format: date-time + correlation_id: + type: string + format: uuid + payload: + type: object + AuditList: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/AuditRecord' + pagination: + type: object + PolicyContribution: + type: object + required: + - policy_artifact + properties: + policy_artifact: + type: object + description: DCM policy artifact per B-policy-contract schema + rationale: + type: string + shadow_first: + type: boolean + default: true + ContributionAccepted: + type: object + properties: + contribution_uuid: + type: string + format: uuid + status: + type: string + shadow_mode: + type: boolean + CredentialSummary: + type: object + properties: + credential_uuid: + type: string + format: uuid + credential_type: + type: string + entity_uuid: + type: string + format: uuid + expires_at: + type: string + format: date-time + rotated_at: + type: string + format: date-time + CredentialValue: + type: object + properties: + credential_uuid: + type: string + format: uuid + credential_type: + type: string + value: + type: object + description: Credential-type-specific value (e.g., kubeconfig, token, certificate) + expires_at: + type: string + format: date-time + + WorkloadProfile: + type: object + description: "Workload analysis classification for a DCM resource entity (doc 46)" + properties: + workload_profile_uuid: {type: string, format: uuid} + subject_entity_uuid: {type: string, format: uuid} + analyzed_at: {type: string, format: date-time} + analysis_version: {type: string} + classification: + type: object + properties: + resource_type_match: + type: object + properties: + primary: {type: string} + confidence: {type: string, enum: [high, medium, low, undetermined]} + workload_archetype: + type: object + properties: + type: {type: string, enum: [web_server, database, batch_processor, message_broker, api_gateway, cache, storage, monitoring, unknown]} + confidence: {type: string, enum: [high, medium, low, undetermined]} + migration_readiness: + type: object + properties: + containerization_score: {type: integer, minimum: 1, maximum: 10} + blockers: {type: array, items: {type: string}} + suggested_target: {type: string} + lifecycle_recommendation: + type: object + properties: + dcm_lifecycle_model: {type: string} + rehydration_eligible: {type: boolean} + notes: {type: string} + + # ── Subscription Schemas (doc 50) ──────────────────────────────── + + Subscription: + type: object + required: [subscription_uuid, tenant_uuid, catalog_item_uuid, lifecycle_state] + properties: + subscription_uuid: {type: string, format: uuid} + handle: {type: string} + display_name: {type: string} + tenant_uuid: {type: string, format: uuid} + catalog_item_uuid: {type: string, format: uuid} + resource_type: {type: string} + provider_uuid: {type: string, format: uuid} + lifecycle_state: + type: string + enum: [PENDING, PROVISIONING, ACTIVE, SUSPENDED, RENEWAL_PENDING, TIER_CHANGE_PENDING, EXPIRED, CANCELLED, DECOMMISSIONING, DECOMMISSIONED] + terms: + type: object + properties: + tier: {type: string} + consumption_model: {type: string, enum: [on_demand, reserved, subscription]} + billing_period: {type: string} + auto_renew: {type: boolean} + renewal_advance_notice: {type: string, description: 'ISO 8601 duration'} + started_at: {type: string, format: date-time} + expires_at: {type: string, format: date-time} + terms_version: {type: string} + entitlements: + type: object + properties: + max_instances: {type: integer} + resource_limits: {type: object, additionalProperties: true} + capabilities: {type: array, items: {type: string}} + update_channels: + type: array + items: + type: object + properties: + channel: {type: string} + auto_apply: {type: boolean} + managed_entity_count: {type: integer} + version: {type: string} + created_at: {type: string, format: date-time} + + SubscriptionCreate: + type: object + required: [catalog_item_uuid, consumption_model] + properties: + catalog_item_uuid: {type: string, format: uuid} + consumption_model: {type: string, enum: [on_demand, reserved, subscription]} + subscription_tier: {type: string} + auto_renew: {type: boolean, default: true} + display_name: {type: string} + fields: {type: object, additionalProperties: true} + + SubscriptionUpdate: + type: object + properties: + tier: {type: string} + auto_renew: {type: boolean} + update_channels: + type: array + items: + type: object + properties: + channel: {type: string} + auto_apply: {type: boolean} + + SubscriptionList: + type: object + properties: + subscriptions: {type: array, items: {$ref: '#/components/schemas/Subscription'}} + next_page_token: {type: string} + + SubscriptionUpdateRecord: + type: object + properties: + update_uuid: {type: string, format: uuid} + subscription_uuid: {type: string, format: uuid} + entity_uuid: {type: string, format: uuid} + channel: {type: string} + status: {type: string, enum: [PENDING, APPROVED, REJECTED, APPLIED, FAILED, EXPIRED]} + update_payload: {type: object, additionalProperties: true} + submitted_at: {type: string, format: date-time} + decided_at: {type: string, format: date-time} + auto_applied: {type: boolean} + + SubscriptionUpdateList: + type: object + properties: + updates: {type: array, items: {$ref: '#/components/schemas/SubscriptionUpdateRecord'}} + next_page_token: {type: string} diff --git a/schemas/openapi/dcm-operator-api.yaml b/schemas/openapi/dcm-operator-api.yaml new file mode 100644 index 0000000..4e35381 --- /dev/null +++ b/schemas/openapi/dcm-operator-api.yaml @@ -0,0 +1,621 @@ +openapi: "3.1.0" + +info: + title: DCM Operator Interface — Services API + version: "1.0.0" + description: | + The DCM Services API is the contract between the DCM control plane and Service Provider + operators. DCM calls this API to create, update, discover, and decommission resources. + The operator implements this API; DCM is the client. + + **Conformance levels:** Operators implement one of five conformance levels. Higher levels + unlock additional DCM capabilities. See `dcm-operator-interface-spec.md` for the full + conformance model. + + | Level | Minimum endpoints required | + |-------|---------------------------| + | Level 0 | Label-based passive discovery only — no API required | + | Level 1 | POST /create, GET /list, GET /{id}, DELETE /{id}, POST /health | + | Level 2 | Level 1 + PUT /{id} (update), POST /capacity, POST /discover | + | Level 3 | Level 2 + lifecycle callbacks, decommission_confirmation | + | Level 4 | Level 3 + streaming status, dependency graph, rehydration support | + + **Authentication:** DCM uses mTLS for all operator interactions. Every request presents + a valid DCM-issued certificate. Operators must validate the certificate chain against the + DCM CA registered at provider registration time. + + **Idempotency:** All mutating operations include a `request_id` (DCM request UUID). Operators + must implement idempotency: a second request with the same `request_id` must return the same + result without performing the operation again. + + **Callback pattern:** For long-running operations (create, update, decommission), the operator + responds immediately with `PROVISIONING`/`UPDATING`/`DECOMMISSIONING` status and later + calls the DCM Callback API to report completion or failure. Level 1 operators may alternatively + block until completion. + + + + **AEP Alignment:** This API follows [AEP](https://aep.dev) conventions: + custom methods use colon syntax (`POST /resources/{name}:suspend`), + async operations return an `Operation` resource (AEP-136 LRO), + and list pagination uses `page_size`/`page_token` parameters. + + contact: + name: DCM Project + url: https://github.com/dcm-project + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0 + +servers: + - url: https://{operator-host}/api/v1/{service_type} + description: Operator endpoint (DCM is the client; operator implements this API) + variables: + operator-host: + description: Operator base URL declared at provider registration + default: operator.namespace.svc + service_type: + description: Resource type path segment (e.g., compute.virtualmachine) + default: compute.virtualmachine + +security: + - MutualTLS: [] + +tags: + - name: resources + description: Resource CRUD operations (Level 1+) + - name: discovery + description: Active discovery of existing resources (Level 2+) + - name: capacity + description: Capacity reporting for placement engine (Level 2+) + - name: lifecycle + description: Lifecycle callback endpoints DCM calls for state transitions (Level 3+) + - name: health + description: Provider health endpoint (all levels) + - name: registration + description: Provider self-registration with DCM + +paths: + + /health: + get: + tags: [health] + operationId: healthCheck + summary: Provider health check (all conformance levels) + description: | + DCM polls this endpoint on the configured interval. Operators must respond within + the `failure_threshold` timeout or DCM will record a failed health check. + security: [] + servers: + - url: https://{operator-host} + variables: + operator-host: + default: operator.namespace.svc + responses: + "200": + description: Provider health status + content: + application/json: + schema: { $ref: "#/components/schemas/HealthResponse" } + + /: + post: + tags: [resources] + operationId: createResource + summary: Create a resource (Level 1+) + description: | + DCM dispatches a Requested State payload. The operator naturalizes it to the + provider-native format and initiates resource creation. + + **Response contract:** Return immediately with `PROVISIONING` status (Level 1 may block + to REALIZED, but this is discouraged for resources taking >30s). Report completion via + the DCM Callback API or via the next discovery cycle. + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/CreateRequest" } + responses: + "202": + description: Request accepted; resource creation initiated + content: + application/json: + schema: { $ref: "#/components/schemas/CreateResponse" } + "200": + description: Resource realized synchronously (Level 1 blocking response) + content: + application/json: + schema: { $ref: "#/components/schemas/RealizedStatePayload" } + "400": { $ref: "#/components/responses/BadRequest" } + "403": { $ref: "#/components/responses/Forbidden" } + "409": + description: Resource already exists with this request_id (idempotency) + content: + application/json: + schema: { $ref: "#/components/schemas/CreateResponse" } + "422": + description: Capacity insufficient or validation failure + content: + application/json: + schema: { $ref: "#/components/schemas/DenialResponse" } + + get: + tags: [resources] + operationId: listResources + summary: List all resources managed by this operator (Level 1+) + parameters: + - name: page_size + in: query + schema: { type: integer, default: 100, maximum: 1000 } + - name: page_token + in: query + schema: { type: string } + - name: lifecycle_state + in: query + schema: { type: string } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/ResourceList" } + + /{resource_id}: + get: + tags: [resources] + operationId: getResource + summary: Get current state of a specific resource (Level 1+) + parameters: + - { $ref: "#/components/parameters/resource_id" } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/RealizedStatePayload" } + "404": { $ref: "#/components/responses/NotFound" } + + put: + tags: [resources] + operationId: updateResource + summary: Apply a delta update to a resource (Level 2+) + description: | + DCM sends the delta (changed fields only). The operator applies the changes and + responds immediately with the current lifecycle state. Reports completion via callback. + parameters: + - { $ref: "#/components/parameters/resource_id" } + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/UpdateRequest" } + responses: + "202": { description: Update accepted and initiated } + "200": + description: Update applied synchronously + content: + application/json: + schema: { $ref: "#/components/schemas/RealizedStatePayload" } + "409": { description: Idempotent response — update already applied } + + delete: + tags: [resources] + operationId: decommissionResource + summary: Decommission and remove a resource (Level 1+) + description: | + DCM sends a decommission request. The operator initiates removal and responds + immediately with `DECOMMISSIONING` status. Reports completion via callback. + + At Level 3+, DCM first sends a `decommission_confirmation` callback to allow + lifecycle policies (retain data, notify stakeholders) to run before deletion proceeds. + parameters: + - { $ref: "#/components/parameters/resource_id" } + requestBody: + content: + application/json: + schema: { $ref: "#/components/schemas/DecommissionRequest" } + responses: + "200": + description: Operation initiated. Poll `operation.name` for completion. + content: + application/json: + schema: { $ref: "#/components/schemas/Operation" } + "409": { description: Resource already decommissioned } + + /discover: + post: + tags: [discovery] + operationId: discoverResources + summary: Trigger active discovery of all resources (Level 2+) + description: | + DCM calls this on the configured schedule to discover the current state of all + resources managed by this operator. The operator returns a complete snapshot of + all currently realized resources in DCM Unified Data Model format. + + This is the mechanism for drift detection — DCM compares Discovered State + (returned here) against Realized State (stored by DCM after last realization). + servers: + - url: https://{operator-host}/api/v1/{service_type} + variables: + operator-host: { default: operator.namespace.svc } + service_type: { default: compute.virtualmachine } + requestBody: + content: + application/json: + schema: { $ref: "#/components/schemas/DiscoverRequest" } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/DiscoverResponse" } + "202": + description: Discovery initiated asynchronously; results will be pushed to DCM Callback API + + /capacity: + post: + tags: [capacity] + operationId: reportCapacity + summary: Report current capacity for placement engine queries (Level 2+) + description: | + DCM calls this when evaluating provider placement for a new request. + The operator reports current available, reserved, and committed capacity. + DCM uses this to select the best provider and to avoid over-committing. + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/CapacityQueryRequest" } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/CapacityReport" } + +# ─── COMPONENTS ──────────────────────────────────────────────────────────────── + +components: + + securitySchemes: + MutualTLS: + type: mutualTLS + description: | + All DCM → Operator interactions use mTLS. DCM presents its certificate; the + operator must validate the certificate chain against the DCM CA registered at + provider registration time. Operators must also present a valid certificate + to DCM on the reverse check. + + parameters: + resource_id: + name: resource_id + in: path + required: true + schema: { type: string } + description: Operator-assigned resource ID (stable identifier; different from DCM entity UUID) + + responses: + BadRequest: + description: Malformed request + content: + application/json: + schema: { $ref: "#/components/schemas/OperatorError" } + Forbidden: + description: mTLS certificate not trusted or insufficient permissions + content: + application/json: + schema: { $ref: "#/components/schemas/OperatorError" } + NotFound: + description: Resource not found + content: + application/json: + schema: { $ref: "#/components/schemas/OperatorError" } + + schemas: + + HealthResponse: + type: object + required: [status, version] + additionalProperties: false + properties: + status: + type: string + enum: [healthy, degraded, unhealthy] + version: { type: string } + capabilities_available: + type: array + items: { type: string } + conformance_level: + type: integer + minimum: 1 + maximum: 4 + details: + type: object + additionalProperties: true + description: Provider-specific; DCM treats as opaque + + CreateRequest: + type: object + required: [request_id, dcm_entity_uuid, tenant_uuid, resource_type, spec] + additionalProperties: false + properties: + request_id: + type: string + format: uuid + description: DCM request UUID — idempotency key; operators must deduplicate on this + dcm_entity_uuid: + type: string + format: uuid + description: DCM-assigned entity UUID; must be echoed back in all responses and callbacks + tenant_uuid: + type: string + format: uuid + resource_type_uuid: + type: string + format: uuid + resource_type_name: + type: string + description: FQN (e.g., Compute.VirtualMachine) + spec: + type: object + additionalProperties: true + description: | + DCM Unified Data Model fields for this resource. Field names match the + Resource Type Specification schema registered for this resource type. + All fields carry provenance metadata where applicable. + relationships: + type: array + items: + type: object + properties: + relation: { type: string } + target_entity_uuid: { type: string, format: uuid } + target_resource_type: { type: string } + callback_url: + type: string + format: uri + description: DCM Callback API URL for reporting async completion + override_controls: + type: object + additionalProperties: true + description: Field-level override constraints (Level 3+) + scheduled_at: + type: string + format: date-time + description: For scheduled/deferred requests — when to start execution + + CreateResponse: + type: object + required: [resource_id, dcm_request_id, lifecycle_state] + additionalProperties: false + properties: + resource_id: + type: string + description: Operator-assigned stable resource ID + dcm_request_id: + type: string + format: uuid + description: Echoed from request + lifecycle_state: + type: string + enum: [PROVISIONING, REALIZED, FAILED] + estimated_ready_at: + type: string + format: date-time + provider_reference: + type: object + description: Provider-native reference (e.g., Kubernetes namespace/name for CRs) + additionalProperties: true + + UpdateRequest: + type: object + required: [request_id, delta_fields] + additionalProperties: false + properties: + request_id: + type: string + format: uuid + dcm_entity_uuid: + type: string + format: uuid + delta_fields: + type: object + additionalProperties: true + description: Changed fields only — not a full replacement + callback_url: + type: string + format: uri + + DecommissionRequest: + type: object + additionalProperties: false + properties: + request_id: + type: string + format: uuid + dcm_entity_uuid: + type: string + format: uuid + reason: + type: string + retain_data: + type: boolean + default: false + description: If true, operator should retain underlying data (e.g., PVC retention) + callback_url: + type: string + format: uri + + RealizedStatePayload: + type: object + required: [resource_id, dcm_entity_uuid, lifecycle_state, realized_at, spec] + additionalProperties: false + description: | + The Realized State of a resource in DCM Unified Data Model format. + This is what the operator sends back to DCM after realization, and what + DCM stores in the Realized State Store. All provider-native identifiers + should be included in `provider_metadata` — the `spec` must be in DCM format. + properties: + resource_id: + type: string + dcm_entity_uuid: + type: string + format: uuid + dcm_request_id: + type: string + format: uuid + lifecycle_state: + type: string + enum: [PROVISIONING, REALIZED, OPERATIONAL, DEGRADED, FAILED, DECOMMISSIONING, DECOMMISSIONED] + realized_at: + type: string + format: date-time + spec: + type: object + additionalProperties: true + description: Realized field values in DCM Unified Data Model format + provider_metadata: + type: object + additionalProperties: true + description: Provider-native metadata (opaque to DCM; stored for operator use) + failure_reason: + type: string + description: Present when lifecycle_state is FAILED + + DenialResponse: + type: object + required: [request_id, denial_reason, denial_timestamp] + additionalProperties: false + properties: + request_id: + type: string + format: uuid + denial_reason: + type: string + enum: [INSUFFICIENT_RESOURCES, VALIDATION_FAILED, POLICY_REJECTED, UNSUPPORTED_CONFIGURATION] + denial_timestamp: + type: string + format: date-time + resource_type_uuid: + type: string + format: uuid + estimated_available_at: + type: string + format: date-time + details: + type: string + + DiscoverRequest: + type: object + additionalProperties: false + properties: + scope: + type: string + enum: [full, delta] + default: full + since: + type: string + format: date-time + description: For delta discovery — only return resources changed since this time + + DiscoverResponse: + type: object + required: [discovery_timestamp, resources] + additionalProperties: false + properties: + discovery_timestamp: + type: string + format: date-time + resources: + type: array + items: { $ref: "#/components/schemas/RealizedStatePayload" } + pagination: + type: object + properties: + page_token: { type: string } + has_more: { type: boolean } + + CapacityQueryRequest: + type: object + additionalProperties: false + properties: + resource_type_name: { type: string } + requested_spec: { type: object, additionalProperties: true } + tenant_uuid: { type: string, format: uuid } + + CapacityReport: + type: object + required: [provider_id, report_timestamp, capacity] + additionalProperties: false + properties: + provider_id: + type: string + format: uuid + report_timestamp: + type: string + format: date-time + next_report_at: + type: string + format: date-time + capacity: + type: object + required: [available_units, reserved_units, committed_units] + additionalProperties: false + properties: + available_units: { type: integer, minimum: 0 } + reserved_units: { type: integer, minimum: 0 } + committed_units: { type: integer, minimum: 0 } + unit_definition: { type: string, description: "What one 'unit' means (e.g., '1 vCPU + 2GB RAM')" } + confidence: { type: string, enum: [high, medium, low], default: high } + can_fulfill: + type: boolean + description: Whether the requested_spec (if provided) can be fulfilled + + OperatorError: + type: object + required: [error] + additionalProperties: false + properties: + error: + type: object + required: [code, message] + properties: + code: { type: string } + message: { type: string } + + Operation: + type: object + description: | + AEP-136 Long-Running Operation returned by async create/update/decommission responses. + DCM polls this until done is true. + required: [name, done] + additionalProperties: false + properties: + name: + type: string + description: Stable operation resource path + done: + type: boolean + default: false + metadata: + type: object + properties: + resource_id: { type: string } + resource_type: { type: string } + operation_type: { type: string, enum: [create, update, decommission] } + response: + type: object + description: Present when done=true and successful. Contains RealizedStatePayload. + error: + $ref: "#/components/schemas/OperatorError" + description: Present when done=true and failed. + + ResourceList: + type: object + description: List of discovered resources returned by the discover endpoint. + required: [resources] + additionalProperties: false + properties: + resources: + type: array + items: + $ref: "#/components/schemas/RealizedStatePayload" + total_count: + type: integer + description: Total number of resources discovered diff --git a/schemas/openapi/dcm-provider-callback-api.yaml b/schemas/openapi/dcm-provider-callback-api.yaml new file mode 100644 index 0000000..bf855ea --- /dev/null +++ b/schemas/openapi/dcm-provider-callback-api.yaml @@ -0,0 +1,868 @@ +openapi: "3.1.0" + +info: + title: DCM Provider Callback API + version: "1.0.0" + description: | + The DCM Provider Callback API defines the endpoints that the DCM control plane exposes + for Service Provider operators to call. Operators use these endpoints to: + + - Register with DCM and declare their capabilities + - Report capacity for placement engine decisions + - Push realized state after resource creation or update (the Denaturalization step) + - Report interim progress on long-running operations + - Notify DCM of authorized provider-side state changes + - Report lifecycle events (health changes, unsanctioned changes, maintenance windows) + + **Authentication — Two-Layer Model** (see `43-provider-callback-auth.md` for full specification): + + - **Layer 1 — mTLS:** Every connection requires the provider's registered certificate. + DCM validates the certificate chain against the CA registered at provider activation. + Proves transport-level identity. + + - **Layer 2 — Provider Callback Credential:** Every call requires an `Authorization: Bearer` + header containing the provider's active callback credential. This credential is a + `dcm_interaction` type credential issued by DCM's credential management system at activation time. + It is scoped to the specific `provider_uuid` and a set of allowed operations. It is + short-lived (profile-governed: PT15M for fsi/sovereign, PT1H for standard) and must be + rotated before expiry. DCM initiates rotation automatically. + + - **Entity-level authorization** is checked per call independent of credential validity: + DCM verifies that the calling provider is the provider that was dispatched to for the + specific entity. A valid credential does not grant access to entities hosted at other + providers or entities the provider was not dispatched to. + + - **Registration calls** use a registration token (single-use, admin-issued) rather than + the callback credential — no callback credential exists until activation completes. + + **Idempotency:** All mutating operations are idempotent. Use the same `request_id` or + `notification_uuid` to safely retry without side effects. + + **Direction:** This is the reverse of the Operator Interface Services API. The Operator + Interface defines endpoints operators implement (DCM calls them). This document defines + endpoints DCM implements (operators call them). + + **Conformance level requirements** are noted per endpoint. Level 1 operators only need + registration and realized state push. Level 2+ add capacity, events, and update notifications. + + + + **AEP Alignment:** This API follows [AEP](https://aep.dev) conventions: + custom methods use colon syntax (`POST /resources/{name}:suspend`), + async operations return an `Operation` resource (AEP-136 LRO), + and list pagination uses `page_size`/`page_token` parameters. + + contact: + name: DCM Project + url: https://github.com/dcm-project + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0 + +servers: + - url: https://{dcm-host}/ + description: DCM Control Plane + variables: + dcm-host: + description: Hostname of the DCM control plane + default: dcm.example.com + +security: + - ProviderCredential: [] + +tags: + - name: registration + description: Provider registration and capacity reporting (Level 1+) + - name: realized-state + description: Realized state push — the Denaturalization step (Level 1+) + - name: interim-status + description: Interim progress updates for long-running operations (Level 1+, optional) + - name: update-notifications + description: Provider-initiated authorized state change notifications (Level 2+) + - name: lifecycle-events + description: Resource lifecycle and health event reporting (Level 2+) + +paths: + + # ─── REGISTRATION ───────────────────────────────────────────────────────── + + /api/v1/providers: + post: + tags: [registration] + operationId: registerProvider + summary: Register or update a provider registration with DCM (Level 1+) + description: | + Called by the operator on startup to register with DCM. Registration is idempotent — + re-registering with the same `name` updates the existing registration rather than + creating a duplicate. Safe to call on every operator restart. + + On first registration, DCM creates a new provider record in SUBMITTED status and + routes it through the approval pipeline. On re-registration with the same name, + DCM updates the version, capabilities, and endpoints without re-triggering approval + (unless the sovereignty declaration changes). + + **Timing:** Call after the operator's HTTP server is ready to receive requests. + Retry with exponential backoff on failure. Registration failure does not block + operator startup — the operator functions normally for Kubernetes consumers. + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/ProviderRegistrationRequest" } + responses: + "200": + description: Registration updated (existing provider) + content: + application/json: + schema: { $ref: "#/components/schemas/ProviderRegistrationResponse" } + "201": + description: New registration created; pending approval + content: + application/json: + schema: { $ref: "#/components/schemas/ProviderRegistrationResponse" } + "400": { $ref: "#/components/responses/BadRequest" } + "409": + description: Registration conflict — name already registered with different sovereignty declaration + content: + application/json: + schema: { $ref: "#/components/schemas/ProviderError" } + + /api/v1/providers/{provider_id}/capacity: + post: + tags: [registration] + operationId: reportCapacity + summary: Push capacity report to DCM for placement engine decisions (Level 2+) + description: | + Operators push capacity reports on a scheduled interval (declared at registration). + DCM uses this data to make placement decisions and to avoid over-committing resources. + + Reports are also accepted on-demand when capacity changes significantly (e.g., after + a node is added or removed). Providers should not push more than once per minute + unless responding to a significant capacity event. + parameters: + - { $ref: "#/components/parameters/provider_id" } + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/CapacityReport" } + responses: + "202": + description: Capacity report accepted + "400": { $ref: "#/components/responses/BadRequest" } + "403": { $ref: "#/components/responses/Forbidden" } + "429": + description: Rate limit exceeded — capacity reports accepted max once per 60 seconds + content: + application/json: + schema: { $ref: "#/components/schemas/ProviderError" } + + # ─── REALIZED STATE ─────────────────────────────────────────────────────── + + /api/v1/instances/{resource_id}/status: + put: + tags: [realized-state] + operationId: pushRealizedState + summary: Push realized or terminal state to DCM — the Denaturalization step (Level 1+) + description: | + Called by the operator's reconciliation loop when a resource reaches a terminal + lifecycle state (OPERATIONAL, FAILED, DEGRADED, DECOMMISSIONED) or when the + operator needs to report a provider-native state transition. + + This is the **Denaturalization** step: the operator translates Kubernetes-native + CR status into DCM Unified Data Model format and pushes it here. DCM stores the + result as a Realized State record in the append-only Realized State Store. + + **Idempotency:** Use the same `dcm_request_id` to safely retry. DCM will not + create duplicate Realized State records for the same `dcm_request_id` + terminal state. + + **Required for decommission:** When a resource is fully removed, the operator must + push a payload with `lifecycle_state: DECOMMISSIONED`. This closes the entity's + lifecycle in DCM and releases any held allocations or relationships. + + **Level 3 — provenance required:** Level 3 conformance requires `field_provenance` + to be populated for all realized fields. This enables full audit chain. + parameters: + - { $ref: "#/components/parameters/resource_id" } + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/RealizedStatePush" } + responses: + "200": + description: Realized state accepted and stored + content: + application/json: + schema: { $ref: "#/components/schemas/RealizedStateAck" } + "400": { $ref: "#/components/responses/BadRequest" } + "403": { $ref: "#/components/responses/Forbidden" } + "404": + description: resource_id not recognized by DCM — provider may need to re-register + content: + application/json: + schema: { $ref: "#/components/schemas/ProviderError" } + "409": + description: | + Idempotent response — this request_id + terminal state already recorded. + The response body contains the existing Realized State record UUID. + content: + application/json: + schema: { $ref: "#/components/schemas/RealizedStateAck" } + + # ─── INTERIM STATUS ─────────────────────────────────────────────────────── + + /api/v1/provider/entities/{entity_uuid}/status: + post: + tags: [interim-status] + operationId: pushInterimStatus + summary: Push interim progress update for a long-running operation (Level 1+, optional) + description: | + Operators may push interim status updates for long-running operations (provisioning + complex resources, composite service constituents) to give DCM — and therefore + consumers — live visibility into multi-step operations without waiting for terminal state. + + DCM uses interim status to: + - Update `current_step` and progress fields in the request status response + - Publish `request.progress_updated` event (info urgency) to the Message Bus + - Deliver live updates to consumers via SSE stream + + **Rate limit:** Maximum one update per 10 seconds per `entity_uuid`. DCM rate-limits + interim status calls and will return 429 if the limit is exceeded. + + **Not a replacement for terminal state:** Interim status supplements the realized + state push. Operators must still call `PUT /api/v1/instances/{resource_id}/status` + to report terminal state. + + **For Composite Service requests:** Include `constituent_status` to give + visibility into each constituent's progress independently. + parameters: + - { $ref: "#/components/parameters/entity_uuid" } + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/InterimStatusPush" } + responses: + "202": + description: Interim status accepted; consumers and SSE stream updated + "400": { $ref: "#/components/responses/BadRequest" } + "403": { $ref: "#/components/responses/Forbidden" } + "429": + description: Rate limit exceeded — max one interim status per 10 seconds per entity + headers: + Retry-After: + schema: { type: integer } + description: Seconds until the rate limit resets + content: + application/json: + schema: { $ref: "#/components/schemas/ProviderError" } + + # ─── UPDATE NOTIFICATIONS ───────────────────────────────────────────────── + + /api/v1/provider/entities/{entity_uuid}/update-notification: + post: + tags: [update-notifications] + operationId: submitUpdateNotification + summary: Notify DCM of an authorized provider-initiated state change (Level 2+) + description: | + Called when the provider has made an authorized state change to a resource + outside of a DCM-initiated request (e.g., auto-scaling, auto-healing, maintenance). + + **Key principle:** Providers never write directly to DCM's Realized State. They + submit a notification here; DCM processes it through the governance pipeline + (Policy Engine evaluation); DCM writes the Realized State only if approved. + + **Distinct from drift:** A provider submitting an update notification is asserting + that the change was authorized by a pre-existing policy or operational agreement. + Drift is detected through discovery and represents an unauthorized or untracked change. + + **Pre-authorization declarations:** Providers may declare categories of updates + they routinely make at registration time, enabling organizations to pre-authorize + them in policy rather than reviewing each one. Pre-authorized notification types + receive `AUTO_APPROVED` status immediately. + + **Idempotency:** Use the same `notification_uuid` to safely retry. DCM will not + create duplicate Realized State records for the same `notification_uuid`. + parameters: + - { $ref: "#/components/parameters/entity_uuid" } + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/UpdateNotificationRequest" } + responses: + "202": + description: Notification accepted; processing begun + content: + application/json: + schema: { $ref: "#/components/schemas/UpdateNotificationAccepted" } + "400": { $ref: "#/components/responses/BadRequest" } + "403": { $ref: "#/components/responses/Forbidden" } + "409": + description: Idempotent — notification_uuid already processed + content: + application/json: + schema: { $ref: "#/components/schemas/UpdateNotificationAccepted" } + + /api/v1/provider/notifications/{notification_uuid}: + get: + tags: [update-notifications] + operationId: getNotificationStatus + summary: Poll the processing status of a submitted update notification (Level 2+) + description: | + Allows providers to check whether a submitted update notification has been + approved, rejected, or is still awaiting consumer approval. + + Providers should poll with backoff rather than polling at high frequency. + Alternatively, subscribe to the `provider_update.*` event domain via webhook + for push notification of decision outcomes. + parameters: + - { $ref: "#/components/parameters/notification_uuid" } + responses: + "200": + content: + application/json: + schema: { $ref: "#/components/schemas/UpdateNotificationStatus" } + "404": { $ref: "#/components/responses/NotFound" } + + # ─── LIFECYCLE EVENTS ───────────────────────────────────────────────────── + + /api/v1/instances/{resource_id}/events: + post: + tags: [lifecycle-events] + operationId: reportLifecycleEvent + summary: Report a resource lifecycle or health event to DCM (Level 2+) + description: | + Called by operators to notify DCM of events affecting the operational status of + a managed resource. DCM receives the event, evaluates it through the Policy Engine, + and determines the appropriate response (notify consumer, trigger recovery, escalate). + + DCM acts as the Tenant advocate — it does not expose raw infrastructure events to + consumers directly. It applies policy, filters urgency, and routes notifications + to the appropriate audience based on the governance model. + + **Idempotency:** Use the same `event_uuid` to safely retry. DCM will not create + duplicate records for the same `event_uuid`. + + **UNSANCTIONED_CHANGE:** When the provider detects a CR was modified without a + corresponding DCM request ID (i.e., someone modified Kubernetes resources directly), + report this as `UNSANCTIONED_CHANGE`. DCM will flag this as drift, notify the Tenant, + and fire the appropriate Recovery Policy. + parameters: + - { $ref: "#/components/parameters/resource_id" } + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/LifecycleEventReport" } + responses: + "202": + description: Event accepted; DCM policy evaluation initiated + "400": { $ref: "#/components/responses/BadRequest" } + "403": { $ref: "#/components/responses/Forbidden" } + "409": + description: Idempotent — event_uuid already recorded + +# ─── COMPONENTS ──────────────────────────────────────────────────────────────── + +components: + + securitySchemes: + ProviderCredential: + type: http + scheme: bearer + description: | + Short-lived provider interaction credential issued by DCM at registration time. + Scoped to the specific provider UUID. Must be rotated before expiry. Rotation + is managed via the credential management system integration declared at registration. + + parameters: + + provider_id: + name: provider_id + in: path + required: true + schema: { type: string, format: uuid } + description: DCM-assigned provider UUID + + resource_id: + name: resource_id + in: path + required: true + schema: { type: string } + description: Operator-assigned resource ID (as returned in CreateResponse) + + entity_uuid: + name: entity_uuid + in: path + required: true + schema: { type: string, format: uuid } + description: DCM entity UUID (provided by DCM in the CreateRequest) + + notification_uuid: + name: notification_uuid + in: path + required: true + schema: { type: string, format: uuid } + description: Provider-assigned notification UUID (from UpdateNotificationRequest) + + responses: + + BadRequest: + description: Malformed request payload + content: + application/json: + schema: { $ref: "#/components/schemas/ProviderError" } + + Forbidden: + description: Invalid or expired provider credential, or credential scoped to different provider + content: + application/json: + schema: { $ref: "#/components/schemas/ProviderError" } + + NotFound: + description: Resource or notification not found + content: + application/json: + schema: { $ref: "#/components/schemas/ProviderError" } + + schemas: + + ProviderError: + type: object + required: [error] + additionalProperties: false + properties: + error: + type: object + required: [code, message, request_id] + additionalProperties: false + properties: + code: { type: string } + message: { type: string } + request_id: { type: string, format: uuid } + + ProviderRegistrationRequest: + type: object + required: [name, display_name, conformance_level, endpoint, version, service_types] + additionalProperties: false + properties: + name: + type: string + description: Unique provider name — natural key for idempotent re-registration + pattern: "^[a-z0-9][a-z0-9-_/]*[a-z0-9]$" + display_name: { type: string, maxLength: 256 } + conformance_level: + type: integer + minimum: 1 + maximum: 4 + endpoint: + type: string + format: uri + description: Base URL of the operator's DCM Services API + version: + type: string + pattern: "^\\d+\\.\\d+\\.\\d+$" + service_types: + type: array + minItems: 1 + items: + type: object + required: [service_type, service_type_uuid, operations_supported] + additionalProperties: false + properties: + service_type: + type: string + description: DCM Resource Type FQN (e.g., Storage.Database) + pattern: "^[A-Z][a-zA-Z0-9]+\\.[A-Z][a-zA-Z0-9]+$" + service_type_uuid: + type: string + format: uuid + crd_reference: + type: object + description: Kubernetes CRD reference (Kubernetes operators only) + additionalProperties: false + properties: + group: { type: string } + version: { type: string } + kind: { type: string } + operations_supported: + type: array + items: + type: string + enum: [CREATE, READ, UPDATE, DELETE, DISCOVER] + field_mapping_ref: + type: string + format: uri + description: URL or reference to the field mapping declaration (Level 2+) + kubernetes: + type: object + description: Kubernetes-specific registration metadata (Kubernetes operators only) + additionalProperties: false + properties: + cluster_id: { type: string } + cluster_endpoint: { type: string, format: uri } + namespace_strategy: + type: string + enum: [per_tenant, shared, per_resource] + sovereignty_declaration: + type: object + description: Where this provider operates and which jurisdictions it is subject to + additionalProperties: false + properties: + operating_jurisdictions: + type: array + items: { type: string, pattern: "^[A-Z]{2}$" } + data_residency_zones: + type: array + items: { type: string } + regulatory_frameworks: + type: array + items: { type: string } + update_capabilities: + type: array + description: Pre-authorization declarations for provider-initiated updates (Level 2+) + items: + type: object + required: [notification_type, affected_fields] + additionalProperties: false + properties: + notification_type: + type: string + enum: [authorized_change, maintenance_change, auto_scale, auto_heal] + affected_fields: + type: array + items: { type: string } + max_change_magnitude: { type: string } + typical_trigger: { type: string } + cancellation_capabilities: + type: object + description: Request cancellation support declaration (Level 2+) + additionalProperties: false + properties: + supports_cancellation: { type: boolean } + cancellation_supported_during: + type: array + items: { type: string, enum: [DISPATCHED, PROVISIONING] } + partial_rollback_possible: { type: boolean } + cancellation_response_time_seconds: { type: integer } + + ProviderRegistrationResponse: + type: object + required: [provider_id, name, status, conformance_level_accepted] + additionalProperties: false + properties: + provider_id: + type: string + format: uuid + description: DCM-assigned provider UUID — stable across re-registrations + name: { type: string } + status: + type: string + enum: [registered, updated, pending_approval] + conformance_level_accepted: { type: integer } + capabilities_enabled: + type: array + items: { type: string } + credential_ref: + type: string + description: Reference to the provider interaction credential (retrieve via credential management system) + credential_expires_at: + type: string + format: date-time + + CapacityReport: + type: object + required: [provider_id, report_timestamp, capacity_by_service_type] + additionalProperties: false + properties: + provider_id: + type: string + format: uuid + report_timestamp: + type: string + format: date-time + next_report_at: + type: string + format: date-time + description: When the provider will send the next scheduled report + capacity_by_service_type: + type: array + minItems: 1 + items: + type: object + required: [service_type_uuid, available_units, reserved_units, committed_units] + additionalProperties: false + properties: + service_type_uuid: { type: string, format: uuid } + available_units: { type: integer, minimum: 0 } + reserved_units: { type: integer, minimum: 0 } + committed_units: { type: integer, minimum: 0 } + unit_definition: + type: string + description: What one unit represents (e.g., "1 database cluster", "1 vCPU") + kubernetes_resources: + type: object + description: Raw Kubernetes resource availability (Kubernetes operators only) + additionalProperties: false + properties: + available_cpu_millicores: { type: integer } + available_memory_bytes: { type: integer } + available_storage_bytes: { type: integer } + node_count: { type: integer } + + RealizedStatePush: + type: object + required: [resource_id, dcm_entity_uuid, lifecycle_state, realized_at, spec] + additionalProperties: false + description: | + The Denaturalization payload — provider-native state translated into DCM Unified + Data Model format. DCM stores this in the append-only Realized State Store. + properties: + resource_id: + type: string + description: Operator-assigned resource ID + dcm_entity_uuid: + type: string + format: uuid + description: DCM entity UUID (provided by DCM in the CreateRequest) + dcm_request_id: + type: string + format: uuid + description: DCM request UUID (idempotency key — provide for all non-spontaneous state changes) + lifecycle_state: + type: string + enum: [PROVISIONING, REALIZED, OPERATIONAL, DEGRADED, FAILED, DECOMMISSIONING, DECOMMISSIONED] + realized_at: + type: string + format: date-time + spec: + type: object + additionalProperties: true + description: All realized field values in DCM Unified Data Model format + field_provenance: + type: object + additionalProperties: + type: object + properties: + source_type: { type: string, enum: [provider, operator, kubernetes_status] } + source_uuid: { type: string, format: uuid } + timestamp: { type: string, format: date-time } + description: Per-field provenance. Required for Level 3 conformance. + provider_metadata: + type: object + additionalProperties: true + description: Provider-native metadata stored by DCM but treated as opaque + kubernetes_reference: + type: object + description: Kubernetes CR reference (Kubernetes operators only) + additionalProperties: false + properties: + namespace: { type: string } + name: { type: string } + uid: { type: string } + resource_version: { type: string } + relationships: + type: array + description: Any relationships created or discovered during realization + items: + type: object + properties: + relation: { type: string } + target_entity_uuid: { type: string, format: uuid } + failure_reason: + type: string + description: Required when lifecycle_state is FAILED + + RealizedStateAck: + type: object + required: [realized_state_uuid, entity_uuid, lifecycle_state] + additionalProperties: false + properties: + realized_state_uuid: { type: string, format: uuid } + entity_uuid: { type: string, format: uuid } + lifecycle_state: { type: string } + recorded_at: { type: string, format: date-time } + + InterimStatusPush: + type: object + required: [request_id, lifecycle_state] + additionalProperties: false + properties: + request_id: + type: string + format: uuid + description: DCM request UUID this status update corresponds to + lifecycle_state: + type: string + enum: [PROVISIONING, UPDATING, DECOMMISSIONING] + description: Current non-terminal state + progress: + type: object + additionalProperties: false + properties: + step_current: { type: integer, minimum: 1 } + step_total: { type: integer, minimum: 1 } + step_label: { type: string } + step_started_at: { type: string, format: date-time } + estimated_completion: { type: string, format: date-time } + constituent_status: + type: array + description: Per-constituent status for Composite Service requests + items: + type: object + required: [ref, status] + additionalProperties: false + properties: + ref: { type: string, description: "Constituent identifier (matches the Composite Service composition declaration's component_id)" } + status: { type: string, enum: [PENDING, PROVISIONING, REALIZED, FAILED] } + completed_at: { type: string, format: date-time } + started_at: { type: string, format: date-time } + notes: + type: string + maxLength: 512 + description: Optional human-readable detail for consumer display + + UpdateNotificationRequest: + type: object + required: [provider_uuid, notification_uuid, notification_type, changed_fields, effective_at] + additionalProperties: false + properties: + provider_uuid: + type: string + format: uuid + notification_uuid: + type: string + format: uuid + description: Provider-assigned idempotency key — must be unique per change event + notification_type: + type: string + enum: [authorized_change, maintenance_change, auto_scale, auto_heal] + description: | + authorized_change: explicitly authorized by policy. + maintenance_change: result of a maintenance window. + auto_scale: automatic scaling action. + auto_heal: automatic healing/recovery action. + changed_fields: + type: object + additionalProperties: + type: object + required: [previous_value, new_value] + additionalProperties: false + properties: + previous_value: { description: "Value before the change" } + new_value: { description: "Value after the change" } + change_reason: { type: string } + authorizing_policy_ref: + oneOf: [{ type: string, format: uuid }, { type: "null" }] + description: UUID of the DCM policy that pre-authorizes this change type + effective_at: + type: string + format: date-time + description: When the change took effect on the provider side + provider_evidence_ref: + type: string + description: Provider-side reference (e.g., Kubernetes event UID, cloud audit log ID) + + UpdateNotificationAccepted: + type: object + required: [notification_uuid, status, entity_uuid] + additionalProperties: false + properties: + notification_uuid: { type: string, format: uuid } + entity_uuid: { type: string, format: uuid } + status: + type: string + enum: [processing, auto_approved, pending_consumer_approval, pending_admin_approval] + realized_state_uuid: + oneOf: [{ type: string, format: uuid }, { type: "null" }] + description: Set immediately when status is auto_approved + notification_status_url: + type: string + format: uri + description: URL to poll for status updates + + UpdateNotificationStatus: + type: object + required: [notification_uuid, status, entity_uuid] + additionalProperties: false + properties: + notification_uuid: + type: string + format: uuid + status: + type: string + enum: [processing, approved, auto_approved, pending_approval, rejected] + entity_uuid: { type: string, format: uuid } + realized_state_uuid: + oneOf: [{ type: string, format: uuid }, { type: "null" }] + consumer_approval_required: { type: boolean } + consumer_notified_at: + oneOf: [{ type: string, format: date-time }, { type: "null" }] + resolved_at: + oneOf: [{ type: string, format: date-time }, { type: "null" }] + rejection_reason: + oneOf: [{ type: string }, { type: "null" }] + + LifecycleEventReport: + type: object + required: [event_uuid, event_type, provider_id, resource_id, dcm_entity_uuid, event_timestamp, severity] + additionalProperties: false + properties: + event_uuid: + type: string + format: uuid + description: Provider-assigned idempotency key + event_type: + type: string + enum: + - ENTITY_HEALTH_CHANGE + - DEGRADATION + - MAINTENANCE_SCHEDULED + - MAINTENANCE_STARTED + - MAINTENANCE_COMPLETED + - UNSANCTIONED_CHANGE + - CAPACITY_CHANGE + - DECOMMISSION_NOTICE + - PROVIDER_DEGRADATION + provider_id: + type: string + format: uuid + resource_id: + type: string + dcm_entity_uuid: + type: string + format: uuid + event_timestamp: + type: string + format: date-time + severity: + type: string + enum: [INFO, WARNING, CRITICAL] + requires_immediate_action: { type: boolean, default: false } + details: + type: object + additionalProperties: true + description: | + Event-type-specific detail. Common patterns: + + For UNSANCTIONED_CHANGE: + changed_fields: [{field_path, previous_value, new_value}] + change_source: "direct_kubernetes_edit" | "external_tool" + + For DEGRADATION: + degraded_components: [{component, status, detail}] + impact_on_consumers: "none" | "reduced_performance" | "partial_outage" + + For MAINTENANCE_SCHEDULED: + maintenance_window: {start_at, end_at, window_type} + expected_impact: "none" | "brief_unavailability" | "full_unavailability" + + For CAPACITY_CHANGE: + previous_available_units: integer + new_available_units: integer + change_reason: string + related_request_id: + oneOf: [{ type: string, format: uuid }, { type: "null" }] + description: DCM request UUID if this event is related to an in-progress operation diff --git a/schemas/sql/001-initial.sql b/schemas/sql/001-initial.sql new file mode 100644 index 0000000..43a20f6 --- /dev/null +++ b/schemas/sql/001-initial.sql @@ -0,0 +1,591 @@ +-- DCM PostgreSQL Schema — Initial +-- Spec ref: DCM data model doc 49, doc 51 (Infrastructure Optimization) +-- Implements: STI-001 (mandatory tenant_uuid predicate), STI-002 (RLS) +-- +-- This schema implements ALL FOUR DCM data domains in a single database: +-- Intent Domain — append-only consumer declarations +-- Requested Domain — append-only assembled/validated payloads +-- Realized Domain — versioned provider-confirmed state +-- Discovered Domain — ephemeral discovery snapshots +-- Plus: audit records (hash chain), operations (LRO), pipeline events, subscriptions + +\connect dcm + +-- ─── Extensions ────────────────────────────────────────────────────────────── + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + +-- ─── Tenants ────────────────────────────────────────────────────────────────── + +CREATE TABLE tenants ( + tenant_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + handle VARCHAR(64) UNIQUE NOT NULL, + display_name VARCHAR(256) NOT NULL, + status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' + CHECK (status IN ('ACTIVE', 'SUSPENDED', 'DECOMMISSIONED')), + profile VARCHAR(32) NOT NULL DEFAULT 'dev' + CHECK (profile IN ('minimal', 'dev', 'standard', 'prod', 'fsi', 'sovereign')), + data_classifications_permitted JSONB NOT NULL DEFAULT '["internal"]', + sovereignty_zones JSONB NOT NULL DEFAULT '[]', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- ─── Actors (users and service accounts) ────────────────────────────────────── + +CREATE TABLE actors ( + actor_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tenant_uuid UUID NOT NULL REFERENCES tenants(tenant_uuid), + actor_type VARCHAR(32) NOT NULL + CHECK (actor_type IN ('human', 'service_account', 'system_component', 'provider')), + handle VARCHAR(256) NOT NULL, + display_name VARCHAR(256) NOT NULL, + auth_method VARCHAR(32) NOT NULL DEFAULT 'internal' + CHECK (auth_method IN ('internal', 'external')), + password_hash VARCHAR(256), -- argon2id hash (internal auth only) + totp_secret_ref VARCHAR(256), -- reference to TOTP secret in secrets table (optional MFA) + external_id VARCHAR(512), -- IdP subject claim (external auth only) + auth_provider_uuid UUID, -- which auth_provider (external auth only) + roles JSONB NOT NULL DEFAULT '[]', -- direct role assignments (internal); merged with IdP claims (external) + status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + last_login_at TIMESTAMPTZ, + UNIQUE(tenant_uuid, handle) +); + +-- ─── Entities (Realized State) ──────────────────────────────────────────────── +-- Spec ref: DCM data model doc 01 (Entity Types), doc 02 (Four States) +-- Realized domain — one row per realized entity per version. + +CREATE TABLE realized_entities ( + realized_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + entity_uuid UUID NOT NULL, -- Stable identifier across versions + tenant_uuid UUID NOT NULL REFERENCES tenants(tenant_uuid), + resource_type VARCHAR(256) NOT NULL, -- FQN e.g. Compute.VirtualMachine + resource_type_uuid UUID, + entity_type VARCHAR(64) NOT NULL + CHECK (entity_type IN ('infrastructure_resource', 'composite_resource', + 'process_resource', 'shared_resource', 'allocatable_pool')), + lifecycle_state VARCHAR(32) NOT NULL + CHECK (lifecycle_state IN ('PROVISIONING', 'OPERATIONAL', 'DEGRADED', + 'SUSPENDED', 'FAILED', 'DECOMMISSIONED', + 'INGESTED', 'INGESTION_PENDING')), + request_uuid UUID, -- The request that created/updated this + provider_uuid UUID, -- Which provider owns this entity + realized_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + realized_by UUID REFERENCES actors(actor_uuid), + fields JSONB NOT NULL DEFAULT '{}', -- Full realized field set + provenance JSONB NOT NULL DEFAULT '{}', -- Field-level provenance map + provider_metadata JSONB NOT NULL DEFAULT '{}', -- Provider-supplied metadata + sovereignty_zones JSONB NOT NULL DEFAULT '[]', + tags JSONB NOT NULL DEFAULT '{}', + version_major INTEGER NOT NULL DEFAULT 1, + version_minor INTEGER NOT NULL DEFAULT 0, + version_revision INTEGER NOT NULL DEFAULT 0, + is_current BOOLEAN NOT NULL DEFAULT TRUE -- Only one current per entity_uuid +); + +CREATE INDEX idx_realized_tenant ON realized_entities(tenant_uuid); +CREATE INDEX idx_realized_entity ON realized_entities(entity_uuid); +CREATE INDEX idx_realized_lifecycle ON realized_entities(tenant_uuid, lifecycle_state); +CREATE INDEX idx_realized_resource_type ON realized_entities(tenant_uuid, resource_type); +CREATE INDEX idx_realized_current ON realized_entities(entity_uuid, is_current) WHERE is_current = TRUE; + +-- ─── Operations (LRO tracking) ──────────────────────────────────────────────── +-- Spec ref: doc 25 §2 (Request Orchestrator), doc 49 §7.1 (operation_uuid issuer) +-- operation_uuid == request_uuid, issued by API Gateway at ingress. + +CREATE TABLE operations ( + operation_uuid UUID PRIMARY KEY, -- == request_uuid, issued by API Gateway + tenant_uuid UUID NOT NULL REFERENCES tenants(tenant_uuid), + resource_uuid UUID, -- AEP convention: the resource this operation acts on + operation_type VARCHAR(64) NOT NULL -- Lifecycle operation vocabulary (doc B §2.2) + CHECK (operation_type IN ('initial_provisioning', 'update', 'scale', + 'rehydration', 'decommission', 'ownership_transfer', + 'subscription_renewal', 'drift_remediation', + 'provider_migration', 'compliance_rescan')), + changed_fields JSONB, -- Array of field paths that changed (update/scale ops) + status VARCHAR(32) NOT NULL DEFAULT 'INITIATED' + CHECK (status IN ('INITIATED', 'ASSEMBLING', 'POLICY_EVALUATION', + 'POLICY_BLOCKED', 'PENDING_OVERRIDE', + 'PLACEMENT', 'DISPATCHED', 'PROVISIONING', + 'OPERATIONAL', 'FAILED', 'CANCELLED', + 'OVERRIDE_DENIED', 'OVERRIDE_TIMEOUT')), + actor_uuid UUID REFERENCES actors(actor_uuid), + catalog_item_uuid UUID, + resource_type VARCHAR(256), + metadata JSONB NOT NULL DEFAULT '{}', + score NUMERIC(5,2), + selected_provider_uuid UUID, + error_code VARCHAR(128), + error_message TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + completed_at TIMESTAMPTZ +); + +CREATE INDEX idx_operations_tenant ON operations(tenant_uuid, status); + +-- ─── Override Requests ─────────────────────────────────────────────────────── +-- Spec ref: doc B §18.8 (Override Approval Flow) + +CREATE TABLE override_requests ( + override_request_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + request_uuid UUID NOT NULL REFERENCES operations(operation_uuid), + tenant_uuid UUID NOT NULL REFERENCES tenants(tenant_uuid), + blocking_policy_handle VARCHAR(256) NOT NULL, + blocking_policy_enforcement VARCHAR(16) NOT NULL CHECK (blocking_policy_enforcement IN ('hard', 'soft')), + blocking_reason TEXT NOT NULL, + resolution_guidance JSONB NOT NULL DEFAULT '{}', -- compliant_values, suggestions per field + required_approval_type VARCHAR(16) NOT NULL CHECK (required_approval_type IN ('single', 'dual')), + eligible_approver_roles JSONB NOT NULL DEFAULT '[]', + -- Consumer-initiated override request + consumer_justification TEXT, + consumer_compensating_controls JSONB, + resolution_action VARCHAR(32) CHECK (resolution_action IN ('modify', 'request_override', 'cancel', 'escalate')), + status VARCHAR(16) NOT NULL DEFAULT 'blocked' + CHECK (status IN ('blocked', 'pending', 'approved', 'rejected', 'expired', 'resolved', 'cancelled')), + timeout_at TIMESTAMPTZ NOT NULL, + -- First approval + first_approver_uuid UUID REFERENCES actors(actor_uuid), + first_approver_role VARCHAR(64), + first_justification TEXT, + first_compensating_controls JSONB, + first_approved_at TIMESTAMPTZ, + -- Second approval (dual-approval only) + second_approver_uuid UUID REFERENCES actors(actor_uuid), + second_approver_role VARCHAR(64), + second_approved_at TIMESTAMPTZ, + -- Metadata + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + resolved_at TIMESTAMPTZ +); + +CREATE INDEX idx_override_status ON override_requests(status, timeout_at); +CREATE INDEX idx_override_request ON override_requests(request_uuid); + +CREATE INDEX idx_operations_tenant ON operations(tenant_uuid); +CREATE INDEX idx_operations_status ON operations(tenant_uuid, status); +CREATE INDEX idx_operations_resource ON operations(resource_uuid); + +-- ─── Audit Records ──────────────────────────────────────────────────────────── +-- Spec ref: DCM data model doc 16 (Universal Audit), doc 49 §3 (Hash Chain) +-- Implements: Tamper-evident hash chain with SHA-256 + +CREATE TABLE audit_records ( + record_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + record_timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), + entity_uuid UUID, -- Subject entity (if applicable) + entity_type VARCHAR(64), + tenant_uuid UUID REFERENCES tenants(tenant_uuid), + action VARCHAR(128) NOT NULL, -- Closed vocabulary + -- WHO + immediate_actor_uuid UUID, + immediate_actor_type VARCHAR(32), + authorized_by_uuid UUID, + session_uuid UUID, + signer_uuid UUID NOT NULL, -- Service or actor that signed this leaf + signer_type VARCHAR(16) NOT NULL CHECK (signer_type IN ('service', 'actor', 'provider')), + -- WHAT + subject_handle VARCHAR(512), + stage VARCHAR(64), -- Pipeline stage (intent_submitted, layer_applied, etc.) + source VARCHAR(256), -- Which layer/policy/service + source_type VARCHAR(64), -- layer_merge, policy_gatekeeper, policy_transformation, etc. + decision VARCHAR(32), -- allow, deny, applied, resolved, etc. + -- CHAIN OF CUSTODY — payload integrity + input_payload_hash VARCHAR(64), -- SHA-256 of payload BEFORE this mutation + output_payload_hash VARCHAR(64), -- SHA-256 of payload AFTER this mutation + context_hash VARCHAR(64), -- Evaluation context hash (policy stages only) + -- FIELD-LEVEL DETAIL (mutation/field granularity only) + fields_changed JSONB, -- Array of field paths changed (mutation+) + field_mutations JSONB, -- Per-field old/new hashes (field granularity only) + -- MERKLE TREE (RFC 9162 pattern) + leaf_index BIGINT NOT NULL, -- Position in global Merkle tree + record_hash VARCHAR(64) NOT NULL, -- SHA-256 of record content + previous_leaf_hash VARCHAR(64), -- Hash of previous leaf for this request + signature TEXT NOT NULL, -- Ed25519/ECDSA-P256 over all fields + -- METADATA + dcm_version VARCHAR(32), + request_uuid UUID, + policy_uuid UUID, + provider_uuid UUID +); + +CREATE INDEX idx_audit_entity ON audit_records(entity_uuid, leaf_index); +CREATE INDEX idx_audit_tenant ON audit_records(tenant_uuid, record_timestamp); +CREATE INDEX idx_audit_action ON audit_records(action, record_timestamp); +CREATE INDEX idx_audit_request ON audit_records(request_uuid, leaf_index); +CREATE INDEX idx_audit_leaf ON audit_records(leaf_index); + +-- Signed Tree Heads (RFC 9162 pattern) +CREATE TABLE signed_tree_heads ( + sth_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tree_size BIGINT NOT NULL, -- Number of leaves when STH was computed + timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), + sha256_root_hash VARCHAR(64) NOT NULL, -- Merkle root hash + signature TEXT NOT NULL, -- Signed by DCM audit signing key + signing_key_id VARCHAR(128) NOT NULL -- Identifies which key signed this STH +); + +CREATE INDEX idx_sth_tree_size ON signed_tree_heads(tree_size); + +-- Merkle tree intermediate nodes (materialized mode — optional) +CREATE TABLE merkle_tree_nodes ( + level INT NOT NULL, -- Tree level (0 = leaves) + position BIGINT NOT NULL, -- Position at this level + hash VARCHAR(64) NOT NULL, -- SHA-256 hash of this node + PRIMARY KEY (level, position) +); + +-- Audit table is append-only — enforce via policy +REVOKE UPDATE, DELETE ON audit_records FROM dcm_app; +GRANT INSERT, SELECT ON audit_records TO dcm_app; +GRANT INSERT, SELECT ON audit_records TO dcm_audit; + +-- ─── Providers ──────────────────────────────────────────────────────────────── + +CREATE TABLE providers ( + provider_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + handle VARCHAR(128) UNIQUE NOT NULL, + display_name VARCHAR(256) NOT NULL, + provider_type VARCHAR(64) NOT NULL + CHECK (provider_type IN ('service_provider', 'information_provider', + 'auth_provider', + 'peer_dcm', 'process_provider')), + status VARCHAR(32) NOT NULL DEFAULT 'PENDING' + CHECK (status IN ('PENDING', 'ACTIVE', 'SUSPENDED', 'DEREGISTERED', 'SANDBOX')), + endpoint VARCHAR(512) NOT NULL, -- mTLS endpoint URL + public_key_pem TEXT, + capabilities JSONB NOT NULL DEFAULT '{}', + supported_resource_types JSONB NOT NULL DEFAULT '[]', + sovereignty_declarations JSONB NOT NULL DEFAULT '[]', + registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + last_health_check TIMESTAMPTZ, + health_status VARCHAR(32) DEFAULT 'UNKNOWN' +); + +CREATE INDEX idx_providers_status ON providers(status); +CREATE INDEX idx_providers_type ON providers(provider_type, status); + +-- ─── Service Catalog ────────────────────────────────────────────────────────── + +CREATE TABLE catalog_items ( + catalog_item_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + handle VARCHAR(128) UNIQUE NOT NULL, + display_name VARCHAR(256) NOT NULL, + description TEXT, + resource_type VARCHAR(256) NOT NULL, + provider_uuid UUID REFERENCES providers(provider_uuid), + field_schema JSONB NOT NULL DEFAULT '{}', -- JSON Schema for request fields + cost_estimate JSONB, + visibility_policy JSONB NOT NULL DEFAULT '{}', -- RBAC / group visibility rules + status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE', + version_major INTEGER NOT NULL DEFAULT 1, + version_minor INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_catalog_status ON catalog_items(status); +CREATE INDEX idx_catalog_resource_type ON catalog_items(resource_type); + +-- ─── Row-Level Security (STI-001, STI-002) ──────────────────────────────────── +-- Enforce tenant isolation at storage layer. +-- dcm_app cannot query across tenant boundaries. +-- dcm_admin bypasses RLS for platform admin operations (separately audited). + +ALTER TABLE realized_entities ENABLE ROW LEVEL SECURITY; +ALTER TABLE operations ENABLE ROW LEVEL SECURITY; +ALTER TABLE audit_records ENABLE ROW LEVEL SECURITY; + +CREATE POLICY tenant_isolation_realized + ON realized_entities + FOR ALL + TO dcm_app + USING (tenant_uuid = current_setting('dcm.current_tenant_uuid')::uuid); + +CREATE POLICY tenant_isolation_operations + ON operations + FOR ALL + TO dcm_app + USING (tenant_uuid = current_setting('dcm.current_tenant_uuid')::uuid); + +CREATE POLICY tenant_isolation_audit + ON audit_records + FOR SELECT + TO dcm_app + USING (tenant_uuid = current_setting('dcm.current_tenant_uuid')::uuid); + +-- dcm_admin bypasses RLS (explicit grant) +ALTER TABLE realized_entities FORCE ROW LEVEL SECURITY; +ALTER TABLE operations FORCE ROW LEVEL SECURITY; +GRANT ALL ON ALL TABLES IN SCHEMA public TO dcm_admin; +ALTER ROLE dcm_admin BYPASSRLS; + +-- ─── Triggers ──────────────────────────────────────────────────────────────── + +-- Auto-set updated_at +CREATE OR REPLACE FUNCTION update_updated_at() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER operations_updated_at + BEFORE UPDATE ON operations + FOR EACH ROW EXECUTE FUNCTION update_updated_at(); + +-- Enforce append-only on audit (belt-and-suspenders beyond REVOKE) +CREATE OR REPLACE FUNCTION prevent_audit_modification() +RETURNS TRIGGER AS $$ +BEGIN + RAISE EXCEPTION 'Audit records are immutable. Record UUID: %', OLD.record_uuid; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER audit_immutable + BEFORE UPDATE OR DELETE ON audit_records + FOR EACH ROW EXECUTE FUNCTION prevent_audit_modification(); + +-- ─── Intent Domain (doc 51 §2.3) ──────────────────────────────────────────── +-- Append-only. Raw consumer declarations. Never modified after write. + +CREATE TABLE intent_records ( + intent_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + entity_uuid UUID NOT NULL, + tenant_uuid UUID NOT NULL REFERENCES tenants(tenant_uuid), + catalog_item_uuid UUID NOT NULL, + submitted_by UUID NOT NULL, + submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + submitted_via VARCHAR(32) NOT NULL + CHECK (submitted_via IN ('api', 'gitops', 'cli', 'message_bus')), + intent_version INTEGER NOT NULL DEFAULT 1, + fields JSONB NOT NULL DEFAULT '{}', + provenance JSONB NOT NULL DEFAULT '{}' +); + +CREATE INDEX idx_intent_entity ON intent_records(entity_uuid, intent_version); +CREATE INDEX idx_intent_tenant ON intent_records(tenant_uuid, submitted_at); +REVOKE UPDATE, DELETE ON intent_records FROM dcm_app; + +-- ─── Requested Domain (doc 51 §2.3) ───────────────────────────────────────── +-- Append-only. Assembled, policy-evaluated, placed payloads. + +CREATE TABLE requested_records ( + requested_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + entity_uuid UUID NOT NULL, + tenant_uuid UUID NOT NULL REFERENCES tenants(tenant_uuid), + operation_uuid UUID NOT NULL REFERENCES operations(operation_uuid), + intent_uuid UUID NOT NULL REFERENCES intent_records(intent_uuid), + resource_type VARCHAR(256) NOT NULL, + provider_uuid UUID NOT NULL, + assembled_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + assembled_payload JSONB NOT NULL DEFAULT '{}', + layer_sources JSONB NOT NULL DEFAULT '[]', + policy_results JSONB NOT NULL DEFAULT '{}', + placement_result JSONB NOT NULL DEFAULT '{}', + provenance JSONB NOT NULL DEFAULT '{}' +); + +CREATE INDEX idx_requested_entity ON requested_records(entity_uuid); +CREATE INDEX idx_requested_tenant ON requested_records(tenant_uuid); +CREATE INDEX idx_requested_operation ON requested_records(operation_uuid); +REVOKE UPDATE, DELETE ON requested_records FROM dcm_app; + +-- ─── Discovered Domain (doc 51 §2.3) ──────────────────────────────────────── +-- Ephemeral snapshots from provider discovery runs. + +CREATE TABLE discovered_records ( + discovery_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + entity_uuid UUID, + tenant_uuid UUID REFERENCES tenants(tenant_uuid), + provider_uuid UUID NOT NULL, + resource_type VARCHAR(256) NOT NULL, + discovered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + discovery_run_uuid UUID NOT NULL, + discovered_fields JSONB NOT NULL DEFAULT '{}', + provider_native_id VARCHAR(512), + match_confidence VARCHAR(16) DEFAULT 'exact' + CHECK (match_confidence IN ('exact', 'high', 'low', 'unmatched')) +); + +CREATE INDEX idx_discovered_entity ON discovered_records(entity_uuid, discovered_at); +CREATE INDEX idx_discovered_run ON discovered_records(discovery_run_uuid); +CREATE INDEX idx_discovered_orphans ON discovered_records(entity_uuid) WHERE entity_uuid IS NULL; + +-- ─── Pipeline Events (doc 51 §2.3) ────────────────────────────────────────── +-- Append-only event log. LISTEN/NOTIFY for real-time pipeline routing. + +CREATE TABLE pipeline_events ( + event_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + event_type VARCHAR(128) NOT NULL, + entity_uuid UUID, + request_uuid UUID, + tenant_uuid UUID, + actor_uuid UUID, + payload JSONB NOT NULL DEFAULT '{}', + published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + consumed_by JSONB NOT NULL DEFAULT '[]', + consumed_at TIMESTAMPTZ +); + +CREATE INDEX idx_events_type ON pipeline_events(event_type, published_at); +CREATE INDEX idx_events_entity ON pipeline_events(entity_uuid, published_at); +CREATE INDEX idx_events_unconsumed ON pipeline_events(event_type, published_at) + WHERE consumed_at IS NULL; +REVOKE UPDATE, DELETE ON pipeline_events FROM dcm_app; + +-- Notify trigger for real-time pipeline routing +CREATE OR REPLACE FUNCTION notify_pipeline_event() RETURNS TRIGGER AS $$ +BEGIN + PERFORM pg_notify('dcm_pipeline', json_build_object( + 'event_uuid', NEW.event_uuid, + 'event_type', NEW.event_type, + 'entity_uuid', NEW.entity_uuid + )::text); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER pipeline_event_notify + AFTER INSERT ON pipeline_events + FOR EACH ROW EXECUTE FUNCTION notify_pipeline_event(); + +-- ─── RLS on new tables ────────────────────────────────────────────────────── + +ALTER TABLE intent_records ENABLE ROW LEVEL SECURITY; +ALTER TABLE requested_records ENABLE ROW LEVEL SECURITY; +ALTER TABLE discovered_records ENABLE ROW LEVEL SECURITY; +ALTER TABLE pipeline_events ENABLE ROW LEVEL SECURITY; + +CREATE POLICY tenant_isolation_intent ON intent_records + FOR ALL TO dcm_app + USING (tenant_uuid = current_setting('dcm.current_tenant_uuid')::uuid); +CREATE POLICY tenant_isolation_requested ON requested_records + FOR ALL TO dcm_app + USING (tenant_uuid = current_setting('dcm.current_tenant_uuid')::uuid); +CREATE POLICY tenant_isolation_discovered ON discovered_records + FOR ALL TO dcm_app + USING (tenant_uuid = current_setting('dcm.current_tenant_uuid')::uuid); +CREATE POLICY tenant_isolation_events ON pipeline_events + FOR SELECT TO dcm_app + USING (tenant_uuid = current_setting('dcm.current_tenant_uuid')::uuid); + +-- ─── Secrets (doc 31, doc 51 §4.2) ─────────────────────────────────────────── +-- Internal secrets management using envelope encryption. +-- Each secret value is AES-256-GCM encrypted with a per-secret DEK. +-- DEKs are encrypted with the master KEK from the deployment environment. +-- External mode (Vault) bypasses this table entirely. + +CREATE TABLE secrets ( + secret_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tenant_uuid UUID REFERENCES tenants(tenant_uuid), -- null for system secrets + secret_path VARCHAR(512) NOT NULL, -- hierarchical path (e.g., providers/{uuid}/credentials) + secret_type VARCHAR(64) NOT NULL + CHECK (secret_type IN ('credential', 'encryption_key', + 'signing_key', 'certificate', 'generic')), + encrypted_value BYTEA NOT NULL, -- AES-256-GCM encrypted + encrypted_dek BYTEA NOT NULL, -- DEK encrypted with KEK + encryption_algorithm VARCHAR(32) NOT NULL DEFAULT 'AES-256-GCM', + kek_id VARCHAR(128) NOT NULL, -- identifies which KEK encrypted the DEK + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + rotated_at TIMESTAMPTZ, + expires_at TIMESTAMPTZ, + lifecycle_state VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' + CHECK (lifecycle_state IN ('ACTIVE', 'ROTATING', 'REVOKED', 'EXPIRED')), + UNIQUE(secret_path) +); + +CREATE INDEX idx_secrets_tenant ON secrets(tenant_uuid); +CREATE INDEX idx_secrets_path ON secrets(secret_path); +CREATE INDEX idx_secrets_expiry ON secrets(expires_at) WHERE lifecycle_state = 'ACTIVE'; + +-- Secrets table: no UPDATE on encrypted_value (rotation creates new row, revokes old) +-- SELECT restricted to dcm_app via RLS +ALTER TABLE secrets ENABLE ROW LEVEL SECURITY; +CREATE POLICY tenant_isolation_secrets ON secrets + FOR ALL TO dcm_app + USING (tenant_uuid IS NULL OR tenant_uuid = current_setting('dcm.current_tenant_uuid')::uuid); + +-- ─── Subscriptions (doc 50) ────────────────────────────────────────────────── + +CREATE TABLE subscriptions ( + subscription_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tenant_uuid UUID NOT NULL REFERENCES tenants(tenant_uuid), + handle VARCHAR(256) NOT NULL, + display_name VARCHAR(256) NOT NULL, + catalog_item_uuid UUID NOT NULL, + resource_type VARCHAR(256) NOT NULL, + provider_uuid UUID NOT NULL, + lifecycle_state VARCHAR(32) NOT NULL DEFAULT 'PENDING' + CHECK (lifecycle_state IN ( + 'PENDING', 'PROVISIONING', 'ACTIVE', + 'SUSPENDED', 'RENEWAL_PENDING', 'TIER_CHANGE_PENDING', + 'EXPIRED', 'CANCELLED', 'DECOMMISSIONING', 'DECOMMISSIONED' + )), + terms JSONB NOT NULL DEFAULT '{}', + entitlements JSONB NOT NULL DEFAULT '{}', + update_channels JSONB NOT NULL DEFAULT '[]', + terms_version VARCHAR(32) NOT NULL DEFAULT '1.0.0', + started_at TIMESTAMPTZ, + expires_at TIMESTAMPTZ, + grace_period INTERVAL NOT NULL DEFAULT '30 days', + auto_renew BOOLEAN NOT NULL DEFAULT true, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(tenant_uuid, handle) +); + +CREATE INDEX idx_subscriptions_tenant ON subscriptions(tenant_uuid, lifecycle_state); +CREATE INDEX idx_subscriptions_provider ON subscriptions(provider_uuid); +CREATE INDEX idx_subscriptions_expiry ON subscriptions(expires_at) WHERE lifecycle_state = 'ACTIVE'; + +CREATE TABLE subscription_entities ( + subscription_uuid UUID NOT NULL REFERENCES subscriptions(subscription_uuid), + entity_uuid UUID NOT NULL, + role VARCHAR(64) NOT NULL DEFAULT 'managed', + bound_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (subscription_uuid, entity_uuid) +); + +CREATE TABLE subscription_updates ( + update_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + subscription_uuid UUID NOT NULL REFERENCES subscriptions(subscription_uuid), + entity_uuid UUID NOT NULL, + provider_uuid UUID NOT NULL, + channel VARCHAR(64) NOT NULL, + status VARCHAR(32) NOT NULL DEFAULT 'PENDING' + CHECK (status IN ('PENDING', 'APPROVED', 'REJECTED', + 'APPLIED', 'FAILED', 'EXPIRED')), + update_payload JSONB NOT NULL DEFAULT '{}', + submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + decided_at TIMESTAMPTZ, + decided_by UUID, + applied_at TIMESTAMPTZ, + auto_applied BOOLEAN NOT NULL DEFAULT false +); + +CREATE INDEX idx_sub_updates_subscription ON subscription_updates(subscription_uuid, status); +CREATE INDEX idx_sub_updates_pending ON subscription_updates(status, submitted_at) WHERE status = 'PENDING'; + +ALTER TABLE subscriptions ENABLE ROW LEVEL SECURITY; +ALTER TABLE subscription_entities ENABLE ROW LEVEL SECURITY; +ALTER TABLE subscription_updates ENABLE ROW LEVEL SECURITY; + +CREATE POLICY tenant_isolation_subscriptions ON subscriptions + FOR ALL TO dcm_app + USING (tenant_uuid = current_setting('dcm.current_tenant_uuid')::uuid); +CREATE POLICY tenant_isolation_sub_entities ON subscription_entities + FOR ALL TO dcm_app + USING (subscription_uuid IN ( + SELECT subscription_uuid FROM subscriptions + WHERE tenant_uuid = current_setting('dcm.current_tenant_uuid')::uuid + )); +CREATE POLICY tenant_isolation_sub_updates ON subscription_updates + FOR ALL TO dcm_app + USING (subscription_uuid IN ( + SELECT subscription_uuid FROM subscriptions + WHERE tenant_uuid = current_setting('dcm.current_tenant_uuid')::uuid + ));