feat: vendor permission system source

This commit is contained in:
云服务部-叶林立
2026-08-19 14:35:19 +08:00
parent 198584daf8
commit 410c50a3e5
809 changed files with 157793 additions and 139 deletions
@@ -0,0 +1,428 @@
import { describe, expect, it } from "vitest";
import {
EXTENSION_TAG,
renderPolicyDenial,
renderUnavailableDenial,
renderUserDenial,
} from "#src/presentation/agent-renderer";
import type { PromptPayload } from "#src/presentation/prompt-payload";
import { makePromptPayload } from "#test/helpers/prompt-details-fixtures";
/** A payload of the given kind, with request facts and evidence overridden. */
function payload(
kind: PromptPayload["kind"],
request: Partial<PromptPayload["request"]>,
evidence: PromptPayload["evidence"] = [],
): PromptPayload {
const base = makePromptPayload();
return {
...base,
kind,
request: { ...base.request, ...request },
evidence,
};
}
/** A bash ask, the shape whose value the renderer must never echo. */
function bashPayload(
request: Partial<PromptPayload["request"]> = {},
): PromptPayload {
return payload("bash", {
surface: "bash",
toolName: "bash",
value: "rm -rf build",
matchedPattern: "rm *",
...request,
});
}
describe("EXTENSION_TAG", () => {
it("attributes every block reason to this extension", () => {
expect(EXTENSION_TAG).toBe("[pi-permission-system]");
});
});
describe("renderPolicyDenial", () => {
it("names the surface and the matched rule for a bash deny", () => {
expect(renderPolicyDenial(bashPayload(), null)).toBe(
"[pi-permission-system] Denied by policy: 'bash' (rule 'rm *').",
);
});
it("never echoes the command, however large", () => {
const command = `cat <<'EOF' > gen.py\n${"x".repeat(70_000)}\nEOF`;
const rendered = renderPolicyDenial(
bashPayload({ value: command, matchedPattern: "*" }),
null,
);
expect(rendered).toBe(
"[pi-permission-system] Denied by policy: 'bash' (rule '*').",
);
expect(rendered).not.toContain("xxx");
});
it("names the tool when it differs from the surface", () => {
expect(
renderPolicyDenial(
payload("path", {
surface: "path",
toolName: "read",
value: "/etc/passwd",
matchedPattern: "/etc/*",
}),
null,
),
).toBe(
"[pi-permission-system] Denied by policy: 'path' for tool 'read' for path '/etc/passwd' (rule '/etc/*').",
);
});
it("names the invoked tool when a shell alias re-exposed bash", () => {
expect(
renderPolicyDenial(
bashPayload({ invokedToolName: "exec_command" }),
null,
),
).toBe(
"[pi-permission-system] Denied by policy: 'bash' (invoked as 'exec_command') (rule 'rm *').",
);
});
it("names the requesting agent when the ask carries one", () => {
expect(
renderPolicyDenial(
bashPayload({
requester: { agentName: "scout", forwarded: false, sessionId: null },
}),
null,
),
).toBe(
"[pi-permission-system] Denied by policy: 'bash' for agent 'scout' (rule 'rm *').",
);
});
it("carries the operator's deny-with-reason text", () => {
expect(renderPolicyDenial(bashPayload(), "destructive by policy")).toBe(
"[pi-permission-system] Denied by policy: 'bash' (rule 'rm *'). Reason: destructive by policy.",
);
});
it("names the nested context a bash unit ran in", () => {
expect(
renderPolicyDenial(
bashPayload({ commandContext: "command_substitution" }),
null,
),
).toBe(
"[pi-permission-system] Denied by policy: 'bash' (rule 'rm *', inside command substitution).",
);
});
it.each([
"<indirection-bash-wrapper>",
"<opaque-bash-wrapper>",
"<unparseable-bash-command>",
])("surfaces the %s sentinel as the matched rule", (sentinel) => {
expect(
renderPolicyDenial(bashPayload({ matchedPattern: sentinel }), null),
).toBe(
`[pi-permission-system] Denied by policy: 'bash' (rule '${sentinel}').`,
);
});
it("omits the rule clause when no pattern matched", () => {
expect(
renderPolicyDenial(bashPayload({ matchedPattern: null }), null),
).toBe("[pi-permission-system] Denied by policy: 'bash'.");
});
it("names the escaped boundary for a tool external-directory deny", () => {
expect(
renderPolicyDenial(
payload(
"external_directory",
{
surface: "external_directory",
toolName: "write",
value: "/etc/hosts",
matchedPattern: "*",
},
[{ label: "working directory", text: "/repo", detail: null }],
),
null,
),
).toBe(
"[pi-permission-system] Denied by policy: 'external_directory' for tool 'write' for path '/etc/hosts' (rule '*'): outside working directory '/repo'.",
);
});
it("names the canonical target when a path resolves elsewhere", () => {
expect(
renderPolicyDenial(
payload(
"external_directory",
{
surface: "external_directory",
toolName: "read",
value: "link",
matchedPattern: "*",
},
[
{ label: "resolves to", text: "/etc/shadow", detail: null },
{ label: "working directory", text: "/repo", detail: null },
],
),
null,
),
).toBe(
"[pi-permission-system] Denied by policy: 'external_directory' for tool 'read' for path 'link' (resolves to '/etc/shadow') (rule '*'): outside working directory '/repo'.",
);
});
it("names every escaping path but not the command for a bash external-directory deny", () => {
expect(
renderPolicyDenial(
payload(
"bash_external_directory",
{
surface: "external_directory",
toolName: "bash",
value: "diff /etc/hosts ~/.ssh/config",
matchedPattern: "*",
},
[
{ label: "working directory", text: "/repo", detail: null },
{ label: "external path", text: "/etc/hosts", detail: null },
{
label: "external path",
text: "~/.ssh/config",
detail: "/home/me/.ssh/config",
},
],
),
null,
),
).toBe(
"[pi-permission-system] Denied by policy: 'external_directory' for tool 'bash' for paths '/etc/hosts', '~/.ssh/config' (resolves to '/home/me/.ssh/config') (rule '*'): outside working directory '/repo'.",
);
});
it("names the MCP target", () => {
expect(
renderPolicyDenial(
payload("mcp", {
surface: "mcp",
toolName: "mcp",
value: "github:create_issue",
matchedPattern: "github:*",
}),
null,
),
).toBe(
"[pi-permission-system] Denied by policy: 'mcp' for target 'github:create_issue' (rule 'github:*').",
);
});
it("names the skill", () => {
expect(
renderPolicyDenial(
payload("skill", {
surface: "skill",
toolName: null,
value: "deploy",
matchedPattern: "deploy",
}),
null,
),
).toBe(
"[pi-permission-system] Denied by policy: 'skill' for skill 'deploy' (rule 'deploy').",
);
});
it("names the skill a read reached, and the path it reached it through", () => {
expect(
renderPolicyDenial(
payload(
"skill_read",
{
surface: "skill",
toolName: null,
value: "deploy",
matchedPattern: "deploy",
},
[
{
label: "read path",
text: ".pi/skills/deploy/SKILL.md",
detail: null,
},
],
),
null,
),
).toBe(
"[pi-permission-system] Denied by policy: 'skill' for skill 'deploy' (rule 'deploy'), reached via '.pi/skills/deploy/SKILL.md'.",
);
});
it("states the tool once for a generic tool ask, whose value is its own name", () => {
expect(
renderPolicyDenial(
payload("tool", {
surface: "webfetch",
toolName: "webfetch",
value: "webfetch",
matchedPattern: "web*",
}),
null,
),
).toBe(
"[pi-permission-system] Denied by policy: 'webfetch' (rule 'web*').",
);
});
it("names nothing beyond the surface for a payload-less forwarded relay", () => {
expect(
renderPolicyDenial(
payload("forwarded", {
surface: "bash",
toolName: "bash",
value: "rm -rf /",
matchedPattern: "*",
}),
null,
),
).toBe("[pi-permission-system] Denied by policy: 'bash' (rule '*').");
});
});
describe("renderUserDenial", () => {
it("attributes the refusal to the user", () => {
expect(renderUserDenial(bashPayload(), null)).toBe(
"[pi-permission-system] The user denied this 'bash' call (rule 'rm *').",
);
});
it("carries the human's typed reason", () => {
expect(renderUserDenial(bashPayload(), "not with sudo")).toBe(
"[pi-permission-system] The user denied this 'bash' call (rule 'rm *'). Reason: not with sudo.",
);
});
it("never echoes the command", () => {
const rendered = renderUserDenial(
bashPayload({ value: "x".repeat(70_000), matchedPattern: "*" }),
"too big",
);
expect(rendered).toBe(
"[pi-permission-system] The user denied this 'bash' call (rule '*'). Reason: too big.",
);
});
it("names the flagged path for a tool ask", () => {
expect(
renderUserDenial(
payload("path", {
surface: "path",
toolName: "read",
value: "/etc/passwd",
matchedPattern: "/etc/*",
}),
"not that file",
),
).toBe(
"[pi-permission-system] The user denied this 'path' call for tool 'read' for path '/etc/passwd' (rule '/etc/*'). Reason: not that file.",
);
});
});
describe("renderUnavailableDenial", () => {
it("states that approval was required and unreachable", () => {
expect(renderUnavailableDenial(bashPayload(), null)).toBe(
"[pi-permission-system] This 'bash' call (rule 'rm *') requires approval, but no interactive UI is available.",
);
});
it("carries an abandoning authority's reason", () => {
expect(
renderUnavailableDenial(
bashPayload(),
"Session 'parent-1' is not serving forwarded requests",
),
).toBe(
"[pi-permission-system] This 'bash' call (rule 'rm *') requires approval, but no interactive UI is available. Reason: Session 'parent-1' is not serving forwarded requests.",
);
});
it("names the flagged path for a tool ask", () => {
expect(
renderUnavailableDenial(
payload("path", {
surface: "path",
toolName: "read",
value: "/etc/passwd",
matchedPattern: "/etc/*",
}),
null,
),
).toBe(
"[pi-permission-system] This 'path' call for tool 'read' for path '/etc/passwd' (rule '/etc/*') requires approval, but no interactive UI is available.",
);
});
it("omits the escaped boundary, which no retry shape would change", () => {
expect(
renderUnavailableDenial(
payload(
"external_directory",
{
surface: "external_directory",
toolName: "write",
value: "/etc/hosts",
matchedPattern: "*",
},
[{ label: "working directory", text: "/repo", detail: null }],
),
null,
),
).toBe(
"[pi-permission-system] This 'external_directory' call for tool 'write' for path '/etc/hosts' (rule '*') requires approval, but no interactive UI is available.",
);
});
});
describe("the flagged-element field cap", () => {
it("shortens an oversized path and marks it", () => {
const long = `/etc/${"a".repeat(500)}`;
const rendered = renderPolicyDenial(
payload("path", {
surface: "path",
toolName: "read",
value: long,
matchedPattern: "*",
}),
null,
{ fieldMaxWidth: 20 },
);
expect(rendered).toBe(
"[pi-permission-system] Denied by policy: 'path' for tool 'read' for path '/etc/aaaaaaaaaaaaaaa\u2026' (rule '*').",
);
});
it("leaves a path within the budget untouched", () => {
expect(
renderPolicyDenial(
payload("path", {
surface: "path",
toolName: "read",
value: "/etc/hosts",
matchedPattern: "*",
}),
null,
{ fieldMaxWidth: 400 },
),
).toBe(
"[pi-permission-system] Denied by policy: 'path' for tool 'read' for path '/etc/hosts' (rule '*').",
);
});
});
@@ -0,0 +1,645 @@
import { describe, expect, it } from "vitest";
import {
completeViewBudget,
type DialogBudget,
renderPromptDialog,
} from "#src/presentation/dialog-renderer";
import { makePromptPayload } from "#test/helpers/prompt-details-fixtures";
const WIDE = completeViewBudget(200);
/** The rendered lines for a payload built from the shared structural fixture. */
function render(
overrides: Parameters<typeof makePromptPayload>[0],
): readonly string[] {
return renderPromptDialog(makePromptPayload(overrides), WIDE).lines;
}
/** A payload whose request facts override the fixture's defaults. */
function requestFacts(
overrides: Partial<ReturnType<typeof makePromptPayload>["request"]>,
) {
return { ...makePromptPayload().request, ...overrides };
}
describe("renderPromptDialog", () => {
describe("the invariant core", () => {
it("renders a bash ask as one aligned fact per line", () => {
expect(
render({
kind: "bash",
request: requestFacts({
requester: {
agentName: "scout",
forwarded: false,
sessionId: null,
},
surface: "bash",
toolName: "bash",
value: "cat /etc/hosts",
matchedPattern: "*",
}),
}),
).toEqual([
"agent : scout",
"tool : bash",
"rule : *",
"command : cat /etc/hosts",
]);
});
it("names the gate surface when it differs from the tool", () => {
expect(
render({
kind: "external_directory",
request: requestFacts({
surface: "external_directory",
toolName: "write",
value: "/etc/hosts",
matchedPattern: "*",
}),
}),
).toEqual([
"tool : write",
"surface : external_directory",
"rule : *",
"path : /etc/hosts",
]);
});
it("omits an agent line for an unnamed local requester, and a surface the value label already names", () => {
expect(
render({
kind: "path",
request: requestFacts({
surface: "path",
toolName: "read",
value: "/tmp/x",
matchedPattern: null,
}),
}),
).toEqual(["tool : read", "path : /tmp/x"]);
});
it("omits the value line when the tool line already carries it", () => {
expect(
render({
kind: "tool",
request: requestFacts({
surface: "fetch",
toolName: "fetch",
value: "fetch",
matchedPattern: "*",
}),
}),
).toEqual(["tool : fetch", "rule : *"]);
});
it("labels an MCP ask's value as the target", () => {
expect(
render({
kind: "mcp",
request: requestFacts({
surface: "mcp__github__create_issue",
toolName: "mcp__github__create_issue",
value: "github:create_issue",
matchedPattern: "mcp__github__*",
}),
}),
).toEqual([
"tool : mcp__github__create_issue",
"rule : mcp__github__*",
"target : github:create_issue",
]);
});
it("renders a skill ask without repeating the surface", () => {
expect(
render({
kind: "skill",
request: requestFacts({
surface: "skill",
toolName: null,
value: "deploy",
matchedPattern: null,
}),
}),
).toEqual(["skill : deploy"]);
});
it("renders the executed unit of an unstrippable wrapper", () => {
expect(
render({
kind: "bash",
request: requestFacts({
surface: "bash",
toolName: "bash",
value: "xargs grep foo",
matchedPattern: "<indirection-bash-wrapper>",
executedUnit: "grep foo",
}),
}),
).toEqual([
"tool : bash",
"rule : <indirection-bash-wrapper>",
"command : xargs grep foo",
"runs : grep foo",
]);
});
it("names the nested context the offending unit ran in", () => {
expect(
render({
kind: "bash",
request: requestFacts({
surface: "bash",
toolName: "bash",
value: "rm -rf /tmp/x",
matchedPattern: "rm *",
commandContext: "command_substitution",
}),
}),
).toEqual([
"tool : bash",
"rule : rm *",
"command : rm -rf /tmp/x",
"context : command substitution",
]);
});
it("names the invoked tool when a shell alias re-exposes bash", () => {
expect(
render({
kind: "bash",
request: requestFacts({
surface: "bash",
toolName: "bash",
invokedToolName: "exec_command",
value: "ls",
matchedPattern: "*",
}),
}),
).toEqual([
"tool : bash (invoked as exec_command)",
"rule : *",
"command : ls",
]);
});
});
describe("evidence", () => {
it("renders each entry under the core, sharing the label column", () => {
expect(
render({
kind: "bash",
request: requestFacts({
surface: "bash",
toolName: "bash",
value: "rm -rf build",
matchedPattern: "rm *",
}),
evidence: [
{
label: "full command",
text: "npm run clean && rm -rf build",
detail: null,
},
],
}),
).toEqual([
"tool : bash",
"rule : rm *",
"command : rm -rf build",
"full command : npm run clean && rm -rf build",
]);
});
it("keeps an entry's detail on the entry's own line", () => {
expect(
render({
kind: "bash_external_directory",
request: requestFacts({
surface: "external_directory",
toolName: "bash",
value: "cat /etc/hosts /tmp/x",
matchedPattern: "*",
}),
evidence: [
{ label: "working directory", text: "/repo", detail: null },
{
label: "external path",
text: "/etc/hosts",
detail: "/private/etc/hosts",
},
{ label: "external path", text: "/tmp/x", detail: null },
],
}),
).toEqual([
"tool : bash",
"surface : external_directory",
"rule : *",
"command : cat /etc/hosts /tmp/x",
"working directory : /repo",
"external path : /etc/hosts → /private/etc/hosts",
"external path : /tmp/x",
]);
});
it("renders a skill read's path", () => {
expect(
render({
kind: "skill_read",
request: requestFacts({
surface: "skill",
toolName: null,
value: "deploy",
matchedPattern: null,
}),
evidence: [
{
label: "read path",
text: "/skills/deploy/SKILL.md",
detail: null,
},
],
}),
).toEqual(["skill : deploy", "read path : /skills/deploy/SKILL.md"]);
});
});
describe("the per-field width cap", () => {
const NARROW: DialogBudget = {
maxRows: Number.POSITIVE_INFINITY,
fieldMaxWidth: 12,
width: 200,
};
/** A bash ask whose command is the pathological field. */
function bashAsk(command: string, fullCommand?: string) {
return makePromptPayload({
kind: "bash",
request: {
...makePromptPayload().request,
toolName: "bash",
surface: "bash",
value: command,
matchedPattern: "*",
},
evidence:
fullCommand === undefined
? []
: [{ label: "full command", text: fullCommand, detail: null }],
});
}
it("clips a core field and marks it, without stating a count", () => {
const view = renderPromptDialog(
bashAsk("echo the quick brown fox"),
NARROW,
);
expect(view.lines).toEqual([
"tool : bash",
"rule : *",
"command : echo the qui…",
]);
expect(view.elided).toBe(true);
});
it("clips an evidence field the same way", () => {
const view = renderPromptDialog(
bashAsk("echo hi", "echo hi && echo there"),
NARROW,
);
expect(view.lines).toEqual([
"tool : bash",
"rule : *",
"command : echo hi",
"full command : echo hi && e…",
]);
expect(view.elided).toBe(true);
});
it("reproduces the field verbatim under the complete view", () => {
const view = renderPromptDialog(
bashAsk("echo the quick brown fox"),
completeViewBudget(200),
);
expect(view.lines).toContain("command : echo the quick brown fox");
expect(view.elided).toBe(false);
});
it("indents a multi-line field under its label", () => {
const view = renderPromptDialog(
bashAsk("cat <<'EOF'\nfirst line\nsecond line\nEOF"),
completeViewBudget(200),
);
expect(view.lines).toEqual([
"tool : bash",
"rule : *",
"command : cat <<'EOF'",
" first line",
" second line",
" EOF",
]);
});
});
describe("the row budget", () => {
/** A tool ask with `count` evidence entries, each one row wide. */
function askWithEvidence(count: number) {
return makePromptPayload({
kind: "tool",
request: {
...makePromptPayload().request,
surface: "fetch",
toolName: "fetch",
value: "fetch",
matchedPattern: "*",
},
evidence: Array.from({ length: count }, (_, index) => ({
label: "input",
text: `entry ${index}`,
detail: null,
})),
});
}
it("drops evidence that does not fit and marks the drop", () => {
const view = renderPromptDialog(askWithEvidence(6), {
maxRows: 5,
fieldMaxWidth: 200,
width: 200,
});
expect(view.lines).toEqual([
"tool : fetch",
"rule : *",
"input : entry 0",
"input : entry 1",
"…",
]);
expect(view.elided).toBe(true);
});
it("marks nothing when every entry fits", () => {
const view = renderPromptDialog(askWithEvidence(2), {
maxRows: 5,
fieldMaxWidth: 200,
width: 200,
});
expect(view.lines).toHaveLength(4);
expect(view.elided).toBe(false);
});
it("counts rows after wrapping to the width", () => {
const payload = makePromptPayload({
kind: "tool",
request: {
...makePromptPayload().request,
surface: "fetch",
toolName: "fetch",
value: "fetch",
matchedPattern: "*",
},
evidence: [{ label: "input", text: "a".repeat(60), detail: null }],
});
// The same entry is one row at a wide width and four once wrapped, so
// the identical budget admits it only in the first case.
expect(
renderPromptDialog(payload, {
maxRows: 5,
fieldMaxWidth: 200,
width: 200,
}),
).toEqual({
lines: ["tool : fetch", "rule : *", `input : ${"a".repeat(60)}`],
elided: false,
});
const wrapped = renderPromptDialog(payload, {
maxRows: 5,
fieldMaxWidth: 200,
width: 20,
});
expect(wrapped.lines).toEqual(["tool : fetch", "rule : *", "…"]);
expect(wrapped.elided).toBe(true);
});
it("renders the whole core when it alone exceeds the budget", () => {
const view = renderPromptDialog(askWithEvidence(3), {
maxRows: 1,
fieldMaxWidth: 200,
width: 200,
});
expect(view.lines).toEqual(["tool : fetch", "rule : *"]);
expect(view.elided).toBe(true);
});
it("bounds the reported forwarded here-string ask (#710)", () => {
const body = Array.from(
{ length: 200 },
() => "- a finding line about some module in the codebase",
).join("\n");
const command = `@'\n${body}\n'@ | Out-File -FilePath report.md`;
const budget = { maxRows: 24, fieldMaxWidth: 400, width: 120 };
const view = renderPromptDialog(
makePromptPayload({
kind: "forwarded",
request: {
...makePromptPayload().request,
requester: {
agentName: "scout",
forwarded: true,
sessionId: "abc123",
},
surface: "bash",
toolName: null,
value: command,
matchedPattern: null,
},
evidence: [
{
label: "requested",
text: `Subagent 'scout' requested bash command '${command}'.`,
detail: null,
},
],
}),
budget,
);
expect(view.lines.length).toBeLessThanOrEqual(budget.maxRows);
expect(view.lines[0]).toBe("subagent : scout · session abc123");
expect(view.lines[1]).toBe("surface : bash");
expect(view.lines[2]).toBe("command : @'");
expect(view.elided).toBe(true);
});
it("bounds the same forwarded here-string ask carrying the child's own payload (#745)", () => {
// The same ask, arriving as the child built it rather than as a relayed
// sentence: `kind: "bash"` with the child's real evidence entries. A
// different input to the same budget, so the row bound is measured here
// rather than inferred from the `kind: "forwarded"` pin above.
const body = Array.from(
{ length: 200 },
() => "- a finding line about some module in the codebase",
).join("\n");
const command = `@'\n${body}\n'@ | Out-File -FilePath report.md`;
const budget = { maxRows: 24, fieldMaxWidth: 400, width: 120 };
const view = renderPromptDialog(
makePromptPayload({
kind: "bash",
request: {
...makePromptPayload().request,
requester: {
agentName: "scout",
forwarded: true,
sessionId: "abc123",
},
surface: "bash",
toolName: "bash",
value: command,
matchedPattern: "*",
},
evidence: [
{ label: "full command", text: command, detail: null },
{ label: "working directory", text: "/repo", detail: null },
],
}),
budget,
);
expect(view.lines.length).toBeLessThanOrEqual(budget.maxRows);
expect(view.elided).toBe(true);
});
});
describe("highlighting the flagged element", () => {
/** A visible stand-in for the theme's warning colour. */
const mark = (text: string) => `[${text}]`;
it("paints the flagged value and its whole-token occurrences in evidence", () => {
expect(
renderPromptDialog(
makePromptPayload({
kind: "bash",
request: {
...makePromptPayload().request,
surface: "bash",
toolName: "bash",
value: "ls",
matchedPattern: "*",
},
evidence: [
{
label: "full command",
text: "lsof | ls && /usr/bin/lsblk",
detail: null,
},
],
}),
completeViewBudget(200),
mark,
).lines,
).toEqual([
"tool : bash",
"rule : *",
"command : [ls]",
"full command : lsof | [ls] && /usr/bin/lsblk",
]);
});
it("paints each escaping path rather than the command that referenced them", () => {
expect(
renderPromptDialog(
makePromptPayload({
kind: "bash_external_directory",
request: {
...makePromptPayload().request,
surface: "external_directory",
toolName: "bash",
value: "cat /etc/hosts /etc/hostsbackup",
matchedPattern: "*",
},
evidence: [
{ label: "working directory", text: "/repo", detail: null },
{ label: "external path", text: "/etc/hosts", detail: null },
],
}),
completeViewBudget(200),
mark,
).lines,
).toEqual([
"tool : bash",
"surface : external_directory",
"rule : *",
"command : cat [/etc/hosts] /etc/hostsbackup",
"working directory : /repo",
"external path : [/etc/hosts]",
]);
});
it("leaves the text untouched when no paint is supplied", () => {
expect(
render({
kind: "bash",
request: requestFacts({
surface: "bash",
toolName: "bash",
value: "ls",
matchedPattern: "*",
}),
}),
).toEqual(["tool : bash", "rule : *", "command : ls"]);
});
});
describe("a forwarded ask", () => {
it("names the requesting subagent and its session", () => {
expect(
render({
kind: "forwarded",
request: requestFacts({
requester: {
agentName: "scout",
forwarded: true,
sessionId: "abc123",
},
surface: "bash",
toolName: null,
value: "cat /etc/hosts",
matchedPattern: null,
}),
}),
).toEqual([
"subagent : scout · session abc123",
"surface : bash",
"command : cat /etc/hosts",
]);
});
it("renders a version-skewed request that names neither agent nor session", () => {
expect(
render({
kind: "forwarded",
request: requestFacts({
requester: { agentName: "", forwarded: true, sessionId: "" },
surface: "read",
toolName: null,
value: "/tmp/x",
matchedPattern: null,
}),
}),
).toEqual(["subagent : unknown", "surface : read", "value : /tmp/x"]);
});
});
});
@@ -0,0 +1,120 @@
import { describe, expect, it } from "vitest";
import {
describeBashCommandContext,
flaggedElementLabel,
flaggedElements,
valueLabel,
} from "#src/presentation/fact-vocabulary";
import type {
PromptEvidence,
PromptPayloadKind,
} from "#src/presentation/prompt-payload";
import { makePromptPayload } from "#test/helpers/prompt-details-fixtures";
/** A payload of the given kind whose flagged value and evidence are set. */
function payloadOf(
kind: PromptPayloadKind,
value: string,
evidence: PromptEvidence[] = [],
) {
const base = makePromptPayload();
return {
...base,
kind,
request: { ...base.request, value },
evidence,
};
}
/** An `external path` evidence entry, as the bash external-directory gate emits it. */
function externalPath(path: string, resolved: string | null): PromptEvidence {
return { label: "external path", text: path, detail: resolved };
}
describe("flaggedElements", () => {
it("flags the decision-relevant value for a single-value ask", () => {
expect(flaggedElements(payloadOf("path", "/etc/hosts"))).toEqual([
"/etc/hosts",
]);
});
it("flags the command for a bash ask", () => {
expect(flaggedElements(payloadOf("bash", "rm -rf build"))).toEqual([
"rm -rf build",
]);
});
it("flags the escaping paths rather than the command for a bash external-directory ask", () => {
expect(
flaggedElements(
payloadOf("bash_external_directory", "diff /etc/hosts ~/.ssh/config", [
{ label: "working directory", text: "/repo", detail: null },
externalPath("/etc/hosts", null),
externalPath("~/.ssh/config", "/home/me/.ssh/config"),
]),
),
).toEqual(["/etc/hosts", "~/.ssh/config"]);
});
it("flags nothing when the value is empty", () => {
expect(flaggedElements(payloadOf("tool", ""))).toEqual([]);
});
});
describe("valueLabel", () => {
it.each([
["bash", "command"],
["bash_external_directory", "command"],
["mcp", "target"],
["tool", "tool"],
["path", "path"],
["external_directory", "path"],
["skill", "skill"],
["skill_read", "skill"],
] as const)("labels a %s ask's value %s", (kind, label) => {
expect(valueLabel(payloadOf(kind, "value"))).toBe(label);
});
it.each([
["bash", "command"],
["skill", "skill"],
["read", "value"],
])("infers a payload-less forwarded ask's label from its %s surface", (surface, label) => {
const base = payloadOf("forwarded", "value");
expect(valueLabel({ ...base, request: { ...base.request, surface } })).toBe(
label,
);
});
});
describe("flaggedElementLabel", () => {
it("labels the escaping paths a bash external-directory ask flags", () => {
expect(
flaggedElementLabel(payloadOf("bash_external_directory", "cmd")),
).toBe("path");
});
it.each([
"bash",
"mcp",
"path",
"skill",
] as const)("agrees with the value label for a %s ask, whose value is what it flags", (kind) => {
const single = payloadOf(kind, "value");
expect(flaggedElementLabel(single)).toBe(valueLabel(single));
});
});
describe("describeBashCommandContext", () => {
it.each([
["command_substitution", "command substitution"],
["process_substitution", "process substitution"],
["subshell", "subshell"],
] as const)("names a %s context", (context, label) => {
expect(describeBashCommandContext(context)).toBe(label);
});
it("names no context for a current-shell command", () => {
expect(describeBashCommandContext(null)).toBeUndefined();
});
});
@@ -0,0 +1,126 @@
import { describe, expect, test } from "vitest";
import {
buildBashExternalDirectoryAskPayload,
buildExternalDirectoryAskPayload,
buildPathAskPayload,
} from "#src/presentation/path-ask-payload";
describe("buildPathAskPayload", () => {
test("carries the typed path as the decision value and the rule that fired", () => {
const payload = buildPathAskPayload({
toolName: "read",
pathValue: "/etc/passwd",
agentName: "my-agent",
matchedPattern: "/etc/*",
});
expect(payload.kind).toBe("path");
expect(payload.request).toEqual({
requester: { agentName: "my-agent", forwarded: false, sessionId: null },
surface: "path",
toolName: "read",
invokedToolName: null,
value: "/etc/passwd",
matchedPattern: "/etc/*",
commandContext: null,
executedUnit: null,
});
expect(payload.evidence).toEqual([]);
});
test("leaves the requester unnamed when no agent is active", () => {
expect(
buildPathAskPayload({
toolName: "read",
pathValue: "/etc/passwd",
agentName: null,
}).request.requester,
).toEqual({ agentName: null, forwarded: false, sessionId: null });
});
});
describe("buildExternalDirectoryAskPayload", () => {
test("carries the typed path, the boundary, and the requester", () => {
const payload = buildExternalDirectoryAskPayload({
toolName: "write",
pathValue: "/tmp/out.txt",
cwd: "/projects/my-app",
agentName: "my-agent",
});
expect(payload.kind).toBe("external_directory");
expect(payload.request.toolName).toBe("write");
expect(payload.request.value).toBe("/tmp/out.txt");
expect(payload.request.requester.agentName).toBe("my-agent");
expect(payload.evidence).toEqual([
{ label: "working directory", text: "/projects/my-app", detail: null },
]);
});
test("discloses the resolved path as its own entry when it differs", () => {
expect(
buildExternalDirectoryAskPayload({
toolName: "read",
pathValue: "demo-symlink-passwd",
resolvedPath: "/etc/passwd",
cwd: "/projects/my-app",
agentName: null,
}).evidence,
).toEqual([
{ label: "resolves to", text: "/etc/passwd", detail: null },
{ label: "working directory", text: "/projects/my-app", detail: null },
]);
});
test("omits the disclosure when resolvedPath is undefined", () => {
const payload = buildExternalDirectoryAskPayload({
toolName: "read",
pathValue: "/etc/passwd",
cwd: "/projects/my-app",
agentName: null,
});
expect(payload.evidence).toEqual([
{ label: "working directory", text: "/projects/my-app", detail: null },
]);
});
});
describe("buildBashExternalDirectoryAskPayload", () => {
test("makes the command the value and the paths it reached evidence", () => {
const payload = buildBashExternalDirectoryAskPayload({
command: "cat /etc/passwd",
externalPaths: [{ path: "/etc/passwd" }],
cwd: "/projects/my-app",
agentName: "my-agent",
toolName: "bash",
});
expect(payload.kind).toBe("bash_external_directory");
expect(payload.request.value).toBe("cat /etc/passwd");
expect(payload.request.requester.agentName).toBe("my-agent");
expect(payload.evidence).toEqual([
{ label: "working directory", text: "/projects/my-app", detail: null },
{ label: "external path", text: "/etc/passwd", detail: null },
]);
});
test("binds each path's canonical alias to its own entry", () => {
const payload = buildBashExternalDirectoryAskPayload({
command: "cat a b",
externalPaths: [
{ path: "/a", resolvedPath: "/private/a" },
{ path: "/b" },
],
cwd: "/repo",
agentName: null,
toolName: "bash",
});
expect(payload.evidence).toEqual([
{ label: "working directory", text: "/repo", detail: null },
{ label: "external path", text: "/a", detail: "/private/a" },
{ label: "external path", text: "/b", detail: null },
]);
});
});
@@ -0,0 +1,120 @@
import { describe, expect, it } from "vitest";
import type { PromptPayload } from "#src/presentation/prompt-payload";
import { renderReviewLogFacts } from "#src/presentation/review-log-renderer";
import { makePromptPayload } from "#test/helpers/prompt-details-fixtures";
/** A payload whose request facts override the structural fixture's defaults. */
function payload(
request: Partial<PromptPayload["request"]>,
rest: Partial<PromptPayload> = {},
): PromptPayload {
const base = makePromptPayload();
return { ...base, request: { ...base.request, ...request }, ...rest };
}
describe("renderReviewLogFacts", () => {
it("records the gate surface and the rule that fired", () => {
expect(
renderReviewLogFacts(
payload({ surface: "bash", toolName: "bash", matchedPattern: "rm *" }),
),
).toEqual({ surface: "bash", matchedPattern: "rm *" });
});
it("omits a fact the ask does not carry rather than writing a null", () => {
expect(
renderReviewLogFacts(payload({ surface: "skill", matchedPattern: null })),
).toEqual({ surface: "skill" });
});
it("records the unit a wrapper will actually run", () => {
expect(
renderReviewLogFacts(
payload({
surface: "bash",
matchedPattern: "<indirection-bash-wrapper>",
executedUnit: "aws s3 rm s3://bucket",
}),
),
).toEqual({
surface: "bash",
matchedPattern: "<indirection-bash-wrapper>",
executedUnit: "aws s3 rm s3://bucket",
});
});
it("records the nested context an offending bash unit ran in", () => {
expect(
renderReviewLogFacts(
payload({
surface: "bash",
matchedPattern: "*",
commandContext: "command_substitution",
}),
),
).toEqual({
surface: "bash",
matchedPattern: "*",
commandContext: "command_substitution",
});
});
it("records the name a shell alias invoked bash under", () => {
expect(
renderReviewLogFacts(
payload({
surface: "bash",
matchedPattern: "*",
invokedToolName: "exec_command",
}),
),
).toEqual({
surface: "bash",
matchedPattern: "*",
invokedToolName: "exec_command",
});
});
it("records the requesting session when the ask was forwarded", () => {
expect(
renderReviewLogFacts(
payload({
surface: "bash",
matchedPattern: "*",
requester: {
agentName: "scout",
forwarded: true,
sessionId: "child-7",
},
}),
),
).toEqual({
surface: "bash",
matchedPattern: "*",
forwarded: true,
requesterSessionId: "child-7",
});
});
it("marks a local ask with no forwarding fields at all", () => {
const facts = renderReviewLogFacts(payload({ surface: "read" }));
expect(facts).toEqual({ surface: "read" });
});
it("persists no evidence and no annotations", () => {
expect(
renderReviewLogFacts(
payload(
{ surface: "external_directory", matchedPattern: "*" },
{
evidence: [
{ label: "working directory", text: "/repo", detail: null },
{ label: "external path", text: "/etc/hosts", detail: null },
],
annotations: [{ source: "judge", text: "looks risky" }],
},
),
),
).toEqual({ surface: "external_directory", matchedPattern: "*" });
});
});
@@ -0,0 +1,74 @@
import { describe, expect, test } from "vitest";
import {
buildSkillAskPayload,
buildSkillPathAskPayload,
} from "#src/presentation/skill-ask-payload";
import type { SkillPromptEntry } from "#src/skill-prompt-sanitizer";
function skillEntry(name: string): SkillPromptEntry {
return {
name,
description: "A skill",
location: `/skills/${name}/SKILL.md`,
state: "ask",
normalizedLocation: `/skills/${name}/SKILL.md`,
normalizedBaseDir: `/skills/${name}`,
};
}
describe("buildSkillAskPayload", () => {
test("makes the skill the decision-relevant value", () => {
const payload = buildSkillAskPayload("librarian", "my-agent");
expect(payload.kind).toBe("skill");
expect(payload.request).toEqual({
requester: { agentName: "my-agent", forwarded: false, sessionId: null },
surface: "skill",
toolName: null,
invokedToolName: null,
value: "librarian",
matchedPattern: null,
commandContext: null,
executedUnit: null,
});
expect(payload.evidence).toEqual([]);
});
test("leaves the requester unnamed when no agent is active", () => {
expect(buildSkillAskPayload("librarian", null).request.requester).toEqual({
agentName: null,
forwarded: false,
sessionId: null,
});
});
});
describe("buildSkillPathAskPayload", () => {
test("keeps the skill as the value and the path as evidence", () => {
const payload = buildSkillPathAskPayload(
skillEntry("librarian"),
"/skills/librarian/SKILL.md",
null,
);
expect(payload.kind).toBe("skill_read");
expect(payload.request.value).toBe("librarian");
expect(payload.evidence).toEqual([
{
label: "read path",
text: "/skills/librarian/SKILL.md",
detail: null,
},
]);
});
test("names the requesting agent on the payload", () => {
expect(
buildSkillPathAskPayload(
skillEntry("librarian"),
"/skills/librarian/SKILL.md",
"my-agent",
).request.requester.agentName,
).toBe("my-agent");
});
});
@@ -0,0 +1,278 @@
import { describe, expect, test } from "vitest";
import { findEvidence } from "#src/presentation/prompt-payload";
import {
buildToolAskPayload,
type ToolAskFacts,
} from "#src/presentation/tool-ask-payload";
import type { ToolInputFormatterLookup } from "#src/tool-input-formatter-registry";
import type { PermissionCheckResult } from "#src/types";
import {
makePermissionCheckResult,
makeToolPreviewFormatter,
} from "#test/helpers/presentation-fixtures";
function makeFormatter(lookup?: ToolInputFormatterLookup) {
return makeToolPreviewFormatter({}, lookup);
}
function makeMcpLookup(preview: string): ToolInputFormatterLookup {
return { get: (name) => (name === "mcp" ? () => preview : undefined) };
}
function toolResult(
toolName: string,
overrides: Partial<PermissionCheckResult> = {},
): PermissionCheckResult {
return makePermissionCheckResult(toolName, overrides);
}
function mcpResult(
target: string,
overrides: Partial<PermissionCheckResult> = {},
): PermissionCheckResult {
return makePermissionCheckResult("mcp", { target, ...overrides });
}
/** Build a payload the way the per-tool gate does, defaulting the surface. */
function buildPayload(
facts: Omit<ToolAskFacts, "surface" | "agentName"> & {
surface?: string;
agentName?: string | null;
},
) {
return buildToolAskPayload({
agentName: null,
surface: facts.check.toolName,
...facts,
});
}
describe("buildToolAskPayload", () => {
describe("the invariant core", () => {
test("carries the gate surface, matched rule, and offending command", () => {
const payload = buildPayload({
check: toolResult("bash", {
command: "rm -rf foo",
matchedPattern: "rm *",
commandContext: "command_substitution",
}),
surface: "bash",
});
expect(payload.kind).toBe("bash");
expect(payload.request).toEqual({
requester: { agentName: null, forwarded: false, sessionId: null },
surface: "bash",
toolName: "bash",
invokedToolName: null,
value: "rm -rf foo",
matchedPattern: "rm *",
commandContext: "command_substitution",
executedUnit: null,
});
});
test("carries the executed unit of a wrapper (#713)", () => {
const payload = buildPayload({
check: toolResult("bash", {
command: "xargs grep foo",
matchedPattern: "<indirection-bash-wrapper>",
executedUnit: "grep foo",
}),
surface: "bash",
});
expect(payload.request.executedUnit).toBe("grep foo");
});
test("names the invoked tool when a shell alias re-exposes bash (#574)", () => {
const payload = buildPayload({
check: toolResult("bash", { command: "ls" }),
surface: "bash",
invokedToolName: "exec_command",
});
expect(payload.request.toolName).toBe("bash");
expect(payload.request.invokedToolName).toBe("exec_command");
});
test("omits the invoked tool when it repeats the gated one", () => {
const payload = buildPayload({
check: toolResult("read"),
invokedToolName: "read",
});
expect(payload.request.invokedToolName).toBeNull();
});
test("leaves the value empty for a bash check with no resolved command", () => {
expect(buildPayload({ check: toolResult("bash") }).request.value).toBe(
"",
);
});
test("lands the annotations slot empty", () => {
expect(buildPayload({ check: toolResult("read") }).annotations).toEqual(
[],
);
});
});
describe("the bash evidence", () => {
test("names the agent when one is known", () => {
expect(
buildPayload({
check: toolResult("read"),
agentName: "my-agent",
input: { path: "/src" },
formatter: makeFormatter(),
}).request.requester.agentName,
).toBe("my-agent");
});
test("carries the enclosing command when the gated unit is only part of it", () => {
expect(
findEvidence(
buildPayload({
check: toolResult("bash", { command: "rm -rf ." }),
input: { command: 'echo "hello" && rm -rf .' },
formatter: makeFormatter(),
}),
"full command",
),
).toEqual({
label: "full command",
text: 'echo "hello" && rm -rf .',
detail: null,
});
});
test("omits the enclosing command when it is the gated unit", () => {
expect(
findEvidence(
buildPayload({
check: toolResult("bash", { command: "git push" }),
input: { command: "git push" },
formatter: makeFormatter(),
}),
"full command",
),
).toBeUndefined();
});
test.each([
["input is undefined", undefined],
["input has no command field", { unrelated: "value" }],
["input command is empty", { command: "" }],
])("omits the enclosing command when %s", (_case, input) => {
expect(
findEvidence(
buildPayload({
check: toolResult("bash", { command: "git push" }),
input,
formatter: makeFormatter(),
}),
"full command",
),
).toBeUndefined();
});
test("adds no input preview, since the command is the value", () => {
expect(
findEvidence(
buildPayload({
check: toolResult("bash", { command: "git status" }),
input: { command: "git status" },
formatter: makeFormatter(),
}),
"input",
),
).toBeUndefined();
});
});
describe("mcp", () => {
test("makes the target the decision-relevant value", () => {
const payload = buildPayload({
check: mcpResult("server:query", { matchedPattern: "server:*" }),
formatter: makeFormatter(),
});
expect(payload.kind).toBe("mcp");
expect(payload.request.value).toBe("server:query");
expect(payload.request.matchedPattern).toBe("server:*");
});
test("carries the argument summary a registered formatter produced", () => {
expect(
findEvidence(
buildPayload({
check: mcpResult("exa:search"),
input: { tool: "exa:search", arguments: { query: "typescript" } },
formatter: makeFormatter(makeMcpLookup('with query: "typescript"')),
}),
"input",
),
).toEqual({
label: "input",
text: 'with query: "typescript"',
detail: null,
});
});
test("carries no evidence when the registered formatter declines", () => {
const noArgsLookup: ToolInputFormatterLookup = {
get: (name) => (name === "mcp" ? () => undefined : undefined),
};
expect(
buildPayload({
check: mcpResult("exa:search"),
input: { tool: "exa:search" },
formatter: makeFormatter(noArgsLookup),
}).evidence,
).toEqual([]);
});
test("carries no evidence when no formatter is provided", () => {
expect(
buildPayload({
check: mcpResult("exa:search"),
input: { tool: "exa:search", arguments: { query: "test" } },
}).evidence,
).toEqual([]);
});
});
describe("generic tools", () => {
test("carries the real input preview as evidence", () => {
expect(
findEvidence(
buildPayload({
check: toolResult("read"),
input: { path: "/src/foo.ts" },
formatter: makeFormatter(),
}),
"input",
)?.text,
).toContain("path '/src/foo.ts'");
});
test("carries no evidence when the formatter produces nothing", () => {
expect(
buildPayload({
check: toolResult("task"),
input: {},
formatter: makeFormatter(),
}).evidence,
).toEqual([]);
});
test("carries no evidence when no formatter is provided", () => {
expect(
buildPayload({ check: toolResult("task"), input: { path: "/src" } })
.evidence,
).toEqual([]);
});
});
});