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:
@@ -1,4 +0,0 @@
|
||||
|
||||
# Local AI tooling artifacts
|
||||
.swarm/
|
||||
ruvector.db
|
||||
@@ -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 為字串。
|
||||
|
||||
@@ -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<string> = 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<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_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)) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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 ?? ''),
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user