Merge pull request #826 from iflytek/feature/skill-suites-spec

docs(spec): define skill suite lifecycle and compatibility
This commit is contained in:
XiaoSeS 2026-09-07 17:06:53 +08:00 committed by GitHub
commit 19cc56be9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 940 additions and 0 deletions

View file

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-07

View file

@ -0,0 +1,3 @@
# add-skill-suites
Define versioned Skill Suites as typed collections of published Skill versions for Issue #715.

View file

@ -0,0 +1,346 @@
## Context
SkillHub 的现有发布单元是一个根目录包含 `SKILL.md` 的 Skill 包。SkillVersion 独立完成包校验、安全扫描、可见性审核、发布和下架。Agent Skills 开放规范同样只认识一个目录中的一个 `SKILL.md`,没有 Suite 协议。
外部方案提供了三类参考Vercel 把多 Skill 仓库作为安装来源Claude Plugin/Superpowers 把多个能力封装为特定 Agent 的版本化插件;腾讯 SkillSet 和 VS Code Extension Pack 把集合建成独立资源。SkillHub 选择轻量集合模型Suite 管理精确的已发布 SkillVersion 引用,成员仍是独立 Skill。
### Ubiquitous language
| Term | Definition |
| --- | --- |
| Skill | 可独立发布、扫描、审核、搜索和安装的 Agent Skill。 |
| Suite | Namespace 所有的、可版本化的 Skill 集合;它不是 Skill也不是多 Skill ZIP。 |
| SuiteVersion | Suite 在某一时刻不可变的成员快照和元数据。 |
| Member | SuiteVersion 引用的一个精确、已发布 SkillVersion。 |
| Entry Skill | 可选的普通 Member用于表达工作流入口不通过名称推断。 |
| Install plan | 服务端解析出的 SuiteVersion、成员精确版本、fingerprint 和下载信息。 |
| Degraded Suite | 已发布 SuiteVersion 的至少一个成员当前不可下载;历史快照仍可查看,但不能完整安装。 |
## Goals / Non-Goals
**Goals:**
- 一次选择和安装一组经过策划的 Skill并保证失败时不留下半套结果。
- 保留每个 Skill 的独立身份、版本、所有权、扫描、审核、搜索和安装能力。
- 让 SuiteVersion 成为可重现的精确成员快照,而不是随成员 `latest` 漂移的查询。
- 兼容现有 Skill 协议和不同 Agent 的标准 Skill 目录。
- 复用 Namespace 权限、可见性和审核原则,并明确 Suite 与成员生命周期的边界。
**Non-Goals:**
- 不把 `SUITE.yaml` 放进多 Skill ZIP 并由服务端自动创建成员 Skill。
- 不让 Suite 发布、审核、下架、隐藏或删除操作级联改变成员 Skill。
- 不实现嵌套 Suite、版本范围、动态 `latest`、条件成员、成员别名或跨 Registry 引用。
- 不把 MCP、Agent、Hook、LSP 或运行时配置纳入 Suite。
- 不在 v1 提供 Suite promotion、评分、评论或成员自动升级策略。
## Decisions
### 1. Suite 是独立聚合,不是新的 Skill 包格式
新增三个核心关系:
```text
SkillSuite 1 ── * SkillSuiteVersion 1 ── * SkillSuiteVersionMember * ── 0..1 SkillVersion
```
建议持久化字段:
```text
skill_suite
id, namespace_id, slug, display_name, summary,
status, latest_version_id, hidden, hidden_at, hidden_by,
created_by, created_at, updated_by, updated_at
skill_suite_version
id, suite_id, version, status, visibility, changelog, entry_skill_version_id,
published_at, yanked_at, yanked_by, yank_reason,
created_by, created_at
skill_suite_version_member
suite_version_id, skill_version_id(nullable), position,
namespace_slug_snapshot, skill_slug_snapshot,
skill_version_snapshot, fingerprint_snapshot
```
成员表保存外键和不可变快照。SkillVersion 被治理性硬删除时,外键使用 `ON DELETE SET NULL`Suite 历史仍显示原坐标、版本和 fingerprint并被标记为不可用。Suite 不阻塞必要的数据删除,也不伪装成仍可安装。
可见性属于 SuiteVersion 的审核快照,而不是覆盖全部历史版本的 Suite 容器字段。列表和默认详情展示 `latestVersionId` 对应的可见性;读取或安装历史版本时使用该版本自己的可见性并叠加实时 Member 权限检查。
**Alternative:** 一份 Suite ZIP 创建 N 个 Skill。拒绝因为会引入部分发布、重复扫描、N 个审核任务、所有权覆盖和回滚困难。
### 2. Skill 与 Suite 使用类型化身份
完整身份为:
```text
(resourceType, namespaceSlug, slug)
```
因此 `SKILL @global/marketing``SUITE @global/marketing` 可以同时存在。数据库分别约束 `skill(namespace_id, slug)``skill_suite(namespace_id, slug)`,不建立跨类型唯一约束。
命令、API、Web URL 和搜索结果必须携带类型:
```text
skillhub install @global/marketing
skillhub suite install @global/marketing
/api/v1/skills/...
/api/v1/suites/...
```
现有无类型 `skillhub install` 永远按 Skill 解析,不能根据搜索结果自动猜测 Suite。
**Alternative:** 在同一 Namespace 中跨类型保留 slug。拒绝因为限制了正常命名且类型化入口已经能够消除机器歧义。
### 3. SuiteVersion 引用精确的已发布 SkillVersion
作者可以在 `suite.yaml` 中选择成员坐标和版本:
```yaml
apiVersion: skillhub.iflytek.com/v1alpha1
kind: SkillSuite
metadata:
namespace: global
slug: superpowers
version: 1.0.0
displayName: Superpowers
spec:
visibility: PUBLIC
entrySkill: "@global/using-superpowers@1.0.0"
members:
- skill: "@global/using-superpowers"
version: 1.0.0
- skill: "@global/brainstorming"
version: 2.1.0
```
该文件是 API/CLI 的创作输入,不是下载到 Agent 的包。服务端在创建 SuiteVersion 时解析并保存精确 `skillVersionId` 和 fingerprint。一个 SuiteVersion 最多包含 100 个不同 Skill同一 Skill 不允许重复出现。
成员发布新版本不会改变已有 SuiteVersion。采用新版本、添加、删除、重排成员或修改 Entry Skill 都必须创建新的 SuiteVersion。
为了降低创作成本Web/CLI 在添加 Member 时默认推荐该 Skill 当前可安装的最新版本,但保存时立即解析为精确 `skillVersionId`、version 和 fingerprint并向作者展示实际固定的版本。作者可以显式选择其他仍处于 PUBLISHED 的历史版本。
不得在已发布 SuiteVersion 中保存 `latest` 或在安装时重新解析最新版本。可以提供“更新成员版本”辅助操作,但该操作必须先展示版本差异,并创建或修改 DRAFT SuiteVersion它不是后台自动升级。
Member 从当前 SkillHub Registry 的专用候选查询中选择,而不是让前端加载全部 Skill 后自行过滤,也不直接复用只覆盖公开市场的普通搜索。候选查询接收 Suite Namespace、目标可见性和搜索条件服务端只返回当前用户可读取的 ACTIVE、非 hidden、具有 PUBLISHED 可安装版本的 Skill并给出版本列表和与目标 Suite 的兼容性结果。默认推荐最新可安装版本但最终保存精确版本。v1 不支持外部 Registry 成员。
成员版本失效时按阶段处理:
| 发生阶段 | 处理方式 |
| --- | --- |
| 创建或编辑 DRAFT 前 | 无法选择不可安装版本;已经选中的版本标记错误 |
| 提交审核时 | 重新校验并阻止提交,保留 DRAFT |
| 等待审核期间 | 审核通过前重新校验;失败时保持 PENDING_REVIEW 并给出阻塞成员 |
| SuiteVersion 发布后 | 不改写快照和 PUBLISHED 状态,计算为 degraded 并阻止整组安装 |
| 可逆条件恢复后 | hidden、archive 或访问范围恢复且成员精确版本仍可安装时,自动重新计算为 available |
| YANKED 或硬删除 | 不自动换到新版本;创建新的 SuiteVersion 并显式选择有效成员版本 |
**Alternative:** 保存版本范围或 `latest`。推迟,因为相同 SuiteVersion 会随时间解析出不同内容,破坏可重复安装和审计。
### 4. Suite 与 Skill 生命周期相互独立
Skill 继续使用现有生命周期:
```text
上传 → SCANNING/SCAN_FAILED → UPLOADED → PENDING_REVIEW
→ PUBLISHED → YANKED
```
Suite 没有可执行包不进入扫描状态。SuiteVersion 使用:
```text
DRAFT → PENDING_REVIEW → PUBLISHED → YANKED
↘ REJECTED
```
PRIVATE Suite 按现有 PRIVATE Skill 原则允许授权管理者从 DRAFT 直接发布。PUBLIC 和 NAMESPACE_ONLY Suite 进入一次 Suite 级审核。审核只检查 Suite 元数据、成员组成、可见性和可安装性,不重新审核成员 Skill。
Suite 容器复用 `ACTIVE/ARCHIVED` 和独立的 hidden 治理覆盖。Suite 的发布、拒绝、下架、隐藏、归档或删除都不改变任何 Member。Skill 的新版本、下架、隐藏、归档、可见性变化或硬删除也不改写已经发布的 SuiteVersion。
在 Suite 提交审核、审核通过和安装三个时间点都重新验证成员资格,防止期间状态或权限发生变化。
#### 提交与发布流程
| 阶段 | Skill | Suite | 二者关系 |
| --- | --- | --- | --- |
| 创建内容 | 上传一个包含 `SKILL.md` 的包并创建 SkillVersion | 创建 SuiteVersion选择已经发布的精确 SkillVersion | Suite 不接收或拆分 Skill 包 |
| 校验 | 校验包结构、文件类型和元数据 | 校验成员存在、已发布、无重复、可见性兼容及 Entry Skill 合法 | Suite 复用成员已有结果,不重新校验成员包 |
| 安全扫描 | 进入 `SCANNING`,成功后进入 `UPLOADED`,失败进入 `SCAN_FAILED` | 无扫描状态 | Suite 没有可执行包,因此不创建扫描任务 |
| 提交审核 | PUBLIC/NAMESPACE_ONLY SkillVersion 进入 `PENDING_REVIEW` | PUBLIC/NAMESPACE_ONLY SuiteVersion 进入 `PENDING_REVIEW` | Suite 只产生一个自己的审核任务,不复制成员审核任务 |
| 审核通过 | SkillVersion 进入 `PUBLISHED` | 再次检查全部成员后SuiteVersion 进入 `PUBLISHED` | Suite 发布不修改成员;成员必须仍可用于该 Suite |
| 安装 | 安装一个 SkillVersion | 先解析并校验整组成员,再以客户端事务安装全部成员 | Agent 最终只看到标准 Skill 目录 |
| 后续变更 | 新版本、下架、隐藏、归档或删除只作用于该 Skill | 新版本、下架、隐藏、归档或删除只作用于该 Suite | 已发布 SuiteVersion 不被自动改写;成员失效时 Suite 显示 degraded |
这意味着 Suite 是成员 Skill 的“版本化清单”,不是它们的父生命周期。删除 Suite 不删除 Skill更新 Skill 不更新 Suite更新 Suite 也不重新发布 Skill。
### 5. 审核任务支持类型化目标
现有 `review_task` 只指向 SkillVersion。为了保留一个审核中心和一致权限规则将其扩展为类型化审核目标
```text
subject_type: SKILL_VERSION | SUITE_VERSION
subject_version_id
```
现有 Skill 审核行回填为 `SKILL_VERSION`,旧 Skill 字段在兼容迁移完成前保留。领域服务通过目标处理器完成状态转换,避免 Suite 复制一整套审核控制器和权限判断。
Suite 属于 Namespace不形成脱离 Namespace 的永久个人所有权。Namespace MEMBER 可以创建 Suite并在仍属于该 Namespace 时管理自己创建的 DRAFT/REJECTED Suite 和提交新版本OWNER/ADMIN 可以管理该 Namespace 下全部 Suite。创建者离开 Namespace 后立即失去这类管理权Suite 及其审核历史仍属于 Namespace由 OWNER/ADMIN 接管。GLOBAL Namespace 沿用现有平台投稿和审核规则。
审核权限沿用当前 Namespace/平台角色原则提交者可以查看自己的审核TEAM OWNER/ADMIN 按现有规则审核GLOBAL 使用平台审核角色;自审限制不因 Suite 改变。
**Alternative:** 新建 `suite_review_task`。拒绝,因为会产生第二个审核中心、重复权限规则,并增加未来治理能力的分叉。
### 6. Suite 可见范围不能宽于任一成员
发布时必须满足Suite 的全部潜在安装者都有权读取每个 Member。具体规则为
- PUBLIC Suite 的所有成员必须为 PUBLIC。
- NAMESPACE_ONLY Suite 的成员必须为 PUBLIC或属于 Suite 同一 Namespace 且为 NAMESPACE_ONLY不能包含 PRIVATE Member。
- PRIVATE Suite 的成员可以为 PUBLIC或属于 Suite 同一 Namespace 的 NAMESPACE_ONLY/PRIVATE不能引用其他 Namespace 的非 PUBLIC Member。
这些规则检查的是 Suite 的目标受众,而不只是当前操作者。创建者必须具备 Suite 所属 Namespace 的当前投稿权限,并在引用时能够读取每个精确 Member。
安装时仍逐个执行实时授权和可下载性检查。成员后续变为不可见、YANKED、hidden、ARCHIVED 或被硬删除时SuiteVersion 进入可观察的 degraded 状态;安装整体失败,并指出阻塞成员,不自动替换为其他版本。
权限和可见性变化遵循以下原则:
- Member 从 PUBLIC 收窄为 NAMESPACE_ONLY/PRIVATE导致已发布 Suite 的目标受众不再全部可访问时Suite 变为 degraded。
- Member 扩大可见范围且重新满足规则时Suite 可用性自动恢复,不创建新 SuiteVersion。
- 用户离开 Namespace 后,立即失去 NAMESPACE_ONLY Suite 及其同 Namespace Member 的访问权Suite 不缓存历史授权。
- Suite 创建者离开 Namespace 后,其 `createdBy` 仅保留为审计事实,不再授予管理权或 PRIVATE Suite 访问权;已发布 Suite 继续存在OWNER/ADMIN 可以接管。
- Suite 的可见性变化属于新的发布决策,必须通过新 SuiteVersion 和对应审核完成;临时隐藏/恢复仍使用治理覆盖。
- 安装计划在返回任何下载地址前校验 Suite 和全部 Member。无权查看 Member 的用户只获得不泄露私有元数据的通用失败信息;管理员可查看具体阻塞成员。
Suite 操作权限为:
| 操作 | 权限 |
| --- | --- |
| 创建 Suite | 当前 Namespace MEMBER/ADMIN/OWNERGLOBAL 沿用现有平台投稿规则 |
| 编辑自己的 DRAFT/REJECTED、提交新版本 | 创建者仍是当前 Namespace 成员,或 Namespace ADMIN/OWNER |
| 管理任意 Suite、接管离职用户内容 | Namespace ADMIN/OWNER平台治理角色按现有规则处理 |
| 查看 PUBLISHED Suite | PUBLIC 为所有人NAMESPACE_ONLY 为当前 Namespace 成员PRIVATE 为当前创建者及 Namespace ADMIN/OWNERSUPER_ADMIN 可治理查看 |
| 查看 DRAFT/REJECTED | 当前创建者、Namespace ADMIN/OWNER 和有治理权限的平台角色 |
| 查看 PENDING_REVIEW | 提交者、Namespace ADMIN/OWNER 和有权审核的角色 |
| 安装 Suite | 先满足 SuiteVersion 自身可见性,再满足全部 Member 的实时访问和可安装条件 |
这里的“当前创建者”必须仍属于 Suite Namespace。离开 Namespace 后不再通过历史 `createdBy` 获得访问权。
### 7. Suite 不在 Agent 目录中伪装成 Skill
安装 Suite 时,成员按普通 Skill 安装到各 Agent 的标准 Skill 根目录。Suite 不创建:
```text
<skills-root>/<suite-slug>/SKILL.md
```
如果 Suite 表达工作流,`entrySkillVersionId` 指向一个普通 Member。Entry Skill 可以与 Suite 同名,也可以不同名;关系只来自显式 ID不由 slug 推断。
这避免腾讯 SkillSet 当前把编排提示写入普通 Skill 目录造成的覆盖问题,也保证所有 Agent 只需理解标准 Skill。
### 8. Suite 安装是一个客户端事务
`skillhub suite install` 执行:
1. 解析一个精确 SuiteVersion 和全部 Member。
2. 检查 Suite/成员权限、状态、目标目录、现有来源冲突和空间限制。
3. 下载全部成员到目标根目录内的临时目录。
4. 校验每个成员的 fingerprint 和 Skill 元数据。
5. 按稳定顺序获取所有目标锁,备份将被替换的同源目录。
6. 移动全部成员并一次性写入 inventory。
7. 任一步失败时恢复所有备份并保持原 inventory。
服务端只返回安装计划和成员下载能力不尝试对用户文件系统提供分布式事务。CLI 在现有 staged install、target lock 和 rollback 机制上扩展为多成员计划。
### 9. inventory 记录来源集合而不是单一所有者
inventory schema 增加 `suites`,并让 Skill 安装目标记录来源集合:
```json
{
"items": [
{
"registry": "https://registry.example.com",
"namespace": "global",
"slug": "brainstorming",
"version": "2.1.0",
"fingerprint": "sha256:...",
"installedBy": ["direct", "suite:@global/superpowers@1.0.0"],
"targets": []
}
],
"suites": [
{
"registry": "https://registry.example.com",
"namespace": "global",
"slug": "superpowers",
"version": "1.0.0",
"members": ["@global/brainstorming@2.1.0"]
}
]
}
```
卸载 Suite 只移除对应来源引用。仍被直接安装或被其他 Suite 引用的 Skill 保留;仅由该 Suite 引入且未被本地修改的成员才自动删除。检测到本地修改时保留目录并报告,不做破坏性清理。
### 10. 查询、升级和展示保持类型明确
新增的类型化资源发现入口返回 `resourceType`Web 使用类型徽标及独立 `/skills/...``/suites/...` 页面。现有 Skill 搜索接口继续只返回 Skill避免旧 CLI 或第三方客户端把 Suite 响应按 Skill 反序列化。Suite 详情显示版本、精确成员、Entry Skill、可用状态和阻塞原因。
`suite check` 比较 inventory 快照、磁盘 fingerprint 和远端 SuiteVersion`suite upgrade` 先显示成员增删改计划,再使用与安装相同的原子流程应用新的精确 SuiteVersion。升级不会单独追随 Member 的最新版本。
### 11. 生命周期兼容通过隔离状态与能力协商保证
SkillVersion 和 SuiteVersion 使用独立状态类型。不得把 Suite 专属状态加入现有 `SkillVersionStatus`,也不得因为成员失效或恢复而改写现有 SkillVersion 状态。Suite 的 `degraded` 是根据成员当前可用性计算出的展示/安装状态,不是 `SkillSuiteVersionStatus` 的持久化生命周期值。
Server 和 CLI 按以下组合兼容:
| 组合 | 预期行为 |
| --- | --- |
| 旧 CLI + 新 Server | 原有 Skill 搜索、解析、下载和安装完全不变;旧 CLI 看不到 Suite也不会误装 Suite |
| 新 CLI + 旧 Server | 普通 Skill 命令正常Suite 命令通过能力探测得到“不支持 Suite”的明确结果不猜测接口或回退成 Skill 安装 |
| 新 CLI + 新 Server | 使用 Suite 专用命令、类型化接口和新版 inventory |
| 已安装旧 inventory + 新 CLI | 缺少 Suite 字段时按空集合读取;首次 Suite 写入时原子升级 schema不改变已有 Skill 记录 |
| 新 Server + 旧数据库数据 | 数据库迁移只新增表和兼容字段;已有 Skill、ReviewTask 和审计记录含义不变 |
能力探测应使用 Server 已有版本/能力入口,或新增稳定的 capability 字段;不得仅依赖 HTTP 404 推断,因为 404 也可能代表代理路径或权限配置错误。Suite API、Suite 状态和 Suite review subject 必须以增量方式加入 OpenAPI现有 Skill 字段不得改名或改变枚举含义。
审核迁移采用双读/兼容窗口:现有 Skill 审核数据仍能通过旧的 Skill 关联字段读取,新写入的 Suite 审核使用类型化 subject确认所有运行版本均支持新结构后才允许在后续迁移中收紧旧字段。应用回滚期间保留新增表和字段禁止回滚数据库结构。
### 12. 拒绝重提和安装统计采用可追溯语义
SuiteVersion 被拒绝后允许由管理者退回 DRAFT保留原审核记录并修改未发布版本后再次提交。PUBLISHED/YANKED SuiteVersion 永远不可编辑;这些版本的任何变化都创建新 SuiteVersion。每次重新提交创建新的审核轮次不覆盖旧决定。
一次 Suite 安装计划使用服务端生成的唯一 `operationId` 串联一条 Suite 安装计划审计和多条 Member 下载审计。服务端成功签发完整安装计划后Suite 安装请求数增加一次;计划内每个 Member SkillVersion 按现有下载统计口径增加一次,并标记 `source=SUITE`。同一 `operationId` 的安全重试不得重复计数。
服务端无法可靠知道 CLI 最终是否完成本地文件提交,因此该指标表示“安装计划/下载已签发”不宣称是本地安装成功数。CLI 后续校验或提交失败不反向扣减服务端计数v1 不增加客户端完成回调或遥测上报。
## Risks / Trade-offs
- **审核表从 Skill 专用扩展为类型化目标** → 使用增量迁移、回填和兼容读取,保留现有 Skill 审核回归测试。
- **成员状态改变会使历史 Suite 无法安装** → 保留不可变快照、展示 degraded 原因,禁止静默换版。
- **多成员、多 Agent 目标导致锁和回滚复杂** → 限制 100 个成员预检后按规范化路径排序加锁inventory 最后原子写入。
- **共享成员卸载可能误删直接安装内容** → inventory 保存多来源引用,本地修改和来源不明时 fail closed。
- **Skill/Suite 同 slug 可能让自然语言含糊** → CLI、API、URL、搜索结果和安装提示始终携带资源类型`install` 固定解析 Skill。
- **在现有 Skill 搜索中直接混入 Suite 会破坏旧客户端** → 保留 Skill-only 旧接口,另增类型化资源发现入口。
- **审核目标和状态枚举扩展可能破坏滚动升级** → Suite 使用独立状态;审核表按兼容窗口增量迁移,并在混合版本验证后再收紧旧字段。
- **不支持一份 ZIP 创建全部成员,首次迁移多 Skill 仓库仍需发布成员** → v1 优先保证领域和生命周期正确;以后可增加调用现有发布 API 的批量 CLI 编排,但不改变 Suite 模型。
## Migration Plan
1. 新增 Suite 三张表、索引和类型化审核字段;回填现有审核任务为 `SKILL_VERSION`
2. 先发布兼容旧 API 的 Server新表为空时现有行为不变。
3. 发布 Web 的 Suite 管理和类型化审核展示,重新生成 OpenAPI 类型。
4. 发布支持 Suite 和新版 inventory 的 CLI读取旧 inventory 时将缺少的 `suites``installedBy` 视为空。
5. 用本地 exact-SHA 镜像验证 Skill 正常流、Suite 生命周期、成员失效和整组回滚。
回滚应用版本时保留新增表和字段,旧版本忽略它们;在确认没有 Suite 数据前不得删除迁移结构。
## Open Questions
以下内容明确推迟,不阻塞 v1
- 是否提供“批量发布成员后创建 Suite”的 CLI 编排。
- 是否为 Suite 增加收藏、评分、评论和独立订阅。
- 是否允许跨 Registry Suite 或对外导出通用集合清单。
- 是否在后续支持 Suite promotion 和组织级强制安装策略。

View file

@ -0,0 +1,44 @@
## Why
SkillHub 当前只能逐个发布和安装 Skill无法把一组已经发布、彼此协作的 Skill 作为一个可版本化、可审核、可一次安装的产品交付。Issue #715 提出的真实需求是保留成员 Skill 的独立性,同时为团队工作流、入门工具集和专家套件提供稳定的集合身份、成员快照和安装生命周期。
## What Changes
- 新增 Namespace 所有的 `SkillSuite` 及不可变的 `SkillSuiteVersion`
- SuiteVersion 只引用当前 Registry 中已经发布的精确 SkillVersion不重新上传、创建、扫描或发布成员 Skill。
- Skill 与 Suite 使用类型化身份;同一 Namespace 下允许二者使用相同 slug同类型 Suite 仍保持 `(namespace, slug)` 唯一。
- 新增 Suite 创建、编辑、审核、发布、下架、归档、查询和安装接口;新增显式类型化的资源发现入口,现有 Skill 搜索接口继续只返回 Skill。
- 新增 `skillhub suite install/check/upgrade/remove`,成员继续安装为标准 Agent SkillSuite 本身不生成同名 `SKILL.md`
- Suite 安装采用完整预检、全部暂存、fingerprint 校验和整体提交/回滚,避免部分安装。
- CLI inventory 记录 Suite 快照以及每个成员的直接安装和 Suite 来源,安全处理共享成员的卸载。
- 新增人类可编辑的 `suite.yaml`,仅作为 Suite 定义和发布输入,不改变现有单 Skill ZIP 协议。
## Decision Relative to Issue #715
本变更保留 Issue #715 的核心目标——版本化管理一组 Skill 并一次安装——但有意调整原提案中的三项实现:
- 不接受一份 Suite ZIP 自动创建多个 Skillv1 只组合已经独立发布的 SkillVersion避免部分发布、所有权冲突和重复扫描。
- 不为每个 Member 复制审核任务Member 已经完成自身审核Suite 只审核集合元数据、成员关系、可见性和可安装性。
- 不让既有 `skillhub install` 猜测资源类型Suite 使用 `skillhub suite install`,保证旧 CLI 和 Skill/Suite 同 slug 时行为确定。
“批量发布多个 Skill 后创建 Suite”可以作为后续 CLI 编排能力,但不会改变上述领域模型。
## Capabilities
### New Capabilities
- `skill-suites`: 定义 Skill Suite 的身份、成员关系、版本与审核生命周期、可见性、查询、原子安装、升级和卸载行为。
### Modified Capabilities
无。仓库尚无已归档 OpenSpec capability现有 Skill 发布和安装契约保持兼容。
## Impact
- **Domain / persistence**:新增 Suite 聚合、版本、成员快照和仓储;审核任务支持类型化目标。
- **API / OpenAPI**:新增 Suite 管理、审核、解析、安装计划和类型化资源发现接口;现有 Skill API 响应保持兼容;需要重新生成 Web 类型。
- **Web**:新增 Suite 列表、详情、编辑、版本和安装指引,并在搜索结果中展示 Skill/Suite 类型。
- **CLI**:新增 Suite 子命令、整组安装事务和 inventory schema 的向后兼容扩展。
- **Security / governance**成员仍使用现有扫描和审核结果Suite 只审核元数据与成员组成,不重复扫描成员包。
- **Compatibility**:现有 `skillhub install`、单 Skill ZIP、Skill URL 和已安装 Skill 不改变;旧 CLI 只是无法使用新 Suite 命令。
- **Out of scope**:嵌套 Suite、版本范围、跨 Registry 成员、可选成员、MCP/Hook/Agent 插件包、Suite 自动批量创建成员 Skill。

View file

@ -0,0 +1,496 @@
## ADDED Requirements
### Requirement: Suite SHALL be a typed Namespace resource
系统 SHALL 将 Suite 作为 Namespace 所有的独立资源,并以 `SUITE + namespace + slug` 标识。系统 SHALL 在同一 Namespace 内保持 Suite slug 唯一,但 SHALL 允许 Skill 与 Suite 使用相同 slug。
#### Scenario: Skill and Suite share a slug
- **WHEN** `SKILL @global/marketing` 已存在
- **THEN** 授权用户可以创建 `SUITE @global/marketing`
- **AND** API、URL、搜索结果和 CLI 操作通过资源类型明确区分二者
#### Scenario: Duplicate Suite slug
- **WHEN** 同一 Namespace 已存在 `SUITE @global/marketing`
- **THEN** 系统拒绝再次创建同 slug Suite
- **AND** 返回可操作的冲突说明
#### Scenario: Legacy install remains Skill-specific
- **WHEN** Skill 和 Suite 共用 `@global/marketing`
- **AND** 用户执行 `skillhub install @global/marketing`
- **THEN** CLI 只解析并安装 Skill
- **AND** 只有 `skillhub suite install @global/marketing` 才解析 Suite
### Requirement: SuiteVersion SHALL reference immutable published Skill versions
系统 SHALL 只允许 SuiteVersion 引用同一 Registry 中状态为 PUBLISHED 的精确 SkillVersion并 SHALL 保存成员坐标、版本和 fingerprint 快照。一个 SuiteVersion SHALL 最多包含 100 个不同 Skill。
#### Scenario: Create a valid SuiteVersion
- **WHEN** 管理者提交不超过 100 个不同的已发布 SkillVersion
- **THEN** 系统创建 DRAFT SuiteVersion
- **AND** 每个 Member 保存精确 SkillVersion ID、坐标、版本、fingerprint 和顺序
#### Scenario: Add a Member without choosing a version
- **WHEN** 作者添加一个 Skill 且没有显式选择版本
- **THEN** 系统向作者推荐当前可安装的最新 SkillVersion
- **AND** 保存前明确展示解析出的版本
- **AND** Member 最终保存为精确 SkillVersion ID、版本和 fingerprint而不是 `latest`
#### Scenario: Select an older published version
- **WHEN** 作者显式选择一个仍处于 PUBLISHED 且可访问的历史 SkillVersion
- **THEN** 系统允许将该精确版本保存为 Member
#### Scenario: Reject an unresolved or unpublished member
- **WHEN** 成员坐标或版本不存在,或对应 SkillVersion 不是 PUBLISHED
- **THEN** 系统拒绝创建或提交该 SuiteVersion
- **AND** 返回每个无效成员的坐标和原因
#### Scenario: Update Members to currently installable versions
- **WHEN** 作者对 DRAFT SuiteVersion 请求更新 Member 版本
- **THEN** 系统先展示每个拟议版本变化
- **AND** 只有作者确认后才更新 DRAFT 的精确 Member 引用
- **AND** PUBLISHED SuiteVersion 保持不变
#### Scenario: Reject duplicate or excessive members
- **WHEN** SuiteVersion 重复引用同一个 Skill或成员数超过 100
- **THEN** 系统拒绝该定义
- **AND** 不创建部分成员关系
### Requirement: Entry Skill SHALL be an explicit optional Member
SuiteVersion MAY 指定一个 Entry Skill。指定时Entry Skill SHALL 精确指向该 SuiteVersion 的一个 Member系统 SHALL NOT 根据 Suite 和 Skill 的同名关系推断入口。
#### Scenario: Valid Entry Skill
- **WHEN** SuiteVersion 将一个现有 Member 指定为 Entry Skill
- **THEN** 系统保存该精确 SkillVersion 关系
- **AND** Suite 详情和安装计划将其标记为入口
#### Scenario: Entry Skill is not a Member
- **WHEN** 提交的 Entry Skill 不在 Member 集合中
- **THEN** 系统拒绝该 SuiteVersion
#### Scenario: Suite has no Entry Skill
- **WHEN** Suite 仅表示安装集合
- **THEN** 系统允许 Entry Skill 为空
- **AND** 安装后不生成额外的编排 Skill
### Requirement: Member candidates SHALL be filtered by the Server
系统 SHALL 从当前 Registry 的 SkillVersion 数据中提供 Suite Member 候选,并 SHALL 根据当前用户访问权、Skill/Namespace 状态、PUBLISHED 可安装版本、Suite Namespace 和目标 SuiteVersion 可见性在服务端过滤。候选查询 SHALL NOT 泄露用户无权查看的 Skillv1 SHALL NOT 返回外部 Registry 成员。
#### Scenario: Open the Member picker
- **WHEN** 有投稿权限的用户为 DRAFT SuiteVersion 搜索 Member
- **THEN** 系统只搜索当前用户可读取的 ACTIVE、非 hidden 且具有 PUBLISHED 可安装版本的 Skill
- **AND** 返回精确版本选择以及与目标 Suite 可见性的兼容结果
#### Scenario: Search for an inaccessible private Skill
- **WHEN** 用户搜索自己无权访问的 PRIVATE Skill
- **THEN** 候选查询不返回该 Skill、版本、fingerprint 或其他私有元数据
#### Scenario: Search for a personally accessible but audience-incompatible Skill
- **WHEN** 用户可以访问一个跨 Namespace 的非 PUBLIC Skill
- **AND** 该 Skill 不满足目标 Suite 的受众规则
- **THEN** 系统不允许选择该 Skill
- **AND** 可以向用户说明目标可见性或 Namespace 不兼容,但不得向其他用户暴露该 Skill
### Requirement: Suite SHALL NOT own or mutate Member lifecycle
创建、提交、审核、发布、拒绝、下架、隐藏、归档或删除 Suite SHALL NOT 创建、发布、重新扫描、重新审核、下架、隐藏、归档或删除任何 Member Skill 或 SkillVersion。
#### Scenario: Publish a Suite of existing Skills
- **WHEN** SuiteVersion 通过发布流程
- **THEN** 系统只改变 Suite 和 SuiteVersion 状态
- **AND** 所有 Member 的状态、所有权、统计和审核记录保持不变
#### Scenario: Remove a Suite
- **WHEN** 授权管理者下架、归档或删除 Suite
- **THEN** Member Skill 仍可按其自身权限独立搜索、安装和管理
### Requirement: Suite SHALL use a scan-free version lifecycle
SuiteVersion SHALL 使用 DRAFT、PENDING_REVIEW、PUBLISHED、REJECTED、YANKED 状态,且 SHALL NOT 进入 SCANNING、SCAN_FAILED 或 UPLOADED。PUBLIC 和 NAMESPACE_ONLY Suite SHALL 完成一次 Suite 级审核PRIVATE Suite SHALL 按现有 PRIVATE Skill 的直接发布原则处理。
#### Scenario: Submit a public Suite for review
- **WHEN** 授权管理者提交有效的 PUBLIC SuiteVersion
- **THEN** SuiteVersion 转为 PENDING_REVIEW
- **AND** 系统创建一个目标类型为 SUITE_VERSION 的审核任务
- **AND** 不为 Member 创建新的审核任务或扫描任务
#### Scenario: Approve a Suite
- **WHEN** 有权限的审核者批准 PENDING_REVIEW SuiteVersion
- **AND** 全部成员仍满足发布条件
- **THEN** SuiteVersion 转为 PUBLISHED
- **AND** Suite.latestVersionId 指向该版本
#### Scenario: Reject a Suite
- **WHEN** 有权限的审核者拒绝 PENDING_REVIEW SuiteVersion
- **THEN** SuiteVersion 转为 REJECTED
- **AND** Member 状态保持不变
#### Scenario: Publish a private Suite
- **WHEN** 授权管理者确认发布有效的 PRIVATE DRAFT SuiteVersion
- **THEN** SuiteVersion 直接转为 PUBLISHED
- **AND** 系统不创建审核或扫描任务
### Requirement: Suite publication SHALL revalidate Member eligibility
系统 SHALL 在提交审核、审核批准和直接发布时重新验证全部 Member 的状态、可见性和访问范围。验证失败 SHALL 保持原状态并阻止发布动作。
#### Scenario: Member becomes unavailable during review
- **WHEN** SuiteVersion 处于 PENDING_REVIEW
- **AND** 一个 Member 在批准前被 YANKED、隐藏、归档或删除
- **THEN** 批准操作失败且 SuiteVersion 保持 PENDING_REVIEW
- **AND** 响应指出阻塞 Member 和当前状态
### Requirement: Published SuiteVersion SHALL be immutable
PUBLISHED 和 YANKED SuiteVersion 的元数据、Member、顺序、Entry Skill、成员版本和 fingerprint 快照 SHALL 不可修改。任何组成变化 SHALL 创建新的 SuiteVersion。
#### Scenario: Member publishes a newer version
- **WHEN** Member Skill 发布一个新版本
- **THEN** 现有 SuiteVersion 仍引用原 SkillVersion
- **AND** Suite 安装不会自动解析到新版本
#### Scenario: Change Suite membership
- **WHEN** 管理者添加、移除、重排 Member 或改变 Entry Skill
- **THEN** 系统要求创建一个新的 SuiteVersion
- **AND** 旧 SuiteVersion 保持不变
### Requirement: Suite visibility SHALL not exceed Member accessibility
SuiteVersion SHALL 保存自身经过审核的可见性快照,其发布范围 SHALL 不得宽于任何 Member 的可访问范围。安装历史版本时 SHALL 使用该 SuiteVersion 的可见性,而不是最新版本的可见性。安装时系统 SHALL 再次对当前用户逐个检查 Suite 和 Member 的实时权限。
#### Scenario: Public Suite contains a non-public Member
- **WHEN** PUBLIC SuiteVersion 包含 NAMESPACE_ONLY 或 PRIVATE Member
- **THEN** 系统拒绝提交或发布
- **AND** 指出可见性不兼容的 Member
#### Scenario: Namespace Suite uses an allowed Member
- **WHEN** NAMESPACE_ONLY SuiteVersion 引用 PUBLIC Member或同 Namespace 的 NAMESPACE_ONLY Member
- **THEN** 该 Member 通过可见性验证
#### Scenario: Namespace Suite contains a private Member
- **WHEN** NAMESPACE_ONLY SuiteVersion 引用 PRIVATE Member
- **THEN** 系统拒绝提交或发布
- **AND** 即使当前提交者本人可以访问该 Member 也不例外
#### Scenario: Private Suite uses same-Namespace Members
- **WHEN** PRIVATE SuiteVersion 引用 PUBLIC Member或同 Namespace 的 NAMESPACE_ONLY/PRIVATE Member
- **AND** 操作者具备 Suite Namespace 管理权限和所有 Member 读取权限
- **THEN** Member 通过静态可见性验证
#### Scenario: Suite references a cross-Namespace non-public Member
- **WHEN** SuiteVersion 引用其他 Namespace 的 NAMESPACE_ONLY 或 PRIVATE Member
- **THEN** 系统拒绝提交或发布
- **AND** 不以提交者个人的跨 Namespace 权限替代目标受众校验
#### Scenario: Installer loses Member access
- **WHEN** 用户能够读取 Suite但当前无权下载至少一个 Member
- **THEN** 安装预检整体失败
- **AND** 不下载或修改任何本地 Skill
#### Scenario: Change Suite visibility
- **WHEN** 管理者希望扩大或收窄已发布 Suite 的可见性
- **THEN** 系统要求创建包含目标可见性的新 SuiteVersion 并走对应审核流程
- **AND** 已发布历史 SuiteVersion 的可见性保持不变
#### Scenario: Install a historical SuiteVersion
- **WHEN** 用户请求安装非最新的 PUBLISHED SuiteVersion
- **THEN** 系统按该历史版本自己的可见性检查 Suite 访问权
- **AND** 继续逐个检查 Member 的当前访问权和可下载性
#### Scenario: Do not disclose inaccessible Member metadata
- **WHEN** 用户能够定位 Suite 但无权查看阻塞的 PRIVATE Member
- **THEN** 安装预检返回不泄露 Member 私有名称、下载地址或元数据的失败信息
- **AND** 有治理权限的管理员可以查看具体阻塞 Member 和原因
### Requirement: Unavailable Members SHALL degrade rather than rewrite a SuiteVersion
Member 在 Suite 发布后变得不可用时,系统 SHALL 保留 SuiteVersion 历史快照并将其显示为 degraded。系统 SHALL NOT 删除成员关系或自动替换为其他版本。
#### Scenario: Member is yanked or hidden after Suite publication
- **WHEN** PUBLISHED SuiteVersion 的 Member 被 YANKED、隐藏或归档
- **THEN** Suite 详情保留原成员坐标和版本
- **AND** 标记该成员不可用及原因
- **AND** Suite 安装预检失败
#### Scenario: Member visibility becomes narrower
- **WHEN** PUBLIC SuiteVersion 的 Member 从 PUBLIC 变为 NAMESPACE_ONLY 或 PRIVATE
- **THEN** SuiteVersion 保持 PUBLISHED 且显示 degraded
- **AND** 新安装整体失败
- **AND** 系统不自动选择该 Skill 的其他版本
#### Scenario: A reversible Member restriction is restored
- **WHEN** Member 因 hidden、archive 或可见范围收窄导致 Suite degraded
- **AND** 后续恢复后该精确 SkillVersion 再次满足 Suite 可见性和安装条件
- **THEN** Suite 自动重新计算为可安装
- **AND** 不创建或修改 SuiteVersion
#### Scenario: A permanently unavailable Member has a newer version
- **WHEN** Suite Member 已 YANKED 或硬删除
- **AND** 同一 Skill 存在其他 PUBLISHED 版本
- **THEN** 原 SuiteVersion 仍保持 degraded
- **AND** 管理者必须创建新 SuiteVersion 才能采用有效版本
#### Scenario: Member is hard-deleted
- **WHEN** PUBLISHED SuiteVersion 引用的 SkillVersion 被治理性硬删除
- **THEN** Suite 历史保留成员坐标、版本和 fingerprint 快照
- **AND** 成员外键可以为空并标记为已删除
- **AND** Suite 安装预检失败
#### Scenario: A deleted coordinate is recreated
- **WHEN** 被硬删除的 Member 后续以相同 namespace、slug 和 version 字符串重新创建
- **THEN** 原 SuiteVersion 不自动关联新 SkillVersion
- **AND** 原成员快照保持 tombstoned 和 degraded
- **AND** 管理者必须创建新 SuiteVersion 才能引用新实体及其 fingerprint
### Requirement: Suite installation SHALL be atomic across Members and targets
CLI SHALL 在修改目标目录前完成全部成员和全部 Agent 目标的解析、权限、冲突和下载预检。CLI SHALL 暂存并校验全部 Member 后统一提交;失败时 SHALL 恢复已有目录和 inventory。
#### Scenario: Install all Members successfully
- **WHEN** 全部 Member 可下载、fingerprint 匹配且目标可写
- **THEN** CLI 将全部 Member 安装到每个选定 Agent 的标准 Skill 目录
- **AND** 一次性记录 Suite 和 Member inventory
#### Scenario: A Member download or fingerprint fails
- **WHEN** 任一 Member 下载失败或 fingerprint 不匹配
- **THEN** Suite 安装整体失败
- **AND** 所有安装前已存在的目录和 inventory 保持不变
- **AND** 不留下已提交的部分 Member
#### Scenario: Commit fails after replacing some targets
- **WHEN** CLI 在文件提交阶段替换部分目标后发生错误
- **THEN** CLI 回滚本次已经替换的全部目标
- **AND** 恢复备份和安装前 inventory
- **AND** 无法完成的回滚必须保留备份路径并明确报告
### Requirement: Suite installation SHALL preserve Agent Skills compatibility
CLI SHALL 将每个 Member 作为普通 Skill 安装到 Agent 已支持的 Skill 根目录。CLI SHALL NOT 为 Suite 创建同名 `SKILL.md` 或要求 Agent 理解 Suite 协议。
#### Scenario: Suite and Skill share a slug locally
- **WHEN** `SKILL @global/marketing``SUITE @global/marketing` 同时存在
- **AND** 用户安装该 Suite
- **THEN** CLI 只安装 Suite 的 Member
- **AND** 不创建或覆盖 `<skills-root>/marketing/SKILL.md` 作为 Suite 编排文件
### Requirement: Inventory SHALL track shared installation provenance
CLI inventory SHALL 记录已安装 SuiteVersion、精确成员快照以及每个 Skill 目标的直接安装和 Suite 来源集合。旧 inventory SHALL 能够无损读取并迁移缺省字段。
#### Scenario: A Skill is direct-installed and Suite-installed
- **WHEN** 同一精确 SkillVersion 已直接安装,随后又被 Suite 引用
- **THEN** CLI 复用兼容的本地内容
- **AND** inventory 同时记录 direct 和 Suite 来源
#### Scenario: Multiple Suites share a Member
- **WHEN** 两个已安装 Suite 引用同一精确 SkillVersion 和目标目录
- **THEN** CLI 保留一个成员目录
- **AND** inventory 记录两个 Suite 来源
#### Scenario: Existing inventory has no Suite fields
- **WHEN** CLI 读取升级前的 inventory schema
- **THEN** CLI 将缺失的 Suite 和来源集合按空值处理
- **AND** 已安装 Skill 记录和目标路径保持不变
### Requirement: Suite removal SHALL be ownership-safe
`skillhub suite remove` SHALL 仅移除该 Suite 的来源记录。CLI SHALL 只自动删除不再被直接安装、未被其他 Suite 引用且未被本地修改的 Member 目录。
#### Scenario: Remove an exclusively Suite-installed Member
- **WHEN** Member 仅由被删除 Suite 安装且本地文件未修改
- **THEN** CLI 删除该 Member 目录和对应 inventory 来源
#### Scenario: Preserve a shared or direct-installed Member
- **WHEN** Member 仍有 direct 来源或其他 Suite 来源
- **THEN** CLI 保留 Member 目录及剩余来源
#### Scenario: Preserve a locally modified Member
- **WHEN** 待清理 Member 的当前 fingerprint 与安装基线不同
- **THEN** CLI 保留该目录
- **AND** 报告本地修改和人工处理建议
### Requirement: Suite check and upgrade SHALL use exact snapshots
CLI SHALL 提供 Suite 状态检查和升级计划。升级 SHALL 解析目标 SuiteVersion 的精确 Member 集合并使用与安装相同的原子事务,不得逐个追随 Member latest。
#### Scenario: Check an intact Suite
- **WHEN** inventory、磁盘内容和远端 SuiteVersion 快照一致
- **THEN** `skillhub suite check` 报告最新且完整
#### Scenario: Check a degraded local Suite
- **WHEN** Member 缺失、本地修改、版本不符或远端已不可用
- **THEN** `skillhub suite check` 按 Member 报告差异和阻塞原因
#### Scenario: Upgrade to a new SuiteVersion
- **WHEN** 用户确认从一个 SuiteVersion 升级到另一个版本
- **THEN** CLI 展示成员新增、删除和版本变化
- **AND** 通过原子安装事务应用完整目标快照
### Requirement: Suite governance actions SHALL not cascade to Members
Suite SHALL 支持版本下架、容器隐藏、恢复和归档,并 SHALL 与 Member 治理状态解耦。
#### Scenario: Yank the latest SuiteVersion
- **WHEN** 授权管理者下架 Suite 的最新 PUBLISHED 版本
- **THEN** 该版本转为 YANKED
- **AND** Suite.latestVersionId 重新指向仍可发布的最新 SuiteVersion 或为空
- **AND** Member 状态保持不变
#### Scenario: Hide or archive a Suite
- **WHEN** 管理者隐藏或归档 Suite
- **THEN** Suite 按现有治理规则停止普通发现或新版本操作
- **AND** Member 的发现和生命周期保持不变
### Requirement: Typed discovery SHALL preserve existing Skill search contracts
Suite SHALL 有独立 API 和 Web URL。新的类型化资源发现结果中每条 Skill 或 Suite 结果 SHALL 返回 `resourceType`。现有 Skill 搜索接口 SHALL 继续只返回 Skill并保持原响应契约。Suite 详情 SHALL 返回精确版本、成员、Entry Skill、可用性和 degraded 原因。
#### Scenario: Search returns same-slug resources
- **WHEN** 搜索命中同 Namespace、同 slug 的 Skill 和 Suite
- **THEN** 系统返回两条独立结果
- **AND** 每条结果包含不同的 `resourceType` 和详情 URL
#### Scenario: Existing Skill search remains Skill-only
- **WHEN** 旧 CLI 或第三方客户端调用现有 Skill 搜索接口
- **THEN** 响应只包含 Skill
- **AND** 字段、枚举含义和解析行为与引入 Suite 前保持兼容
#### Scenario: Resolve a Suite install plan
- **WHEN** 授权用户通过 Suite 专用接口解析某个版本
- **THEN** 响应包含 SuiteVersion 身份以及有序的精确 Member 版本、fingerprint 和可下载状态
### Requirement: Suite operations SHALL be authorized and audited
Suite 创建、编辑、提交、审核、发布、下架、隐藏、恢复、归档和删除 SHALL 使用现有 Namespace 与平台角色原则,并 SHALL 产生包含 Suite 类型、Suite ID、SuiteVersion ID、操作者和变更摘要的审计记录。
#### Scenario: Unauthorized user modifies a Suite
- **WHEN** 用户不具备该 Namespace 的 Suite 管理权限
- **THEN** 系统拒绝修改且不改变 Suite 状态
#### Scenario: Namespace Member creates a Suite
- **WHEN** 当前 Namespace MEMBER 创建 Suite
- **THEN** 系统允许创建并记录 createdBy
- **AND** 该用户在仍属于 Namespace 时可以维护自己的 DRAFT/REJECTED Suite 和提交新版本
#### Scenario: Suite creator leaves the Namespace
- **WHEN** Suite 创建者不再是 Suite Namespace 成员
- **THEN** createdBy 继续作为审计事实保留
- **AND** 该用户立即失去基于创建者身份的编辑、提交和 PRIVATE Suite 访问权
- **AND** 已发布 Suite、SuiteVersion 和审核记录保持不变
- **AND** Namespace ADMIN/OWNER 可以接管后续维护
#### Scenario: Public user views and installs a public Suite
- **WHEN** 用户访问 PUBLISHED PUBLIC SuiteVersion
- **THEN** 用户可以查看 Suite 公开元数据
- **AND** 只有全部 Member 仍公开且可安装时才能获得完整安装计划
#### Scenario: Namespace member accesses a namespace Suite
- **WHEN** 当前 Namespace MEMBER 访问 PUBLISHED NAMESPACE_ONLY SuiteVersion
- **THEN** 用户可以查看并在全部 Member 校验通过后安装
#### Scenario: Regular member accesses a private Suite
- **WHEN** 普通 Namespace MEMBER 不是 Suite 当前创建者且尝试访问 PRIVATE SuiteVersion
- **THEN** 系统拒绝查看和安装
- **AND** Namespace ADMIN/OWNER 及仍在 Namespace 内的当前创建者可以按规则访问
#### Scenario: Suite review is recorded once
- **WHEN** SuiteVersion 被提交并完成审核
- **THEN** 审核中心记录一个 SUITE_VERSION 审核任务及决定
- **AND** 不为 Member 复制审核记录
### Requirement: Rejected SuiteVersions SHALL preserve review history
REJECTED SuiteVersion MAY 由有权限的管理者退回 DRAFT、修改并重新提交。系统 SHALL 保留每次审核轮次和决定。PUBLISHED 或 YANKED SuiteVersion SHALL NOT 退回可编辑状态。
#### Scenario: Edit and resubmit a rejected SuiteVersion
- **WHEN** 管理者将 REJECTED SuiteVersion 退回 DRAFT、修正成员或元数据并重新提交
- **THEN** 系统创建新的审核轮次
- **AND** 原拒绝决定、审核意见和操作者记录保持可查询
#### Scenario: Attempt to edit a published SuiteVersion
- **WHEN** 管理者尝试修改 PUBLISHED 或 YANKED SuiteVersion
- **THEN** 系统拒绝修改
- **AND** 提示创建新的 SuiteVersion
### Requirement: Suite download metrics SHALL remain attributable and idempotent
一次 Suite 安装计划 SHALL 使用服务端生成的唯一 operation ID 关联 Suite 请求与 Member 下载。服务端成功签发完整安装计划后SHALL 记录一次 Suite 安装请求,并 SHALL 按现有下载口径为计划内每个 Member SkillVersion 记录一次来源为 SUITE 的下载。同一 operation ID 的重试 SHALL NOT 重复计数。该指标 SHALL 表示服务端计划/下载签发,不得标记为 CLI 本地安装成功。
#### Scenario: Issue a complete Suite install plan
- **WHEN** 服务端完成 Suite 和全部 Member 的权限、状态及可下载性预检并签发完整安装计划
- **THEN** Suite 安装请求数增加一次
- **AND** 每个计划内 Member SkillVersion 下载数按现有口径增加一次并记录 Suite 来源
- **AND** 相关审计记录共享同一个 operation ID
#### Scenario: Suite plan preflight fails
- **WHEN** 服务端因权限、状态或成员不可用而无法签发完整安装计划
- **THEN** 不增加 Suite 安装请求数
- **AND** 不增加 Member 下载数
#### Scenario: Local installation fails after plan issuance
- **WHEN** CLI 在服务端签发计划后因下载、校验或文件提交失败并回滚
- **THEN** 服务端已记录的计划和下载计数保持不变
- **AND** 系统不将这些计数描述为本地安装成功数
#### Scenario: Retry an already recorded operation
- **WHEN** 客户端使用相同 operation ID 安全重试已经记录成功的安装
- **THEN** 系统返回已有计划或幂等成功
- **AND** Suite 和 Member 统计不重复增加
### Requirement: Existing Skill workflows SHALL remain compatible
引入 Suite 后,现有单 Skill 包协议、发布、扫描、审核、URL、API 和 CLI 安装行为 SHALL 保持不变。Suite 专用能力 SHALL 是增量接口。
#### Scenario: Publish and install an ordinary Skill
- **WHEN** 用户在不使用 Suite 的情况下发布并安装一个 Skill
- **THEN** 系统继续使用现有 Skill 生命周期和安装路径
- **AND** 不要求 Suite manifest 或新版 Suite inventory 数据
#### Scenario: Old CLI accesses a registry with Suites
- **WHEN** 不支持 Suite 的旧 CLI 使用现有 Skill API
- **THEN** Skill 搜索、解析、下载和安装仍正常工作
- **AND** 旧 CLI 不会把 Suite 误识别为 Skill
### Requirement: Skill and Suite lifecycle types SHALL remain isolated
系统 SHALL 为 SkillVersion 和 SuiteVersion 使用独立生命周期状态类型。Suite 专属状态 SHALL NOT 改变现有 `SkillVersionStatus` 的字段或枚举含义。Suite 的 degraded 可用性 SHALL 由成员当前状态计算,不得作为对 SkillVersion 状态的反向写入。
#### Scenario: Member availability changes
- **WHEN** PUBLISHED SuiteVersion 的 Member 变为不可安装或重新恢复可用
- **THEN** 系统重新计算 Suite 的可用性和阻塞原因
- **AND** SuiteVersion 的 PUBLISHED 状态保持不变
- **AND** Member SkillVersion 的生命周期状态不被 Suite 修改
#### Scenario: Existing client parses Skill status
- **WHEN** 旧客户端读取引入 Suite 后的 Skill API
- **THEN** 其看到的 Skill 状态枚举和值域与引入 Suite 前一致
### Requirement: Server and CLI versions SHALL fail compatibly
Suite 能力 SHALL 以增量方式提供。旧 CLI 使用新 Server 时 SHALL 保持全部普通 Skill 行为;新 CLI 使用不支持 Suite 的旧 Server 时 SHALL 保持普通 Skill 命令可用,并 SHALL 对 Suite 命令返回明确的不支持结果。
#### Scenario: New CLI uses an old Server
- **WHEN** CLI 请求 Suite 能力而 Server 未声明支持
- **THEN** CLI 停止 Suite 操作并说明 Server 不支持该能力
- **AND** 不将 Suite 命令降级为普通 Skill 安装
- **AND** 普通 Skill 命令仍可使用
#### Scenario: Upgrade a legacy inventory
- **WHEN** 新 CLI 首次向没有 Suite 字段的旧 inventory 写入 Suite 安装结果
- **THEN** CLI 在同一次原子写入中增加新版字段
- **AND** 已有 Skill、目标路径、版本和 fingerprint 记录保持不变
### Requirement: Review storage migration SHALL support rolling compatibility
审核存储从 Skill 专用关联扩展为类型化 subject 时,系统 SHALL 回填现有任务为 `SKILL_VERSION`,并 SHALL 在兼容窗口保留旧 Skill 关联的可读性。数据库迁移 SHALL 为增量迁移,应用回滚 SHALL NOT 要求删除新增 Suite 数据结构。
#### Scenario: Read an existing Skill review after migration
- **WHEN** 数据库迁移前已经存在 Skill 审核任务
- **THEN** 新版本应用仍按原 SkillVersion 读取和处理该任务
- **AND** 其审核决定、权限和审计语义保持不变
#### Scenario: Mixed application versions during rollout
- **WHEN** 部署期间同时存在支持和不支持 Suite subject 的应用实例
- **THEN** 现有 Skill 审核流程保持可用
- **AND** Suite 审核写入只在所有处理实例均支持类型化 subject 后启用

View file

@ -0,0 +1,49 @@
## 1. Persistence and domain model
- [ ] 1.1 Add Flyway migrations for `skill_suite`, `skill_suite_version`, and `skill_suite_version_member`, including per-type slug uniqueness, version uniqueness, version-level visibility, ordering, snapshot fields, indexes, and `ON DELETE SET NULL` member references.
- [ ] 1.2 Implement Suite aggregate entities, statuses, repositories, package boundaries, and domain invariants for 100-member limits, exact versions, duplicate detection, and Entry Skill membership.
- [ ] 1.3 Add focused repository and domain tests for same-slug Skill/Suite coexistence, immutable published versions, hard-deleted member snapshots, and latest-version recalculation.
- [ ] 1.4 Implement computed Suite availability and blocking reasons without adding degraded to the persisted SuiteVersion lifecycle enum.
## 2. Lifecycle, authorization, and review
- [ ] 2.1 Implement Suite draft, submit, approve, reject, direct-private-publish, yank, hide, restore, archive, and delete workflows without Member lifecycle side effects.
- [ ] 2.2 Generalize review tasks to typed subjects, backfill existing rows as `SKILL_VERSION`, and preserve all existing Skill review behavior and queries.
- [ ] 2.3 Reuse Namespace/platform authorization rules and add Suite-specific audit events for every material lifecycle action.
- [ ] 2.4 Add tests covering roles, self-review rules, member eligibility revalidation at submit/approve/publish, visibility compatibility, namespace freeze/archive, and non-cascading governance.
- [ ] 2.5 Add a rollout compatibility gate so Suite review writes are enabled only after all active application versions support typed review subjects.
- [ ] 2.6 Implement rejected-to-draft resubmission with immutable review rounds, and prevent published/yanked version edits.
## 3. Server API and search
- [ ] 3.1 Add transport-only Suite controllers and application services for management, version history, review actions, detail, and typed resolution of install plans.
- [ ] 3.2 Add a typed resource discovery projection with `resourceType` and Suite metadata while keeping the existing Skill search endpoint Skill-only.
- [ ] 3.3 Return ordered Member snapshots, Entry Skill, availability, and degraded reasons without N+1 member resolution.
- [ ] 3.4 Regenerate `web/src/api/generated/schema.d.ts` with `make generate-api` and run the OpenAPI drift check.
- [ ] 3.5 Record idempotent Suite-plan and Member-download audit/statistics using a shared operation ID, preserving the existing server-side download-count semantics.
- [ ] 3.6 Add a server-filtered Member candidate query scoped by caller access, Suite Namespace, target visibility, current installability, and exact versions.
## 4. Web experience
- [ ] 4.1 Add typed Skill/Suite search cards and independent Suite list/detail/version routes.
- [ ] 4.2 Add Suite creation and draft editing with the server-filtered Member picker, exact published versions, ordering, visibility, and optional Entry Skill.
- [ ] 4.3 Extend the review center with typed Suite review details and ensure existing Skill review actions remain unchanged.
- [ ] 4.4 Add install instructions using `skillhub suite install`, degraded-member explanations, and responsive/error/loading/empty states.
- [ ] 4.5 Default Member selection to the current installable version, display the pinned exact version, and provide an explicit version-diff update action for drafts.
## 5. CLI and local lifecycle
- [ ] 5.1 Add `skillhub suite install/check/upgrade/remove` and a typed Suite resolver without changing `skillhub install` resolution.
- [ ] 5.2 Extend the existing staged installer to preflight, download, fingerprint-check, lock, commit, and roll back all Members and selected Agent targets as one operation.
- [ ] 5.3 Extend inventory with backward-compatible Suite snapshots and multi-source `installedBy` provenance.
- [ ] 5.4 Implement safe Suite removal that preserves direct-installed, shared, unknown-source, or locally modified Member directories.
- [ ] 5.5 Add CLI tests for same-slug Skill/Suite, missing permissions, unavailable members, checksum failure, disk/rename failure, incomplete rollback reporting, shared members, legacy inventory, and multi-Agent targets.
- [ ] 5.6 Add Server capability detection and old-Server/new-CLI plus new-Server/old-CLI compatibility tests.
## 6. Documentation and validation
- [ ] 6.1 Document `suite.yaml`, Suite/Skill terminology, typed coordinates, lifecycle boundaries, CLI commands, compatibility, and operator limits.
- [ ] 6.2 Run targeted backend tests, `make test-backend-app`, frontend unit/type/lint checks, CLI tests/build, and OpenAPI drift validation.
- [ ] 6.3 Build exact-SHA local Server/Web images and run authenticated Compose smoke tests for ordinary Skill and Suite flows.
- [ ] 6.4 Execute the OpenSpec scenario matrix, including normal flow, same-slug compatibility, lifecycle independence, degraded members, authorization, atomic failure/recovery, upgrade, removal, rolling review migration, legacy inventory, and old/new Server/CLI combinations.
- [ ] 6.5 Complete independent implementation review, manual Web/CLI retest instructions, privacy/readiness checks, and the Chinese merge-readiness report before requesting merge authorization.