Skip to content

Adopt better-auth's account-issuer rollback: drop sys_account.issuer, retire the backfill, raise the family to 1.7.3 #17440

Description

@hotlong

Maintainer ruling 2026-09-10 on #16629: option 1 — adopt upstream's rollback. The four-dimension analysis backing it is on that card; the load-bearing findings are repeated below so this card stands alone.

Blocked-by: nothing. #16629 stays open until this lands, then closes with it.

The decision, and the two facts that decided it

better-auth 1.7.3 removed the issuer-scoped account identity outright (better-auth/better-auth#10909). createLocalAccountIssuer is deleted, accountSchema.issuer is gone, AccountKey is back to (providerId, accountId), and the account.issuer column and its unique index are gone from get-tables. There is no drop-in replacement. #16186 shipped an exact 1.7.2 pin as a stopgap; this card is the durable half.

1. issuer is a net liability today. From packages/qa/dogfood/test/showcase-demo-personas-loginable.dogfood.test.ts:

a credential row whose issuer is not the local credential issuer is invisible to findAccountByKey — sign-in then fails INVALID_EMAIL_OR_PASSWORD behind a "User not found" warn that points at the sys_user row, which is fine, instead of at the account, which is not. Four checklist items had that recorded as a knownGap, each rediscovering it.

A field whose failure mode is a silent lockout with a misleading error, which four separate people rediscovered independently. Dropping it removes that whole class.

2. Its discriminating power is close to zero here. sys_sso_provider declares { fields: ['provider_id'], unique: true } — unique within the environment. So provider_id → issuer is a function, and (provider_id, account_id) already determines what (issuer, account_id) determines.

⭐ The one thing that must be built before anything is dropped

Uniqueness moves from (issuer, account_id) to (provider_id, account_id), which changes which rows collide. Two rows sharing provider_id + account_id and differing only in issuer are legal today and collide after.

Ship a preflight that detects exactly that class and refuses, before any column is dropped. It must:

  • find rows where (provider_id, account_id) repeats with differing issuer
  • report them — count and enough identifying detail for an operator to act
  • refuse to proceed rather than letting the constraint fail during apply

⛔ Do not add the new constraint and let it blow up as the detection mechanism. An operator whose identity table half-migrated at 2am is the worst outcome this card can produce.

⛔ Do not invent a ceremony shape. This repo has one: ADR-0131 D10 — "a manual operator ceremony: plan → backup → apply → post-check, per-table, idempotent + resumable, with a boot refusal behind it, never an automatic boot step", delivered through the os migrate family. Read it and follow it. If it genuinely does not fit a column drop, say why rather than improvising.

The residual risk, which the preflight does not cover

The one real case where issuer still discriminates: a provider_id whose registration is re-pointed at a different IdP. Old rows and new rows then carry different issuers under one provider_id, and the new key treats them as one account.

⛔ This must be answered in this card, not left implicit — the honest answer is probably that re-pointing forces a rebuild of the account bindings rather than relying on a key to separate them. Whatever you conclude, state it in the PR body and pin it with a test. Dropping the column while leaving this unaddressed turns a known narrow risk into an unguarded one.

The removal surface

  • packages/platform-objects/src/identity/sys-account.object.ts — the issuer field (:177) and the { fields: ['issuer', 'account_id'], unique: true } index (:334); four generated translation bundles carry its label
  • packages/plugins/plugin-auth/src/backfill-account-issuer.ts + its test — the boot-time stamping pass exists only to serve the 1.7 model; it retires whole
  • packages/plugins/plugin-auth/src/account-issuer-parity.test.ts — pins our derivation against a @better-auth/core/social-providers surface 1.7.3 no longer has
  • auth-schema-config.ts, managed-extension-fields.ts, auth-manager.ts — map the column into better-auth's schema
  • raise the family floor to 1.7.3: @better-auth/core, better-auth, @better-auth/oauth-provider, @better-auth/scim, @better-auth/sso. ⛔ The family moves as one line@better-auth/core@1.7.2 and @better-auth/kysely-adapter@1.7.3 are mutually incompatible in both directions
  • pnpm check:vendor-export-contract (from bug(plugin-auth): published 17.1.0/17.2.0/17.3.0 float @better-auth/core to 1.7.3, which dropped createLocalAccountIssuer — a fresh objectstack dev --seed-admin never creates the system tables and never seeds #16186) asserts the declared range is exact and that our imports resolve. It will need its expectation updated in step — ⛔ and it must still be a real gate afterwards, not loosened to accommodate the bump

⚠️ showcase-demo-personas-loginable.dogfood.test.ts asserts the issuer explicitly because this trap bit four times. After the rollback that assertion is meaningless, but the test's other half — a real sign-in over the real HTTP auth route, driven with the returned token — is exactly as valuable as before. ⛔ Update it deliberately, recording why the issuer half went away. Do not delete the file.

Not in scope

  • #11627's hash-shadow-key machinery stays. It is a generic driver capability serving five UNIQUE members of the >768-char class, not something built for this index. Dropping this index does not make it dead code.

Acceptance

  • the preflight detects the collision class on a fixture that contains it, and refuses — proven by watching it refuse, not by asserting it would
  • a fresh install and an existing-data upgrade both end with working sign-in, over a real auth route
  • the re-pointed-provider answer is stated and pinned
  • check:vendor-export-contract passes against 1.7.3 and still fails when pointed at a version missing an imported symbol
  • the changeset carries its ADR-0087 disposition (pnpm check:adr-0087-registration enforces it) and states the FROM → TO mapping, per AGENTS.md

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions