Commit Graph
769 Commits
Author SHA1 Message Date
AskaEth a0a35596c8 Merge branch 'feat/adoptwire-wire-adopt-wizard-entry-on-ins'
Dev Guards / 架构与合规守门(同 main 口径) (push) Blocked by required conditions
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Blocked by required conditions
Dev Guards / fmt (rustfmt --check) (push) Waiting to run
Dev Guards / clippy (ubuntu) (push) Waiting to run
Guards / core 零 UI 守门 (push) Blocked by required conditions
Guards / presenter 零 UI 守门 (push) Blocked by required conditions
Guards / design 零第三方依赖守门 (push) Blocked by required conditions
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Blocked by required conditions
Guards / 文件行数 ≤400 守门 (push) Blocked by required conditions
Guards / 依赖许可与来源(cargo-deny) (push) Blocked by required conditions
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Blocked by required conditions
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Blocked by required conditions
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 / 依赖许可与来源(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: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 / 文件行数 ≤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
Guards / 依赖许可与来源(cargo-deny) (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 证据 — 探测可用产物与硬件适配器 (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 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
AskaEth f7cfc8ded6 feat(app-iced): 单行输入框透出 on_submit,回车接上存档改名与强调色提交
债务台账 P18(M250 + M247 的共同缺口):iced 0.14 的 text_input 有 on_submit、
没有 on_unfocus(builder 逐条核过:on_input/on_input_maybe/on_submit/on_submit_maybe/
on_paste/on_paste_maybe,on_unfocus 零命中),而本仓的 text_field 只透出
on_input ⇒ 行内重命名只能点按钮、强调色拿不到「用户确认」这一下。

- field.rs:Field 变 `Field<'a, M>`(M 只被新加的 on_submit 用到,调用点的类型由
  text_field 的 on_input 一并推出,一行都不用改),回车消息唯一从 submit_message()
  取,`on_submit_maybe(None)` 是默认档 ⇒ 既有调用点逐位不变;禁用态与 on_input 同一
  条规矩被摘掉。
- 存档改名:回车与「改名」按钮共用 commit_rename() 一个消息产地,空名/原名不变/
  非法字符那几道校验因此对回车同样成立。
- 强调色:回车发 Change::AccentCommit(不是又一次 Accent(原文))⇒ 立即 commit(),
  把「打了一半就放着」的 #12 亮成红框 + 错行;逐键不报短错的规矩不变,落盘仍取
  上一次合法值。
- 判据:submit_is_opt_in(未接就是 None)、text_field_forwards_the_submit_callback、
  submit_is_wired_only_at_the_two_call_sites(白名单扫源码)、
  enter_takes_the_same_message_as_the_rename_button、the_rename_field_wires_enter、
  the_accent_field_wires_enter_to_commit、enter_commits_a_half_typed_value_and_
  persists_only_legal_ones。真键盘回车量不到(Element 无内省、造不出 iced::Renderer
  的测试替身),报告里单列。
2026-11-11 19:55:21 +08:00
AskaEth 026b9f6e94 Merge branch 'feat/m253-gpucigate-wire-gpu-shot-gate-into-c'
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 18:43:34 +08:00