Connecting Events - Links Proposal
June 24, 2026 · View on GitHub
Abstract
This proposal will outline how to connect individual CDEvents to one other. Currently there's no way of associating events without needing to backtrack across certain subject attributes, e.g. id. While this does give us the ability to construct some graph, we do not know when a particular chain starts or finishes.
This proposal will outline a new approach that will allow for connecting CDEvents and give a clear distinction of when an activity chain starts and finishes.
CDEvents Architecture
Below is an example diagram for a CDEvent system to help illustrate how users and systems may utilize CDEvents. In this case, a user is merging a GitHub pull request and then using some CDEvent front-end to query all links for their request to get a full view of all that has happened.
+--------------------------------------------------------------------------+ +------------------+
| | (links) | |
| Event Bus +--------->| |
| | | |
+----------------------+---------------------------------------------------+ | |
^ | ^ | ^ | |
| | | | | | |
| | | | | | |
| v | v | | |
+------+------+ +--------+----+ +-------------+ +--------+----+ | Links |
| | | | | |<--+ | | Service |
| GitHub | | Jenkins +-->| Artifactory | | Spinnaker | | |
| | | | | +-->| | | |
+-------------+ +-------------+ +-------------+ +------+------+ | |
^ | | |
| v | |
| +-------------+ | |
| | | | |
| | Kubernetes | | |
| | | | |
| user merges a pull +-------------+ +------------------+
| request from GitHub ^ |
| | |
| | |
| | |
+------+-----+ | |
| | | |
| User | front-end requests all links for a particular request | |
| +--------------------------------------------------------------------------------+ |
| o | |
| /|\ |<----------------------------------------------------------------------------------+
| / \ |
+------------+
Semantics
This section will define various terms to ensure there are no assumptions being made when we talk about linking events
- CI - Continuous integration
- CD - Continuous delivery
- Chain - A chain is an end to end representation of all activities performed in the CI/CD life cycle of some entity
- Link - A link is an object which describes how an event is related to another event.
Goals
The biggest challenge in this whole process is ensuring that connected events can be retrieved quickly and efficiently, while also providing the necessary metadata and structure to give a detailed picture of how things occurred.
- Provide a way of quickly retrieving all related links
- Keep link data structured small and simple
- Scalable
Use Cases
This section will go over a few use cases and explain how this approach can be used to solve for each particular case.
1. Fan Out Fan In
The fan out fan in use case is an example where a system may make parallel requests (fan out), and merge back to some other or the very same system (fan in)
Let us assume we have 3 system in our CI/CD environment. A continuous integration environment, which we will call CI system, that runs tests and builds artifacts, an artifact store that receives artifacts from the CI system, and lastly the CD system which consume these artifacts shown in the diagram below.
+-------------------+ +-----------------+
+-->| Build for Mac |-->| Test on Mac |---+
+-----------+ | +-------------------+ +-----------------+ |
| Static | | +-------------------+ +-----------------+ | +---------------------------+
| Code |---+-->| Build for SLES |-->| Test on SLES |---+-->| Promote & Deliver Release |
| Analysis | | +-------------------+ +-----------------+ | +---------------------------+
+-----------+ | +-------------------+ +-----------------+ |
+-->| Build for Windows |-->| Test on Windows |---+
+-------------------+ +-----------------+
The diagram above shows the CI event creating 3 separate artifacts that will make it's way to the artifact store. Some CD system would then consume those artifact, but would need to group all the artifact when the CI system finishes its pipeline. This is not meant to be a full diagram of the whole CDEvents flow, but a simple representation of the artifact(s) life cycle.
2. Generic UI
With the goal of interoperability, this allows for compatible standalone generic UIs for any CI/CD system.
Connecting Events
An individual event usually has some connection to some trigger. This can be a
new commit added to a pull request, a test suite being called in the CI
process, or publishing a new artifact for some system to consume. While these
events mean something themselves, they do not give the proper context of what
caused what. This section will introduce two new fields, chainId and
links, within the CDEvents context that will allow for giving some path
between CDEvents.
{
"context": {
"specversion": "0.6.0-draft",
"id": "505b31c2-8bc8-47b3-a1a0-269d7a8530ac",
"source": "dev/jenkins",
"type": "dev.cdevents.testsuite.finished.0.1.1",
"chainId": "00000000-0000-0000-0000-000000000001", # new chain id field
"timestamp": "2023-03-20T14:27:05.315384Z"
},
"subject": {
"id": "MyTestSuite",
"source": "/tests/com/org/package",
"content": {}
}
}
The chainId is an ID that will be generated when a new CDEvent chain is
wanted or if no CDEvent chain is present. This ID will follow the
UUID format. Chain IDs will
serve as a bucket for all CDEvents with some sort of path to each other.
Optional Links Field
While links can be sent separately, links can also be embedded in the CDEvent
context as an optional field. Embedded links look similar to separate links
except START links are not needed since we can infer when a chain has started
based on the context of the event. This leaves only three types of links that
may be embedded, PATH, RELATION and END. While END might be possible to
infer, it may not be as simple especially if there are gaps or islands. Having
an event specifically say it is the END of a chain will allow for UIs or
systems to act accordingly based off the ending notation.
{
"context": {
"specversion": "0.6.0-draft",
"id": "271069a8-fc18-44f1-b38f-9d70a1695819",
"chainId": "7ff3f526-1a0e-4d35-8a4c-7d6295e97359",
"source": "/event/source/123",
"type": "dev.cdevents.pipelinerun.queued.0.1.1",
"timestamp": "2023-03-20T14:27:05.315384Z",
"links": [
{
"linkType": "RELATION",
"linkKind": "TRIGGER",
"target": {
"contextId": "5328c37f-bb7e-4bb7-84ea-9f5f85e4a7ce" # context id of a change.merged CDEvent
}
}
]
},
"subject": {
"id": "mySubject123",
"source": "/event/source/123",
"content": {
"pipelineName": "myPipeline",
"uri": "https://www.example.com/mySubject123"
}
}
}
Above shows a simple relation link that would allow a trigger relation of a
changed.merge and the pipelinerun.queued event. To illustrate links
further, we can allow for a path link between pipelinerun.queued to the
pipelinerun.started event shown below.
{
"context": {
"specversion": "0.6.0-draft",
"id": "271069a8-fc18-44f1-b38f-9d70a1695819",
"chainId": "7ff3f526-1a0e-4d35-8a4c-7d6295e97359",
"source": "/event/source/123",
"type": "dev.cdevents.pipelinerun.started.0.1.1",
"timestamp": "2023-03-20T14:27:05.315384Z",
"links": [
{
"linkType": "PATH",
"from": {
"contextId": "271069a8-fc18-44f1-b38f-9d70a1695819" # context id of the pipelinerun.queued event
}
}
]
},
"subject": {
"id": "mySubject123",
"source": "/event/source/123",
"content": {
"pipelineName": "myPipeline",
"uri": "https://www.example.com/mySubject123"
}
}
}
Propagation
Chain propagation will be handled differently depending on the protocol that is used. At a high level, the SDK expects calls to construct the proper paths, but connecting of events will be handled completely by the SDKs.
There are a couple cases where propagation is difficult, and maybe even impossible depending on what the receiving services are willing to do. In events where the SDKs are not used, it is up to these receiving services to pass the chain IDs. If this is not done, then there will be a break in the chain, which means that a new chain will be started, or missing links that will cause these services to not be visible.
Links Spec
So far we have only talked about what a service may receive when consuming a CDEvent with links. However, when we store a link, there's a lot more metadata that can and should be added.
The idea is we would expect users to start links, connect links, and end links accordingly through APIs we would provide. This is very similar to how tracing works in that the individual services dictate when a tracing span starts and finishes. Granularity in tracing is completely up to the engineer which this proposal also intends users to do.
This will introduce new APIs to the CDEvents SDKs to handle automatic injection of links or allow users to provide their own definition of what a CDEvent chain is.
These links can be, but are not limited to being, sent when a CDEvent has completed: to some collector, the link service, or the event bus. Further the link service will allow for tagging of various metadata allowing for users to specify certain labels when looking at links.
Some users may prefer to not run a separate links service especially if they know their overall flow may only contain a few links. If that is the case, simply turning on link payload aggregation, will send all links in the payload. Mind you, this can make the payload very large, but may be good for debugging.
The chain ID header will continue to propagate, unless the user explicitly starts a new CDEvent chain. If there is no chain ID, the client will generate one and that will be used for the lifetime of the whole events chain.
In the case when an event consumer is also an event producer, such a consumer will easily be able to construct the link to the consumed event. A producer of events with start links is often not an event consumer itself. When links are sent stand-alone, and not embedded within the CDEvents themselves, an event consumer that is not an event producer could still construct the links if it has the necessary information for it. An event producer that wants to provide a link to some earlier sent event will need to look it up in a links service or similar if the producer wants to embed the link into the produced event itself. For events produced within a service, that service should be able to construct the links between those events by itself.
+-----+ +-----+ +-----+ +--------------+ +-----------+
| Git | | CI | | CD | | Link Service | | Event Bus |
+--+--+ +--+--+ +--+--+ +-------+------+ +-----+-----+
| | | #1 (send change merged event) | |
+----------------------------------------------------------------------------------------------------------------------------->|
| | | #2 (source change link to start chain) | |
+----------------------------------------------------------------------------------------------------------------------------->|
| | | | #3 (proxy link #1) |
| | | |<-----------------------|
| | | #4 (receive change merged event) | |
| |<----------------------------------------------------------------------------------------------------------------+
| | | #5 (send pipeline queued event) | |
| +---------------------------------------------------------------------------------------------------------------->|
| | | #6 (source change links connecting #1 -> #5) | |
| +---------------------------------------------------------------------------------------------------------------->|
| | | | #7 (proxy link #6) |
| | | |<-----------------------|
| | | #8 (send pipeline started event) | |
| +---------------------------------------------------------------------------------------------------------------->|
| | | #9 (pipeline queued links connecting #5 -> #8) | |
| +---------------------------------------------------------------------------------------------------------------->|
| | | | #10 (proxy link #9) |
| | | |<-----------------------|
| | | #11 (send build queued event) | |
| +---------------------------------------------------------------------------------------------------------------->|
| | | #12 (pipeline started links connecting #6 -> #9) | |
| +---------------------------------------------------------------------------------------------------------------->|
| | | | #13 (proxy link #12) |
| | | |<-----------------------|
| | | #14 (send build started event) | |
| +---------------------------------------------------------------------------------------------------------------->|
| | | #15 (build queued event links connecting #11 -> #14) | |
| +---------------------------------------------------------------------------------------------------------------->|
| | | | #17 (proxy link #16) |
| | | |<-----------------------|
| | | #18 (send build completed event) | |
| +---------------------------------------------------------------------------------------------------------------->|
| | | #19 (build started event links connecting #14 -> #18) | |
| +---------------------------------------------------------------------------------------------------------------->|
| | | | #20 (proxy link #15) |
| | | |<-----------------------|
| | | #21 (send pipeline finished event) | |
| +---------------------------------------------------------------------------------------------------------------->|
| | | #22 (build completed event links connecting #18 -> #21) | |
| +---------------------------------------------------------------------------------------------------------------->|
| | | | #23 (proxy link #22) |
| | | |<-----------------------|
| | | #24 (receive pipeline finished event) | |
| | |<---------------------------------------------------------------------------------------------------+
| | | #25 (send pipeline queued event) | |
| | +--------------------------------------------------------------------------------------------------->|
| | | #26 (pipeline finished event links connecting #21 -> #25) | |
| | +--------------------------------------------------------------------------------------------------->|
| | | | #27 (proxy link #26) |
| | | |<-----------------------|
| | | #28 (send pipeline started event) | |
| | +--------------------------------------------------------------------------------------------------->|
| | | #29 (pipeline queued links connecting #25 -> #28) | |
| | +--------------------------------------------------------------------------------------------------->|
| | | | #30 (proxy link #29) |
| | | |<-----------------------|
| | | #31 (send environment created event) | |
| | +--------------------------------------------------------------------------------------------------->|
| | | #32 (pipeline started links connecting #28 -> #31) | |
| | +--------------------------------------------------------------------------------------------------->|
| | | | #33 (proxy link #32) |
| | | |<-----------------------|
| | | #34 (environment created end link for #31) | |
| | +--------------------------------------------------------------------------------------------------->|
| | | | #32 (proxy link #31) |
| | | |<-----------------------|
This example shows how these different types would be used in a CI/CD setting, but is not the only architecture.
Payloads
This section will describe the first few sequences in the sequence diagram to help explain the overall flow using payloads from CDEvents.
- This is our very first event to the start of our CI/CD chain. This event would have been sent from some source management tool like Github, Gitlabs, etc.
{
"context": {
"specversion": "0.6.0-draft",
"chainId": "d0be0005-cca7-4175-8fe3-f64d2f27bc01",
"id": "38a09112-a1ab-4c26-94c4-edfc234ef631",
"source": "/event/source/123",
"type": "dev.cdevents.change.merged.0.1.2",
"timestamp": "2023-03-20T14:27:05.315384Z"
},
"subject": {
"id": "mySubject123",
"source": "/event/source/123",
"type": "change",
"content": {
"repository": {
"id": "cdevents/service",
"source": "https://github.com/cdevents/service/pull/1234"
}
}
}
}
Something to call out here is that the chainId may have been null, for
whatever reason, prior to this event. This means any parents to this event did
not generate a chainId. When an event is sent, it is important that the
sender generates this id.
- We send the start link to let the links service know that we are creating a new chain.
{
"chainId": "d0be0005-cca7-4175-8fe3-f64d2f27bc01",
"linkType": "START",
"timestamp": "2023-03-20T14:27:05.315384Z",
"start": {
"contextId": "38a09112-a1ab-4c26-94c4-edfc234ef631" # context.id of #1
}
}
-
Event bus proxies the link payload from
#2to the links service. -
Shows some consumer consuming
#1to do some action. -
The CI system will queue a pipeline execution, and will generates a
context.idto be sent
{
"context": {
"specversion": "0.6.0-draft",
"chainId": "d0be0005-cca7-4175-8fe3-f64d2f27bc01",
"id": "AA6945F8-B0F1-48DD-B658-25ACF95BD2F5",
"source": "/event/source/123",
"type": "dev.cdevents.pipelinerun.queued.0.1.1",
"timestamp": "2023-03-20T14:27:05.315384Z"
},
"subject": {
"id": "mySubject123",
"source": "/event/source/123",
"content": {
"pipelineName": "myPipeline",
"uri": "https://www.example.com/mySubject123"
}
}
}
- As the send change merge event is sent, the system will follow up with
sending a link associated with the prior event which connects
#1to#5
{
"chainId": "d0be0005-cca7-4175-8fe3-f64d2f27bc01",
"linkType": "PATH",
"timestamp": "2023-03-20T14:27:05.315384Z",
"from": {
"contextId": "38a09112-a1ab-4c26-94c4-edfc234ef631" # context.id of #1
},
"to": {
"contextId": "aa6945f8-b0f1-48dd-b658-25acf95bd2f5" # context.id of #5
},
"tags": {
"ci.environment": "prod"
}
}
-
The event bus will then forward link
#6to the links service -
Paylods from
8-33are very similar to all prior payloads shown here, but the last sequence is the ending link.
{
"chainId": "d0be0005-cca7-4175-8fe3-f64d2f27bc01",
"linkType": "END",
"timestamp": "2023-03-20T14:27:05.315384Z",
"end": {
"contextId": "7d5e011f-5073-44a7-b4f0-86dd7d4c2c7f" # context.id of #31
}
}
Link Types
This section will describe the four different linkTypes: START, END, PATH, and
RELATION.
First is the common link fields shared between all links
| Name | Description |
|---|---|
| chainId | This represents the full life cycles of a series of events in CDEvents |
| linkType | An enum that represents the type of link, e.g. 'START', 'END', 'PATH', 'RELATION' |
| timestamp | The timestamp of when the link was created. This field is omitted when embedding links in the CDEvent context |
| tags | Custom metadata that an individual link can have. It is important to note values and keys can only be strings |
Start Link
Start links are used to indicate that a new chain has been started, and is a
special type of PATH link. The reasoning for having a separate link type for
both START and END is that it allows for clear indication of starting and
stopping a chain. If we relied only on PATH link types to indicate either of
these states, then consumers may not be able to distinguish the two different
states. This makes it very clear and easy for consuming systems.
| Name | Description |
|---|---|
| start | An node object that describes the event that is associated with starting the chain |
{
"chainId": "97ef590e-0285-45ad-98bb-9660ffaa567e",
"linkType": "START",
"timestamp": "2023-03-20T14:27:05.315384Z",
"start": {
"contextId": "a721d6ba-bbd6-4737-9274-5ddd2526b92f"
},
"tags": {
"ci.environment": "prod"
}
}
End Link
End links are used to indicate that a new chain has completed, and are a
special type of PATH link.
| Name | Description |
|---|---|
| from | Where the link is coming from. This field is omitted when embedded this link type. |
| end | An node object that describes the event that is associated with ending the chain |
{
"chainId": "97ef590e-0285-45ad-98bb-9660ffaa567e",
"linkType": "END",
"timestamp": "2023-03-20T14:27:05.315384Z",
"from": {
"contextId": "bf9d3c52-1c12-4029-a8d6-e4aca6c69127"
},
"end": {
"contextId": "bf9d3c52-1c12-4029-a8d6-e4aca6c69127"
},
"tags": {
"ci.environment": "prod"
}
}
Path Link
A path link is used to indicate a path that a request has taken, which may be from system to system or could describe a path within a system like tests.
| Name | Description |
|---|---|
| from | Where the link is coming from. This field is omitted when embedded this link type. |
| to | Where the link is going to |
{
"chainId": "97ef590e-0285-45ad-98bb-9660ffaa567e",
"linkType": "PATH",
"timestamp": "2023-03-20T14:27:05.315384Z",
"from": {
"contextId": "f27e36a4-5c78-43c0-840a-52524dfeed03"
},
"to": {
"contextId": "f004290e-5e45-45f4-b97a-fa82499f534c"
},
"tags": {
"ci.environment": "prod"
}
}
Relation Link
Relation links are used to add some context to certain events
| Name | Description |
|---|---|
| linkKind | A stringed value representing any sort of relationship the link has to the event |
| source | The entity from which the linkKind is applied to. This field is omitted when embedding this link type. |
| target | An event that will be associated with the source |
{
"chainId": "97ef590e-0285-45ad-98bb-9660ffaa567e",
"linkType": "RELATION",
"linkKind": "ARTIFACT",
"timestamp": "2023-03-20T14:27:05.315384Z",
"source": {
"contextId": "5668c352-dd9d-4dee-b334-384e4661d21b"
},
"target": {
"contextId": "3579a5aa-ef46-4ee8-95db-0540298835de"
},
"tags": {
"ci.environment": "prod"
}
}
Cross-Domain Linking with domainId
The Problem
contextId requires the publisher to know the parent event's context ID.
If the parent is not a CDEvent, there is no context ID to reference.
For example, GitHub does not emit CDEvents today. A build event triggered by a
GitHub PR cannot use contextId to link back to that PR — there is no CDEvent
context ID to know. Without domainId, the only option is to bury that
causality in customData, where it is unstructured and non-queryable.
domainId provides a first-class way to express causality and relationships
across system boundaries, regardless of whether the referenced system emits
CDEvents.
Event-to-Event vs Event-to-Resource Linking
contextId and domainId represent two fundamentally different linking models.
contextId is event-to-event: it points to a single, specific CDEvent by
its unique context ID. One event references exactly one other event.
domainId is many-to-many: a domainId URN acts as a container. Many
CDEvents can reference the same external resource, and a single CDEvent can
reference many external resources. There is no requirement that any CDEvent
know about the others referencing the same domainId.
contextId — event-to-event (1:1)
+--------------------+ contextId +--------------------+
| CDEvent |<----- "abc-123" ------| CDEvent |
| id: "abc-123" | | build.started |
| change.merged | | links: [{ |
+--------------------+ | target: { |
| contextId: |
| "abc-123" |
| } |
| }] |
+--------------------+
domainId — many-to-many (N events, M resources)
Many events referencing one resource (domainId as container/grouping key):
cdevents:v0:github::xibz:cdevents-spec:pr:42
(external resource — container)
^
|
+--------------------+--------------------+
| | |
+----------+-------+ +---------+---------+ +------+-----------+
| CDEvent | | CDEvent | | CDEvent |
| build.started | | testrun.started | | service.deployed |
| domainId: | | domainId: | | domainId: |
| cdevents:v0: | | cdevents:v0: | | cdevents:v0: |
| github::xibz:... | | github::xibz:... | | github::xibz:... |
+------------------+ +-------------------+ +------------------+
One event referencing many resources (fan-out):
+----------------------------------+
| CDEvent |
| service.deployed |
| links: [ +-----> cdevents:v0:github::xibz:cdevents-spec:pr:42
| { domainId: |
| cdevents:v0:github::xibz:... }, +-----> cdevents:v0:jira::xibz:cdevents-project:issue:12
| { domainId: |
| cdevents:v0:jira::xibz:... }, +-----> cdevents:v0:circleci::xibz:my-pipeline:execution:789
| { domainId: |
| cdevents:v0:circleci::xibz:... }|
| ] |
+----------------------------------+
A consumer querying by cdevents:v0:github::xibz:cdevents-spec:pr:42 gets back every CDEvent
that referenced that resource — build, test, deploy — without any single event
needing to know about the others. A single event can simultaneously express
causality across multiple external systems by listing multiple domainId links,
covering fan-out scenarios where one action triggers work across several systems.
CDEvents Domain IDs
domainId values are URIs following this format:
The cdevents: scheme prefix was chosen deliberately. The original design used a URN (urn:), but URN namespaces require formal registration — for example, urn:github:... would need to be registered by GitHub, and urn:jira:... by Atlassian. Using urn:cdevents:... for everything would raise the question of why a URN is needed at all if CDEvents is acting as the sole authority. A URI with a cdevents: scheme avoids these registry obligations while remaining a valid, parseable identifier under RFC 3986.
cdevents:v0:<provider>:<provider-id>:<namespace>:<groups>:<type>:<resource id>
All segments MUST be present. If a segment has no value, it MUST be left empty rather than omitted (e.g. cdevents:v0:github::xibz:...). Omitting a segment shifts all following segments and introduces ambiguity.
Only type and resource id are required to be non-empty. All other segments are contextual — they narrow the identity of the resource but MAY be left empty when the information is not known or not applicable (e.g. an artifact whose provider registry is unknown).
Segment values MUST reflect the exact canonical form as the value appears in the provider's system, before encoding is applied. When URL-encoding is required, percent-encoded hex digits MUST be uppercase (e.g. %2F not %2f).
| Segment | Required | Description |
|---|---|---|
version | yes | The version of the domainId URI format (currently v0). This allows the format to evolve without breaking existing consumers. |
provider | no | A governed identifier for the tool or system type that owns the resource. When present, MUST match a known provider defined in PROVIDERS.md. Free-form values are not permitted — without a governed list, the same tool would be referenced as gh, github, GitHub, etc., making cross-system queries unreliable. |
provider-id | no | Identifies who owns and where a specific instance of the provider is running. This is entirely company or org-defined — CDEvents does not govern, assign, or interpret this value. It exists solely to distinguish between two instances of the same tool owned or operated by different teams or organizations (e.g. org A's internal GitHub Enterprise vs org B's). Producers and consumers within an organization MUST agree on the value out of band. MUST be URL-encoded when present. |
namespace | no | The top-level organizational unit within the provider's data model (e.g., a GitHub org, a Jira workspace). Producer-defined; not governed by CDEvents. MUST be URL-encoded when present. |
groups | no | One or more organizational subdivisions within the namespace, separated by / to express nesting as defined by the provider's data model. For example, a GitHub repository is a single group (my-repo); a GitLab project nested under subgroups would be group/subgroup/project. The / separator is structural and MUST NOT be encoded. Individual group segment values MUST be URL-encoded. Leading and trailing / are invalid. Producer-defined; not governed by CDEvents. |
type | yes | A governed resource type. MUST be one of the values defined in Common Resource Types |
resource id | yes | The publicly exposed identifier that end users see for this resource (e.g. a PR number, commit SHA, ticket number, or PURL). MUST be URL-encoded. MUST NOT be an internal or opaque system-generated ID. |
Examples:
- GitHub PR (no provider-id):
cdevents:v0:github::xibz:cdevents-spec:pr:42 - GitHub PR (org A's internal GitHub Enterprise):
cdevents:v0:github:org-a-github:xibz:cdevents-spec:pr:42 - GitHub PR (org B's internal GitHub Enterprise):
cdevents:v0:github:org-b-github:xibz:cdevents-spec:pr:42 - Jira ticket:
cdevents:v0:jira::xibz:cdevents-project:issue:12345 - Datadog alert:
cdevents:v0:datadog::prod:api-monitors:alert:98765 - GitLab MR (nested groups):
cdevents:v0:gitlab::my-company:platform/backend:mr:99
Provider Validation
The provider segment MUST match a known provider. CDEvents ships a default governed list of commonly used providers in PROVIDERS.md, which serves as the baseline for validation.
SDKs MUST support extending the provider list at initialization time, allowing organizations to register additional providers beyond the CDEvents default. Validation MUST check against the union of the CDEvents default list and any organization-supplied providers.
When a provider value is not found in either list, the SDK SHOULD emit a warning. Whether to reject or allow the value SHOULD be configurable by the caller.
Common Resource Types
The type segment is a governed field. Producers MUST use one of the following
values. This ensures interoperability — consumers can match and query by type
without handling variations like pull_request, PR, or pullrequest.
Most types are concrete and singular — pr, commit, issue, branch, tag,
artifact, environment, alert, and execution each represent a single,
unambiguous concept. If subtypes are added to these in the future (e.g.
execution.stage), the bare type retains its original meaning and existing URNs
remain valid — there is no migration cost.
definition is treated differently. A bare definition could mean "a workflow
definition" today, but if definition.policy or definition.infrastructure were
added later, bare definition would become underspecified — consumers could no
longer tell which kind of definition was intended without provider knowledge. To
avoid this migration problem, definition is an abstract parent type that MUST
NOT be used directly. Producers MUST always use a concrete subtype such as
definition.workflow. New subtypes SHOULD follow the definition.<kind> naming
convention and be proposed for addition to this list.
execution specifically refers to a top-level run — a pipeline execution, workflow
run, or build. Sub-level concepts such as stages, jobs, and steps are either
covered by CDEvents events via contextId (if the system emits CDEvents) or are
out of scope for domainId.
| Type | Description | resource id example |
|---|---|---|
pr | A pull or merge request | 42 (PR number) |
commit | A source code commit | abc123def456 (commit SHA) |
issue | An issue or ticket in a tracking system | 1234 (issue number) |
branch | A source code branch | main, feature/my-branch |
tag | A source code tag or release | v1.2.3 |
definition | Abstract parent. MUST NOT be used directly — use a concrete subtype. | — |
definition.workflow | An automation or pipeline definition — any template that describes a sequence of steps to be executed (e.g. a GitHub Actions workflow file, a CircleCI pipeline config, a Jenkinsfile). | .github%2Fworkflows%2Fci.yml |
execution | A top-level run of a pipeline, workflow, or build. Always refers to the top-level execution — stages, jobs, and steps are out of scope. | 789 (run number) |
artifact | A build artifact (binary, container image, package, etc.). The resource id SHOULD be a PURL, URL-encoded. | pkg%3Adocker%2Fmyapp%401.0.0 |
environment | A target deployment environment | production, staging |
alert | A monitoring or observability alert | 98765 (alert ID) |
If a resource does not fit any of the above types, it SHOULD be proposed for addition to this list before using a custom value.
Usage in Relation Links
domainId can be used in place of contextId in the source and target
fields of a RELATION link. Both embedded and standalone relation links support
this.
Example: Build triggered by a GitHub PR
{
"context": {
"id": "build-event-789",
"chainId": "d0be0005-cca7-4175-8fe3-f64d2f27bc01"
},
"links": [
{
"linkType": "RELATION",
"linkKind": "triggeredBy",
"target": {
"domainId": "cdevents:v0:github::xibz:cdevents-spec:pr:42"
}
}
]
}
Example: Rollback pipeline triggered by a Datadog alert
{
"links": [
{
"linkType": "RELATION",
"linkKind": "triggeredBy",
"target": {
"domainId": "cdevents:v0:datadog::prod:api-monitors:alert:98765"
}
}
]
}
Example: Deployment failure with full cross-domain causality
A deployment failure can link back to its causes across multiple systems, without requiring any system to know another system's internal context IDs:
{
"context": { "id": "deploy-event-999" },
"links": [
{
"linkType": "RELATION",
"linkKind": "causedBy",
"target": {
"domainId": "cdevents:v0:circleci::xibz:my-pipeline:execution:789"
}
},
{
"linkType": "RELATION",
"linkKind": "causedBy",
"target": {
"domainId": "cdevents:v0:github::xibz:cdevents-spec:commit:abc123def456"
}
},
{
"linkType": "RELATION",
"linkKind": "causedBy",
"target": {
"domainId": "cdevents:v0:github::xibz:cdevents-spec:pr:42"
}
}
]
}
Consumers can query directly by domainId URN without parsing customData or
needing to know the context IDs of external systems.
When to Use contextId vs domainId
| Scenario | Use |
|---|---|
| Linking to another CDEvent whose context ID is known | contextId |
| Linking to a system that does not emit CDEvents | domainId |
| Linking to a CDEvent but context ID is not available | domainId as a fallback |
Each system uses what it knows: contextId for events within the CDEvents
ecosystem, and domainId URNs for anything outside it.
Scalability
Scalability is one of the bigger goals in this proposal and we wanted to ensure fast lookups. This section is going to describe how the proposed links format will be scalable and also provide tactics on how DB read/writes can be done.
The purpose of the chain ID is to ensure very fast lookups no matter the database. Without a chain ID the database or its client would need to recursively follow event references, upstream or downstream depending on the use case. A graph DB would easily provide that, and it is also possible to implement on top of SQL like DBs and document DBs, but it will never be as fast as querying for a chain ID.
Instead a link service that processes and stores the links to some DB is much preferred as it gives companies and developers options to choose from. When using an SQL database, the chain ID could be the secondary key to easily retrieve indexed entities. Links could be easily sorted by timestamps which should roughly coordinate to their linked neighbors, parent and child.
CDEvents that are to be ingested by some service would also have to worry about the number of events returned. This problem is mitigated in that only the immediate parent(s) links are returned, and any higher ancestry are excluded. If some service needs to get access to a higher (a parent's parent) they would need to use the links API to retrieve them.