diff --git a/README.md b/README.md index 5a639b7..dac8ee9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DesireCore Registry -DesireCore 官方注册表仓库,包含可安装的应用、MCP 服务、HTTP API 服务,以及只用于发现和合规披露的第三方外部集成。 +DesireCore 官方注册表仓库,包含应用、插件、MCP 服务、HTTP API 服务,以及只用于发现和合规披露的第三方外部集成。可否安装由条目的来源和治理证据决定。 DesireCore 客户端启动时会克隆此仓库,并定期同步更新。用户在应用商店中看到的所有条目均来自此仓库。 @@ -9,7 +9,7 @@ DesireCore 客户端启动时会克隆此仓库,并定期同步更新。用户 ```text . ├── README.md # 本文件 -├── SCHEMA_VERSION # 数据格式版本号(当前 4.1.0) +├── SCHEMA_VERSION # 数据格式版本号(当前 4.2.0) ├── manifest.json # 仓库元数据(版本、统计、维护者) ├── package.json # Registry 校验入口 ├── schemas/ # legacy entry、仓库 manifest 与 catalog sidecar Schema @@ -44,10 +44,30 @@ DesireCore 客户端启动时会克隆此仓库,并定期同步更新。用户 原生应用必须声明宿主机 Node 版本、`docker: false`、精确管理端口、最低客户端版本、不可变 Release URL/ref/SHA-256 和审核记录。安装说明同时覆盖安装、升级、验证及卸载,不随 DesireCore 启停,不默认启用控制或隧道。条目禁止 `connection`、`exposes`、`endpoint`、工具数等内部服务配置;`allowAgentBinding` 为 false。兼容来源字段只写在 manifest.source 与 sidecar.provenance.content,二者必须一致;sidecar 不写运行时 governance.source。 -安装要求支持 native-app 的客户端(Control 条目最低 10.0.169)及核心应用安装技能 >=1.4.0。目录同步不能升级桌面客户端或技能;旧版看不到条目或提示升级时,不得修改类型绕过兼容检查。新制品已发布不代表新版桌面客户端已发布,也不代表用户已完成 ChatGPT 联调。 +安装要求支持 native-app 的客户端(Control 条目最低 10.0.170)及核心应用安装技能 >=1.5.0。目录同步不能升级桌面客户端或技能;旧版看不到条目或提示升级时,不得修改类型绕过兼容检查。新制品已发布不代表新版桌面客户端已发布,也不代表用户已完成 ChatGPT 联调。 English: Native applications are independently installed host processes. Their outward MCP protocol does not make them internal service packages. Pinned artifacts, explicit client/skill compatibility and App lifecycle receipts are required; catalog publication alone cannot upgrade an older client. The Control listing is the sole application entry, not a Docker or MCP duplicate. +## 应用与插件(4.2) + +插件仍是统一 App 产品,`productKind: plugin` 表示它向宿主提供能力;`productKind: application` 表示它交付独立业务。`type` 只区分交付形态:Docker、宿主进程或纯声明 `artifact`。插件使用同一来源内产品 ID、版本、安装材料与安装登记,不重复创建 MCP 服务或第二份安装事实。 + +应用类型必须明确声明 `entrypoints`。普通 native/Docker 应用可省略 `productKind`,受信读取时归一为 `application`,并至少有一个入口;插件必须明确声明 `productKind: plugin`,无 UI 插件可以声明空数组,但必须通过 `extension.contributes` 提供至少一项实际宿主贡献。新 `artifact` 仍要求明确产品归属,使用 `install.method: artifact`、`docker: false`、空端口与声明式 runtime,不能伪装成 Docker 或宿主进程。 + +`extension` 区分提供与消费: + +- `contributes` 指明本产品提供的贡献 ID、贡献点、契约版本及包内 `definitionRef.path/sha256`。当前贡献点限于 `document.parsers`、`session.panels`、`session.widgets`、`session.actions`、`tools`、`skills`。 +- `requires` 指明必需或可选的直接能力依赖,使用精确 `sourceId/appId/contributionId/contractVersion`,可加版本范围。普通应用可以只声明依赖;插件即使有依赖也必须提供贡献。两项同时为空无实际关系,校验拒绝。 +- 产品引用不携带安装实例 ID、凭据、启用状态或权限。客户端运行时解析精确安装实例,由目标宿主维护接入、配置和撤销;目录声明不会自动安装、启用或授权依赖。 + +依赖中的 `extension.requires[].sourceId` 是产品引用;条目自身的 `sourceId` 与 `hasInstall` 仍只由受信客户端注入。贡献材料必须使用规范 POSIX 包内相对路径并固定摘要;实际获取与消费仍由客户端核验字节与文件归属。 + +4.2 对现有 9 个应用在 sidecar 中声明 `application` 身份,legacy manifest 省略旧客户端不认识的 `productKind`,并提供读取安装/使用材料后委派 Agent 的真实维护入口。现有目录没有可引用的用户 UI 服务 ID,不猜测 API endpoint 或任意本机端口为 Web 页面;`legacy-port:`/`native-port:` 仅供客户端兼容适配层生成,发布者不能自行声明。原有版本、来源、许可证、审核及 `listing-only` 事实保留,未完成治理证据的条目不会因为格式迁移变为可安装。当前官方条目数仍为 23,没有新增或默认启用插件。 + +Catalog sidecar 的 App spec 同步 `productKind`、可选 `extension` 和入口类型摘要;与 manifest 的受信归一结果必须一致。4.2 Schema 的固定 LF SHA-256 为 `ea63d6305ae17994e595aea009a131c41300d2b7fdb07ec83cc2c19c37b32038`。旧普通应用 manifest 保留已发布 10.0.177 可读取的字段;这不表示旧客户端支持 4.2 插件、artifact 或受新 authored 契约校验的外部集成。新能力上线前须确保目标客户端固定了此摘要并支持 4.2,目录发布不会升级客户端。 + +English: Plugins are App products with host contributions, sharing the canonical catalog and installation identity. Deployment type does not decide product kind. Applications may provide contributions or consume direct dependencies; a plugin must provide a real contribution. Artifact packages contain declarative materials without ports or processes. Dependency product references do not grant installation, activation or permission. Existing listings retain their source and compliance evidence; schema migration does not make listing-only content installable or publish a client release. + ## 条目格式 每个条目是 `entries//` 下的一个目录,目录名即条目 ID。 @@ -62,7 +82,7 @@ English: Native applications are independently installed host processes. Their o |------|------|------|------| | `id` | string | ✅ | 唯一标识,与目录名一致 | | `name` | string | ✅ | 显示名称 | -| `type` | string | ✅ | 条目类型:`native-app` / `docker-app` / `mcp` / `http-api` / `external-integration` | +| `type` | string | ✅ | 条目类型:`native-app` / `docker-app` / `artifact` / `mcp` / `http-api` / `external-integration` | | `version` | string | ✅ | 上游原始版本字符串;可为 SemVer、CalVer 或不透明版本 | | `description` | string | ✅ | 一行功能摘要 | | `author` | string | | 作者或组织 | @@ -176,7 +196,7 @@ Sidecar 只允许声明条目自身事实: - `provenance.content`:内容上游及其不可变 ref/digest;Catalog 仓库来源由客户端受信上下文注入。 - `governance`:listing-only/installable、维护者、许可证、品牌和审核证据。 - `compatibility`:平台必须显式区分 `known` / `all` / `unknown`。 -- `spec`:App 分类、入口类型与放置策略,或 Service 协议、鉴权类型、能力与工具数;安装命令、endpoint 和凭据不进入统一目录元数据。 +- `spec`:App 的产品归属、宿主贡献/依赖、分类、入口类型与放置策略,或 Service 协议、鉴权类型、能力与工具数;安装命令、endpoint 和凭据不进入统一目录元数据。 示例(证据不足的条目必须失败关闭为 listing-only): @@ -231,6 +251,8 @@ cat > entries/my-app/manifest.json << 'EOF' "id": "my-app", "name": "My App", "type": "docker-app", + "productKind": "application", + "entrypoints": [{ "id": "manage", "name": "委派 Agent 打开与维护 My App", "kind": "agent" }], "version": "1.0.0", "author": "Author", "description": "一行功能描述", @@ -349,7 +371,7 @@ external integration 不是开放的自助条目类型。新增 ID、URL、扩 1. 编辑 `entries//manifest.json` 中的字段 2. 如有安装/使用流程变更,同步更新 `install.md` / `usage.md`;external integration 不适用 -3. **务必更新 `version` 字段**(客户端通过版本号判断是否有更新) +3. 上游已发布新版本时更新 `version` 字段;它保留上游版本事实,仅修改目录元数据或格式不能伪造新产品版本 4. 新增或删除条目时同步更新根 `manifest.json#stats` 5. 执行 `npm ci && npm test` 6. 提交并创建 PR @@ -368,7 +390,9 @@ external integration 不是开放的自助条目类型。新增 ID、URL、扩 | 1.0.0 | 初始格式 — 单文件 JSON 数组 | | 2.0.0 | 分散式目录 — apps/mcp/services 三目录,每个条目 `/index.json` | | 3.0.0 | 统一 entries/ 目录,manifest.json + install.md + usage.md | -| 4.0.0 | **当前** — Draft-07 严格判别 Schema、仓库校验和 listing-only external integration | +| 4.0.0 | Draft-07 严格判别 Schema、仓库校验和 listing-only external integration | +| 4.1.0 | 原生应用、不可变制品与 App 生命周期边界 | +| 4.2.0 | **当前** — 应用/插件身份、纯声明 artifact、宿主贡献与直接能力依赖 | ## 校验 @@ -377,7 +401,7 @@ npm ci npm test ``` -校验包含 JSON Schema、目录与 ID、全局唯一性、根版本、统计、来源注入字段、external 单文件布局、固定 Kimi ID、完整官方 URL、扩展 ID、组件/权限/准入集合、真实日历日期和不可变供应链审核记录。 +校验包含 JSON Schema、目录与 ID、全局唯一性、根版本、统计、来源注入字段、贡献/入口 ID 唯一性、应用与插件关系、artifact 无进程约束、sidecar 一致性、external 单文件布局、固定 Kimi ID、完整官方 URL、扩展 ID、组件/权限/准入集合、真实日历日期和不可变供应链审核记录。条件块使用 Draft-07 的父约束补充语义;Ajv 保留严格关键字检查,关闭 `strictTypes/strictRequired` 的额外作者风格限制,不降低数据值校验。 Schema v4 同时保留 `catalog-metadata.v1.json` 统一目录 sidecar、strict validator 与 legacy fallback。 diff --git a/SCHEMA_VERSION b/SCHEMA_VERSION index ee74734..6aba2b2 100644 --- a/SCHEMA_VERSION +++ b/SCHEMA_VERSION @@ -1 +1 @@ -4.1.0 +4.2.0 diff --git a/entries/anythingllm/catalog-metadata.v1.json b/entries/anythingllm/catalog-metadata.v1.json index 5ef1e8f..3f04cb5 100644 --- a/entries/anythingllm/catalog-metadata.v1.json +++ b/entries/anythingllm/catalog-metadata.v1.json @@ -76,6 +76,10 @@ }, "spec": { "kind": "app", - "category": "tools" + "category": "tools", + "productKind": "application", + "entrypointKinds": [ + "agent" + ] } } diff --git a/entries/anythingllm/manifest.json b/entries/anythingllm/manifest.json index 7659d17..8632196 100644 --- a/entries/anythingllm/manifest.json +++ b/entries/anythingllm/manifest.json @@ -43,5 +43,12 @@ "path": "/api/v1", "name": "AnythingLLM API" } + ], + "entrypoints": [ + { + "id": "manage", + "name": "委派 Agent 打开与维护 AnythingLLM", + "kind": "agent" + } ] } diff --git a/entries/coze/catalog-metadata.v1.json b/entries/coze/catalog-metadata.v1.json index 775abd7..95c2827 100644 --- a/entries/coze/catalog-metadata.v1.json +++ b/entries/coze/catalog-metadata.v1.json @@ -75,6 +75,10 @@ }, "spec": { "kind": "app", - "category": "ai-platform" + "category": "ai-platform", + "productKind": "application", + "entrypointKinds": [ + "agent" + ] } } diff --git a/entries/coze/manifest.json b/entries/coze/manifest.json index 989a16b..33e2c17 100644 --- a/entries/coze/manifest.json +++ b/entries/coze/manifest.json @@ -43,5 +43,12 @@ "path": "/api", "name": "Coze API" } + ], + "entrypoints": [ + { + "id": "manage", + "name": "委派 Agent 打开与维护 Coze", + "kind": "agent" + } ] } diff --git a/entries/desirecore-control/catalog-metadata.v1.json b/entries/desirecore-control/catalog-metadata.v1.json index 4f2665c..041209b 100644 --- a/entries/desirecore-control/catalog-metadata.v1.json +++ b/entries/desirecore-control/catalog-metadata.v1.json @@ -111,6 +111,10 @@ "showInMyApps": true, "allowNavPin": true, "allowAgentBinding": false - } + }, + "productKind": "application", + "entrypointKinds": [ + "agent" + ] } } diff --git a/entries/desirecore-control/manifest.json b/entries/desirecore-control/manifest.json index fa59644..6cb99d3 100644 --- a/entries/desirecore-control/manifest.json +++ b/entries/desirecore-control/manifest.json @@ -28,5 +28,12 @@ "listingMaintainer": { "name": "DesireCore Registry maintainers", "url": "https://github.com/desirecore/registry" }, "upstreamMaintainer": { "name": "desirecore-agent", "url": "https://github.com/desirecore-agent/desirecore-cdp-mcp" }, "branding": { "relationship": "independent-listing", "nameUsage": "nominative", "logoStatus": "not-used" }, - "compliance": { "licenseEvidencePath": "LICENSE", "noticePath": "NOTICE", "reviewedRef": "38184fbb01f82d4dd850b67366cdac048a09474b", "reviewedAt": "2026-09-18", "reviewedBy": "release-and-package-validation (see review.md)", "upstreamEndorsed": false } + "compliance": { "licenseEvidencePath": "LICENSE", "noticePath": "NOTICE", "reviewedRef": "38184fbb01f82d4dd850b67366cdac048a09474b", "reviewedAt": "2026-09-18", "reviewedBy": "release-and-package-validation (see review.md)", "upstreamEndorsed": false }, + "entrypoints": [ + { + "id": "manage", + "name": "委派 Agent 打开与维护 DesireCore Control", + "kind": "agent" + } + ] } diff --git a/entries/dify/catalog-metadata.v1.json b/entries/dify/catalog-metadata.v1.json index 9009361..175904a 100644 --- a/entries/dify/catalog-metadata.v1.json +++ b/entries/dify/catalog-metadata.v1.json @@ -77,6 +77,10 @@ }, "spec": { "kind": "app", - "category": "ai-platform" + "category": "ai-platform", + "productKind": "application", + "entrypointKinds": [ + "agent" + ] } } diff --git a/entries/dify/manifest.json b/entries/dify/manifest.json index eb27ceb..ab86818 100644 --- a/entries/dify/manifest.json +++ b/entries/dify/manifest.json @@ -44,5 +44,12 @@ "path": "/v1", "name": "Dify API" } + ], + "entrypoints": [ + { + "id": "manage", + "name": "委派 Agent 打开与维护 Dify", + "kind": "agent" + } ] } diff --git a/entries/lobechat/catalog-metadata.v1.json b/entries/lobechat/catalog-metadata.v1.json index d93fc18..30357de 100644 --- a/entries/lobechat/catalog-metadata.v1.json +++ b/entries/lobechat/catalog-metadata.v1.json @@ -77,6 +77,10 @@ }, "spec": { "kind": "app", - "category": "chat" + "category": "chat", + "productKind": "application", + "entrypointKinds": [ + "agent" + ] } } diff --git a/entries/lobechat/manifest.json b/entries/lobechat/manifest.json index 417ec92..64fd41e 100644 --- a/entries/lobechat/manifest.json +++ b/entries/lobechat/manifest.json @@ -44,5 +44,12 @@ "path": "/api", "name": "LobeChat API" } + ], + "entrypoints": [ + { + "id": "manage", + "name": "委派 Agent 打开与维护 LobeChat", + "kind": "agent" + } ] } diff --git a/entries/n8n/catalog-metadata.v1.json b/entries/n8n/catalog-metadata.v1.json index ac1635b..d33ba37 100644 --- a/entries/n8n/catalog-metadata.v1.json +++ b/entries/n8n/catalog-metadata.v1.json @@ -76,6 +76,10 @@ }, "spec": { "kind": "app", - "category": "workflow" + "category": "workflow", + "productKind": "application", + "entrypointKinds": [ + "agent" + ] } } diff --git a/entries/n8n/manifest.json b/entries/n8n/manifest.json index b55d596..7268737 100644 --- a/entries/n8n/manifest.json +++ b/entries/n8n/manifest.json @@ -43,5 +43,12 @@ "path": "/api/v1", "name": "n8n REST API" } + ], + "entrypoints": [ + { + "id": "manage", + "name": "委派 Agent 打开与维护 n8n", + "kind": "agent" + } ] } diff --git a/entries/open-webui/catalog-metadata.v1.json b/entries/open-webui/catalog-metadata.v1.json index c1d294a..0cfb68c 100644 --- a/entries/open-webui/catalog-metadata.v1.json +++ b/entries/open-webui/catalog-metadata.v1.json @@ -76,6 +76,10 @@ }, "spec": { "kind": "app", - "category": "chat" + "category": "chat", + "productKind": "application", + "entrypointKinds": [ + "agent" + ] } } diff --git a/entries/open-webui/manifest.json b/entries/open-webui/manifest.json index 71af1f4..b64461d 100644 --- a/entries/open-webui/manifest.json +++ b/entries/open-webui/manifest.json @@ -43,5 +43,12 @@ "path": "/api", "name": "Open WebUI API" } + ], + "entrypoints": [ + { + "id": "manage", + "name": "委派 Agent 打开与维护 Open WebUI", + "kind": "agent" + } ] } diff --git a/entries/openclaw/catalog-metadata.v1.json b/entries/openclaw/catalog-metadata.v1.json index 2650c8e..70dd8ad 100644 --- a/entries/openclaw/catalog-metadata.v1.json +++ b/entries/openclaw/catalog-metadata.v1.json @@ -77,6 +77,10 @@ }, "spec": { "kind": "app", - "category": "ai-platform" + "category": "ai-platform", + "productKind": "application", + "entrypointKinds": [ + "agent" + ] } } diff --git a/entries/openclaw/manifest.json b/entries/openclaw/manifest.json index 391d874..c454398 100644 --- a/entries/openclaw/manifest.json +++ b/entries/openclaw/manifest.json @@ -45,5 +45,12 @@ "path": "/", "name": "OpenClaw Gateway" } + ], + "entrypoints": [ + { + "id": "manage", + "name": "委派 Agent 打开与维护 OpenClaw", + "kind": "agent" + } ] } diff --git a/entries/ragflow/catalog-metadata.v1.json b/entries/ragflow/catalog-metadata.v1.json index e5cc5e3..6a36108 100644 --- a/entries/ragflow/catalog-metadata.v1.json +++ b/entries/ragflow/catalog-metadata.v1.json @@ -76,6 +76,10 @@ }, "spec": { "kind": "app", - "category": "rag" + "category": "rag", + "productKind": "application", + "entrypointKinds": [ + "agent" + ] } } diff --git a/entries/ragflow/manifest.json b/entries/ragflow/manifest.json index 723021c..0e074aa 100644 --- a/entries/ragflow/manifest.json +++ b/entries/ragflow/manifest.json @@ -45,5 +45,12 @@ "path": "/api/v1", "name": "RagFlow API" } + ], + "entrypoints": [ + { + "id": "manage", + "name": "委派 Agent 打开与维护 RagFlow", + "kind": "agent" + } ] } diff --git a/manifest.json b/manifest.json index d8e2b86..4d4c724 100644 --- a/manifest.json +++ b/manifest.json @@ -1,12 +1,12 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "id": "desirecore-registry-manifest", - "version": "4.1.0", + "version": "4.2.0", "name": "DesireCore Registry", - "description": "DesireCore 官方注册表 — 原生应用、Docker 应用、MCP 服务、HTTP API 和第三方外部集成的统一目录", + "description": "DesireCore 官方注册表 — 应用、插件、MCP 服务、HTTP API 和第三方外部集成的统一目录", "maintainer": "DesireCore Team", "repository": "https://github.com/desirecore/registry", - "lastUpdated": "2026-09-18", + "lastUpdated": "2026-10-05", "stats": { "totalEntries": 23, "dockerApps": 8, @@ -15,7 +15,7 @@ "httpApis": 5, "externalIntegrations": 1 }, - "dataVersion": "4.1.0", + "dataVersion": "4.2.0", "catalogMetadata": { "version": "1.0.0", "schema": "schemas/catalog-metadata.v1.schema.json", diff --git a/package-lock.json b/package-lock.json index 85c2c3b..fa9d5db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@desirecore/registry", - "version": "4.1.0", + "version": "4.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@desirecore/registry", - "version": "4.1.0", + "version": "4.2.0", "devDependencies": { "ajv": "8.20.0" }, diff --git a/package.json b/package.json index 9e54790..82b4685 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "@desirecore/registry", - "version": "4.1.0", + "version": "4.2.0", "private": true, "description": "Validated catalog data for the DesireCore Registry", "type": "module", "scripts": { "validate": "node scripts/validate-registry.mjs", - "test": "node --test scripts/validate-registry.test.mjs scripts/native-app.test.mjs && npm run validate" + "test": "node --test scripts/validate-registry.test.mjs scripts/native-app.test.mjs scripts/plugin-contract.test.mjs && npm run validate" }, "devDependencies": { "ajv": "8.20.0" diff --git a/schemas/catalog-metadata.v1.schema.json b/schemas/catalog-metadata.v1.schema.json index 6ff2b0c..317c154 100644 --- a/schemas/catalog-metadata.v1.schema.json +++ b/schemas/catalog-metadata.v1.schema.json @@ -236,14 +236,269 @@ "required": ["kind"], "properties": { "kind": { "const": "app" }, + "productKind": { + "type": "string", + "enum": [ + "application", + "plugin" + ], + "description": "主要产品归属;应用提供独立业务,插件增加宿主能力。旧定义缺失时由受信适配层归一为 application。" + }, + "extension": { + "type": "object", + "required": [ + "apiVersion", + "hosts", + "runtime", + "contributes" + ], + "additionalProperties": false, + "properties": { + "apiVersion": { + "const": "1.0.0", + "description": "宿主扩展协议版本;当前支持 1.0.0。" + }, + "hosts": { + "type": "array", + "minItems": 1, + "maxItems": 2, + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "agent-service", + "desktop-host" + ] + }, + "description": "执行宿主;未实现的宿主只作声明并显示不兼容,客户端展示面在 UI 定义中另行声明。" + }, + "runtime": { + "oneOf": [ + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "const": "declarative", + "description": "仅由宿主读取声明,不创建执行进程。" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "kind", + "protocol", + "descriptorRef" + ], + "properties": { + "kind": { + "const": "service", + "description": "既有服务 owner 管理的协议连接;贡献调用继续走原授权和审批。" + }, + "protocol": { + "type": "string", + "enum": [ + "mcp", + "http" + ], + "description": "服务通信协议,不代表连接已经建立。" + }, + "descriptorRef": { + "type": "object", + "required": [ + "path", + "sha256" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!/)(?!.*\\\\)(?!.*:)(?!.*\\u0000)(?!.*(?:^|/)(?:\\.|\\.\\.)(?:/|$))(?!.*//)[^/]+(?:/[^/]+)*$", + "description": "固定在核验制品根下的规范 POSIX 相对文件路径;拒绝绝对路径、反斜杠、盘符、空段、点段及 NUL。解析时仍须核验 realpath containment。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "固定文件字节的 SHA-256;消费前核验,编辑须提交新安装修订。" + } + }, + "description": "安装制品根下内容固定的材料引用;相对路径和摘要必须共同匹配。" + } + }, + "additionalProperties": false + } + ], + "description": "贡献执行承载;descriptor 只定位受核验材料,不能直接把任意 endpoint 当运行事实。" + }, + "contributes": { + "type": "array", + "maxItems": 64, + "items": { + "type": "object", + "required": [ + "id", + "point", + "contractVersion", + "definitionRef" + ], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "产品内稳定贡献 ID;与来源、产品及精确安装实例共同定位运行绑定。" + }, + "point": { + "type": "string", + "enum": [ + "document.parsers", + "session.panels", + "session.widgets", + "session.actions", + "tools", + "skills" + ], + "description": "具有实际宿主消费者的贡献点;未知贡献点不得接入。" + }, + "contractVersion": { + "const": 1, + "description": "该贡献点的消费契约版本;不兼容版本不激活。" + }, + "definitionRef": { + "type": "object", + "required": [ + "path", + "sha256" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!/)(?!.*\\\\)(?!.*:)(?!.*\\u0000)(?!.*(?:^|/)(?:\\.|\\.\\.)(?:/|$))(?!.*//)[^/]+(?:/[^/]+)*$", + "description": "固定在核验制品根下的规范 POSIX 相对文件路径;拒绝绝对路径、反斜杠、盘符、空段、点段及 NUL。解析时仍须核验 realpath containment。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "固定文件字节的 SHA-256;消费前核验,编辑须提交新安装修订。" + } + }, + "description": "安装制品根下内容固定的材料引用;相对路径和摘要必须共同匹配。" + } + }, + "description": "包内贡献定义引用;业务层补充贡献 ID 唯一性及内容摘要核验。" + }, + "description": "本产品提供的宿主贡献;仅消费能力的普通应用允许空数组,独立插件必须至少一项。每项定义从安装材料读取并核验。" + }, + "configurationSchemaRef": { + "type": "object", + "required": [ + "path", + "sha256" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!/)(?!.*\\\\)(?!.*:)(?!.*\\u0000)(?!.*(?:^|/)(?:\\.|\\.\\.)(?:/|$))(?!.*//)[^/]+(?:/[^/]+)*$", + "description": "固定在核验制品根下的规范 POSIX 相对文件路径;拒绝绝对路径、反斜杠、盘符、空段、点段及 NUL。解析时仍须核验 realpath containment。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "固定文件字节的 SHA-256;消费前核验,编辑须提交新安装修订。" + } + }, + "description": "安装制品根下内容固定的材料引用;相对路径和摘要必须共同匹配。" + }, + "requires": { + "type": "array", + "maxItems": 64, + "items": { + "type": "object", + "required": [ + "sourceId", + "appId", + "contributionId", + "contractVersion", + "optional" + ], + "additionalProperties": false, + "properties": { + "sourceId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "直接依赖的受信产品来源,禁止按同名产品猜测。" + }, + "appId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "所依赖的应用或插件产品 ID;静态定义不携带安装实例 ID。" + }, + "contributionId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "消费方需要的具体贡献 ID。" + }, + "contractVersion": { + "const": 1, + "description": "消费方支持的贡献契约版本。" + }, + "optional": { + "type": "boolean", + "description": "true 为可选增强;false 缺失时阻断受影响功能,不自动安装或卸载。" + }, + "versionRange": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "可选发布版本 semver 范围,由满足性检查器校验。" + } + }, + "description": "直接能力依赖;运行时须显式解析兼容的精确安装实例,不建设递归依赖求解器。" + }, + "description": "本产品消费的必需或可选直接能力依赖;不产生使用授权。" + } + }, + "not": { + "properties": { + "contributes": { + "maxItems": 0 + }, + "requires": { + "maxItems": 0 + } + } + }, + "description": "产品提供或消费宿主能力的关系契约;至少声明一项贡献或依赖。普通应用可仅消费,独立插件仍须提供实际贡献。" + }, "category": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$" }, "entrypointKinds": { "type": "array", - "items": { "enum": ["builtin", "embedded-web", "external"] }, + "items": { "enum": ["builtin", "embedded-web", "external", "agent"] }, "uniqueItems": true }, "placementPolicy": { "$ref": "#/$defs/placementPolicy" } }, + "allOf": [{"if":{"properties":{"productKind":{"const":"plugin"}},"required":["productKind"]},"then":{"required":["extension"],"properties":{"extension":{"properties":{"contributes":{"minItems":1}}}}}}], "additionalProperties": false }, "serviceSpec": { diff --git a/schemas/registry-entry.schema.json b/schemas/registry-entry.schema.json index cd472c1..e0b365a 100644 --- a/schemas/registry-entry.schema.json +++ b/schemas/registry-entry.schema.json @@ -1,90 +1,871 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://github.com/desirecore/registry/schemas/registry-entry.schema.json", - "title": "DesireCore Registry Entry", - "description": "A strictly discriminated DesireCore Registry catalog entry. External integrations are discovery-only pointers and cannot carry installation or connection semantics.", + "title": "DesireCore Registry Entry 4.2", + "description": "严格区分应用/插件与服务/外部集成的 Registry 4.2 authored 契约。产品归属、部署方式、宿主贡献分别声明;声明不产生授权。此候选须在 Registry 外仓 review 后发布。", "oneOf": [ - { "$ref": "#/definitions/dockerApp" }, - { "$ref": "#/definitions/nativeApp" }, - { "$ref": "#/definitions/mcp" }, - { "$ref": "#/definitions/httpApi" }, - { "$ref": "#/definitions/externalIntegration" } + { + "$ref": "#/definitions/dockerApp" + }, + { + "$ref": "#/definitions/nativeApp" + }, + { + "$ref": "#/definitions/artifact" + }, + { + "$ref": "#/definitions/mcp" + }, + { + "$ref": "#/definitions/httpApi" + }, + { + "$ref": "#/definitions/externalIntegration" + } ], "definitions": { "nativeApp": { "type": "object", "description": "宿主机原生应用:独立安装和运行,不通过 MCP 服务注册或 Docker 容器部署。", - "required": ["id", "name", "type", "version", "author", "description", "tags", "icon", "iconLetter", "platformSupport", "category", "fullDesc", "shortDesc", "install", "source", "requiredClientVersion", "placementPolicy", "stewardship", "availability", "license", "redistribution", "listingMaintainer", "upstreamMaintainer", "branding", "compliance"], + "required": [ + "id", + "name", + "type", + "version", + "author", + "description", + "tags", + "icon", + "iconLetter", + "platformSupport", + "category", + "fullDesc", + "shortDesc", + "install", + "source", + "requiredClientVersion", + "placementPolicy", + "stewardship", + "availability", + "license", + "redistribution", + "listingMaintainer", + "upstreamMaintainer", + "branding", + "compliance", + "entrypoints" + ], "additionalProperties": false, "properties": { - "id": { "$ref": "#/definitions/id" }, - "name": { "$ref": "#/definitions/nonEmptyString" }, - "type": { "const": "native-app" }, - "version": { "$ref": "#/definitions/version" }, - "author": { "$ref": "#/definitions/nonEmptyString" }, - "description": { "$ref": "#/definitions/nonEmptyString" }, - "tags": { "$ref": "#/definitions/tags" }, - "icon": { "$ref": "#/definitions/nonEmptyString" }, - "iconLetter": { "type": "string", "minLength": 1, "maxLength": 2 }, - "platformSupport": { "$ref": "#/definitions/platformSupport" }, - "category": { "const": "tools" }, - "fullDesc": { "$ref": "#/definitions/nonEmptyString" }, - "shortDesc": { "$ref": "#/definitions/nonEmptyString" }, - "githubUrl": { "$ref": "#/definitions/httpsUrl" }, - "requiredClientVersion": { "$ref": "#/definitions/version" }, + "id": { + "$ref": "#/definitions/id" + }, + "name": { + "$ref": "#/definitions/nonEmptyString" + }, + "type": { + "const": "native-app" + }, + "version": { + "$ref": "#/definitions/version" + }, + "author": { + "$ref": "#/definitions/nonEmptyString" + }, + "description": { + "$ref": "#/definitions/nonEmptyString" + }, + "tags": { + "$ref": "#/definitions/tags" + }, + "icon": { + "$ref": "#/definitions/nonEmptyString" + }, + "iconLetter": { + "type": "string", + "minLength": 1, + "maxLength": 2 + }, + "platformSupport": { + "$ref": "#/definitions/platformSupport" + }, + "category": { + "type": "string", + "minLength": 1, + "pattern": "^[a-z0-9-]+$", + "description": "应用分类(slug 格式),可扩展任意分类" + }, + "fullDesc": { + "$ref": "#/definitions/nonEmptyString" + }, + "shortDesc": { + "$ref": "#/definitions/nonEmptyString" + }, + "githubUrl": { + "$ref": "#/definitions/httpsUrl" + }, + "requiredClientVersion": { + "$ref": "#/definitions/version" + }, "install": { - "type": "object", "required": ["method", "requirements", "configNeeded"], "additionalProperties": false, + "type": "object", + "required": [ + "method", + "requirements", + "configNeeded" + ], + "additionalProperties": false, "properties": { - "method": { "const": "native-node", "description": "按审核后的 install.md 在宿主机安装固定 Node 制品;不自动启动应用或隧道。" }, + "method": { + "const": "native-node", + "description": "按审核后的 install.md 在宿主机安装固定 Node 制品;不自动启动应用或隧道。" + }, "requirements": { - "type": "object", "required": ["docker", "node", "minMemory", "minDisk", "ports"], "additionalProperties": false, + "type": "object", + "required": [ + "docker", + "node", + "minMemory", + "minDisk", + "ports" + ], + "additionalProperties": false, "properties": { - "docker": { "const": false }, - "node": { "type": "string", "minLength": 1, "maxLength": 80 }, - "minMemory": { "$ref": "#/definitions/nonEmptyString" }, - "minDisk": { "$ref": "#/definitions/nonEmptyString" }, - "ports": { "type": "array", "minItems": 1, "maxItems": 1, "uniqueItems": true, "items": { "type": "integer", "minimum": 1, "maximum": 65535 }, "description": "安装默认使用的本机管理页面端口,不是对外公网端口或内部服务。" } + "docker": { + "const": false + }, + "node": { + "type": "string", + "minLength": 1, + "maxLength": 80 + }, + "minMemory": { + "$ref": "#/definitions/nonEmptyString" + }, + "minDisk": { + "$ref": "#/definitions/nonEmptyString" + }, + "ports": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "uniqueItems": true, + "items": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "description": "安装默认使用的本机管理页面端口,不是对外公网端口或内部服务。" + } } }, - "configNeeded": { "type": "array", "items": { "$ref": "#/definitions/nonEmptyString" } } + "configNeeded": { + "type": "array", + "items": { + "$ref": "#/definitions/nonEmptyString" + } + } } }, "placementPolicy": { - "type": "object", "required": ["showInMyApps", "allowNavPin", "allowAgentBinding"], "additionalProperties": false, - "properties": { "showInMyApps": { "type": "boolean" }, "allowNavPin": { "type": "boolean" }, "allowAgentBinding": { "const": false } } + "type": "object", + "description": "目录维护者声明的可放置范围;只限制能力,不能替用户自动固定或绑定", + "required": [ + "showInMyApps", + "allowNavPin", + "allowAgentBinding" + ], + "properties": { + "showInMyApps": { + "type": "boolean", + "description": "安装后是否允许出现在“我的应用”" + }, + "allowNavPin": { + "type": "boolean", + "description": "用户是否可将应用固定到主导航" + }, + "allowAgentBinding": { + "type": "boolean", + "description": "用户是否可将应用关联到 Agent 会话侧栏" + } + }, + "additionalProperties": false }, "source": { - "type": "object", "required": ["kind", "url", "ref", "sha256"], "additionalProperties": false, + "type": "object", + "description": "经过审核的上游来源指针;安装时必须进一步证明 ref 或制品摘要不可变。", + "required": [ + "kind", + "url", + "ref" + ], "properties": { - "kind": { "const": "release" }, "url": { "$ref": "#/definitions/httpsUrl" }, - "ref": { "type": "string", "pattern": "^[a-f0-9]{40}(?:[a-f0-9]{24})?$" }, - "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" }, - "upstreamRepo": { "$ref": "#/definitions/httpsUrl" } - } + "kind": { + "type": "string", + "enum": [ + "git", + "release", + "package", + "container" + ], + "description": "上游来源类型:Git 仓库、Release 制品、包管理器制品或容器镜像。" + }, + "url": { + "type": "string", + "format": "uri", + "description": "权威上游地址;不得由客户端在安装请求中替换。" + }, + "ref": { + "type": "string", + "minLength": 1, + "description": "审核版本引用;安装候选必须是完整 commit、制品版本或容器 digest。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-fA-F0-9]{64}$", + "description": "Release、package 或其他下载响应体的 SHA-256 摘要。" + }, + "path": { + "type": "string", + "minLength": 1, + "description": "单仓库目录中的相对路径;不得作为运行时任意本地路径使用。" + }, + "upstreamRepo": { + "type": "string", + "format": "uri", + "description": "制品与源码不在同一地址时对应的上游源码仓库。" + } + }, + "additionalProperties": false + }, + "stewardship": { + "type": "string", + "enum": [ + "official", + "partner", + "community", + "pointer" + ], + "description": "条目治理身份:official 仅能由受信 official catalog provider 注入;partner/community/pointer 分别表示合作方、社区维护或纯上游指针。" + }, + "availability": { + "type": "string", + "enum": [ + "listing-only", + "installable" + ], + "description": "目录可用性:listing-only 仅允许浏览上游;installable 才允许进入既有受治理获取流程,不表示已经安装或正在运行。" + }, + "license": { + "type": "string", + "minLength": 1, + "description": "覆盖实际制品的许可证标识。" + }, + "redistribution": { + "type": "string", + "enum": [ + "allowed", + "source-pointer-only", + "verify-package-terms" + ], + "description": "内容转交策略:allowed 可按许可证分发;source-pointer-only 只能从权威上游取得;verify-package-terms 表示条款尚未完成复核。" + }, + "listingMaintainer": { + "type": "object", + "description": "目录策展者或上游维护者身份;两种角色必须分别存放,不能把 author 当成已验证维护者。", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "维护者的公开名称或组织名称。" + }, + "url": { + "type": "string", + "format": "uri", + "description": "维护者的公开主页、项目主页或可验证身份页面。" + }, + "verified": { + "type": "boolean", + "description": "平台是否已验证该身份;不等同于许可证授权、内容审核或上游背书。" + } + }, + "additionalProperties": false + }, + "upstreamMaintainer": { + "type": "object", + "description": "目录策展者或上游维护者身份;两种角色必须分别存放,不能把 author 当成已验证维护者。", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "维护者的公开名称或组织名称。" + }, + "url": { + "type": "string", + "format": "uri", + "description": "维护者的公开主页、项目主页或可验证身份页面。" + }, + "verified": { + "type": "boolean", + "description": "平台是否已验证该身份;不等同于许可证授权、内容审核或上游背书。" + } + }, + "additionalProperties": false }, - "stewardship": { "enum": ["community", "partner", "pointer"] }, - "availability": { "enum": ["installable", "listing-only"] }, - "license": { "$ref": "#/definitions/nonEmptyString" }, - "redistribution": { "enum": ["allowed", "source-pointer-only", "verify-package-terms"] }, - "listingMaintainer": { "$ref": "#/definitions/nativeMaintainer" }, - "upstreamMaintainer": { "$ref": "#/definitions/nativeMaintainer" }, "branding": { - "type": "object", "required": ["relationship", "nameUsage", "logoStatus"], "additionalProperties": false, - "properties": { "relationship": { "const": "independent-listing" }, "nameUsage": { "const": "nominative" }, "logoStatus": { "const": "not-used" } } + "type": "object", + "description": "名称、Logo 和官方关系的展示依据;代码许可证不能自动授予商标或宣传素材权利。", + "required": [ + "relationship", + "nameUsage", + "logoStatus" + ], + "properties": { + "relationship": { + "type": "string", + "enum": [ + "official", + "partner", + "independent-listing" + ], + "description": "与上游的公开关系:官方、已确认合作,或独立事实索引。" + }, + "nameUsage": { + "type": "string", + "enum": [ + "owned", + "licensed", + "nominative" + ], + "description": "项目名称的使用依据:自有、已获许可,或识别上游所需的指称性使用。" + }, + "logoStatus": { + "type": "string", + "enum": [ + "not-used", + "license-covered", + "trademark-policy-allowed", + "permission-obtained" + ], + "description": "Logo 使用依据;未确认权利时必须为 not-used,并由客户端使用中性图标。" + } + }, + "additionalProperties": false }, "compliance": { - "type": "object", "required": ["licenseEvidencePath", "reviewedRef", "reviewedAt", "reviewedBy", "upstreamEndorsed"], "additionalProperties": false, + "type": "object", + "description": "绑定到本次不可变来源的合规审核记录;新版本必须重新审核,不能继承旧版本结论。", + "required": [ + "licenseEvidencePath", + "reviewedRef", + "reviewedAt", + "reviewedBy", + "upstreamEndorsed" + ], "properties": { - "licenseEvidencePath": { "$ref": "#/definitions/nonEmptyString" }, "noticePath": { "$ref": "#/definitions/nonEmptyString" }, - "reviewedRef": { "type": "string", "pattern": "^[a-f0-9]{40}(?:[a-f0-9]{24})?$" }, - "reviewedAt": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "reviewedBy": { "$ref": "#/definitions/nonEmptyString" }, "upstreamEndorsed": { "const": false } + "licenseEvidencePath": { + "type": "string", + "minLength": 1, + "description": "在已审核源码或制品中定位许可证文本的相对路径。" + }, + "noticePath": { + "type": "string", + "minLength": 1, + "description": "需要履行 NOTICE 或署名义务时,对应材料的相对路径。" + }, + "reviewedRef": { + "type": "string", + "minLength": 1, + "description": "实际完成许可证、品牌和供应链复核的不可变 ref,必须与获取来源一致。" + }, + "reviewedAt": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}(?:T.*)?$", + "description": "审核日期或 ISO 8601 时间戳。" + }, + "reviewedBy": { + "type": "string", + "minLength": 1, + "description": "可追溯的审核者或审核流程标识。" + }, + "upstreamEndorsed": { + "type": "boolean", + "description": "上游是否明确认可该条目;false 时必须显示非官方、未经背书声明。" + } + }, + "additionalProperties": false + }, + "productKind": { + "type": "string", + "enum": [ + "application", + "plugin" + ], + "description": "主要产品归属;应用提供独立业务,插件增加宿主能力。旧定义缺失时由受信适配层归一为 application。" + }, + "extension": { + "type": "object", + "required": [ + "apiVersion", + "hosts", + "runtime", + "contributes" + ], + "additionalProperties": false, + "properties": { + "apiVersion": { + "const": "1.0.0", + "description": "宿主扩展协议版本;当前支持 1.0.0。" + }, + "hosts": { + "type": "array", + "minItems": 1, + "maxItems": 2, + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "agent-service", + "desktop-host" + ] + }, + "description": "执行宿主;未实现的宿主只作声明并显示不兼容,客户端展示面在 UI 定义中另行声明。" + }, + "runtime": { + "oneOf": [ + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "const": "declarative", + "description": "仅由宿主读取声明,不创建执行进程。" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "kind", + "protocol", + "descriptorRef" + ], + "properties": { + "kind": { + "const": "service", + "description": "既有服务 owner 管理的协议连接;贡献调用继续走原授权和审批。" + }, + "protocol": { + "type": "string", + "enum": [ + "mcp", + "http" + ], + "description": "服务通信协议,不代表连接已经建立。" + }, + "descriptorRef": { + "type": "object", + "required": [ + "path", + "sha256" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!/)(?!.*\\\\)(?!.*:)(?!.*\\u0000)(?!.*(?:^|/)(?:\\.|\\.\\.)(?:/|$))(?!.*//)[^/]+(?:/[^/]+)*$", + "description": "固定在核验制品根下的规范 POSIX 相对文件路径;拒绝绝对路径、反斜杠、盘符、空段、点段及 NUL。解析时仍须核验 realpath containment。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "固定文件字节的 SHA-256;消费前核验,编辑须提交新安装修订。" + } + }, + "description": "安装制品根下内容固定的材料引用;相对路径和摘要必须共同匹配。" + } + }, + "additionalProperties": false + } + ], + "description": "贡献执行承载;descriptor 只定位受核验材料,不能直接把任意 endpoint 当运行事实。" + }, + "contributes": { + "type": "array", + "maxItems": 64, + "items": { + "type": "object", + "required": [ + "id", + "point", + "contractVersion", + "definitionRef" + ], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "产品内稳定贡献 ID;与来源、产品及精确安装实例共同定位运行绑定。" + }, + "point": { + "type": "string", + "enum": [ + "document.parsers", + "session.panels", + "session.widgets", + "session.actions", + "tools", + "skills" + ], + "description": "具有实际宿主消费者的贡献点;未知贡献点不得接入。" + }, + "contractVersion": { + "const": 1, + "description": "该贡献点的消费契约版本;不兼容版本不激活。" + }, + "definitionRef": { + "type": "object", + "required": [ + "path", + "sha256" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!/)(?!.*\\\\)(?!.*:)(?!.*\\u0000)(?!.*(?:^|/)(?:\\.|\\.\\.)(?:/|$))(?!.*//)[^/]+(?:/[^/]+)*$", + "description": "固定在核验制品根下的规范 POSIX 相对文件路径;拒绝绝对路径、反斜杠、盘符、空段、点段及 NUL。解析时仍须核验 realpath containment。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "固定文件字节的 SHA-256;消费前核验,编辑须提交新安装修订。" + } + }, + "description": "安装制品根下内容固定的材料引用;相对路径和摘要必须共同匹配。" + } + }, + "description": "包内贡献定义引用;业务层补充贡献 ID 唯一性及内容摘要核验。" + }, + "description": "本产品提供的宿主贡献;仅消费能力的普通应用允许空数组,独立插件必须至少一项。每项定义从安装材料读取并核验。" + }, + "configurationSchemaRef": { + "type": "object", + "required": [ + "path", + "sha256" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!/)(?!.*\\\\)(?!.*:)(?!.*\\u0000)(?!.*(?:^|/)(?:\\.|\\.\\.)(?:/|$))(?!.*//)[^/]+(?:/[^/]+)*$", + "description": "固定在核验制品根下的规范 POSIX 相对文件路径;拒绝绝对路径、反斜杠、盘符、空段、点段及 NUL。解析时仍须核验 realpath containment。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "固定文件字节的 SHA-256;消费前核验,编辑须提交新安装修订。" + } + }, + "description": "安装制品根下内容固定的材料引用;相对路径和摘要必须共同匹配。" + }, + "requires": { + "type": "array", + "maxItems": 64, + "items": { + "type": "object", + "required": [ + "sourceId", + "appId", + "contributionId", + "contractVersion", + "optional" + ], + "additionalProperties": false, + "properties": { + "sourceId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "直接依赖的受信产品来源,禁止按同名产品猜测。" + }, + "appId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "所依赖的应用或插件产品 ID;静态定义不携带安装实例 ID。" + }, + "contributionId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "消费方需要的具体贡献 ID。" + }, + "contractVersion": { + "const": 1, + "description": "消费方支持的贡献契约版本。" + }, + "optional": { + "type": "boolean", + "description": "true 为可选增强;false 缺失时阻断受影响功能,不自动安装或卸载。" + }, + "versionRange": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "可选发布版本 semver 范围,由满足性检查器校验。" + } + }, + "description": "直接能力依赖;运行时须显式解析兼容的精确安装实例,不建设递归依赖求解器。" + }, + "description": "本产品消费的必需或可选直接能力依赖;不产生使用授权。" + } + }, + "not": { + "properties": { + "contributes": { + "maxItems": 0 + }, + "requires": { + "maxItems": 0 + } + } + }, + "description": "产品提供或消费宿主能力的关系契约;至少声明一项贡献或依赖。普通应用可仅消费,独立插件仍须提供实际贡献。" + }, + "entrypoints": { + "type": "array", + "maxItems": 32, + "items": { + "oneOf": [ + { + "type": "object", + "description": "DesireCore 自带并随客户端发布的页面入口;第三方目录不得伪造此入口类型", + "required": [ + "id", + "name", + "kind", + "route" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "应用内稳定入口 ID" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "入口显示名称" + }, + "kind": { + "const": "builtin", + "description": "内置页面入口判别字段" + }, + "route": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^/(?!/)", + "description": "客户端内置路由;必须是单斜杠开头的相对应用路由,不能是 URL" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "由隔离 App Host 承载的 Web 应用入口,运行时 URL 只可由服务端从 serviceRef 解析", + "required": [ + "id", + "name", + "kind", + "serviceRef" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "应用内稳定入口 ID" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "入口显示名称" + }, + "kind": { + "const": "embedded-web", + "description": "隔离 Web 入口判别字段" + }, + "serviceRef": { + "type": "string", + "minLength": 1, + "maxLength": 240, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "Registry 服务 ID 或服务端生成的本机端口引用;不是 URL" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "由系统浏览器打开的目录声明入口;运行时 URL 只可由服务端从 serviceRef 解析", + "required": [ + "id", + "name", + "kind", + "serviceRef" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "应用内稳定入口 ID" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "入口显示名称" + }, + "kind": { + "const": "external", + "description": "外部浏览器入口判别字段" + }, + "serviceRef": { + "type": "string", + "minLength": 1, + "maxLength": 240, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "Registry 服务 ID;不是 URL,解析结果还需 HTTPS 或本机 loopback 校验" + }, + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^/(?!/)", + "description": "可选服务内路径;解析层禁止凭据、反斜杠和目录穿越" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "id", + "name", + "kind" + ], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "应用内稳定入口 ID。" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "交给 Agent 打开的入口显示名。" + }, + "kind": { + "const": "agent", + "description": "读取实例维护资料后委派 Agent;无需服务端口或标准启动接口。" + } + } + } + ], + "description": "应用的一个用户可见启动入口,按 kind 严格判别" + }, + "description": "明确用户入口;plugin 允许空数组,普通应用至少一个入口。" + } + }, + "allOf": [ + { + "if": { + "properties": { + "productKind": { + "const": "plugin" + } + }, + "required": [ + "productKind" + ] + }, + "then": { + "required": [ + "extension" + ], + "properties": { + "extension": { + "properties": { + "contributes": { + "minItems": 1 + } + } + } + } + }, + "else": { + "properties": { + "entrypoints": { + "minItems": 1 + } + } } } - } + ] }, "nativeMaintainer": { - "type": "object", "required": ["name", "url"], "additionalProperties": false, - "properties": { "name": { "$ref": "#/definitions/nonEmptyString" }, "url": { "$ref": "#/definitions/httpsUrl" }, "verified": { "type": "boolean" } } + "type": "object", + "required": [ + "name", + "url" + ], + "additionalProperties": false, + "properties": { + "name": { + "$ref": "#/definitions/nonEmptyString" + }, + "url": { + "$ref": "#/definitions/httpsUrl" + }, + "verified": { + "type": "boolean" + } + } }, "id": { "type": "string", @@ -111,7 +892,11 @@ "uniqueItems": true, "items": { "type": "string", - "enum": ["macos", "windows", "linux"] + "enum": [ + "macos", + "windows", + "linux" + ] }, "description": "Desktop platforms on which the entry is relevant." }, @@ -119,7 +904,9 @@ "type": "array", "minItems": 1, "uniqueItems": true, - "items": { "$ref": "#/definitions/nonEmptyString" }, + "items": { + "$ref": "#/definitions/nonEmptyString" + }, "description": "Human-readable discovery and search tags." }, "capabilities": { @@ -133,96 +920,201 @@ }, "environment": { "type": "object", - "additionalProperties": { "type": "string" } + "additionalProperties": { + "type": "string" + } }, "mcpInstall": { "type": "object", - "required": ["method", "command", "args"], + "required": [ + "method", + "command", + "args" + ], "additionalProperties": false, "properties": { - "method": { "$ref": "#/definitions/nonEmptyString" }, - "packageName": { "$ref": "#/definitions/nonEmptyString" }, - "command": { "$ref": "#/definitions/nonEmptyString" }, + "method": { + "$ref": "#/definitions/nonEmptyString" + }, + "packageName": { + "$ref": "#/definitions/nonEmptyString" + }, + "command": { + "$ref": "#/definitions/nonEmptyString" + }, "args": { "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + } }, "postInstall": { "type": "array", - "items": { "$ref": "#/definitions/nonEmptyString" } + "items": { + "$ref": "#/definitions/nonEmptyString" + } }, - "env": { "$ref": "#/definitions/environment" } + "env": { + "$ref": "#/definitions/environment" + } } }, "connection": { "type": "object", - "required": ["transport"], + "required": [ + "transport" + ], "additionalProperties": false, "properties": { "transport": { "type": "string", - "enum": ["stdio", "streamable-http", "sse"] + "enum": [ + "stdio", + "streamable-http", + "sse" + ] + }, + "command": { + "$ref": "#/definitions/nonEmptyString" }, - "command": { "$ref": "#/definitions/nonEmptyString" }, "args": { "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + } }, - "url": { "$ref": "#/definitions/nonEmptyString" }, - "env": { "$ref": "#/definitions/environment" } + "url": { + "$ref": "#/definitions/nonEmptyString" + }, + "env": { + "$ref": "#/definitions/environment" + } } }, "dockerApp": { "type": "object", "required": [ - "id", "name", "type", "version", "author", "description", "tags", "icon", - "iconLetter", "platformSupport", "category", "fullDesc", "shortDesc", "install" + "id", + "name", + "type", + "version", + "author", + "description", + "tags", + "icon", + "iconLetter", + "platformSupport", + "category", + "fullDesc", + "shortDesc", + "install", + "entrypoints" ], "additionalProperties": false, "properties": { - "id": { "$ref": "#/definitions/id" }, - "name": { "$ref": "#/definitions/nonEmptyString" }, - "type": { "const": "docker-app" }, - "version": { "$ref": "#/definitions/version" }, - "author": { "$ref": "#/definitions/nonEmptyString" }, - "description": { "$ref": "#/definitions/nonEmptyString" }, - "tags": { "$ref": "#/definitions/tags" }, - "icon": { "$ref": "#/definitions/nonEmptyString" }, - "iconLetter": { "type": "string", "minLength": 1, "maxLength": 2 }, - "platformSupport": { "$ref": "#/definitions/platformSupport" }, + "id": { + "$ref": "#/definitions/id" + }, + "name": { + "$ref": "#/definitions/nonEmptyString" + }, + "type": { + "const": "docker-app" + }, + "version": { + "$ref": "#/definitions/version" + }, + "author": { + "$ref": "#/definitions/nonEmptyString" + }, + "description": { + "$ref": "#/definitions/nonEmptyString" + }, + "tags": { + "$ref": "#/definitions/tags" + }, + "icon": { + "$ref": "#/definitions/nonEmptyString" + }, + "iconLetter": { + "type": "string", + "minLength": 1, + "maxLength": 2 + }, + "platformSupport": { + "$ref": "#/definitions/platformSupport" + }, "category": { "type": "string", - "enum": ["ai-platform", "chat", "workflow", "rag", "tools"] + "minLength": 1, + "pattern": "^[a-z0-9-]+$", + "description": "应用分类(slug 格式),可扩展任意分类" + }, + "fullDesc": { + "$ref": "#/definitions/nonEmptyString" + }, + "shortDesc": { + "$ref": "#/definitions/nonEmptyString" + }, + "stars": { + "type": "integer", + "minimum": 0 + }, + "githubUrl": { + "$ref": "#/definitions/httpsUrl" }, - "fullDesc": { "$ref": "#/definitions/nonEmptyString" }, - "shortDesc": { "$ref": "#/definitions/nonEmptyString" }, - "stars": { "type": "integer", "minimum": 0 }, - "githubUrl": { "$ref": "#/definitions/httpsUrl" }, "install": { "type": "object", - "required": ["method", "requirements", "configNeeded"], + "required": [ + "method", + "requirements", + "configNeeded" + ], "additionalProperties": false, "properties": { - "method": { "type": "string", "enum": ["docker", "docker-compose"] }, + "method": { + "type": "string", + "enum": [ + "docker", + "docker-compose" + ] + }, "requirements": { "type": "object", - "required": ["docker", "minMemory", "minDisk", "ports"], + "required": [ + "docker", + "minMemory", + "minDisk", + "ports" + ], "additionalProperties": false, "properties": { - "docker": { "const": true }, - "minMemory": { "$ref": "#/definitions/nonEmptyString" }, - "minDisk": { "$ref": "#/definitions/nonEmptyString" }, + "docker": { + "const": true + }, + "minMemory": { + "$ref": "#/definitions/nonEmptyString" + }, + "minDisk": { + "$ref": "#/definitions/nonEmptyString" + }, "ports": { "type": "array", "minItems": 1, "uniqueItems": true, - "items": { "type": "integer", "minimum": 1, "maximum": 65535 } + "items": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + } } } }, "configNeeded": { "type": "array", - "items": { "$ref": "#/definitions/nonEmptyString" } + "items": { + "$ref": "#/definitions/nonEmptyString" + } } } }, @@ -230,77 +1122,860 @@ "type": "array", "items": { "type": "object", - "required": ["type", "port", "path", "name"], + "required": [ + "type", + "port", + "path", + "name" + ], "additionalProperties": false, "properties": { - "type": { "const": "http-api" }, - "port": { "type": "integer", "minimum": 1, "maximum": 65535 }, - "path": { "type": "string", "pattern": "^/" }, - "name": { "$ref": "#/definitions/nonEmptyString" } + "type": { + "const": "http-api" + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "path": { + "type": "string", + "pattern": "^/" + }, + "name": { + "$ref": "#/definitions/nonEmptyString" + } + } + } + }, + "productKind": { + "type": "string", + "enum": [ + "application", + "plugin" + ], + "description": "主要产品归属;应用提供独立业务,插件增加宿主能力。旧定义缺失时由受信适配层归一为 application。" + }, + "extension": { + "type": "object", + "required": [ + "apiVersion", + "hosts", + "runtime", + "contributes" + ], + "additionalProperties": false, + "properties": { + "apiVersion": { + "const": "1.0.0", + "description": "宿主扩展协议版本;当前支持 1.0.0。" + }, + "hosts": { + "type": "array", + "minItems": 1, + "maxItems": 2, + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "agent-service", + "desktop-host" + ] + }, + "description": "执行宿主;未实现的宿主只作声明并显示不兼容,客户端展示面在 UI 定义中另行声明。" + }, + "runtime": { + "oneOf": [ + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "const": "declarative", + "description": "仅由宿主读取声明,不创建执行进程。" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "kind", + "protocol", + "descriptorRef" + ], + "properties": { + "kind": { + "const": "service", + "description": "既有服务 owner 管理的协议连接;贡献调用继续走原授权和审批。" + }, + "protocol": { + "type": "string", + "enum": [ + "mcp", + "http" + ], + "description": "服务通信协议,不代表连接已经建立。" + }, + "descriptorRef": { + "type": "object", + "required": [ + "path", + "sha256" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!/)(?!.*\\\\)(?!.*:)(?!.*\\u0000)(?!.*(?:^|/)(?:\\.|\\.\\.)(?:/|$))(?!.*//)[^/]+(?:/[^/]+)*$", + "description": "固定在核验制品根下的规范 POSIX 相对文件路径;拒绝绝对路径、反斜杠、盘符、空段、点段及 NUL。解析时仍须核验 realpath containment。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "固定文件字节的 SHA-256;消费前核验,编辑须提交新安装修订。" + } + }, + "description": "安装制品根下内容固定的材料引用;相对路径和摘要必须共同匹配。" + } + }, + "additionalProperties": false + } + ], + "description": "贡献执行承载;descriptor 只定位受核验材料,不能直接把任意 endpoint 当运行事实。" + }, + "contributes": { + "type": "array", + "maxItems": 64, + "items": { + "type": "object", + "required": [ + "id", + "point", + "contractVersion", + "definitionRef" + ], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "产品内稳定贡献 ID;与来源、产品及精确安装实例共同定位运行绑定。" + }, + "point": { + "type": "string", + "enum": [ + "document.parsers", + "session.panels", + "session.widgets", + "session.actions", + "tools", + "skills" + ], + "description": "具有实际宿主消费者的贡献点;未知贡献点不得接入。" + }, + "contractVersion": { + "const": 1, + "description": "该贡献点的消费契约版本;不兼容版本不激活。" + }, + "definitionRef": { + "type": "object", + "required": [ + "path", + "sha256" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!/)(?!.*\\\\)(?!.*:)(?!.*\\u0000)(?!.*(?:^|/)(?:\\.|\\.\\.)(?:/|$))(?!.*//)[^/]+(?:/[^/]+)*$", + "description": "固定在核验制品根下的规范 POSIX 相对文件路径;拒绝绝对路径、反斜杠、盘符、空段、点段及 NUL。解析时仍须核验 realpath containment。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "固定文件字节的 SHA-256;消费前核验,编辑须提交新安装修订。" + } + }, + "description": "安装制品根下内容固定的材料引用;相对路径和摘要必须共同匹配。" + } + }, + "description": "包内贡献定义引用;业务层补充贡献 ID 唯一性及内容摘要核验。" + }, + "description": "本产品提供的宿主贡献;仅消费能力的普通应用允许空数组,独立插件必须至少一项。每项定义从安装材料读取并核验。" + }, + "configurationSchemaRef": { + "type": "object", + "required": [ + "path", + "sha256" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!/)(?!.*\\\\)(?!.*:)(?!.*\\u0000)(?!.*(?:^|/)(?:\\.|\\.\\.)(?:/|$))(?!.*//)[^/]+(?:/[^/]+)*$", + "description": "固定在核验制品根下的规范 POSIX 相对文件路径;拒绝绝对路径、反斜杠、盘符、空段、点段及 NUL。解析时仍须核验 realpath containment。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "固定文件字节的 SHA-256;消费前核验,编辑须提交新安装修订。" + } + }, + "description": "安装制品根下内容固定的材料引用;相对路径和摘要必须共同匹配。" + }, + "requires": { + "type": "array", + "maxItems": 64, + "items": { + "type": "object", + "required": [ + "sourceId", + "appId", + "contributionId", + "contractVersion", + "optional" + ], + "additionalProperties": false, + "properties": { + "sourceId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "直接依赖的受信产品来源,禁止按同名产品猜测。" + }, + "appId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "所依赖的应用或插件产品 ID;静态定义不携带安装实例 ID。" + }, + "contributionId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "消费方需要的具体贡献 ID。" + }, + "contractVersion": { + "const": 1, + "description": "消费方支持的贡献契约版本。" + }, + "optional": { + "type": "boolean", + "description": "true 为可选增强;false 缺失时阻断受影响功能,不自动安装或卸载。" + }, + "versionRange": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "可选发布版本 semver 范围,由满足性检查器校验。" + } + }, + "description": "直接能力依赖;运行时须显式解析兼容的精确安装实例,不建设递归依赖求解器。" + }, + "description": "本产品消费的必需或可选直接能力依赖;不产生使用授权。" + } + }, + "not": { + "properties": { + "contributes": { + "maxItems": 0 + }, + "requires": { + "maxItems": 0 + } + } + }, + "description": "产品提供或消费宿主能力的关系契约;至少声明一项贡献或依赖。普通应用可仅消费,独立插件仍须提供实际贡献。" + }, + "entrypoints": { + "type": "array", + "maxItems": 32, + "items": { + "oneOf": [ + { + "type": "object", + "description": "DesireCore 自带并随客户端发布的页面入口;第三方目录不得伪造此入口类型", + "required": [ + "id", + "name", + "kind", + "route" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "应用内稳定入口 ID" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "入口显示名称" + }, + "kind": { + "const": "builtin", + "description": "内置页面入口判别字段" + }, + "route": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^/(?!/)", + "description": "客户端内置路由;必须是单斜杠开头的相对应用路由,不能是 URL" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "由隔离 App Host 承载的 Web 应用入口,运行时 URL 只可由服务端从 serviceRef 解析", + "required": [ + "id", + "name", + "kind", + "serviceRef" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "应用内稳定入口 ID" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "入口显示名称" + }, + "kind": { + "const": "embedded-web", + "description": "隔离 Web 入口判别字段" + }, + "serviceRef": { + "type": "string", + "minLength": 1, + "maxLength": 240, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "Registry 服务 ID 或服务端生成的本机端口引用;不是 URL" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "由系统浏览器打开的目录声明入口;运行时 URL 只可由服务端从 serviceRef 解析", + "required": [ + "id", + "name", + "kind", + "serviceRef" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "应用内稳定入口 ID" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "入口显示名称" + }, + "kind": { + "const": "external", + "description": "外部浏览器入口判别字段" + }, + "serviceRef": { + "type": "string", + "minLength": 1, + "maxLength": 240, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "Registry 服务 ID;不是 URL,解析结果还需 HTTPS 或本机 loopback 校验" + }, + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^/(?!/)", + "description": "可选服务内路径;解析层禁止凭据、反斜杠和目录穿越" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "id", + "name", + "kind" + ], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "应用内稳定入口 ID。" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "交给 Agent 打开的入口显示名。" + }, + "kind": { + "const": "agent", + "description": "读取实例维护资料后委派 Agent;无需服务端口或标准启动接口。" + } + } + } + ], + "description": "应用的一个用户可见启动入口,按 kind 严格判别" + }, + "description": "明确用户入口;plugin 允许空数组,普通应用至少一个入口。" + }, + "placementPolicy": { + "type": "object", + "description": "目录维护者声明的可放置范围;只限制能力,不能替用户自动固定或绑定", + "required": [ + "showInMyApps", + "allowNavPin", + "allowAgentBinding" + ], + "properties": { + "showInMyApps": { + "type": "boolean", + "description": "安装后是否允许出现在“我的应用”" + }, + "allowNavPin": { + "type": "boolean", + "description": "用户是否可将应用固定到主导航" + }, + "allowAgentBinding": { + "type": "boolean", + "description": "用户是否可将应用关联到 Agent 会话侧栏" + } + }, + "additionalProperties": false + }, + "stewardship": { + "type": "string", + "enum": [ + "official", + "partner", + "community", + "pointer" + ], + "description": "条目治理身份:official 仅能由受信 official catalog provider 注入;partner/community/pointer 分别表示合作方、社区维护或纯上游指针。" + }, + "availability": { + "type": "string", + "enum": [ + "listing-only", + "installable" + ], + "description": "目录可用性:listing-only 仅允许浏览上游;installable 才允许进入既有受治理获取流程,不表示已经安装或正在运行。" + }, + "license": { + "type": "string", + "minLength": 1, + "description": "覆盖实际制品的许可证标识。" + }, + "redistribution": { + "type": "string", + "enum": [ + "allowed", + "source-pointer-only", + "verify-package-terms" + ], + "description": "内容转交策略:allowed 可按许可证分发;source-pointer-only 只能从权威上游取得;verify-package-terms 表示条款尚未完成复核。" + }, + "source": { + "type": "object", + "description": "经过审核的上游来源指针;安装时必须进一步证明 ref 或制品摘要不可变。", + "required": [ + "kind", + "url", + "ref" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "git", + "release", + "package", + "container" + ], + "description": "上游来源类型:Git 仓库、Release 制品、包管理器制品或容器镜像。" + }, + "url": { + "type": "string", + "format": "uri", + "description": "权威上游地址;不得由客户端在安装请求中替换。" + }, + "ref": { + "type": "string", + "minLength": 1, + "description": "审核版本引用;安装候选必须是完整 commit、制品版本或容器 digest。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-fA-F0-9]{64}$", + "description": "Release、package 或其他下载响应体的 SHA-256 摘要。" + }, + "path": { + "type": "string", + "minLength": 1, + "description": "单仓库目录中的相对路径;不得作为运行时任意本地路径使用。" + }, + "upstreamRepo": { + "type": "string", + "format": "uri", + "description": "制品与源码不在同一地址时对应的上游源码仓库。" + } + }, + "additionalProperties": false + }, + "listingMaintainer": { + "type": "object", + "description": "目录策展者或上游维护者身份;两种角色必须分别存放,不能把 author 当成已验证维护者。", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "维护者的公开名称或组织名称。" + }, + "url": { + "type": "string", + "format": "uri", + "description": "维护者的公开主页、项目主页或可验证身份页面。" + }, + "verified": { + "type": "boolean", + "description": "平台是否已验证该身份;不等同于许可证授权、内容审核或上游背书。" + } + }, + "additionalProperties": false + }, + "upstreamMaintainer": { + "type": "object", + "description": "目录策展者或上游维护者身份;两种角色必须分别存放,不能把 author 当成已验证维护者。", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "维护者的公开名称或组织名称。" + }, + "url": { + "type": "string", + "format": "uri", + "description": "维护者的公开主页、项目主页或可验证身份页面。" + }, + "verified": { + "type": "boolean", + "description": "平台是否已验证该身份;不等同于许可证授权、内容审核或上游背书。" + } + }, + "additionalProperties": false + }, + "branding": { + "type": "object", + "description": "名称、Logo 和官方关系的展示依据;代码许可证不能自动授予商标或宣传素材权利。", + "required": [ + "relationship", + "nameUsage", + "logoStatus" + ], + "properties": { + "relationship": { + "type": "string", + "enum": [ + "official", + "partner", + "independent-listing" + ], + "description": "与上游的公开关系:官方、已确认合作,或独立事实索引。" + }, + "nameUsage": { + "type": "string", + "enum": [ + "owned", + "licensed", + "nominative" + ], + "description": "项目名称的使用依据:自有、已获许可,或识别上游所需的指称性使用。" + }, + "logoStatus": { + "type": "string", + "enum": [ + "not-used", + "license-covered", + "trademark-policy-allowed", + "permission-obtained" + ], + "description": "Logo 使用依据;未确认权利时必须为 not-used,并由客户端使用中性图标。" + } + }, + "additionalProperties": false + }, + "compliance": { + "type": "object", + "description": "绑定到本次不可变来源的合规审核记录;新版本必须重新审核,不能继承旧版本结论。", + "required": [ + "licenseEvidencePath", + "reviewedRef", + "reviewedAt", + "reviewedBy", + "upstreamEndorsed" + ], + "properties": { + "licenseEvidencePath": { + "type": "string", + "minLength": 1, + "description": "在已审核源码或制品中定位许可证文本的相对路径。" + }, + "noticePath": { + "type": "string", + "minLength": 1, + "description": "需要履行 NOTICE 或署名义务时,对应材料的相对路径。" + }, + "reviewedRef": { + "type": "string", + "minLength": 1, + "description": "实际完成许可证、品牌和供应链复核的不可变 ref,必须与获取来源一致。" + }, + "reviewedAt": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}(?:T.*)?$", + "description": "审核日期或 ISO 8601 时间戳。" + }, + "reviewedBy": { + "type": "string", + "minLength": 1, + "description": "可追溯的审核者或审核流程标识。" + }, + "upstreamEndorsed": { + "type": "boolean", + "description": "上游是否明确认可该条目;false 时必须显示非官方、未经背书声明。" + } + }, + "additionalProperties": false + } + }, + "allOf": [ + { + "if": { + "properties": { + "productKind": { + "const": "plugin" + } + }, + "required": [ + "productKind" + ] + }, + "then": { + "required": [ + "extension" + ], + "properties": { + "extension": { + "properties": { + "contributes": { + "minItems": 1 + } + } + } + } + }, + "else": { + "properties": { + "entrypoints": { + "minItems": 1 + } } } } - } + ] }, "mcp": { "type": "object", "required": [ - "id", "name", "type", "version", "author", "description", "tags", "icon", - "platformSupport", "capabilities", "connection" + "id", + "name", + "type", + "version", + "author", + "description", + "tags", + "icon", + "platformSupport", + "capabilities", + "connection" ], "additionalProperties": false, "properties": { - "id": { "$ref": "#/definitions/id" }, - "name": { "$ref": "#/definitions/nonEmptyString" }, - "type": { "const": "mcp" }, - "version": { "$ref": "#/definitions/version" }, - "author": { "$ref": "#/definitions/nonEmptyString" }, - "description": { "$ref": "#/definitions/nonEmptyString" }, - "tags": { "$ref": "#/definitions/tags" }, - "icon": { "$ref": "#/definitions/nonEmptyString" }, - "platformSupport": { "$ref": "#/definitions/platformSupport" }, - "capabilities": { "$ref": "#/definitions/capabilities" }, - "toolCount": { "type": "integer", "minimum": 0 }, - "install": { "$ref": "#/definitions/mcpInstall" }, - "connection": { "$ref": "#/definitions/connection" }, - "sourceAppId": { "$ref": "#/definitions/id" }, - "sourceAppName": { "$ref": "#/definitions/nonEmptyString" } + "id": { + "$ref": "#/definitions/id" + }, + "name": { + "$ref": "#/definitions/nonEmptyString" + }, + "type": { + "const": "mcp" + }, + "version": { + "$ref": "#/definitions/version" + }, + "author": { + "$ref": "#/definitions/nonEmptyString" + }, + "description": { + "$ref": "#/definitions/nonEmptyString" + }, + "tags": { + "$ref": "#/definitions/tags" + }, + "icon": { + "$ref": "#/definitions/nonEmptyString" + }, + "platformSupport": { + "$ref": "#/definitions/platformSupport" + }, + "capabilities": { + "$ref": "#/definitions/capabilities" + }, + "toolCount": { + "type": "integer", + "minimum": 0 + }, + "install": { + "$ref": "#/definitions/mcpInstall" + }, + "connection": { + "$ref": "#/definitions/connection" + }, + "sourceAppId": { + "$ref": "#/definitions/id" + }, + "sourceAppName": { + "$ref": "#/definitions/nonEmptyString" + } } }, "httpApi": { "type": "object", "required": [ - "id", "name", "type", "version", "author", "description", "tags", "icon", - "platformSupport", "endpoint", "capabilities" + "id", + "name", + "type", + "version", + "author", + "description", + "tags", + "icon", + "platformSupport", + "endpoint", + "capabilities" ], "additionalProperties": false, "properties": { - "id": { "$ref": "#/definitions/id" }, - "name": { "$ref": "#/definitions/nonEmptyString" }, - "type": { "const": "http-api" }, - "version": { "$ref": "#/definitions/version" }, - "author": { "$ref": "#/definitions/nonEmptyString" }, - "description": { "$ref": "#/definitions/nonEmptyString" }, - "tags": { "$ref": "#/definitions/tags" }, - "icon": { "$ref": "#/definitions/nonEmptyString" }, - "platformSupport": { "$ref": "#/definitions/platformSupport" }, - "endpoint": { "type": "string", "pattern": "^https?://[^\\s]+$" }, - "capabilities": { "$ref": "#/definitions/capabilities" }, - "sourceAppId": { "$ref": "#/definitions/id" }, - "sourceAppName": { "$ref": "#/definitions/nonEmptyString" } + "id": { + "$ref": "#/definitions/id" + }, + "name": { + "$ref": "#/definitions/nonEmptyString" + }, + "type": { + "const": "http-api" + }, + "version": { + "$ref": "#/definitions/version" + }, + "author": { + "$ref": "#/definitions/nonEmptyString" + }, + "description": { + "$ref": "#/definitions/nonEmptyString" + }, + "tags": { + "$ref": "#/definitions/tags" + }, + "icon": { + "$ref": "#/definitions/nonEmptyString" + }, + "platformSupport": { + "$ref": "#/definitions/platformSupport" + }, + "endpoint": { + "type": "string", + "pattern": "^https?://[^\\s]+$" + }, + "capabilities": { + "$ref": "#/definitions/capabilities" + }, + "sourceAppId": { + "$ref": "#/definitions/id" + }, + "sourceAppName": { + "$ref": "#/definitions/nonEmptyString" + } } }, "maintainer": { "type": "object", - "required": ["name", "url", "verified"], + "required": [ + "name", + "url", + "verified" + ], "additionalProperties": false, "properties": { "name": { - "allOf": [{ "$ref": "#/definitions/nonEmptyString" }], + "allOf": [ + { + "$ref": "#/definitions/nonEmptyString" + } + ], "description": "Human-readable maintainer identity." }, "url": { - "allOf": [{ "$ref": "#/definitions/httpsUrl" }], + "allOf": [ + { + "$ref": "#/definitions/httpsUrl" + } + ], "description": "Public source used to verify the maintainer identity." }, "verified": { @@ -314,82 +1989,156 @@ "oneOf": [ { "type": "object", - "required": ["kind", "label", "url"], + "required": [ + "kind", + "label", + "url" + ], "additionalProperties": false, "properties": { - "kind": { "const": "product" }, - "label": { "$ref": "#/definitions/nonEmptyString" }, - "url": { "const": "https://www.kimi.ai/products/kimi-webbridge" } + "kind": { + "const": "product" + }, + "label": { + "$ref": "#/definitions/nonEmptyString" + }, + "url": { + "const": "https://www.kimi.ai/products/kimi-webbridge" + } } }, { "type": "object", - "required": ["kind", "label", "url"], + "required": [ + "kind", + "label", + "url" + ], "additionalProperties": false, "properties": { - "kind": { "const": "documentation" }, - "label": { "$ref": "#/definitions/nonEmptyString" }, - "url": { "const": "https://www.kimi.ai/help/kimi-webbridge/kimi-webbridge-introduction" } + "kind": { + "const": "documentation" + }, + "label": { + "$ref": "#/definitions/nonEmptyString" + }, + "url": { + "const": "https://www.kimi.ai/help/kimi-webbridge/kimi-webbridge-introduction" + } } }, { "type": "object", - "required": ["kind", "label", "extensionId", "url"], + "required": [ + "kind", + "label", + "extensionId", + "url" + ], "additionalProperties": false, "properties": { - "kind": { "const": "chrome-web-store" }, - "label": { "$ref": "#/definitions/nonEmptyString" }, + "kind": { + "const": "chrome-web-store" + }, + "label": { + "$ref": "#/definitions/nonEmptyString" + }, "extensionId": { "const": "fldmhceldgbpfpkbgopacenieobmligc", "description": "The audited Chrome Web Store extension identifier." }, - "url": { "const": "https://chromewebstore.google.com/detail/kimi-webbridge/fldmhceldgbpfpkbgopacenieobmligc" } + "url": { + "const": "https://chromewebstore.google.com/detail/kimi-webbridge/fldmhceldgbpfpkbgopacenieobmligc" + } } }, { "type": "object", - "required": ["kind", "label", "extensionId", "url"], + "required": [ + "kind", + "label", + "extensionId", + "url" + ], "additionalProperties": false, "properties": { - "kind": { "const": "edge-add-ons" }, - "label": { "$ref": "#/definitions/nonEmptyString" }, + "kind": { + "const": "edge-add-ons" + }, + "label": { + "$ref": "#/definitions/nonEmptyString" + }, "extensionId": { "const": "bnlffdbcfnanfbknnlaflhlhkocccckg", "description": "The audited Microsoft Edge Add-ons extension identifier." }, - "url": { "const": "https://microsoftedge.microsoft.com/addons/detail/kimi-webbridge/bnlffdbcfnanfbknnlaflhlhkocccckg" } + "url": { + "const": "https://microsoftedge.microsoft.com/addons/detail/kimi-webbridge/bnlffdbcfnanfbknnlaflhlhkocccckg" + } } }, { "type": "object", - "required": ["kind", "label", "url"], + "required": [ + "kind", + "label", + "url" + ], "additionalProperties": false, "properties": { - "kind": { "const": "privacy" }, - "label": { "$ref": "#/definitions/nonEmptyString" }, - "url": { "const": "https://www.kimi.ai/user/agreement/userPrivacy?version=v2" } + "kind": { + "const": "privacy" + }, + "label": { + "$ref": "#/definitions/nonEmptyString" + }, + "url": { + "const": "https://www.kimi.ai/user/agreement/userPrivacy?version=v2" + } } }, { "type": "object", - "required": ["kind", "label", "url"], + "required": [ + "kind", + "label", + "url" + ], "additionalProperties": false, "properties": { - "kind": { "const": "terms" }, - "label": { "$ref": "#/definitions/nonEmptyString" }, - "url": { "const": "https://www.kimi.ai/user/agreement/modelUse?version=v2" } + "kind": { + "const": "terms" + }, + "label": { + "$ref": "#/definitions/nonEmptyString" + }, + "url": { + "const": "https://www.kimi.ai/user/agreement/modelUse?version=v2" + } } } ] }, "browserExtensionComponent": { "type": "object", - "required": ["kind", "name", "description", "distribution", "browsers"], + "required": [ + "kind", + "name", + "description", + "distribution", + "browsers" + ], "additionalProperties": false, "properties": { - "kind": { "const": "browser-extension" }, - "name": { "$ref": "#/definitions/nonEmptyString" }, - "description": { "$ref": "#/definitions/nonEmptyString" }, + "kind": { + "const": "browser-extension" + }, + "name": { + "$ref": "#/definitions/nonEmptyString" + }, + "description": { + "$ref": "#/definitions/nonEmptyString" + }, "distribution": { "const": "official-upstream-only", "description": "DesireCore does not mirror or repackage this third-party component." @@ -399,19 +2148,48 @@ "minItems": 2, "maxItems": 2, "uniqueItems": true, - "items": { "type": "string", "enum": ["chrome", "edge"] }, - "allOf": [{ "contains": { "const": "chrome" } }, { "contains": { "const": "edge" } }] + "items": { + "type": "string", + "enum": [ + "chrome", + "edge" + ] + }, + "allOf": [ + { + "contains": { + "const": "chrome" + } + }, + { + "contains": { + "const": "edge" + } + } + ] } } }, "localDaemonComponent": { "type": "object", - "required": ["kind", "name", "description", "distribution", "platforms"], + "required": [ + "kind", + "name", + "description", + "distribution", + "platforms" + ], "additionalProperties": false, "properties": { - "kind": { "const": "local-daemon" }, - "name": { "$ref": "#/definitions/nonEmptyString" }, - "description": { "$ref": "#/definitions/nonEmptyString" }, + "kind": { + "const": "local-daemon" + }, + "name": { + "$ref": "#/definitions/nonEmptyString" + }, + "description": { + "$ref": "#/definitions/nonEmptyString" + }, "distribution": { "const": "official-upstream-only", "description": "DesireCore does not mirror or repackage this third-party component." @@ -421,41 +2199,121 @@ "minItems": 2, "maxItems": 3, "uniqueItems": true, - "items": { "type": "string", "enum": ["macos", "windows", "linux"] }, - "allOf": [{ "contains": { "const": "macos" } }, { "contains": { "const": "windows" } }] + "items": { + "type": "string", + "enum": [ + "macos", + "windows", + "linux" + ] + }, + "allOf": [ + { + "contains": { + "const": "macos" + } + }, + { + "contains": { + "const": "windows" + } + } + ] } } }, "externalComponent": { "description": "One strictly discriminated Kimi WebBridge physical component.", "oneOf": [ - { "$ref": "#/definitions/browserExtensionComponent" }, - { "$ref": "#/definitions/localDaemonComponent" } + { + "$ref": "#/definitions/browserExtensionComponent" + }, + { + "$ref": "#/definitions/localDaemonComponent" + } ] }, "artifactReview": { "type": "object", - "required": ["component", "platform", "architecture", "version", "url", "sha256", "reviewedAt", "reviewedBy"], + "required": [ + "component", + "platform", + "architecture", + "version", + "url", + "sha256", + "reviewedAt", + "reviewedBy" + ], "additionalProperties": false, "properties": { - "component": { "const": "local-daemon" }, - "platform": { "type": "string", "enum": ["macos", "windows", "linux"] }, - "architecture": { "type": "string", "enum": ["x64", "arm64"] }, - "version": { "$ref": "#/definitions/version" }, - "url": { "$ref": "#/definitions/httpsUrl" }, - "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" }, - "reviewedAt": { "type": "string", "format": "date" }, - "reviewedBy": { "$ref": "#/definitions/nonEmptyString" } + "component": { + "const": "local-daemon" + }, + "platform": { + "type": "string", + "enum": [ + "macos", + "windows", + "linux" + ] + }, + "architecture": { + "type": "string", + "enum": [ + "x64", + "arm64" + ] + }, + "version": { + "$ref": "#/definitions/version" + }, + "url": { + "$ref": "#/definitions/httpsUrl" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "reviewedAt": { + "type": "string", + "format": "date" + }, + "reviewedBy": { + "$ref": "#/definitions/nonEmptyString" + } } }, "externalIntegration": { "type": "object", "required": [ - "id", "name", "type", "version", "author", "description", "category", "shortDesc", - "fullDesc", "tags", "icon", "platformSupport", "requiredClientVersion", "stewardship", - "availability", "redistribution", "branding", "listingMaintainer", "upstreamMaintainer", - "integration", "officialLinks", "components", "permissions", "profileRecommendation", - "dataBoundaries", "admission", "compliance" + "id", + "name", + "type", + "version", + "author", + "description", + "category", + "shortDesc", + "fullDesc", + "tags", + "icon", + "platformSupport", + "requiredClientVersion", + "stewardship", + "availability", + "redistribution", + "branding", + "listingMaintainer", + "upstreamMaintainer", + "integration", + "officialLinks", + "components", + "permissions", + "profileRecommendation", + "dataBoundaries", + "admission", + "compliance" ], "additionalProperties": false, "properties": { @@ -463,18 +2321,40 @@ "const": "kimi-webbridge", "description": "The only external-integration ID admitted by Registry Schema v4." }, - "name": { "$ref": "#/definitions/nonEmptyString" }, - "type": { "const": "external-integration" }, - "version": { "$ref": "#/definitions/version" }, - "author": { "$ref": "#/definitions/nonEmptyString" }, - "description": { "$ref": "#/definitions/nonEmptyString" }, - "category": { "const": "tools" }, - "shortDesc": { "$ref": "#/definitions/nonEmptyString" }, - "fullDesc": { "$ref": "#/definitions/nonEmptyString" }, - "tags": { "$ref": "#/definitions/tags" }, + "name": { + "$ref": "#/definitions/nonEmptyString" + }, + "type": { + "const": "external-integration" + }, + "version": { + "$ref": "#/definitions/version" + }, + "author": { + "$ref": "#/definitions/nonEmptyString" + }, + "description": { + "$ref": "#/definitions/nonEmptyString" + }, + "category": { + "const": "tools" + }, + "shortDesc": { + "$ref": "#/definitions/nonEmptyString" + }, + "fullDesc": { + "$ref": "#/definitions/nonEmptyString" + }, + "tags": { + "$ref": "#/definitions/tags" + }, "icon": { "type": "string", - "enum": ["monitor", "puzzle", "globe"], + "enum": [ + "monitor", + "puzzle", + "globe" + ], "description": "A neutral DesireCore icon name; upstream logos are not permitted in this listing type." }, "platformSupport": { @@ -482,38 +2362,92 @@ "minItems": 2, "maxItems": 2, "uniqueItems": true, - "items": { "type": "string", "enum": ["macos", "windows"] }, - "allOf": [{ "contains": { "const": "macos" } }, { "contains": { "const": "windows" } }], + "items": { + "type": "string", + "enum": [ + "macos", + "windows" + ] + }, + "allOf": [ + { + "contains": { + "const": "macos" + } + }, + { + "contains": { + "const": "windows" + } + } + ], "description": "The reviewed listing must support both macOS and Windows." }, "requiredClientVersion": { - "allOf": [{ "$ref": "#/definitions/version" }], + "allOf": [ + { + "$ref": "#/definitions/version" + } + ], "description": "Minimum DesireCore version that understands this strict listing-only entry." }, - "stewardship": { "const": "pointer" }, - "availability": { "const": "listing-only" }, - "redistribution": { "const": "source-pointer-only" }, + "stewardship": { + "const": "pointer" + }, + "availability": { + "const": "listing-only" + }, + "redistribution": { + "const": "source-pointer-only" + }, "branding": { "type": "object", - "required": ["relationship", "nameUsage", "logoStatus"], + "required": [ + "relationship", + "nameUsage", + "logoStatus" + ], "additionalProperties": false, "properties": { - "relationship": { "const": "independent-listing" }, - "nameUsage": { "const": "nominative" }, - "logoStatus": { "const": "not-used" } + "relationship": { + "const": "independent-listing" + }, + "nameUsage": { + "const": "nominative" + }, + "logoStatus": { + "const": "not-used" + } } }, - "listingMaintainer": { "$ref": "#/definitions/maintainer" }, - "upstreamMaintainer": { "$ref": "#/definitions/maintainer" }, + "listingMaintainer": { + "$ref": "#/definitions/maintainer" + }, + "upstreamMaintainer": { + "$ref": "#/definitions/maintainer" + }, "integration": { "type": "object", - "required": ["kind", "adapterId", "executionAvailability", "installMode"], + "required": [ + "kind", + "adapterId", + "executionAvailability", + "installMode" + ], "additionalProperties": false, "properties": { - "kind": { "const": "local-extension-bridge" }, - "adapterId": { "$ref": "#/definitions/id" }, - "executionAvailability": { "const": "blocked" }, - "installMode": { "const": "upstream-guided" } + "kind": { + "const": "local-extension-bridge" + }, + "adapterId": { + "$ref": "#/definitions/id" + }, + "executionAvailability": { + "const": "blocked" + }, + "installMode": { + "const": "upstream-guided" + } } }, "officialLinks": { @@ -521,7 +2455,9 @@ "minItems": 6, "maxItems": 6, "uniqueItems": true, - "items": { "$ref": "#/definitions/officialLink" }, + "items": { + "$ref": "#/definitions/officialLink" + }, "description": "One reviewed official source for each required link kind." }, "components": { @@ -529,17 +2465,25 @@ "minItems": 2, "maxItems": 2, "uniqueItems": true, - "items": { "$ref": "#/definitions/externalComponent" }, + "items": { + "$ref": "#/definitions/externalComponent" + }, "description": "The upstream browser extension and local daemon, neither redistributed by DesireCore." }, "permissions": { "type": "object", - "required": ["browserExtension"], + "required": [ + "browserExtension" + ], "additionalProperties": false, "properties": { "browserExtension": { "type": "object", - "required": ["apiPermissions", "hostPermissions", "riskLevel"], + "required": [ + "apiPermissions", + "hostPermissions", + "riskLevel" + ], "additionalProperties": false, "properties": { "apiPermissions": { @@ -549,45 +2493,84 @@ "uniqueItems": true, "items": { "type": "string", - "enum": ["tabs", "activeTab", "debugger", "storage", "alarms", "tabGroups", "windows"] + "enum": [ + "tabs", + "activeTab", + "debugger", + "storage", + "alarms", + "tabGroups", + "windows" + ] } }, "hostPermissions": { "type": "array", "minItems": 1, "maxItems": 1, - "items": { "const": "" } + "items": { + "const": "" + } }, - "riskLevel": { "const": "high" } + "riskLevel": { + "const": "high" + } } } } }, - "profileRecommendation": { "const": "dedicated-browser-profile" }, + "profileRecommendation": { + "const": "dedicated-browser-profile" + }, "dataBoundaries": { "type": "object", - "required": ["webBridgeLocalExecution", "desireCoreModelContext", "modelProviderDisclosureRequired"], + "required": [ + "webBridgeLocalExecution", + "desireCoreModelContext", + "modelProviderDisclosureRequired" + ], "additionalProperties": false, "properties": { "webBridgeLocalExecution": { - "allOf": [{ "$ref": "#/definitions/nonEmptyString" }], + "allOf": [ + { + "$ref": "#/definitions/nonEmptyString" + } + ], "description": "A scoped statement of the upstream claim about the local WebBridge execution path." }, "desireCoreModelContext": { - "allOf": [{ "$ref": "#/definitions/nonEmptyString" }], + "allOf": [ + { + "$ref": "#/definitions/nonEmptyString" + } + ], "description": "A separate disclosure that content entering DesireCore context may be sent to the selected model provider." }, - "modelProviderDisclosureRequired": { "const": true } + "modelProviderDisclosureRequired": { + "const": true + } } }, "admission": { "type": "object", - "required": ["status", "code", "reason", "missingPrerequisites"], + "required": [ + "status", + "code", + "reason", + "missingPrerequisites" + ], "additionalProperties": false, "properties": { - "status": { "const": "blocked" }, - "code": { "const": "native-integration-not-admitted" }, - "reason": { "$ref": "#/definitions/nonEmptyString" }, + "status": { + "const": "blocked" + }, + "code": { + "const": "native-integration-not-admitted" + }, + "reason": { + "$ref": "#/definitions/nonEmptyString" + }, "missingPrerequisites": { "type": "array", "minItems": 4, @@ -607,22 +2590,876 @@ }, "compliance": { "type": "object", - "required": ["reviewedAt", "reviewedBy", "upstreamEndorsed", "relationshipDisclosure", "artifactReviews"], + "required": [ + "reviewedAt", + "reviewedBy", + "upstreamEndorsed", + "relationshipDisclosure", + "artifactReviews" + ], "additionalProperties": false, "properties": { - "reviewedAt": { "type": "string", "format": "date" }, - "reviewedBy": { "$ref": "#/definitions/nonEmptyString" }, - "upstreamEndorsed": { "const": false }, - "relationshipDisclosure": { "const": "independent-listing-not-endorsed" }, + "reviewedAt": { + "type": "string", + "format": "date" + }, + "reviewedBy": { + "$ref": "#/definitions/nonEmptyString" + }, + "upstreamEndorsed": { + "const": false + }, + "relationshipDisclosure": { + "const": "independent-listing-not-endorsed" + }, "artifactReviews": { "type": "array", "minItems": 1, - "items": { "$ref": "#/definitions/artifactReview" }, + "items": { + "$ref": "#/definitions/artifactReview" + }, "description": "Immutable upstream artifact audit facts; these records do not admit installation or redistribution." } } } } + }, + "artifact": { + "type": "object", + "description": "纯声明制品:由宿主读取受核验文件,不需要 Node、Docker、端口或虚假运行进程。安装仍走 Agent 材料/核验/唯一账本。", + "required": [ + "id", + "name", + "type", + "version", + "author", + "description", + "tags", + "icon", + "iconLetter", + "platformSupport", + "category", + "fullDesc", + "shortDesc", + "install", + "source", + "requiredClientVersion", + "placementPolicy", + "stewardship", + "availability", + "license", + "redistribution", + "listingMaintainer", + "upstreamMaintainer", + "branding", + "compliance", + "productKind", + "entrypoints", + "extension" + ], + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/definitions/id" + }, + "name": { + "$ref": "#/definitions/nonEmptyString" + }, + "type": { + "const": "artifact", + "description": "声明制品交付类别,与 productKind 正交。" + }, + "version": { + "$ref": "#/definitions/version" + }, + "author": { + "$ref": "#/definitions/nonEmptyString" + }, + "description": { + "$ref": "#/definitions/nonEmptyString" + }, + "tags": { + "$ref": "#/definitions/tags" + }, + "icon": { + "$ref": "#/definitions/nonEmptyString" + }, + "iconLetter": { + "type": "string", + "minLength": 1, + "maxLength": 2 + }, + "platformSupport": { + "$ref": "#/definitions/platformSupport" + }, + "category": { + "type": "string", + "minLength": 1, + "pattern": "^[a-z0-9-]+$", + "description": "应用分类(slug 格式),可扩展任意分类" + }, + "fullDesc": { + "$ref": "#/definitions/nonEmptyString" + }, + "shortDesc": { + "$ref": "#/definitions/nonEmptyString" + }, + "githubUrl": { + "$ref": "#/definitions/httpsUrl" + }, + "requiredClientVersion": { + "$ref": "#/definitions/version" + }, + "install": { + "type": "object", + "description": "应用安装配置:描述如何安装和部署此应用", + "required": [ + "method", + "requirements" + ], + "allOf": [ + { + "if": { + "properties": { + "method": { + "const": "native-node" + } + }, + "required": [ + "method" + ] + }, + "then": { + "properties": { + "requirements": { + "required": [ + "node" + ], + "properties": { + "docker": { + "const": false + } + } + } + } + } + }, + { + "if": { + "properties": { + "method": { + "const": "artifact" + } + }, + "required": [ + "method" + ] + }, + "then": { + "properties": { + "requirements": { + "properties": { + "docker": { + "const": false + }, + "ports": { + "maxItems": 0 + } + } + } + } + } + } + ], + "properties": { + "method": { + "const": "artifact", + "description": "按安装材料获取、核验和维护纯声明文件。" + }, + "requirements": { + "type": "object", + "description": "系统要求:安装前需满足的硬件和环境条件", + "required": [ + "docker", + "ports" + ], + "properties": { + "docker": { + "type": "boolean", + "description": "是否需要 Docker 环境" + }, + "minMemory": { + "type": "string", + "description": "发布者声明的内存要求或建议;未知时省略,不用通用默认值或当前运行用量填充。" + }, + "minDisk": { + "type": "string", + "description": "发布者声明的磁盘空间要求或建议;未知时省略,不代表实测占用。" + }, + "ports": { + "type": "array", + "description": "需要开放的端口号列表", + "items": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + } + }, + "node": { + "type": "string", + "minLength": 1, + "maxLength": 80, + "description": "宿主机 Node.js 版本要求;native-node 安装必填。" + }, + "envVars": { + "type": "array", + "description": "需要配置的环境变量名列表(如 API_KEY、DATABASE_URL)", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "configNeeded": { + "type": "array", + "items": { + "type": "string" + }, + "description": "安装前需准备的配置项(人类可读清单)" + } + }, + "additionalProperties": false + }, + "placementPolicy": { + "type": "object", + "description": "目录维护者声明的可放置范围;只限制能力,不能替用户自动固定或绑定", + "required": [ + "showInMyApps", + "allowNavPin", + "allowAgentBinding" + ], + "properties": { + "showInMyApps": { + "type": "boolean", + "description": "安装后是否允许出现在“我的应用”" + }, + "allowNavPin": { + "type": "boolean", + "description": "用户是否可将应用固定到主导航" + }, + "allowAgentBinding": { + "type": "boolean", + "description": "用户是否可将应用关联到 Agent 会话侧栏" + } + }, + "additionalProperties": false + }, + "source": { + "type": "object", + "description": "经过审核的上游来源指针;安装时必须进一步证明 ref 或制品摘要不可变。", + "required": [ + "kind", + "url", + "ref" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "git", + "release", + "package", + "container" + ], + "description": "上游来源类型:Git 仓库、Release 制品、包管理器制品或容器镜像。" + }, + "url": { + "type": "string", + "format": "uri", + "description": "权威上游地址;不得由客户端在安装请求中替换。" + }, + "ref": { + "type": "string", + "minLength": 1, + "description": "审核版本引用;安装候选必须是完整 commit、制品版本或容器 digest。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-fA-F0-9]{64}$", + "description": "Release、package 或其他下载响应体的 SHA-256 摘要。" + }, + "path": { + "type": "string", + "minLength": 1, + "description": "单仓库目录中的相对路径;不得作为运行时任意本地路径使用。" + }, + "upstreamRepo": { + "type": "string", + "format": "uri", + "description": "制品与源码不在同一地址时对应的上游源码仓库。" + } + }, + "additionalProperties": false + }, + "stewardship": { + "type": "string", + "enum": [ + "official", + "partner", + "community", + "pointer" + ], + "description": "条目治理身份:official 仅能由受信 official catalog provider 注入;partner/community/pointer 分别表示合作方、社区维护或纯上游指针。" + }, + "availability": { + "type": "string", + "enum": [ + "listing-only", + "installable" + ], + "description": "目录可用性:listing-only 仅允许浏览上游;installable 才允许进入既有受治理获取流程,不表示已经安装或正在运行。" + }, + "license": { + "type": "string", + "minLength": 1, + "description": "覆盖实际制品的许可证标识。" + }, + "redistribution": { + "type": "string", + "enum": [ + "allowed", + "source-pointer-only", + "verify-package-terms" + ], + "description": "内容转交策略:allowed 可按许可证分发;source-pointer-only 只能从权威上游取得;verify-package-terms 表示条款尚未完成复核。" + }, + "listingMaintainer": { + "type": "object", + "description": "目录策展者或上游维护者身份;两种角色必须分别存放,不能把 author 当成已验证维护者。", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "维护者的公开名称或组织名称。" + }, + "url": { + "type": "string", + "format": "uri", + "description": "维护者的公开主页、项目主页或可验证身份页面。" + }, + "verified": { + "type": "boolean", + "description": "平台是否已验证该身份;不等同于许可证授权、内容审核或上游背书。" + } + }, + "additionalProperties": false + }, + "upstreamMaintainer": { + "type": "object", + "description": "目录策展者或上游维护者身份;两种角色必须分别存放,不能把 author 当成已验证维护者。", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "维护者的公开名称或组织名称。" + }, + "url": { + "type": "string", + "format": "uri", + "description": "维护者的公开主页、项目主页或可验证身份页面。" + }, + "verified": { + "type": "boolean", + "description": "平台是否已验证该身份;不等同于许可证授权、内容审核或上游背书。" + } + }, + "additionalProperties": false + }, + "branding": { + "type": "object", + "description": "名称、Logo 和官方关系的展示依据;代码许可证不能自动授予商标或宣传素材权利。", + "required": [ + "relationship", + "nameUsage", + "logoStatus" + ], + "properties": { + "relationship": { + "type": "string", + "enum": [ + "official", + "partner", + "independent-listing" + ], + "description": "与上游的公开关系:官方、已确认合作,或独立事实索引。" + }, + "nameUsage": { + "type": "string", + "enum": [ + "owned", + "licensed", + "nominative" + ], + "description": "项目名称的使用依据:自有、已获许可,或识别上游所需的指称性使用。" + }, + "logoStatus": { + "type": "string", + "enum": [ + "not-used", + "license-covered", + "trademark-policy-allowed", + "permission-obtained" + ], + "description": "Logo 使用依据;未确认权利时必须为 not-used,并由客户端使用中性图标。" + } + }, + "additionalProperties": false + }, + "compliance": { + "type": "object", + "description": "绑定到本次不可变来源的合规审核记录;新版本必须重新审核,不能继承旧版本结论。", + "required": [ + "licenseEvidencePath", + "reviewedRef", + "reviewedAt", + "reviewedBy", + "upstreamEndorsed" + ], + "properties": { + "licenseEvidencePath": { + "type": "string", + "minLength": 1, + "description": "在已审核源码或制品中定位许可证文本的相对路径。" + }, + "noticePath": { + "type": "string", + "minLength": 1, + "description": "需要履行 NOTICE 或署名义务时,对应材料的相对路径。" + }, + "reviewedRef": { + "type": "string", + "minLength": 1, + "description": "实际完成许可证、品牌和供应链复核的不可变 ref,必须与获取来源一致。" + }, + "reviewedAt": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}(?:T.*)?$", + "description": "审核日期或 ISO 8601 时间戳。" + }, + "reviewedBy": { + "type": "string", + "minLength": 1, + "description": "可追溯的审核者或审核流程标识。" + }, + "upstreamEndorsed": { + "type": "boolean", + "description": "上游是否明确认可该条目;false 时必须显示非官方、未经背书声明。" + } + }, + "additionalProperties": false + }, + "productKind": { + "type": "string", + "enum": [ + "application", + "plugin" + ], + "description": "主要产品归属;应用提供独立业务,插件增加宿主能力。旧定义缺失时由受信适配层归一为 application。" + }, + "extension": { + "type": "object", + "required": [ + "apiVersion", + "hosts", + "runtime", + "contributes" + ], + "additionalProperties": false, + "properties": { + "apiVersion": { + "const": "1.0.0", + "description": "宿主扩展协议版本;当前支持 1.0.0。" + }, + "hosts": { + "type": "array", + "minItems": 1, + "maxItems": 2, + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "agent-service", + "desktop-host" + ] + }, + "description": "执行宿主;未实现的宿主只作声明并显示不兼容,客户端展示面在 UI 定义中另行声明。" + }, + "runtime": { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "const": "declarative", + "description": "只由宿主读取声明,不创建执行进程。" + } + }, + "additionalProperties": false + }, + "contributes": { + "type": "array", + "maxItems": 64, + "items": { + "type": "object", + "required": [ + "id", + "point", + "contractVersion", + "definitionRef" + ], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "产品内稳定贡献 ID;与来源、产品及精确安装实例共同定位运行绑定。" + }, + "point": { + "type": "string", + "enum": [ + "document.parsers", + "session.panels", + "session.widgets", + "session.actions", + "tools", + "skills" + ], + "description": "具有实际宿主消费者的贡献点;未知贡献点不得接入。" + }, + "contractVersion": { + "const": 1, + "description": "该贡献点的消费契约版本;不兼容版本不激活。" + }, + "definitionRef": { + "type": "object", + "required": [ + "path", + "sha256" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!/)(?!.*\\\\)(?!.*:)(?!.*\\u0000)(?!.*(?:^|/)(?:\\.|\\.\\.)(?:/|$))(?!.*//)[^/]+(?:/[^/]+)*$", + "description": "固定在核验制品根下的规范 POSIX 相对文件路径;拒绝绝对路径、反斜杠、盘符、空段、点段及 NUL。解析时仍须核验 realpath containment。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "固定文件字节的 SHA-256;消费前核验,编辑须提交新安装修订。" + } + }, + "description": "安装制品根下内容固定的材料引用;相对路径和摘要必须共同匹配。" + } + }, + "description": "包内贡献定义引用;业务层补充贡献 ID 唯一性及内容摘要核验。" + }, + "description": "本产品提供的宿主贡献;仅消费能力的普通应用允许空数组,独立插件必须至少一项。每项定义从安装材料读取并核验。" + }, + "configurationSchemaRef": { + "type": "object", + "required": [ + "path", + "sha256" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!/)(?!.*\\\\)(?!.*:)(?!.*\\u0000)(?!.*(?:^|/)(?:\\.|\\.\\.)(?:/|$))(?!.*//)[^/]+(?:/[^/]+)*$", + "description": "固定在核验制品根下的规范 POSIX 相对文件路径;拒绝绝对路径、反斜杠、盘符、空段、点段及 NUL。解析时仍须核验 realpath containment。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "固定文件字节的 SHA-256;消费前核验,编辑须提交新安装修订。" + } + }, + "description": "安装制品根下内容固定的材料引用;相对路径和摘要必须共同匹配。" + }, + "requires": { + "type": "array", + "maxItems": 64, + "items": { + "type": "object", + "required": [ + "sourceId", + "appId", + "contributionId", + "contractVersion", + "optional" + ], + "additionalProperties": false, + "properties": { + "sourceId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "直接依赖的受信产品来源,禁止按同名产品猜测。" + }, + "appId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "所依赖的应用或插件产品 ID;静态定义不携带安装实例 ID。" + }, + "contributionId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "消费方需要的具体贡献 ID。" + }, + "contractVersion": { + "const": 1, + "description": "消费方支持的贡献契约版本。" + }, + "optional": { + "type": "boolean", + "description": "true 为可选增强;false 缺失时阻断受影响功能,不自动安装或卸载。" + }, + "versionRange": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "可选发布版本 semver 范围,由满足性检查器校验。" + } + }, + "description": "直接能力依赖;运行时须显式解析兼容的精确安装实例,不建设递归依赖求解器。" + }, + "description": "本产品消费的必需或可选直接能力依赖;不产生使用授权。" + } + }, + "not": { + "properties": { + "contributes": { + "maxItems": 0 + }, + "requires": { + "maxItems": 0 + } + } + }, + "description": "产品提供或消费宿主能力的关系契约;至少声明一项贡献或依赖。普通应用可仅消费,独立插件仍须提供实际贡献。" + }, + "entrypoints": { + "type": "array", + "maxItems": 32, + "items": { + "oneOf": [ + { + "type": "object", + "description": "DesireCore 自带并随客户端发布的页面入口;第三方目录不得伪造此入口类型", + "required": [ + "id", + "name", + "kind", + "route" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "应用内稳定入口 ID" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "入口显示名称" + }, + "kind": { + "const": "builtin", + "description": "内置页面入口判别字段" + }, + "route": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^/(?!/)", + "description": "客户端内置路由;必须是单斜杠开头的相对应用路由,不能是 URL" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "由隔离 App Host 承载的 Web 应用入口,运行时 URL 只可由服务端从 serviceRef 解析", + "required": [ + "id", + "name", + "kind", + "serviceRef" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "应用内稳定入口 ID" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "入口显示名称" + }, + "kind": { + "const": "embedded-web", + "description": "隔离 Web 入口判别字段" + }, + "serviceRef": { + "type": "string", + "minLength": 1, + "maxLength": 240, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "Registry 服务 ID 或服务端生成的本机端口引用;不是 URL" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "由系统浏览器打开的目录声明入口;运行时 URL 只可由服务端从 serviceRef 解析", + "required": [ + "id", + "name", + "kind", + "serviceRef" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "应用内稳定入口 ID" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "入口显示名称" + }, + "kind": { + "const": "external", + "description": "外部浏览器入口判别字段" + }, + "serviceRef": { + "type": "string", + "minLength": 1, + "maxLength": 240, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "Registry 服务 ID;不是 URL,解析结果还需 HTTPS 或本机 loopback 校验" + }, + "path": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^/(?!/)", + "description": "可选服务内路径;解析层禁止凭据、反斜杠和目录穿越" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "id", + "name", + "kind" + ], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]*$", + "description": "应用内稳定入口 ID。" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 120, + "description": "交给 Agent 打开的入口显示名。" + }, + "kind": { + "const": "agent", + "description": "读取实例维护资料后委派 Agent;无需服务端口或标准启动接口。" + } + } + } + ], + "description": "应用的一个用户可见启动入口,按 kind 严格判别" + }, + "description": "明确用户入口;plugin 允许空数组,普通应用至少一个入口。" + } + }, + "allOf": [ + { + "if": { + "properties": { + "productKind": { + "const": "plugin" + } + }, + "required": [ + "productKind" + ] + }, + "then": { + "required": [ + "extension" + ], + "properties": { + "extension": { + "properties": { + "contributes": { + "minItems": 1 + } + } + } + } + }, + "else": { + "properties": { + "entrypoints": { + "minItems": 1 + } + } + } + } + ] } } } diff --git a/schemas/registry-manifest.v3.schema.json b/schemas/registry-manifest.v3.schema.json index 68755fd..c3f652e 100644 --- a/schemas/registry-manifest.v3.schema.json +++ b/schemas/registry-manifest.v3.schema.json @@ -22,6 +22,7 @@ "totalEntries": { "type": "integer", "minimum": 0 }, "dockerApps": { "type": "integer", "minimum": 0 }, "nativeApps": { "type": "integer", "minimum": 0 }, + "artifactApps": { "type": "integer", "minimum": 0, "description": "纯声明制品的 App 条目数,包括插件;不是安装数。" }, "mcpServices": { "type": "integer", "minimum": 0 }, "httpApis": { "type": "integer", "minimum": 0 }, "externalIntegrations": { "type": "integer", "minimum": 0 } diff --git a/scripts/catalog/application-contract.mjs b/scripts/catalog/application-contract.mjs new file mode 100644 index 0000000..95a1e8e --- /dev/null +++ b/scripts/catalog/application-contract.mjs @@ -0,0 +1,20 @@ +/** 发布者关系声明的跨字段检查;不推导安装、权限或运行时健康。 */ +export function validateApplicationContract(manifest) { + if (!['docker-app', 'native-app', 'artifact'].includes(manifest.type)) return [] + const errors = [] + const ids = new Set() + for (const entrypoint of Array.isArray(manifest.entrypoints) ? manifest.entrypoints : []) { + if (!entrypoint || typeof entrypoint !== 'object') continue + if (ids.has(entrypoint.id)) errors.push('entrypoints: duplicate entrypoint id ' + entrypoint.id) + ids.add(entrypoint.id) + if (entrypoint.kind === 'builtin') errors.push('entrypoints: third-party Registry cannot declare builtin routes') + if (/^(?:legacy|native)-port:/.test(entrypoint.serviceRef ?? '')) errors.push('entrypoints: reserved port references are minted only by the trusted client adapter') + } + ids.clear() + for (const contribution of Array.isArray(manifest.extension?.contributes) ? manifest.extension.contributes : []) { + if (!contribution || typeof contribution !== 'object') continue + if (ids.has(contribution.id)) errors.push('extension.contributes: duplicate contribution id ' + contribution.id) + ids.add(contribution.id) + } + return errors +} diff --git a/scripts/catalog/static-metadata.test.mjs b/scripts/catalog/static-metadata.test.mjs index 633309a..2a75c61 100644 --- a/scripts/catalog/static-metadata.test.mjs +++ b/scripts/catalog/static-metadata.test.mjs @@ -9,9 +9,9 @@ test('metrics are rejected without interpreting examples or configuration parame assert.deepEqual(findStaticMetrics({ description: 'downloads files', toolCount: 13, configSchema: { properties: { rating: { type: 'number' } } } }), []) }) -test('Registry 4.1 keeps the exact published client schema identity', () => { +test('Registry 4.2 keeps the exact reviewed client schema identity', () => { const version = readFileSync(new URL('../../SCHEMA_VERSION', import.meta.url), 'utf8').trim() - assert.equal(version, '4.1.0') + assert.equal(version, '4.2.0') const bytes = readFileSync(new URL('../../schemas/registry-entry.schema.json', import.meta.url)) - assert.equal(createHash('sha256').update(bytes).digest('hex'), '767e103a66b4f8f722d1eb4d9073c218536e8f551ef43ac766dcd1be8f88fc62') + assert.equal(createHash('sha256').update(bytes).digest('hex'), 'ea63d6305ae17994e595aea009a131c41300d2b7fdb07ec83cc2c19c37b32038') }) diff --git a/scripts/catalog/validate-registry.mjs b/scripts/catalog/validate-registry.mjs index 546fe44..8d4ca5a 100644 --- a/scripts/catalog/validate-registry.mjs +++ b/scripts/catalog/validate-registry.mjs @@ -18,7 +18,7 @@ if (json) { } else { const { counts } = report process.stdout.write(`Registry 校验:${report.ok ? '通过' : '失败'}\n`) - process.stdout.write(`条目 ${counts.totalEntries}(App ${counts.dockerApps + (counts.nativeApps ?? 0)}(原生 ${counts.nativeApps ?? 0}) / MCP ${counts.mcpServices} / HTTP ${counts.httpApis})\n`) + process.stdout.write(`条目 ${counts.totalEntries}(App ${counts.dockerApps + (counts.nativeApps ?? 0) + (counts.artifactApps ?? 0)}(原生 ${counts.nativeApps ?? 0} / 声明制品 ${counts.artifactApps ?? 0}) / MCP ${counts.mcpServices} / HTTP ${counts.httpApis})\n`) process.stdout.write(`Catalog sidecar ${counts.sidecars},legacy fallback ${counts.legacyOnly}\n`) for (const item of report.diagnostics) { process.stdout.write(`${item.level === 'error' ? 'ERROR' : 'WARN '} ${item.file} ${item.path} [${item.code}] ${item.message}\n`) diff --git a/scripts/catalog/validator.mjs b/scripts/catalog/validator.mjs index 3837418..b8b75bf 100644 --- a/scripts/catalog/validator.mjs +++ b/scripts/catalog/validator.mjs @@ -3,6 +3,7 @@ import { isDeepStrictEqual } from 'node:util' import { basename, join, relative, resolve, sep } from 'node:path' import { validateJsonSchema } from './json-schema.mjs' import { findStaticMetrics } from './static-metadata.mjs' +import { validateApplicationContract } from './application-contract.mjs' export const CATALOG_SIDECAR_FILENAME = 'catalog-metadata.v1.json' @@ -76,7 +77,7 @@ function timestampShapeIsConsistent(timestamp) { function validateSidecarSemantics(manifest, sidecar, file) { const errors = [] const add = (code, path, message) => errors.push(diagnostic('error', code, file, path, message)) - const expectedKind = ['docker-app', 'native-app'].includes(manifest.type) ? 'app' : 'service' + const expectedKind = ['docker-app', 'native-app', 'artifact'].includes(manifest.type) ? 'app' : 'service' if (sidecar.identity?.id !== manifest.id) add('identity-mismatch', '$.identity.id', 'sidecar identity.id 必须与 legacy manifest.id 一致') if (sidecar.identity?.kind !== expectedKind) add('kind-mismatch', '$.identity.kind', `legacy ${manifest.type} 必须映射为 ${expectedKind}`) @@ -128,6 +129,10 @@ function validateSidecarSemantics(manifest, sidecar, file) { if (expectedKind === 'app') { if (sidecar.spec?.category !== manifest.category) add('spec-mismatch', '$.spec.category', 'App category 必须与 legacy category 一致') + if (sidecar.spec?.productKind !== undefined && sidecar.spec.productKind !== (manifest.productKind ?? 'application')) add('product-kind-mismatch', '$.spec.productKind', '产品归属必须与 manifest 一致') + if (sidecar.spec?.extension !== undefined && !isDeepStrictEqual(sidecar.spec.extension, manifest.extension)) add('extension-mismatch', '$.spec.extension', '宿主贡献与直接依赖必须与 manifest 一致') + const entrypointKinds = [...new Set((Array.isArray(manifest.entrypoints) ? manifest.entrypoints : []).map(entry => entry?.kind))] + if (sidecar.spec?.entrypointKinds !== undefined && !sameStringSet(sidecar.spec.entrypointKinds, entrypointKinds)) add('entrypoint-mismatch', '$.spec.entrypointKinds', '入口类型摘要必须与 manifest 一致') } else { const expectedProtocol = manifest.type === 'mcp' ? 'mcp' : 'http' if (sidecar.spec?.protocol !== expectedProtocol) add('spec-mismatch', '$.spec.protocol', `Service protocol 必须是 ${expectedProtocol}`) @@ -135,7 +140,7 @@ function validateSidecarSemantics(manifest, sidecar, file) { if (manifest.toolCount !== undefined && sidecar.spec?.toolCount !== manifest.toolCount) add('spec-mismatch', '$.spec.toolCount', 'toolCount 必须与 legacy 一致') } - if (manifest.type === 'native-app') { + if (['native-app', 'artifact'].includes(manifest.type)) { for (const field of ['stewardship', 'availability', 'redistribution', 'listingMaintainer', 'upstreamMaintainer', 'branding', 'compliance']) { if (!isDeepStrictEqual(manifest[field], sidecar.governance?.[field])) add('native-governance-mismatch', '$.governance.' + field, '原生应用 manifest 与 sidecar 的治理事实必须一致') } @@ -201,7 +206,7 @@ export function validateRegistry(repoRoot, options = {}) { const manifestFile = join(root, 'manifest.json') const schemaVersionFile = join(root, 'SCHEMA_VERSION') const rootSchema = readJson(join(root, 'schemas', 'registry-manifest.v3.schema.json'), diagnostics) - const entrySchema = readJson(join(root, 'schemas', 'registry-entry.v3.schema.json'), diagnostics) + const legacyEntrySchema = readJson(join(root, 'schemas', 'registry-entry.v3.schema.json'), diagnostics) const externalEntrySchema = readJson(join(root, 'schemas', 'registry-entry.schema.json'), diagnostics) const sidecarSchema = readJson(join(root, 'schemas', 'catalog-metadata.v1.schema.json'), diagnostics) const rootManifest = readJson(manifestFile, diagnostics) @@ -210,8 +215,10 @@ export function validateRegistry(repoRoot, options = {}) { if (rootManifest && rootSchema) diagnostics.push(...schemaDiagnostics(rootManifest, rootSchema, relativeFile(root, manifestFile))) const schemaVersion = existsSync(schemaVersionFile) ? readFileSync(schemaVersionFile, 'utf8').trim() : '' + const currentEntrySchema = /^4\./.test(schemaVersion) ? externalEntrySchema : legacyEntrySchema if (!schemaVersion) diagnostics.push(diagnostic('error', 'missing-schema-version', 'SCHEMA_VERSION', '$', 'SCHEMA_VERSION 不能为空')) if (rootManifest?.version !== schemaVersion) diagnostics.push(diagnostic('error', 'schema-version-mismatch', 'manifest.json', '$.version', 'manifest.version 必须与 SCHEMA_VERSION 一致')) + if (rootManifest?.dataVersion !== schemaVersion) diagnostics.push(diagnostic('error', 'schema-version-mismatch', 'manifest.json', '$.dataVersion', 'manifest.dataVersion 必须与 SCHEMA_VERSION 一致')) const seenIds = new Set() for (const entryDir of entryDirs) { @@ -229,11 +236,13 @@ export function validateRegistry(repoRoot, options = {}) { counts.totalEntries += 1 if (entry.type === 'docker-app') counts.dockerApps += 1 if (entry.type === 'native-app') counts.nativeApps = (counts.nativeApps ?? 0) + 1 + if (entry.type === 'artifact') counts.artifactApps = (counts.artifactApps ?? 0) + 1 if (entry.type === 'mcp') counts.mcpServices += 1 if (entry.type === 'http-api') counts.httpApis += 1 if (entry.type === 'external-integration') counts.externalIntegrations += 1 - const effectiveEntrySchema = ['external-integration', 'native-app'].includes(entry.type) ? externalEntrySchema : entrySchema + const effectiveEntrySchema = ['external-integration', 'native-app', 'artifact'].includes(entry.type) ? externalEntrySchema : currentEntrySchema if (effectiveEntrySchema) diagnostics.push(...schemaDiagnostics(entry, effectiveEntrySchema, manifestRelative)) + for (const message of validateApplicationContract(entry)) diagnostics.push(diagnostic('error', 'application-contract', manifestRelative, '$', message)) if (entry.id !== basename(entryDir)) diagnostics.push(diagnostic('error', 'directory-id-mismatch', manifestRelative, '$.id', 'manifest.id 必须与 entries/ 目录名一致')) if (seenIds.has(entry.id)) diagnostics.push(diagnostic('error', 'duplicate-id', manifestRelative, '$.id', 'Registry 条目 ID 重复')) seenIds.add(entry.id) @@ -255,10 +264,10 @@ export function validateRegistry(repoRoot, options = {}) { } if (rootManifest?.stats) { - for (const key of ['totalEntries', 'dockerApps', 'nativeApps', 'mcpServices', 'httpApis', 'externalIntegrations']) { - if (key === 'nativeApps' && rootManifest.stats[key] === undefined && !counts.nativeApps) continue + for (const key of ['totalEntries', 'dockerApps', 'nativeApps', 'artifactApps', 'mcpServices', 'httpApis', 'externalIntegrations']) { + if (['nativeApps', 'artifactApps'].includes(key) && rootManifest.stats[key] === undefined && !counts[key]) continue if (rootManifest.stats[key] === undefined && key === 'externalIntegrations' && counts[key] === 0) continue - if (rootManifest.stats[key] !== (key === 'nativeApps' ? (counts.nativeApps ?? 0) : counts[key])) diagnostics.push(diagnostic('error', 'stats-mismatch', 'manifest.json', `$.stats.${key}`, `声明 ${rootManifest.stats[key]},实际 ${counts[key]}`)) + if (rootManifest.stats[key] !== (counts[key] ?? 0)) diagnostics.push(diagnostic('error', 'stats-mismatch', 'manifest.json', `$.stats.${key}`, `声明 ${rootManifest.stats[key]},实际 ${counts[key] ?? 0}`)) } } diff --git a/scripts/catalog/validator.test.mjs b/scripts/catalog/validator.test.mjs index d876c11..f529f94 100644 --- a/scripts/catalog/validator.test.mjs +++ b/scripts/catalog/validator.test.mjs @@ -197,7 +197,7 @@ function codes(report) { return report.errors.map((item) => item.code) } -test('当前 v4.1 条目保持可读,原生应用与既有 App/Service 具备 sidecar', () => { +test('当前 v4.2 条目保持可读,原生应用与既有 App/Service 具备 sidecar', () => { const report = validateRegistry(repoRoot) assert.equal(report.ok, true, JSON.stringify(report.errors, null, 2)) assert.deepEqual({ diff --git a/scripts/native-app.test.mjs b/scripts/native-app.test.mjs index b744c0f..02773b1 100644 --- a/scripts/native-app.test.mjs +++ b/scripts/native-app.test.mjs @@ -17,7 +17,7 @@ function fixture() { const manifest = JSON.parse(readFileSync(join(source, 'manifest.json'), 'utf8')) manifest.stats = { totalEntries: 1, dockerApps: 0, nativeApps: 1, mcpServices: 0, httpApis: 0, externalIntegrations: 0 } writeFileSync(join(root, 'manifest.json'), JSON.stringify(manifest)) - writeFileSync(join(root, 'SCHEMA_VERSION'), '4.1.0\n') + writeFileSync(join(root, 'SCHEMA_VERSION'), manifest.version + '\n') return root } function mutate(root, file, change) { diff --git a/scripts/plugin-contract.test.mjs b/scripts/plugin-contract.test.mjs new file mode 100644 index 0000000..5cf0d4c --- /dev/null +++ b/scripts/plugin-contract.test.mjs @@ -0,0 +1,147 @@ +import assert from 'node:assert/strict' +import { test, afterEach } from 'node:test' +import { cpSync, readFileSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs' +import { join, dirname } from 'node:path' +import { tmpdir } from 'node:os' +import { fileURLToPath } from 'node:url' +import { spawnSync } from 'node:child_process' +import { validateRegistry } from './catalog/validator.mjs' + +const source = join(dirname(fileURLToPath(import.meta.url)), '..') +const temporary = [] +afterEach(() => { for (const root of temporary.splice(0)) rmSync(root, { recursive: true, force: true }) }) +const contribution = { id: 'help', point: 'session.panels', contractVersion: 1, definitionRef: { path: 'panel.json', sha256: 'a'.repeat(64) } } +const requires = [{ sourceId: 'official', appId: 'capability-provider', contributionId: 'help', contractVersion: 1, optional: false }] +const extension = { apiVersion: '1.0.0', hosts: ['agent-service'], runtime: { kind: 'declarative' }, contributes: [contribution] } + +function fixture(type = 'artifact') { + const root = mkdtempSync(join(tmpdir(), 'registry-plugin-')) + temporary.push(root) + cpSync(join(source, 'schemas'), join(root, 'schemas'), { recursive: true }) + cpSync(join(source, 'entries/desirecore-control'), join(root, 'entries/desirecore-control'), { recursive: true }) + const manifest = JSON.parse(readFileSync(join(source, 'manifest.json'), 'utf8')) + manifest.stats = { totalEntries: 1, dockerApps: 0, nativeApps: type === 'native-app' ? 1 : 0, artifactApps: type === 'artifact' ? 1 : 0, mcpServices: 0, httpApis: 0, externalIntegrations: 0 } + writeFileSync(join(root, 'manifest.json'), JSON.stringify(manifest)) + writeFileSync(join(root, 'SCHEMA_VERSION'), manifest.version + '\n') + mutate(root, 'manifest.json', entry => { + entry.type = type + entry.productKind = 'plugin' + entry.entrypoints = [] + entry.extension = structuredClone(extension) + if (type === 'artifact') entry.install = { method: 'artifact', requirements: { docker: false, ports: [] } } + }) + mutate(root, 'catalog-metadata.v1.json', sidecar => { + sidecar.spec.productKind = 'plugin' + sidecar.spec.entrypointKinds = [] + sidecar.spec.extension = structuredClone(extension) + }) + return root +} +function mutate(root, file, change) { + const path = join(root, 'entries/desirecore-control', file) + const data = JSON.parse(readFileSync(path, 'utf8')) + change(data) + writeFileSync(path, JSON.stringify(data)) +} +function result(root) { + const cli = spawnSync(process.execPath, [join(source, 'scripts/validate-registry.mjs'), '--root', root], { encoding: 'utf8' }) + return { cli, offline: validateRegistry(root) } +} +function expectValid(root) { + const report = result(root) + assert.equal(report.cli.status, 0, report.cli.stderr) + assert.equal(report.offline.ok, true, JSON.stringify(report.offline.errors)) + return report +} +function expectInvalid(root) { + const report = result(root) + assert.equal(report.cli.status, 1, report.cli.stdout + report.cli.stderr) + assert.equal(report.offline.ok, false, JSON.stringify(report.offline)) +} + +test('无 UI artifact 插件仍为 App,无端口与执行进程', () => { + const report = expectValid(fixture()) + assert.equal(report.offline.counts.artifactApps, 1) + assert.equal(report.offline.counts.mcpServices, 0) +}) +test('普通原生应用可携带宿主贡献', () => { + const root = fixture('native-app') + mutate(root, 'manifest.json', data => { data.productKind = 'application'; data.entrypoints = [{ id: 'manage', name: '维护', kind: 'agent' }] }) + mutate(root, 'catalog-metadata.v1.json', data => { data.spec.productKind = 'application'; data.spec.entrypointKinds = ['agent'] }) + expectValid(root) +}) +test('旧普通应用省略主要归属时受信归一 application,保留显式维护入口', () => { + const root = fixture('native-app') + mutate(root, 'manifest.json', data => { + delete data.productKind + delete data.extension + data.entrypoints = [{ id: 'manage', name: '维护', kind: 'agent' }] + }) + mutate(root, 'catalog-metadata.v1.json', data => { + data.spec.productKind = 'application' + delete data.spec.extension + data.spec.entrypointKinds = ['agent'] + }) + expectValid(root) + mutate(root, 'catalog-metadata.v1.json', data => { data.spec.productKind = 'plugin' }) + const report = result(root) + assert.equal(report.offline.ok, false) + assert.ok(report.offline.errors.some(error => error.code === 'product-kind-mismatch')) +}) +test('省略归属不能把无入口插件声明冒充旧普通应用', () => { + const root = fixture('native-app') + mutate(root, 'manifest.json', data => { delete data.productKind }) + expectInvalid(root) +}) +test('原生应用的真实服务 runtime 由固定 descriptorRef 定位', () => { + const root = fixture('native-app') + const runtime = { kind: 'service', protocol: 'http', descriptorRef: { path: 'runtime.json', sha256: 'c'.repeat(64) } } + mutate(root, 'manifest.json', data => { data.extension.runtime = structuredClone(runtime) }) + mutate(root, 'catalog-metadata.v1.json', data => { data.spec.extension.runtime = structuredClone(runtime) }) + expectValid(root) +}) +test('普通应用可只依赖能力,依赖 sourceId 不冒充目录身份', () => { + const root = fixture('native-app') + mutate(root, 'manifest.json', data => { data.productKind = 'application'; data.entrypoints = [{ id: 'manage', name: '维护', kind: 'agent' }]; data.extension.contributes = []; data.extension.requires = structuredClone(requires) }) + mutate(root, 'catalog-metadata.v1.json', data => { data.spec.productKind = 'application'; data.spec.entrypointKinds = ['agent']; data.spec.extension.contributes = []; data.spec.extension.requires = structuredClone(requires) }) + expectValid(root) +}) +for (const [name, change] of [ + ['插件无贡献', data => { data.extension.contributes = []; data.extension.requires = structuredClone(requires) }], + ['贡献与依赖均为空', data => { data.extension.contributes = [] }], + ['非法贡献点', data => { data.extension.contributes[0].point = 'arbitrary.code' }], + ['材料相对路径穿越', data => { data.extension.contributes[0].definitionRef.path = '../panel.json' }], + ['材料缺摘要', data => { delete data.extension.contributes[0].definitionRef.sha256 }], + ['artifact 声明端口', data => { data.install.requirements.ports = [9333] }], + ['artifact 伪造执行服务', data => { data.extension.runtime = { kind: 'service', protocol: 'http', descriptorRef: { path: 'runtime.json', sha256: 'c'.repeat(64) } } }], + ['伪造条目来源', data => { data.sourceId = 'official' }], + ['其他层级伪造来源', data => { data.extension.runtime.sourceId = 'official' }], + ['重复贡献 ID', data => { data.extension.contributes.push(structuredClone(contribution)) }], + ['重复入口 ID', data => { data.entrypoints = [{ id: 'x', name: '一', kind: 'agent' }, { id: 'x', name: '二', kind: 'agent' }] }], + ['第三方伪造 builtin', data => { data.entrypoints = [{ id: 'x', name: '一', kind: 'builtin', route: '/settings' }] }], + ['第三方指定 reserved port', data => { data.entrypoints = [{ id: 'x', name: '一', kind: 'external', serviceRef: 'native-port:9333' }] }], + ['结构错误 entrypoints', data => { data.entrypoints = {} }], + ['结构错误贡献数组', data => { data.extension.contributes = [null] }], +]) test('CLI 与离线校验都拒绝 ' + name, () => { + const root = fixture() + mutate(root, 'manifest.json', change) + expectInvalid(root) +}) + +test('同一产品侧录不能改变 productKind 或关系声明', () => { + const root = fixture() + mutate(root, 'catalog-metadata.v1.json', data => { data.spec.productKind = 'application' }) + const report = validateRegistry(root) + assert.equal(report.ok, false) + assert.ok(report.errors.some(error => error.code === 'product-kind-mismatch')) +}) +test('artifact 统计必须对应真实文件,来源摘要不可漂移', () => { + const root = fixture() + mutate(root, 'catalog-metadata.v1.json', data => { data.provenance.content.sha256 = 'b'.repeat(64) }) + assert.equal(validateRegistry(root).ok, false) + const manifestPath = join(root, 'manifest.json') + const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) + manifest.stats.artifactApps = 0 + writeFileSync(manifestPath, JSON.stringify(manifest)) + expectInvalid(root) +}) diff --git a/scripts/validate-registry.mjs b/scripts/validate-registry.mjs index 0f3008e..f9b2378 100644 --- a/scripts/validate-registry.mjs +++ b/scripts/validate-registry.mjs @@ -3,6 +3,7 @@ import { dirname, join, relative, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import Ajv from 'ajv' import { findStaticMetrics } from './catalog/static-metadata.mjs' +import { validateApplicationContract } from './catalog/application-contract.mjs' const scriptDirectory = dirname(fileURLToPath(import.meta.url)) const rootArgumentIndex = process.argv.indexOf('--root') @@ -46,11 +47,13 @@ const sorted = (values) => [...values].sort() const sameSet = (actual, expected) => JSON.stringify(sorted(actual)) === JSON.stringify(sorted(expected)) -const containsKey = (value, prohibitedKey) => { - if (Array.isArray(value)) return value.some((item) => containsKey(item, prohibitedKey)) +const containsKey = (value, prohibitedKey, path = '') => { + if (Array.isArray(value)) return value.some((item, index) => containsKey(item, prohibitedKey, `${path}[${index}]`)) if (!value || typeof value !== 'object') return false - if (Object.hasOwn(value, prohibitedKey)) return true - return Object.values(value).some((item) => containsKey(item, prohibitedKey)) + // 静态依赖必须指明产品来源;这不是客户端注入的条目来源身份。 + const declaredDependency = prohibitedKey === 'sourceId' && /^extension\.requires\[\d+\]$/.test(path) + if (Object.hasOwn(value, prohibitedKey) && !declaredDependency) return true + return Object.entries(value).some(([key, item]) => containsKey(item, prohibitedKey, path ? `${path}.${key}` : key)) } const assertHttpsUrl = (rawUrl, context) => { @@ -290,7 +293,8 @@ if (rootManifest) { let validateEntry = null if (entrySchema) { try { - const ajv = new Ajv({ allErrors: true, strict: true, validateFormats: false }) + // Draft-07 条件块补充父 Schema 的约束;无需在每个 then/not 再写局部 type。 + const ajv = new Ajv({ allErrors: true, strict: true, strictTypes: false, strictRequired: false, validateFormats: false }) validateEntry = ajv.compile(entrySchema) } catch (error) { addError(`schemas/registry-entry.schema.json: unable to compile (${error.message})`) @@ -343,6 +347,7 @@ for (const directoryEntry of await readdir(entriesRoot, { withFileTypes: true }) if (validateEntry && !validateEntry(manifest)) { addError(`entries/${directoryEntry.name}/manifest.json: ${formatAjvErrors(validateEntry.errors)}`) } + for (const message of validateApplicationContract(manifest)) addError(`entries/${directoryEntry.name}/manifest.json: ${message}`) if (manifest.type === 'external-integration') { await validateExternalIntegration(entryDirectory, manifest) } @@ -353,6 +358,7 @@ if (rootManifest) { totalEntries: manifests.length, dockerApps: manifests.filter((entry) => entry.type === 'docker-app').length, ...(manifests.some((entry) => entry.type === 'native-app') || rootManifest.stats?.nativeApps !== undefined ? { nativeApps: manifests.filter((entry) => entry.type === 'native-app').length } : {}), + ...(manifests.some((entry) => entry.type === 'artifact') || rootManifest.stats?.artifactApps !== undefined ? { artifactApps: manifests.filter((entry) => entry.type === 'artifact').length } : {}), mcpServices: manifests.filter((entry) => entry.type === 'mcp').length, httpApis: manifests.filter((entry) => entry.type === 'http-api').length, externalIntegrations: manifests.filter((entry) => entry.type === 'external-integration').length, @@ -380,7 +386,7 @@ if (errors.length > 0) { const counts = rootManifest.stats console.log( `Registry validation passed: ${counts.totalEntries} entries ` + - `(${counts.dockerApps} Docker, ${counts.mcpServices} MCP, ` + + `(${counts.dockerApps} Docker, ${counts.nativeApps ?? 0} native, ${counts.artifactApps ?? 0} artifact, ${counts.mcpServices} MCP, ` + `${counts.httpApis} HTTP API, ${counts.externalIntegrations} external integration).`, ) }