@sema-agent/core 7.52.0 → 7.54.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 (357) hide show
  1. package/CHANGELOG.md +470 -0
  2. package/LICENSE +3 -1
  3. package/README.md +3 -1
  4. package/dist/agents/cross-session-envelope.d.ts +8 -0
  5. package/dist/agents/cross-session-envelope.js +6 -0
  6. package/dist/agents/cross-session-judge.d.ts +21 -1
  7. package/dist/agents/cross-session-judge.js +8 -0
  8. package/dist/agents/cross-session-ref.d.ts +5 -0
  9. package/dist/agents/cross-session-ref.js +1 -0
  10. package/dist/agents/org-delivery-gate.d.ts +122 -0
  11. package/dist/agents/org-delivery-gate.js +69 -0
  12. package/dist/agents/peer-held-queue.d.ts +9 -4
  13. package/dist/agents/peer-held-queue.js +6 -5
  14. package/dist/agents/peer-session-drain.d.ts +35 -6
  15. package/dist/agents/peer-session-drain.js +271 -182
  16. package/dist/agents/read-inbox-tool.d.ts +7 -0
  17. package/dist/agents/read-inbox-tool.js +5 -1
  18. package/dist/agents/send-message-tool.js +3 -4
  19. package/dist/agents/subagent.d.ts +6 -5
  20. package/dist/agents/subagent.js +117 -83
  21. package/dist/agents/tool-filter.d.ts +25 -4
  22. package/dist/agents/tool-filter.js +13 -3
  23. package/dist/brain/anthropic.d.ts +6 -1
  24. package/dist/brain/anthropic.js +4 -4
  25. package/dist/brain/model-presets.js +2 -1
  26. package/dist/brain/route-adjudicator.d.ts +6 -0
  27. package/dist/brain/routing.d.ts +24 -1
  28. package/dist/brain/routing.js +54 -19
  29. package/dist/brain/timeout.js +5 -4
  30. package/dist/core/a2a.d.ts +5 -2
  31. package/dist/core/a2a.js +33 -14
  32. package/dist/core/allow-entry.d.ts +3 -1
  33. package/dist/core/allow-entry.js +3 -2
  34. package/dist/core/approval-object.d.ts +87 -0
  35. package/dist/core/approval-object.js +85 -0
  36. package/dist/core/arg-summary.d.ts +3 -2
  37. package/dist/core/arg-summary.js +17 -11
  38. package/dist/core/ask-origin.d.ts +43 -27
  39. package/dist/core/ask-origin.js +20 -11
  40. package/dist/core/ask-question.d.ts +0 -1
  41. package/dist/core/ask-question.js +4 -67
  42. package/dist/core/ask-unresolvable-notice.d.ts +3 -2
  43. package/dist/core/auto-compaction.d.ts +106 -50
  44. package/dist/core/auto-compaction.js +69 -53
  45. package/dist/core/auto-mode-prompt.d.ts +4 -2
  46. package/dist/core/auto-mode-prompt.js +4 -3
  47. package/dist/core/canonical-json.d.ts +69 -0
  48. package/dist/core/canonical-json.js +130 -8
  49. package/dist/core/checkpoint-requirements.d.ts +13 -7
  50. package/dist/core/checkpoint-requirements.js +4 -1
  51. package/dist/core/checkpoint-store.d.ts +60 -36
  52. package/dist/core/checkpoint-store.js +7 -6
  53. package/dist/core/child-clamp-seats.d.ts +45 -6
  54. package/dist/core/child-clamp-seats.js +56 -4
  55. package/dist/core/compliance.js +3 -1
  56. package/dist/core/consolidate-scope.d.ts +1 -1
  57. package/dist/core/cross-boundary-taint.d.ts +213 -0
  58. package/dist/core/cross-boundary-taint.js +196 -0
  59. package/dist/core/decision-receipt.d.ts +44 -1
  60. package/dist/core/decision-receipt.js +16 -0
  61. package/dist/core/effective-path-target.d.ts +28 -6
  62. package/dist/core/effective-path-target.js +28 -2
  63. package/dist/core/engine-notice.d.ts +47 -4
  64. package/dist/core/engine-notice.js +17 -1
  65. package/dist/core/engine-storage-backend.d.ts +73 -0
  66. package/dist/core/engine-storage-backend.js +1 -0
  67. package/dist/core/file-history-store.d.ts +6 -2
  68. package/dist/core/file-history-store.js +1 -0
  69. package/dist/core/gate-fold.js +13 -4
  70. package/dist/core/gate-lanes.js +41 -34
  71. package/dist/core/gate-outcome.d.ts +18 -9
  72. package/dist/core/governance-codes.d.ts +9 -2
  73. package/dist/core/governance-codes.js +9 -0
  74. package/dist/core/hands-band-screen.d.ts +7 -0
  75. package/dist/core/hands-band-screen.js +32 -0
  76. package/dist/core/hooks.d.ts +21 -19
  77. package/dist/core/hooks.js +6 -5
  78. package/dist/core/locked-config.d.ts +110 -31
  79. package/dist/core/locked-config.js +155 -42
  80. package/dist/core/lsp.d.ts +1 -1
  81. package/dist/core/mcp-probe.js +1 -1
  82. package/dist/core/mcp-server-spec.d.ts +152 -17
  83. package/dist/core/mcp-server-spec.js +223 -40
  84. package/dist/core/mcp.d.ts +21 -14
  85. package/dist/core/mcp.js +59 -54
  86. package/dist/core/memory-engine/engine.d.ts +14 -19
  87. package/dist/core/memory-engine/engine.js +9 -6
  88. package/dist/core/memory-engine/export-bundle.d.ts +1 -1
  89. package/dist/core/memory-engine/export-bundle.js +1 -1
  90. package/dist/core/memory-engine/file-backend.d.ts +2 -6
  91. package/dist/core/memory-engine/file-backend.js +1 -12
  92. package/dist/core/memory.d.ts +3 -3
  93. package/dist/core/mode-policy.d.ts +16 -26
  94. package/dist/core/mode-policy.js +5 -18
  95. package/dist/core/mods/kernel/dispatch.js +13 -15
  96. package/dist/core/mods/kernel/events.d.ts +9 -6
  97. package/dist/core/mods/kernel/events.js +2 -2
  98. package/dist/core/mods/kernel/receive.d.ts +41 -11
  99. package/dist/core/mods/kernel/receive.js +124 -54
  100. package/dist/core/mods/kernel/site-rules.d.ts +80 -15
  101. package/dist/core/mods/kernel/site-rules.js +232 -40
  102. package/dist/core/mods/stations.d.ts +0 -6
  103. package/dist/core/mods/stations.js +0 -7
  104. package/dist/core/mounted-mcp.d.ts +57 -0
  105. package/dist/core/mounted-mcp.js +71 -2
  106. package/dist/core/own-property-receipt.d.ts +13 -0
  107. package/dist/core/own-property-receipt.js +3 -1
  108. package/dist/core/parent-spec-seat.d.ts +12 -15
  109. package/dist/core/parent-spec-seat.js +14 -6
  110. package/dist/core/pause-registry.d.ts +35 -1
  111. package/dist/core/pause-registry.js +10 -0
  112. package/dist/core/permission-rule-consent.d.ts +18 -5
  113. package/dist/core/permission-rule-consent.js +13 -8
  114. package/dist/core/permission-rule-model.d.ts +87 -15
  115. package/dist/core/permission-rule-model.js +87 -8
  116. package/dist/core/permission-rule-org.d.ts +200 -33
  117. package/dist/core/permission-rule-org.js +217 -91
  118. package/dist/core/permission-rule-session.js +2 -2
  119. package/dist/core/permission-rule-store.d.ts +8 -0
  120. package/dist/core/permission-rule-store.js +1 -0
  121. package/dist/core/permission-rule-sync.js +3 -2
  122. package/dist/core/permission-rules.d.ts +7 -1
  123. package/dist/core/permission-rules.js +37 -8
  124. package/dist/core/read-only-shell.d.ts +1 -1
  125. package/dist/core/read-only-shell.js +30 -9
  126. package/dist/core/remote-env.d.ts +41 -1
  127. package/dist/core/remote-env.js +18 -0
  128. package/dist/core/resume-startup-cause.d.ts +18 -0
  129. package/dist/core/resume-startup-cause.js +12 -0
  130. package/dist/core/retention.js +7 -2
  131. package/dist/core/retired-keys.d.ts +8 -2
  132. package/dist/core/retired-keys.js +12 -1
  133. package/dist/core/roles.js +60 -10
  134. package/dist/core/rule-lane-reads.js +1 -1
  135. package/dist/core/runner/approval-feedback.d.ts +46 -0
  136. package/dist/core/runner/approval-feedback.js +41 -0
  137. package/dist/core/runner/compaction-call-options.d.ts +3 -3
  138. package/dist/core/runner/compaction-call-options.js +2 -2
  139. package/dist/core/runner/contracts.d.ts +34 -41
  140. package/dist/core/runner/cross-boundary-lane.d.ts +87 -0
  141. package/dist/core/runner/cross-boundary-lane.js +190 -0
  142. package/dist/core/runner/decide-continuation.d.ts +4 -4
  143. package/dist/core/runner/decide-continuation.js +3 -1
  144. package/dist/core/runner/gate-exit.d.ts +32 -12
  145. package/dist/core/runner/gate-exit.js +77 -34
  146. package/dist/core/runner/inherited-ask-grants.js +2 -2
  147. package/dist/core/runner/mcp-redial.d.ts +52 -10
  148. package/dist/core/runner/mcp-redial.js +38 -9
  149. package/dist/core/runner/permission-rule-lanes.d.ts +5 -2
  150. package/dist/core/runner/permission-rule-lanes.js +22 -10
  151. package/dist/core/runner/person-path-rows.d.ts +5 -3
  152. package/dist/core/runner/person-path-rows.js +2 -2
  153. package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -4
  154. package/dist/core/runner/prepare-acquire-reconcile.js +3 -3
  155. package/dist/core/runner/prepare-ask-lane.js +3 -3
  156. package/dist/core/runner/prepare-caps-and-workflow.d.ts +6 -5
  157. package/dist/core/runner/prepare-caps-and-workflow.js +3 -3
  158. package/dist/core/runner/prepare-config-doors.d.ts +58 -15
  159. package/dist/core/runner/prepare-config-doors.js +167 -146
  160. package/dist/core/runner/prepare-context-lane.js +6 -3
  161. package/dist/core/runner/prepare-defer-classify.js +4 -7
  162. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  163. package/dist/core/runner/prepare-delegation-surface.js +8 -7
  164. package/dist/core/runner/prepare-execution-env.d.ts +1 -2
  165. package/dist/core/runner/prepare-execution-env.js +2 -1
  166. package/dist/core/runner/prepare-gate-stations.d.ts +2 -0
  167. package/dist/core/runner/prepare-gate-stations.js +4 -3
  168. package/dist/core/runner/prepare-hands-readface.d.ts +3 -3
  169. package/dist/core/runner/prepare-hands-readface.js +3 -2
  170. package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
  171. package/dist/core/runner/prepare-inherited-gate.js +3 -2
  172. package/dist/core/runner/prepare-listings.d.ts +1 -1
  173. package/dist/core/runner/prepare-listings.js +4 -2
  174. package/dist/core/runner/prepare-memory-engine-session.d.ts +0 -6
  175. package/dist/core/runner/prepare-memory-engine-session.js +19 -6
  176. package/dist/core/runner/prepare-memory.d.ts +3 -2
  177. package/dist/core/runner/prepare-memory.js +6 -2
  178. package/dist/core/runner/prepare-offload-wrappers.d.ts +4 -4
  179. package/dist/core/runner/prepare-offload-wrappers.js +2 -1
  180. package/dist/core/runner/prepare-park-ask.d.ts +1 -1
  181. package/dist/core/runner/prepare-park-ask.js +9 -3
  182. package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
  183. package/dist/core/runner/prepare-policy-chain.js +14 -19
  184. package/dist/core/runner/prepare-project-context.d.ts +1 -1
  185. package/dist/core/runner/prepare-project-context.js +6 -4
  186. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  187. package/dist/core/runner/prepare-prompt-assembly.js +20 -16
  188. package/dist/core/runner/prepare-prompt-inputs.d.ts +12 -6
  189. package/dist/core/runner/prepare-prompt-inputs.js +6 -3
  190. package/dist/core/runner/prepare-protocol-tools.d.ts +7 -5
  191. package/dist/core/runner/prepare-protocol-tools.js +19 -12
  192. package/dist/core/runner/prepare-question-face.d.ts +4 -2
  193. package/dist/core/runner/prepare-question-face.js +4 -3
  194. package/dist/core/runner/prepare-safety-scan.d.ts +7 -2
  195. package/dist/core/runner/prepare-safety-scan.js +34 -27
  196. package/dist/core/runner/prepare-suspend-saga.d.ts +2 -2
  197. package/dist/core/runner/prepare-suspend-saga.js +6 -2
  198. package/dist/core/runner/prepare-task.js +22 -19
  199. package/dist/core/runner/prepare-turn-wiring.d.ts +10 -5
  200. package/dist/core/runner/prepare-turn-wiring.js +14 -49
  201. package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -5
  202. package/dist/core/runner/prepare-wiring-manifest.js +9 -6
  203. package/dist/core/runner/prepare-workspace-restore.js +2 -2
  204. package/dist/core/runner/resume-admission.d.ts +4 -1
  205. package/dist/core/runner/resume-admission.js +16 -14
  206. package/dist/core/runner/resume-apply.js +4 -4
  207. package/dist/core/runner/resume-internals-and-config.d.ts +3 -3
  208. package/dist/core/runner/resume-internals-and-config.js +3 -8
  209. package/dist/core/runner/resume-policy-outcome.d.ts +2 -2
  210. package/dist/core/runner/resume-policy-outcome.js +18 -15
  211. package/dist/core/runner/resume-preflight.js +2 -2
  212. package/dist/core/runner/resume-review-outcome.js +3 -3
  213. package/dist/core/runner/retired-runner-deps.d.ts +5 -6
  214. package/dist/core/runner/retired-runner-deps.js +1 -4
  215. package/dist/core/runner/run-leg.js +97 -43
  216. package/dist/core/runner/run-recovery-lanes.js +5 -3
  217. package/dist/core/runner/run-settle-and-teardown.js +2 -0
  218. package/dist/core/runner/run-terminal-adoption.js +21 -9
  219. package/dist/core/runner/run-turn-boundary.js +9 -3
  220. package/dist/core/runner/runtask.js +1 -1
  221. package/dist/core/runner/session-file-state-replay.d.ts +5 -2
  222. package/dist/core/runner/session-file-state-replay.js +3 -2
  223. package/dist/core/runner/skills-seat.d.ts +15 -0
  224. package/dist/core/runner/skills-seat.js +57 -0
  225. package/dist/core/runner/stream-steer-verb.js +2 -2
  226. package/dist/core/runner/sub-agent-deny-lists.d.ts +13 -11
  227. package/dist/core/runner/sub-agent-deny-lists.js +7 -5
  228. package/dist/core/runner/synthetic-tools.d.ts +5 -2
  229. package/dist/core/runner/synthetic-tools.js +2 -12
  230. package/dist/core/runner/tool-face-overlay.d.ts +24 -6
  231. package/dist/core/runner/tool-face-overlay.js +24 -15
  232. package/dist/core/runner-deps.d.ts +37 -22
  233. package/dist/core/session-policy-store.d.ts +19 -0
  234. package/dist/core/session-policy-store.js +22 -2
  235. package/dist/core/session-store.d.ts +35 -16
  236. package/dist/core/session-store.js +20 -12
  237. package/dist/core/{shell-lexer.d.ts → shell-run-candidates.d.ts} +36 -12
  238. package/dist/core/{shell-lexer.js → shell-run-candidates.js} +145 -225
  239. package/dist/core/shell-wrapper-table.d.ts +5 -3
  240. package/dist/core/shell-wrapper-table.js +1 -1
  241. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
  242. package/dist/core/store-contracts/mailbox-store-contract.js +13 -0
  243. package/dist/core/store-contracts/session-store-contract.d.ts +49 -0
  244. package/dist/core/store-contracts/session-store-contract.js +639 -0
  245. package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +12 -0
  246. package/dist/core/store-contracts/session-store-incarnation-contract.js +5 -2
  247. package/dist/core/task-event.d.ts +44 -6
  248. package/dist/core/task-notification.js +2 -2
  249. package/dist/core/task-registry-agent.js +1 -0
  250. package/dist/core/task-registry-shared.d.ts +5 -0
  251. package/dist/core/task-registry.d.ts +7 -0
  252. package/dist/core/task-registry.js +17 -0
  253. package/dist/core/task-result.d.ts +1 -1
  254. package/dist/core/task-spec.d.ts +7 -7
  255. package/dist/core/tighten-task-spec.d.ts +6 -4
  256. package/dist/core/tighten-task-spec.js +2 -2
  257. package/dist/core/tool-catalog-entries.d.ts +1 -1
  258. package/dist/core/tool-catalog-entries.js +52 -52
  259. package/dist/core/tool-face-reader.d.ts +49 -0
  260. package/dist/core/tool-face-reader.js +94 -0
  261. package/dist/core/tool-face.d.ts +17 -8
  262. package/dist/core/tool-face.js +1 -0
  263. package/dist/core/tool-policy.d.ts +67 -23
  264. package/dist/core/tool-policy.js +72 -60
  265. package/dist/core/tool-roster.d.ts +46 -28
  266. package/dist/core/tool-roster.js +91 -14
  267. package/dist/core/tool-spec-members.js +2 -2
  268. package/dist/core/tool-spec.d.ts +20 -4
  269. package/dist/core/tools.js +2 -0
  270. package/dist/core/trace.d.ts +24 -2
  271. package/dist/core/types.d.ts +3 -3
  272. package/dist/core/types.js +1 -1
  273. package/dist/core/untrusted-text.d.ts +23 -0
  274. package/dist/core/untrusted-text.js +10 -10
  275. package/dist/core/user-attachment.d.ts +56 -2
  276. package/dist/core/user-attachment.js +139 -56
  277. package/dist/core/wiring-manifest.d.ts +4 -2
  278. package/dist/engine/compaction/compaction.d.ts +2 -2
  279. package/dist/engine/compaction/utils.d.ts +2 -2
  280. package/dist/engine/harness/agent-harness.js +2 -1
  281. package/dist/engine/harness/types.d.ts +10 -4
  282. package/dist/engine/llm/types.d.ts +7 -2
  283. package/dist/engine/llm/types.js +2 -1
  284. package/dist/engine/loop/agent-loop.d.ts +12 -1
  285. package/dist/engine/loop/agent-loop.js +17 -5
  286. package/dist/engine/loop/types.d.ts +7 -1
  287. package/dist/engine/session/repo-utils.d.ts +5 -0
  288. package/dist/engine/session/repo-utils.js +9 -0
  289. package/dist/engine/session/storage-base.js +2 -2
  290. package/dist/index.d.ts +37 -17
  291. package/dist/index.js +31 -14
  292. package/dist/internal/harness.d.ts +1 -0
  293. package/dist/internal/harness.js +1 -0
  294. package/dist/internal/llm.d.ts +1 -1
  295. package/dist/internal/llm.js +1 -1
  296. package/dist/orchestration/goal.d.ts +24 -1
  297. package/dist/orchestration/goal.js +3 -2
  298. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  299. package/dist/orchestration/run-workflow-tool.js +1 -0
  300. package/dist/orchestration/workflow-governance.d.ts +1 -1
  301. package/dist/orchestration/workflow.d.ts +3 -0
  302. package/dist/orchestration/workflow.js +2 -1
  303. package/dist/prompt-assembly/artifact-store.d.ts +44 -9
  304. package/dist/prompt-assembly/artifact-store.js +38 -12
  305. package/dist/prompt-assembly/artifact.d.ts +12 -11
  306. package/dist/prompt-assembly/artifact.js +52 -21
  307. package/dist/prompt-assembly/assemble.d.ts +2 -0
  308. package/dist/prompt-assembly/assemble.js +2 -1
  309. package/dist/prompt-assembly/catalog/index.js +2 -1
  310. package/dist/prompt-assembly/catalog/memory.d.ts +13 -0
  311. package/dist/prompt-assembly/catalog/memory.js +6 -0
  312. package/dist/prompt-assembly/catalog/schema.d.ts +19 -2
  313. package/dist/prompt-assembly/catalog/schema.js +3 -0
  314. package/dist/prompt-assembly/composer.d.ts +2 -0
  315. package/dist/prompt-assembly/composer.js +1 -1
  316. package/dist/prompt-assembly/overlay.d.ts +21 -11
  317. package/dist/prompt-assembly/overlay.js +57 -19
  318. package/dist/prompt-assembly/select.d.ts +21 -6
  319. package/dist/prompt-assembly/select.js +41 -4
  320. package/dist/prompts/default.d.ts +1 -12
  321. package/dist/prompts/default.js +0 -3
  322. package/dist/shell/lex.d.ts +87 -8
  323. package/dist/shell/lex.js +283 -49
  324. package/dist/stores/file/index.d.ts +8 -6
  325. package/dist/stores/file/permission-rule-store.d.ts +1 -1
  326. package/dist/stores/file/permission-rule-store.js +2 -1
  327. package/dist/tools/artifact/artifact-tool.js +14 -3
  328. package/dist/tools/fs/bash-program-position.d.ts +3 -15
  329. package/dist/tools/fs/bash-program-position.js +3 -10
  330. package/dist/tools/fs/bash-readonly-classifier.js +2 -0
  331. package/dist/tools/fs/fs-bash.d.ts +3 -3
  332. package/dist/tools/fs/fs-bash.js +72 -101
  333. package/dist/tools/fs/fs-read.d.ts +12 -0
  334. package/dist/tools/fs/fs-read.js +10 -9
  335. package/dist/tools/fs/fs-shared.d.ts +27 -31
  336. package/dist/tools/fs/fs-shared.js +12 -15
  337. package/dist/tools/fs/fs-write.js +1 -1
  338. package/dist/tools/fs/index.js +2 -2
  339. package/dist/tools/fs/read-deny-home.d.ts +1 -1
  340. package/dist/tools/fs/read-deny-home.js +5 -4
  341. package/dist/tools/fs/read-deny.d.ts +16 -3
  342. package/dist/tools/fs/read-deny.js +12 -9
  343. package/dist/tools/fs/safety.d.ts +7 -6
  344. package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
  345. package/dist/tools/fs/shell-read-bindings.js +15 -16
  346. package/dist/tools/fs/shell-read-device.js +1 -1
  347. package/dist/tools/fs/shell-read-plan.js +36 -47
  348. package/dist/tools/fs/shell-read-word.d.ts +1 -6
  349. package/dist/tools/fs/shell-read-word.js +2 -20
  350. package/dist/tools/fs/shell-reentry-text.js +1 -1
  351. package/dist/tools/web.d.ts +2 -2
  352. package/package.json +4 -1
  353. package/test/export-surface.snapshot.json +5651 -5457
  354. package/wire-changes/core@7.53.0.json +1968 -0
  355. package/wire-changes/core@7.54.0.json +2870 -0
  356. package/dist/core/shell-scan.d.ts +0 -60
  357. package/dist/core/shell-scan.js +0 -183
@@ -2,12 +2,15 @@ import { screenGateOutcome, SETTLEMENT_IS_REFUSAL } from "../gate-outcome.js";
2
2
  import { askFaceOf, classifyAsk, headlessApprovalRemedy } from "../ask-origin.js";
3
3
  import { wireOf } from "../tool-name-readings.js";
4
4
  import { isProtectedWrite } from "../tool-registry.js";
5
- import { attestationOf, decisionText } from "../tool-policy.js";
5
+ import { attestationOf, decisionText, licenseCovers } from "../tool-policy.js";
6
6
  import { pathDenialCardOf } from "../../tools/fs/safety.js";
7
- import { boundInputHashOf } from "../canonical-json.js";
7
+ import { argsAsTheToolReads } from "../effective-path-target.js";
8
8
  import { askUnresolvableRemedySeat } from "../ask-unresolvable-notice.js";
9
9
  import { inlineUntrusted } from "../untrusted-text.js";
10
10
  import { formatHookFeedback } from "../reminder-mint.js";
11
+ import { types } from "node:util";
12
+ import { receive } from "../mods/kernel/receive.js";
13
+ import { describeBindingFault, MAX_DEPTH } from "../canonical-json.js";
11
14
  export function mintGateOutcome(facts) {
12
15
  const settlement = facts.settled !== undefined ? Object.freeze({ ...facts.settled.settlement, who: Object.freeze({ ...facts.settled.settlement.who }) }) : undefined;
13
16
  const attribution = facts.classifier !== undefined ? Object.freeze({ ...facts.classifier, ...(facts.classifier.fallback !== undefined ? { fallback: Object.freeze({ ...facts.classifier.fallback }) } : {}) }) : undefined;
@@ -81,12 +84,7 @@ export function createSettlementLedger(call) {
81
84
  `(\`attestation\`), never a resolver's member; denied fail-closed rather than recording a settlement nobody can attribute`, contractRefusal(origin));
82
85
  }
83
86
  const edited = resolved.action === "allow" && resolved.updatedInput !== undefined;
84
- const licensed = () => {
85
- if (edited)
86
- return record.inputDigest === boundInputHashOf(resolved.updatedInput);
87
- const askedDigest = boundInputHashOf(req.args);
88
- return record.inputDigest === askedDigest && (resolved.presentedInput === undefined || boundInputHashOf(resolved.presentedInput) === askedDigest);
89
- };
87
+ const licensed = () => edited ? licenseCovers(record.inputDigest, resolved.updatedInput) : licenseCovers(record.inputDigest, req.args) && (resolved.presentedInput === undefined || licenseCovers(record.inputDigest, resolved.presentedInput));
90
88
  const bound = record?.settlement !== undefined && record.origin === origin && sameClasses(record.classes, asked.classes) ? record.settlement : undefined;
91
89
  const refusal = bound !== undefined && SETTLEMENT_IS_REFUSAL[bound.kind] ? bound : undefined;
92
90
  if (resolved.action === "allow") {
@@ -127,7 +125,7 @@ export function createSettlementLedger(call) {
127
125
  return { decision, deniedBy: refusal ? "ask_resolution" : "policy" };
128
126
  },
129
127
  keepForBytes(runs) {
130
- if (settled !== undefined && settled.licensed !== boundInputHashOf(runs))
128
+ if (settled !== undefined && !licenseCovers(settled.licensed, runs))
131
129
  settled = undefined;
132
130
  },
133
131
  causeOf(decision) {
@@ -153,32 +151,63 @@ export function cloneObserverInput(input) {
153
151
  return input;
154
152
  }
155
153
  }
154
+ const RECORD_RECEIPT = Object.freeze({ accessors: "refuse", depth: MAX_DEPTH, others: "refuse", contain: true, isProxy: (v) => types.isProxy(v) });
156
155
  export function frozenInputSnapshot(input) {
157
- try {
158
- return deepFreeze(structuredClone(input));
156
+ const received = receive(input, RECORD_RECEIPT);
157
+ if (!received.ok)
158
+ return notRecorded(input, received.fault);
159
+ const record = received.value;
160
+ const symbolMarkers = new Map();
161
+ for (const fault of received.faults ?? []) {
162
+ const above = fault.path.slice(0, -1);
163
+ const holder = memberAt(record, above);
164
+ const last = fault.path[fault.path.length - 1];
165
+ if (typeof holder !== "object" || holder === null || last === undefined)
166
+ continue;
167
+ const rawHolder = memberAt(input, above);
168
+ const symbolKeyed = fault.kind === "hidden" && (Object.hasOwn(holder, last) || typeof rawHolder !== "object" || rawHolder === null || !Object.hasOwn(rawHolder, last));
169
+ if (symbolKeyed) {
170
+ const listed = symbolMarkers.get(holder);
171
+ if (listed === undefined)
172
+ symbolMarkers.set(holder, [notRecorded(input, fault)]);
173
+ else
174
+ listed.push(notRecorded(input, fault));
175
+ }
176
+ else
177
+ Object.defineProperty(holder, last, { value: notRecorded(input, fault), enumerable: true, writable: true, configurable: true });
159
178
  }
160
- catch {
161
- return Object.freeze(Array.isArray(input) ? [...input] : input !== null && typeof input === "object" ? { ...input } : input);
179
+ for (const [holder, markers] of symbolMarkers) {
180
+ let key = "[not recorded: symbol-keyed members]";
181
+ while (Object.hasOwn(holder, key))
182
+ key += "'";
183
+ Object.defineProperty(holder, key, { value: markers, enumerable: true, writable: true, configurable: true });
162
184
  }
185
+ return freezeRecord(record);
163
186
  }
164
- function deepFreeze(value) {
165
- if (value === null || typeof value !== "object" || Object.isFrozen(value))
166
- return value;
167
- Object.freeze(value);
168
- for (const key of Reflect.ownKeys(value)) {
169
- const d = Object.getOwnPropertyDescriptor(value, key);
170
- if (d !== undefined && "value" in d)
171
- deepFreeze(d.value);
187
+ function freezeRecord(record) {
188
+ const pending = [record];
189
+ for (let at = pending.pop(); at !== undefined || pending.length > 0; at = pending.pop()) {
190
+ if (typeof at !== "object" || at === null || Object.isFrozen(at))
191
+ continue;
192
+ Object.freeze(at);
193
+ for (const member of Object.values(at))
194
+ pending.push(member);
172
195
  }
173
- return value;
196
+ return record;
197
+ }
198
+ const notRecorded = (input, fault) => `[not recorded: ${describeBindingFault(input, fault)}]`;
199
+ function memberAt(value, path) {
200
+ let at = value;
201
+ for (const key of path)
202
+ at = typeof at === "object" && at !== null ? at[key] : undefined;
203
+ return at;
174
204
  }
175
205
  const PARK_FAILURE_CAUSE_MAX = 600;
176
- function withParkFailureCause(reason, parkFailed) {
206
+ function withParkFailureCause(reason, parkFailed, nobodyJudged) {
177
207
  if (parkFailed === undefined)
178
208
  return reason;
179
- return (`${reason} — note: a durable approval park was attempted for this call FIRST and could not be minted ` +
180
- `(${inlineUntrusted(parkFailed, PARK_FAILURE_CAUSE_MAX)}), so the refusal above is what the fallback had ` +
181
- `left to say, not the reason the call stopped.`);
209
+ const note = `${reason} — note: a durable approval park was attempted for this call and could not be minted (${inlineUntrusted(parkFailed, PARK_FAILURE_CAUSE_MAX)})`;
210
+ return nobodyJudged ? `${note}; with nobody to judge the call, that is why it stopped — it can be issued again once its park can be minted.` : `${note}.`;
182
211
  }
183
212
  export function passAskFace(pass) {
184
213
  return askFaceOf(wireOf("gate.askFace", pass.req));
@@ -191,11 +220,15 @@ export async function engineFailClosedExit(pass, reason) {
191
220
  return { block: true, reason: formatHookFeedback(reason, input.reminderMark), gate, finalInput, preToolContext: pass.preToolContext };
192
221
  }
193
222
  export const unownedArgsMessage = (subject, reason) => `${subject} could not be taken into the gate's own copy (${reason}) — denied fail-closed`;
194
- export async function unownedArgsExit(pass, reason) {
195
- pass.decision = { action: "deny", message: unownedArgsMessage(`the arguments of "${pass.toolName}"`, reason) };
196
- pass.deniedBy = "policy";
223
+ export async function untakenValueExit(pass, untaken) {
224
+ pass.currentInput = untaken.value;
225
+ pass.decision = { action: "deny", message: untaken.message };
226
+ pass.deniedBy = untaken.deniedBy;
197
227
  return await exitGate(pass);
198
228
  }
229
+ export async function unownedArgsExit(pass, reason) {
230
+ return await untakenValueExit(pass, { value: pass.currentInput, message: unownedArgsMessage(`the arguments of "${pass.toolName}"`, reason), deniedBy: "policy" });
231
+ }
199
232
  export function hookDenyExit(pass, deniedReason) {
200
233
  const { input } = pass;
201
234
  return {
@@ -220,7 +253,7 @@ export async function exitGate(pass) {
220
253
  const { input, toolName, toolCallId, ledger, notifyPermissionDenied } = pass;
221
254
  if (pass.decision.action === "deny") {
222
255
  if (pass.decision.updatedInput !== undefined) {
223
- pass.currentInput = pass.decision.updatedInput;
256
+ pass.currentInput = argsAsTheToolReads(pass.req, pass.decision.updatedInput);
224
257
  }
225
258
  if (pass.deniedBy === undefined)
226
259
  throw new Error(`the tool gate refused "${toolName}" without a refusing layer — every deny site attributes itself`);
@@ -235,11 +268,13 @@ export async function exitGate(pass) {
235
268
  const unresolvable = foldCause ?? unresolvableCauseOfSettlement(gate);
236
269
  const tableSeat = unresolvable !== undefined ? askUnresolvableRemedySeat(unresolvable) : undefined;
237
270
  const remedySeat = tableSeat === "absent" && input.askSeatForm === "declared_none" && pass.askClassification?.classes.has("content_question") !== true ? "declared_none" : tableSeat;
238
- const remedy = remedySeat !== undefined ? headlessApprovalRemedy({ mode: input.permissionMode?.(), classification: foldCause !== undefined ? ANCESTOR_UNRESOLVED_CLASSIFICATION : pass.askClassification, approverSeat: remedySeat, coveredWrite: isProtectedWrite(pass.req), ...(pass.shellUnplacedOptions !== undefined ? { unplacedOptions: pass.shellUnplacedOptions } : {}) }) : undefined;
239
- const denyReason = withParkFailureCause(`${decisionText(pass.decision) ?? `tool "${toolName}" denied by policy`}${remedy !== undefined ? ` — ${remedy}` : ""}`, pass.parkFailed);
271
+ const bindingRefusal = attestationOf(pass.decision, { toolCallId, toolName })?.bindingRefusal;
272
+ const remedy = bindingRefusal !== undefined ? undefined : remedySeat !== undefined ? headlessApprovalRemedy({ mode: input.permissionMode?.(), classification: foldCause !== undefined ? ANCESTOR_UNRESOLVED_CLASSIFICATION : pass.askClassification, approverSeat: remedySeat, coveredWrite: isProtectedWrite(pass.req), ...(pass.shellUnplacedOptions !== undefined ? { unplacedOptions: pass.shellUnplacedOptions } : {}) }) : undefined;
273
+ const denyReason = withParkFailureCause(`${decisionText(pass.decision) ?? `tool "${toolName}" denied by policy`}${remedy !== undefined ? ` — ${remedy}` : ""}`, pass.parkFailed, unresolvable !== undefined);
240
274
  if (unresolvable !== undefined) {
241
275
  const cause = unresolvable;
242
- pass.notifier.notify(() => input.onAskUnresolvable?.({ toolName, toolCallId, cause, parkLaneExisted: pass.suspendAsk !== undefined, ...(remedy !== undefined ? { remedy } : {}) }), "toolGate.onAskUnresolvable");
276
+ const noticeRemedy = bindingRefusal ?? remedy;
277
+ pass.notifier.notify(() => input.onAskUnresolvable?.({ toolName, toolCallId, cause, parkLaneExisted: pass.suspendAsk !== undefined, ...(noticeRemedy !== undefined ? { remedy: noticeRemedy } : {}) }), "toolGate.onAskUnresolvable");
243
278
  }
244
279
  const finalInput = frozenInputSnapshot(pass.currentInput);
245
280
  await notifyPermissionDenied({
@@ -265,9 +300,17 @@ export async function exitGate(pass) {
265
300
  if (pass.decision.action === "allow") {
266
301
  const rw = pass.decision.updatedInput !== undefined ? pass.decision.updatedInput : pass.policyRewrite;
267
302
  if (rw !== undefined)
268
- pass.currentInput = rw;
303
+ pass.currentInput = argsAsTheToolReads(pass.req, rw);
269
304
  }
270
305
  ledger.keepForBytes(pass.currentInput);
306
+ const crossBoundary = await input.crossBoundaryCheck?.exit({ toolName, args: pass.currentInput, toolCallId }, ledger.settled?.settlement.who.party === "person" ? "person" : "policy");
307
+ if (crossBoundary !== undefined) {
308
+ pass.decision = { action: "deny", message: crossBoundary.message, decisionReason: "safety" };
309
+ pass.deniedBy = crossBoundary.kind === "unconfirmed" ? "ask_resolution" : "policy";
310
+ if (crossBoundary.kind === "unconfirmed")
311
+ ledger.set({ settlement: { kind: "blanket_allow_refused", who: { party: "none" }, when: Date.now() }, origin: ledger.settled?.origin ?? "safety_tighten" });
312
+ return exitGate(pass);
313
+ }
271
314
  const release = pass.arbitration?.kind === "released" ? pass.arbitration : (() => { if (ledger.settled !== undefined)
272
315
  return undefined; const a = pass.arbitration === undefined ? input.ancestorArbitrationOf?.(toolCallId) : undefined; return a?.kind === "released" ? a : undefined; })();
273
316
  return {
@@ -1,6 +1,6 @@
1
1
  import { primaryActivityArg } from "../arg-summary.js";
2
2
  import { askApproverIdentity, attest, attestationOf } from "../tool-policy.js";
3
- import { boundInputHashOf } from "../canonical-json.js";
3
+ import { exactBoundInputHashOf } from "../canonical-json.js";
4
4
  export function askGrantCovers(granted, asked) {
5
5
  return asked !== undefined && asked.length > 0 && granted.length > 0 && asked.every((c) => c === "ordinary" || granted.includes(c));
6
6
  }
@@ -34,5 +34,5 @@ export function consumeInheritedAskGrant(grants, onAsk, humanReviewRef, decision
34
34
  const settlement = attestationOf(grant.settled, req)?.settlement;
35
35
  if (settlement === undefined || decision.action !== "ask")
36
36
  return reused;
37
- return attest(reused, req, { settlement, ...(decision.origin !== undefined ? { origin: decision.origin } : {}), ...(decision.classes !== undefined ? { classes: decision.classes } : {}), inputDigest: boundInputHashOf(grant.presented) });
37
+ return attest(reused, req, { settlement, ...(decision.origin !== undefined ? { origin: decision.origin } : {}), ...(decision.classes !== undefined ? { classes: decision.classes } : {}), inputDigest: exactBoundInputHashOf(grant.presented) });
38
38
  }
@@ -15,22 +15,28 @@
15
15
  */
16
16
  import type { AgentTool } from "../../internal/harness.js";
17
17
  import type { McpDroppedTool, McpReconnectResult, MaterializedMcp, McpToolAxis } from "../mcp.js";
18
- import type { RunnerDeps, ToolEffect } from "../types.js";
18
+ import type { RunnerDeps, TaskSpec, ToolEffect } from "../types.js";
19
19
  import type { IrreversibilityTier, ToolFaceSnapshot } from "./contracts.js";
20
20
  import type { RosterBuilder, ToolRosterDeltaSeat } from "../tool-roster.js";
21
+ import type { McpInstructionsEntry } from "../prompt-section-seat.js";
21
22
  /** One server's assertion about its own prefix domain — the shape both faces reduce to. `tools` is
22
23
  * the roster the domain must END UP with (empty withdraws it); `added`/`removed` are the reporting
23
- * half and never drive the swap. */
24
+ * half and never drive the swap. `instructions`: the server's current connection's instructions entry, when the face that produced
25
+ * the delta hands it back (a refresh does; a re-dial queues its new text itself). */
24
26
  export interface McpRosterDelta {
25
27
  server: string;
26
28
  prefix: string;
27
29
  tools: AgentTool[];
28
30
  axes: McpToolAxis[];
29
31
  dropped: McpDroppedTool[];
32
+ instructions?: McpInstructionsEntry;
30
33
  }
31
34
  /** The seats a roster application writes through. Every one of them is borrowed from the phase that
32
35
  * minted it — this module owns nothing. */
33
36
  export interface McpRosterApplyInput {
37
+ /** borrowed-readonly — the run's spec; only `excludeAllTools` is read: the valve's whole-face switch decides what a live roster
38
+ * change may mount, beside the selector (`valveLeaves`). */
39
+ spec: Pick<TaskSpec, "excludeAllTools">;
34
40
  /** borrowed-readonly — the frozen tool face; its selector keeps a tool the leg may not use from ever
35
41
  * being mounted by a face the model or a host can trigger. */
36
42
  toolFaceSnapshot: Pick<ToolFaceSnapshot, "selector">;
@@ -48,20 +54,53 @@ export interface McpRosterApplyInput {
48
54
  }>;
49
55
  /** borrowed-mutable — the run's shared roster. */
50
56
  roster: RosterBuilder;
57
+ /** borrowed-readonly — the run's frozen MCP server declarations: a roster reading is of a declared server, and a name none is declared under holds nothing. */
58
+ declared: ReadonlyArray<{
59
+ readonly name: string;
60
+ }>;
61
+ /** borrowed-mutable — the materialization's instructions delta lane; a roster change settles the server's adds on it. */
62
+ instructionsDelta: MaterializedMcp["instructionsDelta"];
63
+ }
64
+ /** What one application did to the server's domain on the roster — read off the roster before and after the swap, the ONE source of
65
+ * "how many of this server's tools are mounted" for the re-dial's notice and receipt and the refresh tool's receipt. A name the
66
+ * valve left out, a name the deployment's `toolFaces` overlay refused and a name the server listed but the roster did not take are
67
+ * all simply absent from `mounted`. */
68
+ export interface McpRosterReading {
69
+ /** The names of the server's tools the roster holds after the application, in wire order. */
70
+ mounted: string[];
71
+ /** The names `mounted` holds that the roster did not hold before. */
72
+ added: string[];
73
+ /** The names the roster held before that `mounted` no longer holds. */
74
+ removed: string[];
75
+ /** The names the server listed that the roster did not take (the valve's, the overlay's). */
76
+ excluded: string[];
51
77
  }
52
78
  /** What one application did, for the caller's receipt. `applied:false` means the axis fold refused
53
- * and the domain was left exactly as it was (the tables restored) — no roster write happened. */
54
- export type McpRosterApplyOutcome = {
79
+ * and the domain was left exactly as it was (the tables restored) — no roster write happened, so `mounted` is what it held. */
80
+ export type McpRosterApplyOutcome = ({
55
81
  applied: true;
56
- pushed: number;
57
- excluded: string[];
58
- } | {
82
+ } & McpRosterReading) | ({
59
83
  applied: false;
60
84
  foldError: string;
61
- };
85
+ } & McpRosterReading);
86
+ /** What the roster holds of one server with no swap made — the reading of an arm that asserted no roster (a failed refresh, a refused re-dial, a fold refusal).
87
+ * A name no server is declared under holds nothing, even when it mints a declared server's prefix. */
88
+ export declare function rosterHolding(input: Pick<McpRosterApplyInput, "roster" | "declared">, serverName: string): McpRosterReading;
89
+ /** A refresh or re-dial result read off the roster instead of the server's own listing: `toolCount` is the tools mounted, `added` /
90
+ * `removed` the names that came onto / left the roster, and — on a result that asserts a roster — `tools` the roster's tools of the
91
+ * server (with `axes` cut to them), so `toolCount === tools.length`. One meaning, the one `MaterializedMcp.refresh` / `reconnect` give
92
+ * for their own handles. */
93
+ export declare function settledOnRoster<T extends {
94
+ toolCount: number;
95
+ added: string[];
96
+ removed: string[];
97
+ tools?: AgentTool[];
98
+ axes?: McpToolAxis[];
99
+ }>(result: T, reading: McpRosterReading, roster: Pick<RosterBuilder, "tools">): T;
62
100
  /**
63
- * Apply one server's delta to the live roster. Behavior-identical to the body this was extracted
64
- * from; the comments are the extracted reasoning, not new claims.
101
+ * Apply one server's delta to the live roster, and settle the server's instruction adds on the delta lane: the teaching answer over the
102
+ * roster after the swap, against the lane's ledger of what the model holds (both faces — the model's refresh and the host's re-dial —
103
+ * through this one step).
65
104
  */
66
105
  export declare function applyMcpRosterDelta(input: McpRosterApplyInput, delta: McpRosterDelta): McpRosterApplyOutcome;
67
106
  /** The seats the post-application commit writes through. */
@@ -105,5 +144,8 @@ export interface McpRedialInput extends McpRosterApplyInput, McpRosterCommitInpu
105
144
  * leave the model holding tools that can only fail), and an arm that never touched the connection —
106
145
  * unknown name, revoked server, re-dial already in flight, disposed materialization — asserts nothing
107
146
  * and the mounted roster is left alone.
147
+ *
148
+ * The result it hands back counts what the ROSTER took: `toolCount` is the server's tools mounted after the swap, `added` /
149
+ * `removed` the names that came onto / left the roster, and the notice reads the same three — one source for "how many are mounted".
108
150
  */
109
151
  export declare function redialMcpServer(input: McpRedialInput, server: string): Promise<McpReconnectResult>;
@@ -1,9 +1,31 @@
1
+ import { valveLeaves } from "../../agents/tool-filter.js";
2
+ import { instructionsTaught, instructionsTold, tellInstructions, toolNamesOfServer, withdrawInstructionAdds } from "../mounted-mcp.js";
1
3
  import { deliverEngineNotice } from "../types.js";
2
4
  import { thrownText } from "../thrown-value.js";
5
+ export function rosterHolding(input, serverName) {
6
+ const mounted = input.declared.some((d) => d.name === serverName) ? toolNamesOfServer(input.roster.tools.map((t) => t.name), serverName) : [];
7
+ return { mounted, added: [], removed: [], excluded: [] };
8
+ }
9
+ export function settledOnRoster(result, reading, roster) {
10
+ const settled = { ...result, toolCount: reading.mounted.length, added: reading.added, removed: reading.removed };
11
+ if (result.tools === undefined)
12
+ return settled;
13
+ const held = new Set(reading.mounted);
14
+ return { ...settled, tools: roster.tools.filter((t) => held.has(t.name)), ...(result.axes !== undefined ? { axes: result.axes.filter((a) => held.has(a.name)) } : {}) };
15
+ }
16
+ function settleInstructions(lane, delta, taught) {
17
+ if (!taught)
18
+ return withdrawInstructionAdds(lane, delta.server);
19
+ if (delta.instructions !== undefined && !instructionsTold(lane, delta.server) && !lane.pendingAdds.some((e) => e.server === delta.server))
20
+ tellInstructions(lane, delta.instructions);
21
+ }
3
22
  export function applyMcpRosterDelta(input, delta) {
4
- const { toolFaceSnapshot, withFaces, remoteToolOffload, foldProtocolAxes, toolEffects, axisExplicitNegatives, roster } = input;
5
- const pushable = delta.tools.filter((t) => toolFaceSnapshot.selector.admits(t));
6
- const excluded = delta.tools.filter((t) => !toolFaceSnapshot.selector.admits(t)).map((t) => t.name);
23
+ const { spec, toolFaceSnapshot, withFaces, remoteToolOffload, foldProtocolAxes, toolEffects, axisExplicitNegatives, roster } = input;
24
+ const domainNow = () => toolNamesOfServer(roster.tools.map((t) => t.name), delta.server);
25
+ const before = domainNow();
26
+ const leaves = valveLeaves(spec, toolFaceSnapshot.selector);
27
+ const pushable = delta.tools.filter(leaves);
28
+ const valveExcluded = delta.tools.filter((t) => !leaves(t)).map((t) => t.name);
7
29
  const domainSnapshot = (m) => new Map([...m].filter(([name]) => name.startsWith(delta.prefix)));
8
30
  const restoreDomain = (m, snap) => {
9
31
  for (const name of [...m.keys()])
@@ -19,15 +41,20 @@ export function applyMcpRosterDelta(input, delta) {
19
41
  for (const name of priorDomainNegatives.keys())
20
42
  axisExplicitNegatives.delete(name);
21
43
  try {
22
- foldProtocolAxes(delta.axes.filter((a) => !excluded.includes(a.name)), "MCP");
44
+ foldProtocolAxes(delta.axes.filter((a) => !valveExcluded.includes(a.name)), "MCP");
23
45
  }
24
46
  catch (foldErr) {
25
47
  restoreDomain(toolEffects, priorDomainEffects);
26
48
  restoreDomain(axisExplicitNegatives, priorDomainNegatives);
27
- return { applied: false, foldError: thrownText(foldErr) };
49
+ settleInstructions(input.instructionsDelta, delta, instructionsTaught(leaves, delta.server, roster.tools, delta.tools));
50
+ return { applied: false, foldError: thrownText(foldErr), ...rosterHolding(input, delta.server) };
28
51
  }
29
52
  roster.replaceDomain(delta.prefix, withFaces(pushable, delta.server).map((t) => remoteToolOffload(t)), { source: "mcp", mountedBy: "optional", origin: { peer: delta.server } });
30
- return { applied: true, pushed: pushable.length, excluded };
53
+ const mounted = domainNow();
54
+ const heldNow = new Set(mounted);
55
+ const heldBefore = new Set(before);
56
+ settleInstructions(input.instructionsDelta, delta, instructionsTaught(leaves, delta.server, roster.tools, delta.tools));
57
+ return { applied: true, mounted, added: mounted.filter((n) => !heldBefore.has(n)), removed: before.filter((n) => !heldNow.has(n)), excluded: delta.tools.map((t) => t.name).filter((n) => !heldNow.has(n)) };
31
58
  }
32
59
  export async function commitMcpRosterChanges(input) {
33
60
  await input.rebuildHarnessToolsRef.current?.();
@@ -38,9 +65,11 @@ export async function commitMcpRosterChanges(input) {
38
65
  return undefined;
39
66
  }
40
67
  export async function redialMcpServer(input, server) {
41
- const result = await input.mcp.reconnect(server);
42
- if (result.tools !== undefined) {
43
- const outcome = applyMcpRosterDelta(input, { server: result.server, prefix: result.prefix, tools: result.tools, axes: result.axes ?? [], dropped: result.dropped ?? [] });
68
+ const listed = await input.mcp.reconnect(server);
69
+ let result = settledOnRoster(listed, rosterHolding(input, listed.server), input.roster);
70
+ if (listed.tools !== undefined) {
71
+ const outcome = applyMcpRosterDelta(input, { server: listed.server, prefix: listed.prefix, tools: listed.tools, axes: listed.axes ?? [], dropped: listed.dropped ?? [] });
72
+ result = settledOnRoster(listed, outcome, input.roster);
44
73
  if (!outcome.applied)
45
74
  input.deps.onError?.(Object.assign(new Error(`MCP re-dial of "${server}" connected but its safety-axis fold refused, so its tools were not mounted: ${outcome.foldError}`), { code: "mcp.server_unavailable" }), { phase: "mcp", sessionId: input.sessionId });
46
75
  const ungated = await commitMcpRosterChanges(input);
@@ -117,12 +117,15 @@ export interface PermissionRuleLanes {
117
117
  } | undefined;
118
118
  }
119
119
  /** The ORG lane: the org's verdict about one call, off the store view `read` supplies — the gate's `orgRules` face over the
120
- * call's pinned read ({@link createPermissionRuleLanes}), and {@link orgVerdictBeforeClaim} over a view of its own. */
120
+ * call's pinned read ({@link createPermissionRuleLanes}), and {@link orgVerdictBeforeClaim} over a view of its own.
121
+ * `onDegraded` receives the snapshot's degraded line ({@link describeUnreadableOrgRows}) on every adjudication over a
122
+ * snapshot carrying a row this layer cannot read. */
121
123
  export declare function orgRuleLane(cfg: {
122
124
  read: (toolCallId: string) => Promise<Pick<EffectivePermissionRules, "org">>;
123
125
  questionToolMounted: boolean;
124
126
  questionToolName: string;
125
127
  onRevisionDefect: (message: string) => void;
128
+ onDegraded: (message: string) => void;
126
129
  }): NonNullable<PermissionRuleLanes["org"]>;
127
130
  /** The org's CURRENT verdict about a parked call, read by the resume entry before the claim (the row's session and principal,
128
131
  * bounded like the gate's wait; unreadable ⇒ unavailable). The mount and a revision defect are the resumed leg's facts, so
@@ -172,7 +175,7 @@ export declare function createPermissionRuleLanes(cfg: {
172
175
  questionToolMounted: boolean;
173
176
  questionToolName: string;
174
177
  onRevisionDefect: (message: string) => void;
175
- /** Sink for a judgment against the rows that threw. */
178
+ /** Sink for a judgment against the rows that threw, and for the org lane's degraded-snapshot line. */
176
179
  onDisclosure: (message: string) => void;
177
180
  /** The run's one reader of the person's rules: the call's ONE read, pinned, that the read station, the org lane and the
178
181
  * personal lane all take (the lanes consume a reader; they do not make one). */
@@ -1,10 +1,10 @@
1
1
  import { persistedRuleMandateOf, ruleClearanceOf } from "../ask-origin.js";
2
2
  import {} from "../hooks.js";
3
- import { adjudicatePersistedDomainRules, adjudicatePersistedNameRules, adjudicatePersistedPathRules, adjudicatePersistedRules, callPathFaceOf, domainRuleSubjectOf, faceRowsSpeakFor, laneToolNamesOf, nameRuleSubjectOf, ruleToolGrammarOf, segmentCoverageOf, suggestRuleForName, suggestRulesForCommand } from "../permission-rule-model.js";
3
+ import { adjudicatePersistedDomainRules, adjudicatePersistedNameRules, adjudicatePersistedPathRules, adjudicatePersistedRules, callPathFaceOf, domainRuleSubjectOf, escapeForDisclosure, faceRowsSpeakFor, laneToolNamesOf, nameRuleSubjectOf, ruleToolGrammarOf, segmentCoverageOf, suggestRuleForName, suggestRulesForCommand } from "../permission-rule-model.js";
4
4
  import { callTargetViewsOf, validatePath } from "../effective-path-target.js";
5
5
  import { bindPathRules, persistedRowAnchors, projectScopeIdentity } from "../path-rule-binding.js";
6
6
  import { heldBy, seatOf, wireOf } from "../tool-name-readings.js";
7
- import { orgRuleVerdictFor, settleOrgVerdictWithin } from "../permission-rule-org.js";
7
+ import { describeUnreadableOrgRows, readOrgRules, settleOrgVerdictWithin } from "../permission-rule-org.js";
8
8
  import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
9
9
  import { ownerStoreOf } from "../permission-rule-provider.js";
10
10
  import { thrownText } from "../thrown-value.js";
@@ -18,12 +18,19 @@ export function inheritedAskRuleEvidence(provider) {
18
18
  return Object.freeze({ orgRevisionAbsent: org, orgRuleAbsent: org, personalRuleDotsAbsent: personal });
19
19
  }
20
20
  export function orgRevisionEvidenceOf(resolution, onDefect) {
21
- const reported = resolution.revision;
21
+ let reported;
22
+ try {
23
+ reported = resolution.revision;
24
+ }
25
+ catch (err) {
26
+ onDefect(`the org rule partition's revision could not be read (${thrownText(err)}) — the adjudication stands, but the ask carries no revision evidence for this call`);
27
+ return {};
28
+ }
22
29
  if (reported === undefined)
23
30
  return {};
24
31
  if (typeof reported === "number" && Number.isFinite(reported))
25
32
  return { revision: reported };
26
- onDefect(`the org rule partition reported revision ${JSON.stringify(reported)} — a snapshot revision is a finite number; ` +
33
+ onDefect(`the org rule partition reported revision ${typeof reported === "string" ? `"${escapeForDisclosure(reported)}"` : escapeForDisclosure(reported)} — a snapshot revision is a finite number; ` +
27
34
  `the adjudication stands, but the ask carries no revision evidence for this call`);
28
35
  return {};
29
36
  }
@@ -59,11 +66,16 @@ export function orgRuleLane(cfg) {
59
66
  if (resolution.status === "unavailable" || resolution.status === "ungoverned") {
60
67
  return { status: "unavailable", disclosures: resolution.status === "ungoverned" ? ["the store reports no org partition while the provider declares one — unreadable governance, never an empty one"] : resolution.disclosures };
61
68
  }
62
- const revisionCell = orgRevisionEvidenceOf(resolution, cfg.onRevisionDefect);
69
+ const reading = readOrgRules(resolution.rules);
70
+ if ("refused" in reading)
71
+ return { status: "unavailable", disclosures: [`the store's org resolution cannot be read (${reading.refused}) — unreadable governance, never an empty one`] };
63
72
  const command = req.args?.command;
64
- if (!laneToolNamesOf(req).includes(PERSISTED_RULE_TOOL) || typeof command !== "string")
65
- return { status: "available", ...revisionCell };
66
- const verdict = orgRuleVerdictFor(resolution.rules, { tool: PERSISTED_RULE_TOOL, command });
73
+ const face = callPathFaceOf(req);
74
+ const verdict = reading.verdictFor({ tool: laneToolNamesOf(req), ...(typeof command === "string" ? { command } : {}), ...(face !== undefined ? { face } : {}) });
75
+ const revisionCell = orgRevisionEvidenceOf(resolution, cfg.onRevisionDefect);
76
+ const degraded = describeUnreadableOrgRows(reading.unreadable, revisionCell.revision);
77
+ if (degraded !== undefined)
78
+ cfg.onDegraded(degraded);
67
79
  return verdict === undefined ? { status: "available", ...revisionCell } : { status: "available", verdict, ...revisionCell };
68
80
  },
69
81
  };
@@ -72,7 +84,7 @@ export function orgVerdictBeforeClaim(cfg, req) {
72
84
  const unreadable = { status: "unavailable", disclosures: [] };
73
85
  const read = () => {
74
86
  const store = ownerStoreOf(cfg.provider, cfg.principal, cfg.localOwnerDeclared);
75
- return orgRuleLane({ read: () => store.effective({ sessionId: cfg.sessionId }), questionToolMounted: true, questionToolName: ASK_USER_QUESTION_TOOL_NAME, onRevisionDefect: () => undefined }).adjudicate(req);
87
+ return orgRuleLane({ read: () => store.effective({ sessionId: cfg.sessionId }), questionToolMounted: true, questionToolName: ASK_USER_QUESTION_TOOL_NAME, onRevisionDefect: () => undefined, onDegraded: () => undefined }).adjudicate(req);
76
88
  };
77
89
  return settleOrgVerdictWithin(Promise.resolve().then(read).catch(() => unreadable), unreadable, { timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS, ...(cfg.signal !== undefined ? { signal: cfg.signal } : {}) });
78
90
  }
@@ -101,7 +113,7 @@ export function createPermissionRuleLanes(cfg) {
101
113
  throw new Error(read.unreadable ?? "the store did not answer");
102
114
  return { org: read.org };
103
115
  };
104
- const org = provider.partitions.org ? orgRuleLane({ read: orgViewOf, questionToolMounted: cfg.questionToolMounted, questionToolName: cfg.questionToolName, onRevisionDefect: cfg.onRevisionDefect }) : undefined;
116
+ const org = provider.partitions.org ? orgRuleLane({ read: orgViewOf, questionToolMounted: cfg.questionToolMounted, questionToolName: cfg.questionToolName, onRevisionDefect: cfg.onRevisionDefect, onDegraded: cfg.onDisclosure }) : undefined;
105
117
  const personalAdjudicate = async (req) => {
106
118
  if (anonymous && !cfg.localOwnerDeclared)
107
119
  return undefined;
@@ -14,8 +14,8 @@
14
14
  *
15
15
  * Every read's outcome lands in the run's unit, per personal partition (the durable one — its user and project rows — and
16
16
  * the session one), and only where it is newer, by the order the reads STARTED: a read that answers late never replaces
17
- * a later one. The prepare read is the unit's first write. A reader that is not a call (the compaction summary's working-file
18
- * reader, the user-attachment seat's reader, the Artifact tool's local read, a read root's grant eligibility, the classifier's table, a language server's session read)
17
+ * a later one. The prepare read is the unit's first write. A reader that is not a call (the compaction's kept-tail
18
+ * normalizer, the user-attachment seat's reader (the compaction restore's too), the Artifact tool's local read, a read root's grant eligibility, the classifier's table, a language server's session read)
19
19
  * reads the unit's newest tables: while the rows the unit last took are compiling it waits for them (within
20
20
  * {@link PATH_TABLES_READY_BOUND_MS}), and a read of tables still compiling, or that could not be compiled, refuses —
21
21
  * never the tables of rows the unit has since replaced.
@@ -237,7 +237,9 @@ export declare function ruleStoreUnreadableNotice(onNotice: RunnerDeps["onNotice
237
237
  /**
238
238
  * The run's ONE `permission.rule_row_dropped` notice (audience user): the person's store served project-scoped rows whose
239
239
  * scope root is not a complete absolute path, left out of the checks; minted by whichever read met them first, once per
240
- * run. The sentence names no path; the rows (bounded) ride the detail. The trace keeps every read's line.
240
+ * run. The sentence names no path; the rows ride the detail: `rule` is the rule text as the store served it, through the one
241
+ * reading ({@link noticeRuleText}) that `permission.allowed_tool_not_granted`'s `entries[].entry` uses too — the machine-read side carries the
242
+ * original, the sentence spells — and `scopeRoot` neutralized and bounded. The trace keeps every read's line.
241
243
  */
242
244
  export declare function ruleRowsDroppedNotice(onNotice: RunnerDeps["onNotice"], ids: {
243
245
  readonly sessionId: string;
@@ -1,6 +1,6 @@
1
1
  import { ownerStoreOf } from "../permission-rule-provider.js";
2
2
  import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
3
- import { PATH_RULE_FACES, READ_RULE_TOOL, eligiblePersisted } from "../permission-rule-model.js";
3
+ import { PATH_RULE_FACES, READ_RULE_TOOL, eligiblePersisted, noticeRuleText } from "../permission-rule-model.js";
4
4
  import { bindPathRules, persistedRowAnchors, projectScopeIdentity } from "../path-rule-binding.js";
5
5
  import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
6
6
  import { thrownText } from "../thrown-value.js";
@@ -382,7 +382,7 @@ export function ruleRowsDroppedNotice(onNotice, ids) {
382
382
  deliverEngineNotice(onNotice, {
383
383
  code: "permission.rule_row_dropped",
384
384
  message: `${rows.length} of your persisted permission rule rows ${one ? "is" : "are"} scoped to a project directory that is not written as a complete absolute path, so ${one ? "it takes" : "they take"} no part in this run's checks — a deny or ask row among them protects nothing. Rewrite ${one ? "it" : "them"} with the project's full path, or remove ${one ? "it" : "them"} (this notice's detail names ${one ? "it" : "them"}).`,
385
- detail: { rows: rows.slice(0, 8).map((r) => ({ rule: inlineUntrusted(r.rule, 200), scopeRoot: inlineUntrusted(r.scopeRoot, 200) })), count: rows.length, sessionId: ids.sessionId, runId: ids.runId },
385
+ detail: { rows: rows.slice(0, 8).map((r) => ({ rule: noticeRuleText(r.rule), scopeRoot: inlineUntrusted(r.scopeRoot, 200) })), count: rows.length, sessionId: ids.sessionId, runId: ids.runId },
386
386
  });
387
387
  };
388
388
  }
@@ -38,10 +38,9 @@ export interface PrepareAcquireReconcileInput {
38
38
  * suspension point BEFORE reconcile) and `suspendedBatch` (wake-reconcile SKIPS those calls —
39
39
  * they are resumed, not crash-interrupted). Absent for a fresh/non-durable run. */
40
40
  resume: Pick<PrepareResume, "leafId" | "suspendedBatch"> | undefined;
41
- /** borrowed-readonly here — the safety scan's effect registry (its mutation-owner table lives on
42
- * {@link import("./prepare-safety-scan.js").PrepareSafetyScanResult}); reconcile classifies an
43
- * orphaned call's retry-safety by it. */
44
- toolEffects: Map<string, ToolEffect>;
41
+ /** borrowed-readonly — the safety scan's pre-mount reading ({@link import("./prepare-safety-scan.js").PrepareSafetyScanResult.wakeEffects}):
42
+ * this reconcile runs before any tool mounts, so a contested name reads its least repeat-safe member; it classifies an orphan's retry-safety. */
43
+ wakeEffects: Map<string, ToolEffect>;
45
44
  /** borrowed-readonly — subagent transcript persistence: the trusted spawner's placement
46
45
  * declaration for THIS run's session ({@link RunInternals.sessionPlacement}), forwarded verbatim
47
46
  * into the create-form acquire so the store creates the child's transcript into its subagent
@@ -8,7 +8,7 @@ import { detectConflicts, settleBranchWriteBack } from "./branch-writeback.js";
8
8
  import { settleTeardownLeg } from "./teardown-bounded.js";
9
9
  const RECONCILE_MAX_RETRIES = 3;
10
10
  export async function prepareAcquireReconcile(input) {
11
- const { sessions, spec, resume, toolEffects } = input;
11
+ const { sessions, spec, resume, wakeEffects } = input;
12
12
  let acquired;
13
13
  let session;
14
14
  let conflictRef;
@@ -64,7 +64,7 @@ export async function prepareAcquireReconcile(input) {
64
64
  throw e;
65
65
  }
66
66
  if (userAttachmentOf(msg) !== undefined) {
67
- const e = new Error(`resumeAt entry "${spec.resumeAt}" is an attachment frame that trails a user message; resume at that user message instead`);
67
+ const e = new Error(`resumeAt entry "${spec.resumeAt}" is an attachment frame — a file the engine delivered, not a turn boundary; resume at the user message it belongs to, or at another user message or finished assistant turn`);
68
68
  e.code = "resume_at.not_a_message";
69
69
  throw e;
70
70
  }
@@ -100,7 +100,7 @@ export async function prepareAcquireReconcile(input) {
100
100
  own.ids = new Set();
101
101
  branch = { storage, leafBefore, leafSet, own };
102
102
  }
103
- wakeRecovered = (await reconcileInterruptedSession(session, toolEffects, resume?.suspendedBatch)).recovered;
103
+ wakeRecovered = (await reconcileInterruptedSession(session, wakeEffects, resume?.suspendedBatch)).recovered;
104
104
  break;
105
105
  }
106
106
  catch (err) {
@@ -2,7 +2,7 @@ import { cardDisclosureOf } from "../allow-entry.js";
2
2
  import { primaryActivityArg } from "../arg-summary.js";
3
3
  import { classicEventBase, observeClassic } from "../mods/stations.js";
4
4
  import { advertisedWritableRootsSeat, exemptRootsSeat, attest, attestationOf, isLiveApproverSeat, resolveAsk } from "../tool-policy.js";
5
- import { boundInputHashOf } from "../canonical-json.js";
5
+ import { exactBoundInputHashOf } from "../canonical-json.js";
6
6
  import { composeCallSignal, raceSettlementAgainstSignal } from "./abort-race.js";
7
7
  import { failedJudgmentDeny, policyCheckAbortedDeny } from "./policy-throw.js";
8
8
  import { askMandateMember } from "../ask-origin.js";
@@ -85,8 +85,8 @@ export function prepareAskLane(input) {
85
85
  };
86
86
  const settlement = { kind: "approver_unavailable", who: { party: "none" }, when: now() };
87
87
  return decision.action === "ask"
88
- ? attest(intercepted, req, { settlement, ...(decision.origin !== undefined ? { origin: decision.origin } : {}), ...(decision.classes !== undefined ? { classes: decision.classes } : {}), inputDigest: boundInputHashOf(req.args) })
89
- : attest(intercepted, req, { settlement, inputDigest: boundInputHashOf(req.args) });
88
+ ? attest(intercepted, req, { settlement, ...(decision.origin !== undefined ? { origin: decision.origin } : {}), ...(decision.classes !== undefined ? { classes: decision.classes } : {}), inputDigest: exactBoundInputHashOf(req.args) })
89
+ : attest(intercepted, req, { settlement, inputDigest: exactBoundInputHashOf(req.args) });
90
90
  }
91
91
  const grantReuse = consumeInheritedAskGrant(inheritedAskGrants, onAsk, humanReviewRef, decision, req);
92
92
  if (grantReuse !== undefined)