AskaEth
|
1004af81ca
|
fix(download): 同源换表示时不再沿用上一份的校验器 + 三条 p3 收口(M293 r2)
复审 r2(p2-1):r1 的 `same_source` 只挡了「源不同」,没挡「同一 URL、换了一份表示」。
`done` 为空(Restart:`SizeChanged` / `HashChanged` / `ChunkLayout` …)时没有任何字节要接着用,
但 `journal.begin` 仍把上一份表示的 ETag 写进**新一轮**元数据;`record` 又只在缺值时写 ⇒ 本轮
3 段(新内容)配的是旧校验器 ⇒ 重试拿它发 `If-Range` ⇒ 服务器按 RFC 7233 回 200 + 完整正文 ⇒
`RangeUnsupported` ⇒ 丢整份 ⇒ 单连接从头下。实测 113664 字节(2.775×),比「干脆不续传」的
72704(1.775×)还多一整份。
修法:「谁能当校验器」收成一个 `carry`,两个维度缺一不可 ——
let carry = if done.is_empty() { None } else { same_source };
`if_range`、`etag`、`last_modified` 三处全部走它。清掉之后本轮响应里的真实校验器由
`ChunkJournal::record` 填进来;最坏也只是 `NoValidator` ⇒ 整份重下(1.775× 基线),不会更坏。
文档(`chunk_resume` 模块头的「校验器跟着源与表示走」、`record`、`carry` 处)同步改成两维口径。
判据(新用例 `chunked/resume.rs::stale_validator_from_the_previous_representation_is_not_carried`):
同一 URL、盘上是上一份表示的元数据(旧总长/旧段区间/旧 ETag `"v1"`/旧摘要)⇒ 本轮因
`HashChanged` 走 Restart、服务器此刻发 `"v2"`。断言:重试的 `If-Range` 只许是 `"v2"`、字节数
41984(1.025×,而不是 113664)、hits 5、resumed_from 30720、终态文件逐字节正确。
变异(两种,均实测红):
* 只把元数据那两行退回 `same_source`(`if_range` 仍按 `done` 门控)⇒ 复现复审的探针数字:
PROBE stale_validator if_range=["\"v1\""] hits=6 bytes=113664 resumed_from=0,
断言红在 `left: ["\"v1\""] right: ["\"v2\""]`;
* 把 `carry` 整体退回 `same_source`(连带首轮也发 `If-Range`)⇒ 红在
`left: ["\"v1\"","\"v1\"","\"v1\"","\"v1\""]`。
三条 p3:
* p3-1 `Last-Modified` 支路端到端补上了:夹具新增 `last_modified` 字段 / `with_last_modified()`
/ 发出该响应头,`validator_ok` 扩展成「ETag 或 HTTP-date 比得上」(RFC 7233 的弱校验器兜底)。
新用例 `chunked/last_modified.rs`:服务器**只给** Last-Modified,重试必须带这个日期发
`If-Range`、只补 1 段(41984 = 1.025×)。变异(`record` 不写 last_modified)⇒ 红在
「只该在重试那一次发条件请求:[]」(变成 NoValidator 干净重下)。用例同时写明它抓不住真实
服务器的 HTTP-date 比较语义。
* p3-2 `ChunkJournal::save` 的同步原子写:判断「可以接受」并给了量级 —— 一个作业 ≤17 次
(16 段 + 建账)、每次 1–3 KiB,SSD ≈ ≤30 ms / 网络盘 ≈ ≤1 s,摊在分钟到小时的作业上占比
1e-4 量级;且顺序有意义(record-then-trust),挪 `spawn_blocking` 要为几毫秒引入跨 await
加锁的顺序风险;与单连接路径同口径。退路写在文档里。
* p3-3 `integrity=None` 时末尾摘要空转的一致性边界写进 `chunk_resume` 模块头「已知代价」+
`verify_part` 文档:只剩旧段自证与 `If-Range` 两道,「同长度换内容」不可察觉;现实入口是
上游没给合法 sha1 的 `ModFile`(`addon/online/download.rs` 只在 sha1 合法时才设 integrity),
以及 `Library` 元数据 / `download.bin` 缓存等无摘要调用点;要收紧属于产品决策。
字节判据复测(三条探针形态):
healthy bytes=41984 size=40960 ratio=1.025 hits=5 resumed_from=30720
nofeature bytes=72704 size=40960 ratio=1.775 hits=8 resumed_from=0
staleval bytes=41984 size=40960 ratio=1.025 hits=5 resumed_from=30720 if_range=["\"v2\""]
即 stale-validator 那条从 2.775× 回到 1.025×,healthy 档未变差。
门禁:lib 903 / engine_mock 17 / chunked 22 / resume 12 / partfile 7 / cache 6 全绿;
clippy -D warnings 0、fmt --check 0。改动的单文件最大 391 行(chunked_test.rs),无超 400。
|
2026-11-13 06:59:58 +08:00 |
|
AskaEth
|
ffca0f4454
|
fix(download): 换源后不再沿用旧源校验器 + 三条 p3 收口(M293 r1)
复审 r1(p2-1)指出的链条:源 A 落 3/4 段失败 ⇒ 元数据 url=A + A 的 ETag;换源 B 时
`decide` 判 UrlChanged、删盘重来,但 `ChunkJournal::begin` 把 A 的校验器原样写进 url=B 的
新元数据,而 `record` 只在 `etag.is_none()` 时写入 ⇒ B 的真实校验器永远盖不上。于是拿 **A 的
ETag** 对 B 发 `If-Range`:按 RFC 7233 §3.2 服务器回 200 + 完整正文 ⇒ `fetch_chunk` 判
RangeUnsupported ⇒ 丢掉整份 ⇒ 单连接从头下 —— 债务以另一条路径复现。
修法(复审给的选项 A):校验器与**记下它的 URL** 绑定,`same_source = previous.filter(|m|
m.url == candidate.url)`,`etag` / `last_modified` / `If-Range` 三处全部走它,让「谁能当校验器」
只有一个判定点。不选 B(换源时清空)是因为 B 只是 A 在「URL 变了」这一特例上的写法,条件放在
读取处比散到写入处更难漏;不选 C(多源各记一份)是因为 `ResumeMeta` 本来就有 url,再加一个来源
字段会多一处可与 url 漂移的状态。
夹具此前不解析 `If-Range`,这一跳只能读代码推。现在 `support/` 记录每个请求的 `If-Range` 取值,
并按 RFC 7233 §3.2 处置(对不上就忽略 Range、回 200 + 完整正文),于是「真实服务器上的后果」也
进了用例。
判据(新用例 `chunked/switch.rs`,两个夹具服务器当一个官方源 + 一个镜像源):
* 源 B 收到的 `If-Range` 只许是它自己的 `"src-b"`(源 A 同源续传照旧带 `"src-a"` 作对照);
* 源 B 的字节数仍是 3 段 + 卡住的 1KiB + 补 1 段 = 41984(1.025×),换源没把同源续传打回整份重下;
* 最终文件逐字节正确;
* 第二条用例走「新源自己不发 ETag」那一支:`NoValidator` ⇒ 新源重试整份重下(`resumed_from=0`、
8 个请求、72704 字节),一个条件请求都不许发 —— 这是「无法证明就不复用」的明确取舍。
变异(去掉 URL 过滤)⇒ 两条都红,实测原文:
assertion `left == right` failed: 源 B 收到了不属于它的校验器
left: ["\"src-a\""] right: ["\"src-b\""]
新源没有任何校验器,就不许发条件请求(带 A 的值更是串源):["\"src-a\""]
同一变异下还实测到了后果:`b_bytes=113664 b_hits=6 resumed_from=0`(200 白下一份 + 单连接整份
重下 = 2.77×),即复审预测的那一跳确实存在。
三条 p3 一并收口:
* p3-1:`abort_all()` 之前用 `try_join_next` 把「已落完但还没被收走」的段记进账
(`absorb_completed`,总长判据与正常回收同一道)。这是尽力减少浪费、不是正确性前提,且
它是「谁先完成」的竞态 ⇒ 没有确定性用例能钉住,已在文档里写明边界。
* p3-2:`verify_chunks` 改为返回 `Option<u64>`(可复用字节数),`AttemptOk.resumed_from`
直接取它,删掉原来「记录另算一遍」的双份口径。
* p3-3:`chunk_resume` 模块头新增「已知代价」:重读量正比于**复用的字节**(不是整份文件)、
为什么不能用「相信摘要」来省掉、以及超时后 `spawn_blocking` 线程不可取消在 Windows 上的
句柄风险。
拆文件守 400 行硬线:`await_chunks` + `absorb_completed` 移到 `transfer_chunk/join.rs`
(逐字节搬移),`MockResponse` 移到 `support/response.rs`(逐字节搬移,只加 `with_etag` /
`without_etag` 两个构造器)。
门禁:lib 903 / engine_mock 17 / chunked 20 / resume 12 / partfile 7 / cache 6 全绿,
clippy -D warnings 0、fmt --check 0;字节判据复测未变差(resume 41984 = 1.025×,
noresume 72704 = 1.775×)。
|
2026-11-13 06:45:55 +08:00 |
|
AskaEth
|
3ade2d14a8
|
feat(download): 分块路径写续传元数据并自证旧字节(M293)
台账 P21 第 2 条 + P19(b):分块路径此前进来先 `part.discard()`、失败也 `discard()`,
188MB 的 JDK 任何一次失败都从 0 重下;M289 又给预算耗尽加了「不再重试、不再换源」的
短路,慢链路上一次晚到的失败就变成「这个文件下不来」。
写这一侧:
* `ResumeMeta` 新增 `chunks: Option<Vec<ChunkRecord>>`(段区间 + 该段 SHA-1),
由新增的 `chunk_resume::ChunkJournal` 每成功回收一段就原子落一次盘;
* `fetch_chunk` 顺带带回段摘要、`Content-Range` 总长与 ETag/Last-Modified,
所以元数据里同时有 total / 源 URL / 分块清单 / 校验所需信息;
* 分块失败不再丢中间文件,只有 `RangeUnsupported`(单连接接不住这种形状)才丢。
判这一侧:
* `chunk_resume::decide`(纯函数)要求策略允许、URL / 总长 / 摘要 / 校验器全对得上,
且每条记录精确命中本次分块规划里的一段,否则从头再来;
* `chunk_resume::verify_records` 按记录重读旧段、重算摘要:任何一段对不上就整份丢掉
(元数据本身已不可信,同批其它记录也不再被信任);
* 续传的段请求带 `If-Range`,让服务器回答「内容变了没有」;
* 整文件摘要照旧在末尾整份重算,覆盖新下 + 复用的全部字节。
单连接路径零改动:`resume::decide` 的判定一字未动,只新增
「元数据里带分块清单 ⇒ 强行判 Restart 交给分块路径」这一条,避免预分配全长文件被
单连接的 `Complete` 误判成「已下满」。
判据(都是「服务端实际吐了多少字节」,不是「最终 hash 对」):
* 3/4 处失败后续传完成:41984 字节 / 40960 文件 = 1.025×(断言 < 1.5×);
* 变异(关掉续传同样夹具):72704 = 1.775×,`< 1.5×` 必红(已实测);
* 变异(去掉旧段摘要自证):坏段被复用 ⇒ 51200 ≠ 40960 必红(已实测);
* 变异(去掉总长比较):旧大小的段被复用 ⇒ 61440 ≠ 40960 必红(已实测)。
|
2026-11-13 06:26:18 +08:00 |
|
AskaEth
|
6e1d5fa82e
|
Merge branch 'feat/m292-dlsettings-wire-download-timeouts-i'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-13 04:29:11 +08:00 |
|
AskaEth
|
f6cdbff885
|
test(download): 补强最后一跳 + 写明「抓得住/抓不住」(M292 r1)
复审与塔的门禁在本 worktree 报了一条 `left: 120s / right: 300s`
(`changed_settings_reach_the_engine_config_and_the_budget`)。归因结论:**不是源码缺陷**,
是目标目录里跑到了不属于这棵树的 `unlucky_core` 测试二进制。本轮只做两件事:
## 1. 最后一跳补强(presenter)
`the_three_timeout_keys_travel_from_the_file_to_the_engine_config` 此前是自己拼
`tuning.apply_to(EngineConfig::default())`,即「调优 → 引擎」这一跳是**用例自己接的**,
守不住「宿主有没有把调优交给 core」。现在改走 GUI 的生产装配点
`crate::host::engine::engine(&env)`(内部 `apply_tuning` → `shared::set_tuning` → 按数据根重建),
断言读 `engine.config()`。这一步断了(宿主没调 `apply_tuning`、或 `shared` 装配点没用
`apply_to`),三条断言全红。
## 2. 用例文档写清边界(config/tests.rs)
`changed_settings_reach_the_engine_config_and_the_budget` 的文档补上返工取证:
- **抓得住**(三键逐一变异,删掉 `apply_to` 里对应赋值即红):
`response_timeout` ⇒ 278 行 `60s/180s`;`chunk_stall_timeout` ⇒ 279 行 `120s/300s`;
`min_avg_throughput` ⇒ 280 行 `32768/16384`。另抓 `from_raw` 的 KiB→字节换算与
`chunk_job_budget` 忽略下限。
- **抓不住**:①宿主有没有调用 `tuning`/`apply_to`(前半段归宿主用例);②跑的是不是这棵树的
二进制(编译期常量与代码内容都不在它视野里 —— 本轮就栽在这里)。
`config/tests.rs` 因此从 394 → 398 行(仍在 400 硬线内)。
|
2026-11-13 04:06:42 +08:00 |
|
AskaEth
|
2ecb33afae
|
feat(download): download.* 三个超时/阈值键接进 schema 与引擎(M292 2/2)
P21 第 1 条:M289 的四个超时/阈值只挂在 `EngineConfig`,`DownloadTuning::apply_to` 只搬并发与
每源尝试次数 ⇒ 慢链路用户没有任何自救手段。出厂等价于「平均吞吐 ≥32 KiB/s 才活得下来」:
188MB 在 20 KiB/s 上要 160min > 100.3min 预算,必被杀;被杀后不再重试、不再换源,所以是
「下不完」而不是「慢一点」。
## 键(schema 1.9.0,新键推 minor)
| 键 | 默认 | 范围 | 落到 |
|---|---|---|---|
| `download.throughput_floor_kib` | 32 KiB/s | 4~4096 | `EngineConfig::min_avg_throughput`(×1024 → 字节/秒)|
| `download.stall_timeout_secs` | 120s | 30~600 | `EngineConfig::chunk_stall_timeout` |
| `download.response_timeout_secs` | 60s | 10~600 | `EngineConfig::response_timeout` |
三个默认值与 M289 的硬编码**逐值相等** ⇒ 不设 = 当前行为。上界(`MAX_*` 常量)就是「把挂住变成
无界」那道兜底:越界值在写入口被 schema 拒、引擎侧再回落一次出厂值。
## 通路
`RawTuning`(宿主只按 `KEY_*` 常量取裸值)→ `DownloadTuning::from_raw`(默认/区间/回落/KiB→字节
全在 core 一处)→ `apply_to` → `EngineConfig` → `stall::job_budget(total, floor, min_throughput)`。
`job_budget` 内部 `min_throughput.max(1)` 挡除零(设置层已拒 0,这是纵深防御)。
两个宿主都读六个键:`crates/cli/src/download.rs::tuning`、`presenter::host::settings::download::
tuning_of`。CLI 在**非出厂值**时往 stderr 打一行 `DownloadTuning::summary()`(`--dry-run` 也会经过
它;实测 `ull --data-dir … java install 21 --dry-run` 的 stderr 是
「下载调优:并发=8 单源尝试=3 吞吐下限=16KiB/s 无进展窗口=300s 单次请求上界=180s gh_proxy=跟随内置默认」,
出厂/非法值时一行都不打)。
## 文档口径(各只写一处)
- `stall.rs` 模块头:「改这几个数要付的代价」(调低下限换到的是「慢」不是「无界」;两档窗口是
单次尝试的界,最坏要乘候选数×尝试次数)+「数字从哪来」改成「默认值 / 能不能改」+ 补上 P21
第 3 条要的「`permit_timeout` 是按一次尝试反推,不是一个作业」。
## 两个纯搬移(为 400 行硬线)
- `config.rs` 423 → 261:`DownloadTuning` / `RawTuning` / `secs_in_range` / `impl` 整段搬到
`config/tuning.rs`(194 行),`pub use tuning::{DownloadTuning, RawTuning};` 保住原路径;
搬移块 172 行原样 diff = 0(无归一化)。
- `stall.rs` 440 → 343:测试搬到 `stall/tests.rs`(103 行),唯一变换是整体去 4 格缩进;两个
未改动的用例函数体与 HEAD 逐字符(去空白)相同。
## 用例
core(`config/tests.rs` + `stall/tests.rs` + `keys/download.rs`):默认值与 schema/引擎三处同源、
改设置 ⇒ 引擎配置与总期限都变、0 与越界回落且不除零、doc 里的数字、`summary()` 的生效值、键顺序。
cli:生产入口 `build_engine` 拿到三个新值 + `announce` 只在非出厂值时给行 + 非法值回落。
presenter:文件 → `tuning_of` → `EngineConfig` 三段一致 + 键名常量与 schema 登记绑定。
|
2026-11-13 03:35:08 +08:00 |
|
AskaEth
|
f4b5a73594
|
refactor(setting): download 四键纯搬移到 setting/keys/download.rs(M292 1/2)
`keys.rs` 396 行、贴着全仓 400 行硬线,M292 要再加三个键 ⇒ 先纯搬移腾地方。
- 新增 `crates/core/src/setting/keys/download.rs`:并发 / 单源尝试次数 / 地区判定 /
镜像偏好四条登记项,**逐字搬来**(唯一机械变换是 `.with(` 外壳换成 `vec![` 项、
`))` 换成 `),` 定界符)。
- `keys.rs` 原地换成一行 `.with_all(download::specs())`,登记顺序不变。
逐字节证明(搬移块 28 行):
git show HEAD~1:crates/core/src/setting/keys.rs | sed -n '109,136p' \
| sed -e 's/^ \.with(/ /' -e 's/^ ))$/ ),/' > old.txt
sed -n '/^ vec!\[$/,/^ \]$/p' keys/download.rs | sed '1d;$d' > new.txt
diff -u old.txt new.txt; echo $? # 0
两条归一化规则都是**行首精确缩进锚定**:块内 `.with(` 只出现在四个项首、`))` 只出现在
四个项尾(各 4 处,与项数一一对应),所以它们不可能顺手改掉块内的真实差异。
|
2026-11-13 03:20:00 +08:00 |
|
AskaEth
|
bba98d5898
|
Merge branch 'feat/m291-quietspawn-route-presenter-junction'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-13 00:55:30 +08:00 |
|
AskaEth
|
7d16317411
|
fix(m291): 守卫文档在 400 行内收(⑧ 并入 ⑦ 段,净增 0 行),make_dir_link 可见性收窄到 pub(super)(M291 r1)
|
2026-11-13 00:48:38 +08:00 |
|
AskaEth
|
375cc4f1fa
|
test(presenter): junction 夹具改成逐段 join(带正斜杠的路径让 mklink 报无效语法,用例一直静默跳过),守卫补记 M291 变异(M291)
|
2026-11-13 00:31:14 +08:00 |
|
AskaEth
|
27980cf5b3
|
fix(presenter): junction 夹具改走 platform::child::quiet,两处副本收成一份共享 helper(M291)
|
2026-11-13 00:28:32 +08:00 |
|
AskaEth
|
5617709792
|
Merge branch 'feat/m290-dlbound-bound-the-remaining-bare-se'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-13 00:24:26 +08:00 |
|
AskaEth
|
bde6649aab
|
fix(download): text_bounded 不再信 Content-Length 预分配,补两道内存闸;api/version 第四处裸 send 也上界(M290 r1)
复审 r1(p2-1 / p3-1 / p3-2 / p3-5)返工,三处既有结论(send_bounded 逐字节未动、三个既有调用方
零改动、阶段只用两档)原样保留。
**p2-1 `http.rs::text_bounded` 的内存**(复审判据成立):`Content-Length` 是「对面说了算」的数字,
拿它喂 `Vec::with_capacity` 等于让远端决定我们的内存,而分配失败是 abort、进不了 `Result`。
改成三道闸:
1. 头里声明 > `MAX_IN_MEMORY_BYTES`(64 MiB)⇒ 读完头立刻有界失败,**不预分配、不读正文**;
2. 容量只按真实到达增长(`Vec::new()`);
3. 读循环逐块卡同一道上限(在累加前判,不会先撑过上限一块)。
两道闸的 `message` 分别说「声明的长度」与「内存上限」,`kind` 都是 `InvalidRequest` ⇒ 与超时
(`Timeout` + 阶段)可区分。
**p3-1 第四处裸 `send()`**:`game/java/api/version.rs::fetch_release_page` 原本
`client.get(url).send()` + `response.text()`、整文件 0 处 timeout(生产路径:`source::pinned` →
`api::fetch_version`)。改走 `http::send_bounded` + `http::text_bounded`,界是参数(生产
`http::default_deadlines()`,用例压到 200ms)。
**p3-2 `download/stall.rs` 模块头**:把「`game/java/api.rs`、`game/java/net.rs`、`update/github.rs`
是裸 `send()`——未审」这句假事实换成实测清单(谁已收、谁可以留、为什么留:`auth/http.rs` 自带
`.timeout`、`preflight.rs` 由 `check_with_deadline` 套住、`region.rs` 自带 3s),并给
`http::text_bounded` 补一行「入口/上界/阶段」。
**p3-5 doc**:删掉「与 `Response::text()` 的唯一差别是 charset 解码」这条过度披露——本仓 reqwest
没开 `charset` feature,`text()` 本来就是 `from_utf8_lossy`,解码逐字节等价;改成真实差别
(读空闲 + 内存上限)。
离线用例(全在 `#[cfg(test)]`,夹具 `game::java::net::probe` 新增 `Mode::Declared{declared,sent}`
与 `Mode::Flood(n)`,外加共用的 `bounded()` 护栏助手):
* `api/version.rs::bounded_probes`:静默 ⇒ 阶段等响应头;正文停顿 ⇒ 阶段等字节;
声明 `Content-Length: 999999999999` 且不发正文 ⇒ <2s 返回 `InvalidRequest` 且文案含
「声明的长度」;不声明长度灌 65 MiB ⇒ 读循环在 64 MiB 处拒。
* `net.rs::tests` 的六条(4 条原有 + 2 条正常路径)改用同一个 `bounded()` 助手,断言一条未放宽。
变异实跑(贴红原文见 mission 报告):
1. 退回 `Vec::with_capacity(declared)` 且删提前拒绝 ⇒ `memory allocation of 999999999999 bytes
failed` + 进程 abort(0xc0000409),测试二进制消失——不是一条红;
2. 只删提前拒绝(预分配仍是 `Vec::new()`)⇒ 干净红:`left: Timeout / right: InvalidRequest`(0.21s);
3. 删循环里的上限卡口 ⇒ 干净红:`expect_err` 拿到 `Ok("xxxx…")`(0.54s);
4. `fetch_release_page` 退回裸 `send()` + `text()` ⇒ 两条 version 用例各在护栏 5s 处 panic(5.01s)。
门禁(真实退出码):check=0 / lib=896 passed / game=120 passed / launch=91 passed /
engine_mock=17 passed / clippy -D warnings=0 / fmt=0。行数:http 389、stall 378、api 389、
net 390、version 397、github 363(全 ≤400)。
|
2026-11-13 00:22:22 +08:00 |
|
AskaEth
|
ad99303a95
|
fix(java/update): 三处模块外的一锤子 GET 也上界,失败时报出卡在哪一段(M290)
`download_bytes`(M289)修掉之后,core 里还剩三处裸 `send()`,都在「拿上游文本/JSON」的
路径上:`game/java/api.rs::fetch_latest`(Adoptium latest)、`game/java/net.rs::get_text`
(四家发行版的一锤子 GET)、`update/github.rs::get_candidate_text`(更新清单)。上游接了连接
就装死时它们永不返回,与 M289 修掉的是同族形态。
改动(全部复用 M289 的构造点,没有第二套超时包装):
* `download/http.rs` 新增 `text_bounded`(正文按 `read_step` 的空闲判定读成 `String`,与
`bytes.rs` 的读循环同口径)与 `default_deadlines()`(取 `EngineConfig::default()` 的两段界,
不另抄秒数);`send_bounded` 的签名逐字节未动,`transfer.rs`/`chunked.rs`/`bytes.rs` 三处
调用方零改动。
* 三处一律 `send_bounded`(等响应头)+ `text_bounded`(等字节):失败时 `message` 里带
`StallReport` 的阶段(`await_headers` / `await_bytes`)与已收/预期/无进展三个数。
这三条路径只用到这两档:没有分块回收、也没有引擎许可等待,所以 `await_chunk_join` /
`acquire_permit` 不适用。
* `update/github.rs` 的界取 `engine.config().deadlines()`(手里有引擎就不抄默认值)。
正常路径零改变:`fetch_latest` / `get_text` 的公开签名与错误分类一字未动(`fetch_latest` 的
404 文案与 hint 保留),`update` 的 `github_cases` 全部断言未改。
离线用例(`game::java::net::tests`,6 条;夹具 `net::probe::Hanging` 也是 M290 新增):
silent 服务端 ⇒ 引擎自己在 200ms 上界内返回(外层 5s 护栏没触发),并断言 message 含
「等响应头/await_headers」;正文停住 ⇒ 「等字节/await_bytes」;200 + 真 JSON ⇒ 正文逐字节
原样(含中文)。变异已实跑:`send_bounded` 退回裸 `send()` 三处各自在外层 5s 处挂断;
`text_bounded` 退回 `response.text()` ⇒ 等字节用例 5s 挂断;正文改成空串 ⇒ 正常路径用例红。
门禁:cargo check(core+cli, --all-targets)/ test --lib(892 passed)/ --test game(120)/
--test launch(91)/ --test engine_mock(17)/ clippy -D warnings / fmt --check,退出码全 0。
|
2026-11-13 00:22:21 +08:00 |
|
AskaEth
|
53de2a3922
|
Merge branch 'feat/m288-tokenguard-test-colors-no-longer-tr'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-13 00:04:21 +08:00 |
|
AskaEth
|
9780fca1c7
|
fix(app-iced): blend 用例端点改用真实令牌色(M288)
`blend_hits_both_endpoints_bit_for_bit` 原先用 `Color::from_rgb(0.1, 0.2, 0.3)` /
`(0.7, 0.8, 0.9)` 当端点,撞上 `tests/no_hardcoded_tokens.rs` 的色值守门(它扫 `src/**`,
测试模块也算在里面),使 base 上「全量测试绿」永不可达。
修法在**被判的那一侧**:端点取状态层真正会插值的一对角色——`Role::Surface`(容器)与
`Role::OnSurface`(内容)——经产品同一条令牌路径(`BrandBoard::for_flavor(..).scheme()`
→ `to_iced`)取值,浅/深两套板各跑一遍;判据值全部现算(每通道取两端 min/max)。
三条判据一条不少:端点逐位相等、中点**严格**落在两端之间(比原先的闭区间更强)、α 全程 1.0。
守卫文件一行未改(仍 400 行),扫描范围没有放宽。
|
2026-11-12 23:55:24 +08:00 |
|
AskaEth
|
224aa7d9ff
|
Merge branch 'feat/m287-listradius-list-row-corner-from-com'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 23:41:34 +08:00 |
|
AskaEth
|
5174845cda
|
fix(app-iced): 列表行圆角改从组件表取(M287)
`list_item.rs` 硬写 `ui.radius_dp(ShapeScale::None)`,旁边还写着「4.x:ListItem 的形状
是 `CornerNone`(直角),没有圆角档」——而令牌表 `component_map::table` 的 `ListItem`
是 `Small(8)`。产品侧根本没读表,注释在替错误站台。
改为 `row_border(ui)` → `ui.radius_dp(Component::ListItem.spec().shape)`,数值只留在
令牌表。变异实测(只改表的那一行 `Small→Large`):行圆角 8 → 16,240×60 选中行的顶行
缩进 6 → 13;改回后 11 张图逐字节复原(`DevDocs/artifacts/listradius/`,不入库)。
同 commit 改掉那条直角注释、模块文档补 4.9 行,并加两条单测:渲染路径的
`Border.radius` = 表值折算;源码里不许再出现硬编码的 `ShapeScale::*` 档。
外观影响的边界(写进取证报告):行只在 选中/悬停/按下 时有容器色,未选中静息与禁用
两态 `fill` 为 `None`,那两态逐字节不变是**正确**的,不是没生效。
|
2026-11-12 23:38:56 +08:00 |
|
AskaEth
|
a07cda34cb
|
Merge branch 'feat/m289-dltimeout-bound-every-download-awai'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 23:27:19 +08:00 |
|
AskaEth
|
d6f862a43c
|
fix(download): 补齐 download_bytes 与等许可两处无界 await,预算耗尽不再重试(M289 r1)
r1 复审 p2-1:`stall.rs` 那句「全仓只有两处无界 await」是假话。真实清单五处,
M289 之前只修了两处;这次补齐:
* `bytes.rs::fetch_bytes` 的 `send()` 改走 `http::send_bounded`(与文件下载同一个构造点);
整包 `response.bytes()` 换成 `bytes_stream` + `http::read_step` 的空闲判定,并边读边卡
`MAX_IN_MEMORY_BYTES`(`Content-Length` 可缺失/可撒谎)。生产调用点:版本清单、
版本 JSON、Modrinth 元数据。
* `engine::acquire` 的 `acquire_owned()` 加上界(`EngineConfig::permit_timeout`,默认
120min ≥ 单个作业总期限),到点报新阶段 `StallStage::AcquirePermit`(「等许可」),
不冒充「服务器不给数据」。这就是「卡死作业攥着许可把队列堵死」的连坐形态。
* `stall.rs` 模块头改成一张「哪些入口有界、谁托管给谁」的表,并写清五处 await 的真实清单。
r1 复审 p2-2:总期限用完是链路级判决,不再按满预算重试/换源:
* `DownloadError::job_budget_exhausted()` + `DownloadError::is_retryable()`(覆盖
`kind.is_retryable()`),引擎与 `download_bytes` 都改用它;
* 引擎在预算耗尽时直接结束候选循环,不再换源(同一份字节、同一条链路,换源只会再
烧满一整个预算)。188MB/20 KiB/s 的用户不再从「1.6 小时慢慢下完」变成「几小时反复重下」。
* `stall.rs` 写明「慢链路照样活」的前提 = **平均吞吐 ≥ 32 KiB/s**,以及它挡掉哪类链路;
`download.*` schema 接线留给另一个 mission(`setting/keys.rs` 不在 scope)。
顺带(复审 p3):
* `transfer.rs` 的读空闲统一走 `EngineConfig::deadlines()`(不再各读字段);
* `verify_part` 补一条代价说明:`spawn_blocking` 到点不可取消,可能让同名重建拿到
`Access is denied`(**该假设未验**,注释里已标);
* `engine.rs` 因这轮新增又贴到 410 行 ⇒ `finish` / `materialize` / `millis` 逐字节搬到
`engine/finish.rs`(`engine::millis` 路径用 `pub(crate) use` 保持可用)。
新增用例(全离线、<2s):`download_bytes` 静默服务端 / 正文中途卡住、等许可超时、
预算耗尽不重试(`max_attempts_per_source = 3` 下仍只发一轮)、预算耗尽不换源
(官方源 `hits == 0`)。
|
2026-11-12 23:24:19 +08:00 |
|
AskaEth
|
cc0def3415
|
feat(download): 给每条下载 await 上界并在超时时报出「卡在哪一段」(M289 / P19)
全仓下载路径此前只有两处无界 await(分块与单连接的 `send()`),reqwest 自己的
`timeout` 又是 `None`:服务器接了连接不回一个字节 ⇒ future 永不结束,
`ull java install` 那条 `silent()` 路径零事件零日志,用户只看到「卡住不动」。
上界选「单次请求 + 读空闲」,**不是**「整作业总预算」:
* `region.rs` 的 3s 是探测极小响应(整作业 = 一次 GET,两者同一件事);
* `font.rs` 的 ATTEMPT/TOTAL 是几百 KB 的字体(整作业几秒就完);
* 188MB 的 JDK 两者都不能照抄,所以分三段互不顶替的界,理由与取数**只写在**
`crates/core/src/download/stall.rs` 模块头一处(含「别改成 reqwest 的 timeout,
它覆盖整个响应体读取」)。
改动:
* 新增 `download::stall`:`StallStage`(连接 / 等响应头 / 等字节 / 等分块回收 / 校验)+
`StallReason`(期限用完 / 没有新字节)+ `StallReport`(阶段 + 已收 / 预期字节 +
无进展时长)+ `job_budget`(总期限按 32 KiB/s 平均吞吐反推)。
* `http::send_bounded` / `http::read_step`:两处 `send()` 与两处读循环的**唯一**构造点;
超时错误带阶段与三个数,`message` 里也抄一份(整体失败时 `suppressed` 只搬 message)。
* `transfer_chunk::await_chunks`:`JoinSet::join_next` 加总期限与无进度窗口,两者靠
`StallReason` 分开报;窗口内有新字节就续期(慢但有效不误杀)。
* `verify_part` 挪到 blocking 池并套上界:校验阶段卡住也报得出来(阶段=校验)。
* `engine.rs`:整体失败时把**最后一次**尝试的失速现场带上去,并写进用户可见的那行字。
* 配置:`response_timeout` 60s / `chunk_stall_timeout` 120s / `chunk_job_floor` 10min /
`verify_timeout` 120s(`EngineConfig`,未动设置 schema)。
测试(全离线、`127.0.0.1`、<2s):夹具新增 `silent()` / `stalling_after` / `short_body` /
`oversize_body` / `dripping`;用例覆盖静默服务端、中途卡住、短包、超长包、慢但持续
(4 倍于上界也要成功)、超时后引擎仍能下载(RAII 许可自还,不写手动归还)、以及分块
作业的「无进度 vs 总期限」两条。
另:`config/tests.rs` 的一处表达式在去缩进后行宽变化,随本 commit 由 rustfmt 合成一行
(纯搬移 commit 提交时它仍是逐字节原文)。
|
2026-11-12 23:24:19 +08:00 |
|
AskaEth
|
61c8a4eff9
|
refactor(download): 纯搬移为 M289 腾出行数余量(config 单测 / engine 只读查询面)
两个文件都是 400 行零余量的硬线,本 mission 要加超时字段与失速诊断,先纯搬移:
* `config.rs` 的 `#[cfg(test)] mod tests`(174 行)→ `config/tests.rs`,
原文件用 `#[cfg(test)] #[path = "config/tests.rs"] mod tests;` 声明;
* `engine.rs` 的七个只读 getter(`config` / `concurrency_probe` / `client` /
`region` / `mirrors` / `health` / `cache`,44 行)→ `engine/inspect.rs`。
逐字节未改:搬移块去缩进后与 HEAD 原文 `diff` 均为 exit 0。
行为零变化(同一批测试仍全绿);新文件都在 `crates/core/src/download/**` 内,
外部路径(`download::config::EngineConfig` 等)不变。
|
2026-11-12 23:24:18 +08:00 |
|
AskaEth
|
8b48c494cd
|
Merge branch 'feat/forcedwire-force-state-through-settings'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 23:15:05 +08:00 |
|
AskaEth
|
5ce6d8567b
|
docs(app-iced): 残余清单改成「残留快照,不是契约」(M285 r3 p2-1)
r3 复审的判据改成「不假」而不是「穷尽」,按它给的结构重写这一段:
- 删掉「**所有** components::selection 的 chip/switch 站点」(这个词两头都不准:字面上吞掉了
已接的 general/language.rs + general/theme.rs 那 6 枚,又把另一个页面 pages/content 的 chip
算了进来);
- 第 3 条改成「还剩没搭线的那几处(已知:…;这一串会漂,接线时请更新)」,并补
java/view.rs 运行时的「设为默认」chip(action_chip 在非默认行返回可点的 Chip::filter,
runtime_row 没有 forced 形参);同时明写 language/theme 那 6 枚已接、别算进残余;
- 新增第 5 条:ai/view.rs 的 base_url / model / api_key 三只 Field(此前四处都不落);
- 末尾加「这份清单是残留快照,不是契约:接上一处就划掉一处,判据只有『有没有拿到 forced』」。
顺带订正 r3 的两条 p3(同一段、纯注释):
- 主题 chip 枚数 2 → 3(general/theme.rs 的 THEME_MODES: [ThemeMode; 3]);
- 第 4 条去掉「下载按钮」:java/download_view.rs 里一个 Button 都没有(那两处是 chip,已在第 3 条)。
第 1 条(Downloads 组 + e283c585… 三态 md5)与「已经消费的」「没搭线≠组件库缺能力」两句一字未动。
纯 //! 注释,零代码行。harness.rs 347 → 356 行。
|
2026-11-12 23:12:21 +08:00 |
|
AskaEth
|
a75ee465a7
|
docs(app-iced): harness 文档的残余清单改写(三条与代码不符的句子,M285 r2 p2-1)
r2 复审逐句拿代码核过,三处不符:
(a) 「裸 iced::button 已经消费」—— 8 颗预设色块本身就是裸 button(...) 且不消费(accent.rs:153/
163-166,ACCENT_PRESETS: [&str; 8])⇒ 改成「这几类控件(choice / chip / action_button)已消费」。
(b) 残余清单漏了三类 ⇒ 补:Downloads 组整组(body 形参就是 _forced,switch/chip/两颗 Button::new
都不看 forced;实测 --screen=download 三态 md5 全等 e283c585…)、所有 components::selection 的
chip / switch 站点(update 自动检查 chip、ai provider/reveal chip 与开关、download 两处、java
prefs_view 与 download_view)、java 的字段/预设/下载按钮(fields.rs / prefs_view.rs /
download_view.rs)。同时修掉与本 mission 自己的 artifact README §1「下载页不消费 forced」的
互相打脸。
(c) 「要接得先给组件开口」对滑块/文本框是假的 ⇒ Slider::state / Field::state / Chip::state /
Switch::state 全都存在且被消费(gallery 矩阵用的强制视觉态),没接是**没搭线**;真正缺 forced
入口的只有本地助手 accent::swatch。并加一句反向锚:别把「裸 button」当「已消费」的同义词。
纯文档,零接线改动。harness.rs 330 → 347 行。
|
2026-11-12 23:12:20 +08:00 |
|
AskaEth
|
3255579bba
|
docs(app-iced): 把「裸 button 不消费 forced」这句假话改掉 + 用法表补 --force-state(M285 r1 p2-1)
复审原话:harness.rs:43-45 那段「设置页外观区的滑块 / 文本框 / 裸 iced::button 都不消费
forced」**正是 M282/M284 放弃像素验收的原始理由**——M285 之后后半句已经是假的,留着它下一
个 agent 会再推一遍同一个死胡同。
- pages/harness.rs:参数清单补 --force-state=rest|hover|pressed;那段改成现状(裸 button 已
经消费:外观 11 处 choice + 语言/主题 chip + 日志 action_button 走 settings::resolve_state;
components::buttons::Button 三组走 forced_button;仍不消费的只剩滑块 / 文本框 / 色块),
并明确「别把它改回去」。
- examples/pages_demo.rs:用法表补 --force-state / --appearance / --passthrough(此前
--force-state=bogus 先报约束、却打出一张没有该参数的用法表)。
实测:`--force-state=bogus` → exit 2,用法表里含 [--force-state=rest|hover|pressed]。
顺带 r1 p3-1(机制说法订正):forced_button 的文档写清真实优先级 —— Button::forced() 是
`self.state.or(没有消息 ⇒ Disabled)`,**state 在前**,所以 disabled 也能被 .state() 盖掉;
并加一条单测钉住实际结果(forced_button(Button::disabled(..), Some(Hovered)) ⇒ state:
Some(Hovered))。今天 7 个站点都写在启用分支里所以安全,但**别**把它套到 if..else 外面。
行数:settings/mod.rs 396 → 397(先算过余量,未破 400);harness.rs 321 → 330;
pages_demo.rs 37 → 39。判据 1 复验:重出 5 张 rest 图,md5 与基线逐张相同。
|
2026-11-12 23:12:20 +08:00 |
|
AskaEth
|
e15bd741cd
|
fix(app-iced): 守住 400 行 + 接线计数点跟着语言行搬(M285 门禁红)
- settings/mod.rs 401 → 396 行(压自己新加的文档,不删任何语义):
components::gate::every_source_file_stays_under_the_line_limit 红过。
- pages/tests/base_wiring.rs:语言行搬进 general/language.rs 后,`BASE.role()` 的
计数点也要跟着搬(general.rs 0 次 → language.rs 1 次),否则
every_card_internal_control_site_still_passes_a_base 红。计数与语义都没变。
|
2026-11-12 23:12:20 +08:00 |
|
AskaEth
|
20607a0584
|
test(app-iced): 三处测试调用点补 forced=None(M285 形参同步)
section_columns / settings::view / appearance::section 各加了 forced 形参,
测试里的调用点必须同步(同行情改,不增行;frame.rs 用一次局部绑定避免折行)。
frame.rs 393 → 394 行。产品逻辑零改动。
|
2026-11-12 23:12:20 +08:00 |
|
AskaEth
|
7b80b0e314
|
feat(app-iced): --force-state 穿过设置页与外观区(M285 FORCEDWIRE)
pages::Model.forced 此前只到得了外壳 + 实例页(唯一消费者 pages/instances.rs:159),
设置页「外观」区的滑块/文本框/裸 iced::button 全都不消费它 ⇒ M284 那四处状态层
(语言档 / 主题档 / 日志 action_button / 外观 choice)只有单测证据,做不出像素判据。
改动:
- settings/mod.rs: resolve_state(forced, status) 与 forced_button(button, forced)
成为全仓唯一两个「forced 怎么解释」的点;view → form → section_columns 一路
新增 forced 形参(默认 None ⇒ 今天的行为逐字节不变)。
- Group.body(registry.rs)加第 4 个形参 Option<Interaction>,六组统一签名。
- 四类裸 iced::button 控件把 forced 递进样式闭包并经 resolve_state 落地:
语言档 chip(general/language.rs)、主题档 chip(general/theme.rs)、
日志 action_button(logs_row.rs)、外观 choice(appearance/section/mod.rs,
含 accent/window/bg_image/colour_source 共 10 处调用)。
- components::buttons::Button 的三组(update / ai / java)走 forced_button 的 .state()。
- harness: request.rs 解析 --force-state=rest|hover|pressed(未知值报错,词与产品
cli.rs 同一套),slug 加 -hover/-pressed;app.rs 把它写进 model.forced,
截图通道默认 Rest 的纪律保持(显式值优先)。
- pages/mod.rs: 唯一调用点把 model.forced 传给 settings::view(397 → 399 行)。
单测:resolve_state 的 Some 覆盖 / None 退回;forced_button 的 Some 钉态 / None 原样。
|
2026-11-12 23:12:20 +08:00 |
|
AskaEth
|
767727efb7
|
refactor(app-iced): 语言行从 general.rs 纯搬移到 general/language.rs(M285 前置)
general.rs 390 行、撞 400 行硬线,而 M285 要给语言行三枚 chip 接 forced。
切法与既有的 general/theme.rs 同构(一个行控件一个文件):正文 view + chip +
chip_style。语句逐字节搬移(去缩进去可见性后 diff 退出码 0),只把
fn language_row 改名成 pub(super) fn view(与 theme::view 同一入口命名)。
general.rs 390 → 245 行;language.rs 170 行。
|
2026-11-12 23:12:19 +08:00 |
|
AskaEth
|
589162de89
|
refactor(app-iced): 设置页分组注册表纯搬移到 settings/registry.rs(M285 前置)
settings/mod.rs 撞 400 行硬线,而 M285 要给 Group.body 加 forced 形参。
按语义切分(不按行数):mod.rs 留「整页 → 表单列 → 分组正文」流水线,
registry.rs 留「分组是什么」—— Body / Group / GROUPS / GROUP_LABELS /
SELECTED / GENERAL_GROUP / UPDATE_GROUP / AI_GROUP / JAVA_GROUP。
行为零变化:搬移块去缩进去可见性后与 mod.rs:190-275 逐字节相同
(diff -u 退出码 0,见 mission 报告)。可见性只有两类改动:
- Group / Body 用 pub(crate),因为 mod.rs 要 pub(super) use(E0365:再导出
不能比原声明更可见);
- 其余常量 pub(super),mod.rs 私有 use,子模块的 super::GROUPS 写法照旧可用。
|
2026-11-12 23:12:19 +08:00 |
|
AskaEth
|
f931af793d
|
Merge branch 'feat/javapin-core-jdk-install-with-explicit-v'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 22:46:09 +08:00 |
|
AskaEth
|
c8f820caec
|
Merge branch 'feat/statelayer-pressed-alpha-and-card-surfac'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 21:50:18 +08:00 |
|
AskaEth
|
747521fe54
|
fix(app-iced): pressed 状态层改走 Interaction 表 + 外观 choice 叠卡底(M284)
四处手写按钮(语言 chip / 主题 chip / 日志行两个动作 / 外观互斥按钮)把
`matches!(status, Hovered | Pressed)` 合成分支换成 `Interaction::from_status(status)`
→ `interaction::layer_color`:pressed 从被压成 hover 的 8% 回到令牌表的 10%。
`appearance/section/mod.rs` 的 `choice` 合成底从写死的 `Role::Surface` 换成
`frame::BASE.role()`(= `MaterialLayer::Card` = `SurfaceContainerLow`),与同屏
`general.rs` / `general/theme.rs` 同源——M234/M237 修过两轮的那个错,这是漏网的第三处。
四处样式闭包抽成自由函数(`choice_style` / `chip_style` / `action_style`),单测直接喂
`Hovered` 与 `Pressed`:断言两色不等 + 各自逐值等于 `state_over` 的令牌求值(改 α 也会红)。
语言行那枚 chip 的断言住在 `general/theme.rs`(`general.rs` 已 390 行,贴 400 硬线)。
未验:hover/pressed 的图像判据。`--force-state` 到不了设置页/外观区(那里不消费
`Model.forced`,`pages/harness.rs` 自己写着),接线另立任务;本轮只出 rest 图并比 md5。
|
2026-11-12 21:48:03 +08:00 |
|
AskaEth
|
bc2ad85c63
|
Merge branch 'feat/navpill-rail-hover-pressed-state-layer-o'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 21:44:36 +08:00 |
|
AskaEth
|
813ec88d89
|
fix(app-iced): 导航轨 hover/pressed 状态层落回药丸(M283)
`rail_item` 里同一个元素并存两种形状:选中画 56×32 `radius(Full)` 的药丸,
而 hover/pressed 走 `button(stack).style(..)`,底色铺在整个按钮上
(88×52,`Border::default()` ⇒ r=0)。用户原话就是这一处:「侧边栏按钮
hover/click 是父容器整个方形区域颜色加深,而不是 MD3E 应有的胶囊 UI」。
改法照 `components/navigation/drawer/item.rs`(全仓唯一正确的导航实现):
- 状态层改由药丸自己的 `container.style` 求值,经 `interaction::layer_color`;
选中/未选中两条分支不合并(选中静息兜底 `SecondaryContainer`,未选中兜底 `None`);
- `base` 取 `MaterialLayer::Canvas.surface_role()`(导航轨画的就是 Canvas),不写字面量角色;
- `button.style` 的 `background` 恒为 `None` —— 按钮不再有任何形状上的主张;
- `interaction::tracked` 只在 `forced.is_none()` 时套(强制态是取证,不许有过渡中间帧);
- 轨底账户位同型:状态层落在头像那个 Ø40 圆上,不是 88×52 的按钮;
- 药丸几何收敛到 `state_layer_box()` 一个定义点(`rail_item` 与单测都读它)。
判据:
- `shell::tests::the_state_layer_is_drawn_on_the_pill_not_across_the_rail`(几何)
- `shell::tests::the_rail_never_paints_its_state_layer_on_the_button`(结构:扫源码,
`button(stack)..on_press` 闭包里不许出现 `Background::Color`、`tracked` 必须有
`forced.is_none()` 守卫)。两条都做过反向验证:把写法改回去,两条同时红。
取证(`DevDocs/artifacts/navpill/`):`--force-state=rest` 与改前**逐字节相同**
(md5 1ebb611b3edc156925ca802d3f4fa76d);hover/pressed 的高亮区从 88×52 矩形
变成 56×32 药丸(四角为空),账户位从 88×52 变成 Ø40 圆。
|
2026-11-12 21:42:30 +08:00 |
|
AskaEth
|
26ebf9a944
|
Merge branch 'feat/railexit-demo-fixture-that-actually-star'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 21:29:01 +08:00 |
|
AskaEth
|
ca086a40ee
|
feat(app-iced): 右栗折叠腿的取证夹具 ULL_AGENT_RAIL=exit-collapsed(M279)
`AgentUi::new()` 出厂就是折叠档,而 `collapse()` 只在档位是 Expanded 时才改档并
`observe_rail(false)`(`pages/agent/state.rs:245-255`)⇒ 原来的 `ULL_AGENT_RAIL=collapsed`
是**空操作**:配 `ULL_MOTION_AT` 拍「折叠中途」时,拍到的只是静息折叠 + 页面入场位移
(M257 r2 复审实测,md5 与静息帧相同)。而折叠腿恰好是「页面模型比物理区更宽」的那一侧
(`content_width()` 吃目标档、`rail_width()` 吃动画进度),最可能出现右侧裁切/重叠的方向
反而没有任何像素证据。
新取值 `exit-collapsed`:先 `expand()`,把展开这一腿 `snap_rail(true)` 落成静息,再在
**同一拍** `collapse()` ⇒ `observe_rail(false)` 真的从进度 1.0 起跑一条 1 → 0 的折叠轨道。
snap 不能省:钉住相位时两条轨道共用同一个起点(`Pump::start_now()` 恒为 0),不 snap 的
话收起那一腿会从展开的半路反向,三张相位图就不是同一条腿,位移表也就没法与展开腿对照。
原有 `collapsed` / `expanded` / `detached`(含「认不出的取值 ⇒ 一动不动」)语义逐字未改,
由新用例 `the_old_rail_values_keep_their_exact_meaning` 守卫。档位落法抽成纯函数
`seed_rail`(用例直接喂名字,不摆环境变量);启动日志多一个 `seed=` 字段——静息落档与
「真的起了折叠轨道」两者的 `rail` 都是 `collapsed`,只有 `seed` 能分辨。
用例:`exit_collapsed_really_starts_the_collapse_leg_from_full_open`
(轨道在跑、第一帧严格 320、进度严格递减、末端严格 0、帧数 10~20)。
|
2026-11-12 20:59:54 +08:00 |
|
AskaEth
|
fc48094249
|
Merge branch 'feat/javareg-registry-fallback-for-microsoft'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 20:20:31 +08:00 |
|
AskaEth
|
23e98635dd
|
feat(core): JDK 发现补 Microsoft 注册表支(HKLM\SOFTWARE\Microsoft\JDK\*\MSI\Path)(M281)
`registry_homes()` 原来只读 `HKLM\SOFTWARE\JavaSoft\JDK`,而 MS JDK 的 MSI 把安装路径
登记在 `HKLM\SOFTWARE\Microsoft\JDK\<ver>\<flavor>\MSI` 的 `Path`(本机 21/25 两条,
JavaSoft 那个键在本机根本不存在)。装在自定义目录(不在 Program Files 下)的 MS JDK
因此漏扫——Program Files 那支兜住的只是默认位置。
- 新增 `parse_msi_paths()`:判据是「当前键尾段叫 MSI」(不数层数),多一层/空层都认;
同键里的 `JavaHome REG_DWORD`、非 MSI 键下的 `Path`、值名不是整段 `Path` 的都不认。
- 注册表读取改为可注入:`RegSource` trait + `ProcessReg`(真 `reg.exe`),
`registry_homes_from(&dyn RegSource)` 让单测喂真实 `reg query` 输出样本,不依赖本机。
- 两个键 × 64/32 两个视图各查一遍,与 JavaSoft 那支同口径;键不存在照旧静默跳过。
- `scan_java_binaries()` 拆出 `java_binaries_in(roots, homes)`:注入式验收可以屏蔽
Program Files / PATH / JAVA_HOME,只走注册表;去重改用仓库既有的 `path_key` 口径
(末尾反斜杠、`\?\` 前缀、大小写都不会重复列)。
- 注册表这支整体搬进 `discover/registry.rs`(`mod registry;` + `pub use`,
`discover::registry_homes` / `parse_java_home` 公开路径不变,`mod.rs` 一行未动)。
|
2026-11-12 20:19:23 +08:00 |
|
AskaEth
|
749d6a8fe2
|
Merge branch 'feat/motion2-sidebar-collapse-and-hover-press'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 20:08:04 +08:00 |
|
AskaEth
|
c869a7b346
|
fix(motion2): 修掉 drawer 药丸守卫的同义反复(M257 r1 复审 P2-1)
`rail_selected_label_is_secondary` 里那条 `pill_color(..) == pill_color(..)` 比的是同一个
表达式,恒真、抓不到任何东西,而注释还写着「rail 与 drawer 两条线的药丸一致」。
药丸从 M257 起不吃 layout(`pill_color` 的签名里没有 `NavLayout`),所以「两条线一致」由
签名保证;本用例改判真正要守的那一件事:静息选中药丸来自 `SecondaryContainer`。
变异验证:把 `pill_color` 的兜底角色换成 `Primary` ⇒ 该用例在 tests.rs:54 红。
|
2026-11-12 20:05:58 +08:00 |
|
AskaEth
|
b61f342431
|
feat(motion2): 右栗折叠过渡 + 状态层 hover/pressed 过渡
A2 侧边栏(右栗 AI 栏)折叠/展开:
- 帧泵第三条轨道(pump/rail):展开 medium1 250ms + Emphasized、收起 short4 200ms +
EmphasizedAccelerate;拆出档走 snap(不做过渡);MotionScale::NONE 下不插轨道
- 宽度在 metrics::rail_width 逐帧插值;面板内容按目标宽排版 + 按动画宽裁切
(不重排 / 不溢出),宽度回到 48 时换成图标条(图标贴右端,位置不跳)
- pages/agent/mod.rs 的展开档面板纯搬移到 panel.rs(371 → 289 行)
A8 状态层过渡:
- interaction/layer:令牌化的进度时钟(short1 50ms),NONE 当帧落终值
- interaction/hover:取样控件把起跑搬到 Widget::update(早于订阅表求值)
- 按钮五式 + IconButton + 导航项药丸统一走 interaction::layer_color
(没有取样控件时逐位退回旧口径,出图基线不变)
- components/motion/mod.rs 的 needs_frames / advance 各加一句委托,
帧订阅仍然只有一处判断点(app/subscription.rs 未改)
|
2026-11-12 20:05:58 +08:00 |
|
AskaEth
|
c4706c759c
|
Merge branch 'feat/multiroot-discover-official-launcher-gam'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 19:56:45 +08:00 |
|
AskaEth
|
72137dc658
|
fix(presenter): 回落日志两因并列 + 老 ext 档案覆盖 + 死根档案边界用例(M274 r3)
|
2026-11-12 19:55:52 +08:00 |
|
AskaEth
|
70c6aec6dd
|
fix(presenter): 写设置那道门按(根,版本)判绑定 + 反查回落留日志 + 旗标改真名(M274 r2 P1-1)
|
2026-11-12 19:55:52 +08:00 |
|
AskaEth
|
a614b49538
|
fix(presenter): 多根 r1 复审修四处(磁贴按自己根描述 / 挑 Java 按自己根要求 / 一拍一次扫描 / 只读内容级快照)(M274 r1)
|
2026-11-12 19:55:52 +08:00 |
|
AskaEth
|
dc8323531b
|
feat(presenter): 多根发现(配置根 + 官方启动器标准位置),实例按自己的根解析存档/模组/启动(M274)
|
2026-11-12 19:55:51 +08:00 |
|
AskaEth
|
dfc0f4bcb3
|
Merge branch 'feat/ghostfix-boot-must-not-draw-a-stale-prev'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 19:44:26 +08:00 |
|
AskaEth
|
fb30598604
|
fix(app-iced): 首屏(Kind::Boot)不再把旧页画成 250ms 幽灵
`pages::render` 把 `previous_view` 整棵画在当前页**下面**(`stack![opaque(ghost),
current]`),只在空隙里露出来。boot 那份快照是 `construct.rs` 的零实例初始模型,
被 `refresh.rs` 在第一次 refresh 时换进去 ⇒ 每次启动都闪一张「这里还没有实例…」+ 空态
图标磁贴(退场腿 250ms,`Kind::Boot` 用的就是它)。
修法:`App::sync_motion` 丢掉旧页快照的判据从「没有轨道了」扩成「没有轨道了 **或**
这一档本来就没有旧页」,后者走 `Kind::has_old_page()`——M256 写好的设计口径,此前全仓
无人调用(死代码)。丢在 `update` 的尾巴、`view` 之前 ⇒ 一帧都画不到;不动
`components/motion/**`(M257 的 scope),也不动 `app/state/refresh.rs`(不在本 scope)。
用例:`motion_sync/tests.rs` 新增三条(首屏每拍都不许留旧页 / Peer 换页仍留着旧页退场 /
NONE 档既不起轨道也不留旧页),删掉守卫即红。`motion_sync.rs` 已 398 行,按 400 行硬线
把测试模块**纯搬移**到 `motion_sync/tests.rs`(233 行逐字节相同,仅去掉模块缩进)。
出图:`DevDocs/artifacts/ghostfix/`(页面区 y<720 残余 = 0px;去掉守卫的同一棵树在同样
三相位残余 6897/5291/4762px)。
|
2026-11-12 19:35:19 +08:00 |
|
AskaEth
|
a9f691e3ff
|
Merge branch 'feat/cifix2-stabilize-mtime-probe-and-annotat'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 18:51:27 +08:00 |
|
AskaEth
|
686dd9335a
|
test(core): mtime 探针改用显式 set_modified,update-e2e 失败升注解(M275)
tree_snapshot_sees_new_nodes_and_mtime_changes 的「同内容重写」时间维度断言在
Windows/NTFS 上不可靠:同一系统时钟 tick 内的两次写、或时间戳只在句柄关闭时按
粗粒度落盘,都会让 mtime 与写之前逐字节相同 —— main 上 test (windows-latest)
偶发红在这行 assert_ne!(check-run 130965734990)。改成显式时间戳:
File::options().write(true).open + set_modified(基线 + 5s),值由测试指定、
与 tick/落盘粒度无关;不用 sleep、不用重试。用例原本的目的(快照必须看得见
mtime 变化)保持不变,另附 4s 下界断言把「我们推的那一下」与 stat 抖动分开。
ci.yml 的 update-e2e 步骤原本是裸 cargo test,失败只留一句 exit code 1、
check-run 上零注解,匿名侧等于信息为零。改成与 test 作业同款:pwsh +
Tee-Object 存 $RUNNER_TEMP 日志、失败把 panicked at / error / test result:
FAILED / failures: 行以 ::error:: 输出(% → %25)、打印并 exit $LASTEXITCODE
(cargo 真码,不是 Tee 的码)。步骤名 update-e2e 与 --test update_e2e_test
逐字保留(check_test_source_off.py 的 ALLOWED_STEP 钉死这两点)。
行数不变(ci.yml 397 → 398,≤400):注释块等价压缩腾出的行给运行时逻辑。
|
2026-11-12 18:44:51 +08:00 |
|
AskaEth
|
a0a35596c8
|
Merge branch 'feat/adoptwire-wire-adopt-wizard-entry-on-ins'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 14:36:05 +08:00 |
|
AskaEth
|
c5009442c5
|
fix(presenter): 自动发现实例按盘上痕迹选运行目录 + 空态用例补上分支/传参(M273 r1)
r1 复审的两条 P2。
## P2-1:外部实例不再一律 isolated
`versions/<id>/` 里出现 `mods` / `saves` / `config` / `options.txt` 任一 ⇒ `isolated`
(gameDir = `versions/<id>`);否则 ⇒ `shared`(gameDir = 游戏根 `.minecraft`)。
判据只 `exists()`,只读,不改盘;写盘仍然只在用户第一次改设置时(`ensure_profile`)。
- 判据收在 `host/game/discovered.rs::base_mode()`,签名 `(env, id, version_id)`:
**有档案的实例返回全局 `instances.mode`**(D20 原口径,一字不变),自动发现的按痕迹判。
- 派生运行目录的五处(启动 / mods / saves / addon install / addon pack)从传
`global_mode` 改成传 `base_mode` ⇒ 「读到的存档/模组在哪」与磁贴上的 ISOLATION 同一份判据。
- `isolation_of(env, id, version_id)` 多收一个版本 id:实例文件优先,缺省 `base_mode`。
于是懒创建档案(只写 launch 段、isolation 仍为空)之后,磁贴不会从「共用目录」翻回「独立目录」。
- 磁贴/详情:自动发现的实例多一句「运行目录由盘上已有的文件决定」(i18n 中英);
按启动时再弹一条说清**这一次**用哪一边(`pages::instances::discovered_run_toast`,
同文案在屏不重复叠加)。
- `dto::DISCOVERED_ID_PREFIX` / `is_discovered_instance` 成为「自动发现」的公开判据
(不给 `InstanceSummary` 加字段:那要改 24 处结构体字面量,跨 agent / cli)。
## P2-2:空态用例的覆盖面
r1 复审实测:把 `!block.rows.is_empty()` 改成 `block.rows.is_empty()`(0 实例时反而没按钮),
当时三条用例全绿。现在:
- `detail_empty` 拆成 `empty_not_selected` / `empty_no_instances` 两个函数,分支各自可断言;
- `the_cta_lives_only_in_the_no_instances_branch`:CTA 只在「一个都没有」那一支、
「没选中」那一支必须是 `Vec::new()`,且分派必须写成 `if has_instances {`(取反即红);
- `the_page_asks_the_empty_state_with_the_negated_row_count`:两条调用点(宽窗 + 窄窗)
都必须传 `!block.rows.is_empty()`,条数也钉住(多一条即红)。
- 两个变异已自证:传参取反 ⇒ 后一条红;CTA 挪支 ⇒ 前一条红(红原文见交付报告)。
## P3
- `java::target_major()` 改读并集快照:只有自动发现实例的用户原来会拿到默认 JDK17 而不是
该版本要求的 JDK(`profiles.json` 是空的)。有档案时顺序与旧写法一致。
- `instances()` 全量扫仍在 UI 线程——本轮不动,刷新时机与代价写进报告(塔另开一条)。
## 结构
`host/game.rs` 394 → 286 行:自动发现那一簇(痕迹判据 / 扫出来的版本 / 合成视图)搬到
`host/game/discovered.rs`(138 行),只为守 400 行硬线;`game::base_mode` 仍是同一条路径。
## 门禁
app-iced lib 973 passed / presenter all-targets 386 passed / i18n 36 passed /
fmt 0 / clippy -D warnings 0。
|
2026-11-12 14:21:30 +08:00 |
|
AskaEth
|
5479f25068
|
feat(presenter): 实例清单改并集(档案 ∪ versions/*)+ 空态接导入向导(M273)
用户实测:HMCL 能看见 `.minecraft/versions/樱落怡然客户端`,ULL 的实例页是空的
(日志 `instances=0`)。根因不是路径找错,而是 `host::game::instances()` 只列
`profiles.json` 里的档案,第三方启动器装好的版本在界面上根本不存在。
## 列表改并集(presenter/src/host/game.rs)
- `versions/*` 里**能解析出清单**(`<id>/<id>.json` 的 id 与目录名一致、继承链走到底、
符号链接不越界)且没有被任何档案绑定的版本,就地合成一条实例视图:
显示名 = 目录名、`version_id` = 目录名。扫描复用 `core::game::adopt::scan`,
没有另写一套目录遍历。
- 只读:合成视图不建目录、不写 profiles.json、不碰 versions/ 一个字节。
- 档案优先:某版本已被档案绑定时只按档案出一条(名字/隔离档/启动参数都保留),不重复列。
- 坏目录(无清单 / JSON 坏 / id 不符 / 缺父 / 越界)不当实例;`versions/` 读不出来也只是
退回「只有档案」的旧行为,不让实例页报错。
- 外部实例的视图 id 用 `ext-<fnv64>`:`is_valid_instance_id` 只收 `[A-Za-z0-9_-]`,
而版本目录名可以是中文(现场就是),直接沿用版本 id 会让**启动**在读实例设置时就失败。
## 懒创建档案(只有第一次改它才写盘)
`set_isolation()` 与 `apply_launch_args()` 的「找不到实例」改成 `game::ensure_profile()`:
外部实例第一次改隔离档 / 启动参数时才写 profiles.json(幂等,id 与视图 id 相同)。
## 空态可见入口 + 文案(app-iced / i18n)
- 空态分两种:列表里有东西但没选中(原「未选择实例」)与**一个实例都没有**
(新:「还没有实例」+ 实心主行动「导入已有版本」)。用户现场看不到任何入口正是前一种文案
在误导。
- `adopt_action()` 成为「导入已有版本」意图的唯一产地,页头按钮与空态主行动共用它。
## 门禁
`cargo test -p unlucky-app-iced --lib` 971 passed / `cargo test -p unlucky-presenter
--all-targets` 384 passed / `cargo test -p unlucky-i18n` 36 passed /
`cargo fmt --all --check` 0 / `cargo clippy -p unlucky-presenter -p unlucky-app-iced
--all-targets -- -D warnings` 0。
|
2026-11-12 13:13:53 +08:00 |
|
AskaEth
|
751ae80f28
|
chore(release): 版本号 0.2.5(动效第一批 + 发布链路三处根因修复 + 平台测试收口)
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 12:06:46 +08:00 |
|
AskaEth
|
1c3fc2bdd9
|
Merge branch 'feat/quietspawn2-quiet-child-spawn-for-motion'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 12:02:29 +08:00 |
|
AskaEth
|
634955d323
|
fix(app-iced): 探针与字体测试的子进程改走 platform::child::quiet(M272)
`cargo test --workspace` 在合并 tip f8d83d1 上被 child_quiet_guard 抓住两处绕开
`platform::child::quiet` 的子进程构造:
① `platform/motion.rs` 的 OS「减少动效」探针(M256 新增,Linux gsettings / macOS
defaults)是产品代码真回归:`run()` 里裸 `Command::new(program)`。改走 `quiet(program)`。
`quiet` 返回的就是普通 `Command`,`.spawn()` 的 `Child` 仍归 `run` 自己,所以
**300ms 超时 + 超时 kill + wait 一个字没丢**;`OnceLock` 缓存、单调用点、不在帧路径
这三条性质也原样保留(这次只换了构造器)。
② `fonts/tests.rs` 里重起自身 test binary 的那处同样改走 `quiet`;`.output()` 的管道
语义不变(已实测子进程断言仍然「1 passed」)。没有往 ALLOWED_ROLES 加白。
|
2026-11-12 11:28:56 +08:00 |
|
AskaEth
|
f8d83d1f77
|
Merge branch 'feat/motion1-page-transition-and-dialog-motio'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 10:52:05 +08:00 |
|
AskaEth
|
add8617494
|
fix(app-iced): 修 about_center 的平移项(模态从「斜滑」回到「绕中心缩放」)+ 补几何断言
复审 r3 的 P1:about_center 写成 T(-c)·S·T(c),按 iced_core 的「先右后左」算出来是
p → s·p − (1−s)c,不动点在 (−cx,−cy)(窗口外的镜像点)⇒ 卡片开框时从左上斜滑约
(1−s)·c,而不是绕中心缩放。改成 T(c)·S·T(-c)(= s·p + (1−s)c,不动点就是 c),注释同步。
根因是 zoom.rs 里 0 条 #[test](矩阵从没被断言过,967 条全绿抓不住)。新增
tests::about_center_keeps_the_center_fixed:中心不动(<1e-3)、尺寸 = 倍率、缩小时左上角
收向中心;三组矩形 × 三档倍率。把 translate 换回去即红。
顺带补齐「抓得住/抓不住」:components/motion/** 与 motion_sync.rs、platform/motion.rs 里
31 条用例现在全部两侧都有(脚本核对 missing=0)。
|
2026-11-12 10:50:04 +08:00 |
|
AskaEth
|
b6127b4735
|
fix(app-iced): 补回被变异回滚误删的两处修复(钉住档位 + 退场起点)
上一提交把一个中间态(BYPASS_PROBE + serialize 未删、Modal::Closing 无 from)提交了:
变异实验后我用 git checkout -- 复原,而那些修复当时还没入库,于是被一起抹掉,
直接用例把这件事抓了出来(3 条恒定红)。现在按正确内容重做:
- delete motion::serialize()(空操作)
- FORCED_SCALE: Cell<Option<MotionScale>>,configure 里钉住优先
- Modal::Closing { start_ms, from },退场从当前进度连续缩到 0
(教训记在报告:变异只在已提交的树上做,别在未提交的树里 checkout。)
|
2026-11-12 10:50:03 +08:00 |
|
AskaEth
|
787cb16189
|
fix(app-iced): 删掉空操作串行门 + 钉住档位贯穿产品启动路径 + 退场起点接当前进度
复审 r2 的三条:
1) 删掉 motion::serialize() 那把串行门:PUMP/LAST_FRAME 都是 thread_local,libtest 每用例
一线程 ⇒ 它改变不了任何结果(ULL_MOTION=off 下握门照样红)。留一个不起作用的同步原语
比没有更坏。subscription.rs 与 motion_sync.rs 的 idle_app 恢复原签名。
2) 真实根因是「喂给 configure 的值」:idle_app 先钉 STANDARD,随后 on_boot →
configure_motion(app/update.rs:276)→ boot_env::motion_scale(..) 再定一次档,把钉住覆盖掉。
改成 BYPASS_PROBE(bool) → FORCED_SCALE(Cell<Option<MotionScale>>):configure 里钉住优先,
于是三条定档路径(configure_for_test / on_boot / boot_env 的 env·后端 tier)都穿不过去。
两条用例钉住它(纯合成 + 真 Message::Boot),删掉「钉住优先」两处都红。
3) P2-2:Modal::Closing 记住起手进度 from,progress = from*(1-eased) —— 进场途中被关掉时
从当前进度接着缩,不再先跳到 1.0(反向硬跳)。新用例
closing_during_the_entrance_does_not_jump_to_fully_open,把 from 改回 1.0 即红。
|
2026-11-12 10:50:03 +08:00 |
|
AskaEth
|
c21c6df51b
|
fix(app-iced): 动效全局态按「测试隔离」修 —— 串行门 + 探针旁路(复审 P2-5 返工)
tip 8b6f090 上 7 条用例在 -j 2 下红,根因两层:
1) P2-5 引入的 configure_for_test 只挡调用点,而 App::update(Boot) 会走**产品** configure
再按真探针定档 —— 本机(RDP 会话)SPI_GETCLIENTAREAANIMATION 报「动画关」⇒ 档位被压成
NONE ⇒ 位移恒 0 ⇒ 门槛 B/C 的守卫全红(日志里 [motion] scale=0.00 就是它)。
2) 动效全局态(档位 + 进程级时间轴)在并行用例间共享,谁先写谁赢。
修法(判据一条没动):
- components :: motion :: serialize():**跨模块共用的唯一一把**串行门,subscription.rs 与
update/motion_sync.rs 的用例都取它(idle_app 把 guard 一起返回,活到用例结束)。
- configure_for_test 置 thread-local BYPASS_PROBE,configure 读它 ⇒ 产品启动路径也不会再按
探针改档;reset() 清旁路,供「故意走真探针」那条用例使用。
- NONE 仍要求严格 0、STANDARD 仍要求 12~20 帧与端点归位。
|
2026-11-12 10:50:03 +08:00 |
|
AskaEth
|
1edf645c2f
|
fix(app-iced): 复审 p2 五条(方向取轨序 / 退场重开 / 遮罩口径 / 恒真断言 / 探针隔离)
- P2-1 方向改由**导航轨序**决定:新增 nav_rank(route)(NavItem::ALL 下标),
Pump::observe 收 (screen, rank, detail),forward 比较 (rank, key) 而非只比屏键 ——
修掉 Logs↔Settings 的左右镜像;Download↔Content 同 rank,仍判同级水平(已登记偏离)。
- P2-2 退场途中开新框:observe_modal 先当场收尾(Hidden + 待关)再起 Opening;
sync_motion 收尾时若队列里还有框就不把 dialog_model 置空。
- P2-3 只改文字:遮罩起手是**整块画布色的不透明矩形**(把页面内容遮住),不是透明看不见。
- P2-4 删掉恒真断言(单个 Option 的 <=1),写明「不累积」是字段形状 + mem::replace 给的;
tests/timeline.rs 与 tests/modal.rs 的每条用例都补了「抓不住」。
- P2-5 新增 motion::configure_for_test(绕过 OS 探针),用例一律走它;
另留一条故意走真探针的用例证明 Reduced ⇒ NONE 的合成仍在产品入口上。
- 顺带:pump 的模态状态机拆到 pump/modal.rs(守 400 行硬线)。
|
2026-11-12 10:50:03 +08:00 |
|
AskaEth
|
4a77f1b421
|
fix(app-iced): 退场窗口内 Esc 也被吃掉(补键盘入口的闸门)
模态退场期间逻辑状态已关(state.dialogs 空),但 Esc 走的是 Message::Agent(Key) 那条
独立路径,不经过 on_dialog 的 modal_closing 闸门 ⇒ 那 150ms 里 Esc 会落到 AI 栏去收起面板。
escape() 顶部加同一道闸门(返回 true = 被吃掉),并给退场用例补一条断言。
|
2026-11-12 10:50:02 +08:00 |
|
AskaEth
|
098f3b8946
|
fix(guards): 把 unsafe-scope 的说明注释压缩到 397 行(守卫文件自己也要过 400 行红线)
M256 加 EXPECTED 一行后本文件 401 行,触发 guards.yml::file-size 自己那条门禁。
只合并注释里的重复表述(词法器段 8→6 行)+ 去掉两处纯分隔空注释行;
判据逻辑、shell 片段、EXPECTED 的三条路径一字未动。
|
2026-11-12 10:50:02 +08:00 |
|
AskaEth
|
b1c09c4c91
|
feat(app-iced): 页面切换按导航深度定向 + 模态缩放淡入(M256 MOTION1)
- transition.rs:唯一一张动效表。同级换页走水平(±12 / 旧页 ∓8)、进深层走纵向
(新页 +16 上浮 / 旧页 -8),进腿 medium2/medium1(300/250),退腿 short4→medium1(更短),
进用 Standard、退用 StandardAccelerate。幅度全部取自 Space 令牌。
- pump.rs:Slot 三互斥槽 → Kind(Boot/Peer{forward}/Push/Pop)+ 两腿轨道;新增模态状态机
(Opening/Open/Closing,展开后不占帧,退场走完交回一次 take_modal_closed)。
- components/motion/zoom.rs:绕中心缩放的自绘控件(renderer.with_transformation,
tiny-skia 与 wgpu 都实现了,文字也真的跟着缩放);scale==1.0 时不套变换 ⇒ 出图基线不变。
- pages/mod.rs:page_with_settings_transition(ui, current, previous, …) 叠
stack![opaque(旧页@退场位移), 新页@进场位移];旧函数只剩一句委托(blast radius 不变)。
- App.previous_view:换页时用 std::mem::replace **移动**旧模型(冻结快照),过渡结束当拍
置 None(不随换页次数增长)。
- 模态:逻辑状态当拍关(state.dialogs 立刻空),画面多留 150ms;退场期间弹窗消息被闸门吞掉。
- platform/motion.rs:系统「减少动效」三态探针(Windows=SystemParametersInfoW,
Linux/macOS=子进程+300ms 超时),OnceLock 缓存;Unknown **不降级**(探针失败不静默关动效)。
- MotionScale::HALF 语义核清并写进 design 文档:是**时长减半**(更快),不是半速。
|
2026-11-12 10:50:02 +08:00 |
|
AskaEth
|
5a182e80de
|
Merge branch 'feat/cachedim-rust-cache-key-includes-image-d'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 10:36:30 +08:00 |
|
AskaEth
|
a6060e7649
|
fix(cache): 发布侧 key 只取 inputs.os 且 tag run 只读不写(M270 r1 复审 P2)
r1 复审(p2-1items)指出:tag 触发的 run 只能读 main 或同名 tag 的缓存,而 main 上
没有任何 job 跑 ubuntu-22.04 / windows-11-arm 这些标签 ⇒ 发布侧四个槽位每次发布都是冷编,
写出的 target 缓存永远拿不回来,还会占 10 GB 配额把 main 的热缓存 LRU 挤掉。r0 的
「key = inputs.os + inputs.slot」既没解决死缓存,还把 Windows 借 main 热缓存的路掐了。
- 发布侧 key 改成 `${{ inputs.os }}`:linux-x64(22.04) 与 main 的 ubuntu-latest 仍然不同
key(M269 那条必须断),而 windows-x64(windows-latest) 与 main 的 ci/build 重新同 key
—— 同镜像,借它的热缓存安全(M270 前 Windows 一直走这条且产物是绿的)。
- 发布侧加 `save-if: ${{ github.ref_type != 'tag' }}`:tag run 一律不写(写出来是死缓存),
只有 main 上的 dispatch 预热会写 —— 那次 run 的作用域是 main,之后的 tag run 才读得到。
刻意不用 `cache-targets: false`:那会把 Windows 借热缓存的路一起掐掉。
- ci.yml 侧只修 Nit:`Linux-X64` → `Linux-x64`(node 的 os.arch() 是小写)。
- docs 照实重写:tag 缓存作用域、发布侧每次冷编的必然性、预热怎么做、只读不写防什么、
「同一镜像标签但底层悄悄升级」这档剩余风险与手动 bump 办法;并补回 M164 MSYS 报错的
原文(可搜索的指路)。新的 key 演算:改前主 key 相同 2 对(跨镜像 1 对)/ 跨镜像前缀
命中 2 组;r1 = 1 对(全是同镜像 windows)/ 0 组。
|
2026-11-12 10:31:23 +08:00 |
|
AskaEth
|
93dc27bed8
|
ci(cache): rust-cache 的 key 带上镜像维度,发布构建不再吃 24.04 编的 target/(M270)
M269 已端到端定性:`aws-lc-sys` 的 `bcm.o` 在 glibc≥2.38 的头下编出 `__isoc23_sscanf`,
被从 24.04 的缓存恢复到 22.04(glibc 2.35)去链接 ⇒ undefined。根因是 `Swatinem/rust-cache@v2`
的 key 只有 `-Linux-X64`:`ci.yml` 的 `build`(ubuntu-latest = 24.04)与 `release-build.yml`
的 `build`(ubuntu-22.04)同 job id、同 rustc、同一份 `Cargo.lock` ⇒ key 逐字节相同。
- 7 处 `Swatinem/rust-cache@v2` 全部加显式 `key:`(发布侧 `inputs.os-inputs.slot`,CI 侧矩阵
维度或该 job 的字面量镜像标签)。`key` 落在 key 的**前缀段**,故主 key 与兜底 `restoreKey`
两条恢复路径一起分开。
- 演算判据(按 config.ts 复刻拼接,12 站点 × 2 锁状态两两比对):改前主 key 相同 2 对、
restoreKey 跨站点前缀命中 4 组;改后均为 0。
- 机制注释写进两个 workflow(`-C metadata` 不含 glibc ⇒ 恢复后 cargo 判 fresh 的因果链),
完整推导、`cache-targets` 的取舍结论与「抓得住/抓不住」写进 packaging/docs/build-and-ci.md §3。
- 两个 workflow 仍 ≤400 行(397 / 399):加注释腾地方时按「判据一字不动、只并句」精简了
M164/M171/M184/M147 几处既有注释。
|
2026-11-12 10:18:06 +08:00 |
|
AskaEth
|
14e8cc4ef2
|
Merge branch 'feat/releasename-explicit-run-name-for-releas'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 09:29:04 +08:00 |
|
AskaEth
|
e2d8fd9593
|
ci(release): 发布工作流加显式 run-name,Actions 列表不再显示提交信息(M268)
release.yml / release-guard.yml 之前没有 run-name,GitHub 默认拿提交信息当运行名,
于是 v0.2.4 那条把「孤儿提交 / 无父 / 树 = dev <hash>」这类快照模型术语贴到了公开界面。
- release.yml: tag push / tag ref dispatch => `Release v0.2.4`;分支 ref dispatch => `Release 试跑 main(分支 ref,不发布)`
- release-guard.yml: push main / dispatch => `Release guard · main`;PR => `Release guard · PR #123`
- release-publish.yml: workflow_call 不产生自己的 run,故不加(文件头写明理由)
- 只新增 run-name,所有已有 `name:` 一字未动
- packaging/docs/artifacts-and-version.md 新增 §2.1:里程碑快照提交信息只写版本 + 一句话,附正/反例
|
2026-11-12 09:29:01 +08:00 |
|
AskaEth
|
ba5403a056
|
Merge branch 'feat/gentkey-multiline-pinned-key-output'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 09:28:49 +08:00 |
|
AskaEth
|
66eb912df0
|
fix(packaging): --generate-key 打印的 PinnedKey 改成 rustfmt 保持的多行形态(M265)
`--generate-key` 与「信任表是空的」报错里给人粘贴的 `PinnedKey` 原来是单行:
PinnedKey { key_id: "…", public_key_hex: "<64 hex>" },
这一行有 113+ 字符(即使 key_id 为空),超出 .rustfmt.toml 的 max_width = 100,
所以 `cargo fmt` **必然**把它拆成 4 行 —— 人贴单行 → 下一轮 fmt 改动 → 谁忘了跑 fmt
就把 fmt 红带进 main(T30 那次就是这条路径)。
改的是「打印模板」与文档,不是解析器(KID_RE 的 \s* 本来就吃两种形态):
- 新增 render_pinned_key(),字段各占一行、元素缩进 4/字段缩进 8、结尾逗号,
与 trust.rs 里 cargo fmt 生成的形状逐字一致;两条打印路径都走它。
- 提示文案不再暗示「贴完还要 fmt」,改成「贴完直接提交,不用再跑 fmt」。
- docs:signing.md §3 与 signing-runbook.md §2/§4/§8 的「那一行」→「那一段(4 行)」,
并在 runbook §7 明写这条断言在没装 rustfmt 时的退化边界。
- --self-test 新增形态断言:① 渲染结果 == 4 行期望字面量 ② 形状与 trust.rs 现存条目
(字段值抹平后逐行)一致 ③ 单行必然超 max_width、多行每行都不超 ④ 贴进标记之间能解析
⑤ 有 rustfmt 时在临时副本上真跑一遍,要求粘贴块字节不变(没有则打 note 跳过,不装成验过)。
|
2026-11-12 09:26:05 +08:00 |
|
AskaEth
|
cb051ca933
|
Merge branch 'feat/releasefix-windows-pe-version-readback-r'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 09:25:37 +08:00 |
|
AskaEth
|
bb572ac2dd
|
refactor(release): PE 读回步骤体拆进 packaging/ci-pe-version-gate.sh(release-build.yml 400 行守门)
复审 P1-1:main 上 release-build.yml 是 400 行(贴线),上一版 +23/−10 顶到 421,越过
guards.yml 的 file-size 硬门禁(LIMIT=400 是用户硬约束,不许抬)。按复审给定的方向「拆」:
- 新增 packaging/ci-pe-version-gate.sh(59 行):读回两个产物 + 从 Cargo.toml 同源取版本号 +
失败逐条转 ::error:: 注解(上限 30 行)+ 非 0 退出;找不到 python 仍 fail;
临时日志用 mktemp + trap 清理,仓库根按脚本位置反推(不依赖 cwd)
- release-build.yml 那一步只留 bash packaging/ci-pe-version-gate.sh "$BUILD_DIR" →
文件 421 → 396 行(比 main 的 400 还少 4)
- 判据一行没改:仍然读产物字节、仍然由 check_pe_version.py 决定红绿、仍然非 0 退出
复审 P2-2:check_pe_version.py 的「什么时候退出非 0」清单补上第 7 条第 ③ 项
(签名后不足 16 字节、读不到 dwFileVersion,check():124-125)。
|
2026-11-12 09:25:30 +08:00 |
|
AskaEth
|
47386ef2ff
|
fix(release): PE 版本读回在 Windows runner 上崩在 print 编码上(判据内部一条未降)
v0.2.4 Release run 43031591576 的 windows-x64 / windows-arm64 都红在「PE 版本资源读回」,
而 cargo build 是绿的、注解 API 上只有一句 Process completed with exit code 1.
根因:check_pe_version.py 把中文打在 stdout/stderr,本机 cp936 能编,Windows runner 是 en-US
镜像(代码页 1252),print() 直接 UnicodeEncodeError —— 判据全过也红,而且崩在 print 上,
日志里只剩 traceback。同仓 5 个 packaging 脚本已有 reconfigure(errors=backslashreplace) 守卫,
Windows 槽位跑的脚本里只有这一个漏了。
- check_pe_version.py: safe_streams()(管道钉 UTF-8、终端只放宽 errors)、
每产物摘要行 flush(stderr 的判据不再跑到摘要前面)、模块 docstring 补判据清单;
--self-test 增加「版本资源齐全的合成 PE 必须绿」正控 + 真子进程 cp1252/cp437 回归(红/绿两路)
- release-build.yml: 读回步骤先 ls -l 落产物大小,输出 tee 后逐行转 ::error:: 注解
(下次红能看到原因,不再只有退出码)
- 判据未降:仍然是读产物字节、仍然 exit 1,未改任何断言
实测:RUSTFLAGS='-C target-feature=+crt-static' 的 release 产物本机读回 exit 0;
同产物同命令在 PYTHONIOENCODING=cp1252 下,修前 exit 1 + traceback、修后 exit 0。
|
2026-11-12 09:25:30 +08:00 |
|
AskaEth
|
03b644fa0c
|
Merge branch 'feat/deadcode-linux-cfg-gate-listing-helper'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 22:58:04 +08:00 |
|
AskaEth
|
150243e0b6
|
fix(core): listing() 与唯一调用点同 gate #[cfg(windows)],消掉 Linux 上的 dead_code
CI 的 clippy (ubuntu-latest) 退出码 101 的唯一首因:
error: function `listing` is never used
--> crates/core/tests/adopt/surface_cases.rs:147:4
note: `-D dead-code` implied by `-D warnings`
`listing()` 的 4 处调用(43/65/69/73 行)全部落在 `#[cfg(windows)]` 的
`a_junction_on_the_instances_dir_must_not_write_into_the_game_root` 里,helper 本身没 gate
⇒ Linux 上调用点整块编掉,helper 变死代码;Windows 上跑不出来(本地一直绿的原因)。
最小改法:只给 helper 加 `#[cfg(windows)]`,与调用点对齐(同仓既有写法见
tests/launch/watchdog_cases.rs 的 `chatty_lines()`)。不删用例、不改断言、不给整个 mod 加
cfg、不用 #[allow(dead_code)] 静音。
验证:Linux 容器 ull-linux-check:bookworm 上 cargo clippy --workspace --all-targets
-- -D warnings;Windows 上 cargo clippy -p unlucky-core --all-targets -- -D warnings
且 adopt 测试清单仍有那条 junction 用例(4 处调用仍在编)。
|
2026-11-11 22:57:43 +08:00 |
|
AskaEth
|
a8ef1c70af
|
Merge branch 'feat/testport-three-platform-dependent-test-f'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 22:43:22 +08:00 |
|
AskaEth
|
d4959fd3d3
|
test(m262): 三条平台/时序依赖的测试改成确定性判据
① prefs.rs:custom 落点不再写死 D:/JDKs(Linux 上它是相对路径,会被拼到数据根后,
同一条断言在两个平台上验的是两件事)。改由临时目录给绝对路径;另加一条相对路径
必须按数据根解析的断言,把上一版抓不住的那一半补上。
② fonts/tests.rs:install::status() 读的是进程级静态槽,同 binary 的 page_messages
用例切 zh_cn 会真的发起下载并把它写成 Downloading ⇒ 前后快照比较与谁并行谁赢。
改成用 current_exe() 在**只有本用例的进程**里量,并断言前后逐值相等(任何写槽都算越界)。
③ fixture_cases.rs:目录 mtime 不再赌「写文件恰好落在另一个 tick」,改为显式钉 mtime
(t0/t1 整秒,对任何 filetime 粒度无余数)并逐 ns 比对。
三条都补了「抓得住什么 / 抓不住什么」。
|
2026-11-11 22:41:06 +08:00 |
|
AskaEth
|
98eb4c14c1
|
Merge branch 'feat/cinightly-nightly-ci-on-dev'
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
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 22:21:47 +08:00 |
|
AskaEth
|
8abe8c1fa3
|
ci(m266): r3 只修注释的事实性,不动触发面(复审 p2-3items)
- ci.yml: 换算口径统一成「19:17 UTC = 本地(UTC+8)次日 03:17」(cron 是 '17 19',
原先内联写 17:19 差 12 小时,同一文件两套换算互相打架),全文只留这一种写法
- ci.yml: 通知收件人按文档原话改(发给「最后修改 cron 语法的人」,不是「默认分支的维护者」)
- ci.yml: 新增边界 ⑤ public 仓库 60 天无活动 scheduled workflow 自动 disable,
这是「夜里那轮某天不再响且没人发现」的真实失效模式
- ci.yml: 「:30 也挤」无文档依据,改成文档原话(整点高负载 + 负载过高时排队任务会被 dropped)
- ci.yml: ③ 的 event 依赖断言改严:除 concurrency 组那一处与 runtime-smoke-macos 的 if 之外无 event 表达式
- docs: 归因改对——base(main) 上 :101 当时是对的,:246/:283 早就指偏(依赖安装步 / 一行注释),
行号锚点本来就在腐,不是本次改动造成
行为面零改动:on / concurrency / jobs 的键值行与 r2 逐字相同(本提交的 ci.yml 差异全部是注释行)
|
2026-11-11 22:21:30 +08:00 |
|
AskaEth
|
55f0e9bbf1
|
ci(m266): 按塔判决把 cron 挪到本地凌晨 03:17(cron 是 UTC,写 17 19 * * *),补默认分支边界与证据强度
- cron 从 '17 3 * * *' 改成 '17 19 * * *'(17:19 UTC = 本地 UTC+8 的 03:17),
文件头写死「本地每天 03:17」,不留让读者自己换算的口子
- 文件头第 ① 条写严厉:schedule 只对默认分支生效、跑的是 main 末梢,dev 未合入的内容不在这一轮;
「dev 已经有人天天看着了」是错的(全量矩阵在 dev 上一次都没跑过)
- 新增第 ④ 条:定时必须与 push main 分列 concurrency 组,因为取消不是红,
被静默取消的夜间入口会让「昨晚没报错」变成一句假话
- concurrency 注释里把结论降级为「GitHub 文档语义,未在真实 runner 上构造并发验证」
- docs/backend-cjk-evidence-matrix.md:三处 文件:行号 锚点改成作业名 + 步骤名(行号必再腐),
并加一条「锚点不用行号」的说明防复发
|
2026-11-11 22:21:30 +08:00 |
|
AskaEth
|
86746153f9
|
ci(m266): ci.yml 加夜间定时触发(schedule 17 3 * * *),补 T30 的全量矩阵缺口
- 只新增 schedule,push main / pull_request / workflow_dispatch 三条一字不改
- 文件头写清为什么是 schedule 而不是 push dev(成本/排队 vs T+1 发现延迟),这是故意的取舍
- concurrency.group 带上 github.event_name:定时与 push main 落在同一个 ref 上,
否则 cancel-in-progress 会让两者互相取消,夜跑被取消 = 那一夜没有结果
- 逐个 job 核过 github.event 依赖:只有 runtime-smoke-macos 的 workflow_dispatch 条件,
schedule 下按设计跳过;其余 job 无 event 表达式,夜间不空转
|
2026-11-11 22:21:30 +08:00 |
|
AskaEth
|
060910c8b7
|
Merge branch 'feat/fmtfix-trust-rs-pinned-key-rustfmt'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 22:11:18 +08:00 |
|
AskaEth
|
00965e19d2
|
fix(core): trust.rs 的 PinnedKey 交回 rustfmt 排版(多行字段)+ 自测锁住多行解析
v0.2.4 推 main 后 CI 的 fmt job 红:cargo fmt --all --check 退出码 1,唯一 diff 在
crates/core/src/update/trust.rs:60 —— PRODUCTION 里那条 PinnedKey 被写成单行。
* trust.rs:由 `cargo fmt --all` 直接生成(不是手写对齐)。git diff 只有这一处;
两个 PINNED-KEYS 标记行的字形与行位置未动;public_key_hex 与 HEAD 逐字比对 identical。
* sign_manifest.py --self-test:补一条多行(rustfmt)形态样例的解析断言。解析器本身无需改
(KID_RE 字段之间是 \s*,本来就跨行),但旧自测只喂单行样例 —— 变异重放(换成单行整行正则)
时旧自测全绿、新断言红,正是这次事故的形状。
验证(真实退出码):cargo fmt --all --check = 0;python packaging/sign_manifest.py
--check-pinned-keyring --key <私钥路径> = 0;signing_ready_selftest.py = 0;
signing_ready_wiring_selftest.py = 0;sign_manifest.py --self-test = 0;
cargo test -p unlucky-core --lib update = 0(114 passed / 0 failed)。
|
2026-11-11 22:01:26 +08:00 |
|
AskaEth
|
38c568d6b7
|
Merge branch 'feat/gatefix-presenter-wildcard-dependency'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 21:52:13 +08:00 |
|
AskaEth
|
3c99b55110
|
fix(presenter): 给 [dev-dependencies] 的 unlucky-core 补 version 修 cargo-deny wildcard
crates/presenter/Cargo.toml 的 [dev-dependencies] 路径依赖漏写 version,
deny.toml 的 [bans] wildcards = "deny" 因此报
error[wildcard](cargo deny check licenses bans sources 退出码 2),
CI Guards 的 cargo-deny 步红。
补上与其他路径依赖同款的 version = "0.2.4",features 保持 update-test-key 不变;
该条仍只在 [dev-dependencies],不进发布二进制。
|
2026-11-11 21:49:55 +08:00 |
|
AskaEth
|
68362e5408
|
feat(core): 钉入生产签名公钥(key_id ull-release)+ .gitignore 防私钥入库
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 21:35:10 +08:00 |
|
AskaEth
|
c9eed39bd6
|
docs: 新增 CHANGELOG(自上而下,0.2.4 起;早期版本按 tag 从简)
|
2026-11-11 21:28:33 +08:00 |
|
AskaEth
|
4b184df1ba
|
chore: 版本号 0.2.4(里程碑快照;本批含存档截图/强调色/回车提交/GPU 门禁/签名就绪度)
|
2026-11-11 21:26:15 +08:00 |
|
AskaEth
|
fa30dd9450
|
Merge branch 'feat/m255-signready-signing-readiness-checker'
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
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 20:22:55 +08:00 |
|
AskaEth
|
bc1b09b776
|
fix(packaging): 播报 workflow 的种子只在 main 注入 + --key 用法错回 2 + 指针修正(M255 r1 P2-1/2/3)
P2-1(安全):`signing-status.yml` 原先把生产签名种子注入到 `pull_request` 与任意 ref 的
`workflow_dispatch` 运行里,而 CI 跑的是那个 ref 自己的代码 ⇒ 任何能推分支的人都能把种子搬走
(这把种子能给全量已装客户端签可信清单)。改成:
- 去掉 `pull_request` 触发;
- 注入行加 ref 门:`github.ref == 'refs/heads/main' && secrets.… || ''`;
- 非 main 用 `ULL_SIGNING_KEY_WITHHELD` 说明原因,于是 ② 读「未判定(未注入)」而不是「没配」。
runbook §2 步② 加硬顺序:先合这笔、再录 Secret。
P2-2:`--key` 读不了(不存在 / 是目录 / 非 UTF-8)曾在 main 的 try 之外抛 traceback + rc=1,
与「未就绪」同码;现在统一 rc=2 + 一行说明,并补用例 `key_unreadable`。
P2-3:4 处文案指针修正(`signing-ready.yml` → `signing-status.yml`;§3 → §2 步③;§1–§3 →
§2 步①–③;§4 → §2 步⑤ / §5)。新增两条机检:`section_refs`(输出的 `§N` 必须是 runbook 真实
章节号)与 `signing_ready_wiring_selftest.py`(文档里的 `packaging/…`/`crates/…`/`.github/…`
路径必须真存在),后者另带三处变异重放:去掉 ref 门 / 加回 pull_request / 改坏文档路径 ⇒ 必须红。
顺带修掉 r1 复审指出的两处小问题:`--release-assets` 里只有 `manifest.json.sig` 时的子串假读数
(先剥 `.sig` 再判清单)、③ 那个从不参与判定的 bool 保持原样(无行为影响)。
selftest 拆成两份(400 行红线):判据本体 + 接线/文案。guards.yml 仍 399 行零改动。
|
2026-11-11 20:17:04 +08:00 |
|
AskaEth
|
bff48e27fa
|
feat(packaging): 签名就绪度播报 + 操作手册(M255)
M254 查出 `trust.rs` 的 `PRODUCTION` 表在 PINNED-KEYS 标记之间是空的 ⇒ 任何正式二进制都验不过
任何清单(fail-closed)。工具链早就齐了,缺的是操作步骤与可见性;本条补的就是这两样,不生成钥、
不改 Rust、不碰 release-publish 的判据。
- `packaging/check_signing_ready.py`(只读、零第三方依赖):三行读数 ① 信任表空不空 ② 环境有没有钥
(顺手对账「这把钥在不在表里」,种子一个字节都不打印)③ 最近一次发布的 .sig/清单痕迹(取不到写
「未知」)。退出码 0 就绪 / 1 未就绪(打印 fail-closed 后果 + 一行修复命令)/ 2 用法错。
- `packaging/tests/signing_ready_selftest.py`:判据自测,含**两个方向的变异重放**(退出码永远 0 ⇒
判据①必须红;永远 1 ⇒ 判据②必须红)与接线判据(没人 uses: 它、没有 error 级注解、就绪度那步恒 exit 0)。
- `packaging/docs/signing-runbook.md`:人类照做的 5 步 + 每步判据 + 11 条已知坑 + 没验的部分(无任何密钥值)。
- `.github/workflows/signing-status.yml`:把三行搬进 job summary 的**播报** workflow。guards.yml 已
399/400,与 gpu-evidence.yml 同先例另开文件;刻意不声明 workflow_call:,未就绪用 ::warning::、
就绪度那步恒 exit 0,明确「不应被设为 required」。
- `packaging/README.md` / `packaging/docs/signing.md`:索引与指针。
|
2026-11-11 20:05:45 +08:00 |
|
AskaEth
|
fbb663c87c
|
Merge branch 'feat/m252-fieldtouch-expose-on-submit-and-wir'
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
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 19:55:23 +08:00 |
|
AskaEth
|
9ce54a8119
|
test(app-iced): accent 回车守卫改判代码行并整取实参(M252 r2 P2-1)
上一轮(e2a9692)声称三处守卫都改了,实际只有 field.rs 与 rows.rs 进了提交:
`section/accent.rs` 的改写留在工作树里,被我自己重放 C4 变异时的 `git checkout --`
还原掉了 —— 提交面因此看不到它,那条守卫仍是 `body.contains(..)`,注释掉接线后它照样绿
(只有白名单顺手红)。先修过程、再修判据:
- `the_accent_field_wires_enter_to_commit` 改成 `code_line(body, ".on_submit(")` 取**代码行**,
再用 `call_arguments` **整取**实参并断言等于 `["accent_commit"]` —— 顺带堵上
「`accent_commit_none` 这类同前缀产地静默通过」。
- `code_lines()` 的边界文档换成实测口径(`app-iced/src` 9 处 `r#"`、8 处内容含引号;
全仓 `crates/**.rs` 291 处),并写清两个方向:内嵌引号 ⇒ 尾部注释漏剥 ⇒ **假绿**;
JSON 原始字符串同行有 `//` ⇒ 多剥 ⇒ **假红**。两种都由 `code/tests.rs` 钉住。
过程纪律(写进报告):**变异重放只在已提交的树上做**,否则 `git checkout --` 会把未提交的
修复一起扔掉,而「声称改了、diff 没动」正是这次被复审抓到的形状。
|
2026-11-11 19:55:21 +08:00 |
|
AskaEth
|
a17f6fb29f
|
test(app-iced): 接线守卫改判「代码行」,注释掉不再算接线(M252 r1 P1-1)
复审实测:三处接线守卫都是纯文本 `contains`,把真接线注释掉(行注释,或 `/*` 独占一行的
块注释)之后 943 条用例一条不红;`field.rs` 那处连 rustc 的 unused-variable 兜底也能用
`let _submit` 一起绕掉 —— 功能整条静默失效时,测试与 lint 两条线同时不响。
- `pages/layout/source/code.rs`(新):`code_lines()` 剥 `//` 与跨行/可嵌套 `/* … */`,
字符串与字符字面量整段保留(`"http://x"` 里的 `//` 不剥);`code_line()` / `has_code_line()`
给「某行代码的行首就是那个调用」这条谓词。边界写在模块文档:原始字符串里出现 `"` 是唯一
可能朝松方向错的形态(本仓 src 现无此写法),其余偏差都是假红。
- 三处守卫改用它:`text_field_forwards_the_submit_callback`(行首是 `on_submit_maybe(`
且实参不是 `None`、消息来自 `submit_message`)、`the_rename_field_wires_enter`(回车与
「改名」按钮的行都含 `commit_rename()`,且都不含第二种页面意图 —— 一并关掉 r1 P2-1
「Debug 自证撑不住判据」)、`the_accent_field_wires_enter_to_commit`(含产地那条代码行)。
- `submit_is_wired_only_at_the_two_call_sites` 的 `scan()` 换成同一谓词并跳过测试文件
(守卫自己的 fixture 里也有 `.on_submit(` 样本)。
- T29 顺手收敛 scope 内的同族旧断言:`pages/instances/tests.rs` 的行距/散文接线判据与
`pages/instances/tests/i18n.rs` 的五条取词判据改走 `code_text()`(一行改动、方向不变)。
|
2026-11-11 19:55:21 +08:00 |
|
AskaEth
|
92a3c1bbc8
|
refactor(app-iced): 「禁用不提交」的规则收进 Field::submit_message
原来 text_field 里内联着 `if state.is_disabled() { None } else { field.submit_message() }`,
那条分支在 Element 上量不到(没有内省接口)⇒ 等于没验。现在把状态作为参数交给
Field::submit_message(state),规则本身可单测:submit_is_opt_in 多了 Disabled 那一档断言
(接了回调 + 禁用 ⇒ None),text_field 相应少 3 行。
|
2026-11-11 19:55:21 +08:00 |
|
AskaEth
|
ebce41b2c5
|
test(app-iced)+docs(appearance): 强调色「已提交」那一档的出图开关
D30 要拍「回车之后」的样子,但出图通道没有键盘(渲染第 3~4 帧就落盘)。所以把强调色
的两档取证开关(ULL_APPEARANCE_ACCENT / …_COMMIT)收进 accent 自己的模块:第二档在灌值
之后补一次 Accent::commit(),它做的正是回车那条消息(Change::AccentCommit)在状态层做的事
(State::apply 开头的 commit)。拍的因此是同一个状态,不是另一条渲染分支。
顺带两处口径订正:
- state.rs 少了 3 行(它贴 400 行硬线),强调色的 env 开关不再散在 state 里;
- rows.rs 的模块文档把「名字规则」指到 core 的
game/saves/write/boundary::check_name(原来写的是 presenter)。
|
2026-11-11 19:55:21 +08:00 |
|