Compare commits

...

1 Commits

Author SHA1 Message Date
Дмитрий d1ad4e8559 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).
2026-06-12 08:45:17 +03:00
2 changed files with 126 additions and 14 deletions
+59 -14
View File
@@ -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);
});
});