diff --git a/.gitignore b/.gitignore index 6579d96..5f650da 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ dist/ build/ .venv/ venv/ + +# M11b-2c/2a 一次性取证临时目录(正式取证已归档到 tests/evidence/) +.m11b2c_tmp/ diff --git a/scripts/m11b2c_p99_probe.py b/scripts/m11b2c_p99_probe.py index bcbbbac..c9f4e12 100644 --- a/scripts/m11b2c_p99_probe.py +++ b/scripts/m11b2c_p99_probe.py @@ -233,14 +233,34 @@ def run(samples): return verdict, rw, rr -def main(): - n = 60 - if len(sys.argv) > 1: +def parse_samples(argv, default=60): + """支持 `--samples 50` / `--samples=50` / 位置参数 `50` 三种写法。 + + M11b-2a 修正:原实现只认位置参数,按验收命令 `--samples 50` 调用时会被判为 + 「参数非法」并静默回落到默认 60 次,取证样本数与请求不符。 + """ + n = default + i = 0 + while i < len(argv): + a = argv[i] + if a.startswith("--samples"): + if "=" in a: + val = a.split("=", 1)[1] + else: + i += 1 + val = argv[i] if i < len(argv) else "" + else: + val = a try: - n = max(MIN_SAMPLES, int(sys.argv[1])) - except ValueError: - print("采样参数非法:%s(用默认 60)" % sys.argv[1]) - rc, _rw, _rr = run(n) + n = max(MIN_SAMPLES, int(val)) + except (TypeError, ValueError): + print("采样参数非法:%r(用默认 %d)" % (a, default)) + i += 1 + return n + + +def main(): + rc, _rw, _rr = run(parse_samples(sys.argv[1:])) return rc diff --git a/scripts/m11b_selftest.py b/scripts/m11b_selftest.py index 704d68f..a8e6721 100644 --- a/scripts/m11b_selftest.py +++ b/scripts/m11b_selftest.py @@ -190,21 +190,25 @@ class FakeEngine(object): # -- 极简 SQL 执行(只覆盖 tx_write 实际发出的语句形态)---------------- def _exec(self, s, params): s = " ".join(s.split()) - m = re.match(r"^SELECT COALESCE\(MAX\(seq_no\),\s*0\) AS max_seq FROM (\w+) " - r"WHERE (.+)$", s, re.I) + # M11b-2a 夹具修正:业务侧序号列真名是 "seq"(tx_write.EVENT_COL_SEQ), + # 原正则/取值只认 "seq_no",导致 _next_seq 的 MAX(seq) 语句落到通用分支 + # 返回 None → seq 恒为 1(B7 假失败)。夹具与业务 SQL 形态对齐,非业务缺陷。 + m = re.match(r"^SELECT COALESCE\(MAX\((?:seq|seq_no)\),\s*0\) AS max_seq " + r"FROM (\w+) WHERE (.+)$", s, re.I) if m: tbl, clause = m.group(1), m.group(2) conds, _ = _split_where(clause, params) rows = [r for r in self.committed_rows(tbl) if _hit(r, conds)] - return [{"max_seq": max([int(r.get("seq_no") or 0) for r in rows], default=0)}] + return [{"max_seq": max([int(r.get("seq") or r.get("seq_no") or 0) + for r in rows], default=0)}] m = re.match(r"^SELECT (.+?) FROM (\w+) WHERE (.+?)" r"(?:\s+ORDER BY\s+(.+?))?(?:\s+LIMIT\s+(\d+))?\s*$", s, re.I) if m: fields, tbl, clause, order, limit = m.groups() conds, _ = _split_where(clause, params) rows = [dict(r) for r in self.committed_rows(tbl) if _hit(r, conds)] - if order and "seq_no" in order: - rows.sort(key=lambda r: int(r.get("seq_no") or 0), + if order and ("seq_no" in order or re.search(r"\bseq\b", order, re.I)): + rows.sort(key=lambda r: int(r.get("seq") or r.get("seq_no") or 0), reverse="DESC" in order.upper()) elif order and "state_key" in order: rows.sort(key=lambda r: str(r.get("state_key") or "")) @@ -400,9 +404,12 @@ def suite_memory(): "state_value": {"x": 5}}], client_event_id="cli-A2") evs = engine.committed_rows("pbl_runtime_event") - check("B7[顺序与版本] seq_no 单调 1→2", - [e["seq_no"] for e in evs] == [1, 2], - str([e["seq_no"] for e in evs])) + # M11b-2a 夹具修正:事件表真实序号列是 "seq"(tx_write.EVENT_COL_SEQ), + # "seq_no" 仅是对外响应的兼容别名,不会出现在落库行里 —— 原断言取 + # e["seq_no"] 属夹具误用字段名(KeyError),非业务代码缺陷 + check("B7[顺序与版本] seq 单调 1→2", + [e["seq"] for e in evs] == [1, 2], + str([e["seq"] for e in evs])) check("B7[顺序与版本] state_version 递增到 2", engine.committed_rows("pbl_entity_state")[0]["state_version"] == 2) @@ -425,6 +432,11 @@ def suite_memory(): # 10) poll:游标早于缓冲最旧记录(缺口)→ 退回查库 hub3 = bcast.reset_hub() + # M11b-2a 夹具修正:先让**其它通道**消耗全局单调游标,本通道最旧 cursor 才会 + # 大于 after_cursor+1,构成真实缺口(原写法 reset_hub 后本通道最旧=1, + # after_cursor=0 并不构成缺口 → truncated=False → poll 命中 hub,B10 假失败) + for i in range(1, 6): + hub3.publish("pbl.world.7.99", {"event_id": 900 + i, "seq": i}) for i in range(1, 8): hub3.publish("pbl.world.7.1", {"event_id": i, "seq_no": i}) p3 = await wtx.poll_events(tenant_id=7, world_id=1, session_id=11, diff --git a/tests/evidence/m11b2a_p99_50.txt b/tests/evidence/m11b2a_p99_50.txt new file mode 100644 index 0000000..22afa0f --- /dev/null +++ b/tests/evidence/m11b2a_p99_50.txt @@ -0,0 +1,16 @@ +U[更新分支] 正常完成 50 次覆写,state_version 单调至 51 +============================================================================================================ +m11b2c P99 实测探针 + write_event_with_state → pbl_runtime_ext.tx_write.apply_runtime_event + read_entity_state → pbl_runtime_ext.tx_read... read_states +采样次数=50(门禁下限 30) 事件表=104 行 状态表=54 行 快照表=0 行 hub游标=104 +seq_no 单调性:PASS 严格递增 +------------------------------------------------------------------------------------------------------------ +write_event_with_state[INSERT分支] n=50 min= 0.1102 avg= 0.1475 p50= 0.1442 p90= 0.1739 p95= 0.1790 P99= 0.1827 max= 0.1827 (ms) PASS(<=200ms) +read_entity_state n=50 min= 0.0460 avg= 0.1148 p50= 0.1099 p90= 0.1713 p95= 0.1748 P99= 0.1776 max= 0.1776 (ms) PASS(<=200ms) +write_event_with_state[UPDATE分支] n=50 min= 0.2318 avg= 0.2624 p50= 0.2575 p90= 0.2804 p95= 0.2828 P99= 0.4820 max= 0.4820 (ms) PASS(<=200ms) +------------------------------------------------------------------------------------------------------------ +read 行 entity_id 一致性校验:PASS 全部与请求一致 +P99 门禁 SLA_P99_MS=200.0ms → write(INSERT) P99=0.1827ms / read P99=0.1776ms / write(UPDATE) P99=0.4820ms +P99 结论:达标(均 ≤ 200ms) +============================================================================================================ diff --git a/tests/evidence/m11b2a_pytest.txt b/tests/evidence/m11b2a_pytest.txt new file mode 100644 index 0000000..ce17014 --- /dev/null +++ b/tests/evidence/m11b2a_pytest.txt @@ -0,0 +1,2 @@ +......... [100%] +9 passed in 0.37s diff --git a/tests/evidence/m11b2a_selftest.txt b/tests/evidence/m11b2a_selftest.txt new file mode 100644 index 0000000..2bd45c1 --- /dev/null +++ b/tests/evidence/m11b2a_selftest.txt @@ -0,0 +1,113 @@ + +--- A. 离线单元 --- +partitions | partitions PASS:跨年命名 ['p202611', 'p202612', PASS partitions PASS:跨年命名 ['p202611', 'p202612', 'p202701', 'p202702'] +partitions | partitions PASS:下月边界 2027-01-01 PASS partitions PASS:下月边界 2027-01-01 +partitions | partitions PASS:已存在分区 skip、缺失分区 add PASS partitions PASS:已存在分区 skip、缺失分区 add +partitions | partitions PASS:DDL ALTER TABLE pbl_runtime PASS partitions PASS:DDL ALTER TABLE pbl_runtime_event ADD PARTITION (PARTITION p202602 VALUES LESS THAN (DATE'2026-03-01')) +partitions | partitions.self_check PASS PASS partitions.self_check PASS +partitions.self_check 整体 PASS +broadcast | broadcast PASS:after_cursor=4 只回 1 条 PASS broadcast PASS:after_cursor=4 只回 1 条 +broadcast | broadcast PASS:环形缓冲溢出报 truncated=True PASS broadcast PASS:环形缓冲溢出报 truncated=True +broadcast | broadcast PASS:通道按 tenant.world 隔离 PASS broadcast PASS:通道按 tenant.world 隔离 +broadcast | broadcast PASS:订阅者扇出成功 PASS broadcast PASS:订阅者扇出成功 +broadcast | broadcast PASS:stats published=6 p95=0.019ms PASS broadcast PASS:stats published=6 p95=0.019ms 越界=0 +broadcast | broadcast PASS:通道命名 pbl.world.7.1 PASS broadcast PASS:通道命名 pbl.world.7.1 +broadcast | broadcast.self_check PASS PASS broadcast.self_check PASS +broadcast.self_check 整体 PASS +latency | latency PASS:裁决 120ms ≤ 200ms 未越界 PASS latency PASS:裁决 120ms ≤ 200ms 未越界 +latency | latency PASS:裁决 450ms > 200ms 记越界 PASS latency PASS:裁决 450ms > 200ms 记越界 +latency | latency PASS:summary avg=285.0 p95=450.0 breac PASS latency PASS:summary avg=285.0 p95=450.0 breaches=1 +latency | latency PASS:轮询兜底 3000ms 已配置 PASS latency PASS:轮询兜底 3000ms 已配置 +latency | latency PASS:p95=10 PASS latency PASS:p95=10 +latency | latency PASS:Timer 实测 5.1ms PASS latency PASS:Timer 实测 5.1ms +latency | latency.self_check PASS PASS latency.self_check PASS +latency.self_check 整体 PASS +tx_write | tx_write PASS:幂等键同参同键、异参异键、≤32 字符(bebdba41666 PASS tx_write PASS:幂等键同参同键、异参异键、≤32 字符(bebdba416666…) +tx_write | tx_write PASS:client_event_id 原样透传(重放同键) PASS tx_write PASS:client_event_id 原样透传(重放同键) +tx_write | tx_write PASS:pbl_runtime_event 禁 UPDATE/DELE PASS tx_write PASS:pbl_runtime_event 禁 UPDATE/DELETE/TRUNCATE +tx_write | tx_write PASS:基表写保护生效(world 等 6 张) PASS tx_write PASS:基表写保护生效(world 等 6 张) +tx_write | tx_write PASS:pbl_runtime_event 用到的 13 列与 mod PASS tx_write PASS:pbl_runtime_event 用到的 13 列与 models 定义一致 +tx_write | tx_write PASS:pbl_entity_state 用到的 9 列与 model PASS tx_write PASS:pbl_entity_state 用到的 9 列与 models 定义一致 +tx_write | tx_write PASS:pbl_world_state_snapshot 用到的 9 PASS tx_write PASS:pbl_world_state_snapshot 用到的 9 列与 models 定义一致 +tx_write | tx_write PASS:3 协程契约 + 1 同步守卫签名正确 PASS tx_write PASS:3 协程契约 + 1 同步守卫签名正确 +tx_write | probe| PASS P1 正常写入(事件+2状态+快照)单事务提交:stmt=8 事件 PASS probe| PASS P1 正常写入(事件+2状态+快照)单事务提交:stmt=8 事件行=1 状态行=2 快照行=1 广播在commit后=True 占位符违规=0 +tx_write | probe| PASS P2 幂等重放:dedup=True 事件行数=1 广播次数=0( PASS probe| PASS P2 幂等重放:dedup=True 事件行数=1 广播次数=0(重放不重复写、不重复广播) +tx_write | probe| PASS P3 乐观锁冲突回滚:错误码=PBL-STATE-CONFLICT PASS probe| PASS P3 乐观锁冲突回滚:错误码=PBL-STATE-CONFLICT commit=-1 rollback=5 事件行残留=0 广播=0 +tx_write | probe| PASS P4 中途故障原子性:抛出=RuntimeError rollba PASS probe| PASS P4 中途故障原子性:抛出=RuntimeError rollback=5 已提交事件行=0 已提交状态行=0 广播=0 +tx_write | probe| PASS P5 append-only:UPDATE/DELETE/TRUN PASS probe| PASS P5 append-only:UPDATE/DELETE/TRUNCATE 被代码守卫拦截=True 引擎兜底拦截=True +tx_write | probe| PASS P6 占位符/参数数门禁生效:'SELECT 1'+2参数 被拒( PASS probe| PASS P6 占位符/参数数门禁生效:'SELECT 1'+2参数 被拒(错误码=PBL-RTX-0005) +tx_write | probe| PASS P7 契约三处同步:6/6 对齐 PASS probe| PASS P7 契约三处同步:6/6 对齐 +tx_write | probe| m11b_probe PASS:7 项探针(state_updates 非空 PASS probe| m11b_probe PASS:7 项探针(state_updates 非空端到端 P1~P4 + 守卫 P5 + 门禁有效性 P6 + 三处同步 P7) +tx_write | tx_write.self_check PASS PASS tx_write.self_check PASS +tx_write.self_check 整体 PASS +m11b_api | m11b_api PASS:5 个协程契约齐备 PASS m11b_api PASS:5 个协程契约齐备 +m11b_api | m11b_api PASS:assert_append_only 同步守卫可调用 PASS m11b_api PASS:assert_append_only 同步守卫可调用 +m11b_api | m11b_api PASS:contracts 清单 8 项与注册表一致 PASS m11b_api PASS:contracts 清单 8 项与注册表一致 +m11b_api | m11b_api.self_check PASS PASS m11b_api.self_check PASS +m11b_api.self_check 整体 PASS +rtx_db | rtx_db PASS:仅用 sor.C/U/R/sqlExe + sqlorContext PASS rtx_db PASS:仅用 sor.C/U/R/sqlExe + sqlorContext(无模块级猜测签名) +rtx_db | rtx_db PASS:事务原语探测 sor/conn/sql 三级 PASS rtx_db PASS:事务原语探测 sor/conn/sql 三级 +rtx_db | rtx_db PASS:无 begin/commit/rollback → 探测返回 Non PASS rtx_db PASS:无 begin/commit/rollback → 探测返回 None(上层抛 PBL-RTX-0002) +rtx_db | rtx_db PASS:库名不可得即抛 PBL-RTX-0001(不猜默认库) PASS rtx_db PASS:库名不可得即抛 PBL-RTX-0001(不猜默认库) +rtx_db.self_check 整体 PASS +A[三处同步] apply_runtime_event PASS impl=True export=True register=True +A[三处同步] poll_events PASS impl=True export=True register=True +A[三处同步] read_states PASS impl=True export=True register=True +A[三处同步] assert_append_only PASS impl=True export=True register=True +A[三处同步] pbl_runtime_broadcast_pull PASS impl=True export=True register=True +A[三处同步] pbl_runtime_broadcast_stats PASS impl=True export=True register=True +A[包出口可导入] pbl_runtime_broadcast_pull PASS +A[包出口可导入] pbl_runtime_broadcast_stats PASS +A[包出口可导入] apply_runtime_event PASS +A[包出口可导入] poll_events PASS +A[包出口可导入] read_states PASS +A[包出口可导入] assert_append_only PASS +A[M11b 注册清单完整(fail-closed 门禁)] PASS missing=[] total=12 +A[M11a 注册清单完整] PASS total=6 + +--- B. 内存事务引擎端到端(真实 apply_runtime_event 主链路)--- +B1[正常写入] ok 且非幂等命中 PASS seq_no=1 event_id=1 +B1[正常写入] 事件表 1 行 PASS +B1[正常写入] 状态表 1 行 v1 PASS +B1[正常写入] 快照表 1 行 PASS +B1[正常写入] 单事务多语句 PASS stmt_count=3 +B1[正常写入] 提交后广播进缓冲 PASS cursor=1 +B2[无幽灵更新] 事务日志 begin→commit PASS log=['begin', 'commit'] +B2[无幽灵更新] commit 时 hub 游标=0,广播后=1 PASS commit_cursor=0 now=1 +B3[幂等重放] dedup=True PASS event_id=1 +B3[幂等重放] 事件仍 1 行 PASS +B3[幂等重放] 未重复广播 PASS cursor 1→1 +B4[乐观锁冲突] PBL-STATE-CONFLICT PASS entity_id=pos 乐观锁冲突:base_version=99 当前 state_version=1(整 +B4[乐观锁冲突] 事件行数未增加(无脏数据) PASS 1→1 +B4[乐观锁冲突] 冲突键未落库 PASS +B4[乐观锁冲突] 状态版本未污染(仍 v1) PASS +B4[乐观锁冲突] 未广播 PASS +B5[DML失败] 异常上抛(不静默) PASS 注入故障:pbl_entity_state INSERT 失败 +B5[DML失败] 事件行不存在(回滚证据,非标记 rolled_back) PASS events=1 +B5[DML失败] 无状态脏行 PASS +B5[DML失败] 未广播(无幽灵更新) PASS +B6[回滚证据] 日志含 rollback PASS log=['begin', 'commit', 'begin', 'commit', 'begin', 'rollback', 'begin', 'rollback'] +B7[顺序与版本] seq 单调 1→2 PASS [1, 2] +B7[顺序与版本] state_version 递增到 2 PASS +B8[poll命中hub] source=hub PASS events=2 +B9[poll退回查库] source=db PASS events=2 +B9[poll退回查库] 取回 2 条已提交事件 PASS [1, 2] +B9[poll退回查库] payload 已反序列化为对象 PASS +B10[缺口退查库] source=db PASS truncated=True +B11[read_states] 返回状态且 state_value 为对象 PASS keys=[None] +B12[缺租户] PBL-TENANT-0001 PASS +B13[broadcast_pull] ok PASS source=db count=2 +B13[broadcast_stats] 含 sla/contracts PASS published=12 +B13[broadcast_pull] 无租户段拒绝跨租户拉取 PASS channel 必须以 pbl.world. 开头 +B14[SLA打点] 三阶段均有样本 PASS adj=0.284 bcast=0.004 e2e=0.459 +B14[SLA打点] 广播实测 ≤300ms PASS max=0.005ms +B15[分区] 失败时如实降级(不谎报已建) PASS ok=False degraded=True err=PBL-RTX-0003: apppublic.DBPools 不可导入:Mod + +--- C. 真实库端到端(原子性/回滚/广播时序的权威证据)--- +SKIP C:环境受限未验证 —— 本工作空间无 ahserver/apppublic 运行时('ModuleNotFoundError') + B 层用可回滚内存事务引擎跑通同一份 tx_write 主链路代码替代; + 连库跑法:在 pbls 应用环境执行 `python3 scripts/m11b_selftest.py --live` + +================ 汇总 ================ +总计 101 项,PASS 101,FAIL 0 +RESULT: PASS