Skip to content

refactor(plugin-auth): move the better-auth global before-hook into auth-manager-before-hook.ts (#25224 cut 13) - #25548

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

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

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 23, 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 two path-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 .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 fifth cut it applies to.

Cut 13 of the auth-manager.ts split: the global before-hook of the better-auth instance. It is the second of the three createAuthInstance slices the seat set (hooks.after, then hooks.before, then the rest of the method). #25224 stays open for the later cuts.

This follows the amended claim 6376168919, which amends 6374301142 on the file cut 12 left (#25536, landed note 6375260860). The shape is cut 12's: #23050's, applied to a closure. The seat ruled that in scope at 19:56Z, with the bars listed below.

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

auth-manager.ts goes from 8,362 to 7,800 lines. The cut is +734 / −597 over 4 files: 1,331 changed lines, under the 3,000-line line. Two of the four files are path readers outside the move, so check-governed-merges.mjs --test ran on the final file list before the claim: NOT governed (0 of 4 paths hit the register), and --branch HEAD agrees. One commit: 3ec293d86a refactor(plugin-auth): move the better-auth global before-hook into auth-manager-before-hook.ts.

The move

moved base lines proof lines tokens members it reads
the hooks.before arrow body in createAuthInstance :2246–:2827 584 1,326 18 methods, 1 field

The lines are base 93a6ba4136. The 584 proof lines are the body with its braces (:2245's { to :2828's }).

  • What it is: createAuthInstance (:1695, private, annotated Promise<Auth<any>>) builds one config literal for better-auth. Its hooks.before is createAuthMiddleware(async (ctx: any) => {BODY}): the before-hook every better-auth endpoint's request passes through before the endpoint runs.

  • What BODY does, in order (all of it moves, with its comments): the actor resolver handed to the attribution scope, the refresh skip for a minted principal session, the per-number OTP send guard, the password complexity and reuse checks, the change-email capture, the remove-member, organization-delete and no-record revoke refusals, the held-caller refusal on the admin plugin's role-scalar actions, the no-record admin revoke refusal, the held-caller refusal on rewriting or deleting an identity provider, the identity-provider registration gate, the cloud-as-IdP authorization gate, the break-glass last-local-credential refusal, the audience posture and uniqueness refusal on password sign-up, the cloud-managed invite-member refusal, the account lockout gate, and the first-user disableSignUp flip.

  • Where it goes: a new module, packages/plugins/plugin-auth/src/auth-manager-before-hook.ts (696 lines). BODY becomes the body of export async function globalBeforeHook(this: BeforeHookHost, ctx: any, setShouldSkipSessionRefresh: typeof import('better-auth/api').setShouldSkipSessionRefresh), re-indented eight spaces (four levels) and otherwise unedited. The module header gives the reasoning, in the form the twelve earlier auth-manager-*.ts modules use.

  • What stays: createAuthInstance keeps its place, docblock and signature, and hooks.before keeps its place in the literal. Its arrow is now:

    // [#25224 cut 13] The body moved to `auth-manager-before-hook.ts`; this
    // arrow hands it the members it reads (`BeforeHookHost`) and the one
    // local it read, `setShouldSkipSessionRefresh`.
    before: createAuthMiddleware((ctx: any) => {
      const readConfig = () => this.config;
      const host: BeforeHookHost = {
        assertAccountNotLocked: (...a) => this.assertAccountNotLocked(...a),
        // … one closure per method, alphabetical, 18 in all, and:
        get config() { return readConfig(); },
      };
      return globalBeforeHook.call(host, ctx, setShouldSkipSessionRefresh);
    }),

    Nothing else in createAuthInstance changes.

The seat's bars

Body.

  • BODY is the same TypeScript token sequence at base and in globalBeforeHook: 584 lines, 1,326 tokens. Every line is the base line minus exactly eight spaces. The first parameter (ctx: any) is the same text; the second is the captured local, below. Neither side has a return annotation.
  • async moves from the arrow to the function. The head arrow is not async, and it returns globalBeforeHook.call(host, ctx, setShouldSkipSessionRefresh), the function's own promise, unchanged. better-auth's createAuthMiddleware is better-call 1.4.4's createMiddleware, whose handler wrapper runs const response = await _handler(internalContext); inside its try (dist/middleware.mjs:15). So it awaits the same promise BODY settles, with the same value or rejection, and its catch sees the same error. The reader closure, the host literal and the .call cannot throw before that promise exists.
  • No arguments, new.target or super (0 of each), where an arrow and a function differ. There is no bare this: BODY's only this uses are 28 member reads, this.X.

One captured local, now a parameter. Unlike cut 12's body, this one read a createAuthInstance local: setShouldSkipSessionRefresh, once (:2273, if (carriesPrincipalSession(ctx)) await setShouldSkipSessionRefresh(true);).

  • The method binds it once, const { createAuthMiddleware, setShouldSkipSessionRefresh } = await import('better-auth/api'); at :1697, before it builds the literal, and never reassigns it.
  • It becomes the function's second parameter, under the same name, typed typeof import('better-auth/api').setShouldSkipSessionRefresh. The head arrow passes the same binding on every call. The base arrow read that const when the hook ran, and the head arrow reads it at the same moment and hands it on, so BODY calls the same function object.
  • None of the method's other locals declared before the literal (betterAuth, createAuthMiddleware, plugins, passwordHasher) is read in BODY. The binding proof below finds no other identifier in BODY bound in the method.

Receiver.

  • BODY's this was the AuthManager instance, the arrow's lexical receiver. It is now the host.
  • BeforeHookHost names exactly the 19 members 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). The method runs on the manager and is looked up when BODY calls it, so a spy or double set on the manager is still the one BODY reaches: assertAccountNotLocked, assertActorNotHeld, assertAdminRevokeUserSessionIdentifiesRecord, assertOrganizationDeletable, assertPasswordComplexity, assertPasswordNotReused, assertPhoneOtpSendAllowed, assertRemoveMemberPermitted, assertRevokeSessionIdentifiesRecord, existingAddressSignUpAnswersSuccess, hasExistingUserFor, isAuthGateActive, isBootstrapCreation, isOperatorProvisioning, isPlatformAdminUserId, resolveActor, resolvePasswordChangeUserId and validateAudienceAdmission.
  • One is a field: config. The class reassigns it after construction (setRuntimeBaseUrl at :4549, applyConfigPatch at :4630), so it goes as a getter over a reader closure (const readConfig = () => this.config; and get config() { return readConfig(); }), the shape cut 6's stubs use. BODY reads the manager's current object each time. It is readonly on the interface, and BODY never writes it.
  • A function-valued member, 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): BODY calls this.config.oidcAuthorizeGate({…}) (:2546, after the presence check at :2536), an optional function on the config object (AuthManagerOptions, :789). Its receiver is this.config, which the getter returns as the manager's current object, so the gate is looked up and called on the same object, at the same moment, with the same this, as before.
  • hasExistingUserFor and isBootstrapCreation read private statics. They stay in the class, per ruling A, and BODY reaches them as members.

One static read, a value import. BODY reads AuthManager.OPERATOR_PROVISIONING_TICKET_FIELD once (base :2820, module :688), a public static readonly string (:4844). The module imports the class by value from ./auth-manager.js, as cut 8's auth-manager-membership-admission.ts does. The one read is inside the function, at call time, so the cycle is never read during evaluation (below).

Host lifetime. The host is built inside the arrow, once per run of the hook: each time better-auth runs hooks.before on a request. This is what every method stub in this series does. Nothing BODY reads is captured early: the 18 closures look each method up on the manager at call time, and the getter reads config when BODY reads it.

Readers. The decision letters are read on the merge base and on the head below: 10 drop, as expected. No anchor entry is owed. Tenant census and symbol-anchor output are byte-identical at both ends, and the full plugin-auth suite passes at both ends. The two path readers that read the moved lines are migrated in this commit.

Imports and evaluation order

Nine names leave auth-manager.ts, because BODY was their last reader there:

  • setAuthActorResolver (its import line goes);
  • SIGN_IN_EMAIL_PATH (cut 12 left BODY its last reader here; the block keeps type AuthEventAuditSurface);
  • VENDOR_ROLE_SCALAR_ADMIN_PATHS, carriesPrincipalSession and ORGANIZATION_DELETE_PATH (each block keeps its other names);
  • the whole last-local-credential.js block: isLastLocalCredentialHolder, LAST_LOCAL_CREDENTIAL_CODE and LAST_LOCAL_CREDENTIAL_MESSAGE.

isCloudManaged, CLOUD_MANAGED_REFUSAL_CODE and CLOUD_MANAGED_REFUSAL_MESSAGES have other readers in auth-manager.ts, so both files import them. One import line is added after cut 12's, with a comment in the same form: import { globalBeforeHook, type BeforeHookHost } from './auth-manager-before-hook.js'.

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, except one: @better-auth/core/dist/utils/ip.mjs now runs just after @objectstack/platform-objects' identity entry instead of just before it. Its own dependencies (env/env-impl.mjs, zod) run much earlier at both, unchanged. Its top level is two zod schema objects (z.ipv4(), z.ipv6()), constants and function declarations, and it registers nothing.
  • The CommonJS entry. A Module._load log for dist/index.js gives 127 first loads at both, the same apart from that one module, which now loads last.
  • plugin-auth's own modules. A walk of the bundle's top level and its eager init_* calls finds two that run later: auth-session-audit.ts, whose one value import in auth-manager.ts was SIGN_IN_EMAIL_PATH, and last-local-credential.ts. Their top levels are string and regex constants and function declarations, with no side effect. auth-session-audit.ts's one import is getIP from that ip module, which is why the ip module moves with it. No importer reads either module's bindings at load time: all 24 references in the 5 importing files are inside functions, and index.ts re-exports both.
  • auth-actor-attribution.ts, whose setAuthActorResolver leaves, is an __esm-wrapped module that another importer initialises earlier, at both, so it does not move. The other modules keep a value import in auth-manager.ts.
  • The new module's own top level is imports, one interface and one function declaration. Its value import of auth-manager.ts, for the static, and auth-manager.ts's import of it form a cycle with nothing read at load time.

The decision-letter layer: −10, as expected

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 93a6ba4136 84 52,274 6,681 1,955
head 3ec293d86a 84 52,274 6,682 1,945
  • The ten letters are in BODY's comments: ADR-0095 D3 (:2258), ADR-0069 D1 (:2292, :2308), ADR-0069 D3 (:2406, :2445, :2492), ADR-0135 D6 and ADR-0068 D4 (:2463), ADR-0068 D4 (:2481) and ADR-0069 D2 (:2787). They leave auth-manager.ts, which is anchored, for a module that is not. Their citations still resolve, so the citation count does not move.
  • No entry is owed. The auth-manager.ts entry has two paragraphs: ADR-0108 (better-auth's organization roles map) and ADR-0093 D2 (settleMembership and its two seams, user.create.after and session.create.before, which are database hooks, not hooks.before). Neither describes this hook, and no other entry names it. So, per the seat's bar, no entry is added for the new module.

Path readers

Two readers outside the move read the moved lines by path, and both follow BODY in this commit:

  • api-error-code.pin.test.ts: authManagerFiles gains 'auth-manager-before-hook.ts'. BODY carries 5 of the auth manager's new APIError( sites (:2505, :2552, :2651, :2691, :2781). Without the entry the floor would still pass, silently, at 25 against ≥ 24. With it the count stays 30, as at base.
  • scripts/check-dispatcher-error-vocabulary.mjs: APIERROR_ARGUMENT_POSITION_CENSUS.liveMemberFile now names packages/plugins/plugin-auth/src/auth-manager-before-hook.ts, where BASE_ERROR_CODES.USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL (APIError.from, base :2752) now lives, with a two-line comment that the counts stay the reading at measuredOn. The three comments that name the file the idiom lives in say the new module too. No count, pattern or verdict changes. The gate exits 0 at both: 56 sites found and 56 classified, and only its scanned-file count moves (3,424 → 3,425). --self-test passes (11 shapes, 396 assertions).

Read, and not edited:

  • The tenant-audit census. No census site moves: BODY has no engine write (its one adapter.findOne is in a comment, :2802), no this.X.delete() and no elevation read. check-tenant-audit-census exits 0 at both with no --write, and its output is byte-identical. The --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 (each 562 lines earlier at head, the cut's net), and 0 are in the new module. Only scannedSources (747 → 748) and measuredAt differ, 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. The members are the same with line numbers ignored (one member's write line, in observeFirstMemberships, which stays, moves 5,739 → 5,177), and the quiet rows are identical. Only the scanned file count moves (3,474 → 3,475, the new module). tryStatements (3,640), droppedNotAwaited (235) and every other statistic are the same: BODY's try blocks move whole.
  • 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'; BODY has none.
  • The plugin-factory scans in better-auth-schema-parity.test.ts and managed-extension-fields.test.ts match const { X } = await import('better-auth/plugins…' | '@better-auth/…'). BODY's dynamic imports are better-auth/api and @better-auth/core/error, and @better-auth/core is excluded, so none is a factory. Both suites pass at head.
  • rate-limit-storage-isolation.test.ts walks the import graph from rate-limit-storage.ts, which reaches neither file. It passes at head.
  • The system-context census reads 149 elevation read sites at both, with byte-identical output.
  • 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 BODY blanked, and at head. Two of their symbols occur as a token on a line of BODY: AuthManager (ADR-0135 :104; BODY :2820, the static read) and isBootstrapCreation (the identity-auth checklist area :1810; BODY :2823, a member call). Each resolves to its declaration, which stays, at all three readings.

Proof

Builds and suites ran through os-verify-lock, and every exit code was captured before any pipe. Base 93a6ba4136. 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): 584 lines and 1,326 tokens, each line the base line minus eight spaces.

  • The head arrow's shape, checked by the same run: not async, the same first parameter, the reader closure for config, one host literal whose keys are exactly BeforeHookHost's, whose 18 method values are each (...a) => this.<key>(...a) and whose config is the getter over the reader, then return globalBeforeHook.call(host, ctx, setShouldSkipSessionRefresh), and nothing else.

  • Binding identity: a second proof walks all 356 identifiers of 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 base file). All 356 resolve the same way at both ends:

    • 69 to a parameter: ctx, and the captured local, which the proof is told about;
    • 119 to a declaration inside BODY, the same declaring identifier by position;
    • 29 to the class's members: the 28 receiver reads (AuthManager at base, BeforeHookHost at head) and the one static;
    • 12 to imports with the same specifier and imported name, the class itself among them;
    • 2 to the same declaration in the base file: oidcAuthorizeGate on AuthManagerOptions, twice;
    • 125 to no declaration: globals and property names on untyped values.

    None resolves to any other createAuthInstance local or to an unexported top-level declaration of either file.

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

    • Token proof: setShouldSkipSessionRefresh(true) changed to (false) fails the token and line checks, and the run exits 1.
    • Binding proof: the module's last-local-credential import pointed at another specifier gives 3 differences, and the run exits 1. Run without being told about the captured local, it reports that local, and exits 1.
  • An independent reading: git diff --color-moved=plain --color-moved-ws=allow-indentation-change 93a6ba4136 3ec293d86a over the two source files marks 588 of the 593 deleted lines and 596 of the 727 added lines as moved.

    • The 5 unmoved deletions are four import-name lines (SIGN_IN_EMAIL_PATH,, VENDOR_ROLE_SCALAR_ADMIN_PATHS,, carriesPrincipalSession, and ORGANIZATION_DELETE_PATH,, each now on its own line in the module) and the before: createAuthMiddleware(async (ctx: any) => { 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 arrow's comment, reader, host literal and return, 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 createAuthInstance; → createAuthInstance;, line 1712, a one-line change) hashes raw 35b18682171cd394898c9e56bc8d5272ccf20aeff0d1966fc568cb5e566a08df and normalised e69c1b0157b45e48bbc16bd64eb939abf9be33ccb31553485df93ef05aac6fc3. 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). globalBeforeHook and BeforeHookHost are not among them, and dist/index.d.ts names neither.
  • The bundle: the same 18 files in dist/ at both, plus the two .build-input-hash stamps the build writes from its inputs. Only index.js, index.mjs and their two .map files differ, as a move of code inside the bundle must. tsup bundles the new module into the root entry (splitting: false).

3. Tests and lint.

  • pnpm exec vitest run --maxWorkers=3 in packages/plugins/plugin-auth: base Test Files 191 passed (191), Tests 3607 passed | 10 skipped (3617), exit 0. Head: the same counts, exit 0. The pin test's authManagerFiles sum is 30 at both.
  • pnpm --filter @objectstack/plugin-auth typecheck (both tsc projects and check:test-typecheck): exit 0. pnpm --filter @objectstack/runtime typecheck: exit 0.
  • eslint --no-inline-config (the root lint script's binary and flags) over the 4 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.

  • 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.

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

…uth-manager-before-hook.ts

The body of the arrow createAuthInstance passes to createAuthMiddleware
for hooks.before moves unedited into globalBeforeHook, re-indented four
levels. hooks.before keeps its place in the config literal; its arrow
now builds one host of the nineteen AuthManager members the body reads
(eighteen methods as late-bound closures, config as a getter) and
returns globalBeforeHook.call(host, ctx, setShouldSkipSessionRefresh).
The async moved from the arrow to the function. The one
createAuthInstance local the arrow read, setShouldSkipSessionRefresh,
is the function's second parameter under the same name; the body's one
static read, AuthManager.OPERATOR_PROVISIONING_TICKET_FIELD, is a value
import of the class, read at call time. Nine imports leave
auth-manager.ts with it.

Two readers outside the move follow it: the api-error-code pin counts
the new module among the auth manager's files, and the vocabulary
gate's APIError argument census names it as the live member's file.

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 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/kernel/contracts/auth-service.mdx (via isAuthGateActive (symbol, a field of interface BeforeHookHost), isAuthGateActive (literal, a string literal in isAuthGateActive))
  • content/docs/permissions/system-context.mdx (via resolveActor (symbol, a field of interface BeforeHookHost), resolveActor (literal, a string literal in resolveActor))
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 4df174bcc48d694e654f90105ebd9e68d3545136 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 4df174bcc48d694e654f90105ebd9e68d3545136

⚠️ 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 4df174bcc48d694e654f90105ebd9e68d3545136 → 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-23T23:17Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25224 cut 13 (claim 6376168919, amending 6374301142; os-dev-report 6376554601) · reviewed head 3ec293d86a · merge base 93a6ba4136

  • Shape: the createAuthInstance hooks.before closure moves unedited into auth-manager-before-hook.ts as globalBeforeHook(this: BeforeHookHost, ctx, setShouldSkipSessionRefresh). The original spot keeps a .call(host, …) wrapper. This is the closure-move route recorded for cuts 12 and 13.
    • The host carries 18 late-bound method closures. config is a getter, because setRuntimeBaseUrl and applyConfigPatch reassign it.
    • setShouldSkipSessionRefresh becomes the second parameter.
    • oidcAuthorizeGate is disclosed as a function-valued member. The public static is read by value import, as in cut 8.
    • 4 files, +734 / −597, 1,331 changed lines.
  • Readers outside the module, migrated in the same commit:
    • The api-error-code pin authManagerFiles gains the new file. The sum stays 30, and without the entry it would have passed silently at 25.
    • scripts/check-dispatcher-error-vocabulary.mjs: liveMemberFile is repointed and 3 prose mentions follow. The seat's head run names auth-manager-before-hook.ts for BASE_ERROR_CODES.USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL.
    • The scan grows by one file (3,424 → 3,425). It still finds 56 unregistered sites, all 56 classified.
  • .d.ts, ruling 6348891160 item 1, re-run by the seat in its own worktree:
    • turbo run build at 93a6ba4136 and at this head.
    • index.d.ts raw 1b3a48357d137155… / normalised cee7303ccdfe7279…, and index.d.mts raw b5e8dc982b634f0a… / normalised 562d0a4ce656f97a…, at both ends. The raw diff is 0 lines.
    • The seat's positive control makes head line 347, private options;, public. That moves both hashes, to raw 51a55969ad8afac6… and normalised 66bcd34e7ea6963c….
  • Gates re-run by the seat, all exit 0 at base and at head:
    • check-adr-symbol-anchors: output byte-identical.
    • check-tenant-audit-census: output byte-identical, 238 sites.
    • check-adr-anchors
    • check-dispatcher-error-vocabulary
  • Decision-letter count, read by the seat: 1955 at the merge base and 1945 at head, with 84 anchored files at both ends. The ten letters inside the body leave with it, and no anchor paragraph describes it, so no entry is owed. The report reads main 4df174bcc4 as 1,956 → 1,946.
  • Report:
    • The body is token-identical: 584 lines, 1,326 tokens, and 356/356 identifier bindings. Each proof has a failing negative control.
    • The suite gives 191 / 3,607 / 10 at both ends and on the merged tree. 80 derived gates exit 0.
    • An ESM evaluation-order probe found only @better-auth/core utils/ip.mjs moving, by one place, with no side effects.
  • Governed: not governed. check-governed-merges --pr 25548 gives 0 of 4 paths, including the scripts/ gate, and 1,331 lines. So this is not Tier H.
  • CI: 34 success and 12 expected skips, none failed or pending (46 runs).
  • Mergeable: git merge-tree onto today's main (673ebfd4dc) is clean. It is also clean pairwise against feat(security): the external organization role is the EXTERNAL principal — owner-only org-wide default, explicit grants only #25541. That PR's one auth-manager.ts hunk is in externalDirectoryDoorDeps, outside this cut.
  • Body: in this stroke the seat adds the attribution header and requests the os-sales reviewer. The footer is present.
  • Landing: ready + auto-merge through the relay. The card stays open and dispatched. Cut 14, the rest of createAuthInstance, is claimed afresh after this merges.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot requested a review from os-sales October 23, 2026 23:18
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 23:18
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 23:18
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit 19d3720 Oct 23, 2026
54 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-lg3wrj branch October 23, 2026 23:45
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