Wave-1 order 4 (P7, P12, the P9 test lock, the P3 plan-approval guard). All command hooks; none runs a model. - P12: pre-bash-executor answers permissionDecision "ask" before merge into main, push, tag/publish and destructive git (holds in auto mode). post-approval-log writes exactly what the person was asked to the K2 approval log: merge/push to main as `merge`, tag/publish as `release`, with a one-hour expiry; nothing when no person was asked. - P3: pre-plan-gate runs the K3 plan gate on a Voyage plan leaving plan mode and sends a refused plan back; "not measured" asks, never passes. - P9: lib/util/test-lock.mjs (K8). pre-write-executor refuses edits to locked test files; stop-test-lock refuses "done" for the locking session (stop_hook_active guarded). - P7: post-edit-check runs the target repo's own formatter/linter per edit, only during a Voyage plan run or after opt-in (.claude/voyage-guards.json); lib/verification/step-check.mjs (K9) fails the step on findings; no tool reads "not measured", never green. - STORM cap matcher also names `Agent` (the tool's name since 2.1.63). - Mod precondition stated in README, SECURITY.md and every PreToolUse stop guard: Voyage's guards are deterministic only when no installed mod handles `tool.check`. K2, K3 and K5 are stubbed in tests until orders 2 and 3 are merged. Two independent review rounds; every finding has a regression test. Suite 1600 (1598 pass / 0 fail / 2 skipped). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
71 lines
3.4 KiB
JavaScript
71 lines
3.4 KiB
JavaScript
// tests/lib/guards-prose-pins.test.mjs
|
|
// Prose pins owned by the guards process (wave-1 order 4). Counted as doc pins
|
|
// by lib/util/test-census.mjs (PIN_FILE_RE matches `prose-pins.test.mjs`).
|
|
//
|
|
// The mod precondition: a Claude Code mod that handles `tool.check` answers
|
|
// AFTER the rules and the PreToolUse hooks, and «its answer can replace
|
|
// theirs» — it can approve a call a plugin PreToolUse hook blocked
|
|
// (code.claude.com/docs/en/permissions § Extend permissions with hooks,
|
|
// verified verbatim 06.10). So Voyage's stop guards are deterministic only
|
|
// under a stated condition, and the condition is written where the guards are
|
|
// described: README § Security hardening, SECURITY.md, and the head of every
|
|
// PreToolUse stop guard.
|
|
|
|
import { test } from 'node:test';
|
|
import { strict as assert } from 'node:assert';
|
|
import { readFileSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
const read = (rel) => readFileSync(join(ROOT, rel), 'utf8');
|
|
|
|
export const MOD_PRECONDITION = "Voyage's guards are deterministic only when no installed mod handles `tool.check`.";
|
|
|
|
const STOP_GUARDS = [
|
|
'hooks/scripts/pre-bash-executor.mjs',
|
|
'hooks/scripts/pre-write-executor.mjs',
|
|
'hooks/scripts/pre-agent-cap.mjs',
|
|
'hooks/scripts/pre-plan-gate.mjs',
|
|
];
|
|
|
|
test('mod precondition: README § Security hardening states it', () => {
|
|
const md = read('README.md');
|
|
const start = md.indexOf('### Security hardening');
|
|
assert.ok(start !== -1);
|
|
const end = md.indexOf('\n### ', start + 1);
|
|
assert.ok(md.slice(start, end).includes(MOD_PRECONDITION), 'README § Security hardening must state the mod precondition');
|
|
});
|
|
|
|
test('mod precondition: SECURITY.md states it', () => {
|
|
assert.ok(read('SECURITY.md').includes(MOD_PRECONDITION));
|
|
});
|
|
|
|
test('mod precondition: the head of every PreToolUse stop guard states it', () => {
|
|
for (const rel of STOP_GUARDS) {
|
|
const head = read(rel).split('\n').slice(0, 40).join('\n').replace(/^\/\/ ?/gm, '').replace(/\n/g, ' ');
|
|
assert.ok(head.replace(/\s+/g, ' ').includes(MOD_PRECONDITION), `${rel}: the first 40 lines must state the mod precondition`);
|
|
}
|
|
});
|
|
|
|
test('mod precondition: the guards cannot see a mod; the docs say so where it is stated', () => {
|
|
// Looked up 06.10: no hook input field names installed mods; the documented
|
|
// ways to see them are `/plugin` and a CLI subcommand, both for a person.
|
|
assert.match(read('SECURITY.md'), /cannot tell whether such a mod is installed/);
|
|
});
|
|
|
|
test('P12 guard: README names the classes it asks about and that it holds in auto mode', () => {
|
|
const md = read('README.md');
|
|
const sec = md.slice(md.indexOf('### Security hardening'), md.indexOf('#### Recommended: disable Skill shell execution'));
|
|
for (const w of ['merge into `main`', 'push', 'tag', 'destructive git', 'auto mode']) {
|
|
assert.ok(sec.includes(w), `README § Security hardening must mention ${w}`);
|
|
}
|
|
});
|
|
|
|
test('borrowings are credited where the guards are described (PLAN fixed limit 3)', () => {
|
|
const md = read('README.md');
|
|
const sec = md.slice(md.indexOf('### Security hardening'), md.indexOf('#### Recommended: disable Skill shell execution'));
|
|
for (const src of ['Superpowers', 'agent-skills', 'ai-native-sdlc', 'Pocock', 'Kiro', 'Cherny', 'Claude Code']) {
|
|
assert.ok(sec.includes(src), `credit missing for ${src}`);
|
|
}
|
|
});
|