397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
44 lines
2.8 KiB
JavaScript
44 lines
2.8 KiB
JavaScript
#!/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;
|
||
}
|