← 返回目录

第 3 卷

第 3 / 4 卷日期:

复制代码 1{

2"type": "rule.update", 3"id": "rule-7a9546c1",

4"state": "active", 5"at": 1750000000001,

6"by": "人(/firewall approve)", 7"patch": {}

8}

两条都带 by 与 at,标注这次变更来自谁、发生在什么时候。by 的来源有三类:人的命令、Agent 的工具,以及人绕过账本直接改文件的带外修改。少了这两个字段,事后就分不出

“这条规则是 Agent 在级别 2 下自己转正的还是人批的”,也说不清几笔改动的先后;有了它们,逐条读事件就能复原一次规则变更的经过,这正是“人掌握决定权”能被审计的关键。

上面两条分属其中两类。本讲一共四类事件:rule.add 新增、rule.update 修改、 rule.reject 驳回或下线、rule.remove 删除。

再说折叠:拿一个初始状态,把事件按顺序逐个作用上去,走完得到最终状态。本讲是从一个空 Map 出发,事件依次作用,最后剩下什么规则,就是当前规则集。实现就是一个 Map 从

头走到尾:

复制代码

1exportfunction fold(events: RuleEvent[]): Rule[] {
2constmap =newMap<string, rule>()
3for(conste of events) {
4if(e.type ==='rule.add') {
5map.set(e.id, { ...e.input, id: e.id, state: e.state ??'candidate'}) //
6}elseif(e.type ==='rule.update') {
7constr = map.get(e.id)
8if(r) map.set(e.id, { ...r, ...e.patch, state: e.state ?? r.state })
9}elseif(e.type ==='rule.reject') {

16return [...map.values()] 17}</string, rule>

最后看落盘。内存中折叠出的规则集还要落成数据面读的文件(rules.ledger.json),写的时候排队执行,避免并发互相覆盖;先写临时文件再 rename,不留半截。数据面判定请

求时读的就是这个文件,所以文件一落地,这条规则就算实际生效了。至于它加载之后怎么匹配请求,本讲不展开,如果你感兴趣可以看第 18 讲的引擎代码。

以下是删掉 rules.ledger.json 后首次运行 npm run start:ch19 的真实输出。草稿 id 随机,“Agent 起草候选”与“Agent 测试”两步走真实 DeepSeek 模型,规则名、条件与

token 用量每次运行都可能不同。这个示例每次运行都换一个新的落盘目录,所以权威不会累积上一次的事实;但那个规则文件留在磁盘上不会自己消失,再跑一次时,它里面的规则会在

启动时被补记回权威,事件数与规则数因此越跑越多。想复现这组数字,先删掉那个文件。

复制代码 1=== /firewall level 1(默认)===

2自主级别已设为 1:起草自动;批准与驳回要人确认 3

4=== Agent起草候选->人审批->发布(真实模型)===
5模型原始回复: {"name":"block ip 1.2.3.4","action":"deny","when":{"field":"ip","op":
6模型token用量:in=175out=40
7草稿rule-7a9546c1起草为候选: {"name":"block ip 1.2.3.4","when":{"field":"ip","op"
8人审批后rule-7a9546c1已active

9 10=== 带外修改:人直接改文件,补记对账 ===

11带外新增 rule-outofband 已补记 12

13=== facts 校验 === 14一致=true,事件数=3,当前规则数=2

15

16===重启恢复(新进程从磁盘上的JSONL恢复权威)===
17恢复后规则数=2
19===账本事实写进dsh会话日志(会话日志是权威,文件只是产物)===
20权威:会话日志内事实=3条,折叠出规则数=2
26Agent起草候选rule-99b8429d(state=candidate)
27人审批后rule-99b8429d state=active
28facts一致=true,事件数=5

29

本讲小结

这一讲把规则从“纯手工操作”推进到“可生成、可审批、可生效”。

受约束生成:模型按 prompt 写明的字段约定起草,代码侧 isValidRuleWhen 把关, field / op 只能取约定值、value 非空,吐出的规则一定合法;第一稿一律是候选,不

直接生效。

审批闸与自主级别:审批闸装在工具流水线前两站,tools/pre-execute 按级别拦写操作、tools.guard 用白名单限工具范围;自主级别分为三档:0 / manual / off、1

(默认)、2,决定闸开多大,人用 /firewall 命令来掌控。

账本:变更记成 rule.add、rule.update、rule.reject、rule.remove 四类事件,追加进会话日志,折叠出规则集再原子写落成数据面读的文件,文件只是产物,权威在

会话日志。

这背后真正的变化是规则从“一个配置文件”变成“有变更记录、能审计、能回放”的账本。

Agent 的权限和能力由人来掌控。下一讲我们看 Agent 怎么从异常请求里长出候选规则。

注:本讲 GitHub 链接

思考题

动手实操:示例在 src/firewall/ch19/,直接改代码、重跑验证。

  1. 跑 approval-e2e.test.ts,它走真实的 tools.execute 流水线,你会看到 0 级别

的写工具被 tools/pre-execute 拦下;再跑 approval.test.ts,它只断言

全站分卷索引 · 4 卷
  1. 1第 1 卷
  2. 2第 2 卷
  3. 3第 3 卷
  4. 4第 4 卷