5d00e71275
頂層 D22 決策(leo 2026-07-03 拍板):推什麼由開發環境歸屬決定, Gitea private=除機敏值/build 產物/.github 外全 push。 解 T1.5 卡點:雲端工人 clone 拿得到 credential-store-migration.md,可就地改寫 SDD。 機敏掃描兩輪通過(新增 189 檔約 2.1MB,node_modules/dist/wasm 照舊排除)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
183 lines
14 KiB
Markdown
183 lines
14 KiB
Markdown
# workflow-discovery — Design
|
||
|
||
> **狀態**:草案,待確認。對應 `requirements.md`。
|
||
> **建立**:2026-06-27(issue #8)
|
||
|
||
---
|
||
|
||
## 1. 設計總綱
|
||
|
||
三件事,全部把能力落在 API(cypher-executor + KBDB),CLI/MCP 只暴露:
|
||
|
||
1. **強制 description**:部署端點驗證 description 非空 → 否則 422/400 擋下(兩條路徑共用同一驗證)。
|
||
2. **可搜的 metadata**:workflow metadata 同時寫進一個 **embeddable entry**(吃 issue #7 的 `/entries/search` 語意 + 降級)。
|
||
3. **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`)。MCP `u6u_deploy_workflow.ts:22` 打 `http://cypher-executor/workflows/deploy` → **目前必 404**。意味 MCP 部署路徑現狀是壞的 / 從未端到端驗過。這影響「兩條路徑」的定義,見 §3.1a 待決。
|
||
|
||
description 強制**落在 cypher-executor 的部署端點**。實際部署端點有三個:
|
||
- `POST /webhooks`(token 式匿名):description optional。
|
||
- `POST /webhooks/named`(CLI `acr 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` 介面層**做了一整段:
|
||
1. `loadWorkflowYaml` + `parseTriplets`(解析 YAML flow → triplets)
|
||
2. `POST /cypher/search`(triplets → 執行圖 graph)
|
||
3. config 套節點、組 `{id, name, nodes, edges}` graph
|
||
4. `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_type` filter,若無則補一個 filter 參數——base 通用 filter,不寫死 workflow)。
|
||
- **self-hosted 影響**:description 強制對所有用戶生效 → 回填策略(R3)讓既有資產平滑過渡,不一刀斷。
|
||
- **不改**:workflow 執行/trigger 路徑、Vectorize 開關機制(複用 #7)、零件搜尋。
|
||
|
||
---
|
||
|
||
## 9. 拍板結果(leo 2026-06-27 issue #8,4 點全定)
|
||
|
||
1. **Q1 方案 C(雙寫)✅** — record 保 list/get 零回歸 + entry_type=workflow embeddable entry 吃 #7 語意,waitUntil 非阻塞。
|
||
2. **Q2 description「操盤 CC 據實生成、用戶可改」✅(leo 翻案)** — 非「介面層不生成」也非「逼用戶手填」。強制非空仍落 API;空時要求**操盤 CC 據實補一句**(非介面層機械塞佔位,仍禁佔位)。定位=一句「能做什麼」非文章。詳見 §3.2。
|
||
3. **Q3 回填「下次部署強制 + 提示式 backfill」✅** — 無 desc 列出待 re-deploy、不自動編造、不掛 cron。
|
||
4. **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 端到端綠燈為完成標準。
|