@sema-agent/core 7.8.0 → 7.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -1
- package/dist/agents/subagent.d.ts +1 -1
- package/dist/core/ask-origin.d.ts +12 -1
- package/dist/core/ask-origin.js +5 -1
- package/dist/core/checkpoint-store.d.ts +34 -28
- package/dist/core/gate-lanes.js +49 -23
- package/dist/core/gate-outcome.d.ts +8 -4
- package/dist/core/gate-outcome.js +2 -1
- package/dist/core/hooks.d.ts +56 -31
- package/dist/core/permission-rule-consent.d.ts +29 -36
- package/dist/core/permission-rule-consent.js +110 -60
- package/dist/core/permission-rule-model.d.ts +254 -55
- package/dist/core/permission-rule-model.js +323 -43
- package/dist/core/permission-rule-org.d.ts +9 -8
- package/dist/core/permission-rule-org.js +6 -15
- package/dist/core/permission-rule-provider.d.ts +10 -6
- package/dist/core/permission-rule-provider.js +12 -8
- package/dist/core/permission-rule-session.d.ts +7 -6
- package/dist/core/permission-rule-session.js +29 -10
- package/dist/core/permission-rule-store.d.ts +40 -18
- package/dist/core/permission-rule-store.js +68 -42
- package/dist/core/permission-rule-sync.d.ts +9 -3
- package/dist/core/permission-rule-sync.js +29 -25
- package/dist/core/permission-rule-syntax.d.ts +30 -0
- package/dist/core/permission-rule-syntax.js +44 -0
- package/dist/core/permission-rules.d.ts +55 -33
- package/dist/core/permission-rules.js +65 -55
- package/dist/core/persisted-rule-arms.d.ts +56 -0
- package/dist/core/persisted-rule-arms.js +48 -0
- package/dist/core/runner/contracts.d.ts +21 -2
- package/dist/core/runner/permission-rule-lanes.d.ts +33 -26
- package/dist/core/runner/permission-rule-lanes.js +27 -21
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-safety-scan.js +8 -2
- package/dist/core/runner/prepare-task.js +1 -1
- package/dist/core/runner/resume-admission.d.ts +53 -0
- package/dist/core/runner/resume-admission.js +83 -0
- package/dist/core/runner/resume-apply.d.ts +50 -0
- package/dist/core/runner/resume-apply.js +184 -0
- package/dist/core/runner/resume-checkpoint-screen.d.ts +18 -0
- package/dist/core/runner/resume-checkpoint-screen.js +108 -0
- package/dist/core/runner/resume-claim.d.ts +32 -0
- package/dist/core/runner/resume-claim.js +27 -0
- package/dist/core/runner/resume-internals-and-config.d.ts +33 -0
- package/dist/core/runner/resume-internals-and-config.js +50 -0
- package/dist/core/runner/resume-policy-outcome.d.ts +31 -0
- package/dist/core/runner/resume-policy-outcome.js +127 -0
- package/dist/core/runner/resume-preflight.d.ts +40 -0
- package/dist/core/runner/resume-preflight.js +122 -0
- package/dist/core/runner/resume-review-outcome.d.ts +30 -0
- package/dist/core/runner/resume-review-outcome.js +88 -0
- package/dist/core/runner/run-harness-handlers.js +1 -1
- package/dist/core/runner/runtask.d.ts +20 -0
- package/dist/core/runner/runtask.js +85 -734
- package/dist/core/runner/tool-end-body.d.ts +12 -5
- package/dist/core/runner/tool-end-body.js +5 -5
- package/dist/core/runner/tool-output-projection.d.ts +9 -6
- package/dist/core/runner/tool-output-projection.js +1 -18
- package/dist/core/runner/turn-attachments.d.ts +2 -2
- package/dist/core/store-contracts/permission-rule-sync-contract.js +29 -10
- package/dist/core/tool-roster.d.ts +9 -0
- package/dist/core/tool-roster.js +15 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/stores/file/checkpoint-store.js +1 -1
- package/dist/stores/file/permission-rule-store.d.ts +28 -23
- package/dist/stores/file/permission-rule-store.js +64 -16
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
- package/dist/tools/fs/fs-pdf.d.ts +1 -1
- package/dist/tools/fs/notebook.d.ts +1 -1
- package/dist/tools/fs/safety.d.ts +1 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +43 -11
|
@@ -9,7 +9,33 @@
|
|
|
9
9
|
* **一个** policy(不要按层各编译再组合:combinePolicies 的 fold 里 ask 胜 allow,跨 policy 的
|
|
10
10
|
* 显式 allow 规则会被另一 policy 的 ask 吞掉)。
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* ## The CONTENT-FORM command lane (the shell tool's own grammar)
|
|
13
|
+
*
|
|
14
|
+
* A `Bash(<command>)` / `Bash(<prefix>:*)` / `Bash(<body> *)` rule is not a `param:pattern` rule — its
|
|
15
|
+
* content is a COMMAND SPELLING in the persisted-rule model's grammar (`permission-rule-model.ts`), and
|
|
16
|
+
* this compiler now compiles it AS SUCH for the two tightening behaviors: the text goes through the ONE
|
|
17
|
+
* validator (`parseRuleText(text, behavior)` — the tightening floor, so `Bash(node:*)` as a deny is a
|
|
18
|
+
* legitimately wide tightening) and the call is judged with the ONE tightening reach
|
|
19
|
+
* (`ruleReachesProgramRun`: the whole command line or any program run in a connector chain — upstream's
|
|
20
|
+
* per-subcommand deny, the same instrument the org partition and the personal store's deny/ask rows use).
|
|
21
|
+
* It sits in the field-level tier of the evaluation order — right after the bare deny (resp. ask) — which
|
|
22
|
+
* is upstream's placement (`bs()` whole-tool deny → `AN(…,"deny")` field-level deny → `ph()` whole-tool
|
|
23
|
+
* ask → `AN(…,"ask")` field-level ask → allow). A content-form ALLOW is refused (`unsupported.bash_prefix`):
|
|
24
|
+
* a standing approval is a CONSENT record and lives in the persisted-rule store (the import door carries
|
|
25
|
+
* a settings allow list there); inside a policy fold an allow is "no objection" and would clear nothing.
|
|
26
|
+
*
|
|
27
|
+
* The PATH lane is the same shape for every tool the catalog declares a path target for: a
|
|
28
|
+
* `Edit(~/.sema/settings.json)` / `Write(src/**)` / `Read(//etc/**)` deny or ask compiles through the same
|
|
29
|
+
* validator (the model's path grammar — the `//abs-dir/**` directory form and upstream's pattern form:
|
|
30
|
+
* `//` absolute, `~/` home, `/` project root, otherwise working-directory relative; `*` within a segment,
|
|
31
|
+
* `**` across) and is judged with `pathRuleReaches` on the call's PROTECTIVE path target (design/388: the
|
|
32
|
+
* object face's declaration, the catalog's for the name as the floor). The bases a pattern resolves
|
|
33
|
+
* against are the deployment's (`pathBases`); a pattern whose base the policy was not given is refused at
|
|
34
|
+
* compile time (`unsupported.path_base`) rather than left to reach nothing at run time — a deny that
|
|
35
|
+
* silently matches nothing is the failure form this module's header calls the most dangerous one. A
|
|
36
|
+
* path-form ALLOW stays refused (`unsupported.file_glob`): the Read directory allow is the consent store's.
|
|
37
|
+
*
|
|
38
|
+
* 🔴 与 CC 的有意差(design/127 r2):
|
|
13
39
|
* - CC 参数级 generic 道无 allow lane(gnn 只有 deny/ask)—— 我们同样:`allow` + 参数级 content =
|
|
14
40
|
* 无效规则;auto-allow 只来自裸工具规则。
|
|
15
41
|
* - CC 对尾缀垃圾静默降级为裸名(Lh `n !== len-1`)—— `parsePermissionRule` 保持 CC parity,但
|
|
@@ -19,6 +45,9 @@
|
|
|
19
45
|
* 「以为挡住了」比没挡更危险。`primaryFieldGeneric:"allow-lexical"` 显式解锁。
|
|
20
46
|
*/
|
|
21
47
|
import type { NamedToolPolicy } from "./tool-policy.js";
|
|
48
|
+
import { parsePermissionRule, type ParsedPermissionRule } from "./permission-rule-syntax.js";
|
|
49
|
+
import { type PathRuleBases } from "./permission-rule-model.js";
|
|
50
|
+
export { parsePermissionRule, type ParsedPermissionRule };
|
|
22
51
|
/** [K-PLATFORM-SWEEP] MINOR#11: Bash's REAL non-primary arg names, accepted as generic param rules
|
|
23
52
|
* (`Bash(timeout:...)`). Exported SOLELY so a lockstep test can assert it against the live Bash tool
|
|
24
53
|
* schema — a schema param added without updating this set would compile to silently-never-matching
|
|
@@ -32,25 +61,29 @@ export interface PermissionRule {
|
|
|
32
61
|
* 优先级/tighten-only 语义在 Settings-loader,不在这里。 */
|
|
33
62
|
source?: string;
|
|
34
63
|
}
|
|
35
|
-
/** Parse result — CC `Lh` parity (INCLUDING the silent bare-name downgrade on malformed parens; the
|
|
36
|
-
* factory validator is strict on top of this, see module doc). */
|
|
37
|
-
export interface ParsedPermissionRule {
|
|
38
|
-
toolName: string;
|
|
39
|
-
ruleContent?: string;
|
|
40
|
-
}
|
|
41
64
|
export interface PermissionRuleIssue {
|
|
42
65
|
rule: string;
|
|
43
66
|
/** `invalid.*` = 语法/语义错;`unsupported.*` = CC 合法但 v1 不支持的 lane(独立
|
|
44
67
|
* 分类,绝不静默 no-op —— `Bash(npm run:*)` 被当 generic 参数规则接受后永不匹配是最危险的失败形态)。 */
|
|
45
|
-
code: "invalid.empty_tool" | "invalid.paren" | "invalid.param_split" | "invalid.allow_param_rule" | "invalid.primary_field" | "invalid.cap_exceeded"
|
|
68
|
+
code: "invalid.empty_tool" | "invalid.paren" | "invalid.param_split" | "invalid.allow_param_rule" | "invalid.primary_field" | "invalid.cap_exceeded"
|
|
69
|
+
/** A content-form Bash ALLOW rule (`Bash(npm run:*)` in an allow list): a standing approval is a consent
|
|
70
|
+
* record and belongs to the persisted-rule store, not to a policy fold. Deny/ask content rules COMPILE. */
|
|
71
|
+
| "unsupported.bash_prefix"
|
|
72
|
+
/** A content-form Bash deny/ask rule the ONE rule validator refuses (a redirection, a substitution, a
|
|
73
|
+
* mid-pattern star, control characters…) — the message carries the validator's own code and sentence. */
|
|
74
|
+
| "invalid.command_rule"
|
|
46
75
|
/** design/388 B15 — the closed rule-lint set over a tool's RULE FACE (derived scalar vocabulary; a
|
|
47
76
|
* declaration only narrows it): the named parameter is not one the tool carries; it is carried but
|
|
48
77
|
* is an object/array (a rule on it can never match); the tool is unknown to the resolver a leg
|
|
49
78
|
* supplied (absent resolver ⇒ the catalog alone judges and an unknown tool stays permissive). */
|
|
50
79
|
| "unsupported.param_unknown" | "unsupported.param_non_scalar" | "unsupported.tool_unknown"
|
|
51
|
-
/**
|
|
52
|
-
*
|
|
53
|
-
| "unsupported.
|
|
80
|
+
/** A path-form ALLOW rule (`Edit(src/**)` / `Read(//x/**)` in an allow list): a standing approval by
|
|
81
|
+
* path is the consent store's (the Read directory form imports there); deny/ask path rules COMPILE. */
|
|
82
|
+
| "unsupported.file_glob"
|
|
83
|
+
/** A path-form deny/ask whose spelling needs a base this policy was not given (`/x` needs
|
|
84
|
+
* `pathBases.root`; a relative pattern needs `pathBases.cwd` or `root`) — refused rather than compiled
|
|
85
|
+
* into a rule that reaches nothing. */
|
|
86
|
+
| "unsupported.path_base" | "unsupported.mcp_paren"
|
|
54
87
|
/** A parenthesised rule whose NAME is a covering spelling in a non-MCP protocol namespace
|
|
55
88
|
* (`a2a__<peer>__*(x:y)`, `a2a__<peer>(x:y)`). The param DSL is keyed by EXACT tool name, and a
|
|
56
89
|
* covering spelling is not one — no mounted tool can ever bear it, so the rule compiled clean and
|
|
@@ -89,19 +122,6 @@ export interface PermissionRulePolicyOptions {
|
|
|
89
122
|
/** 已知主字段参数规则(`Bash(command:…)` 等)的处置。默认 `"reject"`(整串 lexical ≠ 前缀/AST,
|
|
90
123
|
* 假安全感);`"allow-lexical"` 显式接受并按 generic 语义匹配。 */
|
|
91
124
|
primaryFieldGeneric?: "reject" | "allow-lexical";
|
|
92
|
-
/**
|
|
93
|
-
* design/179 D7 — where a Bash PREFIX rule (`Bash(npm run:*)`) belongs.
|
|
94
|
-
*
|
|
95
|
-
* This DSL is a pure function with no access to a deployment's wiring, so it cannot discover on its own
|
|
96
|
-
* that a persisted-rule store exists; the choice has to be said out loud. `"reject"` is the default and
|
|
97
|
-
* keeps this file's behavior byte-identical to what it was: a prefix rule is an `unsupported.bash_prefix`
|
|
98
|
-
* issue, because a generic parameter matcher would compile it into a rule that never matches anything.
|
|
99
|
-
* `"rules-store"` says the deployment routes that form through the persisted allow-rule lane instead —
|
|
100
|
-
* the issue is then re-coded to {@link PermissionRuleIssue.code} `unsupported.bash_prefix_rules_store`,
|
|
101
|
-
* which reads as "handled elsewhere" rather than "you wrote something wrong". Either way the rule does
|
|
102
|
-
* NOT enter this policy: the two lanes stay separate, and only the report changes.
|
|
103
|
-
*/
|
|
104
|
-
bashPrefixLane?: "reject" | "rules-store";
|
|
105
125
|
caps?: Partial<PermissionRuleCaps>;
|
|
106
126
|
/**
|
|
107
127
|
* design/388 B15 — the leg's RULE-FACE resolver (typically `ruleFacesFromRoster(prepared.toolRoster)`): with it
|
|
@@ -110,13 +130,14 @@ export interface PermissionRulePolicyOptions {
|
|
|
110
130
|
* carry stays permissive (a rule for it compiles unjudged).
|
|
111
131
|
*/
|
|
112
132
|
ruleFaces?: (toolName: string) => RuleFaceView | undefined;
|
|
133
|
+
/**
|
|
134
|
+
* The bases a path-form deny/ask pattern resolves against at match time: `root` for `/…` (the task's
|
|
135
|
+
* project root), `cwd` for a relative pattern (absent ⇒ `root`), `home` for `~/…` (absent ⇒ the process
|
|
136
|
+
* owner's home). Facts of the DEPLOYMENT and the task, never of the rule — a pattern is stored as
|
|
137
|
+
* spelled. A rule whose base is absent here is refused at compile time (`unsupported.path_base`).
|
|
138
|
+
*/
|
|
139
|
+
pathBases?: PathRuleBases;
|
|
113
140
|
}
|
|
114
|
-
/**
|
|
115
|
-
* Parse one rule string — CC `Lh` :55214 parity, including its lenient downgrades (malformed parens /
|
|
116
|
-
* trailing garbage ⇒ the WHOLE string becomes the bare tool name). The strict complement lives in
|
|
117
|
-
* {@link validatePermissionRules} — use that (or the factory) to refuse the downgrades.
|
|
118
|
-
*/
|
|
119
|
-
export declare function parsePermissionRule(rule: string): ParsedPermissionRule;
|
|
120
141
|
/**
|
|
121
142
|
* Linear-time `*` glob match (design/127 M6: NOT a RegExp — patterns may come from tenant-supplied
|
|
122
143
|
* Settings layers; the classic two-pointer walk has zero ReDoS surface and the same semantics as CC's
|
|
@@ -168,11 +189,12 @@ export declare function catalogRuleFaceView(toolName: string): RuleFaceView | un
|
|
|
168
189
|
export declare const fileToolParamVocabulary: (canonicalTool: string) => ReadonlySet<string> | undefined;
|
|
169
190
|
/** Dry-run 校验/迁移报告:不 throw,返回全部 issues(含 `unsupported.*` 分类,
|
|
170
191
|
* 供从 CC settings 迁移的部署逐条对照)。 */
|
|
171
|
-
export declare function validatePermissionRules(rules: PermissionRule[], opts?: Pick<PermissionRulePolicyOptions, "primaryFieldGeneric" | "caps" | "
|
|
192
|
+
export declare function validatePermissionRules(rules: PermissionRule[], opts?: Pick<PermissionRulePolicyOptions, "primaryFieldGeneric" | "caps" | "ruleFaces" | "pathBases">): PermissionRuleIssue[];
|
|
172
193
|
/**
|
|
173
194
|
* Compile declarative permission rules into ONE {@link ToolPolicy} (design/127). Evaluation order
|
|
174
|
-
* inside the policy (CC `G1e` :595272 isomorph): bare deny →
|
|
175
|
-
* bare allow → `defaultAction
|
|
195
|
+
* inside the policy (CC `G1e` :595272 isomorph): bare deny → command deny → param deny → bare ask →
|
|
196
|
+
* command ask → param ask → bare allow → `defaultAction` (the content-form command tiers are the
|
|
197
|
+
* field-level tiers of upstream's arbiter, beside the generic param tiers). `check()` is idempotent and side-effect-free (combinePolicies 契约);
|
|
176
198
|
* all parsing/validation happens HERE, the hot path never re-parses.
|
|
177
199
|
*
|
|
178
200
|
* ⚠️ Composition note: an `allow` from this policy is "no objection", NOT a
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { MCP_NAMESPACE, protocolOf } from "./protocol-table.js";
|
|
2
2
|
import { catalogRuleFaceOf, pathTargetOf } from "./tool-registry.js";
|
|
3
|
+
import { indexOfUnescaped, lastIndexOfUnescaped, parsePermissionRule } from "./permission-rule-syntax.js";
|
|
4
|
+
import { isUsablePathBase, lexicalNormalAbsolutePathOf, parseRuleText, pathRuleReaches, ruleReachesProgramRun } from "./permission-rule-model.js";
|
|
5
|
+
import { pathTargetValue, protectivePathTargetOf } from "./tool-registry.js";
|
|
6
|
+
export { parsePermissionRule };
|
|
3
7
|
export const BASH_GENERIC_PARAMS = new Set(catalogRuleFaceOf("Bash")?.params ?? []);
|
|
4
8
|
const DEFAULT_CAPS = {
|
|
5
9
|
maxRules: 256,
|
|
@@ -8,50 +12,6 @@ const DEFAULT_CAPS = {
|
|
|
8
12
|
maxStars: 16,
|
|
9
13
|
maxScalarValueChars: 16_384,
|
|
10
14
|
};
|
|
11
|
-
function indexOfUnescaped(s, ch) {
|
|
12
|
-
for (let i = 0; i < s.length; i++) {
|
|
13
|
-
if (s[i] !== ch)
|
|
14
|
-
continue;
|
|
15
|
-
let backslashes = 0;
|
|
16
|
-
for (let j = i - 1; j >= 0 && s[j] === "\\"; j--)
|
|
17
|
-
backslashes++;
|
|
18
|
-
if (backslashes % 2 === 0)
|
|
19
|
-
return i;
|
|
20
|
-
}
|
|
21
|
-
return -1;
|
|
22
|
-
}
|
|
23
|
-
function lastIndexOfUnescaped(s, ch) {
|
|
24
|
-
for (let i = s.length - 1; i >= 0; i--) {
|
|
25
|
-
if (s[i] !== ch)
|
|
26
|
-
continue;
|
|
27
|
-
let backslashes = 0;
|
|
28
|
-
for (let j = i - 1; j >= 0 && s[j] === "\\"; j--)
|
|
29
|
-
backslashes++;
|
|
30
|
-
if (backslashes % 2 === 0)
|
|
31
|
-
return i;
|
|
32
|
-
}
|
|
33
|
-
return -1;
|
|
34
|
-
}
|
|
35
|
-
function unescapeRuleContent(s) {
|
|
36
|
-
return s.replaceAll("\\(", "(").replaceAll("\\)", ")").replaceAll("\\\\", "\\");
|
|
37
|
-
}
|
|
38
|
-
export function parsePermissionRule(rule) {
|
|
39
|
-
const open = indexOfUnescaped(rule, "(");
|
|
40
|
-
if (open === -1)
|
|
41
|
-
return { toolName: rule.trim() };
|
|
42
|
-
const close = lastIndexOfUnescaped(rule, ")");
|
|
43
|
-
if (close === -1 || close <= open)
|
|
44
|
-
return { toolName: rule.trim() };
|
|
45
|
-
if (close !== rule.length - 1)
|
|
46
|
-
return { toolName: rule.trim() };
|
|
47
|
-
const name = rule.substring(0, open);
|
|
48
|
-
const content = rule.substring(open + 1, close);
|
|
49
|
-
if (!name)
|
|
50
|
-
return { toolName: rule.trim() };
|
|
51
|
-
if (content === "" || content === "*")
|
|
52
|
-
return { toolName: name.trim() };
|
|
53
|
-
return { toolName: name.trim(), ruleContent: unescapeRuleContent(content) };
|
|
54
|
-
}
|
|
55
15
|
export function wildcardMatch(pattern, value) {
|
|
56
16
|
let p = 0;
|
|
57
17
|
let v = 0;
|
|
@@ -125,7 +85,7 @@ function countStars(s) {
|
|
|
125
85
|
n++;
|
|
126
86
|
return n;
|
|
127
87
|
}
|
|
128
|
-
function compile(rules, caps, primaryFieldGeneric,
|
|
88
|
+
function compile(rules, caps, primaryFieldGeneric, ruleFaces, pathBases = {}) {
|
|
129
89
|
const faceOf = (toolName) => {
|
|
130
90
|
if (ruleFaces !== undefined) {
|
|
131
91
|
const face = ruleFaces(toolName);
|
|
@@ -178,7 +138,7 @@ function compile(rules, caps, primaryFieldGeneric, bashPrefixLane = "reject", ru
|
|
|
178
138
|
`Write the parenthesised rule against one exact tool name, or drop the parentheses to keep the covering rule.`);
|
|
179
139
|
continue;
|
|
180
140
|
}
|
|
181
|
-
const entry = byTool.get(toolName) ?? { bare: {}, param: { deny: [], ask: [] } };
|
|
141
|
+
const entry = byTool.get(toolName) ?? { bare: {}, param: { deny: [], ask: [] }, content: { deny: [], ask: [] } };
|
|
182
142
|
if (parsed.ruleContent === undefined) {
|
|
183
143
|
if (isNamespacedCoveringRuleName(toolName)) {
|
|
184
144
|
namespacedCovering[r.behavior].push({ ruleName: toolName, ruleText: text, ...(r.source !== undefined ? { source: r.source } : {}) });
|
|
@@ -194,12 +154,17 @@ function compile(rules, caps, primaryFieldGeneric, bashPrefixLane = "reject", ru
|
|
|
194
154
|
const paramPart = colon > 0 ? content.slice(0, colon).trim() : "";
|
|
195
155
|
const looksLikeParamRule = colon > 0 && /^[A-Za-z_][A-Za-z0-9_]*$/.test(paramPart) && BASH_GENERIC_PARAMS.has(paramPart);
|
|
196
156
|
if (!looksLikeParamRule || content.endsWith(":*")) {
|
|
197
|
-
if (
|
|
198
|
-
bad(text, "unsupported.
|
|
157
|
+
if (r.behavior === "allow") {
|
|
158
|
+
bad(text, "unsupported.bash_prefix", "a Bash command ALLOW rule (`Bash(npm run:*)`, `Bash(git *)`) is a standing approval and belongs to the persisted-rule store (import it, or approve the command once and keep the rule) — a policy fold compiles command DENY/ASK rules only");
|
|
159
|
+
continue;
|
|
199
160
|
}
|
|
200
|
-
|
|
201
|
-
|
|
161
|
+
const parsedCommand = parseRuleText(text, r.behavior);
|
|
162
|
+
if ("reject" in parsedCommand) {
|
|
163
|
+
bad(text, "invalid.command_rule", `${parsedCommand.reject.code}: ${parsedCommand.reject.message}`);
|
|
164
|
+
continue;
|
|
202
165
|
}
|
|
166
|
+
entry.content[r.behavior].push({ parsed: parsedCommand.rule, ruleText: text, ...(r.source !== undefined ? { source: r.source } : {}) });
|
|
167
|
+
byTool.set(toolName, entry);
|
|
203
168
|
continue;
|
|
204
169
|
}
|
|
205
170
|
}
|
|
@@ -211,7 +176,30 @@ function compile(rules, caps, primaryFieldGeneric, bashPrefixLane = "reject", ru
|
|
|
211
176
|
}
|
|
212
177
|
const face = resolved.face;
|
|
213
178
|
if (face !== undefined && face.pathTargeting === true && colon <= 0) {
|
|
214
|
-
|
|
179
|
+
if (r.behavior === "allow") {
|
|
180
|
+
bad(text, "unsupported.file_glob", "a path-form ALLOW rule (`Edit(src/**)`, `Read(//x/**)`) is a standing approval and belongs to the persisted-rule store (the Read directory form imports there) — a policy fold compiles path DENY/ASK rules only");
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const parsedPath = parseRuleText(text, r.behavior);
|
|
184
|
+
if ("reject" in parsedPath) {
|
|
185
|
+
bad(text, "invalid.command_rule", `${parsedPath.reject.code}: ${parsedPath.reject.message}`);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const pattern = parsedPath.rule.command;
|
|
189
|
+
if (parsedPath.rule.match === "path" && !pattern.startsWith("//")) {
|
|
190
|
+
const needsRoot = pattern.startsWith("/");
|
|
191
|
+
const base = pattern.startsWith("~/") ? (pathBases.home ?? "/") : needsRoot ? pathBases.root : (pathBases.cwd ?? pathBases.root);
|
|
192
|
+
if (!isUsablePathBase(base)) {
|
|
193
|
+
bad(text, "unsupported.path_base", pattern.startsWith("~/")
|
|
194
|
+
? `"${text}" is relative to the home directory, and this policy's \`pathBases.home\` is not an absolute path — the rule would reach nothing`
|
|
195
|
+
: needsRoot
|
|
196
|
+
? `"${text}" is relative to the project root, and this policy was given no absolute \`pathBases.root\` to resolve it against — the rule would reach nothing`
|
|
197
|
+
: `"${text}" is relative to the working directory, and this policy was given no absolute \`pathBases.cwd\` (or \`root\`) to resolve it against — the rule would reach nothing`);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
entry.content[r.behavior].push({ parsed: parsedPath.rule, ruleText: text, ...(r.source !== undefined ? { source: r.source } : {}) });
|
|
202
|
+
byTool.set(toolName, entry);
|
|
215
203
|
continue;
|
|
216
204
|
}
|
|
217
205
|
if (face?.params !== undefined && colon > 0) {
|
|
@@ -257,7 +245,7 @@ function compile(rules, caps, primaryFieldGeneric, bashPrefixLane = "reject", ru
|
|
|
257
245
|
}
|
|
258
246
|
export function validatePermissionRules(rules, opts) {
|
|
259
247
|
const caps = { ...DEFAULT_CAPS, ...opts?.caps };
|
|
260
|
-
return compile(rules, caps, opts?.primaryFieldGeneric ?? "reject", opts?.
|
|
248
|
+
return compile(rules, caps, opts?.primaryFieldGeneric ?? "reject", opts?.ruleFaces, opts?.pathBases).issues;
|
|
261
249
|
}
|
|
262
250
|
function isPlainObjectArgs(args) {
|
|
263
251
|
return typeof args === "object" && args !== null && !Array.isArray(args);
|
|
@@ -287,7 +275,8 @@ function ruleMessage(kind, ruleText, source) {
|
|
|
287
275
|
}
|
|
288
276
|
export function createPermissionRulePolicy(rules, opts) {
|
|
289
277
|
const caps = { ...DEFAULT_CAPS, ...opts?.caps };
|
|
290
|
-
const
|
|
278
|
+
const pathBases = opts?.pathBases ?? {};
|
|
279
|
+
const { byTool, namespacedCovering, issues } = compile(rules, caps, opts?.primaryFieldGeneric ?? "reject", opts?.ruleFaces, pathBases);
|
|
291
280
|
const setLevelCap = issues.some((i) => i.code === "invalid.cap_exceeded" && i.rule === "");
|
|
292
281
|
if (issues.length > 0 && ((opts?.onInvalidRule ?? "throw") === "throw" || setLevelCap)) {
|
|
293
282
|
const e = new Error(`createPermissionRulePolicy: ${issues.length} invalid/unsupported rule(s):\n` +
|
|
@@ -314,9 +303,9 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
314
303
|
const defaultAction = opts?.defaultAction ?? "allow";
|
|
315
304
|
const nameSets = { deny: [], ask: [], allow: [] };
|
|
316
305
|
for (const [toolName, entry] of byTool) {
|
|
317
|
-
if (entry.bare.deny !== undefined || entry.param.deny.length > 0)
|
|
306
|
+
if (entry.bare.deny !== undefined || entry.param.deny.length > 0 || entry.content.deny.length > 0)
|
|
318
307
|
nameSets.deny.push(toolName);
|
|
319
|
-
if (entry.bare.ask !== undefined || entry.param.ask.length > 0)
|
|
308
|
+
if (entry.bare.ask !== undefined || entry.param.ask.length > 0 || entry.content.ask.length > 0)
|
|
320
309
|
nameSets.ask.push(toolName);
|
|
321
310
|
if (entry.bare.allow !== undefined)
|
|
322
311
|
nameSets.allow.push(toolName);
|
|
@@ -327,6 +316,19 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
327
316
|
}
|
|
328
317
|
const coveringHit = (lane, toolName) => lane.length === 0 ? undefined : lane.find((r) => namespacedRuleNameCovers(r.ruleName, toolName));
|
|
329
318
|
const hasCovering = namespacedCovering.deny.length > 0 || namespacedCovering.ask.length > 0 || namespacedCovering.allow.length > 0;
|
|
319
|
+
const commandParam = catalogRuleFaceOf("Bash")?.primaryParams[0] ?? "command";
|
|
320
|
+
const cwdBase = (pathBases.cwd ?? pathBases.root ?? "").replace(/\/+$/, "");
|
|
321
|
+
const contentHit = (lane, req) => {
|
|
322
|
+
if (lane.length === 0)
|
|
323
|
+
return undefined;
|
|
324
|
+
const pt = protectivePathTargetOf(req);
|
|
325
|
+
const spelled = pt !== undefined ? pathTargetValue(req.args, pt) : undefined;
|
|
326
|
+
const target = spelled === undefined ? undefined : lexicalNormalAbsolutePathOf(spelled.startsWith("/") ? spelled : `${cwdBase}/${spelled}`);
|
|
327
|
+
const command = isPlainObjectArgs(req.args) ? req.args[commandParam] : undefined;
|
|
328
|
+
return lane.find((r) => r.parsed.match === "subpath" || r.parsed.match === "path"
|
|
329
|
+
? target !== undefined && pathRuleReaches(r.parsed, target, pathBases)
|
|
330
|
+
: typeof command === "string" && ruleReachesProgramRun(r.parsed, command));
|
|
331
|
+
};
|
|
330
332
|
return {
|
|
331
333
|
nameSets: [nameSets],
|
|
332
334
|
check(req) {
|
|
@@ -341,6 +343,10 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
341
343
|
return { action: "deny", message: ruleMessage("denied", coveringDeny.ruleText, coveringDeny.source) };
|
|
342
344
|
}
|
|
343
345
|
if (entry) {
|
|
346
|
+
const contentDeny = contentHit(entry.content.deny, req);
|
|
347
|
+
if (contentDeny) {
|
|
348
|
+
return { action: "deny", message: ruleMessage("denied", contentDeny.ruleText, contentDeny.source) };
|
|
349
|
+
}
|
|
344
350
|
const paramDeny = matchParamRules(entry.param.deny, req.args, caps.maxScalarValueChars);
|
|
345
351
|
if (paramDeny) {
|
|
346
352
|
return { action: "deny", message: ruleMessage("denied", paramDeny.ruleText, paramDeny.source) };
|
|
@@ -355,6 +361,10 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
355
361
|
return { action: "ask", message: ruleMessage("flagged", coveringAsk.ruleText, coveringAsk.source), matchedAskRule: coveringAsk.ruleText };
|
|
356
362
|
}
|
|
357
363
|
if (entry) {
|
|
364
|
+
const contentAsk = contentHit(entry.content.ask, req);
|
|
365
|
+
if (contentAsk) {
|
|
366
|
+
return { action: "ask", message: ruleMessage("flagged", contentAsk.ruleText, contentAsk.source), matchedAskRule: contentAsk.ruleText };
|
|
367
|
+
}
|
|
358
368
|
const paramAsk = matchParamRules(entry.param.ask, req.args, caps.maxScalarValueChars);
|
|
359
369
|
if (paramAsk) {
|
|
360
370
|
return { action: "ask", message: ruleMessage("flagged", paramAsk.ruleText, paramAsk.source), matchedAskRule: paramAsk.ruleText };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tool gate's PERSISTED-RULE arms — the pieces of the persisted-rule lane's consumption that are pure
|
|
3
|
+
* functions of (the lane's answer, the standing decision), pulled out of the gate's lane station so the
|
|
4
|
+
* station holds only the wiring: where the read happens, and where each arm is applied.
|
|
5
|
+
*
|
|
6
|
+
* What lives here: {@link applyPersistedTightening} — the DENY and ASK arms (a person's own standing
|
|
7
|
+
* "never run this" and "ask me each time" rows, their settings' deny/ask lists imported into their store),
|
|
8
|
+
* which the station applies to EVERY decision right after the org layer, and again on an approved edit's
|
|
9
|
+
* bytes — and {@link disclosedRuleSet}, the display form both arms and the allow arm's disclosure use. The
|
|
10
|
+
* lane's bounded READ and the ALLOW arm (a standing yes clearing a classify-tier ask) stay in the station:
|
|
11
|
+
* the read closes over the gate input, and the allow arm is a conjunction over the gate's own state
|
|
12
|
+
* (mandates, the org latch, the marked-call seat) rather than a function of the answer alone.
|
|
13
|
+
*/
|
|
14
|
+
import type { PermissionResult } from "./tool-policy.js";
|
|
15
|
+
import type { PersistedRuleHit, PersistedRuleHitRule } from "./hooks.js";
|
|
16
|
+
/** The lane's one read, normalized: at most one of `hit` / `unreadable` / `coverage` (the gate's reader
|
|
17
|
+
* produces it; the arms below read it). */
|
|
18
|
+
export interface PersistedRuleRead {
|
|
19
|
+
readonly hit?: PersistedRuleHit;
|
|
20
|
+
readonly unreadable?: true;
|
|
21
|
+
}
|
|
22
|
+
/** Display form of a rule set (design/375 §5.1): every rule text is a DISPLAY value on a human trust
|
|
23
|
+
* boundary, and the set must stay readable however large a lane makes it — each member rides
|
|
24
|
+
* `inlineUntrusted` (200 cap, the same cap the durable leg's `RiskDescriptor.shadowedRule` carries),
|
|
25
|
+
* at most FIVE are listed, and the rest are counted rather than dropped silently. */
|
|
26
|
+
export declare function disclosedRuleSet(rules: readonly PersistedRuleHitRule[]): string;
|
|
27
|
+
/**
|
|
28
|
+
* The persisted-rule lane's TIGHTENING arms — a person's own standing DENY and ASK rows — applied to
|
|
29
|
+
* `decision`. The station runs this on EVERY decision, right after the org layer and in the same
|
|
30
|
+
* posture: an allow from the policy fold is exactly what a deny row must be able to refuse, and a bare
|
|
31
|
+
* ask is exactly what an ask row must be able to mark. Precedence across the person's rows is the
|
|
32
|
+
* upstream arbiter's (deny > ask > allow), already resolved inside the lane's one verdict; precedence
|
|
33
|
+
* across AUTHORITIES is org > personal (the org layer ran first, and a personal ask never loosens an
|
|
34
|
+
* org outcome — it stamps a marker on an ask, or turns an allow into one).
|
|
35
|
+
* · DENY — the call is refused outright, with `decisionReason: "persisted_rule"` (the same word the
|
|
36
|
+
* lane's allow carries: one authority, one word) and the rule text disclosed through the display
|
|
37
|
+
* transform; the STATION attributes the refusal (`tightened: "deny"` ⇒ it writes the pass's refusing
|
|
38
|
+
* layer — the attribution is a station's word and lives in the station file).
|
|
39
|
+
* · ASK — on an allow, a fresh ask is minted carrying `matchedAskRule` (the marker an explicit `ask`
|
|
40
|
+
* permission rule stamps, so the ask's origin derives as `ask_rule`: the classifier never answers
|
|
41
|
+
* it, the persisted ALLOW arm never clears it, and the rule-offer face reports `shadowed`); on an
|
|
42
|
+
* ask, the marker is stamped if absent and the message names the rule — the standing question
|
|
43
|
+
* outranks a remembered yes across lanes, exactly as the DSL's own order says within one; a deny
|
|
44
|
+
* stays a deny.
|
|
45
|
+
* · UNREADABLE — the wired store could not be read (a read failure, a timeout): the person's deny/ask
|
|
46
|
+
* rows cannot be enforced, so the call fails CLOSED the way an unreadable org snapshot does — an
|
|
47
|
+
* allow tightens to an ask carrying `requiresRealApproval` (no automatic lane clears it; the gate
|
|
48
|
+
* derives the origin `rule_store_unavailable` from its own record of the read), an ask acquires the
|
|
49
|
+
* bit, a deny stays a deny. Reading an unreadable store as "no rule" would erase every deny the
|
|
50
|
+
* person wrote the moment their file is locked or corrupt.
|
|
51
|
+
* An allow hit (or no hit) returns the decision untouched: the allow arm is the station's.
|
|
52
|
+
*/
|
|
53
|
+
export declare function applyPersistedTightening(decision: PermissionResult, read: PersistedRuleRead | undefined): {
|
|
54
|
+
decision: PermissionResult;
|
|
55
|
+
tightened?: "deny" | "ask" | "unreadable";
|
|
56
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { inlineUntrusted } from "./untrusted-text.js";
|
|
2
|
+
export function disclosedRuleSet(rules) {
|
|
3
|
+
const shown = rules.slice(0, 5).map((r) => inlineUntrusted(r.rule, 200));
|
|
4
|
+
const rest = rules.length - shown.length;
|
|
5
|
+
return rest > 0 ? `${shown.join(", ")} and ${rest} more` : shown.join(", ");
|
|
6
|
+
}
|
|
7
|
+
export function applyPersistedTightening(decision, read) {
|
|
8
|
+
if (read?.unreadable === true) {
|
|
9
|
+
if (decision.action === "deny")
|
|
10
|
+
return { decision };
|
|
11
|
+
const message = "the persisted permission-rule store could not be read for this call — the person's own deny/ask rules cannot be enforced, so this call needs a real approval until the store is readable";
|
|
12
|
+
return {
|
|
13
|
+
decision: decision.action === "allow"
|
|
14
|
+
? { action: "ask", message, decisionReason: "persisted_rule", requiresRealApproval: true, ...(decision.updatedInput !== undefined ? { updatedInput: decision.updatedInput } : {}) }
|
|
15
|
+
: { ...decision, requiresRealApproval: true, message: `${decision.message !== undefined ? `${decision.message} ` : ""}(${message})` },
|
|
16
|
+
tightened: "unreadable",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const hit = read?.hit;
|
|
20
|
+
if (hit === undefined || hit.behavior === "allow")
|
|
21
|
+
return { decision };
|
|
22
|
+
const shown = disclosedRuleSet(hit.rules);
|
|
23
|
+
if (hit.behavior === "deny") {
|
|
24
|
+
return { decision: { action: "deny", message: `a persisted deny rule (${shown}) refuses this call`, decisionReason: "persisted_rule" }, tightened: "deny" };
|
|
25
|
+
}
|
|
26
|
+
if (decision.action === "deny")
|
|
27
|
+
return { decision };
|
|
28
|
+
if (decision.action === "allow") {
|
|
29
|
+
return {
|
|
30
|
+
decision: {
|
|
31
|
+
action: "ask",
|
|
32
|
+
message: `a persisted ask rule (${shown}) requires approval for this call`,
|
|
33
|
+
decisionReason: "persisted_rule",
|
|
34
|
+
matchedAskRule: shown,
|
|
35
|
+
...(decision.updatedInput !== undefined ? { updatedInput: decision.updatedInput } : {}),
|
|
36
|
+
},
|
|
37
|
+
tightened: "ask",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
decision: {
|
|
42
|
+
...decision,
|
|
43
|
+
...(decision.matchedAskRule === undefined ? { matchedAskRule: shown } : {}),
|
|
44
|
+
message: `${decision.message !== undefined ? `${decision.message} ` : ""}(a persisted ask rule (${shown}) also requires approval for this call)`,
|
|
45
|
+
},
|
|
46
|
+
tightened: "ask",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -315,6 +315,14 @@ export interface Prepared {
|
|
|
315
315
|
/** design/99 §E6 — the DENY-NARROWING layers (session rules + skill scope, deny-only). Re-checked on RESUME
|
|
316
316
|
* before an approved pending tool executes, so a rule tightened during the suspend still applies. */
|
|
317
317
|
denyNarrowingPolicy?: ToolPolicy;
|
|
318
|
+
/** #632 — the person's own persisted-rule lane (deny/ask rows), re-adjudicated on RESUME at the same
|
|
319
|
+
* station as the deny-narrowing layers: a deny row added while the call was parked vetoes the execution
|
|
320
|
+
* (`deniedBy: "persisted_rule"`); an ask row or an unreadable store fails closed the same way (the approval
|
|
321
|
+
* answered a question the person has since asked to be asked again — re-issue and re-approve). Absent when
|
|
322
|
+
* no rule store is wired. */
|
|
323
|
+
persistedRuleLane?: {
|
|
324
|
+
adjudicate: (req: ToolCallRequest) => Promise<import("../hooks.js").PersistedRuleAnswer>;
|
|
325
|
+
};
|
|
318
326
|
/** RB-63: the deployment's own caller policy, re-checked on a durable resume ONLY when the approver
|
|
319
327
|
* rewrote the pending call's args (see the composition site for why the edit case is special). */
|
|
320
328
|
basePolicyForResumeEdit?: ToolPolicy;
|
|
@@ -761,8 +769,8 @@ export interface Prepared {
|
|
|
761
769
|
/**
|
|
762
770
|
* design/388 L4 — the leg's `tool_end.structured` projector: the engine card set ∪ the caller rows' declared
|
|
763
771
|
* `renderHints.resultCards`; an undeclared `details.type` is dropped and announced ONCE per leg
|
|
764
|
-
* (`config.tool_card_undeclared`). The
|
|
765
|
-
* module-level
|
|
772
|
+
* (`config.tool_card_undeclared`). The ONE card port: every tool_end body projection (the live loop, the resumed
|
|
773
|
+
* batch, the reconcile-recovered orphan) takes it as a parameter; no module-level card set exists to fall back to.
|
|
766
774
|
*/
|
|
767
775
|
structuredProjector: (result: unknown) => unknown;
|
|
768
776
|
/** Name→effect map for every tool this task can call (design/44 §3). Used by the abort-path orphan
|
|
@@ -2355,6 +2363,17 @@ export interface HarnessHandlersDeps {
|
|
|
2355
2363
|
* Signatures are spelled out (not `Pick<Runner, …>`), because naming the class from here would be the
|
|
2356
2364
|
* very edge this seat exists to remove.
|
|
2357
2365
|
*/
|
|
2366
|
+
/**
|
|
2367
|
+
* The Runner's deployment deps as a LIVE read for the lanes that read them after an await (the resume ladder's
|
|
2368
|
+
* rungs). `Runner.deps` is not a constant: `swapModels` replaces the object, so a lane handed the object at its
|
|
2369
|
+
* call would read a stale catalog — and a stale anything — where the one-function ladder read `this.deps.x` at the
|
|
2370
|
+
* instant of the read. The seat is the Runner's own view (a getter over the private field), so `runner.deps.x`
|
|
2371
|
+
* in a lane is exactly `this.deps.x` in the method it came from.
|
|
2372
|
+
*/
|
|
2373
|
+
export interface RunnerDepsSeat {
|
|
2374
|
+
/** The deps object the Runner holds RIGHT NOW — never cache it across an await. */
|
|
2375
|
+
readonly deps: RunnerDeps;
|
|
2376
|
+
}
|
|
2358
2377
|
export interface RunnerSelfSeat {
|
|
2359
2378
|
/** The session store this Runner was constructed over — the delegation tools acquire / release / fork through it. */
|
|
2360
2379
|
readonly sessions: SessionStore;
|
|
@@ -9,13 +9,15 @@
|
|
|
9
9
|
*
|
|
10
10
|
* ## One read per adjudicated call
|
|
11
11
|
*
|
|
12
|
-
* The gate consults the org lane (`orgRules.adjudicate`)
|
|
13
|
-
* (`persistedRules.
|
|
12
|
+
* The gate consults the org lane (`orgRules.adjudicate`) and then the personal lane
|
|
13
|
+
* (`persistedRules.adjudicate`) on every decision, in that order (the personal lane's deny/ask rows must
|
|
14
|
+
* be able to tighten an allow, so it can no longer wait for a surviving ask). The org lane's read is
|
|
14
15
|
* HANDED to the personal lane by `toolCallId` (taken once, then forgotten), so a governed deployment pays
|
|
15
16
|
* one store query — one org-source fetch — per call, exactly as it did with two seams. An entry the
|
|
16
|
-
* personal lane never claims (
|
|
17
|
-
* evicted or never-handed entry simply costs the personal lane its own read. The
|
|
18
|
-
* device, not a semantic one: both lanes judge one snapshot, which is the honest shape
|
|
17
|
+
* personal lane never claims (a call on a tool it does not speak for) is evicted first-in-first-out past
|
|
18
|
+
* a small bound; an evicted or never-handed entry simply costs the personal lane its own read. The
|
|
19
|
+
* handoff is a cost device, not a semantic one: both lanes judge one snapshot, which is the honest shape
|
|
20
|
+
* for one decision.
|
|
19
21
|
*
|
|
20
22
|
* ## Where the lanes do NOT go
|
|
21
23
|
*
|
|
@@ -25,14 +27,12 @@
|
|
|
25
27
|
*/
|
|
26
28
|
import type { AskRuleEvidence, ToolCallRequest } from "../tool-policy.js";
|
|
27
29
|
import { persistedRuleMandateOf, type OrgGateVerdict, type PersistedRuleAnswer, type PersistedRuleHit } from "../hooks.js";
|
|
28
|
-
import type {
|
|
30
|
+
import type { PersistedRule, PersistedRuleVerdict, RuleOffer, SegmentCoverage } from "../permission-rule-model.js";
|
|
29
31
|
import { type OrgRuleResolution } from "../permission-rule-org.js";
|
|
30
32
|
import type { PermissionRuleStoreProvider } from "../permission-rule-provider.js";
|
|
31
|
-
/** The one SHELL tool the persisted-rule lane speaks for
|
|
33
|
+
/** The one SHELL tool the persisted-rule lane's COMMAND grammar speaks for (the rule-offer face mints for
|
|
34
|
+
* it alone). The PATH grammar's tools are derived from the catalog (`ruleToolGrammarOf`). */
|
|
32
35
|
export declare const PERSISTED_RULE_TOOL = "Bash";
|
|
33
|
-
/** design/382 §2.5 — the READ tool the directory-rule family speaks for (its one other admission
|
|
34
|
-
* point, the cd-segment notch, lives inside the model's own conjunction/coverage arms). */
|
|
35
|
-
export declare const DIRECTORY_RULE_TOOL = "Read";
|
|
36
36
|
/**
|
|
37
37
|
* design/252 G-2 — the evidence an INHERITED-lane ask carries.
|
|
38
38
|
*
|
|
@@ -67,28 +67,35 @@ export declare function orgRevisionEvidenceOf(resolution: OrgRuleResolution, onD
|
|
|
67
67
|
revision?: number;
|
|
68
68
|
};
|
|
69
69
|
/**
|
|
70
|
-
* design/252 G-2 / design/375 — project the
|
|
71
|
-
* each member's canonical text plus its own add DOTS.
|
|
72
|
-
*
|
|
73
|
-
* segment order
|
|
70
|
+
* design/252 G-2 / design/375 — project the model's verdict onto the gate's rule-lane answer: the
|
|
71
|
+
* behavior that spoke, and each member's canonical text plus its own add DOTS. For an allow the set is
|
|
72
|
+
* the admitting COVERAGE SET (a single rule admitting the whole command is a one-element set; a compound
|
|
73
|
+
* admitted by the per-segment conjunction is the set of covering rules in segment order — the model's
|
|
74
|
+
* own reporting-order contract); for a deny/ask it is the one reaching rule the precedence walk chose.
|
|
74
75
|
*
|
|
75
76
|
* `adds` on a listed rule are the LIVE adds — the store's read already dropped tombstoned dots — so
|
|
76
|
-
* this is the set that actually
|
|
77
|
+
* this is the set that actually decided the call, which is the identity a later audit reconciles a
|
|
77
78
|
* shadowed ask against. The sanitized display text deliberately is not one. Copied rather than
|
|
78
79
|
* aliased: the evidence must not change under a store that reuses its row objects.
|
|
79
80
|
*/
|
|
80
|
-
export declare function persistedRuleHitOf(
|
|
81
|
+
export declare function persistedRuleHitOf(verdict: PersistedRuleVerdict | undefined): PersistedRuleHit | undefined;
|
|
81
82
|
/**
|
|
82
|
-
* design/382 §2.5-1 — the persisted-rule lane's
|
|
83
|
-
* identity the rule family is defined over — absolute as spelled, relative
|
|
84
|
-
* tracked working directory (the very base the
|
|
85
|
-
* tracker moved), then normalized lexically, zero IO.
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
83
|
+
* design/382 §2.5-1, widened to every path-targeting tool — the persisted-rule lane's PATH arm: the call's
|
|
84
|
+
* target path in the SAME lexical identity the rule family is defined over — absolute as spelled, relative
|
|
85
|
+
* resolved against the LIVE tracked working directory (the very base the file tools themselves resolve
|
|
86
|
+
* against; task root when no tracker moved), then normalized lexically, zero IO.
|
|
87
|
+
*
|
|
88
|
+
* TWO readings of the call's path slot, one per direction (design/388 B6/B17): the tightening arms judge
|
|
89
|
+
* the PROTECTIVE target (the object face's declaration, the catalog's for the name as the floor — reading
|
|
90
|
+
* the catalog for an undeclared same-name tool can only tighten a deny); the allow arm judges the DECLARED
|
|
91
|
+
* target (the face's OWN declaration only — a same-name tool that declares no path target is unconfirmable
|
|
92
|
+
* and no grant admits it). The precedence walk, the admission predicates and the Read-only allow are the
|
|
93
|
+
* model's (`adjudicatePersistedPathRules`). An ALLOW here clears ASKS only — the read fences (workspace
|
|
94
|
+
* containment, the sensitive-read deny face) judge inside the tool exactly as they would with no rule
|
|
95
|
+
* anywhere — and an unresolvable path is simply no verdict (fail toward asking on the allow side, no
|
|
96
|
+
* refusal on the tightening side: a rule about a path speaks about no other shape).
|
|
90
97
|
*/
|
|
91
|
-
export declare function
|
|
98
|
+
export declare function pathRuleLaneAnswer(table: readonly PersistedRule[], req: Pick<ToolCallRequest, "toolName" | "args" | "face">, ctx: {
|
|
92
99
|
root: string | undefined;
|
|
93
100
|
sessionId: string | undefined;
|
|
94
101
|
liveCwd: string | undefined;
|
|
@@ -97,7 +104,7 @@ export declare function directoryRuleLaneAnswer(table: readonly PersistedAllowRu
|
|
|
97
104
|
* a deployment without one keeps a decision path byte-identical to a build without the feature. */
|
|
98
105
|
export interface PermissionRuleLanes {
|
|
99
106
|
personal: {
|
|
100
|
-
|
|
107
|
+
adjudicate: (req: ToolCallRequest) => Promise<PersistedRuleAnswer>;
|
|
101
108
|
} | undefined;
|
|
102
109
|
org: {
|
|
103
110
|
adjudicate: (req: ToolCallRequest) => Promise<OrgGateVerdict>;
|