mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-10 22:41:02 +00:00
docs(spec): define skill suite lifecycle and compatibility
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
parent
53df1041f5
commit
accd0e1f67
6 changed files with 940 additions and 0 deletions
2
openspec/changes/add-skill-suites/.openspec.yaml
Normal file
2
openspec/changes/add-skill-suites/.openspec.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
schema: spec-driven
|
||||
created: 2026-09-07
|
||||
3
openspec/changes/add-skill-suites/README.md
Normal file
3
openspec/changes/add-skill-suites/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# add-skill-suites
|
||||
|
||||
Define versioned Skill Suites as typed collections of published Skill versions for Issue #715.
|
||||
346
openspec/changes/add-skill-suites/design.md
Normal file
346
openspec/changes/add-skill-suites/design.md
Normal 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/OWNER;GLOBAL 沿用现有平台投稿规则 |
|
||||
| 编辑自己的 DRAFT/REJECTED、提交新版本 | 创建者仍是当前 Namespace 成员,或 Namespace ADMIN/OWNER |
|
||||
| 管理任意 Suite、接管离职用户内容 | Namespace ADMIN/OWNER;平台治理角色按现有规则处理 |
|
||||
| 查看 PUBLISHED Suite | PUBLIC 为所有人;NAMESPACE_ONLY 为当前 Namespace 成员;PRIVATE 为当前创建者及 Namespace ADMIN/OWNER;SUPER_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 和组织级强制安装策略。
|
||||
44
openspec/changes/add-skill-suites/proposal.md
Normal file
44
openspec/changes/add-skill-suites/proposal.md
Normal 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 Skill;Suite 本身不生成同名 `SKILL.md`。
|
||||
- Suite 安装采用完整预检、全部暂存、fingerprint 校验和整体提交/回滚,避免部分安装。
|
||||
- CLI inventory 记录 Suite 快照以及每个成员的直接安装和 Suite 来源,安全处理共享成员的卸载。
|
||||
- 新增人类可编辑的 `suite.yaml`,仅作为 Suite 定义和发布输入,不改变现有单 Skill ZIP 协议。
|
||||
|
||||
## Decision Relative to Issue #715
|
||||
|
||||
本变更保留 Issue #715 的核心目标——版本化管理一组 Skill 并一次安装——但有意调整原提案中的三项实现:
|
||||
|
||||
- 不接受一份 Suite ZIP 自动创建多个 Skill;v1 只组合已经独立发布的 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。
|
||||
496
openspec/changes/add-skill-suites/specs/skill-suites/spec.md
Normal file
496
openspec/changes/add-skill-suites/specs/skill-suites/spec.md
Normal 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 泄露用户无权查看的 Skill,v1 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 后启用
|
||||
49
openspec/changes/add-skill-suites/tasks.md
Normal file
49
openspec/changes/add-skill-suites/tasks.md
Normal 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.
|
||||
Loading…
Add table
Reference in a new issue