Why it fails open¶
Two failures look similar and are not:
- The schema could not be used. Unknown URN, not a URN at all, no validator for its language, registry unreachable — and a schema that resolved but will not compile, is not a descriptor set, or names no root message. The guard processes the message unvalidated, and increments a counter.
- The schema was used and the payload does not match. The guard refuses, with
ErrInvalidPayload.
The line is not "did we get bytes back", it is "could we actually check anything". A schema whose bytes will not compile has not resolved into anything usable, so it belongs on the open side with the other resolution failures. Putting it on the closed side — which an earlier version of this module did — means one mis-published schema rejects 100% of a topic's traffic while the counter reads zero. That is the outage this design exists to prevent, reached through the failure most likely to actually occur.
Why the first one is open¶
Because of where the registry sits, or rather where it must not.
A service embeds its schemas and validates without a network call. That is the common path by design, so the failing path is already the rare one: a service meeting a schema it does not hold.
If that failed closed, a registry outage would become an outage of everything downstream of it. The registry would sit in the path of every message its consumers handle — which is precisely the thing a registry should most avoid being, and precisely what embedding exists to prevent. Failing closed would hand back the power that the whole offline-first design was built to deny it.
So: a schema we cannot check is a message we cannot vouch for, not a message we should destroy.
Why the second one is closed¶
Because it is an answer.
The schema resolved. The payload was checked against it. It does not conform. Passing that on would make the entire exercise decorative — a validator that never refuses anything is a comment.
The counter is not optional¶
Failing open silently is worse than failing closed
A fail-open you cannot see is indistinguishable from a validator that is not running. Six months later somebody asks whether the messages in the archive were validated and the honest answer is "we have no idea".
It is a counter and not a log line, deliberately. A log line about an unvalidated message is invisible until somebody goes looking for it, and nobody goes looking for a thing they do not know happened. A metric that is normally zero and is suddenly not is the difference between a known degradation and a silent one.
Which is why the things that are not counted matter as much as the things that are. An event with
no dataschema is not counted; a payload that failed validation is not counted. Both would put
traffic through the metric during healthy operation, and a counter that ticks in normal running is a
counter nobody can alert on.
See Alert on unvalidated messages.
The test that matters¶
A test asserting "an unresolvable URN still processes the message" would pass identically if the counter were never incremented — and a silent fail-open is the exact failure this design exists to prevent.
So the assertion is on the number, and the suite is checked by deleting the increment.