Repository navigation
refactor(plugin-auth): move AuthManager's sign-in lockout path into auth-manager-sign-in-lockout.ts (#25224 cut 2) - #25307
Conversation
…uth-manager-sign-in-lockout.ts (#25224 cut 2) Seven AuthManager method bodies (assertAccountNotLocked, recordSignInOutcome, auditRefusedSignIn, resolveSignInAccountId, findSignInAccount, resolveTwoFactorAccountLockout, stampLastLogin; 173 lines) move unedited, re-indented one level, into a new module by the #23050 shape: the class keeps each method, signature and docblock, and each body becomes a host of exactly the members it reads plus a .call. config goes as a getter; methods go as late-bound closures. Nothing re-exports the module. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
…lockout bodies too (#25224 cut 2) api-error-code.pin.test.ts sums the new APIError( sites of the files AuthManager's method bodies live in. One of them (ACCOUNT_LOCKED, in assertAccountNotLocked) moved to auth-manager-sign-in-lockout.ts, so the module joins that list and the sum stays 27; without it the sum read 26 and the floor of 24 held in silence. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
…sign-in lockout bodies (#25224 cut 2) The census keys its committed site table by path, so the three engine.update('sys_user', ...) sites of recordSignInOutcome and stampLastLogin now list under auth-manager-sign-in-lockout.ts (3) and auth-manager.ts's sys_user row drops from 8 to 5. Every site keeps its object, tenancy, options and elevation reading (236 sites at base and head). What moves is how the census reaches those three: in the class their receiver was this.getDataEngine(), whose declared return type the census reads off the method in the same file; in the module it is the same call on a this typed as Pick<SignInLockoutHost, ...>, which the census does not follow, so they are placed by the object name sys_user instead (readable engine type 195 to 192, placed by object name 31 to 34). The page's two hand-written counts of erased-receiver sites follow, 41 to 44. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
…-lg3wrj Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 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 bb28e6874612af767c223710f6c031013633e1bb && git checkout bb28e6874612af767c223710f6c031013633e1bb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 829cb7791796187ca71dcc628ec049d3290161de daacbb4008ba560eb476680a08c2a96316f79e6a && git checkout -B drift-repro 829cb7791796187ca71dcc628ec049d3290161de && git merge --no-ff daacbb4008ba560eb476680a08c2a96316f79e6a
node scripts/docs-audit/affected-docs.mjs --json 829cb7791796187ca71dcc628ec049d3290161de
|
…-lg3wrj The two tenant-audit census documents conflicted with #25303's one new sys_user write. Both were taken from main and regenerated with node scripts/tenant-audit-census.mjs --write; the page's two hand-written erased-receiver counts were set to the census's figure (44). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
席位核对 · ACCEPT · 2026-10-22T23:52Z
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 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, and its entry exports the same 155 names under ESM and CJS (measured below).Cut 2 of the
auth-manager.tssplit: the sign-in lockout path (ADR-0069 D2) and the last-login stamp (D7). SevenAuthManagermethod bodies, 173 lines, move unedited (re-indented one level) into one new module,packages/plugins/plugin-auth/src/auth-manager-sign-in-lockout.ts. #25224 stays open for the later cuts.This follows the card's claim
6351729156(amending6349004758for cut 2, on the file cut 1 left: PR #25273, landed note6351216603). The shape is cut 1's, which is #23050's (landed note6136102437).Branch:
claude/project-thread-lg3wrj, the harness-pinned branch for this session, restarted fromorigin/mainafter #25273 merged. It names no issue, so the card relation is this body's first line.auth-manager.tsgoes from 10,004 to 9,897 lines. The cut is +340 / −181 over 5 files (521 changed lines, under the 3,000-line line; two of the files are the regenerated tenant-audit census). Not a governed surface (check-governed-merges.mjs --branch HEAD: 0 of the 5 paths hit the register).Why this seam
The first reading took the whole ADR-0069 enforcement tail (16 methods, base
:8852–:9951: password policy, lockout, session controls). Five of those methods (assertPasswordNotReused,recordPasswordHistory,unlockUser,enforceSessionControls,enforceConcurrentCap) hold 6 of the file's 12 query-optionsany-erasure sites (measured with ESLint itself: the file linted at a path the baseline does not list reports exactly 12, the baseline's count).scripts/query-options-erasure-baseline.jsongrandfathers erasures per file and only ever loses keys, and thequery-options/no-any-erasurerule blocks every file it does not list, so a move cannot carry a site into a new file withoutpnpm lintand the ratchet going red. Those five stay inauth-manager.ts. The fork was ruled on the card (6351993113, letter A, acknowledged in6352024441): after this cut merges, a separate sweep PR drops the file's 12 counted assertions in place (all 12, in nine methods; this paragraph's six are the ones the first reading reached) and ratchets the baseline down, and the nine methods move after it. This cut is the sign-in path's coherent remainder, and its bodies hold none (the new module lints with 0 sites;auth-manager.tskeeps its 12, so the baseline holds untouched).What moved
assertAccountNotLocked:9438–:9472config,findSignInAccount,getDataEnginerecordSignInOutcome:9497–:9542config,findSignInAccount,getDataEngine,logDurabilityDegradationauditRefusedSignIn:9561–:9586config,resolveSignInAccountIdresolveSignInAccountId:9594–:9611findSignInAccount,getDataEngine,logDurabilityDegradationfindSignInAccount:9641–:9651resolveTwoFactorAccountLockout:9682–:9691configstampLastLogin:9721–:9747getDataEngine,logDurabilityDegradationLines are base
49185cdb8a. The members are a walk of everythis.Xin each body with the TypeScript compiler API: no barethis, nothis[...], noarguments, nosuper, no assignment to athismember.No module-level declaration moves. The imports that only the moved bodies used left with them (
accountLockedEventFor,refusalCodeOf,signInClientFingerprintandsignInFailureEventForfromauth-session-audit.js, and theEngineQueryOptionstype); the new module imports each from the moduleauth-manager.tsimported it from.The shape
.d.tslists each one. Its body is now a host literal of exactly the members that body reads, typed as aPickofSignInLockoutHost, plus.call.findSignInAccountreads no member and calls its function directly.SignInLockoutHost(in the new module) names the five members the bodies read, each typed as the class member (AuthManager['getDataEngine'], …). The new 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 includesfindSignInAccountandresolveSignInAccountId, 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.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 throws synchronously where it used to reject.resolveTwoFactorAccountLockoutwas synchronous and stays so.Readers re-pointed in the same PR
api-error-code.pin.test.tsnew APIError(sites over the filesAuthManager's bodies live in (floor 24)ACCOUNT_LOCKEDthrow ofassertAccountNotLockedmoved, so the module joins the listcheck-tenant-audit-census.mjs, its committed page and counts file)auth-manager.tsupdatesys_user: 8auth-manager.ts: 5,auth-manager-sign-in-lockout.ts: 3; regenerated withnode scripts/tenant-audit-census.mjs --write, and the page's two hand-written erased-receiver counts follow (41 → 44); see belowWithout the pin edit the sum read 26 and the floor held in silence, which is the drift the card forbids. Measured as reading no moved line: the plugin-factory scans in
better-auth-schema-parity.test.tsandmanaged-extension-fields.test.ts(the module imports no factory),pnpm check:dispatcher-error-vocabulary(exit 0; no row names a stamp in a moved body), the query-options erasure ratchet (62 unswept non-test site(s) in 16 file(s), none new, key set verified against49185cd), the ADR-0093/0108 anchor ofauth-manager.ts(namessettleMembershipand the roles map, neither moved), and nodocs/qa/platform-checklistanchor or docs page names a moved method.The census reading
The census moves in one way a reviewer should see. It reads 236 engine write sites at base and at head, and every site keeps its object, tenancy, options and elevation reading (compared per site, with the new module's file name mapped back to
auth-manager.ts). What changes is how it reaches three of them: theengine.update('sys_user', ...)calls ofrecordSignInOutcome(2, base:9508and:9524) andstampLastLogin(1, base:9730).const engine = this.getDataEngine(). The census reads that through the method's declared return type in the same file (getDataEngine(): IDataEngine | undefined), so the three counted as "receiver carried a readable engine type".thistyped asPick<SignInLockoutHost, ...>, and the census does not follow a function'sthisparameter. It places the three by their literal object name,sys_user, a declared object, so none of them is lost or excused by a ledger row, and the run reports 0 unresolved and 0 unledgered receivers at base and at head.readable engine type195 → 192 andplaced by the object NAME31 → 34. The two hand-written counts the gate checks on the page (PROSE_COUNTS) are that sum, so they move 41 → 44. The corpus-scale block (not enforced) is re-measured too: 706 sources, 1 more than base for the new module.Keeping the three typed would mean editing the moved bodies (an annotation on
engine) or teaching the census to readthisparameters. The first is not a move and the second is a census logic change, so neither belongs in this PR. Later cuts that move engine writes on this host shape will show the same effect.Proof
Builds and suites ran through
os-verify-lock, every exit code captured before any pipe. Base49185cdb8a; head75983184cc(the movec204bd096c, the pin reader4ee263903a, the censusf898b1e719, then a merge oforigin/mainat5801a4f2c9, whose five commits touch noplugin-authfile and auto-merged the census page's prose). Proofs 1 to 3 were taken at4ee263903a, whoseplugin-authsource is the head's, and re-read at75983184ccafter a fullturbo run build: the suite gave the same counts and both.d.tsfiles the same raw hashes.1. The moved text. For each of the seven methods, the base class body and the new module's 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), every line is the base line minus exactly two leading spaces, the parameters and return type are the same text, and the class method keeps its docblock and signature with
asyncdropped: 7 of 7 pass, 173 body lines. An independent reading:git diff --color-moved=plain --color-moved-ws=allow-indentation-change 49185cdb8a 4ee263903aover the two files marks 163 of the 169 deleted lines and 177 of the 327 added lines as moved. The 6 other deleted lines are the sixprivate async …(signature lines (their methods stay, withoutasync); the 150 other added lines are the seven class methods' new signature lines and host literals,SignInLockoutHost, the module header, the per-function pointer docblocks and the new import.2.
.d.tsand exports (ruling6348891160items 1 and 2). Built throughturbo run buildat base andpnpm run buildof the package at head:dist/index.d.ts462694a9be58c0ed9b0bc7541828e62911a2a2c874a27d5e6cea9740ae734d4b462694a9be58c0ed9b0bc7541828e62911a2a2c874a27d5e6cea9740ae734d4b05e3e4dc5f32e40f6d16b2f48a4e132b4315a9c98b9478941b0351154e0610df05e3e4dc5f32e40f6d16b2f48a4e132b4315a9c98b9478941b0351154e0610dfdist/index.d.mtsea7e59d064510a044cf630f63b2c9f4b5cd41dd22d32b326267abfd870272fdbea7e59d064510a044cf630f63b2c9f4b5cd41dd22d32b326267abfd870272fdb80fc0552ec518819842bad5e3fab8cc24075c382a5693594a1428b9e73fd6e4880fc0552ec518819842bad5e3fab8cc24075c382a5693594a1428b9e73fd6e48node -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 assertAccountNotLocked;→assertAccountNotLocked;, line 4015, a one-line change) hashes raw5e437fc5736b14b2261fbf4976a1e7c5d47d3fadd275566e4ce8af718aabdceeand normalisedec108851cd97419575810eec1cbc85dad9a983519eeb3ee90ef07d1646f52dd1, both different from the readings above.dist/index.mjs155 names and CJSdist/index.js155 names at base and at head, the same list (sha256 of the sorted namesed3ac8b902031d30…on all four). The bundle's bytes differ (index.mjs), as a move of code inside it must.3. Tests.
pnpm exec vitest run --maxWorkers=3inpackages/plugins/plugin-auth, at head and at base in the same container:Test Files 185 passed (185),Tests 3493 passed | 10 skipped (3503)on both, VERDICT command-exit 0 on both, and the same at75983184cc.pnpm --filter @objectstack/plugin-auth typecheck(bothtscprojects andcheck:test-typecheck): exit 0.eslint --no-inline-config(the rootlintscript's flags) over the 3 files: exit 0.4. Gates.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 89 commands for the 5 paths (59 for the three source paths, 30 more for the two census documents); all 89 ran atf898b1e719and again at75983184ccafter the full build, each exit code captured before any pipe, all 89 exit 0 both times.--ran: 89 derived, 89 run, 0 NOT-MEASURED, 0 UNRUN. At4ee263903a(before the census commit) the derivation was 59 and the only two that exited 1 werenode scripts/check-tenant-audit-census.mjsand its--self-test(the per-file drift and the two prose counts above); afternode scripts/tenant-audit-census.mjs --writeand the two prose edits both exit 0, and a further--writechanges only the not-comparedMeasured on … atstamp. Measured at base and head: the census reads 236 sites with 0 unresolved and 0 unledgered receivers on both, and every site's object, tenancy, options and elevation reading is the same.check-governed-merges.mjs --branch HEAD: NOT governed, 521 changed lines. Docs drift (node scripts/docs-audit/affected-docs.mjs --json 49185cdb8a, exit 0): 6 pages, each reached through theAuthManagerclass name, thedurationSecondsname (a field of the class, not a moved line), or a literal the moved bodies carry unchanged (ACCOUNT_LOCKED,locked_until,login_failed,last_login_ip); no page restates a rule this move changes, so no page edit is owed beyond the census's own two documents.Changeset
None:
skip-changeset, per ruling6348891160item 4. The published declarations and export lists are unchanged; 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