docs(sdd): credential 遷移 SDD 依 T1.5 spike 改寫——Secrets Store 產品換成 Workers per-script Secrets

依據 T1.5 spike 四驗證全過(2026-07-03,證據 Gitea Arcrun#2):
① API 動態加 secret 免重部署即讀 ② env[ref] 動態字串索引可行
③ 14 把未觸上限(D21 單用戶:記錄即可)④ 重部署後 secrets 存活。

- §2.3 全段改寫:寫入走 CF API PUT /workers/scripts/{script}/secrets、
  讀取走 env[ref]、原三條候選路徑待決撤銷(T1 spike 實證 Secrets Store
  binding 靜態 + .get(ref) 參數被忽略 + REST 唯寫,路徑不成立)
- §2.5 改寫:取值=env[secret_ref](零網路呼叫),wasi-shim
  secret_get(ref) host 端實作=env[ref]
- T3 改寫:ensure store + 注入 store_id 整步取消,改為確保 secrets
  寫入 token/account id 就緒+非關鍵 worker 跑一次 acr update 驗存活
- T4 改寫:secret_get(ref) 實作=env[ref](spike ② 已證)
- T1 標完成(負結果)、T1.5 標完成、T2-T9 解凍施工
- 其餘矛盾句對齊用詞(§0/§2.1/§2.2/§2.4/§3/§4/§5/§6 Q-a/Q-d、
  回填端點更名 migrate-to-workers-secrets)
- 雙讀過渡、回填、回滾、治理(D1 只存 metadata+secret_ref)設計不動

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
uncle6me-web
2026-07-03 07:22:38 +08:00
parent 5d00e71275
commit 406f0fedf8
@@ -1,8 +1,9 @@
# Credential Store 遷移 SDD — KV → D1(目錄)+ Cloudflare Secrets Store(密文)
# Credential Store 遷移 SDD — KV → D1(目錄)+ CF Workers per-script Secrets(密文)
> 建立:2026-06-29 by arcrun CC|對應 issueArcrun#13(優先序 3)|決策:leo 2026-06-29 拍板(D19
> **修訂 2026-07-03T1.5 spike,證據 Arcrun#2**:密文的家由「CF Secrets Store 產品」改為「**CF Workers per-script Secrets**」(`wrangler secret put` / API `PUT /accounts/{id}/workers/scripts/{script}/secrets` 那套)。原因:T1 spike 實證 Secrets Store 的 Worker binding 部署時靜態宣告、`.get(ref)` 參數被忽略,不支援「runtime 依 D1 ref 動態查任意 secret」;T1.5 spike 四驗證全過(API 動態加免重部署/`env[ref]` 動態索引可行/14 把未觸上限/重部署後存活),方向定案。§2.3、§2.5、T3、T4 已依此改寫;**雙讀過渡、回填、回滾、治理(D1 只存 metadata+secret_ref 不存密文)設計不動**。D212026-07-02 leo 拍板):當作 SaaS 不存在,按單用戶自架設計,多租戶段落標「future SaaS 再議」。
> 範圍宣告:本檔是既有 SDD `credential-primitives-wasm/` 的補充設計(rule 02 §4.3 例外:現有 SDD 目錄內新增單檔)。**不施工,先 SDD,總管審對齊後放行。**
> 取代關係:本檔的儲存決策**取代** `credential-store-redesign.md` §4.5 的「維持 KV」推薦——leo 看完該推薦後**拍板走 D1+Secrets Store**D19),故 Phase B 翻回「搬」。redesign.md 的 Atelegram 一致性,已做)/ C(友善前門)/ Q1(acr parts)不受影響。
> 取代關係:本檔的儲存決策**取代** `credential-store-redesign.md` §4.5 的「維持 KV」推薦——leo 看完該推薦後**拍板走 D1+Secrets Store**D19;密文家其後依 T1.5 spike 修訂為 Workers per-script Secrets,見上方修訂註,D19 原則不變),故 Phase B 翻回「搬」。redesign.md 的 Atelegram 一致性,已做)/ C(友善前門)/ Q1(acr parts)不受影響。
> 詞彙:component=TinyGo WASMrecipe=http_request+固定設定|workflow=多步 YAMLauth-recipe=credential 怎麼注入。
---
@@ -14,8 +15,8 @@
動機(leo 親述):用戶 API key 外泄被盜刷的經濟損失 + 商譽風險(「就算不是我們過失也會怪在我們身上」)。**我們不持有明文、不持有金鑰 = 不背這個鍋。**
```
密文值(token / SA JSON / private key → Cloudflare Secrets StoreCF 託管金鑰arcrun 拿不到明文)
credential 目錄(name/service/metadata → 使用者自己的 D1(不含密文,只含指向 Secrets Store 的 secret_ref
密文值(token / SA JSON / private key → CF Workers per-script SecretsCF 託管,API 唯寫arcrun 拿不到明文)
credential 目錄(name/service/metadata → 使用者自己的 D1(不含密文,只含指向 Workers Secrets 的 secret_refenv var 名
舊自管 ENCRYPTION_KEY + 密文存 KV → 廢掉(回填完成後);KV 降為暫存/熱讀快取,非真相源
界面 acr creds list → 讀 D1(顯示清單 + last_used),看得到但讀不回值
```
@@ -25,7 +26,7 @@ credential 目錄(name/service/metadata → 使用者自己的 D1(不含
- **不能 read 既有值、不能 edit 既有值**(連 owner 都不行)。只能:
- **整筆 replace**(重貼新值覆蓋舊的)
- **delete**
- read 不回值「不是限制、是設計」——值在 Secrets Storearcrun 拿不到。
- read 不回值「不是限制、是設計」——值在 Workers SecretsAPI 唯寫,T1.5 spike 實證讀不回)arcrun 拿不到。
---
@@ -49,7 +50,7 @@ credential 目錄(name/service/metadata → 使用者自己的 D1(不含
### 2.1 三方職責
| 元件 | 存什麼 | 誰拿得到明文 |
|---|---|---|
| **Cloudflare Secrets Store** | 密文值本體(token/SA JSON/private key | **只有 CF runtime 在執行期注入時**arcrun TS/WASM/owner 界面**都拿不回** |
| **CF Workers per-script Secrets**(掛在 cypher worker 上的 secrets | 密文值本體(token/SA JSON/private key | **只有 CF runtime 在執行期以 env var 注入時**。管理 API 唯寫讀不回值(T1.5 spike 實證),arcrun TS/WASM/owner 界面**都拿不回** |
| **使用者的 D1**(與 KBDB 共用那顆 `arcrun-kbdb` | credential **目錄**`api_key / name / service / sensitivity / created_at / last_used_at / secret_ref`,**無密文** | 全員可讀目錄(非機密),**無人從這拿到值** |
| **CREDENTIALS_KV** | 過渡期:舊自管密文;遷移後:降為可選熱讀快取或廢用 | (遷移後不再是真相源) |
@@ -61,7 +62,7 @@ CREATE TABLE IF NOT EXISTS credentials (
name TEXT NOT NULL, -- credential 名(= auth-recipe required_secrets[].key,如 telegram_bot_token
service TEXT, -- 對應 servicetelegram / notion …),可空
sensitivity TEXT NOT NULL DEFAULT 'standard', -- 'standard' | 'high'high = SA JSON/private key
secret_ref TEXT NOT NULL, -- 指向 Secrets Store 的 secret 名(不是密文本體)
secret_ref TEXT NOT NULL, -- 指向 Workers Secrets 的 env var 名(如 CRED_TELEGRAM_BOT_TOKEN不是密文本體)
created_at INTEGER NOT NULL,
last_used_at INTEGER, -- 執行注入時更新(治理面 last_used 顯示用)
PRIMARY KEY (api_key, name)
@@ -70,43 +71,39 @@ CREATE INDEX IF NOT EXISTS idx_cred_apikey ON credentials(api_key);
```
> ⚠️ **「name 是名字標籤不是密文」**leo 防誤解):`name`/`secret_ref` 都是字串標籤,**密文值永不進 D1**。
### 2.3 Secrets Store 整合(密文存 store、D1 存 ref
### 2.3 Workers per-script Secrets 整合(密文存 cypher worker 的 secrets、D1 存 ref
**已核實 CF Secrets Store 形態(wrangler 文件):**
- 管理 CLI`wrangler secrets-store store create/list/delete``wrangler secrets-store secret put/get/list/delete <STORE_ID> <name>`
- Worker bindingwrangler.jsonc):
```jsonc
"secrets_store_secrets": [
{ "binding": "CRED_STORE", "store_id": "<STORE_ID>", "secret_name": "<name>" }
]
```
- 執行期:`await env.CRED_STORE.get()` 回密文值。
> 依據:T1.5 spike 2026-07-03,證據 Arcrun#2curl/wrangler 原始輸出)。本節取代原「CF Secrets Store 產品」設計——T1 spike 實證該產品的 binding 部署時靜態宣告、`.get(ref)` 參數被忽略、REST 唯寫,不支援「runtime 依 D1 ref 動態查任意 secret」,整案曾停等重新設計;總管裁決轉向 Workers per-script SecretsT1.5 四驗證全過後定案。
**⚠️ 設計約束(必須在施工前對齊,本 SDD 標為待決)**:
上述 binding 形態要求 `secret_name` **在 wrangler config 部署時靜態宣告**——這不適合「每個用戶任意數量的動態 credential」(不可能為每個 token 改 toml 重部署)。三條候選路徑,**請 leo/總管拍板選一**:
**已實證的 Workers per-script Secrets 形態(T1.5 spikeArcrun#2):**
- **寫入(動態、免重部署)**:CF API `PUT /accounts/{account_id}/workers/scripts/{script_name}/secrets`body `{"name":"<secret_ref>","text":"<值>","type":"secret_text"}`(等價 CLI`wrangler secret put <NAME>`,不需本地源碼)。**不改 wrangler.toml、不重上傳 code**spike ① 實證寫入後下一次 invocation 立即可讀(連續 5 次呼叫一致,無延遲、無需等 CF roll 版本)。
- **讀取(runtime 動態 by ref**secret 以 env var 形式進 worker`env` 是普通 JS 物件 → **`env[secret_ref]` 用字串動態索引**(spike ② 實證,`?which=SECRET_B` 正確取回對應值)。這正是 Secrets Store binding 做不到的事(T1 spike ④-b`.get(ref)` 參數被忽略)。
- **唯寫**:管理 API 只能 create/update/delete/list 名字,**讀不回值**(與 Secrets Store 同性質)→ D19「不擁有內容物」保持。
- **`secret_ref` 命名規則照舊**`secret_ref`env var 名(如 `CRED_TELEGRAM_BOT_TOKEN`),D1 目錄存 ref 不存值(§2.2)。命名需避開 cypher worker 既有 vars/bindings 名(施工時以 `CRED_` 前綴隔離命名空間)。
- **數量上限(D21 降級:記錄即可,不做阻擋設計)**:spike ③ 實測 14 把全部 HTTP 201、讀取正常,未觸上限;官方書面上限數字未取得(文件頁被 proxy 擋)。self-hosted 單用戶場景不視為阻擋項;多租戶天花板=future SaaS 再議,不為它增加複雜度。
- **重部署存活**:spike ④ 實證 `wrangler deploy` 重上傳 code 後 14 把 secret 全數存活、值不變——secrets 是掛在 script 外部的獨立資源,不走 wrangler.toml,理論上不受 `stripOfficialOnlyBindings` 注入邏輯影響(不重演 `[ai]` binding 被清的坑)。⚠️ 誠實註記:spike 測的是 `wrangler deploy` 非實際 `acr update` CLI(底層同一支 `PUT /workers/scripts/{name}` API,推論成立但非直接實測)——正式導入時於非關鍵 worker 跑一次 `acr update` 全流程驗證(併入 T3)。
1. **Secrets Store Account REST API(動態 by ref,推薦)**worker 執行期用 CF API`accounts/{id}/secrets_store/stores/{store_id}/secrets/{secret_ref}/value`,或官方提供的等價端點)依 `secret_ref` 動態取值。需一把「能讀 Secrets Store 的 CF API token」給 cypher worker本身高敏感走 `wrangler secret put` 注入 worker,非進 git)。**好處**:完全動態、不用為每 cred 改 toml。**代價**worker 持有一把能讀 store 的 token(但仍比「自管全域 ENCRYPTION_KEY」好——CF 託管、可輪替、範圍限 store)。
2. **靜態 binding + 預宣告**:只適合固定少量 credential,動態場景不可行 → 排除。
3. **過渡折衷**:高敏感(SA JSON/private key)走 Secrets Store;低敏感 token 暫留 KV(AES-GCM) → 但這不滿足 D19「廢自管 ENCRYPTION_KEY、owner 也讀不回」→ 僅作為回填未完成前的中繼。
**寫入路徑的 token 需求**cypher worker 需持有一把「能打 Workers Scripts secrets API 的 CF API token」+ account id(寫入/刪除用;本身高敏感`wrangler secret put` 注入 cypher worker,非進 git)。這仍比「自管全域 ENCRYPTION_KEY」好——CF 託管值、token 可輪替、且該 token 讀不回任何 secret 值(API 唯寫)。
> **本 SDD 預設走路徑 1REST by ref**,並標「待 leo 確認 self-hosted leo21c CF 帳號的 Secrets Store API 路徑與 token 範圍」。施工前需一次 spike 驗證 leo21c 帳號可建 store + worker 可 by-ref 取值
**self-hostedleo21c**Secrets Store 是 CF 原生能力,leo21c 自己的 CF 帳號支援。`acr init/update` 需新增「ensure Secrets Store store 存在」步驟(類比現有 `ensureD1Database` / `ensureKvNamespace`),把 `store_id` 注入 worker。
**self-hostedleo21c**per-script secrets 是 Workers 原生能力(就是現行 `ENCRYPTION_KEY` 住的機制),任何 CF 帳號皆支援、**無 store 資源需 ensure**(對比原 Secrets Store 設計省去「acr init/update ensure store + 注入 store_id」整步)。`acr init/update` 只需確保上述寫入 token/account id 設定就緒(見 T3
> 註:這與「Anthropic Routine env-var 型 self-hosted 不支援」是**不同層**(那是 Anthropic 雲端;這是 CF 帳號能力)——別混(leo 提醒)。
### 2.4 寫入流程(client 加密 → Secrets Store
> ⚠️ 鐵律對齊:現行是 **client 端加密 + 自管 ENCRYPTION_KEY**。走 Secrets Store 後,**密文交給 CF 託管**arcrun 不再自管金鑰。client 端是否仍加密一層 = 待決(見 §6 開放問題 Q-b):
> - 選項甲:client 不再加密,明文值經 TLS 送到 cypher → cypher 用「能 store 的 token」寫進 Secrets StoreCF 端加密託管)。**arcrun TS 短暫經手明文**(記憶體中,不落地)。
> - 選項乙:保留 client 加密當「傳輸期保護」,但這需 arcrun 持有解密金鑰才能寫進 store 明文槽 → 又回到自管金鑰,違 D19。
### 2.4 寫入流程(client 加密 → Workers Secrets
> ⚠️ 鐵律對齊:現行是 **client 端加密 + 自管 ENCRYPTION_KEY**。走 Workers Secrets 後,**密文交給 CF 託管**arcrun 不再自管金鑰。client 端是否仍加密一層 = 待決(見 §6 開放問題 Q-b):
> - 選項甲:client 不再加密,明文值經 TLS 送到 cypher → cypher 用「能 secrets API 的 token」`PUT` 進 Workers SecretsCF 端託管)。**arcrun TS 短暫經手明文**(記憶體中,不落地)。
> - 選項乙:保留 client 加密當「傳輸期保護」,但這需 arcrun 持有解密金鑰才能寫進 secrets 明文槽 → 又回到自管金鑰,違 D19。
> → 傾向**選項甲**(符合「不持有金鑰」),但「TS 短暫經手明文」需 leo 接受(mindset §7 誠實標:不是零接觸,是不落地、不持久、不持金鑰)。
### 2.5 讀取/注入流程(執行期 by ref)
> 依據:T1.5 spike 2026-07-03Arcrun#2)——`env[ref]` 動態字串索引實證可行(spike ②),取值不再需要任何對外 REST 呼叫(值已由 CF runtime 以 env var 注入 worker)。
1. cypher 執行 workflow,遇 recipe `auth_service='telegram'`
2. 查 D1 `credentials` where api_key+name=`telegram_bot_token` → 取 `secret_ref` + `sensitivity`
3. 用 `secret_ref` 向 Secrets Store 取密文值(路徑 1REST by ref)。
4. 依 `auth_recipe:telegram` 的 `inject.path/header` 注入(與現行 auth-dispatcher 同型,只是值來源從「KV 密文+WASM 解密」換成「Secrets Store 取值」)。
3.`secret_ref` 在執行期動態取值:**`env[secret_ref]`**Workers Secrets 以 env var 注入,字串索引原生可行;零網路呼叫、零延遲)。
4.`auth_recipe:telegram``inject.path/header` 注入(與現行 auth-dispatcher 同型,只是值來源從「KV 密文+WASM 解密」換成「`env[ref]` 取值」)。
5. 更新 D1 `last_used_at`
> ⚠️ rule 02 §2.2/§2.3 對齊:解密/注入仍不在 cypher TS 實作業務邏輯。Secrets Store 取值是「host 能力」(類比 `kv_get`/`crypto_decrypt`),落在 wasi-shim host function(新增 `secret_get(ref)`)或等價的 host 邊界,**WASM 零件仍是注入邏輯的所在**。施工時須守此界線(見 §5)。
> ⚠️ rule 02 §2.2/§2.3 對齊:解密/注入仍不在 cypher TS 實作業務邏輯。`env[ref]` 取值是「host 能力」(類比 `kv_get`/`crypto_decrypt`),落在 wasi-shim host function(新增 `secret_get(ref)`host 端實作=`env[ref]`spike ② 已證此場景成立),**WASM 零件仍是注入邏輯的所在**。施工時須守此界線(見 §5)。
---
@@ -115,8 +112,8 @@ CREATE INDEX IF NOT EXISTS idx_cred_apikey ON credentials(api_key);
| 端點 | 行為 | 對齊 D19 |
|---|---|---|
| `GET /credentials` | 讀 **D1**,回 `[{name, service, sensitivity, created_at, last_used_at}]`**不含 secret_ref 對外、不含值** | 顯示清單 + last_used |
| `PUT /credentials/:name`(replace) | 整筆覆寫:寫新值進 Secrets Store同 ref 覆蓋或新 ref+ 更新 D1 metadata | 只能 replace |
| `DELETE /credentials/:name` | 刪 D1 row + 刪 Secrets Store secret | 可 delete |
| `PUT /credentials/:name`(replace) | 整筆覆寫:寫新值進 Workers SecretsAPI `PUT` 同 ref 覆蓋或新 ref+ 更新 D1 metadata | 只能 replace |
| `DELETE /credentials/:name` | 刪 D1 row + 刪 Workers Secrets secretAPI `DELETE .../secrets/{ref}` | 可 delete |
| ~~`GET /credentials/:name/value`~~ | **不存在 / 移除任何讀回值的路徑** | 讀不回值 = 設計 |
CLI 薄殼(rule 07):`acr creds list`(讀 D1 顯示)、`acr creds replace <name>`(覆寫)、`acr creds delete <name>`。**移除任何「印出 credential 值」的路徑**。
@@ -127,58 +124,59 @@ CLI 薄殼(rule 07):`acr creds list`(讀 D1 顯示)、`acr creds repla
### 4.1 雙讀過渡(不停機)
執行期取 credential 值的順序:
1. 先查 D1 有無 `secret_ref` → 有 → 走 Secrets Store 取值(新家)。
1. 先查 D1 有無 `secret_ref` → 有 → 走 Workers Secrets 取值(`env[ref]`新家)。
2. 找不到 → fallback 舊路徑:`CREDENTIALS_KV {api_key}:cred:{name}` + WASM `crypto_decrypt`(舊自管)。
→ 新寫入一律走新家;舊資料未回填前仍可讀。
### 4.2 回填(一次性、冪等、可審)
`POST /credentials/migrate-to-secrets-store`(類比現有 `migrate-cron-index` 冪等端點):
- 對每個 `{api_key}:cred:{name}` KV rowWASM 解密取明文 → 寫進 Secrets Store(領 `secret_ref`)→ 在 D1 建 row(含 ref + metadata)。
`POST /credentials/migrate-to-workers-secrets`(類比現有 `migrate-cron-index` 冪等端點;端點名隨 2026-07-03 修訂由 `migrate-to-secrets-store` 更名,設計不變):
- 對每個 `{api_key}:cred:{name}` KV rowWASM 解密取明文 → `PUT` 進 Workers Secrets(領 `secret_ref`env var 名)→ 在 D1 建 row(含 ref + metadata)。
- 冪等:D1 已有該 (api_key,name) row 且 ref 可解析 → 跳過。
- **誠實回報**逐筆 ok/fail(不假綠,mindset §7)。
- **回填完成且驗證通過後**,才執行 §4.4 廢 ENCRYPTION_KEY。
### 4.3 回滾錨點
- 回填**不刪 KV 舊密文**(保留為回滾錨點),只新增 D1+Secrets Store
- 出問題 → 雙讀 fallback 自動回到 KV 路徑;D1 row 可刪、Secrets Store secret 可刪 → 回到純 KV 狀態。
- 回填**不刪 KV 舊密文**(保留為回滾錨點),只新增 D1+Workers Secrets。
- 出問題 → 雙讀 fallback 自動回到 KV 路徑;D1 row 可刪、Workers Secrets secret 可刪 → 回到純 KV 狀態。
- 只有在「全租戶回填驗證綠 + 觀察期無 fallback 命中」後,才進 §4.4。
### 4.4 廢自管 ENCRYPTION_KEY(最後一步)
- 移除 `crypto_decrypt` 對 credential 的依賴路徑(KV 密文路徑停用)。
- `wrangler secret delete ENCRYPTION_KEY`cypher / auth_static_key / auth_service_account)。
- 清理 KV 舊密文(確認 Secrets Store 是唯一真相源後)。
- 清理 KV 舊密文(確認 Workers Secrets 是唯一真相源後)。
- ⚠️ **此步不可逆**,須 leo 明示放行 + 回填觀察期通過。
---
## 5. 與鐵律 / 既有架構對齊(施工硬約束)
- **rule 02 §2.2/§2.3**cypher TS 不實作 credential 解密/注入業務邏輯。Secrets Store 取值走 host function 邊界(新增 `secret_get(ref)` 於 wasi-shim),注入仍在 WASM auth primitive。
- **rule 01 加解密**:自管 AES-GCM 路徑在回填後廢除;新家由 CF Secrets Store 託管金鑰arcrun 不持金鑰)。
- **rule 02 §2.2/§2.3**cypher TS 不實作 credential 解密/注入業務邏輯。Workers Secrets 取值走 host function 邊界(新增 `secret_get(ref)` 於 wasi-shimhost 端實作=`env[ref]`),注入仍在 WASM auth primitive。
- **rule 01 加解密**:自管 AES-GCM 路徑在回填後廢除;新家由 CF Workers Secrets 託管(arcrun 不持金鑰,管理 API 唯寫讀不回值)。
- **不新增 component**mindset §1);不新增 service bindingrule 03 §3.1)。
- **薄殼(rule 07**CLI/MCP 只做「list 顯示 / replace 覆寫 / delete」介面轉換,store 能力在 API。
- **部署繞開 GitHub Actions**D1 migration + Secrets Store ensure 走 `acr init/update`wrangler 直推),不掛 Actions。
- **薄殼(rule 07**CLI/MCP 只做「list 顯示 / replace 覆寫 / delete」介面轉換,secrets 能力在 API。
- **部署繞開 GitHub Actions**D1 migration + secrets 寫入設定走 `acr init/update`wrangler 直推),不掛 Actionssecrets 本體是掛在 script 外部的獨立資源,重部署不受影響(T1.5 spike ④)
- **D1 同源**:用使用者既有 `arcrun-kbdb` D1(不新建第二顆),migration `0002_credentials.sql` 走現有 `deploy.ts` migration 注入路徑(與 `0001_base.sql` 同模式)。
---
## 6. 開放問題(施工前須 leo / 總管拍板)
- **Q-a(核心)**Secrets Store 動態 by-ref 取值路徑(§2.3 路徑 1 REST API)—— leo21c 自己的 CF 帳號是否支援、API 路徑與 token 範圍?需一次 spike 驗證。**這是整個遷移的硬前置**(取值不通則 D19 走不了)
- **Q-a(核心)****已解(T1 + T1.5 spike,證據 Arcrun#2**。原問「Secrets Store 動態 by-ref 取值路徑是否可行」→ T1 spike 負結果(binding 靜態、`.get(ref)` 參數被忽略、REST 唯寫),該產品路徑不成立;總管裁決轉向 **Workers per-script Secrets**T1.5 spike 驗證全過(§2.3),硬前置解除
- **Q-b**:寫入時 client 是否仍加密一層(§2.4 甲/乙)。傾向甲(不持金鑰,TS 短暫經手明文不落地)——需 leo 接受此誠實 trade-off。
- **Q-c**`sensitivity` 分級的判定(誰標 high/standard)——auth-recipe 可加 `sensitivity` 欄宣告該 service 的 credential 等級,或一律 high。
- **Q-d**:與 redesign.md C(友善前門:.env → 一次填)整合——C 的「值來源 .env」在新架構下是「.env 明文 → cypher 寫進 Secrets Store」,與 §2.4 甲一致。確認 C 依附本 SDD(store 是 Secrets Store 不是 D1)。
- **Q-d**:與 redesign.md C(友善前門:.env → 一次填)整合——C 的「值來源 .env」在新架構下是「.env 明文 → cypher `PUT` 進 Workers Secrets」,與 §2.4 甲一致。確認 C 依附本 SDD(值的家是 Workers Secrets 不是 D1)。
---
## 7. 任務分解(待放行後施工;本批僅 SDD
- [ ] T1 spikeleo21c CF 帳號 Secrets Store create store + worker by-ref 取值驗證(解 Q-a)。**硬前置。**
## 7. 任務分解(T1.5 全過,T2-T9 解凍施工——2026-07-03,依總管 Arcrun#2 裁決
- [x] T1 spikeleo21c CF 帳號 Secrets Store create store + worker by-ref 取值驗證(解 Q-a)。**結果:負結果,路徑不成立**(binding 靜態宣告、`.get(ref)` 參數被忽略、REST 唯寫;2026-07-02,證據 Arcrun#2)→ 轉向 T1.5。
- [x] T1.5 spike**Workers per-script Secrets 四驗證全過**(① API 動態加免重部署即讀 ② `env[ref]` 動態索引可行 ③ 14 把未觸上限(D21:記錄即可)④ 重部署後存活;2026-07-03,證據 Arcrun#2)。**方向定案,硬前置解除。**
- [ ] T2 D1 migration `0002_credentials.sql`(§2.2+ deploy.ts 注入。
- [ ] T3 `acr init/update` 新增 ensure Secrets Store store + 注入 store_id(類比 ensureD1Database)。
- [ ] T4 wasi-shim 新增 `secret_get(ref)` host function(守 rule 02 邊界)。
- [ ] T5 寫入路徑:`POST/PUT /credentials` 改寫 Secrets Store + D1 ref(§2.4)。
- [ ] T6 讀取/注入路徑:auth-dispatcher 改 D1 ref → Secrets Store 取值(§2.5+ 更新 last_used。
- [ ] 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
- [ ] 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)。
- [ ] T8 回填端點 `POST /credentials/migrate-to-secrets-store`(§4.2,冪等可審)。
- [ ] T8 回填端點 `POST /credentials/migrate-to-workers-secrets`(§4.2,冪等可審)。
- [ ] T9 治理端點 list/replace/delete + 移除任何 read-value 路徑(§3+ CLI 薄殼。
- [ ] T10 回填驗證 + 觀察期 → 廢 ENCRYPTION_KEY(§4.4leo 明示放行)。
> **每個 cred 操作跨 TS / WASM / host-function / 兩個 store,必端到端實測**(防再假綠,mindset §7)。T1 不通則整案停,先解 Q-a
> **每個 cred 操作跨 TS / WASM / host-function / 兩個 store,必端到端實測**(防再假綠,mindset §7)。原「T1 不通則整案停」已兌現一輪:T1 負結果 → 整案停 → 總管裁決轉向 → T1.5 全過 → **T2-T9 解凍**。備援(若施工再撞死路):codegen binding + 自動重部署(Arcrun#2 總管裁決的方向 1)