Files
portal/tools/judge-verdict-slots.mjs
T
Дмитрий ae81a52a99 fix(router-mentor): third audit (correctness lens) — close 3 M4 fail-open/crash holes
Третий построчный аудит машин 1-4 свежим объективом (корректность логики /
реальные баги — НЕ понимание, НЕ грабли; это были два прошлых прохода).
4 читающих под-агента code-analyzer. M1/M2/M3 — багов ядра нет (подтверждено).
M4 (судья, инертен; код должен быть верен и при включении): 3 реальные дыры по TDD.

M4:
- judge-engine.mjs runJudge: (raw.objections||[]).filter((o)=>o.verdict) падал на
  objections=[null] (o.verdict на null) и на не-массиве (.filter is not a function).
  || гасит только falsy. Краш ломал вердикт; в инертной обёртке выброс уходил в
  catch→block:false = fail-open. Fix: Array.isArray(...)?...:[] + (o && o.verdict).
- judge-verdict-slots.mjs: String(raw).trim().length скрывал не-строки — слот {}
  давал '[object Object]' (длина 15) и проходил как содержательный (мусорный
  объект/массив штамповал форму вердикта). Fix: слот обязан быть строкой
  (typeof raw !== 'string' → trivial). Мягкий fail-open формы закрыт.
- judge-orchestrator.mjs runGateLadder: step.run() без try/catch пробрасывал
  исключение упавшего шага пола вместо «пол не пройден» → решение неопределённо
  (в обёртке catch→block:false = fail-open). Fix: бросок шага = passed:false
  (fail-closed → блок), последующие не запускаются. Чистый модуль теперь сам
  гарантирует безопасную сторону, не полагаясь на обёртку.

Регрессия tools-only 2560 passed + 2 skip (+5 TDD-тестов, 0 регрессий).

Осознанно НЕ менялось (без призраков):
- M1 verifyChain без 3-го арг = нарушение контракта вызова, не валидный вход.
- M2 node-в-цепочке = то же разрешение, что одиночный node (контракт, тест L53);
  readonly-git-в-цепочке блок = осознанный default-deny (fail-safe).
- M3 defer уже защищён G-фиксом (if e.status!=='pending' return e — ДО defer);
  N3 stale-комментарий (код строже докстринга).
- M4-C DESTRUCTIVE_RE иллюстративен (divergence всё равно судится; разрушительный
  bash режется полом M2/M5 до судьи); M4-D slop-counter↔logVerdict — live-wiring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:11:12 +03:00

25 lines
1.8 KiB
JavaScript

#!/usr/bin/env node
/**
* judge-verdict-slots (§9.2) — ОТДЕЛЬНЫЙ валидатор формы вердикта (не сам судья,
* как A0: подсудный не судит себя). Вердикт обязан нести слот на КАЖДУЮ требуемую
* для гейта линзу; пустой/тривиальный слот → невалидно → ГО не выдаётся. Тут проверяем
* только ФОРМУ (наличие+нетривиальность); качество мысли внутри слота — не сюда
* (часть честного потолка ~0.5%, добивают премортем/оценщик/владелец).
*/
const DEFAULT_MIN_LEN = 8; // тривиальным считаем слот короче порога (после trim)
export function validateVerdictSlots(verdict, requiredLenses = [], { minLen = DEFAULT_MIN_LEN } = {}) {
const slots = (verdict && typeof verdict === 'object' && verdict.slots) || {};
const missing = [];
const trivial = [];
for (const lens of requiredLenses) {
const raw = slots[lens];
if (raw === undefined || raw === null) { missing.push(lens); continue; }
// Аудит M1-M4 (свежий объектив): слот обязан быть СТРОКОЙ. Раньше String(raw) скрывал
// не-строки — `{}` давал '[object Object]' (длина 15) и проходил как содержательный,
// т.е. мусорный объект/массив штамповал слот. Теперь не-строка → тривиально (форма невалидна).
if (typeof raw !== 'string' || raw.trim().length < minLen) trivial.push(lens);
}
return { valid: missing.length === 0 && trivial.length === 0, missing, trivial };
}