AskaEth
|
7521c47944
|
Merge branch 'feat/m216-coexistcore-shared-minecraft-no-sil'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 21:10:48 +08:00 |
|
AskaEth
|
aa61565fe7
|
fix(coexist): r2 返工——帮助写真实例路径 + 加载器安装也说出「原版清单被替换」+ --json 给出绑定成败(M216)
r2 复审两条 P2 必修 + 一条「能一行接上就接」的 P3。
P2-1(`ull help pack` 的落点少一层 `.ULL/data`)
- 「数据根」在这份帮助自己的定义里是「含 `.ULL/` 与 `.minecraft/` 的父目录」(`--data-dir` 那行),
所以真路径是 `<数据根>/.ULL/data/instances/<实例目录名>`。r1 写成 `<数据根>/instances/<实例名>`,
照读会去一个不存在的位置找包内容。
- help.rs 与 model.rs 的字段文档改成真路径,并点明目录名是归一化后的结果(`Better-MC`)。
- 用例改成 `help_describes_the_new_default_target_with_the_real_path`:先断言真路径整串在、
再断言 `<数据根>/instances/` **不在**(这一条就是抓「少写 .ULL/data」那种错法)。
P2-2(装加载器时原版那一步的「替换了清单」没人说)
- `loader.rs`:`plan.vanilla_install_needed` 那条路以前只拿 `vanilla_report` 判 `is_complete()`
就丢掉;现在把它留在 `vanilla_report` 里传给 `report_loader::build`。
- `report_loader.rs`:`build` / `human_lines` 各多一个 `vanilla: Option<&InstallReport>`;
新增 `vanilla_lines`(人读:`告警(原版): …` + `原版版本清单已替换(旧的那份备份在 …)`)与
`vanilla_json`(`--json` 的 `vanilla` 段:warnings / version_json / manifest{kind,backup})。
- 用例 `a_replaced_vanilla_manifest_is_said_out_loud_in_the_loader_output`:用真
`LoaderInstallPlan`(最小 Fabric,零网络)调 `human_lines`,断言告警与备份路径都在;
未先装原版时不多说话;`vanilla_json` 的 `kind`/`backup` 结构正确、`None` 时没有该段。
P3(复审「能一行接上就接」):`--json` 给出导入绑定的**结构化成败**
- core `pack_dir::bind_created_instance` 返回 `Option<Result<String, String>>`(None=不需要绑 /
Ok=已绑 / Err=绑失败),一句话就能分出成败,不必猜文案。
- CLI:`outcome` 多写一个 `"instance_bound": true|false`(人读行照旧给原因);
GUI:`import.rs` 按 Ok/Err 分别记 `instance_bound` / `instance_binding_failed` 注记。
- 端到端用例里补断言 `data.instance_bound == true`。
|
2026-11-09 20:43:46 +08:00 |
|
AskaEth
|
4e1323212b
|
fix(coexist): 复审 r1 返工——帮助文案跟上新落点 + 「替换了清单」进人读通道 + 导入建实例绑到内容目录(M216 P2/P3/§5)
P2-1 ② 改了缺省落点,CLI 自己的文档还在写旧行为
- `cli/help.rs`「目标目录缺省 = 数据根的共享游戏目录(.minecraft/)」→ 新实例目录
`<数据根>/instances/<实例名>`(并登记实例);
- `cli/model.rs` 的 `PackImportArgs::target` 字段文档同步。
- 用例 `help_describes_the_new_default_target`(断言帮助含新落点、不含旧落点)。
P2-2 ③ 的「显式说明」进不了任何人读通道
- CLI:`human_lines` 原只遍历 `plan.warnings`,而运行期告警(含「已替换版本清单 …旧的那份
备份在 …」)在 `report.warnings` 里 ⇒ `ull install` 人读输出从没说过这句话。现在有 report 就
报 report.warnings(它开头就是 plan 告警的副本),`manifest=Replaced` 再单独打一行备份路径。
- GUI:`install.rs` 新增 `Job::journal_report`(把 `report.warnings` 与 `manifest` 的备份路径写进
安装日志),`install/routes.rs` 两处 `ensure_complete` 之前各加一行调用(tower 批的两行)。
此前 `report.manifest` 全仓零消费者。
- 用例 `a_replaced_manifest_is_said_out_loud_in_the_human_output`(走真 `InstallPlan` 排版)。
P3-1 缺省目录被占的 hint 删掉不成立的 `--overwrite` 那半句(它绕过不了这条预检)。
P3-2 GUI 落点改成委托 core 的 `pack_dir::instance_dir`(此前 presenter 自己拼 `data()/instances/…`);
`convert::dir_name` 缩成 `#[cfg(test)]` 的委托入口(生产侧已无调用点,留着继续钉归一化判据)。
P3-3 补 `the_default_target_is_a_new_instance_dir_and_the_instance_is_bound_to_it`:不带 `--target`
跑通整条 CLI,断言落点、内容、登记、以及实例设置里的 `isolation`/`gameDir`。
§5 导入建出来的实例原先「导入成功但内容永远不会被启动用到」
- 新增 `pack_dir::bind_instance_dir`(`InstanceConfig.isolation = Custom` + `game_dir = 导入目录`)
与 `pack_dir::bind_created_instance`(判据+文案,CLI 与 GUI 同一份);
- CLI 在缺省建实例后、GUI 在 `import.rs::run` 里各调一次;只在「我们新建了实例目录且档案是新建」
时绑(显式 `--target` / `--no-register` / 复用已有档案都不动);
- 运行目录 = 导入目录(saves/mods/logs/config),**versions/libraries/assets 仍按 D20 恒共享**;
- 未改 `derive_game_dir`。
- 用例:正向 `binding_an_imported_instance_points_it_at_the_import_dir`、反向
`a_freshly_registered_instance_still_inherits_the_global_mode`。
|
2026-11-09 19:46:41 +08:00 |
|
AskaEth
|
96f15c79fb
|
fix(coexist): 多启动器共存——存储模式写回同源 + 整合包缺省落点对齐 GUI + 版本清单不再静默覆盖(M216)
三条 M215 判定的共存债,一次修完:
① 存储模式切不回来(high)
- 写侧:`host::settings::set` 老实现一律写 `env.store()`(= 解析后数据根那份),而启动解析
只读 `<exe>/.ULL/config/launcher.json` ⇒ Standard 下把模式改成 portable 写进了 `%APPDATA%`,
下次启动看不见,切不回来。
- 现在 `core.*` 四个数据根键一律落便携那份(判据收在 `settings_bridge::setting_path` 一处,
与 CLI `Ctx::setting_path_for` 同一契约),其余键照旧。
- 读侧:`settings_bridge` 删掉自己那份 `request_from_portable`/`DATA_ROOT_KEYS` 拷贝,改用
`host::env` 的那份(presenter 侧两份逐字拷贝合成一处,不新增真相源)。
- 设置页 `core.data_custom_root` 的显示值也读便携那份,否则改完输入看起来凭空消失。
② `ull pack import` 缺省落点与 GUI 不一致(high)
- CLI 老缺省 = `ctx.game().root()`(共享 `.minecraft` 根);GUI 导入页落在
`<data>/instances/<实例目录名>` 并登记。
- 新增 `core::game::install::pack_dir`(`pack_stem` / `instance_dir_name` / `slug` /
`instance_dir` / `import_target`):命名与落点只有一份实现;CLI 调它,presenter 的
`convert::{dir_name,pack_stem,slug}` 改成一行委托 core(对外签名不变)。
- CLI 缺省现在与 GUI 同源,并复用 GUI 那条「非空目录不复用」预检。
③ 装同版本静默覆盖 `versions/<id>/<id>.json`(medium)
- `write_version_json` 三档:不存在 → `Fresh`;逐字节相同 → `Identical`(一个字节都不写,
重装同版本照旧正常);不同 → 先 copy 到 `<dest>.ullbak-<pid>` 再原子写 → `Replaced{backup}`。
- `InstallReport` 新增结构化字段 `manifest: Option<ManifestWrite>`,并把「已替换版本清单 +
备份路径」推进 `report.warnings`(进 `--json` 的 `result.warnings`)。
用例(全部零网络,走真的 `execute`):三档各一条,各写抓得住/抓不住。
三个变异各跑一次(只改实现侧)都红:写回落点改回旧的、缺省改回 `.minecraft` 根、去掉备份只覆盖。
|
2026-11-09 18:42:41 +08:00 |
|
AskaEth
|
ab5b593708
|
Merge branch 'feat/m214-javacompat-java-version-distance-la'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 17:27:23 +08:00 |
|
AskaEth
|
ff78fe2cae
|
fix(core): 老版本 Java 按版本距离选 + 启动看门狗有界等待 + 结束归属(M214 D4/D6/D10)
D4:`pick` 的排序键从「来源序」改成「版本距离 → 来源序」,全不满足时选最接近要求的那个
(托管 25 与 JAVA_HOME/扫盘 17 之间选 17,而不是按 rank 选托管)。新增 `select::decide`:
不符且**没有人显式指定 Java** 时 `ull launch` 直接报 `java_mismatch` 不 spawn(`--java` /
实例 `javaPath` 是豁免,只告警)——1.12.2 配 Java 17/25 必然起不来,警告没有信息量。
`JavaMismatch` + `check` 拆到 `mismatch.rs` 并加 `advice()`(mapping.rs 408→282 行)。
D6:`LaunchSession::finish` 的等进程改成**有界**。判据在 `startup_watch.rs`(纯逻辑):
①窗口从未出现且**连续 N 秒没有任何日志**(还在打日志就说明还在加载,慢加载不被判死);
②日志出现关闭标记后 N 秒仍未退出(实机「关窗后 java.exe 仍存活」的形状)。
到点只**停止等待、绝不杀进程**:写 `session_end(still_running)` + 索引 + `launcher.log`,
返回 `LaunchError::Watchdog`,CLI 翻成 `launch_timeout`。两个时长可配
(默认 300s/60s;`ULL_LAUNCH_STARTUP_TIMEOUT_SECS` / `ULL_LAUNCH_SHUTDOWN_GRACE_SECS`,
生效值每次启动写进 launcher.log 与人读输出)。
D10:把「被终止」与「应用错误」分开。①我们自己杀的:`tree_kill` **送达即登记**
`kill_registry`(pid 登记簿,取走语义、上限 64),覆盖 GUI 从别的线程 `kill_tree` 的路径,
`finish` 据此判 `killed`;②外部强杀:Windows 上「退出码 1 + 零错误输出 + 无崩溃/JVM 关键字」
判 `killed` 并标 `terminated_externally`(任务管理器与 `taskkill /F` 都是
`TerminateProcess(h, 1)`,应用真崩给的是 NTSTATUS 异常码)。
顺带:`SessionHandle` 拆 `session_handle.rs`(launch_session.rs 422→343 行)、
`NativesReport::to_json` 落 core(cli/launch.rs 腾出空间,仍 399 行)。
用例:select 距离/取舍、startup_watch 状态机、watchdog_cases(4 条真进程)、
exit_cases(外部终止与「我们杀的」区分)、pipeline 跨线程 kill。
|
2026-11-09 17:07:22 +08:00 |
|
AskaEth
|
5959a27add
|
fix(core): child_quiet.rs 446 行越 400 红线,按职责拆成两个测试文件
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
M213 r2 把守卫的剥注释+两条新用例都塞进同一个文件,涨到 446 行。tower 的合并门里
400 行扫描算出了 OVER=1,但 push 写在扫描之后没有用它拦住自己,所以 dev 上先红了一次。
- child_quiet.rs(85):quiet 的**行为**测试(管道/退出码/env/错误语义)
- child_quiet_guard.rs(370):源码扫描型守卫(角色表 + 4 条变异对应的用例 + 解析 helper)
拆分后复验:两文件 5+4=9 条全绿;变异 D(discover.rs:156 改裸构造)仍 exit 101 并点名该行
——守卫的牙没在拆分中丢掉。
|
2026-11-09 15:14:23 +08:00 |
|
AskaEth
|
aa8c8f506a
|
Merge branch 'feat/m213-consolesilence2-quiet-child-process'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 15:08:49 +08:00 |
|
AskaEth
|
6dd88a27a3
|
fix(core): 守卫的剥注释不再吞文件 + 变异 D 变红(M213 复审 P1-1 / P2-1)
复审 P1-1:`code_lines()` 对每一行找 `/*`,找着就当块注释开头。本仓库的文档注释里到处是
glob(`/usr/lib/jvm/*`、`rules.d/*.yaml`、`crash-reports/*.txt`、`.github/workflows/**`),
字符串里也有(`"mods/*.jar"`),这些 `/*` 没有配对的 `*/`,于是 in_block 一旦为真就再也
不会变假 —— 该文件从那一行到 EOF 全部被吞掉。实测 crates/core/src 里 8/371 个文件中招,
其中一个是 game/java/discover.rs(它自己有一处 reg query),复审判的变异 D 因此全绿。
- code_lines 只在「去掉前导空白后以 /* 开头」的行上进入块注释态,行中/字符串里的 /* 一律
当普通代码。本仓库零处行首块注释,所以结构上不可能再吞代码。
- 新增 code_lines_is_not_fooled_by_glob_slashes_in_strings_or_docs:字符串里的 /* 不许
吞掉后一行、文档 glob 不许吞掉真代码、真块注释仍要被剥。
- 新增 the_scan_reaches_the_end_of_every_file:逐文件核对**最后一行代码的行号**必须出现在
扫面结果里(比数总行数尖锐 —— 文件普遍以 } 结尾,光比内容会被别处的 } 蒙混),外加
全仓可见行占比 ≥60% 的兜底(本机实测 81%)。
- 变异 D(discover.rs:156 改回裸 Command::new)现在 exit 101 并点名该行;A/B/C 复验仍红。
复审 P2-1:原注释说「两个 flag 不能叠加」,按 MSDN 这是错的。改成:两个 flag 在 Win32 层
可任意组合,真正的坑是 std 的 creation_flags 是 setter(后一次静默覆盖前一次)。结论不变
(launch/process.rs 仍零改动),顺带修正 DETACHED_PROCESS 的说明。
|
2026-11-09 15:07:56 +08:00 |
|
AskaEth
|
40a296471d
|
fix(core): 所有子进程统一走 platform::child::quiet + 角色守卫(M213)
GUI 父进程(PE Subsystem = 2)spawn 控制台子进程时,Windows 会为子进程新建一块
控制台——桌面上就是「黑框闪一下」。改前 crates/core/src 有 21 处 Command::new,
只有 tree_kill 那一处带了 CREATE_NO_WINDOW,于是探 N 个 JDK 就闪 N 次 java -version。
- 新增 crates/core/src/platform/child.rs:唯一构造器 quiet(program),Windows 上统一
.creation_flags(CREATE_NO_WINDOW),其它平台原样返回。tree_kill 里那份就地合并进来,
不再保留第二份常量。
- 20 处 spawn 全部改走它(region_system / java{discover,manual,probe,proc,termux} /
addon exec / platform{linux_caps,macos_version,theme_probe,vk_probe,windows_version,
wallpaper/io})。
- launch/process.rs 零改动:游戏进程要 CREATE_NEW_PROCESS_GROUP 撑 taskkill /T,
带不带控制台窗口是产品决定。
- crates/core/tests/child_quiet.rs:按角色判「有没有绕开构造器」的守卫,外加构造器
本身的 4 条行为用例。三条变异(裸 Command::new / 别名 Spawn::new / 删掉 creation_flags)
都实测变红并已还原。
|
2026-11-09 15:07:56 +08:00 |
|
AskaEth
|
bb8be87c8f
|
Merge branch 'feat/m212-agentlog-agent-and-mcp-log-diag-too'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 14:42:10 +08:00 |
|
AskaEth
|
fbb4a79955
|
fix(agent): MCP 读日志/诊断崩溃改走 session_index::event_file(D2 第三处读取点)
M212(M211 复审观察 7 确认的真 bug):
- `agent/ports/events.rs::event_path` 仍在 `root.join("game").join(&entry.log_path)`,
而 `log_path` 已经是**相对会话根**的 `game/<id>.jsonl` ⇒ 对每个真实会话解析成
`game/game/<id>.jsonl`。后果是 `ull agent` / MCP 的 read_game_log 与 diagnose_crash
两个工具对真实会话全报「读会话事件流失败」——内置 AI Agent 的看家本领是坏的。
现在解析只走 M211 建立的唯一真相源 `session_index::event_file`(新旧两个根都走它)。
- 夹具:`ports/session.rs`、`ports/diagnose.rs` 的索引夹具此前写裸文件名
`20261029T124501-9f3c2a.jsonl`,与生产差一层 `game/`,所以这个 bug 在测试里永远看不见。
改成 `session_index::log_path_for(id)` + `session_index::event_file` 落盘。
- 用例:`ports/events.rs` 新增三条(解析结果 = 写入侧落盘的那一个 / 文件缺失时指向的仍是
生产形状 / 旧根回落走同一套解析);`cli/tests/agent_mcp_stdio_test.rs` 新增一条真机
MCP 用例(起 `ull agent serve --stdio`,对一份生产形状的会话调 list_game_sessions →
read_game_log → diagnose_crash)。旧实现下两条路径都红,红的是
`...\logs\game\game\20261030T101500-abcdef.jsonl`。
|
2026-11-09 14:28:42 +08:00 |
|
AskaEth
|
994ffda626
|
Merge branch 'feat/m211-logreach-session-jsonl-path-double'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 14:14:14 +08:00 |
|
AskaEth
|
a91930e021
|
test(core): crash_scan 用例改用 slice::from_ref(clippy::cloned_ref_to_slice_refs)
|
2026-11-09 14:12:23 +08:00 |
|
AskaEth
|
4b30bf07f0
|
fix(launch): 会话事件流路径只留一个真相源 + crash_found 只认真实证据 + ull launch 也写 launcher.log
M211(来自 M208 实机 e2e 的 D2/D3/D5):
- D2「game/ 双写」:log_path 的契约原来有两种说法(字段文档写「相对 game/」,写入侧一直
写「相对会话根」的 game/<sid>.jsonl),于是两处读取点各自又多 join 一层 game/,
ull logs / ull diag / GUI 日志页对每个真实会话都报「找不到事件流」。现在解析只走 core 的
session_index::event_file(CLI 与 presenter 共用),写入侧只走
session_index::log_path_for。方向选的是「读取侧不再 join」:存量索引里的 log_path 从第一版
起就是 game/<id>.jsonl,改读取侧能把用户盘上已有的旧会话一起救回来(零迁移、零改写)。
- D2 夹具:cli/tests/support 与 presenter/host/logs_tests 的索引夹具改成生产形状
(game/<id>.jsonl)。夹具此前自己说了第三种形状,所以这个 bug 在测试里永远看不见——
修夹具比修路径更重要。
- D3「crash_found 恒为 true」:logs/latest.log 每局都写,不再被当现场文件(只在已经找到
权威现场时作为补充证据)。崩溃结论改由 crash_scan::has_crash_evidence 判定(权威现场文件
/ 非 0 退出 + 错误行),索引旗标由 SessionWriter::mark_crash 写入;crash_found 事件恢复
成纯事实记录。
- D5「ull launch 从不写 launcher.log」:现在与 GUI 共用
launcher_log::record_start / record_end(字段名与顺序只在一个地方定义),写的是同一份
<data>/logs/launcher.log。
测试:core 新增 session_path_tests(写入形状 = 读取解析结果 = 真落点的往返)、crash_scan 的
干净退出用例、launcher_log 的行格式锁;CLI 新增 a_launch_attempt_writes_the_launcher_log。
|
2026-11-09 14:12:23 +08:00 |
|
AskaEth
|
1748e046f8
|
fix(cli): progress.rs 测试 helper 的返回类型抽成 Recorder 别名
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
M210 引入 type_complexity(clippy 1.99 -D warnings)。M210 的 brief 里没有 clippy
这一项(五步门禁是它 spawn 之后才补上的),所以第一轮没抓到;新门禁在合并态立刻咬住。
|
2026-11-09 14:05:52 +08:00 |
|
AskaEth
|
96753c5b38
|
Merge branch 'feat/m210-mirrorfix-bmclapi-libraries-missing'
|
2026-11-09 13:59:58 +08:00 |
|
AskaEth
|
d8ca411117
|
fix(core): bmclapi 库镜像补 /maven;install 进度行改说实话
D1(high):mirrors.json 的 bmclapi-libraries 少了 inject,官方
libraries.minecraft.net/<path> 被重写成镜像根 + <path>(BMCLAPI 的 maven 仓库挂在
/maven 下),实测 70/70 个库全 404、全部回落官方源(单请求 EMA 6.3 s)。本轮实测:
guava:32.1.2-jre 无 /maven → 404 / 9 B;带 /maven → 200(2 次 302)/ 3041591 B,
sha1 与官方逐字节一致。补法与 bmclapi-assets-objects 的 /assets 同一机制(inject),
不把 /maven 拼进 to(to 是镜像根)。
守卫两道,抓不同的东西:
- 形态:凡 from 指向 maven 仓库的规则,to+inject 必须以 /maven 结尾(并点名覆盖的
5 条规则 id,删/改名都会红);
- 行为:4 个真实官方 maven 根展开后必须逐字等于 /maven/<子路径>。
原有 prefix_rule_preserves_sub_path 的期望值曾把 D1 的 bug 钉成期望,一并改正。
变异(删掉 inject)实测三条同时红,恢复后 mirrors 14 passed。
D11(low):进度回调只累加「处理到第几个文件」,还打「下载 {done}/{total}」,
重装同版本(全部已就绪)时会连打 3961 行「下载」而一个字节都没下。拆出
install/progress.rs:计数全部来自 FileOutcome.status(Downloaded/Skipped/Failed),
下载数为 0 时人读行不出现「下载」二字(改说「已就绪 d/t(跳过 N 个)」),--json 的
progress 事件补 downloaded/skipped/failed。用例构造 96 个全 Skipped ⇒ 断言每行都不含
「下载」且末行为「已就绪 96/96(跳过 96 个)」;变异(退回旧文本)实测红。
其它镜像表(assets / jar / version_manifest / 索引 / fabric / forge)的 inject 形状
实测一致,另有 liteloader 目标路径 404、quilt maven 无规则两条【不同类】观察,
不在本轮范围,报告里交回 tower。
|
2026-11-09 13:57:32 +08:00 |
|
AskaEth
|
391ebf88b7
|
fix(ci): 修两处 clippy -D warnings 红(needless_lifetimes / drain_collect)
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
两处都来自 M201(5dda9ac),非本轮 mission 引入。tower 合并门此前只跑
fmt+check+test,从未跑 clippy,所以 dev 上 CI 的 clippy job 一直是红的没人发现。
- presenter/host/settings/download_tests.rs: chosen 的显式生命周期可省略
- app-iced/.../download_tests.rs: drain(..).collect() -> mem::take
语义等价,纯 lint。门禁已补上 clippy(见 00-恢复指引 §6)。
|
2026-11-09 13:56:09 +08:00 |
|
AskaEth
|
413553d3ea
|
Merge branch 'feat/m207-uasweep-unify-user-agent-contact-in'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 13:15:58 +08:00 |
|
AskaEth
|
e3ae71ac37
|
M207: UA 收成一份带联系方式 + 两处字符串副本(gh_proxy 键、用例注释)
1) UA 只有一份定义:core 的 `download::http::USER_AGENT`
(`Unlucky-Launcher/<CARGO_PKG_VERSION> (+仓库 URL)`,不用邮箱)。
- 引擎侧:`EngineConfig::default().user_agent` 直接取它(config.rs 等量替换,仍是 400 行)
- 带 header 侧:presenter 不再持有 UA 字面量,`get_with_headers` 发 `engine.config().user_agent`
M205 复审核实过的缺口:Modrinth 收到的是引擎那份 `UnluckyLauncher/<ver>`(无连字符、无联系方式),
带联系方式的那份只发给 CurseForge 搜索 —— Modrinth 的 UA 政策在真正会被限流的路径上没被满足。
断言:core 一条钉形状(版本 == CARGO_PKG_VERSION、联系方式段 == 仓库 URL、不含 `@`);
presenter 一条在回环上抓**发出去的字节**(两条路径的 UA 逐字节相同且 == EngineConfig::default())。
2) `app-iced/.../java/downloads/engine.rs`:`"download.gh_proxy"` 字面量改成 `KEY_GH_PROXY`。
3) `presenter/src/host/addon/http.rs` 的用例注释改成事实(Modrinth 走 core 的 EngineTransport,
`get` 的无 header 分支生产零调用者),模块说明表格同步。
4) `docs/download-settings.md`:§6.1 / §6.1.1 / §7 按 M201(iced 真控件 + 快照通路)之后的现状重写。
|
2026-11-09 13:13:20 +08:00 |
|
AskaEth
|
a853986283
|
Merge branch 'feat/m206-badgesel-current-item-marker-contra'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 12:58:50 +08:00 |
|
AskaEth
|
b7a1abb18a
|
fix(m206): 「当前默认」改用选中的语汇,不再套禁用配色
Java 组用 `Chip::badge` 表示「哪个 JDK 是默认」,而 badge 走的是「无消息 ⇒ 禁用」
那条路(`OnSurface`@38% 叠 surface):M204 实测字/底只有 1.84:1(浅)/ 2.28:1(深),
成了整页最不可读的一段,而它正是「能不能改默认 Java」这个问题的答案本身。
同一张设置页里相邻的「下载」组表达「选中」用的是 `SecondaryContainer` +
`OnSecondaryContainer`(7.21:1)——两个相邻页面对同一个语义用了两套语汇。
改法(不动禁用语义):
1. 新增 `Chip::current(label)` + `paint::current_item`:**与选中 chip 同一套颜色**,
但没有消息、不吃五态(没有可点性就没有反馈)。它不等于「未选中」,也不等于
「禁用」——第三档,判据表写在 `components/selection/chip.rs` 的模块文档里。
2. `Chip::paint(&self, ui)` 成为「哪个档位 → 什么颜色」的唯一入口,`chip()` 用它求
静息色,断言也用它——档位的判据只有一处,「改回旧配色」的变异会被直接抓住。
3. Java 页的动作位抽成 `view::action_chip`,当前默认走 `Chip::current`,其余仍是可点
的 `Chip::filter(.., false, ..)`。
4. 下载组**不改**:它本来就是参照语汇(M201 已验收),改完两页逐字段同色。
断言(`pages/settings/java/tests/marker.rs`,浅深两档都跑):
对比度 ≥4.5:1(实测 7.21/7.21)、与下载组选中 chip 逐字段相等、以及反向断言
「禁用态仍走 38% 字 + 12% 容器且与当前项肉眼可分」。两条变异都在报告里复现过:
`action_chip` 换回 `Chip::badge` ⇒ 对比度断言红(只有 1.84:1);`Chip::current` 里
`marker: true` 改 `false` ⇒ 三条全红。
举证图(离屏 `--screenshot`,浅/深 + 改前/改后 + 下载组对照)与实测色值表见
`DevDocs/artifacts/m206/`(gitignore,不入库)。
|
2026-11-09 12:44:18 +08:00 |
|
AskaEth
|
46559910c9
|
Merge branch 'feat/m204-javagroup-java-runtime-settings-gro'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 12:11:26 +08:00 |
|
AskaEth
|
8a050c4402
|
fix(m204): 补「扫描中/还没扫描过/正在取消」三格种子 + try_lock 说明对得上代码
视觉复审 r1 判 p2-1items:六张举证图里「取消」全程是禁用态,「正在扫描… 3/7」
一格都没拍,而 README 却写成「两态」——证据覆盖不到结论(与 M201 同一个坑)。
本提交补种子与拆文件,不出产品行为改动。
- `pages/settings/java/seed.rs`:新增 `scanning` / `unscanned` / `cancelling` 三档。
三档都把快照写进 `ScanSlot`(新增 `ScanSlot::seed_report`)而不只改 `State.scanning`:
`clock.advance` 每拍用槽里的快照重算 `scanning`,只改 State 的话第一拍就被推回「没在扫」。
- `pages/settings/java/scan.rs`:`ScanReport` / `Job` / `ScanSlot` / `run_staged` / `rows`
从 `store.rs` 拆出(加 `seed_report` 之后那个文件 408 行,越过 400 行硬线);
`store.rs` 用 `pub use` 再导出 `ScanReport` / `ScanSlot`,既有路径一个都没改。
- `app/view.rs`:`note_screenshot_group` 的锁从 `lock()` 换成 `try_lock()`——代码复审 r1 指出
注释写着「拿不到锁就不打、不能把界面帧卡在锁上」,而实现用的是阻塞 `lock()`,只兜住中毒
不兜住等待。选 try_lock 而不是改注释:注释承诺的保证应由代码提供,不能靠「今天只有一个
调用点、锁里只有一次比较」这种会随改动失效的前提。
- `pages/settings/java/tests/cmdline.rs`:新增 `the_scan_seeds_cover_the_running_states`,
逐档钉 `State.scanning` 与 `scan.report().running`(后者才是「心跳下一拍会不会把它推开」)。
|
2026-11-09 12:09:02 +08:00 |
|
AskaEth
|
92ab3d4ddf
|
fix(m204): Java 组写盘成功后请 host 重发设置快照
`pages/settings/java/store.rs` 的 `save()` / `set_selected()` / `prefs_store::save()`
是页面自己的一次同步原子落盘(`SettingStore::set` 多个键 + 一次 `save()`),
不经过 `AppCommand::SetSetting` 的写盘分支;而 `CoreEvent::SettingsChanged`
全仓只有 `dispatch.rs` 的 `SetSetting` 分支与 `host/mod.rs::refresh_all` 两处发出。
于是三键写成功之后,`AppState::settings_groups` 里 java 组那几行会停在旧值,
直到下一次任何别的写入顺手带一份新快照回来。
修法用一条**动作键**(`dto::JAVA_REFRESH_ACTION` = `java.action.refresh`,
与 `download.action.detect_region` 同一族:不写盘、见到就返回 Ok):
写盘成功后派发它,`SetSetting` 分支照常重发快照。
不用 `RefreshAll`:那个除重发快照之外还会 `versions::spawn_list` 与
`start_update_check`——每点一次「保存内存」打两个网络请求(实测:一次真 host 出图
就在数据根里落了 `cache/meta/version_manifest_v2.json`),代价与动作不相称。
另外给 D30 §2.1 补一行自证身份的日志:`--screenshot` 且身份变化时打
`[shot] settings route=… group=… theme=…`(`view` 每帧都跑,所以只在变化时落一行),
免得再出现「拿外观页当下载组证据」那种图与结论对不上的事故。
守卫(`tests/java_refresh.rs`,4 条):
- 真 `CoreHost` + 临时数据根跑完整链:写盘 ⇒ 快照里 `java.extra_args` 真的变了;
- 被拒的保存不许请求刷新;
- 成功之后确实派发了那条动作键(键名写错就红);
- 「立即扫描」在 App 层真的接线(此前没有任何用例驱动 `Message::Scan`)。
|
2026-11-09 12:09:01 +08:00 |
|
AskaEth
|
a48d055118
|
Merge branch 'feat/m205-debtsweep2-gh-proxy-assertion-user'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 11:36:57 +08:00 |
|
tower-worker
|
67a03f9402
|
M205 r1: 修 config.rs 的假话与「最坏 9」的措辞,UA 说明改成事实
- download/config.rs: KEY_GH_PROXY 的「本文件没有断言把它与 schema 登记绑起来」是 M205 自己造出来的假话,改成指向 proxy.rs 的新用例(纯文字替换,400 行不变)
- addon/online/download.rs: 断言消息与边界段改说「出厂档 3x3 = 9;retry_limit 调到 N 时是 3xN(上限 10 => 30)」,别再让人把出厂值读成事实上限
- presenter/host/addon/http.rs: 「Modrinth 的请求走 get_via_engine」是错的(实际走 core::addon::online::EngineTransport -> download_bytes,带的是引擎 UA;get_via_engine 今天只有单测在用),改成事实
|
2026-11-09 11:20:27 +08:00 |
|
tower-worker
|
d4aa3b23f4
|
M205: gh-proxy 键绑定断言、外层重试预算判据、UA 版本号来源、下载设置文档更正
- setting/keys/proxy: 新增断言把 KEY_GH_PROXY 常量与 schema 登记绑起来(键名 / Text{max_len:256} / 默认空串)
- addon/online/download: RETRY_ATTEMPTS 与引擎每源次数是两件事(层级相乘、管的失败类型不同),写清判据并用两条编译期断言钉住 3 与 3x3=9 的边界
- presenter/host/addon/http: UA 版本号改为从 CARGO_PKG_VERSION 拼出,补断言钉住版本 == CARGO_PKG_VERSION 且联系方式段不丢
- docs/download-settings: §3.1 四条 CLI 命令都吃设置(update 是自更新路径),§6 补 M11-M13 三条 CLI 变异,§1/§7 里 gh_proxy 已合入的事实
|
2026-11-09 11:12:59 +08:00 |
|
AskaEth
|
4c29cbac3e
|
Merge branch 'feat/m202-settingsflow-iced-settings-data-pat'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 10:53:32 +08:00 |
|
AskaEth
|
5dda9ac31d
|
M201 r2: 修视觉 r1 的两条 P2 + 代码复审的坏值提示 p2
视觉 r1(fix-then-merge):
- P2-1 分隔线贴字:downloaded 组的行间距从 0 改成 `Space::X3`(12dp)。实测
说明末行→分隔线 1–3px → **12–13px**、分隔线→下一标题 4–5px → **12–17px**
(同族页 9–18px;同一把尺子量演示回退卡 16–19px)。不动 `frame::divider` /
`demo_card`;`spacing` 只作用于子节点之间,卡片首行顶距与末行底距仍是 20。
- P2-2 孤字「效」:`with_note` 把「原值 X 已按出厂 N 生效」从说明段落里挪到
**独立一行**(`\n` 分隔,渲染器按行断)。浅/深/窄三张复验:无 <20px 窄行。
两种措辞(出厂 8 / 已按 1)一字未改,presenter 侧新增 `ends_with("\n原值 …")`
断言把「独占一行」钉住。
代码复审 p2(非整数坏值被静默丢掉):
- `raw_int`(`Value::as_i64`)→ `raw_text`:文件里显式写过的原值转**上屏文本**
(字符串取原文不带 serde 引号、数字/布尔 `to_string`、`null` 当没写)。
于是 `"auto"` / `8.5` / `true` / `-4` 都能进那句提示,不再「画着 8 却说不出
用户写的 auto 被忽略」。
- 新用例 `every_bad_value_shape_says_what_the_raw_value_was`:负整数 / 字符串 /
浮点 / 布尔 / null 五种形态。
验证:fmt --all --check / check -p unlucky-app-iced --all-targets /
test -p unlucky-app-iced(845+6) / test -p unlucky-presenter(316) /
test -p unlucky-i18n(36),全部 EXIT=0。
|
2026-11-09 10:51:16 +08:00 |
|
AskaEth
|
ab573838f3
|
M201: 接通 iced 设置页的数据通路(下载组:真值 + 真控件 + 写回闭环)
M193 的用户投诉「设置页里改了不生效」的结构性根因是 iced 侧没有设置数据通路:
`CoreEvent::SettingsChanged` 在 `crates/app-iced/src` 零命中、`pages::Model` 没有设置快照、
`download::State` 是空 struct。presenter 那套设置组早就是真值来源(`host::settings::groups`
由 `refresh_all` 与 `dispatch` 的 `SetSetting` 分支发成 `SettingsChanged`),只是没有接到 iced。
这条线现在通了:
host::settings::groups → SettingsChanged → AppState::settings_groups
→ pages::Model.settings → settings::States.settings → 下载组控件
→ AppCommand::SetSetting → 写盘 + 重新发 SettingsChanged(闭环)
主要改动
- presenter `host/settings/download.rs`:并发/重试两行改成画**引擎生效值**(`DownloadTuning`),
文件原值与生效值不一致时写明「原值 X 已按出厂 N 生效」(P3-5,两个数都现读);
gh-proxy 行补「当前生效:跟随内置默认/已关闭/覆盖为 X」。
- app-iced `pages/settings/download.rs`:`GROUP.body = Some(body)`,按 `SettingControl`
画 choice(filter chip)/ text(输入框 + 应用)/ action(按钮)/ toggle / readonly;写回走
`app/update/settings/download.rs` → `AppCommand::SetSetting`(不做乐观回显)。
- `pages::Model.settings` + `settings::States.settings`:快照进页面的唯一通道。
- i18n:新增 `SettingsDownloadsApply`(金表锚点追加,`split_keeps_every_translation` 绿)。
- 取证:`ULL_SCREENSHOT_REAL_HOST=1` 让 `--screenshot` 接真 host(截图通道默认强制 mock,
而 mock 不发 `SettingsChanged`,真值屏拍不出来)。
测试(新增 11 条)
- presenter `download_tests.rs`:P3-5 的越界回落 / 0→1 / 无差异不出提示 + P3-3 的
gh-proxy 文件→三态端到端链。
- app-iced `app/update/settings/download_tests.rs`:假后端闭环 ×4 + **真 `CoreHost` 端到端**
(点 16 ⇒ 磁盘上 `launcher.json` 真的有 16)。
- `pages/settings/tests/download_section.rs`:`body.is_none()` 绊线翻成 `is_some()` +
控件形状 / 空快照不编值。
|
2026-11-09 10:51:16 +08:00 |
|
AskaEth
|
0cc1a35420
|
Merge branch 'feat/m203-clituning-cli-download-tuning-from'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 10:24:51 +08:00 |
|
AskaEth
|
7995f62908
|
fix(m203): 给第 4 条吃设置的路径(ull update)补打点,并清掉 KEY_GH_PROXY 的腐化 doc
复审 r1 P2-1(p2-1items / fix-then-merge):`ull update` 复用 `install::build_engine` 后成了第 4 条
吃设置的路径,但没有任何用例钉它——把 update 换回裸引擎,四条 download::tests 全绿。按 pack 的完全
同形接缝补上:
- `commands/update.rs` 新增 `pub(crate) fn engine(ctx)`(`job()` 改调它),doc 写明这条路径上并发
不可观测(自更新只下一个 artifact),随设置变的是每源重试次数与 gh-proxy 前缀;
- `download/tests.rs` 新增 `update_engine_takes_the_concurrency_from_the_setting_store`:
写 concurrency=4 ⇒ 断言 `config().max_concurrency` 与 `concurrency_probe()` 都是 4。
顺带收掉复审的三条观察:
1. 新增 `the_gh_proxy_setting_reaches_the_candidate_chain`:镜像优先地区档案下,出厂(空串 = 跟随
内置)确实生成 gh-proxy 候选;手写 `gh_proxy: "off"` 后候选里没有 gh-proxy(读的是 `plan()` 的
候选链,不是配置值;两段式,避免「本来就没有候选」的白绿)。
2. 测试写侧改用 `ctx.setting_path_for(KEY_CONCURRENCY)`(`ull config set` 用的那个表达式),不再与
实现读侧的 `launcher_config()` 共用同一个表达式——实现改读别的文件时用例会红而不是一起漂移。
3. `core/download/config.rs` 的 `KEY_GH_PROXY` doc 已腐化(写「暂未登记、M197 才登记、别删这条弱
断言」,而该键在 main 上就已登记、本文件根本没有针对该常量的断言):改成事实,并保留「弱在哪」
——常量值与 schema 登记没被断言绑在一起,改名只会让宿主侧静默退化成 Builtin。
变异验证(只改实现侧,逐次还原):
- update::engine 换回裸 `EngineConfig::default()` ⇒ 只有 update 那条红(left 8 / right 4),另五条绿;
- `download::tuning` 不读 `KEY_GH_PROXY`(传 None)⇒ 只有 gh_proxy 那条红(off 档仍有
`https://gh-proxy.com/...` 候选),另五条绿;
- `gh_proxy_override("")` 返回 Off ⇒ gh_proxy 那条的「出厂该套内置前缀」与回落用例的
`tuning == default` 同时红(两处都该红)。
|
2026-11-09 10:07:41 +08:00 |
|
AskaEth
|
b31ca636d1
|
feat(m203): CLI 三条下载命令改吃 download.* 设置(并发/重试/gh-proxy)
`ull install` / `ull java` / `ull pack` 此前各自 `DownloadEngine::new(EngineConfig::default(), …)`:
用户在设置里配的 `download.concurrency` / `retry_limit` / `gh_proxy` 一概不生效;M193 把出厂并发
从 64 改成 8 之后,它们还静默跟着降了。现在三处都经 `crates/cli/src/download.rs::engine(ctx)`:
- core:`shared/tuned.rs` 新增 `engine_with_tuning(cache_root, tune)` = `set_tuning` + `engine_for`,
由 `shared.rs` 重导出;`engine_for` 的签名与行为一字未改(presenter 的两步语义原样保留)。
放子模块是为了不动 `engine.rs`(那是 400 行零余量),`shared.rs` 仍 398 行。
- CLI:`download::tuning(ctx)` 用 `SettingStore::load(ctx.launcher_config(), launcher_schema())` 读
三个键(与 `ull config set` 同一份文件),默认值/夹取/回落在 `DownloadTuning::from_settings`;
键缺失或坏值静默回落出厂值,`engine()` 没有错误通道,安装不会因此中断。
- 顺带:`ull update` 复用 `install::build_engine`,也随之吃到设置。
- 测试:`crates/cli/src/download/tests.rs` 三条命令各一条(读 `config().max_concurrency` 与
`concurrency_probe()` 的真实值)+ 一条「缺失/越界/负数/类型不对 ⇒ 静默回落出厂值」;
`shared/tuned.rs` 两条守住「灌调优与取引擎是一步」。变异实测:pack 换回裸 `EngineConfig::default()`
⇒ 只有 pack 那条红(left 8 / right 2)。
|
2026-11-09 10:07:41 +08:00 |
|
AskaEth
|
c97630d59e
|
Merge branch 'feat/m204-cleanupdoc-debt-sweep-four-small-it'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 09:58:56 +08:00 |
|
AskaEth
|
8e37bb9cf9
|
docs(packaging): §12 文件清单里本文的节号范围补到 §11–§16
复审观察 1:那张表还写「索引 + §11 / §12 / §13」,而本文现在也有 §14/§15/§16(含本次新编的
§16)。是改动前就存在的陈旧,顺手补全。
|
2026-11-09 09:49:44 +08:00 |
|
AskaEth
|
3b5f95375d
|
docs(presenter): 「抓不住」那段别再点名 signature_cases.rs 管 manifest_sha256(复审 P2)
信封字段与钥表确实在 core/src/update/tests/signature_cases.rs;但 manifest_sha256 那条冗余校验
(signature.rs:161-171,要求「钥已钉住 + 方程验过 + 声明 sha 不同」)全仓没有任何用例覆盖,
现有篡改用例都先死在方程上。原文把这一项也推给那个文件,等于把责任推给了不存在的守卫。
改成如实写「全仓无用例,已立 finding」。只动 doc,用例逻辑与断言未改(394 → 395 行)。
|
2026-11-09 09:49:41 +08:00 |
|
AskaEth
|
ba96a4fc94
|
docs(presenter): 更正 ATTEMPTS 注释里「拿不到宿主设置」的假话(复审 P1)
原文把「没读设置」写成了物理限制。事实:get_with_headers 手里就有 env,第一句 engine(env)
已经走过 apply_tuning(host/settings/download.rs → core/download/shared.rs),用户当前值就在
engine.config().max_attempts_per_source。这条路径之所以恒为出厂默认,只是因为它没读那个字段。
只改注释,行为不变(仍是编译期 const)。同时把不对称写清:另一条无 header 的路径走引擎,
本来就跟随 download.retry_limit(源冷却中时引擎给 1 次,那是恢复探测)。
|
2026-11-09 09:49:36 +08:00 |
|
AskaEth
|
133f14ba0b
|
test(presenter): 坏清单用例按错误分类区分「JSON 坏」与「签名被拒」
台账 §10 第 7 条 / M199 复审 P3-2:原来只断言「结果是 Failed」,而它喂的每一份清单都签对了
——验签一旦 fail-open 或签名错误被误分类,这条用例照样绿。
现在两组各钉自己的分类:
- 内容坏(5 个变体,签名都对):kind 必须是 invalid_manifest / invalid_version;
- 字节好、只有签名对不上(放行就会报「有更新 0.9.0」):kind 必须是 signature_invalid 且不可重试。
变异证明(只改实现侧,跑完已还原):
- A core::update::signature::verify_manifest fail-open → ② 分支红,实际 Available(0.9.0);
- B core::update::manifest::from_json 把解析失败误分类成 SignatureInvalid → ① 分支红,
「not json:坏内容分类是 signature_invalid」。
两个变异各只打红对应分支,说明两类坏输入确实各走各的分类。
文件仍在 400 行红线内(394 行)。
|
2026-11-09 09:32:58 +08:00 |
|
AskaEth
|
06fcbb8a51
|
docs(packaging): 修掉两个 §14 撞号(M190 让位到 §16)
台账 §10 第 5 条:packaging/README.md 里 §14 被 M169(更新链路测试通道)与 M190(产物署名与
清单签名)各用了一次。
判据(谁后插进来谁让位):§14 与 §15 都被仓库里的引用钉着——packaging/check_test_source_off.py
引 §14(M169),.github/workflows/release-build.yml 与 crates/app-iced/src/platform/win32/console.rs
引 §15(M196);这两处引用都在本 mission 的 scope 外。所以 M169 保持 §14、M196 保持 §15,
M190 那一节整段后移到文末编 §16,内容一字未改,只改编号与位置(新增一段「编号来历」说明为什么
它让位)。全文 §11/§12/§13/§14/§15/§16 各一次,无重复。
|
2026-11-09 09:32:54 +08:00 |
|
AskaEth
|
9163054d46
|
refactor(presenter): 带 header 的请求重试次数改引 core 的单一来源
台账 §10 第 4 条:http.rs 的 const ATTEMPTS: u32 = 3 是「数字 3 的第二处副本」。
改成引用 unlucky_core::download::config::DEFAULT_MAX_ATTEMPTS_PER_SOURCE(download.retry_limit
的 schema 默认值同源),core 侧一个字未改。
这条带 header 的路径(只有 CurseForge 用)拿不到宿主设置,所以只吃出厂默认——这一点写进了
doc 注释,免得下一个人以为它会跟随用户的 download.retry_limit。
|
2026-11-09 09:32:51 +08:00 |
|
AskaEth
|
56402d97a1
|
Merge branch 'feat/m201-cfgkeyring-compile-time-strip-of-th'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 08:37:18 +08:00 |
|
AskaEth
|
50d13d26ab
|
fix(m201): 测试钥改由 #[cfg] 编译期剔除,无测试档产物里不再留那段字节
`keyring()` 原先用 `cfg!(...)` 判断要不要并入测试钥 —— 那是「运行期值为假的分支」,
`TEST_ONLY` 的字节照编进产物(实测:无 feature 的 dev / release 两档 core rlib 里,
测试公钥十六进制与 key_id 都能 grep 到)。
* `TEST_ONLY` 拆成两条同名 const:`#[cfg(any(test, feature = "update-test-source",
feature = "update-test-key"))]` 的真条目 + `#[cfg(not(any(...)))]` 的空表;
`keyring()` 删掉 `cfg!` 判断,改成无条件 `extend_from_slice(TEST_ONLY)`(空表那档等于
什么都没并,`mut` 两档都有用武之地,不需要 `unused_mut` 口子)。
* 文档把 M199 降级掉的那句强断言改回真话,并贴出六行实测读数(dev / release 均 0 命中,
带 `--features update-test-key` 的对照臂 2 命中)。
* 明确划界:读数只到 rlib 为止;最终 exe 经 `lto = "fat"` + `strip = "symbols"` 之后是否
仍有那段字节**未验**。
验收:cargo fmt --all --check → 0;cargo clippy --workspace --all-targets -- -D warnings → 0;
cargo test -p unlucky-presenter → 311 passed / 0 failed(1 ignored);
cargo test --workspace --all-targets → 0。
|
2026-11-09 08:19:04 +08:00 |
|
AskaEth
|
de41e4c3b1
|
Merge branch 'feat/m200-presenterfix-sign-manifest-fixtures'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 07:56:36 +08:00 |
|
AskaEth
|
540b82e197
|
fix(m200-r1): 文档降到实测能撑的程度 + 守门收紧 core 声明内容(复审 P2-1 / P3-1)
复审 reviewer-presenterfix-r1 的 fix-then-merge 两条,逐条落地。
P2-1(a)「两档」措辞被本次提交自己作废 ⇒ 三处全改成三档:
* `crates/core/src/update/trust.rs` 模块文档(读者最先看到的那句,上次漏了)
* `crates/core/src/update/tests/support_sign.rs` 模块文档 + `TEST_SIGNING_SEED` 的文档
P2-1(b)「三者都进不了发布产物」被复证实测证伪(`cfg!()` 是运行时假分支,不是 `#[cfg]`
编译期剔除;无 feature 的 dev 与 --release 两档 core rlib 里公钥十六进制与 `test-only-key` 都在)
⇒ 降级到事实能撑的断言:「不会被 `keyring()` 并进信任表,正式构建永远不认这把测试钥」,
并明写「字节仍编在产物里」「字节级不在产物里未证、也未声称」「换 `#[cfg]` 是另一条结构性改动」。
没有换成 `#[cfg]`(那是复审建议另开的 mission)。
P3-1 守门对 core「自己的声明」只认形状 ⇒ 收紧为只许空数组:
`DECLARATION` 从 `^\s*update-test-key\s*=\s*\[` 改成 `^\s*update-test-key\s*=\s*\[\s*\]\s*$`,
并加一条自检向量(core 声明里偷偷带子 feature ⇒ 必须红)。`--self-test` 从 6 条变 7 条
(5 红 / 2 绿)+ 基线,`packaging/README.md` 的计数同步。
⚠ 一个自己踩出来的坑,已被既有守门抓住:收紧时我先在 `check_test_key_off.py` 里把那条更宽的
测试通道 feature 写成**字面量**(自检向量 + 文档表格),`check_test_source_off.py` 立刻红
(`packaging/**` 按字面扫,这次提交前它一直是绿的)。改法照该文件自己的声明办——用
`OTHER_FEATURE = "-".join(("update", "test", "source"))` 拼出来,源码里字面量为 0,
自检向量仍然构造出完整字符串并必须判红。
验证(均在最终树上):`cargo test --workspace --all-targets` 0、`cargo fmt --all --check` 0、
`check_test_key_off.py --self-test` 0(7 向量)/ 正式扫描 0、`check_test_source_off.py` 0、
`check_workflows_yaml.py` 0。
|
2026-11-09 07:56:33 +08:00 |
|
AskaEth
|
528f5706a0
|
docs(m200): 补 update-test-key 的两处文档一致性(cherry-pick 之外)
`80b291c` 原样落盘(与本提交前一个 commit 逐字节相同),这里只补两处**文档一致性**,
不改任何判据、不改任何 feature 行为:
1. `crates/core/src/update/trust.rs`:`TEST_ONLY` 的文档注释还写着「**只**在 `cfg(test)` 与
`update-test-source` 构建里存在」——同一文件里 `keyring()` 新增的表已经把三档(`cfg(test)` /
`update-test-source` / `update-test-key`)讲清了,上面这句变成了自相矛盾的旧口径。改成三档,
指向 `keyring()` 的说明。
2. `packaging/README.md` §12「文件清单」:新守门 `packaging/check_test_key_off.py` 已接进
`release-guard.yml`,但清单表里没有它(同类守门 `check_test_source_off.py` 在表里),补一行。
验证:`cargo test --workspace --all-targets` exit 0(311 passed)、`cargo clippy --workspace
--all-targets -- -D warnings` exit 0、`cargo fmt --all --check` exit 0、
`packaging/check_test_key_off.py`(自检 + 正式扫描)exit 0、`packaging/check_workflows_yaml.py`
exit 0。
|
2026-11-09 07:56:33 +08:00 |
|
AskaEth
|
19cc5c7950
|
fix(m190): presenter 更新链路用例补上清单签名(6 条既有测试回绿)+ 测试公钥守门
背景:M190 把「验签」做成了强制且唯一收口(`source::verified_manifest`,先验签再解析),而
`crates/presenter` 的更新链路用例只写 `manifest.json`、不写 `.sig` —— 它们写在验签之前,所以在
dev 上红成 6 条 `signature_missing`。真发布本来就必须带 `.sig`,所以修的是**夹具**,不是判据:
没有放宽任何断言、没有在测试里关掉验签、没有把 `available=true` 改成「接受 Failed」。
夹具(`crates/presenter/src/host/update_tests.rs`):
* `write_manifest()` 把清单**连签名**成对落盘(`dist/stable.json` + `dist/stable.json.sig`),
`dir_source()` 改走它;三处直接写清单的用例(坏清单 / 空清单 / 缺本平台产物)也改走它。
* 签名用 `TEST_SIGNING_SEED` + `key_id = test-only-key`,信封形状与 core 的
`tests/support_sign.rs` **同源**(文件头写明「改一处要改两处」;presenter 不依赖 core 的
`#[cfg(test)]` 资产,也没把测试资产导出成生产 API)。
为了让这把**公开**测试公钥在 presenter 的测试构建里被认得:
* core 新增 feature `update-test-key`(**只**把 `trust::TEST_ONLY` 并进信任表,不开任何数据源开关);
* 只有 `crates/presenter` 的 `[dev-dependencies]` 打开它(库/发布构建拿不到);锁里只多
`ed25519-dalek` 这一行(presenter 的 dev-dep,包已在锁里,无新包、无版本变动);
* 新守门 `packaging/check_test_key_off.py`(238 行,带 6 条变异自检)接进 `release-guard.yml`:
core 的 `Cargo.toml` 只许自己的声明、presenter 只许 `[dev-dependencies]` 段内,其余(workflow /
packaging 脚本 / 根 Cargo.toml / cargo 配置)出现即红 —— 这把钥进发布构建就等于「谁都能签出官方更新」。
* 没有用 `debug_assertions`/`cfg(test)` 旁路:那会让「测试构建的信任表和发布构建不同」变成一句注释,
而不是一条门禁。
|
2026-11-09 07:56:32 +08:00 |
|
AskaEth
|
1f2d89c23e
|
Merge branch 'feat/m199-javaguard-auto-scan-and-proc-kill-g'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 07:20:18 +08:00 |
|
AskaEth
|
aae9a233ff
|
fix(m198): JAVAGUARD——首屏自动扫 / 子进程回收 / 参数长度三条守卫 + 设置页正文结构断言
- 首屏自动扫:删掉 `cfg!(test)` 分叉,生产入口在测试里真跑;新增
`the_first_hydrate_starts_a_real_scan_by_itself`(删掉那一行、或把实参翻成 `true`
都红)。顺手删掉恒为假的 `seeded && !live`:截图种子档在上方就 return 了,编译器也
报 "assigned but never used"。
- proc:`kill()` + `wait()` 收进 `terminate()`(三个分支共用);用例先用同一条命令
真跑一次证明子进程起得来(`spawn().ok()?` 的空跑假绿),并新增 Linux/Android 的
僵尸回收用例(只 kill 不 wait ⇒ `/proc/<pid>` 留成僵尸)。
- java.extra_args:`MAX_JVM_ARGS = 1024` 镜像 schema,`Refusal::JvmTooLong` + 中英
文案 + JVM 输入框变红;App 级用例断言回执是具名的「太长」而不是通用的「写不进去」。
- 设置页正文:判据①从「数 `stack = stack.push(` 的出现次数」升级成按角色断言
(唯一来源 section_columns、`push(` 恰一次且实参是循环变量、尾表达式、禁
extend/chain/once/concat/append),单行折叠变异体现在会红。
|
2026-11-09 06:46:29 +08:00 |
|