@sema-agent/core 7.44.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 +418 -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 +65 -38
- package/dist/agents/tool-filter.d.ts +82 -5
- package/dist/agents/tool-filter.js +134 -4
- package/dist/core/agent-config-dir.d.ts +20 -0
- package/dist/core/agent-config-dir.js +49 -0
- package/dist/core/agent-definition.d.ts +2 -2
- package/dist/core/allow-entry.d.ts +30 -4
- package/dist/core/allow-entry.js +32 -5
- package/dist/core/ask-origin.d.ts +23 -4
- package/dist/core/ask-origin.js +11 -5
- package/dist/core/checkpoint-store.d.ts +41 -4
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/effective-path-target.d.ts +143 -10
- package/dist/core/effective-path-target.js +113 -9
- package/dist/core/engine-notice.d.ts +25 -3
- package/dist/core/engine-notice.js +12 -6
- package/dist/core/gate-fold.js +3 -0
- package/dist/core/gate-lanes.js +11 -3
- package/dist/core/gate-outcome.d.ts +5 -0
- package/dist/core/gate-outcome.js +2 -0
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +5 -0
- package/dist/core/memory-engine/content-origin.d.ts +0 -34
- package/dist/core/memory-engine/content-origin.js +9 -4
- package/dist/core/memory-engine/layout.d.ts +5 -1
- package/dist/core/memory-engine/layout.js +1 -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 -36
- package/dist/core/permission-rules.js +50 -40
- package/dist/core/real-approval.d.ts +9 -6
- package/dist/core/real-approval.js +5 -5
- package/dist/core/runner/child-tree.d.ts +110 -0
- package/dist/core/runner/child-tree.js +107 -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 +54 -2
- 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 +6 -0
- package/dist/core/runner/prepare-ask-lane.js +4 -2
- package/dist/core/runner/prepare-config-doors.js +3 -1
- package/dist/core/runner/prepare-execution-env.d.ts +5 -2
- package/dist/core/runner/prepare-execution-env.js +11 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +6 -0
- package/dist/core/runner/prepare-gate-stations.js +3 -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-listings.d.ts +4 -4
- package/dist/core/runner/prepare-listings.js +2 -22
- package/dist/core/runner/prepare-park-ask.d.ts +5 -0
- package/dist/core/runner/prepare-park-ask.js +6 -4
- package/dist/core/runner/prepare-policy-chain.d.ts +16 -2
- package/dist/core/runner/prepare-policy-chain.js +14 -5
- 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 +41 -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/resume-policy-outcome.js +2 -1
- package/dist/core/runner/run-leg.js +5 -1
- package/dist/core/runner/run-notification-lane.js +2 -2
- package/dist/core/runner/runtask.d.ts +2 -0
- package/dist/core/runner/runtask.js +4 -2
- package/dist/core/runner/stream-steer-verb.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +32 -0
- package/dist/core/runner/sub-agent-deny-lists.js +121 -0
- package/dist/core/runner/synthetic-tools.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.js +3 -2
- 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/session-policy-store.d.ts +6 -2
- package/dist/core/session-policy-store.js +2 -1
- 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/skill-grant.d.ts +10 -2
- package/dist/core/skill-grant.js +20 -4
- package/dist/core/synthetic-read-verdict.d.ts +3 -0
- package/dist/core/synthetic-read-verdict.js +3 -1
- package/dist/core/task-notification.d.ts +12 -1
- package/dist/core/task-notification.js +13 -1
- package/dist/core/task-spec.d.ts +5 -3
- 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 +45 -12
- package/dist/core/tool-policy.js +18 -20
- package/dist/core/tool-spec.d.ts +23 -4
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/write-protect.d.ts +4 -0
- package/dist/core/write-protect.js +30 -9
- package/dist/index.d.ts +10 -6
- package/dist/index.js +9 -5
- package/dist/orchestration/run-spec.js +8 -7
- package/dist/orchestration/workflow.js +3 -2
- 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 +83 -0
- package/dist/tools/fs/removal-targets.js +236 -0
- 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 -37
- 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 +49 -3
- package/wire-changes/core@7.45.0.json +1084 -0
- package/wire-changes/core@7.46.0.json +1522 -0
|
@@ -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,12 +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).
|
|
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`).
|
|
152
159
|
*/
|
|
153
160
|
pathBases?: PathRuleBases;
|
|
154
161
|
}
|
|
@@ -201,28 +208,7 @@ export interface RuleFaceView {
|
|
|
201
208
|
export declare function catalogRuleFaceView(toolName: string): RuleFaceView | undefined;
|
|
202
209
|
/** Exported for the lockstep guard only (the scalar vocabulary of a catalog tool) — not part of the rule DSL. */
|
|
203
210
|
export declare const fileToolParamVocabulary: (canonicalTool: string) => ReadonlySet<string> | undefined;
|
|
204
|
-
/**
|
|
205
|
-
* The deny-table row of ONE face-keyed path deny rule, resolved against `bases` — the row's reach IS the rule's (the same
|
|
206
|
-
* parsed rule, the same bases). Every source of such rows builds them here: a policy's compiled rules, and the person's
|
|
207
|
-
* persisted store rows. The caller holds the bases the pattern needs (`ruleBasesNeeded`): a row whose base is missing
|
|
208
|
-
* reads `unreadable`, which this row treats as a reach — a deny row that cannot be judged holds.
|
|
209
|
-
*/
|
|
210
|
-
export declare function pathRuleDenyRowOf(rule: {
|
|
211
|
-
readonly parsed: ParsedRule;
|
|
212
|
-
readonly ruleText: string;
|
|
213
|
-
readonly faces: readonly DenyFace[];
|
|
214
|
-
}, bases: PathRuleBases): PathRuleDenyRow;
|
|
215
211
|
/** Dry-run 校验/迁移报告:不 throw,返回全部 issues(含 `unsupported.*` 分类,
|
|
216
212
|
* 供从 CC settings 迁移的部署逐条对照)。 */
|
|
217
213
|
export declare function validatePermissionRules(rules: PermissionRule[], opts?: Pick<PermissionRulePolicyOptions, "primaryFieldGeneric" | "caps" | "ruleFaces" | "pathBases">): PermissionRuleIssue[];
|
|
218
|
-
/**
|
|
219
|
-
* Compile declarative permission rules into ONE {@link ToolPolicy} (design/127). Evaluation order
|
|
220
|
-
* inside the policy (CC `G1e` :595272 isomorph): bare deny → command deny → param deny → bare ask →
|
|
221
|
-
* command ask → param ask → bare allow → `defaultAction` (the content-form command tiers are the
|
|
222
|
-
* field-level tiers of upstream's arbiter, beside the generic param tiers). `check()` is idempotent and side-effect-free (combinePolicies 契约);
|
|
223
|
-
* all parsing/validation happens HERE, the hot path never re-parses.
|
|
224
|
-
*
|
|
225
|
-
* ⚠️ This policy states no allow: a call no rule reaches gets `defaultAction`, whose `"allow"` is "no objection" (states
|
|
226
|
-
* nothing, clears nothing). Express default-closed via `defaultAction`, never via a trailing always-ask policy.
|
|
227
|
-
*/
|
|
228
214
|
export declare function createPermissionRulePolicy(rules: PermissionRule[], opts?: PermissionRulePolicyOptions): NamedToolPolicy;
|
|
@@ -2,10 +2,10 @@ import { MCP_NAMESPACE, protocolOf } from "./protocol-table.js";
|
|
|
2
2
|
import { splitNamespacedName } from "./tool-name-aliases.js";
|
|
3
3
|
import { catalogRuleFaceOf, pathTargetOf } from "./tool-registry.js";
|
|
4
4
|
import { indexOfUnescaped, lastIndexOfUnescaped, parsePermissionRule } from "./permission-rule-syntax.js";
|
|
5
|
-
import { NAME_RULE_FACES, PATH_RULE_BASE_LABEL, PATH_RULE_FACES, callPathFaceOf, isUsablePathBase, nameRuleAdmits, nameRuleSubjectOf, parseRuleText,
|
|
6
|
-
import { pathFamilyOf
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { NAME_RULE_FACES, PATH_RULE_BASE_LABEL, PATH_RULE_FACES, callPathFaceOf, isUsablePathBase, nameRuleAdmits, nameRuleSubjectOf, parseRuleText, programRunReachOf, ruleBasesNeeded, ruleToolGrammarOf } from "./permission-rule-model.js";
|
|
6
|
+
import { pathFamilyOf } from "../tools/fs/safety.js";
|
|
7
|
+
import { callTargetReadingOf, callTargetViewsOf } from "./effective-path-target.js";
|
|
8
|
+
import { bindPathRules, pathRowsReach, policyAnchors, rowsWhereTheyLead } from "./path-rule-binding.js";
|
|
9
9
|
import { ruleDenialSentence } from "./rule-denial-sentence.js";
|
|
10
10
|
import { heldValues } from "./tool-name-readings.js";
|
|
11
11
|
export { parsePermissionRule };
|
|
@@ -93,22 +93,6 @@ export const fileToolParamVocabulary = (canonicalTool) => {
|
|
|
93
93
|
const face = catalogRuleFaceOf(canonicalTool);
|
|
94
94
|
return face?.params === undefined || pathTargetOf(canonicalTool) === undefined ? undefined : new Set(face.params);
|
|
95
95
|
};
|
|
96
|
-
export function pathRuleDenyRowOf(rule, bases) {
|
|
97
|
-
const family = pathFamilyOf(bases);
|
|
98
|
-
const tree = bases.root ?? bases.cwd;
|
|
99
|
-
const head = pathRuleHeadOf(rule.parsed, bases);
|
|
100
|
-
return {
|
|
101
|
-
rule: rule.ruleText,
|
|
102
|
-
tool: rule.parsed.tool,
|
|
103
|
-
pattern: parsePermissionRule(rule.ruleText).ruleContent ?? "",
|
|
104
|
-
faces: rule.faces,
|
|
105
|
-
reaches: (absolutePath) => {
|
|
106
|
-
const id = pathIdentityOf(absolutePath, family, tree);
|
|
107
|
-
return id.ok && pathRuleReachOf(rule.parsed, id.identity, bases).reach !== "not_reached";
|
|
108
|
-
},
|
|
109
|
-
...(head !== undefined ? { head: spellingOfIdentity(head, family) } : {}),
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
96
|
function countStars(s) {
|
|
113
97
|
let n = 0;
|
|
114
98
|
for (const c of s)
|
|
@@ -233,9 +217,9 @@ function compile(rules, caps, primaryFieldGeneric, ruleFaces, pathBases = {}) {
|
|
|
233
217
|
continue;
|
|
234
218
|
}
|
|
235
219
|
for (const base of ruleBasesNeeded(parsedPath.rule)) {
|
|
236
|
-
const
|
|
237
|
-
if (!isUsablePathBase(
|
|
238
|
-
bad(text, "unsupported.path_base", `"${text}" is relative to ${PATH_RULE_BASE_LABEL[base]}, and
|
|
220
|
+
const declared = base === "cwd" ? (pathBases.cwd ?? pathBases.root) : pathBases[base];
|
|
221
|
+
if (declared !== undefined && !isUsablePathBase(declared, pathFamilyOf(pathBases))) {
|
|
222
|
+
bad(text, "unsupported.path_base", `"${text}" is relative to ${PATH_RULE_BASE_LABEL[base]}, and the \`pathBases.${base === "cwd" && pathBases.cwd === undefined ? "root" : base}\` this policy declares is not an absolute path of its tree's family — the rule could not be judged on any call`);
|
|
239
223
|
}
|
|
240
224
|
}
|
|
241
225
|
if (issues.length > 0 && issues[issues.length - 1].rule === text && issues[issues.length - 1].code === "unsupported.path_base")
|
|
@@ -315,6 +299,7 @@ function ruleDenial(req, ruleText, source) {
|
|
|
315
299
|
function ruleAskMessage(ruleText, source) {
|
|
316
300
|
return `flagged by permission rule "${ruleText}"${source ? ` (${source})` : ""}`;
|
|
317
301
|
}
|
|
302
|
+
const NO_LEADS = new Map();
|
|
318
303
|
export function createPermissionRulePolicy(rules, opts) {
|
|
319
304
|
const caps = { ...DEFAULT_CAPS, ...opts?.caps };
|
|
320
305
|
const pathBases = opts?.pathBases ?? {};
|
|
@@ -360,16 +345,55 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
360
345
|
deny: [...byTool.values()].flatMap((e) => e.content.deny.filter((r) => r.faces !== undefined)),
|
|
361
346
|
ask: [...byTool.values()].flatMap((e) => e.content.ask.filter((r) => r.faces !== undefined)),
|
|
362
347
|
};
|
|
363
|
-
const pathDenyRules = faceRules.deny.map((r) => pathRuleDenyRowOf({ parsed: r.parsed, ruleText: r.ruleText, faces: r.faces }, pathBases));
|
|
364
348
|
const contentLaneOf = (behavior, entry, callFace) => [
|
|
365
349
|
...(entry?.content[behavior].filter((r) => r.faces === undefined) ?? []),
|
|
366
350
|
...(callFace !== undefined ? faceRules[behavior].filter((r) => r.faces.includes(callFace)) : []),
|
|
367
351
|
];
|
|
368
352
|
const commandParam = catalogRuleFaceOf("Bash")?.primaryParams[0] ?? "command";
|
|
353
|
+
const pathRuleOf = new Map();
|
|
354
|
+
for (const e of byTool.values()) {
|
|
355
|
+
for (const behavior of ["deny", "ask"]) {
|
|
356
|
+
for (const r of e.content[behavior]) {
|
|
357
|
+
if (r.parsed.match !== "path" && r.parsed.match !== "subpath")
|
|
358
|
+
continue;
|
|
359
|
+
pathRuleOf.set(r, { parsed: r.parsed, ruleText: r.ruleText, ...(behavior === "deny" && r.faces !== undefined ? { faces: r.faces } : {}) });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const pathSpecs = [...pathRuleOf.values()];
|
|
364
|
+
const bound = new Map();
|
|
365
|
+
const boundAt = (anchors) => {
|
|
366
|
+
const key = JSON.stringify([anchors.root ?? null, anchors.cwd ?? null, anchors.home ?? null]);
|
|
367
|
+
const prior = bound.get(key);
|
|
368
|
+
if (prior !== undefined)
|
|
369
|
+
return prior;
|
|
370
|
+
const rows = bindPathRules(pathSpecs, anchors);
|
|
371
|
+
const of = new Map();
|
|
372
|
+
[...pathRuleOf.keys()].forEach((r, i) => of.set(r, rows[i]));
|
|
373
|
+
const out = { rows, of };
|
|
374
|
+
bound.set(key, out);
|
|
375
|
+
return out;
|
|
376
|
+
};
|
|
369
377
|
const contentHit = (lane, req) => {
|
|
370
378
|
if (lane.length === 0)
|
|
371
379
|
return undefined;
|
|
372
|
-
const
|
|
380
|
+
const pathLane = lane.filter((r) => pathRuleOf.has(r));
|
|
381
|
+
if (pathLane.length > 0) {
|
|
382
|
+
const views = callTargetViewsOf(req, "tighten", { ...(pathBases.root !== undefined ? { root: pathBases.root } : {}), ...(pathBases.cwd !== undefined ? { cwd: pathBases.cwd } : {}) });
|
|
383
|
+
if (views !== undefined) {
|
|
384
|
+
const { of } = boundAt(policyAnchors(pathBases, req.pathRuleAnchors));
|
|
385
|
+
const rows = rowsWhereTheyLead(pathLane.map((r) => of.get(r)), callTargetReadingOf(req)?.leads ?? NO_LEADS);
|
|
386
|
+
const hit = pathRowsReach(rows, views, "tighten");
|
|
387
|
+
const rule = hit === undefined ? undefined : pathLane.find((r) => of.get(r).spec === hit.row.spec);
|
|
388
|
+
if (hit !== undefined && hit.unreadable === undefined)
|
|
389
|
+
return { rule: rule };
|
|
390
|
+
if (hit !== undefined)
|
|
391
|
+
return judgeOthers(lane.filter((r) => !pathRuleOf.has(r)), req) ?? { rule: rule, unreadable: hit.unreadable };
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return judgeOthers(lane.filter((r) => !pathRuleOf.has(r)), req);
|
|
395
|
+
};
|
|
396
|
+
const judgeOthers = (lane, req) => {
|
|
373
397
|
const command = isPlainObjectArgs(req.args) ? req.args[commandParam] : undefined;
|
|
374
398
|
let unreadable;
|
|
375
399
|
for (const r of lane) {
|
|
@@ -379,20 +403,6 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
379
403
|
return { rule: r };
|
|
380
404
|
continue;
|
|
381
405
|
}
|
|
382
|
-
if (r.parsed.match === "subpath" || r.parsed.match === "path") {
|
|
383
|
-
if (target === undefined || target.kind === "refused")
|
|
384
|
-
continue;
|
|
385
|
-
if (target.kind === "unreadable") {
|
|
386
|
-
unreadable ??= { rule: r, unreadable: target.reason };
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
const outcome = pathRuleReachOf(r.parsed, target.identity, pathBases);
|
|
390
|
-
if (outcome.reach === "reached")
|
|
391
|
-
return { rule: r };
|
|
392
|
-
if (outcome.reach === "unreadable")
|
|
393
|
-
unreadable ??= { rule: r, unreadable: outcome.reason };
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
406
|
if (typeof command !== "string")
|
|
397
407
|
continue;
|
|
398
408
|
const outcome = programRunReachOf(r.parsed, command);
|
|
@@ -427,7 +437,7 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
427
437
|
};
|
|
428
438
|
return {
|
|
429
439
|
nameSets: [nameSets],
|
|
430
|
-
...(
|
|
440
|
+
...(pathSpecs.length > 0 ? { pathRules: (anchors) => boundAt(policyAnchors(pathBases, anchors)).rows } : {}),
|
|
431
441
|
check(req) {
|
|
432
442
|
const entry = entryFor(req);
|
|
433
443
|
const covering = hasCovering && protocolOf(req.toolName) !== undefined;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* may still refuse it.
|
|
7
7
|
*/
|
|
8
8
|
import type { Checkpoint } from "./checkpoint-store.js";
|
|
9
|
+
import type { HostDecider } from "./gate-outcome.js";
|
|
9
10
|
import type { OrgGateVerdict } from "./hooks.js";
|
|
10
11
|
import type { AskRequest, PermissionResult, ResolvedAsk } from "./tool-policy.js";
|
|
11
12
|
/** The synchronous seat's refusal of a configuration's allow: one settlement word for both forms
|
|
@@ -16,10 +17,12 @@ export declare function unjudgedAllowRefused(req: AskRequest, by: "blanket" | "p
|
|
|
16
17
|
/** A durable row demands a person when its gate carries the real-approval bit (the gate's own, which every resume
|
|
17
18
|
* check reads; the pending action's `requiresRealApproval` is its display twin). */
|
|
18
19
|
export declare function rowDemandsAPerson(cp: Checkpoint): boolean;
|
|
19
|
-
/** A question a
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
/** A question a decide is held against, in each form a station holds one: the parked row, a policy's re-check of an
|
|
21
|
+
* approver's edit, the organization's CURRENT verdict about the call, and the request an approver was handed (the card a
|
|
22
|
+
* live door answers). */
|
|
23
|
+
export type HeldQuestion = Checkpoint | PermissionResult | OrgGateVerdict | AskRequest;
|
|
24
|
+
/** Whether a decide made by `decidedBy` cannot answer the held question: a question demanding a person is not
|
|
23
25
|
* the policy's to answer. One predicate for every station that holds a decide against such a question — the resume
|
|
24
|
-
* entry (the row, an edit's re-check, the org's current verdict)
|
|
25
|
-
|
|
26
|
+
* entry (the row, an edit's re-check, the org's current verdict), the execution belt (the last two, re-read) and a live
|
|
27
|
+
* door answering the request an approver was handed. */
|
|
28
|
+
export declare function policyCannotAnswer(question: HeldQuestion, decidedBy: HostDecider): boolean;
|
|
@@ -16,11 +16,11 @@ export function rowDemandsAPerson(cp) {
|
|
|
16
16
|
return cp.gate.kind === "irreversible_ask" && cp.gate.realApproval !== undefined;
|
|
17
17
|
}
|
|
18
18
|
function demandsAPerson(question) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return question
|
|
19
|
+
const gate = Reflect.get(question, "gate");
|
|
20
|
+
const verdict = Reflect.get(question, "verdict");
|
|
21
|
+
const row = typeof gate === "object" && gate !== null && rowDemandsAPerson(question);
|
|
22
|
+
const org = Reflect.get(question, "status") === "available" && typeof verdict === "object" && verdict !== null && Reflect.get(verdict, "behavior") === "ask";
|
|
23
|
+
return row || Reflect.get(question, "requiresRealApproval") === true || org;
|
|
24
24
|
}
|
|
25
25
|
export function policyCannotAnswer(question, decidedBy) {
|
|
26
26
|
return decidedBy === "policy" && demandsAPerson(question);
|