Files
Arcrun/cypher-executor/src/routes/init-seed.ts
T
Leo 47c6aaea03 feat(t152): workers_ai_chat 種子(auth: binding,免金鑰)+ 修 /init/seed 吃掉 3.12 欄位+ 修 D1 LIKE 長查詢 500
SDD: workflow-discovery 3.12/3.13(不是新規格;3.12 已 confirmed 並實作完成)

## 1) workers_ai_chat 種子(新)
Cloudflare Workers AI 走 env.AI binding ⇒ 用戶不必填任何 API 金鑰就能問答。
放種子表而非產品安裝器:「裝好後預設有哪些 recipe」是平台能力(rule 07 薄殼原則)。
換模型/換供應商=改這一筆 recipe,workflow 不動。

選型實測(1.4.4 實例,真實長度 RAG prompt,每個模型連跑 2 次):
  llama-4-scout-17b        2373/2173 ms   答案最完整、引用正確 ← 選它
  llama-3.3-70b-fp8-fast   3261/2147 ms   可用但波動較大
  mistral-small-3.1-24b    3560/3631 ms
  qwen2.5-coder-32b        3572/3353 ms
  gpt-oss-120b             1971/2295 ms   回應形狀不同,response 取不到文字
  gemma-3-12b-it            5018 帳號無權限
對照舊路徑 Gemini gemma-4-31b-it:同型提問 16.87 s,且吐整段英文思考草稿。

## 2) 修 /init/seed 靜默吃掉 3.12 欄位
3.12 給 RecipeDefinition 加了 body_template/response_map/auth/binding_name,
但 /init/seed 是**列舉欄位重建** recipe record ⇒ 不在名單上的欄位被丟掉。
最惡劣的地方是「哪裡都不會紅」:recipe 查得到、endpoint 對,只有跑起來像沒設定過。
與 08-02 syncManifest 吃掉 manifest.daemon 欄同型(教訓:東西還在不在也要進機械閘)。
加 tests/init-seed-recipe-fields.test.ts:拿掉修復會紅、補回會綠(已實測會擋)。

## 3) 修 D1 LIKE pattern 50 bytes 上限造成的 500
/entries/search?q=… 只要 q 超過 48 bytes 就回 HTTP 500,沒有錯誤訊息。
逐 byte 二分:48→200/49→500;中文 16 字→200/17 字→500。
判別實驗:q 固定 48 bytes、其他 filter 全塞滿讓 SQL 變很長 → 仍 200
⇒ 爆的是 LIKE 的 pattern('%'+q+'%' = 50),不是 statement 長度。
中文問句超過 16 字是常態,而 rag_chat 用整句問題當 q ⇒ 聊天對正常問句等於不能用。
(=InkStoneCo status.md 待辦第 1 條「KBDB keyword 長查詢會炸」的根因。)
修法:q ≤ 48 bytes 走原路(行為逐字不變),超過才拆詞/切 UTF-8 邊界片段。
kbdb 全套 83 測全綠(含新增 8 項)。

## 4) 順手
- 移除被 commit 進 repo 的 node_modules 壞 symlink(指向 leo Mac 的絕對路徑,
  害任何 fresh clone 裝不起來、切分支還會把裝好的蓋掉——本次撞了兩次)。
- pending-changes.md 加 P2 提案(fan-out 並行執行)+等裁決,未動引擎。

驗證:cypher-executor 新增測試 17/17 綠;tsc 與基線逐字相同;
全套測試失敗集合與基線**逐字相同**(基線 14 個失敗,本分支 t173 既有,非本次引入)。
2026-08-03 02:56:53 +00:00

111 lines
4.9 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';
import { ensurePortalTemplates } from './portal';
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,
// ③ payload/回應/binding 三層(3.12):不列進來的欄位會被**靜默吃掉**——
// 種子帶了 body_template/response_map/auth 卻沒進 KV,症狀是 recipe 存在但跑起來
// 「像沒設定過」,且哪裡都不會紅(08-02 manifest.daemon 欄被列舉式重建吃掉的同型)。
body_template: seed.body_template,
response_map: seed.response_map,
auth: seed.auth,
binding_name: seed.binding_name,
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)}`);
}
}
// portal-auth P2#24/#25):portal_user / portal_library template 也是「裝好後預設就緒」
// 的種子(KBDB 萬用表 template,零新表),冪等 ensure(已存在跳過)。seed 是 API 行為(rule 07)。
const portalTemplates = await ensurePortalTemplates(c.env);
const allOk = apiFail === 0 && authFail === 0 && portalTemplates.errors.length === 0;
return c.json(
{
success: allOk,
api_recipes: { seeded: apiOk, failed: apiFail, errors: apiErrors },
auth_recipes: { seeded: authOk, failed: authFail, errors: authErrors },
portal_templates: portalTemplates,
message: allOk
? `seed 完成:${apiOk} 個 API recipe + ${authOk} 個 auth recipe + portal templates(新建 ${portalTemplates.created.length}/已存在 ${portalTemplates.existing.length}`
: `seed 部分失敗(誠實回報,未假綠):API ${apiOk}✓/${apiFail}✗,auth ${authOk}✓/${authFail}✗,portal templates 錯誤 ${portalTemplates.errors.length}`,
},
allOk ? 200 : 207,
);
});