feat(m7-phase8): sealedPlanCoversEdit live-wiring + matcher extension to discipline sources
planCoversAction (signed-plan + tree-valid + leaf-match, fail-CLOSED) wires the §6 build-loop differentiator live. main() matcher now fires for tools/enforce-*.mjs (ad-hoc → LAW/escape; under sealed plan → CARD). decide() skips doc-malice prose layers for code and allows build-loop CARD (M2/content-floor/TDD govern). Hook inert until Phase 8 registration. +10 tests; regression tools-only 3397 passed / 2 skip. Plan: docs/superpowers/plans/2026-06-08-router-mentor-machine-7-phase-5.md (Deferred Ф8).
This commit is contained in:
@@ -159,17 +159,26 @@ export async function decide({ filePath, content, skillActive, protectiveSubset
|
||||
if (escapeAction && escapeGrantOpen(escapeAction, escapeGrants, escapeConsumed, now)) {
|
||||
return { block: false, reason: 'normative: escaped owner law-edit (§6)' };
|
||||
}
|
||||
// Контент-слои (defense-in-depth) — бегут для ВСЕЙ нормативки ДО classification, чтобы
|
||||
// вредный контент ловился конкретным reason'ом. «Память = совет, НИКОГДА не разрешение».
|
||||
// Layer 1 — recovery keywords.
|
||||
const rec = hasRecoveryPattern(content);
|
||||
if (rec.flagged) return { block: true, reason: 'recovery-pattern in normative content (layer 1)' };
|
||||
// Layer 3 — suspicious feedback.
|
||||
const sus = hasSuspiciousFeedback(content);
|
||||
if (sus.flagged) return { block: true, reason: 'suspicious self-authorization feedback in normative content (layer 3)' };
|
||||
// Layer 2 — fake-rule formulation.
|
||||
const fake = hasFakeRuleClaim(content);
|
||||
if (fake.flagged) return { block: true, reason: 'fake-rule permission claim in normative content (layer 2)' };
|
||||
|
||||
// M7 Ф8 (§6): дисциплинарный исходник (tools/enforce-*.mjs и пр.) — это КОД, не проза.
|
||||
// doc-malice слои (recovery/fake-rule/suspicious) — детекторы прозы нормативных ДОКУМЕНТОВ;
|
||||
// к коду неприменимы (легитимный код упоминает gate-config.json / settings.json / rm). Для кода
|
||||
// решает только КАРТА/ЗАКОН: под печатью → CARD (M2/content-floor/TDD govern); вне → LAW (escape).
|
||||
const disciplineSource = isDisciplineSourcePath(filePath);
|
||||
|
||||
if (!disciplineSource) {
|
||||
// Контент-слои (defense-in-depth) — бегут для нормативных ДОКУМЕНТОВ ДО classification, чтобы
|
||||
// вредный контент ловился конкретным reason'ом. «Память = совет, НИКОГДА не разрешение».
|
||||
// Layer 1 — recovery keywords.
|
||||
const rec = hasRecoveryPattern(content);
|
||||
if (rec.flagged) return { block: true, reason: 'recovery-pattern in normative content (layer 1)' };
|
||||
// Layer 3 — suspicious feedback.
|
||||
const sus = hasSuspiciousFeedback(content);
|
||||
if (sus.flagged) return { block: true, reason: 'suspicious self-authorization feedback in normative content (layer 3)' };
|
||||
// Layer 2 — fake-rule formulation.
|
||||
const fake = hasFakeRuleClaim(content);
|
||||
if (fake.flagged) return { block: true, reason: 'fake-rule permission claim in normative content (layer 2)' };
|
||||
}
|
||||
|
||||
// M7 Фаза 5 (§6): classification КАРТА/ЗАКОН. ЗАКОН (Pravila/PSR/Tooling + дисциплинарный
|
||||
// исходник ВНЕ плана + контент-правка правил) требует escape владельца — скил недостаточен
|
||||
@@ -179,7 +188,15 @@ export async function decide({ filePath, content, skillActive, protectiveSubset
|
||||
return { block: true, reason: `§6: правка ЗАКОНА (${cls.reason}) требует escape владельца — скил недостаточен` };
|
||||
}
|
||||
|
||||
// КАРТА — claude-md-management как канал operational-карты.
|
||||
// M7 Ф8 (§6): CARD дисциплинарного исходника = build-loop под ЗАПЕЧАТАННЫМ планом. Авторизован
|
||||
// самим планом — стена М2 enforce-ит членство шага, content-floor рубит опасные команды, TDD/
|
||||
// criterion-gate держат качество. doc-skill (claude-md-management) и doc-судья здесь не применяются
|
||||
// (это код, а не документ-карта). До CARD дошёл только plan-covered случай (ad-hoc → LAW выше).
|
||||
if (disciplineSource) {
|
||||
return { block: false, reason: 'discipline source under sealed plan (build-loop §6)' };
|
||||
}
|
||||
|
||||
// КАРТА-ДОКУМЕНТ — claude-md-management как канал operational-карты.
|
||||
// Layer 5 — legit skill must be active.
|
||||
if (!skillActive) {
|
||||
return { block: true, reason: 'normative write without an active legit skill (claude-md-management) — direct bypass attempt' };
|
||||
@@ -208,6 +225,21 @@ import { homedir } from 'node:os';
|
||||
import { readStdin, parseEventJson, readTranscript, turnToolUses, exitDecision } from './enforce-hook-helpers.mjs';
|
||||
import { multiJudgeConsensus, JUDGE_MODELS } from './llm-judge.mjs';
|
||||
import { canonicalAction, escapeGrantOpen, loadFloorEscapes, loadConsumed } from './escape-grant.mjs';
|
||||
import { verifyFrozenPlan, validatePlanTree, treeLeaves, actionMatchesStep, loadFrozenPlan } from './plan-lock.mjs';
|
||||
|
||||
/**
|
||||
* §6 build-loop live-wiring (Ф8): покрыта ли правка дисциплинарного исходника шагом
|
||||
* ЗАПЕЧАТАННОГО плана? Фикция без печати — считается только подписанный план (verifyImpl).
|
||||
* Совпадение с ЛЮБЫМ листом дерева (порядок шагов держит стена М2 отдельно). Любой провал
|
||||
* (нет плана / битая печать / битая структура / нет совпадения) → false → консервативно ЗАКОН.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function planCoversAction({ frozenPlan, key, action, verifyImpl = verifyFrozenPlan, normalize } = {}) {
|
||||
if (!frozenPlan || !action) return false;
|
||||
if (!verifyImpl(frozenPlan, key)) return false;
|
||||
if (!validatePlanTree(frozenPlan.steps).ok) return false;
|
||||
return treeLeaves(frozenPlan.steps).some((s) => actionMatchesStep(s, action, { normalize }));
|
||||
}
|
||||
|
||||
/** True if any tool use in the turn is a legit normative-edit skill. */
|
||||
export function detectLegitSkillActive(toolUses) {
|
||||
@@ -239,7 +271,9 @@ async function main() {
|
||||
const event = parseEventJson(await readStdin());
|
||||
const toolName = event.tool_name;
|
||||
const filePath = event.tool_input && event.tool_input.file_path;
|
||||
if (!isNormativePath(filePath)) { exitDecision({ block: false }); return; }
|
||||
// M7 Ф8 (§6): matcher расширен с нормативных ДОКУМЕНТОВ на дисциплинарные исходники машин —
|
||||
// ad-hoc правка tools/enforce-*.mjs ловится как ЗАКОН (требует escape), build-loop под планом → CARD.
|
||||
if (!isNormativePath(filePath) && !isDisciplineSourcePath(filePath)) { exitDecision({ block: false }); return; }
|
||||
|
||||
const content = extractWrittenContent(toolName, event.tool_input);
|
||||
const transcript = readTranscript(event.transcript_path);
|
||||
@@ -247,10 +281,21 @@ async function main() {
|
||||
const sessionId = event.session_id;
|
||||
const escapeAction = canonicalAction(event.tool_name, event.tool_input || {}); // §6 escape binding-key
|
||||
|
||||
// M7 Ф8 (§6) live-wiring: дисциплинарный исходник под ЗАПЕЧАТАННЫМ планом → CARD (build-loop).
|
||||
// fail-CLOSED: нет ключа/плана/совпадения (или любая ошибка) → false → ЗАКОН (требует escape).
|
||||
let sealedPlanCoversEdit = false;
|
||||
try {
|
||||
const { resolveReceiptKey } = await import('./receipt-key-config.mjs');
|
||||
const key = resolveReceiptKey();
|
||||
const runtimeDir = `${homedir()}/.claude/runtime`;
|
||||
const frozenPlan = loadFrozenPlan({ sessionId, runtimeDir });
|
||||
sealedPlanCoversEdit = planCoversAction({ frozenPlan, key, action: { op: toolName, object: filePath } });
|
||||
} catch { sealedPlanCoversEdit = false; }
|
||||
|
||||
const result = await decide({
|
||||
filePath, content, skillActive,
|
||||
protectiveSubset: isProtectiveNormativePath(filePath), // 7.2: degraded-судья → fail-CLOSE для подмножества
|
||||
sealedPlanCoversEdit: false, // §6 build-loop: Ф8 live-wiring через plan-lock actionMatchesStep; пока консервативно false
|
||||
sealedPlanCoversEdit, // §6 build-loop: Ф8 live-wiring через plan-lock actionMatchesStep
|
||||
escapeAction,
|
||||
escapeGrants: loadFloorEscapes(sessionId),
|
||||
escapeConsumed: loadConsumed(sessionId),
|
||||
|
||||
@@ -282,3 +282,70 @@ describe('decide §6 — ЗАКОН требует escape, КАРТА — ски
|
||||
expect(r.reason).toMatch(/fake.?rule/i);
|
||||
});
|
||||
});
|
||||
|
||||
import { planCoversAction } from './enforce-normative-content-rules.mjs';
|
||||
import { freezePlan } from './plan-lock.mjs';
|
||||
|
||||
describe('planCoversAction — запечатанный план покрывает правку дисциплинарного исходника (Ф8 live-wiring)', () => {
|
||||
const steps = [{ n: 1, op: 'Edit', object: 'tools/enforce-floor.mjs' }];
|
||||
const action = { op: 'Edit', object: 'tools/enforce-floor.mjs' };
|
||||
|
||||
it('нет замороженного плана → false (консервативно: ad-hoc → ЗАКОН)', () => {
|
||||
expect(planCoversAction({ frozenPlan: null, key: 'k', action })).toBe(false);
|
||||
});
|
||||
it('печать невалидна → false (фикция без печати)', () => {
|
||||
expect(planCoversAction({ frozenPlan: { steps }, key: 'k', action, verifyImpl: () => false })).toBe(false);
|
||||
});
|
||||
it('структура дерева невалидна → false (fail-CLOSED, SE-4 пустой substeps)', () => {
|
||||
const bad = { steps: [{ substeps: [] }] };
|
||||
expect(planCoversAction({ frozenPlan: bad, key: 'k', action, verifyImpl: () => true })).toBe(false);
|
||||
});
|
||||
it('лист совпадает с действием при валидной печати → true (build-loop CARD)', () => {
|
||||
expect(planCoversAction({ frozenPlan: { steps }, key: 'k', action, verifyImpl: () => true })).toBe(true);
|
||||
});
|
||||
it('ни один лист не совпал → false', () => {
|
||||
const other = { op: 'Edit', object: 'tools/enforce-supreme-gate.mjs' };
|
||||
expect(planCoversAction({ frozenPlan: { steps }, key: 'k', action: other, verifyImpl: () => true })).toBe(false);
|
||||
});
|
||||
it('реальная печать freezePlan + правильный ключ → true; неверный ключ → false (end-to-end seal)', () => {
|
||||
const plan = freezePlan({ steps, key: 'secret-key', nowMs: 1000 });
|
||||
expect(planCoversAction({ frozenPlan: plan, key: 'secret-key', action })).toBe(true);
|
||||
expect(planCoversAction({ frozenPlan: plan, key: 'wrong-key', action })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('decide build-loop §6 — дисциплинарный исходник под/вне печати (live sealedPlanCoversEdit)', () => {
|
||||
it('дисциплинарный исходник ПОД печатью → block:false без claude-md-management (M2/content-floor/TDD govern)', async () => {
|
||||
const r = await decide({
|
||||
filePath: 'tools/enforce-floor.mjs', content: 'обычная правка кода', skillActive: false,
|
||||
sealedPlanCoversEdit: true,
|
||||
});
|
||||
expect(r.block).toBe(false);
|
||||
});
|
||||
it('дисциплинарный исходник ВНЕ печати → block (ЗАКОН, требует escape владельца)', async () => {
|
||||
const r = await decide({
|
||||
filePath: 'tools/enforce-floor.mjs', content: 'обычная правка кода', skillActive: true,
|
||||
sealedPlanCoversEdit: false,
|
||||
multiJudgeImpl: async () => ({ decision: 'NO' }),
|
||||
});
|
||||
expect(r.block).toBe(true);
|
||||
expect(r.reason).toMatch(/escape|ЗАКОН|§6/i);
|
||||
});
|
||||
it('дисциплинарный исходник ВНЕ печати но с escape владельца → block:false', async () => {
|
||||
const action = canonicalAction('Edit', { file_path: 'tools/enforce-floor.mjs' });
|
||||
const r = await decide({
|
||||
filePath: 'tools/enforce-floor.mjs', content: 'обычная правка кода', skillActive: false,
|
||||
sealedPlanCoversEdit: false,
|
||||
escapeAction: action, escapeGrants: [{ action, ts: 999 }], escapeConsumed: [], now: 1000,
|
||||
});
|
||||
expect(r.block).toBe(false);
|
||||
});
|
||||
it('дисциплинарный исходник под печатью с командой-строкой в коде (gate-config.json) → block:false (doc-malice слои не применяются к коду)', async () => {
|
||||
const r = await decide({
|
||||
filePath: 'tools/enforce-floor.mjs',
|
||||
content: "const P = /gate-config\\.json/i; // legit code mentioning a path",
|
||||
skillActive: false, sealedPlanCoversEdit: true,
|
||||
});
|
||||
expect(r.block).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user