main
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5f419bcb91 |
refactor(rename): pmon → inkwatch 全量改名(阶段 A:代码层,不碰数据层标识)
## 范围与边界 **本次只改代码层**:客户端包名、Android/iOS/macOS 标识、环境变量前缀、Rust crate 名、 类名/文件名、以及全部文案与注释。**数据层标识一处未动**(卷名 `pmon_pg_data`、 `POSTGRES_USER/DB`、连接串默认值),那些在阶段 B 单独做迁移,理由见下。 统计:**114 个文件、1687 处**替换 + **8 个文件/目录重命名**。 ## 改了什么 | 类别 | 旧 | 新 | |---|---|---| | Dart 包名 | `pmon_client` | `inkwatch_client`(41 个文件的 import 同步) | | Android namespace | `com.pmon.pmon_client` | `com.inkwatch.eink` | | Android applicationId | `com.inkwatch.eink` | **保持不变** —— 它本来就已是新名 | | Kotlin 包目录 | `kotlin/com/pmon/pmon_client/` | `kotlin/com/inkwatch/eink/` | | macOS bundle | `com.pmon.pmonClient` | `com.inkwatch.eink` | | 环境变量 | `PMON_*`(12 个) | `INKWATCH_*` | | Rust crate | `pmon-core` / `pmon_core` | `inkwatch-core` / `inkwatch_core` | | 动态库 | `libpmon_core.dylib` | `libinkwatch_core.dylib` | | 类/文件 | `PmonApp` `PmonColors` `PmonChannel` `PmonCollectorService` `pmon_agent.dart` … | `Inkwatch*` / `inkwatch_*.dart` | ## 三处**必须单独说**的判断 **① applicationId 刻意没动。** `build.gradle.kts:49` 有既有说明:水墨屏专属版用 `com.inkwatch.eink`,与主干通用版**刻意不同**,为的是两版本能在同一台手机上共存 (versionCode 各自递增,同包名会互相覆盖且降级被拒装)。它已经是新名,动它反而 破坏这个设计。我改的是 `namespace`(内部分包名,只影响编译期),并把它统一到同一个 `com.inkwatch.eink`。 **② 局域网 P2P 线协议前缀一起改了**(`PMON-PUNCH:` / `PMON-PONG:` / `PMON-FRAME:` → `INKWATCH-*`)。这**不是纯文案**:两端必须同时升级,否则新旧版本在同一局域网里互不 认。判断依据是这三个前缀只用于同一用户的两台设备直连,且当前尚未对外发布;若以后 要支持跨版本,正确做法是加协议版本协商而不是回退旧前缀。 **③ 数据层标识一处未动。** `deploy/` 下 28 个文件仍是 `pmon`,因为卷名/库名/库用户 的改动会直接导致连不上库或指向新空卷(`docker-compose.all.yml:26-29` 里写着: 改名会让 compose 去找 `inkwatch_pg_data` 这套新空卷,等于把生产清库)。实测当前 数据是 **2.5GB、128 个账号、58 台设备**,不是测试库。 ## 安全网 动手前做了完整备份: `.backup-rename-20261108/pmon_before_rename.dump`(`pg_dump -Fc`,152MB), 并记录了迁移前各表行数基线,供阶段 B 逐表比对。 ## 验证 客户端全量 **595 通过 / 2 失败** —— 与改名前的数字**完全一致**,两条仍为 `libinkwatch_core.dylib` 未编译(Rust 核心未构建,环境缺口)。**数字一致**是这次 改名的核心判据:一个纯机械改名必须不改变任何测试结果,若通过数变了就说明改坏了东西。 服务端代码只动了注释与环境变量名,逻辑零改动,阶段 B 后一并回归。 |
||
|
|
930c35957b |
feat(installer): Windows 上「安装成功但不会立刻启动」必须说出来(平台实话)
## 为什么必须补这句(team-lead 拍板采纳)
三个平台的 `install_commands`(真相源 `rust-core/src/keepalive.rs`)语义不同:
| 平台 | 安装命令 | 装完是否立即在跑 |
|---|---|---|
| macOS | `launchctl bootstrap` + plist `RunAtLoad=true` | 是 |
| Linux | `systemctl --user enable --now`(`--now`) | 是 |
| Windows | `reg add`(HKCU Run 项) + `schtasks /SC ONLOGON` | **否,只在下次登录时起** |
于是 Windows 上「install 返回 0」**只承诺"已注册",从不承诺"已在跑"**。
可上游(GUI 常驻卡)曾把它读成"已启动",进而停掉自己的采集 ——
两个都没在采、界面还很平静。这行字把「启动」从**承诺**降级成**事实描述**。
## 改动(`client/bin/pmon_install.dart`)
1. 新增 `launchSemanticsNoteFor(PmonPlatform)`(平台作参数的纯函数):
· Windows → `仅在下次登录时启动`
· 其它 → `安装命令本应已使进程立即启动,但本平台无法探测其是否在运行`
2. `install` **成功路径**打印 `说明:已注册 · <上面那句>`。
位置刻意放在 `hardFailures` 判定**之后**:安装失败时不该说"已注册"。
触发条件是"本平台有没有探针"(平台属性),与是否 `--no-start` 无关。
3. `status --json` 的 `autostart` 增 `note` 字段(探针可用时为 null;
不可用时为该句)—— 因为 Windows 上这行字是**唯一**的状态说明,
GUI 读的是 JSON,不能只留在人话输出里。人话 `status` 也打印同一句。
⚠️ **这是"平台 → 启动语义"知识的第二份**(第一份在 Rust 的 `install_commands`)。
不能改成嗅探命令字符串(本项目明令禁止判 `cmd.contains('bootout')` 那类)。
正解与 `stderr_log_path` 同款:让 `AutoStartPlan` 返回 `starts_immediately: bool`,
Dart 只读不算。**本轮未做**(要改 Rust 结构 + FFI 出口),已写进注释,
并集中在**这一个函数**里,不允许出现第三份。
## 测试
- 新增 `client/test/installer_launch_note_test.dart`(5 项):本机是 macOS,
跑不到 Windows 分支,故把平台做成参数逐条断言 —— 与 `app_paths_test.dart` 同一手法。
含反面对照(Windows 那句不得出现"已启动";macOS/Linux 那句不得出现"登录"),
以及两条**形状护栏**:读 `keepalive.rs` 断言 Windows 分支确实有 `/SC ONLOGON`
且没有 `systemctl`/`launchctl`、Linux 分支确实有 `enable --now`。
(写这条时踩了一次"抓错对象":`Platform::Windows =>` 在 `as_str`/`autostart_mechanism`
里也出现,第一版抓到显示名导致假红,已限定在 `plan_autostart` 内、
边界同时认 `=> {` 与 `=> Ok(AutoStartPlan {`。)
- `installer-clean-e2e.js` 38 项(+1):JSON `autostart.note` **两条分支都断言** ——
探针可用必须为 null(不给假说明),不可用时必须非空且含"启动"。
- 变异自证:临时让 `probeAgentProcess()` 恒返回 unavailable →
`status --json` 得到 `probe_available=false` + `note='安装命令本应已使进程立即启动…'`,
人话 `status` 出现 `说明: …`,e2e 走"非空"分支仍全绿;恢复后回到 null 分支。
("install 真正成功 + 探针不可用"这个组合在本机**无法构造**:`launchctl bootstrap`
在此环境恒失败(I/O error 5),install 永远走不到成功分支 —— 如实说明,
不假装验过。install 侧与人话/JSON 侧调用的是同一个函数,字符串由单测覆盖。)
## 顺带修掉一个我自己引入的偶发失败(必须修掉,不能留着)
`cli-exit-code-e2e.js` 原用 `127.0.0.1:3107` 当"打不通的网关",
但实测**8 次里有 2 次 3107 上真的有人在听**(relay 往返成功、`relay_check` 退出 0),
"打不通 ⇒ 退出码非零"这条被静默反转 —— 它先是在 run-all 里红了一次我才发现。
改为**端口 1**(特权端口,正常环境永不监听),并在注释里写明"别换成看起来空闲的端口"。
换端口后连跑 10 次 0 失败。
## 验证
- `dart analyze lib bin`:**No issues found!**
- `flutter test`:**582/0**(含本笔新增 5 项)
- `cargo test --release`:**61/0**
- `bash tests/run-all.sh`:**32 套件 31 通过 / 1 失败**(唯一红仍是隐私扫描,未碰)
- `node tests/installer-clean-e2e.js`:**38/0/1 跳过**
- `node tests/cli-exit-code-e2e.js`:**11/0**(连跑 10 次稳定)
- `guard-structure-test.js`:**44/0**
## 归属备案
`tests/run-all.sh` 里我把套件计数 37→38 的改动,被他人提交
`205e862` 一并带走了(同文件、`git add` 粒度拆不开,与我上次遇到的情况对称)。
内容已在 HEAD,无丢失;此处留痕避免日后考古困惑。
|
||
|
|
b11def8167 |
fix(agent): 退出码显式落到进程边界,并补「真实子进程退出码」e2e
## 依据:本 SDK 上 main 的返回值完全不设退出码(实测矩阵)
Dart SDK 3.13.3,各跑 `dart run` 与 `dart compile exe` 两种形态:
| 写法 | `dart run` | `dart compile exe` |
|---|---|---|
| `int main() { return 3; }` | **0** | **0** |
| `Future<int> main() async { return 3; }` | **0** | **0** |
| `void main() { exitCode = 3; }` | 3 | 3 |
即**只有显式 `exitCode =` / `exit()` 才生效**(官方文档说 `Future<int> main` 会设,本 SDK 不成立)。
所以 `cffdc50`「安装器不再撒谎、返回非零」的代码逻辑对,但**进程边界是死的**;
`pmon_agent.dart` 同样是 `Future<int> main`,后果更重:plist 用
`KeepAlive.SuccessfulExit=false`,launchd 靠「非零退出」判崩溃 —— 退出码被吞成 0
等于**采集端崩了也不会被拉起**,用户只看到历史断档,无任何报错。
## 改动:`bin/pmon_agent.dart`
`Future<void> main` + `exitCode = await runAgent(argv)`;函数体抽成 `runAgent`
(返回码语义清晰、可被测试直接调)。**语义逐条守住,没有一刀切归零**:
| 退出点 | 改前 | 改后 | 理由 |
|---|---|---|---|
| `--help` | return 0(被吞成 0) | 0 | 帮助是正常完成 |
| `--print-plan` 成功 | return 0 | 0 | 只打印,正常完成 |
| `--print-plan` 且核心返回 error | return 0 | **1** | 用户要的「计划」没拿到(iOS 不支持/路径非法);退出 0 会让脚本以为拿到了产物 |
| 抢不到单实例锁 | return 0 | **0(刻意不变)** | 非零会被 launchd 当崩溃立刻重启 → 两进程互踢成无限重启循环,比"没起来"更糟 |
| `--duration` 正常到期 | return 0 | 0 | 冒烟/CI 的正常路径 |
| Ctrl-C(SIGINT) | `exit(0)` | 0 | 用户主动停;非零会让 launchd 立刻拉起("关不掉的进程") |
| 常驻等待(Completer) | return 0 | 0 | 理论不可达(只有信号或 kill 能结束) |
| 未捕获异常(核心库缺失等) | 无 catch → 255 | **255(不变)** | 实测 `Future<void> main` 抛异常仍是 255;崩溃必须非零,否则 launchd 不重启 |
顺带把 `--print-plan` 里硬编码的 label 换成 `kDefaultAutostartLabel`
(label 唯一真相源是 `host/autostart_label.dart`,不在 bin/ 再写一份)。
## `pmon_viewer.dart` / `relay_check.dart`:**不需要改**,它们已经是显式 `exit()`
核对结果(`grep -c "exit("`):
- `pmon_viewer.dart`:**7 处** —— `exit(4)`(缺 --account / 启动失败 / 缺 --device)、
`exit(3)`(配对失败 / 未订阅成功)、`exit(0)`(--list),末尾 `exit(ok ? 0 : 2)`。
- `relay_check.dart`:**2 处** —— `exit(same ? 0 : 1)`、catch 里 `exit(1)`。
`exit()` 比 `exitCode =` **更强**(立即终止并带上码),失败语义本来就到达边界。
我实测确认并写进了新套件:`pmon_viewer` 缺 `--account` → **4**,`relay_check` 打不通 → **1**。
顺带说明:`viewer-e2e-test.js:257` 那条 `cli.code === 0` 断言**因此是有效的**(不是死断言)。
为"统一写法"去把它们改成 `exitCode =` 是纯噪音,且会削弱语义,未做。
## 新增 `tests/cli-exit-code-e2e.js`(11 项,已接入 run-all)
**一律读子进程真实 exit code**(`spawnSync`/`spawn` 的 status),**不 import 被测代码断言返回值**
—— 断言返回值正是这次漏掉的东西。覆盖:
- 必须 0:`--help`、`--print-plan`、**抢不到单实例锁**、`--duration` 到期(后两条用"先起一个持锁实例再起第二个"构造,temp HOME 隔离)
- 必须非零:`pmon-install install --agent-bin <不存在>`、`pmon-viewer` 缺 `--account`、`relay_check` 打不通网关
## 变异自证
1. **旧写法复现缺陷**:把 `main` 临时改回 `Future<int>`,并让 `--help` `return 7` ——
新写法实测进程退出码 **7**;旧写法实测 **0**(7 被吞)。同一份代码、只改 main 签名。
2. **测试确实会红**:临时注释掉 `pmon_install.dart` 的 `exitCode = await runCli(argv);`
→ `cli-exit-code-e2e` ⑤ 变红(`exit=0`),`installer-clean-e2e` 的
「说失败⇒退出码非零」同时变红;恢复后两套全绿(11/0、34/0/1)。
## 验证
- `dart analyze lib bin`:**No issues found!**
- `flutter test`:**559/0**(含他人新增)
- `cargo test --release`:**61/0**
- `bash tests/run-all.sh`:**32 套件 31 通过 / 1 失败**(唯一红仍是隐私扫描,未碰)
- `node tests/cli-exit-code-e2e.js`:**11 通过 / 0 失败**
- `guard-structure-test.js`:**44/0**
|