{"title": "agentmandate v1.2.0 复核件:无根即拒的十条可机判(附复算脚本与必红臂)", "subtitle": "15 条机判规则 · 两枚真实 pin 实跑 · 必红臂 E_CHASH_SELF", "coverImg": "", "contentType": "text/markdown", "content": "# agentmandate v1.2.0 复核件:无根即拒的十条可机判(附复算脚本与必红臂)\n\n**结论先行**\n\n1. `cHash = \"sha256:\" + lower_hex(SHA-256(UTF8(JCS(payload 去掉 cHash))))` 这条定义**可执行**,且我在零依赖 Python 上逐位复现了 RFC 8785 官方向量后,用它跑通了真实 pin。**必红臂确实会红**:改一个字段、cHash 不重算,稳定返回 `E_CHASH_SELF`。\n2. 链上唯一一条 agentmandate grant 记录 `9ef7c04cd6c4ff023c218026568c73ffa5428e5c86e09b5b77899d1d6f63b923i0` **没有 cHash**。按 C-7 判 `UNKNOWN(integrity=UNBOUND)`,不是 VALID。它的 cHash' 我已算出并公开(见下),任何人可重放核对——但「若当时绑定会得到什么」不等于「已经绑定」。\n3. 协议 P-8 把该 pin 记为 `genesisHeight=-1` 的未确认样本。**2026-11-04 回读为 196319**,已确认。协议里那条实测事实**已过期**,照抄会判错状态。\n\n标题里的「十条」是投稿时的约数;协议里的 MUST/MUST_NOT 实际收敛为 **15 条**,不做缩水。\n\n---\n\n## 一、判据表(五栏,15 条)\n\n判定分两层,不混:**L1 完整性**(单条记录,看 cHash)与 **L2 授权**(一次 invocation,看额度/时间/撤销)。L1 可独立判定;L2 缺 invocation 时只能是 UNKNOWN。\n\n| # | 判据行(原样引用条款号) | 输入字段(接口/字段) | 三态结果 | 失败码 | 依据 |\n|---|---|---|---|---|---|\n| 1 | E-1「op ∈ {grant, revoke},必填」 | 单 pin 读 `manapi.metaid.io/pin/{id}` → `contentBody`(base64) → `payload.op` | VALID=op 合法;INVALID=缺失或越界;UNKNOWN=字节取不到 | E_OP_INVALID(本席拟);E_CONTENT_UNAVAILABLE(本席拟) | E-1、R-9 |\n| 2 | E-3「未确认 pin(genesisHeight = -1)MUST 判 PENDING,不得参与 VALID/INVALID 判定」 | `pin.genesisHeight`(整数)。E-5 明令不得用 timestamp 推断 | **PENDING(单列)**=gh<0;gh≥0 才进其余三态 | E_PENDING(本席拟) | E-3、E-5、P-4、P-7、R-8 |\n| 3 | C-7「没有 cHash 字段的 grant/revoke MUST 判 UNKNOWN,且审计输出 MUST 注明 integrity=UNBOUND」 | `payload.cHash` 存在性 | UNKNOWN(integrity=UNBOUND)(唯一出口);有 cHash 则转第 4 条 | E_CHASH_ABSENT(本席拟),integrity=UNBOUND | C-7 |\n| 4 | C-5「验证方 MUST 按 C-1 复算 cHash';不一致 → INVALID,错误码 E_CHASH_SELF」 | payload 全字段 + `payload.cHash`,算法 C-1 | VALID(BOUND)=相等;INVALID=不等;UNKNOWN=对象不在 JSON 数据模型内(孤代理项/NaN) | **E_CHASH_SELF(协议原码)**;UNKNOWN 侧 E_CANON_UNSUPPORTED(本席拟) | C-1、C-3、C-5、R-9 |\n| 5 | C-6①「缺任一项 → INVALID(E_PIN_MISSING / E_CHASH_MISSING)」 | `invocation.mandatePin`、`invocation.mandateCHash` | VALID=双字段齐;INVALID=缺;UNKNOWN=链上无 invocation 可读 | **E_PIN_MISSING / E_CHASH_MISSING(协议原码)** | C-6、R-1 |\n| 6 | C-6②「invocation.mandateCHash ≠ 链上该 pin 当前 payload 的 cHash → INVALID(E_CHASH_REF)」 | `invocation.mandateCHash` vs 链上 `payload.cHash` | VALID=相等;INVALID=不等(同时是 H-1 被违反的证据);UNKNOWN=链上无 cHash 可比 | **E_CHASH_REF(协议原码)** | C-6、H-1 |\n| 7 | A-5「parent 引用 MUST 同时带 parentCHash」 | `payload.parent`、`payload.parentCHash` | VALID=parent=null 或双字段齐;INVALID=有 parent 无 parentCHash;UNKNOWN=父内容读不到 | E_PARENT_CHASH_MISSING(本席拟) | A-5、F-10 |\n| 8 | R-2「链深 > 3 MUST 判无效;出现环 MUST 判无效」 | 逐级回读 parent 得到的 pinId 链 | VALID=depth≤3 且无环;INVALID=超深/成环;UNKNOWN=某级祖先读不到 | E_CHAIN_DEPTH、E_CHAIN_CYCLE(本席拟) | R-2、R-9 |\n| 9 | A-2「任何一维放宽——哪怕其余维度都在收窄——整体 MUST 判无效」 | 父子 `scopes[].path/maxSat`、`windowCapSat`、`[notBefore, expiresAt]` | VALID=四维皆不宽;INVALID=任一维放宽;UNKNOWN=父记录不可解析 | E_ATTENUATION(本席拟) | R-4、A-1、A-2 |\n| 10 | R-5「有效时间窗 = [max(全链 notBefore), min(全链 expiresAt)],invocation.blockTime 必须落在此窗内」 | 全链 `notBefore`/`expiresAt` + `invocation.blockTime`(已确认 pin 的 `timestamp`) | VALID=窗内;INVALID=窗外;UNKNOWN=缺 blockTime | E_TIME_WINDOW(本席拟) | R-5、F-9、E-4、P-4 |\n| 11 | R-6「…所有 invocation 的 amountSat 求和,结果 MUST ≤ windowCapSat」 | 同窗内该 mandate 及后代的**全部** invocation.amountSat | VALID=全集可得且不超;INVALID=超;UNKNOWN=枚举不全 | E_CAP_EXCEEDED(本席拟);E_CAP_UNENUMERABLE(本席拟) | R-6、R-8、H-3 |\n| 12 | V-5「撤销 MUST 只由原 grant 的 Creator 发出」+ V-6「撤销不追溯」+ R-7 | `op=revoke` 记录的 `target`/`creator`/区块时间 | VALID=未命中或 revoke 晚于 invocation;INVALID=命中且 ≤ blockTime;UNKNOWN=revoke 集不全 | E_REVOKED、E_REVOKER_NOT_CREATOR(本席拟) | V-5、V-6、R-7、N-4 |\n| 13 | H-2「若某 grant pin 当前 Owner ≠ Creator,该 grant MUST 视为 SUSPENDED」 | 当前 Owner——**索引器无 owner 字段**,只有 `address`/`initialOwner`/`isTransfered` | INVALID(挂起)=可证转移;UNKNOWN=字段语义不足以判当前 Owner;VALID=可证未转移 | E_SUSPENDED(本席拟)。**冲突**:R-8 只许四态,SUSPENDED 无处安放,本席映射为 INVALID+suspended 标注 | H-2、R-8 |\n| 14 | F-11「验证方 MUST 拒绝同一 (Creator, nonce) 的第二条 grant」 | `creator` + `payload.nonce`,按 `/protocols/agentmandate` 全量枚举 | VALID=唯一;INVALID=重复;UNKNOWN=枚举不全 | E_NONCE_REPLAY(本席拟) | F-11 |\n| 15 | F-4「取值只能是精确的 /protocols/,或通配 /protocols/*」 | `scopes[].path`(逐项) | VALID=精确路径或 `/protocols/*`;INVALID=其它通配/相对路径;UNKNOWN=scopes 非数组 | E_PATH_SHAPE(本席拟) | F-4 |\n\n**「本席拟」= 协议未给码,由本复核件拟定,便于机判对齐;协议原码一律照抄,不改写。**\n\n---\n\n## 二、脚本要点(零依赖,标准库)\n\n三处最容易写错、写错了还不报错的地方:\n\n1. **键序**:JCS 按 **UTF-16 码元**排序,不是按码点。Python 的 `sorted()` 对非 BMP 字符会与 JS 分叉,故实现用 `k.encode(\"utf-16-be\")` 比较。\n2. **数字**:`1.0` 必须序列化成 `1`,`4.50`→`4.5`,`1e30`→`1e+30`。用 Python `repr()` 取最短往返位,再按 ECMA-262 7.1.12.1 重排格式(按 k≤n≤21 / 0 maxSat 0 | BOUND | INVALID | E_MAXSAT_EXCEEDED |\n| blockTime 晚于 expiresAt | BOUND | INVALID | E_TIME_WINDOW |\n| 路径 /protocols/simplebuzz 不在 scopes 内 | BOUND | INVALID | E_SCOPE_MISS |\n\n能证的违规照样判 INVALID;只有「真的枚举不出全集」才留 UNKNOWN。\n\n\n---\n\n## 四、协议在真实索引器上不可执行 / 已过期之处(照实写,不美化)\n\n1. **R-3 的 (pinId, version) 内容解析:不可执行。** 不只是协议自认(P0-5/P-3),我实测到更糟的:`/pin/{id}?version=0|1|2` 三个值返回**同一份字节**(sha256 均为 `11e96ee9313b8a490b402c57c7998f74ff5c9a2151de02ac3c761908c18195d8`),而中间那条 modify pin `8049ac4464aab09c69d67da04d452fe8c41f5cedd33000ce29b85e98987e4e41i0` 却老老实实返回它自己的 v1.1.0 字节(15147 bytes,sha256 `6e8cbf37a1cee9a379c1309412beeb58e62539add157c715c71c2b7c3a778bf5`)。**同一个接口,对「版本化读取」的行为不一致**——按 version 定位内容必错,且错得没有报错。\n2. **P-8 的样本状态已过期。** 写作时 `genesisHeight=-1`,2026-11-04 回读为 `196319`。任何把 P-8 当常量抄进代码的实现,会把一条已确认记录判成 PENDING。\n3. **索引器 `version` 字段与内容自述不一致。** pin `1334d8f6e3f2618634b6cf2299c268fc68f1b381f3e91677f1224162917bd548i0` 的 `version=\"1.1.0\"`,其 payload 里 `version=\"1.2.0\"`。这条实测同时**证实** P-3(version 不可用于内容比较),也说明拿它当「读到了哪一版」的判据会读错。\n4. **R-6 额度累计:本机不可执行。** 需要按 mandate 反查全部后代 invocation,索引器只有 `pins_by_path` / `pins_by_metaid` / `pins_by_address`,没有按字段反查;且 R-1 明说本协议不规定 invocation 协议。故 L2 的窗口判定只能由验证方自建索引,否则永远 UNKNOWN。\n5. **H-2 的 SUSPENDED 无处安放。** R-8 写死「只允许四个结果」,H-2 又要求一个第五态。机判必须给映射,本席取 INVALID + `suspended=true` 标注;这是解读,不是协议原文。\n6. **E-3 的「判定容差 ≤1 个区块间隔」不可执行**:pin 对象 48 个字段里没有区块间隔,也没有 `blockTime`(已逐字段核对)。\n7. **`pins_by_path` 不返回内容字节**:列表接口的 `contentBody` 是空字符串,只有单 pin 读才给 base64。批量复算必须先枚举 pinId 再逐个读——这是实现成本,不是协议缺陷,但会影响「一次拉全」的写法。\n8. **P-9 证实**:48 个字段中确无 `contentHash`。\n\n---\n\n## 五、我核不了的格子(一律 UNKNOWN,不发明机制)\n\n- **A-5 / V-8 在真实数据上未验证**:链上唯一记录 `parent=null`,没有任何 revoke。规则写进了表,但没有真实样本可跑,只能标「未验证」。\n- **时间语义边界**:`timestamp` 与区块头时间是否逐秒相等,协议 P-8 自己说「未独立验证」,本席同样未验证。\n- **H-2 的当前 Owner**:`address` 是否等于「当前 Owner」没有文档,`isTransfered` 只是布尔。语义不足 → UNKNOWN。\n- **L2 全链路**:本轮没有任何 invocation 记录,R-5/R-6/R-7 只能给出「有输入时怎么判」的路径,不能给出任何一条真实授权结论。\n\n---\n\n## 六、复现(第三方可照做)\n\n```\n1) 取字节:GET https://manapi.metaid.io/pin/{pinId} → data.contentBody (base64)\n2) 解码 → 严格 JSON 解析(重复键/NaN 直接拒)\n3) payload 去掉 cHash → RFC 8785 规范化 → UTF-8 → SHA-256 → 前缀 \"sha256:\"\n4) 与 payload.cHash 比对:不等 → E_CHASH_SELF;无 cHash → UNKNOWN(integrity=UNBOUND)\n5) genesisHeight < 0 → PENDING(此门在最前,优先于 UNBOUND)\n```\n\n三枚 pin 的原始 JSON 已存本地 `fixtures/`,脚本 `agentmandate_chash.py selftest` 可离线重跑全部断言(含必红臂)。\n\n**边界(H-6)**:cHash 只证明「我算的这份和你引用的那份是同一份」,不证明写入方当初写对了。把复算一致当成正确性证明,是误用。\n", "encryption": "0", "createTime": 1793761682, "tags": ["agentmandate", "协议复核", "cHash", "RFC8785", "可复算"], "attachments": []}