import { test, expect, describe, beforeEach, afterEach, vi } from "vitest";
import { mkdtemp, rm, writeFile, mkdir } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { executeRegisteredCommand } from "../runtime/execute-command.ts";
import { createCacheLayer, type CacheLayer } from "../cache/cache-layer.ts";
import { createKernelLogger } from "../logger.ts";
import { createDefaultIO, EMPTY_WORKPIECE_ENV } from "@warpgogol/werkstatt-shared/kernel";
import type {
ActualState,
KernelCommandDefinition,
KernelResultCacheContext,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-shared/kernel";
/*
RFC-1133: end-to-end tests for the executor-level command-result cache.
executeRegisteredCommand is the single choke point — driving it directly
exercises the same block that executeKernelCommand and both pipeline
executors flow through. Covers: second-run cache hit (AC-1), flag-variant
key separation (AC-2), --force read-bypass + write-refresh (AC-3),
--site/--id alias equivalence (AC-6), schema-less commands (AC-7), and
cacheBypassFlags on the direct path (AC-9).
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- Fixtures stay inline and minimal so each case states its own setup.
- RFC-1311: Compass header sweep — declares the direct suite's contract for test-file scaffold coverage.
*/
// @ai-invariant fixtures must not couple to internals beyond the public surface under test
function makeCommand(
executions: { count: number },
overrides: Partial = {},
): KernelCommandDefinition {
return {
name: "test.cacheable",
modulePath: "packages/test/src/mod.ts",
description: "cacheable test command",
scope: "workspace",
reads: ["src/input.txt"],
execute: () => {
executions.count++;
return { exitCode: 0, summary: "ran" };
},
...overrides,
};
}
function makeContext(
root: string,
resultCache?: KernelResultCacheContext,
force = false,
dryRun = false,
workpieceEnv: ReadonlyMap = EMPTY_WORKPIECE_ENV,
): KernelRuntimeContext {
const { io, intents } = createDefaultIO();
return {
workspaceRoot: root,
site: undefined,
siteExplicit: false,
logger: createKernelLogger("json"),
dryRun,
force,
outputFormat: "json",
io,
fileIntents: intents,
actualState: {
components: new Map(),
commands: new Map(),
pipelines: new Map(),
} as unknown as ActualState,
workpieceEnv,
resultCache,
};
}
describe("RFC-1133: direct-path command-result cache", () => {
let tmpDir: string;
let layer: CacheLayer;
beforeEach(async () => {
tmpDir = await mkdtemp(join(tmpdir(), "rfc1133-"));
await mkdir(join(tmpDir, "src"), { recursive: true });
await writeFile(join(tmpDir, "src", "input.txt"), "test");
layer = await createCacheLayer(tmpDir);
});
afterEach(async () => {
await layer.close();
await rm(tmpDir, { recursive: true, force: true });
});
function cacheCtx(): KernelResultCacheContext {
return { layer, moduleHashCache: new Map() };
}
test("AC-1: second identical execution is served from cache", async () => {
const executions = { count: 0 };
const cmd = makeCommand(executions);
const ctx = makeContext(tmpDir, cacheCtx());
const first = await executeRegisteredCommand(cmd, ctx, []);
const second = await executeRegisteredCommand(cmd, ctx, []);
expect(first.ok).toBe(true);
expect(executions.count).toBe(1);
expect(second.cached).toBe(true);
expect(second.ok).toBe(true);
});
test("AC-2: a different flag value produces a distinct cache entry", async () => {
const executions = { count: 0 };
const cmd = makeCommand(executions, {
flags: { id: { kind: "string", description: "target id" } },
});
const ctx = makeContext(tmpDir, cacheCtx());
await executeRegisteredCommand(cmd, ctx, ["--id", "a"]);
const second = await executeRegisteredCommand(cmd, ctx, ["--id", "b"]);
expect(executions.count).toBe(2);
expect(second.cached).toBeFalsy();
});
test("AC-3: --force bypasses the read and refreshes the entry", async () => {
const executions = { count: 0 };
const cmd = makeCommand(executions);
const cache = cacheCtx();
await executeRegisteredCommand(cmd, makeContext(tmpDir, cache), []);
const forced = await executeRegisteredCommand(cmd, makeContext(tmpDir, cache, true), []);
const after = await executeRegisteredCommand(cmd, makeContext(tmpDir, cache), []);
expect(executions.count).toBe(2);
expect(forced.cached).toBeFalsy();
expect(after.cached).toBe(true);
});
test("AC-6: --site alias and canonical --id share one entry", async () => {
const executions = { count: 0 };
const cmd = makeCommand(executions, {
flags: { id: { kind: "string", description: "target id" } },
});
const ctx = makeContext(tmpDir, cacheCtx());
await executeRegisteredCommand(cmd, ctx, ["--site", "foo"]);
const second = await executeRegisteredCommand(cmd, ctx, ["--id", "foo"]);
expect(executions.count).toBe(1);
expect(second.cached).toBe(true);
});
test("AC-7: schema-less command caches; a non-invisible flag splits the key", async () => {
const executions = { count: 0 };
const cmd = makeCommand(executions); // no flags schema
const ctx = makeContext(tmpDir, cacheCtx());
await executeRegisteredCommand(cmd, ctx, []);
const second = await executeRegisteredCommand(cmd, ctx, []);
const third = await executeRegisteredCommand(cmd, ctx, ["--foo", "bar"]);
expect(executions.count).toBe(2);
expect(second.cached).toBe(true);
expect(third.cached).toBeFalsy();
});
test("AC-9: cacheBypassFlags skips read and write on the direct path", async () => {
const executions = { count: 0 };
const cmd = makeCommand(executions, {
flags: { rfc: { kind: "string", description: "rfc filter" } },
cacheBypassFlags: ["rfc"],
});
const ctx = makeContext(tmpDir, cacheCtx());
await executeRegisteredCommand(cmd, ctx, ["--rfc", "RFC-0001"]);
const second = await executeRegisteredCommand(cmd, ctx, ["--rfc", "RFC-0001"]);
expect(executions.count).toBe(2);
expect(second.cached).toBeFalsy();
});
test("invisible universal flags do not fragment the cache key", async () => {
const executions = { count: 0 };
const cmd = makeCommand(executions);
const ctx = makeContext(tmpDir, cacheCtx());
await executeRegisteredCommand(cmd, ctx, ["--json"]);
const second = await executeRegisteredCommand(cmd, ctx, ["--verbose"]);
expect(executions.count).toBe(1);
expect(second.cached).toBe(true);
});
test("RFC-1303: declared envInputs hash into the key — an env change invalidates the entry", async () => {
const executions = { count: 0 };
const cmd = makeCommand(executions, { envInputs: ["RFC1303_TEST_ENV"] });
const ctx = makeContext(tmpDir, cacheCtx());
vi.stubEnv("RFC1303_TEST_ENV", "a");
await executeRegisteredCommand(cmd, ctx, []);
const hit = await executeRegisteredCommand(cmd, ctx, []);
expect(executions.count).toBe(1);
expect(hit.cached).toBe(true);
vi.stubEnv("RFC1303_TEST_ENV", "b");
const miss = await executeRegisteredCommand(cmd, ctx, []);
expect(miss.cached).toBeFalsy();
expect(executions.count).toBe(2);
vi.unstubAllEnvs();
const unset = await executeRegisteredCommand(cmd, ctx, []);
expect(unset.cached).toBeFalsy();
expect(executions.count).toBe(3);
});
test("RFC-1303 REVIEW-02: a workpieceEnv (.env channel) change busts the cache like process.env", async () => {
const executions = { count: 0 };
const cmd = makeCommand(executions, { envInputs: ["RFC1303_TEST_ENV"] });
const cache = cacheCtx();
delete process.env.RFC1303_TEST_ENV; // process.env unset → workpieceEnv channel decides
const envA = makeContext(tmpDir, cache, false, false, new Map([["RFC1303_TEST_ENV", "a"]]));
await executeRegisteredCommand(cmd, envA, []);
const envB = makeContext(tmpDir, cache, false, false, new Map([["RFC1303_TEST_ENV", "b"]]));
const miss = await executeRegisteredCommand(cmd, envB, []);
expect(miss.cached).toBeFalsy();
expect(executions.count).toBe(2);
// process.env wins over workpieceEnv — same effective value replays.
vi.stubEnv("RFC1303_TEST_ENV", "b");
const envStale = makeContext(
tmpDir,
cache,
false,
false,
new Map([["RFC1303_TEST_ENV", "zzz"]]),
);
const hit = await executeRegisteredCommand(cmd, envStale, []);
expect(hit.cached).toBe(true);
expect(executions.count).toBe(2);
vi.unstubAllEnvs();
});
test("RFC-1303: envInputs order does not fragment the key; undeclared env changes do not", async () => {
const executions = { count: 0 };
const cmdA = makeCommand(executions, { envInputs: ["RFC1303_B", "RFC1303_A"] });
const cmdB = makeCommand(executions, { envInputs: ["RFC1303_A", "RFC1303_B"] });
const cache = cacheCtx();
vi.stubEnv("RFC1303_A", "1");
vi.stubEnv("RFC1303_B", "2");
await executeRegisteredCommand(cmdA, makeContext(tmpDir, cache), []);
const hit = await executeRegisteredCommand(cmdB, makeContext(tmpDir, cache), []);
expect(hit.cached).toBe(true);
expect(executions.count).toBe(1);
// An env var the command does not declare must not touch the key.
vi.stubEnv("RFC1303_UNDECLARED", "x");
const still = await executeRegisteredCommand(cmdA, makeContext(tmpDir, cache), []);
expect(still.cached).toBe(true);
expect(executions.count).toBe(1);
vi.unstubAllEnvs();
});
test("--dry-run neither reads nor writes the cache", async () => {
const executions = { count: 0 };
const cmd = makeCommand(executions);
const cache = cacheCtx();
await executeRegisteredCommand(cmd, makeContext(tmpDir, cache), []);
const dry = await executeRegisteredCommand(cmd, makeContext(tmpDir, cache, false, true), []);
const after = await executeRegisteredCommand(cmd, makeContext(tmpDir, cache), []);
expect(executions.count).toBe(2);
expect(dry.cached).toBeFalsy();
expect(after.cached).toBe(true);
});
});