頂層 D22 決策(leo 2026-07-03 拍板):推什麼由開發環境歸屬決定, Gitea private=除機敏值/build 產物/.github 外全 push。 解 T1.5 卡點:雲端工人 clone 拿得到 credential-store-migration.md,可就地改寫 SDD。 機敏掃描兩輪通過(新增 189 檔約 2.1MB,node_modules/dist/wasm 照舊排除)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
23 KiB
Implementation Tasks: Credential Primitives TS → WASM
嚴格規範(richblack 2026-04-19):cypher-executor TS 不得實作任何 credential / auth / template / JWT / 解密邏輯。全部走 TinyGo WASM + host functions(方案 B)。
封測狀態:推遲(richblack 2026-04-19 決定)。先完成 Phase 1-3 清除違規 TS,再啟動封測。
Phase 0:核心合併(u6u-core → arcrun)
- 0.1 把
u6u-core/builtins/搬到arcrun/builtins/ - 0.2 確認
arcrun/registry/components/21 個零件的 contract.yaml 完整(21/21) - 0.3 刪除
arcrun/credentials/整個目錄(重複,credential route 已在 cypher-executor) - 0.4 更新
arcrun/cypher-executor/wrangler.toml:確認 CREDENTIALS_KV binding 存在 - 0.5 刪除
matrix/u6u-core/整個目錄(2026-04-19 完成,只剩 credentials/ 已被 cypher-executor 取代) - 0.6 在
cypher-executor/src/lib/wasi-shim.ts新增 host functions:u6u.kv_get(keyPtr, keyLen, outPtr, outLenPtr) uint32— 依 key 前綴路由到CREDENTIALS_KV/RECIPES,越權檢查 api_keyu6u.crypto_decrypt(encPtr, encLen, ivPtr, ivLen, outPtr, outLenPtr) uint32— 用env.ENCRYPTION_KEY+crypto.subtleAES-GCM 解密;key 不暴露給 WASMu6u.crypto_sign_rs256(dataPtr, dataLen, pkcs8Ptr, pkcs8Len, outPtr, outLenPtr) uint32—crypto.subtle.sign('RSASSA-PKCS1-v1_5' + SHA-256)- 2026-04-19 完成:wasi-shim.ts 新增
createArcrunHostFunctions(env, apiKey)factory,集中 AES-GCM 解密 + RSA sign + KV 前綴路由越權檢查。WASI imports 的 u6u namespace wiring 本來就已接好(只是當時沒有實作 factory)。typecheck 通過。
- 0.7 在
cypher-executor/src/lib/component-loader.ts新增 WASM runner 路徑:- 所有 WASM 零件(含 auth primitive、API 零件、未來用戶自製)一律走 HTTP URL(
{canonical-id-kebab}.arcrun.dev)到獨立 Worker - R2 動態注入路徑作廢(richblack 2026-04-19 確認:CF workerd 不支援以 R2 物件臨時 instantiate WASM;用戶自製零件同樣走「產生獨立 Worker」流程,不走 R2)
- cypher-executor 本身不做 WASM instantiate,也不直接呼叫
createArcrunHostFunctions;那個 factory 是零件 Worker 側(.component-builds/{name}/src/index.ts)用的,在 Phase 1 建立 auth_static_key Worker 時接上 - 2026-04-19 完成:
component-loader.ts新增WASM_HTTP_RUNNER_IDS(10 個 canonical_id,6 個 API 零件 + 4 個 auth primitive)+wasmWorkerUrl()URL 慣例輔助函數;解析鏈新增為第 8 層(放在BUILTIN_API_RECIPESfallback 之後,避免 Phase 3 尚未完成時 API 零件 Worker 未部署造成 404;Phase 3 刪除BUILTIN_API_RECIPES後,API 零件會自然落到此層)。auth primitive 從此層進入。tsc --noEmit通過。
- 所有 WASM 零件(含 auth primitive、API 零件、未來用戶自製)一律走 HTTP URL(
Phase 1:auth_static_key WASM(優先,涵蓋 80% 服務)
方案 B:WASM 自行讀 KV + 解密,TS 不碰 plaintext。
- 1.1 建立
arcrun/registry/components/auth_static_key/目錄 - 1.2 寫
component.contract.yaml(input:{action, api_key, service, request}→ output:{success, auth_headers, auth_query, auth_body, runtime}) - 1.3 實作
main.go(TinyGo):- 宣告 host imports:
kv_get/crypto_decrypt(static_key 不需要 http_request) - 從 stdin 讀
{action, api_key, service} kv_get("auth_recipe:" + service)→ recipe JSON → 驗證primitive == "static_key"- 對每個 non-optional
recipe.required_secrets:kv_get("{api_key}:cred:{name}")→{encrypted, iv}→crypto_decrypt→ plaintext - 展開
{{secret.X}}/{{runtime.X}}模板於inject.header/query/body;未知 key 展空字串(與 TS parity);其他 namespace 的{{...}}原樣保留 - 輸出 stdout JSON
{success, auth_headers, auth_query, auth_body, runtime}
- 宣告 host imports:
- 1.4
tinygo build -o auth_static_key.wasm -target=wasi main.go— 2026-04-19 編譯通過(1.1MB,在 contract 限制 2MB 內) - [🔄] 1.5 建立
.component-builds/auth_static_key/(用component-worker-template)並部署到auth-static-key.arcrun.dev- 2026-04-20 完成建置部分:
.component-builds/auth_static_key/{wrangler.toml, package.json, tsconfig.json, src/index.ts, component.wasm}全數到位 - 方案 A:
src/index.ts直接 import../../../cypher-executor/src/lib/wasi-shim的createWasiShim+createArcrunHostFunctions(以ArcrunHostEnv結構型別相容);AES 解密邏輯仍只存在於 wasi-shim.ts 一處(rule 02 §2.2) - 綁同組 KV:CREDENTIALS_KV (e7f4320f88d343f187e35e3543dd74c9) / RECIPES (9cf9db905c6241f78503199e58b2ffe0);ENCRYPTION_KEY 走
wrangler secret put wrangler deploy --dry-run通過(1192 KiB, 419 KiB gzip);實際wrangler deploy+secret put ENCRYPTION_KEY留給 richblack 執行
- 2026-04-20 完成建置部分:
- 1.6 建立
auth-dispatcher.ts(取代credential-injector.ts):查 auth recipe → HTTP POST 到對應 auth primitive URL → 合併_auth_headers進 ctx- 2026-04-20 完成:
cypher-executor/src/actions/auth-dispatcher.ts新建,exporttryAuthDispatch(componentId, input, env, apiKey) - 流程:查
resolveAuthRecipe→ primitive 在SUPPORTED_PRIMITIVES(目前只有static_key)→ fetchwasmWorkerUrl('auth_static_key')→ 合併_auth_headers/_auth_query/_auth_body - 自引用防護:
AUTH_PRIMITIVE_IDSset 排除 4 個auth_*componentId wasmWorkerUrl從component-loader.tsexport 出來共用graph-executor.ts改為:先試tryAuthDispatch(新路徑),沒命中 fallback 到舊injectCredentials(Phase 1.9 刪)- 檢查過 auth-dispatcher.ts 無
crypto.subtle/interpolate/{{secret./ hard-code API URL,符合 rule 02 §2.2 tsc --noEmit通過
- 2026-04-20 完成:
- 1.7 端對端測試:openai recipe → 成功注入
Authorization: Bearer <openai_key> - 1.8 端對端測試:twilio recipe(Basic Auth)→ 成功注入
- 1.9 刪除
credential-injector.ts整檔(decryptCredential/decryptSecrets/interpolateTemplate/BUILTIN_CREDENTIALS_MAP全刪)
Phase 2:auth_service_account WASM
- [🔄] 2.1 建立
arcrun/registry/components/auth_service_account/目錄 - [🔄] 2.2 寫
component.contract.yaml - [🔄] 2.3 實作
main.go:- 從 stdin 讀
{api_key, service}+kv_get拿 recipe + 解密 SA JSON - 解析 SA JSON 取
client_email/private_key(PEM) - PEM → PKCS8 bytes(純 Go,base64 decode + 去 header/footer)
- 組 JWT header + payload(base64url),呼叫
crypto_sign_rs256(signingInput, pkcs8)拿 signature - 組完整 JWT →
http_requestPOSTtoken_uri→ 拿access_token - 展開
{{runtime.access_token}}模板
- 從 stdin 讀
- 2.4
tinygo build -o auth_service_account.wasm -target=wasi main.go— 2026-04-20 編譯通過(1.1MB,在 contract 限制 2MB 內) - 2.5 建立
.component-builds/auth_service_account/並部署到auth-service-account.arcrun.dev- 2026-04-20 完成建置部分:
.component-builds/auth_service_account/{wrangler.toml, package.json, tsconfig.json, src/index.ts, component.wasm}全數到位 - 方案 A:
src/index.ts重用createArcrunHostFunctions提供 kv_get/crypto_decrypt/crypto_sign_rs256,額外加http_requesthost function(token exchange 用,非 crypto 不受 §2.2 約束)。http_request 直接回 response body 原文(WASM 端 json.Unmarshal 找 access_token) - 綁同組 KV:CREDENTIALS_KV / RECIPES;ENCRYPTION_KEY 走
wrangler secret put wrangler deploy --dry-run通過(1248 KiB, 440 KiB gzip);實際wrangler deploy+secret put ENCRYPTION_KEY留給 richblack 執行auth-dispatcher.ts的SUPPORTED_PRIMITIVES加入'service_account',workflow 用 google SA recipe 會自動走新 WASM 路徑
- 2026-04-20 完成建置部分:
- 2.6 端對端測試:google_sheets_sa recipe → 成功取得 access_token → 注入 header
- 2.7 刪除
lib/jwt-signer.ts整檔 — 2026-04-20 完成cypher-executor/src/lib/jwt-signer.ts已刪除(RS256 JWT 邏輯移入auth_service_account.wasm)credential-injector.ts原 line 23import { exchangeGoogleJwt }移除credential-injector.ts原 line 140-150 service_account 分支改為 throw(任何 service_account recipe 已被 auth-dispatcher 攔截;這條 TS fallback 若被觸發即表架構錯亂,直接爆錯比沈默解密更安全)cypher-executortsc --noEmit 通過
Phase 3:清理 component-loader 的 TS 實作(全刪)
目標:BUILTIN_API_RECIPES 整段刪除,所有服務走 WASM runner(HTTP URL 路徑)。
- 3.1 確認
http_request.wasm/gmail.wasm/telegram.wasm/line_notify.wasm/google_sheets.wasm都在registry/components/且可執行 — 2026-04-20 驗證 6 個(含 cron)全數存在,main.go + .wasm 齊備 - 3.2 確認上述零件 Worker 都已部署(
{name}.arcrun.dev可用) — 2026-04-20 完成建置部分- 6 個 Worker 建置到位:
.component-builds/{http_request, gmail, telegram, line_notify, google_sheets, cron}/{wrangler.toml, package.json, tsconfig.json, src/index.ts, component.wasm} - 方案 A:5 個需 http_request 的零件(http_request/gmail/telegram/line_notify/google_sheets)
src/index.ts共用模板;cron 是純計算不註冊 host function - 全部透過
createWasiShim複用 cypher-executor/src/lib/wasi-shim.ts(rule 02 §2.2 邊界) - 6 個
wrangler deploy --dry-run全通過(~1.17 MB / ~413 KB gzip 每個);實際wrangler deploy留給 richblack 執行
- 6 個 Worker 建置到位:
- 3.3
component-loader.ts的內建路徑改為查對應 Worker URL → HTTP POST — 2026-04-20 完成- 原本第 7 層是
BUILTIN_API_RECIPESfallback、第 8 層是WASM_HTTP_RUNNER_IDS(HTTP URL);兩層合併為第 7 層WASM_HTTP_RUNNER_IDS直接走makeHttpRunner(wasmWorkerUrl(id)) - 解析鏈新編號 1-8,順序不變(外部 URL → recipe hash → component hash → R2 → Service Binding → auth recipe runner → WASM HTTP runner → 找不到)
- 原本第 7 層是
- 3.4 刪除
BUILTIN_API_RECIPES整個 Record(http_request/gmail/telegram/line_notify/google_sheets/cron的 TS 實作全刪) — 2026-04-20 完成cypher-executor/src/lib/component-loader.ts原 line 253-326BUILTIN_API_RECIPES常數 + fallback lookup 全刪(約 80 行)- 全域搜尋確認:
gmail.googleapis.com/...messages/send/api.telegram.org/bot.*sendMessage/sheets.googleapis.com/v4/spreadsheets/notify-api.line.me/api/notify在 cypher-executor TS 中已不存在(auth-recipe-seeds.ts 的base_url是 recipe 資料欄位,不是 hard-coded API call) cypher-executortsc --noEmit 通過
- 3.5 端對端測試:workflow 用 gmail auth recipe + gmail.wasm Worker → 成功發信
- 3.6 端對端測試:workflow 用 http_request.wasm Worker + auth_static_key 注入 → 成功呼叫任意 API
Phase 4:auth_oauth2 + auth_mtls WASM(封測後)
- 4.1 建立
arcrun/registry/components/auth_oauth2/ - 4.2 實作:
needs_refresh/refresh/authenticate三個 action - 4.3 建立
arcrun/registry/components/auth_mtls/ - 4.4 實作:輸出 TLS cert/key(實際 mTLS handshake 由 Worker runtime 執行,WASM 無法做 socket)
Phase 5:封測啟動門檻 — 核心穩定驗證
全部通過才能啟動封測。
- 5.1 所有 20 個 auth recipe seed 可正常運作(static_key 17 個 + service_account 3 個)
- 5.2
cypher-executor/src/actions/credential-injector.ts不存在 - 5.3
cypher-executor/src/lib/jwt-signer.ts不存在 - 5.4
cypher-executor/src/lib/component-loader.ts無BUILTIN_API_RECIPES/BUILTIN_CREDENTIALS_MAP - 5.5
cypher-executor/src/全域搜尋crypto.subtle.decrypt只出現在wasi-shim.ts的crypto_decrypthost function - 5.6
cypher-executor/src/全域搜尋crypto.subtle.sign只出現在wasi-shim.ts的crypto_sign_rs256host function - 5.7
cypher-executor/src/全域搜尋interpolate回傳 0 筆(template 展開全在 WASM) - 5.8 全域搜尋
{{secret\./{{runtime\.在 TS 檔案中回傳 0 筆
Phase 6:通用 CI/CD deploy workflow
背景(2026-04-20 richblack 決定):現 .github/workflows/deploy.yml 只部署 cypher-executor + registry + 已刪除的 credentials,漏掉 Phase 1-3 產出的 8 個 Worker,且硬編碼每個 job 導致未來新增 Worker 都要改 CI。改為通用掃描式 workflow:任何含 wrangler.toml 的目錄 = 部署單位,改到該目錄下任何檔案 = 觸發重新 deploy。
關鍵決策:
- 零件
.wasm由 CI build(不 commit):registry/components/{name}/main.go改動時才重 build,用 timestamp / content hash 判斷 .component-builds/{name}/component.wasm由 CI 從registry/components/{name}/{name}.wasm複製產生(deploy 前一步)- 統一用 pnpm(
.component-builds/*本來就是;順勢把 cypher-executor 的package-lock.json砍了) - runtime secret(
ENCRYPTION_KEY)不進 CI,由 richblack 一次性wrangler secret put - registry Worker 的
wrangler.toml現階段不改(職責是合約管理,與封測無關;sandboxAcceptance.ts的 rule 02 §2.2 審查留到 Phase 5 用戶自製零件啟動時)
Tasks
- 6.1 改寫
.github/workflows/deploy.yml:動態掃描所有含wrangler.toml的目錄(排除node_modules/+ Pages 專案),用 matrix job fanout 部署;分兩層(tier1=.component-builds/*,tier2=其他),tier1 全綠後才 tier2(避免 service binding target 未存在) - 6.2 加上 TinyGo build 步驟:tier1 matrix 一律 setup-tinygo + 從
registry/components/{name}/main.gorebuild.wasm→ copy 到.component-builds/{name}/component.wasm - 6.3 diff-aware:push 到 main 時比對
github.event.before..github.sha,只 deploy 有 diff 的 Worker(含registry/components/{name}/連動.component-builds/{name}/);workflow_dispatch提供force_all+only選項 - 6.4 統一 pnpm:刪除
cypher-executor/package-lock.json+registry/package-lock.json;workflow 優先pnpm install --frozen-lockfile,若該目錄無pnpm-lock.yaml則 fallback 到--no-frozen-lockfile(混合期容錯) - 6.5 加
max-parallel: 5控制 Workers API rate limit(tier1 和 tier2 各自) - 6.6 驗證:
workflow_dispatch+force_all=true手動跑一次,24 個 Worker 全綠 — 2026-04-20 完成- 最終綠色 run 24668903627(28/28 jobs,含 discover + summary):tier1 24 個零件 Worker + tier2 2 個 orchestration Worker(cypher-executor / registry)全 success
- 過程中修兩輪:先修
setup-node的cache: 'pnpm'對 legacypackage-lock.json目錄失效(改為不用 cache);再修 tier2 三個 package.json(cypher-executor/registry/builtins)遺漏wranglerdevDependency + regen pnpm-lock.yaml - ENCRYPTION_KEY secret 已由 richblack 授權、CC 從 .env pipe 到三個 Worker:
arcrun-auth-static-key、arcrun-auth-service-account、arcrun-cypher-executor(不顯示內容)
- 6.7 文件:在
.claude/rules/加一份05-deploy-convention.md(「新增 Worker = 新目錄 + wrangler.toml,不用改 CI」)
Phase 7:auth primitive 改走 service binding(解 self-hosted CF 1042)
2026-06-06 richblack 拍板。來源:壓測報告階段 11(
test_arcrun/docs/壓測報告.md §11)。
背景(架構演化):
- 當初設想「用戶對每個服務建零件」→ 零件用 service binding 要 redeploy → 用戶建 workflow 要 redeploy(不可接受)→ 故禁 service binding、改 cypher binding(HTTP URL)。
- 後來架構演化:用戶要的是 recipe(資料,存 KV)+ 固定 primitive,不是新零件。
acr recipe push進 KV 不 deploy。用戶用「http_request primitive + 不同 recipe」打各種服務,永不新增 primitive。 - 結論:「用戶建 workflow 要 deploy」這個禁令前提在 recipe 模型下不成立。primitive(http_request + 4 個 auth)是平台固定基礎設施,和 13 個邏輯零件同類,給它 service binding 的 deploy 成本只在平台部署時付一次,不落用戶身上。
根因(壓測階段 11,唯讀證據):
auth-dispatcher.ts:67-68用fetch(wasmWorkerUrl('auth_service_account', WORKER_SUBDOMAIN))打 workers.dev → CF 回 404 + "error code: 1042"(CF 邊緣頁,非 auth worker 回應)。- 1042 精確定義(官方 docs,2026-06-06 查證):「Worker tried to fetch from another Worker on the same zone, only supported when
global_fetch_strictly_publicflag is used」。關鍵是 same zone:- self-hosted 踩:cypher(
arcrun-cypher-executor.{sub}.workers.dev)與 auth worker(arcrun-auth-*.{sub}.workers.dev)同在{sub}.workers.devzone → 同 zone fetch → 1042。 - 官方不踩:官方 cypher 在自訂域
cypher.arcrun.dev,打 auth 的*.uncle6-me.workers.dev是跨 zone,不觸發 same-zone 限制(非「官方有 flag」——官方 wrangler.toml 只有nodejs_compat,無global_fetch_strictly_public)。 - 這是 P0#9「同 zone 522」的另一形態:之前解的是
*.arcrun.dev同 zone,沒解到*.workers.dev同 zone。service binding 是內部 RPC 不經 zone → 徹底免疫(13 邏輯零件正是走 binding 才不踩)。
- self-hosted 踩:cypher(
- 直接從外部打
arcrun-auth-service-account.leo21c.workers.dev→ 200 + 真ya29...token → JWT/解密/token 換取鏈本身全正常,唯一卡點是 cypher 打不到自己帳號的 auth worker。 - service binding 是 CF 內部 RPC,不經公網 → 同時繞開同 zone 522(P0#9)與同帳號 workers.dev 子請求 1042,比 workers.dev fetch 乾淨(13 邏輯零件正是用 binding 才不踩)。
技術前提(已驗證 ✅):
- CF API 查 leo21c(全新自架帳號)的
arcrun-cypher-executorsettings → 實綁 13 個 service binding(SVC_SET→arcrun-set …)。證明deploy.ts的 binding 注入在 self-hosted 生效,auth binding 走同路綁得上。
修法演進:A(service binding)→ 廢 → B(global_fetch_strictly_public flag)
先做了 A(service binding)後評估廢棄,改用 B(flag)——richblack 2026-06-06 拍板。
- A(service binding)為何廢:service binding 靜態,加/改要重 deploy cypher(官方 docs 證實)。richblack 判定不夠乾淨,且有更簡單的 B。
- B(
global_fetch_strictly_publicflag)為何對:官方 docs——此 flag 讓fetch()走公網「前門」,same-zone fetch 也能通。cypher wrangler.toml 加一行即解,用戶無感、不用域名、不用重 deploy、不動 service binding 禁令。 - B 安全(查證官方 docs):唯一副作用是「Worker fetch 自己 hostname 會 self-loop」;cypher 只打外部 API + sibling auth worker(皆非自己 hostname)→ 不 self-loop。
- B 官方/self-host 共用:官方 cypher 本就跨 zone,加 flag 行為不變;self-host 同 zone 被修好。同一份 toml 兩邊通用。
- 評估但廢的他案:arcrun.dev 子域給 self-host cypher——查證後子域同 zone(zone=註冊域名)照踩 1042,且跨帳號 route 落地等於 PaaS 轉向,廢。
Tasks
- 7.1 規範:
02-forbidden §3.1/03/CLAUDE.md鐵律4 /pre-bash-guard 3.1——禁令維持原狀(不解禁 service binding),僅加註「same-zone 1042 用 flag 解」。(先前一度改成「平台 primitive 例外允許 binding」,因改用 B 已全部還原) - 7.2
hook 解禁→ 還原:pre-bash-guard.sh規則 3.1 回原本「禁止新增 service binding」(B 不需解禁) - 7.3
cypher-executor/wrangler.toml:compatibility_flags加global_fetch_strictly_public(+ 註解病因/安全)。移除先前加的 SVC_AUTH_*(grep SVC_AUTH=0) - 7.4
auth-dispatcher.ts/types.ts:還原到 A 之前(移除 binding 優先邏輯與 SVC_AUTH_* 型別,回單純fetch(workers.dev)——flag 讓它同 zone 也通);tsc exit 0 - 7.5
cli/src/lib/deploy.ts:無需改——flag 在 cypher wrangler.toml,stripOfficialOnlyBindings()不碰compatibility_flags,self-hosted 部署自動帶上 - 7.6 驗收(客觀證據):全新自架帳號
POST /webhooks/named/{ns}/{wf}/trigger讓append_row的 auth 注入回 200、真的寫進 Google Sheets(第一個端到端驗 self-hosted auth 鏈的測試);tsc --noEmitexit 0 - 7.7 design.md 同步(§修改 auth-dispatcher 補「binding 優先、fetch fallback」+ 架構演化 + 1042 根因):§修改 的 auth-dispatcher 補「binding 優先、fetch fallback」;記架構演化(recipe 取代零件 → primitive 可 binding)
Phase 8:{{credential.X}} 用戶面注入語法(2026-06-10 richblack 確認 change,design §8)
根因:壓測 401 = workflow 寫
{{credential.X}}但三條 template 展開路徑都不認此 namespace。 修法:auth_static_key 加resolve_credentialsaction(WASM 解密),graph-executor 偵測+回填(不解密)。
- 8.1
registry/components/auth_static_key/main.go:input 加Names []string;main 在service必填檢查之前分流action == "resolve_credentials"→ 走新handleResolveCredentials(不查 recipe、 不要求 service),每個 namekvGet("{api_key}:cred:{name}")+cryptoDecrypt→ 回{success, credentials:{name: plaintext}};缺 name → success:false + error 指明(不假綠) - 8.2
tinygo build -target=wasi -o auth_static_key.wasm main.go編譯通過(1.1MB)+ copy 到.component-builds/auth_static_key/component.wasm - 8.3
cypher-executor/src/graph-executor.ts:node.data interpolate 後、node 執行前,掃描殘留{{credential.NAME}}→ 有則 POSTwasmWorkerUrl('auth_static_key'){action:"resolve_credentials", api_key, names}→ 回填 node.data。不解密、不碰 ENCRYPTION_KEY(rule 02 §2.2) 實作放auth-dispatcher.ts的resolveCredentialRefs(遞迴掃描+回填,無 {{credential.}} 則零開銷不打 WASM),graph-executor 只呼叫 - 8.4
tsc --noEmitexit 0 + grep 確認 graph-executor / auth-dispatcher 無 crypto.subtle / {{secret. / ENCRYPTION_KEY 業務邏輯 - 8.5 端對端驗收:機制已實證打通(2026-06-13 leo21c 壓測)。實際走 Notion 而非 OpenAI 路徑——
node.data
{{credential.notion_token}}→ 注入解密後 token → 真讀到 Notion Recipes 資料 (「蕃茄蘑菇燉雞」+ iCook 連結),同時 401 假綠根治全鏈驗證(host fn error envelope → 零件 parsed["error"] → cypher isFailure(),401 回 success:false)。客觀證據:真服務 2xx + 真 body。 (8.5 原訂 OpenAI 為驗收服務,實際以 Notion 達成同等證據;機制與服務無關,故視為驗收完成) - 8.6 design.md / status.md / BACKLOG.md 同步(§8 設計 + 8.1-8.4 done 標記)
Notes
- 方案 B 是唯一方案(方案 A 已廢棄,違反 CLAUDE.md §禁止行為)
- Phase 8 的
resolve_credentials是用戶面入口,與 authenticate(recipe 自動注入)並存不重疊 - Phase 0.6(host functions)+ 0.7(WASM runner)是 Phase 1-3 的硬前置,必須先做
- 若 TinyGo
encoding/base64可用就直接用;若不可用則自行實作(見 gmail/main.go 的base64URLEncode) auth_mtls的 TLS handshake 無法在 WASM 內做(WASI preview1 沒 socket),只能輸出 cert/key 讓 Worker 在 fetch 時用- 每個 auth primitive WASM 都是獨立部署的 Worker(透過
component-worker-template/),不是從 R2 動態載入 - Cypher binding = workflow YAML 裡的 URL 清單,不是 Cloudflare service binding