From c69bd0b78517e694c038038e7805a2a6416c1e61 Mon Sep 17 00:00:00 2001 From: "dobby-yivi-agent[bot]" <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:47:10 +0000 Subject: [PATCH 1/4] docs: COMPATIBILITY.md declaring the external consumer guarantees 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 --- CLAUDE.md | 1 + COMPATIBILITY.md | 132 +++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 3 files changed, 135 insertions(+) create mode 100644 COMPATIBILITY.md diff --git a/CLAUDE.md b/CLAUDE.md index 3dcb26ab..9e00b0ff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,6 +24,7 @@ Migrated from the dobby memory repo (`encryption4all/dobby`). This file is the s ## Release & configuration - release-plz reads conventional-commit PR titles (PRs are squash-merged, so the title becomes the commit subject) to decide the version bump. A breaking change needs `fix(scope)!:`/`feat(scope)!:` (the `!` is what the semantic-PR-title check and release-plz both key on) plus a `BREAKING CHANGE:` footer, or the break silently ships as a patch/minor bump with no changelog warning. +- The npm package `@e4a/pg-wasm` is NOT versioned from `pg-wasm/Cargo.toml`. `delivery.yml`'s `publish-wasm` job writes `pg-wasm/pkg/package.json` from scratch and stamps it with the released pg-core version, so npm serves 0.6.1 while the crate manifest still says 0.5.5. When you need a pg-wasm version (support windows, reader pins, consumer ranges), read it from npm, never from the crate manifest. - `pg-wasm`'s web target: set `[package.metadata.wasm-pack.profile.release.wasm-bindgen] omit-default-module-path = true` in `pg-wasm/Cargo.toml` to drop the `new URL('index_bg.wasm', import.meta.url)` branch from generated `__wbg_init` glue (Webpack 5 otherwise statically resolves it and breaks bundler consumers that always pass an explicit `module_or_path`). Cleaner than post-build regex-stripping the generated JS. - `pg-pkg` (the postguard PKG service) CLI flags: `-t irma_token`, `-i irma_server_url`, `-d postgres_url`. Env vars: `IRMA_SERVER`, `DATABASE_URL`, `RUST_LOG`. diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md new file mode 100644 index 00000000..b87cf8f3 --- /dev/null +++ b/COMPATIBILITY.md @@ -0,0 +1,132 @@ +# Compatibility + +What an external consumer of PostGuard can rely on: which HTTP endpoints stay +put, which stored ciphertexts stay readable, and which published SDK versions +are kept working against the current server and the current wire format. + +Three seams are covered, each with its own guarantee. + +## Hosted API + +The `/v2` contract is [`pg-pkg/api-description.yaml`](pg-pkg/api-description.yaml). + +Changes to `/v2` are additive only. New endpoints, new optional request fields +and new response fields are allowed, so a client written against an older +revision of the spec keeps working. These are not allowed on `/v2`: removing a +route or a field, renaming either, narrowing a type, making an optional field +required, or changing the status code for a condition a client already handles. + +A change that cannot be made additively ships under a new prefix (`/v3`), with +`/v2` left running until its consumers are gone. + +`/v2/irma/...` is a legacy alias for the canonical `/v2/request/...`; both +prefixes serve the same handlers, and the alias is kept for deployed clients +([#257]). + +Planned enforcement: an oasdiff gate that diffs the spec against `main` and +fails on a breaking change ([#249]). Until that lands, this is a review rule. + +## Stored artifacts + +Containers and envelopes stay readable. Once a release can open a format, no +later release drops that ability, and there is no expiry on it. A container +that cannot be opened fails with a typed error, never silently. + +The guarantee starts at container format `VERSION_V3` (wire value `2`, bincode +header, [`pg-core/src/consts.rs`](pg-core/src/consts.rs)), which is the only +format current readers accept. `VERSION_V1` (Kiltz-Vahlis-1) and `VERSION_V2` +(MessagePack header) predate this document and were dropped before it; +`preamble_checked` rejects both with `Error::IncorrectVersion`. + +Format changes roll out readers first. Read support ships in one release and +the write default flips a major later, so nothing gets written that the +installed base cannot open. + +Email envelopes are the `@e4a/pg-js` layer and carry the same guarantee; their +compat gate is [postguard-js#131]. + +Enforcement today: `pg-core/tests/wire_format.rs` opens the committed golden +fixtures under `pg-core/testdata/wire-format-v3/` on every +`cargo test -p pg-core --features test,rust,stream`. Planned: the append-only +corpus in [postguard-e2e#19], one artifact per readable container format and +envelope tier, tested in both directions. + +## SDK support windows + +The window is the set of published SDK versions kept working against the +current server and the current wire format. + +- `@e4a/pg-js` (npm): the last two majors. `1.x` leaves the window when the + Outlook migration lands ([postguard-outlook-addon#125]) and telemetry shows + no `1.x` traffic. +- `@e4a/pg-wasm` (npm): every version a supported `pg-js` resolves. +- `E4A.PostGuard` (NuGet): the last major. `0.x` counts as one line until + `1.0`. +- `pg-core` (crates.io): the last two minors. + +A version stays in the window for at least 12 months after its successor +ships, and longer while live client-version telemetry still shows it +([postguard-ops#64]). + +Read support for stored artifacts is not part of this window. It never drops, +whatever happens to the SDK version that wrote the bytes. + +No window is declared for `pg-cli` or `pg-ffi`. `pg-ffi` consumers pin an +exact release rather than building against the latest one +([postguard-dotnet#50]). + +## Reader list + +The window above resolved to concrete versions: the highest published patch of +each line in the window. This is the list the compat gates install and run as +readers. + +``` +# +crates.io pg-core 0.6.1 0.5.10 +npm @e4a/pg-wasm 0.6.1 +npm @e4a/pg-js 2.3.3 1.11.0 +nuget E4A.PostGuard 0.6.0 +``` + +Checked against the registries on 2026-07-27. A producer publishing a new +version moves a pin; a line leaves the list only through the deprecation +process below. The npm version of `@e4a/pg-wasm` tracks the released `pg-core` +version rather than `pg-wasm/Cargo.toml`, so read that pin from npm and not +from the crate manifest. + +The gates that execute this list: + +- [#251], the wire-compat gate as a required PR check, with the Rust half in + [#260] and the Node half in [#261] +- [postguard-e2e#25], the forward-direction fixture job +- [postguard-e2e#21], the version sweep run nightly and pre-deploy +- [postguard-js#131], the envelope-compat gate + +A gate that needs a different set of readers changes this file first. + +## Deprecation + +1. Announce. The release that starts the clock says what is deprecated, in its + changelog entry and next to the item in the spec or in this file. +2. Observe. `pg-pkg` exports `postguard_clients{client,client_version,host,...}` + per request, so the versions in the field are measurable. Scraping it is + [postguard-ops#64]; while that is not running there is no field data, and + nothing gets removed. +3. Remove. Only once the window has expired and telemetry shows no traffic for + what is being removed. + +Skipping step 2 is how you break the consumers you cannot see. + +[#249]: https://github.com/encryption4all/postguard/issues/249 +[#251]: https://github.com/encryption4all/postguard/issues/251 +[#257]: https://github.com/encryption4all/postguard/issues/257 +[#260]: https://github.com/encryption4all/postguard/issues/260 +[#261]: https://github.com/encryption4all/postguard/issues/261 +[postguard-js#131]: https://github.com/encryption4all/postguard-js/issues/131 +[postguard-e2e#19]: https://github.com/encryption4all/postguard-e2e/issues/19 +[postguard-e2e#21]: https://github.com/encryption4all/postguard-e2e/issues/21 +[postguard-e2e#25]: https://github.com/encryption4all/postguard-e2e/issues/25 +[postguard-dotnet#50]: https://github.com/encryption4all/postguard-dotnet/issues/50 +[postguard-outlook-addon#125]: https://github.com/encryption4all/postguard-outlook-addon/issues/125 +[postguard-ops#64]: https://github.com/privacybydesign/postguard-ops/issues/64 diff --git a/README.md b/README.md index c97afac4..47e00762 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ PostGuard is an Identity-Based Encryption (IBE) service for encrypting messages PostGuard uses [Yivi](https://yivi.app), a privacy-friendly identity platform, for identity authentication. This repository contains the core protocol library, the PKG server, WASM bindings, a CLI tool, and FFI bindings. All other PostGuard tools and SDKs depend on this. +What external consumers can rely on (`/v2` endpoint stability, the archival read guarantee, SDK support windows and the deprecation process) is in [COMPATIBILITY.md](./COMPATIBILITY.md). + ### Workspace | Crate/package | Description | From 1a8db6cd18538cd1da85b960f8b2b42b31be1829 Mon Sep 17 00:00:00 2001 From: "dobby-yivi-agent[bot]" <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:15:27 +0000 Subject: [PATCH 2/4] docs: announce the /v2/irma deprecation and hedge the unlanded gates 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. --- COMPATIBILITY.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index b87cf8f3..33064eab 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -20,8 +20,10 @@ A change that cannot be made additively ships under a new prefix (`/v3`), with `/v2` left running until its consumers are gone. `/v2/irma/...` is a legacy alias for the canonical `/v2/request/...`; both -prefixes serve the same handlers, and the alias is kept for deployed clients -([#257]). +prefixes serve the same handlers today. The alias is deprecated. New clients +should use `/v2/request/...`, and the alias will be removed once the deprecation +process at the bottom of this file has run for it ([#257]). Until then it keeps +working, so a deployed client on `/v2/irma/...` is not broken by this notice. Planned enforcement: an oasdiff gate that diffs the spec against `main` and fails on a breaking change ([#249]). Until that lands, this is a review rule. @@ -43,7 +45,7 @@ the write default flips a major later, so nothing gets written that the installed base cannot open. Email envelopes are the `@e4a/pg-js` layer and carry the same guarantee; their -compat gate is [postguard-js#131]. +compat gate is planned in [postguard-js#131]. Enforcement today: `pg-core/tests/wire_format.rs` opens the committed golden fixtures under `pg-core/testdata/wire-format-v3/` on every @@ -78,8 +80,9 @@ exact release rather than building against the latest one ## Reader list The window above resolved to concrete versions: the highest published patch of -each line in the window. This is the list the compat gates install and run as -readers. +each line in the window. This is the list the compat gates will install and run +as readers. None of those gates runs yet, so for now this list is a review rule +like the `/v2` one above. ``` # @@ -95,12 +98,12 @@ process below. The npm version of `@e4a/pg-wasm` tracks the released `pg-core` version rather than `pg-wasm/Cargo.toml`, so read that pin from npm and not from the crate manifest. -The gates that execute this list: +Planned consumers of this list, each still an open issue: - [#251], the wire-compat gate as a required PR check, with the Rust half in [#260] and the Node half in [#261] - [postguard-e2e#25], the forward-direction fixture job -- [postguard-e2e#21], the version sweep run nightly and pre-deploy +- [postguard-e2e#21], the version sweep, to run nightly and pre-deploy - [postguard-js#131], the envelope-compat gate A gate that needs a different set of readers changes this file first. From a76509651ab5562d75637097c72c89a11352c407 Mon Sep 17 00:00:00 2001 From: "dobby-yivi-agent[bot]" <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:33:08 +0000 Subject: [PATCH 3/4] docs: date the /v2/irma deprecation and make step 1 achievable 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 --- COMPATIBILITY.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 33064eab..b9b89ebd 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -20,10 +20,11 @@ A change that cannot be made additively ships under a new prefix (`/v3`), with `/v2` left running until its consumers are gone. `/v2/irma/...` is a legacy alias for the canonical `/v2/request/...`; both -prefixes serve the same handlers today. The alias is deprecated. New clients -should use `/v2/request/...`, and the alias will be removed once the deprecation -process at the bottom of this file has run for it ([#257]). Until then it keeps -working, so a deployed client on `/v2/irma/...` is not broken by this notice. +prefixes serve the same handlers today. The alias is deprecated as of +2026-07-27. New clients should use `/v2/request/...`, and the alias will be +removed once the deprecation process at the bottom of this file has run for it +([#257]). Until then it keeps working, so a deployed client on `/v2/irma/...` +is not broken by this notice. Planned enforcement: an oasdiff gate that diffs the spec against `main` and fails on a breaking change ([#249]). Until that lands, this is a review rule. @@ -110,8 +111,10 @@ A gate that needs a different set of readers changes this file first. ## Deprecation -1. Announce. The release that starts the clock says what is deprecated, in its - changelog entry and next to the item in the spec or in this file. +1. Announce. Say what is deprecated next to the item in the spec or in this + file, with the date the clock starts. The next release of the affected + component repeats it in its changelog entry. The date is what step 3 counts + from, so an announcement without one does not start the window. 2. Observe. `pg-pkg` exports `postguard_clients{client,client_version,host,...}` per request, so the versions in the field are measurable. Scraping it is [postguard-ops#64]; while that is not running there is no field data, and From 2c1d32a005cf82c74c0acf54ddb05d35e46d1a40 Mon Sep 17 00:00:00 2001 From: "dobby-yivi-agent[bot]" <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:34:32 +0000 Subject: [PATCH 4/4] docs: note that release-plz strands hand-written [Unreleased] entries 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 --- CLAUDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CLAUDE.md b/CLAUDE.md index 9e00b0ff..7d391e68 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,6 +25,7 @@ Migrated from the dobby memory repo (`encryption4all/dobby`). This file is the s - release-plz reads conventional-commit PR titles (PRs are squash-merged, so the title becomes the commit subject) to decide the version bump. A breaking change needs `fix(scope)!:`/`feat(scope)!:` (the `!` is what the semantic-PR-title check and release-plz both key on) plus a `BREAKING CHANGE:` footer, or the break silently ships as a patch/minor bump with no changelog warning. - The npm package `@e4a/pg-wasm` is NOT versioned from `pg-wasm/Cargo.toml`. `delivery.yml`'s `publish-wasm` job writes `pg-wasm/pkg/package.json` from scratch and stamps it with the released pg-core version, so npm serves 0.6.1 while the crate manifest still says 0.5.5. When you need a pg-wasm version (support windows, reader pins, consumer ranges), read it from npm, never from the crate manifest. +- Do not hand-write changelog entries under a crate's `## [Unreleased]` heading. `release-plz.toml` sets no changelog template, so entries are generated from commit subjects and each new version section is inserted *below* the `## [Unreleased]` heading. A manual bullet there is never folded into the release that follows it — it just sits above the newest version forever, undated. To get something into a dated changelog entry, it has to ride a commit subject on a PR that bumps that crate. - `pg-wasm`'s web target: set `[package.metadata.wasm-pack.profile.release.wasm-bindgen] omit-default-module-path = true` in `pg-wasm/Cargo.toml` to drop the `new URL('index_bg.wasm', import.meta.url)` branch from generated `__wbg_init` glue (Webpack 5 otherwise statically resolves it and breaks bundler consumers that always pass an explicit `module_or_path`). Cleaner than post-build regex-stripping the generated JS. - `pg-pkg` (the postguard PKG service) CLI flags: `-t irma_token`, `-i irma_server_url`, `-d postgres_url`. Env vars: `IRMA_SERVER`, `DATABASE_URL`, `RUST_LOG`.