mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
refactor(permission-system): simplify policy and autoreview flow
This commit is contained in:
@@ -245,7 +245,6 @@ describe("loadUnifiedConfig", () => {
|
||||
"*": "ask",
|
||||
read: "allow",
|
||||
bash: { "*": "ask", "git *": "allow" },
|
||||
external_directory: "ask",
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -256,7 +255,6 @@ describe("loadUnifiedConfig", () => {
|
||||
"*": "ask",
|
||||
read: "allow",
|
||||
bash: { "*": "ask", "git *": "allow" },
|
||||
external_directory: "ask",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -401,32 +399,6 @@ describe("loadUnifiedConfig", () => {
|
||||
expect(result.issues.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("parses piInfrastructureReadPaths when a valid string array is present", () => {
|
||||
const configPath = join(tempDir, "config.json");
|
||||
writeFileSync(
|
||||
configPath,
|
||||
JSON.stringify({ piInfrastructureReadPaths: ["/extra/path"] }),
|
||||
);
|
||||
const result = loadUnifiedConfig(configPath);
|
||||
expect(result.config.piInfrastructureReadPaths).toEqual(["/extra/path"]);
|
||||
});
|
||||
|
||||
it("parses piInfrastructureReadPaths as empty array when set to []", () => {
|
||||
const configPath = join(tempDir, "config.json");
|
||||
writeFileSync(
|
||||
configPath,
|
||||
JSON.stringify({ piInfrastructureReadPaths: [] }),
|
||||
);
|
||||
const result = loadUnifiedConfig(configPath);
|
||||
expect(result.config.piInfrastructureReadPaths).toEqual([]);
|
||||
});
|
||||
|
||||
it("omits piInfrastructureReadPaths when absent", () => {
|
||||
const configPath = join(tempDir, "config.json");
|
||||
writeFileSync(configPath, JSON.stringify({ debugLog: false }));
|
||||
const result = loadUnifiedConfig(configPath);
|
||||
expect(result.config).not.toHaveProperty("piInfrastructureReadPaths");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["string", "not-an-array"],
|
||||
@@ -446,7 +418,7 @@ describe("loadUnifiedConfig", () => {
|
||||
});
|
||||
|
||||
describe("mergeUnifiedConfigs", () => {
|
||||
it("deep-merges permission objects so project overrides global per-key", () => {
|
||||
it("deep-merges permission objects so the later config overrides per key", () => {
|
||||
const merged = mergeUnifiedConfigs(
|
||||
{
|
||||
permission: {
|
||||
@@ -496,7 +468,7 @@ describe("mergeUnifiedConfigs", () => {
|
||||
expect(merged.permission).toEqual({ bash: "deny" });
|
||||
});
|
||||
|
||||
it("replaces scalar runtime knobs (project wins)", () => {
|
||||
it("replaces scalar runtime knobs with later values", () => {
|
||||
const merged = mergeUnifiedConfigs(
|
||||
{
|
||||
debugLog: true,
|
||||
@@ -513,7 +485,7 @@ describe("mergeUnifiedConfigs", () => {
|
||||
expect(merged.doublePressToConfirm).toBe(false);
|
||||
});
|
||||
|
||||
it("replaces the prompt-budget scalars (project wins)", () => {
|
||||
it("replaces prompt-budget scalars with later values", () => {
|
||||
const merged = mergeUnifiedConfigs(
|
||||
{ promptMaxRows: 24, promptFieldMaxWidth: 400 },
|
||||
{ promptMaxRows: 10 },
|
||||
@@ -523,7 +495,7 @@ describe("mergeUnifiedConfigs", () => {
|
||||
expect(merged.promptFieldMaxWidth).toBe(400);
|
||||
});
|
||||
|
||||
it("replaces the review-log field width (project wins)", () => {
|
||||
it("replaces the review-log field width with a later value", () => {
|
||||
expect(
|
||||
mergeUnifiedConfigs(
|
||||
{ reviewLogFieldMaxWidth: 1000 },
|
||||
@@ -612,34 +584,6 @@ describe("mergeUnifiedConfigs", () => {
|
||||
expect(merged).not.toHaveProperty("toolTextSummaryMaxLength");
|
||||
});
|
||||
|
||||
it("override piInfrastructureReadPaths replaces base array", () => {
|
||||
const merged = mergeUnifiedConfigs(
|
||||
{ piInfrastructureReadPaths: ["/base/path"] },
|
||||
{ piInfrastructureReadPaths: ["/override/path"] },
|
||||
);
|
||||
expect(merged.piInfrastructureReadPaths).toEqual(["/override/path"]);
|
||||
});
|
||||
|
||||
it("base piInfrastructureReadPaths survives when override omits it", () => {
|
||||
const merged = mergeUnifiedConfigs(
|
||||
{ piInfrastructureReadPaths: ["/kept/path"] },
|
||||
{ debugLog: true },
|
||||
);
|
||||
expect(merged.piInfrastructureReadPaths).toEqual(["/kept/path"]);
|
||||
});
|
||||
|
||||
it("piInfrastructureReadPaths is absent when both base and override omit it", () => {
|
||||
const merged = mergeUnifiedConfigs({ debugLog: true }, { yoloMode: false });
|
||||
expect(merged).not.toHaveProperty("piInfrastructureReadPaths");
|
||||
});
|
||||
|
||||
it("override piInfrastructureReadPaths as empty array replaces non-empty base", () => {
|
||||
const merged = mergeUnifiedConfigs(
|
||||
{ piInfrastructureReadPaths: ["/base/path"] },
|
||||
{ piInfrastructureReadPaths: [] },
|
||||
);
|
||||
expect(merged.piInfrastructureReadPaths).toEqual([]);
|
||||
});
|
||||
|
||||
it("override authorizerChain replaces base array", () => {
|
||||
const merged = mergeUnifiedConfigs(
|
||||
@@ -764,27 +708,6 @@ describe("loadAndMergeConfigs", () => {
|
||||
writeFileSync(join(extensionRoot, "config.json"), JSON.stringify(content));
|
||||
}
|
||||
|
||||
it("merges global and project new-layout configs", () => {
|
||||
writeGlobal({
|
||||
debugLog: true,
|
||||
permission: { "*": "ask", read: "allow" },
|
||||
});
|
||||
writeProject({
|
||||
permission: { "*": "allow", write: "deny" },
|
||||
});
|
||||
|
||||
const result = loadAndMergeConfigs(agentDir, cwd, extensionRoot);
|
||||
// The merged config leaves a permissive top-level '*' with no bash '*' policy,
|
||||
// so the bash-fallback footgun warning is expected.
|
||||
expect(result.issues).toHaveLength(1);
|
||||
expect(result.issues[0]).toContain("bash");
|
||||
expect(result.merged.debugLog).toBe(true);
|
||||
expect(result.merged.permission).toEqual({
|
||||
"*": "allow",
|
||||
read: "allow",
|
||||
write: "deny",
|
||||
});
|
||||
});
|
||||
|
||||
it("detects legacy global policy and emits migration issue", () => {
|
||||
writeLegacyGlobalPolicy({
|
||||
@@ -800,18 +723,6 @@ describe("loadAndMergeConfigs", () => {
|
||||
expect(result.merged.permission).toBeUndefined();
|
||||
});
|
||||
|
||||
it("detects legacy project policy and emits migration issue", () => {
|
||||
writeLegacyProjectPolicy({
|
||||
bash: { "git status": "allow" },
|
||||
});
|
||||
|
||||
const result = loadAndMergeConfigs(agentDir, cwd, extensionRoot);
|
||||
expect(result.issues).toHaveLength(1);
|
||||
expect(result.issues[0]).toContain(".pi/agent/pi-permissions.jsonc");
|
||||
expect(result.issues[0]).toContain(".pi/extensions/pi-permission-system");
|
||||
// Legacy file has no flat-format permission key — no rules extracted
|
||||
expect(result.merged.permission).toBeUndefined();
|
||||
});
|
||||
|
||||
it("detects legacy extension runtime config and emits migration issue", () => {
|
||||
writeLegacyExtensionConfig({
|
||||
@@ -878,57 +789,15 @@ describe("loadAndMergeConfigs", () => {
|
||||
expect(result.issues).toEqual([]);
|
||||
});
|
||||
|
||||
describe("includeProjectScope", () => {
|
||||
it("omits the new project config when includeProjectScope is false", () => {
|
||||
writeGlobal({
|
||||
permission: { "*": "ask", bash: "deny" },
|
||||
});
|
||||
writeProject({
|
||||
permission: { bash: "allow" },
|
||||
});
|
||||
it("ignores new and legacy project policy files", () => {
|
||||
writeGlobal({ permission: { "*": "ask", bash: "deny" } });
|
||||
writeProject({ permission: { bash: "allow" } });
|
||||
writeLegacyProjectPolicy({ permission: { "*": "allow" } });
|
||||
|
||||
const result = loadAndMergeConfigs(agentDir, cwd, extensionRoot, {
|
||||
includeProjectScope: false,
|
||||
});
|
||||
const result = loadAndMergeConfigs(agentDir, cwd, extensionRoot);
|
||||
|
||||
// The untrusted project's `bash: allow` must not override global `deny`.
|
||||
expect(result.merged.permission).toEqual({ "*": "ask", bash: "deny" });
|
||||
expect(result.project).toEqual({});
|
||||
});
|
||||
|
||||
it("omits the legacy project policy when includeProjectScope is false", () => {
|
||||
writeGlobal({ permission: { "*": "ask" } });
|
||||
writeLegacyProjectPolicy({ permission: { "*": "allow" } });
|
||||
|
||||
const result = loadAndMergeConfigs(agentDir, cwd, extensionRoot, {
|
||||
includeProjectScope: false,
|
||||
});
|
||||
|
||||
expect(result.merged.permission).toEqual({ "*": "ask" });
|
||||
expect(
|
||||
result.issues.some((i) => i.includes("pi-permissions.jsonc")),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("includes the project config when includeProjectScope is true", () => {
|
||||
writeGlobal({ permission: { "*": "ask", bash: "deny" } });
|
||||
writeProject({ permission: { bash: "allow" } });
|
||||
|
||||
const result = loadAndMergeConfigs(agentDir, cwd, extensionRoot, {
|
||||
includeProjectScope: true,
|
||||
});
|
||||
|
||||
expect(result.merged.permission).toEqual({ "*": "ask", bash: "allow" });
|
||||
});
|
||||
|
||||
it("includes the project config by default (option omitted)", () => {
|
||||
writeGlobal({ permission: { "*": "ask", bash: "deny" } });
|
||||
writeProject({ permission: { bash: "allow" } });
|
||||
|
||||
const result = loadAndMergeConfigs(agentDir, cwd, extensionRoot);
|
||||
|
||||
expect(result.merged.permission).toEqual({ "*": "ask", bash: "allow" });
|
||||
});
|
||||
expect(result.merged.permission).toEqual({ "*": "ask", bash: "deny" });
|
||||
expect(result.issues).toEqual([]);
|
||||
});
|
||||
|
||||
describe("deprecated tool-preview caps", () => {
|
||||
|
||||
Reference in New Issue
Block a user