feat(shadow-replay): M2 adapter signed plan

This commit is contained in:
Дмитрий
2026-06-09 06:22:40 +03:00
parent d5feca2672
commit a7f3ebd971
2 changed files with 32 additions and 1 deletions
+15
View File
@@ -10,6 +10,8 @@
*/
import { floorDecide } from './floor-decide.mjs';
import { snapshotNeeded } from './snapshot-decide.mjs';
import { decide as supremeDecide } from './enforce-supreme-gate.mjs';
import { freezePlan } from './plan-lock.mjs';
// ── Фикстуры (данные) ──────────────────────────────────────────────────────
@@ -76,3 +78,16 @@ export function decideM6(toolUse) {
const need = snapshotNeeded(toolUse && toolUse.name, (toolUse && toolUse.input) || {});
return { blocked: !!need, reason: need ? 'снимок нужен (разрушительный Bash)' : 'снимок не нужен' };
}
export const REPLAY_KEY = 'shadow-replay-test-key';
/** Заморозить синтетический план тест-ключом (artifactId=null → без проверки артефакта). */
export function freezeReplayPlan(steps) {
return freezePlan({ steps, artifactId: null, key: REPLAY_KEY, nowMs: 0 });
}
/** М2: план-матч ядро через decide(). frozenArtifact=null (план без artifact_id). */
export function decideM2(toolUse, frozenPlan, stepPtr) {
const r = supremeDecide({ toolUse, frozenPlan, frozenArtifact: null, stepPtr, key: REPLAY_KEY });
return { blocked: r.decision === 'block', reason: r.reason };
}
+17 -1
View File
@@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest';
import {
BENIGN_FIXTURE, RISKY_FIXTURE, M2_PLAN_STEPS, M2_EVENTS, M4_FIXTURE,
classifyOutcome, decideM5, decideM6,
classifyOutcome, decideM5, decideM6, decideM2, freezeReplayPlan,
} from './shadow-replay.mjs';
describe('shadow-replay fixtures', () => {
@@ -70,3 +70,19 @@ describe('М6 снимок адаптер', () => {
expect(decideM6({ name: 'Bash', input: { command: 'git log' } }).blocked).toBe(false);
});
});
describe('М2 стена адаптер', () => {
const plan = freezeReplayPlan(M2_PLAN_STEPS);
it('in-plan правка (шаг 1) → allow', () => {
expect(decideM2({ name: 'Edit', input: { file_path: 'app/Foo.php' } }, plan, 0).blocked).toBe(false);
});
it('observe (Read) → allow', () => {
expect(decideM2({ name: 'Read', input: { file_path: 'app/Foo.php' } }, plan, 0).blocked).toBe(false);
});
it('out-of-plan мутация → block', () => {
expect(decideM2({ name: 'Edit', input: { file_path: 'app/Other.php' } }, plan, 0).blocked).toBe(true);
});
it('seed skill → allow', () => {
expect(decideM2({ name: 'Skill', input: { skill: 'superpowers:writing-plans' } }, plan, 0).blocked).toBe(false);
});
});