feat(credential-store-migration): T5 寫入路徑改走 Workers Secrets + D1 ref

POST/PUT /credentials 改寫:密文值 PUT 進 CF Workers per-script Secrets
(唯寫,D19 不持有內容物),D1 credentials 表只存目錄(不含密文)。
secret_ref = CRED_<NAME>_<sha256(api_key)[:8]>,避免跨租戶撞名。
DELETE/GET /credentials 本次不動(仍走舊 KV,T9 範圍),已誠實標注。

新增 CREDENTIALS_DB D1 binding(與 KBDB base 共用同一顆 arcrun-kbdb,
沿用既有 database_id 注入機制)+ CF_SECRETS_API_TOKEN/CF_ACCOUNT_ID
env vars(CF_ACCOUNT_ID 由 deploy.ts 自動注入,同 WORKER_SUBDOMAIN 模式)。

§2.4 選項甲落地:client 不再加密,明文值經 TLS 傳輸,cypher 短暫經手
明文不落地不持金鑰——與舊版 01-tech-stack.md 傳輸格式不同,是本 SDD
對舊格式的刻意取代(§6 Q-b 仍需 leo 明確接受)。

驗證:tsc --noEmit exit 0;vitest 26/27(1 pre-existing 無關失敗)。
端到端:真實部署 leo21c arcrun-cypher-executor,POST/PUT /credentials
成功寫入 Workers Secrets + D1 row(CF API + D1 query 雙重確認),
清理測試資料後 restore wrangler.toml(無帳號專屬 id 殘留 git)。

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 23:04:59 +00:00
parent c3c0a8b17d
commit c24edbbdb6
6 changed files with 243 additions and 33 deletions
@@ -197,7 +197,35 @@ CLI 薄殼(rule 07):`acr creds list`(讀 D1 顯示)、`acr creds repla
`fn(...)` 呼叫——用 probe 測試證實 `kv_get` 的 import 同樣不可直接呼叫,確認是既有架構的
環境限制非 secret_get 缺陷,已移除該 2 案例並在測試檔內註記;pointer 機制的真實驗證改由
T5 wrangler 部署端到端涵蓋。
- [ ] T5 寫入路徑:`POST/PUT /credentials` 改寫 Workers SecretsAPI `PUT`+ D1 ref(§2.4)。
- [x] T5 寫入路徑:`POST/PUT /credentials` 改寫 Workers SecretsAPI `PUT`+ D1 ref(§2.4)。2026-07-03 完成:
`cypher-executor/src/routes/credentials.ts` 整檔改寫 POST(建立)+ 新增 PUT `/credentials/:name`
(覆寫):兩者共用 `writeCredential()` → 1) `putWorkerSecret()` PUT 進 CF Workers Scripts secrets
管理 API(唯寫)2) `upsertCredentialRow()` D1 upsert(不含密文,覆寫時保留原 `created_at`)。
`secret_ref` 命名=`CRED_<NAME 大寫>_<sha256(api_key) 前 8 碼大寫>``cypher-executor/src/lib/hash.ts`
匯出既有 `sha256Prefix`,避免跨租戶同名 credential 撞名覆蓋彼此的 secret;純函式衍生非解密/簽章邏輯,
不違 rule 02 §2.2)。DELETE/GET `/credentials` 本次刻意不動(仍讀寫舊 KV,T9 治理端點範圍),
已在檔頭與 §3 comment 誠實標注此缺口。`types.ts` Bindings 加 `CREDENTIALS_DB: D1Database`
(與 KBDB base 共用同一顆 `arcrun-kbdb` D1+ `CF_SECRETS_API_TOKEN?` / `CF_ACCOUNT_ID?`
`wrangler.toml``[[d1_databases]] binding="CREDENTIALS_DB"`(沿用 kbdb 同款 database_id
注入機制,deploy.ts 不用改)+ `[vars] CF_ACCOUNT_ID=""` 佔位(T3 由 deploy.ts 自動注入非機密
帳號 id,同 WORKER_SUBDOMAIN 模式)。**§2.4 選項甲落地**client 不再 AES-GCM 加密,明文值經
TLS 傳輸,cypher 短暫在記憶體經手明文不落地不持金鑰)——與舊版 `01-tech-stack.md` 記載的
`{name,encrypted,iv}` 傳輸格式不同,此為本 SDD 對舊格式的刻意取代,§6 Q-b 仍列需 leo 明確
接受的誠實 trade-off(先落地,若不接受選項甲需回頭改,見任務完成報告「撞牆」段)。
驗證:`tsc --noEmit` exit 0cli + cypher-executor 皆是);`vitest run` 26/27 通過(1 個
pre-existing 無關失敗,見 T4 記錄,non-regression 已用 git stash 驗證)。**端到端證據**(真實
leo21c `arcrun-cypher-executor` worker,非測試替身——寫入端點本身就是要驗的東西,無替代):
比照 mistakes #23 手法本地 patch `wrangler.toml`KV/D1 id、CF_ACCOUNT_ID、WORKER_SUBDOMAIN、
KBDB_BASE_URL 換 leo21c 真實值,strip `[ai]`/`[[routes]]`)→ `wrangler deploy --dry-run` 核對
binding 表(`env.CREDENTIALS_DB (arcrun-kbdb) D1 Database` 等全對)→ 真部署 → `wrangler secret put
CF_SECRETS_API_TOKEN`(沿用 `CLOUDFLARE_API_TOKEN`)→ `curl POST /credentials`
`{"success":true,"secret_ref":"CRED_TELEGRAM_BOT_TOKEN_9BB9FB82",...}` → CF API `GET
.../workers/scripts/arcrun-cypher-executor/secrets` 確認該 secret 真的存在 → D1 query 確認
對應 rowapi_key/name/service/sensitivity/secret_ref/created_at 全對)→ `curl PUT
/credentials/telegram_bot_token` 覆寫值,確認 `secret_ref` 不變、`created_at` 不變(覆寫語意
正確)→ 清理: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。
- [ ] T7 雙讀 fallback(§4.1)。
- [ ] T8 回填端點 `POST /credentials/migrate-to-workers-secrets`(§4.2,冪等可審)。