@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
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* design/179 §2 — the persisted
|
|
3
|
-
*
|
|
4
|
-
* A persisted
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* design/179 §2 — the persisted permission-rule model and its matchers.
|
|
3
|
+
*
|
|
4
|
+
* A persisted rule is one person's standing instruction about a tool call, in one of THREE behaviors
|
|
5
|
+
* ({@link RULE_BEHAVIORS}): an `allow` is the standing form of ONE recorded human approval (the person
|
|
6
|
+
* confirmed a command once on an approval card and asked not to be asked again); a `deny` and an `ask`
|
|
7
|
+
* are the standing forms of "never run this" and "ask me every time" — the same content-form rules
|
|
8
|
+
* upstream reads out of its settings' deny/ask lists. ONE grammar, ONE canonical spelling and ONE
|
|
9
|
+
* matcher family serve all three; the behavior is a FIELD beside the text, never part of it, so
|
|
10
|
+
* `Bash(rm -r:*)` is one spelling whether it allows, denies or asks. This module owns the rule's SHAPE
|
|
11
|
+
* (text grammar, canonical spelling, scope, behavior) and its MATCH semantics; who may mint one, where
|
|
12
|
+
* it is stored, and where in the gate it is consumed live in `permission-rule-store.ts`,
|
|
13
|
+
* `permission-rule-consent.ts` and `gate-lanes.ts` respectively.
|
|
14
|
+
*
|
|
15
|
+
* The two DIRECTIONS read a command differently, on purpose: an allow must cover the WHOLE call
|
|
16
|
+
* ({@link ruleAdmitsCommand} — one recorded yes clears exactly what it spells), while a deny/ask
|
|
17
|
+
* speaks about any PROGRAM RUN the call may perform ({@link ruleReachesProgramRun} — whole command or
|
|
18
|
+
* any segment of a chain, the wider tightening reading). Precedence across behaviors is the upstream
|
|
19
|
+
* one, deny > ask > allow ({@link RULE_BEHAVIOR_PRECEDENCE}, the one table every adjudication reads).
|
|
9
20
|
*
|
|
10
21
|
* ## The floor: what a rule may name, and what a rule may match
|
|
11
22
|
*
|
|
@@ -58,6 +69,48 @@
|
|
|
58
69
|
* here) and no leading-env-assignment stripping (`FOO=1 git status` simply does not match
|
|
59
70
|
* `Bash(git status:*)`) — the last two are strict-side divergences from upstream, registered as such.
|
|
60
71
|
*/
|
|
72
|
+
import type { AssertAllKeysHandled } from "./ask-origin.js";
|
|
73
|
+
/**
|
|
74
|
+
* The closed set of rule BEHAVIORS — what a matching rule says about the call. The upstream vocabulary
|
|
75
|
+
* verbatim (`allow` / `deny` / `ask`, the three settings lists), in PRECEDENCE order: when rules of more
|
|
76
|
+
* than one behavior speak for one call, the earliest member here decides ({@link RULE_BEHAVIOR_PRECEDENCE}
|
|
77
|
+
* is that order as a table, and {@link adjudicatePersistedRules} is the one place that walks it).
|
|
78
|
+
*
|
|
79
|
+
* The type is derived FROM the array so a member added to either side is a compile error; a value read
|
|
80
|
+
* off a wire or a file goes through {@link isRuleBehavior} (present but outside the set = refused, never
|
|
81
|
+
* defaulted) or {@link readRowBehavior} (the ONE reading of a row that predates the field).
|
|
82
|
+
*/
|
|
83
|
+
export declare const RULE_BEHAVIORS: readonly ["deny", "ask", "allow"];
|
|
84
|
+
export type RuleBehavior = (typeof RULE_BEHAVIORS)[number];
|
|
85
|
+
/** Membership test for {@link RuleBehavior}. Exported because the word crosses process boundaries (a
|
|
86
|
+
* store row, a sync payload, a settings entry) and a host validating one must not hand-roll the set. */
|
|
87
|
+
export declare function isRuleBehavior(v: unknown): v is RuleBehavior;
|
|
88
|
+
/**
|
|
89
|
+
* The precedence table: the rank at which each behavior decides a call when several rules speak (lower
|
|
90
|
+
* decides first). Upstream's arbiter walks deny, then ask, then allow — an explicit deny is never
|
|
91
|
+
* outvoted by an allow, and a standing "ask me each time" is never silenced by a remembered yes. Every
|
|
92
|
+
* adjudication over a mixed rule table orders its walk by THIS table (never by a hand-written if-chain),
|
|
93
|
+
* so the rank of a behavior is stated once.
|
|
94
|
+
*/
|
|
95
|
+
export declare const RULE_BEHAVIOR_PRECEDENCE: {
|
|
96
|
+
readonly deny: 0;
|
|
97
|
+
readonly ask: 1;
|
|
98
|
+
readonly allow: 2;
|
|
99
|
+
};
|
|
100
|
+
/** Compile-time fence over the precedence table: `never` while every behavior has a rank. */
|
|
101
|
+
export type PrecedenceTableCoversEveryBehavior = AssertAllKeysHandled<Exclude<RuleBehavior, keyof typeof RULE_BEHAVIOR_PRECEDENCE>>;
|
|
102
|
+
/** The behaviors in precedence order — the walk every mixed-table adjudication takes. */
|
|
103
|
+
export declare const RULE_BEHAVIORS_BY_PRECEDENCE: readonly RuleBehavior[];
|
|
104
|
+
/**
|
|
105
|
+
* The ONE reading of a persisted row's behavior at every seam that may meet a row written before the
|
|
106
|
+
* field existed — the file store's schema migration, the sync wire's inbound picker, a checkpoint's
|
|
107
|
+
* rehydrated session rows. An ABSENT behavior reads `"allow"`: every row an engine without the field
|
|
108
|
+
* ever wrote was an allow, so that reading is the row's own meaning, not a default. A PRESENT value
|
|
109
|
+
* outside the closed set is refused (`undefined`) — a word this engine cannot read must not become an
|
|
110
|
+
* allow by falling through (the loud-bad-value doctrine), and must not become a deny either (a
|
|
111
|
+
* fabricated tightening is still a fabrication).
|
|
112
|
+
*/
|
|
113
|
+
export declare function readRowBehavior(v: unknown): RuleBehavior | undefined;
|
|
61
114
|
/** The tools the rule lane speaks for. `"Bash"` is the historical member; `"Read"` (design/382 §2.2)
|
|
62
115
|
* speaks EXACTLY ONE grammar — the double-slash directory form `Read(//abs/**)`, the directory
|
|
63
116
|
* read-authorization a compound card's cd segment mints — and its admission is the CLOSED two-point
|
|
@@ -70,7 +123,19 @@
|
|
|
70
123
|
* silence the classifier's questions, never a mandated one), so a recorded human yes is exactly what
|
|
71
124
|
* clears it. The org/hook/matchedAskRule immunities stay: those conjuncts live in the gate's
|
|
72
125
|
* persisted-rule lane and do not loosen with this set. */
|
|
73
|
-
export type PersistedRuleTool =
|
|
126
|
+
export type PersistedRuleTool = string;
|
|
127
|
+
/** The one shell tool the command grammar speaks for. */
|
|
128
|
+
export declare const COMMAND_RULE_TOOL = "Bash";
|
|
129
|
+
/** The one tool whose path family has an ALLOW form (the `Read(//abs-dir/**)` directory grant). */
|
|
130
|
+
export declare const READ_RULE_TOOL = "Read";
|
|
131
|
+
/**
|
|
132
|
+
* Which tools a rule may name — the model's tool set is DERIVED, never a hand-kept list: the shell tool
|
|
133
|
+
* takes the COMMAND grammar; every tool the catalog declares a path target for (Read / Edit / Write /
|
|
134
|
+
* Glob / NotebookEdit / …, `pathTargetOf`) takes the PATH grammar. A rule on any other tool has no
|
|
135
|
+
* content this model can judge and is refused (`unsupported.tool`). The tool set therefore moves with the
|
|
136
|
+
* catalog's declarations, exactly as the protective fences' covered set does.
|
|
137
|
+
*/
|
|
138
|
+
export declare function ruleToolGrammarOf(toolName: string): "command" | "path" | undefined;
|
|
74
139
|
/**
|
|
75
140
|
* Match forms. Four members, two of them one predicate:
|
|
76
141
|
* · `exact` — admits the one string the rule spells.
|
|
@@ -82,13 +147,19 @@ export type PersistedRuleTool = "Bash" | "Read";
|
|
|
82
147
|
* on the single-command body. Kept as a fully supported COMPATIBILITY READ — stored rules are never
|
|
83
148
|
* migrated or rewritten — and it remains the ONLY spelling of the compound-prefix form
|
|
84
149
|
* (`Bash(cd /tmp && adb pull:*)`): the trailing-star form deliberately has no compound reading
|
|
85
|
-
* (see {@link
|
|
150
|
+
* (see {@link parseRuleText}), because upstream's own trailing-star pattern is inert on a
|
|
86
151
|
* compound and importing/minting one as a chain licence would grant what nobody approved.
|
|
87
|
-
* · `subpath` — the
|
|
88
|
-
* one absolute directory. Never admits any command; its one predicate is
|
|
89
|
-
*
|
|
152
|
+
* · `subpath` — the directory form (design/382 §2.2): `Tool(//abs-dir/**)`, reaching every path under
|
|
153
|
+
* one absolute directory. Never admits any command; its one predicate is {@link directoryRuleAdmits}.
|
|
154
|
+
* As an ALLOW it exists for the Read tool only (a standing yes to reads under a directory); as a
|
|
155
|
+
* deny/ask it is legal on every path-targeting tool.
|
|
156
|
+
* · `path` — the PATH PATTERN form, deny/ask only: upstream's file-rule spelling over a tool's path
|
|
157
|
+
* target (`Edit(~/.sema/settings.json)`, `Write(src/**)`, `Read(//etc/*)`), stored AS SPELLED and
|
|
158
|
+
* resolved at match time against the call's bases (`//` absolute, `~/` home, `/` project root,
|
|
159
|
+
* otherwise the working directory). Its predicate is {@link pathRuleReaches}. Not an allow form: a
|
|
160
|
+
* standing approval by path pattern would be a wider grant than any card ever shows a person.
|
|
90
161
|
*/
|
|
91
|
-
export type PersistedRuleMatch = "exact" | "prefix" | "wildcard" | "subpath";
|
|
162
|
+
export type PersistedRuleMatch = "exact" | "prefix" | "wildcard" | "subpath" | "path";
|
|
92
163
|
/**
|
|
93
164
|
* Where a rule applies — design/382 §4.1, the THREE consent dimensions.
|
|
94
165
|
*
|
|
@@ -143,13 +214,24 @@ export interface RuleAdd {
|
|
|
143
214
|
createdAt: string;
|
|
144
215
|
}
|
|
145
216
|
/**
|
|
146
|
-
* A persisted
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
217
|
+
* A persisted rule for one tool, in one of the three behaviors.
|
|
218
|
+
*
|
|
219
|
+
* The logical identity of a rule is the TRIPLE (behavior, rule text, scope): the same text may stand as
|
|
220
|
+
* an allow and as a deny in one bucket (upstream keeps the three lists side by side and lets precedence
|
|
221
|
+
* decide), so a tombstone, a quarantine row and a join group all key on the triple — a key on (text,
|
|
222
|
+
* scope) alone would let a delete aimed at the allow take the deny with it.
|
|
223
|
+
*
|
|
224
|
+
* `adds` is a real set, not a scalar: two concurrently approved records for the same identity each
|
|
225
|
+
* redeem their own dot, and a rule is live iff at least one of its adds survives the tombstones.
|
|
226
|
+
* Organization rules are a separate partition (a server-published snapshot, no CRDT) and never enter
|
|
227
|
+
* this type; the personal deny/ask rows here are the PERSON's own standing instructions (their settings'
|
|
228
|
+
* deny/ask lists, imported through the same consent door the allow list takes).
|
|
151
229
|
*/
|
|
152
|
-
export interface
|
|
230
|
+
export interface PersistedRule {
|
|
231
|
+
/** What a match of this rule says about the call — REQUIRED on every row this engine writes. A row
|
|
232
|
+
* read without one is a pre-field row and reads `allow` through {@link readRowBehavior}, at the
|
|
233
|
+
* reading seams only (the store's schema migration, the sync picker, a checkpoint's session rows). */
|
|
234
|
+
behavior: RuleBehavior;
|
|
153
235
|
/** Canonical rule text: `Bash(git status)` (exact) or `Bash(git status:*)` (prefix). A prefix body
|
|
154
236
|
* may itself be a connector chain (`Bash(cd /tmp && adb pull:*)`) — see the module note on
|
|
155
237
|
* connectors for what such a rule reaches. */
|
|
@@ -170,6 +252,8 @@ export interface PersistedAllowRule {
|
|
|
170
252
|
* in no tombstone for the same (rule, scope).
|
|
171
253
|
*/
|
|
172
254
|
export interface RuleTombstone {
|
|
255
|
+
/** The identity's behavior member — a tombstone deletes adds of ONE (behavior, text, scope). */
|
|
256
|
+
behavior: RuleBehavior;
|
|
173
257
|
rule: string;
|
|
174
258
|
scope: RuleScope;
|
|
175
259
|
removedDots: RuleDot[];
|
|
@@ -185,8 +269,11 @@ export interface RuleReject {
|
|
|
185
269
|
code: RuleRejectCode;
|
|
186
270
|
message: string;
|
|
187
271
|
}
|
|
188
|
-
/** The parsed, canonical shape of one rule text — everything but the scope and the
|
|
189
|
-
|
|
272
|
+
/** The parsed, canonical shape of one rule text under one behavior — everything but the scope and the
|
|
273
|
+
* provenance. The behavior is the parser's INPUT (it decides which validator floor applies), carried
|
|
274
|
+
* out so a consumer holding the parse never re-derives which list the text came from. */
|
|
275
|
+
export interface ParsedRule {
|
|
276
|
+
behavior: RuleBehavior;
|
|
190
277
|
rule: string;
|
|
191
278
|
tool: PersistedRuleTool;
|
|
192
279
|
match: PersistedRuleMatch;
|
|
@@ -209,7 +296,7 @@ export declare const MAX_RULE_TEXT_CHARS = 512;
|
|
|
209
296
|
* an EXACT rule naming a whole interpreter command line stays legal, since it authorizes one command.
|
|
210
297
|
*
|
|
211
298
|
* TWO READERS, and they consult this table with DIFFERENT questions — do not read either as the other.
|
|
212
|
-
* {@link
|
|
299
|
+
* {@link parseRuleText} asks "is this rule TEXT's own head a row?" and compares the basename as
|
|
213
300
|
* SPELLED, so an alias spelling (`SSH host:*`, `ssh.exe host:*`) is a different name to it and enters
|
|
214
301
|
* the store like any other rule. {@link genericPrefixBody} asks "should the engine PROPOSE a body with
|
|
215
302
|
* this head?" and normalizes first ({@link screenedHeadName}: case, trailing periods, executable
|
|
@@ -391,23 +478,46 @@ export declare function hasUnrenderableCharacters(text: string): boolean;
|
|
|
391
478
|
* product's settings, reloading a store file, and any future backend. A shape refused here cannot enter
|
|
392
479
|
* the store through any door.
|
|
393
480
|
*
|
|
394
|
-
* `
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
481
|
+
* `behavior` is REQUIRED — there is no default, because the two directions have different floors and
|
|
482
|
+
* a defaulted allow is exactly the silent-widening shape a validator must not have: the
|
|
483
|
+
* bare-interpreter-prefix refusal is an ALLOW-side argument (one approval click cannot be read as
|
|
484
|
+
* authorizing arbitrary programs), while a deny/ask has the opposite polarity — `Bash(node:*)` as a DENY
|
|
485
|
+
* is a legitimately wide tightening, and refusing it would silently disarm exactly the rule a person
|
|
486
|
+
* most wants. The tightening behaviors therefore admit the interpreter-headed prefix form and change
|
|
487
|
+
* nothing else; the allow floor is unchanged.
|
|
399
488
|
*/
|
|
400
|
-
export declare function
|
|
401
|
-
|
|
402
|
-
}): {
|
|
403
|
-
rule: ParsedAllowRule;
|
|
489
|
+
export declare function parseRuleText(text: string, behavior: RuleBehavior): {
|
|
490
|
+
rule: ParsedRule;
|
|
404
491
|
} | {
|
|
405
492
|
reject: RuleReject;
|
|
406
493
|
};
|
|
407
|
-
/** The canonical spelling of a rule for a (already folded) command and match form
|
|
408
|
-
* command is the lexical-normal absolute directory (one leading slash), re-spelled in the
|
|
409
|
-
* double-slash directory form. */
|
|
410
|
-
export declare function
|
|
494
|
+
/** The canonical spelling of a rule for a (already folded) command and match form on `tool`. For
|
|
495
|
+
* `subpath` the command is the lexical-normal absolute directory (one leading slash), re-spelled in the
|
|
496
|
+
* double-slash directory form; for `path` it is the pattern as spelled. */
|
|
497
|
+
export declare function formatRuleText(command: string, match: PersistedRuleMatch, tool: PersistedRuleTool): string;
|
|
498
|
+
/**
|
|
499
|
+
* The bases a path pattern resolves against at match time — facts of the CALL, never of the rule:
|
|
500
|
+
* `home` for `~/…` (absent ⇒ `os.homedir()`), `root` for `/…` (the task's project root), `cwd` for a
|
|
501
|
+
* relative pattern (the live working directory; absent ⇒ `root`). A pattern whose base is absent
|
|
502
|
+
* resolves to nothing and reaches nothing.
|
|
503
|
+
*/
|
|
504
|
+
export interface PathRuleBases {
|
|
505
|
+
readonly root?: string;
|
|
506
|
+
readonly cwd?: string;
|
|
507
|
+
readonly home?: string;
|
|
508
|
+
}
|
|
509
|
+
/** Is this base usable by {@link resolvePathPattern} — an absolute path with a lexical normal form? A rule
|
|
510
|
+
* compiler asks this at compile time so a pattern is refused rather than compiled inert. */
|
|
511
|
+
export declare function isUsablePathBase(base: string | undefined): boolean;
|
|
512
|
+
/**
|
|
513
|
+
* The PATH family's tightening predicate: does this deny/ask rule reach the call's target path? The
|
|
514
|
+
* target is the caller's ALREADY-RESOLVED lexical-normal absolute path (the same identity the read/write
|
|
515
|
+
* fences judge with). A `subpath` rule reaches the directory and everything under it
|
|
516
|
+
* ({@link directoryRuleAdmits}); a `path` rule resolves its spelling against the call's bases and
|
|
517
|
+
* matches segment-wise (`*` within a segment, `**` across segments), reaching the path it names and
|
|
518
|
+
* everything under a directory it names. A command-family rule reaches no path.
|
|
519
|
+
*/
|
|
520
|
+
export declare function pathRuleReaches(rule: Pick<PersistedRule, "match" | "command">, target: string, bases: PathRuleBases): boolean;
|
|
411
521
|
/**
|
|
412
522
|
* Does this rule's command pattern admit `command`?
|
|
413
523
|
*
|
|
@@ -425,7 +535,7 @@ export declare function formatAllowRuleText(command: string, match: PersistedRul
|
|
|
425
535
|
* same count equality is the containment: `Bash(a && b:*)` admits `a && b x`, never `a && b x && c`.
|
|
426
536
|
* An EXACT rule has no such reach by construction: it admits one string, the one it spells.
|
|
427
537
|
*/
|
|
428
|
-
export declare function ruleAdmitsCommand(rule: Pick<
|
|
538
|
+
export declare function ruleAdmitsCommand(rule: Pick<PersistedRule, "match" | "command">, command: string): boolean;
|
|
429
539
|
/**
|
|
430
540
|
* The same match, asked the DENY/ASK layer's question — "does this rule speak about this program run?".
|
|
431
541
|
*
|
|
@@ -440,7 +550,38 @@ export declare function ruleAdmitsCommand(rule: Pick<PersistedAllowRule, "match"
|
|
|
440
550
|
* fewer. A shape only the ALLOW side could read would be a standing approval no published policy could
|
|
441
551
|
* see — the inversion this whole ticket exists to prevent.
|
|
442
552
|
*/
|
|
443
|
-
export declare function ruleAdmitsProgramRun(rule: Pick<
|
|
553
|
+
export declare function ruleAdmitsProgramRun(rule: Pick<PersistedRule, "match" | "command">, command: string): boolean;
|
|
554
|
+
/**
|
|
555
|
+
* Does one TIGHTENING (deny/ask) rule speak about this call — as a whole, or about any single program
|
|
556
|
+
* run in it? The ONE reach predicate of the tightening direction, shared by the org partition, the
|
|
557
|
+
* personal deny/ask rows and the rule DSL's content-form deny/ask rules, so no two of them can disagree
|
|
558
|
+
* about what a deny reaches.
|
|
559
|
+
*
|
|
560
|
+
* The allow lane asks a different question and gets a different answer, deliberately. An allow rule is
|
|
561
|
+
* one recorded human yes and must cover the WHOLE call to clear it: `Bash(npm test)` does not clear
|
|
562
|
+
* `npm test && curl evil.example`, because nobody said yes to the second half. A deny/ask rule is a
|
|
563
|
+
* standing statement about a program run, and a call that MAY perform that run is a call the rule
|
|
564
|
+
* speaks about — so a segment hit is a hit. Deliberately an OVER-approximation, in the tightening
|
|
565
|
+
* direction: the segments are the runs the text can reach, not the runs that will happen, so a
|
|
566
|
+
* short-circuited branch (`true || curl evil.example`) and a commented-out tail
|
|
567
|
+
* (`echo ok # ; curl evil.example`) are judged as though they ran. Answering "does this definitely
|
|
568
|
+
* execute" needs evaluation, not parsing; over-firing costs an approval prompt, and the opposite error
|
|
569
|
+
* costs the rule. Reading the compound as one unmatched blob is what once made `deny Bash(curl:*)`
|
|
570
|
+
* silent on `npm test && curl evil.example`: a rule standing on paper and matching nothing in fact.
|
|
571
|
+
*
|
|
572
|
+
* `ruleAdmitsProgramRun`, not `ruleAdmitsCommand`, on BOTH arms — this direction asks which programs
|
|
573
|
+
* the call runs, and the matching predicate refuses a quoted operator that is ordinary argument text
|
|
574
|
+
* (a `&` inside a URL); inheriting that refusal here silenced a deny on the commonest curl spelling.
|
|
575
|
+
* EVERY segment is judged, including the sole segment of a one-element list: the two readings differ
|
|
576
|
+
* on terminator handling, so `curl evil.example;` has a single segment `curl evil.example` that the
|
|
577
|
+
* whole-command arm refuses and this arm must still see (a trailing `;` once carried a chain past the
|
|
578
|
+
* fence). A command the lane cannot read at all (a redirection, a substitution) has no segments; the
|
|
579
|
+
* whole-command arm still runs and answers false, as it always did — those shapes are refused upstream.
|
|
580
|
+
*
|
|
581
|
+
* A path-family rule (`subpath` / `path`) reaches no command: its predicates are {@link directoryRuleAdmits}
|
|
582
|
+
* and {@link pathRuleReaches}.
|
|
583
|
+
*/
|
|
584
|
+
export declare function ruleReachesProgramRun(rule: Pick<PersistedRule, "match" | "command">, command: string): boolean;
|
|
444
585
|
/** design/382 §3.3-5 — one BREADTH note about a rule a person is about to persist: which wide shape
|
|
445
586
|
* it has, said in words a surface can show beside the input line. A warning is never a refusal —
|
|
446
587
|
* the closed refusal set is untouched — and never adjudication input. */
|
|
@@ -478,7 +619,7 @@ export interface EditedRuleBreadthWarning {
|
|
|
478
619
|
* The upstream editor validates edited text but shows no breadth evidence at all; this seat is a
|
|
479
620
|
* superset disclosure (registered divergence, design/382 §6-4).
|
|
480
621
|
*/
|
|
481
|
-
export declare function ruleBreadthWarningsOf(rule: Pick<
|
|
622
|
+
export declare function ruleBreadthWarningsOf(rule: Pick<PersistedRule, "match" | "command">): readonly EditedRuleBreadthWarning[];
|
|
482
623
|
/**
|
|
483
624
|
* The segments of `command` as the deny/ask layer must judge them, or `undefined` for a command this
|
|
484
625
|
* lane cannot read.
|
|
@@ -501,8 +642,8 @@ export declare function pathWithinRoot(path: string, root: string): boolean;
|
|
|
501
642
|
/**
|
|
502
643
|
* The LEXICAL NORMALIZER for a CALL's target path (design/382 §2.2 — the rule family's one path
|
|
503
644
|
* identity is the lexical normal form, zero IO): collapse slash runs and `.` segments, resolve `..`
|
|
504
|
-
* segments textually
|
|
505
|
-
*
|
|
645
|
+
* segments textually (a `..` at the root stays at the root, as POSIX resolves it), and answer the
|
|
646
|
+
* one-leading-slash absolute spelling — or `undefined` for a relative path. RULE
|
|
506
647
|
* bodies never come through here (their canonical spelling is unique and non-normal spellings are
|
|
507
648
|
* refused at the validator); this is for the comparison INPUT a caller derives from a live call.
|
|
508
649
|
*/
|
|
@@ -520,13 +661,16 @@ export declare function lexicalNormalAbsolutePathOf(path: string): string | unde
|
|
|
520
661
|
* caller cannot smuggle a second path identity through the comparison. Containment is the
|
|
521
662
|
* word-boundary one ({@link pathWithinRoot}: `/a` never reaches `/ab`).
|
|
522
663
|
*
|
|
523
|
-
* What it never does, stated as loudly as what it does (§2.5): admits READS ONLY —
|
|
524
|
-
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
*
|
|
664
|
+
* What it never does, stated as loudly as what it does (§2.5): as an ALLOW it admits READS ONLY — the
|
|
665
|
+
* TOOL conjunct is the caller's (every consumer judges eligibility with the call's tool, and
|
|
666
|
+
* {@link adjudicatePersistedPathRules} takes the allow arm for the Read tool alone), so a directory allow
|
|
667
|
+
* never admits a write, never a whole Bash command, never a non-cd segment — and it clears ASKS only,
|
|
668
|
+
* never a filesystem fence: workspace containment, the sensitive-read deny face and every violation
|
|
669
|
+
* refusal judge exactly as they would without the rule (a fence refusal is structural, not an ask, so
|
|
670
|
+
* there is nothing here for it to clear). As a DENY/ASK the same containment refuses or questions every
|
|
671
|
+
* path under the directory, on whichever path tool the rule names.
|
|
528
672
|
*/
|
|
529
|
-
export declare function directoryRuleAdmits(rule: Pick<
|
|
673
|
+
export declare function directoryRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, path: string): boolean;
|
|
530
674
|
/** Does a rule's scope cover a task running in `cwd` (and, for the session dimension, in the session
|
|
531
675
|
* named by `sessionId`)? A project rule needs a cwd to compare against; without one it covers nothing
|
|
532
676
|
* (fail-closed). A session rule (design/382 §4.3) covers a call iff the call's `sessionId` equals the
|
|
@@ -539,7 +683,7 @@ export declare function directoryRuleAdmits(rule: Pick<PersistedAllowRule, "tool
|
|
|
539
683
|
export declare function scopeCoversCwd(scope: RuleScope, cwd: string | undefined, sessionId?: string): boolean;
|
|
540
684
|
/** Is this rule live — i.e. does it still carry at least one add? Deleted adds are removed by the store
|
|
541
685
|
* when tombstones are applied, so a rule with an empty `adds` is a rule that no longer exists. */
|
|
542
|
-
export declare function isRuleLive(rule:
|
|
686
|
+
export declare function isRuleLive(rule: PersistedRule): boolean;
|
|
543
687
|
/**
|
|
544
688
|
* design/375 §5.1 — MAY this rule speak about this call's CONTEXT? Tool identity plus scope coverage,
|
|
545
689
|
* and nothing about the rule's liveness: the CONTEXT question is the half a not-yet-persisted rule (a
|
|
@@ -551,7 +695,7 @@ export declare function isRuleLive(rule: PersistedAllowRule): boolean;
|
|
|
551
695
|
* cwd to compare against covers nothing (fail-closed, {@link scopeCoversCwd}).
|
|
552
696
|
*
|
|
553
697
|
* `scope` rides beside the rule rather than inside it for the PROPOSED half's sake: a
|
|
554
|
-
* {@link
|
|
698
|
+
* {@link ParsedRule} carries no scope, so a proposal is judged against the scope it would LAND in.
|
|
555
699
|
*
|
|
556
700
|
* design/382 §4.3 — the call context carries a THIRD axis, `sessionId` (optional: an existing caller
|
|
557
701
|
* that threads only `{tool, cwd}` still compiles and still means what it meant — no session rule can
|
|
@@ -576,13 +720,16 @@ export declare function eligibleContext(rule: {
|
|
|
576
720
|
* conjunction arm and its coverage-set selection here, the suggestion face's coverage judgement and the
|
|
577
721
|
* consent prepare's ({@link segmentCoverageOf} is the shared instrument). One predicate, so a rule that
|
|
578
722
|
* one consumer refuses cannot be a rule another counts: the historically inlined trio of checks
|
|
579
|
-
* (live / tool / scope) is this function, verbatim
|
|
723
|
+
* (live / tool / scope) is this function, verbatim — plus the BEHAVIOR conjunct: a mixed table holds
|
|
724
|
+
* allow, deny and ask rows, and a consumer asking "which allow rules cover this" must not be answered
|
|
725
|
+
* by a deny row that happens to spell the same text. The behavior is a REQUIRED argument for the same
|
|
726
|
+
* reason the parser's is: a defaulted allow here is a silent widening.
|
|
580
727
|
*/
|
|
581
|
-
export declare function eligiblePersisted(rule:
|
|
728
|
+
export declare function eligiblePersisted(rule: PersistedRule, call: {
|
|
582
729
|
tool: string;
|
|
583
730
|
cwd: string | undefined;
|
|
584
731
|
sessionId?: string;
|
|
585
|
-
}): boolean;
|
|
732
|
+
}, behavior: RuleBehavior): boolean;
|
|
586
733
|
/**
|
|
587
734
|
* Find the rules that admit this command for a task in `cwd`, or `undefined`.
|
|
588
735
|
*
|
|
@@ -611,8 +758,12 @@ export declare function eligiblePersisted(rule: PersistedAllowRule, call: {
|
|
|
611
758
|
*
|
|
612
759
|
* Order is defined only for reporting (what the decision names); no rule is stronger than another —
|
|
613
760
|
* they are all the same one answer, "the person already said yes to this".
|
|
761
|
+
*
|
|
762
|
+
* ALLOW rows only: this is the allow direction's question, and a deny/ask row in the same table is not
|
|
763
|
+
* coverage (the eligibility predicate's behavior conjunct). The mixed-table walk that orders the three
|
|
764
|
+
* behaviors is {@link adjudicatePersistedRules}.
|
|
614
765
|
*/
|
|
615
|
-
export declare function findAdmittingRule(rules: readonly
|
|
766
|
+
export declare function findAdmittingRule(rules: readonly PersistedRule[], call: {
|
|
616
767
|
tool: string;
|
|
617
768
|
command: string;
|
|
618
769
|
cwd: string | undefined;
|
|
@@ -622,7 +773,55 @@ export declare function findAdmittingRule(rules: readonly PersistedAllowRule[],
|
|
|
622
773
|
* `./x` against the moved cursor, not the task root). Defaults to `cwd`; the scope/eligibility
|
|
623
774
|
* axis stays `cwd` either way. */
|
|
624
775
|
execCwd?: string;
|
|
625
|
-
}): readonly
|
|
776
|
+
}): readonly PersistedRule[] | undefined;
|
|
777
|
+
/** The answer of {@link adjudicatePersistedRules}: which behavior decided, and the rule(s) it stands
|
|
778
|
+
* on — a one-element set for a deny/ask (the first table-order rule of the deciding behavior that
|
|
779
|
+
* reaches the call) and the allow direction's coverage SET ({@link findAdmittingRule}). */
|
|
780
|
+
export interface PersistedRuleVerdict {
|
|
781
|
+
readonly behavior: RuleBehavior;
|
|
782
|
+
readonly rules: readonly PersistedRule[];
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* The ONE adjudication over a MIXED persisted-rule table for a shell call: walk the behaviors in
|
|
786
|
+
* {@link RULE_BEHAVIOR_PRECEDENCE} order and answer with the first behavior whose eligible rules speak
|
|
787
|
+
* for the call — deny before ask before allow, the upstream arbiter's order across its three lists.
|
|
788
|
+
*
|
|
789
|
+
* Each behavior is judged with ITS direction's instrument: a deny/ask row through
|
|
790
|
+
* {@link ruleReachesProgramRun} (the wide tightening reading: whole command or any program run in
|
|
791
|
+
* it), an allow through {@link findAdmittingRule} (the narrow reading: the whole call, or every segment
|
|
792
|
+
* of it, covered by recorded yeses). The asymmetry is the point and runs one way only: a deny reads
|
|
793
|
+
* MORE of a command than an allow does, so an exact allow naming a whole chain can never outrun a deny
|
|
794
|
+
* on one of its parts. Eligibility (live, this tool, scope covers the call, THIS behavior) is the one
|
|
795
|
+
* predicate every consumer shares. `undefined` ⇒ no rule of any behavior speaks.
|
|
796
|
+
*/
|
|
797
|
+
export declare function adjudicatePersistedRules(rules: readonly PersistedRule[], call: {
|
|
798
|
+
tool: string;
|
|
799
|
+
command: string;
|
|
800
|
+
cwd: string | undefined;
|
|
801
|
+
sessionId?: string;
|
|
802
|
+
execCwd?: string;
|
|
803
|
+
}): PersistedRuleVerdict | undefined;
|
|
804
|
+
/**
|
|
805
|
+
* The path-family sibling of {@link adjudicatePersistedRules}: the same precedence walk over the rules
|
|
806
|
+
* of one path-targeting tool, judged on the call's resolved target path.
|
|
807
|
+
*
|
|
808
|
+
* TWO targets, because the two directions read the call's path slot differently (design/388 B6/B17): the
|
|
809
|
+
* tightening arms judge the PROTECTIVE target (`protectivePathTargetOf` — the object face's declaration,
|
|
810
|
+
* the catalog's for the name as the floor: reading the catalog for an undeclared same-name tool can only
|
|
811
|
+
* tighten a deny) with {@link pathRuleReaches}; the allow arm judges the DECLARED target
|
|
812
|
+
* (`declaredPathTargetOf` — the face's OWN declaration only: a same-name tool that declares no path
|
|
813
|
+
* target is unconfirmable, and no grant admits it) with {@link directoryRuleAdmits}, and for the Read tool
|
|
814
|
+
* ONLY — a directory allow is a standing yes to reads, never to a write. Either target may be absent
|
|
815
|
+
* (no resolvable path in that reading); the arm that needs it then speaks for nothing.
|
|
816
|
+
*/
|
|
817
|
+
export declare function adjudicatePersistedPathRules(rules: readonly PersistedRule[], call: {
|
|
818
|
+
tool: string;
|
|
819
|
+
cwd: string | undefined;
|
|
820
|
+
sessionId?: string;
|
|
821
|
+
}, targets: {
|
|
822
|
+
readonly tighten?: string;
|
|
823
|
+
readonly allow?: string;
|
|
824
|
+
}, bases: PathRuleBases): PersistedRuleVerdict | undefined;
|
|
626
825
|
/**
|
|
627
826
|
* design/375 §5.2 — the per-segment coverage table for `command`: which segments an eligible rule
|
|
628
827
|
* already admits, judged with the SAME split and the same eligibility predicates as the gate's
|
|
@@ -654,9 +853,9 @@ export declare function findAdmittingRule(rules: readonly PersistedAllowRule[],
|
|
|
654
853
|
* adjudication inputs.
|
|
655
854
|
*/
|
|
656
855
|
export declare function segmentCoverageOf(command: string, rules: {
|
|
657
|
-
persisted: readonly
|
|
856
|
+
persisted: readonly PersistedRule[];
|
|
658
857
|
proposed?: readonly {
|
|
659
|
-
rule:
|
|
858
|
+
rule: ParsedRule;
|
|
660
859
|
scope: RuleScope;
|
|
661
860
|
}[];
|
|
662
861
|
}, call: {
|