4dd2098e7b
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
51 lines
2.9 KiB
JavaScript
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();
|
|
});
|
|
});
|