Initialize harness coding docs from design spec
Split docs/softbox-catalog-design.md into the numbered harness doc set (00-06, api.md, current-state, agent-context, tasks) following the harness_coding_docs template and soft_quay conventions. Register the nine open decision items from the design spec into the 06-tasks roadmap as W- tasks and backlog entries. Keep the original design spec as an archived design input with a header note. Recreated after the repository's previous git history was lost to an external reset; content matches the original initial commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
# 构建产物
|
||||
/dist/
|
||||
/bin/
|
||||
|
||||
# 本机私有配置(示例文件用 *.example 入库)
|
||||
*.env
|
||||
!*.env.example
|
||||
|
||||
# 密钥材料绝不入库(测试密钥对放 testdata/ 并显式命名)
|
||||
*.pem
|
||||
*.key
|
||||
|
||||
# 工具缓存
|
||||
__pycache__/
|
||||
*.pyc
|
||||
@@ -0,0 +1,63 @@
|
||||
# AGENTS.md
|
||||
|
||||
> AI coding agent 的仓库级入口。进入本仓库后,先读本文,再进入 `docs/00-ai-start-here.md`。
|
||||
|
||||
## 项目定位
|
||||
|
||||
本仓库是 **soft_quay_web**:SoftBox 软件盒子的中央发布 / 登记系统(客户端仓库历史文档中也称 `softbox-catalog`,指同一系统)。职责:软件登记 + 构建包接收校验 + Ed25519 签名服务 + 双通道清单生成 + 对象存储发布 + 许可证/撤销签发 + Web 管理后台。
|
||||
|
||||
最终产物是放在对象存储 / CDN 上的**静态签名文件**(manifest、ZIP 包、许可证、撤销名单),不是面向客户端的动态 API;客户端 `soft_quay` 只内置公钥、离线验签。
|
||||
|
||||
本仓库不包含子软件(app-*)业务源码,不包含盒子客户端的下载 / 安装 / 授权实现。
|
||||
|
||||
## 必读顺序
|
||||
|
||||
每次开始工作前:
|
||||
|
||||
1. 本文件。
|
||||
2. `docs/agent-context.json`:上下文路由清单。
|
||||
3. 清单 `bootstrap.always_read` 中的文件(`docs/00-ai-start-here.md`、`docs/05-coding-rules.md`、`docs/current-state.md`)。
|
||||
4. 本轮任务文件(`docs/tasks/W-<编号>.md`)。
|
||||
5. 按任务类型读取清单 `routes` 中的文档;重复路径只读一次。
|
||||
|
||||
首次接入或清单校验失败时,按 `docs/00-ai-start-here.md` 的完整顺序读取 01→06。
|
||||
|
||||
## 硬性边界(违反即返工,多数同时是安全事故)
|
||||
|
||||
- **私钥隔离**:Ed25519 私钥只存在于受控签名服务(KMS/HSM 或独立最小权限进程);绝不进 Web 进程、代码仓库、测试数据、环境变量明文或日志。仓库内一切签名示例只用与客户端 corpus `public_key_base64` 配对的**测试密钥对**。
|
||||
- **协议以客户端为权威源**:清单 / 包 / 许可证协议以 `soft_quay/docs/api.md` 与 `soft_quay/schemas/*.json` 为准;字段变化必须先在客户端仓库定稿,本仓库不得单方面新增/修改协议字段。
|
||||
- **canonicalization 不得自举**:规范化与签名的跨实现回归只消费 `soft_quay/testdata/catalog/canonical-vectors.json` 的静态期望值(bytes/公钥/签名);禁止用本仓库 canonicalizer 重新生成期望值。
|
||||
- **不造第二套验签域**:单公钥协议升级前,不得为 package、图标或任何对象另造签名域;package `signature` 的独立语义是未定项(见 `docs/api.md`)。
|
||||
- **通道隔离**:`manifest-modern.json` 与 `manifest-win7.json` 全程不交叉;win7 清单不得出现 Win7 无法启动的包。
|
||||
- **发布原子性**:ZIP 包与图标必须先于引用它们的签名清单可用;任何"先切清单再传包"的捷径都不允许。
|
||||
- **审计 append-only**:发布 / 下架 / 撤销 / 许可证签发的审计记录只追加,不可覆盖或删除。
|
||||
- **不保存原始硬件标识**:许可证只处理 `machine_hash`;原始序列号 / MAC 不入库、不入日志。
|
||||
- 真实私钥、真实注册码、真实生产 URL、云凭据一律不入库;示例只用占位符和测试数据。
|
||||
|
||||
完整规则见 `docs/05-coding-rules.md`,协议摘要见 `docs/api.md`。
|
||||
|
||||
## 工作规则
|
||||
|
||||
- 一次只领取一个任务(`docs/tasks/` 中 `status: TODO` 且依赖全 `DONE`、编号最靠前的),按 `docs/tasks/README.md` 约定流转状态。
|
||||
- 任务编号使用 `W-<编号>`;引用客户端仓库任务时写全称(如 `soft_quay/T-614`),避免跨仓库混淆。
|
||||
- 执行记录写进该任务文件的 `## 执行记录`;项目现实变化(启动/验证路径、目录、blocker)覆盖更新 `docs/current-state.md`。
|
||||
- 需求变化先改文档再改代码;不在代码里发明文档没有的接口、字段和状态。
|
||||
- 未定项(见 `docs/06-tasks.md` 裁定清单)未裁定前,不得在代码里替用户做决定;先落裁定任务。
|
||||
- 提交信息使用英文祈使句,任务相关提交带上 `W-<编号>`。
|
||||
|
||||
## Agent 执行模式
|
||||
|
||||
- 默认**单 Agent 串行执行**;当前 Agent 独立完成任务落文档、实现、审查、自测、状态更新和 Git 提交。
|
||||
- 不启动子 Agent,不把测试设计、安全审查或代码审查委派给其他 Agent;需要复核时由当前 Agent 分阶段自行检查。
|
||||
- 项目同一时间只保留一个活跃任务;依赖链严格按顺序完成和提交。
|
||||
- 只有用户明确要求多 Agent 时,才允许先修改并提交本节及协作规则,再启动子 Agent。
|
||||
|
||||
## 验证
|
||||
|
||||
统一入口为根目录 `./init.sh` / `./init.ps1`;W-001 工程骨架完成前,脚本顶部三个命令为占位符,运行会主动失败并提示,属预期行为。真实命令由 W-001 替换并同步到 `docs/03-tech-stack.md`、`docs/00-ai-start-here.md` 与 `docs/current-state.md`。
|
||||
|
||||
修改文档链接或文件名后,用 `rg` 搜索旧名称确认引用一致;涉及上下文清单时运行:
|
||||
|
||||
```bash
|
||||
python3 scripts/validate_agent_context.py
|
||||
```
|
||||
@@ -0,0 +1,11 @@
|
||||
# CLAUDE.md
|
||||
|
||||
> Claude Code 的仓库级薄入口。进入本仓库后,先读本文,再读 [`AGENTS.md`](AGENTS.md)。
|
||||
|
||||
本仓库的权威 agent 规则、文档入口、工作边界和验证方式统一维护在 [`AGENTS.md`](AGENTS.md)。
|
||||
|
||||
Claude Code 处理本仓库任务时:
|
||||
|
||||
1. 先读取 [`AGENTS.md`](AGENTS.md)。
|
||||
2. 再按 `AGENTS.md` 的要求进入 [`docs/00-ai-start-here.md`](docs/00-ai-start-here.md) 和相关模板。
|
||||
3. 不在本文重复维护任务流程、编码规则或文档清单,避免和 `AGENTS.md` 漂移。
|
||||
@@ -0,0 +1,110 @@
|
||||
# AI 开发入口
|
||||
|
||||
> 给 AI coding agent 的项目入口。这里负责导航和流程,硬性编码规则见 [`05-coding-rules.md`](05-coding-rules.md)。
|
||||
|
||||
## 一句话定位
|
||||
|
||||
soft_quay_web 是 SoftBox 软件盒子的中央发布 / 登记系统:软件登记 + 构建包接收校验 + Ed25519 签名 + 双通道清单生成 + 对象存储发布 + 许可证/撤销签发 + Web 管理后台。产物是**静态签名文件**,信任根是私钥;客户端 `soft_quay` 只内置公钥、离线验签。
|
||||
|
||||
第一里程碑(M1):产出被 `soft_quay` 客户端验签通过、被 corpus + Schema 回归通过的 manifest——在任何 UI 之前达成。
|
||||
|
||||
## 上下文读取
|
||||
|
||||
首次接入、[`agent-context.json`](agent-context.json) 缺失或校验失败时,按这个顺序建立完整上下文:
|
||||
|
||||
1. [`01-vision.md`](01-vision.md):为什么做、为谁做、什么不做。
|
||||
2. [`02-requirements.md`](02-requirements.md):要什么、怎么算达成。
|
||||
3. [`03-tech-stack.md`](03-tech-stack.md):技术选型与待定项。
|
||||
4. [`04-architecture.md`](04-architecture.md):信任模型、组件边界、数据模型、发布流水线。
|
||||
5. [`05-coding-rules.md`](05-coding-rules.md):写代码前必须遵守的规则。
|
||||
6. [`06-tasks.md`](06-tasks.md):阶段路线图、里程碑、未定项裁定清单和待办池。
|
||||
7. [`tasks/README.md`](tasks/README.md):任务文件约定(一任务一文件);本轮任务从 `docs/tasks/` 领取。
|
||||
8. [`current-state.md`](current-state.md):当前代码现实、可运行命令、下一步任务。
|
||||
|
||||
日常会话不需要机械重读全部文档:
|
||||
|
||||
1. 读取仓库级规则(`AGENTS.md`)和 [`agent-context.json`](agent-context.json)。
|
||||
2. 读取 `bootstrap.always_read`。
|
||||
3. 读取本轮任务文件。
|
||||
4. 按任务类型读取 `routes` 中的文档;一个文件命中多个路由时只读一次。
|
||||
5. 记录默认分支头提交为 `context_ref`;同一会话中文件 SHA 未变化时复用已读内容。
|
||||
|
||||
清单的使用、缓存和断连降级规则见 [`agent-context.md`](agent-context.md)。
|
||||
|
||||
## 固定开工流程
|
||||
|
||||
1. `pwd`:确认在正确的仓库根目录。
|
||||
2. 读 [`current-state.md`](current-state.md) 和 `docs/tasks/` 中当前活跃任务,恢复已验证状态、下一步和当前 blocker。
|
||||
3. `git log --oneline -5`:看清最近发生了什么。
|
||||
4. 运行 `./init.sh`(Windows 原生 PowerShell 用 `./init.ps1`);W-001 完成前脚本会提示命令未替换并失败,属预期。
|
||||
5. W-002 之后:跑 corpus + Schema 回归闸门,确认基线没坏。
|
||||
6. **如果基线已坏,先修基线**,不要在坏的起点上叠新功能。
|
||||
7. 基线绿了,再从 `docs/tasks/` 领取唯一任务(路线图见 [`06-tasks.md`](06-tasks.md))。
|
||||
|
||||
## 当前阶段
|
||||
|
||||
项目处于**文档就绪、代码未起步**阶段:harness coding 文档集已建立,设计规格见 [`softbox-catalog-design.md`](softbox-catalog-design.md)。下一步按路线图落成并执行 Phase 0(W-001 骨架与权威源接入、W-003 私钥管理裁定)。
|
||||
|
||||
客户端 `soft_quay` 已完成 Phase 0~4,当前 blocker 之一是缺可信 Catalog 发布源——正是本仓库的 M2/M3 要解决的。
|
||||
|
||||
## 领取任务规则
|
||||
|
||||
任务以「一任务一文件」存放在 `docs/tasks/`(约定见 [`tasks/README.md`](tasks/README.md)):
|
||||
|
||||
- 单 Agent 只领取一个 frontmatter `status: TODO` 且依赖均 `DONE` 的任务文件,取编号最靠前的;项目同一时间只保留一个活跃任务。
|
||||
- 若 `docs/tasks/` 暂无可领任务,先按 [`06-tasks.md`](06-tasks.md) 路线图把下一个建议任务落成任务文件,再领取。
|
||||
- 开始前把该文件 frontmatter 的 `status` 改为 `DOING`;本轮只完成这一个任务;验收通过后改为 `DONE`。
|
||||
- **执行记录写进该任务文件的 `## 执行记录`**(改了什么、跑了什么验证、结果、决策)。
|
||||
- 项目现实变化(启动/验证路径、目录结构、blocker)覆盖更新 [`current-state.md`](current-state.md)。
|
||||
- 结束会话前过一遍 [`clean-state-checklist.md`](clean-state-checklist.md)。
|
||||
- 做完即停,汇报验证结果,等待下一步指令。
|
||||
|
||||
如果代码实际状态和任务文件冲突,先说明冲突,不要擅自跳步或重排。
|
||||
|
||||
## 事实来源
|
||||
|
||||
项目事实只信:
|
||||
|
||||
- `soft_quay/docs/api.md` 与 `soft_quay/schemas/`、`soft_quay/testdata/catalog/canonical-vectors.json`:协议与签名域的**唯一权威源**。
|
||||
- [`api.md`](api.md):发布端视角摘要与本仓库特有接口合约。
|
||||
- [`04-architecture.md`](04-architecture.md):信任模型、组件边界、数据模型、流水线。
|
||||
- [`02-requirements.md`](02-requirements.md):功能范围与验收标准。
|
||||
- [`03-tech-stack.md`](03-tech-stack.md):选型与待定项。
|
||||
|
||||
不要把以下内容当事实来源:
|
||||
|
||||
- [`softbox-catalog-design.md`](softbox-catalog-design.md) 中与编号文档冲突的部分(它是拆分前的原始规格,冲突时以编号文档为准)。
|
||||
- 客户端仓库中对发布侧的历史称呼(`softbox-catalog`)所暗示的另一个仓库——就是本仓库。
|
||||
- 未被任务或需求引用的草稿。
|
||||
|
||||
## 常见任务该看哪里
|
||||
|
||||
做 canonicalization / 签名:
|
||||
|
||||
- 先看 `api.md` §签名域,再看客户端 corpus 向量;`05-coding-rules.md` §2 的自举禁令必须遵守。
|
||||
|
||||
做 Registry / Ingestion / Manifest Generator:
|
||||
|
||||
- 先看 `api.md` 的清单与包协议,再看 `04-architecture.md` 的组件职责、数据模型和流水线。
|
||||
|
||||
做签名服务 / 密钥:
|
||||
|
||||
- 先看 `04-architecture.md` §五与 W-003 裁定结论;私钥隔离规则见 `05-coding-rules.md` §3。
|
||||
|
||||
做 Web 管理端(Phase 5):
|
||||
|
||||
- 先看 `02-requirements.md` 对应验收标准与 W-501 裁定结论;UI 文档届时按模板补齐。
|
||||
|
||||
做发布 / 部署:
|
||||
|
||||
- 先看 `03-tech-stack.md` 与 `current-state.md` 的当前真实命令。
|
||||
|
||||
## 验证命令
|
||||
|
||||
统一入口为根目录 `./init.sh` / `./init.ps1`。**当前(W-001 前)三个命令为占位符,运行会主动失败并提示**;W-001 用真实命令替换后同步本节、`03-tech-stack.md` 与 `current-state.md`。
|
||||
|
||||
涉及上下文清单变化时运行:
|
||||
|
||||
```bash
|
||||
python3 scripts/validate_agent_context.py
|
||||
```
|
||||
@@ -0,0 +1,48 @@
|
||||
# 项目愿景
|
||||
|
||||
## 一、核心目标
|
||||
|
||||
soft_quay_web 要解决:SoftBox 软件发布者(自己)发布自家系列软件时,元数据、签名、托管、许可证分散、无审计、私钥无处安放的问题。
|
||||
|
||||
> 让发布者在一个后台里完成「登记软件 → 接收构建包 → 校验 → 签名 → 生成双通道清单 → 上传 → 客户端发现新版本」,而**签名私钥永不离开发布系统,客户端只内置公钥**。
|
||||
|
||||
它把整个 SoftBox 产品家族的"信任根"收敛到一处:客户端拿到的每一份清单、每一个安装包、每一张许可证,都能用内置 Ed25519 公钥**离线验签**,不依赖任何在线接口的可用性。
|
||||
|
||||
它不是应用商店服务,也不是动态在线 API:最终产物是放在对象存储 / CDN 上的**静态签名文件**。
|
||||
|
||||
## 二、目标用户
|
||||
|
||||
- **软件发布者(自己)**:唯一的人类操作者;集中、安全、可审计地发布 / 下架 / 撤销软件、签发许可证。
|
||||
- **子软件 CI(app-* 仓库)**:机器角色;向本系统提交标准 ZIP 候选包。
|
||||
- **soft_quay 客户端(间接)**:不直接访问本系统,只消费静态签名产物;它的离线验签规则是本系统一切设计的约束来源。
|
||||
|
||||
## 三、设计原则
|
||||
|
||||
遇到取舍时,以这些原则为准:
|
||||
|
||||
- **静态分发优先**:产物是静态签名文件,不是运行时动态 API;客户端离线验签。
|
||||
- **私钥隔离至上**:Ed25519 私钥是整个体系的信任根,只存在于受控签名服务,绝不进客户端、代码仓库或普通后台进程。
|
||||
- **协议以客户端为权威源**:清单 / 包协议 Schema 与签名向量 corpus 以 `soft_quay` 为准,发布端**字节级对齐**,不另造 canonicalizer 或第二套验签域。
|
||||
- **通道隔离**:modern / win7 双通道全程不交叉,Win7 用户永不拿到无法启动的现代版包。
|
||||
- **可审计**:每次发布、下架、撤销、许可证签发都有不可否认的 append-only 记录。
|
||||
- **先协议后界面**:先做到产物能被客户端验签通过,再迭代 Web 管理界面。
|
||||
|
||||
## 四、核心价值主张
|
||||
|
||||
| 价值点 | 说明 |
|
||||
| --- | --- |
|
||||
| 单一信任根 | 一处持钥、一处签名;客户端全线离线验真 |
|
||||
| 一站式发布 | 登记、校验、签名、双通道清单、上传在一条流水线完成 |
|
||||
| 离线可表达的治理 | 下架(status)、撤销(签名名单 + 宽限期)都能表达在静态签名文件里 |
|
||||
| 双通道安全 | modern / win7 清单隔离,遗留用户不被误伤 |
|
||||
| 不可否认审计 | 每次签名与发布动作可追溯到操作者与时间 |
|
||||
|
||||
## 五、不做什么(非目标)
|
||||
|
||||
- 不含子软件业务源码——那些在各自 `app-*` 仓库。
|
||||
- 不含盒子的下载 / 更新 / 安装 / 授权实现——那些在 `soft_quay` 客户端。
|
||||
- 不做面向客户端的运行时接口、账号会话——客户端只拉静态签名文件。
|
||||
- 不生成未测试目标的占位包。
|
||||
- 不定义第二套包级验签域——外层清单 Ed25519 签名已覆盖 package 的 `url/size/sha256/signature` 文本。
|
||||
|
||||
> MVP 的具体功能范围与验收标准,见 [需求](02-requirements.md);原始设计规格见 [softbox-catalog-design.md](softbox-catalog-design.md)。
|
||||
@@ -0,0 +1,81 @@
|
||||
# 需求
|
||||
|
||||
> 本文只描述**要什么**与**怎么算达成**,不涉及技术实现。
|
||||
> 技术方案、组件边界见 [架构设计](04-architecture.md);协议字段见 [协议合约](api.md)(权威源为客户端仓库 `soft_quay`)。
|
||||
|
||||
## 一、业务现状
|
||||
|
||||
| 项 | 状态 |
|
||||
| --- | --- |
|
||||
| 客户端 | `soft_quay` 已完成 Phase 0~4:清单验签、下载、安装、更新、自更新均可运行,但**真实 Catalog 来源未配置**,显示 `catalog_source_unconfigured` |
|
||||
| 协议 | 清单 / 包 / 许可证协议 v1 已在 `soft_quay/docs/api.md` + `schemas/` 定稿;canonicalization corpus 已冻结(soft_quay/T-614) |
|
||||
| 发布侧 | 本仓库为全新项目,尚无代码;客户端当前 blocker 之一就是缺可信发布源 |
|
||||
| 约束 | 客户端不做在线校验;下架、撤销、兼容都必须离线表达在签名文件里 |
|
||||
|
||||
## 二、用户角色
|
||||
|
||||
- **发布者(操作者)**:登记软件、触发发布 / 下架 / 撤销、签发许可证、查看审计。
|
||||
- **子软件 CI**:提交标准 ZIP 候选包与 Release 元数据。
|
||||
- **soft_quay 客户端**:只消费对象存储 / CDN 上的静态签名文件,不访问本系统任何接口。
|
||||
|
||||
## 三、功能清单
|
||||
|
||||
### 第一版(按阶段交付,阶段见 [任务路线图](06-tasks.md))
|
||||
|
||||
| 功能 | 发布者能做什么 | 优先级 |
|
||||
| --- | --- | --- |
|
||||
| 协议对齐签名 | 产出与客户端字节级一致的规范 JSON + Ed25519 签名;corpus 全向量回归通过 | P0 |
|
||||
| Schema 校验 | 发布前自动校验 manifest / app.json 合规,不合规拒绝发布 | P0 |
|
||||
| 双通道清单生成 | 一键产出 `manifest-modern.json` / `manifest-win7.json`,通道互不交叉 | P0 |
|
||||
| 本地静态发布模拟 | 本地生成签名清单 + 静态 HTTP 服务,客户端可跑通「清单→下载→安装」闭环 | P0 |
|
||||
| 软件登记 | 维护每款软件的元数据(id/name/version/channel/status/…) | P1 |
|
||||
| 构建包接收校验 | 接收 CI 的 ZIP 候选包,校验结构与声明一致性,计算 size/SHA-256 | P1 |
|
||||
| 发布记录 | 每次发布登记 url/size/sha256/signature 坐标,可浏览历史 | P1 |
|
||||
| 正式发布 | 原子上传对象存储 / CDN:先包后清单 | P1 |
|
||||
| 签名服务隔离 | 私钥进 KMS/HSM 或独立签名服务;Web 端只经受控接口请求签名 | P1 |
|
||||
| 下架与撤销 | 用 status 下架软件;维护签名的许可证撤销名单 | P2 |
|
||||
| 许可证签发 | 绑定 machine_hash + products 签发 Ed25519 许可证 | P2 |
|
||||
| Web 管理后台 | 登记 / 发布 / 下架 / 撤销 / 签发 / 审计查看的操作界面 | P2 |
|
||||
| 审计 | 每次发布 / 下架 / 撤销 / 签发的 append-only 记录 | P2 |
|
||||
|
||||
### 后续迭代(记录不实现)
|
||||
|
||||
| 功能 | 描述 |
|
||||
| --- | --- |
|
||||
| 密钥轮换 | 需先与客户端协调协议升级(key ID 字段);单公钥期间禁止另造签名域 |
|
||||
| package.signature 独立验签域 | v1 客户端不独立验证;定义待签名字节 + 公钥域后再实现 |
|
||||
| 图标分发映射 | `sha256:` 内容哈希到实际下载位置 / 分辨率变体的发布端映射 |
|
||||
| 多操作者与审批流 | 多人权限模型、发布审批;首期单操作者 |
|
||||
|
||||
## 四、核心用户故事
|
||||
|
||||
1. 作为发布者,我在本地用测试密钥对生成一份签名清单,soft_quay 客户端验签通过并显示软件列表——这是第一里程碑,在任何 UI 之前达成。
|
||||
2. 子软件 CI 产出 `json-parser_1.4.2_windows_amd64.zip` 后,我把它提交给发布系统;系统校验包结构与登记信息一致、算出 size/SHA-256,生成新清单、签名并发布;客户端下次刷新即看到新版本。
|
||||
3. 我下架一款软件时,只改它的 `status`,不改名;客户端按 status 过滤,旧用户不受影响。
|
||||
4. 我给一台机器签发许可证时,只提交 machine_hash 与产品列表;系统不保存任何原始硬件序列号。
|
||||
5. 任何人拿到发布系统的数据库或 Web 服务器权限,都拿不到签名私钥;伪造清单在客户端必然验签失败。
|
||||
|
||||
## 五、验收标准
|
||||
|
||||
- **字节级对齐**:对 corpus 中每个合法向量,本系统 canonicalizer 产出的规范字节与 `signed_payload_base64` 逐字节一致;每个非法向量按 `want_error` 拒绝;期望值不得自举。
|
||||
- **客户端验签**:生成的 manifest 被 soft_quay 客户端(内置对应测试公钥)验签通过;篡改任一字节后验签失败。
|
||||
- **Schema 合规**:不合规清单(未知字段、重复 id、非法 SemVer、architectures 与 packages 不对应、非 HTTPS URL)被拒绝且有明确错误信息。
|
||||
- **通道隔离**:win7 清单中不出现 `min_os` 高于 win7 或仅 modern 的包;两清单 `channel` 字段正确。
|
||||
- **发布原子性**:清单可见时,其引用的所有包与图标已可下载;模拟"清单先于包"的发布被系统拒绝。
|
||||
- **私钥隔离**:代码库、配置样例、日志、数据库 dump 中均无私钥材料;签名只能通过受控接口对已知结构进行。
|
||||
- **Ingestion**:包结构非法、app.json 与登记不一致、声明版本 / 架构不符的候选包被拒绝并给出原因。
|
||||
- **审计**:每次发布 / 下架 / 撤销 / 签发均产生含操作者、时间、内容摘要、签名指纹的记录;记录不可修改。
|
||||
|
||||
## 六、范围边界与决策
|
||||
|
||||
| 问题 | 决策 |
|
||||
| --- | --- |
|
||||
| 客户端接口 | 无;客户端只拉静态文件,本系统无面向客户端的运行时 API |
|
||||
| 账号体系 | 首期单发布者;多操作者 / 审批流为后续迭代(裁定项 W-501) |
|
||||
| 首期部署 | 本地 / 静态文件模拟,不先建正式对象存储 |
|
||||
| 首期密钥 | 离线生成的测试密钥对(与客户端内置测试公钥、corpus 配对);正式私钥管理另裁(W-003) |
|
||||
| 包目标 | 只登记实际支持并测试过的目标,不生成占位包 |
|
||||
|
||||
## 七、待确认 / 风险点
|
||||
|
||||
设计规格 §十 的九个未定项已全部登记为裁定任务或 Backlog,映射见 [任务路线图](06-tasks.md) 的「未定项裁定清单」。其中立项前必须裁定的是:签名私钥管理(W-003)、协议权威源引用方式(W-001 内裁定)、正式域名与对象存储(W-004,Phase 3 前)。
|
||||
@@ -0,0 +1,44 @@
|
||||
# 技术栈(Tech Stack)
|
||||
|
||||
> "用什么"的统一速查表。未定项必须标为待定,不要让 agent 在代码里自行决定;裁定后更新本文再动代码。
|
||||
|
||||
## 一、技术栈一览
|
||||
|
||||
| 维度 | 选型 | 状态 | 理由 / 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| 后端语言 | Go | 建议(W-001 立项确认) | 与客户端同栈,可**直接复用同一套 canonical/verify 实现思路与 Schema**,天然字节对齐——这是选 Go 的最强理由。若改选其他语言,必须用 corpus 做严格字节回归 |
|
||||
| 协议 Schema / corpus | 引用 `soft_quay` 仓库(git submodule 或版本化拷贝 + CI 一致性校验,方式 W-001 裁定) | 待定 | 权威源唯一,不另建;CI 必须证明本仓库消费的副本与客户端一致 |
|
||||
| 签名算法 | Ed25519 | 已定 | 与客户端内置公钥配对;签名域规则见 [api.md](api.md) |
|
||||
| 包完整性 | SHA-256 | 已定 | Ingestion 计算整包哈希写入清单 |
|
||||
| 私钥保管 | KMS / HSM 或独立最小权限签名服务 | 待定(W-003) | 绝不入库、不进 Web 进程环境变量明文;首期用离线测试密钥对 |
|
||||
| 发布记录存储 | 关系型库(候选 SQLite → PostgreSQL) | 待定(W-201) | 首期里程碑(协议对齐)不需要数据库;Registry 落地时定 |
|
||||
| 产物托管 | 对象存储 / CDN | 待定(W-004) | 首期用本地静态 HTTP 服务模拟 |
|
||||
| Web 框架 / UI | 待定 | 待定(W-501) | Phase 5 才启动 UI;先协议后界面 |
|
||||
| 测试 | 单元测试 + corpus 字节回归 + Schema 校验闸门 | 已定原则 | corpus 断言为 CI 必跑项,期望值禁止自举 |
|
||||
| CI | 待定(参考 soft_quay 的 Gitea Actions + 本地脚本双轨) | 待定(W-002) | 本地脚本先行,保证无 CI 也能跑同一套闸门 |
|
||||
|
||||
## 二、决策记录与演进
|
||||
|
||||
- **协议权威源**:`soft_quay/docs/api.md`、`soft_quay/schemas/*.json`、`soft_quay/testdata/catalog/canonical-vectors.json` 是唯一权威;本仓库文档只做发布端视角摘要,冲突时以客户端仓库为准。
|
||||
- **先协议后 UI**:第一里程碑是"产出被客户端验签通过、被 corpus + Schema 回归通过的 manifest";Web 界面在流水线可信之后才开工。
|
||||
- **首期本地模拟**:本地生成签名清单 + 本地静态 HTTP 服务即可让客户端跑通闭环,不先建对象存储,避免过早绑定云厂商。
|
||||
- **签名服务只签受控结构**:清单 / 许可证 / 撤销名单三类;不对任意字节签名,防止被当通用签名 oracle。
|
||||
- **单公钥时代不做轮换字段**:客户端协议无 key ID;多公钥 / 轮换需先在客户端仓库做协议升级,本仓库不得抢跑。
|
||||
|
||||
## 三、构建与运行命令
|
||||
|
||||
> 工程骨架由 W-001 建立;根目录 `init.sh` / `init.ps1` 为统一入口,W-001 完成前三个命令为占位符,运行会主动失败。
|
||||
|
||||
| 用途 | 命令 |
|
||||
| --- | --- |
|
||||
| 统一入口 | `./init.sh`(PowerShell 用 `./init.ps1`)——【W-001 替换真实命令】 |
|
||||
| 依赖同步 | 【待定】 |
|
||||
| 测试与 corpus 回归 | 【待定】 |
|
||||
| 本地静态发布模拟 | 【待定,W-204】 |
|
||||
|
||||
## 四、依赖纪律
|
||||
|
||||
- 新增第三方依赖前,先说明用途、替代方案和维护成本。
|
||||
- 加密实现优先使用语言标准库(Go 为 `crypto/ed25519`);不引入自定义或小众加密库。
|
||||
- 不允许同一职责并存两套实现(如两套 canonicalizer、两套 Schema 校验)。
|
||||
- 不确定的技术选型先更新本文,再进入代码。
|
||||
@@ -0,0 +1,135 @@
|
||||
# 架构设计
|
||||
|
||||
> 系统结构、职责边界、数据模型与发布流水线。协议字段以 [api.md](api.md)(权威源 `soft_quay` 仓库)为准;原始设计规格见 [softbox-catalog-design.md](softbox-catalog-design.md)。
|
||||
|
||||
## 一、信任模型(一切设计的出发点)
|
||||
|
||||
客户端 `soft_quay` 对远端内容的信任**仅来自 Ed25519 签名**:
|
||||
|
||||
```text
|
||||
soft_quay_web(持私钥) soft_quay 客户端(内置公钥)
|
||||
───────────────────────── ─────────────────────────
|
||||
用受限规范 JSON + 私钥签名 ──► 拉取 → 去掉顶层 signature → 同规则规范化
|
||||
生成 manifest-modern/win7.json → Ed25519 验签 → Schema/channel/架构过滤
|
||||
上传对象存储 / CDN(HTTPS) → 缓存;验签失败则拒绝、退回上次可信缓存
|
||||
包 url/size/sha256 写入清单 → 下载 → 同句柄 size→SHA-256→安全解压
|
||||
```
|
||||
|
||||
由此推出的硬性要求:
|
||||
|
||||
1. **规范化必须字节级一致**:发布端与客户端对同一 JSON 输入必须产出逐字节相同的规范字节与签名;共用 `canonical-vectors.json` corpus 作为跨实现回归基线,发布端不得自举期望值。
|
||||
2. **私钥保管是最高安全目标**:私钥泄露 = 可伪造任意清单 / 包记录 / 许可证 = 客户端全线沦陷。
|
||||
3. **客户端不做在线校验**:下架、撤销、版本兼容都必须能"离线表达"在签名文件里(status 字段、签名撤销名单 + 宽限期)。
|
||||
|
||||
## 二、组件与职责
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────┐
|
||||
子软件 app-* CI │ 构建标准 ZIP 包 + Release 元数据 │
|
||||
└───────────────┬─────────────────────────────┘
|
||||
│ 上传候选包
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ soft_quay_web │
|
||||
│ ┌────────────┐ ┌────────────┐ ┌──────────────────────┐ │
|
||||
│ │ Ingestion │──►│ Registry │──►│ Manifest Generator │ │
|
||||
│ │ 包接收校验 │ │ 元数据/发布 │ │ 双通道清单组装 │ │
|
||||
│ └────────────┘ │ 记录 (DB) │ └──────────┬───────────┘ │
|
||||
│ │ └────────────┘ │ 待签名规范字节 │
|
||||
│ │ size/SHA-256 ▼ │
|
||||
│ │ ┌──────────────────────┐ │
|
||||
│ │ │ Signing Service │ │
|
||||
│ │ │ (Ed25519 私钥,隔离/ │ │
|
||||
│ │ │ KMS/HSM,受限接口) │ │
|
||||
│ │ └──────────┬───────────┘ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌────────────┐ ┌────────────┐ ┌──────────────────────┐ │
|
||||
│ │ License & │ │ Web 管理端 │ │ Publisher (发布器) │ │
|
||||
│ │ Revocation │ │ (UI) │ │ 上传对象存储/CDN │ │
|
||||
│ └────────────┘ └────────────┘ └──────────┬───────────┘ │
|
||||
│ │ 审计日志 (append-only) │ │
|
||||
└────────┼──────────────────────────────────────┼──────────────┘
|
||||
▼ ▼
|
||||
审计/合规存档 对象存储 / CDN(静态托管)
|
||||
│ HTTPS
|
||||
▼
|
||||
soft_quay 客户端
|
||||
```
|
||||
|
||||
| 组件 | 职责 | 不含 |
|
||||
| --- | --- | --- |
|
||||
| Ingestion | 接收子软件 CI 的 ZIP 候选包;校验包协议结构(app.json/files.json/payload)、计算 size 与 SHA-256、比对声明版本/ID/架构 | 构建子软件、跑子软件业务 |
|
||||
| Registry | 软件元数据、发布记录、包坐标(url/size/sha256)、status、channel、Catalog 版本 | 子软件源码、客户端状态 |
|
||||
| Manifest Generator | 从 Registry 按 channel 组装双通道清单,产出**待签名规范字节** | 签名(委托签名服务) |
|
||||
| Signing Service | 唯一持有 Ed25519 私钥;只对清单/许可证/撤销名单三类受控结构签名 | 业务逻辑、任意数据签名 |
|
||||
| License & Revocation | 绑定 machine_hash + products 签发许可证;维护签名撤销名单 + 宽限期 | 保存原始硬件序列号/MAC |
|
||||
| Publisher | 签名清单、ZIP 包、图标上传对象存储/CDN;保证原子发布(先包后清单) | 生成未签名/未校验产物 |
|
||||
| Web 管理端 | 发布者操作界面与编排;签名经受控接口调用 | 直接持有私钥 |
|
||||
| Audit | append-only 记录每次发布/下架/撤销/签发 | 可被覆盖的可变日志 |
|
||||
|
||||
### 关键边界
|
||||
|
||||
- **签名服务是唯一持钥点**:其余组件(含 Web 后台)只能提交"受限结构 + 请求签名",拿回签名串,拿不到私钥。
|
||||
- **Web 管理端不等于签名端**:私钥不进 Web 进程。
|
||||
- **协议 Schema / corpus 单一权威源**:引用 `soft_quay/schemas/` 与 `canonical-vectors.json`,CI 校验发布端产物能被同一份 corpus 与 Schema 通过。
|
||||
|
||||
## 三、数据模型(建议,W-201 定稿)
|
||||
|
||||
> 首期里程碑(协议对齐)不需要数据库;下面是 Registry 落地时的起点草案,定稿后更新本节并删除"建议"标注。
|
||||
|
||||
```text
|
||||
Software 1 ──── * Release 1 ──── * PackageArtifact
|
||||
│ │
|
||||
│ └──── * AuditEvent(所有实体的操作都产生)
|
||||
└ id 永久稳定
|
||||
|
||||
License * ──── RevocationEntry(按 license_id 撤销)
|
||||
```
|
||||
|
||||
| 实体 | 关键字段 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| Software | `id`(永久稳定)、name、description、category、tags、icon(sha256)、homepage、tutorial、entry_exe、requires_admin、status | 客户端列表页唯一来源;一行对应清单里一个 app |
|
||||
| Release | software_id、version(SemVer)、channel(modern/win7)、min_os、architectures、created_at、catalog_version | 一次版本发布;同 software 同 channel 只有一个 active release 进清单 |
|
||||
| PackageArtifact | release_id、arch(386/amd64)、url、size、sha256、signature、ingested_at | 来自 Ingestion 的校验结果;url 为发布后的绝对 HTTPS |
|
||||
| License | license_id、machine_hash、products[]、issued_at、perpetual、update_policy、rebind_policy | 不保存原始硬件标识 |
|
||||
| RevocationEntry | license_id、revoked_at、reason 摘要 | 进入签名撤销名单 |
|
||||
| AuditEvent | actor、action、target、content_digest、signature_fingerprint、at | append-only,不可修改 |
|
||||
|
||||
## 四、发布流水线
|
||||
|
||||
```text
|
||||
子软件 CI 产出标准 ZIP 包 + Release 元数据
|
||||
→ Ingestion 校验包结构 / app.json 一致性 / 计算 size + SHA-256
|
||||
→ Registry 登记发布记录(url 坐标、status、channel、Catalog 版本)
|
||||
→ Schema 校验(manifest/app schema)
|
||||
→ Manifest Generator 组装 modern/win7 待签名规范字节
|
||||
→ Signing Service 用 Ed25519 私钥签名
|
||||
→ CI 用 canonical-vectors corpus + Schema 回归验证产物
|
||||
→ Publisher 原子上传:先传 ZIP 包/图标到对象存储,再切换签名清单
|
||||
→ soft_quay 客户端发现新版本
|
||||
```
|
||||
|
||||
- 一个产品可产出多目标(如 `json-parser_1.4.2_windows_amd64.zip`、`..._win7_amd64.zip`);只生成实际支持并测试过的目标。
|
||||
- **原子性**:包必须先于清单可用,避免客户端拿到指向尚未上传包的清单。
|
||||
|
||||
## 五、签名与密钥管理
|
||||
|
||||
- **私钥隔离**:理想部署为 KMS/HSM 或独立最小权限签名服务;Web 后台与业务进程只经受控接口请求签名,不接触私钥材料(保管方案 W-003 裁定)。
|
||||
- **只签受控结构**:清单 / 许可证 / 撤销名单;不对任意字节签名。
|
||||
- **公钥分发**:客户端内置公钥;测试用公钥见 corpus。正式公钥的内置与更新方式随密钥轮换设计裁定(Backlog,需客户端协议升级)。
|
||||
- **签名审计**:每次签名请求记录操作者、目标结构摘要、时间、密钥指纹。
|
||||
|
||||
## 六、版本 / 协议兼容管理
|
||||
|
||||
| 版本类型 | 示例 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| 产品版本 | 1.4.2 | 用户看到的软件版本 |
|
||||
| SDK 版本 | sdk/core v1.2.0 | 公共能力兼容性 |
|
||||
| 软件包协议 | schema_version 1 | 盒子与包的结构协议 |
|
||||
| Catalog 版本 | 2026.07.16.1 | 目录发布时间 |
|
||||
|
||||
原则:SDK 升级不强迫历史软件立即升级;盒子至少兼容当前与前一个包协议版本;清单顶层 `min_box_version` 声明所需最低盒子版本。
|
||||
|
||||
## 七、开发顺序
|
||||
|
||||
见 [任务路线图](06-tasks.md):Phase 0 裁定与骨架 → Phase 1 协议对齐核心(最高风险先行)→ Phase 2 登记与本地模拟发布闭环 → Phase 3 签名隔离与正式发布 → Phase 4 许可证与撤销 → Phase 5 Web 管理端与审计。
|
||||
@@ -0,0 +1,82 @@
|
||||
# 编码规则(Coding Rules)
|
||||
|
||||
> 每次写代码前先读完本文件。与技术细节冲突时,以 [技术栈](03-tech-stack.md) / [架构设计](04-architecture.md) 为准;与"该不该做"冲突时,以 [需求](02-requirements.md) 为准;协议字段以 [api.md](api.md)(权威源 `soft_quay` 仓库)为准。
|
||||
|
||||
## 0. 黄金法则
|
||||
|
||||
1. **不臆造**:数据字段、文件、接口、依赖,不确定就查证或询问。
|
||||
2. **守范围**:只做当前任务要求的事,不顺手加后续功能。
|
||||
3. **照架构**:使用既定技术栈和组件边界,不擅自引入新框架。
|
||||
4. **小步改**:一次只解决一个问题,不夹带无关重构。
|
||||
5. **可验证**:改完必须能构建、能测试、对得上验收标准。
|
||||
|
||||
## 1. 动手前
|
||||
|
||||
- 按链路确认:`vision` → `requirements` → `tech-stack` → `architecture` → `tasks`。
|
||||
- 找到本任务对应的验收标准,写之前就知道"怎么算做对"。
|
||||
- 先找现有函数、组件、工具和测试,复用优先。
|
||||
- 未定项(06-tasks 裁定清单)未裁定前不得替用户在代码里做决定。
|
||||
|
||||
## 2. 协议对齐纪律(本项目最容易翻车的地方)
|
||||
|
||||
- canonicalization / 签名的跨实现回归**只消费** `soft_quay/testdata/catalog/canonical-vectors.json` 的静态期望值(`document`/`signed_payload_base64`/`signature`/`want_error`);**禁止**用本仓库 canonicalizer 或测试私钥重新生成"正确"向量。
|
||||
- 签名域规则严格执行 [api.md](api.md) §签名域:只移除顶层 `signature`;拒绝重复键、尾随数据、浮点/指数、`-0`、非法 surrogate;signature 必须是唯一标准 padded Base64(64 字节),CR/LF/空白/padding 变体一律拒绝;大整数保持原始十进制 token 不失精度。
|
||||
- 协议字段只信 `soft_quay/docs/api.md` 与 `soft_quay/schemas/`;本仓库不得单方面新增/修改协议字段,需要变更时先在客户端仓库定稿,再同步本仓库文档与实现。
|
||||
- 不定义第二套验签域(package 级、图标级或其他);单公钥协议升级前不做 key ID / 轮换字段。
|
||||
- 发布前必须通过 Schema 校验:`additionalProperties: false`、id 唯一且 `^[a-z0-9-]+$`、SemVer 合法、architectures 与 packages 键一一对应、URL 为无用户信息无 fragment 的绝对 HTTPS。
|
||||
|
||||
## 3. 安全纪律(违反即安全事故)
|
||||
|
||||
- **私钥永不落地**:私钥不进代码、配置样例、环境变量明文、日志、测试数据、数据库;`testdata/` 只放与客户端 corpus 公钥配对的专用测试密钥对。
|
||||
- 签名服务只对清单 / 许可证 / 撤销名单三类已知结构签名;拒绝任意字节签名请求。
|
||||
- Ingestion 对候选 ZIP 必须校验:包结构(app.json/files.json/payload)、app.json 与登记的 id/version/channel/min_os/architecture 一致、解压防护(路径穿越、文件数/体积/压缩比上限——复用客户端已验证的安全规则,不另造宽松版)。
|
||||
- 双通道清单生成后必须有通道隔离断言:win7 清单不得含不兼容包。
|
||||
- Publisher 必须先确认包与图标可下载,再发布引用它们的清单;失败时清单不得切换。
|
||||
- 许可证与审计:不保存原始硬件序列号/MAC;日志不记录注册码、令牌、敏感查询参数;审计记录 append-only。
|
||||
- 云凭据、真实生产域名配置不入库;用环境配置 + `.example` 占位文件。
|
||||
|
||||
## 4. 事实来源纪律
|
||||
|
||||
- 协议只信 `soft_quay` 仓库;架构边界只信 `docs/04-architecture.md`;当前现实只信 `docs/current-state.md` 和代码。
|
||||
- `docs/softbox-catalog-design.md` 是原始设计规格,编号文档拆分后以编号文档为准;两者冲突时先修文档再动代码。
|
||||
- 不虚构字段、事件、错误码、配置项。
|
||||
- 数据结构变化必须同步更新 `04-architecture.md`、`api.md` 和相关任务;涉及协议的必须先走客户端仓库。
|
||||
|
||||
## 5. 范围纪律
|
||||
|
||||
- 只做 `02-requirements.md` 列出的第一版功能;「后续迭代」表中的功能只记录,不实现。
|
||||
- 不做面向客户端的运行时 API、账号会话、评论评分等清单外功能。
|
||||
- 不为"将来可能用到"提前抽象。
|
||||
|
||||
## 6. 代码规范
|
||||
|
||||
- 标识符使用英文;错误码用稳定英文枚举,UI 负责中文文案。
|
||||
- 错误必须处理,不吞错;每个失败路径要能落到操作者可见的状态或审计/日志。
|
||||
- 注释解释"为什么",不复述"做了什么"。
|
||||
- 状态与产物写入一律临时文件 + 原子替换。
|
||||
- (若选 Go)使用 `gofmt` 与 `go vet`;加密只用标准库 `crypto/ed25519`。
|
||||
|
||||
## 7. 测试与验证
|
||||
|
||||
完成前至少检查:
|
||||
|
||||
- [ ] corpus 全向量回归通过(合法向量字节一致,非法向量按 `want_error` 拒绝)。
|
||||
- [ ] Schema 校验闸门通过;构造的非法样例被拒绝。
|
||||
- [ ] 涉及签名 / 发布 / Ingestion 的改动覆盖:成功、校验失败、签名服务不可用、发布中断各路径。
|
||||
- [ ] 对得上需求验收标准;没有夹带无关改动。
|
||||
- [ ] 涉及文档事实变化时,文档已同步。
|
||||
- [ ] 已在当前任务文件(`docs/tasks/W-<编号>.md`)的 `## 执行记录` 记录跑过的命令和结果作为证据。
|
||||
- [ ] 回复里如实说明跑了什么命令、结果如何。
|
||||
|
||||
## 8. 绝不
|
||||
|
||||
- 绝不把私钥、token、密码、云凭据写进代码或文档样例的真实值里。
|
||||
- 绝不用本仓库实现自举 corpus 期望值,或为让回归通过而修改向量。
|
||||
- 绝不为了让测试通过而删除断言、降低验收标准。
|
||||
- 绝不在没说明的情况下改公共协议(manifest/app.json/许可证)或抢在客户端仓库前变更协议。
|
||||
- 绝不绕过 Schema 校验、签名流程或发布原子性"先跑起来再说"。
|
||||
- 绝不擅自删除用户已有文件或重置工作区。
|
||||
|
||||
## 9. 拿不准就问
|
||||
|
||||
问题要具体,说明你卡在哪里、有哪些选项、倾向哪个选项以及原因。
|
||||
@@ -0,0 +1,100 @@
|
||||
# 任务路线图(Roadmap)
|
||||
|
||||
> 本文是**只读路线图**:维护阶段划分、里程碑、待办池和建议拆分清单。
|
||||
> 真实任务以「一任务一文件」存放在 [`tasks/`](tasks/README.md)(`docs/tasks/W-<编号>.md`);本文不跟踪单任务状态。
|
||||
> 本仓库任务编号用 `W-`;引用客户端任务写全称(如 `soft_quay/T-614`)。
|
||||
|
||||
## 使用规则
|
||||
|
||||
1. **开工先落文件**:从下方建议清单把下一个任务按 [`tasks/README.md`](tasks/README.md) 落成 `docs/tasks/W-<编号>.md`(沿用建议编号),把验收要点展开成可执行、可观察的步骤,再开始实现。
|
||||
2. **单 Agent 串行执行**:同一时间只做一个任务;前一任务 `DONE`、验证并提交后才开始下一任务。
|
||||
3. **不跳步**:依赖未完成的任务不能开工;裁定类任务未完成前,依赖其结论的实现任务不能开工。
|
||||
4. **本文只在规划变化时修改**:单个任务开工或完成不修改本文。
|
||||
5. **动手前**先读 `00-ai-start-here.md`、`05-coding-rules.md` 和 `current-state.md`。
|
||||
|
||||
## 未定项裁定清单
|
||||
|
||||
设计规格 §十 的九个未定项全部登记如下;裁定结论写入对应文档(03/04/api)后,相关实现任务才可开工。
|
||||
|
||||
| 未定项 | 归属 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 签名私钥管理(保管/轮换/签发流程) | W-003 | 立项前裁定;首期用离线测试密钥对 |
|
||||
| 协议 Schema/corpus 引用方式 | W-001 | submodule vs 版本化拷贝 + CI 一致性校验 |
|
||||
| 正式域名与对象存储/CDN(含带宽/防盗链) | W-004 | Phase 3 开工前裁定 |
|
||||
| 密钥 ID / 轮换字段 | Backlog | 需先在客户端仓库做协议升级,单公钥期间不得另造签名域 |
|
||||
| package.signature 独立语义 | Backlog | v1 客户端不独立验;定义待签名字节 + 公钥域后再实现 |
|
||||
| 图标分发字段映射 | Backlog | `sha256:` 到实际下载位置/分辨率变体的发布端映射格式 |
|
||||
| 跨仓库 CI 对齐(消费 corpus 的证据) | W-002 + Backlog | 与 `soft_quay/T-614` 跨仓库协调,证据交换机制记 Backlog |
|
||||
| 授权产品映射(product_id ↔ products) | W-403 | Phase 4 |
|
||||
| 发布端形态与操作者权限模型 | W-501 | Phase 5 开工时裁定;首期单操作者 |
|
||||
|
||||
## 建议拆分清单
|
||||
|
||||
### Phase 0 · 立项裁定与工程骨架
|
||||
|
||||
| ID | 任务 | 依赖 | 验收要点 |
|
||||
| --- | --- | --- | --- |
|
||||
| W-001 | 初始化工程骨架与协议权威源接入 | - | 确认后端语言(建议 Go)并更新 `03-tech-stack.md`;裁定并落实 `soft_quay` schemas/corpus 的引用方式;工程可构建、测试可运行;用真实命令替换 `init.sh`/`init.ps1` 顶部三个变量,并同步 `00-ai-start-here.md`、`03-tech-stack.md`、`current-state.md` |
|
||||
| W-002 | corpus + Schema 回归闸门 | W-001 | 本地脚本(后接 CI)执行:corpus 引用一致性校验 + 全部向量断言入口 + Schema 校验入口;任何向量失败即闸门失败 |
|
||||
| W-003 | 裁定:签名私钥管理方案 | - | 文档决策任务:谁持有、如何保管与轮换、签发流程;结论写入 `03-tech-stack.md` 与 `04-architecture.md` §五;明确首期测试密钥对与正式密钥的切换条件 |
|
||||
| W-004 | 裁定:正式域名与对象存储/CDN | - | 文档决策任务:域名、存储选型、带宽成本与防盗链策略;结论写入 `03-tech-stack.md`;Phase 3 前完成即可 |
|
||||
|
||||
### Phase 1 · 协议对齐核心(最高风险)
|
||||
|
||||
| ID | 任务 | 依赖 | 验收要点 |
|
||||
| --- | --- | --- | --- |
|
||||
| W-101 | 受限规范化与 Ed25519 签名/验签 | W-002 | canonicalizer 实现 api.md §签名域全部拒绝规则;corpus 合法向量逐字节一致、非法向量按 `want_error` 拒绝;期望值零自举 |
|
||||
| W-102 | manifest / app Schema 校验器 | W-101 | Schema 合规、无未知/重复/缺失字段、id 唯一、SemVer 合法、architectures↔packages 对应、channel 一致、绝对 HTTPS;非法样例表驱动全拒 |
|
||||
| W-103 | 端到端:客户端可验签的 manifest | W-101, W-102 | 用离线测试密钥对(与客户端 corpus 公钥配对)生成 manifest-modern/win7;`soft_quay` 客户端验签通过;篡改任一字节后验签失败;**达成 M1** |
|
||||
|
||||
### Phase 2 · 登记与本地发布闭环
|
||||
|
||||
| ID | 任务 | 依赖 | 验收要点 |
|
||||
| --- | --- | --- | --- |
|
||||
| W-201 | Registry 数据模型定稿与存储 | W-103 | `04-architecture.md` §三数据模型定稿(去掉"建议"标注);Software/Release/PackageArtifact 读写与约束(id 稳定、同 channel 单 active release)落地并有测试 |
|
||||
| W-202 | Ingestion:候选包接收校验 | W-201 | 定稿提交方式与认证(api.md §六);包结构/app.json 一致性/size/SHA-256 校验;非法包表驱动全拒;不生成未测试目标 |
|
||||
| W-203 | 双通道 Manifest Generator | W-201 | 从 Registry 按 channel 组装待签名规范字节;通道隔离断言;status/catalog 版本正确 |
|
||||
| W-204 | 本地静态发布模拟与客户端闭环 | W-202, W-203 | 本地静态 HTTP 服务托管签名清单 + ZIP;客户端跑通「清单→下载→安装」;发布顺序为先包后清单;**达成 M2** |
|
||||
|
||||
### Phase 3 · 签名隔离与正式发布
|
||||
|
||||
| ID | 任务 | 依赖 | 验收要点 |
|
||||
| --- | --- | --- | --- |
|
||||
| W-301 | 独立签名服务 | W-204, W-003 | 私钥只在签名服务;只签三类受控结构;拒绝任意字节;每次请求审计(操作者/结构摘要/时间/密钥指纹);业务进程无私钥材料的验证手段 |
|
||||
| W-302 | Publisher 原子发布 | W-301, W-004 | 上传对象存储/CDN;先包后清单强制;失败不切清单;发布结果可回查 |
|
||||
| W-303 | 正式密钥生成与公钥内置协调 | W-301 | 按 W-003 方案生成正式密钥;与 `soft_quay` 协调正式公钥内置;测试密钥不再用于生产产物 |
|
||||
|
||||
### Phase 4 · 许可证与撤销
|
||||
|
||||
| ID | 任务 | 依赖 | 验收要点 |
|
||||
| --- | --- | --- | --- |
|
||||
| W-401 | 许可证签发 | W-301 | machine_hash + products 签发;客户端离线验签通过;不保存原始硬件标识;签发入审计 |
|
||||
| W-402 | 撤销名单结构裁定与签发 | W-401 | 与客户端协调定稿 revocation list 字段结构与宽限期时长(写入客户端 api.md 后同步本仓库);签名名单可被客户端消费 |
|
||||
| W-403 | 授权产品映射管理 | W-401 | product_id 与许可证 products 的登记与映射;映射错误可被发现 |
|
||||
|
||||
### Phase 5 · Web 管理端与审计
|
||||
|
||||
| ID | 任务 | 依赖 | 验收要点 |
|
||||
| --- | --- | --- | --- |
|
||||
| W-501 | 裁定权限模型 + 管理端骨架 | W-204 | 裁定操作者认证与权限(首期单操作者);Web 框架选型写入 `03-tech-stack.md`;管理端骨架可登录 |
|
||||
| W-502 | 登记/发布/下架/撤销/签发操作流 | W-501, W-302 | 全部操作可在后台完成并走既有流水线;危险操作有确认 |
|
||||
| W-503 | append-only 审计与查看 | W-502 | 审计记录不可覆盖;后台可按时间/操作者/对象检索 |
|
||||
|
||||
## 里程碑
|
||||
|
||||
- **M1**:corpus + Schema 回归通过,产出被 `soft_quay` 客户端验签通过的 manifest(Phase 0~1)。
|
||||
- **M2**:本地模拟发布闭环:登记 → 接收校验 → 签名 → 静态发布 → 客户端安装(Phase 2)。
|
||||
- **M3**:正式发布链路:签名服务隔离 + 对象存储原子发布 + 正式密钥(Phase 3)。
|
||||
- **M4**:许可证 / 撤销签发可用,客户端离线验证通过(Phase 4)。
|
||||
- **M5**:Web 管理端 + 审计,发布者全流程后台化(Phase 5)。
|
||||
|
||||
## 待办池(Backlog)
|
||||
|
||||
- 密钥 ID / 轮换字段(需客户端协议升级先行)。
|
||||
- package.signature 独立签名域定义与实现。
|
||||
- 图标 `sha256:` → 下载位置 / 分辨率变体的发布端映射格式。
|
||||
- 跨仓库 CI 对齐证据交换机制(与 `soft_quay/T-614` 协调)。
|
||||
- 多操作者权限与发布审批流。
|
||||
- 防盗链与带宽成本优化细化(若 W-004 未完全覆盖)。
|
||||
- `routes.md` / 交互清单等 UI 文档(Phase 5 启动时按模板补齐)。
|
||||
- files.json v1.1 强制后的 Ingestion 校验升级。
|
||||
@@ -0,0 +1,42 @@
|
||||
# 项目文档导航
|
||||
|
||||
> soft_quay_web(SoftBox 中央发布 / 登记系统)的 harness coding 文档集。agent 开始工作时以 [`00-ai-start-here.md`](00-ai-start-here.md) 为入口。
|
||||
|
||||
## 一句话定位
|
||||
|
||||
soft_quay_web = 软件登记 + 构建包接收校验 + Ed25519 签名服务 + 双通道清单生成 + 对象存储发布 + 许可证/撤销签发 + Web 管理后台;产物是静态签名文件,信任根是私钥,客户端 `soft_quay` 只内置公钥离线验签。
|
||||
|
||||
## 文档导航
|
||||
|
||||
- [`../AGENTS.md`](../AGENTS.md):AI coding agent 的仓库级入口(含硬性边界)。
|
||||
- [`../CLAUDE.md`](../CLAUDE.md):Claude Code 的薄入口,具体规则以 `AGENTS.md` 为准。
|
||||
- [`../progress.md`](../progress.md):可选历史归档 / 项目级大事记。
|
||||
- [AI 开发入口](00-ai-start-here.md):agent 每次开始工作的入口、阅读顺序和任务领取规则。
|
||||
- [项目愿景](01-vision.md):为什么做、为谁做、设计原则、非目标。
|
||||
- [需求](02-requirements.md):要什么、用户故事、验收标准,不写技术实现。
|
||||
- [技术栈](03-tech-stack.md):选型、待定项、构建命令、依赖纪律。
|
||||
- [架构设计](04-architecture.md):信任模型、组件边界、数据模型、发布流水线、密钥管理。
|
||||
- [编码规则](05-coding-rules.md):协议对齐与安全的硬约束。
|
||||
- [任务路线图](06-tasks.md):Phase 0-5 阶段划分、里程碑、未定项裁定清单、待办池;只读。
|
||||
- [任务文件(默认)](tasks/README.md):一任务一文件 `docs/tasks/W-<编号>.md`,单 Agent 串行执行。
|
||||
- [协议合约](api.md):发布端视角的清单 / 包 / 许可证摘要与本仓库特有接口;权威源在 `soft_quay` 仓库。
|
||||
- [设计规格(原始)](softbox-catalog-design.md):立项前的完整设计规格;内容已拆分进编号文档,冲突时以编号文档为准。
|
||||
- [当前实现状态](current-state.md):可覆盖的当前快照。
|
||||
- [Agent 上下文清单](agent-context.md) / [`agent-context.json`](agent-context.json) / [`Schema`](agent-context.schema.json):按任务类型选择文档。
|
||||
- [收尾检查清单](clean-state-checklist.md):会话结束前逐项检查。
|
||||
- [`../init.sh`](../init.sh) / [`../init.ps1`](../init.ps1):标准启动与验证入口(W-001 前为占位符)。
|
||||
- [`../scripts/validate_agent_context.py`](../scripts/validate_agent_context.py):零第三方依赖校验上下文清单。
|
||||
|
||||
## 任务 / 进度 / 当前状态
|
||||
|
||||
- `tasks/`(`docs/tasks/W-<编号>.md`)维护任务:规格、依赖、状态(frontmatter)和执行记录,一任务一文件。
|
||||
- `06-tasks.md` 维护路线图:阶段划分、里程碑和待办池,不跟踪单任务状态。
|
||||
- `current-state.md` 维护当前快照:当前目录、当前可运行命令、任务摘要和下一个可领取任务。
|
||||
- `../progress.md` 可选:历史归档或项目级大事记,不逐任务追加。
|
||||
|
||||
## 维护原则
|
||||
|
||||
- 需求变化先改文档,再改代码。
|
||||
- 协议(manifest / app.json / 许可证)以客户端仓库 `soft_quay` 为唯一权威源;本仓库文档只做摘要,变更先走客户端仓库。
|
||||
- 代码现实变化后同步 `current-state.md`;任务长期状态和执行证据写进对应任务文件。
|
||||
- agent 开始新任务前,必须从 `00-ai-start-here.md` 进入。
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"schema": "docs/agent-context.schema.json",
|
||||
"schema_version": 1,
|
||||
"authority": {
|
||||
"bootstrap": "local_checkout",
|
||||
"framework_templates": "current_repository",
|
||||
"project_facts": "current_project_repository",
|
||||
"coordination": "local_task_files"
|
||||
},
|
||||
"bootstrap": {
|
||||
"always_read": [
|
||||
"AGENTS.md",
|
||||
"docs/00-ai-start-here.md",
|
||||
"docs/05-coding-rules.md",
|
||||
"docs/current-state.md"
|
||||
]
|
||||
},
|
||||
"routes": {
|
||||
"documentation": [
|
||||
"docs/README.md",
|
||||
"docs/01-vision.md",
|
||||
"docs/02-requirements.md",
|
||||
"docs/softbox-catalog-design.md"
|
||||
],
|
||||
"protocol": [
|
||||
"docs/api.md",
|
||||
"docs/04-architecture.md",
|
||||
"docs/05-coding-rules.md"
|
||||
],
|
||||
"data": [
|
||||
"docs/02-requirements.md",
|
||||
"docs/04-architecture.md",
|
||||
"docs/api.md"
|
||||
],
|
||||
"deploy": [
|
||||
"docs/03-tech-stack.md",
|
||||
"docs/current-state.md"
|
||||
]
|
||||
},
|
||||
"tasks": {
|
||||
"roadmap": "docs/06-tasks.md",
|
||||
"directory": "docs/tasks/",
|
||||
"template": "docs/tasks/_template.md"
|
||||
},
|
||||
"refresh": {
|
||||
"context_ref": "default_branch_head_sha",
|
||||
"cache_key": "file_sha",
|
||||
"unchanged_file": "reuse_within_current_session",
|
||||
"changed_ref": "reread_manifest_and_routed_documents"
|
||||
},
|
||||
"degraded_mode": {
|
||||
"continue_claimed_task": true,
|
||||
"claim_new_task": false,
|
||||
"write_remote_state": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
# Agent 上下文清单
|
||||
|
||||
> [`agent-context.json`](agent-context.json) 是机器可读的文档路由,[`agent-context.schema.json`](agent-context.schema.json) 定义结构契约;本文解释 agent 应如何使用它。清单只保存路径和刷新规则,不复制文档正文。
|
||||
|
||||
## 解决什么问题
|
||||
|
||||
上下文清单解决的是"本轮该读什么":
|
||||
|
||||
1. 先读 `bootstrap.always_read`,建立最小安全与状态上下文。
|
||||
2. 根据任务类型选择一个或多个 `routes`(本仓库:`documentation` 文档类、`protocol` 协议/签名类、`data` 数据模型类、`deploy` 构建部署类)。
|
||||
3. 只读取这些路径和本轮任务文件。
|
||||
4. 用默认分支头提交 SHA 作为 `context_ref`,用单文件 SHA 作为缓存键。
|
||||
|
||||
## 首次接入与日常会话
|
||||
|
||||
首次接入、清单缺失或清单校验失败时,执行 `00-ai-start-here.md` 中的完整阅读顺序,先修复清单再做功能任务。
|
||||
|
||||
日常会话执行:
|
||||
|
||||
```text
|
||||
AGENTS.md
|
||||
-> agent-context.json
|
||||
-> bootstrap.always_read
|
||||
-> 本轮任务文件(docs/tasks/W-<编号>.md)
|
||||
-> routes.<任务类型>
|
||||
-> 修改与验证
|
||||
```
|
||||
|
||||
一个任务可以命中多个路由,重复路径只加载一次。
|
||||
|
||||
## 提交 SHA 与缓存
|
||||
|
||||
- `context_ref`:领取任务时默认分支的头提交 SHA。
|
||||
- 同一会话内文件 SHA 未变化时复用已读内容;默认分支头变化时重新读取清单及变化文件。
|
||||
- 本地有未提交改动:本地内容仅对当前 worktree 有效;回复和任务记录中要说明差异。
|
||||
|
||||
缓存只用于减少重复读取,不能跨提交假定内容不变,也不能代替 Git 历史。
|
||||
|
||||
## 权威来源
|
||||
|
||||
| 信息 | 权威来源 |
|
||||
| --- | --- |
|
||||
| 仓库级硬规则 | 最近作用域的 `AGENTS.md` |
|
||||
| 协议(清单/包/许可证/签名域) | 客户端仓库 `soft_quay`(api.md、schemas、corpus);本仓库 `docs/api.md` 仅为摘要 |
|
||||
| 需求、架构、编码纪律 | 本仓库版本化文档 |
|
||||
| 任务规格与长期执行证据 | `docs/tasks/W-<编号>.md` |
|
||||
| 当前代码行为 | 代码与真实验证结果 |
|
||||
|
||||
## 断连 / 客户端仓库不可用降级
|
||||
|
||||
同级客户端仓库(`../soft_quay`)不可读时:
|
||||
|
||||
- 可以基于本仓库已引用的 Schema/corpus 副本(W-001 落实后)继续当前任务。
|
||||
- 不得凭记忆修改协议相关实现;涉及协议疑义先恢复对权威源的访问。
|
||||
|
||||
## 清单维护
|
||||
|
||||
新增、移动或删除清单引用的文件时,同步修改 `agent-context.json`,并运行:
|
||||
|
||||
```bash
|
||||
python3 scripts/validate_agent_context.py
|
||||
```
|
||||
|
||||
校验必须确认:必需分区存在、路径为仓库相对路径、引用文件真实存在、最小启动文件齐全。
|
||||
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://example.invalid/schemas/agent-context.schema.json",
|
||||
"title": "Harness Coding agent context manifest",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schema",
|
||||
"schema_version",
|
||||
"authority",
|
||||
"bootstrap",
|
||||
"routes",
|
||||
"tasks",
|
||||
"refresh",
|
||||
"degraded_mode"
|
||||
],
|
||||
"properties": {
|
||||
"schema": {
|
||||
"const": "docs/agent-context.schema.json"
|
||||
},
|
||||
"schema_version": {
|
||||
"const": 1
|
||||
},
|
||||
"authority": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"required": [
|
||||
"bootstrap",
|
||||
"framework_templates",
|
||||
"project_facts",
|
||||
"coordination"
|
||||
]
|
||||
},
|
||||
"bootstrap": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["always_read"],
|
||||
"properties": {
|
||||
"always_read": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"items": {"$ref": "#/$defs/repositoryPath"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"routes": {
|
||||
"type": "object",
|
||||
"minProperties": 1,
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"items": {"$ref": "#/$defs/repositoryPath"}
|
||||
}
|
||||
},
|
||||
"tasks": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["roadmap", "directory", "template"],
|
||||
"properties": {
|
||||
"roadmap": {"$ref": "#/$defs/repositoryPath"},
|
||||
"directory": {"$ref": "#/$defs/repositoryPath"},
|
||||
"template": {"$ref": "#/$defs/repositoryPath"}
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["context_ref", "cache_key", "unchanged_file", "changed_ref"],
|
||||
"properties": {
|
||||
"context_ref": {"const": "default_branch_head_sha"},
|
||||
"cache_key": {"const": "file_sha"},
|
||||
"unchanged_file": {"const": "reuse_within_current_session"},
|
||||
"changed_ref": {"const": "reread_manifest_and_routed_documents"}
|
||||
}
|
||||
},
|
||||
"degraded_mode": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["continue_claimed_task", "claim_new_task", "write_remote_state"],
|
||||
"properties": {
|
||||
"continue_claimed_task": {"type": "boolean"},
|
||||
"claim_new_task": {"type": "boolean"},
|
||||
"write_remote_state": {"type": "boolean"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"repositoryPath": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"pattern": "^(?!/)(?!.*\\\\)(?!.*(^|/)\\.\\.(/|$))(?![A-Za-z][A-Za-z0-9+.-]*:).+$"
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
# 协议合约(发布端视角摘要)
|
||||
|
||||
> **权威源在客户端仓库**:`soft_quay/docs/api.md`、`soft_quay/schemas/*.json`、`soft_quay/testdata/catalog/canonical-vectors.json`。
|
||||
> 本文是发布端视角的摘要与本仓库特有接口的合约位;字段冲突时以客户端仓库为准,协议变更必须先在客户端仓库定稿。
|
||||
|
||||
## 一、Catalog 清单 v1(manifest-{modern,win7}.json)
|
||||
|
||||
顶层(`additionalProperties: false`,全部必填):
|
||||
|
||||
| 字段 | 约束 |
|
||||
| --- | --- |
|
||||
| `schema_version` | 常量 `1` |
|
||||
| `channel` | `modern` 或 `win7` |
|
||||
| `generated_at` | RFC3339 date-time |
|
||||
| `min_box_version` | SemVer |
|
||||
| `apps` | app 数组 |
|
||||
| `signature` | Ed25519,`^[A-Za-z0-9+/]{86}==$` |
|
||||
|
||||
每个 app(`additionalProperties: false`):
|
||||
|
||||
| 字段 | 约束 |
|
||||
| --- | --- |
|
||||
| `id` | `^[a-z0-9-]+$`,永久稳定不可变 |
|
||||
| `name` / `description` | 非空字符串 |
|
||||
| `version` | SemVer 2.0.0 |
|
||||
| `channel` | 常量 `stable` |
|
||||
| `status` | `active` / `deprecated` / `hidden` |
|
||||
| `category` | 非空(单一分类) |
|
||||
| `tags` | 非空字符串数组(≥1) |
|
||||
| `icon` | `^sha256:[0-9A-Fa-f]{64}$`(内容引用,非 URL;可选) |
|
||||
| `homepage` / `tutorial` | 绝对 HTTPS(可选) |
|
||||
| `min_os` | `windows-7-sp1` / `windows-10` / `windows-11` |
|
||||
| `architectures` | `["386"|"amd64"]`,唯一,≥1 |
|
||||
| `entry_exe` | 安全相对路径(拒绝首尾空格/尾随点/DOS 设备名/反斜杠/`:<>"|?*`/`..`) |
|
||||
| `requires_admin` | 布尔 |
|
||||
| `packages` | `{386?, amd64?}`,≥1;键与 `architectures` 一一对应 |
|
||||
|
||||
package 对象:`url`(绝对 HTTPS)、`size`(整数 ≥1)、`sha256`(64 hex)、`signature`(Ed25519 形状)。
|
||||
|
||||
通用约定:传输 HTTPS,JSON UTF-8;时间 RFC3339(UTC);URL 无用户信息、无 fragment;数字仅整数 token(签名域拒绝浮点/指数/`-0`,大整数保持 token 不失精度)。
|
||||
|
||||
## 二、签名域(必须与客户端字节对齐)
|
||||
|
||||
1. 输入为单个 UTF-8 JSON object;拒绝重复键、尾随数据、浮点/指数数字、非法 Unicode surrogate。
|
||||
2. 读取顶层 `signature`(标准 padded Base64,严格解码为 64 字节;拒绝 CR/LF、其他空白、缺失/额外 padding),从对象移除该字段。
|
||||
3. 对剩余值递归规范化:对象键按 Unicode 码点升序、数组保序、字符串按 JSON 转义、数字仅整数 token、无多余空白。
|
||||
4. Ed25519 对上述规范字节签名。
|
||||
|
||||
**跨实现回归**:corpus 含测试公钥(`public_key_base64`)与向量(`document` / `signed_payload_base64` / `signature` / `want_error`),覆盖 Unicode 键序、转义、`<>&`/U+2028/U+2029、合法/非法 surrogate、`-0`/大整数、嵌套 signature、Base64 变体。发布端 CI **必读该 corpus 断言,不得自举期望值**。
|
||||
|
||||
## 三、package.signature 的语义(未定项)
|
||||
|
||||
当前客户端**不独立验证** package 的 `signature`——其文本已被外层清单 Ed25519 签名覆盖。但 Schema 要求该字段存在且形状合法。发布端 v1 须产出形状合法的 Ed25519 串;其独立签名域(待签名字节 + 公钥)尚未定义,在客户端协议升级前**不得**自行定义(见 06-tasks Backlog)。
|
||||
|
||||
## 四、许可证 v1(签发 → 客户端离线验证)
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": 1,
|
||||
"license_id": "lic-...",
|
||||
"machine_hash": "...",
|
||||
"products": ["product-json-parser"],
|
||||
"issued_at": "2026-07-16T00:00:00Z",
|
||||
"perpetual": true,
|
||||
"update_policy": "updates-until-2027-12-31",
|
||||
"rebind_policy": "self-service-1-per-90d",
|
||||
"signature": "..."
|
||||
}
|
||||
```
|
||||
|
||||
- `machine_hash` 由客户端平台层多硬件标识清洗生成;许可证**不保存**原始序列号/MAC。
|
||||
- Ed25519 签名;客户端内置公钥离线验签。
|
||||
- 撤销名单同样签名 + 客户端缓存 + 网络失败宽限期;名单具体结构与宽限期时长是未定项(W-402)。
|
||||
|
||||
## 五、标准软件包协议 v1(ZIP,发布端负责接收校验)
|
||||
|
||||
```text
|
||||
<id>_<version>_windows_<arch>.zip
|
||||
├─ app.json # schema_version/id/name/vendor/version/channel/min_os/
|
||||
│ # architecture/entrypoint/working_directory/product_id/
|
||||
│ # supports_trial/requires_admin/data_policy/update_policy
|
||||
├─ files.json # 解压后文件清单(v1.1 强制,v1 推荐)
|
||||
└─ payload/ # 实际安装文件
|
||||
```
|
||||
|
||||
发布端校验 app.json 与登记一致、payload 结构合法,并计算整包 SHA-256 写入清单;`data_policy` 固定 `local-app-data`、`update_policy` 固定 `managed-by-softbox`。
|
||||
|
||||
## 六、本仓库特有接口(待定稿)
|
||||
|
||||
以下接口不面向客户端,是发布系统内部 / 对 CI 的合约;各自任务落地时在本节定稿。
|
||||
|
||||
| 接口 | 说明 | 状态 |
|
||||
| --- | --- | --- |
|
||||
| Ingestion 提交接口 | 子软件 CI 提交候选 ZIP + Release 元数据的方式(HTTP 端点 / 对象存储投递)与认证 | 待定(W-202) |
|
||||
| 签名服务接口 | 受控结构 + 请求签名 → 签名串;调用方拿不到私钥;每次请求审计 | 待定(W-301) |
|
||||
| Web 管理端 API | 操作者认证与登记/发布/下架/撤销/签发操作 | 待定(W-501) |
|
||||
| 静态产物布局 | 对象存储上 manifest-*.json / ZIP / 图标的路径布局与图标 `sha256:` → 实际位置映射 | 待定(W-204 首版本地布局;图标映射见 Backlog) |
|
||||
@@ -0,0 +1,19 @@
|
||||
# 干净收尾检查清单
|
||||
|
||||
> 每轮会话结束前逐项过一遍,确保仓库处于"下一轮无需人工修复即可直接开工"的状态。
|
||||
> 这是把"提前宣告完成"挡在门外的最后一道关卡,配合 [`05-coding-rules.md`](05-coding-rules.md) 的验证清单使用。
|
||||
|
||||
收尾前确认:
|
||||
|
||||
- [ ] 标准启动路径仍可用(`./init.sh` 或本项目等价命令能跑通;W-001 前占位失败属预期,需如实说明)。
|
||||
- [ ] 标准验证 / corpus + Schema 回归(W-002 后)仍可运行,结果如实。
|
||||
- [ ] 本轮执行记录已写进当前任务文件(`docs/tasks/W-<编号>.md`)的 `## 执行记录`(含跑过的命令和结果作为证据)。
|
||||
- [ ] 任务文件 frontmatter 的 `status` 真实反映 `DONE` 与未验证的边界,没有"假 DONE"。
|
||||
- [ ] [`current-state.md`](current-state.md) 的项目级快照与现实一致(启动/验证路径、目录要点、blocker)。
|
||||
- [ ] 没有半成品改动处于未记录状态;如有,已写明 `BLOCKED` / `PARTIAL` 和原因。
|
||||
- [ ] 代码处于可安全恢复的状态(必要时已提交,提交信息清晰、带 `W-<编号>`)。
|
||||
- [ ] 本轮实际修改没有超出任务 `write_paths`。
|
||||
- [ ] 仓库内没有出现私钥材料、云凭据或真实生产 URL;测试数据只用测试密钥对。
|
||||
- [ ] 涉及上下文清单变化时,已运行 `python3 scripts/validate_agent_context.py`。
|
||||
|
||||
任意一项不满足,就先补到满足,再结束会话。
|
||||
@@ -0,0 +1,53 @@
|
||||
# 当前实现状态
|
||||
|
||||
> 本文是可覆盖的**项目级快照**,记录代码与任务的现实状态,帮助 AI coding agent 避免只看计划而忽略仓库现状。
|
||||
> 执行记录写进各任务文件(`docs/tasks/W-<编号>.md`)的 `## 执行记录`,不在本文重复维护。
|
||||
|
||||
## 当前快照
|
||||
|
||||
- 日期:2026-07-20
|
||||
- 阶段:**文档就绪、代码未起步**。harness coding 文档集(00~06、api、tasks、agent-context 等)已建立;设计规格 `softbox-catalog-design.md` 已拆分归位。
|
||||
- 代码:无。没有 go.mod / 源码目录;`init.sh` / `init.ps1` 顶部三个命令为占位符,运行会主动失败(预期,W-001 替换)。
|
||||
- 协议权威源:`../soft_quay/docs/api.md`、`../soft_quay/schemas/`(manifest/app/installed-app/download-task)、`../soft_quay/testdata/catalog/canonical-vectors.json` 均存在于同级客户端仓库;引用方式(submodule / 版本化拷贝)未裁定(W-001)。
|
||||
- 客户端配套现状:`soft_quay` 已完成 Phase 0~4,corpus 已冻结(soft_quay/T-614);客户端当前 blocker 之一是缺可信 Catalog 发布源,对应本仓库 M2/M3。
|
||||
- 版本管理:git 已初始化(main 分支,本地,无远端)。
|
||||
- 当前 blocker:无技术 blocker;立项裁定项(签名私钥管理 W-003、权威源引用方式 W-001)未裁定前,对应实现不能开工。
|
||||
|
||||
## 当前目录要点
|
||||
|
||||
| 路径 | 状态 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `docs/` | 已有 | harness coding 文档集(本次初始化完成)+ 原始设计规格 |
|
||||
| `docs/tasks/` | 已有 | 仅 README 与模板,尚无任务文件;下一步按路线图落 W-001/W-003 |
|
||||
| `scripts/` | 已有 | `validate_agent_context.py`(上下文清单校验) |
|
||||
| `init.sh` / `init.ps1` | 占位 | W-001 替换真实命令 |
|
||||
| 源码目录 | 无 | W-001 建立 |
|
||||
|
||||
## 任务状态
|
||||
|
||||
任务状态以 `docs/tasks/` 各任务文件 frontmatter 的 `status` 为准。本节只写项目级摘要:
|
||||
|
||||
- 已完成:无(项目刚初始化)。
|
||||
- 正在进行:无。下一步按 [`06-tasks.md`](06-tasks.md) 落成 Phase 0 任务;W-003(私钥管理裁定)与 W-001(骨架)无相互依赖,建议先落 W-001。
|
||||
|
||||
## 当前可运行内容
|
||||
|
||||
```bash
|
||||
# 上下文清单校验(唯一当前可运行的验证):
|
||||
python3 scripts/validate_agent_context.py
|
||||
|
||||
# init 脚本当前会按预期失败并提示命令未替换:
|
||||
./init.sh
|
||||
```
|
||||
|
||||
## 开始编码前检查
|
||||
|
||||
1. 读仓库级 agent 规则文件 `AGENTS.md`。
|
||||
2. 读 `docs/00-ai-start-here.md`。
|
||||
3. 读 `docs/05-coding-rules.md`。
|
||||
4. 在 `docs/tasks/` 找到 frontmatter `status: TODO` 且依赖均 `DONE` 的任务文件;暂无任务时,先按 `docs/06-tasks.md` 路线图落成任务文件。
|
||||
5. 把任务改为 `DOING` 再动手。
|
||||
|
||||
## 维护规则
|
||||
|
||||
当实际状态发生变化时,同步更新本文件:新增入口文件、初始化框架、新增可运行命令、发现文档和代码现实不一致、阶段或 blocker 变化。本文件只保留当前快照,不保留完整历史。
|
||||
@@ -0,0 +1,354 @@
|
||||
# soft_quay_web 设计说明(愿景 · 架构 · 协议契约)
|
||||
|
||||
> **定位说明(2026-07-20)**:本文是立项前的**原始设计规格**,内容已拆分进本仓库 harness coding 编号文档
|
||||
> ([`01-vision.md`](01-vision.md) / [`02-requirements.md`](02-requirements.md) / [`03-tech-stack.md`](03-tech-stack.md) /
|
||||
> [`04-architecture.md`](04-architecture.md) / [`api.md`](api.md) / [`06-tasks.md`](06-tasks.md) 的裁定清单)。
|
||||
> 日常开发以编号文档为准,与本文冲突时以编号文档为准;本文保留作设计输入存档,不再逐项维护。
|
||||
|
||||
> 本文是 **soft_quay_web**(SoftBox 软件盒子的中央发布 / 登记系统;其 Web 管理端即本仓库 `soft_quay_web`)的设计规格。
|
||||
> 权威协议以客户端仓库 `soft_quay/docs/api.md`、`soft_quay/schemas/*.json` 和 `soft_quay/testdata/catalog/canonical-vectors.json` 为准;本文引用而非另建一份,两者变化时同步。
|
||||
> 状态:**尚未落地**,属客户端 `02-requirements.md` 第七节待确认风险。本文供另起本仓库时作为规格起点。
|
||||
> 更新日期:2026-07-19
|
||||
|
||||
---
|
||||
|
||||
## 一、愿景
|
||||
|
||||
让 SoftBox 的软件发布者(自己)能够**集中、安全、可审计地**把自家系列软件发布给所有盒子用户:一处登记元数据、一处签名、一处托管,客户端离线即可验真。
|
||||
|
||||
> 发布者在一个后台里完成"登记软件 → 接收构建包 → 校验 → 签名 → 生成双通道清单 → 上传 → 发现新版本",而**签名私钥永不离开发布系统,客户端只内置公钥**。
|
||||
|
||||
它把整个 SoftBox 产品家族的"信任根"收敛到一处:客户端拿到的每一份清单、每一个安装包、每一张许可证,都能用内置 Ed25519 公钥离线验签,不依赖任何在线接口的可用性。
|
||||
|
||||
### 设计原则
|
||||
|
||||
- **静态分发优先**:最终产物是放在对象存储 / CDN 上的**静态签名文件**,不是运行时动态 API;客户端离线验签。
|
||||
- **私钥隔离至上**:Ed25519 私钥是整个体系的信任根,只存在于受控签名服务,绝不进客户端、代码仓库或普通后台进程。
|
||||
- **协议以客户端为权威源**:清单 / 包协议 Schema 和签名向量 corpus 以 `soft_quay` 为准,发布端**字节级对齐**,不另造 canonicalizer 或第二套验签域。
|
||||
- **通道隔离**:modern / win7 双通道全程不交叉,Win7 用户永不拿到无法启动的现代版包。
|
||||
- **可审计**:每次发布、下架、撤销、许可证签发都有不可否认的记录。
|
||||
|
||||
---
|
||||
|
||||
## 二、定位与范围
|
||||
|
||||
### 2.1 一句话定位
|
||||
|
||||
soft_quay_web = **软件登记 + 构建包接收校验 + Ed25519 签名服务 + 双通道清单生成 + 对象存储发布 + 许可证/撤销签发 + Web 管理后台**;产物是静态签名文件,信任根是私钥。
|
||||
|
||||
### 2.2 做什么
|
||||
|
||||
见第五节功能清单。
|
||||
|
||||
### 2.3 明确不做
|
||||
|
||||
- **不含子软件业务源码**——那些在各自 `app-*` 仓库。
|
||||
- **不含盒子的下载 / 更新 / 安装 / 授权实现**——那些在 `soft_quay` 客户端。
|
||||
- **不是动态在线 API / 应用商店服务**——无面向客户端的运行时接口、无账号会话;客户端只拉静态签名文件。
|
||||
- **不生成未测试目标的占位包**。
|
||||
- **不定义第二套包级验签域**——外层清单 Ed25519 签名已覆盖 package 的 `url/size/sha256/signature` 文本(见 §6.4)。
|
||||
|
||||
---
|
||||
|
||||
## 三、与客户端的信任模型(核心)
|
||||
|
||||
这是本系统一切设计的出发点。客户端 `soft_quay` 对远端内容的信任**仅来自 Ed25519 签名**:
|
||||
|
||||
```text
|
||||
soft_quay_web(持私钥) soft_quay 客户端(内置公钥)
|
||||
───────────────────────── ─────────────────────────
|
||||
用受限规范 JSON + 私钥签名 ──► 拉取 → 去掉顶层 signature → 同规则规范化
|
||||
生成 manifest-modern/win7.json → Ed25519 验签 → Schema/channel/架构过滤
|
||||
上传对象存储 / CDN(HTTPS) → 缓存;验签失败则拒绝、退回上次可信缓存
|
||||
包 url/size/sha256 写入清单 → 下载 → 同句柄 size→SHA-256→安全解压
|
||||
```
|
||||
|
||||
由此推出对本系统的硬性要求:
|
||||
|
||||
1. **规范化必须字节级一致**:发布端与客户端对同一 JSON 输入必须产出**逐字节相同**的规范字节与签名。二者共用 `canonical-vectors.json` corpus 作为跨实现回归基线,**发布端不得用自身 canonicalizer 重新生成期望值**。
|
||||
2. **私钥保管是最高安全目标**:私钥泄露 = 可伪造任意清单/包记录/许可证 = 客户端全线沦陷。
|
||||
3. **客户端不做在线校验**:因此下架、撤销、版本兼容都必须能"离线表达"在签名文件里(status 字段、签名撤销名单 + 宽限期)。
|
||||
|
||||
---
|
||||
|
||||
## 四、系统架构
|
||||
|
||||
### 4.1 组件
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────┐
|
||||
子软件 app-* CI │ 构建标准 ZIP 包 + Release 元数据 │
|
||||
└───────────────┬─────────────────────────────┘
|
||||
│ 上传候选包
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ soft_quay_web │
|
||||
│ │
|
||||
│ ┌────────────┐ ┌────────────┐ ┌──────────────────────┐ │
|
||||
│ │ Ingestion │──►│ Registry │──►│ Manifest Generator │ │
|
||||
│ │ 包接收校验 │ │ 元数据/发布 │ │ 双通道清单组装 │ │
|
||||
│ └────────────┘ │ 记录 (DB) │ └──────────┬───────────┘ │
|
||||
│ │ └────────────┘ │ 待签名规范字节 │
|
||||
│ │ size/SHA-256 ▼ │
|
||||
│ │ ┌──────────────────────┐ │
|
||||
│ │ │ Signing Service │ │
|
||||
│ │ │ (Ed25519 私钥,隔离/ │ │
|
||||
│ │ │ KMS/HSM,受限接口) │ │
|
||||
│ │ └──────────┬───────────┘ │
|
||||
│ │ │ 签名 │
|
||||
│ ▼ ▼ │
|
||||
│ ┌────────────┐ ┌────────────┐ ┌──────────────────────┐ │
|
||||
│ │ License & │ │ Web 管理端 │ │ Publisher (发布器) │ │
|
||||
│ │ Revocation │ │ soft_quay_ │ │ 上传对象存储/CDN │ │
|
||||
│ │ 签发 │ │ web(UI) │ │ │ │
|
||||
│ └────────────┘ └────────────┘ └──────────┬───────────┘ │
|
||||
│ │ 审计日志 (append-only) │ │
|
||||
└────────┼──────────────────────────────────────┼──────────────┘
|
||||
▼ ▼
|
||||
审计/合规存档 对象存储 / CDN(静态托管)
|
||||
manifest-*.json / *.zip / icons
|
||||
│ HTTPS
|
||||
▼
|
||||
soft_quay 客户端
|
||||
```
|
||||
|
||||
### 4.2 组件职责
|
||||
|
||||
| 组件 | 职责 | 不含 |
|
||||
| --- | --- | --- |
|
||||
| Ingestion(接收校验) | 接收子软件 CI 的 ZIP 候选包;校验包协议结构(app.json/files.json/payload)、计算 size 与 SHA-256、比对声明版本/ID/架构 | 构建子软件、跑子软件业务 |
|
||||
| Registry(登记库) | 软件元数据、发布记录、包坐标(url/size/sha256)、status、channel、Catalog 版本 | 子软件源码、客户端状态 |
|
||||
| Manifest Generator | 从 Registry 按 channel 组装 `manifest-modern.json` / `manifest-win7.json`,产出**待签名规范字节** | 签名(委托签名服务) |
|
||||
| Signing Service | 唯一持有 Ed25519 私钥;对规范字节签名(清单/许可证/撤销名单);理想部署为 KMS/HSM 或独立最小权限服务 | 业务逻辑、任意数据签名(只签受控结构) |
|
||||
| License & Revocation | 绑定 machine_hash + products 签发许可证;维护签名撤销名单 + 宽限期 | 保存原始硬件序列号/MAC |
|
||||
| Publisher(发布器) | 把签名清单、ZIP 包、图标上传对象存储 / CDN;保证原子发布(先传包再切清单) | 生成未签名/未校验产物 |
|
||||
| Web 管理端(soft_quay_web) | 发布者操作界面:登记/发布/下架/撤销、发布记录浏览、许可证签发、审计查看 | 直接持有私钥(经受控签名服务接口) |
|
||||
| Audit | append-only 记录每次发布/下架/撤销/签发 | 可被覆盖的可变日志 |
|
||||
|
||||
### 4.3 关键边界
|
||||
|
||||
- **签名服务是唯一持钥点**:其余组件(含 Web 后台)只能提交"受限结构 + 请求签名",拿回签名串,拿不到私钥。
|
||||
- **Web 管理端不等于签名端**:soft_quay_web 负责人机交互与编排,签名经受控接口调用签名服务;私钥不进 Web 进程。
|
||||
- **协议 Schema / corpus 单一权威源**:引用 `soft_quay/schemas/` 与 `canonical-vectors.json`,CI 校验发布端产物能被同一份 corpus 与 Schema 通过。
|
||||
|
||||
---
|
||||
|
||||
## 五、功能清单
|
||||
|
||||
### 5.1 软件登记与元数据
|
||||
|
||||
维护每款软件的元数据(客户端列表页唯一来源),字段与约束见 §6.1:稳定 `id`、`name`、`description`、`version`、`channel`、`status`、`category`、`tags`、`icon`、`homepage`、`tutorial`、`min_os`、`architectures`、`entry_exe`、`requires_admin`、`packages`。
|
||||
|
||||
### 5.2 构建包接收与校验(Ingestion)
|
||||
|
||||
接收子软件 CI 产出的标准 ZIP 包并校验:
|
||||
|
||||
- 包结构:根 `app.json` + 可选 `files.json` + `payload/`;
|
||||
- `app.json` 与登记的 id/version/channel/min_os/architecture 一致;
|
||||
- 计算 `size` 与 `SHA-256`,写入发布记录;
|
||||
- 只接受实际支持并测试过的目标,不生成未测试平台占位包。
|
||||
|
||||
### 5.3 发布记录(Release)
|
||||
|
||||
每次发布登记包的下载坐标与校验信息:`url`(绝对 HTTPS)、`size`、`sha256`、`signature`(见 §6.4)。
|
||||
|
||||
### 5.4 Schema 校验(协议权威)
|
||||
|
||||
发布前用 `soft_quay/schemas/manifest.schema.json`、`app.schema.json` 校验:Schema 合规、无未知/重复/缺失字段、id 唯一、SemVer 合法、architectures 与 packages 键一一对应、channel 目标一致、URL 为绝对 HTTPS。
|
||||
|
||||
### 5.5 Ed25519 签名(核心)
|
||||
|
||||
- 签名域 = 移除顶层 `signature` 后的**受限规范 JSON**(§6.3);
|
||||
- `signature` 为唯一标准 padded Base64;
|
||||
- 与客户端共用 `canonical-vectors.json` 做跨实现回归。
|
||||
|
||||
### 5.6 双通道清单生成
|
||||
|
||||
产出 `manifest-modern.json` 与 `manifest-win7.json`,`channel` 字段隔离,互不交叉。
|
||||
|
||||
### 5.7 发布流水线
|
||||
|
||||
见 §七。
|
||||
|
||||
### 5.8 下架与撤销
|
||||
|
||||
- 软件下架用 `status`(active / deprecated / hidden),不改名;
|
||||
- 维护**签名的许可证撤销名单**,客户端缓存 + 网络失败宽限期。
|
||||
|
||||
### 5.9 许可证签发
|
||||
|
||||
绑定 `machine_hash` + `products` 签发 Ed25519 许可证(§6.5);不保存原始硬件标识。
|
||||
|
||||
### 5.10 版本 / 协议兼容管理
|
||||
|
||||
| 版本类型 | 示例 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| 产品版本 | 1.4.2 | 用户看到的软件版本 |
|
||||
| SDK 版本 | sdk/core v1.2.0 | 公共能力兼容性 |
|
||||
| 软件包协议 | schema_version 1 | 盒子与包的结构协议 |
|
||||
| Catalog 版本 | 2026.07.16.1 | 目录发布时间 |
|
||||
|
||||
原则:SDK 升级不强迫历史软件立即升级;盒子至少兼容当前与前一个包协议版本;清单顶层 `min_box_version` 声明所需最低盒子版本。
|
||||
|
||||
### 5.11 审计
|
||||
|
||||
append-only 记录发布/下架/撤销/许可证签发,含操作者、时间、内容摘要、签名指纹。
|
||||
|
||||
---
|
||||
|
||||
## 六、数据与协议契约
|
||||
|
||||
> 以下字段与约束**必须**与 `soft_quay/schemas/*.json` 和 `api.md` 一致;此处为发布端视角的摘要,冲突时以客户端仓库为准。
|
||||
|
||||
### 6.1 Catalog 清单 v1(manifest-{modern,win7}.json)
|
||||
|
||||
顶层(`additionalProperties: false`,全部必填):
|
||||
|
||||
| 字段 | 约束 |
|
||||
| --- | --- |
|
||||
| `schema_version` | 常量 `1` |
|
||||
| `channel` | `modern` 或 `win7` |
|
||||
| `generated_at` | RFC3339 date-time |
|
||||
| `min_box_version` | SemVer |
|
||||
| `apps` | app 数组 |
|
||||
| `signature` | Ed25519,`^[A-Za-z0-9+/]{86}==$` |
|
||||
|
||||
每个 app(`additionalProperties: false`):
|
||||
|
||||
| 字段 | 约束 |
|
||||
| --- | --- |
|
||||
| `id` | `^[a-z0-9-]+$`,永久稳定不可变 |
|
||||
| `name` / `description` | 非空字符串 |
|
||||
| `version` | SemVer 2.0.0 |
|
||||
| `channel` | 常量 `stable` |
|
||||
| `status` | `active` / `deprecated` / `hidden` |
|
||||
| `category` | 非空(单一分类) |
|
||||
| `tags` | 非空字符串数组(≥1) |
|
||||
| `icon` | `^sha256:[0-9A-Fa-f]{64}$`(内容引用,非 URL;可选) |
|
||||
| `homepage` / `tutorial` | 绝对 HTTPS(可选) |
|
||||
| `min_os` | `windows-7-sp1` / `windows-10` / `windows-11` |
|
||||
| `architectures` | `["386"|"amd64"]`,唯一,≥1 |
|
||||
| `entry_exe` | 安全相对路径(拒绝首尾空格/尾随点/DOS 设备名/反斜杠/`:<>"|?*`/`..`;见 schema 的 pattern 与运行时校验) |
|
||||
| `requires_admin` | 布尔 |
|
||||
| `packages` | `{386?, amd64?}`,≥1;每个含 `url/size/sha256/signature` |
|
||||
|
||||
package 对象:`url`(绝对 HTTPS)、`size`(整数 ≥1)、`sha256`(64 hex)、`signature`(Ed25519)。
|
||||
|
||||
### 6.2 通用约定
|
||||
|
||||
- 传输 HTTPS,JSON UTF-8;时间 RFC3339(UTC)。
|
||||
- URL 只接受无用户信息、无 fragment 的绝对 HTTPS。
|
||||
- 数字仅整数 token(签名域拒绝浮点/指数/`-0`,大整数保持 token 不失精度)。
|
||||
|
||||
### 6.3 签名域(必须与客户端字节对齐)
|
||||
|
||||
1. 输入为单个 UTF-8 JSON object;拒绝重复键、尾随数据、浮点/指数数字、非法 Unicode surrogate。
|
||||
2. 读取顶层 `signature`(标准 padded Base64,严格解码为 64 字节;拒绝 CR/LF、其他空白、缺失/额外 padding),从对象移除该字段。
|
||||
3. 对剩余值递归规范化:对象键按 Unicode 码点升序、数组保序、字符串按 JSON 转义、数字仅整数 token、无多余空白。
|
||||
4. Ed25519 对上述规范字节签名。
|
||||
|
||||
**跨实现回归**:`soft_quay/testdata/catalog/canonical-vectors.json` 含测试公钥(`public_key_base64`)与向量(`document` / `signed_payload_base64` / `signature` / `want_error`),覆盖 Unicode 键序、转义、`<>&`/U+2028/U+2029、合法/非法 surrogate、`-0`/大整数、嵌套 signature、Base64 变体。发布端 CI **必读该 corpus 断言**,不得自举期望值。
|
||||
|
||||
### 6.4 package.signature 的语义(待定,见未定项)
|
||||
|
||||
当前客户端**不独立验证** package 的 `signature`——其文本已被外层清单 Ed25519 签名覆盖,客户端不臆造第二套包级验签域。但 Schema 要求该字段存在且形状合法。发布端 v1 须产出形状合法的 Ed25519 串;**其独立签名域(待签名字节 + 公钥)尚未定义**,是未定项。
|
||||
|
||||
### 6.5 许可证(server 签发 → 客户端离线验证)
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": 1,
|
||||
"license_id": "lic-...",
|
||||
"machine_hash": "...",
|
||||
"products": ["product-json-parser"],
|
||||
"issued_at": "2026-07-16T00:00:00Z",
|
||||
"perpetual": true,
|
||||
"update_policy": "updates-until-2027-12-31",
|
||||
"rebind_policy": "self-service-1-per-90d",
|
||||
"signature": "..."
|
||||
}
|
||||
```
|
||||
|
||||
- `machine_hash` 由客户端平台层多硬件标识清洗生成;许可证**不保存**原始序列号/MAC。
|
||||
- Ed25519 签名;客户端内置公钥离线验签,保存于 `licenses/`,更新不覆盖。
|
||||
- 撤销名单同样签名 + 客户端缓存 + 网络失败宽限期。
|
||||
- 子软件用 SDK licensing 逻辑**独立再验证**(签名 + machine_hash + product_id)。
|
||||
|
||||
### 6.6 标准软件包协议 v1(ZIP,发布端负责接收校验)
|
||||
|
||||
```text
|
||||
<id>_<version>_windows_<arch>.zip
|
||||
├─ app.json # schema_version/id/name/vendor/version/channel/min_os/
|
||||
│ # architecture/entrypoint/working_directory/product_id/
|
||||
│ # supports_trial/requires_admin/data_policy/update_policy
|
||||
├─ files.json # 解压后文件清单(v1.1 强制,v1 推荐)
|
||||
└─ payload/ # 实际安装文件
|
||||
```
|
||||
|
||||
发布端校验 app.json 与登记一致、payload 结构合法,并计算整包 SHA-256 写入清单;`data_policy` 固定 `local-app-data`、`update_policy` 固定 `managed-by-softbox`。
|
||||
|
||||
---
|
||||
|
||||
## 七、发布流水线
|
||||
|
||||
```text
|
||||
子软件 CI 产出标准 ZIP 包 + Release 元数据
|
||||
→ Ingestion 校验包结构 / app.json 一致性 / 计算 size + SHA-256
|
||||
→ Registry 登记发布记录(url 坐标、status、channel、Catalog 版本)
|
||||
→ Schema 校验(manifest/app schema)
|
||||
→ Manifest Generator 组装 modern/win7 待签名规范字节
|
||||
→ Signing Service 用 Ed25519 私钥签名
|
||||
→ CI 用 canonical-vectors corpus + Schema 回归验证产物
|
||||
→ Publisher 原子上传:先传 ZIP 包/图标到对象存储,再切换签名清单
|
||||
→ soft_quay 客户端发现新版本
|
||||
```
|
||||
|
||||
一个产品可产出多目标(如 `json-parser_1.4.2_windows_amd64.zip`、`..._win7_amd64.zip`、`..._win7_386.zip`);只生成实际支持并测试过的目标。原子性要求:**包必须先于清单可用**,避免客户端拿到指向尚未上传包的清单。
|
||||
|
||||
---
|
||||
|
||||
## 八、签名与密钥管理
|
||||
|
||||
- **私钥隔离**:理想部署为 KMS/HSM 或独立最小权限签名服务;Web 后台与业务进程只经受控接口请求签名,不接触私钥材料。
|
||||
- **只签受控结构**:签名服务只对"清单 / 许可证 / 撤销名单"这几类已知结构签名,不对任意字节签名(防被当通用签名 oracle)。
|
||||
- **公钥分发**:客户端内置公钥;测试用公钥见 corpus。正式公钥的内置与更新方式随密钥轮换设计裁定。
|
||||
- **审计**:每次签名请求记录操作者、目标结构摘要、时间、密钥指纹。
|
||||
|
||||
---
|
||||
|
||||
## 九、技术选型建议(soft_quay_web)
|
||||
|
||||
> 待定,以下为建议,立项时在本仓库 tech-stack 文档固定。
|
||||
|
||||
- 形态:Web 管理后台 + 后端 API + 独立签名服务 + 对象存储集成。
|
||||
- 后端语言:建议 Go(与客户端同栈,可**直接复用同一套 canonical/verify 实现与 Schema**,天然字节对齐;这是选 Go 的最强理由)。若选其他语言,必须用 corpus 做严格字节回归。
|
||||
- 存储:发布记录用关系型库;产物用对象存储 / CDN。
|
||||
- 签名:私钥入 KMS/HSM 或隔离服务;绝不入库、不进 Web 进程环境变量明文。
|
||||
- 协议 Schema / corpus:以 `soft_quay` 为权威源引用(git submodule 或版本化拷贝 + CI 校验一致),不另建。
|
||||
|
||||
---
|
||||
|
||||
## 十、未定项 / 待确认(立项前必须裁定)
|
||||
|
||||
| 项 | 说明 |
|
||||
| --- | --- |
|
||||
| 签名私钥管理 | 谁持有、如何保管与**轮换**、签发流程;私钥绝不进客户端/仓库/Web 进程 |
|
||||
| 密钥 ID / 轮换字段 | 清单/许可证尚无 key ID / rotation 字段;`api.md` 注明"单公钥协议升级前不得另造签名域",多公钥/轮换需先做协议升级 |
|
||||
| package.signature 独立语义 | v1 客户端不独立验;若要独立包级验签,需定义待签名字节 + 公钥域(见 §6.4) |
|
||||
| 正式域名与对象存储 | 清单/包的正式 HTTPS 域名、对象存储/CDN 选型、**带宽成本与防盗链**策略 |
|
||||
| 图标分发字段 | 图标从内容哈希(`sha256:`)到实际下载位置/分辨率变体的**发布端映射格式**(`api.md` 待实现项) |
|
||||
| 撤销名单结构 | revocation list 字段结构与**宽限期时长** |
|
||||
| 跨仓库 CI 对齐 | 发布端消费同一 corpus / Schema 的 CI 证据需与 `soft_quay`(T-614)跨仓库协调 |
|
||||
| 授权产品映射 | `product_id` 与许可证 `products` 的登记与映射管理 |
|
||||
| 发布端形态与权限模型 | Web 后台的操作者权限、审批流(谁能发布/下架/撤销/签发)|
|
||||
|
||||
---
|
||||
|
||||
## 十一、首期落地建议
|
||||
|
||||
- 首期用**本地 / 静态文件模拟**:本地生成签名清单 + 本地 HTTP 静态服务,即可让客户端跑通"清单→下载→安装"闭环,不必先建正式对象存储。
|
||||
- 私钥先用**离线生成的测试密钥对**(与客户端内置测试公钥、corpus `public_key_base64` 配对);正式私钥管理另裁。
|
||||
- **先落协议对齐再落 UI**:第一里程碑做到"能产出被 `soft_quay` 客户端验签通过、被 corpus + Schema 回归通过的 manifest",再迭代 Web 管理界面。
|
||||
- 协议 Schema 与 corpus 以 `soft_quay` 为权威源引用,避免漂移。
|
||||
|
||||
> 本文为设计规格;字段与协议细节以 `soft_quay/docs/api.md` 与 `soft_quay/schemas/` 为准,两者变化时同步本文。
|
||||
@@ -0,0 +1,85 @@
|
||||
# 任务文件(一任务一文件 · 默认任务管理方式)
|
||||
|
||||
> 本目录是项目任务的**默认存放位置**:每个任务一个文件 `docs/tasks/W-<编号>.md`。当前项目使用单 Agent 串行执行。
|
||||
> 阶段划分、里程碑和待办池见路线图 [`../06-tasks.md`](../06-tasks.md);路线图只读,不跟踪单任务状态。
|
||||
> 本仓库任务编号前缀为 `W-`;引用客户端仓库任务写全称(如 `soft_quay/T-614`),避免跨仓库混淆。
|
||||
|
||||
## 为什么默认一任务一文件
|
||||
|
||||
- **单 agent**:领任务只读一个文件就拿到完整上下文(背景、方案、验收、执行记录);执行记录和任务绑定,审查时 `git log -p` 一个文件即可回放全程。
|
||||
- **低上下文成本**:只读取当前任务规格和执行记录,减少跨文件同步与重复检索。
|
||||
- **保留扩展性**:如果未来由用户明确启用多 Agent,现有任务文件仍可作为写路径和依赖边界;启用前必须先更新并提交协作规则。
|
||||
|
||||
## 文件命名与 ID
|
||||
|
||||
- 文件名:`docs/tasks/W-<编号>.md`(如 `docs/tasks/W-101.md`);同族细分用后缀 `W-101a.md`。
|
||||
- 落实路线图建议任务时,**沿用路线图 [`../06-tasks.md`](../06-tasks.md) 里的建议编号**(如 W-101)。
|
||||
- 路线图之外的新任务:取「路线图建议编号 + `docs/tasks/` 现有文件」里最大的 `W-###`,`+1`。
|
||||
- **建文件即防撞**:若目标编号文件已存在,改用下一个号,不要覆盖别人的文件。
|
||||
- 模板 `_template.md` 以下划线开头,不是真实任务、不参与编号扫描。
|
||||
|
||||
## 每个任务文件的结构(frontmatter + 正文)
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: W-101
|
||||
title: 一句话任务名
|
||||
phase: 1 # 所属阶段,沿用路线图的 Phase 编号
|
||||
deps: [W-002] # 依赖的任务 ID
|
||||
status: TODO # TODO | DOING | DONE | BLOCKED
|
||||
created: 【日期】
|
||||
issue: null # 远端 Issue 编号;未启用时保持 null
|
||||
context_ref: null # 领取时默认分支提交 SHA
|
||||
claim_branch: null
|
||||
work_branch: null
|
||||
write_paths: # 允许修改的仓库相对路径
|
||||
- docs/tasks/W-101.md
|
||||
- 【path/to/module】
|
||||
---
|
||||
|
||||
## 问题 / 背景
|
||||
## 方案
|
||||
## 验收要点
|
||||
## 边界(不改什么)
|
||||
## 协作约束
|
||||
## 执行记录
|
||||
```
|
||||
|
||||
## 领取 / 完成流程
|
||||
|
||||
- 状态:`TODO` · `DOING` · `DONE` · `BLOCKED`。项目同一时间只保留一个活跃任务。
|
||||
- 单 Agent 一次只领一个 `status: TODO` 且依赖全 `DONE` 的任务,取编号最靠前的;若本目录暂无可领任务,先按路线图把下一个建议任务落成任务文件,再领取。
|
||||
- 裁定类任务(如 W-003、W-004)的产出是写进文档的决策结论,不是代码;验收同样要可检查(结论落在哪个文档哪一节)。
|
||||
- `write_paths` 必须在动手前写清,用于约束本任务允许修改和提交的路径。
|
||||
- 做完自测、按「passing 需证据」把验证命令与结果写清、改 `status: DONE`。
|
||||
- **执行记录写进本任务文件的 `## 执行记录` 一节**——不逐任务追加共享的 `progress.md`(可选历史归档),也不逐任务覆盖 `current-state.md`(项目级快照,只在启动/验证路径、目录结构或 blocker 变化时更新)。
|
||||
- **只改当前任务文件**;不要顺带修改其他任务的状态或执行记录。
|
||||
|
||||
### 当前单 Agent 模式
|
||||
|
||||
1. 当前 Agent 负责从任务落文档到最终提交的完整生命周期,不启动或委派子 Agent。
|
||||
2. 测试设计、安全检查、代码审查和提交前复核由当前 Agent 分阶段执行,结果统一写入任务执行记录。
|
||||
3. 前一个任务达到 `DONE`、完整验证通过并提交后,才正式落成或领取下一个依赖任务。
|
||||
4. 若用户以后明确启用多 Agent,必须先修改并提交 `AGENTS.md` 和本文;在该提交之前不得启动子 Agent。
|
||||
|
||||
## 用户指令暗语(可选约定,与 soft_quay 一致)
|
||||
|
||||
> 默认值:不注明视角就是全栈工程师视角;每步产物默认提交 git(只提交本次相关文件)。
|
||||
|
||||
| 用户输入 | agent 执行 |
|
||||
| --- | --- |
|
||||
| `bug: <现象>` / `需求: <描述>` | 先查代码再给分析和方案,**只讨论不改代码** |
|
||||
| `grill: <方案>` | 反方评审,逐点挑战该方案 |
|
||||
| `落task` | 把已讨论定案落成 `docs/tasks/W-<编号>.md`(按上述规则查号防撞),**只写文档不写代码,写完自动提交 git** |
|
||||
| `审 W-<编号>` | **以 git 历史为准**先核代码事实,再审核该改动是否合理、给缺口 |
|
||||
| `补` | 把讨论新增的结论补进当前任务文件并提交 git |
|
||||
| `做 W-<编号>` | 实现该任务 + 跑任务内验证命令;**验证全绿才提交**;验证失败 → 报告、不提交、状态留 DOING 或标 BLOCKED 记原因 |
|
||||
| `记backlog: <一行>` | 追加进待办池(`docs/06-tasks.md` Backlog)并提交,只记一行、不建任务文件 |
|
||||
|
||||
`落task`/`补` 可带参数;新会话或无对话上下文时 agent **必须先问清指代对象,不得猜**。
|
||||
|
||||
## 与路线图和共享文件的关系
|
||||
|
||||
- [`../06-tasks.md`](../06-tasks.md):只读路线图(Phase 划分、里程碑、裁定清单、Backlog、建议拆分清单);任务状态以任务文件 frontmatter 为准。
|
||||
- `../../progress.md`:可选工件,用作历史归档或项目级大事记。
|
||||
- [`../current-state.md`](../current-state.md):项目级快照(启动/验证路径、目录要点、blocker);任务状态不在此维护。
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
id: W-XXX
|
||||
title: 一句话任务名
|
||||
phase: 1
|
||||
deps: []
|
||||
status: TODO
|
||||
created: 【日期】
|
||||
issue: null
|
||||
context_ref: null
|
||||
claim_branch: null
|
||||
work_branch: null
|
||||
write_paths:
|
||||
- docs/tasks/W-XXX.md
|
||||
- 【允许修改的仓库相对路径】
|
||||
---
|
||||
|
||||
## 问题 / 背景
|
||||
|
||||
(现象、根因、为什么要做)
|
||||
|
||||
## 方案
|
||||
|
||||
(怎么改,落到"改哪个文件、改成什么")
|
||||
|
||||
## 验收要点
|
||||
|
||||
(可验证的完成标准;按「passing 需证据」写清跑哪个验证命令,不写"应该能用"。裁定类任务写清结论落进哪个文档哪一节。)
|
||||
|
||||
## 边界(不改什么)
|
||||
|
||||
(明确不碰的模块/流程;涉及协议时重申:协议字段变更必须先走 soft_quay 客户端仓库)
|
||||
|
||||
## 协作约束
|
||||
|
||||
(领取时的 `context_ref`;任何新增写路径先检查与其他活跃任务是否重叠。)
|
||||
|
||||
## 执行记录
|
||||
|
||||
(做完在此记录:改了哪些文件、跑的验证命令与结果、阻塞、关键决策。
|
||||
执行记录只写进本任务文件,不逐任务追加共享的 `progress.md`。)
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
# 标准启动与验证入口(Windows PowerShell 版),与 init.sh 等价,二选一:
|
||||
# - Windows 原生 PowerShell:用本文件 ./init.ps1
|
||||
# - WSL / Git Bash / macOS / Linux:用 ./init.sh
|
||||
# 一条命令完成:依赖安装 -> 基础验证 -> 打印启动命令。
|
||||
# 三个命令是仓库统一入口;W-001 工程骨架任务负责替换为真实命令,并同步
|
||||
# docs/03-tech-stack.md、docs/00-ai-start-here.md 与 docs/current-state.md。
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Utf8 = [System.Text.UTF8Encoding]::new($false)
|
||||
[Console]::OutputEncoding = $Utf8
|
||||
$OutputEncoding = $Utf8
|
||||
Set-Location -Path $PSScriptRoot
|
||||
|
||||
$InstallCmd = 'Write-Host "[init] INSTALL_CMD 未替换:W-001 完成工程骨架后填入真实依赖同步命令"; exit 1'
|
||||
$VerifyCmd = 'Write-Host "[init] VERIFY_CMD 未替换:W-001 完成后填入 corpus + Schema 回归等验证命令"; exit 1'
|
||||
$StartCmd = 'Write-Host "[init] START_CMD 未替换:W-001 完成后填入构建/启动命令"; exit 1'
|
||||
|
||||
Write-Host "==> 当前目录: $($PWD.Path)"
|
||||
|
||||
Write-Host "==> 同步依赖"
|
||||
Invoke-Expression $InstallCmd
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
Write-Host "==> 运行基础验证"
|
||||
Invoke-Expression $VerifyCmd
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
Write-Host "==> 启动命令"
|
||||
Write-Host " $StartCmd"
|
||||
|
||||
if ($env:RUN_START_COMMAND -eq "1") {
|
||||
Write-Host "==> 启动应用"
|
||||
Invoke-Expression $StartCmd
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
} else {
|
||||
Write-Host "如果希望 init.ps1 直接启动应用,请设置环境变量 RUN_START_COMMAND=1。"
|
||||
Write-Host "如果基础验证失败,先修复基线状态,不要在坏的起点上继续叠新功能。"
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 标准启动与验证入口(Unix shell 版),与 init.ps1 等价,二选一:
|
||||
# - WSL / Git Bash / macOS / Linux:用 ./init.sh
|
||||
# - Windows 原生 PowerShell:用 ./init.ps1
|
||||
# 一条命令完成:依赖安装 -> 基础验证 -> 打印启动命令。
|
||||
# 三个命令是仓库统一入口;W-001 工程骨架任务负责替换为真实命令,并同步
|
||||
# docs/03-tech-stack.md、docs/00-ai-start-here.md 与 docs/current-state.md。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
INSTALL_CMD="echo '[init] INSTALL_CMD 未替换:W-001 完成工程骨架后填入真实依赖同步命令' && exit 1"
|
||||
VERIFY_CMD="echo '[init] VERIFY_CMD 未替换:W-001 完成后填入 corpus + Schema 回归等验证命令' && exit 1"
|
||||
START_CMD="echo '[init] START_CMD 未替换:W-001 完成后填入构建/启动命令' && exit 1"
|
||||
|
||||
echo "==> 当前目录: $PWD"
|
||||
|
||||
echo "==> 同步依赖"
|
||||
eval "$INSTALL_CMD"
|
||||
|
||||
echo "==> 运行基础验证"
|
||||
eval "$VERIFY_CMD"
|
||||
|
||||
echo "==> 启动命令"
|
||||
printf ' %s\n' "$START_CMD"
|
||||
|
||||
if [ "${RUN_START_COMMAND:-0}" = "1" ]; then
|
||||
echo "==> 启动应用"
|
||||
eval "$START_CMD"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
echo "如果希望 init.sh 直接启动应用,请设置 RUN_START_COMMAND=1。"
|
||||
echo "如果基础验证失败,先修复基线状态,不要在坏的起点上继续叠新功能。"
|
||||
@@ -0,0 +1,6 @@
|
||||
# 项目大事记(可选历史归档)
|
||||
|
||||
> 执行记录默认写进各任务文件(`docs/tasks/W-<编号>.md`)的 `## 执行记录`;本文只记项目级大事,不逐任务追加。
|
||||
|
||||
- 2026-07-19:完成设计规格 `docs/softbox-catalog-design.md` 初稿。
|
||||
- 2026-07-20:按 harness coding 模板建立文档集(00~06、api、tasks、agent-context 等),设计规格拆分归位;仓库初始化 git。
|
||||
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate the agent context manifest with the Python standard library."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Any
|
||||
|
||||
|
||||
EXPECTED_SCHEMA = "docs/agent-context.schema.json"
|
||||
REQUIRED_TOP_LEVEL = {
|
||||
"schema",
|
||||
"schema_version",
|
||||
"authority",
|
||||
"bootstrap",
|
||||
"routes",
|
||||
"tasks",
|
||||
"refresh",
|
||||
"degraded_mode",
|
||||
}
|
||||
REQUIRED_BOOTSTRAP = {
|
||||
"AGENTS.md",
|
||||
"docs/00-ai-start-here.md",
|
||||
"docs/05-coding-rules.md",
|
||||
"docs/current-state.md",
|
||||
}
|
||||
TASK_PATH_KEYS = {"roadmap", "directory", "template"}
|
||||
SENSITIVE_KEY = re.compile(r"(?:token|password|secret|credential)", re.IGNORECASE)
|
||||
URI_SCHEME = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*:")
|
||||
|
||||
|
||||
def load_json(path: Path, root: Path, errors: list[str]) -> Any:
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except FileNotFoundError:
|
||||
errors.append(f"文件不存在:{display_path(path, root)}")
|
||||
except json.JSONDecodeError as exc:
|
||||
errors.append(
|
||||
f"JSON 语法错误:{display_path(path, root)}:{exc.lineno}:{exc.colno}"
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def display_path(path: Path, root: Path) -> str:
|
||||
try:
|
||||
return path.relative_to(root).as_posix()
|
||||
except ValueError:
|
||||
return path.as_posix()
|
||||
|
||||
|
||||
def require_mapping(value: Any, name: str, errors: list[str]) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
errors.append(f"{name} 必须是对象。")
|
||||
return {}
|
||||
return value
|
||||
|
||||
|
||||
def require_string_list(value: Any, name: str, errors: list[str]) -> list[str]:
|
||||
if not isinstance(value, list) or not value or not all(
|
||||
isinstance(item, str) and item for item in value
|
||||
):
|
||||
errors.append(f"{name} 必须是非空字符串数组。")
|
||||
return []
|
||||
if len(value) != len(set(value)):
|
||||
errors.append(f"{name} 不得包含重复路径。")
|
||||
return value
|
||||
|
||||
|
||||
def validate_repo_path(root: Path, value: str, name: str, errors: list[str]) -> None:
|
||||
path = PurePosixPath(value)
|
||||
if (
|
||||
path.is_absolute()
|
||||
or ".." in path.parts
|
||||
or "\\" in value
|
||||
or URI_SCHEME.match(value)
|
||||
):
|
||||
errors.append(f"{name} 必须是安全的仓库相对路径:{value}")
|
||||
return
|
||||
|
||||
target = root.joinpath(*path.parts)
|
||||
if not target.exists():
|
||||
errors.append(f"{name} 引用路径不存在:{value}")
|
||||
|
||||
|
||||
def find_sensitive_keys(value: Any, location: str, errors: list[str]) -> None:
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
child_location = f"{location}.{key}"
|
||||
if SENSITIVE_KEY.search(key):
|
||||
errors.append(f"清单不得保存敏感配置字段:{child_location}")
|
||||
find_sensitive_keys(child, child_location, errors)
|
||||
elif isinstance(value, list):
|
||||
for index, child in enumerate(value):
|
||||
find_sensitive_keys(child, f"{location}[{index}]", errors)
|
||||
|
||||
|
||||
def validate_manifest(root: Path) -> list[str]:
|
||||
root = root.resolve()
|
||||
manifest_path = root / "docs" / "agent-context.json"
|
||||
errors: list[str] = []
|
||||
manifest = load_json(manifest_path, root, errors)
|
||||
schema = load_json(root / EXPECTED_SCHEMA, root, errors)
|
||||
if manifest is None or schema is None:
|
||||
return errors
|
||||
if not isinstance(schema, dict) or schema.get("type") != "object":
|
||||
errors.append("agent-context.schema.json 不是有效的对象 Schema。")
|
||||
|
||||
root_object = require_mapping(manifest, "manifest", errors)
|
||||
actual_keys = set(root_object)
|
||||
missing = sorted(REQUIRED_TOP_LEVEL - actual_keys)
|
||||
unexpected = sorted(actual_keys - REQUIRED_TOP_LEVEL)
|
||||
if missing:
|
||||
errors.append("缺少顶层字段:" + ", ".join(missing))
|
||||
if unexpected:
|
||||
errors.append("存在未知顶层字段:" + ", ".join(unexpected))
|
||||
if root_object.get("schema") != EXPECTED_SCHEMA:
|
||||
errors.append(f"schema 必须是 {EXPECTED_SCHEMA}。")
|
||||
if root_object.get("schema_version") != 1:
|
||||
errors.append("schema_version 必须为 1。")
|
||||
|
||||
authority = require_mapping(root_object.get("authority"), "authority", errors)
|
||||
for key in ("bootstrap", "framework_templates", "project_facts", "coordination"):
|
||||
if not isinstance(authority.get(key), str) or not authority[key]:
|
||||
errors.append(f"authority.{key} 必须是非空字符串。")
|
||||
|
||||
bootstrap = require_mapping(root_object.get("bootstrap"), "bootstrap", errors)
|
||||
always_read = require_string_list(
|
||||
bootstrap.get("always_read"), "bootstrap.always_read", errors
|
||||
)
|
||||
missing_bootstrap = sorted(REQUIRED_BOOTSTRAP - set(always_read))
|
||||
if missing_bootstrap:
|
||||
errors.append("bootstrap.always_read 缺少:" + ", ".join(missing_bootstrap))
|
||||
|
||||
routes = require_mapping(root_object.get("routes"), "routes", errors)
|
||||
if not routes:
|
||||
errors.append("routes 至少需要一个任务类型。")
|
||||
|
||||
path_values: list[tuple[str, str]] = [(EXPECTED_SCHEMA, "schema")]
|
||||
path_values.extend((path, "bootstrap.always_read") for path in always_read)
|
||||
for route, value in routes.items():
|
||||
paths = require_string_list(value, f"routes.{route}", errors)
|
||||
path_values.extend((path, f"routes.{route}") for path in paths)
|
||||
|
||||
tasks = require_mapping(root_object.get("tasks"), "tasks", errors)
|
||||
if set(tasks) != TASK_PATH_KEYS:
|
||||
errors.append("tasks 必须且只能包含 roadmap、directory、template。")
|
||||
for key in sorted(TASK_PATH_KEYS):
|
||||
value = tasks.get(key)
|
||||
if isinstance(value, str) and value:
|
||||
path_values.append((value, f"tasks.{key}"))
|
||||
else:
|
||||
errors.append(f"tasks.{key} 必须是非空字符串。")
|
||||
|
||||
refresh = require_mapping(root_object.get("refresh"), "refresh", errors)
|
||||
expected_refresh = {
|
||||
"context_ref": "default_branch_head_sha",
|
||||
"cache_key": "file_sha",
|
||||
"unchanged_file": "reuse_within_current_session",
|
||||
"changed_ref": "reread_manifest_and_routed_documents",
|
||||
}
|
||||
if refresh != expected_refresh:
|
||||
errors.append("refresh 必须使用约定的提交 SHA 与文件 SHA 刷新策略。")
|
||||
|
||||
degraded = require_mapping(root_object.get("degraded_mode"), "degraded_mode", errors)
|
||||
expected_degraded = {
|
||||
"continue_claimed_task": True,
|
||||
"claim_new_task": False,
|
||||
"write_remote_state": False,
|
||||
}
|
||||
if degraded != expected_degraded:
|
||||
errors.append("degraded_mode 必须禁止领取新任务和写入远端状态。")
|
||||
|
||||
for value, name in path_values:
|
||||
validate_repo_path(root, value, name, errors)
|
||||
find_sensitive_keys(root_object, "manifest", errors)
|
||||
return errors
|
||||
|
||||
|
||||
def manifest_summary(root: Path) -> tuple[int, int]:
|
||||
manifest = json.loads(
|
||||
(root / "docs" / "agent-context.json").read_text(encoding="utf-8")
|
||||
)
|
||||
paths = {manifest["schema"]}
|
||||
paths.update(manifest["bootstrap"]["always_read"])
|
||||
for values in manifest["routes"].values():
|
||||
paths.update(values)
|
||||
paths.update(manifest["tasks"].values())
|
||||
return len(manifest["routes"]), len(paths)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="校验 Agent 上下文清单。")
|
||||
parser.add_argument(
|
||||
"--root",
|
||||
type=Path,
|
||||
default=Path(__file__).resolve().parents[1],
|
||||
help="仓库根目录;默认取脚本上一级。",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
root = args.root.resolve()
|
||||
if not root.is_dir():
|
||||
print("ERROR: 仓库根目录不存在。", file=sys.stderr)
|
||||
return 2
|
||||
errors = validate_manifest(root)
|
||||
if errors:
|
||||
for error in errors:
|
||||
print(f"ERROR: {error}", file=sys.stderr)
|
||||
return 1
|
||||
route_count, path_count = manifest_summary(root)
|
||||
print(
|
||||
"agent-context 校验通过:"
|
||||
f"{route_count} 个任务路由,{path_count} 个有效仓库路径。"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user