Group balances
BALANCE#v1 in GroupData stores participant net amounts in group-currency minor
units. Expense and settlement create, edit and delete operations commit the source
row, its balance delta and notification event in one DynamoDB transaction. Existing
version/uniqueness conditions reject a repeated or stale mutation before any delta
commits. Guest takeover keeps its participant ID; bulk transfers use the same
expense and settlement mutation paths. The existing allocator determines FX,
rounding and item splits.
Initialized dashboard reads use the membership GSI query, group-head BatchGet and balance BatchGet. Group and People balance reads use the snapshot and current roster, without loading expense or settlement history. The roster keeps current zero-balance members visible; removed zero-balance members are omitted.
Rollout and migration
Deploy the new transaction writers to every stage before applying the backfill. Wait for invocations of the old Lambda code to finish (including recurring and transfer writers). New groups initialize their aggregate atomically with creation. Legacy groups keep history-based reads until explicitly migrated: a user request never publishes a legacy snapshot during a rolling deployment.
With normal AWS credentials and the repository dependencies installed:
npx tsx apps/services/scripts/backfill-group-balances.ts --table tailspend-dev-groups
npx tsx apps/services/scripts/backfill-group-balances.ts --table tailspend-dev-groups --apply
Use the actual deployed GroupData table name for beta and production. The first
command is a dry run. --apply scans active group heads and initializes missing
snapshots. It is safe to resume: initialized groups are skipped. Each migration
uses strongly consistent base-table reads and conditionally publishes against
BalanceRevision; a concurrent new writer changes that revision in the same
transaction as its source write, forcing the migration to recompute. Three busy
retries fail explicitly and can be retried later. No schema deployment or new IAM
permission is required. Do not roll back to writers that omit aggregate deltas
after initialization without disabling aggregate reads and rebuilding snapshots.
The migration and AWS read-capacity measurements must be run against the target stage; local mock tests do not claim deployment or production cost measurements.