fix(data): complete verified contracts and source maintenance (#114)

This commit is contained in:
2026-10-03 03:49:55 -07:00
committed by GitHub
parent 647d6f0cfd
commit 642985e819
208 changed files with 5313 additions and 2285 deletions

View File

@@ -0,0 +1,128 @@
import { readFile, writeFile } from 'node:fs/promises'
import { createHash } from 'node:crypto'
import { fileURLToPath } from 'node:url'
import { resolve, dirname } from 'node:path'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
export const SOURCES = Object.freeze({
node: 'https://nodejs.org/dist/index.json',
npm: 'https://api.github.com/repos/npm/cli/releases?per_page=100',
pnpm: 'https://api.github.com/repos/pnpm/pnpm/releases?per_page=100',
yarn: 'https://api.github.com/repos/yarnpkg/berry/releases?per_page=100',
hatch: 'https://raw.githubusercontent.com/pypa/hatch/b998d2b755bc0dca20054f96da8981532444fc2a/src/hatch/python/distributions.py',
})
const hash = value => createHash('sha256').update(JSON.stringify(value)).digest('hex')
const stableVersion = /^v\d+\.\d+\.\d+$/
export function selectNodeVersions(rows, now) {
if (!Array.isArray(rows)) throw new Error('Invalid official Node index')
const counts = new Map()
const selected = rows.filter(row => {
if (!stableVersion.test(row.version) || !/^\d{4}-\d{2}-\d{2}$/.test(row.date)) return false
if (row.date > now.slice(0, 10)) return false
const major = Number(row.version.split('.')[0].slice(1))
if (major < 16 || (counts.get(major) ?? 0) >= 3) return false
counts.set(major, (counts.get(major) ?? 0) + 1)
return true
})
if (!selected.length) throw new Error('No stable Node releases')
return selected
}
export function selectPackageReleases(rows, name, now) {
if (!Array.isArray(rows)) throw new Error(`Invalid ${name} releases`)
const seen = new Set()
return rows.flatMap(row => {
if (row.draft || row.prerelease) return []
let tag = row.tag_name
if (name === 'yarn') {
const match = /^@yarnpkg\/cli\/(\d+\.\d+\.\d+)$/.exec(tag)
if (!match) return []
tag = `v${match[1]}`
}
if (!stableVersion.test(tag) || seen.has(tag)) return []
if (!row.published_at || !Number.isFinite(Date.parse(row.published_at)) || Date.parse(row.published_at) > Date.parse(now)) return []
seen.add(tag)
// Client-compatible field name; value is publication time, not commit creation time.
return [{ tag_name: tag, created_at: row.published_at }]
}).slice(0, 30)
}
export function verifyRecommended(recommended, nodeRows, sums, pythonRelease, now = new Date().toISOString()) {
const nowMs = Date.parse(now)
if (!Number.isFinite(nowMs)) throw new Error('Invalid verification time')
const publishedMs = Date.parse(pythonRelease.published_at)
if (!Number.isFinite(publishedMs) || publishedMs > nowMs) throw new Error('Python release unpublished or future publication date')
const node = nodeRows.find(row => row.version === `v${recommended.node.version}`)
if (!node || node.lts !== recommended.node.ltsCodename) throw new Error('Recommended Node release/LTS mismatch')
if (!/^\d{4}-\d{2}-\d{2}$/.test(node.date) || node.date > now.slice(0, 10)) throw new Error('Node release missing or future publication date')
if (pythonRelease.tag_name !== recommended.python.distTag || pythonRelease.draft || pythonRelease.prerelease) throw new Error('Python release tag/status mismatch')
const lines = new Map(sums.split('\n').flatMap(line => {
const match = /^([a-f0-9]{64})\s+\*?(\S+)$/.exec(line.trim())
return match ? [[match[2], match[1]]] : []
}))
const nodeSuffixes = { 'darwin-arm64': 'darwin-arm64.tar.gz', 'darwin-x64': 'darwin-x64.tar.gz', 'win32-x64': 'win-x64.zip', 'win32-arm64': 'win-arm64.zip', 'linux-x64': 'linux-x64.tar.gz', 'linux-arm64': 'linux-arm64.tar.gz' }
const pythonTriples = { 'darwin-arm64': 'aarch64-apple-darwin', 'darwin-x64': 'x86_64-apple-darwin', 'win32-x64': 'x86_64-pc-windows-msvc', 'win32-arm64': 'aarch64-pc-windows-msvc', 'linux-x64': 'x86_64-unknown-linux-gnu', 'linux-arm64': 'aarch64-unknown-linux-gnu' }
for (const platform of Object.keys(nodeSuffixes)) {
const path = recommended.node.archives[platform]
if (path !== `v${recommended.node.version}/node-v${recommended.node.version}-${nodeSuffixes[platform]}`) throw new Error('Unsafe Node archive path')
if (lines.get(path.split('/').at(-1)) !== recommended.node.sha256[platform]) throw new Error(`Node digest mismatch: ${platform}`)
}
for (const platform of Object.keys(pythonTriples)) {
const path = recommended.python.archives[platform]
if (path !== `${recommended.python.distTag}/cpython-${recommended.python.version}+${recommended.python.distTag}-${pythonTriples[platform]}-install_only_stripped.tar.gz`) throw new Error('Unsafe Python archive path')
const asset = pythonRelease.assets.find(row => row.name === path.split('/').at(-1))
if (!asset || asset.state !== 'uploaded' || asset.digest !== `sha256:${recommended.python.sha256[platform]}`) throw new Error(`Python digest mismatch: ${platform}`)
}
}
async function officialText(url) {
const allowed = new Set([...Object.values(SOURCES)])
const recommended = JSON.parse(await readFile(resolve(ROOT, 'runtimes/recommended.json'), 'utf8'))
allowed.add(`https://nodejs.org/dist/v${recommended.node.version}/SHASUMS256.txt`)
allowed.add(`https://api.github.com/repos/astral-sh/python-build-standalone/releases/tags/${recommended.python.distTag}`)
if (!allowed.has(url)) throw new Error(`Unapproved source: ${url}`)
const response = await fetch(url, { redirect: 'error', signal: AbortSignal.timeout(30000), headers: { 'User-Agent': 'config-center-runtime-audit' } })
if (!response.ok) throw new Error(`${url}: HTTP ${response.status}`)
return response.text()
}
async function main() {
const write = process.argv.includes('--write')
if (process.argv.slice(2).some(arg => arg !== '--write')) throw new Error('Usage: node scripts/update-runtime-catalog.mjs [--write]')
const recommended = JSON.parse(await readFile(resolve(ROOT, 'runtimes/recommended.json'), 'utf8'))
const old = JSON.parse(await readFile(resolve(ROOT, 'runtimes/versions-fallback.json'), 'utf8'))
const sumsUrl = `https://nodejs.org/dist/v${recommended.node.version}/SHASUMS256.txt`
const pythonUrl = `https://api.github.com/repos/astral-sh/python-build-standalone/releases/tags/${recommended.python.distTag}`
const urls = [...Object.values(SOURCES), sumsUrl, pythonUrl]
const texts = await Promise.all(urls.map(officialText))
const source = Object.fromEntries(urls.map((url, i) => [url, texts[i]]))
const nodes = JSON.parse(source[SOURCES.node])
const pythonRelease = JSON.parse(source[pythonUrl])
const now = new Date().toISOString()
verifyRecommended(recommended, nodes, source[sumsUrl], pythonRelease, now)
// The fallback mirrors bundled Hatch 1.16.5, not standalone's latest release.
// Fail closed if its existing rows no longer match that official distribution map.
for (const row of old.python) {
const expected = row.name.startsWith('pypy') ? `${row.name}-v${row.version}-` : `cpython-${row.version}`
const escapedName = row.name.replaceAll('.', '\\.')
const section = new RegExp(`^ ['\"]${escapedName}['\"]: \\{([\\s\\S]*?)(?=^ ['\"]|^\\})`, 'm').exec(source[SOURCES.hatch])
if (!section || !section[1].includes(expected)) throw new Error(`Hatch pin mismatch: ${row.name}`)
}
const next = { ...old, generatedAt: now, node: selectNodeVersions(nodes, now), pkgManagers: {} }
for (const name of ['npm', 'pnpm', 'yarn']) {
next.pkgManagers[name] = selectPackageReleases(JSON.parse(source[SOURCES[name]]), name, now)
if (!next.pkgManagers[name].length) throw new Error(`No stable ${name} releases`)
}
if (!next.node.some(row => row.version === `v${recommended.node.version}`)) throw new Error('Recommended Node absent from snapshot')
if (write) {
await writeFile(resolve(ROOT, 'runtimes/versions-fallback.json'), `${JSON.stringify(next, null, 2)}\n`)
for (const name of ['node', 'npm', 'pnpm', 'yarn']) {
const rows = name === 'node' ? next.node : next.pkgManagers[name]
const path = name === 'node' ? 'node-snapshot.md' : `package-managers/${name}-snapshot.md`
await writeFile(resolve(ROOT, 'docs/runtime-sources', path), `# ${name} 离线目录快照\n\n- 实际读取:${now}\n- 官方来源:[官方目录](${SOURCES[name]})\n- 数据位置:\`runtimes/versions-fallback.json#${name === 'node' ? 'node' : `pkgManagers.${name}`}\`\n- 目录条数:${rows.length}\n- 首条版本:${rows[0].version ?? rows[0].tag_name}\n- 本仓条目指纹(JSON.stringify、SHA-256):\`${hash(rows)}\`\n- 本轮读取的官方正文 SHA-256:\`${createHash('sha256').update(source[SOURCES[name]]).digest('hex')}\`\n\n筛选规则:${name === 'node' ? 'Node 16 及以上,每个大版本最多三条正式发布;保留上游顺序。不表示所有大版本仍受支持。' : '读取官方仓库最近 100 个 releases,排除 draft、prerelease、非正式版本标签及未来发布日期,最多 30 条;Yarn 只接受 @yarnpkg/cli 正式标签。created_at 字段保持客户端结构,值取 published_at。目录不代表当前平台安装测试已通过。'}\n`)
}
}
console.log(JSON.stringify({ write, generatedAt: now, node: next.node.length, packageManagers: Object.fromEntries(Object.entries(next.pkgManagers).map(([key, value]) => [key, value.length])), python: 'preserved: bundled Hatch 1.16.5 pin; recommended archive hashes verified against official manifests' }))
}
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) main().catch(error => { console.error(error.message); process.exitCode = 1 })

View File

@@ -0,0 +1,91 @@
import { readFileSync, readdirSync } from 'node:fs'
import { resolve, relative, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { isDeepStrictEqual } from 'node:util'
const ROOT = fileURLToPath(new URL('../', import.meta.url))
const OFFICIAL_HOSTS = {
tavily: ['docs.tavily.com'], brave: ['api.search.brave.com', 'brave.com'],
serper: ['serper.dev'], 'doubao-search-global': ['www.volcengine.com', 'docs.volcengine.com'],
'configured-vision-openai': ['developers.openai.com', 'platform.openai.com'],
'configured-vision-anthropic': ['platform.claude.com', 'docs.anthropic.com'],
}
function files(root, directory, extension) {
const dir = join(root, directory)
return readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
const path = join(directory, entry.name)
return entry.isDirectory() ? files(root, path, extension) : entry.name.endsWith(extension) ? [path] : []
})
}
export function validateDataSources(root = ROOT) {
const errors = [], records = new Map()
const read = path => JSON.parse(readFileSync(join(root, path), 'utf8'))
for (const page of files(root, 'docs/data-sources', '.md')) {
const text = readFileSync(join(root, page), 'utf8')
if (!text.includes('<!-- data-source:start -->')) continue
try {
const match = text.match(/<!-- data-source:start -->\s*```json\s*([\s\S]*?)```\s*<!-- data-source:end -->/)
if (!match) throw new Error('invalid metadata block')
const record = JSON.parse(match[1])
const target = resolve(root, record.config)
if (relative(root, target).startsWith('..')) throw new Error('config escapes repository')
if (records.has(record.config)) throw new Error('duplicate config record')
if (!/^\d{4}-\d{2}-\d{2}$/.test(record.checkedAt)) throw new Error('missing checkedAt')
const config = read(record.config)
records.set(record.config, record)
if (record.kind === 'routing-policy') {
if (record.status !== 'policy' || record.config !== 'compute/service-map.json') throw new Error('invalid routing policy')
if (!isDeepStrictEqual(config, record.claims)) errors.push(`${page}: routing policy drift: update the documented default selection`)
const providers = files(root, 'compute/providers', '.json').filter(p => !p.endsWith('/_index.json')).map(read)
const external = new Set((record.externalProviders ?? []).map(item => {
if (!item.id || !item.reason?.trim()) throw new Error('external provider needs reason')
return item.id
}))
for (const [service, route] of Object.entries(config)) {
const provider = providers.find(item => item.id === route.providerId)
if (external.has(route.providerId)) continue
if (!provider || !provider.models?.some(model => model.modelName === route.modelName)) throw new Error(`unresolved route ${service}: ${route.providerId}/${route.modelName}`)
}
} else if (record.kind === 'policy' || record.kind === 'api-contract') {
if (!record.claims || !Object.keys(record.claims).length) throw new Error('missing claims')
for (const [key, value] of Object.entries(record.claims)) {
if (!isDeepStrictEqual(config[key], value)) throw new Error(`contract drift: ${key}`)
}
if (record.kind === 'policy') {
if (record.status !== 'policy' || record.config !== 'compute/pricing.json') throw new Error('invalid pricing policy')
if (!isDeepStrictEqual(config, record.claims)) throw new Error('unrecorded pricing field')
} else {
if (!['partial', 'pending'].includes(record.status)) throw new Error('invalid verification status')
if (!Array.isArray(record.verifiedFields) || record.verifiedFields.some(key => typeof key !== 'string' || key.split('.').reduce((value, segment) => value?.[segment], record.claims) === undefined)) throw new Error('invalid verified fields')
if (record.status === 'pending' && record.verifiedFields.length) throw new Error('pending cannot assert verified fields')
if (record.status === 'partial' && !record.verifiedFields.length) throw new Error('partial needs verified fields')
if (!record.sources?.length) throw new Error('missing official source')
for (const source of record.sources) {
const url = new URL(source.url)
if (url.protocol !== 'https:' || url.username || url.password || url.search || !OFFICIAL_HOSTS[config.id]?.includes(url.hostname)) throw new Error('unapproved source URL')
if (!['fetched', 'shell', 'failed'].includes(source.retrieval)) throw new Error('invalid retrieval')
if (!/^\d{4}-\d{2}-\d{2}$/.test(source.checkedAt ?? '') || Number.isNaN(Date.parse(source.checkedAt)) || new Date(source.checkedAt).toISOString().slice(0, 10) !== source.checkedAt) throw new Error('invalid source checkedAt')
if (source.resolvedUrl) {
const resolved = new URL(source.resolvedUrl)
if (resolved.protocol !== 'https:' || resolved.username || resolved.password || resolved.searchParams.has('token') || !OFFICIAL_HOSTS[config.id]?.includes(resolved.hostname)) throw new Error('unapproved resolved source URL')
}
if (source.contentSha256 != null && !/^[a-f0-9]{64}$/.test(source.contentSha256)) throw new Error('invalid source digest')
if (source.retrieval !== 'failed' && (!source.resolvedUrl || !source.contentSha256)) throw new Error('read source needs resolved URL and digest')
}
if (record.verifiedFields.length && !record.sources.some(source => source.retrieval === 'fetched')) throw new Error('verified field without fetched source')
for (const key of ['endpoint', 'method', 'auth', 'request', 'response']) if (!(key in record.claims)) throw new Error(`missing contract field ${key}`)
}
} else throw new Error('unknown record kind')
} catch (error) { errors.push(`${page}: ${error.message}`) }
}
const required = ['compute/pricing.json', 'compute/service-map.json', ...files(root, 'api-providers', '.json').filter(path => !path.endsWith('/_index.json'))]
for (const path of required) if (!records.has(path)) errors.push(`missing source record: ${path}`)
for (const path of records.keys()) if (!required.includes(path)) errors.push(`out of scope source record: ${path}`)
return { errors, records: records.size }
}
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
const result = validateDataSources()
for (const error of result.errors) console.error(error)
console.log(`Data sources: ${result.records} policy/API records; ${result.errors.length} failed (not account-call verification)`)
process.exitCode = result.errors.length ? 1 : 0
}

View File

@@ -0,0 +1,62 @@
import { readFileSync } from 'node:fs'
import { createHash } from 'node:crypto'
import { fileURLToPath } from 'node:url'
import { resolve, join } from 'node:path'
import { SOURCES } from './update-runtime-catalog.mjs'
const ROOT = fileURLToPath(new URL('../', import.meta.url))
const hash = value => createHash('sha256').update(JSON.stringify(value)).digest('hex')
function tableRows(text, columns) {
return text.split('\n').filter(line => line.startsWith('|')).map(line => line.split('|').slice(1, -1).map(cell => cell.trim().replace(/^`|`$/g, ''))).filter(cells => cells.length === columns && !cells.every(cell => /^[- :]+$/.test(cell)))
}
export function validateRuntimeSources(root = ROOT, now = new Date().toISOString()) {
const errors = []
const read = path => readFileSync(join(root, path), 'utf8')
try {
const fallback = JSON.parse(read('runtimes/versions-fallback.json'))
const recommended = JSON.parse(read('runtimes/recommended.json'))
if (!Number.isFinite(Date.parse(fallback.generatedAt)) || Date.parse(fallback.generatedAt) > Date.parse(now)) errors.push('fallback: invalid/future generatedAt')
for (const name of ['node', 'npm', 'pnpm', 'yarn']) {
const rows = name === 'node' ? fallback.node : fallback.pkgManagers[name]
const path = name === 'node' ? 'node-snapshot.md' : `package-managers/${name}-snapshot.md`
const page = read(`docs/runtime-sources/${path}`)
if (!rows.length) errors.push(`${name}: empty snapshot`)
if (!page.includes(`- 官方来源:[官方目录](${SOURCES[name]})\n`)) errors.push(`${name}: official source URL drift`)
const seen = new Set()
for (const row of rows) {
const version = row.version ?? row.tag_name
const date = row.date ?? row.created_at
if (!/^v\d+\.\d+\.\d+$/.test(version) || seen.has(version)) errors.push(`${name}: invalid/duplicate stable version`)
seen.add(version)
if (!Number.isFinite(Date.parse(date)) || Date.parse(date) > Date.parse(fallback.generatedAt)) errors.push(`${name}: invalid/future row publication date`)
}
const expectedVersion = rows[0]?.version ?? rows[0]?.tag_name
if (!page.includes(`- 实际读取:${fallback.generatedAt}\n`)) errors.push(`${name}: snapshot date drift`)
if (!page.includes(`- 目录条数:${rows.length}\n`)) errors.push(`${name}: snapshot count drift`)
if (!page.includes(`- 首条版本:${expectedVersion}\n`)) errors.push(`${name}: first version drift`)
if (!page.includes(`- 本仓条目指纹(JSON.stringify、SHA-256):\`${hash(rows)}\`\n`)) errors.push(`${name}: snapshot fingerprint drift`)
if (!/- 本轮读取的官方正文 SHA-256:`[a-f0-9]{64}`\n/.test(page)) errors.push(`${name}: missing official body digest record`)
}
const pythonRows = tableRows(read('docs/runtime-sources/python-fallback.md'), 2).filter(cells => cells[0] !== '名称')
if (JSON.stringify(pythonRows) !== JSON.stringify(fallback.python.map(row => [row.name, row.version]))) errors.push('python: Hatch pin table drift')
for (const name of ['node', 'python']) {
const page = read(`docs/runtime-sources/${name}-recommended.md`)
const expected = recommended[name]
const sourceUrl = name === 'node' ? `https://nodejs.org/dist/v${expected.version}/SHASUMS256.txt` : `https://api.github.com/repos/astral-sh/python-build-standalone/releases/tags/${expected.distTag}`
if (!page.includes(`- 官方来源:[官方摘要清单](${sourceUrl})。\n`)) errors.push(`${name}: recommended official source URL drift`)
if (!page.includes(`- 数据:\`runtimes/recommended.json#${name}\`,版本 \`${expected.version}\`。\n`)) errors.push(`${name}: recommended version drift`)
if (name === 'node' && !page.includes(`LTS 名 ${expected.ltsCodename};`)) errors.push('node: recommended LTS drift')
if (name === 'python' && !page.includes(`tag \`${expected.distTag}\``)) errors.push('python: recommended distTag drift')
const rows = tableRows(page, 3).filter(cells => cells[0] !== '平台')
const values = Object.entries(expected.archives).map(([platform, path]) => [platform, path, expected.sha256[platform]])
if (JSON.stringify(rows) !== JSON.stringify(values)) errors.push(`${name}: recommended archive/hash table drift`)
}
} catch (error) { errors.push(`runtime source read: ${error.message}`) }
return { errors }
}
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
const result = validateRuntimeSources()
for (const error of result.errors) console.error(error)
console.log(`Runtime sources: snapshot and recommended tables; ${result.errors.length} failed (offline consistency, not official re-fetch or installation verification)`)
process.exitCode = result.errors.length ? 1 : 0
}

View File

@@ -57,7 +57,7 @@ const get = (row, path) => path.split('.').reduce((value, key) => value?.[key],
const dateValid = (value) => /^\d{4}-\d{2}-\d{2}$/.test(value ?? '') && !Number.isNaN(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value
const configSupplier = (config) => {
const name = config.split('/').at(-1).replace(/\.json$/, '')
const aliases = { 'openai-codex': 'openai', 'local-whisper': 'openai', 'anthropic-claude': 'anthropic', dashscope: 'alibaba', qwen: 'alibaba', wan: 'alibaba', happyhorse: 'alibaba', 'dashscope-coding': 'alibaba', 'dashscope-token-plan': 'alibaba', 'zhipu-embedding': 'zhipu', 'tencent-token': 'tencent', 'moorethread-coding': 'moorethread' }
const aliases = { 'openai-codex': 'openai', 'local-whisper': 'openai', 'anthropic-claude': 'anthropic', dashscope: 'alibaba', qwen: 'alibaba', wan: 'alibaba', happyhorse: 'alibaba', 'dashscope-coding': 'alibaba', 'dashscope-token-plan': 'alibaba', 'zhipu-embedding': 'zhipu', 'tencent-token': 'tencent', 'xunfei-x2': 'xunfei', 'moorethread-coding': 'moorethread' }
return aliases[name] ?? name.replace(/-coding$/, '')
}

View File

@@ -121,6 +121,7 @@ const SUSPICIOUS_EXTRA_KEYS = {
provider: {
reasoningEffort: '客户端只读嵌套的 extra.reasoning.supportedEfforts,扁平写法永不生效',
defaultReasoningEffort: '同上,应并入 extra.reasoning.defaultEffort',
defaultEffort: '扁平默认值不被客户端读取,应并入 extra.reasoning.defaultEffort 并核查接入面默认',
},
spec: {
reasoningEffort: 'reasoning effort 是接入面能力,只能声明在 provider model 的 extra.reasoning 中',