@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
package/dist/core/gate-lanes.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { attestClassifierDenyCause, classifierDenyCauseOf, decisionText, describeThrown } from "./tool-policy.js";
|
|
2
|
-
import {
|
|
3
|
-
import { classifierParseFailureDenyMessage,
|
|
2
|
+
import { arbitrateByMode, classifyAsk, fileCommandOptionsSentence, mandateDisclosureOf, persistedRuleMandateOf, ruleClearanceOf, safetyAxisOf, stampAskOrigin } from "./ask-origin.js";
|
|
3
|
+
import { classifierParseFailureDenyMessage, denialLimitFallbackMessage, unarmedWindow } from "./auto-mode.js";
|
|
4
|
+
import { classifierUnavailableArm } from "./classifier-unavailable-arm.js";
|
|
4
5
|
import { SETTLEMENT_NOBODY_JUDGED } from "./gate-outcome.js";
|
|
5
6
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
6
7
|
import { isRuleBehavior } from "./permission-rule-model.js";
|
|
7
8
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
8
9
|
import { applyPersistedTightening, disclosedRuleSet } from "./persisted-rule-arms.js";
|
|
9
|
-
import { readOnlyShellVerdict } from "./read-only-shell.js";
|
|
10
|
+
import { CD_GIT_COMPOUND_SENTENCE, changesDirectoryBeforeGit, readOnlyShellVerdict } from "./read-only-shell.js";
|
|
11
|
+
import { readShellCommand } from "./shell-lexer.js";
|
|
10
12
|
import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
|
|
11
13
|
import { catalogRuleFaceOf } from "./tool-registry.js";
|
|
14
|
+
import { callAnswersTo, callFaceSeat } from "./tool-roster.js";
|
|
15
|
+
import { modeAcceptsFileCommand } from "./mode-policy.js";
|
|
12
16
|
import { ORG_ADJUDICATION_TIMEOUT_MS, ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, settleOrgVerdictWithin } from "./permission-rule-org.js";
|
|
13
17
|
import { exitGate } from "./runner/gate-exit.js";
|
|
14
18
|
import { traceHookCrash } from "./gate-crash-trace.js";
|
|
@@ -165,14 +169,19 @@ export async function runGateLanes(pass) {
|
|
|
165
169
|
writeProtected: pass.writeProtected === true,
|
|
166
170
|
writeProtectionUnresolved: pass.writeProtectionUnresolved === true,
|
|
167
171
|
});
|
|
168
|
-
const askOriginFacts = (org,
|
|
169
|
-
contentQuestion:
|
|
172
|
+
const askOriginFacts = (org, ruleCallUnreadable) => ({
|
|
173
|
+
contentQuestion: callAnswersTo(pass.req, ASK_USER_QUESTION_TOOL_NAME),
|
|
170
174
|
markedUnresolvable: markedUnresolvable(),
|
|
171
175
|
org,
|
|
172
|
-
...(
|
|
176
|
+
...(ruleCallUnreadable ? { ruleCallUnreadable } : {}),
|
|
173
177
|
tightened: pass.tightenedBy,
|
|
174
178
|
mandateMarks: mandateMarksNow(),
|
|
179
|
+
...(input.toolMarkSources !== undefined ? { toolMarkSources: input.toolMarkSources } : {}),
|
|
180
|
+
...(input.a2aIsolatePeers === true ? { a2aIsolatePeers: true } : {}),
|
|
175
181
|
});
|
|
182
|
+
const classifyStanding = (unstamped, facts) => {
|
|
183
|
+
pass.askClassification = classifyAsk(unstamped, facts);
|
|
184
|
+
};
|
|
176
185
|
let orgTightenCount = 0;
|
|
177
186
|
let orgEvidence = (() => {
|
|
178
187
|
const w = input.orgRules === undefined ? "not_wired" : "not_adjudicated";
|
|
@@ -191,7 +200,7 @@ export async function runGateLanes(pass) {
|
|
|
191
200
|
let decided = current;
|
|
192
201
|
let pending;
|
|
193
202
|
try {
|
|
194
|
-
pending = Promise.resolve(input.orgRules.adjudicate({ toolName, args, toolCallId }));
|
|
203
|
+
pending = Promise.resolve(input.orgRules.adjudicate({ toolName, args, toolCallId, ...callFaceSeat(pass.req.face) }));
|
|
195
204
|
}
|
|
196
205
|
catch (err) {
|
|
197
206
|
pending = Promise.resolve({ status: "unavailable", disclosures: [`the org adjudication face threw before answering: ${describeThrown(err)}`] });
|
|
@@ -208,18 +217,14 @@ export async function runGateLanes(pass) {
|
|
|
208
217
|
orgEvidence = { revisionAbsent: "unavailable", ruleAbsent: "unavailable" };
|
|
209
218
|
orgRealApprovalRequired = true;
|
|
210
219
|
await notifier.notifyAsync(() => input.orgRules?.onUnavailable?.({ toolName, toolCallId, message: answer.disclosures.join("; ") }), "toolGate.orgSnapshotUnavailable");
|
|
220
|
+
const orgUnavailableMessage = `tool "${toolName}" needs approval: this deployment is org-governed and cannot currently adjudicate ` +
|
|
221
|
+
`against an organization policy snapshot — every allow tightens to a real approval until it can`;
|
|
211
222
|
if (decided.action === "allow") {
|
|
212
|
-
decided = {
|
|
213
|
-
action: "ask",
|
|
214
|
-
message: `tool "${toolName}" needs approval: this deployment is org-governed and cannot currently adjudicate ` +
|
|
215
|
-
`against an organization policy snapshot — every allow tightens to a real approval until it can`,
|
|
216
|
-
decisionReason: ORG_UNAVAILABLE_DECISION_REASON,
|
|
217
|
-
requiresRealApproval: true,
|
|
218
|
-
};
|
|
223
|
+
decided = { action: "ask", message: orgUnavailableMessage, decisionReason: ORG_UNAVAILABLE_DECISION_REASON, requiresRealApproval: true };
|
|
219
224
|
orgTightenCount += 1;
|
|
220
225
|
}
|
|
221
226
|
else if (decided.action === "ask" && decided.requiresRealApproval !== true) {
|
|
222
|
-
decided = { ...decided, requiresRealApproval: true };
|
|
227
|
+
decided = { ...decided, requiresRealApproval: true, ...(pass.tightenedBy !== undefined ? { message: orgUnavailableMessage } : {}) };
|
|
223
228
|
orgTightenCount += 1;
|
|
224
229
|
}
|
|
225
230
|
return { decision: decided, org: "unavailable" };
|
|
@@ -241,23 +246,20 @@ export async function runGateLanes(pass) {
|
|
|
241
246
|
return { decision: decided, org: undefined };
|
|
242
247
|
orgTightenCount += 1;
|
|
243
248
|
orgRealApprovalRequired = true;
|
|
249
|
+
const orgRuleMessage = unreadable !== undefined
|
|
250
|
+
? `an organization policy rule (${rule}) may reach this call, which could not be read against it (${unreadable}) — a person must decide`
|
|
251
|
+
: `an organization policy rule (${rule}) requires approval for this call`;
|
|
244
252
|
return {
|
|
245
253
|
decision: decided.action === "ask"
|
|
246
|
-
?
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
message: unreadable !== undefined
|
|
250
|
-
? `an organization policy rule (${rule}) may reach this call, which could not be read against it (${unreadable}) — a person must decide`
|
|
251
|
-
: `an organization policy rule (${rule}) requires approval for this call`,
|
|
252
|
-
decisionReason: ORG_RULE_DECISION_REASON,
|
|
253
|
-
requiresRealApproval: true,
|
|
254
|
-
},
|
|
254
|
+
?
|
|
255
|
+
{ ...decided, requiresRealApproval: true, ...(decided.requiresRealApproval !== true && pass.tightenedBy !== undefined ? { message: orgRuleMessage } : {}) }
|
|
256
|
+
: { action: "ask", message: orgRuleMessage, decisionReason: ORG_RULE_DECISION_REASON, requiresRealApproval: true },
|
|
255
257
|
org: "rule",
|
|
256
258
|
};
|
|
257
259
|
};
|
|
258
260
|
const orgAnswer = await applyOrgLayer(pass.decision, pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput);
|
|
259
261
|
pass.decision = orgAnswer.decision;
|
|
260
|
-
const
|
|
262
|
+
const ruleCallUnreadableOf = (lane) => lane?.unreadable === true && lane.reason !== undefined;
|
|
261
263
|
const readPersistedRules = async (req) => {
|
|
262
264
|
let pendingHit;
|
|
263
265
|
try {
|
|
@@ -348,10 +350,10 @@ export async function runGateLanes(pass) {
|
|
|
348
350
|
};
|
|
349
351
|
}
|
|
350
352
|
}
|
|
351
|
-
const originFacts = askOriginFacts(orgAnswer.org,
|
|
353
|
+
const originFacts = askOriginFacts(orgAnswer.org, ruleCallUnreadableOf(laneAnswer));
|
|
352
354
|
if (pass.decision.action === "ask") {
|
|
353
|
-
const {
|
|
354
|
-
|
|
355
|
+
const stamped = { ...pass.decision, ruleEvidence: mintRuleEvidence(personalEvidence), ...(laneCoverage !== undefined ? { segmentCoverage: laneCoverage } : {}) };
|
|
356
|
+
classifyStanding(stamped, originFacts);
|
|
355
357
|
pass.decision = stampAskOrigin(stamped, originFacts);
|
|
356
358
|
}
|
|
357
359
|
if (allowLayerMayClear() && pass.decision.action === "ask" && ruleMayClear() && pass.probeBoundaryDeclined !== true && pass.req.toolName === COMMAND_RULE_TOOL) {
|
|
@@ -359,7 +361,8 @@ export async function runGateLanes(pass) {
|
|
|
359
361
|
const args = pass.req.args;
|
|
360
362
|
const command = typeof args === "object" && args !== null && !Array.isArray(args) ? args[commandParam] : undefined;
|
|
361
363
|
const backgrounded = typeof args === "object" && args !== null && args.run_in_background === true;
|
|
362
|
-
|
|
364
|
+
const shape = typeof command === "string" ? readShellCommand(command) : undefined;
|
|
365
|
+
if (typeof command === "string" && shape !== undefined && !backgrounded && readOnlyShellVerdict(command, shape, input.trackedCwd?.()).readOnly) {
|
|
363
366
|
pass.decision = {
|
|
364
367
|
action: "allow",
|
|
365
368
|
message: "the command is read-only (upstream's read-only command tables) — allowed without asking",
|
|
@@ -368,15 +371,21 @@ export async function runGateLanes(pass) {
|
|
|
368
371
|
};
|
|
369
372
|
await notifier.notifyAsync(() => input.onReadOnlyAllowed?.({ toolName: pass.req.toolName, toolCallId, command }), "toolGate.readOnlyAllowed");
|
|
370
373
|
}
|
|
374
|
+
else if (shape !== undefined && pass.tightenedBy === "shell_gate" && changesDirectoryBeforeGit(shape, input.trackedCwd?.())) {
|
|
375
|
+
pass.decision = { ...pass.decision, message: CD_GIT_COMPOUND_SENTENCE };
|
|
376
|
+
}
|
|
371
377
|
}
|
|
372
378
|
if (pass.decision.action === "ask")
|
|
373
379
|
ledger.supersede();
|
|
374
|
-
if (input.autoMode && pass.decision.action === "ask" && pass.decision.origin !== undefined &&
|
|
375
|
-
const
|
|
376
|
-
|
|
380
|
+
if (input.autoMode && pass.decision.action === "ask" && pass.decision.origin !== undefined && pass.askClassification?.row.classifierEligible === true && (input.permissionMode === undefined || input.permissionMode() === "auto")) {
|
|
381
|
+
const classifySignal = callSignal ?? input.abortSignal;
|
|
382
|
+
const heard = await input.autoMode.decider
|
|
383
|
+
.decide({ req: pass.req, askMessage: decisionText(pass.decision) }, classifySignal)
|
|
377
384
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
378
|
-
|
|
379
|
-
if (verdict
|
|
385
|
+
const verdict = heard.kind === "allow" && input.permissionMode !== undefined && input.permissionMode() !== "auto" ? undefined : heard;
|
|
386
|
+
if (verdict !== undefined)
|
|
387
|
+
pass.classifierRound = verdict.classifier;
|
|
388
|
+
if (verdict?.kind === "allow") {
|
|
380
389
|
input.autoMode.denialTracking?.recordAllow();
|
|
381
390
|
pass.decision = {
|
|
382
391
|
action: "allow",
|
|
@@ -385,14 +394,17 @@ export async function runGateLanes(pass) {
|
|
|
385
394
|
...(pass.policyRewrite !== undefined ? { updatedInput: pass.policyRewrite } : {}),
|
|
386
395
|
};
|
|
387
396
|
}
|
|
388
|
-
else if (verdict
|
|
389
|
-
pass.decision
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
397
|
+
else if (verdict?.kind === "unavailable") {
|
|
398
|
+
const arm = classifierUnavailableArm({ ask: pass.decision, req: pass.req, cause: verdict.cause, attribution: verdict.classifier, seat: input.autoMode.unavailable, aborted: classifySignal?.aborted === true });
|
|
399
|
+
if (arm.kind === "asked") {
|
|
400
|
+
classifyStanding(arm.ask, originFacts);
|
|
401
|
+
pass.decision = stampAskOrigin(arm.ask, originFacts);
|
|
402
|
+
pass.classifierRound = undefined;
|
|
403
|
+
}
|
|
404
|
+
else
|
|
405
|
+
pass.decision = arm.deny;
|
|
394
406
|
}
|
|
395
|
-
else {
|
|
407
|
+
else if (verdict !== undefined) {
|
|
396
408
|
const reason = verdict.kind === "block" ? (verdict.reason ? inlineUntrusted(verdict.reason) : "") : classifierParseFailureDenyMessage(verdict.classifier);
|
|
397
409
|
const category = verdict.kind === "block" && verdict.category ? inlineUntrusted(verdict.category) : "";
|
|
398
410
|
const tracked = input.autoMode.denialTracking?.recordBlock();
|
|
@@ -404,6 +416,7 @@ export async function runGateLanes(pass) {
|
|
|
404
416
|
requiresRealApproval: true,
|
|
405
417
|
denialLimitFallback: tracked.fallback,
|
|
406
418
|
};
|
|
419
|
+
classifyStanding(fallbackAsk, originFacts);
|
|
407
420
|
pass.decision = stampAskOrigin(fallbackAsk, originFacts);
|
|
408
421
|
}
|
|
409
422
|
else {
|
|
@@ -430,7 +443,7 @@ export async function runGateLanes(pass) {
|
|
|
430
443
|
pass.hookAsk === undefined &&
|
|
431
444
|
input.egress !== true &&
|
|
432
445
|
input.peerMessage !== true &&
|
|
433
|
-
|
|
446
|
+
pass.askClassification?.row.sandboxAdmission === true &&
|
|
434
447
|
pass.probeBoundaryDeclined !== true &&
|
|
435
448
|
input.sandboxAdmission.boundaryCapable !== true) {
|
|
436
449
|
const recorded = input.sandboxAdmission.askClassesOf(toolCallId);
|
|
@@ -447,6 +460,42 @@ export async function runGateLanes(pass) {
|
|
|
447
460
|
};
|
|
448
461
|
}
|
|
449
462
|
}
|
|
463
|
+
if (pass.decision.action === "ask" && input.permissionMode !== undefined && pass.askClassification !== undefined) {
|
|
464
|
+
const standing = pass.decision;
|
|
465
|
+
const mode = input.permissionMode();
|
|
466
|
+
const fileCommandByWord = modeAcceptsFileCommand(mode, toolName, pass.policyRewrite ?? pass.currentInput);
|
|
467
|
+
const verdict = arbitrateByMode(mode, pass.askClassification, { ...(standing.origin !== undefined ? { origin: standing.origin } : {}), ...(standing.mandate !== undefined ? { mandate: standing.mandate } : {}) }, { fileCommand: pass.shellWritesPlaced === true && fileCommandByWord });
|
|
468
|
+
if (verdict.kind === "ask" && fileCommandByWord && pass.shellWritesPlaced !== true && pass.shellUnplacedOptions !== undefined && pass.askClassification.classes.size === 1 && pass.askClassification.classes.has("ordinary")) {
|
|
469
|
+
pass.decision = { ...standing, message: fileCommandOptionsSentence(pass.shellUnplacedOptions.program) };
|
|
470
|
+
}
|
|
471
|
+
if (verdict.kind === "accepted") {
|
|
472
|
+
ledger.supersede();
|
|
473
|
+
pass.decision = { action: "allow", message: `accepted by the ${verdict.mode} permission mode (one of its file commands)`, decisionReason: "mode", ...(pass.policyRewrite !== undefined ? { updatedInput: pass.policyRewrite } : {}) };
|
|
474
|
+
}
|
|
475
|
+
else if (verdict.kind === "released") {
|
|
476
|
+
pass.arbitration = verdict;
|
|
477
|
+
ledger.supersede();
|
|
478
|
+
await notifier.notifyAsync(() => input.onModeReleased?.({ toolName, toolCallId, release: { mode: verdict.mode, askClass: verdict.askClass, classes: verdict.classes, ...(verdict.origin !== undefined ? { origin: verdict.origin } : {}), ...(verdict.mandate !== undefined ? { mandate: verdict.mandate } : {}), ...(verdict.reason !== undefined ? { reason: verdict.reason } : {}) }, ...(pass.shellReadUnestablished === true ? { readUnestablished: true } : {}) }), "toolGate.onModeReleased");
|
|
479
|
+
pass.decision = {
|
|
480
|
+
action: "allow",
|
|
481
|
+
message: `released by the ${verdict.mode} permission mode (the ${verdict.askClass} ask was not put to anyone)`,
|
|
482
|
+
decisionReason: "mode",
|
|
483
|
+
...(pass.policyRewrite !== undefined ? { updatedInput: pass.policyRewrite } : {}),
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
else if (verdict.kind === "denied_by_mode") {
|
|
487
|
+
pass.arbitration = verdict;
|
|
488
|
+
pass.deniedBy = "mode";
|
|
489
|
+
pass.denyCause = undefined;
|
|
490
|
+
pass.decision = {
|
|
491
|
+
action: "deny",
|
|
492
|
+
message: verdict.mode === "dontAsk"
|
|
493
|
+
? `refused by the dontAsk permission mode: this call needed approval (${verdict.askClass}) and the mode answers no ask; run under a mode with an approver, or change the call`
|
|
494
|
+
: `refused by the bypassPermissions permission mode: the write-protection judge could not resolve this write's target, and with nobody to ask the mode refuses rather than releases it`,
|
|
495
|
+
decisionReason: "mode",
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
}
|
|
450
499
|
const safety = safetyAxisOf({
|
|
451
500
|
egress: input.egress,
|
|
452
501
|
shellGated: input.shellGated,
|
|
@@ -466,8 +515,8 @@ export async function runGateLanes(pass) {
|
|
|
466
515
|
...(callSignal !== undefined ? { signal: callSignal } : {}),
|
|
467
516
|
...(d.action === "ask" && d.denialLimitFallback !== undefined ? { denialLimitFallback: unarmedWindow(d.denialLimitFallback) } : {}),
|
|
468
517
|
...(d.action === "ask" && d.origin !== undefined ? { origin: d.origin } : {}),
|
|
469
|
-
...(d.action === "ask" && d.ruleStoreUnreadable !== undefined ? { ruleStoreUnreadable: d.ruleStoreUnreadable } : {}),
|
|
470
518
|
...(d.action === "ask" && d.mandated === true ? { mandated: true, ...(d.mandate !== undefined ? { mandate: d.mandate } : {}) } : {}),
|
|
519
|
+
...(d.action === "ask" && d.authoredBy === "deployment" ? { authoredBy: "deployment" } : {}),
|
|
471
520
|
});
|
|
472
521
|
if (suspendAsk && pass.decision.action === "ask") {
|
|
473
522
|
const parkArgs = [pass.req, pass.currentInput, safety, undefined, realApprovalOf(pass.decision), pass.decision.action === "ask" ? pass.decision.persistedRuleShadowed : undefined, pass.decision.action === "ask" ? pass.decision.decisionReason : undefined, pass.decision.action === "ask" ? pass.decision.probeReason : undefined, pass.decision.action === "ask" ? pass.decision.probeCause : undefined, pass.decision.action === "ask" ? pass.decision.segmentCoverage : undefined, pass.decision.action === "ask" ? pass.decision.matchedAskRule : undefined, pass.decision.action === "ask" ? pass.decision.probeMandated : undefined];
|
|
@@ -642,10 +691,10 @@ export async function runGateLanes(pass) {
|
|
|
642
691
|
editDenied = recheck;
|
|
643
692
|
break;
|
|
644
693
|
}
|
|
645
|
-
let
|
|
694
|
+
let editRuleCallUnreadable = false;
|
|
646
695
|
if (input.persistedRules !== undefined && toolName !== ASK_USER_QUESTION_TOOL_NAME) {
|
|
647
696
|
const editLane = await readPersistedRules({ toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) });
|
|
648
|
-
|
|
697
|
+
editRuleCallUnreadable = ruleCallUnreadableOf(editLane);
|
|
649
698
|
const editTightened = applyPersistedTightening(recheck, editLane);
|
|
650
699
|
recheck = editTightened.decision;
|
|
651
700
|
if (editTightened.tightened === "deny")
|
|
@@ -666,10 +715,27 @@ export async function runGateLanes(pass) {
|
|
|
666
715
|
editArgs = recheck.updatedInput;
|
|
667
716
|
editRewrittenSinceHuman = true;
|
|
668
717
|
}
|
|
669
|
-
const {
|
|
670
|
-
const editAskSnapshot = { ...recheckUnstamped, ruleEvidence: mintRuleEvidence({ dotsAbsent: "not_adjudicated" }) };
|
|
718
|
+
const editAskSnapshot = { ...recheck, ruleEvidence: mintRuleEvidence({ dotsAbsent: "not_adjudicated" }) };
|
|
671
719
|
markedThisRound = undefined;
|
|
672
|
-
const
|
|
720
|
+
const editFacts = askOriginFacts(editOrg.org, editRuleCallUnreadable);
|
|
721
|
+
classifyStanding(editAskSnapshot, editFacts);
|
|
722
|
+
const editAskDecision = stampAskOrigin(editAskSnapshot, editFacts);
|
|
723
|
+
if (input.permissionMode !== undefined && pass.askClassification !== undefined) {
|
|
724
|
+
const editVerdict = arbitrateByMode(input.permissionMode(), pass.askClassification, { ...(editAskDecision.origin !== undefined ? { origin: editAskDecision.origin } : {}), ...(editAskDecision.mandate !== undefined ? { mandate: editAskDecision.mandate } : {}) });
|
|
725
|
+
if (editVerdict.kind === "released") {
|
|
726
|
+
ledger.supersede();
|
|
727
|
+
pass.arbitration = editVerdict;
|
|
728
|
+
await notifier.notifyAsync(() => input.onModeReleased?.({ toolName, toolCallId, release: { mode: editVerdict.mode, askClass: editVerdict.askClass, classes: editVerdict.classes, ...(editVerdict.origin !== undefined ? { origin: editVerdict.origin } : {}), ...(editVerdict.mandate !== undefined ? { mandate: editVerdict.mandate } : {}), ...(editVerdict.reason !== undefined ? { reason: editVerdict.reason } : {}) }, ...(pass.shellReadUnestablished === true ? { readUnestablished: true } : {}) }), "toolGate.onModeReleased");
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
if (editVerdict.kind === "denied_by_mode") {
|
|
732
|
+
ledger.supersede();
|
|
733
|
+
pass.arbitration = editVerdict;
|
|
734
|
+
editDenied = { action: "deny", message: `the approved edit for "${toolName}" raised a new approval requirement that the ${editVerdict.mode} permission mode refuses`, decisionReason: "mode" };
|
|
735
|
+
pass.deniedBy = "mode";
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
673
739
|
const editAskReq = { toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
|
|
674
740
|
const rr = await (callSignal !== undefined ? resolveAsk(editAskDecision, editAskReq, callSignal) : resolveAsk(editAskDecision, editAskReq));
|
|
675
741
|
ledger.fromResolved(rr, editAskDecision, toolName);
|
|
@@ -702,14 +768,14 @@ export async function runGateLanes(pass) {
|
|
|
702
768
|
}
|
|
703
769
|
if (editDenied === undefined && input.shellReadCheck !== undefined) {
|
|
704
770
|
const station = input.shellReadCheck;
|
|
705
|
-
const judged = station.judge(
|
|
771
|
+
const judged = station.judge(pass.req, editArgs, input.trackedCwd?.(), callSignal ?? input.abortSignal, { seat: "rejudge", toolCallId });
|
|
706
772
|
if (judged !== null) {
|
|
707
773
|
let verdict;
|
|
708
774
|
try {
|
|
709
775
|
verdict = await withGateTimeout(judged, boundedAwaitDeadlineMs(), callSignal ?? input.abortSignal, "shellReadStation");
|
|
710
776
|
}
|
|
711
777
|
catch {
|
|
712
|
-
verdict = station.unanswered(
|
|
778
|
+
verdict = station.unanswered(pass.req, "the read station could not answer");
|
|
713
779
|
}
|
|
714
780
|
if (verdict.disposition !== "vouch") {
|
|
715
781
|
editDenied = {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* policy composes: a policy says allow / ask / deny; this file says how the pass that consumed that
|
|
8
8
|
* verdict ended.
|
|
9
9
|
*/
|
|
10
|
-
import { type AskOrigin, type AssertAllKeysHandled } from "./ask-origin.js";
|
|
10
|
+
import { type AskClass, type AskOrigin, type AssertAllKeysHandled, type PermissionMode, type PersistedRuleMandate } from "./ask-origin.js";
|
|
11
11
|
import { type AutoModeClassifierRound } from "./auto-mode.js";
|
|
12
12
|
/**
|
|
13
13
|
* How the wait an ask was in ENDED — the closed set. One word per way a wait can end; the `who` on the
|
|
@@ -197,7 +197,8 @@ export type NobodyJudgedTableCoversEveryKind = AssertAllKeysHandled<Exclude<Sett
|
|
|
197
197
|
* - `org` — an organization policy rule denied.
|
|
198
198
|
* - `classifier` — the auto-mode classifier denied directly (its ASK-side role is `origin:
|
|
199
199
|
* "denial_limit_fallback"`).
|
|
200
|
-
* - `
|
|
200
|
+
* - `persisted_rule` — the person's persisted-rule lane refused: a deny row (live), or, on a durable resume, a deny
|
|
201
|
+
* row, a call unreadable against the rows, or a wait for them the task's cancel ended.
|
|
201
202
|
* - `compliance` — the compliance call-time lock.
|
|
202
203
|
* - `write_protection` — an approved edit was rewritten by the restriction chain onto a write-protected
|
|
203
204
|
* path that no approval covers.
|
|
@@ -206,15 +207,18 @@ export type NobodyJudgedTableCoversEveryKind = AssertAllKeysHandled<Exclude<Sett
|
|
|
206
207
|
* - `read_boundary` — the shell read station refused: a deny-listed read (refused on every seat, with no
|
|
207
208
|
* ask), a read the read-only shell face may not make, or a rewritten / re-established command whose reads
|
|
208
209
|
* no longer are what a person approved.
|
|
210
|
+
* - `mode` — the permission MODE refused a surviving ask at the gate's arbitration point: `dontAsk` turns an
|
|
211
|
+
* ask nobody is present to answer into a refusal, and `bypassPermissions` refuses a write whose protection
|
|
212
|
+
* could not be judged. The disposition carries `mode`, `askClass` and `classes` beside the word.
|
|
209
213
|
*/
|
|
210
|
-
export declare const DENIED_BY_VALUES: readonly ["policy", "hook", "org", "persisted_rule", "classifier", "
|
|
214
|
+
export declare const DENIED_BY_VALUES: readonly ["policy", "hook", "org", "persisted_rule", "classifier", "compliance", "write_protection", "ask_resolution", "read_boundary", "mode"];
|
|
211
215
|
export type DeniedBy = (typeof DENIED_BY_VALUES)[number];
|
|
212
216
|
/** Membership test for {@link DeniedBy}. */
|
|
213
217
|
export declare function isDeniedBy(v: unknown): v is DeniedBy;
|
|
214
218
|
/** Which layers may VETO an approval a person already gave (invariant I3: a `human_allowed` settlement
|
|
215
219
|
* beside a `denied` disposition names one of these). The approval-edit re-screen (hook), the policy, org
|
|
216
220
|
* and persisted-rule re-checks, and the write-protection re-judge run AFTER the person's yes; the
|
|
217
|
-
* classifier
|
|
221
|
+
* classifier and compliance run before any ask exists, and `ask_resolution` IS the
|
|
218
222
|
* settlement, not a veto of it. `persisted_rule` = a person's own standing DENY row (their settings'
|
|
219
223
|
* deny list, imported) — the personal-store sibling of `org`, and like it re-judged on an edited
|
|
220
224
|
* command. `read_boundary` vetoes too: the rewrite re-judge and the pre-spawn re-establishment run after
|
|
@@ -225,11 +229,11 @@ export declare const DENIED_BY_MAY_VETO: {
|
|
|
225
229
|
readonly org: true;
|
|
226
230
|
readonly persisted_rule: true;
|
|
227
231
|
readonly classifier: false;
|
|
228
|
-
readonly plan_mode: false;
|
|
229
232
|
readonly compliance: false;
|
|
230
233
|
readonly write_protection: true;
|
|
231
234
|
readonly ask_resolution: false;
|
|
232
235
|
readonly read_boundary: true;
|
|
236
|
+
readonly mode: false;
|
|
233
237
|
};
|
|
234
238
|
/** Compile-time fence: `never` while every deny layer has a veto row. */
|
|
235
239
|
export type VetoTableCoversEveryDeniedBy = AssertAllKeysHandled<Exclude<DeniedBy, keyof typeof DENIED_BY_MAY_VETO>>;
|
|
@@ -259,11 +263,11 @@ export declare const DENIED_BY_MAY_CARRY_CAUSE: {
|
|
|
259
263
|
readonly org: false;
|
|
260
264
|
readonly persisted_rule: false;
|
|
261
265
|
readonly classifier: true;
|
|
262
|
-
readonly plan_mode: false;
|
|
263
266
|
readonly compliance: false;
|
|
264
267
|
readonly write_protection: false;
|
|
265
268
|
readonly ask_resolution: false;
|
|
266
269
|
readonly read_boundary: false;
|
|
270
|
+
readonly mode: false;
|
|
267
271
|
};
|
|
268
272
|
/** Compile-time fence: `never` while every deny layer has a cause row. */
|
|
269
273
|
export type CauseTableCoversEveryDeniedBy = AssertAllKeysHandled<Exclude<DeniedBy, keyof typeof DENIED_BY_MAY_CARRY_CAUSE>>;
|
|
@@ -285,12 +289,37 @@ export type CauseTableCoversEveryDeniedBy = AssertAllKeysHandled<Exclude<DeniedB
|
|
|
285
289
|
export type GateDisposition = {
|
|
286
290
|
kind: "allowed";
|
|
287
291
|
classifier?: AutoModeClassifierRound;
|
|
292
|
+
released?: ModeRelease;
|
|
288
293
|
} | {
|
|
289
294
|
kind: "denied";
|
|
290
295
|
deniedBy: DeniedBy;
|
|
291
296
|
cause?: ClassifierDenyCause;
|
|
292
297
|
classifier?: AutoModeClassifierRound;
|
|
298
|
+
mode?: ModeRefusal["mode"];
|
|
299
|
+
askClass?: AskClass;
|
|
300
|
+
classes?: readonly AskClass[];
|
|
293
301
|
};
|
|
302
|
+
/**
|
|
303
|
+
* A permission MODE released a surviving ask — the allowed disposition's account of the question it did not put
|
|
304
|
+
* to anyone: the mode, the disclosed class word (`askClass`, the first member of the folded set in `ASK_CLASSES`
|
|
305
|
+
* order — one of {@link RELEASABLE_ASK_CLASSES}), the whole set, and the origin / mandate words the card would
|
|
306
|
+
* have shown. `reason: "rule_unreadable"` marks a release over a rule the call could not be read against.
|
|
307
|
+
*/
|
|
308
|
+
export interface ModeRelease {
|
|
309
|
+
mode: PermissionMode;
|
|
310
|
+
askClass: AskClass;
|
|
311
|
+
classes: readonly AskClass[];
|
|
312
|
+
origin?: AskOrigin;
|
|
313
|
+
mandate?: PersistedRuleMandate;
|
|
314
|
+
reason?: "rule_unreadable";
|
|
315
|
+
}
|
|
316
|
+
/** The two modes that refuse on their own account (`deniedBy: "mode"`): `dontAsk` every non-authority ask,
|
|
317
|
+
* `bypassPermissions` a write whose protection could not be judged. `plan` refuses nothing here — its questions ask. */
|
|
318
|
+
export interface ModeRefusal {
|
|
319
|
+
mode: "bypassPermissions" | "dontAsk";
|
|
320
|
+
askClass: AskClass;
|
|
321
|
+
classes: readonly AskClass[];
|
|
322
|
+
}
|
|
294
323
|
/**
|
|
295
324
|
* The WHOLE record of one tool-gate pass: the final disposition, plus — when this pass settled an ask —
|
|
296
325
|
* the settlement it consumed and the ask's origin. Minted ONCE (the gate's exit; the decide lane for a
|
|
@@ -310,7 +339,11 @@ export type GateDisposition = {
|
|
|
310
339
|
* I6 `disposition.classifier` present ⇒ both model ids are non-empty strings, and a `fallback` on it
|
|
311
340
|
* names a `to` that IS `modelUsed` (the ladder landed where the record says) with a cause in
|
|
312
341
|
* {@link import("./auto-mode.js").AUTO_MODE_FALLBACK_CAUSES} — a record whose `to` and `modelUsed`
|
|
313
|
-
* disagree would let a reader answer "which model decided this call?" two ways
|
|
342
|
+
* disagree would let a reader answer "which model decided this call?" two ways;
|
|
343
|
+
* I7 `disposition.released` present ⇒ `kind === "allowed"`, no settlement, `mode` is `bypassPermissions`,
|
|
344
|
+
* `askClass` is a releasable class and every member of `classes` is an ask class — a mode releases nothing
|
|
345
|
+
* a person answered and nothing the table calls immune;
|
|
346
|
+
* I8 `deniedBy === "mode"` ⇔ `mode` present (`bypassPermissions` | `dontAsk`) with `askClass` and `classes`.
|
|
314
347
|
* Nothing here is read out of an ABSENCE: an ordinary allow with no ask is `{ disposition: { kind: "allowed" } }`,
|
|
315
348
|
* a direct policy deny is `{ disposition: { kind: "denied", deniedBy: "policy" } }`, and neither carries a
|
|
316
349
|
* settlement because neither settled one.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isAskOrigin } from "./ask-origin.js";
|
|
1
|
+
import { isAskClass, isAskOrigin, isPermissionMode, RELEASABLE_ASK_CLASSES } from "./ask-origin.js";
|
|
2
2
|
import { isAutoModeFallbackCause } from "./auto-mode.js";
|
|
3
3
|
export const SETTLEMENT_KINDS = [
|
|
4
4
|
"human_allowed",
|
|
@@ -56,7 +56,7 @@ export const SETTLEMENT_NOBODY_JUDGED = {
|
|
|
56
56
|
policy_refused: true,
|
|
57
57
|
task_aborted: false,
|
|
58
58
|
};
|
|
59
|
-
export const DENIED_BY_VALUES = ["policy", "hook", "org", "persisted_rule", "classifier", "
|
|
59
|
+
export const DENIED_BY_VALUES = ["policy", "hook", "org", "persisted_rule", "classifier", "compliance", "write_protection", "ask_resolution", "read_boundary", "mode"];
|
|
60
60
|
const DENIED_BY_SET = new Set(DENIED_BY_VALUES);
|
|
61
61
|
export function isDeniedBy(v) {
|
|
62
62
|
return DENIED_BY_SET.has(v);
|
|
@@ -67,11 +67,11 @@ export const DENIED_BY_MAY_VETO = {
|
|
|
67
67
|
org: true,
|
|
68
68
|
persisted_rule: true,
|
|
69
69
|
classifier: false,
|
|
70
|
-
plan_mode: false,
|
|
71
70
|
compliance: false,
|
|
72
71
|
write_protection: true,
|
|
73
72
|
ask_resolution: false,
|
|
74
73
|
read_boundary: true,
|
|
74
|
+
mode: false,
|
|
75
75
|
};
|
|
76
76
|
export const CLASSIFIER_DENY_CAUSES = ["unavailable", "parse_error"];
|
|
77
77
|
const CLASSIFIER_DENY_CAUSE_SET = new Set(CLASSIFIER_DENY_CAUSES);
|
|
@@ -84,11 +84,11 @@ export const DENIED_BY_MAY_CARRY_CAUSE = {
|
|
|
84
84
|
org: false,
|
|
85
85
|
persisted_rule: false,
|
|
86
86
|
classifier: true,
|
|
87
|
-
plan_mode: false,
|
|
88
87
|
compliance: false,
|
|
89
88
|
write_protection: false,
|
|
90
89
|
ask_resolution: false,
|
|
91
90
|
read_boundary: false,
|
|
91
|
+
mode: false,
|
|
92
92
|
};
|
|
93
93
|
export function screenGateOutcome(g) {
|
|
94
94
|
const defects = [];
|
|
@@ -116,6 +116,28 @@ export function screenGateOutcome(g) {
|
|
|
116
116
|
else if (isDeniedBy(d.deniedBy) && !DENIED_BY_MAY_CARRY_CAUSE[d.deniedBy])
|
|
117
117
|
defects.push(`I5: a classifier cause cannot stand beside deniedBy "${d.deniedBy}"`);
|
|
118
118
|
}
|
|
119
|
+
if (d.kind === "allowed" && d.released !== undefined) {
|
|
120
|
+
const r = d.released;
|
|
121
|
+
if (s !== undefined)
|
|
122
|
+
defects.push("I7: a mode release carries no settlement — nobody answered the ask it released");
|
|
123
|
+
if (r.mode !== "bypassPermissions")
|
|
124
|
+
defects.push(`I7: a mode release names mode "${String(r.mode)}" — only bypassPermissions releases`);
|
|
125
|
+
if (!isAskClass(r.askClass) || !RELEASABLE_ASK_CLASSES.includes(r.askClass))
|
|
126
|
+
defects.push(`I7: released.askClass "${String(r.askClass)}" is not a releasable class`);
|
|
127
|
+
if (!Array.isArray(r.classes) || r.classes.length === 0 || !r.classes.every((c) => isAskClass(c)))
|
|
128
|
+
defects.push("I7: released.classes must be a non-empty list of ask classes");
|
|
129
|
+
if (r.origin !== undefined && !isAskOrigin(r.origin))
|
|
130
|
+
defects.push(`I7: released.origin "${String(r.origin)}" is outside the closed set`);
|
|
131
|
+
}
|
|
132
|
+
if (d.kind === "denied") {
|
|
133
|
+
const modeRefusal = d.deniedBy === "mode";
|
|
134
|
+
if (modeRefusal !== (d.mode !== undefined))
|
|
135
|
+
defects.push("I8: deniedBy mode and disposition.mode must be present together");
|
|
136
|
+
if (modeRefusal && (!isPermissionMode(d.mode) || (d.mode !== "bypassPermissions" && d.mode !== "dontAsk")))
|
|
137
|
+
defects.push(`I8: a mode refusal names mode "${String(d.mode)}" — only bypassPermissions, dontAsk and plan refuse`);
|
|
138
|
+
if (modeRefusal && (!isAskClass(d.askClass) || !Array.isArray(d.classes) || d.classes.length === 0 || !d.classes.every((c) => isAskClass(c))))
|
|
139
|
+
defects.push("I8: a mode refusal carries askClass and a non-empty classes list");
|
|
140
|
+
}
|
|
119
141
|
const attribution = d.classifier;
|
|
120
142
|
if (attribution !== undefined) {
|
|
121
143
|
const named = (v) => typeof v === "string" && v !== "";
|
|
@@ -12,7 +12,6 @@ export declare const GOVERNANCE_CODES: {
|
|
|
12
12
|
readonly "config.compliance_required": "transient";
|
|
13
13
|
readonly "config.compliance_denied": "terminal";
|
|
14
14
|
readonly "config.locked_key": "terminal";
|
|
15
|
-
readonly "config.tool_mount_denied": "terminal";
|
|
16
15
|
readonly "memory.capture_optout_denied": "terminal";
|
|
17
16
|
};
|
|
18
17
|
export type GovernanceCode = keyof typeof GOVERNANCE_CODES;
|
|
@@ -42,6 +41,23 @@ export declare const NON_GOVERNANCE_MEMORY_CODES: ReadonlySet<string>;
|
|
|
42
41
|
* `undefined` (NOT governance — the caller falls through to its ordinary taxonomy).
|
|
43
42
|
*/
|
|
44
43
|
export declare function governanceRetryClass(code: string): GovernanceRetryClass | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* The CLOSED set of codes the `spec.tools` DECLARATION door refuses a caller's tool entry with, at prepare,
|
|
46
|
+
* before anything mounts: the entry cannot be built from its own members (`config.tool_mount_denied`), a
|
|
47
|
+
* member over a roster row's wire bound (`config.tool_roster_bound` / `config.tool_name_too_long` /
|
|
48
|
+
* `config.tool_contract_too_long`), a schema over the row's key bounds (`config.tool_schema_bound`), a
|
|
49
|
+
* declared face or result card the schema / namespace cannot carry (`config.tool_face_invalid`), a name or
|
|
50
|
+
* alias in the engine's protocol namespace (`config.tool_name_invalid`), an egress tool declared other than
|
|
51
|
+
* `write` (`config.egress_requires_write_effect` — also minted, same meaning, for a protocol tool's resolved
|
|
52
|
+
* axes), a name the run reserves for a tool the engine injects (`config.reserved_tool_name` — structured output,
|
|
53
|
+
* the skill tool, tool search, the offload reader, an injected protocol tool; `runGoal`'s completion tool, which
|
|
54
|
+
* refuses before any run). Each ends the run `failed` with the code on `terminal.code`. A caller-shape refusal, not a policy
|
|
55
|
+
* verdict: none of these is a governance code ({@link GOVERNANCE_CODES}); re-issuing the same declaration
|
|
56
|
+
* changes nothing. Every mint site at the door types its code against this set.
|
|
57
|
+
*/
|
|
58
|
+
export declare const TOOL_DECLARATION_CODES: readonly ["config.tool_mount_denied", "config.tool_roster_bound", "config.tool_name_too_long", "config.tool_contract_too_long", "config.tool_schema_bound", "config.tool_face_invalid", "config.tool_name_invalid", "config.egress_requires_write_effect", "config.reserved_tool_name"];
|
|
59
|
+
/** A code in {@link TOOL_DECLARATION_CODES}. */
|
|
60
|
+
export type ToolDeclarationCode = (typeof TOOL_DECLARATION_CODES)[number];
|
|
45
61
|
/**
|
|
46
62
|
* The CLOSED registry of reasons a permission-rule sync round may refuse an inbound record or move a
|
|
47
63
|
* local row into the quarantine area (design/182 §4.3/§5.2/§8.1). Same doctrine as
|
|
@@ -119,7 +135,7 @@ export type NoticeAudience = "user" | "operator";
|
|
|
119
135
|
* the union's catalog ({@link EngineNotice.code}). The groups below say only which family a row joins,
|
|
120
136
|
* so the catalog is not written a second time here and the two cannot drift.
|
|
121
137
|
*/
|
|
122
|
-
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.write_protection_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "route.response_model_mismatch", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed"];
|
|
138
|
+
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.write_protection_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "resume.legacy_mode_seed_migrated", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "route.response_model_mismatch", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed"];
|
|
123
139
|
/** A code this engine mints (see {@link ENGINE_NOTICE_CODES}). NOT the type of
|
|
124
140
|
* `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
|
|
125
141
|
* sink is a supported shape, and narrowing that field would break it. */
|
|
@@ -5,7 +5,6 @@ export const GOVERNANCE_CODES = {
|
|
|
5
5
|
"config.compliance_required": "transient",
|
|
6
6
|
"config.compliance_denied": "terminal",
|
|
7
7
|
"config.locked_key": "terminal",
|
|
8
|
-
"config.tool_mount_denied": "terminal",
|
|
9
8
|
"memory.capture_optout_denied": "terminal",
|
|
10
9
|
};
|
|
11
10
|
export const NON_GOVERNANCE_MEMORY_CODES = new Set([
|
|
@@ -93,6 +92,17 @@ export function governanceRetryClass(code) {
|
|
|
93
92
|
return "terminal";
|
|
94
93
|
return undefined;
|
|
95
94
|
}
|
|
95
|
+
export const TOOL_DECLARATION_CODES = [
|
|
96
|
+
"config.tool_mount_denied",
|
|
97
|
+
"config.tool_roster_bound",
|
|
98
|
+
"config.tool_name_too_long",
|
|
99
|
+
"config.tool_contract_too_long",
|
|
100
|
+
"config.tool_schema_bound",
|
|
101
|
+
"config.tool_face_invalid",
|
|
102
|
+
"config.tool_name_invalid",
|
|
103
|
+
"config.egress_requires_write_effect",
|
|
104
|
+
"config.reserved_tool_name",
|
|
105
|
+
];
|
|
96
106
|
export const RULE_SYNC_DROP_CODES = {
|
|
97
107
|
invalid_rule_text: "inbound-refused-or-local-quarantined",
|
|
98
108
|
metadata_mismatch: "inbound-refused-or-local-quarantined",
|
|
@@ -128,6 +138,11 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
128
138
|
"config.peer_admission_out_of_range",
|
|
129
139
|
"config.peer_lane_unmounted",
|
|
130
140
|
"config.secret_env_scrubbed",
|
|
141
|
+
"config.permission_mode_unavailable",
|
|
142
|
+
"permission.ask_released_by_mode",
|
|
143
|
+
"permission.read_deny_unapplied",
|
|
144
|
+
"permission.rule_store_unavailable",
|
|
145
|
+
"resume.legacy_mode_seed_migrated",
|
|
131
146
|
"peer.inbound_disposition",
|
|
132
147
|
"peer.held_settled",
|
|
133
148
|
"peer.idle_subscription",
|
|
@@ -135,6 +150,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
135
150
|
"classifier.fallback",
|
|
136
151
|
"classifier.role_defaulted",
|
|
137
152
|
"classifier.contract_broken",
|
|
153
|
+
"classifier.unavailable",
|
|
138
154
|
"checkpoint.execution_outcome_unrecorded",
|
|
139
155
|
"delegation.transcript_integrity",
|
|
140
156
|
"delegation.ask_unresolvable",
|
|
@@ -187,6 +203,7 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
187
203
|
"task.user_steer_undrained": "user",
|
|
188
204
|
"task.user_followup_undrained": "user",
|
|
189
205
|
"classifier.denial_limit": "user",
|
|
206
|
+
"classifier.unavailable": "user",
|
|
190
207
|
"classifier.fallback": "operator",
|
|
191
208
|
"classifier.role_defaulted": "operator",
|
|
192
209
|
"classifier.contract_broken": "operator",
|
|
@@ -198,11 +215,16 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
198
215
|
"approval.read_root_granted": "user",
|
|
199
216
|
"approval.read_root_grant_rejected": "user",
|
|
200
217
|
"config.durable_gate_unavailable": "user",
|
|
218
|
+
"config.permission_mode_unavailable": "user",
|
|
219
|
+
"permission.ask_released_by_mode": "user",
|
|
220
|
+
"permission.read_deny_unapplied": "user",
|
|
221
|
+
"permission.rule_store_unavailable": "user",
|
|
201
222
|
"memory.capture_opted_out": "user",
|
|
202
223
|
"memory.capture_optout_unpersisted": "user",
|
|
203
224
|
"memory.consolidation_withheld": "operator",
|
|
204
225
|
"delegation.ask_unresolvable": "user",
|
|
205
226
|
"config.autocompact_window_clamped": "operator",
|
|
227
|
+
"resume.legacy_mode_seed_migrated": "operator",
|
|
206
228
|
"config.env_timeout_discarded": "operator",
|
|
207
229
|
"config.materialize_env_discarded": "operator",
|
|
208
230
|
"config.models_swapped": "operator",
|