Files
portal/tools/plan-lock-judge-mode.test.mjs
T
Дмитрий 4dd2098e7b feat(seal): validate judge_mode at freeze time (defense-in-depth, SE-2 §4)
assertValidJudgeMode guard in freezePlan/freezeArtifact: fail-CLOSE on any
mode != {null|shadow|live-block}. Closes YAGNI-candidate from sealed-plan §11
/ gate1+se2 §4 (SE-a) — bogus mode can no longer enter a seal at the source
(complements the wall's fail-closed live-block whitelist).

- freezePlan validates the judgeMode param; freezeArtifact validates the
  embedded artifact.judge_mode (injected by seal-orchestration).
- guard sits before id/sig computation -> no partially-signed bogus-mode seal.
- throw is best-effort-swallowed at enforce-judge-gate.mjs onWiredSeal ->
  no seal produced (fail-CLOSE), hook never crashes.
- real flow never trips it (judgeGateMode yields only inert/shadow/live-block,
  inert never seals) — drift-only guard.

TDD: new tools/plan-lock-judge-mode.test.mjs (6 tests). Regression tools-only
3449 passed / 2 skipped / 0 failed (was 3443+2skip). 0 regressions.
sharp-edges + variant-analysis: clean (only two seal producers, both guarded;
wall comparison already fail-closed).

Plan: docs/superpowers/plans/2026-06-09-seal-time-judge-mode-validation.md
2026-06-10 03:48:07 +03:00

51 lines
2.9 KiB
JavaScript

// tools/plan-lock-judge-mode.test.mjs
// Seal-time judge_mode validation (defense-in-depth).
// Источник: spec 2026-06-09-sealed-plan-production-design.md §11 + 2026-06-09-gate1-and-se2-design.md §4 (SE-a).
import { describe, it, expect } from 'vitest';
import { freezePlan, freezeArtifact, assertValidJudgeMode } from './plan-lock.mjs';
const KEY = 'plan-lock-test-key';
const STEPS = [{ n: 1, op: 'Write', object: 'tools/foo.mjs', intent: 'x' }];
const ART = { sections: { '§1': 'a' }, goal: 'g' };
describe('assertValidJudgeMode (seal-time guard)', () => {
it('accepts null / undefined / shadow / live-block', () => {
expect(() => assertValidJudgeMode(null)).not.toThrow();
expect(() => assertValidJudgeMode(undefined)).not.toThrow();
expect(() => assertValidJudgeMode('shadow')).not.toThrow();
expect(() => assertValidJudgeMode('live-block')).not.toThrow();
});
it('throws (fail-CLOSE) on bogus values: typo / wrong case / wrong type / inert / empty', () => {
expect(() => assertValidJudgeMode('Shadow')).toThrow();
expect(() => assertValidJudgeMode('live_block')).toThrow();
expect(() => assertValidJudgeMode('inert')).toThrow();
expect(() => assertValidJudgeMode(42)).toThrow();
expect(() => assertValidJudgeMode({})).toThrow();
expect(() => assertValidJudgeMode('')).toThrow();
});
});
describe('freezePlan — judge_mode validated at seal', () => {
it('seals with valid judgeMode (shadow / live-block / null default)', () => {
expect(freezePlan({ steps: STEPS, judgeMode: 'live-block', key: KEY, nowMs: 1 }).judge_mode).toBe('live-block');
expect(freezePlan({ steps: STEPS, judgeMode: 'shadow', key: KEY, nowMs: 1 }).judge_mode).toBe('shadow');
expect(freezePlan({ steps: STEPS, key: KEY, nowMs: 1 }).judge_mode).toBe(null); // default — legacy ok
});
it('throws (fail-CLOSE) on bogus judgeMode — no seal produced', () => {
expect(() => freezePlan({ steps: STEPS, judgeMode: 'Shadow', key: KEY, nowMs: 1 })).toThrow();
expect(() => freezePlan({ steps: STEPS, judgeMode: 'live_block', key: KEY, nowMs: 1 })).toThrow();
});
});
describe('freezeArtifact — embedded judge_mode validated at seal', () => {
it('seals with valid embedded judge_mode; legacy artifact without the field still seals', () => {
expect(freezeArtifact({ artifact: { ...ART, judge_mode: 'live-block' }, key: KEY, nowMs: 1 }).judge_mode).toBe('live-block');
expect(freezeArtifact({ artifact: { ...ART, judge_mode: 'shadow' }, key: KEY, nowMs: 1 }).judge_mode).toBe('shadow');
expect(freezeArtifact({ artifact: ART, key: KEY, nowMs: 1 }).artifact_id).toMatch(/^[0-9a-f]{64}$/); // legacy, нет поля → ok
});
it('throws (fail-CLOSE) on bogus embedded judge_mode', () => {
expect(() => freezeArtifact({ artifact: { ...ART, judge_mode: 'Shadow' }, key: KEY, nowMs: 1 })).toThrow();
expect(() => freezeArtifact({ artifact: { ...ART, judge_mode: 99 }, key: KEY, nowMs: 1 })).toThrow();
});
});