@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
@@ -1,3 +1,4 @@
1
+ import { type ReceiptFault } from "./mods/kernel/receive.js";
1
2
  import type { ToolContentOrigin, ToolEffect } from "./tool-spec.js";
2
3
  import type { ToolFamily, ToolPathTarget, ToolRenderHints, ToolRuleFace } from "./tool-face.js";
3
4
  /** Definition of an MCP server to connect to for the duration of one task, then discard. */
@@ -54,6 +55,8 @@ export interface McpServerSpec {
54
55
  * override is AUTHORITATIVE and may LOWER an effect (vouch a tool is `read`/`idempotent`) as well as raise it
55
56
  * (`egress` / irreversible). This is the ONLY trusted way to drop an MCP tool below the fail-closed `write`
56
57
  * default (design F). Folds over the server hints in prepare-task (caller > server hint > fail-closed write).
58
+ * `boundaryFlow: "engine"` vouches that a call's effect stays inside the engine: the tool's roster row states no boundary legs
59
+ * (`boundaryLegs: []`) instead of the legs its effect implies — it needs `egress: false` beside it (refused otherwise).
57
60
  *
58
61
  * A DIFFERENT AXIS from {@link contentOrigin}: this one is about what a call DOES (repeat-safety,
59
62
  * blast radius, reversibility — it feeds the approval gate); the content class is about what a call
@@ -61,7 +64,7 @@ export interface McpServerSpec {
61
64
  * a read-only tool can return third-party text, and a deployment's own writer brings back nothing
62
65
  * external — so vouching on one axis never quietly vouches on the other.
63
66
  *
64
- * THE SHAPE IS JUDGED, NOT TRUSTED: {@link readMcpToolAxes} is the one reader of this declaration. The
67
+ * THE SHAPE IS JUDGED, NOT TRUSTED: {@link readToolAxes} is the one reader of this declaration (an A2A peer's `toolAxes` is read by it too). The
65
68
  * preparation door runs it on every entry, and a declaration it cannot read refuses the whole prepare
66
69
  * (`config.invalid_argument`) — one bad tool refuses the entry, a bad key is never dropped and a bad word
67
70
  * never defaults, because a caller's safety axis that silently reads as absent is a trust root that is not
@@ -74,16 +77,19 @@ export interface McpServerSpec {
74
77
  effect?: ToolEffect;
75
78
  egress?: boolean;
76
79
  irreversibility?: "always" | "never";
80
+ boundaryFlow?: "engine";
77
81
  }>;
78
82
  /**
79
83
  * design/388 §1.1 / §2.7 — the OPTIONAL per-tool FACE overlay for this server's tools (keyed by the server's
80
84
  * RAW tool name, like {@link allowTools} / {@link toolAxes}): a deployment lets an MCP tool enter a specialised
81
85
  * approval card, be confirmed by path, carry a display name / summary, or narrow its rule vocabulary. TRUSTED
82
- * (the deployment writes it), but validated against the MATERIALIZED schema at every listing (first mount and
83
- * every refresh): a `pathTarget.param` that is not a top-level property, an `approvalCard` whose keys the schema
84
- * lacks, a `ruleFace` naming a non-scalar key ⇒ the tool mounts WITHOUT the overlay and the leg announces
85
- * `config.tool_face_invalid` once; a key naming a tool the listing does not carry announces
86
- * `config.tool_face_undeclared` once. Applied whole or not at all.
86
+ * (the deployment writes it). It is read once by `readToolFaces` at the preparation door and at `materializeMcpTools`: a
87
+ * declaration that does not fit on its own (a member the types do not have, a member of the wrong kind, a word outside a
88
+ * closed table, a reserved approval card, an empty tool name) refuses the entry under `config.tool_face_invalid` — the whole
89
+ * prepare at the door. What only the schema can say is judged at every listing (first mount and every refresh): a
90
+ * `pathTarget.param` that is not a top-level property, an `approvalCard` whose keys the schema lacks, a `ruleFace` naming a
91
+ * non-scalar key ⇒ that tool is NOT mounted and the leg announces `config.tool_face_invalid` once; a key naming a tool the
92
+ * listing does not carry announces `config.tool_face_undeclared` once. Applied whole or not at all.
87
93
  */
88
94
  toolFaces?: Record<string, {
89
95
  family?: ToolFamily;
@@ -167,31 +173,151 @@ export interface McpServerSpec {
167
173
  }
168
174
  type ToolAxes = NonNullable<McpServerSpec["toolAxes"]>;
169
175
  /**
170
- * {@link readMcpToolAxes}'s answer. `ok: true` carries the declaration as a PLAIN COPY the reader built from
176
+ * The surfaces that declare `toolAxes`, which {@link readToolAxes} names its reading by: an MCP server's keys are the server's
177
+ * remote TOOL names, an A2A peer's keys are the peer's remote SKILL ids.
178
+ */
179
+ export type ToolAxesSurface = "mcp" | "a2a";
180
+ /**
181
+ * {@link readToolAxes}'s answer. `ok: true` carries the declaration as a PLAIN COPY the reader built from
171
182
  * what it judged (never the caller's own object); `toolAxes` is absent when the entry declares nothing — no
172
183
  * key at all, or only empty per-tool objects (which the engine reads the same as no declaration). `ok: false`
173
- * carries one sentence naming the tool and the member that did not fit, never a value.
184
+ * carries one sentence naming the tool (or skill) and the member that did not fit, never a value.
174
185
  */
175
- export type McpToolAxesRead = {
186
+ export type ToolAxesRead = {
176
187
  ok: true;
177
188
  toolAxes?: ToolAxes;
178
189
  } | {
179
190
  ok: false;
180
191
  problem: string;
181
192
  };
193
+ /** A name the declarer wrote, quoted and bounded for a sentence (the sentence names; it never carries a value). The bound holds on the
194
+ * QUOTED form — escaping a quote, a backslash or a control character lengthens a name — so a sentence's budget is spent the same way
195
+ * whatever the name holds. */
196
+ export declare function quotedName(name: string): string;
197
+ /** The kind of a value that is plain data, for a sentence that names what was declared without carrying it. */
198
+ export declare function dataKind(value: unknown): string;
199
+ /**
200
+ * Where a declaration stops being one: the path from the declaration's root — a list index as a number, a member name as a string — and
201
+ * the clause that says what is there ("it contains itself; …"): a kind and a remedy, never a value the declarer wrote.
202
+ */
203
+ export interface DeclarationFault {
204
+ readonly path: readonly (string | number)[];
205
+ readonly problem: string;
206
+ }
207
+ /** {@link receiveDeclaration}'s answer: the engine's own frozen copy, or where and why the declaration could not be received. */
208
+ export type DeclarationRead = {
209
+ readonly ok: true;
210
+ readonly value: unknown;
211
+ } | {
212
+ readonly ok: false;
213
+ readonly fault: DeclarationFault;
214
+ };
215
+ /**
216
+ * A receipt's refusal of a declaration (or of the list that carries declarations), said as a declaration refusal says it. The receipt's
217
+ * path does not say which kind of container each name came from, so a segment of digits alone is read as a list index.
218
+ */
219
+ export declare function declarationFaultOf(fault: ReceiptFault): DeclarationFault;
220
+ /** What a declaration's own type fixes at one path, beyond being plain data: a string there, or a record of names to strings. */
221
+ export interface DeclarationPathRule {
222
+ readonly path: readonly string[];
223
+ readonly holds: "a string" | "names to strings";
224
+ /** The member must be there (a declaration with no value at the path is refused, as one with a value of another kind is). */
225
+ readonly required?: true;
226
+ }
227
+ /**
228
+ * Per surface, every path its declaration's type fixes beyond plain data — the declarer's `name` (a string, always there) and every record
229
+ * the type writes as `Record<string, string>`: an MCP server's `transport.env` (a stdio server's environment) and `transport.headers` (an
230
+ * HTTP server's headers); an A2A peer's `headers`. A path that is not its surface's (an A2A peer's `transport`) is not judged by another
231
+ * surface's row: it is a member the surface does not read.
232
+ */
233
+ export declare const DECLARATION_PATH_RULES: Readonly<Record<ToolAxesSurface, readonly DeclarationPathRule[]>>;
234
+ /** The rules of a declaration that fixes no path beyond plain data and lists of names (a `toolAxes` record, a lock declaration): the one
235
+ * empty table, passed on purpose — {@link receiveDeclaration} takes its rules as an argument and refuses an absent one. */
236
+ export declare const NO_DECLARATION_PATH_RULES: readonly DeclarationPathRule[];
237
+ /**
238
+ * The one reading of a declaration a caller hands the engine — an MCP server's or an A2A peer's entry (the snapshot), a `toolAxes`
239
+ * record ({@link readToolAxes}), a lock declaration: the engine's one receipt of a caller's object in its declaration mode, so the
240
+ * value is plain data held to the engine's one depth ({@link MODS_VALUE_DEPTH} levels below it) and every own member is read once, then, when the copy is a record, the declaration's own
241
+ * vocabulary over the copy (a list holds names; no member is named `__proto__`; each path `rules` names holds what its rule says — an
242
+ * entry's rules are its surface's {@link DECLARATION_PATH_RULES}). The copy is the answer — the caller's object is never handed on and
243
+ * never read again. A copy that is not a record is answered as it is, for the reader that expected a record to word.
244
+ *
245
+ * `rules` is required: a surface's {@link DECLARATION_PATH_RULES} row, or an empty list ({@link NO_DECLARATION_PATH_RULES} is the engine's
246
+ * own) for a declaration that fixes no path. A value that is not a list (an absent one — what an `at` outside the surfaces indexes to — `null`, a string, an inherited
247
+ * member of the table) is a caller's mistake about which declaration it holds, refused loudly (`config.invalid_argument`) rather than
248
+ * read as "no path rule", which would judge the declaration by less than its surface fixes.
249
+ */
250
+ export declare function receiveDeclaration(declared: unknown, rules: readonly DeclarationPathRule[]): DeclarationRead;
251
+ /**
252
+ * The per-tool declarations a spec carries as ONE record keyed by the remote tool (or skill) name — what each is called in a
253
+ * sentence about it. Every reader of such a record ({@link readToolAxes}, `readToolFaces`) opens it through
254
+ * {@link receivePerToolDeclaration} and words a refusal through the two sentence functions below, so the one receipt, the one
255
+ * fault vocabulary and the one sentence shape are not written twice.
256
+ */
257
+ declare const PER_TOOL_DECLARATIONS: {
258
+ readonly toolAxes: {
259
+ readonly plural: "axes";
260
+ readonly singular: "axis";
261
+ };
262
+ readonly toolFaces: {
263
+ readonly plural: "faces";
264
+ readonly singular: "face";
265
+ };
266
+ };
267
+ type PerToolDeclaration = keyof typeof PER_TOOL_DECLARATIONS;
268
+ /** {@link receivePerToolDeclaration}'s answer: the receipt's copy as a record keyed by name, or the one sentence that says why not. */
269
+ type PerToolDeclarationRead = {
270
+ readonly ok: true;
271
+ readonly perTool: Readonly<Record<string, unknown>>;
272
+ } | {
273
+ readonly ok: false;
274
+ readonly problem: string;
275
+ };
182
276
  /**
183
- * The one reader of a raw {@link McpServerSpec.toolAxes} declaration — the engine's preparation door and
184
- * every host that assembles the field from JSON ask this same function. Absent ⇒ `{ ok: true }` (nothing
185
- * declared). Anything else must be an object of per-tool objects whose members are exactly `effect` /
186
- * `egress` / `irreversibility` with legal values; a declaration of any other shape, an empty tool name, an
277
+ * A per-tool declaration's receipt: {@link receiveDeclaration} (plain data held to the engine's one depth, every own member read once,
278
+ * lists of names, no `__proto__`), then the copy must be a record of names. A refusal names the tool (or skill) and the member where
279
+ * reading stopped, never a value. `unit` is what the keys are called (`tool` / `skill`).
280
+ */
281
+ export declare function receivePerToolDeclaration(declared: unknown, field: PerToolDeclaration, unit: string): PerToolDeclarationRead;
282
+ /** The sentence for one tool's (or skill's) declaration that does not fit: `detail` says which member and why. */
283
+ export declare function perToolEntryRefusal(field: PerToolDeclaration, unit: string, key: string, detail: string): string;
284
+ /** The sentence for a per-tool declaration that names a tool (or skill) with an empty name. */
285
+ export declare function perToolEmptyName(field: PerToolDeclaration, unit: string): string;
286
+ /**
287
+ * The one reader of a raw `toolAxes` declaration — {@link McpServerSpec.toolAxes} and {@link A2aServerSpec.toolAxes} are the same
288
+ * declaration, and the engine's preparation door, its materialization of an MCP server and of an A2A peer, and every host that
289
+ * assembles the field from JSON ask this same function. `at` names the surface the declaration sits on, because that decides what its
290
+ * keys are called in the sentence: `"mcp"` — the server's remote tools; `"a2a"` — the peer's remote skills. Absent ⇒ `{ ok: true }`
291
+ * (nothing declared). Anything else must be an object of per-tool objects whose members are exactly `effect` /
292
+ * `egress` / `irreversibility` / `boundaryFlow` with legal values; a declaration of any other shape, an empty tool name, an
187
293
  * unknown member, a word outside the vocabulary or a non-boolean `egress` on ANY one tool refuses the whole
188
294
  * declaration (`ok: false`) — the good tools are not kept and nothing is defaulted, since a caller axis is
189
295
  * two-way (it can vouch a tool down as well as mark it up) and a silently lost half is a trust root that is
190
- * not in force. The reader judges SHAPE only; whether the axes contradict each other (`egress` over a
191
- * non-write effect) is the ingestion's own arithmetic. The reader is total: a getter, a proxy trap or a tag
192
- * that throws while the declaration is read is a refusal that says so (never the thrown text), not a throw.
296
+ * not in force. `boundaryFlow: "engine"` (the call's effect stays inside the engine — the tool's roster row states no
297
+ * boundary legs) needs `egress: false` in the same entry — `egress: true` contradicts it and an absent one leaves a server hint
298
+ * or the peer default marking it an external write — refused here; whether the other axes contradict each other (`egress`
299
+ * over a non-write effect) is the ingestion's own arithmetic. The declaration is read once, as plain data, by {@link receiveDeclaration} — a
300
+ * Proxy, a map, a class instance, a record built on a prototype or in another realm, a getter that throws, a structure that contains
301
+ * itself, a tool named `__proto__` are refusals that say where they sit (never the thrown text), not a throw — and judged on that copy.
302
+ * `at` is the caller's own word, not the declarer's: one outside {@link ToolAxesSurface} throws `config.invalid_argument` naming the
303
+ * surfaces, whatever is declared.
304
+ */
305
+ export declare function readToolAxes(declared: unknown, at: ToolAxesSurface): ToolAxesRead;
306
+ /** {@link readToolAxes} for an MCP server's declaration — the entry a host that mounts MCP servers asks. */
307
+ export declare function readMcpToolAxes(declared: unknown): ToolAxesRead;
308
+ /**
309
+ * The word a sentence about one declaration of the surface `at` names the declarer by — `MCP server`, `A2A peer`. The same row the
310
+ * `toolAxes` refusal below reads, so every sentence the engine writes about a declaration it cannot read calls the declarer one thing.
193
311
  */
194
- export declare function readMcpToolAxes(declared: unknown): McpToolAxesRead;
312
+ export declare function declarerWord(at: ToolAxesSurface): string;
313
+ /**
314
+ * The refusal an entry's unreadable `toolAxes` becomes (`config.invalid_argument`), ONE sentence for every surface that declares them:
315
+ * the subject is the surface's declarer word and `name` (already quoted and bounded by the caller: `MCP server "db"`, `A2A peer "agent"`),
316
+ * `problem` is {@link readToolAxes}'s own sentence.
317
+ */
318
+ export declare function toolAxesRefusal(at: ToolAxesSurface, name: string, problem: string): Error & {
319
+ code: "config.invalid_argument";
320
+ };
195
321
  /** The identity of one mount of a declared server, as the host minted it ({@link McpServerSpec.fingerprint}). */
196
322
  export interface McpMountIdentity {
197
323
  readonly fingerprint: string;
@@ -242,11 +368,20 @@ export interface A2aServerSpec {
242
368
  * raise it — e.g. `{ egress: false, effect: "read" }` vouches that a skill only reads. Note that
243
369
  * lowering `effect` WITHOUT clearing `egress` is a contradiction prepare-task refuses by design (an
244
370
  * external write must be repeat-unsafe).
371
+ *
372
+ * THE SHAPE IS JUDGED, NOT TRUSTED, by the same reader as {@link McpServerSpec.toolAxes} ({@link readToolAxes} with `at: "a2a"`) and
373
+ * with the same disposition: a task's preparation door runs it on every peer of `TaskSpec.a2a`, and a declaration it cannot read
374
+ * refuses the whole prepare (`config.invalid_argument`, subject `A2A peer "<name>"`) before any peer is dialed — one bad skill refuses
375
+ * the task, a bad key is never dropped and a bad word never defaults. The materialization entrance (`materializeA2aTools`, the library
376
+ * entrance) asks it once per peer, when it reads that peer's declaration: there a declaration it cannot read fails THAT peer's dial
377
+ * (never dialed, a warning whose cause is the same refusal), its siblings unaffected; a refresh does not ask again, it dials the copy
378
+ * that was judged (a peer refused at the entrance is refused again by every refresh, from the stored refusal).
245
379
  */
246
380
  toolAxes?: Record<string, {
247
381
  effect?: ToolEffect;
248
382
  egress?: boolean;
249
383
  irreversibility?: "always" | "never";
384
+ boundaryFlow?: "engine";
250
385
  }>;
251
386
  }
252
387
  /**
@@ -1,30 +1,171 @@
1
1
  import { SAFETY_AXIS_VOCABULARY } from "./safety-axis-vocab.js";
2
- const TOOL_AXIS_MEMBERS = { effect: true, egress: true, irreversibility: true };
2
+ import { INLINE_TRUNCATION_MARK, inlineUntrusted } from "./untrusted-text.js";
3
+ import { DECLARATION_RECEIPT_MODE } from "./own-property-receipt.js";
4
+ import { isPlainRecord, receive } from "./mods/kernel/receive.js";
5
+ import { MODS_VALUE_DEPTH } from "./mods/kernel/site-rules.js";
6
+ const TOOL_AXES_SURFACES = {
7
+ mcp: { key: "tool", declarer: "MCP server" },
8
+ a2a: { key: "skill", declarer: "A2A peer" },
9
+ };
10
+ function invalidArgument(message) {
11
+ const refusal = new Error(message);
12
+ refusal.code = "config.invalid_argument";
13
+ return refusal;
14
+ }
15
+ function toolAxesSurfaceOf(at) {
16
+ if (typeof at === "string" && Object.hasOwn(TOOL_AXES_SURFACES, at))
17
+ return TOOL_AXES_SURFACES[at];
18
+ throw invalidArgument(`toolAxes is read for a surface, and the surfaces are ${Object.keys(TOOL_AXES_SURFACES).map((s) => JSON.stringify(s)).join(" | ")} ` +
19
+ `(an MCP server's tools, an A2A peer's skills); the surface named is not one of them.`);
20
+ }
21
+ const TOOL_AXIS_MEMBERS = { effect: true, egress: true, irreversibility: true, boundaryFlow: true };
3
22
  const TOOL_AXIS_MEMBER_NAMES = Object.keys(TOOL_AXIS_MEMBERS);
4
23
  const TOOL_EFFECT_WORDS = SAFETY_AXIS_VOCABULARY.toolEffect;
5
24
  const ENGINE_DERIVED_IRREVERSIBILITY = "maybe";
6
25
  const IRREVERSIBILITY_POLE_WORDS = SAFETY_AXIS_VOCABULARY.irreversibility.filter((word) => word !== ENGINE_DERIVED_IRREVERSIBILITY);
7
26
  const irreversibilityPolesLockstep = true;
8
27
  void irreversibilityPolesLockstep;
9
- function quotedName(name) {
10
- return JSON.stringify(name.length > 80 ? `${name.slice(0, 80)}…` : name);
11
- }
12
- function describeKind(value) {
13
- return value === null ? "null" : Array.isArray(value) ? "an array" : typeof value === "object" ? "an object that is not a plain data record (a map, a class instance, or members that are inherited or not enumerable)" : `a ${typeof value}`;
14
- }
15
- const ROOT_PROTOTYPE_NAMES = Object.getOwnPropertyNames(Object.prototype).sort().join("\n");
16
- function isRootPrototype(proto) {
17
- if (proto === Object.prototype)
18
- return true;
19
- return Object.getPrototypeOf(proto) === null && Object.getOwnPropertyNames(proto).sort().join("\n") === ROOT_PROTOTYPE_NAMES;
20
- }
21
- function isPlainRecord(value) {
22
- if (typeof value !== "object" || value === null)
23
- return false;
24
- const proto = Object.getPrototypeOf(value);
25
- if (proto !== null && !isRootPrototype(proto))
26
- return false;
27
- return Object.getOwnPropertyNames(value).length === Object.keys(value).length;
28
+ const toolAxesDeclarationsAreOne = true;
29
+ void toolAxesDeclarationsAreOne;
30
+ export function quotedName(name) {
31
+ const whole = JSON.stringify(name);
32
+ if (name.length <= 80 && whole.length <= QUOTED_NAME_MAX)
33
+ return whole;
34
+ let cut = Math.min(name.length, 80);
35
+ while (cut > 0 && JSON.stringify(`${name.slice(0, cut)}…`).length > QUOTED_NAME_MAX)
36
+ cut--;
37
+ return JSON.stringify(`${name.slice(0, cut)}…`);
38
+ }
39
+ const QUOTED_NAME_MAX = 83;
40
+ function memberNames(path) {
41
+ let rendered = "";
42
+ for (const [index, name] of path.entries()) {
43
+ if (rendered.length > 120)
44
+ return `${rendered}${INLINE_TRUNCATION_MARK}`;
45
+ rendered += `${index === 0 ? "" : "."}${quotedName(String(name))}`;
46
+ }
47
+ return rendered;
48
+ }
49
+ export function dataKind(value) {
50
+ return value === null ? "null" : value === undefined ? "undefined" : Array.isArray(value) ? "an array" : typeof value === "object" ? "a record" : `a ${typeof value}`;
51
+ }
52
+ const RECEIPT_FAULT_PROBLEMS = {
53
+ proxy: "it is a Proxy, whose traps would run as it is read; copy it into plain data (a spread or a JSON round trip)",
54
+ "not-data": "it is not plain data (a function, a bigint, a symbol, or an object whose prototype is neither Object.prototype nor null: a class instance, " +
55
+ "a map, `process.env`, a record built on a prototype or in another realm — a `vm` context, a worker); copy it into plain data (a spread " +
56
+ "or a JSON round trip), writing out any member it inherits",
57
+ "getter-threw": "its getter threw while it was read",
58
+ "prototype-moved": "its prototype changed while it was read",
59
+ cycle: "it contains itself; declare it as plain data",
60
+ "too-deep": `it is nested past ${MODS_VALUE_DEPTH} levels`,
61
+ uncopyable: "it could not be copied whole",
62
+ accessor: "it is an accessor",
63
+ hidden: "it is a member that is not an enumerable data property",
64
+ };
65
+ export function declarationFaultOf(fault) {
66
+ return { path: fault.path.map((key) => (/^(?:0|[1-9]\d*)$/.test(key) ? Number(key) : key)), problem: RECEIPT_FAULT_PROBLEMS[fault.kind] };
67
+ }
68
+ export const DECLARATION_PATH_RULES = Object.freeze({
69
+ mcp: Object.freeze([
70
+ { path: ["name"], holds: "a string", required: true },
71
+ { path: ["transport", "env"], holds: "names to strings" },
72
+ { path: ["transport", "headers"], holds: "names to strings" },
73
+ ].map((rule) => Object.freeze({ ...rule, path: Object.freeze([...rule.path]) }))),
74
+ a2a: Object.freeze([
75
+ { path: ["name"], holds: "a string", required: true },
76
+ { path: ["headers"], holds: "names to strings" },
77
+ ].map((rule) => Object.freeze({ ...rule, path: Object.freeze([...rule.path]) }))),
78
+ });
79
+ export const NO_DECLARATION_PATH_RULES = Object.freeze([]);
80
+ const PROTO_MEMBER = `it is a member named "__proto__", which a copy by assignment would turn into a prototype — not a name a declaration can carry`;
81
+ function declarationVocabularyFault(root, rules) {
82
+ const asked = new Set();
83
+ const path = [];
84
+ const stack = [];
85
+ const ruleAt = () => rules.find((rule) => rule.path.length === path.length && rule.path.every((key, i) => key === path[i]));
86
+ const onRuleRoute = () => rules.some((rule) => rule.path.length > path.length && path.every((key, i) => key === rule.path[i]));
87
+ const ruleProblem = (rule, value) => {
88
+ if (rule.holds === "a string")
89
+ return typeof value === "string" ? undefined : `it is ${dataKind(value)}, not a string`;
90
+ if (!isPlainRecord(value))
91
+ return `it is ${dataKind(value)}, not a record of names to strings`;
92
+ for (const [key, member] of Object.entries(value)) {
93
+ path.push(key);
94
+ if (key === "__proto__")
95
+ return PROTO_MEMBER;
96
+ if (member !== undefined && typeof member !== "string")
97
+ return `it is ${dataKind(member)}, not a string: this record maps names to strings`;
98
+ path.pop();
99
+ }
100
+ return undefined;
101
+ };
102
+ const meet = (value) => {
103
+ if (typeof value !== "object" || value === null || (asked.has(value) && !onRuleRoute()))
104
+ return undefined;
105
+ asked.add(value);
106
+ if (Array.isArray(value)) {
107
+ for (let index = 0; index < value.length; index++) {
108
+ const own = Object.hasOwn(value, index);
109
+ if (own && typeof value[index] === "string")
110
+ continue;
111
+ path.push(index);
112
+ return `it is ${own ? dataKind(value[index]) : "a hole"}, not a string: every list a declaration carries is a list of names`;
113
+ }
114
+ const named = Object.keys(value).find((key) => !(/^(?:0|[1-9]\d*)$/.test(key) && Number(key) < value.length));
115
+ if (named === undefined)
116
+ return undefined;
117
+ path.push(named);
118
+ return "it is a list member that is not an index: a list declares its names at its indices only";
119
+ }
120
+ stack.push({ members: Object.entries(value), next: 0 });
121
+ return undefined;
122
+ };
123
+ for (const rule of rules) {
124
+ if (rule.required !== true)
125
+ continue;
126
+ let at = root;
127
+ for (const key of rule.path)
128
+ at = isPlainRecord(at) && Object.hasOwn(at, key) ? at[key] : undefined;
129
+ if (at === undefined)
130
+ return { path: [...rule.path], problem: `it is missing: a declaration carries ${rule.holds} here` };
131
+ }
132
+ let problem = meet(root);
133
+ while (problem === undefined && stack.length > 0) {
134
+ const frame = stack[stack.length - 1];
135
+ if (frame.next === frame.members.length) {
136
+ stack.pop();
137
+ if (stack.length > 0)
138
+ path.pop();
139
+ continue;
140
+ }
141
+ const [key, member] = frame.members[frame.next++];
142
+ path.push(key);
143
+ const rule = key === "__proto__" || member === undefined ? undefined : ruleAt();
144
+ if (key === "__proto__")
145
+ problem = PROTO_MEMBER;
146
+ else if (rule !== undefined)
147
+ problem = ruleProblem(rule, member);
148
+ else {
149
+ const depth = stack.length;
150
+ problem = meet(member);
151
+ if (problem === undefined && stack.length > depth)
152
+ continue;
153
+ }
154
+ if (problem === undefined)
155
+ path.pop();
156
+ }
157
+ return problem === undefined ? undefined : { path, problem };
158
+ }
159
+ export function receiveDeclaration(declared, rules) {
160
+ if (!Array.isArray(rules)) {
161
+ throw invalidArgument(`a declaration is received with the path rules of its surface (${Object.keys(DECLARATION_PATH_RULES).map((s) => `DECLARATION_PATH_RULES[${JSON.stringify(s)}]`).join(" | ")}), ` +
162
+ `or an empty list when it fixes no path; what was passed is ${dataKind(rules)}, not a list of rules.`);
163
+ }
164
+ const received = receive(declared, DECLARATION_RECEIPT_MODE);
165
+ if (!received.ok)
166
+ return { ok: false, fault: declarationFaultOf(received.fault) };
167
+ const fault = isPlainRecord(received.value) ? declarationVocabularyFault(received.value, rules) : undefined;
168
+ return fault === undefined ? { ok: true, value: received.value } : { ok: false, fault };
28
169
  }
29
170
  function isToolEffectWord(value) {
30
171
  return typeof value === "string" && TOOL_EFFECT_WORDS.includes(value);
@@ -34,40 +175,71 @@ function isIrreversibilityPoleWord(value) {
34
175
  }
35
176
  function readToolAxisEntry(raw) {
36
177
  if (!isPlainRecord(raw))
37
- return `the declaration is ${describeKind(raw)}, not an object of axes`;
38
- for (const member of Object.keys(raw)) {
39
- if (!TOOL_AXIS_MEMBER_NAMES.includes(member))
40
- return `unknown member ${quotedName(member)} (the members are ${TOOL_AXIS_MEMBER_NAMES.join(", ")})`;
178
+ return `the declaration is ${dataKind(raw)}, not an object of axes`;
179
+ const listed = Object.keys(raw);
180
+ const unknown = listed.find((member) => !TOOL_AXIS_MEMBER_NAMES.includes(member));
181
+ if (unknown !== undefined)
182
+ return `unknown member ${quotedName(unknown)} (the members are ${TOOL_AXIS_MEMBER_NAMES.join(", ")})`;
183
+ const declared = Object.create(null);
184
+ for (const member of TOOL_AXIS_MEMBER_NAMES) {
185
+ const value = raw[member];
186
+ if (value !== undefined && !listed.includes(member))
187
+ return `member ${quotedName(member)} is a hidden member: its name answers a value the declaration does not list as its own`;
188
+ declared[member] = value;
41
189
  }
42
- const { effect, egress, irreversibility } = raw;
190
+ const { effect, egress, irreversibility, boundaryFlow } = declared;
43
191
  if (effect !== undefined && !isToolEffectWord(effect))
44
192
  return `member "effect" is not one of ${TOOL_EFFECT_WORDS.join(" | ")}`;
45
193
  if (egress !== undefined && typeof egress !== "boolean")
46
194
  return `member "egress" is not a boolean`;
47
195
  if (irreversibility !== undefined && !isIrreversibilityPoleWord(irreversibility))
48
196
  return `member "irreversibility" is not one of ${IRREVERSIBILITY_POLE_WORDS.join(" | ")}`;
197
+ if (boundaryFlow !== undefined && boundaryFlow !== "engine")
198
+ return `member "boundaryFlow" is not "engine"`;
199
+ if (boundaryFlow === "engine" && egress !== false)
200
+ return `member "boundaryFlow": "engine" needs "egress": false in the same entry — add it, or drop "boundaryFlow" (${egress === true ? '"egress": true contradicts it' : 'with no "egress", the server hint or the peer default stays in force'})`;
49
201
  return {
50
202
  ...(effect !== undefined ? { effect } : {}),
51
203
  ...(egress !== undefined ? { egress } : {}),
52
204
  ...(irreversibility !== undefined ? { irreversibility } : {}),
205
+ ...(boundaryFlow !== undefined ? { boundaryFlow } : {}),
53
206
  };
54
207
  }
55
- export function readMcpToolAxes(declared) {
208
+ const PER_TOOL_DECLARATIONS = {
209
+ toolAxes: { plural: "axes", singular: "axis" },
210
+ toolFaces: { plural: "faces", singular: "face" },
211
+ };
212
+ export function receivePerToolDeclaration(declared, field, unit) {
213
+ const received = receiveDeclaration(declared, NO_DECLARATION_PATH_RULES);
214
+ if (!received.ok) {
215
+ const [key, ...member] = received.fault.path;
216
+ if (key === undefined)
217
+ return { ok: false, problem: `${field} cannot be read: ${received.fault.problem}` };
218
+ const where = member.length > 0 ? `member ${memberNames(member)}: ` : "";
219
+ return { ok: false, problem: perToolEntryRefusal(field, unit, String(key), `${where}${received.fault.problem}`) };
220
+ }
221
+ const perTool = received.value;
222
+ if (!isPlainRecord(perTool))
223
+ return { ok: false, problem: `${field} is ${dataKind(perTool)}, not an object of per-${unit} ${PER_TOOL_DECLARATIONS[field].singular} declarations` };
224
+ return { ok: true, perTool };
225
+ }
226
+ export function perToolEntryRefusal(field, unit, key, detail) {
227
+ return `the ${PER_TOOL_DECLARATIONS[field].plural} declared for ${unit} ${quotedName(key)} do not fit: ${detail}`;
228
+ }
229
+ export function perToolEmptyName(field, unit) {
230
+ return `${field} has an empty ${unit} name`;
231
+ }
232
+ export function readToolAxes(declared, at) {
233
+ const unit = toolAxesSurfaceOf(at).key;
56
234
  if (declared === undefined)
57
235
  return { ok: true };
58
- let perTool;
59
- try {
60
- if (!isPlainRecord(declared))
61
- return { ok: false, problem: `toolAxes is ${describeKind(declared)}, not an object of per-tool axis declarations` };
62
- perTool = Object.entries(declared);
63
- }
64
- catch {
65
- return { ok: false, problem: "toolAxes could not be read: a property threw while it was read" };
66
- }
236
+ const received = receivePerToolDeclaration(declared, "toolAxes", unit);
237
+ if (!received.ok)
238
+ return { ok: false, problem: received.problem };
67
239
  const entries = [];
68
- for (const [tool, raw] of perTool) {
69
- if (tool.length === 0)
70
- return { ok: false, problem: "toolAxes has an empty tool name" };
240
+ for (const [key, raw] of Object.entries(received.perTool)) {
241
+ if (key.length === 0)
242
+ return { ok: false, problem: perToolEmptyName("toolAxes", unit) };
71
243
  let entry;
72
244
  try {
73
245
  entry = readToolAxisEntry(raw);
@@ -76,9 +248,20 @@ export function readMcpToolAxes(declared) {
76
248
  entry = "a member threw while it was read";
77
249
  }
78
250
  if (typeof entry === "string")
79
- return { ok: false, problem: `the axes declared for tool ${quotedName(tool)} do not fit: ${entry}` };
251
+ return { ok: false, problem: perToolEntryRefusal("toolAxes", unit, key, entry) };
80
252
  if (Object.keys(entry).length > 0)
81
- entries.push([tool, entry]);
253
+ entries.push([key, entry]);
82
254
  }
83
255
  return entries.length > 0 ? { ok: true, toolAxes: Object.fromEntries(entries) } : { ok: true };
84
256
  }
257
+ export function readMcpToolAxes(declared) {
258
+ return readToolAxes(declared, "mcp");
259
+ }
260
+ export function declarerWord(at) {
261
+ return toolAxesSurfaceOf(at).declarer;
262
+ }
263
+ export function toolAxesRefusal(at, name, problem) {
264
+ return invalidArgument(`${toolAxesSurfaceOf(at).declarer} ${name} declares toolAxes the engine cannot read: ${inlineUntrusted(problem, 240)}. A per-tool safety-axis declaration is the ` +
265
+ `caller's trust root for the approval gate, so it is refused rather than partly applied — dropping one key or defaulting one word ` +
266
+ `would leave a declaration that reads as in force while it is not.`);
267
+ }
@@ -110,14 +110,15 @@ export interface MaterializedMcp {
110
110
  * G1 通告层续批 (CC `mcp_instructions_delta` parity) — the run-scoped instruction-delta ref the run
111
111
  * loop drains at turn boundaries (`spec.attachments.mcpInstructions` opt-in; `newTools` consumption
112
112
  * discipline: drained only when the announcement survived the byte cap intact).
113
- * - `pendingRemovals`: an INSTRUCTION-BEARING server whose transport closed mid-task (the CC
114
- * "servers have disconnected — their instructions above no longer apply" copy is instruction-scoped,
115
- * so instruction-less servers are not tracked). `dispose()` disarms first: task teardown is not a
116
- * disconnect.
117
- * - `pendingAdds`: the CC add lane ("# MCP Server Instructions" reminder). Structurally present for a
118
- * dynamic mid-task connect face; TODAY nothing populates it — sema connects all servers at prepare
119
- * and carries their initial instructions in the stable system prompt (design/64 §17.3, deliberate
120
- * CC deviation: prefix-cache-correct).
113
+ * - `pendingRemovals`: a server whose instructions are in the model's context and whose transport closed
114
+ * mid-task, or whose re-dial failed (the CC "servers have disconnected — their instructions above no
115
+ * longer apply" copy names only what the model holds — the lane's ledger, `instructionsTold`).
116
+ * `dispose()` disarms first: task teardown is not a disconnect.
117
+ * - `pendingAdds`: the CC add lane ("# MCP Server Instructions" reminder). A server's INITIAL
118
+ * instructions ride the stable system prompt instead (design/64 §17.3, deliberate CC deviation:
119
+ * prefix-cache-correct); this lane carries what comes later — a re-dial queues its new
120
+ * connection's text here, and a run adds the text of a server it is to teach and has not told.
121
+ * A materialization alone takes every entry of `serverInstructions` as taught; a run states its block.
121
122
  */
122
123
  instructionsDelta: {
123
124
  pendingAdds: McpInstructionsEntry[];
@@ -163,7 +164,7 @@ export interface MaterializedMcp {
163
164
  droppedTools: McpDroppedTool[];
164
165
  /**
165
166
  * design/99 §E9 — per-server status, a PURE PROJECTION of THIS materialization (no new connection logic).
166
- * One entry per declared server. **Semantics = the moment of this task leg's materialize** — core never
167
+ * One entry per declared server DIALED (none for one `dialable` kept from the dial). **Semantics = the moment of this task leg's materialize** — core never
167
168
  * persists MCP connections, so it is "did this leg connect", NOT a live session-health signal between tasks.
168
169
  * A consumer (service) surfacing it as session status must understand that. `toolNames` are the namespaced
169
170
  * (`<server>__<tool>`) names exposed.
@@ -178,7 +179,7 @@ export interface MaterializedMcp {
178
179
  /**
179
180
  * RefreshMcpTools (CC 2.1.218 parity, anchored to the CLI form) — re-read the tool list of one server (or
180
181
  * every CONNECTED server when `server` is omitted) over the EXISTING connection. NEVER dials or
181
- * re-dials: a dead/never-connected server reports `not_connected`; a live listTools failure
182
+ * re-dials: a dead/never-connected server reports `not_connected` (so does one whose connection closed while the walk was reading); a live listTools failure
182
183
  * reports `failed` (the connection is left as-is). A refreshed entry carries the NEW intake
183
184
  * result (tools/axes/dropped — the same schema-gate/namespacing/axis pipeline as connect) plus
184
185
  * the added/removed namespaced tool names relative to the previous listing; the caller (the
@@ -229,6 +230,7 @@ export interface McpRefreshResult {
229
230
  * contact it (no tools/list round trip; the engine must not hand a severed server a request
230
231
  * channel or splice its post-revocation text into the model catalog). */
231
232
  status: "refreshed" | "not_connected" | "failed" | "revoked";
233
+ /** The same reading as {@link McpReconnectResult.toolCount}: the server's tools the producer holds after the call. */
232
234
  toolCount: number;
233
235
  added: string[];
234
236
  /**
@@ -243,6 +245,9 @@ export interface McpRefreshResult {
243
245
  tools?: AgentTool[];
244
246
  axes?: McpToolAxis[];
245
247
  dropped?: McpDroppedTool[];
248
+ /** On a `"refreshed"` entry whose connection carries instructions: that connection's entry as its dial disposed of it (`author: "host"` when replaced) —
249
+ * a refresh re-reads tools, never instructions; a run whose roster change first leaves something of the server teaches it. */
250
+ instructions?: McpInstructionsEntry;
246
251
  /**
247
252
  * Set on `not_connected` / `failed` / `revoked`; ALSO set on a `"refreshed"` entry whose listing walk
248
253
  * did not finish (see {@link listingIncomplete}) — a refresh that produced usable tools is not a
@@ -287,12 +292,14 @@ export interface McpReconnectResult {
287
292
  /** Why, on every non-`reconnected` outcome (neutralized + bounded when it embeds remote text).
288
293
  * Never the only machine-readable fact: see {@link status}. */
289
294
  reason?: string;
295
+ /** How many of the server's tools the producer holds after the call — its roster: the run's (the valve and the deployment's
296
+ * overlay applied) for the run handle's `reconnectMcpServer`, this materialization's own handles for `MaterializedMcp.reconnect`
297
+ * (no run). A name no server is declared under holds nothing. Equal to `tools.length` whenever `tools` is present. */
290
298
  toolCount: number;
291
- /** Namespaced names this re-dial added / took away relative to what the server had mounted a moment
292
- * ago — a first successful dial adds its whole roster, a failed dial removes it. */
299
+ /** The namespaced names that came onto / left that roster (a first dial adds its whole roster, a failed dial removes it). */
293
300
  added: string[];
294
301
  removed: string[];
295
- /** See the type docstring: present iff this result asserts a roster for `prefix`. */
302
+ /** See the type docstring: present iff this result asserts a roster for `prefix` — and then it is that roster's tools of the server. */
296
303
  tools?: AgentTool[];
297
304
  axes?: McpToolAxis[];
298
305
  dropped?: McpDroppedTool[];
@@ -609,7 +616,7 @@ reminderDisclosure?: {
609
616
  }, poolOpts?: {
610
617
  pool: McpConnectionPool;
611
618
  ownsPool?: boolean;
612
- }, promptSections?: PromptSectionDisposer): Promise<MaterializedMcp>;
619
+ }, promptSections?: PromptSectionDisposer, dialable?: (spec: McpServerSpec) => boolean): Promise<MaterializedMcp>;
613
620
  /**
614
621
  * Fold the caller's AUTHORITATIVE per-tool override (design F: caller = trust root) over the server-hint axis.
615
622
  * Unlike server hints, the caller may RAISE or LOWER any axis: `effect` sets the repeat-safety class (lower to