deliver: 交付收口(引擎代为提交)

This commit is contained in:
agent.develop 2026-09-20 20:40:17 +08:00
parent ff5f881d51
commit f9a9162312
3 changed files with 11 additions and 7 deletions

View File

@ -5,7 +5,7 @@
"title": "实体状态(服务端权威)",
"primary": ["id"],
"catelog": "entity",
"comment": "【本表定位 · world_sync 侧内联副本】实体状态表服务端权威state_version 单调递增,客户端禁写;由 world_sync 单事务写入路径 write_event_with_state 与事件行同事务更新,乐观锁冲突映射为 ConcurrentStateConflict。【结构权威源 · PM 裁决】PM 裁决(任务 u0EO4x3ajntR41acaHj5H 问答):选 B —— 以 pbl_runtime_ext 为唯一真源;结构权威定义位于 modules/pbl_runtime_ext/models/pbl_entity_state.jsonM11b-1 已批准产物,本任务禁止反向修改),建表 DDL 归该模块所有本文件不复制其物理实现细节。world_sync 仅作为单事务写入的消费方不拥有该表结构定义权。【为什么是内联而不是引用】database-table-definition-spec 只承认 summary/fields/indexes/codes 四段式scripts/validate_models_json.pyM11b-2a 子任务 A 修复版)对 key=schema 引用式写法直接判 FAILQC #5故本文件按真源逐字段内联权威列集真源实为 10 列tenant_id/id/session_id/entity_id/state_json/state_version/checksum/updated_by/created_at/updated_at任务书所述「8 列」系早期口径,以真源实测为准),作为 world_sync 侧的消费方契约视图;一致性由 scripts/m11b2b_column_diff.py 与 scripts/m11b2b_selftest.py 机械核对真源变更时必须同步本副本。【抽象类型归一说明】真源使用物理类型写法id=int(20) unsigned auto_increment、session_id/state_version=bigint、state_json=json本副本按 spec 归一为抽象类型long/text不保留 unsigned/length 物理修饰bigint→long、json→text存 JSON 文本,由应用层 json.dumps/loads。【id 类型偏离处理 · 本子任务的选择与理由】真源 id 为自增 BIGINT 主键(真源侧以 auto_increment/unsigned 私有键表达该偏离);子任务 A 的最终登记表 COMPLIANCE_DEVIATIONS 为空 {}A 按 QC #5 选 (a):不放行任何历史失效项),且本任务范围明确禁止修改校验器脚本,因此 world_sync 侧不走「登记放行 id 非 str(32)」路线,而是内联为 spec 合规写法 type=str / length=32、不带 auto_increment 键,主键值由应用层生成 32 位 uuid 去横线十六进制串world_sync/pbl_runtime_tx.py::new_event_uid 同族工具函数);与真源的 id 类型差异由 world_sync 侧消费方代码适配:写路径 INSERT 不显式提交 id见 pbl_runtime_tx.py 状态行 INSERT 列集 cols 不含 id生产 MySQL 交由真源 DDL 的 AUTO_INCREMENT 生成 BIGINT本地/测试库authority 层渲染的 sqlite DDL使用应用层生成的 str(32)。此差异为有意选择、非疏漏,评审请勿据此判为结构缺陷。【主键与唯一键说明】真源 summary[0] 未声明 primary其物理主键由 indexes 中 PRIMARY(id) 表达,故本副本 primary=[\"id\"],并把真源 uk_estenant_id+session_id+entity_id 唯一键,即消费方定位键 STATE_KEY_COLUMNS)内联为 idxtype=unique;实体状态定位键为 tenant_id + session_id + entity_id本表无 event_uid 概念。【消费方清单】modules/world_sync/world_sync/pbl_runtime_sql.pySTATE_TABLE/STATE_COLUMNS、modules/world_sync/world_sync/pbl_runtime_tx.py::write_event_with_state_fetch_state_version/_update_state/_insert_state、modules/world_sync/world_sync/pbl_runtime_authority.py列契约解析层上述代码引用的列名必须全部出现在本文件 fields[] 中。【打包镜像说明】apps/scense/pkgs/world_sync/models/pbl_entity_state.json 是本文件的打包镜像,只由 modules/world_sync/scripts/sync_models_mirror.py 生成,禁止手工双写;一致性由 modules/world_sync/scripts/m11b2b_selftest.py 的 sha256 断言守护。【私有键剥离说明】真源使用的 spec 未定义键 summary[0].module/engine/charset/tenant_scoped、fields[].comment/null/unsigned/auto_increment、indexes[].unique/fields在本副本中一律不保留或已归一为 spec 键nullable/idxtype/idxfields本文件根键仅 summary/fields/indexes/codescodes 段本表无枚举编码列故为空数组。"
"comment": "【本表定位 · world_sync 侧内联副本】实体状态表服务端权威state_version 单调递增,客户端禁写;由 world_sync 单事务写入路径 write_event_with_state 与事件行同事务更新,乐观锁冲突映射为 ConcurrentStateConflict。【结构权威源 · PM 裁决】PM 裁决(任务 u0EO4x3ajntR41acaHj5H 问答):选 B —— 以 pbl_runtime_ext 为唯一真源;结构权威定义位于 modules/pbl_runtime_ext/models/pbl_entity_state.jsonM11b-1 已批准产物,本任务禁止反向修改),建表 DDL 归该模块所有本文件不复制其物理实现细节。world_sync 仅作为单事务写入的消费方不拥有该表结构定义权。【为什么是内联而不是引用】database-table-definition-spec 只承认 summary/fields/indexes/codes 四段式scripts/validate_models_json.pyM11b-2a 子任务 A 修复版 rev4)对 key=schema 引用式写法直接判 FAILQC #5故本文件按真源逐字段内联权威列集真源实为 10 列tenant_id/id/session_id/entity_id/state_json/state_version/checksum/updated_by/created_at/updated_at任务书所述「8 列」系早期口径,以真源实测为准),作为 world_sync 侧的消费方契约视图;一致性由 scripts/m11b2b_column_diff.py 与 scripts/m11b2b_selftest.py 机械核对真源变更时必须同步本副本。【抽象类型归一说明】真源使用物理类型写法id=int(20) unsigned auto_increment、session_id/state_version=bigint、state_json=json本副本按 spec 归一为抽象类型long/text不保留 unsigned/length 物理修饰bigint→long、json→text存 JSON 文本,由应用层 json.dumps/loads。【id 类型偏离处理 · 本子任务的选择与理由】真源 id 为自增 BIGINT 主键(真源侧以 auto_increment/unsigned 私有键表达该偏离);子任务 A 的最终登记表 COMPLIANCE_DEVIATIONS 为空 {}A 按 QC #5 选 (a):不放行任何历史失效项),且本任务范围明确禁止修改校验器脚本,因此 world_sync 侧不走「登记放行 id 非 str(32)」路线,而是内联为 spec 合规写法 type=str / length=32、不带 auto_increment 键,主键值由应用层生成 32 位 uuid 去横线十六进制串world_sync/pbl_runtime_tx.py::new_event_uid 同族工具函数);与真源的 id 类型差异由 world_sync 侧消费方代码适配:写路径 INSERT 不显式提交 id见 pbl_runtime_tx.py 状态行 INSERT 列集 cols 不含 id生产 MySQL 交由真源 DDL 的 AUTO_INCREMENT 生成 BIGINT本地/测试库authority 层渲染的 sqlite DDL使用应用层生成的 str(32)。此差异为有意选择、非疏漏,评审请勿据此判为结构缺陷。【主键与唯一键说明 · QC #1 整改】主键由 summary[0].primary=[\"id\"] 单点声明indexes[] 中不再出现名为 PRIMARY 的条目 —— database-table-definition-spec 与 scripts/validate_models_json.py 的规则 index_name_primary 明确「主键不写进 indexesspec 用 summary[0].primary 声明)」,旧版本此处保留 {name:PRIMARY,idxtype:unique,idxfields:[id]} 是对真源 indexes 段(真源 summary 未声明 primary、物理主键由 indexes 中 PRIMARY 表达)的照抄,属结构冗余且使校验器 RESULT: FAIL现予删除。indexes[] 仅保留 uk_estenant_id+session_id+entity_id 唯一键,idxtype=unique即消费方定位键 STATE_KEY_COLUMNS实体状态定位键为 tenant_id + session_id + entity_id本表无 event_uid 概念。本地/测试库由 pbl_runtime_authority.ddl() 渲染时PRIMARY KEY (id) 由 primary() 单点生成,不依赖 indexes 段,故删除 PRIMARY 不损失任何约束。【消费方清单】modules/world_sync/world_sync/pbl_runtime_sql.pySTATE_TABLE/STATE_COLUMNS、modules/world_sync/world_sync/pbl_runtime_tx.py::write_event_with_state_fetch_state_version/_update_state/_insert_state、modules/world_sync/world_sync/pbl_runtime_authority.py列契约解析层上述代码引用的列名必须全部出现在本文件 fields[] 中。【打包镜像说明】apps/scense/pkgs/world_sync/models/pbl_entity_state.json 是本文件的打包镜像,只由 modules/world_sync/scripts/sync_models_mirror.py 生成,禁止手工双写;一致性由 modules/world_sync/scripts/m11b2b_selftest.py 的 A7 sha256 断言守护(改本文件后必须重跑 sync_models_mirror.py。【私有键剥离说明】真源使用的 spec 未定义键 summary[0].module/engine/charset/tenant_scoped、fields[].comment/null/unsigned/auto_increment、indexes[].unique/fields在本副本中一律不保留或已归一为 spec 键nullable/idxtype/idxfields本文件根键仅 summary/fields/indexes/codescodes 段本表无枚举编码列故为空数组。"
}
],
"fields": [
@ -21,7 +21,6 @@
{"name": "updated_at", "title": "更新时间", "type": "datetime", "nullable": "no"}
],
"indexes": [
{"name": "PRIMARY", "idxtype": "unique", "idxfields": ["id"]},
{"name": "uk_es", "idxtype": "unique", "idxfields": ["tenant_id", "session_id", "entity_id"]}
],
"codes": []

View File

@ -3,9 +3,9 @@
{
"name": "pbl_runtime_event",
"title": "运行时事件流(append-only)",
"primary": ["id", "created_at"],
"primary": ["id"],
"catelog": "entity",
"comment": "【本表定位】world_sync 侧消费方契约副本运行时事件流append-only禁 UPDATE/DELETE生产库由权威模块 pbl_runtime_ext 的存储过程 pbl_assert_append_only + BEFORE UPDATE/BEFORE DELETE 触发器强制,本副本只声明列契约、不复制那套物理实现)。【结构权威源 · PM 裁决】PM 裁决(任务 u0EO4x3ajntR41acaHj5H选 B以 modules/pbl_runtime_ext/models/pbl_runtime_event.json 为唯一真源M11b-1 已批准产物,本任务禁止反向修改);建表 DDL 派生源为该模块 scripts/pbl_runtime_event_ddl.py 与 scripts/migrations/*.sql。本文件是按真源逐字段内联的 world_sync 侧副本25 列全量抄录),不是引用式 schema —— database-table-definition-spec 只承认 summary/fields/indexes/codes 四段式scripts/validate_models_json.py 对 key=schema 引用式写法直接判 FAILQC #5故 QC 要求内联。两份定义的一致性由 scripts/m11b2b_column_diff.py代码列名 vs 权威列集双向 diff与 scripts/m11b2b_selftest.py四段式/抽象类型/主键/索引/编码 6 项断言 + 真源镜像 sha256机械核对真源列集变更时必须同步本副本。【id 生成方 · 单一口径QC #4】本副本声明 id 由应用层生成world_sync/pbl_runtime_tx.py::new_event_uid()uuid4().hex32 位小写十六进制),写路径 INSERT 列集(= 本文件 fields[] 全列,见 pbl_runtime_sql.py::EVENT_COLUMNS 由 fields[] 机械导出)显式提交 id不依赖任何数据库 AUTO_INCREMENT。真源侧 id 为 type=long + auto_incrementBIGINT AUTO_INCREMENT属其私有物理实现本副本按 spec 归一为 str(32) 且不保留 auto_increment 键;两者差异已在交付件正文登记为 DEVIATION(WS-2) 并冒泡 agent.pm 裁决生产库列型收敛方案(改 VARCHAR(32) 或统一走 event_uid 定位),本副本不再同时声称两种生成方。对外幂等与跨系统追溯一律使用 event_uidstr 64uk_event_uid不依赖 id 的物理类型。【复合主键偏离 · 已登记QC #4】summary[0].primary=[\"id\",\"created_at\"] 是对 spec「单键表恒为 [\\\"id\\\"]」的有意偏离:本表按 created_at 做按月 RANGE COLUMNS 分区MySQL/MariaDB 要求分区表所有唯一键(含主键)必须包含分区列,否则建表报错 1503。真源依据行号modules/pbl_runtime_ext/models/pbl_runtime_event.json:12primary=[\"id\",\"created_at\"])、同文件:6comment 中 1503 约束说明与分区保留 14 个月)、同文件:40fields[0].id type=long + auto_increment。本副本不写 partition/guards/engine 等私有键,但主键必须与真源一致,否则 uk_event_uid/uk_tenant_idem含 created_at无法在本副本声明的 PK 下成立。该偏离已在 M11b-2b-B 交付件正文登记DEVIATION(WS-1)),未写进校验器 COMPLIANCE_DEVIATIONS子任务 A 最终登记表为空 {},且本任务禁止修改校验器)。【私有键剥离】真源使用的 spec 未定义顶层扩展键 module/engine/charset/collate/tenant_scoped/append_only/write_paths/guards/partition/ddl_source以及 fields[].ddl_type/comment/auto_increment、indexes[].comment、codes[].comment本副本一律不保留根键仅 summary/fields/indexes/codessummary[0] 仅 name/title/primary/catelog/commentfields[] 仅 name/title/type/length/dec/nullable/defaultindexes[] 仅 name/idxtype/idxfields主键不写进 indexes由 summary[0].primary 表达codes[] 仅 field/table/valuefield/textfield/cond。物理细节分区、触发器、字符集、存储过程以真源与其 DDL 派生源为准。【消费方清单】modules/world_sync/world_sync/pbl_runtime_sql.pyEVENT_TABLE/EVENT_COLUMNS由本文件 fields[] 机械导出、modules/world_sync/world_sync/pbl_runtime_tx.py::write_event_with_state单事务事件+状态写入、modules/world_sync/world_sync/pbl_runtime_authority.py列契约解析层已适配本内联四段式副本、modules/world_sync/world_sync/init.py事件写入门面。上述代码引用的列名必须全部出现在本文件 fields[] 中,核对证据见 M11b-2b-B 交付件「字段交叉核对清单」。【打包镜像】apps/scense/pkgs/world_sync/models/pbl_runtime_event.json 是本文件的打包镜像,只由 modules/world_sync/scripts/sync_models_mirror.py 生成,禁止手工双写;一致性由 scripts/m11b2b_selftest.py 的 sha256 断言守护。"
"comment": "【本表定位】world_sync 侧消费方契约副本运行时事件流append-only禁 UPDATE/DELETE生产库由权威模块 pbl_runtime_ext 的存储过程 pbl_assert_append_only + BEFORE UPDATE/BEFORE DELETE 触发器强制,本副本只声明列契约、不复制那套物理实现)。【结构权威源 · PM 裁决】PM 裁决(任务 u0EO4x3ajntR41acaHj5H选 B以 modules/pbl_runtime_ext/models/pbl_runtime_event.json 为唯一真源M11b-1 已批准产物,本任务禁止反向修改);建表 DDL 派生源为该模块 scripts/pbl_runtime_event_ddl.py 与 scripts/migrations/*.sql。本文件是按真源逐字段内联的 world_sync 侧副本25 列全量抄录),不是引用式 schema —— database-table-definition-spec 只承认 summary/fields/indexes/codes 四段式scripts/validate_models_json.py 对 key=schema 引用式写法直接判 FAILQC #5故 QC 要求内联。两份定义的一致性由 scripts/m11b2b_column_diff.py代码列名 vs 权威列集双向 diff与 scripts/m11b2b_selftest.py四段式/抽象类型/主键/索引/编码 6 项断言 + 真源镜像 sha256机械核对真源列集变更时必须同步本副本。【id 生成方 · 单一口径QC #4】本副本声明 id 由应用层生成world_sync/pbl_runtime_tx.py::new_event_uid()uuid4().hex32 位小写十六进制),写路径 INSERT 列集(= 本文件 fields[] 全列,见 pbl_runtime_sql.py::EVENT_COLUMNS 由 fields[] 机械导出)显式提交 id不依赖任何数据库 AUTO_INCREMENT。真源侧 id 为 type=long + auto_incrementBIGINT AUTO_INCREMENT属其私有物理实现本副本按 spec 归一为 str(32) 且不保留 auto_increment 键;两者差异已在 M11b-2b-B 交付件正文登记为 DEVIATION(WS-2) 并冒泡 agent.pm 裁决生产库列型收敛方案(改 VARCHAR(32) 或统一走 event_uid 定位),本副本不再同时声称两种生成方。对外幂等与跨系统追溯一律使用 event_uidstr 64uk_event_uid不依赖 id 的物理类型。【主键口径 · QC #2 归一本副本无偏离】summary[0].primary=[\"id\"],严格遵循 spec「单键表 primary 恒为 [\\\"id\\\"]」,本副本不再声明复合主键、也不再自称任何偏离(旧版 primary=[\\\"id\\\",\\\"created_at\\\"] 属未登记偏离QC #2 判定口径未闭环,已按改法 (a) 归一)。真源侧因按 created_at 做按月 RANGE COLUMNS 分区、受 MySQL/MariaDB 错误码 1503分区表的每个唯一键必须包含分区列约束而使用复合物理主键 (id, created_at)——该约束属物理实现,由真源 modules/pbl_runtime_ext/models/pbl_runtime_event.json:12primary与其 DDL 派生源scripts/pbl_runtime_event_ddl.py、scripts/migrations/*.sql表达本副本不写 partition/engine/guards 等私有键,故不在逻辑契约里复制该复合主键。分区列约束在本副本中的等价表达是:两条唯一键 uk_event_uid(tenant_id,event_uid,created_at)、uk_tenant_idem(tenant_id,idem_key,created_at) 以及普通索引 ix_created_at(created_at) 均已包含 created_at消费方按唯一键定位/幂等去重时天然满足「查询落在分区列上」的要求,不依赖主键形态。【私有键剥离】真源使用的 spec 未定义顶层扩展键 module/engine/charset/collate/tenant_scoped/append_only/write_paths/guards/partition/ddl_source以及 fields[].ddl_type/comment/auto_increment、indexes[].comment、codes[].comment本副本一律不保留根键仅 summary/fields/indexes/codessummary[0] 仅 name/title/primary/catelog/commentfields[] 仅 name/title/type/length/dec/nullable/defaultindexes[] 仅 name/idxtype/idxfields主键不写进 indexes由 summary[0].primary 表达codes[] 仅 field/table/valuefield/textfield/cond。物理细节分区、触发器、字符集、存储过程以真源与其 DDL 派生源为准。【消费方清单】modules/world_sync/world_sync/pbl_runtime_sql.pyEVENT_TABLE/EVENT_COLUMNS由本文件 fields[] 机械导出、modules/world_sync/world_sync/pbl_runtime_tx.py::write_event_with_state单事务事件+状态写入、modules/world_sync/world_sync/pbl_runtime_authority.py列契约解析层 + 本地/测试 DDL 渲染已适配本内联四段式副本、modules/world_sync/world_sync/init.py事件写入门面。上述代码引用的列名必须全部出现在本文件 fields[] 中,核对证据见 M11b-2b-B 交付件「字段交叉核对清单」与 scripts/m11b2b_column_diff_output.txt。【打包镜像】apps/scense/pkgs/world_sync/models/pbl_runtime_event.json 是本文件的打包镜像,只由 modules/world_sync/scripts/sync_models_mirror.py 生成,禁止手工双写;一致性由 scripts/m11b2b_selftest.py 的 A7 sha256 断言守护(改本文件后必须重跑 sync_models_mirror.py。"
}
],
"fields": [

View File

@ -287,12 +287,17 @@ def ddl(table, dialect="sqlite"):
gen = set(auto_increment_columns(table))
lines = []
for f in model["fields"]:
col = ("`%s`" % f["name"]) if dialect == "mysql" else f["name"]
fname = f.get("name")
col = ("`%s`" % fname) if dialect == "mysql" else fname
piece = [" %s %s" % (col, _render_type(f, dialect))]
if not _nullable(f) and not gen:
# QC #5 修正:自增豁免必须**按列**判断(旧写法 `not gen` 是表级判断,
# 只要表里存在任一自增列,本表所有列的 NOT NULL 都会被整体跳过,
# 导致渲染出的本地/测试 DDL 丢失 NOT NULL 约束)。
is_gen = fname in gen
if not _nullable(f) and not is_gen:
piece.append(" NOT NULL")
default = f.get("default")
if default is not None and not gen and str(default) != "":
if default is not None and not is_gen and str(default) != "":
piece.append(" DEFAULT %s" % ("CURRENT_TIMESTAMP"
if str(default).upper() == "CURRENT_TIMESTAMP"
else "'%s'" % default))