From 8c1dedaa2f638790747d355df565b2625125ba56 Mon Sep 17 00:00:00 2001 From: richblack Date: Tue, 26 May 2026 19:39:01 +0800 Subject: [PATCH] =?UTF-8?q?chore(cypher-executor):=20=E6=B8=85=E9=99=A4=20?= =?UTF-8?q?KBDB-specific=20TS=20=E9=82=8F=E8=BC=AF=E8=88=87=20WASM=20?= =?UTF-8?q?=E7=99=BD=E5=90=8D=E5=96=AE=E9=81=95=E8=A6=8F=E9=A0=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 刪除 src/lib/kbdb-partner.ts(整檔) - routes/auth.ts:移除 kbdb-partner import + 3 處 ensureKbdbPartner/revokeKbdbPartner 呼叫 - wrangler.toml:刪除 KBDB_BASE_URL 與 KBDB_INTERNAL_TOKEN 註解 - lib/component-loader.ts:WASM_HTTP_RUNNER_IDS 移除 claude_api + 6 個 kbdb_*; doc comment / wasmWorkerUrl 範例 / 第 7 步註解全部清掉 Phase 3 與 kbdb 字樣 - types.ts:Bindings 移除 KBDB_BASE_URL 宣告 - graph-executor.ts:註解範例改為非 kbdb 等效描述 同捎(分開議題,一起進): - .gitignore 刪除 - webhooks-named.ts:resumable-workflow ?async=1 分支(waitUntil + 202) tsc --noEmit 通過。 Co-Authored-By: Claude Opus 4.7 --- cypher-executor/.gitignore | 4 - cypher-executor/src/graph-executor.ts | 2 +- cypher-executor/src/lib/component-loader.ts | 28 +++---- cypher-executor/src/lib/kbdb-partner.ts | 81 -------------------- cypher-executor/src/routes/auth.ts | 11 --- cypher-executor/src/routes/webhooks-named.ts | 22 +++++- cypher-executor/src/types.ts | 1 - cypher-executor/wrangler.toml | 2 - 8 files changed, 29 insertions(+), 122 deletions(-) delete mode 100644 cypher-executor/.gitignore delete mode 100644 cypher-executor/src/lib/kbdb-partner.ts diff --git a/cypher-executor/.gitignore b/cypher-executor/.gitignore deleted file mode 100644 index 2b4d267..0000000 --- a/cypher-executor/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ - -# Local AI tooling artifacts -.swarm/ -ruvector.db diff --git a/cypher-executor/src/graph-executor.ts b/cypher-executor/src/graph-executor.ts index 44c9ed0..c7d85e3 100644 --- a/cypher-executor/src/graph-executor.ts +++ b/cypher-executor/src/graph-executor.ts @@ -558,7 +558,7 @@ function propagateCtx( * 支援嵌套 path:{{item.content}} → ctx.item.content * 支援 array index:{{paragraphs.0.entity}} → ctx.paragraphs[0].entity * 非 string 值(object/array)遞迴展開內部 string;undefined / null / number / bool 保留原值 - * 2026-05-13 加遞迴:原本只跑 top-level,set 零件 values 嵌套 / kbdb_create_block content 內含 {{x.y}} 用不了。 + * 2026-05-13 加遞迴:原本只跑 top-level,set 零件 values 嵌套 / 任何零件 body 內含 {{x.y}} 用不了。 * 2026-05-14 加 single-ref pass-through:若整個 string 是 `{{x}}` 且 x 是 array / object, * 回 raw value 不 stringify(讓 filter `items: "{{list.blocks}}"` 能拿到真陣列)。 * 多 ref 或混合文字仍 stringify 為字串。 diff --git a/cypher-executor/src/lib/component-loader.ts b/cypher-executor/src/lib/component-loader.ts index feb838f..9838265 100644 --- a/cypher-executor/src/lib/component-loader.ts +++ b/cypher-executor/src/lib/component-loader.ts @@ -3,17 +3,16 @@ * * 解析優先序: * + * 0. trigger_workflow 內建 orchestration 零件(in-process call,繞 CF self-fetch 死鎖) * 1. 內建零件(BUILTIN_COMPONENTS)— 純 JS,最快 * 2. 外部 URL(https://...)— 直接 fetch,n8n/MCP/任何 HTTP 服務 - * 3. cmp_xxxxxxxx hash → 查 registry KV idx → canonical_id → 邏輯 Worker - * 4. rec_xxxxxxxx hash → 查 RECIPES KV idx → canonical_id → KV recipe 執行 + * 3. cmp_xxxxxxxx hash → 查 WEBHOOKS KV idx → canonical_id → 邏輯 Worker + * 4. rec_xxxxxxxx hash → 查 RECIPES KV idx → recipe 執行 * 5. 邏輯零件 canonical_id → Service Binding(同帳號不走公網) * 5.5. Auth recipe(平台預建)→ Auth Recipe Runner * 6. KV recipe canonical_id → 從 RECIPES KV 讀取 recipe → fetch 外部 API - * 7. 內建 API recipe(gmail/telegram/gsheets 等,寫死的 fallback — Phase 3 將刪除) - * 8. WASM HTTP runner(auth primitive / API 零件 → 獨立 Worker URL) - * Phase 3 刪掉 7 之後,6 個 API 零件也會落到這裡;目前優先保留 7 以免 Worker 未部署造成 404。 - * 9. 找不到 → 報錯 + * 7. WASM HTTP runner(auth primitive / API 零件 → 獨立 Worker URL) + * 8. 找不到 → 報錯 */ import { BUILTIN_COMPONENTS } from './constants'; @@ -35,25 +34,19 @@ import type { Bindings, ComponentRunner, ServiceBinding } from '../types'; // 應改為從 component-registry KV 動態查(registry 已有 backfill index,知道所有 canonical_id) // SDD 待開:cypher-executor-dynamic-component-discovery const WASM_HTTP_RUNNER_IDS: ReadonlySet = new Set([ - // API 零件(對應 registry/components/ 下的 TinyGo WASM) + // 通用 HTTP 零件 'http_request', + // 下一階段待降級為 recipe(http_request + 固定設定) 'gmail', 'telegram', 'line_notify', 'google_sheets', 'cron', - // Auth primitives(Phase 1-4 將逐步部署對應 Worker) + // Auth primitives 'auth_static_key', 'auth_service_account', 'auth_oauth2', 'auth_mtls', - // Mira 零件(2026-05-07 加,吃狗糧推 7-B 時撞到白名單擋) - 'claude_api', - 'kbdb_ingest', - 'kbdb_get', - 'kbdb_create_block', - 'kbdb_patch_block', - 'kbdb_upsert_block', ]); /** @@ -68,7 +61,7 @@ const WASM_HTTP_RUNNER_IDS: ReadonlySet = new Set([ export function wasmWorkerUrl(canonicalId: string, subdomain: string): string { const kebab = canonicalId.replace(/_/g, '-'); // 平台慣例:component worker 名稱 = `arcrun-{kebab}`(見 rule 03 / rule 05), - // 例如 canonical_id=kbdb_get → worker 名 arcrun-kbdb-get → URL arcrun-kbdb-get.{subdomain}.workers.dev + // 例如 canonical_id=http_request → worker 名 arcrun-http-request → URL arcrun-http-request.{subdomain}.workers.dev return `https://arcrun-${kebab}.${subdomain}.workers.dev`; } @@ -141,8 +134,7 @@ export function createComponentLoader(env: Bindings) { if (kvRecipe) return makeRecipeRunner(kvRecipe); // 7. WASM HTTP runner:auth primitive / API 零件 → 獨立 Worker URL - // Phase 3 後 6 個 API 零件(http_request / gmail / telegram / line_notify / - // google_sheets / cron)與 4 個 auth primitive 都從這裡走。 + // 白名單見 WASM_HTTP_RUNNER_IDS(http_request、5 個待降級 API 零件、4 個 auth primitive)。 // 對應 Worker 部署於 arcrun-{canonical-id-kebab}.{WORKER_SUBDOMAIN}.workers.dev // (見 P0 #9 / rule 03)。 if (WASM_HTTP_RUNNER_IDS.has(componentId)) { diff --git a/cypher-executor/src/lib/kbdb-partner.ts b/cypher-executor/src/lib/kbdb-partner.ts deleted file mode 100644 index 2b2edb0..0000000 --- a/cypher-executor/src/lib/kbdb-partner.ts +++ /dev/null @@ -1,81 +0,0 @@ -// KBDB Partner 同步工具 -// Arcrun 用戶登入/rotate/revoke 時,同步更新 KBDB partner 記錄 -// 讓 ak_xxx Key 可以直接存取 KBDB(不需要第二把 Key) - -type KbdbEnv = { - KBDB_INTERNAL_TOKEN?: string; - KBDB_BASE_URL?: string; -}; - -function kbdbBase(env: KbdbEnv): string { - return (env.KBDB_BASE_URL ?? 'https://kbdb.finally.click').replace(/\/$/, ''); -} - -async function sha256Hex(input: string): Promise { - const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input)); - return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join(''); -} - -/** - * 在 KBDB 建立或更新 Arcrun 用戶的 partner 記錄。 - * 失敗時靜默 log,不影響 Arcrun 登入流程。 - */ -export async function ensureKbdbPartner(env: KbdbEnv, email: string, apiKey: string): Promise { - const token = env.KBDB_INTERNAL_TOKEN; - if (!token) { - console.warn('[kbdb-partner] KBDB_INTERNAL_TOKEN not set, skipping'); - return; - } - - try { - const apiKeyHash = await sha256Hex(apiKey); - const base = kbdbBase(env); - - const res = await fetch(`${base}/admin/partners/by-key-hash`, { - method: 'PUT', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - name: `arcrun:${email}`, - org_namespace: `arcrun:${email}`, - api_key_hash: apiKeyHash, - }), - }); - - if (!res.ok) { - const body = await res.text().catch(() => ''); - console.error(`[kbdb-partner] ensureKbdbPartner failed: ${res.status} ${body}`); - } - } catch (err) { - console.error('[kbdb-partner] ensureKbdbPartner error:', err); - } -} - -/** - * 撤銷 KBDB 中對應的 partner 記錄(使用舊的 api_key_hash 找到並刪除)。 - * 失敗時靜默 log。 - */ -export async function revokeKbdbPartner(env: KbdbEnv, oldApiKey: string): Promise { - const token = env.KBDB_INTERNAL_TOKEN; - if (!token) return; - - try { - const oldHash = await sha256Hex(oldApiKey); - const partnerId = `partner-arcrun-${oldHash.slice(0, 16)}`; - const base = kbdbBase(env); - - const res = await fetch(`${base}/admin/partners/${partnerId}`, { - method: 'DELETE', - headers: { 'Authorization': `Bearer ${token}` }, - }); - - if (!res.ok && res.status !== 404) { - const body = await res.text().catch(() => ''); - console.error(`[kbdb-partner] revokeKbdbPartner failed: ${res.status} ${body}`); - } - } catch (err) { - console.error('[kbdb-partner] revokeKbdbPartner error:', err); - } -} diff --git a/cypher-executor/src/routes/auth.ts b/cypher-executor/src/routes/auth.ts index 6809325..a48e2a2 100644 --- a/cypher-executor/src/routes/auth.ts +++ b/cypher-executor/src/routes/auth.ts @@ -9,7 +9,6 @@ import { Hono } from 'hono'; import type { Bindings } from '../types'; -import { ensureKbdbPartner, revokeKbdbPartner } from '../lib/kbdb-partner'; export const authRouter = new Hono<{ Bindings: Bindings }>(); @@ -365,9 +364,6 @@ authRouter.get('/auth/callback', async (c) => { await c.env.USERS_KV.put(`apikey:${apiKey}`, userKey); } - // 同步 KBDB partner 記錄(允許 ak_xxx 直接存取 KBDB) - void ensureKbdbPartner(c.env, email, apiKey); - // Create session (TTL 7 days) const sessionId = randomToken(32); const session: SessionRecord = { @@ -447,10 +443,6 @@ authRouter.put('/me/api-key/rotate', async (c) => { await c.env.USERS_KV.delete(`apikey:${oldKey}`); await c.env.USERS_KV.put(`apikey:${newKey}`, userKey); - // 更新 KBDB partner 記錄(舊 Key 撤銷,新 Key 建立) - void revokeKbdbPartner(c.env, oldKey); - void ensureKbdbPartner(c.env, user.email, newKey); - return c.json({ success: true, api_key: newKey, @@ -469,9 +461,6 @@ authRouter.delete('/me/api-key', async (c) => { await c.env.USERS_KV.put(userKey, JSON.stringify(revoked)); await c.env.USERS_KV.delete(`apikey:${user.api_key}`); - // 撤銷 KBDB partner 記錄 - void revokeKbdbPartner(c.env, user.api_key); - // Clear session cookie const sessId = getSessionId(c.req.raw); if (sessId) await c.env.SESSIONS_KV.delete(`sess:${sessId}`); diff --git a/cypher-executor/src/routes/webhooks-named.ts b/cypher-executor/src/routes/webhooks-named.ts index c66776b..1d3e92f 100644 --- a/cypher-executor/src/routes/webhooks-named.ts +++ b/cypher-executor/src/routes/webhooks-named.ts @@ -143,6 +143,23 @@ webhooksNamedRouter.post('/webhooks/named/:name/trigger', async (c) => { // 無 body 時使用空 context } + const graph = record.graph as { id?: string; nodes?: unknown[] }; + const workflowId = graph.id ?? name; + const nodes = Array.isArray(graph.nodes) ? (graph.nodes as GraphNode[]) : []; + const userAgent = c.req.header('User-Agent') ?? undefined; + + // resumable-workflow SDD §5:?async=1 → 背景執行(waitUntil)+ 立回 202,不依賴呼叫端連線。 + // 不帶 ?async=1 維持原同步行為(向後相容)。 + if (c.req.query('async') === '1') { + c.executionCtx.waitUntil( + executeWebhookGraph(c.env, record.graph, triggerContext, name, apiKey, c.executionCtx, userAgent) + .then(result => + writeExecutionVerdict(c.env, workflowId, nodes, result.success ? 'success' : 'failed', result.duration_ms, result.error ?? ''), + ), + ); + return c.json({ accepted: true }, 202); + } + const result = await executeWebhookGraph( c.env, record.graph, @@ -150,12 +167,9 @@ webhooksNamedRouter.post('/webhooks/named/:name/trigger', async (c) => { name, apiKey, c.executionCtx, - c.req.header('User-Agent') ?? undefined, + userAgent, ); - const graph = record.graph as { id?: string; nodes?: unknown[] }; - const workflowId = graph.id ?? name; - const nodes = Array.isArray(graph.nodes) ? (graph.nodes as GraphNode[]) : []; c.executionCtx.waitUntil( writeExecutionVerdict(c.env, workflowId, nodes, result.success ? 'success' : 'failed', result.duration_ms, result.error ?? ''), ); diff --git a/cypher-executor/src/types.ts b/cypher-executor/src/types.ts index 0bb7c8e..bb0d88c 100644 --- a/cypher-executor/src/types.ts +++ b/cypher-executor/src/types.ts @@ -52,7 +52,6 @@ export type Bindings = { SESSION_SIGNING_SECRET?: string; // 用於 HMAC session ID(可選,也可直接用 UUID) // KBDB 整合 KBDB_INTERNAL_TOKEN?: string; - KBDB_BASE_URL?: string; // 預設 https://kbdb.inkstone.app // Component Worker subdomain(workers.dev 帳號 subdomain) // 必填:cypher-executor 用此組出 component worker URL(避開同 zone 自循環死鎖,見 P0 #9) // self-hosted fork 必須改 wrangler.toml [vars] 為自己的帳號 subdomain diff --git a/cypher-executor/wrangler.toml b/cypher-executor/wrangler.toml index 35cb037..04287e5 100644 --- a/cypher-executor/wrangler.toml +++ b/cypher-executor/wrangler.toml @@ -103,8 +103,6 @@ service = "arcrun-ai-transform-run" ENVIRONMENT = "production" # MULTI_TENANT = "true" # ENCRYPTION_KEY 透過 wrangler secret set 設定 -KBDB_BASE_URL = "https://kbdb.finally.click" -# KBDB_INTERNAL_TOKEN 透過 wrangler secret set 設定 # Component worker subdomain(workers.dev 帳號 subdomain) # cypher-executor fetch component worker 一律走 arcrun-{name}.{WORKER_SUBDOMAIN}.workers.dev