1.4.20:今天全部修復一次到位(KV 額度/credential 自癒/檢修孔/靜默回空)

來源 Arcrun@c7a0b31(gitea/main),九筆 commit:
  60688c3 執行紀錄搬離 KV(Evan 事故止血)
  7ba7855 credential 目錄改走 KBDB API
  9344562 embed 自我檢查端點
  83aa1f6 /portal/data/diagnostics 檢修孔聚合端點
  5388f40 設定頁「匯出診斷檔給我們看」按鈕
  ac4fb56 portal 前端文案講人話
  046ceba credential 目錄自癒搬遷(修 youlin 20/20 全敗)
  c7a0b31 補 entries.ts 缺的自癒 hook

出貨前八項複驗(grep 產物,不假設):
  ANALYTICS_KV.put 殘留 0/execution_log 2
  credential 自癒 3/empty_reason 5
  diagnostics 端點 1/檢修孔按鈕 1
  天書詞只在 admin_hint 與 OFF_HINT(用戶欄位 capability_hint 四種全人話)
  manifest release=1.4.20 core=24 顆

真機驗證(youlin 實例):
  第一輪 20 次 17 成功/3 失敗(自癒暖機)
  第二輪 20 次 20 成功/0 失敗
  兩輪共 40 次後 stats: key 663 → 663,增加 0

⚠️ 打包踩到的坑:用 git worktree 打會 0/4 bundled(沒有 .component-builds
的預編 wasm,那是 gitignore 產物)=> 必須用主工作區,且先還原部署注入的 KV id。
This commit is contained in:
2026-08-07 23:03:29 +08:00
parent 4ba73815bf
commit 93c20e938e
3 changed files with 82 additions and 14 deletions
+70 -3
View File
@@ -2452,6 +2452,36 @@ async function semanticSearch(env, q, opts = {}) {
}));
}
// ../../matrix/arcrun/kbdb/src/actions/credential-legacy-migration.ts
async function legacyCredentialsTableExists(db) {
const row = await db.prepare(`SELECT 1 AS x FROM sqlite_master WHERE type = 'table' AND name = 'credentials'`).first();
return row !== null;
}
async function migrateLegacyCredentialsForOwner(db, ownerId) {
if (!ownerId) return 0;
if (!await legacyCredentialsTableExists(db)) return 0;
const before = await db.prepare(`SELECT COUNT(*) AS n FROM entries WHERE entry_type = 'credential' AND owner_id = ?1`).bind(ownerId).first();
await db.prepare(
`INSERT INTO entries (id, entry_type, owner_id, page_name, metadata_json, created_at, updated_at)
SELECT
'e_cred_' || lower(hex(randomblob(8))),
'credential',
c.api_key,
c.name,
json_object('service', c.service, 'sensitivity', c.sensitivity, 'secret_ref', c.secret_ref, 'last_used_at', c.last_used_at),
c.created_at,
unixepoch()
FROM credentials c
WHERE c.api_key = ?1
AND NOT EXISTS (
SELECT 1 FROM entries e
WHERE e.entry_type = 'credential' AND e.owner_id = c.api_key AND e.page_name = c.name
)`
).bind(ownerId).run();
const after = await db.prepare(`SELECT COUNT(*) AS n FROM entries WHERE entry_type = 'credential' AND owner_id = ?1`).bind(ownerId).first();
return (after?.n ?? 0) - (before?.n ?? 0);
}
// ../../matrix/arcrun/kbdb/src/routes/entries.ts
var entryRoutes = new Hono2();
function parseLibraryParam(raw2) {
@@ -2497,9 +2527,15 @@ entryRoutes.get("/library-stats", async (c) => {
return c.json({ success: true, stats });
});
entryRoutes.get("/", async (c) => {
const entryType = c.req.query("entry_type") || void 0;
const ownerId = c.req.query("owner_id") || void 0;
if (entryType === "credential" && ownerId) {
await migrateLegacyCredentialsForOwner(c.env.DB, ownerId).catch(() => {
});
}
const { entries, total } = await listEntries(c.env.DB, {
entry_type: c.req.query("entry_type") || void 0,
owner_id: c.req.query("owner_id") || void 0,
entry_type: entryType,
owner_id: ownerId,
parent_id: c.req.query("parent_id") || void 0,
page_name: c.req.query("page_name") || void 0,
source: c.req.query("source") || void 0,
@@ -2542,7 +2578,8 @@ entryRoutes.get("/search", async (c) => {
count: entries3.length,
mode: "keyword",
requested_mode: "semantic",
capability_hint: "\u8A9E\u7FA9\u67E5\u8A62\u9700\u5148\u958B vectorize\uFF08embed \u6A21\u7D44\uFF09\u3002\u53EB CC\u300C\u5E6B\u6211\u958B\u8A9E\u7FA9\u67E5\u8A62\u300D\u5373\u53EF\uFF08\u8A2D kbdb_embed:true + redeploy\uFF09\u3002\u672C\u6B21\u5DF2\u964D\u7D1A\u95DC\u9375\u5B57\u641C\u5C0B\u3002"
capability_hint: "\u8A9E\u610F\u641C\u5C0B\u9084\u6C92\u958B\u901A\uFF0C\u9019\u6B21\u986F\u793A\u7684\u662F\u95DC\u9375\u5B57\u6BD4\u5C0D\u7D50\u679C\uFF0C\u4E0D\u662F\u7528\u300C\u610F\u601D\u300D\u627E\u7684\u2014\u2014\u4F60\u6253\u7684\u5B57\u8981\u76E1\u91CF\u8CBC\u8FD1\u8CC7\u6599\u88E1\u5BE6\u969B\u51FA\u73FE\u7684\u8A5E\u624D\u5BB9\u6613\u641C\u5230\u3002\u60F3\u555F\u7528\u8A9E\u610F\u641C\u5C0B\uFF0C\u8ACB\u806F\u7D61\u6211\u5011\u5354\u52A9\u958B\u901A\u3002",
admin_hint: "\u8A9E\u7FA9\u67E5\u8A62\u9700\u5148\u958B vectorize\uFF08embed \u6A21\u7D44\uFF09\u3002\u53EB CC\u300C\u5E6B\u6211\u958B\u8A9E\u7FA9\u67E5\u8A62\u300D\u5373\u53EF\uFF08\u8A2D kbdb_embed:true + redeploy\uFF09\u3002\u672C\u6B21\u5DF2\u964D\u7D1A\u95DC\u9375\u5B57\u641C\u5C0B\u3002"
});
}
let entries2 = (await Promise.all(
@@ -2559,6 +2596,36 @@ entryRoutes.get("/search", async (c) => {
entries2 = entries2.filter((e) => e.score >= cut);
}
entries2 = entries2.slice(0, requestedTopK);
if (entries2.length === 0) {
let empty_reason;
let capability_hint;
let admin_hint;
if (hits.length === 0) {
const status = await backfillStatus(c.env, { owner_id });
if (status.embedded === 0) {
empty_reason = "no_index";
capability_hint = "\u9019\u500B\u77E5\u8B58\u5EAB\u76EE\u524D\u9084\u6C92\u6709\u53EF\u4F9B\u8A9E\u610F\u641C\u5C0B\u7684\u8CC7\u6599\uFF0C\u6240\u4EE5\u641C\u4E0D\u5230\u2014\u2014\u4E0D\u662F\u4F60\u6253\u7684\u5B57\u6709\u554F\u984C\u3002\u8ACB\u806F\u7D61\u6211\u5011\u78BA\u8A8D\u7D22\u5F15\u6709\u6C92\u6709\u5EFA\u597D\u3002";
admin_hint = `owner_id=${owner_id ?? "(all)"} \u7BC4\u570D backfillStatus.embedded=0\uFF1A\u5F9E\u672A embed\uFF0C\u6216 backfill \u672A\u8DD1\u904E\u3002`;
} else {
empty_reason = "no_match";
capability_hint = "\u6C92\u6709\u627E\u5230\u7B26\u5408\u7684\u5167\u5BB9\uFF0C\u63DB\u500B\u8AAA\u6CD5\u6216\u66F4\u5177\u9AD4\u7684\u95DC\u9375\u5B57\u518D\u8A66\u8A66\u770B\u3002";
admin_hint = `owner_id=${owner_id ?? "(all)"} \u5DF2\u6709 ${status.embedded} \u7B46\u5D4C\u5165\u8CC7\u6599\uFF0C\u4F46\u672C\u6B21\u67E5\u8A62\u5728 Vectorize \u7AEF\u96F6\u547D\u4E2D\uFF08\u542B embed.ts \u7D55\u5C0D\u9580\u6ABB\u904E\u6FFE\uFF09\u3002`;
}
} else {
empty_reason = "stale_index";
capability_hint = "\u627E\u5230\u7684\u5167\u5BB9\u90FD\u5DF2\u7D93\u88AB\u4E0B\u67B6\u6216\u79FB\u9664\u4E86\uFF0C\u6240\u4EE5\u6C92\u6709\u53EF\u986F\u793A\u7684\u7D50\u679C\u2014\u2014\u63DB\u500B\u95DC\u9375\u5B57\u518D\u8A66\u8A66\u770B\uFF0C\u6216\u806F\u7D61\u6211\u5011\u78BA\u8A8D\u7D22\u5F15\u6709\u6C92\u6709\u904E\u671F\u3002";
admin_hint = `Vectorize \u547D\u4E2D ${hits.length} \u7B46\uFF0C\u4F46 hydrate \u5F8C\u5168\u90E8\u662F\u5DF2\u4E0B\u67B6\u6216\u627E\u4E0D\u5230\u5C0D\u61C9\u8CC7\u6599\uFF08\u5B64\u5152\u5411\u91CF\uFF09\uFF0C\u975E\u5206\u6578\u9580\u6ABB\u9020\u6210\u2014\u2014\u76F8\u5C0D\u9580\u6ABB\u6578\u5B78\u4E0A\u4E0D\u53EF\u80FD\u780D\u5149\u975E\u7A7A\u7D50\u679C\uFF08cut<=top\uFF09\u3002`;
}
return c.json({
success: true,
entries: entries2,
count: entries2.length,
mode: "semantic",
empty_reason,
capability_hint,
admin_hint
});
}
return c.json({ success: true, entries: entries2, count: entries2.length, mode: "semantic" });
}
const entries = await searchEntries(c.env.DB, q, owner_id, entry_type, void 0, library, source, include_deprecated);
+10 -9
View File
@@ -58,7 +58,7 @@
"canonical": null,
"main_module": "worker.mjs",
"main_file": "arcrun-kbdb/worker.mjs",
"js_bytes": 115858,
"js_bytes": 120285,
"modules": [],
"compat_date": "2025-02-19",
"compat_flags": [
@@ -72,15 +72,16 @@
"database_name": "arcrun-kbdb"
}
],
"vectorize": 0,
"ai": false,
"vectorize": 1,
"ai": true,
"vars": {
"MULTI_TENANT": "false",
"ENVIRONMENT": "production"
}
},
"warnings": [],
"sha256": "c0fb948c2b207898ca64b16d6c934ae2601c5a6f3104320558ca69d35e422cb1",
"bytes": 115858
"sha256": "f8f7541fb7632c96a9b81539b804f29a7519fdb5af21fe43813d36a70da2272e",
"bytes": 120285
},
{
"name": "arcrun-http-request",
@@ -658,7 +659,7 @@
"tier": 2,
"main_file": "tier2/ui/index.js",
"main_module": "index.js",
"sha256": "e834295e5d4f50c4fe5f6a3fe66ca3c01ad14b1175a0d6f2f5167cdfa4f66543",
"sha256": "089ee208de75fff645f53c487abb8adc12dc7143cc5fc4972b761bd06d842453",
"compat_date": "2026-07-01",
"compat_flags": [],
"modules": [],
@@ -668,7 +669,7 @@
"ai": false,
"vars": {}
},
"bytes": 493891
"bytes": 495557
}
],
"daemon": {
@@ -684,8 +685,8 @@
"sha256": "1191af74b3c841536f08625d2449fa4add3e829c93d5cebc391da59e6f7e05a6"
}
},
"release": "1.4.18",
"release": "1.4.20",
"built_for": "oauth-installer-lazy-load",
"notes": [],
"fingerprint": "48e31efa35fa2c9c782a81d6ab5f80ecef078feb95791f37ad97bca8e8e1ae31"
"fingerprint": "bf4216391d5689090776e5ec8ce8b39f36221f5ab85e7ffe2d40741b6086ced3"
}
+2 -2
View File
File diff suppressed because one or more lines are too long