Appearance
ADR-0056: A published version is the payload the gate passed, stored and served as it is
Status: Accepted — amends ADR-0053, which stands Date: 2026-09-11
Decisions
D-127 At publish, the exact snapshot the gate checked is stored with the snapshot row, and every pull serves that payload and reads nothing live
The publisher serialises the candidate once, runs corpus_check.py over that object, and stores the same object in the publish transaction. The device's pull answers with the stored payload of the published row. No curation write made after a publish can reach a device until the next publish has checked it.
What each amendment touches
- ADR-0053 — D-122 stands: a snapshot still links to the documents it covers and carries the published marker. What changes is that the link no longer scopes a live read, because the pull makes none; it records what a version shipped. D-123 stands, and is now met by construction — the stored payload carries exactly the explanations that existed at publish. D-124 stands untouched. ADR-0053's consequence that "a version describes everything it shipped" was true of which documents and false of what they contained; this ADR is what makes it true.
Context
Closing the snapshot-shape gap (ADR-0053, D-124) exposed the handoff's blocker 14. The pull served the documents a published snapshot covered but read their content live, and no curation command refuses a write to a covered document. So after a publish, a span selected on a published document — including one bearing on a Restriction nobody had reconciled — reached every device on the next pull under the old version. corpus_check.py runs at publish and nowhere else, so D-44's reconciliation check never saw it. The link ADR-0053 recorded scoped which documents a device received, and said nothing about what was in them.
The owner ruled on it the same day (grill Q9): store the published payload and serve it.
It is stored in Postgres, in the snapshot row, rather than in object storage. The deployed instance has no object storage configured — only a loopback bind resolves storage (D-110) — and the pull runs on the deployed instance.
Decision
A published version is the stored payload that passed the gate. The pull serves it and nothing else.
Rejected options
- Refuse curation writes to covered documents until the next publish — rejected. Curating a correction to a live document would then need a draft or unpublish concept that does not exist, and the pull would still be assembling bytes the gate never saw.
- Run the gate at every pull — rejected. A Python subprocess per request, and a failing gate would empty every device's pull until a curator fixed it.
- Leave it until the first real publish — rejected. The hole is live from that publish on.
- Object storage — not available where the pull runs.
This ruling may not be re-decided
If a change contradicts this ADR: stop and raise it. Do not implement over it.
Specifically: do not re-serialise live rows for a device, do not store a second serialisation alongside or instead of the one the gate checked, and do not make the stored payload optional.
Consequences
What becomes true. The bytes a device receives are the bytes the gate passed. A version is immutable, so rolling back to it serves exactly what it shipped. A publish landing mid-pull cannot mix two versions, because one pull is one stored value.
What this costs. Every published version keeps a full copy of the Corpus as JSONB — small at the Study Area's scale, and the price of a version meaning something. A seventh unapplied migration after the template's. The pull no longer reflects a correction until someone publishes it, which is the point.
What still holds it together. The payload is the serialiser's output, which emits ISO strings rather than dates so it survives the Json column unchanged; a test pins that round trip. The route's response schema validates the stored payload on the way out, so one that no longer fits the contract is refused at the boundary.
Coverage
No upstream finding lands here. The ruling came from reading the pull while closing blocker 13 (D-104).