Why a URN and not a URL¶
The obvious thing to put in dataschema is a URL:
It is dereferenceable, everybody understands it, and it needs no registry to be useful on day one. We did not do that.
The argument¶
A dataschema outlives the infrastructure that served it.
Once a message carries one, that string is in log lines, in archived object storage, in a JetStream stream and in whatever a consumer wrote down. Three years later the schema host has been renamed, moved behind a different gateway, or replaced with something else entirely — and every one of those historical messages now points at a dead host.
Nothing rewrites them. They are not editable; that is what makes them a record.
A URN separates identity from location. urn:phpboyscout:schema:orders.created:3 names a schema and
says nothing about where it lives, which is what the specification's own wording asks for:
"Identifies the schema". Resolution then becomes configuration rather than a promise embedded in
every message — embedded in the binary, cached, or fetched over the network, and changeable without
touching a single archived event.
The cost, stated¶
It is not dereferenceable. Somebody outside the estate holding one of our messages cannot fetch the schema without knowing our resolver, where a URL would have just worked.
That is acceptable for internal traffic. It would not be if we published events externally, and if that changes, the decision to revisit is this one.
The hybrid we did not rule out¶
Publishing at a URL as well, with the resolver mapping URN to URL, is additive: the URN does not change when a URL appears beside it, and no archived message is invalidated.
The one thing that would be awkward to retrofit is that the URLs would ideally exist from the first published message rather than appearing halfway through the archive. That is a reason to decide sooner rather than a reason to decide now.
Why the version is an integer¶
orders.created:3, not :v3.1.0 and not :2026-08-28.
Semantic versioning would imply a compatibility promise, and
go/schema does not check compatibility in v1. A promise nobody
verifies is precisely the class of claim this estate has spent the past week removing. A date encodes
when rather than what, and answers no question a consumer asks.
An integer is unambiguous, orders trivially, and claims nothing.