Files
portal/tools/enforce-reconcile.test.mjs
T
Дмитрий a0a73d8d3a feat(m5): Пакет 8 — Δ3 честный двухтакт reconcile (8.1 пред-запись + 8.2 реконсилер)
Δ3: убрано обещание «атомарно на исполнении» (PreToolUse не видит факт). Достижимый максимум —
два такта:
- 8.1 (runGate): пред-запись НАМЕРЕНИЯ в журнал ДО allow. Журнал вернул false ИЛИ бросил →
  стена НЕ разрешает (block), указатель не двигается («нет записи → нет действия», явно).
  Backward-compat: push → length (truthy) = успех; только явный false/throw → block.
- 8.2 (enforce-reconcile.mjs, новый): PostToolUse-сверка. reconcileAction — исполненное
  действие без пред-записи → action-without-record (возможен обход). findOrphanIntents —
  пред-записи без исполнения → record-without-action. WARN-уровень (не блок: PreToolUse-пол
  уже отработал, PostToolUse не отменяет исполненное). Чистые функции + fail-quiet I/O main.

+2 (supreme-gate runGate) +5 (reconcile) тестов. Полная tools-only регрессия 2788 + 2 skip
(0 регрессий). Машина 5 (Пакеты 5-8) собрана полностью.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:29:48 +03:00

48 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.
// tools/enforce-reconcile.test.mjs
// 8.2 (Δ3, Машина 5 Пакет 8) — PostToolUse-реконсилер: двунаправленная сверка журнала
// НАМЕРЕНИЙ (пред-запись supreme-gate, 8.1) с фактом исполнения.
// reconcileAction — исполненное действие имеет совпадающую пред-запись? нет → action-without-record.
// findOrphanIntents — пред-записи без соответствующего исполнения → record-without-action.
// WARN-уровень (не блок): расхождение — сигнал владельцу, не остановка (PreToolUse-пол уже отработал).
import { describe, it, expect } from 'vitest';
import { reconcileAction, findOrphanIntents } from './enforce-reconcile.mjs';
describe('reconcileAction (8.2): действие без журнальной пред-записи', () => {
const journal = [
{ op: 'Write', object: 'tools/foo.mjs', step: 1 },
{ op: 'Bash', object: 'npx vitest run foo', step: 2 },
];
it('действие с совпадающей пред-записью → matched', () => {
expect(reconcileAction({ action: { op: 'Write', object: 'tools/foo.mjs' }, journalEntries: journal }).matched).toBe(true);
});
it('действие БЕЗ пред-записи → action-without-record (возможен обход стены)', () => {
const r = reconcileAction({ action: { op: 'Write', object: 'tools/evil.mjs' }, journalEntries: journal });
expect(r.matched).toBe(false);
expect(r.flag).toBe('action-without-record');
});
it('пустой журнал → action-without-record', () => {
expect(reconcileAction({ action: { op: 'Bash', object: 'x' }, journalEntries: [] }).matched).toBe(false);
});
});
describe('findOrphanIntents (8.2): запись без действия', () => {
const journal = [
{ op: 'Write', object: 'tools/foo.mjs', step: 1 },
{ op: 'Bash', object: 'npx vitest run foo', step: 2 },
];
it('все намерения исполнены → ok, без сирот', () => {
const executed = [{ op: 'Write', object: 'tools/foo.mjs' }, { op: 'Bash', object: 'npx vitest run foo' }];
const r = findOrphanIntents({ journalEntries: journal, executedActions: executed });
expect(r.ok).toBe(true);
expect(r.orphans).toEqual([]);
});
it('намерение без исполнения → record-without-action (сирота)', () => {
const executed = [{ op: 'Write', object: 'tools/foo.mjs' }];
const r = findOrphanIntents({ journalEntries: journal, executedActions: executed });
expect(r.ok).toBe(false);
expect(r.flag).toBe('record-without-action');
expect(r.orphans).toHaveLength(1);
expect(r.orphans[0].object).toBe('npx vitest run foo');
});
});