From c5d696556eb6af2a22cce75d8ca4ebe495dbdebd Mon Sep 17 00:00:00 2001 From: uncle6me-web Date: Tue, 11 Aug 2026 22:38:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=9D=E7=AE=A1=EF=BC=9A#89/#90/#91=20?= =?UTF-8?q?=E5=8D=A1=E5=9C=A8=E4=BA=BA=E9=A1=9E=E9=96=98=E5=89=8D=E7=9A=84?= =?UTF-8?q?=E7=94=A2=E7=89=A9=EF=BC=8C=E5=BE=9E=20session=20=E6=9A=AB?= =?UTF-8?q?=E5=AD=98=E7=9B=AE=E9=8C=84=E6=90=B6=E9=80=B2=E7=89=88=E6=8E=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三樣都做完並實測過,但落地的最後一步是終端機裡等人親手打字的互動閘。 它們原本只存在於某個 session 的 scratchpad——那種目錄一關就沒了。 · recipes/gitea_put_file.yaml 出貨線 7 站等它 · recipes/cf_worker_deploy_simple.yaml ⚠️ 只適用無 bindings 的簡單情形(見 #90) · hash-component/ sha256/sha1/md5,已與系統原生指令逐位元核對 (.wasm 是 1.3MB 編譯產物,不進版控,README 附重編指令) README 寫了落地指令與各自的注意事項。 Co-Authored-By: Claude Opus 5 --- pending-human-gate/README.md | 64 +++++++++++++ .../hash-component/component.contract.yaml | 74 +++++++++++++++ pending-human-gate/hash-component/main.go | 89 +++++++++++++++++++ .../hash-component/wrangler.toml | 11 +++ .../recipes/cf_worker_deploy_simple.yaml | 21 +++++ .../recipes/gitea_put_file.yaml | 11 +++ 6 files changed, 270 insertions(+) create mode 100644 pending-human-gate/README.md create mode 100644 pending-human-gate/hash-component/component.contract.yaml create mode 100644 pending-human-gate/hash-component/main.go create mode 100644 pending-human-gate/hash-component/wrangler.toml create mode 100644 pending-human-gate/recipes/cf_worker_deploy_simple.yaml create mode 100644 pending-human-gate/recipes/gitea_put_file.yaml diff --git a/pending-human-gate/README.md b/pending-human-gate/README.md new file mode 100644 index 0000000..9b682bd --- /dev/null +++ b/pending-human-gate/README.md @@ -0,0 +1,64 @@ +# 卡在人類閘前的產物(`Arcrun#89` / `#90` / `#91`) + +> **為什麼這個資料夾存在**:這三樣東西都做完並實測過了,但落地的最後一步是 +> **終端機裡等人親手打字的互動閘**,AI 打不進去。 +> 2026-08-11 它們原本只存在於某個 session 的暫存目錄——**那種目錄一關就沒了**。 +> 先搶進版控,等人有空時再落地。 + +--- + +## 一、兩份 recipe(`#89`/`#90`) + +`recipes/gitea_put_file.yaml` — 把檔案寫回 Gitea repo。**出貨線有 7 站等它。** +`recipes/cf_worker_deploy_simple.yaml` — 部署單檔 Worker(classic 格式)。 + +**落地指令**(一份跑一次): + +``` +acr recipe push pending-human-gate/recipes/gitea_put_file.yaml +``` + +跑的時候會停下來要你**親手輸入資源名確認**——那是「把資源變成可被外部呼叫」的暴露同意閘, +不是卡住,是設計如此。 + +⚠️ **`cf_worker_deploy_simple.yaml` 先別急著推**:`#90` 查出一件結構性的事—— +recipe 引擎的 body 一律 JSON,而 Cloudflare 上傳 Worker 的 API 要的是原始 JS 或 multipart。 +⇒ **classic 版只適用於沒有 bindings 的簡單情形**。而實查安裝器那站有 9 把 KV + 一顆 D1, +**classic 版幫不上它**。詳見 `Leo/Arcrun#90`。 + +### 金鑰(D36) + +兩份 recipe 都只寫名字(`gitea_token`/`cf_api_token`),真身由 credential 中心在執行前回填。 +對應的 auth-recipe **已經註冊在 leo21c 上**,可以直接查證: + +``` +curl -s https://arcrun-cypher-executor.leo21c.workers.dev/auth-recipes/gitea +``` + +--- + +## 二、`hash` 零件(`#91`) + +`hash-component/` — sha256/sha1/md5,hex/base64。出貨線的版本號機制與成品指紋核對都要它。 + +**已實測**(tinygo 編出來、wasmtime 真跑,三種演算法都跟系統原生指令**逐位元一致**)。 +`.wasm` 是 1.3 MB 編譯產物,**沒有進版控**——要驗自己重編: + +``` +cd pending-human-gate/hash-component && tinygo build -target=wasi -o /tmp/hash.wasm main.go +echo '{"algorithm":"sha256","input":"hello"}' | wasmtime /tmp/hash.wasm +printf 'hello' | shasum -a 256 # 兩者應該一致 +``` + +**落地要走零件投稿流程**(D27/D28):`docs/component-pr-review-standard.md` 的 checklist ++ 人在終端機互動跑 `scripts/component-arm.sh`。 +🔴 `registry/components/` 底下有機械閘(`component-guard.sh`)擋著 AI 直接寫入——**那是刻意的**, +所以這份放在 `pending-human-gate/`,不是放在它最終該去的位置。 + +--- + +## 落地之後 + +三樣都上去之後,`Arcrun#89`/`#91` 才能從 **◐ 半通** 變 **✅**—— +而判準是**貼一次真實的執行輸出**(recipe 對某個測試檔案回 2xx、零件在真端點上跑出正確雜湊), +不是「推上去了」。 diff --git a/pending-human-gate/hash-component/component.contract.yaml b/pending-human-gate/hash-component/component.contract.yaml new file mode 100644 index 0000000..e2015c4 --- /dev/null +++ b/pending-human-gate/hash-component/component.contract.yaml @@ -0,0 +1,74 @@ +canonical_id: "hash" +display_name: "計算雜湊" +category: "logic" +version: "v1" +wasi_target: "preview1" +stability: "floating" +runtime_compat: + - "cf-workers" + - "workerd" + - "wazero" +constraints: + max_size_kb: 2048 + max_cold_start_ms: 50 + no_network_syscall: true + no_filesystem_syscall: true + io_model: "stdin_stdout_json" +input_schema: + type: object + required: [input] + properties: + algorithm: + type: string + enum: [sha256, sha1, md5] + description: 雜湊演算法,預設 sha256 + input: + type: string + description: 要算雜湊的內容 + encoding: + type: string + enum: [hex, base64] + description: 輸出編碼,預設 hex +output_schema: + type: object + properties: + success: + type: boolean + data: + type: object + properties: + result: + type: string + algorithm: + type: string + encoding: + type: string +gherkin_tests: + - scenario: "sha256 hex(預設)" + given: '{"algorithm":"sha256","input":"hello"}' + then_contains: '"result":"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"' + - scenario: "sha1" + given: '{"algorithm":"sha1","input":"hello"}' + then_contains: '"result":"aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"' + - scenario: "md5" + given: '{"algorithm":"md5","input":"hello"}' + then_contains: '"result":"5d41402abc4b2a76b9719d911017c592"' + - scenario: "base64 編碼" + given: '{"algorithm":"sha256","input":"hello","encoding":"base64"}' + then_contains: '"result":"LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="' + - scenario: "預設 algorithm=sha256" + given: '{"input":"hello"}' + then_contains: '"algorithm":"sha256"' + - scenario: "不支援的 algorithm" + given: '{"algorithm":"crc32","input":"hello"}' + then_contains: '{"success":false' +tags: [builtin, logic, hash, checksum, versioning] +description: >- + 計算內容雜湊(sha256/sha1/md5,輸出 hex 或 base64)。純計算,無網路/檔案 syscall。 + 用途:出貨線版本號機制(Leo/Arcrun#91)——內容一變雜湊必變,是「改了東西版本沒動」在結構上 + 不可能發生的機制來源;build 站核對官方成品指紋也用它。 +config_example: | + compute_hash: # 節點名稱(可自訂) + algorithm: "sha256" # 演算法(選填,預設 sha256),可選值:sha256/sha1/md5 + input: "{{ctx.bundle_content}}" # 要算雜湊的內容(必填) + encoding: "hex" # 輸出編碼(選填,預設 hex),可選值:hex/base64 diff --git a/pending-human-gate/hash-component/main.go b/pending-human-gate/hash-component/main.go new file mode 100644 index 0000000..ea7ca96 --- /dev/null +++ b/pending-human-gate/hash-component/main.go @@ -0,0 +1,89 @@ +// hash — 計算內容雜湊(純計算,無網路/檔案 syscall) +// 支援: sha256, sha1, md5;輸出編碼: hex(預設), base64 +// 用途:出貨線版本號機制(Leo/Arcrun#91)——內容一變雜湊必變, +// 是「改了東西版本沒動」在結構上不可能發生的機制來源。 +// +//go:build tinygo + +package main + +import ( + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "io" + "os" +) + +type Input struct { + Algorithm string `json:"algorithm"` // sha256(預設)| sha1 | md5 + Input string `json:"input"` + Encoding string `json:"encoding"` // hex(預設)| base64 +} + +func main() { + raw, err := io.ReadAll(os.Stdin) + if err != nil { + writeError("failed to read stdin: " + err.Error()) + return + } + var in Input + if err := json.Unmarshal(raw, &in); err != nil { + writeError("invalid input JSON: " + err.Error()) + return + } + + algorithm := in.Algorithm + if algorithm == "" { + algorithm = "sha256" + } + encoding := in.Encoding + if encoding == "" { + encoding = "hex" + } + + var sum []byte + switch algorithm { + case "sha256": + h := sha256.Sum256([]byte(in.Input)) + sum = h[:] + case "sha1": + h := sha1.Sum([]byte(in.Input)) + sum = h[:] + case "md5": + h := md5.Sum([]byte(in.Input)) + sum = h[:] + default: + writeError("不支援的 algorithm: " + algorithm + "(支援 sha256/sha1/md5)") + return + } + + var result string + switch encoding { + case "hex": + result = hex.EncodeToString(sum) + case "base64": + result = base64.StdEncoding.EncodeToString(sum) + default: + writeError("不支援的 encoding: " + encoding + "(支援 hex/base64)") + return + } + + out, _ := json.Marshal(map[string]interface{}{ + "success": true, + "data": map[string]interface{}{ + "result": result, + "algorithm": algorithm, + "encoding": encoding, + }, + }) + os.Stdout.Write(out) +} + +func writeError(msg string) { + out, _ := json.Marshal(map[string]interface{}{"success": false, "error": msg}) + os.Stdout.Write(out) +} diff --git a/pending-human-gate/hash-component/wrangler.toml b/pending-human-gate/hash-component/wrangler.toml new file mode 100644 index 0000000..385631e --- /dev/null +++ b/pending-human-gate/hash-component/wrangler.toml @@ -0,0 +1,11 @@ +name = "arcrun-hash" +main = "src/index.ts" +compatibility_date = "2025-02-19" +workers_dev = true + +[vars] +COMPONENT_ID = "hash" + +[[routes]] +pattern = "hash.arcrun.dev/*" +zone_name = "arcrun.dev" diff --git a/pending-human-gate/recipes/cf_worker_deploy_simple.yaml b/pending-human-gate/recipes/cf_worker_deploy_simple.yaml new file mode 100644 index 0000000..9865c4c --- /dev/null +++ b/pending-human-gate/recipes/cf_worker_deploy_simple.yaml @@ -0,0 +1,21 @@ +canonical_id: cf_worker_deploy_simple +display_name: Cloudflare Worker Deploy (single-file, classic format) +description: >- + PUT /accounts/{account_id}/workers/scripts/{script_name} 部署單檔 Worker(CF 「classic Service + Worker」格式,非 ES module)。_path 帶 /{account_id}/workers/scripts/{script_name}。 + auth: cloudflare_workers static_key(Bearer token)。 + ⚠️ 已知限制(誠實記錄,非隱藏債):這個 recipe 走 arcrun 的「recipe body 一律 JSON.stringify」 + 引擎行為(cypher-executor/src/lib/component-loader.ts makeRecipeRunner),CF 這支 API 卻要求 + body 是「原始 JS 原始碼」或(現代 ES module + bindings 情境)multipart/form-data——兩者都不是 + JSON。純 recipe 模型在這支 API 上天生對不上,這不是可以在 recipe schema 裡修的事。 + 正解=07-thin-shell §3.5 自力救濟階梯「第三方 API 缺能力→ workflow/code-node 補丁」: + 用 http_request 零件直接打(body 走它的原生 string 模式,不透過本 recipe wrapper), + header 用 {{credential.cf_api_token}} 直接內插(D36 credential 模板,不必經過 recipe/auth_service + 間接層);若目標 Worker 需要 bindings/compatibility_flags(現代 ES module 格式常態), + 上游加一個 code 節點組出 multipart/form-data body(純資料編碼,非業務邏輯,合法局部整形)。 + 本 recipe 保留給「目標帳號仍接受 classic 格式」的簡單場景;不保證覆蓋所有部署情境。 +endpoint: https://api.cloudflare.com/client/v4/accounts{{_path}} +method: PUT +auth_service: cloudflare_workers +headers: + Content-Type: application/javascript diff --git a/pending-human-gate/recipes/gitea_put_file.yaml b/pending-human-gate/recipes/gitea_put_file.yaml new file mode 100644 index 0000000..7284550 --- /dev/null +++ b/pending-human-gate/recipes/gitea_put_file.yaml @@ -0,0 +1,11 @@ +canonical_id: gitea_put_file +display_name: Gitea Put File (Create/Update) +description: >- + Gitea PUT /repos/{owner}/{repo}/contents/{filepath} 建立或更新檔案並產生 commit。 + _path 帶完整路徑(例 /Leo/arcrun-rag-bundles/contents/manifest.json,filepath 各段需 URL-encode)。 + body 帶 {message, content(base64), branch, sha(更新既有檔案時必填,取自前一次 GET 的 content.sha; + 新建檔案時不帶)}。auth: gitea static_key,header Authorization: token (D36:定義只留 + {{credential.*}} 名字,真身由 credential 中心於執行前回填,非本 recipe 職責)。 +endpoint: https://git.uncle6.me/api/v1/repos{{_path}} +method: PUT +auth_service: gitea