feat(spec,middleware): CloudEvents binary content mode - #110
Open
ucekmez wants to merge 1 commit into
Open
Conversation
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>
This was referenced Aug 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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,specversionanddatacontenttyperather than relocating them — not a like-for-like comparison. Re-derived honestly against the spec's own §4.2 example: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_versionis not a legal header — the divergence flagged in #96 and #100. §5.2.1 resolves it for the binary path: underscores are removed (eep_version→ce-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_formatgainscloudevents/v1.0-binary; structured remains the default.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.Scope
Checklist
Verification
@eep-dev/middlewaretests/tests/cross-impl/openapi-route-paritycodegen-schema-types --checkNotes 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.