397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
137 lines
8.5 KiB
JavaScript
137 lines
8.5 KiB
JavaScript
// 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);
|
||
});
|
||
}
|
||
});
|