mirror of
https://git.openapi.site/https://github.com/desirecore/market.git
synced 2026-09-05 16:13:53 +08:00
feat: 市场支持团队条目类型并上架合同审查团队 (#110)
## 变更 / What
市场此前只有 `agents` 与 `skills` 两类条目。本 PR 加入**团队(teams)**条目类型,并上架第一条真实团队
listing。
The market supported only `agents` and `skills`. This PR adds a
**teams** entry type and lists the first real team.
## 一、支持团队条目类型
「支持一种新条目类型」实际涉及 4 组共 11 个文件,比表面看到的多:
**客户端契约快照**
- 新增 `schemas/market-team-entry.client.schema.json`,用 esbuild 打包客户端
`packages/schemas/src/market.ts` 后导出生成。用同样方法重新生成
`market-agent-entry.client.schema.json` 验证过管线——字节完全一致(含属性顺序),确认不是手工誊抄。
**Sidecar schema**
- `identity.kind` 枚举加 `team`;新增 `$defs.teamSpec`;接入 `spec.oneOf` 与
kind→spec 派发
**校验器(工作量主要在这里)**
- `scripts/catalog/validate_catalog_metadata.py`:`load_legacy`
原先硬编码只认两个根目录。抽出 `CATALOG_ROOTS` 常量同时驱动允许的父目录集合与错误文案;按 kind 分派客户端 schema
校验;`teams` 进 stats 与 `--require-complete` 覆盖统计;把**严格 provenance
比对**与「可安装 pointer 必须自带不可变 ref」两道门禁扩展到团队
- `scripts/i18n/validate-i18n.py`:**它独立重算计数并逐个校验 `entry.json`**,不接团队会漏校
- `.github/workflows/i18n-validate.yml`:变更检测的 grep 不含 `teams/`——**一个只改
teams 的 PR 会报「无 i18n 相关变更,跳过校验」然后零校验通过**
- 测试:`test_validate_catalog_metadata.py` 29→47,`test_validate_i18n.py`
9→17
**顺带修正一条本就不对的规则**:`icon` 此前被要求「每个 entry.json 都必须有非空内联 SVG」,但运行时 schema 里
`marketAgentSchema` 与 `marketTeamSchema` **都没有 `icon` 字段**(只有 skill
有)。也就是说这条规则对 Agent pointer 同样在强加死重量,只因本仓库暂无 agent pointer 条目而未暴露。改为
`ICON_RENDERED_KINDS = {"skill"}`,agent/team 声明 icon
时给**警告**而非错误,文案说明「下一个维护者会以为改它能改变卡片」。
## 二、上架合同审查团队
`teams/contract-review-team/`(`entry.json` + sidecar)。
**团队条目是 fork 指针卡,不分发正文**:市场只存展示元数据 + git-only `source`,真实定义(`team.json`
/ `members.json` / `shared/`)在 `source.repoUrl` 指向的仓库里。安装即
`forkTeam`,更新即 `git pull`——组合固定,因此**没有** `installPolicy` /
`updatePolicy`。
| 字段 | 值 | 依据 |
|---|---|---|
| `source.ref` | `73cd87a9901cc548871927e9d5dbec8e4cc6c2b1` | v0.1.1
的**完整 SHA**。tag 不是可复现 pin,validator 有测试专门拒绝 |
| `latestVersion` | `0.1.1` | 上游真实 tag,与 `release.version` 交叉校验 |
| `license` | `MIT` | 上游仓库真有 LICENSE,已在 pinned ref 的快照中复验 |
| `redistribution` | `source-pointer-only` |
市场从不打包团队正文,只给指针——这是交付形态,与许可证宽松与否无关 |
| `requiredClientVersion` | `10.0.137` | 六个成员都声明了 `FileDigest`
内置工具,它随该版本发布 |
| `memberCount` / `memberNames` | 6 / 5 名 | schema 规定前者**含**组长、后者**不含**
|
| `availability` | `listing-only` | 见下 |
**`availability` 为什么不是 `installable`**:四项证据满足两项(不可变 pin ✓、已知 license
✓),缺的 `reviewedAt` 与 `governance.compliance`
本质是**一次尚未发生的治理审查**——需要具名方在具体日期针对这个确切 ref 审过许可合规、第三方内容与商标使用。没发生的事不能写进目录。
补充一个事实:本仓库**零个 sidecar 有 `compliance` 块,29 个 pointer 条目全是
listing-only**,`installable` 路径从未在任何真实条目上走过。这不阻止安装——fork 由 `source` 驱动。
**`license.evidencePath` 的基准此前是未定义的**:schema 只说
`safeRelativePath`,没规定相对谁。仓库里仅有的两个先例(`guizang-ppt`、`presentation-forge`)都是
vendored 技能,LICENSE 物理上在条目目录里。按那个读法,pointer 条目写 `evidencePath`
断言的是市场目录下有该文件——对 pointer 永远不成立。新增 `license-evidence` 规则按条目形态分派:vendored
要求文件存在(error),pointer 要求条目已 pin(warning),两种读法写进 README。
## 校验 / Validation
```
test_validate_catalog_metadata.py 47 tests OK
test_validate_i18n.py 17 tests OK
test_collection_generator.py exit 0
validate_catalog_metadata.py --require-complete
0 error, 116 warning (agents=1, teams=1, publishableSkills=62, sidecars=64)
validate-i18n.py / --online 0 error, 116 warning
translate.py --check exit 0
gen-collection-children.py --check exit 0
```
116 warnings 即加入团队之前的基线——**本条 listing 贡献 0 个警告**。
真实条目上的反向控制(跑在 rsync 副本上,仓库保持干净):
```
source.kind=zip → team-entry-schema (error)
install/updatePolicy 出现 → team-entry-schema (error)
requiredClientVersion 漂移 → legacy-consistency (error)
memberCount 漂移 → legacy-consistency (error)
provenance ref 漂移 → legacy-consistency (error)
可安装但无不可变 ref → installable-evidence (error)
evidencePath 在未 pin 的 pointer → license-evidence (warning)
```
另用**客户端真实校验器**(`parseMarketTeamEntry`,不是快照)验证条目通过,且多写一个字段会被拒。
## 公开信息边界 / Public information boundary
全树扫描无新增命中。团队内容使用「某某科技(北京)有限公司」这类标准中文占位。
---------
Co-authored-by: yi-ge <mizan57533@gmail.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://desirecore.com/schemas/market/catalog-metadata.v1.schema.json",
|
||||
"title": "DesireCore Market catalog metadata sidecar v1",
|
||||
"description": "Strict source-owned metadata stored next to one legacy Market Agent or Skill. The trusted catalog source ID, catalog commit, catalog path and catalog trust are injected by DesireCore and are intentionally prohibited here.",
|
||||
"description": "Strict source-owned metadata stored next to one legacy Market Agent, Team or Skill. The trusted catalog source ID, catalog commit, catalog path and catalog trust are injected by DesireCore and are intentionally prohibited here.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"schemaVersion",
|
||||
@@ -19,7 +19,7 @@
|
||||
"$schema": {
|
||||
"type": "string",
|
||||
"const": "../../schemas/catalog-metadata.v1.schema.json",
|
||||
"description": "Relative schema reference from agents/<id>/ or skills/<id>/."
|
||||
"description": "Relative schema reference from agents/<id>/, teams/<id>/ or skills/<id>/."
|
||||
},
|
||||
"schemaVersion": {
|
||||
"type": "integer",
|
||||
@@ -50,6 +50,7 @@
|
||||
"spec": {
|
||||
"oneOf": [
|
||||
{ "$ref": "#/$defs/agentSpec" },
|
||||
{ "$ref": "#/$defs/teamSpec" },
|
||||
{ "$ref": "#/$defs/skillSpec" }
|
||||
]
|
||||
}
|
||||
@@ -71,6 +72,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"identity": {
|
||||
"properties": { "kind": { "const": "team" } },
|
||||
"required": ["kind"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"spec": { "$ref": "#/$defs/teamSpec" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
@@ -158,7 +174,7 @@
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"enum": ["agent", "skill"]
|
||||
"enum": ["agent", "team", "skill"]
|
||||
},
|
||||
"id": {
|
||||
"$ref": "#/$defs/slug"
|
||||
@@ -521,6 +537,38 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"teamSpec": {
|
||||
"type": "object",
|
||||
"description": "Team facts owned by the listing. A Team is always a fork pointer: installation forks the team repository and updates are a git pull, so a Team never declares installPolicy or updatePolicy. Every field here is presentation metadata that may drift from the forked repository; membership, permissions and capability resolution must read the forked team.json and members.json instead.",
|
||||
"required": ["kind"],
|
||||
"properties": {
|
||||
"kind": { "const": "team" },
|
||||
"supervisorName": { "type": "string", "minLength": 1, "maxLength": 200 },
|
||||
"supervisorAgentId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 160,
|
||||
"pattern": "^[A-Za-z0-9_-]+$"
|
||||
},
|
||||
"memberCount": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "Total member count including the supervisor."
|
||||
},
|
||||
"memberNames": {
|
||||
"type": "array",
|
||||
"maxItems": 32,
|
||||
"items": { "type": "string", "minLength": 1, "maxLength": 200 }
|
||||
},
|
||||
"requiredSkills": {
|
||||
"type": "array",
|
||||
"maxItems": 128,
|
||||
"uniqueItems": true,
|
||||
"items": { "type": "string", "minLength": 1, "maxLength": 160 }
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"collectionChild": {
|
||||
"type": "object",
|
||||
"required": ["identity", "path", "presentation", "release"],
|
||||
|
||||
219
schemas/market-team-entry.client.schema.json
Normal file
219
schemas/market-team-entry.client.schema.json
Normal file
@@ -0,0 +1,219 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"stewardship",
|
||||
"license",
|
||||
"redistribution",
|
||||
"source"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "团队条目唯一标识符,采用与 team.json#id 相同的团队 slug 规则(不允许首尾连字符),保证市场 ID 天然是一个合法团队目录名。",
|
||||
"pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "默认显示名称(源 locale),本地化变体见 i18n"
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"pattern": "^[a-z0-9-]+$",
|
||||
"description": "市场项目分类(slug 格式),由仓库 categories.json 定义"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "标签列表,用于搜索和分类"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "市场列表展示的内联 SVG 图标。**只有 Skill 卡片渲染它**——marketAgentSchema 与 marketTeamSchema 都没有 icon 字段,Agent 与团队卡片一律走 avatar,条目声明了 icon 也不会显示。Pointer 技能将其随离线元数据返回。"
|
||||
},
|
||||
"latestVersion": {
|
||||
"type": "string",
|
||||
"description": "市场登记的最新版本(semver)",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
"i18n": {
|
||||
"type": "object",
|
||||
"description": "按 locale 提供的本地化展示元数据(仅 name/shortDesc,轻量)",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "该 locale 下的显示名称"
|
||||
},
|
||||
"shortDesc": {
|
||||
"type": "string",
|
||||
"description": "该 locale 下的简短描述"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"maintainer": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"verified"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "维护者名称,如 \"DesireCore Official\""
|
||||
},
|
||||
"verified": {
|
||||
"type": "boolean",
|
||||
"description": "是否官方认证维护者,认证后显示蓝色勾选标记"
|
||||
},
|
||||
"account": {
|
||||
"type": "string",
|
||||
"description": "维护方账号标识,如 GitHub 组织/用户名 \"desirecore\""
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "维护方主页或仓库 URL"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"stewardship": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"official",
|
||||
"partner",
|
||||
"community",
|
||||
"pointer"
|
||||
],
|
||||
"description": "维护方分类:official(官方)/ partner(合作伙伴官方)/ community(社区已收录)/ pointer(仅指针,不分发内容)"
|
||||
},
|
||||
"license": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "许可证:SPDX id(MIT/Apache-2.0/MIT-0…)或特殊值 not-declared / source-available / packaged-distribution"
|
||||
},
|
||||
"redistribution": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"allowed",
|
||||
"source-pointer-only",
|
||||
"verify-package-terms"
|
||||
],
|
||||
"description": "再分发策略:allowed(可分发)/ source-pointer-only(仅指针,安装时拉取)/ verify-package-terms(需核验条款)"
|
||||
},
|
||||
"source": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"repoUrl"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"git"
|
||||
],
|
||||
"description": "团队只支持 git 源:安装是 fork 团队仓库、更新是 git pull,zip / web 无法表达这两个动作。"
|
||||
},
|
||||
"repoUrl": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "团队 git 仓库的 HTTPS 克隆地址,如 https://github.com/desirecore/team-contract-review.git"
|
||||
},
|
||||
"repoBranch": {
|
||||
"type": "string",
|
||||
"description": "团队仓库分支名称,默认 main。安装时会真的按这个分支克隆;必须是合法分支名(不带 refs/ 前缀、不以 - 开头),否则安装失败关闭而不是静默改装默认分支。",
|
||||
"default": "main"
|
||||
},
|
||||
"ref": {
|
||||
"type": "string",
|
||||
"description": "锁定的 commit SHA / tag,用于可复现安装;为空表示取分支 HEAD。只接受**完整 40 位 SHA-1** 或标签名:缩写 oid 会随仓库增长变歧义、也远比完整 SHA 容易被构造碰撞,而降级 git 后端根本不解析它。安装时会按该 ref 克隆并读回真实 HEAD 逐字复核,不一致就中止整个安装——一个不被兑现的锁比没有锁更危险,因为它会被信任。"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"description": "团队 fork 源。**这是团队条目的核心**:安装端点用 repoUrl + repoBranch 调用 forkTeam,更新用同一仓库 git pull。"
|
||||
},
|
||||
"requiredClientVersion": {
|
||||
"type": "string",
|
||||
"description": "此条目要求的最低客户端版本(semver),低于此版本时提示升级",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
"avatar": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"t",
|
||||
"bg"
|
||||
],
|
||||
"properties": {
|
||||
"t": {
|
||||
"type": "string",
|
||||
"description": "头像显示的文字(通常为一个汉字)",
|
||||
"maxLength": 2
|
||||
},
|
||||
"bg": {
|
||||
"type": "string",
|
||||
"description": "头像背景 CSS 渐变值,如 linear-gradient(135deg, #007AFF, #005ECB)"
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"description": "图片头像:市场仓库中该条目目录内的位图(如 \"assets/avatar.webp\")。存在且文件可读时优先于 t + bg 渲染,否则自动回落——所以 t 与 bg 仍为必填,它们是确定的回落形态。仅支持 PNG / JPEG / WebP。注意:这是**市场展示**元数据,与安装后 agent.json 的 avatar.image 是两套,安装时不迁移。",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "相对于该市场条目目录的图片路径。禁止绝对路径和 .. 路径穿越",
|
||||
"minLength": 5,
|
||||
"maxLength": 240,
|
||||
"pattern": "^(?![\\\\/])(?![A-Za-z]:)(?!.*(?:^|[\\\\/])\\.\\.(?:[\\\\/]|$)).+\\.(?:png|jpe?g|webp)$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"supervisorName": {
|
||||
"type": "string",
|
||||
"description": "组长(supervisor)Agent 的显示名称,仅用于卡片与详情页展示;真实组长以团队仓库 team.json#supervisor 为准。"
|
||||
},
|
||||
"supervisorAgentId": {
|
||||
"type": "string",
|
||||
"description": "组长 Agent ID(展示与去重用)。只是发布者声明,不作为安装期白名单依据。",
|
||||
"pattern": "^[A-Za-z0-9_-]+$"
|
||||
},
|
||||
"memberCount": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "团队成员总数(**含组长**),用于卡片上\"N 名成员\"的展示;不参与任何安装校验。"
|
||||
},
|
||||
"memberNames": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxItems": 32,
|
||||
"description": "成员显示名预览列表(不含组长),用于卡片上的成员缩略展示;不保证与仓库成员完全一致。"
|
||||
},
|
||||
"requiredSkills": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxItems": 128,
|
||||
"uniqueItems": true,
|
||||
"description": "团队声明的必需 Skill ID,用于安装前向用户提示前置条件;真实校验由安装后的团队 capability audit 执行。"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"$comment": "Generated from desirecore/desirecore packages/schemas/src/market.ts#marketTeamEntrySchema at commit 947d9f32c6c81ebe5ece78f6295155814c888a22, source blob d6b361d149773e7384babe3011d86d25ded4e493. Keep the complete client contract; regenerate from that export instead of editing fields manually."
|
||||
}
|
||||
Reference in New Issue
Block a user