/*
RFC-1210: unit tests for remediation.signal.ingest — trust boundary (SIG-02),
routing (auto-mission / action-pack / suppressed-autonomy-off / deduped /
rate-capped), and dedup ledger behavior.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-1210: initial tests — all ingest routes against a tmp workspace fixture with injected openMission.
- RFC-1210: closed-loop signal ingest to bounded missions (RFC-1210)
*/
import { test, expect } from "vitest";
import { mkdtemp, rm, mkdir, writeFile, readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
runRemediationSignalIngest,
type IngestDeps,
type SignalIngestData,
} from "../remediation-signal-ingest.ts";
import { buildActualState } from "../../runtime/reconciler.ts";
import type {
KernelCommandInput,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-shared/kernel";
import type { ModuleExport } from "@warpgogol/werkstatt-shared/kernel";
import { createDefaultIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
import { stringify as stringifyYaml } from "yaml";
const SITE = "acme";
function makeContext(workspaceRoot: string): KernelRuntimeContext {
const mod: ModuleExport = {
name: "test-module",
version: "1.0.0",
declarations: [],
commands: [],
pipelines: [],
};
return {
io: createDefaultIO().io,
workspaceRoot,
actualState: buildActualState([mod]),
dryRun: false,
logger: { info: () => {}, warn: () => {}, error: () => {} },
} as unknown as KernelRuntimeContext;
}
function input(flags: Record): KernelCommandInput {
return { argv: [], flags };
}
async function seedWorkspace(
opts: { autonomy?: "off" | "bounded"; signalSources?: string[] } = {},
): Promise {
const root = await mkdtemp(join(tmpdir(), "rem-ingest-"));
const workspaceRoot = join(root, "werkstatt");
const cacheClone = join(root, "systems-cache", SITE);
await mkdir(join(workspaceRoot, "missions"), { recursive: true });
await mkdir(cacheClone, { recursive: true });
await writeFile(
join(cacheClone, "system-state.yaml"),
stringifyYaml({
schemaVersion: "1.0.0",
systemId: SITE,
currentMission: null,
lastRelease: null,
lastPropagated: {},
accessPin: null,
passportRequired: false,
ownershipRequired: false,
remediation: {
autonomy: opts.autonomy ?? "bounded",
signalSources: opts.signalSources ?? ["operator", "observability-bridge"],
},
}),
);
return workspaceRoot;
}
/** Injected mission.open — creates a real open mission manifest on disk. */
function fakeOpenMission(workspaceRoot: string): IngestDeps["openMission"] & {
calls: string[];
} {
const calls: string[] = [];
const fn = async (
_root: string,
systemId: string,
brief: string,
): Promise<{ exitCode: number; missionId?: string; summary?: string }> => {
calls.push(brief);
const missionId = `${systemId}-m000001`;
const dir = join(workspaceRoot, "missions", missionId);
await mkdir(dir, { recursive: true });
await writeFile(
join(dir, "mission.yaml"),
stringifyYaml({
schemaVersion: "1.0.0",
missionId,
systemId,
state: "open",
brief,
openedAt: new Date().toISOString(),
openedBy: "remediation:test",
closedAt: null,
closedBy: null,
pinAtOpen: "6.0.0",
materializedAt: null,
reconciledAt: null,
migratedAt: null,
releaseId: null,
rfcId: null,
operationId: "op-test",
}),
);
return { exitCode: 0, missionId };
};
fn.calls = calls;
return fn;
}
test("ingest: undeclared source exits 1 with SIG-02 and opens no mission", async () => {
const workspaceRoot = await seedWorkspace();
try {
const openMission = fakeOpenMission(workspaceRoot);
const result = await runRemediationSignalIngest(
input({ site: SITE, class: "typography", source: "rogue-bot" }),
makeContext(workspaceRoot),
{ openMission },
);
expect(result.exitCode).toBe(1);
const diags = (result.data as unknown as { diagnostics: { ruleId: string }[] }).diagnostics;
expect(diags[0].ruleId).toBe("SIG-02");
expect(openMission.calls).toHaveLength(0);
} finally {
await rm(join(workspaceRoot, ".."), { recursive: true });
}
});
test("ingest: unknown class exits 1 with SIG-01", async () => {
const workspaceRoot = await seedWorkspace();
try {
const result = await runRemediationSignalIngest(
input({ site: SITE, class: "invented-class", source: "operator" }),
makeContext(workspaceRoot),
{ openMission: fakeOpenMission(workspaceRoot) },
);
expect(result.exitCode).toBe(1);
const diags = (result.data as unknown as { diagnostics: { ruleId: string }[] }).diagnostics;
expect(diags[0].ruleId).toBe("SIG-01");
} finally {
await rm(join(workspaceRoot, ".."), { recursive: true });
}
});
test("ingest: autonomy off suppresses every class (AC-5)", async () => {
const workspaceRoot = await seedWorkspace({ autonomy: "off" });
try {
const openMission = fakeOpenMission(workspaceRoot);
const result = await runRemediationSignalIngest(
input({ site: SITE, class: "typography", source: "operator" }),
makeContext(workspaceRoot),
{ openMission },
);
expect(result.exitCode).toBe(0);
const data = result.data as SignalIngestData;
expect(data.route).toBe("suppressed-autonomy-off");
expect(openMission.calls).toHaveLength(0);
// signal is still journaled — autonomy-off is visible, never silent
const ledger = await readFile(
join(workspaceRoot, "..", "systems-cache", SITE, "operations", "remediation-signals.jsonl"),
"utf8",
);
expect(ledger).toContain("suppressed-autonomy-off");
} finally {
await rm(join(workspaceRoot, ".."), { recursive: true });
}
});
test("ingest: non-autoFixable class routes to action-pack (AC-3)", async () => {
const workspaceRoot = await seedWorkspace();
try {
const openMission = fakeOpenMission(workspaceRoot);
const result = await runRemediationSignalIngest(
input({ site: SITE, class: "content-regression", source: "operator" }),
makeContext(workspaceRoot),
{ openMission },
);
expect(result.exitCode).toBe(0);
expect((result.data as SignalIngestData).route).toBe("action-pack");
expect(openMission.calls).toHaveLength(0);
} finally {
await rm(join(workspaceRoot, ".."), { recursive: true });
}
});
test("ingest: autoFixable + bounded opens exactly one mission (AC-2, AC-8)", async () => {
const workspaceRoot = await seedWorkspace();
try {
const openMission = fakeOpenMission(workspaceRoot);
const result = await runRemediationSignalIngest(
input({
site: SITE,
class: "typography",
source: "observability-bridge",
evidence: "probes/run-42",
}),
makeContext(workspaceRoot),
{ openMission },
);
expect(result.exitCode).toBe(0);
const data = result.data as SignalIngestData;
expect(data.route).toBe("auto-mission");
expect(data.missionId).toBe(`${SITE}-m000001`);
expect(openMission.calls).toHaveLength(1);
expect(openMission.calls[0]).toContain("typography.fix");
// signal evidence file inside the mission dir
const evidence = JSON.parse(
await readFile(
join(workspaceRoot, "missions", `${SITE}-m000001`, "remediation-signal.json"),
"utf8",
),
);
expect(evidence.evidenceRefs).toEqual(["probes/run-42"]);
} finally {
await rm(join(workspaceRoot, ".."), { recursive: true });
}
});
test("ingest: second signal for open (site,class) dedups (AC-4)", async () => {
const workspaceRoot = await seedWorkspace();
try {
const openMission = fakeOpenMission(workspaceRoot);
const ctx = makeContext(workspaceRoot);
const first = await runRemediationSignalIngest(
input({ site: SITE, class: "typography", source: "operator" }),
ctx,
{ openMission },
);
expect((first.data as SignalIngestData).route).toBe("auto-mission");
const second = await runRemediationSignalIngest(
input({ site: SITE, class: "typography", source: "operator" }),
ctx,
{ openMission },
);
expect((second.data as SignalIngestData).route).toBe("deduped");
expect(openMission.calls).toHaveLength(1);
} finally {
await rm(join(workspaceRoot, ".."), { recursive: true });
}
});
test("ingest: closed prior mission does not dedup a new signal", async () => {
const workspaceRoot = await seedWorkspace();
try {
const openMission = fakeOpenMission(workspaceRoot);
const ctx = makeContext(workspaceRoot);
await runRemediationSignalIngest(
input({ site: SITE, class: "typography", source: "operator" }),
ctx,
{ openMission },
);
// close the fake mission
const manifestPath = join(
workspaceRoot,
"missions",
`${SITE}-m000001`,
"mission.yaml",
);
const { parse: parseYaml } = await import("yaml");
const manifest = parseYaml(await readFile(manifestPath, "utf8"));
manifest.state = "closed";
manifest.closedAt = new Date().toISOString();
await writeFile(manifestPath, stringifyYaml(manifest));
const second = await runRemediationSignalIngest(
input({ site: SITE, class: "typography", source: "operator" }),
ctx,
{ openMission },
);
// rate cap for typography is 2/day — second auto-mission is allowed
expect((second.data as SignalIngestData).route).toBe("auto-mission");
expect(openMission.calls).toHaveLength(2);
} finally {
await rm(join(workspaceRoot, ".."), { recursive: true });
}
});
test("ingest: rate cap degrades to rate-capped, never silently drops", async () => {
const workspaceRoot = await seedWorkspace();
try {
const openMission = fakeOpenMission(workspaceRoot);
const ctx = makeContext(workspaceRoot);
// generated-drift cap = 1/day
await runRemediationSignalIngest(
input({ site: SITE, class: "generated-drift", source: "operator" }),
ctx,
{ openMission },
);
// close the mission so dedup doesn't fire first
const manifestPath = join(
workspaceRoot,
"missions",
`${SITE}-m000001`,
"mission.yaml",
);
const { parse: parseYaml } = await import("yaml");
const manifest = parseYaml(await readFile(manifestPath, "utf8"));
manifest.state = "closed";
await writeFile(manifestPath, stringifyYaml(manifest));
const capped = await runRemediationSignalIngest(
input({ site: SITE, class: "generated-drift", source: "operator" }),
ctx,
{ openMission },
);
expect(capped.exitCode).toBe(0);
expect((capped.data as SignalIngestData).route).toBe("rate-capped");
const ledger = await readFile(
join(workspaceRoot, "..", "systems-cache", SITE, "operations", "remediation-signals.jsonl"),
"utf8",
);
expect(ledger).toContain("rate-capped");
} finally {
await rm(join(workspaceRoot, ".."), { recursive: true });
}
});