
上周我把一份端侧智能体的提交声明丢给自己写的校验脚本,跑完 12 条判据,屏幕上跳出 verdict=BLOCK。缺的只是电子签名一个字段,另外一条高危权限没写用途说明。其实这份声明人工抽查时看着挺齐,表格每行都填了字,机器一读就把两处漏洞摆到台面上。
我把脚本连跑三遍,16 项自检全绿之后,才把结果写进下面的表格。文中每个数字都能用第五节的命令复现,没跑出来的数我一个也没写。
本文是实操文,随文脚本
agent_compliance_probe.py(纯标准库,只用 json 与 sqlite3),零外部依赖。它把文件里的文字要求翻译成可执行判据,不是法律意见,条款以官方原文为准。
一、为什么把要求写成可跑的清单

11 月 9 日的预研包把工信厅信管〔2026〕42 号列进 TOP3,这份 10 月 24 日印发施行的文件,给预置智能体的手机、平板、可穿戴设备进网加了几道前置动作:智能体适配验证、涉诈风险安全评估、反诈能力前置性评测;OTA 现网升级要提交方案与报告并经专家评估;应用分发平台要公示适配验证列表;开发者要电子签名声明是否同意智能体调用(来源:本仓 output/gen/2026-11-09/daily_brief.md 第 ① 节 TOP3 摘述,经 8 路 WebSearch 交叉验证)。
同一周 OpenRouter 周榜是 210万亿 Token 量级,人工逐份翻表格,翻到第三十份就会凭印象打勾(来源:每日经济新闻据 OpenRouter 测算,经预研包交叉验证)。
我做的不是逐条解读政策,而是把这些要求翻译成 12 个能被机器判真假的检查项。比如「电子签名声明」落成签名长度与签名主体比对,「适配验证列表」落成列表长度与逐项证据检查;再例如「OTA 升级方案」落成非空判断与专家评估批准位。这样一份声明交上来,机器给出的是可归档的三态结果,不是一句「看起来没问题」。
二、声明文件长什么样,台账存什么

输入是一个 JSON 声明文件,工程上可以把它当成提交包里的 agent_declaration.json。演示用的字段长这样:
{
"app_id": "com.example.assistant",
"version": "3.2.0",
"agent_declared": true,
"consent": "granted",
"signature": "MEUCIQD7x9f2k0aB4n6v8c1e3g5",
"signer": "example-dev",
"developer": "example-dev",
"signed_at": "2026-11-09T02:10:00+08:00",
"ota_plan": {"channel": "stable", "gray_percent": 5},
"permissions": [{"name": "READ_SMS", "justification": "验证码自动填充"}]
}
这里的灰度 5% 是脚本里的示例值,不是任何厂商的实际配置(来源:随文脚本 good_manifest())。
台账用 SQLite 存两张表:submissions 一行代表一份声明的一次归档,主键是内容哈希;checks 一行代表一条判据的结果。拆成两张表,是为了让「整份声明的结论」与「逐条判据的明细」都能被单独复核。
三、12 条判据逐条对应

| 编号 | 检查项 | 对应的要求 |
|---|---|---|
| C01 | 预置智能体声明为真 | 声明是否预置智能体 |
| C02 | 调用同意等于 granted | 电子签名声明是否同意调用 |
| C03 | 签名字符串长度达标 | 签名非空、可追溯 |
| C04 | 签名主体等于开发者 | 签名责任可归属 |
| C05 | 时间戳可解析 | 签名时间可核验 |
| C06 | 验证列表不少于 3 项 | 适配验证列表存在 |
| C07 | 每项带结果与证据 | 验证结果可复核 |
| C08 | 涉诈评估有风险项 | 涉诈风险安全评估 |
| C09 | 前置评测通过且有报告 | 反诈能力前置性评测 |
| C10 | OTA 升级方案非空 | 现网升级提交方案 |
| C11 | 专家评估已批准 | 升级经专家评估 |
| C12 | 高危权限带用途说明 | 权限声明可解释 |
每条结果是 PASS、FAIL、UNKNOWN 三态之一。字段没交,记 UNKNOWN,不记 PASS,也不记 0。整份声明只在 12 条全为 PASS 时给 verdict=PASS,其余情况一律 BLOCK。
四、核心代码:三态判据与幂等归档

下面这段是脚本的骨架,可以直接复制成 probe.py 运行。完整版(含 12 条判据与 16 项自检)已落盘在同目录 agent_compliance_probe.py。
import hashlib, json, sqlite3
from datetime import datetime, timezone
MISSING = object() # 缺失哨兵,不用 0 或空串
def field(d, k):
return d.get(k, MISSING)
def judge(d, key, ok):
v = field(d, key)
return "UNKNOWN" if v is MISSING else ("PASS" if ok(v) else "FAIL")
def canonical_hash(d):
raw = json.dumps(d, sort_keys=True, ensure_ascii=False,
separators=(",", ":"))
return hashlib.sha256(raw.encode()).hexdigest()
def archive(conn, m, results):
h = canonical_hash(m)
n_pass = sum(r["status"] == "PASS" for r in results)
verdict = "PASS" if n_pass == len(results) else "BLOCK"
with conn: # 事务包住两张表,避免半截台账
conn.execute("INSERT OR IGNORE INTO submissions VALUES (?,?,?,?,?)",
(h, m.get("app_id", ""), m.get("version", ""),
datetime.now(timezone.utc).isoformat(), verdict))
conn.executemany(
"INSERT OR REPLACE INTO checks VALUES (?,?,?,?)",
[(h, r["code"], r["status"], r["detail"]) for r in results])
return h, verdict
judge() 把三态收在一处,canonical_hash() 用 sort_keys=True 保证键顺序变化不产生新版本,archive() 用 INSERT OR IGNORE 保证同一份声明复验不新增行。
五、跑起来:三条命令与实测数字

python agent_compliance_probe.py --selftest
python agent_compliance_probe.py --demo
python agent_compliance_probe.py --demo --db ./agent_compliance.db
python agent_compliance_probe.py --verify agent_declaration.json --db ./agent_compliance.db
--selftest 输出 16 项检查全绿,含 9 个正控与 7 个负控。负控专门盯着「缺失被当成通过」这类假阳:N1 缺签名不是 PASS,是 UNKNOWN、N2 denied 不能靠 truthy 蒙混过关、N5 空声明 12 条全不是 PASS。
--demo 故意删掉签名字段、把 READ_SMS 的用途说明留空,输出是:PASS 10 / FAIL 1 / UNKNOWN 1 / verdict BLOCK。10 条 PASS 占 83.3%,那 1 条 UNKNOWN 占 8.3%,但结论照样是拦下(来源:本地脚本 --demo 实测输出,2026-11-09)。
--demo --db 会在同目录生成 agent_compliance.db,本机实测 20480 字节,内容哈希前 12 位是 35af06cdef56。
复验同一份声明两次,submissions 仍是 1 行、checks 仍是 12 行,重复写入 0%(来源:--selftest P3 与 --demo --db 落盘产物)。
六、工程取舍:为什么用 SQLite 落台账
其一,单文件可归档。审核方要的是一份能随 OTA 报告一起提交的附件,.db 文件拷走即可,不要求对方装数据库服务。
其二,约束交给数据库。PRIMARY KEY (manifest_hash) 与 INSERT OR IGNORE 直接把幂等写进存储层,比在应用层维护一个「已处理集合」可靠。
其三,事务天然够用。with conn: 把两张表的写入包成一个原子操作,中途抛异常不会留下「有汇总没明细」的半截台账。
其四,离线可跑。sqlite3 是标准库,断网也能复验。
不过 SQLite 也有边界:多节点并发写入时写锁会顶住,到了分发平台那种吞吐,需要警惕把本地台账当权威结论,换 PostgreSQL 更合适。
七、踩坑记录:五处都是真跑出来的
坑一,or "" 把缺失抹平。 早版写 sig = d.get("signature") or "",缺字段和交空串变成一个值,后续长度判断直接放行。改成 MISSING 哨兵后才分得开。
坑二,truthy 判断把拒绝当同意。 if d.get("consent") 遇到字符串 "denied" 照样为真。布尔判断要显式写成 == "granted",这条被 N2 负控锁住。
坑三,键顺序抖动造出重复归档行。 直接用 json.dumps(d) 求哈希,同一个对象换一次插入顺序就是新哈希。sort_keys=True 之后,P5 键顺序不影响内容哈希 才转绿。
坑四,拿时间戳当主键。 早版用 datetime.now() 做去重键,同一份声明复验一次就多一行。换成内容哈希,submissions 才稳在 1 行。
坑五,批量写入半途失败。 executemany 不在事务里,异常会留下 12 条里只写了 5 条的台账。用 with conn: 包住后,要么全进要么全退。
八、换成真实系统的替换点
演示脚本做的是静态声明校验,接真实系统要换三处。
把 evaluate() 里的签名长度检查换成公钥验签,把 C09 的报告存在性换成报告哈希与实验室回执比对。--verify 指向提交包里真实的声明文件,别再读演示字典。--db 指向共享台账目录,并在 CI 里加一步:构建产物打完包就跑一次 --verify,verdict=BLOCK 直接让流水线失败。
把「进网前才发现漏材料」提前到打包时刻,返工成本差着量级。
九、边界:清单能挡住什么,挡不住什么
清单能挡住的是漏项、格式错、签名主体不一致这类硬伤。它挡不住伪造的证据,字段齐了也不代表反诈能力真的达标。另一个角度是规则本身会随实施细则更新,所以 version 字段和内容哈希都要留痕,方便日后回答「当时按哪一版判的」。
我把 UNKNOWN 留成独立状态,没并进 FAIL。「没交」和「交了但不合格」要走的补件流程不一样,混成一类后续对账就分不清。
留三个问题给你:
- 你会把「字段缺失」判成 UNKNOWN,还是直接判 FAIL?
- 适配验证列表该由开发者自报,还是由分发平台逐项复验?
- OTA 专家评估意见,台账里该存哈希还是存全文?
但缺一项,就该拦在进网之前。
别把缺失当成通过。
数据与事件来源
- 工信厅信管〔2026〕42 号(2026-10-24 印发施行)要点:转引自本仓
output/gen/2026-11-09/daily_brief.md第 ① 节 TOP3 摘述,经预研包 8 路 WebSearch 交叉验证;本文只做工程映射,条款以官方原文为准。 - 12 条判据、16/16 自检、PASS 10 / FAIL 1 / UNKNOWN 1、83.3% 与 8.3% 占比、内容哈希
35af06cdef56、复验幂等结果:来源:本地脚本agent_compliance_probe.py --selftest与--demo实测输出(2026-11-09)。 - 台账
agent_compliance.db(20480 字节):来源:--demo --db落盘产物,可用sqlite3命令行直接打开复核。 - 参考来源:210万亿 Token 周调用量背景,每日经济新闻据 OpenRouter 测算,经预研包交叉验证。
这份清单我会随细则更新继续改,欢迎在评论区聊聊你踩过的适配验证坑,顺手收藏备用。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/superdangbo/article/details/170359583



