fix(shadow): respect queue phases and compress input evidence

This commit is contained in:
zgw
2026-09-08 11:23:50 +08:00
parent 414b3bf4a9
commit af8b5f1830
40 changed files with 1035 additions and 46 deletions

View File

@@ -0,0 +1,58 @@
# 2026-09-08 影子阶段与存储修复
适用:现场源码基线 `414b3bf`,分支 `optimize/wcs`,目录 `C:\project\FT546-WCS-SHANDOW`,Windows CMD + IDEA + Java 8 + Maven 3.6.0。
本次更新仅用于继续影子测试。正式调度、PLC 通讯和生产数据库写入路径不变。本次没有现场 SQL 迁移、权限调整、清理历史数据、扩大容量上限或远程进程操作。
## 修复后的行为
1. 从现有只读视图增加采集 `issued`、`execution_status`。只有明确的 `0 / 1` 待下发阶段可产生新预测。已下发、已完成、生命周期不明、已到终点及无移动阶段记录为 `OBSERVATION_ONLY`,不产生虚拟寄存器写入,也不计入算法不一致。
2. 保留队列已绑定的车辆;忙碌、禁止发送、低电量和故障门槛不放宽。绑定阶段按正式程序的发送规则,从车辆当前位置到当前终点预测,不再回到旧队列起点折返。跨楼层及两阶段交接当前只观察,仍需专门的状态机证据才能评价其新调度行为。
3. 旧结果还原遵循 PLC 发送规则:`tsj` 的 `default02=1/2` 终点为 `09050<楼层>`,`8` 为 `07050<楼层>`。路径可在提升机入口结束,而实际发送端点会被改写,不能直接用路径末点当作发送末点。与本阶段终点不符的旧路径,以及待下发阶段起点已漂移的路径,记录为旧结果不可用。
4. 新输入采用无损 gzip;同一次证据保存中的相同输入只存一份压缩正文,其余记录引用本批首条输入。原始 JSON 的哈希保持不变。使用现有表、同一事务,不增加生产访问权限。
5. 安全等待/避让点只能通过 `shadow.traffic.safe-wait-sites`、`shadow.traffic.safe-yield-sites` 显式配置。默认空列表,状态显示 `UNCONFIGURED`。配置点必须是可通行数字站点且至少有两个相邻出口;这个校验不能代替现场人员确认物理安全。未取得现场确认前保持空列表。
6. `/shadow/status` 和运行心跳新增 `runtimeIdentity`。本次 `buildId` 为 `ft546-shadow-20260908-phase-storage-v2`。`effectiveSettingsSha256` 自动计算,覆盖返回的非秘密调度、存储、端口及安全点配置;不包含数据库连接凭据、外部配置文件或已编译类的完整指纹。
## 现场更新顺序
与操作人员逐步进行,每一步确认结果后再进入下一步。只停止 `Ft546ShadowApplication`;不要停止 `RuoYiApplication`,不要使用 Stop All,也不要批量结束 Java 进程。
1. 在 IDEA 中选中影子程序 `Ft546ShadowApplication` 的运行窗口,点红色停止按钮,确认这个影子进程退出。
2. CMD 进入 `C:\project\FT546-WCS-SHANDOW`,先执行 `git status --short` 和 `git fetch origin optimize/wcs`。保留现场配置、忽略文件和 `shadow-evidence`。若有与本次源码重叠的本地修改,先备份并核对;不要 reset、clean 或强制覆盖。
3. 使用交付消息指定的准确提交执行 `git merge --ff-only <提交号>`,再执行 `git log -1 --oneline` 核对。不要无条件更新到将来的其他提交。
4. IDEA 刷新 Maven,保持原有 Java 8 和 Maven 3.6.0,编译 `ruoyi-wcs-shadow` 及其依赖。沿用此前已验证的离线依赖,不需要部署脚本或 JAR 包。
5. 核对影子运行配置:主类仍为 `com.ruoyi.shadow.Ft546ShadowApplication`;数据库账号、生产只读库、证据库、18091 本机端口和存储上限沿用现场设置。新增安全点列表保持空,直到现场确认。
6. 修正运行元数据后,仅启动这个影子配置。浏览器访问 `http://127.0.0.1:18091/shadow/status`。核对 `READY`、全部启动检查 PASS、上述 buildId,以及 `cycleDiagnostics.dispatch` 内各项计数。只观察时 `eligiblePhases=0` 可以是正常状态,不代表算法完成验证。
## IDEA 中两个人工哈希字段
历史运行的元数据不修改。审计发现此前配置哈希填入了源码归档哈希;新运行不要照抄或互换这两个值。
- `shadow.evidence.run.artifact-sha256`:记录本次交付物的 SHA-256。源码运行时,可以使用当前准确提交的源码归档哈希,并在现场记录中注明 `source archive`,不能称为运行中 JAR 或编译类的校验值。
- `shadow.evidence.run.configuration-sha256`:记录本次现场运行配置快照文件的 SHA-256。配置快照保留在现场受控目录,不提交到 Git,不发送含密码的文件。
- 在 CMD 中可以使用 `certutil -hashfile "文件完整路径" SHA256` 计算实际文件的哈希。若用源码归档,可先运行 `git archive --format=tar -o "%TEMP%\ft546-shadow-source.tar" HEAD`,再对这个文件运行 certutil。归档仅用于现场计算元数据,不是额外的部署包。
- 状态中的 `operatorArtifactSha256` / `operatorConfigurationSha256` 是上述人工输入,程序没有将其认证为实际运行产物。`buildId` 是代码内版本标签;`effectiveSettingsSha256` 是程序自动计算的配置摘要,两者与人工哈希分开显示。
## 证据导出兼容性
`shadow_input_batch.captured_json` 现在允许三种形式:旧版原始 JSON、`ft546-input-gzip-v1` 压缩信封、`ft546-input-ref-v1` 同批引用。`content_hash` 始终校验原始 JSON,不能对压缩信封直接算哈希后判定损坏。
推荐从生产服务器本机浏览器或可访问本机 HTTP 的 AI 工具读取:
```text
http://127.0.0.1:18091/shadow/inputs/<cycle_id>
```
每次返回一条输入:`cycleId`、`runId`、`contentHash`、`hashVerified=true`、`capturedJson`(原始 JSON 字符串)。接口只查询证据库;拒绝非本机连接、缺失/跨运行/循环/多层引用、损坏哈希和超限解压。单份原始输入最多 8 MiB。
如果 AI 只能查询 SQL,应同时导出引用记录和所指向的根记录,保留原样交给分析程序解码。仅按尾部时间窗口导出可能遗漏根记录。不要再假定 `captured_json` 始终直接包含 `maps/queues/vehicles`。
同批根输入与引用在同一事务中写入,任一子表失败整批回滚。压缩格式不影响原始输入哈希、决定哈希和逻辑证据哈希。旧程序回退运行仍可新增旧格式输入;分析工具必须继续理解新旧两种物理存储形式。不要逐条删除某个根输入而保留引用。
## 验证与剩余边界
- 最终本地验证命令:`mvn -o -pl ruoyi-wcs-shadow -am test -Dshadow.replay.audit-directory=<本地审计导出目录>`。覆盖 WCS 核心、FT546 协议和影子模块;现场样本留在 Git 之外。旧 300 条归档已不在原附件路径,本次使用仍可读取的 2026-09-08 审计导出回放,未编造或补填其生命周期。
- 新增回归覆盖生命周期隔离、已到终点、绑定车辆不可重分配、忙碌车辆保护、提升机端点改写、旧阶段路径、显式安全点、压缩还原、篡改/解压上限/引用校验和事务回滚。
- 2026-09-08 审计导出的 25 条样本包含 17 份不同输入,离线回放保持 JSON 与哈希原样。输入字段按 SQL Server nvarchar 字节估算,从 3,294,254 字节降至 604,048 字节(18.34%);此数字不包括其他表、索引、页开销、事务日志,也不代表数据库文件自动缩小。
- 当日最后一次容量检查为 14.39 GiB / 16 GiB。压缩只降低后续增长;旧空间不释放,容量上限不变。再次达到保护上限仍会停止影子采集,需要另行评估归档方案,不能只调大阈值。
- `READY` 只说明影子采集链路健康。正式版接管前还需本次新运行的有效待下发样本、路线与协议差异、拥堵/避让及特殊交接阶段证据;本修复不构成正式版上线批准。

View File

@@ -0,0 +1,27 @@
# FT546 shadow phase and evidence repair
The user approved repairing the five findings in the 2026-09-08 field assessment. Execute in the existing `optimize/wcs` worktree. Subagents inspect/review only; the main agent implements and verifies.
## Constraints and design
- Preserve Java 8, Maven 3.6.0, offline dependencies, the formal Admin/PLC/WMS runtime, existing evidence and SQL permissions. No live migration, deletion, threshold increase or process operation.
- Read queue `issued` and `execution_status` from the existing source views. Only explicit pending `0/1` rows may produce new predictions; executing, completed, unknown and already-arrived phases produce observation evidence. Preserve an existing vehicle binding with a shadow-only assignment adapter. Do not relax busy-vehicle checks.
- Reconstruct legacy PLC endpoints using the actual sending rules, including lift endpoint rewriting and stale path/phase rejection. Observation rows are separate from algorithm differences.
- Store new input JSON losslessly as a versioned gzip envelope; share the first stored input within a save batch through a bounded cycle reference. Preserve original logical hashes. Keep old raw JSON readable. All input rows/references remain in existing tables, in the existing transaction. A loopback input export endpoint returns original JSON with its verified hash, so readers do not need to understand physical storage.
- Configure explicitly approved safe wait/yield site IDs; validate topology membership and safe-exit constraints. Empty configuration remains protected and produces an explicit diagnostic. Never infer physical safety from ordinary passability.
- Publish an embedded repair build ID and a computed digest of nonsecret effective runtime settings independently of operator-supplied artifact/config hashes. Record the required IDEA field correction in the upgrade notes; do not rewrite historical runs.
## Execution checklist
- [x] Baseline `mvn -o -pl ruoyi-wcs-shadow -am test` passes; keep the baseline log outside the worktree.
- [x] Add failing lifecycle and legacy projection tests, including already-issued, completed-at-destination, pending bound vehicle and lift `tsj` cases. Add fields to `ShadowQueueFact`/`SqlServerShadowFactReader`, a shadow eligibility classifier/assignment adapter, and observation support in evidence collection/comparison. Preserve old fixture hashes when new fields are absent; absent lifecycle is observation-only for dispatch.
- [x] Add failing storage round-trip, tamper, inflation-limit, reference, rollback and old-format tests. Implement `ShadowInputPayloadCodec`, repository input resolution and a bounded loopback input endpoint. Budget physical inserted payload while enforcing logical input bounds.
- [x] Add configured-safe-point and invalid-point tests. Wire the settings into `ShadowDispatchSnapshotAssembler` and expose blocked-resource/safe-point diagnostics without enabling unconfigured avoidance.
- [x] Add independent runtime build/config identity tests and wire identity into the status/heartbeat. Document source-only deployment fields and physical input export format.
- [x] Run focused tests after each change, then the full reactor and historical/new captured-input replay. Validate real field inputs remain immutable and incomplete old lifecycle cannot authorize dispatch.
- [x] Run independent read-only review of phase safety and evidence compatibility. Resolve findings, update the runbook and confirm no formal runtime source changes.
- Delivery after this plan snapshot: commit/push the scoped shadow repair under the existing authorization for `optimize/wcs`; record the exact revision in the handoff. Do not deploy remotely.
Implementation decisions: `BaseSRgvThread` passes the live vehicle site to the sender, so bound phase predictions now start there directly. Cross-floor and two-stage handoffs remain observation-only pending state-machine evidence. Reviewer suggestion to reject all lift-entry paths was not adopted: the formal sender explicitly rewrites the endpoint after path construction. Final review identified IPv4-mapped loopback compatibility; a failing regression reproduced it and numeric `InetAddress.isLoopbackAddress` parsing fixes it without DNS or forwarded-header trust.
Private field replay: 25 rows / 17 unique raw inputs preserve their exact JSON and input hashes. New gzip root payloads total 604,048 nvarchar bytes versus 3,294,254 raw bytes (18.34%). This is input payload only, not total SQL allocation. The earlier 300-row archive is no longer at its attachment path; current verification uses the available 2026-09-08 audit exports plus explicit pending lifecycle fixtures.