Repository navigation
refactor(plugin-auth): move AuthManager's database and auth secret inputs into auth-manager-database-secret.ts (#25224 cut 7) - #25404
Conversation
…puts into auth-manager-database-secret.ts Three method bodies move unedited: createDatabaseConfig, which hands better-auth the ObjectQL adapter factory or, with no data engine, the in-memory store keyed by model name; resolveAuthSecret, which resolves the auth secret once per manager; and generateSecret, which reads it from the environment and refuses to boot production without one. The class keeps all three methods, their docblocks and signatures, and hands each body the members it reads. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
📓 Docs Drift CheckThis PR changes 1 package(s): 8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 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 abd798bee12e780fd948be172d9d302a133afce3 && git checkout abd798bee12e780fd948be172d9d302a133afce3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2dfeb1a77254d984c701d7b4b8f1e3892f489686 83f5b0249eca67550fce142918146599a1835200 && git checkout -B drift-repro 2dfeb1a77254d984c701d7b4b8f1e3892f489686 && git merge --no-ff 83f5b0249eca67550fce142918146599a1835200
node scripts/docs-audit/affected-docs.mjs --json 2dfeb1a77254d984c701d7b4b8f1e3892f489686
|
席位核对 · ACCEPT · 2026-10-23T11:29Z
Generated by Claude Code |
Requested by Sales · project thread
Part of #25224
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160
The basis for the
Clause-②: noline: this is a move. No accept set, export, route, refusal, code 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 159 names under ESM and CJS (measured below).Cut 7 of the
auth-manager.tssplit: the database and the auth secretcreateAuthInstancehands better-auth. ThreeAuthManagermethod bodies (97 lines) move unedited, re-indented one level, into one new module,packages/plugins/plugin-auth/src/auth-manager-database-secret.ts. #25224 stays open for the later cuts.This follows the amended claim
6363170079, which amends6361212912on the file cut 6 left (#25400, landed note6362338361). The shape is cuts 1 to 6's, which is #23050's.Branch:
claude/project-thread-lg3wrj, the harness-pinned branch for this session, restarted fromorigin/main860f002f8bafter #25400 merged. It names no issue, so the card relation is this body's first line.auth-manager.tsgoes from 8,971 to 8,909 lines. The cut is +192 / −91 over 2 files: 283 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).Why this seam
These are the two inputs
createAuthInstancereads from the manager when it builds the better-auth instance (secret: this.resolveAuthSecret()anddatabase: await this.createDatabaseConfig()), and the environment read behind the first:createDatabaseConfig: the ObjectQL adapter factory, with the created-record callback that stages the id of a user the current endpoint call created, or, with no data engine, better-auth's in-memory store keyed by model name and the warning that it is not for production;resolveAuthSecret: the auth secret, resolved once per manager and memoised inresolvedAuthSecret;generateSecret: the secret fromOS_AUTH_SECRET(or its two legacy spellings), the refusal to boot production without one, and the development fallback with its warning.What stays, and why:
createAuthInstancestays. Its body readsreadDisableSignUpEnv, a module-level function thatauth-manager.tsdoes not export and thatgetPublicConfigalso reads.index.tsre-exportsauth-manager.tswhole (export * from './auth-manager.js'), so exporting it there would publish it. Per the landed note6360455119, a body that reads an unexported module helper comes back asneeds_decisionbefore it is cut.buildPluginListandresolvePasswordHasherstay for the same reason (readBooleanEnv,isWebContainerRuntime).stageUserCreatedInRequeststays: it writes the request-scoped record of created users that the session seam reads, part of the membership area, a later cut.What moved
createDatabaseConfig:4670config,stageUserCreatedInRequestresolveAuthSecret:4741config,generateSecret,resolvedAuthSecret(read and written)generateSecret:4748Lines are base
860f002f8b. 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. No body reads a static (AuthManager.X) or a module-level declaration ofauth-manager.ts. The one assignment to athismember isresolveAuthSecret's toresolvedAuthSecret.Imports. No import leaves
auth-manager.ts. The new module importsreadEnvWithDeprecationfrom@objectstack/typesandcreateObjectQLAdapterFactoryfrom./objectql-adapter.js, the placesauth-manager.tsimports them from.auth-manager.tskeeps both import lines unchanged:readEnvWithDeprecationfor its other read (:3024, the CORS origin);createObjectQLAdapterFactorybecause a docblock that stays links it ({@link createObjectQLAdapterFactory},:895), and TypeScript resolves that link through the import. The same import line bringswithSystemContextandwithSystemReadContext, which the file still calls.Evaluation order. Nothing moves. Both modules the new one imports are imported by
auth-manager.tsabove the new module's import line, so they are evaluated before it, as before. The new module's top level is three imports (one type-only), an interface and three function declarations, so the point at which it is evaluated cannot be observed.The dynamic import.
createDatabaseConfig's callback readstryGetCurrentAuthEndpointContextthroughawait import('@better-auth/core/context'), at call time. It moves with the body, unedited. A bare specifier resolves the same from both files: they sit in the same directory and are bundled into the same entry.The shape
.d.tslists each one. Each body is now a host literal of exactly the members that body reads, typed as aPickofDatabaseSecretHost, plus.call.generateSecretreads no member, so its method calls the moved function directly.DatabaseSecretHostlives in the new module. It names the 4 members the bodies read, each typed as the class member (AuthManager['config'], …). The module importsAuthManageras a type only, so there is no runtime cycle.stageUserCreatedInRequestandgenerateSecretgo as late-bound closures ((...a) => this.stageUserCreatedInRequest(...a)). The adapter's created-record callback runsstageUserCreatedInRequestinside every creating endpoint call, long after the factory was built, and still reaches the method on the manager, so a spy or double set there is still the one it reaches.configgoes as a getter. The class reassigns it after construction (setRuntimeBaseUrl,applyConfigPatch), so it is read off the manager when a body reads it, as before. The adapter factory readsthis.config.dataEnginewhencreateDatabaseConfigruns, as before.resolvedAuthSecretgoes as a get/set pair.resolveAuthSecretstores the secret on the manager, so every consumer still reads the one memo, and a manager resolves its secret once.createDatabaseConfigis no longerasync. It returns the moved async function's own promise, socreateAuthInstanceawaits the same promise the body settles. Building the host cannot throw, so the method does not now throw synchronously where it used to reject.resolveAuthSecretandgenerateSecretwere synchronous and stay so:generateSecret's production refusal still throws synchronously from the method.resolveAuthSecretalso frombuildPluginList's SCIM arm), not per request.Path readers
None needs migrating: no reader keys on a moved line. Measured as reading no moved line:
better-auth-schema-parity.test.tsandmanaged-extension-fields.test.ts. The moved text carries one dynamic import their pattern matches:const { tryGetCurrentAuthEndpointContext } = await import('@better-auth/core/context'). Both scans skip every@better-auth/corespecifier ("runtime utilities … not plugin factories, and they declare no schema"), so the factory set they derive fromauth-manager.tsandauth-manager-actor-guards.tsis the same at base and head, and the new module is not added to their file lists.check-dispatcher-error-vocabulary.mjs's file-scoped import bindings (guard ③, which reads the dynamicconst { … } = await import(…)form).tryGetCurrentAuthEndpointContextleavesauth-manager.ts's binding set. A binding matters to the gate only as the object of a member expression in anAPIErrorargument, and neither the moved text nor the new module has anew APIError(. The gate reads 56 sites found and 56 classified at base and at head; its scope grows by the one new file (3,394 → 3,395 non-test source files).api-error-code.pin.test.ts'sauthManagerFilesfloor: nonew APIError(sits in the moved bodies, so the floor reads the same sites over the same files at base and head.check-auth-mount-ledger.mjsandauth-manager-base-path.test.ts: no base-path literal or declaration sits in the moved text.isSystemread. The census reads 148 elevation read sites at base and at head.auth-manager.test.ts: "createDatabaseConfig – adapter wrapping", and "generateSecret – production secret guard (P0-1)", which callsgenerateSecret()on the manager (the boot refusal in production without a secret, the ephemeral development secret, andOS_AUTH_SECRETused even in production);user-created-in-request.test.ts(ADR-0093 D7), which awaitscreateDatabaseConfig()on the manager and drives the created-record callback;auth-route-ledger.conformance.test.ts, on the in-memory fallback.first-session-membership-ordering.test.tsdrivesstageUserCreatedInRequeston the manager directly. That method stays, and it is the one the callback's closure reaches, so the test reads the same member at base and head.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 3 bodies blanked, and at head. None names a moved method. One names a token the moved text also carries:#AuthManager(ADR-0135), in the in-memory warning's text. It resolves on the class declaration, which stays.The decision-letter layer drops by one
The seat asks each cut's report for this count at both ends.
check-adr-anchors' decision-letter layer checksADR-NNNN Dkcitations only in anchored files.auth-manager.tsis anchored; the new module is not, as cuts 1 to 6's modules are not.createDatabaseConfig's callback comment citesADR-0093 D7(the adapter as the one place that sees the created user's id inside the creating endpoint call). It moves with the body, so the layer reads one citation fewer: 2,015 at base860f002f8band 2,014 at head. Onorigin/main2dfeb1a772(below) it reads 2,005 alone and 2,004 with this cut. The gate exits 0 at both.auth-manager.ts's anchor entry names ADR-0108 and ADR-0093, and its invariant describes the role map and the D2 membership seams, not D7. Its required text stays inauth-manager.ts, and the entry still holds.ADR-0131 D7.Proof
Builds and suites ran through
os-verify-lock, and every exit code was captured before any pipe. Base860f002f8b, head83f5b0249e. 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 fromcreateDatabaseConfig.createDatabaseConfigresolveAuthSecretgenerateSecret'sys_user'→'sys_users'increateDatabaseConfig's created-record callback) 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 860f002f8b 83f5b0249eover the two source files marks 90 of the 91 deleted lines and 100 of the 192 added lines as moved.createDatabaseConfig'sasyncsignature line.DatabaseSecretHost, the module header, the per-function pointer docblocks and the import blocks.2.
.d.tsand exports (ruling6348891160items 1 and 2).dist/index.d.ts12679e13f74973b0510551a65b50808e7c3936a2d273cf2f262a9196648c073712679e13f74973b0510551a65b50808e7c3936a2d273cf2f262a9196648c0737c7a9fa25f118cfec858d770b979767744d27d9757a9ea4063faeea6238c33c01c7a9fa25f118cfec858d770b979767744d27d9757a9ea4063faeea6238c33c01dist/index.d.mtsdf57562ff950159e738d790cb7ee82a655ba0fffddafe4153bdcc50c4537336fdf57562ff950159e738d790cb7ee82a655ba0fffddafe4153bdcc50c4537336f68f824aea0947d9895b968ed46a7972eaae43ef0f2bda7e3ba9113002233ade668f824aea0947d9895b968ed46a7972eaae43ef0f2bda7e3ba9113002233ade6node -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 createDatabaseConfig;→createDatabaseConfig;, line 1842, a one-line change) hashes raw4def12da7d94e4d585fad40a829795f80a7fdfeec9d8d4936f68d770a8e3e53dand normalisedf98f154135c7fb43f7716274364a3263a4a40d749febc84653c2ad97a4e6f19d. 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).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 189 passed (189),Tests 3580 passed | 10 skipped (3590), VERDICT command-exit 0. Head: the same counts, VERDICT command-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 files: exit 0.auth-manager.tsandauth-manager-actor-guards.ts: 16 factories at base and the same 16 at head. The@better-auth/coreimports it skips are 3 at base and 2 at head; the one that left istryGetCurrentAuthEndpointContext, now in the new module.4. The census and the anchors, at base and at head.
node scripts/tenant-audit-census.mjs --json), compared per site with the new module's file name mapped back toauth-manager.ts: 238 sites on both sides, 0 in the new module, 0 per-site differences in object, verb, tenancy, context and elevation, 0 unresolved and 0 unledgered on both, and every total the same.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: 80 anchored files at both. The decision-letter layer reads 2,015 at base and 2,014 at head, as above. The number layer reads 52,147 citations at both, across 6,630 files at base and 6,631 at head (the new module carries the movedADR-0093 D7).check-scripts-symbol-anchors(4,061 anchors),check-spec-docblock-symbol-anchors(5,071 anchors, both read in the same built tree),check-system-context-census(148 elevation read sites) andcheck-platform-checklist(16 areas, 329 items): the same reading at both.check-dispatcher-error-vocabulary: 56 found and 56 classified at both.5. Gates.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 53 commands for the 2 paths. All 53 ran at83f5b0249eafter 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, 283 changed lines.node scripts/docs-audit/affected-docs.mjs --json 860f002f8b, exit 0): 11 pages, 3 of them release-owned. Every one is listed through a literal the moved text carries unchanged:OS_AUTH_SECRET,AUTH_SECRETandBETTER_AUTH_SECRETingenerateSecret, andsys_oauth_resourcein a comment increateDatabaseConfig. No page restates a rule this move changes, so no doc edit is owed.plugin-auth/srcor a reader above. Since then refactor(plugin-security): move the RLS filter layers out of security-plugin.ts (#25223 cut 7) #25401 (plugin-securityonly) merged, andorigin/mainis2dfeb1a772. On that tree with this cut applied (no conflict):2dfeb1a772passes 3 of 3;check-tenant-audit-census,check-adr-anchors,check-adr-symbol-anchors,check-platform-checklist,check-system-context-census,check-auth-mount-ledgerandcheck-dispatcher-error-vocabularyexit 0;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