效果状态与证据字段
五种状态、双轴信任、Evidence 与 EffectRecord 的字段
这一页是字段级参考。概念与设计理由见 证据与信任, 这里只列字段名、类型、CBOR/JSON 键位、取值判据,以及它们在哪一段源码里被写入或被读出。
权威定义分布在三处:
| 内容 | 源码 |
|---|---|
Status 五个常量、Evidence、Effect、Verifiable() | ANetCore/effect/effect.go |
EffectRecord 及其五个子结构、验收谓词 | ANetCore/tsir/predicate.go |
证据链记录 EventRecord、preimage、GENESIS_PREV | ANetCore/ael/ael.go |
五种状态
Effect envelope 有四个字段(CBOR key 1–4):Status、Record *tsir.EffectRecord、
Message string、Evidence *Evidence。判定可验证性的只有一个方法:
即 OK 但没有 Record 也不可验证。effect_test.go 的用例把这条钉死了:
{"ok without record", Effect{Status: OK}, false}。
Evidence 是可加的,有没有它都不影响 Verifiable()(TestEvidenceOptional)。
判据表
| 状态 | 判据 | Verifiable() |
|---|---|---|
OK | 执行了,并且带回了可核对的效果信号(metric 或内容寻址的 artifact) | 有 Record 时为 true |
UNVERIFIED | 执行了,但当前信任层级下不存在可验证的效果信号。不是失败 | false |
FAILED | 执行失败 | false |
UNAVAILABLE | 调用时刻目标不可达,或本节点无法作答 | false |
PAYMENT_REQUIRED | 提供方愿意做,但要先付款;价格随状态一起返回 | false |
每一种的真实例子与处理方式
OK — Modbus 适配器写 coil 之后 ReadCoils 读回,值与写入一致,才
Metric(metric, got).Requested(...).Protocol("modbus").NativeAck(true).Observed(...).Trust(V2, A1).Verified()
(ANetLink/adapters/modbus/modbus.go)。
读回不一致时它走的是 Fail("readback mismatch: wrote %v, device reports %v"),而不是 OK。
调用方:这是唯一能拿去评估 TSIR 验收谓词的状态。谓词只读 Record,
所以先看 verifiable 字段(daemon 把 Verifiable() 原样写进交付物),再读 metrics。
UNVERIFIED — 两条产生路径都在构造上强制:
ANetLink/sdk/effect.go的EffectBuilder起手就是Unverified;Build()里if e.Status == effect.OK && e.Record == nil { e.Status = effect.Unverified }。ANetLink/runtime/runtime.go在返回路径上:设备Profile.MaxTrust < profile.VerifiableFrom(即< V2) 时把OK降级为Unverified,并在Message为空时写入downgraded: device trust level does not admit verification。
调用方:不得折叠进 OK,也不得折叠进 FAILED。effect.go 的包注释写明合并会污染上游信任核算。
处理方式是要么找一条独立的读取路径把它升到 OK,要么把"未验证"如实往上传。
FAILED — shell 模块(加法 tag shell)的 run 是一个带完整证据的失败例子:
非零退出码返回 Failed 且 Message 为 exit N;超时被杀返回 Failed 且 Message 为
killed after <timeout>。两种情况都仍然带 Record
(exit_code / duration_ms / output_bytes)与 Evidence(observed_state 是命令输出)。
调用方:失败不等于没有信息。Message 与 Record.Metrics 通常够判断是否值得重试。
UNAVAILABLE — 四个已核实的产生点:
ANetLink/runtime/runtime.go:设备离线,Message为device offline; 设备已注册但还没描述自己,Message为device has not described itself yet。ANetLink/adapters/modbus/modbus.go:建连失败(a.client(dev)出错)。ANet/module/shell/provider.go:调用被拒绝时也用UNAVAILABLE—— 调用未携带 身份且allow_local关闭,或调用方不在白名单。这两种都会同时写一条anet.shell.refused到证据链。ANet/internal/daemon/capability.go:本节点没有任何 provider 提供这个能力,且没有配置 auto-reply 时,答UNAVAILABLE,Message为this node does not serve <capability>。 配了 auto-reply 的节点仍然把它交给 agent 处理(TestCapabilityUnresolvableFallsThrough)。 同一文件里还有一条:能力标了价而这个构建没有付款模块(-tags no_x402)时,不做而是答UNAVAILABLE。
第四条是 2026-09 的改动。此前委派一个对方不提供的能力会永远停在 queued:没有错误、
没有效果状态、两条链上都没有记录,委派方分不清"不提供"、"宕机"和"还在做"。
调用方:与 FAILED 分开处理。ANet/module/service/service_test.go 的
TestUnreachableIsNotFailed 专门守着这个区分:连不上是 UNAVAILABLE,
对面返回 HTTP 4xx/5xx 是 FAILED。前者应当换提供方或稍后再试,后者不应当无脑重试。
shell 模块用 UNAVAILABLE 表达"这个节点不接受你的命令",语义上是拒绝而不是不可达。
Message 会明说(this node does not accept commands from <aid>),
但只看状态字的调用方会把授权失败读成临时故障。
PAYMENT_REQUIRED — 由 ANet/internal/daemon/paylink.go 的 answerPaymentRequired 产生:
能力标了价、调用没带付款时,daemon 不执行,而是把 capabilityResult.Status 置为
PAYMENT_REQUIRED,Message 写成 <capability> costs N credits on <network>,
并把 x402 PaymentRequired body 放进 payment_required 字段。
带了付款但结算失败(payer.Settle 报错或 st.Failed != "")时也是这个状态,
Message 是结算失败的原因(internal/daemon/capability.go)。
调用方:读 payment_required.accepts,用 anet delegate <aid> --capability <id> --pay 重新委派。
报价是一次完整应答 —— 它被签名、生成回执、上双方证据链、按普通交付投递,
所以第二次委派是新的一次交互,不是把第一次续上。
一个例外要知道:daemon 调用 provider 时如果 p.Invoke 返回的是 Go error 而不是 effect,
internal/daemon/capability.go 把状态写成字面量 "FAILED" 而不经过 effect.Status
(res.Status, res.Message = "FAILED", err.Error())。取值相同,但这条路径不经过五常量。
Evidence:一次执行的出处
ANetCore/effect/effect.go。八个字段,CBOR 键 1–8,全部 omitempty:
| Go 字段 | JSON / 线上名 | 类型 | CBOR | 含义 |
|---|---|---|---|---|
Requested | requested | string | 1 | 请求的是什么。适配器惯例是 能力=值,例如 light.onoff=on;shell 与 service 模块写的是能力 id 本身 |
Protocol | protocol | string | 2 | 承载这次调用的协议:zigbee / modbus / opcua / onvif / shell / http / taskboard … 无固定枚举 |
NativeAck | native_ack | bool | 3 | 协议本身确认了这条命令。与"效果被验证"是两件事 |
ObservedState | observed_state | string | 4 | 读回实际报告的状态。Evidence 里唯一能装任意内容的字符串通道 —— Requested / Protocol / Quirk 也是字符串,但语义是固定的 |
LatencyMS | latency_ms | int64 | 5 | 毫秒。字段注释是 invoke → effect;EffectBuilder 的实现是 NewEffect() 到 Build() 之间的墙钟时间 |
VerifyTrust | verify_trust | uint8 | 6 | 效果验证档 V0–V4 |
AuthTrust | auth_trust | uint8 | 7 | 身份认证档 A0–A4 |
Quirk | quirk | string | 8 | 本次读数应用过的厂商偏差修正,取值是逗号连接的 quirk id 列表(Applied.IDs() 用 strings.Join(ids, ",")) |
Quirk 的用途在 ANetLink/quirk/registry.go 的 ApplyEffect:修正改了 metric 就打标签。
源码注释:被修正过的值不是设备放到线上的那个值,分不清两者的消费方就无法审计这次修正。
修正只改读数,不改信任(runtime.go 注释:一台谎报单位的设备不会因此更可信)。
渲染成 JSON 的规则
所有对外的面共用一个函数 provider.Provenance()(ANet/provider/provenance.go),
理由写在注释里:因人而异的证据比没有证据更糟。规则是:
verify_trust与auth_trust总是出现,即使是 0。protocol/requested/observed_state/quirk非空才出现。native_ack只在为true时出现。latency_ms只在> 0时出现。
返回 map 而不是 struct 是刻意的:encoding/json 对 map 会排序键,
交付物的字节稳定,回执的 result_cid 才稳定。
一份真实的 evidence(取自生产节点 anet.capability.effect 记录):
跨进程时字段名不变
同一组字段名在四处线协上出现,snake_case 一致:
| 面 | 位置 |
|---|---|
| C1 HTTP/CBOR(ANetLink 北向) | ANetLink/northbound/c1serv/c1serv.go 的 evidenceWire,CBOR 键与 effect.Evidence 对齐 |
| ANetLink 的 MCP 工具 | ANetLink/northbound/mcpserv/mcpserv.go 的 evidenceOut |
| daemon 的 anetlink provider 解码 | ANet/provider/anetlink/anetlink.go |
| daemon 对外的一切结果与证据链 | ANet/provider/provenance.go |
verify_trust 与 auth_trust
定义在 ANetLink/profile/trust.go。两条轴正交:
一台 Modbus PLC 身份弱(A1)但效果验证可以很强;一个云 API 可能把设备认证得很好,却只能 ack 命令(V1)。
verify_trust(V0–V4)
| 档 | 含义 |
|---|---|
V0 | 无法验证 —— 命令进了黑盒 |
V1 | 只有传输层 ack。云 API 的上限 |
V2 | 设备自报的状态读回,经本地链路 |
V3 | 独立的状态确认(另一条读取路径,或另一个传感器) |
V4 | 物理闭环 —— 效果是被测量的,不是被汇报的 |
profile.VerifiableFrom = V2:低于 V2 的效果不允许声称 OK,由 runtime 在返回路径上执行降级。
历史别名仍然有效:type Trust = VerifyTrust,T0 = V0 … T4 = V4。
早期文档里的 T0–T4 阶梯指的就是 verify 轴。
auth_trust(A0–A4)
| 档 | 含义 |
|---|---|
A0 | 未知 —— 没有身份 |
A1 | 网关背书 —— 桥为它担保(例如可信 RS485 段上的一个 Modbus 单元),设备侧无凭据 |
A2 | 协议认证 —— 设备协议认证了会话(OPC UA 安全通道、签名的 MQTT 客户端) |
A3 | 设备密钥认证 —— 每设备一把密钥证明了持有(Matter operational credential) |
A4 | 硬件根信任 —— 证明链到安全元件 / TEE |
只能被压低
任何下游都不能超过传输的上限,任何东西(包括 quirk)都不能把它抬上去。
service 模块是这条规则在配置面上的形态。挂一个本机 HTTP 服务上网时,
服务可以在应答的 evidence 里自述,applyDeclared(ANet/module/service/service.go)
会接受 observed_state / protocol / quirk / native_ack 四项 ——
但不读 verify_trust 与 auth_trust。verify 轴被钉在 V1,auth 轴留在 A0。
TestTrustIsNotTheOperatorsToDeclare 用一个在应答里写 "verify_trust":4 的服务验证这一点:
结果仍然是 verify_trust: 1。
EffectRecord 与 metrics
ANetCore/tsir/predicate.go。五个字段,CBOR 键 1–5,全部 omitempty:
| Go 字段 | CBOR | 类型 | 说明 |
|---|---|---|---|
Artifacts | 1 | []Artifact | Path / SizeBytes / ContentCID / MediaType |
Tests | 2 | []TestResult | ID / Status(1 pass / 2 fail / 3 absent) |
Metrics | 3 | map[string]float64 | 量测值 |
Resources | 4 | []ResourceRef | Kind / ID / MergeClass |
Effects | 5 | []EffectRef | Verb(1 create / 2 modify / 3 delete / 4 call / 5 get)+ Resource |
Metrics 是验收谓词唯一能做数值比较的域:ThresholdClause 的形状是
{Metric string, Op int, Value float64},Op 取 CmpLT / CmpLE / CmpEQ / CmpGE / CmpGT。
metrics 只装 float64。
位置:tsir.EffectRecord.Metrics map[string]float64。
行为:CID、字节块、字符串、列表、嵌套对象都放不进去 —— 编译期就放不进去。
影响:把这些东西塞进 metrics 的尝试只有两种下场,要么编译失败,要么被静默丢弃。
它们的通道是 Evidence.ObservedState(唯一装得下任意内容的字符串字段),
内容寻址的产物走 EffectRecord.Artifacts。
发现方式:ANet/module/service/service.go 的 numbersIn 只把应答的顶层数字提为 metric,
嵌套对象留在 observed_state,注释说明理由是展平会发明字段名。
两条已核实的具体做法:
- ANetLink 的
EffectBuilder.Snapshot(data, mediaType)把 JPEG 字节做成tsir.Artifact{Path: "snapshot", SizeBytes: …, ContentCID: anetcid.SumRaw(data), MediaType: …}。 空帧直接不附:len(data) == 0时原样返回,注释是"空帧不是证据"。snapshot_bytes这个数值 metric 不是Snapshot()加的,是调用它的适配器自己加的 (adapters/onvif/media.go、adapters/hikvision/hikvision.go、adapters/dahua/dahua.go都是Snapshot(data, mt).Metric("snapshot_bytes", float64(len(data))))。 EffectBuilder.StreamURI(uri)把 RTSP 地址做成Artifact{Path: uri, MediaType: MediaTypeRTSP}。 注释说明 URI 是句柄不是内容,所以不做内容寻址;没有实际探过流的适配器必须把效果留在UNVERIFIED。
Build() 的兜底规则:len(metrics) > 0 || len(artifacts) > 0 才构造 Record;
Status == OK 而 Record == nil 时降级为 UNVERIFIED。
证据链记录
ANetCore/ael/ael.go 的 EventRecord。键 1–10 构成 preimage,
记录 id 是 preimage 的 CID,签名也签在同一份 preimage 上(sign-binds-id)。
| CBOR | Go 字段 | JSON(读取面) | 说明 |
|---|---|---|---|
| 1 | ChainDID | chain_did(只在 head 里) | 这是谁的链。daemon 写的是 did:anet: 前缀形式 |
| 2 | Seq | seq | 序号,从 0 起 |
| 3 | PrevID | prev_id | 前一条的 id;创世条目用 GenesisPrev() |
| 4 | EventType | event_type | 见下一节 |
| 5 | VersionMajor | — | 当前只有 2(ael.VersionMajor2) |
| 6 | Payload | payload | 内嵌的 CoreDet-CBOR 数据项 |
| 7 | Timestamp | timestamp | unix 毫秒 |
| 8 | SignerAID | signer_aid | 签名者。daemon 写的是不带 did:anet: 前缀的裸 AID |
| 9 | KeyStateSeq | — | 签名时的密钥状态序号 |
| 10 | CriticalExtensions | — | 可选 |
| 11 | ID | id | = ComputeID(),不在 preimage |
| 12 | Sig | sig | 分离签名,读取面渲染为 base64,不在 preimage |
| 13 | VersionMinor | — | 不在 preimage |
| 14 | CorrID | — | P4 追踪关联,不在 preimage |
chain_did / seq / signer_aid / key_state_seq 都折进 id,所以一条记录无法跨链重放。
创世哨兵是冻结常量:
由 ael_test.go 的 TestGenesisPrev 守着。
读取面的两个结构
ANet/internal/daemon/ledger.go:
head.length 是链长(下一个 seq),head.state 是 ACTIVE 或 QUARANTINED。
链为空时 state 也报 ACTIVE —— 源码注释:空链不是坏链。
payload 里的字节值渲染为 base64,这是显示选择,plainValue 里有注释说明。
CBOR 的 map 键可以不是字符串,plainMap 会把 map[any]any 重新键为字符串。
存储
路径 <数据目录>/evidence.ael.jsonl(Layout.EvidenceLedgerPath())。
文件名与内容不符。
位置:~/.anet/evidence.ael.jsonl。
行为:后缀是 .jsonl,内容是每行一条 base64 的 CoreDet-CBOR。
grep anet.capability.effect 对它返回 0 行,哪怕链里确实有这类事件。
影响:按后缀去排查会得出"链是空的"这个错误结论。
发现方式:早期版本确实写 JSON;事件 id 由 CBOR preimage 推导,而 CBOR 区分字节串与文本串、
JSON 不区分,装了字节的 payload 经 JSON 往返后 id 推不出来,
daemon 会在接受过一次结果之后的第一次重启时对着自己写的链报 tamper/fork? 而拒绝启动。
路径保留旧名是为了让升级的节点接着写同一条链,而不是在旁边悄悄开第二条。
读老节点留下的 JSON 行仍然兼容(decodeRecord 见行首 { 走 JSON 分支)。
event_type 清单
事件类型是开放注册的。以下是已在源码中定义的全部取值。
daemon 自己的链
| event_type | 常量位置 | 谁写 | payload 字段 |
|---|---|---|---|
anet.capability.effect | internal/daemon/ledger.go | 提供方 | interaction_id capability caller_aid status verifiable metrics result_cid evidence |
anet.interaction.receipt | internal/daemon/ledger.go | 提供方 | interaction_id requester_aid result_cid |
anet.delegation.sent | internal/daemon/ledger.go | 委派方 | interaction_id provider_aid request_cid;能力委派路径另加 capability |
anet.result.accepted | internal/daemon/ledger.go | 委派方 | interaction_id result_cid receipt_bytes receipt_verified |
一次完成的交互在两条链上都留痕:提供方记 anet.interaction.receipt,
委派方记 anet.result.accepted。receipt_verified 是布尔 ——
一条只记"已接受"的链事后分不清"审核过的完成"与"没人审核得了的完成"。
anet.delegation.sent 有两个写入点,payload 不同:散文委派在
internal/daemon/relay.go,只写三个字段;能力委派在 internal/daemon/capability.go,
另写 capability,因为"调的是哪个能力"正是这条路径要回答的问题。
anet.capability.effect 也有第二个写入点:module/x402/voucher.go 的 voucher 兑付门。
那条路径的 payload 只有 interaction_id capability status via(via 固定为
voucher),没有 caller_aid / verifiable / metrics / result_cid / evidence ——
按字段解析这类记录的读者要能容忍这两种形状。
付款(x402 模块,-tags no_x402 可去掉)
| event_type | 常量位置 | 谁写 | payload 字段 |
|---|---|---|---|
anet.payment.authorized | module/x402/pay.go | 付款方 | interaction_id authorization_id pay_to amount network |
anet.payment.settled | 写入方常量在 internal/daemon/paylink.go;module/x402/pay.go 的同名常量只被 reconcile.go 用来读 | 见下 | 见下 |
anet.credit.redeemed | module/x402/pay.go | 赎回方 | amount reference auth_id verified;hub 返回了回执就另加 receipt,verified 只在该回执验签通过且金额相符时为真 |
anet.voucher.redeemed | module/x402/voucher.go | 提供方 | voucher_id auth_id payer capability amount network |
anet.voucher.refused | module/x402/voucher.go | 提供方 | reason capability,视情况另加 payer 或 voucher_id |
anet.issuance.head_seen | module/x402/audit.go | 审计方 | chain_did seq head_id observed_at |
anet.payment.settled 有两种 payload 形状,取决于写它的是哪一侧:
- 提供方(
internal/daemon/capability.go,结算成功后立即写):interaction_idcapabilitytransactionamountnetwork。 - 委派方(
internal/daemon/delegation.go的recordSettlement,从交付物里读回):interaction_idtransactionamountnetworkverified;verified为真时另加payeeauth_idreceipt。 注释说明理由:"提供方说它收到了钱"和"hub 签了字说 credit 动了"是两件不同的事实。
anet.voucher.redeemed 另有一层用途:它是花掉集合的持久化。
启动时回读这些记录重建 spent set(上限 maxSpentReplay 条),
否则进程重启会忘掉所有已用过的 voucher,一次付款可以换两次工作。
shell(加法 tag,默认不在构建里)
| event_type | 常量位置 | payload 字段 |
|---|---|---|
anet.shell.command | module/shell/provider.go | caller capability command exit_code killed duration_ms output_bytes |
anet.shell.refused | module/shell/provider.go | caller capability reason |
anet.shell.command 在答复调用方之前写,且无论命令怎么退出都写。
命令的输出不入链(可能很大、可能带任意内容),入链的是"跑了什么、谁要求的、怎么结束的"。
hub 自己的链(不在 daemon 链上)
ANetHub/internal/aghub/issuance.go,payload 统一是 aid / amount / reason:
| event_type | 含义 |
|---|---|
anet.credit.issued | credit 被创建:注册赠额或运营发放 |
anet.credit.retired | credit 因赎回被销毁 |
anet.credit.opening | 链建立之前就已存在的存量。单列出来,读者才能看出总量里有多少是无背书的 |
读取端点 GET /x402/issuance 与 GET /x402/issuance/head。
每个条目带一个 record 字段,是签过名的 ael.EventRecord(base64 CoreDet-CBOR)——
核对链的读者必须用它,而不是用旁边那些解析好的 JSON 字段,因为后者是 hub 渲染的。
ANetCore 内置的族
ael.go 定义了三个:genesis、dispute、converge.fork_detected。
分叉锚点由 Ledger.quarantine 在检出等价物(同一 (chain_did, seq) 上两条不同 id)时写入,
payload 是一个四键 CBOR map:1 chain_did、2 seq、3 既有记录 id、4 冲突记录 id。
源码注释把它称作 P4.2 的 ForkRecord,但仓库里没有这个名字的 Go 类型 —— 只有这四个键。
genesis 与 dispute 在 ANetCore / ANet / ANetHub 的非测试代码里只有常量,没有写入点:
daemon 的链没有单独的创世条目,seq 0 就是第一条真实事件,只是它的 prev_id 是 GENESIS_PREV。
读取与核对
--type精确匹配event_type,不做前缀或通配。--since是序号,保留seq >= since的条目。--limit默认 50,上限 1000(defaultEvidenceLimit/maxEvidenceLimit)。 超限时保留的是尾部,即最新的那些;结果按序号升序,最新的在最后。
对应控制面端点是 POST /evidence,请求体三个可选字段
event_type / since / limit;MCP 工具名是 evidence_read。
读取只从已验证的内存账本出,不重读文件。理由写在 ledger.go:
第二条直接解析文件的读取路径会是同一批字节的未验证视图,两者不一致时会变成"谁的副本才对"的争论。
审计 hub 的发行链用:
它拉 hub 的发行链、验签、验链接,并与本节点此前记过的 anet.issuance.head_seen 比对。
它不能阻止 hub 发行 credit —— hub 就是发行方 —— 但 hub 无法在不与本节点已有记录矛盾的前提下追溯改写。
查结构体定义:
anet evidence 是可用的子命令,但没有出现在 anet help --all 的输出里(0.1.7 核实)。
anet visibility、anet x402-authorize、anet mcp 同样如此:usageAll() 是一份手工维护的
清单,与 main.go 的实际分发表有漂移。要看某个命令接受哪些 flag,以 knownFlags 表为准 ——
0.1.7 起 CLI 会拒绝不在表里的 flag 并把接受的列出来,不再静默忽略。
已知缺口
以下四条都是对着 0.1.7 源码核实过的现状,不是设计意图。
1. EffectRecord.Artifacts 过不了 C1 北向。
位置:ANetLink/northbound/c1serv/c1serv.go 的 invokeResp、
ANetLink/northbound/mcpserv/mcpserv.go 的 invokeOut、
ANet/provider/anetlink/anetlink.go 的解码结构、
ANet/internal/daemon/capability.go 的 capabilityResult —— 四处都只有
Metrics map[string]float64,没有 artifacts 字段。
行为:适配器用 Snapshot() 生成的 ContentCID 在跨出 ANetLink 运行时的那一刻被丢弃。
影响:远端调用方收到 snapshot_bytes 这个数字和 observed_state 里的
"N bytes of image/jpeg",拿不到内容寻址的 CID,也就无法据此核对帧内容。
发现方式:在四个仓里检索 Artifacts,ANet/ 与 ANetLink/northbound/ 下没有非测试的命中。
2. ANetLink 的 MCP 面丢 quirk。
位置:ANetLink/northbound/mcpserv/mcpserv.go 的 evidenceOut 结构与其赋值处。
行为:八个 Evidence 字段里只映射了七个,Quirk 不在其中。
影响:通过 ANetLink MCP 工具读到的效果,看不出读数是否被厂商偏差修正过 ——
而这正是 quirk 层被设计出来要防止的那一件事。
发现方式:C1 那条线协有 TestEveryEvidenceFieldCrossesTheWire 逐字段比对
(c1serv.go 的注释明说这是唯一的守护),MCP 这条线协没有等价测试。
3. service 模块的注释与实现描述不一致。
位置:ANet/module/service/service.go 的 applyDeclared。
行为:函数注释说信任取"声称值与 HTTP 应答所能确立的值中较小的那个",
实现是根本不读声称的 verify_trust / auth_trust,verify 轴固定 V1、auth 轴固定 A0。
影响:结果是安全的(配置抬不高信任),但读注释的人会以为存在一条"声称 V1 则接受 V1"的路径,实际没有。
发现方式:TestTrustIsNotTheOperatorsToDeclare 断言的是固定值 1 与 0,不是取较小值。
4. ANetLink MCP 的状态枚举少一档。
位置:ANetLink/northbound/mcpserv/mcpserv.go 的 invokeOut.Status 的 jsonschema 描述,
写的是 OK | UNVERIFIED | FAILED | UNAVAILABLE。
行为:少列 PAYMENT_REQUIRED。
影响:实践中 ANetLink 设备不标价,这一档在该面上不会出现,所以是文案不全而不是行为缺陷;
但把它当成完整状态集来实现消费方会漏一个分支。
发现方式:与 ANetCore/effect/effect.go 的五常量比对。