feat(credentials): T6 讀取/注入接新家 + T7 雙讀 fallback (credential-store-migration 方案 A)
D19「擁有目錄不擁有內容物」遷移的讀取斷點。密文住 cypher per-script secrets、 D1 只存 secret_ref、auth worker 讀不到 cypher 的 secrets → cypher 先取值塞 payload。 WASM 端(T7 fallback 骨架,零行為改變可獨立部署驗): - auth_static_key/main.go + auth_service_account/main.go:Input 加 resolved_secrets, 解密處改「有 resolved 就用、沒有才 fallback 舊 kv_get+crypto_decrypt」。default 等於舊碼。 - tinygo build 兩支通過,copy 到 .component-builds/*/component.wasm。 TS 端(T6 主路徑): - auth-dispatcher.ts 新增 resolveSecretsFromNewHome:查 D1 拿 secret_ref → secret_get(env[ref], T4) 取明文 → 組 map(取不到的 name 缺席,不放空字串) → 更新 last_used_at。tryAuthDispatch + resolveCredentialRefs 都塞 resolved_secrets。 - rule 02 §2.2 對齊:只查 ref/取值/塞字串,不解密不展開模板不組 JWT。 驗證:cypher-executor + cli tsc exit 0;vitest 41/42(新增 auth-dispatcher.test.ts 6 案例全過, 剩 1 pre-existing 無關失敗)。待 leo21c 部署驗 WASM 端到端(不由本任務部署,acr update 硬綁 GitHub codeload=mistakes #23)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+39
-5
@@ -250,7 +250,34 @@ CLI 薄殼(rule 07):`acr creds list`(讀 D1 顯示)、`acr creds repla
|
||||
正確)→ 清理:CF API `DELETE .../secrets/{ref}` + D1 `DELETE FROM credentials`,二次查詢確認
|
||||
皆已清空。部署完成後**立刻 restore `wrangler.toml` 備份**,git 追蹤版本無 leo21c 帳號專屬 id
|
||||
殘留(已 grep 確認)。
|
||||
- [⛔] T6 讀取/注入路徑:auth-dispatcher 改 D1 ref → `env[ref]` 取值(§2.5)+ 更新 last_used。
|
||||
- [x] T6 讀取/注入路徑:auth-dispatcher 改 D1 ref → `env[ref]` 取值(§2.5)+ 更新 last_used。
|
||||
〔2026-07-05 本機(有 TinyGo 0.40.1)接手,方案 A,leo 已拍板〕**接續 2026-07-04 [cloud-worker]
|
||||
揭露的正確落地方式**(cypher 先取值塞 payload,因 auth worker 讀不到 cypher 的 secrets):
|
||||
**WASM 端(T7 fallback 骨架,可獨立部署驗)**:
|
||||
- `registry/components/auth_static_key/main.go`:Input 加 `ResolvedSecrets map[string]string`;
|
||||
`authenticate` 的解密迴圈與 `handleResolveCredentials` 都改為「`input.ResolvedSecrets[key]`
|
||||
有值就用,沒有才 fallback 舊 `kvGet`+`cryptoDecrypt`」。default(此欄 nil)行為完全等於遷移前舊碼。
|
||||
- `registry/components/auth_service_account/main.go`:SA JSON 取得同型(有 resolved 就用,沒有才舊路徑)。
|
||||
- `tinygo build -target=wasi` 兩支皆通過(static_key 1.13MB / service_account 1.18MB,在 2MB 限制內),
|
||||
copy 到 `.component-builds/{name}/component.wasm`。
|
||||
**TS 端(T6 主路徑)**:`cypher-executor/src/actions/auth-dispatcher.ts` 新增
|
||||
`resolveSecretsFromNewHome(env, apiKey, names)`:查 D1 `credentials`(api_key+name IN names)拿
|
||||
`secret_ref` → `createArcrunHostFunctions(env, apiKey).secret_get(ref)`(T4,host 端 = `env[ref]`)
|
||||
取明文 → 組 `{name:value}` map,**取不到值的 name 缺席(不放空字串)** 讓 WASM fallback(T7)→
|
||||
取到值的 name `UPDATE credentials SET last_used_at`。`tryAuthDispatch` 取 recipe 非 optional
|
||||
`required_secrets[].key` 當 names、`resolveCredentialRefs` 用已收集的 names,兩者都把
|
||||
`resolved_secrets` 塞進送 auth WASM 的 POST body。**rule 02 §2.2 對齊**:只查 ref/取值/塞字串,
|
||||
不解密、不展開模板、不組 JWT(grep 確認無 `crypto.subtle`/`interpolate`/`{{secret.`/`BUILTIN_*`)。
|
||||
**驗證**:`cypher-executor` + `cli` `tsc --noEmit` 皆 exit 0;`vitest run` 41/42(新增
|
||||
`tests/auth-dispatcher.test.ts` 6 案例全過:D1 有 ref+新家有值→進 map/無 ref→缺席走 fallback/
|
||||
有 ref 但新家沒值 secret_get 回 null→缺席不放空字串/混合/空 names/取值後更新 last_used_at;
|
||||
剩 1 個 pre-existing 無關失敗 executor.test.ts「不存在」措辭,見 T8 記錄,非本次退步——
|
||||
git baseline 35/36 → 41/42,只增不減)。
|
||||
**待 leo21c 部署驗(本任務不部署,`acr update` 硬綁 GitHub codeload=mistakes #23,交總管/後續)**:
|
||||
WASM 端到端(resolved_secrets 命中新家的真實注入)在 vitest 測不到(JSPI import 限制,同 T4 記錄),
|
||||
`secret_get` 的 pointer/memory-write 也待部署端到端涵蓋。TS 側 `resolveSecretsFromNewHome` 的
|
||||
D1 查詢 + secret_get + last_used 更新已用真實 D1 binding + fake env(模擬 per-script secret 注入)
|
||||
單元覆蓋。
|
||||
〔2026-07-04 [cloud-worker] 卡在框架級工具缺口,非設計問題〕**T6 需要修改
|
||||
`registry/components/auth_static_key/main.go`(與 `auth_service_account/main.go`)**:
|
||||
這兩個 auth primitive 是獨立部署的 TinyGo WASM Worker,密文值現在住在
|
||||
@@ -273,10 +300,17 @@ CLI 薄殼(rule 07):`acr creds list`(讀 D1 顯示)、`acr creds repla
|
||||
決定是否要在雲端環境補裝工具鏈(例如允許 proxy 放行 github.com release 下載,或
|
||||
改用其他 TinyGo 取得管道)。整案不硬繞(不改用「TS 直接組 header」這種違反 rule 02
|
||||
的捷徑),留給有 TinyGo 環境的一方接手 main.go 改動 + 重新編譯部署。
|
||||
- [⛔] T7 雙讀 fallback(§4.1)。〔同上,隨 T6 main.go 改動一併落地——D1 查得到 secret_ref
|
||||
走新路徑,查不到就是 WASM 原有的 `kv_get`+`crypto_decrypt` 分支,本來就會自然發生,
|
||||
不需要額外的「fallback 判斷」程式碼,只要 T6 的 `resolved_secrets` 是「有給才用、沒給
|
||||
就照舊」的設計即可。同樣卡在 T6 的 TinyGo 前置。〕
|
||||
- [x] T7 雙讀 fallback(§4.1)。〔2026-07-05 本機,隨 T6 一併落地。〕如原設計預期:
|
||||
**不需額外的「fallback 判斷」程式碼**——T6 的 `resolved_secrets` 就是「有給才用、沒給
|
||||
就照舊 kv_get+crypto_decrypt」的設計(WASM 端 `input.ResolvedSecrets[key]` 的 `ok` 判斷即分流)。
|
||||
雙讀在**兩層**自然成立:
|
||||
(1) cypher 層——`resolveSecretsFromNewHome` 查 D1 查不到 ref(從未回填)或 `secret_get` 回 null
|
||||
(新家還沒值)→ 該 name 不進 `resolved_secrets` map;
|
||||
(2) WASM 層——收到的 `resolved_secrets` 缺該 key → 走既有 `kvGet`+`cryptoDecrypt` 舊路徑。
|
||||
新寫入(T5)一律新家;舊資料未回填(T8)前仍可讀(舊 KV 不刪,§4.3 回滾錨點不動)。
|
||||
單元測試涵蓋「無 ref→缺席」「有 ref 但新家沒值 secret_get 回 null→缺席」兩條 fallback 分流
|
||||
(見 T6 的 `tests/auth-dispatcher.test.ts`)。default(無 resolved_secrets 欄)行為 = 遷移前舊碼,
|
||||
故 WASM 端此改動可獨立部署、零行為改變地驗「沒弄壞既有」。
|
||||
- [x] T8 回填端點 `POST /credentials/migrate-to-workers-secrets`(§4.2,冪等可審)。
|
||||
2026-07-04 [cloud-worker] 完成:`cypher-executor/src/routes/credentials.ts` 新增
|
||||
端點,重用 `createArcrunHostFunctions(env, apiKey).crypto_decrypt`(wasi-shim.ts
|
||||
|
||||
Reference in New Issue
Block a user