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,86 @@
import { getToolInputPath } from "#src/access-intent/tool-input-path";
import type { PathNormalizer } from "#src/path-normalizer";
import type { ScopedPermissionResolver } from "#src/permission-resolver";
import { buildPathAskPayload } from "#src/presentation/path-ask-payload";
import { SessionApproval } from "#src/session-approval";
import { deriveApprovalPattern } from "#src/session-rules";
import type { ToolAccessExtractorLookup } from "#src/tool-access-extractor-registry";
import type { GateDescriptor, GateResult } from "./descriptor";
import { accessFactsFromPath } from "./helpers";
import type { ToolCallContext } from "./types";
/**
* Build a pure descriptor for the cross-cutting path permission gate (tools).
*
* Returns `null` when the gate does not apply (tool is not path-bearing,
* no extractable path, the `path` surface evaluates to `allow`, or no
* explicit `path` rule matched — i.e. only the universal default fired).
* Returns a `GateDescriptor` when the path matches a `deny` or `ask` rule.
*/
export function describePathGate(
tcc: ToolCallContext,
resolver: ScopedPermissionResolver,
normalizer: PathNormalizer,
extractors?: ToolAccessExtractorLookup,
): GateResult {
const filePath = getToolInputPath(tcc.toolName, tcc.input, extractors);
if (!filePath) return null;
// Emit an access-path intent so the resolver matches the lexical aliases
// *and* the canonical (symlink-resolved) form, the same set
// `external_directory` matches (#418, #486).
const accessPath = normalizer.forPath(filePath);
const check = resolver.resolve({
kind: "access-path",
surface: "path",
path: accessPath,
agentName: tcc.agentName ?? undefined,
});
if (check.state === "allow") return null;
// No explicit path rule matched — only the universal default fired.
// Skip the gate to preserve backward compatibility: configs without a
// "path" key should not trigger path-level prompts (#58).
if (check.matchedPattern === undefined) return null;
// Derive the approval pattern from the lexical absolute form so it matches
// the policy values a later call produces.
const pattern = deriveApprovalPattern(accessPath.value());
const payload = buildPathAskPayload({
toolName: tcc.toolName,
pathValue: filePath,
agentName: tcc.agentName,
matchedPattern: check.matchedPattern,
});
const descriptor: GateDescriptor = {
surface: "path",
input: { path: filePath },
payload,
sessionApproval: SessionApproval.single("path", pattern),
promptDetails: {
source: "tool_call",
agentName: tcc.agentName,
toolCallId: tcc.toolCallId,
toolName: tcc.toolName,
path: filePath,
accessIntent: accessFactsFromPath("path", accessPath),
},
logContext: {
source: "tool_call",
toolCallId: tcc.toolCallId,
toolName: tcc.toolName,
agentName: tcc.agentName,
path: filePath,
},
decision: {
surface: "path",
value: filePath,
},
preCheck: check,
};
return descriptor;
}