From 95b405fd25d05c934954f75f0d3b03a0ef63f380 Mon Sep 17 00:00:00 2001 From: "agent.develop" Date: Sun, 20 Sep 2026 18:32:45 +0800 Subject: [PATCH] =?UTF-8?q?deliver:=20=E4=BA=A4=E4=BB=98=E6=94=B6=E5=8F=A3?= =?UTF-8?q?=EF=BC=88=E5=BC=95=E6=93=8E=E4=BB=A3=E4=B8=BA=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/validate_models_json.py | 69 +++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/scripts/validate_models_json.py b/scripts/validate_models_json.py index 1895f3d..3fc9091 100644 --- a/scripts/validate_models_json.py +++ b/scripts/validate_models_json.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -"""validate_models_json —— database-table-definition-spec 机械校验器(M11b-2b-A 修复版 rev3)。 +"""validate_models_json —— database-table-definition-spec 机械校验器(M11b-2b-A 修复版 rev4)。 QC #4/#7/#9/#10 要求:表定义 JSON 必须是 spec 规定的四段式(summary/fields/indexes/ codes),类型必须是 spec 声明的抽象类型,主键 id 必须是 str/length>=32,索引必须用 @@ -11,13 +11,19 @@ codes),类型必须是 spec 声明的抽象类型,主键 id 必须是 str/ QC #2 抽象类型误报:纯抽象类型名(datetime/timestamp/date/time/text/long/…)一律放行, 只有 type 字符串本身含括号或空格(方言写法 varchar(64)/decimal(15,2)/datetime(3)) 或本身就是 spec 不存在的非抽象类型名(varchar/bigint/json/blob/…)才判违规。 + NATIVE_TYPE_WORDS 中已删除 "datetime(" / "timestamp(" 两项。 QC #3 CLI 与文档一致:argparse 真正提供 --allow-registered-deviation(见下方「用法」)。 + 同一轮 QC #3 追加要求「拦截分布可一条命令复现」:本版本在结尾输出 + ``RULE_STATS: <规则标签>=<条数>`` 逐规则计数 + ``RULE_STATS_TOTAL`` 总量, + 复核者只需一条命令即可核对分布,不必依赖任何中间文件: + python3 modules/world_sync/scripts/validate_models_json.py \ + $(ls -d modules/*/models) 2>&1 | grep "^RULE_STATS" QC #4 root_keys_ok:由 validate_model 显式计算 (not missing and not extra) 并随结果返回, main() 直接打印该布尔值,不再用「错误串里是否含『四段式』」猜测。 QC #5 引用式 key/schema:spec 不承认引用式结构,直接判 ERROR 并提示改为内联四段式; COMPLIANCE_DEVIATIONS 中针对 pbl_runtime_event/pbl_entity_state 的 4 条失效登记项 一并删除(引用式在根键检查处即 FAIL,根本到不了 fields 校验,登记形同虚设)。 - QC #1(本轮)放行机制对 auto_increment 生效:字段段「spec 未定义键」检查原先无条件报错, + QC #1(第三轮)放行机制对 auto_increment 生效:字段段「spec 未定义键」检查原先无条件报错, 导致 (表名,"id","auto-increment") 即使已登记、即使传了 --allow-registered-deviation, 仍残留 `fields[N](id) 含 spec 未定义键: auto_increment` 而整体 FAIL —— id-not-str32 可豁免、auto_increment 不可豁免,机制自相矛盾。现在该检查改为**偏离感知**: @@ -35,7 +41,10 @@ codes),类型必须是 spec 声明的抽象类型,主键 id 必须是 str/ 典型场景:分区表物理主键需要 long + auto_increment(MySQL 1503:分区键必须进主键)。 若某表确需此类写法,须先在其模块任务里把 (表名,"id","id-not-str32") 与 (表名,"id","auto-increment") 登记进 COMPLIANCE_DEVIATIONS,再配合开关使用 —— 两者缺一不可, - 只登记不传开关仍 FAIL,只传开关不登记也 FAIL(本文件末尾自测 5 给出四态实测输出)。 + 只登记不传开关仍 FAIL,只传开关不登记也 FAIL。 + 与并行子任务 B 的接口:B 把 pbl_runtime_event/pbl_entity_state 内联为四段式后,若保留 + id=long(+auto_increment) 写法,须由 B 在其任务内按上述键格式登记(本任务按 QC #5 选 (a), + 登记表保持为空 {},不放行任何历史失效项)。 用法: python validate_models_json.py [<目录2> ...] @@ -81,6 +90,49 @@ COMPLIANCE_DEVIATIONS = {} DEVIATION_HINT = "需 --allow-registered-deviation 放行" +# QC #3(第三轮):逐规则计数标签 —— 让「真·非抽象类型仍被拦截」的分布不依赖中间文件, +# 一条命令即可复现(结尾 RULE_STATS 行)。标签按下列顺序尝试匹配(首个命中即归类)。 +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 禁止",)), +) + + +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 + return "other" + def _is_reference_style(data): """识别引用式结构:{"key": "schema", "schema": {...}} 之类(spec 不承认)。""" @@ -372,13 +424,20 @@ def main(argv=None): total_files = 0 total_errors = 0 total_deviations = 0 + rule_counts = {} report = [] for path in collect_targets(dirs, name_filters or None): errors, deviations, table, root_keys_ok = validate_model(path, strict_id=strict_id) total_files += 1 total_errors += len(errors) total_deviations += len(deviations) + per_file_rules = {} + for e in errors: + tag = _rule_tag(e) + rule_counts[tag] = rule_counts.get(tag, 0) + 1 + per_file_rules[tag] = per_file_rules.get(tag, 0) + 1 report.append({"path": path, "table": table, "root_keys_ok": root_keys_ok, + "rule_stats": per_file_rules, "errors": errors, "deviations": deviations}) print("---- %s (table=%s)" % (path, table)) print(" root_keys_ok=%s" % root_keys_ok) # QC #4:打印显式计算的布尔值 @@ -389,6 +448,10 @@ def main(argv=None): if not errors: print(" OK: 符合 database-table-definition-spec(偏离 %d 条已登记)" % len(deviations)) print("=" * 60) + # QC #3:逐规则计数(不依赖中间文件即可复核「真·非抽象类型仍被拦截、抽象类型未放水」) + for tag, cnt in sorted(rule_counts.items(), key=lambda kv: (-kv[1], kv[0])): + print("RULE_STATS: %s=%d" % (tag, cnt)) + 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")) if args.json_report: