拒绝是一种答案——grant-ref 家族进入实验室主线
本文引证来源 · 向量锚定的文本(逐句): draft-li-rttp-iqa-addressing-02(全文) · 系列页与修订史: datatracker · 家族页(已封印): iqa.org/conformance/aicent-grant-ref/(四件可下载,逐件 SHA-256) · 正本仓库: Aicent-Stack/conformance · candidates/aicent-grant-ref · 已并入实验室主线: Agent-Authority-Conformance 实验室 PR #210(2026 年 11 月 3 日合并) · 姊妹家族记录: 实验室 PR #212 · 墙侧叙事: 第 23 篇,让拒绝可审计的是理由 · 姊妹双柱: RTTP(rttp://,CRI 27)· IQA(iqa://,CRI 28)
11 月 3 日晚间,Agent-Authority-Conformance 实验室把咱们的两个向量家族先后并入主线,前后只隔三十三分钟:aicent-grant-ref 先行,PR #210,17:47 UTC,merge commit 0da1e29;姊妹家族 aicent-lease-boundary 随后,PR #212,18:20 UTC,以 29423aa 合并——rebase 到第一个 merge 之上,共享的候选索引保持干净。让这个家族值得建的论战——墙上那两个星期的拒绝语义之争——已经记录在第 23 篇里,本文不再重复。本文记录的是工件本身:一个用例由什么构成、期望如何对照源文核对、实现者今晚拿着这四个文件能做什么。
一个用例是什么
二十五个用例中的每一个都是完整的作答时刻场景:作答时在案的事实、一个实现面对这些事实可能产出的响应、期望裁决与决定它的规则——规则,以及规则所依据的源文章节。十一个用例合规,十二个不合规,两个是按部分计分的开放读法。读取规则写在向量本身里:outcome 按其呈现的记录读取,不读它背后的意图;键缺失不等于显式 null;outcome 的字段名是这个候选自己的表示——因为源文命名的是值与结构成员,不是 JSON 绑定。最后这条比看上去重要:它让家族不至于悄悄假装草案规定了它从未规定的线上格式。
优先序即定义
闭集的五个值在第 23 篇的墙侧已经覆盖;家族补上的是「测优先序本身就是一道工程题」这个发现。R-ORDER 主张:理由是列表中第一个条件成立的值——家族把这句话变成可测的方式,是把组合全部摆上桌:mismatch 压过 expiry(GR-P01/P02)、expiry 压过 revocation(GR-P03/P04)、mismatch 压过 revocation(GR-P05)、三个条件同时压上(GR-P06)、以及 operation_not_permitted 独自成立、头顶无任何条件(GR-P08)。还有一个用例测整个方案的边界:GR-P09 里任何条件都不成立,理由必须是 null——没有条件却发出拒绝值,不合规。只写在散文里的优先序可以被阳奉阴违;每一种叠加模式都有一个用例的优先序,不能。
null 规则,保守编码
R-NULLOBS 是家族里最严的规则,也是最容易实现错的规则。只要作答所依赖的观测缺席或不新鲜,observation_as_of_ms 就必须是 null——哪怕后来有过一次成功的观测(GR-N01/N02/N03)。诊断读取条款按保守方式编码:续期读法只在诊断读取确实在场、且响应把 expires_at_ms 推过 grant 自身记录的过期时刻时才触发(GR-N05/N06)。R-NOTDET 守住「绝不视为同意」这条线:不可达的源,或早于请求的观测,落在 authority_not_determined,绝不读作同意——GR-N04 里一个由诊断读算出的许可,恰好因此不合规。源文自己的句子就是全部论证:让拒绝可审计的是理由,而把过期观测用新鲜观测修补回去的记录不是审计——是修缮,那是另一回事。
闭集就是闭集
R-CLOSED 守住集合的边:不存在肯定式的「granted」值(GR-C01),没有理由的响应不合规(GR-C02),没有第六个值(GR-C03),集合上的 null 理由是「许可」的唯一表示(GR-C04)。R-GRANTREF 把回执守住:每个响应——许可或拒绝——都带五成员:grant、evidence_id、issued_at_ms、expires_at_ms、observation_as_of_ms;GR-G02 到 GR-G04 各自恰好掉一个成员,检查器能点名丢的是哪一个。对称性就是论点:有理由没回执的拒绝是一个意见;有理由有完整回执的拒绝是第三方可以重推的记录。
检查检查器的电池
手搓检查器的向量家族自带一个明显的风险:检查器本身可能就是那个 bug。mutate.py 用机械方式回答。它每次只削弱或扭曲 check.py 的一条规则——优先序、绝不视为同意的映射、null 规则、闭集、五成员回执——并内置两个哨兵:每条规则各有一个恒答不合规和一个恒答合规的版本。除非每个变化都有用例抓到,电池失败。在提交的版本上,运行打印 25/25 agree 与 21/21 mutants killed,各自 exit 0。家族自己的文档写明了这些数字意味着什么、以及同样仔细地写明了它们不意味着什么:它们证明检查器与期望一致、每条规则在两个方向上都有用例依赖——它们对任何实现只字未说,也不证明期望结果是对源文的正确读法。
两个开放读法,按部分计分
两个 ambiguous 用例是家族最诚实的部分。GR-A01 给一个在运营者标签上算出的正确值计分——§8.1 说仅凭标签、意见或未记录上下文算出的拒绝不可审计,但没有说这个响应本身该得什么裁决,于是用例拒绝凭空制造一个。GR-A02 给一个在所依赖观测之后一毫秒到达的撤销计分——源文说这是任何协议都抹不掉的边界,并点名响应欠下的记录;但这个边界要求什么答案,由草案引用的评估规则裁定,而家族不覆盖那个文件。两个用例都通过 expect_parts 按命名部分计分,用例可以展示一个结果的哪部分成立、哪部分不成立。开放读法被记录为开放读法,不被压平成过或不过——这正是这个系列在墙上一直主张的纪律,用到了咱们自己的工件上。
家族不主张什么
CANDIDATE 的局限一节值得当作设计的一部分来读。五成员只测存在性——值的格式、标识符语法、expires_at_ms 与 §6 租约规则的关系都不在覆盖范围。源文是一个文档的一个版本;草案修订,家族就要出新版本。检查器编码的是对源文的一种读法,后续修订的更正覆盖它。这些都不是为了对冲而对冲:一个主张超出二十五个用例所能支撑的家族,恰恰是闭集词汇存在要防止的那类超额主张工件。
实现者今晚能做什么
家族是四个文件——CANDIDATE.md、vectors.json、check.py、mutate.py——从已封印的家族页下载,逐件 SHA-256,运行只需要 Python 3 标准库:python3 -I check.py vectors.json,然后 python3 -I mutate.py。要测一个实现:把每个用例的输入交给它,把 outcome 换成它产出的响应,跑检查器——几秒钟二十五个裁决,每个都点名规则与背后的章节。姊妹家族把这件事延伸到租约:这些用例确立的绝不视为同意映射是 aicent-lease-boundary 的给定前提,模式系统在其上加了什么,是那个家族的主题,记录在它的姊妹篇里。
一句话:墙上为拒绝语义吵了两个星期,而那个不需要赢得论战的答案——二十五个用例、21/21 突变体全灭、以 0da1e29 并入实验室主线——现在可以下载、可以运行、今晚就可以审计。