feat(nono): establish dynamic permissions baseline
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user