Files
Arcrun/system-dev/docs/3-specs/workflow-discovery/design.md
T
uncle6me-web 7bd7b4b26a SDD 生命週期鐵律遷移:單一活性制度上線(portal-auth=active,其餘 paused/draft)
- 鋪檔(自 system-dev-template v1.15.0):SDD-LIFECYCLE.md+pending-changes.md
  +sdd-guard.sh(覆蓋舊版,加單一活性檢查)+sdd-check.md+sdd-active-check.sh
- settings.json PreToolUse(Write|Edit|MultiEdit)掛上 sdd-guard.sh
- 全部 SDD design.md 掛 frontmatter:portal-auth=active(現行 portal 線,
  #61 demo 四件套剛 merge);artifact-sharing=draft(零任務動工);
  其餘 16 份=paused(皆有未完成任務,無明顯死件,不硬 close)
- CLAUDE.md 加「SDD 生命週期鐵律」段(指向 SDD-LIFECYCLE.md+濃縮五條)
  +SDD 速查表改以 frontmatter status: active 為現行判準
- 驗證:sdd-active-check exit 0(恰 1 份 active);guard pipe-test code 檔
  exit 0 帶現行 SDD 提示;反向測試(造 2 份 active)guard exit 2/check exit 1 全擋

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:03:16 +08:00

188 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
status: paused
superseded_by: ""
---
# workflow-discovery — Design
> **狀態**:草案,待確認。對應 `requirements.md`。
> **建立**2026-06-27issue #8
---
## 1. 設計總綱
三件事,全部把能力落在 APIcypher-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. 核心決策 Q1metadata 存哪(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 寫失敗不阻塞部署但回 warningfire-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 透傳給 AIAI 看到就能主動問用戶開 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_workflowAI 收到自然語言意圖時**主動 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 #84 點全定)
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 端到端綠燈為完成標準。