#!/usr/bin/env node /** * loop-termination (§4 SE-R7-6 спеки R6.3) — внешняя петля наставника закрывается ТОЛЬКО по * явному «цель достигнута» владельца ЛИБО GO судьи gate-3 goal_achieved [judge-engine.mjs:26]. * Контроллер сам петлю НЕ закрывает. Чистый детерминированный хелпер; wiring в петлю — sub-plan C. * Строгий ===true (fail-safe): любой иной truthy НЕ закрывает (не дать «случайно» завершить). */ export function loopTerminationDecision({ ownerDeclaredDone = false, judgeGate3Go = false } = {}) { if (ownerDeclaredDone === true) return { terminate: true, reason: 'владелец объявил цель достигнутой' }; if (judgeGate3Go === true) return { terminate: true, reason: 'судья gate-3 goal_achieved → GO на завершение' }; return { terminate: false, reason: 'продолжать — контроллер сам не закрывает петлю (SE-R7-6)' }; } /** * E-S1 §g2: продукт судьи gate-3 (цель достигнута?) — детерминированная сводка исполненного: * цель + шаги + их по-критерию GREEN. Форма совместима с buildJudgePrompt (judge-engine). */ export function buildGate3Product({ goal = '', planSteps = [], greenRuns = [] } = {}) { const greenById = new Map(); for (const g of Array.isArray(greenRuns) ? greenRuns : []) { if (g && g.stepId != null) greenById.set(String(g.stepId), g.criterion ?? true); } const lines = (Array.isArray(planSteps) ? planSteps : []).map((s, i) => { const id = s && s.id != null ? String(s.id) : String(i); const green = greenById.has(id) ? greenById.get(id) : null; return { text: `${s && s.op} ${s && s.object}`, green }; }); const hasEvidence = lines.some((l) => l.green != null); const product = hasEvidence ? lines.map((l) => `${l.text}${l.green != null ? ` [green: ${l.green}]` : ''}`).join('\n') : 'нет доказательств исполнения (шаги не подтверждены по-критерию)'; return { product, goal: String(goal || ''), cards: [] }; } /** * E-S1 §g1/§g4: оркестратор замыкания внешней петли. Закрытие ТОЛЬКО через loopTerminationDecision * (SE-R7-6). Degraded судья (wired:false) НИКОГДА не закрывает/не арбитрит — fail-safe против * ложного исхода на сбое транспорта. Входы аутентифицирует потребитель (§t1: ownerArbitration — * только из подписанного канала владельца; gate3Verdict — только выход движка); функция чистая. */ export function decideGate3Closure({ gate3Verdict = null, noGoCount = 0, ownerArbitration = null, maxRounds = 3 } = {}) { const degraded = !!gate3Verdict && gate3Verdict.wired === false; if (degraded && ownerArbitration !== 'accept') { return { state: 'negotiate', terminate: false, reason: 'судья gate-3 недоступен — не закрывать, повтор/доработка' }; } if (ownerArbitration === 'accept') { const t = loopTerminationDecision({ ownerDeclaredDone: true }); return { state: 'closed', terminate: t.terminate, reason: t.reason }; } if (gate3Verdict && gate3Verdict.decision === 'GO' && gate3Verdict.wired !== false) { const t = loopTerminationDecision({ judgeGate3Go: true }); return { state: 'closed', terminate: t.terminate, reason: t.reason }; } if (ownerArbitration === 'continue') { return { state: 'open', terminate: false, reason: 'владелец выбрал продолжать' }; } const rc = (Number.isInteger(noGoCount) && noGoCount >= 0) ? noGoCount : maxRounds; if (rc < maxRounds) { return { state: 'negotiate', terminate: false, reason: `gate-3 NO-GO; круг ${rc}/${maxRounds} — переговоры` }; } return { state: 'arbitrate', terminate: false, reason: `gate-3 NO-GO ${maxRounds} круга — арбитраж владельцу`, card: true }; }