Skip to content

feat(spec,middleware): CloudEvents binary content mode - #110

Open
ucekmez wants to merge 1 commit into
feat/canonical-api-descriptionsfrom
feat/cloudevents-binary-content-mode
Open

feat(spec,middleware): CloudEvents binary content mode#110
ucekmez wants to merge 1 commit into
feat/canonical-api-descriptionsfrom
feat/cloudevents-binary-content-mode

Conversation

@ucekmez

@ucekmez ucekmez commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

PR 18 of a stacked series. Base is #109. Not for merge without review.

EEP delivered only in CloudEvents structured mode: the whole envelope as one JSON document. Binary mode — context attributes as protocol metadata, body carrying only data — was not offered, so every subscriber parsed a full envelope to decide whether it wanted the payload.

First, a correction to my own audit

I reported this as EEP's largest wire optimisation at "62% smaller frames". That number was computed by dropping source, time, specversion and datacontenttype rather than relocating them — not a like-for-like comparison. Re-derived honestly against the spec's own §4.2 example:

bytes vs structured
SSE structured (today) 354
SSE binary, lossless 286 19% smaller
Webhook structured 315
Webhook binary (HTTP/1.1) 274 13% smaller
Webhook body alone 283 → 56 80% smaller

So: 19% and 13%, not 62%. Still worth having — the body shrinks ~80%, HTTP/2 HPACK indexes the ce-* names and non-varying values across deliveries on a connection, and a subscriber can route on headers without parsing the body — but it is not the headline optimisation I claimed.

§5.2.1 says this plainly and points publishers optimising bytes at conditional requests (#101), compression (#101) and subscription filters (#107) first: those remove whole responses and whole deliveries rather than shrinking them. I'd rather the spec be honest about the ordering than oversell a mode.

This also resolves the CloudEvents naming problem

CloudEvents excludes the underscore from context attribute names, so ce-eep_version is not a legal header — the divergence flagged in #96 and #100. §5.2.1 resolves it for the binary path: underscores are removed (eep_versionce-eepversion). Structured mode keeps the underscored spelling that every deployed implementation emits, so nothing breaks. Whether to rename in structured mode too stays open for v1.0.

What changed

  • delivery_format gains cloudevents/v1.0-binary; structured remains the default.
  • SSE binary mode relocates only the two attributes SSE natively frames (id:, event:) and stops repeating them in the body. SSE has no per-event header channel for the rest, and inventing one would not be CloudEvents.
  • Binary mode relocates attributes and MUST NOT drop them. An object-valued attribute has no header representation, so the renderer keeps it in the body rather than losing it — with a test asserting every attribute survives the mode change.
  • Signing is unchanged: the signature covers the raw body bytes, so a binary-mode subscriber verifies exactly what it received without reassembling an envelope. Tested.

Scope

  • Spec / schema only
  • TypeScript package(s)
  • Python package(s)
  • Tests / CI
  • Docs / examples

Checklist

  • I read CONTRIBUTING.md and CODE_OF_CONDUCT.md.
  • Tests added or updated where appropriate.
  • Breaking change? No. Opt-in per subscription; structured stays the default and is byte-identical to before.
  • Documentation updated for user-visible behavior.

Verification

Suite Result
@eep-dev/middleware 250 passed (was 233)
tests/ 199 passed
tests/cross-impl/ 56 passed, 3 skipped
openapi-route-parity
codegen-schema-types --check no drift

Notes for reviewers

The "no attribute is lost" test is the one that matters. Binary mode is a relocation, and the failure mode is silently dropping something that has no header representation. The test iterates every attribute on the event and asserts it is present in either a header or the body.

SSE binary mode is specified but not implemented — the middleware's SSE handler emits a single connection-hint frame rather than a real event stream, so there is nothing to render events through yet. The spec text is what an implementor needs.

No Python middleware parity — flagging rather than skipping silently.

EEP delivered only in CloudEvents structured mode: the whole envelope as
one JSON document. Binary mode — where context attributes travel as
protocol metadata and the body carries only `data` — was not offered,
so every subscriber parsed a full envelope to decide whether it wanted
the payload.

Honest accounting, because the audit that raised this overstated it.
Measured against the §4.2 example, binary mode is ~19% smaller per SSE
frame and ~13% per webhook over HTTP/1.1. The earlier "62%" figure was
computed by dropping `source`, `time`, `specversion` and
`datacontenttype` rather than relocating them, which is not a
like-for-like comparison. The body alone shrinks ~80%, and under HTTP/2
the `ce-*` header names and the attributes that do not vary between
deliveries are indexed by HPACK, so the saving grows with sustained
traffic. The more reliable benefit is that a subscriber can route and
filter on headers without parsing the body at all.

§5.2.1 says so plainly, and points publishers optimising bytes at
conditional requests (§3.2.1), compression (§3.2.2) and subscription
filters (§5.1.3) first — those remove whole responses and whole
deliveries rather than shrinking them.

This also resolves, for the binary path, the CloudEvents attribute
naming divergence recorded in §7: CloudEvents excludes the underscore
from context attribute names, so `ce-eep_version` is not a legal header.
In binary mode the underscores are removed (`eep_version` →
`ce-eepversion`). Structured mode keeps the underscored spelling that
every deployed implementation emits, so nothing breaks.

- `delivery_format` gains `cloudevents/v1.0-binary`; structured stays
  the default.
- SSE binary mode relocates only the two attributes SSE natively frames
  (`id:`, `event:`) and stops repeating them in the body; SSE has no
  per-event header channel for the rest.
- Binary mode relocates attributes and MUST NOT drop them. An
  object-valued attribute has no header representation, so the renderer
  keeps it in the body rather than losing it — with a test asserting
  every attribute survives the mode change.
- Signing is unchanged: the signature covers the raw body bytes, so a
  binary-mode subscriber verifies exactly what it received without
  reassembling an envelope.

Refs: EEP audit 2026-08 finding O1
Signed-off-by: Ugur Cekmez <ucekmez@gmail.com>
Copilot AI lite review requested due to automatic review settings August 26, 2026 19:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

2 participants