@sema-agent/core 7.45.0 → 7.46.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 +246 -1
- package/dist/agents/extra-tool-names.d.ts +65 -0
- package/dist/agents/extra-tool-names.js +124 -0
- package/dist/agents/subagent.d.ts +17 -5
- package/dist/agents/subagent.js +33 -25
- package/dist/agents/tool-filter.d.ts +17 -1
- package/dist/agents/tool-filter.js +7 -0
- package/dist/core/agent-config-dir.d.ts +20 -0
- package/dist/core/agent-config-dir.js +49 -0
- package/dist/core/allow-entry.d.ts +5 -3
- package/dist/core/allow-entry.js +3 -1
- package/dist/core/ask-origin.js +1 -1
- package/dist/core/checkpoint-store.d.ts +39 -2
- package/dist/core/effective-path-target.d.ts +136 -4
- package/dist/core/effective-path-target.js +110 -2
- package/dist/core/engine-notice.d.ts +5 -0
- package/dist/core/gate-fold.js +3 -0
- package/dist/core/gate-lanes.js +9 -3
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/path-rule-binding.d.ts +65 -0
- package/dist/core/path-rule-binding.js +99 -0
- package/dist/core/permission-rule-model.d.ts +47 -18
- package/dist/core/permission-rule-model.js +38 -42
- package/dist/core/permission-rule-provider.d.ts +7 -0
- package/dist/core/permission-rule-provider.js +10 -3
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +6 -2
- package/dist/core/permission-rule-sync.js +2 -2
- package/dist/core/permission-rules.d.ts +22 -40
- package/dist/core/permission-rules.js +50 -43
- package/dist/core/runner/child-tree.d.ts +97 -0
- package/dist/core/runner/child-tree.js +104 -0
- package/dist/core/runner/construction-doors.d.ts +5 -0
- package/dist/core/runner/construction-doors.js +6 -0
- package/dist/core/runner/contracts.d.ts +46 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
- package/dist/core/runner/permission-rule-lanes.js +18 -12
- package/dist/core/runner/persisted-path-rows.d.ts +31 -7
- package/dist/core/runner/persisted-path-rows.js +31 -12
- package/dist/core/runner/prepare-ask-lane.d.ts +4 -0
- package/dist/core/runner/prepare-ask-lane.js +4 -2
- package/dist/core/runner/prepare-execution-env.d.ts +5 -2
- package/dist/core/runner/prepare-execution-env.js +10 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -0
- package/dist/core/runner/prepare-gate-stations.js +2 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
- package/dist/core/runner/prepare-hands-readface.js +6 -6
- package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
- package/dist/core/runner/prepare-inherited-gate.js +8 -2
- package/dist/core/runner/prepare-park-ask.d.ts +3 -0
- package/dist/core/runner/prepare-park-ask.js +5 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +14 -2
- package/dist/core/runner/prepare-policy-chain.js +11 -4
- package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
- package/dist/core/runner/prepare-suspend-saga.js +7 -1
- package/dist/core/runner/prepare-task.js +37 -18
- package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
- package/dist/core/runner/prepare-turn-wiring.js +2 -1
- package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
- package/dist/core/runner/prepare-workspace-restore.js +1 -0
- package/dist/core/runner/resume-apply.js +17 -3
- package/dist/core/runner/resume-checkpoint-screen.js +13 -0
- package/dist/core/runner/run-leg.js +5 -1
- package/dist/core/runner/runtask.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +1 -1
- package/dist/core/runner/write-protection-station.d.ts +3 -2
- package/dist/core/runner/write-protection-station.js +1 -0
- package/dist/core/runner-deps.d.ts +14 -0
- package/dist/core/sensitive-path-policy.d.ts +8 -0
- package/dist/core/sensitive-path-policy.js +18 -2
- package/dist/core/shell-wrapper-projection.d.ts +11 -0
- package/dist/core/shell-wrapper-projection.js +56 -0
- package/dist/core/shell-wrapper-table.d.ts +144 -34
- package/dist/core/shell-wrapper-table.js +40 -20
- package/dist/core/synthetic-read-verdict.d.ts +3 -0
- package/dist/core/synthetic-read-verdict.js +3 -1
- package/dist/core/tool-catalog-entries.d.ts +21 -0
- package/dist/core/tool-catalog-entries.js +9 -0
- package/dist/core/tool-policy.d.ts +30 -8
- package/dist/core/tool-policy.js +9 -7
- package/dist/core/tool-spec.d.ts +3 -2
- package/dist/core/write-protect.d.ts +4 -0
- package/dist/core/write-protect.js +30 -9
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -2
- package/dist/orchestration/run-spec.js +8 -7
- package/dist/orchestration/workflow.js +1 -1
- package/dist/shell/lex.d.ts +7 -0
- package/dist/shell/lex.js +17 -0
- package/dist/tools/fs/bash-program-position.d.ts +8 -87
- package/dist/tools/fs/bash-program-position.js +33 -69
- package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
- package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/program-read-model.d.ts +694 -0
- package/dist/tools/fs/program-read-model.js +1252 -0
- package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
- package/dist/tools/fs/program-read-model.rows.js +613 -0
- package/dist/tools/fs/read-deny-home.d.ts +19 -1
- package/dist/tools/fs/read-deny-home.js +62 -24
- package/dist/tools/fs/read-deny.d.ts +54 -14
- package/dist/tools/fs/read-deny.js +104 -50
- package/dist/tools/fs/removal-targets.d.ts +7 -6
- package/dist/tools/fs/removal-targets.js +22 -22
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +10 -3
- package/dist/tools/fs/shell-read-argv.d.ts +85 -0
- package/dist/tools/fs/shell-read-argv.js +386 -0
- package/dist/tools/fs/shell-read-bindings.d.ts +273 -0
- package/dist/tools/fs/shell-read-bindings.js +746 -0
- package/dist/tools/fs/shell-read-check.js +23 -15
- package/dist/tools/fs/shell-read-establish.js +79 -5
- package/dist/tools/fs/shell-read-judge.d.ts +11 -1
- package/dist/tools/fs/shell-read-judge.js +9 -1
- package/dist/tools/fs/shell-read-plan.d.ts +31 -7
- package/dist/tools/fs/shell-read-plan.js +299 -393
- package/dist/tools/fs/shell-read-word.d.ts +29 -26
- package/dist/tools/fs/shell-read-word.js +30 -38
- package/dist/tools/fs/shell-read-write-targets.d.ts +15 -64
- package/dist/tools/fs/shell-read-write-targets.js +83 -136
- package/dist/tools/fs/shell-reentry-text.d.ts +38 -6
- package/dist/tools/fs/shell-reentry-text.js +90 -53
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +17 -3
- package/wire-changes/core@7.46.0.json +1522 -0
|
@@ -76,7 +76,7 @@ import { type DenyFace } from "../tools/fs/read-deny.js";
|
|
|
76
76
|
import type { AssertAllKeysHandled } from "./ask-origin.js";
|
|
77
77
|
import { type ToolCallLike } from "./tool-registry.js";
|
|
78
78
|
import { type PathFamily, type PathIdentity } from "../tools/fs/safety.js";
|
|
79
|
-
import type
|
|
79
|
+
import { type TargetViews } from "./path-rule-binding.js";
|
|
80
80
|
/**
|
|
81
81
|
* The closed set of rule BEHAVIORS — what a matching rule says about the call. The upstream vocabulary
|
|
82
82
|
* verbatim (`allow` / `deny` / `ask`, the three settings lists), in PRECEDENCE order: when rules of more
|
|
@@ -144,7 +144,7 @@ export declare const READ_RULE_TOOL = "Read";
|
|
|
144
144
|
* its tool's name as its reach (`Write(<path>)` is judged on the Write tool, a stricter reading the reference does not make).
|
|
145
145
|
*
|
|
146
146
|
* The two columns are enforced where each behavior can be: a deny row is exported to the engine's deny tables
|
|
147
|
-
* (`ToolPolicy.
|
|
147
|
+
* (`ToolPolicy.pathRules` bound where the policy is installed, the person's rows at prepare), which refuse inside the tool, descend into a search's
|
|
148
148
|
* results and reach the shell's file commands; an ask is a question asked at the gate before the call runs, so it is
|
|
149
149
|
* judged there, on the call's own path target only — it does not descend (a search rooted above the asked path is not
|
|
150
150
|
* asked, and its results are not filtered).
|
|
@@ -600,7 +600,7 @@ export declare function isUsablePathBase(base: string | undefined, family: PathF
|
|
|
600
600
|
* defect (a `~/` deny falling back to the engine host's home guarded the wrong directory; a `/…` deny with
|
|
601
601
|
* no root reached nothing at all).
|
|
602
602
|
*/
|
|
603
|
-
export declare function pathRuleReachOf(rule: Pick<PersistedRule, "match" | "command" | "behavior">, target: PathIdentity, bases: PathRuleBases): ProgramRunReachOutcome;
|
|
603
|
+
export declare function pathRuleReachOf(rule: Pick<PersistedRule, "match" | "command" | "behavior">, target: PathIdentity, bases: PathRuleBases, treeFamily?: PathFamily | undefined): ProgramRunReachOutcome;
|
|
604
604
|
/**
|
|
605
605
|
* Where a path rule's reach STARTS under the call's bases — its HEAD: the location the resolved pattern names before
|
|
606
606
|
* its first segment carrying `*` (the whole path when none does), or a directory rule's directory. Everything the rule
|
|
@@ -772,7 +772,7 @@ export declare function lexicalNormalAbsolutePathOf(path: string, family: PathFa
|
|
|
772
772
|
* there is nothing here for it to clear). As a DENY/ASK the same containment refuses or questions every
|
|
773
773
|
* path under the directory, on whichever path tool the rule names.
|
|
774
774
|
*/
|
|
775
|
-
export declare function directoryRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, path: PathIdentity, bases: PathRuleBases): boolean;
|
|
775
|
+
export declare function directoryRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, path: PathIdentity, bases: PathRuleBases, treeFamily?: PathFamily | undefined): boolean;
|
|
776
776
|
/** The identity of a directory SPELLING (the cd resolver's `/`-rooted output) under the call's tree, for the
|
|
777
777
|
* consumers that hand {@link directoryRuleAdmits} a spelling; a rule minted from it stores this form. */
|
|
778
778
|
export declare function directoryIdentityOf(spelling: string, bases: PathRuleBases): PathIdentity | undefined;
|
|
@@ -785,7 +785,18 @@ export declare function directoryIdentityOf(spelling: string, bases: PathRuleBas
|
|
|
785
785
|
* r3-② cwd posture), never inferred from the process. ONE function on purpose: this is the scope
|
|
786
786
|
* conjunct of the single eligibility predicate ({@link eligibleContext}), and a sister predicate
|
|
787
787
|
* would be the second fact source the 375 §5.1 discipline exists to forbid. */
|
|
788
|
-
export declare function scopeCoversCwd(scope: RuleScope, cwd: string | undefined, sessionId?: string): boolean;
|
|
788
|
+
export declare function scopeCoversCwd(scope: RuleScope, cwd: string | undefined, sessionId?: string, identity?: ScopeRootIdentity): boolean;
|
|
789
|
+
/**
|
|
790
|
+
* A leg's root as an IDENTITY for project coverage: its canonical spelling, and the leg's resolution of a project row's
|
|
791
|
+
* scope root to the location it leads to. A DENY or ASK project row written under one spelling of a directory (a link the
|
|
792
|
+
* host spelled, a firmlink) covers a leg rooted at another spelling of the same directory: the row applies by where the two
|
|
793
|
+
* roots ARE, not only by how they are spelled. An allow row never reads it ({@link spelledScope}). A caller that holds no
|
|
794
|
+
* resolution (no leg, no environment) compares the spellings alone.
|
|
795
|
+
*/
|
|
796
|
+
export interface ScopeRootIdentity {
|
|
797
|
+
readonly canonicalRoot: string;
|
|
798
|
+
readonly canonicalOf: (root: string) => string | undefined;
|
|
799
|
+
}
|
|
789
800
|
/** Is this rule live — i.e. does it still carry at least one add? Deleted adds are removed by the store
|
|
790
801
|
* when tombstones are applied, so a rule with an empty `adds` is a rule that no longer exists. */
|
|
791
802
|
export declare function isRuleLive(rule: PersistedRule): boolean;
|
|
@@ -815,6 +826,7 @@ export declare function eligibleContext(rule: {
|
|
|
815
826
|
tool: string;
|
|
816
827
|
cwd: string | undefined;
|
|
817
828
|
sessionId?: string;
|
|
829
|
+
scopeIdentity?: ScopeRootIdentity;
|
|
818
830
|
}): boolean;
|
|
819
831
|
/**
|
|
820
832
|
* design/375 §5.1 — MAY this PERSISTED rule participate in admitting this call at all? The liveness
|
|
@@ -834,7 +846,16 @@ export declare function eligiblePersisted(rule: PersistedRule, call: {
|
|
|
834
846
|
tool: string;
|
|
835
847
|
cwd: string | undefined;
|
|
836
848
|
sessionId?: string;
|
|
849
|
+
scopeIdentity?: ScopeRootIdentity;
|
|
837
850
|
}, behavior: RuleBehavior): boolean;
|
|
851
|
+
/**
|
|
852
|
+
* The call context an ALLOW row's scope is judged in: the spellings alone. A deny or ask row applies wherever its project's
|
|
853
|
+
* directory is (its scope root resolved where it leads — more rows, only ever more protection); an allow row applies only
|
|
854
|
+
* where its scope root is SPELLED, so a link someone can re-point never carries a standing approval to another directory.
|
|
855
|
+
*/
|
|
856
|
+
export declare function spelledScope<C extends {
|
|
857
|
+
scopeIdentity?: ScopeRootIdentity;
|
|
858
|
+
}>(call: C): Omit<C, "scopeIdentity">;
|
|
838
859
|
/**
|
|
839
860
|
* Find the rules that admit this command for a task in `cwd`, or `undefined`.
|
|
840
861
|
*
|
|
@@ -914,19 +935,23 @@ export declare function adjudicatePersistedRules(rules: readonly PersistedRule[]
|
|
|
914
935
|
cwd: string | undefined;
|
|
915
936
|
sessionId?: string;
|
|
916
937
|
execCwd?: string;
|
|
938
|
+
scopeIdentity?: ScopeRootIdentity;
|
|
917
939
|
}): PersistedRuleVerdict | undefined;
|
|
918
940
|
/**
|
|
919
941
|
* The path-family sibling of {@link adjudicatePersistedRules}: the same precedence walk over the rules
|
|
920
|
-
* of one path-targeting tool, judged on the call's
|
|
942
|
+
* of one path-targeting tool, judged on the call's target views by the one judgment every path rule meets
|
|
943
|
+
* ({@link pathRowsReach}: rows outer, views inner).
|
|
944
|
+
*
|
|
945
|
+
* TWO readings, because the two directions read the call's path slot differently (design/388 B6/B17): the
|
|
946
|
+
* tightening arms judge the PROTECTIVE reading's views (`views.tighten` — the object face's declaration, the catalog's
|
|
947
|
+
* for the name as the floor: reading the catalog for an undeclared same-name tool can only tighten a deny), every row
|
|
948
|
+
* also where its head leads (`leads`, the judging leg's resolution); the allow arm judges the DECLARED reading's views
|
|
949
|
+
* (`views.allow` — the face's OWN declaration only: a same-name tool that declares no path target is unconfirmable, and
|
|
950
|
+
* no grant admits it) with the directory rows, for the Read tool ONLY — a directory allow is a standing yes to reads,
|
|
951
|
+
* never to a write — and admits only when every view lies under the directory. Either reading may be absent (no
|
|
952
|
+
* resolvable path in that reading); the arm that needs it then speaks for nothing.
|
|
921
953
|
*
|
|
922
|
-
*
|
|
923
|
-
* tightening arms judge the PROTECTIVE target (`protectivePathTargetOf` — the object face's declaration,
|
|
924
|
-
* the catalog's for the name as the floor: reading the catalog for an undeclared same-name tool can only
|
|
925
|
-
* tighten a deny) with {@link pathRuleReaches}; the allow arm judges the DECLARED target
|
|
926
|
-
* (`declaredPathTargetOf` — the face's OWN declaration only: a same-name tool that declares no path
|
|
927
|
-
* target is unconfirmable, and no grant admits it) with {@link directoryRuleAdmits}, and for the Read tool
|
|
928
|
-
* ONLY — a directory allow is a standing yes to reads, never to a write. Either target may be absent
|
|
929
|
-
* (no resolvable path in that reading); the arm that needs it then speaks for nothing.
|
|
954
|
+
* A row is anchored by `anchorsOf` (the person's rows: a project row at its scope root, the rest at the chain's top leg).
|
|
930
955
|
*
|
|
931
956
|
* `call.face` is the face the call's protective target is on (`read` / `write`): an ASK row speaks for the call when it
|
|
932
957
|
* is the call's tool's row, or when {@link PATH_RULE_FACES} puts that face in the row's ask column (a `Read(<path>)` ask
|
|
@@ -938,10 +963,11 @@ export declare function adjudicatePersistedPathRules(rules: readonly PersistedRu
|
|
|
938
963
|
cwd: string | undefined;
|
|
939
964
|
sessionId?: string;
|
|
940
965
|
face?: DenyFace | undefined;
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
readonly
|
|
944
|
-
|
|
966
|
+
scopeIdentity?: ScopeRootIdentity;
|
|
967
|
+
}, views: {
|
|
968
|
+
readonly tighten?: TargetViews;
|
|
969
|
+
readonly allow?: TargetViews;
|
|
970
|
+
}, anchorsOf: (rule: PersistedRule) => PathRuleBases, leads: ReadonlyMap<string, string | undefined>): PersistedRuleVerdict | undefined;
|
|
945
971
|
/**
|
|
946
972
|
* The name-family sibling of {@link adjudicatePersistedRules}: the same precedence walk over the rules of one name tool
|
|
947
973
|
* ({@link NAME_RULE_FACES}), judged on the one string the call names (`nameRuleSubjectOf`). A rule of the tool speaks for the call
|
|
@@ -955,6 +981,7 @@ export declare function adjudicatePersistedNameRules(rules: readonly PersistedRu
|
|
|
955
981
|
name: string | undefined;
|
|
956
982
|
cwd: string | undefined;
|
|
957
983
|
sessionId?: string;
|
|
984
|
+
scopeIdentity?: ScopeRootIdentity;
|
|
958
985
|
}): PersistedRuleVerdict | undefined;
|
|
959
986
|
/**
|
|
960
987
|
* The ONE offer a name tool's approval card carries: the exact rule for the name the call carries (`Skill(<name>)`, the reference's
|
|
@@ -1009,6 +1036,8 @@ export declare function segmentCoverageOf(command: string, rules: {
|
|
|
1009
1036
|
/** The relative-cd resolution base (see {@link findAdmittingRule}'s member of the same name) —
|
|
1010
1037
|
* defaults to `cwd`; scope/eligibility stays `cwd`. */
|
|
1011
1038
|
execCwd?: string;
|
|
1039
|
+
/** The leg's root as an identity for project coverage ({@link ScopeRootIdentity}). */
|
|
1040
|
+
scopeIdentity?: ScopeRootIdentity;
|
|
1012
1041
|
}): readonly SegmentCoverage[] | undefined;
|
|
1013
1042
|
/** One row of a per-segment coverage table (see {@link segmentCoverageOf}): the FOLDED, trimmed
|
|
1014
1043
|
* segment text (a display/correlation seat, never adjudication input) and whether an eligible rule
|
|
@@ -6,6 +6,7 @@ import { inlineUntrusted } from "./untrusted-text.js";
|
|
|
6
6
|
import { pathTargetOf, protectivePathTargetOf } from "./tool-registry.js";
|
|
7
7
|
import { heldValues } from "./tool-name-readings.js";
|
|
8
8
|
import { carriesDotSegment, isAbsoluteForFamily, isIdentityDir, isPathIdentity, isShellRootedSpellingUnmapped, isUncIdentity, pathFamilyOf, pathIdentityOf } from "../tools/fs/safety.js";
|
|
9
|
+
import { bindPathRules, pathRowsReach, rowsWhereTheyLead } from "./path-rule-binding.js";
|
|
9
10
|
import { readShellCommand } from "./shell-lexer.js";
|
|
10
11
|
import { readOnlyLineObjection, readOnlyShellVerdict } from "./read-only-shell.js";
|
|
11
12
|
export const RULE_BEHAVIORS = Object.freeze(["deny", "ask", "allow"]);
|
|
@@ -68,7 +69,7 @@ export const BARE_INTERPRETER_NAMES = new Set([
|
|
|
68
69
|
"bash", "sh", "zsh", "ksh", "csh", "tcsh", "dash", "fish", "cmd", "powershell", "pwsh", "busybox",
|
|
69
70
|
"source", ".", "trap", "eval", "exec", "command", "builtin",
|
|
70
71
|
"enable", "compgen", "complete", "bind", "mapfile", "readarray",
|
|
71
|
-
"env", "noglob", "xargs",
|
|
72
|
+
"env", "noglob", "nocorrect", "xargs",
|
|
72
73
|
"sudo", "doas", "pkexec", "su", "runuser", "ssh", "chroot", "setpriv",
|
|
73
74
|
"nohup", "nice", "ionice", "chrt", "taskset", "stdbuf", "timeout", "time", "watch",
|
|
74
75
|
"setsid", "flock", "unshare", "nsenter", "script", "numactl", "prlimit", "systemd-run", "parallel",
|
|
@@ -440,8 +441,8 @@ function baseValueOf(base, bases) {
|
|
|
440
441
|
}
|
|
441
442
|
}
|
|
442
443
|
}
|
|
443
|
-
function resolvePathPattern(pattern, bases, behavior) {
|
|
444
|
-
const family =
|
|
444
|
+
function resolvePathPattern(pattern, bases, behavior, treeFamily = pathFamilyOf(bases)) {
|
|
445
|
+
const family = treeFamily;
|
|
445
446
|
const tree = bases.root ?? bases.cwd;
|
|
446
447
|
if (pattern.startsWith("//")) {
|
|
447
448
|
const abs = pathIdentityOf("/" + pattern.slice(2), family, tree);
|
|
@@ -503,15 +504,15 @@ function globSegmentsReach(globs, target) {
|
|
|
503
504
|
};
|
|
504
505
|
return walk(0, 0);
|
|
505
506
|
}
|
|
506
|
-
export function pathRuleReachOf(rule, target, bases) {
|
|
507
|
+
export function pathRuleReachOf(rule, target, bases, treeFamily = pathFamilyOf(bases)) {
|
|
507
508
|
if (rule.match === "subpath")
|
|
508
|
-
return directoryRuleAdmits(rule, target, bases) ? REACHED : NOT_REACHED;
|
|
509
|
+
return directoryRuleAdmits(rule, target, bases, treeFamily) ? REACHED : NOT_REACHED;
|
|
509
510
|
if (rule.match !== "path")
|
|
510
511
|
return NOT_REACHED;
|
|
511
|
-
const family =
|
|
512
|
+
const family = treeFamily;
|
|
512
513
|
if (!isPathIdentity(target, family))
|
|
513
514
|
return NOT_REACHED;
|
|
514
|
-
const r = resolvePathPattern(rule.command, bases, rule.behavior);
|
|
515
|
+
const r = resolvePathPattern(rule.command, bases, rule.behavior, family);
|
|
515
516
|
if ("missingBase" in r) {
|
|
516
517
|
return { reach: "unreadable", reason: `the rule is relative to ${PATH_RULE_BASE_LABEL[r.missingBase]} and this call supplies no absolute \`${r.missingBase}\` base of this tree's family to resolve it against` };
|
|
517
518
|
}
|
|
@@ -706,10 +707,10 @@ function isLexicalNormalAbsoluteDir(path) {
|
|
|
706
707
|
const segments = path.split("/").slice(1);
|
|
707
708
|
return segments.length > 0 && segments.every((s) => s !== "" && s !== "." && s !== "..");
|
|
708
709
|
}
|
|
709
|
-
export function directoryRuleAdmits(rule, path, bases) {
|
|
710
|
+
export function directoryRuleAdmits(rule, path, bases, treeFamily = pathFamilyOf(bases)) {
|
|
710
711
|
if (rule.match !== "subpath")
|
|
711
712
|
return false;
|
|
712
|
-
const family =
|
|
713
|
+
const family = treeFamily;
|
|
713
714
|
if (carriesDotSegment(rule.command, family))
|
|
714
715
|
return false;
|
|
715
716
|
const dir = pathIdentityOf(rule.command, family, bases.root ?? bases.cwd);
|
|
@@ -820,21 +821,29 @@ function resolveCdSegmentDirectories(segments, cwd) {
|
|
|
820
821
|
return dir === undefined || isShellRootedSpellingUnmapped(pathFamilyOf(bases), dir) ? undefined : directoryIdentityOf(dir, bases);
|
|
821
822
|
});
|
|
822
823
|
}
|
|
823
|
-
export function scopeCoversCwd(scope, cwd, sessionId) {
|
|
824
|
+
export function scopeCoversCwd(scope, cwd, sessionId, identity) {
|
|
824
825
|
if (scope.kind === "global")
|
|
825
826
|
return true;
|
|
826
827
|
if (scope.kind === "session")
|
|
827
828
|
return sessionId !== undefined && sessionId === scope.sessionId;
|
|
828
|
-
|
|
829
|
+
if (cwd === undefined)
|
|
830
|
+
return false;
|
|
831
|
+
if (pathWithinRoot(cwd, scope.root))
|
|
832
|
+
return true;
|
|
833
|
+
return identity !== undefined && pathWithinRoot(identity.canonicalRoot, identity.canonicalOf(scope.root) ?? scope.root);
|
|
829
834
|
}
|
|
830
835
|
export function isRuleLive(rule) {
|
|
831
836
|
return rule.adds.length > 0;
|
|
832
837
|
}
|
|
833
838
|
export function eligibleContext(rule, call) {
|
|
834
|
-
return rule.tool === call.tool && scopeCoversCwd(rule.scope, call.cwd, call.sessionId);
|
|
839
|
+
return rule.tool === call.tool && scopeCoversCwd(rule.scope, call.cwd, call.sessionId, call.scopeIdentity);
|
|
835
840
|
}
|
|
836
841
|
export function eligiblePersisted(rule, call, behavior) {
|
|
837
|
-
return rule.behavior === behavior && isRuleLive(rule) && eligibleContext(rule, call);
|
|
842
|
+
return rule.behavior === behavior && isRuleLive(rule) && eligibleContext(rule, behavior === "allow" ? spelledScope(call) : call);
|
|
843
|
+
}
|
|
844
|
+
export function spelledScope(call) {
|
|
845
|
+
const { scopeIdentity: _identity, ...spelled } = call;
|
|
846
|
+
return spelled;
|
|
838
847
|
}
|
|
839
848
|
function firstEligibleAdmittingRule(rules, commandText, call) {
|
|
840
849
|
for (const rule of rules) {
|
|
@@ -911,41 +920,28 @@ export function adjudicatePersistedRules(rules, call) {
|
|
|
911
920
|
}
|
|
912
921
|
return undefined;
|
|
913
922
|
}
|
|
914
|
-
export function adjudicatePersistedPathRules(rules, call,
|
|
923
|
+
export function adjudicatePersistedPathRules(rules, call, views, anchorsOf, leads) {
|
|
915
924
|
const speaks = (rule, behavior) => eligiblePersisted(rule, call, behavior) ||
|
|
916
925
|
(behavior === "ask" && call.face !== undefined && PATH_RULE_FACES[rule.tool]?.ask?.includes(call.face) === true && eligiblePersisted(rule, { ...call, tool: rule.tool }, behavior));
|
|
926
|
+
const bind = (rows) => bindPathRules(rows.map((rule) => ({ parsed: rule, ruleText: rule.rule, rule })), (s) => anchorsOf(s.rule));
|
|
917
927
|
for (const behavior of RULE_BEHAVIORS_BY_PRECEDENCE) {
|
|
918
928
|
if (behavior === "allow") {
|
|
919
|
-
if (
|
|
929
|
+
if (views.allow === undefined || call.tool !== READ_RULE_TOOL)
|
|
920
930
|
continue;
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
if (directoryRuleAdmits(rule, targets.allow.identity, bases))
|
|
925
|
-
return { behavior, rules: [rule] };
|
|
926
|
-
}
|
|
931
|
+
const hit = pathRowsReach(bind(rules.filter((rule) => rule.match === "subpath" && eligiblePersisted(rule, call, behavior))), views.allow, "allow");
|
|
932
|
+
if (hit !== undefined)
|
|
933
|
+
return { behavior, rules: [hit.row.spec.rule] };
|
|
927
934
|
continue;
|
|
928
935
|
}
|
|
929
|
-
|
|
930
|
-
if (tighten === undefined || tighten.kind === "refused")
|
|
936
|
+
if (views.tighten === undefined)
|
|
931
937
|
continue;
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
continue;
|
|
940
|
-
}
|
|
941
|
-
const outcome = pathRuleReachOf(rule, tighten.identity, bases);
|
|
942
|
-
if (outcome.reach === "reached")
|
|
943
|
-
return { behavior, rules: [rule] };
|
|
944
|
-
if (outcome.reach === "unreadable")
|
|
945
|
-
unreadable ??= { behavior: "ask", rules: [rule], unreadable: outcome.reason };
|
|
946
|
-
}
|
|
947
|
-
if (unreadable !== undefined)
|
|
948
|
-
return unreadable;
|
|
938
|
+
const rows = rules.filter((rule) => (rule.match === "path" || rule.match === "subpath") && speaks(rule, behavior));
|
|
939
|
+
if (rows.length === 0)
|
|
940
|
+
continue;
|
|
941
|
+
const hit = pathRowsReach(rowsWhereTheyLead(bind(rows), leads), views.tighten, "tighten");
|
|
942
|
+
if (hit === undefined)
|
|
943
|
+
continue;
|
|
944
|
+
return hit.unreadable !== undefined ? { behavior: "ask", rules: [hit.row.spec.rule], unreadable: hit.unreadable } : { behavior, rules: [hit.row.spec.rule] };
|
|
949
945
|
}
|
|
950
946
|
return undefined;
|
|
951
947
|
}
|
|
@@ -987,9 +983,9 @@ export function segmentCoverageOf(command, rules, call) {
|
|
|
987
983
|
(dir !== undefined && firstEligibleDirectoryRule(rules.persisted, dir, call) !== undefined) ||
|
|
988
984
|
proposed.some((p) => p.rule.tool === "Read"
|
|
989
985
|
? dir !== undefined &&
|
|
990
|
-
eligibleContext({ tool: p.rule.tool, scope: p.scope }, { tool: "Read"
|
|
986
|
+
eligibleContext({ tool: p.rule.tool, scope: p.scope }, { ...spelledScope(call), tool: "Read" }) &&
|
|
991
987
|
directoryRuleAdmits(p.rule, dir, basesOfCwd(call.execCwd ?? call.cwd))
|
|
992
|
-
: eligibleContext({ tool: p.rule.tool, scope: p.scope }, call) && ruleAdmitsCommand(p.rule, segment)),
|
|
988
|
+
: eligibleContext({ tool: p.rule.tool, scope: p.scope }, spelledScope(call)) && ruleAdmitsCommand(p.rule, segment)),
|
|
993
989
|
};
|
|
994
990
|
});
|
|
995
991
|
}
|
|
@@ -86,6 +86,13 @@ export interface EffectivePermissionRules {
|
|
|
86
86
|
* served that are not canonical session rows of this session). The org partition's lines ride
|
|
87
87
|
* `org.disclosures`. */
|
|
88
88
|
disclosures: string[];
|
|
89
|
+
/** The durable rows left out because their project scope root is not a complete absolute path (a foreign writer's or a
|
|
90
|
+
* hand edit's row): each row's text and its scope root as served (a non-string root as its type). Absent when none. The
|
|
91
|
+
* reader tells the person (`permission.rule_row_dropped`). */
|
|
92
|
+
droppedRows?: ReadonlyArray<{
|
|
93
|
+
readonly rule: string;
|
|
94
|
+
readonly scopeRoot: string;
|
|
95
|
+
}>;
|
|
89
96
|
}
|
|
90
97
|
/**
|
|
91
98
|
* The host-visible store, already anchored to one owner. One read verb, which never throws: each
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { escapeForDisclosure } from "./permission-rule-model.js";
|
|
2
|
-
import { PERMISSION_RULE_WRITER, errText, normalizePersistedRule, sameRuleIdentity, writerOf, } from "./permission-rule-store.js";
|
|
2
|
+
import { PERMISSION_RULE_WRITER, errText, isValidDurableScope, normalizePersistedRule, sameRuleIdentity, writerOf, } from "./permission-rule-store.js";
|
|
3
3
|
import { UNGOVERNED_ORG_RESOLUTION, compileOrgShadowPredicate, createOrgRulePartition } from "./permission-rule-org.js";
|
|
4
4
|
export function ruleSourceOf(scope) {
|
|
5
5
|
return scope.kind === "global" ? "user" : scope.kind;
|
|
@@ -80,7 +80,11 @@ class UnifiedPermissionRuleStore {
|
|
|
80
80
|
const sessionRead = "unreadable" in sessionRows ? [] : sessionRows;
|
|
81
81
|
const shadowed = compileOrgShadowPredicate(orgResolution.rules);
|
|
82
82
|
const rules = [];
|
|
83
|
-
|
|
83
|
+
const illegal = durableRead.rules.filter((r) => !isValidDurableScope(r.scope));
|
|
84
|
+
const droppedRows = illegal.map((r) => ({ rule: String(r.rule), scopeRoot: scopeRootAsServed(r.scope.root) }));
|
|
85
|
+
if (illegal.length > 0)
|
|
86
|
+
disclosures.push(`the persisted rule partition served ${illegal.length} row(s) whose project scope root is not a complete absolute path — dropped, not adjudicated: ${illegal.slice(0, 3).map((r) => `${escapeForDisclosure(r.rule)} (${escapeForDisclosure(r.scope.root)})`).join(", ")}`);
|
|
87
|
+
for (const r of [...sessionRead, ...durableRead.rules.filter((x) => !illegal.includes(x))]) {
|
|
84
88
|
rules.push({ ...r, source: ruleSourceOf(r.scope), status: r.behavior === "allow" && shadowed(r) ? "shadowed-by-org" : "live" });
|
|
85
89
|
}
|
|
86
90
|
const removed = [];
|
|
@@ -93,7 +97,7 @@ class UnifiedPermissionRuleStore {
|
|
|
93
97
|
continue;
|
|
94
98
|
removed.push({ behavior: t.behavior, rule: t.rule, scope: t.scope, source: ruleSourceOf(t.scope), status: "removed" });
|
|
95
99
|
}
|
|
96
|
-
return { rules, removed, org: orgResolution, rev: durableRead.rev, disclosures, ...failed };
|
|
100
|
+
return { rules, removed, org: orgResolution, rev: durableRead.rev, disclosures, ...failed, ...(droppedRows.length > 0 ? { droppedRows } : {}) };
|
|
97
101
|
}
|
|
98
102
|
async readSessionRows(sessionId, disclosures) {
|
|
99
103
|
if (this.session === undefined || sessionId === undefined)
|
|
@@ -116,6 +120,9 @@ class UnifiedPermissionRuleStore {
|
|
|
116
120
|
}
|
|
117
121
|
}
|
|
118
122
|
}
|
|
123
|
+
function scopeRootAsServed(root) {
|
|
124
|
+
return typeof root === "string" ? root : `(${root === null ? "null" : typeof root})`;
|
|
125
|
+
}
|
|
119
126
|
function routingWriter(durable, session) {
|
|
120
127
|
return {
|
|
121
128
|
nextDot: () => durable.nextDot(),
|
|
@@ -457,6 +457,14 @@ export declare function isValidConsentScope(scope: RuleScope): boolean;
|
|
|
457
457
|
* duty (design/382 §10): every such entrance, present or future, carries this face.
|
|
458
458
|
*/
|
|
459
459
|
export declare function isValidDurableScope(scope: RuleScope): boolean;
|
|
460
|
+
/**
|
|
461
|
+
* A PROJECT scope's root is a complete absolute path — `/…`, a drive-rooted `X:\…` / `X:/…`, a UNC `\host\…` — the one
|
|
462
|
+
* predicate every scope face reads (the consent face, the durable face, the sync wire's pick). It is what a project row's
|
|
463
|
+
* path patterns anchor at (its `/x` and relative words), so a root that is not one (`C:`, a relative spelling, empty)
|
|
464
|
+
* would let the row apply (containment is a prefix test) while its anchor reads nothing: such a row is refused at every
|
|
465
|
+
* write, dropped at every read, and disclosed.
|
|
466
|
+
*/
|
|
467
|
+
export declare function isProjectScopeRoot(root: unknown): root is string;
|
|
460
468
|
/** Is `dot` at or below the frontier? Absent actor ⇒ not below (a frontier never covers what it never saw). */
|
|
461
469
|
export declare function dotAtOrBelowFrontier(dot: RuleDot, frontier: RuleSyncFrontier): boolean;
|
|
462
470
|
/**
|
|
@@ -2,6 +2,7 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import { canonicalize } from "./canonical-json.js";
|
|
3
3
|
import { escapeForDisclosure, isRuleBehavior, isRuleLive, parseRuleText } from "./permission-rule-model.js";
|
|
4
4
|
import { thrownText } from "./thrown-value.js";
|
|
5
|
+
import { isAbsolutePathForm } from "../tools/fs/safety.js";
|
|
5
6
|
export function sameRuleOwner(a, b) {
|
|
6
7
|
return a.kind === "principal" ? b.kind === "principal" && a.principal === b.principal : b.kind === "local-owner";
|
|
7
8
|
}
|
|
@@ -143,12 +144,15 @@ export function assertWriteDeltaScopeDurable(delta) {
|
|
|
143
144
|
export function isValidConsentScope(scope) {
|
|
144
145
|
const sc = scope;
|
|
145
146
|
return (sc?.kind === "global" ||
|
|
146
|
-
(sc?.kind === "project" &&
|
|
147
|
+
(sc?.kind === "project" && isProjectScopeRoot(sc.root)) ||
|
|
147
148
|
(sc?.kind === "session" && typeof sc.sessionId === "string" && sc.sessionId !== ""));
|
|
148
149
|
}
|
|
149
150
|
export function isValidDurableScope(scope) {
|
|
150
151
|
const sc = scope;
|
|
151
|
-
return sc?.kind === "global" || (sc?.kind === "project" &&
|
|
152
|
+
return sc?.kind === "global" || (sc?.kind === "project" && isProjectScopeRoot(sc.root));
|
|
153
|
+
}
|
|
154
|
+
export function isProjectScopeRoot(root) {
|
|
155
|
+
return typeof root === "string" && isAbsolutePathForm(root);
|
|
152
156
|
}
|
|
153
157
|
function isValidDot(dot) {
|
|
154
158
|
const d = dot;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { escapeForDisclosure, isRuleBehavior, parseRuleText } from "./permission-rule-model.js";
|
|
2
|
-
import { applyTombstones, errText, joinFrontiers, joinRuleStates, normalizePersistedRule, readRuleWriteOutcome, ruleSyncVector, sameRuleIdentity, screenRuleSyncState, writerOf, } from "./permission-rule-store.js";
|
|
2
|
+
import { applyTombstones, errText, isProjectScopeRoot, joinFrontiers, joinRuleStates, normalizePersistedRule, readRuleWriteOutcome, ruleSyncVector, sameRuleIdentity, screenRuleSyncState, writerOf, } from "./permission-rule-store.js";
|
|
3
3
|
import { emitTrace } from "./trace.js";
|
|
4
4
|
export const PERMISSION_RULE_SYNC_PATH = "/v1/permission-rules/sync";
|
|
5
5
|
export const LOCAL_OWNER_UNSYNCABLE_CODE = "permission_rules.local_owner_unsyncable";
|
|
@@ -262,7 +262,7 @@ function pickScopeCaptured(v) {
|
|
|
262
262
|
return { kind, scope: { kind: "global" } };
|
|
263
263
|
if (kind === "project") {
|
|
264
264
|
const root = s?.root;
|
|
265
|
-
return { kind, scope:
|
|
265
|
+
return { kind, scope: isProjectScopeRoot(root) ? { kind: "project", root } : undefined };
|
|
266
266
|
}
|
|
267
267
|
if (kind === "session") {
|
|
268
268
|
const sessionId = s?.sessionId;
|
|
@@ -30,11 +30,13 @@
|
|
|
30
30
|
* `Edit(~/.sema/settings.json)` / `Write(src/**)` / `Read(//etc/**)` deny or ask compiles through the same
|
|
31
31
|
* validator (the model's path grammar — the `//abs-dir/**` directory form and upstream's pattern form:
|
|
32
32
|
* `//` absolute, `~/` home, `/` project root, otherwise working-directory relative; `*` within a segment,
|
|
33
|
-
* `**` across) and is judged
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
33
|
+
* `**` across) and is judged on the call's PROTECTIVE path target (design/388: the object face's declaration, the
|
|
34
|
+
* catalog's for the name as the floor), every view of it (`callTargetViewsOf`), by the one judgment every path rule
|
|
35
|
+
* meets (`pathRowsReach`). A pattern's ANCHORS are the rule's own fact, taken once where the policy is installed: the
|
|
36
|
+
* declared `pathBases` word by word, else the leg the Runner installs the policy on (`policyAnchors`) — never the
|
|
37
|
+
* call's live working directory. A declared base that is not a usable absolute path is refused at compile time
|
|
38
|
+
* (`unsupported.path_base`); a policy called outside a Runner leg with no declared base reads such a rule
|
|
39
|
+
* `unreadable` (the fail-closed ask).
|
|
38
40
|
* A `Read(...)` / `Edit(...)` deny or ask reaches the calls on its behavior's FACES (`PATH_RULE_FACES`), not only
|
|
39
41
|
* its own tool: a `Read` ask asks for a Grep / Glob rooted in its path, judged on that call's own target.
|
|
40
42
|
*
|
|
@@ -53,8 +55,7 @@
|
|
|
53
55
|
*/
|
|
54
56
|
import type { NamedToolPolicy } from "./tool-policy.js";
|
|
55
57
|
import { parsePermissionRule, type ParsedPermissionRule } from "./permission-rule-syntax.js";
|
|
56
|
-
import { type
|
|
57
|
-
import type { DenyFace, PathRuleDenyRow } from "../tools/fs/read-deny.js";
|
|
58
|
+
import { type PathRuleBases } from "./permission-rule-model.js";
|
|
58
59
|
export { parsePermissionRule, type ParsedPermissionRule };
|
|
59
60
|
/** [K-PLATFORM-SWEEP] MINOR#11: Bash's REAL non-primary arg names, accepted as generic param rules
|
|
60
61
|
* (`Bash(timeout:...)`). Exported SOLELY so a lockstep test can assert it against the live Bash tool
|
|
@@ -93,9 +94,10 @@ export interface PermissionRuleIssue {
|
|
|
93
94
|
* is an object/array (a rule on it can never match); the tool is unknown to the resolver a leg
|
|
94
95
|
* supplied (absent resolver ⇒ the catalog alone judges and an unknown tool stays permissive). */
|
|
95
96
|
| "unsupported.param_unknown" | "unsupported.param_non_scalar" | "unsupported.tool_unknown"
|
|
96
|
-
/** A path-form deny/ask whose spelling needs a base this policy
|
|
97
|
-
* `pathBases.root`; a relative pattern
|
|
98
|
-
* into a rule that reaches nothing.
|
|
97
|
+
/** A path-form deny/ask whose spelling needs a base this policy DECLARES as something other than an absolute path
|
|
98
|
+
* of its tree's family (`/x` reads `pathBases.root`; a relative pattern `pathBases.cwd`, else `root`; `~/` `home`)
|
|
99
|
+
* — a bad declared value, refused rather than compiled into a rule that reaches nothing. An undeclared base is no
|
|
100
|
+
* refusal: the rule is anchored where the policy is installed. */
|
|
99
101
|
| "unsupported.path_base" | "unsupported.mcp_paren"
|
|
100
102
|
/** A parenthesised rule whose NAME is a covering spelling in a non-MCP protocol namespace
|
|
101
103
|
* (`a2a__<peer>__*(x:y)`, `a2a__<peer>(x:y)`). The param DSL is keyed by EXACT tool name, and a
|
|
@@ -143,16 +145,17 @@ export interface PermissionRulePolicyOptions {
|
|
|
143
145
|
*/
|
|
144
146
|
ruleFaces?: (toolName: string) => RuleFaceView | undefined;
|
|
145
147
|
/**
|
|
146
|
-
* The
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
148
|
+
* The ANCHORS of this rule set's path patterns ({@link PathRuleBases}), word by word: `root` for `/…`, `cwd` for a
|
|
149
|
+
* relative pattern (the declared root stands in when absent — the word's own meaning), `home` for `~/…` (an
|
|
150
|
+
* execution environment's home — never the engine process's own; #644). A word declared here anchors the rules
|
|
151
|
+
* wherever the policy runs; a word left out is anchored where the Runner installs the policy — the root and the
|
|
152
|
+
* environment's home of the leg it enters the delegation chain on — and stays there for every child and every `cd`.
|
|
153
|
+
* A declared value that is not an absolute path of its tree's family is refused at compile time
|
|
151
154
|
* (`unsupported.path_base`, naming the base — {@link ruleBasesNeeded} is the same classification).
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* answers it with the fail-closed ask (`ruleUnreadable`).
|
|
155
|
+
* These are the RULES' anchors, never the call's: a call's target is read from the call (its target reading, else its
|
|
156
|
+
* own `root` / `cwd`); only a host calling the policy directly with a request that carries neither has its target
|
|
157
|
+
* resolved against `root` / `cwd` here. A target nothing locates is unreadable: every path rule able to reach the
|
|
158
|
+
* call answers it with the fail-closed ask (`ruleUnreadable`).
|
|
156
159
|
*/
|
|
157
160
|
pathBases?: PathRuleBases;
|
|
158
161
|
}
|
|
@@ -205,28 +208,7 @@ export interface RuleFaceView {
|
|
|
205
208
|
export declare function catalogRuleFaceView(toolName: string): RuleFaceView | undefined;
|
|
206
209
|
/** Exported for the lockstep guard only (the scalar vocabulary of a catalog tool) — not part of the rule DSL. */
|
|
207
210
|
export declare const fileToolParamVocabulary: (canonicalTool: string) => ReadonlySet<string> | undefined;
|
|
208
|
-
/**
|
|
209
|
-
* The deny-table row of ONE face-keyed path deny rule, resolved against `bases` — the row's reach IS the rule's (the same
|
|
210
|
-
* parsed rule, the same bases). Every source of such rows builds them here: a policy's compiled rules, and the person's
|
|
211
|
-
* persisted store rows. The caller holds the bases the pattern needs (`ruleBasesNeeded`): a row whose base is missing
|
|
212
|
-
* reads `unreadable`, which this row treats as a reach — a deny row that cannot be judged holds.
|
|
213
|
-
*/
|
|
214
|
-
export declare function pathRuleDenyRowOf(rule: {
|
|
215
|
-
readonly parsed: ParsedRule;
|
|
216
|
-
readonly ruleText: string;
|
|
217
|
-
readonly faces: readonly DenyFace[];
|
|
218
|
-
}, bases: PathRuleBases): PathRuleDenyRow;
|
|
219
211
|
/** Dry-run 校验/迁移报告:不 throw,返回全部 issues(含 `unsupported.*` 分类,
|
|
220
212
|
* 供从 CC settings 迁移的部署逐条对照)。 */
|
|
221
213
|
export declare function validatePermissionRules(rules: PermissionRule[], opts?: Pick<PermissionRulePolicyOptions, "primaryFieldGeneric" | "caps" | "ruleFaces" | "pathBases">): PermissionRuleIssue[];
|
|
222
|
-
/**
|
|
223
|
-
* Compile declarative permission rules into ONE {@link ToolPolicy} (design/127). Evaluation order
|
|
224
|
-
* inside the policy (CC `G1e` :595272 isomorph): bare deny → command deny → param deny → bare ask →
|
|
225
|
-
* command ask → param ask → bare allow → `defaultAction` (the content-form command tiers are the
|
|
226
|
-
* field-level tiers of upstream's arbiter, beside the generic param tiers). `check()` is idempotent and side-effect-free (combinePolicies 契约);
|
|
227
|
-
* all parsing/validation happens HERE, the hot path never re-parses.
|
|
228
|
-
*
|
|
229
|
-
* ⚠️ This policy states no allow: a call no rule reaches gets `defaultAction`, whose `"allow"` is "no objection" (states
|
|
230
|
-
* nothing, clears nothing). Express default-closed via `defaultAction`, never via a trailing always-ask policy.
|
|
231
|
-
*/
|
|
232
214
|
export declare function createPermissionRulePolicy(rules: PermissionRule[], opts?: PermissionRulePolicyOptions): NamedToolPolicy;
|