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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/specs/hosted.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Every Worker answers `/api/health` and `/api/ready`, 404s anything else under it

**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; Dormouse's own tables migrate from `hosted/server/dormouse-migrations/`. **Never edit a merged migration**: a migrated database never reruns one, so append the next number (pinned by `hosted/server/tests/migrations.test.ts`).

**Must declare every peer dependency of the installed packages in `hosted/package.json`**, so they share Hosted's copy and Renovate updates them.
**Must declare every required peer dependency of the installed packages in `hosted/package.json`'s `dependencies`**, so they share Hosted's copy and Renovate updates them. **Never put an optional peer (pgstencil's tooling) in `dependencies`.**

Source of truth: `WORKERS` in `hosted/scripts/workers.mjs`; `workerApp` in `hosted/server/worker-app.ts`; `verifyPackages` in `hosted/scripts/production.mjs`; `hosted/server/runtime-roles.sql`. Pinned by `hosted/server/tests/boundary.test.ts` and `hosted/server/tests/runtime-roles.test.ts`.

Expand Down
2 changes: 1 addition & 1 deletion docs/specs/security-supply-chain.rationale.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Why the test is "puts on a user's machine, or Hosted serves or runs" rather than

Why Hosted is disclosed though none of it is installed on a computer: `relay.dormouse.sh` serves Pocket, which keeps the phone's Client statics and is one of the two endpoints `docs/specs/remote-security-model.md` -> "Trust Model" names, and its Worker routes every Hosted session and carries the one-time handshake. Hosted's code is part of a Hosted user's trust base as much as the Terminal's is.

Why the Hosted section discloses more than ships: the relay and voice Worker bundles hold 17 npm packages each and the account Worker's 33 (34 releases), and the account page only React's three (wrangler dry-run and Vite source maps, 2026-10) — `pgstencil`, `@pgstencil/auth`, `hono`, and `pg`'s family in all three Workers, `better-auth`'s family in the account Worker alone — while the walk reaches 189 rows past the earlier sections, because `pgstencil` 0.3.0 declares its test harness (`testcontainers`, `cheerio`, `turndown`, `node-pg-migrate`) as runtime dependencies. Without that harness the walk reaches 39, which `pgstencil` 0.4.0 does once published. Pocket and the one-time page bundle only packages the Terminal already lists (their source maps, the same date). A walk over the bundles' metafiles would be exact, but the disclosure gate would then need the whole Hosted build.
Why the Hosted section discloses more than ships: the relay and voice Worker bundles hold 17 npm packages each and the account Worker's 33 (34 releases), and the account page only React's three (wrangler dry-run and Vite source maps, 2026-10) — `pgstencil`, `@pgstencil/auth`, `hono`, and `pg`'s family in all three Workers, `better-auth`'s family in the account Worker alone — while the walk reaches 39 rows past the earlier sections: the extra eight are regular dependencies of `better-auth` and `@pgstencil/auth` that no bundle holds (`@better-auth/{drizzle,mongo,prisma}-adapter`, `@standard-schema/spec`, `nanostores`, `oauth4webapi`, `openid-client`, `set-cookie-parser`). Under `pgstencil` 0.3.0 it reached 189, because that release declared its test harness (`testcontainers`, `cheerio`, `turndown`, `node-pg-migrate`) as runtime dependencies; 0.4.0 makes them optional peers, which `hosted/` installs as devDependencies. Pocket and the one-time page bundle only packages the Terminal already lists (their source maps, the same date). A walk over the bundles' metafiles would be exact, but the disclosure gate would then need the whole Hosted build.

Why the Hosted walk alone skips optional peers: the sidecar and the Relay ship a `node_modules` tree, where every linked peer is on disk; Hosted ships only bundles, which hold a package only where code imports it. pnpm supplies `better-auth`'s optional peers `vitest`, `react`, and `pg` from what the workspace installs, and `vitest` alone dragged in `vite`, `playwright`, and `jsdom`: 133 rows, none bundled. Every package the three Workers and the account page bundle is still disclosed (checked against their source maps, 2026-10): an optional peer a Worker imports, such as `pg`, is reached through a dependency edge too, here `pgstencil`'s. One supplied only by a development dependency and imported by a Worker would go undisclosed.

Expand Down
8 changes: 5 additions & 3 deletions hosted/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"preview:smoke": "node scripts/preview-smoke.mjs"
},
"dependencies": {
"@pgstencil/auth": "^0.3.0",
"pgstencil": "^0.3.0",
"@pgstencil/auth": "^0.4.0",
"pgstencil": "^0.4.0",
"kysely": "^0.29.5",
"hono": "^4.13.8",
"@hono/node-server": "^2.0.10",
Expand All @@ -39,6 +39,8 @@
"tsx": "4.23.15",
"esbuild": "^0.28.2",
"wrangler": "4.141.0",
"miniflare": "5.20260908.0-alpha"
"miniflare": "5.20260908.0-alpha",
"node-pg-migrate": "^9.0.0",
"testcontainers": "^12.2.1"
}
}
15 changes: 11 additions & 4 deletions hosted/server/tests/artifacts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@ const packages = [
["@pgstencil/auth", "@pgstencil/auth/better-auth"],
] as const;

test("Hosted declares every peer of the installed pgstencil packages", () => {
test("Hosted declares every required peer of the installed pgstencil packages, and no optional one, in dependencies", () => {
const { dependencies } = JSON.parse(readFileSync("package.json", "utf8")) as {
dependencies: Record<string, string>;
};
for (const [name, entry] of packages) {
const manifest = new URL("../package.json", import.meta.resolve(entry));
const { peerDependencies = {} } = JSON.parse(readFileSync(manifest, "utf8")) as {
const { peerDependencies = {}, peerDependenciesMeta = {} } = JSON.parse(readFileSync(manifest, "utf8")) as {
peerDependencies?: Record<string, string>;
peerDependenciesMeta?: Record<string, { optional?: boolean }>;
};
for (const peer of Object.keys(peerDependencies))
expect(Object.keys(dependencies), `${name} peers on ${peer}`).toContain(peer);
for (const peer of Object.keys(peerDependencies)) {
// An optional peer is pgstencil's tooling, which the Workers never ship.
if (peerDependenciesMeta[peer]?.optional) {
expect(Object.keys(dependencies), `${name}'s optional peer ${peer}`).not.toContain(peer);
} else {
expect(Object.keys(dependencies), `${name} peers on ${peer}`).toContain(peer);
}
}
}
});

Expand Down
Loading
Loading