Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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