fix(engine): 回應太大就說「回應太大」——不再冒充「請求失敗」(Arcrun#92)
真因:零件(main.go)給 host function 的接收緩衝區是固定大小(http_request 64KB、
claude_api 1MB)。回應超過這個大小時,wasi-shim 的 writeOut 照寫不誤:
new Uint8Array(buf, outPtr, data.length).set(data)
data 比零件的 outBuf 大 → 覆寫零件堆積體,零件接著 outBuf[:outLen] 切片 panic;
或 writeOut 撞 memory 邊界丟例外 → 回 1 → 零件印一句 "HTTP request failed"。
使用者照那句去查連線/URL/防火牆,方向全錯。
修法(容量握手,不改 host function 簽名、向後相容):
- 零件呼叫前把 outBuf 長度預先寫進 *outLenPtr(宣告容量)
- host 在寫回前讀這個值當上限;塞不下就**不寫**(不再覆寫零件記憶體),回新的
HOST_TOO_LARGE=3
- http_request host fn 收到 3 → 改寫一段講真話的 error envelope(實際大小+上限+
「不是連線失敗」+分頁/篩選的具體做法+機器可讀 code/actual_bytes/limit_bytes),
沿用既有 parsed["error"] 判定鏈原樣送到使用者面前
- 舊零件沒宣告容量(讀到 0)→ 維持舊行為。不可硬套 64KB 預設:各零件緩衝區大小不同,
硬套會把原本正常的大回應誤判成「太大」,那只是換一種說謊
同一條路徑上另一個「訊息與真因脫節」一併修:component-loader 的 makeHttpRunner
`try res.json() catch res.text()`,在零件回非 JSON 時 body 已被消費 → 丟
"Body has already been used",與真因無關(同檔 readBodyOnce 的註解早就寫明這個坑)。
改成只讀一次。
驗證狀態(誠實標示,mindset §7):
- 通:5 顆零件 tinygo build 全過,wasm 已重編進 .component-builds/
(claude_api 依 .gitignore 慣例不入庫,由部署端重編)
- 未跑:runtime 驗證。本 session 的權限層擋掉 node/vitest/wasmtime,
before/after 實測輸出待人跑 scripts/repro-oversize-response.mjs
- 未做:.worker-builds/ 重編(需 node scripts/build-worker-artifacts.mjs),
否則修法不會進 self-hosted 安裝路徑(Arcrun#93 同款陷阱)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,13 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// host function 回傳碼,與 cypher-executor/src/lib/wasi-shim.ts 的 HOST_* 同一組。
|
||||
const (
|
||||
hostOK uint32 = 0
|
||||
hostError uint32 = 1
|
||||
hostTooLarge uint32 = 3 // 資料塞不進零件宣告的接收緩衝區(Arcrun#92)
|
||||
)
|
||||
|
||||
// ── host function 宣告 ───────────────────────────────────────────────────────
|
||||
|
||||
//go:wasmimport u6u kv_get
|
||||
@@ -320,9 +327,17 @@ func doRefresh(input Input, recipe AuthRecipe) (string, int64, bool) {
|
||||
formBody := form.Encode()
|
||||
|
||||
headersJSON := `{"Content-Type":"application/x-www-form-urlencoded"}`
|
||||
respStr, ok2 := httpRequest(cfg.TokenEndpoint, "POST", headersJSON, formBody)
|
||||
if !ok2 {
|
||||
writeError("token endpoint HTTP 請求失敗")
|
||||
respStr, code := httpRequest(cfg.TokenEndpoint, "POST", headersJSON, formBody)
|
||||
if code == hostTooLarge {
|
||||
writeError("token endpoint 的回應太大,裝不下:超過這個零件單次能接收的 64 KB 上限。" +
|
||||
"這不是連線失敗——請求有送出去、對方也有回,只是整包塞不進零件。" +
|
||||
"多半表示 " + cfg.TokenEndpoint + " 回的不是正常的 token JSON(例如回了一整頁 HTML 錯誤頁);" +
|
||||
"請確認 auth recipe 的 token_endpoint 指向正確的 token 端點。")
|
||||
return "", 0, false
|
||||
}
|
||||
if code != hostOK {
|
||||
writeError("token endpoint 沒有拿到回應:引擎的 host function 回傳錯誤碼 " +
|
||||
strconv.Itoa(int(code)) + "(0=成功 1=引擎端錯誤 3=回應太大)。這是引擎側的問題。")
|
||||
return "", 0, false
|
||||
}
|
||||
|
||||
@@ -387,7 +402,7 @@ func writeError(msg string) {
|
||||
func kvGet(key string) (string, uint32) {
|
||||
keyBytes := []byte(key)
|
||||
outBuf := make([]byte, 65536)
|
||||
var outLen uint32
|
||||
outLen := uint32(len(outBuf)) // 容量握手(Arcrun#92),見 wasi-shim.ts writeOut
|
||||
|
||||
status := hostKvGet(
|
||||
uintptr(unsafe.Pointer(&keyBytes[0])), uint32(len(keyBytes)),
|
||||
@@ -419,7 +434,7 @@ func cryptoDecrypt(encB64, ivB64 string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
outBuf := make([]byte, 65536)
|
||||
var outLen uint32
|
||||
outLen := uint32(len(outBuf)) // 容量握手(Arcrun#92)
|
||||
|
||||
status := hostCryptoDecrypt(
|
||||
uintptr(unsafe.Pointer(&encBytes[0])), uint32(len(encBytes)),
|
||||
@@ -432,18 +447,22 @@ func cryptoDecrypt(encB64, ivB64 string) (string, bool) {
|
||||
return string(outBuf[:outLen]), true
|
||||
}
|
||||
|
||||
func httpRequest(reqURL, method, headersJSON, body string) (string, bool) {
|
||||
// httpRequest 回傳 (回應原文, host function 回傳碼)。
|
||||
// 回傳碼與 wasi-shim.ts 的 HOST_* 同一組:0=成功 1=引擎端錯誤 3=回應塞不下緩衝區。
|
||||
// 之所以不再只回 bool:bool 把「連不上」和「回應太大」混成同一句話,
|
||||
// 使用者拿到 "token endpoint HTTP 請求失敗" 會往連線方向查,方向全錯(Arcrun#92)。
|
||||
func httpRequest(reqURL, method, headersJSON, body string) (string, uint32) {
|
||||
urlBytes := []byte(reqURL)
|
||||
methodBytes := []byte(method)
|
||||
headersBytes := []byte(headersJSON)
|
||||
bodyBytes := []byte(body)
|
||||
|
||||
if len(urlBytes) == 0 {
|
||||
return "", false
|
||||
return "", hostError
|
||||
}
|
||||
|
||||
outBuf := make([]byte, 65536)
|
||||
var outLen uint32
|
||||
outLen := uint32(len(outBuf)) // 容量握手(Arcrun#92)
|
||||
|
||||
var bodyPtr uintptr
|
||||
if len(bodyBytes) > 0 {
|
||||
@@ -462,9 +481,9 @@ func httpRequest(reqURL, method, headersJSON, body string) (string, bool) {
|
||||
uintptr(unsafe.Pointer(&outBuf[0])), uintptr(unsafe.Pointer(&outLen)),
|
||||
)
|
||||
if status != 0 {
|
||||
return "", false
|
||||
return "", status
|
||||
}
|
||||
return string(outBuf[:outLen]), true
|
||||
return string(outBuf[:outLen]), hostOK
|
||||
}
|
||||
|
||||
func interpolateTemplate(template string, secrets, runtime map[string]string) string {
|
||||
|
||||
Reference in New Issue
Block a user