15e217fcb4
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
105 lines
7.3 KiB
JavaScript
105 lines
7.3 KiB
JavaScript
#!/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 };
|
||
}
|