feat(cryptify): merge cryptify into the workspace - #277
Merged
Conversation
Remove frontend, add dev API UI, and clean up project
Add linux/arm64 Docker image builds
Remove frontend, improve backend reliability
Replace single QEMU multi-platform job with parallel native runner jobs that merge into a multi-arch manifest. Add cargo-chef to Dockerfile for dependency layer caching. Add release/edge Cargo profiles (lto=true for published releases, lto=false for all other builds).
anchore/scan-action@v3 was pinning grype v0.74.4 (Nov 2023). v7.3.2 brings grype v0.104+ with an up-to-date vulnerability database. github/codeql-action/upload-sarif@v3 was showing a deprecation warning and will be removed in December 2026; upgrade to v4. Both actions are now pinned to their commit SHA per security best practice.
Bumps [keccak](https://github.com/RustCrypto/sponges) from 0.1.5 to 0.1.6. - [Commits](RustCrypto/sponges@keccak-v0.1.5...keccak-v0.1.6) --- updated-dependencies: - dependency-name: keccak dependency-version: 0.1.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.10.1 to 1.11.1. - [Release notes](https://github.com/tokio-rs/bytes/releases) - [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md) - [Commits](tokio-rs/bytes@v1.10.1...v1.11.1) --- updated-dependencies: - dependency-name: bytes dependency-version: 1.11.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [time](https://github.com/time-rs/time) from 0.3.44 to 0.3.47. - [Release notes](https://github.com/time-rs/time/releases) - [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md) - [Commits](time-rs/time@v0.3.44...v0.3.47) --- updated-dependencies: - dependency-name: time dependency-version: 0.3.47 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
…keccak-0.1.6 chore(deps): bump keccak from 0.1.5 to 0.1.6 in /cryptify
…bytes-1.11.1 chore(deps): bump bytes from 1.10.1 to 1.11.1 in /cryptify
…time-0.3.47 chore(deps): bump time from 0.3.44 to 0.3.47 in /cryptify
Many email clients strip out SVGs for security and anti-tracking reasons, causing the PostGuard logo to not render for a large portion of recipients. Replace the inline SVG logo with a hosted PNG image from postguard.eu. Closes encryption4all/postguard-website#29
…site fix: replace SVGs with accessible alternatives in email template
Bumps [rustls-webpki](https://github.com/rustls/webpki) from 0.103.8 to 0.103.10. - [Release notes](https://github.com/rustls/webpki/releases) - [Commits](rustls/webpki@v/0.103.8...v/0.103.10) --- updated-dependencies: - dependency-name: rustls-webpki dependency-version: 0.103.10 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
…rustls-webpki-0.103.10 chore(deps): bump rustls-webpki from 0.103.8 to 0.103.10 in /cryptify
…als-separate-fields feat: accept smtp_username and smtp_password as separate config fields
…email (#187) * fix(email): make recipient download link a prominent selectable code block The notification email's download-link block ({{link_str}}) rendered at 13px — smaller than the 16px primary button above it — and used a low weight/contrast style that was hard to read and select. July 2026 user test (postguard-website#296) flagged it as small and hard to copy. Restyle the {{link_str}} URL as a full-width monospace code block: - font-size 16px (no smaller than the primary button) - monospace family, higher contrast (dark #030E17 text) - more padding, subtle background + border so it reads as a code block - display:block + word-break for easy full selection No inline copy buttons (unreliable across email clients). Plain-text template already renders the label + URL on their own lines and stays consistent. Adds a render assertion guarding the >=16px monospace style. Closes #186 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(email): pin download-link assertion to restyled <a> The prior font-size:16px substring check was satisfied by the primary download button regardless, so it failed to guard the link block. Assert a contiguous substring unique to the restyled <a> (display:block + monospace + font-size:16px) so a font-size regression genuinely fails. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: dobby-yivi-agent[bot] <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Add an optional `metrics_token` config (also settable via ROCKET_METRICS_TOKEN). When set, /metrics requires `Authorization: Bearer <token>` (constant-time compared via subtle); when unset it stays open and logs a startup warning, so rolling this out doesn't break existing scrapers. The auth lives in the app, not the ingress, so it travels with cryptify to external hosts that can't be firewalled. /health stays public. Co-authored-by: dobby-yivi-agent[bot] <275734547+dobby-yivi-agent[bot]@users.noreply.github.com>
The website tags its uploads with X-Cryptify-Source for per-channel metrics (postguard-website#228), but the header was never added to the CORS allow-list. Browsers include it in the preflight's Access-Control-Request-Headers, rocket_cors rejects the preflight with a 403 carrying no Access-Control-Allow-Origin, and uploads from the website fail before they start. Staging hits this directly; production only works because its nginx answers preflights itself. Add the header to the allow-list, and extract build_cors() so the preflight smoke tests exercise the production CORS config instead of a test-local copy — the duplicated config is why the existing smoke test couldn't catch this regression.
* docs: add agent & contributor notes (migrated from dobby memory) * docs: correct purge timer notes in CLAUDE.md The Purge timer block described a hard 15-minute eviction from creation at src/store.rs:80. It is actually a resettable 1-hour idle timeout: Store::create (src/store.rs:292) seeds the deadline from idle_ttl (DEFAULT_UPLOAD_SESSION_IDLE_TIMEOUT_SECS = 60 * 60) and Store::touch re-inserts it on each chunk PUT / status check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: dobby-yivi-agent[bot] <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: retry the startup verifying-key fetch instead of panicking Cryptify fetched the IBS verifying key from pg-pkg once at startup and panicked if the PKG was unreachable or the response unparsable, creating a hard restart-order dependency (cryptify -> pg-pkg -> irma server): a brief PKG unavailability window during a rolling deploy took cryptify down with it. The fetch now retries transient failures with exponential backoff (500ms..10s) for a 120s budget, mirroring the existing /api-key/validate retry pattern, and runs the blocking minreq call via spawn_blocking. If the budget is exhausted the process still exits with a clear error so a misconfigured pkg_url does not fail silently. Fixes #235 * fix: log non-2xx PKG responses distinctly from parse failures in the startup retry
…#193) Part 2 of #236 (the cryptify leg). Finalize required exactly pbdf.sidn-pbdf.email.email in the sender's signing identity; test environments cannot issue pbdf credentials, blocking real end-to-end flows. The `email_attribute` config key (default: the production value, so deployed configs are unaffected) now names the attribute type carrying the sender's email. Companion changes: pg-pkg (#244) and pg-js.
These three dependencies were declared in Cargo.toml but never used anywhere in the crate. Removing them prunes qrcode's image subtree (image, moxcms, pxfm, byteorder-lite) and strum's heck from Cargo.lock. Closes #191 Co-authored-by: dobby-yivi-agent[bot] <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* chore: remove unused irma dependency The `irma` crate was declared in Cargo.toml but never imported anywhere in the crate. Attributes reach cryptify already signed inside the PostGuard-sealed file and are read back through pg-core's Unsealer, so there is no IRMA client here to migrate. `irma` stays in Cargo.lock as a transitive dependency of pg-core. Closes #194 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: say irma is still compiled in via pg-core, not just in the lockfile `cargo tree -i irma` shows irma 0.2.1 on the normal dependency edge under pg-core, so it is built into the binary, not lockfile residue. It also brings reqwest 0.11.27 alongside cryptify's own reqwest 0.13.4. The old wording could lead the postguard-e2e knownDrift follow-up to record the pair as resolved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: dobby-yivi-agent[bot] <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* docs: true up api-description.yaml with the mounted routes
The spec had drifted from the service in ways a client reading it would hit:
- `/staging/preview/{uuid}` was mounted but undocumented.
- `PUT /fileupload/{uuid}` and `POST /fileupload/finalize/{uuid}` documented a
409 for a `cryptifytoken` mismatch; both return 400, and no route in the
service returns 409.
- Finalize validates a `cryptifytoken` header, which the spec did not list, so
a client following the spec got a 400 from the extractor.
- 400 bodies are plain-text messages, not `{"message": …}` JSON.
- `/metrics` requires `Authorization: Bearer <metrics_token>` when the
deployment configures one; the spec said firewall-only and listed no 401.
- `/filedownload/{uuid}` answers `Range` requests with 206 or 416 and never
returns the documented 400, and it sets no `Content-Type` at all.
- Missing responses: 400/422/500 on init, 500/503 on chunk PUT, 500 on
finalize, 503 on `/usage`.
- `PayloadTooLarge` gained `resets_at` on the rolling-window 413.
`api_routes()` is now the single mount list, and `mod api_description_tests`
compares it against the spec so a route can no longer be added, removed, or
renamed without the spec following.
Refs #247
* docs: correct four spec statements and document the exclusive Content-Range end
Follow-up on the review of this PR. All five were verified against the
handlers, not inferred from the spec:
- `PUT /fileupload/{uuid}`: the `Content-Range` end byte is exclusive
(`upload_chunk` uses `end - start`), which no version of the spec has ever
said. An external client following RFC 7233 sends one byte too many and gets
a 400. Documented on the parameter, since the spec is the contract pg-js and
pg-dotnet are written against.
- `GET /filedownload/{uuid}`: `Accept-Ranges: bytes` is set on the response
builder after both 404 early returns, so it is not "always" advertised.
Reworded, and added the header to the 416, which does carry it.
- `GET /filedownload/{uuid}`: added the 500 from a failed `file.seek` on the
range path.
- `POST /fileupload/init`: the 400 also covers a syntactically invalid JSON
body, which Rocket answers with its default error page rather than the
plain-text message. Same caveat the chunk and finalize 400s already carry.
- `StagingPreview.confirmation`: null whenever `state.sender` is unset, i.e.
when previewing before finalize — the normal staging flow, and the case the
old wording excluded.
cargo fmt/clippy/test all pass (154 tests); the spec still validates as
OpenAPI 3.0.3 with all $refs resolving.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: dobby-yivi-agent[bot] <275734547+dobby-yivi-agent[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit b2a0cda35e4646797ab2e3e3bc952685236647fb)
#203) * ci: pin the oasdiff gate's settings with a mutation test `fail-on: ERR` with no `include-checks`, the setting the gate landed with in #201, fails open on ten of the changes the spec's contract forbids. Measured against oasdiff v1.26.1 by mutating the real spec one way per rule: a `401` that becomes a `403` and a dropped response enum value rate ERR but are opt-in, and a removed or renamed optional response property, a removed request parameter, a removed request property and the constraint-narrowing `*-set` family all rate WARN. Cryptify's routes are unversioned, so each of those reaches every pinned client the moment it deploys. Adds `mod api_gate_tests` in `src/main.rs`, which runs the engine with the flags the action's entrypoint builds and asserts stop-or-pass per mutation. It fails on the shipped settings and names every gap; it passes on `fail-on: WARN` plus the two opt-in checks. The engine is not on any runner, so it skips in CI while `every_api_gate_mutation_still_applies` still runs there. The workflow itself is in a PR comment: the App has no `workflows: write`. Refs #202. Part of #247 (workstream C). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci: assert the workflow runs the settings the gate test pins Review on #203: `FAIL_ON` and `INCLUDE_CHECKS` described settings nothing tied to the committed workflow, which still says `fail-on: ERR` with no `include-checks`, and `cargo test` was green anyway. So the branch shipped a passing test certifying a configuration the gate does not use, which is the fail-open the module exists to catch. `the_workflow_uses_the_settings_this_module_pins` reads `.github/workflows/api-diff.yml` and asserts its two step inputs are the two constants. It needs no `oasdiff`, so it runs on every runner. It is red until a maintainer applies the workflow patch from the PR comment, which makes the merge order enforced rather than documented. Also from the review: CLAUDE.md now says the settings are pending rather than present-tense fact and names where the patch lives; the tier counts are the `-f json` ones (WARN adds 30 on top of ERR's 212, not 31 on 213 — the table output has a header row and a trailing blank); the versioned-route mutation goes through `once` like every other anchor, so a renamed `getUsage` fails loudly instead of no-op'ing; and a root `.gitattributes` marks the spec `-text` so the multi-line anchors survive a Windows checkout. Refs #202. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test: assert the api gate still runs, not only what it runs with `the_workflow_uses_the_settings_this_module_pins` pinned the gate's two inputs but not whether the job is ever triggered, which is the one fail-open it could not see: settings that read correctly on a gate that never fires. The workflow's own header comment names the foreseeable edit ("There is deliberately no `on: paths:` filter"), and a later `paths:` would skip the gate on every PR that does not touch the spec with the test still green. Asserts a `pull_request` trigger, no `paths:`/`paths-ignore:` filter, and no `if:` on the job. The trigger check is line-based over the `on:` block rather than a byte-exact match, so a rewrite between the mapping, list and scalar forms of `on:` does not go red for nothing. * ci: tune the oasdiff gate to WARN with the two opt-in ERR checks Dobby's YAML from the #203 comment, applied verbatim (the App cannot push workflows). This is the half its guard test was failing on: the_workflow_uses_the_settings_this_module_pins reads this file and asserts fail-on and include-checks match the constants the test module pins, so the settings and the mutation test that certifies them cannot land apart. At fail-on=ERR with no include-checks the gate passed ten of the changes this spec's contract forbids. --------- Co-authored-by: dobby-yivi-agent[bot] <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Ruben Hensen <ruben.hensen@protonmail.com>
Second half of the #255 spike. The commit before this one is the history-preserving subtree import; this one makes it build, test and release as a workspace member. Resolver: pg-pkg's sqlx and cryptify's rusqlite both reach libsqlite3-sys, which sets `links = "sqlite3"`, and only one package per resolve graph may claim a given `links` value. Optional dependencies sit in the graph whether or not their feature is on, so pg-pkg pulls sqlx-sqlite in despite enabling only `postgres`. sqlx-sqlite 0.9.0 wants libsqlite3-sys >=0.30.1,<0.38 and rusqlite 0.40.1 wants ^0.38.1: empty intersection, workspace does not resolve. sqlx 0.9 + rusqlite 0.39 is the newest pair that does. - root Cargo.toml: cryptify joins `members` - cryptify: pg-core becomes a path dependency; own Cargo.lock, release-plz.toml, .github/ and duplicate profile blocks dropped - build.rs: walk up for Cargo.lock — a member has none beside its manifest, and the panic would have been at build time, not resolve time - api-diff.yml: one matrix over both specs, not two jobs. Both api-gate suites require exactly one `fail-on:`/`include-checks:` line in the file - build.yml: matrices key on the crate directory so `cryptify` fits; this renames the `Test/Format/Clippy workspace` contexts - Dockerfile: build context moves to the repo root
Root .gitignore's `*.pem` matches the four fixtures #241 committed under pg-pkg/testdata/jwt_rotation/, leaving them tracked *and* ignored. release-plz refuses to run against that, so delivery.yml's `Release-plz PR` job has failed on every push to main since #241 landed on 2026-07-16 (12 runs). `Release-plz release` is unaffected, which is why nothing looked broken. Belongs in its own PR, not the cryptify spike; carried here only so the spike could get far enough to answer the release-plz question on #255.
Neither failure shows up in the test matrix; both are Docker-only. `cargo chef prepare` loads every workspace member's manifest, including ones the build never compiles, so the Dockerfile's member-by-member COPY list has to gain cryptify or the image fails with "failed to load manifest for workspace member /app/cryptify". With that fixed it fails again on MSRV: sqlx 0.9 declares rust-version 1.94.0 and this image pinned rust 1.91.1. sqlx 0.9 is what resolves the libsqlite3-sys `links` conflict, so the pin moves rather than the dependency. 1.96.1-slim-trixie is what cryptify's own Dockerfile already used, so both images now agree. Verified by building both: pg-pkg-spike:edge (192MB), cryptify-spike:edge (170MB).
Brings in the 0.6.2/0.3.7/0.6.0/0.1.3 release (#187) and the .gitignore fix (#274). Two conflicts: - .gitignore: took main's wording, which is what #274 actually shipped. The spike carried an earlier, terser version of the same fix. - cryptify's pg-core requirement moved 0.6.1 -> 0.6.2 by hand. release-plz maintains these reqs on unpublished members too (pg-pkg carries one), but it could not touch cryptify's during #187 because cryptify was not yet a member. Merged rather than rebased on purpose: rebasing replays all 296 imported commits onto main and discards the subtree merge.
Open
This was referenced Aug 4, 2026
Closed
rubenhensen
added a commit
that referenced
this pull request
Aug 4, 2026
Both findings re-derived before applying; both hold. The vector was wrong, and wrongly framed. ba380a1 has a single parent, so PR #277 was squash-merged, and squash_merge_commit_message = COMMIT_MESSAGES concatenates every imported commit message into the merge body -- 1678 lines carrying all 17 refs. That is what closed #146, four seconds after it landed. The history re-attach (9887e1a, a two-parent merge) carries no keywords of its own and landed thirteen minutes later, when #146 was already closed, so it cannot be the cause. The bullet said the opposite, which would teach the next agent that a plain squash merge of an import PR is safe. It is sufficient on its own, and it fires precisely because squashing concatenates the messages while discarding the history. That also surfaces a cheaper defusal than filter-repo: edit the squash body at merge time, no SHA rewrite. The commit_id heuristic was falsified by the bullet's own example. It claimed a keyword close carries a commit_id and a hand close does not, but #273 -- named right there as legitimately closed by a55c6a0, whose message ends in Closes #273 -- has commit_id: null. The discriminator is where the keyword sat: a commit-message keyword is attributed to the commit and carries the sha, a PR-body keyword is attributed to the PR and carries null. #146 has the sha because PR #277's body said only Closes #255; #273 has null because PR #274's body said Closes #273. So null means "not attributed to a commit", not "closed by hand", and the old rule would have called a real keyword close a hand close. Also fixed a stale cross-reference: the squash setting is documented in the release-plz bullet above, not below.
rubenhensen
added a commit
that referenced
this pull request
Aug 4, 2026
* docs: true up CLAUDE.md with the cryptify workspace merge Root CLAUDE.md still described a four-crate workspace with cryptify as a separate repo, and mentioned cryptify zero times, though ba380a1 (#277) merged it as a fifth member on 2026-07-31. Found while reconciling the #247 map tree (#281). What was stale or missing: - Workspace members, the per-crate CI invocations (cryptify has no features of its own, so --all-targets), and the check-context rename from Test workspace (core) to Test workspace (pg-core) that came with keying the matrices on the crate directory. - The standing sqlx<->rusqlite coupling the merge deliberately bought: one links = "sqlite3" claimant per graph, optional deps in the graph whether enabled or not, so sqlx 0.9 + rusqlite 0.39 is the only intersecting pair and every rusqlite bump waits on sqlx's ceiling. - api-diff.yml is now a two-spec matrix, and its single fail-on and include-checks literals are load-bearing: both pg-pkg/tests/api_gate.rs and cryptify's in-crate api_gate_tests grep for exactly one of each. - Two Docker builds, cryptify's built from the repo root, both on rust:1.96.1-slim-trixie. - cryptify's release-plz entry, and the hand-placed cryptify-v0.1.27 floor tag that stops release-plz changelogging the whole import. - cryptify/CLAUDE.md exists, so root is no longer the single home. - cryptify keeps a renamed rand08 dev-dependency because pg-core's Sealer/TestSetup are still on rand 0.8 while the crate runs 0.10. Two facts the reconciliation itself established: - pg-core 0.6.2 is the first published release off the abandoned irma 0.2.1 and reqwest 0.11 (#263), which is why pg-compat's =0.6.1 pin resolves a tree unlike the workspace's -- and that pin plus COMPATIBILITY.md's crates.io reader row are now a release behind, with no gate reading them (#268). - A history-preserving import silently closes issues here: the cryptify re-attach carried 18 closing keywords resolved against this repo's numbering and closed live issue #146. Includes the audit command and the timeline query that distinguishes a keyword close from a hand one. * docs: correct the counts, the links declarant and the format matrix Review fixes on #287, all four verified against the repo first. - Line 13 still said `cargo fmt --manifest-path pg-<crate>/...`, which expands to pg-cryptify now that the matrix keys on the crate directory. The file contradicted the bullet six lines above it. - The import bullet over-counted: ba380a1 carries 17 closing references, not 18. #273 came from a55c6a0 (#274), an ordinary commit the day before, so the original range e642786..origin/main swept in keywords the import never carried. 16 no-ops, not 17. Records the range mistake itself, since the bullet's command is what the next agent runs. - #146 stood four days, not five, and the bullet now says it was reopened -- a reader who checks and finds it open doubts the rest. - links = "sqlite3" is declared by libsqlite3-sys, not rusqlite; the relative clause named the wrong claimant. Confirmed with cargo metadata: rusqlite, sqlx and sqlx-sqlite all declare none. The error came in from #285's body, so it was inherited rather than invented. - The cryptify image bullet read present-tense, but build/scan/finalize are all gated on vars.PUBLISH_CRYPTIFY_IMAGE and skip while it is unset. CLAUDE.md is auto-loaded, so a present-tense claim about a job that does not run is the expensive kind of wrong. The required-checks parenthetical held up, and is now stronger for being challenged: branch protection on main requires exactly the two Wire compat contexts. That list lives in classic branch protection, NOT in the main ruleset (which has no required_status_checks rule), and dobby-coder gets 403 on the protection endpoint -- so an agent reading the ruleset alone concludes nothing is required. Worth documenting, so it is. * docs: fix the close vector and the commit_id rule in the import bullet Both findings re-derived before applying; both hold. The vector was wrong, and wrongly framed. ba380a1 has a single parent, so PR #277 was squash-merged, and squash_merge_commit_message = COMMIT_MESSAGES concatenates every imported commit message into the merge body -- 1678 lines carrying all 17 refs. That is what closed #146, four seconds after it landed. The history re-attach (9887e1a, a two-parent merge) carries no keywords of its own and landed thirteen minutes later, when #146 was already closed, so it cannot be the cause. The bullet said the opposite, which would teach the next agent that a plain squash merge of an import PR is safe. It is sufficient on its own, and it fires precisely because squashing concatenates the messages while discarding the history. That also surfaces a cheaper defusal than filter-repo: edit the squash body at merge time, no SHA rewrite. The commit_id heuristic was falsified by the bullet's own example. It claimed a keyword close carries a commit_id and a hand close does not, but #273 -- named right there as legitimately closed by a55c6a0, whose message ends in Closes #273 -- has commit_id: null. The discriminator is where the keyword sat: a commit-message keyword is attributed to the commit and carries the sha, a PR-body keyword is attributed to the PR and carries null. #146 has the sha because PR #277's body said only Closes #255; #273 has null because PR #274's body said Closes #273. So null means "not attributed to a commit", not "closed by hand", and the old rule would have called a real keyword close a hand close. Also fixed a stale cross-reference: the squash setting is documented in the release-plz bullet above, not below. * docs: state both import close-vectors, drop the unverified defusal Cycle-3 review findings, both re-derived from primary sources first. The blocking one is right and my previous fix overcorrected. "Preserving the history is not the trigger" is false as a general rule, and the counterexample is the case the bullet cites as its own guard. Measured on postguard-js: import PR #137 merged with a REAL two-parent merge commit (b1bb2ee, three-line message, zero closing keywords, no squash body anywhere), and js#128/#129 closed six seconds later attributed to imported commits a0ce27f/2acf42f -- single-parent, committed 2026-06-04 -- then reopened fifteen minutes later. In the history-preserving case GitHub never reads the merge commit's message; it attributes the closes to the imported commits once they become reachable from the default branch. What postguard's own incident supports is weaker than what I wrote: the squash body got there first, and by the time 9887e1a landed all 17 targets were already closed, so GitHub had nothing left to close. That vector was untested here, not disproven. The bullet now states both as independently sufficient, which also matters because the squash-body defusal does not exist on vector 2 -- there is no body to edit. Dropped the commit_message-on-PUT-/pulls/N/merge recipe rather than keep it. The REST reference words that field "Extra detail to append to automatic commit message" and documents nothing about merge_method=squash, so whether it replaces the concatenated body is unverified -- and if it appends, the defusal silently no-ops and every keyword still fires. Recorded as an open question to confirm on a throwaway repo, not as a recipe. Non-blocking: the sqlx ceiling is binding today, not future. rusqlite 0.40.0 wants libsqlite3-sys ^0.38.0 and 0.40.1 wants ^0.38.1, both published and unyanked, both outside sqlx-sqlite 0.9.0's >=0.30.1, <0.38.0 -- so a lone rusqlite bump does not resolve at all. "Future" invited a reader to treat the current pins as headroom and misdiagnose the resolver error.
rubenhensen
pushed a commit
to encryption4all/postguard-docs
that referenced
this pull request
Aug 5, 2026
* docs: correct the notify-shape claim in the pg-js upload options (from encryption4all/postguard-js#151) The SDK never shipped a notify-shape validator; the pg-js test suite pins its removal. `{ notify: true }` sends no mail and suppresses the silent-upload notice instead of throwing. * docs: point the repo pages at the postguard-js and postguard workspaces postguard-website, postguard-tb-addon, postguard-outlook-addon and postguard-examples were folded into the postguard-js pnpm workspace and archived (encryption4all/postguard-js#132, #134, #137, #141, #145). Cryptify's source moved into the postguard Cargo workspace, with image publishing left behind in its own repository (encryption4all/postguard#277). --------- Co-authored-by: dobby-yivi-agent[bot] <275734547+dobby-yivi-agent[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #255. The decision comment on that issue has the full spike writeup and the measurements; this is the change it recommends.
cryptify becomes a workspace member, compiled against the in-tree
pg-core. All 402 of its commits come across as a history-preserving subtree merge.Why
Today postguard builds pg-core against bincode-next 3.0.0-rc.14 and cryptify builds pg-core against bincode-next 3.1.1, because each repo resolves its own lockfile. Same source, two versions of the serializer that writes the container header, in two services that exchange containers, with nothing comparing them. CLAUDE.md's rule (pin the exact rc, re-audit on any bump) is not reaching cryptify's copy.
That is benign right now, and provably so:
pg-compat's lockfile is also on 3.1.1, so the green wire-compat check already demonstrates rc.14-sealed bytes opening under 3.1.1. But it falls out of another gate's dependency resolution by accident, and the next bincode bump is unguarded. One workspace is one lockfile, and the #127 memory-vs-stream class becomes a PR-time test failure rather than a cross-repo surprise.The price
libsqlite3-syssetslinks = "sqlite3"and only one package per resolve graph may claim it. pg-pkg reaches it throughsqlx, cryptify throughrusqlite. Optional deps are in the graph regardless of features, so pg-pkg pullssqlx-sqlitein despite enabling onlypostgres.Empty intersection. The newest pair that intersects is sqlx 0.9 + rusqlite 0.39, so this PR carries both. pg-pkg needed no source changes for sqlx 0.9.
The standing cost, and the part worth a deliberate yes: every future rusqlite bump now waits on sqlx's
libsqlite3-sysceiling. cryptify is already one release ahead of what sqlx permits.What else had to change, and why
cryptify/build.rsreadCargo.lockbeside its manifest to stampPG_CORE_VERSIONinto theX-PostGuardheader. A member has no lockfile there, so it now walks up fromCARGO_MANIFEST_DIR.api-diff.ymlcovers both specs via a matrix, not two jobs.pg-pkg/tests/api_gate.rsand cryptify'smod api_gate_testseach require exactly onefail-on:and oneinclude-checks:line in that file, so neither can claim a setting the committed job does not use. Two oasdiff steps would give two of each and fail both suites.build.yml's matrices key on the crate directory instead of apg-prefix, which renames theTest/Format/Clippy workspace (…)contexts. Only the twoWire compatcontexts are required onmain, so nothing breaks, but a ruleset added later needs the new names.COPY cryptify(cargo-chef loads every member's manifest even for a build that never compiles it) and its Rust pin moved 1.91.1 → 1.96.1, because sqlx 0.9 declaresrust-version = 1.94.0. Both of those passed the whole test matrix on host stable and failed only in Docker..github/,Cargo.lockandrelease-plz.tomlare gone, replaced by an entry in the rootrelease-plz.toml.Verification
Run locally on this branch, mirroring
build.yml's per-crate invocations:pg-core(test,rust,stream)pg-pkg(all-features)pg-cli(all-features)pg-ffi(all-features)cryptify(all-targets)cryptifyclippy-D warningscargo fmt --all --checkBoth images build:
docker build .anddocker build -f cryptify/Dockerfile ..Deliberately not in this PR
Image publishing still comes from the cryptify repo.
delivery.yml's Docker jobs push toghcr.io/${{ github.repository }}, so publishing cryptify's image from here means either renaming it away fromghcr.io/encryption4all/cryptifyor changing that package's GHCR access. Either affects whatever pulls it, so it is an ops decision rather than a mechanical port.That makes this a coherent intermediate state, not a half-finished one: postguard becomes source of truth for the code and gets the one-lockfile guarantee immediately, while the cryptify repo keeps building and pushing images exactly as it does today. Decommissioning it is a follow-up, and it needs privacybydesign/postguard-ops in the loop.
Also outstanding: release-plz will treat cryptify as an initial release. A subtree import brings commits, not tags, and the old tags were bare
v0.1.27against this workspace'scryptify-v{version}pattern. Without a hand-placedcryptify-v0.1.27tag on the import commit, the first changelog entry covers all 402 imported commits. Worth doing before this lands onmain.Reviewing this
The subtree merge makes the diff large.
git diff main...HEAD -- . ':!cryptify'is the 8-file change to the existing tree; everything undercryptify/is imported as-is apart fromCargo.toml,build.rs,Dockerfileand the oneinclude_str!path insrc/main.rs.