fix(kbdb): 藏書地圖讀端每次都觸發重算寫 D1(Arcrun#87 止血)
liveTripletCountsByLibrary 沒有過濾 status,recomputeLibraryMap 只算 COALESCE(status,'active')='active'——兩邊判準不一致,只要庫裡有一筆 superseded triplet 就永遠判定 stale,導致每次讀地圖(GET /map、GET /map/:library、 kbdb_get_map MCP 工具)都觸發重算並新建一筆 library_map record,無止盡寫 D1, 且加劇既有的非原子 supersede 競態(kb 44 筆全 superseded、notes 兩筆同時 active 即 arcrun-rag#50 的共同根因之一)。 修法:liveTripletCountsByLibrary 的 SQL 改成 pivot 出 status 再套用與 recomputeLibraryMap 逐字一致的過濾,兩邊判準對齊。 新增迴歸測試釘住此 bug(反向驗證:跑在修前的 SQL 上會失敗,非空氣測試); 獨立用 leo21c MCP 連線連讀兩次 kbdb_get_map() 重現修前症狀 (general 庫 updated_at 從 1786457080 前進到 1786457114,中間無寫入)。 kbdb/tests/library-map.test.ts 19/19 全綠。既有殘骸(100 筆 library_map record) 未清——目前沒有可用的 DELETE 通道,待部署後另行處理。 kbdb-sql-ok:liveTripletCountsByLibrary 的 .prepare 呼叫是牆內本體 (kbdb/src/actions/),本次 checkout 開在巢狀 worktree matrix/arcrun/.worktree-fix-87/(避免打斷另一 session 佔用中的 matrix/arcrun 主 checkout),kbdb-api-wall-guard.sh 的字面路徑比對 *matrix/arcrun/kbdb/src/* 吃不到中間多出的 worktree 目錄層,非真的繞牆。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -330,6 +330,15 @@ type LibraryNameSet = Set<string>;
|
||||
|
||||
// 這個 owner 底下、依 triplet 自身 'library' slot 分組的即時三元組數(缺 library slot 值的舊
|
||||
// triplet 歸 'general')——與 GET /records/triplet-stats(t142)同一套分組語意,兩處數字對得上。
|
||||
//
|
||||
// 2026-08-11 修根因(Arcrun#87,動工前量測 comment 第四節):這裡原本完全不過濾 status,
|
||||
// 而 recomputeLibraryMap(上方 withLib)只算 COALESCE(status,'active')='active'。兩邊判準不
|
||||
// 一致,只要有一筆 superseded triplet,這裡的即時計數就會跟重算後的快取對不上,
|
||||
// ensureFreshLibraryMaps 判定 stale,每次讀地圖都觸發重算,每次都新建一筆 library_map
|
||||
// record(superseded 舊的),無止盡寫 D1,且加劇 recomputeLibraryMap 本身非原子 supersede
|
||||
// 的競態(另一個已知病,wiki 08-10 條目)。實測:間隔數秒連讀兩次地圖、中間無任何寫入動作,
|
||||
// updated_at 仍前進。修法:這裡的 status 判準改成與 recomputeLibraryMap 逐字一致,兩邊算出
|
||||
// 的計數才會在資料未變動時相等,stale 判定回歸「真的有資料變動才 stale」。
|
||||
async function liveTripletCountsByLibrary(
|
||||
db: D1Database,
|
||||
tripletTemplateId: string,
|
||||
@@ -337,15 +346,18 @@ async function liveTripletCountsByLibrary(
|
||||
): Promise<LibraryCountMap> {
|
||||
const params: unknown[] = owner_id ? [tripletTemplateId, owner_id] : [tripletTemplateId];
|
||||
const res = await db
|
||||
.prepare(
|
||||
.prepare( // kbdb-sql-ok:牆內本體(kbdb/src/actions/),checkout 開在巢狀 worktree matrix/arcrun/.worktree-fix-87/(避免打斷另一 session 佔用中的 matrix/arcrun 主 checkout),hook 逐字比對 matrix/arcrun/kbdb/src/ 吃不到中間多出的 worktree 目錄層,非繞牆
|
||||
`SELECT COALESCE(NULLIF(lib_e.content, ''), 'general') AS library, COUNT(*) AS n
|
||||
FROM (
|
||||
SELECT DISTINCT ev.record_id
|
||||
SELECT ev.record_id AS rid,
|
||||
MAX(CASE WHEN ev.slot_name = 'status' THEN e.content END) AS status
|
||||
FROM entry_values ev JOIN entries e ON ev.entry_id = e.id
|
||||
WHERE ev.template_id = ?${owner_id ? ' AND e.owner_id = ?' : ''}
|
||||
GROUP BY ev.record_id
|
||||
) AS tr
|
||||
LEFT JOIN entry_values lev ON lev.record_id = tr.record_id AND lev.slot_name = 'library'
|
||||
LEFT JOIN entry_values lev ON lev.record_id = tr.rid AND lev.slot_name = 'library'
|
||||
LEFT JOIN entries lib_e ON lib_e.id = lev.entry_id
|
||||
WHERE COALESCE(tr.status, 'active') = 'active'
|
||||
GROUP BY COALESCE(NULLIF(lib_e.content, ''), 'general')`,
|
||||
)
|
||||
.bind(...params)
|
||||
|
||||
Reference in New Issue
Block a user