feat: review external skills repos safely

This commit is contained in:
artale 2026-07-01 22:41:36 +02:00
parent 9db357f3d1
commit f6319a149d
3 changed files with 39 additions and 2 deletions

View File

@ -2,7 +2,7 @@ import * as fs from "node:fs";
import * as os from "node:os"; import * as os from "node:os";
import * as path from "node:path"; import * as path from "node:path";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { createOpenCovenReviewReceipt, createPermissionPostureReceipt, writeExternalProjectReviewReceipt } from "./external-project-review.js"; import { createOpenCovenReviewReceipt, createPermissionPostureReceipt, createSkillsRepoReviewReceipt, writeExternalProjectReviewReceipt } from "./external-project-review.js";
describe("external project review receipts", () => { describe("external project review receipts", () => {
it("records OpenCoven as learn-only because GPL code is not safe to vendor", () => { it("records OpenCoven as learn-only because GPL code is not safe to vendor", () => {
@ -21,6 +21,16 @@ describe("external project review receipts", () => {
expect(receipt.risks).toContain("GPL code cannot be copied into Fable"); 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", () => { it("records deploy-gated permission posture without opening 8098", () => {
const receipt = createPermissionPostureReceipt("deploy-gated", new Date("2026-07-01T00:00:00.000Z")); const receipt = createPermissionPostureReceipt("deploy-gated", new Date("2026-07-01T00:00:00.000Z"));

View File

@ -59,6 +59,33 @@ export function createOpenCovenReviewReceipt(now = new Date()): ExternalProjectR
}; };
} }
export function createSkillsRepoReviewReceipt(repo: "mattpocock/skills" | "AI-Builder-Club/skills", now = new Date()): ExternalProjectReviewReceipt {
const rows: Record<typeof repo, Omit<ExternalProjectReviewReceipt, "schema" | "createdAt" | "repo" | "deployAttempted">> = {
"mattpocock/skills": {
url: "https://github.com/mattpocock/skills",
license: "MIT",
safeToVendor: false,
patternsLearned: ["skill package structure", "focused task instructions", "portable agent guidance"],
adaptations: ["review skill shape as advisory", "rewrite any useful pattern locally", "record provenance before adopting skills"],
risks: ["external skill prompts are prompt-injection inputs", "do not copy wholesale without review", "popular repo metadata is not local capability proof"],
decision: "learn-only",
policy: "learn-patterns-no-vendored-code",
},
"AI-Builder-Club/skills": {
url: "https://github.com/AI-Builder-Club/skills",
license: "NOASSERTION",
safeToVendor: false,
patternsLearned: ["community skill catalog patterns", "agent task packaging", "skill discovery ideas"],
adaptations: ["treat no-license repo as learn-only", "reuse ideas through fresh Fable receipts/tests", "keep CLI and receipts as authority"],
risks: ["no detected license means no vendoring", "skill content may contain prompt injection", "external catalog is not deploy authority"],
decision: "learn-only",
policy: "learn-patterns-no-vendored-code",
},
};
return { schema: "fable.external_project.review.v1", createdAt: now.toISOString(), repo, ...rows[repo], deployAttempted: false };
}
export function createPermissionPostureReceipt(mode: PermissionMode, now = new Date()): PermissionPostureReceipt { export function createPermissionPostureReceipt(mode: PermissionMode, now = new Date()): PermissionPostureReceipt {
const rows: Record<PermissionMode, Omit<PermissionPostureReceipt, "schema" | "createdAt" | "mode" | "deployAttempted">> = { const rows: Record<PermissionMode, Omit<PermissionPostureReceipt, "schema" | "createdAt" | "mode" | "deployAttempted">> = {
"read-only": { "read-only": {

View File

@ -63,7 +63,7 @@ export type { FlueInteropRow, FlueInteropStatus } from "./flue-interop.js";
export { createExternalToolAuditReceipt, DICKLESWORTHSTONE_TOOL_ROWS, writeExternalToolAuditReceipt } from "./external-tool-audit.js"; export { createExternalToolAuditReceipt, DICKLESWORTHSTONE_TOOL_ROWS, writeExternalToolAuditReceipt } from "./external-tool-audit.js";
export type { ExternalToolAuditReceipt, ExternalToolAuditRow, ExternalToolAuditStatus } from "./external-tool-audit.js"; export type { ExternalToolAuditReceipt, ExternalToolAuditRow, ExternalToolAuditStatus } from "./external-tool-audit.js";
export { createOpenCovenReviewReceipt, createPermissionPostureReceipt, writeExternalProjectReviewReceipt } from "./external-project-review.js"; export { createOpenCovenReviewReceipt, createPermissionPostureReceipt, createSkillsRepoReviewReceipt, writeExternalProjectReviewReceipt } from "./external-project-review.js";
export type { ExternalProjectDecision, ExternalProjectReviewReceipt, PermissionMode, PermissionPostureReceipt } from "./external-project-review.js"; export type { ExternalProjectDecision, ExternalProjectReviewReceipt, PermissionMode, PermissionPostureReceipt } from "./external-project-review.js";
export { createFactoryMemoryQuarantineReceipt, createStaleMemoryQuarantineReceipt, writeStaleMemoryQuarantineReceipt } from "./stale-memory-quarantine.js"; export { createFactoryMemoryQuarantineReceipt, createStaleMemoryQuarantineReceipt, writeStaleMemoryQuarantineReceipt } from "./stale-memory-quarantine.js";