visionA/visionA-backend/migrations/0005_create_agents.up.sql
jim800121chen 8369cab85c feat(db): migration 0005 agents 模型(個人設備管理 A' 走向第二階段地基)
- 新增 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>
2026-07-16 04:05:53 +08:00

72 lines
5.0 KiB
SQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 0005_create_agents.up.sql
--
-- 個人設備管理走向 A'ADR-018 Accepted新增 agents 實體(一條已配對的 tunnel 連線
-- = 一個 agentdevices 語意轉為「一顆實體 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。
--
-- 環境事實(與 00010003 相同已驗證PostgreSQL 14.23gen_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/linuxagent 上報,可空)
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 indexList 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 合成 idkl520-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;
-- 註冊軸 filtertask 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各建一筆 agentid 沿用來源 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 本來就是「連線佔位」而非真 USBserial 皆 NULL故全部 is_representative=true。
UPDATE devices d
SET agent_id = d.id, -- 對應 3aagent.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)。