chore(cypher-executor): 清除 KBDB-specific TS 邏輯與 WASM 白名單違規項

- 刪除 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 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 19:39:01 +08:00
parent 3de4cff014
commit 8c1dedaa2f
8 changed files with 29 additions and 122 deletions
-4
View File
@@ -1,4 +0,0 @@
# Local AI tooling artifacts
.swarm/
ruvector.db
+1 -1
View File
@@ -558,7 +558,7 @@ function propagateCtx(
* 支援嵌套 path{{item.content}} → ctx.item.content
* 支援 array index{{paragraphs.0.entity}} → ctx.paragraphs[0].entity
* 非 string 值(object/array)遞迴展開內部 stringundefined / null / number / bool 保留原值
* 2026-05-13 加遞迴:原本只跑 top-levelset 零件 values 嵌套 / kbdb_create_block content 內含 {{x.y}} 用不了。
* 2026-05-13 加遞迴:原本只跑 top-levelset 零件 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 為字串。
+10 -18
View File
@@ -3,17 +3,16 @@
*
* 解析優先序:
*
* 0. trigger_workflow 內建 orchestration 零件(in-process call,繞 CF self-fetch 死鎖)
* 1. 內建零件(BUILTIN_COMPONENTS)— 純 JS,最快
* 2. 外部 URLhttps://...)— 直接 fetchn8n/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 recipegmail/telegram/gsheets 等,寫死的 fallback — Phase 3 將刪除
* 8. WASM HTTP runnerauth primitive / API 零件 → 獨立 Worker URL
* Phase 3 刪掉 7 之後,6 個 API 零件也會落到這裡;目前優先保留 7 以免 Worker 未部署造成 404。
* 9. 找不到 → 報錯
* 7. WASM HTTP runnerauth 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<string> = new Set([
// API 零件(對應 registry/components/ 下的 TinyGo WASM
// 通用 HTTP 零件
'http_request',
// 下一階段待降級為 recipehttp_request + 固定設定)
'gmail',
'telegram',
'line_notify',
'google_sheets',
'cron',
// Auth primitivesPhase 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<string> = 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_IDShttp_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)) {
-81
View File
@@ -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<string> {
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<void> {
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<void> {
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);
}
}
-11
View File
@@ -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}`);
+18 -4
View File
@@ -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 ?? ''),
);
-1
View File
@@ -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 subdomainworkers.dev 帳號 subdomain
// 必填:cypher-executor 用此組出 component worker URL(避開同 zone 自循環死鎖,見 P0 #9)
// self-hosted fork 必須改 wrangler.toml [vars] 為自己的帳號 subdomain
-2
View File
@@ -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 subdomainworkers.dev 帳號 subdomain
# cypher-executor fetch component worker 一律走 arcrun-{name}.{WORKER_SUBDOMAIN}.workers.dev