Enhancement Proposal

Proposal: User migration (bulk and just-in-time)

Adoption requires migrating users off a legacy IdP (Auth0, an old store) without a mass password reset. This specs bulk import and lazy/just-in-time migration on the directory (ADR-0011), authentication-method (ADR-0015), and federation (ADR-0019) seams — no new architecture.

Motivation

A mass forced reset on cutover is the surest way to lose users and flood support. Mature platforms migrate transparently; Vord must too, or it cannot displace an incumbent.

Design

  • Bulk import via the admin API / SCIM (ADR-0021 / ADR-0018): principals, attributes, and group memberships, plus pre-existing password hashes where the legacy algorithm is supported as a one-time legacy verifier (bcrypt / PBKDF2 / scrypt), rehashed to Argon2id on first successful login.
  • Import at scale is a resumable job, not a big POST. An import is a first-class job object on the admin API: chunked uploads, dry-run mode producing a validation report (conflicts against the realm's uniqueness index per ADR-0011, malformed rows, unsupported hash formats) before anything is written, checkpointed progress so a failed run resumes instead of restarting, per-row error reporting (skip-and-report, never abort-all), and idempotent re-submission (row identity = the external id, so a retried chunk cannot double-create). Throughput is a stated target validated against the M4 write-rate scenario (V3, architecture §10): importing a 500K-principal tenant must be an hours-order job that coexists with live login traffic under the per-tenant fairness limits (ADR-0012) — imports run as a tenant workload, not around the quota system.
  • Just-in-time / lazy migration: on first login, verify against the legacy verifier (or, for federated/social users, via federation, ADR-0019), then provision locally (ADR-0011) and rehash to Argon2id — so users migrate one login at a time, no mass reset.
  • All migration steps are events (ADR-0022) — including per-job start/checkpoint/finish and the dry-run report reference; a deprovisioned legacy user is not imported.

Alternatives considered

  • Force-reset everyone on cutover — rejected (UX disaster, churn, support load).
  • Run both systems indefinitely — rejected (operational drag; migration should converge).

Graduation criteria

Bulk import works at the stated scale: a 500K-principal dry-run produces a correct validation report; the real run checkpoints, survives a kill-and-resume, reports per-row failures, and stays within tenant fairness budgets under concurrent login load; a legacy password hash verifies once and is rehashed to Argon2id; federated users migrate via JIT; no mass reset is required.

Backward compatibility

A legacy-hash verifier is a pluggable verifier within ADR-0015; reuses directory / provisioning / federation; no architecture change.

Open questions

  • Which legacy hash formats to support; the connector for verifying against a live legacy store during JIT.