deliver: 交付收口(引擎代为提交)
This commit is contained in:
parent
aff1f0da0c
commit
c66868cbd3
96
docs/work-log-2026-09-20.md
Normal file
96
docs/work-log-2026-09-20.md
Normal file
@ -0,0 +1,96 @@
|
||||
# work-log 2026-09-20 — world_sync(M11b-2a 迁移 + M11b-2a-1 根包合规 + M11b-2a-2 文档归档)
|
||||
|
||||
- 仓库:`modules/world_sync`(remote `yumoqing/world_sync`,branch `main`)
|
||||
- 记录人:agent.develop | 记录时间:2026-09-20 19:20 (+08:00)
|
||||
- 关联任务:`[M11b-2a]` u6kCWdLV2k5cmuG8Yb5BY(父)/`[M11b-2a-1]` dPgvSjRDNQ6kIQi0K-6HT(approved)/`[M11b-2a-2]` hVZNlBKmtiNNzn6BPeA3J(本记录)
|
||||
- 关联迭代:pbls-初始迭代 | 响应 QC 未过项:#1 开发说明缺失、#2 过程归档缺失(#3 由 M11b-2a-1 处置)
|
||||
|
||||
---
|
||||
|
||||
## 1. Scope(本任务链做了什么 / 不做什么)
|
||||
|
||||
**做:**
|
||||
1. 把 M11b-2「单事务事件+状态原子写入」实现从应用侧 `apps/scense/pkgs/world_sync/world_sync/` 迁移到模块仓库 `modules/world_sync/world_sync/`(5 个新增子模块 + `__init__.py` + `init.py`),tests/scripts 同迁 —— 落实「develop 模块源码必须落 modules/」。
|
||||
2. 三处接线:内层包 re-export 契约符号、`init.py` loader 命名与 ServerEnv 登记、根包导出(后者经核对判定违规后撤销,见 §3)。
|
||||
3. 结构合规处置:删除仓库根 `__init__.py`(156 行转发包)+ `pyproject.toml` 收紧打包面 + 导入闭包重验。
|
||||
4. 文档归档(M11b-2a-2):`projects/pbls/docs/02-develop/dev-notes-m11b2a-world-sync-migration.md` + 本 work-log。
|
||||
|
||||
**不做:** 业务逻辑(事务/行锁/回滚/乐观并发/权限判定)零改动;`models/` JSON 表定义零改动(M11b-2b);apps 侧部署副本清理未强制(§5 遗留项 3)。
|
||||
|
||||
---
|
||||
|
||||
## 2. Commit 列表
|
||||
|
||||
| commit | 时间 | 内容 |
|
||||
|---|---|---|
|
||||
| `01941aa` | 2026-09-20 | `deliver: 交付收口(引擎代为提交)` —— 迁移本体落库:`world_sync/{pbl_runtime_errors,pbl_runtime_sql,pbl_runtime_tx,pbl_runtime_tx_env,pbl_runtime_tx_sqlor}.py` + `__init__.py`(6187B) + `init.py`(22123B) + tests/scripts;三处接线成立(含当时的根包转发,156 行) |
|
||||
| `9f97d58` | 09-20 18:31 | `M11b-2a: resolve root-package spec compliance (drop repo-root __init__.py forwarding package)` —— **删除** `modules/world_sync/__init__.py`(1 file changed, 156 deletions);commit message 内记录宿主接线行号证据与删除后复验结论;用 pathspec 有意排除 M11b-2b WIP `scripts/validate_models_json.py` |
|
||||
| `95b405f` | 09-20 | `deliver: 交付收口(引擎代为提交)` —— M11b-2a-1 交付件收口 |
|
||||
| `aff1f0d` | 09-20 19:12 | `M11b-2a: resolve root-package spec compliance (rev2: tighten packaging surface, record real import evidence)` —— `pyproject.toml:25-28` `[tool.setuptools.packages.find] where=["."] include=["world_sync*"] namespaces=false`;**更正 rev1 不实的 case1 输出**,补 host(venv editable)取证 |
|
||||
| 本任务 | — | 仅新增文档:`projects/pbls/docs/02-develop/dev-notes-m11b2a-world-sync-migration.md`、`modules/world_sync/docs/work-log-2026-09-20.md`(git 收口由引擎执行,本记录不自称已 commit/push) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 关键决策与陷阱
|
||||
|
||||
### 3.1 为何「删除」根包转发,而不是「保留 + 收紧」(QC #3 给了二选一)
|
||||
|
||||
- **规范**:module-development-spec 规定 `mymodule/` 是仓库根、`mymodule/mymodule/` 才是 Python 包,仓库根不列 `__init__.py`;同机构 `modules/pbl_runtime_ext/` 等仓库根均无 `__init__.py`。
|
||||
- **宿主接线实测不依赖它**:`apps/scense/app/scense.py:50 from world_sync.init import load_world_sync`(L57 调用)、`apps/yuanjing/app/yuanjing.py:66 __import__(f'{module}.init', ...)` —— 两者都以 sys.path=仓库根解析到**内层** `world_sync/init.py`。转发表无人调用 = 死代码。
|
||||
- **风险**:根包 + `packages.find include=["world_sync*"]` 组合会把仓库根一并发现为包,产生「同一模块两条导入路径、两份 `__init__` 语义」的双路径歧义,后续 develop 极易误用根包路径写出跑不通的接线。
|
||||
- **决策**:走 (a) 删除(`9f97d58`),并追加 (b) 的配置部分(`aff1f0d`)把约束固化进 `pyproject.toml`,防回归。
|
||||
|
||||
### 3.2 陷阱:`init/` 数据目录遮蔽 `init.py`(case1 的真实 FAIL)
|
||||
|
||||
`modules/world_sync/init/data.json` 是模块既有数据目录。当 sys.path 落在**模块父目录** `modules/` 时,`world_sync.init` 优先解析到该目录(namespace 片段)而非 `init.py`,于是 `from world_sync.init import load_world_sync` → `ImportError: cannot import name 'load_world_sync' from 'world_sync.init' (unknown location)`。
|
||||
用删除前基线 `01941aa` 沙箱复刻同场景:**删除前同样 FAIL** ⇒ 非本次删除引入的回归。结论:world_sync 的 loader 接线以 **sys.path=仓库根**(宿主实际做法)为准,`sys.path=modules/` 不作为契约路径;若要支持需单独立项(重命名数据目录或调 `scripts/load_path.py` 注入顺序)。
|
||||
|
||||
### 3.3 陷阱:把「删除前的验证输出」当成「删除后的证据」(流程教训)
|
||||
|
||||
M11b-2a-1 rev1 文档 §4.2 与交付摘要 §3 中 case1 的 OK 输出块为不实记录(错误归因),被 QC #7/#8/#14 指出。rev2 已删除伪造块、以 `bash projects/pbls/scripts/m11b2a1_rev2_revalidate.sh` 落盘输出替换(全文 `projects/pbls/evidence/m11b2a1_rev2_import_closure.txt`)。本 work-log 与 dev-notes 中所有输出为 2026-09-20 19:20 **再次独立复跑**,与 rev2 一致。教训:验证必须在处置动作之后重跑,且证据要落盘可复核。
|
||||
|
||||
### 3.4 决策:单一真源
|
||||
|
||||
契约函数实现只在 `world_sync/pbl_runtime_tx_sqlor.py`,`__init__.py:40 from .init import (...)` / L119–120 只做 re-export(实测 `write_event_with_state.__module__ == 'world_sync.pbl_runtime_tx_sqlor'`),不允许在包 `__init__` 或根包中写第二份实现。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证(2026-09-20 19:20 实测)
|
||||
|
||||
| 项 | 命令 | 结果 |
|
||||
|---|---|---|
|
||||
| 语法 | `python3 -m py_compile world_sync/*.py scripts/*.py` | `rc=0` 全通过 |
|
||||
| case2 sys.path=仓库根 | `import world_sync` / `from world_sync.init import load_world_sync` / 4 契约符号 | OK;`__file__` → `modules/world_sync/./world_sync/__init__.py`;仓库根 `__init__.py` 存在性 = `False`;`__all__ len=62 MISSING=[]`;`issubclass(ConcurrentStateConflict, PblRuntimeError)=True` |
|
||||
| case1 sys.path=`modules/` | 同上 | **FAIL**(`_NamespacePath`/`__file__=None`/`ImportError (unknown location)`),根因 §3.2,非回归 |
|
||||
| host sys.path=`apps/scense/pkgs/world_sync` | 同上 | OK;`__file__` → apps 副本内层包;`__all__: 62 MISSING: []` |
|
||||
| 迁移一致性 | `diff -q` 8 个 .py(模块仓库 vs apps 副本) | 8/8 IDENTICAL |
|
||||
| 工作区 | `git status --porcelain` | 无输出 = clean;branch `main`,`remotes/origin/main` 存在,另有 `archive/m11b1-in-world-sync` |
|
||||
|
||||
完整输出块见 `projects/pbls/docs/02-develop/dev-notes-m11b2a-world-sync-migration.md` §4。
|
||||
|
||||
---
|
||||
|
||||
## 5. 受环境限制未验证项(不得当作已验证)
|
||||
|
||||
1. **真实 DB 建连与写入路径未跑**:沙箱无 MySQL/PG。`configure_runtime_writer`(`init.py:326`)、单事务内「事件写 + 实体状态写 + 行锁」原子性、失败回滚、`ConcurrentStateConflict` 实际抛出路径 —— 均只有静态/import/mock 级覆盖,需部署态联调。
|
||||
2. **`tests/test_m11b2_single_tx.py` / `test_m11b2_wiring.py` 未执行**:依赖 DB fixture,本轮只保证可编译可导入。
|
||||
3. **`env.pbl_runtime_conn_factory` 端到端解析未跑**:`init.py:480/483` 的 ServerEnv 键解析链需应用启动态验证。
|
||||
4. **apps 侧薄壳清理未做(原任务第 4 项为可选)**:`apps/scense/pkgs/world_sync/world_sync/` 保留 8 个 IDENTICAL 文件,因为宿主 `scense.py:50` 实际加载的就是该 editable 副本;删除会让 scense 立即失去模块。建议后续改为部署期同步产物(`build.sh` / `scripts/sync_models_to_app.py`)+ `.gitignore` 排除,做到「模块仓库唯一真源」。
|
||||
5. **`models/` JSON 与 SQL 列清单 diff**:留 M11b-2b(`scripts/m11b2b_column_diff.py` 已就位)。
|
||||
6. **`sys.path=modules/` 契约路径**:已确认不可用并文档化(§3.2),未修复。
|
||||
|
||||
---
|
||||
|
||||
## 6. 当前 branch-commit 状态(记录时点)
|
||||
|
||||
```
|
||||
branch: main(tracking remotes/origin/main);工作区 clean
|
||||
HEAD: aff1f0d M11b-2a: resolve root-package spec compliance (rev2: tighten packaging surface, record real import evidence)
|
||||
95b405f deliver: 交付收口(引擎代为提交)
|
||||
9f97d58 M11b-2a: resolve root-package spec compliance (drop repo-root __init__.py forwarding package)
|
||||
01941aa deliver: 交付收口(引擎代为提交)
|
||||
仓库根结构:README.md / pyproject.toml / init/(数据目录) / json/ / models/ / scripts/ / skill/ / tests/ / wwwroot/ / world_sync/(内层包) / docs/(本记录)
|
||||
无仓库根 __init__.py —— 对外唯一路径:world_sync.init(loader)、world_sync(内层包,sys.path=仓库根)、world_sync.world_sync.*(子模块,sys.path=仓库根时等价 world_sync.*)
|
||||
```
|
||||
|
||||
本任务(M11b-2a-2)新增文档后的 commit 由引擎交付收口统一执行,本记录不自称已 commit/push。
|
||||
@ -18,6 +18,12 @@ codes),类型必须是 spec 声明的抽象类型,主键 id 必须是 str/
|
||||
复核者只需一条命令即可核对分布,不必依赖任何中间文件:
|
||||
python3 modules/world_sync/scripts/validate_models_json.py \
|
||||
$(ls -d modules/*/models) 2>&1 | grep "^RULE_STATS"
|
||||
第四轮补强(本轮唯一修项):RULE_STATS_TAGS 改为「tag -> 候选文案列表,每个候选是
|
||||
需同时命中的子串元组」,_rule_tag() 取首个「任一候选全命中」的 tag;并在打印
|
||||
RULE_STATS_TOTAL **之前**断言 other==0 —— 仍有未归类 ERROR 时逐条打印
|
||||
``RULE_STATS_OTHER_DETAIL: <path> | <原文>`` 后以退出码 2 结束(视为分布不可复现)。
|
||||
不改动任何 errors.append 文案与判定逻辑,故既有回归断言字符串
|
||||
(errors=0 / RESULT: PASS / DEVIATION(registered) / root_keys_ok=)全部不变。
|
||||
QC #4 root_keys_ok:由 validate_model 显式计算 (not missing and not extra) 并随结果返回,
|
||||
main() 直接打印该布尔值,不再用「错误串里是否含『四段式』」猜测。
|
||||
QC #5 引用式 key/schema:spec 不承认引用式结构,直接判 ERROR 并提示改为内联四段式;
|
||||
@ -91,46 +97,91 @@ COMPLIANCE_DEVIATIONS = {}
|
||||
DEVIATION_HINT = "需 --allow-registered-deviation 放行"
|
||||
|
||||
# QC #3(第三轮):逐规则计数标签 —— 让「真·非抽象类型仍被拦截」的分布不依赖中间文件,
|
||||
# 一条命令即可复现(结尾 RULE_STATS 行)。标签按下列顺序尝试匹配(首个命中即归类)。
|
||||
# 一条命令即可复现(结尾 RULE_STATS 行)。
|
||||
#
|
||||
# QC #3(第四轮)结构重构:本表是「tag -> 候选文案列表」,**每个候选是一个需同时命中的子串
|
||||
# 元组**(AND),tag 命中条件是「任一候选全命中」(OR)。_rule_tag() 按本表顺序取首个命中。
|
||||
# 上一轮的缺陷(本轮唯一修项,全平台扫描 983 条 ERROR 中 35 条落入 other):
|
||||
# 1) 多条「候选文案」被误写成「同时命中」的单一元组 —— unreadable_json 的三串、
|
||||
# summary_shape 的三串因此永不命中;
|
||||
# 2) code_required_missing 的 needle ".必填" 与真实文案 "codes[0].table 必填"
|
||||
# (「.」与「必填」之间有一个空格)差一个空格 → 10 条 codes 必填全漏;
|
||||
# 3) fields[N] / indexes[N](x) / codes[N] 的「必须是 object」、「fields 必须是非空数组」、
|
||||
# "indexes[N](x).name 必填"、"summary[0] 含 spec 未定义键" 等文案根本没有标签。
|
||||
# 所有 needle 均逐条对照 validate_model() / check_type_value() 里 errors.append 的**原文**写就。
|
||||
#
|
||||
# 顺序铁律:**更具体的标签必须排在更泛的之前**,否则会被泛化标签抢走。本表已知的子串包含关系:
|
||||
# - "idxfields 必须是非空数组" ⊃ "fields 必须是非空数组" → index_fields_bad 早于 fields_array_shape
|
||||
# - "indexes[N](x).name 必填" ⊃ ".name 必填" → index_name_missing 早于 field_name_missing
|
||||
# - "summary[0].name 必填" ⊃ ".name 必填" → summary_required_missing 早于二者
|
||||
# - "fields[N](id) 含 spec 未定义键: auto_increment" ⊃ field_unknown_key
|
||||
# → id_auto_increment 早于 field_unknown_key
|
||||
# - "id 带 spec 未定义键 auto_increment" / "auto_increment 未在偏离登记表中" 不得被
|
||||
# 泛化标签抢走 → id_auto_increment_key / id_auto_increment_unregistered 单列且排最前
|
||||
RULE_STATS_TAGS = (
|
||||
("unreadable_json", ("JSON 不可解析", "文件不可读", "根对象必须是 JSON object")),
|
||||
("reference_style_key_schema", ("引用式写法",)),
|
||||
("root_key_extra", ("根键多出 spec 未定义的段",)),
|
||||
("root_key_missing", ("根键缺少 spec 必需段",)),
|
||||
("type_db_native", ("是数据库原生类型",)),
|
||||
("type_db_dialect", ("是数据库方言具体类型",)),
|
||||
("type_not_in_abstract_table", ("不在 spec 抽象类型表内",)),
|
||||
("type_not_string", ("type 必须是非空字符串",)),
|
||||
("pk_id_not_str32", ("主键 id 必须 str 且 length>=32",)),
|
||||
("id_auto_increment", ("auto_increment",)),
|
||||
("field_length_dec_missing", ("必须带正整数",)),
|
||||
("field_nullable_bad", ("nullable 只能是",)),
|
||||
("field_name_missing", (".name 必填",)),
|
||||
("field_title_missing", (".title 必填",)),
|
||||
("field_unknown_key", ("fields[", "含 spec 未定义键")),
|
||||
("field_dup_name", ("字段名重复",)),
|
||||
("summary_shape", ("summary 必须是恰好 1 条记录的数组", "summary[0].", "summary[0] 含 spec 未定义键")),
|
||||
("primary_not_array", ("primary 必须是非空数组",)),
|
||||
("primary_unknown_col", ("primary 引用了不存在的列",)),
|
||||
("index_unknown_key", ("indexes[", "含 spec 未定义键")),
|
||||
("index_type_bad", ("idxtype 必须是",)),
|
||||
("index_fields_bad", ("idxfields 必须是非空数组",)),
|
||||
("index_ref_unknown_col", ("idxfields 引用不存在的列",)),
|
||||
("index_name_primary", ("主键不写进 indexes",)),
|
||||
("index_dup_name", ("索引名重复",)),
|
||||
("code_unknown_key", ("codes[", "含 spec 未定义键")),
|
||||
("code_required_missing", ("codes[", ".必填")),
|
||||
("code_ref_unknown_col", ("codes[", "引用不存在的列")),
|
||||
("code_cond_parentid", ("cond 必须用 parentid=",)),
|
||||
("code_table_dot", ("codes.table 禁止",)),
|
||||
# ---- 1) 文件级:三种不可用情形(同一 tag,三个**候选**,任一命中即归类)----
|
||||
("unreadable_json", (("JSON 不可解析",), ("文件不可读",),
|
||||
("根对象必须是 JSON object",))),
|
||||
("reference_style_key_schema", (("引用式写法",),)),
|
||||
("root_key_extra", (("根键多出 spec 未定义的段",),)),
|
||||
("root_key_missing", (("根键缺少 spec 必需段",),)),
|
||||
# ---- 2) summary 段:原 summary_shape 一个永不命中的 tag 拆成三类,各自单列 ----
|
||||
("summary_shape_bad", (("summary 必须是恰好 1 条记录的数组",),)),
|
||||
("summary_unknown_key", (("summary[0] 含 spec 未定义键",),)),
|
||||
("summary_required_missing", (("summary[0].name 必填",), ("summary[0].title 必填",))),
|
||||
# 承载原 primary_not_array 的同一批文案("summary[0].primary 必须是非空数组…"),
|
||||
# 更名以与 summary_* 系列对齐,条数不变;泛化 needle 保留以兼容任何前缀变体。
|
||||
("summary_primary_shape", (("primary 必须是非空数组",),)),
|
||||
# ---- 3) type 四类(QC #2 判定行为不变,此处只归类)----
|
||||
("type_db_native", (("是数据库原生类型",),)),
|
||||
("type_db_dialect", (("是数据库方言具体类型",),)),
|
||||
("type_not_in_abstract_table", (("不在 spec 抽象类型表内",),)),
|
||||
("type_not_string", (("type 必须是非空字符串",),)),
|
||||
("field_length_dec_missing", (("必须带正整数",),)),
|
||||
("field_nullable_bad", (("nullable 只能是",),)),
|
||||
# ---- 4) id 专属:auto_increment 三种文案各自单列,且必须早于泛化 field_unknown_key ----
|
||||
("id_auto_increment_unregistered", (("auto_increment 未在偏离登记表中",),)),
|
||||
("id_auto_increment_key", (("id 带 spec 未定义键 auto_increment",),)),
|
||||
("pk_id_not_str32", (("主键 id 必须 str 且 length>=32",),)),
|
||||
("id_auto_increment", (("fields[", "含 spec 未定义键: auto_increment"),)),
|
||||
("field_unknown_key", (("fields[", "含 spec 未定义键"),)),
|
||||
# ---- 5) indexes 段(**整段**早于 fields_array_shape / field_name_missing,理由见上)----
|
||||
("index_unknown_key", (("indexes[", "含 spec 未定义键"),)),
|
||||
("index_name_missing", (("indexes[", ".name 必填"),)),
|
||||
("index_type_bad", (("idxtype 必须是",),)),
|
||||
("index_fields_bad", (("idxfields 必须是非空数组",),)),
|
||||
("index_ref_unknown_col", (("idxfields 引用不存在的列",),)),
|
||||
("index_name_primary", (("主键不写进 indexes",),)),
|
||||
("index_dup_name", (("索引名重复",),)),
|
||||
# ---- 6) codes 段 ----
|
||||
("code_unknown_key", (("codes[", "含 spec 未定义键"),)),
|
||||
("code_ref_unknown_col", (("codes[", "引用不存在的列"),)),
|
||||
# 真实文案 "codes[0].table 必填"(有空格),故用 ("codes[", "必填") 两子串同时命中
|
||||
("code_required_missing", (("codes[", "必填"),)),
|
||||
("code_cond_parentid", (("cond 必须用 parentid=",),)),
|
||||
("code_table_dot", (("codes.table 禁止",),)),
|
||||
# ---- 7) 其余结构 / 必填类(泛化,必须排在带前缀的专属标签之后)----
|
||||
# fields[N] / indexes[N](x) / codes[N] 三种「必须是 object」文案共用一个 tag
|
||||
("item_not_object", (("必须是 object",),)),
|
||||
("fields_array_shape", (("fields 必须是非空数组",),)),
|
||||
("field_name_missing", ((".name 必填",),)),
|
||||
("field_title_missing", ((".title 必填",),)),
|
||||
("field_dup_name", (("字段名重复",),)),
|
||||
("primary_unknown_col", (("primary 引用了不存在的列",),)),
|
||||
)
|
||||
|
||||
|
||||
def _rule_tag(message):
|
||||
"""把一条 ERROR 文本归类到规则标签(QC #3:分布可由一条命令复现)。"""
|
||||
for tag, needles in RULE_STATS_TAGS:
|
||||
if all(n in message for n in needles):
|
||||
return tag
|
||||
"""把一条 ERROR 文本归类到规则标签(QC #3:分布可由一条命令复现)。
|
||||
|
||||
语义(第四轮):RULE_STATS_TAGS 每项为 (tag, candidates),candidates 是若干候选,
|
||||
每个候选是**需同时命中**的子串元组;本函数返回**首个**「任一候选全命中」的 tag,
|
||||
全不命中返回 "other"(main() 会断言 other==0,不允许出现)。
|
||||
"""
|
||||
for tag, candidates in RULE_STATS_TAGS:
|
||||
for needles in candidates:
|
||||
if all(n in message for n in needles):
|
||||
return tag
|
||||
return "other"
|
||||
|
||||
|
||||
@ -425,6 +476,7 @@ def main(argv=None):
|
||||
total_errors = 0
|
||||
total_deviations = 0
|
||||
rule_counts = {}
|
||||
other_details = [] # QC #3(第四轮):未归类 ERROR 明细 (path, 原文),用于断言 other==0
|
||||
report = []
|
||||
for path in collect_targets(dirs, name_filters or None):
|
||||
errors, deviations, table, root_keys_ok = validate_model(path, strict_id=strict_id)
|
||||
@ -436,6 +488,8 @@ def main(argv=None):
|
||||
tag = _rule_tag(e)
|
||||
rule_counts[tag] = rule_counts.get(tag, 0) + 1
|
||||
per_file_rules[tag] = per_file_rules.get(tag, 0) + 1
|
||||
if tag == "other":
|
||||
other_details.append((path, e))
|
||||
report.append({"path": path, "table": table, "root_keys_ok": root_keys_ok,
|
||||
"rule_stats": per_file_rules,
|
||||
"errors": errors, "deviations": deviations})
|
||||
@ -451,6 +505,21 @@ def main(argv=None):
|
||||
# QC #3:逐规则计数(不依赖中间文件即可复核「真·非抽象类型仍被拦截、抽象类型未放水」)
|
||||
for tag, cnt in sorted(rule_counts.items(), key=lambda kv: (-kv[1], kv[0])):
|
||||
print("RULE_STATS: %s=%d" % (tag, cnt))
|
||||
# QC #3(第四轮)硬门禁:归类不得有黑洞 —— other 必须为 0,否则「各标签条数之和 ==
|
||||
# RULE_STATS_TOTAL」不成立、分布无法逐规则核对。此时逐条打印未归类原文并以退出码 2 结束
|
||||
# (视为分布不可复现),不再打印 RULE_STATS_TOTAL / RESULT。
|
||||
other_count = rule_counts.get("other", 0)
|
||||
if other_count:
|
||||
for path, msg in other_details:
|
||||
print("RULE_STATS_OTHER_DETAIL: %s | %s" % (path, msg))
|
||||
print("RULE_STATS_CHECK: FAIL(%d 条 ERROR 未归入任何规则标签,拦截分布不可复现)"
|
||||
% other_count)
|
||||
if args.json_report:
|
||||
with open(args.json_report, "w", encoding="utf-8") as fh:
|
||||
json.dump(report, fh, ensure_ascii=False, indent=1)
|
||||
return 2
|
||||
assert sum(rule_counts.values()) == total_errors, \
|
||||
"RULE_STATS 各标签条数之和(%d) != errors 总数(%d)" % (sum(rule_counts.values()), total_errors)
|
||||
print("RULE_STATS_TOTAL: errors=%d files=%d" % (total_errors, total_files))
|
||||
print("files=%d errors=%d registered_deviations=%d" % (total_files, total_errors, total_deviations))
|
||||
print("RESULT: %s" % ("PASS" if total_errors == 0 else "FAIL"))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user