/** * design/127 — **参数级权限规则 DSL**(CC 2.1.201 追平批 γ)。声明式规则字符串 → {@link ToolPolicy} * 编译器:`"Tool"`(裸)/ `"Tool(param:pattern)"`(参数级,`*` 通配全串锚定)。CC ground truth * 行号级:解析 `Lh` :55214 · 通配 `ZMr` :55196 · 参数级 matcher `gnn` :595123 · 评估序 `G1e` :595272。 * * 架构边界(design/127 §0,重判过的「core 不长 rule interpreter」):这是纯函数式、opt-in、零 * run-loop 介入的「声明式数据→ToolPolicy」工厂(`createCoarseCommandNamePolicy` 同形态先例)。 * core 不读设置文件、不管 source 层级合并 —— Settings-loader 把各层合并成**单一规则表**后编译 * **一个** policy(不要按层各编译再组合:combinePolicies 的 fold 里 ask 胜 allow,跨 policy 的 * 显式 allow 规则会被另一 policy 的 ask 吞掉 —— codex 127 审 B3)。 * * 🔴 与 CC 的三处有意差(design/127 r2): * - CC 参数级 generic 道无 allow lane(gnn 只有 deny/ask)—— 我们同样:`allow` + 参数级 content = * 无效规则;auto-allow 只来自裸工具规则。 * - CC 对尾缀垃圾静默降级为裸名(Lh `n !== len-1`)—— `parsePermissionRule` 保持 CC parity,但 * **工厂 validator strict**:降级即 invalid(fail-fast 世界观,写错的规则不能静默变宽)。 * - 已知主字段参数规则(如 `Bash(command:…)`)默认 **reject**:通配是 trim 后**整串 lexical** * 匹配,不是 shell AST / 前缀 lane —— `Bash(command:git push*)` 匹配不了 `cd /x && git push`, * 「以为挡住了」比没挡更危险(codex 127 审 M2)。`primaryFieldGeneric:"allow-lexical"` 显式解锁。 */ import type { ToolPolicy } from "./tool-policy.js"; /** [K-PLATFORM-SWEEP] MINOR#11: Bash's REAL non-primary arg names, accepted as generic param rules * (`Bash(timeout:...)`). Exported SOLELY so a lockstep test can assert it against the live Bash tool * schema — a schema param added without updating this set would compile to silently-never-matching * rules (the exact drift class the Opus 复审 m note warned about). */ export declare const BASH_GENERIC_PARAMS: ReadonlySet; export interface PermissionRule { /** CC 语法:`"Tool"` 或 `"Tool(param:pattern)"`。`Tool()`/`Tool(*)` 归一为裸规则;括号可 `\(`/`\)` 转义。 */ rule: string; behavior: "deny" | "ask" | "allow"; /** Provenance(codex 127 审 M7):进 deny/ask 的 message 供审计(e.g. `"policySettings"`)。层级 * 优先级/tighten-only 语义在 Settings-loader,不在这里。 */ source?: string; } /** Parse result — CC `Lh` parity (INCLUDING the silent bare-name downgrade on malformed parens; the * factory validator is strict on top of this, see module doc). */ export interface ParsedPermissionRule { toolName: string; ruleContent?: string; } export interface PermissionRuleIssue { rule: string; /** `invalid.*` = 语法/语义错;`unsupported.*` = CC 合法但 v1 不支持的 lane(codex 127 审 B5:独立 * 分类,绝不静默 no-op —— `Bash(npm run:*)` 被当 generic 参数规则接受后永不匹配是最危险的失败形态)。 */ code: "invalid.empty_tool" | "invalid.paren" | "invalid.param_split" | "invalid.allow_param_rule" | "invalid.primary_field" | "invalid.cap_exceeded" | "unsupported.bash_prefix" | "unsupported.file_glob" | "unsupported.mcp_paren"; message: string; } export interface PermissionRuleCaps { maxRules: number; maxRuleLength: number; maxPatternLength: number; maxStars: number; /** Scalar arg values longer than this are NOT matched textually — the rule **fail-closes** to its * lane (a deny rule targeting an oversized value denies; an ask rule asks). 租户规则 × 模型巨串 * 的热路径成本上界(codex 127 审 M6)。 */ maxScalarValueChars: number; } export interface PermissionRulePolicyOptions { /** Invalid/unsupported rules: `"throw"`(default — 部署作者写错要立刻知道)aggregates ALL issues * into one error; `"skip"` drops them (pair with {@link validatePermissionRules} for a report, and/or * supply {@link onIssue}). Exception(codex 实现审 M3):`invalid.cap_exceeded` on the WHOLE rule set * (maxRules) throws EVEN under `"skip"` — skipping it would compile an EMPTY table and fail-open. */ onInvalidRule?: "throw" | "skip"; /** Called once per dropped rule under `"skip"` (audit surface — silent drops are how a deployment * discovers, months later, that its deny never existed). Default: one aggregated `console.warn`. */ onIssue?: (issue: PermissionRuleIssue) => void; /** What the policy returns when NO rule matches. Default `"allow"` = 本 policy 无意见(组合语义)。 * default-closed 部署用 `"ask"`/`"deny"` —— 必须在这 INSIDE the policy(codex 127 审 B3:链尾 * ask policy 会在 fold 里吞掉本 policy 的显式 allow)。 */ defaultAction?: "allow" | "ask" | "deny"; /** 已知主字段参数规则(`Bash(command:…)` 等)的处置。默认 `"reject"`(整串 lexical ≠ 前缀/AST, * 假安全感);`"allow-lexical"` 显式接受并按 generic 语义匹配。 */ primaryFieldGeneric?: "reject" | "allow-lexical"; caps?: Partial; } /** * Parse one rule string — CC `Lh` :55214 parity, including its lenient downgrades (malformed parens / * trailing garbage ⇒ the WHOLE string becomes the bare tool name). The strict complement lives in * {@link validatePermissionRules} — use that (or the factory) to refuse the downgrades. */ export declare function parsePermissionRule(rule: string): ParsedPermissionRule; /** * Linear-time `*` glob match (design/127 M6: NOT a RegExp — patterns may come from tenant-supplied * Settings layers; the classic two-pointer walk has zero ReDoS surface and the same semantics as CC's * anchored `^…$`+dotAll construction: every non-`*` char is literal, `*` spans anything incl. newlines). */ export declare function wildcardMatch(pattern: string, value: string): boolean; /** Dry-run 校验/迁移报告(codex 127 审 B5):不 throw,返回全部 issues(含 `unsupported.*` 分类, * 供从 CC settings 迁移的部署逐条对照)。 */ export declare function validatePermissionRules(rules: PermissionRule[], opts?: Pick): PermissionRuleIssue[]; /** * Compile declarative permission rules into ONE {@link ToolPolicy} (design/127). Evaluation order * inside the policy (CC `G1e` :595272 isomorph): bare deny → param deny → bare ask → param ask → * bare allow → `defaultAction`. `check()` is idempotent and side-effect-free (combinePolicies 契约); * all parsing/validation happens HERE, the hot path never re-parses. * * ⚠️ Composition note (codex 127 审 B3): an `allow` from this policy is "no objection", NOT a * cross-policy override — another policy's ask/deny still outranks it in the combinePolicies fold. * Express default-closed via `defaultAction`, never via a trailing always-ask policy. */ export declare function createPermissionRulePolicy(rules: PermissionRule[], opts?: PermissionRulePolicyOptions): ToolPolicy; //# sourceMappingURL=permission-rules.d.ts.map