Skip to content

refactor(plugin-auth): move the organization plugin factory into auth-manager-organization-plugin.ts (#25224 cut 15) - #25594

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 and one anchor entry. 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 eight declarations this cut exports in place inside the package, buildOrganizationPlugin and OrganizationPluginHost 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 seventh cut it applies to.

Cut 15 of the auth-manager.ts split: the organization plugin's factory inside buildPluginList. It is the first of the two buildPluginList cuts the seat set at 02:10Z (this factory, then the rest of the method); the series ends after cut 16, and #25224 stays open for it.

This follows the amended claim 6379187398, which amends 6377759458 on the file cut 14 left (#25566, landed note 6378561924). The shape is the closure move cuts 12 and 13 used (a factory arrow's body into a module function called on a host), with cut 10's get/set pair (#25508) for the one field the body writes.

Branch: claude/project-thread-lg3wrj, the harness-pinned branch for this session, restarted from origin/main after #25566 merged. It names no issue, so the card relation is this body's first line.

auth-manager.ts goes from 7,122 to 6,556 lines. The cut is +734 / −608 over 7 files: 1,342 changed lines, under the 3,000-line line. Four of the seven files are path readers outside the move and one is a new anchor entry, so check-governed-merges.mjs ran on the trial file list before the cut: NOT governed (0 of 7 paths hit the register), and --branch HEAD agrees. One commit: 361581a546 refactor(plugin-auth): move the organization plugin factory into auth-manager-organization-plugin.ts.

The move

moved base lines proof lines tokens members it reads
the body of the organization factory arrow in buildPluginList :1991–:2559 571 1,854 8 methods, 2 fields

The lines are base ba9e0c3749. The 571 proof lines are the body with its braces (the arrow's { on :1990 to its } on :2560).

  • What it is: buildPluginList (:1892, private async, annotated Promise<any[]>) builds better-auth's plugin list. For each optional plugin it calls await this.addOptionalPlugin(plugins, name, async () => {…}), whose arrow loads the plugin and builds it. The organization call is at :1990, inside if (enabled.organization).

  • What the body does, in order (all of it moves, with its comments): the deferred better-auth/plugins/organization import; the closed organization-role vocabulary and the roles map built from it (better-auth/plugins/organization/access), stored on the manager as orgRolesMap; one options object (the schema, teams, the requireEmailVerificationOnInvitation: false declaration, the organization limit, the membership limit, the organization hooks for creation, invitation, acceptance, slug change and deletion with their refusal codes, and the invitation e-mail); the vendor plugin built from it; and the rebuilt /organization/list-user-invitations listing, with its warning when the vendor no longer exposes it.

  • Where it goes: a new module, packages/plugins/plugin-auth/src/auth-manager-organization-plugin.ts (683 lines). The body becomes the body of export async function buildOrganizationPlugin(this: OrganizationPluginHost), re-indented four spaces (two levels) and otherwise unedited. The module header gives the reasoning, in the form the fourteen earlier auth-manager-*.ts modules use.

  • What stays: buildPluginList keeps its place, its docblock, its signature and every other line. The organization call keeps its place in it, and its arrow is now:

    // [#25224 cut 15] The body of this factory moved to
    // `auth-manager-organization-plugin.ts`; this arrow hands it the members
    // it reads (`OrganizationPluginHost`).
    await this.addOptionalPlugin(plugins, 'organization', () => {
      const readConfig = () => this.config;
      const readOrgRolesMap = () => this.orgRolesMap;
      const writeOrgRolesMap = (v: AuthManager['orgRolesMap']) => { this.orgRolesMap = v; };
      const host: OrganizationPluginHost = {
        get config() { return readConfig(); },
        emailLocaleArg: (...a) => this.emailLocaleArg(...a),
        // … one closure per method, alphabetical, 8 in all, and:
        get orgRolesMap() { return readOrgRolesMap(); },
        set orgRolesMap(v) { writeOrgRolesMap(v); },
      };
      return buildOrganizationPlugin.call(host);
    });

The seat's bars

Body.

  • The body is the same TypeScript token sequence at base and in the module function: 571 lines with its braces, 1,854 tokens. Each of the 569 lines between the braces is the base line minus exactly four spaces. The closing brace is the one line that moves further: at base the body sat at the arrow line's own column, so its } was at column 6, and in the module it is at column 0.
  • async moves from the arrow to the function. The arrow is not async, and it returns buildOrganizationPlugin.call(host), the function's own promise, so addOptionalPlugin awaits the same promise the body settles. The readers, the writer, the host literal and the .call cannot throw before the promise exists, and a throw inside the body is still a rejection of it.
  • No parameter, no annotation, no captured local. The arrow and the function take no parameter, and neither carries a return annotation. The body uses no arguments, new.target or super (0 of each), no bare this, and no this under a non-arrow function (its one non-arrow function node is a function type). It reads no local of buildPluginList: enabled and plugins appear only in the if and in the call's own arguments, and the body's dataEngine is its own local.
  • buildPluginList is byte-identical at base and at head outside the arrow, apart from the three comment lines above the call (772 lines at base, checked by the same run).

Receiver.

  • The body's this was the AuthManager instance, the arrow's lexical receiver. It is now the host.
  • OrganizationPluginHost names exactly the 10 members the body reads, no more, each typed as the class member (AuthManager['x']).
  • 8 are methods, so each goes as a late-bound closure, (...a) => this.x(...a): emailLocaleArg, getAppName, getConsolePageUrl, getDataEngine, getEmailService, multiOrgPostureEffective, resolveMembershipRole 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 organization hooks and the invitation e-mail, which better-auth runs long after the function returns.
  • Two are fields:
    • config, which the class reassigns after construction (setRuntimeBaseUrl and applyConfigPatch), goes as a getter over a reader closure, readonly on the interface. The body reads it 9 times and never writes it.
    • orgRolesMap goes as a get/set pair, 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, routeOwnership and routeOwnershipFor). The body writes it once (:2050, this.orgRolesMap = customOrgRoles) and never reads it. The setter writes the manager's own field through the writer closure, so the field the actor guards read back (assertRemoveMemberPermitted and assertOrganizationDeletable, through their own getters) is the same field, written at the same moment, as before. It is not 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 reads five function-valued members off this.config. It calls this.config.invitationPlacement?.() (twice, :2245 and :2290) and this.config.logger.warn(msg, meta) (:2487) on their own objects; it reads onOrganizationCreated (:2178) and onInvitationAccepted (:2307) into a local cb and calls that; and it hands getI18n on as a value (:2485). Each is read off the object the getter returns, the manager's current config, at the same moment as before, so every call has the same callee and the same this.
  • The body reads no AuthManager.X, so the module imports the class as a type only.

Eight top-level declarations, exported in place, and one already exported.

  • The body reads nine top-level declarations of auth-manager.ts:
    • the five organization hook refusal codes: ORGANIZATION_RESERVED_IDENTIFIER_CODE, ORGANIZATION_CREATION_DISABLED_CODE, INVITE_PLACEMENT_UNSUPPORTED_CODE, INVITE_PLACEMENT_OUTSIDE_ISSUER_SCOPE_CODE and ORGANIZATION_SLUG_LOCKED_CODE;
    • the slug guard's and the placement check's read-fault answers, slugGuardReadFaultApiError and invitationPlacementReadFaultApiError;
    • the placement gate's refusal test, isInvitationPlacementGateRefusal;
    • readInvitationPlacementIntent, which is already exported.
  • Per ruling 6364006498 (A), the eight unexported ones get export in place, in this cut, the first whose body reads them, and the module imports all nine by value from ./auth-manager.js. 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.
  • They stay where they are, so the two read-fault answers and their new APIError( sites stay in auth-manager.ts.
  • src/index.ts names its auth-manager.ts exports one by one and names none of them, so nothing new is published. readInvitationPlacementIntent was exported and published at base, and is in both export lists and in dist/index.d.ts at both ends, unchanged.

Host lifetime. The host is built each time addOptionalPlugin runs the factory, which buildPluginList 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 getter and the get/set pair read and write the fields when the body, or a hook it handed better-auth, does.

Readers. The decision letters are read on the merge base and on the head below: none drop, and the new module is anchored by the entry under Path readers. Tenant census and symbol-anchor output are byte-identical at both ends, and the full plugin-auth suite passes at both ends. The four path readers whose text or lists name where the moved lines live are migrated in this commit.

Imports and evaluation order

Fifteen imported names leave auth-manager.ts, because the body was their last reader there:

  • the type OrganizationOptions (its whole better-auth/plugins/organization line);
  • the whole ./cloud-managed.js line (CLOUD_MANAGED_REFUSAL_CODE, CLOUD_MANAGED_REFUSAL_MESSAGES);
  • resolveOrgLimit (the @objectstack/types block keeps resolveTenancyPosture and isMcpServerEnabled);
  • the whole ./membership-limit.js line (resolveMembershipLimitOption);
  • the whole @objectstack/spec block (MEMBERSHIP_ROLE_DELEGATED_ADMIN, MEMBERSHIP_ROLE_EXTERNAL);
  • withSystemContext (the ./objectql-adapter.js line keeps createObjectQLAdapterFactory and withSystemReadContext);
  • the whole ./list-user-invitations-verification.js, ./invitation-role-cap.js, ./invitation-role-label.js and ./organization-delete.js blocks (applyDeclaredInvitationVerificationToListing, invitationRoleCapFailure, isPlainMemberInvitation, invitationRoleLabel, isDefaultOrganizationTarget, removeOrganizationAuthStructure);
  • buildOrganizationPluginSchema (the ./auth-schema-config.js block keeps its other names).

One comment and one line are added after cut 14's: import { buildOrganizationPlugin, type OrganizationPluginHost } from './auth-manager-organization-plugin.js'. isCloudManaged, postureEnforcesWall, resolveTenancyPosture, withSystemReadContext and isPlaceholderEmail have other readers, so both files import them. One prose mention of withSystemContext stays in a docblock (:892 at head, in backticks, not a {@link}), so nothing dangles.

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 apart from one adjacent pair just before the bundle's own body: @objectstack/platform-objects/identity and @better-auth/core/utils/ip swap places (169 and 170). The bundle's first platform-objects/identity import is invitation-role-label.ts's (SysMember), and that module's segment now sits later in the bundle (line 4,828 at base, 8,522 at head), after the first utils/ip import. Neither module imports the other. utils/ip's top level imports @better-auth/core's env module and zod, builds two zod schemas and declares constants and functions; platform-objects/identity imports three @objectstack/spec entries, all evaluated earlier at both, and builds object schemas. Neither writes process.env or a global.
  • The CommonJS entry. A Module._load log for dist/index.js gives 127 first loads at both. Two move earlier: @better-auth/core/dist/utils/ip.mjs and @better-auth/core/dist/context/index.mjs were the last two loads at base and now come right after @objectstack/spec/identity, ahead of platform-objects/identity, rest, observability, metadata-core, node's module and metadata/view-container. Their top levels are the ones above and the inert context chain cut 14 measured (its only load-time reads are of process.env), and none of those five package entries writes process.env or a global anywhere in its file (module is node's own).
  • plugin-auth's own modules. A walk of the bundle's top level and its eager init_* calls finds six that run later at both entries: cloud-managed.ts, membership-limit.ts, list-user-invitations-verification.ts, invitation-role-label.ts, organization-delete.ts and grant-set-name.ts, whose last value import from auth-manager.ts left with the body (grant-set-name.ts moves with organization-delete.ts, which imports it). Their top levels are imports, constants with no call in their initializers, types and function declarations: no load-time call at all. No importer reads any of their bindings at load time: all 35 references in the 11 importing files are inside functions, and index.ts re-exports cloud-managed.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 nine declarations, and auth-manager.ts's import of it form a cycle with nothing read at load time, as cut 14's does.

The decision-letter layer: 0 drop

check-adr-anchors, measured on the merge base and on the head. The gate exits 0 at both.

point anchored files citations files decision letters
merge base ba9e0c3749 84 52,305 6,698 1,939
head 361581a546 85 52,307 6,700 1,939
  • The body's letters leave auth-manager.ts, which is anchored, for the new module, which the new entry below anchors, so the count does not move.
  • The two new citations are the entry's two ADR-0108 mentions; the two new files are the module and the entry.

Path readers

Four readers outside the move follow it, in this commit, and one anchor entry is added:

  • api-error-code.pin.test.ts: authManagerFiles, the files AuthManager's throws are counted over, gains auth-manager-organization-plugin.ts. The body carries 7 new APIError( sites, so the sum over the list is 30 at head, as at base (7 in the module; auth-manager.ts goes 9 → 2, the two read-fault answers that stay). Without the new entry the sum would be 23, under the floor of 24, and the pin would fail: it is a floor over a list, not a silent zero.
  • 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-organization-plugin.ts (the comment above each list now says "any of them" for "either"). The body holds one such import, organization. At base the scan finds 16 factories; at head without the new entry it would find 15, missing organization, 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 :861 and the in-test note at :1127, head numbering) that name the files the five sendTemplate sites live in now name auth-manager-organization-plugin.ts (the body of the organization plugin's factory) among them; the invitation send moved with the body. No assertion, count or pattern changes.
  • The anchor entry: scripts/adr-anchors/packages__plugins__plugin-auth__src__auth-manager-organization-plugin.ts.json (new), with adrs: ["ADR-0108"] and the auth-manager.ts entry's ADR-0108 paragraph verbatim ("better-auth's organization roles map registers the closed framework vocabulary ONLY…"). The body is that mechanism: it builds the roles map from the closed vocabulary and stores it. This is cut 4's precedent (an entry for the new module carrying the paragraph verbatim; the old entry stays unchanged), and ruling 6353073966 item 1 says scripts/adr-anchors/ is not a governed-register row. The auth-manager.ts entry is not edited: it keeps ADR-0108, which the file still cites (:771, the additionalOrgRoles removal note), and its ADR-0093 D2 paragraph.

Read, and not edited:

  • scripts/check-dispatcher-error-vocabulary.mjs: the body's 7 new APIError( sites each carry a registered or standard code, 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 only its scanned-file count moves (3,434 → 3,435). --self-test is among the derived gates.
  • 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 (753 → 754) 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 566 lines up with the cut's net: :4442 → :3876). Every statistic is the same apart from files (3,484 → 3,485): tryStatements 3,646 and droppedNotAwaited 234 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 names it once, in a comment. scripts/publish-smoke.sh names auth-manager.ts for the admin: pluginConfig.admin ?? scimEffective line, in the enabled literal, which stays.
  • 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. Two of their symbols occur as a token on a line of the body: AuthManager (ADR-0135 :104), whose declaration is the class, which stays, and ORGANIZATION_CREATION_DISABLED_CODE (the attachments-storage checklist area :1116), whose declaration (:1403 at base) stays and gains only export. Each resolves to its declaration at all three readings.

Proof

Builds and suites ran through os-verify-lock, and every exit code was captured before any pipe. Base ba9e0c3749. 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 arrow 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): 571 lines and 1,854 tokens, each line between the braces the base line minus four spaces.

  • The stub's shape, checked by the same run: the arrow is not async and takes no parameter; it holds the two reader closures and the writer closure, one host literal whose keys are exactly OrganizationPluginHost's, alphabetical, whose 8 method values are each (...a) => this.<key>(...a), whose config is a getter over its reader and whose orgRolesMap is a getter over its reader and a setter over the writer, then return buildOrganizationPlugin.call(host), and nothing else. The interface types every member AuthManager['x'], and exactly the getter-only field is readonly. buildPluginList outside the arrow is byte-identical apart from the three comment lines.

  • Binding identity: a second proof walks all 496 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 496 resolve the same way at both ends:

    • 324 to a declaration inside the body, the same declaring identifier by position;
    • 18 to receiver members (AuthManager at base, OrganizationPluginHost at head);
    • 31 reads of 29 imported names, with the same specifier and imported name, the nine ./auth-manager.js names among them;
    • 11 to the same declaration in auth-manager.ts: seven AuthManagerOptions members read through this.config (dataEngine, getI18n, invitationPlacement, logger and its warn, onInvitationAccepted and onOrganizationCreated), and the locale of emailLocaleArg's answer;
    • 112 to no declaration: globals and property names on untyped values.

    None resolves to a local of buildPluginList, to an unexported top-level declaration of auth-manager.ts or to a top-level declaration of the module.

  • Negative controls: each proof reads the module text, and each control changes it in memory only; the file is untouched.

    • Token proof: organization(organizationOptions) changed to organization?.(organizationOptions) fails the token and line checks, and the run exits 1.
    • Binding proof: the module's membership-limit 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 eight unexported declarations, and exits 1.
  • An independent reading: git diff --color-moved=plain --color-moved-ws=allow-indentation-change ba9e0c3749 361581a546 over the two source files marks 577 of the 600 deleted lines and 580 of the 717 added lines as moved.

    • The 23 unmoved deletions are 14 import lines (the import names and block closers listed under Imports, and the ./objectql-adapter.js line, rewritten without withSystemContext), the first lines of the eight declarations (now with export) and the arrow'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, writer, host literal and return, the eight export lines, the rewritten ./objectql-adapter.js line, and the new import comment and line.

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.
  • 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). None of the eight declarations exported in place, buildOrganizationPlugin or OrganizationPluginHost is among them, and dist/index.d.ts names none of them.
  • 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 6 TypeScript 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 anchored-file, citation and file counts, as in the table above, with the decision letters equal.

5. Gates.

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 71 commands for the 7 paths. All 71 ran at head after the full build, and all 71 exited 0.
  • --ran over the record with each exit code: 71 derived, 71 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 7 paths hit the register), 1,342 changed lines.
  • Docs drift (node scripts/docs-audit/affected-docs.mjs --json ba9e0c3749, exit 0): 41 pages, all name matches of names the moved code carries (the VALIDATION_ERROR, sys_member, sys_organization, sys_invitation and sys_environment literals, and AuthManager). 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. 11 of the 41 are release-owned (the release notes and releases/implementation-status.mdx) and not edited.
  • Serial constraints (in the claim, and re-read at 03:13Z before this PR): 8 PRs are open, and none touches auth-manager.ts, the new module's path, a reader edited above or scripts/adr-anchors/. The Version Packages PR chore: version packages #24876 touches plugin-auth's package.json, its CHANGELOG.md and changesets only. origin/main has moved 8 merges past this base, to e073455929; none touches plugin-auth, scripts/adr-anchors/ or a reader named above.

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

…-manager-organization-plugin.ts

The body of the arrow AuthManager.buildPluginList() hands
addOptionalPlugin for 'organization' moves unedited into the module
function buildOrganizationPlugin, re-indented two levels: the closed
organization-role vocabulary and the roles map built from it, the
options object (schema, teams, the invitation verification declaration,
the organization and membership limits, the organization hooks with
their refusal codes, the invitation e-mail), the vendor plugin and the
rebuilt invitation listing. The arrow keeps its place in
buildPluginList; it now builds one host of the ten AuthManager members
the body reads (eight methods as late-bound closures, config as a
getter, orgRolesMap as a get/set pair because the body stores the roles
map on the manager) and returns buildOrganizationPlugin.call(host). The
async moved from the arrow to the function.

The eight top-level declarations of auth-manager.ts the body reads and
the file did not export (five hook refusal codes, two read-fault
answers and the placement gate's refusal test) are exported in place
and imported by value, with readInvitationPlacementIntent; src/index.ts
names its auth-manager.ts exports one by one, so nothing new is
published. Fifteen imported names leave auth-manager.ts with the body.

Readers that follow it: the api-error-code pin counts the module among
the AuthManager files, the two plugin-factory scans read it, the
population comments in auth-email-locale.test.ts name it, and the
module gets its own ADR anchor entry carrying the ADR-0108 paragraph
verbatim, since the body is the roles map that paragraph describes.

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 41 documentable anchor(s).

30 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json e073455929a60b67c48da3f91324261e57aaa26b.

⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

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 e073455929a60b67c48da3f91324261e57aaa26b → packageMentionDocs.

Which tree this was computed on

This run read content/docs from cae3a7d4a5672fd571c19ccd58e2c3bda3c49010 — the merge of head 361581a546d70f3f2e0597fe598428349ae27cef into base e073455929a60b67c48da3f91324261e57aaa26b, 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 cae3a7d4a5672fd571c19ccd58e2c3bda3c49010 && git checkout cae3a7d4a5672fd571c19ccd58e2c3bda3c49010
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e073455929a60b67c48da3f91324261e57aaa26b 361581a546d70f3f2e0597fe598428349ae27cef && git checkout -B drift-repro e073455929a60b67c48da3f91324261e57aaa26b && git merge --no-ff 361581a546d70f3f2e0597fe598428349ae27cef

node scripts/docs-audit/affected-docs.mjs --json e073455929a60b67c48da3f91324261e57aaa26b

⚠️ 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 e073455929a60b67c48da3f91324261e57aaa26b → 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-24T03:59Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25224 cut 15 (claim 6379187398, amending 6377759458; os-dev-report 6379514849) · reviewed head 361581a546 · merge base ba9e0c3749

  • Shape: the organization plugin's factory arrow inside buildPluginList (body :1991–:2559) moves unedited into auth-manager-organization-plugin.ts as buildOrganizationPlugin(this: OrganizationPluginHost), using the closure shape of cuts 12 and 13. The one written field, orgRolesMap, takes a get/set pair in the refactor(plugin-auth): move AuthManager's request routing into auth-manager-request-routing.ts (#25224 cut 10) #25508 form, and the host is one literal with 8 late-bound methods. The PR touches 7 files, +734 / −608, for 1,342 changed lines, under the 3,000 bar. Eight declarations are exported in place inside the package under ruling 6364006498 (A); none of them is published.
  • Readers outside the moved text, migrated: the authManagerFiles list in api-error-code.pin.test.ts, the two factory-scan lists (better-auth-schema-parity, managed-extension-fields), and the comments in auth-email-locale.test.ts. The seat read the diff: only file names and comment text change.
  • New anchor entry scripts/adr-anchors/…auth-manager-organization-plugin.ts.json (ADR-0108, the paragraph verbatim; this follows the cut 4 precedent and ruling 6353073966 item 1). The seat ran a negative control: with the entry removed at head, check-adr-anchors reads 1926 decision letters instead of 1939, so the entry carries the 13 citations that moved with the body.
  • .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 at both ends.
    • check-tenant-audit-census: 238 sites.
    • check-adr-anchors.
    • check-dispatcher-error-vocabulary.
    • At head, check-system-context-census (149) and check-platform-checklist.
  • Decision letters, read by the seat: 1939 at the merge base and 1939 at head, so the cut drops none. The report gives 1,883 on today's main both alone and with the cut.
  • Tests and gates (report): token identity holds at 571 lines / 1,854 tokens, and 496 of 496 identifiers bind the same way. The suite passes 3,626 tests at base, at head and on the merged tree. 71 of 71 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.
  • Body: the attribution header and the os-sales reviewer are added by the seat through the relay with this verdict. The footers are present.
  • Landing: HELD. Sales paused merges for a release at 2026-10-24T03:57Z. The cut is not governed (check-governed-merges --pr 25594: 1,342 lines). The seat readies it and arms auto-merge through the relay only after merges resume, and it re-checks git merge-tree against main at that point. The card stays open and dispatched. Cut 16 waits for this cut to land.

Generated by Claude Code

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