Repository navigation
refactor(plugin-auth): move AuthManager's database hooks and identity-source stamp into auth-manager-database-hooks.ts (#25224 cut 4) - #25383
Conversation
…-source stamp bodies into auth-manager-database-hooks.ts Two method bodies move unedited: composeDatabaseHooks (the account, user and session hooks composed around a host's databaseHooks) and stampIdentitySource (the sys_user.source provenance stamp its account.create.after runs). The class keeps both methods, their docblocks and signatures, and hands each body the members it reads. The new module gets its own ADR-0093 anchor entry, and the tenant-audit census documents are regenerated. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
📓 Docs Drift CheckThis PR changes 1 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 9 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 19 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 eaee0cd46bd8ece0b77d67ff9337f34cdc064d82 && git checkout eaee0cd46bd8ece0b77d67ff9337f34cdc064d82
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 448374d3f88f052faaf1fde377a54c8c2ce0eb47 8f41da7fd7edb0cc5fc7e992fdebe5a1b85c3efb && git checkout -B drift-repro 448374d3f88f052faaf1fde377a54c8c2ce0eb47 && git merge --no-ff 8f41da7fd7edb0cc5fc7e992fdebe5a1b85c3efb
node scripts/docs-audit/affected-docs.mjs --json 448374d3f88f052faaf1fde377a54c8c2ce0eb47
|
席位核对 · ACCEPT · 2026-10-23T05:39Z
Generated by Claude Code |
Requested by Sales · project thread
Part of #25224
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160, 6353073966
The basis for the
Clause-②: noline: this is a move. No accept set, export, route, refusal or answer changes. The.d.tsbar is ruling6348891160item 1 (on #25222, covering this card).@objectstack/plugin-auth's publisheddist/index.d.tsanddist/index.d.mtsare raw byte-identical to base (0 diff lines, so the union-order normalisation has nothing to do), with the ruling's positive control. Its entry exports the same 155 names under ESM and CJS (measured below).Cut 4 of the
auth-manager.tssplit: the better-auth database hooks, and the identity-source stamp they run. TwoAuthManagermethod bodies, 406 lines, move unedited (re-indented one level) into one new module,packages/plugins/plugin-auth/src/auth-manager-database-hooks.ts. #25224 stays open for the later cuts.This follows the amended claim
6357765071, which amends6356007354on the file cut 3 left (#25362, landed note6357231692). The shape is cuts 1 to 3's, which is #23050's.Branch:
claude/project-thread-lg3wrj, the harness-pinned branch for this session, restarted fromorigin/mainfb63d396b1after #25362 merged. It names no issue, so the card relation is this body's first line.auth-manager.tsgoes from 9,592 to 9,212 lines. The cut is +538 / −418 over 5 files: 956 changed lines, under the 3,000-line line. Two of the files are the regenerated tenant-audit census, and one is the new module's ADR anchor entry. It is not a governed surface (check-governed-merges.mjs --branch HEAD: 0 of the 5 paths hit the register).Why this seam
stampIdentitySource(cloud ADR-0024 D4, thesys_user.sourceprovenance stamp) is the ninth method of ruling6351993113. Cut 3 left it for its own area. Its only caller isaccount.create.after, insidecomposeDatabaseHooks.composeDatabaseHooksis where better-auth's database hooks are assembled around a host's owndatabaseHooks. Each hook calls into another section of the class (membership settlement, self-registration grants, owner-verified staging, the IdP locale), but the composition itself is one body with one job.settleMembership,settleSelfRegistrationGrant,takeOwnerVerifiedStamp,isCreatedInThisRequest) stay for that area's cut.What moved
composeDatabaseHooks:8431–:8792audienceLogError,config,idpLocale,isCreatedInThisRequest,linkingContext,settleMembership,settleSelfRegistrationGrant,stampIdentitySource,takeOwnerVerifiedStampstampIdentitySource:8865–:8910getDataEngine,logDurabilityDegradationLines are base
fb63d396b1. The members come from a walk of everythis.Xin each body with the TypeScript compiler API. Neither body has a barethis,this[...],argumentsorsuper, athisinside afunctionor a nested class, or an assignment to athismember. Neither reads a static (AuthManager.X).composeDatabaseHooksreturns an object of arrow functions. Theirthisis the function's ownthis, which is now the host.Imports. No module-level declaration moves. Seven imported names are read only by the moved bodies, so they leave
auth-manager.ts's import lists and the new module imports each from the same place:clearUnlinkTombstone,clearUserUnlinkTombstonesandrecordUnlinkTombstone(./implicit-account-linking.js);emitAuthSessionAuditEvent,loginEventForandlogoutEventFor(./auth-session-audit.js);holdPrincipalSessionExpiry(./principal-session-mint.js).Each of the three modules stays imported by
auth-manager.tsfor other names, so the order in which modules are evaluated does not change.withSystemReadContext(./objectql-adapter.js) and theBetterAuthOptionstype are also read by code that stays, so both files import them.The shape
.d.tslists each one. Each body is now a host literal of exactly the members that body reads, typed as aPickofDatabaseHooksHost, plus.call.DatabaseHooksHostlives in the new module. It names the 11 members the bodies read, each typed as the class member (AuthManager['settleMembership'], …). The module importsAuthManageras a type only, so there is no runtime cycle.composeDatabaseHooksthe literal is namedhookHost. The method's own parameter is already namedhost(the host stack'sdatabaseHooks), and the stub passes it on:composeDatabaseHooks.call(hookHost, host).composeDatabaseHooksreturned. So nothing is copied at composition:(...a) => this.settleMembership(...a)). A test double or spy set on the manager is still the one a hook reaches. That includesstampIdentitySource, whichaccount.create.afterasks through the manager.configgoes as a getter. The class reassigns it after construction (setRuntimeBaseUrl,applyConfigPatch), and the hooks read itsdataEngine,loggerandgetAuditSinkwhen they run. ItsautoActiveOrganizationis still read once, at composition, as before.idpLocalegoes as a getter too, souser.delete.afterandsession.create.afterreach the field the manager holds when they run.stampIdentitySourceis no longerasync. It returns the moved async function's own promise, so a caller awaits the same promise the body settles. Building the host cannot throw, so the method does not now throw synchronously where it used to reject.composeDatabaseHookswas synchronous and stays so.composeDatabaseHookscall, which runs once per better-auth instance, and one perstampIdentitySourcecall, which runs on each account link.Readers re-pointed in the same PR
check:adr-anchors(scripts/adr-anchors/)auth-manager.tsentry lists ADR-0093 and ADR-0108packages__plugins__plugin-auth__src__auth-manager-database-hooks.ts.jsonlists ADR-0093 and carries the ADR-0093 D2 invariant paragraph of theauth-manager.tsentry, verbatim.check-tenant-audit-census.mjs, its committed page and counts file)auth-manager.tsupdatesys_user: 2auth-manager-database-hooks.tsupdatesys_user: 2. Regenerated withnode scripts/tenant-audit-census.mjs --write, and the page's hand-written erased-receiver figures follow (see below).Why the ADR anchor entry. The D2 paragraph names the two seams that must never disagree,
user.create.afterandsession.create.before, and both are composed in the body that moved. The new entry follows what #25223's cuts 2 to 4 did for their new modules (#25360 the latest). Per ruling6353073966item 1,scripts/adr-anchors/is not a row of the governed register.The
auth-manager.tsentry is unchanged and still holds:settleMembership, the "single assembly" its D2 paragraph names, stays in the class, as does ADR-0108's role map.docs/adr/**is not touched.Measured as reading no moved line:
api-error-code.pin.test.ts: neither body throws anAPIError. TheauthManagerFilesfloor reads the same 27 sites, and the per-file textual control finds 0 in the new module.better-auth-schema-parity.test.tsandmanaged-extension-fields.test.ts: neither body has anawait import(of a better-auth plugin.pnpm check:dispatcher-error-vocabulary: exit 0. Itsauth-manager.tsrow namesdeliverPhoneOtp, which did not move.check-auth-mount-ledger.mjsandauth-manager-base-path.test.ts: they read the base-path default, which neither body spells.UNTYPED_RECEIVERSrow forauth-manager.ts: it names the SCIM transaction'sdb, which did not move.isSystemread.stampIdentitySource's{ isSystem: true, … }is a context it builds, not a read.composeDatabaseHooksand one callsstampIdentitySource, each through the class method. All pass at head (below).auth-manager.ts#symbolanchor in the tracked tree outside changelogs was mapped withsymbolSegmentResolutionfromscripts/symbol-anchors.mjs. There are 29, in ADR-0127, ADR-0134, ADR-0135 and four platform-checklist areas, and all 29 aredeclaration-class at base, with the two bodies blanked, and at head. The one that names a moved method,access-security.json:2419#composeDatabaseHooks, resolves on the method the class keeps. These checkers were run by hand at base and at head, becausedispatch-gatesdoes not derive the ADR one:check-adr-symbol-anchors: 2,383 anchors across 142 records, 466 symbol (440 declaration, 26 literal), the same at both.check-adr-anchors: 78 anchored files at base, 79 at head (the new entry).check-scripts-symbol-anchors(4,058 anchors),check-spec-docblock-symbol-anchors(5,071 anchors),check-system-context-census(148 elevation read sites) andcheck-platform-checklist(16 areas, 329 items): the same reading at both.The census reading
The census reads 237 engine write sites at base and at head. Compared per site, with the new module's file name mapped back to
auth-manager.ts, every site keeps its object, verb, tenancy, context and elevation reading: 0 differences, and 0 unresolved and 0 unledgered receivers on both sides.What changes is how the census reaches the 2
engine.update('sys_user', …)sites ofstampIdentitySource, theenv_nativeand theidp_provisionedwrite:const engine = this.getDataEngine(). The census reads it through the method's declared return type in the same file, so the 2 counted as "receiver carried a readable engine type".thistyped asPick<DatabaseHooksHost, ...>. The census does not follow a function'sthisparameter, so it places them by their literal object name,sys_user, a declared object.readable engine type185 → 183 andplaced by the object NAME42 → 44, the only total that moves. The page's two hand-written erased-receiver counts (PROSE_COUNTS) are that sum plus the parameter and ledger rows, so they move 52 → 54. 54 of 237 is 22.8%, so "just over a fifth" stays true.composeDatabaseHooksholds no engine write. Itssys_memberlookups are reads.Measured online names the base,fb63d396b.The effect is the same as cuts 2 and 3 (3 and 8 sites there). Keeping these 2 typed would mean annotating
enginein the moved body, which is not a move, or teaching the census to readthisparameters, which is a census logic change. So neither belongs in this PR.Proof
Builds and suites ran through
os-verify-lock, and every exit code was captured before any pipe. Basefb63d396b1, head8f41da7fd7. A fullturbo run buildran first. Thenplugin-authwas built and its suite run at base, then built and run at head, on the same dependency dists.1. The moved text.
asyncdropped fromstampIdentitySource.composeDatabaseHooks360 lines, 1,613 tokens;stampIdentitySource46 lines, 263 tokens).role: 'owner'→role: 'admin'incomposeDatabaseHooks'selectActiveOrg) makes that method fail (tokens and lines DIFFER), and the run exits 1. Restored afterwards;cmpagainst the saved copy was clean.git diff --color-moved=plain --color-moved-ws=allow-indentation-change fb63d396b1 8f41da7fd7over the two source files marks 404 of the 409 deleted lines and 418 of the 522 added lines as moved. The other added lines are the stubs' host literals,DatabaseHooksHost, the module header, the per-function pointer docblocks and the import blocks; the other 5 deleted lines are four of the import names that left (emitAuthSessionAuditEvent,loginEventFor,logoutEventFor,holdPrincipalSessionExpiry; the module imports them on one line or under another module's block) andstampIdentitySource'sasyncsignature line.2.
.d.tsand exports (ruling6348891160items 1 and 2).dist/index.d.ts8a39f1035b6a64888117bb152de21865289e67bc8111d5a14ea457e0fb084d518a39f1035b6a64888117bb152de21865289e67bc8111d5a14ea457e0fb084d51486138d3045a7ae05205c64d640eb98c77a85e3ac797a0be0e9ee351e8230bca486138d3045a7ae05205c64d640eb98c77a85e3ac797a0be0e9ee351e8230bcadist/index.d.mts074a15f3d7e075752bfb5aa299151d77d013fedfae947a8bcc8d28d8446b15df074a15f3d7e075752bfb5aa299151d77d013fedfae947a8bcc8d28d8446b15df8cb244809d4e1cee802060cfe8c37ff784d6f16d341672b7e2d74700ba2e12298cb244809d4e1cee802060cfe8c37ff784d6f16d341672b7e2d74700ba2e1229node -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 stampIdentitySource;→stampIdentitySource;, line 3821, a one-line change) hashes rawae7b5fe254d6dfde9189a1ec5f556eebda8fc8c803859713d430e24f0d9c6503and normalised5546200f387e32564d475768d41a8ed51fbb2a6cd2450f8d71babceea0219912. Both differ from the readings above.dist/index.mjsand CJSdist/index.jseach have 155 names at base and at head, the same list (the sha256 of the sorted names ised3ac8b902031d30…on all four).dist/on both sides, 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 187 passed (187),Tests 3526 passed | 10 skipped (3536), VERDICT command-exit 0. Head: the same counts, VERDICT command-exit 0. The six suites that call the two methods through the manager are among them:auth-manager.test.ts,auth-session-audit.test.ts,first-session-membership-ordering.test.ts,membership-policy-setting.test.ts,user-created-in-request.test.tsanddurability-swallow-repair.test.ts.pnpm --filter @objectstack/plugin-auth typecheck(bothtscprojects andcheck:test-typecheck): exit 0.eslint --no-inline-config(the rootlintscript's binary and flags) over the 2 TypeScript files: exit 0.4. Gates.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 93 commands for the 5 paths. All 93 ran at8f41da7fd7after the full build, and all 93 exited 0.--ran: 93 derived, 93 run, 0 NOT-MEASURED.check-governed-merges.mjs --branch HEAD: NOT governed, 956 changed lines.node scripts/docs-audit/affected-docs.mjs --json fb63d396b1, exit 0): 20 hand-written and 9 release-owned pages, each reached through a literal the moved bodies carry unchanged (sys_member,sys_account,idp_provisioned). No page restates a rule this move changes, so no page edit is owed beyond the census's own two documents.auth-manager.ts, the new module, the anchor entry, the census documents or the six suites above.origin/mainhas since moved tod12e650ded(feat(spec): ExecutionStatus gains missed, a scheduled due time that passed while no kernel was ticking #25363,packages/speconly), andgit merge-treeof it with this head 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