feat: 薄殼原則落地 + seed 下沉 API + MCP 進主庫 + 部署一致性

壓測四橫向問題修正(docs 壓測報告):

① 薄殼原則成鐵律:能力長在 API,CLI/MCP/lib 只暴露
   - seed 下沉成 API 行為:cypher-executor POST /init/seed(一次灌 API+auth recipe),
     種子資料移到 server src/lib/api-recipe-seeds.ts,CLI 改薄殼一次呼叫
   - 解除 deployFullyOk 連坐 + init 補 seed auth recipe + update 補 seed/全 KV
   - registry SUBMISSIONS_KV 補進 REQUIRED_KV_NAMESPACES(修 20/21)

② MCP 統一帳號來源(單一 remote MCP + .env 切 MCP URL)
   - MCP 從 sibling repo 搬進 arcrun/mcp/(remote Worker,route 改 mcp.arcrun.dev)
   - config 加 mcp_url 三層解析 + getMcpUrl + DEFAULT_MCP_URL
   - 新增 acr mcp-setup:依 config 寫專案 .mcp.json(接案切資料夾自動切 MCP)
   - acr --version 改動態讀 package.json(根治漂移)

③ Deploy 一致性
   - tests/release.feature + scripts/check-release.sh
   - local-deploy.sh:CLI npm publish + auto patch bump + CHANGELOG
   - local-deploy.sh bash 3.2 相容修正(mapfile / 空陣列 set -u)
   - builtins/pnpm-lock.yaml

④ README self-hosted 同步現況(移除 R2 殘留、加 flag/env、多帳號)

CLI bump → 1.3.0

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
uncle6me-web
2026-06-06 15:45:35 +08:00
parent 5f381a44a6
commit 3e65e22775
58 changed files with 8608 additions and 74 deletions
+102
View File
@@ -0,0 +1,102 @@
/**
* /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(不走 POST /recipes 的 exposure_consent gate):種子是平台預建、非用戶互動 push,
* 帶 seed 層級的 consent 憑證(誠實標來源,軌跡可審;mindset §7:機制價值是歸責+可審非防偽)。
* - 誠實回報:逐筆 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 { 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();
const seedConsent = {
confirmed_by_human: true as const,
understood: 'platform seed (init/seed): 平台預建 recipe,非用戶互動 push',
confirmed_at: new Date(now).toISOString(),
};
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);
const existing = await c.env.RECIPES.get(`recipe:${canonicalId}`, 'json') as RecipeDefinition | null;
const recipe: RecipeDefinition = {
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,
exposure_consent: existing?.exposure_consent ?? seedConsent,
created_at: existing?.created_at ?? now,
updated_at: now,
};
await Promise.all([
c.env.RECIPES.put(`recipe:${canonicalId}`, JSON.stringify(recipe)),
c.env.RECIPES.put(`idx:${hashId}`, canonicalId),
]);
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,
);
});