diff --git a/conformance/reconciliation_finished.json b/conformance/reconciliation_finished.json new file mode 100644 index 0000000..c65f191 --- /dev/null +++ b/conformance/reconciliation_finished.json @@ -0,0 +1,62 @@ +{ + "context": { + "specversion": "0.6.0-draft", + "id": "271069a8-fc18-44f1-b38f-9d70a1695819", + "chainId": "4c8cb7dd-3448-41de-8768-eec704e2829b", + "source": "/gitops/argocd", + "type": "dev.cdevents.reconciliation.finished.0.1.0-draft", + "timestamp": "2026-07-27T17:32:10Z", + "schemaUri": "https://myorg.com/schema/custom", + "links": [ + { + "linkType": "RELATION", + "linkKind": "TRIGGER", + "target": { + "contextId": "5328c37f-bb7e-4bb7-84ea-9f5f85e4a7ce" + }, + "tags": { + "foo1": "bar", + "foo2": "bar" + } + }, + { + "linkType": "PATH", + "from": { + "contextId": "5328c37f-bb7e-4bb7-84ea-9f5f85e4a7ce" + }, + "tags": { + "foo1": "bar", + "foo2": "bar" + } + }, + { + "linkType": "END", + "from": { + "contextId": "5328c37f-bb7e-4bb7-84ea-9f5f85e4a7ce" + }, + "tags": { + "foo1": "bar", + "foo2": "bar" + } + } + ] + }, + "subject": { + "id": "argocd-sync-00481", + "source": "/gitops/argocd", + "content": { + "desiredState": { + "id": "a8f3c21", + "source": "/scm/github/orders-svc" + }, + "target": { + "id": "prod", + "source": "/clusters/prod", + "type": "cluster" + }, + "driftDetected": true, + "changed": true, + "outcome": "success" + } + } +} diff --git a/conformance/reconciliation_started.json b/conformance/reconciliation_started.json new file mode 100644 index 0000000..471f838 --- /dev/null +++ b/conformance/reconciliation_started.json @@ -0,0 +1,60 @@ +{ + "context": { + "specversion": "0.6.0-draft", + "id": "271069a8-fc18-44f1-b38f-9d70a1695819", + "chainId": "4c8cb7dd-3448-41de-8768-eec704e2829b", + "source": "/gitops/argocd", + "type": "dev.cdevents.reconciliation.started.0.1.0-draft", + "timestamp": "2026-07-27T17:32:10Z", + "schemaUri": "https://myorg.com/schema/custom", + "links": [ + { + "linkType": "RELATION", + "linkKind": "TRIGGER", + "target": { + "contextId": "5328c37f-bb7e-4bb7-84ea-9f5f85e4a7ce" + }, + "tags": { + "foo1": "bar", + "foo2": "bar" + } + }, + { + "linkType": "PATH", + "from": { + "contextId": "5328c37f-bb7e-4bb7-84ea-9f5f85e4a7ce" + }, + "tags": { + "foo1": "bar", + "foo2": "bar" + } + }, + { + "linkType": "END", + "from": { + "contextId": "5328c37f-bb7e-4bb7-84ea-9f5f85e4a7ce" + }, + "tags": { + "foo1": "bar", + "foo2": "bar" + } + } + ] + }, + "subject": { + "id": "argocd-sync-00481", + "source": "/gitops/argocd", + "content": { + "desiredState": { + "id": "a8f3c21", + "source": "/scm/github/orders-svc" + }, + "target": { + "id": "prod", + "source": "/clusters/prod", + "type": "cluster" + }, + "driftDetected": true + } + } +} diff --git a/continuous-deployment.md b/continuous-deployment.md index fba55e9..ae1d5a2 100644 --- a/continuous-deployment.md +++ b/continuous-deployment.md @@ -16,10 +16,13 @@ Continuous Deployment (CD) events are related to continuous deployment pipelines This specification defines two subjects in this stage: `environment` and `service`. The term `service` is used to represent a running Artifact. A `service` can represent a binary that is running, a daemon, an application, a docker container. The term `environment` represent any platform which has all the means to run a `service`. +Some systems reach a target state by converging toward it rather than by applying a discrete deployment. A [`reconciliation`](#reconciliation) is one such convergence between a declared desired state and the observed state of a target. + | Subject | Description | Predicates | |---------|-------------|------------| | [`environment`](#environment) | An environment where to run services | [`created`](#environment-created), [`modified`](#environment-modified), [`deleted`](#environment-deleted)| | [`service`](#service) | A service | [`deployed`](#service-deployed), [`upgraded`](#service-upgraded), [`rolledback`](#service-rolledback), [`removed`](#service-removed), [`published`](#service-published)| +| [`reconciliation`](#reconciliation) | A convergence between a declared desired state and the observed state of a target | [`started`](#reconciliation-started), [`finished`](#reconciliation-finished)| ### `environment` @@ -43,6 +46,27 @@ A `service` can represent for example a binary that is running, a daemon, an app | environment | `Object` ([`environment`](#environment)) | Reference for the environment where the service runs | `{"id": "1234"}`, `{"id": "maven123, "source": "tekton-dev-123"}` | | artifactId | `Purl` | Identifier of the artifact deployed with this service | `pkg:oci/myapp@sha256%3A0b31b1c02ff458ad9b7b81cbdf8f028bd54699fa151f221d1e8de6817db93427`, `pkg:golang/mygit.com/myorg/myapp@234fd47e07d1004f0aed9c` | +### `reconciliation` + +A [`reconciliation`](#reconciliation) is a convergence between a declared desired state and the observed state of a target. It is what a controller performs when it brings a target into line with a revision it has been told to converge on. + +The relation is what defines it: which desired state was being converged to, whether the target had drifted from it, and whether the convergence closed the gap. `environment.modified` reports that a mutation occurred but carries none of that, so a consumer cannot distinguish a target that converged cleanly from one that was changed by hand, nor tell which revision the running state corresponds to. + +The subject is defined over the declared-versus-observed relation generally, not over cluster convergence specifically. A controller reconciling a cluster to a commit and a process reconciling a data store to a declared contract are the same relation, and both are in scope. + +A `reconciliation` is emitted when a convergence occurs — when observed state differs from desired and the controller acts. A cycle that finds nothing to do emits nothing. This keeps the subject an occurrence model, and means a large estate does not produce continuous events reporting that nothing happened. A convergence that was expected and did not occur is found by querying for the event that is missing. + +| Field | Type | Description | Examples | +|-------|------|-------------|----------| +| id | `String` | See [id](spec.md#id-subject)| `argocd-sync-00481` | +| source | `URI-Reference` | See [source](spec.md#source-subject) | `/gitops/argocd` | +| desiredState | `Object` | The declared desired state being converged to, typically a commit or revision | `{"id": "a8f3c21", "source": "/scm/github/orders-svc"}` | +| target | `Object` | The target being converged. `type` names its kind. | `{"id": "prod", "source": "/clusters/prod", "type": "cluster"}` | +| driftDetected | `Boolean` | Whether observed state differed from desired at the start of the convergence | `true` | +| changed | `Boolean` | Whether the convergence applied any change | `true` | +| outcome | `String (enum)` | outcome of a finished `reconciliation` | `success`, `failure`, `cancel`, or `error` | +| reason | `String` | Detail related to the outcome | `Target rejected the applied revision` | + ## Events ### [`environment created`](conformance/environment_created.json) @@ -161,3 +185,38 @@ This event represents an existing instance of a service that has an accessible U | id | `String` | See [id](spec.md#id-subject)| `service/myapp`, `daemonset/myapp` | ✅ | | source | `URI-Reference` | See [source](spec.md#source-subject) | | | | environment | `Object` ([`environment`](#environment)) | Reference for the environment where the service runs | `{"id": "1234"}`, `{"id": "maven123, "source": "tekton-dev-123"}` | ✅ | + +### [`reconciliation started`](conformance/reconciliation_started.json) + +A controller has begun converging a target toward a declared desired state. + +- Event Type: __`dev.cdevents.reconciliation.started.0.1.0-draft`__ +- Predicate: started +- Subject: [`reconciliation`](#reconciliation) + +| Field | Type | Description | Examples | Required | +|-------|------|-------------|----------|----------------------------| +| id | `String` | See [id](spec.md#id-subject)| `argocd-sync-00481` | ✅ | +| source | `URI-Reference` | [source](spec.md#source) from the context | | | +| desiredState | `Object` | The declared desired state being converged to | `{"id": "a8f3c21", "source": "/scm/github/orders-svc"}` | ✅ | +| target | `Object` | The target being converged | `{"id": "prod", "source": "/clusters/prod", "type": "cluster"}` | ✅ | +| driftDetected | `Boolean` | Whether observed state differed from desired at the start of the convergence | `true` | | + +### [`reconciliation finished`](conformance/reconciliation_finished.json) + +A convergence has terminated, successfully or not. + +- Event Type: __`dev.cdevents.reconciliation.finished.0.1.0-draft`__ +- Predicate: finished +- Subject: [`reconciliation`](#reconciliation) + +| Field | Type | Description | Examples | Required | +|-------|------|-------------|----------|----------------------------| +| id | `String` | See [id](spec.md#id-subject)| `argocd-sync-00481` | ✅ | +| source | `URI-Reference` | [source](spec.md#source) from the context | | | +| desiredState | `Object` | The declared desired state converged to | `{"id": "a8f3c21", "source": "/scm/github/orders-svc"}` | ✅ | +| target | `Object` | The target that was converged | `{"id": "prod", "source": "/clusters/prod", "type": "cluster"}` | ✅ | +| driftDetected | `Boolean` | Whether observed state differed from desired at the start of the convergence | `true` | | +| changed | `Boolean` | Whether the convergence applied any change | `true` | | +| outcome | `String (enum)` | outcome of a finished `reconciliation` | `success`, `failure`, `cancel`, or `error` | `success`, `failure`, `cancel`, `error` | +| reason | `String` | Detail related to the outcome | `Target rejected the applied revision` | | diff --git a/schemas/reconciliationfinished.json b/schemas/reconciliationfinished.json new file mode 100644 index 0000000..6949717 --- /dev/null +++ b/schemas/reconciliationfinished.json @@ -0,0 +1,164 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cdevents.dev/0.6.0-draft/schema/reconciliation-finished-event", + "properties": { + "context": { + "properties": { + "specversion": { + "type": "string", + "minLength": 1 + }, + "id": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "minLength": 1, + "format": "uri-reference" + }, + "type": { + "type": "string", + "enum": [ + "dev.cdevents.reconciliation.finished.0.1.0-draft" + ], + "default": "dev.cdevents.reconciliation.finished.0.1.0-draft" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "schemaUri": { + "type": "string", + "minLength": 1, + "format": "uri" + }, + "chainId": { + "type": "string", + "minLength": 1 + }, + "links": { + "$ref": "links/embeddedlinksarray" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "specversion", + "id", + "source", + "type", + "timestamp" + ] + }, + "subject": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "minLength": 1, + "format": "uri-reference" + }, + "content": { + "properties": { + "desiredState": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "minLength": 1, + "format": "uri-reference" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id" + ] + }, + "target": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "minLength": 1, + "format": "uri-reference" + }, + "type": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id", + "type" + ] + }, + "driftDetected": { + "type": "boolean" + }, + "changed": { + "type": "boolean" + }, + "outcome": { + "type": "string", + "enum": [ + "success", + "failure", + "cancel", + "error" + ] + }, + "reason": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "desiredState", + "target", + "outcome" + ] + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id", + "content" + ] + }, + "customData": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "string", + "contentEncoding": "base64" + } + ] + }, + "customDataContentType": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "context", + "subject" + ] +} diff --git a/schemas/reconciliationstarted.json b/schemas/reconciliationstarted.json new file mode 100644 index 0000000..28daa13 --- /dev/null +++ b/schemas/reconciliationstarted.json @@ -0,0 +1,148 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cdevents.dev/0.6.0-draft/schema/reconciliation-started-event", + "properties": { + "context": { + "properties": { + "specversion": { + "type": "string", + "minLength": 1 + }, + "id": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "minLength": 1, + "format": "uri-reference" + }, + "type": { + "type": "string", + "enum": [ + "dev.cdevents.reconciliation.started.0.1.0-draft" + ], + "default": "dev.cdevents.reconciliation.started.0.1.0-draft" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "schemaUri": { + "type": "string", + "minLength": 1, + "format": "uri" + }, + "chainId": { + "type": "string", + "minLength": 1 + }, + "links": { + "$ref": "links/embeddedlinksarray" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "specversion", + "id", + "source", + "type", + "timestamp" + ] + }, + "subject": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "minLength": 1, + "format": "uri-reference" + }, + "content": { + "properties": { + "desiredState": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "minLength": 1, + "format": "uri-reference" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id" + ] + }, + "target": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "minLength": 1, + "format": "uri-reference" + }, + "type": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id", + "type" + ] + }, + "driftDetected": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "desiredState", + "target" + ] + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id", + "content" + ] + }, + "customData": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "string", + "contentEncoding": "base64" + } + ] + }, + "customDataContentType": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "context", + "subject" + ] +}