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
20 changes: 20 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -2130,6 +2130,26 @@ jobs:
- name: CLI test child-env guard
run: pnpm check:cli-test-child-env

# #13940. A version stamp about a PINNED VENDOR is an attestation: it says a
# behaviour was MEASURED against the version it names. The same drift has
# been swept by hand four times (#10073: 29 stamps, #10188: +3, #11362: +2,
# #13940: 95) and the population GREW each round, so the sweep was trailing
# a producer rather than converging.
# The producer is a sentence that fuses a permanent fact with a LIVE READING
# of a value that moves — "the installed better-auth 1.7.1" is not stale
# after a bump, it is false. This gate reds on exactly that shape and on
# nothing else. It deliberately does NOT hold stamps equal to the resolved
# pin: restamping without re-measuring manufactures an attestation nobody
# made, which is strictly worse than a stale one, and 130 of today's stamps
# are anchored historical facts that must never be rewritten. Drift is
# measured and REPORTED, never enforced.
# Static text scan over ~5,600 sources; no build, no install (the pin comes
# from pnpm-lock.yaml). Runs its own --self-test first: the gate computes
# its own population, so a scan that stopped matching would print a
# confident green over a tree it never read.
- name: Vendor version stamps
run: pnpm check:vendor-version-stamps

# #12016. A CLI command id spelled as a string literal OUTSIDE the CLI
# package has nothing tying it to the command file it names: rename the
# command, update the CLI and the docs, and the engine's own drift warning
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,7 @@
"check:org-identifier": "node scripts/check-org-identifier.mjs --self-test && node scripts/check-org-identifier.mjs",
"check:runner-env-posture": "node scripts/check-runner-env-posture.mjs --self-test && node scripts/check-runner-env-posture.mjs",
"check:cli-test-child-env": "node scripts/check-cli-test-child-env.mjs --self-test && node scripts/check-cli-test-child-env.mjs",
"check:vendor-version-stamps": "node scripts/check-vendor-version-stamps.mjs --self-test && node scripts/check-vendor-version-stamps.mjs",
"check:cli-command-ids": "node scripts/check-cli-command-ids.mjs --self-test && node scripts/check-cli-command-ids.mjs",
"check:authz-resolver": "node scripts/check-single-authz-resolver.mjs --self-test && node scripts/check-single-authz-resolver.mjs",
"check:slot-lookup": "node scripts/check-slot-lookup-ratchet.mjs --self-test && node scripts/check-slot-lookup-ratchet.mjs",
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/commands/init.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,8 +147,10 @@ export const SCAFFOLD_BUILT_DEPENDENCIES = ['better-sqlite3', 'esbuild'];
* finding.
*
* ⚠️ These four do NOT retire with the SCIM rc pin, even though one of them
* names scim. Stable `@better-auth/scim@1.7.1` still peers
* `@better-auth/utils@0.4.2`, so this skew outlives that pin. They retire
* names scim. Stable `@better-auth/scim@1.7.2` still peers
* `@better-auth/utils@0.4.2` EXACTLY — re-read off the installed package
* manifest on 2026-08-31 for #13940, unchanged from 1.7.1 — so this skew
* outlives that pin. They retire
* when the four packages accept 0.5.0 upstream, or when
* `SCAFFOLD_PNPM_RANGE` reaches `>=10.31` — pnpm 10.31 changed peer
* resolution so that all four land on 0.4.2 by themselves. Measured on the
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ export const SysMember = ObjectSchema.create({
// The two optional fields are NOT symmetric. `organizationId` defaults
// to the caller's active organization when omitted; `teamId` has no such
// fallback — omit it and the member simply joins no team. Measured on the
// installed better-auth 1.7.1
// then-installed better-auth 1.7.1
// (`dist/plugins/organization/routes/crud-members.mjs`, `addMember`):
// `ctx.body.organizationId || session?.session.activeOrganizationId`
// against `"teamId" in ctx.body ? ctx.body.teamId : void 0`, and no
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@
* ## The measurement this shape rests on
*
* Whether better-auth 1.7.1 permits overriding a path another plugin registers
* was explicitly UNMEASURED when this was ruled. Measured now, on the installed
* was explicitly UNMEASURED when this was ruled. Measured on the then-installed
* `better-auth@1.7.1`:
*
* - `checkEndpointConflicts` (`dist/api/index.mjs`) builds its registry by
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,7 @@
* ## The defect, and where it is minted
*
* Not here: the wrong answer comes out of the pinned vendor. better-auth
* `1.7.1` (the installed line, re-read for this card),
* `1.7.1` (the line installed at the time, re-read for this card),
* `dist/plugins/admin/routes.mjs`, `revokeUserSession` runs, after its
* `session: ["revoke"]` permission check:
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@
*
* The bridges re-dispatch into `@better-auth/sso` "so all of its gates run"
* (register-sso-provider.ts). The premise this file pins is that the vendor's
* gates are NOT a platform-admin gate. Measured on the INSTALLED
* gates are NOT a platform-admin gate. Measured on the THEN-INSTALLED
* `@better-auth/sso` 1.7.1 (dist/index.mjs, `registerSSOProvider`):
*
* • `/sso/register` requires a session — and nothing more — when the body
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/adopt-membership.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,7 +70,7 @@
* membership**, so a `create` naming an (org, user) pair that already exists is
* not a second membership — it is that membership.
*
* Blast radius, measured against the installed better-auth (re-measured on
* Blast radius, measured against the then-installed better-auth (re-measured on
* 1.7.1) rather than assumed — accept-invitation is the ONLY `member` create
* that can reach an existing pair:
*
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/auth-manager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3815,7 +3815,7 @@ export class AuthManager {
*
* The scan existed to make the match case-insensitive, on the stated belief
* that invitation addresses are stored as the inviter typed them while
* better-auth lowercases the registrant's. Measured on the installed
* better-auth lowercases the registrant's. Measured on the then-installed
* better-auth 1.7.1, that belief is wrong on BOTH halves — the vendor
* normalizes each side before this gate ever sees it:
*
Expand Down
4 changes: 2 additions & 2 deletions packages/plugins/plugin-auth/src/auth-plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2143,7 +2143,7 @@ export class AuthPlugin implements Plugin {
// #9653 — ADR-0068 D4: the platform-admin gate runs HERE, before the
// bridge delegates. Registering an identity provider is a platform-
// operator action, and the delegated authorization is NOT a substitute:
// measured on the installed @better-auth/sso 1.7.1, the vendor's
// measured on the then-installed @better-auth/sso 1.7.1, the vendor's
// /sso/register admits ANY authenticated user when no organizationId is
// supplied (the org-admin check is inside `if (ctx.body.organizationId)`),
// and the auth-manager before-hook that narrows this admits org
Expand DownExpand Up@@ -2352,7 +2352,7 @@ export class AuthPlugin implements Plugin {
// reached by any AUTHENTICATED caller, admin or not, before either
// authorization layer had run.
//
// MEASURED on the installed better-auth 1.7.1 before this mount existed,
// MEASURED on the then-installed better-auth 1.7.1 before this mount existed,
// one authenticated non-admin, two targets: naming the break-glass
// holder answered `409 LAST_LOCAL_CREDENTIAL` while naming an ordinary
// user answered `403 YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS`. Two different
Expand Down
8 changes: 5 additions & 3 deletions packages/plugins/plugin-auth/src/auth-schema-config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -953,9 +953,11 @@ export const buildOidcProviderPluginSchema = buildOauthProviderPluginSchema;

// NOTE: there is intentionally no scim mapping constant here, and no
// `buildScimPluginSchema()`. `@better-auth/scim` hardcodes its models and exposes
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.1`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all;
// measured 2026-08-19 on the rc, re-measured 2026-08-27 on stable, #3653), so
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.2`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all — its
// six members are connections, authentication, managedConnections, identity,
// projection, compatibility; measured 2026-08-19 on the rc, re-measured
// 2026-08-27 on stable 1.7.1, and again 2026-08-31 on 1.7.2 for #13940), so
// there is nowhere to hand one. This is no longer true
// of `@better-auth/sso@1.7.1`, which now accepts one (#8224) — for scim, and for
// scim alone, the ADAPTER layer is the only available route.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
// synthetic `ctx`. That is the right shape for the guard's own predicate, and
// it is structurally blind to the defect this file pins: hook ORDER relative to
// endpoint middleware does not exist in a synthetic call. So every assertion
// here goes through `AuthManager.handleRequest` on the installed better-auth
// here goes through `AuthManager.handleRequest` on the then-installed better-auth
// 1.7.1, where the vendor's own middleware really runs, and reads a status and
// a code off a real `Response`.
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,7 +46,7 @@
*
* The two optional fields are NOT symmetric. `organizationId` defaults to the
* caller's active organization when omitted; `teamId` has no such fallback —
* omit it and the member simply joins no team. Measured on the installed
* omit it and the member simply joins no team. Measured on the then-installed
* better-auth 1.7.1 (`dist/plugins/organization/routes/crud-members.mjs`,
* inside `addMember`'s handler):
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@
//
// • the pure normalizer, where the three narrowings are cheap to enumerate
// (empty body only · 401/403 only · `/admin/` only), and
// • the real `AuthManager.handleRequest` seam on the installed better-auth
// • the real `AuthManager.handleRequest` seam on the then-installed better-auth
// 1.7.1, where the vendor's `adminMiddleware` actually produces the refusal.
//
// ⛔ A refusal-only suite is not enough here and the lane has paid for that
Expand DownExpand Up@@ -146,7 +146,7 @@ describe('#10349 — the normalizer fills in a bodyless vendor /admin/ refusal',
});

// ───────────────────────────────────────────────────────────────────────────
// The real seam, on the installed better-auth 1.7.1
// The real seam, on the then-installed better-auth 1.7.1
// ───────────────────────────────────────────────────────────────────────────

const SECRET = 'test-secret-at-least-32-chars-long!!';
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@
* `adminMiddleware`, which is `getAuthoritativeSessionFromCtx(ctx)` followed by
* `APIError.fromStatus('UNAUTHORIZED')` — no body argument at all.
*
* Measured on the installed better-auth 1.7.1, anonymous, through
* Measured on the then-installed better-auth 1.7.1, anonymous, through
* `AuthManager.handleRequest`, ten vendor-lane routes answered:
*
* POST /admin/impersonate-user -> 401 content-type: application/json body ''
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/dispatcher-error-vocabulary.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -382,7 +382,7 @@ export const UNREGISTERED_CODE_SITES: readonly UnregisteredCodeSite[] = [
door: 'none',
verdict: 'foreign-vocabulary',
why:
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the installed vendor at " +
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the then-installed vendor at " +
'`dist/plugins/admin/error-codes`, spelled there exactly as it is here — and read at runtime ' +
'off `plugin.$ERROR_CODES`, never retyped. The caller-side refusal, raised ' +
"`APIError.from('FORBIDDEN', notAllowed)` inside a better-auth endpoint, so it leaves as the " +
Expand Down
1 change: 1 addition & 0 deletions scripts/check-release-page-status.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -721,6 +721,7 @@ function selfTest() {
'## Landed since 17.0.0-rc.0',
'',
'The 17.0.0 train is currently published as `17.0.0-rc.6`; nothing reaches the `latest` tag.',
// vendor-stamp:fixture — synthetic page text; this version is assertion input, not a claim about this tree
'This paragraph is history, not status, and pins `better-auth 1.7.0-rc.2`.',
].join('\n');
const bq = statusBlockquote(page);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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
20 changes: 20 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -2130,6 +2130,26 @@ jobs:
- name: CLI test child-env guard
run: pnpm check:cli-test-child-env

# #13940. A version stamp about a PINNED VENDOR is an attestation: it says a
# behaviour was MEASURED against the version it names. The same drift has
# been swept by hand four times (#10073: 29 stamps, #10188: +3, #11362: +2,
# #13940: 95) and the population GREW each round, so the sweep was trailing
# a producer rather than converging.
# The producer is a sentence that fuses a permanent fact with a LIVE READING
# of a value that moves — "the installed better-auth 1.7.1" is not stale
# after a bump, it is false. This gate reds on exactly that shape and on
# nothing else. It deliberately does NOT hold stamps equal to the resolved
# pin: restamping without re-measuring manufactures an attestation nobody
# made, which is strictly worse than a stale one, and 130 of today's stamps
# are anchored historical facts that must never be rewritten. Drift is
# measured and REPORTED, never enforced.
# Static text scan over ~5,600 sources; no build, no install (the pin comes
# from pnpm-lock.yaml). Runs its own --self-test first: the gate computes
# its own population, so a scan that stopped matching would print a
# confident green over a tree it never read.
- name: Vendor version stamps
run: pnpm check:vendor-version-stamps

# #12016. A CLI command id spelled as a string literal OUTSIDE the CLI
# package has nothing tying it to the command file it names: rename the
# command, update the CLI and the docs, and the engine's own drift warning
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,7 @@
"check:org-identifier": "node scripts/check-org-identifier.mjs --self-test && node scripts/check-org-identifier.mjs",
"check:runner-env-posture": "node scripts/check-runner-env-posture.mjs --self-test && node scripts/check-runner-env-posture.mjs",
"check:cli-test-child-env": "node scripts/check-cli-test-child-env.mjs --self-test && node scripts/check-cli-test-child-env.mjs",
"check:vendor-version-stamps": "node scripts/check-vendor-version-stamps.mjs --self-test && node scripts/check-vendor-version-stamps.mjs",
"check:cli-command-ids": "node scripts/check-cli-command-ids.mjs --self-test && node scripts/check-cli-command-ids.mjs",
"check:authz-resolver": "node scripts/check-single-authz-resolver.mjs --self-test && node scripts/check-single-authz-resolver.mjs",
"check:slot-lookup": "node scripts/check-slot-lookup-ratchet.mjs --self-test && node scripts/check-slot-lookup-ratchet.mjs",
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/commands/init.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,8 +147,10 @@ export const SCAFFOLD_BUILT_DEPENDENCIES = ['better-sqlite3', 'esbuild'];
* finding.
*
* ⚠️ These four do NOT retire with the SCIM rc pin, even though one of them
* names scim. Stable `@better-auth/scim@1.7.1` still peers
* `@better-auth/utils@0.4.2`, so this skew outlives that pin. They retire
* names scim. Stable `@better-auth/scim@1.7.2` still peers
* `@better-auth/utils@0.4.2` EXACTLY — re-read off the installed package
* manifest on 2026-08-31 for #13940, unchanged from 1.7.1 — so this skew
* outlives that pin. They retire
* when the four packages accept 0.5.0 upstream, or when
* `SCAFFOLD_PNPM_RANGE` reaches `>=10.31` — pnpm 10.31 changed peer
* resolution so that all four land on 0.4.2 by themselves. Measured on the
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ export const SysMember = ObjectSchema.create({
// The two optional fields are NOT symmetric. `organizationId` defaults
// to the caller's active organization when omitted; `teamId` has no such
// fallback — omit it and the member simply joins no team. Measured on the
// installed better-auth 1.7.1
// then-installed better-auth 1.7.1
// (`dist/plugins/organization/routes/crud-members.mjs`, `addMember`):
// `ctx.body.organizationId || session?.session.activeOrganizationId`
// against `"teamId" in ctx.body ? ctx.body.teamId : void 0`, and no
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@
* ## The measurement this shape rests on
*
* Whether better-auth 1.7.1 permits overriding a path another plugin registers
* was explicitly UNMEASURED when this was ruled. Measured now, on the installed
* was explicitly UNMEASURED when this was ruled. Measured on the then-installed
* `better-auth@1.7.1`:
*
* - `checkEndpointConflicts` (`dist/api/index.mjs`) builds its registry by
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,7 @@
* ## The defect, and where it is minted
*
* Not here: the wrong answer comes out of the pinned vendor. better-auth
* `1.7.1` (the installed line, re-read for this card),
* `1.7.1` (the line installed at the time, re-read for this card),
* `dist/plugins/admin/routes.mjs`, `revokeUserSession` runs, after its
* `session: ["revoke"]` permission check:
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@
*
* The bridges re-dispatch into `@better-auth/sso` "so all of its gates run"
* (register-sso-provider.ts). The premise this file pins is that the vendor's
* gates are NOT a platform-admin gate. Measured on the INSTALLED
* gates are NOT a platform-admin gate. Measured on the THEN-INSTALLED
* `@better-auth/sso` 1.7.1 (dist/index.mjs, `registerSSOProvider`):
*
* • `/sso/register` requires a session — and nothing more — when the body
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/adopt-membership.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,7 +70,7 @@
* membership**, so a `create` naming an (org, user) pair that already exists is
* not a second membership — it is that membership.
*
* Blast radius, measured against the installed better-auth (re-measured on
* Blast radius, measured against the then-installed better-auth (re-measured on
* 1.7.1) rather than assumed — accept-invitation is the ONLY `member` create
* that can reach an existing pair:
*
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/auth-manager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3815,7 +3815,7 @@ export class AuthManager {
*
* The scan existed to make the match case-insensitive, on the stated belief
* that invitation addresses are stored as the inviter typed them while
* better-auth lowercases the registrant's. Measured on the installed
* better-auth lowercases the registrant's. Measured on the then-installed
* better-auth 1.7.1, that belief is wrong on BOTH halves — the vendor
* normalizes each side before this gate ever sees it:
*
Expand Down
4 changes: 2 additions & 2 deletions packages/plugins/plugin-auth/src/auth-plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2143,7 +2143,7 @@ export class AuthPlugin implements Plugin {
// #9653 — ADR-0068 D4: the platform-admin gate runs HERE, before the
// bridge delegates. Registering an identity provider is a platform-
// operator action, and the delegated authorization is NOT a substitute:
// measured on the installed @better-auth/sso 1.7.1, the vendor's
// measured on the then-installed @better-auth/sso 1.7.1, the vendor's
// /sso/register admits ANY authenticated user when no organizationId is
// supplied (the org-admin check is inside `if (ctx.body.organizationId)`),
// and the auth-manager before-hook that narrows this admits org
Expand DownExpand Up@@ -2352,7 +2352,7 @@ export class AuthPlugin implements Plugin {
// reached by any AUTHENTICATED caller, admin or not, before either
// authorization layer had run.
//
// MEASURED on the installed better-auth 1.7.1 before this mount existed,
// MEASURED on the then-installed better-auth 1.7.1 before this mount existed,
// one authenticated non-admin, two targets: naming the break-glass
// holder answered `409 LAST_LOCAL_CREDENTIAL` while naming an ordinary
// user answered `403 YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS`. Two different
Expand Down
8 changes: 5 additions & 3 deletions packages/plugins/plugin-auth/src/auth-schema-config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -953,9 +953,11 @@ export const buildOidcProviderPluginSchema = buildOauthProviderPluginSchema;

// NOTE: there is intentionally no scim mapping constant here, and no
// `buildScimPluginSchema()`. `@better-auth/scim` hardcodes its models and exposes
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.1`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all;
// measured 2026-08-19 on the rc, re-measured 2026-08-27 on stable, #3653), so
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.2`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all — its
// six members are connections, authentication, managedConnections, identity,
// projection, compatibility; measured 2026-08-19 on the rc, re-measured
// 2026-08-27 on stable 1.7.1, and again 2026-08-31 on 1.7.2 for #13940), so
// there is nowhere to hand one. This is no longer true
// of `@better-auth/sso@1.7.1`, which now accepts one (#8224) — for scim, and for
// scim alone, the ADAPTER layer is the only available route.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
// synthetic `ctx`. That is the right shape for the guard's own predicate, and
// it is structurally blind to the defect this file pins: hook ORDER relative to
// endpoint middleware does not exist in a synthetic call. So every assertion
// here goes through `AuthManager.handleRequest` on the installed better-auth
// here goes through `AuthManager.handleRequest` on the then-installed better-auth
// 1.7.1, where the vendor's own middleware really runs, and reads a status and
// a code off a real `Response`.
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,7 +46,7 @@
*
* The two optional fields are NOT symmetric. `organizationId` defaults to the
* caller's active organization when omitted; `teamId` has no such fallback —
* omit it and the member simply joins no team. Measured on the installed
* omit it and the member simply joins no team. Measured on the then-installed
* better-auth 1.7.1 (`dist/plugins/organization/routes/crud-members.mjs`,
* inside `addMember`'s handler):
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@
//
// • the pure normalizer, where the three narrowings are cheap to enumerate
// (empty body only · 401/403 only · `/admin/` only), and
// • the real `AuthManager.handleRequest` seam on the installed better-auth
// • the real `AuthManager.handleRequest` seam on the then-installed better-auth
// 1.7.1, where the vendor's `adminMiddleware` actually produces the refusal.
//
// ⛔ A refusal-only suite is not enough here and the lane has paid for that
Expand DownExpand Up@@ -146,7 +146,7 @@ describe('#10349 — the normalizer fills in a bodyless vendor /admin/ refusal',
});

// ───────────────────────────────────────────────────────────────────────────
// The real seam, on the installed better-auth 1.7.1
// The real seam, on the then-installed better-auth 1.7.1
// ───────────────────────────────────────────────────────────────────────────

const SECRET = 'test-secret-at-least-32-chars-long!!';
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@
* `adminMiddleware`, which is `getAuthoritativeSessionFromCtx(ctx)` followed by
* `APIError.fromStatus('UNAUTHORIZED')` — no body argument at all.
*
* Measured on the installed better-auth 1.7.1, anonymous, through
* Measured on the then-installed better-auth 1.7.1, anonymous, through
* `AuthManager.handleRequest`, ten vendor-lane routes answered:
*
* POST /admin/impersonate-user -> 401 content-type: application/json body ''
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/dispatcher-error-vocabulary.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -382,7 +382,7 @@ export const UNREGISTERED_CODE_SITES: readonly UnregisteredCodeSite[] = [
door: 'none',
verdict: 'foreign-vocabulary',
why:
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the installed vendor at " +
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the then-installed vendor at " +
'`dist/plugins/admin/error-codes`, spelled there exactly as it is here — and read at runtime ' +
'off `plugin.$ERROR_CODES`, never retyped. The caller-side refusal, raised ' +
"`APIError.from('FORBIDDEN', notAllowed)` inside a better-auth endpoint, so it leaves as the " +
Expand Down
1 change: 1 addition & 0 deletions scripts/check-release-page-status.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -721,6 +721,7 @@ function selfTest() {
'## Landed since 17.0.0-rc.0',
'',
'The 17.0.0 train is currently published as `17.0.0-rc.6`; nothing reaches the `latest` tag.',
// vendor-stamp:fixture — synthetic page text; this version is assertion input, not a claim about this tree
'This paragraph is history, not status, and pins `better-auth 1.7.0-rc.2`.',
].join('\n');
const bq = statusBlockquote(page);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
20 changes: 20 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -2130,6 +2130,26 @@ jobs:
- name: CLI test child-env guard
run: pnpm check:cli-test-child-env

# #13940. A version stamp about a PINNED VENDOR is an attestation: it says a
# behaviour was MEASURED against the version it names. The same drift has
# been swept by hand four times (#10073: 29 stamps, #10188: +3, #11362: +2,
# #13940: 95) and the population GREW each round, so the sweep was trailing
# a producer rather than converging.
# The producer is a sentence that fuses a permanent fact with a LIVE READING
# of a value that moves — "the installed better-auth 1.7.1" is not stale
# after a bump, it is false. This gate reds on exactly that shape and on
# nothing else. It deliberately does NOT hold stamps equal to the resolved
# pin: restamping without re-measuring manufactures an attestation nobody
# made, which is strictly worse than a stale one, and 130 of today's stamps
# are anchored historical facts that must never be rewritten. Drift is
# measured and REPORTED, never enforced.
# Static text scan over ~5,600 sources; no build, no install (the pin comes
# from pnpm-lock.yaml). Runs its own --self-test first: the gate computes
# its own population, so a scan that stopped matching would print a
# confident green over a tree it never read.
- name: Vendor version stamps
run: pnpm check:vendor-version-stamps

# #12016. A CLI command id spelled as a string literal OUTSIDE the CLI
# package has nothing tying it to the command file it names: rename the
# command, update the CLI and the docs, and the engine's own drift warning
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,7 @@
"check:org-identifier": "node scripts/check-org-identifier.mjs --self-test && node scripts/check-org-identifier.mjs",
"check:runner-env-posture": "node scripts/check-runner-env-posture.mjs --self-test && node scripts/check-runner-env-posture.mjs",
"check:cli-test-child-env": "node scripts/check-cli-test-child-env.mjs --self-test && node scripts/check-cli-test-child-env.mjs",
"check:vendor-version-stamps": "node scripts/check-vendor-version-stamps.mjs --self-test && node scripts/check-vendor-version-stamps.mjs",
"check:cli-command-ids": "node scripts/check-cli-command-ids.mjs --self-test && node scripts/check-cli-command-ids.mjs",
"check:authz-resolver": "node scripts/check-single-authz-resolver.mjs --self-test && node scripts/check-single-authz-resolver.mjs",
"check:slot-lookup": "node scripts/check-slot-lookup-ratchet.mjs --self-test && node scripts/check-slot-lookup-ratchet.mjs",
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/commands/init.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,8 +147,10 @@ export const SCAFFOLD_BUILT_DEPENDENCIES = ['better-sqlite3', 'esbuild'];
* finding.
*
* ⚠️ These four do NOT retire with the SCIM rc pin, even though one of them
* names scim. Stable `@better-auth/scim@1.7.1` still peers
* `@better-auth/utils@0.4.2`, so this skew outlives that pin. They retire
* names scim. Stable `@better-auth/scim@1.7.2` still peers
* `@better-auth/utils@0.4.2` EXACTLY — re-read off the installed package
* manifest on 2026-08-31 for #13940, unchanged from 1.7.1 — so this skew
* outlives that pin. They retire
* when the four packages accept 0.5.0 upstream, or when
* `SCAFFOLD_PNPM_RANGE` reaches `>=10.31` — pnpm 10.31 changed peer
* resolution so that all four land on 0.4.2 by themselves. Measured on the
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ export const SysMember = ObjectSchema.create({
// The two optional fields are NOT symmetric. `organizationId` defaults
// to the caller's active organization when omitted; `teamId` has no such
// fallback — omit it and the member simply joins no team. Measured on the
// installed better-auth 1.7.1
// then-installed better-auth 1.7.1
// (`dist/plugins/organization/routes/crud-members.mjs`, `addMember`):
// `ctx.body.organizationId || session?.session.activeOrganizationId`
// against `"teamId" in ctx.body ? ctx.body.teamId : void 0`, and no
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@
* ## The measurement this shape rests on
*
* Whether better-auth 1.7.1 permits overriding a path another plugin registers
* was explicitly UNMEASURED when this was ruled. Measured now, on the installed
* was explicitly UNMEASURED when this was ruled. Measured on the then-installed
* `better-auth@1.7.1`:
*
* - `checkEndpointConflicts` (`dist/api/index.mjs`) builds its registry by
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,7 @@
* ## The defect, and where it is minted
*
* Not here: the wrong answer comes out of the pinned vendor. better-auth
* `1.7.1` (the installed line, re-read for this card),
* `1.7.1` (the line installed at the time, re-read for this card),
* `dist/plugins/admin/routes.mjs`, `revokeUserSession` runs, after its
* `session: ["revoke"]` permission check:
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@
*
* The bridges re-dispatch into `@better-auth/sso` "so all of its gates run"
* (register-sso-provider.ts). The premise this file pins is that the vendor's
* gates are NOT a platform-admin gate. Measured on the INSTALLED
* gates are NOT a platform-admin gate. Measured on the THEN-INSTALLED
* `@better-auth/sso` 1.7.1 (dist/index.mjs, `registerSSOProvider`):
*
* • `/sso/register` requires a session — and nothing more — when the body
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/adopt-membership.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,7 +70,7 @@
* membership**, so a `create` naming an (org, user) pair that already exists is
* not a second membership — it is that membership.
*
* Blast radius, measured against the installed better-auth (re-measured on
* Blast radius, measured against the then-installed better-auth (re-measured on
* 1.7.1) rather than assumed — accept-invitation is the ONLY `member` create
* that can reach an existing pair:
*
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/auth-manager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3815,7 +3815,7 @@ export class AuthManager {
*
* The scan existed to make the match case-insensitive, on the stated belief
* that invitation addresses are stored as the inviter typed them while
* better-auth lowercases the registrant's. Measured on the installed
* better-auth lowercases the registrant's. Measured on the then-installed
* better-auth 1.7.1, that belief is wrong on BOTH halves — the vendor
* normalizes each side before this gate ever sees it:
*
Expand Down
4 changes: 2 additions & 2 deletions packages/plugins/plugin-auth/src/auth-plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2143,7 +2143,7 @@ export class AuthPlugin implements Plugin {
// #9653 — ADR-0068 D4: the platform-admin gate runs HERE, before the
// bridge delegates. Registering an identity provider is a platform-
// operator action, and the delegated authorization is NOT a substitute:
// measured on the installed @better-auth/sso 1.7.1, the vendor's
// measured on the then-installed @better-auth/sso 1.7.1, the vendor's
// /sso/register admits ANY authenticated user when no organizationId is
// supplied (the org-admin check is inside `if (ctx.body.organizationId)`),
// and the auth-manager before-hook that narrows this admits org
Expand DownExpand Up@@ -2352,7 +2352,7 @@ export class AuthPlugin implements Plugin {
// reached by any AUTHENTICATED caller, admin or not, before either
// authorization layer had run.
//
// MEASURED on the installed better-auth 1.7.1 before this mount existed,
// MEASURED on the then-installed better-auth 1.7.1 before this mount existed,
// one authenticated non-admin, two targets: naming the break-glass
// holder answered `409 LAST_LOCAL_CREDENTIAL` while naming an ordinary
// user answered `403 YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS`. Two different
Expand Down
8 changes: 5 additions & 3 deletions packages/plugins/plugin-auth/src/auth-schema-config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -953,9 +953,11 @@ export const buildOidcProviderPluginSchema = buildOauthProviderPluginSchema;

// NOTE: there is intentionally no scim mapping constant here, and no
// `buildScimPluginSchema()`. `@better-auth/scim` hardcodes its models and exposes
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.1`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all;
// measured 2026-08-19 on the rc, re-measured 2026-08-27 on stable, #3653), so
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.2`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all — its
// six members are connections, authentication, managedConnections, identity,
// projection, compatibility; measured 2026-08-19 on the rc, re-measured
// 2026-08-27 on stable 1.7.1, and again 2026-08-31 on 1.7.2 for #13940), so
// there is nowhere to hand one. This is no longer true
// of `@better-auth/sso@1.7.1`, which now accepts one (#8224) — for scim, and for
// scim alone, the ADAPTER layer is the only available route.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
// synthetic `ctx`. That is the right shape for the guard's own predicate, and
// it is structurally blind to the defect this file pins: hook ORDER relative to
// endpoint middleware does not exist in a synthetic call. So every assertion
// here goes through `AuthManager.handleRequest` on the installed better-auth
// here goes through `AuthManager.handleRequest` on the then-installed better-auth
// 1.7.1, where the vendor's own middleware really runs, and reads a status and
// a code off a real `Response`.
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,7 +46,7 @@
*
* The two optional fields are NOT symmetric. `organizationId` defaults to the
* caller's active organization when omitted; `teamId` has no such fallback —
* omit it and the member simply joins no team. Measured on the installed
* omit it and the member simply joins no team. Measured on the then-installed
* better-auth 1.7.1 (`dist/plugins/organization/routes/crud-members.mjs`,
* inside `addMember`'s handler):
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@
//
// • the pure normalizer, where the three narrowings are cheap to enumerate
// (empty body only · 401/403 only · `/admin/` only), and
// • the real `AuthManager.handleRequest` seam on the installed better-auth
// • the real `AuthManager.handleRequest` seam on the then-installed better-auth
// 1.7.1, where the vendor's `adminMiddleware` actually produces the refusal.
//
// ⛔ A refusal-only suite is not enough here and the lane has paid for that
Expand DownExpand Up@@ -146,7 +146,7 @@ describe('#10349 — the normalizer fills in a bodyless vendor /admin/ refusal',
});

// ───────────────────────────────────────────────────────────────────────────
// The real seam, on the installed better-auth 1.7.1
// The real seam, on the then-installed better-auth 1.7.1
// ───────────────────────────────────────────────────────────────────────────

const SECRET = 'test-secret-at-least-32-chars-long!!';
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@
* `adminMiddleware`, which is `getAuthoritativeSessionFromCtx(ctx)` followed by
* `APIError.fromStatus('UNAUTHORIZED')` — no body argument at all.
*
* Measured on the installed better-auth 1.7.1, anonymous, through
* Measured on the then-installed better-auth 1.7.1, anonymous, through
* `AuthManager.handleRequest`, ten vendor-lane routes answered:
*
* POST /admin/impersonate-user -> 401 content-type: application/json body ''
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/dispatcher-error-vocabulary.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -382,7 +382,7 @@ export const UNREGISTERED_CODE_SITES: readonly UnregisteredCodeSite[] = [
door: 'none',
verdict: 'foreign-vocabulary',
why:
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the installed vendor at " +
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the then-installed vendor at " +
'`dist/plugins/admin/error-codes`, spelled there exactly as it is here — and read at runtime ' +
'off `plugin.$ERROR_CODES`, never retyped. The caller-side refusal, raised ' +
"`APIError.from('FORBIDDEN', notAllowed)` inside a better-auth endpoint, so it leaves as the " +
Expand Down
1 change: 1 addition & 0 deletions scripts/check-release-page-status.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -721,6 +721,7 @@ function selfTest() {
'## Landed since 17.0.0-rc.0',
'',
'The 17.0.0 train is currently published as `17.0.0-rc.6`; nothing reaches the `latest` tag.',
// vendor-stamp:fixture — synthetic page text; this version is assertion input, not a claim about this tree
'This paragraph is history, not status, and pins `better-auth 1.7.0-rc.2`.',
].join('\n');
const bq = statusBlockquote(page);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
20 changes: 20 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -2130,6 +2130,26 @@ jobs:
- name: CLI test child-env guard
run: pnpm check:cli-test-child-env

# #13940. A version stamp about a PINNED VENDOR is an attestation: it says a
# behaviour was MEASURED against the version it names. The same drift has
# been swept by hand four times (#10073: 29 stamps, #10188: +3, #11362: +2,
# #13940: 95) and the population GREW each round, so the sweep was trailing
# a producer rather than converging.
# The producer is a sentence that fuses a permanent fact with a LIVE READING
# of a value that moves — "the installed better-auth 1.7.1" is not stale
# after a bump, it is false. This gate reds on exactly that shape and on
# nothing else. It deliberately does NOT hold stamps equal to the resolved
# pin: restamping without re-measuring manufactures an attestation nobody
# made, which is strictly worse than a stale one, and 130 of today's stamps
# are anchored historical facts that must never be rewritten. Drift is
# measured and REPORTED, never enforced.
# Static text scan over ~5,600 sources; no build, no install (the pin comes
# from pnpm-lock.yaml). Runs its own --self-test first: the gate computes
# its own population, so a scan that stopped matching would print a
# confident green over a tree it never read.
- name: Vendor version stamps
run: pnpm check:vendor-version-stamps

# #12016. A CLI command id spelled as a string literal OUTSIDE the CLI
# package has nothing tying it to the command file it names: rename the
# command, update the CLI and the docs, and the engine's own drift warning
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,7 @@
"check:org-identifier": "node scripts/check-org-identifier.mjs --self-test && node scripts/check-org-identifier.mjs",
"check:runner-env-posture": "node scripts/check-runner-env-posture.mjs --self-test && node scripts/check-runner-env-posture.mjs",
"check:cli-test-child-env": "node scripts/check-cli-test-child-env.mjs --self-test && node scripts/check-cli-test-child-env.mjs",
"check:vendor-version-stamps": "node scripts/check-vendor-version-stamps.mjs --self-test && node scripts/check-vendor-version-stamps.mjs",
"check:cli-command-ids": "node scripts/check-cli-command-ids.mjs --self-test && node scripts/check-cli-command-ids.mjs",
"check:authz-resolver": "node scripts/check-single-authz-resolver.mjs --self-test && node scripts/check-single-authz-resolver.mjs",
"check:slot-lookup": "node scripts/check-slot-lookup-ratchet.mjs --self-test && node scripts/check-slot-lookup-ratchet.mjs",
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/commands/init.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,8 +147,10 @@ export const SCAFFOLD_BUILT_DEPENDENCIES = ['better-sqlite3', 'esbuild'];
* finding.
*
* ⚠️ These four do NOT retire with the SCIM rc pin, even though one of them
* names scim. Stable `@better-auth/scim@1.7.1` still peers
* `@better-auth/utils@0.4.2`, so this skew outlives that pin. They retire
* names scim. Stable `@better-auth/scim@1.7.2` still peers
* `@better-auth/utils@0.4.2` EXACTLY — re-read off the installed package
* manifest on 2026-08-31 for #13940, unchanged from 1.7.1 — so this skew
* outlives that pin. They retire
* when the four packages accept 0.5.0 upstream, or when
* `SCAFFOLD_PNPM_RANGE` reaches `>=10.31` — pnpm 10.31 changed peer
* resolution so that all four land on 0.4.2 by themselves. Measured on the
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ export const SysMember = ObjectSchema.create({
// The two optional fields are NOT symmetric. `organizationId` defaults
// to the caller's active organization when omitted; `teamId` has no such
// fallback — omit it and the member simply joins no team. Measured on the
// installed better-auth 1.7.1
// then-installed better-auth 1.7.1
// (`dist/plugins/organization/routes/crud-members.mjs`, `addMember`):
// `ctx.body.organizationId || session?.session.activeOrganizationId`
// against `"teamId" in ctx.body ? ctx.body.teamId : void 0`, and no
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@
* ## The measurement this shape rests on
*
* Whether better-auth 1.7.1 permits overriding a path another plugin registers
* was explicitly UNMEASURED when this was ruled. Measured now, on the installed
* was explicitly UNMEASURED when this was ruled. Measured on the then-installed
* `better-auth@1.7.1`:
*
* - `checkEndpointConflicts` (`dist/api/index.mjs`) builds its registry by
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,7 @@
* ## The defect, and where it is minted
*
* Not here: the wrong answer comes out of the pinned vendor. better-auth
* `1.7.1` (the installed line, re-read for this card),
* `1.7.1` (the line installed at the time, re-read for this card),
* `dist/plugins/admin/routes.mjs`, `revokeUserSession` runs, after its
* `session: ["revoke"]` permission check:
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@
*
* The bridges re-dispatch into `@better-auth/sso` "so all of its gates run"
* (register-sso-provider.ts). The premise this file pins is that the vendor's
* gates are NOT a platform-admin gate. Measured on the INSTALLED
* gates are NOT a platform-admin gate. Measured on the THEN-INSTALLED
* `@better-auth/sso` 1.7.1 (dist/index.mjs, `registerSSOProvider`):
*
* • `/sso/register` requires a session — and nothing more — when the body
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/adopt-membership.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,7 +70,7 @@
* membership**, so a `create` naming an (org, user) pair that already exists is
* not a second membership — it is that membership.
*
* Blast radius, measured against the installed better-auth (re-measured on
* Blast radius, measured against the then-installed better-auth (re-measured on
* 1.7.1) rather than assumed — accept-invitation is the ONLY `member` create
* that can reach an existing pair:
*
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/auth-manager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3815,7 +3815,7 @@ export class AuthManager {
*
* The scan existed to make the match case-insensitive, on the stated belief
* that invitation addresses are stored as the inviter typed them while
* better-auth lowercases the registrant's. Measured on the installed
* better-auth lowercases the registrant's. Measured on the then-installed
* better-auth 1.7.1, that belief is wrong on BOTH halves — the vendor
* normalizes each side before this gate ever sees it:
*
Expand Down
4 changes: 2 additions & 2 deletions packages/plugins/plugin-auth/src/auth-plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2143,7 +2143,7 @@ export class AuthPlugin implements Plugin {
// #9653 — ADR-0068 D4: the platform-admin gate runs HERE, before the
// bridge delegates. Registering an identity provider is a platform-
// operator action, and the delegated authorization is NOT a substitute:
// measured on the installed @better-auth/sso 1.7.1, the vendor's
// measured on the then-installed @better-auth/sso 1.7.1, the vendor's
// /sso/register admits ANY authenticated user when no organizationId is
// supplied (the org-admin check is inside `if (ctx.body.organizationId)`),
// and the auth-manager before-hook that narrows this admits org
Expand DownExpand Up@@ -2352,7 +2352,7 @@ export class AuthPlugin implements Plugin {
// reached by any AUTHENTICATED caller, admin or not, before either
// authorization layer had run.
//
// MEASURED on the installed better-auth 1.7.1 before this mount existed,
// MEASURED on the then-installed better-auth 1.7.1 before this mount existed,
// one authenticated non-admin, two targets: naming the break-glass
// holder answered `409 LAST_LOCAL_CREDENTIAL` while naming an ordinary
// user answered `403 YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS`. Two different
Expand Down
8 changes: 5 additions & 3 deletions packages/plugins/plugin-auth/src/auth-schema-config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -953,9 +953,11 @@ export const buildOidcProviderPluginSchema = buildOauthProviderPluginSchema;

// NOTE: there is intentionally no scim mapping constant here, and no
// `buildScimPluginSchema()`. `@better-auth/scim` hardcodes its models and exposes
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.1`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all;
// measured 2026-08-19 on the rc, re-measured 2026-08-27 on stable, #3653), so
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.2`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all — its
// six members are connections, authentication, managedConnections, identity,
// projection, compatibility; measured 2026-08-19 on the rc, re-measured
// 2026-08-27 on stable 1.7.1, and again 2026-08-31 on 1.7.2 for #13940), so
// there is nowhere to hand one. This is no longer true
// of `@better-auth/sso@1.7.1`, which now accepts one (#8224) — for scim, and for
// scim alone, the ADAPTER layer is the only available route.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
// synthetic `ctx`. That is the right shape for the guard's own predicate, and
// it is structurally blind to the defect this file pins: hook ORDER relative to
// endpoint middleware does not exist in a synthetic call. So every assertion
// here goes through `AuthManager.handleRequest` on the installed better-auth
// here goes through `AuthManager.handleRequest` on the then-installed better-auth
// 1.7.1, where the vendor's own middleware really runs, and reads a status and
// a code off a real `Response`.
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,7 +46,7 @@
*
* The two optional fields are NOT symmetric. `organizationId` defaults to the
* caller's active organization when omitted; `teamId` has no such fallback —
* omit it and the member simply joins no team. Measured on the installed
* omit it and the member simply joins no team. Measured on the then-installed
* better-auth 1.7.1 (`dist/plugins/organization/routes/crud-members.mjs`,
* inside `addMember`'s handler):
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@
//
// • the pure normalizer, where the three narrowings are cheap to enumerate
// (empty body only · 401/403 only · `/admin/` only), and
// • the real `AuthManager.handleRequest` seam on the installed better-auth
// • the real `AuthManager.handleRequest` seam on the then-installed better-auth
// 1.7.1, where the vendor's `adminMiddleware` actually produces the refusal.
//
// ⛔ A refusal-only suite is not enough here and the lane has paid for that
Expand DownExpand Up@@ -146,7 +146,7 @@ describe('#10349 — the normalizer fills in a bodyless vendor /admin/ refusal',
});

// ───────────────────────────────────────────────────────────────────────────
// The real seam, on the installed better-auth 1.7.1
// The real seam, on the then-installed better-auth 1.7.1
// ───────────────────────────────────────────────────────────────────────────

const SECRET = 'test-secret-at-least-32-chars-long!!';
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@
* `adminMiddleware`, which is `getAuthoritativeSessionFromCtx(ctx)` followed by
* `APIError.fromStatus('UNAUTHORIZED')` — no body argument at all.
*
* Measured on the installed better-auth 1.7.1, anonymous, through
* Measured on the then-installed better-auth 1.7.1, anonymous, through
* `AuthManager.handleRequest`, ten vendor-lane routes answered:
*
* POST /admin/impersonate-user -> 401 content-type: application/json body ''
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/dispatcher-error-vocabulary.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -382,7 +382,7 @@ export const UNREGISTERED_CODE_SITES: readonly UnregisteredCodeSite[] = [
door: 'none',
verdict: 'foreign-vocabulary',
why:
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the installed vendor at " +
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the then-installed vendor at " +
'`dist/plugins/admin/error-codes`, spelled there exactly as it is here — and read at runtime ' +
'off `plugin.$ERROR_CODES`, never retyped. The caller-side refusal, raised ' +
"`APIError.from('FORBIDDEN', notAllowed)` inside a better-auth endpoint, so it leaves as the " +
Expand Down
1 change: 1 addition & 0 deletions scripts/check-release-page-status.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -721,6 +721,7 @@ function selfTest() {
'## Landed since 17.0.0-rc.0',
'',
'The 17.0.0 train is currently published as `17.0.0-rc.6`; nothing reaches the `latest` tag.',
// vendor-stamp:fixture — synthetic page text; this version is assertion input, not a claim about this tree
'This paragraph is history, not status, and pins `better-auth 1.7.0-rc.2`.',
].join('\n');
const bq = statusBlockquote(page);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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
20 changes: 20 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -2130,6 +2130,26 @@ jobs:
- name: CLI test child-env guard
run: pnpm check:cli-test-child-env

# #13940. A version stamp about a PINNED VENDOR is an attestation: it says a
# behaviour was MEASURED against the version it names. The same drift has
# been swept by hand four times (#10073: 29 stamps, #10188: +3, #11362: +2,
# #13940: 95) and the population GREW each round, so the sweep was trailing
# a producer rather than converging.
# The producer is a sentence that fuses a permanent fact with a LIVE READING
# of a value that moves — "the installed better-auth 1.7.1" is not stale
# after a bump, it is false. This gate reds on exactly that shape and on
# nothing else. It deliberately does NOT hold stamps equal to the resolved
# pin: restamping without re-measuring manufactures an attestation nobody
# made, which is strictly worse than a stale one, and 130 of today's stamps
# are anchored historical facts that must never be rewritten. Drift is
# measured and REPORTED, never enforced.
# Static text scan over ~5,600 sources; no build, no install (the pin comes
# from pnpm-lock.yaml). Runs its own --self-test first: the gate computes
# its own population, so a scan that stopped matching would print a
# confident green over a tree it never read.
- name: Vendor version stamps
run: pnpm check:vendor-version-stamps

# #12016. A CLI command id spelled as a string literal OUTSIDE the CLI
# package has nothing tying it to the command file it names: rename the
# command, update the CLI and the docs, and the engine's own drift warning
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,7 @@
"check:org-identifier": "node scripts/check-org-identifier.mjs --self-test && node scripts/check-org-identifier.mjs",
"check:runner-env-posture": "node scripts/check-runner-env-posture.mjs --self-test && node scripts/check-runner-env-posture.mjs",
"check:cli-test-child-env": "node scripts/check-cli-test-child-env.mjs --self-test && node scripts/check-cli-test-child-env.mjs",
"check:vendor-version-stamps": "node scripts/check-vendor-version-stamps.mjs --self-test && node scripts/check-vendor-version-stamps.mjs",
"check:cli-command-ids": "node scripts/check-cli-command-ids.mjs --self-test && node scripts/check-cli-command-ids.mjs",
"check:authz-resolver": "node scripts/check-single-authz-resolver.mjs --self-test && node scripts/check-single-authz-resolver.mjs",
"check:slot-lookup": "node scripts/check-slot-lookup-ratchet.mjs --self-test && node scripts/check-slot-lookup-ratchet.mjs",
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/commands/init.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,8 +147,10 @@ export const SCAFFOLD_BUILT_DEPENDENCIES = ['better-sqlite3', 'esbuild'];
* finding.
*
* ⚠️ These four do NOT retire with the SCIM rc pin, even though one of them
* names scim. Stable `@better-auth/scim@1.7.1` still peers
* `@better-auth/utils@0.4.2`, so this skew outlives that pin. They retire
* names scim. Stable `@better-auth/scim@1.7.2` still peers
* `@better-auth/utils@0.4.2` EXACTLY — re-read off the installed package
* manifest on 2026-08-31 for #13940, unchanged from 1.7.1 — so this skew
* outlives that pin. They retire
* when the four packages accept 0.5.0 upstream, or when
* `SCAFFOLD_PNPM_RANGE` reaches `>=10.31` — pnpm 10.31 changed peer
* resolution so that all four land on 0.4.2 by themselves. Measured on the
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ export const SysMember = ObjectSchema.create({
// The two optional fields are NOT symmetric. `organizationId` defaults
// to the caller's active organization when omitted; `teamId` has no such
// fallback — omit it and the member simply joins no team. Measured on the
// installed better-auth 1.7.1
// then-installed better-auth 1.7.1
// (`dist/plugins/organization/routes/crud-members.mjs`, `addMember`):
// `ctx.body.organizationId || session?.session.activeOrganizationId`
// against `"teamId" in ctx.body ? ctx.body.teamId : void 0`, and no
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@
* ## The measurement this shape rests on
*
* Whether better-auth 1.7.1 permits overriding a path another plugin registers
* was explicitly UNMEASURED when this was ruled. Measured now, on the installed
* was explicitly UNMEASURED when this was ruled. Measured on the then-installed
* `better-auth@1.7.1`:
*
* - `checkEndpointConflicts` (`dist/api/index.mjs`) builds its registry by
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,7 @@
* ## The defect, and where it is minted
*
* Not here: the wrong answer comes out of the pinned vendor. better-auth
* `1.7.1` (the installed line, re-read for this card),
* `1.7.1` (the line installed at the time, re-read for this card),
* `dist/plugins/admin/routes.mjs`, `revokeUserSession` runs, after its
* `session: ["revoke"]` permission check:
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@
*
* The bridges re-dispatch into `@better-auth/sso` "so all of its gates run"
* (register-sso-provider.ts). The premise this file pins is that the vendor's
* gates are NOT a platform-admin gate. Measured on the INSTALLED
* gates are NOT a platform-admin gate. Measured on the THEN-INSTALLED
* `@better-auth/sso` 1.7.1 (dist/index.mjs, `registerSSOProvider`):
*
* • `/sso/register` requires a session — and nothing more — when the body
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/adopt-membership.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,7 +70,7 @@
* membership**, so a `create` naming an (org, user) pair that already exists is
* not a second membership — it is that membership.
*
* Blast radius, measured against the installed better-auth (re-measured on
* Blast radius, measured against the then-installed better-auth (re-measured on
* 1.7.1) rather than assumed — accept-invitation is the ONLY `member` create
* that can reach an existing pair:
*
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/auth-manager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3815,7 +3815,7 @@ export class AuthManager {
*
* The scan existed to make the match case-insensitive, on the stated belief
* that invitation addresses are stored as the inviter typed them while
* better-auth lowercases the registrant's. Measured on the installed
* better-auth lowercases the registrant's. Measured on the then-installed
* better-auth 1.7.1, that belief is wrong on BOTH halves — the vendor
* normalizes each side before this gate ever sees it:
*
Expand Down
4 changes: 2 additions & 2 deletions packages/plugins/plugin-auth/src/auth-plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2143,7 +2143,7 @@ export class AuthPlugin implements Plugin {
// #9653 — ADR-0068 D4: the platform-admin gate runs HERE, before the
// bridge delegates. Registering an identity provider is a platform-
// operator action, and the delegated authorization is NOT a substitute:
// measured on the installed @better-auth/sso 1.7.1, the vendor's
// measured on the then-installed @better-auth/sso 1.7.1, the vendor's
// /sso/register admits ANY authenticated user when no organizationId is
// supplied (the org-admin check is inside `if (ctx.body.organizationId)`),
// and the auth-manager before-hook that narrows this admits org
Expand DownExpand Up@@ -2352,7 +2352,7 @@ export class AuthPlugin implements Plugin {
// reached by any AUTHENTICATED caller, admin or not, before either
// authorization layer had run.
//
// MEASURED on the installed better-auth 1.7.1 before this mount existed,
// MEASURED on the then-installed better-auth 1.7.1 before this mount existed,
// one authenticated non-admin, two targets: naming the break-glass
// holder answered `409 LAST_LOCAL_CREDENTIAL` while naming an ordinary
// user answered `403 YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS`. Two different
Expand Down
8 changes: 5 additions & 3 deletions packages/plugins/plugin-auth/src/auth-schema-config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -953,9 +953,11 @@ export const buildOidcProviderPluginSchema = buildOauthProviderPluginSchema;

// NOTE: there is intentionally no scim mapping constant here, and no
// `buildScimPluginSchema()`. `@better-auth/scim` hardcodes its models and exposes
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.1`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all;
// measured 2026-08-19 on the rc, re-measured 2026-08-27 on stable, #3653), so
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.2`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all — its
// six members are connections, authentication, managedConnections, identity,
// projection, compatibility; measured 2026-08-19 on the rc, re-measured
// 2026-08-27 on stable 1.7.1, and again 2026-08-31 on 1.7.2 for #13940), so
// there is nowhere to hand one. This is no longer true
// of `@better-auth/sso@1.7.1`, which now accepts one (#8224) — for scim, and for
// scim alone, the ADAPTER layer is the only available route.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
// synthetic `ctx`. That is the right shape for the guard's own predicate, and
// it is structurally blind to the defect this file pins: hook ORDER relative to
// endpoint middleware does not exist in a synthetic call. So every assertion
// here goes through `AuthManager.handleRequest` on the installed better-auth
// here goes through `AuthManager.handleRequest` on the then-installed better-auth
// 1.7.1, where the vendor's own middleware really runs, and reads a status and
// a code off a real `Response`.
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,7 +46,7 @@
*
* The two optional fields are NOT symmetric. `organizationId` defaults to the
* caller's active organization when omitted; `teamId` has no such fallback —
* omit it and the member simply joins no team. Measured on the installed
* omit it and the member simply joins no team. Measured on the then-installed
* better-auth 1.7.1 (`dist/plugins/organization/routes/crud-members.mjs`,
* inside `addMember`'s handler):
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@
//
// • the pure normalizer, where the three narrowings are cheap to enumerate
// (empty body only · 401/403 only · `/admin/` only), and
// • the real `AuthManager.handleRequest` seam on the installed better-auth
// • the real `AuthManager.handleRequest` seam on the then-installed better-auth
// 1.7.1, where the vendor's `adminMiddleware` actually produces the refusal.
//
// ⛔ A refusal-only suite is not enough here and the lane has paid for that
Expand DownExpand Up@@ -146,7 +146,7 @@ describe('#10349 — the normalizer fills in a bodyless vendor /admin/ refusal',
});

// ───────────────────────────────────────────────────────────────────────────
// The real seam, on the installed better-auth 1.7.1
// The real seam, on the then-installed better-auth 1.7.1
// ───────────────────────────────────────────────────────────────────────────

const SECRET = 'test-secret-at-least-32-chars-long!!';
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@
* `adminMiddleware`, which is `getAuthoritativeSessionFromCtx(ctx)` followed by
* `APIError.fromStatus('UNAUTHORIZED')` — no body argument at all.
*
* Measured on the installed better-auth 1.7.1, anonymous, through
* Measured on the then-installed better-auth 1.7.1, anonymous, through
* `AuthManager.handleRequest`, ten vendor-lane routes answered:
*
* POST /admin/impersonate-user -> 401 content-type: application/json body ''
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/dispatcher-error-vocabulary.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -382,7 +382,7 @@ export const UNREGISTERED_CODE_SITES: readonly UnregisteredCodeSite[] = [
door: 'none',
verdict: 'foreign-vocabulary',
why:
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the installed vendor at " +
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the then-installed vendor at " +
'`dist/plugins/admin/error-codes`, spelled there exactly as it is here — and read at runtime ' +
'off `plugin.$ERROR_CODES`, never retyped. The caller-side refusal, raised ' +
"`APIError.from('FORBIDDEN', notAllowed)` inside a better-auth endpoint, so it leaves as the " +
Expand Down
1 change: 1 addition & 0 deletions scripts/check-release-page-status.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -721,6 +721,7 @@ function selfTest() {
'## Landed since 17.0.0-rc.0',
'',
'The 17.0.0 train is currently published as `17.0.0-rc.6`; nothing reaches the `latest` tag.',
// vendor-stamp:fixture — synthetic page text; this version is assertion input, not a claim about this tree
'This paragraph is history, not status, and pins `better-auth 1.7.0-rc.2`.',
].join('\n');
const bq = statusBlockquote(page);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
20 changes: 20 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -2130,6 +2130,26 @@ jobs:
- name: CLI test child-env guard
run: pnpm check:cli-test-child-env

# #13940. A version stamp about a PINNED VENDOR is an attestation: it says a
# behaviour was MEASURED against the version it names. The same drift has
# been swept by hand four times (#10073: 29 stamps, #10188: +3, #11362: +2,
# #13940: 95) and the population GREW each round, so the sweep was trailing
# a producer rather than converging.
# The producer is a sentence that fuses a permanent fact with a LIVE READING
# of a value that moves — "the installed better-auth 1.7.1" is not stale
# after a bump, it is false. This gate reds on exactly that shape and on
# nothing else. It deliberately does NOT hold stamps equal to the resolved
# pin: restamping without re-measuring manufactures an attestation nobody
# made, which is strictly worse than a stale one, and 130 of today's stamps
# are anchored historical facts that must never be rewritten. Drift is
# measured and REPORTED, never enforced.
# Static text scan over ~5,600 sources; no build, no install (the pin comes
# from pnpm-lock.yaml). Runs its own --self-test first: the gate computes
# its own population, so a scan that stopped matching would print a
# confident green over a tree it never read.
- name: Vendor version stamps
run: pnpm check:vendor-version-stamps

# #12016. A CLI command id spelled as a string literal OUTSIDE the CLI
# package has nothing tying it to the command file it names: rename the
# command, update the CLI and the docs, and the engine's own drift warning
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,7 @@
"check:org-identifier": "node scripts/check-org-identifier.mjs --self-test && node scripts/check-org-identifier.mjs",
"check:runner-env-posture": "node scripts/check-runner-env-posture.mjs --self-test && node scripts/check-runner-env-posture.mjs",
"check:cli-test-child-env": "node scripts/check-cli-test-child-env.mjs --self-test && node scripts/check-cli-test-child-env.mjs",
"check:vendor-version-stamps": "node scripts/check-vendor-version-stamps.mjs --self-test && node scripts/check-vendor-version-stamps.mjs",
"check:cli-command-ids": "node scripts/check-cli-command-ids.mjs --self-test && node scripts/check-cli-command-ids.mjs",
"check:authz-resolver": "node scripts/check-single-authz-resolver.mjs --self-test && node scripts/check-single-authz-resolver.mjs",
"check:slot-lookup": "node scripts/check-slot-lookup-ratchet.mjs --self-test && node scripts/check-slot-lookup-ratchet.mjs",
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/commands/init.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,8 +147,10 @@ export const SCAFFOLD_BUILT_DEPENDENCIES = ['better-sqlite3', 'esbuild'];
* finding.
*
* ⚠️ These four do NOT retire with the SCIM rc pin, even though one of them
* names scim. Stable `@better-auth/scim@1.7.1` still peers
* `@better-auth/utils@0.4.2`, so this skew outlives that pin. They retire
* names scim. Stable `@better-auth/scim@1.7.2` still peers
* `@better-auth/utils@0.4.2` EXACTLY — re-read off the installed package
* manifest on 2026-08-31 for #13940, unchanged from 1.7.1 — so this skew
* outlives that pin. They retire
* when the four packages accept 0.5.0 upstream, or when
* `SCAFFOLD_PNPM_RANGE` reaches `>=10.31` — pnpm 10.31 changed peer
* resolution so that all four land on 0.4.2 by themselves. Measured on the
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ export const SysMember = ObjectSchema.create({
// The two optional fields are NOT symmetric. `organizationId` defaults
// to the caller's active organization when omitted; `teamId` has no such
// fallback — omit it and the member simply joins no team. Measured on the
// installed better-auth 1.7.1
// then-installed better-auth 1.7.1
// (`dist/plugins/organization/routes/crud-members.mjs`, `addMember`):
// `ctx.body.organizationId || session?.session.activeOrganizationId`
// against `"teamId" in ctx.body ? ctx.body.teamId : void 0`, and no
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@
* ## The measurement this shape rests on
*
* Whether better-auth 1.7.1 permits overriding a path another plugin registers
* was explicitly UNMEASURED when this was ruled. Measured now, on the installed
* was explicitly UNMEASURED when this was ruled. Measured on the then-installed
* `better-auth@1.7.1`:
*
* - `checkEndpointConflicts` (`dist/api/index.mjs`) builds its registry by
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,7 @@
* ## The defect, and where it is minted
*
* Not here: the wrong answer comes out of the pinned vendor. better-auth
* `1.7.1` (the installed line, re-read for this card),
* `1.7.1` (the line installed at the time, re-read for this card),
* `dist/plugins/admin/routes.mjs`, `revokeUserSession` runs, after its
* `session: ["revoke"]` permission check:
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@
*
* The bridges re-dispatch into `@better-auth/sso` "so all of its gates run"
* (register-sso-provider.ts). The premise this file pins is that the vendor's
* gates are NOT a platform-admin gate. Measured on the INSTALLED
* gates are NOT a platform-admin gate. Measured on the THEN-INSTALLED
* `@better-auth/sso` 1.7.1 (dist/index.mjs, `registerSSOProvider`):
*
* • `/sso/register` requires a session — and nothing more — when the body
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/adopt-membership.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,7 +70,7 @@
* membership**, so a `create` naming an (org, user) pair that already exists is
* not a second membership — it is that membership.
*
* Blast radius, measured against the installed better-auth (re-measured on
* Blast radius, measured against the then-installed better-auth (re-measured on
* 1.7.1) rather than assumed — accept-invitation is the ONLY `member` create
* that can reach an existing pair:
*
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/auth-manager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3815,7 +3815,7 @@ export class AuthManager {
*
* The scan existed to make the match case-insensitive, on the stated belief
* that invitation addresses are stored as the inviter typed them while
* better-auth lowercases the registrant's. Measured on the installed
* better-auth lowercases the registrant's. Measured on the then-installed
* better-auth 1.7.1, that belief is wrong on BOTH halves — the vendor
* normalizes each side before this gate ever sees it:
*
Expand Down
4 changes: 2 additions & 2 deletions packages/plugins/plugin-auth/src/auth-plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2143,7 +2143,7 @@ export class AuthPlugin implements Plugin {
// #9653 — ADR-0068 D4: the platform-admin gate runs HERE, before the
// bridge delegates. Registering an identity provider is a platform-
// operator action, and the delegated authorization is NOT a substitute:
// measured on the installed @better-auth/sso 1.7.1, the vendor's
// measured on the then-installed @better-auth/sso 1.7.1, the vendor's
// /sso/register admits ANY authenticated user when no organizationId is
// supplied (the org-admin check is inside `if (ctx.body.organizationId)`),
// and the auth-manager before-hook that narrows this admits org
Expand DownExpand Up@@ -2352,7 +2352,7 @@ export class AuthPlugin implements Plugin {
// reached by any AUTHENTICATED caller, admin or not, before either
// authorization layer had run.
//
// MEASURED on the installed better-auth 1.7.1 before this mount existed,
// MEASURED on the then-installed better-auth 1.7.1 before this mount existed,
// one authenticated non-admin, two targets: naming the break-glass
// holder answered `409 LAST_LOCAL_CREDENTIAL` while naming an ordinary
// user answered `403 YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS`. Two different
Expand Down
8 changes: 5 additions & 3 deletions packages/plugins/plugin-auth/src/auth-schema-config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -953,9 +953,11 @@ export const buildOidcProviderPluginSchema = buildOauthProviderPluginSchema;

// NOTE: there is intentionally no scim mapping constant here, and no
// `buildScimPluginSchema()`. `@better-auth/scim` hardcodes its models and exposes
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.1`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all;
// measured 2026-08-19 on the rc, re-measured 2026-08-27 on stable, #3653), so
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.2`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all — its
// six members are connections, authentication, managedConnections, identity,
// projection, compatibility; measured 2026-08-19 on the rc, re-measured
// 2026-08-27 on stable 1.7.1, and again 2026-08-31 on 1.7.2 for #13940), so
// there is nowhere to hand one. This is no longer true
// of `@better-auth/sso@1.7.1`, which now accepts one (#8224) — for scim, and for
// scim alone, the ADAPTER layer is the only available route.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
// synthetic `ctx`. That is the right shape for the guard's own predicate, and
// it is structurally blind to the defect this file pins: hook ORDER relative to
// endpoint middleware does not exist in a synthetic call. So every assertion
// here goes through `AuthManager.handleRequest` on the installed better-auth
// here goes through `AuthManager.handleRequest` on the then-installed better-auth
// 1.7.1, where the vendor's own middleware really runs, and reads a status and
// a code off a real `Response`.
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,7 +46,7 @@
*
* The two optional fields are NOT symmetric. `organizationId` defaults to the
* caller's active organization when omitted; `teamId` has no such fallback —
* omit it and the member simply joins no team. Measured on the installed
* omit it and the member simply joins no team. Measured on the then-installed
* better-auth 1.7.1 (`dist/plugins/organization/routes/crud-members.mjs`,
* inside `addMember`'s handler):
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@
//
// • the pure normalizer, where the three narrowings are cheap to enumerate
// (empty body only · 401/403 only · `/admin/` only), and
// • the real `AuthManager.handleRequest` seam on the installed better-auth
// • the real `AuthManager.handleRequest` seam on the then-installed better-auth
// 1.7.1, where the vendor's `adminMiddleware` actually produces the refusal.
//
// ⛔ A refusal-only suite is not enough here and the lane has paid for that
Expand DownExpand Up@@ -146,7 +146,7 @@ describe('#10349 — the normalizer fills in a bodyless vendor /admin/ refusal',
});

// ───────────────────────────────────────────────────────────────────────────
// The real seam, on the installed better-auth 1.7.1
// The real seam, on the then-installed better-auth 1.7.1
// ───────────────────────────────────────────────────────────────────────────

const SECRET = 'test-secret-at-least-32-chars-long!!';
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@
* `adminMiddleware`, which is `getAuthoritativeSessionFromCtx(ctx)` followed by
* `APIError.fromStatus('UNAUTHORIZED')` — no body argument at all.
*
* Measured on the installed better-auth 1.7.1, anonymous, through
* Measured on the then-installed better-auth 1.7.1, anonymous, through
* `AuthManager.handleRequest`, ten vendor-lane routes answered:
*
* POST /admin/impersonate-user -> 401 content-type: application/json body ''
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/dispatcher-error-vocabulary.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -382,7 +382,7 @@ export const UNREGISTERED_CODE_SITES: readonly UnregisteredCodeSite[] = [
door: 'none',
verdict: 'foreign-vocabulary',
why:
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the installed vendor at " +
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the then-installed vendor at " +
'`dist/plugins/admin/error-codes`, spelled there exactly as it is here — and read at runtime ' +
'off `plugin.$ERROR_CODES`, never retyped. The caller-side refusal, raised ' +
"`APIError.from('FORBIDDEN', notAllowed)` inside a better-auth endpoint, so it leaves as the " +
Expand Down
1 change: 1 addition & 0 deletions scripts/check-release-page-status.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -721,6 +721,7 @@ function selfTest() {
'## Landed since 17.0.0-rc.0',
'',
'The 17.0.0 train is currently published as `17.0.0-rc.6`; nothing reaches the `latest` tag.',
// vendor-stamp:fixture — synthetic page text; this version is assertion input, not a claim about this tree
'This paragraph is history, not status, and pins `better-auth 1.7.0-rc.2`.',
].join('\n');
const bq = statusBlockquote(page);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
20 changes: 20 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -2130,6 +2130,26 @@ jobs:
- name: CLI test child-env guard
run: pnpm check:cli-test-child-env

# #13940. A version stamp about a PINNED VENDOR is an attestation: it says a
# behaviour was MEASURED against the version it names. The same drift has
# been swept by hand four times (#10073: 29 stamps, #10188: +3, #11362: +2,
# #13940: 95) and the population GREW each round, so the sweep was trailing
# a producer rather than converging.
# The producer is a sentence that fuses a permanent fact with a LIVE READING
# of a value that moves — "the installed better-auth 1.7.1" is not stale
# after a bump, it is false. This gate reds on exactly that shape and on
# nothing else. It deliberately does NOT hold stamps equal to the resolved
# pin: restamping without re-measuring manufactures an attestation nobody
# made, which is strictly worse than a stale one, and 130 of today's stamps
# are anchored historical facts that must never be rewritten. Drift is
# measured and REPORTED, never enforced.
# Static text scan over ~5,600 sources; no build, no install (the pin comes
# from pnpm-lock.yaml). Runs its own --self-test first: the gate computes
# its own population, so a scan that stopped matching would print a
# confident green over a tree it never read.
- name: Vendor version stamps
run: pnpm check:vendor-version-stamps

# #12016. A CLI command id spelled as a string literal OUTSIDE the CLI
# package has nothing tying it to the command file it names: rename the
# command, update the CLI and the docs, and the engine's own drift warning
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,7 @@
"check:org-identifier": "node scripts/check-org-identifier.mjs --self-test && node scripts/check-org-identifier.mjs",
"check:runner-env-posture": "node scripts/check-runner-env-posture.mjs --self-test && node scripts/check-runner-env-posture.mjs",
"check:cli-test-child-env": "node scripts/check-cli-test-child-env.mjs --self-test && node scripts/check-cli-test-child-env.mjs",
"check:vendor-version-stamps": "node scripts/check-vendor-version-stamps.mjs --self-test && node scripts/check-vendor-version-stamps.mjs",
"check:cli-command-ids": "node scripts/check-cli-command-ids.mjs --self-test && node scripts/check-cli-command-ids.mjs",
"check:authz-resolver": "node scripts/check-single-authz-resolver.mjs --self-test && node scripts/check-single-authz-resolver.mjs",
"check:slot-lookup": "node scripts/check-slot-lookup-ratchet.mjs --self-test && node scripts/check-slot-lookup-ratchet.mjs",
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/commands/init.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,8 +147,10 @@ export const SCAFFOLD_BUILT_DEPENDENCIES = ['better-sqlite3', 'esbuild'];
* finding.
*
* ⚠️ These four do NOT retire with the SCIM rc pin, even though one of them
* names scim. Stable `@better-auth/scim@1.7.1` still peers
* `@better-auth/utils@0.4.2`, so this skew outlives that pin. They retire
* names scim. Stable `@better-auth/scim@1.7.2` still peers
* `@better-auth/utils@0.4.2` EXACTLY — re-read off the installed package
* manifest on 2026-08-31 for #13940, unchanged from 1.7.1 — so this skew
* outlives that pin. They retire
* when the four packages accept 0.5.0 upstream, or when
* `SCAFFOLD_PNPM_RANGE` reaches `>=10.31` — pnpm 10.31 changed peer
* resolution so that all four land on 0.4.2 by themselves. Measured on the
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ export const SysMember = ObjectSchema.create({
// The two optional fields are NOT symmetric. `organizationId` defaults
// to the caller's active organization when omitted; `teamId` has no such
// fallback — omit it and the member simply joins no team. Measured on the
// installed better-auth 1.7.1
// then-installed better-auth 1.7.1
// (`dist/plugins/organization/routes/crud-members.mjs`, `addMember`):
// `ctx.body.organizationId || session?.session.activeOrganizationId`
// against `"teamId" in ctx.body ? ctx.body.teamId : void 0`, and no
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@
* ## The measurement this shape rests on
*
* Whether better-auth 1.7.1 permits overriding a path another plugin registers
* was explicitly UNMEASURED when this was ruled. Measured now, on the installed
* was explicitly UNMEASURED when this was ruled. Measured on the then-installed
* `better-auth@1.7.1`:
*
* - `checkEndpointConflicts` (`dist/api/index.mjs`) builds its registry by
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,7 @@
* ## The defect, and where it is minted
*
* Not here: the wrong answer comes out of the pinned vendor. better-auth
* `1.7.1` (the installed line, re-read for this card),
* `1.7.1` (the line installed at the time, re-read for this card),
* `dist/plugins/admin/routes.mjs`, `revokeUserSession` runs, after its
* `session: ["revoke"]` permission check:
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@
*
* The bridges re-dispatch into `@better-auth/sso` "so all of its gates run"
* (register-sso-provider.ts). The premise this file pins is that the vendor's
* gates are NOT a platform-admin gate. Measured on the INSTALLED
* gates are NOT a platform-admin gate. Measured on the THEN-INSTALLED
* `@better-auth/sso` 1.7.1 (dist/index.mjs, `registerSSOProvider`):
*
* • `/sso/register` requires a session — and nothing more — when the body
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/adopt-membership.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,7 +70,7 @@
* membership**, so a `create` naming an (org, user) pair that already exists is
* not a second membership — it is that membership.
*
* Blast radius, measured against the installed better-auth (re-measured on
* Blast radius, measured against the then-installed better-auth (re-measured on
* 1.7.1) rather than assumed — accept-invitation is the ONLY `member` create
* that can reach an existing pair:
*
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/auth-manager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3815,7 +3815,7 @@ export class AuthManager {
*
* The scan existed to make the match case-insensitive, on the stated belief
* that invitation addresses are stored as the inviter typed them while
* better-auth lowercases the registrant's. Measured on the installed
* better-auth lowercases the registrant's. Measured on the then-installed
* better-auth 1.7.1, that belief is wrong on BOTH halves — the vendor
* normalizes each side before this gate ever sees it:
*
Expand Down
4 changes: 2 additions & 2 deletions packages/plugins/plugin-auth/src/auth-plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2143,7 +2143,7 @@ export class AuthPlugin implements Plugin {
// #9653 — ADR-0068 D4: the platform-admin gate runs HERE, before the
// bridge delegates. Registering an identity provider is a platform-
// operator action, and the delegated authorization is NOT a substitute:
// measured on the installed @better-auth/sso 1.7.1, the vendor's
// measured on the then-installed @better-auth/sso 1.7.1, the vendor's
// /sso/register admits ANY authenticated user when no organizationId is
// supplied (the org-admin check is inside `if (ctx.body.organizationId)`),
// and the auth-manager before-hook that narrows this admits org
Expand DownExpand Up@@ -2352,7 +2352,7 @@ export class AuthPlugin implements Plugin {
// reached by any AUTHENTICATED caller, admin or not, before either
// authorization layer had run.
//
// MEASURED on the installed better-auth 1.7.1 before this mount existed,
// MEASURED on the then-installed better-auth 1.7.1 before this mount existed,
// one authenticated non-admin, two targets: naming the break-glass
// holder answered `409 LAST_LOCAL_CREDENTIAL` while naming an ordinary
// user answered `403 YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS`. Two different
Expand Down
8 changes: 5 additions & 3 deletions packages/plugins/plugin-auth/src/auth-schema-config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -953,9 +953,11 @@ export const buildOidcProviderPluginSchema = buildOauthProviderPluginSchema;

// NOTE: there is intentionally no scim mapping constant here, and no
// `buildScimPluginSchema()`. `@better-auth/scim` hardcodes its models and exposes
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.1`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all;
// measured 2026-08-19 on the rc, re-measured 2026-08-27 on stable, #3653), so
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.2`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all — its
// six members are connections, authentication, managedConnections, identity,
// projection, compatibility; measured 2026-08-19 on the rc, re-measured
// 2026-08-27 on stable 1.7.1, and again 2026-08-31 on 1.7.2 for #13940), so
// there is nowhere to hand one. This is no longer true
// of `@better-auth/sso@1.7.1`, which now accepts one (#8224) — for scim, and for
// scim alone, the ADAPTER layer is the only available route.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
// synthetic `ctx`. That is the right shape for the guard's own predicate, and
// it is structurally blind to the defect this file pins: hook ORDER relative to
// endpoint middleware does not exist in a synthetic call. So every assertion
// here goes through `AuthManager.handleRequest` on the installed better-auth
// here goes through `AuthManager.handleRequest` on the then-installed better-auth
// 1.7.1, where the vendor's own middleware really runs, and reads a status and
// a code off a real `Response`.
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,7 +46,7 @@
*
* The two optional fields are NOT symmetric. `organizationId` defaults to the
* caller's active organization when omitted; `teamId` has no such fallback —
* omit it and the member simply joins no team. Measured on the installed
* omit it and the member simply joins no team. Measured on the then-installed
* better-auth 1.7.1 (`dist/plugins/organization/routes/crud-members.mjs`,
* inside `addMember`'s handler):
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@
//
// • the pure normalizer, where the three narrowings are cheap to enumerate
// (empty body only · 401/403 only · `/admin/` only), and
// • the real `AuthManager.handleRequest` seam on the installed better-auth
// • the real `AuthManager.handleRequest` seam on the then-installed better-auth
// 1.7.1, where the vendor's `adminMiddleware` actually produces the refusal.
//
// ⛔ A refusal-only suite is not enough here and the lane has paid for that
Expand DownExpand Up@@ -146,7 +146,7 @@ describe('#10349 — the normalizer fills in a bodyless vendor /admin/ refusal',
});

// ───────────────────────────────────────────────────────────────────────────
// The real seam, on the installed better-auth 1.7.1
// The real seam, on the then-installed better-auth 1.7.1
// ───────────────────────────────────────────────────────────────────────────

const SECRET = 'test-secret-at-least-32-chars-long!!';
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@
* `adminMiddleware`, which is `getAuthoritativeSessionFromCtx(ctx)` followed by
* `APIError.fromStatus('UNAUTHORIZED')` — no body argument at all.
*
* Measured on the installed better-auth 1.7.1, anonymous, through
* Measured on the then-installed better-auth 1.7.1, anonymous, through
* `AuthManager.handleRequest`, ten vendor-lane routes answered:
*
* POST /admin/impersonate-user -> 401 content-type: application/json body ''
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/dispatcher-error-vocabulary.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -382,7 +382,7 @@ export const UNREGISTERED_CODE_SITES: readonly UnregisteredCodeSite[] = [
door: 'none',
verdict: 'foreign-vocabulary',
why:
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the installed vendor at " +
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the then-installed vendor at " +
'`dist/plugins/admin/error-codes`, spelled there exactly as it is here — and read at runtime ' +
'off `plugin.$ERROR_CODES`, never retyped. The caller-side refusal, raised ' +
"`APIError.from('FORBIDDEN', notAllowed)` inside a better-auth endpoint, so it leaves as the " +
Expand Down
1 change: 1 addition & 0 deletions scripts/check-release-page-status.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -721,6 +721,7 @@ function selfTest() {
'## Landed since 17.0.0-rc.0',
'',
'The 17.0.0 train is currently published as `17.0.0-rc.6`; nothing reaches the `latest` tag.',
// vendor-stamp:fixture — synthetic page text; this version is assertion input, not a claim about this tree
'This paragraph is history, not status, and pins `better-auth 1.7.0-rc.2`.',
].join('\n');
const bq = statusBlockquote(page);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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
20 changes: 20 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -2130,6 +2130,26 @@ jobs:
- name: CLI test child-env guard
run: pnpm check:cli-test-child-env

# #13940. A version stamp about a PINNED VENDOR is an attestation: it says a
# behaviour was MEASURED against the version it names. The same drift has
# been swept by hand four times (#10073: 29 stamps, #10188: +3, #11362: +2,
# #13940: 95) and the population GREW each round, so the sweep was trailing
# a producer rather than converging.
# The producer is a sentence that fuses a permanent fact with a LIVE READING
# of a value that moves — "the installed better-auth 1.7.1" is not stale
# after a bump, it is false. This gate reds on exactly that shape and on
# nothing else. It deliberately does NOT hold stamps equal to the resolved
# pin: restamping without re-measuring manufactures an attestation nobody
# made, which is strictly worse than a stale one, and 130 of today's stamps
# are anchored historical facts that must never be rewritten. Drift is
# measured and REPORTED, never enforced.
# Static text scan over ~5,600 sources; no build, no install (the pin comes
# from pnpm-lock.yaml). Runs its own --self-test first: the gate computes
# its own population, so a scan that stopped matching would print a
# confident green over a tree it never read.
- name: Vendor version stamps
run: pnpm check:vendor-version-stamps

# #12016. A CLI command id spelled as a string literal OUTSIDE the CLI
# package has nothing tying it to the command file it names: rename the
# command, update the CLI and the docs, and the engine's own drift warning
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,6 +85,7 @@
"check:org-identifier": "node scripts/check-org-identifier.mjs --self-test && node scripts/check-org-identifier.mjs",
"check:runner-env-posture": "node scripts/check-runner-env-posture.mjs --self-test && node scripts/check-runner-env-posture.mjs",
"check:cli-test-child-env": "node scripts/check-cli-test-child-env.mjs --self-test && node scripts/check-cli-test-child-env.mjs",
"check:vendor-version-stamps": "node scripts/check-vendor-version-stamps.mjs --self-test && node scripts/check-vendor-version-stamps.mjs",
"check:cli-command-ids": "node scripts/check-cli-command-ids.mjs --self-test && node scripts/check-cli-command-ids.mjs",
"check:authz-resolver": "node scripts/check-single-authz-resolver.mjs --self-test && node scripts/check-single-authz-resolver.mjs",
"check:slot-lookup": "node scripts/check-slot-lookup-ratchet.mjs --self-test && node scripts/check-slot-lookup-ratchet.mjs",
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/commands/init.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,8 +147,10 @@ export const SCAFFOLD_BUILT_DEPENDENCIES = ['better-sqlite3', 'esbuild'];
* finding.
*
* ⚠️ These four do NOT retire with the SCIM rc pin, even though one of them
* names scim. Stable `@better-auth/scim@1.7.1` still peers
* `@better-auth/utils@0.4.2`, so this skew outlives that pin. They retire
* names scim. Stable `@better-auth/scim@1.7.2` still peers
* `@better-auth/utils@0.4.2` EXACTLY — re-read off the installed package
* manifest on 2026-08-31 for #13940, unchanged from 1.7.1 — so this skew
* outlives that pin. They retire
* when the four packages accept 0.5.0 upstream, or when
* `SCAFFOLD_PNPM_RANGE` reaches `>=10.31` — pnpm 10.31 changed peer
* resolution so that all four land on 0.4.2 by themselves. Measured on the
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,7 +88,7 @@ export const SysMember = ObjectSchema.create({
// The two optional fields are NOT symmetric. `organizationId` defaults
// to the caller's active organization when omitted; `teamId` has no such
// fallback — omit it and the member simply joins no team. Measured on the
// installed better-auth 1.7.1
// then-installed better-auth 1.7.1
// (`dist/plugins/organization/routes/crud-members.mjs`, `addMember`):
// `ctx.body.organizationId || session?.session.activeOrganizationId`
// against `"teamId" in ctx.body ? ctx.body.teamId : void 0`, and no
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@
* ## The measurement this shape rests on
*
* Whether better-auth 1.7.1 permits overriding a path another plugin registers
* was explicitly UNMEASURED when this was ruled. Measured now, on the installed
* was explicitly UNMEASURED when this was ruled. Measured on the then-installed
* `better-auth@1.7.1`:
*
* - `checkEndpointConflicts` (`dist/api/index.mjs`) builds its registry by
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,7 @@
* ## The defect, and where it is minted
*
* Not here: the wrong answer comes out of the pinned vendor. better-auth
* `1.7.1` (the installed line, re-read for this card),
* `1.7.1` (the line installed at the time, re-read for this card),
* `dist/plugins/admin/routes.mjs`, `revokeUserSession` runs, after its
* `session: ["revoke"]` permission check:
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@
*
* The bridges re-dispatch into `@better-auth/sso` "so all of its gates run"
* (register-sso-provider.ts). The premise this file pins is that the vendor's
* gates are NOT a platform-admin gate. Measured on the INSTALLED
* gates are NOT a platform-admin gate. Measured on the THEN-INSTALLED
* `@better-auth/sso` 1.7.1 (dist/index.mjs, `registerSSOProvider`):
*
* • `/sso/register` requires a session — and nothing more — when the body
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/adopt-membership.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,7 +70,7 @@
* membership**, so a `create` naming an (org, user) pair that already exists is
* not a second membership — it is that membership.
*
* Blast radius, measured against the installed better-auth (re-measured on
* Blast radius, measured against the then-installed better-auth (re-measured on
* 1.7.1) rather than assumed — accept-invitation is the ONLY `member` create
* that can reach an existing pair:
*
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-auth/src/auth-manager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3815,7 +3815,7 @@ export class AuthManager {
*
* The scan existed to make the match case-insensitive, on the stated belief
* that invitation addresses are stored as the inviter typed them while
* better-auth lowercases the registrant's. Measured on the installed
* better-auth lowercases the registrant's. Measured on the then-installed
* better-auth 1.7.1, that belief is wrong on BOTH halves — the vendor
* normalizes each side before this gate ever sees it:
*
Expand Down
4 changes: 2 additions & 2 deletions packages/plugins/plugin-auth/src/auth-plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2143,7 +2143,7 @@ export class AuthPlugin implements Plugin {
// #9653 — ADR-0068 D4: the platform-admin gate runs HERE, before the
// bridge delegates. Registering an identity provider is a platform-
// operator action, and the delegated authorization is NOT a substitute:
// measured on the installed @better-auth/sso 1.7.1, the vendor's
// measured on the then-installed @better-auth/sso 1.7.1, the vendor's
// /sso/register admits ANY authenticated user when no organizationId is
// supplied (the org-admin check is inside `if (ctx.body.organizationId)`),
// and the auth-manager before-hook that narrows this admits org
Expand DownExpand Up@@ -2352,7 +2352,7 @@ export class AuthPlugin implements Plugin {
// reached by any AUTHENTICATED caller, admin or not, before either
// authorization layer had run.
//
// MEASURED on the installed better-auth 1.7.1 before this mount existed,
// MEASURED on the then-installed better-auth 1.7.1 before this mount existed,
// one authenticated non-admin, two targets: naming the break-glass
// holder answered `409 LAST_LOCAL_CREDENTIAL` while naming an ordinary
// user answered `403 YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS`. Two different
Expand Down
8 changes: 5 additions & 3 deletions packages/plugins/plugin-auth/src/auth-schema-config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -953,9 +953,11 @@ export const buildOidcProviderPluginSchema = buildOauthProviderPluginSchema;

// NOTE: there is intentionally no scim mapping constant here, and no
// `buildScimPluginSchema()`. `@better-auth/scim` hardcodes its models and exposes
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.1`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all;
// measured 2026-08-19 on the rc, re-measured 2026-08-27 on stable, #3653), so
// NO `schema` option — still true of the installed stable `@better-auth/scim@1.7.2`
// (`SCIMOptions` declares no `schema` / `modelName` / `fields` member at all — its
// six members are connections, authentication, managedConnections, identity,
// projection, compatibility; measured 2026-08-19 on the rc, re-measured
// 2026-08-27 on stable 1.7.1, and again 2026-08-31 on 1.7.2 for #13940), so
// there is nowhere to hand one. This is no longer true
// of `@better-auth/sso@1.7.1`, which now accepts one (#8224) — for scim, and for
// scim alone, the ADAPTER layer is the only available route.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
// synthetic `ctx`. That is the right shape for the guard's own predicate, and
// it is structurally blind to the defect this file pins: hook ORDER relative to
// endpoint middleware does not exist in a synthetic call. So every assertion
// here goes through `AuthManager.handleRequest` on the installed better-auth
// here goes through `AuthManager.handleRequest` on the then-installed better-auth
// 1.7.1, where the vendor's own middleware really runs, and reads a status and
// a code off a real `Response`.
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,7 +46,7 @@
*
* The two optional fields are NOT symmetric. `organizationId` defaults to the
* caller's active organization when omitted; `teamId` has no such fallback —
* omit it and the member simply joins no team. Measured on the installed
* omit it and the member simply joins no team. Measured on the then-installed
* better-auth 1.7.1 (`dist/plugins/organization/routes/crud-members.mjs`,
* inside `addMember`'s handler):
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@
//
// • the pure normalizer, where the three narrowings are cheap to enumerate
// (empty body only · 401/403 only · `/admin/` only), and
// • the real `AuthManager.handleRequest` seam on the installed better-auth
// • the real `AuthManager.handleRequest` seam on the then-installed better-auth
// 1.7.1, where the vendor's `adminMiddleware` actually produces the refusal.
//
// ⛔ A refusal-only suite is not enough here and the lane has paid for that
Expand DownExpand Up@@ -146,7 +146,7 @@ describe('#10349 — the normalizer fills in a bodyless vendor /admin/ refusal',
});

// ───────────────────────────────────────────────────────────────────────────
// The real seam, on the installed better-auth 1.7.1
// The real seam, on the then-installed better-auth 1.7.1
// ───────────────────────────────────────────────────────────────────────────

const SECRET = 'test-secret-at-least-32-chars-long!!';
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@
* `adminMiddleware`, which is `getAuthoritativeSessionFromCtx(ctx)` followed by
* `APIError.fromStatus('UNAUTHORIZED')` — no body argument at all.
*
* Measured on the installed better-auth 1.7.1, anonymous, through
* Measured on the then-installed better-auth 1.7.1, anonymous, through
* `AuthManager.handleRequest`, ten vendor-lane routes answered:
*
* POST /admin/impersonate-user -> 401 content-type: application/json body ''
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/dispatcher-error-vocabulary.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -382,7 +382,7 @@ export const UNREGISTERED_CODE_SITES: readonly UnregisteredCodeSite[] = [
door: 'none',
verdict: 'foreign-vocabulary',
why:
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the installed vendor at " +
"better-auth 1.7.1's own admin-plugin vocabulary — verified in the then-installed vendor at " +
'`dist/plugins/admin/error-codes`, spelled there exactly as it is here — and read at runtime ' +
'off `plugin.$ERROR_CODES`, never retyped. The caller-side refusal, raised ' +
"`APIError.from('FORBIDDEN', notAllowed)` inside a better-auth endpoint, so it leaves as the " +
Expand Down
1 change: 1 addition & 0 deletions scripts/check-release-page-status.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -721,6 +721,7 @@ function selfTest() {
'## Landed since 17.0.0-rc.0',
'',
'The 17.0.0 train is currently published as `17.0.0-rc.6`; nothing reaches the `latest` tag.',
// vendor-stamp:fixture — synthetic page text; this version is assertion input, not a claim about this tree
'This paragraph is history, not status, and pins `better-auth 1.7.0-rc.2`.',
].join('\n');
const bq = statusBlockquote(page);
Expand Down
Loading
Loading