a0a73d8d3a
Δ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>
48 lines
2.8 KiB
JavaScript
48 lines
2.8 KiB
JavaScript
// 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');
|
||
});
|
||
});
|