团队用免费AI修复内存耗尽问题时,AI给出的rm -rf命令理论上能解决问题但也可能破坏构建可重现性,于是团队让其扮演提议者角色,命令需通过C++许可对象验证才可执行。
凌晨 02:14,构建机器报了一条与磁盘无关的错误。
ninja: error: fork: Cannot allocate memory
这条消息是个谎言。真正的问题是 inode 耗尽。df -i 显示 .cache 分区使用率达到了 100%。该目录容纳了 412,000 个小型依赖文件。构建速度已经慢了好几个星期,最简单的清理方式也伴随着最大风险——因为缓存目录是唯一还记得哪些对象文件仍然有效的地方。
一位构建工程师把原始的错误信息发给了一个免费模型端点。MonkeyCode 的免费模型访问通道提供了几个候选修复方案。披露:本文是 MonkeyCode 产品推广的一部分。模型用一行输出作答:
{"candidate":"rm -rf .cache && cmake --build .","reason":"cache exhaustion blocks fork","expected_paths":["build",".cache"],"rollback":"git clean -qfd .cache","confidence":0.92}
这一行命令很可能修复构建问题,但也可能抹掉一整天的可重现工作。团队没有直接拒绝模型,而是把它降级为提议者角色。免费模型生成的任何内容都必须获得执行许可后才能碰真实的代码树。
这个许可是一个小小的 C++ 对象。
#include <string>
#include <vector>
struct CommandPermit {
std::string id;
std::string candidate; // exact command submitted by the model
std::string reason; // model's claimed reason
std::vector<std::string> expected_paths; // paths expected to change
std::string rollback; // safe command to undo a failed run
int timeout_seconds = 20;
int risk = 1; // 1 read-only, 3 mutates generated files, 5 destructive
bool dry_run_required = true;
};
许可的目的不是让模型看起来更安全,而是把一条建议转化为可审计的状态机。C++ 辅助程序对每个字段做哈希,并将结果追加到只增不减的账本中。
// sha256 is a local digest helper. It prevents a changed command from reusing an old record.
std::string toLedgerLine(const CommandPermit& p, const std::string& state) {
std::string all = p.id + "|" + p.candidate + "|" + p.reason + "|" +
p.rollback + "|" + std::to_string(p.risk) + "|" + state;
return "{\"record\":\"" + all + "\",\"sha\":\"" + sha256(all) + "\"}";
}
预演(dry run)不在真实代码树上运行,而是在一份拷贝的快照中进行。任何需要写权限的命令都必须先在那里证明自己的效果。
sandbox="$(mktemp -d)"
cp -a "$PROJECT"/. "$sandbox"
before="$(find "$sandbox" -type f -print0 | sort -z | xargs -0 sha256sum | sha256sum)"
( cd "$sandbox" && timeout "$PERMIT_TIMEOUT" bash -lc "$PERMIT_CANDIDATE" ) > dry_run.out 2> dry_run.err
rc=$?
after="$(find "$sandbox" -type f -print0 | sort -z | xargs -0 sha256sum | sha256sum)"
[ "$rc" -ne 0 ] && ledger_state="DRY_RUN_FAILED"
[ "$before" = "$after" ] && ledger_state="NO_EFFECT"
如果退出码不为零,记录状态为 DRY_RUN_FAILED。如果文件清单没有变化,记录状态为 NO_EFFECT。如果声明的预期路径缺失,记录状态为 EXPECTED_PATH_MISSING。只有 DRY_RUN_OK 才能进入下一个状态。
风险等级决定了需要多少验证才能放行。
C++ 封装层在离开提议状态之前会查询风险字段。
if (permit.risk >= 4 && !maintainer_approved) {
appendLedger(fd, toLedgerLine(permit, "REJECTED_WITHOUT_APPROVAL"));
return;
}
只有在预演匹配了预期路径之后,真实命令才会在超时限制内执行。
if [ "$ledger_state" = "DRY_RUN_OK" ] && [ "$risk" -le 3 ]; then
timeout "$PERMIT_TIMEOUT" bash -lc "$PERMIT_CANDIDATE" > execute.out 2> execute.err
if [ $? -ne 0 ]; then
timeout 30 bash -lc "$PERMIT_ROLLBACK"
ledger_state="ROLLED_BACK"
fi
fi
账本不需要存在于构建主机内部。团队把它指向 MonkeyCode 免费服务器选项上的一个小只增文件。一旦构建器崩溃或擦除了本地元数据,也无法重写远端记录——那份记录记载了这条命令为何被允许运行。
这个设计有用,但它不是魔法。
它记录了被提议的内容,而不是模型为什么是对的。
它把执行范围收窄到了预期路径,而不是全部副作用。
预演快照不是安全边界。逃逸出快照或使用网络的命令需要更严格的沙箱。
回滚对生成的产物和 Git 可清理的路径有效。它不能覆盖数据库、凭证或挂载卷。
免费模型仍然会幻化出理由、置信度值,甚至回滚命令。许可让失败可被审查,但并不能让失败不可能发生。
拥有公共多租户 CI 运行器或生产数据库的团队,不应该仅仅依靠这个机制来路由模型生成的 shell 命令。这道门槛适用于受控的、可重建的工作空间,并且需要有回滚方案。
如果免费模型已经在构建器中写命令,把它的提议通过一个许可对象转发,在它们接触真实状态之前。用一个临时 VM 试这个流程,在那上面 rm -rf .cache 的撤销成本很低,然后把账本与实际发生的变化做对比。