feat(kbdb): recipe 公庫/私庫雙向機制 + UUID 身份 + KBDB Base + 市場數據
kbdb-base SDD §7.5(公庫/私庫雙向機制,richblack 2026-06-07 拍板)。
## KBDB Base worker(新)
- kbdb/:D1-only 核心三表(entries/templates/entry_values)+ CRUD + LIKE search
+ recipe-stats 端點(市場數據)+ 0001_base.sql migration(含 recipe_stat seed)
## Phase 2.3:init 建 D1 + 套 migration
- cli cf-api.ts 加 listD1Databases/ensureD1Database;init 建 arcrun-kbdb D1
- deploy.ts 部署後對 D1 套 0001_base.sql(CF /d1/query API,idempotent)+ 注入 database_id
## Phase 5.1:recipe 成功記錄(市場數據來源)
- GraphExecutor 收集本次用到的 recipe uuid(usedRecipeKeys)
- executeWebhookGraph 執行結束一次性記 per-uuid 成功/失敗到 KBDB(fire-and-forget)
## Phase 7.5:recipe UUID 身份 + app-store 模型
- recipe 領 uuid=唯一身份;canonical_id/author/公私=屬性(§7.5.5)
- recipe:{uuid} + idx:canonical/installed/hash;resolveRecipe 向後相容不破執行鏈
- POST /recipes/submit=領新 uuid 新增作者版本(非覆蓋,app-store)
- GET /public-recipes 搜尋(多作者+per-uuid 市場星數)/ :id pull(選市場最佳)
- 落空→found:false 創作引導(§7.5.6 閉環)
- POST /recipes/migrate-uuid 一次性轉舊 key(增量寫不刪舊、冪等)
- init-seed 用 UUID(author=system)
## 薄殼(rule 07 §5:CLI + MCP 覆蓋同組能力)
- CLI: acr recipe search/pull/submit-p(config 加 DEFAULT_PUBLIC_LIBRARY_URL)
- MCP: arcrun_recipe_search/pull/submit_p/push/list/delete(補齊漂移)
## 壓測修正
- api-recipe-seeds: google_sheets_append PUT→POST(:append 正確動詞,階段12)
四 worker tsc 全綠(cypher/cli/kbdb/mcp)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,41 @@ import { graphSchema } from '../lib/schemas';
|
||||
import { createComponentLoader } from '../lib/component-loader';
|
||||
import { recordTelemetry } from '../lib/telemetry';
|
||||
|
||||
/**
|
||||
* kbdb-base §7.1+§7.5.h:一條工作流執行結束後,把這次用到的 recipe 各記一次成功/失敗到 KBDB 市場星數。
|
||||
* 判定單位是「工作流執行」(n8n execution):整體成功 → 用到的每個 recipe key +1 成功;整體失敗 → 各 +1 失敗。
|
||||
* **key = recipe uuid**(per-uuid,能區分同 canonical 的不同作者版本 §7.5.5;舊資料 fallback canonical_id)。
|
||||
*
|
||||
* fire-and-forget(用 ctx.waitUntil,仿 recordTelemetry):記錄失敗不影響工作流結果。
|
||||
* KBDB 端點 POST {KBDB_BASE_URL}/recipe-stats/record { canonical_id, ok, at }——
|
||||
* 該欄位名為 canonical_id 但語意已是 recipe key(uuid),KBDB 端只當 stat 的主鍵字串用。
|
||||
*/
|
||||
function recordRecipeStats(
|
||||
env: Bindings,
|
||||
recipeKeys: Set<string>,
|
||||
ok: boolean,
|
||||
at: number,
|
||||
ctx?: ExecutionContext,
|
||||
): void {
|
||||
if (recipeKeys.size === 0) return;
|
||||
const base = (env.KBDB_BASE_URL ?? 'https://kbdb.finally.click').replace(/\/$/, '');
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (env.KBDB_INTERNAL_TOKEN) headers['Authorization'] = `Bearer ${env.KBDB_INTERNAL_TOKEN}`;
|
||||
|
||||
const promise = Promise.all(
|
||||
[...recipeKeys].map(key =>
|
||||
fetch(`${base}/recipe-stats/record`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ canonical_id: key, ok, at }),
|
||||
}).catch(() => undefined),
|
||||
),
|
||||
).then(() => undefined);
|
||||
|
||||
if (ctx?.waitUntil) ctx.waitUntil(promise);
|
||||
else void promise;
|
||||
}
|
||||
|
||||
type WebhookRecord = {
|
||||
graph: Record<string, unknown>;
|
||||
description: string;
|
||||
@@ -58,6 +93,9 @@ export async function executeWebhookGraph(
|
||||
agent_user_agent: userAgent,
|
||||
}, ctx);
|
||||
|
||||
// kbdb-base §7.1:整體成功 → 用到的 recipe 各記成功一次。
|
||||
recordRecipeStats(env, executor.usedRecipeKeys, true, Date.now(), ctx);
|
||||
|
||||
return { success: true, data: result.data, duration_ms };
|
||||
} catch (err) {
|
||||
const duration_ms = Date.now() - start;
|
||||
@@ -73,6 +111,12 @@ export async function executeWebhookGraph(
|
||||
agent_user_agent: userAgent,
|
||||
}, ctx);
|
||||
|
||||
// kbdb-base §7.1:真錯(非 paused)→ 用到的 recipe 各記失敗一次。
|
||||
// paused 是「執行中暫停等 callback」非失敗,不記(resume 後成功才會在那條路徑記成功)。
|
||||
if (!isPaused) {
|
||||
recordRecipeStats(env, executor.usedRecipeKeys, false, Date.now(), ctx);
|
||||
}
|
||||
|
||||
if (err instanceof ExecutionError) {
|
||||
const traceFormatted = err.trace.map(s => ({
|
||||
node: s.nodeId,
|
||||
|
||||
Reference in New Issue
Block a user