Sigra.Upgrade.Backfill (Sigra v1.20.0)

Copy Markdown View Source

Library-resident backfill logic for mix sigra.upgrade --backfill-personal-orgs.

The host app's generated data migration (priv/repo/data_migrations/*_backfill_personal_orgs.exs) is a 10-line shim that calls run_personal_orgs/2. All batching, telemetry, and SQL logic lives here so fixes ship via mix deps.update — the host never re-runs the generator.

Idempotency guarantees (Phase 18 D-03)

Two independent layers:

  1. Selector-levelwhere: not exists(...) narrows the work set to users without a personal org on every re-run. A keyset cursor (u.id > ^last_cursor) avoids Postgres OFFSET's O(n) scan and makes crash-resume free.
  2. Insert-levelRepo.insert_all/3 with on_conflict: :nothing and conflict_target: {:unsafe_fragment, "(owner_user_id) WHERE personal = true"} catches any race with concurrent signups, collapsing duplicates silently.

Personal workspace naming (Phase 18 D-04)

  • Name — "{display_name || email_local_part || "Personal"}'s Workspace".
  • Slug — "user-#{user.id}". Opaque, immutable, PII-safe.

Telemetry

Emits [:sigra, :upgrade, :backfill, :batch] per batch with measurements:

  • :batch_index — zero-based counter
  • :batch_size — rows returned by the selector
  • :inserted — rows actually inserted (may be < batch_size on conflict)
  • :total_processed — running total across batches

A terminal [:sigra, :upgrade, :backfill, :done] event fires once when the selector returns an empty batch, with %{total_processed: n, batches: k} measurements.

Summary

Functions

Backfills a personal organization for every user that does not already have one.

Functions

run_personal_orgs(repo, opts \\ [])

@spec run_personal_orgs(
  module(),
  keyword()
) :: {:ok, non_neg_integer()}

Backfills a personal organization for every user that does not already have one.

Idempotent: safe to re-run. See module docs for guarantees.

Returns {:ok, total_inserted}.