refactor(pi-permission-inner-cmd): dispatch commands through a handler registry

- replace the hardcoded timeout switch with an engine that iterates registered handlers
- extract the timeout logic into handlers/timeout.ts and add handlers/env.ts that defers env as non-transparent
- thread a partial-evidence bag so the engine exception log retains handler-derived values like innerCommand
- add CONTEXT.md with the transparent vs non-transparent wrapper glossary
- record ADR 0002: env always defers to the AI judge and is never unwrapped
This commit is contained in:
2026-08-12 00:12:41 +08:00
parent c00ae33031
commit 5134e85d32
8 changed files with 269 additions and 125 deletions
@@ -402,6 +402,36 @@ describe("authorizeInnerCommand — fail-closed deferrals", () => {
});
});
describe("authorizeInnerCommand — env wrapper", () => {
it("defers on an env wrapper with a debug log (non-transparent)", async () => {
const { verdict, log, check } = await run({
recoveredCommand: "env FOO=bar pnpm test",
states: { "pnpm test": "allow" },
});
expect(verdict.kind).toBe("defer");
// env is non-transparent: never unwrapped, so the inner command is not
// re-evaluated through the deterministic policy.
expect(check).toEqual([]);
expect(log).toEqual([
{
level: "debug",
event: "inner_cmd.env_non_transparent",
details: { command: "env FOO=bar pnpm test" },
},
]);
});
it("does not claim a command that only contains env later", async () => {
// Starts with printf, not env -> no handler claims it -> silent defer.
const { verdict, log } = await run({
recoveredCommand: "printf hi; env | sort",
states: {},
});
expect(verdict.kind).toBe("defer");
expect(log).toEqual([]);
});
});
describe("authorizeInnerCommand — exceptions defer with a debug log", () => {
it("defers when reading the session id throws (logs only safe data)", async () => {
const { verdict, log } = await run({