(Entity-Rule Definition Language · 实体规则定义语言)
状态:v2.3 · beta
日期:2026-10-08
版本语义:本文档(ERDL 语言规范)版本为 v2.3;规则文件顶层protocol: "erdl/v2"(协议标识,固定值)与version: "2.2.0"(规则格式版本)为独立版本标识,与本文档版本互不混同(本次 v2.3 变更不改规则格式,version保持 2.2.0)。
版本策略:破坏性变更限文档 major 版本:规则格式的破坏性变更限versionmajor(如 2.2.0 → 3.0.0);DO 哈希原像 schema 的破坏性变更(如 v2.3 新增context)由 DO 内eval_profile.spec_version(§8.2a.1b)显式标识,验证方据此选择复算 schema——不违反 §2.3 的规则格式 non-breaking 承诺。
作者:唐启鑫
商标:ERDL™ 是深圳市秒镜科技有限公司的商标。
定位:ERDL(Entity-Rule Definition Language,实体规则定义语言)是一种以 YAML/JSON 承载的声明式规则定义格式,用于精确表达实体结构与行为规则。本规范独立且中立——仅定义格式本身,不依赖任何特定实现或上层框架;其确定性求值与规范化形式支持跨实现逐字节验证。在 ERDL 中,规则决定一切:规则既是语义的载体,也是执行的边界、审计的证据与治理的事实。
规范语言:本文档中 MUST / MUST NOT / SHOULD / SHOULD NOT / MAY 按 [RFC 2119] 解释。中文正文的规范性措辞采用双语标注——如「必须(MUST)」「不得(MUST NOT)」——括号内 RFC 2119 关键词为规范性依据;个别未加标注的中文语气词(「不得」「禁止」「必须」「须」等)为行文措辞,其规范性级别以对应英文版(erdl-language-spec-v2.3.en.md)同句的 RFC 2119 关键词为准。
ERDL(Entity-Rule Definition Language,实体规则定义语言)是一种以 YAML/JSON 承载的声明式规则定义格式,用于精确表达实体结构与行为规则。它包含规则声明(Rule,定义 when → then 决策)与预置实体命名空间(Entity,如 agent/tool/task——是字段引用的语义命名空间约定,非顶层声明;见 §3)。Entity 界定对象的结构,Rule 规定条件的后果;二者共同构成可执行、可审查、可验证的规则表达。
ERDL 是多方语义层:它不仅是规则格式,更是人、LLM、系统与审计四方共享的语义约定层。
| 参与方 | ERDL 的角色 |
|---|---|
| 人(业务/领域专家) | 自然语言规则的精确翻译结果,可读、可审 |
| LLM(通用大模型) | 结构化输入,消除歧义,支持确定性求值 |
| 系统(规则引擎) | 标准化规则描述,并以 fn 委派控制调用边界 |
| 审计(监管/合规) | 比代码与自然语言更清晰的可追溯规则记录 |
在这一语义层中,规则决定一切:人表达意图,LLM 翻译语义,系统执行决策,审计复核证据。ERDL 的确定性语义层为 LLM 提供明确方向:使用者以自然语言描述规则,经 ERDL 精确翻译后,由 LLM 基于结构化语义确定性执行——对话界面即为统一入口。(资料性宣称,附前提):本规范的确定性保证仅覆盖翻译完成后的求值层(§7 E1–E12);「自然语言 → ERDL」的翻译本身依赖 LLM,属非确定性过程,不在「同一输入⇒同一输出」的逐字节承诺内。
AI 治理的核心难题,不是模型能否给出答案,而是概率性输出如何满足监管、审计与追责所要求的确定性边界。ERDL 以 YAML/JSON 承载声明式规则,将实体结构、行为约束和处置动作固化为可验证的确定性规则层,使 AI 的「应当如何行动」从训练假设转变为可检查对象。
从信任到验证:传统治理依赖对齐评估或事后解释,难以证明某次具体决策遵循了哪些约束。ERDL 将约束表达为 when → then 规则,每次求值绑定 canonical_tree 快照与结果哈希,可独立重算、逐字节跨实现复核。治理方不再只是相信模型「被正确训练」,而是可以验证某次行为是否命中规则、命中哪条规则、为何得到该结果。
可追责的证据链:ERDL 求值结果可哈希、可重算,可将规则版本、输入快照、决策输出与哈希锚定形成审计记录。审计不是复述日志,而是可重现的证明过程——为何放行、为何拦截,均可回溯查验,避免事后解释成为唯一依据。「由谁批准」仅在 actor 身份附认证证据进链(§6a.5.4 事件认证证据)时方可验证——裸 actor 字符串不构成可验证的批准证明(见 §6a.5.4),故「批准人」的可回溯性以认证证据为前提,非无条件的格式属性。
机制化的人机权责边界:ERDL 可通过 REQUEST_HUMAN、ESCALATE 等决策类型,将高风险操作、敏感实体变更、不可逆动作等强制交由人工裁决。人的最终决策权不再依赖流程口号,而是被编码为可执行、可测试、可审计的规则路径。
合规即代码与跨组织互认:法规、平台政策和内部红线可转写为 ERDL 规则,并通过测试向量持续验证。不同组织、不同实现或第三方审计机构可基于同一规则与规范化树独立复算,形成「信任但验证」的治理范式,使合规从声明文档变为可运行、可检查、可互认的治理事实。
语义载体是内核,不是语法。
同一规范化树 ⇒ 同一哈希(单向承诺)。
同一(规则集, 事实, 评估选项, 状态)⇒ 同一决策与 DO。
等价语义不保证同一树:如a AND b与b AND a语义等价但树结构不同、哈希不同。确定性保证的是「同一输入 ⇒ 同一输出」,而非「等价输入 ⇒ 同一树」——后者不成立,也不在本承诺内。
任何以「运算符语法」为语义载体的方案,都会因新需求而被迫线性扩张运算符,成本永不收敛。因此本规范把语义收敛到唯一的内核(表达式树),而把多种书写形态作为内核的确定性投影——它们不是各自独立的语言,而是同一语义的不同视图。规则决定一切:规则的有效性不取决于书写入口或实现形态,而取决于唯一、可重算、可哈希、可逐字节验证的规范化语义。
一个 ERDL 文档(*.erdl.yaml)由六个顶层字段构成,其中四个 MUST、两个 MAY,字段顺序 MUST 固定:
protocol: "erdl/v2" # 协议标识,固定值
version: "2.2.0" # 规则格式版本
metadata: { ... } # 文档级元数据(见 §2.2)
state: [ ... ] # 状态空间声明(可选,见 §6a)
transitions: [ ... ] # 状态转移规则(可选,见 §6a)
rules: [ ... ] # 规则列表(见 §4)
| 顶层字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
protocol |
string | MUST | 协议标识,固定值 "erdl/v2" |
version |
string | MUST | 规则格式版本(语义化版本) |
metadata |
object | MUST | 文档级元数据 |
state |
array | MAY | 状态空间声明(§6a) |
transitions |
array | MAY | 状态转移规则(§6a) |
rules |
array | MUST | 规则列表,元素见 §4 |
metadata:
name: "my-first-rule-set"
description: "允许读文件操作"
category: coding
decision: ALLOW # 所有规则不匹配时的 fallback 决策
tags: [example]
| 字段 | 类型 | 说明 |
|---|---|---|
name |
string | 规则集名称 |
description |
string | 规则集描述 |
category |
string | 规则集分类(coding/security/compliance…) |
decision |
string | fallback 决策(所有规则不匹配时的默认裁决,见 §6) |
tags |
array | 标签 |
protocol: "erdl/v2" 开头;protocol 大版本内,新增约束对存量规则 SHOULD Non-breaking(加载时 Warning 而非 Error);跨大版本(如 erdl/v1 → erdl/v2)为 breaking change,不适用向后兼容承诺。例外:when:"true" + 拦截性 then 在任何版本均拒绝加载(Error)。本 non-breaking 承诺仅约束规则格式兼容性(存量规则仍可加载、求值、产生相同决策);DO 哈希原像 schema 的演进独立于此,由 eval_profile.spec_version(§8.2a.1b)标识。一个 ERDL 文档从文件到决策结果,走固定的五步管线。理解这条管线,即可理解 ERDL 如何被「解析」与「求值」:
| 步骤 | 动作 | 输入 → 输出 | 依据 |
|---|---|---|---|
| ① 加载 | 读入规则文档 | *.erdl.yaml → 结构化对象 |
§2.1–§2.3 |
| ② 校验 | 加载时类型检查 | 结构化对象 → 合法文档(拒绝非法) | E5、§6a.2/§6a.4/§6a.7 |
| ③ 编译 | 三种书写形态归一化 | 合法文档 → 表达式树(canonical_tree) | E7、§8.2 |
| ④ 求值 | 树对输入事实逐节点判定 | 表达式树 + context → 决策 | §7 |
| ⑤ 输出 | 生成求值证据 | 决策 → 可哈希、可重算的求值结果 | E6、§8 |
*.erdl.yaml,按 §2.3 格式约定解析(YAML 与 JSON 等价,无损互转)。when/expr 互斥、状态块校验(§6a.2/§6a.4/§6a.7)等;违规拒绝加载。within/rate 的状态由 temporal_state 受控注入,§6a 授权状态由 state.* 受控注入。输入事实(context)与求值结果(output)的完整契约见 §7.0。
Entity 是规则作用的主体(经 context 传递)。ERDL 预置以下 Entity 类型:
| Entity 类型 | 说明 |
|---|---|
agent |
单个 Agent 实例 |
tool |
Agent 调用的工具 |
task |
Agent 执行的任务 |
workflow |
多 Agent 编排流程 |
human |
人类审批者 |
guardian |
监管者(supervisor) |
规则中的字段引用(如 tool.name、context.amount)以 Entity 为语义命名空间。字段路径承重:字段名一旦发布即冻结 [FREEZE-1],别名 MUST 先行归一化为规范名。
字段路径文法(MUST):字段引用路径为点分隔的段序列(a.b.c),每段为 snake_case 标识符;$ 为根引用,$.path 为根点路径;state.*/event.* 为受控命名空间(§6a.3/§6a.7)。路径解析按事实对象键路径逐段解析(§7.0.1);数组下标与含点键 MUST 以确定性的点分割为准(实现 MUST 在文档中固定其路径文法,避免含点键/数组访问的分歧)。
Rule 是 ERDL 的核心单元:Rule = Metadata + When(条件)+ Then(动作)+ Audit(审计)。When 编译为表达式树(§5),Then 为决策类型(§6),Audit 记录规则求值的审计信息。
rules[] 子字段顺序 MUST 固定为:name → description → category → priority → override → ring → tier → enabled → when → gloss → then → message → instruction → correction → unless → explanation → alternative → legal_basis → source_text。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string | MUST | 规则唯一标识,格式 [CAT]-[NNN]-描述 |
description |
string | MUST | 人读描述 |
category |
string | MAY | 规则级分类;缺省继承 metadata.category(见 §2.2),允许同一文档内混合分类 |
priority |
integer | MUST | 数字越小越优先(见 §7.1) |
override |
string | SHOULD | 覆盖级别:critical > high > normal > low(默认 normal) |
ring |
integer | SHOULD | 执行环:0 内核 / 1 恢复 / 2 审批 / 3 建议(默认 3 建议) |
tier |
integer | MAY | 规则层级 0–5(0–2 安全底线 MUST 用 Simple,≥3 业务全景可用 Expression);tier 只决定书写形态,不决定求值错误的折叠方向(见 E12);缺省 = 未声明(永不 locked,见 §7.1a) |
enabled |
boolean | MAY | 规则启用标志(默认 true);false 时求值跳过该规则 |
when |
object / string | MUST | 触发条件(见 §5);字符串形态仅为 catch-all 字面量 "true"(无条件命中,编译产物为字面量 true 节点,§7.0.2 判定);决策表默认行 when: [] 是 §5.4 rows[] 的行级子字段(编译为字面量 true),非本顶层字段的第三形态 |
gloss |
string | MUST | 引擎从 when 树渲染的自然语言可读投影(§5.5);lint 校验 gloss == render(树),禁手写;不进哈希(G4) |
then |
string | MUST | 决策类型(见 §6) |
message |
string | SHOULD | 决策消息(拦截性 then MUST 非空) |
instruction |
string | MAY | 建议指令(ALLOW + instruction 场景) |
correction |
string | MAY | 纠正文本(CORRECT 决策;求值输出的 primary_correction 来源,见 §7.0.3) |
unless |
object/null | MAY | 豁免条件块(可选) |
explanation |
string / object | MAY | 双语解释(规则为何存在、防止何种危害) |
alternative |
string / object | MAY | 被拦截时建议的替代动作 |
legal_basis |
string | MAY | 法规依据(条款引用) |
source_text |
string | MAY | 所依据法规的原文摘录 |
protocol: "erdl/v2"
version: "2.2.0"
metadata:
name: "my-first-rule-set"
description: "允许读文件操作"
category: coding
decision: ALLOW
tags: [example]
rules:
- name: "COD-001-allow-read"
description: "放行 read_file 工具的所有调用"
priority: 10
override: high
ring: 3
when:
logic: AND
conditions:
- field: "tool.name"
operator: eq
value: "read_file"
gloss: "tool.name equals read_file" # 引擎生成(G2),lint 校验
then: ALLOW
message: "read_file 调用已放行"
unless: null
when 是规则的内嵌条件表达式。ERDL 提供三种书写形态(Simple / Expression / 决策表),任一形态编译归一化到同一语义内核(表达式树),再经渲染还原为任一形态;另有一个可读投影 gloss(§5.5)从树确定性生成自然语言。
| 投影面 | 承载 | 适用 tier | 说明 |
|---|---|---|---|
| A · Simple | 30 运算符 | 0–2(MUST) | 安全底线,最常用的书写形态 |
| B · Expression | 完整 34 节点树 | ≥3 | 业务全景:逻辑组合/量词/算术/时间/聚合 |
| C · 决策表 | 矩阵 | — | 业务/财务人员首选,编译到同一内核 |
tier 是规则层级(0–5),由低到高表示规则的约束强度与适用范围——本规范中 tier 0–2 为安全底线(MUST 用 Simple),tier ≥3 为业务全景(可用 Expression)。when 与 expr 不得(MUST NOT)共存(E5)。
Simple 是保留的既有语义单元集合,30 运算符 = 28 条件 + 2 修饰符,一字不改。它对应系统安全规则(tier 0–2)。
| 族 | 数量 | 运算符 |
|---|---|---|
| 比较 | 6 | eq · ne · gt · gte · lt · lte |
| 列表 | 2 | in · not_in |
| 字符串 | 5 | contains · not_contains · match · starts_with · ends_with |
| 边界否定 | 2 | not_starts_with · not_ends_with |
| 存在性 | 2 | exists · not_exists |
| 长度 | 5 | length_gt · length_gte · length_lt · length_lte · length_eq |
| 范围 | 2 | between · not_between |
| 计数 | 4 | count_gt · count_gte · count_lt · count_lte |
| 修饰符 | 2 | within(时间窗口)· rate(速率限制) |
"100" gt 50 恒为 false;"2" gt "10" 为 true);跨类型返回 false;casefold 规范化节点显式折叠(扩展 profile,见 §5.3.3);[min,max] 仅支持数值,非数值返回 false;in_set{ref,digest} 节点外置引用(版本化哈希引用,名单不进树、不受 256 内联上限约束,见 §5.3.3);matches → match、neq → ne 两个历史别名并归一。别名不是新增运算符(仍为 30 运算符全集);规范化形态 MUST 用规范名,别名不进树、不进哈希。不实现别名仍属合规。权威编译映射:30 运算符全部有确定编译归宿,无悬空——13 直接节点(eq/ne/gt/gte/lt/lte·in·contains/starts_with/ends_with/match·exists·between)、6 not 派生(not_in/not_contains/not_starts_with/not_ends_with/not_exists/not_between)、9 length/count 组合(length_* 5 + count_* 4)、2 时间修饰符(within/rate)。
| # | Simple 运算符 | 编译归宿 | 表达式树表达 |
|---|---|---|---|
| 1-6 | eq ne gt gte lt lte |
直接节点 | 比较节点 |
| 7 | in |
直接节点 | 集合节点 in |
| 8 | not_in |
not + exists 守卫 | exists(field) AND not(in(...)) |
| 9-12 | contains starts_with ends_with match |
直接节点 | 字符串节点 |
| 13 | not_contains |
not + exists 守卫 | exists(field) AND not(contains(...)) |
| 14-15 | not_starts_with not_ends_with |
not + exists 守卫 | exists(field) AND not(...) |
| 16 | exists |
直接节点 | 存在节点(值非 null 且非 undefined;空字符串 ""、0、false 均视为「存在」——「存在」≠「非空字符串」) |
| 17 | not_exists |
not 组合 | not(exists(...)) |
| 18-22 | length_gt/gte/lt/lte/eq |
组合 + exists 守卫 | exists(field) AND length(field) 比较 n |
| 23 | between |
直接节点 | 范围节点(仅数值) |
| 24 | not_between |
not + exists 守卫 | exists(field) AND not(between(...)) |
| 25-28 | count_gt/gte/lt/lte |
组合 + exists 守卫 | exists(field) AND aggregate(count(...)) 比较 n |
| 29 | within |
时间修饰 | 时间窗口(as_of 由引擎注入) |
| 30 | rate |
时间修饰 + 聚合 | 速率限制(temporal_state) |
exists 守卫(E11 空值传播的编译层保障):not_*(除 not_exists)与 length_*/count_* 组合派生 MUST 编译为 exists(field) AND <派生表达式>,而非裸 not(正向算子) 或裸 length/count(...) 比较。原因:正向算子对缺失字段返回 false、length(缺失) 返回 0,若直接 not 翻转或数值比较,空值传播被破坏(fail-open)。not_exists 是唯一例外——语义即「感知字段缺失」,保持裸 not(exists(...))。
有状态算子(within/rate):within 与 rate 是仅有的两个有状态算子,其求值依赖跨决策的滑动窗口内计数。这一状态不存储在表达式树节点中,而由独立的 Guard 状态管理器维护,以 temporal_state 字段进入审计记录——表达式树本身仍是纯函数(E1 成立),状态源可审计、可重算。
有状态算子真值语义(MUST):计数达阈值 → 条件成立(触发);未达阈值 → 记录本次事件并返回 false(放行)。
| 算子 | 阈值 | 首次/未超限 | 达阈值后 |
|---|---|---|---|
rate: "N/窗口" |
N | 前 N 次:record + false | 第 N+1 次起:true |
within: "窗口" |
1 | 首次:record + false | 窗口内第 2 次起:true(去重) |
配套约束(均 MUST):① 计数后置(仅当正向条件成立才计数);② 计数隔离键(within 以 field+operator+value+scope 为键,rate 以 field+operator+value+rate+scope 为键;scope 为计数主体作用域——按哪个字段分组(如 user.id/tool.name),避免「不同主体共享一个计数器」的全局计数;缺省为全局不分组);③ record 时机在「未超限」分支写入。
Expression 开放完整内核表达力,面向复杂业务规则(tier ≥3)。语义内核是一棵类型化表达式树,由 34 个节点构成(归为 10 组),节点集冻结于 [FREEZE-2]:
| 组 | 节点 | 语义能力 |
|---|---|---|
| 取值 | field · var · 字面量 | 引用字段、上下文变量、常量(var 仅 $/$.path,不得(MUST NOT)读时钟与随机) |
| 逻辑 | and · or · not | 组合关系 |
| 比较 | eq · ne · gt · gte · lt · lte | 操作数可为字段、变量、字面量或算术子树 |
| 集合 | in | 标量属于集合 |
| 字符串 | contains · match · starts_with · ends_with | 模式匹配;match 走安全正则 |
| 存在/量纲 | exists · length · between | 存在性、长度(Unicode 码点)、闭区间 |
| 量词 | all · any · none | 数组逐元素判定;空数组一律 false |
| 算术 | add · sub · mul · div · round | 定点小数确定性运算 |
| 时间 | days_between · epoch_ms · date_add · date_part · month_last_day | 日期差、时间戳、日期推演、分量提取、月末取日 |
| 聚合 | aggregate(count/sum/avg/min/max) | 数组聚合 |
节点总数:取值 3 + 逻辑 3 + 比较 6 + 集合 1 + 字符串 4 + 存在/量纲 3 + 量词 3 + 算术 5 + 时间 5 + 聚合 1 = 34。「比较」6 运算符、「字符串」4 运算符、「算术」5 运算符、「量词」3 种类、「聚合」5 函数在实现中分别以参数化节点类型承载,故「34 个语义节点」在代码中映射为更少的类型字面量——二者是语义节点与类型投影的关系,非数量矛盾。
字符串规范化扩展节点(extension profile):除上述 34 节点冻结内核外,内核扩展 profile 提供字符串规范化节点(单目
string_transform,3 运算符):casefold(Unicode 简单大小写折叠,确定性、locale 无关)、trim(去除首尾 Unicode 空白)、path_normalize(POSIX 词法路径规范化:统一\//分隔符、折叠连续分隔符、词法解析./..、保留前导/)。三者均为纯字符串变换(字符串 → 字符串),非字符串输入返回null+type_mismatchwarning(安全折叠)。它们不进 34 节点冻结内核(与 fn 节点同定位),但有确定 S-expression 形态(§8.2.1)进哈希。
名单外置节点(extension profile):
in_set{ref,digest}(in的名单外置变体,单目in_set)——对大名单(如反洗钱国家/制裁名单 >256 项)做成员判断,不进 34 节点冻结内核。ref为名单引用标识,digest为名单内容的版本化哈希(sha256:hex(JCS(values))),名单本体由求值上下文注入(resolveSet(ref)),不进表达式树、不受in的 256 内联上限约束;digest不匹配或ref未注册时 fail-closed(not_ruleable)。成员语义与in相同(字符串 NFC 比较,见 §7.3(a)),有确定 S-expression 形态(§8.2.1)进哈希。
表达式书写示例:
when:
expr:
lt:
- div:
- sub: [{ field: "tool.args.price" }, { field: "context.cost" }]
- field: "tool.args.price"
- 0.15
决策表面向业务与财务人员,以行列结构表达多条件组合,编译到同一内核:
kind: decision_table
columns:
- field: "context.amount"
label: "申请金额"
rows:
- when: [["gte", 10000]]
then: "REQUEST_HUMAN"
priority: 1
- when: [["gte", 5000]]
then: "ESCALATE"
priority: 2
- when: [] # 默认行(无条件命中,兜底)
then: "ALLOW"
priority: 3
编译规则(E7):① 每行 when 条件组按字段列序编译为逻辑与(and),条件单元编译为比较节点;② 行序即优先级(自上而下首个命中,与 priority 一致,二者不得(MUST NOT)冲突);③ 默认行 when: [] 编译为字面量 true;④ then 值 MUST 属于 §6 决策类型枚举;⑤ 编译后产生与手写 Simple/Expression 相同的表达式树。
决策表是 rules[] 的语法糖(MUST):决策表逐行展开为规则——每行一条规则(then 对应每条规则的 action.decision),priority/ring/override/gloss/message 等字段按行归属;展开后的规则集与手写 rules[] 语义等价(E7)。行序即优先级,与 priority 一致;缺省 priority 时按行序(1, 2, 3, …)自动编号,默认行(when: [])MUST 排在最后且 priority 最大。
gloss 是从树确定性生成的自然语言表述:
gloss: "当(售价 减 成本)除以 售价 小于 15% 时,需人工审批" # 引擎生成,lint 强制一致
五条不变量(全部 MUST):
| # | 不变量 |
|---|---|
| G1 | gloss = render(树):由冻结渲染模板确定性生成 |
| G2 | 每条规则与每条转移规则 MUST 携带 gloss,lint 校验 gloss == render(树),禁手写 |
| G3 | gloss 有字段契约时 MUST 用 Entity 的 display_name(中英双语字段,gloss 取英文值);无契约时回退原始字段路径(event.* payload 键除外,见 §5.5)——使 lint 校验 gloss == render(树) 在无契约时仍可复现 |
| G4 | gloss 为渲染产物(不进哈希),展示时实时 render(树) 呈现 |
| G5 | Simple 规则同样生成 gloss(编译为树后渲染)——阅读层不分层 |
gloss 渲染模板(逐节点,英文为 canonical;中文模板为展示层可选投影,不参与跨实现验证;{A}/{B}/{C} 为子表达式递归渲染结果):
| 节点 | 中文模板 | English template |
|---|---|---|
field |
{field} |
{field} |
var |
$ 或路径 |
$ or path |
literal |
{value} |
{value} |
and |
{A} 且 {B} |
{A} and {B} |
or |
{A} 或 {B} |
{A} or {B} |
not |
非({A}) |
not ({A}) |
eq |
{A} 等于 {B} |
{A} equals {B} |
ne |
{A} 不等于 {B} |
{A} does not equal {B} |
gt |
{A} 大于 {B} |
{A} is greater than {B} |
gte |
{A} 大于等于 {B} |
{A} is greater than or equal to {B} |
lt |
{A} 小于 {B} |
{A} is less than {B} |
lte |
{A} 小于等于 {B} |
{A} is less than or equal to {B} |
in |
{A} 在 {B} 中 |
{A} in {B} |
contains |
{A} 包含 {B} |
{A} contains {B} |
match |
{A} 匹配 {B} |
{A} matches {B} |
starts_with |
{A} 以 {B} 开头 |
{A} starts with {B} |
ends_with |
{A} 以 {B} 结尾 |
{A} ends with {B} |
exists |
{A} 已发生 |
{A} exists |
length |
{A} 的长度 |
length of {A} |
between |
{A} 在闭区间 {B} 到 {C}(含两端) |
{A} is in the inclusive range {B} to {C} |
all |
{A} 中所有元素满足「{B}」 |
all elements in {A} satisfy "{B}" |
any |
{A} 中至少一个元素满足「{B}」 |
at least one element in {A} satisfy "{B}" |
none |
{A} 中没有元素满足「{B}」 |
no elements in {A} satisfy "{B}" |
add |
{A} 加 {B} |
{A} plus {B} |
sub |
{A} 减 {B} |
{A} minus {B} |
mul |
{A} 乘 {B} |
{A} times {B} |
div |
{A} 除以 {B} |
{A} divided by {B} |
round |
{A} 四舍五入 |
{A} rounded |
days_between |
{A} 与 {B} 之间的天数 |
days between {A} and {B} |
epoch_ms |
{A} 的时间戳 |
epoch ms of {A} |
date_add |
{A} 加 {B} {unit} |
{A} plus {B} {unit} |
date_part |
{A} 的 {part} |
{part} of {A} |
month_last_day |
{A} 所在月的最后一日 |
the last day of the month of {A} |
aggregate(count) |
{A} 的元素个数 |
count of {A} |
aggregate(sum) |
{A} 之和 |
sum of {A} |
aggregate(avg) |
{A} 的平均值 |
average of {A} |
aggregate(min) |
{A} 的最小值 |
minimum of {A} |
aggregate(max) |
{A} 的最大值 |
maximum of {A} |
state.* 的 gloss 渲染(G3):路径首段为
state的 field 节点,渲染state.<name>的display_name(en,G3,§6a.1),缺省回退状态变量名;exists布尔特例**不适用**于状态字段(状态枚举值非布尔,exists(state.x)恒为「变量已声明且始终有值」语义,不因枚举值而变)。
*event. 的 gloss 渲染(G3)**:路径首段为
event的 field 节点(仅出现在transitions[].when,§6a.7),其四个保留字段渲染固定英文可读名:event.event_id→ event id、event.on→ event name、event.actor→ event actor、event.at→ event time;payload 键event.<key>无 display_name,渲染时直接用键名(裸路径)。
exists渲染(MUST):exists统一渲染为{A} 已存在(中文)/{A} is present(英文)——MUST NOT 对is_*/has_*字段做「is true」特例:exists的语义是「字段存在」(§5.2.3,false 也算存在),渲染为「is true」会把exists(has_consent)(值为 false 时)误导为「has_consent 为真」。
gloss 渲染细节(跨实现须精确复现):
not(eq({A},{B}))规范化为ne模板({A} does not equal {B}),而非字面嵌套not ({A} equals {B});- 字符串字面量带引号渲染(
"rm"),list 字面量的字符串成员带引号(["a", "b"]);- 算术节点(
add/sub/mul/div)带括号渲染((a plus b)),以在自然语言阅读中保留运算符优先级。
then 的值 MUST 属于以下 13 种决策类型:
| # | 决策类型 | 语义 |
|---|---|---|
| 1 | ALLOW | 放行 |
| 2 | DENY | 拦截 |
| 3 | CORRECT | 纠正偏差 |
| 4 | NOTIFY | 通知 |
| 5 | REQUEST_HUMAN | 请求人工裁决 |
| 6 | ESCALATE | 升级 |
| 7 | DELEGATE | 委派 |
| 8 | DEFER | 延期 |
| 9 | EMERGENCY_HALT | 紧急停止 |
| 10 | ROLLBACK | 回滚 |
| 11 | QUARANTINE | 隔离 |
| 12 | WORKFLOW | 工作流(状态机;子态 WORKFLOW_WAITING / WORKFLOW_PROGRESS;注意:与 §6a 授权状态机不同) |
| 13 | GUIDE | 引导 |
为什么定义 13 种决策类型(设计说明):
传统 IT 的规则引擎与访问控制,决策是二元的——ALLOW(放行)或 DENY(拒绝)。它假设决策者是「系统」,系统只需回答「放行还是拒绝」。
但 AI 时代的决策对象是一个有理解力、却不完全可靠的 LLM。面对 LLM 的产出,简单的「拒绝」是放弃——它丢弃了 LLM 已完成的全部工作,也放弃了「纠正一次、继续前进」的可能。本规范的设计思想是:确定性规则不是为了拒绝 LLM,而是为了把 LLM 的价值发挥到最大,同时守住安全底线。
13 种决策类型正是这一思想的展开,分为五类:
| 类别 | 决策类型 | 含义 |
|---|---|---|
| 放行与拦截 | ALLOW / DENY | 二元底线:明确安全就放行,明确越界就拦截 |
| 引导而非放弃 | CORRECT / GUIDE | LLM 产出有偏差时,纠正或引导它回到正确方向,而不是丢弃整个产出 |
| 人机协同 | REQUEST_HUMAN / ESCALATE / DELEGATE / DEFER | 不确定时引入人工裁决、升级、委派或延期,把「机器搞不定」交给「人或流程」 |
| 安全兜底 | EMERGENCY_HALT / ROLLBACK / QUARANTINE | 危险时果断干预:紧急停止、回滚已发生的副作用、隔离可疑对象 |
| 过程性 | NOTIFY / WORKFLOW | 通知(记录而不阻断)、工作流(进入多步骤状态机) |
一句话:传统 IT 问「放行还是拒绝」,ERDL 问「如何让 LLM 在受控中做得更好」。 DENY 是最后的手段,而非唯一的手段。
委托授权、撤销、审批流等会话级状态需要显式声明。本节定义状态空间(state)与状态转移规则(transitions),把 §5.2 的 within/rate「树外计数状态」泛化为命名授权状态。状态本体由引擎维护(表达式树内核之外),表达式树内核(34 节点)不新增节点——状态经 state.* 受控注入只读访问。
硬边界:状态层是有限状态机(FSM),不是图灵机。状态空间 MUST 有限(枚举 + 资源上限),拒绝任意长度历史、递归、下推栈。超过 FSM 的表达(事件溯源)一律不进入引擎。
分层边界(单实例):状态变量是文档实例级单键——一个状态变量在当前 ERDL 文档实例内持有单一值。per-实体/per-授权关系的多实例状态(如 P→A 与 A→B 的授权各需一个状态),由组织层为每个关系实例化一个文档承载,超出本节范围(分层:组织层消费 ERDL 原语,本状态块是单实例 FSM 的原语)。
state:
- name: authorization # 状态变量名,文档内唯一
values: [authorized, revoked] # 枚举值(2–4 个字符串,值内唯一)
initial: authorized # 初始状态(MUST 属于 values)
display_name: { zh: 授权状态, en: authorization state } # 可选,gloss 可读名(G3)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string | MUST | 状态变量名:非空 ASCII 标识符([A-Za-z_][A-Za-z0-9_]*),MUST NOT 含 .(避免与 field 点路径解析冲突;限 ASCII 使键序在 UTF-8 码点与 UTF-16 码元下无分歧),MUST NOT 为保留字 state,文档内 MUST 唯一;state.<name> 命名空间 |
values |
array | MUST | 枚举值列表:2–4 个非空 ASCII 标识符字符串,MUST NOT 含 .,值内 MUST 唯一;字符串 MUST NFC 规范化(E10)后参与比较 |
initial |
string | MUST | 初始状态,MUST 是 values 之一 |
display_name |
object | MAY | 双语可读名 { zh, en }(同 Entity 约定,G3);gloss 渲染 state.<name> 取 en 值,缺省回退 name |
状态变量在文档加载时即以
initial初始化,始终有值——不存在「运行时缺失」。需要表达「授权尚未建立」(unavailable)时,MUST 在values中显式声明一个哨兵值(如unestablished),并以其为initial,而非依赖空值传播。
转移规则是状态机的 F(转移函数),确定性、仅由事件触发:
transitions[] 子字段顺序 MUST 固定为:on → name → audit_as → reason → enabled → when → gloss → set(name 为可选字段,MAY,格式同 §4.1;gloss 为渲染产物,MUST,见下方 gloss 条款)。
transitions:
- on: revoke # 触发事件名(受控注入的唯一入口)
name: "TR-001-revoke" # 可选,规则标识(格式同 §4.1)
audit_as: DELEGATE # 审计承载(仅审计,不参与求值/短路)
reason: revoke # 转移语义标识(区分委派与撤销)
enabled: true # 启用标志(默认 true)
when: { ... } # 可选守卫(表达式树,只读 state.* 与 event.*,不读自由 context)
set:
authorization: revoked # 状态转移:state.authorization ← revoked
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
on |
string | MUST | 触发事件名(受控注入,见 §6a.3);MUST 匹配 [a-z][a-z0-9_]{0,31}(事件名进审计与哈希,自由 Unicode 徒增规范化负担) |
name |
string | MAY | 转移规则标识(格式同 §4.1 的 [CAT]-[NNN]-描述);缺省时错误归因用 (on, reason, 定义序号) |
audit_as |
string | MAY | 审计分类标签(仅审计,见 §6a.5):缺省为 NOTIFY;取值收窄为 {ALLOW, NOTIFY, DELEGATE, ESCALATE, REQUEST_HUMAN}(借用 then 词表,但语义为审计分类而非求值决策——本字段不参与 §7.0.2 求值、不触发任何短路),MUST NOT 取拦截性类型(DENY/EMERGENCY_HALT 等) |
reason |
string | SHOULD | 转移语义标识(如 revoke);MUST 匹配 [a-z][a-z0-9_]{0,31},同文档内 SHOULD 唯一(lint) |
enabled |
boolean | MAY | 启用标志(默认 true);false 时该转移规则不参与事件处理(灰度/应急关闭,免改文档) |
when |
object | MAY | 守卫条件(编译为表达式树,只读 state.* 与 event.*,MUST NOT 读自由 context,见 §6a.7) |
gloss |
string | MUST | 引擎从 when 树渲染的自然语言可读投影(§5.5);lint 校验 gloss == render(树),禁手写;不进哈希(G4) |
set |
object | MUST | 状态转移映射;键是状态变量名,值 MUST 是该变量 values 之一 |
transitions 的 gloss(G2,MUST):
transitions[].when同rules[].when,其守卫表达式树同样由引擎渲染 gloss(§5.5),lint 校验gloss == render(树),禁手写;state.*/event.*的渲染见 §5.5。转移规则的 gloss 仅作用于审计可读性(不进哈希,同 G4),不改变求值语义。
on 事件为原子事务——守卫按 transitions 定义顺序逐条求值(同一事件前快照);遇到第一个 EvaluationError 即停止,不执行任何 set(fail-closed),记 transition_error 审计事件,其 error 记该条(第一个出错的)规则的错误,errored=true 口径同 E3;type_mismatch 类 warning 仅按 §7.3(a) 折叠语义处理(errored=false、非错误、不停止求值),但不记录——转移审计记录无 warnings 字段(§6a.5);全部守卫通过后,一次性提交全部 set。transitions[].when MUST NOT 使用 within/rate(加载时 Error)——转移求值无副作用计数,与「转移不产生计数」的纯性一致,避免「事件是否计一次数」的歧义。transitions[].when 的节点集 MUST 为:Simple 条件运算符(§5.2 的 28 条件运算符)+ 逻辑节点(and/or/not)+ 时间节点(epoch_ms/days_between/date_add/date_part/month_last_day,供新鲜度时间比较,§6a.7)+ field/literal;MUST NOT 使用:量词(all/any/none)、算术(add/sub/mul/div/round)、聚合(count/sum/avg/min/max)、fn(函数委派)、within/rate(有状态算子)。其中 fn 本就不在转移守卫可编译范围内(Grade C 兜底,非内核),此禁为显式防御,避免实现者误将 fn 引入守卫。资源上限按 E4 Grade A 配额(算术深度≤2 / 树深≤6 / 节点≤64);转移守卫求值错误不适用 E12 分 tier 折叠,一律按 §6a.2 原子 fail-closed(EvaluationError → 不提交任何 set,§6a.5)。when 中 state.* 读的是事件到达时刻的状态快照(即本事件所有转移生效前的状态),不是转移后的中间态——保证守卫判定与转移结果解耦、确定性。error 记第一个出错的规则),因此定义顺序属哈希语义——实现 MUST NOT 依赖定义顺序改变 set 结果,但 MUST 按定义顺序归因错误。evaluate)时执行——两者分离,避免求值产生副作用。evaluate();evaluate() 与事件处理在实例锁下互斥,故 evaluate() 启动时所读 state.* 必为「全部已到达事件提交之后」的状态;event_id 重复到达只处理一次,重复项丢弃并记链外日志(不进哈希链);injectEvent 返回显式回执——committed(已提交转移)、noop(无匹配转移或守卫不满足,状态不变、无审计记录、不递增 state_version)、rejected(超限 payload / 重复 event_id / 认证失败 / 守卫错误,链外记录)。调用方 MUST 据此判断,MUST NOT 把 noop 误认为「已撤销」——撤销类(reason 为 revoke 语义)的 noop MUST 写一条链外告警记录或链上摘要记录,避免运维误以为已撤销。(on, 状态变量) 下,对同一状态变量 set 到不同值的多条 enabled 转移规则构成确定性冲突。加载器 MUST 完成以下确定性检查,宁拒勿纵(set 到相同值的规则幂等,不构成冲突,免检查):
when 省略,或编译产物为字面量 true 节点——实现 MUST NOT 做超出字面量的常量折叠,避免跨实现分歧),则其 MUST 是该 (on, 状态变量) 下唯一规则;与其他任何规则共存即 Error;logic: AND 的 conditions 元素(单条件时即顶层合取项);expr 形态 = 顶层 and 节点的直接子节点;嵌套于 or/not 下的条件一律不得作为证明依据(视为不可证明);精度规则(MUST):Simple 形态 logic: OR 时,整个条件组视为单个顶层合取项(OR 的子条件不构成合取项),因它不是 eq 合取项,两两证明一律落入 (3) 视为不可证明——除非 set 同值(幂等免检查);state.<v> 的顶层 eq 合取项且常量不同;(b) 两条规则均有同一字段的顶层 eq/in 合取项且常量集合交集为空(eq 视为单元素集合);enabled: false 的规则不参与检查(运行时亦不生效),但与 enabled 规则冲突时 lint SHOULD 警告——否则 enable 瞬间即违规。transitions 数量上限(§6a.4),两两检查为 O(n²) 有界开销。audit_as 一致性(可判定,MUST):同一 on 事件名下的全部 enabled 转移规则,其 audit_as MUST 相同(同一事件类型 = 同一审计姿态);不一致即加载时 Error。依据:成功转移记录以「事件」为粒度(§6a.5)——一个事件 = 一条记录、一个 audit_as 位;若同事件内各规则 audit_as 不同,记录无法唯一取值,两个实现会给出不同答案,构成审计链分叉。enabled: false 的规则不参与检查,但与 enabled 规则 audit_as 不同时 lint SHOULD 警告。set 的值 MUST 属于该变量的 values;转移方向(如 authorized → revoked)由 values 枚举 + set 声明共同决定,引擎只执行声明的转移,不推断未声明的转移(fail-closed)。rules[].when、rules[].unless、transitions[].when、决策表单元、transitions[].set 的键)引用 state.<name> 且 <name> 未在 state 声明;"state"(无路径段)——单独 state 无状态变量名,无法解析,拒绝;transitions[].when 引用自由 context(§6a.7);rules[].when / rules[].unless / 决策表单元引用 event.*——event 命名空间仅存在于转移求值上下文(§6a.7),规则求值(§7.0.2)中无当前事件;引用即加载时 Error(否则按 E11 恒 false 静默死规则,正是 E11 要防的 fail-silent)。transitions 的 set 更新,外部(context / 调用方)不能直接写。state.<name> 命名空间只读注入 context——与 context 字段严格隔离(context 外部可写,state 引擎持有)。state 时进入受控状态命名空间(如 state.authorization)。context.state.*、tool.state.* 等首段非 state 的路径仍按 context 解析——但 lint SHOULD 警告,避免审阅者误判。state 是保留命名空间:context 顶层字段 MUST NOT 使用 state 作为键,避免与状态快照冲突;field 路径恰为 "state"(无路径段)是加载时错误(§6a.2)。state.<name> 复用现有 field 节点(resolveField 时 state. 前缀走受控状态读取)。initial 保证),不存在「缺失」语义;引用未声明的状态变量是加载时错误(§6a.2)。state.* 的求值语义与 context 字段完全一致(§7.3 全部适用)——gt/between/length 等对状态值的判定与对 context 字段相同:length(state.x) 对枚举字符串按码点长度,between 对非数值恒 false,字符串比较走 E10 码点序。唯一差异:声明变量不存在缺失(E11 永不触发)。as_of/temporal_state 同一受控注入机制(E1)。状态空间 MUST 有限,超限拒绝加载(Error):
| 维度 | 上限 | 依据 |
|---|---|---|
状态变量数(state 数组长度) |
≤ 4 | 授权/新鲜度/审批/任务 四类常见状态 |
单个状态变量 values 枚举值 |
2–4 | 撤销 2 态(valid/revoked)、授权/审批/任务最多 3–4 态 |
| 状态组合空间(∏ values 长度) | ≤ 4⁴ = 256 | 防组合爆炸 |
转移规则数(transitions 数组长度)/ 文档 |
≤ 32 | 约束两两互斥检查(§6a.2)为 O(n²) 有界开销 |
distinct on 事件名数 |
≤ 16 | 事件名空间有限,防无限膨胀 |
事件 payload |
≤ 8 键 / 深度 ≤ 2 / 单值 ≤ 256B | 受控负载,防大负载(§6a.7) |
委托链深度(per-实体多实例的 hop 数)属组织层约束,不属单实例 FSM 状态空间维度,不在本节资源上限内(§6a 分层边界)。
状态在内核外 ≠ 状态不可审计。审计靠三环闭合 + 密码学链接环,缺一不可:
| 环 | 审计什么 | 机制 |
|---|---|---|
| ① 转移链 | 状态从 authorized→revoked 的每一步 | 转移事件 = 转移审计记录(audit_as 映射到 §6a.2 收窄取值),走 previous_hash 串行锚定 |
| ② 快照 | 求值时读到的状态值 | state_snapshot 进 DO,进哈希原像(§7.0.3) |
| ③ 合法性 | 只有转移规则能改状态,方向合法 | 引擎验证转移(fail-closed),未声明的转移不执行 |
state_snapshot 结构state_snapshot 结构(出处锚定,MUST):state_snapshot 为:
{ values: { <状态变量名>: <值> }, # 本次求值实际读到的状态变量(on-demand,非全量)
state_version: <uint>, # 初始为 0(即 genesis);每个成功提交的事件事务 +1
transitions_head: <hash> } # 最近一次成功改变状态的审计记录的哈希;初始为 genesis 哈希
三者一并进 DO 哈希原像(§7.0.3)。state_version 与 transitions_head 把快照锚定到转移链:
set 合并为一次递增;初始为 0(即 genesis)。transition_error 记录(EvaluationError 时,§6a.2)沿 previous_hash 链接入链(保证链完整),但不应用 set、不递增 state_version、不移动 transitions_head。doc_tree_hash 定义重算目标文档的文档级哈希,与链上 genesis 记录的 doc_tree_hash 比对,不一致即判定该链不属于本文档(拒绝,防跨文档链移植);第 1 步(遍历)——从 genesis 沿 previous_hash 遍历全链,对成功记录应用其 set 并递增计数;当计数 == state_version 时,重放得到的全状态中,凡 state_snapshot.values 里出现的状态变量,其值 MUST 等于 values[变量],且当前记录哈希 MUST 等于 transitions_head;error 记录仅遍历、不应用、不计数。state_snapshot 序列化规范化values 的键按状态变量名 RFC 8785 JCS 键序(UTF-16 码元升序)排列,序列化为 JSON object;temporal_state、state_snapshot、canonical_trees 的先后次序)见 §8.2a。两个实现若键序、编码、字段序任一不同,会算出不同 DO 哈希,违背「语义=树=哈希」的核心承诺——故三者 MUST 规范化。
事件注入认证(MUST):事件注入 MUST 经引擎认证——actor 身份(§6a.7)进转移审计记录;未认证事件 MUST 拒绝(fail-closed)。任意调用方不得注入 revoke/authorize 事件。
事件认证证据(SHOULD):已认证事件 SHOULD 携带可验证的认证证据——签名或证明摘要(如 JWS 的 kid + 摘要),一并写入转移审计记录;actor 身份进链时,其认证证据 SHOULD 进链,使「由谁批准」可独立验证(堵住 actor 仅字符串可伪造的缺口)。注:当前链内仅记录 actor 字符串——§8.2a.2 记录字段序尚无证据字段——故「由谁批准」暂不可独立验证。
audit_as 不构成人工批准证据:
audit_as仅是审计标签,不携带任何批准证明;攻击者可注入actor: human-1的伪造事件。人工批准的唯一可审计形态 = 认证身份层以 human 身份注入事件(actor进链)——audit_as: REQUEST_HUMAN不意味「本条转移即人工批准」。
成功转移记录(链上一等记录,MUST):成功提交 set 的转移生成一条成功转移记录,格式为:
{ type: "transition", event_id, on, actor, at, audit_as, set, state_version, previous_hash }
type 固定为 "transition";audit_as = 该 on 事件统一的审计承载值(§6a.2 收窄取值 + 同事件一致性校验),进哈希原像;set = 本事务全部规则 set 的合并映射 { <状态变量名>: <值> },键按状态变量名 RFC 8785 JCS 键序(UTF-16 码元升序)(同变量同值幂等合并已在冲突检查中保证无歧义);state_version = 本次事务提交后的版本号(= 上一版本 +1);previous_hash = 链上前一条记录(genesis 或更早的 transition/transition_error)的哈希;transition_error 记录(链上一等记录,MUST):transition_error(EvaluationError 时,§6a.2)为链上一等记录,格式为:
{ type: "transition_error", event_id, on, actor, at, audit_as, error, errored: true, previous_hash }
audit_as = 该 on 事件统一的审计承载值(§6a.2 收窄取值 + 同事件一致性校验);error = 第一个(按 transitions 定义顺序)EvaluationError 的规则的错误描述(§6a.2 事件处理原子性:遇到第一个即停止);previous_hash 链接入链(保链完整);set、不移动 transitions_head、不递增 state_version(口径同重放验证);set/state_version 字段,errored 恒为 true;完整字段序与固定键集见 §8.2a。守卫 warning 不记录(MUST):转移守卫的 type_mismatch warning 仅影响求值折叠(§7.3(a),
errored=false),不记录——成功/错误转移记录均无 warnings 字段;守卫的审计重点是转移结果(set是否提交),非求值过程警告。
被拒事件落点(MUST,链内链外分离):未认证、重复 event_id、超资源上限的事件 MUST NOT 进入哈希链;在链外审计存储中记录(actor 若已知、事件名、event_id、拒绝原因,内容消毒后落盘)——链内只留可信记录,链外留入侵痕迹。这保证:审计员在链内看到的是可信状态转移全史,在链外看到注入尝试 / 重放 / 超限攻击痕迹,两者互不污染。
genesis 记录(MUST):文档加载时 MUST 为 initial 生成 genesis 审计记录(initial 快照 + 文档规范树哈希)。这是转移链 ① 的起点——一份 initial=authorized 的文档,链上可回答「它凭什么一开始是 authorized」。
两种加载入口(MUST):① create(首次创建实例)——生成 genesis(含 instance_id,见下),写新链;② restore(从既有链恢复)——MUST 校验最新权威头(§6a.9),失败则 fail-closed(拒绝加载,MUST NOT 把状态重置为 initial)。实现 MUST 区分二者,MUST NOT 把 restore 按 create 处理——否则重启会把状态重置为 initial,若 initial 是授权态则撤销被绕过。
genesis 原像字节级定义(MUST):
doc_tree_hash = sha256(JCS({ name, state, transitions }))——状态机文档级 canonical 形式(区别于 §8.2 的表达式树级 canonical)。doc_tree_hash 锚定状态机身份(state + transitions),不包含 rules——rules 的版本溯源由 DO 层 rule_set_version.id(RFC-002 §2.3)承担,两者各司其职:改 state/transitions 变 doc_tree_hash(防跨文档链移植),改 rules 变 rule_set_version.id(防规则集漂移),互不重叠。rule_set_hash 绑定(MUST):doc_tree_hash 不含 rules,状态机同名同构而规则不同的文档可共用同一条链——状态值(如 revoked)的含义取决于消费它的规则。因此 DO 层的 rule_set_hash(§8.2a.1a,规则语义全集哈希)与 doc_tree_hash 共同锚定「状态机 + 规则」:复算/验证时二者 MUST 一致,否则判定该链/决策不属于当前(状态机, 规则)组合。metadata.name 不是安全边界——同名不代表同规则,身份绑定以哈希为准。
{ type: "genesis", instance_id, protocol, doc_tree_hash, initial: {变量名码点升序}, at, previous_hash: null },其中 instance_id 为实例标识(同一文档的不同实例 MUST 用不同 instance_id,使各自 genesis 哈希不同,防链跨实例移植;必要时含关系绑定:授权方、被授权方、范围摘要)。doc_tree_hash 原像字段序与固定键集(MUST,逐字段钉死):
name → metadata.name(文档名,身份标识)
state → [ { name, values, initial } ](状态空间声明;不含 display_name)
transitions → [ { on, name, audit_as, reason, enabled, when, set } ](转移规则;不含 gloss)
null 且键不省略(固定键集合);字符串 NFC(E10)、数字 JCS(§8.2 编码口径)。transitions[].enabled 缺省时编码 true(§6a.2 默认 true,非 null);state[].initial 缺省时编码 null(§6a.1 initial 为 MUST 字段,缺省即非法文档,加载时已拒;此处仅定义缺席编码以防实现分歧)。transitions[].when 存编译后的 S-expression(§8.2 树级 canonical),非源 YAML——保证 Simple 与 Expression 书写同语义同哈希(E7);缺席(无条件转移)编码 null。state[].display_name、transitions[].gloss、metadata 除 name 外的字段(description/category/decision/tags)。其中 initial 的键按状态变量名 RFC 8785 JCS 键序(UTF-16 码元升序);previous_hash: null 键不省略(固定键集合)。三类记录的完整字段序与固定键集见 §8.2a。
initial(genesis 记录)与state_snapshot.values(§7.0.3)虽同为「状态变量 → 值」映射,但分属 genesis 记录与 DO 求值结果两个不同原像,字段名各自固定——initial表达初始态、values表达求值读到的当前态;实现者 MUST 按各自字段名序列化,不得(MUST NOT)混用。
规范性指引(SHOULD):承载授权语义的文档,initial SHOULD 取最保守哨兵值(unestablished/revoked);显式 bootstrap 授权必须走一次有 actor 的转移事件,留下链上出处,而非凭空 initial=authorized。
并发语义(per 文档实例 MUST 串行化):同一文档实例上的事件处理与 evaluate() MUST 互斥;evaluate() 看到的 state_snapshot 为求值开始时刻的一致快照,禁止中间态。
第 ③ 环是快照式(状态外部物化)做不到、也是显式状态机的核心:引擎验证「状态是怎么合法变到这一步的」,而不是照单全收外部状态。
within/rate 是本节 FSM 的极窄特例(状态=计数、转移=+1、输出=超限)。状态块是它们的泛化,不改变其既有语义。两者并存:within/rate 仍由 temporal_state 受控注入,状态块经 state.* 受控注入。状态块与 within/rate 计数器互不共享命名空间:状态块用 state.<name>(由 §6a.1 state 声明驱动),within/rate 用 field+operator+value(§5.2 计数隔离键)——两者键隔离规则各自独立,互不冲突。
转移规则的守卫求值同样收敛进「受控注入」模型(E1),不读自由 context。事件对象、守卫上下文、编译口径 MUST 如下:
事件对象(Event),结构 MUST 为:
event:
event_id: "evt-2026-09-12-0001" # 文档内唯一,重放去重依据
on: revoke # 事件名(匹配 transitions.on)
at: "2026-09-12T10:00:00Z" # 引擎注入的 UTC 时刻(转移求值的 as_of)
actor: "agent-A" # 已认证的事件源标识
payload: { ... } # 受限负载(见下)
| 字段 | 类型 | 说明 |
|---|---|---|
event_id |
string | 文档内唯一;重放去重依据(同一 event_id 只处理一次) |
on |
string | 事件名,匹配 transitions.on |
at |
string | 引擎注入的 UTC 时刻(转移求值的 as_of);MUST NOT 由外部提供(E9 禁墙钟同样适用) |
actor |
string | 已认证的事件源标识(身份层提供,非 payload 自证);字符串 MUST NFC 规范化(E10,进转移审计记录哈希原像) |
payload |
object | 受限负载:≤8 键、深度 ≤2、叶子值标量、单值 ≤256B;键 MUST NOT 含 . 且 MUST NOT 为四个保留字段名(event_id/on/actor/at) |
*event. 解析机制(MUST)**:
event.* 与 state.* 同样复用 field 节点首段拦截(resolveField 时首段为 event 走受控事件读取);event.event_id / event.on / event.actor / event.at + payload 键(绑定为 event.<key>);payload 键值可为对象(深度 ≤2),守卫可读 event.<key>.<sub> 嵌套路径(深度 ≤2),解析规则同 context 字段路径(§3);exists/not_exists 可感知缺失);转移求值上下文(when 的输入):when 守卫的求值上下文 MUST 仅为以下两类,MUST NOT 读取自由 context:
state.*(本事件所有转移生效前的状态,§6a.2);event.*(event.event_id/event.on/event.actor/event.at + payload 键 event.<key>;event.at 为引擎注入的 UTC 时刻,即转移求值的 as_of)。守卫读取 state.* 与 event.* 之外的任意字段(自由 context)MUST 在加载时拒绝(Error,见 §6a.2 加载时校验全集)。
编译与求值口径:转移求值 MUST 走与规则求值相同的唯一编译管线(E7);其 warning / errored 口径与 §7.3 完全一致。
时间与新鲜度(无时间触发器,MUST):状态机不含时间触发器——转移仅由事件驱动,状态不会自行过期。需要「新鲜度」状态(如 fresh/stale)时,两种模式二选一:
on: expire 事件,触发 fresh → stale 转移(引擎不维护定时器);when 用 event.at / epoch_ms 比对过期时刻,在事件到达时判定过期(不依赖后台定时器)。示例(授权过期,模式 2):
state:
- name: authorization
values: [authorized, revoked]
initial: revoked
transitions:
- on: exercise
audit_as: ESCALATE
reason: auth_expired
when:
gt:
- epoch_ms: { field: "event.at" }
- epoch_ms: { field: "event.expires" }
set: { authorization: revoked }
引擎不负责「随时间自动过期」——时间只作为事件的受控属性(
event.at)或守卫的时间比较输入存在,与 E9(禁墙钟、as_of 受控注入)一致。到期/额度的承载(MUST):到期、额度等标量约束 MUST 由授权根签发的凭据字段承载(写入链,随 authorize 事件提交),MUST NOT 由调用方 payload 的
event.expires承载——调用方可省略或延长expires,导致过期形同虚设。守卫引用event.expires时,其值 MUST 来自已认证凭据(§6a.5.4 事件认证),而非自由 payload。「每次evaluate前注入exercise事件」是边界义务(集成要求,§6a.8):引擎不自动推进时间,过期判定由边界在每次求值前注入 exercise 事件触发。
§6a 的授权状态由执行边界(Action Guard / 工具调用守卫,§9.1)消费,用于门控安全敏感副作用。evaluate() 是纯函数(E1):它返回决策与 state_snapshot = { values, state_version, transitions_head }(§7.0.3),但不亲自提交被门控的副作用——提交发生在执行边界这一独立组件中,于 evaluate() 返回并释放实例锁之后。
由此留下 check/act 窗口:evaluate() 可能基于 state_version = N 返回 ALLOW,随后一条 revoke 事件在效果落地前提交 state_version = N+1,导致效果在已失效的授权谱系下执行。
执行边界重校验(MUST):对授权依赖 §6a 状态的安全敏感副作用,执行边界 MUST 保证:在「用于授权决策的状态版本」与「受保护效果的提交」之间,任何影响授权谱系的状态变更事件不得提交。合规边界通过以下任一方式满足:
{ state_version, transitions_head },与决策的 state_snapshot 比对;任一不匹配即 fail-closed(不执行——按不可用/过期授权处理,AV-05/AV-10/AV-14 语义);或分层(引擎 vs 边界):引擎 MUST 暴露重校验原语——当前 state_version/transitions_head 可在实例锁下读取——但不执行副作用,也不替边界在效果提交期间持有锁(E1:求值纯、提交在引擎之外)。故 check/act 原子性是执行边界通过「对照引擎快照锚点重校验」来履行的集成义务,而非引擎侧的副作用执行保证。
对抗性合规向量(V-STATE):authorized@N → evaluate(ALLOW@N) → revoke@N+1(效果提交前)→ 尝试执行效果。期望:该效果 MUST NOT 在过期的 ALLOW 下执行;边界重校验并 fail-closed(或等效封闭边界)。这是 AV-05 / AV-10 在执行边界的有状态延续。
§6a.5 的重放验证证明「快照与某个有效的转移链前缀一致」(完整性 / 来源),但不证明「该前缀是当前最新的权威前缀」(新鲜度)。二者 MUST 区分。
回滚攻击(对抗性场景):authorized@N / HN → revoke 提交 @N+1 / HN+1 → 重启 / 恢复 / 副本还原出一个结束于 {N, HN} 的有效历史前缀 → 该前缀重放验证通过(它未被伪造或修改,只是被取代)→ 求值看到 authorization = authorized → 执行边界对同一被还原实例重校验,再次观察到 {N, HN}。结果:已撤销的授权被恢复为可行使,且不违反现有哈希链重放检查。
最新权威头新鲜度(MUST):对授权依赖 §6a 状态的安全敏感副作用,执行 / 恢复边界 MUST 保证:在授权该副作用之前,接受的 { state_version, transitions_head } 是同一文档实例的最新权威状态,而非被后续权威状态取代的历史前缀。合规边界通过以下任一方式满足:
若最新权威状态的新鲜度无法确立,授权行使 MUST fail closed(按不可用 / 过期授权处理,AV-05 / AV-10 / AV-14 语义)。
分层(引擎 vs 边界):哈希链完整性由引擎保证(§6a.5);跨重启 / 恢复 / 副本边界的最新权威头新鲜度,是执行 / 恢复边界通过「外部锚点」履行的集成义务——引擎不维护跨实例的持久 epoch,也不替边界决定恢复策略(E1:求值纯)。持久新鲜度锚点(durable freshness anchor)由组织 / 部署层负责提供;且 MUST 保留 fail-closed 属性:若执行边界无法确立恢复所得的 { state_version, transitions_head } 相对于权威持久化状态足够新鲜,则受保护(authority-bearing)效果 MUST NOT 继续执行——成功重放 / 完整性验证永远不构成「权威仍最新」的充分证据。
对抗性合规向量(V-STATE):authorized@N / HN → revoke 提交 @N+1 / HN+1 → 还原有效历史前缀(结束于 @N / HN)→ 重放验证通过 → 求值受保护操作 → 尝试效果。期望:受保护效果 MUST NOT 使用被取代的授权状态执行;系统 MUST 确立 {N, HN} 仍是最新权威状态、发现其已被取代、或在新鲜度无法确立时 fail closed。
§6a.2 把 authorize / revoke 统一建模为事件触发的状态转移(FSM 的 F 函数),事件注入经 actor 认证(§6a.5.4)。但 actor 认证只证明「谁触发了事件」,不证明「触发者是否有权建立该授权」。若不约束授权状态的「建立/重建」来源,撤销即退化为可逆的本地状态位——被撤销的主体可仅通过触发 authorize / re-authorize 转移使 revoked → authorized,而无需证明新的授权从何而来。
授权根源绑定(MUST):承载授权语义的状态变量,其「使授权可行使」的转移(即 set 使授权变量进入「可行使」值,如 authorized)MUST 有授权根源 provenance——触发该转移的事件 actor MUST 归因于一个有权建立/重建该授权的 principal/authority(授权根,authorization root),而非被授权主体自身。撤销后,revoked → authorized 的 re-authorization MUST 有新的有效授权基础;仅凭本地状态转移(无授权根源 provenance)不得(MUST NOT)使授权重新可行使。被授权的后代/主体 MUST NOT 通过仅触发 authorize / re-authorize 状态转移来恢复自己被撤销的授权。
分层(引擎 vs 边界):引擎暴露「授权建立/重建」转移的可识别标记(reason 语义标识,如 reason: authorize)并将 actor 记录进转移审计链(§6a.5.4 已有);授权根源资格的判定(谁有权建立该授权)是执行边界/组织层的集成义务——§6a 单实例 FSM 不建模 P→A→B 授权链(§6a.1 分层边界),谁有权授权由组织层裁决。执行边界在提交 authorize / re-authorize 事件前 MUST 校验 actor 的授权根源资格;无法确立授权根源即 fail-closed(按不可用/未授权处理,AV-05 / AV-10 / AV-14 语义)。
与组织层不变量的接口:本节的授权根源绑定是组织层委托权威不变量(权威不放大 INV-01、窄化继承 INV-03、传递撤销 INV-04)在单实例 FSM 层的原语支撑——组织层消费「授权建立/重建必须归因于授权根」的原语来保证委派链的 INV 不变量。这些不变量的完整定义见 §6b,超出本节单实例 FSM 范围(§6a.1 分层边界)。
§6a.8–§6a.10 的集成义务以 MUST 陈述,其合规与否取决于执行 / 恢复边界能否读取引擎暴露的状态锚点。为消除「集成义务无接口定义」的悬空,本规范钉死引擎 MUST 暴露的最小重校验 API(接口签名,语言中立;具体宿主语言的类型映射由实现定义,字段语义 MUST 与下表一致):
| 原语 | 签名 | 语义 |
|---|---|---|
get_head |
() → { state_version: uint, transitions_head: hash } |
返回当前文档实例的最新权威头(§6a.9 新鲜度、§6a.8 重校验的对照锚点);在实例锁下读取 |
get_value |
(name: string) → string \| undefined |
返回某状态变量当前值(§6a.3 state.<name> 只读解析的底层原语) |
get_chain |
() → AuditRecord[] |
返回转移链审计记录(genesis / transition / transition_error,§6a.5 三类) |
snapshot |
(read_vars: string[]) → { values, state_version, transitions_head } |
按需状态快照(§6a.5.1 / §7.0.3,进 DO 哈希原像) |
inject_event |
(event: Event) → { record: AuditRecord \| null, disposition: committed \| noop \| rejected, error? } |
注入一个事件(§6a.2.1 原子处理;回执 committed / noop / rejected) |
get_head 是 §6a.8 重校验原语的接口形态:执行边界在提交受保护效果前 MUST 以 get_head() 与决策的 state_snapshot(§7.0.3)比对,不匹配即 fail-closed(§6a.8 方式 1);inject_event 的回执是 §6a.2.1 事件处理原子性的接口形态:rejected(超限 / 重复 / 认证失败 / 守卫错误)与 noop(无匹配 / 守卫不满足)MUST 区分,前者记链外审计、后者静默(§6a.2.1 / §6a.5.4);署名(attribution):委托授权安全不变量(INV-01–INV-05)及相关对抗一致性向量(AV-01–AV-14)由 Ravindra Annam 提出,随后在与 OpenOBA 的技术评审与协作中进一步细化与完善。
§6a 定义单实例 FSM(单个授权关系的状态机);本节定义委派链(多个授权关系沿「授权根 → 中间节点 → 被授权主体」组合)的安全不变量——约束「授权如何沿委派链传播」,是组织行为层的规范性语义。分层:§6a 提供「授权状态的可验证裁决」,本节保证「委派链的安全不变量」;per-授权关系的多实例状态由组织层为每个关系实例化一个文档承载(§6a.1 分层边界)。本节「委派」指授权委派(delegation of authority,沿授权链传播权限),与 §6 的 DELEGATE 决策类型(人机协同:把「机器搞不定」交给人或流程)语义不同。
一切委派、下达、再委托、传递委派、特权中介、下游约束变更、撤销,都 MUST NOT 让有效权威超出或逃逸起源权威链:
effective_authority(subject) ⊆ authority(chain)—— 有效权威是起源权威链的子集,任何操作不得放大它。
每条不变量 = 性质 + 违反形态 + 规范性断言。
effective_authority ⊆ authority(chain)。委派方授予的权限 ⊆ 委派方自己拥有的权限;权限不能通过委派链被放大。effective_authority(delegate) MUST ⊆ authority(chain);多个 child grant 对同一有界起源权威的聚合消耗 MUST 满足起源权威守恒(aggregate conservation)。constraints(delegate) MUST ⊆ constraints(delegator);下游约束变更 MUST NOT 放宽。authority(resource) MUST ⊆ authority(tool) ⊆ authority(skill) ⊆ authority(agent)。本节是 §6a.9(最新权威头新鲜度)在委派链层的推广:§6a.9 约束单实例 FSM 的状态头新鲜度,本节约束委派链祖先撤销状态的新鲜度。
行使依赖可撤销祖先的权威前,执行边界 MUST 确立撤销状态满足配置的新鲜度要求;可见撤销的缺失 MUST NOT 单独构成持续有效;无法确立新鲜度即 fail-closed。机制中立:monotonic epoch / lease / version vector / signed status object / online introspection / 等价机制。
收敛标准 = decision + matched_invariant + first_invalid_boundary。完整向量表见独立 conformance 套件(vectors/ + conformance/CONFORMANCE.md)。
求值 = 表达式树(规则编译产物)对输入事实(context)逐节点判定的纯函数过程(E1)。本节定义求值的输入契约、算法步骤与输出契约,供实现者与使用者对齐。
求值输入是一个事实对象(context),承载规则作用主体的当前状态,以 Entity(§3)为命名空间:
context:
tool: # Entity: tool
name: "issue_refund"
args: { amount: 8000, order_id: "O1024" }
country: "CN" # 自由字段直接挂 context 顶层(与 RFC-002 的 `context` 字段对齐)
role: "operator"
# 其他 Entity:agent / task / workflow / human / guardian(按需提供)
tool.name、context.amount、tool.args.amount)按事实对象的键路径解析(§3);as_of(求值时刻,UTC)、temporal_state(within/rate 滑动窗口状态)与 state.*(§6a 授权状态快照)由引擎注入,属受控外部输入(E1);字段契约声明事实字段的类型与语义,用于:① LLM prompt 生成(约束字段名,禁自造);② gloss 渲染(display_name,G3);③ 严格模式下的运行时类型校验。
| 字段 | 类型 | 说明 |
|---|---|---|
field |
string | 字段名(snake_case,进内核/哈希) |
display_name |
object | 双语显示名 { zh, en }(进 gloss,G3;仅渲染层,不进内核/哈希) |
type |
string | 字段类型:number / boolean / string / string[] / date |
optional |
boolean(可选,默认 true) | 字段是否可缺失;false 表示必填,缺失 → 求值错误(fail-closed,E12) |
description |
string | 字段语义 |
default_value |
任意(可选) | 缺失时的默认值 |
definition_period |
string(可选) | 定义周期:DAY / MONTH / YEAR / ETERNITY(对齐 OpenFisca Variable.definition_period);信息性字段——仅用于参数演化提示,不进内核、不进哈希、不参与求值 |
provenance |
string(可选,informative) | 字段来源类型(§7.0.1b:system_observed / llm_asserted / user_input / config / derived);信息性字段——声明事实来源供治理与规则审阅,不进内核、不进哈希、不参与求值(规范性红线见 §7.0.1b) |
严格模式下,声明了契约的字段其值类型与 type 不匹配 → 记 type_mismatch warning(同 §7.3(a) 严格模式);声明了 default_value 且字段缺失 → 按默认值求值(而非 E11 空值传播);声明了 optional: false 且字段缺失且无 default_value → 求值错误(errored=true,E12 fail-closed)。
与 §6 以 13 种决策类型系统化结论空间相对应,事实对象(context)的前提空间按来源作如下分类。本节为资料性(informative)分类,与输出侧的封闭枚举有意不对称:决策类型进 fold 与哈希,须封闭冻结(§6 封闭枚举的既有要求);事实来源开放演化(新集成形态持续出现),不进求值语义、不进哈希,可自由扩展:
| 来源类型 | 说明 | 典型示例 |
|---|---|---|
system_observed |
系统实测:宿主系统直接观测的值 | context.amount 取自支付系统返回 |
llm_asserted |
LLM 生成的主张:模型输出的工具名、参数、意图——可能幻觉,属「待验证的主张」而非「事实」 | tool.name、tool.args.* |
user_input |
人类用户的直接输入 | 用户在表单/对话中提交的金额 |
config |
配置快照:部署时确定的静态参数 | 阈值、白名单 |
derived |
派生值:由上述来源计算得出 | 累计金额、聚合统计 |
ERDL 的事实中立立场(设计说明):引擎不担保事实真伪——对 llm_asserted 字段,ERDL 担保的是「对给定输入的求值确定 + 原像完整(可复算)」,而非「输入本身可信」。事实可信度的治理由三层承担:① 字段契约(§7.0.1a)以信息性 provenance 字段声明来源;② 规则作者显式消费来源差异(对 llm_asserted 字段的规则比对 system_observed 更严——由规则表达,而非引擎隐式降权);③ state.*/event.* 的受控注入与事件认证(§6a.3/§6a.5.4)为授权谱系提供来源担保。实现不得(MUST NOT)将 provenance 纳入求值语义或哈希原像——信任评估混入确定性求值将破坏「语义=树=哈希」的封闭内核定位(E1/E6)。
输入:规则集 rules[] + 事实对象 context(状态机事件已先行处理,见步骤 0)
输出:决策结果(见 7.0.3)
0. 事件先行(happens-before 声明,无额外动作):事件已在到达时即时处理(eager,§6a.2)。
evaluate() 获取实例锁(§6a.5),其 state.* 输入即所有已到达事件提交后的状态。
1. 排序:按 priority 从小到大(值越小越优先)
2. 分组:按 ring 从 0 到 3 顺序执行(0 内核 → 1 恢复 → 2 审批 → 3 建议)
3. 每个 ring 内,按序求值每条规则:
a. unless 豁免先于 when 判定——命中豁免则记录后跳过该规则(unless 与 when 共享同一求值上下文:context + state.* 只读注入)
b. 编译后的 when 表达式树对 context 逐节点求值(true / false / 错误)
c. 命中不短路(仅 `EMERGENCY_HALT` 例外,见下):除 `EMERGENCY_HALT` 命中即短路外,其余决策(含 DENY/ROLLBACK/QUARANTINE/WORKFLOW)命中后继续求值(override ALLOW 可能覆盖)
d. override:仅 DENY → ALLOW 方向覆盖,不得覆盖到更不安全状态(§7.1)
4. 兜底:无规则命中 → metadata.decision(fallback 决策,§2.2)
5. 汇总:产出 decision + matched_rules + 证据(canonical_tree / hash / eval_trace / state_snapshot)
total_evaluated)。when 为以下两者之一:① when: "true";② 编译产物为字面量 true 节点(含决策表仅默认行的编译产物)。判定发生在编译期,不依赖书写形态;实现 MUST NOT 做字面量以外的常量折叠(1 eq 1 不折叠为 catch-all,避免跨实现分歧)。注:rules[].when 为 MUST 字段(§4.1),不存在「when 省略」的规则形态;transitions[].when 可省略(§6a.2),其无条件转移由同节冲突检查 (0) 单独判定。when 表达式树的逻辑节点(and/or/quantifier)全量求值(所有子节点都求值,左→右,不短路)——短路会改变 eval_warnings/state_snapshot.values/record 次数,进而改变 DO 哈希;实现 MUST 全量求值,MUST NOT 短路。EMERGENCY_HALT 命中即短路;WORKFLOW 命中进入工作流状态机(§6 决策类型 WORKFLOW;注意:授权状态机在 §6a,二者不同),但 WORKFLOW MUST NOT 屏蔽其后的拦截——仅在无 DENY/ROLLBACK/QUARANTINE/EMERGENCY_HALT 命中时启动;DENY/ROLLBACK/QUARANTINE 不短路——继续求值以判断是否有 override ALLOW 覆盖。求值结果 MUST 包含以下字段:
缺席编码(MUST,§8.2a):对象型可空字段(
primary_instruction、primary_reason、primary_explanation、primary_correction、temporal_state、state_snapshot)无值时编码为null,键 MUST NOT 省略;列表型字段(unless_exemptions、eval_warnings、canonical_trees)空态编码为[](键不省略),matched_rules恒为数组——保证 DO 哈希原像结构恒定。errored例外:它是布尔标志位(E3),永远有值(EvaluationError → true,否则 false),不参与缺席编码。空态编码完整边界见 §8.2a。
| 字段 | 说明 |
|---|---|
decision |
最终决策(§6 枚举之一,或 fallback 决策) |
matched_rules |
命中的规则(按求值顺序) |
unless_exemptions |
被 unless 豁免的规则(单独记录,不计入 matched_rules) |
primary_instruction |
首要指令(ALLOW + instruction 场景) |
primary_reason |
首要理由(DENY 等拦截场景) |
primary_explanation |
首要解释(可中英双语) |
primary_correction |
纠正文本(CORRECT 决策;来源为规则字段 correction,见 §4.1) |
total_evaluated |
实际进入 unless/when 求值的规则总数(被 catch-all 惰性跳过的规则不计入) |
total_matched |
命中的规则总数 |
temporal_state |
within/rate 滑动窗口状态快照(无命中时编码为 null,键不省略,§8.2a) |
state_snapshot |
求值时 state.* 读取到的状态快照:{ values, state_version, transitions_head }(无状态被读取时编码为 null,键不省略,§8.2a);进 DO 哈希原像(§6a.5) |
canonical_trees |
命中规则的 canonical 树快照(tree = 规范化树 JSON)与哈希(sha256: 前缀),E6 证据 |
rule_set_hash |
规则语义全集的哈希(含 fallback 决策;§8.2a.1a),弥补 canonical_trees 只覆盖命中规则 when 树的缺口,使第三方可验证「没有别的规则本该命中」 |
eval_warnings |
求值过程中的非致命警告(E3) |
indeterminate_rules |
when 求值为 unknown 的规则名列表(S3 顶层 unknown 折叠;空态编码 [],键不省略,§8.2a) |
errored |
求值是否发生错误(E3);Guard 上下文 fail-close、覆盖所有 tier(E12) |
as_of |
引擎注入的求值时刻(ISO UTC,E9) |
求值证据(canonical_tree 快照、结果哈希、eval_trace)为可独立重算的派生产物(§8.2、E6)——canonical_tree 进哈希,eval_trace 不进哈希(§8.3)。
priority 从小到大排序(值越小越先);override 级别排序(critical > high > normal > low);override 枚举:critical > high > normal > low(默认 normal);override 仅允许 DENY → ALLOW 方向覆盖(不得(MUST NOT)覆盖到更不安全状态);覆盖需「覆盖者级别更高且环更内核」:override ALLOW(critical/high)覆盖拦截规则 r,当且仅当 level(o) > level(r) 且 ring(o) ≤ ring(r)——外环(高 ring 号)不得(MUST NOT)覆盖内环(低 ring 号);收紧方向(DENY / ROLLBACK / QUARANTINE 覆盖 ALLOW)是「不得(MUST NOT)覆盖到更不安全状态」的默认推论,不比较 ring、无需 override(override 挂在收紧决策上无效、不影响收紧)——覆盖只可朝更安全方向(收紧)自由发生,朝更不安全方向(放松)须 override 显式授权;when 为字面量 true(无条件命中)的规则,无论 then 是 DENY 还是 ALLOW,也无论是否携带 override,都 MUST NOT 推翻任何显式条件(when 非字面量 true)规则已建立的决策。兜底规则仅在没有任何显式条件规则命中时才生效(§5.4 决策表「默认行」同义)。依据:兜底规则代表「其余情形」的弱、通用意图,显式条件规则代表「特定情形」的强、特定意图;令兜底改写显式决议属「覆盖到更不安全状态」,违反第 5 条的安全单调性。多规则命中时,最终 decision 由「决策强度偏序 + fold」确定(而非依赖首命中或未定义顺序)。13 种决策的强度偏序(数字越小越强,最终决策取最强):
| 强度 | 决策类型 | 类别 |
|---|---|---|
| 0 | EMERGENCY_HALT | 终端(命中即短路) |
| 1 | DENY / ROLLBACK / QUARANTINE | 拦截(收紧) |
| 2 | REQUEST_HUMAN / WORKFLOW | 人机协同 |
| 3 | ESCALATE | 人机协同 |
| 4 | DELEGATE | 人机协同 |
| 5 | DEFER | 人机协同 |
| 6 | CORRECT | 引导 |
| 7 | GUIDE | 引导 |
| 8 | ALLOW | 放行 |
NOTIFY 不参与主决策:NOTIFY 是「附带动作」(记录而不阻断),命中后记入
matched_rules与通知列表,不改变最终decision,也不参与 fold。
fold 规则(集合式,置换不变,MUST):
决策合并是集合式的(先收集全部命中,再一次性判定),而非顺序 fold——顺序 fold 让「override ALLOW 能否覆盖 DENY」依赖求值顺序(谁 priority/ring 更小谁先求值),破坏置换不变性。设:
override ∈ {critical, high} 规则;level(o) > level(r) 且 ring(o) ≤ ring(r)(外环不得(MUST NOT)覆盖内环;level 即 override 级别 critical > high > normal > low);locked——不得(MUST NOT)被任何 override ALLOW 覆盖(locked 规则恒计入「未被覆盖拦截类」,不参与覆盖判定)。依据:tier 0–2 是安全底线,tier ≥3 业务全景规则的 override 授权不得(MUST NOT)放松安全底线拦截;locked 由规则 tier 派生(tier ≤2 且拦截类 ⇒ locked),非独立书写字段;tier 缺省(未声明)的规则永不 locked。fold 伪码(MUST):
resolve(hits, indeterminate_rules, on_indeterminate):
if 任一命中为 EMERGENCY_HALT: return it # EMERGENCY_HALT 为终端(强度 0,命中即短路)
R = hits where restrictive(decision)
O = hits where decision == ALLOW and override in {critical, high}
locked(r) = tier(r) <= 2 # tier 0-2 拦截锁定(MUST NOT 被覆盖)
uncovered = R where locked(r) or not exists(o in O: level(o) > level(r) and ring(o) <= ring(r))
if uncovered non-empty: return strongest(uncovered) # 拦截类都强度 1,取 ring 最小者
nonRestrictive = hits where not restrictive(decision) and decision != NOTIFY
if indeterminate_rules 非空且 context == Guard: nonRestrictive += on_indeterminate # S3:unknown → 合成命中(缺省 REQUEST_HUMAN)
if nonRestrictive non-empty: return strongest(nonRestrictive) # 按强度偏序
return undefined # 落到 fallback
restrictive(d) = d in {DENY, ROLLBACK, QUARANTINE}
level(rule) = override 级别(critical > high > normal > low;缺省 normal)
strongest(S) = S 中强度偏序最小者;同强度取定义序最早者
集合式 fold 保证「置换不变性」:同一规则集任意排列,最终决策相同。收紧(拦截)方向自由,放松(放行/引导)方向须
override显式授权且环不更外——任何两个实现据此得到同一最终决策,不依赖命中顺序。
| 编号 | 约束 |
|---|---|
| E1 | 求值是纯函数:无副作用、无隐式外部状态、无时钟读取;within/rate 的计数、授权状态快照(state.*,§6a)与 as_of 同级,属受控外部输入——求值阶段只读预状态,但 within/rate 计数是受控副作用:within/rate 的 record 在求值时写入(计数低于阈值时,即放行操作),因此 evaluate() 对这两个算子不幂等——同一输入重复求值会再次计数;状态本体由引擎维护,表达式树只读快照 |
| E2 | 定点小数 scale=14 + half-even 字符串序列化(求值口径:运算输出精度,非 canonical 编码);中间计算用高精度有界有理数,仅输出节点舍入 |
| E3 | 求值错误记 eval_warnings 并置 errored=true,折叠方向按 E12 分 tier |
| E4 | 资源上限(分级):Grade A 算术深度≤2 / 树深≤6 / 节点≤64 / 数组≤10000 / 量词不嵌套 / 正则输入长度≤10000;Grade B 树深≤10 / 节点≤256 / 算术深度≤4,量词嵌套≤2 层;Grade C 不适用 |
| E5 | 加载时类型检查;when 与 expr 不得(MUST NOT)共存 |
| E6 | 树即证据:canonical_tree(树快照)作为求值证据参与哈希;eval_trace 为可重算派生产物,不进哈希 |
| E7 | Simple 与 Expression 编译到同一求值核心,禁止两个求值器 |
| E8 | 量词安全折叠:空数组 → all/any/none 一律 unknown(非 false;not(unknown)=unknown 不 fail-open) |
| E9 | 禁读墙钟;as_of 由引擎注入并记入审计记录 |
| E10 | 字符串 NFC 规范化 |
| E11 | undefined 哨兵语义(空值传播,见 §7.3) |
| E12 | 求值错误处理:Guard 上下文(安全边界的求值;参考实现 evaluate() 即 Guard 上下文)缺省 fail-close——所有 tier 的求值错误一律折叠向拦截侧(DENY);非 Guard 上下文(模拟/分析)中 tier≤2 fail-close、tier 3–5 折叠为 false |
内核显式排除:字符串拼接、正则替换、位运算、日期格式化、递归引用、用户自定义节点——以维持求值的封闭性与可验证性。
运维建议(非求值语义,非 MUST):单规则求值的墙钟耗时上限(如 ≤50ms)属部署层 DoS 防护的运维建议——不参与求值结果、不进入哈希、不构成确定性语义的一部分;参考实现以确定性节点/深度上限(E4 的分级结构上限)替代墙钟计时,与 E1(纯函数)E9(禁读墙钟)一致。
以下语义 MUST 在文档与向量中显式标注,避免与标准实现产生语义误解:
Agent 上下文高度动态,字段缺失是常态。求值采用 Kleene 三值逻辑(true / false / unknown),避免「缺失/类型不匹配折 false + not 翻转」的 fail-open:
| 场景 | 行为 |
|---|---|
| 字段缺失(undefined/null)时的相等/数值比较 | 返回 unknown(记 type_mismatch warning;非 NPE) |
== null / != null 检查 |
正常返回 true / false(感知字段存在性) |
| 类型不匹配的比较 | 返回 unknown(记 type_mismatch warning;禁止隐式转换;errored=false) |
| 字段缺失时的算术运算 | 传播为 unknown(缺失/空值操作数使整个算术为 unknown,errored=false);仅除零、溢出、非法类型(字符串/对象进算术)为 EvaluationError(errored=true) |
逻辑节点(and/or/not)的 unknown 操作数 |
Kleene 三值:not(unknown)=unknown;and 任一 false→false、全 true→true、否则 unknown;or 任一 true→true、全 false→false、否则 unknown |
顶层 unknown 折叠(MUST):规则 when 求值为 unknown 时不命中(规则仅在 when === true 时命中)——unknown 既非放行也非拦截,不触发 then;Guard 上下文经 metadata.on_indeterminate 配置顶层兜底(缺省 REQUEST_HUMAN;tier 0–2 MAY 配 DENY),并记入审计。
not 与 unknown(fail-open 防护,MUST):
not(unknown) = unknown——缺失/类型不匹配经 not 后仍为 unknown,绝不翻转为 true(堵住「缺失字段 → 比较 false → not true → fail-open」的漏洞)。
warning 不对称(跨实现 MUST 精确复现):类型不匹配/缺失比较统一记
type_mismatchwarning(errored=false);in(右非数组)、字符串节点(contains/match/starts_with/ends_with)、length(非 str/array)、aggregate(非数组/非数值元素)、量词(all/any/none的非数组操作数)同样记type_mismatchwarning。第三方实现 MUST 精确复现此 warning 集合。
严格模式(strict mode,求值选项,默认关闭):默认宽松(lenient)下比较节点类型不匹配静默 unknown(如上)。开启严格模式后,比较节点(
eq/ne/gt/gte/lt/lte/between)类型不匹配 → 记type_mismatchwarning(errored仍 false)——使 LLM 参数类型错误(如数字写成字符串"100" gt 50)在审计中可见。严格模式只改变「比较节点类型不匹配」的 warning 行为,不改变三值折叠语义;宽松模式 MUST 保持现状(向后兼容)。
标准量词语义下 all(空)=true(空洞真)。本规范刻意偏离:all/any/none(空) 一律折叠为 unknown(非 false)——既防「无元素可校验却被判为放行」(空洞真),又保证 not(all(空)) = unknown 不翻转为 true(fail-open 防护),并在审计记录中记录安全折叠。over 为非数组(缺失/标量/对象)时记 type_mismatch warning:all/any/none 折叠为 unknown 且 errored: false。第三方实现 MUST 采用本折叠语义。
中间计算采用高精度有界有理数(如 128 位整数分子/分母),仅输出节点按 scale=14 + half-even 舍入为字符串序列化(IEEE 754-2019 ROUND_HALF_EVEN)。一致性比较的是 scale-14 定点值(数值相等),而非字符串拼写:尾零无意义("35" ≡ "35.0")。此「字符串序列化」为求值口径(运算输出精度),不进入 canonical_tree 哈希;canonical 编码口径见 §8.2(带类型十进制字符串对象,非 JCS number)。
errored=true,E12 fail-closed),非静默折叠为 false。eq/ne/gt/gte/lt/lte/between)作用于精确有理数(未舍入的中间值),非舍入后的 scale-14 字符串。round 节点为 round(x, digits, mode),digits 为舍入位数(0–14,缺省 0),mode 为 half_even(IEEE 754-2019 ROUND_HALF_EVEN,缺省)——「四舍五入」(half-up)MUST NOT 出现,统一 half-even。match 节点 MUST 同时满足:① 输入长度上限(≤10000 字符);② 线性时间匹配——优先确定性引擎(RE2 类,无回溯),或受安全语法子集约束;③ 正则语法子集(EBNF 见下)。安全语法子集 MUST 限制为正则语言:禁止反向引用(\1–\9、\k<name>)与环视((?=) / (?!) 前瞻、(?<=) / (?<!) 后顾)——此类非正则构造依赖回溯顺序、无法逐字节确定,且无法由 SMT 验证器(erdl-formal)表达。内联大小写标志((?i))不提供(匹配始终大小写敏感,§5.2)。违反上述限制(嵌套量词、反向引用、环视或输入长度超限)的正则折叠为 unknown + regex_re_dos warning,且 errored: false——它不是 E3 的 EvaluationError;折叠为 unknown(而非 false)使 not(match(...)) 不翻转(避免 fail-open,§7.3(a))。MUST NOT 以「正则步数」作为上限度量——RE2 类引擎无回溯步数概念、步数上限不可跨实现复现;以输入长度上限 + 线性时间引擎替代(确定性、可移植)。
安全语法子集 EBNF(MUST):
regex := alternation
alternation := concatenation ( "|" concatenation )*
concatenation := repetition+
repetition := atom quantifier?
quantifier := "?" | "*" | "+" | "{" n ( "," m )? "}"
atom := literal | "." | char_class | group | anchor
group := "(" ( "?:" | "?<name>" )? alternation ")"
char_class := "[" "^"? char_item+ "]"
anchor := "^" | "$"
literal := 非元字符 | 反斜杠转义字符
禁止项(非正则构造,MUST NOT):反向引用(\1–\9、\k<name>)、环视((?=) / (?!) 前瞻、(?<=) / (?<!) 后顾)、内联标志((?i) 等)。
| 函数 | 空数组结果 | 依据 |
|---|---|---|
count(空) |
0 |
标准计数语义 |
sum(空) |
0 |
空和恒等元 |
avg(空) |
unknown |
安全折叠(避免除零;unknown 使 not(avg(空)) 不翻转) |
min(空) |
unknown |
安全折叠(标准 +Infinity,禁用;unknown 不 fail-open) |
max(空) |
unknown |
安全折叠(标准 −Infinity,禁用;unknown 不 fail-open) |
aggregate 的 over MUST 为数组;非数组(缺失/标量/对象)返回 null + type_mismatch warning(折叠为 false)。count(缺失) 与 count(空数组) 语义不同:前者 type_mismatch,后者 0。
所有时间节点统一以 UTC 求值,保证跨实现、跨时区逐字节一致:
YYYY-MM-DD)按 UTC 解析;date-time 按 ISO 8601 带时区解析(字面量整秒精度,不支持小数秒字面量),无时区后缀按 UTC;epoch_ms 节点返回毫秒时间戳(Unix epoch 毫秒)——「整秒」仅约束 date-time 字符串字面量解析,不约束内部毫秒时间戳;date_part):一律取 UTC 分量;date_add、month_last_day):按 UTC 日历运算;date_add 的 amount MUST 为整数(非整数返回 null + type_mismatch warning,折叠为 false)——时长是整数单位,半偶数舍入「加 1.5 个月」无业务语义,禁止隐式舍入;days_between):UTC 毫秒差 ÷ 86400000 向下取整(floor,朝负无穷取整——如 -0.5 → -1,非朝零截断);YYYY-MM-DDThh:mm:ss.sssZ,毫秒精度)——引擎注入的 as_of 与事件 at 按此形态序列化;实现 MUST 内部持有 UTC 毫秒时间戳、序列化为该固定形态(语言中立,不绑定具体宿主 API 名)。业务本地时区由引擎注入 as_of 时转换为 UTC 时刻,求值器以 UTC 纯函数运算。
E4 结构性资源限制违规(nodes / tree-depth / arithmetic-depth / array / quantifier-nesting 超出分级上限)抛出——引擎返回 value: null 且 value_type: "null"、threw: true(非 E3 的 EvaluationError;errored 仍为 false)。正则 ReDoS 违规(§7.3(d))折叠为 false + regex_re_dos(非抛出)。E5 加载时互斥违规记录 value: true(= 检测到违规)。上述 E12 折叠与 errored 规则仅适用于求值向量。
when: "true" 的语义是「对所有操作生效」,仅适用于建议性规则:
| 规则 | 级别 |
|---|---|
when: "true" MUST NOT 与 then: DENY 搭配 |
MUST NOT |
when: "true" MUST NOT 与 then: EMERGENCY_HALT 搭配 |
MUST NOT |
when: "true" MUST NOT 与 then: CORRECT 搭配 |
MUST NOT |
when: "true" MUST NOT 与 then: REQUEST_HUMAN 搭配 |
MUST NOT |
when: "true" MAY 与 then: ALLOW + instruction 搭配 |
MAY |
when: "true" MAY 与 then: NOTIFY 搭配 |
MAY |
| 安全类规则(category=security)MUST 至少含 1 个 condition | MUST |
工具拦截类规则 SHOULD 含 tool.name 条件 |
SHOULD |
文件操作规则 SHOULD 含 tool.args.path |
SHOULD |
命令操作规则 SHOULD 含 tool.args.command |
SHOULD |
ERDL 文档以 YAML 承载,可无损转换为 JSON。规范化树(canonical_tree)以 JSON 对象形态序列化。
表达式树是唯一求值、唯一哈希、唯一重算的基准对象。要使其哈希可跨实现逐字节一致,树 MUST 具有唯一规范化形式:
| 规范化规则 | 说明 |
|---|---|
| 节点序固定 | 子节点按规范顺序排列(左→右严格定序),与源书写顺序无关 |
| 字段名承重 | 字段引用路径承重——字段名发布即冻结 [FREEZE-1],别名 MUST 先行归一化 |
| 字面量规范 | 数字字面量的 canonical 编码口径为带类型十进制字符串对象 {"n":"0.15"}(数字 0.15 编码为 {"n":"0.15"},字符串 "0.15" 仍为裸值 "0.15"——二者不再碰撞),非 JCS IEEE 754 number 序列化——避免超过 2^53 的整数与超 17 位有效数字的小数在 IEEE754 下失真或碰撞;字符串 NFC 规范化 |
| var 规范 | 仅支持 $ / $.path,路径段为确定字节序列 |
| 元数据剥离 | 注释、来源行号、格式、作者等非语义元数据一律不进规范化树 |
树哈希的对象是规范化树,而非任何特定实现的内存表示或序列化文本。 两个结构等价的树(仅字段书写顺序、空格、变量命名不同)规范化后产生完全相同的字节序列与哈希。
树哈希的版本化域分隔前缀(MUST):树哈希 =
sha256("erdl-tree-v3:" + canonical 字节)——erdl-tree-v3:是版本化域分隔前缀(S2 破坏性变更),使新旧编码口径的树哈希互不混同(不含数字字面量的树在新旧口径下字节相同,但前缀不同则哈希不同)。
规范化树以 S-expression(JSON 形态)序列化,每个节点是一个单键对象(MUST 恰好一个键)。节点形态(MUST,逐节点钉死):
| 节点 | S-expression 形态 | 说明 |
|---|---|---|
| 字面量 literal | 数字为带类型对象 { n: "<十进制字符串>" };字符串/布尔/null 为裸值 |
数字 typed({n} 键为保留键,非节点名,消除数字/字符串碰撞);字符串 NFC(E10);数组字面量(如 in 的右操作数)为裸数组 |
| field | { field: "路径" } |
字段引用(snake_case,FREEZE-1) |
| var | { var: "路径" } |
路径仅限 $或$.path(根或点路径) |
| and / or | { and: [子…] } / { or: [子…] } |
逻辑(n 目,见下方子节点排序) |
| not | { not: 子 } |
单子节点 |
| eq / ne / gt / gte / lt / lte | { eq: [左, 右] } |
比较(二目) |
| in | { in: [左, 右] } |
右操作数为数组 |
| contains / match / starts_with / ends_with | { contains: [左, 右] } |
字符串(二目) |
| exists | { exists: 子 } |
存在性 |
| length | { length: 子 } |
长度 |
| between | { between: [值, 最小, 最大] } |
三目 |
| all / any / none | { all: { binding, over, predicate } } |
量词 |
| add / sub / mul / div / round | { add: [子…] } |
算术(n 目) |
| days_between | { days_between: [from, to] } |
时间差 |
| epoch_ms | { epoch_ms: 子 } |
时间戳 |
| date_add | { date_add: { unit, base, amount } } |
日期推演 |
| date_part | { date_part: { unit, arg } } |
分量提取 |
| month_last_day | { month_last_day: 子 } |
月末 |
| count / sum / avg / min / max | { count: 子 } |
聚合 |
| fn | { fn: { name, args } } |
函数委派 |
| casefold / trim / path_normalize | { casefold: 子 } |
字符串规范化(单目,扩展 profile) |
| in_set | { in_set: { ref, digest, value } } |
名单外置成员(扩展 profile) |
子节点排序(MUST):所有节点的子节点数组保持定义顺序(左→右),不排序——排序会改变语义(如 and/or 的求值顺序与 §7.3(a) 的 warning 不对称相关),故禁止。节点对象内的键(如 date_add 的 unit/base/amount、量词的 binding/over/predicate、fn 的 name/args)由 JCS 按键排序(§8.2 编码口径)。
优先条款(MUST):正文与向量冲突时,以正文为准(向量是参考实现的一致性测试,非规范权威);若向量暴露正文未覆盖的语义,MUST 将结论写回正文(§10.3.3「读本规范即可实现」)。
求值结果的 DO 哈希原像,其字段序、键集合、缺席编码 MUST 如下定义,否则两个实现必然算出不同哈希(v2.2 往 DO 新增了 state_snapshot 结构化字段,其局部键序已定义但整体原像未定义,属全局失锚):
字段固定序(MUST):
context → decision → matched_rules → unless_exemptions → primary_instruction → primary_reason
→ primary_explanation → primary_correction → total_evaluated → total_matched
→ temporal_state → state_snapshot → canonical_trees → rule_set_hash → eval_profile → eval_warnings → indeterminate_rules → errored → as_of
DO 哈希的版本化域分隔前缀(MUST):语言层求值结果 DO 哈希 =
sha256("erdl-eval-do-v3:" + JCS(原像) 字节)——erdl-eval-do-v3:是版本化域分隔前缀,与树哈希前缀erdl-tree-v3:(§8.2)同一机制:使 DO 原像 schema 变更(如 S1/S4 收口)前后的哈希互不混同,也使 DO 哈希与树哈希互不混同。
context进 DO(MUST):context为求值输入的事实对象(§7.0.1),进 DO 哈希原像——使「针对这份输入作出的这个决策」可独立复算,而非仅复算「决策 → 命中规则 → 树」的输出侧。context在字段序首,语义上为「输入 → 决策」的完整闭环;缺失输入事实的 DO 无法回答「这个决策是针对什么输入作出的」。新增
context字段是 breaking 变更(MUST 显式标注):新增context改变了 DO 哈希原像的字段序与键集(旧 schema 的 DO 哈希全部失效),属 v2.3 的 breaking 变更——验证方 MUST 依据eval_profile.spec_version(§8.2a.1b)选择复算 schema。context在字段序中的具体位置(首/中/末)不影响语义,只要完整输入上下文进原像即可;本规范不把「位置」作为语义约束。注意分层:本节「求值结果 DO」是语言层求值结果的哈希原像(context字段序首);RFC-002 治理层的decision-object(v1.5)是另一套哈希原像,其中事实对象字段名为context(CORE 14 之一,非字段序首)——二者是同一事实对象在不同层的两种命名,但属于两套不同的 DO 哈希原像,MUST NOT 混同:语言层 breaking 不改变 RFC-002decision-object的字段序,反之亦然。
验证方式(语言层 DO):语言层求值结果 DO 的跨实现确定性由(a)§8.2a.1 的形式化 pinned 字段序/键集/缺席编码(规范性形式定义,非行文近似)与(b)引擎自证(
evaluation-object.spec.ts)保证。无需专门的跨实现向量族——RFC-002 治理层decision-object(v1.5)才是跨实现向量层;语言层以形式化定义 + 引擎自证收口。
context 的 Merkle 承诺(可选 profile,MUST 支持):
context可能含个人信息与密钥,全量进 DO 会令「可验证性」与「合规删除」冲突。可选地对context做 Merkle 承诺——每个字段值带盐哈希为叶子,DO 只存 Merkle 根(context_hash),复算时按需披露字段加证明。无论是否承诺,脱敏必须(MUST)先于求值(求值所用的值与记录的值一致,MUST NOT 先记录原文再求值)。
固定键集合(MUST):无值的键编码为 null,键 MUST NOT 省略(保证原像结构恒定);数组按出现顺序;字符串 NFC(E10);数字 typed {"n":"<十进制>"}(§8.2 编码口径)。空态编码(MUST):列表型字段(matched_rules、unless_exemptions、eval_warnings、canonical_trees)空态编码为 [](键不省略);仅对象型可空字段(primary_instruction/primary_reason/primary_explanation/primary_correction、temporal_state、state_snapshot)无值时编码为 null——数组恒数组、对象可 null,边界唯一。
DO 子结构键集与键序(MUST,逐字段钉死):子结构条目同样固定键集与键序(S1)——缺席编码 null、键不省略;列表型空态 []:
| 子结构 | 条目键序(固定) | 说明 |
|---|---|---|
matched_rules[] / unless_exemptions[] |
rule_id → rule_name → decision → priority → ring → instruction → reason → correction → explanation → alternative → corrected_args |
命中/豁免规则的规范条目;可选字段缺席编码 null;explanation/alternative 双语时编码 {zh, en}(NFC);corrected_args 为 CORRECT 决策的参数修正对象(snake_case 键),缺席 null |
canonical_trees[] |
rule_id → tree → hash |
E6 树证据:tree 为规范化树 S-expression(§8.2.1);hash 为树哈希(sha256: 前缀,域分隔前缀见 §8.2) |
eval_warnings[] |
code → node_type |
code 为封闭枚举(E3):type_mismatch / division_by_zero / quantifier_empty / aggregate_empty / regex_re_dos / array_over_limit / invalid_date / not_ruleable;自由文本 message 不进原像(诊断措辞跨实现不一致,会破坏字节恒定);node_type 缺席编码 null |
temporal_state[] |
rule_id → operator → field → window_ms → count → limit |
within/rate 窗口计数快照(S6 计数键要素的输出面);operator ∈ {within, rate};limit 仅 rate 条目携带("N/1m" 的 N),within 条目无 limit 键 |
indeterminate_rules[] |
(字符串数组) | when 求值为 unknown 的规则名列表(S3),空态 [] |
rule_set_hash 是规则语义全集的哈希,弥补「canonical_trees 只覆盖命中规则的 when 树」的缺口——未命中的规则、then/priority/ring/override/enabled/unless 都进 rule_set_hash,使第三方可验证「没有别的规则本该命中」。
规则规范对象(rule canonical object,MUST):每条规则规范化为:
{ name, when_tree, unless_tree, then, priority, override, ring, tier, enabled, instruction, reason, correction, explanation, alternative }
when_tree = 规则 when 编译后的 S-expression(§8.2 树级 canonical);无条件(catch-all)编码为字面量 true 节点;unless_tree = 规则 unless 编译后的 S-expression;无 unless 编码为 null;then = 决策类型(§6 枚举);priority = 数字(JCS number);override = critical/high/normal/low(缺省 normal);ring = 0–3(缺省 3);tier = 0–5(缺席编码 null;tier 0–2 为安全底线,§7.1a locked);enabled = 布尔(缺省 true);instruction / reason / correction / explanation / alternative = 规则文本字段(M5:它们求值后输出为 primary_*,correction 具安全相关性——纠正文本改变拦截语义);缺席编码 null;双语对象编码 {zh, en}(NFC)。rule_set_hash 计算(MUST):
rule_set_hash = sha256(JCS({ fallback_decision, on_indeterminate, rules: [规则规范对象…] }))
fallback_decision = metadata.decision(缺省 ALLOW);on_indeterminate = metadata.on_indeterminate(缺省 REQUEST_HUMAN,S3);rules 按统一排序键(ring 升序 → priority 升序 → override 级别 → 定义顺序)排列——与 §7.0.2 的 ring 分组执行序一致(ring 主序);eval_profile 记录求值选项——严格模式、上下文、字段契约、规范版本。这些选项会改变决策或 eval_warnings,却不在 DO 里,第三方就无法复算。eval_profile 进 DO 原像:
{ strict, context, contract_hash, spec_version }
strict = 严格模式开关(§7.3(a),默认 false);context = 求值上下文(guard = 安全边界求值(evaluate()),analysis = 模拟/分析);消歧:此 context 为求值选项(Guard / 非 Guard,E12 折叠方向),与 §7.0.1 的事实对象 context(DO 原像字段序首)为不同概念——键名相同属历史沿革,以所处结构区分(eval_profile.context vs 原像顶层 context);contract_hash = 字段契约的 sha256 哈希(契约哈希化引用;无契约时为 null);spec_version = 规范版本(如 v2.3);engine_id 不进原像(S4):engine_id 是实现标识(参考实现为 erdl-engine),进原像会破坏跨实现逐字节一致——它仅随 DO 传输信封携带(诊断用途),不影响哈希;原像的 schema 选择由 spec_version 承担。转移链审计记录的原像字段序(MUST,分三类):转移链由三类审计记录构成——成功转移(type: "transition")、转移错误(type: "transition_error")、起源(type: "genesis")。三类字段集不同,各自字段序与固定键集 MUST 如下(type 字段进原像以区分类型):
成功转移记录(transition):
type → event_id → on → actor → at → audit_as → fired → reason → set → state_version → previous_hash
转移错误记录(transition_error):
type → event_id → on → actor → at → audit_as → fired → reason → error → errored → previous_hash
起源记录(genesis):
type → instance_id → protocol → doc_tree_hash → initial → at → previous_hash
固定键集与缺席编码(MUST):每类记录的键集即上列字段序(不跨类型补齐缺失字段);set/initial 的键按状态变量名 RFC 8785 JCS 键序(UTF-16 码元升序);字符串 NFC(E10);数字 JCS(§8.2 编码口径);previous_hash 缺席(仅 genesis)时编码为 null 且键不省略。fired = 本事务触发的转移规则名(name 缺省取 on),多条以 , 连接为字符串(M7);reason = 转移语义标识(§6a.2 reason 字段),取本事务首个非空值,缺席编码 null(M7)。
这一「哈希树、校验 gloss 与树一致」的机制,使 gloss 在哈希模式下无需逐字节一致、也无需进哈希,即可获得树的密码学锚定。
ERDL 的集成目标,是把关键判断从模型推理、框架代码或口头约定中抽离出来,变成可加载、可执行、可验证的规则资产。它通常以 YAML/JSON 规则文档承载,通过 when → then 完成决策,并输出可哈希、可逐字节复算的求值证据。以下三种集成路径,对应三类典型工程落点。
角色:在 AI Agent 链路中,ERDL 是 LLM 意图与系统执行之间的「确定性闸门」——Agent 可以生成动作,但动作是否允许执行,必须由规则求值决定。
模拟:客服 Agent 收到用户「退款 8000 元」的请求。LLM 将意图转换为工具调用:issue_refund(amount=8000, order_id=O1024)。在真正触达支付系统前,Action Guard 把工具名、参数、会话上下文打包成事实对象,交给 ERDL 求值。规则集中,R1 写作:when tool.name == "issue_refund" 且 tool.args.amount > 5000 → REQUEST_HUMAN;R2 写作:when tool.name == "issue_refund" → ALLOW。由于 R1 先命中,系统返回 REQUEST_HUMAN。Agent 停止调用支付工具,转为生成人工审批任务,并向用户返回「需人工复核」的话术。
集成要点:第一,规则在模型之外独立求值,Prompt 不再承担安全边界;第二,命中记录、输入摘要、canonical_tree 与结果哈希一并写入审计日志,任何一次拦截都可回放;第三,规则变更只需更新 ERDL 文档,不必重写 Agent 框架、工具实现或模型提示词。
角色:在 MCP 生态中,ERDL 规则通过 ERDL MCP Server 暴露为标准工具,任何 MCP 兼容 Agent 都能调用同一套规则,实现跨实现的合规分发与互认。
模拟:某企业将资金合规规则部署为 ERDL MCP Server,并暴露 guard_check 工具。第三方 Agent 准备执行一笔高额退款,但自身不掌握企业规则。执行前,它通过 MCP 调用 guard_check,传入动作描述:action="issue_refund"、amount=8000、channel="payment"。ERDL MCP Server 加载规则集,完成求值,返回:decision=REQUEST_HUMAN、matched_rule=R1、hash=0x9f...。第三方 Agent 根据返回值停止直接执行,转入人工审批流程,并将该决策凭据写入自己的执行日志。
集成要点:这里的关键是「规则即服务」。规则不再硬编码在某个 Agent 框架里,而是通过标准协议分发;不同厂商、不同语言、不同运行时的客户端,都可以消费同一份判断逻辑。由于返回结果携带规则命中与哈希信息,调用方即使不保存规则原文,也能把决策凭据归档,供后续审计或独立复算。规则升级时,只需更新 MCP Server 端,客户端无需重构执行链。
角色:在规则引擎内部,ERDL 是规则定义语言本身,负责以声明式方式表达业务策略,并提供确定性求值语义。
模拟:反洗钱引擎需要表达「大额交易且高风险国家 → 拦截」。业务团队编写 ERDL 规则:when context.amount > 10000 且 context.country in ["高风险国家列表"] → DENY。其中高风险国家名单(>256 项,FATF/OFAC 等制裁名单常达数千项)以 in_set{ref,digest} 节点外置引用——名单版本化哈希进规则、名单本体由引擎上下文注入,不受 in 的 256 内联上限约束(§5.2.2)。引擎加载 YAML 后,不会把它当成普通配置,而是编译为表达式树:比较节点处理金额阈值,集合节点判断国家归属,逻辑节点完成与运算。随后,引擎按照 E1–E12 求值语义执行,输出 DENY。该规则还可生成 canonical_tree 与哈希值;另一实现只要加载同一规则、同一输入,就能重算出相同结果。
集成要点:这里的核心价值是「可验证的确定性」。ERDL 不只让规则可读,还让引擎实现可以被测试向量约束。V-ENGINE 类测试向量可以证明:不同引擎、不同平台、不同版本,对同一规则应产生逐字节一致的结果。由此,规则引擎不再只是业务系统内部黑盒,而是可以接受第三方独立验证的执行器。监管方、审计方或平台方都能依据输入、规则哈希与求值证据,判断「引擎算得对不对」。
一个最小 ERDL 文档 + 一次求值,走完「写 → 加载 → 求值 → 得结果」全链路(管线见 §2.4)。
第一步 · 写规则(refund.erdl.yaml):
protocol: "erdl/v2"
version: "2.2.0"
metadata:
name: "refund-guard"
description: "退款金额管控"
category: coding
decision: ALLOW
rules:
- name: "SEC-001-refund-limit"
description: "退款超过 5000 需人工审批"
priority: 10
when:
logic: AND
conditions:
- field: "tool.name"
operator: eq
value: "issue_refund"
- field: "tool.args.amount"
operator: gt
value: 5000
gloss: "tool.name equals issue_refund and tool.args.amount is greater than 5000" # 引擎生成(G2)
then: REQUEST_HUMAN
message: "退款金额超过 5000,需人工审批"
第二步 · 加载 + 校验 + 编译:解析 YAML,校验通过后把 when 编译为表达式树(§2.4 步骤①②③)。
第三步 · 求值:给定事实对象:
context:
tool:
name: "issue_refund"
args: { amount: 8000 }
规则 SEC-001 命中(tool.name == "issue_refund" 且 amount > 5000)。
第四步 · 结果:
decision: REQUEST_HUMAN
matched_rules: ["SEC-001-refund-limit"]
primary_reason: "退款金额超过 5000,需人工审批"
total_evaluated: 1
total_matched: 1
若输入改为 amount: 100,规则不命中,走 metadata.decision fallback → decision: ALLOW。
见 §4.2(Simple 规则)与 §5.3(Expression 规则)、§5.4(决策表)。
状态块完整示例(§6a):
protocol: "erdl/v2"
version: "2.2.0"
metadata:
name: "delegated-refund-authority"
description: "委托退款授权的撤销与新鲜度"
category: security
decision: DENY
state:
- name: authorization
values: [authorized, revoked]
initial: revoked
display_name: { zh: 授权状态, en: authorization state }
transitions:
- on: bootstrap
audit_as: DELEGATE
reason: authorize
when: { eq: [{ field: "state.authorization" }, "revoked"] }
gloss: "authorization state equals revoked" # 引擎生成(G2),lint 校验
set: { authorization: authorized }
- on: revoke
audit_as: DELEGATE
reason: revoke
gloss: "true" # 无条件转移:字面量 true
set: { authorization: revoked }
rules:
- name: "SEC-001-revoked-denies-exercise"
description: "授权已撤销时拦截受保护操作"
priority: 10
ring: 0
when:
logic: AND
conditions:
- field: "state.authorization"
operator: eq
value: revoked
then: DENY
message: "授权已撤销"
注意:
state块在 rules 求值前已由事件(transitions)更新;state.*是受控注入(§6a.3),不是 context 字段。
state_snapshot)求值输出示例(含 state_snapshot,V-STATE 雏形):上例经 bootstrap(授权)与 revoke(撤销)两次事件后,规则求值输出如下:
# 事件序列(先于规则求值处理,eager):
# bootstrap → set authorization: authorized (state_version 0 → 1)
# revoke → set authorization: revoked (state_version 1 → 2)
decision: DENY # 命中 SEC-001,authorization=revoked
matched_rules: ["SEC-001-revoked-denies-exercise"]
primary_reason: "授权已撤销"
total_evaluated: 1
total_matched: 1
temporal_state: null # 无 within/rate 命中
state_snapshot: # §6a.5,进 DO 哈希原像
values: { authorization: revoked } # 求值读到的状态(on-demand,非全量)
state_version: 2 # 2 个成功事务(bootstrap + revoke)
transitions_head: "sha256:…" # revoke 记录的哈希
canonical_trees: [ { rule_id: "SEC-001-…", tree: …, hash: "sha256:…" } ]
eval_warnings: [] # 列表型字段空态为 []
errored: false
as_of: "2026-09-12T10:00:00Z"
本规范的语义 MUST 由可独立重算的测试向量证明。表达层向量(V-ENGINE / V-GLOSS / V-PROJ)覆盖:34 节点 × 4 场景(正常/边界/异常/空值)、E1-E12 语义、Simple 30 运算符编译映射、gloss 渲染模板;状态层向量(V-STATE)覆盖 §6a 全部 MUST 语义:事件对象校验(event_id/on/actor/at/payload 受限负载)、同变量冲突检查 (0)–(4) 正/反例与同事件 audit_as 一致性、单事件多规则原子性(遇首个 EvaluationError 即停止、全过则一次性提交)、守卫错误 fail-closed 与 transition_error 链位置(不应用 set/不递增版本/不移动 head)、state_version/transitions_head 重放验证、重复 event_id 幂等丢弃、无匹配事件静默、规则侧引用 event.*/未声明 state.* 加载失败、catch-all 与显式规则两趟交互、执行边界 check/act 重校验(authorized@N → ALLOW@N → revoke@N+1 于效果提交前、fail-closed,§6a.8)、最新权威头新鲜度(authorized@N/HN → revoke@N+1/HN+1 → 还原历史前缀 → 重放通过 → 拒绝效果,反回滚,§6a.9)。裁决语义层向量(V-RESOLVE,R01–R13)覆盖 §7.1 ring/override/catch-all 裁决语义(含收紧方向边界 R08/R13)。
五步验证法:加载向量输入 → 生成表达式树 → 重算求值结果 → 与答案对比 → 判定一致。
第三方 Runner 验证流程(从零到合规):
实现 MUST 通过可独立重算的测试向量(§10)证明一致性;「实现与规范一致」的唯一判定标准是:对同一(规则集, 事实, 评估选项, 状态)输入,产出与向量答案逐字节一致的 DO 哈希。(此处「DO」指 RFC-002 治理层决策对象——跨实现向量层;§8.2a.1 的语言层求值结果 DO 以形式化定义 + 引擎自证收口,不另设向量族。)符合性分两级:
casefold/trim/path_normalize)、名单外置(in_set)、状态块(§6a)、函数委派(附录 D)为可选扩展——实现 MAY 不支持,但一旦声明支持即 MUST 完全符合对应节,且 MUST 在实现元数据中声明支持的扩展集合。match 节点 MUST 输入长度上限 + 线性时间引擎(§7.3(d)),禁止反向引用/环视;in 名单 ≤256 内联上限(大名单走 in_set 外置引用);hasOwnProperty 而非 in,MUST NOT 访问对象原型链;state.* 受控注入(§6a.3),仅 transitions[].set 可更新,外部不可写;previous_hash 串行锚定(§6a.5),DO 哈希原像固定字段序 + JCS(RFC 8785)+ SHA-256;context 全量进 DO 哈希原像(§8.2a.1),与「可验证性」和「合规删除」存在张力。实现 MUST 支持 context 的 Merkle 承诺(可选 profile)——字段值带盐哈希为叶子、DO 只存 Merkle 根,复算时按需披露;actor 身份进链时认证证据(JWS kid + 摘要)SHOULD 一并进链(§6a.5.4),不含身份凭据原文(当前仅记录 actor 字符串);本规范的扩展点(新增节点类型、新增决策类型、新增书写形态、新增 profile)MUST 遵循注册机制:
casefold/in_set 扩展 profile),MUST NOT 改现有节点语义、MUST NOT 删除节点;protocol/version 标识,破坏性变更限 major。| 组 | 节点 | 数量 |
|---|---|---|
| 取值 | field · var · 字面量 | 3 |
| 逻辑 | and · or · not | 3 |
| 比较 | eq · ne · gt · gte · lt · lte | 6 |
| 集合 | in | 1 |
| 字符串 | contains · match · starts_with · ends_with | 4 |
| 存在/量纲 | exists · length · between | 3 |
| 量词 | all · any · none | 3 |
| 算术 | add · sub · mul · div · round | 5 |
| 时间 | days_between · epoch_ms · date_add · date_part · month_last_day | 5 |
| 聚合 | aggregate(count/sum/avg/min/max) | 1 |
合计 34 节点。
| 族 | 运算符 | 数量 |
|---|---|---|
| 比较 | eq · ne · gt · gte · lt · lte | 6 |
| 列表 | in · not_in | 2 |
| 字符串 | contains · not_contains · match · starts_with · ends_with | 5 |
| 边界否定 | not_starts_with · not_ends_with | 2 |
| 存在性 | exists · not_exists | 2 |
| 长度 | length_gt · length_gte · length_lt · length_lte · length_eq | 5 |
| 范围 | between · not_between | 2 |
| 计数 | count_gt · count_gte · count_lt · count_lte | 4 |
| 修饰符 | within · rate | 2 |
合计 30 运算符(28 条件运算符 + 2 条件修饰符)。
见 §6。
对于内核显式排除、确有需求的场景,提供函数委派(FnRegistry)作为受控兜底:
| 约束 | 说明 |
|---|---|
| 注册制 | 函数 MUST 注册方可引用,未注册不可调用 |
| 沙箱执行 | 受限环境,受资源配额与超时约束 |
| 确定性豁免声明 | 用于 Guard 求值路径的函数 MUST 声明并保证确定性 |
| 审计可溯 | 每次调用记入审计记录,可离线核验 |
fn 节点读状态(§6a):fn 节点不属于 34 节点冻结内核(附录 D 受控兜底,Grade C)——fn 的参数可引用
state.*/event.*(受控状态经 field 节点注入后作为参数传入);但 fn 本身 MUST 保持确定性(上述「确定性豁免声明」),且不得(MUST NOT)直接写状态(状态只能由transitions[].set更新,§6a.3)。
规则分级(Grade):
| Grade | 表达方式 | 审计 SLA |
|---|---|---|
| A | 纯 Simple(30 运算符) | 最高,纯文本可重算 |
| B | Expression 树 | 高,eval_trace MUST |
| C | 含函数委派 | 分层,C 级不得(MUST NOT)冒充纯文本可重算 |
含函数委派的规则(Grade C)MUST 在 gloss 中显式标记「含不可重算的函数委派」;函数委派的调用输入 + 输出哈希 MUST 纳入结果哈希的原像。
函数委派调用约定与注册表(MUST,消除「无调用约定/无注册表格式」的悬空):
| 项 | 约定 |
|---|---|
| 函数签名 | name(params) -> returnType——name 为 snake_case 标识符,params 为逗号分隔的参数名,returnType 为返回类型;签名是函数在注册表中的唯一键 |
| 注册表条目 | { signature, sandbox, timeout, deterministic, ... }——sandbox ∈ pure/network/filesystem,deterministic 声明 Guard 路径可求值性,timeout 为超时上限 |
| 哈希原像 | 委托调用的哈希原像 MUST 含:fn_id(= 签名)、version(函数版本)、args_hash(参数 JCS 哈希)、result_hash(结果哈希)——四者缺一不可,使 Grade C 调用可离线核验「调用的是哪个版本、输入了什么、得到了什么」 |
分级与 tier 约束(MUST):Grade C(函数委派)MUST NOT 用于 tier 0–2(安全底线)——tier 0–2 的安全敏感规则 MUST 用纯 Simple 内核(Grade A),不得(MUST NOT)经 fn 委派绕过封闭求值内核;Grade C 仅限 tier ≥3 的业务全景规则。
| 术语 | 一句话定义 |
|---|---|
| Entity(实体) | 规则作用的主体类型(agent/tool/task/workflow/human/guardian),字段引用的命名空间(§3) |
| Rule(规则) | when → then 决策单元 |
| DO(Decision Object) | 单次求值的密码学审计记录(可哈希、可独立验证);与「求值结果」(Evaluation Result,§7.0.3 的运行时输出)区分——DO 是求值结果的规范化序列化形态(§8.2a 字段序 + JCS RFC 8785 + SHA-256),求值结果是引擎的运行时返回 |
| genesis | 状态块加载时为 initial 生成的链起点审计记录(初始快照 + 文档规范树哈希,§6a.5) |
| transition_error | 转移守卫求值错误(EvaluationError)产生的链上一等记录(不应用 set、不递增版本、不移动 head,§6a.5) |
| event(事件对象) | 状态转移的触发输入 { event_id, on, at, actor, payload }(§6a.7) |
| actor | 已认证的事件源标识(身份层提供,进转移审计记录,§6a.7) |
| when | 规则触发条件(编译为表达式树) |
| then | 规则命中后的决策类型(§6) |
| tier | 规则层级 0–5,由低到高表示约束强度;tier 0–2 用 Simple,≥3 可用 Expression |
| ring | 执行环 0–3(内核/恢复/审批/建议),求值按环序执行 |
| Guard 上下文 | 安全边界的求值上下文(参考实现 evaluate());求值错误一律 fail-close(E12),覆盖所有 tier |
| 非 Guard 上下文 | 模拟/分析等非安全边界的求值;tier 3-5 的求值错误折叠 false(E12) |
| override | 覆盖级别 critical > high > normal > low,仅允许 DENY → ALLOW 方向 |
| 表达式树 | 求值语义内核(34 节点,10 组),三种书写形态编译归一化到它 |
| canonical_tree | 规范化树,唯一哈希、唯一重算的基准对象(§8.2) |
| gloss | 从树确定性生成的自然语言可读投影(§5.5) |
| eval_trace | 逐节点求值轨迹(可重算派生产物,不进哈希,E6) |
| eval_warnings | 求值过程中的非致命警告(E3) |
| errored | 求值是否发生错误(E3):EvaluationError(除零/非法日期/元数错误/算术类型不匹配[字符串或对象进算术])→ true(即便 E12 折叠为 false);类型不匹配比较、空值传播、缺失字段算术 → false(后者折叠为 unknown,非错误) |
| temporal_state | 时序状态:within/rate 滑动窗口状态(有状态算子,计数语义)——与「授权状态」(state block)异义 |
| 状态块(state block) | 授权状态:§6a 声明的命名状态机(state 顶层块 + transitions 转移规则)——与「时序状态」temporal_state 异义,两者互不共享命名空间 |
| 状态变量(state variable) | 状态块声明的命名状态(§6a),state.<name> 命名空间,仅由转移规则更新 |
| 状态空间(state space) | 文档声明的全部状态变量及其枚举值集合(有限,受资源上限约束) |
| 状态转移(state transition) | 事件触发的确定性状态变更 state.<name> ← value(状态机 F 函数,§6a.2) |
| 受控注入(controlled injection) | 引擎持有的输入(as_of/temporal_state/state.*),外部不可写,仅由引擎机制更新(§6a.3、E1) |
| state_snapshot | 求值时状态快照,进 DO 哈希原像(§6a.5) |
| 执行边界(enforcement boundary) | 消费 §6a 决策并提交被门控副作用的组件(Action Guard / 工具调用守卫,§6a.8) |
| check/act 原子性 | §6a.8 义务:授权决策与被门控副作用提交之间,无授权谱系状态变更落地 |
| 最新权威头(latest authoritative head) | 同一文档实例当前最新的权威状态锚点 {state_version, transitions_head};跨重启/恢复/副本边界需外部锚点确立新鲜度(§6a.9) |
| 持久新鲜度锚点(durable freshness anchor) | 组织/部署层提供的持久锚点,跨重启/恢复/副本边界确立最新权威头的新鲜度(单调 epoch / 持久锚点 / 签名 checkpoint / 共识背书);执行边界据此判定恢复状态是否足够新鲜(§6a.9) |
| 授权基础(authorization basis) | 某权威的来源——建立/重建该权威的 root grant 或独立验证的 re-authorization 决策对象;区别于授权根(有权建立它的 principal)与起源权威链(谱系)。 |
| 授权根源(authorization root) | 有权建立/重建某授权的 principal/authority;授权「可行使化」转移的事件 actor MUST 归因于它(§6a.10) |
| 委派链(delegation chain) | 多个授权关系沿「授权根 → 中间节点 → 被授权主体」的组合(§6b) |
| 有效权威(effective authority) | 主体实际可行使的权限;MUST ⊆ 起源权威链(§6b) |
| 起源权威链(authority chain) | 从授权根到被授权主体的完整授权谱系;有效权威 MUST 是它的子集(§6b) |
| 委托权威不变量(delegated-authority invariants) | 委派链的五条安全不变量 INV-01~05(权威不放大/溯源连续/窄化继承/传递撤销/能力边界轴,§6b) |
| 转移合法性(transition validity) | 引擎验证转移:仅执行声明的转移、值属枚举、未声明转移不执行(fail-closed) |
| as_of | 引擎注入的求值时刻(UTC,E9) |
| 事实对象(context) | 求值输入,承载 Entity 当前状态(§7.0.1) |
| 求值上下文(eval_profile.context) | 求值选项之一(guard/analysis,E12 折叠方向,§8.2a.1b)——与事实对象 context(§7.0.1)为不同概念,仅键名相同 |
| fallback 决策 | 无规则命中时 metadata.decision 的兜底裁决(§2.2) |
| NFC | Unicode 规范化形式 C(字符串归一,E10) |
| ReDoS | 正则拒绝服务攻击;match 节点 MUST 输入长度上限 + 线性时间引擎防护(§7.3(d)) |
| half-even | 银行家舍入(ROUND_HALF_EVEN),E2 定点小数输出舍入 |
| 空值传播 | 字段缺失统一返回 false 的安全失败语义(E11) |
| 求值口径 | E2 定点小数的运算输出精度(scale=14 + half-even 字符串序列化);不进入 canonical_tree 哈希 |
| 编码口径 | §8.2 数字字面量的 canonical 序列化(带类型对象 {"n":"<十进制字符串>"},非 JCS number);进哈希 |
本规范与既有规则 / 授权标准的关系(设计取舍对照,非规范性):
| 标准 | 领域 | 与 ERDL 的关系 |
|---|---|---|
| CEL(Common Expression Language,Google) | 通用表达式求值 | 同为确定性表达式语言;CEL 面向运行时求值,ERDL 面向规则声明 + 密码学审计(canonical_tree + DO 哈希、逐字节跨实现验证),求值只是其中一环 |
| DMN(Decision Model and Notation,OMG) | 业务决策建模 | 同为声明式决策;DMN 面向业务建模与执行引擎,ERDL 强调确定性内核 + 可独立重算的哈希证据链(§8),并显式限定表达式树为封闭内核(30 运算符 + 受控扩展 profile) |
| XACML(eXtensible Access Control Markup Language,OASIS) | 访问控制策略 | 同为声明式授权策略;XACML 面向策略决策点(PDP)架构,ERDL 面向逐字节一致的求值语义与可哈希审计(§8.2a),且不绑定特定部署拓扑 |
| UCAN(User Controlled Authorization Networks) | 去中心化能力授权 | 同为授权链表达;UCAN 以委派链承载能力,ERDL 的 §6b 委托权威安全模型(INV-01~05)与之互补——UCAN 表达「谁授予什么」,ERDL 提供「授权状态的可验证裁决」(§6a FSM + 审计链) |
| OAuth 2.0 Token Exchange(RFC 8693) | 令牌委派 | 同为委派 / 委托;RFC 8693 定义令牌交换协议,ERDL 不定义传输协议,只定义授权决策与状态的确定性语义——可作为 RFC 8693 之上、决定「换发是否允许」的裁决层 |
设计取舍(要点):ERDL 的关键取舍是把「确定性内核」与「书写形态」分离——多种书写形态(Simple / Expression / 决策表)编译到唯一表达式树(E7),从而换取跨实现逐字节一致的求值结果与哈希(§8),这是 CEL/DMN/XACML 均未作为首要目标的属性;代价是表达力受封闭内核约束(30 运算符 + 受控扩展 profile),超出内核的需求走函数委派(附录 D,Grade C)。
| 版本 | 日期 | 变更 |
|---|---|---|
| v2.3 | 2026-10-06 | §7.0.1b 新增事实来源类型学(informative 五类:system_observed/llm_asserted/user_input/config/derived)+ 事实中立立场设计说明(provenance 不得(MUST NOT)进求值语义与哈希原像);§7.0.1a 字段契约新增信息性 provenance 字段;§8.2a.1b 与附录 E 补 context 双义消歧注(事实对象 vs 求值上下文;键名改名 evaluation_context 列为 v2.4 候选收口项) |
| v2.3 | 2026-10-06 | 正文对齐复审收口实现(收口决定补落正文):§8.2a.1 新增 DO 子结构键集/键序表(S1)与 DO 哈希域分隔前缀 erdl-eval-do-v3:;§8.2a.1a 规则规范对象补规则文本五字段(M5)、rule_set_hash 原像补 on_indeterminate(S3)、排序键统一为 (ring, priority, override, 定义序);§8.2a.1b eval_profile 移除 engine_id(S4,仅随传输信封携带);§8.2a.2 转移记录补 fired/reason(M7);§7.0.3 输出契约补 indeterminate_rules 行;§7.1a 补 tier 0–2 拦截锁定(locked);§10.2 示例 ruleId → rule_id |
| v2.3 | 2026-10-06 | 复审收口(S1–S6 + M1/M2/M5/M7/M10,破坏性):S2 数字字面量 canonical 编码改为带类型对象 {"n":"<十进制字符串>"}(消除数字/字符串碰撞),树哈希加版本化域分隔前缀 erdl-tree-v3:;S1 新增 DO 子结构键集/键序定义 + 命名统一 snake_case + 新增 indeterminate_rules;S4 engine_id 移出 DO 哈希原像(仅进信封);S3 新增 metadata.on_indeterminate(缺省 REQUEST_HUMAN,tier 0–2 可配 DENY),unknown 不再 fail-open;S5 决策合并改为集合式 fold(置换不变)+ override 外环不得(MUST NOT)覆盖内环(ring(o) ≤ ring(r))+ WORKFLOW 取消终端(仅 EMERGENCY_HALT 短路)+ tier 0–2 拦截 locked;S6 条件级 scope 可写 + 计数键纳入规则名/窗口/scope 值;M1/M2 缺失字段算术与空聚合折叠为 unknown(非 false/错误);M5 规则文本(instruction/reason/correction/explanation/alternative)进 rule_set_hash;M7 转移审计记录新增 fired/reason 字段;M10 资源限制违规记入 eval_warnings |
| v2.3 | 2026-10-06 | fact 字段名全量统一为 context(与引擎参数名、RFC-002 治理层字段名一致);§7.0.1 消除 context.context 套娃(自由字段直接挂顶层) |
| v2.3 | 2026-10-04 | §6a 引擎实现(参考实现落地):新增 state-definition.ts(加载时校验:state/transitions 结构、同变量冲突、state/event 引用检查、守卫白名单)与 state-machine.ts(事件驱动 FSM:eager FIFO、event_id 去重、守卫原子求值、genesis/transition/transition_error 审计链、按需 state_snapshot);Evaluator 新增 stateMachine 选项 + state.* 受控读取 + EvaluationResult.stateSnapshot |
| v2.3 | 2026-10-04 | §7.0.1a 新增字段契约(EntityFieldContract)+ §7.3(a) 新增严格模式(strict mode)——声明字段类型 + 比较节点类型不匹配在严格模式下记 warning,修复审计隐患「fail-open」与「静默 false」 |
| v2.3 | 2026-10-04 | §8.2a.1 求值结果 DO 字段序新增 context(输入事实对象)——修复「DO 哈希原像缺输入事实」的规范缺口,使「针对这份输入作出的这个决策」可独立复算;context 在字段序首,语义为「输入 → 决策」完整闭环(breaking:DO 哈希原像字段序变更) |
| v2.2 | 2026-09-28 | §7.1 措辞澄清 + override 缺席排序对齐:item 2 明确「同 priority 按 override 级别排序(critical > high > normal > low)」;item 6 统一「when 为字面量 true」;override 缺席排序对齐「默认 normal」(erdl-formal 缺席 rank 4 → 2)——清除 erdl-vectors#4 待确认-A/B/C |
| v2.2 | 2026-09-27 | §7.1 第 5 条补收紧方向明示并修正 override 挂 DENY 的语义:DENY / ROLLBACK / QUARANTINE 覆盖 ALLOW(收紧)是「不得(MUST NOT)覆盖到更不安全状态」的默认推论,不比较 ring、无需 override(override 挂在收紧决策上无效);override 仅作用于放松方向(DENY → ALLOW)——回应 erdl-vectors PR#5 R08 的规范歧义 |
| v2.3 | 2026-10-07 | 移除 AV-15(re-authorization provenance,§6a.10)与 AV-16(multi-root basis-scoped revocation,§6b.4)及 §6b.4 整节——多根组合撤销与重授权溯源非当前阶段核心,委托授权对抗向量收敛为 AV-01~14 |
| v2.2 | 2026-09-17 | 落实 conformance 向量 AV-15(re-authorization provenance,§6a.10)与 AV-16(multi-root basis-scoped revocation,§6b.4)——各为 attack(→DENY)/legal(→ALLOW)双面的单一向量;§6a.10/§6b.4 的 V-STATE 标注对应向量编号;§6b.5 对抗向量族由「AV-01 |
| v2.2 | 2026-09-16 | 新增 §6b.4 按授权基础收敛的撤销(basis-scoped revocation,多根组合)——主体有效权威是其当前有效各授权基础可导出权威的并集;revoke(basis-X) 移除恰恰好 basis-X 可导出的权威(不多:下游完整传递闭包;不少:其他授权基础的贡献保留);MUST NOT 把主体权威归约为单一主体级全局 revoked/authorized 位(禁止过撤销与欠撤销);存活授权基础 MUST NOT 保留只属于已撤销谱系的权威;将 INV-04 的「下游子树」细化为按授权基础相对;术语表新增 authorization basis |
| v2.2 | 2026-09-15 | §6 决策类型补设计说明:13 种决策类型的设计思想——AI 时代发挥 LLM 价值而非简单放行/拒绝;五类分组(放行与拦截 / 引导而非放弃 / 人机协同 / 安全兜底 / 过程性) |
| v2.2 | 2026-09-15 | 新增 §6a.9 最新权威头新鲜度(反回滚,集成要求)——成功重放验证 ≠ 状态最新(区分完整性/来源与新鲜度);授权敏感副作用前执行/恢复边界 MUST 确立 {state_version, transitions_head} 是最新权威头(未被后续权威状态取代),机制实现中立(单调 epoch/持久锚点/签名 checkpoint/共识背书);无法确立新鲜度即 fail-closed;V-STATE 增 authorized@N/HN → revoke@N+1/HN+1 → 还原历史前缀 → 重放通过 → 拒绝效果 的反回滚向量 |
| v2.2 | 2026-09-15 | §6a.9 分层澄清(回应 Finding 2 收尾):持久新鲜度锚点由组织/部署层负责提供;保留 fail-closed 属性——执行边界无法确立恢复的 {state_version, transitions_head} 相对权威持久化状态足够新鲜时,受保护效果 MUST NOT 继续执行;成功重放/完整性验证不构成「权威仍最新」的充分证据 |
| v2.2 | 2026-09-15 | 新增 §6a.10 授权建立/重建的根源绑定(授权根源 provenance,集成要求)——授权「可行使化」转移 MUST 有授权根源(actor 归因于有权建立该授权的 principal);撤销后 re-authorization MUST 有新的有效授权基础;被授权主体 MUST NOT 自恢复被撤销授权;授权根源资格判定是边界/组织层集成义务(无法确立即 fail-closed);V-STATE 增 授权根建立→撤销→非授权根 re-authorize→尝试效果(DENY)与 授权根重建→ALLOW 两向量 |
| v2.2 | 2026-09-15 | 新增 §6b 委托权威安全模型(组织行为层)——总纲「委派不得制造权威」;五条不变量 INV-01 |
| v2.2 | 2026-09-14 | 新增 §6a.8 执行边界 check/act 原子性(集成要求)——授权依赖 §6a 状态的安全敏感副作用,执行边界 MUST 重校验或封闭同步边界,使决策与效果之间无授权谱系状态变更落地;引擎暴露重校验原语、边界履行义务(保留 E1 纯性);V-STATE 增 authorized@N → ALLOW@N → revoke@N+1 → 尝试执行效果 的 fail-closed 向量 |
| v2.2 | 2026-09-12 | 新增 §6a 状态块与状态转移(受控状态源):state/transitions 两个可选顶层字段;状态受控注入(state.* 复用 field 节点,不新增节点);资源上限(≤4 变量/2–4 枚举/≤256 组合/≤32 转移规则/≤16 事件名/≤8 键 payload) |
| v2.2 | 2026-09-12 | 状态转移审计闭环:转移链 + 快照 + 合法性 + 出处锚定;state_snapshot 扩展为 {values,state_version,transitions_head},键按状态变量名码点升序 + 字符串 NFC 规范化 |
| v2.2 | 2026-09-12 | 同变量冲突可判定互斥检查((0)-(4) sound 约束:无条件唯一 + 仅顶层合取项作证明依据,宁拒勿纵) |
| v2.2 | 2026-09-12 | §6a.7 事件与转移求值上下文:事件对象 event_id/on/at/actor/payload;守卫只读 state.+event.,不读自由 context |
| v2.2 | 2026-09-12 | 事件处理原子性(按定义顺序逐条求值→遇首个 EvaluationError 即停止不提交任何 set fail-closed→全过则一次性提交;单事件内顺序不影响结果);事件注入认证(actor 进审计记录、未认证拒绝);并发串行化(事件处理与 evaluate 互斥);genesis 记录(initial 生成初始快照 + 规范树哈希) |
| v2.2 | 2026-09-12 | 加载时校验全集(任意表达式位置引用未声明 state. |
| v2.2 | 2026-09-12 | decision 更名 audit_as(仅审计承载、不参与求值/短路,取值收窄为 {ALLOW,NOTIFY,DELEGATE,ESCALATE,REQUEST_HUMAN});transitions 增 enabled(默认 true)、reason 约束([a-z][a-z0-9_]{0,31} + 文档内唯一);state 增 display_name(双语,gloss 取 en 回退 name) |
| v2.2 | 2026-09-12 | transitions.when 节点白名单(Simple 条件 + 时间节点,禁量词/算术/聚合/fn/within/rate);状态机无时间触发器(新鲜度靠外部 sweeper 或守卫时间比对) |
| v2.2 | 2026-09-12 | §7.0.2 求值算法补事件先行声明(步骤 0)与 catch-all 惰性两趟、修正 WORKFLOW 交叉引用(状态机区分 §6 工作流 / §6a 授权);§7.0.3 新增 state_snapshot 输出字段(进哈希原像);E1 扩展授权状态快照为受控外部输入;术语表补状态变量/状态空间/状态转移/受控注入/state_snapshot/转移合法性 |
| v2.1 | 2026-09-12 | §8.2 字面量规范的数字 canonical 编码定为 JCS(RFC 8785)IEEE 754 number 序列化(对齐参考实现);区分「求值口径」(E2 定点小数)与「编码口径」(§8.2 canonical 序列化);E12 明确 Guard 上下文语义(Guard 上下文覆盖所有 tier 一律 fail-close;非 Guard 上下文 tier≤2 fail-close、tier 3–5 折叠 false);术语表补「非 Guard 上下文」「求值口径」「编码口径」;§7.3(a) 明确字段缺失算术分界(比较节点→false、算术节点→EvaluationError);§7.0.2/§7.0.3 与 E12 口径统一 |
| v2.1 | 2026-09-10 | §7.3(c) 明确一致性比较的是 scale-14 定点值数值(尾零不敏感:"35" ≡ "35.0"),而非字符串拼写——十进制字符串是编码,不是比较单位;§7.3(a) 将 warning 不对称扩展至逻辑节点(and/or 非布尔操作数静默折叠)与量词(all/any/none 非数组操作数记 type_mismatch);§7.3(b) 明确量词非数组 over;§7.3(d) 明确 ReDoS 折叠(false + regex_re_dos、errored: false);§7.3(g) 新增:E4 结构性资源限制违规抛出(value: null + threw: true),E5 互斥记录 value: true;§5.5 补 gloss 渲染细节(not(eq) 规范化、字符串/list 字面量带引号、算术带括号);§7.3(a) 明确 errored 口径:in/字符串/length/aggregate 记 type_mismatch warning 但 errored: false(仅 warning,非 E3 的 EvaluationError) |
| v2.1 | 2026-09-09 | §7.3(a) 补 warning 不对称标注(比较/between 静默 false 无 warning;in/字符串/length/aggregate 记 type_mismatch);§5.5 gloss 渲染模板英文措辞对齐实际渲染(in/between/length/match/epoch_ms/date_part/date_add/aggregate/quantifier/var);§5.5 gloss 渲染语言定为英文 canonical(G3 display_name 取英文值;中文模板为展示层可选投影,不参与跨实现验证);§7.2 E3 / §7.3(a) / 附录 E 补 errored 求值错误标志语义:EvaluationError(除零/非法日期/元数错误/算术类型不匹配)→ errored=true(即便 E12 折叠为 false);类型不匹配比较与空值传播 → errored=false(非错误) |
| v2.1 | 2026-09-05 | §7.1 新增第 6 条:空条件规则(catch-all/兜底)MUST NOT 改写显式条件规则所确立的决策(双向);兜底规则仅在无显式条件规则命中时生效;§7.3(f) 明确 date-time 输入解析为整秒精度(不支持小数秒),跨实现对齐 |
| v2.1 | 2026-09-04 | §7.3(d) 明确安全语法子集为「正则语言」:禁止反向引用(\1–\9、\k<name>)与环视((?=)/(?!)/(?<=)/(?<!));明确内联大小写标志不提供(匹配始终大小写敏感) |
| v2.1 | 2026-09-03 | §4.1 新增 category(规则级覆盖)、enabled(启用标志)、correction(CORRECT 纠偏文本)三个可选字段,补全字段表与固定顺序;§7.0.3 补 primary_correction 来源交叉引用。协议 erdl/v2 不变;规则格式版本 2.0.0 → 2.1.0(新增可选字段,Non-breaking) |
| v2.0 | 2026-08-30 | 定稿 |
concordia-python-expression),仅凭 spec + 契约的 Python 实现逐字节验证 V-ENGINE 表达层全部 240 条向量;其 RESULTS.md 记录了 16 处 spec 歧义(A1–A16),其中四处暴露了现已修复的真实缺口。within/rate)。他对求值器的 review 揭示了状态突变的 temporal_state 证据缺口与 total_evaluated 计数漂移——现均已修复并由一致性向量覆盖。委托授权安全不变量(INV-01–INV-05)与相关对抗一致性向量(AV-01–AV-14)由他提出,随后在与 OpenOBA 的技术评审与协作中进一步细化与完善,并成为 OpenOBA 多 Agent 治理方向的基础。他还为委托授权 conformance 贡献了独立 Python runner(ravindra-annam-python-independent,Python 3 stdlib spec-only 独立表达式树求值器),逐字节验证 AV-01~AV-14 对抗向量(14/14)。他还贡献了首个独立 §7.1 resolution runner(PR #5):13 条 neutral V-RESOLVE 向量(R01–R13)+ spec-only runner,其推导暴露并解决了收紧方向边界(R08/R13),现已在 §7.1 第 5 条明示。© 2026 深圳市秒镜科技有限公司 · MIT License