Write the compatibility policy into the repository as a document a stranger can read without asking anyone. It names four frozen surfaces — the on-disk format, the wire protocol, declaration semantics, and the public API — and for each one says what a 1.x release may do to it. It also names, out loud, the things 1.0.0 does not promise. Each component (server, the three clients, the app) gets its own version line; what binds them is the protocol version, not a shared number.
This is a promise on a public surface, and a promise made by silence is still a promise. After the tag, the answer to "may this change in 1.1" is whatever the first person to ask happens to assume, and by then their code depends on the assumption. A shared version number is the same trap in reverse: it forces a client release every time the server patches a bug, and undoing that later means renumbering something somebody already installed.
Measured 2026-09-20. Three of the four surfaces have a value that can be read today:internal/pager/pager.go:61holdsMagic = [8]byte{'S','A','P','E','D','B',0,1}andpager.go:63holdsconst Format uint16 = 1; frame types 1…13 are declared ininternal/protocol/frame.go:37-58, ending atDeclare Type = 13. The fourth already has a guard:sapedb_test.go:44isTestTheSurfaceIsExactlyTheseThirtyThreeNames, which compares the exported name set in both directions rather than counting it. What does not exist anywhere is a document saying which of these four a 1.x release may touch.
pager.Format and pager.Magic byte-for-byte. Changing either
turns it red, so the change cannot be made without someone deleting an assertion on purpose.TestTheSurfaceIsExactlyTheseThirtyThreeNames still passes, and the policy document
points at it by name as the enforcement for the API surface.internal/.SAPEDB. This ticket records it, it does not reopen it.