// 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); }); });