fix(kbdb): 開通 PATCH /kbdb/records/:id proxy + 補三元組 library 補標測試
leo 2026-08-11:貼 wiki 卡「這裏就有 3 個三元組,如果三元組是 0 一定是有錯」—— 三元組沒有不見(新式 1,633 筆/舊式 636 筆都還在),問題是地圖看不到:新式三元組 只有 171 筆填了 library slot,其餘因寫入時 template 還沒有該 slot 而被靜默丟棄 (record-crud.ts createRecord 只替「已宣告」的 slot 建值,不是替 caller 傳的 values 建值——順序錯了值就消失,不報錯)。 本次改動: - cypher-executor/src/routes/kbdb-proxy.ts:補 PATCH /kbdb/records/:recordId。 基本盤(kbdb/src/routes/records.ts)早有這個端點(mira-dissolve T2.1 的 updateRecord),但這條 proxy 之前只轉發 GET/POST /kbdb/records,外部(工作流/ CLI/補標腳本)打不到——能力在,通道沒開,補標三元組只能繞去改表(違 D38)。 純轉發、無業務邏輯,比照既有 PATCH /kbdb/entries/:id 慣例。 - kbdb/tests/triplet-library-backfill.test.ts:真 node:sqlite 驗三件事—— ①源頭順序(ensure-slot 必須在 write 之前,事後補救不了已寫的那筆,重現+ 驗證正解)②存量補標(地圖輸出前後對照)③不會重複做(同批跑兩次,第二次 touch 0 筆)。順手記錄一個相關但不在本次範圍的小落差:liveTripletCountsByLibrary 的 'general' fallback 桶與 recomputeLibraryMap 的精確比對語意沒對齊。 - cypher-executor/tests/kbdb-records-patch-proxy.test.ts:新端點的租戶閘/ 參數驗證/轉發/404 透傳。 全數綠燈:kbdb 181/181、cypher-executor 新增 8 案全過(既有 14 案失敗為 pre-existing,已用 git stash 比對確認與本次改動無關)。 kbdb-graph-plugin(實際的三元組寫入端 createTriplet/ingestEnvelope)需要同款 修正(library 補進 TRIPLET_SLOTS + 從 source_uri 推導),但該 repo 現行 0 份 active SDD,其 sdd-guard 明確要求人決定要不要開啟——本次不越界代為決定, 留在報告中交回。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,27 @@ kbdbProxyRouter.get('/kbdb/records/:recordId', async (c) => {
|
||||
return new Response(res.body, { status: res.status, headers: { 'Content-Type': 'application/json' } });
|
||||
});
|
||||
|
||||
// PATCH /kbdb/records/:recordId — 翻某筆 record 的 slot 值({ values:{slot:content} })。
|
||||
// 補上基本盤既有能力(kbdb/src/routes/records.ts 的 PATCH /records/:recordId,mira-dissolve T2.1)
|
||||
// 缺的對外通道——2026-08-11 leo 三元組 library 補標核實:base 早有這個端點,但這條 proxy
|
||||
// 之前只轉發 GET/POST,插件/工作流打不到,補標三元組只能繞去改表(違 D38)。單純轉發,無業務邏輯。
|
||||
// by-id 沿用既有慣例(require-key,不額外做 owner 比對——與本檔 GET .../:recordId、
|
||||
// PATCH /kbdb/entries/:id 同款)。
|
||||
kbdbProxyRouter.patch('/kbdb/records/:recordId', async (c) => {
|
||||
if (!tenant(c)) return c.json(NEED_KEY, 401);
|
||||
const body = await c.req.json().catch(() => null);
|
||||
if (!body || typeof body.values !== 'object' || body.values === null) {
|
||||
return c.json({ error: 'values 必填({slot名: 內容})' }, 400);
|
||||
}
|
||||
const { base, headers } = kbdbBase(c.env);
|
||||
const res = await fetch(`${base}/records/${encodeURIComponent(c.req.param('recordId'))}`, {
|
||||
method: 'PATCH',
|
||||
headers,
|
||||
body: JSON.stringify({ values: body.values }),
|
||||
});
|
||||
return new Response(res.body, { status: res.status, headers: { 'Content-Type': 'application/json' } });
|
||||
});
|
||||
|
||||
// ── search(限本租戶範圍內)────────────────────────────────────────────────────
|
||||
|
||||
// GET /kbdb/search?q=&entry_type=&source=&library=&mode= — entries 搜尋,限本租戶 owner_id。
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* PATCH /kbdb/records/:recordId proxy 測試(2026-08-11,三元組 library 補標需求核實)
|
||||
*
|
||||
* 背景:基本盤 kbdb/src/routes/records.ts 早有 PATCH /records/:recordId(mira-dissolve T2.1,
|
||||
* updateRecord 已支援「補一個 record 原本沒有的 slot 值」的 idempotent grow)。但這條 cypher
|
||||
* proxy(kbdb-proxy.ts)之前只轉發 GET/POST /kbdb/records,沒開 PATCH——外部(工作流/CLI/
|
||||
* 任何走 X-Arcrun-API-Key 的呼叫者)打不到,等於基本盤能力在,通道沒開。
|
||||
*
|
||||
* 驗證 IO 接線(聚合真身在 KBDB 基本盤,這裡只測轉發,比照 kbdb-map-proxy.test.ts 慣例):
|
||||
* 1. 租戶閘:無 X-Arcrun-API-Key → 401 不碰 KBDB
|
||||
* 2. body 沒有 values → 400,不轉發
|
||||
* 3. 轉發:PATCH /kbdb/records/:id → base PATCH /records/:id,body 只帶 { values }
|
||||
* 4. base 404(record 不存在)→ 原樣透傳,不假裝成功
|
||||
*
|
||||
* KBDB 打 fetchMock 假 host(wrangler.test.toml KBDB_BASE_URL=https://kbdb.test)+
|
||||
* disableNetConnect——測試絕不外連。
|
||||
*/
|
||||
import { SELF, fetchMock } from 'cloudflare:test';
|
||||
import { beforeAll, afterEach, describe, it, expect } from 'vitest';
|
||||
|
||||
const KEY = { 'X-Arcrun-API-Key': 'leo', 'Content-Type': 'application/json' };
|
||||
|
||||
beforeAll(() => {
|
||||
fetchMock.activate();
|
||||
fetchMock.disableNetConnect();
|
||||
});
|
||||
afterEach(() => fetchMock.assertNoPendingInterceptors());
|
||||
|
||||
describe('PATCH /kbdb/records/:recordId — 租戶閘', () => {
|
||||
it('無 X-Arcrun-API-Key → 401,不碰 KBDB', async () => {
|
||||
const res = await SELF.fetch('http://localhost/kbdb/records/rec_1', {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ values: { library: 'kb' } }),
|
||||
});
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /kbdb/records/:recordId — 參數驗證', () => {
|
||||
it('body 沒有 values → 400,不轉發', async () => {
|
||||
const res = await SELF.fetch('http://localhost/kbdb/records/rec_1', {
|
||||
method: 'PATCH',
|
||||
headers: KEY,
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /kbdb/records/:recordId — 轉發', () => {
|
||||
it('轉發 base PATCH /records/:id,body 只帶 values(不夾帶其他欄位)', async () => {
|
||||
fetchMock
|
||||
.get('https://kbdb.test')
|
||||
.intercept({
|
||||
path: '/records/rec_1',
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ values: { library: 'gitea:Leo/kb' } }),
|
||||
})
|
||||
.reply(200, {
|
||||
success: true,
|
||||
record: { record_id: 'rec_1', template_id: 'tpl-triplet', values: { library: 'gitea:Leo/kb' } },
|
||||
});
|
||||
const res = await SELF.fetch('http://localhost/kbdb/records/rec_1', {
|
||||
method: 'PATCH',
|
||||
headers: KEY,
|
||||
body: JSON.stringify({ values: { library: 'gitea:Leo/kb' } }),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
const data = (await res.json()) as { success: boolean; record: { values: Record<string, string> } };
|
||||
expect(data.success).toBe(true);
|
||||
expect(data.record.values.library).toBe('gitea:Leo/kb');
|
||||
});
|
||||
|
||||
it('base 404(record 不存在)→ 原樣透傳,不假裝成功', async () => {
|
||||
fetchMock
|
||||
.get('https://kbdb.test')
|
||||
.intercept({ path: '/records/nope', method: 'PATCH' })
|
||||
.reply(404, { success: false, error: 'not found' });
|
||||
const res = await SELF.fetch('http://localhost/kbdb/records/nope', {
|
||||
method: 'PATCH',
|
||||
headers: KEY,
|
||||
body: JSON.stringify({ values: { library: 'kb' } }),
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
const data = (await res.json()) as { success: boolean };
|
||||
expect(data.success).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user