- 新增 agents 表 + devices 加 4 欄(agent_id/agent_local_device_id/ registered_at/is_representative)+ 2 index + 純 SQL data migration - data migration 採 R-A:agent.id := device.id 決定性推導、冪等、 soft-deleted device 排除(WHERE deleted_at IS NULL) - 守住 ADR-018 走向 A':不碰 session_tokens FK、不改現有 partial unique index uq_devices_owner_serial_active、對 0001-0003 零破壞 - migrate_0005_db_test.go 6 個 dbtest case(apply/data migration/ rollback 對稱/re-apply 冪等/session_tokens 零影響/既有 device 讀寫回歸) - 修 TestMigrate_UpDownUp 編號 gap 假設(0001/2/3/5 無 0004): assert.Equal(topVer-1) → assert.Less(downVer, topVer) - .gitignore 加 .logs/ + **/.logs/(本機執行 log per-branch 不進 git) Reviewer 通過(0 Critical/0 Major/3 Minor/4 Sug)。全 db package 41 dbtest 130 綠、build/vet/test 綠、gitleaks 0。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
72 lines
5.0 KiB
SQL
72 lines
5.0 KiB
SQL
-- 0005_create_agents.up.sql
|
||
--
|
||
-- 個人設備管理走向 A'(ADR-018 Accepted):新增 agents 實體(一條已配對的 tunnel 連線
|
||
-- = 一個 agent);devices 語意轉為「一顆實體 USB」,掛 agent_id。
|
||
-- 對齊 .autoflow/04-architecture/personal-device-mgmt-data-model.md §2.3 / §2.6
|
||
-- 與 docs/autoflow/04-architecture/adr/adr-018-agent-device-model.md §5.1–§5.4,
|
||
-- 精確 SQL 規格見 .autoflow/04-architecture/migration-0005-spec.md。
|
||
--
|
||
-- 環境事實(與 0001–0003 相同,已驗證):PostgreSQL 14.23,gen_random_uuid() 內建可直接用。
|
||
--
|
||
-- 不碰的東西(走向 A' 核心,避開破壞性 migration):
|
||
-- - session_tokens.device_id 的 FK / NOT NULL:物理 schema 一字不動(改綁 representative
|
||
-- device 是純語意變更,不需 DDL)。
|
||
-- - uq_devices_owner_serial_active:現有 partial unique 已是 task 6 去重正確基礎,不動。
|
||
|
||
-- ── (1) agents 表 ────────────────────────────────────────────────────────────
|
||
CREATE TABLE agents (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
owner_user_id UUID NOT NULL REFERENCES users(id),
|
||
name TEXT NOT NULL DEFAULT 'local-agent',
|
||
platform TEXT, -- darwin/windows/linux(agent 上報,可空)
|
||
agent_version TEXT, -- agent 版本(可空)
|
||
last_paired_at TIMESTAMPTZ, -- 最近一次配對完成時間
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
deleted_at TIMESTAMPTZ
|
||
);
|
||
|
||
-- owner-scoped active index(List agents by owner;沿用 0002 partial 慣例)。
|
||
CREATE INDEX idx_agents_owner_active ON agents (owner_user_id) WHERE deleted_at IS NULL;
|
||
|
||
-- ── (2) devices 加欄位(語意轉為「實體 USB」)────────────────────────────────
|
||
-- 全部 nullable(或有 DEFAULT),對既有 row 零破壞:既有 device 加欄後 agent_id=NULL、
|
||
-- registered_at=NULL、is_representative=false,語意 = 「尚未歸入 agent 的舊資料」,
|
||
-- 由 (3) data migration 補齊。
|
||
ALTER TABLE devices ADD COLUMN agent_id UUID REFERENCES agents(id); -- NULL=代表 device/舊資料
|
||
ALTER TABLE devices ADD COLUMN agent_local_device_id TEXT; -- local agent 合成 id(kl520-0),路由除錯輔助
|
||
ALTER TABLE devices ADD COLUMN registered_at TIMESTAMPTZ; -- NULL=未註冊(註冊軸)
|
||
ALTER TABLE devices ADD COLUMN is_representative BOOLEAN NOT NULL DEFAULT false;-- true=agent 佔位/代表 device(非真 USB)
|
||
|
||
-- agent_id 反查(List devices by agent;只索引未刪除)。
|
||
CREATE INDEX idx_devices_agent_active ON devices (agent_id) WHERE deleted_at IS NULL;
|
||
-- 註冊軸 filter(task 3 黃色「未註冊」/ task 4 filter「只看未註冊」;只索引未刪除)。
|
||
CREATE INDEX idx_devices_registered ON devices (registered_at) WHERE deleted_at IS NULL;
|
||
|
||
-- ── (3) data migration:既有 device(皆 serial=NULL 的連線佔位)→ 1:1 建 agent + 標 representative ──
|
||
-- 純 SQL 兩句對應(本 runner 是 golang-migrate iofs,只跑 SQL,無 Go 逐筆 hook)。
|
||
--
|
||
-- 決定性推導方案(agent.id := device.id):讓每筆現有 device 產生的 agent 用「與 device 相同
|
||
-- 的 UUID」。agents 與 devices 主鍵空間獨立(不同表),沿用同一 UUID 值不衝突;且讓 3b 的
|
||
-- 回填變成「device.agent_id := device.id」的自我對應,一句 UPDATE 完成、無關聯歧義、天然冪等。
|
||
-- 取捨與替代方案見 migration-0005-spec.md §2.1 / §5 R-A(使用者已裁決採此方案)。
|
||
|
||
-- 3a. 對每筆現有未刪除 device,各建一筆 agent(id 沿用來源 device.id)。
|
||
INSERT INTO agents (id, owner_user_id, name, last_paired_at, created_at, updated_at)
|
||
SELECT d.id, d.owner_user_id, 'local-agent', d.paired_at, d.created_at, now()
|
||
FROM devices d
|
||
WHERE d.deleted_at IS NULL;
|
||
|
||
-- 3b. 回填:每筆現有 device 綁到「用它自己 id 建出來的 agent」,並標記為 representative。
|
||
-- (現有 device 本來就是「連線佔位」而非真 USB,serial 皆 NULL,故全部 is_representative=true。)
|
||
UPDATE devices d
|
||
SET agent_id = d.id, -- 對應 3a:agent.id 就是 device.id
|
||
is_representative = true,
|
||
updated_at = now()
|
||
WHERE d.deleted_at IS NULL
|
||
AND d.agent_id IS NULL; -- 冪等保護:已綁過的不重綁
|
||
|
||
-- 註:已 soft-deleted 的舊 device 不建 agent、不回填(它們已退出使用;未來若復活走正常 exchange 流程)。
|
||
-- 真實 USB device 在本次遷移後「還不存在」——等 agent 下次 exchange 帶 serial 上來(WP-0)
|
||
-- 或 task 5 註冊時才建立,這符合「舊資料無序號」的事實(ADR-018 §4.3)。
|