From e4ef8976554ac439e84cb544e8de07e76d97c14d Mon Sep 17 00:00:00 2001 From: Dadisi Sanyika Date: Wed, 29 Jul 2026 11:28:46 -0700 Subject: [PATCH] feat(cd): add changeWindow subject and events A changeWindow is a bounded interval during which changes to a target are governed by a window policy. The window is the gate: these events report that a gate exists, when it is in force, and when it stops being in force. Window-governed change control is common and has no representation in the vocabulary. environment.modified says a mutation happened; it cannot say whether that mutation was permitted or whether a restriction was violated, and those are different audit facts. A window is either permissive, where changes are allowed only inside it, or restrictive, where changes are forbidden inside it. windowType carries which, and the audit reading of every event on this subject depends on it, so it is present on all of them. Arriving at the gate is a separate occurrence from the gate existing. access reports a change presented to the window and the result, carrying the same status values as the approval subject's closed event so consumers handle both alike. breached is not an access result: it reports a change that took effect without passing the gate at all. Seven predicates: created, opened, access, breached, modified, revoked and closed. modified carries the window's complete resulting state rather than a description of what moved, so a consumer is not left to diff two events. revoked and closed together account for the window ceasing to exist, so there is no separate deletion predicate. Event versions start at 0.1.0-draft. Signed-off-by: Dadisi Sanyika --- .spellcheck-en-custom.txt | 1 + conformance/changewindow_access.json | 64 +++++++++ conformance/changewindow_breached.json | 63 +++++++++ conformance/changewindow_closed.json | 58 +++++++++ conformance/changewindow_created.json | 62 +++++++++ conformance/changewindow_modified.json | 62 +++++++++ conformance/changewindow_opened.json | 58 +++++++++ conformance/changewindow_revoked.json | 63 +++++++++ continuous-deployment.md | 166 +++++++++++++++++++++++ schemas/changewindowaccess.json | 174 +++++++++++++++++++++++++ schemas/changewindowbreached.json | 164 +++++++++++++++++++++++ schemas/changewindowclosed.json | 145 +++++++++++++++++++++ schemas/changewindowcreated.json | 166 +++++++++++++++++++++++ schemas/changewindowmodified.json | 166 +++++++++++++++++++++++ schemas/changewindowopened.json | 145 +++++++++++++++++++++ schemas/changewindowrevoked.json | 164 +++++++++++++++++++++++ 16 files changed, 1721 insertions(+) create mode 100644 conformance/changewindow_access.json create mode 100644 conformance/changewindow_breached.json create mode 100644 conformance/changewindow_closed.json create mode 100644 conformance/changewindow_created.json create mode 100644 conformance/changewindow_modified.json create mode 100644 conformance/changewindow_opened.json create mode 100644 conformance/changewindow_revoked.json create mode 100644 schemas/changewindowaccess.json create mode 100644 schemas/changewindowbreached.json create mode 100644 schemas/changewindowclosed.json create mode 100644 schemas/changewindowcreated.json create mode 100644 schemas/changewindowmodified.json create mode 100644 schemas/changewindowopened.json create mode 100644 schemas/changewindowrevoked.json diff --git a/.spellcheck-en-custom.txt b/.spellcheck-en-custom.txt index 4bb487d..dddf06c 100644 --- a/.spellcheck-en-custom.txt +++ b/.spellcheck-en-custom.txt @@ -25,6 +25,7 @@ cardpane cdevent cdevents ce +changewindow cloudevents contenttype customdata diff --git a/conformance/changewindow_access.json b/conformance/changewindow_access.json new file mode 100644 index 0000000..d0d1afc --- /dev/null +++ b/conformance/changewindow_access.json @@ -0,0 +1,64 @@ +{ + "context": { + "specversion": "0.6.0-draft", + "id": "271069a8-fc18-44f1-b38f-9d70a1695819", + "chainId": "4c8cb7dd-3448-41de-8768-eec704e2829b", + "source": "/changecontrol/prod", + "type": "dev.cdevents.changewindow.access.0.1.0-draft", + "timestamp": "2026-12-24T14:05:00Z", + "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": "freeze-holiday-2026", + "source": "/changecontrol/prod", + "content": { + "target": { + "id": "prod", + "source": "/clusters/prod", + "type": "environment" + }, + "windowType": "restrictive", + "notBefore": "2026-12-20T00:00:00Z", + "notAfter": "2027-01-02T00:00:00Z", + "change": { + "id": "deploy-90217", + "source": "/prod/deploys" + }, + "status": "rejected", + "reason": "Deployment requested during active holiday freeze" + } + } +} diff --git a/conformance/changewindow_breached.json b/conformance/changewindow_breached.json new file mode 100644 index 0000000..ae603b9 --- /dev/null +++ b/conformance/changewindow_breached.json @@ -0,0 +1,63 @@ +{ + "context": { + "specversion": "0.6.0-draft", + "id": "271069a8-fc18-44f1-b38f-9d70a1695819", + "chainId": "4c8cb7dd-3448-41de-8768-eec704e2829b", + "source": "/changecontrol/prod", + "type": "dev.cdevents.changewindow.breached.0.1.0-draft", + "timestamp": "2026-12-24T14:05:00Z", + "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": "freeze-holiday-2026", + "source": "/changecontrol/prod", + "content": { + "target": { + "id": "prod", + "source": "/clusters/prod", + "type": "environment" + }, + "windowType": "restrictive", + "notBefore": "2026-12-20T00:00:00Z", + "notAfter": "2027-01-02T00:00:00Z", + "change": { + "id": "deploy-90217", + "source": "/prod/deploys" + }, + "reason": "Deployment executed during active holiday freeze" + } + } +} diff --git a/conformance/changewindow_closed.json b/conformance/changewindow_closed.json new file mode 100644 index 0000000..db522ae --- /dev/null +++ b/conformance/changewindow_closed.json @@ -0,0 +1,58 @@ +{ + "context": { + "specversion": "0.6.0-draft", + "id": "271069a8-fc18-44f1-b38f-9d70a1695819", + "chainId": "4c8cb7dd-3448-41de-8768-eec704e2829b", + "source": "/changecontrol/prod", + "type": "dev.cdevents.changewindow.closed.0.1.0-draft", + "timestamp": "2026-12-24T14:05:00Z", + "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": "freeze-holiday-2026", + "source": "/changecontrol/prod", + "content": { + "target": { + "id": "prod", + "source": "/clusters/prod", + "type": "environment" + }, + "windowType": "restrictive", + "notBefore": "2026-12-20T00:00:00Z", + "notAfter": "2027-01-02T00:00:00Z" + } + } +} diff --git a/conformance/changewindow_created.json b/conformance/changewindow_created.json new file mode 100644 index 0000000..be0c55b --- /dev/null +++ b/conformance/changewindow_created.json @@ -0,0 +1,62 @@ +{ + "context": { + "specversion": "0.6.0-draft", + "id": "271069a8-fc18-44f1-b38f-9d70a1695819", + "chainId": "4c8cb7dd-3448-41de-8768-eec704e2829b", + "source": "/changecontrol/prod", + "type": "dev.cdevents.changewindow.created.0.1.0-draft", + "timestamp": "2026-12-24T14:05:00Z", + "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": "freeze-holiday-2026", + "source": "/changecontrol/prod", + "content": { + "target": { + "id": "prod", + "source": "/clusters/prod", + "type": "environment" + }, + "windowType": "restrictive", + "notBefore": "2026-12-20T00:00:00Z", + "notAfter": "2027-01-02T00:00:00Z", + "author": { + "id": "alice", + "source": "/changecontrol/prod" + } + } + } +} diff --git a/conformance/changewindow_modified.json b/conformance/changewindow_modified.json new file mode 100644 index 0000000..f7fbe11 --- /dev/null +++ b/conformance/changewindow_modified.json @@ -0,0 +1,62 @@ +{ + "context": { + "specversion": "0.6.0-draft", + "id": "271069a8-fc18-44f1-b38f-9d70a1695819", + "chainId": "4c8cb7dd-3448-41de-8768-eec704e2829b", + "source": "/changecontrol/prod", + "type": "dev.cdevents.changewindow.modified.0.1.0-draft", + "timestamp": "2026-12-24T14:05:00Z", + "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": "freeze-holiday-2026", + "source": "/changecontrol/prod", + "content": { + "target": { + "id": "prod", + "source": "/clusters/prod", + "type": "environment" + }, + "windowType": "restrictive", + "notBefore": "2026-12-20T00:00:00Z", + "notAfter": "2027-01-02T00:00:00Z", + "author": { + "id": "alice", + "source": "/changecontrol/prod" + } + } + } +} diff --git a/conformance/changewindow_opened.json b/conformance/changewindow_opened.json new file mode 100644 index 0000000..286e5f0 --- /dev/null +++ b/conformance/changewindow_opened.json @@ -0,0 +1,58 @@ +{ + "context": { + "specversion": "0.6.0-draft", + "id": "271069a8-fc18-44f1-b38f-9d70a1695819", + "chainId": "4c8cb7dd-3448-41de-8768-eec704e2829b", + "source": "/changecontrol/prod", + "type": "dev.cdevents.changewindow.opened.0.1.0-draft", + "timestamp": "2026-12-24T14:05:00Z", + "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": "freeze-holiday-2026", + "source": "/changecontrol/prod", + "content": { + "target": { + "id": "prod", + "source": "/clusters/prod", + "type": "environment" + }, + "windowType": "restrictive", + "notBefore": "2026-12-20T00:00:00Z", + "notAfter": "2027-01-02T00:00:00Z" + } + } +} diff --git a/conformance/changewindow_revoked.json b/conformance/changewindow_revoked.json new file mode 100644 index 0000000..4a5e03c --- /dev/null +++ b/conformance/changewindow_revoked.json @@ -0,0 +1,63 @@ +{ + "context": { + "specversion": "0.6.0-draft", + "id": "271069a8-fc18-44f1-b38f-9d70a1695819", + "chainId": "4c8cb7dd-3448-41de-8768-eec704e2829b", + "source": "/changecontrol/prod", + "type": "dev.cdevents.changewindow.revoked.0.1.0-draft", + "timestamp": "2026-12-24T14:05:00Z", + "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": "freeze-holiday-2026", + "source": "/changecontrol/prod", + "content": { + "target": { + "id": "prod", + "source": "/clusters/prod", + "type": "environment" + }, + "windowType": "restrictive", + "notBefore": "2026-12-20T00:00:00Z", + "notAfter": "2027-01-02T00:00:00Z", + "author": { + "id": "alice", + "source": "/changecontrol/prod" + }, + "reason": "Freeze lifted early by change board" + } + } +} diff --git a/continuous-deployment.md b/continuous-deployment.md index fba55e9..cffa8c4 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`. +Changes to a target are often governed by when they are permitted to happen. A [`changeWindow`](#changewindow) is a bounded interval during which a window policy governs changes to 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)| +| [`changeWindow`](#changewindow) | A bounded interval during which changes to a target are governed by a window policy | [`created`](#changewindow-created), [`opened`](#changewindow-opened), [`access`](#changewindow-access), [`breached`](#changewindow-breached), [`modified`](#changewindow-modified), [`revoked`](#changewindow-revoked), [`closed`](#changewindow-closed)| ### `environment` @@ -43,6 +46,31 @@ 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` | +### `changeWindow` + +A [`changeWindow`](#changewindow) is a bounded interval during which changes to a target are governed by a window policy. The window is the gate: these events report that a gate exists, when it is in force, and when it stops being in force. + +A window is either **permissive** — changes are allowed only inside it, as with a maintenance or deployment window — or **restrictive**, where changes are forbidden inside it, as with a freeze. `windowType` carries which, and the audit reading of every event on this subject depends on it, so it is present on all of them. + +Window-governed change control has no representation in the vocabulary today. `environment.modified` says a mutation happened; it cannot say whether that mutation was permitted or whether a restriction was violated, and those are different audit facts. + +Arriving at the gate is a separate occurrence from the gate existing. [`access`](#changewindow-access) reports a change presented to the window and the result, carrying the same `status` values as the `approval` subject's `closed` event so consumers handle both alike. [`breached`](#changewindow-breached) is not an access result: it reports a change that took effect without passing the gate at all. + +`revoked` and `closed` are the terminal events. A revoked window was invalidated by an actor before its bounds ran out; a closed window reached `notAfter` and ended normally. Between them they account for the window ceasing to exist, so there is no separate deletion predicate. + +| Field | Type | Description | Examples | +|-------|------|-------------|----------| +| id | `String` | See [id](spec.md#id-subject)| `freeze-holiday-2026` | +| source | `URI-Reference` | See [source](spec.md#source-subject) | `/changecontrol/prod` | +| target | `Object` | The resource whose changes the window governs. `type` names its kind. | `{"id": "prod", "source": "/clusters/prod", "type": "environment"}` | +| windowType | `String (enum)` | Whether changes are allowed only inside the window or forbidden inside it | `permissive`, `restrictive` | +| notBefore | `Timestamp` | Window start bound | `2026-12-20T00:00:00Z` | +| notAfter | `Timestamp` | Window end bound | `2027-01-02T00:00:00Z` | +| author | `Object` | The actor the producer attributes the action to | `{"id": "alice", "source": "/changecontrol/prod"}` | +| change | `Object` ([`change`](source-code-version-control.md#change)) | The change presented to the window, or the change that bypassed it | `{"id": "deploy-90217", "source": "/prod/deploys"}` | +| status | `String (enum)` | The result of presenting a change to the window | `approved`, `rejected`, `cancelled`, `expired` | +| reason | `String` | Why the window exists, or detail for a result, revocation or breach | `Holiday change freeze` | + ## Events ### [`environment created`](conformance/environment_created.json) @@ -161,3 +189,141 @@ 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"}` | ✅ | + +### [`changeWindow created`](conformance/changewindow_created.json) + +A change window has been brought into existence with its bounds, type and target. The window is not necessarily in force yet. + +- Event Type: __`dev.cdevents.changewindow.created.0.1.0-draft`__ +- Predicate: created +- Subject: [`changeWindow`](#changewindow) + +| Field | Type | Description | Examples | Required | +|-------|------|-------------|----------|----------------------------| +| id | `String` | See [id](spec.md#id-subject)| `freeze-holiday-2026` | ✅ | +| source | `URI-Reference` | [source](spec.md#source) from the context | | | +| target | `Object` | The resource whose changes the window governs | `{"id": "prod", "source": "/clusters/prod", "type": "environment"}` | ✅ | +| windowType | `String (enum)` | Whether changes are allowed only inside the window or forbidden inside it | `permissive`, `restrictive` | `permissive`, `restrictive` | +| notBefore | `Timestamp` | Window start bound | `2026-12-20T00:00:00Z` | ✅ | +| notAfter | `Timestamp` | Window end bound | `2027-01-02T00:00:00Z` | ✅ | +| author | `Object` | The actor the producer attributes the creation to | `{"id": "alice", "source": "/changecontrol/prod"}` | ✅ | +| reason | `String` | Why the window exists | `Holiday change freeze` | | + +### [`changeWindow opened`](conformance/changewindow_opened.json) + +A change window has become active. Its policy is now in force for the target. + +- Event Type: __`dev.cdevents.changewindow.opened.0.1.0-draft`__ +- Predicate: opened +- Subject: [`changeWindow`](#changewindow) + +| Field | Type | Description | Examples | Required | +|-------|------|-------------|----------|----------------------------| +| id | `String` | See [id](spec.md#id-subject)| `freeze-holiday-2026` | ✅ | +| source | `URI-Reference` | [source](spec.md#source) from the context | | | +| target | `Object` | The resource whose changes the window governs | `{"id": "prod", "source": "/clusters/prod", "type": "environment"}` | ✅ | +| windowType | `String (enum)` | Whether changes are allowed only inside the window or forbidden inside it | `permissive`, `restrictive` | `permissive`, `restrictive` | +| notBefore | `Timestamp` | Window start bound | `2026-12-20T00:00:00Z` | | +| notAfter | `Timestamp` | Window end bound | `2027-01-02T00:00:00Z` | | +| reason | `String` | Why the window exists | `Holiday change freeze` | | + +### [`changeWindow access`](conformance/changewindow_access.json) + +A change was presented to the window and a result was reached. `status` carries the result, using the same values as the `approval` subject's `closed` event: `approved`, `rejected`, `cancelled`, `expired`. + +Receiving this event is a record of what the window decided. A consumer MUST NOT treat it as authorization for anything. + +- Event Type: __`dev.cdevents.changewindow.access.0.1.0-draft`__ +- Predicate: access +- Subject: [`changeWindow`](#changewindow) + +| Field | Type | Description | Examples | Required | +|-------|------|-------------|----------|----------------------------| +| id | `String` | See [id](spec.md#id-subject)| `freeze-holiday-2026` | ✅ | +| source | `URI-Reference` | [source](spec.md#source) from the context | | | +| target | `Object` | The resource whose changes the window governs | `{"id": "prod", "source": "/clusters/prod", "type": "environment"}` | ✅ | +| windowType | `String (enum)` | Whether changes are allowed only inside the window or forbidden inside it | `permissive`, `restrictive` | `permissive`, `restrictive` | +| change | `Object` ([`change`](source-code-version-control.md#change)) | The change presented to the window | `{"id": "deploy-90217", "source": "/prod/deploys"}` | ✅ | +| status | `String (enum)` | The result of presenting the change to the window | `approved`, `rejected`, `cancelled`, `expired` | `approved`, `rejected`, `cancelled`, `expired` | +| notBefore | `Timestamp` | Window start bound | `2026-12-20T00:00:00Z` | | +| notAfter | `Timestamp` | Window end bound | `2027-01-02T00:00:00Z` | | +| reason | `String` | Detail for the result | `Deployment requested during active holiday freeze` | | + +### [`changeWindow breached`](conformance/changewindow_breached.json) + +A change took effect in violation of the window, without passing the gate. This is not an access result: nothing was presented and nothing decided. + +Which changes constitute a breach depends on polarity. A change outside a `permissive` window is a breach; a change inside a `restrictive` window is a breach. + +- Event Type: __`dev.cdevents.changewindow.breached.0.1.0-draft`__ +- Predicate: breached +- Subject: [`changeWindow`](#changewindow) + +| Field | Type | Description | Examples | Required | +|-------|------|-------------|----------|----------------------------| +| id | `String` | See [id](spec.md#id-subject)| `freeze-holiday-2026` | ✅ | +| source | `URI-Reference` | [source](spec.md#source) from the context | | | +| target | `Object` | The resource whose changes the window governs | `{"id": "prod", "source": "/clusters/prod", "type": "environment"}` | ✅ | +| windowType | `String (enum)` | Whether changes are allowed only inside the window or forbidden inside it | `permissive`, `restrictive` | `permissive`, `restrictive` | +| change | `Object` ([`change`](source-code-version-control.md#change)) | The change that bypassed the window | `{"id": "deploy-90217", "source": "/prod/deploys"}` | ✅ | +| notBefore | `Timestamp` | Window start bound | `2026-12-20T00:00:00Z` | | +| notAfter | `Timestamp` | Window end bound | `2027-01-02T00:00:00Z` | | +| reason | `String` | Detail for the breach | `Deployment executed during active holiday freeze` | | + +### [`changeWindow modified`](conformance/changewindow_modified.json) + +A change window has been changed. The subject retains its identity across the change. + +This event carries the window's complete state after the change — the resulting `notBefore`, `notAfter`, `windowType` and `target` — rather than a description of what moved. A consumer reading a single `modified` event knows the window's current bounds without holding an earlier event to compare against. + +- Event Type: __`dev.cdevents.changewindow.modified.0.1.0-draft`__ +- Predicate: modified +- Subject: [`changeWindow`](#changewindow) + +| Field | Type | Description | Examples | Required | +|-------|------|-------------|----------|----------------------------| +| id | `String` | See [id](spec.md#id-subject)| `freeze-holiday-2026` | ✅ | +| source | `URI-Reference` | [source](spec.md#source) from the context | | | +| target | `Object` | The governed resource, as it stands after the change | `{"id": "prod", "source": "/clusters/prod", "type": "environment"}` | ✅ | +| windowType | `String (enum)` | The polarity, as it stands after the change | `permissive`, `restrictive` | `permissive`, `restrictive` | +| notBefore | `Timestamp` | The start bound, as it stands after the change | `2026-12-20T00:00:00Z` | ✅ | +| notAfter | `Timestamp` | The end bound, as it stands after the change | `2027-01-02T00:00:00Z` | ✅ | +| author | `Object` | The actor the producer attributes the change to | `{"id": "alice", "source": "/changecontrol/prod"}` | ✅ | +| reason | `String` | Detail for the change | `Freeze extended through the first trading day` | | + +### [`changeWindow revoked`](conformance/changewindow_revoked.json) + +A change window has been invalidated by an actor before its bounds ran out. Its policy is no longer in force. + +- Event Type: __`dev.cdevents.changewindow.revoked.0.1.0-draft`__ +- Predicate: revoked +- Subject: [`changeWindow`](#changewindow) + +| Field | Type | Description | Examples | Required | +|-------|------|-------------|----------|----------------------------| +| id | `String` | See [id](spec.md#id-subject)| `freeze-holiday-2026` | ✅ | +| source | `URI-Reference` | [source](spec.md#source) from the context | | | +| target | `Object` | The resource the window governed | `{"id": "prod", "source": "/clusters/prod", "type": "environment"}` | ✅ | +| windowType | `String (enum)` | Whether changes were allowed only inside the window or forbidden inside it | `permissive`, `restrictive` | `permissive`, `restrictive` | +| author | `Object` | The actor the producer attributes the revocation to | `{"id": "alice", "source": "/changecontrol/prod"}` | ✅ | +| notBefore | `Timestamp` | Window start bound | `2026-12-20T00:00:00Z` | | +| notAfter | `Timestamp` | Window end bound | `2027-01-02T00:00:00Z` | | +| reason | `String` | Detail for the revocation | `Freeze lifted early by change board` | | + +### [`changeWindow closed`](conformance/changewindow_closed.json) + +A change window reached `notAfter` and ended normally. Its policy is no longer in force. + +- Event Type: __`dev.cdevents.changewindow.closed.0.1.0-draft`__ +- Predicate: closed +- Subject: [`changeWindow`](#changewindow) + +| Field | Type | Description | Examples | Required | +|-------|------|-------------|----------|----------------------------| +| id | `String` | See [id](spec.md#id-subject)| `freeze-holiday-2026` | ✅ | +| source | `URI-Reference` | [source](spec.md#source) from the context | | | +| target | `Object` | The resource the window governed | `{"id": "prod", "source": "/clusters/prod", "type": "environment"}` | ✅ | +| windowType | `String (enum)` | Whether changes were allowed only inside the window or forbidden inside it | `permissive`, `restrictive` | `permissive`, `restrictive` | +| notBefore | `Timestamp` | Window start bound | `2026-12-20T00:00:00Z` | | +| notAfter | `Timestamp` | Window end bound | `2027-01-02T00:00:00Z` | | +| reason | `String` | Detail for the closure | `Holiday change freeze ended` | | diff --git a/schemas/changewindowaccess.json b/schemas/changewindowaccess.json new file mode 100644 index 0000000..f2d5ef7 --- /dev/null +++ b/schemas/changewindowaccess.json @@ -0,0 +1,174 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cdevents.dev/0.6.0-draft/schema/changewindow-access-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.changewindow.access.0.1.0-draft" + ], + "default": "dev.cdevents.changewindow.access.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": { + "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" + ] + }, + "windowType": { + "type": "string", + "enum": [ + "permissive", + "restrictive" + ] + }, + "notBefore": { + "type": "string", + "format": "date-time" + }, + "notAfter": { + "type": "string", + "format": "date-time" + }, + "change": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "minLength": 1, + "format": "uri-reference" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id" + ] + }, + "status": { + "type": "string", + "enum": [ + "approved", + "rejected", + "cancelled", + "expired" + ] + }, + "reason": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "target", + "windowType", + "change", + "status" + ] + } + }, + "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/changewindowbreached.json b/schemas/changewindowbreached.json new file mode 100644 index 0000000..85b0b1c --- /dev/null +++ b/schemas/changewindowbreached.json @@ -0,0 +1,164 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cdevents.dev/0.6.0-draft/schema/changewindow-breached-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.changewindow.breached.0.1.0-draft" + ], + "default": "dev.cdevents.changewindow.breached.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": { + "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" + ] + }, + "windowType": { + "type": "string", + "enum": [ + "permissive", + "restrictive" + ] + }, + "notBefore": { + "type": "string", + "format": "date-time" + }, + "notAfter": { + "type": "string", + "format": "date-time" + }, + "change": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "minLength": 1, + "format": "uri-reference" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id" + ] + }, + "reason": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "target", + "windowType", + "change" + ] + } + }, + "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/changewindowclosed.json b/schemas/changewindowclosed.json new file mode 100644 index 0000000..7431e5e --- /dev/null +++ b/schemas/changewindowclosed.json @@ -0,0 +1,145 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cdevents.dev/0.6.0-draft/schema/changewindow-closed-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.changewindow.closed.0.1.0-draft" + ], + "default": "dev.cdevents.changewindow.closed.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": { + "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" + ] + }, + "windowType": { + "type": "string", + "enum": [ + "permissive", + "restrictive" + ] + }, + "notBefore": { + "type": "string", + "format": "date-time" + }, + "notAfter": { + "type": "string", + "format": "date-time" + }, + "reason": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "target", + "windowType" + ] + } + }, + "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/changewindowcreated.json b/schemas/changewindowcreated.json new file mode 100644 index 0000000..efac2e9 --- /dev/null +++ b/schemas/changewindowcreated.json @@ -0,0 +1,166 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cdevents.dev/0.6.0-draft/schema/changewindow-created-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.changewindow.created.0.1.0-draft" + ], + "default": "dev.cdevents.changewindow.created.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": { + "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" + ] + }, + "windowType": { + "type": "string", + "enum": [ + "permissive", + "restrictive" + ] + }, + "notBefore": { + "type": "string", + "format": "date-time" + }, + "notAfter": { + "type": "string", + "format": "date-time" + }, + "author": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "minLength": 1, + "format": "uri-reference" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id" + ] + }, + "reason": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "target", + "windowType", + "author", + "notBefore", + "notAfter" + ] + } + }, + "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/changewindowmodified.json b/schemas/changewindowmodified.json new file mode 100644 index 0000000..0d857a9 --- /dev/null +++ b/schemas/changewindowmodified.json @@ -0,0 +1,166 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cdevents.dev/0.6.0-draft/schema/changewindow-modified-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.changewindow.modified.0.1.0-draft" + ], + "default": "dev.cdevents.changewindow.modified.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": { + "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" + ] + }, + "windowType": { + "type": "string", + "enum": [ + "permissive", + "restrictive" + ] + }, + "notBefore": { + "type": "string", + "format": "date-time" + }, + "notAfter": { + "type": "string", + "format": "date-time" + }, + "author": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "minLength": 1, + "format": "uri-reference" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id" + ] + }, + "reason": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "target", + "windowType", + "author", + "notBefore", + "notAfter" + ] + } + }, + "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/changewindowopened.json b/schemas/changewindowopened.json new file mode 100644 index 0000000..f12bcf4 --- /dev/null +++ b/schemas/changewindowopened.json @@ -0,0 +1,145 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cdevents.dev/0.6.0-draft/schema/changewindow-opened-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.changewindow.opened.0.1.0-draft" + ], + "default": "dev.cdevents.changewindow.opened.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": { + "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" + ] + }, + "windowType": { + "type": "string", + "enum": [ + "permissive", + "restrictive" + ] + }, + "notBefore": { + "type": "string", + "format": "date-time" + }, + "notAfter": { + "type": "string", + "format": "date-time" + }, + "reason": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "target", + "windowType" + ] + } + }, + "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/changewindowrevoked.json b/schemas/changewindowrevoked.json new file mode 100644 index 0000000..c707015 --- /dev/null +++ b/schemas/changewindowrevoked.json @@ -0,0 +1,164 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cdevents.dev/0.6.0-draft/schema/changewindow-revoked-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.changewindow.revoked.0.1.0-draft" + ], + "default": "dev.cdevents.changewindow.revoked.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": { + "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" + ] + }, + "windowType": { + "type": "string", + "enum": [ + "permissive", + "restrictive" + ] + }, + "notBefore": { + "type": "string", + "format": "date-time" + }, + "notAfter": { + "type": "string", + "format": "date-time" + }, + "author": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "minLength": 1, + "format": "uri-reference" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id" + ] + }, + "reason": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "target", + "windowType", + "author" + ] + } + }, + "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" + ] +}