Decision Log
本文集中记录已经收敛过的设计细节和仍需拍板的问题。关闭某个决策时,必须同步更新对应 authority document。
权威文档优先级:
Closed Decisions
OD-001 CLI Contract
状态:closed。权威文档见 启动流程运行时语义。
决策:
--config <path>可缺省,默认config/app.yaml。--config可重复传入;多个文件按传入顺序合并,合并规则以 配置运行时语义 为准。--node-id属于shield_clusteroptional module。未启用 cluster 时传入必须返回 CLI 错误。- 旧 subcommand 只作为 legacy CLI 存在,不进入新的
shield::run公共契约。 shield可执行文件和示例可执行文件都应直接调用shield::run(argc, argv),因此使用同一套参数集合。
OD-002 Process Lifecycle
状态:closed。权威文档见 启动流程运行时语义。
决策:
shield::run负责通用 signal handling、runtime loop、help/version 输出和退出码。- Windows 使用
SetConsoleCtrlHandler,POSIX 使用 SIGINT/SIGTERM。 shield::run初始化 runtime 后阻塞等待停止信号;底层 bootstrap API 只负责 initialize/shutdown。- 用户自定义 main 可以绕过
shield::run,直接调用 lower-level bootstrap API。 - 退出码以 启动流程运行时语义 为准。
OD-003 Physical Directory Layout
状态:closed。权威文档见 目录结构设计 和 重构路线图。
决策:
- 短期保持当前
src/core、src/net、src/lua等物理目录,先用 CMake target 和 include 边界收敛依赖。 - 不在 Phase 1 同时做大规模文件搬迁和语义重构。
- optional module 的目标物理目录保留为
src/optional/shield_cluster等形态,但只在对应模块实现时创建或迁移。 - public include 路径保持
include/shield/...,不随 target 名做破坏性改名。
OD-004 Phase 1 Network Scope
状态:closed。权威文档见 网络运行时语义。
决策:
- Phase 1 只冻结 TCP session 与 basic transport framing。
- UDP、KCP、WebSocket 保留为目标语义和后续扩展,不作为 Phase 1 验收阻塞项。
- HTTP 业务 gateway 不进入 core;HTTP 管理端点只在
shield_ops显式启用时存在。
OD-005 Optional Module Failure Policy
状态:closed。权威文档见 官方可选模块契约 和 配置运行时语义。
决策:
- 配置包含 optional module 配置段,但对应模块未构建或未启用时,启动失败。
- 显式启用的 optional module 初始化失败默认 fail fast。
shield_cluster第一版允许远端连接失败时 degrade 到单节点,但必须持续暴露 unhealthy 状态;配置非法、节点身份冲突和本地监听失败仍 fail fast。shield_ops启用后绑定管理端口失败必须 fail fast。shield_global、shield_player、shield_server初始化失败默认 fail fast。
OD-006 Data API Phase Split
状态:closed。权威文档见 数据访问运行时语义 和 Lua API 契约。
决策:
- 数据访问不进入
shield_core,也不再通过独立shield_data模块暴露。 - Phase 1 当前契约是插件系统 v1:manifest scan、显式 instance、显式 binding、C ABI interface 和插件自注册 Lua namespace。
- SQL、文档库、缓存、队列和排行榜能力分别由 provider 插件提供。
- 业务 Lua 使用插件 namespace + binding 逻辑名,例如
shield.database.mysql("database.default")。 shield.db.*/shield.redis.*是 legacy API,已删除且不提供 public compatibility shim。- prepared statement、pipeline、eval、sentinel/cluster 等能力由具体插件或后续插件接口独立演进,不反向扩大 core API。
OD-007 Legacy Compatibility Window
状态:closed。权威文档见 重构路线图 和 Lua API 测试用例。
决策:
- 不提供 public compatibility shim。
- legacy Lua API 删除以
lua-api-tests.md的负向测试为门槛。 - legacy C++ target 删除以 examples/tests 不再引用为门槛。
- 旧 CLI subcommand 可以作为 legacy CLI 代码短期存在,但不进入新的
shield::run公共契约。
OD-008 Player Setup API Form
状态:closed。权威文档见 玩家生命周期运行时语义 和 官方可选模块契约。
决策:
- P0 只冻结
shield.player.setup(M, opts)作为业务 Player Service 唯一推荐入口。 shield.player.Base作为高级风格留待 Phase 2+;P0 不实现,P2 边界见 OD-014。- setup 的 opts 字段去
on_前缀(auth/login/client_message/disconnect/reconnect/logout/save),与 module-levelon_init/on_exit隔离命名空间。 - 必填钩子(
auth/login/client_message/disconnect/logout)缺失即返回nil, Error{code="setup_invalid"},service 不进入 running 状态。 - 未在 setup 中提供的可选钩子由框架按文档明列的默认实现执行,不允许"未提供即 noop"的隐式行为。
- 业务覆盖可选钩子时,默认实现不自动执行;保留默认行为需显式调用
shield.player.defaults.*helper。
OD-009 Player Persistence Adapter Boundary
状态:closed。权威文档见 玩家生命周期运行时语义 和 官方可选模块契约。
决策:
- persistence adapter 是
shield_player拥有的轻量契约,不属于shield_data。 - adapter 底层必须通过插件 namespace + binding 逻辑名访问数据能力,不重新定义 SQL/Redis/文档库语义。
- adapter 不拥有连接池;连接池归属对应插件 instance。
- adapter 不引入 ORM、mapper、schema 工具链;只接受可 LuaPack 编码的 table 白名单字段。
- adapter 失败复用对应数据插件错误码;player 域只新增
persistence_save_failed等明确属于本模块的错误。 - 未配置 persistence 时,
on_save默认实现为 no-op。
OD-010 Player Cross-Service Reference
状态:closed。权威文档见 玩家生命周期运行时语义 和 官方可选模块契约。
决策:
- 跨 service 玩家引用命名为
PlayerRef,不命名为PlayerHandle,避免与ServiceHandle混淆。 PlayerRef是 player 模块内部引用,不是ServiceHandle的替代品。PlayerRef结构为{ uid, node_id, service_id, epoch };epoch来自 shield_cluster 的node_epoch,单节点为 0。- 跨 service payload 只能传
PlayerRef(或客户端 RPC 的只读ClientContext/ClientRef标记),禁止传裸连接句柄或完整PlayerSession。 shield.player.resolve(ref)P0 仅冻结本地 resolve;远端 resolve 留 Phase 2+,由shield_cluster+shield_player协作定义。- ref 失效返回
nil, Error{code="player_not_found"},不抛错。
OD-011 Player Extended States
状态:closed。权威文档见 玩家生命周期运行时语义。
决策:
anonymous和spectator都是 opt-in 状态,默认状态机仍是connecting -> authenticating -> online。authhook 仍为必填;业务通过 auth 返回值声明匿名或旁观连接。- 未显式开启时返回
anonymous_disabled或spectator_disabled。 - 匿名升级为正式账号必须重新认证,并生成新的
PlayerRef。
OD-012 Player Scale Model
状态:closed。权威文档见 玩家生命周期运行时语义。
决策:
- 默认模型是
service_per_player。 player_pool是大规模场景的可选运行策略,不改变shield.player.*public API。player_pool下PlayerRef.service_id指向 pool shard;玩家级路由由shield_player在 shard 内完成。- 实现前必须补齐内存、延迟、GC、mailbox backlog 和重连窗口缓存基准。
OD-013 Player Multi-Device Policy
状态:closed。权威文档见 玩家生命周期运行时语义。
决策:
- 默认策略为
single。 kick_old会让旧会话以logoutreason"replaced"退出。multi使用(uid, device_id)索引,shield.player.get(uid)返回主会话,设备枚举走PlayerManager.get_devices(uid)。- 未提供稳定
device_id时只允许临时会话,不参与同设备重连。
OD-014 Player Base API Boundary
状态:closed。权威文档见 玩家生命周期运行时语义。
决策:
shield.player.Base是 P2 语法糖,必须基于shield.player.setup。- Base 不引入第二套 lifecycle,不支持多继承,不恢复 setup 字段的
on_*前缀。 - P0/P1 测试以 setup 为准;Base 测试只验证无额外语义。
OD-015 Runtime Primitives Scope
状态:closed。权威文档见 基础组件与运行时适配边界、重构路线图 和 网络运行时语义。
决策:
shield.buffer/shield.crypto/shield.socket/shield.stream/shield.tls这类 runtime primitives 只保留为后置方向,不进入当前主路径。- 不把 cosocket 风格 API 放进当前对业务用户的默认心智模型。
- 当前推荐心智模型保持三件事:客户端入站走 gateway 单一 target + 只读
ClientContext/ClientRef,服务间走shield.send/call,对外 HTTP 走shield.http。 - 若未来推进 primitives,也应作为高级能力或后置阶段能力,而不是当前默认推荐路径。
- JWT/Auth 不是基础组件层中心能力,也不作为 Shield 官方插件发布;应由业务层基于基础原语或外部服务实现。
后续执行(2026-09-28,AD-08):shield.crypto 一期切片已落地(编解码 / SHA-256 / HMAC-SHA256 / random_bytes / constant_time_compare),让上述“业务层基于基础原语”句式可行; 参考实现为 Lua 层 scripts/lib/jwt.lua,plugins/auth_jwt 标弃用(保留一个过渡期)。其余 primitives(shield.buffer / socket / stream / tls)仍为后置方向。
OD-016 Server State Machine and API Surface
状态:closed。权威文档见 服务器状态运行时语义 和 官方可选模块契约。
决策:
- ServerManager 是进程级 C++ 单例(照 PlayerManager:
global()/set_global()),不是 service:没有 mailbox、没有 CAF actor、不 spawn service、不进入 Starter 编排。 - 状态机为
starting → running ⇄ maintenance、running/maintenance/starting → shutdown;shutdown终态;→ starting恒非法;starting → maintenance非法;同值 set 幂等成功且不通知;非法迁移返回错误不崩溃。 starting → running由 bootstrap init complete 自动触发(mark_ready),不暴露为 API。- 配置段只有
server_manager.{name, info.{name,version,region}},全可选;删除草稿的enabled(启用只由编译开关决定)、state(初始状态恒为 starting)、on_state_change(通知开关由 watch 注册行为表达)字段。 - 状态观察者注册为
shield.server:watch(fn);投递必须走 system 消息通道(send_system)——普通send()静默拒绝on_前缀方法,而状态通知方法名属于保留前缀域。回调签名带 dispatch ctx(fn(ctx, new_state)),与 handler dispatch 规则一致。 - 否决"C++ 注册表持有 Lua 回调对象"方案:会让纯 C++ 的 ServerManager 链接脚本绑定层,破坏可单测性。C++ 注册表只存
{watch_id, service_id};观察者回调由每 VM 的编排层持有并安装转发 handler,观察者 service 退出时按判活自动注销。
OD-017 Server Shutdown Handover and Maintenance Boundary
状态:closed。权威文档见 服务器状态运行时语义。
决策:
server:shutdown(ms)只做三件事:迁移到shutdown、通知观察者、ms毫秒后调用与 SIGINT/SIGTERM 同路径的进程停止请求。它不接管 drain 预算——超时与退出顺序仍归 bootstrapshutdown.timeout.*。- 重复
shutdown返回shutdown_already_scheduled,不重复计时;计时线程用std::jthread+ stop_token,bootstrap 两条清理路径都显式 stop。 - 维护模式 P0 只提供
state()真相查询;登录准入 gate 由业务 /shield_player层自行实现,core 与 gateway 不感知维护状态。 - 状态通知回调经 bootstrap 注入的
notify_fn回调进入 messaging 层,ServerManager 不直接依赖shield_lua,避免环形依赖。 - ops 暴露并入
/ops/status的server块与 consoleroot.server命令;不设独立/ops/server端点,不提供 ops 写操作入口。
Open Decisions
当前没有仍需设计拍板的基础语义项。后续若发现新的未决问题,先在本节记录,再同步更新对应权威文档。