feat(#829): enforce whole mutator-class lease gate
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful

This commit is contained in:
ms-lead-reviewer
2026-07-17 22:35:18 -05:00
parent 439f5f4bc9
commit 77b137ccc0
12 changed files with 476 additions and 19 deletions

View File

@@ -28,6 +28,7 @@ import { execSync, spawnSync } from 'node:child_process';
// ---------------------------------------------------------------------------
const MOSAIC_HOME = process.env['MOSAIC_HOME'] ?? join(homedir(), '.config', 'mosaic');
const MUTATOR_GATE = join(MOSAIC_HOME, 'tools', 'lease-broker', 'mutator-gate.py');
// ---------------------------------------------------------------------------
// Helpers
@@ -106,6 +107,23 @@ function nowIso(): string {
return new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
}
function checkPiMutatorGate(toolName: string): { block: true; reason: string } | undefined {
const result = spawnSync('python3', [MUTATOR_GATE, '--runtime', 'pi'], {
input: `${JSON.stringify({ tool_name: toolName })}\n`,
encoding: 'utf8',
timeout: 2_000,
env: process.env,
});
if (result.status === 0) return undefined;
const detail = String(result.stderr ?? '')
.trim()
.split('\n')[0];
return {
block: true,
reason: detail || 'BLOCKED: Mosaic mutator gate is unavailable or the lease is UNVERIFIED.',
};
}
// ---------------------------------------------------------------------------
// Mission detection
// ---------------------------------------------------------------------------
@@ -250,6 +268,11 @@ export default function register(pi: ExtensionAPI) {
let hbModel: string | null = null;
let hbTimer: ReturnType<typeof setInterval> | null = null;
// ── Whole mutator-class authorization gate ────────────────────────────
// Every Pi tool, including unknown/custom tools, reaches the broker-backed
// class gate before execution. Broker/script failure blocks fail-closed.
pi.on('tool_call', async (event) => checkPiMutatorGate(event.toolName));
// ── Session Start ─────────────────────────────────────────────────────
pi.on('session_start', async (_event, ctx) => {
sessionCwd = process.cwd();