docs(m196): r2 复审 —— 给「无控制台宿主」那条下边界声明

复审指出 README §15 把两个**近似**形态(父进程句柄不继承 / 父子都无句柄)直接叫「服务形状」,
顶层又写「服务 / 计划任务」,读者会把它当成「真服务也测过」。补一段边界:① ② 都是近似,
真 SCM 服务与计划任务(session 0、no window station)、start /b、ConPTY 全部未测,结论靠
「句柄无效时 std 写返回 Ok」这条机制外推;第三方交叉读数(复审 r2 那组)单独标注来源,
不混进我自己的读数。

同一处过度概括也出现在 console.rs 的 NoConsole 文档(原文「④ / 服务 / 计划任务」),
按同一口径改成「④ 双击,以及其他没有控制台的宿主」并指向 README §15 的边界段。

只改文档,不动代码。
This commit is contained in:
tower-worker committed 2026-11-08 20:22:36 +08:00
1 parent 3fd33a672f
commit 26ba1579ab
2 files changed
+19 -12

No files matched your search

+17 -11
View File
@@ -166,14 +166,20 @@ CUI(3),`grep -rn windows_subsystem crates/` 当时零命中。同一个修复
`.github/workflows/ci.yml` 的 `build` job(PR 阶段就红,不必等到 tag)。
- 抓不住(如实记):只看那 4 个字节,不看 GUI 双击的实际观感——「不再多一个黑框」在本仓库的验证环境
(RDP / 无干净无控制台桌面)只能由 PE 头 + 进程/窗口枚举间接证明,人眼验收归真机。
- **既无控制台也不重定向的宿主(快捷方式 / 服务 / 计划任务):输出照旧无处可去,退出码不变**
(M196 r2 实测,覆盖 r1 复审 P2-3 提出的「退出码会变成 `EXIT_INTERNAL`(2)」——那条实测不成立)。
两种宿主形态各测两条命令,`child_exit_code` 全是 **0**:①父进程有 std 句柄但子进程不继承;
②父子都没有 std 句柄(服务形状,子进程实测 `stdin/stdout/stderr = 0x0`、`AttachConsole` 返回 0 /
`GetLastError=6`)。原因在 Rust std 的 Windows 实现:句柄取不到时 `Write`/`flush` 直接返回 `Ok`
(独立探针实测 `SetStdHandle(out)` = `0x0` / `0x1234` / `(HANDLE)-1` 三种取值,`write=Ok(())`、
`flush=Ok(())` 全成立),所以 `crates/cli/src/runner.rs` 那条「写失败 ⇒ `EXIT_INTERNAL`」的分支不会
被触发。结论:本 diff **没有**给 CLI 退出码引入可观察差异;真正的差异只有「没控制台就没人看得见输出」
这件事,而 CUI 时代在服务里同样是隐形的。想让这种情形有专属退出码/文案,得在 CLI 侧显式判一次能力
(`platform::win32::console` 的 `output_visible()` 就是现成的判据),那在 `crates/cli/**`、不在 M196 的
scope 内——这里只如实记录形态,不改结构。
- **既无控制台也不重定向的宿主:输出照旧无处可去,退出码不变**(M196 r2 实测,反驳 r1 复审 P2-3 的
「退出码会变成 `EXIT_INTERNAL`(2)」——那条是**纯推理、无实测**)。两个**近似**形态 × 两条命令,
`child_exit_code` 全是 **0**:① 父进程有 std 句柄、子进程不继承(`dwFlags=0` + `bInheritHandles=FALSE`);
② 父进程 `FreeConsole()` 后把 `in/out/err` 三个 std 句柄全置 0,再起子进程(子进程侧实测
`stdin/stdout/stderr = 0x0`、`AttachConsole` 返回 0 / `GetLastError=6`)。原因在 Rust std 的 Windows
实现:句柄取不到时 `Write`/`flush` 直接返回 `Ok`(独立探针实测 `SetStdHandle(out)` = `0x0` / `0x1234` /
`(HANDLE)-1` 三种取值,`write=Ok(())`、`flush=Ok(())` 全成立),所以 `crates/cli/src/runner.rs` 那条
「写失败 ⇒ `EXIT_INTERNAL`」的分支不会被触发。结论:本 diff **没有**给 CLI 退出码引入可观察差异;
真正的差异只有「没控制台就没人看得见输出」这件事本身。
**边界(别把上面读成「真服务也测过」)**:①② 都只是**近似**形态——真 SCM 服务与计划任务(session 0、
no window station)、`start /b`、Windows Terminal / ConPTY 全部**未测**;对它们的结论只能靠「句柄无效
时 std 写返回 `Ok`」这条机制外推,不是在那些形态里亲测。第三方交叉(**复审的读数,不是我跑的**):
r1 复审在 r2 用更干净的形态(父 `FreeConsole()` + 三句柄置 0 + 子进程
`DETACHED_PROCESS | dwFlags=0 | bInheritHandles=FALSE`)独立复现,产品二进制 `--version` /
`instances list` / `--help` 全 0(`logs list` 是 1,但重定向跑同一条也是 1 ⇒ 用户错误,不是写失败)。
想让这种情形有专属退出码/文案,得在 CLI 侧显式判一次能力(`platform::win32::console` 的
`output_visible()` 就是现成判据),那在 `crates/cli/**`、不在 M196 的 scope 内——这里只如实记录形态。