b739d5adad
Болезни B (роутер в пустоту) + A (наставник не заворачивал) — лечение Р7/Р8 (Подход 1): наставник — единый мозг-рецензент, зовёт classify() как функцию (3 слоя + граф nodes.yaml + карточки — код не тронут, новый вызыватель), судит спеку+план+выбор скилов, заворачивает NO-GO. - validateMentorVerdict + промпты (план/спека): явное decision GO|NO-GO (поглощённый Р7) - plan-skills.mjs: parsePlanSkills (skills-json) + extractPlanGoal (зеркало extractGoal судьи) - mentor-seam: renderSkillContext; onPlanWrite зовёт classifyImpl (fail-safe: сбой → без скил-сверки) - decideMentorObjection: заворот на decision=NO-GO ИЛИ сломанный вердикт; mentor-GO только на чистом GO - formatMentorObjection доносит суть (recommendation + reasoning + plan_points), GO -> пусто - enforce-mentor main: loadRegistry + classify; счётчик L1 decision-aware (Р7/§3.4) - скил-сверка — только план (gate2); спека (gate1) — по сути + decision - включает redesign согласования L1->L2 (Фазы 0-6, способ B: наставник->судья->печать) - регрессия tools-only 3901 passed + 2 skip (база 3877, +24 теста, 0 регрессий) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
58 lines
2.8 KiB
JavaScript
58 lines
2.8 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* receipt-sign — подпись/проверка расписок и хеш-первооснова Журнала.
|
|
* canonicalJson: стабильная сериализация (сорт ключей, без пробелов) — иначе
|
|
* HMAC/хеш зависят от порядка ключей. signPayload/verifyReceipt — HMAC-SHA256.
|
|
*/
|
|
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
|
|
/**
|
|
* R-31 — домены подписи: каждый тип записи подписывается со своей меткой, чтобы
|
|
* подпись одного типа (напр. голова журнала) НЕ принималась за подпись другого
|
|
* (напр. замороженный план). Метка примешивается в HMAC до содержания.
|
|
*/
|
|
export const RECEIPT_DOMAINS = Object.freeze({
|
|
JOURNAL_HEAD: 'journal-head',
|
|
FROZEN_PLAN: 'frozen-plan',
|
|
FROZEN_ARTIFACT: 'frozen-artifact',
|
|
APPROVAL: 'approval',
|
|
STEP_PTR: 'step-ptr',
|
|
M5_GREEN: 'm5-green',
|
|
VERIFY_PASS: 'verify-pass',
|
|
FLOOR_ESCAPE: 'floor-escape',
|
|
JUDGE_GO: 'judge-go',
|
|
MENTOR_GO: 'mentor-go',
|
|
});
|
|
|
|
/** Стабильная сериализация: ключи объектов сортируются рекурсивно, без пробелов. */
|
|
export function canonicalJson(value) {
|
|
if (value === null || typeof value !== 'object') return JSON.stringify(value);
|
|
if (Array.isArray(value)) return '[' + value.map(canonicalJson).join(',') + ']';
|
|
const keys = Object.keys(value).sort();
|
|
return '{' + keys.map((k) => JSON.stringify(k) + ':' + canonicalJson(value[k])).join(',') + '}';
|
|
}
|
|
|
|
/** HMAC-SHA256(hex) над `domain\0 + canonicalJson(payload)`. Без ключа → null.
|
|
* domain='' по умолчанию — обратная совместимость. */
|
|
export function signPayload(payload, key, domain = '') {
|
|
if (!key) return null;
|
|
return createHmac('sha256', String(key)).update(String(domain) + ' |