Files
portal/tools/mentor-journal.mjs
T

44 lines
2.8 KiB
JavaScript
Raw Normal View History

#!/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;
}