moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,348 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { MindDB, ExecutionTraceStore } from '@waggle/core';
import {
EvalDatasetBuilder,
detectSecrets,
SECRET_PATTERN_NAMES,
toJSONL as evalToJSONL,
fromJSONL as evalFromJSONL,
type EvalExample,
} from '../src/eval-dataset.js';
describe('detectSecrets', () => {
it('returns null for clean text', () => {
expect(detectSecrets('Hello world, no secrets here.')).toBeNull();
expect(detectSecrets('pk_test_looks_partial')).toBeNull(); // too short
});
it('detects AWS access keys', () => {
expect(detectSecrets('AKIAIOSFODNN7EXAMPLE used for upload')).toBe('aws-access-key');
});
it('detects GitHub PATs', () => {
expect(detectSecrets('token=ghp_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789')).toBe('github-pat');
});
it('detects OpenAI keys', () => {
expect(detectSecrets('sk-proj-aaaaaaaaaaaaaaaaaaaaaaaa')).toBe('openai-key');
});
it('detects Anthropic keys', () => {
expect(detectSecrets('key = sk-ant-api03-aaaaaaaaaaaaaaaaaaa')).toBe('anthropic-key');
});
it('detects Google API keys', () => {
// Google API keys = literal "AIza" + exactly 35 chars
expect(detectSecrets('AIzaSyA0B1c2d3e4F5G6H7I8J9K0L1M2N3O4P5Q')).toBe('google-api-key');
});
it('detects Stripe keys', () => {
expect(detectSecrets('sk_live_51AbCdEfGhIjKlMnOpQrStUvWxYz')).toBe('stripe-secret');
});
it('detects private key blocks', () => {
expect(detectSecrets('-----BEGIN RSA PRIVATE KEY-----\nMII...')).toBe('private-key-block');
});
it('detects JWT tokens', () => {
const jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
expect(detectSecrets(jwt)).toBe('jwt');
});
it('detects basic-auth URLs', () => {
expect(detectSecrets('Use https://user:hunter2@example.com/api'))
.toBe('basic-auth-url');
});
it('detects env-var password assignments', () => {
expect(detectSecrets('PASSWORD=supersecret123'))
.toBe('env-password');
});
it('detects postgres connection strings with creds', () => {
expect(detectSecrets('postgres://admin:pw1234@db.local:5432/prod'))
.toBe('pgsql-url');
});
it('SECRET_PATTERN_NAMES is non-empty and unique', () => {
expect(SECRET_PATTERN_NAMES.length).toBeGreaterThan(10);
expect(new Set(SECRET_PATTERN_NAMES).size).toBe(SECRET_PATTERN_NAMES.length);
});
});
describe('JSONL round-trip', () => {
const examples: EvalExample[] = [
{
input: 'write a haiku',
expected_output: 'Cherry blossoms fall / ...',
metadata: { source: 'trace', personaId: 'writer', outcome: 'success' },
},
{
input: 'explain closures',
expected_output: 'A closure captures variables from its enclosing scope.',
metadata: { source: 'trace', personaId: 'coder', outcome: 'verified' },
},
];
it('round-trips through JSONL', () => {
const jsonl = evalToJSONL(examples);
expect(jsonl.split('\n')).toHaveLength(2);
const parsed = evalFromJSONL(jsonl);
expect(parsed).toEqual(examples);
});
it('skips malformed lines silently', () => {
const jsonl = [
JSON.stringify(examples[0]),
'{ this is not valid json',
'',
JSON.stringify(examples[1]),
].join('\n');
const parsed = evalFromJSONL(jsonl);
expect(parsed).toHaveLength(2);
});
it('skips lines missing required fields', () => {
const jsonl = [
JSON.stringify({ input: 'x' }), // missing expected_output + metadata
JSON.stringify(examples[0]),
].join('\n');
const parsed = evalFromJSONL(jsonl);
expect(parsed).toHaveLength(1);
});
});
describe('EvalDatasetBuilder', () => {
let db: MindDB;
let store: ExecutionTraceStore;
let builder: EvalDatasetBuilder;
beforeEach(() => {
db = new MindDB(':memory:');
store = new ExecutionTraceStore(db);
builder = new EvalDatasetBuilder(store);
});
afterEach(() => {
db.close();
});
function seedTrace(input: string, output: string, outcome: 'success' | 'verified' | 'corrected' | 'abandoned' = 'success', personaId = 'coder'): number {
const id = store.start({ input, personaId });
store.finalize(id, { outcome, output });
return id;
}
// ── sourceFromTraces ──
describe('sourceFromTraces', () => {
it('pulls only traces matching the requested outcomes', () => {
seedTrace('a?', 'A.', 'success');
seedTrace('b?', 'B.', 'verified');
seedTrace('c?', 'C.', 'corrected');
seedTrace('d?', 'D.', 'abandoned');
const examples = builder.sourceFromTraces(['success', 'verified'], false);
expect(examples).toHaveLength(2);
expect(examples.map(e => e.input).sort()).toEqual(['a?', 'b?']);
});
it('includes corrections when flag set', () => {
seedTrace('a?', 'A.', 'success');
seedTrace('b?', 'B.', 'corrected');
const examples = builder.sourceFromTraces(['success'], true);
expect(examples).toHaveLength(2);
const corrected = examples.find(e => e.metadata.outcome === 'corrected');
expect(corrected?.metadata.source).toBe('correction');
});
it('maps trace fields onto metadata', () => {
seedTrace('hello', 'world', 'success', 'writer');
const [example] = builder.sourceFromTraces(['success'], false);
expect(example.metadata.personaId).toBe('writer');
expect(example.metadata.traceId).toBeGreaterThan(0);
expect(example.metadata.source).toBe('trace');
});
});
// ── build (end-to-end) ──
describe('build', () => {
it('returns deterministic split with same seed', async () => {
for (let i = 0; i < 20; i++) {
seedTrace(`question ${i}?`, `answer ${i}.`, 'success');
}
const a = await builder.build({ seed: 42 });
const b = await builder.build({ seed: 42 });
expect(a.train.map(e => e.input)).toEqual(b.train.map(e => e.input));
expect(a.val.map(e => e.input)).toEqual(b.val.map(e => e.input));
expect(a.holdout.map(e => e.input)).toEqual(b.holdout.map(e => e.input));
});
it('different seeds produce different orderings', async () => {
for (let i = 0; i < 30; i++) {
seedTrace(`question ${i} here?`, `answer ${i} here.`, 'success');
}
const a = await builder.build({ seed: 1 });
const b = await builder.build({ seed: 999 });
const aIds = a.train.map(e => e.input).join('|');
const bIds = b.train.map(e => e.input).join('|');
expect(aIds).not.toBe(bIds);
});
it('splits 60/20/20 when enough examples', async () => {
for (let i = 0; i < 100; i++) {
seedTrace(`question ${i} here?`, `answer ${i} here.`, 'success');
}
const split = await builder.build({ seed: 1 });
expect(split.train.length).toBe(60);
expect(split.val.length).toBe(20);
expect(split.holdout.length).toBe(20);
expect(split.stats.total).toBe(100);
});
it('honors custom split ratios', async () => {
for (let i = 0; i < 50; i++) {
seedTrace(`question ${i} here?`, `answer ${i} here.`, 'success');
}
const split = await builder.build({ seed: 1, splitRatios: [0.8, 0.1, 0.1] });
expect(split.train.length).toBe(40);
expect(split.val.length).toBe(5);
expect(split.holdout.length).toBe(5);
});
it('rejects ratios that do not sum to 1', async () => {
await expect(builder.build({ splitRatios: [0.5, 0.3, 0.3] })).rejects.toThrow();
});
it('filters out examples containing secrets', async () => {
seedTrace('clean input', 'clean output', 'success');
seedTrace('here is a key: AKIAIOSFODNN7EXAMPLE', 'sure', 'success');
seedTrace('what is your token?', 'ghp_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789', 'success');
const split = await builder.build({ seed: 1 });
expect(split.stats.sourced).toBe(3);
expect(split.stats.acceptedAfterSecretScan).toBe(1);
expect(split.rejected.filter(r => r.reason.startsWith('secret:'))).toHaveLength(2);
});
it('filters examples by min length', async () => {
seedTrace('?', 'ok', 'success'); // below both mins
seedTrace('a proper question', 'a proper answer', 'success');
const split = await builder.build({ seed: 1, minInputChars: 5, minOutputChars: 3 });
expect(split.stats.total).toBe(1);
expect(split.rejected.some(r => r.reason === 'too-short-input')).toBe(true);
});
it('filters examples over max combined length', async () => {
seedTrace('this is an ok prompt', 'this is an ok answer', 'success');
seedTrace('x'.repeat(5000), 'y'.repeat(5000), 'success');
const split = await builder.build({ seed: 1, maxCombinedChars: 1000 });
expect(split.stats.total).toBe(1);
expect(split.rejected.some(r => r.reason === 'too-long')).toBe(true);
});
it('filters low-signal garbage', async () => {
seedTrace('.......................', '!!!!!!!!!!!!!!!!', 'success');
seedTrace('real question?', 'real answer.', 'success');
const split = await builder.build({ seed: 1 });
expect(split.stats.total).toBe(1);
expect(split.rejected.some(r => r.reason === 'low-signal')).toBe(true);
});
it('dedupes by input hash', async () => {
seedTrace('same prompt', 'answer a', 'success');
seedTrace('same prompt', 'answer b', 'success');
seedTrace('different prompt', 'answer c', 'success');
const split = await builder.build({ seed: 1 });
expect(split.stats.unique).toBe(2);
expect(split.rejected.some(r => r.reason === 'duplicate')).toBe(true);
});
it('applies optional LLM judge', async () => {
seedTrace('keep this example please', 'kept response', 'success');
seedTrace('drop this example please', 'dropped response', 'success');
const split = await builder.build({
seed: 1,
judge: async (ex) => ({
keep: !ex.input.includes('drop'),
reason: ex.input.includes('drop') ? 'contains drop' : undefined,
}),
});
expect(split.stats.acceptedAfterJudge).toBe(1);
expect(split.rejected.some(r => r.reason.startsWith('judge:'))).toBe(true);
});
it('keeps example when judge throws (fail-open)', async () => {
seedTrace('some prompt text', 'some answer text', 'success');
const split = await builder.build({
seed: 1,
judge: async () => {
throw new Error('llm down');
},
});
expect(split.stats.acceptedAfterJudge).toBe(1);
expect(split.stats.total).toBe(1);
});
it('accepts external harvest/correction examples', async () => {
const harvest: EvalExample = {
input: 'harvest prompt',
expected_output: 'harvest answer',
metadata: { source: 'harvest' },
};
const correction: EvalExample = {
input: 'correction prompt',
expected_output: 'correction answer',
metadata: { source: 'correction' },
};
const split = await builder.build({
seed: 1,
harvestExamples: [harvest],
correctionExamples: [correction],
});
const allInputs = [...split.train, ...split.val, ...split.holdout].map(e => e.input);
expect(allInputs).toContain('harvest prompt');
expect(allInputs).toContain('correction prompt');
});
it('maps correction trace output to correctionFeedback when available', async () => {
const id = store.start({ input: 'wrong approach', personaId: 'coder' });
store.finalize(id, { outcome: 'success', output: 'first try' });
store.markCorrected(id, 'Should use async/await here');
const examples = builder.sourceFromTraces(['success'], true);
const corrected = examples.find(e => e.metadata.outcome === 'corrected');
expect(corrected?.expected_output).toBe('Should use async/await here');
});
it('returns empty splits when no traces match', async () => {
const split = await builder.build({ seed: 1 });
expect(split.stats.total).toBe(0);
expect(split.train).toEqual([]);
expect(split.val).toEqual([]);
expect(split.holdout).toEqual([]);
});
it('tiny datasets assign leftover to holdout', async () => {
for (let i = 0; i < 3; i++) {
seedTrace(`question ${i} here?`, `answer ${i} here.`, 'success');
}
// Ratios 0.6/0.2/0.2 with n=3 → floor(1.8)=1, floor(0.6)=0, remainder=2 → holdout
const split = await builder.build({ seed: 1 });
expect(split.train.length + split.val.length + split.holdout.length).toBe(3);
});
});
});