mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
93 lines
2.6 KiB
TypeScript
93 lines
2.6 KiB
TypeScript
import type { Rule, RuleOrigin, Ruleset } from "./rule";
|
|
import type { PermissionState } from "./types";
|
|
|
|
/**
|
|
* Synthesize a single universal catch-all rule from the universal default.
|
|
*
|
|
* Produces one rule:
|
|
* `{ surface: "*", pattern: "*", action: universalDefault, layer: "default" }`
|
|
*
|
|
* Per-surface catch-alls (`bash["*"]`, `mcp["*"]`, etc.) are regular config
|
|
* rules. Their exact surface is selected before the universal fallback when the
|
|
* value-pattern specificity is tied.
|
|
*/
|
|
export function synthesizeDefaults(
|
|
universalDefault: PermissionState,
|
|
origin: RuleOrigin = "builtin",
|
|
): Ruleset {
|
|
return [
|
|
{
|
|
surface: "*",
|
|
pattern: "*",
|
|
action: universalDefault,
|
|
layer: "default",
|
|
origin,
|
|
},
|
|
];
|
|
}
|
|
|
|
/**
|
|
* MCP metadata operation targets that are auto-allowed when any explicit MCP
|
|
* allow rule exists in the config layer.
|
|
*/
|
|
const MCP_BASELINE_TARGETS: readonly string[] = [
|
|
"mcp_status",
|
|
"mcp_list",
|
|
"mcp_search",
|
|
"mcp_describe",
|
|
"mcp_connect",
|
|
];
|
|
|
|
/**
|
|
* Conditionally synthesize MCP baseline auto-allow rules.
|
|
*
|
|
* Emits allow rules for the 5 MCP metadata targets only when `configRules`
|
|
* contains at least one `surface: "mcp", action: "allow"` rule. This replicates
|
|
* the `hasAnyMcpAllowRule` heuristic as actual rules.
|
|
*
|
|
* When `permission["mcp"]` is `"allow"` (or `mcp["*"]` is `"allow"`), the
|
|
* synthesized config catch-all already covers all MCP targets — no separate
|
|
* baseline rules are needed (and this function is not called in that case).
|
|
*
|
|
* Baseline rules are placed BEFORE config rules in the composed array so
|
|
* that explicit config deny rules can still override them.
|
|
*
|
|
* All rules carry `layer: "baseline"`.
|
|
*/
|
|
export function synthesizeBaseline(configRules: Ruleset): Ruleset {
|
|
const hasAnyMcpAllow = configRules.some(
|
|
(r) => r.surface === "mcp" && r.action === "allow",
|
|
);
|
|
if (!hasAnyMcpAllow) {
|
|
return [];
|
|
}
|
|
return MCP_BASELINE_TARGETS.map(
|
|
(target): Rule => ({
|
|
surface: "mcp",
|
|
pattern: target,
|
|
action: "allow",
|
|
layer: "baseline",
|
|
origin: "baseline",
|
|
}),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Concatenate all rule layers into a single flat ruleset.
|
|
*
|
|
* Priority order (lowest → highest, i.e. earlier index → later index):
|
|
* defaults → baseline → config
|
|
*
|
|
* Session rules are NOT included here — they are appended at call-time inside
|
|
* `checkPermission()` so that the cached composed ruleset remains session-agnostic.
|
|
*
|
|
* `evaluate()` scans from the end, so later layers override earlier ones.
|
|
*/
|
|
export function composeRuleset(
|
|
defaults: Ruleset,
|
|
baseline: Ruleset,
|
|
config: Ruleset,
|
|
): Ruleset {
|
|
return [...defaults, ...baseline, ...config];
|
|
}
|