diff --git a/.github/audit/hosted.md b/.github/audit/hosted.md index 219711275..597e4fbf9 100644 --- a/.github/audit/hosted.md +++ b/.github/audit/hosted.md @@ -18,11 +18,26 @@ Read `docs/specs/hosted.md`, `hosted/server/`, `hosted/src/`, `hosted/scripts/`, Deployment boundary quantifies over the preview and production paths, which live in those scripts and workflows rather than in the Worker. -Verify the vendored packages by their provenance rather than by reading them: -hash each archive in `vendor/` against `vendor/build.json`; read each archive's -own claim with `tar -xOf vendor/.tgz package/dist/provenance.json` and -check that it names `build.json`'s commit and does not record `dirty`; then -check that commit against pgstencil `main` and its audit: +Verify the installed `pgstencil` and `@pgstencil/auth` packages by reading each +`dist/provenance.json`, without auditing package code. Require a 40-hex commit, +no `dirty: true`, and the same commit in both packages. Confirm `pnpm-lock.yaml` +resolves both through the npm registry with integrity hashes. Inspect Hosted's +runtime imports for references to a sibling pgstencil checkout. + +Verify npm's signed SLSA provenance for each installed package/version. Use a +temporary npm consumer of the exact locked versions and `npm audit signatures +--json --include-attestations` (npm does not audit a pnpm-only install). Each +package **must appear in `verified`** with a SLSA provenance bundle; reject +`invalid` and `missing` entries too. Decode the verified SLSA DSSE payload and +the Fulcio certificate in that bundle. The certificate SAN must be +`https://github.com/diffplug/pgstencil/.github/workflows/release.yml@refs/heads/main`; +its source-repository digest extension `1.3.6.1.4.1.57264.1.13` must equal +the installed `dist/provenance.json` commit. The signed subject must identify +the installed package/version and digest; the payload's +`externalParameters.workflow` and `resolvedDependencies` must agree with the +certificate and commit. npm verifies the signature and subject digest, but +the payload's workflow claim alone is not the signer identity. Then check the +commit against pgstencil `main` and its audit: ```sh gh api repos/diffplug/pgstencil/compare/...main --jq .status @@ -30,9 +45,12 @@ gh api repos/diffplug/pgstencil/commits//check-runs \ --jq '.check_runs[] | select(.name=="security-audit") | .conclusion' ``` -The first must be `ahead` or `identical`, the second `success`. The packed code +The first must be `ahead` or `identical`. A commit can carry several +`security-audit` runs, and a `cancelled` one, from a manual dispatch that was +stopped, is not a verdict: ignore `cancelled`, then require at least one +`success` and no other conclusion. The released code itself is audited in `diffplug/pgstencil` by that repository's own -`security-audit` workflow against its `SECURITY.md`; do not audit the tarballs' +`security-audit` workflow against its `SECURITY.md`; do not audit the installed packages' contents here — audit how `hosted/` configures the adapter. Distinguish tested code from pending production configuration; do not treat local provider simulations as live OAuth acceptance, and treat a checked-in placeholder as no @@ -42,7 +60,7 @@ report its state as INFO under `### Qualitative findings`. ## Qualitative pass -You own `hosted/` and `vendor/`. You **read** `.github/workflows/hosted-preview.yml` +You own `hosted/` and the installed pgstencil boundary. You **read** `.github/workflows/hosted-preview.yml` and `.github/workflows/hosted-production.yml` for the Deployment boundary above, but you do not own them: `ci-and-secrets` owns those workflows' credentials, environments, reviewers, and token placement diff --git a/.github/renovate.json b/.github/renovate.json index 2d81c9159..87bae9803 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -148,6 +148,16 @@ "matchPackageNames": ["node"], "enabled": false }, + { + "description": "pgstencil releases are staged by a workflow and approved with 2FA; group the three packages and open the PR as soon as one is approved", + "matchManagers": ["npm"], + "matchPackageNames": ["pgstencil", "@pgstencil/**"], + "groupName": "pgstencil", + "groupSlug": "pgstencil", + "separateMajorMinor": false, + "minimumReleaseAge": null, + "schedule": ["at any time"] + }, { "description": "canopy pins the pristine @xterm/addon-webgl AND @xterm/xterm to the exact commit the SDF fork's sdf branch is based on (the addon's beta counter is offset from core's; canopy/README.md records the current correspondence). They are the UpstreamVsFork regression baseline and move in lockstep with the hand-cut @diffplug/xterm-addon-webgl-sdf tarball, which Renovate cannot see, so bumping either one is a manual edit made when the fork rebases — never a Renovate bump (see canopy/README.md and docs/specs/webgl-text.md). File-scoped: lib and standalone still track upstream betas via the xterm group above. Must stay last; later rules win", "matchManagers": ["npm"], diff --git a/docs/specs/hosted.md b/docs/specs/hosted.md index e06595d3a..a8aee932c 100644 --- a/docs/specs/hosted.md +++ b/docs/specs/hosted.md @@ -9,11 +9,11 @@ **Must run committed Better Auth migrations before deploying code that needs them, never during a Worker request.** Postgres is reached through an uncached Hyperdrive binding. The runtime creates and closes its database pool within each request. -**Must pin locally packed core/auth packages through root pnpm overrides and commit archives, provenance, and lockfile together.** `vendor/build.json` records the source commit, archive hashes, and `dirty: false`, which production preflight requires; each archive's `package/dist/provenance.json` names its source commit, and a pack pgstencil marked dirty cannot be vendored. No runtime import depends on a sibling checkout. The auth migrations remain owned by the package. +**Must install released core/auth packages from npm and commit their lockfile integrity hashes.** The installed packages' `dist/provenance.json` must name the same clean pgstencil commit; no runtime import depends on a sibling checkout. The auth migrations remain owned by the package. -**Must declare every peer dependency of the pinned archives in `hosted/package.json`**, so they share Hosted's copy and Renovate updates them. +**Must declare every peer dependency of the installed packages in `hosted/package.json`**, so they share Hosted's copy and Renovate updates them. -Source of truth: `auth` in `hosted/server/worker.ts`; `workerApp` in `hosted/server/worker-app.ts`; `migrations` in `hosted/server/migrations.ts`; `scripts/sync-pgstencil.mjs`. Pinned by `hosted/server/tests/artifacts.test.ts`. +Source of truth: `auth` in `hosted/server/worker.ts`; `workerApp` in `hosted/server/worker-app.ts`; `migrations` in `hosted/server/migrations.ts`; `verifyPackages` in `hosted/scripts/production.mjs`. Pinned by `hosted/server/tests/artifacts.test.ts`. ## Identity and login @@ -65,7 +65,7 @@ Source of truth: `touchesHosted` in `hosted/scripts/changed.mjs`; `.github/workf ## Production releases -**Must deploy only manually selected main revisions after Hosted tests/build and accepted clean package provenance.** `verifyPackages` checks the archive hashes and each archive's own packed provenance; preflight checks uncached Hyperdrive, matching migration/runtime database identity with distinct roles, and required Worker secret names. Back up, encrypt, decrypt, and restore-test before applying migrations; upload only the encrypted archive. Production has no public candidate URL. +**Must deploy only manually selected main revisions after Hosted tests/build and accepted clean package provenance.** `verifyPackages` checks both installed packages' clean, matching provenance; preflight checks uncached Hyperdrive, matching migration/runtime database identity with distinct roles, and required Worker secret names. Back up, encrypt, decrypt, and restore-test before applying migrations; upload only the encrypted archive. Production has no public candidate URL. **Must record an immutable annotated hosted/YYYY-MM-DD tag only after live verification.** Tags identify the deployed commit and verification run/attempt; retries are idempotent and redeployments get new tags. Dating and repeat-deployment suffixes: `recordDeployment`. Code rollback never reverses migrations. diff --git a/docs/specs/security-audit.rationale.md b/docs/specs/security-audit.rationale.md index 373b66820..489860f2e 100644 --- a/docs/specs/security-audit.rationale.md +++ b/docs/specs/security-audit.rationale.md @@ -8,7 +8,7 @@ The three release-gate pieces are named separately because they break independen One context holding every subject matter degrades application security — the domain with the most code behind it, and the easiest to crowd out with API responses. -Hosted accounts were split out of `application-security` on 2026-09-21. That domain already carried remote control (where the depth goes), the local boundaries, Hosted, and the catch-all sweep, and it had overrun the 32-minute deadline more than once, so a remote-control pass that ran out of time took the Hosted results down with it. Hosted is a disjoint tree — `hosted/`, `vendor/`, and the two `hosted-*.yml` workflows it reads for the Deployment boundary — with its own spec, so it splits cleanly and now writes its own fragment. It also gives the pgstencil provenance checks a prompt that is about them rather than a paragraph inside one about pairing code. It runs on Opus for the same reason `application-security` does: the Worker's origin gate and the deployment path are read, not enumerated. +Hosted accounts were split out of `application-security` on 2026-09-21. That domain already carried remote control (where the depth goes), the local boundaries, Hosted, and the catch-all sweep, and it had overrun the 32-minute deadline more than once, so a remote-control pass that ran out of time took the Hosted results down with it. Hosted is a disjoint tree — `hosted/`, its installed pgstencil dependencies, and the two `hosted-*.yml` workflows it reads for the Deployment boundary — with its own spec, so it splits cleanly and now writes its own fragment. It also gives the pgstencil provenance checks a prompt that is about them rather than a paragraph inside one about pairing code. It runs on Opus for the same reason `application-security` does: the Worker's origin gate and the deployment path are read, not enumerated. Folding the application-security scope back into a shared context is how that spec stops being audited without anyone deciding to stop auditing it. diff --git a/docs/specs/security-hosted.md b/docs/specs/security-hosted.md index 73803d056..7836e406a 100644 --- a/docs/specs/security-hosted.md +++ b/docs/specs/security-hosted.md @@ -23,11 +23,12 @@ Pinned by `hosted/server/tests/workers.test.ts` and `hosted/server/tests/policy. ## Deployment boundary -**Must vendor pgstencil from a commit on its `main`.** A Dormouse branch may vendor a pgstencil branch while a cross-repo change is in flight; `main` must not merge it until pgstencil has. +**Must depend on a released pgstencil** whose installed `dist/provenance.json` names a commit on pgstencil `main` with a passing `security-audit`. The core and auth packages must name the same clean commit. - **FAIL IF** a production Worker exposes the captured-email inbox or deterministic clock controls, or imports the testing injection module; inspect `hosted/server/worker.ts`, the build configuration, and `hosted/server/tests/worker-entry.ts`. -- **FAIL IF** an archive's SHA-256 differs from `vendor/build.json`, `build.json` records `dirty`, either archive's `package/dist/provenance.json` is missing, records `dirty`, or names a commit other than `build.json`'s, the core/auth pnpm overrides cease resolving to those archives, or a runtime import depends on a sibling source checkout. -- **FAIL IF** `vendor/build.json`'s commit is not on pgstencil `main` (`gh api repos/diffplug/pgstencil/compare/...main`, status `ahead` or `identical`), or that commit's `security-audit` check run (`gh api repos/diffplug/pgstencil/commits//check-runs`) is missing or not `success`. pgstencil's own audit is the evidence for the packed code; Dormouse audits only how Hosted configures it. +- **FAIL IF** either installed pgstencil package lacks `dist/provenance.json`, records `dirty`, or names a different commit; `pnpm-lock.yaml` resolves either package from outside npm; or a runtime import depends on a sibling pgstencil checkout. Inspect `verifyPackages` in `hosted/scripts/production.mjs`, `hosted/server/tests/artifacts.test.ts`, and Hosted runtime imports. +- **FAIL IF** either installed package lacks a verified npm SLSA provenance attestation whose Fulcio certificate SAN names `diffplug/pgstencil` `.github/workflows/release.yml` on `refs/heads/main`, whose source-repository digest (OID `1.3.6.1.4.1.57264.1.13`) equals `dist/provenance.json`'s commit, or whose signed subject/payload disagrees with the installed package, certificate, or commit. +- **FAIL IF** that commit is not on pgstencil `main` (`gh api repos/diffplug/pgstencil/compare/...main`, status `ahead` or `identical`), or its `security-audit` check runs (`gh api repos/diffplug/pgstencil/commits//check-runs`) include no `success`, or any conclusion other than `success` and `cancelled`. pgstencil audits the released code; Dormouse audits only how Hosted configures it. - **FAIL IF** the local email inbox accepts a foreign Host or Origin or cross-site Fetch Metadata; inspect `allowedDevRequest` in `hosted/server/dev-host-guard.ts`, including the upgrade guard in `hosted/server/dev.ts`. - **FAIL IF** the production deploy can proceed without `preflight` establishing an uncached Hyperdrive, a matching migration/runtime database, and distinct runtime and migration roles; inspect `preflight` in `hosted/scripts/production.mjs` and its ordering ahead of the deploy step in `.github/workflows/hosted-production.yml`. diff --git a/docs/specs/security-supply-chain.md b/docs/specs/security-supply-chain.md index 49776de32..9684797d8 100644 --- a/docs/specs/security-supply-chain.md +++ b/docs/specs/security-supply-chain.md @@ -70,9 +70,10 @@ Source of truth: `bundle_node_runtime` / `verify_node_version` in `standalone/sr ## Cooldown and alerts -**Maturity gating runs in both the pnpm configuration and the Renovate configuration.** +**Maturity gating runs in both the pnpm configuration and the Renovate configuration, except for pgstencil releases audited on their main commit, staged, and approved with 2FA.** (rationale) - **FAIL IF** `pnpm-workspace.yaml` is missing `minimumReleaseAge: 1440`. +- **FAIL IF** `minimumReleaseAgeExclude` in `pnpm-workspace.yaml` contains anything except `pgstencil` and `@pgstencil/*`, or a Renovate package rule sets `minimumReleaseAge: null` for any package outside `pgstencil` and `@pgstencil/**`. - **FAIL IF** `.github/renovate.json` is missing `npm` or `cargo` from `enabledManagers` (npm covers `/`; cargo covers `/standalone/src-tauri`), or is missing `minimumReleaseAge` package rules for those managers (rationale). - **FAIL IF** `.github/renovate.json` has no `vulnerabilityAlerts` block, or that block does not set `minimumReleaseAge` **explicitly**. Renovate's built-in default for that block is `minimumReleaseAge: null`, force-applied before lookup, so *omitting* the key drops the cooldown rather than inheriting it from `packageRules`. Keeping it is deliberate (rationale). - **FAIL IF** secret scanning or its push protection is disabled on the repository (`gh api repos/diffplug/dormouse --jq .security_and_analysis`), or Dependabot alerts are off (`GET /repos/diffplug/dormouse/vulnerability-alerts` must answer 204, not 404). Push protection is the one control that acts *before* a credential lands, blocking a push whose diff carries a recognized provider token; it applies to `dormouse-bot` too (rationale). diff --git a/docs/specs/security-supply-chain.rationale.md b/docs/specs/security-supply-chain.rationale.md index 4a56ef032..8f5f0baab 100644 --- a/docs/specs/security-supply-chain.rationale.md +++ b/docs/specs/security-supply-chain.rationale.md @@ -30,6 +30,8 @@ Why alternate version declarations are excluded: in the pinned [setup-node imple What the `minimumReleaseAge` package rules are: the Renovate equivalent of the pnpm dependency cooldown window, applied per manager. +The pgstencil exception has a different gate: its release workflow stages a version only after a passing `security-audit` on the packaged commit, and a human approves the staged package with npm 2FA before it becomes public. The package carries that commit in `dist/provenance.json`; Hosted verifies matching clean commits for core and auth, and its audit checks npm-signed workflow attestations bind those bytes to that commit. The exclusion list is deliberately narrow so other dependencies retain the withdrawal window. + Why the `vulnerabilityAlerts` cooldown is kept rather than dropped for speed: it guards the opposite threat from the alert itself — a compromised release that gets yanked within a day, which a reviewer reading a dependency diff cannot detect the way the ecosystem's own yank process can. Nothing here auto-merges, and the Dependabot alert already makes the vulnerability visible the moment it is published, so what the cooldown costs is a day before the remediation PR appears, not a day before anyone knows. Why push protection covering `dormouse-bot` is the point rather than an incidental: an injected agent pasting a token into a file is exactly the shape it stops. diff --git a/docs/specs/security.md b/docs/specs/security.md index 2090807af..664bea99e 100644 --- a/docs/specs/security.md +++ b/docs/specs/security.md @@ -49,7 +49,7 @@ last column means nothing cheaper does. | **Push, when enabled, cannot be aimed back into the tailnet.** | [What crosses the boundary](./security-remote.md#what-crosses-the-boundary) | `relay/test/push-endpoint.test.mjs` | | **Every dependency that reaches a machine is disclosed** at [dormouse.sh/supply-chain](https://dormouse.sh/supply-chain), and a change without the disclosure fails CI. | [Disclosure](./security-supply-chain.md#disclosure) | `.github/workflows/ci.yml` | | **The bundled runtime is the version disclosed.** The build verifies the binary against the pin. | [Bundled runtime](./security-supply-chain.md#bundled-runtime) | `standalone/src-tauri/build.rs` | -| **No newly published dependency is adopted for 24 hours**, security fixes included. | [Cooldown and alerts](./security-supply-chain.md#cooldown-and-alerts) | audit | +| **Dependencies wait 24 hours**, except audited pgstencil releases approved with 2FA. | [Cooldown and alerts](./security-supply-chain.md#cooldown-and-alerts) | audit | | **Merging to `main` and creating a tag are admin-only**, and every workflow this repository authors pins its actions by commit. | [GitHub Actions Policies](./security-ci.md#github-actions-policies) | audit | | **The bot maintainer cannot merge, tag, or read a release secret**, and its token never enters its own environment. | [Automated Maintainer (tend)](./security-ci.md#automated-maintainer-tend) | `.github/workflows/workflow-audit.yaml`, nightly | | **Publishing the extension takes a second human's approval.** | [VS Code Extension Releases](./security-ci.md#vs-code-extension-releases) | audit | diff --git a/hosted/README.md b/hosted/README.md index 3e3cc1f38..8cca6f2f7 100644 --- a/hosted/README.md +++ b/hosted/README.md @@ -5,7 +5,7 @@ The marketing website is a separate application. Hosted voice and the managed Relay are not implemented. See [the spec](../docs/specs/hosted.md). This file is the whole operator runbook, in the order an operator works: run -locally, refresh packages, set up GitHub, provision previews, provision +locally, update packages, set up GitHub, provision previews, provision production, release, accept, recover. Marketing, desktop releases, Hosted production, and PR previews have separate deployment credentials. A passing local test implies no cloud resource and no real-provider acceptance. @@ -35,29 +35,20 @@ Tests run the production composition in real workerd with disposable Postgres clones and a local OAuth simulator. The build includes a Wrangler dry-run; it does not deploy. -## Refresh private packages +## Update pgstencil -```sh -node scripts/sync-pgstencil.mjs /path/to/pgstencil [revision] -``` +`hosted/package.json` installs released `pgstencil` and `@pgstencil/auth` from +npm. They share a version and Hosted declares their peer dependencies. Approved +pgstencil releases are exempt from the pnpm and Renovate cooldowns because the +release workflow requires a passing security audit, stages the archives, and +requires a maintainer's 2FA approval before publishing. -This checks out the pgstencil revision (default `HEAD`) in a temporary clean -worktree, runs `pnpm packages:pack` there, vendors core/auth, records the commit, -`dirty: false` and SHA-256 hashes in `vendor/build.json`, and installs. Each -archive carries its own `package/dist/provenance.json`, and the sync refuses one -that is missing it, that reports a dirty pack, or that names a commit other than -the one packed here. To try an unfinished pgstencil change, commit it to a local -branch and sync that revision. -The sync also warns when the vendored commit is not on pgstencil's -`origin/main`: a Dormouse branch may vendor a pgstencil branch while a -cross-repo change is in flight, but Dormouse `main` must vendor a pgstencil -`main` commit, and the nightly audit fails until it does. The direct Node -command also works before the archives exist (pnpm may otherwise auto-install -first). See `docs/specs/hosted.md` -> "Application boundary" for what has to be -committed together. - -Re-run integration tests after every refresh. Vendor an accepted pgstencil -revision before a production release. +Run `pnpm install` and re-run Hosted's integration tests after an update. The +production preflight reads `dist/provenance.json` from the installed packages +and requires matching clean commits. `docs/specs/security-hosted.md` -> +"Deployment boundary" owns the upstream audit check. For an unreleased change, +pack pgstencil in a clean checkout and use a temporary pnpm override on a +branch. ## Resource inventory @@ -142,8 +133,8 @@ Use dedicated Dormouse resources in the existing Cloudflare, Neon, and Postmark accounts. 1. Create a dedicated Dormouse production Postgres database on Neon, on - PostgreSQL 17; the backup/restore tooling pins PostgreSQL - 17.11. Keep development and previews separate. Enable backups and a + PostgreSQL 18; the backup/restore tooling pins PostgreSQL + 18.6. Keep development and previews separate. Enable backups and a suitable PITR window, and verify a restore into a separate database before accepting real accounts. 2. Create a Cloudflare Hyperdrive configuration for that database with **query diff --git a/hosted/package.json b/hosted/package.json index a979c5aaf..39ef3d0fd 100644 --- a/hosted/package.json +++ b/hosted/package.json @@ -18,8 +18,8 @@ "preview:smoke": "node scripts/preview-smoke.mjs" }, "dependencies": { - "@pgstencil/auth": "file:../vendor/pgstencil-auth-0.1.0.tgz", - "pgstencil": "file:../vendor/pgstencil-0.1.0.tgz", + "@pgstencil/auth": "^0.2.1", + "pgstencil": "^0.2.1", "kysely": "^0.29.5", "hono": "^4.13.8", "@hono/node-server": "^2.0.10", diff --git a/hosted/scripts/changed.mjs b/hosted/scripts/changed.mjs index ace7d765f..ac1db63ae 100644 --- a/hosted/scripts/changed.mjs +++ b/hosted/scripts/changed.mjs @@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url"; export function touchesHosted(paths) { return paths.some( (path) => - /^(hosted\/|vendor\/|lib\/src\/(theme|lib\/(themes\/|(?:local-json-store|is-record|css-color)\.ts$))|scripts\/sync-pgstencil\.mjs$|\.github\/workflows\/hosted-[^/]+\.yml$)/.test( + /^(hosted\/|lib\/src\/(theme|lib\/(themes\/|(?:local-json-store|is-record|css-color)\.ts$))|\.github\/workflows\/hosted-[^/]+\.yml$)/.test( path, ) || ["package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml"].includes(path), diff --git a/hosted/scripts/changed.test.mjs b/hosted/scripts/changed.test.mjs index 85cc5f9e6..ce6a5a43e 100644 --- a/hosted/scripts/changed.test.mjs +++ b/hosted/scripts/changed.test.mjs @@ -5,7 +5,6 @@ test("Hosted and shared inputs trigger previews; unrelated application changes d for (const path of [ "hosted/README.md", "hosted/server/worker.ts", - "vendor/build.json", "pnpm-lock.yaml", ".github/workflows/hosted-preview.yml", "lib/src/theme-colors.css", diff --git a/hosted/scripts/production-backup.mjs b/hosted/scripts/production-backup.mjs index d7e611880..b5aca9615 100644 --- a/hosted/scripts/production-backup.mjs +++ b/hosted/scripts/production-backup.mjs @@ -10,7 +10,7 @@ import { required, hyperdriveOrigin } from "./preview.mjs"; // Only the encrypted archive leaves the runner, after decrypt-and-restore succeeds. const temporary = await mkdtemp(join(tmpdir(), "hosted-backup-")); const container = `hosted-restore-${randomUUID()}`; -const image = "postgres:17.11-alpine"; +const image = "postgres:18.6-alpine"; function run(step, command, args, env = process.env) { const result = spawnSync(command, args, { env, diff --git a/hosted/scripts/production.mjs b/hosted/scripts/production.mjs index 7de24de04..801cffe82 100644 --- a/hosted/scripts/production.mjs +++ b/hosted/scripts/production.mjs @@ -1,7 +1,5 @@ import assert from "node:assert/strict"; -import { createHash } from "node:crypto"; import { readFile, writeFile, mkdir, rm } from "node:fs/promises"; -import { spawnSync } from "node:child_process"; import { resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { required, cloudflare, hyperdriveOrigin } from "./preview.mjs"; @@ -31,54 +29,30 @@ export function productionConfig(base, env) { }; } export async function verifyPackages() { - const manifest = JSON.parse( - await readFile(new URL("../../vendor/build.json", import.meta.url), "utf8"), - ); - assert.match(manifest.commit, /^[a-f0-9]{40}$/); - assert.equal( - manifest.dirty, - false, - "Production requires a clean sync of a committed pgstencil revision; refresh the vendored packages first", - ); - assert.deepEqual( - manifest.files.map((entry) => entry.filename).sort(), - ["pgstencil-0.1.0.tgz", "pgstencil-auth-0.1.0.tgz"], - "Expected both pinned pgstencil archives", - ); - for (const entry of manifest.files) { - assert.match(entry.filename, /^pgstencil(?:-auth)?-[\w.-]+\.tgz$/); - const archive = new URL(`../../vendor/${entry.filename}`, import.meta.url); - assert.equal( - createHash("sha256") - .update(await readFile(archive)) - .digest("hex"), - entry.sha256, - "Vendored archive checksum mismatch", - ); - // The archive's own provenance is what pgstencil's audit is keyed to — - // docs/specs/security-hosted.md -> "Deployment boundary". - const packed = spawnSync( - "tar", - ["-xOf", fileURLToPath(archive), "package/dist/provenance.json"], - { encoding: "utf8" }, + const commits = []; + for (const name of ["pgstencil", "@pgstencil/auth/better-auth"]) { + // Resolve the installed entrypoint, then read the provenance beside it. + const entry = import.meta.resolve(name); + const provenance = JSON.parse( + await readFile(new URL("./provenance.json", entry), "utf8"), ); - assert.equal( - packed.status, - 0, - `${entry.filename} carries no package/dist/provenance.json; refresh the vendored packages first`, + assert.match( + provenance.commit, + /^[a-f0-9]{40}$/, + "Production requires accepted, clean pgstencil provenance", ); - const provenance = JSON.parse(packed.stdout); assert.notEqual( provenance.dirty, true, - "Production requires accepted, clean pgstencil provenance; refresh the vendored packages first", - ); - assert.equal( - provenance.commit, - manifest.commit, - `${entry.filename} was packed from a commit other than the one vendor/build.json records`, + "Production requires accepted, clean pgstencil provenance", ); + commits.push(provenance.commit); } + assert.equal( + commits[0], + commits[1], + "Production requires accepted, clean pgstencil provenance", + ); } export async function preflight(env, config, api = cloudflare(env)) { const origin = hyperdriveOrigin(required(env, "DATABASE_URL")); diff --git a/hosted/server/tests/artifacts.test.ts b/hosted/server/tests/artifacts.test.ts index cec619b39..3f5c69cc6 100644 --- a/hosted/server/tests/artifacts.test.ts +++ b/hosted/server/tests/artifacts.test.ts @@ -1,94 +1,39 @@ import { test, expect } from "vitest"; -import { createHash } from "node:crypto"; import { readFileSync } from "node:fs"; -import { execFileSync } from "node:child_process"; -import { fileURLToPath } from "node:url"; -test("consumed package bytes match the recorded source snapshot", () => { - const build = JSON.parse(readFileSync("../vendor/build.json", "utf8")) as { - commit: string; - dirty: boolean; - files: { filename: string; sha256: string }[]; - }; - expect(build.commit).toMatch(/^[a-f0-9]{40}$/); - // The sync only packs committed revisions; production preflight requires this. - expect(build.dirty).toBe(false); - expect(build.files.map((file) => file.filename).sort()).toEqual([ - "pgstencil-0.1.0.tgz", - "pgstencil-auth-0.1.0.tgz", - ]); - for (const file of build.files) { - expect( - createHash("sha256") - .update(readFileSync("../vendor/" + file.filename)) - .digest("hex"), - ).toBe(file.sha256); - // Each archive names the pgstencil commit it was packed from, so the bytes - // themselves — not just build.json — say what pgstencil's own audit covers. - const provenance = JSON.parse( - execFileSync("tar", [ - "-xOf", - "../vendor/" + file.filename, - "package/dist/provenance.json", - ]).toString(), - ) as { commit: string; dirty?: boolean }; - expect(provenance.commit).toBe(build.commit); - expect(provenance.dirty).not.toBe(true); - } - // Same-version tarball refreshes must update installed code as well as metadata. - for (const [archive, entry] of [ - ["pgstencil-0.1.0.tgz", "pgstencil"], - ["pgstencil-auth-0.1.0.tgz", "@pgstencil/auth/better-auth"], - ]) { - const file = fileURLToPath(import.meta.resolve(entry)); - const archivePath = `package/dist/${file.split("/").at(-1)}`; - const packed = execFileSync("tar", [ - "-xOf", - "../vendor/" + archive, - archivePath, - ]); - expect(readFileSync(file)).toEqual(packed); - } -}); +const packages = [ + ["pgstencil", "pgstencil"], + ["@pgstencil/auth", "@pgstencil/auth/better-auth"], +] as const; -// pnpm resolves the workspace override ahead of hosted/package.json, so a bump -// that edits only one of them installs a build the snapshot above never saw. -test("both pinned specifiers name the recorded archives", () => { - const { dependencies } = JSON.parse( - readFileSync("package.json", "utf8"), - ) as { dependencies: Record }; - const workspace = readFileSync("../pnpm-workspace.yaml", "utf8"); - for (const [name, filename] of [ - ["pgstencil", "pgstencil-0.1.0.tgz"], - ["@pgstencil/auth", "pgstencil-auth-0.1.0.tgz"], - ]) { - expect(dependencies[name]).toBe(`file:../vendor/${filename}`); - const override = workspace.match( - new RegExp(`^ {2}'?${name}'?: (\\S+)$`, "m"), - )?.[1]; - expect(override).toBe(`file:vendor/${filename}`); +test("Hosted declares every peer of the installed pgstencil packages", () => { + const { dependencies } = JSON.parse(readFileSync("package.json", "utf8")) as { + dependencies: Record; + }; + for (const [name, entry] of packages) { + const manifest = new URL("../package.json", import.meta.resolve(entry)); + const { peerDependencies = {} } = JSON.parse(readFileSync(manifest, "utf8")) as { + peerDependencies?: Record; + }; + for (const peer of Object.keys(peerDependencies)) + expect(Object.keys(dependencies), `${name} peers on ${peer}`).toContain(peer); } }); -// strictPeerDependencies only rejects an out-of-range peer. pnpm resolves an -// undeclared one itself, where Renovate never sees it and Hosted's own imports -// can get a second copy. -test("Hosted declares every peer of the pinned archives", () => { - const { dependencies } = JSON.parse( - readFileSync("package.json", "utf8"), - ) as { dependencies: Record }; - for (const archive of ["pgstencil-0.1.0.tgz", "pgstencil-auth-0.1.0.tgz"]) { - const manifest = execFileSync( - "tar", - ["-xOf", "../vendor/" + archive, "package/package.json"], - { encoding: "utf8" }, +test("the lockfile resolves both pgstencil packages from npm", () => { + const lockfile = readFileSync("../pnpm-lock.yaml", "utf8"); + const lines = lockfile.split("\n"); + for (const [name, entry] of packages) { + const manifest = JSON.parse( + readFileSync(new URL("../package.json", import.meta.resolve(entry)), "utf8"), + ) as { version: string }; + const specifier = `${name}@${manifest.version}`; + const key = name.startsWith("@") ? `'${specifier}'` : specifier; + const index = lines.indexOf(` ${key}:`); + expect(index, `${name} has a registry resolution`).toBeGreaterThan(-1); + expect(lines[index + 1]).toMatch( + /^ resolution: \{integrity: sha512-[A-Za-z0-9+/=]+\}$/, ); - const { peerDependencies = {} } = JSON.parse(manifest) as { - peerDependencies?: Record; - }; - for (const peer of Object.keys(peerDependencies)) - expect(Object.keys(dependencies), `${archive} peers on ${peer}`).toContain( - peer, - ); } + expect(lockfile).not.toMatch(/file:[^\s]*pgstencil/); }); diff --git a/package.json b/package.json index 2268640df..dfab8efef 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "dev:hosted": "pnpm --filter dormouse-hosted dev", "build:hosted": "pnpm --filter dormouse-hosted build", "test:hosted": "pnpm --filter dormouse-hosted test", - "pgstencil:sync": "node scripts/sync-pgstencil.mjs", "build": "pnpm run build:vscode && pnpm --filter dormouse-lib build:pocket && pnpm --filter dormouse-website build && pnpm build:hosted", "test": "node scripts/spec-lint.mjs && node scripts/spec-lint-selftest.mjs && node scripts/public-docs-lint.mjs && node scripts/xterm-lint.mjs && node --test scripts/xterm-bump.test.mjs && node scripts/loopback-lint.mjs && node scripts/loopback-lint-selftest.mjs && node scripts/deploy-lint.mjs && node scripts/deploy-lint-selftest.mjs && node scripts/installer-verify-test.mjs && node scripts/ps1-cmdlet-lint.mjs && node scripts/ps1-cmdlet-lint-selftest.mjs && node scripts/e2e-lint.mjs && node scripts/e2e-lint-selftest.mjs && node scripts/clamp-issue-body-selftest.mjs && node --test scripts/sign-and-deploy.test.mjs && node --test scripts/workflow-audit.test.mjs && node --test scripts/pairing-walkthrough/proc.test.mjs && node --test scripts/security-audit.test.mjs && pnpm --filter dormouse-hosted test:deploy && pnpm -r --filter \"!dormouse-hosted\" run test", "lint:specs": "node scripts/spec-lint.mjs && node scripts/spec-lint-selftest.mjs", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e362ad666..94dbd4384 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,10 +4,6 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -overrides: - pgstencil: file:vendor/pgstencil-0.1.0.tgz - '@pgstencil/auth': file:vendor/pgstencil-auth-0.1.0.tgz - importers: .: @@ -97,8 +93,8 @@ importers: specifier: ^2.0.10 version: 2.1.1(hono@4.13.8) '@pgstencil/auth': - specifier: file:../vendor/pgstencil-auth-0.1.0.tgz - version: file:vendor/pgstencil-auth-0.1.0.tgz(hono@4.13.8)(kysely@0.29.5)(pg@8.23.0)(pgstencil@file:vendor/pgstencil-0.1.0.tgz(kysely@0.29.5)(supports-color@10.2.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11) + specifier: ^0.2.1 + version: 0.2.1(hono@4.13.8)(kysely@0.29.5)(pg@8.23.0)(pgstencil@0.2.1(kysely@0.29.5)(supports-color@10.2.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11) hono: specifier: ^4.13.8 version: 4.13.8 @@ -106,8 +102,8 @@ importers: specifier: ^0.29.5 version: 0.29.5 pgstencil: - specifier: file:../vendor/pgstencil-0.1.0.tgz - version: file:vendor/pgstencil-0.1.0.tgz(kysely@0.29.5)(supports-color@10.2.2) + specifier: ^0.2.1 + version: 0.2.1(kysely@0.29.5)(supports-color@10.2.2) react: specifier: ^19.2.6 version: 19.3.0 @@ -2455,14 +2451,13 @@ packages: cpu: [x64] os: [win32] - '@pgstencil/auth@file:vendor/pgstencil-auth-0.1.0.tgz': - resolution: {integrity: sha512-uSTdJg6o55DELcvTfeDJTJClsupe/z1NJJw0RFVdAOZIu+DexsSb/Xx5kY5Bcpyq7V51DKhu5rQzZ2BC14yUGw==, tarball: file:vendor/pgstencil-auth-0.1.0.tgz} - version: 0.1.0 + '@pgstencil/auth@0.2.1': + resolution: {integrity: sha512-B5Wu035642KEr0V/VJf+C0MMM9PiAGvpXF9VTeKeiTnBifYQIkO9Cks+RLPiO2laxdBWLlNxo6RDVbjLaIiufA==} engines: {node: '>=24'} peerDependencies: hono: ^4.13.7 kysely: ^0.29.5 - pgstencil: ^0.1.0 + pgstencil: ^0.2.1 '@phosphor-icons/react@2.1.10': resolution: {integrity: sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==} @@ -5389,9 +5384,8 @@ packages: pgpass@1.0.5: resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} - pgstencil@file:vendor/pgstencil-0.1.0.tgz: - resolution: {integrity: sha512-XKNjf7opuYcQU1+zK0YCFb3eXvMGsjIrHIXyUANpZoBVWd1XV8c4i4u6S0d3rqjOw9LHeTu/2kdA+Z9wSrCSPw==, tarball: file:vendor/pgstencil-0.1.0.tgz} - version: 0.1.0 + pgstencil@0.2.1: + resolution: {integrity: sha512-Rvap6rNjtJfncsZUvM97BQwPsvOoi5lpg0R8a+r+PJOmAKh0UfwLNaoV2iONqJxSnZtSa5A+n6xPQ2ojyA1qdw==} engines: {node: '>=24'} peerDependencies: kysely: ^0.29.5 @@ -6112,10 +6106,6 @@ packages: undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - undici@7.28.0: - resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} - engines: {node: '>=20.18.1'} - undici@7.29.0: resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} engines: {node: '>=20.18.1'} @@ -7952,14 +7942,14 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.21.2': optional: true - '@pgstencil/auth@file:vendor/pgstencil-auth-0.1.0.tgz(hono@4.13.8)(kysely@0.29.5)(pg@8.23.0)(pgstencil@file:vendor/pgstencil-0.1.0.tgz(kysely@0.29.5)(supports-color@10.2.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11)': + '@pgstencil/auth@0.2.1(hono@4.13.8)(kysely@0.29.5)(pg@8.23.0)(pgstencil@0.2.1(kysely@0.29.5)(supports-color@10.2.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11)': dependencies: better-auth: 1.7.3(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11) hono: 4.13.8 jose: 6.2.12 kysely: 0.29.5 openid-client: 6.8.8 - pgstencil: file:vendor/pgstencil-0.1.0.tgz(kysely@0.29.5)(supports-color@10.2.2) + pgstencil: 0.2.1(kysely@0.29.5)(supports-color@10.2.2) transitivePeerDependencies: - '@cloudflare/workers-types' - '@lynx-js/react' @@ -9212,7 +9202,7 @@ snapshots: parse5: 7.3.0 parse5-htmlparser2-tree-adapter: 7.1.0 parse5-parser-stream: 7.1.2 - undici: 7.28.0 + undici: 7.29.0 whatwg-mimetype: 4.0.0 chokidar@5.0.0: @@ -10911,7 +10901,7 @@ snapshots: dependencies: split2: 4.2.0 - pgstencil@file:vendor/pgstencil-0.1.0.tgz(kysely@0.29.5)(supports-color@10.2.2): + pgstencil@0.2.1(kysely@0.29.5)(supports-color@10.2.2): dependencies: cheerio: 1.2.0 kysely: 0.29.5 @@ -11839,8 +11829,6 @@ snapshots: undici-types@7.18.2: {} - undici@7.28.0: {} - undici@7.29.0: {} undici@8.10.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e70ea5fb0..85191aeae 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -42,6 +42,6 @@ peerDependencyRules: # sockets that would break first. "@hono/node-ws>@hono/node-server": ^2.0.0 minimumReleaseAge: 1440 -overrides: - pgstencil: file:vendor/pgstencil-0.1.0.tgz - '@pgstencil/auth': file:vendor/pgstencil-auth-0.1.0.tgz +minimumReleaseAgeExclude: + - pgstencil + - "@pgstencil/*" diff --git a/scripts/spec-lint.mjs b/scripts/spec-lint.mjs index d7d9ebdc2..ed78b71c3 100644 --- a/scripts/spec-lint.mjs +++ b/scripts/spec-lint.mjs @@ -84,7 +84,7 @@ import { countWords, proseLines as proseLinesOf, SOURCE_EXTENSIONS } from './spe const SPECS_DIR = 'docs/specs'; const TOP_LEVEL_DIRS = [ - 'lib/', 'standalone/', 'vscode-ext/', 'website/', 'relay/', 'hosted/', 'vendor/', + 'lib/', 'standalone/', 'vscode-ext/', 'website/', 'relay/', 'hosted/', 'remote-lib-common/', 'dor/', 'dor-lib-common/', 'canopy/', 'docs/', 'scripts/', 'deploy/', '.github/', '.claude/', '.vscode/', ]; diff --git a/scripts/spec-word-budgets.json b/scripts/spec-word-budgets.json index 170eeeb77..04373779d 100644 --- a/scripts/spec-word-budgets.json +++ b/scripts/spec-word-budgets.json @@ -21,10 +21,10 @@ "docs/specs/remote-security-model.md": 4750, "docs/specs/security-audit.md": 2100, "docs/specs/security-ci.md": 2950, - "docs/specs/security-hosted.md": 600, + "docs/specs/security-hosted.md": 650, "docs/specs/security-local.md": 3200, "docs/specs/security-remote.md": 5750, - "docs/specs/security-supply-chain.md": 1150, + "docs/specs/security-supply-chain.md": 1200, "docs/specs/security.md": 1900, "docs/specs/shortcuts.md": 1100, "docs/specs/standalone.md": 11800, diff --git a/scripts/sync-pgstencil.mjs b/scripts/sync-pgstencil.mjs deleted file mode 100644 index b32354691..000000000 --- a/scripts/sync-pgstencil.mjs +++ /dev/null @@ -1,161 +0,0 @@ -import { execFileSync } from "node:child_process"; -import { - mkdirSync, - mkdtempSync, - readFileSync, - writeFileSync, - copyFileSync, -} from "node:fs"; -import { tmpdir } from "node:os"; -import { join, resolve } from "node:path"; -import { createHash } from "node:crypto"; -import { fileURLToPath } from "node:url"; - -const root = fileURLToPath(new URL("../", import.meta.url)); -const args = process.argv.slice(2); -// There are no flags, so a stray one (such as the removed --working-tree), or an -// empty argument from an unset variable, must not be read as a path or revision. -if ( - args.length < 1 || - args.length > 2 || - args.some((arg) => !arg || arg.startsWith("-")) -) - throw new Error("Usage: pnpm pgstencil:sync /path/to/pgstencil [revision]"); -const [source, revision = "HEAD"] = args; -const repository = resolve(source); -const run = (command, args, cwd, env = process.env) => - execFileSync(command, args, { cwd, stdio: "inherit", env }); -const git = (...args) => - execFileSync("git", args, { cwd: repository, encoding: "utf8" }).trim(); -const succeeds = (attempt) => { - try { - attempt(); - return true; - } catch { - return false; - } -}; -// Every pgstencil pack writes package/dist/provenance.json: the archive's own -// claim of the commit it came from, plus `dirty` when it was packed -// --allow-dirty. Dormouse verifies that claim instead of auditing the packed -// code, which pgstencil audits in its own repository — -// docs/specs/security-hosted.md -> "Deployment boundary". -const provenanceOf = (archive) => { - let raw; - try { - raw = execFileSync( - "tar", - ["-xOf", archive, "package/dist/provenance.json"], - { encoding: "utf8" }, - ); - } catch { - throw new Error(`${archive} carries no package/dist/provenance.json`); - } - let claim; - try { - claim = JSON.parse(raw); - } catch { - throw new Error(`${archive}: package/dist/provenance.json is not JSON`); - } - if (typeof claim?.commit !== "string" || !/^[0-9a-f]{40}$/.test(claim.commit)) - throw new Error(`${archive}: provenance.json names no commit`); - return claim; -}; -const manifest = JSON.parse( - readFileSync(resolve(root, "hosted/package.json"), "utf8"), -); -// pnpm resolves `overrides:` ahead of hosted/package.json, so a bump that edits -// only the manifest would still install the previous tarball while build.json -// below records the new one. Read the block by line rather than adding a YAML -// dependency to a root that has no devDependencies at all. -const workspace = readFileSync(resolve(root, "pnpm-workspace.yaml"), "utf8"); -const overrides = new Map( - (/^overrides:\n((?:[ \t].*\n?|\n)*)/m.exec(workspace)?.[1] ?? "") - .split("\n") - .flatMap((line) => { - const entry = /^ {2}['"]?([^'":]+)['"]?: *(\S+)$/.exec(line); - return entry ? [[entry[1], entry[2]]] : []; - }), -); -// Pack a committed revision in a temporary worktree after a frozen install, so -// nothing uncommitted, untracked or ignored in the pgstencil checkout (a stray -// migration, editor settings, stale build output, a local node_modules) can -// reach an archive, and build.json truthfully records `dirty: false`. To try an -// unfinished pgstencil change, commit it to a local branch and sync that. Each -// archive's own provenance must name `commit`; a pack that reports itself -// dirty (pgstencil's --allow-dirty) is refused below rather than vendored. -const commit = git("rev-parse", "--verify", `${revision}^{commit}`); -// Check the pins before the slow install and before any archive is replaced. -const read = (path) => git("show", `${commit}:${path}`); -const archives = [ - ["pgstencil", "pgstencil"], - ["auth", "@pgstencil/auth"], -].map(([directory, name]) => { - const { version } = JSON.parse(read(`packages/${directory}/package.json`)); - const filename = `${name.replace("@", "").replace("/", "-")}-${version}.tgz`; - if (manifest.dependencies[name] !== `file:../vendor/${filename}`) - throw new Error(`Update hosted/package.json for ${filename}`); - if (overrides.get(name) !== `file:vendor/${filename}`) - throw new Error( - `Update the pnpm-workspace.yaml override for ${name} to file:vendor/${filename}`, - ); - return filename; -}); -const checkout = mkdtempSync(join(tmpdir(), "pgstencil-sync-")); -// pgstencil's scripts locate their project from this variable before the cwd. -const pgstencil = { ...process.env, PGSTENCIL_PROJECT_ROOT: checkout }; -git("worktree", "add", "--detach", checkout, commit); -try { - run("pnpm", ["install", "--frozen-lockfile"], checkout, pgstencil); - run("pnpm", ["packages:pack"], checkout, pgstencil); - mkdirSync(resolve(root, "vendor"), { recursive: true }); - for (const filename of archives) { - const target = resolve(root, "vendor", filename); - copyFileSync(resolve(checkout, "dist/packages", filename), target); - // Both archives are held to the commit packed here, so a stale pack — - // or two archives disagreeing with each other — stops the sync before - // build.json can record a commit the bytes do not carry. - const claim = provenanceOf(target); - if (claim.dirty === true) - throw new Error( - `${filename} was packed --allow-dirty; a dirty pack cannot be vendored`, - ); - if (claim.commit !== commit) - throw new Error( - `${filename} was packed from ${claim.commit}, not the ${commit} packed here`, - ); - } -} finally { - git("worktree", "remove", "--force", checkout); -} -const files = archives.map((filename) => ({ - filename, - sha256: createHash("sha256") - .update(readFileSync(resolve(root, "vendor", filename))) - .digest("hex"), -})); -writeFileSync( - resolve(root, "vendor/build.json"), - JSON.stringify({ commit, dirty: false, files }, null, 2) + "\n", -); -// A changed tarball integrity is resolved by a normal install. --force also -// installs foreign-platform optional binaries and distorts dependency disclosure. -run( - "pnpm", - ["--filter", "dormouse-hosted", "update", "pgstencil", "@pgstencil/auth"], - root, -); -// Dormouse `main` must vendor a pgstencil `main` commit, because the nightly -// audit reads that commit's pgstencil `security-audit` check run — but a -// Dormouse branch may vendor a pgstencil branch while a cross-repo change is -// in flight, so this warns and does not fail. -const fetched = succeeds(() => git("fetch", "origin", "main")); -const onMain = - fetched && - succeeds(() => git("merge-base", "--is-ancestor", commit, "origin/main")); -if (!onMain) - console.warn( - fetched - ? `warning: pgstencil ${commit} is not on origin/main — Dormouse main must vendor a pgstencil main commit, and the nightly audit fails until it does` - : `warning: could not fetch pgstencil origin/main to check ${commit} — Dormouse main must vendor a pgstencil main commit`, - ); diff --git a/vendor/build.json b/vendor/build.json deleted file mode 100644 index 217299d6b..000000000 --- a/vendor/build.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "commit": "eaba6d3f15b7a7ecb102e88d3e446e13a32b2f72", - "dirty": false, - "files": [ - { - "filename": "pgstencil-0.1.0.tgz", - "sha256": "7e95e6f90b567eb6e6e757fe3f0c32ff2b0e65ff7791b51da43c442536947c7a" - }, - { - "filename": "pgstencil-auth-0.1.0.tgz", - "sha256": "0f7539ad5171c2d88dd294eda5d1d96af08a4946945a01d4259f356c07c26fca" - } - ] -} diff --git a/vendor/pgstencil-0.1.0.tgz b/vendor/pgstencil-0.1.0.tgz deleted file mode 100644 index b5e62eb36..000000000 Binary files a/vendor/pgstencil-0.1.0.tgz and /dev/null differ diff --git a/vendor/pgstencil-auth-0.1.0.tgz b/vendor/pgstencil-auth-0.1.0.tgz deleted file mode 100644 index 5abcc6fa9..000000000 Binary files a/vendor/pgstencil-auth-0.1.0.tgz and /dev/null differ