Files
brain/tools/loop-termination.mjs

105 lines
7.3 KiB
JavaScript
Raw Permalink 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 Фаза 2 §u2: пользовательская карточка приёмки — чистый сборщик полей простого языка
* (не код). Пустые входы → честные заглушки (не выдумки). honestyChecked!==true → карточка несёт
* видимое предупреждение «автоматическая сверка честности недоступна» (§u4 degraded-эскалация).
*/
export function buildOwnerCard({ goal = '', change = [], verifySteps = [], boundary = '', kind = 'machinery', honestyChecked = false } = {}) {
const toList = (v) => (Array.isArray(v) ? v : (v == null || String(v).trim() === '' ? [] : [v]))
.map((x) => String(x)).filter((x) => x.trim() !== '');
const k = kind === 'screen' ? 'screen' : 'machinery';
const changeArr = toList(change);
const stepsArr = toList(verifySteps);
const card = {
goal: String(goal || '').trim() || '(цель не указана)',
change: changeArr.length ? changeArr : ['(что изменилось — не указано)'],
verifySteps: stepsArr.length ? stepsArr : [k === 'screen' ? '(шаги проверки на экране не указаны)' : '(сценарий проверки не указан)'],
boundary: String(boundary || '').trim() || '(граница не указана)',
kind: k,
honestyChecked: honestyChecked === true,
};
if (card.honestyChecked !== true) {
card.warning = 'автоматическая сверка честности недоступна — проверь по шагам сам';
}
return card;
}
/**
* 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, delivery = 'internal', cardVerdict = null } = {}) {
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 };
}
const codeGo = !!gate3Verdict && gate3Verdict.decision === 'GO' && gate3Verdict.wired !== false;
if (codeGo) {
if (delivery === 'user-result') {
// §u5: код исправен, но пользовательский результат требует И сверенной карточки, И подписи владельца
const cardDegraded = !!cardVerdict && cardVerdict.wired === false;
const cardGo = !!cardVerdict && cardVerdict.decision === 'GO' && cardVerdict.wired !== false;
const cardNoGo = !!cardVerdict && cardVerdict.wired === true && cardVerdict.decision !== 'GO';
if (cardNoGo) {
return { state: 'await-card', terminate: false, reason: 'карточка приукрашена/неточна — доработать, владельца не звать' };
}
if (cardDegraded) {
return { state: 'await-owner', terminate: false, unverified: true, reason: 'судья карточки недоступен — показать владельцу непроверенную карточку с предупреждением' };
}
if (cardGo) {
return { state: 'await-owner', terminate: false, reason: 'код-GO + карточка сверена — ждём подписанного решения владельца' };
}
return { state: 'await-card', terminate: false, reason: 'код-GO; карточка ещё не сверена — собрать и подать судье карточки' };
}
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 };
}