Files
deepseek-harness/scripts/persistence-source-policy.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

70 lines
3.6 KiB
TypeScript

/** Literal source groups and the supported reader promise saved in schema history. */
import type { SchemaNode, SchemaProperty, SourceCompatibility } from './persistence-schema-model.ts'
// Reserve role and shared-consumer identities, frozen rename targets, and request-only producer identities against reuse.
// Existing kinds outside this set also reject retroactive qualification during comparison.
const RESERVED_SOURCE_KINDS = new Set([
'user', 'model', 'tool', 'system-prompt', 'compact-checkpoint', 'dsh-session-title-llm', 'tool-registry', 'runtime-context', 'plugin',
'auto-review', 'compact-basic', 'code-mode',
])
/**
* Group source alternatives by their required literal wire discriminator.
* @param nodes - self-contained type graph.
* @param index - source property value.
* @returns kind groups, or undefined when any alternative is not a closed source record.
*/
export function sourceKindGroups(nodes: readonly SchemaNode[], index: number): Map<string, number[]> | undefined {
const source = nodes[index]
const alternatives = source?.kind === 'union' ? source.types : [index]
const groups = new Map<string, number[]>()
for (const alternative of alternatives) {
const node = nodes[alternative]
if (node?.kind !== 'object') return undefined
const property = node.properties.find(property => property.name === 'kind')
const kind = property === undefined ? undefined : nodes[property.type]
if (property?.optional !== false || kind?.kind !== 'literal' || typeof kind.value !== 'string' || kind.value.length === 0) return undefined
const group = groups.get(kind.value) ?? []
group.push(alternative)
groups.set(kind.value, group)
}
return groups
}
/**
* Validate the supported policy and its containing message/source declaration.
* @param nodes - self-contained graph.
* @param owner - object carrying the explicitly bound source property.
* @param property - bound source property.
* @returns whether the metadata authorizes source-group comparison.
*/
export function validSourceCompatibility(
nodes: readonly SchemaNode[], owner: SchemaNode, property: SchemaProperty,
): boolean {
const policy = property.compatibility
if (policy === undefined || property.name !== 'source' || property.optional || owner.kind !== 'object') return false
const roleProperty = owner.properties.find(property => property.name === 'role')
const role = roleProperty === undefined ? undefined : nodes[roleProperty.type]
if (roleProperty?.optional !== false || role?.kind !== 'literal'
|| !(role.value === 'user' && policy.binding === 'session.user-message.source'
|| role.value === 'developer' && policy.binding === 'session.developer-message.source')) return false
const groups = sourceKindGroups(nodes, property.type)
return groups !== undefined && policy.attributionKinds.every(kind => groups.has(kind) && !RESERVED_SOURCE_KINDS.has(kind))
}
/**
* Check that both recorded promises agree and no existing kind changes qualification.
* Both inputs are validated v1 policies with fixed policy, discriminator, and preservation fields.
* @param before - predecessor promise.
* @param after - successor promise.
* @param existingKinds - predecessor wire kinds, including semantic groups.
* @returns whether the additive rule applies to this transition.
*/
export function matchingSourceCompatibility(
before: SourceCompatibility, after: SourceCompatibility, existingKinds: Iterable<string>,
): boolean {
return before.binding === after.binding
&& [...existingKinds].every(kind => before.attributionKinds.includes(kind) === after.attributionKinds.includes(kind))
}