This commit was merged in pull request #842.
This commit is contained in:
@@ -1,6 +1,37 @@
|
||||
{
|
||||
"model": "opus",
|
||||
"hooks": {
|
||||
"PreCompact": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason pre-compact"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "compact",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason session-start-compact"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "resume|clear",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason session-start-rollover --bump-generation"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
|
||||
93
packages/mosaic/framework/runtime/pi/lease-lifecycle.ts
Normal file
93
packages/mosaic/framework/runtime/pi/lease-lifecycle.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
export type LeaseLifecycleRunner = (args: string[]) => boolean;
|
||||
|
||||
type LifecycleEvent = {
|
||||
reason?: unknown;
|
||||
toolName?: unknown;
|
||||
};
|
||||
|
||||
type LifecycleHandler = (
|
||||
event: LifecycleEvent,
|
||||
context: Record<string, unknown>,
|
||||
) => unknown | Promise<unknown>;
|
||||
|
||||
export interface LeaseLifecyclePiApi {
|
||||
on(event: string, handler: LifecycleHandler): void;
|
||||
}
|
||||
|
||||
const ROLLOVER_REASONS = new Set(['reload', 'new', 'resume', 'fork']);
|
||||
|
||||
function eventReason(event: LifecycleEvent): string {
|
||||
return typeof event.reason === 'string' && event.reason.length > 0 ? event.reason : 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Register redundant Pi compaction observers and same-PID generation rollover.
|
||||
*
|
||||
* A failed pre-compaction observer cancels compaction. A failed post-compaction
|
||||
* observer or generation rollover locally blocks later tools in addition to the
|
||||
* broker-backed all-tools gate.
|
||||
*/
|
||||
export function registerLeaseLifecycleHooks(
|
||||
pi: LeaseLifecyclePiApi,
|
||||
runRevoker: LeaseLifecycleRunner,
|
||||
): void {
|
||||
let postCompactReason: string | null = null;
|
||||
let postCompactFailure = false;
|
||||
let rolloverFailure = false;
|
||||
|
||||
pi.on('session_before_compact', async (event) => {
|
||||
const reason = eventReason(event);
|
||||
const revoked = runRevoker([
|
||||
'--runtime',
|
||||
'pi',
|
||||
'--reason',
|
||||
`pi-session-before-compact:${reason}`,
|
||||
]);
|
||||
if (!revoked) return { cancel: true };
|
||||
return undefined;
|
||||
});
|
||||
|
||||
pi.on('session_compact', async (event) => {
|
||||
postCompactReason = eventReason(event);
|
||||
});
|
||||
|
||||
pi.on('context', async () => {
|
||||
if (postCompactReason === null) return undefined;
|
||||
const reason = postCompactReason;
|
||||
const revoked = runRevoker([
|
||||
'--runtime',
|
||||
'pi',
|
||||
'--reason',
|
||||
`pi-context-after-compact:${reason}`,
|
||||
]);
|
||||
if (revoked) {
|
||||
postCompactReason = null;
|
||||
postCompactFailure = false;
|
||||
} else {
|
||||
postCompactFailure = true;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
pi.on('session_start', async (event) => {
|
||||
const reason = eventReason(event);
|
||||
if (!ROLLOVER_REASONS.has(reason)) return undefined;
|
||||
const revoked = runRevoker([
|
||||
'--runtime',
|
||||
'pi',
|
||||
'--reason',
|
||||
`pi-session-start:${reason}`,
|
||||
'--bump-generation',
|
||||
]);
|
||||
rolloverFailure = !revoked;
|
||||
return undefined;
|
||||
});
|
||||
|
||||
pi.on('tool_call', async () => {
|
||||
if (!postCompactFailure && !rolloverFailure) return undefined;
|
||||
return {
|
||||
block: true,
|
||||
reason: 'BLOCKED: Mosaic lease lifecycle revoke failed; runtime remains UNVERIFIED.',
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import { join, basename } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import { execSync, spawnSync } from 'node:child_process';
|
||||
import { registerLeaseLifecycleHooks, type LeaseLifecyclePiApi } from './lease-lifecycle.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Config
|
||||
@@ -29,6 +30,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');
|
||||
const LEASE_REVOKER = join(MOSAIC_HOME, 'tools', 'lease-broker', 'revoke-lease.py');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -107,6 +109,15 @@ function nowIso(): string {
|
||||
return new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
||||
}
|
||||
|
||||
function runPiLeaseRevoker(args: string[]): boolean {
|
||||
const result = spawnSync('python3', [LEASE_REVOKER, ...args], {
|
||||
encoding: 'utf8',
|
||||
timeout: 2_000,
|
||||
env: process.env,
|
||||
});
|
||||
return result.status === 0;
|
||||
}
|
||||
|
||||
function checkPiMutatorGate(toolName: string): { block: true; reason: string } | undefined {
|
||||
const result = spawnSync('python3', [MUTATOR_GATE, '--runtime', 'pi'], {
|
||||
input: `${JSON.stringify({ tool_name: toolName })}\n`,
|
||||
@@ -268,6 +279,9 @@ export default function register(pi: ExtensionAPI) {
|
||||
let hbModel: string | null = null;
|
||||
let hbTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
// ── Compaction observers and same-PID generation rollover ─────────────
|
||||
registerLeaseLifecycleHooks(pi as unknown as LeaseLifecyclePiApi, runPiLeaseRevoker);
|
||||
|
||||
// ── 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.
|
||||
|
||||
Reference in New Issue
Block a user