@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
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
*/
|
|
35
35
|
import type { AssistantMessage, Model } from "../../internal/llm.js";
|
|
36
36
|
import { type AutoModeClassified } from "../auto-mode.js";
|
|
37
|
-
import type {
|
|
37
|
+
import type { ClassifierUnavailableInfo, ClassifierUnavailableSeat } from "../classifier-unavailable-arm.js";
|
|
38
|
+
import type { EngineNotice, RunnerDeps } from "../types.js";
|
|
38
39
|
/**
|
|
39
40
|
* The rungs a classification walks, in order: the classifier seat, and — under `classifierFallback: "main"` (the
|
|
40
41
|
* default) — the run's main model behind it. The knob is screened by `autoModeClassifierFallbackOf`, here and
|
|
@@ -108,6 +109,47 @@ export declare function announceClassifierRoleDefaulted(args: {
|
|
|
108
109
|
sessionId?: string;
|
|
109
110
|
runId?: string;
|
|
110
111
|
}): void;
|
|
112
|
+
/**
|
|
113
|
+
* The `classifier.unavailable` notice — a classification that could not RULE (the classifier leg failed, or its
|
|
114
|
+
* per-level budget ran out) and where the call went: `"asked"` = on down the ask chain to this leg's approval seat (a
|
|
115
|
+
* person or a durable park decides; the handed-on ask carries `requiresRealApproval`, so no automatic lane answers it),
|
|
116
|
+
* `"denied"` = refused (no approval seat: nobody to ask). Audience `user`: the refusal is the session user's own
|
|
117
|
+
* call, and on a lane with no approval face (`-p`) this line is the only place they hear why it did not run. Once
|
|
118
|
+
* per classification that could not answer, from both classifier stations (the leg's gate and an ancestor's frozen
|
|
119
|
+
* classifier in the policy fold) — a call judged by more than one classifier can carry one line per classifier. It is never a block: the denial limit does not count it, so this line is not a step toward the
|
|
120
|
+
* limit's card either.
|
|
121
|
+
*
|
|
122
|
+
* `detail: { toolName, toolCallId, cause, disposition, sessionId?, runId? }` — `cause` is the
|
|
123
|
+
* `AutoModeUnavailableCause` word (`timeout` / `error`). The sentence names the seat the station forked on — `leg` (this
|
|
124
|
+
* run's own approval seat) or `ancestor` (the frozen approver of the ancestor layer whose classifier it was) — and, on a
|
|
125
|
+
* timeout, the budget the classifier actually held (`budgetMs`, the decider's {@link AutoModeDecider.timeoutMs}; a
|
|
126
|
+
* hand-built decider states none, and the sentence names the knob alone).
|
|
127
|
+
*/
|
|
128
|
+
export declare function classifierUnavailableNotice(args: ClassifierUnavailableInfo & ClassifierUnavailableStation & {
|
|
129
|
+
sessionId?: string;
|
|
130
|
+
runId?: string;
|
|
131
|
+
}): EngineNotice;
|
|
132
|
+
/** A classifier station's fork facts, wired once per leg: whether an ask the station hands on reaches a person, and
|
|
133
|
+
* the `classifier.unavailable` notice through the one throat. Both stations (the gate, the inherited fold) take it. */
|
|
134
|
+
export declare function classifierUnavailableSeat(args: ClassifierUnavailableStation & {
|
|
135
|
+
approvalSeat: boolean;
|
|
136
|
+
onNotice: RunnerDeps["onNotice"];
|
|
137
|
+
sessionId?: string;
|
|
138
|
+
runId?: string;
|
|
139
|
+
}): ClassifierUnavailableSeat;
|
|
140
|
+
/** Which station forked (`leg` — the leg's gate, on its own approval seat; `ancestor` — an ancestor's frozen classifier in
|
|
141
|
+
* the policy fold, on that ancestor's frozen approver) and the per-level budget its classifier held, when stated.
|
|
142
|
+
*
|
|
143
|
+
* ONE question at both stations — does the route a handed-on ask takes from HERE end at a person — asked of two routes:
|
|
144
|
+
* the leg's gate hands its ask down the leg's own chain (`askReachesPerson(wiring_manifest.ask)`: a live approver, or the
|
|
145
|
+
* durable park); the inherited station resolves at the ancestor's frozen approver, whose only road to a person is being
|
|
146
|
+
* a live function (`isLiveApproverSeat`: a blanket or absent seat there ends in a refusal, and the leg's park is reached
|
|
147
|
+
* only through a live approver's `unavailable`). Reading the leg's route at the inherited station would hand the ask to a
|
|
148
|
+
* frozen seat that cannot answer it. */
|
|
149
|
+
export interface ClassifierUnavailableStation {
|
|
150
|
+
station: "leg" | "ancestor";
|
|
151
|
+
budgetMs?: number;
|
|
152
|
+
}
|
|
111
153
|
/**
|
|
112
154
|
* The classifier's REASONING SEAT, read off the model a classification is actually asked on — ONE predicate:
|
|
113
155
|
* does the wire SPELL off ({@link thinkingOffSpelled})? Yes ⇒ `off` + the verdict-sized cap (the request pays
|
|
@@ -94,6 +94,35 @@ export function announceClassifierRoleDefaulted(args) {
|
|
|
94
94
|
...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}),
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
|
+
export function classifierUnavailableNotice(args) {
|
|
98
|
+
const whose = args.station === "ancestor" ? "the classifier of the ancestor layer that asked" : "the classifier";
|
|
99
|
+
const why = args.cause === "timeout"
|
|
100
|
+
? `${whose} did not answer within its per-level budget (${args.budgetMs !== undefined ? `${args.budgetMs} ms, ` : ""}RunnerDeps.autoMode.timeoutMs)`
|
|
101
|
+
: `${whose} could not run (error)`;
|
|
102
|
+
const next = args.disposition === "asked"
|
|
103
|
+
? args.station === "ancestor"
|
|
104
|
+
? "so the call was handed on to that layer's approver rather than refused by the classifier."
|
|
105
|
+
: "so the call was handed on to a person (a live approver or a durable park) rather than refused by the classifier."
|
|
106
|
+
: (args.station === "ancestor" ? "and that layer has no live approver" : "and this run has no approval seat") +
|
|
107
|
+
", so the call was refused and did not run." +
|
|
108
|
+
(args.cause === "timeout" ? " A deployment on a slow gateway raises the budget." : "");
|
|
109
|
+
return {
|
|
110
|
+
code: "classifier.unavailable",
|
|
111
|
+
message: `auto mode could not judge "${args.toolName}": ${why}, ${next}`,
|
|
112
|
+
detail: {
|
|
113
|
+
toolName: args.toolName,
|
|
114
|
+
toolCallId: args.toolCallId,
|
|
115
|
+
cause: args.cause,
|
|
116
|
+
disposition: args.disposition,
|
|
117
|
+
...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}),
|
|
118
|
+
...(args.runId !== undefined ? { runId: args.runId } : {}),
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export function classifierUnavailableSeat(args) {
|
|
123
|
+
const { approvalSeat, onNotice, sessionId, runId, station, budgetMs } = args;
|
|
124
|
+
return { approvalSeat, onUnavailable: (info) => deliverEngineNotice(onNotice, classifierUnavailableNotice({ ...info, station, ...(budgetMs !== undefined ? { budgetMs } : {}), ...(sessionId !== undefined ? { sessionId } : {}), ...(runId !== undefined ? { runId } : {}) })) };
|
|
125
|
+
}
|
|
97
126
|
function classifierFallbackAnnouncer(args) {
|
|
98
127
|
const announced = new Set();
|
|
99
128
|
return (attribution) => {
|
|
@@ -22,6 +22,11 @@ import type { Model } from "../../internal/llm.js";
|
|
|
22
22
|
import type { WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
23
23
|
import type { ToolManifestRow } from "../../prompt-assembly/tool-catalog.js";
|
|
24
24
|
import type { CwdRef, ContainmentFace } from "../../tools/fs/index.js";
|
|
25
|
+
/** A leg's WRITE face: the face in force (read per call, following the mode's face) and whether a seat or a clamp DECLARED the fence (a checkpoint row records it; a resume keeps it). */
|
|
26
|
+
export interface WriteFaceSeat {
|
|
27
|
+
readonly inForce: () => ContainmentFace;
|
|
28
|
+
readonly fenced: boolean;
|
|
29
|
+
}
|
|
25
30
|
import type { MaterializedA2a } from "../a2a.js";
|
|
26
31
|
import type { CompactionForkContext, CompactionPhaseDurations, MaybeCompactOptions, RapidRefillState } from "../auto-compaction.js";
|
|
27
32
|
import type { AutoModeArmingRecipe } from "../auto-mode-arming.js";
|
|
@@ -47,7 +52,8 @@ import type { SystemInjectionPriority, TaskNotificationPayload } from "../task-n
|
|
|
47
52
|
import type { OnAsk, PermissionResult, ResolvedAsk, ToolCallRequest, ToolPolicy } from "../tool-policy.js";
|
|
48
53
|
import type { RemoteExecutionEnv, SnapshotId, WorkspaceHandle } from "../remote-env.js";
|
|
49
54
|
import type { ToolDisclosureManifest } from "../trace.js";
|
|
50
|
-
import type { AgentDefinition,
|
|
55
|
+
import type { AgentDefinition, Brain, CompactOutcome, ModelRef, ModelRoles, NestedUsageAccum, PermissionMode, RunnerDeps, ShellGateDoctrine, TaskEvent, TaskEventIdentity, TaskResult, TaskSpec, TaskStream, ToolActivity, ToolEffect } from "../types.js";
|
|
56
|
+
import type { PermissionModeController } from "../mode-policy.js";
|
|
51
57
|
import type { WiringManifest } from "../wiring-manifest.js";
|
|
52
58
|
import type { ActiveSkillFrame } from "./active-skill-scope.js";
|
|
53
59
|
import type { BudgetAxis } from "./assemble-result.js";
|
|
@@ -275,10 +281,11 @@ export interface Prepared {
|
|
|
275
281
|
reminderDisclosureCounts: import("../reminder-disclosure.js").ReminderDisclosureCounts;
|
|
276
282
|
/** Whether the full shell is on this leg's roster (the inherited-gate phase's bit, asserted against the mount). */
|
|
277
283
|
fullShellReachable: boolean;
|
|
278
|
-
/** The run's
|
|
279
|
-
*
|
|
280
|
-
* reminder; nothing else reads them, and no gate decision keys on either. */
|
|
284
|
+
/** The run's approval-posture facts as prepared — the effective permission mode and the ask seam's effective
|
|
285
|
+
* answer. The first user turn renders them into the engine's own marked reminder; no gate decision keys on them. */
|
|
281
286
|
approvalPosture: import("../auto-mode-directive.js").ApprovalPostureFacts;
|
|
287
|
+
/** The leg's live permission-mode seat: the effective mode every mode consumer reads, the steer verb writes. */
|
|
288
|
+
permissionMode: PermissionModeController;
|
|
282
289
|
/** The ISOLATION-AWARE working-tree root for this task (a worktree's cwd when `isolation: "worktree"`, else
|
|
283
290
|
* `deps.rootPath ?? executionEnv.cwd`) — the same value the hands/LSP/policy/restore use. The Runner's
|
|
284
291
|
* rewind/snapshot path MUST key off THIS, not `deps.rootPath`, or a worktree-isolated turn snapshots the base
|
|
@@ -413,9 +420,9 @@ export interface Prepared {
|
|
|
413
420
|
denyNarrowingPolicy?: ToolPolicy;
|
|
414
421
|
/** The person's own persisted-rule lane (deny/ask rows), re-adjudicated on RESUME at the same
|
|
415
422
|
* station as the deny-narrowing layers: a deny row added while the call was parked vetoes the execution
|
|
416
|
-
* (`deniedBy: "persisted_rule"`)
|
|
417
|
-
*
|
|
418
|
-
* no rule store is wired. */
|
|
423
|
+
* (`deniedBy: "persisted_rule"`), and so does a call the lane could not read against the rows (re-issue and
|
|
424
|
+
* re-approve); an ask row does not (the approval is its answer), and a store that could not be read
|
|
425
|
+
* contributes no row. Absent when no rule store is wired. */
|
|
419
426
|
persistedRuleLane?: {
|
|
420
427
|
adjudicate: (req: ToolCallRequest) => Promise<import("../hooks.js").PersistedRuleAnswer>;
|
|
421
428
|
};
|
|
@@ -1019,12 +1026,6 @@ export interface Prepared {
|
|
|
1019
1026
|
* `drain` and calls `releaseRun` with it at the run's terminal. */
|
|
1020
1027
|
runIdent: string;
|
|
1021
1028
|
};
|
|
1022
|
-
/** The live plan-mode flag (`enter_plan_mode` flips it, run-local one-way). Exposed
|
|
1023
|
-
* so the run loop's plan-mode attachment producer reads the SAME flag the write-deny enforces —
|
|
1024
|
-
* never a second source of truth. Always present (`active:false` when plan mode is unused). */
|
|
1025
|
-
planModeRef: {
|
|
1026
|
-
active: boolean;
|
|
1027
|
-
};
|
|
1028
1029
|
/** The ctx `requestStopAfterTurn` primitive's run-local mirror (one-way, set when a tool asked for a
|
|
1029
1030
|
* clean stop after the current turn). The harness latch itself is private to the loop; the turn
|
|
1030
1031
|
* boundary reads THIS to know that no next request follows — a boundary bundle collected there
|
|
@@ -1271,6 +1272,9 @@ export interface PrepareResume {
|
|
|
1271
1272
|
/** The §4.bis correctness-state snapshot to re-seed (activeTools / outputRef / nestedStats /
|
|
1272
1273
|
* readFileState). */
|
|
1273
1274
|
seed: CheckpointState;
|
|
1275
|
+
/** The decision this resume carries (the gate word and, for a review, approve / edit / reject) — read by the mode seed
|
|
1276
|
+
* (`modeRequestAfterResume`: an approved or edited `plan_review` exits a recorded plan to its pre-plan word). Absent on a prepare that resumes nothing. */
|
|
1277
|
+
outcome?: ResumeRun["outcome"];
|
|
1274
1278
|
/** How many times this task already suspended (the resumed checkpoint's
|
|
1275
1279
|
* `suspendCount`). The next suspend mints `priorSuspendCount + 1`; past `maxSuspends` it fails
|
|
1276
1280
|
* (`suspend.loop`) instead of re-suspending. Absent/0 ⇒ no prior suspends. */
|
|
@@ -1316,11 +1320,12 @@ export interface PrepareResume {
|
|
|
1316
1320
|
* Parent effective-policy inheritance (tighten-only, {@link RunInternals.inheritedGate}): the spawning
|
|
1317
1321
|
* chain's already-evaluated FINAL gate, split into two halves —
|
|
1318
1322
|
*
|
|
1319
|
-
* - **Data half** (`ancestorRules`, `shellGate`): serializable snapshots. Each ancestor's per-session
|
|
1323
|
+
* - **Data half** (`ancestorRules`, `shellGate`, `permissionMode`): serializable snapshots. Each ancestor's per-session
|
|
1320
1324
|
* permission rules ride as (sessionId, principal, rev, rules) so the child's prepare can re-read them
|
|
1321
1325
|
* LIVE (rev-monotonic — a lagging replica never loosens the snapshot) and re-compile them against the
|
|
1322
|
-
* CHILD's own env/root/effects as extra deny-narrowing layers. `shellGate`
|
|
1323
|
-
* (
|
|
1326
|
+
* CHILD's own env/root/effects as extra deny-narrowing layers. `shellGate` is the operator floor
|
|
1327
|
+
* (`"always"`): a child inherits it and cannot lift it. `permissionMode` is the ceiling a child's own
|
|
1328
|
+
* declaration is clamped under.
|
|
1324
1329
|
* - **Opaque half** (`parentConstraints`): each ancestor's live caller `ToolPolicy` plus its FROZEN
|
|
1325
1330
|
* `onAsk`, ancestors first — and, for a hook-wired ancestor, a SECOND entry: its PreToolUse screening
|
|
1326
1331
|
* face folded into a `ToolPolicy` (`createPreToolUseConstraintPolicy`), carrying its own frozen
|
|
@@ -1346,30 +1351,25 @@ export interface InheritedGate {
|
|
|
1346
1351
|
gen?: number;
|
|
1347
1352
|
rules: SessionPermissionRules;
|
|
1348
1353
|
}>;
|
|
1349
|
-
/** The chain's
|
|
1354
|
+
/** The chain's shell floor (`"always"`) — a child inherits it; its own spec cannot lift it. */
|
|
1350
1355
|
shellGate?: ShellGateDoctrine;
|
|
1351
1356
|
/**
|
|
1352
|
-
* The chain's
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1355
|
-
*
|
|
1356
|
-
*
|
|
1357
|
-
* model-authored argument); absent ⇒ the child is an auto-mode task only if its own spec says so.
|
|
1358
|
-
* Persists on the checkpoint's data half (`CheckpointState.inheritedGate.autoModeRequested`) so a
|
|
1359
|
-
* redemption in another process — no seat re-passed, no waking chain — reads the same intent the
|
|
1360
|
-
* suspend leg had; the intent folds by OR across seat, live chain and seed, while the deny bit and
|
|
1361
|
-
* the deployment face stay per-leg.
|
|
1357
|
+
* The chain's EFFECTIVE permission mode at spawn (data half, session-wide like every mode): the CEILING a
|
|
1358
|
+
* child's own declaration is clamped under (`TaskSpec.permissionMode` absent ⇒ inherit; declared ⇒ adopted only
|
|
1359
|
+
* where the rank table admits it, else folded and disclosed). Persists on the checkpoint's data half
|
|
1360
|
+
* (`CheckpointState.inheritedGate.permissionMode`) so a redemption in another process resumes under the same
|
|
1361
|
+
* declared mode — and re-judges the deployment's deny bits for its own leg.
|
|
1362
1362
|
*/
|
|
1363
|
-
|
|
1363
|
+
permissionMode?: PermissionMode;
|
|
1364
1364
|
/**
|
|
1365
|
-
* The
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
1368
|
-
* and no gate lane reads it; absent ⇒ the child declares a posture only if its own spec does. Persists
|
|
1369
|
-
* on the checkpoint's data half so a redemption elsewhere reads the posture the suspend leg was under;
|
|
1370
|
-
* the sources fold seat-first (own seat, then the live chain, then the seed).
|
|
1365
|
+
* The parent's LIVE mode seat (opaque half; never serialized): the child's effective mode is DERIVED from it on
|
|
1366
|
+
* every read, so a parent's steer reaches a running child on the child's next arbitration without a forwarding
|
|
1367
|
+
* frame. `sessionId` is the parent's, the address of the user notice a folded child declaration is announced on.
|
|
1371
1368
|
*/
|
|
1372
|
-
|
|
1369
|
+
permissionModeCeiling?: {
|
|
1370
|
+
readonly current: PermissionMode;
|
|
1371
|
+
readonly sessionId: string;
|
|
1372
|
+
};
|
|
1373
1373
|
/**
|
|
1374
1374
|
* Org-memory admission freeze (ruled 2026-08-05): the spawning chain's FROZEN admitted org-scope
|
|
1375
1375
|
* set — every org memory scope the parent actually mounted (deployment-origin + admitted request).
|
|
@@ -2780,6 +2780,8 @@ export interface LiveHandle {
|
|
|
2780
2780
|
runId: string;
|
|
2781
2781
|
hookTimeoutMs: number;
|
|
2782
2782
|
hookIdentity: HookInvocationIdentity;
|
|
2783
|
+
/** The leg's live permission-mode seat (`TaskStream.setPermissionMode` steers it). */
|
|
2784
|
+
permissionMode: PermissionModeController;
|
|
2783
2785
|
}
|
|
2784
2786
|
/** The run body's backstop CARRIER: the effective ids and the post-prepare
|
|
2785
2787
|
* observations the run body publishes as they are minted, so the stream layer's failure backstop names the
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type AutoModeDecider, type AutoModeDenialLimitOptions, type AutoModeDenialTracker, type DenialLimitFallback, type UnarmedDenialLimitFallback } from "../auto-mode.js";
|
|
2
2
|
import { type AutoModeArmingRecipe } from "../auto-mode-arming.js";
|
|
3
|
+
import { type ClassifierUnavailableSeat } from "../classifier-unavailable-arm.js";
|
|
3
4
|
import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "../tool-policy.js";
|
|
4
5
|
import { type EngineNotice } from "../types.js";
|
|
5
|
-
import { type AskOrigin, type
|
|
6
|
+
import { type AskOrigin, type AskOriginFacts } from "../ask-origin.js";
|
|
6
7
|
import type { Prepared, StopForDenialLimit } from "./contracts.js";
|
|
7
8
|
/**
|
|
8
9
|
* The classifier DENIAL-LIMIT arms of the tool gate's inherited (delegation) lane, extracted
|
|
@@ -83,13 +84,29 @@ export declare function createDenialLimitStop(opts: {
|
|
|
83
84
|
export type InheritedAsk = Extract<PermissionResult, {
|
|
84
85
|
action: "ask";
|
|
85
86
|
}>;
|
|
87
|
+
/**
|
|
88
|
+
* The delegation half of the live gate's classifier eligibility: the SAME class row (`classifyAsk` over the
|
|
89
|
+
* ask's own members) answers for the FROZEN classifier — an ask raised by a hook, minted by an
|
|
90
|
+
* explicit `ask` permission rule, or already a denial-limit fallback is exactly as out of the frozen
|
|
91
|
+
* decider's reach as the live one's, because the person's / hook's standing decision travels with the
|
|
92
|
+
* frozen entry and a frozen decider resolving it would re-open the same hole one process over. The
|
|
93
|
+
* three GATE-HELD facts of the derivation are not this station's to judge: an ancestor's frozen
|
|
94
|
+
* classifier runs inside the policy fold, BEFORE the live gate's org layer has spoken and before its
|
|
95
|
+
* question-tool and marked-call lanes run, so they read as absent here and those asks meet their
|
|
96
|
+
* exclusions downstream, where the facts exist. Excluded asks fall through to the frozen approver
|
|
97
|
+
* chain, the delegation lane's original chain. The word is derived inside {@link judgeInheritedClassifier}
|
|
98
|
+
* — once off the incoming ask for the exclusion and the unchanged-ask branches, once off the re-spoken
|
|
99
|
+
* ask this station mints at the bound — and threaded to the request mint, never re-read off the
|
|
100
|
+
* caller-owned object there.
|
|
101
|
+
* The MANDATE marks are empty for the same reason — no tool seat of this task, no tighten of its own;
|
|
102
|
+
* what the ancestor's ask carries on ITSELF still answers, read off the decision by the one predicate.
|
|
103
|
+
*/
|
|
104
|
+
export declare const INHERITED_STATION_FACTS: AskOriginFacts;
|
|
86
105
|
/** The members of a surviving ask that ride onto the approval request with a COMPUTED value — what the
|
|
87
106
|
* four request mint stations (the gate's own and the three inherited-lane ones) spread after the seats
|
|
88
107
|
* they spell themselves: the ask's origin word and its denial-limit fallback. */
|
|
89
108
|
export interface AskRequestCarry {
|
|
90
109
|
origin?: AskOrigin;
|
|
91
|
-
/** Rides only from the gate's OWN station (the inherited stations' facts never read a rule store). */
|
|
92
|
-
ruleStoreUnreadable?: RuleStoreUnreadable;
|
|
93
110
|
denialLimitFallback?: DenialLimitFallback;
|
|
94
111
|
}
|
|
95
112
|
/** The GATE's own mint station: both members are read off the surviving decision, which is the gate's
|
|
@@ -126,8 +143,8 @@ export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
|
126
143
|
* unchanged-ask branches it is the SAME read the exclusion judged by (a caller-owned decision with a
|
|
127
144
|
* stateful accessor cannot answer the exclusion with one word and the card with another); on the
|
|
128
145
|
* bound branch it is derived from the re-spoken ask this station minted (its own object). */
|
|
129
|
-
/** `ask` is the incoming ask unchanged (the caller's own object)
|
|
130
|
-
* it is a `deny` whose attested cause reads `unavailable`. */
|
|
146
|
+
/** `ask` is the incoming ask unchanged (the caller's own object), or — an UNAVAILABLE classification with a live frozen
|
|
147
|
+
* approver — that ask with the classifier sentence appended; without one it is a `deny` whose attested cause reads `unavailable`. */
|
|
131
148
|
| {
|
|
132
149
|
kind: "resolve";
|
|
133
150
|
ask: A;
|
|
@@ -142,8 +159,9 @@ export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
|
142
159
|
* gate would have produced; `block` AT the bound ⇒ the ask is re-spoken in the classifier's voice as
|
|
143
160
|
* the fallback (`decisionReason:"classifier"`, `requiresRealApproval`, the member carrying counts +
|
|
144
161
|
* window) for the arm to resolve at the frozen approver; `parse_error` ⇒ the same block path with the CC
|
|
145
|
-
* parse-failure sentence (counted); `unavailable` ⇒
|
|
146
|
-
*
|
|
162
|
+
* parse-failure sentence (counted); `unavailable` ⇒ the gate's own exit (`classifierUnavailableArm`) on the
|
|
163
|
+
* frozen approver — a live one resolves the incoming ask, none ⇒ a DENY that says so (CC 2.1.250 form), never
|
|
164
|
+
* counted; excluded / no classifier ⇒ the incoming ask, unchanged. The member may ALREADY be on the incoming
|
|
147
165
|
* ask (a deeper ancestor's fallback, minted inside the nested fold — the case the exclusion names):
|
|
148
166
|
* it rides to the approver and the headless arm unchanged, whichever tracker minted it; only a
|
|
149
167
|
* tracker that counted THIS streak is reset by the person's later allow (`mintedHere`).
|
|
@@ -160,6 +178,9 @@ export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
|
160
178
|
*/
|
|
161
179
|
export declare function judgeInheritedClassifier<A extends InheritedAsk>(opts: {
|
|
162
180
|
autoMode: InheritedAutoMode | undefined;
|
|
181
|
+
/** The leg's live permission mode (the gate's seat): under `dontAsk` / `bypassPermissions` / `plan` the decider is not
|
|
182
|
+
* dispatched (the mode's arbitration decides first). Absent ⇒ a decider on the chain is dispatched. */
|
|
183
|
+
liveMode?: () => import("../ask-origin.js").PermissionMode;
|
|
163
184
|
ask: A;
|
|
164
185
|
req: ToolCallRequest;
|
|
165
186
|
signal: AbortSignal;
|
|
@@ -167,6 +188,8 @@ export declare function judgeInheritedClassifier<A extends InheritedAsk>(opts: {
|
|
|
167
188
|
/** The rewrite the arm's allow would have carried (the ask's own `updatedInput`, the re-check's edited
|
|
168
189
|
* args) — handed IN so the allow is minted here, attested, once; absent ⇒ a bare allow. */
|
|
169
190
|
allowRewrite?: unknown;
|
|
191
|
+
/** An `unavailable` verdict's fork facts (`classifierUnavailableArm`): is the frozen approver live; who hears it. */
|
|
192
|
+
unavailableSeat?: ClassifierUnavailableSeat;
|
|
170
193
|
}): Promise<InheritedClassifierJudgment<A>>;
|
|
171
194
|
/** The resolver's answer as the settlement reads it. */
|
|
172
195
|
export type FallbackResolution = Pick<ResolvedAsk, "action" | "settlement" | "approverUnavailable">;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { classifierParseFailureDenyMessage,
|
|
1
|
+
import { classifierParseFailureDenyMessage, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow } from "../auto-mode.js";
|
|
2
2
|
import { sanitizeAutoModeArmingRecipe, tightenDenialLimit } from "../auto-mode-arming.js";
|
|
3
|
+
import { classifierUnavailableArm } from "../classifier-unavailable-arm.js";
|
|
3
4
|
import { attestClassifierDenyCause, attestClassifierRound } from "../tool-policy.js";
|
|
4
5
|
import { SETTLEMENT_NOBODY_JUDGED } from "../gate-outcome.js";
|
|
5
6
|
import { deliverEngineNotice } from "../types.js";
|
|
6
7
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
7
|
-
import { askOriginOf,
|
|
8
|
+
import { askOriginOf, classifyAsk } from "../ask-origin.js";
|
|
8
9
|
export function attachRebuiltDenialTrackers(entries, denialLimit) {
|
|
9
10
|
if (entries === undefined)
|
|
10
11
|
return undefined;
|
|
@@ -58,7 +59,7 @@ export function createDenialLimitStop(opts) {
|
|
|
58
59
|
};
|
|
59
60
|
return { gateStopRef, stopForDenialLimit };
|
|
60
61
|
}
|
|
61
|
-
const INHERITED_STATION_FACTS = { contentQuestion: false, markedUnresolvable: false, org: undefined, tightened: undefined, mandateMarks: {} };
|
|
62
|
+
export const INHERITED_STATION_FACTS = { contentQuestion: false, markedUnresolvable: false, org: undefined, tightened: undefined, mandateMarks: {} };
|
|
62
63
|
function fallbackCarry(fallback, liveApprover, tracker) {
|
|
63
64
|
if (fallback === undefined)
|
|
64
65
|
return {};
|
|
@@ -67,7 +68,6 @@ function fallbackCarry(fallback, liveApprover, tracker) {
|
|
|
67
68
|
export function gateAskCarry(decision, liveApprover, tracker) {
|
|
68
69
|
return {
|
|
69
70
|
...(decision.origin !== undefined ? { origin: decision.origin } : {}),
|
|
70
|
-
...(decision.ruleStoreUnreadable !== undefined ? { ruleStoreUnreadable: decision.ruleStoreUnreadable } : {}),
|
|
71
71
|
...fallbackCarry(decision.denialLimitFallback, liveApprover, tracker),
|
|
72
72
|
};
|
|
73
73
|
}
|
|
@@ -80,22 +80,28 @@ export function inheritedAskCarry(judged, liveApprover, tracker) {
|
|
|
80
80
|
export async function judgeInheritedClassifier(opts) {
|
|
81
81
|
const { autoMode, ask, req } = opts;
|
|
82
82
|
const origin = askOriginOf(ask, INHERITED_STATION_FACTS);
|
|
83
|
-
|
|
83
|
+
const modeDecidesFirst = () => {
|
|
84
|
+
const live = opts.liveMode?.();
|
|
85
|
+
return live === "dontAsk" || live === "bypassPermissions" || live === "plan";
|
|
86
|
+
};
|
|
87
|
+
if (autoMode === undefined || modeDecidesFirst() || !classifyAsk(ask, INHERITED_STATION_FACTS).row.classifierEligible) {
|
|
84
88
|
return { kind: "resolve", ask, fallback: ask.denialLimitFallback, mintedHere: false, origin };
|
|
85
89
|
}
|
|
86
90
|
const verdict = await autoMode.decider
|
|
87
91
|
.decide({ req, ...(ask.message !== undefined ? { askMessage: ask.message } : {}) }, opts.signal)
|
|
88
92
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
93
|
+
if (verdict.kind === "allow" && modeDecidesFirst())
|
|
94
|
+
return { kind: "resolve", ask, fallback: ask.denialLimitFallback, mintedHere: false, origin };
|
|
89
95
|
const attributed = (d) => (verdict.classifier !== undefined ? attestClassifierRound(d, verdict.classifier, req) : d);
|
|
90
96
|
if (verdict.kind === "allow") {
|
|
91
97
|
autoMode.denialTracking?.recordAllow();
|
|
92
98
|
return { kind: "allow", result: attributed({ action: "allow", ...(opts.allowRewrite !== undefined ? { updatedInput: opts.allowRewrite } : {}) }) };
|
|
93
99
|
}
|
|
94
100
|
if (verdict.kind === "unavailable") {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
};
|
|
101
|
+
const arm = classifierUnavailableArm({ ask, req, cause: verdict.cause, seat: opts.unavailableSeat, aborted: opts.signal.aborted });
|
|
102
|
+
if (arm.kind === "asked")
|
|
103
|
+
return { kind: "resolve", ask: arm.ask, fallback: ask.denialLimitFallback, mintedHere: false, origin };
|
|
104
|
+
return { kind: "deny", result: attributed(arm.deny) };
|
|
99
105
|
}
|
|
100
106
|
const reason = verdict.kind === "block" ? (verdict.reason ? inlineUntrusted(verdict.reason) : "") : classifierParseFailureDenyMessage(verdict.classifier);
|
|
101
107
|
const category = verdict.kind === "block" && verdict.category ? inlineUntrusted(verdict.category) : "";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isRemoteExecutionEnv } from "../remote-env.js";
|
|
2
2
|
import { deliverEngineNotice } from "../engine-notice.js";
|
|
3
3
|
const BOOLEAN_CAPABILITY_KEYS = ["isolation", "suspendable", "canonicalPathAuthoritative"];
|
|
4
|
-
const OPTIONAL_METHOD_KEYS = ["writeFileExclusive", "writeFileGuarded", "setFileMode", "readLink", "canonicalChain"];
|
|
4
|
+
const OPTIONAL_METHOD_KEYS = ["writeFileExclusive", "writeFileGuarded", "setFileMode", "readLink", "canonicalChain", "fileIdentity"];
|
|
5
5
|
export function malformedCapabilityDeclarations(env) {
|
|
6
6
|
const out = [];
|
|
7
7
|
for (const key of OPTIONAL_METHOD_KEYS) {
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
* the reminder envelope and the text helpers. Its `import type` edges up to `hooks.ts` name the gate's public
|
|
22
22
|
* contract (`ToolGateInput`, `ToolGateResult`, the observer payload), which stays declared there.
|
|
23
23
|
*/
|
|
24
|
-
import { type ClassifierDenyCause, type DeniedBy, type GateOutcome, type Settlement } from "../gate-outcome.js";
|
|
24
|
+
import { type ClassifierDenyCause, type DeniedBy, type GateOutcome, type ModeRefusal, type ModeRelease, type Settlement } from "../gate-outcome.js";
|
|
25
25
|
import type { AutoModeClassifierRound } from "../auto-mode.js";
|
|
26
|
-
import type
|
|
26
|
+
import { type Arbitration, type AskClassification, type AskOrigin, type AskOriginFacts } from "../ask-origin.js";
|
|
27
27
|
import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "../tool-policy.js";
|
|
28
28
|
import type { SafeNotifier } from "../safe-notify.js";
|
|
29
29
|
import type { HookSeatOutcome, PermissionDeniedPayload, PreToolUseResult, ToolGateInput, ToolGateResult } from "../hooks.js";
|
|
@@ -45,6 +45,10 @@ export interface GateExitFacts {
|
|
|
45
45
|
* Orthogonal to `deniedBy`: one that ALLOWED is attributed too (a fallback model's allow is exactly the case
|
|
46
46
|
* an operator needs to see), which is why it rides the disposition's shared member, not the deny arm. */
|
|
47
47
|
classifier?: AutoModeClassifierRound;
|
|
48
|
+
/** The permission mode RELEASED the surviving ask (the allow exit's account of it); never beside `deniedBy`. */
|
|
49
|
+
released?: ModeRelease;
|
|
50
|
+
/** The permission mode REFUSED the surviving ask — only beside `deniedBy: "mode"`. */
|
|
51
|
+
modeRefusal?: ModeRefusal;
|
|
48
52
|
}
|
|
49
53
|
/**
|
|
50
54
|
* Mint the outcome. A defect here is an ENGINE defect (a refusing site that did not say who refused, a
|
|
@@ -228,6 +232,14 @@ export interface GatePass {
|
|
|
228
232
|
* card on the gate result, so the refusal's `tool_end` still says WHAT was found (`readonly_out_of_root`) beside
|
|
229
233
|
* WHO refused (`ask_resolution`). Writer: the fold's read station. */
|
|
230
234
|
shellReadDetails?: unknown;
|
|
235
|
+
/** The read station PLACED every write the command makes inside the write roots (`ShellReadVerdict.writesPlaced`) — the
|
|
236
|
+
* one precondition the mode's file-command acceptance reads (the reference's path checks precede its acceptEdits arm). */
|
|
237
|
+
shellWritesPlaced?: true;
|
|
238
|
+
/** The read station did NOT place the command's writes because a `cp` / `mv` segment carries an option word
|
|
239
|
+
* (`ShellReadVerdict.unplacedOptions`) — the one non-placement reason the mode's card and the headless refusal name. */
|
|
240
|
+
shellUnplacedOptions?: {
|
|
241
|
+
readonly program: "cp" | "mv";
|
|
242
|
+
};
|
|
231
243
|
/** borrowed-mutable — the FORM of the classifier round behind a deny (`unavailable` / `parse_error`), written
|
|
232
244
|
* beside `deniedBy` by the gate's own classifier step (its attestation, read once) or by the fold / the
|
|
233
245
|
* re-check when an inherited station's attested deny arrives through the chain; undefined for a ruling
|
|
@@ -260,14 +272,19 @@ export interface GatePass {
|
|
|
260
272
|
* twin on the answered side, and narrower: the ask stays ordinary for every ANSWERER, and only the allow
|
|
261
273
|
* layer's read-only-command arm stands down. Writer: the fold. Reader: that arm, and nothing else. */
|
|
262
274
|
probeBoundaryDeclined?: true;
|
|
275
|
+
/** borrowed-mutable — the shell read station could not establish what this command reads (finding `unestablishable`
|
|
276
|
+
* or `declined`): a mode's release of the call is a release past the leg's read-deny table, announced as such.
|
|
277
|
+
* Writer: the fold's read station. Reader: the release point. */
|
|
278
|
+
shellReadUnestablished?: true;
|
|
263
279
|
/** borrowed-mutable — the shell read station minted the standing ask's directory-grant offer
|
|
264
280
|
* (`probeReadRootCandidate`). Read by the classify seat's shape probe, which withdraws the offer when its
|
|
265
281
|
* own objection stands beside the station's — the grant answers the station's finding, not the shape's.
|
|
266
282
|
* Writer: the fold's read station. */
|
|
267
283
|
shellReadOffer?: true;
|
|
268
284
|
/** borrowed-mutable — the write-protection judge covered this call and its table MATCHED: the allow
|
|
269
|
-
* layer must not clear the surviving ask, a live decider still may.
|
|
270
|
-
* on BOTH its arms
|
|
285
|
+
* layer must not clear the surviving ask, a live decider still may. Writers: the fold's fourth tighten,
|
|
286
|
+
* on BOTH its arms (a write tool's target), and the fold's read station (a shell command's written targets,
|
|
287
|
+
* `ShellReadVerdict.writeProtected`). Reader: the lanes' mandate. */
|
|
271
288
|
writeProtected?: true;
|
|
272
289
|
/** borrowed-mutable — the same judge could NOT RESOLVE the target: no automatic lane may answer the
|
|
273
290
|
* ask. Writer and reader as above; the pair is mutually exclusive (one verdict per judgment). */
|
|
@@ -276,6 +293,15 @@ export interface GatePass {
|
|
|
276
293
|
* approved path (a redaction is not cancelled because a person confirmed). Writer: the fold. Readers: the
|
|
277
294
|
* fold's probe and write-protection judges, the lanes, the allow exit. */
|
|
278
295
|
policyRewrite: unknown;
|
|
296
|
+
/** borrowed-mutable — the standing ask's CLASS SET and folded row (`classifyAsk`), written at the lanes' origin
|
|
297
|
+
* stamp and at every re-mint (the denial-limit fallback, the approved-edit round); read by the classifier
|
|
298
|
+
* station, the sandbox leg and the arbitration point. Writer: the lanes. */
|
|
299
|
+
askClassification?: AskClassification;
|
|
300
|
+
/** borrowed-mutable — the mode arbitration's verdict when it RELEASED or REFUSED the standing ask (the exit
|
|
301
|
+
* projects it onto the disposition). Writer: the lanes' arbitration point. */
|
|
302
|
+
arbitration?: Exclude<Arbitration, {
|
|
303
|
+
kind: "ask";
|
|
304
|
+
}>;
|
|
279
305
|
}
|
|
280
306
|
/**
|
|
281
307
|
* The engine's fail-closed block of phase 1 — the PreToolUse face THREW or never ANSWERED, so the engine
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { screenGateOutcome, SETTLEMENT_IS_REFUSAL } from "../gate-outcome.js";
|
|
2
|
+
import { headlessApprovalRemedy } from "../ask-origin.js";
|
|
3
|
+
import { isProtectedWrite } from "../tool-registry.js";
|
|
2
4
|
import { askUnresolvableCauseOf, classifierDenyCauseOf, classifierRoundOf, decisionText, engineSettlementOf } from "../tool-policy.js";
|
|
3
5
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
4
6
|
import { formatHookFeedback } from "../reminder-mint.js";
|
|
@@ -7,8 +9,14 @@ export function mintGateOutcome(facts) {
|
|
|
7
9
|
const attribution = facts.classifier !== undefined ? Object.freeze({ ...facts.classifier, ...(facts.classifier.fallback !== undefined ? { fallback: Object.freeze({ ...facts.classifier.fallback }) } : {}) }) : undefined;
|
|
8
10
|
const outcome = Object.freeze({
|
|
9
11
|
disposition: Object.freeze(facts.deniedBy !== undefined
|
|
10
|
-
? {
|
|
11
|
-
|
|
12
|
+
? {
|
|
13
|
+
kind: "denied",
|
|
14
|
+
deniedBy: facts.deniedBy,
|
|
15
|
+
...(facts.cause !== undefined ? { cause: facts.cause } : {}),
|
|
16
|
+
...(attribution !== undefined ? { classifier: attribution } : {}),
|
|
17
|
+
...(facts.modeRefusal !== undefined ? { mode: facts.modeRefusal.mode, askClass: facts.modeRefusal.askClass, classes: Object.freeze([...facts.modeRefusal.classes]) } : {}),
|
|
18
|
+
}
|
|
19
|
+
: { kind: "allowed", ...(attribution !== undefined ? { classifier: attribution } : {}), ...(facts.released !== undefined ? { released: Object.freeze({ ...facts.released, classes: Object.freeze([...facts.released.classes]) }) } : {}) }),
|
|
12
20
|
...(facts.settled !== undefined ? { settlement: settlement, origin: facts.settled.origin } : {}),
|
|
13
21
|
});
|
|
14
22
|
const defects = screenGateOutcome(outcome);
|
|
@@ -16,6 +24,10 @@ export function mintGateOutcome(facts) {
|
|
|
16
24
|
throw new Error(`the tool gate composed an outcome that violates its own record: ${defects.join("; ")}`);
|
|
17
25
|
return outcome;
|
|
18
26
|
}
|
|
27
|
+
function releaseRecordOf(a) {
|
|
28
|
+
const { kind: _kind, ...record } = a;
|
|
29
|
+
return record;
|
|
30
|
+
}
|
|
19
31
|
export function directDeny(deniedBy) {
|
|
20
32
|
return mintGateOutcome({ deniedBy });
|
|
21
33
|
}
|
|
@@ -111,17 +123,24 @@ function unresolvableCauseOfSettlement(gate) {
|
|
|
111
123
|
export async function exitGate(pass) {
|
|
112
124
|
const { input, event, toolName, toolCallId, ledger, notifyPermissionDenied } = pass;
|
|
113
125
|
if (pass.decision.action === "deny") {
|
|
114
|
-
const denyReason = withParkFailureCause(decisionText(pass.decision) ?? `tool "${toolName}" denied by policy`, pass.parkFailed);
|
|
115
126
|
if (pass.decision.updatedInput !== undefined) {
|
|
116
127
|
pass.currentInput = pass.decision.updatedInput;
|
|
117
128
|
}
|
|
118
129
|
if (pass.deniedBy === undefined)
|
|
119
130
|
throw new Error(`the tool gate refused "${toolName}" without a refusing layer — every deny site attributes itself`);
|
|
120
|
-
const
|
|
131
|
+
const ancestorArbitration = pass.arbitration === undefined && pass.deniedBy === "policy" ? input.ancestorArbitrationOf?.(toolCallId) : undefined;
|
|
132
|
+
const modeRefusal = pass.arbitration?.kind === "denied_by_mode" ? pass.arbitration : ancestorArbitration?.kind === "denied_by_mode" ? ancestorArbitration : undefined;
|
|
133
|
+
if (modeRefusal !== undefined) {
|
|
134
|
+
pass.deniedBy = "mode";
|
|
135
|
+
ledger.supersede();
|
|
136
|
+
}
|
|
137
|
+
const gate = mintGateOutcome({ deniedBy: pass.deniedBy, ...(pass.denyCause !== undefined ? { cause: pass.denyCause } : {}), ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}), ...(pass.classifierRound !== undefined ? { classifier: pass.classifierRound } : {}), ...(modeRefusal !== undefined ? { modeRefusal: { mode: modeRefusal.mode, askClass: modeRefusal.askClass, classes: modeRefusal.classes } } : {}) });
|
|
121
138
|
const unresolvable = askUnresolvableCauseOf(pass.decision, { toolCallId, toolName }) ?? unresolvableCauseOfSettlement(gate);
|
|
139
|
+
const remedy = unresolvable === "no_approver_wired" ? headlessApprovalRemedy({ mode: input.permissionMode?.(), classification: pass.askClassification, coveredWrite: isProtectedWrite(pass.req), ...(pass.shellUnplacedOptions !== undefined ? { unplacedOptions: pass.shellUnplacedOptions } : {}) }) : undefined;
|
|
140
|
+
const denyReason = withParkFailureCause(`${decisionText(pass.decision) ?? `tool "${toolName}" denied by policy`}${remedy !== undefined ? ` — ${remedy}` : ""}`, pass.parkFailed);
|
|
122
141
|
if (unresolvable !== undefined) {
|
|
123
142
|
const cause = unresolvable;
|
|
124
|
-
pass.notifier.notify(() => input.onAskUnresolvable?.({ toolName, toolCallId, cause, parkLaneExisted: pass.suspendAsk !== undefined }), "toolGate.onAskUnresolvable");
|
|
143
|
+
pass.notifier.notify(() => input.onAskUnresolvable?.({ toolName, toolCallId, cause, parkLaneExisted: pass.suspendAsk !== undefined, ...(remedy !== undefined ? { remedy } : {}) }), "toolGate.onAskUnresolvable");
|
|
125
144
|
}
|
|
126
145
|
await notifyPermissionDenied({
|
|
127
146
|
toolName,
|
|
@@ -136,7 +155,7 @@ export async function exitGate(pass) {
|
|
|
136
155
|
reason: formatHookFeedback(denyReason, input.reminderMark),
|
|
137
156
|
gate,
|
|
138
157
|
...(() => {
|
|
139
|
-
const details = pass.denyDetails ?? pass.shellReadDetails;
|
|
158
|
+
const details = pass.denyDetails ?? (modeRefusal !== undefined ? undefined : pass.shellReadDetails);
|
|
140
159
|
return details !== undefined ? { details } : {};
|
|
141
160
|
})(),
|
|
142
161
|
preToolContext: pass.preToolContext,
|
|
@@ -147,9 +166,11 @@ export async function exitGate(pass) {
|
|
|
147
166
|
if (rw !== undefined)
|
|
148
167
|
pass.currentInput = rw;
|
|
149
168
|
}
|
|
169
|
+
const release = pass.arbitration?.kind === "released" ? pass.arbitration : (() => { if (ledger.settled !== undefined)
|
|
170
|
+
return undefined; const a = pass.arbitration === undefined ? input.ancestorArbitrationOf?.(toolCallId) : undefined; return a?.kind === "released" ? a : undefined; })();
|
|
150
171
|
return {
|
|
151
172
|
updatedInput: pass.currentInput === event.input ? undefined : pass.currentInput,
|
|
152
|
-
gate: mintGateOutcome({ ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}), ...(pass.classifierRound !== undefined ? { classifier: pass.classifierRound } : {}) }),
|
|
173
|
+
gate: mintGateOutcome({ ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}), ...(pass.classifierRound !== undefined ? { classifier: pass.classifierRound } : {}), ...(release !== undefined ? { released: releaseRecordOf(release) } : {}) }),
|
|
153
174
|
preToolContext: pass.preToolContext,
|
|
154
175
|
};
|
|
155
176
|
}
|
|
@@ -30,7 +30,7 @@ import { persistedRuleMandateOf, type RuleClearanceFacts } from "../ask-origin.j
|
|
|
30
30
|
import { type OrgGateVerdict, type PersistedRuleAnswer, type PersistedRuleHit, type PersistedRuleUnreadable } from "../hooks.js";
|
|
31
31
|
import type { PersistedRule, PersistedRuleVerdict, RuleOffer, SegmentCoverage } from "../permission-rule-model.js";
|
|
32
32
|
import { type OrgRuleResolution } from "../permission-rule-org.js";
|
|
33
|
-
import type
|
|
33
|
+
import { type EffectivePermissionRules, type PermissionRuleStoreProvider } from "../permission-rule-provider.js";
|
|
34
34
|
/** The one SHELL tool the persisted-rule lane's COMMAND grammar speaks for (the rule-offer face mints for
|
|
35
35
|
* it alone). The PATH grammar's tools are derived from the catalog (`ruleToolGrammarOf`). */
|
|
36
36
|
export declare const PERSISTED_RULE_TOOL = "Bash";
|
|
@@ -81,10 +81,9 @@ export declare function orgRevisionEvidenceOf(resolution: OrgRuleResolution, onD
|
|
|
81
81
|
*/
|
|
82
82
|
export declare function persistedRuleHitOf(verdict: PersistedRuleVerdict | undefined): PersistedRuleHit | undefined;
|
|
83
83
|
/** The COMMAND arm's projection of a verdict: a call the lexer could not read against a standing deny/ask
|
|
84
|
-
* row is the lane's UNREADABLE answer
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* other verdict is {@link persistedRuleHitOf}'s hit. */
|
|
84
|
+
* row is the lane's UNREADABLE answer carrying the lexer's reason — it fails closed (a real-approval ask no
|
|
85
|
+
* automatic lane clears) — never a matched ask rule, which would let a blanket `onAsk: "allow"` clear a
|
|
86
|
+
* question nobody can answer. Every other verdict is {@link persistedRuleHitOf}'s hit. */
|
|
88
87
|
export declare function persistedRuleAnswerOf(verdict: PersistedRuleVerdict | undefined): PersistedRuleHit | PersistedRuleUnreadable | undefined;
|
|
89
88
|
/**
|
|
90
89
|
* Widened to every path-targeting tool — the persisted-rule lane's PATH arm: the call's
|
|
@@ -152,6 +151,11 @@ export declare function createPermissionRuleLanes(cfg: {
|
|
|
152
151
|
* that were dropped) and for a durable read failure — each read's lines are delivered once, whichever
|
|
153
152
|
* lane took the read. */
|
|
154
153
|
onDisclosure: (message: string) => void;
|
|
154
|
+
/** The person is told their store (or the named sources of it) was skipped for a call (the run's one notice,
|
|
155
|
+
* `ruleStoreUnreadableNotice`). */
|
|
156
|
+
onStoreUnreadable: (at: "call", sources?: EffectivePermissionRules["unreadableSources"]) => void;
|
|
157
|
+
/** The run's cut: a read it interrupts is abandoned quietly — a cancelled wait is no fact about the store. */
|
|
158
|
+
signal?: AbortSignal;
|
|
155
159
|
}): PermissionRuleLanes;
|
|
156
160
|
/** The closed reason set for the rule-offer factory's empty answer — the same three
|
|
157
161
|
* spellings the `AskRequest` and durable-row seats declare, kept at the one factory that fills both.
|