deliver: 交付收口(引擎代为提交)
This commit is contained in:
parent
ecc7f23965
commit
881d50a669
53
docs/work-log-2026-09-22-m11b2c-a1-qc12.md
Normal file
53
docs/work-log-2026-09-22-m11b2c-a1-qc12.md
Normal file
@ -0,0 +1,53 @@
|
||||
# Work Log — 2026-09-22(M11b-2c-A1:回退越权核查 + QC #12 根因表述修正)
|
||||
|
||||
- **模块仓库**: `modules/pbl_runtime_ext`(PBL scense_runtime 薄扩展,M11a/M11b 单事务事件+状态写入与广播)
|
||||
- **任务**: [M11b-2c-A1] 回退越权改动并在夹具层收敛 PBL-RTX-0004(QC 第 3 次退回,核心意见 #12)
|
||||
- **执行环境**: Python 3.10.12 / pytest 9.1.1 / venv=`.venv`(已 `pip install -e .`)
|
||||
- **起始 HEAD**: `ecc7f23 deliver: 交付收口(引擎代为提交)`
|
||||
- **结束状态**: `git status --short` 为空(工作区干净);`9 passed, 0 failed`
|
||||
|
||||
## 1. 本轮时间线(命令与实测结果)
|
||||
|
||||
| # | 命令 | 结果 | 退出码 |
|
||||
|---|------|------|--------|
|
||||
| 1 | `git status --short` / `git diff HEAD --stat -- pbl_runtime_ext/tx_write.py` | 均无输出 ⇒ 工作区与 HEAD 一致,**无本轮未提交的业务越权改动残留** | 0 |
|
||||
| 2 | `.venv/bin/python -m py_compile pbl_runtime_ext/tx_write.py` | OK | 0 |
|
||||
| 3 | `python3 -c "...[(x['name'],x.get('null')) for x in json.load(open('models/pbl_entity_state.json'))['fields']]"` | 四列 `tenant_id/session_id/entity_id/created_at` 全部 `null=False`(QC #12 要求的取证) | 0 |
|
||||
| 4 | 旧/新 `required_columns()` 判据对两表实测对比 | state 表 OLD==NEW(旧实现本就能识别);event 表 OLD=`[]` → NEW=`['event_uid','event_type']` | 0 |
|
||||
| 5 | `grep -rn "state_updates" scripts/ tests/` | 14 处调用点,`state_key/state_value` 形态正确,四列非 state_updates 合法入参 | 0 |
|
||||
| 6 | `.venv/bin/python -m pytest tests/ -v --tb=short` | **9 passed in 0.41s**(0 failed) | 0 |
|
||||
| 7 | `.venv/bin/python scripts/m11b_selftest.py` | 总计 110 项,PASS 110,FAIL 0,RESULT: PASS | 0 |
|
||||
|
||||
## 2. QC #12 响应(核心)
|
||||
|
||||
旧交付件根因判定 #3 表述错误,已删除并重写,要点:
|
||||
|
||||
1. **前提错**:`pbl_entity_state` 四列并非「无 NOT NULL 声明」,实测均为 `{"null": false}`;`type` 标注 `str/bigint/str/datetime` 与声明一致。
|
||||
2. **结论错**:旧 `required_columns()` 判据 `fd.get("null") is False`(见 `scripts/m11b2c_a3_patch_tx_write.py` `OLD_1` 第 23 行)**识别的正是 `null: False` 写法**,对 state 表这四列能正确判为必填 ⇒ 不存在「nullable 写法识别缺失」。实测 OLD 与 NEW 对 state 表返回集合完全相同。
|
||||
3. **推论删除**:「夹具无论怎么补建表列都无法消除该报错」基于错误前提,已删去。
|
||||
4. **归因统一**:state 表 PBL-RTX-0004 误报唯一归因到 #4 —— `_apply_state()` UPDATE 分支沿用 INSERT 全行校验,调用方按 UPSERT-UPDATE 语义不传定位维度(`tenant_id/session_id/entity_id` 走 WHERE)与 `created_at`(保持建档原值)→ 被误判缺列。
|
||||
5. **nullable 识别缺失的真实归属**:只影响 `pbl_runtime_event`(该表用规范写法 `nullable: "no"`,旧判据读不到 → OLD=`[]` → 事件表 INSERT 门禁**恒真 = 漏报**)。误报(state)与漏报(event)两向缺陷已在结论中分表列清,不再混谈。
|
||||
|
||||
修正后复跑 pytest = **9 passed**,符合 QC #12 验收要求。
|
||||
|
||||
## 3. 关键技术判定与偏差上报
|
||||
|
||||
- **不回退 `5542cf5` 的业务改动**:该 commit 是 PBL-RTX-0004 的唯一有效修复。若回退,`build_row()` 恢复「UPDATE 也跑 INSERT 全行校验」,T3 必然重新失败(回到 `3 failed, 6 passed`),与验收标准「`X passed, 0 failed`」冲突。
|
||||
- **夹具层无可补之处**(三条路径全部实测排除):① `tests/conftest.py` 只注入 sys.path,不建表,无「夹具建表缺列」;② `state_updates` 调用参数形态正确,四列不是其合法入参;③ models 两表 NOT NULL 声明均完整。⇒ 属「夹具/调用参数已正确仍报缺列」的业务实现缺陷,任务描述中「夹具层可收敛」的前提不成立,已如实记录。
|
||||
- **禁止整文件 `git checkout` 已遵守**:未执行任何 checkout/restore,避免误删前轮合法改动(删 append-only 死代码 + build-system 规范化)。
|
||||
- 修复后语义:`build_row(full_row=False)` 表达 UPDATE SET 子集语义并禁止 SET 不可变列(`tenant_id/id/created_at`,违者 `PBL-RTX-0006`);`_is_not_null()` 双写法识别 + `_has_db_default()` 排除自增/DEFAULT 列。
|
||||
|
||||
## 4. 允许范围与产出边界
|
||||
|
||||
- 允许修改的四个夹具文件(`scripts/m11b_selftest.py`、`scripts/m11b2c_p99_probe.py`、`tests/conftest.py`、`tests/test_m11b2c_runtime_ext.py`)本轮**零改动**;`pbl_runtime_ext/*.py` 业务实现与 `json/` 表定义本轮**零改动**。
|
||||
- 本轮产出仅两份文本:本工作日志 + `.m11b2c_tmp/m11b2c_a1_conclusion.md`(待归档文本,`.m11b2c_tmp/` 已在 `.gitignore` 登记)。
|
||||
- **未创建、未覆盖 `projects/pbls/docs/02-develop/` 下任何 log 终稿**(留证与交付件由 A2 承接)。
|
||||
|
||||
## 5. 环境限制说明
|
||||
|
||||
- 本机 MySQL/MariaDB 走 socket 不可用(`/run/mysqld/mysqld.sock` 不存在),实库 DDL 装载 runner `tests/run_m11b1a_localdb.sh` 需 TCP `127.0.0.1:3306` 沙箱库;本轮验证以离线 harness + 内存引擎 + selftest 110 项为准,实库回归归 A2/部署阶段。
|
||||
|
||||
## 6. 分支/提交状态
|
||||
|
||||
- 分支:`master`(HEAD `ecc7f23`);工作区干净,本轮无新增未提交改动。
|
||||
- git 收口由引擎代为执行,本日志不自称已 commit/push。
|
||||
119
scripts/m11b2c_a3_replay_check.py
Normal file
119
scripts/m11b2c_a3_replay_check.py
Normal file
@ -0,0 +1,119 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""M11b-2c-A3 交付自洽性重放校验器(可复跑,QC 复核用)。
|
||||
|
||||
背景(QC 退回 #10):上一轮引擎在交付时又代为提交了一次补丁器修正(51c3f26),
|
||||
说明 agent 交付的仓库状态并非最终自洽状态——补丁器与定稿 tx_write.py 不一致,
|
||||
靠引擎收口补齐。本脚本把「HEAD 即最终态」变成可机械复验的断言。
|
||||
|
||||
做法(不污染工作区,全部在临时目录内完成):
|
||||
1. 从 git 取 HEAD 版本的补丁器 scripts/m11b2c_a3_patch_tx_write.py;
|
||||
2. 从 git 取 5542cf5^(修复前)版本的 pbl_runtime_ext/tx_write.py 原文;
|
||||
3. 在临时目录里按补丁器自身的 TARGET 相对路径摆好文件,直接 import 并调用其
|
||||
main()(等价于在机构工作空间根执行该补丁器);
|
||||
4. 把重放产物与 HEAD 版本的 tx_write.py 逐字节比对(cmp + sha256 双口径);
|
||||
5. 一致 -> REPLAY_IDENTICAL: YES,退出码 0;不一致 -> NO,退出码 1。
|
||||
|
||||
用法(在模块仓库内或机构工作空间根均可):
|
||||
python3 scripts/m11b2c_a3_replay_check.py
|
||||
"""
|
||||
import hashlib
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
PATCHER = "scripts/m11b2c_a3_patch_tx_write.py"
|
||||
TX_WRITE = "pbl_runtime_ext/tx_write.py"
|
||||
FIX_COMMIT = "5542cf5d5182130aa929e5aa37d0c22a043cee25"
|
||||
|
||||
|
||||
def _git_rev_parse(rev):
|
||||
return _git("rev-parse", rev).strip()
|
||||
|
||||
|
||||
def _git(*args):
|
||||
out = subprocess.run(["git", "-C", REPO] + list(args),
|
||||
check=True, stdout=subprocess.PIPE)
|
||||
return out.stdout.decode("utf-8")
|
||||
|
||||
|
||||
def _sha(path):
|
||||
with io.open(path, "rb") as f:
|
||||
return hashlib.sha256(f.read()).hexdigest()
|
||||
|
||||
|
||||
def main():
|
||||
print("== M11b-2c-A3 补丁器/定稿一致性重放校验 ==")
|
||||
print("repo : %s" % REPO)
|
||||
head = _git_rev_parse("HEAD")
|
||||
print("HEAD : %s" % head)
|
||||
print("HEAD subject : %s" % _git("log", "-1", "--format=%s").strip())
|
||||
print("fix commit : %s" % FIX_COMMIT)
|
||||
print("patcher@HEAD : %s" % _git_rev_parse("HEAD:%s" % PATCHER))
|
||||
print("tx_write@HEAD : %s" % _git_rev_parse("HEAD:%s" % TX_WRITE))
|
||||
print("tx_write@fix^ : %s" % _git_rev_parse("%s^:%s" % (FIX_COMMIT, TX_WRITE)))
|
||||
|
||||
tmp = tempfile.mkdtemp(prefix="m11b2c_a3_replay_")
|
||||
rc = 1
|
||||
try:
|
||||
# 1) 取 HEAD 版补丁器
|
||||
patcher_dir = os.path.join(tmp, "inst", "modules", "pbl_runtime_ext", "scripts")
|
||||
target_dir = os.path.join(tmp, "inst", "modules", "pbl_runtime_ext", "pbl_runtime_ext")
|
||||
os.makedirs(patcher_dir)
|
||||
os.makedirs(target_dir)
|
||||
with io.open(os.path.join(patcher_dir, os.path.basename(PATCHER)), "w",
|
||||
encoding="utf-8") as f:
|
||||
f.write(_git("show", "HEAD:%s" % PATCHER))
|
||||
|
||||
# 2) 取修复前 tx_write.py 原文,摆到补丁器 TARGET 的相对位置
|
||||
pre_path = os.path.join(target_dir, "tx_write.py")
|
||||
with io.open(pre_path, "w", encoding="utf-8") as f:
|
||||
f.write(_git("show", "%s^:%s" % (FIX_COMMIT, TX_WRITE)))
|
||||
head_path = os.path.join(tmp, "tx_write_HEAD.py")
|
||||
with io.open(head_path, "w", encoding="utf-8") as f:
|
||||
f.write(_git("show", "HEAD:%s" % TX_WRITE))
|
||||
|
||||
pre_sha = _sha(pre_path)
|
||||
head_sha = _sha(head_path)
|
||||
print("pre-fix sha256 : %s" % pre_sha)
|
||||
print("HEAD sha256 : %s" % head_sha)
|
||||
|
||||
# 3) 在临时目录内执行 HEAD 版补丁器(TARGET 是相对路径,故 cwd 必须是 inst/)
|
||||
sys.path.insert(0, patcher_dir)
|
||||
mod_name = "m11b2c_a3_patch_tx_write"
|
||||
if mod_name in sys.modules:
|
||||
del sys.modules[mod_name]
|
||||
patcher = __import__(mod_name)
|
||||
cwd = os.getcwd()
|
||||
os.chdir(os.path.join(tmp, "inst"))
|
||||
try:
|
||||
prc = patcher.main()
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
print("patcher exit rc : %d (0=已应用 4 处补丁, 2=片段命中数异常)" % prc)
|
||||
if prc != 0:
|
||||
print("REPLAY_IDENTICAL: NO(补丁器在修复前原文上执行失败,rc=%d)" % prc)
|
||||
return 1
|
||||
|
||||
# 4) 逐字节比对
|
||||
replayed_sha = _sha(pre_path)
|
||||
print("replayed sha256 : %s" % replayed_sha)
|
||||
cmp_rc = subprocess.run(["cmp", pre_path, head_path]).returncode
|
||||
print("cmp 退出码 : %d (0=逐字节一致)" % cmp_rc)
|
||||
if replayed_sha == head_sha and cmp_rc == 0:
|
||||
print("REPLAY_IDENTICAL: YES(补丁器重放产物 == HEAD 定稿,逐字节一致)")
|
||||
print("结论:HEAD 即最终自洽态,补丁器与定稿 tx_write.py 一致,无待引擎收口内容。")
|
||||
rc = 0
|
||||
else:
|
||||
print("REPLAY_IDENTICAL: NO(补丁器与定稿不一致,仓库状态非最终态)")
|
||||
rc = 1
|
||||
return rc
|
||||
finally:
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
x
Reference in New Issue
Block a user