Repository navigation
refactor(plugin-auth): move AuthManager's audience gate and membership admission into auth-manager-membership-admission.ts (#25224 cut 8) - #25423
Conversation
…p admission into auth-manager-membership-admission.ts Four method bodies move unedited: validateAudienceAdmission, the audience gate better-auth runs on every user creation; settleMembership, the membership reconciler's one assembly of inputs; and landSelfRegistrationGrant and reportUngrantedSelfRegistrant, which land the self-registration permission set or report it as not granted. The class keeps all four 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
…rant insert The self-registration grant's sys_user_permission_set insert now sits in auth-manager-membership-admission.ts, so its census row names that file. No site, verb, object, tenancy, context or total changes. The corpus-scale block (not enforced) is re-measured on the previous commit. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 6 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 9af7b7ffc9cc6b21d65a7f22218899191a1c6d63 && git checkout 9af7b7ffc9cc6b21d65a7f22218899191a1c6d63
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 925fd0772c3edde0a574285b2ca6fcbfe854179a fa0187ad1f8bae1274635d2d77a1c7ac312fe8fc && git checkout -B drift-repro 925fd0772c3edde0a574285b2ca6fcbfe854179a && git merge --no-ff fa0187ad1f8bae1274635d2d77a1c7ac312fe8fc
node scripts/docs-audit/affected-docs.mjs --json 925fd0772c3edde0a574285b2ca6fcbfe854179a
|
席位核对 · ACCEPT · 2026-10-23T13:36Z
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. 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). Ruling6364006498(Sales's ruling A on this card, for bodies that read shared unexported helpers) is cited because the claim runs under it; no body in this cut reads such a helper, so itsindex.tsname list does not land here.Cut 8 of the
auth-manager.tssplit: who may join, and what joining settles. FourAuthManagermethod bodies (163 lines) move unedited, re-indented one level, into one new module,packages/plugins/plugin-auth/src/auth-manager-membership-admission.ts. #25224 stays open for the later cuts.This follows the amended claim
6365318480, which amends6363170079on the file cut 7 left (#25404, landed note6364312052). The shape is cuts 1 to 7's, which is #23050's.Branch:
claude/project-thread-lg3wrj, the harness-pinned branch for this session, restarted fromorigin/mainf0fdd45bbcafter #25404 merged. It names no issue, so the card relation is this body's first line.auth-manager.tsgoes from 8,909 to 8,790 lines. The cut is +321 / −173 over 5 files: 494 changed lines, under the 3,000-line line. It is not a governed surface (check-governed-merges.mjs --branch HEAD: 0 of the 5 paths hit the register). Two commits: the move, and the census documents--writeregenerates for it.Why this seam
These are the audience gate and what a new user's creation settles:
validateAudienceAdmission: the gate better-auth'suser.validateUserInfocallback runs on every user creation. It asks the manager the bootstrap, invitation and operator-provisioning questions, gives the audience verdict and refusal codes, and stages the owner-verified stamp and the self-registration grant;settleMembership: the membership reconciler's one assembly of inputs, which both creation seams call, with the live membership policy;landSelfRegistrationGrant: the self-registration permission set written for the membership's organization, idempotently;reportUngrantedSelfRegistrant: the durability-level report of a grant that did not land.What stays, and why:
settleSelfRegistrationGrantstays. Its body doesthis.pendingSelfRegistrationGrants.delete(email)(:5857). The tenant-audit census reads athis.receiver's type from class property declarations only, so in the new module that call is a receiver it cannot read, andcheck-tenant-audit-censusfails with an untyped-receiver problem (measured on a draft that moved it). Placing it would need a newUNTYPED_RECEIVERSrow inscripts/tenant-audit-census.mjs, a ledger that is shrink-only. In the class, the census reads it as before.sendExistingAccountSignUpNoticestays:auth-email-locale.test.tsdescribes the auth mail surface by its call sites (see the finding at the end).private staticstay, as agreed with the seat: reading one from outside the class is TS2341, and making it public would change the.d.ts.What moved
settleMembership:4899config,getMembershipPolicyvalidateAudienceAdmission:5158audienceLogError,config,enterpriseOAuthProviderIds,getAudience,hasPendingInvitationFor,isBootstrapCreation,isOperatorProvisioning,selfRegistrationSetResolvable,stageOwnerVerifiedStamp,stageSelfRegistrationGrantlandSelfRegistrationGrant:5894configreportUngrantedSelfRegistrant:6057audienceLogErrorLines are base
f0fdd45bbc. 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 assigns to athismember or reads a module-level declaration ofauth-manager.ts.The one value read of
AuthManager.validateAudienceAdmissionreads the public staticAuthManager.OPERATOR_PROVISIONING_TICKET_FIELD(base:5183). So the new module importsAuthManagerby value from./auth-manager.js, the first cut in the series to do so. That is the only value read: the module's otherAuthManagermentions are the host interface's indexed-access types and comments. It sits inside the function body and runs only when better-auth calls the gate. The module's top level is six imports, an interface and four function declarations, so nothing reads the class while the two modules are first evaluated.Imports. These leave
auth-manager.ts, each now with no reader there, and the new module imports each from the same place:classifyCreationMethod,decideAudienceAdmission,AUDIENCE_CONFIG_ERRORandtype AudienceCreationClassfrom./audience-posture.js(the line stays for its six other names);shouldStampOwnerVerifiedAtCreation(./walled-owner-operator-stamp.js) andGRANT_SET_NAME_FIELD(./grant-set-name.js), whose import lines go;reconcileMembership(./reconcile-membership.js). The line stays fortype MembershipPolicyandtype ReconcileOutcome, the second stillsettleMembership's return type on the class.The new module also imports
withSystemReadContextfrom./objectql-adapter.js, whichauth-manager.tskeeps importing.Evaluation order. The three modules whose value import leaves are now first reached through the new module's import line, later in
auth-manager.ts's import list. Their top levels are declarations and literal constants. Every module they import (@objectstack/types,@objectstack/core,@objectstack/spec/security,./auth-actor-attribution.js) is imported byauth-manager.tsabove the new module's line. So the change in order cannot be observed.The shape
.d.tslists each one. Each body is now a host literal of exactly the members that body reads, typed as aPickofMembershipAdmissionHost, plus.call.MembershipAdmissionHostlives in the new module. It names the 11 members the bodies read, each typed as the class member (AuthManager['config'], …).(...a) => this.m(...a)), so a double or spy set on the manager is still the one a body reaches: the bootstrap, invitation and operator-provisioning probes, the two stagings andgetMembershipPolicy(a live platform setting) all stay on the class.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.asyncmethods are no longerasync. 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.reportUngrantedSelfRegistrantwas synchronous and stays so.validateAudienceAdmissionalso on the password sign-up route) and on the self-registration grant's paths, not on every request.Path readers
Migrated in this PR:
landSelfRegistrationGrantcarries one census site, thesys_user_permission_setinsert (base:5907).node scripts/tenant-audit-census.mjs --write(the second commit) moves its row indocs/audits/2026-08-tenant-audit-write-call-sites.counts.mdfromauth-manager.tsto the new file. It also re-measures the corpus-scale block in both documents, which the checker does not enforce: measured at487d57fed(the move commit), 723 tracked sources scanned (716 at the last write; 722 at base, 723 at head) and 174 same-named calls subtracted (172 at the last write; 174 at base and head). No prose figure moves, andcheck-tenant-audit-censusexits 0.find-envelope-limb-removal.test.ts's B5site:label, which namedauth-manager.tsas the file of thesys_user_permission_setexistence read. That read moves withlandSelfRegistrationGrant, so the label names the new file. It is a test-title string only; no gate or test reads it back.Measured as reading no moved line:
check-dispatcher-error-vocabulary.mjsandapi-error-code.pin.test.ts'sauthManagerFilesfloor: nonew APIError(sits in the moved bodies. The gate reads 56 sites found and 56 classified at base and at head.better-auth-schema-parity.test.tsandmanaged-extension-fields.test.ts: the imports that leave are local modules, not plugin factories, and the moved text has no dynamic import.check-auth-mount-ledger.mjsandauth-manager-base-path.test.ts: no base-path literal or declaration sits in the moved text.audience-posture.test.ts(the gate's verdicts and refusal codes);logger-receiver-detach.test.ts(the degradation report's logger receiver);find-envelope-limb-removal.test.ts(the grant's existence read);self-registration-grant-first-membership.test.ts(the grant's landing);dev-admin-seed-credential-gate.test.ts(operator provisioning through the gate; it readsAuthManager.OPERATOR_PROVISIONING_TICKET_FIELDitself);signup-existing-address-refusal.test.ts.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 4 bodies blanked, and at head. None names a moved method. Two name a token the moved text also carries, and both resolve on declarations that stay:#AuthManager(ADR-0135; the moved gate readsAuthManager.OPERATOR_PROVISIONING_TICKET_FIELD) and#isBootstrapCreation(docs/qa/platform-checklist/areas/identity-auth.json:1810; the gate callsthis.isBootstrapCreation).The decision-letter layer does not move
The seat asks each cut's report for this count at both ends.
check-adr-anchors' decision-letter layer reads 2,004 at basef0fdd45bbcand 2,004 at head, and the gate exits 0 at both. The moved text citesADR-0078without a decision letter, so only the number layer sees it move: 52,156 citations at both, across 6,632 files at base and 6,633 at head. No anchor entry is added for the new module, as for cuts 1 to 7's modules.Proof
Builds and suites ran through
os-verify-lock, and every exit code was captured before any pipe. Basef0fdd45bbc, headfa0187ad1f(the move is487d57fedd). 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 from the three that had it.settleMembershipvalidateAudienceAdmissionlandSelfRegistrationGrantreportUngrantedSelfRegistrant'sys_user_permission_set'→'sys_user_permission_sets'inlandSelfRegistrationGrant's insert) 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 f0fdd45bbc fa0187ad1fover the two source files marks 161 of the 165 deleted lines and 167 of the 313 added lines as moved.reconcileMembershipimport line and the threeasyncsignature lines.MembershipAdmissionHost, 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 validateAudienceAdmission;→validateAudienceAdmission;, line 2107, a one-line change) hashes rawa44eb00dde9098cdcb14efc26e659d46f940a6df42d3cc1759bb78ad6d0f1db8and normaliseddf635741a7c0a9d7d3f34be34bc52a32ea6406d9ada1112acd0de9f924990750. 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), 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 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, 1 in the new module at head, 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,004 at both, as above.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.5. Gates.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 89 commands for the 5 paths. All 89 ran atfa0187ad1fafter the full build, and all 89 exited 0.--ranover the record with each exit code: 89 derived, 89 run, 0 NOT-MEASURED, a derived zero.check-governed-merges.mjs --branch HEAD: NOT governed, 494 changed lines.node scripts/docs-audit/affected-docs.mjs --json f0fdd45bbc, exit 0): 12 pages, 6 of them release-owned. Every one is listed throughsys_user_permission_set, a literallandSelfRegistrationGrantcarries unchanged. No page restates a rule this move changes, so no doc edit is owed.plugin-auth/src, the census script or its two documents, or a reader above.origin/mainis stillf0fdd45bbc.Finding, outside this cut
The population comments in
plugin-auth/src/auth-email-locale.test.ts(around:44,:179and:857–:865) describe the auth mail surface as fivesendTemplatecall sites, and say no other file underplugin-auth/srccalls one. On base there are six: five inauth-manager.tsand one inauth-manager-email-sms-delivery.ts(moved there by cut 5). This cut touches neither the sends nor the comments, and the comments are not a gate. Reported on the card for the seat to triage.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