fix(cypher): credential 全部從新家取到時不再打 auth_static_key worker(Arcrun#124)

病:cypher 看到 {{credential.X}} 就無條件 fetch
arcrun-auth-static-key.{WORKER_SUBDOMAIN}.workers.dev。那顆 worker
**不在網頁安裝器的首裝清單裡**(只裝 cypher-executor / kbdb / http_request / code
四顆),也沒有「用到才長」的機制 ⇒ fetch 撞 CF 邊緣 404 `error code: 1042`
⇒ 凡是用到 {{credential.X}} 的節點全部 500。

實測兩台都一字不差地同死(唯讀證據):
  leo21c  GET /q/bfezv28v/rag_chat  → 500 Node kw_search failed: ... 1042
  youlin  GET /q/yuga3bse/rag_chat  → 500 Node kw_search failed: ... 1042
  geek6688(手工維護、那顆在)      → 過得了這關
leo 的 8 支 RAG 工作流每一支都用 {{credential.}}(rag_chat 6 處、
rag_ingest_card 7 處)⇒ 問答與寫入同時死,不只 ingest。

修法:resolveCredentialRefs 在 resolveSecretsFromNewHome 之後,若 nameList
每一個都已取到明文,就直接 replaceCredentialRefs 回傳,不打那顆 worker。

為什麼是零行為變化:回填本來就在本檔做(replaceCredentialRefs);那顆 worker
在這條路徑上唯一多做的事是幫「沒取到」的 name fallback 舊 KV(T7 雙讀)。
全取到時它收到 resolved_secrets 就是原樣回傳。只要有任一 name 沒取到,
仍照舊全量交給 WASM 走 T7——不縮減既有能力,只在等價時省掉那一跳。

證據那把金鑰早就在手上:leo21c 的 credential 目錄 last_used_at=1786715805,
與最後一次失敗執行的時間戳同一秒(touchLastUsed 只在真的從新家取到值時才寫)。

🔴 這個 1042 與 d2048e2/95a1462 那兩輪不是同一個病:那兩輪是 same-zone fetch
被擋(解=global_fetch_strictly_public flag,service binding 的解法已被 revert
不要拿回來);本案是目標 worker 根本不存在。同一個錯誤碼、兩種原因。

邊界:rule 02 §2.2 不變(不解密/不展開模板/不組 JWT);rule 3.1 不變(不加
service binding)。明文來源仍是 secret_get host function,與改動前同一條路。

驗證:tsc 對本檔零錯誤;auth-dispatcher+credentials 測試 5 紅 8 綠,
與改動前基準線逐項相同(零回歸;那 5 紅是 7ba7855 註明的「刻意留紅燈」)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
uncle6me-web
2026-08-14 23:25:26 +08:00
parent 9609924818
commit 340b461f58
@@ -245,6 +245,35 @@ export async function resolveCredentialRefs(
// 沒取到的 name 缺席 → WASM 的 resolve_credentials 分支 fallback 舊 KVT7)。
const resolvedSecrets = await resolveSecretsFromNewHome(env, apiKey, nameList);
// ── 全部取到 → 不必打 auth_static_key workerArcrun#1242026-08-14)────────────
//
// 這條路徑上,那顆 worker 唯一比這裡多做的事,是「幫**沒取到**的 name 回頭翻舊 KV」
// T7 雙讀 fallback)。當 nameList 每一個都已經從新家取到明文時,它收到
// `resolved_secrets` 後就是原樣回傳——回填本來就由下面的 `replaceCredentialRefs`
// 在本檔做。**所以那趟往返在「全取到」時不產生任何結果差異。**
//
// 為什麼這不是效能微調,是修 bug`wasmWorkerUrl` 推導出的
// `arcrun-auth-static-key.{WORKER_SUBDOMAIN}.workers.dev` **在網頁安裝器裝出來的實例上不存在**
// (首裝只裝 cypher-executor / kbdb / http_request / code 四顆,auth primitive 不在其中,
// 也沒有「用到才長」的機制)。於是 fetch 撞 CF 邊緣的 404 `error code: 1042`
// ⇒ 下面的 `!res.ok` throw ⇒ **凡是工作流用到 {{credential.X}} 的節點全部 500**。
// 實測:leo21c 與全新安裝的 youlin 兩台,問答與寫入路徑一字不差地同死。
//
// 🔴 給下一個人:**這個 1042 跟 d2048e295a1462 那兩輪的 1042 不是同一個病。**
// 那兩輪是「same-zone fetch 被 CF 擋」(解法=`global_fetch_strictly_public` flag
// 且 service binding 的解法**已被 revert、不要再拿回來**)。本案是「**目標 worker 根本不存在**」
// ——同一個錯誤碼、兩種原因。照舊帳去動 flag 或 binding 對本案完全無效(flag 早就在了)。
//
// 邊界(rule 02 §2.2)不變:本檔仍然不解密、不展開模板、不組 JWT。
// 明文的來源是 `secret_get`host function,實作 = env[ref]),與改動前完全同一條路;
// 這裡只是不再為了「拿一份自己已經拿到的東西」去 fetch 一顆可能不存在的 worker。
//
// 刻意保留 WASM 路徑:只要有任何一個 name 沒從新家取到(舊租戶、遷移過渡期的 KV 存量),
// 就照舊全量交給 WASM 走 T7 雙讀——**不縮減既有能力,只在等價時省掉那一跳**。
if (nameList.every((n) => Object.prototype.hasOwnProperty.call(resolvedSecrets, n))) {
return replaceCredentialRefs(data, resolvedSecrets) as Record<string, unknown>;
}
const url = wasmWorkerUrl('auth_static_key', env.WORKER_SUBDOMAIN);
const res = await fetch(url, {
method: 'POST',