aios-core 2.1.6 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.aios-core/core/README.md +229 -229
- package/.aios-core/core/data/agent-config-requirements.yaml +368 -368
- package/.aios-core/core/data/aios-kb.md +923 -923
- package/.aios-core/core/data/workflow-patterns.yaml +267 -267
- package/.aios-core/core/docs/SHARD-TRANSLATION-GUIDE.md +335 -335
- package/.aios-core/core/docs/component-creation-guide.md +457 -457
- package/.aios-core/core/docs/session-update-pattern.md +307 -307
- package/.aios-core/core/docs/template-syntax.md +266 -266
- package/.aios-core/core/docs/troubleshooting-guide.md +624 -624
- package/.aios-core/core/elicitation/elicitation-engine.js +1 -1
- package/.aios-core/core/index.esm.js +42 -42
- package/.aios-core/core/index.js +1 -1
- package/.aios-core/core/migration/migration-config.yaml +83 -83
- package/.aios-core/core/migration/module-mapping.yaml +89 -89
- package/.aios-core/core/quality-gates/layer2-pr-automation.js +1 -1
- package/.aios-core/core/quality-gates/quality-gate-config.yaml +86 -86
- package/.aios-core/core/registry/README.md +179 -179
- package/.aios-core/core/utils/security-utils.js +1 -1
- package/.aios-core/core-config.yaml +391 -382
- package/.aios-core/data/agent-config-requirements.yaml +368 -368
- package/.aios-core/data/aios-kb.md +923 -923
- package/.aios-core/data/technical-preferences.md +3 -3
- package/.aios-core/data/workflow-patterns.yaml +267 -267
- package/.aios-core/development/README.md +142 -142
- package/.aios-core/development/agent-teams/team-all.yaml +15 -15
- package/.aios-core/development/agent-teams/team-fullstack.yaml +18 -18
- package/.aios-core/development/agent-teams/team-ide-minimal.yaml +10 -10
- package/.aios-core/development/agent-teams/team-no-ui.yaml +13 -13
- package/.aios-core/development/agent-teams/team-qa-focused.yaml +155 -155
- package/.aios-core/development/agents/aios-master.md +339 -339
- package/.aios-core/development/agents/analyst.md +195 -195
- package/.aios-core/development/agents/architect.md +359 -359
- package/.aios-core/development/agents/data-engineer.md +468 -468
- package/.aios-core/development/agents/dev.md +390 -390
- package/.aios-core/development/agents/devops.md +398 -398
- package/.aios-core/development/agents/pm.md +198 -198
- package/.aios-core/development/agents/po.md +256 -256
- package/.aios-core/development/agents/qa.md +312 -312
- package/.aios-core/development/agents/sm.md +220 -220
- package/.aios-core/development/agents/ux-design-expert.md +451 -451
- package/.aios-core/development/scripts/greeting-config-cli.js +85 -85
- package/.aios-core/development/tasks/add-mcp.md +319 -319
- package/.aios-core/development/tasks/advanced-elicitation.md +318 -318
- package/.aios-core/development/tasks/analyst-facilitate-brainstorming.md +341 -341
- package/.aios-core/development/tasks/analyze-framework.md +696 -696
- package/.aios-core/development/tasks/analyze-performance.md +637 -637
- package/.aios-core/development/tasks/apply-qa-fixes.md +340 -340
- package/.aios-core/development/tasks/architect-analyze-impact.md +826 -826
- package/.aios-core/development/tasks/audit-codebase.md +429 -429
- package/.aios-core/development/tasks/audit-tailwind-config.md +270 -270
- package/.aios-core/development/tasks/audit-utilities.md +358 -358
- package/.aios-core/development/tasks/bootstrap-shadcn-library.md +286 -286
- package/.aios-core/development/tasks/brownfield-create-epic.md +485 -485
- package/.aios-core/development/tasks/brownfield-create-story.md +356 -356
- package/.aios-core/development/tasks/build-component.md +478 -478
- package/.aios-core/development/tasks/calculate-roi.md +455 -455
- package/.aios-core/development/tasks/ci-cd-configuration.md +764 -764
- package/.aios-core/development/tasks/cleanup-utilities.md +670 -670
- package/.aios-core/development/tasks/collaborative-edit.md +1108 -1108
- package/.aios-core/development/tasks/compose-molecule.md +284 -284
- package/.aios-core/development/tasks/consolidate-patterns.md +414 -414
- package/.aios-core/development/tasks/correct-course.md +279 -279
- package/.aios-core/development/tasks/create-agent.md +321 -321
- package/.aios-core/development/tasks/create-brownfield-story.md +726 -726
- package/.aios-core/development/tasks/create-deep-research-prompt.md +498 -498
- package/.aios-core/development/tasks/create-doc.md +316 -316
- package/.aios-core/development/tasks/create-next-story.md +774 -774
- package/.aios-core/development/tasks/create-suite.md +283 -283
- package/.aios-core/development/tasks/create-task.md +371 -371
- package/.aios-core/development/tasks/create-workflow.md +370 -370
- package/.aios-core/development/tasks/db-analyze-hotpaths.md +572 -572
- package/.aios-core/development/tasks/db-apply-migration.md +381 -381
- package/.aios-core/development/tasks/db-bootstrap.md +642 -642
- package/.aios-core/development/tasks/db-domain-modeling.md +693 -693
- package/.aios-core/development/tasks/db-dry-run.md +293 -293
- package/.aios-core/development/tasks/db-env-check.md +260 -260
- package/.aios-core/development/tasks/db-expansion-pack-integration.md +663 -663
- package/.aios-core/development/tasks/db-explain.md +631 -631
- package/.aios-core/development/tasks/db-impersonate.md +495 -495
- package/.aios-core/development/tasks/db-load-csv.md +593 -593
- package/.aios-core/development/tasks/db-policy-apply.md +653 -653
- package/.aios-core/development/tasks/db-rls-audit.md +411 -411
- package/.aios-core/development/tasks/db-rollback.md +739 -739
- package/.aios-core/development/tasks/db-run-sql.md +613 -613
- package/.aios-core/development/tasks/db-schema-audit.md +1011 -1011
- package/.aios-core/development/tasks/db-seed.md +390 -390
- package/.aios-core/development/tasks/db-smoke-test.md +351 -351
- package/.aios-core/development/tasks/db-snapshot.md +569 -569
- package/.aios-core/development/tasks/db-supabase-setup.md +712 -712
- package/.aios-core/development/tasks/db-verify-order.md +515 -515
- package/.aios-core/development/tasks/deprecate-component.md +956 -956
- package/.aios-core/development/tasks/dev-apply-qa-fixes.md +318 -318
- package/.aios-core/development/tasks/dev-backlog-debt.md +469 -469
- package/.aios-core/development/tasks/dev-develop-story.md +846 -846
- package/.aios-core/development/tasks/dev-improve-code-quality.md +872 -872
- package/.aios-core/development/tasks/dev-optimize-performance.md +1033 -1033
- package/.aios-core/development/tasks/dev-suggest-refactoring.md +870 -870
- package/.aios-core/development/tasks/dev-validate-next-story.md +348 -348
- package/.aios-core/development/tasks/document-project.md +552 -552
- package/.aios-core/development/tasks/environment-bootstrap.md +1311 -1311
- package/.aios-core/development/tasks/execute-checklist.md +301 -301
- package/.aios-core/development/tasks/export-design-tokens-dtcg.md +274 -274
- package/.aios-core/development/tasks/extend-pattern.md +269 -269
- package/.aios-core/development/tasks/extract-tokens.md +467 -467
- package/.aios-core/development/tasks/facilitate-brainstorming-session.md +518 -518
- package/.aios-core/development/tasks/generate-ai-frontend-prompt.md +260 -260
- package/.aios-core/development/tasks/generate-documentation.md +284 -284
- package/.aios-core/development/tasks/generate-migration-strategy.md +522 -522
- package/.aios-core/development/tasks/generate-shock-report.md +501 -501
- package/.aios-core/development/tasks/github-devops-github-pr-automation.md +427 -427
- package/.aios-core/development/tasks/github-devops-pre-push-quality-gate.md +733 -733
- package/.aios-core/development/tasks/github-devops-repository-cleanup.md +374 -374
- package/.aios-core/development/tasks/github-devops-version-management.md +483 -483
- package/.aios-core/development/tasks/improve-self.md +822 -822
- package/.aios-core/development/tasks/index-docs.md +387 -387
- package/.aios-core/development/tasks/init-project-status.md +506 -506
- package/.aios-core/development/tasks/integrate-expansion-pack.md +314 -314
- package/.aios-core/development/tasks/kb-mode-interaction.md +283 -283
- package/.aios-core/development/tasks/learn-patterns.md +900 -900
- package/.aios-core/development/tasks/mcp-workflow.md +437 -437
- package/.aios-core/development/tasks/modify-agent.md +381 -381
- package/.aios-core/development/tasks/modify-task.md +424 -424
- package/.aios-core/development/tasks/modify-workflow.md +465 -465
- package/.aios-core/development/tasks/po-backlog-add.md +370 -370
- package/.aios-core/development/tasks/po-manage-story-backlog.md +523 -523
- package/.aios-core/development/tasks/po-pull-story-from-clickup.md +540 -540
- package/.aios-core/development/tasks/po-pull-story.md +316 -316
- package/.aios-core/development/tasks/po-stories-index.md +351 -351
- package/.aios-core/development/tasks/po-sync-story-to-clickup.md +457 -457
- package/.aios-core/development/tasks/po-sync-story.md +303 -303
- package/.aios-core/development/tasks/pr-automation.md +701 -701
- package/.aios-core/development/tasks/propose-modification.md +842 -842
- package/.aios-core/development/tasks/qa-backlog-add-followup.md +425 -425
- package/.aios-core/development/tasks/qa-gate.md +373 -373
- package/.aios-core/development/tasks/qa-generate-tests.md +1174 -1174
- package/.aios-core/development/tasks/qa-nfr-assess.md +557 -557
- package/.aios-core/development/tasks/qa-review-proposal.md +1157 -1157
- package/.aios-core/development/tasks/qa-review-story.md +682 -682
- package/.aios-core/development/tasks/qa-risk-profile.md +566 -566
- package/.aios-core/development/tasks/qa-run-tests.md +277 -277
- package/.aios-core/development/tasks/qa-test-design.md +387 -387
- package/.aios-core/development/tasks/qa-trace-requirements.md +476 -476
- package/.aios-core/development/tasks/release-management.md +723 -723
- package/.aios-core/development/tasks/security-audit.md +554 -554
- package/.aios-core/development/tasks/security-scan.md +790 -790
- package/.aios-core/development/tasks/setup-database.md +741 -741
- package/.aios-core/development/tasks/setup-design-system.md +462 -462
- package/.aios-core/development/tasks/setup-github.md +874 -874
- package/.aios-core/development/tasks/setup-llm-routing.md +1 -1
- package/.aios-core/development/tasks/setup-mcp-docker.md +584 -584
- package/.aios-core/development/tasks/setup-project-docs.md +1 -1
- package/.aios-core/development/tasks/shard-doc.md +537 -537
- package/.aios-core/development/tasks/sm-create-next-story.md +480 -480
- package/.aios-core/development/tasks/sync-documentation.md +864 -864
- package/.aios-core/development/tasks/tailwind-upgrade.md +294 -294
- package/.aios-core/development/tasks/test-as-user.md +621 -621
- package/.aios-core/development/tasks/test-validation-task.md +171 -171
- package/.aios-core/development/tasks/undo-last.md +346 -346
- package/.aios-core/development/tasks/update-manifest.md +409 -409
- package/.aios-core/development/tasks/ux-create-wireframe.md +617 -617
- package/.aios-core/development/tasks/ux-ds-scan-artifact.md +672 -672
- package/.aios-core/development/tasks/ux-user-research.md +559 -559
- package/.aios-core/development/tasks/validate-next-story.md +422 -422
- package/.aios-core/development/workflows/README.md +83 -83
- package/.aios-core/development/workflows/brownfield-fullstack.yaml +297 -297
- package/.aios-core/development/workflows/brownfield-service.yaml +187 -187
- package/.aios-core/development/workflows/brownfield-ui.yaml +197 -197
- package/.aios-core/development/workflows/greenfield-fullstack.yaml +333 -333
- package/.aios-core/development/workflows/greenfield-service.yaml +206 -206
- package/.aios-core/development/workflows/greenfield-ui.yaml +235 -235
- package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +335 -335
- package/.aios-core/docs/component-creation-guide.md +457 -457
- package/.aios-core/docs/session-update-pattern.md +307 -307
- package/.aios-core/docs/standards/AGENT-PERSONALIZATION-STANDARD-V1.md +572 -572
- package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-QUICK-REFERENCE.md +185 -185
- package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-V2.1.md +354 -354
- package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +1963 -1963
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-COMPLETE.md +821 -821
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +1190 -1190
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +439 -439
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.2-SUMMARY.md +1339 -1339
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +5398 -5398
- package/.aios-core/docs/standards/EXECUTOR-DECISION-TREE.md +697 -697
- package/.aios-core/docs/standards/OPEN-SOURCE-VS-SERVICE-DIFFERENCES.md +511 -511
- package/.aios-core/docs/standards/QUALITY-GATES-SPECIFICATION.md +556 -556
- package/.aios-core/docs/standards/STANDARDS-INDEX.md +210 -210
- package/.aios-core/docs/standards/STORY-TEMPLATE-V2-SPECIFICATION.md +550 -550
- package/.aios-core/docs/standards/TASK-FORMAT-SPECIFICATION-V1.md +1414 -1414
- package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +523 -523
- package/.aios-core/docs/template-syntax.md +266 -266
- package/.aios-core/docs/troubleshooting-guide.md +624 -624
- package/.aios-core/index.esm.js +15 -15
- package/.aios-core/index.js +1 -1
- package/.aios-core/infrastructure/README.md +126 -126
- package/.aios-core/infrastructure/integrations/pm-adapters/README.md +59 -59
- package/.aios-core/infrastructure/scripts/approval-workflow.js +1 -1
- package/.aios-core/infrastructure/scripts/batch-creator.js +1 -1
- package/.aios-core/infrastructure/scripts/component-generator.js +3 -3
- package/.aios-core/infrastructure/scripts/component-metadata.js +1 -1
- package/.aios-core/infrastructure/scripts/component-search.js +1 -1
- package/.aios-core/infrastructure/scripts/coverage-analyzer.js +1 -1
- package/.aios-core/infrastructure/scripts/dependency-analyzer.js +1 -1
- package/.aios-core/infrastructure/scripts/dependency-impact-analyzer.js +1 -1
- package/.aios-core/infrastructure/scripts/framework-analyzer.js +1 -1
- package/.aios-core/infrastructure/scripts/improvement-engine.js +1 -1
- package/.aios-core/infrastructure/scripts/llm-routing/install-llm-routing.js +26 -13
- package/.aios-core/infrastructure/scripts/llm-routing/templates/claude-free-tracked.cmd +127 -0
- package/.aios-core/infrastructure/scripts/llm-routing/templates/claude-free-tracked.sh +108 -0
- package/.aios-core/infrastructure/scripts/llm-routing/templates/deepseek-proxy.cmd +71 -0
- package/.aios-core/infrastructure/scripts/llm-routing/templates/deepseek-proxy.sh +65 -0
- package/.aios-core/infrastructure/scripts/llm-routing/templates/deepseek-usage.cmd +51 -0
- package/.aios-core/infrastructure/scripts/llm-routing/templates/deepseek-usage.sh +16 -0
- package/.aios-core/infrastructure/scripts/llm-routing/usage-tracker/index.js +549 -0
- package/.aios-core/infrastructure/scripts/modification-risk-assessment.js +1 -1
- package/.aios-core/infrastructure/scripts/performance-analyzer.js +1 -1
- package/.aios-core/infrastructure/scripts/pm-adapter.js +134 -134
- package/.aios-core/infrastructure/scripts/repository-detector.js +3 -3
- package/.aios-core/infrastructure/scripts/template-engine.js +1 -1
- package/.aios-core/infrastructure/scripts/template-validator.js +1 -1
- package/.aios-core/infrastructure/scripts/test-generator.js +1 -1
- package/.aios-core/infrastructure/scripts/test-quality-assessment.js +1 -1
- package/.aios-core/infrastructure/scripts/transaction-manager.js +1 -1
- package/.aios-core/infrastructure/scripts/usage-analytics.js +1 -1
- package/.aios-core/infrastructure/scripts/visual-impact-generator.js +2 -2
- package/.aios-core/infrastructure/templates/github-workflows/README.md +109 -109
- package/.aios-core/infrastructure/tests/regression-suite-v2.md +621 -621
- package/.aios-core/infrastructure/tools/README.md +222 -222
- package/.aios-core/infrastructure/tools/cli/github-cli.yaml +200 -200
- package/.aios-core/infrastructure/tools/cli/railway-cli.yaml +260 -260
- package/.aios-core/infrastructure/tools/cli/supabase-cli.yaml +224 -224
- package/.aios-core/infrastructure/tools/local/ffmpeg.yaml +261 -261
- package/.aios-core/infrastructure/tools/mcp/21st-dev-magic.yaml +127 -127
- package/.aios-core/infrastructure/tools/mcp/browser.yaml +103 -103
- package/.aios-core/infrastructure/tools/mcp/clickup.yaml +534 -534
- package/.aios-core/infrastructure/tools/mcp/context7.yaml +78 -78
- package/.aios-core/infrastructure/tools/mcp/desktop-commander.yaml +180 -180
- package/.aios-core/infrastructure/tools/mcp/exa.yaml +103 -103
- package/.aios-core/infrastructure/tools/mcp/google-workspace.yaml +930 -930
- package/.aios-core/infrastructure/tools/mcp/n8n.yaml +551 -551
- package/.aios-core/infrastructure/tools/mcp/supabase.yaml +808 -808
- package/.aios-core/install-manifest.yaml +347 -347
- package/.aios-core/product/README.md +56 -56
- package/.aios-core/product/checklists/accessibility-wcag-checklist.md +80 -0
- package/.aios-core/product/checklists/architect-checklist.md +443 -443
- package/.aios-core/product/checklists/change-checklist.md +182 -182
- package/.aios-core/product/checklists/component-quality-checklist.md +74 -0
- package/.aios-core/product/checklists/database-design-checklist.md +119 -119
- package/.aios-core/product/checklists/dba-predeploy-checklist.md +97 -97
- package/.aios-core/product/checklists/dba-rollback-checklist.md +99 -99
- package/.aios-core/product/checklists/migration-readiness-checklist.md +75 -0
- package/.aios-core/product/checklists/pattern-audit-checklist.md +88 -0
- package/.aios-core/product/checklists/pm-checklist.md +375 -375
- package/.aios-core/product/checklists/po-master-checklist.md +441 -441
- package/.aios-core/product/checklists/pre-push-checklist.md +108 -108
- package/.aios-core/product/checklists/release-checklist.md +122 -122
- package/.aios-core/product/checklists/story-dod-checklist.md +101 -101
- package/.aios-core/product/checklists/story-draft-checklist.md +215 -215
- package/.aios-core/product/data/atomic-design-principles.md +108 -0
- package/.aios-core/product/data/brainstorming-techniques.md +36 -36
- package/.aios-core/product/data/consolidation-algorithms.md +142 -0
- package/.aios-core/product/data/database-best-practices.md +182 -0
- package/.aios-core/product/data/design-token-best-practices.md +107 -0
- package/.aios-core/product/data/elicitation-methods.md +134 -134
- package/.aios-core/product/data/integration-patterns.md +207 -0
- package/.aios-core/product/data/migration-safety-guide.md +329 -0
- package/.aios-core/product/data/mode-selection-best-practices.md +471 -471
- package/.aios-core/product/data/postgres-tuning-guide.md +300 -0
- package/.aios-core/product/data/rls-security-patterns.md +333 -0
- package/.aios-core/product/data/roi-calculation-guide.md +142 -0
- package/.aios-core/product/data/supabase-patterns.md +330 -0
- package/.aios-core/product/data/test-levels-framework.md +148 -148
- package/.aios-core/product/data/test-priorities-matrix.md +174 -174
- package/.aios-core/product/data/wcag-compliance-guide.md +267 -0
- package/.aios-core/product/templates/1mcp-config.yaml +225 -225
- package/.aios-core/product/templates/activation-instructions-inline-greeting.yaml +63 -63
- package/.aios-core/product/templates/activation-instructions-template.md +258 -258
- package/.aios-core/product/templates/agent-template.yaml +120 -120
- package/.aios-core/product/templates/architecture-tmpl.yaml +650 -650
- package/.aios-core/product/templates/brainstorming-output-tmpl.yaml +155 -155
- package/.aios-core/product/templates/brownfield-architecture-tmpl.yaml +475 -475
- package/.aios-core/product/templates/brownfield-prd-tmpl.yaml +279 -279
- package/.aios-core/product/templates/changelog-template.md +134 -134
- package/.aios-core/product/templates/command-rationalization-matrix.md +152 -152
- package/.aios-core/product/templates/competitor-analysis-tmpl.yaml +292 -292
- package/.aios-core/product/templates/design-story-tmpl.yaml +587 -587
- package/.aios-core/product/templates/ds-artifact-analysis.md +70 -70
- package/.aios-core/product/templates/front-end-architecture-tmpl.yaml +205 -205
- package/.aios-core/product/templates/front-end-spec-tmpl.yaml +348 -348
- package/.aios-core/product/templates/fullstack-architecture-tmpl.yaml +804 -804
- package/.aios-core/product/templates/github-pr-template.md +67 -67
- package/.aios-core/product/templates/gordon-mcp.yaml +140 -140
- package/.aios-core/product/templates/ide-rules/antigravity-rules.md +115 -115
- package/.aios-core/product/templates/ide-rules/claude-rules.md +221 -221
- package/.aios-core/product/templates/ide-rules/cline-rules.md +84 -84
- package/.aios-core/product/templates/ide-rules/copilot-rules.md +92 -92
- package/.aios-core/product/templates/ide-rules/cursor-rules.md +115 -115
- package/.aios-core/product/templates/ide-rules/gemini-rules.md +85 -85
- package/.aios-core/product/templates/ide-rules/roo-rules.md +86 -86
- package/.aios-core/product/templates/ide-rules/trae-rules.md +104 -104
- package/.aios-core/product/templates/ide-rules/windsurf-rules.md +80 -80
- package/.aios-core/product/templates/index-strategy-tmpl.yaml +53 -53
- package/.aios-core/product/templates/market-research-tmpl.yaml +251 -251
- package/.aios-core/product/templates/mcp-workflow.js +271 -271
- package/.aios-core/product/templates/migration-plan-tmpl.yaml +1022 -1022
- package/.aios-core/product/templates/migration-strategy-tmpl.md +524 -524
- package/.aios-core/product/templates/personalized-agent-template.md +258 -258
- package/.aios-core/product/templates/personalized-checklist-template.md +340 -340
- package/.aios-core/product/templates/personalized-task-template-v2.md +905 -905
- package/.aios-core/product/templates/personalized-task-template.md +344 -344
- package/.aios-core/product/templates/personalized-template-file.yaml +322 -322
- package/.aios-core/product/templates/personalized-workflow-template.yaml +460 -460
- package/.aios-core/product/templates/prd-tmpl.yaml +201 -201
- package/.aios-core/product/templates/project-brief-tmpl.yaml +220 -220
- package/.aios-core/product/templates/qa-gate-tmpl.yaml +240 -240
- package/.aios-core/product/templates/rls-policies-tmpl.yaml +1203 -1203
- package/.aios-core/product/templates/schema-design-tmpl.yaml +428 -428
- package/.aios-core/product/templates/state-persistence-tmpl.yaml +219 -219
- package/.aios-core/product/templates/story-tmpl.yaml +331 -331
- package/.aios-core/product/templates/task-execution-report.md +495 -495
- package/.aios-core/product/templates/task-template.md +122 -122
- package/.aios-core/product/templates/token-exports-tailwind-tmpl.js +395 -395
- package/.aios-core/product/templates/tokens-schema-tmpl.yaml +305 -305
- package/.aios-core/product/templates/workflow-template.yaml +133 -133
- package/.aios-core/scripts/README.md +354 -354
- package/.aios-core/scripts/aios-doc-template.md +325 -325
- package/.aios-core/scripts/elicitation-engine.js +1 -1
- package/.aios-core/scripts/test-template-system.js +1 -1
- package/.aios-core/scripts/workflow-management.md +69 -69
- package/.aios-core/user-guide.md +1413 -1413
- package/.aios-core/working-in-the-brownfield.md +361 -361
- package/LICENSE +1 -1
- package/README.md +704 -703
- package/bin/aios-init-old.js +3 -3
- package/bin/aios-init-v4.js +1 -1
- package/bin/aios-init.backup-v1.1.4.js +1 -1
- package/bin/aios-init.js +3 -3
- package/bin/aios.js +279 -279
- package/bin/utils/install-errors.js +339 -339
- package/bin/utils/install-transaction.js +445 -445
- package/index.d.ts +18 -18
- package/index.esm.js +20 -20
- package/index.js +6 -6
- package/package.json +8 -11
- package/packages/installer/src/config/templates/env-template.js +27 -4
- package/packages/installer/src/detection/detect-project-type.js +81 -81
- package/packages/installer/tests/integration/wizard-detection.test.js +8 -6
- package/packages/installer/tests/unit/env-template.test.js +8 -8
- package/src/config/ide-configs.js +1 -1
- package/src/wizard/feedback.js +2 -2
- package/src/wizard/index.js +1 -1
- package/src/wizard/validation/report-generator.js +1 -1
- package/src/wizard/validation/troubleshooting-system.js +13 -13
- package/tools/diagnose-installation.js +266 -0
- package/tools/diagnose-npx-issue.ps1 +96 -0
- package/tools/quick-diagnose.cmd +85 -0
- package/tools/quick-diagnose.ps1 +117 -0
- package/.aios-core/infrastructure/scripts/_archived/final-todo-count.js +0 -122
- package/.aios-core/infrastructure/scripts/_archived/fix-yaml-formatting.js +0 -89
- package/.aios-core/infrastructure/scripts/_archived/migration-generator.js +0 -780
- package/.aios-core/infrastructure/scripts/_archived/migration-path-generator.js +0 -950
- package/.aios-core/infrastructure/scripts/_archived/phase2-entrada-saida-errors.js +0 -425
- package/.aios-core/infrastructure/scripts/_archived/phase2-spot-check.js +0 -132
- package/.aios-core/infrastructure/scripts/_archived/phase3-tools-scripts-validation.js +0 -381
- package/.aios-core/infrastructure/scripts/_archived/phase4-metadata-performance.js +0 -203
- package/.aios-core/infrastructure/scripts/_archived/test-yaml-parsing.js +0 -24
- package/.aios-core/infrastructure/scripts/_archived/verify-yaml-fix.js +0 -51
- package/.aios-core/tasks/find-component.md.legacy +0 -391
- package/.aios-core/tasks/generate-commit-message.md.legacy +0 -426
- package/.aios-core/tasks/generate-migration.md.legacy +0 -382
- package/.aios-core/tasks/rollback-modification.md.legacy +0 -307
- package/.aios-core/tasks/update-tests.md.legacy +0 -283
|
@@ -1,1022 +1,1022 @@
|
|
|
1
|
-
---
|
|
2
|
-
template_name: "Schema Migration Plan"
|
|
3
|
-
template_version: "1.0.0"
|
|
4
|
-
output_format: "markdown"
|
|
5
|
-
destination: "migration-plan.md"
|
|
6
|
-
description: "Plan and validate a safe schema migration with rollback and tests"
|
|
7
|
-
|
|
8
|
-
sections:
|
|
9
|
-
- id: summary
|
|
10
|
-
title: "Executive Summary"
|
|
11
|
-
instruction: |
|
|
12
|
-
Summarize the change:
|
|
13
|
-
- Objective and scope
|
|
14
|
-
- Risk level (Low/Med/High) and why
|
|
15
|
-
- Environments impacted (dev/staging/prod)
|
|
16
|
-
- Expected migration time window and rollback window
|
|
17
|
-
elicit: true
|
|
18
|
-
|
|
19
|
-
- id: change-set
|
|
20
|
-
title: "Change Set"
|
|
21
|
-
instruction: |
|
|
22
|
-
Detail every schema change:
|
|
23
|
-
- Tables created/altered/dropped
|
|
24
|
-
- Columns added/modified/removed (types, defaults, constraints)
|
|
25
|
-
- Indexes (create/alter/drop)
|
|
26
|
-
- Functions/triggers/views (create/replace/drop)
|
|
27
|
-
- RLS policies (add/remove/modify)
|
|
28
|
-
elicit: true
|
|
29
|
-
|
|
30
|
-
- id: dependencies
|
|
31
|
-
title: "Dependencies & Ordering"
|
|
32
|
-
instruction: |
|
|
33
|
-
List dependencies and execution order:
|
|
34
|
-
1) Extensions
|
|
35
|
-
2) Tables & constraints
|
|
36
|
-
3) Functions
|
|
37
|
-
4) Triggers
|
|
38
|
-
5) RLS
|
|
39
|
-
6) Views / MatViews
|
|
40
|
-
Note any cross-object dependencies that require two-phase rollout.
|
|
41
|
-
elicit: true
|
|
42
|
-
|
|
43
|
-
- id: data-migration
|
|
44
|
-
title: "Data Migration & Backfill"
|
|
45
|
-
instruction: |
|
|
46
|
-
Strategies for migrating existing data safely at scale.
|
|
47
|
-
|
|
48
|
-
## Small Data Sets (< 100K rows)
|
|
49
|
-
|
|
50
|
-
**Simple approach** for tables with few rows:
|
|
51
|
-
|
|
52
|
-
```sql
|
|
53
|
-
-- Direct UPDATE (< 100K rows)
|
|
54
|
-
UPDATE users SET email_address = email
|
|
55
|
-
WHERE email_address IS NULL;
|
|
56
|
-
|
|
57
|
-
-- Single transaction, fast execution
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
**When safe**:
|
|
61
|
-
- Small tables (< 100K rows)
|
|
62
|
-
- Low traffic tables
|
|
63
|
-
- Maintenance window available
|
|
64
|
-
|
|
65
|
-
## Large Data Sets (> 100K rows)
|
|
66
|
-
|
|
67
|
-
**Batched approach** prevents table locks and reduces transaction size.
|
|
68
|
-
|
|
69
|
-
### Pattern 1: Basic Batching (Single Process)
|
|
70
|
-
|
|
71
|
-
```sql
|
|
72
|
-
-- Backfill in batches to avoid long locks
|
|
73
|
-
DO $$
|
|
74
|
-
DECLARE
|
|
75
|
-
batch_size INT := 1000; -- Adjust based on row size and available memory
|
|
76
|
-
rows_updated INT;
|
|
77
|
-
total_updated INT := 0;
|
|
78
|
-
batch_count INT := 0;
|
|
79
|
-
BEGIN
|
|
80
|
-
LOOP
|
|
81
|
-
-- Update one batch
|
|
82
|
-
WITH batch AS (
|
|
83
|
-
SELECT id FROM users
|
|
84
|
-
WHERE email_address IS NULL
|
|
85
|
-
LIMIT batch_size
|
|
86
|
-
FOR UPDATE SKIP LOCKED -- ⭐ CRITICAL: Avoid lock contention
|
|
87
|
-
)
|
|
88
|
-
UPDATE users
|
|
89
|
-
SET email_address = email
|
|
90
|
-
FROM batch
|
|
91
|
-
WHERE users.id = batch.id;
|
|
92
|
-
|
|
93
|
-
GET DIAGNOSTICS rows_updated = ROW_COUNT;
|
|
94
|
-
EXIT WHEN rows_updated = 0; -- No more rows to process
|
|
95
|
-
|
|
96
|
-
total_updated := total_updated + rows_updated;
|
|
97
|
-
batch_count := batch_count + 1;
|
|
98
|
-
|
|
99
|
-
RAISE NOTICE 'Batch %: Updated % rows (total: %)',
|
|
100
|
-
batch_count, rows_updated, total_updated;
|
|
101
|
-
|
|
102
|
-
-- Throttle to avoid overloading DB
|
|
103
|
-
PERFORM pg_sleep(0.1); -- 100ms pause between batches
|
|
104
|
-
END LOOP;
|
|
105
|
-
|
|
106
|
-
RAISE NOTICE 'Backfill complete: % batches, % total rows',
|
|
107
|
-
batch_count, total_updated;
|
|
108
|
-
END $$;
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
**Key techniques**:
|
|
112
|
-
- `FOR UPDATE SKIP LOCKED` - Avoids lock contention, enables parallel processing
|
|
113
|
-
- `LIMIT batch_size` - Controls transaction size
|
|
114
|
-
- `pg_sleep()` - Throttles load, allows other transactions to proceed
|
|
115
|
-
- `RAISE NOTICE` - Progress tracking
|
|
116
|
-
|
|
117
|
-
### Pattern 2: Parallel Batching (Multiple Workers)
|
|
118
|
-
|
|
119
|
-
**For very large tables** (millions of rows), run multiple workers concurrently:
|
|
120
|
-
|
|
121
|
-
```sql
|
|
122
|
-
-- Worker 1 (run in psql session 1)
|
|
123
|
-
DO $$
|
|
124
|
-
DECLARE
|
|
125
|
-
batch_size INT := 5000;
|
|
126
|
-
worker_id INT := 1;
|
|
127
|
-
rows_updated INT;
|
|
128
|
-
BEGIN
|
|
129
|
-
LOOP
|
|
130
|
-
WITH batch AS (
|
|
131
|
-
SELECT id FROM orders
|
|
132
|
-
WHERE status_new IS NULL
|
|
133
|
-
ORDER BY id -- ⭐ CRITICAL: Deterministic ordering
|
|
134
|
-
LIMIT batch_size
|
|
135
|
-
FOR UPDATE SKIP LOCKED -- Skip rows locked by other workers
|
|
136
|
-
)
|
|
137
|
-
UPDATE orders
|
|
138
|
-
SET status_new = status
|
|
139
|
-
FROM batch
|
|
140
|
-
WHERE orders.id = batch.id;
|
|
141
|
-
|
|
142
|
-
GET DIAGNOSTICS rows_updated = ROW_COUNT;
|
|
143
|
-
EXIT WHEN rows_updated = 0;
|
|
144
|
-
|
|
145
|
-
RAISE NOTICE '[Worker %] Updated % rows', worker_id, rows_updated;
|
|
146
|
-
PERFORM pg_sleep(0.05); -- Shorter pause with multiple workers
|
|
147
|
-
END LOOP;
|
|
148
|
-
END $$;
|
|
149
|
-
|
|
150
|
-
-- Worker 2-4: Same script, different worker_id in RAISE NOTICE
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
**Why it works**:
|
|
154
|
-
- `FOR UPDATE SKIP LOCKED` allows workers to grab different rows
|
|
155
|
-
- Each worker skips rows locked by others
|
|
156
|
-
- No deadlocks or contention
|
|
157
|
-
- Near-linear speedup (4 workers ≈ 4x faster)
|
|
158
|
-
|
|
159
|
-
### Pattern 3: Progress Tracking Table
|
|
160
|
-
|
|
161
|
-
**Track progress** for resumable migrations:
|
|
162
|
-
|
|
163
|
-
```sql
|
|
164
|
-
-- Create progress tracking table
|
|
165
|
-
CREATE TABLE migration_progress (
|
|
166
|
-
migration_name TEXT PRIMARY KEY,
|
|
167
|
-
last_processed_id BIGINT,
|
|
168
|
-
total_processed BIGINT DEFAULT 0,
|
|
169
|
-
started_at TIMESTAMPTZ DEFAULT NOW(),
|
|
170
|
-
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
-- Resumable backfill with progress tracking
|
|
174
|
-
DO $$
|
|
175
|
-
DECLARE
|
|
176
|
-
batch_size INT := 5000;
|
|
177
|
-
last_id BIGINT;
|
|
178
|
-
rows_updated INT;
|
|
179
|
-
migration TEXT := 'users_email_address_backfill';
|
|
180
|
-
BEGIN
|
|
181
|
-
-- Get last processed ID (resume from failure)
|
|
182
|
-
SELECT COALESCE(last_processed_id, 0) INTO last_id
|
|
183
|
-
FROM migration_progress
|
|
184
|
-
WHERE migration_name = migration;
|
|
185
|
-
|
|
186
|
-
-- Initialize if not exists
|
|
187
|
-
INSERT INTO migration_progress (migration_name, last_processed_id)
|
|
188
|
-
VALUES (migration, 0)
|
|
189
|
-
ON CONFLICT (migration_name) DO NOTHING;
|
|
190
|
-
|
|
191
|
-
LOOP
|
|
192
|
-
-- Process batch starting after last_id
|
|
193
|
-
WITH batch AS (
|
|
194
|
-
SELECT id FROM users
|
|
195
|
-
WHERE id > last_id
|
|
196
|
-
AND email_address IS NULL
|
|
197
|
-
ORDER BY id -- Deterministic ordering
|
|
198
|
-
LIMIT batch_size
|
|
199
|
-
FOR UPDATE SKIP LOCKED
|
|
200
|
-
)
|
|
201
|
-
UPDATE users
|
|
202
|
-
SET email_address = email
|
|
203
|
-
FROM batch
|
|
204
|
-
WHERE users.id = batch.id
|
|
205
|
-
RETURNING users.id INTO last_id;
|
|
206
|
-
|
|
207
|
-
GET DIAGNOSTICS rows_updated = ROW_COUNT;
|
|
208
|
-
EXIT WHEN rows_updated = 0;
|
|
209
|
-
|
|
210
|
-
-- Update progress
|
|
211
|
-
UPDATE migration_progress
|
|
212
|
-
SET
|
|
213
|
-
last_processed_id = last_id,
|
|
214
|
-
total_processed = total_processed + rows_updated,
|
|
215
|
-
updated_at = NOW()
|
|
216
|
-
WHERE migration_name = migration;
|
|
217
|
-
|
|
218
|
-
RAISE NOTICE '[%] Processed up to ID %, % rows this batch',
|
|
219
|
-
migration, last_id, rows_updated;
|
|
220
|
-
|
|
221
|
-
PERFORM pg_sleep(0.1);
|
|
222
|
-
END LOOP;
|
|
223
|
-
|
|
224
|
-
RAISE NOTICE '[%] Complete: % total rows',
|
|
225
|
-
migration,
|
|
226
|
-
(SELECT total_processed FROM migration_progress WHERE migration_name = migration);
|
|
227
|
-
END $$;
|
|
228
|
-
|
|
229
|
-
-- Check progress (run in separate session)
|
|
230
|
-
SELECT
|
|
231
|
-
migration_name,
|
|
232
|
-
last_processed_id,
|
|
233
|
-
total_processed,
|
|
234
|
-
updated_at,
|
|
235
|
-
AGE(NOW(), updated_at) AS time_since_update
|
|
236
|
-
FROM migration_progress;
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
**Benefits**:
|
|
240
|
-
- Resumable on failure/cancellation
|
|
241
|
-
- Live progress monitoring
|
|
242
|
-
- Deterministic (no duplicate processing)
|
|
243
|
-
|
|
244
|
-
## Batch Size Guidelines
|
|
245
|
-
|
|
246
|
-
**Factors to consider**:
|
|
247
|
-
- Row size (larger rows → smaller batches)
|
|
248
|
-
- Available memory (`maintenance_work_mem`)
|
|
249
|
-
- Transaction timeout limits
|
|
250
|
-
- Lock contention sensitivity
|
|
251
|
-
|
|
252
|
-
**Recommended sizes**:
|
|
253
|
-
| Row Size | Batch Size | Reasoning |
|
|
254
|
-
|----------|------------|-----------|
|
|
255
|
-
| Small (< 1KB) | 5,000-10,000 | Fast, low memory |
|
|
256
|
-
| Medium (1-10KB) | 1,000-5,000 | Balance speed/memory |
|
|
257
|
-
| Large (> 10KB) | 100-1,000 | Avoid memory exhaustion |
|
|
258
|
-
| JSONB/text heavy | 500-2,000 | Variable size risk |
|
|
259
|
-
|
|
260
|
-
## Throttling Strategies
|
|
261
|
-
|
|
262
|
-
```sql
|
|
263
|
-
-- Light throttle (10 batches/sec, low impact)
|
|
264
|
-
PERFORM pg_sleep(0.1);
|
|
265
|
-
|
|
266
|
-
-- Medium throttle (5 batches/sec, safer for production)
|
|
267
|
-
PERFORM pg_sleep(0.2);
|
|
268
|
-
|
|
269
|
-
-- Heavy throttle (2 batches/sec, minimal impact)
|
|
270
|
-
PERFORM pg_sleep(0.5);
|
|
271
|
-
|
|
272
|
-
-- Adaptive throttle (based on DB load)
|
|
273
|
-
-- Check pg_stat_activity connection count
|
|
274
|
-
SELECT pg_sleep(
|
|
275
|
-
CASE
|
|
276
|
-
WHEN (SELECT count(*) FROM pg_stat_activity WHERE state = 'active') > 50
|
|
277
|
-
THEN 0.5 -- High load, slow down
|
|
278
|
-
ELSE 0.1 -- Normal load, faster
|
|
279
|
-
END
|
|
280
|
-
);
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
## Verification Queries
|
|
284
|
-
|
|
285
|
-
```sql
|
|
286
|
-
-- Check completion
|
|
287
|
-
SELECT COUNT(*) AS remaining
|
|
288
|
-
FROM users
|
|
289
|
-
WHERE email_address IS NULL;
|
|
290
|
-
-- Expected: 0
|
|
291
|
-
|
|
292
|
-
-- Check data integrity
|
|
293
|
-
SELECT COUNT(*) AS mismatches
|
|
294
|
-
FROM users
|
|
295
|
-
WHERE email != email_address;
|
|
296
|
-
-- Expected: 0 (or acceptable threshold)
|
|
297
|
-
|
|
298
|
-
-- Sample verification
|
|
299
|
-
SELECT id, email, email_address
|
|
300
|
-
FROM users
|
|
301
|
-
WHERE email != email_address
|
|
302
|
-
OR email_address IS NULL
|
|
303
|
-
LIMIT 100;
|
|
304
|
-
|
|
305
|
-
-- Performance check (should use index)
|
|
306
|
-
EXPLAIN ANALYZE
|
|
307
|
-
SELECT * FROM users WHERE email_address IS NULL;
|
|
308
|
-
-- Look for "Index Scan" not "Seq Scan"
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
## Lock Impact Analysis
|
|
312
|
-
|
|
313
|
-
```sql
|
|
314
|
-
-- Monitor locks during backfill
|
|
315
|
-
SELECT
|
|
316
|
-
pid,
|
|
317
|
-
usename,
|
|
318
|
-
query_start,
|
|
319
|
-
state,
|
|
320
|
-
wait_event_type,
|
|
321
|
-
wait_event,
|
|
322
|
-
query
|
|
323
|
-
FROM pg_stat_activity
|
|
324
|
-
WHERE state != 'idle'
|
|
325
|
-
AND query LIKE '%users%'
|
|
326
|
-
ORDER BY query_start;
|
|
327
|
-
|
|
328
|
-
-- Check for lock contention
|
|
329
|
-
SELECT
|
|
330
|
-
locktype,
|
|
331
|
-
relation::regclass AS table_name,
|
|
332
|
-
mode,
|
|
333
|
-
granted,
|
|
334
|
-
COUNT(*) AS lock_count
|
|
335
|
-
FROM pg_locks
|
|
336
|
-
WHERE relation::regclass::text LIKE '%users%'
|
|
337
|
-
GROUP BY locktype, relation, mode, granted;
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
## Best Practices Summary
|
|
341
|
-
|
|
342
|
-
1. **Use FOR UPDATE SKIP LOCKED** for parallelizable batching
|
|
343
|
-
2. **ORDER BY** for deterministic processing (resumable)
|
|
344
|
-
3. **Batch size 1,000-10,000** depending on row size
|
|
345
|
-
4. **Throttle with pg_sleep()** to reduce DB load
|
|
346
|
-
5. **Track progress** in separate table
|
|
347
|
-
6. **Monitor locks** during execution
|
|
348
|
-
7. **Verify data** before and after migration
|
|
349
|
-
8. **Test in staging** with production-size data
|
|
350
|
-
|
|
351
|
-
## When NOT to Use Batching
|
|
352
|
-
|
|
353
|
-
- Tables < 100K rows (direct UPDATE faster)
|
|
354
|
-
- Maintenance window available (faster without throttling)
|
|
355
|
-
- No other traffic (no contention risk)
|
|
356
|
-
|
|
357
|
-
## Common Pitfalls
|
|
358
|
-
|
|
359
|
-
- **Forgetting FOR UPDATE SKIP LOCKED** → deadlocks with parallel workers
|
|
360
|
-
- **No ORDER BY** → non-deterministic, duplicate processing
|
|
361
|
-
- **Too large batches** → lock contention, memory issues
|
|
362
|
-
- **No throttling** → DB overload, affects production traffic
|
|
363
|
-
- **No progress tracking** → can't resume on failure
|
|
364
|
-
elicit: true
|
|
365
|
-
|
|
366
|
-
- id: safety
|
|
367
|
-
title: "Safety & Rollback"
|
|
368
|
-
instruction: |
|
|
369
|
-
Safety plan:
|
|
370
|
-
- Pre-migration snapshot strategy
|
|
371
|
-
- Rollback script outline (what to undo, in order)
|
|
372
|
-
- Roll-forward strategy if rollback is unsafe
|
|
373
|
-
- Advisory locks to avoid concurrent runs
|
|
374
|
-
elicit: true
|
|
375
|
-
|
|
376
|
-
- id: testing
|
|
377
|
-
title: "Testing Strategy"
|
|
378
|
-
instruction: |
|
|
379
|
-
Tests to run:
|
|
380
|
-
- Dry-run (BEGIN; \i file; ROLLBACK)
|
|
381
|
-
- Smoke tests (post-migration)
|
|
382
|
-
- RLS positive/negative tests (impersonation)
|
|
383
|
-
- Performance baselines & EXPLAIN checks on hot paths
|
|
384
|
-
elicit: true
|
|
385
|
-
|
|
386
|
-
- id: operations
|
|
387
|
-
title: "Operational Runbook"
|
|
388
|
-
instruction: |
|
|
389
|
-
Provide exact commands with placeholders:
|
|
390
|
-
- Set env, check psql/pg_dump versions
|
|
391
|
-
- Take snapshot
|
|
392
|
-
- Apply migration
|
|
393
|
-
- Post snapshot
|
|
394
|
-
- Run smoke tests
|
|
395
|
-
Include expected outputs and success criteria.
|
|
396
|
-
elicit: true
|
|
397
|
-
|
|
398
|
-
- id: communication
|
|
399
|
-
title: "Communication & Approval"
|
|
400
|
-
instruction: |
|
|
401
|
-
- Stakeholders, approvers
|
|
402
|
-
- Change window notice
|
|
403
|
-
- Post-deploy validation owners
|
|
404
|
-
- Incident/rollback contact path
|
|
405
|
-
elicit: true
|
|
406
|
-
|
|
407
|
-
- id: version-tracking
|
|
408
|
-
title: "Schema Version Tracking"
|
|
409
|
-
instruction: |
|
|
410
|
-
DB Sage uses custom schema_migrations table for enhanced tracking beyond Supabase's built-in migration system.
|
|
411
|
-
|
|
412
|
-
## Setup (run once in initial migration)
|
|
413
|
-
|
|
414
|
-
```sql
|
|
415
|
-
CREATE TABLE IF NOT EXISTS public.schema_migrations (
|
|
416
|
-
version TEXT PRIMARY KEY,
|
|
417
|
-
name TEXT NOT NULL,
|
|
418
|
-
applied_at TIMESTAMPTZ DEFAULT NOW(),
|
|
419
|
-
applied_by TEXT NOT NULL,
|
|
420
|
-
execution_time_ms INTEGER,
|
|
421
|
-
success BOOLEAN NOT NULL DEFAULT false,
|
|
422
|
-
checksum TEXT NOT NULL, -- SHA256 do arquivo de migration
|
|
423
|
-
rollback_script TEXT, -- Script de rollback
|
|
424
|
-
notes TEXT,
|
|
425
|
-
CONSTRAINT valid_checksum CHECK (length(checksum) = 64),
|
|
426
|
-
CONSTRAINT valid_version CHECK (version ~ '^\d{14}$') -- Format: YYYYMMDDHHmmss
|
|
427
|
-
);
|
|
428
|
-
|
|
429
|
-
CREATE INDEX idx_migrations_applied_at
|
|
430
|
-
ON schema_migrations(applied_at DESC);
|
|
431
|
-
|
|
432
|
-
CREATE INDEX idx_migrations_success
|
|
433
|
-
ON schema_migrations(success) WHERE success = false;
|
|
434
|
-
|
|
435
|
-
COMMENT ON TABLE schema_migrations IS
|
|
436
|
-
'Custom migration tracking with checksums, rollback scripts, and execution metadata';
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
## Migration File Structure
|
|
440
|
-
|
|
441
|
-
Every migration should follow this template:
|
|
442
|
-
|
|
443
|
-
```sql
|
|
444
|
-
-- Migration: 20251027120000_add_users_table
|
|
445
|
-
-- Checksum: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
|
446
|
-
-- Applied by: user@example.com
|
|
447
|
-
-- Rollback: See section at end of file
|
|
448
|
-
|
|
449
|
-
BEGIN;
|
|
450
|
-
|
|
451
|
-
-- Record migration start
|
|
452
|
-
INSERT INTO public.schema_migrations (
|
|
453
|
-
version, name, applied_by, success, checksum, rollback_script, notes
|
|
454
|
-
)
|
|
455
|
-
VALUES (
|
|
456
|
-
'20251027120000',
|
|
457
|
-
'add_users_table',
|
|
458
|
-
current_user,
|
|
459
|
-
false, -- Will update to true on success
|
|
460
|
-
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
|
|
461
|
-
$rollback$
|
|
462
|
-
-- Rollback script
|
|
463
|
-
DROP TABLE IF EXISTS users CASCADE;
|
|
464
|
-
DELETE FROM public.schema_migrations WHERE version = '20251027120000';
|
|
465
|
-
$rollback$,
|
|
466
|
-
'Initial users table creation'
|
|
467
|
-
)
|
|
468
|
-
ON CONFLICT (version) DO NOTHING;
|
|
469
|
-
|
|
470
|
-
-- Migration DDL/DML statements here
|
|
471
|
-
CREATE TABLE users (
|
|
472
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
473
|
-
email TEXT UNIQUE NOT NULL,
|
|
474
|
-
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
475
|
-
);
|
|
476
|
-
|
|
477
|
-
-- Update to success
|
|
478
|
-
UPDATE public.schema_migrations
|
|
479
|
-
SET
|
|
480
|
-
success = true,
|
|
481
|
-
execution_time_ms = EXTRACT(MILLISECOND FROM (NOW() - applied_at))::INTEGER
|
|
482
|
-
WHERE version = '20251027120000';
|
|
483
|
-
|
|
484
|
-
COMMIT;
|
|
485
|
-
|
|
486
|
-
-- ROLLBACK SECTION (DO NOT EXECUTE - stored in schema_migrations table)
|
|
487
|
-
/*
|
|
488
|
-
BEGIN;
|
|
489
|
-
DROP TABLE IF EXISTS users CASCADE;
|
|
490
|
-
DELETE FROM public.schema_migrations WHERE version = '20251027120000';
|
|
491
|
-
COMMIT;
|
|
492
|
-
*/
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
## Checksum Generation
|
|
496
|
-
|
|
497
|
-
Generate SHA256 checksum before applying migration:
|
|
498
|
-
|
|
499
|
-
```bash
|
|
500
|
-
# Generate checksum (exclude rollback section)
|
|
501
|
-
checksum=$(sha256sum migration.sql | awk '{print $1}')
|
|
502
|
-
echo "Checksum: $checksum"
|
|
503
|
-
|
|
504
|
-
# Verify against stored checksum after application
|
|
505
|
-
psql -c "SELECT version, checksum FROM schema_migrations WHERE version = '20251027120000'"
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
## Query Migration History
|
|
509
|
-
|
|
510
|
-
```sql
|
|
511
|
-
-- Recent successful migrations
|
|
512
|
-
SELECT
|
|
513
|
-
version,
|
|
514
|
-
name,
|
|
515
|
-
applied_at,
|
|
516
|
-
applied_by,
|
|
517
|
-
execution_time_ms,
|
|
518
|
-
notes
|
|
519
|
-
FROM schema_migrations
|
|
520
|
-
WHERE success = true
|
|
521
|
-
ORDER BY applied_at DESC
|
|
522
|
-
LIMIT 10;
|
|
523
|
-
|
|
524
|
-
-- Failed migrations (investigate)
|
|
525
|
-
SELECT
|
|
526
|
-
version,
|
|
527
|
-
name,
|
|
528
|
-
applied_at,
|
|
529
|
-
applied_by,
|
|
530
|
-
notes
|
|
531
|
-
FROM schema_migrations
|
|
532
|
-
WHERE success = false
|
|
533
|
-
ORDER BY applied_at DESC;
|
|
534
|
-
|
|
535
|
-
-- Pending rollbacks
|
|
536
|
-
SELECT
|
|
537
|
-
version,
|
|
538
|
-
name,
|
|
539
|
-
applied_at,
|
|
540
|
-
LENGTH(rollback_script) AS rollback_size_bytes
|
|
541
|
-
FROM schema_migrations
|
|
542
|
-
WHERE success = true
|
|
543
|
-
AND rollback_script IS NOT NULL
|
|
544
|
-
ORDER BY applied_at DESC;
|
|
545
|
-
|
|
546
|
-
-- Checksum verification (detect tampering)
|
|
547
|
-
SELECT
|
|
548
|
-
version,
|
|
549
|
-
name,
|
|
550
|
-
checksum,
|
|
551
|
-
applied_at
|
|
552
|
-
FROM schema_migrations
|
|
553
|
-
WHERE success = true
|
|
554
|
-
ORDER BY applied_at DESC;
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
## Integration with Supabase CLI
|
|
558
|
-
|
|
559
|
-
Supabase CLI manages migrations in `supabase/migrations/` directory with timestamp prefix.
|
|
560
|
-
Our schema_migrations table complements this by adding:
|
|
561
|
-
- WHO applied the migration (applied_by)
|
|
562
|
-
- WHEN exactly (applied_at)
|
|
563
|
-
- SUCCESS status (for partial failures)
|
|
564
|
-
- CHECKSUM verification (integrity)
|
|
565
|
-
- ROLLBACK script (automated recovery)
|
|
566
|
-
|
|
567
|
-
Use both systems together:
|
|
568
|
-
- Supabase CLI for development workflow (`supabase db diff`, `supabase db reset`)
|
|
569
|
-
- schema_migrations for production audit trail and rollback capability
|
|
570
|
-
|
|
571
|
-
## Rollback Execution
|
|
572
|
-
|
|
573
|
-
To rollback a migration:
|
|
574
|
-
|
|
575
|
-
```sql
|
|
576
|
-
-- 1. Retrieve rollback script
|
|
577
|
-
SELECT rollback_script
|
|
578
|
-
FROM schema_migrations
|
|
579
|
-
WHERE version = '20251027120000';
|
|
580
|
-
|
|
581
|
-
-- 2. Execute rollback script (in transaction)
|
|
582
|
-
BEGIN;
|
|
583
|
-
-- Copy rollback script here
|
|
584
|
-
DROP TABLE IF EXISTS users CASCADE;
|
|
585
|
-
DELETE FROM public.schema_migrations WHERE version = '20251027120000';
|
|
586
|
-
COMMIT;
|
|
587
|
-
|
|
588
|
-
-- 3. Verify rollback
|
|
589
|
-
SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'users';
|
|
590
|
-
-- Should return 0
|
|
591
|
-
```
|
|
592
|
-
elicit: false
|
|
593
|
-
|
|
594
|
-
- id: zero-downtime
|
|
595
|
-
title: "Zero-Downtime Migrations"
|
|
596
|
-
instruction: |
|
|
597
|
-
Use expand/contract pattern for non-breaking schema changes. Forward-only migrations that allow
|
|
598
|
-
old and new application versions to coexist.
|
|
599
|
-
|
|
600
|
-
## Overview - Expand/Contract Pattern
|
|
601
|
-
|
|
602
|
-
Zero-downtime migrations use 4-6 phases:
|
|
603
|
-
|
|
604
|
-
1. **EXPAND** - Add new schema (additive only, backward compatible)
|
|
605
|
-
2. **DEPLOY v2** - Deploy app version writing to both old and new schema
|
|
606
|
-
3. **BACKFILL** - Migrate existing data (batched, throttled)
|
|
607
|
-
4. **VALIDATE** - Verify data integrity
|
|
608
|
-
5. **DEPLOY v3** - Deploy app version reading from new schema only
|
|
609
|
-
6. **CONTRACT** - Remove old schema (after all apps updated)
|
|
610
|
-
|
|
611
|
-
## Pattern 1: Add Column (Safe - No Downtime)
|
|
612
|
-
|
|
613
|
-
Adding a column with constant default is safe per PostgreSQL docs:
|
|
614
|
-
"Adding a column with a constant default value does not require each row to be updated."
|
|
615
|
-
|
|
616
|
-
```sql
|
|
617
|
-
-- PHASE 1: EXPAND (safe, instant)
|
|
618
|
-
ALTER TABLE users ADD COLUMN bio TEXT DEFAULT '';
|
|
619
|
-
|
|
620
|
-
-- No other phases needed - existing app ignores new column
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
## Pattern 2: Rename Column (Requires Expand/Contract)
|
|
624
|
-
|
|
625
|
-
**Unsafe**:
|
|
626
|
-
```sql
|
|
627
|
-
ALTER TABLE users RENAME COLUMN email TO email_address; -- ❌ BREAKS OLD APP
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
**Safe** (6 phases):
|
|
631
|
-
|
|
632
|
-
**PHASE 1: EXPAND** (add new column + sync trigger):
|
|
633
|
-
```sql
|
|
634
|
-
-- Add new column
|
|
635
|
-
ALTER TABLE users ADD COLUMN email_address TEXT;
|
|
636
|
-
|
|
637
|
-
-- Trigger to keep columns in sync
|
|
638
|
-
CREATE OR REPLACE FUNCTION sync_email_columns()
|
|
639
|
-
RETURNS TRIGGER AS $$
|
|
640
|
-
BEGIN
|
|
641
|
-
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
|
|
642
|
-
-- Sync both directions
|
|
643
|
-
NEW.email_address := COALESCE(NEW.email_address, NEW.email);
|
|
644
|
-
NEW.email := COALESCE(NEW.email, NEW.email_address);
|
|
645
|
-
END IF;
|
|
646
|
-
RETURN NEW;
|
|
647
|
-
END;
|
|
648
|
-
$$ LANGUAGE plpgsql;
|
|
649
|
-
|
|
650
|
-
CREATE TRIGGER sync_email
|
|
651
|
-
BEFORE INSERT OR UPDATE ON users
|
|
652
|
-
FOR EACH ROW EXECUTE FUNCTION sync_email_columns();
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
**PHASE 2: DEPLOY v2** (app writes to BOTH columns):
|
|
656
|
-
```typescript
|
|
657
|
-
// App version 2 - dual writes
|
|
658
|
-
await db.query(
|
|
659
|
-
'INSERT INTO users (email, email_address) VALUES ($1, $1)',
|
|
660
|
-
[email]
|
|
661
|
-
)
|
|
662
|
-
```
|
|
663
|
-
|
|
664
|
-
**PHASE 3: BACKFILL** (migrate existing data in batches):
|
|
665
|
-
```sql
|
|
666
|
-
-- Backfill in batches (avoid long table locks)
|
|
667
|
-
DO $$
|
|
668
|
-
DECLARE
|
|
669
|
-
batch_size INT := 1000;
|
|
670
|
-
rows_updated INT;
|
|
671
|
-
total_updated INT := 0;
|
|
672
|
-
BEGIN
|
|
673
|
-
LOOP
|
|
674
|
-
-- Update one batch
|
|
675
|
-
WITH batch AS (
|
|
676
|
-
SELECT id FROM users
|
|
677
|
-
WHERE email_address IS NULL
|
|
678
|
-
LIMIT batch_size
|
|
679
|
-
FOR UPDATE SKIP LOCKED -- Avoid lock contention
|
|
680
|
-
)
|
|
681
|
-
UPDATE users
|
|
682
|
-
SET email_address = email
|
|
683
|
-
FROM batch
|
|
684
|
-
WHERE users.id = batch.id;
|
|
685
|
-
|
|
686
|
-
GET DIAGNOSTICS rows_updated = ROW_COUNT;
|
|
687
|
-
EXIT WHEN rows_updated = 0;
|
|
688
|
-
|
|
689
|
-
total_updated := total_updated + rows_updated;
|
|
690
|
-
RAISE NOTICE 'Backfilled % rows (total: %)', rows_updated, total_updated;
|
|
691
|
-
|
|
692
|
-
-- Throttle to avoid overloading DB
|
|
693
|
-
PERFORM pg_sleep(0.1);
|
|
694
|
-
END LOOP;
|
|
695
|
-
|
|
696
|
-
RAISE NOTICE 'Backfill complete: % total rows updated', total_updated;
|
|
697
|
-
END $$;
|
|
698
|
-
```
|
|
699
|
-
|
|
700
|
-
**PHASE 4: VALIDATE** (verify data integrity):
|
|
701
|
-
```sql
|
|
702
|
-
-- Check all data migrated
|
|
703
|
-
SELECT COUNT(*) FROM users WHERE email_address IS NULL;
|
|
704
|
-
-- Expected: 0
|
|
705
|
-
|
|
706
|
-
-- Check data consistency
|
|
707
|
-
SELECT COUNT(*) FROM users WHERE email != email_address;
|
|
708
|
-
-- Expected: 0 (or acceptable threshold for dirty data)
|
|
709
|
-
|
|
710
|
-
-- Sample verification
|
|
711
|
-
SELECT id, email, email_address
|
|
712
|
-
FROM users
|
|
713
|
-
WHERE email != email_address OR email_address IS NULL
|
|
714
|
-
LIMIT 10;
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
**PHASE 5: DEPLOY v3** (app reads from email_address only):
|
|
718
|
-
```typescript
|
|
719
|
-
// App version 3 - reads from new column
|
|
720
|
-
await db.query('SELECT email_address FROM users WHERE id = $1', [id])
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
**PHASE 6: CONTRACT** (remove old column and trigger):
|
|
724
|
-
```sql
|
|
725
|
-
-- Drop sync trigger and function
|
|
726
|
-
DROP TRIGGER IF EXISTS sync_email ON users;
|
|
727
|
-
DROP FUNCTION IF EXISTS sync_email_columns();
|
|
728
|
-
|
|
729
|
-
-- Remove old column
|
|
730
|
-
ALTER TABLE users DROP COLUMN email;
|
|
731
|
-
|
|
732
|
-
-- Optional: Rename to canonical name
|
|
733
|
-
ALTER TABLE users RENAME COLUMN email_address TO email;
|
|
734
|
-
```
|
|
735
|
-
|
|
736
|
-
## Pattern 3: Change Column Type
|
|
737
|
-
|
|
738
|
-
**Example**: Change `age INT` → `age NUMERIC(5,2)`
|
|
739
|
-
|
|
740
|
-
**PHASE 1: EXPAND**:
|
|
741
|
-
```sql
|
|
742
|
-
ALTER TABLE users ADD COLUMN age_new NUMERIC(5,2);
|
|
743
|
-
|
|
744
|
-
-- Sync trigger (optional if app will write)
|
|
745
|
-
CREATE OR REPLACE FUNCTION sync_age_columns()
|
|
746
|
-
RETURNS TRIGGER AS $$
|
|
747
|
-
BEGIN
|
|
748
|
-
NEW.age_new := NEW.age::NUMERIC(5,2);
|
|
749
|
-
RETURN NEW;
|
|
750
|
-
END;
|
|
751
|
-
$$ LANGUAGE plpgsql;
|
|
752
|
-
|
|
753
|
-
CREATE TRIGGER sync_age
|
|
754
|
-
BEFORE INSERT OR UPDATE ON users
|
|
755
|
-
FOR EACH ROW
|
|
756
|
-
WHEN (NEW.age IS NOT NULL)
|
|
757
|
-
EXECUTE FUNCTION sync_age_columns();
|
|
758
|
-
```
|
|
759
|
-
|
|
760
|
-
**PHASE 2: BACKFILL**:
|
|
761
|
-
```sql
|
|
762
|
-
UPDATE users SET age_new = age::NUMERIC(5,2) WHERE age_new IS NULL;
|
|
763
|
-
```
|
|
764
|
-
|
|
765
|
-
**PHASE 3: APP MIGRATION** → read from age_new
|
|
766
|
-
|
|
767
|
-
**PHASE 4: CONTRACT**:
|
|
768
|
-
```sql
|
|
769
|
-
DROP TRIGGER IF EXISTS sync_age ON users;
|
|
770
|
-
DROP FUNCTION IF EXISTS sync_age_columns();
|
|
771
|
-
ALTER TABLE users DROP COLUMN age;
|
|
772
|
-
ALTER TABLE users RENAME COLUMN age_new TO age;
|
|
773
|
-
```
|
|
774
|
-
|
|
775
|
-
## Pattern 4: Add NOT NULL Constraint (Requires Data Backfill)
|
|
776
|
-
|
|
777
|
-
**Unsafe**:
|
|
778
|
-
```sql
|
|
779
|
-
ALTER TABLE users ALTER COLUMN email SET NOT NULL; -- ❌ Fails if NULLs exist
|
|
780
|
-
```
|
|
781
|
-
|
|
782
|
-
**Safe**:
|
|
783
|
-
|
|
784
|
-
**PHASE 1: Add default value**:
|
|
785
|
-
```sql
|
|
786
|
-
ALTER TABLE users ALTER COLUMN email SET DEFAULT 'unknown@example.com';
|
|
787
|
-
```
|
|
788
|
-
|
|
789
|
-
**PHASE 2: Backfill NULLs**:
|
|
790
|
-
```sql
|
|
791
|
-
UPDATE users SET email = 'unknown@example.com' WHERE email IS NULL;
|
|
792
|
-
```
|
|
793
|
-
|
|
794
|
-
**PHASE 3: Validate**:
|
|
795
|
-
```sql
|
|
796
|
-
SELECT COUNT(*) FROM users WHERE email IS NULL; -- Expected: 0
|
|
797
|
-
```
|
|
798
|
-
|
|
799
|
-
**PHASE 4: Add constraint**:
|
|
800
|
-
```sql
|
|
801
|
-
ALTER TABLE users ALTER COLUMN email SET NOT NULL;
|
|
802
|
-
```
|
|
803
|
-
|
|
804
|
-
## Pattern 5: CREATE INDEX CONCURRENTLY
|
|
805
|
-
|
|
806
|
-
**Always use CONCURRENTLY in production**:
|
|
807
|
-
|
|
808
|
-
```sql
|
|
809
|
-
-- ✅ SAFE (non-blocking)
|
|
810
|
-
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
|
|
811
|
-
|
|
812
|
-
-- ❌ UNSAFE (blocks writes)
|
|
813
|
-
CREATE INDEX idx_users_email ON users(email);
|
|
814
|
-
```
|
|
815
|
-
|
|
816
|
-
CONCURRENTLY allows reads/writes to continue during index creation.
|
|
817
|
-
|
|
818
|
-
## When to Use Zero-Downtime
|
|
819
|
-
|
|
820
|
-
**Use expand/contract for**:
|
|
821
|
-
- Column renames
|
|
822
|
-
- Type changes
|
|
823
|
-
- Table splits/merges
|
|
824
|
-
- Adding NOT NULL constraints
|
|
825
|
-
- Removing columns with data
|
|
826
|
-
|
|
827
|
-
**Don't need for** (already safe):
|
|
828
|
-
- Adding nullable columns with constant defaults
|
|
829
|
-
- Creating new tables
|
|
830
|
-
- Adding indexes (use CONCURRENTLY)
|
|
831
|
-
- Adding constraints (check, foreign key) to empty tables
|
|
832
|
-
- Renaming tables (if app uses dynamic table names)
|
|
833
|
-
|
|
834
|
-
## Risks & Trade-offs
|
|
835
|
-
|
|
836
|
-
**Pros**:
|
|
837
|
-
- Zero downtime
|
|
838
|
-
- Gradual rollout
|
|
839
|
-
- Safe rollback at any phase
|
|
840
|
-
- No maintenance window needed
|
|
841
|
-
|
|
842
|
-
**Cons**:
|
|
843
|
-
- Longer deployment cycle (days/weeks vs minutes)
|
|
844
|
-
- Increased complexity (6 phases vs 1)
|
|
845
|
-
- Duplicate data temporarily (storage cost)
|
|
846
|
-
- Requires app coordination (multiple deploys)
|
|
847
|
-
- Sync triggers add write overhead
|
|
848
|
-
|
|
849
|
-
## Decision Tree
|
|
850
|
-
|
|
851
|
-
```
|
|
852
|
-
Does migration break existing app code?
|
|
853
|
-
NO → Standard migration ✅
|
|
854
|
-
YES → Does it affect hot path (high traffic table)?
|
|
855
|
-
NO → Maintenance window OK (document downtime) ✅
|
|
856
|
-
YES → Use expand/contract pattern ✅
|
|
857
|
-
```
|
|
858
|
-
|
|
859
|
-
## Supabase-Specific Notes
|
|
860
|
-
|
|
861
|
-
Supabase emphasizes **forward-only migrations**:
|
|
862
|
-
- No explicit rollback command in CLI
|
|
863
|
-
- Use `supabase db reset` for local development only
|
|
864
|
-
- Production rollbacks require new forward migrations
|
|
865
|
-
|
|
866
|
-
This aligns perfectly with expand/contract philosophy:
|
|
867
|
-
Each phase is a separate forward migration.
|
|
868
|
-
elicit: false
|
|
869
|
-
|
|
870
|
-
- id: supabase-cli
|
|
871
|
-
title: "Supabase CLI Integration"
|
|
872
|
-
instruction: |
|
|
873
|
-
How DB Sage migration workflow integrates with Supabase CLI.
|
|
874
|
-
|
|
875
|
-
## Supabase CLI Migration Workflow
|
|
876
|
-
|
|
877
|
-
### Local Development
|
|
878
|
-
|
|
879
|
-
```bash
|
|
880
|
-
# 1. Create new migration
|
|
881
|
-
supabase migration new add_users_table
|
|
882
|
-
|
|
883
|
-
# 2. Edit migration file in supabase/migrations/<timestamp>_add_users_table.sql
|
|
884
|
-
|
|
885
|
-
# 3. Test migration locally
|
|
886
|
-
supabase db reset # Recreates DB from scratch + applies all migrations
|
|
887
|
-
|
|
888
|
-
# 4. Verify changes
|
|
889
|
-
supabase db diff --schema public # Show diff vs remote
|
|
890
|
-
|
|
891
|
-
# 5. Commit migration file
|
|
892
|
-
git add supabase/migrations/<timestamp>_add_users_table.sql
|
|
893
|
-
git commit -m "feat: add users table"
|
|
894
|
-
```
|
|
895
|
-
|
|
896
|
-
### Staging/Production Deployment
|
|
897
|
-
|
|
898
|
-
**CI/CD Pipeline** (GitHub Actions recommended):
|
|
899
|
-
|
|
900
|
-
```yaml
|
|
901
|
-
name: Deploy to Production
|
|
902
|
-
on:
|
|
903
|
-
push:
|
|
904
|
-
branches: [main]
|
|
905
|
-
|
|
906
|
-
jobs:
|
|
907
|
-
deploy:
|
|
908
|
-
runs-on: ubuntu-latest
|
|
909
|
-
steps:
|
|
910
|
-
- uses: actions/checkout@v3
|
|
911
|
-
|
|
912
|
-
- name: Setup Supabase CLI
|
|
913
|
-
uses: supabase/setup-cli@v1
|
|
914
|
-
|
|
915
|
-
- name: Deploy migrations
|
|
916
|
-
run: supabase db push
|
|
917
|
-
env:
|
|
918
|
-
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
|
919
|
-
SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }}
|
|
920
|
-
SUPABASE_PROJECT_ID: ${{ secrets.SUPABASE_PROJECT_ID }}
|
|
921
|
-
```
|
|
922
|
-
|
|
923
|
-
### Pulling Remote Schema
|
|
924
|
-
|
|
925
|
-
Sync production schema to local:
|
|
926
|
-
|
|
927
|
-
```bash
|
|
928
|
-
# Pull all remote migrations
|
|
929
|
-
supabase db pull
|
|
930
|
-
|
|
931
|
-
# Creates: supabase/migrations/<timestamp>_remote_schema.sql
|
|
932
|
-
```
|
|
933
|
-
|
|
934
|
-
**When to use**: After manual changes in Supabase Dashboard.
|
|
935
|
-
|
|
936
|
-
## DB Sage Enhancement Layer
|
|
937
|
-
|
|
938
|
-
DB Sage adds custom schema_migrations table **on top** of Supabase CLI:
|
|
939
|
-
|
|
940
|
-
| Feature | Supabase CLI | DB Sage schema_migrations |
|
|
941
|
-
|---------|--------------|---------------------------|
|
|
942
|
-
| Version tracking | ✅ Timestamp in filename | ✅ + applied_at |
|
|
943
|
-
| Applied by | ❌ | ✅ current_user |
|
|
944
|
-
| Checksums | ❌ | ✅ SHA256 |
|
|
945
|
-
| Rollback scripts | ❌ | ✅ Stored in table |
|
|
946
|
-
| Success/failure | ❌ | ✅ success boolean |
|
|
947
|
-
| Execution time | ❌ | ✅ execution_time_ms |
|
|
948
|
-
|
|
949
|
-
**Use both together**:
|
|
950
|
-
- Supabase CLI for development workflow and deployments
|
|
951
|
-
- schema_migrations for production audit trail
|
|
952
|
-
|
|
953
|
-
## Permission Management
|
|
954
|
-
|
|
955
|
-
**Critical**: Migrations created in Supabase Dashboard may have wrong owner.
|
|
956
|
-
|
|
957
|
-
```sql
|
|
958
|
-
-- Fix ownership (run after pulling remote schema)
|
|
959
|
-
ALTER TABLE users OWNER TO postgres;
|
|
960
|
-
ALTER TYPE user_role OWNER TO postgres;
|
|
961
|
-
ALTER FUNCTION get_user_role() OWNER TO postgres;
|
|
962
|
-
```
|
|
963
|
-
|
|
964
|
-
**Best practice**: Create all migrations as SQL files, not via Dashboard.
|
|
965
|
-
|
|
966
|
-
## Migration List & Status
|
|
967
|
-
|
|
968
|
-
```bash
|
|
969
|
-
# Check migration status (local vs remote)
|
|
970
|
-
supabase migration list
|
|
971
|
-
|
|
972
|
-
# Output:
|
|
973
|
-
# Local Remote Status
|
|
974
|
-
# 20240101 20240101 Applied
|
|
975
|
-
# 20240102 - Pending
|
|
976
|
-
```
|
|
977
|
-
|
|
978
|
-
## Rollback Strategy
|
|
979
|
-
|
|
980
|
-
Supabase CLI does **not** support explicit rollback.
|
|
981
|
-
|
|
982
|
-
**Options**:
|
|
983
|
-
1. **Local**: `supabase db reset` (destructive, dev only)
|
|
984
|
-
2. **Production**: Create new forward migration that undoes changes
|
|
985
|
-
3. **DB Sage**: Use rollback_script from schema_migrations table
|
|
986
|
-
|
|
987
|
-
**Example** (using DB Sage):
|
|
988
|
-
```sql
|
|
989
|
-
-- Retrieve rollback script
|
|
990
|
-
SELECT rollback_script FROM schema_migrations
|
|
991
|
-
WHERE version = '20251027120000';
|
|
992
|
-
|
|
993
|
-
-- Execute retrieved script
|
|
994
|
-
```
|
|
995
|
-
|
|
996
|
-
## Best Practices
|
|
997
|
-
|
|
998
|
-
1. **Always test locally** with `supabase db reset` before deploying
|
|
999
|
-
2. **Use CI/CD** for staging/production (not local machine)
|
|
1000
|
-
3. **Separate projects** for dev/staging/prod
|
|
1001
|
-
4. **Store credentials** as GitHub secrets (never commit)
|
|
1002
|
-
5. **Reassign ownership** to postgres after dashboard changes
|
|
1003
|
-
6. **Small migrations** (easier to debug, faster to apply)
|
|
1004
|
-
7. **Idempotent scripts** (IF NOT EXISTS, DROP IF EXISTS)
|
|
1005
|
-
|
|
1006
|
-
## Troubleshooting
|
|
1007
|
-
|
|
1008
|
-
**"must be owner of table" error**:
|
|
1009
|
-
```sql
|
|
1010
|
-
ALTER TABLE mytable OWNER TO postgres;
|
|
1011
|
-
```
|
|
1012
|
-
|
|
1013
|
-
**Migration applied but not tracked**:
|
|
1014
|
-
```bash
|
|
1015
|
-
supabase migration repair <version> --status applied
|
|
1016
|
-
```
|
|
1017
|
-
|
|
1018
|
-
**Reset local DB** (development only):
|
|
1019
|
-
```bash
|
|
1020
|
-
supabase db reset
|
|
1021
|
-
```
|
|
1022
|
-
elicit: false
|
|
1
|
+
---
|
|
2
|
+
template_name: "Schema Migration Plan"
|
|
3
|
+
template_version: "1.0.0"
|
|
4
|
+
output_format: "markdown"
|
|
5
|
+
destination: "migration-plan.md"
|
|
6
|
+
description: "Plan and validate a safe schema migration with rollback and tests"
|
|
7
|
+
|
|
8
|
+
sections:
|
|
9
|
+
- id: summary
|
|
10
|
+
title: "Executive Summary"
|
|
11
|
+
instruction: |
|
|
12
|
+
Summarize the change:
|
|
13
|
+
- Objective and scope
|
|
14
|
+
- Risk level (Low/Med/High) and why
|
|
15
|
+
- Environments impacted (dev/staging/prod)
|
|
16
|
+
- Expected migration time window and rollback window
|
|
17
|
+
elicit: true
|
|
18
|
+
|
|
19
|
+
- id: change-set
|
|
20
|
+
title: "Change Set"
|
|
21
|
+
instruction: |
|
|
22
|
+
Detail every schema change:
|
|
23
|
+
- Tables created/altered/dropped
|
|
24
|
+
- Columns added/modified/removed (types, defaults, constraints)
|
|
25
|
+
- Indexes (create/alter/drop)
|
|
26
|
+
- Functions/triggers/views (create/replace/drop)
|
|
27
|
+
- RLS policies (add/remove/modify)
|
|
28
|
+
elicit: true
|
|
29
|
+
|
|
30
|
+
- id: dependencies
|
|
31
|
+
title: "Dependencies & Ordering"
|
|
32
|
+
instruction: |
|
|
33
|
+
List dependencies and execution order:
|
|
34
|
+
1) Extensions
|
|
35
|
+
2) Tables & constraints
|
|
36
|
+
3) Functions
|
|
37
|
+
4) Triggers
|
|
38
|
+
5) RLS
|
|
39
|
+
6) Views / MatViews
|
|
40
|
+
Note any cross-object dependencies that require two-phase rollout.
|
|
41
|
+
elicit: true
|
|
42
|
+
|
|
43
|
+
- id: data-migration
|
|
44
|
+
title: "Data Migration & Backfill"
|
|
45
|
+
instruction: |
|
|
46
|
+
Strategies for migrating existing data safely at scale.
|
|
47
|
+
|
|
48
|
+
## Small Data Sets (< 100K rows)
|
|
49
|
+
|
|
50
|
+
**Simple approach** for tables with few rows:
|
|
51
|
+
|
|
52
|
+
```sql
|
|
53
|
+
-- Direct UPDATE (< 100K rows)
|
|
54
|
+
UPDATE users SET email_address = email
|
|
55
|
+
WHERE email_address IS NULL;
|
|
56
|
+
|
|
57
|
+
-- Single transaction, fast execution
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**When safe**:
|
|
61
|
+
- Small tables (< 100K rows)
|
|
62
|
+
- Low traffic tables
|
|
63
|
+
- Maintenance window available
|
|
64
|
+
|
|
65
|
+
## Large Data Sets (> 100K rows)
|
|
66
|
+
|
|
67
|
+
**Batched approach** prevents table locks and reduces transaction size.
|
|
68
|
+
|
|
69
|
+
### Pattern 1: Basic Batching (Single Process)
|
|
70
|
+
|
|
71
|
+
```sql
|
|
72
|
+
-- Backfill in batches to avoid long locks
|
|
73
|
+
DO $$
|
|
74
|
+
DECLARE
|
|
75
|
+
batch_size INT := 1000; -- Adjust based on row size and available memory
|
|
76
|
+
rows_updated INT;
|
|
77
|
+
total_updated INT := 0;
|
|
78
|
+
batch_count INT := 0;
|
|
79
|
+
BEGIN
|
|
80
|
+
LOOP
|
|
81
|
+
-- Update one batch
|
|
82
|
+
WITH batch AS (
|
|
83
|
+
SELECT id FROM users
|
|
84
|
+
WHERE email_address IS NULL
|
|
85
|
+
LIMIT batch_size
|
|
86
|
+
FOR UPDATE SKIP LOCKED -- ⭐ CRITICAL: Avoid lock contention
|
|
87
|
+
)
|
|
88
|
+
UPDATE users
|
|
89
|
+
SET email_address = email
|
|
90
|
+
FROM batch
|
|
91
|
+
WHERE users.id = batch.id;
|
|
92
|
+
|
|
93
|
+
GET DIAGNOSTICS rows_updated = ROW_COUNT;
|
|
94
|
+
EXIT WHEN rows_updated = 0; -- No more rows to process
|
|
95
|
+
|
|
96
|
+
total_updated := total_updated + rows_updated;
|
|
97
|
+
batch_count := batch_count + 1;
|
|
98
|
+
|
|
99
|
+
RAISE NOTICE 'Batch %: Updated % rows (total: %)',
|
|
100
|
+
batch_count, rows_updated, total_updated;
|
|
101
|
+
|
|
102
|
+
-- Throttle to avoid overloading DB
|
|
103
|
+
PERFORM pg_sleep(0.1); -- 100ms pause between batches
|
|
104
|
+
END LOOP;
|
|
105
|
+
|
|
106
|
+
RAISE NOTICE 'Backfill complete: % batches, % total rows',
|
|
107
|
+
batch_count, total_updated;
|
|
108
|
+
END $$;
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Key techniques**:
|
|
112
|
+
- `FOR UPDATE SKIP LOCKED` - Avoids lock contention, enables parallel processing
|
|
113
|
+
- `LIMIT batch_size` - Controls transaction size
|
|
114
|
+
- `pg_sleep()` - Throttles load, allows other transactions to proceed
|
|
115
|
+
- `RAISE NOTICE` - Progress tracking
|
|
116
|
+
|
|
117
|
+
### Pattern 2: Parallel Batching (Multiple Workers)
|
|
118
|
+
|
|
119
|
+
**For very large tables** (millions of rows), run multiple workers concurrently:
|
|
120
|
+
|
|
121
|
+
```sql
|
|
122
|
+
-- Worker 1 (run in psql session 1)
|
|
123
|
+
DO $$
|
|
124
|
+
DECLARE
|
|
125
|
+
batch_size INT := 5000;
|
|
126
|
+
worker_id INT := 1;
|
|
127
|
+
rows_updated INT;
|
|
128
|
+
BEGIN
|
|
129
|
+
LOOP
|
|
130
|
+
WITH batch AS (
|
|
131
|
+
SELECT id FROM orders
|
|
132
|
+
WHERE status_new IS NULL
|
|
133
|
+
ORDER BY id -- ⭐ CRITICAL: Deterministic ordering
|
|
134
|
+
LIMIT batch_size
|
|
135
|
+
FOR UPDATE SKIP LOCKED -- Skip rows locked by other workers
|
|
136
|
+
)
|
|
137
|
+
UPDATE orders
|
|
138
|
+
SET status_new = status
|
|
139
|
+
FROM batch
|
|
140
|
+
WHERE orders.id = batch.id;
|
|
141
|
+
|
|
142
|
+
GET DIAGNOSTICS rows_updated = ROW_COUNT;
|
|
143
|
+
EXIT WHEN rows_updated = 0;
|
|
144
|
+
|
|
145
|
+
RAISE NOTICE '[Worker %] Updated % rows', worker_id, rows_updated;
|
|
146
|
+
PERFORM pg_sleep(0.05); -- Shorter pause with multiple workers
|
|
147
|
+
END LOOP;
|
|
148
|
+
END $$;
|
|
149
|
+
|
|
150
|
+
-- Worker 2-4: Same script, different worker_id in RAISE NOTICE
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Why it works**:
|
|
154
|
+
- `FOR UPDATE SKIP LOCKED` allows workers to grab different rows
|
|
155
|
+
- Each worker skips rows locked by others
|
|
156
|
+
- No deadlocks or contention
|
|
157
|
+
- Near-linear speedup (4 workers ≈ 4x faster)
|
|
158
|
+
|
|
159
|
+
### Pattern 3: Progress Tracking Table
|
|
160
|
+
|
|
161
|
+
**Track progress** for resumable migrations:
|
|
162
|
+
|
|
163
|
+
```sql
|
|
164
|
+
-- Create progress tracking table
|
|
165
|
+
CREATE TABLE migration_progress (
|
|
166
|
+
migration_name TEXT PRIMARY KEY,
|
|
167
|
+
last_processed_id BIGINT,
|
|
168
|
+
total_processed BIGINT DEFAULT 0,
|
|
169
|
+
started_at TIMESTAMPTZ DEFAULT NOW(),
|
|
170
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
-- Resumable backfill with progress tracking
|
|
174
|
+
DO $$
|
|
175
|
+
DECLARE
|
|
176
|
+
batch_size INT := 5000;
|
|
177
|
+
last_id BIGINT;
|
|
178
|
+
rows_updated INT;
|
|
179
|
+
migration TEXT := 'users_email_address_backfill';
|
|
180
|
+
BEGIN
|
|
181
|
+
-- Get last processed ID (resume from failure)
|
|
182
|
+
SELECT COALESCE(last_processed_id, 0) INTO last_id
|
|
183
|
+
FROM migration_progress
|
|
184
|
+
WHERE migration_name = migration;
|
|
185
|
+
|
|
186
|
+
-- Initialize if not exists
|
|
187
|
+
INSERT INTO migration_progress (migration_name, last_processed_id)
|
|
188
|
+
VALUES (migration, 0)
|
|
189
|
+
ON CONFLICT (migration_name) DO NOTHING;
|
|
190
|
+
|
|
191
|
+
LOOP
|
|
192
|
+
-- Process batch starting after last_id
|
|
193
|
+
WITH batch AS (
|
|
194
|
+
SELECT id FROM users
|
|
195
|
+
WHERE id > last_id
|
|
196
|
+
AND email_address IS NULL
|
|
197
|
+
ORDER BY id -- Deterministic ordering
|
|
198
|
+
LIMIT batch_size
|
|
199
|
+
FOR UPDATE SKIP LOCKED
|
|
200
|
+
)
|
|
201
|
+
UPDATE users
|
|
202
|
+
SET email_address = email
|
|
203
|
+
FROM batch
|
|
204
|
+
WHERE users.id = batch.id
|
|
205
|
+
RETURNING users.id INTO last_id;
|
|
206
|
+
|
|
207
|
+
GET DIAGNOSTICS rows_updated = ROW_COUNT;
|
|
208
|
+
EXIT WHEN rows_updated = 0;
|
|
209
|
+
|
|
210
|
+
-- Update progress
|
|
211
|
+
UPDATE migration_progress
|
|
212
|
+
SET
|
|
213
|
+
last_processed_id = last_id,
|
|
214
|
+
total_processed = total_processed + rows_updated,
|
|
215
|
+
updated_at = NOW()
|
|
216
|
+
WHERE migration_name = migration;
|
|
217
|
+
|
|
218
|
+
RAISE NOTICE '[%] Processed up to ID %, % rows this batch',
|
|
219
|
+
migration, last_id, rows_updated;
|
|
220
|
+
|
|
221
|
+
PERFORM pg_sleep(0.1);
|
|
222
|
+
END LOOP;
|
|
223
|
+
|
|
224
|
+
RAISE NOTICE '[%] Complete: % total rows',
|
|
225
|
+
migration,
|
|
226
|
+
(SELECT total_processed FROM migration_progress WHERE migration_name = migration);
|
|
227
|
+
END $$;
|
|
228
|
+
|
|
229
|
+
-- Check progress (run in separate session)
|
|
230
|
+
SELECT
|
|
231
|
+
migration_name,
|
|
232
|
+
last_processed_id,
|
|
233
|
+
total_processed,
|
|
234
|
+
updated_at,
|
|
235
|
+
AGE(NOW(), updated_at) AS time_since_update
|
|
236
|
+
FROM migration_progress;
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Benefits**:
|
|
240
|
+
- Resumable on failure/cancellation
|
|
241
|
+
- Live progress monitoring
|
|
242
|
+
- Deterministic (no duplicate processing)
|
|
243
|
+
|
|
244
|
+
## Batch Size Guidelines
|
|
245
|
+
|
|
246
|
+
**Factors to consider**:
|
|
247
|
+
- Row size (larger rows → smaller batches)
|
|
248
|
+
- Available memory (`maintenance_work_mem`)
|
|
249
|
+
- Transaction timeout limits
|
|
250
|
+
- Lock contention sensitivity
|
|
251
|
+
|
|
252
|
+
**Recommended sizes**:
|
|
253
|
+
| Row Size | Batch Size | Reasoning |
|
|
254
|
+
|----------|------------|-----------|
|
|
255
|
+
| Small (< 1KB) | 5,000-10,000 | Fast, low memory |
|
|
256
|
+
| Medium (1-10KB) | 1,000-5,000 | Balance speed/memory |
|
|
257
|
+
| Large (> 10KB) | 100-1,000 | Avoid memory exhaustion |
|
|
258
|
+
| JSONB/text heavy | 500-2,000 | Variable size risk |
|
|
259
|
+
|
|
260
|
+
## Throttling Strategies
|
|
261
|
+
|
|
262
|
+
```sql
|
|
263
|
+
-- Light throttle (10 batches/sec, low impact)
|
|
264
|
+
PERFORM pg_sleep(0.1);
|
|
265
|
+
|
|
266
|
+
-- Medium throttle (5 batches/sec, safer for production)
|
|
267
|
+
PERFORM pg_sleep(0.2);
|
|
268
|
+
|
|
269
|
+
-- Heavy throttle (2 batches/sec, minimal impact)
|
|
270
|
+
PERFORM pg_sleep(0.5);
|
|
271
|
+
|
|
272
|
+
-- Adaptive throttle (based on DB load)
|
|
273
|
+
-- Check pg_stat_activity connection count
|
|
274
|
+
SELECT pg_sleep(
|
|
275
|
+
CASE
|
|
276
|
+
WHEN (SELECT count(*) FROM pg_stat_activity WHERE state = 'active') > 50
|
|
277
|
+
THEN 0.5 -- High load, slow down
|
|
278
|
+
ELSE 0.1 -- Normal load, faster
|
|
279
|
+
END
|
|
280
|
+
);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Verification Queries
|
|
284
|
+
|
|
285
|
+
```sql
|
|
286
|
+
-- Check completion
|
|
287
|
+
SELECT COUNT(*) AS remaining
|
|
288
|
+
FROM users
|
|
289
|
+
WHERE email_address IS NULL;
|
|
290
|
+
-- Expected: 0
|
|
291
|
+
|
|
292
|
+
-- Check data integrity
|
|
293
|
+
SELECT COUNT(*) AS mismatches
|
|
294
|
+
FROM users
|
|
295
|
+
WHERE email != email_address;
|
|
296
|
+
-- Expected: 0 (or acceptable threshold)
|
|
297
|
+
|
|
298
|
+
-- Sample verification
|
|
299
|
+
SELECT id, email, email_address
|
|
300
|
+
FROM users
|
|
301
|
+
WHERE email != email_address
|
|
302
|
+
OR email_address IS NULL
|
|
303
|
+
LIMIT 100;
|
|
304
|
+
|
|
305
|
+
-- Performance check (should use index)
|
|
306
|
+
EXPLAIN ANALYZE
|
|
307
|
+
SELECT * FROM users WHERE email_address IS NULL;
|
|
308
|
+
-- Look for "Index Scan" not "Seq Scan"
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Lock Impact Analysis
|
|
312
|
+
|
|
313
|
+
```sql
|
|
314
|
+
-- Monitor locks during backfill
|
|
315
|
+
SELECT
|
|
316
|
+
pid,
|
|
317
|
+
usename,
|
|
318
|
+
query_start,
|
|
319
|
+
state,
|
|
320
|
+
wait_event_type,
|
|
321
|
+
wait_event,
|
|
322
|
+
query
|
|
323
|
+
FROM pg_stat_activity
|
|
324
|
+
WHERE state != 'idle'
|
|
325
|
+
AND query LIKE '%users%'
|
|
326
|
+
ORDER BY query_start;
|
|
327
|
+
|
|
328
|
+
-- Check for lock contention
|
|
329
|
+
SELECT
|
|
330
|
+
locktype,
|
|
331
|
+
relation::regclass AS table_name,
|
|
332
|
+
mode,
|
|
333
|
+
granted,
|
|
334
|
+
COUNT(*) AS lock_count
|
|
335
|
+
FROM pg_locks
|
|
336
|
+
WHERE relation::regclass::text LIKE '%users%'
|
|
337
|
+
GROUP BY locktype, relation, mode, granted;
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Best Practices Summary
|
|
341
|
+
|
|
342
|
+
1. **Use FOR UPDATE SKIP LOCKED** for parallelizable batching
|
|
343
|
+
2. **ORDER BY** for deterministic processing (resumable)
|
|
344
|
+
3. **Batch size 1,000-10,000** depending on row size
|
|
345
|
+
4. **Throttle with pg_sleep()** to reduce DB load
|
|
346
|
+
5. **Track progress** in separate table
|
|
347
|
+
6. **Monitor locks** during execution
|
|
348
|
+
7. **Verify data** before and after migration
|
|
349
|
+
8. **Test in staging** with production-size data
|
|
350
|
+
|
|
351
|
+
## When NOT to Use Batching
|
|
352
|
+
|
|
353
|
+
- Tables < 100K rows (direct UPDATE faster)
|
|
354
|
+
- Maintenance window available (faster without throttling)
|
|
355
|
+
- No other traffic (no contention risk)
|
|
356
|
+
|
|
357
|
+
## Common Pitfalls
|
|
358
|
+
|
|
359
|
+
- **Forgetting FOR UPDATE SKIP LOCKED** → deadlocks with parallel workers
|
|
360
|
+
- **No ORDER BY** → non-deterministic, duplicate processing
|
|
361
|
+
- **Too large batches** → lock contention, memory issues
|
|
362
|
+
- **No throttling** → DB overload, affects production traffic
|
|
363
|
+
- **No progress tracking** → can't resume on failure
|
|
364
|
+
elicit: true
|
|
365
|
+
|
|
366
|
+
- id: safety
|
|
367
|
+
title: "Safety & Rollback"
|
|
368
|
+
instruction: |
|
|
369
|
+
Safety plan:
|
|
370
|
+
- Pre-migration snapshot strategy
|
|
371
|
+
- Rollback script outline (what to undo, in order)
|
|
372
|
+
- Roll-forward strategy if rollback is unsafe
|
|
373
|
+
- Advisory locks to avoid concurrent runs
|
|
374
|
+
elicit: true
|
|
375
|
+
|
|
376
|
+
- id: testing
|
|
377
|
+
title: "Testing Strategy"
|
|
378
|
+
instruction: |
|
|
379
|
+
Tests to run:
|
|
380
|
+
- Dry-run (BEGIN; \i file; ROLLBACK)
|
|
381
|
+
- Smoke tests (post-migration)
|
|
382
|
+
- RLS positive/negative tests (impersonation)
|
|
383
|
+
- Performance baselines & EXPLAIN checks on hot paths
|
|
384
|
+
elicit: true
|
|
385
|
+
|
|
386
|
+
- id: operations
|
|
387
|
+
title: "Operational Runbook"
|
|
388
|
+
instruction: |
|
|
389
|
+
Provide exact commands with placeholders:
|
|
390
|
+
- Set env, check psql/pg_dump versions
|
|
391
|
+
- Take snapshot
|
|
392
|
+
- Apply migration
|
|
393
|
+
- Post snapshot
|
|
394
|
+
- Run smoke tests
|
|
395
|
+
Include expected outputs and success criteria.
|
|
396
|
+
elicit: true
|
|
397
|
+
|
|
398
|
+
- id: communication
|
|
399
|
+
title: "Communication & Approval"
|
|
400
|
+
instruction: |
|
|
401
|
+
- Stakeholders, approvers
|
|
402
|
+
- Change window notice
|
|
403
|
+
- Post-deploy validation owners
|
|
404
|
+
- Incident/rollback contact path
|
|
405
|
+
elicit: true
|
|
406
|
+
|
|
407
|
+
- id: version-tracking
|
|
408
|
+
title: "Schema Version Tracking"
|
|
409
|
+
instruction: |
|
|
410
|
+
DB Sage uses custom schema_migrations table for enhanced tracking beyond Supabase's built-in migration system.
|
|
411
|
+
|
|
412
|
+
## Setup (run once in initial migration)
|
|
413
|
+
|
|
414
|
+
```sql
|
|
415
|
+
CREATE TABLE IF NOT EXISTS public.schema_migrations (
|
|
416
|
+
version TEXT PRIMARY KEY,
|
|
417
|
+
name TEXT NOT NULL,
|
|
418
|
+
applied_at TIMESTAMPTZ DEFAULT NOW(),
|
|
419
|
+
applied_by TEXT NOT NULL,
|
|
420
|
+
execution_time_ms INTEGER,
|
|
421
|
+
success BOOLEAN NOT NULL DEFAULT false,
|
|
422
|
+
checksum TEXT NOT NULL, -- SHA256 do arquivo de migration
|
|
423
|
+
rollback_script TEXT, -- Script de rollback
|
|
424
|
+
notes TEXT,
|
|
425
|
+
CONSTRAINT valid_checksum CHECK (length(checksum) = 64),
|
|
426
|
+
CONSTRAINT valid_version CHECK (version ~ '^\d{14}$') -- Format: YYYYMMDDHHmmss
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
CREATE INDEX idx_migrations_applied_at
|
|
430
|
+
ON schema_migrations(applied_at DESC);
|
|
431
|
+
|
|
432
|
+
CREATE INDEX idx_migrations_success
|
|
433
|
+
ON schema_migrations(success) WHERE success = false;
|
|
434
|
+
|
|
435
|
+
COMMENT ON TABLE schema_migrations IS
|
|
436
|
+
'Custom migration tracking with checksums, rollback scripts, and execution metadata';
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## Migration File Structure
|
|
440
|
+
|
|
441
|
+
Every migration should follow this template:
|
|
442
|
+
|
|
443
|
+
```sql
|
|
444
|
+
-- Migration: 20251027120000_add_users_table
|
|
445
|
+
-- Checksum: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
|
446
|
+
-- Applied by: user@example.com
|
|
447
|
+
-- Rollback: See section at end of file
|
|
448
|
+
|
|
449
|
+
BEGIN;
|
|
450
|
+
|
|
451
|
+
-- Record migration start
|
|
452
|
+
INSERT INTO public.schema_migrations (
|
|
453
|
+
version, name, applied_by, success, checksum, rollback_script, notes
|
|
454
|
+
)
|
|
455
|
+
VALUES (
|
|
456
|
+
'20251027120000',
|
|
457
|
+
'add_users_table',
|
|
458
|
+
current_user,
|
|
459
|
+
false, -- Will update to true on success
|
|
460
|
+
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
|
|
461
|
+
$rollback$
|
|
462
|
+
-- Rollback script
|
|
463
|
+
DROP TABLE IF EXISTS users CASCADE;
|
|
464
|
+
DELETE FROM public.schema_migrations WHERE version = '20251027120000';
|
|
465
|
+
$rollback$,
|
|
466
|
+
'Initial users table creation'
|
|
467
|
+
)
|
|
468
|
+
ON CONFLICT (version) DO NOTHING;
|
|
469
|
+
|
|
470
|
+
-- Migration DDL/DML statements here
|
|
471
|
+
CREATE TABLE users (
|
|
472
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
473
|
+
email TEXT UNIQUE NOT NULL,
|
|
474
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
-- Update to success
|
|
478
|
+
UPDATE public.schema_migrations
|
|
479
|
+
SET
|
|
480
|
+
success = true,
|
|
481
|
+
execution_time_ms = EXTRACT(MILLISECOND FROM (NOW() - applied_at))::INTEGER
|
|
482
|
+
WHERE version = '20251027120000';
|
|
483
|
+
|
|
484
|
+
COMMIT;
|
|
485
|
+
|
|
486
|
+
-- ROLLBACK SECTION (DO NOT EXECUTE - stored in schema_migrations table)
|
|
487
|
+
/*
|
|
488
|
+
BEGIN;
|
|
489
|
+
DROP TABLE IF EXISTS users CASCADE;
|
|
490
|
+
DELETE FROM public.schema_migrations WHERE version = '20251027120000';
|
|
491
|
+
COMMIT;
|
|
492
|
+
*/
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## Checksum Generation
|
|
496
|
+
|
|
497
|
+
Generate SHA256 checksum before applying migration:
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
# Generate checksum (exclude rollback section)
|
|
501
|
+
checksum=$(sha256sum migration.sql | awk '{print $1}')
|
|
502
|
+
echo "Checksum: $checksum"
|
|
503
|
+
|
|
504
|
+
# Verify against stored checksum after application
|
|
505
|
+
psql -c "SELECT version, checksum FROM schema_migrations WHERE version = '20251027120000'"
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
## Query Migration History
|
|
509
|
+
|
|
510
|
+
```sql
|
|
511
|
+
-- Recent successful migrations
|
|
512
|
+
SELECT
|
|
513
|
+
version,
|
|
514
|
+
name,
|
|
515
|
+
applied_at,
|
|
516
|
+
applied_by,
|
|
517
|
+
execution_time_ms,
|
|
518
|
+
notes
|
|
519
|
+
FROM schema_migrations
|
|
520
|
+
WHERE success = true
|
|
521
|
+
ORDER BY applied_at DESC
|
|
522
|
+
LIMIT 10;
|
|
523
|
+
|
|
524
|
+
-- Failed migrations (investigate)
|
|
525
|
+
SELECT
|
|
526
|
+
version,
|
|
527
|
+
name,
|
|
528
|
+
applied_at,
|
|
529
|
+
applied_by,
|
|
530
|
+
notes
|
|
531
|
+
FROM schema_migrations
|
|
532
|
+
WHERE success = false
|
|
533
|
+
ORDER BY applied_at DESC;
|
|
534
|
+
|
|
535
|
+
-- Pending rollbacks
|
|
536
|
+
SELECT
|
|
537
|
+
version,
|
|
538
|
+
name,
|
|
539
|
+
applied_at,
|
|
540
|
+
LENGTH(rollback_script) AS rollback_size_bytes
|
|
541
|
+
FROM schema_migrations
|
|
542
|
+
WHERE success = true
|
|
543
|
+
AND rollback_script IS NOT NULL
|
|
544
|
+
ORDER BY applied_at DESC;
|
|
545
|
+
|
|
546
|
+
-- Checksum verification (detect tampering)
|
|
547
|
+
SELECT
|
|
548
|
+
version,
|
|
549
|
+
name,
|
|
550
|
+
checksum,
|
|
551
|
+
applied_at
|
|
552
|
+
FROM schema_migrations
|
|
553
|
+
WHERE success = true
|
|
554
|
+
ORDER BY applied_at DESC;
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
## Integration with Supabase CLI
|
|
558
|
+
|
|
559
|
+
Supabase CLI manages migrations in `supabase/migrations/` directory with timestamp prefix.
|
|
560
|
+
Our schema_migrations table complements this by adding:
|
|
561
|
+
- WHO applied the migration (applied_by)
|
|
562
|
+
- WHEN exactly (applied_at)
|
|
563
|
+
- SUCCESS status (for partial failures)
|
|
564
|
+
- CHECKSUM verification (integrity)
|
|
565
|
+
- ROLLBACK script (automated recovery)
|
|
566
|
+
|
|
567
|
+
Use both systems together:
|
|
568
|
+
- Supabase CLI for development workflow (`supabase db diff`, `supabase db reset`)
|
|
569
|
+
- schema_migrations for production audit trail and rollback capability
|
|
570
|
+
|
|
571
|
+
## Rollback Execution
|
|
572
|
+
|
|
573
|
+
To rollback a migration:
|
|
574
|
+
|
|
575
|
+
```sql
|
|
576
|
+
-- 1. Retrieve rollback script
|
|
577
|
+
SELECT rollback_script
|
|
578
|
+
FROM schema_migrations
|
|
579
|
+
WHERE version = '20251027120000';
|
|
580
|
+
|
|
581
|
+
-- 2. Execute rollback script (in transaction)
|
|
582
|
+
BEGIN;
|
|
583
|
+
-- Copy rollback script here
|
|
584
|
+
DROP TABLE IF EXISTS users CASCADE;
|
|
585
|
+
DELETE FROM public.schema_migrations WHERE version = '20251027120000';
|
|
586
|
+
COMMIT;
|
|
587
|
+
|
|
588
|
+
-- 3. Verify rollback
|
|
589
|
+
SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'users';
|
|
590
|
+
-- Should return 0
|
|
591
|
+
```
|
|
592
|
+
elicit: false
|
|
593
|
+
|
|
594
|
+
- id: zero-downtime
|
|
595
|
+
title: "Zero-Downtime Migrations"
|
|
596
|
+
instruction: |
|
|
597
|
+
Use expand/contract pattern for non-breaking schema changes. Forward-only migrations that allow
|
|
598
|
+
old and new application versions to coexist.
|
|
599
|
+
|
|
600
|
+
## Overview - Expand/Contract Pattern
|
|
601
|
+
|
|
602
|
+
Zero-downtime migrations use 4-6 phases:
|
|
603
|
+
|
|
604
|
+
1. **EXPAND** - Add new schema (additive only, backward compatible)
|
|
605
|
+
2. **DEPLOY v2** - Deploy app version writing to both old and new schema
|
|
606
|
+
3. **BACKFILL** - Migrate existing data (batched, throttled)
|
|
607
|
+
4. **VALIDATE** - Verify data integrity
|
|
608
|
+
5. **DEPLOY v3** - Deploy app version reading from new schema only
|
|
609
|
+
6. **CONTRACT** - Remove old schema (after all apps updated)
|
|
610
|
+
|
|
611
|
+
## Pattern 1: Add Column (Safe - No Downtime)
|
|
612
|
+
|
|
613
|
+
Adding a column with constant default is safe per PostgreSQL docs:
|
|
614
|
+
"Adding a column with a constant default value does not require each row to be updated."
|
|
615
|
+
|
|
616
|
+
```sql
|
|
617
|
+
-- PHASE 1: EXPAND (safe, instant)
|
|
618
|
+
ALTER TABLE users ADD COLUMN bio TEXT DEFAULT '';
|
|
619
|
+
|
|
620
|
+
-- No other phases needed - existing app ignores new column
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
## Pattern 2: Rename Column (Requires Expand/Contract)
|
|
624
|
+
|
|
625
|
+
**Unsafe**:
|
|
626
|
+
```sql
|
|
627
|
+
ALTER TABLE users RENAME COLUMN email TO email_address; -- ❌ BREAKS OLD APP
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
**Safe** (6 phases):
|
|
631
|
+
|
|
632
|
+
**PHASE 1: EXPAND** (add new column + sync trigger):
|
|
633
|
+
```sql
|
|
634
|
+
-- Add new column
|
|
635
|
+
ALTER TABLE users ADD COLUMN email_address TEXT;
|
|
636
|
+
|
|
637
|
+
-- Trigger to keep columns in sync
|
|
638
|
+
CREATE OR REPLACE FUNCTION sync_email_columns()
|
|
639
|
+
RETURNS TRIGGER AS $$
|
|
640
|
+
BEGIN
|
|
641
|
+
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
|
|
642
|
+
-- Sync both directions
|
|
643
|
+
NEW.email_address := COALESCE(NEW.email_address, NEW.email);
|
|
644
|
+
NEW.email := COALESCE(NEW.email, NEW.email_address);
|
|
645
|
+
END IF;
|
|
646
|
+
RETURN NEW;
|
|
647
|
+
END;
|
|
648
|
+
$$ LANGUAGE plpgsql;
|
|
649
|
+
|
|
650
|
+
CREATE TRIGGER sync_email
|
|
651
|
+
BEFORE INSERT OR UPDATE ON users
|
|
652
|
+
FOR EACH ROW EXECUTE FUNCTION sync_email_columns();
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
**PHASE 2: DEPLOY v2** (app writes to BOTH columns):
|
|
656
|
+
```typescript
|
|
657
|
+
// App version 2 - dual writes
|
|
658
|
+
await db.query(
|
|
659
|
+
'INSERT INTO users (email, email_address) VALUES ($1, $1)',
|
|
660
|
+
[email]
|
|
661
|
+
)
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
**PHASE 3: BACKFILL** (migrate existing data in batches):
|
|
665
|
+
```sql
|
|
666
|
+
-- Backfill in batches (avoid long table locks)
|
|
667
|
+
DO $$
|
|
668
|
+
DECLARE
|
|
669
|
+
batch_size INT := 1000;
|
|
670
|
+
rows_updated INT;
|
|
671
|
+
total_updated INT := 0;
|
|
672
|
+
BEGIN
|
|
673
|
+
LOOP
|
|
674
|
+
-- Update one batch
|
|
675
|
+
WITH batch AS (
|
|
676
|
+
SELECT id FROM users
|
|
677
|
+
WHERE email_address IS NULL
|
|
678
|
+
LIMIT batch_size
|
|
679
|
+
FOR UPDATE SKIP LOCKED -- Avoid lock contention
|
|
680
|
+
)
|
|
681
|
+
UPDATE users
|
|
682
|
+
SET email_address = email
|
|
683
|
+
FROM batch
|
|
684
|
+
WHERE users.id = batch.id;
|
|
685
|
+
|
|
686
|
+
GET DIAGNOSTICS rows_updated = ROW_COUNT;
|
|
687
|
+
EXIT WHEN rows_updated = 0;
|
|
688
|
+
|
|
689
|
+
total_updated := total_updated + rows_updated;
|
|
690
|
+
RAISE NOTICE 'Backfilled % rows (total: %)', rows_updated, total_updated;
|
|
691
|
+
|
|
692
|
+
-- Throttle to avoid overloading DB
|
|
693
|
+
PERFORM pg_sleep(0.1);
|
|
694
|
+
END LOOP;
|
|
695
|
+
|
|
696
|
+
RAISE NOTICE 'Backfill complete: % total rows updated', total_updated;
|
|
697
|
+
END $$;
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
**PHASE 4: VALIDATE** (verify data integrity):
|
|
701
|
+
```sql
|
|
702
|
+
-- Check all data migrated
|
|
703
|
+
SELECT COUNT(*) FROM users WHERE email_address IS NULL;
|
|
704
|
+
-- Expected: 0
|
|
705
|
+
|
|
706
|
+
-- Check data consistency
|
|
707
|
+
SELECT COUNT(*) FROM users WHERE email != email_address;
|
|
708
|
+
-- Expected: 0 (or acceptable threshold for dirty data)
|
|
709
|
+
|
|
710
|
+
-- Sample verification
|
|
711
|
+
SELECT id, email, email_address
|
|
712
|
+
FROM users
|
|
713
|
+
WHERE email != email_address OR email_address IS NULL
|
|
714
|
+
LIMIT 10;
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
**PHASE 5: DEPLOY v3** (app reads from email_address only):
|
|
718
|
+
```typescript
|
|
719
|
+
// App version 3 - reads from new column
|
|
720
|
+
await db.query('SELECT email_address FROM users WHERE id = $1', [id])
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
**PHASE 6: CONTRACT** (remove old column and trigger):
|
|
724
|
+
```sql
|
|
725
|
+
-- Drop sync trigger and function
|
|
726
|
+
DROP TRIGGER IF EXISTS sync_email ON users;
|
|
727
|
+
DROP FUNCTION IF EXISTS sync_email_columns();
|
|
728
|
+
|
|
729
|
+
-- Remove old column
|
|
730
|
+
ALTER TABLE users DROP COLUMN email;
|
|
731
|
+
|
|
732
|
+
-- Optional: Rename to canonical name
|
|
733
|
+
ALTER TABLE users RENAME COLUMN email_address TO email;
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
## Pattern 3: Change Column Type
|
|
737
|
+
|
|
738
|
+
**Example**: Change `age INT` → `age NUMERIC(5,2)`
|
|
739
|
+
|
|
740
|
+
**PHASE 1: EXPAND**:
|
|
741
|
+
```sql
|
|
742
|
+
ALTER TABLE users ADD COLUMN age_new NUMERIC(5,2);
|
|
743
|
+
|
|
744
|
+
-- Sync trigger (optional if app will write)
|
|
745
|
+
CREATE OR REPLACE FUNCTION sync_age_columns()
|
|
746
|
+
RETURNS TRIGGER AS $$
|
|
747
|
+
BEGIN
|
|
748
|
+
NEW.age_new := NEW.age::NUMERIC(5,2);
|
|
749
|
+
RETURN NEW;
|
|
750
|
+
END;
|
|
751
|
+
$$ LANGUAGE plpgsql;
|
|
752
|
+
|
|
753
|
+
CREATE TRIGGER sync_age
|
|
754
|
+
BEFORE INSERT OR UPDATE ON users
|
|
755
|
+
FOR EACH ROW
|
|
756
|
+
WHEN (NEW.age IS NOT NULL)
|
|
757
|
+
EXECUTE FUNCTION sync_age_columns();
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
**PHASE 2: BACKFILL**:
|
|
761
|
+
```sql
|
|
762
|
+
UPDATE users SET age_new = age::NUMERIC(5,2) WHERE age_new IS NULL;
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
**PHASE 3: APP MIGRATION** → read from age_new
|
|
766
|
+
|
|
767
|
+
**PHASE 4: CONTRACT**:
|
|
768
|
+
```sql
|
|
769
|
+
DROP TRIGGER IF EXISTS sync_age ON users;
|
|
770
|
+
DROP FUNCTION IF EXISTS sync_age_columns();
|
|
771
|
+
ALTER TABLE users DROP COLUMN age;
|
|
772
|
+
ALTER TABLE users RENAME COLUMN age_new TO age;
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
## Pattern 4: Add NOT NULL Constraint (Requires Data Backfill)
|
|
776
|
+
|
|
777
|
+
**Unsafe**:
|
|
778
|
+
```sql
|
|
779
|
+
ALTER TABLE users ALTER COLUMN email SET NOT NULL; -- ❌ Fails if NULLs exist
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
**Safe**:
|
|
783
|
+
|
|
784
|
+
**PHASE 1: Add default value**:
|
|
785
|
+
```sql
|
|
786
|
+
ALTER TABLE users ALTER COLUMN email SET DEFAULT 'unknown@example.com';
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
**PHASE 2: Backfill NULLs**:
|
|
790
|
+
```sql
|
|
791
|
+
UPDATE users SET email = 'unknown@example.com' WHERE email IS NULL;
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
**PHASE 3: Validate**:
|
|
795
|
+
```sql
|
|
796
|
+
SELECT COUNT(*) FROM users WHERE email IS NULL; -- Expected: 0
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
**PHASE 4: Add constraint**:
|
|
800
|
+
```sql
|
|
801
|
+
ALTER TABLE users ALTER COLUMN email SET NOT NULL;
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
## Pattern 5: CREATE INDEX CONCURRENTLY
|
|
805
|
+
|
|
806
|
+
**Always use CONCURRENTLY in production**:
|
|
807
|
+
|
|
808
|
+
```sql
|
|
809
|
+
-- ✅ SAFE (non-blocking)
|
|
810
|
+
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
|
|
811
|
+
|
|
812
|
+
-- ❌ UNSAFE (blocks writes)
|
|
813
|
+
CREATE INDEX idx_users_email ON users(email);
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
CONCURRENTLY allows reads/writes to continue during index creation.
|
|
817
|
+
|
|
818
|
+
## When to Use Zero-Downtime
|
|
819
|
+
|
|
820
|
+
**Use expand/contract for**:
|
|
821
|
+
- Column renames
|
|
822
|
+
- Type changes
|
|
823
|
+
- Table splits/merges
|
|
824
|
+
- Adding NOT NULL constraints
|
|
825
|
+
- Removing columns with data
|
|
826
|
+
|
|
827
|
+
**Don't need for** (already safe):
|
|
828
|
+
- Adding nullable columns with constant defaults
|
|
829
|
+
- Creating new tables
|
|
830
|
+
- Adding indexes (use CONCURRENTLY)
|
|
831
|
+
- Adding constraints (check, foreign key) to empty tables
|
|
832
|
+
- Renaming tables (if app uses dynamic table names)
|
|
833
|
+
|
|
834
|
+
## Risks & Trade-offs
|
|
835
|
+
|
|
836
|
+
**Pros**:
|
|
837
|
+
- Zero downtime
|
|
838
|
+
- Gradual rollout
|
|
839
|
+
- Safe rollback at any phase
|
|
840
|
+
- No maintenance window needed
|
|
841
|
+
|
|
842
|
+
**Cons**:
|
|
843
|
+
- Longer deployment cycle (days/weeks vs minutes)
|
|
844
|
+
- Increased complexity (6 phases vs 1)
|
|
845
|
+
- Duplicate data temporarily (storage cost)
|
|
846
|
+
- Requires app coordination (multiple deploys)
|
|
847
|
+
- Sync triggers add write overhead
|
|
848
|
+
|
|
849
|
+
## Decision Tree
|
|
850
|
+
|
|
851
|
+
```
|
|
852
|
+
Does migration break existing app code?
|
|
853
|
+
NO → Standard migration ✅
|
|
854
|
+
YES → Does it affect hot path (high traffic table)?
|
|
855
|
+
NO → Maintenance window OK (document downtime) ✅
|
|
856
|
+
YES → Use expand/contract pattern ✅
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
## Supabase-Specific Notes
|
|
860
|
+
|
|
861
|
+
Supabase emphasizes **forward-only migrations**:
|
|
862
|
+
- No explicit rollback command in CLI
|
|
863
|
+
- Use `supabase db reset` for local development only
|
|
864
|
+
- Production rollbacks require new forward migrations
|
|
865
|
+
|
|
866
|
+
This aligns perfectly with expand/contract philosophy:
|
|
867
|
+
Each phase is a separate forward migration.
|
|
868
|
+
elicit: false
|
|
869
|
+
|
|
870
|
+
- id: supabase-cli
|
|
871
|
+
title: "Supabase CLI Integration"
|
|
872
|
+
instruction: |
|
|
873
|
+
How DB Sage migration workflow integrates with Supabase CLI.
|
|
874
|
+
|
|
875
|
+
## Supabase CLI Migration Workflow
|
|
876
|
+
|
|
877
|
+
### Local Development
|
|
878
|
+
|
|
879
|
+
```bash
|
|
880
|
+
# 1. Create new migration
|
|
881
|
+
supabase migration new add_users_table
|
|
882
|
+
|
|
883
|
+
# 2. Edit migration file in supabase/migrations/<timestamp>_add_users_table.sql
|
|
884
|
+
|
|
885
|
+
# 3. Test migration locally
|
|
886
|
+
supabase db reset # Recreates DB from scratch + applies all migrations
|
|
887
|
+
|
|
888
|
+
# 4. Verify changes
|
|
889
|
+
supabase db diff --schema public # Show diff vs remote
|
|
890
|
+
|
|
891
|
+
# 5. Commit migration file
|
|
892
|
+
git add supabase/migrations/<timestamp>_add_users_table.sql
|
|
893
|
+
git commit -m "feat: add users table"
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
### Staging/Production Deployment
|
|
897
|
+
|
|
898
|
+
**CI/CD Pipeline** (GitHub Actions recommended):
|
|
899
|
+
|
|
900
|
+
```yaml
|
|
901
|
+
name: Deploy to Production
|
|
902
|
+
on:
|
|
903
|
+
push:
|
|
904
|
+
branches: [main]
|
|
905
|
+
|
|
906
|
+
jobs:
|
|
907
|
+
deploy:
|
|
908
|
+
runs-on: ubuntu-latest
|
|
909
|
+
steps:
|
|
910
|
+
- uses: actions/checkout@v3
|
|
911
|
+
|
|
912
|
+
- name: Setup Supabase CLI
|
|
913
|
+
uses: supabase/setup-cli@v1
|
|
914
|
+
|
|
915
|
+
- name: Deploy migrations
|
|
916
|
+
run: supabase db push
|
|
917
|
+
env:
|
|
918
|
+
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
|
919
|
+
SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }}
|
|
920
|
+
SUPABASE_PROJECT_ID: ${{ secrets.SUPABASE_PROJECT_ID }}
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
### Pulling Remote Schema
|
|
924
|
+
|
|
925
|
+
Sync production schema to local:
|
|
926
|
+
|
|
927
|
+
```bash
|
|
928
|
+
# Pull all remote migrations
|
|
929
|
+
supabase db pull
|
|
930
|
+
|
|
931
|
+
# Creates: supabase/migrations/<timestamp>_remote_schema.sql
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
**When to use**: After manual changes in Supabase Dashboard.
|
|
935
|
+
|
|
936
|
+
## DB Sage Enhancement Layer
|
|
937
|
+
|
|
938
|
+
DB Sage adds custom schema_migrations table **on top** of Supabase CLI:
|
|
939
|
+
|
|
940
|
+
| Feature | Supabase CLI | DB Sage schema_migrations |
|
|
941
|
+
|---------|--------------|---------------------------|
|
|
942
|
+
| Version tracking | ✅ Timestamp in filename | ✅ + applied_at |
|
|
943
|
+
| Applied by | ❌ | ✅ current_user |
|
|
944
|
+
| Checksums | ❌ | ✅ SHA256 |
|
|
945
|
+
| Rollback scripts | ❌ | ✅ Stored in table |
|
|
946
|
+
| Success/failure | ❌ | ✅ success boolean |
|
|
947
|
+
| Execution time | ❌ | ✅ execution_time_ms |
|
|
948
|
+
|
|
949
|
+
**Use both together**:
|
|
950
|
+
- Supabase CLI for development workflow and deployments
|
|
951
|
+
- schema_migrations for production audit trail
|
|
952
|
+
|
|
953
|
+
## Permission Management
|
|
954
|
+
|
|
955
|
+
**Critical**: Migrations created in Supabase Dashboard may have wrong owner.
|
|
956
|
+
|
|
957
|
+
```sql
|
|
958
|
+
-- Fix ownership (run after pulling remote schema)
|
|
959
|
+
ALTER TABLE users OWNER TO postgres;
|
|
960
|
+
ALTER TYPE user_role OWNER TO postgres;
|
|
961
|
+
ALTER FUNCTION get_user_role() OWNER TO postgres;
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
**Best practice**: Create all migrations as SQL files, not via Dashboard.
|
|
965
|
+
|
|
966
|
+
## Migration List & Status
|
|
967
|
+
|
|
968
|
+
```bash
|
|
969
|
+
# Check migration status (local vs remote)
|
|
970
|
+
supabase migration list
|
|
971
|
+
|
|
972
|
+
# Output:
|
|
973
|
+
# Local Remote Status
|
|
974
|
+
# 20240101 20240101 Applied
|
|
975
|
+
# 20240102 - Pending
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
## Rollback Strategy
|
|
979
|
+
|
|
980
|
+
Supabase CLI does **not** support explicit rollback.
|
|
981
|
+
|
|
982
|
+
**Options**:
|
|
983
|
+
1. **Local**: `supabase db reset` (destructive, dev only)
|
|
984
|
+
2. **Production**: Create new forward migration that undoes changes
|
|
985
|
+
3. **DB Sage**: Use rollback_script from schema_migrations table
|
|
986
|
+
|
|
987
|
+
**Example** (using DB Sage):
|
|
988
|
+
```sql
|
|
989
|
+
-- Retrieve rollback script
|
|
990
|
+
SELECT rollback_script FROM schema_migrations
|
|
991
|
+
WHERE version = '20251027120000';
|
|
992
|
+
|
|
993
|
+
-- Execute retrieved script
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
## Best Practices
|
|
997
|
+
|
|
998
|
+
1. **Always test locally** with `supabase db reset` before deploying
|
|
999
|
+
2. **Use CI/CD** for staging/production (not local machine)
|
|
1000
|
+
3. **Separate projects** for dev/staging/prod
|
|
1001
|
+
4. **Store credentials** as GitHub secrets (never commit)
|
|
1002
|
+
5. **Reassign ownership** to postgres after dashboard changes
|
|
1003
|
+
6. **Small migrations** (easier to debug, faster to apply)
|
|
1004
|
+
7. **Idempotent scripts** (IF NOT EXISTS, DROP IF EXISTS)
|
|
1005
|
+
|
|
1006
|
+
## Troubleshooting
|
|
1007
|
+
|
|
1008
|
+
**"must be owner of table" error**:
|
|
1009
|
+
```sql
|
|
1010
|
+
ALTER TABLE mytable OWNER TO postgres;
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
**Migration applied but not tracked**:
|
|
1014
|
+
```bash
|
|
1015
|
+
supabase migration repair <version> --status applied
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
**Reset local DB** (development only):
|
|
1019
|
+
```bash
|
|
1020
|
+
supabase db reset
|
|
1021
|
+
```
|
|
1022
|
+
elicit: false
|