/* Unit tests for execute pipeline closed mission covering isClosedWorkpiece; writePatternToCheckPath; mutatingStepRunsOnClosedWorkpiece. 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 execute suite's contract for test-file scaffold coverage. */ // @ai-invariant fixtures must not couple to kernel internals beyond the public surface under test import { test, expect, describe, beforeEach, afterEach } from "vitest"; import { execSync } from "node:child_process"; import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs"; import { join } from "node:path"; import { isClosedWorkpiece, writePatternToCheckPath, mutatingStepRunsOnClosedWorkpiece, mutatingCommandRunsOnClosedWorkpiece, } from "../closed-workpiece.ts"; import { executeRegisteredCommand } from "../execute-command.ts"; import { createKernelLogger } from "../../logger.ts"; import { createDefaultIO } from "@warpgogol/werkstatt-shared/kernel"; import type { DiscoveredSiteWorkspace, KernelCommandDefinition, KernelRuntimeContext, } from "@warpgogol/werkstatt-shared/kernel"; // ADR-0085: closed-mission workpiece — mutating pipeline steps become no-ops // unless every declared write resolves to a gitignored path inside the site. // ADR-0087: the same guard is enforced in executeRegisteredCommand so direct // executeKernelCommand calls cannot bypass it. function makeCommand( name: string, mutatesState: boolean | undefined, writes?: string[], ): KernelCommandDefinition { return { name, description: "test", scope: "app", mutatesState, writes, execute: async () => ({ exitCode: 0, ok: true, summary: "ok" }), } as KernelCommandDefinition; } describe("isClosedWorkpiece", () => { let tmpDir: string; beforeEach(() => { tmpDir = mkdtempSync(join(process.cwd(), "tmp-closed-wp-")); }); afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); test("returns false when .closed sentinel is absent", async () => { expect(await isClosedWorkpiece(tmpDir, createDefaultIO().io)).toBe(false); }); test("returns true when .closed sentinel exists", async () => { writeFileSync(join(tmpDir, ".closed"), "closed\n"); expect(await isClosedWorkpiece(tmpDir, createDefaultIO().io)).toBe(true); }); }); describe("writePatternToCheckPath", () => { test("strips / prefix from concrete paths", () => { expect(writePatternToCheckPath("/package.json", "site")).toBe("package.json"); }); test("cuts at the first glob character so parent-dir ignores apply", () => { expect(writePatternToCheckPath("/dist/client/**", "site")).toBe("dist/client"); expect(writePatternToCheckPath("/public/**/*.md", "site")).toBe("public"); }); test("replaces {app} and other placeholders with literals", () => { expect(writePatternToCheckPath("/public/{app}-indexnow.txt", "warpgogol")).toBe( "public/warpgogol-indexnow.txt", ); expect(writePatternToCheckPath("/src/pages/api/{route}.ts", "site")).toBe( "src/pages/api/x.ts", ); }); test("returns null for non- patterns (outside the site directory)", () => { expect(writePatternToCheckPath("systems/{system}/bordbuch.json", "site")).toBeNull(); expect(writePatternToCheckPath("uni.registry.yaml", "site")).toBeNull(); expect(writePatternToCheckPath("/public/manifest.json", "site")).toBeNull(); }); }); describe("mutatingStepRunsOnClosedWorkpiece", () => { const ignored = new Set(["dist/.well-known/cosmic-passport.json", "dist/client", ".cache/pdf"]); test("runs when every declared write is gitignored (dist-only mutator)", () => { const cmd = makeCommand("passport.emit", true, ["/dist/.well-known/cosmic-passport.json"]); expect( mutatingStepRunsOnClosedWorkpiece(cmd, "site", ignored), "dist-only mutators must run — release.prepare rebuild path needs them", ).toBe(true); }); test("skips when any declared write is tracked", () => { const cmd = makeCommand("config.regenerate", true, [ "/package.json", "/astro.config.mjs", ]); expect(mutatingStepRunsOnClosedWorkpiece(cmd, "site", ignored)).toBe(false); }); test("skips when writes mix gitignored and tracked paths", () => { const cmd = makeCommand("image.variants.generate", true, [ "/src/image-variants.generated.yaml", "/public/_img/**", ]); expect(mutatingStepRunsOnClosedWorkpiece(cmd, "site", ignored)).toBe(false); }); test("skips when writes are undeclared (empty writes cannot prove confinement)", () => { const cmd = makeCommand("bordbuch.commit", true, []); expect(mutatingStepRunsOnClosedWorkpiece(cmd, "site", ignored)).toBe(false); }); test("skips when writes resolve outside the site directory", () => { const cmd = makeCommand("uni.registry.build", true, ["uni.registry.yaml"]); expect(mutatingStepRunsOnClosedWorkpiece(cmd, "site", ignored)).toBe(false); }); test("runs everything when git tracking is absent (null ignoredPaths)", () => { const cmd = makeCommand("config.regenerate", true, ["/package.json"]); expect( mutatingStepRunsOnClosedWorkpiece(cmd, "site", null), "without a git repo no write can produce committable churn", ).toBe(true); }); }); describe("collectGitignoredPaths integration (real git repo)", () => { let tmpDir: string; beforeEach(() => { tmpDir = mkdtempSync(join(process.cwd(), "tmp-closed-git-")); execSync("git init -q", { cwd: tmpDir }); writeFileSync(join(tmpDir, ".gitignore"), "dist/\n.cache/\npublic/_img/\n"); mkdirSync(join(tmpDir, "src"), { recursive: true }); }); afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); test("gitignored write prefixes are detected via git check-ignore", () => { // Reproduce the pipeline-run batch: dist-only mutator runs, tracked writer skips. const distOnly = makeCommand("passport.emit", true, [ "/dist/.well-known/cosmic-passport.json", ]); const tracked = makeCommand("config.regenerate", true, ["/package.json"]); const candidates = [distOnly, tracked].flatMap((c) => (c.writes ?? []) .map((w) => writePatternToCheckPath(w, "site")) .filter((p): p is string => p !== null), ); const output = execSync("git check-ignore --stdin", { cwd: tmpDir, input: `${candidates.join("\n")}\n`, encoding: "utf-8", }); const ignored = new Set( output .split("\n") .map((l) => l.trim()) .filter(Boolean), ); expect(mutatingStepRunsOnClosedWorkpiece(distOnly, "site", ignored)).toBe(true); expect(mutatingStepRunsOnClosedWorkpiece(tracked, "site", ignored)).toBe(false); }); test("mutatingCommandRunsOnClosedWorkpiece resolves gitignore internally", async () => { const site = { name: "site", directory: tmpDir } as DiscoveredSiteWorkspace; const distOnly = makeCommand("passport.emit", true, [ "/dist/.well-known/cosmic-passport.json", ]); const tracked = makeCommand("config.regenerate", true, ["/package.json"]); const io = createDefaultIO().io; expect(await mutatingCommandRunsOnClosedWorkpiece(distOnly, site, io)).toBe(true); expect(await mutatingCommandRunsOnClosedWorkpiece(tracked, site, io)).toBe(false); }); }); describe("executeRegisteredCommand closed-workpiece guard (ADR-0087)", () => { let tmpDir: string; beforeEach(() => { tmpDir = mkdtempSync(join(process.cwd(), "tmp-closed-exec-")); execSync("git init -q", { cwd: tmpDir }); writeFileSync(join(tmpDir, ".gitignore"), "dist/\n"); writeFileSync(join(tmpDir, ".closed"), "closed\n"); }); afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); function makeContext(siteDir: string): KernelRuntimeContext { const { io, intents } = createDefaultIO(); return { logger: createKernelLogger(), workspaceRoot: siteDir, site: { name: "site", directory: siteDir } as DiscoveredSiteWorkspace, io, fileIntents: intents, } as unknown as KernelRuntimeContext; } test("mutating command with tracked writes is skipped without executing", async () => { let executed = false; const cmd = makeCommand("config.regenerate", true, ["/package.json"]); cmd.execute = async () => { executed = true; return { exitCode: 0, ok: true, summary: "ran" }; }; const report = await executeRegisteredCommand(cmd, makeContext(tmpDir), []); expect(executed, "mutating command must not run on a closed workpiece").toBe(false); expect(report.summary).toBe("Skipped: closed-mission"); expect(report.ok).toBe(true); }); test("read-only command is not affected by the closed-workpiece guard", async () => { // mutatesState: false bypasses the guard entirely — the command would // proceed to flag parsing/execution; we only assert the guard did not // produce a closed-mission skip report. const cmd = makeCommand("content.validate", false, []); cmd.execute = async () => ({ exitCode: 0, ok: true, summary: "validated" }); const report = await executeRegisteredCommand(cmd, makeContext(tmpDir), []); expect(report.summary).not.toBe("Skipped: closed-mission"); }); test("mutating command runs when the workpiece is not closed", async () => { rmSync(join(tmpDir, ".closed")); let executed = false; const cmd = makeCommand("config.regenerate", true, ["/package.json"]); cmd.execute = async () => { executed = true; return { exitCode: 0, ok: true, summary: "ran" }; }; const report = await executeRegisteredCommand(cmd, makeContext(tmpDir), []); expect(executed).toBe(true); expect(report.summary).toBe("ran"); }); }); // RFC-1260 (COPS-07, generalized): cache clones resolve through the // site-workspace resolver but are read-only targets — mutating commands are // skipped at the executor, mirroring the ADR-0085 closed-workpiece guard. describe("executeRegisteredCommand cache-clone guard", () => { let tmpDir: string; beforeEach(() => { tmpDir = mkdtempSync(join(process.cwd(), "tmp-clone-exec-")); }); afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); function makeCloneContext(): KernelRuntimeContext { const { io, intents } = createDefaultIO(); return { logger: createKernelLogger(), workspaceRoot: tmpDir, site: { name: "site", directory: tmpDir, source: "cache-clone", } as DiscoveredSiteWorkspace, io, fileIntents: intents, } as unknown as KernelRuntimeContext; } test("mutating command is skipped without executing", async () => { let executed = false; const cmd = makeCommand("agents.generate", true, ["/AGENTS.md"]); cmd.execute = async () => { executed = true; return { exitCode: 0, ok: true, summary: "ran" }; }; const report = await executeRegisteredCommand(cmd, makeCloneContext(), []); expect(executed, "mutating command must not run on a cache clone").toBe(false); expect(report.summary).toBe("Skipped: cache-clone"); expect(report.ok).toBe(true); }); test("read-only command is not affected by the cache-clone guard", async () => { const cmd = makeCommand("content.get", false, []); cmd.execute = async () => ({ exitCode: 0, ok: true, summary: "read" }); const report = await executeRegisteredCommand(cmd, makeCloneContext(), []); expect(report.summary).not.toBe("Skipped: cache-clone"); }); test("mutating flag on a read-declared command is refused on a cache clone", async () => { let executed = false; const cmd = makeCommand("typography.check", false, []); cmd.mutatingFlags = ["fix"]; cmd.flags = { fix: { kind: "boolean", description: "Apply fixes in place." }, }; cmd.execute = async () => { executed = true; return { exitCode: 0, ok: true, summary: "fixed" }; }; const report = await executeRegisteredCommand(cmd, makeCloneContext(), ["--fix"]); expect(executed, "flag-upgraded mutator must not run on a cache clone").toBe(false); expect(report.summary).toBe("Skipped: cache-clone"); }); test("the same read-declared command without the flag runs on a cache clone", async () => { const cmd = makeCommand("typography.check", false, []); cmd.mutatingFlags = ["fix"]; cmd.flags = { fix: { kind: "boolean", description: "Apply fixes in place." }, }; cmd.execute = async () => ({ exitCode: 0, ok: true, summary: "checked" }); const report = await executeRegisteredCommand(cmd, makeCloneContext(), []); expect(report.summary).not.toBe("Skipped: cache-clone"); }); });