skillhub/docs/oss-01-core-contract-freeze.md
XiaoSeS 532d0450aa
feat(skill): add UPLOADED status for PRIVATE skill lifecycle (#290)
* feat(skill): add UPLOADED status for PRIVATE skill lifecycle

## Summary
- Add UPLOADED status for PRIVATE skills after security scan passes
- PRIVATE skill owners can test before confirming publish or submitting for review
- Rerelease now follows visibility rules (PRIVATE→UPLOADED, PUBLIC→PENDING_REVIEW)
- Auto-withdraw changes status to UPLOADED (not DRAFT) to keep versions visible

## Changes
- SkillVersionStatus: Add UPLOADED enum value
- SkillPublishService: PRIVATE skills go to UPLOADED after scan
- SecurityScanService: Visibility-based status transition after scan
- SkillGovernanceService: Withdraw→UPLOADED, delete allows UPLOADED
- SkillQueryService: Include UPLOADED in version list filters
- SkillReviewSubmitService: New service for submit-review and confirm-publish
- SkillLifecycleController: Add submit-review and confirm-publish endpoints
- Frontend: Add buttons, dialogs, and hooks for new operations

## Workflow
- PRIVATE: Publish → SCANNING → UPLOADED → confirm-publish → PUBLISHED
- PUBLIC: Publish → SCANNING → PENDING_REVIEW → PUBLISHED

* feat(review): add backward compatibility for DRAFT status

Support both DRAFT (legacy) and UPLOADED (new flow) status in:
- SkillReviewSubmitService.submitForReview
- SkillReviewSubmitService.confirmPublish
- ReviewService.submitReview (both overloads)

This ensures existing data with DRAFT status continues to work
with the new visibility-based workflow introduced in OSS-02.
2026-04-13 11:39:45 +08:00

19 KiB
Raw Permalink Blame History

OSS-01 Core 契约审计与冻结

1. 审计结论

SkillHub 开源项目已具备 AstronClaw 主链路所需的绝大部分 Core 能力。现有接口覆盖了 skill 唯一标识查询、版本元数据查询、创建(发布)和删除。无需在开源 Core 中新增 AstronClaw 专属接口;对 AstronClaw 而言,查询类和主链路类能力都应统一由 SaaS 层 AstronClaw Adapter 封装后对外提供,而不是直接绑定开源 Core 的接口形态。


2. Core 接口清单

以下接口构成 Core 基线能力,供 SaaS 层统一封装后对 AstronClaw 提供;这些接口本身不应被视为 AstronClaw 的长期直接契约。

2.1 skill 唯一标识与详情查询

接口 路径 说明
skill 详情 GET /api/v1/skills/{namespace}/{slug} 返回 SkillDetailResponse,包含完整 identity 和状态
版本解析 GET /api/v1/skills/{namespace}/{slug}/resolve?version=&tag=&hash= 返回 ResolveVersionResponse,解析人类可读版本选择器到精确版本

2.2 指定版本安装元数据查询

接口 路径 说明
版本详情 GET /api/v1/skills/{namespace}/{slug}/versions/{version} 返回 SkillVersionDetailResponse,含 metadata 和 manifest
版本文件列表 GET /api/v1/skills/{namespace}/{slug}/versions/{version}/files 返回 List<SkillFileResponse>
版本下载 GET /api/v1/skills/{namespace}/{slug}/versions/{version}/download 下载指定版本 bundle
版本列表 GET /api/v1/skills/{namespace}/{slug}/versions?page=&size= 分页返回版本列表

2.3 创建(发布)个人 skill

接口 路径 说明
发布 skill POST /api/v1/skills/{namespace}/publish 上传包并发布,返回 PublishResponse

2.4 删除个人 skill

接口 路径 说明
硬删除by ID DELETE /api/v1/skills/id/{skillId} 需 SUPER_ADMIN 权限
硬删除by 坐标) DELETE /api/v1/skills/{namespace}/{slug} 需 SUPER_ADMIN 权限
归档 POST /api/v1/skills/{namespace}/{slug}/archive owner 或 namespace admin 可操作
取消归档 POST /api/v1/skills/{namespace}/{slug}/unarchive 恢复为 ACTIVE

2.5 版本生命周期

接口 路径 说明
删除版本 DELETE /api/v1/skills/{namespace}/{slug}/versions/{version} 仅 DRAFT/REJECTED/SCAN_FAILED 可删
撤回审核 POST /api/v1/skills/{namespace}/{slug}/versions/{version}/withdraw-review PENDING_REVIEW → DRAFT
重新发布 POST /api/v1/skills/{namespace}/{slug}/versions/{version}/rerelease 重新发布版本

2.6 ClawHub 兼容接口(已有)

接口 路径 说明
解析 skill GET /api/v1/resolve?slug=&version= ClawHub 协议兼容
解析 skill路径 GET /api/v1/resolve/{canonicalSlug}?version= ClawHub 协议兼容
下载 GET /api/v1/download/{canonicalSlug}?version= 302 重定向到下载地址
删除 skill DELETE /api/v1/skills/{canonicalSlug} owner 可操作
取消删除 POST /api/v1/skills/{canonicalSlug}/undelete owner 可操作
发布 skill POST /api/v1/skills ClawHub 协议兼容
发布到 namespace POST /api/v1/publish ClawHub 协议兼容

3. 字段语义冻结表

3.1 Skill Identity 字段

字段 类型 含义 稳定性 说明
skill.id Long skill 全局唯一主键 不可变 自增,创建后永不改变,可作为外部映射主键
namespace (slug) String(64) skill 所属命名空间标识 不可变 全局唯一,创建后不可改名
skill.slug String(100) skill 在 namespace 内的唯一标识 不可变 创建后不可改名,namespace + slug 构成业务坐标
skill.displayName String(200) skill 展示名称 可变 仅用于展示,不可作为映射依据
skill.ownerId String skill 创建者 ID 不可变 创建时绑定,不可转移
skill.summary String(TEXT) skill 简介 可变 展示用
skill.visibility Enum 可见性 可变 PUBLIC / NAMESPACE_ONLY / PRIVATE
skill.status Enum skill 状态 可变 ACTIVE / HIDDEN / ARCHIVED
skill.hidden boolean 是否被管理员隐藏 可变 与 status 独立的隐藏标记
skill.latestVersionId Long 最新版本指针 可变 指向当前最新已发布版本yank/删除后自动回退
skill.downloadCount Long 下载次数 可变 累计值
skill.starCount Integer 收藏数 可变 累计值

3.2 SkillVersion 字段

字段 类型 含义 稳定性 说明
version.id Long 版本全局唯一主键 不可变 自增
version.skillId Long 所属 skill ID 不可变 外键
version.version String(64) 版本号 不可变 1.0.0,创建后不可改
version.status Enum 版本状态 可变 见状态语义表
version.bundleReady boolean bundle 是否可用 可变 true 表示 bundle 已构建完成,可下载安装
version.downloadReady boolean 是否允许下载 可变 yank 后设为 false
version.publishedAt Instant 发布时间 一次写入 首次发布时设置
version.parsedMetadataJson JSONB 解析后的元数据 一次写入 包含 package_name 等运行时信息
version.manifestJson JSONB manifest 原始内容 一次写入 skill 包的 manifest
version.changelog String(TEXT) 变更日志 可变 展示用
version.fileCount Integer 文件数量 一次写入 发布时确定
version.totalSize Long 总大小(字节) 一次写入 发布时确定
version.yankedAt Instant yank 时间 一次写入 yank 时设置
version.yankReason String(TEXT) yank 原因 一次写入 yank 时设置

3.3 关键字段含义冻结

字段 冻结定义
skill_id skill.idLong 类型自增主键全局唯一创建后不可变。AstronClaw 应以此作为 external_skill_mapping 的外部主键
namespace namespace.slugString(64),全局唯一,不可改名。与 slug 组合构成业务坐标
slug skill.slugString(100)namespace 内唯一,不可改名。namespace/slug 是人类可读的稳定坐标
version skill_version.versionString(64),同一 skill 内唯一,不可改。如 1.0.0
bundle_url 通过 GET /{namespace}/{slug}/versions/{version}/download 获取,或通过 resolve 接口的 downloadUrl 字段获取。不是数据库字段,而是动态生成的下载地址
bundle_ready skill_version.bundleReadyboolean。true 表示 bundle 已构建完成可安装。AstronClaw 安装前必须校验此字段
package_name 存储在 skill_version.parsedMetadataJson 中,从 skill 包的 manifest 解析而来。同一 skill 跨版本应保持稳定。AstronClaw 用于运行时安装/卸载标识

3.4 Namespace 字段

字段 类型 含义 稳定性
namespace.id Long 命名空间主键 不可变
namespace.slug String(64) 命名空间标识 不可变,全局唯一
namespace.displayName String(128) 展示名称 可变
namespace.type Enum 类型 不可变,GLOBAL / TEAM
namespace.status Enum 状态 可变,ACTIVE / FROZEN / ARCHIVED

4. 状态语义冻结表

4.1 Skill 状态(SkillStatus

状态 市场可见 可新装 已装是否保留 可被 owner 操作 说明
ACTIVE 是(受 visibility 控制) 是(需有 PUBLISHED 版本) 正常状态
HIDDEN 受限 管理员隐藏,独立于 status 的 hidden 标记
ARCHIVED 可取消归档 owner 或 namespace admin 归档

4.2 版本状态(SkillVersionStatus

状态 是否允许安装 是否允许下载 市场可见 可转换到 说明
DRAFT SCANNING, 可删除 初始状态,编辑中
SCANNING SCAN_FAILED, PENDING_REVIEW, PUBLISHED 安全扫描中
SCAN_FAILED 可删除 安全扫描失败
PENDING_REVIEW PUBLISHED, REJECTED, → DRAFT(撤回) 等待审核
PUBLISHED YANKED 已发布,可安装
REJECTED 可删除 审核拒绝
YANKED 否(或弱可见) 不可逆 已撤回,已装不受影响

4.3 可见性(SkillVisibility

可见性 市场列表可见 谁可查看 谁可安装
PUBLIC 所有人 所有人(需 PUBLISHED + bundleReady
NAMESPACE_ONLY namespace 成员 namespace 成员
PRIVATE 仅 owner 仅 owner

4.4 删除语义

操作 类型 可逆 数据影响 已装实例影响
硬删除 skill 永久删除 删除所有记录、文件、存储对象slug 可复用 不影响AstronClaw 已装快照独立
归档 skill 状态变更 无数据删除status → ARCHIVED 不影响
隐藏 skill 标记变更 无数据删除hidden → true 不影响
删除版本 永久删除 仅删除 DRAFT/REJECTED/SCAN_FAILED 版本 不影响(这些版本未被安装)
Yank 版本 状态变更 status → YANKEDdownloadReady → false 不影响已装实例

4.5 AstronClaw 安装判断规则

AstronClaw 判断一个 skill 版本是否可安装,需同时满足:

skill.status == ACTIVE
  AND skill.hidden == false
  AND skill.visibility 允许当前用户访问
  AND version.status == PUBLISHED
  AND version.bundleReady == true

已安装实例不受后续状态变更影响。即使 skill 被删除/归档/隐藏,或版本被 yankAstronClaw 本地安装快照仍可正常使用和卸载。

5. 错误语义表

5.1 统一响应结构

{
  "code": 0,
  "msg": "操作成功",
  "data": { ... },
  "timestamp": "2026-04-10T08:00:00Z",
  "requestId": "req-xxx"
}
  • code = 0 表示成功
  • code > 0 表示错误,值为 HTTP 状态码

5.2 错误码映射

HTTP 状态码 场景 异常类型 说明
400 参数非法 BadRequestException / DomainBadRequestException 请求参数校验失败
401 未认证 UnauthorizedException / AuthFlowException 未登录或 token 过期
403 无权限 ForbiddenException / DomainForbiddenException 无操作权限
404 未找到 DomainNotFoundException skill/version/namespace 不存在
408 请求超时 AsyncRequestTimeoutException 异步请求超时
503 存储不可用 StorageAccessException 对象存储访问失败
500 服务异常 Exception 未预期的内部错误

5.3 Core 主链路关键错误场景

场景 HTTP 状态码 msg 示例 AstronClaw 处理建议
skill 不存在 404 error.skill.notFound 映射失败,提示用户
版本不存在 404 error.skill.notFound 安装/升级失败,提示用户
版本不可安装(非 PUBLISHED 400 error.badRequest 拒绝安装,提示版本状态
bundle 未就绪 400 error.badRequest 拒绝安装,提示稍后重试
无权访问PRIVATE skill 403 error.forbidden 提示无权限
namespace 不存在 404 error.namespace.notFound 映射失败
存储服务不可用 503 error.storage.unavailable 降级处理,已装 skill 不受影响
删除不允许(非 owner 403 error.forbidden 提示无权限

6. Core vs SaaS Adapter 能力分界

6.1 Core 已满足的能力

说明:

下表表示“开源 Core 已具备、可供 SaaS 封装”的能力,并不表示 AstronClaw 应直接调用这些开源接口。

PRD 需求 Core 接口 满足程度 备注
skill 唯一标识查询 GET /{namespace}/{slug} 完全满足 返回 idnamespaceslug
指定版本安装元数据 GET /{namespace}/{slug}/versions/{version} 基本满足 返回 status、metadatapackage_nameparsedMetadataJson
版本解析 GET /{namespace}/{slug}/resolve 完全满足 支持 version/tag/hash 解析
bundle 下载 GET /{namespace}/{slug}/versions/{version}/download 完全满足 直接下载
创建(发布)个人 skill POST /{namespace}/publish 完全满足 返回 skillId、namespace、slug、version、status
删除个人 skill DELETE /{namespace}/{slug} (ClawHub 兼容) 完全满足 owner 可操作
归档 skill POST /{namespace}/{slug}/archive 完全满足 可逆操作
版本状态查询 GET /{namespace}/{slug} 中的 headlineVersion/publishedVersion 完全满足 包含版本状态
labels 数据 GET /{namespace}/{slug} 中的 labels 字段 完全满足 返回 List<SkillLabelDto>

6.2 需要 SaaS Adapter 新增的能力

PRD 需求 原因 Adapter 建议
市场列表查询(搜索/过滤/排序) Core 不提供面向页面的聚合列表 GET /api/v1/astronclaw/adapter/skills/market
市场详情AstronClaw DTO Core 返回的 DTO 包含 Core 内部字段,需适配 GET /api/v1/astronclaw/adapter/skills/{id}
owner 维度"我创建的"查询 Core 的 /me/skills 返回 Core DTO需适配 GET /api/v1/astronclaw/adapter/skills/mine
is_installed 补全 安装关系在 AstronClaw 侧 AstronClaw 本地补全,不在 Adapter
package_name 顶层字段 当前在 parsedMetadataJson 内,需提取 Adapter 解析 JSON 后平铺返回
bundle_url 直接返回 当前需通过 download 接口获取 Adapter 可直接返回预签名 URL
统一 can_install 判断 需组合 status + visibility + bundleReady Adapter 计算后返回布尔值
统一 can_delete 判断 需组合 owner + status Adapter 计算后返回布尔值

6.3 分界原则

Core 负责skill 生命周期真相identity、version、status、artifact
Adapter 负责:面向 AstronClaw 的 DTO 适配(字段平铺、状态聚合、权限预判断)

补充原则:

  1. 即使开源 Core 已经具备某项主链路能力,AstronClaw 仍应统一通过 SaaS Adapter 消费。
  2. 该原则同时适用于唯一标识查询、版本元数据、创建个人 skill、删除个人 skill。
  3. 开源文档中的接口清单用于说明 Core 能力边界,不应被解读为 AstronClaw 的直接对接建议。

7. 成功 / 失败 / 边界样例

7.1 查询 skill identity — 成功

GET /api/v1/skills/my-namespace/my-skill
{
  "code": 0,
  "data": {
    "id": 42,
    "slug": "my-skill",
    "displayName": "My Skill",
    "ownerId": "user-123",
    "status": "ACTIVE",
    "visibility": "PUBLIC",
    "namespace": "my-namespace",
    "labels": [{"slug": "nlp", "type": "CATEGORY", "displayName": "NLP"}],
    "headlineVersion": {"id": 100, "version": "1.2.0", "status": "PUBLISHED"},
    "publishedVersion": {"id": 100, "version": "1.2.0", "status": "PUBLISHED"}
  }
}

AstronClaw 映射关键字段:id=42namespace=my-namespaceslug=my-skill

7.2 查询 skill identity — 不存在

GET /api/v1/skills/my-namespace/nonexistent
{
  "code": 404,
  "msg": "Skill not found",
  "data": null
}

7.3 查询指定版本元数据 — 成功

GET /api/v1/skills/my-namespace/my-skill/versions/1.2.0
{
  "code": 0,
  "data": {
    "id": 100,
    "version": "1.2.0",
    "status": "PUBLISHED",
    "changelog": "Bug fixes",
    "fileCount": 3,
    "totalSize": 102400,
    "publishedAt": "2026-04-01T10:00:00Z",
    "parsedMetadataJson": "{\"name\":\"my-skill\",\"package_name\":\"my_namespace__my_skill\",\"version\":\"1.2.0\"}",
    "manifestJson": "{...}"
  }
}

package_nameparsedMetadataJson 中提取。

7.4 查询已 YANKED 版本

GET /api/v1/skills/my-namespace/my-skill/versions/1.0.0
{
  "code": 0,
  "data": {
    "id": 98,
    "version": "1.0.0",
    "status": "YANKED",
    "publishedAt": "2026-03-01T10:00:00Z"
  }
}

AstronClaw 判断 status != PUBLISHED,拒绝新安装。已装实例不受影响。

7.5 发布(创建)个人 skill — 成功

POST /api/v1/skills/my-namespace/publish
Content-Type: multipart/form-data
file: <skill-package.tar.gz>
visibility: PRIVATE
{
  "code": 0,
  "data": {
    "skillId": 43,
    "namespace": "my-namespace",
    "slug": "new-skill",
    "version": "0.1.0",
    "status": "DRAFT",
    "fileCount": 2,
    "totalSize": 51200
  }
}

7.6 删除个人 skill — 成功

DELETE /api/v1/skills/my-namespace/my-skill
{
  "code": 0,
  "data": {
    "ok": true
  }
}

7.7 删除个人 skill — 无权限

DELETE /api/v1/skills/other-namespace/other-skill
{
  "code": 403,
  "msg": "Forbidden",
  "data": null
}

7.8 边界skill 已归档后查询

GET /api/v1/skills/my-namespace/archived-skill
{
  "code": 0,
  "data": {
    "id": 44,
    "slug": "archived-skill",
    "status": "ARCHIVED",
    "visibility": "PUBLIC"
  }
}

skill 仍可查询,但 AstronClaw 应根据 status=ARCHIVED 判断不可新装。


8. 遗留问题与建议

8.1 package_name 提取

当前 package_name 嵌套在 parsedMetadataJson JSONB 字段中,不是顶层字段。

建议SaaS Adapter 在返回 AstronClaw DTO 时,解析 JSON 并将 package_name 提取为顶层字段。Core 不需要改动。

8.2 bundle_url 获取方式

当前没有直接返回 bundle_url 的字段,需通过 download 接口获取。ResolveVersionResponse 中有 downloadUrl 字段。

建议SaaS Adapter 可通过 resolve 接口获取 downloadUrl,或直接生成预签名 URL 返回给 AstronClaw。

8.3 删除接口权限

当前 DELETE /api/v1/skills/{namespace}/{slug}portal 路径)需要 SUPER_ADMIN 权限。ClawHub 兼容接口 DELETE /api/v1/skills/{canonicalSlug} 允许 owner 操作。

建议SaaS Adapter 应统一封装 owner 可操作的删除接口,对 AstronClaw 暴露稳定契约AstronClaw 不直接依赖开源删除接口路径。

8.4 hiddenstatus 的关系

当前 hidden 是独立于 status 的布尔标记(管理员操作),而 HIDDENSkillStatus 枚举值之一但实际代码中 skill 的 status 枚举包含 ACTIVEHIDDENARCHIVED

建议SaaS Adapter 统一为 AstronClaw 提供一个 is_visible 聚合字段,屏蔽内部 hidden 标记与 status 的复杂关系。