Skip to content

[阻塞整改] PR #17 必须关闭重做:GUI 2.0 重构破坏升级兼容、数据安全与运行契约 #18

Description

@yltx

@ShiinaKuroko:PR #17 目前不是“再补几个测试、修几个小问题就能合并”的状态,而是一次把新功能、用户数据迁移、任务调度、Python 环境、Electron 文件权限、资源存储和更新机制全部塞进同一个 4.45 万行巨型提交的高风险重写。

审计结论很明确:当前 PR 应当关闭,不应继续在原分支上堆补丁;请完成系统性整改、拆分提交、补齐迁移和测试后重新开 PR。

这里批评的是工程交付质量,不是功能方向。可视化计划、舰队编辑、舰船库和 GUI 2.0 的方向有价值,但不能以破坏 v1.4.1 用户数据、改变既有任务语义、扩大任意文件读写边界和制造双重配置来源为代价。

关联:


一、必须立即修复的安全与数据完整性问题

1. Electron 文件 IPC 越过应用目录边界

electron/main.ts 的路径解析接受绝对路径,对相对路径也缺少可靠的 containment / traversal 校验。save-fileread-fileappend-fileresolve-app-pathopen-folder 因而可以触达应用受控目录之外的路径。

这不是“代码风格问题”,而是主进程文件能力暴露过宽。renderer 一旦传入恶意或错误路径,就可能读取、覆盖或追加任意可访问文件。

整改要求:

  • IPC 不接受任意绝对路径;
  • 明确每类文件允许的根目录;
  • 使用 path.resolve 后验证目标仍位于允许根目录内;
  • 拒绝 ..、盘符跳转、UNC 路径和符号链接逃逸;
  • 为路径穿越和越权绝对路径补充自动测试。

2. “原子写入”失败时可能先删除旧文件

当前替换流程在第一次 rename 失败后删除目标文件再重试。第二次 rename 如果仍失败,旧的有效计划/配置已经丢失。

受影响的不是临时缓存,而是:

  • 舰队方案;
  • 出征方案;
  • 旧计划迁移结果。

**整改要求:**失败时必须保留旧文件;不得以“先删目标再 rename”冒充原子替换。需要覆盖文件占用、权限错误、跨卷、rename 二次失败等测试。

3. 保存设置会静默删除未知嵌套 YAML 字段

ConfigModel.toYaml() 重建 emulatoraccount 节,只保留 GUI 已知字段。后端新增字段或用户自定义嵌套字段会在一次保存后消失。

**整改要求:**做深层合并,不是重建整个 section;增加未知嵌套字段 round-trip 测试。


二、v1.4.1 升级兼容性被破坏

4. 旧用户计划升级后从 GUI 中消失

v1.4.1 使用:

<appRoot>/plans

PR 改为:

<appRoot>/resource/user_battle_plans

启动过程没有自动迁移旧目录。“旧计划转换”是手工文件选择功能,不是版本升级迁移。

结果是文件还在磁盘上,但升级后计划列表、任务创建和部分任务组流程看不到它们。

**整改要求:**应用启动时执行可重复、可回滚、带版本记录的自动迁移;迁移前后保持计划 identity;不得要求普通用户手工找旧文件。

5. 删除 v1.4.1 正在使用的活动预设

PR 删除四个 活动20260730-*.yaml,同时移除 builtin_event_20260730,没有等价替代。规范允许清理的是未使用示例,不是当前发布版本的活动工作流。

**整改要求:**恢复资源,或提供新格式下等价的活动计划、模板映射和升级验证。

6. 旧 path-form 任务组没有真正迁移

规范要求把旧路径推断为 managedSource + managedFile,但当前主要是继续保留旧 path。旧文件路径又被移动或删除,已有任务组可能直接失效。

**整改要求:**提供真实旧版 task_groups.json fixture,验证加载、保存、执行、再次加载均兼容。

7. autoFleetFallback 被删除,第一舰队保护失效

当前仓库有明确兼容逻辑:后端不能自动重组第一舰队,因此自动编队计划需要回退到第二舰队。

PR 删除了:

  • TaskGroupModel.autoFleetFallback
  • import/export 保留逻辑;
  • queueLoader 回退逻辑;
  • 直接执行计划时的第一舰队保护。

已有任务组仍能解析,但字段被静默忽略,原本会安全切换到第二舰队的任务可能直接操作第一舰队。

**整改要求:**恢复兼容行为,或把旧字段显式迁移为 fleet_id: 2;禁止静默忽略。

8. 周常模板复用旧 ID,却执行不同任务序列

builtin_weekly 有 12 个计划;PR 变成 10 个,第一章从 1-5 改到 1-1,并删除 3-3、6-3 等阶段。

模板 ID 没变,因此旧任务组升级后会无提示地执行不同内容。

**整改要求:**改变语义就使用新模板 ID;或提供模板版本和明确迁移,不得复用旧 ID 偷换执行内容。

9. “刷胖次”同一索引被重新解释

旧索引对应 9-2、7-4、8-5、2-1;PR 第三个索引变成 8-2 周常方案。保存的 lootPlanIndex=2 会从 8-5 刷胖次静默变成 8-2 周常。

**整改要求:**保留旧索引语义,或执行显式索引迁移;不要让数字索引绑定到完全不同的任务。

10. 决战自动化旧配置被删除但没有等价迁移

旧配置包括:

  • auto_decisive
  • decisive_ticket_reserve
  • decisive_template_id

PR 将它们列为 legacy 并删除,但新 gui_settings.json 没有完整承接这些语义。加载/保存其他配置时就可能永久删除旧值。

**整改要求:**逐字段迁移,并在迁移前后验证行为等价;无对应能力的字段必须保留并提示,不能直接删除。

11. 部分 gui_settings.json 会吞掉有效旧配置

只要 automation 对象存在,迁移逻辑就可能把整个新对象当权威来源,即使它只有一个字段;随后 YAML 中其他旧字段又被删除。

**整改要求:**按字段优先级合并,不得按“对象是否存在”二选一。


三、计划和任务调度语义被改变

12. candidate-only 槽位被改成严格主候选

GUI 使用 name: slot.name ?? candidates[0],把没有顶层 name 的纯候选槽位强行提升第一个候选为主候选。

后端 #521 的语义是:无顶层 name 时,各候选是平等替代项。GUI 当前转换会改变约束和回退顺序。

**整改要求:**保留 name 缺省状态;建立 GUI→YAML→API→后端的跨仓契约测试。

13. 无限任务的逻辑身份和完成事件不一致

无限任务第一轮结束后,旧 task ID 被报告为 completed,然后使用新 task ID 继续。SchedulerBinder 却把 completed 当作整个逻辑任务终止,清理 cron/pending 状态。

可能出现:

界面/cron:任务已完成
实际:新 ID 下仍在继续执行

**整改要求:**区分“单轮完成”和“逻辑任务完成”,无限任务必须拥有稳定的父任务身份和可追踪的停止语义。


四、Python、后端和 CUDA 环境边界混乱

14. 外部依赖安装位置与外部后端运行路径不一致

外部仓库模式把依赖安装到 GUI 管理的 python/site-packages,但启动外部后端时又不把该目录加入 sys.path。安装可能报告成功,运行仍然 import 失败。

**整改要求:**依赖必须安装到用户选择的外部解释器/虚拟环境,或启动路径必须和安装目标严格一致。

15. 外部仓库无效时静默切回内置后端

外部仓库被移动、删除或结构不对时,当前逻辑可能恢复 managed site-packages 并启动内置后端,而不是明确失败。

这会让 UI 显示“外部模式”,实际运行另一个版本,制造极难排查的版本错配。

**整改要求:**外部模式无效必须阻止启动并给出明确错误;禁止静默改变后端来源。

16. CUDA 检测与实际后端启动使用不同 sys.path

后端启动会注入 GUI 本地 site-packages;CUDA 检测直接执行 Python,不注入同一路径。可能出现后端能 import torch,设置页却报告 No module named torch

**整改要求:**CUDA/OCR 检测必须复用后端启动的同一解释器、PYTHONPATH、DLL 路径和环境变量。

17. 外部后端依赖私有 monkey-patch 契约

启动流程假定 OCREngine.create.__func__Launcher.load_config 和 logger 内部结构固定,但外部模式只检查 autowsgr/server/main.py 是否存在。

**整改要求:**明确支持的后端版本范围,做能力检测;最好把兼容接口放到后端正式 API,停止由 GUI monkey-patch 私有实现。


五、资源、打包和更新机制自相矛盾

18. 内置舰船库升级后不会覆盖旧版本

运行时使用 copyDirNoOverwrite() 把打包舰船库复制到 userData。首次安装后,后续版本中的新版 manifest、SQLite、标签和立绘都会因为“文件已存在”而被跳过。

**整改要求:**按 manifest 版本升级,或使用版本目录 + 原子切换;不能一边把新版舰船库打进安装包,一边永远不更新已有用户副本。

19. 用户计划仍写在安装目录

舰船库正确放在 app.getPath('userData'),用户作战计划/舰队计划却写到 <exe-dir>/resource/...。安装在 Program Files 等受保护目录时可能无法创建或保存。

**整改要求:**用户生成数据统一迁移到 userData;安装资源目录保持只读。

20. 2.0.1-dev 与稳定更新频道没有隔离

版本是 2.0.1-dev,release workflow 默认却可能按非 prerelease 发布,updater 也没有明确 prerelease/channel 策略。

**整改要求:**开发版、预发布版和稳定版必须使用独立版本规范和更新频道,禁止 -dev 进入稳定 latest.yml

21. 更新检查失败被显示成“已经是最新版”

updater 异常返回 null,UI 把 null 当作无更新。网络错误、签名错误、channel 错误、latest.yml 错误都会被误报为成功。

**整改要求:**明确区分“有更新 / 无更新 / 检查失败”。

22. 更新安装前没有可靠停止后端

退出时只是 ChildProcess.kill() 并立即清空引用,没有调用系统停止接口、等待进程树结束或确认文件锁释放。

**整改要求:**先优雅停止,再等待,超时后终止完整进程树;必须测试任务运行中更新和 Windows 文件锁场景。


六、重构违反仓库现有架构边界

23. View 层直接承担 IPC、持久化和领域归一化

仓库文档要求 View → Controller → Model/IPC,View 不直接负责数据持久化。

FleetPlannerView.tsDecisivePlanView.ts 直接调用 Electron bridge,处理:

  • 舰船库读取;
  • 队伍计划列表;
  • 保存和覆盖冲突;
  • 当前文件 identity;
  • 设置读写;
  • 领域数据归一化。

**整改要求:**把持久化、领域规则和文件 identity 移回 Controller/Model/Service;View 只接收 ViewObject 并上报用户意图。

24. electron/main.ts 已经变成业务逻辑垃圾场

仓库文档要求 main 只负责窗口、IPC 注册和生命周期,业务逻辑拆模块并通过 Context 注入。

PR 却把设置迁移、计划解析、舰队归一化、资源更新、文件策略和大量业务流程塞进 electron/main.ts,单文件增加约 2300 行。

这不是“文件有点长”,而是权限边界、业务边界和测试边界全部被搅在一起。

**整改要求:**至少拆出:

  • secure file service;
  • plan repository/migration service;
  • team-plan service;
  • GUI settings repository;
  • ship-library service;
  • backend environment service;
  • updater service。

25. 旧模板系统仍在启动,但新导航基本不可达

TemplateControllerTemplateModel、旧 Template Views 和 DOM 仍保留并初始化,新主导航却没有完整入口。结果是两套计划创建系统并存,一套半不可达,却继续做 I/O 和注册事件。

**整改要求:**明确迁移策略:要么完整保留并接入,要么迁移数据后删除;不要留下半死不活的旧系统。

26. 架构文档没有随 4.45 万行重构更新

现有文档仍描述旧配置来源、薄 main、ViewObject 流、模板/任务组模型和旧资源布局。PR 只更新用户层说明,工程文档与代码已经不是同一个系统。

**整改要求:**新 PR 必须同步更新架构 ADR、存储边界、迁移状态机、IPC 权限模型、调度生命周期和发布升级流程。


七、当前 PR 的交付方式不可接受

当前 PR 有约 1095 个文件、44,553 行新增、4,245 行删除,并且只有一个大提交承载 UI、迁移、资源、运行时和发布机制。GitHub 上只有 GitGuardian,没有 build、TypeScript、迁移、设置持久化、打包资源或升级测试 CI。

这种交付方式使审查者无法可靠地区分:

  • 新功能;
  • 数据迁移;
  • 安全边界;
  • 资源替换;
  • 后端兼容;
  • 发布风险。

继续在这个 PR 上叠加补丁只会让问题更难审查。


要求的整改和重新提交流程

请关闭当前 PR #17。整改完成后,以新的分支和新的 PR 重新提交,并满足以下条件:

A. 拆分为可审查的提交或前置 PR

至少分离:

  1. 存储目录与迁移框架;
  2. 安全文件服务与原子写入;
  3. 计划/舰队模型和跨仓契约;
  4. 任务组和 scheduler 兼容;
  5. Python managed/external 环境;
  6. 舰船库资源与升级机制;
  7. GUI 视图;
  8. 更新/发布机制;
  9. 文档和测试。

B. 提供真实升级测试

必须覆盖:

  • v1.4.1 旧 plans
  • task_groups.json
  • autoFleetFallback
  • 旧周常/刷胖次模板;
  • 决战旧配置;
  • 未知 YAML 字段;
  • 活动 20260730 预设;
  • 重复执行迁移的幂等性;
  • 迁移失败回滚。

C. 建立 CI

新 PR 至少自动执行:

  • TypeScript/build;
  • legacy plan migration tests;
  • settings round-trip tests;
  • IPC path-security tests;
  • atomic-write failure tests;
  • scheduler lifecycle tests;
  • packaged-resource integrity;
  • managed/external Python smoke tests;
  • 与后端 #521 的 schema contract tests。

D. 提供手工验证矩阵

至少包括:

  • 从真实 v1.4.1 安装原地升级;
  • 默认安装目录和 Program Files
  • 中文/空格路径;
  • bundled/system/external Python;
  • CPU/CUDA OCR;
  • 外部仓库失效;
  • 任务运行中退出/更新;
  • 旧任务组实际执行;
  • candidate-only 舰队实际换船;
  • 活动入口和夜战路线。

E. 重新开 PR 时必须说明

  • 旧数据如何迁移;
  • 哪些语义故意改变;
  • 如何回滚;
  • 与后端最低/最高兼容版本;
  • 用户数据写入位置;
  • IPC 允许的文件边界;
  • 测试和手工验证证据;
  • 与本 Issue 每一项的对应整改结果。

最终结论

GUI 2.0 的方向可以继续,但当前 PR 的工程状态不能接受:它在没有可靠升级迁移、没有完整 CI、没有稳定跨仓契约、没有清晰权限边界的情况下,一次性替换了过多核心路径。

不要继续要求维护者在这个巨型 PR 上“边审边补”。请先关闭 PR,按上述要求完成整改,拆分并验证后重新提交。

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions