fix(guardrail): 输入护栏上限按推导预算派生(修「大附件被拦死」)+ 第 5 条 flake
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 真 bug:文档注入预算(1M 窗口 ⇒ 349525 字符)> 输入护栏硬编码上限(262144)⇒ 带 744KB 附件的 POST /chat 直接 input_length_exceeded/guardrail_input,整个回合作废(主会话活服务实测)。

- 修法:guardrailMaxInputChars → guardrailMinInputChars(256Ki 下限) + 纯函数 deriveGuardrailInputLimit(log, override, budgetTokens, docInjectChars):派生值 = max(下限, 推导预算 tokens)(1M ⇒ 699050);新增 env GUARDRAIL_MAX_INPUT_CHARS(未设/0=派生、>0=覆盖、<0=WARN 回落);预算只在 wire_context 推导一次并经 buildGuardrailPipeline(ctxBudget) 传入(避免「窗口未声明」WARN 记两遍);输出上限 65536 刻意不参与派生(有显式回归钉住)。

- 关系不变量:文档注入预算 ≤ 输入上限,人为破坏 ⇒ 一条 WARN(不静默)。

- 第 5 条 flake(exec/TestTerminal_Timeout_Killed):仅改测试、terminal.go 保持 pristine;根因不是「杀进程失效」而是旧断言的 ~200ms 精度把宿主负载下的进程创建耗时算了进去(重压下 cmd.Start 会直接返回 deadline exceeded,红行却报 process not killed)⇒ 改为「控制组证明同命令不设短超时正常退出」的确定性证明 + 把墙钟断言降级为 10s 语义判别界(子进程自然存活期拉到 30s)。

- 主会话独立验收:隔离 worktree(G2 的 10 个文件,排除 M1 在途的 wire_channels.go)build/vet 绿、全树 59 包 0 FAIL、双平台交叉编译绿、Guardrail* 全 PASS、exec -count=20 绿;变异独立复现(把派生值退回硬编码 ⇒ 4 条红,含 budget=699050 → 262144)。

- 残留缺口(已排 G2b):护栏量的是整段装配请求、注入预算是单份 ⇒ ≥2 份各撞注入预算的大附件仍可能被拦。
This commit is contained in:
AskaEth committed 2026-10-04 19:15:12 +08:00
1 parent d37d4a899b
commit 51c1672355
10 files changed
+577 -48

No files matched your search

+15 -3
View File
@@ -226,11 +226,23 @@ SUBAGENT_TIMEOUT_SEC=600
# 单份附件注入模型可见上下文的字符上限(sidecar 仍存全文,只受 8 MiB 安全上限约束)。
# 留空 = 按推导出的预算算:推导预算的 1/8(按一个回合最多 5 个附件留余量),下限 64K 字符。
# 它决定「每回合往上下文里放多少」,也因此决定清单行给的续读游标从哪里开始。
# ⚠️ 它必须**不超过**下面 GUARDRAIL_MAX_INPUT_CHARS 生效的输入上限,否则大附件会在装配完之后
# 被输入护栏整回合拦死(input_length_exceeded)。改了本键就顺手核对那一项。
# DOCUMENT_MAX_INJECT_CHARS=
# 请求侧字符上限(输入护栏跑在**组装完成的请求**上:系统提示词 + 记忆块 + 整段对话 + 工具结果)。
# 三种取值:
# · 未设置 / 0(默认)= 由推导出的上下文预算派生:max(262144, 推导预算 token 数)
# (1 字符 ≈ 1 token 的保守口径);1M 窗口 ⇒ 预算 699050 token ⇒ 上限 699050 字符,
# 而单份附件注入预算是 349525 字符 ⇒ 关系不变量「注入预算 ≤ 输入上限」成立,
# 「分享大文件」才走得通。小窗口部署下派生的 token 数低于 262144,行为与旧硬编码一致。
# · > 0 = 显式覆盖(原样使用;调小到注入预算之下会让大附件被拦,届时启动日志里有 WARN)
# · < 0 = 非法:记一条 WARN 并回落派生值(绝不静默生效)
# 它只动**输入**侧;输出上限(65536 字符)防的是模型输出失控,与上下文预算无关,不开放配置。
# 敏感信息(API key / bearer token / 密码 / 私钥)为阻断档,同样是代码内定的安全默认值。
# (取值与关系不变量的落点在 cmd/cyrene/wire_guardrail.go)
# GUARDRAIL_MAX_INPUT_CHARS=
# 内容策略关键词表(逗号分隔,大小写不敏感的子串匹配)。留空 = 不挂关键词护栏
# (项目不内置任何词表,只有你显式声明过的词才会被拦)。敏感信息(API key /
# bearer token / 密码 / 私钥)为阻断档、请求与响应各有长度上限,这两项是代码
# 内定的安全默认值,不对外开放配置(见 cmd/cyrene/wire_guardrail.go)。
# (项目不内置任何词表,只有你显式声明过的词才会被拦)。
# GUARDRAIL_KEYWORDS=
# ---- 主人身份说明书注入范围(design/11 §0 红线) ----