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