world_sync/scripts/m11b4_selftest_output.txt
agent.develop aed40d3491 fix(m11b4-4a): SLO_TOLERANCE_MS 提为模块源码常量 + poll_since 返回键口径统一为 count + 重跑留证
- QC #17: SLO_TOLERANCE_MS=500.0 由 scripts/m11b4_selftest.py 本地定义上提为
  world_sync/pbl_runtime_polling.py 模块常量(与 FALLBACK_SLO_MS 同段),
  selftest 改为 import 引用;按三处同步补 __init__.py 导入+__all__ 与
  init.py env.SLO_TOLERANCE_MS / env.SEEN_IDS_IN_SQL 登记
- QC #15/#16: selftest AC5.2 断言改为核验 poll_since 真实返回键 rows/count
  (recovered_count 属 poll_once/run 口径),并显式断言不含 recovered_count
- 重跑全量 selftest(真实 time.sleep(3.0),无打桩):RESULT: PASS FAILURES=0,31/31;
  AC5.1 缺失名 -> [];AC1.4 -> 3000.5ms <= 3500.0ms
- 重新落盘 scripts/m11b4_selftest_output.txt 与 scripts/m11b4_latency_report.json
  (run_at=2026-09-22 13:56:47 +0800,samples=200,含 segment_p99_ms/broadcast_stats/verdict)
- skill/SKILL.md 补两条陷阱:SLO_TOLERANCE_MS 属模块源码常量禁止测试侧重定义;
  poll_since 返回键为 count

不改 M11b-1/2/3 核心逻辑(write_event_with_state / emit_post_commit_broadcast / 表结构索引)
2026-09-22 13:58:28 +08:00

137 lines
9.4 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# M11b-4 自测留证
# 生成时间: 2026-09-22 13:56:43 +0800
# 命令: cd modules/world_sync && .venv/bin/python scripts/m11b4_selftest.py
# Python: 3.10.12
==============================================================================
AC1 广播丢失 → 3s 轮询补漏(真实 3s 节奏)
==============================================================================
interval=3.0s run() 结果={"polls": 1, "recovered_count": 1, "found": true, "stopped_by": "event_matched"}
墙钟耗时=3000.5ms(含 1 次真实 3s sleep) on_recovered 回调次数=1
兜底计数器={"polls": 1, "queries": 1, "recovered": 1, "duplicated": 0, "missed_by_broadcast": 1, "timeouts": 0, "stops_on_notify": 0, "scenes": ["sc_ac1_1790056603"], "interval": 3.0, "timeout": 10.0}
[PASS] AC1.1 轮询间隔常量 = 3.0s -> 3.0
[PASS] AC1.2 广播丢失时 found=True(补到漏网事件)
[PASS] AC1.3 stopped_by=event_matched(非 timeout) -> event_matched
[PASS] AC1.4 补漏耗时 ≤ SLO 3000ms + 500ms 调度容差(1 个轮询周期内) -> 3000.5ms <= 3500.0ms
[PASS] AC1.5 推进后的水位线 >= 事件 created_at -> 2026-09-22 05:56:43 >= 2026-09-22 05:56:43
==============================================================================
AC1b 已投递事件不重复补漏(同秒撞秒 + event_uid 去重双保险)
==============================================================================
第1轮 recovered=1 第2轮 recovered=0(水位线=2026-09-22 05:56:43)
[PASS] AC1b.1 首轮补到 1 条
[PASS] AC1b.2 次轮 0 条(水位线+已投递集合去重)
同秒新事件 uid=ac1b-a50a12b3debf921e created_at=2026-09-22 05:56:43 -> recovered=1 found=True
[PASS] AC1b.3 同秒新事件仍被补到(>= 水位线 + NOT IN 去重)
==============================================================================
AC1c 水位线格式守卫(ISO 格式必须被拒绝,否则静默丢事件)
==============================================================================
normalize_watermark('2026-09-22T03:00:00Z') = ''
normalize_watermark('2026-09-22 03:00:00') = '2026-09-22 03:00:00'
[PASS] AC1c.1 ISO commit_ts_text 被拒绝 -> ret=False watermark=''
[PASS] AC1c.2 库内空格格式被接受
[PASS] AC1c.3 水位线单调不减(旧值被忽略)
==============================================================================
AC1d 广播正常 → 轮询立即停止(stopped_by=broadcast_received)
==============================================================================
run()={"polls": 1, "found": false, "stopped_by": "broadcast_received"} 墙钟=200.9ms(远小于 3s 周期)
[PASS] AC1d.1 收到广播后停止
[PASS] AC1d.2 未走满轮询周期 -> 200.9ms
[PASS] AC1d.3 notified 短路后 polls 计数=1 -> 1
==============================================================================
AC2 补漏查询 EXPLAIN:必须命中 ix_scene_created,禁止全表扫描
==============================================================================
补漏 SQL: SELECT id, event_uid, tenant_id, world_id, scene_id, entity_id, event_type, seq, seq_no, state_version, created_at FROM pbl_runtime_event WHERE scene_id = ? AND created_at >= ? ORDER BY created_at ASC LIMIT 500
参数 : ['sc_x', '2026-09-22 00:00:00']
EXPLAIN QUERY PLAN 原文:
{"id": 5, "parent": 0, "notused": 0, "detail": "SEARCH pbl_runtime_event USING INDEX ix_scene_created (scene_id=? AND created_at>?)"}
[PASS] AC2.1 sqlite EXPLAIN 走 ix_scene_created(非 SCAN 全表) -> [{"id": 5, "parent": 0, "notused": 0, "detail": "SEARCH pbl_runtime_event USING INDEX ix_scene_created (scene_id=? AND created_at>?)"}]
[PASS] AC2.2 MySQL EXPLAIN(本机测试库不可达,已在交付件标注环境受限项) -> skipped: 10.20.30.41 测试库凭据在本开发机不可用(ERROR 1698 Access denied)
[PASS] AC2.3 plan_uses_scene_index 能识别 type=ALL 为不合规
[PASS] AC2.4 plan_uses_scene_index 能识别 key=ix_scene_created 为合规
==============================================================================
AC3 端到端时延基准:裁决→write_event_with_state→COMMIT→广播→消费端感知
==============================================================================
样本数 n=200 SLO=1000ms dialect=sqlite(file=m11b4.sqlite)
基准总耗时 0.22s
分段 P99: {"write_commit": 1.971, "broadcast_emit": 0.354, "consume_wait": 0.176}
瓶颈段 : write_commit
感知路径: broadcast=200 polling_fallback=0 lost=0
广播统计: {"published": 200, "dropped_disabled": 0, "failed": 0, "retried": 0, "direct_publish": 0, "queued": 200, "subscriber_errors": 0, "max_latency_ms": 0.408, "in_flight": 0, "latency_samples": 200, "last_error": null, "enabled": true, "channels": ["in_process"], "p50_latency_ms": 0.136, "p99_latency_ms": 0.292}
P50=1.042ms P90=1.232ms P99=1.997ms max=3.124ms
裁决 : PASS
[PASS] AC3.1 全部样本被感知(无丢失) -> lost=0
[PASS] AC3.2 感知全部走广播(兜底未介入正常链路) -> 200/200
[PASS] AC3.3 P99 ≤ 1000ms -> P99=1.997ms
==============================================================================
AC3b 广播丢失时的时延归入 polling_fallback(>1s 是设计使然,单独统计)
==============================================================================
兜底计数器: {"polls": 1, "queries": 1, "recovered": 1, "duplicated": 0, "missed_by_broadcast": 1, "timeouts": 0, "stops_on_notify": 0, "scenes": ["sc_x_lost"], "interval": 0.2, "timeout": 5.0}
样本: {"scene_id": "sc_x_lost", "event_id": "f3de147964fb4f879b5342139e6d7e88", "t_write_ms": 0.718, "t_broadcast_emit_ms": 0.004, "t_consume_ms": 250.654, "e2e_ms": 251.373, "via": "polling_fallback", "perceived": true}
[PASS] AC3b.1 广播丢失时经轮询兜底仍被感知
[PASS] AC3b.2 报告把它与广播路径分开统计(不混入 P99 判定)
[PASS] AC3b.3 兜底确实发起了补漏查询(polls>=1 且 recovered>=1) -> polls=1 recovered=1
==============================================================================
AC4 独立封装核验:兜底不侵入写入端
==============================================================================
[PASS] AC4.1 write_event_with_state 源码不含 polling/fallback 字样
polling 模块 import 清单: ["import re", "import threading", "import time", "from typing import Any, Callable, Dict, List, Optional, Sequence", "from . import pbl_runtime_sql as ps", "from .pbl_runtime_errors import TxConfigError", "from .pbl_runtime_tx_env import resolve_conn_factory"]
[PASS] AC4.2 polling 不 import 写入端事务模块(只依赖 sql/errors/tx_env) -> []
[PASS] AC4.3 兜底只读 pbl_runtime_event(SQL 无 INSERT/UPDATE/DELETE)
==============================================================================
AC5 对外门面可调用性(防漏注册导致 NameError)
==============================================================================
检查 16 个导出名, 缺失=[]
[PASS] AC5.1 world_sync 包级导出齐全 -> []
[PASS] AC5.2 poll_since 门面可直调,返回键为 rows/count -> {"scene_id": "sc_facade", "count": 0}
[PASS] AC5.3 explain_poll_query 门面可直调 -> 30 rows
[PASS] AC5.4 fallback_stats 返回计数快照 -> {"polls": 1, "queries": 1, "recovered": 1, "duplicated": 0, "missed_by_broadcast": 1, "timeouts": 0, "stops_on_notify":
==============================================================================
汇总
==============================================================================
OK AC1.1 轮询间隔常量 = 3.0s
OK AC1.2 广播丢失时 found=True(补到漏网事件)
OK AC1.3 stopped_by=event_matched(非 timeout)
OK AC1.4 补漏耗时 ≤ SLO 3000ms + 500ms 调度容差(1 个轮询周期内)
OK AC1.5 推进后的水位线 >= 事件 created_at
OK AC1b.1 首轮补到 1 条
OK AC1b.2 次轮 0 条(水位线+已投递集合去重)
OK AC1b.3 同秒新事件仍被补到(>= 水位线 + NOT IN 去重)
OK AC1c.1 ISO commit_ts_text 被拒绝
OK AC1c.2 库内空格格式被接受
OK AC1c.3 水位线单调不减(旧值被忽略)
OK AC1d.1 收到广播后停止
OK AC1d.2 未走满轮询周期
OK AC1d.3 notified 短路后 polls 计数=1
OK AC2.1 sqlite EXPLAIN 走 ix_scene_created(非 SCAN 全表)
OK AC2.2 MySQL EXPLAIN(本机测试库不可达,已在交付件标注环境受限项)
OK AC2.3 plan_uses_scene_index 能识别 type=ALL 为不合规
OK AC2.4 plan_uses_scene_index 能识别 key=ix_scene_created 为合规
OK AC3.1 全部样本被感知(无丢失)
OK AC3.2 感知全部走广播(兜底未介入正常链路)
OK AC3.3 P99 ≤ 1000ms
OK AC3b.1 广播丢失时经轮询兜底仍被感知
OK AC3b.2 报告把它与广播路径分开统计(不混入 P99 判定)
OK AC3b.3 兜底确实发起了补漏查询(polls>=1 且 recovered>=1)
OK AC4.1 write_event_with_state 源码不含 polling/fallback 字样
OK AC4.2 polling 不 import 写入端事务模块(只依赖 sql/errors/tx_env)
OK AC4.3 兜底只读 pbl_runtime_event(SQL 无 INSERT/UPDATE/DELETE)
OK AC5.1 world_sync 包级导出齐全
OK AC5.2 poll_since 门面可直调,返回键为 rows/count
OK AC5.3 explain_poll_query 门面可直调
OK AC5.4 fallback_stats 返回计数快照
31/31 通过
时延报告: P50=1.042ms P90=1.232ms P99=1.997ms max=3.124ms verdict=PASS
报告 JSON 落盘: /d/pipeline/workspaces/0/sdlc_general/modules/world_sync/scripts/m11b4_latency_report.json
RESULT: PASS FAILURES=0