feat(kbdb): 樹狀 record 模型第一刀——record 有身分、關係是唯一機制、entry_values 拆表(v7 定稿實作)

規格:system-dev/docs/3-specs/pending-changes.md「record 要有身分」v7 定稿(leo 2026-08-15 confirm)。
模型一句話(leo):「真身在 pool 的 entry 裡,所有的虛擬表虛擬欄位都是指向這個 entry 的指標。」

- 0007 migration:池上型別化指標欄(src/rel/dst)+一對方向 partial index+啟動常數
  (sys_root/sys_belongs/sys_field_of)+templates 鏡射成 sheet/field entry+
  每筆 record 一顆身分 entry(id=原 record_id,引用不失效)+每格一條關係列
  (id 由舊儲存格列 id 衍生 ⇒ INSERT OR IGNORE 天然冪等)+拆 entry_values
  (0006 墊表→搬→拆手法)。純 INSERT、value entries 一列不動(向量索引不失效)。
- record-crud 整份改寫到關係列(#128 指標語意/共用保護/N+1 批次/租戶過濾全數保留,
  驗收測試 232→236 綠);library-map 四段縱轉橫 SQL、records triplet-stats 改查關係列。
- entry-crud:機制列隔離(未指定 entry_type 的列表/搜尋不回機制節點);deleteEntry
  接手舊 entry_values FK 的不變量(dst 被指著→拒刪)。
- 孤兒偵測重設計(v7 §5 點名):新模型孤兒=指標指向不存在 id 的關係列,
  LEFT JOIN 斷鏈掃描(承接 2026-06-24 清理事故的 FK 形狀),
  GET /maintenance/relation-orphans 唯讀巡檢。
- cli deploy.ts:0007 逐句套用+容錯 duplicate column(SQLite 無欄位級 IF NOT EXISTS,
  整檔送 /query 會在重跑時假紅)。
- 測試:tree-record-migration.test.ts 驗資料零漏/雙跑冪等/孤兒掃描;
  釘死三表的斷言依 confirm 後規格改口(execution-log/credential-legacy 兩處)。

遷移期雙軌(第二刀收):templates 表仍是欄位定義真相源;六種 metadata_json 打包型
與 §7 減法封鎖(拿掉 entry_type/metadata_json 欄)留待第二刀。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
uncle6me-web
2026-08-15 21:34:48 +08:00
parent 969acff325
commit ceb7638d74
25 changed files with 799 additions and 231 deletions
@@ -29,6 +29,7 @@ function makeSqliteD1(): D1Database {
raw.exec(readFileSync(new URL('../migrations/0001_base.sql', import.meta.url), 'utf8')); // kbdb-sql-ok: 測試 adapter 套 migration 原檔,比照 execution-log.test.ts
raw.exec(readFileSync(new URL('../migrations/0002_credentials.sql', import.meta.url), 'utf8')); // kbdb-sql-ok: 測試 adapter 套 migration 原檔
raw.exec(readFileSync(new URL('../migrations/0005_credential_template.sql', import.meta.url), 'utf8')); // kbdb-sql-ok: 測試 adapter 套 migration 原檔
raw.exec(readFileSync(new URL('../migrations/0007_tree_record_model.sql', import.meta.url), 'utf8')); // kbdb-sql-ok:測試治具套 0007(樹狀 record 模型,v7 定稿)——真 schema 就是遷移後的 schema
function stmt(sql: string, params: unknown[]) {
const s = {
bind(...args: unknown[]) { return stmt(sql, args); },
@@ -89,8 +90,9 @@ describe('credential-legacy-migration — 反向驗證:重現 2026-08-07 youli
.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`) // kbdb-sql-ok: 測試查詢
.all<{ name: string }>();
const names = (tables.results ?? []).map((t) => t.name).sort();
// entries/templates/entry_values 三張核心表 + credentials(舊表,尚未清理)——沒有第五張表。
expect(names).toEqual(['credentials', 'entries', 'entry_values', 'templates']);
// entries/templates 兩張核心表(entry_values 已由 0007 拆掉——關係的第二套實作,D92)
// + credentials(舊表,尚未清理)——沒有第四張表。
expect(names).toEqual(['credentials', 'entries', 'templates']);
});
});