@sema-agent/core 7.33.8 → 7.35.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 +462 -3
- 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/errors.d.ts +23 -1
- package/dist/brain/errors.js +19 -2
- package/dist/brain/open-responses.js +1 -0
- package/dist/brain/openai.js +1 -0
- package/dist/brain/status-sink.d.ts +7 -1
- package/dist/brain/stream-engine.d.ts +3 -0
- package/dist/brain/stream-engine.js +2 -1
- package/dist/brain/stream-shared.d.ts +3 -1
- package/dist/brain/stream-shared.js +16 -4
- package/dist/core/a2a.d.ts +3 -1
- package/dist/core/a2a.js +1 -1
- package/dist/core/ask-origin.d.ts +379 -154
- package/dist/core/ask-origin.js +224 -45
- package/dist/core/ask-question.js +2 -1
- 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 +54 -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 +19 -3
- package/dist/core/gate-lanes.js +116 -50
- package/dist/core/gate-outcome.d.ts +40 -7
- package/dist/core/gate-outcome.js +26 -4
- package/dist/core/governance-codes.d.ts +18 -2
- package/dist/core/governance-codes.js +23 -1
- package/dist/core/hooks.d.ts +53 -29
- package/dist/core/hooks.js +1 -3
- package/dist/core/mcp.js +1 -1
- package/dist/core/message-utils.d.ts +2 -1
- package/dist/core/message-utils.js +2 -1
- package/dist/core/mode-policy.d.ts +185 -0
- package/dist/core/mode-policy.js +184 -0
- package/dist/core/oracle-isolation.d.ts +3 -3
- package/dist/core/permission-rule-model.d.ts +20 -3
- package/dist/core/permission-rule-model.js +40 -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 +56 -9
- package/dist/core/persisted-rule-arms.d.ts +14 -10
- package/dist/core/persisted-rule-arms.js +13 -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/active-skill-scope.js +5 -4
- package/dist/core/runner/assemble-result.js +2 -2
- 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 +62 -38
- package/dist/core/runner/denial-limit-arms.d.ts +35 -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/file-history-request.d.ts +26 -0
- package/dist/core/runner/file-history-request.js +90 -0
- 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 +11 -6
- package/dist/core/runner/permission-rule-lanes.js +55 -48
- 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 +6 -4
- package/dist/core/runner/prepare-ask-lane.js +3 -3
- 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-file-history.d.ts +9 -3
- package/dist/core/runner/prepare-file-history.js +4 -90
- package/dist/core/runner/prepare-gate-stations.d.ts +37 -9
- package/dist/core/runner/prepare-gate-stations.js +46 -29
- 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 +30 -23
- package/dist/core/runner/prepare-inherited-gate.js +45 -25
- package/dist/core/runner/prepare-park-ask.d.ts +6 -4
- package/dist/core/runner/prepare-park-ask.js +7 -5
- package/dist/core/runner/prepare-policy-chain.d.ts +25 -4
- package/dist/core/runner/prepare-policy-chain.js +203 -180
- 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 +56 -24
- 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-brain-sinks.js +3 -1
- package/dist/core/runner/run-identity-wiring.js +1 -1
- package/dist/core/runner/run-leg.js +29 -18
- package/dist/core/runner/run-notification-lane.d.ts +3 -0
- package/dist/core/runner/run-notification-lane.js +3 -1
- 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.d.ts +1 -0
- package/dist/core/runner/runtask.js +8 -11
- package/dist/core/runner/session-mode-seats.d.ts +48 -0
- package/dist/core/runner/session-mode-seats.js +73 -0
- package/dist/core/runner/session-rule-policy.js +3 -2
- 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/secret-env.d.ts +30 -58
- package/dist/core/secret-env.js +149 -586
- package/dist/core/sensitive-path-policy.d.ts +4 -2
- package/dist/core/sensitive-path-policy.js +41 -31
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
- 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 +84 -30
- package/dist/core/tool-policy.js +76 -16
- package/dist/core/tool-registry.d.ts +38 -0
- package/dist/core/tool-registry.js +25 -1
- package/dist/core/tool-roster.d.ts +17 -2
- package/dist/core/tool-roster.js +11 -1
- package/dist/core/tool-spec.d.ts +8 -9
- package/dist/core/trace.d.ts +18 -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 +6 -1
- package/dist/engine/execution-env/node-execution-env.js +16 -3
- package/dist/engine/harness/agent-harness.js +2 -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/engine/lsp/node-lsp-manager.js +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.js +15 -6
- 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/stores/file/index.d.ts +5 -3
- package/dist/stores/file/session-store.d.ts +8 -2
- package/dist/stores/file/session-store.js +5 -5
- 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 +50 -0
- package/dist/tools/fs/file-read-check.js +79 -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 +52 -13
- package/dist/tools/fs/shell-read-check.js +199 -20
- 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 +117 -11
- package/wire-changes/core@7.34.0.json +2621 -0
- package/wire-changes/core@7.35.0.json +584 -0
|
@@ -7,7 +7,7 @@ import type { ReadEntry } from "../tools/fs/safety.js";
|
|
|
7
7
|
import type { RepairBundle } from "../agents/repair-loop.js";
|
|
8
8
|
import type { WorkspaceHandle } from "./remote-env.js";
|
|
9
9
|
import type { SessionPermissionRules } from "./session-policy-store.js";
|
|
10
|
-
import type {
|
|
10
|
+
import type { NestedUsageAccum, PermissionMode, ShellGateDoctrine, TaskResult } from "./types.js";
|
|
11
11
|
import type { ActiveWorktreeSession } from "../internal/harness-types.js";
|
|
12
12
|
/**
|
|
13
13
|
* design/45 — the **durable-checkpoint** primitive: cross-process / resumable `suspend`/`resume`.
|
|
@@ -130,11 +130,9 @@ export interface RiskDescriptor {
|
|
|
130
130
|
};
|
|
131
131
|
/** The tool whose call is gated (mirrors the gate's `toolName`). */
|
|
132
132
|
toolName: string;
|
|
133
|
-
/** The
|
|
134
|
-
* seat
|
|
135
|
-
*
|
|
136
|
-
* Absent for per-tool safety-axis / plain policy asks. Answers "which doctrine was live when this gate
|
|
137
|
-
* fired" from the persisted row alone — three artifact layers could not answer that in the field. */
|
|
133
|
+
/** The operator's shell floor (`"always"`), present ONLY when this ask was minted through the engine's shell
|
|
134
|
+
* seat AND a floor was in force. Absent for a shape-tier ask with no floor, and for per-tool safety-axis /
|
|
135
|
+
* plain policy asks. Answers "was an operator floor live when this gate fired" from the persisted row alone. */
|
|
138
136
|
shellGateDoctrine?: ShellGateDoctrine;
|
|
139
137
|
/** A REDACTED, length-capped ONE-LINE summary of the call (the command for a shell gate; a brief key-arg
|
|
140
138
|
* digest otherwise) for the inbox preview. NEUTRALIZED via
|
|
@@ -816,6 +814,9 @@ export type PendingAction = {
|
|
|
816
814
|
* when a person's rule also speaks — never a substitute for this one). Display metadata: no gate, CAS or
|
|
817
815
|
* resume path reads it. */
|
|
818
816
|
mandated?: true;
|
|
817
|
+
/** The PARK twin of `AskRequest.authoredBy`: the deployment's own governance layer minted this parked question — a
|
|
818
|
+
* person's under every mode, whatever grant or rule the redeeming host holds. Display / seat metadata. */
|
|
819
|
+
authoredBy?: "deployment";
|
|
819
820
|
/** The PARK twin of `AskRequest.mandate` (same derivation, same precedent): the table word the bit stands on,
|
|
820
821
|
* present only when it is the whole reason. Written only as a member of the closed set. */
|
|
821
822
|
mandate?: import("./ask-origin.js").PersistedRuleMandate;
|
|
@@ -833,10 +834,6 @@ export type PendingAction = {
|
|
|
833
834
|
* Written only when it is a member of the set; a pre-existing row reads absent (unreported).
|
|
834
835
|
* Display metadata — the resume belts keep reading the gate's own bit. */
|
|
835
836
|
origin?: import("./ask-origin.js").AskOrigin;
|
|
836
|
-
/** #688 C3 (additive, same precedent as `origin`) — the mechanism behind an `origin: "rule_store_unavailable"`
|
|
837
|
-
* (`store` / `call`, {@link import("./ask-origin.js").RULE_STORE_UNREADABLE_KINDS}). Written only when it is a
|
|
838
|
-
* member of the set; a pre-existing row reads absent. Display metadata for the durable card. */
|
|
839
|
-
ruleStoreUnreadable?: import("./ask-origin.js").RuleStoreUnreadable;
|
|
840
837
|
/**
|
|
841
838
|
* design/80 D-1 §2 (slice 1a.2): the server-minted **opaque** boundInputHash of {@link args} — a
|
|
842
839
|
* SHA-256 (hex) via {@link import("./canonical-json.js").boundInputHashOf}, computed ONCE here at
|
|
@@ -871,6 +868,10 @@ export type PendingAction = {
|
|
|
871
868
|
*/
|
|
872
869
|
family?: import("./tool-face.js").ToolFamily;
|
|
873
870
|
pathTarget?: import("./tool-face.js").ToolRosterPathTarget;
|
|
871
|
+
/** The names a rule may spell the parked tool by (`ToolCallFace.names`: the wire name, then the aliases that resolve
|
|
872
|
+
* to it) off the same roster row at the same mint — the pre-CAS recheck judges an edit against rules written on
|
|
873
|
+
* either name, as the live gate judged the original. Absent on a row minted before it ⇒ the row's `toolName` alone. */
|
|
874
|
+
names?: readonly string[];
|
|
874
875
|
/**
|
|
875
876
|
* #438 (additive; no checkpoint-version bump — the `reminderMark` precedent: an optional field an
|
|
876
877
|
* older reader ignores. Unlike that precedent seat — which resume DOES read, through its
|
|
@@ -1098,7 +1099,7 @@ export interface CheckpointState {
|
|
|
1098
1099
|
* Parent effective-policy inheritance (tighten-only): the durable projection of the run's
|
|
1099
1100
|
* {@link import("./runner/contracts.js").InheritedGate}. Only the serializable DATA half persists —
|
|
1100
1101
|
* `ancestorRules` (each ancestor's session-rule snapshot: sessionId/principal/rev/rules, re-compiled on
|
|
1101
|
-
* resume against the resumed leg's env)
|
|
1102
|
+
* resume against the resumed leg's env), `shellGate` (the chain's operator floor) and `permissionMode`. The OPAQUE
|
|
1102
1103
|
* half (`parentConstraints` — live ToolPolicy/onAsk closures) CANNOT persist; `requiresParentConstraint`
|
|
1103
1104
|
* records only its PRESENCE, so the resume pre-CAS ladder can fail-loud (`resume.parent_constraint_missing`,
|
|
1104
1105
|
* checkpoint stays `pending`) when the resuming caller did not re-supply the closures via
|
|
@@ -1115,27 +1116,18 @@ export interface CheckpointState {
|
|
|
1115
1116
|
rules: SessionPermissionRules;
|
|
1116
1117
|
}>;
|
|
1117
1118
|
shellGate?: ShellGateDoctrine;
|
|
1118
|
-
/** The chain's
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1121
|
-
*
|
|
1122
|
-
*
|
|
1123
|
-
*
|
|
1124
|
-
*
|
|
1125
|
-
*
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
*
|
|
1129
|
-
|
|
1130
|
-
/** The chain's declared APPROVER POSTURE at suspend (data half, same law as `shellGate`): the word
|
|
1131
|
-
* the suspended leg was told to render — its own seat, the one its live chain carried, or the one an
|
|
1132
|
-
* earlier suspend of the same chain recorded. A resume leg reads it beside the re-supplied seat and
|
|
1133
|
-
* the waking chain, so a redemption in another process states the same posture the suspend leg did.
|
|
1134
|
-
* It authorizes nothing (the gate never reads it) and it renders nothing on a resume leg either —
|
|
1135
|
-
* the first-turn frame that carries it is already in the restored transcript; what it feeds is the
|
|
1136
|
-
* chain the resumed leg hands its OWN children. Absent on older checkpoints and on tasks that
|
|
1137
|
-
* declared no posture (byte-identical row). */
|
|
1138
|
-
approverPosture?: ApproverPosture;
|
|
1119
|
+
/** The chain's DECLARED permission mode at suspend (data half, same law as `shellGate`): the suspended leg's
|
|
1120
|
+
* own effective declaration — its request clamped under its parent's ceiling — before the deployment's fold.
|
|
1121
|
+
* A resume leg reads it as the CEILING its re-passed spec is clamped under (tighten-only, the shell floor's
|
|
1122
|
+
* law) and re-judges the deny bits and the classifier face for its own leg: a `bypassPermissions` row does not
|
|
1123
|
+
* revive the mode where the redeeming deployment denies it, and an `auto` row arms only where a face is
|
|
1124
|
+
* wired. Absent on a task that declared nothing and inherited nothing (`default`). Rows written before this
|
|
1125
|
+
* member carried `autoModeRequested` / `approverPosture` / a doctrine `shellGate`; the resume leg migrates
|
|
1126
|
+
* them once, announced (`resume.legacy_mode_seed_migrated`), and the next suspend writes this member. */
|
|
1127
|
+
permissionMode?: PermissionMode;
|
|
1128
|
+
/** Beside a recorded `plan`: the declared word the leg had when a steer or the model's `EnterPlanMode` moved it into
|
|
1129
|
+
* plan — the mode an approved or edited `plan_review` resumes under ({@link import("./mode-policy.js").modeRequestAfterResume}). */
|
|
1130
|
+
prePlanMode?: PermissionMode;
|
|
1139
1131
|
/** Org-memory admission freeze (ruled 2026-08-05): the chain's admitted org-scope set at
|
|
1140
1132
|
* suspend (data half, plain strings). The resume leg folds it seed ∩ live (tighten-only) and
|
|
1141
1133
|
* re-runs admission under it — a resume must never widen the delegation freeze. Absent on
|
|
@@ -1950,6 +1942,8 @@ export interface CheckpointSummary {
|
|
|
1950
1942
|
* OMITTED (the {@link toolCallId} absence-shape contract), screened to the literal `true` — a pre-#1079 row
|
|
1951
1943
|
* reads absent (unreported). ECHO-ONLY; nothing gates on it. */
|
|
1952
1944
|
mandated?: true;
|
|
1945
|
+
/** The row's `PendingAction.tool_approval.authoredBy`, echoed as the one literal. ECHO-ONLY. */
|
|
1946
|
+
authoredBy?: "deployment";
|
|
1953
1947
|
/** The row's `PendingAction.tool_approval.mandate`, echoed ONLY as a member of the closed set. ECHO-ONLY. */
|
|
1954
1948
|
mandate?: import("./ask-origin.js").PersistedRuleMandate;
|
|
1955
1949
|
/** The row's `PendingAction.tool_approval.denialLimitFallback`, projected BESIDE the preview
|
|
@@ -1962,9 +1956,6 @@ export interface CheckpointSummary {
|
|
|
1962
1956
|
* value is a member of the closed set ({@link import("./ask-origin.js").isAskOrigin}) — a bad value
|
|
1963
1957
|
* reads as absent, never as a word. OMITTED when the row carries none (a pre-existing row). */
|
|
1964
1958
|
origin?: import("./ask-origin.js").AskOrigin;
|
|
1965
|
-
/** The row's `PendingAction.tool_approval.ruleStoreUnreadable` (#688 C3), echoed ONLY when the stored word is a
|
|
1966
|
-
* member of the closed set ({@link import("./ask-origin.js").isRuleStoreUnreadable}); OMITTED otherwise. */
|
|
1967
|
-
ruleStoreUnreadable?: import("./ask-origin.js").RuleStoreUnreadable;
|
|
1968
1959
|
/**
|
|
1969
1960
|
* PRESENCE ONLY — this pending `tool_approval` row's payload carries at least one DIRECTIONAL
|
|
1970
1961
|
* format control (the {@link import("./tool-policy.js").AskRequest.hasBidiControls} character class;
|
|
@@ -3027,3 +3018,31 @@ export declare class InMemoryCheckpointStore implements CheckpointStore {
|
|
|
3027
3018
|
/** Test/inspection helper: number of stored checkpoints. */
|
|
3028
3019
|
get size(): number;
|
|
3029
3020
|
}
|
|
3021
|
+
/** What {@link migrateLegacyModeSeed} read off a checkpoint row's `inheritedGate`: the mode and the floor the resume
|
|
3022
|
+
* leg inherits, and — when the row carried a pre-mode key — what was migrated and what was dropped. */
|
|
3023
|
+
export interface LegacyModeSeedMigration {
|
|
3024
|
+
permissionMode?: PermissionMode;
|
|
3025
|
+
prePlanMode?: PermissionMode;
|
|
3026
|
+
shellGate?: ShellGateDoctrine;
|
|
3027
|
+
/** Present iff the row carried a retired key (`autoModeRequested` / `approverPosture` / a doctrine `shellGate`). */
|
|
3028
|
+
migrated?: {
|
|
3029
|
+
permissionMode?: PermissionMode;
|
|
3030
|
+
droppedKeys: readonly string[];
|
|
3031
|
+
};
|
|
3032
|
+
}
|
|
3033
|
+
/**
|
|
3034
|
+
* Read a row's mode seed ONCE, migrating the pre-7.34.0 keys the row may still carry. The matrix, key by key:
|
|
3035
|
+
* - `permissionMode` present ⇒ it must be a member of the closed set (else refused, `config.permission_mode_invalid`,
|
|
3036
|
+
* without reading the retired keys); the two retired mode keys beside it are DROPPED unvalidated (the new key is
|
|
3037
|
+
* the truth), `shellGate` follows the value rule below;
|
|
3038
|
+
* - `autoModeRequested`: absent / `false` ⇒ no mode from it; `true` ⇒ `auto`; anything else ⇒ refused
|
|
3039
|
+
* (`resume.legacy_mode_seed`);
|
|
3040
|
+
* - `approverPosture`: any value ⇒ dropped (it never authorized anything; the mode comes from the request / chain,
|
|
3041
|
+
* and a `"bypass"` posture beside `autoModeRequested: true` reads `auto` — a posture never becomes an executable
|
|
3042
|
+
* bypass);
|
|
3043
|
+
* - `shellGate`: absent ⇒ none; `"always"` ⇒ kept (a live key, not a retired encoding); `"off"` / `"classify"` ⇒
|
|
3044
|
+
* dropped (no floor); anything else ⇒ refused (`resume.legacy_mode_seed`).
|
|
3045
|
+
* Idempotent: a row the resume leg writes back carries `permissionMode` and the floor only, so a second read migrates
|
|
3046
|
+
* nothing. Migration precedes any retired-key door, so a window row is read rather than refused.
|
|
3047
|
+
*/
|
|
3048
|
+
export declare function migrateLegacyModeSeed(gate: object | undefined): LegacyModeSeedMigration;
|
|
@@ -3,11 +3,12 @@ import { uuidv7 } from "../internal/harness.js";
|
|
|
3
3
|
import { PROBE_CAUSE_PATH_MAX, PROBE_REASON_MAX, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
4
4
|
import { carriesBidiControls } from "./tool-policy.js";
|
|
5
5
|
import { readDenialLimitFallback } from "./auto-mode.js";
|
|
6
|
-
import { isAskOrigin, isPersistedRuleMandate
|
|
6
|
+
import { isAskOrigin, isPersistedRuleMandate } from "./ask-origin.js";
|
|
7
7
|
import { checkpointExecutionRecorded, executionRecordDisposition, ExecutionOutcomeConflictError, } from "./checkpoint-execution-record.js";
|
|
8
8
|
import { renderUntrustedCommandText, stripFormatCharacters } from "./permission-rule-model.js";
|
|
9
9
|
import { redactSecrets } from "./untrusted-egress.js";
|
|
10
10
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
11
|
+
import { isPermissionMode } from "./ask-origin.js";
|
|
11
12
|
export function mintCheckpointToken() {
|
|
12
13
|
return randomBytes(16).toString("hex");
|
|
13
14
|
}
|
|
@@ -298,15 +299,15 @@ export function summarizeCheckpoint(cp) {
|
|
|
298
299
|
...(tool?.previewWithheld === "oversize" || tool?.previewWithheld === "unavailable" ? { previewWithheld: tool.previewWithheld } : {}),
|
|
299
300
|
...(tool?.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
300
301
|
...(tool?.mandated === true ? { mandated: true } : {}),
|
|
302
|
+
...(tool?.authoredBy === "deployment" ? { authoredBy: "deployment" } : {}),
|
|
301
303
|
...(() => {
|
|
302
304
|
const fb = readDenialLimitFallback(tool?.denialLimitFallback);
|
|
303
305
|
return fb !== undefined ? { denialLimitFallback: fb } : {};
|
|
304
306
|
})(),
|
|
305
307
|
...(() => {
|
|
306
308
|
const origin = tool?.origin;
|
|
307
|
-
const ruleStoreUnreadable = tool?.ruleStoreUnreadable;
|
|
308
309
|
const mandate = tool?.mandate;
|
|
309
|
-
return { ...(isAskOrigin(origin) ? { origin } : {}), ...(
|
|
310
|
+
return { ...(isAskOrigin(origin) ? { origin } : {}), ...(isPersistedRuleMandate(mandate) ? { mandate } : {}) };
|
|
310
311
|
})(),
|
|
311
312
|
...(hasBidi ? { hasBidiControls: true } : {}),
|
|
312
313
|
};
|
|
@@ -732,3 +733,57 @@ export class InMemoryCheckpointStore {
|
|
|
732
733
|
return this.cps.size;
|
|
733
734
|
}
|
|
734
735
|
}
|
|
736
|
+
export function migrateLegacyModeSeed(gate) {
|
|
737
|
+
if (gate === undefined)
|
|
738
|
+
return {};
|
|
739
|
+
const raw = gate;
|
|
740
|
+
const refusal = (code, message) => {
|
|
741
|
+
const e = new Error(message);
|
|
742
|
+
e.code = code;
|
|
743
|
+
return e;
|
|
744
|
+
};
|
|
745
|
+
const dropped = [];
|
|
746
|
+
let permissionMode;
|
|
747
|
+
const declared = raw["permissionMode"];
|
|
748
|
+
if (declared !== undefined) {
|
|
749
|
+
if (!isPermissionMode(declared))
|
|
750
|
+
throw refusal("config.permission_mode_invalid", `the checkpoint row's inheritedGate.permissionMode is ${JSON.stringify(declared)}, not a permission mode — the row cannot be resumed under a mode this engine does not know`);
|
|
751
|
+
permissionMode = declared;
|
|
752
|
+
if (raw["autoModeRequested"] !== undefined)
|
|
753
|
+
dropped.push("autoModeRequested");
|
|
754
|
+
if (raw["approverPosture"] !== undefined)
|
|
755
|
+
dropped.push("approverPosture");
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
const intent = raw["autoModeRequested"];
|
|
759
|
+
if (intent === true) {
|
|
760
|
+
permissionMode = "auto";
|
|
761
|
+
dropped.push("autoModeRequested");
|
|
762
|
+
}
|
|
763
|
+
else if (intent !== undefined && intent !== false) {
|
|
764
|
+
throw refusal("resume.legacy_mode_seed", `the checkpoint row's inheritedGate.autoModeRequested is ${JSON.stringify(intent)}, which the one-time mode migration cannot read (only true / false / absent are) — the row is refused rather than resumed under a guessed mode`);
|
|
765
|
+
}
|
|
766
|
+
else if (intent === false) {
|
|
767
|
+
dropped.push("autoModeRequested");
|
|
768
|
+
}
|
|
769
|
+
if (raw["approverPosture"] !== undefined)
|
|
770
|
+
dropped.push("approverPosture");
|
|
771
|
+
}
|
|
772
|
+
const prePlan = raw["prePlanMode"];
|
|
773
|
+
if (prePlan !== undefined && (!isPermissionMode(prePlan) || prePlan === "plan"))
|
|
774
|
+
throw refusal("config.permission_mode_invalid", `the checkpoint row's inheritedGate.prePlanMode is ${JSON.stringify(prePlan)}, not a permission mode a plan can return to`);
|
|
775
|
+
const floor = raw["shellGate"];
|
|
776
|
+
let shellGate;
|
|
777
|
+
if (floor === "always")
|
|
778
|
+
shellGate = "always";
|
|
779
|
+
else if (floor === "off" || floor === "classify")
|
|
780
|
+
dropped.push("shellGate");
|
|
781
|
+
else if (floor !== undefined)
|
|
782
|
+
throw refusal("resume.legacy_mode_seed", `the checkpoint row's inheritedGate.shellGate is ${JSON.stringify(floor)}, which is neither the floor word "always" nor a retired doctrine word — the row is refused rather than resumed under a guessed floor`);
|
|
783
|
+
return {
|
|
784
|
+
...(permissionMode !== undefined ? { permissionMode } : {}),
|
|
785
|
+
...(prePlan !== undefined ? { prePlanMode: prePlan } : {}),
|
|
786
|
+
...(shellGate !== undefined ? { shellGate } : {}),
|
|
787
|
+
...(dropped.length > 0 ? { migrated: { ...(declared === undefined && permissionMode !== undefined ? { permissionMode } : {}), droppedKeys: dropped } } : {}),
|
|
788
|
+
};
|
|
789
|
+
}
|
|
@@ -2,6 +2,7 @@ export const CHILD_CLAMP_SEATS = ["handsReadOnly", "interactiveTools", "readFace
|
|
|
2
2
|
export const CHILD_CLAMP_SEATS_NOT_CARRIED = Object.freeze({
|
|
3
3
|
toolPolicy: "not a clamp a child spec copies: a parent's denies reach its children through the ancestor-constraint chain the child's own prepare folds, not by putting the parent's policy object on the child seat",
|
|
4
4
|
shellGate: "travels on the inherited-gate lane (`InheritedGate.shellGate`, a trusted internals seat), not on the child spec — a spec-level copy would be a second source for the same clamp",
|
|
5
|
+
permissionMode: "travels on the inherited-gate lane (`InheritedGate.permissionMode` / `permissionModeCeiling` — the parent's declared word and its LIVE seat): a child with no word of its own inherits that ceiling on every read, and a word it does declare is clamped under it; a spec-level copy would freeze the parent's word at spawn and be a second source for the same clamp",
|
|
5
6
|
onAsk: "per-lane by construction: the delegation lane wraps the parent's approver with spawn provenance before it becomes the child's seat, and a lane with no approver must hand down none",
|
|
6
7
|
hooks: "a deployment seat, not an inherited clamp — `tightenTaskSpec` refuses to merge two hook sets rather than composing them, so there is no tightening value to carry",
|
|
7
8
|
excludeTools: "roster control rather than containment: the lanes that copy a parent's roster do so beside these seats, and the workflow lane authors its child roster from the governance baseline instead of the host's",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE exit for an auto-mode classification that could not RULE (`unavailable`: the classifier leg failed, or its
|
|
3
|
+
* per-level budget ran out) — shared by both classifier stations, the leg's gate and an ancestor's frozen classifier
|
|
4
|
+
* in the policy fold, so the rule is written once.
|
|
5
|
+
*
|
|
6
|
+
* ONE fork, on the APPROVAL SEAT (does an ask this station hands on reach a person — a live approver, or a durable
|
|
7
|
+
* park): with one, the ask goes on, its text saying why auto mode is asking, and a person decides; without one, the
|
|
8
|
+
* call is refused with CC's `x1t` sentence and the classification's FORM (`unavailable`) is attested on the deny. The seat is
|
|
9
|
+
* the only input to the fork — not the cause, not the mode. The handed-on ask carries `requiresRealApproval`: no
|
|
10
|
+
* automatic lane may answer it in the classifier's stead — not a blanket `onAsk:"allow"`, not the sandbox admission, not a
|
|
11
|
+
* budget resolver over its park (the park mints the non-budgetable kind). CC refuses either way; asking where a person can be
|
|
12
|
+
* asked is a declared departure (a classifier that is slow or down must not turn an attended session into a string
|
|
13
|
+
* of refusals). Neither arm is a block: the denial limit counts classifier RULINGS, and a person's refusal of the
|
|
14
|
+
* handed-on ask is a person's, not the classifier's.
|
|
15
|
+
*
|
|
16
|
+
* The observer hears every such classification with where the call went, except while the call is being aborted — a halt,
|
|
17
|
+
* or the run's end (the classifier did not fail; the call ended). It is observe-only: a throw never changes the arm.
|
|
18
|
+
*/
|
|
19
|
+
import { type AutoModeClassifierRound, type AutoModeUnavailableCause } from "./auto-mode.js";
|
|
20
|
+
import { type PermissionResult, type ToolCallRequest } from "./tool-policy.js";
|
|
21
|
+
/** Where an `unavailable` classification sent the call: `asked` = on to the approval seat, `denied` = refused. */
|
|
22
|
+
export type ClassifierUnavailableDisposition = "asked" | "denied";
|
|
23
|
+
/** What the stations' observer receives — the facts the `classifier.unavailable` notice is composed from. */
|
|
24
|
+
export interface ClassifierUnavailableInfo {
|
|
25
|
+
toolName: string;
|
|
26
|
+
toolCallId: string;
|
|
27
|
+
cause: AutoModeUnavailableCause;
|
|
28
|
+
disposition: ClassifierUnavailableDisposition;
|
|
29
|
+
}
|
|
30
|
+
export type ClassifierUnavailableObserver = (info: ClassifierUnavailableInfo) => void;
|
|
31
|
+
/** The station's two facts for the fork: whether an ask it hands on reaches a person (`true` only — anything else is
|
|
32
|
+
* no seat), and who hears it. */
|
|
33
|
+
export interface ClassifierUnavailableSeat {
|
|
34
|
+
approvalSeat?: boolean;
|
|
35
|
+
onUnavailable?: ClassifierUnavailableObserver;
|
|
36
|
+
}
|
|
37
|
+
/** The arm the fork chose: the ask to hand on, or the refusal. */
|
|
38
|
+
export type ClassifierUnavailableArm<A> = {
|
|
39
|
+
kind: "asked";
|
|
40
|
+
ask: A;
|
|
41
|
+
} | {
|
|
42
|
+
kind: "denied";
|
|
43
|
+
deny: PermissionResult;
|
|
44
|
+
};
|
|
45
|
+
export declare function classifierUnavailableArm<A extends Extract<PermissionResult, {
|
|
46
|
+
action: "ask";
|
|
47
|
+
}>>(args: {
|
|
48
|
+
ask: A;
|
|
49
|
+
req: ToolCallRequest;
|
|
50
|
+
cause: AutoModeUnavailableCause;
|
|
51
|
+
/** The ladder's attribution, for the refusal's sentence (it names both rungs when the ladder moved). */
|
|
52
|
+
attribution?: AutoModeClassifierRound;
|
|
53
|
+
seat: ClassifierUnavailableSeat | undefined;
|
|
54
|
+
/** The call is being aborted (its per-call signal — a halt, or the run's end): the observer is not told (the arm is chosen
|
|
55
|
+
* as ever). */
|
|
56
|
+
aborted: boolean;
|
|
57
|
+
}): ClassifierUnavailableArm<A>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { classifierUnavailableAskMessage, classifierUnavailableDenyMessage } from "./auto-mode.js";
|
|
2
|
+
import { attestClassifierDenyCause } from "./tool-policy.js";
|
|
3
|
+
export function classifierUnavailableArm(args) {
|
|
4
|
+
const { ask, req, cause } = args;
|
|
5
|
+
const seated = args.seat?.approvalSeat === true;
|
|
6
|
+
if (!args.aborted) {
|
|
7
|
+
try {
|
|
8
|
+
args.seat?.onUnavailable?.({ toolName: req.toolName, toolCallId: req.toolCallId, cause, disposition: seated ? "asked" : "denied" });
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (seated)
|
|
14
|
+
return { kind: "asked", ask: { ...ask, message: classifierUnavailableAskMessage(ask.message ?? `approval required for "${req.toolName}"`, cause), requiresRealApproval: true } };
|
|
15
|
+
const deny = { action: "deny", message: classifierUnavailableDenyMessage(req.toolName, cause, args.attribution), decisionReason: "classifier" };
|
|
16
|
+
return { kind: "denied", deny: attestClassifierDenyCause(deny, "unavailable", req) };
|
|
17
|
+
}
|
|
@@ -101,3 +101,15 @@ export type PathTargetReading = {
|
|
|
101
101
|
* against names no file, and claiming it names `/<spelling>` was a fabrication no fence should judge.
|
|
102
102
|
*/
|
|
103
103
|
export declare function effectivePathTargetOf(call: ToolCallLike, pt: ToolCallPathTarget | undefined, bases: ToolPathBases): PathTargetReading;
|
|
104
|
+
/**
|
|
105
|
+
* The SELECTION half of {@link effectivePathTargetOf}, before any identity is applied: the spelling the tool opens and
|
|
106
|
+
* the base it resolves that spelling against — the slot's own value, or an absolute pattern's base directory — or,
|
|
107
|
+
* with `spelled` absent, the base itself (the tool searches all of it). A caller that resolves canonically (the tool,
|
|
108
|
+
* the read face's own station) reads this; the rule lanes read the lexical identity above. Same order as above.
|
|
109
|
+
*/
|
|
110
|
+
export declare function effectivePathSpellingOf(call: ToolCallLike, pt: ToolCallPathTarget | undefined, bases: ToolPathBases): {
|
|
111
|
+
readonly spelled?: string;
|
|
112
|
+
readonly base: string | undefined;
|
|
113
|
+
} | {
|
|
114
|
+
readonly refused: Win32NamespaceRefusal;
|
|
115
|
+
} | undefined;
|
|
@@ -23,26 +23,37 @@ export function splitAbsoluteGlobPattern(pattern, family) {
|
|
|
23
23
|
return { baseDir, relativePattern: spelled.slice(cut + 1) };
|
|
24
24
|
}
|
|
25
25
|
export function effectivePathTargetOf(call, pt, bases) {
|
|
26
|
+
const spelling = effectivePathSpellingOf(call, pt, bases);
|
|
27
|
+
if (spelling === undefined)
|
|
28
|
+
return undefined;
|
|
29
|
+
if ("refused" in spelling)
|
|
30
|
+
return { kind: "refused", reason: spelling.refused };
|
|
31
|
+
if (spelling.spelled !== undefined)
|
|
32
|
+
return resolveSpelling(spelling.spelled, spelling.base, bases);
|
|
33
|
+
const declaredBase = spelling.base;
|
|
34
|
+
if (declaredBase === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
const family = pathFamilyOf(bases);
|
|
37
|
+
return isAbsoluteForFamily(family, declaredBase) ? readingOf(pathIdentityOf(declaredBase, family, bases.root ?? bases.cwd)) : unreadableBase(declaredBase);
|
|
38
|
+
}
|
|
39
|
+
export function effectivePathSpellingOf(call, pt, bases) {
|
|
26
40
|
if (pt === undefined)
|
|
27
41
|
return undefined;
|
|
28
42
|
const declaredBase = pathTargetBaseOf(pt, bases);
|
|
29
43
|
const spelled = pathTargetValue(call.args, pt);
|
|
30
44
|
if (spelled !== undefined)
|
|
31
|
-
return
|
|
45
|
+
return { spelled, base: declaredBase };
|
|
32
46
|
if ((pt.absent ?? "none") === "none")
|
|
33
47
|
return undefined;
|
|
34
48
|
if (pt.patternParam !== undefined) {
|
|
35
49
|
const pattern = pathTargetValue(call.args, { param: pt.patternParam });
|
|
36
50
|
const split = pattern === undefined ? undefined : splitAbsoluteGlobPattern(pattern, pathFamilyOf(bases));
|
|
37
51
|
if (split !== undefined && "refused" in split)
|
|
38
|
-
return {
|
|
52
|
+
return { refused: split.refused };
|
|
39
53
|
if (split !== undefined)
|
|
40
|
-
return
|
|
54
|
+
return { spelled: split.baseDir, base: declaredBase };
|
|
41
55
|
}
|
|
42
|
-
|
|
43
|
-
return undefined;
|
|
44
|
-
const family = pathFamilyOf(bases);
|
|
45
|
-
return isAbsoluteForFamily(family, declaredBase) ? readingOf(pathIdentityOf(declaredBase, family, bases.root ?? bases.cwd)) : unreadableBase(declaredBase);
|
|
56
|
+
return { base: declaredBase };
|
|
46
57
|
}
|
|
47
58
|
function resolveSpelling(spelled, declaredBase, bases) {
|
|
48
59
|
const family = pathFamilyOf(bases);
|
|
@@ -222,6 +222,18 @@ export interface EngineNotice {
|
|
|
222
222
|
* ladder position (0 the seat, 1 the model behind it), since two rungs may share an id. A ruling from
|
|
223
223
|
* that rung resets its count; an `unavailable` round neither counts nor resets. Once per run per rung,
|
|
224
224
|
* and by construction before the denial limit's third block hands the user its card.
|
|
225
|
+
* - `"classifier.unavailable"` (`src/core/runner/classifier-ladder.ts`, `classifierUnavailableNotice`; minted by
|
|
226
|
+
* both classifier stations — the leg's gate and an ancestor's frozen classifier in the policy fold) — a
|
|
227
|
+
* classification could NOT RULE (the classifier leg failed, or its per-level budget
|
|
228
|
+
* `RunnerDeps.autoMode.timeoutMs` ran out) and says where the call went: `disposition: "asked"` = on down the
|
|
229
|
+
* ask chain to the leg's approval seat (a live approver or a durable park decides; the handed-on ask carries
|
|
230
|
+
* `requiresRealApproval`, so no automatic lane answers it; a declared departure from CC, which refuses either
|
|
231
|
+
* way), `"denied"` = refused with the classifier's own sentence (no approval seat).
|
|
232
|
+
* `detail: { toolName, toolCallId, cause, disposition, sessionId?, runId? }` — `cause` is the
|
|
233
|
+
* `AutoModeUnavailableCause` word. Once per classification that could not answer (a call judged by an ancestor's
|
|
234
|
+
* classifier and the leg's own can carry two); not minted while the call is being aborted (a halt, or the run's end). Audience
|
|
235
|
+
* `user`: on a lane with no approval face this line is the only place the user hears why the call did not
|
|
236
|
+
* run. It is not a block — the denial limit does not count it.
|
|
225
237
|
* - `"route.base_url_changed_key_unchanged"` (key↔URL pairing) — `Runner.swapDeps` moved a
|
|
226
238
|
* same-name entry's `baseUrl` while its Model-visible credential half (auth-bearing headers)
|
|
227
239
|
* did not change: legal (a provider changing domains), but worth one loud line — if the
|
|
@@ -281,6 +293,44 @@ export interface EngineNotice {
|
|
|
281
293
|
* secret-shaped keys from a spawned child's environment (a tool's shell, or a language server).
|
|
282
294
|
* Once per PROCESS, shared by every scrub site ({@link import("./secret-env.js").announceSecretEnvScrub});
|
|
283
295
|
* `detail: { names }` — every withheld key's name, never a value.
|
|
296
|
+
* - `"config.permission_mode_unavailable"` — the permission mode a task requested is not the mode the leg runs
|
|
297
|
+
* under: the deployment denies it (`by: "operator_deny"` — `RuntimeCaps.bypassPermissions` / `autoMode` is
|
|
298
|
+
* `false`), a requested `auto` has no classifier face (`by: "face_absent"`), or a child's declaration was folded
|
|
299
|
+
* under its parent's ceiling (`by: "inherited_ceiling"`, minted on the PARENT's stream with the child's
|
|
300
|
+
* `sessionId` beside the parent's — a child's own manifest is not shown to the person). The leg runs in
|
|
301
|
+
* `effective` (always `default` for the first two arms), announced once per leg and once per steer that folds;
|
|
302
|
+
* `detail: { requested, effective, by, sessionId, runId }`. Audience `user`.
|
|
303
|
+
* - `"config.permission_mode_invalid"`, `"config.spec_retired_key"`, `"config.shell_gate_value_retired"` and
|
|
304
|
+
* `"resume.legacy_mode_seed"` are ERROR codes, not notices (named here only to keep the mode family's spellings
|
|
305
|
+
* in one place): a `permissionMode` word outside the closed set (on the spec or on a checkpoint row), a spec
|
|
306
|
+
* carrying a retired mode key (`autoModeRequested`, `approverPosture`), a `shellGate` value other than
|
|
307
|
+
* `"always"`, and a checkpoint row whose pre-mode seed the migration cannot read. Each is a door refusal: the
|
|
308
|
+
* code rides the thrown error (the run fails, or the resume is refused) and never crosses
|
|
309
|
+
* {@link deliverEngineNotice}, so all four are deliberately absent from {@link ENGINE_NOTICE_CODES} /
|
|
310
|
+
* {@link NOTICE_AUDIENCE}, like `config.deps_retired_key`. A consumer diffing its own table against the
|
|
311
|
+
* catalog must not add a row for them; it reads them on the error's `code`.
|
|
312
|
+
* - `"permission.ask_released_by_mode"` — the `bypassPermissions` mode released a surviving ask instead of
|
|
313
|
+
* putting it to anyone; once per (mode, askClass) per run, `detail: { mode, askClass, origin?, mandate?, count,
|
|
314
|
+
* firstToolCallId, sessionId, runId }` — `count` is how many releases of that pair the run had made when the
|
|
315
|
+
* notice was minted (the first). The per-call record is `tool_end.gate.disposition.released`. Audience `user`.
|
|
316
|
+
* - `"permission.read_deny_unapplied"` — the mode released a call whose reads the shell read station could not
|
|
317
|
+
* establish (`unestablishable` / `declined`) while a read-deny table is in force: no row of the table was applied
|
|
318
|
+
* to what it reads (C-R124, "released and disclosed"). Every such call, `detail: { mode, toolCallId, tiers, rows,
|
|
319
|
+
* rowCount, sessionId, runId }` (`rows` = the first eight non-built-in rows). Audience `user`.
|
|
320
|
+
* - `"permission.rule_store_unavailable"` — the person's persisted permission rules could not be read (a read
|
|
321
|
+
* failure, a corrupt or unreadable store, a read that did not answer in time), so that read's rows were SKIPPED:
|
|
322
|
+
* none of their allow, ask or deny rows took part in the decisions it served — a deny row among them protected
|
|
323
|
+
* nothing meanwhile — and no card is raised for it. `detail.at` names the read that found it first: `"prepare"`
|
|
324
|
+
* (the path rows the run's file and shell checks take at run start — the next run reads them again) or `"call"`
|
|
325
|
+
* (one call's own rule check — every later call reads again, so the rows apply as soon as a read succeeds). Once
|
|
326
|
+
* per run, whichever read fails first; the trace keeps every read's line (`permission.rule_store_unreadable`).
|
|
327
|
+
* `detail: { at, sessionId, runId }`. Audience `user`.
|
|
328
|
+
* - `"resume.legacy_mode_seed_migrated"` — a checkpoint row written before the one-key mode (7.34.0) carried
|
|
329
|
+
* `autoModeRequested` / `approverPosture` / a doctrine `shellGate`; the resume leg read them once into
|
|
330
|
+
* `permissionMode` (`autoModeRequested: true` ⇒ `auto`; a posture is dropped — it never authorized anything;
|
|
331
|
+
* `shellGate` `"off"` / `"classify"` are dropped, `"always"` kept) and the next suspend writes the new member.
|
|
332
|
+
* `detail: { migrated: { permissionMode?, droppedKeys }, sessionId, runId }`. Audience `operator`. A row the
|
|
333
|
+
* migration cannot read refuses the resume instead (`resume.legacy_mode_seed`, the error-code row above).
|
|
284
334
|
* - `"config.durable_gate_unavailable"` — a durable-approval MANDATE is in force on a leg with NO
|
|
285
335
|
* checkpoint store: the per-principal `forceDurableGate` entitlement, or the task's own
|
|
286
336
|
* `durableApproval` opt-in with no live approver seat (`durableMandateSourceOf`, the predicate the
|
|
@@ -686,8 +736,9 @@ export interface EngineNotice {
|
|
|
686
736
|
* facility (an in-fold deny, no settlement on the frame). The deny itself is unchanged (the
|
|
687
737
|
* fail-closed sentence as the tool result); this is its person-facing half, minted at the gate's ONE
|
|
688
738
|
* deny exit, once per tool call, by `ask-unresolvable-notice.ts`. `detail: { sessionId, toolName,
|
|
689
|
-
* toolCallId, cause, parkLaneExisted }` — `parkLaneExisted` says whether a park lane was armed and
|
|
690
|
-
* declined/failed (`true`) or none existed (`false`, the usual shape)
|
|
739
|
+
* toolCallId, cause, parkLaneExisted, remedy? }` — `parkLaneExisted` says whether a park lane was armed and
|
|
740
|
+
* declined/failed (`true`) or none existed (`false`, the usual shape); `remedy` (the headless auto-deny only)
|
|
741
|
+
* is the one sentence naming the caller-side change that would have let the call run, as the tool text carries it. Audience `"user"`: the refused
|
|
691
742
|
* work is the session user's own; the operator hears it through the sink like every code.
|
|
692
743
|
*
|
|
693
744
|
* - `"memory.consolidation_recommended"` (design/339 §2.2/§6.2) — the engine-minted per-scope
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExecutionEnv } from "../internal/harness-types.js";
|
|
2
|
-
import type { ToolPolicy } from "./tool-policy.js";
|
|
2
|
+
import type { PermissionResult, ToolPolicy } from "./tool-policy.js";
|
|
3
3
|
/** design/388 B22 — the identity triple the exemption probe hands the store beside the name. */
|
|
4
4
|
export interface ToolCallIdentity {
|
|
5
5
|
capabilityId: string;
|
|
@@ -31,6 +31,10 @@ export interface FsWriteGatePolicyOptions {
|
|
|
31
31
|
* to the ask-resolution chain (live onAsk / durable park / headless deny); `"allow"` is the
|
|
32
32
|
* acceptEdits-mode shape (domain checks still run so a sensitive-path deny composes unchanged). */
|
|
33
33
|
defaultWrite: "ask" | "allow";
|
|
34
|
+
/** The ask the default verdict mints in place of the gate's ordinary write question — a permission mode's own
|
|
35
|
+
* sentence over the resolved `path`, with the gate's domain note (a configured domain that was not honored) to
|
|
36
|
+
* append when there is one. Read only where `defaultWrite` is `"ask"`. */
|
|
37
|
+
defaultAsk?: (path: string, note: string | undefined) => PermissionResult;
|
|
34
38
|
/**
|
|
35
39
|
* Session-exemption lookup ("allow all for this session" grants recorded by the deployment's approval
|
|
36
40
|
* surface). Consulted ONCE per covered write, after the domain checks and immediately BEFORE the gate
|
|
@@ -109,7 +109,10 @@ export function createFsWriteGatePolicy(opts) {
|
|
|
109
109
|
}
|
|
110
110
|
if (defaultWrite === "allow")
|
|
111
111
|
return { action: "allow" };
|
|
112
|
-
|
|
112
|
+
const note = refusedDomain === undefined ? undefined : ` — note: the configured write domain ${refusedDomain} was not honored; a domain that holds the root would exempt every write under it`;
|
|
113
|
+
if (opts.defaultAsk !== undefined)
|
|
114
|
+
return opts.defaultAsk(path, note);
|
|
115
|
+
return ask(`approve write to "${path}"?${advisory}${note ?? ""}`);
|
|
113
116
|
},
|
|
114
117
|
};
|
|
115
118
|
}
|
package/dist/core/gate-fold.js
CHANGED
|
@@ -50,7 +50,7 @@ export async function runGateFold(pass) {
|
|
|
50
50
|
}
|
|
51
51
|
if ((pass.decision.action === "allow" || pass.decision.action === "ask") && input.shellReadCheck !== undefined) {
|
|
52
52
|
const station = input.shellReadCheck;
|
|
53
|
-
const judged = station.judge(
|
|
53
|
+
const judged = station.judge(pass.req, pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput, input.trackedCwd?.(), pass.callSignal ?? input.abortSignal, { toolCallId });
|
|
54
54
|
if (judged !== null) {
|
|
55
55
|
let verdict;
|
|
56
56
|
try {
|
|
@@ -61,7 +61,7 @@ export async function runGateFold(pass) {
|
|
|
61
61
|
if (kind !== "aborted" && input.abortSignal?.aborted !== true) {
|
|
62
62
|
traceHookCrash(input, new Error(`shell read station ${kind === "timeout" ? "timed out" : "threw"} for tool "${toolName}" (toolCallId ${toolCallId}) — where this call reads could not be established: ${thrownText(err)}`), notifier);
|
|
63
63
|
}
|
|
64
|
-
verdict = station.unanswered(
|
|
64
|
+
verdict = station.unanswered(pass.req, kind === "timeout" ? "the read station timed out" : "the read station could not answer");
|
|
65
65
|
}
|
|
66
66
|
applyShellReadVerdict(pass, verdict, toolName, input.shellGated === true);
|
|
67
67
|
}
|
|
@@ -175,6 +175,14 @@ function shellReadAskMessage(toolName, finding) {
|
|
|
175
175
|
}
|
|
176
176
|
function applyShellReadVerdict(pass, verdict, toolName, shellGated) {
|
|
177
177
|
const account = verdict.sentence !== undefined && verdict.sentence.trim().length > 0 ? inlineUntrusted(verdict.sentence, PROBE_REASON_MAX) : undefined;
|
|
178
|
+
if (verdict.writesPlaced === true)
|
|
179
|
+
pass.shellWritesPlaced = true;
|
|
180
|
+
if (verdict.writeProtected === true)
|
|
181
|
+
pass.writeProtected = true;
|
|
182
|
+
if (verdict.unplacedOptions !== undefined)
|
|
183
|
+
pass.shellUnplacedOptions = verdict.unplacedOptions;
|
|
184
|
+
if (verdict.finding === "unestablishable" || verdict.finding === "declined" || verdict.unestablished === true)
|
|
185
|
+
pass.shellReadUnestablished = true;
|
|
178
186
|
switch (verdict.disposition) {
|
|
179
187
|
case "vouch":
|
|
180
188
|
return;
|
|
@@ -207,16 +215,24 @@ function applyShellReadVerdict(pass, verdict, toolName, shellGated) {
|
|
|
207
215
|
return;
|
|
208
216
|
}
|
|
209
217
|
case "ordinary": {
|
|
218
|
+
verdict.admit?.();
|
|
219
|
+
if (verdict.details !== undefined)
|
|
220
|
+
pass.shellReadDetails = verdict.details;
|
|
210
221
|
if (pass.decision.action === "allow") {
|
|
222
|
+
const candidate = normalizeReadRootCandidate(verdict.readRootCandidate);
|
|
211
223
|
pass.decision = {
|
|
212
224
|
action: "ask",
|
|
213
225
|
message: shellReadAskMessage(toolName, verdict.finding),
|
|
214
226
|
decisionReason: "safety",
|
|
215
227
|
...(account !== undefined ? { probeReason: account } : {}),
|
|
228
|
+
...(candidate !== undefined ? { probeReadRootCandidate: candidate } : {}),
|
|
216
229
|
};
|
|
230
|
+
if (candidate !== undefined)
|
|
231
|
+
pass.shellReadOffer = true;
|
|
217
232
|
pass.tightenedBy = shellGated ? "shell_gate" : "safety";
|
|
218
233
|
}
|
|
219
|
-
|
|
234
|
+
if (verdict.finding === "declined")
|
|
235
|
+
pass.probeBoundaryDeclined = true;
|
|
220
236
|
return;
|
|
221
237
|
}
|
|
222
238
|
default: {
|