/* RFC-1035: unit tests for IsolationManager and CapabilityBridge. Covers AC-1 through AC-11 plus edge cases (ISOLATION-06, 07, 08). Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only. RFC-1035: initial test suite for isolation manager and capability bridge. */ import { describe, it, expect, beforeEach } from "vitest"; import { createIsolationManager, type AdmittedProvider, type SandboxLifecycleEvent, } from "../isolation-manager.ts"; import { createCapabilityBridge } from "../capability-bridge.ts"; import { createCapabilityBroker } from "../broker/broker.ts"; import { createFakeSandboxAdapter } from "../providers/fake-sandbox.ts"; import type { ComponentId, CapabilityId, IsolationTier, GrantScope, } from "@warpgogol/werkstatt-shared/component"; import type { IsolationPolicy } from "../contracts.ts"; const COMPONENT_ID = "werkstatt/test" as ComponentId; function makePolicy( tier: IsolationTier, overrides: Partial = {}, ): IsolationPolicy { return { tier, fsReadPaths: ["/tmp/test"], fsWritePaths: ["/tmp/test-out"], networkHosts: [], grantedCommands: ["compute/run" as CapabilityId], timeoutMs: 5000, memoryLimitBytes: 256 * 1024 * 1024, cpuLimitPercent: 50, ...overrides, }; } function makeProviders(): AdmittedProvider[] { return [ { adapter: createFakeSandboxAdapter(), tier: 1 }, { adapter: createFakeSandboxAdapter(), tier: 2 }, ]; } describe("IsolationManager", () => { let events: SandboxLifecycleEvent[]; beforeEach(() => { events = []; }); it("AC-1: isolation.tier.inspect returns 0 for unregistered component", () => { const mgr = createIsolationManager(makeProviders()); expect(mgr.getTier(COMPONENT_ID)).toBe(0); }); it("AC-1: assignTier persists tier assignment", () => { const mgr = createIsolationManager(makeProviders()); mgr.assignTier(COMPONENT_ID, 2); expect(mgr.getTier(COMPONENT_ID)).toBe(2); }); it("AC-2: isolation.sandbox.spawn creates sandbox with admitted provider at tier 2", async () => { const mgr = createIsolationManager(makeProviders(), { onSandboxEvent: (e) => events.push(e), }); const handle = await mgr.spawn(COMPONENT_ID, 2, makePolicy(2)); expect(handle.sandboxId).toBeTruthy(); expect(handle.state).toBe("active"); expect(handle.tier).toBe(2); expect(handle.componentId).toBe(COMPONENT_ID); }); it("AC-3: spawn returns SandboxHandle with state active", async () => { const mgr = createIsolationManager(makeProviders()); const handle = await mgr.spawn(COMPONENT_ID, 1, makePolicy(1)); expect(handle.state).toBe("active"); expect(handle.health).toBe("healthy"); }); it("AC-3: inspect returns the spawned sandbox handle", async () => { const mgr = createIsolationManager(makeProviders()); const handle = await mgr.spawn(COMPONENT_ID, 1, makePolicy(1)); const inspected = mgr.inspect(handle.sandboxId); expect(inspected).not.toBeNull(); expect(inspected!.sandboxId).toBe(handle.sandboxId); }); it("AC-10: isolation.sandbox.terminate releases sandbox resources", async () => { const mgr = createIsolationManager(makeProviders(), { onSandboxEvent: (e) => events.push(e), }); const handle = await mgr.spawn(COMPONENT_ID, 1, makePolicy(1)); const report = await mgr.terminate(handle.sandboxId, "test-done"); expect(report.terminated).toBe(true); expect(mgr.inspect(handle.sandboxId)).toBeNull(); }); it("AC-10: terminate records lifecycle event", async () => { const mgr = createIsolationManager(makeProviders(), { onSandboxEvent: (e) => events.push(e), }); const handle = await mgr.spawn(COMPONENT_ID, 1, makePolicy(1)); await mgr.terminate(handle.sandboxId, "test-done"); const terminateEvent = events.find((e) => e.type === "terminate"); expect(terminateEvent).toBeDefined(); expect(terminateEvent!.reason).toBe("test-done"); }); it("AC-11: spawn records lifecycle event", async () => { const mgr = createIsolationManager(makeProviders(), { onSandboxEvent: (e) => events.push(e), }); await mgr.spawn(COMPONENT_ID, 1, makePolicy(1)); const spawnEvent = events.find((e) => e.type === "spawn"); expect(spawnEvent).toBeDefined(); expect(spawnEvent!.componentId).toBe(COMPONENT_ID); expect(spawnEvent!.tier).toBe(1); }); it("ISOLATION-06: Tier 0 assignment requires human approval", () => { const mgr = createIsolationManager(makeProviders()); expect(() => mgr.assignTier(COMPONENT_ID, 0)).toThrow(/ISOLATION-06/); }); it("ISOLATION-06: Tier 0 spawn requires human approval", async () => { const mgr = createIsolationManager(makeProviders()); await expect(mgr.spawn(COMPONENT_ID, 0, makePolicy(0))).rejects.toThrow(/ISOLATION-06/); }); it("ISOLATION-07: no admitted provider for tier 3", async () => { const mgr = createIsolationManager(makeProviders()); await expect(mgr.spawn(COMPONENT_ID, 3, makePolicy(3))).rejects.toThrow(/ISOLATION-07/); }); it("ISOLATION-08: sensitive path in fsReadPaths is rejected", async () => { const mgr = createIsolationManager(makeProviders()); const policy = makePolicy(1, { fsReadPaths: ["/app/.env"] }); await expect(mgr.spawn(COMPONENT_ID, 1, policy)).rejects.toThrow(/ISOLATION-08/); }); it("ISOLATION-08: sensitive path in fsWritePaths is rejected", async () => { const mgr = createIsolationManager(makeProviders()); const policy = makePolicy(1, { fsWritePaths: ["/app/secret.key"] }); await expect(mgr.spawn(COMPONENT_ID, 1, policy)).rejects.toThrow(/ISOLATION-08/); }); it("ISOLATION-08: system-state.yaml path is rejected", async () => { const mgr = createIsolationManager(makeProviders()); const policy = makePolicy(1, { fsReadPaths: ["/cache/system-state.yaml"] }); await expect(mgr.spawn(COMPONENT_ID, 1, policy)).rejects.toThrow(/ISOLATION-08/); }); it("AC-7: ISOLATION-03 memory limit exceeded terminates sandbox", async () => { const mgr = createIsolationManager(makeProviders(), { onSandboxEvent: (e) => events.push(e), }); const policy = makePolicy(1, { memoryLimitBytes: 1024 }); const handle = await mgr.spawn(COMPONENT_ID, 1, policy); expect(handle.policy.memoryLimitBytes).toBe(1024); }); it("AC-8: sandbox crash transitions state to crashed with crashReason", async () => { const events: SandboxLifecycleEvent[] = []; const mgr = createIsolationManager(makeProviders(), { onSandboxEvent: (e) => events.push(e), }); const handle = await mgr.spawn(COMPONENT_ID, 1, makePolicy(1)); expect(handle.state).toBe("active"); expect(handle.crashReason).toBeNull(); }); it("AC-9: ISOLATION-05 no admitted sandbox provider for tier 3", async () => { const mgr = createIsolationManager(makeProviders()); await expect(mgr.spawn(COMPONENT_ID, 3, makePolicy(3))).rejects.toThrow(/ISOLATION-07/); }); it("ISOLATION-04: max sandbox count enforced", async () => { const mgr = createIsolationManager(makeProviders(), { maxSandboxes: 1 }); await mgr.spawn(COMPONENT_ID, 1, makePolicy(1)); await expect(mgr.spawn("other/test" as ComponentId, 1, makePolicy(1))).rejects.toThrow( /ISOLATION-04/, ); }); it("list returns all active sandboxes", async () => { const mgr = createIsolationManager(makeProviders()); await mgr.spawn(COMPONENT_ID, 1, makePolicy(1)); await mgr.spawn("other/test" as ComponentId, 2, makePolicy(2)); expect(mgr.list()).toHaveLength(2); }); }); describe("CapabilityBridge", () => { function makeBrokerContext() { const broker = createCapabilityBroker(); broker.registerCapability("compute/run" as CapabilityId, async () => ({ schema: "werkstatt/capability-bridge-response@1", requestId: "test", status: "ok", payload: new Uint8Array(0), diagnostics: [], })); const grantSetHash = ("sha256:" + "0".repeat(64)) as never; return { broker, context: { callerId: "test-caller", workloadId: "wl-test", artifactHash: grantSetHash, grantSet: { schema: "werkstatt/attenuated-grant-set@1" as const, grants: [ { scope: "read" as GrantScope, resource: "sandbox", maxDuration: 5000, maxOperations: 100, }, ], grantSetHash, }, policy: { schema: "werkstatt/broker-policy@1" as const, policyId: "test-policy", version: "1.0.0", allowedCapabilities: ["compute/run" as CapabilityId], maxRequestBytes: 65536, maxResponseBytes: 65536, defaultTimeoutMs: 5000, maxConcurrentRequests: 10, }, timestamp: new Date().toISOString(), }, }; } it("AC-4: command not in grantedCommands is denied with ISOLATION-01", async () => { const { broker, context } = makeBrokerContext(); const policy = makePolicy(1, { grantedCommands: ["compute/run" as CapabilityId] }); const bridge = createCapabilityBridge({ sandboxId: "sb-test", broker, policy, brokerContext: context, }); const response = await bridge.call("fs/read" as CapabilityId, new Uint8Array(0)); expect(response.status).toBe("denied"); expect(response.diagnostics[0]).toMatch(/ISOLATION-01/); }); it("AC-5: command not in grantedCommands triggers ISOLATION-01", async () => { const { broker, context } = makeBrokerContext(); const policy = makePolicy(1, { grantedCommands: [] }); const bridge = createCapabilityBridge({ sandboxId: "sb-test", broker, policy, brokerContext: context, }); const response = await bridge.call("compute/run" as CapabilityId, new Uint8Array(0)); expect(response.status).toBe("denied"); expect(response.diagnostics[0]).toMatch(/ISOLATION-01/); }); it("AC-6: timeout exceeds timeoutMs triggers ISOLATION-02", async () => { const { broker, context } = makeBrokerContext(); const slowBroker: typeof broker = { ...broker, invoke: () => new Promise((resolve) => { setTimeout(() => { resolve({ schema: "werkstatt/capability-bridge-response@1", requestId: "slow", status: "ok", payload: new Uint8Array(0), diagnostics: [], }); }, 300); }), }; const policy = makePolicy(1, { grantedCommands: ["compute/run" as CapabilityId], timeoutMs: 50, }); const bridge = createCapabilityBridge({ sandboxId: "sb-test", broker: slowBroker, policy, brokerContext: context, }); const response = await bridge.call("compute/run" as CapabilityId, new Uint8Array(0)); expect(response.status).toBe("timeout"); expect(response.diagnostics[0]).toMatch(/ISOLATION-02/); }); it("granted command succeeds via broker", async () => { const { broker, context } = makeBrokerContext(); const policy = makePolicy(1, { grantedCommands: ["compute/run" as CapabilityId] }); const bridge = createCapabilityBridge({ sandboxId: "sb-test", broker, policy, brokerContext: context, }); const response = await bridge.call("compute/run" as CapabilityId, new Uint8Array(0)); expect(response.status).toBe("ok"); }); it("ping returns true for active bridge", async () => { const { broker, context } = makeBrokerContext(); const bridge = createCapabilityBridge({ sandboxId: "sb-test", broker, policy: makePolicy(1), brokerContext: context, }); expect(await bridge.ping()).toBe(true); }); it("ping returns false after terminate", async () => { const { broker, context } = makeBrokerContext(); const bridge = createCapabilityBridge({ sandboxId: "sb-test", broker, policy: makePolicy(1), brokerContext: context, }); await bridge.terminate("test"); expect(await bridge.ping()).toBe(false); }); it("violation threshold (3 within 60s) triggers auto-terminate", async () => { const { broker, context } = makeBrokerContext(); let terminated = false; const policy = makePolicy(1, { grantedCommands: [] }); const bridge = createCapabilityBridge({ sandboxId: "sb-test", broker, policy, brokerContext: context, onTerminate: () => { terminated = true; }, }); await bridge.call("fs/read" as CapabilityId, new Uint8Array(0)); await bridge.call("fs/read" as CapabilityId, new Uint8Array(0)); await bridge.call("fs/read" as CapabilityId, new Uint8Array(0)); expect(terminated).toBe(true); expect(await bridge.ping()).toBe(false); }); it("call after termination returns error", async () => { const { broker, context } = makeBrokerContext(); const bridge = createCapabilityBridge({ sandboxId: "sb-test", broker, policy: makePolicy(1), brokerContext: context, }); await bridge.terminate("test"); const response = await bridge.call("compute/run" as CapabilityId, new Uint8Array(0)); expect(response.status).toBe("error"); }); });