/*
RFC-1035: IsolationManager implementation — manages sandbox lifecycle
(spawn, inspect, terminate, list) and component tier assignments.
- Does not implement sandbox providers — those are admitted IsolationAdapterV1 instances.
- Does not implement bordbuch I/O — lifecycle events are emitted via callback.
- Does not implement kernel command handlers — those live in isolation.module.ts.
- RFC-1035: initial implementation of IsolationManager.
*/
import type { ComponentId, IsolationTier } from "@warpgogol/werkstatt-shared/component";
import type {
IsolationAdapterV1,
IsolationManager as IIsolationManager,
IsolationPolicy,
SandboxHandle,
SandboxState,
SandboxedWorkloadCreateV1,
TerminationReportV1,
} from "./contracts.ts";
import type { Sha256Digest } from "@warpgogol/werkstatt-shared/fingerprint";
const DEFAULT_MAX_SANDBOXES = 16;
const SENSITIVE_PATH_PATTERNS: readonly RegExp[] = [
/\.env/i,
/system-state\.yaml$/i,
/\.pem$/i,
/\.key$/i,
/credentials/i,
/secret/i,
];
export interface IsolationManagerOptions {
readonly maxSandboxes?: number;
readonly onSandboxEvent?: (event: SandboxLifecycleEvent) => void;
}
export interface SandboxLifecycleEvent {
readonly type: "spawn" | "terminate" | "crash";
readonly sandboxId: string;
readonly componentId: ComponentId;
readonly tier: IsolationTier;
readonly reason?: string;
readonly crashReason?: string;
}
export interface AdmittedProvider {
readonly adapter: IsolationAdapterV1;
readonly tier: IsolationTier;
}
function generateSandboxId(_componentId: ComponentId): string {
const ts = Date.now().toString(36);
const rand = Math.random().toString(36).slice(2, 8);
return `sandbox-${ts}-${rand}`;
}
function validatePolicyPaths(policy: IsolationPolicy): string | null {
for (const path of policy.fsReadPaths) {
for (const pattern of SENSITIVE_PATH_PATTERNS) {
if (pattern.test(path)) {
return `fsReadPath "${path}" matches sensitive pattern`;
}
}
}
for (const path of policy.fsWritePaths) {
for (const pattern of SENSITIVE_PATH_PATTERNS) {
if (pattern.test(path)) {
return `fsWritePath "${path}" matches sensitive pattern`;
}
}
}
return null;
}
export function createIsolationManager(
providers: ReadonlyArray,
options: IsolationManagerOptions = {},
): IIsolationManager {
const maxSandboxes = options.maxSandboxes ?? DEFAULT_MAX_SANDBOXES;
const sandboxes = new Map();
const tierAssignments = new Map();
const onEvent = options.onSandboxEvent;
function findProvider(tier: IsolationTier): AdmittedProvider | null {
return providers.find((p) => p.tier === tier) ?? null;
}
return {
async spawn(
componentId: ComponentId,
tier: IsolationTier,
policy: IsolationPolicy,
): Promise {
if (tier === 0) {
throw new Error("ISOLATION-06: Tier 0 requires human approval for spawn");
}
const provider = findProvider(tier);
if (!provider) {
throw new Error(`ISOLATION-07: no admitted provider for tier ${tier}`);
}
const sensitiveViolation = validatePolicyPaths(policy);
if (sensitiveViolation) {
throw new Error(`ISOLATION-08: ${sensitiveViolation}`);
}
if (sandboxes.size >= maxSandboxes) {
throw new Error(`ISOLATION-04: spawn failed — max sandbox count ${maxSandboxes} reached`);
}
const sandboxId = generateSandboxId(componentId);
const artifactHash = ("sha256:" + "0".repeat(64)) as Sha256Digest;
const createInput: SandboxedWorkloadCreateV1 = {
workloadId: sandboxId,
artifactHash,
grantSet: {
schema: "werkstatt/attenuated-grant-set@1",
grants: [],
grantSetHash: artifactHash,
},
limits: {
maxMemoryBytes: policy.memoryLimitBytes,
maxCpuTimeMs: policy.cpuLimitPercent * 1000,
maxWallTimeMs: policy.timeoutMs,
maxConcurrency: 1,
maxResponseBytes: 65536,
maxRequestBytes: 65536,
},
bridgeSchemaHash: artifactHash,
idempotencyKey: sandboxId,
};
const workload = await provider.adapter.create(createInput);
const handle: SandboxHandle = {
...workload,
sandboxId,
componentId,
tier,
policy,
state: "active" as SandboxState,
memoryUsageBytes: 0,
cpuUsagePercent: 0,
crashReason: null,
health: "healthy" as const,
};
sandboxes.set(sandboxId, handle);
onEvent?.({
type: "spawn",
sandboxId,
componentId,
tier,
});
return handle;
},
inspect(sandboxId: string): SandboxHandle | null {
return sandboxes.get(sandboxId) ?? null;
},
async terminate(sandboxId: string, reason: string): Promise {
const handle = sandboxes.get(sandboxId);
if (!handle) {
throw new Error(`ISOLATION-09: sandbox ${sandboxId} not found`);
}
const report = await handle.terminate(reason);
const updated: SandboxHandle = {
...handle,
state: "terminated" as SandboxState,
crashReason: null,
health: "unhealthy" as const,
};
sandboxes.set(sandboxId, updated);
sandboxes.delete(sandboxId);
onEvent?.({
type: "terminate",
sandboxId,
componentId: handle.componentId,
tier: handle.tier,
reason,
});
return report;
},
list(): readonly SandboxHandle[] {
return [...sandboxes.values()];
},
assignTier(componentId: ComponentId, tier: IsolationTier): void {
if (tier === 0) {
throw new Error("ISOLATION-06: Tier 0 requires human approval");
}
tierAssignments.set(String(componentId), tier);
},
getTier(componentId: ComponentId): IsolationTier {
return tierAssignments.get(String(componentId)) ?? 0;
},
};
}