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 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 }); }); });