aps-agent/docs/round-66-solver-isolation-w...

139 lines
9.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Round 66 工作计划:Windows 求解器进程隔离与 Fail-Closed
日期:2026-08-03
## 1. 背景与方向选择
- Round 65 已完成并通过独立复审。完成矩阵下一优先级为 P0 可信试点基线。
- 并行只读审计比较了三条候选:全量 P2/P3 证据封套、共享审批存储可靠性、Windows 求解器 fatal。
- 本轮选择 Windows 求解器进程隔离,因为它可直接杀死后端主进程、绕过 Python 异常/审计,并曾出现“测试 passed 且 exit 0,但输出 Windows fatal”的假绿色。
- 全量 P2/P3 证据封套与共享审批迁移 token/损坏源问题登记为后续 P0 轮次,不以本轮完成替代。
## 2. 已确认事实
- 默认 PATH `python` 来自 Anaconda,用户 site OR-Tools 与 Conda pandas/pyarrow/DLL 混装可复现 `0xc0000139 / WinError 127`。
- `.venv` 与 `.sidecar-venv` 的独立求解探针当前通过,但 `.venv` base 仍属于 Anaconda,不能作为发布运行时证明。
- CP/HYBRID 目前在后端主进程调用 `optimize_line_assignment()`;OR-Tools 时限不能隔离导入崩溃、C++ abort、native deadlock 或进程级 fatal。
- 默认标准排产使用 HYBRID,rush/LNS/敏感性/鲁棒性等也通过引擎动态分派进入该边界。
## 3. 本轮目标
建立唯一的求解器进程边界:父进程构造不可变请求,子进程只执行 CP-SAT 优化并返回版本化 JSON;父进程验证退出码、fatal marker、协议、运行时身份和超时后,才允许在父进程物化世界状态。任何 native/runtime/timeout/protocol 失败都结构化失败关闭,不能杀死 8003,不能留下 PO/WO/可发布版本,HYBRID 不能伪装成 RULE 成功。
## 4. 范围
### In scope
- 新增 solver subprocess request/response 协议、fatal marker 检测、OS 级 timeout、进程树回收和运行时身份。
- CP 与 HYBRID 的 `optimize_line_assignment` 改由子进程执行;健康路径保持原 ordered entries、gap、cumulative 和物化语义。
- 子进程失败时父进程 world 深比较不变;CP/HYBRID 返回结构化 `SOLVER_*` 阻断,不做静默成功降级。
- Sidecar 增加 `--solver-child`;普通 CPython 使用 `-I -B -X faulthandler -m server.engines.solver_worker`。
- 强化 `scripts/check_solver_runtime.py`:内部 timeout、模块来源、base executable、user-site、版本和 fatal marker,即使 returncode=0 也拒绝。
- 桌面 child env 清理 Conda PATH 段,继续设置 `PYTHONNOUSERSITE=1`。
- 增加固定服务器运行时锁/身份说明,但不在本轮擅自重建用户 `.venv`。
### Out of scope
- 不修改 `get_engine()` 全局工厂。
- 不把真实 Windows CI、干净离线机或签名冻结产物冒充本地完成;这些保留外部验收。
- 不在本轮同时重构全部 P2/P3 evidence resolver、审批 outbox 或真实 MySQL 多主机。
- 不提交、不推送、不合并、不发布。
## 5. 失败代码与产品语义
父进程统一识别:
- `SOLVER_RUNTIME_UNSAFE`:运行时身份/模块来源不可信。
- `SOLVER_NATIVE_FATAL`:stderr/stdout 命中 fatal marker,包括 returncode=0。
- `SOLVER_PROCESS_EXITED`:非零退出且非特定 runtime 失败。
- `SOLVER_PROCESS_TIMEOUT`:超过求解时限 + 启动宽限。
- `SOLVER_RESPONSE_INVALID`:JSON 截断、缺成功标记、协议版本或 schema 不匹配。
- `SOLVER_VERSION_MISMATCH`:父子协议或引擎版本不一致。
规则:
1. 子进程失败前后 world 完全一致。
2. 显式 CP 失败不降级 RULE。
3. HYBRID native/runtime 失败默认阻断;若未来允许降级,必须是显式策略且不可发布。
4. 只有验证通过的 ordered entries 才进入 `materialize_schedule()`。
5. 失败必须可写算法失败审计或返回结构化阻断;父进程和 8003 必须继续存活。
## 6. 验收标准
- 健康 `.venv`:CP/HYBRID subprocess 与现有 in-process 基准产生等价可行性、工序顺序、line assignment、gap/cumulative 元数据。
- 故障注入:非零退出、returncode=0+fatal marker、超时、非法 JSON、缺 success marker、协议不匹配全部 fail-closed。
- 故障前后 world 深比较一致,0 新 PO/WO/VL,不产生可发布版本。
- 当前默认 Anaconda 探针稳定返回不安全/失败证据;`.venv`、`.sidecar-venv` 探针连续通过。
- 冻结 Sidecar(若现有产物可用)`--solver-child` 冒烟通过;若产物需重打包,记录为构建后验证而不伪造。
- 8003/5173 在模拟 child crash/timeout 后继续 HTTP 200。
- focused、全量黄金、Node、Web build 通过;`git diff --check` 与 `detect_changes(compare main)` 完成。
## 7. 真实运行证据
- 保留默认 Anaconda 失败探针、固定运行时通过探针和产品 API child-failure 存活证据。
- 测试所有 APS_HOME、DB、world、日志输出重定向到临时目录;不写 `server/data/**`。
- 记录当前 MOM world hash,运行前后必须一致。
## 8. 风险与外部阻断
- GitNexus 对 `get_engine` 为 CRITICAL;本轮避免修改。
- 动态引擎分派会低估 `CpSatEngine.solve` / `HybridEngine.solve` 的上游影响,因此按高风险验证。
- 真正 Windows CI、干净 Win10/11、签名冻结产物、Defender 和客户机 DLL 环境需要外部基础设施。
- `.venv` 重建属于运行环境变更,不在未获授权时执行。
## 9. Pre-target-merge 停止点
实现、验证和独立审计完成后,先报告:协议覆盖、失败注入、健康求解等价性、运行时来源、现场数据保护、共享 dirty 基线和残余外部环境。未经用户明确授权,不执行 commit/merge/push/publish。
## 10. 计划可行性审计
PLAN AUDIT: PASS
Blocking issues: none
Clarification needed: none(persistent goal 已授权继续推进最高优先级本地项;默认采用 fail-closed、不静默 RULE 降级)
Non-blocking improvements: 真实冻结产物 smoke 取决于现有 artifact;若需重新打包则只记录为外部/后续构建证据。
## 11. 执行结果与验收收口
状态:实现与真实运行门禁通过;最终全量黄金计数待主 agent 在新增源码 Sidecar `-I` 回归后复跑确认。
### 已完成
- 子进程协议、request/response digest、fatal marker、OS timeout、进程树回收和运行时身份已落地,协议为 `aps.solver-process.v1`。
- CP/HYBRID 仅在 child 执行优化,父进程负责校验与物化;fatal/timeout 时均 `UNAVAILABLE`、0 PO/WO、不可发布,且无 RULE 静默降级。
- 复制 `world.json` 的 CP/HYBRID 均 `OPTIMAL`,1 PO / 10 WO / 10 operationSlots,`runtimeSafe=true`。
- 复制 MOM 数据的 CP/HYBRID 均可完成求解,但现有业务主数据阻断为 0 PO/WO、1 slot/1 conflict;不宣称完成现场主数据。
- fatal 注入后父进程与 8003 存活,业务世界原子;timeout 15.633 秒并回收 grandchild 进程树。
- 默认 PATH Anaconda 探针稳定拒绝;`.venv`、`.sidecar-venv` 各连续 5 次通过。
- 8003 PID `21576`、5173 PID `9808` healthy;浏览器 title/root 正常且无 warning/error。
- Node 60 passed、Web build passed、ruff/compile/node check/diff-check passed。
### 源码 Sidecar `-I` 补充发现
- 真实源码 `--solver-child` 在 Python `-I` 下最初因源码 import root 被移除而失败。
- 经 GitNexus LOW impact 门禁,仅 bootstrap 已解析的 trusted source root,并新增 `test_source_solver_child_entry_runs_under_isolated_python`。
- 修复后实际源码 Sidecar child 为 CP `OPTIMAL`、1 PO / 10 WO / 10 slots、协议 v1、`runtimeSafe=true`。
- 最新 focused 为 80 passed;最终全量黄金为 1029 passed / 2 warnings(300.23 秒)。
### 数据与发布边界
- `world.json` hash:`C6E7FF090719DB505AF5C3B0D8ADD376CBA7843D6E6982A68C1BA02C342052FA`;MOM hash:`6B64ADF6F29A43A518D89718610EE71FAD60E86D3F38CD5CFF26F1740CC53F7D`;JSON 业务源前后不变。
- `server/data/**`、`.env` 无 Git 状态;`master.db` 可能因 license `last_seen` 等运行元数据更新,不以文件 hash 证明业务不变。
- 现有冻结 EXE 仅旧 `--probe-child` 通过;未重建,故新 `--solver-child` 的打包验收仍是构建后停止点。
- 不提交、不推送、不合并、不发布。
### 后续 P0
全量 P2/P3 evidence 封套与审批迁移可靠性(token 摘要、损坏源 fail-closed、DB clock/TTL、CAS 并发)成为下一最高优先级本地项。
### 首轮独立审计修复
- 首轮 `AUDIT: FAIL` 指出成功响应摘要未被父进程验证,且非空请求可被伪造成 `OPTIMAL + 空 entries`。
- 父进程现强制重算 `responseDigest`,校验条目完整排列/业务字段、pipeline/status、可行解 objective/gap 与 operationSlots 覆盖;8 个新增负向用例全部通过。
- 源码冻结路由现按 `sys.frozen` 自动执行自身 `--solver-child`;旧 EXE 未重建,仍为构建后验收边界。
- 最终独立只读复审 `AUDIT: PASS`。
- 复审补强:请求先落自动清理临时文件并作为 child stdin,避免 Windows 管道背压绕过 deadline;slot 越界/订单产品错绑、错误 pipeline/status 均失败关闭。