397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
66 lines
2.9 KiB
JavaScript
66 lines
2.9 KiB
JavaScript
// tools/m7-phase4-invariants.test.mjs
|
|
/**
|
|
* §12 CI-инварианты Фазы 4 (М7): анти-регресс поглощённой дисциплины. Lock-in: красит,
|
|
* если кто-то вернёт fail-open / override-вокабуляр / text-bypass в поглощённые хуки.
|
|
* Scope — ТОЛЬКО поглощённая дисциплина 4a/4b (coverage/todowrite/rationalization). НЕ
|
|
* ещё-не-поглощённые (memory-coverage/branch-switch/verify-before-push) — те retire в Ф8.
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import { readFileSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, join } from 'node:path';
|
|
import { FAIL_CLOSE_DISCIPLINE_HOOKS } from './enforce-hook-helpers.mjs';
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
|
|
// Поглощённая дисциплина Фазы 4a/4b с собственным main (Stop/Pre-хуки).
|
|
const ABSORBED_PHASE4 = [
|
|
'enforce-coverage-verify',
|
|
'enforce-todowrite-skill-verifier',
|
|
'enforce-rationalization-audit',
|
|
];
|
|
|
|
function src(name) {
|
|
return readFileSync(join(here, `${name}.mjs`), 'utf8');
|
|
}
|
|
|
|
// FAIL_OPEN: catch-блок, молча возвращающий block:false (анти-SE2).
|
|
const FAIL_OPEN_RE = /catch\s*(?:\([^)]*\))?\s*\{[^}]*block:\s*false/s;
|
|
// TEXT-BYPASS: контроллер-текст → пропуск (assistantText.includes(...) ветка allow).
|
|
const TEXT_BYPASS_RE = /assistantText\.includes\(/;
|
|
|
|
describe('§12 Фаза 4 — fail-CLOSE (анти-SE2): поглощённая дисциплина не fail-open', () => {
|
|
for (const name of ABSORBED_PHASE4) {
|
|
it(`${name} использует exitDisciplineDecision`, () => {
|
|
expect(src(name).includes('exitDisciplineDecision')).toBe(true);
|
|
});
|
|
it(`${name} НЕ содержит fail-open catch → block:false`, () => {
|
|
expect(FAIL_OPEN_RE.test(src(name))).toBe(false);
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('§12 Фаза 4 — escape≠override: дисциплина не несёт override-вокабуляра', () => {
|
|
for (const name of ABSORBED_PHASE4) {
|
|
it(`${name} не вызывает findOverride (только escape М6 — дверь)`, () => {
|
|
expect(src(name).includes('findOverride(')).toBe(false);
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('§12 Фаза 4 — нет controller-text → allow (text-bypass вырезан, Класс 1)', () => {
|
|
for (const name of ABSORBED_PHASE4) {
|
|
it(`${name} не пропускает по assistantText.includes(marker)`, () => {
|
|
expect(TEXT_BYPASS_RE.test(src(name))).toBe(false);
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('§12 Фаза 4 — манифест: поглощённые хуки числятся в FAIL_CLOSE_DISCIPLINE_HOOKS', () => {
|
|
for (const name of [...ABSORBED_PHASE4, 'enforce-self-debrief-detector']) {
|
|
it(`${name} в манифесте fail-CLOSE (Ф6 self-check)`, () => {
|
|
expect(FAIL_CLOSE_DISCIPLINE_HOOKS).toContain(name);
|
|
});
|
|
}
|
|
});
|