Files
brain/tools/m4a-discipline-invariants.test.mjs
T

42 lines
2.7 KiB
JavaScript

// tools/m4a-discipline-invariants.test.mjs
// Машина 4-A — сквозные инварианты дисциплины судьи (дизайн §9, §11, F1/F2).
import { describe, it, expect } from 'vitest';
import { appendJudgeSubRun, loadJudgeSubRuns, verifyJudgeJournal, requiredSubRunsPresent } from './judge-subrun-journal.mjs';
import { validateVerdictSlots } from './judge-verdict-slots.mjs';
import { partitionObjections } from './judge-anchor.mjs';
import { bumpWatermark, checkNoRollback } from './judge-watermark.mjs';
function memFs() {
const store = new Map();
return { store,
readFileSync: (p) => { if (!store.has(String(p))) { const e = new Error('ENOENT'); e.code = 'ENOENT'; throw e; } return store.get(String(p)); },
appendFileSync: (p, d) => store.set(String(p), (store.has(String(p)) ? store.get(String(p)) : '') + String(d)),
writeFileSync: (p, d) => store.set(String(p), String(d)) };
}
function memChain() { const s = new Map(); return { get: ({ account }) => s.has(account) ? s.get(account) : null, set: ({ account, value }) => s.set(account, String(value)) }; }
const JKEY = 'judge-key';
describe('Машина 4-A — инварианты пола судьи', () => {
it('F1: контроллер не подделает прилежность судьи (чужой ключ → журнал невалиден)', () => {
const fs = memFs();
appendJudgeSubRun({ payload: { lens: 'premortem' }, key: 'controller-key', sessionId: 'S', runtimeDir: '/rt', fsImpl: fs });
const loaded = loadJudgeSubRuns({ sessionId: 'S', runtimeDir: '/rt', fsImpl: fs });
expect(verifyJudgeJournal(loaded.entries, loaded.headSig, JKEY).ok).toBe(false);
});
it('дисциплина #1: требуемая линза без под-прогона → вердикт фейк', () => {
expect(requiredSubRunsPresent([{ lens: 'attacker' }], [{ lens: 'premortem' }]).ok).toBe(false);
});
it('§9.2: ГО с пустым требуемым слотом невалидно', () => {
expect(validateVerdictSlots({ slots: { a: 'подробный слот' } }, ['a', 'b']).valid).toBe(false);
});
it('§9.3: безъякорное возражение не блокирует (уходит в совет)', () => {
const r = partitionObjections([{ text: 'тревожно вообще' }]);
expect(r.blocking).toEqual([]);
});
it('K6: откат зарубки на меньшее значение → блок', () => {
const c = memChain();
bumpWatermark({ sessionId: 'S', value: 12, keychainGet: c.get, keychainSet: c.set });
expect(checkNoRollback({ sessionId: 'S', current: 5, keychainGet: c.get }).rolledBack).toBe(true);
});
});