/*
Unit tests for sternsystem validate config resolve covering RFC-1034: STERN-CONFIG-RESOLVE-01.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-1034: initial tests for config resolution warning.
- RFC-1149: migrate ambient filesystem and process IO to the WorkspaceIO port across engine and shared
*/
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
import path from "node:path";
import os from "node:os";
vi.mock("../kernel/runtime/registry.ts", () => ({
loadAppRuntime: vi.fn(),
}));
vi.mock("./registry-io.ts", () => ({
discoverSystems: vi.fn().mockResolvedValue([]),
hasAppsCollision: vi.fn().mockResolvedValue(false),
resolveMirrors: vi.fn().mockReturnValue({ cachePath: "", gitMirrors: [], backupMirrors: [] }),
resolveMirrorPath: vi.fn(),
resolveWorkpiecePath: vi.fn(),
readSystemState: vi.fn().mockResolvedValue({
schemaVersion: "1.0.0",
systemId: "test-system",
currentMission: null,
lastRelease: null,
lastPropagated: {},
accessPin: null,
}),
isGitAccessible: vi.fn().mockReturnValue(true),
readPassport: vi.fn().mockResolvedValue(null),
}));
vi.mock("../bordbuch/bordbuch-io.ts", () => ({
readBordbuch: vi.fn().mockResolvedValue([]),
}));
vi.mock("../mission/env-persist.ts", () => ({
collectEnvFiles: vi.fn().mockResolvedValue([]),
}));
vi.mock("./external-edit-guard.ts", () => ({
evaluateExternalEditGate: vi.fn().mockReturnValue({ violations: [], warnings: [] }),
}));
vi.mock("./external-edit-collector.ts", () => ({
collectExternalEditInputs: vi.fn().mockResolvedValue({}),
bordbuchFileExists: vi.fn().mockResolvedValue(false),
bordbuchPathFor: vi.fn(),
}));
vi.mock("./handover.ts", () => ({
readAuthorization: vi.fn().mockResolvedValue(null),
isAuthorizationExpired: vi.fn().mockReturnValue(false),
}));
vi.mock("./passport.ts", () => ({
verifyPassport: vi.fn(),
buildPassportPayload: vi.fn(),
computePassportHash: vi.fn(),
}));
vi.mock("../schemas/naming-policy.ts", async (importOriginal) => {
const actual =
await importOriginal();
return {
...actual,
hasTldSuffix: vi.fn().mockReturnValue(false),
};
});
import { runSternsystemValidate } from "./sternsystem-validate.ts";
import { discoverSystems, resolveMirrors } from "./registry-io.ts";
import { loadAppRuntime } from "../kernel/runtime/registry.ts";
import { createDefaultIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
const mockDiscoverSystems = vi.mocked(discoverSystems);
const mockResolveMirrors = vi.mocked(resolveMirrors);
const mockLoadAppRuntime = vi.mocked(loadAppRuntime);
let tmpDir: string;
function makeContext(workspaceRoot: string) {
return {
io: createDefaultIO().io,
workspaceRoot,
logger: {
section() {},
info() {},
warn() {},
error() {},
success() {},
},
} as never;
}
beforeEach(() => {
tmpDir = mkdtempSync(path.join(os.tmpdir(), "stern-config-resolve-XXXX-"));
vi.clearAllMocks();
});
afterEach(() => {
rmSync(tmpDir, { recursive: true, force: true });
});
describe("RFC-1034: STERN-CONFIG-RESOLVE-01", () => {
it("emits warning when cache clone kernel.config.ts fails to load", async () => {
const cachePath = path.join(tmpDir, "cache-clone");
mkdirSync(path.join(cachePath, "tools"), { recursive: true });
writeFileSync(path.join(cachePath, "tools", "kernel.config.ts"), "broken content");
mockDiscoverSystems.mockReturnValue({
systems: [
{
id: "test-system",
mirrors: [{ path: cachePath, storageType: "git", branch: "main" }],
owner: "test-owner",
},
],
errors: [],
} as never);
mockResolveMirrors.mockReturnValue({ cachePath, gitMirrors: [], backupMirrors: [] } as never);
mockLoadAppRuntime.mockRejectedValue(
new Error("Cannot find package '@warpgogol/werkstatt-engine'") as never,
);
const result = await runSternsystemValidate({ flags: {} } as never, makeContext(tmpDir));
const data = result.data as { warnings: Array<{ field: string; message: string }> };
const configWarning = data.warnings.find((w) => w.field === "STERN-CONFIG-RESOLVE-01");
expect(configWarning).toBeTruthy();
expect(configWarning!.message).toContain("kernel.config.ts failed to load");
expect(configWarning!.message).toContain("@warpgogol/werkstatt-engine");
});
it("does not emit warning when kernel.config.ts loads successfully", async () => {
const cachePath = path.join(tmpDir, "cache-clone");
mkdirSync(path.join(cachePath, "tools"), { recursive: true });
writeFileSync(path.join(cachePath, "tools", "kernel.config.ts"), "valid content");
mockDiscoverSystems.mockReturnValue({
systems: [
{
id: "test-system",
mirrors: [{ path: cachePath, storageType: "git", branch: "main" }],
owner: "test-owner",
},
],
errors: [],
} as never);
mockResolveMirrors.mockReturnValue({ cachePath, gitMirrors: [], backupMirrors: [] } as never);
mockLoadAppRuntime.mockResolvedValue({ registry: { listCommandNames: () => [] } } as never);
const result = await runSternsystemValidate({ flags: {} } as never, makeContext(tmpDir));
const data = result.data as { warnings: Array<{ field: string; message: string }> };
const configWarning = data.warnings.find((w) => w.field === "STERN-CONFIG-RESOLVE-01");
expect(configWarning).toBeUndefined();
});
it("skips silently when cache clone has no kernel.config.ts", async () => {
const cachePath = path.join(tmpDir, "cache-clone");
mkdirSync(cachePath, { recursive: true });
mockDiscoverSystems.mockReturnValue({
systems: [
{
id: "test-system",
mirrors: [{ path: cachePath, storageType: "git", branch: "main" }],
owner: "test-owner",
},
],
errors: [],
} as never);
mockResolveMirrors.mockReturnValue({ cachePath, gitMirrors: [], backupMirrors: [] } as never);
const result = await runSternsystemValidate({ flags: {} } as never, makeContext(tmpDir));
const data = result.data as { warnings: Array<{ field: string; message: string }> };
const configWarning = data.warnings.find((w) => w.field === "STERN-CONFIG-RESOLVE-01");
expect(configWarning).toBeUndefined();
expect(mockLoadAppRuntime).not.toHaveBeenCalled();
});
it("is non-fatal — STERN-CONFIG-RESOLVE-01 is a warning, not a violation", async () => {
const cachePath = path.join(tmpDir, "cache-clone");
mkdirSync(path.join(cachePath, "tools"), { recursive: true });
writeFileSync(path.join(cachePath, "tools", "kernel.config.ts"), "broken");
mockDiscoverSystems.mockReturnValue({
systems: [
{
id: "test-system",
mirrors: [{ path: cachePath, storageType: "git", branch: "main" }],
owner: "test-owner",
},
],
errors: [],
} as never);
mockResolveMirrors.mockReturnValue({ cachePath, gitMirrors: [], backupMirrors: [] } as never);
mockLoadAppRuntime.mockRejectedValue(new Error("module not found") as never);
const result = await runSternsystemValidate({ flags: {} } as never, makeContext(tmpDir));
const data = result.data as {
warnings: Array<{ field: string; message: string }>;
violations: Array<{ rule: string }>;
};
const configWarning = data.warnings.find((w) => w.field === "STERN-CONFIG-RESOLVE-01");
expect(configWarning).toBeTruthy();
const configViolation = data.violations.find((v) => v.rule === "STERN-CONFIG-RESOLVE-01");
expect(configViolation).toBeUndefined();
});
});