docs(ft546): design shadow-first safe cutover

This commit is contained in:
zgw
2026-08-12 10:35:51 +08:00
parent f66585f717
commit 48b0b154b6

View File

@@ -0,0 +1,367 @@
# FT546 调度中心影子先行安全接入设计
> 状态:已确认
> 确认日期:2026-08-12
> 实施分支:`optimize/wcs`
> 现场原则:稳定版本始终是唯一生产调度者和唯一 PLC 写入者,直到受控切换完成
## 1. 背景与决策
`javaTest` 中心调度已经覆盖父任务阶段、选车、路径规划、交通资源、换层、二段式任务、避让、故障注入和历史回放。`ruoyi-wcs` 与 FT546 Admin 已完成一部分标准内核、PLC 编码、Outbox、运行模式和内存端到端测试,但生产适配器、现场事实对账和运行模式隔离仍不完整。
本设计采用“影子先行”路线:
1. 现场稳定版本不改动、不停机,继续独占现场任务、业务表、WMS 回调和 PLC 写入。
2. 先运行独立 `javaTest` 离线影子,建立确定性算法基线。
3. 再实现与生产控制进程隔离的实时只读采集与同核影子计算。
4. 先根据现场差异证据决定算法和适配器修改,不预设新算法一定优于旧算法。
5. 通过 SQL Server 集成、现场影子和 HIL 门禁后,才允许在维护窗口内由 `LEGACY` 受控切换到 `STANDARD`。
截至确认日,不能直接把 `ruoyi-admin --ft546.wcs.runtime-mode=SHADOW` 部署为现场影子。当前实现没有为 `SHADOW` 装配标准核心,却仍会在 `ScheduleThread.@PostConstruct` 中查询设备、构造 PLC reader、注册设备运行时并构造旧业务线程;无权限保护的线程操作接口还可以手动启动 PLC reader 和旧调度线程。模式名称本身不构成安全隔离。
## 2. 目标与非目标
### 2.1 目标
- 将 `javaTest` 已验证的调度语义逐步接入 `ruoyi-wcs`,不破坏 FT546 现有业务。
- 保持 `LEGACY` 为默认模式和可验证回退路径。
- 使 `SHADOW` 与未来 `STANDARD` 复用同一调度核心,只在出站能力和状态所有权上不同。
- 在影子阶段强制实现零 PLC 写入、零 WMS 回调、零生产业务表写入和零旧业务调度线程。
- 对旧队列字段和 PLC 地址、数据类型、值、顺序形成可审计差异报告。
- 完整表达父任务阶段、同车绑定、换层、二段下发、避让、业务完成、车辆释放、Outbox 和重启恢复。
- 在缺少真实 PLC 或隔离 SQL Server 时明确证据边界,不把内存测试或历史回放声明为可投产证明。
### 2.2 非目标
- 不在影子采集前主动修改调度规则来追求结果一致。
- 不在现场稳定分支上开发、试验或合并未验收代码。
- 不修改 FT546 PLC 控制流程、安全联锁和既有寄存器语义。
- 不做 `LEGACY` 与 `STANDARD` 并行 PLC 双写。
- 不做运行中热切换或遇错自动切回。
- 不用历史 `agv_id`、`path_info`、完成状态或 `finish_date` 强制新调度作出相同选择。
## 3. 方案比较
### 3.1 采用:同核只读影子后受控接管
未来 `SHADOW` 与 `STANDARD` 使用同一个任务、调度、交通和恢复核心。`SHADOW` 只消费不可变现场事实并生成虚拟命令;`STANDARD` 在就绪、恢复和单写者门禁全部通过后才装配真实出站适配器。
优点是算法差异可以在无现场控制权的条件下观察,切换前能证明协议和值的一致性,切换后也没有第二套调度语义。代价是必须先完成持久化、只读采集、差异报告和运行模式硬隔离。
### 3.2 不采用:直接启用当前 STANDARD
当前 `STANDARD` 绑定的多个 Repository、Gateway、Snapshot 和 Recovery 端口尚无生产实现,不能证明应用可启动、SQL Server 事务可恢复或真实 PLC 语义可靠。直接启用可能造成启动失败、首单失败或发送后结果不明。
### 3.3 不采用:把旧队列线程包装成新调度中心
这种做法会保留旧线程和新内核对任务、车辆、资源和完成事实的双重所有权,无法证明唯一调度者,也无法解决阶段语义、Outbox、`UNKNOWN` 和重启恢复问题。
## 4. 运行模式与单写者边界
| 模式 | 调度所有者 | 现场事实来源 | 核心状态写入 | PLC/WMS 写入 | 用途 |
| --- | --- | --- | --- | --- | --- |
| `LEGACY` | 旧线程 | 现有 PLC reader 与业务表 | 旧表 | 允许,唯一写者 | 当前生产和回退 |
| `SHADOW` | 标准核心,仅计算 | 离线快照或只读采集 | 独立影子库/Schema | 禁止 | 差异采集与准入 |
| `STANDARD` | 标准核心 | 标准快照采集器 | WCS 核心表 | 通过 Outbox 允许 | 最终受控接管 |
强制规则:
1. 默认值保持 `LEGACY`。
2. `SHADOW` 不创建真实 `DeviceCommandGateway`、`WmsNotificationGateway`、旧业务调度线程或 PLC reader。
3. `SHADOW` 的禁止写入必须同时由 Bean 装配、数据库权限、网络 ACL 和自动化测试保证。
4. `STANDARD` 缺少任何必需端口、端点、能力证据或恢复条件时必须启动失败或保持未激活,不能降级为“首单运行时失败”。
5. `LEGACY` 与 `STANDARD` 不能同时拥有有效调度租约或有效 PLC 写通道。
6. 切换采用配置变更加进程重启,不支持运行中热切换。
## 5. 现场稳定版本保护
### 5.1 代码与制品
- 开发只在 `optimize/wcs` worktree 进行,不修改、合并或推送到现场稳定分支。
- 设计确认时,本地远端跟踪引用 `origin/main` 为提交 `652607c`。现场回退必须锁定实际已部署制品、提交号、配置和校验和;不能只依赖可能移动或过期的分支名称。
- 本地 `main` 与 `origin/main` 已分叉,不能把本地 `main` 当作现场回退来源。
- 数据库迁移只允许新增表、列、索引和约束,不删除、不重命名现场旧对象。
### 5.2 进程、网络与数据库
- 实时影子使用独立进程、服务端口、日志目录和部署目录,优先部署在独立主机。
- 影子数据库账号只允许读取批准的现场事实视图;禁止写现场订单、队列、设备、资源、Outbox 和回调表。
- 影子结果写入独立数据库或独立 Schema,不能与生产核心表共用写权限。
- 网络 ACL 禁止影子主机访问 PLC 写端口和生产 WMS 回调地址。
- 影子查询使用独立连接池、短超时、限速和熔断;积压或超时时停止影子计算,不持续压迫生产数据库。
- 影子进程的崩溃、阻塞、快照过期或输出磁盘故障不能反向暂停或修改现场稳定程序。
### 5.3 当前可运行边界
独立 `javaTest` 可以立即运行。它只读取打包的历史与地图资源,使用内存车辆、队列、PLC、WMS 和资源账本,不启动 Spring、不连接 SQL Server/PLC/WMS,只写命令参数指定的本地输出目录。
当前 `ruoyi-admin` 的 `SHADOW` 不允许直接运行现场影子,必须先完成以下整改:
- `ScheduleThread` 按模式装配,`SHADOW` 不执行 `plcReadInit()` 和 `scheduleInit()`。
- `ThreadOperationController` 在 `SHADOW` 禁止启动 PLC reader 和旧调度;接口恢复权限校验并按模式返回明确状态。
- 建立专用 `Ft546ShadowConfiguration`,只装配只读快照源、同核调度、记录型命令网关和差异仓储。
- 影子启动自检必须证明无 PLC/WMS 真实 Bean、无生产写权限和无旧调度线程。
## 6. 父任务与阶段模型
一个业务请求只创建一个父任务,阶段不会重新进入全局业务队列,也不会重新创建 WMS 任务:
```text
WAITING_ASSIGNMENT
-> 可选 FLOOR_TRANSFER_TO_PICKUP
-> EMPTY_TO_PICKUP
-> PICKUP_CONFIRMED
-> 可选 LOADED_RETURN_TO_SOURCE_FRONT
-> LOADED_TO_PLACE
-> CARGO_DEPOSITED / BUSINESS_COMPLETED
-> 可选 EMPTY_RETURN_TO_TARGET_FRONT
-> VEHICLE_RELEASED
```
规则:
- `task_type` 表示执行阶段,业务类型取 `default02`;入库、出库、移库分别为 `1/2/7`。
- 取货确认后载货状态由 `EMPTY` 变为 `LOADED`,车辆与父任务锁定;普通等待、断线和重启不能自动换车。
- 到放货点并取得可靠完成事实后记录 `CARGO_DEPOSITED`,只归档一次并只产生一次 WMS 业务效果。
- 车辆可能在业务完成后继续空车返回目标通道前端,车辆释放时间与业务完成时间分别记录。
- 每个阶段从设备确认的实际位置重新规划,不复用历史路径或上一阶段估算位置。
- 命令身份至少包含 `parentTaskId + phase + routeVersion + commandSegment`;旧 ACK 不能推进新阶段。
- 不需要的阶段必须记录明确跳过原因,不能无痕跳过。
## 7. 选车、路径与避让
- 车辆硬过滤包含在线、自动、健康、已同步、空闲、电量、载货、能力和可达性。
- 同层和跨层可行车辆进入同一稳定选车策略;历史车辆只用于对比。
- 候选路径限制在最短物理距离的 `110%` 内,最多 32 条,按对向风险、占用节点、不兼容预约、活动路线重叠、转弯、距离和站点序列作确定性字典序比较。
- 临时交通冲突只进入 `HOLD`,不能误判为 `NO_ROUTE`。
- 同一任务、车辆、阻塞资源和原因连续三次交通 `HOLD` 后,才允许从实际位置重规划尚未承诺的剩余路线。
- 已发送、已接受或结果为 `UNKNOWN` 的动作不能被新路线覆盖。
- 路线预约必须原子替换;失败时完整保留旧路线和旧预约。
- 只有拓扑中存在经过验证的安全让行点,才创建 `CLEARANCE` 运行步骤;不能临时选择任意空点。
- 避让不创建新 WMS 业务任务,不重复归档或回调。
## 8. 换层编排
提升机不是普通地图边。跨层任务使用幂等换层子作业,并联合保护源入口、提升机本体和目标出口:
```text
预约源入口 + 提升机 + 目标出口
-> 创建幂等换层子作业并锁车
-> 等待旧提升机协议执行事实
-> 核对车辆、楼层、门、端口和实际出口
-> 从实际出口重新规划当前业务阶段
```
- 任一资源预约失败时保持 `BLOCKED`,不发送进入提升机动作。
- 作业提交后断线、故障或重启不释放车辆锁,不创建第二个换层作业。
- 完成事实必须包含匹配车辆、目标层和实际出口;重复完成只推进一次。
- 目标出口占用时保持换层完成待提交状态,不能让车辆出现在已占节点。
- 事实不确定时进入恢复工单,隔离车辆、提升机和相关端口,不盲目释放。
## 9. FT546 旧 PLC 与二段任务
标准核心生成强类型命令意图,FT546 Adapter 负责映射旧寄存器。核心不伪装旧 PLC 不具备的候选缓冲、Prepare/Commit、命令幂等或有序快照能力。
必须保持并逐项回归的写入序列:
```text
普通主任务:4004 -> 4000 -> 4002 -> 4021
跨层任务: 普通主任务序列 + 4020=1 -> 4022=8
二段第一段:4404 -> 4422=1 -> 4402=1 -> 4400
二段第二段:4404 -> 4422=2 -> 4423=99 -> 4402=9 -> 4400
```
对每次虚拟或真实协议发送,证据至少包含:
```text
deviceId
parentTaskId
phase
commandKey
routeVersion
taskNo
taskType
pathPoints
registerAddress
dataType
values
writeSequence
triggerValue
```
相同车辆、路径、阶段和任务输入下,队列字段、寄存器地址、数据类型、值和顺序必须与旧实现逐项一致。新算法可以选择不同车辆或安全路径,但差异必须归类为算法决策差异,不能混入协议不一致。
旧 PLC 不识别 `commandKey`。发送前能证明未发送的失败可以重试;任何写入开始后无法证明结果的命令进入 `UNKNOWN`,保护完整点到点或二段任务路径,不补写单个寄存器、不自动重发。
## 10. 设备事实、Outbox 与恢复
每个标准调度 tick 的因果顺序为:
```text
采集不可变设备事实
-> 校验新鲜度和恢复门禁
-> 加载候选任务、资源和活动命令
-> 生成调度授权或 HOLD
-> 同事务持久化绑定、预约、DeviceCommand 和 CommandOutbox
-> 出站分发
-> 使用新设备事实推进步骤、订单和资源
-> 独立分发 WMS NotificationOutbox
```
- `send` 成功只表示通信层受理,不表示物理完成。
- 订单、步骤、设备命令、设备事实和资源预约保持独立状态。
- 设备事实必须校验设备、任务号、阶段、预期端点、载货变化、模式和时间新鲜度。
- 重复、乱序、倒退、旧阶段或不能关联当前命令的事实只记录审计,不推进状态。
- `UNKNOWN` 保留车辆和未确认资源,只允许继续只读采集、诊断和受控恢复。
- WMS 回调失败不回滚物理任务,只重试通知 Outbox。
- 核心 tick 失败时不创建新设备动作;已存在的 WMS 通知 Outbox 可以独立投递。
- 重启时先取得租约并保持全局调度暂停,加载活动状态、采集设备快照、完成对账,再局部启用安全设备。
## 11. 影子数据与差异报告
### 11.1 数据来源顺序
1. 已打包的历史队列、地图和通道前端快照,用于可重复离线基线。
2. 现场稳定程序已经落库的只读事实,用于实时影子比较。
3. 经过审批的原始报文副本,用于协议和故障审计。
影子不直接增加 PLC 连接。实时事实缺失、过期或互相矛盾时,本轮结果标记为 `INSUFFICIENT_FACTS`,不产生误导性可比结论。
### 11.2 比较维度
协议一致性:
- 队列业务字段、默认字段和阶段字段;
- PLC 地址、数据类型、值、顺序和触发值;
- 换层、二段任务和旧任务号编码;
- 完成、归档和回调的幂等键。
调度合理性:
- 任务到达与父任务归并;
- 车辆资格、选车和同车绑定;
- 每阶段路径、候选质量和资源保护;
- 换层入口/出口、二段、HOLD、避让和重规划;
- 业务完成与车辆释放。
每个差异必须保存相同输入快照、旧结果、新结果、差异类别和可解释原因。协议差异与算法差异分别统计。
### 11.3 已建立的离线基线
2026-08-12 在提交 `f66585f` 上执行:
```text
javaTest tests: 549
failures/errors/skipped: 0/0/0
raw snapshots / unique GUID / duplicates: 240 / 98 / 142
business tasks / operational queues: 76 / 17
completed / waiting / failed: 76 / 0 / 0
incomplete vehicle releases: 0
total safety violations / deadlocks: 0 / 0
archive count / WMS effects / duplicate effects: 76 / 76 / 0
```
本地证据目录:`javaTest/outputs/wcs-history-business-shadow-20260812-f66585f/`。六个报告文件的 `manifest.sha256` 校验结果为零不匹配。该目录是开发机生成证据,不随本设计文档提交。
报告明确标注:这是“真实历史负载驱动纯内存仿真”,不是现场 `SHADOW`、HIL 或 `ACTIVE` 验收。
## 12. 故障隔离
| 故障 | 处理 | 禁止行为 |
| --- | --- | --- |
| 影子快照缺失/过期 | 标记 `INSUFFICIENT_FACTS`,暂停该对象计算 | 猜测设备状态 |
| 影子数据库超时 | 熔断、退避并记录采集缺口 | 加速重试压迫生产库 |
| 命令发送结果不明 | `UNKNOWN`、隔离设备和资源 | 自动重发或释放 |
| 换层事实不一致 | 隔离车辆、提升机和端口,创建 RecoveryCase | 重建换层任务 |
| 二段任务部分写入不明 | 保护整个二段任务范围 | 补写单寄存器 |
| WMS 回调失败 | 重试通知 Outbox | 回滚物理完成 |
| 影子进程崩溃 | 只影响影子报告 | 影响现场调度或告警处置 |
| 输出磁盘满 | 停止新影子计算并告警 | 丢弃差异后继续宣称健康 |
## 13. STANDARD 切换与回退
现场不做热切换。维护窗口内按以下顺序交接:
```text
LEGACY 正常运行
-> STANDARD 以控制关闭方式启动并完成恢复对账
-> 暂停新任务进入
-> 等待或人工核对活动命令
-> 停止 LEGACY 调度写入
-> 获取数据库租约和 fencing token
-> 再次验证旧写者失效
-> 开放 STANDARD 真实出站网关
-> 恢复新任务进入
```
回退规则:
- `STANDARD` 取得写入权之前失败时,`LEGACY` 不停止。
- `STANDARD` 取得写入权之后失败时,先禁止新命令并对账所有活动命令。
- 存在 `UNKNOWN` 时禁止自动切回 `LEGACY`,必须先完成现场对账,避免重复动作。
- 确认无未决物理动作后,释放 `STANDARD` 租约,恢复锁定的稳定制品和 `LEGACY` 配置。
- 回退不回滚新增数据库结构,只停止使用;避免数据库降级造成更大不可用。
- 切换和回退演练必须记录制品校验和、配置快照、操作者、时间、活动任务清单和对账证据。
## 14. 测试与准入门禁
### 14.1 自动化分层
1. 领域测试:父任务阶段、同车绑定、业务完成与车辆释放、换层、避让、HOLD、资源容量和死锁。
2. 协议测试:队列字段、寄存器地址、数据类型、值和顺序逐项对比。
3. 故障测试:重复/旧/乱序 ACK、断线、超时、部分写入、重启、乱序快照、Outbox 和 `UNKNOWN`。
4. 模式隔离测试:`SHADOW` 上下文无 PLC/WMS 真实 Bean、无生产写入、无旧调度线程,线程操作接口 fail-closed。
5. SQL Server 集成测试:事务、唯一键、乐观锁、资源并发、Outbox、租约、fencing token 和重启持久性。
6. 模拟端到端:入库、出库、移库、同层、跨层、二段、通道前端返回和共享资源避让。
7. 历史回放:240 条原始记录归并、76 个父任务全阶段和所有不变量。
8. 实时影子:旧队列与新虚拟队列的连续差异采集,且副作用为零。
9. HIL:隔离 PLC 或测试设备上的寄存器时序、断线、重启和部分写入。
10. 切换演练:单写者交接、启动失败不影响 LEGACY、对账后回退。
### 14.2 STANDARD 硬门槛
- 安全不变量违规数为 0。
- 现场表写入、PLC 写入和 WMS 回调等影子副作用为 0。
- 可比较场景的队列业务字段、PLC 地址、数据类型、值和顺序不一致数为 0。
- 所有算法差异都有输入、旧结果、新结果和决策原因;未经审核的差异数为 0。
- 所有生产 Adapter、Repository、端点、拓扑、能力证据和恢复门禁均就绪。
- SQL Server 集成、现场影子和 HIL 任一缺失时,只能声明本地实现或相应层级验收完成,不能声明可投产。
## 15. 实施顺序
### 阶段 A:离线影子基线
- 保持现场稳定程序不变。
- 固化 `javaTest` 输入清单、测试结果、报告和校验和。
- 建立队列/PLC 协议字段差异格式。
### 阶段 B:实时只读影子硬隔离
- 修复 `SHADOW` 条件装配和线程启动边界。
- 增加只读现场事实 Adapter、记录型命令网关和独立差异仓储。
- 增加启动自检、数据库权限验证和网络不可达证明。
- 现场旁路运行并采集差异,不修改调度算法。
### 阶段 C:按证据补齐同核语义和 FT546 Adapter
- 根据差异逐项补齐父任务阶段、换层、二段、避让、设备事实、Outbox 和恢复。
- 每次修改都用同一采样重新计算,区分协议回归与允许的算法差异。
- 完成生产 MyBatis Repository 和 SQL Server 集成验证。
### 阶段 D:HIL 和受控切换
- 在隔离 PLC/测试设备验证真实协议。
- 完成维护窗口切换和回退演练。
- 只有全部硬门槛通过后才允许切换现场写入权。
## 16. 完成定义
本接入只有在以下事实同时成立时才算完成:
- `SHADOW` 与 `STANDARD` 使用同一核心,`SHADOW` 没有权威写通道。
- 现场稳定版本在影子阶段保持唯一生产调度者和 PLC 写入者。
- 父任务、换层、二段、避让、Outbox、事实对账和恢复均有生产实现及自动化证据。
- 队列业务字段和 PLC 地址、类型、值、顺序与旧逻辑的协议差异为零。
- 所有允许的车辆或路径差异均可解释、已审核且不违反安全不变量。
- SQL Server、实时影子、HIL、单写者切换和回退演练证据齐全。
- `LEGACY` 稳定制品、配置和对账式回退流程仍可执行。