Files
portal/tools/freeze-gate.test.mjs
T
Дмитрий b739d5adad feat(mentor): мерж роутера в наставника — единый рецензент (спека+план+скилы) + decision GO/NO-GO
Болезни B (роутер в пустоту) + A (наставник не заворачивал) — лечение Р7/Р8 (Подход 1):
наставник — единый мозг-рецензент, зовёт classify() как функцию (3 слоя + граф nodes.yaml +
карточки — код не тронут, новый вызыватель), судит спеку+план+выбор скилов, заворачивает NO-GO.

- validateMentorVerdict + промпты (план/спека): явное decision GO|NO-GO (поглощённый Р7)
- plan-skills.mjs: parsePlanSkills (skills-json) + extractPlanGoal (зеркало extractGoal судьи)
- mentor-seam: renderSkillContext; onPlanWrite зовёт classifyImpl (fail-safe: сбой → без скил-сверки)
- decideMentorObjection: заворот на decision=NO-GO ИЛИ сломанный вердикт; mentor-GO только на чистом GO
- formatMentorObjection доносит суть (recommendation + reasoning + plan_points), GO -> пусто
- enforce-mentor main: loadRegistry + classify; счётчик L1 decision-aware (Р7/§3.4)
- скил-сверка — только план (gate2); спека (gate1) — по сути + decision
- включает redesign согласования L1->L2 (Фазы 0-6, способ B: наставник->судья->печать)
- регрессия tools-only 3901 passed + 2 skip (база 3877, +24 теста, 0 регрессий)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 11:38:09 +03:00

77 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// tools/freeze-gate.test.mjs
import { describe, it, expect } from 'vitest';
import { freezeGate } from './freeze-gate.mjs';
const goodVerdict = { plan_points_addressed: ['a'], reasoning: 'r', recommendation: 'rec', confidence: 0.8, decision: 'GO', plan_hash: 'PH1' };
// hasUnresolvedExtractedImpl: true = ЕСТЬ неразрешённая EXTRACTED = ГРЯЗНО = блок. Стаб вместо A.
const verityClean = () => false;
const verityDirty = () => true;
const artifactWithExtracted = [{ kind: 'EXTRACTED', claim: 'c', ref: 'f:1' }];
describe('freezeGate (CD-3 предусловие)', () => {
it('валидный вердикт (wired, plan_hash совпал) + чистый контекст с EXTRACTED → pass', () => {
const r = freezeGate({ mentorVerdict: goodVerdict, mentorWired: true, planHash: 'PH1', verifiedContextArtifact: artifactWithExtracted, hasUnresolvedExtractedImpl: verityClean });
expect(r.pass).toBe(true);
});
it('нет/невалидный вердикт → блок (VA-8)', () => {
const r = freezeGate({ mentorVerdict: { reasoning: '', plan_hash: 'PH1' }, mentorWired: true, planHash: 'PH1', verifiedContextArtifact: artifactWithExtracted, hasUnresolvedExtractedImpl: verityClean });
expect(r.pass).toBe(false);
expect(r.reason).toMatch(/вердикт/i);
});
it('вердикт wired:false → блок (SE-R6-6 не суд)', () => {
const r = freezeGate({ mentorVerdict: goodVerdict, mentorWired: false, planHash: 'PH1', verifiedContextArtifact: artifactWithExtracted, hasUnresolvedExtractedImpl: verityClean });
expect(r.pass).toBe(false);
});
it('неразрешённая EXTRACTED в контексте → блок печати (✅O2)', () => {
const r = freezeGate({ mentorVerdict: goodVerdict, mentorWired: true, planHash: 'PH1', verifiedContextArtifact: artifactWithExtracted, hasUnresolvedExtractedImpl: verityDirty });
expect(r.pass).toBe(false);
expect(r.reason).toMatch(/EXTRACTED|verity|цитат/i);
});
it('A1 (нах.F4): verdict.plan_hash ≠ planHash → блок (stale/чужой вердикт)', () => {
const r = freezeGate({ mentorVerdict: { ...goodVerdict, plan_hash: 'СТАРЫЙ' }, mentorWired: true, planHash: 'PH1', verifiedContextArtifact: artifactWithExtracted, hasUnresolvedExtractedImpl: verityClean });
expect(r.pass).toBe(false);
expect(r.reason).toMatch(/привязан|F4/i);
});
it('Д-2а (VA-9/SE-A3): пустой артефакт (0 EXTRACTED) → блок даже при чистом impl', () => {
const r = freezeGate({ mentorVerdict: goodVerdict, mentorWired: true, planHash: 'PH1', verifiedContextArtifact: [], hasUnresolvedExtractedImpl: verityClean });
expect(r.pass).toBe(false);
expect(r.reason).toMatch(/VA-9|EXTRACTED/i);
});
it('Д-2а: артефакт только из INFERRED (0 EXTRACTED) → блок (VA-9)', () => {
const r = freezeGate({ mentorVerdict: goodVerdict, mentorWired: true, planHash: 'PH1', verifiedContextArtifact: [{ kind: 'INFERRED', derivation_ref: 'd' }], hasUnresolvedExtractedImpl: verityClean });
expect(r.pass).toBe(false);
});
it('F-C4 (sharp-edges, ужесточение A1): planHash не передан (null/"") → блок — печать без binding запрещена', () => {
const noHash = freezeGate({ mentorVerdict: goodVerdict, mentorWired: true, planHash: null, verifiedContextArtifact: artifactWithExtracted, hasUnresolvedExtractedImpl: verityClean });
expect(noHash.pass).toBe(false);
expect(noHash.reason).toMatch(/planHash|binding|F-C4/i);
const emptyHash = freezeGate({ mentorVerdict: goodVerdict, mentorWired: true, planHash: '', verifiedContextArtifact: artifactWithExtracted, hasUnresolvedExtractedImpl: verityClean });
expect(emptyHash.pass).toBe(false);
});
it('fail-CLOSED: hasUnresolvedExtractedImpl бросил / не функция → блок', () => {
const boom = () => { throw new Error('сбой'); };
expect(freezeGate({ mentorVerdict: goodVerdict, mentorWired: true, planHash: 'PH1', verifiedContextArtifact: artifactWithExtracted, hasUnresolvedExtractedImpl: boom }).pass).toBe(false);
expect(freezeGate({ mentorVerdict: goodVerdict, mentorWired: true, planHash: 'PH1', verifiedContextArtifact: artifactWithExtracted }).pass).toBe(false);
});
// FR-1 (финревью 2026-06-11): SE-A3-обязательство context-verity:93-94 закрыто В ГЕЙТЕ —
// VF-1 и SE-A1 не должны проходить печать даже при 1 честной EXTRACTED.
it('FR-1/VF-1: INFERRED без derivation_ref рядом с честной EXTRACTED → блок', () => {
const art = [...artifactWithExtracted, { kind: 'INFERRED', claim: 'догадка', derivation_ref: '' }];
const r = freezeGate({ mentorVerdict: goodVerdict, mentorWired: true, planHash: 'PH1', verifiedContextArtifact: art, hasUnresolvedExtractedImpl: verityClean });
expect(r.pass).toBe(false);
expect(r.reason).toMatch(/VF-1|derivation/i);
});
it('FR-1/SE-A1: неизвестный kind рядом с честной EXTRACTED → блок (третья полоса обхода)', () => {
const art = [...artifactWithExtracted, { kind: 'VERIFIED', claim: 'самозванец' }];
const r = freezeGate({ mentorVerdict: goodVerdict, mentorWired: true, planHash: 'PH1', verifiedContextArtifact: art, hasUnresolvedExtractedImpl: verityClean });
expect(r.pass).toBe(false);
expect(r.reason).toMatch(/SE-A1|kind/i);
});
it('FR-1: валидный mix (EXTRACTED + INFERRED с derivation_ref) → pass', () => {
const art = [...artifactWithExtracted, { kind: 'INFERRED', claim: 'вывод', derivation_ref: 'из f:1' }];
const r = freezeGate({ mentorVerdict: goodVerdict, mentorWired: true, planHash: 'PH1', verifiedContextArtifact: art, hasUnresolvedExtractedImpl: verityClean });
expect(r.pass).toBe(true);
});
});