@@ -82,10 +82,10 @@ export const SCAFFOLD_BUILT_DEPENDENCIES = ['better-sqlite3', 'esbuild'];
8282 * states, keyed `<declaring package>><peer>` — pnpm's scoped `allowedVersions`
8383 * spelling, so each entry widens exactly one declaration and nothing else.
8484 *
85- * Both are reported by `pnpm install` on a brand-new scaffold, and neither is a
86- * real incompatibility. They are declared here because that report is the first
87- * thing a newcomer sees, on the one screen where they are deciding whether this
88- * project is solid, and there is nothing they did to cause it.
85+ * Every one of them is reported by `pnpm install` on a brand-new scaffold, and
86+ * none is a real incompatibility. They are declared here because that report is
87+ * the first thing a newcomer sees, on the one screen where they are deciding
88+ * whether this project is solid, and there is nothing they did to cause it.
8989 *
9090 * - `better-auth>better-sqlite3` — better-auth 1.7.1 peers `^12.0.0` while the
9191 * tree resolves 13.x (`@objectstack/driver-sql`'s optional dependency). The
@@ -105,12 +105,62 @@ export const SCAFFOLD_BUILT_DEPENDENCIES = ['better-sqlite3', 'esbuild'];
105105 * HOST's better-call instance, so the single 1.4.0 copy every install
106106 * already resolves is the correct tree, not a skew to repair.
107107 * ⚠️ This entry retires together with the SCIM rc pin — delete both at once.
108+ * Stable `@better-auth/scim@1.7.1` peers `better-call@1.4.0`, so the skew
109+ * this line covers is genuinely gone the moment the pin moves.
108110 *
109- * `allowedVersions` suppresses the report ONLY; it moves no resolution.
111+ * - `<four>@better-auth/utils` — `@better-auth/core`, `/oauth-provider`,
112+ * `/scim` and `/sso` each peer an EXACT `@better-auth/utils@0.4.2`, while a
113+ * scaffolded tree hands them 0.5.0. The 0.5.0 comes from `better-call@1.4.0`
114+ * (better-auth's own HTTP layer), which DEPENDS on `^0.5.0`;
115+ * `@objectstack/plugin-auth` names the four packages as direct dependencies
116+ * without naming utils, so pnpm satisfies their peer from better-call's copy
117+ * rather than from better-auth's own exact 0.4.2 dependency.
118+ *
119+ * Measured compatible rather than assumed. Those four import exactly three
120+ * symbols across two subpaths — `base64`/`base64Url` (`/base64`),
121+ * `createHash` (`/hash`) and, in core only, `createRandomStringGenerator`
122+ * (`/random`). 0.5.0 exports all three with identical signatures; `/random`
123+ * is unchanged apart from formatting, `/base64` swaps `new Uint8Array(data)`
124+ * for a helper that IS `new Uint8Array(data)` on non-strings, and `/hash`
125+ * only widens its input coercion for views not backed by a plain
126+ * ArrayBuffer. Run against the input shapes those call sites actually pass,
127+ * 0.4.2 and 0.5.0 agree on every value; run end to end (better-auth with the
128+ * sso, oauth-provider and scim plugins), a tree where the four resolve 0.5.0
129+ * and one where they resolve 0.4.2 produce the same transcript — sign-up,
130+ * sign-in, session, both OAuth metadata documents, the RFC 7636 PKCE
131+ * challenge, and the SCIM and SSO endpoint outcomes.
132+ *
133+ * ⛔ A resolution change is the WRONG remedy here, and was measured too:
134+ * pinning utils back to 0.4.2 clears the four lines only by dragging
135+ * `better-call@1.4.0` off its own declared `^0.5.0` — manufacturing one real
136+ * range violation to silence four benign ones.
137+ *
138+ * Spelled `0.5.0` exactly, not `0.5`: 0.5.0 is the version that was
139+ * measured, and a future 0.6.0 SHOULD report again rather than inherit this
140+ * finding.
141+ *
142+ * ⚠️ These four do NOT retire with the SCIM rc pin, even though one of them
143+ * names scim. Stable `@better-auth/scim@1.7.1` still peers
144+ * `@better-auth/utils@0.4.2`, so this skew outlives that pin. They retire
145+ * when the four packages accept 0.5.0 upstream, or when
146+ * `SCAFFOLD_PNPM_RANGE` reaches `>=10.31` — pnpm 10.31 changed peer
147+ * resolution so that all four land on 0.4.2 by themselves. Measured on the
148+ * rendered scaffold, one clean resolve per pnpm version:
149+ *
150+ * pnpm 10.15.0 – 10.30.0 all four reported as unmet peers.
151+ * pnpm >= 10.31.0 resolved to 0.4.2; nothing to report.
152+ *
153+ * `allowedVersions` suppresses the report ONLY; it moves no resolution — the
154+ * lockfile a scaffold resolves is byte-identical with and without this block
155+ * (verified by digest on pnpm 10.15.0 and 10.30.0).
110156 */
111157export const SCAFFOLD_ALLOWED_PEER_VERSIONS : Record < string , string > = {
112158 'better-auth>better-sqlite3' : '13' ,
113159 '@better-auth/scim>better-call' : '1.4.0' ,
160+ '@better-auth/core>@better-auth/utils' : '0.5.0' ,
161+ '@better-auth/oauth-provider>@better-auth/utils' : '0.5.0' ,
162+ '@better-auth/scim>@better-auth/utils' : '0.5.0' ,
163+ '@better-auth/sso>@better-auth/utils' : '0.5.0' ,
114164} ;
115165
116166/**
@@ -186,7 +236,7 @@ export function renderScaffoldPackageJson(
186236
187237/**
188238 * Render the `pnpm-workspace.yaml` that allowlists native build scripts and
189- * declares the two known-benign peer skews.
239+ * declares the known-benign peer skews.
190240 * Declares an explicit empty `packages: []`: a workspace root with no member
191241 * packages, which is what a single-package scaffold is — the file stays purely
192242 * a settings file. Spelling the key out is what lets pnpm 10.0–10.4 (and 9.x)
@@ -257,9 +307,9 @@ export function renderPnpmWorkspaceYaml(
257307 // declaration that is not there.
258308 ...( peerEntries . length === 0 ? [ ] : [
259309 '' ,
260- '# Two third -party peer ranges resolve outside what their declaring package ' ,
261- '# states, and pnpm reports both on a first install. Neither is a real ' ,
262- '# incompatibility:' ,
310+ '# Third -party peer ranges that resolve outside what their declaring' ,
311+ '# package states, and that pnpm reports on a first install. None is a' ,
312+ '# real incompatibility:' ,
263313 '#' ,
264314 '# better-auth peers better-sqlite3 ^12.0.0 while the tree resolves 13.x.' ,
265315 '# That peer is optional and covers handing better-auth a raw' ,
@@ -272,7 +322,18 @@ export function renderPnpmWorkspaceYaml(
272322 '# better-auth plugin has to share the host\'s better-call instance, so' ,
273323 '# the single 1.4.0 copy is the correct resolution.' ,
274324 '#' ,
275- '# These suppress the report only — no resolution moves.' ,
325+ '# @better-auth/core, /oauth-provider, /scim and /sso each peer an exact' ,
326+ '# @better-auth/utils 0.4.2, while better-call (better-auth\'s own HTTP' ,
327+ '# layer) depends on ^0.5.0 and is what the tree resolves them against.' ,
328+ '# 0.5.0 keeps every symbol those four import — base64/base64Url,' ,
329+ '# createHash, createRandomStringGenerator — with the same signatures' ,
330+ '# and the same values on the inputs they pass, so the report is the' ,
331+ '# only difference. Pinning utils back instead would drag better-call' ,
332+ '# off its own declared range, which is a real violation rather than a' ,
333+ '# reported one.' ,
334+ '#' ,
335+ '# These suppress the report only — no resolution moves, and the lockfile' ,
336+ '# is byte-identical with and without this block.' ,
276337 'peerDependencyRules:' ,
277338 ' allowedVersions:' ,
278339 ...peerEntries . map ( ( [ k , v ] ) => ` '${ k } ': '${ v } '` ) ,
0 commit comments