前言
KnockersTaskBlocks 完成結構化文件、互動式 HTML與跨職能流程圖後,下一個需求是讓 Codex 或其他自動化程式把研究結果寫進 Kanboard 任務。
Kanboard 原生 JSON-RPC 已能建立任務、上傳附件與建立任務關聯,但 Task Document 當時只能由登入後的網頁編輯器透過 Session 與 CSRF 儲存。外部 Agent 即使擁有有效 API Key,也無法用正式介面讀取或寫入內容區塊。
如果改成 SSH 進主機呼叫內部 Model,或讓 Agent 操作瀏覽器表單,雖然可以完成一次任務,卻繞開權限、Revision、Validation 與穩定錯誤合約。這篇記錄我如何把 Task Document 接到 Kanboard 原生 JSON-RPC,同時避免 AI 覆蓋使用者剛完成的修改。
API 不應該建立第二套儲存流程
最危險的做法,是為了 API 方便而直接寫入外掛資料表。
網頁儲存原本已經處理:
- 專案是否啟用 Task Blocks。
- 使用者是否能讀取或修改任務。
- Block migration 與完整 schema validation。
- 圖片、附件與連結 ownership。
- Markdown Compatibility Content。
- Revision history 與保留上限。
- Optimistic locking 與 database transaction。
如果 JSON-RPC 重新實作一次,兩條路徑很快就會出現差異。API 可能接受網頁不允許的區塊,也可能忘記更新相容內容或繞過自訂角色限制。
因此 JSON-RPC Procedure 只是一個薄 Adapter,真正儲存仍呼叫和網頁編輯器相同的 Task Document service。
只提供三個方法
外部介面刻意保持小型:
getKnockersTaskDocument
saveKnockersTaskDocument
appendKnockersTaskBlocks
get 回傳目前 revision、Canonical Document、Compatibility Content、支援的 block definitions 與 guardrails。
save 完整取代 Task Document,適合已經讀取最新文件並完成合併的 Client。
append 只把新區塊加入文件尾端,不允許藉此修改、刪除或重排既有區塊。它讓 AI 可以安全追加研究筆記,又不需要每個整合都自行實作 document merge。
原生任務仍由 Kanboard 的 createTask 建立,附件和連結也先走原生 procedure。外掛 API 不複製已經存在的大型任務建立介面,只處理自己的 Task Document aggregate。
為什麼每次寫入都需要 expected_revision?
假設使用者和 Agent 同時讀到 Revision 4:
使用者 ──讀取 Revision 4──→ 修改標題
Agent ──讀取 Revision 4──→ 產生研究內容
使用者 ──儲存成功────────→ Revision 5
Agent ──仍以 Revision 4 儲存
如果 API 只做最後寫入者勝出,Agent 的完整文件會直接覆蓋使用者變更,而且兩邊都以為自己成功。
因此寫入必須帶上讀取時取得的 expected_revision:
{
"jsonrpc": "2.0",
"method": "saveKnockersTaskDocument",
"id": 1,
"params": {
"task_id": 42,
"expected_revision": 4,
"document": {
"schemaVersion": 1,
"blocks": []
}
}
}
資料庫更新條件同時比對 Task ID 與 Revision。受影響列數不是一筆時,server 回傳 revision_conflict,不產生部分寫入,也不增加歷史版本。
Client 接到衝突後要重新讀取 Revision 5,理解最新變更,再決定合併或放棄。不能把 retry 寫成原封不動重送,否則 optimistic locking 只剩形式。
Append 仍然需要 Revision
只追加區塊看起來不會改動既有內容,但仍可能和其他操作產生順序與限制衝突。
例如使用者已經加入新段落、另一個 Agent 也正在追加內容,或文件已接近 block 數與 byte limit。Server 需要在同一個 Canonical Document 上確認 Revision、合併新區塊,再執行完整 validation 與 transaction。
Append 的流程是:
- 讀取目前 Canonical Document。
- 比對
expected_revision。 - 保留既有 block JSON 與順序。
- 將新區塊加入尾端。
- 執行完整文件 validation。
- 產生 Compatibility Content。
- 儲存新 Revision 與 history。
它不是直接對資料表做 array append,而是受限制的 server-side merge。
沿用 Kanboard 的身分與權限
JSON-RPC 使用 Kanboard 原生 HTTP Basic username 與 API Key 驗證,外掛不簽發也不保存第二組憑證。
讀取需要使用者能看見任務所屬專案;寫入則需要和網頁編輯器相同的實際更新權限。自訂角色如果只能修改被指派任務,API 也必須得到同樣結果。
為了避免權限漂移,Web Controller 與 JSON-RPC Adapter 共用同一個 Task Document authorization module。這個模組只回答任務是否存在、專案是否啟用,以及目前使用者能否讀寫,不執行 persistence。
Agent 不能自行宣告附件屬於哪個任務
Task Document 的圖片、附件與內部連結只保存原生資源 ID。API Client 必須先透過 Kanboard procedure 建立資源,再把取得的 ID 放入區塊。
Server 仍會重新確認:
- File 是否真的屬於目前任務。
- Internal Link 是否是這筆任務的原生關聯。
- External Link 是否由目前任務建立。
- HTML 與流程圖是否符合既有大小和結構限制。
Client 傳入一個數字 ID,不代表它有權引用該資源。Ownership 必須由 server 從資料庫關係判斷。
錯誤需要能讓自動化判斷
人類看到錯誤畫面後可以自行理解,Agent 則需要穩定合約。可預期的 domain failure 使用:
{
"ok": false,
"error": {
"code": "revision_conflict",
"message": "Document changed before save."
}
}
主要錯誤包含:
task_not_foundtask_blocks_disabledrevision_conflictvalidation_failed
Authentication 與 authorization failure 維持 Kanboard 標準 JSON-RPC access denied,不把 private exception、stack trace 或資源存在性放進回應。
這不是把 AI 模型裝進 Kanboard
這次外掛只提供穩定的資料介面,不負責:
- 呼叫模型供應商。
- 保存 Prompt 或模型回應。
- 管理 AI 憑證。
- 自動解決衝突。
- 讓 Agent 任意 Patch 資料庫欄位。
AI 仍然在外部執行,Kanboard 只提供受權限、版本與 validation 約束的讀寫能力。這讓不同 Agent、Script 或整合服務都可以使用相同合約,而不把產品綁在單一模型上。
如何驗證 API 沒有走捷徑?
最高層驗收不是直接呼叫 PHP class,而是對真正的 jsonrpc.php 發出請求,確認:
- 方法完成註冊並可被找到。
- 原生 API Key 驗證生效。
- 不同專案角色得到正確結果。
- 空文件以 Revision 0 和標準 envelope 回傳。
- Replace 與 Append 都產生 Compatibility Content。
- Revision history 和保留策略正常。
- 衝突與無效資料不產生部分寫入。
- API 建立的區塊能在任務頁正常呈現。
- 憑證沒有進入 fixture、response 或 log。
這個 API 最重要的設計不是 method 數量,而是沒有建立特權寫入路徑。Agent 和人類編輯器最後都經過同一套授權、validation、projection、Revision 與 transaction。
下一篇將處理讀取的另一端:Kanboard 單一任務公開分享開發實錄:Capability URL、輪替與撤銷。
