#!/usr/bin/env node /** * mentor-journal (§6.3 SE-R6-4) — журнал переговоров наставник↔Claude. tamper-evident * на хеш-цепи (зеркало М1) — переиспользует action-journal-примитивы appendEntry/verifyChain. * Payload несёт task-id (✅O17 binding), круг, сторону, реплику, обоснование (ДР-6: * каждая сторона ОБЯЗАНА обоснование). Журнал ≠ М1 action-journal: своя семантика * записи + per-task счётчик кругов (R2-SE-d). * A4 (SE10): throw на пустом обосновании ОБЯЗАН ловиться вызывающим (onPlanWrite/контроллер, * C2-W3) try/catch → НЕ крах круга; пустое обоснование → требовать заново (переспрос). */ import { appendEntry, verifyChain } from './action-journal.mjs'; /** F-C7 (sharp-edges): стороны переговоров — closed-list (ДР-6 двусторонний спор). */ export const NEGOTIATION_SIDES = Object.freeze(['mentor', 'claude']); /** Добавить запись переговоров. Обоснование обязательно (ДР-6) → throw при пустом. * F-C2: taskId обязателен — запись вне per-task учёта (R2-SE-d) запрещена. * F-C7: side только из NEGOTIATION_SIDES. */ export function appendNegotiation(entries, { taskId, round, side, utterance, justification }, { key, nowMs }) { if (!String(taskId || '').trim()) throw new Error('taskId обязателен — запись вне per-task учёта запрещена (F-C2, R2-SE-d)'); if (!NEGOTIATION_SIDES.includes(side)) throw new Error(`side обязан быть из [${NEGOTIATION_SIDES.join('|')}] (F-C7)`); if (!String(justification || '').trim()) throw new Error('обоснование обязательно (ДР-6 симметрия)'); const payload = { task_id: String(taskId || ''), round: Number(round) || 0, side: String(side || ''), utterance: String(utterance || ''), justification: String(justification), }; return appendEntry(Array.isArray(entries) ? entries : [], payload, { key, nowMs }); } /** Проверить цепь переговоров (зеркало verifyChain М1). */ export function verifyNegotiation(entries, headSig, { key }) { return verifyChain(entries, headSig, { key }); } /** Максимальный номер круга для task-id (per-ЗАДАЧЕ счётчик, R2-SE-d). */ export function roundCount(entries, taskId) { let max = 0; for (const e of (Array.isArray(entries) ? entries : [])) { const p = e && e.payload; if (p && p.task_id === taskId && Number(p.round) > max) max = Number(p.round); } return max; }