feat(credential-store-migration): T4 wasi-shim secret_get host function

新增 secret_get(ref) host function:讀 CF Workers per-script Secrets 的值,
host 端實作 = env[ref] 動態字串索引(T1.5 spike ② 已證可行,零網路呼叫)。

- ArcrunHostEnv 加 index signature 支援任意 secret_ref 動態取值
- WasiHostFunctions 加 secret_get,u6u WASI imports 比照 kv_get 同款
  pointer/memory-write 機制 wiring
- 安全邊界:只允許 CRED_ 前綴(拒絕讀 ENCRYPTION_KEY/CF_SECRETS_API_TOKEN
  等非 credential 機密),比照既有 routedKvGet 前綴檢查精神

驗證:tsc --noEmit exit 0;vitest 18/18 通過(新增 5 案例)。
撞牆記錄:JSPI Suspending 物件不可在單元測試直接呼叫(既有架構環境限制,
kv_get 同樣受影響),已移除不可行的 2 個測試並記錄於測試檔註解。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018D6QoC5waFkcjc2N7csJBB
This commit is contained in:
Claude
2026-07-03 22:56:42 +00:00
parent f9dad311ff
commit c3c0a8b17d
3 changed files with 133 additions and 3 deletions
@@ -179,7 +179,24 @@ CLI 薄殼(rule 07):`acr creds list`(讀 D1 顯示)、`acr creds repla
type='table' AND name='credentials'` → 回 `{"name":"credentials"}`schema dump 確認欄位與
`idx_cred_apikey` index 完全對齊 §2.2;重跑同一份 migration 第二次 → `success:true`(冪等驗證)。
- [ ] T3 `acr init/update` 確保 secrets 寫入設定就緒:cypher worker 持有「能打 Workers Scripts secrets API 的 CF API token」+ account id(§2.3 寫入路徑;per-script secrets **無 store 資源需 ensure**,原「ensure store + 注入 store_id」整步取消)+ 於非關鍵 worker 跑一次 `acr update` 全流程驗證 secrets 存活(補 spike ④ 的誠實缺口)。〔依 T1.5 spike 2026-07-03 改寫,證據 Arcrun#2
- [ ] T4 wasi-shim 新增 `secret_get(ref)` host function(守 rule 02 邊界;host 端實作=`env[ref]` 動態索引,spike ② 已證可行,零網路呼叫)。〔依 T1.5 spike 2026-07-03 改寫,證據 Arcrun#2
- [x] T4 wasi-shim 新增 `secret_get(ref)` host function(守 rule 02 邊界;host 端實作=`env[ref]` 動態索引,spike ② 已證可行,零網路呼叫)。〔依 T1.5 spike 2026-07-03 改寫,證據 Arcrun#22026-07-03 完成:
`cypher-executor/src/lib/wasi-shim.ts``ArcrunHostEnv` 加 index signature`[secretRef: string]:
unknown`)讓 host function 能對任意 secret_ref 動態取值;`WasiHostFunctions`
`secret_get?: (ref: string) => Promise<string | null>``u6u` WASI imports 加 `secret_get`
wiring(與 `kv_get` 同款 pointer/memory-write 機制:0 成功/1 錯誤/2 找不到);host 端實作
`secretGet(env, ref)``env[ref]` 動態索引,**只允許 `CRED_` 前綴**(比照 `routedKvGet`
前綴檢查精神,拒絕讀 `ENCRYPTION_KEY`/`CF_SECRETS_API_TOKEN` 等非 credential 機密,我方判斷的
安全邊界,SDD 未明文但符合 §2.3「CRED_ 前綴隔離命名空間」精神);`createArcrunHostFunctions`
掛上 `secret_get`。驗證:`tsc --noEmit` exit 0(無輸出);`vitest run tests/wasi-shim.test.ts`
18/18 通過(新增 `createArcrunHostFunctions — secret_get` 4 案例對 fake env 物件測 CRED_ 前綴放行
/不存在回 null/非 CRED_ 前綴拒絕/型別不符回 null;`u6u.secret_get` wiring 1 案例測未注入時
同步 fallback)。**誠實撞牆記錄**:原規劃另外 2 個測 pointer/memory-write 機制的案例
(直接呼叫 `.imports.u6u.secret_get(...)`)失敗,查證後發現 vitest-pool-workers 環境的
WebAssembly 支援 JSPI`hostWrap()` 把所有 async host function(含既有 `kv_get`)包成
`WebAssembly.Suspending` 物件,此物件設計上只能當 WASM import 綁定用、不能在 JS 端直接
`fn(...)` 呼叫——用 probe 測試證實 `kv_get` 的 import 同樣不可直接呼叫,確認是既有架構的
環境限制非 secret_get 缺陷,已移除該 2 案例並在測試檔內註記;pointer 機制的真實驗證改由
T5 wrangler 部署端到端涵蓋。
- [ ] T5 寫入路徑:`POST/PUT /credentials` 改寫 Workers SecretsAPI `PUT`+ D1 ref(§2.4)。
- [ ] T6 讀取/注入路徑:auth-dispatcher 改 D1 ref → `env[ref]` 取值(§2.5+ 更新 last_used。
- [ ] T7 雙讀 fallback(§4.1)。