Files
soft_quay/docs/tasks/T-503.md
T

73 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: T-503
title: 授权界面、离线导入与撤销状态
phase: 5
deps: [T-502, T-204]
status: TODO
created: 2026-07-20
issue: null
context_ref: null
claim_branch: null
work_branch: null
write_paths:
- docs/tasks/T-503.md
- core/licensing/
- core/application/
- core/application/launch/
- core/application/install/
- core/installer/
- core/storage/
- app-modern/cmd/softbox/
- app-modern/platform/windows/
- app-modern/ui/gio/
- app-win7/cmd/softbox/
- app-win7/platform/windows/
- app-win7/ui/gio/
- schemas/
- testdata/license/
- docs/04-architecture.md
- docs/06-tasks.md
- docs/api.md
- docs/current-state.md
- docs/routes.md
---
## 问题 / 背景
T-501/T-502 已经能在调用方给出机器摘要和公钥时,严格离线验证一份 License v1;它仍是纯验证器。仓库没有可信的许可证文件生命周期、撤销缓存、授权状态快照或 Gio 页面,也没有把已经验签的 `product_id` 接到启动边界。直接把导入的 JSON、测试 key 或“无文件即允许”的判断接到 UI/启动器,会破坏机器绑定和 fail-closed 边界。
现有 `app.json` 的 `product_id` 与 `supports_trial` 已经在受签名 ZIP 内校验,但安装记录尚未保留它们。若启动检查继续按 app ID 判断授权,客户端无法把许可证产品和已安装软件安全关联。License v1 也没有到期字段,不能把 `perpetual:false` 擅自解释成仍有效的本地试用。
## 方案
1. 在 `core/licensing` 冻结并实现 Revocation List v1。它使用调用方显式注入的授权 Ed25519 公钥(与 License v1 可以是同一受控配置,但绝不回退到测试/环境 key),只允许 `schema_version`、`generated_at`、`expires_at`、`revoked_license_ids`、`signature` 五个必需顶层字段;采用与 License 相同的受限 canonical JSON、严格 padded Base64 Ed25519 签名、UTC 秒级时间、唯一且合法的 `lic-...` ID。`expires_at` 必须晚于 `generated_at` 且间隔不超过 31 天。已验证撤销立即拒绝;未撤销且缓存未过期为 `current`;过期后最多 7 天为 `grace`;无缓存、无效缓存或超过宽限期为 `unavailable`,均不可授权。调用方注入时钟;所有错误/快照/UI 都不包含许可证 ID、签名、原始文档或机器来源。
2. 在 `core/storage` 新增受限 `licenses/` 存储。仅在 `licensing.Verifier` 成功匹配当前 machine hash 后,才把原始许可证 document 原子写入 `licenses/v1/<sha256>.license`(0600、临时文件 sync/close 后 rename);读取时只接受真实目录和该文件名模式、1 MiB 以下普通文件,并逐份重新验签。未知/链接/损坏缓存让授权状态 fail closed,不信任残留内容;相同 document 的导入幂等。撤销列表仅由已经验签的 document 原子替换 `licenses/revocations-v1.json`,与许可证分开保存。更新/安装/自更新都不得删除或覆盖此根。
3. 增加纯 core 的授权用例与受净化事件快照。它以显式的 verifier、machine hash、store、clock 和 `LicenseImportSource`/撤销来源组合,后台完成启动加载、导入与撤销更新,并只向 UI 发布 `LicenseChanged` 的固定状态码、machine hash、产品 ID、正式/非永久展示状态、撤销状态和换绑策略;不发送 license ID、签名、JSON、文件路径或原始 Windows 标识。默认 `UnconfiguredAuthorizationService` 明确发布 `authorization_unconfigured`,不创建 `licenses/`、不允许导入、不授权。UI 点击只入队请求;文件选择、文件读取、验签和写盘均在后台进行。
4. 将已通过签名 ZIP 的 `product_id` 与 `supports_trial` 从 manifest 经 `VerifiedPackage` 保存到 `installed-app.json`,并把启动的 `AuthorizationChecker` 入参改为 `product_id`。历史安装记录缺少这些字段视为授权元数据不可用而拒绝启动;`application/launch` 只接受已验证且撤销状态可用/宽限中的产品授权。`supports_trial:true` 仅表示软件支持试用,License v1 的 `perpetual:false` 仅显示为“非永久许可证”:本任务不实现本地试用计时、到期、绕过授权或自助换绑。
5. 在两套 Gio shell 增加同语义的 `LicensePanel`:左侧可进入“授权”,显示 machine hash、授权产品、正式/非永久状态、撤销当前/宽限/不可用状态,以及不修改本机绑定的换绑说明。无配置、无授权、撤销、宽限和导入失败均有明确可访问文案;只有配置就绪时显示可操作的导入按钮。按钮、键盘焦点和语义标签满足现有最小 44dp 可点击目标;Layout 只 drain 内存事件/请求,不做 I/O。现代和 Win7 command 都仅装配安全默认值;测试可注入假 source/受控测试 key 验证完整后台→relay→UI 链路,发布配置/真实信任根留给 T-603。
6. 更新 API、架构、路由、路线图和快照,使 License v1、撤销/7 天宽限、存储布局、状态码、产品映射和“不提供本地试用/换绑服务”的边界一致;新增不含私钥或生产值的静态/动态测试向量。
## 验收要点
- 许可证在写入前、每次启动加载和每次授权判断时都重新验证签名、machine hash 与 product;外来 JSON、错误 key/hash、超限/链接/未知/损坏缓存、撤销、缺失或超过宽限的撤销缓存一律不能授权,也不泄露敏感原文。
- Revocation List v1 覆盖 canonical 签名、重复/未知字段、非法 ID/时间/Base64、future/过期边界、撤销优先和恰好 7 天宽限边界;不存在本地 allow-all 或使用测试 key 的 production composition。
- 导入写入限制在受控 `licenses/v1/`,幂等、原子且 0600;列表返回深拷贝的非敏感授权事实。导入/加载/撤销失败均映射稳定状态码,不把路径、license ID、签名、JSON、GUID 或卷序列号放入 event、日志或界面。
- 已安装记录保留受验证 manifest 的 product/trial 元数据;启动以 product ID 检查且旧记录缺字段 fail closed。试用和换绑页面只展示已签名事实/下一步说明,不创建未签名试用授权、不改变 machine hash、不替换许可证。
- 两端 Gio 页面能从 `LicenseChanged` 事件刷新,未配置、无授权、撤销、宽限、导入失败和已授权状态均可见;导入点击不在 Layout 做磁盘/网络/哈希,后台结果通过现有有界 relay 唤醒 UI。
- `go -C core vet ./...`、`go -C core test -count=1 ./...`、两个 app 的 `go test -count=1 ./...`、两个 Windows amd64 主程序构建、`./scripts/verify_phase0.ps1`、`python scripts/validate_agent_context.py` 与 `python scripts/validate_harness_governance.py` 全部通过。
## 边界(不改什么)
- 不提交生产公钥、任何私钥、真实 license/revocation、机器摘要、注册码或正式下载 URL;不从环境变量、网络或 UI 文本取得信任根。
- 不实现在线账号、支付、自助换绑、人工申诉接口、许可证签发、撤销列表下载协议、试用计时/过期/本地绕过,或子软件 SDK 分发;这些需要发布端/产品协议另行确定。
- 不改 T-501 的指纹算法、Catalog 协议、下载/安装/更新/自更新事务或 Gio 之外的架构。安装记录只做携带已验证 manifest 授权元数据的最小兼容升级。
## 协作约束
- 当前项目为单 Agent 串行模式;当前 Agent 独占全部 `write_paths`,自行完成规格、实现、测试、审查、状态更新和提交,不启动子 Agent。
- 本规格提交后才领取任务:将本文件改为 `DOING`,填写当时 HEAD 至 `context_ref` 与 `work_branch: agent/codex/T-503`,重跑基线后再实现。完成时只更新本任务为 `DONE`,不提前落成后续任务。
## 执行记录
- 2026-07-20:正式落成。冻结 Revocation List v1、7 天离线宽限和受限 `licenses/` 缓存;明确产品授权必须使用受验证 manifest 的 product ID,且 License v1 不足以实现本地 trial/rebind。默认发布构建仍以未配置状态 fail closed,真实 trust root/发布配置交由 T-603。