Repository navigation
refactor(plugin-auth): move AuthManager's request routing into auth-manager-request-routing.ts (#25224 cut 10) - #25508
Conversation
…anager-request-routing.ts Three method bodies move unedited: handleRequest, the one seam every better-auth request passes through on its way to the vendor handler and back; ownsRoute, which answers whether better-auth routes a request at all; and betterAuthEndpointPath, the endpoint path both key on. The class keeps all three methods, their docblocks and signatures, and hands each body the members it reads; the route-ownership memo stays on the manager. isScimProtocolPath stays in auth-manager.ts, where the SCIM provisioning decision record anchors it, and is exported there for the new module; index.ts does not publish it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 21 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 502ad4fcefc11d949917b1afe535a21c22da1483 && git checkout 502ad4fcefc11d949917b1afe535a21c22da1483
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d44c8936b1a217155c09603a7c3ecec556abda00 27f04eaa1cff935c90569fbc90ae591deb44ffbe && git checkout -B drift-repro d44c8936b1a217155c09603a7c3ecec556abda00 && git merge --no-ff 27f04eaa1cff935c90569fbc90ae591deb44ffbe
node scripts/docs-audit/affected-docs.mjs --json d44c8936b1a217155c09603a7c3ecec556abda00
|
席位核对 · ACCEPT · 2026-10-23T17:28Z
Generated by Claude Code |
Requested by Sales · project thread
Part of #25224
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160, 6364006498
The basis for the
Clause-②: noline: this is a move. No accept set, route, refusal, code or answer changes, and the published surface is measured unchanged:@objectstack/plugin-auth'sdist/index.d.tsanddist/index.d.mtsare raw byte-identical to base at head (0 diff lines, so the union-order normalisation has nothing to do). The positive control is the one ruling6348891160asks for.The
.d.tsbar is ruling6348891160item 1 (on #25222, covering this card). Ruling6364006498is Sales's ruling A on this card, for bodies that read shared unexported helpers. This is the second cut it applies to.Cut 10 of the
auth-manager.tssplit: request routing. ThreeAuthManagermethod bodies (226 lines) move unedited, re-indented one level, into one new module,packages/plugins/plugin-auth/src/auth-manager-request-routing.ts. #25224 stays open for the later cuts.This follows the amended claim
6369962162, which amends6367866058on the file cut 9 left (#25461, landed note6369059246). The shape is cuts 1 to 9's, which is #23050's.Branch:
claude/project-thread-lg3wrj, the harness-pinned branch for this session, restarted fromorigin/main05cc3ee953after #25461 merged. It names no issue, so the card relation is this body's first line.auth-manager.tsgoes from 8,711 to 8,521 lines. The cut is +361 / −233 over 2 files: 594 changed lines, under the 3,000-line line. It is not a governed surface (check-governed-merges.mjs --branch HEAD: 0 of the 2 paths hit the register). One commit,27f04eaa1c.The move
handleRequest(public):6369betterAuthEndpointPath,getOrCreateAuth,resolveClientAddress,runSubjectErasureAtomicallyownsRoute(public):6739betterAuthEndpointPath,getOrCreateAuth, androuteOwnership,routeOwnershipFor(read and written)betterAuthEndpointPath(private):6766getBasePathLines are base
05cc3ee953. The members come from a walk of everythis.Xin each body with the TypeScript compiler API. No body has a barethis,this[...],argumentsorsuper, or athisinside afunctionor a nested class. The members a body assigns arerouteOwnershipandrouteOwnershipFor(ownsRoute).handleRequestis the one seam every better-auth request passes through on its way to the vendor handler and back. The client address is established once, then come the vendor header, the dev-onlyOrigin, the admin-session recovery out of impersonation, the request-state and actor-attribution scopes, the SCIM scope, the atomic erasure, the two refusal envelopes and the server-error log. It reassigns its ownrequestparameter, as before. Its twoawait import(…)calls (@better-auth/core/context,./scim-connection-service.js) and its twoconsole.error('[AuthManager] better-auth returned error:', …)lines move with it, text unchanged.ownsRouteanswers whether better-auth routes a request at all, from a memoized ownership table keyed on the live instance.betterAuthEndpointPathis the endpoint path both key on.isScimProtocolPathis exported in place (ruling6364006498, parameter 2)::1240becomesexport function isScimProtocolPath(…). That is the only edit to text that stays.docs/adr/0134-env-side-scim-provisioning.md:122anchors it asauth-manager.ts#isScimProtocolPath, beside#SCIM_PROTOCOL_PATH_PREFIX(:121), which it reads and which stays too../auth-manager.js, besidetype AuthManager. The one read is insidehandleRequest's body, when a request is handled. The module's top level is imports, the host interface and three function declarations, so nothing reads the binding while the two modules are first evaluated.index.tslistsauth-manager.ts's published names one by one (cut 9) and does not list it, so it is not published. The head's ESM and CJS entries do not export it, anddist/index.d.tsdoes not name it.Imports.
auth-manager.ts, because their only readers were the moved bodies:runWithAuthActorScope,SESSION_ERASURE_PATHS,envelopeVendorAdminRefusal,refuseAnonymousSession,buildBetterAuthRouteOwnership,ADMIN_SESSION_COOKIE_KEY,STOP_IMPERSONATING_PATH,withBearerAdminSessionRecovery,withVendorClientAddressandclientAddressStateEntries. The lines that also carry a name that stays keep it (setAuthActorResolver,type BetterAuthRouteOwnership,rotateCallerBearerOnImpersonation,vendorClientAddressOptions,readRequestClientAddress). The three single-name lines go.type BetterAuthRouteOwnershipstays for therouteOwnershipfield.ORGANIZATION_DELETE_PATHis imported by both files, because the global before-hook inauth-manager.ts(:2379) reads it too.Evaluation order.
session-tombstone.jsis still first reached earlier, throughobjectql-adapter.js(:70).vendor-admin-refusal-envelope.jsandanonymous-session-refusal.jshave no other importer, so they are now first reached through the new module, whose import line isauth-manager.ts's last. Each one's top level is literal constants, onenew Set([401, 403]), function declarations and two value imports:./platform-admin-gate.js, which an earlier import line ofauth-manager.tsalready reaches (admin-impersonate-endpoint.js,:95, imports it by value);@objectstack/spec/api, which nothing else inplugin-authimports by value. So its first load moves later too: in the CJS bundle its firstrequiregoes from line 4,372 to 7,535, and ESM hoists it either way.@objectstack/specdeclares"sideEffects": false, and neither module does anything outside itself, so the later order cannot be observed.The shape
.d.tslists each one. Each body is now a host literal of exactly the members that body reads, typed as aPickofRequestRoutingHost, plus.call.RequestRoutingHostlives in the new module. It names the 7 members the bodies read, each typed as the class member (AuthManager['getBasePath'], …).(...a) => this.m(...a)), so a double or spy set on the manager is still the one a body reaches.handleRequestandownsRouteaskbetterAuthEndpointPaththrough the manager rather than calling the function directly.routeOwnershipandrouteOwnershipForgo as get/set pairs.ownsRoutestores the table it builds, and the instance it built it from, on the manager. So the memo stays the manager's, and it is rebuilt when that instance is replaced, as before. The two private fields stay on the class.handleRequestandownsRouteare no longerasync. Each returns the moved async function's own promise, so a caller awaits the same promise the body settles. Building the host cannot throw, so neither now throws synchronously where it used to reject.betterAuthEndpointPathwas notasyncand stays so.handleRequestbuilds one, and each of its twobetterAuthEndpointPathcalls through the manager builds one more, so three per request;ownsRoutebuilds two.The decision-letter layer drops by one, as agreed with the seat
handleRequest's first comment citesADR-0069 D7.auth-manager.tsis an anchored file and the new module is not. Socheck-adr-anchors' decision-letter layer reads 1,978 at base05cc3ee953and 1,977 at head, and the gate exits 0 at both. That is the one letter citation in the moved text. Its other ADR citation isADR-0112, with no letter.No anchor entry is added for the new module. The
auth-manager.tsentry has two paragraphs (ADR-0093 D2, the membership reconciler; ADR-0108, the organization roles), and neither describes a moved method. The new module's header cites noADR-NNNN. So the number layer reads 52,223 citations at both, across 6,651 files at base and 6,652 at head (the new module).Path readers
No path reader needs migrating: none reads a moved line.
this.X.delete()and no elevation read.check-tenant-audit-censusexits 0 at head with no--write. There are 238 sites at base and at head, compared per site with line numbers ignored: 0 differences, 2 inauth-manager.tsat both, and 0 in the new module. So neither census document is edited.check-dispatcher-error-vocabulary.mjsandapi-error-code.pin.test.ts'sauthManagerFilesfloor: nonew APIError(sits in the moved text. The gate reads 56 sites found and 56 classified at base and at head.better-auth-schema-parity.test.tsandmanaged-extension-fields.test.tsmatchawait import('better-auth/plugins…')andawait import('@better-auth/…')plugin factories. The movedawait import('@better-auth/core/context')is not a plugin factory, and both suites pass at head.check-auth-mount-ledger.mjsandauth-manager-base-path.test.ts: no base-path literal or declaration sits in the moved text.getBasePath,configuredBasePathandrootedBasePathstay on the class.better-auth-route-ownership.ts:103,anonymous-session-refusal.ts:89,vendor-admin-refusal-envelope.ts:83, comments inauth-manager.ts) namesAuthManager.betterAuthEndpointPathorhandleRequest, which stay on the class.auth-catchall-fallthrough.test.tsandauth-catchall-yield.test.ts(route ownership and the plugin-auth 的终结式 catch-all 吞掉 /api/v1/auth/* 下别人的路由 —— console 权限层目前靠 kernel.use() 顺序才活着 #4088 yield);auth-manager-base-path.test.ts(the endpoint path under every configured spelling);oidc-discovery-sync-mount.test.ts;scim-transaction-scope.test.ts(the SCIM scope around the request).auth-manager.ts#symbolanchor in the tracked tree outside changelogs was mapped withsymbolSegmentResolutionfromscripts/symbol-anchors.mjs. There are 29, and all 29 aredeclaration-class at base, with the moved bodies blanked, and at head. None names a moved method. Two name a token the moved text carries, and each resolves on the declaration that stays:#isScimProtocolPath(ADR-0134:122; the movedhandleRequestcalls it);#AuthManager(ADR-0135:104; the moved log text spells[AuthManager]).Proof
Builds and suites ran through
os-verify-lock, and every exit code was captured before any pipe. Base05cc3ee953, head27f04eaa1c. A fullturbo run buildran first. Thenplugin-authwas built and its suite run at base, and again at head, on the same dependency dists.1. The moved text.
asyncdropped fromhandleRequestandownsRoute.handleRequestownsRoutebetterAuthEndpointPath'production'→'productio'inhandleRequest) makes that method fail (tokens and lines DIFFER), and the run exits 1. The module was restored afterwards, andcmpagainst the saved copy was clean.git diff --color-moved=plain --color-moved-ws=allow-indentation-change 05cc3ee953 27f04eaa1cover the two source files marks 226 of the 233 deleted lines and 236 of the 361 added lines as moved.asyncsignature lines,function isScimProtocolPath((nowexport function), three one-line imports that keep a name that stays, and thebuildBetterAuthRouteOwnership,line of a fourth.RequestRoutingHost, the module header, the per-function pointer docblocks and the import blocks.2.
.d.tsand exports (ruling6348891160items 1 and 2).dist/index.d.tsrawb85cb81c24e981304d02184a9ef1294be702aa7aa9df1d327f51e66be9dcfd33dist/index.d.tsnormalisedc86ed39db60c3929cf287b59d6cc9aa1a2505ccbfbaa23a624af35a3fcd11abbdist/index.d.mtsraw3561220e444c82fbf102db534ec2a274041db507ed9e38785c0993ca92b84f70dist/index.d.mtsnormalised6165644e3dfd1d779991e9bd63ceac3b9ab3dc47d910776ac5a88b4293ef6de5node -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.dist/index.d.tswith one private member made public (private betterAuthEndpointPath;→betterAuthEndpointPath;, line 2999, a one-line change) hashes rawb2c8e7ab8a562e54b2698e5eedf0fa8493704a880dd7e2f54ceac8c6963e1190and normalised67551d11baf0aa0d506ae5934c99f6148ab638c4cc856ab2e8ad918b28c38b56. Both differ from the readings above.dist/index.mjsand CJSdist/index.jseach have 159 names at base and at head, the same list (the sha256 of the sorted names is3abb126f5babb701…on all four).isScimProtocolPath,handleRequest,ownsRouteandbetterAuthEndpointPathare not among them, anddist/index.d.tsnamesisScimProtocolPathandRequestRoutingHostnowhere.dist/at both, the same list. Onlyindex.js,index.mjsand their two.mapfiles differ, as a move of code inside the bundle must.3. Tests and lint.
pnpm exec vitest run --maxWorkers=3inpackages/plugins/plugin-auth: baseTest Files 189 passed (189),Tests 3580 passed | 10 skipped (3590), exit 0. Head: the same counts, exit 0.pnpm --filter @objectstack/plugin-auth typecheck(bothtscprojects andcheck:test-typecheck): exit 0.pnpm --filter @objectstack/runtime typecheck: exit 0.eslint --no-inline-config(the rootlintscript's binary and flags) over the 2 source files: exit 0.4. The censuses, the measurement and the anchors, at base and at head.
scannedSources(730 → 731, the new module) andmeasuredAtdiffer.scripts/measure-durability-swallow-family.mjs(a measurement, not a gate): 74 members and 88 quiet rows at both, the same with line numbers ignored. Two statistics move:droppedNotAwaiteddiagnostic, 245 → 242 (delete158 → 156,update25 → 24). Measured with an instrumented scratch copy, never committed: at base,ownsRoute'stry(:6742) followedthis.getOrCreateAuth()into its same-file body and met three un-awaited write-shaped calls there (:5544,:5742,:6961). In the new module thattrycannot follow the call into another file, so it no longer meets them. They were dropped at base too, so no member moves. The othertrythat reaches the same three calls (:5097at base) still does.dispatch-gatesdoes not derive the ADR one. All six exit 0 at both:check-adr-symbol-anchors: 2,384 anchors across 142 records, 466 symbol (440 declaration, 26 literal), at both;check-adr-anchors: 81 anchored files; the decision-letter layer reads 1,978 and 1,977, as above;check-scripts-symbol-anchors: 4,070 anchors at both;check-spec-docblock-symbol-anchors: 5,075 anchors at both (both of these read in the same built tree);check-system-context-census: 148 elevation read sites at both;check-platform-checklist: 16 areas, 329 items at both.5. Gates.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 53 commands for the 2 paths. All 53 ran at27f04eaa1cafter the full build, and all 53 exited 0.--ranover the record with each exit code: 53 derived, 53 run, 0 NOT-MEASURED, a derived zero.check-governed-merges.mjs --branch HEAD: NOT governed, 594 changed lines.node scripts/docs-audit/affected-docs.mjs --json 05cc3ee953, exit 0): 2 pages,content/docs/kernel/contracts/auth-service.mdxandcontent/docs/permissions/authentication.mdx. Both namehandleRequestas theIAuthServicecontract member, which the class method still is, with the same signature. So no doc edit is owed. 0 release-owned pages.origin/mainmoved tod44c8936b1(chore(console): move the objectui pin 1d178e06ac5e → 6fb31dcc9954, past objectui#13370, so the console builds against the spec again #25455, the console pin) before the push, andgit merge-treeof this branch onto it is clean.Changeset
None:
skip-changeset, per ruling6348891160item 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