结论 三场景在两块板上各 36 条判据,36/36 全绿,红 0;两板结果逐条一致。
过程中查出并修复两个真缺陷(时序窗口闭区间、base64 缓冲区差 1 字节),均已回灌规格与代码。 G1 判据(Dev-B ≥12,000 条)已由 .emf 产品通道实测达成:单包 12,000 条一次灌入, 重启后 12,000 条保全、抽样可查回(见 §3.1)。判据脚本是可复跑的——结论一旦失效,对应行会变红。
| 场景 | 验什么 | 怎么判它成立 | 结果 |
|---|---|---|---|
| S1 工业振动 |
故障指纹碎片按域隔离 + 时序异常分钟可被聚合值检出 | 4 个域(轴承/齿轮/电机/泵)各自成碎片;按域名检索只命中本域且带出处四字段;注入 12 个分钟窗口、第 8 分钟幅值 ×8,逐分钟 rms 必须在第 8 格出现尖峰 | 22/22 |
| S2 野外巡检 |
基础包 / 专家包分层 + ADR-014 拒答语义 | 两层碎片各自灌入;通用词只命中基础包、专家专属词只命中专家包;无关查询与红线条目必须拒答且不回退「最接近一条」 | 9/9 |
| S3 私域知识库 |
批量灌库正确性 + 抽样可检索 | 12 包 × 10 条 = 120 条;库条数精确对账 120;随机不重复抽样 8 条每条命中 1 条;库外编号反向对照必须查不到 | 5/5 |
| 判据 | 内容 | Dev-A 实测 | 结果 |
|---|---|---|---|
| S1-01 / 01b | 时序区可用,且如实否认原始波形能力 | streams=3, l1_pages=122, pages_per_stream=40, raw_waveform=false | 绿 |
| S1-02 | 四个故障指纹域灌入 | 7101 轴承 3/3(484B)· 7102 齿轮 2/2(316B)· 7103 电机 2/2(324B)· 7104 泵 2/2(320B) | 绿 |
| S1-03 | frag.list 列出 4 个碎片 | n=4, ids=[7101,7102,7103,7104] | 绿 |
| S1-04 | 按域名检索只命中本域 | 轴承→[7101×3] · 齿轮→[7102×2] · 电机→[7103×2] · 泵→[7104×2] | 绿 |
| S1-05 | 命中带出处四字段(可引用) | {"id":1,"frag_id":7101,"score_q8":255,"text":"轴承外圈故障特征频率 BPFO…","src":"cwru"} | 绿 |
| 判据 | 内容 | 实测 | 结果 |
|---|---|---|---|
| S1-06 | 12 个分钟窗口全部注入成功 | 12/12 rc=0 | 绿 |
| S1-07 | 窗口内 L1 行数精确等于 12 | rows=12 | 绿 |
| S1-08 | 异常分钟 rms ≥ 2× 正常段均值 | 正常均值 111.0 / 异常 890.0 → 8.02× | 绿 |
| S1-08b | 正常段各分钟量级一致(无假阳性) | min=111.0 max=111.0(完全无波动) | 绿 |
| S1-09 | 六种聚合算子全有值 | min=-1617 max=1617 mean=0 std=278.033 rms=278.033 pp=3234 | 绿 |
| S1-10 | 同分钟重复注入被明确拒绝(不静默覆盖) | rc=-5 EM_E_EXISTS,附人话:「该分钟已有聚合行…请把 ts_us 错开到下一分钟」 | 绿 |
逐分钟 rms 曲线(修复前后对照,这是本轮最关键的取证)
修复前(闭区间 [t0,t1]):111 111 111 111 111 111 634 634 111 111 111 111
↑ 第7格本该111 ↑ 真值被稀释成 634
修复后(左闭右开 [t0,t1)):111 111 111 111 111 111 111 890 111 111 111 111
↑ 异常精确定位在第8格
8.02× 与注入的 8× 幅值数学自洽;修复前的 634 恰为 sqrt((111²+890²)/2),
是「一格混进两行」的数学确证,不是巧合。
| 判据 | 内容 | 实测 | 结果 |
|---|---|---|---|
| S2-01 / 02 | 基础包 8101 / 专家包 8102 分层灌入 | 各 accepted=3/3 | 绿 |
| S2-03 | frag.list 列出 2 个分层碎片 | ids=[8101,8102] | 绿 |
| S2-04 | 通用词「巡检」只命中基础包 | count=2, frag_ids=[8101,8101] | 绿 |
| S2-05 | 专家专属词「污闪」只命中专家包 | count=1, frag_ids=[8102] | 绿 |
| S2-06 | ADR-014:无关查询拒答 | count=0, reject=LOW「全库最高分低于 min_score_q8」 | 绿 |
| S2-07 | 拒答时不回退「最接近一条」 | hits=0 | 绿 |
| S2-08 / 09 | ADR-014:红线条目命中即拒且不返回内容 | 写入 id=4;查询 reject=REDLINE, count=0 | 绿 |
S2-06 / 07 / 09 三条是产品承诺「有边界」的直接落点—— 宁可说「不在范围内」,也不给一条勉强沾边的结果。这三条绿,ADR-014 才算真落地,不是写在文档里。
| 判据 | 内容 | 实测 | 结果 |
|---|---|---|---|
| S3-01 | 12 包 × 10 条全部灌入 | accepted 合计 = 120 / 120 | 绿 |
| S3-02 | frag.list 列出 12 个碎片 | n=12 | 绿 |
| S3-03 | 库条数对账精确 | lib_count=120,期望 120 | 绿 |
| S3-04 | 随机不重复抽样 8 条,每条精确命中 1 条 | 0079 0071 0088 0092 0074 0119 0005 0087 → 全 1 | 绿 |
| S3-05 | 反向对照:库外编号查不到 | 0897 → count=0, reject=LOW | 绿 |
上面 S3 的 120 条是正确性验证(受单行上限约束,单包只能 ~1.2 KB)。
本轮补齐了规格第 343 / 377 行本就定义的分块灌库通道
(frag.begin / chunk / commit / abort,此前接的是整包 b64,与规格不符),
于是 12,000 条可以一个包灌进去。
| 判据 | Dev-B 实测(COM4,先 em.wipe 清库) | 结果 |
|---|---|---|
| 单包灌入 12,000 条 | .emf = 907,992 B / 12,000 条 / CRC32=0x662C9D01 → 887 块,211.9 s | 绿 |
accepted 与库增量对账 | accepted=12000;lib_count 0 → 12000(增量 12000) | 绿 |
| 重启后保全 | 复位重启后 em.stat count 仍 = 12000,rec_pages 258/3040 | 绿 |
| 抽样查回(5 个分散位置) | kb 0 / 777 / 4321 / 8888 / 11999 → 均精确命中,frag_id=8701 | 绿 |
| 唯一编号不重复 | kb 11999、kb 8888 均 count=1 | 绿 |
| 坏 CRC 不破坏已有库(规格铁律) | commit → rc=-9 明确报错,imported=0,库条数前后不变、原有记忆仍可检索 | 绿 |
| 跳块 / 部分重叠 / 重发内容不符 | 均 rc=-1 明确拒绝,received 不被推进 | 绿 |
为什么串口侧是 1 KB 分块而不是规格写的 4 KB:
LINE_MAX=2048,base64 膨胀 4/3 —— 1024 B → 1368 字符 + JSON 包裹 ~77 B = 1445 ≤ 2047;
4096 B → 5541 ≫ 2047,整行会被 cli_task 直接丢弃且零响应。
4 KB 是 HTTP 侧口径(请求体无单行上限),属承载差异不是退化;两侧共用同一套 commit 校验。
此结论已回填规格 §5.4。
引擎 em_frag_import() 对 32 B 文件头只有结构校验、没有完整性校验:
它验 magic / version / header_len / 长度自洽 / payload_crc32,
但不验 frag_id、flags、v2 的 vec_dim。
而 payload CRC 对这三类损坏完全无感(payload 一个字节没动)。
所以分块通道 commit 那道覆盖整个文件(含 32 B 头)的 CRC32 是真正在扛事的那一道。
这一点已做成板端实测,不是推断:
| 判据 | 内容 | 实测(COM3,S3b 场景) | 结果 |
|---|---|---|---|
| S3b.1 | 翻转后 payload CRC 仍自洽(反证:引擎单独看不出问题) | 头内 payload_crc32 = 0x6DAF6629,实算 = 0x6DAF6629 → 相等,必然放行 | PASS |
| S3b.2 | commit 被整体 CRC32 拦下 | rc=-9:实收 0x300719CD vs begin 承诺 0x68828C92;已丢弃该包,记忆库未被触碰 | PASS |
| S3b.3 | 未导入任何条目 | imported = 0 | PASS |
| S3b.4 | 库条数前后不变 | 1085 → 1085 | PASS |
| S3b.5 | 翻转出的碎片域没有被凭空建出来 | frag.list 现域中无 8402(原值 8403) | PASS |
数字版本说明:上表是重构后固件 v1.20261028-160744 上重跑的结果,
以这组为准(同一天更早的旧固件上是另一组数)。
⚠ 引用纪律:这类 16 进制数是"那一次、那个包"的量,同一天三次复现得到三组不同数值
(0x9AA0EE81 → 0x18B91930 → 0x6DAF6629),
结论稳定、数值每次不同。判据是「两者是否相等」,禁止把任何一组数抄进断言当期望值——
那会在换包后变成"期望值过期导致的假红"。
不拦会怎样:运输途中把头里 frag_id(offset 28)最低位翻掉,
那批记录会静默落进一个凭空冒出来的碎片域——事后在 frag.list 上只看到
「多了个不知道哪来的碎片」,无从追溯。flags bit0 被翻则整包变 PINNED_ALL 且无人发现。
口径提醒:整包少传字节时引擎返回的是 EM_E_INVAL(-1)
不是 EM_E_CORRUPT(-9)(长度自洽属结构校验)。做 G7 口径统计时别把它算成介质/校验失败。
一个没验成的项(明确列出):跨进程重启后的续传在串口侧验不了——
本项目每开一次串口就复位一次板子,会话缓冲在 PSRAM 里,复位即丢。
真正验到的是「响应丢失 → 原块重发」与「坏块 → 明确拒 → 续传」两条板端语义;
「真杀进程再 --resume」那一步受物理条件所限没跑到,不拿"反正结果对"糊过去。
现象:逐分钟 rms 曲线整体错位一格,异常被「提前」一格报出,真值被稀释(890 → 634)。
根因:em_ts_query.c 里 emlite_ts_agg() 判 m_us > t1 排除、
emlite_ts_window() 判 base+k > m1 排除 —— 两者都是闭区间。
按「第 i 分钟」的自然写法查 [base+i, base+i+1),会把下一分钟那行也算进来。
规格状态:02 分册 §ts.query / §ts.agg 原只给了函数签名、未定义开闭区间。 所以这不是违反规格,而是规格缺口——已回填条款并立 ADR-019(左闭右开是时序 API 通行约定:相邻窗口不重不漏)。
修复中发现的第二个坑(比原缺陷更隐蔽):不能简单地把 agg 改成比微秒、window 保持比整除分钟——
t1 不是整分钟时 m1 = t1 / 60s 的整除会比微秒更严一格,两个函数的行集合就分叉了
(实测 6/6 全分叉)。故两函数统一按分钟序号 mi = base + k 与 [m0, m1) 比较,口径逐字一致。
现象:S3 首轮 12 包全红,报 rc=-1「包非法(magic/version/… 任一不符)」——但包本身是合法的。
根因:cli_json.c 原 static char b64[1600]。1199 字节的 .emf
恰好产出 1600 字符 base64,而放进 char[1600] 需要 1601 字节(含结尾 NUL)
⇒ arg_str → mj_str 静默截成 1599 ⇒ 解出的包短 1~2 字节 ⇒ 引擎判「包非法」。
讽刺点(值得立为通用教训):那段代码注释明写「绝不静默截断、别把包太大误报成包坏了」——
但防线装在 b64_decode 里,截断却发生在更早的 arg_str 拷贝,防线根本没被触发。
已修:b64[LINE_MAX] + 贴边即判太大(strlen(b64) >= sizeof-1 → 明确报「包太大请分包」)。
em.bulk 脚手架实测(Dev-B 97,737 / Dev-A 23,132,
见 G1 容量实测记录)——那是灌库脚手架不是产品通道,两个数各有用途,别混着引用。/api/frag/import 仍未接线(规格 02 分册第 377 行)。
本轮只做了串口侧;main/http_srv.c 里目前没有任何 api/frag 处理,这条欠账还在。ts.info 已如实输出 raw_waveform=false。score_q8=255 是过滤路径的固定值,不是真实相似度——响应里的 match_mode 字段会说明匹配方式,
别把 255 当相似度读。frag.list 的 name / ver 恒为未知(32 B .emf 头里没有这两个字段),
如实输出空串与 0,不假装有名字。同名高版本覆盖(规格 5.3)本期未实现,返回 EM_E_NOSUPPORT。em.wipe 清库(保证条数可精确对账)。这是破坏性操作,
生产路径是否开放需单独评估。cd /c/emgw-build python em_scenarios.py COM3 --scenario all # Dev-A(γ 磁吸卡) python em_scenarios.py COM4 --scenario all # Dev-B(β USB Dongle) # 单场景:--scenario s1|s2|s3 不清库复跑:追加 --no-wipe
脚本自带 36 条断言,红/绿逐条打印并汇总。
每个场景开头 em.wipe 清库,清库后必须轮询等引擎就绪(wipe 只擦页 0..7,
开库判 CORRUPT 后整片重建,Dev-B 需几十秒)——定时睡眠会误判成「卡死」。
判据的可失效性(诚实纪律要求「已实测」必须落到会随结论失效变红的用例上): 区间语义若回退成闭区间 → S1-08b 立即变红; ADR-014 若被绕过 → S2-06/07/09 变红; 灌库若丢条或串号 → S3-03 对账与 S3-04 抽样变红。