The rule
Unrecognised material is not an error. A malformed known value is.- An event of a type this implementation does not recognise MUST NOT abort the run.
- A property that is not described on an event MUST NOT abort the run.
- A union member the implementation does not recognise — a content part of a new kind, an outcome named after it shipped — MUST NOT abort the run.
- A field the protocol DOES describe, carrying a value the schema rejects, MUST be fatal: the consumer MUST fail the run rather than repair, coerce or ignore the value.
messageId holding a number is not a newer
protocol; it is a defect, and a consumer that quietly coerces it hides the defect
until it surfaces as something harder to diagnose.
Every transport answers the same way
These rules bind the protocol, not a wire format. A consumer that supports more than one transport MUST reach the same verdict on the same event however it arrived: if a stream survives as JSON, it MUST survive as protobuf, and if it fails, it MUST fail the same way — except where the binary encoding itself cannot express the difference the verdict turns on, the two cases this page names below (the unnameable envelope variant, and the absences protobuf materialises). That puts a limit on what a reader may decide for itself. Turning bytes into events is the reader’s whole job — a frame whose bytes map to no event at all is a decode failure, and that judgement is properly the reader’s. Whether a successfully read event carries something the implementation recognises is not: that question belongs to the one stage every transport shares. A reader that also validates makes its transport strictly harsher than the others, and a producer’s choice of content type then decides whether a run survives. The binary case needs one accommodation. A text reader can hand on an event of an unknown type, because the type travels as a string and the shared stage can drop it by name. A binary reader cannot: an envelope variant it was not compiled against carries no name to hand on. Such a frame MUST be dropped, with a warning, rather than failing the run — that is the same answer the shared stage would have given, spelled for a transport that cannot defer the question.A binary encoding cannot honour this wherever it gives a field no presence.
In protobuf that covers required scalars and required lists alike: a required
string a producer omitted arrives as an empty string, a required array as an
empty array, and each is accepted where the same event as JSON is rejected as
malformed. The schema already forbids omitting them, so this changes how a
defective producer is diagnosed rather than what a conformant one may send.
What a consumer does with unrecognised material
A consumer that exposes events to application code MUST NOT deliver unrecognised material as though the protocol described it. Having read it, such a consumer MUST do one of:- Drop it, if the whole event is of an unrecognised type. The consumer SHOULD emit a warning naming the type.
- Strip it, if it is an unknown property or an unrecognised union member on an otherwise valid event. The consumer SHOULD emit a warning naming the path that was removed.
remove carrying a leftover value is a valid patch and arrives
whole. A consumer that strips there deletes conformant data, and the warning it
emits is about nothing.
This binds the pipeline, not the parser. A library layer that only parses an
event — the generated models in each SDK, say — keeps unknown properties on
purpose, so that the middleware above it can still see them. An SDK that offers
such a parser without an enforcement stage does not yet satisfy this rule; today
that is Python and .NET, whose models parse leniently with no stage that strips
before application code.
One case is not yet met by the reference implementation: a field whose schema
is a closed set of string values — a message
role, for instance — is checked
as a leaf, so an unrecognised value there is fatal rather than stripped. The
.NET converters reject an unknown role or outcome the same way. The rule above
is what the protocol requires; both are gaps against it rather than a softening
of it.- In an optional position, that field is removed and everything around it survives.
- In a required position, the value containing it is removed instead. A media part whose source is of a kind this implementation has never heard of is removed whole — leaving a part behind with no source would hand the validator something it must treat as malformed, turning an addition into a fatal error.
- Inside a list, the unrecognisable element is dropped and the list survives.
Where middleware sits
Middleware — anything a consumer installs to translate, observe or rewrite the stream — MUST run before enforcement, and enforcement MUST run before verification and before application code sees anything.rawEvent, attribution — is carried through intact;
a chunk’s timestamp is not, because the synthesized events are not the
chunk, and like unknown fields it is judged before expansion only on the
canonical order.
A chunk is an event in its own right, and it MUST be judged as one: whichever
stage meets a malformed chunk first — enforcement on the canonical order
above, expansion where a middleware chain expands early — MUST reject it
rather than repair it, and MUST NOT normalise, default or conceal a malformed
known value enforcement would reject. A consumer that substituted a default
for a malformed role made the same producer defect fatal when sent plainly and
invisible when sent as a chunk, so which form a producer happened to use
decided whether its bug was ever reported.
Verification MUST run after expansion, because what it checks — that messages
and calls open before they continue and close after — only exists once a chunk
has become the events it stands for.
The order is load-bearing in both directions:
- Middleware MUST see material before enforcement removes it. A shim exists precisely to translate a shape the current protocol does not describe; if enforcement stripped that shape first, the shim would receive an event with nothing left to translate and the run would lose data no one could recover.
- Application code MUST NOT see material enforcement would remove. Once the translators have had their chance, whatever is still unrecognised is unrecognised, and it stops there.
Outgoing material
The same boundary applies to what a consumer sends. Immediately before transmission, a consumer MUST remove unrecognised material from the run input, SHOULD warn about what it removed, and MUST treat a malformed known field as fatal before anything is sent. A producer receiving a run input MUST apply the same asymmetry to it: unrecognised material does not make the input invalid, a malformed known field does.What a producer must not do
A producer MUST NOT emit an event type the protocol does not describe in the expectation that consumers will ignore it. Unrecognised events survive so the protocol can add them, not so a producer can smuggle private signalling through the stream;CUSTOM and RAW exist for that and are described by the schema.