Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ 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.
- 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`.

Expand Down
138 changes: 138 additions & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# 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 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.

## 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 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
`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 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.

```
# <registry> <package> <versions...>
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.

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, 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.

## Deprecation

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
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
Comment thread
dobby-coder[bot] marked this conversation as resolved.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
Loading