docs: COMPATIBILITY.md declaring the external consumer guarantees - #264
Conversation
Writes down what external consumers can rely on: /v2 additive-only endpoint stability, the archival read guarantee for containers and envelopes, the SDK support windows, and the announce/observe/remove deprecation process. The support window is resolved to a concrete reader list in a parseable block, so the wire-compat gate (#251, #260, #261), the forward-direction fixture job and the version sweep can install exactly the versions this file declares. Pins verified against npm, crates.io and NuGet on 2026-07-27. Also records in CLAUDE.md that @e4a/pg-wasm's npm version is stamped from the released pg-core version, not from pg-wasm/Cargo.toml. Closes #252 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
VERDICT: request-changes
Rules Dobby 2, cycle 1. Doc-only PR, so the sweep was the docs/process rules: cross-repo link format, closing keywords, conventional PR title, the writing rules, standardized READMEs, and patch-in-comment-gate-is-not-live-yet.
Clean on the process side. Title matches the pr-title.yml semantic check, Closes #252 is same-repo and on its own line against main, rubenhensen is assigned, every cross-repo reference uses a full markdown reference link, and the prose has no banned words, no em dashes and no bold. The reader-list block parses. The pins were spot-checked and the VERSION_V3 and irma|request claims hold (pg-pkg/src/server.rs:308 serves both prefixes).
Two things to fix, both in COMPATIBILITY.md.
1. The /v2/irma alias is presented as kept, but #257 says deprecate and remove, and names this file as the place to announce it. #257's body: "deprecate and remove the /v2/{irma} alias (request is canonical)", process "announce in COMPATIBILITY.md, observe postguard_clients, remove". Line 23 cites #257 as if it supported keeping the alias. Two consequences: an external reader following the link lands on an issue arguing the opposite, and this document's own Deprecation step 1 goes unperformed for the one surface that already has a tracked deprecation.
2. "The gates that execute this list" is present tense, and none of them exist yet. All six cited gates (#251, #260, #261, postguard-e2e#25, postguard-e2e#21, postguard-js#131) are open issues. The Rust half in #265 pins only pg-core =0.6.1, omitting the 0.5.10 this list declares, and its own workflow YAML sits in a PR comment awaiting a maintainer. The document hedges correctly two sections earlier ("Planned enforcement: an oasdiff gate...", "Planned: the append-only corpus..."); the Reader list section should hedge the same way. The list itself is right, the claim about who runs it is not.
One non-blocking note on the postguard-ops#64 link, inline below. Do not change it without asking a maintainer.
Not flipping this out of draft while changes are requested.
Review cycle 1 on #264 flagged two claims in COMPATIBILITY.md. The alias paragraph cited #257 as the reason the alias is kept, while #257 asks to deprecate and remove it and names this file as the announcement venue. The paragraph now carries that announcement: both prefixes still serve the same handlers, the alias is deprecated, new clients use /v2/request/..., and removal runs through the process at the bottom of the file. Deployed clients are unaffected until then. The reader-list section claimed the compat gates install and run the list today. All six cited gates (#251, #260, #261, postguard-e2e#21, postguard-e2e#25, postguard-js#131) are still open issues, so the section now hedges the way the two sections above it already do.
Step 1 required the announcement in a release changelog entry AND next to the item, but release-plz generates changelogs from commit subjects and inserts each version section below the [Unreleased] header, so a hand-written bullet there is never carried into a dated release. A doc-only announcement could not satisfy the step, leaving step 3 gating removal on a window with no start date. Step 1 now asks for the date next to the item, with the changelog entry following in the next release of the affected component, and the /v2/irma alias carries its start date. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
VERDICT: approve
Rules Dobby 2, cycle 2. Sign-off. Reviewed 1a8db6c, then fixed the one open nit myself in a765096 — no bug-severity findings, so this does not go back for another coder cycle.
Rule sweep
Doc-only diff (COMPATIBILITY.md new, one CLAUDE.md bullet, one README.md sentence), so the sweep was the docs/process set: cross-repo link format, closing keywords, conventional PR title, the writing rules, standardized READMEs, no-justification-paragraphs, patch-in-comment-gate-is-not-live-yet, oasdiff-action defaults, self-trigger loops. All clean.
Worth naming because it is the rule this kind of PR usually trips: patch-in-comment-gate-is-not-live-yet, checked in both directions. Every gate the doc names is hedged (Planned enforcement, is planned in, None of those gates runs yet, each still an open issue), and the one thing claimed as live — pg-core/tests/wire_format.rs over the testdata/wire-format-v3/ fixtures — really does run on every PR via build.yml. Nothing understated in the other direction either. .github/workflows/ holds only build.yml, delivery.yml, pr-title.yml, and the doc claims no gate beyond that.
Spot-checked the doc's factual claims against the tree rather than the prose: VERSION_V3 = 2 in pg-core/src/consts.rs, preamble_checked in pg-core/src/util.rs:36 rejecting anything but V3 with Error::IncorrectVersion, the postguard_clients metric in pg-pkg/src/server.rs:31, and the scope("/{_:(irma|request)}") alias in pg-pkg/src/server.rs:308. All accurate. The reader-list block still parses to 4 rows with the four-line reader in the PR body.
The cycle-1 nit, and why I fixed it the other way
The nit was that step 1 of the deprecation process required the announcement in a release changelog entry and next to the item, while this PR only does the second half — so step 3, which gates removal on "the window has expired", had no dated start.
The reviewer offered two cheap fixes. I did not take the first one (add a ### Deprecated bullet under ## [Unreleased] in pg-pkg/CHANGELOG.md) because it does not work in this repo: release-plz.toml sets no changelog template, so entries are generated from commit subjects, and every version section is inserted below the ## [Unreleased] header — visible in all five crate changelogs, each of which has an empty [Unreleased]. A hand-written bullet there would sit above the next release rather than becoming part of it, and would never get dated. It would have looked like a fix while leaving the clock exactly as unstarted.
So I took the second option, in a765096:
- Step 1 now asks for the date next to the item, with the changelog entry following in the next release of the affected component, and says plainly that an announcement without a date does not start the window. That keeps the clock-start requirement, which is the part with teeth, without demanding a release a doc-only announcement cannot produce.
- The
/v2/irmaalias paragraph now reads "deprecated as of 2026-07-27", so the 12-month window has a concrete start.
Nothing else changed. Reader list untouched, pins untouched, parse check still 4 rows, no em dashes or bold introduced.
Left alone deliberately
pg-pkg/api-description.yaml:17-19 still describes the alias as "a legacy alias kept for deployed clients" with no deprecation marker. Editing the promised surface is #257's job, step 1's or permits announcing in this file instead, and the author already argued this in the resolved cycle-1 thread. Informational, not a re-open.
The #265 pin gap the cycle-1 reviewer noted (it pins pg-core =0.6.1 and misses the 0.5.10 this list declares) is real but belongs to that PR.
One open question for you, unchanged from the PR body
The pins live in markdown, so "the document and the test matrix are the same list" holds only while the gates parse this file. If you would rather they read structured data, the author offered a compat/readers.json here that the doc renders from, or a window block alongside current in postguard-e2e's compat/manifest.json. Not a blocker — #260 and #261 can parse the markdown as-is — but worth deciding before three gates hard-code the parser.
Addendum, 2c1d32a: recorded the release-plz [Unreleased] behaviour above as a CLAUDE.md bullet, next to the existing pg-wasm versioning trap. It is the kind of thing that costs a cycle to rediscover.
Flipping out of draft. No advisory reference, nothing embargoed.
Found while resolving the deprecation-step-1 nit on this PR: the obvious fix (a ### Deprecated bullet under pg-pkg CHANGELOG.md [Unreleased]) looks right and does nothing, because release-plz inserts version sections below that heading. Worth recording so the next run does not spend a cycle on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Closes #252
Writes down what external consumers can rely on, per the spec in the issue body and the support-window numbers proposed in rubenhensen's comment.
What is in the doc
/v2is additive only; a change that cannot be made additively ships under a new prefix with/v2left running. Enforcement is written as planned (ci: breaking-change gate on api-description.yaml (oasdiff) #249), not as present-tense fact. The/v2/irmaalias is written up as deprecated, which is the step-1 announcement #257 asks for.VERSION_V3. The doc states plainly thatVERSION_V1/VERSION_V2predate the guarantee and are already rejected bypreamble_checked, rather than implying every container ever written still opens.<registry> <package> <versions...>shape so the gates can parse it out of the markdown. Named the four planned consumers, all still open issues, and stated that a gate needing different readers changes this file first.postguard_clientsmetric named as the observation source and privacybydesign/postguard-ops#64 as the blocker on step 2.The pins
Checked against the live registries today, not copied from the issue:
Derivation rule is in the doc: highest published patch of each line in the window.
pg-wasmresolves to a single version because both@e4a/pg-js@2.3.3and@e4a/pg-js@1.11.0depend on^0.6.1.Two numbers differ from what the thread and the tooling say, both deliberately:
pg-wasm/Cargo.tomlsays0.5.5. npm is right.delivery.yml'spublish-wasmjob writespkg/package.jsonfrom scratch and stamps it with the released pg-core version, so the crate manifest version never reaches npm. Added this to CLAUDE.md, since it will bite whoever writes the reader-install step in compat gate: published pg-wasm/pg-js old-reader step (Node) #261.compat/manifest.jsonhaspg-dotnet.currentat0.5.0, but NuGet serves0.6.0. That looks like a pending auto-bump on their side rather than something to encode here, so the doc uses the published version. Worth a look if the bump job has been quiet.Parse check
The block parses with a four-line reader, which is what #260 and #261 will need:
Checks
Doc-only, no workflow files. Ran the suite per CLAUDE.md anyway:
pg-core(--features test,rust,stream),pg-pkg,pg-cli,pg-ffiall green,cargo fmt --all -- --checkclean. The claims aboutVERSION_V3, theirma|requestalias andwire_format.rswere each checked against the code rather than taken from the API description prose.One thing to decide
The pins live in markdown because you asked for a doc-only PR, so "the document and the test matrix are the same list" holds only as long as the gates parse this file. If you would rather the gates read structured data, the natural move is a
compat/readers.jsonin this repo that COMPATIBILITY.md renders from, or extending postguard-e2e'scompat/manifest.jsonwith awindowblock alongsidecurrent. Say which and I will follow up; I did not want to invent the format inside a doc PR.Review cycle 1
Both blocking items from the consolidated review are fixed in 1a8db6c.
The alias paragraph cited #257 as the reason the alias is kept, while #257 asks to deprecate and remove it and names this file as the place to announce that. It now reads as the announcement: both prefixes still serve the same handlers, the alias is deprecated, new clients use
/v2/request/..., removal runs through the process at the bottom of the file, and a deployed client on the alias is not broken by the notice. The spec's own alias note inapi-description.yamlis left alone, since editing the promised surface is #257's job.The reader-list section said the gates install and run this list. All six cited gates are still open issues, checked again just now, so both anchors are hedged the way the
/v2and fixture-corpus sections already were, and the postguard-js#131 envelope gate one section up is now "planned in" too. The list itself did not change.The reviewer's aside that #265 pins only pg-core
=0.6.1and misses the 0.5.10 this list declares is real, but it is a change to that PR, not this one. Left for #265.Re-ran the pins against crates.io, npm and NuGet after the edits: unchanged, and both
@e4a/pg-js@2.3.3and@e4a/pg-js@1.11.0still depend on@e4a/pg-wasm@^0.6.1. The four-line parse check still returns four rows.cargo fmt --all -- --checkclean,pg-core(--features test,rust,stream),pg-pkg,pg-cliandpg-ffigreen.