arcrun — AI workflow execution engine (clean history)

Self-hosted 開源:WASM 零件 + recipe + cypher-executor,跑在你自己的 Cloudflare。

此為重建的乾淨歷史起點(移除曾誤 commit 的 GCP SA 金鑰,舊歷史保留在
richblack/arcrun 與本地 backup 分支)。含:
- acr init --self-hosted installer(建 KV/R2 + codeload 拉預編譯 wasm + wrangler deploy + seed recipe)
- recipe push 把關(資料外流提醒 + 打通檢查)
- 19 個正當零件預編譯 wasm(claude_api/km_writer/kbdb_upsert_block 排除:違反 DECISIONS §1)
- CLI / cypher-executor / registry / 完整 SDD

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
uncle6me-web
2026-06-03 15:52:38 +08:00
commit 922a57fe34
485 changed files with 89356 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
/**
* workflow.yaml 解析與三元組驗證
*/
import yaml from 'js-yaml';
import { readFileSync } from 'node:fs';
export interface WorkflowYaml {
name: string;
description?: string;
flow: string[];
config?: Record<string, Record<string, unknown>>;
}
export interface ParsedTriplet {
subject: string;
relation: string;
object: string;
}
/** 合法關係詞(拒絕 PIPE)*/
const VALID_RELATIONS = new Set([
'完成後', '失敗時', '對每個', '條件滿足時',
'ON_SUCCESS', 'ON_FAIL', 'FOREACH', 'IF', 'ON_CLICK', 'CALLS_SUBFLOW',
]);
const BANNED_RELATIONS = new Set(['PIPE']);
export function loadWorkflowYaml(filePath: string): WorkflowYaml {
const raw = readFileSync(filePath, 'utf8');
const doc = yaml.load(raw) as WorkflowYaml;
if (!doc.name) throw new Error('workflow.yaml 缺少 name 欄位');
if (!Array.isArray(doc.flow) || doc.flow.length === 0) {
throw new Error('workflow.yaml 的 flow 欄位必須為非空陣列');
}
return doc;
}
export function parseTriplets(flow: string[]): ParsedTriplet[] {
const triplets: ParsedTriplet[] = [];
for (const line of flow) {
const parts = line.split('>>').map(s => s.trim());
if (parts.length !== 3) {
throw new Error(
`三元組格式錯誤:「${line}\n` +
`正確格式:「A >> 關係詞 >> B」`
);
}
const [subject, relation, object] = parts;
triplets.push({ subject, relation, object });
}
return triplets;
}
export function validateRelations(triplets: ParsedTriplet[]): void {
for (const t of triplets) {
if (BANNED_RELATIONS.has(t.relation)) {
throw new Error(
`不允許使用關係詞「${t.relation}」。\n` +
`「PIPE」已棄用,請改用「完成後」或「ON_SUCCESS」。`
);
}
// 容許 FOREACH iterator 命名變體:「對每個 paragraph」/「FOREACH item」
// graph-builder.ts 已支援這個 regexcommit e8fca33 2026-05-07
const foreachMatch = t.relation.match(/^(?:對每個|FOREACH)\s+\w+$/i);
if (foreachMatch) continue;
if (!VALID_RELATIONS.has(t.relation)) {
throw new Error(
`未知關係詞「${t.relation}」。\n` +
`合法關係詞:${[...VALID_RELATIONS].join('、')}\n` +
`FOREACH 支援 iterator 命名:「對每個 X」/「FOREACH X」)`
);
}
}
}
export function getNodeNames(triplets: ParsedTriplet[]): string[] {
const nodes = new Set<string>();
for (const t of triplets) {
nodes.add(t.subject);
nodes.add(t.object);
}
return [...nodes];
}