import { readFile } from 'node:fs/promises' import path from 'node:path' import type { CanonicalPromptRequest, PlaybookMetadata, PlaybookSpecification, RepositoryProfile, } from '@devrunbook/composer' import { describe, expect, it } from 'vitest' import { parse } from 'yaml' import { composeAndCreateGeneratedRun } from './compose-and-create-generated-run' import { computeRenderDigest, type GeneratedRun, type GeneratedRunStore, type ImmutableJsonObject, } from '../generated-runs/create-generated-run' class RecordingStore implements GeneratedRunStore { candidate: GeneratedRun | undefined async createIdempotently(candidate: GeneratedRun) { this.candidate = candidate return { run: candidate, created: true } } } const snapshots = { playbook: { slug: 'bounded-change', version: '1.0.0' }, repositoryProfile: null, normalizedInput: { request: 'Implement the bounded change' }, policy: { autonomyLevel: 'verify', conditionsResolved: true }, provenance: [{ block: 'mission', source: 'playbook' }], } as const function dependencies(store: GeneratedRunStore) { return { store, nextId: () => 'run-1', now: () => new Date('2026-07-27T12:00:00.000Z'), workspaceAuthorization: { findWorkspaceAuthorization: async ( userId: string, workspaceId: string, ) => ({ userId, workspaceId, instanceRole: 'user' as const, workspaceRole: 'editor' as const, userStatus: 'active' as const, }), }, } } describe('composeAndCreateGeneratedRun', () => { it('renders once and immediately persists the exact bytes and verified digest', async () => { const store = new RecordingStore() const result = await composeAndCreateGeneratedRun(dependencies(store), { prompt: { metadata: { slug: 'bounded-change', version: '1.0.0', title: 'Bounded change', }, specification: { intent: { outcome: 'Implement only the requested change.' }, guardrails: [{ text: 'Do not broaden scope.' }], workflow: [ { title: 'Implement', instruction: 'Make the smallest coherent change.', }, ], completion: { criteria: ['Targeted validation passes.'] }, }, template: '# Context\n\nRequest: {{ inputs.request }}', inputs: { request: 'Implement the bounded change' }, workMode: 'execute', autonomyLevel: 'verify', }, snapshots, lint: { exportReadiness: 'ready', findings: [] }, generatedBy: 'user-1', workspaceId: 'workspace-1', playbookVersionId: 'playbook-version-1', idempotencyKey: 'compose-1', }) expect(result.created).toBe(true) expect(store.candidate).toStrictEqual(result.run) expect(result.run.renderedPrompt).toContain( 'Request: Implement the bounded change', ) expect(result.run.renderDigest).toBe( computeRenderDigest(result.run.renderedPrompt), ) expect(Object.isFrozen(result.run.snapshots.policy)).toBe(true) }) it('loads the root-cause inputs and persists golden prompt bytes unchanged', async () => { const repositoryRoot = path.resolve(import.meta.dirname, '../../../..') const playbookRoot = path.join( repositoryRoot, 'content/playbooks/root-cause-bugfix', ) const playbook = parse( await readFile(path.join(playbookRoot, 'playbook.yaml'), 'utf8'), ) as { metadata: PlaybookMetadata spec: PlaybookSpecification & { template: { main: string } } } const example = parse( await readFile(path.join(playbookRoot, 'examples/minimal.yaml'), 'utf8'), ) as { workMode: string autonomyLevel: CanonicalPromptRequest['autonomyLevel'] inputs: CanonicalPromptRequest['inputs'] & ImmutableJsonObject } const profile = parse( await readFile( path.join( repositoryRoot, 'examples/repository-profiles/example-profile.yaml', ), 'utf8', ), ) as RepositoryProfile const golden = await readFile( path.join( repositoryRoot, 'examples/rendered-prompts/root-cause-bugfix.md', ), 'utf8', ) const store = new RecordingStore() const result = await composeAndCreateGeneratedRun(dependencies(store), { prompt: { metadata: playbook.metadata, specification: playbook.spec, template: await readFile( path.join(playbookRoot, playbook.spec.template.main), 'utf8', ), inputs: example.inputs, workMode: example.workMode, autonomyLevel: example.autonomyLevel, repositoryProfile: profile, }, snapshots: { playbook: { slug: playbook.metadata.slug, version: playbook.metadata.version, }, repositoryProfile: { name: profile.metadata.name, revision: 1 }, normalizedInput: example.inputs, policy: { autonomyLevel: example.autonomyLevel }, provenance: [], }, lint: { exportReadiness: 'ready', findings: [] }, generatedBy: 'fixture-user', workspaceId: 'fixture-workspace', playbookVersionId: 'fixture-playbook-version', idempotencyKey: 'root-cause-golden', }) expect(result.run.renderedPrompt).toBe(golden) expect(store.candidate?.renderedPrompt).toBe(golden) expect(result.run.renderDigest).toBe(computeRenderDigest(golden)) }) it('rejects invalid declared inputs, modes, autonomy and missing repositories before storage', async () => { const store = new RecordingStore() await expect( composeAndCreateGeneratedRun(dependencies(store), { prompt: { metadata: { slug: 'strict', version: '1.0.0', title: 'Strict' }, specification: { intent: { outcome: 'Validate first.' }, modes: ['execute'], autonomy: { min: 'implement', max: 'verify', default: 'verify' }, inputs: [ { key: 'request', type: 'string', required: true, minLength: 3, }, ], compatibility: { repositoryRequired: true }, }, template: '{{ inputs.request }}', inputs: { request: '' }, workMode: 'inspect', autonomyLevel: 'repair', }, snapshots: { ...snapshots, normalizedInput: { request: 'different' }, policy: { autonomyLevel: 'observe' }, }, lint: { exportReadiness: 'ready', findings: [] }, generatedBy: 'user-1', workspaceId: 'workspace-1', playbookVersionId: 'playbook-version-1', idempotencyKey: 'strict-invalid', }), ).rejects.toMatchObject({ code: 'composition_input_invalid', details: { issues: expect.arrayContaining([ 'workMode is not supported by this playbook', 'autonomyLevel is outside the playbook autonomy range', 'repositoryProfile is required by this playbook', 'inputs.request must not be empty', 'snapshots.normalizedInput must match the composed inputs', 'snapshots.policy.autonomyLevel must match the composed autonomy level', ]), }, }) expect(store.candidate).toBeUndefined() }) })