94 lines
2.5 KiB
TypeScript
94 lines
2.5 KiB
TypeScript
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.',
|
|
};
|
|
});
|
|
}
|