# `Sigra.Upgrade.Backfill`
[🔗](https://github.com/sztheory/sigra/blob/v1.20.0/lib/sigra/upgrade/backfill.ex#L1)

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-level** — `where: 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-level** — `Repo.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.

# `run_personal_orgs`

```elixir
@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}`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
