# FEATURE REQUEST: KBDB 應提供 upsert block endpoint(目前只能 client 端拼 GET+PATCH/POST) > 日期:2026-05-29 > 來源:arcrun Phase 2(降級假零件成 recipe) > 類型:API 缺口 —— upsert 語義目前不存在於 KBDB,被迫由 client 端拼湊 > 關聯:[BUG-2026-05-29-patch-blocks-403-different-org.md](./BUG-2026-05-29-patch-blocks-403-different-org.md)(拼湊路徑的 PATCH 那段還壞著) --- ## 問題 arcrun 原本有一個 `kbdb_upsert_block` 零件,行為是「依 page_name + user_id 查找,有就更新、沒有就新建」。但它**完全是 client 端拼湊**: ``` GET /blocks?page_name=X&limit=10 # 查找 → client 端 filter user_id 找第一筆 → 找到:PATCH /blocks/:id # 更新 → 沒找到:POST /blocks # 新建 ``` KBDB **沒有** upsert 語義的 endpoint。實測(同一把 key): | 探測 | HTTP | |---|---| | `PUT /blocks` | 404 | | `POST /blocks/upsert` | 404 | | `PUT /blocks/upsert` | 404 | ## 為什麼這是 KBDB 該補的、不是 arcrun 該拼的 arcrun 的設計原則是**薄殼 / 薄 API**:arcrun 只幫既有 API 套一層 recipe(endpoint + auth),**不無中生有功能**。 「先查再分支寫」這套 upsert 邏輯,是在 client 端**變出 KBDB 沒有的功能**。這有幾個壞處: 1. **競態(race condition)**:GET 和後續 PATCH/POST 之間,別人可能插入同 page_name 的 block,造成重複或覆寫。只有 KBDB server 端用單一交易(upsert / `ON CONFLICT`)才能正確。 2. **語義碎裂**:每個 client(arcrun / 其他 SDK)各自拼一套 upsert,filter 規則(怎麼算「同一筆」)可能不一致。 3. **拼湊路徑現在還壞著**:它依賴 PATCH /blocks/:id,而那個 endpoint 目前回 403(見關聯 bug)。 ## 建議 KBDB 提供一個原生 upsert endpoint,例如: ``` POST /blocks/upsert Body: { page_name, user_id, content, type, source, tags, ... } 語義:依 (page_name, user_id) 找唯一 block —— 存在則更新、不存在則建立(單一交易,server 端 ON CONFLICT) 回應:{ id, action: "created" | "updated" } ``` 有了它之後: - arcrun 端只要建一個 `recipe:kbdb_upsert` 指向 `POST /blocks/upsert`,套殼即可,跟其他 5 個 KBDB recipe 一致。 - 競態由 KBDB server 端交易保證,client 不再拼湊。 ## arcrun 端現狀(等 KBDB) - 其餘 5 個 KBDB 操作已降級成 recipe 並驗收:`kbdb_get`(200) / `kbdb_create_block`(201) / `kbdb_ingest`(201) / `kbdb_delete`(200) 綠;`kbdb_patch_block` 因上述 403 bug 待驗。 - `kbdb_upsert_block` **暫不降級、源碼暫留**,等 KBDB 出 `POST /blocks/upsert` 後改建 `recipe:kbdb_upsert` 套殼。