mix aurora_meter.credits.migrate_lots (Aurora Meter v1.0.0)

View Source

Replays every wallet's ledger into aurora_meter_credit_lots and aurora_meter_credit_allocations, reconciles the result against the wallet's own balance row, and cuts the wallet over when the two agree exactly.

mix aurora_meter.credits.migrate_lots -r MyApp.Repo

Run it after core schema version 9 and after every node in the fleet is on a release that honours lots_enabled_at. A node on an older image ignores the column and would keep writing legacy arithmetic to a wallet the allocator now owns; no code can detect that, so it is an operator step. This task refuses to start below version 9 and prints the requirement.

It is shadow by default: it computes and reports and writes nothing except checkpoint rows. That is the run to read first.

A real cutover is permitted from this release

It was refused while AuroraMeter.Credits.reverse/4 did not take the lot path, because a paid refund on a cut-over wallet would have consumed promotional lots in spend order. Both refund calls are lot aware from repair unit R1, so --no-shadow writes. Everything else still works without it: the replay, the reconciliation, the per-wallet report and the list of wallets that cannot be migrated are all produced by the shadow run.

Shadow and real keep separate cursors

The documented sequence is a shadow run, then a real one. They resume from different checkpoint rows, so the rehearsal cannot leave the real run's cursor past the last wallet and make it examine nothing (open-findings.md X427). A run that examines no wallet never overwrites either cursor, and --retry-blocked starts from the first wallet, because every wallet it is for is behind the cursor.

Options

  • -r, --repo: the repo to work through. Defaults to the configured one.
  • --shadow / --no-shadow: shadow is the default. --no-shadow asks for a real cutover.
  • --tenant KEY: one wallet. Repeatable.
  • --batch N: wallets per aggregate checkpoint write. Default 50.
  • --no-resume: start from the first wallet instead of this mode's cursor.
  • --max-rows N: a wallet with more ledger rows than this is deferred and paused rather than migrated under a long lock. Default 50000.
  • --max-tail N: rows committed during the snapshot that will be folded in under the lock before the wallet is deferred as busy. Default 500.
  • --max-wallets N: wallets one run examines. Default 100000.
  • --retry-blocked: reprocess wallets a previous run reported blocked, and wallets an operator paused.
  • --report-only: also report wallets that are already migrated.

Exit status

Non-zero when any wallet was blocked, so a runner cannot report success while wallets were left behind. The summary names every one of them and why.

Also non-zero when the run examined no wallets while wallets still need migrating. A summary of zeros used to mean either "there was nothing to do" or "the cursor was past everything"; the run now counts the second case and fails on it rather than reporting a clean nothing.

What a blocked wallet means

Nothing was written for it and nothing was changed. It keeps working on the legacy writer indefinitely. The reason is on its checkpoint row, readable with AuroraMeter.Credits.LotMigration.status/1. See docs/upgrading-to-lots.md for what each reason means and what to do.

Summary

Functions

Prints one run's summary and decides the task's exit status.

Functions

report(arg)

@spec report({:ok, AuroraMeter.Credits.LotMigration.summary()} | {:error, term()}) ::
  :ok

Prints one run's summary and decides the task's exit status.

Public so the maintainer suite can assert the exit decision itself rather than a proxy for it: a run that left wallets behind must fail, and a test that only checked the printed text would pass for a task that exited 0.