Skip to content

docs(cdrus): RFC for CDrus Expressions and Workflow schema - #326

Open
sol-duara wants to merge 1 commit into
cdevents:mainfrom
Sol-Duara-Inc:feat/cdrus-expressions-rfc
Open

docs(cdrus): RFC for CDrus Expressions and Workflow schema#326
sol-duara wants to merge 1 commit into
cdevents:mainfrom
Sol-Duara-Inc:feat/cdrus-expressions-rfc

Conversation

@sol-duara

Copy link
Copy Markdown

Changes

Adds a draft RFC and two JSON Schemas defining a declarative grammar for SDLC workflow intent on top of CDEvents, in response to #253 (Enable CI/CD Interoperability Through SDLC Workflow Segments).

File What it is
cdrus/README.md The RFC, normative prose
cdrus/expression.schema.json CDrus Expression schema, a unit of intent
cdrus/workflow.schema.json CDrus Workflow schema, tool binding

This is submitted for comment rather than as a finished specification, per §1 of the RFC.

The problem, and how this addresses it

#253 stated it precisely. CDEvents standardizes the vocabulary of what happened; no shared structure exists for what was intended. Without that layer, orchestrators and policy engines encode workflow semantics imperatively, in code or in tool-specific configuration, which is the coupling CDEvents otherwise removes.

#253 asked for three things. All three are addressed:

Propagate what each tool's role in the workflow is, not just what it does. A CDrus Expression declares which CDEvents fulfill an intent such as build, deploy, or verify, independent of any tool. The declaration is the role.

Validate that a tool fulfilled its intended role. An Expression resolves to a determinate tree of expected events (§6.2), so the expectation is expressed in a form that can be checked. Which checking process a consumer runs against it, and how strictly, is the consumer's choice. The RFC is on the emitter side of that boundary: it delivers the verifiable declaration, not a mandated verification procedure.

Port workflows between systems without rewriting automation. Tool binding lives in the Workflow layer, never in the Expression. Swapping Jenkins for Tekton in a pipeline changes one line of one Workflow, and the Expression graph is untouched.

The three questions raised in the #253 thread

Naming and tool binding, raised by @thompson-tomo. The suggestion was to split tool definitions from workflow definitions. The RFC realizes that intuition as a layer split rather than a document split: Expressions carry intent and name no tool, while Workflows carry tool, source, pipeline, and per-event overrides (§5.5). The overrides map is what lets a single composite bind build.* to one tool and deployment.* to another without touching the Expression.

Producer versus consumer compliance, raised by @afrittoli. The observation was that tools producing events cannot always be made compliant, because pipeline definitions are end-user defined, while tools consuming events can reason in segment terms. The RFC adopts that distinction as its architecture (§8.2). Producers emit whatever their native pipeline yields. The Workflow binds a producer's tool identity to each event. Compliance lives on the consumer side, binding lives in the Workflow, and intent lives in the Expression. Requiring producer-side compliance would have demanded behavioral change from every CI/CD tool, which would defeat the goal of #253.

Hosting and governance, raised by @afrittoli. Deferred to the maintainers. See Placement below.

Deviations from #253, and the case for each

segment becomes expression. The RFC gives three reasons (§4.2). A segment implies a piece of a larger pre-existing whole, while the construct here is generative and evaluates to a sequence of events. Segments also do not compose with segments without strain, whereas expressions compose with expressions, and the schema relies on that composition throughout. Migration is a keyword substitution plus the identity tuple: §4.2 preserves the semantic content of the original proposal intact.

Identity replaces implicit versioning. Expressions carry no version. They are addressed by the tuple (group, author, expression), and the change rules are asymmetric (§4.4): adding events to produces never changes identity, while removing events changes identity only when the removal leaves a name hint unsatisfied. A build Expression therefore always contains what build promises, without a version bump every time a step is added.

Identity is a consistent addressing key rather than a verified credential (§4.4, §9.2). All resolution requires is that the tuple be consistent and unique. Whether group and author correspond to real principals is not the language's concern, which is what lets a deployment back the pair with whatever it already has: native RBAC, enterprise Active Directory or LDAP, or a stub for testing. Who may publish under an identity is governance the deployment provides, and the language neither enforces nor depends on it.

required_fields moves to the CDEvent schemas. #253 carried per-segment required fields such as build_system, change_hash, and outcome. The RFC places field requirements on the CDEvent schemas instead (§8.1), so a field is specified once, in the schema that defines the event, rather than in two places that can disagree.

Name hints constrain the declaration. CDEvent subjects are reserved keywords in the expression token (§4.1.1). A subject appearing as a complete token requires the corresponding events in produces: both the beginning and ending event for a subject with a clear begin and end pair, or at least one event otherwise. Hints are a floor and not a ceiling, so a hinted Expression may declare more than its name mentions. This is what makes the asymmetric identity rules above coherent.

Mechanisms added beyond #253

Depth-first nested production (§4.6). A nested produces declares that children were caused by the parent, not merely that they follow it in time. All nested events share the parent's chainId.

Spawned chains in two forms (§4.7, §4.8). spawn declares Blocking chains that the spawning chain waits on. detach declares Detached chains that it does not. Each spawned chain receives its own chainId and a RELATION link of kind TRIGGER back to the spawning event. The two are structurally symmetric and differ in a single bit, whether the spawning chain's completion is gated on them.

Both are built from CDEvents primitives that already exist, so no core change is required. Together they express two cases #253 could not: parallel work that must complete before the parent finishes, such as two test cases under one suite, and downstream work that must be declared so consumers can track it but must not gate completion, such as a notification pipeline. A downstream chain that is never declared is tied to its parent by no declared relation and claimed by no expectation.

A Workflow layer with defaults and per-event overrides (§5). Workflow-wide defaults remove repetition, expression-level bindings apply to a whole resolved Expression, and overrides bind individual event types.

Named event anchors (§4.9). An optional as: label on an event is pure intent metadata, with no effect on production, ordering, versioning, or chain assignment. A reference resolves to the list of every event carrying that anchor, so the behavior is uniform whether an anchor names one event or several.

Scope

The RFC is explicit about its own boundaries. Execution is orthogonal to it (§2.3): Workflows supply the bindings a downstream engine needs, and how that engine works is not specified here. The Expression store is out of scope as an implementation choice (§4.4). How extended event types are created is out of scope (§6.1). Tool discovery is not in scope (§8.3). Who may publish under an identity, and the integrity of stored content, are deployment governance rather than language requirements (§9.2).

Placement

@afrittoli asked whether this belongs in a new repository under the CDEvents org or as a subspec of spec. That decision is left to the maintainers.

For this PR the files sit in a self-contained cdrus/ directory, mirroring how custom/ holds the custom-events subspec. The directory was chosen so that nothing else moves. tools/validate.js globs schemas/*.json and treats every match as an event schema, so placing these files there would have added them to the event-schema count and compiled them as events. Under cdrus/ they are inert to the existing tooling, and the whole directory can be relocated, or lifted into its own repository, without changing a line of content.

Schema $id values use cdrus.dev as an example namespace. Final assignment is a governance decision, and reassignment is a non-breaking structural change (§10.2).

Verification

  • Both schemas compile under the repository's own AJV configuration, Ajv2020 with validateFormats: true.
  • Every complete YAML example in the RFC, 11 documents, validates against the schema it claims to conform to. The twelfth block is the §5.2 skeleton carrying <placeholder> values and is illustrative.
  • The structural rules the prose asserts were tested against the schemas rather than assumed: expression references reject produces, spawn, and detach; spawn and detach each accept the flat and nested forms and reject the two mixed; the top level rejects a bare spawned chain; as: and identity tokens enforce their charset.
  • tools/validate.js reports 54/54 schemas and 50/50 examples, identical to this branch's baseline before the change.
  • The schemas were authored in JSON Schema draft-07 and are converted here to 2020-12 to match the dialect of the CDEvents event schemas. This was required rather than cosmetic, because tools/validate.js builds an Ajv2020 instance, which carries no draft-07 metaschema and fails to compile a draft-07 document. The conversion was verified to preserve every enforcement listed above.

Submitter Checklist

As the author of this PR, please check off the items in this checklist:

  • Has the primer doc been updated if a design decision is involved. Not yet: this is an RFC for comment, and the primer is the right place to describe the relationship between the event vocabulary and the intent grammar if the SIG adopts the layer
  • Have the JSON schemas been updated if the specification changed. Two new schemas are added under cdrus/; no existing event schema is modified
  • Has spec version and event versions been updated according to the versioning policy. No CDEvents event or spec version changes; the RFC adds no requirements to the core specification
  • Meets the CDEvents contributor standards

Adds a draft RFC and two JSON Schemas proposing a declarative grammar for
SDLC workflow intent on top of CDEvents, in response to the Workflow
Segments proposal in cdevents#253.

CDEvents standardizes the vocabulary of what happened. cdevents#253 identified
the missing layer: no shared structure for what was intended. This RFC
proposes two artifacts for that layer.

CDrus Expressions are identity-bound, composable units of intent that
declare which CDEvents fulfill them. Each is addressed by the tuple
(group, author, expression), which replaces implicit versioning with
ownership plus a stable addressing key.

CDrus Workflows are concrete documents that reference Expressions by
identity and bind them to tools, sources and per-event content. The
separation is deliberate: Expressions stay tool-agnostic, Workflows
carry tool binding. That answers the "split tools from workflow"
suggestion in the cdevents#253 thread, and the observation that producers cannot
be made compliant with segments while consumers can reason in their
terms - compliance lives on the consumer side, binding in the Workflow,
intent in the Expression.

Beyond cdevents#253 the RFC contributes depth-first nested production, which
expresses causal structure within a chain, and spawned chains in two
forms: Blocking (spawn, awaited) and Detached (detach, fire-and-forget),
each on its own chainId with a RELATION link of kind TRIGGER back to the
spawning event. Both build on CDEvents primitives that already exist.

The schemas use JSON Schema 2020-12, matching the dialect of the
CDEvents event schemas, and live in a self-contained cdrus/ directory so
they are not picked up by the schemas/ event-schema glob. Placement
remains the open governance question raised in cdevents#253 and is deferred to
the maintainers.

Refs cdevents#253

Signed-off-by: Dadisi Sanyika <dadisi@solduara.com>
@xibz

xibz commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Thanks for the depth here. The verification section alone (11/12 examples validating against claimed schemas, 54/54 existing schemas unregressed) is more rigor than most spec PRs get, and the identity model (unversioned tuple + asymmetric add/remove rules on produces) is a clean way to avoid version churn.

Why I'm asking for a split: CDEvents' stated purpose is a shared vocabulary that stays agnostic about how something happened, so any tool can emit or consume it, "the flexibility to incorporate whatever tooling best suits your specific requirements," per the project's own framing. expression.schema.json stays inside that: it composes and names CDEvents that already exist (subjects, chainId, RELATION/TRIGGER via spawn/detach), without saying who emits them or how long to wait. workflow.schema.json crosses that line: tool, pipeline, timeout_ms, min_wait_ms bind intent to a specific runner with specific timing, that's orchestration-engine configuration, not shared vocabulary. The RFC's own 8.2 draws this same distinction (compliance and binding live on the consumer side, not the producer/vocabulary side); I'm applying that line to where the files themselves should live, not just to producer behavior.

My suggestions:

  1. Split this PR into two separate PRs.
  2. PR A: cdrus/expression.schema.json and the corresponding README sections. No changes requested to this file's content.
  3. PR B: cdrus/workflow.schema.json and its README sections. This should go to a separate repository, not cdevents/spec. Do not merge it here.

The rule, so it's checkable rather than a matter of opinion: anything that tells a consumer which physical tool or pipeline must emit or is bound to an event, or how long to wait for it, is out of scope for this repo. Anything that names or composes CDEvents that already exist, without specifying who emits them or how long to wait, is in scope.

Applying it: group, author, expression, produces, spawn, detach (in expression.schema.json) bind to none of that. in scope, unchanged. tool, source, pipeline, timeout_ms, min_wait_ms, defaults, overrides (in workflow.schema.json) bind to all of it, out of scope here.

Not part of this ask: spawn/detach are defined identically in both files. I'm not asking you to remove or change them anywhere, they stay exactly as written, in expression.schema.json.

I would love for @afrittoli to chime in, but my first thought is this should be split. The RFC's own "Placement" section already raises new-repo-vs-subspec as an open question for workflow.schema.json, a new repository under the CDEvents org, or Sol Duara, Inc.'s own repo, both work; cdevents/spec does not.

@afrittoli - thoughts?

@afrittoli

Copy link
Copy Markdown
Member

Thanks @sol-duara for this submission and @xibz for your comment.
I'll dedicate some time to reviewing this week 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants