fable-agent/src/fable5/external-project-review.tes...

75 lines
3.5 KiB
TypeScript

import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { describe, expect, it } from "vitest";
import { createOpenCovenReviewReceipt, createPermissionPostureReceipt, createSkillsRepoReviewReceipt, writeExternalProjectReviewReceipt } from "./external-project-review.js";
describe("external project review receipts", () => {
it("records OpenCoven as learn-only because GPL code is not safe to vendor", () => {
const receipt = createOpenCovenReviewReceipt(new Date("2026-07-01T00:00:00.000Z"));
expect(receipt).toMatchObject({
schema: "fable.external_project.review.v1",
repo: "OpenCoven/coven-code",
license: "GPL-3.0",
safeToVendor: false,
decision: "learn-only",
policy: "learn-patterns-no-vendored-code",
deployAttempted: false,
});
expect(receipt.patternsLearned).toContain("explicit permission modes");
expect(receipt.risks).toContain("GPL code cannot be copied into Fable");
});
it("records external skills repos as learn-only", () => {
const matt = createSkillsRepoReviewReceipt("mattpocock/skills", new Date("2026-07-01T00:00:00.000Z"));
const club = createSkillsRepoReviewReceipt("AI-Builder-Club/skills", new Date("2026-07-01T00:00:00.000Z"));
expect(matt).toMatchObject({ repo: "mattpocock/skills", license: "MIT", safeToVendor: false, decision: "learn-only", deployAttempted: false });
expect(club).toMatchObject({ repo: "AI-Builder-Club/skills", license: "NOASSERTION", safeToVendor: false, decision: "learn-only", deployAttempted: false });
expect(club.risks).toContain("no detected license means no vendoring");
expect(matt.risks).toContain("external skill prompts are prompt-injection inputs");
});
it("records ECC as learn-only external harness research", () => {
const receipt = createSkillsRepoReviewReceipt("affaan-m/ECC", new Date("2026-07-01T00:00:00.000Z"));
expect(receipt).toMatchObject({ repo: "affaan-m/ECC", license: "MIT", safeToVendor: false, decision: "learn-only", deployAttempted: false });
expect(receipt.patternsLearned).toContain("agent harness optimization patterns");
expect(receipt.risks).toContain("optimization claims need local receipts");
});
it("records deploy-gated permission posture without opening 8098", () => {
const receipt = createPermissionPostureReceipt("deploy-gated", new Date("2026-07-01T00:00:00.000Z"));
expect(receipt).toMatchObject({
schema: "fable.permission_posture.receipt.v1",
mode: "deploy-gated",
approvalRequired: true,
decision: "ready",
deployAttempted: false,
});
expect(receipt.allowed[0]).toContain("8099/deploy");
expect(receipt.blocked).toContain("8098/deploy-webhook");
expect(receipt.blocked).toContain("memory-only deploy claims");
});
it("keeps read-only mode non-mutating", () => {
const receipt = createPermissionPostureReceipt("read-only");
expect(receipt.allowed).toContain("emit non-mutating receipts");
expect(receipt.blocked).toContain("deploy");
expect(receipt.approvalRequired).toBe(false);
});
it("writes receipts", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "external-project-review-"));
const file = path.join(dir, "receipt.json");
writeExternalProjectReviewReceipt(file, createOpenCovenReviewReceipt());
expect(JSON.parse(fs.readFileSync(file, "utf-8"))).toMatchObject({ schema: "fable.external_project.review.v1" });
fs.rmSync(dir, { recursive: true, force: true });
});
});