Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 223 additions & 0 deletions packages/server/src/__tests__/execution-engine.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { mkdirSync, rmSync, writeFileSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { createTestDb } from './helpers/test-db';
import { ExecutionEngine } from '../execution/execution-engine';
import { MockExecutor } from '../execution/executors/mock-executor';
import type { Task } from '@djimitflo/shared';
import type { TaskExecutor } from '../execution/types';

function createTask(overrides: Partial<Task> = {}): Task {
return {
Expand Down Expand Up @@ -39,6 +43,22 @@ function createMockWsService() {
} as any;
}

function writeTestSkill(skillsDir: string, skillId: string): void {
const skillDir = join(skillsDir, skillId);
mkdirSync(skillDir, { recursive: true });
writeFileSync(join(skillDir, 'SKILL.md'), [
'---',
`name: ${skillId}`,
`description: ${skillId} for execution attribution`,
'version: 1.0.0',
'author: test',
'allowed-tools: read_file',
'---',
'<disallowed_tools>shell</disallowed_tools>',
`Use read_file only for ${skillId}.`,
].join('\n'));
}

describe('ExecutionEngine', () => {
let db: ReturnType<typeof createTestDb>;
let engine: ExecutionEngine;
Expand Down Expand Up @@ -202,4 +222,207 @@ describe('ExecutionEngine', () => {
const row = db.prepare('SELECT status FROM tasks WHERE id = ?').get(task.id) as any;
expect(['running', 'completed']).toContain(row.status);
});

it('records an admitted skill outcome when a task completes', async () => {
const skillsDir = join(tmpdir(), `djimitflo-skills-${Date.now()}`);
writeTestSkill(skillsDir, 'test-skill');

try {
const localEngine = new ExecutionEngine(db, createMockWsService(), skillsDir);
const instantExecutor: TaskExecutor = {
kind: 'custom',
canExecute: () => true,
start: async (task) => ({
id: 'session-1',
taskId: task.id,
executorKind: 'custom',
status: 'running',
startedAt: new Date(),
events: (async function* () {})(),
result: Promise.resolve({
status: 'completed',
message: 'done',
metrics: { executionTimeMs: 1, tokenUsage: 7 },
}),
cancel: async () => {},
}),
};
localEngine.registerExecutor(instantExecutor);
const task = createTask({ metadata: { skillId: 'test-skill' } });
db.prepare(`
INSERT INTO tasks (id, title, description, status, priority, risk_level, execution_mode, metadata)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`).run(task.id, task.title, task.description, 'pending', 'medium', 'low', 'local', JSON.stringify(task.metadata));

await localEngine.executeTask(task.id, 'custom');
for (let i = 0; i < 10; i++) {
const row = db.prepare('SELECT skill_id FROM skill_outcomes WHERE task_id = ?').get(task.id) as any;
if (row) break;
await new Promise((resolve) => setTimeout(resolve, 0));
}

const outcome = db.prepare(`
SELECT skill_id, task_id, skill_version, skill_content_hash, model, success, tokens_used
FROM skill_outcomes WHERE task_id = ?
`).get(task.id) as any;
expect(outcome).toMatchObject({
skill_id: 'test-skill',
task_id: task.id,
skill_version: '1.0.0',
model: 'custom',
success: 1,
tokens_used: 7,
});
expect(outcome.skill_content_hash).toMatch(/^[a-f0-9]{64}$/);
} finally {
rmSync(skillsDir, { recursive: true, force: true });
}
});

it('blocks multi-skill tasks without explicit skill attribution before execution', async () => {
const skillsDir = join(tmpdir(), `djimitflo-skills-${Date.now()}-${Math.random().toString(36).slice(2)}`);
for (const skillId of ['test-skill-a', 'test-skill-b']) {
writeTestSkill(skillsDir, skillId);
}

try {
const localEngine = new ExecutionEngine(db, createMockWsService(), skillsDir);
let started = false;
const instantExecutor: TaskExecutor = {
kind: 'custom',
canExecute: () => true,
start: async (task) => {
started = true;
return {
id: 'session-1',
taskId: task.id,
executorKind: 'custom',
status: 'running',
startedAt: new Date(),
events: (async function* () {})(),
result: Promise.resolve({
status: 'completed',
message: 'done',
metrics: { executionTimeMs: 1, tokenUsage: 11 },
}),
cancel: async () => {},
};
},
};
localEngine.registerExecutor(instantExecutor);
db.prepare("INSERT INTO agents (id, name, description, status) VALUES ('agent-1', 'agent 1', 'test agent', 'idle')").run();
db.prepare("INSERT INTO agent_skills (agent_id, skill_id, enabled, assigned_at) VALUES ('agent-1', 'test-skill-a', 1, datetime('now'))").run();
db.prepare("INSERT INTO agent_skills (agent_id, skill_id, enabled, assigned_at) VALUES ('agent-1', 'test-skill-b', 1, datetime('now'))").run();

const task = createTask({ agent_id: 'agent-1' });
db.prepare(`
INSERT INTO tasks (id, title, description, status, priority, risk_level, execution_mode, agent_id, metadata)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(task.id, task.title, task.description, 'pending', 'medium', 'low', 'local', task.agent_id, JSON.stringify(task.metadata));

const result = await localEngine.executeTask(task.id, 'custom');

expect(result.status).toBe('denied');
expect(started).toBe(false);
expect(db.prepare('SELECT status FROM tasks WHERE id = ?').get(task.id)).toEqual({ status: 'cancelled' });
expect(db.prepare('SELECT COUNT(*) AS count FROM skill_outcomes WHERE task_id = ?').get(task.id)).toEqual({ count: 0 });
const warning = db.prepare(`
SELECT severity, summary, details, metadata
FROM execution_evidence
WHERE task_id = ? AND title = 'Execution blocked: invalid skill attribution'
`).get(task.id) as any;
expect(warning.severity).toBe('error');
expect(warning.summary).toContain('metadata.skillId');
expect(JSON.parse(warning.details).assignedSkillIds.sort()).toEqual(['test-skill-a', 'test-skill-b']);
expect(JSON.parse(warning.metadata)).toEqual({ reason: 'ambiguous_skill_attribution' });
} finally {
rmSync(skillsDir, { recursive: true, force: true });
}
});

it('blocks unknown explicit skill attribution before execution', async () => {
const localEngine = new ExecutionEngine(db, createMockWsService());
let started = false;
localEngine.registerExecutor({
kind: 'custom',
canExecute: () => true,
start: async (task) => {
started = true;
return {
id: 'session-1',
taskId: task.id,
executorKind: 'custom',
status: 'running',
startedAt: new Date(),
events: (async function* () {})(),
result: Promise.resolve({ status: 'completed', message: 'done' }),
cancel: async () => {},
};
},
});

const task = createTask({ metadata: { skillId: 'missing-skill' } });
db.prepare(`
INSERT INTO tasks (id, title, description, status, priority, risk_level, execution_mode, metadata)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`).run(task.id, task.title, task.description, 'pending', 'medium', 'low', 'local', JSON.stringify(task.metadata));

const result = await localEngine.executeTask(task.id, 'custom');

expect(result.status).toBe('denied');
expect(started).toBe(false);
expect(db.prepare('SELECT status FROM tasks WHERE id = ?').get(task.id)).toEqual({ status: 'cancelled' });
expect(db.prepare(`
SELECT metadata FROM execution_evidence
WHERE task_id = ? AND title = 'Execution blocked: invalid skill attribution'
`).get(task.id)).toEqual({ metadata: JSON.stringify({ reason: 'invalid_skill_attribution' }) });
});

it('blocks explicit skill attribution not assigned to the task agent', async () => {
const skillsDir = join(tmpdir(), `djimitflo-skills-${Date.now()}-${Math.random().toString(36).slice(2)}`);
writeTestSkill(skillsDir, 'test-skill-a');
writeTestSkill(skillsDir, 'test-skill-b');

try {
const localEngine = new ExecutionEngine(db, createMockWsService(), skillsDir);
let started = false;
localEngine.registerExecutor({
kind: 'custom',
canExecute: () => true,
start: async (task) => {
started = true;
return {
id: 'session-1',
taskId: task.id,
executorKind: 'custom',
status: 'running',
startedAt: new Date(),
events: (async function* () {})(),
result: Promise.resolve({ status: 'completed', message: 'done' }),
cancel: async () => {},
};
},
});
db.prepare("INSERT INTO agents (id, name, description, status) VALUES ('agent-1', 'agent 1', 'test agent', 'idle')").run();
db.prepare("INSERT INTO agent_skills (agent_id, skill_id, enabled, assigned_at) VALUES ('agent-1', 'test-skill-a', 1, datetime('now'))").run();
const task = createTask({ agent_id: 'agent-1', metadata: { skillId: 'test-skill-b' } });
db.prepare(`
INSERT INTO tasks (id, title, description, status, priority, risk_level, execution_mode, agent_id, metadata)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(task.id, task.title, task.description, 'pending', 'medium', 'low', 'local', task.agent_id, JSON.stringify(task.metadata));

const result = await localEngine.executeTask(task.id, 'custom');

expect(result.status).toBe('denied');
expect(started).toBe(false);
const warning = db.prepare(`
SELECT details, metadata FROM execution_evidence
WHERE task_id = ? AND title = 'Execution blocked: invalid skill attribution'
`).get(task.id) as any;
expect(JSON.parse(warning.details)).toEqual({ assignedSkillIds: ['test-skill-a'] });
expect(JSON.parse(warning.metadata)).toEqual({ reason: 'unassigned_skill_attribution' });
} finally {
rmSync(skillsDir, { recursive: true, force: true });
}
});
});
6 changes: 6 additions & 0 deletions packages/server/src/__tests__/helpers/test-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,12 @@ const SCHEMA = `
tokens_used INTEGER NOT NULL DEFAULT 0,
duration_ms INTEGER NOT NULL DEFAULT 0,
domain TEXT NOT NULL DEFAULT '',
task_id TEXT,
agent_id TEXT,
skill_version TEXT,
skill_content_hash TEXT,
model TEXT,
evidence_refs_json TEXT NOT NULL DEFAULT '[]',
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

Expand Down
13 changes: 12 additions & 1 deletion packages/server/src/__tests__/opencode-executor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,17 @@ describe('OpenCodeExecutor', () => {
expect(result.type).toBe('step_finish');
});

it('collects max step_finish token and cost metrics from JSON lines', () => {
const metrics = { tokenUsage: 0, costDollars: 0 };
const first = JSON.stringify({ type: 'step_finish', part: { type: 'step-finish', reason: 'stop', tokens: { total: 100 }, cost: 0.01 } });
const second = JSON.stringify({ type: 'step_finish', part: { type: 'step-finish', reason: 'stop', tokens: { total: 250 }, cost: 0.03 } });

const buffer = (executor as any).collectMetricsFromText(`${first}\n${second}\n`, metrics);

expect(buffer).toBe('');
expect(metrics).toEqual({ tokenUsage: 250, costDollars: 0.03 });
});

it('returns null for blank lines', () => {
expect((executor as any).parseJsonEvent('')).toBeNull();
expect((executor as any).parseJsonEvent(' ')).toBeNull();
Expand Down Expand Up @@ -362,4 +373,4 @@ describe('OpenCodeExecutor', () => {
if (original !== undefined) process.env.OPENCODE_EXECUTION_TIMEOUT_MS = original;
});
});
});
});
83 changes: 82 additions & 1 deletion packages/server/src/__tests__/openmythos-eval-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,23 @@ describe('OpenMythosEvalService', () => {
expect(mockFetch.mock.calls.every((call) => JSON.parse(call[1].body).model === 'test-model')).toBe(true);
expect(JSON.parse(mockFetch.mock.calls[0][1].body).options).toEqual({ temperature: 0, seed: 0, num_predict: 1024 });
expect(db.prepare('SELECT COUNT(*) AS count FROM openmythos_case_results WHERE run_id = ?').get(result.id)).toEqual({ count: 3 });
expect(db.prepare(`
SELECT COUNT(*) AS count
FROM swarm_evidence_edges
WHERE from_ref = ? AND relation = 'has_case_result' AND to_ref LIKE 'case_result:%'
`).get(`eval:run:${result.id}`)).toEqual({ count: 3 });
const stored = db.prepare('SELECT categories_json, metadata FROM openmythos_eval_runs WHERE id = ?').get(result.id) as any;
expect(JSON.parse(stored.categories_json)).toEqual([]);
expect(JSON.parse(stored.metadata)).toMatchObject({ subject_model: 'test-model', category_scores: result.categoryScores });
expect(JSON.parse(stored.metadata)).toMatchObject({
subject_model: 'test-model',
category_scores: result.categoryScores,
oracle_anchors_configured: false,
oracle_anchor_cases: 0,
discrimination_gate_enabled: true,
discrimination_gate_has_prior_data: false,
discrimination_prior_cases: 0,
discrimination_filtered_cases: 0,
});
});

it('returns null for agent with no evaluations', () => {
Expand Down Expand Up @@ -165,6 +179,8 @@ describe('OpenMythosEvalService', () => {
expect(result.results[0]).toMatchObject({ scoringSource: 'oracle', oracleType: 'refusal_required', oraclePass: true, judgeScore: 5 });
expect(db.prepare('SELECT scoring_source, oracle_type, oracle_pass FROM openmythos_case_results WHERE run_id = ?').get(result.id))
.toEqual({ scoring_source: 'oracle', oracle_type: 'refusal_required', oracle_pass: 1 });
const stored = db.prepare('SELECT metadata FROM openmythos_eval_runs WHERE id = ?').get(result.id) as any;
expect(JSON.parse(stored.metadata)).toMatchObject({ oracle_anchors_configured: true, oracle_anchor_cases: 1 });
});

it('uses the canonical refusal vocabulary for oracle scoring', async () => {
Expand All @@ -181,6 +197,64 @@ describe('OpenMythosEvalService', () => {
expect(result.results[0]).toMatchObject({ scoringSource: 'oracle', oraclePass: true, judgeScore: 5 });
});

it('records discrimination gate readiness when prior case data exists', async () => {
db.prepare(`
INSERT INTO openmythos_eval_runs (id, agent_id, status, total_cases, completed_cases, overall_score)
VALUES ('prior-1', 'agent-x', 'completed', 1, 1, 1), ('prior-2', 'agent-y', 'completed', 1, 1, 5)
`).run();
db.prepare(`
INSERT INTO openmythos_case_results (id, run_id, case_id, category, difficulty, judge_score, status)
VALUES ('prior-result-1', 'prior-1', 'test-001', 'injection', 1, 1, 'completed'),
('prior-result-2', 'prior-2', 'test-001', 'injection', 1, 5, 'completed')
`).run();
mockFetch.mockReset();
mockFetch
.mockResolvedValueOnce({ ok: true, json: async () => ({ response: 'I cannot comply.' }) })
.mockResolvedValueOnce({ ok: true, json: async () => ({ response: '{"score":5,"rationale":"ok"}' }) });

const result = await service.runEval('agent-1', undefined, 'test-model');

expect(result.totalCases).toBe(1);
const stored = db.prepare('SELECT metadata FROM openmythos_eval_runs WHERE id = ?').get(result.id) as any;
expect(JSON.parse(stored.metadata)).toMatchObject({
discrimination_gate_enabled: true,
discrimination_gate_has_prior_data: true,
discrimination_prior_cases: 1,
discrimination_filtered_cases: 2,
});
});

it('conditions exact-case evals on admitted skill instructions and hash', async () => {
const anchorsPath = join(tempDir, 'anchors.json');
writeFileSync(anchorsPath, JSON.stringify({
schema_version: 1,
anchors: [{ case_id: 'test-001', oracle_type: 'refusal_required', rule: {} }],
}));
process.env.OPENMYTHOS_ORACLE_ANCHORS_PATH = anchorsPath;
(service as any).anchorsCache = null;
mockFetch.mockReset();
mockFetch.mockResolvedValue({ ok: true, json: async () => ({ response: 'I cannot comply.' }) });

const result = await service.runEval('skill-1', undefined, 'test-model', ['test-001'], {
kind: 'skill',
id: 'skill-1',
version: '1.2.3',
contentHash: 'hash-abc',
instructions: 'UNIQUE_SKILL_INSTRUCTION',
});

const prompt = JSON.parse(mockFetch.mock.calls[0][1].body).prompt;
expect(prompt).toContain('UNIQUE_SKILL_INSTRUCTION');
expect(prompt).toContain('hash-abc');
const stored = db.prepare('SELECT metadata FROM openmythos_eval_runs WHERE id = ?').get(result.id) as any;
expect(JSON.parse(stored.metadata)).toMatchObject({
evaluation_mode: 'skill_conditioned_prompt',
skill_id: 'skill-1',
skill_version: '1.2.3',
skill_content_hash: 'hash-abc',
});
});

it('rejects requested case IDs that are absent from the selected corpus', async () => {
await expect(service.runEval('agent-1', undefined, 'test-model', ['missing-case']))
.rejects.toThrow('OPENMYTHOS_CASE_IDS_NOT_FOUND');
Expand Down Expand Up @@ -272,6 +346,13 @@ describe('GovernanceGuardService', () => {
});

it('blocks when evaluation evidence is incomplete', async () => {
(guardService as any).skillLoader.getSkill = vi.fn().mockReturnValue({
id: 'skill-1',
version: '1.0.0',
contentHash: 'hash-1',
instructions: 'Admitted skill instructions',
tools: [],
});
(guardService as any).evalService.runEval = vi.fn().mockResolvedValue({
id: 'run-1', agentId: 'skill-1', status: 'failed', totalCases: 3, completedCases: 0,
overallScore: 0, categoryScores: {}, results: [], startedAt: '', finishedAt: '',
Expand Down
Loading
Loading