Skip to content

Commit abdc7f2

Browse files
refactor(plugin-auth): move the buildPluginList body into auth-manager-plugin-list.ts (#25224 cut 16) (#25624)
<!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuMjbPDrsigsyqoKsV4WZQMz)_ Part of #25224 Clause-②: no Metadata-protocol: none Ruling-ref: 6348891160, 6364006498 The basis for the `Clause-②: no` line: this is a move plus reader migrations. No accept set, route, refusal, code or answer changes, and the published surface is measured unchanged: - `@objectstack/plugin-auth`'s `dist/index.d.ts` and `dist/index.d.mts` are raw byte-identical to base (0 diff lines, so the union-order normalisation has nothing to do). The positive control is the one ruling `6348891160` asks for. - Its ESM and CJS entries export the same 159 names at base and at head. The two declarations this cut exports in place inside the package, `buildPluginList` and `PluginListHost` are not among them. The `.d.ts` bar is ruling `6348891160` item 1 (on #25222, covering this card). Ruling `6364006498` is Sales's ruling A on this card. This is the eighth cut it applies to. Cut 16 of the `auth-manager.ts` split, and the series' last: **the rest of `buildPluginList`, which is its whole body**. It is the second of the two `buildPluginList` cuts the seat set at 02:10Z (the `organization` factory in cut 15, #25594, then the rest of the method). The series ends after this cut; once it lands, the final report goes on #25224. This follows the amended claim `6383249105`, which amends `6379187398` on the file cut 15 left (#25594, landed note `6382750527`). The shape is the #23050 method move cuts 1 to 11 and 14 used, with cut 10's get/set pair (#25508) for the two fields the body writes. Branch: `claude/project-thread-lg3wrj`, the harness-pinned branch for this session, restarted from `origin/main` after #25594 merged. It names no issue, so the card relation is this body's first line. `auth-manager.ts` goes from 6,556 to 5,789 lines. The cut is +961 / −813 over 5 files: 1,774 changed lines, under the 3,000-line line. Two of the five files are path readers outside the move and one carries two comments, so `check-governed-merges.mjs` ran on the trial file list before the cut: NOT governed (0 of 5 paths hit the register), and `--branch HEAD` agrees. One commit: `a2413e05ce` `refactor(plugin-auth): move the buildPluginList body into auth-manager-plugin-list.ts`. ## The move | moved | base lines | proof lines | tokens | members it reads | |---|---|---:|---:|---:| | the body of `buildPluginList` | `:1876`–`:2666` | 793 | 2,096 | 18 methods, 4 fields | The lines are base `10b8250ad5`. The 793 proof lines are the body with its braces (the method's `{` on `:1875` to its `}` on `:2667`). - **What it is:** `buildPluginList` (`:1875`, `private async`, annotated `Promise<any[]>`) builds better-auth's plugin list. `createAuthInstance` calls it once per build, as `await this.buildPluginList()`, through its host's closure since cut 14. - **What the body does, in order** (all of it moves, with its comments): the reset of the degraded-feature record and of the JWT signing verdict; the `enabled` table (the env and config precedence it documents and the SCIM coherence re-check); and every plugin in the same order: `bearer`, the `organization` factory's arrow (whose body cut 15 moved), `twoFactor`, breached-password rejection, `admin`, `phoneNumber`, `magicLink`, the generic OAuth providers, the OIDC provider with its `jwt` plugin, `sso`, `scim`, device authorization and the custom session, then `return plugins`. - **Where it goes:** a new module, `packages/plugins/plugin-auth/src/auth-manager-plugin-list.ts` (913 lines). The body becomes the body of `export async function buildPluginList(this: PluginListHost): Promise<any[]>`, re-indented two spaces (one level) and otherwise unedited. The module header gives the reasoning, in the form the fifteen earlier `auth-manager-*.ts` modules use. - **What stays:** `buildPluginList` keeps its place, its docblock, its `private` modifier, its (empty) parameter list and its return annotation. Its body is now: ```ts private buildPluginList(): Promise<any[]> { // [#25224 cut 16] The body moved to `auth-manager-plugin-list.ts` (see `PluginListHost`). const readConfig = () => this.config; const readDegradedFeatures = () => this.degradedFeatures; const readJwtSigningFailureReported = () => this.jwtSigningFailureReported; const writeJwtSigningFailureReported = (v: AuthManager['jwtSigningFailureReported']) => { this.jwtSigningFailureReported = v; }; const readOrgRolesMap = () => this.orgRolesMap; const writeOrgRolesMap = (v: AuthManager['orgRolesMap']) => { this.orgRolesMap = v; }; const host: PluginListHost = { addOptionalPlugin: (...a) => this.addOptionalPlugin(...a), buildJwtPlugin: (...a) => this.buildJwtPlugin(...a), computeAuthGate: (...a) => this.computeAuthGate(...a), get config() { return readConfig(); }, get degradedFeatures() { return readDegradedFeatures(); }, // … one closure per method, alphabetical, 18 in all, and: get jwtSigningFailureReported() { return readJwtSigningFailureReported(); }, set jwtSigningFailureReported(v) { writeJwtSigningFailureReported(v); }, get orgRolesMap() { return readOrgRolesMap(); }, set orgRolesMap(v) { writeOrgRolesMap(v); }, }; return buildPluginList.call(host); } ``` ## The seat's bars **Body.** - The body is the same TypeScript token sequence at base and in the module function: 793 lines with its braces, 2,096 tokens. Every one of the 793 lines is the base line minus exactly two spaces. - **`async` moves from the method to the function.** The stub is not `async`, and it returns `buildPluginList.call(host)`, the function's own promise, so `createAuthInstance`'s `await this.buildPluginList()` awaits the same promise the body settles. The readers, the writers, the host literal and the `.call` cannot throw before the promise exists, and a throw inside the body is still a rejection of it. - **Same parameters, same annotation.** The base method, the stub and the function take no parameter and carry the same return annotation, `Promise<any[]>`. The body uses no `arguments`, `new.target` or `super` (0 of each), no bare `this`, and no `this` under a non-arrow function (its only non-arrow function nodes are the getter and the setter in the `organization` arrow's host literal, which read and write through their closures and name no `this`). **Receiver.** - The body's `this` was the `AuthManager` instance. It is now the host. - `PluginListHost` names exactly the 22 members the body reads, no more, each typed as the class member (`AuthManager['x']`). - 18 are methods, so each goes as a late-bound closure, `(...a) => this.x(...a)`: `addOptionalPlugin`, `buildJwtPlugin`, `computeAuthGate`, `deliverPhoneOtp`, `emailLocaleArg`, `enforceSessionControls`, `getAppName`, `getConsolePageUrl`, `getDataEngine`, `getEmailService`, `getMcpResourceUrl`, `isPlatformAdminUserId`, `multiOrgPostureEffective`, `reconcileScimUserLifecycle` (which stays in the class, per ruling A), `resolveAuthSecret`, `resolveMembershipRole`, `resolveTwoFactorAccountLockout` and `storedRecipientLocale`. The method runs on the manager and is looked up when the body calls it, so a spy or double set on the manager is still the one the body reaches, including from the callbacks it hands better-auth (the phone OTP senders, the magic-link sender and the custom session among them), which run long after the function returns. Every method read is a direct call. - Four are fields: - `config`, which the class reassigns after construction (`setRuntimeBaseUrl` and `applyConfigPatch`), and `degradedFeatures`, as every field this series carries goes, go as getters over reader closures, `readonly` on the interface. The body reads `config` 7 times and writes neither. - **`jwtSigningFailureReported` and `orgRolesMap` go as get/set pairs**, the shape the seat set for a moved body that writes a field (cut 10, #25508; cut 6's `reportJwtSigningFailure` stub carries the first the same way, and cut 15 carried `orgRolesMap` so). The body resets `jwtSigningFailureReported` once (`:1881`, `= false`) and never reads it. It reads and writes `orgRolesMap` only through the `organization` arrow's reader and writer (`:1978`, `:1979`). Each setter writes the manager's own field through its writer closure, so `reportJwtSigningFailure`, which reads the flag back, and the actor guards, which read the roles map back, read the same field, written at the same moment, as before. Neither is `readonly` on the interface. - **Function-valued reads, disclosed** (the way #25516 disclosed `resolveKernelService`): the body calls `this.degradedFeatures.clear()` (`:1876`) on the object the getter returns, the manager's own `Map`, at the same moment as before. Its `this.config` reads are data (`plugins`, `phoneOtp`, `oidcProviders`, `dataEngine`); none is called. - The body reads no `AuthManager.X`, so the module imports the class as a type only. Its one read of the class is a type position (`AuthManager['orgRolesMap']`, `:1979`, in the `organization` arrow's writer). **The `organization` arrow.** It moves with the body, unedited, and builds its host from `this`, which is now this host: each of its eight closures calls this host's closure, which calls the manager's method; its `config` getter reads this host's getter; its `orgRolesMap` reader and writer read and write this host's pair, which read and write the manager's field. So it reaches the same members, and the same field, at the same moment as before (cut 14's precedent for a moved body holding an earlier cut's stub). Its import (`buildOrganizationPlugin` and `OrganizationPluginHost`) moves to the new module with it. **Two top-level declarations, exported in place, and five already exported.** - The body reads seven top-level declarations of `auth-manager.ts`: `readBooleanEnv` (`:347`, 4 reads) and `consentOrganizationOf` (`:1453`, once), which were not exported, and `resolveScimEnabled`, `assertScimAdminCoherence`, `resolveOidcProviderEnabled`, `resolveDcrEnabled` and `OAUTH_ORGANIZATION_CLAIM`, which were. - Per ruling `6364006498` (A), the two get `export` in place, in this cut, the first whose body reads them, and the module imports all seven by value from `./auth-manager.js`, with the class as a type. The binding proof checks that each head declaration is the base one with `export ` in front and nothing else changed, and that the base and head reads resolve to it. - `src/index.ts` names its `auth-manager.ts` exports one by one and names neither, so nothing new is published. **Host lifetime.** The host is built each time `buildPluginList` is called, which `createAuthInstance` does once per build, as every stub in this series builds its host per call. Nothing the body reads is captured early: the closures look each method up on the manager at call time, and the getters and the get/set pairs read and write the fields when the body, or a callback it handed better-auth, does. **Readers.** The decision letters are read on the merge base and on the head below: 19 leave the anchored surface, disclosed. Tenant census and symbol-anchor output are byte-identical at both ends, and the full plugin-auth suite passes at both ends. The three readers whose lists or comments name where the moved lines live are migrated in this commit. ## Imports and evaluation order Nine imported names leave `auth-manager.ts`, because the body was their last reader there: - `resolveUserAuthzGrants` (the `@objectstack/core` block keeps its other names); - the whole `./admin-impersonate-endpoint.js` block (`applyPlatformAdminImpersonation`); - the whole `./auth-schema-config.js` block (`buildTwoFactorPluginSchema`, `buildOauthProviderPluginSchema`, `buildDeviceAuthorizationPluginSchema`, `buildAdminPluginSchema`, `buildPhoneNumberPluginSchema`); - cut 15's `./auth-manager-organization-plugin.js` line (`buildOrganizationPlugin`, `type OrganizationPluginHost`) with its three comment lines. One comment and one line replace cut 15's: `import { buildPluginList, type PluginListHost } from './auth-manager-plugin-list.js'`. `AuthPluginConfig` (a type), `MCP_OAUTH_SCOPES` and `isPlaceholderEmail` have other readers, so both files import them. esbuild drops a type-only import, so a module whose last value import leaves `auth-manager.ts` can now be evaluated later. The order was measured rather than argued: - **The ES modules, at run time.** A loader hook logged each ES module as its body ran while `dist/index.mjs` loaded, at base and at head. 171 modules evaluate at both, in the same order. - **The CommonJS entry.** A `Module._load` log for `dist/index.js` gives 127 first loads at both, in the same order. - **plugin-auth's own modules.** A walk of the bundle's top level and its eager `init_*` calls finds three that run later at both entries, whose last value import from `auth-manager.ts` left with the body: `auth-schema-config.ts`, `admin-impersonate-endpoint.ts` and `impersonation-bearer-rotation.ts` (which `admin-impersonate-endpoint.ts` imports). At the CommonJS entry `platform-admin-gate.ts` also moves, by two places: it ran two modules before `principal-session-mint.ts` at base and runs right after it at head; `principal-session-mint.ts` imports `judgeHeldSession` from it and reads it inside a function only. The top levels of all five are imports, types, constants and function declarations (`principal-session-mint.ts` adds one `extends Error` class), with no load-time call, apart from `admin-impersonate-endpoint.ts`'s `VENDOR_ROLE_SCALAR_ADMIN_PATHS`, a `new Set` of string literals (`:191`). No importer reads any of their bindings at load time: all 41 references in the 16 files that import one of these five or the new module (`auth-manager.ts` among them) are inside functions, and `index.ts` re-exports `platform-admin-gate.ts` and `auth-schema-config.ts` with `export *`, which reads nothing. - The new module's own top level is imports, one interface and one function declaration. Its value import of `auth-manager.ts`, for the seven declarations, and `auth-manager.ts`'s import of it form a cycle with nothing read at load time, as cut 14's and cut 15's do. ## The decision-letter layer: 19 drop, disclosed `check-adr-anchors`, measured on the merge base and on the head. The gate exits 0 at both, and every citation still resolves. | point | anchored files | citations | files | decision letters | |---|---:|---:|---:|---:| | merge base `10b8250ad5` | 85 | 52,467 | 6,589 | 1,872 | | head `a2413e05ce` | 85 | 52,467 | 6,590 | 1,853 | - **No anchor entry; 19 letters leave the anchored surface, disclosed.** By the gate's count the body carries 19 decision-letter citations, at these sites: ADR-0057 D4 (`:2532`, `:2569`, `:2594`), ADR-0068 D1/D2 (`:2526`, `:2555`), ADR-0068 D2 (`:2032`, `:2595`, `:2603`), ADR-0069 D1 (`:2007`), D2 (`:2001`), D3 (`:2046`) and D4 (`:2643`), ADR-0090 D3 (`:2526`, `:2555`) and D5 (`:2533`), ADR-0131 D7 (`:2196`) and ADR-0135 D6 (`:1913`, `:2404`, `:2435`). They leave `auth-manager.ts`, which is anchored, for a module that is not, so the count drops by 19 while the gate stays green and every citation still resolves. The `auth-manager.ts` entry carries two paragraphs: ADR-0093 D2 (the membership reconciler, which stays) and ADR-0108 (the organization roles map, whose mechanism cut 15 moved to `auth-manager-organization-plugin.ts`, anchored by its own entry). Neither describes this body, so no entry is owed, per the seat's ruling after cut 5 (an entry would need new invariant text, which a move-only cut must not add; the drop is recorded in the ACCEPT), as cut 14 recorded its 8. ## Path readers Three readers outside the move follow it, in this commit: - **`better-auth-schema-parity.test.ts` and `managed-extension-fields.test.ts`**: each one's `authManagerPluginFactories()` scans a list of files for `const { X } = await import('better-auth/plugins…' | '@better-auth/…')`, and both lists gain `auth-manager-plugin-list.ts` (`:324` and `:634`). The body holds 13 such imports, all of `auth-manager.ts`'s (13 → 0). At base the scan finds 16 factories; at head without the new entry it would find 3 (`organization` from the cut 15 module and the two in `auth-manager-actor-guards.ts`), missing 13, which each test's stale-entry check fails on; with it the scan finds the same 16 as base. - **`auth-email-locale.test.ts`**: the two population comments (the docblock at `:863` and the in-test note at `:1130`, head numbering) that name the files the five `sendTemplate` sites live in now name `auth-manager-plugin-list.ts` (the body of `buildPluginList`) among them; the magic-link send moved with the body, and `auth-manager.ts` keeps one. No assertion, count or pattern changes. Read, and not edited: - **`api-error-code.pin.test.ts`**: the body carries no `new APIError(` site, so `authManagerFiles` is not owed the new module. Its sum stays 30, as at base. - **`scripts/check-dispatcher-error-vocabulary.mjs`**: the body carries no code-stamping site (no `code:` and no `new APIError(`), and the gate's only per-file `plugin-auth` key is `liveMemberFile` (`auth-manager-before-hook.ts`), which the cut does not touch. It exits 0 at both, 56 sites found and 56 classified at both, and its output is identical apart from its scanned-file count (3,444 → 3,445). Its `--self-test` runs inside the derived `pnpm check:dispatcher-error-vocabulary`. - **The tenant-audit census.** No census site moves: the body's engine calls are reads. `check-tenant-audit-census` exits 0 at both with no `--write`, with byte-identical output. Its `--json` document has 238 sites at both and 0 per-site differences with line numbers ignored. 2 sites are in `auth-manager.ts` at both, 0 are in the new module, and 0 are unresolved or unledgered at both. Only `scannedSources` (762 → 763) and `measuredAt` move, so neither census document is edited. - **`scripts/measure-durability-swallow-family.mjs`** (a measurement, not a gate): 77 members and 84 quiet rows at both, identical with line numbers ignored (one member, in `observeFirstMemberships`, which stays, moves 767 lines up with the cut's net: `:3876` → `:3109`). Every statistic is the same apart from `files` (3,494 → 3,495): `tryStatements` 3,647 and `droppedNotAwaited` 230 at both. - **`check-auth-mount-ledger.mjs`** reads only the `basePath` declaration in `auth-manager.ts`, which stays. **`auth-manager-base-path.test.ts`** reads `auth-manager.ts` for code lines carrying `'/api/v1/auth'`; the body has none. - **`rate-limit-storage-isolation.test.ts`** walks the import graph from `rate-limit-storage.ts`, which reaches neither file. It passes at head. - **`check-system-context-census.mjs`** tracks reads of `ExecutionContext.isSystem`; the body has none in code. **`check-cross-package-test-inputs.mjs`** names `auth-manager.ts` in a self-test fixture only. - **Every symbol anchor into the file.** Before the cut, every `auth-manager.ts#symbol` anchor in the tracked tree outside changelogs was mapped with `symbolSegmentResolution` from `scripts/symbol-anchors.mjs`. There are 29, and all 29 are `declaration`-class at base, at base with the body blanked, and at head. Seven of their symbols occur as a token on a line of the body, and each one's declaration stays where it was: `resolveScimEnabled` (ADR-0134 `:90`, `:127`), `reconcileScimUserLifecycle` (`:158`, `:263`; the method, which stays per ruling A), `assertScimAdminCoherence` (`:171`), `AuthManager` (ADR-0135 `:104`, the class), `resolveOidcProviderEnabled` (the ai checklist area `:737`, identity-auth `:1166`), `computeAuthGate` (identity-auth `:1718`, the method) and `twoFactor` (identity-auth `:1429`, which resolves to `getPublicConfig`'s `features.twoFactor`, `:5301` at base, which stays). Each resolves to its declaration at all three readings. - **Prose that now points one file early, disclosed, not edited** (cut 14's precedent: a comment that reads no file is not a path reader): `scripts/publish-smoke.sh:1131` names `auth-manager.ts` for the `admin: pluginConfig.admin ?? scimEffective` line, which now sits in the module; the `why` of the `deliverPhoneOtp` row in `packages/runtime/src/dispatcher-error-vocabulary.ts` (`:1359`) says its two callers are `sendOTP` hooks in `auth-manager.ts`, which now sit in the module (the row's `file` field, which the gate reads, names `auth-manager-email-sms-delivery.ts` and is unchanged); and `auth-manager-organization-plugin.ts`'s header describes the `organization` call in `buildPluginList`, which is now the module function's. If the seat wants any of them re-pointed, it is a comment edit in this PR. ## Proof Builds and suites ran through `os-verify-lock`, and every exit code was captured before any pipe. Base `10b8250ad5`. A full `turbo run build` ran first. Then `plugin-auth` was built and its suite run at base, and built and run again at head, on the same dependency dists. **1. The moved text.** - **Token identity:** the base method body and the module function body are the same TypeScript token sequence (scanner tokens, kind and text, trivia skipped, each template literal re-scanned at its substitutions as the parser does): 793 lines and 2,096 tokens, each line the base line minus two spaces. - **The stub's shape**, checked by the same run: the stub is not `async`, takes no parameter and keeps the docblock, `private` and `Promise<any[]>`; it opens with the six reader and writer closures (one reader per field, one writer per get/set field), then one host literal whose keys are exactly `PluginListHost`'s, alphabetical, whose 18 method values are each `(...a) => this.<key>(...a)`, whose `config` and `degradedFeatures` are getters over their readers and whose `jwtSigningFailureReported` and `orgRolesMap` are getters over their readers and setters over their writers, then `return buildPluginList.call(host)`, and nothing else. The interface types every member `AuthManager['x']`, and exactly the two getter-only fields are `readonly`. The run also checks that the body writes exactly the two get/set fields. - **Binding identity:** a second proof walks all 500 identifiers of the body at base and in the module, in order, and resolves each with the TypeScript checker (the module's `./auth-manager.js` resolved to the head file, where the in-place exports live). All 500 resolve the same way at both ends: - 338 to a declaration inside the body, the same declaring identifier by position; - 48 to receiver members (`AuthManager` at base, `PluginListHost` at head); - 23 reads of 20 imported names, with the same specifier and imported name, the eight `./auth-manager.js` names among them; - 10 to the same declaration in `auth-manager.ts`: six `AuthManagerOptions` members read through `this.config` (`dataEngine`, `oidcProviders`, and `phoneOtp` with its `allowedAttempts`, `expiresIn` and `otpLength`); - 81 to no declaration: globals and property names on untyped values. None resolves to an unexported top-level declaration of `auth-manager.ts` or to a top-level declaration of the module. A read of the class itself would have to be in a type position: the one there is (`:1979`) is. - **Negative controls:** each proof reads the module text, and each control changes it, or the proof's input, in memory only; the file is untouched. - Token proof: `plugins.push(bearer());` changed to `plugins.push(bearer?.());` fails the token and line checks (2 failures), and the run exits 1. Declaring the get/set fields without `jwtSigningFailureReported` fails the write check and the stub-shape checks (4 failures), and the run exits 1. - Binding proof: the module's `admin-impersonate-endpoint` import pointed at another specifier gives 1 difference, and the run exits 1. The `placeholder-email` import shadowed by a module-level `const` gives 1 difference and reports the module top-level read, and exits 1. Run without being told about the in-place exports, it reports the base reads of the two unexported declarations (5 reads), and exits 1. - **An independent reading:** `git diff --color-moved=plain --color-moved-ws=allow-indentation-change 10b8250 a2413e0` over the two source files marks 799 of the 808 deleted lines and 820 of the 954 added lines as moved. - The 9 unmoved deletions are 3 import lines (`resolveUserAuthzGrants`, `applyPlatformAdminImpersonation` and the `./admin-impersonate-endpoint.js` closer), cut 15's three import comment lines, the first lines of the two declarations (now with `export`) and the method's own line (now without `async`). - The other added lines are the module header, its import lines, the host interface and its docblock, the function signature and docblock, the stub's comment, readers, writers, host literal and `return`, the two `export` lines, and the new import comment and line. Of the 820, 14 are stub lines in `auth-manager.ts` that git pairs with the `organization` arrow's own host lines, which have the same text. **2. `.d.ts` and exports (ruling `6348891160` items 1 and 2).** | file | base and head | raw diff lines | |---|---|---:| | `dist/index.d.ts` raw | `1b3a48357d137155de33104277667a922aab62df73b418b03894c56faaacd0ba` | 0 | | `dist/index.d.ts` normalised | `cee7303ccdfe72794efd588c85b628419d4253d730b5a0c5a3f1cdb8dddb4443` | | | `dist/index.d.mts` raw | `b5e8dc982b634f0a5108b1b533626a68767f2da5a56f72dba801059b149e2872` | 0 | | `dist/index.d.mts` normalised | `562d0a4ce656f97aeb937541482cdf531b61d7c75a005918784bf0a52d032dfc` | | - The normaliser, verbatim (the ruling's one-liner): `node -e 'const s=require("fs").readFileSync(process.argv[1],"utf8");process.stdout.write(s.replace(/"[^"\n]*"(?:\s*\|\s*"[^"\n]*")+/g,r=>r.split(/\s*\|\s*/).sort().join(" | ")))' dist/index.d.ts | sha256sum`. - The raw files are byte-identical, so no union-order swap occurs: items 1(b) and 1(d) hold with nothing to account for. They are also the bytes cut 15 measured, since a private method's body is not in the declarations. - **Positive control:** the head `dist/index.d.ts` with one private member made public (`private buildPluginList;` → `buildPluginList;`, line 1829, a one-line change) hashes raw `f8ff1470cbc34cd34a441c67b8763c5adde86fa408a4bcecf1590c60d30bce57` and normalised `fe48a6a7f320e626e941a422a27571c28fbdc47a73bbbdaa0d5048d7307cae55`. Both differ from the readings above. - **Export lists:** ESM `dist/index.mjs` and CJS `dist/index.js` each have 159 names at base and at head, the same list (the sha256 of the sorted names is `3abb126f5babb701…` on all four). Neither in-place export, `buildPluginList` nor `PluginListHost` is among them. `dist/index.d.ts` names neither in-place export nor `PluginListHost`; its 7 `buildPluginList` lines are `private buildPluginList;` and docblock text, the same 7 at both ends. - **The bundle:** `tsup` bundles the new module into the root entry (`splitting: false`). The dist has the same 18 files at base and at head, plus the two `.build-input-hash` stamps the build writes from its inputs. Only `index.js`, `index.mjs` and their maps differ. **3. Tests and lint.** - `pnpm exec vitest run --maxWorkers=3` in `packages/plugins/plugin-auth`: base `Test Files 191 passed (191)`, `Tests 3626 passed | 10 skipped (3636)`, exit 0. Head: the same counts, exit 0. - `pnpm --filter @objectstack/plugin-auth typecheck` (both `tsc` projects and `check:test-typecheck`): exit 0. `pnpm --filter @objectstack/runtime typecheck`: exit 0. - `eslint` over the 5 changed files: exit 0. **4. The censuses, the measurement and the anchors, at base and at head.** - The tenant-audit census, as above: byte-identical gate output, 238 sites at both, 0 per-site differences line-blind, 0 unresolved and 0 unledgered at both. - The durability measurement, as above, with its one moved statistic traced. - These checkers were run by hand at base and at head, because `dispatch-gates` does not derive the ADR one. All six exit 0 at both. Five print byte-identical output at both (`cmp` clean): - `check-adr-symbol-anchors`: 2,384 anchors across 142 records, 466 symbol (440 declaration, 26 literal); - `check-scripts-symbol-anchors`: 4,070 anchors; - `check-spec-docblock-symbol-anchors`: 5,076 anchors; - `check-system-context-census`: 149 elevation read sites; - `check-platform-checklist`: 16 areas, 329 items. The sixth, `check-adr-anchors`, differs only in the file count and the decision letters, as in the table above. **5. Gates.** - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 61 commands for the 5 paths. All 61 ran at head after the full build, and all 61 exited 0. - `--ran` over the record with each exit code: 61 derived, 61 run, 0 NOT-MEASURED, a derived zero. `--tier`: no path-derived mandate. - `check-governed-merges.mjs`: on the trial file list before the cut and `--branch HEAD` after the commit, both read NOT governed (0 of 5 paths hit the register), 1,774 changed lines. - **Docs drift** (`node scripts/docs-audit/affected-docs.mjs --json 10b8250`, exit 0): 21 pages, all name matches of names the moved code carries (the `PLATFORM_ADMIN`, `platform_admin`, `OS_AUTH_TWO_FACTOR` and `OS_SSO_DOMAIN_VERIFICATION` literals, the plugin names `phoneNumber`, `magicLink`, `oidcProvider` and `deviceAuthorization`, and `AuthManager` and `getMcpResourceUrl`). One names `auth-manager.ts`: `content/docs/kernel/services-checklist.mdx` (`:314`), for the identity provider service as a whole, which still holds. The code that reads those names is unchanged, so no doc edit is owed. 7 of the 21 are release notes, release-owned and not edited. - **Serial constraints** (in the claim, re-read at 10:40Z before this PR): 12 PRs are open. One touches `auth-manager.ts`: #25621, whose only hunk there is `:1320`–`:1331` (the docblock of `acceptLanguageHeader` and an `export` in place on it), a region this cut does not touch and the body does not read; `git merge-tree` merges this head with its head cleanly. None of the others touches `plugin-auth`, the new module's path, a reader edited above or `scripts/adr-anchors/`. `origin/main` has moved 5 merges past this base, to `f7b0298968`; none touches `plugin-auth`, `scripts/adr-anchors/` or a reader named above, and `git merge-tree` merges this head with it cleanly. ## Changeset None: `skip-changeset`, per ruling `6348891160` item 4. The published declarations and export lists are unchanged, and the bundle's bytes move only because code moved inside it. --- _Generated by [Claude Code](https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9d9a06e commit abdc7f2

5 files changed

Lines changed: 961 additions & 813 deletions

File tree

‎packages/plugins/plugin-auth/src/auth-email-locale.test.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -860,7 +860,8 @@ describe("#14641 — an invitation reads the INVITEE's own sys_user.locale", ()
860860
* implied: the auth mail surface is the five `sendTemplate` call sites in
861861
* `auth-manager.ts`, `auth-manager-auth-instance.ts` (the body of
862862
* `createAuthInstance`), `auth-manager-organization-plugin.ts` (the body of
863-
* the organization plugin's factory) and `auth-manager-email-sms-delivery.ts`
863+
* the organization plugin's factory), `auth-manager-plugin-list.ts` (the body
864+
* of `buildPluginList`) and `auth-manager-email-sms-delivery.ts`
864865
* (the body of the change-email notice) — `IEmailService` declares exactly two delivering
865866
* members (`send`, `sendTemplate`), plugin-auth's only `.send(` calls are the
866867
* SMS service's, and no other file under `plugin-auth/src` calls either.
@@ -1125,8 +1126,9 @@ describe('#15106 — the CLASS claim: all five auth sends read the recipient col
11251126

11261127
it('every one of the five sends is written in the stored language of its own recipient', async () => {
11271128
// ⚠️ POPULATION: the five `sendTemplate` sites in `auth-manager.ts`,
1128-
// `auth-manager-auth-instance.ts`, `auth-manager-organization-plugin.ts` and
1129-
// `auth-manager-email-sms-delivery.ts` — the same five `driveAllFive` drives and `TEMPLATES` names, and the whole of
1129+
// `auth-manager-auth-instance.ts`, `auth-manager-organization-plugin.ts`,
1130+
// `auth-manager-plugin-list.ts` and `auth-manager-email-sms-delivery.ts` — the
1131+
// same five `driveAllFive` drives and `TEMPLATES` names, and the whole of
11301132
// plugin-auth's mail surface (see this section's header). This is the pin
11311133
// that closes the class; the per-send pins above only cover one member.
11321134
//

0 commit comments

Comments
 (0)