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
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ Migrated from the dobby memory repo (`encryption4all/dobby`). This file is the h
- 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`.
- **Archiving a repo retires its *code*, never its *channels*, and the two are audited separately.** #288–#291 and #293 each checked the image/build *publisher* before archiving; nobody checked who still *fetches*. A channel outlives its source repo in every direction that matters: an archived repo keeps serving GitHub Releases and release assets, a GHCR package is owned by the org rather than the repo, and `raw.githubusercontent.com` keeps serving the frozen default branch. So the failure is never an error — it is a consumer pinned to an artifact set that stops growing, reporting success forever. The full audit of the five (#312, 2026-08-09) is below; **`ghcr.io/…` names were deliberately retained on the move, so an image name matching an archived repo is not by itself a hit** — check the publisher, then check whether new tags are actually arriving.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"#288#291 and #293 each checked the image/build publisher before archiving" misattributes the publisher check. #288#291 are issue-transfer tasks: #290 is "transfer postguard-tb-addon's 17 open issues into postguard-js, then re-archive", and the other three are the same shape for postguard-website, postguard-outlook-addon and postguard-examples. None of them has a publisher step. Only #293 ("cut cryptify's Docker publish pipeline over to postguard, then retire the old repo's build") was about a publisher.

That weakens the exact lesson the entry exists to record: for four of the five repos neither half was audited at archive time, which is a sharper warning than "the publisher was checked, the consumers were not".

Suggested change
- **Archiving a repo retires its *code*, never its *channels*, and the two are audited separately.** #288#291 and #293 each checked the image/build *publisher* before archiving; nobody checked who still *fetches*. A channel outlives its source repo in every direction that matters: an archived repo keeps serving GitHub Releases and release assets, a GHCR package is owned by the org rather than the repo, and `raw.githubusercontent.com` keeps serving the frozen default branch. So the failure is never an error — it is a consumer pinned to an artifact set that stops growing, reporting success forever. The full audit of the five (#312, 2026-08-09) is below; **`ghcr.io/…` names were deliberately retained on the move, so an image name matching an archived repo is not by itself a hit** — check the publisher, then check whether new tags are actually arriving.
- **Archiving a repo retires its *code*, never its *channels*, and the two are audited separately.** #293 checked cryptify's image/build *publisher* before retiring the old build; #288#291 only transferred open issues and re-archived. Nobody, on any of the five, checked who still *fetches*. A channel outlives its source repo in every direction that matters: an archived repo keeps serving GitHub Releases and release assets, a GHCR package is owned by the org rather than the repo, and `raw.githubusercontent.com` keeps serving the frozen default branch. So the failure is never an error — it is a consumer pinned to an artifact set that stops growing, reporting success forever. The full audit of the five (#312, 2026-08-09) is below; **`ghcr.io/…` names were deliberately retained on the move, so an image name matching an archived repo is not by itself a hit** — check the publisher, then check whether new tags are actually arriving.


| Archived repo | Channels it published | Who publishes that channel now | Verdict |
|---|---|---|---|
| `postguard-website` | GitHub Releases `v1.0.0`–`v1.8.0` (no assets); GHCR `postguard-website` | postguard-js `website.yml`, **same GHCR name**; GHCR is at `1.8.2` | Clean. No assets ever existed, so nothing could pin one. |
| `postguard-outlook-addon` | GitHub Releases `v0.1.1`–`v0.5.0`, **each with a `manifest.xml` asset**; GHCR `postguard-outlook-addon` | postguard-js `outlook-addon.yml`, **same GHCR name** (`1.0.0`); manifests now ride `outlook-addin-v*` releases | `releases/latest/download/manifest.xml` still serves v0.5.0 forever. Nothing in-repo fetches it; AppSource / M365 admin-center **unverified, no access**. |
| `postguard-tb-addon` | GitHub Releases with `.xpi` + `updates.json`; **self-hosted Thunderbird auto-update channel** | postguard-js `tb-addon-v*`; `update_url` is now the raw file on `postguard-js@main` | Migration correct — `releases/latest` is `channel-migration-0.9.4`, whose `updates.json` advertises 0.9.4 pointing into postguard-js. That release has **no `.xpi`**, so anything fetching `releases/latest/download/*.xpi` from it 404s. |
| `postguard-examples` | **Zero releases**, no packages. Only raw/blob `main` URLs | postguard-js `examples/` | Pinned-commit doc links still resolve by design; unpinned `tree/main` links were repointed. |
| `cryptify` | GitHub Releases `v0.1.1`–`v0.1.27` (no assets); GHCR `cryptify` | postguard `delivery.yml`, **same GHCR name**, sole publisher since #293 | The one real gap — see the version-tag hole below. Also `ghcr.io/encryption4all/cryptify-backend` is a **dead name** (renamed to `cryptify` years ago) that two live consumers still referenced. |

Two lessons the table does not carry. **A self-hosted add-on update channel needs one last publish from the old repo, and the successor release must not be `releases/latest`.** `postguard-tb-addon` got this right: installed builds ≤0.9.3 poll `postguard-tb-addon/releases/latest/download/updates.json`, so the old repo had to advertise 0.9.4 one final time before going read-only — hence the deliberately non-`v*` tag `channel-migration-0.9.4` (a `v*` tag would have fired the old build workflow). Archive first and every installed add-on stops updating silently. And in the monorepo the channel moved *off* `releases/latest` entirely, onto the raw file on `main`, because `releases/latest` in a repo shared with `@e4a/pg-js` is whichever package released most recently and a pg-js release carries no `updates.json`. **Verify a "verdict" release actually resolves**: `releases/latest` is not the newest `published_at` you see in the API list, so read it from `repos/{o}/{r}/releases/latest` and then `curl -L` the asset itself.
- **A release that predates its own publish job leaves a version with no artifact, and nothing reports it.** `cryptify-v0.1.28` was released from this workspace at `18e0ab0` (2026-08-03 09:38:49 +0200) carrying three security fixes — GHSA-5rhx-xgvv-h78h (unauthenticated `GET /usage`), a non-constant-time `cryptify_token` compare, and RUSTSEC-2026-0141 (lettre) — but `delivery.yml` had **no cryptify publish at all** at that commit: the jobs landed 85 minutes later in `f1b2e14` (#280), gated behind `vars.PUBLISH_CRYPTIFY_IMAGE`, ungated only in `506144c` (#309, 2026-08-07). The old repo never saw the release either, because the release happened here. So **`ghcr.io/encryption4all/cryptify:0.1.28` does not exist and cannot be built by re-running anything** — `workflow_dispatch` takes the workflow file from the ref you dispatch, and that file has no cryptify job. GHCR's highest semver tag is `0.1.27`, pushed by the *archived* repo on 2026-05-16; every tag this pipeline has pushed since taking over is `edge` or `pr-*`. Consequence for ops: `privacybydesign/postguard-ops`' `procolix/environments/prod.tfvars` pins `cryptify_image_tag = "0.1.25"`, and the newest tag it *could* move to today is still the archived repo's last build. The next cryptify release (release-plz #278 has `0.1.28 -> 0.1.29` queued) is what restores semver tagging from this repo; bump the ops pin to that, not to `0.1.28`. General rule: when moving a Docker publish between repos, land the publish job **before** the first release cut in the new home, and check the registry for the tag afterwards — a release-plz release and an image tag are separate events and only one of them is visible in the repo.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stated reason 0.1.28 cannot be recovered is wrong. delivery.yml declares no workflow_dispatch at all — its on: block is only push: branches: [main] and pull_request (.github/workflows/delivery.yml:12-16); build.yml is the only workflow in the repo with a manual trigger, and it runs cargo test/clippy without publishing an image. So "workflow_dispatch takes the workflow file from the ref you dispatch" describes a path that does not exist here, and a reader who trusts it will try gh workflow run delivery.yml, get it rejected outright, and never learn why.

The conclusion is right and actually stronger: the semver tag comes solely from release-plz-release's cryptify_version output (delivery.yml:378), and that job is if: github.ref == 'refs/heads/main', so only a real release on main can ever push a semver tag. That is also exactly why every tag since the takeover is edge or pr-*.

Suggested change
- **A release that predates its own publish job leaves a version with no artifact, and nothing reports it.** `cryptify-v0.1.28` was released from this workspace at `18e0ab0` (2026-08-03 09:38:49 +0200) carrying three security fixes — GHSA-5rhx-xgvv-h78h (unauthenticated `GET /usage`), a non-constant-time `cryptify_token` compare, and RUSTSEC-2026-0141 (lettre) — but `delivery.yml` had **no cryptify publish at all** at that commit: the jobs landed 85 minutes later in `f1b2e14` (#280), gated behind `vars.PUBLISH_CRYPTIFY_IMAGE`, ungated only in `506144c` (#309, 2026-08-07). The old repo never saw the release either, because the release happened here. So **`ghcr.io/encryption4all/cryptify:0.1.28` does not exist and cannot be built by re-running anything** — `workflow_dispatch` takes the workflow file from the ref you dispatch, and that file has no cryptify job. GHCR's highest semver tag is `0.1.27`, pushed by the *archived* repo on 2026-05-16; every tag this pipeline has pushed since taking over is `edge` or `pr-*`. Consequence for ops: `privacybydesign/postguard-ops`' `procolix/environments/prod.tfvars` pins `cryptify_image_tag = "0.1.25"`, and the newest tag it *could* move to today is still the archived repo's last build. The next cryptify release (release-plz #278 has `0.1.28 -> 0.1.29` queued) is what restores semver tagging from this repo; bump the ops pin to that, not to `0.1.28`. General rule: when moving a Docker publish between repos, land the publish job **before** the first release cut in the new home, and check the registry for the tag afterwards — a release-plz release and an image tag are separate events and only one of them is visible in the repo.
- **A release that predates its own publish job leaves a version with no artifact, and nothing reports it.** `cryptify-v0.1.28` was released from this workspace at `18e0ab0` (2026-08-03 09:38:49 +0200) carrying three security fixes — GHSA-5rhx-xgvv-h78h (unauthenticated `GET /usage`), a non-constant-time `cryptify_token` compare, and RUSTSEC-2026-0141 (lettre) — but `delivery.yml` had **no cryptify publish at all** at that commit: the jobs landed 85 minutes later in `f1b2e14` (#280), gated behind `vars.PUBLISH_CRYPTIFY_IMAGE`, ungated only in `506144c` (#309, 2026-08-07). The old repo never saw the release either, because the release happened here. So **`ghcr.io/encryption4all/cryptify:0.1.28` does not exist and cannot be built by re-running anything** — `delivery.yml` has no `workflow_dispatch` trigger at all (only `push` on `main` and `pull_request`), and the semver tag comes solely from `release-plz-release`'s `cryptify_version` output, which only a real release on `main` produces. GHCR's highest semver tag is `0.1.27`, pushed by the *archived* repo on 2026-05-16; every tag this pipeline has pushed since taking over is `edge` or `pr-*`. Consequence for ops: `privacybydesign/postguard-ops`' `procolix/environments/prod.tfvars` pins `cryptify_image_tag = "0.1.25"`, and the newest tag it *could* move to today is still the archived repo's last build. The next cryptify release (release-plz #278 has `0.1.28 -> 0.1.29` queued) is what restores semver tagging from this repo; bump the ops pin to that, not to `0.1.28`. General rule: when moving a Docker publish between repos, land the publish job **before** the first release cut in the new home, and check the registry for the tag afterwards — a release-plz release and an image tag are separate events and only one of them is visible in the repo.


## Operations

Expand Down
11 changes: 8 additions & 3 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,13 @@ envelope tier, tested in both directions.
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-js` (npm): the last two majors. `1.x` leaves the window when
telemetry shows no `1.x` traffic. The other half of that condition, the
Outlook add-in's v1 → v2 migration, **has landed**: the add-in lives at
`apps/outlook-addon` in [postguard-js] on `@e4a/pg-js": "workspace:*"` (2.x)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Malformed inline code span: the opening double quote of the package.json key is missing, so this renders as @e4a/pg-js": "workspace:*" — an unbalanced quote in what is meant to be a verbatim dependency line. No CI job lints markdown here, so nothing else will catch it.

Suggested change
`apps/outlook-addon` in [postguard-js] on `@e4a/pg-js": "workspace:*"` (2.x)
`apps/outlook-addon` in [postguard-js] on `"@e4a/pg-js": "workspace:*"` (2.x)

and released as `outlook-addin-v1.0.0`. [postguard-outlook-addon#125] is
closed and stayed in that now-archived repo, so treat it as a historical
record rather than a tracker.
- `@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`.
Expand Down Expand Up @@ -167,6 +171,7 @@ Skipping step 2 is how you break the consumers you cannot see.
[#262]: https://github.com/encryption4all/postguard/issues/262
[#268]: https://github.com/encryption4all/postguard/issues/268
[#272]: https://github.com/encryption4all/postguard/issues/272
[postguard-js]: https://github.com/encryption4all/postguard-js
[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
Expand Down
Loading