Atlas Migration Implementation Plan
給 agentic workers: 執行本計劃時必須使用
superpowers:subagent-driven-development或superpowers:executing-plans,逐項完成 checkbox (- [ ]) 步驟。
目標: 將 services/api 的 schema 管理由 GORM AutoMigrate 遷移到 Atlas,同時不削弱既有資料庫 invariant,也不送出不安全的 baseline。
架構: Atlas 遷移原本規劃拆成多個小型、可獨立 review 的 PR。2026-05-10 後續決策改為由 PR #588 一次交付完整 runtime migration path,因為專 案尚未正式上線,保留半套 migration ownership 反而會增加風險。下方 PR 1-4 保留為 historical plan,不再作為新的 implementation checklist。
Tech Stack: Go、GORM、PostgreSQL、Atlas、atlas-provider-gorm、GitHub Actions。
Issue And Artifact References
- GitHub issue:
#463[backend] Migration 工具遷移:GORM AutoMigrate → Atlas - 前置討論:
#214 - Migration 目錄:
services/api/migrations/ - Atlas 設定檔:
services/api/atlas.hcl - API server entrypoint:
services/api/cmd/server/main.go - GORM model structs:
services/api/internal/models/ - Atlas loader model list:
services/api/internal/schema.AtlasSchemaModels()
Current Source Of Truth
目前 develop 的實作狀態:
- Atlas migration directory is active at
services/api/migrations/. - Docker entrypoint applies migrations before starting
air,/tachigo, ortachigo. - Local
make rundepends onmake migrate;make run-no-migrateis the explicit skip path. - API binary must not execute schema DDL; startup guard tests enforce this boundary.
- GORM model structs remain the Atlas loader schema source via
AtlasSchemaModels(), not a runtime migration owner. - Legacy raffle token hash repair remains a data-only startup repair.
本文件記錄 issue #463 的遷移背景與現況;future implementation 必須以上方 current source of truth 和 repo code 為準,不得把 historical checklist 當成新的待辦。
Guardrails
- 歷史 guardrail:原本不得把 tooling、baseline SQL、
AutoMigrateremoval 合併在同一個 PR;PR#588例外承擔完整 runtime path,避免新專案保留半套 schema ownership。 docs/atlas-schema-reconciliation.mdreview 前,不得產生大型 baseline migration。- 不得因為
atlas migrate lint通過就宣稱 baseline 安全;lint 驗證 migration mechanics,不驗證 production data compatibility。 - 不得移除 GORM model structs;
atlas-provider-gorm仍需要讀取它們。 - 不得把 production deploy automation 或 rollback automation 混進
#463;issue 已明確排除。 - 任何改變 runtime schema 行為的 PR,都必須在 PR body 補 staging 驗證說明。
Historical Plan
以下 PR 1-4 是原始拆分計畫,已被 PR #588 取代,不再作為新的 implementation checklist。保留它們只為了說明 Atlas ownership 遷移時的決策脈絡。
PR 1:Atlas Tooling Only
目的: 加入最小 Atlas toolchain,讓開發者可以從 GORM schema source 產生 diff,CI 可以驗證 loader 與 migration directory。
Files:
- Create:
services/api/atlas.hcl - Create:
services/api/cmd/loader/main.go - Create:
services/api/tools.go - Modify:
services/api/go.mod - Modify:
services/api/go.sum - Modify:
.github/workflows/ci.yml - Optional:
.gitattributes,僅在這顆 PR 開始導入 Atlas checksum 檔時需要
明確不做:
- 不新增
020_atlas_baseline.sql - 不新增
atlas.sum,除非這顆 PR 明確開始讓 Atlas 管理 migration directory checksum - 不移除
AutoMigrate - 不清理 model tags,除非是 loader compilation 的必要前置
Implementation Steps:
- 新增
services/api/tools.go,使用//go:build tools,並以 blank import 錨定ariga.io/atlas-provider-gorm/gormschema或所選 provider 版本需要的 package path。 - 新增
services/api/cmd/loader/main.go,載入目前所有 GORM model structs。 - 將 GORM 無法表達的 custom PostgreSQL schema objects 放進 loader output,或集中在清楚命名的 loader helper。
- 新增
services/api/atlas.hcl,使用external_schemaprogram 執行 loader。 - 新增 CI job,驗證 Atlas 可以 inspect GORM loader,並把 migration directory 套到 clean PostgreSQL。
- 確認 CI migration apply 只寫入 job 內 ephemeral database,不會對共用 database apply migration。
Verification:
cd services/api
go mod tidy
go run ./cmd/loader/main.go > /tmp/tachigo-gorm-schema.sql
atlas schema inspect --env gorm --url env://src --format '{{ sql . }}' > /tmp/tachigo-atlas-inspect-schema.sql
Expected:loader 可編譯、可輸出 PostgreSQL DDL,且 Atlas 可用 env://src 讀取 GORM schema source;這個 smoke test 不應寫入 migration file 或 atlas.sum。
Commit Message:
chore: add atlas migration tooling
refs #463
Co-Authored-By: Codex <codex[bot]@openai.com>
PR 2:Schema Reconciliation
目的: 在撰寫 baseline SQL 前,先把隱性的 schema history 變成可 review 的 reconciliation 文件。
Files:
- Modify:
docs/atlas-schema-reconciliation.md
Implementation Steps:
- 將
services/api/migrations/001-019依序 apply 到乾淨 PostgreSQL dev database。 - 用另一個乾淨 PostgreSQL dev database 啟動目前 server,讓
AutoMigrate與 runtime patches 跑完。 - 用
pg_dump --schema-only --no-owner --no-privilegesdump 兩邊 schema。 - 用 Atlas 或文字 diff 比對兩個 schema state。
- 將每個差異填進 reconciliation table,並寫出明確決策。
- 每個差異都標成
preserve、drop after review、model drift、runtime patch、data migration或out of scope。
Verification:
cd services/api
docker compose run --no-deps --rm app go test ./...
Expected:文件 PR 不改 runtime,後端測試應通過;若本機 Docker 不可用,PR body 必須明確說明未跑原因。
Commit Message:
docs: reconcile atlas migration baseline inputs
refs #463
Co-Authored-By: Codex <codex[bot]@openai.com>
PR 3:Baseline Strategy And Migration Directory Ownership
目的: 建立第一個 Atlas-owned migration state,同時避免破壞既有環境。
Files:
- Create or modify:
services/api/migrations/020_atlas_baseline.sql - Create or modify:
services/api/migrations/atlas.sum - Modify:
docs/atlas-schema-reconciliation.md
Baseline Decision:
採 Apply-safe reconciliation。001-019 繼續作為歷史 migration,020_atlas_reconcile_current_schema.sql 補齊 GORM AutoMigrate/runtime patch 曾經隱式建立的 current schema,並使用 IF NOT EXISTS / guarded DO $$ blocks 避免對既有 DB 重打完整 schema。
Implementation Steps:
- 根據已 review 的 reconciliation 文件產生候選 baseline。
- 驗證 baseline 可套到 empty database。
- 驗證 baseline 可套到已跑過目前 server
AutoMigrate的 database。 - 驗證 baseline 保留 GORM tags 無法表達的 partial indexes 與 constraints。
- 最終 SQL review 完成後,才重新產生
atlas.sum。
Verification:
cd services/api
atlas schema inspect --env gorm --url env://src --format '{{ sql . }}' > /tmp/tachigo-atlas-inspect-schema.sql
atlas migrate apply --dir "file://migrations" --url "$ATLAS_VERIFY_DATABASE_URL"
docker compose run --no-deps --rm app go test ./...
Expected:Atlas 可以 inspect loader、完整 migration directory 可套到乾淨 PostgreSQL、後端測試通過,且 PR body 明確寫出採用哪個 baseline strategy。
Commit Message:
chore: add atlas baseline migration
refs #463
Co-Authored-By: Codex <codex[bot]@openai.com>
PR 4:Remove Runtime AutoMigrate After Reconciliation Migration(範圍完成於 PR #588;deploy follow-up #212)
目的: 讓 Atlas 成為 runtime schema owner。
Files:
- Modify:
services/api/cmd/server/main.go - Modify or add:
services/api/cmd/server相關測試,若現有 coverage 無法驗證 startup migration 行為 - Modify:
docs/atlas-schema-reconciliation.md
Preconditions:
- PR 1、PR 2、PR 3 已 merge。
- Reconciliation migration 已保留 runtime schema patches 與 high-risk historical invariants。
- PR body 必須列出移除哪些 runtime patches,以及等價 Atlas migration 在哪裡。
Implementation Steps:
- 從 server startup 移除
db.AutoMigrate(...)(PR#588)。 - Manual schema patches 已移入 Atlas;legacy raffle token hash repair 保留為非 schema data repair;startup guard test 防止 API process 再執行 schema DDL(PR
#588)。
Verification:
cd services/api
docker compose run --no-deps --rm app go test ./...
Expected:後端測試通過,且 server startup 不再執行 schema DDL。
Commit Message:
chore: remove gorm automigrate from server startup
refs #463
Co-Authored-By: Codex <codex[bot]@openai.com>
CI 策略決策(2026-05-10)
決策:移除 migrate lint,改用 official/latest Atlas + ephemeral Postgres apply。
背景:atlas migrate lint 從 v0.38 起限 Pro 授權。原本 pin 在 v0.37.0 是為了繼續免費用 lint。經 Claude Code + Codex 討論,新專案不值得為此維護版本 pin。
決定採用的 CI 方案
| 步驟 | 做法 | 理由 |
|---|---|---|
| CI Atlas 版本 | official/latest,不 pin(僅 CI;Docker runtime 另以 ATLAS_VERSION=1.2.0 pin) | 解除 CI lint-era 版本鎖,不依賴 Community binary |
| GORM loader 驗證 | atlas schema inspect --env gorm --url env://src | 確認 external_schema + loader 正常運作 |
| Migration apply | atlas migrate apply --dir file://migrations --url postgres://... 對 ephemeral Postgres | 比 lint 更接近真實;apply 本身會驗 atlas.sum checksum |
| Checksum drift | apply 失敗即可抓到,不另跑 hash --check(該 flag 不存在) | apply 已內建此保護 |
| Destructive DDL guard | CI grep DROP TABLE、TRUNCATE TABLE、DROP COLUMN,需 -- atlas:nolint allowlist 才過 | 取 代 lint 對危險 DDL 的部分保護,且更透明 |
明確不做:
- 不使用
migrate lint(Pro gate) - 不使用 Community binary(不支援
external_schema)
運作邊界
- Fresh DB / 新 Docker volume:API Docker entrypoint 會先套用
001-020,再啟動air(dev target)或/tachigo(runtime target);API binary 本身不再執行 schema DDL。 - Local Go startup:
make run會先執行make migrate;只有明確使用make run-no-migrate才會跳過 Atlas migration。 - 既有 dev DB / 舊 volume:若 schema 是早期
AutoMigrate建出來、但沒有atlas_schema_revisions,Atlas 不能自動判斷已套用哪些歷史 migration;本專案尚未正式上線,建議 reset dev volume 或由 operator 手動 baseline。 - Production deploy automation 仍不屬於
#463範圍;上線前需要在部署 workflow/runbook 中重用同一個atlas migrate apply流程。
Issue #463 Acceptance Checklist
-
services/api/atlas.hcl存在,且atlas migrate diff可使用 GORM loader。 - CI 有 Atlas migration 驗證,且 job 被 backend gate 納入。
-
services/api/migrations/001-019與 GORM model drift 已記錄或修正。 - Baseline strategy 明確,採 guarded apply-safe reconciliation migration。
-
AutoMigrate已從 server startup 移除;server 不再執行 schema DDL。 - Migration runner 存在:Docker image entrypoint 與
make migrate都有atlas migrate applypath;make run依賴make migrate,確保 fresh DB / 新 volume / 本機 Go startup 可正確 bootstrap schema。 - CI 改成 official/latest Atlas + ephemeral Postgres apply(移除舊版 pin、Community binary 與 migrate lint)。
Atlas References
- External schema loading:https://atlasgo.io/atlas-schema/external
- Versioned migration diff:https://atlasgo.io/versioned/diff
Review Checklist
- PR diff 沒超過專案 scope limit;若超過,PR body 有有效 scope exception 理由。
- PR 沒混合 schema migration、service logic、handler/router、frontend changes。
- Partial unique indexes 被保留,或有 issue-backed decision 說明為何移除。
- Custom PostgreSQL enum definitions 保留相同 label set,並依
docs/atlas-schema-reconciliation.md的 enum ordering decision 處理。 -
go mod tidy不會移除 Atlas tooling dependencies。 - CI apply job 對 ephemeral Postgres 跑完整 migration 001–020 無錯誤。