Implement controlled app launch (T-401)

This commit is contained in:
ila
2026-07-19 21:10:59 +08:00
parent d0cf333394
commit 87083c387f
32 changed files with 1802 additions and 31 deletions
+24 -2
View File
@@ -164,7 +164,7 @@ T-102 Phase 1 原型进一步固定:
### 2.4 安装记录 installed-app.json(本地)
记录实际安装的软件 ID、版本、架构、channel 和文件清单;与 `current/`、`staging/`、`backup/` 同级存放于 `apps/<id>/`。
记录实际安装的软件 ID、版本、架构、channel、已验证的启动元数据和文件清单;与 `current/`、`staging/`、`backup/` 同级存放于 `apps/<id>/`。
```json
{
@@ -173,6 +173,10 @@ T-102 Phase 1 原型进一步固定:
"version": "1.4.2",
"architecture": "amd64",
"channel": "stable",
"entrypoint": "JsonParser.exe",
"working_directory": ".",
"min_os": "windows-7-sp1",
"requires_admin": false,
"files": [
{
"path": "JsonParser.exe",
@@ -187,6 +191,7 @@ T-102 Phase 1 原型进一步固定:
- Schema 位于 `schemas/installed-app.schema.json`;未知字段、非法 SemVer、ID/目录不匹配、非 `386|amd64` 架构、非 stable channel、共享 Windows 安全相对路径以外的文件名、大小写折叠重复路径和非法 SHA-256 均拒绝。
- `files` 必须是数组;v1 可以为空。T-302 从实际已验证、CRC/长度检查后写入 staging 的每个 payload 文件计算路径、size 和 SHA-256 并写入完整清单;可选 `files.json` 不是新的信任根,仍保留给后续修复功能。
- T-401 新安装必须写入 `entrypoint`、`working_directory`、`min_os`、`requires_admin`,其值来自已与可信 Catalog 精确比对的 `app.json`。前 3 个字段沿用共享安全相对路径/已知系统版本规则(working directory 额外允许 `.`);entrypoint 必须也是 `files` 内的普通文件。为保持 v1 可读,历史记录可缺少这些可选字段,但启动用例不得猜测默认 EXE、工作目录或最低系统,必须 fail closed。
- 写入使用 app 目录内临时文件 + `installed-app.json.backup` 原子替换;主文件缺失时可读取中断遗留 backup,但所有读取都重新严格校验。
- SemVer 比较遵循 2.0.0:major/minor/patch 与 prerelease 参与 precedence,build metadata 不影响更新判断。
@@ -227,7 +232,7 @@ T-302 在 Switcher 的 health 阶段先运行必需的注入 health check,再
在同句柄 size/SHA、ZIP 预扫描和严格 `app.json` 身份比对均通过后,提取器会在创建 `staging/` 前提供已规划 payload 的准确展开字节数、普通文件数和安全 entrypoint。安装 use case 必须以 `payload_bytes + 64 MiB` 查询 app root 所在卷的可用空间;可用空间不足时不创建 staging。预检不能替代写入、同步、切换或回滚阶段的 fail-closed I/O 错误处理。
安装 use case 同时在上述位置检查当前 `current/<entrypoint>` 是否正在运行。容量、storage failure classifier 与运行状态均通过 core 接口注入,且均为必需依赖;classifier 只对已带 `ErrStagingOutput` 的原始 I/O 链识别磁盘满,core 不导入 Windows API。检查失败按不可安全继续处理。v1 不强杀、不启动、不等待进程退出。Windows Toolhelp 枚举、正常退出等待与启动协议属于后续 T-401,不能进入 core。
安装 use case 同时在上述位置检查当前 `current/<entrypoint>` 是否正在运行。容量、storage failure classifier 与运行状态均通过 core 接口注入,且均为必需依赖;classifier 只对已带 `ErrStagingOutput` 的原始 I/O 链识别磁盘满,core 不导入 Windows API。检查失败按不可安全继续处理。T-401 又在存在旧 `current` 的更新中、`current → backup` rename 紧邻前复查同一目标;明确运行中清理本次 staging 并返回 `app_running`,检测错误返回 `target_state_unavailable`,其他 switch/rename 错误仍为原有安装失败归因。首次安装不做第二次检查。v1 不强杀、不等待进程退出。
安装结果面向调用方的错误码为下表的稳定英文枚举;UI 负责本地化,原始错误只保留给 `errors.Is`、日志和诊断,不得进入 UI payload。
@@ -245,6 +250,23 @@ T-302 在 Switcher 的 health 阶段先运行必需的注入 health check,再
上述任一预检或验证失败都发生在 switch 前;已有版本的 `current` 和 `installed-app.json` 必须保持可用,首次安装不得留下 executable `current`。
### 2.6.1 受控启动与失败码
`core/application/launch` 的请求仅接受 app ID。它从本地记录取得启动元数据,确认 `current` 与工作目录是真实受控目录、entrypoint 是 `current` 内列入 `files` 的普通非 symlink 文件,再依次检查最低系统、授权和该精确 entrypoint 的运行状态,最后调用平台启动器。请求或 UI 不得提供 EXE、路径、参数、URL 或 shell command;授权 checker 是必需注入边界,许可证策略仍由 T-501~T-503 实现。
Windows 平台用 Toolhelp32 快照枚举,并以 `QueryFullProcessImageName` 的规范绝对路径作最终身份匹配;同名 EXE 只可作为查询优化,不能成为运行结论。快照/枚举/候选路径查询失败必须返回错误,不能当作“不在运行”。启动器只接收已验证的绝对 entrypoint 和工作目录:普通启动不经命令 shell 或 PATH 搜索;`requires_admin` 使用固定 `runas` Windows 动词且不传参数。非 Windows stub 对兼容、进程检测和启动均返回明确不支持错误。
| code | 含义 |
| --- | --- |
| `not_installed` | 没有可读取的安装记录 |
| `launch_metadata_invalid` | 历史记录缺少完整启动元数据,或元数据不安全/不一致 |
| `launch_target_unsafe` | `current`、工作目录或 entrypoint 布局不是受控真实对象 |
| `entrypoint_missing` | 记录中的 entrypoint 文件不存在 |
| `compatibility_unavailable` / `app_incompatible` | 无法可靠判定系统,或系统不满足最低版本 |
| `authorization_unavailable` / `not_authorized` | 授权边界不可用,或当前 app 未获授权 |
| `app_running` / `target_state_unavailable` | 精确 entrypoint 已运行,或不能可靠判断其状态 |
| `launch_failed` | 平台拒绝或未能创建受控进程 |
### 2.7 下载任务元数据 download-task.json(本地)
每个任务以稳定 `request_id` 为主键,元数据位于 `downloads/tasks/<request_id>.json`,字节文件位于 `downloads/files/<request_id>.part|.download`。本地路径只由客户端从 request_id 派生,不接受 URL 或 Content-Disposition 提供的文件名。