Files
brain/tools/floor-manifest-check.test.mjs
T

137 lines
8.5 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/floor-manifest-check.test.mjs
// 6.5 (Δ8) — манифест-самопроверка регистрации. SessionStart-хук читает settings.json и
// проверяет, что пол-хук (enforce-floor) + защитные хуки реально зарегистрированы. Не
// зарегистрированы → ГРОМКИЙ WARN (cry), НЕ блок — «проблема черепах» (хук не гарантирует
// регистрацию других хуков; «пол стоит» — сигнал, не гарантия, созвучно judgeHealth в
// judge-orchestrator). Чистое ядро (settings инъектируется) + тонкая I/O-обёртка.
import { describe, it, expect } from 'vitest';
import { collectHookCommands, checkManifest } from './floor-manifest-check.mjs';
const SETTINGS = {
hooks: {
PreToolUse: [
{ matcher: 'Bash', hooks: [{ type: 'command', command: 'node tools/enforce-floor.mjs' }] },
{ matcher: '*', hooks: [{ type: 'command', command: 'node tools/enforce-runtime-write-deny.mjs' }] },
],
Stop: [{ hooks: [{ type: 'command', command: 'node tools/enforce-coverage-verify.mjs' }] }],
SessionStart: [{ hooks: [{ type: 'command', command: 'node tools/floor-manifest-check.mjs' }] }],
},
};
describe('collectHookCommands (6.5, Δ8): собрать все command-строки хуков', () => {
it('обходит все события и вложенные hooks[]', () => {
const cmds = collectHookCommands(SETTINGS);
expect(cmds).toContain('node tools/enforce-floor.mjs');
expect(cmds).toContain('node tools/enforce-runtime-write-deny.mjs');
expect(cmds).toContain('node tools/enforce-coverage-verify.mjs');
});
it('пустые/битые settings → пустой список (не бросает)', () => {
expect(collectHookCommands(null)).toEqual([]);
expect(collectHookCommands({})).toEqual([]);
expect(collectHookCommands({ hooks: { PreToolUse: 'nonsense' } })).toEqual([]);
});
});
describe('checkManifest (6.5, Δ8): WARN при отсутствии регистрации пол-хука, НЕ блок', () => {
it('все требуемые хуки зарегистрированы → ok, не кричит', () => {
const r = checkManifest({ settings: SETTINGS, requiredHooks: ['enforce-floor.mjs', 'enforce-runtime-write-deny.mjs'] });
expect(r.ok).toBe(true);
expect(r.missing).toEqual([]);
expect(r.cry).toBe(false);
expect(r.mode).toBe('protected');
});
it('пол-хук не зарегистрирован → cry:true, mode floor-unverified (но не блок)', () => {
const r = checkManifest({ settings: { hooks: {} }, requiredHooks: ['enforce-floor.mjs'] });
expect(r.ok).toBe(false);
expect(r.missing).toEqual(['enforce-floor.mjs']);
expect(r.cry).toBe(true);
expect(r.mode).toBe('floor-unverified');
});
it('частичная регистрация → перечисляет именно отсутствующие', () => {
const r = checkManifest({ settings: SETTINGS, requiredHooks: ['enforce-floor.mjs', 'enforce-supreme-gate-floor.mjs'] });
expect(r.missing).toEqual(['enforce-supreme-gate-floor.mjs']);
expect(r.cry).toBe(true);
});
it('Δ8: результат — сигнал (cry), решение НЕ блокирующее (поля block нет)', () => {
const r = checkManifest({ settings: { hooks: {} }, requiredHooks: ['enforce-floor.mjs'] });
expect(r.block).toBeUndefined();
});
});
// F-3 (аудит 2026-06-07): DEFAULT_REQUIRED_HOOKS манифеста floor-manifest-check проверял только
// enforce-floor — owner мог зарегистрировать пол, забыть стену/exfil-стражей и получить зелёный
// «protected». Расширяем DEFAULT до security-load-bearing набора (пол + стена + normative/read/mcp
// стражи), чтобы «пол подтверждён» означал весь защитный контур, а не один хук. WARN-only.
describe('floor-manifest-check — F-3: DEFAULT защитный набор шире одного enforce-floor', () => {
it('settings с одним enforce-floor (без стены) → cry (DEFAULT теперь требует защитный набор)', () => {
const onlyFloor = { hooks: { PreToolUse: [{ matcher: '*', hooks: [{ type: 'command', command: 'node tools/enforce-floor.mjs' }] }] } };
const r = checkManifest({ settings: onlyFloor }); // без requiredHooks → DEFAULT
expect(r.cry).toBe(true);
expect(r.missing).toContain('enforce-supreme-gate.mjs');
});
it('settings с полным защитным набором → ok (не кричит)', () => {
const full = { hooks: { PreToolUse: [{ matcher: '*', hooks: [
{ type: 'command', command: 'node tools/enforce-floor.mjs' },
{ type: 'command', command: 'node tools/enforce-supreme-gate.mjs' },
{ type: 'command', command: 'node tools/enforce-normative-content-rules.mjs' },
{ type: 'command', command: 'node tools/enforce-read-path-deny.mjs' },
{ type: 'command', command: 'node tools/enforce-mcp-classification.mjs' },
{ type: 'command', command: 'node tools/enforce-judge-gate.mjs' },
{ type: 'command', command: 'node tools/enforce-snapshot.mjs' },
{ type: 'command', command: 'node tools/enforce-floor-escape-consume.mjs' },
{ type: 'command', command: 'node tools/enforce-skill-journaler.mjs' },
{ type: 'command', command: 'node tools/enforce-verify-gate.mjs' },
{ type: 'command', command: 'node tools/enforce-criterion-gate.mjs' },
{ type: 'command', command: 'node tools/enforce-coverage-verify.mjs' },
{ type: 'command', command: 'node tools/enforce-todowrite-skill-verifier.mjs' },
] }] } };
const r = checkManifest({ settings: full }); // без requiredHooks → DEFAULT
expect(r.ok).toBe(true);
});
});
import { _internals as manifestInternals } from './floor-manifest-check.mjs';
describe('SE-B (М7 Фаза 6): DEFAULT включает М4 (judge-gate) + М6 (snapshot/escape) + М1 журналер', () => {
for (const h of ['enforce-judge-gate.mjs', 'enforce-snapshot.mjs', 'enforce-floor-escape-consume.mjs', 'enforce-skill-journaler.mjs']) {
it(`DEFAULT_REQUIRED_HOOKS содержит ${h}`, () => {
expect(manifestInternals.DEFAULT_REQUIRED_HOOKS).toContain(h);
});
}
it('settings с прежним набором-5 (без М4/М6) → cry: ПОСТ не подтверждён', () => {
const five = { hooks: { PreToolUse: [{ matcher: '*', hooks: [
{ type: 'command', command: 'node tools/enforce-floor.mjs' },
{ type: 'command', command: 'node tools/enforce-supreme-gate.mjs' },
{ type: 'command', command: 'node tools/enforce-normative-content-rules.mjs' },
{ type: 'command', command: 'node tools/enforce-read-path-deny.mjs' },
{ type: 'command', command: 'node tools/enforce-mcp-classification.mjs' },
] }] } };
const r = checkManifest({ settings: five });
expect(r.cry).toBe(true);
expect(r.missing).toContain('enforce-judge-gate.mjs');
});
});
describe('G1: DEFAULT_REQUIRED_HOOKS включает enforce-verify-gate (М5-family verify)', () => {
it('DEFAULT_REQUIRED_HOOKS содержит enforce-verify-gate.mjs', () => {
expect(manifestInternals.DEFAULT_REQUIRED_HOOKS).toContain('enforce-verify-gate.mjs');
});
});
describe('Level B: DEFAULT_REQUIRED_HOOKS включает enforce-criterion-gate (М5-family per-criterion)', () => {
it('DEFAULT_REQUIRED_HOOKS содержит enforce-criterion-gate.mjs', () => {
expect(manifestInternals.DEFAULT_REQUIRED_HOOKS).toContain('enforce-criterion-gate.mjs');
});
});
// G2 (М7 Фаза 8): журнал-K2 fail-CLOSE версии coverage/todowrite (переписаны в Фазе 4a, §4.2)
// внесены в манифест — без регистрации их §10-увольнение из зоопарка потеряло бы coverage/
// todowrite-дисциплину (аудит §10 «🟡 GO при G2»). ПОСТ ПОЛНЫЙ = весь контур + поглощённая дисциплина.
describe('G2 (М7 Фаза 8): DEFAULT_REQUIRED_HOOKS включает журнал-K2 coverage + todowrite', () => {
for (const h of ['enforce-coverage-verify.mjs', 'enforce-todowrite-skill-verifier.mjs']) {
it(`DEFAULT_REQUIRED_HOOKS содержит ${h}`, () => {
expect(manifestInternals.DEFAULT_REQUIRED_HOOKS).toContain(h);
});
}
});