Files
brain/tools/mentor-journal.mjs
T

44 lines
2.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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;
}