@sema-agent/core 7.33.8 → 7.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/CHANGELOG.md +339 -0
  2. package/dist/agents/cross-session-judge.d.ts +16 -7
  3. package/dist/agents/cross-session-judge.js +18 -15
  4. package/dist/agents/peer-session-drain.d.ts +4 -0
  5. package/dist/agents/peer-session-drain.js +5 -2
  6. package/dist/agents/repair-loop.d.ts +1 -1
  7. package/dist/agents/subagent.d.ts +3 -0
  8. package/dist/agents/subagent.js +12 -3
  9. package/dist/brain/anthropic.js +1 -0
  10. package/dist/brain/open-responses.js +1 -0
  11. package/dist/brain/openai.js +1 -0
  12. package/dist/brain/stream-engine.d.ts +3 -0
  13. package/dist/brain/stream-engine.js +2 -1
  14. package/dist/core/a2a.d.ts +3 -1
  15. package/dist/core/a2a.js +1 -1
  16. package/dist/core/ask-origin.d.ts +351 -154
  17. package/dist/core/ask-origin.js +223 -45
  18. package/dist/core/ask-unresolvable-notice.d.ts +5 -2
  19. package/dist/core/ask-unresolvable-notice.js +4 -3
  20. package/dist/core/auto-mode-arming.d.ts +17 -4
  21. package/dist/core/auto-mode-arming.js +31 -6
  22. package/dist/core/auto-mode-defaults.d.ts +16 -7
  23. package/dist/core/auto-mode-defaults.js +2 -1
  24. package/dist/core/auto-mode-directive.d.ts +18 -30
  25. package/dist/core/auto-mode-directive.js +9 -18
  26. package/dist/core/auto-mode-prompt.d.ts +62 -1
  27. package/dist/core/auto-mode-prompt.js +42 -4
  28. package/dist/core/auto-mode-rebuild.d.ts +2 -1
  29. package/dist/core/auto-mode-rebuild.js +1 -0
  30. package/dist/core/auto-mode.d.ts +32 -3
  31. package/dist/core/auto-mode.js +80 -41
  32. package/dist/core/checkpoint-store.d.ts +50 -35
  33. package/dist/core/checkpoint-store.js +58 -3
  34. package/dist/core/child-clamp-seats.js +1 -0
  35. package/dist/core/classifier-unavailable-arm.d.ts +57 -0
  36. package/dist/core/classifier-unavailable-arm.js +17 -0
  37. package/dist/core/effective-path-target.d.ts +12 -0
  38. package/dist/core/effective-path-target.js +18 -7
  39. package/dist/core/engine-notice.d.ts +53 -2
  40. package/dist/core/fs-write-gate-policy.d.ts +5 -1
  41. package/dist/core/fs-write-gate-policy.js +4 -1
  42. package/dist/core/gate-fold.js +17 -1
  43. package/dist/core/gate-lanes.js +111 -46
  44. package/dist/core/gate-outcome.d.ts +40 -7
  45. package/dist/core/gate-outcome.js +26 -4
  46. package/dist/core/governance-codes.d.ts +1 -1
  47. package/dist/core/governance-codes.js +12 -0
  48. package/dist/core/hooks.d.ts +50 -29
  49. package/dist/core/hooks.js +1 -3
  50. package/dist/core/mcp.js +1 -1
  51. package/dist/core/mode-policy.d.ts +182 -0
  52. package/dist/core/mode-policy.js +179 -0
  53. package/dist/core/oracle-isolation.d.ts +3 -3
  54. package/dist/core/permission-rule-model.d.ts +16 -3
  55. package/dist/core/permission-rule-model.js +36 -3
  56. package/dist/core/permission-rule-provider.d.ts +22 -11
  57. package/dist/core/permission-rule-provider.js +17 -7
  58. package/dist/core/permission-rules.d.ts +23 -1
  59. package/dist/core/permission-rules.js +35 -8
  60. package/dist/core/persisted-rule-arms.d.ts +10 -9
  61. package/dist/core/persisted-rule-arms.js +9 -6
  62. package/dist/core/present-plan-tool.d.ts +1 -1
  63. package/dist/core/present-plan-tool.js +3 -3
  64. package/dist/core/read-only-shell.d.ts +19 -19
  65. package/dist/core/read-only-shell.js +38 -12
  66. package/dist/core/retired-keys.d.ts +9 -3
  67. package/dist/core/retired-keys.js +5 -1
  68. package/dist/core/runner/classifier-ladder.d.ts +43 -1
  69. package/dist/core/runner/classifier-ladder.js +29 -0
  70. package/dist/core/runner/contracts.d.ts +37 -35
  71. package/dist/core/runner/denial-limit-arms.d.ts +30 -7
  72. package/dist/core/runner/denial-limit-arms.js +15 -9
  73. package/dist/core/runner/env-capability-census.js +1 -1
  74. package/dist/core/runner/gate-exit.d.ts +30 -4
  75. package/dist/core/runner/gate-exit.js +28 -7
  76. package/dist/core/runner/permission-rule-lanes.d.ts +9 -5
  77. package/dist/core/runner/permission-rule-lanes.js +44 -40
  78. package/dist/core/runner/persisted-path-rows.d.ts +64 -0
  79. package/dist/core/runner/persisted-path-rows.js +89 -0
  80. package/dist/core/runner/prepare-artifact.d.ts +5 -1
  81. package/dist/core/runner/prepare-artifact.js +9 -3
  82. package/dist/core/runner/prepare-ask-lane.d.ts +5 -3
  83. package/dist/core/runner/prepare-ask-lane.js +1 -1
  84. package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
  85. package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
  86. package/dist/core/runner/prepare-gate-stations.d.ts +33 -6
  87. package/dist/core/runner/prepare-gate-stations.js +32 -19
  88. package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
  89. package/dist/core/runner/prepare-hands-readface.js +64 -53
  90. package/dist/core/runner/prepare-inherited-gate.d.ts +22 -22
  91. package/dist/core/runner/prepare-inherited-gate.js +37 -23
  92. package/dist/core/runner/prepare-park-ask.d.ts +2 -2
  93. package/dist/core/runner/prepare-park-ask.js +2 -1
  94. package/dist/core/runner/prepare-policy-chain.d.ts +22 -2
  95. package/dist/core/runner/prepare-policy-chain.js +39 -12
  96. package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
  97. package/dist/core/runner/prepare-protocol-tools.js +13 -3
  98. package/dist/core/runner/prepare-run-refs.d.ts +6 -6
  99. package/dist/core/runner/prepare-run-refs.js +9 -4
  100. package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
  101. package/dist/core/runner/prepare-safety-scan.js +19 -3
  102. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
  103. package/dist/core/runner/prepare-suspend-saga.js +6 -6
  104. package/dist/core/runner/prepare-task.js +51 -22
  105. package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
  106. package/dist/core/runner/prepare-wiring-manifest.js +5 -3
  107. package/dist/core/runner/read-root-grant.d.ts +9 -0
  108. package/dist/core/runner/read-root-grant.js +12 -0
  109. package/dist/core/runner/retired-runner-deps.js +1 -0
  110. package/dist/core/runner/run-identity-wiring.js +1 -1
  111. package/dist/core/runner/run-leg.js +24 -15
  112. package/dist/core/runner/run-notification-lane.js +1 -0
  113. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
  114. package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
  115. package/dist/core/runner/run-turn-boundary.js +1 -1
  116. package/dist/core/runner/runtask.js +3 -0
  117. package/dist/core/runner/stream-mode-verb.d.ts +27 -0
  118. package/dist/core/runner/stream-mode-verb.js +19 -0
  119. package/dist/core/runner/turn-attachments.d.ts +3 -3
  120. package/dist/core/runner/turn-attachments.js +1 -1
  121. package/dist/core/runner/write-protection-station.d.ts +7 -1
  122. package/dist/core/runner/write-protection-station.js +6 -3
  123. package/dist/core/runner-deps.d.ts +31 -34
  124. package/dist/core/runtime-caps.d.ts +12 -3
  125. package/dist/core/safety-axis-vocab.d.ts +2 -2
  126. package/dist/core/safety-axis-vocab.js +1 -1
  127. package/dist/core/sensitive-path-policy.d.ts +4 -2
  128. package/dist/core/sensitive-path-policy.js +39 -30
  129. package/dist/core/task-event.d.ts +1 -1
  130. package/dist/core/task-result.d.ts +1 -1
  131. package/dist/core/task-spec.d.ts +41 -89
  132. package/dist/core/task-stream.d.ts +16 -0
  133. package/dist/core/terminal-cause.js +1 -0
  134. package/dist/core/tighten-task-spec.d.ts +2 -2
  135. package/dist/core/tighten-task-spec.js +12 -14
  136. package/dist/core/tool-catalog-entries.js +4 -3
  137. package/dist/core/tool-policy.d.ts +54 -25
  138. package/dist/core/tool-policy.js +43 -2
  139. package/dist/core/tool-registry.d.ts +2 -0
  140. package/dist/core/tool-registry.js +3 -0
  141. package/dist/core/tool-spec.d.ts +8 -9
  142. package/dist/core/trace.d.ts +7 -6
  143. package/dist/core/types.d.ts +2 -1
  144. package/dist/core/wiring-manifest.d.ts +31 -5
  145. package/dist/core/wiring-manifest.js +28 -1
  146. package/dist/core/write-protect.d.ts +11 -1
  147. package/dist/core/write-protect.js +26 -15
  148. package/dist/engine/execution-env/node-execution-env.d.ts +3 -0
  149. package/dist/engine/execution-env/node-execution-env.js +11 -1
  150. package/dist/engine/harness/types.d.ts +6 -7
  151. package/dist/engine/llm/types.d.ts +2 -4
  152. package/dist/engine/loop/types.d.ts +7 -0
  153. package/dist/index.d.ts +15 -5
  154. package/dist/index.js +12 -3
  155. package/dist/orchestration/workflow-governance.js +1 -1
  156. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  157. package/dist/orchestration/workflow-size-guideline.js +2 -2
  158. package/dist/tools/artifact/artifact-tool.d.ts +17 -0
  159. package/dist/tools/artifact/artifact-tool.js +16 -1
  160. package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
  161. package/dist/tools/fs/bash-readonly-classifier.js +88 -0
  162. package/dist/tools/fs/containment-face.d.ts +9 -3
  163. package/dist/tools/fs/containment-face.js +2 -0
  164. package/dist/tools/fs/file-read-check.d.ts +49 -0
  165. package/dist/tools/fs/file-read-check.js +71 -0
  166. package/dist/tools/fs/fs-bash.d.ts +3 -3
  167. package/dist/tools/fs/fs-read.d.ts +1 -0
  168. package/dist/tools/fs/fs-search-tools.d.ts +2 -2
  169. package/dist/tools/fs/fs-search-tools.js +4 -4
  170. package/dist/tools/fs/fs-shared.js +20 -6
  171. package/dist/tools/fs/fs-write.d.ts +4 -3
  172. package/dist/tools/fs/fs-write.js +9 -9
  173. package/dist/tools/fs/index.d.ts +39 -3
  174. package/dist/tools/fs/index.js +38 -15
  175. package/dist/tools/fs/read-deny-home.d.ts +93 -0
  176. package/dist/tools/fs/read-deny-home.js +225 -0
  177. package/dist/tools/fs/read-deny.d.ts +176 -34
  178. package/dist/tools/fs/read-deny.js +268 -61
  179. package/dist/tools/fs/safety.d.ts +6 -1
  180. package/dist/tools/fs/safety.js +18 -5
  181. package/dist/tools/fs/search.d.ts +3 -0
  182. package/dist/tools/fs/search.js +47 -9
  183. package/dist/tools/fs/shell-read-check.d.ts +42 -11
  184. package/dist/tools/fs/shell-read-check.js +188 -13
  185. package/dist/tools/fs/shell-read-establish.d.ts +7 -3
  186. package/dist/tools/fs/shell-read-establish.js +134 -21
  187. package/dist/tools/fs/shell-read-identity.d.ts +7 -0
  188. package/dist/tools/fs/shell-read-identity.js +21 -0
  189. package/dist/tools/fs/shell-read-judge.d.ts +92 -32
  190. package/dist/tools/fs/shell-read-judge.js +43 -18
  191. package/dist/tools/fs/shell-read-plan.d.ts +4 -0
  192. package/dist/tools/fs/shell-read-plan.js +26 -4
  193. package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
  194. package/dist/tools/fs/shell-read-write-targets.js +173 -0
  195. package/package.json +1 -1
  196. package/test/export-surface.snapshot.json +103 -11
  197. package/wire-changes/core@7.34.0.json +2621 -0
@@ -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 doorOutsideTable = ask?.requiresRealApproval === true || ask?.decisionReason === "hook" || ask?.inheritedUnresolved === true || ask?.ancestorResolved === true;
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
- return mandated ? { mandated: true, ...(mandate !== undefined ? { mandate } : {}) } : {};
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.ruleStore !== undefined)
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 (bindsEveryAutomaticLane(word))
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,214 @@ 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 function probeMandatedAsk(ask) {
182
- return bindsEveryAutomaticLane(persistedRuleMandateOf({ probeMandated: ask.probeMandated === true }));
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 },
196
+ hook: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
197
+ ancestor: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
198
+ unverifiable_delete: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false },
199
+ transcript_write: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false },
200
+ org_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
201
+ governance_unavailable: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
202
+ rule_unreadable: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
203
+ ask_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
204
+ denial_limit_fallback: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
205
+ operator_forced: { bypassImmune: true, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false },
206
+ write_protection_unresolved: { bypassImmune: true, classifierEligible: false, sandboxAdmission: true, ruleMayClear: false },
207
+ probe: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false },
208
+ write_protection: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false },
209
+ tool_marks_mcp: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false },
210
+ tool_marks_a2a: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false },
211
+ policy_rra: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false },
212
+ artifact_shared_read: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
213
+ mode: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
214
+ ordinary: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: true },
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;
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
+ }
259
+ return { bypassImmune, classifierEligible, sandboxAdmission, ruleMayClear };
260
+ }
261
+ export function classifyAsk(d, facts) {
262
+ const classes = decisionAskClassesOf({ ...d, foldedAskClasses: undefined });
263
+ classes.delete("policy_rra");
264
+ if (facts.contentQuestion)
265
+ classes.add("content_question");
266
+ if (facts.markedUnresolvable)
267
+ classes.add("ancestor");
268
+ if (facts.org === "rule")
269
+ classes.add("org_rule");
270
+ if (facts.org === "unavailable")
271
+ classes.add("governance_unavailable");
272
+ if (facts.ruleCallUnreadable === true)
273
+ classes.add("rule_unreadable");
274
+ const marks = facts.mandateMarks;
275
+ if (marks.shellGated === true && marks.irreversibility === "always")
276
+ classes.add("operator_forced");
277
+ const toolMarked = marks.shellGated !== true && (marks.egress === true || marks.irreversibility === "always" || marks.irreversibility === "maybe");
278
+ if (toolMarked) {
279
+ const sources = facts.toolMarkSources;
280
+ if (sources === undefined || sources.size === 0 || sources.has("deployment"))
281
+ classes.add("operator_forced");
282
+ if (sources?.has("mcp_annotation"))
283
+ classes.add("tool_marks_mcp");
284
+ if (sources?.has("a2a_default"))
285
+ classes.add(facts.a2aIsolatePeers === true ? "operator_forced" : "tool_marks_a2a");
286
+ }
287
+ if (marks.probeUnanswered === true)
288
+ classes.add("probe");
289
+ if (marks.writeProtectionUnresolved === true)
290
+ classes.add("write_protection_unresolved");
291
+ if (marks.writeProtected === true)
292
+ classes.add("write_protection");
293
+ for (const c of facts.ancestorClasses ?? [])
294
+ classes.add(c);
295
+ if (d.requiresRealApproval === true && ![...classes].some((c) => RRA_EXPLAINERS.has(c)))
296
+ classes.add("policy_rra");
297
+ for (const c of d.foldedAskClasses ?? [])
298
+ classes.add(c);
299
+ if (classes.size === 0 && d.mandated === true)
300
+ classes.add("operator_forced");
301
+ if (classes.size === 0)
302
+ classes.add("ordinary");
303
+ const askClass = ASK_CLASSES.find((c) => classes.has(c));
304
+ return { classes, askClass, row: foldArbitrationRow(classes) };
305
+ }
306
+ export function headlessDenialMessage(toolName, askMessage) {
307
+ 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}`;
308
+ }
309
+ export function headlessApprovalRemedy(input) {
310
+ const approver = "wire an approver (`onAsk`) to put it to a person";
311
+ const c = input.classification;
312
+ if (input.mode === "acceptEdits" && input.unplacedOptions !== undefined && (c === undefined || (c.classes.size === 1 && c.classes.has("ordinary"))))
313
+ return `${fileCommandOptionsSentence(input.unplacedOptions.program)} Spell the ${input.unplacedOptions.program} without option words for the mode to answer it, or ${approver}`;
314
+ if (c?.row.bypassImmune === true)
315
+ return `no permission mode releases this ask (${c.askClass}): ${approver}, or change the call`;
316
+ if (input.mode === "plan" && c?.classes.has("mode") === true)
317
+ 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}`;
318
+ const onlyOrdinary = c === undefined || (c.classes.size === 1 && c.classes.has("ordinary"));
319
+ if (input.coveredWrite && onlyOrdinary) {
320
+ if (input.mode === "acceptEdits")
321
+ 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}`;
322
+ if (input.mode === undefined || input.mode === "default" || input.mode === "auto")
323
+ 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}`;
324
+ }
325
+ return `run with \`permissionMode: "bypassPermissions"\` to release this ask (the release is disclosed on the call), or ${approver}`;
326
+ }
327
+ export function fileCommandOptionsSentence(program) {
328
+ 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.`;
329
+ }
330
+ export function arbitrateByMode(mode, c, disclosure, call) {
331
+ const has = (k) => c.classes.has(k);
332
+ const classes = ASK_CLASSES.filter((k) => c.classes.has(k));
333
+ switch (mode) {
334
+ case "dontAsk":
335
+ return has("hook") || has("org_rule") || has("operator_forced") || has("governance_unavailable") ? { kind: "ask" } : { kind: "denied_by_mode", mode, askClass: c.askClass, classes };
336
+ case "bypassPermissions":
337
+ if (has("write_protection_unresolved"))
338
+ return { kind: "denied_by_mode", mode, askClass: c.askClass, classes };
339
+ if (c.row.bypassImmune)
340
+ return { kind: "ask" };
341
+ return {
342
+ kind: "released",
343
+ mode,
344
+ askClass: c.askClass,
345
+ classes,
346
+ ...(disclosure?.origin !== undefined ? { origin: disclosure.origin } : {}),
347
+ ...(disclosure?.mandate !== undefined ? { mandate: disclosure.mandate } : {}),
348
+ ...(has("rule_unreadable") ? { reason: "rule_unreadable" } : {}),
349
+ };
350
+ case "acceptEdits":
351
+ return call?.fileCommand === true && c.classes.size === 1 && c.classes.has("ordinary") ? { kind: "accepted", mode } : { kind: "ask" };
352
+ case "plan":
353
+ case "default":
354
+ case "auto":
355
+ return { kind: "ask" };
356
+ default: {
357
+ const _exhaustive = mode;
358
+ return _exhaustive;
359
+ }
360
+ }
183
361
  }
@@ -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 }` — `sessionId` is lifted onto the
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` (a deny that says so) sooner, and can never widen
176
- * a verdict. The denial-limit triple
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, or not plain recipe data) — " +
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, so the recorded arming cannot be " +
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` is omitted — CC
3
- * 2.1.259's per-attempt classifier base (`j7e = 60000`; 2.1.250 `oue`, the same number), which upstream
4
- * stretches to 120 s with prompt size and sema leaves to the deployment knob. The 15 s that stood here was
5
- * never upstream's: live 2026-09-15 a thinking-by-default gateway answers in 5–165 s (Qwen 3.8 p50 11–24 s,
6
- * kimi and GLM p50 ~10 s), so 15 s turned most classifications into `unavailable (timed out)`. Worst-case
7
- * walk = this × rungs (2) = 120 s.
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 = 60000;
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 = 60_000;
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;
@@ -1,4 +1,6 @@
1
- import type { ApproverPosture } from "./task-spec.js";
1
+ import type { AskEffective } from "./wiring-manifest.js";
2
+ /** The permission-mode words the composition reads (the engine's closed set, as the wire spells it). */
3
+ import type { PermissionMode } from "./ask-origin.js";
2
4
  /** The auto-mode directive body (title + the two paragraphs). Rendered when the run's auto-mode
3
5
  * INTENT is resolved true — the seat, the chain it inherited, or the bit a suspend recorded. */
4
6
  export declare const AUTO_MODE_DIRECTIVE: string;
@@ -8,39 +10,25 @@ export declare const BASH_FIRST_DIRECTIVE: string;
8
10
  /** The bypass-posture directive body: the reference implementation's heading over the shell-first
9
11
  * paragraph, the exact pair its bypass arm renders. */
10
12
  export declare const BYPASS_POSTURE_DIRECTIVE: string;
11
- /** The posture words this engine accepts, as literals (the door refuses anything else loudly). The
12
- * TYPE is declared beside the seat it belongs to ({@link ApproverPosture}, task-spec.ts); this array
13
- * is its runtime twin, and the assignment below is the compile-time tie between them. */
14
- export declare const APPROVER_POSTURES: readonly ApproverPosture[];
15
- /** Value-shape check for the word — used by the spec door below and by the chain/checkpoint readers. */
16
- export declare function isApproverPosture(value: unknown): value is ApproverPosture;
17
- /** The SEAT's door screen, declared beside the vocabulary it screens against. The renderer keys on the
18
- * exact word, so any other present value would render zero bytes and be indistinguishable from "no
19
- * posture declared"; it is refused instead. The value that passed is the value RETURNED — the render
20
- * reads that snapshot, never the caller's spec object again. */
21
- export declare function refuseInvalidApproverPosture(spec: {
22
- approverPosture?: ApproverPosture;
23
- }): ApproverPosture | undefined;
24
- /** The resolved posture facts of one leg: both already folded across seat, chain and checkpoint. */
13
+ /** The mode facts of one leg, both the ENGINE's own resolution — never a requested value, never spec shape. */
25
14
  export interface ApprovalPostureFacts {
26
- /** The run's resolved auto-mode intent (seat ∨ inherited chain ∨ checkpoint seed). */
27
- autoMode: boolean;
28
- /** The declared approver posture, absent when the host declared none. */
29
- approverPosture?: ApproverPosture;
15
+ /** The leg's EFFECTIVE permission mode (a requested mode the deployment refused has already folded back). */
16
+ mode: PermissionMode;
17
+ /** The wiring manifest's ask derivation — `auto_allow` means no person answers an ask on this leg. */
18
+ askEffective: AskEffective;
30
19
  }
31
20
  /**
32
- * The ONE composition, and the ONE invariant: **the segment the run's posture calls for is stated
33
- * exactly once in the active model context**. `alreadyStated` is the transcript's ENGINE REGIONS (read
34
- * by metadata — a caller's own bytes are never in one, so a spliced copy cannot suppress the engine's);
35
- * a segment already standing there is not restated, and a segment that has left the context — compacted
36
- * away, or never delivered — is. Returns `undefined` when there is nothing left to say, which is both
37
- * "no posture declared" (zero bytes, byte-identical to the pre-seat shape) and "already said".
21
+ * The ONE composition, and the ONE invariant: **the segment the leg's mode calls for is stated exactly
22
+ * once in the active model context**. `statedBodies` is the transcript's ENGINE REGIONS (read by metadata
23
+ * — a caller's own bytes are never in one, so a spliced copy cannot suppress the engine's); a segment
24
+ * already standing there is not restated, and a segment that has left the context — compacted away, or
25
+ * never delivered — is. Returns `undefined` when there is nothing left to say, which is both "no segment
26
+ * for this mode" (zero bytes) and "already said".
38
27
  *
39
- * Segment-wise rather than body-wise on purpose: a leg whose posture CHANGES — it gains the bypass fact,
40
- * so the segment it owes changes — must state the new segment without restating one already standing,
41
- * and must be able to read a body an earlier leg minted under a composition that joined two. The caller
42
- * wraps the result in the engine's marked reminder shell; this module never wraps, so the mark stays
43
- * owned by its own mint home.
28
+ * Segment-wise rather than body-wise on purpose: a leg whose mode CHANGES — it now runs under bypass, so the
29
+ * segment it owes changes — must state the new segment without restating one already standing, and must be
30
+ * able to read a body an earlier leg minted under a composition that joined two. The caller wraps the result
31
+ * in the engine's marked reminder shell; this module never wraps, so the mark stays owned by its own mint home.
44
32
  */
45
33
  export declare function composeApprovalPostureDirective(facts: ApprovalPostureFacts, statedBodies?: readonly string[]): string | undefined;
46
34
  /** SHA-256 of a directive body — the pins' lock port, so the lock is computed the one way. */