Files
brain/tools/observer-verdicts.test.mjs
T
Дмитрий 81da2e2c45 feat(observer): эпизод несёт четыре вердикта (роутер/наставник/судья/gate3)
Новый observer-verdicts: читает персистентный verdict-snapshot-<sid>.json и сводит к четырём
звеньям (последний вердикт по ts на звено). Эпизод наблюдателя получил поле verdicts из снимка
текущей сессии → по логам восстановимо, на каком звене план отскочил. Раньше в эпизоде был только
сигнал роутера. Граница не тронута (observer-stop-hook, recommended_chain, цепочки). Хвост спеки
роутера §7 (логирование решающих), эпик роутер-реестр этап 3, item 3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 08:42:14 +03:00

58 lines
2.7 KiB
JavaScript

// tools/observer-verdicts.test.mjs
import { describe, it, expect } from 'vitest';
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { readVerdictSnapshot, extractFourVerdicts } from './observer-verdicts.mjs';
describe('extractFourVerdicts (D1)', () => {
it('снимок с четырьмя звеньями → все четыре с правильным status', () => {
const snap = {
router: { status: 'recommend', reason: 'r', ts: 1 },
'mentor:plan': { status: 'GO', reason: 'm', ts: 2 },
'judge:plan': { status: 'GO', reason: 'j', ts: 3 },
'judge:gate3': { status: 'NO-GO', reason: 'g', ts: 4 },
};
const v = extractFourVerdicts(snap);
expect(v.router.status).toBe('recommend');
expect(v.mentor.status).toBe('GO');
expect(v.judge.status).toBe('GO');
expect(v.gate3.status).toBe('NO-GO');
});
it('несколько записей звена → берётся последняя по ts', () => {
const snap = {
'mentor:plan': { status: 'NO-GO', reason: 'old', ts: 1 },
'mentor:spec': { status: 'GO', reason: 'new', ts: 5 },
};
expect(extractFourVerdicts(snap).mentor).toEqual({ status: 'GO', reason: 'new' });
});
it('отсутствующее звено → null', () => {
expect(extractFourVerdicts({ router: { status: 'recommend', ts: 1 } }).mentor).toBeNull();
});
it('пустой снимок → все четыре null', () => {
const v = extractFourVerdicts({});
expect(v).toEqual({ router: null, mentor: null, judge: null, gate3: null });
});
});
describe('readVerdictSnapshot (D1)', () => {
it('читает файл снимка и агрегирует последнюю запись по стадии', () => {
const baseDir = mkdtempSync(join(tmpdir(), 'verdict-snap-'));
const obj = {
h1: { router: { status: 'recommend', reason: 'r', ts: 1 }, 'mentor:plan': { status: 'NO-GO', reason: 'a', ts: 2 } },
h2: { 'mentor:plan': { status: 'GO', reason: 'b', ts: 9 }, 'judge:plan': { status: 'GO', reason: 'j', ts: 10 } },
};
writeFileSync(join(baseDir, 'verdict-snapshot-sX.json'), JSON.stringify(obj));
const snap = readVerdictSnapshot('sX', baseDir);
expect(snap['router'].status).toBe('recommend');
expect(snap['mentor:plan']).toMatchObject({ status: 'GO', ts: 9 }); // последняя по ts
expect(snap['judge:plan'].status).toBe('GO');
rmSync(baseDir, { recursive: true, force: true });
});
it('нет файла → {} (fail-safe)', () => {
const baseDir = mkdtempSync(join(tmpdir(), 'verdict-snap-'));
expect(readVerdictSnapshot('nope', baseDir)).toEqual({});
rmSync(baseDir, { recursive: true, force: true });
});
});