步驟6 執行統計回寫:每顆零件跑完回寫 success_rate(task 29,design.md「執行統計設計」)

- registry 新增 POST /analytics/record:ANALYTICS_KV 計數器(stats:{hash_id}:{version})
  為唯一真相源,衍生值(success_rate/avg_duration_ms/call_count)回填 comp: 記錄
  ——查詢讀取端讀哪就寫哪,不開第二真相源。KV 無 CAS,誠實標註非原子。
- cypher-executor execution-evaluator 從 stub 改真實作:執行收尾(/cypher/execute
  成功與 ExecutionError 路徑+webhook 路徑)對 trace 裡每顆 Component 節點
  fire-and-forget 回寫,waitUntil 包、不增加執行同步延遲(仿 recordRecipeStats 慣例)。
  成敗判定=trace error 或 output.success===false(makeHttpRunner 非 2xx 不 throw)。
- registry 位置沿 search-nodes 慣例:REGISTRY_BASE_URL 覆蓋,未設走 wasmWorkerUrl。
- 新增單測 13 個全綠;本地雙 wrangler dev 端到端實測:http_request 跑 5 次
  (3 成功+2 失敗)→ success_rate 1→0.6、call_count 0→5,/cypher/search 同步可見。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
uncle6me-web
2026-07-31 13:45:05 +08:00
parent 7e87a3336b
commit ea1c0571c1
10 changed files with 490 additions and 52 deletions
+37
View File
@@ -0,0 +1,37 @@
// POST /analytics/record — 零件執行結果回寫端點
// SDD: system-dev/docs/3-specs/arcrun-core-mvp/design.md「執行統計設計」
// 呼叫方:cypher-executor 執行收尾(fire-and-forget,統計失敗不影響執行)
import { Hono } from 'hono';
import type { Bindings } from '../types';
import { recordAnalytics } from '../actions/recordAnalytics';
const app = new Hono<{ Bindings: Bindings }>();
app.post('/record', async c => {
const body = await c.req.json().catch(() => null) as {
canonical_id?: unknown;
version?: unknown;
success?: unknown;
duration_ms?: unknown;
} | null;
if (!body || typeof body.canonical_id !== 'string' || body.canonical_id.trim() === '') {
return c.json({ ok: false, error: 'canonical_id 必填' }, 400);
}
if (typeof body.success !== 'boolean') {
return c.json({ ok: false, error: 'success 必須為 boolean' }, 400);
}
const result = await recordAnalytics({
canonical_id: body.canonical_id.trim(),
version: typeof body.version === 'string' && body.version !== '' ? body.version : undefined,
success: body.success,
duration_ms: typeof body.duration_ms === 'number' ? body.duration_ms : 0,
}, c.env);
if (!result.ok) return c.json(result, 404);
return c.json(result);
});
export default app;