feat(spec,middleware): conditional requests and content coding on Layer 1 - #101
Open
ucekmez wants to merge 1 commit into
Open
feat(spec,middleware): conditional requests and content coding on Layer 1#101ucekmez wants to merge 1 commit into
ucekmez wants to merge 1 commit into
Conversation
…er 1 `grep -ri 'etag|if-none-match|cache-control|304'` across SPECIFICATION.md returned zero matches. So did `accept-encoding`. Layer 1 is the *polled* surface of EEP — manifest, entity resolution, gates, services, capabilities — and none of it was conditionally retrievable. An agent tracking a thousand entities re-downloaded every document on every poll, in a protocol whose front-page claim is byte and token efficiency. Manifests are not small: `eep-manifest.json` constrains 24 properties including nested `x402`, `compliance`, `data_residency` and `discovery_hints` objects. `docs/guides/iot-discovery.md` already told agents that "manifests are cached per their `Cache-Control` header" — a header nothing required anyone to send. Spec: - §3.2.1 Conditional requests and caching: `ETag` REQUIRED on Layer 1 GETs, `If-None-Match` honoured with `304`, `Last-Modified` / `If-Modified-Since` SHOULD, `Cache-Control` REQUIRED. A publisher that cannot guarantee byte-stability MUST use a weak validator rather than a strong one it cannot honour. Gated resources are `private` at minimum, and gates are evaluated BEFORE returning `304` so a subscriber whose access was revoked cannot keep revalidating a cached copy. - §3.2.2 Content coding: honour `Accept-Encoding` on Layer 1 (MUST) and SSE (SHOULD). A publisher compressing an event stream MUST flush at every event boundary — otherwise events sit in the compressor's buffer and the stream stops being a stream — and MUST serve uncompressed rather than break delivery latency if it cannot. Middleware: - New `conditional.ts` computing a strong ETag over a canonical (sorted-key) serialisation. Canonicalisation is the load-bearing part: a publisher building its manifest from a map would otherwise emit a fresh ETag per request, so no conditional request would ever hit — worse than not implementing them, because the client pays for the round-trip and still receives a body. - `If-None-Match` uses RFC 9110's weak comparison, so `W/"x"` matches `"x"`. Applied to manifest, entity, gates and services, with `Cache-Control` chosen per resource (`private` for gate config). - Only 2xx responses are made conditional; collapsing an error to 304 would tell a client its cached success is still valid. compliance-cli: probes for ETag presence, 304 on revalidation, ETag stability across requests, Cache-Control, and gzip negotiation. Refs: EEP audit 2026-08 findings O2, O3 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 9 of a stacked series. Base is #100. Not for merge without review.
Layer 1 is the polled surface of EEP — manifest, entity resolution,
/eep/gates,/eep/services, capabilities — and none of it was conditionally retrievable. An agent tracking a thousand entities re-downloaded every document on every poll, in a protocol whose front-page claim is byte and token efficiency.Manifests are not small:
eep-manifest.jsonconstrains 24 properties including nestedx402,compliance,data_residencyanddiscovery_hintsobjects.docs/guides/iot-discovery.mdalready tells agents that "manifests are cached per theirCache-Controlheader" — a header nothing required anyone to send.What changed
Spec — §3.2.1 Conditional requests and caching
ETagREQUIRED on Layer 1 GETs;If-None-Matchhonoured with304;Last-Modified/If-Modified-SinceSHOULD;Cache-ControlREQUIRED.privateat minimum, and gates are evaluated before returning304— otherwise a subscriber whose access was revoked keeps revalidating a cached copy forever.Spec — §3.2.2 Content coding
Accept-Encodingon Layer 1 (MUST) and SSE (SHOULD).Middleware —
conditional.tsIf-None-Matchuses RFC 9110 §8.8.3.2's weak comparison, soW/"x"matches"x". A string equality check would be wrong.Cache-Control(privatefor gate config).304would tell a client its cached success is still valid.compliance-cli — probes for ETag presence, 304 on revalidation, ETag stability across requests,
Cache-Control, and gzip negotiation.Scope
Checklist
ETag/Cache-Controlare unaffected.Verification
@eep-dev/middleware@eep-dev/compliance-clitests/codegen-schema-types --checkNotes for reviewers
A correction to my own earlier claim. In the audit I reported CloudEvents binary content mode as the single largest wire optimisation at "62% smaller frames". That number was computed by dropping
source,time,specversionanddatacontenttyperather than relocating them — it is not a like-for-like comparison. Re-derived honestly against the spec's own §4.2 example:Binary mode is still worth doing — it removes a JSON parse from the subscriber's hot path and its stable
ce-*headers compress well under HTTP/2 HPACK across repeated deliveries — but it is a 14–20% win, not 62%, and it is not the top wire optimisation. Conditional requests are: a304removes the entire body rather than shrinking it. That is why this PR comes first. Binary mode follows separately with the corrected numbers.Python middleware parity is not included here — flagging rather than silently skipping.
Compression is specified but not implemented in the middleware: it is a transport concern that Express/Fastify/Hono/Koa each solve with their own compression middleware, and the adapters return plain objects rather than owning the response stream. The spec text plus the conformance probe is what makes it checkable; forcing a compression implementation into the adapter layer would fight each framework's own.