feat(nono): establish dynamic permissions baseline

This commit is contained in:
jb
2026-08-26 23:52:56 -04:00
parent aa50f0fd16
commit ab49cc5610
7 changed files with 386 additions and 3 deletions
+62
View File
@@ -10,8 +10,14 @@ import {
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
import { buildPlan, run } from "./pi-links.mjs";
const repositoryRoot = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"..",
);
async function writeJson(file, value) {
await mkdir(path.dirname(file), { recursive: true });
await writeFile(file, `${JSON.stringify(value, null, 2)}\n`);
@@ -205,6 +211,62 @@ test("prune removes only stale links whose targets are inside the repository", a
assert.equal(await readlink(external), path.join(options.root, "outside"));
});
test("removed update exposure is prunable without touching unrelated extension links", async () => {
const options = await fixture();
const updateSource = path.join(options.repoRoot, "extensions", "update");
const externalSource = path.join(options.root, "external-update");
const extensionsDir = path.join(options.agentDir, "extensions");
const updateLink = path.join(extensionsDir, "update");
const externalLink = path.join(extensionsDir, "external");
await writeExtension(updateSource);
await writeExtension(externalSource);
await mkdir(extensionsDir, { recursive: true });
await symlink(updateSource, updateLink);
await symlink(externalSource, externalLink);
const plan = await buildPlan(options);
assert.ok(
plan.operations.some(
(operation) =>
operation.type === "remove-link" &&
operation.destination === updateLink,
),
);
assert.ok(
!plan.operations.some(
(operation) =>
operation.type === "remove-link" &&
operation.destination === externalLink,
),
);
});
test("normal exposure and package evidence retain the Slice 0 security boundary", async () => {
const links = JSON.parse(
await readFile(path.join(repositoryRoot, "config", "pi-links.json"), "utf8"),
);
assert.equal(links.extensions.globalSymlinks.update, undefined);
assert.equal(links.extensions.globalSymlinks["pi-workflow"], undefined);
const lock = JSON.parse(
await readFile(
path.join(repositoryRoot, "config", "pi-package-lock.json"),
"utf8",
),
);
assert.equal(lock.packages.length, 9);
assert.ok(
lock.packages.every((entry) =>
["locked", "blocked"].includes(entry.status),
),
);
assert.ok(lock.extensions.some((entry) => entry.name === "lm-studio"));
assert.equal(
lock.extensions.find((entry) => entry.name === "update")?.status,
"blocked",
);
});
test("duplicate declared skill names fail before planning changes", async () => {
const options = await fixture();
await writeSkill(