fix(invariants): assert runtime relationships, not API shapes

This commit is contained in:
Tianyi Cui
2026-07-20 19:34:19 +08:00
parent 1254c07025
commit 1145ee5fc3
124 changed files with 2923 additions and 2334 deletions

View File

@@ -1,31 +1,50 @@
/** Package-owned runtime contract checks for `@deepseek-ai/dsh-system-prompt`. @module @deepseek-ai/dsh-system-prompt/invariant */
/** Package-owned prompt-assembly invariants. @module @deepseek-ai/dsh-system-prompt/invariant */
import type { Context } from 'cordis'
import { observePluginInvariant, type InvariantInstaller } from '@deepseek-ai/dsh-invariants'
import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants'
import type { PromptAssembly } from './index.ts'
const PACKAGE_NAME = '@deepseek-ai/dsh-system-prompt'
const VARIABLE_NAME = /^[a-z][a-z0-9_]*$/
/** Cordis companion plugin name. */
export const name = 'system-prompt-invariant'
/** Services required before the companion can register. */
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/** Install checks for this package's active plugin fibers. */
/** Validate the authoritative assembly returned by the waterfall. */
function validateAssembly(assembly: PromptAssembly, fail: InvariantFailure): void {
const sectionNames = new Set<string>()
for (const section of assembly.sections) {
if (section.name.length === 0) fail('assembled section names must be non-empty')
if (sectionNames.has(section.name)) fail(`assembled section name ${JSON.stringify(section.name)} is duplicated`)
sectionNames.add(section.name)
if (typeof section.text !== 'string') fail(`assembled section ${JSON.stringify(section.name)} text must be a string`)
}
for (const tool of assembly.tools) {
if (tool.name.length === 0) fail('assembled tool names must be non-empty')
}
for (const [name, value] of Object.entries(assembly.variables)) {
if (!VARIABLE_NAME.test(name)) fail(`assembled variable name ${JSON.stringify(name)} is invalid`)
if (value !== undefined && typeof value !== 'string') {
fail(`assembled variable ${JSON.stringify(name)} must be a string or undefined`)
}
}
}
/** Install validation around the authoritative assembly waterfall result. */
const install: InvariantInstaller = (ctx, fail) => {
observePluginInvariant(ctx, fail, {
name: 'SystemPrompt',
effects: [
'ctx.provide("systemPrompt")',
'systemPrompt.section()',
],
services: [
'systemPrompt',
],
})
ctx.on('system-prompt/assemble', async (_assembly, _context, next) => {
const assembled = await next()
validateAssembly(assembled, fail)
return assembled
}, { global: true, prepend: true })
}
/**
* Register this package's invariant companion.
* Register the system-prompt invariant companion.
* @param ctx - Cordis context carrying the invariant service.
* @returns the installed registration's disposer after setup succeeds.
*/

View File

@@ -0,0 +1,44 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import type { PromptAssembly } from '@deepseek-ai/dsh-system-prompt'
import * as SystemPromptInvariant from '@deepseek-ai/dsh-system-prompt/invariant'
import InvariantService from '@deepseek-ai/dsh-invariants'
async function setup(): Promise<Context> {
const ctx = new Context()
await ctx.plugin(InvariantService)
await ctx.plugin(SystemPromptInvariant)
return ctx
}
const valid = (): PromptAssembly => ({
sections: [{ name: 'identity', text: 'prompt' }],
tools: [{ name: 'echo', description: 'Echo', parameters: {} }],
variables: { cwd: '/repo', optional: undefined },
})
async function assemble(ctx: Context, result: PromptAssembly): Promise<PromptAssembly> {
return ctx.waterfall(
ctx as never, 'system-prompt/assemble', valid(), {},
() => Promise.resolve(result),
)
}
describe('system-prompt invariants', () => {
it('accepts a well-formed authoritative assembly', async () => {
const ctx = await setup()
await expect(assemble(ctx, valid())).resolves.toEqual(valid())
})
it.each([
[{ ...valid(), sections: [{ name: '', text: 'x' }] }, /section names must be non-empty/],
[{ ...valid(), sections: [{ name: 'x', text: 'a' }, { name: 'x', text: 'b' }] }, /section name "x" is duplicated/],
[{ ...valid(), sections: [{ name: 'x', text: 1 as never }] }, /section "x" text must be a string/],
[{ ...valid(), tools: [{ name: '', description: 'x', parameters: {} }] }, /tool names must be non-empty/],
[{ ...valid(), variables: { Bad: 'x' } }, /variable name "Bad" is invalid/],
[{ ...valid(), variables: { value: 1 as never } }, /variable "value" must be a string or undefined/],
])('rejects malformed authoritative assembly %#', async (assembly, message) => {
const ctx = await setup()
await expect(assemble(ctx, assembly)).rejects.toThrow(message)
})
})

View File

@@ -1,34 +1,67 @@
/** Package-owned runtime contract checks for `@deepseek-ai/dsh-tools`. @module @deepseek-ai/dsh-tools/invariant */
/** Package-owned tool-pipeline invariants. @module @deepseek-ai/dsh-tools/invariant */
import type { Context } from 'cordis'
import { observePluginInvariant, type InvariantInstaller } from '@deepseek-ai/dsh-invariants'
import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants'
import type { ToolExecution, ToolExecutionResult } from './index.ts'
const PACKAGE_NAME = '@deepseek-ai/dsh-tools'
/** Cordis companion plugin name. */
export const name = 'tools-invariant'
/** Services required before the companion can register. */
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/** Install checks for this package's active plugin fibers. */
type ToolStage = 'pre' | 'execute' | 'post'
/** Validate the immutable final execution/result snapshot. */
function validateResult(
exec: Readonly<ToolExecution>,
result: Readonly<ToolExecutionResult>,
fail: InvariantFailure,
): void {
if (!Object.isFrozen(exec)) fail('tools/result execution must be frozen before publication')
if (!Object.isFrozen(result) || !Object.isFrozen(result.content)) {
fail('tools/result outcome and content must be frozen before publication')
}
if (exec.name.length === 0 || String(exec.callId).length === 0) {
fail('tools/result execution must carry non-empty name and callId')
}
}
/** Install monotonic pipeline and final-snapshot checks. */
const install: InvariantInstaller = (ctx, fail) => {
observePluginInvariant(ctx, fail, {
name: 'ToolRegistry',
inject: [
'systemPrompt',
],
effects: [
'ctx.provide("tools")',
'systemPrompt.tools()',
],
services: [
'tools',
],
})
const stages = new WeakMap<object, ToolStage>()
ctx.on('internal/dispatch', (_mode, eventName, args) => {
if (eventName === 'tools/pre-execute') {
const exec = args[0] as ToolExecution
if (stages.has(exec)) fail('tools/pre-execute repeated for one execution')
stages.set(exec, 'pre')
return
}
if (eventName === 'tools/execute') {
const exec = args[0] as ToolExecution
if (stages.get(exec) !== 'pre') fail('tools/execute must follow tools/pre-execute')
stages.set(exec, 'execute')
return
}
if (eventName === 'tools/post-execute') {
const exec = args[0] as ToolExecution
const previous = stages.get(exec)
if (previous !== 'pre' && previous !== 'execute') {
fail('tools/post-execute must follow tools/pre-execute or tools/execute')
}
stages.set(exec, 'post')
return
}
if (eventName !== 'tools/result') return
const [exec, result] = args as [Readonly<ToolExecution>, Readonly<ToolExecutionResult>]
validateResult(exec, result, fail)
stages.delete(exec)
}, { global: true })
}
/**
* Register this package's invariant companion.
* Register the tools invariant companion.
* @param ctx - Cordis context carrying the invariant service.
* @returns the installed registration's disposer after setup succeeds.
*/

View File

@@ -0,0 +1,84 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import { scopeTarget } from '@deepseek-ai/dsh-scope'
import { CallId } from '@deepseek-ai/dsh-llm'
import type { ToolExecution, ToolExecutionResult, ToolExecutionToken } from '@deepseek-ai/dsh-tools'
import * as ToolsInvariant from '@deepseek-ai/dsh-tools/invariant'
import InvariantService from '@deepseek-ai/dsh-invariants'
async function setup(): Promise<Context> {
const ctx = new Context()
await ctx.plugin(InvariantService)
await ctx.plugin(ToolsInvariant)
return ctx
}
const execution = (overrides: Partial<ToolExecution> = {}): ToolExecution => ({
token: Symbol('tool') as ToolExecutionToken,
callId: CallId('call-1'),
name: 'echo',
arguments: Object.freeze({ text: 'hi' }),
...overrides,
})
const outcome = (): ToolExecutionResult => Object.freeze({
content: Object.freeze([{ type: 'text' as const, text: 'ok' }]) as never,
isError: false,
})
function emitResult(ctx: Context, exec: ToolExecution, result: ToolExecutionResult): void {
ctx.emit(scopeTarget(ctx as never, undefined), 'tools/result', exec, result)
}
async function stage(ctx: Context, name: 'tools/pre-execute' | 'tools/execute', exec: ToolExecution): Promise<void> {
if (name === 'tools/pre-execute') {
await ctx.waterfall(ctx as never, name, exec, () => Promise.resolve({ kind: 'allow' as const }))
} else {
await ctx.waterfall(ctx as never, name, exec, () => Promise.resolve(outcome()))
}
}
describe('tool-pipeline invariants', () => {
it('accepts dispatch and denial stage orders with frozen results', async () => {
const ctx = await setup()
const dispatched = execution()
await stage(ctx, 'tools/pre-execute', dispatched)
await stage(ctx, 'tools/execute', dispatched)
await ctx.waterfall(ctx as never, 'tools/post-execute', dispatched, outcome(), () => Promise.resolve({ kind: 'accept' as const }))
Object.freeze(dispatched)
emitResult(ctx, dispatched, outcome())
const denied = execution({ callId: CallId('call-2') })
await stage(ctx, 'tools/pre-execute', denied)
await ctx.waterfall(ctx as never, 'tools/post-execute', denied, outcome(), () => Promise.resolve({ kind: 'accept' as const }))
Object.freeze(denied)
emitResult(ctx, denied, outcome())
ctx.emit('tools/change')
})
it('rejects repeated and out-of-order pipeline stages', async () => {
const ctx = await setup()
const exec = execution()
await stage(ctx, 'tools/pre-execute', exec)
await expect(stage(ctx, 'tools/pre-execute', exec)).rejects.toThrow(/repeated/)
const noPre = execution({ callId: CallId('call-2') })
await expect(stage(ctx, 'tools/execute', noPre)).rejects.toThrow(/must follow tools\/pre-execute/)
expect(() => ctx.waterfall(
ctx as never, 'tools/post-execute', noPre, outcome(),
() => Promise.resolve({ kind: 'accept' as const }),
)).toThrow(/must follow tools\/pre-execute or tools\/execute/)
})
it('rejects mutable or anonymous final snapshots', async () => {
const ctx = await setup()
expect(() => { emitResult(ctx, execution(), outcome()) }).toThrow(/execution must be frozen/)
const exec = Object.freeze(execution())
expect(() => { emitResult(ctx, exec, { content: [], isError: false }) })
.toThrow(/outcome and content must be frozen/)
const anonymous = Object.freeze(execution({ name: '' }))
expect(() => { emitResult(ctx, anonymous, outcome()) }).toThrow(/non-empty name and callId/)
})
})