📋 SDD:workflow-discovery(本 commit 同時執行 D35 交接:portal-auth 26/26 完成 → closed superseded_by workflow-discovery;workflow-discovery paused → active。單一活性已驗=1 份) 🎯 對應 task:3.x 搜尋端誠實化(CP2-B) 病灶(leo 2026-07-30 定性「腹語術」): search-nodes.ts 無條件回 status:'found'、missingNodes 永遠 []—— 型別宣告了 'missing' 但程式碼從不使用。實測「完全不存在的東西xyz」也回 found。 ⇒ AI 拿到假信號 → 以為零件存在 → 部署才發現沒有 → 改寫 code ⇒ 正式 workflow 只用 2 個零件、8 個 code 節點含 if×61。 修法: - 查 registry 判真實存在(走 HTTP,守 D28 禁新增 service binding; URL 用既有 wasmWorkerUrl() 慣例組,不自創) - found 時附 input_schema/success_rate/stability ⇒ AI 才填得出 payload、才看得到「測過幾次」(leo:AI 只要填 payload) - 查不通回 'unknown' 而非 'missing'——**誠實限制**: 不能因查詢失敗就宣告零件不存在(那會讓 AI 誤判而重寫 code) - missing 真的回傳出去(原本寫死 []) ⚠️ 實測發現 registry **沒有列表端點**(GET /components → 404,只有 /components/<id>) ⇒ 改為逐個查(節點數通常 <10、5s timeout)。補列表端點後可改抓一次=CP2-B 待辦。 驗:tsc 零錯誤;vitest 9 failed/179 passed=**與改動前 stash 對帳完全相同**(既有債非本次造成)。 ⏳ 待部署到實例後跑 arcrun-usable/verify.sh 驗 01 那組轉綠。
14 KiB
status, superseded_by
| status | superseded_by |
|---|---|
| active |
workflow-discovery — Design
狀態:草案,待確認。對應
requirements.md。 建立:2026-06-27(issue #8)
1. 設計總綱
三件事,全部把能力落在 API(cypher-executor + KBDB),CLI/MCP 只暴露:
- 強制 description:部署端點驗證 description 非空 → 否則 422/400 擋下(兩條路徑共用同一驗證)。
- 可搜的 metadata:workflow metadata 同時寫進一個 embeddable entry(吃 issue #7 的
/entries/search語意 + 降級)。 - search_workflow 工具:薄殼呼叫 cypher 的 workflow 搜尋端點,後者轉發 KBDB
/entries/search,限本租戶。
2. 核心決策 Q1:metadata 存哪(record vs entry vs 雙寫)
| 方案 | 做法 | 優 | 劣 |
|---|---|---|---|
| A. records 表加 q 搜尋 | 給 /records/search 加 q= LIKE 掃 slots |
改動小 | records 吃不到 #7 的 Vectorize 語意;要另接一套語意=重造 #7 已有的輪子 |
| B. 改存 entry(取代 record) | workflow metadata 改寫進 entries(entry_type=workflow),description 進可 embed 欄位 |
直接吃 #7 語意 + 降級閉環,零重造 | 既有 u6u_list_workflows 讀 record 要改;migration 成本 |
| C. 雙寫(record + entry) ⭐ | 部署時同時寫 workflow_metadata record(保既有 list/get 不動)+ 一個 entry_type=workflow 的 embeddable entry(給搜尋) |
list/get 零回歸;搜尋吃 #7 語意;兩者解耦各司其職 | 一份資料兩處;要保證一致(部署原子性) |
推薦:C(雙寫),理由:
- 不回歸:
u6u_list_workflows/u6u_get_workflow繼續讀 record,完全不動(降低框架級風險,守 C1)。 - 吃現成語意:搜尋走 entry → 直接複用 #7 的
/entries/search?mode=semantic+ 降級 + capability_hint,零重造。 - 解耦:record=「我有哪些 workflow」(精確列舉,list 場景);entry=「找做某事的 workflow」(語意檢索,search 場景)。職責不同、欄位不同,本就該分。
- 一致性風險可控:雙寫在同一個部署 API handler 內,entry 寫失敗不阻塞部署但回 warning(fire-and-forget + 可補建),對齊 #7
embedOnWrite的waitUntil非阻塞慣例。
被否的 A:records 加 q 只能 LIKE,要語意還是得自己接 Vectorize=把 #7 在 entries 做好的事在 records 再做一遍。違反「不重造輪子」。 被否的 B:取代 record 會逼 list/get 一起改,框架級風險放大,不值得。
3. R1:強制 description(落 API)
3.1 統一驗證點
⚠️ 實作期發現(2026-06-27,已讀 code):
POST /workflows/deploy在 cypher-executor 根本不存在(route 清單只有/workflows/:name/executions、/workflows/resume)。MCPu6u_deploy_workflow.ts:22打http://cypher-executor/workflows/deploy→ 目前必 404。意味 MCP 部署路徑現狀是壞的 / 從未端到端驗過。這影響「兩條路徑」的定義,見 §3.1a 待決。
description 強制落在 cypher-executor 的部署端點。實際部署端點有三個:
POST /webhooks(token 式匿名):description optional。POST /webhooks/named(CLIacr push,具名):description optional → 改必填。POST /workflows/deploy(MCP 目標):不存在(404)。
3.1a 待決:MCP 部署路徑怎麼收斂(新發現,需拍板)
兩個方向:
- 方向 ① MCP 改打
/webhooks/named(推薦):MCP deploy 不再打死端點,改打 CLI 同一條/webhooks/named(傳 name + graph + description)。→ CLI/MCP 真正共用一條部署端點(rule 07 薄殼一致的最強形態),死端點順手清掉,description 強制只需做在/webhooks/named一處。 - 方向 ② 新建
/workflows/deploy:補上這條 route 給 MCP 專用。→ 但會變成「兩條部署端點各做一次 description 強制」,且與/webhooks/named職責重疊(都是部署具名 workflow),違反「能力只實作一次」。
leo 拍板(2026-06-27 issue #8):方向① ✅。CLI/MCP 共用一條部署端點=rule 07 最強形態;list 改讀
GET /webhooks/named(現成端點,回歸可控)。
強制規則:缺/空白 trim 後為空 → 400 { error: '<§3.2 定位訊息>', requires: 'description' }。驗證是 API 行為(R1.3),薄殼不判空(守 rule 07)。
3.1b 方向①實作期再發現:YAML→部署編排被寫在 CLI 介面層(藏的薄殼債)
讀 code 發現「MCP 改打 /webhooks/named」不是換 URL 就好——/webhooks/named 吃的是 graph 物件,不是 YAML。把 YAML 變 graph 的編排目前在 CLI push.ts 介面層做了一整段:
loadWorkflowYaml+parseTriplets(解析 YAML flow → triplets)POST /cypher/search(triplets → 執行圖 graph)- config 套節點、組
{id, name, nodes, edges}graph POST /webhooks/named(傳 graph)
而 MCP u6u_deploy_workflow 吃 yaml_content 字串、期待一個吃 YAML 的 server 端點(打 /workflows/deploy 傳 Content-Type: application/yaml)——這端點從來不存在(故 404)。
張力:方向① 若讓 MCP 在介面層複製 CLI 那 4 步編排 → 兩個薄殼各做一次 YAML→graph 編排 = 違 rule 07「能力只實作一次」(正是薄殼原則要防的)。
收斂選項(待總管定,§3.1c):
- ①-a 最小修:MCP 介面層複製 CLI 的 YAML→graph 步驟,打 /webhooks/named。→ 快,但複製編排=技術債(與方向①初衷「薄殼統一」自相矛盾)。
- ①-b 編排下沉(正解) ⭐:新增 API 端點
POST /workflows/deploy(這次真的建,但吃 YAML、職責是「YAML→graph→部署」完整編排),CLI 和 MCP 都只傳 YAML。→ 真正一處編排,兩薄殼都瘦。但要把 CLI push.ts 的編排搬進 API(較大重構,且碰 exposure_consent 流程)。 - ①-c 折中:先 ①-a 讓 MCP 通(解 404、issue #8 範圍內),①-b 編排下沉另開 issue(薄殼債獨立追)。
註:①-b 的
/workflows/deploy與被否的「方向②」不同——②是「為 MCP 另開一條重複端點」,①-b 是「把編排下沉成唯一真相端點,CLI 也改用它」。前者增重複,後者消重複。
3.2 description 來源(Q2 定案:操盤 CC 據實生成、用戶可改)
leo 翻案,但守住誠實精神。目標用戶 low-code,不知道要填 description;強迫填一個不懂的欄位=違北極星「不增加用戶負擔」。調和關鍵=分清兩種「生成」:
- ✅ 操盤的 CC 據實生成:CC 剛幫用戶建這工作流,最懂它串了什麼、做什麼 → 由 CC 寫一句真實的「這做什麼」(leo 例:「呼叫可以 Upsert Google Sheets」)。這是真描述非假裝,完全不違 mindset §7。
- ❌ 介面層機械塞佔位(從 name 複製、塞
workflow_xxx預設)=假描述,仍禁(§3.2 原本防的就是這個,保留)。
落地規則:
- YAML
description:為主:CC 寫 workflow YAML 時就據實填好 description(既有慣例,registry/examples已示範)。 - description 強制非空仍落 API(R1 不變,§3.1 驗證點不動)。
- 部署若收到空 description:不是擋下逼用戶手填,而是回明確訊息要求操盤 CC 據實補一句再部署(CC 是 description 的生成者,用戶可改)。錯誤訊息對齊此定位:「description 必填:請操盤的 AI 據實寫一句『這工作流能做什麼』(如「呼叫可 Upsert Google Sheets」),用戶可再改。」
- 內容定位:一句「這工作流能做什麼」,不是寫文章。供語意搜尋命中,要點是「做什麼」可被自然語言意圖匹配。
- 仍禁:介面層(CLI/MCP TS)機械從 name 生成佔位(守 rule 07 + 誠實,hook 7.x 範圍)。「據實生成」是操盤 CC 在寫 YAML 當下做的,不是介面層 deploy 時自動補。
4. R2:可搜的 entry + search_workflow
4.1 部署時寫 embeddable entry(雙寫的 entry 那半)
部署成功後(record 寫完),cypher 端再寫一個 entry 到 KBDB:
POST /kbdb/entries (經 cypher proxy,租戶隔離注 owner_id)
{
entry_type: "workflow",
page_name: <workflow name>,
content: <description>, // 被 embed 的主體
metadata: {
embed: true, // #7 精耕開關:只有標 true 才進 Vectorize
workflow_id, name, deployed_at
}
}
metadata.embed:true→ 命中 #7 的embedOnWrite精耕條件,description 進 Vectorize(若模組開)。- 模組沒開 → entry 照樣存(D1),語意搜降級成 LIKE 仍能命中 content/name(不假綠)。
- owner_id 由 cypher proxy 強制注入本租戶(沿用既有隔離,C4/R2.4)。
4.2 search_workflow 工具(薄殼)
新 MCP 工具 search_workflow(query),形態抄 u6u_search_components:
search_workflow(query) →
cypher GET /workflows/search?q=<query>&mode=semantic
→ KBDB /entries/search?q=&owner_id=&source=&mode=semantic&entry_type=workflow
→ mode=semantic 開:Vectorize 語意命中
→ 沒開:降級 keyword(LIKE) + capability_hint「叫 CC 幫你開語義查詢」
- cypher 加一條
GET /workflows/search(薄轉發到 KBDB/entries/search,限entry_type=workflow+ 本租戶 owner_id)。 - MCP 工具把結果格式化(含 capability_hint 透傳給 AI,AI 看到就能主動問用戶開 Vectorize → R2.3)。
- 命名:MCP 既有工具是
u6u_*前綴(u6u_search_components)。本工具命名u6u_search_workflows(複數對齊u6u_list_workflows),對外描述「用自然語言找現成工作流」。
4.3 Vectorize 開啟(R2.3,不新造)
完全沿用 #7:acr init 問 / kbdb_embed:true + acr update → deploy 建 Vectorize index。AI 從 search 回傳的 capability_hint 得知未開 → 主動問用戶要不要開。本 SDD 不碰 Vectorize 開關機制,只消費它。
5. R3:既有工作流回填
推薦:下次部署強制 + 一次性 backfill 端點(提示式,不自動跑)
- 下次部署強制:R1 生效後,任何 re-deploy 都會被逼填 description → 自然回填(隨用隨補)。
- 一次性 backfill:加
POST /workflows/backfill-search-entries(限本租戶),把現有 workflow_metadata records 中已有 description 的補寫成 entry(讓它們可搜);無 description 的列出來回報「這些需要 re-deploy 補 description」,不自動編造描述(誠實)。 - backfill 是 AI/人主動呼叫(CLI/MCP 暴露),不掛 cron/輪詢(C2 flag 紅線)。
- 不破壞執行:entry 只供搜尋,workflow trigger 仍讀 WEBHOOKS KV / record,互不影響(R3.2)。
6. 薄殼一致性(rule 07)
| 能力 | API(真相) | MCP 薄殼 | CLI 薄殼 |
|---|---|---|---|
| 強制 description | cypher 部署端點驗證 | (deploy 工具透傳錯誤) | (push 透傳錯誤) |
| 搜尋 workflow | cypher /workflows/search → KBDB /entries/search |
u6u_search_workflows(本 SDD) |
acr workflow search(R2.5,次階段,對稱補) |
| backfill | cypher /workflows/backfill-search-entries |
可選暴露 | 可選暴露 |
齊的單位是「能力」不是「端點」(decisions-summary 已釐清):search 能力 MCP 先到位(AI 先用),CLI 對稱補可列次階段,不阻塞。
7. flag 安全自檢(C2)
- search_workflow=AI 收到自然語言意圖時主動 call 一次,無排程、無 webhook 觸發、無跨 repo fan-out。✅
- backfill=人/AI 主動呼叫一次,非 cron。✅
- 雙寫的 entry embed 走
waitUntil非阻塞,是單次部署內的副作用,非輪詢。✅
8. 影響面(框架級,C1)
- 改 cypher-executor:
/webhooks/named+/workflows/deploy加 description 強制;新增/workflows/search+/workflows/backfill-search-entries;部署 handler 加雙寫 entry。 - 改 MCP:
u6u_deploy_workflow抓並傳 description;新增u6u_search_workflows。 - 改 KBDB:可能不用改(複用 #7
/entries/search+entry_type=workflow過濾;確認/entries/search支援entry_typefilter,若無則補一個 filter 參數——base 通用 filter,不寫死 workflow)。 - self-hosted 影響:description 強制對所有用戶生效 → 回填策略(R3)讓既有資產平滑過渡,不一刀斷。
- 不改:workflow 執行/trigger 路徑、Vectorize 開關機制(複用 #7)、零件搜尋。
9. 拍板結果(leo 2026-06-27 issue #8,4 點全定)
- Q1 方案 C(雙寫)✅ — record 保 list/get 零回歸 + entry_type=workflow embeddable entry 吃 #7 語意,waitUntil 非阻塞。
- Q2 description「操盤 CC 據實生成、用戶可改」✅(leo 翻案) — 非「介面層不生成」也非「逼用戶手填」。強制非空仍落 API;空時要求操盤 CC 據實補一句(非介面層機械塞佔位,仍禁佔位)。定位=一句「能做什麼」非文章。詳見 §3.2。
- Q3 回填「下次部署強制 + 提示式 backfill」✅ — 無 desc 列出待 re-deploy、不自動編造、不掛 cron。
- Q4 補 base 通用 entry_type filter ✅ — 總管查證:
/entries/search目前不支援 entry_type 過濾(只有 q/owner_id/source/mode),但 schema 有 entry_type 欄位 + 索引(0001_base.sql:16-20)。要改 4 處:route(entries.ts:43-77)/searchEntries/semanticSearch(embed.ts)/kbdb-proxy。做成 base 通用 filter 不寫死 workflow。
狀態:4 點拍定,已點頭實作。 按 tasks.md Phase 1→6 推進,leo21c 端到端綠燈為完成標準。