Repository navigation
refactor(plugin-auth): move AuthManager's password, auth-gate and session policy into auth-manager-password-session-policy.ts (#25224 cut 3) - #25362
Conversation
…sion policy bodies into auth-manager-password-session-policy.ts Twelve method bodies (ADR-0069 D1/D2/D3/D4 and the per-user gate posture) move unedited; the class keeps each method, its docblock and signature, 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): 19 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 12 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. 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 27a4d449ecebe6a223cf11b2a34e9ff75ec86697 && git checkout 27a4d449ecebe6a223cf11b2a34e9ff75ec86697
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5c9ba22622f9a1ea473a164421d52f934e095919 16770e81ae474713416573d36ebff37cd10feb79 && git checkout -B drift-repro 5c9ba22622f9a1ea473a164421d52f934e095919 && git merge --no-ff 16770e81ae474713416573d36ebff37cd10feb79
node scripts/docs-audit/affected-docs.mjs --json 5c9ba22622f9a1ea473a164421d52f934e095919
|
席位核对 · ACCEPT · 2026-10-23T03:47Z
Generated by Claude Code |
Requested by Sales · project thread
Part of #25224
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160, 6351993113
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 3 of the
auth-manager.tssplit: the password, auth-gate and session policy (ADR-0069). This is the rest of the card's "password policy, lockout, auth gates, session controls" area; cut 2 took its sign-in lockout path. TwelveAuthManagermethod bodies, 481 lines, move unedited (re-indented one level) into one new module,packages/plugins/plugin-auth/src/auth-manager-password-session-policy.ts. #25224 stays open for the later cuts.This follows the amended claim
6356007354, which amends6354594310on the file the sweep PR left (#25326, landed note6355882048). The shape is cut 1's and cut 2's, which is #23050's.Branch:
claude/project-thread-lg3wrj, the harness-pinned branch for this session, restarted fromorigin/main5f4a3f088cafter #25326 merged. It names no issue, so the card relation is this body's first line.auth-manager.tsgoes from 9,927 to 9,587 lines. The cut is +731 / −481 over 5 files: 1,212 changed lines, under the 3,000-line line, and two of the files are the regenerated tenant-audit census. It is not a governed surface (check-governed-merges.mjs --branch HEAD: 0 of the 5 paths hit the register).Why this seam, and what it leaves
Ruling
6351993113held back nine methods until their counted query-options erasures were swept. #25326 did that in place, so the bodies can move without carrying a counted site into a file the baseline does not list.This cut takes eight of those nine, plus the four private bodies of the same area that they lean on:
assertPasswordComplexity,stampPasswordChangedAt,parseHashesandresolvePasswordChangeUserId.What stays in the class:
stampIdentitySource, the ninth. It is cloud ADR-0024 D4 provenance (sys_user.source), a different area, and moves with that area's cut. It no longer carries a counted site, so nothing holds it back.isAuthGateActive,computeAuthGate,noteMustChangePasswordIssuedandcheckPasswordComplexity. Each is a one-expression body no longer than the stub it would leave, andisAuthGateActiveis the per-request check its docblock calls cheap.What moved
assertPasswordComplexity(D1):8895–:8912configrefreshMustChangeCacheIfStale:8998–:9017_mustChangeCache,_mustChangeRefreshing(both written),getDataEnginerefreshOrgMfaCacheIfStale:9025–:9045_orgMfaCache,_orgMfaRefreshing(both written),config,getDataEngineresolveUserAuthGates:9159–:9251_mustChangeCache,_orgMfaCache,config,getDataEngine,refreshMustChangeCacheIfStale,refreshOrgMfaCacheIfStalestampPasswordChangedAt(D3):9258–:9285getDataEngine,logDurabilityDegradationparseHashes:9291–:9299resolvePasswordChangeUserId:9324–:9353resolveActorassertPasswordNotReused(D3):9362–:9408config,getDataEngine,parseHashesrecordPasswordHistory(D3):9414–:9459config,getDataEngine,logDurabilityDegradation,parseHashesunlockUser(D2):9685–:9731getDataEngine,logDurabilityDegradationenforceSessionControls(D4):9740–:9818config,getDataEngine,logDurabilityDegradationenforceConcurrentCap(D4):9825–:9874config,getDataEngine,logDurabilityDegradationLines are base
5f4a3f088c. The members come from a walk of everythis.Xin each body with the TypeScript compiler API. No body has a barethis,this[...],argumentsorsuper. Only the two cache refreshes assign athismember, and only their own cache fields.No module-level declaration moves. The two imports the moved bodies use (
AuthGatefrom@objectstack/core,recoverInternalFieldsForSystemReadfrom./internal-field-readback.js) are also used by code that stays, soauth-manager.tskeeps them. The new module imports each from the same place.The shape
.d.tslists each one. Each body is now a host literal of exactly the members that body reads, typed as aPickofPasswordSessionPolicyHost, plus.call.parseHashesreads no member and calls its function directly.PasswordSessionPolicyHostlives in the new module. It names the 11 members the bodies read, each typed as the class member (AuthManager['getDataEngine'], …). The module importsAuthManageras a type only, so there is no runtime cycle.(...a) => this.getDataEngine(...a)), so a test double or spy set on the manager is still the one a body reaches. That includesparseHashesand the two cache refreshes, which bodies here ask through the manager.configgoes as a getter. The class reassigns it after construction (setBaseUrl,applyConfigPatch), and the bodies read it after anawait._orgMfaCache,_orgMfaRefreshing,_mustChangeCacheand_mustChangeRefreshinggo as getters. In the two refreshes that assign them, they go as a get/set pair, whose setter writes the manager's own field through a closure ((v: AuthManager['_mustChangeCache']) => { this._mustChangeCache = v; }). The refreshes write from a background task that settles after the method returned, and the class'sisAuthGateActiveandnoteMustChangePasswordIssuedread and write the same fields. So every read and every write lands on the field, never on a copy.async. 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 no method now throws synchronously where it used to reject.refreshMustChangeCacheIfStale,refreshOrgMfaCacheIfStaleandparseHasheswere synchronous and stay so.resolveUserAuthGates,enforceSessionControlsand the two refreshes run per request on the session path, so that is one allocation per call there. This is the same per-call cost cut 1 and cut 2 put on their request-path methods.Readers re-pointed in the same PR
api-error-code.pin.test.tsnew APIError(sites over the filesAuthManager's bodies live in (floor 24)PASSWORD_POLICY_VIOLATIONthrow ofassertPasswordComplexityand thePASSWORD_REUSEthrow ofassertPasswordNotReusedmoved, so the module joins the list.check-tenant-audit-census.mjs, its committed page and counts file)auth-manager.tsupdate:sys_user5,sys_session3,sys_account1,sys_two_factor1auth-manager.ts:sys_user2.auth-manager-password-session-policy.ts:sys_user3,sys_session3,sys_account1,sys_two_factor1. Regenerated withnode scripts/tenant-audit-census.mjs --write, and the page's hand-written erased-receiver figures follow (see below).Without the pin edit the sum would read 25 and the floor of 24 would hold in silence, which is the drift the card forbids.
Measured as reading no moved line:
better-auth-schema-parity.test.tsandmanaged-extension-fields.test.ts: the moved bodies import onlybetter-auth/api, which the scan pattern does not match.pnpm check:dispatcher-error-vocabulary: exit 0. Itsauth-manager.tsrow namesdeliverPhoneOtp, which did not move.pnpm check:durability-log-level: exit 0.engine.update(…, { context: SYSTEM_CTX } as any)casts move as written; the ratchet does not count them.check-adr-symbol-anchors(2,383 anchors, 466 symbol),check-adr-anchors,check-scripts-symbol-anchors,check-spec-docblock-symbol-anchors,check-system-context-censusandcheck-platform-checklist, all exit 0. Its one anchor into this region,auth-manager.ts#computeAuthGate, names a method the class keeps. These checkers were run by hand:dispatch-gatesdoes not derive the ADR one.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 8
engine.updatesites in the moved bodies:resolveUserAuthGatessys_userstampPasswordChangedAtsys_userrecordPasswordHistorysys_accountunlockUsersys_user,sys_two_factorenforceSessionControlssys_sessionenforceConcurrentCapsys_sessionconst engine = this.getDataEngine(). The census reads it through the method's declared return type in the same file, so the 8 counted as "receiver carried a readable engine type".thistyped asPick<PasswordSessionPolicyHost, ...>. The census does not follow a function'sthisparameter, so it places them by their literal object names, all of them declared objects.readable engine type193 → 185 andplaced by the object NAME34 → 42, 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 44 → 52. 52 of 237 is 21.9%, so the sentence that called 44 "just under a fifth of the population" now says "just over a fifth".75983184c.The effect is the same as cut 2's (3 sites there). Keeping these 8 typed would mean annotating
enginein the moved bodies, 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. Base5f4a3f088c, head16770e81ae. 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.60_000→60_001inrefreshMustChangeCacheIfStale) makes that method fail (tokens and lines DIFFER), and the run exits 1. Restored afterwards;git diff HEADwas empty.git diff --color-moved=plain --color-moved-ws=allow-indentation-change 5f4a3f088c 16770e81aeover the two source files marks 457 of the 466 deleted lines and 485 of the 710 added lines as moved. The other added lines are the stubs' host literals,PasswordSessionPolicyHost, the module header, the per-function pointer docblocks and the new import.2.
.d.tsand exports (ruling6348891160items 1 and 2).dist/index.d.ts4bc04c66f724c79782493249820b1b7d9bff7c826931ce6b362d325af87978914bc04c66f724c79782493249820b1b7d9bff7c826931ce6b362d325af87978913c8612c88f3f41c963c243e91c2523d2754b2accb4a4c2ba52a5627fd6eaa1e53c8612c88f3f41c963c243e91c2523d2754b2accb4a4c2ba52a5627fd6eaa1e5dist/index.d.mtsd70d7d8063053de2d39bd0dbae06423bbe0ef1dcecf5130b867c6f4d79a0c8cdd70d7d8063053de2d39bd0dbae06423bbe0ef1dcecf5130b867c6f4d79a0c8cd51510aad856bbacd24757d1c62dc7273bfd6313ed543c8b1f1e86195fc2c7cd051510aad856bbacd24757d1c62dc7273bfd6313ed543c8b1f1e86195fc2c7cd0node -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 assertPasswordNotReused;→assertPasswordNotReused;, line 4001, a one-line change) hashes raw7d3450b84103f5d80cfbc8a2e4391ee22919d8137744c6a4550a1b237bcc5115and normalised56cff0cedafef19e8cf0e559a612c61d84b48aef3134c6c01d977887a8ef061e. 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 3525 passed | 10 skipped (3535), VERDICT command-exit 0. Head: the same counts, VERDICT command-exit 0.pnpm --filter @objectstack/plugin-auth typecheck(bothtscprojects andcheck:test-typecheck): exit 0.eslint --no-inline-config(the rootlintscript's binary and flags) over the 3 TypeScript files: exit 0.4. Gates.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 89 commands for the 5 paths. All 89 ran at16770e81aeafter the full build, and all 89 exited 0.--ran: 89 derived, 89 run, 0 NOT-MEASURED, 0 UNRUN.check-governed-merges.mjs --branch HEAD: NOT governed, 1,212 changed lines.node scripts/docs-audit/affected-docs.mjs --json 5f4a3f088c, exit 0): 31 pages, 12 of them release-owned. Every page is reached through a literal the moved bodies carry unchanged (sys_session,sys_account,sys_organization,sys_two_factor,expires_at,revoked_at,previous_password_hashes,must_change_password,MFA_REQUIRED,PASSWORD_EXPIRED,PASSWORD_RESET_REQUIRED), or throughresolveActor, which the class keeps (system-context.mdx, whose census reads the same at base and head). No page restates a rule this move changes, so no page edit is owed beyond the census's own two documents.auth-manager.ts, at the import block and near:8011, outside every moved body.git merge-treeof this branch with feat(spec, platform-objects): declare features.cloudManaged with a hide-when-on gate, and hide the six doors that add people while it is on #25309's 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