Files
deepseek-harness/docs/defensive-patterns.zh.md

30 lines
3.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 防御性模式
[English](defensive-patterns.md) | 中文
来之不易的缺陷类别规则:下面每条模式都是本项目实际发布或差点发布的一类缺陷,以防止其复发的规则形式陈述。在编写生命周期、并发、子进程或清理代码之前请先阅读本文。测试层面的对应规则(真实入口路径、验证实际结果、资源归属)见 [testing.md](testing.md)。
## 正交结果独立上报
一个结果可以同时具有多种性质:进程可能已经超时,却仍以退出码 0 结束,因为它捕获了终止信号。每个独立事实(`timedOut``signal``exitCode`)都应单独上报;切勿把一个标志的上报嵌套在另一个标志的分支中,否则调用方可能把提前终止的运行误判为正常成功。
## 跨 seam 契约两侧都要遵守
当接口文档规定两种合法的信号方式时,消费方必须同时处理两条路径,而不能只处理第一个实现恰好使用的路径。例如,适配器既可以从 `stream()` 抛出异常来报告失败,也可以发送 `finish {kind:'error'|'aborted'}` 分片来结束流。基于依赖库实现的适配器可能无法在流中途抛出异常,只能使用带内路径;如果 agent loop智能体循环只捕获抛出的异常就会把提供方的 401 错误误判为正常完成的轮次。请在类型定义处记录完整契约,并通过真实消费方测试每个分支。
## 异步状态不是同步状态
`agent.followup()` 不会在返回前改变状态;后台任务完成可能与轮次边界发生竞态;`reader.close()` 在 EOF 和 dispose资源释放时都会触发。不要根据刚刚请求的状态变化来控制流程而应让实际触发的事件或 Promise`agent/status``task.done`)驱动生命周期,并观察真实状态转换,例如先观察到 `running`,再观察到 `idle`。不要把状态当作每次 `followup()` 的结果:多个已排队的 `followup()` 可以在同一个 `running` 区间内连续执行多个轮次,而取消或资源释放可能丢弃尚未开始的项。反过来,如果等待的转换根本不会发生,例如 EOF 时从未提交工作,因而系统永远不会进入 `running`,等待就会无限期挂起;必须显式处理「无需等待」的分支。
## Dispose 必须达到完全停稳,而不仅仅是请求停止
如果清理流程只发出终止或中止信号便返回,而不等待工作真正停止,就会留下孤儿进程。清理逻辑应采用异步流程,并等待子进程退出(发出终止信号后等待 `done`);还应在终止进程前关闭监听器和通知注册表,使迟到的完成事件保持静默。测试必须证明 dispose 确实等待了,例如 `await fiber.dispose()` 返回后进程 ID 已不存在,而不能只证明该进程最终会退出。
## 在边界处隔离回调异常
用户提供的监听器如果抛出异常,不得导致它所在的 promise 被 reject也不得饿死排在它后面的监听器。请用 try/catch 包裹分发循环并记录日志;一个行为不当的订阅者绝不能破坏核心生命周期。
## 绝不将环境变量或可预测路径暴露给不可信输出
启动的命令应使用经过清理的环境变量,移除名称匹配 `*KEY*``*SECRET*``*TOKEN*``*PASSWORD*` 的项,防止 harness 凭证通过命令输出、`env` 或 spill 文件泄漏。临时文件和 spill 文件应放在权限为 0700 的私有目录中,使用随机文件名,并以独占且仅所有者可访问的方式打开(`'wx'``0o600`);可预测且全局可读的路径会引发符号链接竞态和信息泄露。