Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-auth): phone_number is better-auth's, and D7 derives the auth manager's whole plugin set - #7980
Conversation
…n derivation WIP checkpoint
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also reference the affected code. These are read-only:
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7820
Executes the maintainer's ruling of 2026-08-12 (读法 1). Both halves, quoted verbatim and untranslated:
What changed
1.
sys_user.phone_numberleavesMANAGED_EXTENSION_FIELDS.AUTH_PHONE_NUMBER_USER_FIELDSinauth-schema-config.tshas shipped the explicitphoneNumbertophone_numbermapping since #2766, so better-auth writes that exact column whenever thephoneNumberplugin is on. No write surface moves: the field was never inMANAGED_EXTENSION_EDITABLE_FIELDS(verified — the only consumer of these maps anywhere inpackages/isauth-plugin.ts, and it reads the editable map only), and the admin bulk-import path that does upsert it runs under a system context offSYS_USER_IMPORT_UPDATE_FIELDS, an unrelated list.2. D7's derivation loads the auth manager's whole plugin set. It loaded
organizationalone whilebuildPluginList()assembles fourteen factories, so for a fully mapped object likesys_userthe guard compared against a fraction of better-auth's real surface and answered green about the rest. Measured, at the pinned 1.7.0-rc.2, the widening adds tosys_user:two_factor_enabled,role,banned,ban_reason,ban_expires,phone_number,phone_number_verified; and tosys_session:impersonated_by.auth-manager.tsis not touched — the widening reads its plugin assembly, it does not change it.The half that "green on the removal" cannot prove
The ruling asks for two directions, so the red one is pinned in-repo rather than asserted in prose:
findCollisions()is now a pure function, exercised against a synthetic registry declaringtwo_factor_enabled— a column that reaches the surface only through a plugin the old derivation did not load. Under the old plugin set that registry produced no collision at all; it now reports one. Camel-case spelling and a re-declaredphone_numberare pinned the same way, plus a non-vacuity case (manager_idstays silent).auth-manager.tsfor the plugin factories it imports and reconciles them against this guard's accounting map in both directions, so a plugin added there cannot quietly stay outside the derivation.constructthunk is in the derived surface, because the thunk is what builds it — there is no way to declare a plugin covered and not load it.One finding worth flagging: do NOT copy the parity gate's call
The obvious widening — reuse
better-auth-schema-parity.test.ts'sgetAuthTables()options — is wrong here, and measurably so. Those options carry ourschema:overrides, andAUTH_INVITATION_SCHEMAdeclares ADR-0105 D8'sadditionalFields(businessUnitId,positions). Derived that way, the "better-auth surface" contains ObjectStack's own extension fields and D7 reports us colliding with ourselves — measured: two false collisions,sys_invitation.business_unit_idandsys_invitation.positions, demanding a rename plus migration of our own columns.So D7 passes the plugin set without the schema overrides, and that omission is now load-bearing rather than accidental: a dedicated assertion fails if our
additionalFieldsever appear in the derived surface, with the reason in its message. The snake_case half the mappings would have provided was already covered —toSnakeCaserecords both spellings.Composition with #7822
This is a removal plus a derivation widening, so it composes with #7822's refusal rather than fighting it: nothing was added to
MANAGED_EXTENSION_FIELDS, which doubles as the ADR-0092 D2 write whitelist. That failure message is preserved, retargeted only where it named a function this PR renamed.One deviation to flag.#7770's exemptions for
sys_two_factor/sys_device_code/sys_jwksstay registered and their "only holds while no extension field is declared" note is untouched — but their reason text said the plugins were not loaded by this call, which the widening makes false. The reasons are restated to say the models are now derived whileMODEL_TO_OBJECTstill deliberately does not map them (mapping them is a coverage expansion the ruling kept unmoved). Thesso/scimreasons had a related inaccuracy the widening exposed:getAuthTables()does emit their models, under better-auth's own names — what those plugins accept noschemaoption for is the mapping, so nothing can key them ontosys_sso_provider/sys_scim_provider. Corrected in place.Verification
Predict-then-mutate, with every direction written down before the mutation. All matched; one produced more reds than predicted (noted below).
phone_numberto the registrysys_user.phone_numberphoneNumberentryphoneNumberas a documentedskipThe extra red in row 2 is correct and not a surprise worth suppressing: narrowing the set removes
phone_numberfrom the derived surface, so the synthetic re-declaration pin stops firing too. Reported as observed rather than as predicted.pnpm --filter @objectstack/plugin-auth test— 43 files, 1046 tests, all passing (D7 file: 19 tests, up from 12).pnpm --filter @objectstack/plugin-auth typecheck— clean. That script excludes*.test.ts, so it does not read this diff's main file; the TEST_DEBT ratchet's own measurement was reproduced for this package instead: 107 rawtscerrors onorigin/main, 107 on this branch — zero added. The single diagnostic in the changed test file is the pre-existingimport.metaTS1470, which merely moved line.pnpm exec eslint --no-inline-configon both changed files — clean (no inline disables were used; they are inert on the runner).node scripts/check-nul-bytes.mjs— OK, 7279 files. Plus a direct control-byte scan of the changed files.pnpm check:adr-anchors— clean.--filter '...@objectstack/plugin-auth'— the prefix form, i.e. the 16 packages that DEPEND ON it: cli, client, client-react, cloud-connection, dogfood, driver-turso, example-crm, example-showcase, example-todo, hono, http-conformance, plugin-auth, plugin-dev, runtime, service-sms, verify): exit 0, all green. The full workspace build was run first — the sweep's first attempt produced false reds from an unbuiltdist(Cannot find package '@objectstack/plugin-auth/rate-limit-storage'), a missing-build artefact and not a signal about this diff.Generated by Claude Code