Files
Arcrun/cypher-executor/src/routes/init-seed.ts
T
uncle6me-web c1a06df68f feat(exposure): 完全移除 acr push 暴露 consent 閘 (Arcrun#13 P1)
leo 2026-06-29 拍板:arcrun 是給 AI 用的系統,push/暴露不再需要人類確認。
- 刪 cypher-executor/src/lib/exposure-consent.ts(server 閘,MCP push 的真正擋點)
- 刪 cli/src/lib/exposure-warning.ts(CLI 互動 + 非 TTY 拒絕)
- recipes.ts / webhooks-named.ts:移除 checkExposureConsent 403 閘,直接放行
- recipe.ts / push.ts:移除 obtainExposureConsent 呼叫,不再 prompt/拒絕
- init-seed / seed-api-recipes:移除種子層級 consent
- exposure_consent 欄位降為向後相容(讀舊 record 不報錯,不再寫入/檢查)
不補審計線索、不做替代防護(leo:先拿掉,出問題再設置)。
tsc 全綠(cypher-executor + cli)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 20:58:32 +08:00

98 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* /init/seed — 一鍵把平台預建的 recipe 種子灌進 RECIPES KVAPI 行為,非介面層職責)
*
* 薄殼原則(rule 07 + 壓測 §4.1/§5.5):
* 「裝好後預設有哪些 recipe」是 API 的能力。seed 由本端點完成,CLI/MCP 等薄殼只呼叫一次。
* 之前 seed 寫在 CLI init.ts(迴圈 POST + deployFullyOk gate),導致 registry 20/21 連坐 →
* seed 永遠被跳過、auth recipe 從不被 seed(壓測 §4.1)。本端點把 seed 下沉到 API,根除連坐。
*
* 行為:
* - 冪等:已存在的 recipe 直接覆寫(重跑安全)。
* - 一次灌「API recipeAPI_RECIPE_SEEDS+ auth recipeAUTH_RECIPE_SEEDS)」兩者。
* - 直接寫 KV:種子是平台預建、非用戶互動 push(暴露 consent 閘已於 Arcrun#13 移除)。
* - 誠實回報:逐筆 ok/fail 計數,不假綠。
*
* 對應 SDD.agents/specs/arcrun/sdk-and-website/self-hosted-init.md §5
*/
import { Hono } from 'hono';
import type { Bindings } from '../types';
import { deriveRecipeHash } from '../lib/hash';
import type { RecipeDefinition, AuthRecipeDefinition } from './recipes';
import { installRecipeRecord, resolveRecipe } from './recipes';
import { API_RECIPE_SEEDS } from '../lib/api-recipe-seeds';
import { AUTH_RECIPE_SEEDS } from '../lib/auth-recipe-seeds';
export const initSeedRouter = new Hono<{ Bindings: Bindings }>();
initSeedRouter.post('/init/seed', async (c) => {
const now = Date.now();
// 暴露 consent 閘已移除(leo 2026-06-29Arcrun#13):種子不再帶 exposure_consent。
let apiOk = 0;
let apiFail = 0;
const apiErrors: string[] = [];
for (const seed of API_RECIPE_SEEDS) {
try {
const canonicalId = seed.canonical_id.trim().toLowerCase();
const hashId = await deriveRecipeHash(canonicalId);
// UUID 模型(§7.5.5):種子 author='system'。冪等:已安裝沿用其 uuid,否則新領。
const existing = await resolveRecipe(canonicalId, c.env.RECIPES);
const recipe: RecipeDefinition = {
uuid: existing?.uuid ?? crypto.randomUUID(),
author: existing?.author ?? 'system',
canonical_id: canonicalId,
hash_id: hashId,
display_name: seed.display_name,
description: seed.description,
endpoint: seed.endpoint,
method: (seed.method ?? 'POST').toUpperCase(),
auth_service: seed.auth_service,
created_at: existing?.created_at ?? now,
updated_at: now,
};
await installRecipeRecord(c.env.RECIPES, recipe);
apiOk++;
} catch (e) {
apiFail++;
apiErrors.push(`${seed.canonical_id}: ${e instanceof Error ? e.message : String(e)}`);
}
}
let authOk = 0;
let authFail = 0;
const authErrors: string[] = [];
for (const seed of AUTH_RECIPE_SEEDS) {
try {
const service = seed.service.trim().toLowerCase();
const existing = await c.env.RECIPES.get(`auth_recipe:${service}`, 'json') as AuthRecipeDefinition | null;
const recipe: AuthRecipeDefinition = {
...seed,
service,
created_at: existing?.created_at ?? now,
updated_at: now,
};
await c.env.RECIPES.put(`auth_recipe:${service}`, JSON.stringify(recipe));
authOk++;
} catch (e) {
authFail++;
authErrors.push(`${seed.service}: ${e instanceof Error ? e.message : String(e)}`);
}
}
const allOk = apiFail === 0 && authFail === 0;
return c.json(
{
success: allOk,
api_recipes: { seeded: apiOk, failed: apiFail, errors: apiErrors },
auth_recipes: { seeded: authOk, failed: authFail, errors: authErrors },
message: allOk
? `seed 完成:${apiOk} 個 API recipe + ${authOk} 個 auth recipe`
: `seed 部分失敗(誠實回報,未假綠):API ${apiOk}✓/${apiFail}✗,auth ${authOk}✓/${authFail}✗`,
},
allOk ? 200 : 207,
);
});