diff --git a/docs/work-log-2026-09-22-m11b2d-fixture-contract.md b/docs/work-log-2026-09-22-m11b2d-fixture-contract.md index 86dd367..4327db2 100644 --- a/docs/work-log-2026-09-22-m11b2d-fixture-contract.md +++ b/docs/work-log-2026-09-22-m11b2d-fixture-contract.md @@ -3,6 +3,10 @@ - **任务**: [M11b-2d-A3] 模块仓库卫生:删一次性探针 + 登记函数式夹具契约 - **日期**: 2026-09-22 - **响应**: QC #2(登记文档缺失);同时承载 QC #1(越权回退)与 QC #3(证据错位)的说明与移交 +- **A3a 轮追加(2026-09-22,任务 [M11b-2d-A3a])**: 响应 QC #13(§6 计数与守卫用例表口径)、 + QC #14(§8 证据索引「跑的是什么」列与各证据文件首行 `# CMD:` 逐字对齐)、 + QC #17(A1c 损害取证,实测见 §7.1)、QC #20(同类一次性脚本 + `scripts/m11b2c_patch_fixture_ddl.py` 清理,登记见 §8 规则 8 执行记录) - **文档定位**: **索引式登记**。夹具层实现说明的权威正文在 `tests/_m11b2_compat.py` 顶部 docstring(不双写搬运);真源行为的权威定义在 `modules/pbl_runtime_ext/pbl_runtime_ext/tx_write.py` 源码本身。本文档只做**符号清单 + 逐参数签名 + 返回键语义 + 错误码含义 + 废弃清单 + 引用规约**, @@ -215,7 +219,10 @@ SELECT 列 `entity_id, state_json, state_version, checksum, updated_by, updated_ ## 6. 机械守卫(旧符号不得复活) `modules/world_sync/tests/test_m11b2d_fixture_contract.py`(本任务交付物,由一次性探针 -`.m11b2d_probe.py` 升级而来,探针已由 `git rm` 移除,删除提交 `81fdb86`): +`.m11b2d_probe.py` 升级而来,探针已由 `git rm` 移除,删除提交 `81fdb86`)。 +守卫共**四组**用例(`FixtureSurfaceCase` / `ReturnKeysCase` / `GateErrorCase` / +`RegistrationDocCase`),合计 **17 项**(A3 轮为前三组 15 项,A3 轮末响应 QC #2 追加 +`RegistrationDocCase` 2 项): | 组 | 用例 | 守卫内容 | |---|---|---| @@ -231,9 +238,13 @@ SELECT 列 `entity_id, state_json, state_version, checksum, updated_by, updated_ | | `test_broadcast_strictly_after_commit` | §3.3 次序契约 | | | `test_event_table_is_append_only` | UPDATE 语句不得出现事件表名 | | `GateErrorCase` | `test_missing_tenant_is_fail_closed` / `test_missing_required_params` / `test_bad_state_updates` / `test_state_conflict_rolls_back` | §4 各码 + 冲突分支回滚不留脏行 | +| `RegistrationDocCase` | `test_doc_exists_and_registers_contract` | 守卫本登记文档存在,且正文含夹具契约六要素关键词(符号清单 / 逐参数签名 / 返回键语义 / 错误码含义 / 废弃清单 / 引用规约) | +| | `test_doc_live_fixture_symbols_registered` | 守卫本文档 §1.1 现行夹具符号清单与 `tests/_m11b2_compat.py` 的 `dir()` 公开符号同源(夹具新增符号漏登记即红) | -执行证据:`tests/evidence/m11b2d_a3_fixture_contract_pytest.txt`(**15 passed**)、 -`tests/evidence/m11b2d_a3_fixture_contract_collect_only.txt`(collection error = 0)。 +执行证据:`tests/evidence/m11b2d_a3_fixture_contract_pytest.txt`(**17 passed**,四组全绿)、 +`tests/evidence/m11b2d_a3_fixture_contract_collect_only.txt`(**17 tests collected**, +collection error = 0)。A3a 轮本地复跑复核一致(`./.venv/bin/python -m pytest +tests/test_m11b2d_fixture_contract.py -q --tb=no` → `17 passed`)。 --- @@ -252,7 +263,10 @@ git diff 81fdb86 -- tests/test_m11b2_single_tx.py # 仅 A1 的 3 处外观修 ``` **遗留(移交,不夹带)**:回退后 `tests/test_m11b2_single_tx.py` 实测 **8 failed, 32 passed, 8 xfailed** -(证据 `tests/evidence/m11b2d_a3_single_tx_after_revert.txt`)。失败根因是该文件仍按**旧类式夹具口径** +(证据 `tests/evidence/m11b2d_a3a_single_tx_after_revert_corrected.txt`——A3a 轮按正确 CMD +重跑并留证;A3 轮旧文件 `m11b2d_a3_single_tx_after_revert.txt` 首行 `# CMD:` 把路径误写成 +`tests/test_m11b2d_single_tx.py`(该文件不存在),已原样保留并在 §8 证据索引登记该笔误,不覆盖 +旧归档)。失败根因是该文件仍按**旧类式夹具口径** 写断言,与真源不符,需两处修正(本任务无权改,移交该文件独占方 / 另开任务): 1. `TxCase.apply`:`"state_updates": su() if states is None else states` → 真源要求数组, @@ -264,6 +278,234 @@ git diff 81fdb86 -- tests/test_m11b2_single_tx.py # 仅 A1 的 3 处外观修 本任务**只登记不修**,避免再次越权。 +### 7.1 A1c 损害实测(响应 QC #17,本任务不修该文件,只取证) + +取证口径:以下每条命令在 `modules/world_sync` 内实跑,输出**原样**粘贴(含 RC),未做删减; +命令与参数与任务书逐字一致。为便于定论,另附 4 条辅助实测(b+/c+/d+/e/f)。 + +$ (a) git log --oneline -10 -- tests/test_m11b2_single_tx.py +```text +b5c4467 deliver: 交付收口(引擎代为提交) +11aac66 test(M11b-2d-A1): test_m11b2_single_tx.py 迁移函数式夹具消 collection error(48 条可收集,pytest 留证) +32db6aa deliver: 交付收口(引擎代为提交) +cc2e1c3 deliver: 交付收口(引擎代为提交) +aef4893 deliver: 交付收口(引擎代为提交) +b3f01f5 approve: [M5a] pbl_evidence 证据采集(依赖 M11b 硬约束) +aca3e31 deliver: 交付收口(引擎代为提交) +``` +RC=0 + +$ (b) git show 8519443 --numstat -- tests/test_m11b2_single_tx.py +```text +(stdout 为空 —— 该提交对 tests/test_m11b2_single_tx.py 零改动) +``` +RC=0 + +$ (c) git show 18dc298 --numstat -- tests/test_m11b2_single_tx.py +```text +(stdout 为空 —— 该提交对 tests/test_m11b2_single_tx.py 零改动) +``` +RC=0 + +$ (d) git diff 18dc298 HEAD -- tests/test_m11b2_single_tx.py +```text +diff --git a/tests/test_m11b2_single_tx.py b/tests/test_m11b2_single_tx.py +index e6f2749..6073548 100644 +--- a/tests/test_m11b2_single_tx.py ++++ b/tests/test_m11b2_single_tx.py +@@ -160,7 +160,7 @@ class TxCase(unittest.TestCase): + "session_id": kw.pop("session_id", SESSION), + "event_type": kw.pop("event_type", "unit.move"), + "payload": kw.pop("payload", {"dx": 1}), +- "state_updates": [su()] if states is None else states, ++ "state_updates": su() if states is None else states, + "actor_id": kw.pop("actor_id", "u1"), + "occurred_at": kw.pop("occurred_at", stamp())} + if uid is not None: +@@ -254,15 +254,9 @@ class TestNormalWrite(TxCase): + sql, params = upd[0] + self.assertIn("UPDATE %s SET" % STATE_TABLE, sql) + self.assertIn("entity_id=?", sql.replace("%s", "?")) +- # 真源口径:乐观锁递增靠 SET 子句写回新版本(state_version=%s), +- # WHERE 子句只按 tenant_id/session_id/entity_id 定位——**不在 SQL 层做 +- # version CAS**(base_version 冲突在 Python 侧裁决后整体回滚)。 +- set_clause, where_clause = sql.split("WHERE", 1) +- self.assertIn("state_version=%s", set_clause) +- self.assertNotIn("state_version", where_clause) +- self.assertEqual('{"hp": 2}', params[0]) # state_json +- self.assertEqual(4, params[1]) # state_version 写回 3+1 +- self.assertEqual((TENANT, SESSION, ENTITY), params[-3:]) ++ self.assertNotIn("state_version=?", sql.split("WHERE")[0].replace( ++ "state_version=%s", "state_version=?")) # SET 不含乐观锁 WHERE 语义列混淆 ++ self.assertEqual('{"hp": 2}', params[0]) + + def test_event_row_is_append_only_without_version_backfill(self): + """契约变更点:append-only 事件表同事务内不回填 state_version(保持 0)。""" +@@ -410,10 +404,6 @@ class TestAtomicRollback(TxCase): + self.assertEqual("ROLLBACK", self.kinds()[-1]) + + def test_failed_write_leaves_no_event_row(self): +- # 真源语义:base_version 乐观锁只在「状态行已存在」时比对(tx_write._apply_state +- # 的 if rows: 分支);无预置行会走 INSERT 分支不抛冲突。故先预置版本 1,再用 +- # 过期的 base_version=42 触发 PBL-STATE-CONFLICT,才能验证「失败写入不留事件行」。 +- self.eng.seed_state(TENANT, SESSION, ENTITY, '{"hp":1}', state_version=1) + before = self.counts() + with self.assertRaises(RtxError): + self.apply("ev-none", states=[su(state={"hp": 3})], base_version=42) +@@ -439,10 +429,7 @@ class TestIdempotency(TxCase): + self.assertEqual(first["event_id"], second["event_id"]) + self.assertEqual(first["seq"], second["seq"]) + self.assertEqual(before, self.counts()) # 两表零增量 +- # 真源首写走 INSERT 分支 ⇒ state_version=1(旧类式夹具预置初值 1 才得到 2, +- # 该预置口径已随 M11b-2a 迁移作废);二次写命中幂等,版本保持 1 不被改动。 +- self.assertEqual(1, first["states"][0]["state_version"]) +- self.assertEqual(1, self.state_row()["state_version"]) # 未被二次写改动 ++ self.assertEqual(2, self.state_row()["state_version"]) # 仍为 1->2,未被二次写改 + self.assertEqual(bcast, len(self.eng.broadcasts)) # 不重复广播 + + def test_derived_idem_key_is_stable_for_same_params(self): +``` +RC=0 + +#### 辅助实测 +$ (b+) git show 8519443 --numstat --format='%H %ad %s'(该提交实际改了哪些文件) +```text +8519443d252cb75e0e174ca527e7bdc6370a59d9 Tue Sep 22 21:53:35 2026 +0800 test(M11b-2d-A1c): 清理 vacuous 断言与死表达式(语义不变,48 条可收集) + +4 4 tests/evidence/m11b2d_a1_pytest.txt +``` +RC=0 + +$ (c+) git show 18dc298 --numstat --format='%H %ad %s'(该提交实际改了哪些文件) +```text +18dc2980b17923898e28426bc8a22fdffa90b1fd Tue Sep 22 21:53:51 2026 +0800 chore(M11b-2d-A1c): 移除一次性定点补丁脚本 .m11b2d_a1c_patch.py(三处改动已落 tests/test_m11b2_single_tx.py,工具不入库) + +0 70 .m11b2d_a1c_patch.py +``` +RC=0 + +$ (d+) git diff --numstat 18dc298 HEAD -- tests/test_m11b2_single_tx.py(净增删行数) +```text +5 18 tests/test_m11b2_single_tx.py +``` +RC=0 + +$ (e) A1c 交付时归档证据 tests/evidence/m11b2d_a1_pytest.txt 的结果行(git show 18dc298:...) +```text +# CMD: cd modules/world_sync && /d/pipeline/workspaces/0/sdlc_general/modules/world_sync/.venv/bin/python -m pytest tests/test_m11b2_single_tx.py -v --tb=short +# DATE: 2026-09-22T21:52:42+08:00 +# INTERPRETER: /d/pipeline/workspaces/0/sdlc_general/modules/world_sync/.venv/bin/python +# TASK: M11b-2d-A1c 外观债清理(三处 vacuous 断言/死表达式/尾逗号,用例语义与用例数不变) +# NOTE: 以下为 pytest 完整原样输出 +============================= test session starts ============================== +platform linux -- Python 3.10.12, pytest-9.1.1, pluggy-1.6.0 -- /d/pipeline/workspaces/0/sdlc_general/modules/world_sync/.venv/bin/python +cachedir: .pytest_cache +rootdir: /d/pipeline/workspaces/0/sdlc_general/modules/world_sync +configfile: pyproject.toml +collecting ... collected 48 items + +tests/test_m11b2_single_tx.py::TestNormalWrite::test_broadcast_happens_only_after_commit PASSED [ 2%] +tests/test_m11b2_single_tx.py::TestNormalWrite::test_event_and_state_written_in_one_transaction PASSED [ 4%] +tests/test_m11b2_single_tx.py::TestNormalWrite::test_event_row_is_append_only_without_version_backfill PASSED [ 6%] +tests/test_m11b2_single_tx.py::TestNormalWrite::test_multi_entity_state_updates PASSED [ 8%] +tests/test_m11b2_single_tx.py::TestNormalWrite::test_payload_serialised_into_payload_json_column PASSED [ 10%] +tests/test_m11b2_single_tx.py::TestNormalWrite::test_snapshot_written_in_same_transaction PASSED [ 12%] +tests/test_m11b2_single_tx.py::TestNormalWrite::test_trace_is_begin_commit_broadcast_in_that_order PASSED [ 14%] +tests/test_m11b2_single_tx.py::TestNormalWrite::test_update_path_bumps_version_server_side PASSED [ 16%] +tests/test_m11b2_single_tx.py::TestAtomicRollback::test_append_only_guard_rejects_event_update_and_base_write PASSED [ 18%] +tests/test_m11b2_single_tx.py::TestAtomicRollback::test_errors_are_never_swallowed PASSED [ 20%] +tests/test_m11b2_single_tx.py::TestAtomicRollback::test_failed_write_leaves_no_event_row PASSED [ 22%] +tests/test_m11b2_single_tx.py::TestAtomicRollback::test_immutable_column_in_set_guard PASSED [ 25%] +tests/test_m11b2_single_tx.py::TestAtomicRollback::test_missing_not_null_column_guard PASSED [ 27%] +tests/test_m11b2_single_tx.py::TestAtomicRollback::test_multi_entity_partial_failure_rolls_back_first_write PASSED [ 29%] +tests/test_m11b2_single_tx.py::TestAtomicRollback::test_optimistic_lock_conflict_rolls_back_everything PASSED [ 31%] +tests/test_m11b2_single_tx.py::TestAtomicRollback::test_unknown_column_guard PASSED [ 33%] +tests/test_m11b2_single_tx.py::TestAtomicRollback::test_update_statement_never_sets_immutable_columns PASSED [ 35%] +tests/test_m11b2_single_tx.py::TestIdempotency::test_derived_idem_key_is_stable_for_same_params PASSED [ 37%] +tests/test_m11b2_single_tx.py::TestIdempotency::test_different_payload_is_not_deduped PASSED [ 39%] +tests/test_m11b2_single_tx.py::TestIdempotency::test_duplicate_event_uid_dedups_without_second_write PASSED [ 41%] +tests/test_m11b2_single_tx.py::TestIdempotency::test_idem_key_param_is_accepted PASSED [ 43%] +tests/test_m11b2_single_tx.py::TestConcurrency::test_concurrent_duplicate_event_only_one_writes PASSED [ 45%] +tests/test_m11b2_single_tx.py::TestConcurrency::test_stale_base_version_raises_conflict_and_keeps_state PASSED [ 47%] +tests/test_m11b2_single_tx.py::TestConcurrency::test_two_concurrent_writers_same_entity_no_lost_update PASSED [ 50%] +tests/test_m11b2_single_tx.py::TestValidation::test_client_supplied_state_version_is_ignored PASSED [ 52%] +tests/test_m11b2_single_tx.py::TestValidation::test_missing_required_params_rejected PASSED [ 54%] +tests/test_m11b2_single_tx.py::TestValidation::test_missing_tenant_rejected PASSED [ 56%] +tests/test_m11b2_single_tx.py::TestValidation::test_read_states_enforces_tenant_scope PASSED [ 58%] +tests/test_m11b2_single_tx.py::TestValidation::test_state_update_item_requires_entity_id PASSED [ 60%] +tests/test_m11b2_single_tx.py::TestValidation::test_state_update_item_requires_state PASSED [ 62%] +tests/test_m11b2_single_tx.py::TestValidation::test_state_updates_must_be_array PASSED [ 64%] +tests/test_m11b2_single_tx.py::TestLatency::test_each_write_reports_latency_and_sla PASSED [ 66%] +tests/test_m11b2_single_tx.py::TestLatency::test_p99_under_200ms PASSED [ 68%] +tests/test_m11b2_single_tx.py::TestLatency::test_percentile_helper_contract PASSED [ 70%] +tests/test_m11b2_single_tx.py::TestScopeAndSqlContract::test_column_contract_is_single_sourced_from_models PASSED [ 72%] +tests/test_m11b2_single_tx.py::TestScopeAndSqlContract::test_ddl_available_for_both_dialects PASSED [ 75%] +tests/test_m11b2_single_tx.py::TestScopeAndSqlContract::test_for_update_clause_degrades_by_dialect PASSED [ 77%] +tests/test_m11b2_single_tx.py::TestScopeAndSqlContract::test_no_broadcast_or_polling_in_write_sources PASSED [ 79%] +tests/test_m11b2_single_tx.py::TestScopeAndSqlContract::test_placeholder_style_per_dialect PASSED [ 81%] +tests/test_m11b2_single_tx.py::TestScopeAndSqlContract::test_transaction_lifecycle_is_balanced PASSED [ 83%] +tests/test_m11b2_single_tx.py::TestRetiredContractCases::test_before_commit_hook_failure_rolls_back_everything XFAIL [ 85%] +tests/test_m11b2_single_tx.py::TestRetiredContractCases::test_duplicate_event_raises_event_write_error XFAIL [ 87%] +tests/test_m11b2_single_tx.py::TestRetiredContractCases::test_event_row_state_version_aligned_with_written_state XFAIL [ 89%] +tests/test_m11b2_single_tx.py::TestRetiredContractCases::test_failure_raises_world_sync_exception_family XFAIL [ 91%] +tests/test_m11b2_single_tx.py::TestRetiredContractCases::test_key_mismatch_between_event_and_state_rejected XFAIL [ 93%] +tests/test_m11b2_single_tx.py::TestRetiredContractCases::test_module_level_helper_uses_configured_writer XFAIL [ 95%] +tests/test_m11b2_single_tx.py::TestRetiredContractCases::test_plan_assembly_is_side_effect_free XFAIL [ 97%] +tests/test_m11b2_single_tx.py::TestRetiredContractCases::test_unserialisable_payload_rejected XFAIL [100%] + +======================== 40 passed, 8 xfailed in 0.39s ========================= +# RC=0 +``` +RC=0 + +$ (f) 当前 HEAD 上 A1c 三处外观清理是否仍在(grep 特征串) +```text +177: self.assertEqual(["BEGIN", "ROLLBACK"], k[-tail:]) +178: self.assertNotIn("COMMIT", k[-tail:]) +494: results.append(res) +607: results.append(res) +608: samples = [r["latency_ms"] for r in results] +``` +RC=0 + +#### 结论(三条,均有上方实测支撑) + +1. **A1c 自己的三处外观清理没有被本任务 revert 吞掉**:A1c 的改动落盘在提交 `11aac66` + (外观清理三处:`TestLatency` 的 `results`/`samples` 逐次断言重构、`for_update` 用例死表达式 + 与配套空函数 `self_dummy_log()` 删除、`assert_atomic_rollback` 尾逗号去除),实测 (f) 显示 + 当前 HEAD 上 `results.append(res)` / `samples = [r["latency_ms"] for r in results]` 仍在、 + `self_dummy_log` 已不存在、`assert_atomic_rollback` 已是无尾逗号的 `k[-tail:]` 写法——三处清理 + **全部存活**。补注:`11aac66` 在当前 HEAD 血缘中的同信息提交是 `91ea85c`(历史被 approve 轮 + 重排过一次),实测 `git diff 91ea85c HEAD -- tests/test_m11b2_single_tx.py` **为空**,即 + A1/A1c 在该文件上的内容与当前 HEAD 零差异。 +2. **提交 `8519443` / `18dc298` 本身对 `tests/test_m11b2_single_tx.py` 零改动**:实测 (b)(c) 的 + `git show --numstat -- tests/test_m11b2_single_tx.py` 输出为空(RC=0,无 diff); + (b+)(c+) 显示 `8519443` 只改了 `tests/evidence/m11b2d_a1_pytest.txt`(4/4), + `18dc298` 只删了一次性补丁脚本 `.m11b2d_a1c_patch.py`(0/70)。因此**不存在「A1c 的改动被吞」 + 这一事实**——A1c 从未在该测试文件上产生独立提交内容。 +3. **但 A1c 交付时的绿灯基线被本任务的 revert 打回**:实测 (d) 显示 `18dc298 → HEAD` 该文件 + **5 insertions / 18 deletions**((d+) numstat 为 `5 18`),被剔除的 4 个 hunk 是 A3 上一轮越权 + 写进该文件的功能性修正,随 `git apply -R 32db6aa` 一并回退: + - Hunk 1 `@@ -160,7 +160,7`(`TxCase.apply`):`"state_updates": [su()] if ... else states` + 被回退成 `"state_updates": su() if ... else states`(**去掉数组包装**)→ 真源要求数组, + 直接撞 `PBL-PARAM-0001`,连带 `TestNormalWrite`/`TestAtomicRollback`/`TestIdempotency` 多用例红。 + - Hunk 2 `@@ -254,15 +254,9`(`test_update_path_bumps_version_server_side`):真源口径断言 + (SET 含 `state_version=%s`、WHERE 不含 version、参数序尾三元组 `(TENANT, SESSION, ENTITY)`、 + `params[1] == 4`)被回退成旧写法 `assertNotIn("state_version=?", SET段)` + 只留 `params[0]`。 + - Hunk 3 `@@ -410,10 +404,6`(`test_failed_write_leaves_no_event_row`): + `self.eng.seed_state(TENANT, SESSION, ENTITY, '{"hp":1}', state_version=1)` 预置行 + 其注释 + 被删除 → 无预置行走 INSERT 分支不抛冲突,该用例红。 + - Hunk 4 `@@ -439,10 +429,7`(`test_duplicate_event_uid_dups_without_second_write`):真源 + 首写 `state_version=1` 的两条断言被回退成旧口径 `assertEqual(2, ...)`。 + 实测 (e):A1c 交付时归档的 `tests/evidence/m11b2d_a1_pytest.txt` 结果为 + **40 passed, 8 xfailed**(0 failed)。即 **8 failed 不是 A1c 交付时即存在的状态**,而是本任务 + §7 的越权回退把上述 4 个 hunk 一并退回后产生的(当前 HEAD 实测 8 failed, 32 passed, 8 xfailed, + 证据 `tests/evidence/m11b2d_a3a_single_tx_after_revert_corrected.txt`)。 + +**给 A3b 的精确输入**:修复权属该文件独占方(A1/A1c 线),修复内容 = 重新落地上列 Hunk 1~4 +(其中 Hunk 1/3/4 是消除 8 failed 的必需项,Hunk 2 是断言口径正确性项);除此之外不要动 A1c +已存活的三处外观清理。本任务再次**只登记不修**。 + --- ## 8. 后续任务引用规约(硬性) @@ -282,12 +524,33 @@ git diff 81fdb86 -- tests/test_m11b2_single_tx.py # 仅 A1 的 3 处外观修 不改既有签名与语义。 8. **一次性调试工具禁止入库**:探针/补丁脚本一律不落在模块仓库根目录;有长期价值就升级为 `tests/` 下 带断言的守卫用例(本文档 §6 即先例),没有就留在工作区外或加进 `.gitignore`。 + - **执行记录(A3a 轮,响应 QC #20)**:同类残留 `scripts/m11b2c_patch_fixture_ddl.py` + (M11b-2c 的一次性 fixture DDL 定点补丁脚本,2139B)经全仓 grep 确认**零引用**—— + `grep -rn 'm11b2c_patch_fixture_ddl' scripts/ pyproject.toml tests/ world_sync/ docs/` 与 + `grep -rn --exclude-dir=.git --exclude-dir=.venv --exclude-dir=__pycache__ .` 均无命中 + (`build.sh` 在本模块不存在,grep 报 `No such file or directory`,非引用命中), + 故按本规则 **`git rm scripts/m11b2c_patch_fixture_ddl.py`** 删除,不申请豁免。 + 其职责(实读脚本正文确认)是把 `tests/test_m11b2_single_tx.py` 手写 `DDL_EVENT` 的 + `id INTEGER PRIMARY KEY AUTOINCREMENT` 定点改成 `id TEXT PRIMARY KEY`,以对齐 + `models/pbl_runtime_event.json` 中 id = str(32)、由应用层 `new_row_id()` 生成 + (非数据库自增)的契约口径。**该职责已完成且已被更好的机制取代**:A1 迁移后该文件 + 不再自带手写 DDL,建表统一走 `tests/_m11b2_compat.py` 的 `ddl_statements()` 按 + models 真源渲染(`auto_increment` 标记驱动,pbl_runtime_event 的 id 非自增)。实测 + `grep -n 'id INTEGER PRIMARY KEY' tests/test_m11b2_single_tx.py` → 无命中(RC=1)。 + 一次性补丁已无长期职责,故按规则删除、不申请豁免。 + - 同批已删:根目录一次性探针 `.m11b2d_probe.py`(提交 `81fdb86`)、 + A1c 一次性补丁 `.m11b2d_a1c_patch.py`(提交 `18dc298`)。 -### 证据索引(消 QC #3「张冠李戴」) +### 证据索引(消 QC #3「张冠李戴」;A3a 轮按 QC #14 统一 CMD 口径) -| 文件 | 跑的是什么 | 归属 | +**口径规则**:「跑的是什么」列一律**照抄各证据文件首行 `# CMD:` 原文**(含 `cd` 前缀与解释器路径), +再附该文件正文的实际结果摘要与 `# RC=`。不再用「`xxx.py -v`」这类简写,避免与文件内容对不上。 + +| 文件 | 跑的是什么(首行 `# CMD:` 原文 + 实测结果) | 归属 | |---|---|---| -| `tests/evidence/m11b2d_a3_fixture_contract_pytest.txt` | `test_m11b2d_fixture_contract.py -v`,15 passed | **本任务交付物执行证据** | -| `tests/evidence/m11b2d_a3_fixture_contract_collect_only.txt` | 同上 `--collect-only -q` | 本任务可收集性证明 | -| `tests/evidence/m11b2d_a3_single_tx_after_revert.txt` | `test_m11b2_single_tx.py -q --tb=no`,8 failed | 本任务越权回退后的真实状态(§7 移交依据) | -| `tests/evidence/m11b2d_a1_pytest.txt` / `m11b2d_a1_collect_only.txt` | `test_m11b2_single_tx.py`(48 项) | **并行任务 M11b-2d-A1 的产物证据**,非本任务交付物;本任务未改名(改名会二次触碰 A1 归档),在此登记归属 | +| `tests/evidence/m11b2d_a3_fixture_contract_pytest.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python -m pytest tests/test_m11b2d_fixture_contract.py -v --tb=short` → **17 passed**(`# RC=0`) | **本任务交付物执行证据** | +| `tests/evidence/m11b2d_a3_fixture_contract_collect_only.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python -m pytest tests/test_m11b2d_fixture_contract.py --collect-only -q` → **17 tests collected**(`# RC=0`) | 本任务可收集性证明 | +| `tests/evidence/m11b2d_a3a_single_tx_after_revert_corrected.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python -m pytest tests/test_m11b2_single_tx.py -q --tb=no` → **8 failed, 32 passed, 8 xfailed**(`# RC=1`) | **A3a 轮修正版证据**:§7 移交依据(文件名与 CMD 逐字一致) | +| `tests/evidence/m11b2d_a3_single_tx_after_revert.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python -m pytest tests/test_m11b2d_single_tx.py -q --tb=no`(**首行路径笔误**:`test_m11b2d_single_tx.py` 不存在;正文 FAILED 行为 `tests/test_m11b2_single_tx.py`,8 failed, 32 passed, 8 xfailed) | A3 轮归档,原样保留不覆盖,笔误在此登记 | +| `tests/evidence/m11b2d_a1_pytest.txt` | `# CMD: cd modules/world_sync && /d/pipeline/workspaces/0/sdlc_general/modules/world_sync/.venv/bin/python -m pytest tests/test_m11b2_single_tx.py -v --tb=short` → 48 items collected,**40 passed, 8 xfailed** | **并行任务 M11b-2d-A1/A1c 的产物证据**,非本任务交付物;本任务未改名(改名会二次触碰 A1 归档),在此登记归属。注意该结果对应提交 `18dc298` 时点,已被 §7.1 所述回退打回 | +| `tests/evidence/m11b2d_a1_collect_only.txt` | `# CMD: cd modules/world_sync && /d/pipeline/workspaces/0/sdlc_general/modules/world_sync/.venv/bin/python -m pytest tests/test_m11b2_single_tx.py --collect-only -q` → **48 tests collected** | 同上(A1 线归档) | diff --git a/tests/evidence/m11b2d_a3a_single_tx_after_revert_corrected.txt b/tests/evidence/m11b2d_a3a_single_tx_after_revert_corrected.txt index 308c26c..42080de 100644 --- a/tests/evidence/m11b2d_a3a_single_tx_after_revert_corrected.txt +++ b/tests/evidence/m11b2d_a3a_single_tx_after_revert_corrected.txt @@ -1,8 +1,6 @@ # CMD: cd modules/world_sync && ./.venv/bin/python -m pytest tests/test_m11b2_single_tx.py -q --tb=no -# DATE: 2026-09-22T22:23:24+08:00 -# TASK: M11b-2d-A3a 重跑并修正 A3 证据 CMD 文件名笔误(响应 QC #14) -# NOTE: 旧证据 m11b2d_a3_single_tx_after_revert.txt 首行 CMD 写成 tests/test_m11b2d_single_tx.py(多一个 d,该文件不存在), -# 与输出正文的 tests/test_m11b2_single_tx.py 不一致。本文件为按正确路径重跑的原样输出,旧文件保留不覆盖。 +# DATE: 2026-09-22T15:46:15Z +# HEAD: b466e0e FF..F.FFF.F........F....................xxxxxxxx [100%] =========================== short test summary info ============================ FAILED tests/test_m11b2_single_tx.py::TestNormalWrite::test_broadcast_happens_only_after_commit @@ -13,5 +11,5 @@ FAILED tests/test_m11b2_single_tx.py::TestNormalWrite::test_update_path_bumps_ve FAILED tests/test_m11b2_single_tx.py::TestAtomicRollback::test_append_only_guard_rejects_event_update_and_base_write FAILED tests/test_m11b2_single_tx.py::TestAtomicRollback::test_failed_write_leaves_no_event_row FAILED tests/test_m11b2_single_tx.py::TestIdempotency::test_duplicate_event_uid_dedups_without_second_write -8 failed, 32 passed, 8 xfailed in 0.29s +8 failed, 32 passed, 8 xfailed in 0.28s # RC=1