Define installation integration task (T-302)

This commit is contained in:
ila
2026-07-18 17:35:11 +08:00
parent a72e7b04dc
commit 6575c9ad9b
4 changed files with 80 additions and 15 deletions
+8 -4
View File
@@ -153,11 +153,13 @@ T-102 Phase 1 原型进一步固定:
- ZIP 名称只接受 UTF-8 `/` 分隔的规范 Windows 安全相对路径;逐段拒绝反斜杠、盘符、冒号/NTFS ADS、NUL/控制字符、Windows 禁止字符、`.`/`..`、首尾 ASCII 空格、尾随句点、DOS 设备名及大小写折叠后的重复输出路径。
- 顶层只允许必需的 `app.json`、可选 `files.json` 与 `payload/`;只把 `payload/` 内容写入全新的 staging。
- 拒绝符号链接、设备/管道等特殊文件和加密条目。
- `Extractor.ExtractFile` 必须接收已验签 Catalog `packages[arch].size` 作为 `expectedPackageSize`;同一已打开的普通 `.download` 文件的实际长度必须与它精确相等,否则在读取 ZIP 前拒绝。T-301 的 known-total 完成文件已经以该 Catalog size 限长,但 T-302 仍须重新取得并传入可信 Catalog 身份,不能信任可篡改的下载 metadata。
- T-302 的 `application.InstallService` 只接收已验签、严格解析并按目标过滤后的 Catalog `Entry` + architecture 与 `.download` 候选路径。它必须确认 entry/package 与 `App.Packages[architecture]` 精确对应;不得从 T-301 task、`DownloadCompleted` payload 或本地 metadata 取得 app/version/size/hash。外层 Catalog Ed25519 签名覆盖嵌套 package 的 `size`、`sha256` 与 `signature` 文本;当前协议未定义 package `signature` 的独立待签名字节/公钥域,客户端不得臆造第二套包级验签。
- `Extractor.ExtractVerifiedFile` 必须接收该 Catalog package 的 `size`、`sha256` 与 app identity expectation。它以一次 `Lstat → open → fstat → Lstat` 取得普通 `.download` 文件,并在**同一打开句柄**上先精确核对 `expectedPackageSize`、再计算/常量时间比较 SHA-256;任一失败时不得构造 ZIP reader、读取 app.json 或创建 staging。T-301 的 known-total 完成文件已经以 Catalog size 限长,但 T-302 仍须执行上述重新对账,不能信任可篡改的下载 metadata。
- 构造 `zip.Reader` 前只读取文件尾部至多 65,557 字节以定位 EOCD,并按需读取固定的 ZIP64 locator/EOCD 记录;校验单磁盘、中央目录 offset/size/entries 的边界及 entries/中央目录大小硬上限。当前默认上限为:原始包 4 GiB、中央目录 64 MiB、10,000 个条目、总展开 4 GiB、单条及总体压缩比 200:1。大小不一致、原始包超限、中央目录超限、声明条目超限分别保留 `ErrArchiveSizeMismatch`、`ErrArchiveTooLarge`、`ErrCentralDirectoryTooLarge`、`ErrTooManyEntries` 错误链;格式、截断、跨盘或不一致 ZIP64 归入 `ErrInvalidArchive`。
- 预扫描通过后使用**同一文件句柄**和已核对的长度创建 `zip.NewReader`;完整中央目录/路径/entrypoint/类型/CRC/展开量预检仍是第二道防线。合法 ZIP64 被支持,不因 32 位 EOCD 哨兵值误拒绝。T-302 按真实包体分布复核上述暂定限额后再冻结。
- SHA-256 通过后,预扫描继续使用**同一文件句柄**和已核对的长度创建 `zip.NewReader`;完整中央目录/路径/entrypoint/类型/CRC/展开量预检仍是第二道防线。合法 ZIP64 被支持,不因 32 位 EOCD 哨兵值误拒绝。T-302 按真实包体分布复核上述暂定限额后再冻结。
- entrypoint 使用 payload 内相对路径表示,不得自带 `payload/` 前缀,且必须精确对应 ZIP 中的普通文件。
- 所有输出路径在创建 staging 前完成规划,并在逐段名称校验后再次验证 native `filepath.Join` 结果仍位于 destination 内;包含性检查是纵深防御,不能替代 Windows 名称规则。
- `app.json` 必须是 ZIP 根目录唯一普通文件,读取上限 1 MiB;严格 JSON 解析拒绝未知字段和尾随值,完整 v1 字段/常量必须通过运行时校验。`id`、`version`、`channel`、`min_os`、`architecture`、`entrypoint`、`requires_admin` 必须与可信 Catalog selection 一致;只有这一比对成功后才可创建 staging 并提取 payload。
### 2.4 安装记录 installed-app.json(本地)
@@ -183,7 +185,7 @@ T-102 Phase 1 原型进一步固定:
规则:
- Schema 位于 `schemas/installed-app.schema.json`;未知字段、非法 SemVer、ID/目录不匹配、非 `386|amd64` 架构、非 stable channel、共享 Windows 安全相对路径以外的文件名、大小写折叠重复路径和非法 SHA-256 均拒绝。
- `files` 必须是数组;v1 可以为空,完整文件清单由 T-302 安装整合时从已验证包写入。
- `files` 必须是数组;v1 可以为空。T-302 从实际已验证、CRC/长度检查后写入 staging 的每个 payload 文件计算路径、size 和 SHA-256 并写入完整清单;可选 `files.json` 不是新的信任根,仍保留给后续修复功能。
- 写入使用 app 目录内临时文件 + `installed-app.json.backup` 原子替换;主文件缺失时可读取中断遗留 backup,但所有读取都重新严格校验。
- SemVer 比较遵循 2.0.0:major/minor/patch 与 prerelease 参与 precedence,build metadata 不影响更新判断。
@@ -214,6 +216,8 @@ phase 只允许:`prepared`、`current_backed_up`、`staging_activated`、`rollba
T-613 为该原型建立了 fail-closed 的耐久顺序:每个 payload 先完成 CRC/长度检查、`Sync`、`Close`;staging 目录按子目录→staging 根→父目录同步。每次 journal 写入均为临时文件内容 `Sync`/`Close` 后 rename,并在 transaction/backup rename、删除后同步 app root。`current → backup`、`staging → current`、rollback/recovery rename 和受控目录删除也必须先同步 app root,才可写下一 phase 或触发测试步骤;只有 `committed` journal 完成该栅栏后才清理 backup 和 journal。非 Windows 打开目录后 `File.Sync`;Windows 以 `CreateFile(FILE_FLAG_BACKUP_SEMANTICS)` 打开读写目录句柄并调用 `FlushFileBuffers`,任一打开、flush 或 close 失败均返回安装耐久错误,不得静默降级。
T-302 在 Switcher 的 health 阶段先运行必需的注入 health check,再原子写入新 `installed-app.json`;health 或记录写失败都必须触发既有 rollback,使旧 current/记录保持可用。只有 health 与记录均成功后才写 committed 并清理 backup/journal。
这些栅栏与注入失败测试只证明代码层面的调用顺序和 fail-closed 行为,不证明断电后硬件/驱动缓存、网络文件系统、文件锁或杀毒软件的物理表现。T-302/T-601 仍须在目标 Windows VM/真机执行断电与干扰故障注入。
### 2.6 下载任务元数据 download-task.json(本地)
@@ -250,7 +254,7 @@ T-613 为该原型建立了 fail-closed 的耐久顺序:每个 payload 先完成
- known total 的 `.part` 已达到精确 total 时,同进程 resume/retry 与重启恢复都直接 sync、核对身份并 finalize,不得再请求 `Range: bytes=<total>-`。
- 完成顺序为 part sync/close → 核对实际写入文件身份 → rename `.download` → metadata completed → DownloadCompleted。rename 前后必须仍是同一普通文件;恢复时以普通文件实际长度对账,不信任 metadata.done。
- 事件投递是 best-effort,失败不得反向把已完成/已取消的持久任务改成失败。队列必须把投递错误交给 `OnObserverError` 记录;application/UI 在启动或事件通道重连后必须用 `Queue.Tasks()` 对账持久状态,不能只依赖某一次终态事件。
- `.download` 仍是**不可信隔离字节**。T-302 必须重新从已验签 Catalog 取得 app/version/arch/size/SHA-256/signature 并验证,通过后才可读取 app.json 或解压。
- `.download` 仍是**不可信隔离字节**。T-302 必须重新从已验签/过滤 Catalog 取得并交叉核对 app/version/arch/size/SHA-256(及已由 Catalog 解析器校验格式、被外层签名覆盖的 package signature 文本),在同一普通文件句柄完成 size+SHA-256 后才可读取 app.json 或解压。
## 3. 许可证(服务端签发 → 本地离线验证)