From 3f0ee54c1c4b1f59241c0338ddcbd66e8f5cd807 Mon Sep 17 00:00:00 2001 From: "agent.develop" Date: Sun, 20 Sep 2026 18:20:03 +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 ++++++++++--- world_sync/__init__.py | 172 +++++++++++++++++++++++--------- world_sync/init.py | 118 ++++++++++++++++++++-- 3 files changed, 292 insertions(+), 67 deletions(-) diff --git a/scripts/validate_models_json.py b/scripts/validate_models_json.py index 423ea66..1895f3d 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 修复版)。 +"""validate_models_json —— database-table-definition-spec 机械校验器(M11b-2b-A 修复版 rev3)。 QC #4/#7/#9/#10 要求:表定义 JSON 必须是 spec 规定的四段式(summary/fields/indexes/ codes),类型必须是 spec 声明的抽象类型,主键 id 必须是 str/length>=32,索引必须用 @@ -17,12 +17,25 @@ codes),类型必须是 spec 声明的抽象类型,主键 id 必须是 str/ QC #5 引用式 key/schema:spec 不承认引用式结构,直接判 ERROR 并提示改为内联四段式; COMPLIANCE_DEVIATIONS 中针对 pbl_runtime_event/pbl_entity_state 的 4 条失效登记项 一并删除(引用式在根键检查处即 FAIL,根本到不了 fields 校验,登记形同虚设)。 + QC #1(本轮)放行机制对 auto_increment 生效:字段段「spec 未定义键」检查原先无条件报错, + 导致 (表名,"id","auto-increment") 即使已登记、即使传了 --allow-registered-deviation, + 仍残留 `fields[N](id) 含 spec 未定义键: auto_increment` 而整体 FAIL —— id-not-str32 + 可豁免、auto_increment 不可豁免,机制自相矛盾。现在该检查改为**偏离感知**: + id 上的 auto_increment 键统一交由 id 分支按 strict/allow 两态处理(已登记 + 传开关 + → 降级为 DEVIATION(registered) 明细,不计入 errors;未登记或未传开关 → 照旧 ERROR); + 其它字段上的未定义键也支持通用登记键 (表名, 列名, "unknown-key:<键名>"),同样两态。 -已登记偏离(唯一允许的偏离,需 --allow-registered-deviation 显式声明才放行): - 主键 id 非 str(32) 或 id 带 auto_increment,且已登记在 COMPLIANCE_DEVIATIONS 中 - (典型场景:分区表物理主键需要 long + auto_increment)。未传该开关时这类项仍判 ERROR, - 并打印「需 --allow-registered-deviation 放行」提示;传入时逐条打印 DEVIATION(registered) - 明细,不静默放过。当前登记表为空(引用式失效项已删除),后续若真源确需偏离再登记。 +偏离放行语义(唯一允许的偏离,需 --allow-registered-deviation 显式声明才放行): + 1) (表名, "id", "id-not-str32") —— 主键 id 非 str 或 length<32; + 2) (表名, "id", "auto-increment") —— id 带 spec 未定义键 auto_increment; + 3) (表名, 列名, "unknown-key:") —— 任意字段带 spec 未定义键 (通用形式)。 + 未传该开关时上述项仍判 ERROR,并打印「需 --allow-registered-deviation 放行」提示;传入时 + 逐条打印 DEVIATION(registered) 明细(含登记说明),不静默放过。 + 注意:只有**登记过**的才可能放行;登记表为空时 --allow-registered-deviation 不放行任何项。 + 典型场景:分区表物理主键需要 long + auto_increment(MySQL 1503:分区键必须进主键)。 + 若某表确需此类写法,须先在其模块任务里把 (表名,"id","id-not-str32") 与 + (表名,"id","auto-increment") 登记进 COMPLIANCE_DEVIATIONS,再配合开关使用 —— 两者缺一不可, + 只登记不传开关仍 FAIL,只传开关不登记也 FAIL(本文件末尾自测 5 给出四态实测输出)。 用法: python validate_models_json.py [<目录2> ...] @@ -81,6 +94,20 @@ def _is_reference_style(data): return False +def _unknown_key_deviation_key(table, field_name, key_name): + """字段段 spec 未定义键对应的登记键(QC #1)。 + + id 上的 auto_increment 复用 (表名,"id","auto-increment"),与 id 分支同一登记项, + 避免「登记了 auto-increment 却因 unknown-key 分支无条件报错」的放行失效缺陷; + 其余未定义键使用通用形式 (表名, 列名, "unknown-key:<键名>")。 + """ + if table is None: + return None + if field_name == "id" and key_name == "auto_increment": + return (table, "id", "auto-increment") + return (table, field_name, "unknown-key:%s" % key_name) + + def check_type_value(type_value): """返回该 type 写法的问题列表(spec 只允许抽象类型)。 @@ -181,9 +208,26 @@ def validate_model(path, strict_id=True): names.append(name) if not f.get("title"): errors.append("%s.title 必填(DDL COMMENT 用)" % where) - unknown = [k for k in f if k not in FIELD_KEYS] + # QC #1:spec 未定义键检查改为偏离感知 —— 已登记且传入 --allow-registered-deviation + # 的键不再计入 errors(id.auto_increment 交由下方 id 分支统一打印 DEVIATION 明细, + # 避免同一偏离重复计数;其它键在此直接降级为 DEVIATION(registered))。 + unknown = [] + for k in sorted([k for k in f if k not in FIELD_KEYS]): + dkey = _unknown_key_deviation_key(table, name, k) + if k == "auto_increment" and name == "id" and dkey in COMPLIANCE_DEVIATIONS: + continue # strict/allow 两态由 id 分支处理(同一登记项,只报一次) + if dkey is not None and dkey in COMPLIANCE_DEVIATIONS: + if strict_id: + # 已登记但未传开关:照旧 ERROR,并明确提示如何放行(与 id 分支同一语义) + errors.append("%s 含 spec 未定义键: %s —— 该项已登记偏离,%s" + % (where, k, DEVIATION_HINT)) + continue + deviations.append("%s: 含 spec 未定义键 %s —— 已登记偏离:%s" + % (where, k, COMPLIANCE_DEVIATIONS[dkey])) + continue + unknown.append(k) if unknown: - errors.append("%s 含 spec 未定义键: %s" % (where, ", ".join(sorted(unknown)))) + errors.append("%s 含 spec 未定义键: %s" % (where, ", ".join(unknown))) t = f.get("type") errors.extend("%s: %s" % (where, m) for m in check_type_value(t)) req = ABSTRACT_TYPES.get(str(t).strip().lower(), ()) @@ -208,14 +252,15 @@ def validate_model(path, strict_id=True): else: errors.append("%s: 主键 id 必须 str 且 length>=32(spec 规则),实测 type=%r length=%r" % (where, t, f.get("length"))) - if f.get("auto_increment"): + if "auto_increment" in f: key = (table, "id", "auto-increment") if key in COMPLIANCE_DEVIATIONS: if strict_id: - errors.append("%s: id auto_increment —— 该项已登记偏离,%s" - % (where, DEVIATION_HINT)) + errors.append("%s: id 带 spec 未定义键 auto_increment(实测 %r)" + " —— 该项已登记偏离,%s" + % (where, f.get("auto_increment"), DEVIATION_HINT)) else: - deviations.append("%s: id auto_increment —— 已登记偏离:%s" + deviations.append("%s: id 带 spec 未定义键 auto_increment —— 已登记偏离:%s" % (where, COMPLIANCE_DEVIATIONS[key])) else: errors.append("%s: auto_increment 未在偏离登记表中(spec 无此键)" % where) diff --git a/world_sync/__init__.py b/world_sync/__init__.py index b8df98a..70b064e 100644 --- a/world_sync/__init__.py +++ b/world_sync/__init__.py @@ -1,15 +1,27 @@ # -*- coding: utf-8 -*- -"""world_sync —— 世界运行时同步模块(PBL scense 侧)。 +"""world_sync —— 世界运行时同步模块(PBL scense 侧)包入口。 -M11b-2 增量:单事务内「事件 + 实体状态」原子落库。 -对外只暴露写入/读取两个函数与异常族;**不含广播、不含轮询兜底、不含时延优化** +**单一真源原则(响应 QC #2 / QC #3)** +===================================== +本文件只做「转发 + 导出」,**不再自带第二份 ``load_world_sync`` 实现**。 +唯一实现位于 :mod:`world_sync.init`(宿主挂载层),它同时完成: + +- ① 实现:``init.py`` 内的 ``load_world_sync(server_env=None, conn_factory=None)``; +- ② 导出:本文件 ``from .init import load_world_sync, ...``(宿主 ``from world_sync import load_world_sync`` + 取到的就是 ①,不存在第二份); +- ③ env 注册:``init.py`` 内 ``env.xxx = xxx`` 的三处同步登记。 + +M11b-2 能力:单事务内「事件 + 实体状态」原子落库。 +对外只暴露写入/读取两个契约函数与异常族;**不含广播、不含轮询兜底、不含时延优化** (这些明确在 M11b-2 范围之外,由后续里程碑承担)。 宿主接线(遵循 project-directory-spec 8.1:模块不读部署环境凭据文件):: # apps/scense/app/scense.py 的 init() 里 from world_sync import load_world_sync - load_world_sync(server_env) # 登记 pbl_runtime_conn_factory 等 + load_world_sync() # 仅登记 CRUD + 表注册 + 契约 + # 或宿主显式注入连接工厂(推荐,模块内部因此完全不需要知道凭据来源): + load_world_sync(server_env=env, conn_factory=my_factory) # 业务侧写入 from world_sync import write_event_with_state @@ -21,64 +33,152 @@ M11b-2 增量:单事务内「事件 + 实体状态」原子落库。 ``conn_factory`` 解析优先级(见 :mod:`world_sync.pbl_runtime_tx_env`): 显式注入 → ServerEnv 登记 → 环境变量 ``PBL_RUNTIME_DB_URL`` → 模块自有 ``conf/db.json``; -四级全空则 fail-closed 抛 :class:`ConnFactoryNotConfigured`。 +四级全空则 fail-closed 抛 :class:`ConnFactoryNotConfigured`(别名 :class:`ConnectionUnavailable`)。 """ +# --- ② 导出:实现层(init.py)为唯一真源,本文件不重复定义任何函数 --- +from .init import ( + # 挂载入口(唯一实现) + load_world_sync, + # 既有 world_sync CRUD + list_world_syncs, + get_world_sync, + create_world_sync, + update_world_sync, + delete_world_sync, + execute_world_sync, + # M11b-1 表注册(只读元数据) + MODULE_TABLES, + module_tables, + get_table_schema, + load_table_model, + pbl_runtime_event_schema, + pbl_runtime_event_partitions, + # M11b-2 运行时写入契约(配置 / 预演 / 诊断) + configure_runtime_writer, + get_runtime_writer, + build_runtime_write_plan, + runtime_write_percentile, + runtime_write_dialect_name, + runtime_write_ddl, + # 模块元信息 + MODULE_NAME, + MODULE_VERSION, + SERVER_ENV_CONN_FACTORY_KEYS, + # 宿主基础包可用性(自检用,不建连) + HOST_DEPS_AVAILABLE, + HOST_DEPS_ERROR, + HostDepsNotAvailable, +) + +# --- M11b-2 契约函数与异常族(真源:pbl_runtime_tx* 系列,init.py 亦登记同名) --- from .pbl_runtime_errors import ( ConcurrentStateConflict, ConnFactoryNotConfigured, + ConnectionUnavailable, EventWriteError, + InvalidWriteRequest, PblRuntimeError, + RuntimeWriteError, StateWriteError, + TransactionAborted, TxAbortError, TxConfigError, ) from .pbl_runtime_sql import ( + DIALECT_MYSQL, + DIALECT_SQLITE, EVENT_TABLE, STATE_TABLE, + ddl_for, detect_dialect, percentile, ) from .pbl_runtime_tx import ( + DEFAULT_EVENT_TYPE, + DEFAULT_SOURCE, + EVENT_REQUIRED, + STATE_KEY_COLUMNS, new_event_id, - read_entity_state as _tx_read_entity_state, + new_event_uid, utc_now_text, - write_event_with_state as _tx_write_event_with_state, ) from .pbl_runtime_tx_env import ( + SERVER_ENV_KEYS, + build_mysql_conn_factory, build_sqlite_conn_factory, + load_db_config, + module_conf_dir, resolve_conn_factory, ) from .pbl_runtime_tx_sqlor import ( + ENV_NAME_KEYS, + POOL_KEYS, + build_conn_factory_from_pools, + host_conn_factory, read_entity_state, write_event_with_state, ) -MODULE_NAME = "world_sync" -MODULE_VERSION = "0.2.0-m11b2" - -#: 宿主 ServerEnv 上登记的 conn_factory 键(pbl_runtime_tx_env.SERVER_ENV_KEYS 与此一致) -SERVER_ENV_CONN_FACTORY_KEYS = ("pbl_runtime_conn_factory", "world_sync_conn_factory") +#: 契约解析优先级键(与 pbl_runtime_tx_env.SERVER_ENV_KEYS 一致,单一来源) +assert tuple(SERVER_ENV_CONN_FACTORY_KEYS) == tuple(SERVER_ENV_KEYS), \ + "world_sync: SERVER_ENV_CONN_FACTORY_KEYS 与 pbl_runtime_tx_env.SERVER_ENV_KEYS 分叉" __all__ = [ - # 写入 / 读取(M11b-2 核心) - "write_event_with_state", - "read_entity_state", - # 接线 + # 挂载入口(唯一实现位于 init.py) "load_world_sync", "MODULE_NAME", "MODULE_VERSION", + "MODULE_TABLES", "SERVER_ENV_CONN_FACTORY_KEYS", + "SERVER_ENV_KEYS", + # 既有 CRUD + "list_world_syncs", + "get_world_sync", + "create_world_sync", + "update_world_sync", + "delete_world_sync", + "execute_world_sync", + # M11b-1 表注册(只读) + "module_tables", + "get_table_schema", + "load_table_model", + "pbl_runtime_event_schema", + "pbl_runtime_event_partitions", + # M11b-2 写入 / 读取契约(核心) + "write_event_with_state", + "read_entity_state", + "configure_runtime_writer", + "get_runtime_writer", + "build_runtime_write_plan", + "runtime_write_percentile", + "runtime_write_dialect_name", + "runtime_write_ddl", # 工厂与方言 "resolve_conn_factory", "build_sqlite_conn_factory", + "build_mysql_conn_factory", + "build_conn_factory_from_pools", + "host_conn_factory", + "load_db_config", + "module_conf_dir", "detect_dialect", + "ddl_for", "percentile", "EVENT_TABLE", "STATE_TABLE", + "DIALECT_MYSQL", + "DIALECT_SQLITE", + "DEFAULT_EVENT_TYPE", + "DEFAULT_SOURCE", + "EVENT_REQUIRED", + "STATE_KEY_COLUMNS", + "POOL_KEYS", + "ENV_NAME_KEYS", "new_event_id", + "new_event_uid", "utc_now_text", - # 异常族 + # 异常族(含 M11b-2a 语义别名,同一对象) "PblRuntimeError", "TxConfigError", "ConnFactoryNotConfigured", @@ -86,34 +186,12 @@ __all__ = [ "StateWriteError", "ConcurrentStateConflict", "TxAbortError", + "RuntimeWriteError", + "InvalidWriteRequest", + "ConnectionUnavailable", + "TransactionAborted", + # 宿主基础包可用性诊断 + "HOST_DEPS_AVAILABLE", + "HOST_DEPS_ERROR", + "HostDepsNotAvailable", ] - - -def load_world_sync(server_env=None, conn_factory=None): - """挂载 world_sync:向 ServerEnv 登记本模块能力与(可选)conn_factory。 - - 幂等:重复调用只覆盖同名键,不产生副作用。返回本模块 ``__all__`` 便于宿主自检。 - - :param server_env: 宿主 ServerEnv 实例(None 时尝试取全局单例) - :param conn_factory: 宿主连接池工厂;提供则登记到 ``pbl_runtime_conn_factory``, - 使模块内部无需知道任何部署环境凭据来源(fail-closed 而非自行找配置文件)。 - """ - if server_env is None: - try: - from appPublic.serverEnv import ServerEnv - server_env = ServerEnv() - except Exception: - server_env = None - - if server_env is not None: - if conn_factory is not None: - setattr(server_env, SERVER_ENV_CONN_FACTORY_KEYS[0], conn_factory) - setattr(server_env, "world_sync_module", MODULE_NAME) - setattr(server_env, "world_sync_version", MODULE_VERSION) - setter = getattr(server_env, "set", None) - if callable(setter): - try: - setter("world_sync_version", MODULE_VERSION) - except Exception: - pass - return list(__all__) diff --git a/world_sync/init.py b/world_sync/init.py index fbe424c..e9a62b1 100644 --- a/world_sync/init.py +++ b/world_sync/init.py @@ -23,10 +23,79 @@ M11b-2a 迁移修正(响应 QC #1):本文件此前导入的 import json import os -from appPublic.uniqueID import getID -from appPublic.timeUtils import curDateString, timestampstr -from sqlor.dbpools import DBPools -from ahserver.serverenv import ServerEnv +# 宿主基础包(appPublic / sqlor / ahserver):M11b-2a 起改为「容错导入 + 显式记录」—— +# 目的是让「三处接线」自检(import 本包 + 调 load_world_sync())不依赖宿主部署环境, +# 而不是吞错误:基础包缺失时记入 HOST_DEPS_ERROR,任何真正访问数据库的函数在调用点 +# 通过 _require_host_deps() fail-fast 抛出带原因的 RuntimeError(不静默返回 None)。 +HOST_DEPS_ERROR = None +try: + from appPublic.uniqueID import getID + from appPublic.timeUtils import curDateString, timestampstr + from sqlor.dbpools import DBPools + from ahserver.serverenv import ServerEnv + HOST_DEPS_AVAILABLE = True +except ImportError as _exc: # pragma: no cover - 仅在无宿主基础包的开发/自检环境触发 + HOST_DEPS_AVAILABLE = False + HOST_DEPS_ERROR = _exc + getID = curDateString = timestampstr = None + DBPools = ServerEnv = None + + +class HostDepsNotAvailable(RuntimeError): + """宿主基础包(appPublic/sqlor/ahserver)不可用时,访问数据库的入口 fail-fast。""" + + def __init__(self, op): + super().__init__( + '%s requires host foundation packages (appPublic/sqlor/ahserver); ' + 'import error was: %r' % (op, HOST_DEPS_ERROR)) + self.op = op + + +def _require_host_deps(op): + if not HOST_DEPS_AVAILABLE: + raise HostDepsNotAvailable(op) + return True + + +class _StandaloneEnv(object): + """无宿主 ServerEnv 时的登记载体(仅供接线自检,不建连、不接库)。""" + + def __init__(self): + self._kv = {} + + def __setattr__(self, k, v): + object.__setattr__(self, k, v) + + def set(self, k, v): + self._kv[k] = v + + def get(self, k, default=None): + return getattr(self, k, self._kv.get(k, default)) + + def get_module_dbname(self, m): + raise HostDepsNotAvailable('get_module_dbname(%s)' % m) + + +def _resolve_env(server_env=None): + """解析登记目标 ServerEnv:显式注入优先,其次宿主全局单例,最后 standalone 载体。""" + if server_env is not None: + return server_env + if ServerEnv is not None: + try: + return ServerEnv() + except Exception as exc: # 宿主单例不可用:记录后降级,不中断接线登记 + standalone_env_error(str(exc)) + return _StandaloneEnv() + + +def standalone_env_error(message): + """记录 ServerEnv 降级原因(幂等,不抛错)。""" + global STANDALONE_ENV_NOTICES + STANDALONE_ENV_NOTICES = list(STANDALONE_ENV_NOTICES) + [message] + return message + + +STANDALONE_ENV_NOTICES = [] # --- M11b-2: 单事务写入(事件 + 实体状态)注册接线(真源实际存在的名字) --- from .pbl_runtime_errors import ( @@ -38,6 +107,7 @@ from .pbl_runtime_errors import ( PblRuntimeError, RuntimeWriteError, StateWriteError, + TransactionAborted, TxAbortError, TxConfigError, ) @@ -79,7 +149,14 @@ from .pbl_runtime_tx_sqlor import ( ) +MODULE_NAME = 'world_sync' +MODULE_VERSION = '0.2.0-m11b2a' +#: 宿主 ServerEnv 上登记 conn_factory 的键(与 pbl_runtime_tx_env.SERVER_ENV_KEYS 一致) +SERVER_ENV_CONN_FACTORY_KEYS = SERVER_ENV_KEYS + + def _get_dbname(): + _require_host_deps('_get_dbname') return ServerEnv().get_module_dbname('world_sync') @@ -91,12 +168,14 @@ def _clean(ns): async def list_world_syncs(params=None): + _require_host_deps('list_world_syncs') db = DBPools() async with db.sqlorContext(_get_dbname()) as sor: return await sor.sqlExe("select id, world_id, sync_type, source, status, result_json, sync_date, created_at from world_sync order by created_at desc", {}) async def get_world_sync(ns): + _require_host_deps('get_world_sync') rec_id = (ns or {}).get('id') if not rec_id: return None @@ -107,6 +186,7 @@ async def get_world_sync(ns): async def create_world_sync(ns): + _require_host_deps('create_world_sync') ns = _clean(dict(ns)) ns['id'] = ns.get('id') or getID() if not ns.get('sync_date'): @@ -121,6 +201,7 @@ async def create_world_sync(ns): async def update_world_sync(ns): + _require_host_deps('update_world_sync') ns = _clean(dict(ns)) rec_id = ns.get('id') if not rec_id: @@ -132,6 +213,7 @@ async def update_world_sync(ns): async def delete_world_sync(ns): + _require_host_deps('delete_world_sync') rec_id = (ns or {}).get('id') if not rec_id: return {'success': False, 'message': '缺少 id'} @@ -142,6 +224,7 @@ async def delete_world_sync(ns): async def execute_world_sync(ns): + _require_host_deps('execute_world_sync') ns = ns or {} world_id = ns.get('world_id', '') sync_type = ns.get('sync_type', '0') @@ -226,6 +309,7 @@ async def pbl_runtime_event_partitions(params=None): " where table_schema=${dbname}$ and table_name=${tbl}$ " " and partition_name is not null " " order by partition_ordinal_position") + _require_host_deps('pbl_runtime_event_partitions') db = DBPools() async with db.sqlorContext(_get_dbname()) as sor: return await sor.sqlExe(sql, {'dbname': _get_dbname(), 'tbl': PBL_RUNTIME_EVENT_TABLE}) @@ -331,12 +415,28 @@ def runtime_write_ddl(ns): return {'success': False, 'message': str(exc)} -def load_world_sync(): - """挂载 world_sync:CRUD + M11b-1 表注册 + M11b-2 单事务写入契约。 +def load_world_sync(server_env=None, conn_factory=None): + """挂载 world_sync:CRUD + M11b-1 表注册 + M11b-2 单事务写入契约(唯一真源)。 - 三处接线之「③ init.py env.xxx = xxx」;导入失败一律原样抛出(不静默降级)。 + 三处接线之「③ init.py env.xxx = xxx」;本函数同时是「② __init__.py 导出」与 + 根包 ``world_sync/__init__.py`` 转发的**唯一** load_world_sync 实现(QC #2: + 不得存在第二份定义)。模块级符号(含 ``TransactionAborted``)导入失败一律原样 + 抛出(不静默降级)。 + + :param server_env: 宿主 ServerEnv 实例;None 时取全局单例,单例不可用时用 + :class:`_StandaloneEnv` 载体完成登记(仅接线自检,不建连)。 + :param conn_factory: 宿主连接池工厂(可调用/DB URL);提供则登记到 + ``pbl_runtime_conn_factory``(契约解析优先级第 2 级)。 + :return: 登记完成的 env。 """ - env = ServerEnv() + env = _resolve_env(server_env) + if conn_factory is not None: + setattr(env, SERVER_ENV_KEYS[0], conn_factory) + env.world_sync_module = MODULE_NAME + env.world_sync_version = MODULE_VERSION + env.world_sync_host_deps_available = HOST_DEPS_AVAILABLE + if not HOST_DEPS_AVAILABLE: + env.world_sync_host_deps_error = repr(HOST_DEPS_ERROR) env.list_world_syncs = list_world_syncs env.get_world_sync = get_world_sync env.create_world_sync = create_world_sync @@ -371,6 +471,8 @@ def load_world_sync(): env.StateWriteError = StateWriteError env.ConcurrentStateConflict = ConcurrentStateConflict env.TransactionAborted = TransactionAborted + env.RuntimeTxAbortError = TxAbortError + env.new_runtime_event_id = new_event_id # 表名/方言常量与 conn_factory 解析优先级说明 env.pbl_runtime_event_table = EVENT_TABLE env.pbl_runtime_state_table = STATE_TABLE