feat: 建立统一目录元数据与获取契约 (#5)

* feat: 建立 Registry 目录元数据验证基础

* feat: 迁移 Registry 应用目录元数据

* test: 解耦目录迁移进度断言

* feat: 迁移 Registry 服务目录元数据

* fix(catalog): 强制 Registry 目录元数据

* fix(catalog): 兼容 Registry v4 外部集成
This commit is contained in:
2026-08-31 04:03:38 -04:00
committed by GitHub
parent 67aeb72b20
commit 223be1f04b
31 changed files with 3169 additions and 10 deletions

View File

@@ -0,0 +1,415 @@
import assert from 'node:assert/strict'
import { afterEach, test } from 'node:test'
import { cpSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import { tmpdir } from 'node:os'
import { fileURLToPath } from 'node:url'
import { validateRegistry } from './validator.mjs'
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..')
const temporaryRoots = []
afterEach(() => {
while (temporaryRoots.length > 0) rmSync(temporaryRoots.pop(), { recursive: true, force: true })
})
function writeJson(file, value) {
mkdirSync(dirname(file), { recursive: true })
writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`)
}
function legacyApp(overrides = {}) {
return {
id: 'demo-app',
name: 'Demo App',
type: 'docker-app',
version: '1.2.3',
author: 'Demo Org',
description: '一个用于测试的应用',
tags: ['demo'],
icon: 'box',
iconLetter: 'D',
platformSupport: ['macos', 'windows', 'linux'],
category: 'tools',
shortDesc: '一个用于测试的应用',
fullDesc: '这是用于验证 Registry 契约的测试应用。',
install: {
method: 'docker',
requirements: { docker: true, minMemory: '1GB', minDisk: '1GB', ports: [8080] },
configNeeded: ['Docker'],
},
...overrides,
}
}
function legacyMcp(overrides = {}) {
return {
id: 'demo-mcp',
name: 'Demo MCP',
type: 'mcp',
version: '2026.8.31',
author: 'Demo Org',
description: '一个用于测试的 MCP 服务',
tags: ['demo'],
icon: 'terminal',
platformSupport: ['macos', 'windows', 'linux'],
capabilities: ['read'],
toolCount: 1,
install: { method: 'npx', packageName: '@demo/mcp', command: 'npx', args: ['-y', '@demo/mcp@1.0.0'] },
connection: { transport: 'stdio', command: 'npx', args: ['-y', '@demo/mcp@1.0.0'] },
...overrides,
}
}
function legacyHttp(overrides = {}) {
return {
id: 'demo-http',
name: 'Demo HTTP',
type: 'http-api',
version: '3.0',
author: 'Demo Org',
description: '一个用于测试的 HTTP 服务',
tags: ['demo'],
icon: 'globe',
platformSupport: ['macos', 'windows', 'linux'],
endpoint: 'https://api.example.com/v1',
capabilities: ['query'],
...overrides,
}
}
function unknownTimestamps(overrides = {}) {
return {
catalogUpdatedAt: { state: 'unknown' },
releasePublishedAt: { state: 'unknown' },
reviewedAt: { state: 'unknown' },
upstreamObservedAt: { state: 'unknown' },
...overrides,
}
}
function appSidecar(overrides = {}) {
return {
$schema: '../../schemas/catalog-metadata.v1.schema.json',
schemaVersion: 1,
identity: { kind: 'app', id: 'demo-app' },
presentation: {
defaultLocale: 'zh-CN',
i18n: {
'zh-CN': {
name: 'Demo App',
summary: '一个用于测试的应用',
description: '这是用于验证 Registry 契约的测试应用。',
},
},
tags: ['demo'],
},
release: { state: 'known', version: '1.2.3', versionScheme: 'semver' },
timestamps: unknownTimestamps(),
provenance: {},
governance: { availability: 'listing-only', license: { state: 'unknown' }, redistribution: 'verify-package-terms' },
compatibility: { platforms: { state: 'all' } },
spec: { kind: 'app', category: 'tools' },
...overrides,
}
}
function mcpSidecar(overrides = {}) {
return {
$schema: '../../schemas/catalog-metadata.v1.schema.json',
schemaVersion: 1,
identity: { kind: 'service', id: 'demo-mcp' },
presentation: {
defaultLocale: 'zh-CN',
i18n: { 'zh-CN': { name: 'Demo MCP', summary: '一个用于测试的 MCP 服务' } },
tags: ['demo'],
},
release: { state: 'known', version: '2026.8.31', versionScheme: 'calver' },
timestamps: unknownTimestamps(),
provenance: {},
governance: { availability: 'listing-only', license: { state: 'unknown' }, redistribution: 'verify-package-terms' },
compatibility: { platforms: { state: 'known', values: ['macos', 'windows', 'linux'] } },
spec: { kind: 'service', protocol: 'mcp', capabilities: ['read'], toolCount: 1 },
...overrides,
}
}
function httpSidecar(overrides = {}) {
return {
$schema: '../../schemas/catalog-metadata.v1.schema.json',
schemaVersion: 1,
identity: { kind: 'service', id: 'demo-http' },
presentation: {
defaultLocale: 'zh-CN',
i18n: { 'zh-CN': { name: 'Demo HTTP', summary: '一个用于测试的 HTTP 服务' } },
tags: ['demo'],
},
release: { state: 'known', version: '3.0', versionScheme: 'opaque' },
timestamps: unknownTimestamps(),
provenance: {},
governance: { availability: 'listing-only', license: { state: 'unknown' }, redistribution: 'verify-package-terms' },
compatibility: { platforms: { state: 'all' } },
spec: { kind: 'service', protocol: 'http', authType: 'unknown', capabilities: ['query'] },
...overrides,
}
}
function makeRegistry(entries, sidecars = new Map(), rootOverrides = {}) {
const root = mkdtempSync(join(tmpdir(), 'registry-validator-'))
temporaryRoots.push(root)
cpSync(join(repoRoot, 'schemas'), join(root, 'schemas'), { recursive: true })
const counts = {
totalEntries: entries.length,
dockerApps: entries.filter((entry) => entry.type === 'docker-app').length,
mcpServices: entries.filter((entry) => entry.type === 'mcp').length,
httpApis: entries.filter((entry) => entry.type === 'http-api').length,
}
writeFileSync(join(root, 'SCHEMA_VERSION'), '3.1.0\n')
writeJson(join(root, 'manifest.json'), {
$schema: 'http://json-schema.org/draft-07/schema#',
id: 'desirecore-registry-manifest',
version: '3.1.0',
name: 'Test Registry',
description: 'Test Registry',
maintainer: 'Test',
repository: 'https://example.com/registry',
lastUpdated: '2026-08-31',
stats: counts,
dataVersion: '3.1.0',
catalogMetadata: {
version: '1.0.0',
schema: 'schemas/catalog-metadata.v1.schema.json',
sidecarPath: 'entries/<id>/catalog-metadata.v1.json',
required: false,
legacyFallback: true,
},
...rootOverrides,
})
for (const entry of entries) {
const entryDir = join(root, 'entries', entry.id)
writeJson(join(entryDir, 'manifest.json'), entry)
if (sidecars.has(entry.id)) writeJson(join(entryDir, 'catalog-metadata.v1.json'), sidecars.get(entry.id))
}
return root
}
function codes(report) {
return report.errors.map((item) => item.code)
}
test('当前 v4 条目保持可读21 个 App/Service 具备 sidecar', () => {
const report = validateRegistry(repoRoot)
assert.equal(report.ok, true, JSON.stringify(report.errors, null, 2))
assert.deepEqual({
totalEntries: report.counts.totalEntries,
dockerApps: report.counts.dockerApps,
mcpServices: report.counts.mcpServices,
httpApis: report.counts.httpApis,
externalIntegrations: report.counts.externalIntegrations,
}, {
totalEntries: 22,
dockerApps: 8,
mcpServices: 8,
httpApis: 5,
externalIntegrations: 1,
})
assert.equal(report.counts.sidecars + report.counts.legacyOnly, report.counts.totalEntries - report.counts.externalIntegrations)
assert.equal(report.warnings.filter((item) => item.code === 'missing-sidecar').length, report.counts.legacyOnly)
})
test('合法 listing-only App sidecar 通过', () => {
const report = validateRegistry(makeRegistry([legacyApp()], new Map([['demo-app', appSidecar()]])))
assert.equal(report.ok, true, JSON.stringify(report.errors, null, 2))
assert.equal(report.counts.sidecars, 1)
})
test('合法 MCP sidecar 保留 CalVer、能力和 toolCount', () => {
const report = validateRegistry(makeRegistry([legacyMcp()], new Map([['demo-mcp', mcpSidecar()]])))
assert.equal(report.ok, true, JSON.stringify(report.errors, null, 2))
})
test('合法 HTTP sidecar 保留 opaque 版本、协议、鉴权 unknown 和能力', () => {
const report = validateRegistry(makeRegistry([legacyHttp()], new Map([['demo-http', httpSidecar()]])))
assert.equal(report.ok, true, JSON.stringify(report.errors, null, 2))
})
test('stats 与实际目录不一致时失败', () => {
const root = makeRegistry([legacyApp()], new Map(), { stats: { totalEntries: 99, dockerApps: 8, mcpServices: 0, httpApis: 0 } })
const report = validateRegistry(root)
assert.equal(report.ok, false)
assert.ok(codes(report).includes('stats-mismatch'))
})
test('目录名与 manifest.id 不一致时失败', () => {
const root = makeRegistry([legacyApp()])
const source = join(root, 'entries', 'demo-app')
const target = join(root, 'entries', 'wrong-directory')
cpSync(source, target, { recursive: true })
rmSync(source, { recursive: true, force: true })
const report = validateRegistry(root)
assert.equal(report.ok, false)
assert.ok(codes(report).includes('directory-id-mismatch'))
})
test('legacy type-specific 必需字段缺失时失败', () => {
const entry = legacyMcp()
delete entry.connection
const report = validateRegistry(makeRegistry([entry]))
assert.equal(report.ok, false)
assert.ok(codes(report).includes('schema'))
})
test('sidecar 严格拒绝额外字段和正文 sourceId', () => {
const sidecar = appSidecar({ sourceId: 'registry:official', unexpected: true })
const report = validateRegistry(makeRegistry([legacyApp()], new Map([['demo-app', sidecar]])))
assert.equal(report.ok, false)
assert.ok(report.errors.filter((item) => item.code === 'schema').length >= 2)
})
test('sidecar 拒绝正文 official 治理声明', () => {
const sidecar = appSidecar({
governance: {
availability: 'listing-only',
stewardship: 'official',
license: { state: 'unknown' },
redistribution: 'verify-package-terms',
},
})
const report = validateRegistry(makeRegistry([legacyApp()], new Map([['demo-app', sidecar]])))
assert.equal(report.ok, false)
assert.ok(codes(report).includes('schema'))
})
test('i18n 必须包含默认语言且不能伪造重复翻译', () => {
const missingDefault = appSidecar({
presentation: { defaultLocale: 'en-US', i18n: { 'zh-CN': appSidecar().presentation.i18n['zh-CN'] } },
})
let report = validateRegistry(makeRegistry([legacyApp()], new Map([['demo-app', missingDefault]])))
assert.ok(codes(report).includes('missing-default-locale'))
const duplicated = appSidecar()
duplicated.presentation.i18n['en-US'] = { ...duplicated.presentation.i18n['zh-CN'] }
report = validateRegistry(makeRegistry([legacyApp()], new Map([['demo-app', duplicated]])))
assert.ok(codes(report).includes('duplicate-translation'))
})
test('证据路径拒绝绝对路径和目录穿越', () => {
const sidecar = appSidecar({
provenance: { content: { kind: 'git', url: 'https://example.com/repo.git', ref: 'a'.repeat(40), path: '../secret' } },
governance: {
availability: 'listing-only',
license: { state: 'unknown' },
redistribution: 'verify-package-terms',
compliance: {
licenseEvidencePath: '/tmp/LICENSE',
reviewedRef: 'a'.repeat(40),
reviewedAt: '2026-08-31',
reviewedBy: 'test',
upstreamEndorsed: false,
},
},
})
const report = validateRegistry(makeRegistry([legacyApp()], new Map([['demo-app', sidecar]])))
assert.equal(report.ok, false)
assert.equal(codes(report).filter((code) => code === 'unsafe-evidence-path').length, 2)
})
test('mutable source 可 listing-only但不能 installable', () => {
const mutableSource = { kind: 'git', url: 'https://example.com/repo.git', ref: 'main' }
let sidecar = appSidecar({ provenance: { content: mutableSource } })
let report = validateRegistry(makeRegistry([legacyApp()], new Map([['demo-app', sidecar]])))
assert.equal(report.ok, true, JSON.stringify(report.errors, null, 2))
sidecar = appSidecar({
provenance: { content: mutableSource },
timestamps: unknownTimestamps({ reviewedAt: { state: 'known', value: '2026-08-31', precision: 'day' } }),
governance: {
availability: 'installable',
stewardship: 'pointer',
license: { state: 'known', value: 'MIT', evidencePath: 'LICENSE' },
redistribution: 'source-pointer-only',
listingMaintainer: { name: 'DesireCore Team', verified: true },
upstreamMaintainer: { name: 'Demo Org', verified: false },
branding: { relationship: 'independent-listing', nameUsage: 'nominative', logoStatus: 'not-used' },
compliance: {
licenseEvidencePath: 'LICENSE',
reviewedRef: 'main',
reviewedAt: '2026-08-31',
reviewedBy: 'catalog-review-v1',
upstreamEndorsed: false,
},
},
})
report = validateRegistry(makeRegistry([legacyApp()], new Map([['demo-app', sidecar]])))
assert.equal(report.ok, false)
assert.ok(codes(report).includes('installable-without-evidence'))
})
test('完整不可变来源与审核证据允许 installable', () => {
const ref = 'a'.repeat(40)
const sidecar = appSidecar({
provenance: { content: { kind: 'git', url: 'https://example.com/repo.git', ref } },
timestamps: unknownTimestamps({ reviewedAt: { state: 'known', value: '2026-08-31', precision: 'day' } }),
governance: {
availability: 'installable',
stewardship: 'pointer',
license: { state: 'known', value: 'MIT', evidencePath: 'LICENSE' },
redistribution: 'source-pointer-only',
listingMaintainer: { name: 'DesireCore Team', verified: true },
upstreamMaintainer: { name: 'Demo Org', verified: false },
branding: { relationship: 'independent-listing', nameUsage: 'nominative', logoStatus: 'not-used' },
compliance: {
licenseEvidencePath: 'LICENSE',
reviewedRef: ref,
reviewedAt: '2026-08-31',
reviewedBy: 'catalog-review-v1',
upstreamEndorsed: false,
},
},
})
const report = validateRegistry(makeRegistry([legacyApp()], new Map([['demo-app', sidecar]])))
assert.equal(report.ok, true, JSON.stringify(report.errors, null, 2))
})
test('审核 ref 和审核时间必须绑定同一来源与 timestamps', () => {
const sidecar = appSidecar({
provenance: { content: { kind: 'git', url: 'https://example.com/repo.git', ref: 'a'.repeat(40) } },
timestamps: unknownTimestamps({ reviewedAt: { state: 'known', value: '2026-08-31', precision: 'day' } }),
governance: {
availability: 'listing-only',
license: { state: 'unknown' },
redistribution: 'verify-package-terms',
compliance: {
licenseEvidencePath: 'LICENSE',
reviewedRef: 'b'.repeat(40),
reviewedAt: '2026-08-30',
reviewedBy: 'catalog-review-v1',
upstreamEndorsed: false,
},
},
})
const report = validateRegistry(makeRegistry([legacyApp()], new Map([['demo-app', sidecar]])))
assert.ok(codes(report).includes('review-ref-mismatch'))
assert.ok(codes(report).includes('review-time-mismatch'))
})
test('--require-sidecars 等价策略会让 legacy-only 失败', () => {
const report = validateRegistry(makeRegistry([legacyApp()]), { requireSidecars: true })
assert.equal(report.ok, false)
assert.ok(codes(report).includes('missing-sidecar'))
})
test('根 manifest 声明 required 时默认拒绝缺失 sidecar', () => {
const root = makeRegistry([legacyApp()], new Map(), {
catalogMetadata: {
version: '1.0.0',
schema: 'schemas/catalog-metadata.v1.schema.json',
sidecarPath: 'entries/<id>/catalog-metadata.v1.json',
required: true,
legacyFallback: true,
},
})
const report = validateRegistry(root)
assert.equal(report.ok, false)
assert.ok(codes(report).includes('missing-sidecar'))
})