Files
brain/tools/freeze-gate.test.mjs
T

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