Repository navigation
refactor(plugin-auth): move AuthManager's WebContainer password hasher into auth-manager-password-hasher.ts (#25224 cut 11) - #25524
Conversation
…R-0093 The membership reconciler's single assembly of its inputs, settleMembership, moved into auth-manager-membership-admission.ts in an earlier cut, but the ADR-0093 D2 invariant that governs it stayed anchored only on auth-manager.ts and auth-manager-database-hooks.ts. A new scripts/adr-anchors entry carries that paragraph verbatim for the module, and one comment-only paragraph in the module header names the decision, so check-adr-anchors can hold the reference there. No code changes. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
…r into auth-manager-password-hasher.ts One method body moves unedited: resolvePasswordHasher, the pure-JS scrypt hasher handed to better-auth inside StackBlitz's WebContainer. The class keeps the method, its docblock and signature; it reads no member of the class, so its body is now a plain call. isWebContainerRuntime stays in auth-manager.ts, where the request-state polyfill also reads 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): 3 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 9f03a2da63d4653305c5c704c4a4e532d2029141 && git checkout 9f03a2da63d4653305c5c704c4a4e532d2029141
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aa2dbab7b6dbf2cfae35a4c78d317fb166b977a1 08e74079306dac6d2a57609088e93ca78f8c98fa && git checkout -B drift-repro aa2dbab7b6dbf2cfae35a4c78d317fb166b977a1 && git merge --no-ff 08e74079306dac6d2a57609088e93ca78f8c98fa
node scripts/docs-audit/affected-docs.mjs --json aa2dbab7b6dbf2cfae35a4c78d317fb166b977a1
|
席位核对 · ACCEPT · 2026-10-23T19:21Z
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 plus a comment-and-anchor reader fix. 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 after each commit (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 third cut it applies to.Cut 11 of the
auth-manager.tssplit: the WebContainer password hasher, after a declared reader fix for cut 8's anchor miss. #25224 stays open for the later cuts.This follows the amended claim
6372234293, which amends6369962162on the file cut 10 left (#25508, landed note6371356601). The shape is cuts 1 to 10's, which is #23050's.Branch:
claude/project-thread-lg3wrj, the harness-pinned branch for this session, restarted fromorigin/main7d3c0139fbafter #25508 merged. It names no issue, so the card relation is this body's first line.auth-manager.tsgoes from 8,521 to 8,496 lines. The cut is +96 / −33 over 4 files: 129 changed lines, under the 3,000-line line. It is not a governed surface (check-governed-merges.mjs --branch HEAD: NOT governed). Two commits, in the order the seat set:e2b7669134chore(plugin-auth): anchor auth-manager-membership-admission.ts to ADR-009308e7407930refactor(plugin-auth): move AuthManager's WebContainer password hasher into auth-manager-password-hasher.tsCommit 1: the reader fix (comment and anchor entry only)
Cut 8 (#25423) moved
settleMembership's body intoauth-manager-membership-admission.ts. The ADR-0093 D2 invariant that governs it stayed anchored only onauth-manager.tsandauth-manager-database-hooks.ts. Cut 10's report named this as an out-of-scope finding, and the seat put the fix in this PR, as its own commit.scripts/adr-anchors/packages__plugins__plugin-auth__src__auth-manager-membership-admission.ts.json.adrsis["ADR-0093"], andinvariantis the D2 paragraph verbatim. That is the same text as the database-hooks entry's invariant and the second paragraph of theauth-manager.tsentry, in the database-hooks entry's JSON layout.settleMembershipis governed by ADR-0093 D2, and thatscripts/adr-anchors/carries the invariant for this file as it does forauth-manager.ts. The presence check needs the citation: an entry whose file never names its ADR failscheck-adr-anchors.Commit 2: the move
resolvePasswordHasher(private):3160The line is base
7d3c0139fb. The 33 proof lines are the two return-type continuation lines and the 31 body lines (:3161–:3193). The body has nothisat all.createAuthInstance(:1696, call at:1700) hands better-auth inside StackBlitz's WebContainer, whosenode:crypto.scryptpolyfill fails sign-up. Outside WebContainer it returnsundefined, so the native hasher stays.await import('@noble/hashes/scrypt.js')(still loaded only inside WebContainer), the scrypt parameters, the{saltHex}:{keyHex}storage format, and itsconsole.warn([AuthManager] WebContainer detected but pure-JS scrypt unavailable: …)..d.tslists it. The body reads no member of the class, so it becomes a plain call,return resolvePasswordHasher();, with the comment "it reads no member of this class". There is no host type.async. It returns the moved async function's own promise, and the one caller awaits it, so the caller settles on the same promise. Calling an async function cannot throw synchronously, so nothing now throws where it used to reject.isWebContainerRuntimeis exported in place (ruling6364006498, parameter 2)::262becomesexport function isWebContainerRuntime(). That is the only edit to code that stays.installWebContainerRequestStatePolyfill(:342) reads it too. The constructor installs that polyfill (:1641)../auth-manager.js. The one read is inside the moved body, when the better-auth instance is built. The two modules import each other. That is safe because the new module's top level is one import and one function declaration, 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 it lists neither this function nor the new module. The head's ESM and CJS entries do not export either name, anddist/index.d.tsnames neither.Imports. No import leaves
auth-manager.ts: the body's only reads are its own dynamic import andisWebContainerRuntime. One import line is added after cut 10's block, with a comment in the same form.The decision-letter layer: +2, as agreed with the seat
check-adr-anchors, measured at each point. The gate exits 0 at all three.7d3c0139fbe2b766913408e7407930ADR-0093 D2, the entry'sadrsid, and its invariant'sADR-0093 D2. The new file is the entry JSON.ADR-NNNN.05cc3ee953and is 82 at this base. This entry makes it 83.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.check-auth-mount-ledger.mjs: no base-path literal or declaration sits in the moved text.better-auth-schema-parity.test.tsandmanaged-extension-fields.test.tsmatchbetter-auth/plugins…and@better-auth/…dynamic imports. The moved@noble/hashes/scrypt.jsimport is neither, and both suites pass at head.rate-limit-storage-isolation.test.tswalks the import graph fromrate-limit-storage.ts, which reaches neither file. It passes at head.isWebContainerRuntime()inauth-manager.ts(jwt-key-algorithm.ts:24) still holds: the function stays there.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 body blanked, and at head. None namesresolvePasswordHasherorisWebContainerRuntime. One names a token the moved text carries,#AuthManager(ADR-0135:104; the moved warning spells[AuthManager]), and it resolves on the class declaration, which stays.Proof
Builds and suites ran through
os-verify-lock, and every exit code was captured before any pipe. Base7d3c0139fb. A fullturbo run buildran first. Thenplugin-authwas built and its suite run at base, built after commit 1, and built and run again at head, on the same dependency dists.1. The moved text.
asyncdropped.p: 1→p: 2in the scrypt parameters) makes the 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 e2b7669134 08e7407930over the source files marks 31 of the 33 deleted lines and 32 of the 83 added lines as moved.function isWebContainerRuntime()(nowexport function) and theprivate async resolvePasswordHasher(): Promise<signature line (now withoutasync).auth-manager-*.tsmodules). The rest of this section, the suites, the typechecks and the gates were measured at the pre-amend heade66a6f4a8d. Measured again after the amend: the move proof is still 1 of 1, the module's comment-free token print is identical to the pre-amend one (355 tokens), a rebuiltdist/matches the head readings byte for byte (only the two.build-input-hashfiles differ),eslintexits 0, andcheck-adr-anchorsreads as in the table.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 resolvePasswordHasher;→resolvePasswordHasher;, line 1732, a one-line change) hashes rawaadc3e91ed586bd9ff14509f08529a4b9df5b0d5ee3ee9c3e9d914e939b6a3bdand normaliseda31ac6fb32392d426d5480769cd97133ed0895014104c731f80316d2b7e0b3c9. Both differ from the readings above.dist/index.mjsand CJSdist/index.jseach have 159 names at all three points, the same list (the sha256 of the sorted names is3abb126f5babb701…on all six).isWebContainerRuntimeandresolvePasswordHasherare not among them.dist/at all three points, the same list. After commit 1 only the two.mapfiles differ, because a comment line moved code positions in the source. At headindex.js,index.mjsand their two.mapfiles differ, as a move of code inside the bundle must.tsupbundles the new module into the root entry (splitting: false).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 3 source files: exit 0.4. The censuses, the measurement and the anchors, at base and at head.
scannedSources(734 → 735, the new module) andmeasuredAtdiffer.scripts/measure-durability-swallow-family.mjs(a measurement, not a gate): 77 members and 84 quiet rows at both, the same with line numbers ignored. Only the scanned file count moves (3,459 → 3,460, the new module).tryStatements(3,633),droppedNotAwaited(242) and every other statistic are the same: the movedtrycalls no same-file helper, so its walk does not change.dispatch-gatesdoes not derive the ADR one. All six exit 0 at both (and the two ADR ones after commit 1 too):check-adr-symbol-anchors: 2,384 anchors across 142 records, 466 symbol (440 declaration, 26 literal), at both;check-adr-anchors: as in the table above;check-scripts-symbol-anchors: 4,070 anchors at both;check-spec-docblock-symbol-anchors: 5,075 anchors at both;check-system-context-census: 149 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 63 commands for the 4 paths. All 63 ran at head after the full build, and all 63 exited 0.--ranover the record with each exit code: 63 derived, 63 run, 0 NOT-MEASURED, a derived zero.check-governed-merges.mjs --branch HEAD: NOT governed, 129 changed lines.node scripts/docs-audit/affected-docs.mjs --json 7d3c0139fb, exit 0): 3 pages,content/docs/kernel/contracts/auth-service.mdx,content/docs/kernel/services-checklist.mdxandcontent/docs/permissions/authentication.mdx. All three are reached only through theAuthManagerclass name, which stays. So no doc edit is owed. 0 release-owned pages.auth-manager.ts: feat(security): an external organization role — portal users read their own identity rows and reach data only through explicit grants #25515, in four hunks (one name added to an import and two new import lines at the top, a hook arm ofcreateAuthInstance, the custom organization roles inbuildPluginList, and a new method aboveresolveActor). None reaches this cut's regions. A scratchgit merge-treeoforigin/main7a70be4365with feat(security): an external organization role — portal users read their own identity rows and reach data only through explicit grants #25515, then this branch, has no conflict at either step.origin/mainhas moved 7 commits past this base. None of them touchesplugin-authorscripts/adr-anchors/. Re-read just before this PR was opened: fix(plugin-email): Send test with "None (log only)" picked but not saved no longer delivers over the live SMTP #25519 has merged since (aa2dbab7b6,plugin-emailonly), ci: prune each Test Core Turbo seed to its own run's tasks before saving it #25523 has opened (.github/workflows/ci.ymland onescripts/ci/file only), and feat(security): an external organization role — portal users read their own identity rows and reach data only through explicit grants #25515's head is unchanged.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