From 9f06e574b255f87ba0a3a243f4bc7d23abf7f523 Mon Sep 17 00:00:00 2001 From: "dobby-coder[bot]" Date: Mon, 27 Jul 2026 07:58:21 +0000 Subject: [PATCH 1/7] ci(pg-dotnet): restore from a committed packages.lock.json pg-dotnet was the only sub-project without a lockfile, so a CI job that builds every sub-project from its lockfile had nothing to lock against. RestorePackagesWithLockFile keeps packages.lock.json in step with the csproj on a normal restore; CI adds --locked-mode, which fails with NU1004 when a PackageReference moves without the lockfile being regenerated. --- pg-dotnet/PostGuard.Example.csproj | 1 + pg-dotnet/packages.lock.json | 223 +++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 pg-dotnet/packages.lock.json diff --git a/pg-dotnet/PostGuard.Example.csproj b/pg-dotnet/PostGuard.Example.csproj index 9444898..155293d 100644 --- a/pg-dotnet/PostGuard.Example.csproj +++ b/pg-dotnet/PostGuard.Example.csproj @@ -6,6 +6,7 @@ enable enable a6cc2768-9070-459f-8eed-ea999be6e096 + true diff --git a/pg-dotnet/packages.lock.json b/pg-dotnet/packages.lock.json new file mode 100644 index 0000000..002a96e --- /dev/null +++ b/pg-dotnet/packages.lock.json @@ -0,0 +1,223 @@ +{ + "version": 1, + "dependencies": { + "net10.0": { + "E4A.PostGuard": { + "type": "Direct", + "requested": "[0.4.1, )", + "resolved": "0.4.1", + "contentHash": "Uo2I1+WJ1JjXHzT51wXIiuTCgR8InNpat0G+H+Fp1g2bDW9cC9ou0unxT2gtIXP0PuDXpUGv98AZqs7y/h623g==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "ehZcoPbjzWzS4XFvuz7R3V55SmpdkyMqFURLH3yXaN9NtXd9tR6CGB7pd49HYtCkenl+G7ctXSFLhNI08xLfRg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "bVGqctAfPGfTxJvNp8pMshtvpsUj6r6JkeiCNVIGVYO5gBxuxdN0Lbr25kEvE/zXdctkEc44g8HssnPgDnFGVA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "6XTfFOnf27WY8kEeZkTZ4YNn0t+imgvdQ0YaAdR4vgURKATo9bCaVJ1KB71IOJAQtJP7Elb53VHlTNXg2CtSsA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Json": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "I63esIFbL3h5pSt7gXpXOlmcwDmYBUoYNEglKfDPFUqtYvSV84f2l28hO2lfVXsV0wdlplgAM7IVz16matapSg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "1g9mzuu8gIHkjYb0jLxOTQVl/QDG5nn0b0JzgT/gbgNKr6gXZzxOHRAsdYRc1eDApB7LdHR8uK5vQrNjIQdRrQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "KLtAZ6A38s1pIfCO2ns6aG14NNGMYNZ4PBYfFK4M+R4A+xuSc6oklhqDcpHZxvDpyBWeFtR5C8iQBw2ng8tUHQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "U+oquaPxFdY8lYeEIWO/AD7jDIl9sPW6aVWMQRHU/pZ/SWpLcOrAj2fcLe1HwXl4sYw1ONI56K/eELT3xr4RRQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "GkPvQe6IdidLu6Q3Lw6+B8NJpW8feW8czZ5mBKt5rXM/x8MvZfEp5WvAsjznzDGd23chIDrW0b2mmt+ScnEgiw==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileSystemGlobbing": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "IUQet3SY51xIFcFZKtAB6a54/Zdxs7T3SQ84kJtOD6yeXfZgiOMksACWD5qtTmXGQGFH4QYGBOT0KIO8Uy/dJw==" + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "OBPo4nYhMyIbtueoC10CBm6AGAbo/A9IV8QQ/6ryZS7VvmqpGT7hunazeHLxFawRzn3oLOq4jhqhpBX4tfswWQ==" + } + }, + "net8.0": { + "E4A.PostGuard": { + "type": "Direct", + "requested": "[0.4.1, )", + "resolved": "0.4.1", + "contentHash": "Uo2I1+WJ1JjXHzT51wXIiuTCgR8InNpat0G+H+Fp1g2bDW9cC9ou0unxT2gtIXP0PuDXpUGv98AZqs7y/h623g==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "ehZcoPbjzWzS4XFvuz7R3V55SmpdkyMqFURLH3yXaN9NtXd9tR6CGB7pd49HYtCkenl+G7ctXSFLhNI08xLfRg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "bVGqctAfPGfTxJvNp8pMshtvpsUj6r6JkeiCNVIGVYO5gBxuxdN0Lbr25kEvE/zXdctkEc44g8HssnPgDnFGVA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Direct", + "requested": "[10.0.8, )", + "resolved": "10.0.8", + "contentHash": "6XTfFOnf27WY8kEeZkTZ4YNn0t+imgvdQ0YaAdR4vgURKATo9bCaVJ1KB71IOJAQtJP7Elb53VHlTNXg2CtSsA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.Json": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "I63esIFbL3h5pSt7gXpXOlmcwDmYBUoYNEglKfDPFUqtYvSV84f2l28hO2lfVXsV0wdlplgAM7IVz16matapSg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "1g9mzuu8gIHkjYb0jLxOTQVl/QDG5nn0b0JzgT/gbgNKr6gXZzxOHRAsdYRc1eDApB7LdHR8uK5vQrNjIQdRrQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileProviders.Physical": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "KLtAZ6A38s1pIfCO2ns6aG14NNGMYNZ4PBYfFK4M+R4A+xuSc6oklhqDcpHZxvDpyBWeFtR5C8iQBw2ng8tUHQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.8", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.8", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.8", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "System.Text.Json": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "U+oquaPxFdY8lYeEIWO/AD7jDIl9sPW6aVWMQRHU/pZ/SWpLcOrAj2fcLe1HwXl4sYw1ONI56K/eELT3xr4RRQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "GkPvQe6IdidLu6Q3Lw6+B8NJpW8feW8czZ5mBKt5rXM/x8MvZfEp5WvAsjznzDGd23chIDrW0b2mmt+ScnEgiw==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.8", + "Microsoft.Extensions.FileSystemGlobbing": "10.0.8", + "Microsoft.Extensions.Primitives": "10.0.8" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "IUQet3SY51xIFcFZKtAB6a54/Zdxs7T3SQ84kJtOD6yeXfZgiOMksACWD5qtTmXGQGFH4QYGBOT0KIO8Uy/dJw==" + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "OBPo4nYhMyIbtueoC10CBm6AGAbo/A9IV8QQ/6ryZS7VvmqpGT7hunazeHLxFawRzn3oLOq4jhqhpBX4tfswWQ==" + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "STVNTIVd+UrYvSo31D2tOOTs19IThdjGDN14FS3/NZb4PWsVakAg/VT4sq+JYWGP/GtazzvXwebWL6LPBAwnFQ==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "HvFIJXM/CMTRu6PBQmjukQZ/O32Vx5fOEYZs0kq0OD5s9vJQ19KHLWCVmnfh3gNC7pbYomm06tzOTgJBflr/nQ==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "10.0.8", + "contentHash": "/fysUDkD7oFGaRPoA7IaFs0wRoO3GlwlCNq2P+xWZqxLy1R4cktRSKfMjJDy9ymS4grL7IDVdt8de8L9a0z55Q==", + "dependencies": { + "System.IO.Pipelines": "10.0.8", + "System.Text.Encodings.Web": "10.0.8" + } + } + } + } +} \ No newline at end of file From 576c668379b3a330da495d938c13162f6b1d3b26 Mon Sep 17 00:00:00 2001 From: "dobby-coder[bot]" Date: Mon, 27 Jul 2026 07:58:21 +0000 Subject: [PATCH 2/7] ci(pg-node): add a check script as the build-gate equivalent pg-node has no bundler, so a build job for it would only prove that npm ci succeeds. The check runs node --check on the CLI entry point and imports src/encryption.mjs, which resolves the named imports against the installed @e4a/pg-js and so fails when an export is renamed or removed. --- pg-node/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pg-node/package.json b/pg-node/package.json index feb05d8..131bc43 100644 --- a/pg-node/package.json +++ b/pg-node/package.json @@ -8,7 +8,8 @@ "scripts": { "start": "node --env-file-if-exists=.env index.mjs", "send": "node --env-file-if-exists=.env index.mjs", - "upload": "node --env-file-if-exists=.env index.mjs --upload-only" + "upload": "node --env-file-if-exists=.env index.mjs --upload-only", + "check": "node --check index.mjs && node --input-type=module -e \"await import('./src/encryption.mjs')\"" }, "dependencies": { "@e4a/pg-js": "^1.10.0" From 6d10682182e3e720dbaf3db07b150e42f0471e08 Mon Sep 17 00:00:00 2001 From: "dobby-coder[bot]" Date: Mon, 27 Jul 2026 07:58:21 +0000 Subject: [PATCH 3/7] docs: record lockfile build commands and the workflows-permission block Adds the per-sub-project install-and-build commands, the packages.lock.json regeneration rule, and a note that the dobby-coder App cannot push files under .github/workflows/ so workflow changes arrive as a patch to apply. --- CLAUDE.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index fbb58e0..4fc77c4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,21 @@ Example applications demonstrating PostGuard integration. No release automation; - `pg-manual/`: Webpack 5 + plain JS using `@e4a/pg-wasm` directly (low-level). ## CI / tests -Only a Conventional Commit PR-title check runs in CI; there's no build/test CI and no test suite (example/reference code only). Code snippets in docs.postguard.eu are sourced from this repo, see postguard-docs' CLAUDE.md for the source-link conventions and the consolidation-commit gotcha. +There is no test suite (example/reference code only). Code snippets in docs.postguard.eu are sourced from this repo, see postguard-docs' CLAUDE.md for the source-link conventions and the consolidation-commit gotcha. + +`.github/workflows/pr-title.yml` lints the PR title against Conventional Commits. Two build workflows (`ci.yml`, a PR gate that builds every sub-project from its lockfile, and `sdk-canary.yml`, a weekly build against the latest published SDKs that opens an issue on failure) were proposed as YAML in a PR comment rather than committed: the `dobby-coder` GitHub App has no `workflows` permission, so any push touching `.github/workflows/` is rejected by the remote with "refusing to allow a GitHub App to create or update workflow". Agents must deliver workflow changes here as a patch for a human to apply. + +Every sub-project builds reproducibly from a committed lockfile: + +| Sub-project | Install from lockfile | Then | +| --- | --- | --- | +| `pg-manual` | `npm ci` | `npm run build` | +| `pg-node` | `npm ci` | `npm run check` | +| `pg-sveltekit` | `npm ci` | `npm run build && npm run check && npm run lint` | +| `pg-dotnet` | `dotnet restore --locked-mode` | `dotnet build --no-restore` | + +- `pg-node` has no bundler, so `npm run check` is its build equivalent: `node --check` on the CLI entry point plus an ESM import of `src/encryption.mjs`. The import is the part that matters: it resolves the named imports against the installed `@e4a/pg-js`, so a renamed or removed export fails the check. It does not catch signature-level changes behind an unchanged export name. +- `pg-dotnet` restores with a lockfile (`packages.lock.json`, enabled by `RestorePackagesWithLockFile`). After changing any `PackageReference`, run `dotnet restore` and commit the regenerated `packages.lock.json` in the same commit, otherwise `--locked-mode` fails with `NU1004: the package reference ... has changed`. ## Known issues / intentional non-fixes - `pg-manual/webpack.config.js` hardcodes `mode: 'development'` intentionally. This is an example app and does not need a production build; don't refile or propose a fix for the dev-only webpack mode. From fcde281ec6553584b53e9e8cb7b5f1df742c9ffb 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 08:20:11 +0000 Subject: [PATCH 4/7] ci(pg-manual): fail the build when @e4a/pg-wasm drops an export The examples load the SDK through a dynamic import('@e4a/pg-wasm') and destructure at runtime, so webpack never analyses those bindings. Renaming the destructured `seal` binding leaves `npm run build` at exit 0 with zero warnings, and `webpack --fail-on-warnings` at exit 0 too, so both the PR gate and the weekly canary would stay green through a breaking pg-wasm release. pg-wasm is only used here, so it had no coverage at all. `npm run check` builds check/sdk-exports.js, which imports the same names statically, with --fail-on-warnings. Dropping `seal` from the SDK now exits 1 while `npm run build` still succeeds. Both workflows already run `npm run check --if-present`, so the proposed YAML is unchanged. Also correct CLAUDE.md's claim about what pg-node's check covers: it never links index.mjs's own imports, only src/. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 13 +++++++++---- pg-manual/check/sdk-exports.js | 15 +++++++++++++++ pg-manual/check/webpack.config.js | 22 ++++++++++++++++++++++ pg-manual/package.json | 1 + 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 pg-manual/check/sdk-exports.js create mode 100644 pg-manual/check/webpack.config.js diff --git a/CLAUDE.md b/CLAUDE.md index 4fc77c4..e0ccb52 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,18 +14,23 @@ Example applications demonstrating PostGuard integration. No release automation; ## CI / tests There is no test suite (example/reference code only). Code snippets in docs.postguard.eu are sourced from this repo, see postguard-docs' CLAUDE.md for the source-link conventions and the consolidation-commit gotcha. -`.github/workflows/pr-title.yml` lints the PR title against Conventional Commits. Two build workflows (`ci.yml`, a PR gate that builds every sub-project from its lockfile, and `sdk-canary.yml`, a weekly build against the latest published SDKs that opens an issue on failure) were proposed as YAML in a PR comment rather than committed: the `dobby-coder` GitHub App has no `workflows` permission, so any push touching `.github/workflows/` is rejected by the remote with "refusing to allow a GitHub App to create or update workflow". Agents must deliver workflow changes here as a patch for a human to apply. +`.github/workflows/pr-title.yml` lints the PR title against Conventional Commits. + +The `dobby-coder` GitHub App has no `workflows` permission, so any push touching `.github/workflows/` is rejected by the remote with "refusing to allow a GitHub App to create or update workflow". Agents must deliver workflow changes here as a patch for a human to apply. + +Pending, remove this paragraph once it lands: two build workflows are written but not committed, for that reason. `ci.yml` is a PR gate that builds every sub-project from its lockfile; `sdk-canary.yml` is a weekly build against the latest published SDKs that opens an issue on failure. The YAML is in a comment on [#66](https://github.com/encryption4all/postguard-examples/pull/66). Every sub-project builds reproducibly from a committed lockfile: | Sub-project | Install from lockfile | Then | | --- | --- | --- | -| `pg-manual` | `npm ci` | `npm run build` | +| `pg-manual` | `npm ci` | `npm run build && npm run check` | | `pg-node` | `npm ci` | `npm run check` | | `pg-sveltekit` | `npm ci` | `npm run build && npm run check && npm run lint` | | `pg-dotnet` | `dotnet restore --locked-mode` | `dotnet build --no-restore` | -- `pg-node` has no bundler, so `npm run check` is its build equivalent: `node --check` on the CLI entry point plus an ESM import of `src/encryption.mjs`. The import is the part that matters: it resolves the named imports against the installed `@e4a/pg-js`, so a renamed or removed export fails the check. It does not catch signature-level changes behind an unchanged export name. +- `pg-node` has no bundler, so `npm run check` is its build equivalent: a `node --check` syntax pass over `index.mjs`, plus an ESM import of `src/encryption.mjs`. The import is the part that matters: it resolves `src/`'s named imports against the installed `@e4a/pg-js`, so a renamed or removed SDK export fails the check. Two things it does not cover. `index.mjs`'s own imports are never linked, because importing `index.mjs` would run the CLI, so renaming an export in `src/config.mjs` still passes the check and only fails at `node index.mjs`. And it says nothing about signature changes behind an unchanged export name. +- `pg-manual`'s `npm run check` is a second, tiny webpack build (`check/webpack.config.js`) run with `--fail-on-warnings`. It exists because the examples reach `@e4a/pg-wasm` through a dynamic `import()` and destructure at runtime, which webpack does not analyse: drop `seal` from the SDK and `npm run build` still compiles successfully with zero warnings. `--fail-on-warnings` on the real build does not close that, it only sees static imports. `check/sdk-exports.js` imports the same names statically so the analysis applies. Add a name there whenever an example destructures a new one. - `pg-dotnet` restores with a lockfile (`packages.lock.json`, enabled by `RestorePackagesWithLockFile`). After changing any `PackageReference`, run `dotnet restore` and commit the regenerated `packages.lock.json` in the same commit, otherwise `--locked-mode` fails with `NU1004: the package reference ... has changed`. ## Known issues / intentional non-fixes @@ -45,4 +50,4 @@ Every sub-project builds reproducibly from a committed lockfile: - `@privacybydesign/yivi-{core,client,popup}` 0.2 to 1.x changed module shape: v0.2 was CJS (`module.exports = class Foo`) and worked under `import * as Foo from '...'` via webpack's CJS interop; v1.0 ships proper ESM with named exports, so `import * as` no longer gives a callable constructor. Switch to `import { YiviCore } from '@privacybydesign/yivi-core'` (same for `YiviClient`/`YiviPopup`); `yivi-css` stays a bare side-effect import. The `yivi.use(...)` plugin contract is unchanged, expect the named-import migration to be the only code change on future yivi majors. - Bumping `webpack-dev-server` does NOT auto-refresh its already-locked transitive `ws` / `http-proxy-middleware` / `launch-editor` versions, even when the new wds's declared ranges already permit the CVE-fixed versions. After bumping `webpack-dev-server`, run `npm update ws http-proxy-middleware launch-editor` in `pg-manual` to pull the fixed versions in-range, then re-check `npm audit`. No `overrides` entry is needed for these three. - Dependency override: `overrides.uuid: ^11.1.1`. The chain is `uuid <- sockjs <- webpack-dev-server`; sockjs's latest still pins `uuid ^8.3.2` (GHSA-w5hq-g745-h8pq), so no in-range fix exists. Use 11.1.1, not 12+: uuid 12+ ships ESM-only (`type: module`), while sockjs `require()`s it; 11.1.1 is the newest patched line with a CJS `main`. Remove the override if sockjs ever bumps its uuid pin, or if webpack-dev-server drops sockjs. Scoped to `pg-manual/` only, the other example dirs don't pull uuid. -- Build verification: `npm install && npm run build` must be a clean compile with zero "export ... was not found" warnings. +- Build verification: `npm install && npm run build && npm run check` must be a clean compile with zero "export ... was not found" warnings. diff --git a/pg-manual/check/sdk-exports.js b/pg-manual/check/sdk-exports.js new file mode 100644 index 0000000..6fcb482 --- /dev/null +++ b/pg-manual/check/sdk-exports.js @@ -0,0 +1,15 @@ +// Static-import probe for the @e4a/pg-wasm exports the examples rely on. +// +// examples/string.js and examples/file.js reach the SDK through a dynamic +// `import('@e4a/pg-wasm')` and destructure at runtime, which webpack cannot +// analyse — a removed or renamed export still compiles cleanly. Importing the +// same names statically here puts them back under webpack's export analysis, +// so `npm run check` fails when the SDK drops one. +// +// Keep this list in step with what the examples destructure. +import { seal, sealStream, Unsealer, StreamUnsealer } from '@e4a/pg-wasm' + +// Referencing the bindings keeps them from being elided before the check runs. +for (const [name, binding] of Object.entries({ seal, sealStream, Unsealer, StreamUnsealer })) { + if (binding === undefined) throw new Error(`@e4a/pg-wasm no longer exports ${name}`) +} diff --git a/pg-manual/check/webpack.config.js b/pg-manual/check/webpack.config.js new file mode 100644 index 0000000..5a384b7 --- /dev/null +++ b/pg-manual/check/webpack.config.js @@ -0,0 +1,22 @@ +// Build config for `npm run check`. Separate from webpack.config.js so the +// probe never lands in dist/ and CleanWebpackPlugin never runs against it. +const path = require('path') + +const root = path.resolve(__dirname, '..') + +module.exports = { + name: 'sdk-exports', + mode: 'development', + entry: path.resolve(__dirname, 'sdk-exports.js'), + output: { + path: path.resolve(root, 'node_modules/.cache/sdk-exports'), + filename: 'sdk-exports.js', + }, + experiments: { + asyncWebAssembly: true, + topLevelAwait: true, + }, + resolve: { + modules: [path.resolve(root, 'node_modules')], + }, +} diff --git a/pg-manual/package.json b/pg-manual/package.json index d1fa3a2..b306077 100644 --- a/pg-manual/package.json +++ b/pg-manual/package.json @@ -31,6 +31,7 @@ }, "scripts": { "build": "webpack", + "check": "webpack --config check/webpack.config.js --fail-on-warnings", "dev": "npx webpack serve" }, "overrides": { From 7963a9b10f013140fdb9d91efdc3ff504acf4bae 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 08:45:52 +0000 Subject: [PATCH 5/7] ci(pg-manual): fail the build on a broken static import `build` ran bare `webpack`, which reports a missing static import as a warning and still exits 0. Both breakages recorded in CLAUDE.md's pg-manual notes (web-streams-polyfill v3->v4, yivi 0.2->1.x) are that shape, so the CI gate would have gone green on either. Measured in pg-manual: re-applying the web-streams-polyfill rename to examples/file.js leaves `npm run build` at exit 0 without the flag and exits 1 with it, while the untouched tree compiles with zero warnings either way. The flag on `build` only reaches static imports, so `check` still carries the dynamic `@e4a/pg-wasm` half. CLAUDE.md now splits the two claims instead of implying the flag is not useful here, and the sdk-exports.js comment names both edits a new probe name needs: an import-only addition is elided and keeps the check at exit 0. --- CLAUDE.md | 3 ++- pg-manual/check/sdk-exports.js | 4 +++- pg-manual/package.json | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e0ccb52..4d1e755 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,7 +30,8 @@ Every sub-project builds reproducibly from a committed lockfile: | `pg-dotnet` | `dotnet restore --locked-mode` | `dotnet build --no-restore` | - `pg-node` has no bundler, so `npm run check` is its build equivalent: a `node --check` syntax pass over `index.mjs`, plus an ESM import of `src/encryption.mjs`. The import is the part that matters: it resolves `src/`'s named imports against the installed `@e4a/pg-js`, so a renamed or removed SDK export fails the check. Two things it does not cover. `index.mjs`'s own imports are never linked, because importing `index.mjs` would run the CLI, so renaming an export in `src/config.mjs` still passes the check and only fails at `node index.mjs`. And it says nothing about signature changes behind an unchanged export name. -- `pg-manual`'s `npm run check` is a second, tiny webpack build (`check/webpack.config.js`) run with `--fail-on-warnings`. It exists because the examples reach `@e4a/pg-wasm` through a dynamic `import()` and destructure at runtime, which webpack does not analyse: drop `seal` from the SDK and `npm run build` still compiles successfully with zero warnings. `--fail-on-warnings` on the real build does not close that, it only sees static imports. `check/sdk-exports.js` imports the same names statically so the analysis applies. Add a name there whenever an example destructures a new one. +- `pg-manual`'s `build` script carries `--fail-on-warnings`. Webpack reports a broken *static* import as a warning and still exits 0, which is how both breakages recorded under "pg-manual build notes" below could have shipped green; the flag turns them into a failed build. The tree compiles with zero warnings today, so keep it there rather than dropping the flag. +- `pg-manual`'s `npm run check` is a second, tiny webpack build (`check/webpack.config.js`), also run with `--fail-on-warnings`. It covers the half the flag on `build` cannot reach: the examples get `@e4a/pg-wasm` through a *dynamic* `import()` and destructure at runtime, and webpack analyses no exports across that boundary, so dropping `seal` from the SDK leaves `npm run build` at exit 0 with zero warnings. `check/sdk-exports.js` imports the same names statically, which puts them back under the analysis. Adding a name means editing both the import and the object literal under it, because an unreferenced import is elided and probes nothing. - `pg-dotnet` restores with a lockfile (`packages.lock.json`, enabled by `RestorePackagesWithLockFile`). After changing any `PackageReference`, run `dotnet restore` and commit the regenerated `packages.lock.json` in the same commit, otherwise `--locked-mode` fails with `NU1004: the package reference ... has changed`. ## Known issues / intentional non-fixes diff --git a/pg-manual/check/sdk-exports.js b/pg-manual/check/sdk-exports.js index 6fcb482..b3014cf 100644 --- a/pg-manual/check/sdk-exports.js +++ b/pg-manual/check/sdk-exports.js @@ -6,7 +6,9 @@ // same names statically here puts them back under webpack's export analysis, // so `npm run check` fails when the SDK drops one. // -// Keep this list in step with what the examples destructure. +// Keep this list in step with what the examples destructure. A new name goes in +// both places below: webpack elides an unreferenced import, so adding one to the +// import alone probes nothing and leaves the check passing. import { seal, sealStream, Unsealer, StreamUnsealer } from '@e4a/pg-wasm' // Referencing the bindings keeps them from being elided before the check runs. diff --git a/pg-manual/package.json b/pg-manual/package.json index b306077..fa33d66 100644 --- a/pg-manual/package.json +++ b/pg-manual/package.json @@ -30,7 +30,7 @@ "util": "^0.12.5" }, "scripts": { - "build": "webpack", + "build": "webpack --fail-on-warnings", "check": "webpack --config check/webpack.config.js --fail-on-warnings", "dev": "npx webpack serve" }, From b2f9cff1f629d31a3f670b901baed45fc7df5ecc 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 08:59:28 +0000 Subject: [PATCH 6/7] docs: scope the --fail-on-warnings claim to the class it catches The bullet said the flag turns "both breakages recorded under pg-manual build notes" into a failed build. Only one of the two is a webpack warning. Measured on this branch by reintroducing each breakage and running npm run build: - web-streams-polyfill rename, `import { PolyfilledWritableStream }`: exit 1, "export 'PolyfilledWritableStream' ... was not found". Caught. - yivi, `import * as YiviCore from '@privacybydesign/yivi-core'`: exit 0, zero warnings, "compiled successfully". npm run check also exit 0. Not caught. `import * as` is valid ESM regardless of what the module exports, so webpack emits nothing; yivi-core 1.0.0 is type: module, and the namespace object throws TypeError: not a constructor at `new YiviCore(...)` in examples/utils.js:63. That is a runtime failure no build gate sees, and check/sdk-exports.js cannot close it either because the mistake is in this repo's import form rather than in the SDK's export list. Scopes the sentence to what was measured. No code change. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4d1e755..2826008 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,7 +30,7 @@ Every sub-project builds reproducibly from a committed lockfile: | `pg-dotnet` | `dotnet restore --locked-mode` | `dotnet build --no-restore` | - `pg-node` has no bundler, so `npm run check` is its build equivalent: a `node --check` syntax pass over `index.mjs`, plus an ESM import of `src/encryption.mjs`. The import is the part that matters: it resolves `src/`'s named imports against the installed `@e4a/pg-js`, so a renamed or removed SDK export fails the check. Two things it does not cover. `index.mjs`'s own imports are never linked, because importing `index.mjs` would run the CLI, so renaming an export in `src/config.mjs` still passes the check and only fails at `node index.mjs`. And it says nothing about signature changes behind an unchanged export name. -- `pg-manual`'s `build` script carries `--fail-on-warnings`. Webpack reports a broken *static* import as a warning and still exits 0, which is how both breakages recorded under "pg-manual build notes" below could have shipped green; the flag turns them into a failed build. The tree compiles with zero warnings today, so keep it there rather than dropping the flag. +- `pg-manual`'s `build` script carries `--fail-on-warnings`. Webpack reports a missing *named* export on a static import as a warning and still exits 0, which is how the `web-streams-polyfill` rename recorded under "pg-manual build notes" below could have shipped green; the flag turns that class into a failed build. It does not catch the yivi breakage recorded there: `import * as Foo` is valid ESM whatever the module exports, so webpack compiles it with zero warnings and the failure is a runtime `TypeError: ... is not a constructor` at `new YiviCore(...)` in `examples/utils.js`. No build gate covers that class, and `check/sdk-exports.js` can't close it either, because the mistake is in this repo's own import form rather than in the SDK's export list; only running the example would catch it. The tree compiles with zero warnings today, so keep the flag rather than dropping it. - `pg-manual`'s `npm run check` is a second, tiny webpack build (`check/webpack.config.js`), also run with `--fail-on-warnings`. It covers the half the flag on `build` cannot reach: the examples get `@e4a/pg-wasm` through a *dynamic* `import()` and destructure at runtime, and webpack analyses no exports across that boundary, so dropping `seal` from the SDK leaves `npm run build` at exit 0 with zero warnings. `check/sdk-exports.js` imports the same names statically, which puts them back under the analysis. Adding a name means editing both the import and the object literal under it, because an unreferenced import is elided and probes nothing. - `pg-dotnet` restores with a lockfile (`packages.lock.json`, enabled by `RestorePackagesWithLockFile`). After changing any `PackageReference`, run `dotnet restore` and commit the regenerated `packages.lock.json` in the same commit, otherwise `--locked-mode` fails with `NU1004: the package reference ... has changed`. From fe93d3454d6dc0509b1dff8cb561cd5db0a44099 Mon Sep 17 00:00:00 2001 From: Ruben Hensen Date: Mon, 27 Jul 2026 14:55:47 +0200 Subject: [PATCH 7/7] ci: add build matrix and weekly SDK canary workflows --- .github/workflows/ci.yml | 66 ++++++++++++++++++ .github/workflows/sdk-canary.yml | 111 +++++++++++++++++++++++++++++++ CLAUDE.md | 2 - 3 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/sdk-canary.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d90d339 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,66 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + node: + name: Build ${{ matrix.project }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + project: [pg-manual, pg-node, pg-sveltekit] + defaults: + run: + working-directory: ${{ matrix.project }} + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 22 + cache: npm + cache-dependency-path: ${{ matrix.project }}/package-lock.json + + - name: Install from lockfile + run: npm ci + + - name: Build + run: npm run build --if-present + + - name: Check + run: npm run check --if-present + + - name: Lint + run: npm run lint --if-present + + dotnet: + name: Build pg-dotnet + runs-on: ubuntu-latest + defaults: + run: + working-directory: pg-dotnet + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-dotnet@v6 + with: + dotnet-version: | + 8.0.x + 10.0.x + + - name: Restore from lockfile + run: dotnet restore --locked-mode + + - name: Build + run: dotnet build --no-restore diff --git a/.github/workflows/sdk-canary.yml b/.github/workflows/sdk-canary.yml new file mode 100644 index 0000000..549e842 --- /dev/null +++ b/.github/workflows/sdk-canary.yml @@ -0,0 +1,111 @@ +name: SDK canary + +# Builds every example against the LATEST published PostGuard SDKs instead of +# the pinned lockfile versions, so we hear about a breaking SDK release before +# a reader of docs.postguard.eu does. + +on: + schedule: + - cron: '0 6 * * 1' + workflow_dispatch: + +permissions: + contents: read + +jobs: + node: + name: Canary ${{ matrix.project }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - project: pg-manual + package: '@e4a/pg-wasm' + - project: pg-node + package: '@e4a/pg-js' + - project: pg-sveltekit + package: '@e4a/pg-js' + defaults: + run: + working-directory: ${{ matrix.project }} + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 22 + + - name: Install latest ${{ matrix.package }} + run: npm install "${{ matrix.package }}@latest" + + - name: Report resolved version + run: node -p "require('./node_modules/${{ matrix.package }}/package.json').version" + + - name: Build + run: npm run build --if-present + + - name: Check + run: npm run check --if-present + + - name: Lint + run: npm run lint --if-present + + dotnet: + name: Canary pg-dotnet + runs-on: ubuntu-latest + defaults: + run: + working-directory: pg-dotnet + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-dotnet@v6 + with: + dotnet-version: | + 8.0.x + 10.0.x + + - name: Install latest E4A.PostGuard + run: dotnet add package E4A.PostGuard + + - name: Build + run: dotnet build + + report: + name: Report failure + runs-on: ubuntu-latest + needs: [node, dotnet] + if: failure() + permissions: + contents: read + issues: write + steps: + - name: Open or update the canary issue + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + set -euo pipefail + + gh label create sdk-canary \ + --description 'Opened by the weekly SDK canary' \ + --color D93F0B --force + + title='SDK canary failed against the latest published SDKs' + body=$(cat <