跳至主要内容

Atlas Migration Implementation Plan

給 agentic workers: 執行本計劃時必須使用 superpowers:subagent-driven-developmentsuperpowers: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, or tachigo.
  • Local make run depends on make migrate; make run-no-migrate is 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、AutoMigrate removal 合併在同一個 PR;PR #588 例外承擔完整 runtime path,避免新專案保留半套 schema ownership。
  • docs/atlas-schema-reconciliation.md review 前,不得產生大型 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_schema program 執行 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-privileges dump 兩邊 schema。
  • 用 Atlas 或文字 diff 比對兩個 schema state。
  • 將每個差異填進 reconciliation table,並寫出明確決策。
  • 每個差異都標成 preservedrop after reviewmodel driftruntime patchdata migrationout 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 reconciliation001-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 applyatlas migrate apply --dir file://migrations --url postgres://... 對 ephemeral Postgres比 lint 更接近真實;apply 本身會驗 atlas.sum checksum
Checksum driftapply 失敗即可抓到,不另跑 hash --check(該 flag 不存在)apply 已內建此保護
Destructive DDL guardCI grep DROP TABLETRUNCATE TABLEDROP 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 apply path;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

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 無錯誤。