Skip to content

refactor(plugin-auth): move the buildPluginList body into auth-manager-plugin-list.ts (#25224 cut 16) - #25624

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-lg3wrj
Oct 24, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-lg3wrj

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

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:

    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, refactor(plugin-auth): move AuthManager's request routing into auth-manager-request-routing.ts (#25224 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 refactor(plugin-security): move the everyone anchor, the explanation, the sharing verdicts and the authored-row write verdict out of security-plugin.ts (#25223 cut 10) #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 10b8250ad5 a2413e05ce 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 10b8250ad5, 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: feat(plugin-auth): a new organization takes its creator's default currency (#25495) #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

🤖 Generated with Claude Code

https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr

…r-plugin-list.ts

The body of AuthManager.buildPluginList() moves unedited into the module
function buildPluginList, re-indented one level: the reset of the
degraded-feature record and the JWT signing verdict, the enabled table
with its env and config precedence and the SCIM coherence re-check, and
every plugin in the same order (bearer, the organization factory's
arrow, 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). The
method keeps its place, docblock, signature and return type; it now
builds one host of the 22 AuthManager members the body reads (18
methods as late-bound closures, config and degradedFeatures as getters,
jwtSigningFailureReported and orgRolesMap as get/set pairs because the
body writes them) and returns buildPluginList.call(host). The async
moved from the method to the function.

The organization factory's arrow, which cut 15 left in the body, moves
with it and builds its host from this host, so it reaches the same
members and the same field as before.

readBooleanEnv and consentOrganizationOf, the two top-level declarations
of auth-manager.ts the body reads that the file did not export, are
exported in place and imported by value with the five that already
were; src/index.ts names its auth-manager.ts exports one by one, so
nothing new is published. Nine imported names leave auth-manager.ts
with the body.

Readers that follow it: the two plugin-factory scans read the module,
and the population comments in auth-email-locale.test.ts name it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-auth, touching 64 documentable anchor(s).

14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/flows.mdx (via PLATFORM_ADMIN (literal, a string literal in a comment in buildPluginList; a string literal in buildPluginList))
  • content/docs/deployment/environment-variables.mdx (via OS_AUTH_TWO_FACTOR (literal, a string literal in buildPluginList), PLATFORM_ADMIN (literal, a string literal in a comment in buildPluginList; a string literal in buildPluginList))
  • content/docs/deployment/tenancy-modes.mdx (via PLATFORM_ADMIN (literal, a string literal in a comment in buildPluginList; a string literal in buildPluginList))
  • content/docs/kernel/contracts/auth-service.mdx (via AuthManager (symbol, a top-level type), getMcpResourceUrl (symbol, a field of interface PluginListHost), getMcpResourceUrl (literal, a string literal in getMcpResourceUrl))
  • content/docs/kernel/runtime-services/sharing-service.mdx (via platform_admin (literal, a string literal in a comment in buildPluginList))
  • content/docs/kernel/services-checklist.mdx (via AuthManager (symbol, a top-level type))
  • content/docs/permissions/authentication.mdx (via AuthManager (symbol, a top-level type), PLATFORM_ADMIN (literal, a string literal in a comment in buildPluginList; a string literal in buildPluginList), deviceAuthorization (literal, a string literal in buildPluginList), magicLink (literal, a string literal in buildPluginList), phoneNumber (literal, a string literal in buildPluginList), platform_admin (literal, a string literal in a comment in buildPluginList))
  • content/docs/permissions/authorization.mdx (via PLATFORM_ADMIN (literal, a string literal in a comment in buildPluginList; a string literal in buildPluginList), platform_admin (literal, a string literal in a comment in buildPluginList))
  • content/docs/permissions/permission-metadata.mdx (via PLATFORM_ADMIN (literal, a string literal in a comment in buildPluginList; a string literal in buildPluginList), platform_admin (literal, a string literal in a comment in buildPluginList))
  • content/docs/permissions/permission-sets.mdx (via PLATFORM_ADMIN (literal, a string literal in a comment in buildPluginList; a string literal in buildPluginList))
  • content/docs/permissions/positions.mdx (via platform_admin (literal, a string literal in a comment in buildPluginList))
  • content/docs/permissions/sharing-rules.mdx (via platform_admin (literal, a string literal in a comment in buildPluginList))
  • content/docs/permissions/sso.mdx (via OS_SSO_DOMAIN_VERIFICATION (literal, a string literal in buildPluginList), deviceAuthorization (literal, a string literal in buildPluginList), oidcProvider (literal, a string literal in buildPluginList))
  • content/docs/protocol/objectui/actions.mdx (via oidcProvider (literal, a string literal in buildPluginList), phoneNumber (literal, a string literal in buildPluginList))

⛔ 7 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v14.mdx (via platform_admin (literal, a string literal in a comment in buildPluginList))
  • content/docs/releases/v15.mdx (via PLATFORM_ADMIN (literal, a string literal in a comment in buildPluginList; a string literal in buildPluginList), phoneNumber (literal, a string literal in buildPluginList))
  • content/docs/releases/v16.mdx (via PLATFORM_ADMIN (literal, a string literal in a comment in buildPluginList; a string literal in buildPluginList))
  • content/docs/releases/v17/17-1.mdx (via PLATFORM_ADMIN (literal, a string literal in a comment in buildPluginList; a string literal in buildPluginList))
  • content/docs/releases/v17/17-4.mdx (via platform_admin (literal, a string literal in a comment in buildPluginList))
  • content/docs/releases/v17/17-5.mdx (via PLATFORM_ADMIN (literal, a string literal in a comment in buildPluginList; a string literal in buildPluginList), platform_admin (literal, a string literal in a comment in buildPluginList))
  • content/docs/releases/v18/18-0.mdx (via platform_admin (literal, a string literal in a comment in buildPluginList))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 21 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 481ac6211641ccb7292508ed423f02e432322e4a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from d4a6dc12b9f3b6afef96e941ad6bd50d6513b6ca — the merge of head a2413e05ce692c7e8127d7aa891859c3c49ec611 into base 481ac6211641ccb7292508ed423f02e432322e4a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d4a6dc12b9f3b6afef96e941ad6bd50d6513b6ca && git checkout d4a6dc12b9f3b6afef96e941ad6bd50d6513b6ca
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 481ac6211641ccb7292508ed423f02e432322e4a a2413e05ce692c7e8127d7aa891859c3c49ec611 && git checkout -B drift-repro 481ac6211641ccb7292508ed423f02e432322e4a && git merge --no-ff a2413e05ce692c7e8127d7aa891859c3c49ec611

node scripts/docs-audit/affected-docs.mjs --json 481ac6211641ccb7292508ed423f02e432322e4a

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 481ac6211641ccb7292508ed423f02e432322e4a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · 2026-10-24T11:29Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25224 cut 16, the series' last cut (claim 6383249105, os-dev-report 6383617104) · reviewed head a2413e05ce · merge base 10b8250ad5

  • Shape: the whole buildPluginList body (791 lines) moves unedited into auth-manager-plugin-list.ts, in the [maintainer] refactor(rest): split packages/rest/src/rest-server.ts (13,701 lines, 70 commits in 30 days) along its register*Endpoints seams, one move-only slice per PR, /meta after #22877 stage 2 #23050 method shape.
  • Readers outside the moved text, migrated: the two factory-scan lists (better-auth-schema-parity and managed-extension-fields) and the comments in auth-email-locale.test.ts. The seat read the diff: only file names and comment text change.
  • .d.ts, ruling 6348891160 item 1, re-run by the seat in its own worktree (turbo run build --filter=@objectstack/plugin-auth at both ends):
    • index.d.ts is raw 1b3a48357d137155… at both ends, and index.d.mts is raw b5e8dc982b634f0a… at both ends. The raw diff is 0 lines.
    • The normalised hashes match as well: cee7303ccdfe7279… and 562d0a4ce656f97a….
    • Positive control: head line 347, private options;, is made public, which moves the hashes to raw 51a55969ad8afac6… and normalised 66bcd34e7ea6963c….
    • CJS exports: 159.
  • Gates re-run by the seat, all exit 0 at base and at head:
    • check-adr-symbol-anchors: 2,384 anchors.
    • check-tenant-audit-census: 238.
    • check-adr-anchors.
    • check-dispatcher-error-vocabulary.
    • At head, check-system-context-census (149) and check-platform-checklist.
  • Disclosure 1, decision letters: accepted, no anchor entry owed.
    • The seat reads 1872 letters at the merge base and 1853 at head, matching the report.
    • The 19 citations leave with the body for the unanchored new module. They are ADR-0024/0057/0068/0069/0071/0090/0131/0134/0135; the seat counted them in the new module.
    • The auth-manager.ts entry anchors ADR-0093 D2 and ADR-0108, and the moved text cites neither. The ADR-0108 roles-map write stays in auth-manager-organization-plugin.ts, under its cut 15 entry. Only the orgRolesMap get/set relay moves.
    • So no entry is owed (ruling 6353073966 item 4, the cut 14 precedent).
  • Disclosure 2, three comments now name a file one level up: accepted as is. They are at publish-smoke.sh:1131, on the deliverPhoneOtp line in dispatcher-error-vocabulary.ts (the file field the gate reads is unchanged), and in the file header of auth-manager-organization-plugin.ts. This follows the cut 14 precedent. A comment-only refresh can ride a later PR and does not hold this cut.
  • Tests and gates (report):
    • Token identity holds at 793 lines / 2,096 tokens, and 500 of 500 identifiers bind the same way, each check with a negative control.
    • The suite passes 3,626 tests with 10 skipped at both ends.
    • 61 of 61 derived gates exit 0.
  • CI: 34 success and 12 expected skips, none failed or pending (46 runs).
  • Mergeable: git merge-tree onto today's main is clean. The one open PR that touches auth-manager.ts, feat(plugin-auth): a new organization takes its creator's default currency (#25495) #25621, edits a different range, and its merge-tree is clean too.
  • Body: the seat adds the attribution header and the os-sales reviewer through the relay with this verdict. The footers are present.
  • Landing: the PR is not governed (check-governed-merges --pr 25624: 1,774 lines). The seat readies it and arms auto-merge through the relay. After it lands, the card thread posts the series' final report, and the seat closes [maintainer] refactor(plugin-auth): split src/auth-manager.ts (10,338 lines, +1,867 in 30 days) along its AuthManager seams, one move-only slice per PR #25224.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot requested a review from os-sales October 24, 2026 11:30
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 24, 2026 11:30
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 24, 2026 11:30
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 24, 2026
Merged via the queue into main with commit abdc7f2 Oct 24, 2026
54 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-lg3wrj branch October 24, 2026 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants