Files
brain/tools/loop-termination.mjs
T
2026-06-17 06:55:43 +03:00

63 lines
4.2 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.
#!/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 };
}