@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/ask-origin.js
CHANGED
|
@@ -3,11 +3,6 @@ const ASK_ORIGIN_SET = new Set(ASK_ORIGINS);
|
|
|
3
3
|
export function isAskOrigin(v) {
|
|
4
4
|
return ASK_ORIGIN_SET.has(v);
|
|
5
5
|
}
|
|
6
|
-
export const RULE_STORE_UNREADABLE_KINDS = ["store", "call"];
|
|
7
|
-
const RULE_STORE_UNREADABLE_SET = new Set(RULE_STORE_UNREADABLE_KINDS);
|
|
8
|
-
export function isRuleStoreUnreadable(v) {
|
|
9
|
-
return RULE_STORE_UNREADABLE_SET.has(v);
|
|
10
|
-
}
|
|
11
6
|
export function askOriginOf(decision, facts) {
|
|
12
7
|
const origin = originLadder(decision, facts);
|
|
13
8
|
if (ORIGIN_IMPLIES_REAL_APPROVAL[origin] === "at_derivation" && decision.requiresRealApproval !== true) {
|
|
@@ -28,7 +23,8 @@ export function stampAskOrigin(decision, facts) {
|
|
|
28
23
|
}
|
|
29
24
|
export function ruleClearanceOf(ask, marks) {
|
|
30
25
|
const marksWord = persistedRuleMandateOf({ ...marks, probeMandated: ask?.probeMandated === true });
|
|
31
|
-
const
|
|
26
|
+
const foldedDoor = (ask?.foldedAskClasses ?? []).some((c) => !MODE_ARBITRATION[c].ruleMayClear);
|
|
27
|
+
const doorOutsideTable = ask?.requiresRealApproval === true || ask?.decisionReason === "hook" || ask?.decisionReason === "mode" || foldedDoor || ask?.authoredBy === "deployment" || ask?.inheritedUnresolved === true || ask?.ancestorResolved === true;
|
|
32
28
|
const mandated = ask?.mandated === true || doorOutsideTable || marksWord !== undefined;
|
|
33
29
|
const stampedWord = ask?.mandated === true && isPersistedRuleMandate(ask.mandate) ? ask.mandate : undefined;
|
|
34
30
|
const mandate = doorOutsideTable || (ask?.mandated === true && stampedWord === undefined) ? undefined : (stampedWord ?? marksWord);
|
|
@@ -39,6 +35,10 @@ export function ruleClearanceOf(ask, marks) {
|
|
|
39
35
|
return "shadowed";
|
|
40
36
|
if (ask?.decisionReason === "hook")
|
|
41
37
|
return "mandated";
|
|
38
|
+
if (ask?.decisionReason === "mode" || foldedDoor)
|
|
39
|
+
return "mandated";
|
|
40
|
+
if (ask?.authoredBy === "deployment")
|
|
41
|
+
return "mandated";
|
|
42
42
|
if (ask?.matchedAskRule !== undefined)
|
|
43
43
|
return "shadowed";
|
|
44
44
|
if (ask?.inheritedUnresolved === true)
|
|
@@ -51,12 +51,15 @@ export function ruleClearanceOf(ask, marks) {
|
|
|
51
51
|
}
|
|
52
52
|
export function askMandateMember(ask) {
|
|
53
53
|
const { mandated, mandate } = ruleClearanceOf(ask, {});
|
|
54
|
-
|
|
54
|
+
const authored = ask?.authoredBy === "deployment" ? { authoredBy: "deployment" } : {};
|
|
55
|
+
return mandated ? { mandated: true, ...(mandate !== undefined ? { mandate } : {}), ...authored } : authored;
|
|
55
56
|
}
|
|
56
57
|
export function ownedClearanceFacts(d) {
|
|
57
|
-
const { mandated, requiresRealApproval, decisionReason, matchedAskRule, probeMandated } = d;
|
|
58
|
+
const { mandated, requiresRealApproval, decisionReason, matchedAskRule, probeMandated, authoredBy, foldedAskClasses } = d;
|
|
58
59
|
return {
|
|
59
60
|
...(mandated === true ? { mandated } : {}),
|
|
61
|
+
...(Array.isArray(foldedAskClasses) && foldedAskClasses.length > 0 ? { foldedAskClasses: [...foldedAskClasses] } : {}),
|
|
62
|
+
...(authoredBy === "deployment" ? { authoredBy } : {}),
|
|
60
63
|
...(requiresRealApproval === true ? { requiresRealApproval } : {}),
|
|
61
64
|
...(decisionReason !== undefined ? { decisionReason } : {}),
|
|
62
65
|
...(typeof matchedAskRule === "string" ? { matchedAskRule } : {}),
|
|
@@ -72,7 +75,7 @@ function originLadder(decision, facts) {
|
|
|
72
75
|
return "org_unavailable";
|
|
73
76
|
if (facts.org === "rule")
|
|
74
77
|
return "org_rule";
|
|
75
|
-
if (facts.
|
|
78
|
+
if (facts.ruleCallUnreadable === true)
|
|
76
79
|
return "rule_store_unavailable";
|
|
77
80
|
if (decision.decisionReason === "hook")
|
|
78
81
|
return "hook";
|
|
@@ -99,44 +102,11 @@ export const ORIGIN_IMPLIES_REAL_APPROVAL = {
|
|
|
99
102
|
safety_tighten: false,
|
|
100
103
|
policy: false,
|
|
101
104
|
};
|
|
102
|
-
const CLASSIFIER_MAY_ANSWER = {
|
|
103
|
-
content_question: false,
|
|
104
|
-
ancestor_marked: false,
|
|
105
|
-
org_unavailable: false,
|
|
106
|
-
org_rule: false,
|
|
107
|
-
rule_store_unavailable: false,
|
|
108
|
-
hook: false,
|
|
109
|
-
ask_rule: false,
|
|
110
|
-
denial_limit_fallback: false,
|
|
111
|
-
shell_gate_tighten: true,
|
|
112
|
-
safety_tighten: true,
|
|
113
|
-
policy: true,
|
|
114
|
-
};
|
|
115
|
-
export function classifierMayAnswer(origin) {
|
|
116
|
-
return CLASSIFIER_MAY_ANSWER[origin];
|
|
117
|
-
}
|
|
118
105
|
export const PERSISTED_RULE_MANDATES = ["operator_always", "tool_marks", "probe_mandate", "probe_unanswered", "write_protection", "write_protection_unresolved"];
|
|
119
106
|
const PERSISTED_RULE_MANDATE_SET = new Set(PERSISTED_RULE_MANDATES);
|
|
120
107
|
export function isPersistedRuleMandate(v) {
|
|
121
108
|
return PERSISTED_RULE_MANDATE_SET.has(v);
|
|
122
109
|
}
|
|
123
|
-
const MANDATE_LANE_ANSWERS = {
|
|
124
|
-
operator_always: { classifier: true, sandboxAdmission: true },
|
|
125
|
-
tool_marks: { classifier: true, sandboxAdmission: true },
|
|
126
|
-
probe_mandate: { classifier: false, sandboxAdmission: false },
|
|
127
|
-
probe_unanswered: { classifier: false, sandboxAdmission: false },
|
|
128
|
-
write_protection: { classifier: true, sandboxAdmission: true },
|
|
129
|
-
write_protection_unresolved: { classifier: false, sandboxAdmission: true },
|
|
130
|
-
};
|
|
131
|
-
export function classifierMayAnswerMandate(mandate) {
|
|
132
|
-
return mandate === undefined || MANDATE_LANE_ANSWERS[mandate].classifier;
|
|
133
|
-
}
|
|
134
|
-
export function sandboxMayAdmitMandate(mandate) {
|
|
135
|
-
return mandate === undefined || MANDATE_LANE_ANSWERS[mandate].sandboxAdmission;
|
|
136
|
-
}
|
|
137
|
-
function bindsEveryAutomaticLane(mandate) {
|
|
138
|
-
return mandate !== undefined && !classifierMayAnswerMandate(mandate) && !sandboxMayAdmitMandate(mandate);
|
|
139
|
-
}
|
|
140
110
|
const MANDATE_DISCLOSURE = {
|
|
141
111
|
probe_mandate: "the reversibility check declared this call structurally gated (the built-in shell check raises this for a read outside the directories allowed for this session) — it is cleared by confirming this call, never by a standing allow rule",
|
|
142
112
|
probe_unanswered: "the reversibility check did not answer for this call (it timed out or failed), so whether the call reads outside the directories allowed for this session is unknown — it is cleared by confirming this call, never by a standing allow rule",
|
|
@@ -150,7 +120,7 @@ export function mandateDisclosureOf(mandate) {
|
|
|
150
120
|
}
|
|
151
121
|
export function persistedRuleMandateOf(marks) {
|
|
152
122
|
const word = irreversibilityMandateOf(marks);
|
|
153
|
-
if (
|
|
123
|
+
if (word === "probe_mandate" || word === "probe_unanswered")
|
|
154
124
|
return word;
|
|
155
125
|
if (marks.writeProtectionUnresolved === true)
|
|
156
126
|
return "write_protection_unresolved";
|
|
@@ -178,6 +148,215 @@ export function safetyAxisOf(marks) {
|
|
|
178
148
|
const shell = raised && marks.shellGated === true;
|
|
179
149
|
return egress || raised ? { egress, irreversible: raised && !shell, ...(shell ? { shell: true } : {}) } : undefined;
|
|
180
150
|
}
|
|
181
|
-
export
|
|
182
|
-
|
|
151
|
+
export const PERMISSION_MODES = ["default", "acceptEdits", "plan", "auto", "bypassPermissions", "dontAsk"];
|
|
152
|
+
const PERMISSION_MODE_SET = new Set(PERMISSION_MODES);
|
|
153
|
+
export function isPermissionMode(v) {
|
|
154
|
+
return PERMISSION_MODE_SET.has(v);
|
|
155
|
+
}
|
|
156
|
+
export const PERMISSION_MODE_RANK = { plan: 0, default: 1, dontAsk: 1, acceptEdits: 2, auto: 3, bypassPermissions: 4 };
|
|
157
|
+
export function clampPermissionMode(declared, ceiling) {
|
|
158
|
+
if (declared === undefined)
|
|
159
|
+
return { effective: ceiling, clamped: false };
|
|
160
|
+
if (declared === ceiling)
|
|
161
|
+
return { effective: declared, clamped: false };
|
|
162
|
+
if (declared === "bypassPermissions")
|
|
163
|
+
return { effective: ceiling, clamped: true };
|
|
164
|
+
if ((declared === "acceptEdits" && ceiling === "auto") || (declared === "auto" && ceiling === "acceptEdits"))
|
|
165
|
+
return { effective: ceiling, clamped: true };
|
|
166
|
+
return PERMISSION_MODE_RANK[declared] <= PERMISSION_MODE_RANK[ceiling] ? { effective: declared, clamped: false } : { effective: ceiling, clamped: true };
|
|
167
|
+
}
|
|
168
|
+
export const ASK_CLASSES = [
|
|
169
|
+
"content_question",
|
|
170
|
+
"hook",
|
|
171
|
+
"ancestor",
|
|
172
|
+
"unverifiable_delete",
|
|
173
|
+
"transcript_write",
|
|
174
|
+
"org_rule",
|
|
175
|
+
"governance_unavailable",
|
|
176
|
+
"rule_unreadable",
|
|
177
|
+
"ask_rule",
|
|
178
|
+
"denial_limit_fallback",
|
|
179
|
+
"operator_forced",
|
|
180
|
+
"write_protection_unresolved",
|
|
181
|
+
"probe",
|
|
182
|
+
"write_protection",
|
|
183
|
+
"tool_marks_mcp",
|
|
184
|
+
"tool_marks_a2a",
|
|
185
|
+
"policy_rra",
|
|
186
|
+
"artifact_shared_read",
|
|
187
|
+
"mode",
|
|
188
|
+
"ordinary",
|
|
189
|
+
];
|
|
190
|
+
const ASK_CLASS_SET = new Set(ASK_CLASSES);
|
|
191
|
+
export function isAskClass(v) {
|
|
192
|
+
return ASK_CLASS_SET.has(v);
|
|
193
|
+
}
|
|
194
|
+
export const MODE_ARBITRATION = {
|
|
195
|
+
content_question: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
196
|
+
hook: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
197
|
+
ancestor: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
198
|
+
unverifiable_delete: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
199
|
+
transcript_write: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
200
|
+
org_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
201
|
+
governance_unavailable: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
202
|
+
rule_unreadable: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
203
|
+
ask_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
204
|
+
denial_limit_fallback: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
205
|
+
operator_forced: { bypassImmune: true, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false },
|
|
206
|
+
write_protection_unresolved: { bypassImmune: true, classifierEligible: false, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: true },
|
|
207
|
+
probe: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
208
|
+
write_protection: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: true },
|
|
209
|
+
tool_marks_mcp: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false },
|
|
210
|
+
tool_marks_a2a: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false },
|
|
211
|
+
policy_rra: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
212
|
+
artifact_shared_read: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
213
|
+
mode: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
214
|
+
ordinary: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: true, hookAskFloor: false },
|
|
215
|
+
};
|
|
216
|
+
export const RRA_EXPLAINERS = new Set(["unverifiable_delete", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "denial_limit_fallback", "ancestor", "artifact_shared_read"]);
|
|
217
|
+
export const RELEASABLE_ASK_CLASSES = ASK_CLASSES.filter((c) => !MODE_ARBITRATION[c].bypassImmune);
|
|
218
|
+
export function decisionAskClassesOf(d) {
|
|
219
|
+
const out = new Set();
|
|
220
|
+
if (d.guard === "unverifiable_delete")
|
|
221
|
+
out.add("unverifiable_delete");
|
|
222
|
+
if (d.guard === "transcript_write")
|
|
223
|
+
out.add("transcript_write");
|
|
224
|
+
if (d.guard === "artifact_shared_read")
|
|
225
|
+
out.add("artifact_shared_read");
|
|
226
|
+
if (d.ruleUnreadable !== undefined)
|
|
227
|
+
out.add("rule_unreadable");
|
|
228
|
+
if (d.matchedAskRule !== undefined)
|
|
229
|
+
out.add("ask_rule");
|
|
230
|
+
if (d.denialLimitFallback !== undefined)
|
|
231
|
+
out.add("denial_limit_fallback");
|
|
232
|
+
if (d.authoredBy === "deployment")
|
|
233
|
+
out.add("operator_forced");
|
|
234
|
+
if (d.probeMandated === true)
|
|
235
|
+
out.add("probe");
|
|
236
|
+
if (d.decisionReason === "hook")
|
|
237
|
+
out.add("hook");
|
|
238
|
+
if (d.decisionReason === "mode")
|
|
239
|
+
out.add("mode");
|
|
240
|
+
if (d.requiresRealApproval === true && ![...out].some((c) => RRA_EXPLAINERS.has(c)))
|
|
241
|
+
out.add("policy_rra");
|
|
242
|
+
if (out.size === 0 && d.mandated === true)
|
|
243
|
+
out.add("operator_forced");
|
|
244
|
+
for (const c of d.foldedAskClasses ?? [])
|
|
245
|
+
out.add(c);
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
export function foldArbitrationRow(classes) {
|
|
249
|
+
if (classes.size === 0)
|
|
250
|
+
return MODE_ARBITRATION.ordinary;
|
|
251
|
+
let bypassImmune = false, classifierEligible = true, sandboxAdmission = true, ruleMayClear = true, hookAskFloor = false;
|
|
252
|
+
for (const c of classes) {
|
|
253
|
+
const row = MODE_ARBITRATION[c];
|
|
254
|
+
bypassImmune ||= row.bypassImmune;
|
|
255
|
+
classifierEligible &&= row.classifierEligible;
|
|
256
|
+
sandboxAdmission &&= row.sandboxAdmission;
|
|
257
|
+
ruleMayClear &&= row.ruleMayClear;
|
|
258
|
+
hookAskFloor ||= row.hookAskFloor;
|
|
259
|
+
}
|
|
260
|
+
return { bypassImmune, classifierEligible, sandboxAdmission, ruleMayClear, hookAskFloor };
|
|
261
|
+
}
|
|
262
|
+
export function classifyAsk(d, facts) {
|
|
263
|
+
const classes = decisionAskClassesOf({ ...d, foldedAskClasses: undefined });
|
|
264
|
+
classes.delete("policy_rra");
|
|
265
|
+
if (facts.contentQuestion)
|
|
266
|
+
classes.add("content_question");
|
|
267
|
+
if (facts.markedUnresolvable)
|
|
268
|
+
classes.add("ancestor");
|
|
269
|
+
if (facts.org === "rule")
|
|
270
|
+
classes.add("org_rule");
|
|
271
|
+
if (facts.org === "unavailable")
|
|
272
|
+
classes.add("governance_unavailable");
|
|
273
|
+
if (facts.ruleCallUnreadable === true)
|
|
274
|
+
classes.add("rule_unreadable");
|
|
275
|
+
const marks = facts.mandateMarks;
|
|
276
|
+
if (marks.shellGated === true && marks.irreversibility === "always")
|
|
277
|
+
classes.add("operator_forced");
|
|
278
|
+
const toolMarked = marks.shellGated !== true && (marks.egress === true || marks.irreversibility === "always" || marks.irreversibility === "maybe");
|
|
279
|
+
if (toolMarked) {
|
|
280
|
+
const sources = facts.toolMarkSources;
|
|
281
|
+
if (sources === undefined || sources.size === 0 || sources.has("deployment"))
|
|
282
|
+
classes.add("operator_forced");
|
|
283
|
+
if (sources?.has("mcp_annotation"))
|
|
284
|
+
classes.add("tool_marks_mcp");
|
|
285
|
+
if (sources?.has("a2a_default"))
|
|
286
|
+
classes.add(facts.a2aIsolatePeers === true ? "operator_forced" : "tool_marks_a2a");
|
|
287
|
+
}
|
|
288
|
+
if (marks.probeUnanswered === true)
|
|
289
|
+
classes.add("probe");
|
|
290
|
+
if (marks.writeProtectionUnresolved === true)
|
|
291
|
+
classes.add("write_protection_unresolved");
|
|
292
|
+
if (marks.writeProtected === true)
|
|
293
|
+
classes.add("write_protection");
|
|
294
|
+
for (const c of facts.ancestorClasses ?? [])
|
|
295
|
+
classes.add(c);
|
|
296
|
+
if (d.requiresRealApproval === true && ![...classes].some((c) => RRA_EXPLAINERS.has(c)))
|
|
297
|
+
classes.add("policy_rra");
|
|
298
|
+
for (const c of d.foldedAskClasses ?? [])
|
|
299
|
+
classes.add(c);
|
|
300
|
+
if (classes.size === 0 && d.mandated === true)
|
|
301
|
+
classes.add("operator_forced");
|
|
302
|
+
if (classes.size === 0)
|
|
303
|
+
classes.add("ordinary");
|
|
304
|
+
const askClass = ASK_CLASSES.find((c) => classes.has(c));
|
|
305
|
+
return { classes, askClass, row: foldArbitrationRow(classes) };
|
|
306
|
+
}
|
|
307
|
+
export function headlessDenialMessage(toolName, askMessage) {
|
|
308
|
+
return `Permission for this tool use was denied: it requires interactive approval, and permission prompts are not available in this session (no approver is wired for "${toolName}" — headless auto-deny). The action was NOT performed. Do not claim it succeeded, and do not retry it in this session — report the limitation to the user, or suggest an alternative. What was requested: ${askMessage}`;
|
|
309
|
+
}
|
|
310
|
+
export function headlessApprovalRemedy(input) {
|
|
311
|
+
const approver = "wire an approver (`onAsk`) to put it to a person";
|
|
312
|
+
const c = input.classification;
|
|
313
|
+
if (input.mode === "acceptEdits" && input.unplacedOptions !== undefined && (c === undefined || (c.classes.size === 1 && c.classes.has("ordinary"))))
|
|
314
|
+
return `${fileCommandOptionsSentence(input.unplacedOptions.program)} Spell the ${input.unplacedOptions.program} without option words for the mode to answer it, or ${approver}`;
|
|
315
|
+
if (c?.row.bypassImmune === true)
|
|
316
|
+
return `no permission mode releases this ask (${c.askClass}): ${approver}, or change the call`;
|
|
317
|
+
if (input.mode === "plan" && c?.classes.has("mode") === true)
|
|
318
|
+
return `the plan permission mode asks before every write and every call to a tool the engine does not define that is not read-only — leave plan mode (\`setPermissionMode\` to another mode, or approve the plan) to run it, or ${approver}`;
|
|
319
|
+
const onlyOrdinary = c === undefined || (c.classes.size === 1 && c.classes.has("ordinary"));
|
|
320
|
+
if (input.coveredWrite && onlyOrdinary) {
|
|
321
|
+
if (input.mode === "acceptEdits")
|
|
322
|
+
return `under \`permissionMode: "acceptEdits"\` the mode asks only for a write outside the working directories — if this target is outside them, add its directory to \`additionalDirectories\`; otherwise (or besides) ${approver}`;
|
|
323
|
+
if (input.mode === undefined || input.mode === "default" || input.mode === "auto")
|
|
324
|
+
return `the mode's write gate asks every write under \`default\`: \`permissionMode: "acceptEdits"\` lets writes inside the working directories (the task root and \`additionalDirectories\`) through without asking (a question another policy raised still stands), or ${approver}`;
|
|
325
|
+
}
|
|
326
|
+
return `run with \`permissionMode: "bypassPermissions"\` to release this ask (the release is disclosed on the call), or ${approver}`;
|
|
327
|
+
}
|
|
328
|
+
export function fileCommandOptionsSentence(program) {
|
|
329
|
+
return `${program} with flags requires manual approval to ensure path safety: the mode cannot automatically validate ${program} commands that use flags, as some flags like --target-directory=PATH can bypass path validation.`;
|
|
330
|
+
}
|
|
331
|
+
export function arbitrateByMode(mode, c, disclosure, call) {
|
|
332
|
+
const has = (k) => c.classes.has(k);
|
|
333
|
+
const classes = ASK_CLASSES.filter((k) => c.classes.has(k));
|
|
334
|
+
switch (mode) {
|
|
335
|
+
case "dontAsk":
|
|
336
|
+
return (has("hook") && !c.row.hookAskFloor) || has("org_rule") || has("operator_forced") || has("governance_unavailable") ? { kind: "ask" } : { kind: "denied_by_mode", mode, askClass: c.askClass, classes };
|
|
337
|
+
case "bypassPermissions":
|
|
338
|
+
if (has("write_protection_unresolved"))
|
|
339
|
+
return { kind: "denied_by_mode", mode, askClass: c.askClass, classes };
|
|
340
|
+
if (c.row.bypassImmune)
|
|
341
|
+
return { kind: "ask" };
|
|
342
|
+
return {
|
|
343
|
+
kind: "released",
|
|
344
|
+
mode,
|
|
345
|
+
askClass: c.askClass,
|
|
346
|
+
classes,
|
|
347
|
+
...(disclosure?.origin !== undefined ? { origin: disclosure.origin } : {}),
|
|
348
|
+
...(disclosure?.mandate !== undefined ? { mandate: disclosure.mandate } : {}),
|
|
349
|
+
...(has("rule_unreadable") ? { reason: "rule_unreadable" } : {}),
|
|
350
|
+
};
|
|
351
|
+
case "acceptEdits":
|
|
352
|
+
return call?.fileCommand === true && c.classes.size === 1 && c.classes.has("ordinary") ? { kind: "accepted", mode } : { kind: "ask" };
|
|
353
|
+
case "plan":
|
|
354
|
+
case "default":
|
|
355
|
+
case "auto":
|
|
356
|
+
return { kind: "ask" };
|
|
357
|
+
default: {
|
|
358
|
+
const _exhaustive = mode;
|
|
359
|
+
return _exhaustive;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
183
362
|
}
|
|
@@ -5,6 +5,7 @@ import { defineTool, errorResult } from "./tools.js";
|
|
|
5
5
|
import { delimitUntrusted, inlineUntrusted } from "./untrusted-text.js";
|
|
6
6
|
import { toolFace } from "./tool-catalog-entries.js";
|
|
7
7
|
import { thrownText } from "./thrown-value.js";
|
|
8
|
+
import { callAnswersTo } from "./tool-roster.js";
|
|
8
9
|
export const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
|
|
9
10
|
export function classifyQuestionOutcome(outcome) {
|
|
10
11
|
let unavailable;
|
|
@@ -134,7 +135,7 @@ export function validateAskQuestions(questions) {
|
|
|
134
135
|
export function createDurableQuestionPolicy() {
|
|
135
136
|
return {
|
|
136
137
|
check(req) {
|
|
137
|
-
if (req
|
|
138
|
+
if (!callAnswersTo(req, ASK_USER_QUESTION_TOOL_NAME)) {
|
|
138
139
|
return { action: "allow" };
|
|
139
140
|
}
|
|
140
141
|
return { action: "ask", message: "AskUserQuestion: awaiting a human answer (durable)" };
|
|
@@ -45,6 +45,9 @@ export interface AskUnresolvableFacts {
|
|
|
45
45
|
/** Whether a durable approval park lane was ARMED for this run (`true`: the park had its one attempt and
|
|
46
46
|
* did not take the call — it declined or failed; `false`: no park lane at all, the usual shape). */
|
|
47
47
|
readonly parkLaneExisted: boolean;
|
|
48
|
+
/** `no_approver_wired` only: the caller-side change that would have let the call run, as the gate minted it for the
|
|
49
|
+
* refusal's tool text (`headlessApprovalRemedy`, ask-origin.ts) — carried verbatim, never re-composed here. */
|
|
50
|
+
readonly remedy?: string;
|
|
48
51
|
}
|
|
49
52
|
/**
|
|
50
53
|
* Compose the `delegation.ask_unresolvable` notice from the gate's deny-exit facts.
|
|
@@ -55,7 +58,7 @@ export interface AskUnresolvableFacts {
|
|
|
55
58
|
* an empty session or tool name, or a cause outside the closed set, is refused at the mint with a
|
|
56
59
|
* `TypeError` rather than shaped into a notice that would route nowhere or say nothing.
|
|
57
60
|
*
|
|
58
|
-
* `detail: { sessionId, toolName, toolCallId, cause, parkLaneExisted }` — `
|
|
59
|
-
* typed key by {@link import("./engine-notice.js").deliverEngineNotice}, the one throat.
|
|
61
|
+
* `detail: { sessionId, toolName, toolCallId, cause, parkLaneExisted, remedy? }` — `remedy` rides a headless
|
|
62
|
+
* auto-deny only (the sentence the refusal's tool text carries); `sessionId` is lifted onto the typed key by {@link import("./engine-notice.js").deliverEngineNotice}, the one throat.
|
|
60
63
|
*/
|
|
61
64
|
export declare function askUnresolvableNotice(facts: AskUnresolvableFacts): EngineNotice;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const ASK_UNRESOLVABLE_CAUSES = ["approver_unavailable", "no_approver_wired", "mandate_unreconstructible"];
|
|
2
|
+
Object.freeze(ASK_UNRESOLVABLE_CAUSES);
|
|
2
3
|
const ASK_UNRESOLVABLE_CAUSE_SET = new Set(ASK_UNRESOLVABLE_CAUSES);
|
|
3
4
|
export function isAskUnresolvableCause(v) {
|
|
4
5
|
return ASK_UNRESOLVABLE_CAUSE_SET.has(v);
|
|
@@ -9,7 +10,7 @@ const CAUSE_CLAUSE = {
|
|
|
9
10
|
mandate_unreconstructible: "an inherited durable-approval mandate binds this ask and this leg has no park facility to honor it",
|
|
10
11
|
};
|
|
11
12
|
export function askUnresolvableNotice(facts) {
|
|
12
|
-
const { sessionId, toolName, toolCallId, cause, parkLaneExisted } = facts;
|
|
13
|
+
const { sessionId, toolName, toolCallId, cause, parkLaneExisted, remedy } = facts;
|
|
13
14
|
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
14
15
|
throw new TypeError("delegation.ask_unresolvable: sessionId is required — a user-audience notice with no session routes nowhere");
|
|
15
16
|
}
|
|
@@ -26,8 +27,8 @@ export function askUnresolvableNotice(facts) {
|
|
|
26
27
|
return {
|
|
27
28
|
code: "delegation.ask_unresolvable",
|
|
28
29
|
message: `approval for "${toolName}" could not be resolved: ${CAUSE_CLAUSE[cause]} and ${park} — ` +
|
|
29
|
-
`the call was denied fail-closed and did not run. To let such approvals wait for a person, run with a checkpoint store and a ` +
|
|
30
|
+
`the call was denied fail-closed and did not run. ${typeof remedy === "string" && remedy.length > 0 ? `To let it run: ${remedy}. ` : ""}To let such approvals wait for a person, run with a checkpoint store and a ` +
|
|
30
31
|
`durable approval opt-in (or a live approver reachable for delegated children).`,
|
|
31
|
-
detail: { sessionId, toolName, toolCallId, cause, parkLaneExisted: parkLaneExisted === true },
|
|
32
|
+
detail: { sessionId, toolName, toolCallId, cause, parkLaneExisted: parkLaneExisted === true, ...(typeof remedy === "string" && remedy.length > 0 ? { remedy } : {}) },
|
|
32
33
|
};
|
|
33
34
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AutoModeRules, AutoModeWindowOptions } from "./auto-mode-prompt.js";
|
|
1
|
+
import type { AutoModeRules, AutoModeWindowOptions, DerivedDenyRule } from "./auto-mode-prompt.js";
|
|
2
2
|
import type { AutoModeDenialLimitOptions } from "./auto-mode.js";
|
|
3
3
|
/** The recipe format's version. A reader that does not know a version REFUSES it (never guesses): the
|
|
4
4
|
* recipe names the criteria a classifier enforces, and a partially-understood criteria set is the one
|
|
@@ -22,6 +22,14 @@ export interface AutoModeArmingRecipe {
|
|
|
22
22
|
rules?: AutoModeRules;
|
|
23
23
|
/** Settings-plane deny rules rendered into `<settings_deny_rules>`. */
|
|
24
24
|
settingsDenyRules?: readonly string[];
|
|
25
|
+
/**
|
|
26
|
+
* The ENGINE's deny lines rendered beside them — the arming leg's effective read-deny table as rules
|
|
27
|
+
* (`readDenyClassifierRules`: the rule text and its letter-case note). Prompt BODY, compared by the fold exactly as `settingsDenyRules` is: a
|
|
28
|
+
* redeeming host recomputes its own from its deployment's read-deny configuration and the row's task
|
|
29
|
+
* additions, and a difference refuses the rebuild (`settings_moved`) — the same rule as a moved deployment
|
|
30
|
+
* setting, no exemption.
|
|
31
|
+
*/
|
|
32
|
+
derivedDenyRules?: readonly DerivedDenyRule[];
|
|
25
33
|
/** `## Session Context` facts appended to the assembled document. */
|
|
26
34
|
sessionContext?: readonly string[];
|
|
27
35
|
/** Transcript-window bounds for the classify prompt. */
|
|
@@ -125,10 +133,14 @@ export declare function sanitizeAutoModeArmingRecipe(value: unknown): AutoModeAr
|
|
|
125
133
|
* caller is expected to say so out loud rather than park a silently thinner arming.
|
|
126
134
|
*
|
|
127
135
|
* Used on BOTH ends of the boundary: the Runner mints it at arming time (so a park can record it), and a
|
|
128
|
-
* redeeming host mints its OWN face into the `current` half of {@link foldAutoModeArming}.
|
|
136
|
+
* redeeming host mints its OWN face into the `current` half of {@link foldAutoModeArming}. `bind` carries
|
|
137
|
+
* what is the ENGINE's rather than the deployment's: the assembled prompt's digest (the arming side only)
|
|
138
|
+
* and the engine's deny lines (`derivedDenyRules`, both sides — a redeeming host passes the lines its own
|
|
139
|
+
* read-deny configuration yields over the row's task additions, `readDenyClassifierRules`).
|
|
129
140
|
*/
|
|
130
141
|
export declare function autoModeArmingRecipeOf(face: AutoModeArmingFace, bind?: {
|
|
131
142
|
promptDigest?: string;
|
|
143
|
+
derivedDenyRules?: readonly DerivedDenyRule[];
|
|
132
144
|
}): AutoModeArmingRecipe | undefined;
|
|
133
145
|
/** Why a rebuild refused. Every arm leaves the redemption on the PRE-#503 path (honest `unavailable` →
|
|
134
146
|
* the frozen approver chain → a human), so a refusal costs a second park, never a widened gate. */
|
|
@@ -172,8 +184,9 @@ export type AutoModeArmingFold = {
|
|
|
172
184
|
* ITS OWN declared settings rather than by whatever the previous redeemer happened to run under. A
|
|
173
185
|
* deployment that wants a tightening to be durable tightens its own settings, where it is auditable.
|
|
174
186
|
* · bodies EQUAL ⇒ rebuild, with `timeoutMs` taking the MINIMUM of the two — fail-closed: a shorter
|
|
175
|
-
* timeout turns a slow classifier into `unavailable` (
|
|
176
|
-
* a
|
|
187
|
+
* timeout turns a slow classifier into `unavailable` sooner (the call goes on to a person where the
|
|
188
|
+
* station has an approval seat, and is refused with a sentence that says so where it has none), and can
|
|
189
|
+
* never widen a verdict. The denial-limit triple
|
|
177
190
|
* (#556) folds the same way through {@link tightenDenialLimit} — fewer blocks before a person looks,
|
|
178
191
|
* and no window ever lengthened or removed.
|
|
179
192
|
* · bodies DIFFER ⇒ refuse. `settings_moved` normally; `epoch_inconsistent` when the two sides
|
|
@@ -12,6 +12,22 @@ function canonicalStrings(v) {
|
|
|
12
12
|
}
|
|
13
13
|
return v.length === 0 ? undefined : v.slice();
|
|
14
14
|
}
|
|
15
|
+
function canonicalDerivedDenyRules(v) {
|
|
16
|
+
if (v === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
if (!Array.isArray(v))
|
|
19
|
+
return null;
|
|
20
|
+
const out = [];
|
|
21
|
+
for (let i = 0; i < v.length; i++) {
|
|
22
|
+
const e = v[i];
|
|
23
|
+
if (!(i in v) || !isPlainRecord(e) || typeof e.rule !== "string")
|
|
24
|
+
return null;
|
|
25
|
+
if (e.anyCase !== undefined && typeof e.anyCase !== "boolean")
|
|
26
|
+
return null;
|
|
27
|
+
out.push({ rule: e.rule, ...(e.anyCase === true ? { anyCase: true } : {}) });
|
|
28
|
+
}
|
|
29
|
+
return out.length === 0 ? undefined : out;
|
|
30
|
+
}
|
|
15
31
|
function canonicalRuleSection(v) {
|
|
16
32
|
const list = canonicalStrings(v);
|
|
17
33
|
if (list === null || list === undefined)
|
|
@@ -74,10 +90,11 @@ export function sanitizeAutoModeArmingRecipe(value) {
|
|
|
74
90
|
if (value.v !== AUTO_MODE_ARMING_RECIPE_VERSION)
|
|
75
91
|
return undefined;
|
|
76
92
|
const settingsDenyRules = canonicalStrings(value.settingsDenyRules);
|
|
93
|
+
const derivedDenyRules = canonicalDerivedDenyRules(value.derivedDenyRules);
|
|
77
94
|
const sessionContext = canonicalStrings(value.sessionContext);
|
|
78
95
|
const timeoutMs = canonicalNumber(value.timeoutMs, 1, false);
|
|
79
96
|
const denialLimit = canonicalDenialLimit(value.denialLimit);
|
|
80
|
-
if (settingsDenyRules === null || sessionContext === null || timeoutMs === null || denialLimit === null)
|
|
97
|
+
if (settingsDenyRules === null || derivedDenyRules === null || sessionContext === null || timeoutMs === null || denialLimit === null)
|
|
81
98
|
return undefined;
|
|
82
99
|
let rules;
|
|
83
100
|
if (value.rules !== undefined) {
|
|
@@ -128,6 +145,7 @@ export function sanitizeAutoModeArmingRecipe(value) {
|
|
|
128
145
|
...(crossSessionMessagesRule === true ? { crossSessionMessagesRule: true } : {}),
|
|
129
146
|
...(rules !== undefined ? { rules } : {}),
|
|
130
147
|
...(settingsDenyRules !== undefined ? { settingsDenyRules } : {}),
|
|
148
|
+
...(derivedDenyRules !== undefined ? { derivedDenyRules } : {}),
|
|
131
149
|
...(sessionContext !== undefined ? { sessionContext } : {}),
|
|
132
150
|
...(window !== undefined ? { window } : {}),
|
|
133
151
|
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
@@ -139,6 +157,7 @@ export function autoModeArmingRecipeOf(face, bind) {
|
|
|
139
157
|
return sanitizeAutoModeArmingRecipe({
|
|
140
158
|
v: AUTO_MODE_ARMING_RECIPE_VERSION,
|
|
141
159
|
...(bind?.promptDigest !== undefined ? { promptDigest: bind.promptDigest } : {}),
|
|
160
|
+
...(bind?.derivedDenyRules !== undefined ? { derivedDenyRules: bind.derivedDenyRules } : {}),
|
|
142
161
|
...(face.rules !== undefined ? { rules: face.rules } : {}),
|
|
143
162
|
...(face.settingsDenyRules !== undefined ? { settingsDenyRules: face.settingsDenyRules } : {}),
|
|
144
163
|
...(face.sessionContext !== undefined ? { sessionContext: face.sessionContext } : {}),
|
|
@@ -156,6 +175,7 @@ function sameArmingBody(a, b) {
|
|
|
156
175
|
r.rules?.hardDeny ?? null,
|
|
157
176
|
r.rules?.environment ?? null,
|
|
158
177
|
r.settingsDenyRules ?? null,
|
|
178
|
+
r.derivedDenyRules ?? null,
|
|
159
179
|
r.sessionContext ?? null,
|
|
160
180
|
r.window?.maxEntries ?? null,
|
|
161
181
|
r.window?.maxCharsPerEntry ?? null,
|
|
@@ -163,13 +183,17 @@ function sameArmingBody(a, b) {
|
|
|
163
183
|
]);
|
|
164
184
|
return body(a) === body(b);
|
|
165
185
|
}
|
|
186
|
+
function armableRecipe(r) {
|
|
187
|
+
return r !== undefined && (r.timeoutMs === undefined || r.timeoutMs <= MAX_TIMER_DELAY_MS) ? r : undefined;
|
|
188
|
+
}
|
|
166
189
|
export function foldAutoModeArming(recorded, current) {
|
|
167
|
-
const rec = sanitizeAutoModeArmingRecipe(recorded);
|
|
190
|
+
const rec = armableRecipe(sanitizeAutoModeArmingRecipe(recorded));
|
|
168
191
|
if (rec === undefined) {
|
|
169
192
|
return {
|
|
170
193
|
ok: false,
|
|
171
194
|
reason: "malformed_recipe",
|
|
172
|
-
message: "the recorded auto-mode arming recipe is not readable by this build (unknown recipe version,
|
|
195
|
+
message: "the recorded auto-mode arming recipe is not readable by this build (unknown recipe version, not plain recipe data, or a " +
|
|
196
|
+
"classifier budget outside the range the live knob takes) — " +
|
|
173
197
|
"no classifier is rebuilt; the redemption keeps its honest refusal and the inherited ask flows the original chain",
|
|
174
198
|
};
|
|
175
199
|
}
|
|
@@ -182,12 +206,13 @@ export function foldAutoModeArming(recorded, current) {
|
|
|
182
206
|
"with no overrides of its own still declares that, and the declaration is what the bound is made of)",
|
|
183
207
|
};
|
|
184
208
|
}
|
|
185
|
-
const cur = sanitizeAutoModeArmingRecipe(current);
|
|
209
|
+
const cur = armableRecipe(sanitizeAutoModeArmingRecipe(current));
|
|
186
210
|
if (cur === undefined) {
|
|
187
211
|
return {
|
|
188
212
|
ok: false,
|
|
189
213
|
reason: "malformed_current_settings",
|
|
190
|
-
message: "the redeeming deployment's own auto-mode settings did not canonicalize into a recipe
|
|
214
|
+
message: "the redeeming deployment's own auto-mode settings did not canonicalize into a recipe its own arming accepts (a malformed " +
|
|
215
|
+
"member, or a classifier budget outside the range the live knob takes), so the recorded arming cannot be " +
|
|
191
216
|
"bounded by the settings in force — no classifier is rebuilt (fail-closed: an unbounded rebuild is the one outcome this fold exists to prevent)",
|
|
192
217
|
};
|
|
193
218
|
}
|
|
@@ -203,7 +228,7 @@ export function foldAutoModeArming(recorded, current) {
|
|
|
203
228
|
: {
|
|
204
229
|
ok: false,
|
|
205
230
|
reason: "settings_moved",
|
|
206
|
-
message: "the auto-mode settings moved between the recorded arming and this deployment's current ones (rule sections / settings-deny rules / " +
|
|
231
|
+
message: "the auto-mode settings moved between the recorded arming and this deployment's current ones (rule sections / settings-deny rules / the engine's read-deny lines / " +
|
|
207
232
|
"session context / window bounds / the cross-session lane rule differ), and free-text rule sets have no sound stricter-than ordering — refusing to rebuild " +
|
|
208
233
|
"(the strictest decidable answer: the inherited ask flows the original chain to a human)",
|
|
209
234
|
};
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hard cap on one classification RUNG (both attempts) when `AutoModeDeciderOptions.timeoutMs`
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* walk = this × rungs (2) =
|
|
2
|
+
* Hard cap on one classification RUNG (both attempts) when `AutoModeDeciderOptions.timeoutMs` /
|
|
3
|
+
* `RunnerDeps.autoMode.timeoutMs` is omitted. Sized for a self-hosted gateway, not a cloud API: a
|
|
4
|
+
* thinking-by-default gateway answers a classification in 5–165 s (Qwen 3.8 p50 11–24 s, kimi and GLM p50
|
|
5
|
+
* ~10 s), and queueing plus prefill of 2–5 min is a normal wait on such a gateway (`docs/TIMEOUTS.md`). 180 s
|
|
6
|
+
* clears the slowest classification observed with margin; CC 2.1.259's per-attempt base is 60 s (`j7e`,
|
|
7
|
+
* stretched to 120 s with prompt size). Worst-case walk = this × rungs (2) = 360 s. Where the leg has an
|
|
8
|
+
* approval seat a rung that runs out of time costs a question to a person, not a refusal (see the gate's
|
|
9
|
+
* classifier station).
|
|
8
10
|
*/
|
|
9
|
-
export declare const AUTO_MODE_DEFAULT_TIMEOUT_MS =
|
|
11
|
+
export declare const AUTO_MODE_DEFAULT_TIMEOUT_MS = 180000;
|
|
12
|
+
/**
|
|
13
|
+
* The classifier call's transient retry budget — the reference implementation's short ladder (2.1.280 `J9e = 4`): a
|
|
14
|
+
* provider error (an HTTP 5xx, a dropped connection) is retried this many times inside the rung's budget, then the rung
|
|
15
|
+
* reports `unavailable` / `error`. Where the leg has an approval seat that question goes to a person, so a long ladder
|
|
16
|
+
* would only make the person wait out the rung's budget and hear "timed out" for a failure that was not one.
|
|
17
|
+
*/
|
|
18
|
+
export declare const AUTO_MODE_CLASSIFIER_MAX_RETRIES = 4;
|
|
10
19
|
/**
|
|
11
20
|
* What an arming RECORDING that carries no `timeoutMs` meant when it was written: the default of its day. Every
|
|
12
21
|
* recipe minted since the deadline moved to 60 s MATERIALISES its bound, so an absent recorded knob dates the
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export const AUTO_MODE_DEFAULT_TIMEOUT_MS =
|
|
1
|
+
export const AUTO_MODE_DEFAULT_TIMEOUT_MS = 180_000;
|
|
2
|
+
export const AUTO_MODE_CLASSIFIER_MAX_RETRIES = 4;
|
|
2
3
|
export const AUTO_MODE_RECORDED_ABSENT_TIMEOUT_MS = 15_000;
|
|
3
4
|
export const AUTO_MODE_DEFAULT_WINDOW_MAX_ENTRIES = 40;
|
|
4
5
|
export const AUTO_MODE_DEFAULT_WINDOW_MAX_CHARS = 2_000;
|