69e20099db
Второй аудит машин 1-4 другим объективом (sharp-edges: устойчивость к неправильному применению / мягкие умолчания / совпадение по пустоте-подстроке). Криптоядра здоровы (подтверждено). 8 реальных дыр закрыты по TDD: M3: - coverage-machine F-1: покрытие считалось по двусторонней ПОДСТРОКЕ — produces "a" покрывал запрос "audit-rls-policy" (ложное «всё покрыто»). Новый tokensCover: точное равенство ИЛИ подмножество слов по границам. coveringSkill + coverageRegistry. - router-engine F-8: confidence не проверялся на диапазон — 5/Infinity проходили как «уверен» (обход воздержания 5.2), -3 как принуд. abstain. validateTrace: [0,1] finite. - round-control C: пустой roundKey="" активировал managed-режим (!= null) → все сессии делили один счётчик-бакет. Теперь managed требует непустую строку. - router-learning-queue G: повторное approve уже-решённого id повторно клало запись в фонд (дубль). applyApprovalBatch: переводит только status==='pending'. M2: - plan-lock F5: шаг с пустым object был джокером (object:'' матчил действие, чей путь не извлёкся → object''). actionMatchesStep: пустой object шага не матчит ничего. M4 (инертна; чистые fail-closed правки кода, корректны и при включении): - judge-slop-counter H: битый/null вердикт в списке ронял счёт (v.missing на null). Теперь не крашит, считается халтурой (безопасная сторона). - judge-engine J: consensusDecision на пустом/битом списке дрейфовал к GO. Теперь GO только если есть голоса И каждый чистый GO; иначе NO-GO (fail-closed для hard-risk). - judge-orchestrator K: finalGate снимал вето пола на любой falsy floorBlocked (undefined от упавшей проверки = fail-open). Теперь снять может только явный false. Регрессия tools-only 2555 passed + 2 skip (+15 TDD-тестов, 0 регрессий). Осознанно НЕ менялось (без призраков): - M1 receipt-sign domain default '' / разделитель пробел — backward-compat контракт (тест 18-19), инъективен на enum-доменах без пробелов. - M1 action-journal атомарность записи головы + битая .jsonl строка — fail-closed (битьё → verifyChain ok:false → стена блокирует); чистого behavioral-теста нет. - M3 round-control requiredSkills=[] — контракт вызывающего (пустой = не требуется). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
27 lines
1.7 KiB
JavaScript
27 lines
1.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* judge-slop-counter (§9.5) — частота халтуры судьи: голые ГО / тонкие вердикты /
|
|
* пропуски линзы. Чистая функция над списком вердиктов (каждый уже несёт missing[]/
|
|
* trivial[] из validateVerdictSlots). Живой источник (лог J8) вшивается в 4-E/4-F;
|
|
* здесь — детерминированный счёт + флаг по порогу. Ловит систематическую штамповку
|
|
* (не качество одного вердикта — это оценщик постфактум 4-F/D31).
|
|
*/
|
|
const DEFAULT_THRESHOLD = 0.3;
|
|
|
|
export function computeSlopStats(verdicts = [], { threshold = DEFAULT_THRESHOLD } = {}) {
|
|
const total = verdicts.length;
|
|
let lensSkips = 0, thinVerdicts = 0, dirty = 0, clean = 0;
|
|
for (const v of verdicts) {
|
|
// H (аудит M1-M4): битый/отсутствующий вердикт не роняет счёт и считается халтурой
|
|
// (безопасная сторона: больше флага, не меньше). Раньше v.missing на null → краш.
|
|
if (!v || typeof v !== 'object') { dirty++; continue; }
|
|
const hasMissing = Array.isArray(v.missing) && v.missing.length > 0;
|
|
const hasTrivial = Array.isArray(v.trivial) && v.trivial.length > 0;
|
|
if (hasMissing) lensSkips++;
|
|
if (hasTrivial) thinVerdicts++;
|
|
if (hasMissing || hasTrivial) dirty++; else clean++;
|
|
}
|
|
const slopRate = total ? dirty / total : 0;
|
|
return { total, lensSkips, thinVerdicts, clean, slopRate, flag: total > 0 && slopRate > threshold };
|
|
}
|