Skip to content

feat(plans): surface legacy no-account plans as 'Unassigned' (backfill not feasible) #973

Closed

Description

@cristim

Summary

Follow-up to #705. The Plans page Account global filter (#715) INNER-JOINs plan_accounts, and #743 made target_accounts required at create time. Plans created before #743 with zero plan_accounts rows ("universal" / legacy no-account plans) are therefore invisible whenever an account filter is active.

The investigated remedy was Option 1: backfill -- associate each legacy no-account plan with its recoverable account(s). After a schema + data-model investigation, backfill is not safely possible, so this issue tracks the recommended alternative instead: surface legacy no-account plans under an "Unassigned" pseudo-account in the filter.

Why backfill is not feasible

A deterministic, recoverable account source for these plans does not exist on any migrated database:

  1. The plans are already hard-deleted. Migration 000060_cleanup_universal_plans (shipped in fix(db): duplicate migration version 000057 on base (cleanup_universal_plans vs drop_user_role_to_groups) #970/fix(db): renumber duplicate migration 000057_cleanup_universal_plans to 000060 (closes #970) #971) runs DELETE FROM purchase_plans WHERE NOT EXISTS (SELECT 1 FROM plan_accounts ...). Every legacy no-account plan is permanently removed by the time any later migration could run, and its .down.sql is a documented no-op (data cannot be reconstructed from SQL). A new backfill migration (next free slot 000065) would match zero rows.
  2. The plan->account link is severed on delete. purchase_executions.plan_id and purchase_history.plan_id are both ON DELETE SET NULL. Even though those child tables carry a cloud_account_id (added in 000011), once 000060 deletes the plan their plan_id becomes NULL, so an orphaned execution/history row can no longer be attributed back to any specific plan. (a) is therefore impossible.
  3. No other plan->account reference exists. purchase_plans has no account column in any migration (000001 ... 000064); plan_accounts is the sole linkage. The plan's services JSONB has no account. (b) is impossible.
  4. Single-account fan-out is unsafe. Assigning every legacy plan to "the one cloud account" only holds in single-account deployments and would silently mis-scope plans in multi-account deployments -- the exact concern that drove 000060 to delete rather than fan-out (see ops(plans): clean up existing universal plans (DB rows with no plan_accounts entry) #742). Guessing associations is explicitly out of scope.

Inserting a backfill migration before 000060 is also impossible: 000060 is already applied on deployed DBs, and golang-migrate does not permit out-of-order versions.

Recommended alternative: "Unassigned" surfacing

Rather than guessing associations, make legacy/unscoped plans discoverable without an account chip selection:

  • Backend getPlans(): when an account filter is active, return plans matching the selected accounts via the plan_accounts join plus plans that have zero plan_accounts rows (LEFT JOIN + pa.account_id IS NULL), grouped under a synthetic "Unassigned" bucket.
  • Frontend Plans page: show an "Unassigned" group/badge so operators can find and re-scope these plans manually.

Note: on databases already migrated past 000060 there are no zero-account plans left, so this primarily protects DBs that predate the upgrade and any future regressions that reintroduce unscoped plans. It is the safe, non-destructive complement to the 000060 cleanup.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    impact/fewLimited audiencepr-createdA PR has been opened for this issue (dedup guard for the auto-PR loop)pr-mergedThe PR for this issue has been mergedpriority/p2Backlog-worthyseverity/lowMinor harmtriagedItem has been triagedtype/bugDefecturgency/this-quarterWithin the quarter

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions