Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/00-product-direction.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ ClawHub CLI 使用单一 slug 模型,slug 校验规则为 `[a-z0-9]([a-z0-9-]*
- 团队技能提升到全局需平台管理员二次审核
- 平台管理员只负责全局空间审核与提升审核,不介入团队空间审核
- 当前不引入自动审核;`PrePublishValidator` 仅作为未来扩展点保留,默认实现为 `NoOp`
- 撤回审核语义统一为 `PENDING_REVIEW → DRAFT`,不再走删除版本记录
- skill 生命周期管理读模型统一为 `headlineVersion / publishedVersion / ownerPreviewVersion / resolutionMode`
- `hidden` 是独立治理覆盖层,不属于 skill 容器状态机

认证与权限:
- OAuth2 标准登录(一期 GitHub OAuth)
Expand Down
25 changes: 16 additions & 9 deletions docs/02-domain-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@
| owner_id | varchar(128) | 主要维护人(可转让) |
| source_skill_id | bigint | 派生来源(团队技能提升到全局时记录原 skill ID),nullable |
| visibility | enum | `PUBLIC` / `NAMESPACE_ONLY` / `PRIVATE` |
| status | enum | `ACTIVE` / `HIDDEN` / `ARCHIVED` |
| latest_version_id | bigint | 最新已发布版本(自动跟随,每次发布自动更新) |
| status | enum | `ACTIVE` / `ARCHIVED` |
| latest_version_id | bigint | latest published pointer,仅指向最新 `PUBLISHED` 版本;若不存在已发布版本则可为 `null` |
| download_count | bigint | |
| star_count | int | |
| rating_avg | decimal(3,2) | 平均评分 |
Expand All @@ -76,6 +76,7 @@
| updated_at | datetime | |

- 唯一约束:`(namespace_id, slug)`
- `status` 表示 skill 容器生命周期,不再承载“隐藏”语义。隐藏是独立的治理覆盖层,由 `hidden` / `hidden_at` / `hidden_by` 表达
- `owner_id` 语义为"主要维护人",可转让。权限主轴是 namespace role,不是 owner:
- namespace ADMIN 对空间内所有 skill 有完整管理权(归档、版本管理、提升到全局),不受 owner 限制
- owner 作为 MEMBER 时可管理自己创建的 skill(提交审核、编辑草稿)
Expand All @@ -102,8 +103,14 @@
| published_at | datetime | |
| created_at | datetime | |

- `status` 覆盖完整审核生命周期
- 状态机:`DRAFT → PENDING_REVIEW → PUBLISHED / REJECTED`,`PUBLISHED → YANKED`
- `status` 表示 version 发布生命周期,和 skill 容器状态、review task 状态分离
- 当前代码下的实际迁移约束:
- 普通用户首次上传/重传新版本后,版本直接进入 `PENDING_REVIEW`
- `SUPER_ADMIN` 直发时可直接进入 `PUBLISHED`
- 审核通过:`PENDING_REVIEW → PUBLISHED`
- 审核拒绝:`PENDING_REVIEW → REJECTED`
- 撤回审核:`PENDING_REVIEW → DRAFT`
- 已发布撤回:`PUBLISHED → YANKED`
- 唯一约束:`(skill_id, version)` 防止重复发布
- `YANKED` 状态:已发布后撤回

Expand All @@ -112,7 +119,7 @@
| 版本状态 | 版本号处理 |
|---------|-----------|
| DRAFT | 可删除该版本记录,重新使用同版本号 |
| PENDING_REVIEW | 可撤回到 DRAFT,然后删除 |
| PENDING_REVIEW | 可撤回到 DRAFT |
| REJECTED | 可删除该版本记录,重新使用同版本号 |
| PUBLISHED | 版本号永久占用,不可复用 |
| YANKED | 版本号永久占用,不可复用,版本列表中显示但标记为不可下载 |
Expand Down Expand Up @@ -144,7 +151,7 @@
| updated_by | varchar(128) | |
| updated_at | datetime | |

- `latest` 是系统保留标签,只读,自动跟随 `skill.latest_version_id`,不允许 API 手动移动
- `latest` 是系统保留标签,只读,自动跟随 `skill.latest_version_id`;其语义严格等价于“最新已发布版本”,不允许 API 手动移动
- 自定义标签(如 `beta`、`stable-2026q1`)允许人工创建和移动
- 唯一约束:`(skill_id, tag_name)`
- `target_version_id` 必须指向 `status = PUBLISHED` 的版本,应用层校验
Expand All @@ -166,7 +173,7 @@

- 仅用于普通发布审核,"提升到全局"使用独立的 `promotion_request` 表
- `version` 字段用于乐观锁,防止多 Pod 并发审核
- 业务约束:同一 `skill_version_id` 在 `status=PENDING` 时只能存在一条记录,重复提交返回 409 Conflict。撤回(PENDING → 删除 review_task + skill_version 回退到 DRAFT)后才能再次提交
- 业务约束:同一 `skill_version_id` 在 `status=PENDING` 时只能存在一条记录,重复提交返回 409 Conflict。撤回时删除 `PENDING` review_task,并将 `skill_version` 回退到 `DRAFT`
- PostgreSQL 并发约束落地:通过唯一索引 `(skill_version_id)` + 软删除标记实现。`review_task` 表增加 `deleted` 字段(bigint, 默认 0),唯一索引改为 `(skill_version_id, deleted)`。撤回时将 `deleted` 设为 `id`(非零值),新提交时 `deleted=0`,利用唯一索引防止并发重复提交。或者采用更简单的方案:撤回时物理删除 review_task 记录,依赖 `INSERT` 的唯一约束 `(skill_version_id)` 防并发。PostgreSQL 还支持 partial unique index 方案:`CREATE UNIQUE INDEX ON review_task (skill_version_id) WHERE status = 'PENDING'`,更优雅地实现"PENDING 状态唯一"约束

### promotion_request
Expand Down Expand Up @@ -293,7 +300,7 @@
| 角色 code | 说明 | 典型权限 |
|-----------|------|---------|
| `SUPER_ADMIN` | 平台超管,拥有所有权限 | 全部 |
| `SKILL_ADMIN` | 技能治理:全局空间审核、提升审核、隐藏/撤回 | `review:approve`, `skill:manage`, `promotion:approve` |
| `SKILL_ADMIN` | 技能治理:全局空间审核、提升审核、隐藏/恢复、撤回已发布版本 | `review:approve`, `skill:manage`, `promotion:approve` |
| `USER_ADMIN` | 用户治理:准入审批、封禁/解封、角色分配(不可分配 SUPER_ADMIN) | `user:manage`, `user:approve` |
| `AUDITOR` | 审计只读:查看审计日志 | `audit:read` |

Expand Down Expand Up @@ -341,7 +348,7 @@

### skill_search_document

一个 skill 对应一条搜索文档,内容取 `latest_version_id` 对应版本
一个 skill 对应一条搜索文档,内容取“最新已发布版本”。实现上可由 `latest_version_id` 作为缓存指针承载,但其语义只能是 latest published pointer

| 字段 | 类型 | 说明 |
|------|------|------|
Expand Down
7 changes: 4 additions & 3 deletions docs/03-authentication-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ API Token 仍保留,但定位从“CLI 唯一认证方式”调整为“平台
| 平台角色 | 职责 |
|---------|------|
| `SUPER_ADMIN` | 全部权限,硬判定短路 |
| `SKILL_ADMIN` | 全局空间审核、提升审核、隐藏/撤回技能 |
| `SKILL_ADMIN` | 全局空间审核、提升审核、隐藏/恢复技能、撤回已发布版本 |
| `USER_ADMIN` | 准入审批、封禁/解封、角色分配(不可分配 SUPER_ADMIN) |
| `AUDITOR` | 审计日志只读 |

Expand All @@ -402,7 +402,8 @@ API Token 仍保留,但定位从“CLI 唯一认证方式”调整为“平台
| 审核团队空间技能 | `review:approve` | 该 namespace 的 ADMIN 或 OWNER |
| 审核全局空间技能 | `review:approve` | 持有 SKILL_ADMIN / SUPER_ADMIN |
| 审核提升申请 | `promotion:approve` | 持有 SKILL_ADMIN / SUPER_ADMIN |
| 隐藏/撤回技能 | `skill:manage` | 持有 SKILL_ADMIN / SUPER_ADMIN |
| 隐藏/恢复技能 | `skill:manage` | 持有 SKILL_ADMIN / SUPER_ADMIN |
| 撤回已发布版本(YANK) | `skill:manage` | 持有 SKILL_ADMIN / SUPER_ADMIN |
| 管理用户角色 | `user:manage` | 持有 USER_ADMIN / SUPER_ADMIN |
| 审批用户准入 | `user:approve` | 持有 USER_ADMIN / SUPER_ADMIN |
| 查看审计日志 | `audit:read` | 持有 AUDITOR / SUPER_ADMIN |
Expand Down Expand Up @@ -600,7 +601,7 @@ window.location.href = '/oauth2/authorization/github'
| `POST /api/v1/skills/{ns}/{slug}/star` | 已登录 | Session/Token |
| `POST /api/v1/skills/{ns}/{slug}/rating` | 已登录 | Session/Token |
| `POST .../versions/{ver}/submit-review` | namespace MEMBER 以上 | `namespace_member.role` |
| `POST .../versions/{ver}/withdraw-review` | 提交人本人 或 namespace ADMIN | `review_task.submitted_by` 或 `namespace_member.role` |
| `POST .../versions/{ver}/withdraw-review` | 提交人本人 | `review_task.submitted_by` |
| `PUT /api/v1/skills/{ns}/{slug}/tags/{tag}` | namespace ADMIN 以上 或 owner | `namespace_member.role` 或 `skill.owner_id` |
| `POST /api/v1/skills/{ns}/{slug}/archive` | namespace ADMIN 以上 或 owner | `namespace_member.role` 或 `skill.owner_id` |
| `DELETE .../versions/{ver}` | namespace ADMIN 以上 或 owner(仅 DRAFT/REJECTED) | `namespace_member.role` 或 `skill.owner_id` + `skill_version.status` |
Expand Down
13 changes: 7 additions & 6 deletions docs/04-search-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ WHERE (visibility = 'PUBLIC')

## 3 搜索文档表 skill_search_document

一个 skill 对应一条搜索文档,内容取 `latest_version_id` 对应版本。版本发布时自动更新该条文档
一个 skill 对应一条搜索文档,但文档内容的来源语义应严格收敛为“当前最新已发布版本”。实现上仍可由 `latest_version_id` 作为缓存指针承载,但它只允许指向 `PUBLISHED` 版本;搜索层不能再把它当作泛化的“当前版本”

| 字段 | 类型 | 说明 |
|------|------|------|
Expand All @@ -80,14 +80,15 @@ PostgreSQL 全文搜索索引:表增加 `search_vector tsvector` 生成列,
## 4 索引写入时机

以下场景触发搜索文档更新(upsert by skill_id):
- 审核通过(`PENDING_REVIEW → PUBLISHED`):`latest_version_id` 自动更新,用新版本内容更新搜索文档
- 审核通过(`PENDING_REVIEW → PUBLISHED`):重算“最新已发布版本”指针,并用该发布版本内容更新搜索文档
- 已发布版本被撤回(`PUBLISHED → YANKED`):重算“最新已发布版本”指针;若不存在任何已发布版本,则移除搜索文档
- 技能状态变更(隐藏/归档/恢复):更新搜索文档的 status 字段

## 5 搜索演进路线

### 5.1 一期数据建模约束

一期"每个 skill 一条搜索文档、内容永远取 latest_version_id"是有意的简化。这个模型在以下场景下会不够用:
一期每个 skill 一条搜索文档、内容永远取最新已发布版本”是有意的简化。当前实现仍使用 `latest_version_id` 作为持久化指针,但这里的语义已经收敛为 latest published pointer。这个模型在以下场景下会不够用:

- 版本级检索(搜索某个旧版本的内容)
- 自定义标签/通道检索(搜索 `@beta` 标签指向的版本内容)
Expand All @@ -96,7 +97,7 @@ PostgreSQL 全文搜索索引:表增加 `search_vector tsvector` 生成列,
这些场景不是简单换 provider 能解决的,需要改表结构和索引写入逻辑。

**一期搜索能力边界(产品限制):**
- 搜索只基于 `latest_version_id` 对应版本的内容
- 搜索只基于“最新已发布版本”的内容
- 不支持按 version 或 tag 搜索内容
- 搜索结果不区分 channel(`beta`、`stable` 等标签通道)
- 用户通过 tag 安装的技能内容可能与搜索结果展示的内容不一致(搜索展示 latest,安装的是 tag 指向的版本)
Expand All @@ -106,8 +107,8 @@ PostgreSQL 全文搜索索引:表增加 `search_vector tsvector` 生成列,

| 阶段 | 实现 | 索引粒度 | 切换方式 |
|------|------|---------|---------|
| 一期 | PostgreSQL Full-Text (tsvector + GIN) | 每 skill 一条(latest_version_id) | 默认 |
| 一点五期 | PostgreSQL Full-Text + 语义向量重排 | 每 skill 一条(latest_version_id) | 配置 `skillhub.search.semantic.enabled=true` |
| 一期 | PostgreSQL Full-Text (tsvector + GIN) | 每 skill 一条(latest published) | 默认 |
| 一点五期 | PostgreSQL Full-Text + 语义向量重排 | 每 skill 一条(latest published) | 配置 `skillhub.search.semantic.enabled=true` |
| 二期 | ES / OpenSearch | 每 skill_version 一条 + skill 聚合文档 | 配置 `search.provider=elasticsearch` |
| 三期 | 向量检索 | 每 skill_version 多条(chunk 级) | 配置 `search.provider=vector` |
| 四期 | 混合排序 | 关键词 + 向量混合 | 配置 `search.provider=hybrid` |
Expand Down
23 changes: 23 additions & 0 deletions docs/05-business-flows.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,26 @@
- 同步创建 `review_task(status=PENDING)`
- 审核通过后转为 `PUBLISHED`
- 审核拒绝后转为 `REJECTED`
- 撤回审核时删除 `PENDING review_task`,并将 `skill_version` 回退到 `DRAFT`
- 例外:提交人持有 `SUPER_ADMIN` 平台角色时,发布入口直接创建 `skill_version(status=PUBLISHED)`,跳过 `review_task` 创建,同时不再要求其必须是目标 namespace 成员
- 上述例外必须对 Web、`/api/v1/publish`、`/api/v1/publish` 保持一致
- 若重传新版本时发现旧的 `PENDING_REVIEW` 版本,旧版本会被自动降回 `DRAFT`,再创建新的待审版本

### 1.2 生命周期读模型

当前代码中的 skill 生命周期展示与操作判断,不再依赖旧的 `latestVersionStatus`、`viewingVersionStatus` 一类拼装字段,而统一基于以下 projection:

- `headlineVersion`:当前详情页/我的技能列表主展示版本
- `publishedVersion`:当前最新可公开分发的已发布版本
- `ownerPreviewVersion`:owner 或 namespace 管理者可见的待审核版本
- `resolutionMode`:`PUBLISHED` / `OWNER_PREVIEW` / `NONE`

业务规则:

- 公开入口只认 `publishedVersion`
- owner 进入详情页时,如果没有可用 `publishedVersion`,才允许 `headlineVersion = ownerPreviewVersion`
- 推广到全局、安装命令、公开下载都只能绑定到 `publishedVersion`
- `hidden` 是独立治理覆盖层,不属于 skill 生命周期状态机

### 对象存储写入策略

Expand Down Expand Up @@ -121,6 +139,11 @@ Web 端与 CLI 保持同一发布语义,只是在交互上可提供更明确
- 原团队 skill 可继续独立迭代
- 两者版本不自动同步,如需同步由 owner 手动操作

提升流程当前严格绑定已发布版本:

- promotion request 的 `source_version_id` 必须指向 `publishedVersion.id`
- 不允许直接提升 `ownerPreviewVersion`

## 3 下载流程

```
Expand Down
28 changes: 22 additions & 6 deletions docs/06-api-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
| GET | `/api/v1/skills/{namespace}/{slug}/versions/{version}` | 版本详情 |
| GET | `/api/v1/skills/{namespace}/{slug}/versions/{version}/files` | 文件清单 |
| GET | `/api/v1/skills/{namespace}/{slug}/versions/{version}/file?path=...` | 读取单个文件(query param 避免路径中 / 的解析问题) |
| GET | `/api/v1/skills/{namespace}/{slug}/download` | 下载默认安装版本(latest_version_id 指向的版本) |
| GET | `/api/v1/skills/{namespace}/{slug}/download` | 下载默认安装版本(最新已发布版本) |
| GET | `/api/v1/skills/{namespace}/{slug}/versions/{version}/download` | 下载指定版本包 |
| GET | `/api/v1/skills/{namespace}/{slug}/resolve` | 解析技能版本(支持 query param: `version`、`tag`、`hash`) |
| GET | `/api/v1/skills/{namespace}/{slug}/tags/{tagName}/download` | 按标签下载(解析标签指向的版本后下载) |
Expand Down Expand Up @@ -206,7 +206,7 @@ Public API 的可见性规则:

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/skills/{namespace}/{slug}/versions/{version}/submit-review` | 将 DRAFT 版本提交审核 |
| POST | `/api/v1/skills/{namespace}/{slug}/versions/{version}/submit-review` | 将 `DRAFT` 版本再次提交审核(当前主要用于撤回后重提) |
| POST | `/api/v1/skills/{namespace}/{slug}/versions/{version}/withdraw-review` | 撤回提审(PENDING_REVIEW → DRAFT,同时删除关联的 PENDING review_task) |
| GET | `/api/v1/skills/{namespace}/{slug}/versions/{version}/draft` | 查看草稿详情(owner 或 namespace ADMIN 以上) |

Expand All @@ -226,6 +226,20 @@ Public API 的可见性规则:
| POST | `/api/v1/skills/{namespace}/{slug}/unarchive` | 恢复归档(namespace ADMIN 或 owner) |
| DELETE | `/api/v1/skills/{namespace}/{slug}/versions/{version}` | 删除 DRAFT/REJECTED 版本 |

当前代码中的 skill 生命周期读模型不再依赖 `latestVersionStatus` / `viewingVersionStatus` 一类拼装字段,而统一使用以下 projection:

- `headlineVersion`:当前页面应展示的主版本
- `publishedVersion`:当前最新可分发的已发布版本
- `ownerPreviewVersion`:owner / namespace 管理者可见的待审核预览版本
- `resolutionMode`:`PUBLISHED` / `OWNER_PREVIEW` / `NONE`

其中:

- 公开详情、公开安装、公开搜索一律只认 `publishedVersion`
- owner 详情页在没有可展示发布版本时,才允许 `headlineVersion` 落到 `ownerPreviewVersion`
- 推广到全局一律使用 `publishedVersion.id`
- `hidden` 是独立治理覆盖层,不属于生命周期状态机

发布成功响应中的 `data` 至少包含以下字段:

- `skillId`
Expand All @@ -241,6 +255,7 @@ Public API 的可见性规则:
- 普通用户发布成功后,`status` 为 `PENDING_REVIEW`
- 持有 `SUPER_ADMIN` 的用户通过 Web、`/api/v1/publish`、`/api/v1/publish` 发布时,`status` 为 `PUBLISHED`,且不要求其必须是目标 namespace 成员
- 当前版本保持该审核策略,不再提供“全员直发”的运行模式
- 撤回审核不会删除版本记录,而是 `PENDING_REVIEW → DRAFT`

## 7.4 Token API(需登录)

Expand Down Expand Up @@ -336,14 +351,15 @@ Admin API 按最小权限拆分,不再统一要求 SUPER_ADMIN:
`latest` 自动跟随最新已发布版本,不可手动移动。

- `skill.latest_version_id`:每次审核通过自动更新,始终指向最新 PUBLISHED 版本
- `yank` 当前最新已发布版本时,需要同步重算 `latest_version_id` 指向下一个最新的 `PUBLISHED` 版本;若不存在则允许为 `null`
- `latest` 标签:系统保留,只读,自动与 `latest_version_id` 同步
- 自定义标签(如 `beta`、`stable-2026q1`):允许人工创建和移动,用于固定安装通道

| 场景 | 使用字段 | 说明 |
|------|---------|------|
| 搜索索引内容 | `latest_version_id` | 搜索文档取最新已发布版本内容 |
| `/download`(不带版本号) | `latest_version_id` | 下载最新已发布版本 |
| CLI `install @team/skill` | `latest_version_id` | 等同于 `@latest` |
| 搜索索引内容 | `publishedVersion` / `latest_version_id` | 外部协议仍叫 latest,但内部语义必须等价于最新已发布版本 |
| `/download`(不带版本号) | `publishedVersion` / `latest_version_id` | 下载最新已发布版本 |
| CLI `install @team/skill` | `publishedVersion` / `latest_version_id` | 等同于 `@latest` |
| CLI `install @team/skill@beta` | `skill_tag` 查询 | 自定义标签指向的版本 |

## 7.9 Resolve 接口说明
Expand All @@ -361,7 +377,7 @@ Admin API 按最小权限拆分,不再统一要求 SUPER_ADMIN:
2. 仅传 `version`:精确匹配版本号
3. 仅传 `tag`:查询 `skill_tag` 表获取 `target_version_id`
4. 仅传 `hash`:遍历已发布版本,比对 fingerprint
5. 均不传:返回 `latest_version_id` 指向的版本
5. 均不传:返回最新已发布版本;实现上可由 `latest_version_id` 或等价 published projection 解析

响应:

Expand Down
2 changes: 1 addition & 1 deletion docs/07-skill-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ CLI 安装后在本地写入 `.astron/metadata.json`:
skillhub 自有 CLI 支持完整 namespace 坐标:

```
install @team/my-skill → 最新已发布版本(latest_version_id)
install @team/my-skill → 最新已发布版本(实现上通常由 `latest_version_id` / published pointer 解析
install @team/my-skill@1.2.0 → 精确版本
install @team/my-skill@latest → 等同于不带版本号(系统保留标签,只读)
install @team/my-skill@beta → beta 标签(自定义标签)
Expand Down
Loading
Loading