mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
80 lines
2.7 KiB
TypeScript
80 lines
2.7 KiB
TypeScript
import { dirname, sep } from "node:path";
|
|
|
|
import type { Ruleset } from "./rule";
|
|
import type { SessionApproval } from "./session-approval";
|
|
import type { SessionApprovalRecorder } from "./session-approval-recorder";
|
|
|
|
/**
|
|
* Ephemeral in-memory store of session-scoped permission approvals.
|
|
*
|
|
* Each approval is stored as a `Rule` with `action: "allow"`, making the
|
|
* ruleset directly usable with `evaluate()` — no custom matching engine needed.
|
|
*
|
|
* Cleared on session_shutdown — never persisted to disk.
|
|
*/
|
|
export class SessionRules implements SessionApprovalRecorder {
|
|
private rules: Ruleset = [];
|
|
|
|
/** Record a wildcard pattern as approved for the given surface. */
|
|
approve(surface: string, pattern: string): void {
|
|
this.rules.push({
|
|
surface,
|
|
pattern,
|
|
action: "allow",
|
|
layer: "session",
|
|
origin: "session",
|
|
});
|
|
}
|
|
|
|
/** Return a defensive copy of the current session ruleset. */
|
|
getRuleset(): Ruleset {
|
|
return [...this.rules];
|
|
}
|
|
|
|
/**
|
|
* Record all patterns from a `SessionApproval` value object.
|
|
*
|
|
* The loop lives here so callers never need to know whether an approval
|
|
* carries one pattern or many — they just tell the store to record it.
|
|
*/
|
|
recordSessionApproval(approval: SessionApproval): void {
|
|
for (const pattern of approval.patterns) {
|
|
this.approve(approval.surface, pattern);
|
|
}
|
|
}
|
|
|
|
/** Remove all session approvals. */
|
|
clear(): void {
|
|
this.rules = [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Derive the wildcard glob pattern to approve from a normalized path.
|
|
*
|
|
* Returns `<parent-dir>/*` so that `evaluate()` / `wildcardMatch()` matches
|
|
* all paths under the approved directory — identical semantics to the former
|
|
* `SessionApprovalCache` prefix matching, using the unified wildcard engine.
|
|
*
|
|
* For paths that already end with a separator (directories), the separator
|
|
* is treated as the directory boundary and `*` is appended directly.
|
|
*
|
|
* The path is expected to be the canonical (cwd-resolved, absolute) form used
|
|
* for policy matching, so the derived pattern matches the same policy values a
|
|
* later tool call produces. Callers that hold a working directory resolve the
|
|
* path to that form first; the function itself stays free of cwd state.
|
|
*/
|
|
export function deriveApprovalPattern(normalizedPath: string): string {
|
|
// If the path already ends with a separator, it's a directory — glob its contents.
|
|
if (normalizedPath.endsWith(sep)) {
|
|
return `${normalizedPath}*`;
|
|
}
|
|
const dir = dirname(normalizedPath);
|
|
if (dir === normalizedPath) {
|
|
// Root path — dirname('/') === '/'
|
|
return `${dir}*`;
|
|
}
|
|
const prefix = dir.endsWith(sep) ? dir : `${dir}${sep}`;
|
|
return `${prefix}*`;
|
|
}
|