@sema-agent/core 7.33.8 → 7.34.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 +339 -0
- package/dist/agents/cross-session-judge.d.ts +16 -7
- package/dist/agents/cross-session-judge.js +18 -15
- package/dist/agents/peer-session-drain.d.ts +4 -0
- package/dist/agents/peer-session-drain.js +5 -2
- package/dist/agents/repair-loop.d.ts +1 -1
- package/dist/agents/subagent.d.ts +3 -0
- package/dist/agents/subagent.js +12 -3
- package/dist/brain/anthropic.js +1 -0
- package/dist/brain/open-responses.js +1 -0
- package/dist/brain/openai.js +1 -0
- package/dist/brain/stream-engine.d.ts +3 -0
- package/dist/brain/stream-engine.js +2 -1
- package/dist/core/a2a.d.ts +3 -1
- package/dist/core/a2a.js +1 -1
- package/dist/core/ask-origin.d.ts +351 -154
- package/dist/core/ask-origin.js +223 -45
- package/dist/core/ask-unresolvable-notice.d.ts +5 -2
- package/dist/core/ask-unresolvable-notice.js +4 -3
- package/dist/core/auto-mode-arming.d.ts +17 -4
- package/dist/core/auto-mode-arming.js +31 -6
- package/dist/core/auto-mode-defaults.d.ts +16 -7
- package/dist/core/auto-mode-defaults.js +2 -1
- package/dist/core/auto-mode-directive.d.ts +18 -30
- package/dist/core/auto-mode-directive.js +9 -18
- package/dist/core/auto-mode-prompt.d.ts +62 -1
- package/dist/core/auto-mode-prompt.js +42 -4
- package/dist/core/auto-mode-rebuild.d.ts +2 -1
- package/dist/core/auto-mode-rebuild.js +1 -0
- package/dist/core/auto-mode.d.ts +32 -3
- package/dist/core/auto-mode.js +80 -41
- package/dist/core/checkpoint-store.d.ts +50 -35
- package/dist/core/checkpoint-store.js +58 -3
- package/dist/core/child-clamp-seats.js +1 -0
- package/dist/core/classifier-unavailable-arm.d.ts +57 -0
- package/dist/core/classifier-unavailable-arm.js +17 -0
- package/dist/core/effective-path-target.d.ts +12 -0
- package/dist/core/effective-path-target.js +18 -7
- package/dist/core/engine-notice.d.ts +53 -2
- package/dist/core/fs-write-gate-policy.d.ts +5 -1
- package/dist/core/fs-write-gate-policy.js +4 -1
- package/dist/core/gate-fold.js +17 -1
- package/dist/core/gate-lanes.js +111 -46
- package/dist/core/gate-outcome.d.ts +40 -7
- package/dist/core/gate-outcome.js +26 -4
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +12 -0
- package/dist/core/hooks.d.ts +50 -29
- package/dist/core/hooks.js +1 -3
- package/dist/core/mcp.js +1 -1
- package/dist/core/mode-policy.d.ts +182 -0
- package/dist/core/mode-policy.js +179 -0
- package/dist/core/oracle-isolation.d.ts +3 -3
- package/dist/core/permission-rule-model.d.ts +16 -3
- package/dist/core/permission-rule-model.js +36 -3
- package/dist/core/permission-rule-provider.d.ts +22 -11
- package/dist/core/permission-rule-provider.js +17 -7
- package/dist/core/permission-rules.d.ts +23 -1
- package/dist/core/permission-rules.js +35 -8
- package/dist/core/persisted-rule-arms.d.ts +10 -9
- package/dist/core/persisted-rule-arms.js +9 -6
- package/dist/core/present-plan-tool.d.ts +1 -1
- package/dist/core/present-plan-tool.js +3 -3
- package/dist/core/read-only-shell.d.ts +19 -19
- package/dist/core/read-only-shell.js +38 -12
- package/dist/core/retired-keys.d.ts +9 -3
- package/dist/core/retired-keys.js +5 -1
- package/dist/core/runner/classifier-ladder.d.ts +43 -1
- package/dist/core/runner/classifier-ladder.js +29 -0
- package/dist/core/runner/contracts.d.ts +37 -35
- package/dist/core/runner/denial-limit-arms.d.ts +30 -7
- package/dist/core/runner/denial-limit-arms.js +15 -9
- package/dist/core/runner/env-capability-census.js +1 -1
- package/dist/core/runner/gate-exit.d.ts +30 -4
- package/dist/core/runner/gate-exit.js +28 -7
- package/dist/core/runner/permission-rule-lanes.d.ts +9 -5
- package/dist/core/runner/permission-rule-lanes.js +44 -40
- package/dist/core/runner/persisted-path-rows.d.ts +64 -0
- package/dist/core/runner/persisted-path-rows.js +89 -0
- package/dist/core/runner/prepare-artifact.d.ts +5 -1
- package/dist/core/runner/prepare-artifact.js +9 -3
- package/dist/core/runner/prepare-ask-lane.d.ts +5 -3
- package/dist/core/runner/prepare-ask-lane.js +1 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
- package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
- package/dist/core/runner/prepare-gate-stations.d.ts +33 -6
- package/dist/core/runner/prepare-gate-stations.js +32 -19
- package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
- package/dist/core/runner/prepare-hands-readface.js +64 -53
- package/dist/core/runner/prepare-inherited-gate.d.ts +22 -22
- package/dist/core/runner/prepare-inherited-gate.js +37 -23
- package/dist/core/runner/prepare-park-ask.d.ts +2 -2
- package/dist/core/runner/prepare-park-ask.js +2 -1
- package/dist/core/runner/prepare-policy-chain.d.ts +22 -2
- package/dist/core/runner/prepare-policy-chain.js +39 -12
- package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
- package/dist/core/runner/prepare-protocol-tools.js +13 -3
- package/dist/core/runner/prepare-run-refs.d.ts +6 -6
- package/dist/core/runner/prepare-run-refs.js +9 -4
- package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
- package/dist/core/runner/prepare-safety-scan.js +19 -3
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
- package/dist/core/runner/prepare-suspend-saga.js +6 -6
- package/dist/core/runner/prepare-task.js +51 -22
- package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
- package/dist/core/runner/prepare-wiring-manifest.js +5 -3
- package/dist/core/runner/read-root-grant.d.ts +9 -0
- package/dist/core/runner/read-root-grant.js +12 -0
- package/dist/core/runner/retired-runner-deps.js +1 -0
- package/dist/core/runner/run-identity-wiring.js +1 -1
- package/dist/core/runner/run-leg.js +24 -15
- package/dist/core/runner/run-notification-lane.js +1 -0
- package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
- package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
- package/dist/core/runner/run-turn-boundary.js +1 -1
- package/dist/core/runner/runtask.js +3 -0
- package/dist/core/runner/stream-mode-verb.d.ts +27 -0
- package/dist/core/runner/stream-mode-verb.js +19 -0
- package/dist/core/runner/turn-attachments.d.ts +3 -3
- package/dist/core/runner/turn-attachments.js +1 -1
- package/dist/core/runner/write-protection-station.d.ts +7 -1
- package/dist/core/runner/write-protection-station.js +6 -3
- package/dist/core/runner-deps.d.ts +31 -34
- package/dist/core/runtime-caps.d.ts +12 -3
- package/dist/core/safety-axis-vocab.d.ts +2 -2
- package/dist/core/safety-axis-vocab.js +1 -1
- package/dist/core/sensitive-path-policy.d.ts +4 -2
- package/dist/core/sensitive-path-policy.js +39 -30
- package/dist/core/task-event.d.ts +1 -1
- package/dist/core/task-result.d.ts +1 -1
- package/dist/core/task-spec.d.ts +41 -89
- package/dist/core/task-stream.d.ts +16 -0
- package/dist/core/terminal-cause.js +1 -0
- package/dist/core/tighten-task-spec.d.ts +2 -2
- package/dist/core/tighten-task-spec.js +12 -14
- package/dist/core/tool-catalog-entries.js +4 -3
- package/dist/core/tool-policy.d.ts +54 -25
- package/dist/core/tool-policy.js +43 -2
- package/dist/core/tool-registry.d.ts +2 -0
- package/dist/core/tool-registry.js +3 -0
- package/dist/core/tool-spec.d.ts +8 -9
- package/dist/core/trace.d.ts +7 -6
- package/dist/core/types.d.ts +2 -1
- package/dist/core/wiring-manifest.d.ts +31 -5
- package/dist/core/wiring-manifest.js +28 -1
- package/dist/core/write-protect.d.ts +11 -1
- package/dist/core/write-protect.js +26 -15
- package/dist/engine/execution-env/node-execution-env.d.ts +3 -0
- package/dist/engine/execution-env/node-execution-env.js +11 -1
- package/dist/engine/harness/types.d.ts +6 -7
- package/dist/engine/llm/types.d.ts +2 -4
- package/dist/engine/loop/types.d.ts +7 -0
- package/dist/index.d.ts +15 -5
- package/dist/index.js +12 -3
- package/dist/orchestration/workflow-governance.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-size-guideline.js +2 -2
- package/dist/tools/artifact/artifact-tool.d.ts +17 -0
- package/dist/tools/artifact/artifact-tool.js +16 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
- package/dist/tools/fs/bash-readonly-classifier.js +88 -0
- package/dist/tools/fs/containment-face.d.ts +9 -3
- package/dist/tools/fs/containment-face.js +2 -0
- package/dist/tools/fs/file-read-check.d.ts +49 -0
- package/dist/tools/fs/file-read-check.js +71 -0
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-read.d.ts +1 -0
- package/dist/tools/fs/fs-search-tools.d.ts +2 -2
- package/dist/tools/fs/fs-search-tools.js +4 -4
- package/dist/tools/fs/fs-shared.js +20 -6
- package/dist/tools/fs/fs-write.d.ts +4 -3
- package/dist/tools/fs/fs-write.js +9 -9
- package/dist/tools/fs/index.d.ts +39 -3
- package/dist/tools/fs/index.js +38 -15
- package/dist/tools/fs/read-deny-home.d.ts +93 -0
- package/dist/tools/fs/read-deny-home.js +225 -0
- package/dist/tools/fs/read-deny.d.ts +176 -34
- package/dist/tools/fs/read-deny.js +268 -61
- package/dist/tools/fs/safety.d.ts +6 -1
- package/dist/tools/fs/safety.js +18 -5
- package/dist/tools/fs/search.d.ts +3 -0
- package/dist/tools/fs/search.js +47 -9
- package/dist/tools/fs/shell-read-check.d.ts +42 -11
- package/dist/tools/fs/shell-read-check.js +188 -13
- package/dist/tools/fs/shell-read-establish.d.ts +7 -3
- package/dist/tools/fs/shell-read-establish.js +134 -21
- package/dist/tools/fs/shell-read-identity.d.ts +7 -0
- package/dist/tools/fs/shell-read-identity.js +21 -0
- package/dist/tools/fs/shell-read-judge.d.ts +92 -32
- package/dist/tools/fs/shell-read-judge.js +43 -18
- package/dist/tools/fs/shell-read-plan.d.ts +4 -0
- package/dist/tools/fs/shell-read-plan.js +26 -4
- package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
- package/dist/tools/fs/shell-read-write-targets.js +173 -0
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +103 -11
- package/wire-changes/core@7.34.0.json +2621 -0
package/dist/core/task-spec.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* beside it in `task-limits.ts`; the terminal record it produces in `task-result.ts`. Layer 0
|
|
5
5
|
* vocabulary; `types.ts` re-exports the three, so no consumer's import changes.
|
|
6
6
|
*/
|
|
7
|
+
import type { PermissionMode } from "./ask-origin.js";
|
|
7
8
|
import type { TSchema } from "typebox";
|
|
8
9
|
import type { ThinkingLevel } from "../internal/harness.js";
|
|
9
10
|
import type { ActorAssertion, Model, ResilienceOptions } from "../internal/llm.js";
|
|
@@ -31,15 +32,12 @@ export type ImageInput = {
|
|
|
31
32
|
url: string;
|
|
32
33
|
};
|
|
33
34
|
/** A single self-contained task with its full configuration. */
|
|
34
|
-
/** The shell-gate
|
|
35
|
-
* shell-gated ask (`RiskDescriptor.shellGateDoctrine`) and
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* today (`"bypass"` — the host answers every card itself, automatically). The engine only RENDERS it,
|
|
41
|
-
* so adding a word here changes what the model is told and nothing else. */
|
|
42
|
-
export type ApproverPosture = "bypass";
|
|
35
|
+
/** The shell-gate FLOOR vocabulary — {@link TaskSpec.shellGate}'s value set, the word the engine persists on a
|
|
36
|
+
* shell-gated ask (`RiskDescriptor.shellGateDoctrine`) and carries down a delegation chain. One value: `"always"`
|
|
37
|
+
* (every shell command is confirmed, under every permission mode — an operator's mandate). The shape tier itself
|
|
38
|
+
* is installed on every full shell regardless; what a permission mode does with the resulting ask is the gate's
|
|
39
|
+
* arbitration, never a doctrine word. */
|
|
40
|
+
export type ShellGateDoctrine = "always";
|
|
43
41
|
export interface TaskSpec {
|
|
44
42
|
taskId?: string;
|
|
45
43
|
/** The instruction / latest user message for this turn. */
|
|
@@ -791,8 +789,8 @@ export interface TaskSpec {
|
|
|
791
789
|
/**
|
|
792
790
|
* Interactive read-boundary whitelist (ruled 2026-08-05, both-arms ruling, arm A): extra directories
|
|
793
791
|
* the READ faces may access — and nothing else. Joins the containment allowlist of the read-only
|
|
794
|
-
* tools (read_file / grep / glob / repo_map / `bash_readonly`) and the
|
|
795
|
-
*
|
|
792
|
+
* tools (read_file / grep / glob / repo_map / `bash_readonly`) and the shell read station's
|
|
793
|
+
* boundary (so a provably read-only command like `head <dir>/settings.json` auto-allows instead
|
|
796
794
|
* of asking on every call), but is NEVER admitted by the write faces (edit_file / write_file /
|
|
797
795
|
* notebook_edit refuse these paths exactly as before). Use `additionalDirectories` when the task
|
|
798
796
|
* should also write there. Same canonicalization and fail-closed skip rules as
|
|
@@ -805,7 +803,7 @@ export interface TaskSpec {
|
|
|
805
803
|
* design/199 件B — TASK-layer ADDITIONS to the built-in sensitive-path READ deny set
|
|
806
804
|
* ({@link import("../tools/fs/read-deny.js").READ_FACE_DEFAULT_DENY_ENTRIES}). Judged by the
|
|
807
805
|
* structured read faces (Read/Grep/Glob/RepoMap, their traversals, the compaction attachment reader) and
|
|
808
|
-
* by the shell READ STATION under every
|
|
806
|
+
* by the shell READ STATION under every mode — the full shell (`Bash`, `Monitor`, whatever
|
|
809
807
|
* {@link shellGate} says) and the `bash_readonly` shell mounted by {@link handsReadOnly} alike: a
|
|
810
808
|
* deny-listed shell read is REFUSED at the gate (`deniedBy: "read_boundary"`), never asked, with the same
|
|
811
809
|
* sentence the structured faces refuse with — one deny judge for every read face — in BOTH containment
|
|
@@ -814,8 +812,8 @@ export interface TaskSpec {
|
|
|
814
812
|
* {@link RunnerDeps.readDenyPatterns}; `[]` ≡ absent (union identity); NOTHING at the task layer
|
|
815
813
|
* can remove a built-in — the one removal channel is the DEPLOYMENT's built-in configuration
|
|
816
814
|
* ({@link RunnerDeps.readDenyBuiltinTiers} / {@link RunnerDeps.readDenyBuiltinExclude}, #245
|
|
817
|
-
* revision of D-4), which no TaskSpec key reaches
|
|
818
|
-
*
|
|
815
|
+
* revision of D-4), which no TaskSpec key reaches (an unconfigured deployment judges the default tiers,
|
|
816
|
+
* {@link import("../tools/fs/read-deny.js").READ_DENY_DEFAULT_TIERS}). Bad entry shapes refuse loudly at prepare
|
|
819
817
|
* (#123). The write faces are untouched.
|
|
820
818
|
*/
|
|
821
819
|
readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
|
|
@@ -890,87 +888,41 @@ export interface TaskSpec {
|
|
|
890
888
|
*/
|
|
891
889
|
enableFork?: boolean;
|
|
892
890
|
/**
|
|
893
|
-
*
|
|
894
|
-
* `
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
898
|
-
*
|
|
899
|
-
*
|
|
900
|
-
*
|
|
901
|
-
*
|
|
902
|
-
* reads is not this doctrine's question under any value: the gate's shell read station judges every
|
|
903
|
-
* shell call's read candidates against the deny set and the containment roots (a deny-listed read is
|
|
904
|
-
* refused, an out-of-root read is a mandated ask) whether `shellGate` is `"off"`, `"classify"` or
|
|
905
|
-
* `"always"`. An egress command (e.g. `curl`) suspends under
|
|
906
|
-
* the IRREVERSIBLE axis (the shell mark is irreversibility, not egress) — the kind is non-budgetable either
|
|
907
|
-
* way, so the budget resolver still never auto-approves it; the axis label is informational.
|
|
908
|
-
* NOT unconditional: a POSITIVE per-tool mark on the shell tool keeps its seat — an explicit
|
|
909
|
-
* `irreversibility:"always"` is never downgraded (the tier combine is tighten-only) and an explicit
|
|
910
|
-
* `"maybe"` keeps its own probe (a probe-less explicit `"maybe"` stays fail-closed ask; the doctrine's
|
|
911
|
-
* generic probe installs only on doctrine-owned seats). An explicit `"never"` is NOT a mark — the
|
|
912
|
-
* doctrine governs that seat as if unmarked.
|
|
913
|
-
* No effect under `handsReadOnly` (that mounts `bash_readonly`, already allowlisted) or with no execution env.
|
|
914
|
-
* **Re-supply on resume:** like `toolPolicy`/`tools`/`durableApproval`, `shellGate` is part of the resume
|
|
915
|
-
* task config — a resume that omits it leaves the resumed run's SUBSEQUENT `bash` calls ungated (the approved
|
|
916
|
-
* pending call itself runs once, having been adjudicated). Re-pass the same value the original task used.
|
|
891
|
+
* The OPERATOR's shell floor: `"always"` ⇒ every `bash` / `Monitor` command is a confirmed ask (an
|
|
892
|
+
* `operator_forced` ask — kept under `bypassPermissions`, not settled by `dontAsk`; a standing allow rule never
|
|
893
|
+
* clears it). Absent ⇒ no floor: the shape tier the engine installs on every full shell (a provably read-only
|
|
894
|
+
* single command runs, everything else asks) is arbitrated by the task's {@link permissionMode}. The retired
|
|
895
|
+
* values `"off"` / `"classify"` are refused at the door (`config.shell_gate_value_retired`): the tier is no longer a
|
|
896
|
+
* doctrine a caller chooses, and the read boundary is judged on every shell call under every mode. A positive
|
|
897
|
+
* per-tool mark keeps its seat (an explicit `irreversibility:"always"` is never downgraded). No effect under
|
|
898
|
+
* `handsReadOnly` or with no execution env. Re-supplied on resume like `toolPolicy` (a checkpoint carries the
|
|
899
|
+
* chain's floor). Delegation: the floor travels the inherited-gate lane; a child cannot lift it.
|
|
917
900
|
*/
|
|
918
901
|
shellGate?: ShellGateDoctrine;
|
|
919
902
|
/**
|
|
920
|
-
* The
|
|
921
|
-
*
|
|
922
|
-
*
|
|
923
|
-
*
|
|
924
|
-
*
|
|
925
|
-
*
|
|
926
|
-
* classifier RULES still enter ONLY through that face, never through this seat), and
|
|
927
|
-
* - {@link RuntimeCaps.autoMode} is not `false` (the per-principal DENY bit; ABSENT is NOT a denial).
|
|
903
|
+
* The task's PERMISSION MODE — the ONE key that says how the engine answers a surviving ask (six words,
|
|
904
|
+
* {@link import("./ask-origin.js").PERMISSION_MODES}; spelled as the wire spells it). ROOT task absent ⇒
|
|
905
|
+
* `"default"`; an engine-spawned CHILD absent ⇒ its parent's effective mode (a child's own declaration is
|
|
906
|
+
* adopted only where the reference implementation's rank table admits it under the parent's — a folded
|
|
907
|
+
* declaration is disclosed on `wiring_manifest.permissionMode.foldedBy: "inherited_ceiling"` and announced,
|
|
908
|
+
* never silent). A value outside the set is refused at the door (`config.permission_mode_invalid`).
|
|
928
909
|
*
|
|
929
|
-
*
|
|
930
|
-
*
|
|
931
|
-
*
|
|
932
|
-
*
|
|
933
|
-
*
|
|
934
|
-
*
|
|
935
|
-
*
|
|
910
|
+
* What the mode DOES is one table and one predicate ({@link import("./ask-origin.js").MODE_ARBITRATION},
|
|
911
|
+
* {@link import("./ask-origin.js").arbitrateByMode}) read after every rule, tighten and classifier station:
|
|
912
|
+
* `bypassPermissions` releases every ask but the immune classes (an explicit ask rule, a hook's ask, the
|
|
913
|
+
* question tool, an unverifiable recursive delete, an org rule, an operator's forced confirmation, unreadable
|
|
914
|
+
* governance, the classifier's denial-limit fallback) and discloses each release; `dontAsk` refuses every ask
|
|
915
|
+
* but a hook's or an operator authority's; `auto` arms the classifier (`RunnerDeps.autoMode` present and
|
|
916
|
+
* `RuntimeCaps.autoMode` not `false`, else folded to `default` and announced); `plan` reads only and mounts the
|
|
917
|
+
* plan pair; `acceptEdits` writes inside the root without asking; `default` asks. The mode also installs its own
|
|
918
|
+
* seats ({@link import("./mode-policy.js").modePolicyOf}).
|
|
936
919
|
*
|
|
937
|
-
*
|
|
938
|
-
*
|
|
939
|
-
* (`
|
|
940
|
-
*
|
|
941
|
-
* suspend leg resolved (this seat, or the bit its chain carried) is recorded on the checkpoint's
|
|
942
|
-
* data half (`CheckpointState.inheritedGate.autoModeRequested`) and read back as one more intent
|
|
943
|
-
* source, so a redemption in another process arms as the suspend leg did without re-passing the
|
|
944
|
-
* seat; re-passing it is still honoured (the sources fold by OR). Intent only — the resuming
|
|
945
|
-
* deployment's face and the resuming principal's deny bit are judged afresh on every leg.
|
|
946
|
-
*/
|
|
947
|
-
autoModeRequested?: true;
|
|
948
|
-
/**
|
|
949
|
-
* The host's declared APPROVER POSTURE for this task — a typed fact seat, the same ownership split
|
|
950
|
-
* as {@link shellGate} and {@link autoModeRequested}: the service layer declares it (it is the layer
|
|
951
|
-
* that knows who sits at the cards), core only CONSUMES it. `"bypass"` says the host answers every
|
|
952
|
-
* approval card itself, automatically.
|
|
953
|
-
*
|
|
954
|
-
* **The gate does not read it.** Cards are raised, routed and recorded byte-identically with and
|
|
955
|
-
* without this seat — no rule is relaxed, no ask is skipped, no decision is pre-empted. The seat's
|
|
956
|
-
* one effect is that the ENGINE tells the model which posture the run is under, in its own marked
|
|
957
|
-
* `<system-reminder>` on the first turn. That is the whole point of the seat: a posture is a fact of
|
|
958
|
-
* the run, and a host that instead splices the sentence into the objective hands the model
|
|
959
|
-
* reminder-shaped text with no engine mark, which the mark declaration tells it to treat as data and
|
|
960
|
-
* flag. One fact, one seat, one mint.
|
|
961
|
-
*
|
|
962
|
-
* Absent ⇒ no posture declared, zero bytes rendered (byte-identical to the pre-seat shape). Only the
|
|
963
|
-
* literal `"bypass"` is a value: any other present value is refused at the door
|
|
964
|
-
* (`config.approver_posture_invalid`) rather than folded back to absence — a marshalled typo must not
|
|
965
|
-
* silently un-declare a posture the host meant to declare.
|
|
966
|
-
*
|
|
967
|
-
* **Inheritance / resume:** session-wide, exactly like {@link autoModeRequested} — an engine-spawned
|
|
968
|
-
* child inherits it through the trusted constraint chain (`InheritedGate.approverPosture`), and the
|
|
969
|
-
* resolved value is recorded on the checkpoint's data half so a redemption in another process still
|
|
970
|
-
* knows the posture without the seat being re-passed. Re-passing it is honoured; the sources fold
|
|
971
|
-
* seat-first.
|
|
920
|
+
* `bypassPermissions` is a REQUEST: it takes effect only where the deployment does not deny it
|
|
921
|
+
* ({@link RuntimeCaps.bypassPermissions} not `false`); a denied request runs in `default`, announced
|
|
922
|
+
* (`config.permission_mode_unavailable`). A running leg changes mode through `TaskStream.setPermissionMode`.
|
|
923
|
+
* Persists on the checkpoint's data half (`CheckpointState.inheritedGate.permissionMode`), re-judged on every leg.
|
|
972
924
|
*/
|
|
973
|
-
|
|
925
|
+
permissionMode?: PermissionMode;
|
|
974
926
|
/** Task-scoped MCP servers, materialized into tools then disposed. */
|
|
975
927
|
mcp?: McpServerSpec[];
|
|
976
928
|
/** Task-scoped A2A peers (remote agents), whose advertised skills mount as `a2a__<peer>__<skill>`
|
|
@@ -275,6 +275,22 @@ export interface TaskStream extends AsyncIterable<TaskEvent> {
|
|
|
275
275
|
}): Promise<{
|
|
276
276
|
outcome: "created" | "existed";
|
|
277
277
|
}>;
|
|
278
|
+
/**
|
|
279
|
+
* Change the running leg's PERMISSION MODE (`TaskSpec.permissionMode`'s live half — a shell's mode toggle, a serving
|
|
280
|
+
* layer's `permission_mode` control frame). The requested word is rewritten on the leg's mode seat; the deployment's
|
|
281
|
+
* fold is re-judged (`bypassPermissions` where `RuntimeCaps.bypassPermissions` is `false`, or `auto` with no
|
|
282
|
+
* classifier face / a `false` deny bit, runs `default` and is announced `config.permission_mode_unavailable`); a leg
|
|
283
|
+
* that became `auto` arms its classifier now — an arming that fails rolls the steer back (the word it replaced stands)
|
|
284
|
+
* and the verb rejects with the arming's code; the NEXT tool call reads the new mode at the gate's arbitration point
|
|
285
|
+
* (a tightening and a loosening both take effect one call later, on one seat), and a child leg derives its own
|
|
286
|
+
* effective mode from this seat on its next read. Resolves the same three-member receipt `wiring_manifest.permissionMode`
|
|
287
|
+
* carries — `{ requested, effective, foldedBy? }` — on EVERY call, moved or not, so a caller renders what the engine
|
|
288
|
+
* says and never what it sent. Typed refusals: `steering.invalid_content` (a word outside the closed set) and the
|
|
289
|
+
* steer-family `steering.not_running` once the task has finished. AUTHORITY (steer-lane law): holding this stream IS
|
|
290
|
+
* the capability handle; a service exposing the verb owns the principal check at its own face — and a deployment that
|
|
291
|
+
* must never let a caller reach `bypassPermissions` says so with the deny bit, which this verb honours.
|
|
292
|
+
*/
|
|
293
|
+
setPermissionMode(mode: import("./ask-origin.js").PermissionMode): Promise<import("./mode-policy.js").PermissionModeReceipt>;
|
|
278
294
|
/**
|
|
279
295
|
* **RE-DIAL one MCP server** on the running task, the action `RefreshMcpTools` deliberately
|
|
280
296
|
* is not (its contract is "never dials or re-dials"). Core holds no MCP connection across task
|
|
@@ -4,6 +4,7 @@ export const TERMINAL_CAUSE_IS_REPLAYABLE = {
|
|
|
4
4
|
blocked: false,
|
|
5
5
|
paused: false,
|
|
6
6
|
};
|
|
7
|
+
Object.freeze(TERMINAL_CAUSE_IS_REPLAYABLE);
|
|
7
8
|
export function isTerminalCauseKind(kind) {
|
|
8
9
|
return typeof kind === "string" && Object.hasOwn(TERMINAL_CAUSE_IS_REPLAYABLE, kind);
|
|
9
10
|
}
|
|
@@ -13,8 +13,8 @@ export declare class TaskSpecTightenError extends Error {
|
|
|
13
13
|
* would drop the base policy entirely.
|
|
14
14
|
* - **`handsReadOnly`** — `true` is stricter. `base:true` + `override:false` ⇒ **throw** (loosening). Otherwise
|
|
15
15
|
* the stricter (`true` if either is true) wins.
|
|
16
|
-
* - **`shellGate`** — `
|
|
17
|
-
*
|
|
16
|
+
* - **`shellGate`** — one value (`"always"`, the operator floor). Either side set ⇒ the floor stands; it cannot
|
|
17
|
+
* be loosened because absence is the only other state and an override cannot unset a base value.
|
|
18
18
|
* - **`onAsk`** — no safe automatic merge (the approver and the policy must be coordinated). Both set to
|
|
19
19
|
* DIFFERENT values ⇒ **throw**; equal ⇒ keep; one set ⇒ that one.
|
|
20
20
|
* - **`hooks`** — no standard composition (stacking hook results is caller-specific). Both set ⇒ **throw**;
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { combinePolicies } from "./tool-policy.js";
|
|
2
|
-
|
|
3
|
-
off: 0,
|
|
4
|
-
classify: 1,
|
|
5
|
-
always: 2,
|
|
6
|
-
};
|
|
2
|
+
import { clampPermissionMode } from "./ask-origin.js";
|
|
7
3
|
export class TaskSpecTightenError extends Error {
|
|
8
4
|
constructor(message) {
|
|
9
5
|
super(message);
|
|
@@ -51,16 +47,18 @@ export function tightenTaskSpec(base, overrides) {
|
|
|
51
47
|
if (base.handsReadOnly !== undefined || overrides.handsReadOnly !== undefined) {
|
|
52
48
|
merged.handsReadOnly = base.handsReadOnly === true || overrides.handsReadOnly === true;
|
|
53
49
|
}
|
|
54
|
-
if (base.
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
throw new TaskSpecTightenError(`tightenTaskSpec: cannot loosen shellGate from "${baseGate}" to "${overrideGate}" (override may only tighten)`);
|
|
50
|
+
if (base.permissionMode !== undefined && overrides.permissionMode !== undefined) {
|
|
51
|
+
const { effective, clamped } = clampPermissionMode(overrides.permissionMode, base.permissionMode);
|
|
52
|
+
if (clamped) {
|
|
53
|
+
throw new TaskSpecTightenError(`tightenTaskSpec: permissionMode override "${overrides.permissionMode}" would loosen the base "${base.permissionMode}" (the override may only tighten; a bypass override needs a bypass base, acceptEdits and auto never adopt each other)`);
|
|
59
54
|
}
|
|
60
|
-
merged.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
merged.permissionMode = effective;
|
|
56
|
+
}
|
|
57
|
+
else if (base.permissionMode !== undefined || overrides.permissionMode !== undefined) {
|
|
58
|
+
merged.permissionMode = overrides.permissionMode ?? base.permissionMode;
|
|
59
|
+
}
|
|
60
|
+
if (base.shellGate !== undefined || overrides.shellGate !== undefined) {
|
|
61
|
+
merged.shellGate = base.shellGate ?? overrides.shellGate;
|
|
64
62
|
}
|
|
65
63
|
if (base.onAsk !== undefined && overrides.onAsk !== undefined && base.onAsk !== overrides.onAsk) {
|
|
66
64
|
throw new TaskSpecTightenError("tightenTaskSpec: conflicting onAsk on base and overrides — onAsk cannot be merged automatically " +
|
|
@@ -56,7 +56,7 @@ export const TOOL_CATALOG_ENTRIES = [
|
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
id: "bash", name: "Bash", source: "builtin", definedIn: "src/tools/fs/fs-bash.ts", mountedBy: ["hands"],
|
|
59
|
-
cards: ["bash", "readonly_out_of_root", "bash_invalid_timeout"],
|
|
59
|
+
cards: ["bash", "readonly_out_of_root", "read_unestablished", "bash_invalid_timeout"],
|
|
60
60
|
face: {
|
|
61
61
|
family: "shell", effect: "write", contentOrigin: "execution", contract: c(FULL_SHELL_CONTRACT_ID),
|
|
62
62
|
ruleFace: SHELL_RULE_FACE,
|
|
@@ -66,7 +66,7 @@ export const TOOL_CATALOG_ENTRIES = [
|
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
id: "bash-readonly", name: "Bash", source: "builtin", definedIn: "src/tools/fs/fs-bash.ts", mountedBy: ["hands"],
|
|
69
|
-
cards: ["bash", "readonly_out_of_root", "bash_invalid_timeout"],
|
|
69
|
+
cards: ["bash", "readonly_out_of_root", "read_unestablished", "bash_invalid_timeout"],
|
|
70
70
|
face: {
|
|
71
71
|
family: "shell", effect: "read", contract: c(READONLY_SHELL_CONTRACT_ID),
|
|
72
72
|
ruleFace: SHELL_RULE_FACE,
|
|
@@ -108,7 +108,7 @@ export const TOOL_CATALOG_ENTRIES = [
|
|
|
108
108
|
{ id: "task-stop-hands", name: "TaskStop", source: "builtin", definedIn: "src/tools/fs/fs-bash.ts", mountedBy: ["hands"], cards: [], face: { family: "delegate", effect: "write", contract: TASK_STOP_CONTRACT } },
|
|
109
109
|
{ id: "task-output", name: "TaskOutput", source: "builtin", definedIn: "src/core/task-registry.ts", mountedBy: ["delegation"], cards: ["task-output"], face: { family: "delegate", effect: "read", contract: TASK_OUTPUT_CONTRACT } },
|
|
110
110
|
{ id: "task-stop", name: "TaskStop", source: "builtin", definedIn: "src/core/task-registry.ts", mountedBy: ["delegation"], cards: ["task-stop"], face: { family: "delegate", effect: "write", contract: TASK_STOP_CONTRACT } },
|
|
111
|
-
{ id: "monitor", name: "Monitor", source: "builtin", definedIn: "src/tools/monitor.ts", mountedBy: ["backgroundTasks"], cards: ["monitor-start", "readonly_out_of_root"], face: { family: "other", effect: "write", contract: c("core.monitor@1"), offloadThresholdChars: 10_000 } },
|
|
111
|
+
{ id: "monitor", name: "Monitor", source: "builtin", definedIn: "src/tools/monitor.ts", mountedBy: ["backgroundTasks"], cards: ["monitor-start", "readonly_out_of_root", "read_unestablished"], face: { family: "other", effect: "write", contract: c("core.monitor@1"), offloadThresholdChars: 10_000 } },
|
|
112
112
|
{ id: "send-message", name: "SendMessage", source: "builtin", definedIn: "src/agents/send-message-tool.ts", mountedBy: ["delegation", "hostInternals", "peerLane"], cards: ["send-message"], face: { family: "protocol", effect: "write", contract: c("core.send_message@1") } },
|
|
113
113
|
{ id: "agent-transcript", name: "AgentTranscript", source: "builtin", definedIn: "src/agents/agent-transcript-tool.ts", mountedBy: ["delegation"], cards: ["agent-transcript"], face: { family: "delegate", effect: "read", contract: c("core.agent_transcript@1") } },
|
|
114
114
|
{ id: "list-agents", name: "ListAgents", source: "builtin", definedIn: "src/agents/list-agents-tool.ts", mountedBy: ["delegation", "peerLane"], cards: ["list-agents"], face: { family: "protocol", effect: "read", aliases: ["ListPeers"], contract: c("core.list_agents@1") } },
|
|
@@ -156,6 +156,7 @@ export const ENGINE_CARDS = Object.freeze({
|
|
|
156
156
|
path_not_in_root: "src/tools/fs/safety.ts",
|
|
157
157
|
read_path_denied: "src/tools/fs/safety.ts",
|
|
158
158
|
readonly_out_of_root: "src/tools/fs/shell-read-check.ts",
|
|
159
|
+
read_unestablished: "src/tools/fs/shell-read-check.ts",
|
|
159
160
|
});
|
|
160
161
|
const byId = new Map();
|
|
161
162
|
for (const e of TOOL_CATALOG_ENTRIES) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AskOrigin } from "./ask-origin.js";
|
|
2
2
|
import { type ReadRootGrantScreen } from "./read-root-grant-screen.js";
|
|
3
3
|
import { type Settlement } from "./gate-outcome.js";
|
|
4
4
|
/** A tool call presented to a policy before it executes. */
|
|
@@ -325,9 +325,8 @@ export type PermissionResult = {
|
|
|
325
325
|
/** This ask is MANDATED — no persisted allow rule and no STANDING ANSWER (a release remembered from an
|
|
326
326
|
* earlier call, at any scope, a session included) may retire it; only an answer produced for THIS call
|
|
327
327
|
* clears it — a person, a hook, a deployment's blanket `onAsk: "allow"` seat, or the live deciders the
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
* blanket seat. ENGINE-STAMPED at the gate's single origin stamp from the ONE derivation
|
|
328
|
+
* ask's class row admits ({@link import("./ask-origin.js").MODE_ARBITRATION}); {@link requiresRealApproval}
|
|
329
|
+
* is the stronger demand that also refuses the blanket seat. ENGINE-STAMPED at the gate's single origin stamp from the ONE derivation
|
|
331
330
|
* the approval card's rule-offer word also reads ({@link import("./ask-origin.js").ruleClearanceOf},
|
|
332
331
|
* which lists the doors). TIGHTENING-ONLY: a layer below the mint may not clear one a layer above
|
|
333
332
|
* set, and a policy self-declaring it only opts its own ask OUT of automatic answering. Carried
|
|
@@ -376,19 +375,34 @@ export type PermissionResult = {
|
|
|
376
375
|
* fallback / the reserved question tool / a marked call / plain policy). ENGINE-STAMPED inside the
|
|
377
376
|
* gate at the same single stamp point as `ruleEvidence`, derived from what the gate's own layers
|
|
378
377
|
* did ({@link import("./ask-origin.js").askOriginOf}) — a policy that self-declares it is overwritten
|
|
379
|
-
* there, so the member is never a channel a policy can loosen through.
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
* decision is caller-composable; every ask the gate hands to its resolver carries it. */
|
|
378
|
+
* there, so the member is never a channel a policy can loosen through. A DISCLOSURE word: the lanes
|
|
379
|
+
* read the ask's class set ({@link import("./ask-origin.js").classifyAsk}), an approval surface may
|
|
380
|
+
* render the card by it. Optional on the TYPE only because a decision is caller-composable; every ask
|
|
381
|
+
* the gate hands to its resolver carries it. */
|
|
384
382
|
origin?: import("./ask-origin.js").AskOrigin;
|
|
385
|
-
/**
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
|
|
391
|
-
|
|
383
|
+
/** WHICH integrity guard minted this ask — the unverifiable recursive delete or the session-transcript
|
|
384
|
+
* write. ENGINE-STAMPED by the two guard policies; a fold-internal fact the class derivation reads
|
|
385
|
+
* ({@link import("./ask-origin.js").classifyAsk}) so the two guards, which mint the same
|
|
386
|
+
* `requiresRealApproval` ask, are told apart by a fact and not by their prose. Never copied onto the
|
|
387
|
+
* approval request or the park row. */
|
|
388
|
+
guard?: "unverifiable_delete" | "transcript_write" | "artifact_shared_read";
|
|
389
|
+
/** The call could not be READ against a rule that may reach it — a content-form deny row, a content-form
|
|
390
|
+
* ask row, or a persisted-rule row (the lexer's word). ENGINE-STAMPED at the three unreadable mint sites;
|
|
391
|
+
* the class derivation reads it as `rule_unreadable` (a mode may release it, a person may clear it, no
|
|
392
|
+
* automatic lane may). It replaces the former practice of stamping `matchedAskRule` on an unreadable
|
|
393
|
+
* read: a rule that COULD NOT be matched is not a rule that matched. Fold-internal; not on the request. */
|
|
394
|
+
ruleUnreadable?: "content_deny" | "content_ask" | "persisted_call";
|
|
395
|
+
/** The ask was authored by the DEPLOYMENT's own governance (an operator's must-ask list, a managed command
|
|
396
|
+
* policy's default ask) — a fact a host's policy states on its own ask so the class derivation reads it
|
|
397
|
+
* as `operator_forced`: a permission mode releases nothing an operator mandated, and `dontAsk` does not
|
|
398
|
+
* settle it. Public: a host composing such a policy sets it; {@link combinePolicies} carries it onto the
|
|
399
|
+
* surviving ask (tighten-only). Not on the approval request — the card shows `origin` / `mandate`. */
|
|
400
|
+
authoredBy?: "deployment";
|
|
401
|
+
/** The classes of the OTHER asks a policy fold collapsed into this one
|
|
402
|
+
* ({@link import("./ask-origin.js").decisionAskClassesOf} per contributing ask, unioned), so a member the
|
|
403
|
+
* fold does not copy — a guard mark, an unreadable-rule fact, a bare real-approval bit — is not lost behind
|
|
404
|
+
* the ask that won the slot. ENGINE-WRITTEN by {@link combinePolicies}; fold-internal, never on the request. */
|
|
405
|
+
foldedAskClasses?: readonly import("./ask-origin.js").AskClass[];
|
|
392
406
|
} | {
|
|
393
407
|
action: "deny";
|
|
394
408
|
updatedInput?: unknown;
|
|
@@ -495,7 +509,23 @@ export interface ToolPolicy {
|
|
|
495
509
|
* describes the policy must read that bit, not the field's presence.
|
|
496
510
|
*/
|
|
497
511
|
readonly projection?: ToolPolicyProjection;
|
|
512
|
+
/** The policy's path DENY rules that speak for a face of the engine's deny table (a `Read(<pattern>)` / `Edit(<pattern>)`
|
|
513
|
+
* deny compiled by `createPermissionRulePolicy`), as rows the prepared leg joins to its read and write tables — so the
|
|
514
|
+
* shell's reads and writes and the traversals meet the rule, not only the tool it names. Carried through
|
|
515
|
+
* {@link combinePolicies}; read with {@link toolPolicyPathDenyRules}. Deny-only: a row can only refuse more. */
|
|
516
|
+
readonly pathDenyRules?: readonly import("../tools/fs/read-deny.js").PathRuleDenyRow[];
|
|
517
|
+
/** Does this policy guard a write to this ABSOLUTE target — would it refuse the write if a write tool made it? The
|
|
518
|
+
* policy's answer for a writer it cannot see as a call (the shell's file commands), read by the shell station's
|
|
519
|
+
* `writesPlaced` fact alone: a guarded target is not placed, so no permission mode answers the command and its ask
|
|
520
|
+
* reaches a person — it is not refused. A PURE judgment: no side effect, no audit record, no hook, no approver; `true`
|
|
521
|
+
* on a target it cannot judge. Carried through {@link combinePolicies} (any member's `true` is the answer). */
|
|
522
|
+
readonly writeTargetGuard?: (target: string, signal?: AbortSignal) => Promise<boolean>;
|
|
498
523
|
}
|
|
524
|
+
/** Every path deny row the given policies carry (combined policies included), in order — the deny tables' rule rows. */
|
|
525
|
+
export declare function toolPolicyPathDenyRules(policies: readonly (ToolPolicy | undefined)[]): readonly import("../tools/fs/read-deny.js").PathRuleDenyRow[];
|
|
526
|
+
/** The one write-target guard the given policies carry together (combined policies included): `true` when any member
|
|
527
|
+
* guards the target, a member that throws counting as guarding it. `undefined` when none carries one. */
|
|
528
|
+
export declare function toolPolicyWriteTargetGuard(policies: readonly (ToolPolicy | undefined)[]): ToolPolicy["writeTargetGuard"];
|
|
499
529
|
/**
|
|
500
530
|
* One deterministic DENY component of a {@link ToolPolicyProjection} (F-012 L1). All fields are plain
|
|
501
531
|
* serializable data — a component round-trips JSON/`structuredClone` unchanged, which is what lets a
|
|
@@ -910,10 +940,10 @@ export declare const COARSE_SHELL_TOOLS: string[];
|
|
|
910
940
|
* you MUST compose explicitly: `combinePolicies(deploymentBaseline, createCoarseCommandNamePolicy(…))`
|
|
911
941
|
* (deny-wins — both policies' denies still fire). {@link tightenTaskSpec} does this for you.
|
|
912
942
|
*
|
|
913
|
-
*
|
|
914
|
-
* `"maybe"` tier is the
|
|
943
|
+
* On the full shell face a plain `ask` from this policy stays a plain ask: the shell seat's
|
|
944
|
+
* `"maybe"` tier is the shape probe's instrument, not a risk label, so the park is the budgetable kind
|
|
915
945
|
* (`human` under `durableApproval`) unless the probe mandated the call (an out-of-root read) or could not
|
|
916
|
-
* answer. A command the
|
|
946
|
+
* answer. A command the shape tier's OWN tighten refuses (`rm -rf …` with no policy asking first) still parks
|
|
917
947
|
* non-budgetable — the safety axis is judged per call by `safetyAxisOf`.
|
|
918
948
|
*/
|
|
919
949
|
export declare function createCoarseCommandNamePolicy(opts: {
|
|
@@ -1299,6 +1329,11 @@ export interface AskRequest {
|
|
|
1299
1329
|
* gate, never caller/worker-settable; tightening-only.
|
|
1300
1330
|
*/
|
|
1301
1331
|
readonly mandated?: true;
|
|
1332
|
+
/** The ask was minted by the deployment's own governance layer (the {@link PermissionResult} ask arm's member of the same
|
|
1333
|
+
* name, carried through every request mint and onto the park row): a person's question under every mode — no persisted
|
|
1334
|
+
* rule, standing answer or mode release retires it. A host approval seat that settles asks from a session grant reads
|
|
1335
|
+
* it to keep its hands off. Filled by the gate, never caller/worker-settable. */
|
|
1336
|
+
readonly authoredBy?: "deployment";
|
|
1302
1337
|
/** WHICH word of the closed {@link import("./ask-origin.js").PERSISTED_RULE_MANDATES} set {@link mandated} stands
|
|
1303
1338
|
* on — present ⇔ that table word is the bit's WHOLE reason (`probe_mandate` = a read outside the session's
|
|
1304
1339
|
* directories). ABSENT beside `mandated: true` = another door holds too (governance, a hook, an ancestor's
|
|
@@ -1317,12 +1352,6 @@ export interface AskRequest {
|
|
|
1317
1352
|
* that forwards a decision's member here does not compile, which is the point: the window is the
|
|
1318
1353
|
* route's fact, and forwarding it silently is how a configured window becomes a wait with none. */
|
|
1319
1354
|
readonly denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
1320
|
-
/** #688 C3 (additive) — the wire twin of the {@link PermissionResult} ask-arm member of the same name: which
|
|
1321
|
-
* mechanism stands behind an `origin: "rule_store_unavailable"` (`store` / `call`,
|
|
1322
|
-
* {@link import("./ask-origin.js").RULE_STORE_UNREADABLE_KINDS}). Present ⇔ that origin; a consumer that
|
|
1323
|
-
* renders "the rule store is unreachable" vs "this command could not be read against your rules" branches
|
|
1324
|
-
* here, never on the message text. Filled by the gate's own carry station from the engine-stamped decision. */
|
|
1325
|
-
readonly ruleStoreUnreadable?: import("./ask-origin.js").RuleStoreUnreadable;
|
|
1326
1355
|
/** (additive) WHICH AUTHORITY raised this ask — the wire twin of the {@link PermissionResult} ask-arm
|
|
1327
1356
|
* member of the same name, one word from the closed {@link import("./ask-origin.js").AskOrigin} set.
|
|
1328
1357
|
* The gate's own mint station copies the engine-stamped word; the three inherited-lane stations
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { brandPolicyAskClass } from "./ask-class.js";
|
|
4
|
+
import { decisionAskClassesOf, headlessDenialMessage } from "./ask-origin.js";
|
|
4
5
|
import { ownReadRootCandidate, readRootGrantSeat } from "./read-root-grant-screen.js";
|
|
5
6
|
import { decidersNote, settledByPerson } from "./gate-outcome.js";
|
|
6
7
|
import { sanitizeAutoModeArmingRecipe } from "./auto-mode-arming.js";
|
|
@@ -121,6 +122,26 @@ export const ASK_EVIDENCE_ABSENCE_VALUES = ["not_wired", "not_adjudicated", "una
|
|
|
121
122
|
export function decisionText(d) {
|
|
122
123
|
return d.message;
|
|
123
124
|
}
|
|
125
|
+
export function toolPolicyPathDenyRules(policies) {
|
|
126
|
+
return policies.flatMap((p) => (Array.isArray(p?.pathDenyRules) ? p.pathDenyRules : []));
|
|
127
|
+
}
|
|
128
|
+
export function toolPolicyWriteTargetGuard(policies) {
|
|
129
|
+
const guards = policies.flatMap((p) => (typeof p?.writeTargetGuard === "function" ? [p.writeTargetGuard] : []));
|
|
130
|
+
if (guards.length === 0)
|
|
131
|
+
return undefined;
|
|
132
|
+
return async (target, signal) => {
|
|
133
|
+
for (const guard of guards) {
|
|
134
|
+
try {
|
|
135
|
+
if ((await guard(target, signal)) !== false)
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
124
145
|
export function namespacedCoveringEntries(entries) {
|
|
125
146
|
return (entries ?? []).filter(isNamespacedCoveringRuleName);
|
|
126
147
|
}
|
|
@@ -452,6 +473,8 @@ export function createApprovalPolicy(opts) {
|
|
|
452
473
|
}
|
|
453
474
|
export function combinePolicies(...policies) {
|
|
454
475
|
const nameSets = policies.flatMap(toolPolicyNameSets);
|
|
476
|
+
const pathDenyRules = toolPolicyPathDenyRules(policies);
|
|
477
|
+
const writeTargetGuard = toolPolicyWriteTargetGuard(policies);
|
|
455
478
|
const childProjections = policies.map((p) => p.projection);
|
|
456
479
|
const projectedComponents = [];
|
|
457
480
|
let combinedRemainder = false;
|
|
@@ -472,6 +495,8 @@ export function combinePolicies(...policies) {
|
|
|
472
495
|
return {
|
|
473
496
|
...(combinedProjection !== undefined ? { projection: combinedProjection } : {}),
|
|
474
497
|
...(nameSets.length > 0 ? { nameSets } : {}),
|
|
498
|
+
...(pathDenyRules.length > 0 ? { pathDenyRules } : {}),
|
|
499
|
+
...(writeTargetGuard !== undefined ? { writeTargetGuard } : {}),
|
|
475
500
|
async check(req, signal) {
|
|
476
501
|
let asked;
|
|
477
502
|
let current = req;
|
|
@@ -482,6 +507,8 @@ export function combinePolicies(...policies) {
|
|
|
482
507
|
let probeMandateSeen = false;
|
|
483
508
|
let mandateSeen = false;
|
|
484
509
|
let fallbackSeen;
|
|
510
|
+
let authoredBySeen = false;
|
|
511
|
+
const contributors = [];
|
|
485
512
|
for (const p of policies) {
|
|
486
513
|
const d = refuseOutOfContractDecision(await p.check(current, signal));
|
|
487
514
|
if (d.action === "deny") {
|
|
@@ -516,6 +543,10 @@ export function combinePolicies(...policies) {
|
|
|
516
543
|
mandateSeen = true;
|
|
517
544
|
if (d.action === "ask" && d.denialLimitFallback !== undefined && fallbackSeen === undefined)
|
|
518
545
|
fallbackSeen = d.denialLimitFallback;
|
|
546
|
+
if (d.action === "ask" && d.authoredBy === "deployment")
|
|
547
|
+
authoredBySeen = true;
|
|
548
|
+
if (d.action === "ask")
|
|
549
|
+
contributors.push(d);
|
|
519
550
|
}
|
|
520
551
|
if (asked) {
|
|
521
552
|
const merged = rewrite?.updatedInput;
|
|
@@ -523,7 +554,14 @@ export function combinePolicies(...policies) {
|
|
|
523
554
|
const withMark = probeMandateSeen && withRuleAsk.probeMandated !== true ? { ...withRuleAsk, probeMandated: true } : withRuleAsk;
|
|
524
555
|
const withMandate = mandateSeen && withMark.mandated !== true ? { ...withMark, mandated: true } : withMark;
|
|
525
556
|
const withFallback = fallbackSeen !== undefined && withMandate.denialLimitFallback === undefined ? { ...withMandate, denialLimitFallback: fallbackSeen, requiresRealApproval: true } : withMandate;
|
|
526
|
-
|
|
557
|
+
const withAuthor = authoredBySeen && withFallback.authoredBy === undefined ? { ...withFallback, authoredBy: "deployment" } : withFallback;
|
|
558
|
+
const folded = new Set(withAuthor.foldedAskClasses ?? []);
|
|
559
|
+
for (const c of contributors)
|
|
560
|
+
if (c !== asked)
|
|
561
|
+
for (const k of decisionAskClassesOf(c))
|
|
562
|
+
folded.add(k);
|
|
563
|
+
const withClasses = folded.size > 0 ? { ...withAuthor, foldedAskClasses: [...folded] } : withAuthor;
|
|
564
|
+
return merged !== undefined ? { ...withClasses, updatedInput: merged } : withClasses;
|
|
527
565
|
}
|
|
528
566
|
const allowed = rewrite ?? ALLOW;
|
|
529
567
|
if (!anyRecord)
|
|
@@ -829,6 +867,7 @@ export function createUnverifiableDeletePolicy(opts) {
|
|
|
829
867
|
action: "ask",
|
|
830
868
|
decisionReason: "safety",
|
|
831
869
|
requiresRealApproval: true,
|
|
870
|
+
guard: "unverifiable_delete",
|
|
832
871
|
message: `Unverifiable recursive delete (fail-closed unless cleared): ${finding}. ` +
|
|
833
872
|
`Re-run the delete with the resolved literal path written into the command itself ` +
|
|
834
873
|
`(or assign the variable in the same command, e.g. \`DIR=/exact/path; rm -rf "$DIR"\`) so the target can be verified.`,
|
|
@@ -905,6 +944,7 @@ export function createTranscriptIntegrityPolicy(opts) {
|
|
|
905
944
|
action: "ask",
|
|
906
945
|
decisionReason: "safety",
|
|
907
946
|
requiresRealApproval: true,
|
|
947
|
+
guard: "transcript_write",
|
|
908
948
|
message: `Session-transcript write (fail-closed unless cleared): ${what}. Session transcripts (the .jsonl files ` +
|
|
909
949
|
`under the agent data dir's sessions/ directory) are harness-written session state, not agent working ` +
|
|
910
950
|
`files — modifying or deleting them tampers with the run's own audit trail. Reading them (ls/cat/grep ` +
|
|
@@ -956,6 +996,7 @@ export function isLiveApproverSeat(onAsk) {
|
|
|
956
996
|
return typeof onAsk === "function";
|
|
957
997
|
}
|
|
958
998
|
export const DURABLE_MANDATE_SOURCES = ["force_durable_gate", "durable_approval"];
|
|
999
|
+
Object.freeze(DURABLE_MANDATE_SOURCES);
|
|
959
1000
|
export function durableMandateSourceOf(args) {
|
|
960
1001
|
if (args.forceDurableGate)
|
|
961
1002
|
return "force_durable_gate";
|
|
@@ -1250,7 +1291,7 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
|
|
|
1250
1291
|
if (onAsk === undefined || onAsk === "deny") {
|
|
1251
1292
|
return {
|
|
1252
1293
|
action: "deny",
|
|
1253
|
-
message:
|
|
1294
|
+
message: headlessDenialMessage(req.toolName, req.message),
|
|
1254
1295
|
decisionReason: "mode",
|
|
1255
1296
|
settlement: settledByNobody("no_approver"),
|
|
1256
1297
|
};
|
|
@@ -28,6 +28,8 @@ export declare function pathConfinableWriteToolNames(): ReadonlySet<string>;
|
|
|
28
28
|
export declare function skillScopeWriteToolNames(): ReadonlySet<string>;
|
|
29
29
|
/** A-8 — every definition that names a path target at all (read or write): the file-tool glob lane. */
|
|
30
30
|
export declare function pathTargetingToolNames(): ReadonlySet<string>;
|
|
31
|
+
/** The tools whose declared path target is a READ (a file read, a search root): the file read station's membership. */
|
|
32
|
+
export declare function readTargetToolNames(): ReadonlySet<string>;
|
|
31
33
|
/** A-10 — the micro-compaction whitelist (`compactable: true`). */
|
|
32
34
|
export declare function compactableToolNames(): ReadonlySet<string>;
|
|
33
35
|
/** A-5 — the aggregate-budget exemptions (`budgetExempt: true`). */
|
|
@@ -31,6 +31,9 @@ export function skillScopeWriteToolNames() {
|
|
|
31
31
|
export function pathTargetingToolNames() {
|
|
32
32
|
return names((e) => e.face.pathTarget !== undefined);
|
|
33
33
|
}
|
|
34
|
+
export function readTargetToolNames() {
|
|
35
|
+
return names((e) => e.face.pathTarget?.access === "read");
|
|
36
|
+
}
|
|
34
37
|
export function compactableToolNames() {
|
|
35
38
|
return names((e) => e.face.compactable === true);
|
|
36
39
|
}
|