import { describe, expect, it } from 'vitest' import { Context } from 'cordis' import { SessionId } from '@deepseek-ai/dsh-session' import { WorkflowRunId } from '@deepseek-ai/dsh-workflow' import type { WorkflowAgentEndInfo, WorkflowAgentInfo, WorkflowResultInfo, WorkflowRunInfo, } from '@deepseek-ai/dsh-workflow' import * as WorkflowInvariant from '@deepseek-ai/dsh-workflow/invariant' import InvariantService from '@deepseek-ai/dsh-invariants' async function setup(): Promise { const ctx = new Context() await ctx.plugin(InvariantService) await ctx.plugin(WorkflowInvariant) return ctx } const info = (overrides: Partial = {}): WorkflowRunInfo => ({ id: WorkflowRunId('workflow-1'), meta: { name: 'review', description: 'Review a change' }, ...overrides, }) const agent = (overrides: Partial = {}): WorkflowAgentInfo => ({ seq: 1, label: 'reviewer', childId: SessionId('child-1'), ...overrides, }) const agentEnd = (overrides: Partial = {}): WorkflowAgentEndInfo => ({ ...agent(), outcome: 'completed', ...overrides, }) const result = (overrides: Partial = {}): WorkflowResultInfo => ({ stopReason: 'completed', agentsStarted: 1, ...overrides, }) describe('workflow invariants', () => { it('accepts a complete workflow and child lifecycle', async () => { const ctx = await setup() const run = info() ctx.emit('workflow/start', run) ctx.emit('workflow/phase', run, 'inspect') ctx.emit('workflow/log', run, 'working') ctx.emit('workflow/agent-start', run, agent()) ctx.emit('workflow/agent-end', run, agentEnd()) ctx.emit('workflow/end', run, result()) ctx.emit('tools/change') }) it('rejects invalid run identity and enclosure', async () => { const ctx = await setup() expect(() => { ctx.emit('workflow/start', info({ id: WorkflowRunId('') })) }).toThrow(/must be non-empty/) const run = info() ctx.emit('workflow/start', run) expect(() => { ctx.emit('workflow/start', run) }).toThrow(/repeated run id/) expect(() => { ctx.emit('workflow/log', info({ meta: { name: 'other', description: 'x' } }), 'x') }) .toThrow(/meta diverges/) const fresh = await setup() expect(() => { fresh.emit('workflow/log', info(), 'x') }).toThrow(/no matching workflow\/start/) }) it('rejects malformed and unpaired child lifecycles', async () => { const ctx = await setup() const run = info() ctx.emit('workflow/start', run) expect(() => { ctx.emit('workflow/agent-start', run, agent({ seq: 0 })) }).toThrow(/seq must be positive/) ctx.emit('workflow/agent-start', run, agent()) expect(() => { ctx.emit('workflow/agent-start', run, agent()) }).toThrow(/repeated seq/) expect(() => { ctx.emit('workflow/agent-end', run, agentEnd({ seq: 2 })) }).toThrow(/no matching start/) expect(() => { ctx.emit('workflow/agent-end', run, agentEnd({ childId: SessionId('other') })) }) .toThrow(/identity diverges/) expect(() => { ctx.emit('workflow/agent-end', run, agentEnd({ outcome: 'unknown' as never })) }) .toThrow(/unknown outcome/) }) it('rejects inconsistent terminal results', async () => { const active = await setup() active.emit('workflow/start', info()) active.emit('workflow/agent-start', info(), agent()) expect(() => { active.emit('workflow/end', info(), result()) }).toThrow(/without workflow\/agent-end/) const count = await setup() count.emit('workflow/start', info()) count.emit('workflow/agent-start', info(), agent()) count.emit('workflow/agent-end', info(), agentEnd()) expect(() => { count.emit('workflow/end', info(), result({ agentsStarted: 0 })) }) .toThrow(/covering every observed agent start/) const completed = await setup() completed.emit('workflow/start', info()) expect(() => { completed.emit('workflow/end', info(), result({ error: 'unexpected' })) }) .toThrow(/absent exactly for completed/) const failed = await setup() failed.emit('workflow/start', info()) expect(() => { failed.emit('workflow/end', info(), result({ stopReason: 'error' })) }) .toThrow(/absent exactly for completed/) }) })