// Тесты sub-plan E Task 5 — терминация внешней петли (§4 SE-R7-6 спеки R6.3). import { describe, it, expect } from 'vitest'; import { loopTerminationDecision } from './loop-termination.mjs'; describe('loopTerminationDecision (SE-R7-6)', () => { it('владелец объявил цель достигнутой → terminate', () => { expect(loopTerminationDecision({ ownerDeclaredDone: true }).terminate).toBe(true); }); it('судья gate-3 GO → terminate', () => { expect(loopTerminationDecision({ judgeGate3Go: true }).terminate).toBe(true); }); it('ни того ни другого → продолжать (контроллер сам не закрывает)', () => { const r = loopTerminationDecision({}); expect(r.terminate).toBe(false); expect(r.reason).toMatch(/контроллер сам не закрывает/); }); it('оба → terminate', () => { expect(loopTerminationDecision({ ownerDeclaredDone: true, judgeGate3Go: true }).terminate).toBe(true); }); it('truthy-не-true НЕ закрывает (строгий ===true, fail-safe)', () => { expect(loopTerminationDecision({ ownerDeclaredDone: 'yes' }).terminate).toBe(false); expect(loopTerminationDecision({ judgeGate3Go: 1 }).terminate).toBe(false); }); }); import { buildGate3Product, decideGate3Closure } from './loop-termination.mjs'; describe('gate-3 buildGate3Product (E-S1 §g2)', () => { it('цель+шаги+greens → продукт со сводкой исполнения и green-метками', () => { const r = buildGate3Product({ goal: 'добавить хелпер', planSteps: [{ id: 's1', op: 'Write', object: 'tools/x.mjs' }], greenRuns: [{ stepId: 's1', criterion: 'x-test' }], }); expect(r.goal).toBe('добавить хелпер'); expect(r.product).toMatch(/Write tools\/x\.mjs/); expect(r.product).toMatch(/green: x-test/); expect(r.cards).toEqual([]); }); it('нет greens → пометка «нет доказательств исполнения»', () => { const r = buildGate3Product({ goal: 'g', planSteps: [{ op: 'Bash', object: 'echo a' }], greenRuns: [] }); expect(r.product).toMatch(/нет доказательств исполнения/); }); it('пустой ввод → не бросает, форма {product,goal,cards}', () => { const r = buildGate3Product({}); expect(r).toEqual({ product: expect.stringMatching(/нет доказательств/), goal: '', cards: [] }); }); }); describe('gate-3 decideGate3Closure (E-S1 §g1/§g4/§t1)', () => { it('degraded судья (wired:false) без accept → negotiate, НЕ closed', () => { const r = decideGate3Closure({ gate3Verdict: { wired: false, decision: 'GO' } }); expect(r.state).toBe('negotiate'); expect(r.terminate).toBe(false); }); it('подписанный владелец accept → closed+terminate (приоритет над судьёй)', () => { const r = decideGate3Closure({ gate3Verdict: { wired: false }, ownerArbitration: 'accept' }); expect(r.state).toBe('closed'); expect(r.terminate).toBe(true); }); it('реальный GO движка → closed+terminate', () => { const r = decideGate3Closure({ gate3Verdict: { wired: true, decision: 'GO' } }); expect(r.state).toBe('closed'); expect(r.terminate).toBe(true); }); it('владелец continue → open', () => { const r = decideGate3Closure({ gate3Verdict: { wired: true, decision: 'NO-GO' }, ownerArbitration: 'continue' }); expect(r.state).toBe('open'); expect(r.terminate).toBe(false); }); it('NO-GO & круг<3 → negotiate', () => { const r = decideGate3Closure({ gate3Verdict: { wired: true, decision: 'NO-GO' }, noGoCount: 1 }); expect(r.state).toBe('negotiate'); }); it('NO-GO & круг>=3 → arbitrate + карточка', () => { const r = decideGate3Closure({ gate3Verdict: { wired: true, decision: 'NO-GO' }, noGoCount: 3 }); expect(r.state).toBe('arbitrate'); expect(r.card).toBe(true); }); it('контроллер без подписи (нет accept, NO-GO) закрыть не может', () => { const r = decideGate3Closure({ gate3Verdict: { wired: true, decision: 'NO-GO' }, noGoCount: 0 }); expect(r.terminate).toBe(false); }); });