Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/permissions/__tests__/deferred-queue.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { describe, it, expect, vi } from "vitest";
import { mkdtempSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { afterEach, describe, it, expect, vi } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { DeferredQueue } from "../reviewer/deferred-queue.js";
import { PermissionTestResources } from "./test-resources.js";

const resources = new PermissionTestResources();

function tmpQueuePath(): string {
const dir = mkdtempSync(join(tmpdir(), "lvis-deferred-queue-"));
const dir = resources.makeTmpDir("lvis-deferred-queue-");
return join(dir, "deferred-queue.jsonl");
}

afterEach(async () => {
await resources.cleanup();
});

const SAMPLE = {
toolName: "fs_write",
source: "builtin" as const,
Expand Down
14 changes: 9 additions & 5 deletions src/permissions/__tests__/host-fetch-verb-snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@
* verb-snapshot flow from the DNS/SSRF/allow-list gate (which is unchanged and
* covered by host-fetch-guard.test.ts).
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

const harness = vi.hoisted(() => ({
capturedRuntimeOptions: null as Record<string, unknown> | null,
Expand Down Expand Up @@ -117,6 +114,13 @@ import {
type EffectEntry,
} from "../effect-ledger.js";
import type { PluginHostApi } from "../../plugins/types.js";
import { PermissionTestResources } from "./test-resources.js";

const resources = new PermissionTestResources();

afterEach(async () => {
await resources.cleanup();
});

type CreateHostApi = (
pluginId: string,
Expand Down Expand Up @@ -186,7 +190,7 @@ async function buildRealHostApi(): Promise<{
createHostApi,
"initPluginRuntime must register a createHostApi factory",
).toBeDefined();
const pluginDataDir = mkdtempSync(join(tmpdir(), "lvis-hostfetch-verb-"));
const pluginDataDir = resources.makeTmpDir("lvis-hostfetch-verb-");
const hostApi = createHostApi!(
"verb-snapshot-plugin",
{
Expand Down
16 changes: 9 additions & 7 deletions src/permissions/__tests__/hostapi-effect-completeness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@
* fail-closed `unclassifiedHostApiMethod` WRITE for an unmapped path, and is a
* PURE side-effect (it never alters the wrapped method's behavior).
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

const harness = vi.hoisted(() => ({
capturedRuntimeOptions: null as Record<string, unknown> | null,
Expand Down Expand Up @@ -102,6 +99,13 @@ import {
type EffectLedger,
} from "../effect-ledger.js";
import type { PluginHostApi } from "../../plugins/types.js";
import { PermissionTestResources } from "./test-resources.js";

const resources = new PermissionTestResources();

afterEach(async () => {
await resources.cleanup();
});

type CreateHostApi = (
pluginId: string,
Expand Down Expand Up @@ -164,9 +168,7 @@ async function buildRealHostApi(): Promise<PluginHostApi> {
createHostApi,
"initPluginRuntime must register a createHostApi factory",
).toBeDefined();
const pluginDataDir = mkdtempSync(
join(tmpdir(), "lvis-hostapi-completeness-"),
);
const pluginDataDir = resources.makeTmpDir("lvis-hostapi-completeness-");
// Build with the FULL capability vocabulary, not a sampled subset. A
// namespace/method wired ONLY under a capability ABSENT from the fixture would
// escape BOTH the non-plain-namespace assertion AND the SOT-coverage assertion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
* guard for that gate.
*/
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

// vi.mock must be at top level (hoisted). We feed the mock from a
Expand Down Expand Up @@ -42,26 +40,32 @@ vi.mock("../../audit/sandbox-audit-sink.js", async () => {
});

import { PermissionManager } from "../permission-manager.js";
import { VerdictCache } from "../reviewer/verdict-cache.js";
import { DeferredQueue } from "../reviewer/deferred-queue.js";
import {
LlmRiskClassifier,
RuleBasedRiskClassifier,
type RiskClassifier,
} from "../reviewer/risk-classifier.js";
import { PermissionTestResources } from "./test-resources.js";

const resources = new PermissionTestResources();

function tmpFile(name: string): string {
const dir = mkdtempSync(join(tmpdir(), "lvis-pm-legacy-null-"));
const dir = resources.makeTmpDir("lvis-pm-legacy-null-");
return join(dir, name);
}

afterEach(async () => {
await resources.cleanup();
});

function makeManager(): {
pm: PermissionManager;
classifier: RiskClassifier;
} {
const pm = new PermissionManager(tmpFile("permissions.json"));
const classifier = new RuleBasedRiskClassifier();
const cache = new VerdictCache(tmpFile("reviewer-cache.jsonl"));
const cache = resources.makeVerdictCache(tmpFile("reviewer-cache.jsonl"));
const queue = new DeferredQueue(tmpFile("deferred-queue.jsonl"));
pm.setReviewer({ classifier, cache, deferredQueue: queue });
return { pm, classifier };
Expand Down Expand Up @@ -215,7 +219,7 @@ describe("PermissionManager — fail-closed gate against legacy null-verdict ent
},
"gpt-4o-mini",
);
const cache = new VerdictCache(tmpFile("reviewer-cache.jsonl"));
const cache = resources.makeVerdictCache(tmpFile("reviewer-cache.jsonl"));
const queue = new DeferredQueue(tmpFile("deferred-queue.jsonl"));
pm.setReviewer({ classifier, cache, deferredQueue: queue });

Expand Down
22 changes: 15 additions & 7 deletions src/permissions/__tests__/permission-manager-reviewer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* Permission policy Phase 3 — PermissionManager.dispatchReviewer + setReviewer wiring.
*/
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
Expand All @@ -29,12 +28,19 @@ import {
unmarkPluginWorkerWrapped,
setActiveSandboxCapability,
} from "../sandbox-capability.js";
import { PermissionTestResources } from "./test-resources.js";

const resources = new PermissionTestResources();

function tmpFile(name: string): string {
const dir = mkdtempSync(join(tmpdir(), "lvis-pm-reviewer-"));
const dir = resources.makeTmpDir("lvis-pm-reviewer-");
return join(dir, name);
}

afterEach(async () => {
await resources.cleanup();
});

function allowedDir(path: string): string {
return caseFoldForMatch(canonicalizePathForMatch(path));
}
Expand All @@ -46,7 +52,7 @@ function makeManager(): {
classifier: RiskClassifier;
} {
const pm = new PermissionManager(tmpFile("permissions.json"));
const cache = new VerdictCache(tmpFile("reviewer-cache.jsonl"));
const cache = resources.makeVerdictCache(tmpFile("reviewer-cache.jsonl"));
const queue = new DeferredQueue(tmpFile("deferred-queue.jsonl"));
const classifier = new RuleBasedRiskClassifier();
pm.setReviewer({ classifier, cache, deferredQueue: queue });
Expand Down Expand Up @@ -670,7 +676,7 @@ describe("MAJOR-1 R2: dispatchReviewer threads abortSignal to LlmRiskClassifier.
},
);

const cache = new VerdictCache(tmpFile("reviewer-cache.jsonl"));
const cache = resources.makeVerdictCache(tmpFile("reviewer-cache.jsonl"));
const queue = new DeferredQueue(tmpFile("deferred-queue.jsonl"));
pm.setReviewer({ classifier: llmClassifier, cache, deferredQueue: queue });

Expand Down Expand Up @@ -708,7 +714,7 @@ describe("MAJOR-1 R2: dispatchReviewer threads abortSignal to LlmRiskClassifier.
}),
};
const llmClassifier = new LlmRiskClassifier(providerStub, "gpt-4o-mini", "deny");
const cache = new VerdictCache(tmpFile("reviewer-cache.jsonl"));
const cache = resources.makeVerdictCache(tmpFile("reviewer-cache.jsonl"));
const queue = new DeferredQueue(tmpFile("deferred-queue.jsonl"));
pm.setReviewer({ classifier: llmClassifier, cache, deferredQueue: queue });

Expand Down Expand Up @@ -761,6 +767,8 @@ describe("#664 flood guard — degraded rule reviewer does not over-defer headle
verdictCachePath: tmpFile("flood-cache.jsonl"),
deferredQueuePath: tmpFile("flood-queue.jsonl"),
});
const wiredCache = pm.getVerdictCache();
if (wiredCache) resources.trackFlushable(wiredCache);
expect(wiring.runtimeMode).toBe("llm-degraded-to-rule");
expect(pm.isReviewerDegradedToRule()).toBe(true);

Expand Down Expand Up @@ -830,7 +838,7 @@ describe("reviewer outcome provenance and base-cache safety", () => {
complete: () => Promise<import("../reviewer/risk-classifier.js").LlmCompletionResult>,
) {
const pm = new PermissionManager(tmpFile("permissions.json"));
const cache = new VerdictCache(tmpFile("reviewer-cache.jsonl"));
const cache = resources.makeVerdictCache(tmpFile("reviewer-cache.jsonl"));
const queue = new DeferredQueue(tmpFile("deferred-queue.jsonl"));
const provider = { complete: vi.fn(complete) };
pm.setReviewer({
Expand Down Expand Up @@ -933,7 +941,7 @@ describe("reviewer outcome provenance and base-cache safety", () => {
const pm = new PermissionManager(tmpFile("permissions.json"));
pm.setReviewer({
classifier: { classify },
cache: new VerdictCache(tmpFile("reviewer-cache.jsonl")),
cache: resources.makeVerdictCache(tmpFile("reviewer-cache.jsonl")),
deferredQueue: queue,
});

Expand Down
21 changes: 12 additions & 9 deletions src/permissions/__tests__/permission-review-scenario-board.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@
* permission policy path. The board is a PR artifact, but these tests keep it
* tied to executable behavior instead of letting it drift into a static mockup.
*/
import { describe, expect, it, vi } from "vitest";
import { readFileSync, mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { readFileSync } from "node:fs";
import { join, resolve } from "node:path";

import { PermissionManager } from "../permission-manager.js";
import { DeferredQueue } from "../reviewer/deferred-queue.js";
import { VerdictCache } from "../reviewer/verdict-cache.js";
import {
RuleBasedRiskClassifier,
type RiskClassifier,
Expand All @@ -26,20 +24,25 @@ import type { ToolCategory, ToolSource } from "../../tools/types.js";
import { buildPluginToolsForTest } from "../../plugins/__tests__/plugin-tool-test-fixture.js";
import type { PluginManifest } from "../../plugins/types.js";
import type { PluginRuntime } from "../../plugins/runtime.js";
import { cleanupTmpDir } from "../../testing/tmp-dir-teardown.js";
import { PermissionTestResources } from "./test-resources.js";

const BOARD_PATH = resolve(process.cwd(), "docs/design/permission-review-scenario-board-v2.html");
const resources = new PermissionTestResources();

function tmpFile(name: string): string {
const dir = mkdtempSync(join(tmpdir(), "lvis-permission-scenarios-"));
const dir = resources.makeTmpDir("lvis-permission-scenarios-");
return join(dir, name);
}

afterEach(async () => {
await resources.cleanup();
});

function makeManager(
mode: "default" | "strict" | "auto" | "allow" = "default",
classifier: RiskClassifier = new RuleBasedRiskClassifier(),
): { pm: PermissionManager; queue: DeferredQueue; cleanup: () => Promise<void> } {
const dir = mkdtempSync(join(tmpdir(), "lvis-permission-scenarios-"));
const dir = resources.makeTmpDir("lvis-permission-scenarios-");
const pm = new PermissionManager(join(dir, "permissions.json"));
const queue = new DeferredQueue(join(dir, "deferred-queue.jsonl"));
pm.setMode(mode);
Expand All @@ -51,10 +54,10 @@ function makeManager(
}
pm.setReviewer({
classifier,
cache: new VerdictCache(join(dir, "reviewer-cache.jsonl")),
cache: resources.makeVerdictCache(join(dir, "reviewer-cache.jsonl")),
deferredQueue: queue,
});
return { pm, queue, cleanup: () => cleanupTmpDir(dir) };
return { pm, queue, cleanup: () => resources.cleanup() };
}

function fixedClassifier(verdict: RiskVerdict): RiskClassifier {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@
* Defends the migration against silently flipping a fail-closed user to
* the new pass-through-LOW semantic at upgrade time.
*/
import { describe, it, expect } from "vitest";
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { afterEach, describe, it, expect } from "vitest";
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import {
readPermissionSettings,
migrateLegacyDisabledMode,
} from "../permission-settings-store.js";
import { PermissionTestResources } from "./test-resources.js";

const resources = new PermissionTestResources();

afterEach(async () => {
await resources.cleanup();
});

function writeRaw(dir: string, body: object): string {
const p = join(dir, "settings.json");
Expand Down Expand Up @@ -119,7 +125,7 @@ describe("migrateLegacyDisabledMode — issue #664 idempotency", () => {

describe("readPermissionSettings — issue #664 migration end-to-end", () => {
it("persists the migrated file on first read", () => {
const dir = mkdtempSync(join(tmpdir(), "lvis-664-mig-"));
const dir = resources.makeTmpDir("lvis-664-mig-");
const filePath = writeRaw(dir, {
permissions: {
reviewer: {
Expand Down Expand Up @@ -149,7 +155,7 @@ describe("readPermissionSettings — issue #664 migration end-to-end", () => {
});

it("preserves user-chosen disabled after migration marker present", () => {
const dir = mkdtempSync(join(tmpdir(), "lvis-664-userpick-"));
const dir = resources.makeTmpDir("lvis-664-userpick-");
const filePath = writeRaw(dir, {
permissions: {
reviewer: {
Expand Down
14 changes: 10 additions & 4 deletions src/permissions/__tests__/permission-slash-reviewer.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/**
* Permission policy Phase 3 — `/permission reviewer` slash + settings persistence tests.
*/
import { describe, it, expect, vi } from "vitest";
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { afterEach, describe, it, expect, vi } from "vitest";
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import {
parsePermissionReviewerCommand,
Expand All @@ -16,12 +15,19 @@ import {
normalizePermissionSettings,
writePermissionSettings,
} from "../permission-settings-store.js";
import { PermissionTestResources } from "./test-resources.js";

const resources = new PermissionTestResources();

function tmpSettingsPath(): string {
const dir = mkdtempSync(join(tmpdir(), "lvis-perm-reviewer-"));
const dir = resources.makeTmpDir("lvis-perm-reviewer-");
return join(dir, "settings.json");
}

afterEach(async () => {
await resources.cleanup();
});

describe("parsePermissionReviewerCommand", () => {
it("parses 'show'", () => {
expect(parsePermissionReviewerCommand("show")).toEqual({ verb: "show", value: "" });
Expand Down
Loading
Loading