b978738be6
Судья мог судить/печатать план, который наставник завернул: mentor-GO привязан к plan_hash = planId(steps) (только шаги), пишется ТОЛЬКО на GO и НЕ стирался на NO-GO. При идентичных steps (менялся лишь текст плана) старое «да» переживало смену содержания — судья находил устаревшее одобрение (mentor-go-store::mentorGoValidFor по plan_hash) и проходил mentorApproved-гейт несмотря на свежий NO-GO наставника. Вскрыто живым прогоном (план опечатался при mentor NO-GO + judge GO). Фикс: clearMentorGo стирает запись; enforce-mentor-on-plan-write на реальном NO-GO (blocked) её зовёт (degraded не трогаем — verdict неизвестен). Инвариант: «да» наставника живёт ⟺ последний проход одобрил. Свод 4376 зелёный. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
28 lines
1.5 KiB
JavaScript
28 lines
1.5 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { buildMentorGo, persistMentorGo, loadMentorGo, clearMentorGo } from './mentor-go-store.mjs';
|
|
|
|
function memFs() {
|
|
const s = new Map();
|
|
return { s,
|
|
writeFileSync: (p, d) => s.set(String(p), d),
|
|
renameSync: (a, b) => { s.set(String(b), s.get(String(a))); s.delete(String(a)); },
|
|
readFileSync: (p) => { if (!s.has(String(p))) { const e = new Error('no'); e.code = 'ENOENT'; throw e; } return s.get(String(p)); },
|
|
unlinkSync: (p) => { if (!s.has(String(p))) { const e = new Error('no'); e.code = 'ENOENT'; throw e; } s.delete(String(p)); },
|
|
};
|
|
}
|
|
const KEY = 'mgk'; const DIR = '/rt'; const SESS = 's1';
|
|
|
|
describe('clearMentorGo — реальный NO-GO стирает прежнее «да» наставника (стейл-fix)', () => {
|
|
it('после persist + clear загрузка возвращает null', () => {
|
|
const fs = memFs();
|
|
persistMentorGo({ record: buildMentorGo({ planHash: 'H', key: KEY }), sessionId: SESS, runtimeDir: DIR, fsImpl: fs });
|
|
expect(loadMentorGo({ sessionId: SESS, runtimeDir: DIR, fsImpl: fs })).not.toBe(null);
|
|
clearMentorGo({ sessionId: SESS, runtimeDir: DIR, fsImpl: fs });
|
|
expect(loadMentorGo({ sessionId: SESS, runtimeDir: DIR, fsImpl: fs })).toBe(null);
|
|
});
|
|
it('clear на отсутствующем файле не бросает (no-op)', () => {
|
|
const fs = memFs();
|
|
expect(() => clearMentorGo({ sessionId: SESS, runtimeDir: DIR, fsImpl: fs })).not.toThrow();
|
|
});
|
|
});
|