Files
deepseek-harness/scripts/render-persistence-schema.ts
T
Tianyi Cui a6981c56f6 feat(persistence): add stable catalogs and policy-aware schema review
Traversal ordinals and first-owner paths gave shared persistence types
unstable display names. Keep authored names and source locations as metadata,
use structural digests for identity, and render complete kind/form groups
with bounded array labels. Preserve historical rendering and stored graphs.

Record supported source compatibility promises with their property binding,
policy version, discriminator, preservation rule, and qualified kind set.
The extractor and classifier require explicit opt-ins in both compared
schemas; ordinary or unsupported changes retain strict classification.
Policy-bearing graphs use the format-2 fingerprint domain while unannotated
graphs keep their format-1 fingerprints. Runtime owners activate bindings
only with the corresponding reader behavior; this tooling layer activates
none and changes no package source or Session generation.

Provide a read-only before/after review command that groups shared structural
evidence without replacing authoritative compatibility diagnostics. Match
changed alternatives only through unique literal discriminators and retain
ambiguous additions and removals as independent facts.

At the X baseline, all 61 roots and 472 schemas, digests, and source fields
are unchanged. The inventory remains format 1 and removes only 296 synthetic
names. Existing acknowledgement history still matches every root. All 195
focused tooling tests, Host build/full lint, and bilingual pairing pass.
2026-09-18 19:05:32 +08:00

320 lines
16 KiB
TypeScript

/** Readable, linked persistence schemas rendered from the fingerprint inventory. */
import { githubSlug } from './verify-md-links.ts'
import { persistenceCatalogText, type PersistenceCatalogLocale } from './persistence-catalog-text.ts'
import { sourceKindGroups } from './persistence-source-policy.ts'
import {
canonicalizeSchema,
schemaChildren,
schemaDigest,
type CanonicalSchema,
type PersistenceSchemaInventory,
type PersistenceType,
type SchemaNode,
} from './persistence-schema-model.ts'
interface TypeDisplay {
readonly type: PersistenceType
readonly label: string
readonly anchor: string
readonly aliases: readonly string[]
}
/** Current catalogs use structural links; historical references retain their original labels and anchors. */
export type PersistenceSchemaRendering = 'current' | 'historical'
function code(text: string): string {
return '`' + text.replaceAll('`', '\\`').replaceAll('|', '\\|') + '`'
}
function sourcePath(source: string): string {
return source.replace(/:\d+(?::\d+)?$/u, '')
}
function nodeAt(schema: CanonicalSchema, index: number): SchemaNode {
const node = schema.nodes[index]
if (!node) throw new Error(`persistence catalog: missing schema node ${String(index)}`)
return node
}
function childPath(node: SchemaNode, position: number): string {
switch (node.kind) {
case 'object': return node.properties[position]?.name ?? `index-${String(position - node.properties.length)}`
case 'array': return 'item'
case 'tuple': return `item-${String(position)}`
case 'union': return `variant-${String(position)}`
case 'primitive':
case 'literal':
case 'opaque': return String(position)
default: return assertNever(node)
}
}
function historicalDisplays(inventory: PersistenceSchemaInventory): Map<string, TypeDisplay> {
const paths = new Map<string, string>()
for (const root of inventory.roots) {
const seen = new Set<number>()
const visit = (index: number, path: string): void => {
if (seen.has(index)) return
seen.add(index)
const digest = schemaDigest(canonicalizeSchema(root.schema.nodes, index))
if (!paths.has(digest)) paths.set(digest, path)
const node = nodeAt(root.schema, index)
schemaChildren(node).forEach((child, position) => { visit(child, `${path}.${childPath(node, position)}`) })
}
visit(0, root.key)
}
const labels = inventory.types.map((type) => {
const node = nodeAt(type.schema, 0)
const label = node.kind === 'primitive' ? node.type
: node.kind === 'literal' ? JSON.stringify(node.value)
: node.kind === 'opaque' ? node.reason
: type.names[0] ?? paths.get(type.digest) ?? node.kind
return { type, label }
})
const counts = new Map<string, number>()
for (const { label } of labels) counts.set(label, (counts.get(label) ?? 0) + 1)
const used = new Set<string>()
return new Map(labels.map(({ type, label: original }) => {
const label = (counts.get(original) ?? 0) > 1
? `${original} (${type.sources[0] ? sourcePath(type.sources[0]) : paths.get(type.digest) ?? 'anonymous'})`
: original
const base = `persistence-type-${githubSlug(label)}`
let anchor = base
for (let suffix = 2; used.has(anchor); suffix += 1) anchor = `${base}-${String(suffix)}`
used.add(anchor)
return [type.digest, { type, label, anchor, aliases: [] }]
}))
}
function structuralLabel(schema: CanonicalSchema, includeFields = false): string {
const node = nodeAt(schema, 0)
switch (node.kind) {
case 'primitive': return node.type
case 'literal': return JSON.stringify(node.value)
case 'opaque': return node.reason
case 'object': {
const tags = ['type', 'kind', 'role', 'form'].flatMap((name) => {
const property = node.properties.find(candidate => candidate.name === name && !candidate.optional)
const value = property === undefined ? undefined : nodeAt(schema, property.type)
return value?.kind === 'literal' ? [`${name}: ${JSON.stringify(value.value)}`] : []
})
const fields = tags.length > 0 && !includeFields ? tags : node.properties.map((property) => {
const tag = tags.find(tag => tag.startsWith(property.name + ':'))
return tag ?? `${property.name}${property.optional ? '?' : ''}`
})
if (fields.length === 0) return '{}'
return `{ ${fields.slice(0, 4).join(', ')}${fields.length > 4 ? ', …' : ''} }`
}
case 'array': return 'array'
case 'tuple': return `tuple (${String(node.elements.length)} positions)`
case 'union': return `union (${String(node.types.length)} variants)`
default: return assertNever(node)
}
}
function currentDisplays(inventory: PersistenceSchemaInventory): Map<string, TypeDisplay> {
const roots = new Map<string, string[]>()
for (const root of inventory.roots) roots.set(root.digest, [...roots.get(root.digest) ?? [], root.key])
const aliases = new Map<string, Set<string>>()
const namesByDigest = new Map(inventory.types.map((type) => {
const names = type.names.filter(name => !inventory.roots.some(root => name.startsWith(root.key + '.') || name.startsWith(root.key + '[')))
const logicalNames = [...new Set([...names, ...roots.get(type.digest) ?? []])].sort()
for (const name of logicalNames) {
for (const value of new Set([name, name.slice(name.lastIndexOf('#') + 1)])) {
const alias = `persistence-type-${githubSlug(value)}`
const owners = aliases.get(alias) ?? new Set<string>()
owners.add(type.digest)
aliases.set(alias, owners)
}
}
return [type.digest, names.filter(name => !roots.get(type.digest)?.includes(name)).sort()]
}))
const types = new Map(inventory.types.map(type => [type.digest, type]))
const labelFor = (schema: CanonicalSchema, seen: ReadonlySet<string>, includeFields = false): string => {
const digest = schemaDigest(schema)
const node = nodeAt(schema, 0)
if (node.kind === 'primitive' || node.kind === 'literal' || node.kind === 'opaque') return structuralLabel(schema)
const named = namesByDigest.get(digest)?.find(name => aliases.get(`persistence-type-${githubSlug(name.slice(name.lastIndexOf('#') + 1))}`)?.size === 1)
if (named !== undefined) return named.slice(named.lastIndexOf('#') + 1)
if (seen.has(digest) || seen.size >= 4) return structuralLabel(schema, includeFields)
if (node.kind === 'array') {
const element = canonicalizeSchema(schema.nodes, node.element)
const label = labelFor(types.get(schemaDigest(element))?.schema ?? element, new Set(seen).add(digest), includeFields)
return `${label}[]`
}
return structuralLabel(schema, includeFields)
}
const labels = inventory.types.map(type => ({ type, label: labelFor(type.schema, new Set()), anchor: `persistence-type-sha256-${type.digest}` }))
const counts = new Map<string, number>()
for (const entry of labels) counts.set(entry.label, (counts.get(entry.label) ?? 0) + 1)
for (const entry of labels) {
if ((counts.get(entry.label) ?? 0) > 1) entry.label = labelFor(entry.type.schema, new Set(), true)
}
const structuralAnchors = new Set(labels.map(entry => entry.anchor))
return new Map(labels.map(entry => [entry.type.digest, {
...entry,
// A logical name shared by different structures has no unambiguous target.
aliases: [...aliases].filter(([alias, owners]) => owners.size === 1 && owners.has(entry.type.digest) && !structuralAnchors.has(alias))
.map(([alias]) => alias).sort(),
}]))
}
function displays(inventory: PersistenceSchemaInventory, rendering: PersistenceSchemaRendering): Map<string, TypeDisplay> {
return rendering === 'current' ? currentDisplays(inventory) : historicalDisplays(inventory)
}
function reference(digest: string, entries: ReadonlyMap<string, TypeDisplay>): string {
const entry = entries.get(digest)
if (!entry) throw new Error(`persistence catalog: reachable type ${digest} is absent from the inventory`)
return `[${code(entry.label)}](#${entry.anchor})`
}
function typeExpression(
schema: CanonicalSchema,
index: number,
entries: ReadonlyMap<string, TypeDisplay>,
locale: PersistenceCatalogLocale,
): string {
const node = nodeAt(schema, index)
if (node.kind === 'primitive') return code(node.type)
if (node.kind === 'literal') return code(JSON.stringify(node.value))
if (node.kind === 'opaque') return `${code(node.reason)}${persistenceCatalogText[locale].opaque}`
return reference(schemaDigest(canonicalizeSchema(schema.nodes, index)), entries)
}
function definition(
entry: TypeDisplay,
entries: ReadonlyMap<string, TypeDisplay>,
locale: PersistenceCatalogLocale,
sourceLink: (source: string) => string | undefined,
headingLevel: number,
rendering: PersistenceSchemaRendering,
): string[] {
const text = persistenceCatalogText[locale]
const schema = entry.type.schema
const node = nodeAt(schema, 0)
const lines = [...[entry.anchor, ...entry.aliases].flatMap(anchor => [`<a id="${anchor}"></a>`, '']), `${'#'.repeat(headingLevel)} ${code(entry.label)}`, '', `SHA-256: ${code(entry.type.digest)}`, '']
if (entry.type.sources.length > 0) {
lines.push(`${text.sources}${entry.type.sources.map((source) => {
const href = sourceLink(sourcePath(source))
return href === undefined ? code(source) : `[${code(source)}](${href})`
}).join(' · ')}`, '')
}
const expression = (index: number): string => typeExpression(schema, index, entries, locale)
switch (node.kind) {
case 'object':
if (node.properties.length === 0 && node.indices.length === 0) lines.push(text.emptyObject, '')
else {
lines.push(text.propertyColumns, '|---|---|---|')
for (const property of node.properties) {
lines.push(`| ${code(property.name)} | ${property.optional ? text.optional : text.required} | ${expression(property.type)} |`)
}
for (const index of node.indices) lines.push(`| [${expression(index.key)}] | ${text.index} | ${expression(index.value)} |`)
lines.push('')
if (rendering === 'current') {
for (const property of node.properties) {
const policy = property.compatibility
if (policy === undefined) continue
lines.push(`${text.sourceCompatibility}${code(property.name)} — ${code(policy.policy)} v${String(policy.version)}; ${code(policy.binding)}; ${code(policy.discriminator)}; ${code(policy.unknownKinds)}.`, '',
`${text.attributionAdditions}${policy.attributionKinds.map(code).join(', ') || code('[]')}.`, '')
}
}
}
break
case 'array': lines.push(`${text.arrayPrefix}${expression(node.element)}${text.arraySuffix}`, ''); break
case 'tuple':
lines.push(text.positionColumns, '|---|---|---|')
node.elements.forEach((element, index) => {
const presence = element.rest ? text.rest : element.optional ? text.optional : text.required
lines.push(`| ${String(index)} | ${presence} | ${expression(element.type)} |`)
})
lines.push('')
break
case 'union': {
const groups = rendering === 'current' ? sourceKindGroups(schema.nodes, 0) : undefined
if (groups !== undefined) {
lines.push(text.sourceColumns, '|---|---|---|---|')
for (const [kind, alternatives] of [...groups].sort(([a], [b]) => a.localeCompare(b))) {
const rows = alternatives.map((index) => {
const variant = nodeAt(schema, index)
if (variant.kind !== 'object') throw new Error('persistence catalog: source group must contain objects')
const form = variant.properties.find(property => property.name === 'form')
const formLabel = form === undefined ? text.notDeclared
: `${expression(form.type)}${form.optional ? ` (${text.optional})` : ''}`
const fields = variant.properties.filter(property => property.name !== 'kind' && property.name !== 'form' && !property.optional)
.map(property => `${code(property.name)}: ${expression(property.type)}`)
if (variant.indices.length > 0) fields.push(text.index)
return { index, formLabel, fields, digest: schemaDigest(canonicalizeSchema(schema.nodes, index)) }
}).sort((a, b) => a.formLabel.localeCompare(b.formLabel) || a.digest.localeCompare(b.digest))
for (const row of rows) lines.push(`| ${code(kind)} | ${row.formLabel} | ${row.fields.join('; ') || text.none} | ${expression(row.index)} |`)
}
lines.push('')
} else lines.push(text.oneOf, '', ...node.types.map(index => `- ${expression(index)}`), '')
break
}
case 'primitive': lines.push(code(node.type), ''); break
case 'literal': lines.push(code(JSON.stringify(node.value)), ''); break
case 'opaque': lines.push(`${code(node.reason)}${text.opaqueExplanation}`, ''); break
default: assertNever(node)
}
return lines
}
/**
* Render every tracked root with its exact digest and resolved type reference.
* @param inventory - complete current-source schemas and declaration metadata.
* @param locale - generated document language.
* @param introduction - paragraphs before the root table; defaults to current-source links.
* @param headingLevel - section depth within the containing reference.
* @param rendering - explicit current mode selects structural links; omission preserves historical output.
* @returns Markdown index including the history and contributor workflow links.
*/
export function renderPersistenceSchemaIndex(
inventory: PersistenceSchemaInventory,
locale: PersistenceCatalogLocale = 'en',
introduction: readonly string[] = [persistenceCatalogText[locale].fingerprintsIntro, persistenceCatalogText[locale].historyIntro],
headingLevel: 2 | 3 = 2,
rendering: PersistenceSchemaRendering = 'historical',
): string {
const entries = displays(inventory, rendering)
const text = persistenceCatalogText[locale]
return [
`${'#'.repeat(headingLevel)} ${text.fingerprints}`, '', ...introduction.flatMap(paragraph => [paragraph, '']),
text.rootColumns, '|---|---|---|---|',
...inventory.roots.map(root => `| ${code(root.key)} | ${root.kind} | ${code(root.digest)} | ${reference(root.digest, entries)} |`), '',
].join('\n')
}
/**
* Render every reachable type once, with links for shared and recursive definitions.
* @param inventory - complete current-source schemas and declaration metadata.
* @param locale - generated document language.
* @param sourceLink - source path to URL; undefined keeps historical locations as text.
* @param headingLevel - section depth; individual definitions use the next heading level.
* @param rendering - explicit current mode selects structural links and unambiguous logical aliases.
* @returns linked Markdown definitions; historical mode preserves the original named and owning-path anchors.
*/
export function renderPersistenceSchemaDefinitions(
inventory: PersistenceSchemaInventory,
locale: PersistenceCatalogLocale = 'en',
sourceLink: (source: string) => string | undefined = source => `../${source}`,
headingLevel: 2 | 3 = 2,
rendering: PersistenceSchemaRendering = 'historical',
): string {
const entries = displays(inventory, rendering)
const text = persistenceCatalogText[locale]
const sorted = [...entries.values()].sort((left, right) => {
if (rendering === 'current' && left.label !== right.label) return left.label < right.label ? -1 : 1
return left.anchor < right.anchor ? -1 : left.anchor > right.anchor ? 1 : 0
})
return [
`${'#'.repeat(headingLevel)} ${text.definitions}`, '', text.definitionsIntro, '',
...sorted.flatMap(entry => definition(entry, entries, locale, sourceLink, headingLevel + 1, rendering)),
].join('\n')
}
function assertNever(value: never): never {
throw new Error(`persistence catalog: unsupported type ${JSON.stringify(value)}`)
}