From f9dad311ffbab7763a91fa32b3ef66beaebbe98f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Jul 2026 22:52:09 +0000 Subject: [PATCH] feat(credential-store-migration): T2 D1 migration 0002_credentials.sql MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 credentials 目錄表(api_key/name/service/sensitivity/secret_ref/ created_at/last_used_at,PRIMARY KEY(api_key,name) + idx_cred_apikey), D19:只存目錄不存密文,密文本體改住 CF Workers per-script Secrets(T5)。 deploy.ts 重用既有 applyD1Migration,緊接 0001_base.sql 之後套用同一顆 D1, 機制完全一致(同一支 CF D1 query API)。 驗證:cli tsc --noEmit exit 0;leo21c arcrun-kbdb D1 (1099d0f3-d062-4202-984c-5a57b0a8c031)實際套用, sqlite_master 確認 table+index 存在,重跑驗證冪等。 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_018D6QoC5waFkcjc2N7csJBB --- cli/src/lib/deploy.ts | 16 +++++++++++++++ kbdb/migrations/0002_credentials.sql | 20 +++++++++++++++++++ .../credential-store-migration.md | 10 +++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 kbdb/migrations/0002_credentials.sql diff --git a/cli/src/lib/deploy.ts b/cli/src/lib/deploy.ts index 0f176b4..e771878 100644 --- a/cli/src/lib/deploy.ts +++ b/cli/src/lib/deploy.ts @@ -264,6 +264,22 @@ export async function downloadAndDeploy( } else { failures.push(`D1 migration: 部署物缺 kbdb/migrations/0001_base.sql(${migPath})`); } + + // 3.6 credential-primitives-wasm T2(credential-store-migration.md §2.2): + // credentials 目錄表(api_key/name/service/sensitivity/secret_ref/created_at/last_used_at)。 + // 同一顆 D1(與 KBDB base 共用),冪等 IF NOT EXISTS,套用機制與 0001_base.sql 完全相同 + // (同一個 applyD1Migration helper,同一支 CF D1 query API)。D19:這張表不含密文, + // 密文本體住在 Workers per-script Secrets(見 cypher-executor/src/routes/credentials.ts)。 + const credMigPath = join(root, 'kbdb', 'migrations', '0002_credentials.sql'); + if (existsSync(credMigPath)) { + try { + await applyD1Migration(ctx, readFileSync(credMigPath, 'utf8')); + } catch (e) { + failures.push(`D1 migration 0002_credentials (${ctx.d1DatabaseId}): ${e instanceof Error ? e.message : String(e)}`); + } + } else { + failures.push(`D1 migration: 部署物缺 kbdb/migrations/0002_credentials.sql(${credMigPath})`); + } } const cypherExecutorUrl = ctx.workerSubdomain diff --git a/kbdb/migrations/0002_credentials.sql b/kbdb/migrations/0002_credentials.sql new file mode 100644 index 0000000..eaa5e6f --- /dev/null +++ b/kbdb/migrations/0002_credentials.sql @@ -0,0 +1,20 @@ +-- credential-primitives-wasm — credential-store-migration T2(D19:D1 只存目錄,不存密文) +-- SDD: system-dev/docs/3-specs/arcrun/credential-primitives-wasm/credential-store-migration.md §2.2 +-- +-- 密文本體不在這裡:值住在 CF Workers per-script Secrets(掛在 cypher worker 上,管理 API 唯寫)。 +-- 這張表只存「目錄」:租戶(api_key) / 名字 / 服務 / 敏感度 / 指向 Workers Secrets 的 env var 名(secret_ref)。 +-- 冪等(IF NOT EXISTS),與 0001_base.sql 同模式,套用機制走 cli/src/lib/deploy.ts applyD1Migration。 +-- 同一顆 D1(與 KBDB base 共用 arcrun-kbdb),不新建第二顆。 + +CREATE TABLE IF NOT EXISTS credentials ( + api_key TEXT NOT NULL, -- 租戶 + name TEXT NOT NULL, -- credential 名(= auth-recipe required_secrets[].key,如 telegram_bot_token) + service TEXT, -- 對應 service(telegram / notion …),可空 + sensitivity TEXT NOT NULL DEFAULT 'standard', -- 'standard' | 'high'(high = SA JSON/private key) + secret_ref TEXT NOT NULL, -- 指向 Workers Secrets 的 env var 名(如 CRED_TELEGRAM_BOT_TOKEN_XXXXXXXX;不是密文本體) + created_at INTEGER NOT NULL, + last_used_at INTEGER, -- 執行注入時更新(治理面 last_used 顯示用) + PRIMARY KEY (api_key, name) +); + +CREATE INDEX IF NOT EXISTS idx_cred_apikey ON credentials(api_key); diff --git a/system-dev/docs/3-specs/arcrun/credential-primitives-wasm/credential-store-migration.md b/system-dev/docs/3-specs/arcrun/credential-primitives-wasm/credential-store-migration.md index 57877d4..2ef0c01 100644 --- a/system-dev/docs/3-specs/arcrun/credential-primitives-wasm/credential-store-migration.md +++ b/system-dev/docs/3-specs/arcrun/credential-primitives-wasm/credential-store-migration.md @@ -169,7 +169,15 @@ CLI 薄殼(rule 07):`acr creds list`(讀 D1 顯示)、`acr creds repla ## 7. 任務分解(T1.5 全過,T2-T9 解凍施工——2026-07-03,依總管 Arcrun#2 裁決) - [x] T1 spike:leo21c 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 注入。 +- [x] T2 D1 migration `0002_credentials.sql`(§2.2)+ deploy.ts 注入。2026-07-03 完成: + `kbdb/migrations/0002_credentials.sql` 新增(IF NOT EXISTS 冪等,schema 對齊 §2.2); + `cli/src/lib/deploy.ts` §3.6 新增區塊,重用既有 `applyD1Migration` helper,緊接在 0001_base.sql + 套用之後、同一顆 D1(`ctx.d1DatabaseId`)、同一支 CF D1 query API,機制與 0001 完全一致。 + `cli` package `tsc --noEmit` exit 0(無輸出)。**端到端證據**(leo21c `arcrun-kbdb` D1, + uuid `1099d0f3-d062-4202-984c-5a57b0a8c031`):`POST .../d1/database/{id}/query` 送整份 + 0002_credentials.sql → `{"success":true,...}`;`SELECT name FROM sqlite_master WHERE + 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〕 - [ ] T5 寫入路徑:`POST/PUT /credentials` 改寫 Workers Secrets(API `PUT`)+ D1 ref(§2.4)。