ops(plans): migration to clean up universal plans (closes #742)#802
Conversation
Add migration 000057 that deletes purchase_plans rows with no plan_accounts entry (universal plans created before #743's API guard). Linked executions and history rows have their plan_id NULLed via the existing ON DELETE SET NULL FK constraints; the .down.sql is a no-op because deleted rows cannot be reconstructed from SQL alone. Includes an integration test covering 2 universal plans deleted, 1 scoped plan preserved, FK SET NULL on child rows, and idempotency.
📝 WalkthroughWalkthroughThis PR introduces database migration 000057 that permanently deletes orphaned ChangesUniversal Plans Cleanup Migration
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
PR #802 (issue #742) also claimed migration 000057 -- the two PRs were dispatched in parallel and each independently verified "000057 is free". Whichever lands second would trip the pre-commit migration-collision hook. Per project memory `project_migration_number_collisions.md`, renumber the lower-priority PR via `git mv` to the next free slot. - 000057_purchase_executions_direct_execute_audit.up.sql -> 000058_* - 000057_purchase_executions_direct_execute_audit.down.sql -> 000058_* - internal/config/types.go: three "Migration 000057" comment refs -> "Migration 000058" Verified 000058 is free on origin/feat/multicloud-web-frontend and no other open PR introduces a clashing migration.
Three PRs were dispatched in parallel and each independently picked the same "next free" migration number 000057. PR #802 (universal-plans cleanup) took 000057, PR #803 (execute-permissions) renumbered to 000058 in a follow-up, and this PR (#804, revocation) now takes 000059. - 000057_purchase_history_revocation.up.sql -> 000059_* - 000057_purchase_history_revocation.down.sql -> 000059_* - internal/config/store_postgres.go: comment ref "migration 000057" -> "migration 000059" - migration file headers updated Verified 000059 is free on origin/feat/multicloud-web-frontend and no other open PR introduces a clashing migration. Per `project_migration_number_collisions.md`.
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
Three PRs were dispatched in parallel and each independently picked the same "next free" migration number 000057. PR #802 (universal-plans cleanup) took 000057, PR #803 (execute-permissions) renumbered to 000058 in a follow-up, and this PR (#804, revocation) now takes 000059. - 000057_purchase_history_revocation.up.sql -> 000059_* - 000057_purchase_history_revocation.down.sql -> 000059_* - internal/config/store_postgres.go: comment ref "migration 000057" -> "migration 000059" - migration file headers updated Verified 000059 is free on origin/feat/multicloud-web-frontend and no other open PR introduces a clashing migration. Per `project_migration_number_collisions.md`.
…oses #289) (#803) * feat(api): execute-any/own permission gate for direct purchase execute (closes #289) Add two new RBAC verbs (execute-any:purchases, execute-own:purchases) that let privileged users bypass the approval email and execute purchases immediately from the dashboard. - auth/types.go: ActionExecuteOwn + ActionExecuteAny constants (not in DefaultUserPermissions -- opt-in only) - config/types.go: three nullable audit fields on PurchaseExecution (executed_by_user_id, executed_at, pre_approval_skip_reason) - migration 000057: ALTER TABLE purchase_executions ADD COLUMN for the three audit fields + partial index on direct-execute rows - store_postgres.go: extend INSERT, all SELECT RETURNING, and scanExecutionRows to carry the three new columns - handler_purchases.go: authorizeSessionExecuteDirect (fail-closed, nil-safe), directExecutePurchase (stamps audit fields, delegates to ApproveAndExecute), and execute_mode="direct" branch in executePurchase - handler_purchases_test.go: 4 new scenarios (no-permission 403, execute-any admin 200, execute-own owner 200, execute-own non-owner 403) - store_postgres_pgxmock_test.go: updated column/row fixtures for new columns * feat(recs): execute-mode toggle in purchase modal for privileged sessions Show a radio-group toggle ("Send for Approval" / "Execute Now") in the purchase modal when the session holds execute-any:purchases or execute-own:purchases; plain approval note otherwise. - permissions.ts: add 'execute-own' and 'execute-any' to Action union type - api/purchases.ts: accept optional executeMode param, send execute_mode in POST body - api/types.ts: add direct_execute?: boolean to PurchaseResult - recommendations.ts: currentExecuteMode state, getExecuteMode / clearExecuteMode exports, conditional toggle rendering with warning callout in openPurchaseModal - app.ts: read getExecuteMode() in handleExecutePurchase, confirmation dialog copy + toast vary by mode, clearExecuteMode on success - __tests__/execute-mode-toggle.test.ts: 6 tests covering toggle visibility by role, default state, radio-change state, and button label updates * fix(migrations): renumber direct-execute-audit 000057 -> 000058 PR #802 (issue #742) also claimed migration 000057 -- the two PRs were dispatched in parallel and each independently verified "000057 is free". Whichever lands second would trip the pre-commit migration-collision hook. Per project memory `project_migration_number_collisions.md`, renumber the lower-priority PR via `git mv` to the next free slot. - 000057_purchase_executions_direct_execute_audit.up.sql -> 000058_* - 000057_purchase_executions_direct_execute_audit.down.sql -> 000058_* - internal/config/types.go: three "Migration 000057" comment refs -> "Migration 000058" Verified 000058 is free on origin/feat/multicloud-web-frontend and no other open PR introduces a clashing migration. * fix(ci): extract buildApprovalPendingResponse to reduce executePurchase complexity on PR #803
Three PRs were dispatched in parallel and each independently picked the same "next free" migration number 000057. PR #802 (universal-plans cleanup) took 000057, PR #803 (execute-permissions) renumbered to 000058 in a follow-up, and this PR (#804, revocation) now takes 000059. - 000057_purchase_history_revocation.up.sql -> 000059_* - 000057_purchase_history_revocation.down.sql -> 000059_* - internal/config/store_postgres.go: comment ref "migration 000057" -> "migration 000059" - migration file headers updated Verified 000059 is free on origin/feat/multicloud-web-frontend and no other open PR introduces a clashing migration. Per `project_migration_number_collisions.md`.
Three PRs were dispatched in parallel and each independently picked the same "next free" migration number 000057. PR #802 (universal-plans cleanup) took 000057, PR #803 (execute-permissions) renumbered to 000058 in a follow-up, and this PR (#804, revocation) now takes 000059. - 000057_purchase_history_revocation.up.sql -> 000059_* - 000057_purchase_history_revocation.down.sql -> 000059_* - internal/config/store_postgres.go: comment ref "migration 000057" -> "migration 000059" - migration file headers updated Verified 000059 is free on origin/feat/multicloud-web-frontend and no other open PR introduces a clashing migration. Per `project_migration_number_collisions.md`.
|
@coderabbitai review |
Rate Limit Exceeded
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@internal/database/postgres/migrations/000057_cleanup_universal_plans_test.go`:
- Around line 152-153: Update the misleading comment in
000057_cleanup_universal_plans_test.go: replace or remove the line claiming
"Re-seed only the scoped plan to simulate a clean DB at version 56" since no
re-seeding occurs; instead state that after the rollback the scoped plan remains
(rollback changes schema version, not data) and that the idempotency test
verifies running migration 000057 is a no-op when universal plans are absent.
Reference the rollback call and the migration ID 000057 in the revised comment
so future maintainers understand intent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 29745840-beac-4b0c-8cf0-93dd073d0320
📒 Files selected for processing (3)
internal/database/postgres/migrations/000057_cleanup_universal_plans.down.sqlinternal/database/postgres/migrations/000057_cleanup_universal_plans.up.sqlinternal/database/postgres/migrations/000057_cleanup_universal_plans_test.go
| // Re-seed only the scoped plan to simulate a clean DB at version 56. | ||
| // The universal plans are intentionally absent (already cleaned up). |
There was a problem hiding this comment.
Clarify or remove misleading comment.
The comment states "Re-seed only the scoped plan to simulate a clean DB at version 56", but no re-seeding occurs. After the rollback on line 151, the scoped plan is already present in the database (rollback changes the schema version, not data). The idempotency test works correctly—it verifies that re-running migration 000057 when no universal plans exist is a no-op—but the comment may confuse future maintainers.
📝 Suggested fix to clarify intent
- // Re-seed only the scoped plan to simulate a clean DB at version 56.
- // The universal plans are intentionally absent (already cleaned up).
+ // Data remains unchanged: scoped plan still present, universal plans already deleted.
+ // Re-running 000057 should be a no-op (no universal plans to delete).📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Re-seed only the scoped plan to simulate a clean DB at version 56. | |
| // The universal plans are intentionally absent (already cleaned up). | |
| // Data remains unchanged: scoped plan still present, universal plans already deleted. | |
| // Re-running 000057 should be a no-op (no universal plans to delete). |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/database/postgres/migrations/000057_cleanup_universal_plans_test.go`
around lines 152 - 153, Update the misleading comment in
000057_cleanup_universal_plans_test.go: replace or remove the line claiming
"Re-seed only the scoped plan to simulate a clean DB at version 56" since no
re-seeding occurs; instead state that after the rollback the scoped plan remains
(rollback changes schema version, not data) and that the idempotency test
verifies running migration 000057 is a no-op when universal plans are absent.
Reference the rollback call and the migration ID 000057 in the revised comment
so future maintainers understand intent.
Three PRs were dispatched in parallel and each independently picked the same "next free" migration number 000057. PR #802 (universal-plans cleanup) took 000057, PR #803 (execute-permissions) renumbered to 000058 in a follow-up, and this PR (#804, revocation) now takes 000059. - 000057_purchase_history_revocation.up.sql -> 000059_* - 000057_purchase_history_revocation.down.sql -> 000059_* - internal/config/store_postgres.go: comment ref "migration 000057" -> "migration 000059" - migration file headers updated Verified 000059 is free on origin/feat/multicloud-web-frontend and no other open PR introduces a clashing migration. Per `project_migration_number_collisions.md`.
|
@coderabbitai review |
✅ Action performedReview finished.
|
…to 000060 (closes #970) (#971) Two migrations shared version 000057: drop_user_role_to_groups (applied on live DBs) and cleanup_universal_plans (from #802, not yet applied). golang- migrate requires unique version numbers. Renumber cleanup_universal_plans to 000060 (a free slot between 000059 and 000063), and update the version references in its SQL comments and test file.
Three PRs were dispatched in parallel and each independently picked the same "next free" migration number 000057. PR #802 (universal-plans cleanup) took 000057, PR #803 (execute-permissions) renumbered to 000058 in a follow-up, and this PR (#804, revocation) now takes 000059. - 000057_purchase_history_revocation.up.sql -> 000059_* - 000057_purchase_history_revocation.down.sql -> 000059_* - internal/config/store_postgres.go: comment ref "migration 000057" -> "migration 000059" - migration file headers updated Verified 000059 is free on origin/feat/multicloud-web-frontend and no other open PR introduces a clashing migration. Per `project_migration_number_collisions.md`.
Summary
000057-- the highest existing migration onorigin/feat/multicloud-web-frontendwas000056; no other open PRs carry a migration, so 000057 is uncontested.Migration shape
Referential safety:
purchase_executions.plan_idis ON DELETE SET NULL (migration 000033);purchase_history.plan_idis ON DELETE SET NULL (initial schema);plan_accounts.plan_idis ON DELETE CASCADE (migration 000011). No orphaned child rows are created.Down migration
The
.down.sqlis an intentional no-op (SELECT 1). Deleted rows cannot be recovered from SQL alone; operators must restore from a backup taken before migration 000057 ran. The file documents this explicitly rather than being silently empty.Test scope
TestMigration_CleanupUniversalPlans(integration tag) covers:Manual verification after merge
Summary by CodeRabbit