feat(self-hosted): acr init --self-hosted installer + recipe push 把關 + commit 部署 wasm

讓任何 CC 用自己的 CF 帳號一鍵 self-host arcrun(戰法轉 self-hosted 開源)。

Task 1 — acr init --self-hosted installer(用戶只給 CF Account ID + token,其餘自動):
- cli/src/lib/cf-api.ts: CfAccountClient(驗 token / 建 KV 冪等 / 建 R2 / 查 workers.dev subdomain)
- cli/src/lib/deploy.ts: 從 GitHub codeload tarball 拉部署物 → 注入用戶 KV id → wrangler deploy
  (tier1 component-builds 先、tier2 cypher-executor/registry 後;部分失敗誠實回報不假綠)
- cli/src/lib/api-recipe-seeds.ts: 10 個現役 API recipe 種子(KBDB 採 Supabase 模式)
- cli/src/commands/init.ts: initSelfHosted() 改寫成 installer 流程
- cli/src/commands/update.ts: acr update(拉新 ref 重部署)
- cypher-executor/scripts/seed-api-recipes.ts: prod 補灌腳本

Task 2 — recipe 入庫把關(封鎖自製零件後,CC 唯一能擴充的是 recipe):
- cli/src/commands/recipe.ts: 新增 probeRecipeEndpoint 打通檢查(提醒級不硬擋,
  含模板誠實說明待 run 才知,401/403 標多半缺 credential 非 bug)
- 資料外流提醒沿用既有 obtainExposureConsent(非 TTY 拒絕)

部署物產製:commit 預編譯 wasm 進 repo(推翻 rule 05「wasm 不 commit」):
- .gitignore: 放行 .component-builds/**/component.wasm(registry 中間產物仍排除)
- 只 commit 19 個正當零件 wasm;claude_api / km_writer / kbdb_upsert_block 排除
  (非薄殼、是把工作流硬塞進零件,違反 DECISIONS §1,待降級)
- rule 05 同步記錄此慣例變更 + 膨脹 trade-off

SDD: sdk-and-website/self-hosted-init.md(installer 定案)、
     component-gatekeeping/recipe-push-gatekeeping.md(recipe 把關)
README 重寫成單一 self-hosted 路徑。CLI typecheck exit 0。

未完(待 richblack):push 此 commit 到 GitHub 後 codeload 才拿得到 wasm;
用第二 CF 帳號端對端驗收 acr init --self-hosted。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 18:44:41 +08:00
parent 51d40ee515
commit fb2d0b0c2d
35 changed files with 1448 additions and 224 deletions
@@ -0,0 +1,78 @@
/**
* seed-api-recipes.ts
*
* 將現役 API recipe 種子上傳至目標 cypher-executorprod 或 self-host)。
* 種子資料的單一來源在 CLI 端(cli/src/lib/api-recipe-seeds.ts),此腳本 import 它,
* 避免兩份種子定義漂移。
*
* 執行:
* npx tsx scripts/seed-api-recipes.ts
*
* 環境變數:
* ARCRUN_API_URL - 目標 cypher-executor,預設 https://cypher.arcrun.dev
* ARCRUN_API_KEY - X-Arcrun-API-KeyPOST /recipes 需要)
*
* 注意:API recipe 帶 endpoint(資料去向)→ POST /recipes 會要 exposure_consent
* data-exfil-warning)。seed 是平台預建、非用戶 push,腳本帶種子層級的 consent。
*
* 對應 SDD.agents/specs/arcrun/sdk-and-website/self-hosted-init.md §5
*/
import { API_RECIPE_SEEDS } from '../../cli/src/lib/api-recipe-seeds.js';
const BASE_URL = process.env.ARCRUN_API_URL ?? 'https://cypher.arcrun.dev';
const API_KEY = process.env.ARCRUN_API_KEY ?? '';
async function main() {
console.log(`\n Seeding ${API_RECIPE_SEEDS.length} API recipes → ${BASE_URL}\n`);
let ok = 0;
let fail = 0;
for (const recipe of API_RECIPE_SEEDS) {
process.stdout.write(` ${recipe.canonical_id.padEnd(24)} `);
try {
const res = await fetch(`${BASE_URL}/recipes`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(API_KEY ? { 'X-Arcrun-API-Key': API_KEY } : {}),
},
body: JSON.stringify({
canonical_id: recipe.canonical_id,
display_name: recipe.display_name,
description: recipe.description,
endpoint: recipe.endpoint,
method: recipe.method,
auth_service: recipe.auth_service,
// 種子層級的暴露同意:平台預建 recipe,非用戶互動 push。
// 格式須符合 cypher-executor ExposureConsentconfirmed_by_human + understood + confirmed_at)。
// 誠實標明來源是 seed,軌跡可審(mindset §7:機制價值是歸責+可審,非防偽)。
exposure_consent: {
confirmed_by_human: true,
understood: `platform seed recipe (api-recipe-seeds.ts): ${recipe.canonical_id}${recipe.endpoint}`,
confirmed_at: new Date().toISOString(),
},
}),
});
if (res.ok) {
console.log('✓');
ok++;
} else {
const err = await res.text().catch(() => '');
console.log(`✗ HTTP ${res.status}: ${err.slice(0, 120)}`);
fail++;
}
} catch (e) {
console.log(`${e instanceof Error ? e.message : String(e)}`);
fail++;
}
}
console.log(`\n 完成:${ok} 成功,${fail} 失敗\n`);
if (fail > 0) process.exit(1);
}
main();