Why a published URN cannot change¶
Republish an identifier with different bytes and the registry refuses. That is the property the whole thing is for.
The argument¶
A message written today carries urn:phpboyscout:schema:orders.created:3 and is then archived — in
a JetStream stream, in object storage, in a log line somebody grepped. Three years later it is still
there, and somebody has to answer what shape was this?
If the bytes behind that identifier can change, the answer they get is not the answer a consumer got
at the time. Worse, nothing tells them that. Every stored dataschema becomes a claim about
something that has since moved, and there is no way to tell which of them are still true.
Immutability is what makes an archived message self-describing rather than merely self-referencing.
Why a refusal rather than content-addressing¶
The stronger design is to make "changed" unrepresentable: identify a schema by the hash of its content, and two different byte strings are simply two different identifiers. Nothing to enforce.
We did not, for one reason. A dataschema appears in log lines an operator has to read at a glance,
and
is legible where
is not. Somebody reading a stack trace at three in the morning gets the first one right and cannot do anything at all with the second.
A refusal buys the same guarantee at the cost of enforcing it, and that cost is a comparison in one function. The hybrid — a content hash carried alongside a readable identifier — remains open, and would be additive.
What it costs¶
You cannot fix a typo. A schema published with a mistake in it stays published; the correction is version 4. That is deliberate and occasionally annoying, and it is much less annoying than a schema that quietly became something else.
Publishing gets a little ceremonial. CI publishing on every pipeline run must be idempotent, which is why republishing identical bytes succeeds. A re-run of the same pipeline is a no-op, not a conflict.
A compromised writer is permanent within a version. Someone who publishes a bad schema under a new identifier cannot be undone by overwriting it; the response is to publish the next version and stop consuming the bad one. That is the same trade every immutable store makes.
Where it is enforced¶
In the Store, so every implementation inherits it, and asserted by a test that is confirmed by
removing the check — a guard nobody has seen fail is decoration.
Any store beyond the in-memory one must keep the guarantee, and must also retain prior versions, which is a separate promise for a separate reason.