import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { test, expect } from "vitest";
import { runRfcValidate } from "@warpgogol/forge/os/rfc";
import type {
KernelCommandInput,
KernelLogger,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-shared/kernel";
import { createDefaultIO } from "@warpgogol/werkstatt-shared/kernel";
/*
Verify the lifecycle and referential validation rules added by RFC-0153:
bidirectional supersession (V-12), status/date coupling (V-16), strict
supersededBy (V-17), and related[] referential integrity (V-18).
- Do not re-test V-01..V-11, V-13..V-15 (covered implicitly elsewhere).
- Fixtures stay inline and minimal so each case states its own setup.
- New test file added by RFC-0153 to cover the new validator rules.
*/
// @ai-invariant fixtures must not couple to internals beyond the public surface under test
const noopLogger: KernelLogger = {
section() {},
info() {},
warn() {},
error() {},
success() {},
event() {},
getEvents() {
return [];
},
};
function ctx(workspaceRoot: string): KernelRuntimeContext {
return {
workspaceRoot,
siteExplicit: false,
logger: noopLogger,
dryRun: false,
outputFormat: "json",
io: createDefaultIO().io,
actualState: undefined as never,
};
}
const input: KernelCommandInput = { argv: [], flags: {} };
interface RfcOpts {
status?: string;
implementedAt?: string;
closedAt?: string;
supersedes?: string[];
supersededBy?: string;
related?: string[];
}
function rfc(id: string, o: RfcOpts = {}): string {
const list = (key: string, arr?: string[]) =>
arr && arr.length ? `${key}:\n${arr.map((x) => ` - ${x}`).join("\n")}\n` : `${key}: []\n`;
return (
`---\n` +
`id: ${id}\n` +
`title: "Fixture ${id}"\n` +
`status: ${o.status ?? "draft"}\n` +
`kind: command\n` +
`scope: workspace\n` +
`owners:\n - architecture\n` +
`reviewers: []\n` +
`createdAt: 2026-06-04\n` +
`updatedAt: 2026-06-04\n` +
`implementedAt:${o.implementedAt ? " " + o.implementedAt : ""}\n` +
`closedAt:${o.closedAt ? " " + o.closedAt : ""}\n` +
list("supersedes", o.supersedes) +
`supersededBy:${o.supersededBy ? " " + o.supersededBy : ""}\n` +
list("related", o.related) +
`---\n\n# ${id}: Fixture ${id}\n\n` +
`## Context\nx\n\n## Decision\nx\n\n` +
`## Acceptance criteria\n- [ ] a\n- [ ] b\n- [ ] c\n\n` +
`## Implementation notes for agents\nx\n`
);
}
async function setup(files: Record): Promise {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "rfc-validate-"));
const dir = path.join(root, "docs", "rfcs");
await fs.mkdir(dir, { recursive: true });
for (const [name, content] of Object.entries(files)) {
await fs.mkdir(path.dirname(path.join(dir, name)), { recursive: true });
await fs.writeFile(path.join(dir, name), content, "utf8");
}
return root;
}
async function rulesFor(files: Record, rule: string): Promise {
const root = await setup(files);
try {
const res = await runRfcValidate(input, ctx(root));
return (res.data?.violations ?? []).filter((v) => v.rule === rule).length;
} finally {
await fs.rm(root, { recursive: true, force: true });
}
}
test("V-12 flags a one-directional supersedes edge (missing back-link)", async () => {
const n = await rulesFor(
{
"RFC-9001-a.md": rfc("RFC-9001", { supersedes: ["RFC-9002"] }),
"RFC-9002-b.md": rfc("RFC-9002"), // no supersededBy back-link
},
"V-12",
);
expect(n >= 1).toBeTruthy();
});
test("V-12 is silent for a fully bidirectional supersession", async () => {
const n = await rulesFor(
{
"RFC-9001-a.md": rfc("RFC-9001", { supersedes: ["RFC-9002"] }),
"RFC-9002-b.md": rfc("RFC-9002", {
status: "superseded",
closedAt: "2026-06-04",
supersededBy: "RFC-9001",
}),
},
"V-12",
);
expect(n).toBe(0);
});
test("V-16 flags implemented status with no implementedAt", async () => {
const n = await rulesFor({ "RFC-9003-c.md": rfc("RFC-9003", { status: "implemented" }) }, "V-16");
expect(n >= 1).toBeTruthy();
});
test("V-17 flags supersededBy set with non-superseded status", async () => {
const n = await rulesFor(
{
"RFC-9001-a.md": rfc("RFC-9001", { supersedes: ["RFC-9004"] }),
"RFC-9004-d.md": rfc("RFC-9004", {
status: "implemented",
implementedAt: "2026-06-04",
supersededBy: "RFC-9001",
}),
},
"V-17",
);
expect(n >= 1).toBeTruthy();
});
test("V-18 flags a related ref to a nonexistent RFC", async () => {
const n = await rulesFor({ "RFC-9005-e.md": rfc("RFC-9005", { related: ["RFC-9999"] }) }, "V-18");
expect(n >= 1).toBeTruthy();
});
test("targeted validation finds archived RFC files by basename id", async () => {
const root = await setup({
"archive/implemented/rfc-9006-archived.md": rfc("RFC-9006"),
});
try {
const res = await runRfcValidate({ argv: [], flags: {} }, ctx(root));
expect(res.data?.count).toBe(1);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("V-02 duplicate id message includes cause and fix hint", async () => {
const root = await setup({
"archive/implemented/rfc-9007-archived.md": rfc("RFC-9007"),
"rfc-9007-duplicate.md": rfc("RFC-9007"),
});
try {
const res = await runRfcValidate(input, ctx(root));
const v02 = (res.data?.violations ?? []).filter((v) => v.rule === "V-02");
expect(v02.length).toBeGreaterThanOrEqual(1);
const msg = v02[0]!.message;
expect(msg).toContain("Duplicate id");
expect(msg).toContain("archive");
expect(msg).toContain("rfc.create");
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});