@sema-agent/core 7.51.2 → 7.52.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 (363) hide show
  1. package/CHANGELOG.md +504 -2
  2. package/dist/agents/agent-transcript-tool.d.ts +1 -1
  3. package/dist/agents/cascade.js +4 -0
  4. package/dist/agents/cumulative-stats.d.ts +3 -0
  5. package/dist/agents/cumulative-stats.js +3 -0
  6. package/dist/agents/list-agents-tool.d.ts +1 -1
  7. package/dist/agents/read-inbox-tool.d.ts +1 -1
  8. package/dist/agents/repair-loop.js +4 -0
  9. package/dist/agents/send-message-tool.d.ts +11 -16
  10. package/dist/agents/send-message-tool.js +11 -13
  11. package/dist/agents/subagent.js +6 -4
  12. package/dist/agents/tool-filter.d.ts +40 -13
  13. package/dist/agents/tool-filter.js +28 -14
  14. package/dist/bench/metrics.d.ts +1 -0
  15. package/dist/brain/anthropic.js +16 -12
  16. package/dist/brain/model-presets.d.ts +4 -5
  17. package/dist/brain/model-presets.js +4 -4
  18. package/dist/brain/open-responses.js +16 -12
  19. package/dist/brain/openai.js +16 -12
  20. package/dist/brain/request-params.d.ts +12 -33
  21. package/dist/brain/request-params.js +5 -2
  22. package/dist/brain/request-route.d.ts +9 -8
  23. package/dist/brain/request-route.js +1 -0
  24. package/dist/brain/route-adjudicator.d.ts +117 -47
  25. package/dist/brain/route-adjudicator.js +171 -42
  26. package/dist/brain/route-conformance.js +2 -2
  27. package/dist/brain/status-sink.d.ts +18 -0
  28. package/dist/brain/status-sink.js +7 -0
  29. package/dist/core/allow-entry.d.ts +40 -8
  30. package/dist/core/allow-entry.js +23 -16
  31. package/dist/core/auto-compaction.d.ts +23 -20
  32. package/dist/core/auto-compaction.js +37 -16
  33. package/dist/core/auto-promote.d.ts +2 -1
  34. package/dist/core/auto-promote.js +2 -2
  35. package/dist/core/caller-tool-name.d.ts +9 -0
  36. package/dist/core/caller-tool-name.js +13 -0
  37. package/dist/core/checkpoint-requirements.d.ts +7 -1
  38. package/dist/core/checkpoint-requirements.js +2 -0
  39. package/dist/core/checkpoint-store.d.ts +36 -12
  40. package/dist/core/checkpoint-store.js +1 -1
  41. package/dist/core/child-clamp-seats.d.ts +6 -0
  42. package/dist/core/child-clamp-seats.js +2 -1
  43. package/dist/core/credential-vocabulary.d.ts +33 -8
  44. package/dist/core/credential-vocabulary.js +32 -7
  45. package/dist/core/decision-receipt.d.ts +2 -2
  46. package/dist/core/delegation-frames.d.ts +9 -2
  47. package/dist/core/delegation-name.d.ts +18 -0
  48. package/dist/core/delegation-name.js +47 -0
  49. package/dist/core/engine-capabilities.d.ts +8 -1
  50. package/dist/core/engine-capabilities.js +1 -1
  51. package/dist/core/engine-notice.d.ts +36 -19
  52. package/dist/core/frontmatter-fence.d.ts +10 -0
  53. package/dist/core/frontmatter-fence.js +6 -0
  54. package/dist/core/gate-fold.js +5 -23
  55. package/dist/core/gate-lanes.js +356 -270
  56. package/dist/core/governance-codes.d.ts +9 -4
  57. package/dist/core/governance-codes.js +9 -3
  58. package/dist/core/hooks.d.ts +276 -734
  59. package/dist/core/hooks.js +97 -192
  60. package/dist/{tools/fs → core}/image-admission.d.ts +21 -3
  61. package/dist/{tools/fs → core}/image-admission.js +24 -6
  62. package/dist/core/mcp-connection-pool.d.ts +4 -1
  63. package/dist/core/mcp-connection-pool.js +7 -2
  64. package/dist/core/mcp-probe.d.ts +12 -9
  65. package/dist/core/mcp-probe.js +2 -2
  66. package/dist/core/mcp-server-spec.d.ts +16 -0
  67. package/dist/core/mcp.d.ts +11 -7
  68. package/dist/core/mcp.js +11 -5
  69. package/dist/core/memory-engine/data-plane.js +5 -1
  70. package/dist/core/memory-engine/distiller.js +16 -2
  71. package/dist/core/memory-engine/engine.d.ts +8 -11
  72. package/dist/core/memory-engine/engine.js +9 -5
  73. package/dist/core/memory-engine/frontmatter.d.ts +6 -0
  74. package/dist/core/memory-engine/frontmatter.js +19 -16
  75. package/dist/core/memory.d.ts +2 -2
  76. package/dist/core/mode-policy.d.ts +4 -0
  77. package/dist/core/mode-policy.js +7 -5
  78. package/dist/core/model-seat.d.ts +4 -1
  79. package/dist/core/mods/dollar.d.ts +109 -0
  80. package/dist/core/mods/dollar.js +154 -0
  81. package/dist/core/mods/host.d.ts +23 -0
  82. package/dist/core/mods/host.js +21 -0
  83. package/dist/core/mods/kernel/budget.d.ts +43 -0
  84. package/dist/core/mods/kernel/budget.js +83 -0
  85. package/dist/core/mods/kernel/dispatch.d.ts +100 -0
  86. package/dist/core/mods/kernel/dispatch.js +701 -0
  87. package/dist/core/mods/kernel/events.d.ts +242 -0
  88. package/dist/core/mods/kernel/events.js +210 -0
  89. package/dist/core/mods/kernel/index.d.ts +6 -0
  90. package/dist/core/mods/kernel/index.js +5 -0
  91. package/dist/core/mods/kernel/receive.d.ts +65 -0
  92. package/dist/core/mods/kernel/receive.js +123 -0
  93. package/dist/core/mods/kernel/registration.d.ts +131 -0
  94. package/dist/core/mods/kernel/registration.js +102 -0
  95. package/dist/core/mods/kernel/site-rules.d.ts +84 -0
  96. package/dist/core/mods/kernel/site-rules.js +104 -0
  97. package/dist/core/mods/stations.d.ts +350 -0
  98. package/dist/core/mods/stations.js +444 -0
  99. package/dist/core/own-property-receipt.js +11 -23
  100. package/dist/core/parent-spec-seat.d.ts +18 -7
  101. package/dist/core/parent-spec-seat.js +6 -2
  102. package/dist/core/path-identity.js +2 -1
  103. package/dist/core/permission-rule-syntax.d.ts +15 -0
  104. package/dist/core/permission-rule-syntax.js +24 -16
  105. package/dist/core/permission-rules.d.ts +7 -1
  106. package/dist/core/permission-rules.js +26 -15
  107. package/dist/core/prompt-section-seat.d.ts +15 -27
  108. package/dist/core/prompt-section-seat.js +73 -40
  109. package/dist/core/protocol-http-auth.d.ts +3 -1
  110. package/dist/core/protocol-http-auth.js +2 -0
  111. package/dist/core/reminder-mint.d.ts +2 -2
  112. package/dist/core/remote-env.d.ts +24 -12
  113. package/dist/core/remote-env.js +1 -0
  114. package/dist/core/resume-startup-cause.d.ts +16 -1
  115. package/dist/core/resume-startup-cause.js +4 -1
  116. package/dist/core/retired-keys.d.ts +4 -0
  117. package/dist/core/retired-keys.js +3 -0
  118. package/dist/core/runner/announce-once-ledger.d.ts +31 -1
  119. package/dist/core/runner/announce-once-ledger.js +22 -0
  120. package/dist/core/runner/assemble-result.d.ts +9 -0
  121. package/dist/core/runner/assemble-result.js +3 -1
  122. package/dist/core/runner/call-hook-stations.d.ts +23 -27
  123. package/dist/core/runner/call-hook-stations.js +58 -42
  124. package/dist/core/runner/caller-stop-gate.d.ts +25 -0
  125. package/dist/core/runner/caller-stop-gate.js +7 -0
  126. package/dist/core/runner/compaction-seams.d.ts +8 -22
  127. package/dist/core/runner/compaction-seams.js +28 -53
  128. package/dist/core/runner/construction-doors.js +11 -1
  129. package/dist/core/runner/contracts.d.ts +85 -83
  130. package/dist/core/runner/credential-origin-seat.d.ts +2 -0
  131. package/dist/core/runner/credential-origin-seat.js +19 -0
  132. package/dist/core/runner/extra-body-dropped-seat.d.ts +2 -6
  133. package/dist/core/runner/gate-exit.d.ts +47 -36
  134. package/dist/core/runner/gate-exit.js +8 -2
  135. package/dist/core/runner/image.js +1 -2
  136. package/dist/core/runner/inline-image.js +1 -1
  137. package/dist/core/runner/notice-seat-input.d.ts +11 -0
  138. package/dist/core/runner/notice-seat-input.js +1 -0
  139. package/dist/core/runner/person-path-rows.js +1 -0
  140. package/dist/core/runner/prepare-acquire-reconcile.js +5 -1
  141. package/dist/core/runner/prepare-ask-lane.d.ts +5 -7
  142. package/dist/core/runner/prepare-ask-lane.js +6 -15
  143. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
  144. package/dist/core/runner/prepare-caps-and-workflow.js +25 -8
  145. package/dist/core/runner/prepare-config-doors.d.ts +5 -0
  146. package/dist/core/runner/prepare-config-doors.js +9 -0
  147. package/dist/core/runner/prepare-defer-classify.js +3 -4
  148. package/dist/core/runner/prepare-gate-stations.d.ts +8 -11
  149. package/dist/core/runner/prepare-gate-stations.js +11 -10
  150. package/dist/core/runner/prepare-inherited-gate.d.ts +8 -12
  151. package/dist/core/runner/prepare-inherited-gate.js +8 -21
  152. package/dist/core/runner/prepare-memory-engine-session.js +1 -1
  153. package/dist/core/runner/prepare-park-ask.js +10 -14
  154. package/dist/core/runner/prepare-policy-chain.d.ts +17 -15
  155. package/dist/core/runner/prepare-policy-chain.js +95 -100
  156. package/dist/core/runner/prepare-project-context.js +7 -10
  157. package/dist/core/runner/prepare-prompt-assembly.d.ts +45 -40
  158. package/dist/core/runner/prepare-prompt-assembly.js +134 -101
  159. package/dist/core/runner/prepare-prompt-inputs.d.ts +18 -19
  160. package/dist/core/runner/prepare-prompt-inputs.js +5 -7
  161. package/dist/core/runner/prepare-protocol-tools.d.ts +6 -4
  162. package/dist/core/runner/prepare-protocol-tools.js +2 -2
  163. package/dist/core/runner/prepare-safety-scan.js +6 -10
  164. package/dist/core/runner/prepare-suspend-saga.d.ts +6 -0
  165. package/dist/core/runner/prepare-suspend-saga.js +7 -5
  166. package/dist/core/runner/prepare-task.js +33 -15
  167. package/dist/core/runner/prepare-turn-wiring.d.ts +13 -15
  168. package/dist/core/runner/prepare-turn-wiring.js +29 -27
  169. package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -6
  170. package/dist/core/runner/prepare-wiring-manifest.js +7 -7
  171. package/dist/core/runner/resume-apply.d.ts +5 -2
  172. package/dist/core/runner/resume-apply.js +24 -42
  173. package/dist/core/runner/resume-checkpoint-screen.d.ts +1 -0
  174. package/dist/core/runner/resume-checkpoint-screen.js +9 -1
  175. package/dist/core/runner/resume-preflight.js +38 -16
  176. package/dist/core/runner/retired-runner-deps.js +3 -1
  177. package/dist/core/runner/run-brain-sinks.d.ts +3 -2
  178. package/dist/core/runner/run-brain-sinks.js +5 -1
  179. package/dist/core/runner/run-compaction-machinery.d.ts +7 -1
  180. package/dist/core/runner/run-compaction-machinery.js +7 -33
  181. package/dist/core/runner/run-harness-handlers.js +4 -2
  182. package/dist/core/runner/run-identity-wiring.d.ts +3 -1
  183. package/dist/core/runner/run-identity-wiring.js +3 -2
  184. package/dist/core/runner/run-leg.js +62 -84
  185. package/dist/core/runner/run-notification-lane.js +1 -0
  186. package/dist/core/runner/run-recovery-lanes.d.ts +3 -0
  187. package/dist/core/runner/run-recovery-lanes.js +11 -10
  188. package/dist/core/runner/run-settle-and-teardown.js +9 -28
  189. package/dist/core/runner/run-stop-and-final-verify.js +18 -25
  190. package/dist/core/runner/run-terminal-adoption.d.ts +3 -0
  191. package/dist/core/runner/run-terminal-adoption.js +33 -22
  192. package/dist/core/runner/run-tool-mount-facts.js +5 -3
  193. package/dist/core/runner/run-turn-boundary.js +23 -34
  194. package/dist/core/runner/runtask.d.ts +6 -7
  195. package/dist/core/runner/runtask.js +14 -72
  196. package/dist/core/runner/session-rule-policy.d.ts +5 -5
  197. package/dist/core/runner/session-rule-policy.js +13 -8
  198. package/dist/core/runner/side-call-account.d.ts +68 -0
  199. package/dist/core/runner/side-call-account.js +61 -0
  200. package/dist/core/runner/stream-halt-verbs.js +12 -17
  201. package/dist/core/runner/stream-lifecycle-verbs.js +8 -5
  202. package/dist/core/runner/stream-steer-verb.d.ts +1 -1
  203. package/dist/core/runner/stream-steer-verb.js +20 -38
  204. package/dist/core/runner/sub-agent-deny-lists.d.ts +121 -22
  205. package/dist/core/runner/sub-agent-deny-lists.js +228 -71
  206. package/dist/core/runner/tool-end-body.d.ts +5 -6
  207. package/dist/core/runner/tool-end-body.js +6 -4
  208. package/dist/core/runner/tool-not-executed.d.ts +15 -0
  209. package/dist/core/runner/tool-not-executed.js +30 -0
  210. package/dist/core/runner/tool-occurrence.d.ts +8 -7
  211. package/dist/core/runner/tool-occurrence.js +5 -5
  212. package/dist/core/runner/tool-result-media-seat.d.ts +1 -1
  213. package/dist/core/runner-deps.d.ts +20 -28
  214. package/dist/core/safety-merge-corpus.js +18 -2
  215. package/dist/core/secret-env.d.ts +88 -20
  216. package/dist/core/secret-env.js +192 -38
  217. package/dist/core/session-policy-store.d.ts +52 -16
  218. package/dist/core/session-policy-store.js +118 -28
  219. package/dist/core/session-reconcile.d.ts +4 -0
  220. package/dist/core/session-reconcile.js +2 -1
  221. package/dist/core/shell-read-silence.js +3 -32
  222. package/dist/core/side-query.d.ts +5 -3
  223. package/dist/core/skill-grant.d.ts +3 -2
  224. package/dist/core/skill-grant.js +17 -9
  225. package/dist/core/skills-directory.d.ts +31 -11
  226. package/dist/core/skills-directory.js +655 -104
  227. package/dist/core/task-event.d.ts +13 -3
  228. package/dist/core/task-result.d.ts +8 -0
  229. package/dist/core/task-spec.d.ts +20 -19
  230. package/dist/core/task-stream.d.ts +14 -4
  231. package/dist/core/tighten-task-spec.d.ts +4 -3
  232. package/dist/core/tighten-task-spec.js +2 -4
  233. package/dist/core/tool-check-reading.d.ts +74 -0
  234. package/dist/core/tool-check-reading.js +80 -0
  235. package/dist/core/tool-policy.d.ts +59 -18
  236. package/dist/core/tool-policy.js +77 -12
  237. package/dist/core/tool-registry.d.ts +20 -0
  238. package/dist/core/tool-registry.js +4 -0
  239. package/dist/core/tool-spec.d.ts +10 -6
  240. package/dist/core/tools.js +2 -0
  241. package/dist/core/trace.d.ts +51 -32
  242. package/dist/core/unc-spelling.d.ts +10 -0
  243. package/dist/core/unc-spelling.js +3 -0
  244. package/dist/core/untrusted-egress.d.ts +8 -0
  245. package/dist/core/untrusted-egress.js +4 -1
  246. package/dist/core/user-attachment.d.ts +12 -2
  247. package/dist/core/user-attachment.js +17 -2
  248. package/dist/core/wiring-manifest.d.ts +14 -13
  249. package/dist/core/wiring-manifest.js +2 -2
  250. package/dist/engine/compaction/utils.js +1 -2
  251. package/dist/engine/execution-env/node-execution-env.d.ts +4 -3
  252. package/dist/engine/execution-env/symlink-chain.js +1 -1
  253. package/dist/engine/harness/agent-harness.d.ts +1 -1
  254. package/dist/engine/harness/agent-harness.js +3 -3
  255. package/dist/engine/harness/types.d.ts +17 -16
  256. package/dist/engine/harness/types.js +7 -1
  257. package/dist/engine/llm/types.d.ts +12 -7
  258. package/dist/engine/loop/agent-loop.js +1 -1
  259. package/dist/engine/loop/types.d.ts +1 -1
  260. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
  261. package/dist/engine/session/import-validate.js +3 -10
  262. package/dist/engine/session/session.d.ts +4 -4
  263. package/dist/engine/session/session.js +4 -4
  264. package/dist/index.d.ts +20 -16
  265. package/dist/index.js +13 -11
  266. package/dist/internal/harness.d.ts +1 -1
  267. package/dist/internal/harness.js +1 -1
  268. package/dist/orchestration/run-spec.d.ts +4 -6
  269. package/dist/orchestration/run-spec.js +39 -63
  270. package/dist/orchestration/run-workflow-tool.js +1 -2
  271. package/dist/orchestration/workflow.d.ts +11 -3
  272. package/dist/orchestration/workflow.js +11 -2
  273. package/dist/prompt-assembly/artifact-store.d.ts +37 -56
  274. package/dist/prompt-assembly/artifact-store.js +31 -49
  275. package/dist/prompt-assembly/artifact.d.ts +89 -76
  276. package/dist/prompt-assembly/artifact.js +167 -146
  277. package/dist/prompt-assembly/assemble.d.ts +34 -29
  278. package/dist/prompt-assembly/assemble.js +33 -220
  279. package/dist/prompt-assembly/bands.d.ts +20 -0
  280. package/dist/prompt-assembly/bands.js +18 -0
  281. package/dist/prompt-assembly/catalog/constitution.d.ts +23 -0
  282. package/dist/prompt-assembly/catalog/constitution.js +69 -0
  283. package/dist/prompt-assembly/catalog/core-behavior.d.ts +9 -0
  284. package/dist/prompt-assembly/catalog/core-behavior.js +85 -0
  285. package/dist/prompt-assembly/catalog/index.d.ts +11 -0
  286. package/dist/prompt-assembly/catalog/index.js +59 -0
  287. package/dist/prompt-assembly/catalog/modes.d.ts +15 -0
  288. package/dist/prompt-assembly/catalog/modes.js +137 -0
  289. package/dist/prompt-assembly/catalog/role.d.ts +10 -0
  290. package/dist/prompt-assembly/catalog/role.js +157 -0
  291. package/dist/prompt-assembly/catalog/schema.d.ts +124 -0
  292. package/dist/prompt-assembly/catalog/schema.js +25 -0
  293. package/dist/prompt-assembly/catalog/sections.d.ts +9 -0
  294. package/dist/prompt-assembly/catalog/sections.js +98 -0
  295. package/dist/prompt-assembly/catalog/team.d.ts +4 -0
  296. package/dist/prompt-assembly/catalog/team.js +258 -0
  297. package/dist/prompt-assembly/composer.d.ts +24 -28
  298. package/dist/prompt-assembly/composer.js +124 -46
  299. package/dist/prompt-assembly/epoch.d.ts +89 -79
  300. package/dist/prompt-assembly/epoch.js +115 -142
  301. package/dist/prompt-assembly/explain.d.ts +58 -34
  302. package/dist/prompt-assembly/explain.js +21 -24
  303. package/dist/prompt-assembly/overlay.d.ts +127 -0
  304. package/dist/prompt-assembly/overlay.js +301 -0
  305. package/dist/prompt-assembly/renderers.d.ts +21 -0
  306. package/dist/prompt-assembly/renderers.js +39 -0
  307. package/dist/prompt-assembly/section-usage.d.ts +3 -3
  308. package/dist/prompt-assembly/section-usage.js +1 -1
  309. package/dist/prompt-assembly/select.d.ts +56 -0
  310. package/dist/prompt-assembly/select.js +69 -0
  311. package/dist/prompt-assembly/sugar.d.ts +31 -0
  312. package/dist/prompt-assembly/sugar.js +20 -0
  313. package/dist/prompt-assembly/turn-snapshot.d.ts +3 -2
  314. package/dist/prompt-assembly/types.d.ts +45 -148
  315. package/dist/prompts/coordinator.d.ts +2 -21
  316. package/dist/prompts/coordinator.js +3 -255
  317. package/dist/prompts/cowork.d.ts +27 -16
  318. package/dist/prompts/cowork.js +2 -23
  319. package/dist/prompts/default.d.ts +38 -500
  320. package/dist/prompts/default.js +20 -285
  321. package/dist/prompts/supervisor.d.ts +6 -71
  322. package/dist/prompts/supervisor.js +6 -117
  323. package/dist/tools/fs/bash-readonly-classifier.d.ts +6 -5
  324. package/dist/tools/fs/dir-entries.d.ts +4 -2
  325. package/dist/tools/fs/dir-entries.js +16 -4
  326. package/dist/tools/fs/fs-bash.d.ts +1 -1
  327. package/dist/tools/fs/fs-bash.js +22 -11
  328. package/dist/tools/fs/fs-pdf.js +17 -13
  329. package/dist/tools/fs/fs-read.js +4 -3
  330. package/dist/tools/fs/fs-shared.d.ts +1 -10
  331. package/dist/tools/fs/fs-shared.js +3 -4
  332. package/dist/tools/fs/fs-write.js +2 -1
  333. package/dist/tools/fs/notebook.js +1 -1
  334. package/dist/tools/fs/pdf.d.ts +20 -10
  335. package/dist/tools/fs/pdf.js +19 -10
  336. package/dist/tools/fs/program-read-model.d.ts +68 -8
  337. package/dist/tools/fs/program-read-model.js +112 -33
  338. package/dist/tools/fs/program-read-model.rows.js +30 -10
  339. package/dist/tools/fs/read-deny-home.d.ts +25 -9
  340. package/dist/tools/fs/read-deny-home.js +28 -27
  341. package/dist/tools/fs/read-deny.d.ts +41 -11
  342. package/dist/tools/fs/read-deny.js +51 -5
  343. package/dist/tools/fs/safety.d.ts +4 -26
  344. package/dist/tools/fs/safety.js +10 -32
  345. package/dist/tools/fs/search.js +15 -10
  346. package/dist/tools/fs/shell-read-argv.d.ts +3 -2
  347. package/dist/tools/fs/shell-read-argv.js +118 -38
  348. package/dist/tools/fs/shell-read-establish.d.ts +5 -5
  349. package/dist/tools/fs/shell-read-establish.js +11 -61
  350. package/dist/tools/fs/shell-read-identity.js +6 -3
  351. package/dist/tools/fs/shell-read-judge.d.ts +13 -15
  352. package/dist/tools/fs/shell-read-judge.js +18 -4
  353. package/dist/tools/fs/shell-read-plan.d.ts +2 -2
  354. package/dist/tools/web.js +1 -1
  355. package/package.json +13 -1
  356. package/test/export-surface.snapshot.json +242 -99
  357. package/wire-changes/core@7.52.0.json +5859 -0
  358. package/dist/core/hook-seats.d.ts +0 -66
  359. package/dist/core/hook-seats.js +0 -37
  360. package/dist/prompt-assembly/packs/sema-default.d.ts +0 -2
  361. package/dist/prompt-assembly/packs/sema-default.js +0 -147
  362. package/dist/prompts/simple-sections.d.ts +0 -58
  363. package/dist/prompts/simple-sections.js +0 -43
@@ -21,10 +21,12 @@
21
21
  * - **a row keeps its grammar**: the write judge stamps the grammar it read the entries in ({@link SESSION_RULES_GRAMMAR}), and
22
22
  * every station reads a row's entries in the grammar the row carries ({@link readSessionRulesShape}) — a row stored before
23
23
  * the stamp keeps the reading it was written under, so an upgrade never turns a stored entry into a grant.
24
+ * - **rules are read as rules or not at all**: rules whose shape cannot be read, or whose toolDeny holds an entry no grammar
25
+ * reads ({@link unreadableDenyEntries}), refuse every call until an operator writes them afresh — never read as fewer.
24
26
  * - **NOT A SANDBOX**: a shell command rule reads the command line the call carries (every program run in its connector
25
27
  * chain); the real isolation boundary is the executionEnv, never these rules.
26
28
  */
27
- import { compileAllowList, type AllowEntryInertDetail, type AllowEntryInertReason } from "./allow-entry.js";
29
+ import { compileAllowList, type AllowEntryInert, type AllowEntryInertDetail, type AllowEntryInertReason } from "./allow-entry.js";
28
30
  /** The per-session rules. All optional; an absent bucket imposes no constraint of that kind. */
29
31
  export interface SessionPermissionRules {
30
32
  /** If set, ONLY the calls these entries admit are allowed — a narrowing allowlist: a call no entry admits is refused.
@@ -51,11 +53,16 @@ export type SessionRuleEntryField = {
51
53
  /**
52
54
  * The rule grammar this engine reads a session's rule entries in: the `grammar` the write judge stamps on every rules value it
53
55
  * judges ({@link judgeSessionRulesWrite}), so every store that writes through it keeps the stamp on the row. It counts
54
- * revisions of what an entry MEANS and moves only when that changes. Grammar 0 is the reading before the one entry reader:
55
- * a bucket's entries were tool names compared as written, so an allow entry the reader now reads as speaking for another name
56
- * (`Bash(rm:*)`, `Bash(*)`) named no tool and admitted nothing.
56
+ * revisions of what an entry MEANS and moves only when that changes:
57
+ * - 0, the reading before the one entry reader: a bucket's entries were tool names compared as written, so an allow entry the
58
+ * reader now reads as speaking for another name (`Bash(rm:*)`, `Bash(*)`) named no tool and admitted nothing — and so did
59
+ * `*`, one more name no tool carried, which voided nothing beside it;
60
+ * - 1, the one entry reader taking its tool name from the lenient rule parser (trimmed before a `(`, any character but a
61
+ * blank, a parenthesis or a control / format character);
62
+ * - 2, the rule syntax's tool-name head (`ruleSpellingOf`): a name is read as written, in the characters names are minted in.
63
+ * An entry this grammar reads as no rule in a row an earlier grammar wrote reads as `earlier_grammar` ({@link compileSessionRuleBucket}).
57
64
  */
58
- export declare const SESSION_RULES_GRAMMAR = 1;
65
+ export declare const SESSION_RULES_GRAMMAR = 2;
59
66
  /** {@link SessionPermissionRules} plus the monotonic `rev` the store stamps (the OCC key). */
60
67
  export interface StoredSessionRules extends SessionPermissionRules {
61
68
  rev: number;
@@ -111,7 +118,8 @@ export declare class SessionPolicyError extends Error {
111
118
  export interface SessionPolicyStoreOptions {
112
119
  /** The names this deployment mounts its OWN tools under — the roster the store reads a rule entry's tool name against
113
120
  * (`readRuleEntry`): a retired name declared here is an ordinary name to the write and to the loosening check alike.
114
- * Absent = none. A name carrying `__` is refused, as it is for a deployment tool. */
121
+ * Absent = none. Each name keeps the caller tool name contract a deployment tool's name keeps at the mount
122
+ * (`callerToolNameProblem`: a name a rule can name, no `__`); one that does not is refused at construction. */
115
123
  mountedToolNames?: Iterable<string>;
116
124
  }
117
125
  /** A store's declared roster from {@link SessionPolicyStoreOptions.mountedToolNames} — the one reader both bundled stores
@@ -205,20 +213,46 @@ export declare function readSessionRulesShape(value: unknown): {
205
213
  * stored form is `..`-free and the tighten diff compares the form the runtime resolves. Both bundled stores and a durable twin
206
214
  * stamp exactly this, so a structuredClone and a JSON round-trip of it are byte-identical. */
207
215
  export declare function normalizeRules(rules: unknown): SessionPermissionRules;
216
+ /**
217
+ * THE ONE ANSWER to "can these rules' denies be read?": the `toolDeny` entries the list reading ({@link compileSessionRuleBucket},
218
+ * in the rules' own grammar) reads as no rule at all. Rules whose deny bucket holds one cannot be read, and rules that cannot be
219
+ * read are never read as fewer: they refuse every call (the run-time layer) and a non-operator write over them is a loosening
220
+ * ({@link loosenReasons}) — until an operator writes them afresh, and the write judge refuses such an entry. Unlike a definition
221
+ * file a plugin ships, which its user cannot edit, a session's rules are written by the session's own operator and can be
222
+ * rewritten, so a deny nobody can read costs one write instead of quietly guarding nothing. An entry the grammar reads that names
223
+ * no mounted tool (or `*`) is not here: it names nothing and refuses nothing, the rest of the bucket refusing as written.
224
+ * `roster` as in {@link compileSessionRuleBucket}; it never changes this answer (a spelling is read before a name is judged).
225
+ */
226
+ export declare function unreadableDenyEntries(rules: SessionRulesInGrammar, roster?: ReadonlySet<string>): AllowEntryInert[];
208
227
  /**
209
228
  * THE LIST READING of one rule bucket of a rules value the shape reading read — what the run-time layer enforces, the tighten
210
229
  * comparison compares and the write judge refuses on: `compileAllowList` over the one entry reader, in the bucket's direction,
211
- * against `roster` when given, in the grammar the value is written in. An entry its grammar reads as nothing that the reader
212
- * reads as a row ({@link readsAsNothingIn}) is moved to the entries that read as nothing, reason `earlier_grammar`.
230
+ * against `roster` when given, in the grammar the value is written in. An entry its grammar reads as nothing
231
+ * ({@link earlierGrammarReads}) is moved to the entries that read as nothing, reason `earlier_grammar`, before the list is
232
+ * compiled: it is one entry that reads as nothing, and the list beside it is read as written. The move is an ENTRY's reading and
233
+ * never the list's: an allow list a `*` voids (in any spelling the voiding test reads as `*`, `* (*)` included) is voided in
234
+ * every grammar from 1 on, so in such a row the list is read whole, as written — only grammar 0, where `*` was one more name, takes
235
+ * it out alone.
213
236
  */
214
237
  export declare function compileSessionRuleBucket(rules: SessionRulesInGrammar, field: SessionRuleEntryField, roster?: ReadonlySet<string>): ReturnType<typeof compileAllowList>;
215
- /** The advisory for allow entries a stored row's earlier grammar reads as nothing ({@link compileSessionRuleBucket}): the session,
216
- * the entries as written, what they do and the way to make them the rules they now spell. */
217
- export declare function earlierGrammarAdvisory(sessionId: string, entries: readonly string[]): string;
238
+ /**
239
+ * The advisory for the entries of one bucket a stored row's earlier grammar reads as nothing ({@link compileSessionRuleBucket},
240
+ * `earlier_grammar`; `grammar` = the row's): the session, the bucket, the entries as written, what they do and the way out. An
241
+ * entry this engine's grammar reads as no rule is named with the reader's word — in `toolAllow` it admits nothing, in `toolDeny`
242
+ * the rules cannot be read and every call is refused ({@link unreadableDenyEntries}); the way out is a rewrite leaving it out
243
+ * (the write judge refuses an entry it cannot read). A grammar-0 allow entry that named no tool there is told as it always was:
244
+ * to make a parenthesized one the rule it now spells, or (`*`, which is no allow rule in any grammar) to leave it out.
245
+ */
246
+ export declare function earlierGrammarAdvisory(sessionId: string, entries: readonly string[], field: SessionRuleEntryField, grammar: number): string;
247
+ /** The clause a run-time refusal carries when the allowlist it judged by holds entries its earlier grammar reads as nothing
248
+ * ({@link compileSessionRuleBucket}; `grammar` = the row's): the grammar the rules are stored in, then which entries name no tool
249
+ * there (grammar 0: `*` by its spelling, the parenthesized ones counted) and how many this engine's grammar reads as no rule —
250
+ * counted, since a stored entry's own text is data and never reaches a sentence the model reads. Empty when none. */
251
+ export declare function earlierGrammarRefusalClause(entries: readonly string[], grammar: number): string;
218
252
  /** A layer's delivery of the entries its snapshots' earlier grammar reads as nothing ({@link compileSessionRuleBucket},
219
- * `earlier_grammar`) to `sink`: once per distinct set per layer (the ledger is this closure's), an empty set is no news, and a
220
- * throwing sink never changes what the snapshot enforces. */
221
- export declare function earlierGrammarAnnouncer(sink: ((entries: readonly string[]) => void) | undefined): (entries: readonly string[]) => void;
253
+ * `earlier_grammar`), one bucket at a time with the grammar its row is written in, to `sink`: once per distinct set per layer
254
+ * (the ledger is this closure's), an empty set is no news, and a throwing sink never changes what the snapshot enforces. */
255
+ export declare function earlierGrammarAnnouncer(sink: ((entries: readonly string[], field: SessionRuleEntryField, grammar: number) => void) | undefined): (entries: readonly string[], field: SessionRuleEntryField, grammar: number) => void;
222
256
  /**
223
257
  * THE ONE WRITE JUDGE every store applies before it stamps a row (both bundled stores, and a durable backend at the same point):
224
258
  * the rules to stamp, or a {@link SessionPolicyError}, in this order — `conflict` (a stale `expectedRev` against `prior`'s rev),
@@ -234,9 +268,11 @@ export declare function judgeSessionRulesWrite(prior: StoredSessionRules | null,
234
268
  };
235
269
  /**
236
270
  * Pure tighten-only diff: the reasons `next` LOOSENS `prior` (empty ⇒ `next` is a pure tightening or no-op), over the EFFECTIVE
237
- * entries of each rule bucket — the list as the run-time layer enforces it ({@link effectiveEntries}). An entry that reads as
271
+ * entries of each rule bucket — the list as the run-time layer enforces it ({@link effectiveEntries}). Rules whose toolDeny holds
272
+ * an entry no grammar reads ({@link unreadableDenyEntries}) refuse every call: as `next` they loosen nothing, and as `prior`
273
+ * every readable `next` loosens them (one reason, naming the entries). Any other entry that reads as
238
274
  * nothing is in neither side's effect, so removing one relaxes nothing and adding one widens nothing (the way a session's owner
239
- * takes out an entry that names nothing); an allowlist a `*` voids admits nothing, so dropping the `*` widens. toolDeny: removing an effective entry loosens. toolAllow: removing the list
275
+ * takes out an entry that names nothing); an allowlist a `*` voids (in this engine's grammar; in grammar 0 a `*` is one such entry beside the rest) admits nothing, so dropping the `*` widens. toolDeny: removing an effective entry loosens. toolAllow: removing the list
240
276
  * (→ everything allowed) or adding an effective entry outside the prior set loosens; prior-undefined → any defined next only
241
277
  * narrows. allowDirs: removing confinement, or adding a dir not within some prior dir, loosens (coarse raw containment).
242
278
  * Entries compare as written, each side's in the grammar that side is written in (a stored row's entry that its grammar reads as
@@ -1,13 +1,14 @@
1
1
  import { posix } from "node:path";
2
2
  import { isWinFormPath } from "../tools/fs/safety.js";
3
- import { NAMESPACED_NAME_SHAPES, carriesNamespaceSeparator } from "./protocol-table.js";
4
- import { compileAllowList, compiledEntryName, inertEntryPhrase, readRuleEntry, renderListEntry } from "./allow-entry.js";
3
+ import { NAMESPACED_NAME_SHAPES } from "./protocol-table.js";
4
+ import { callerToolNameProblem } from "./caller-tool-name.js";
5
+ import { compileAllowList, compiledEntryName, inertEntryPhrase, readRuleEntry, renderListEntry, rootFacingToolNames } from "./allow-entry.js";
5
6
  import { valueTypeLabel } from "./tool-name-list.js";
6
7
  const SESSION_RULE_BUCKETS = { toolAllow: "allow", toolDeny: "deny", allowDirs: "dirs" };
7
8
  const BUCKETS = Object.keys(SESSION_RULE_BUCKETS);
8
9
  const ENTRY_FIELDS = BUCKETS.filter((f) => SESSION_RULE_BUCKETS[f] !== "dirs");
9
10
  const STORE_STAMPS = new Set(["rev", "gen", "grammar"]);
10
- export const SESSION_RULES_GRAMMAR = 1;
11
+ export const SESSION_RULES_GRAMMAR = 2;
11
12
  export class SessionPolicyError extends Error {
12
13
  code;
13
14
  detail;
@@ -29,10 +30,11 @@ export function readMountedToolNames(value) {
29
30
  refuse("must be an iterable of tool names (an array or a Set), not a single value");
30
31
  const names = new Set();
31
32
  for (const n of value) {
32
- if (typeof n !== "string" || n.length === 0)
33
- refuse(`holds ${JSON.stringify(n) ?? String(n)}, which is not a non-empty tool name`);
34
- if (carriesNamespaceSeparator(n))
35
- refuse(`holds ${JSON.stringify(n)}: "__" is reserved for protocol tool names and no deployment tool may carry it`);
33
+ if (typeof n !== "string")
34
+ refuse(`holds ${JSON.stringify(n) ?? String(n)}, which is not a tool name`);
35
+ const problem = callerToolNameProblem(n);
36
+ if (problem !== undefined)
37
+ refuse(`holds ${JSON.stringify(n)}: ${problem}`);
36
38
  names.add(n);
37
39
  }
38
40
  return names;
@@ -116,34 +118,103 @@ function canonicalRules(read) {
116
118
  }
117
119
  return out;
118
120
  }
119
- function readsAsNothingIn(grammar, field, entry, compiled) {
120
- return grammar === 0 && SESSION_RULE_BUCKETS[field] === "allow" && compiledEntryName(compiled) !== entry;
121
+ function earlierGrammarReads(grammar, field, entry, reading) {
122
+ if (grammar >= SESSION_RULES_GRAMMAR)
123
+ return false;
124
+ if (reading.kind === "inert" && reading.reason === "grammar")
125
+ return true;
126
+ if (grammar !== 0 || SESSION_RULE_BUCKETS[field] !== "allow")
127
+ return false;
128
+ return reading.kind === "row" ? compiledEntryName(reading.compiled) !== entry : reading.reason === "wildcard_all";
129
+ }
130
+ function readsAsNoRule(entry, field) {
131
+ const reading = readRuleEntry(entry, SESSION_RULE_BUCKETS[field]);
132
+ return reading.kind === "inert" && reading.reason === "grammar";
133
+ }
134
+ const NO_RULE_REASONS = new Set(["grammar", "earlier_grammar"]);
135
+ export function unreadableDenyEntries(rules, roster) {
136
+ return compileSessionRuleBucket(rules, "toolDeny", roster).inert.filter((i) => NO_RULE_REASONS.has(i.reason));
137
+ }
138
+ function readsAsWildcard(entry) {
139
+ return compileAllowList([entry], "allow").inert.some((i) => i.reason === "wildcard_all");
140
+ }
141
+ const STAR_IS_NO_ALLOW_RULE = "it is no allow rule in any grammar and a write holding it is refused";
142
+ function starWayOut(onlyStars, others, left) {
143
+ return onlyStars
144
+ ? `"*" is no allow rule in any grammar (a write holding it is refused); to drop it, write the session's rules afresh without it ${REWRITE_THROUGH}; ${left}`
145
+ : `${others}, leaving "*" out — ${STAR_IS_NO_ALLOW_RULE}; ${left}`;
121
146
  }
122
147
  export function compileSessionRuleBucket(rules, field, roster) {
123
- const list = compileAllowList(rules[field] ?? [], SESSION_RULE_BUCKETS[field], roster);
124
- const rows = list.rows.filter((r) => !readsAsNothingIn(rules.grammar, field, r.entry, r.compiled));
125
- if (rows.length === list.rows.length)
126
- return list;
127
- const earlier = list.rows.filter((r) => readsAsNothingIn(rules.grammar, field, r.entry, r.compiled)).map((r) => ({ entry: r.entry, reason: "earlier_grammar" }));
128
- return { rows, inert: [...list.inert, ...earlier] };
148
+ const direction = SESSION_RULE_BUCKETS[field];
149
+ const entries = rules[field] ?? [];
150
+ const earlier = new Set(entries.filter((e) => earlierGrammarReads(rules.grammar, field, e, readRuleEntry(e, direction, roster))));
151
+ const whole = compileAllowList(entries, direction, roster);
152
+ const voided = direction === "allow" && whole.inert.some((i) => i.reason === "wildcard_all");
153
+ if (earlier.size === 0 || (voided && rules.grammar > 0))
154
+ return whole;
155
+ const list = compileAllowList(entries.filter((e) => !earlier.has(e)), direction, roster);
156
+ return { rows: list.rows, inert: [...list.inert, ...entries.filter((e) => earlier.has(e)).map((entry) => ({ entry, reason: "earlier_grammar" }))] };
129
157
  }
130
158
  const ADVISORY_ENTRIES_SHOWN = 5;
131
- export function earlierGrammarAdvisory(sessionId, entries) {
132
- const one = entries.length === 1;
133
- const shown = entries.slice(0, ADVISORY_ENTRIES_SHOWN).map(renderListEntry).join(", ");
159
+ function shownEntries(entries, render) {
134
160
  const rest = entries.length > ADVISORY_ENTRIES_SHOWN ? ` and ${entries.length - ADVISORY_ENTRIES_SHOWN} more` : "";
135
- return (`session permission rules of session ${JSON.stringify(sessionId)}: toolAllow ${shown}${rest} ${one ? "was" : "were"} stored before the rule grammar stamp, when session rules compared an entry as a tool name as written, so a parenthesized entry named no tool — ` +
136
- `${one ? "it admits" : "they admit"} nothing, as when written. To make ${one ? "it an allow rule" : "them allow rules"}, write the session's rules afresh through this engine (an operator write of a value without the stored row's rev; the write stamps grammar ${SESSION_RULES_GRAMMAR}); left as stored, ${one ? "it keeps" : "they keep"} admitting nothing.`);
161
+ return `${entries.slice(0, ADVISORY_ENTRIES_SHOWN).map(render).join(", ")}${rest}`;
162
+ }
163
+ const REWRITE_THROUGH = `through this engine (an operator write of a new value without the stored row's rev; the write stamps grammar ${SESSION_RULES_GRAMMAR})`;
164
+ export function earlierGrammarAdvisory(sessionId, entries, field, grammar) {
165
+ const noRule = entries.filter((e) => readsAsNoRule(e, field));
166
+ const namedNoTool = entries.filter((e) => !readsAsNoRule(e, field));
167
+ const sentences = [...(namedNoTool.length > 0 ? [namedNoToolInGrammar0(namedNoTool)] : []), ...(noRule.length > 0 ? [noRuleInThisGrammar(noRule, field, grammar)] : [])];
168
+ return `session permission rules of session ${JSON.stringify(sessionId)}: ${sentences.join(" ")}`;
169
+ }
170
+ function namedNoToolInGrammar0(entries) {
171
+ const one = entries.length === 1;
172
+ const wildcard = entries.some(readsAsWildcard);
173
+ const rules = entries.some((e) => !readsAsWildcard(e));
174
+ const left = `left as stored, ${one ? "it keeps" : "they keep"} admitting nothing.`;
175
+ const way = !wildcard
176
+ ? `To make ${one ? "it an allow rule" : "them allow rules"}, write the session's rules afresh ${REWRITE_THROUGH}; ${left}`
177
+ : starWayOut(!rules, `To make the parenthesized ones allow rules, write the session's rules afresh ${REWRITE_THROUGH}`, left);
178
+ return (`toolAllow ${shownEntries(entries, renderListEntry)} ${one ? "was" : "were"} stored before the rule grammar stamp, when session rules compared an entry as a tool name as written, so ${wildcard ? 'an entry that is no bare tool name (a parenthesized rule, or "*")' : "a parenthesized entry"} named no tool — ` +
179
+ `${one ? "it admits" : "they admit"} nothing, as when written${wildcard ? "; the rest of the list is read as written, not voided" : ""}. ${way}`);
180
+ }
181
+ function noRuleInThisGrammar(entries, field, grammar) {
182
+ const one = entries.length === 1;
183
+ const stored = `${field} ${shownEntries(entries, (entry) => inertEntryPhrase({ entry, reason: "grammar" }))} ${one ? "was" : "were"} stored in rule grammar ${grammar}${grammar === 0 ? ", before the rule grammar stamp" : ""}, and this engine's rule grammar (${SESSION_RULES_GRAMMAR}) reads ${one ? "it" : "them"} as no rule`;
184
+ const way = `write the session's rules afresh ${REWRITE_THROUGH}, leaving ${one ? "it" : "them"} out or spelling the tool's name as a rule spells one — the write judge refuses an entry it cannot read`;
185
+ if (SESSION_RULE_BUCKETS[field] === "deny") {
186
+ return `${stored}: rules whose toolDeny holds an entry no grammar reads cannot be read, so every call is refused until they are rewritten. To lift that, ${way}, and a non-operator write cannot loosen rules that refuse every call.`;
187
+ }
188
+ const stars = entries.filter(readsAsWildcard).length;
189
+ const left = `left as stored, ${one ? "it keeps" : "they keep"} admitting nothing.`;
190
+ const wayOut = stars === 0
191
+ ? `To allow what ${one ? "it" : "they"} meant, ${way}; ${left}`
192
+ : starWayOut(stars === entries.length, `To allow what the others meant, write the session's rules afresh ${REWRITE_THROUGH}, spelling each tool's name as a rule spells one`, left);
193
+ return `${stored} — ${one ? "it admits" : "they admit"} nothing, and the rest of the list is read as written. ${wayOut}`;
194
+ }
195
+ export function earlierGrammarRefusalClause(entries, grammar) {
196
+ if (entries.length === 0)
197
+ return "";
198
+ const noRule = entries.filter((e) => readsAsNoRule(e, "toolAllow")).length;
199
+ const namedNoTool = entries.filter((e) => !readsAsNoRule(e, "toolAllow"));
200
+ const stars = namedNoTool.filter(readsAsWildcard).length;
201
+ const others = namedNoTool.length - stars;
202
+ const named = [...(stars > 0 ? ['"*"'] : []), ...(others > 0 ? [`${others} parenthesized ${others === 1 ? "entry" : "entries"}`] : [])];
203
+ const clauses = [
204
+ ...(namedNoTool.length > 0 ? [`where ${named.join(" and ")} ${namedNoTool.length === 1 ? "names no tool and admits" : "name no tool and admit"} nothing`] : []),
205
+ ...(noRule > 0 ? [`${namedNoTool.length > 0 ? "and " : "where "}${noRule} ${noRule === 1 ? "entry is" : "entries are"} no rule in this engine's rule grammar (${SESSION_RULES_GRAMMAR}) and ${noRule === 1 ? "admits" : "admit"} nothing`] : []),
206
+ ];
207
+ return `; the session's rules are stored in rule grammar ${grammar}, ${clauses.join(" ")}`;
137
208
  }
138
209
  export function earlierGrammarAnnouncer(sink) {
139
210
  const announced = new Set();
140
- return (entries) => {
141
- const key = JSON.stringify(entries);
211
+ return (entries, field, grammar) => {
212
+ const key = JSON.stringify([field, grammar, entries]);
142
213
  if (sink === undefined || entries.length === 0 || announced.has(key))
143
214
  return;
144
215
  announced.add(key);
145
216
  try {
146
- sink(entries);
217
+ sink(entries, field, grammar);
147
218
  }
148
219
  catch {
149
220
  }
@@ -153,23 +224,36 @@ function effectiveEntries(rules, field, mountedToolNames) {
153
224
  return compileSessionRuleBucket(rules, field, mountedToolNames).rows.map((r) => r.entry);
154
225
  }
155
226
  function assertEntriesRead(rules, mountedToolNames) {
227
+ const known = rootFacingToolNames(mountedToolNames);
156
228
  for (const field of ENTRY_FIELDS) {
157
229
  for (const entry of rules[field] ?? []) {
158
- const reading = readRuleEntry(entry, SESSION_RULE_BUCKETS[field], mountedToolNames);
159
- const read = reading.kind === "row" && readsAsNothingIn(rules.grammar, field, entry, reading.compiled) ? { kind: "inert", reason: "earlier_grammar" } : reading;
230
+ const reading = readRuleEntry(entry, SESSION_RULE_BUCKETS[field], known);
231
+ const read = earlierGrammarReads(rules.grammar, field, entry, reading) ? { kind: "inert", reason: "earlier_grammar" } : reading;
160
232
  if (read.kind === "row")
161
233
  continue;
162
234
  const { kind: _kind, ...why } = read;
163
235
  void _kind;
164
236
  const way = why.reason === "earlier_grammar"
165
- ? ` These rules are written in grammar ${rules.grammar} (a stored row from before the grammar stamp), in which this entry names no tool. To make it an allow rule, write the rules afresh — a value without the stored row's rev; the write stamps grammar ${SESSION_RULES_GRAMMAR} — or leave the entry out.`
166
- : why.verdict === "retired"
237
+ ? readsAsNoRule(entry, field)
238
+ ? ` These rules are written in grammar ${rules.grammar} (a stored row), and this engine's grammar (${SESSION_RULES_GRAMMAR}) reads this entry as no rule (grammar) — ` +
239
+ (field === "toolAllow" && readsAsWildcard(entry) ? `leave the entry out: it spells "*", and ${STAR_IS_NO_ALLOW_RULE}.` : "leave the entry out, or spell the tool's name as a rule spells one.")
240
+ : ` These rules are written in grammar ${rules.grammar} (a stored row from before the grammar stamp), in which this entry names no tool.` +
241
+ (readsAsWildcard(entry)
242
+ ? ` It is no allow rule in grammar ${SESSION_RULES_GRAMMAR} either (a write holding it is refused) — leave the entry out.`
243
+ : ` To make it an allow rule, write the rules afresh — a value without the stored row's rev; the write stamps grammar ${SESSION_RULES_GRAMMAR} — or leave the entry out.`)
244
+ : why.verdict === "retired" || why.reason === "near_miss"
167
245
  ? ` If this deployment mounts its own tool under ${renderListEntry(entry)}, declare it in the store's mountedToolNames.`
168
246
  : why.currentName === undefined
169
247
  ? ` An entry is a tool name (Read; a protocol tool is named ${NAMESPACED_NAME_SHAPES}), a protocol namespace (mcp__<server>), or a rule in its tool's own grammar (Bash(<command>), Edit(<path>), Skill(<name>), WebFetch(domain:<host>)).`
170
248
  : "";
171
249
  const sentence = why.guidance !== undefined ? ` — ${why.guidance}` : "";
172
- throw new SessionPolicyError("inert_entry", `session rules ${field} holds ${inertEntryPhrase({ entry, ...why })}, which ${SESSION_RULE_BUCKETS[field] === "deny" ? "refuses" : "admits"} nothing as written${sentence}; nothing was written.${way}`, { field, entry, ...why });
250
+ const does = SESSION_RULE_BUCKETS[field] === "allow"
251
+ ? "admits nothing as written"
252
+ : NO_RULE_REASONS.has(why.reason)
253
+ ? "refuses every call as stored (rules whose toolDeny holds an entry no grammar reads cannot be read) — a write holding it is refused"
254
+ : "refuses nothing as written";
255
+ const carriedBy = why.reason === "near_miss" && why.currentName !== undefined && !mountedToolNames.has(why.currentName) ? { carriedBy: "catalog" } : {};
256
+ throw new SessionPolicyError("inert_entry", `session rules ${field} holds ${inertEntryPhrase({ entry, ...why, ...carriedBy })}, which ${does}${sentence}; nothing was written.${way}`, { field, entry, ...why });
173
257
  }
174
258
  }
175
259
  }
@@ -201,6 +285,12 @@ export function loosenReasons(priorValue, nextValue, mountedToolNames = NO_DECLA
201
285
  };
202
286
  const prior = shapeOf("prior", priorValue);
203
287
  const next = shapeOf("next", nextValue);
288
+ if (unreadableDenyEntries(next, mountedToolNames).length > 0)
289
+ return [];
290
+ const unreadPrior = unreadableDenyEntries(prior, mountedToolNames);
291
+ if (unreadPrior.length > 0) {
292
+ return [`the prior rules refuse every call — their toolDeny holds ${shownEntries(unreadPrior.map((i) => i.entry), (entry) => inertEntryPhrase({ entry, reason: "grammar" }))}, which no grammar reads`];
293
+ }
204
294
  const reasons = [];
205
295
  const keptDeny = new Set(effectiveEntries(next, "toolDeny", mountedToolNames));
206
296
  for (const x of effectiveEntries(prior, "toolDeny", mountedToolNames))
@@ -51,6 +51,9 @@ export interface OrphanToolCall {
51
51
  * message and `errorCode` on the synthetic `tool_end` frame, so a consumer never parses the text. Two-valued
52
52
  * (a third value needs a cross-repo slip, see {@link reconcileInterruptedSession}). */
53
53
  export type ReconciledErrorKind = "interrupted_never_started" | "interrupted_outcome_unknown";
54
+ /** The class that is positive evidence the call never ran: closed with no start observed. The synthetic `tool_end` frame's
55
+ * `notExecuted` (`reconciledEnd`) and the persisted close's `details.notExecuted` both read it — one table, two faces. */
56
+ export declare const closeNeverExecuted: (errorKind: ReconciledErrorKind) => boolean;
54
57
  /** One orphan closed by {@link reconcileInterruptedSession}, with everything the caller needs to mint the
55
58
  * matching stream frame WITHOUT re-reading the session. */
56
59
  export type RecoveredOrphan = OrphanToolCall & {
@@ -153,6 +156,7 @@ export declare function isInterruptedClose(message: {
153
156
  /** The `toolResult` message for one {@link InterruptedResult} — the persisted and the request-view closes are this one shape. */
154
157
  export declare function interruptedResultMessage(result: InterruptedResult, timestamp: number): ToolResultMessage<{
155
158
  errorKind: ReconciledErrorKind;
159
+ notExecuted?: true;
156
160
  }>;
157
161
  /**
158
162
  * Reconcile a resumed session's active branch: close any orphan tool calls with a synthetic
@@ -1,4 +1,5 @@
1
1
  import { readMember } from "./thrown-value.js";
2
+ export const closeNeverExecuted = (errorKind) => errorKind === "interrupted_never_started";
2
3
  const INTERRUPTED_UNKNOWN = "[INTERRUPTED] The previous run ended before this tool call's result was recorded. Its outcome " +
3
4
  "is UNKNOWN — the action may have completed, partially completed, or never run. Do not assume " +
4
5
  "success or failure. Before relying on it: if it is safe to read, query the current state to " +
@@ -119,7 +120,7 @@ export function interruptedResultMessage(result, timestamp) {
119
120
  toolCallId: result.toolCallId,
120
121
  toolName: result.toolName,
121
122
  content: [{ type: "text", text: result.text }],
122
- details: { errorKind: result.errorKind },
123
+ details: { errorKind: result.errorKind, ...(closeNeverExecuted(result.errorKind) ? { notExecuted: true } : {}) },
123
124
  isError: true,
124
125
  timestamp,
125
126
  };
@@ -2,6 +2,7 @@ import { types } from "node:util";
2
2
  import { dispositionsOfRow, SHELL_READ_FINDINGS } from "../tools/fs/shell-read-judge.js";
3
3
  import { receiveContract } from "./decision-receipt.js";
4
4
  import { egressThrownText } from "./untrusted-egress.js";
5
+ import { receive } from "./mods/kernel/receive.js";
5
6
  const DISPOSITIONS = ["refuse", "mandated", "ordinary", "vouch"];
6
7
  const MEMBER_KINDS = {
7
8
  disposition: "word",
@@ -20,37 +21,7 @@ const MEMBER_KINDS = {
20
21
  admit: "admission",
21
22
  };
22
23
  const VERDICT_MEMBERS = Object.keys(MEMBER_KINDS);
23
- const DATA_DEPTH = 8;
24
- function ownedData(value, depth, path) {
25
- if (value === null || value === undefined || typeof value === "string" || typeof value === "number" || typeof value === "boolean")
26
- return { ok: true, value };
27
- if (typeof value !== "object" || depth >= DATA_DEPTH || types.isProxy(value) || path.has(value))
28
- return { ok: false };
29
- const list = Array.isArray(value);
30
- const proto = Object.getPrototypeOf(value);
31
- if (list ? proto !== Array.prototype : proto !== Object.prototype && proto !== null)
32
- return { ok: false };
33
- const own = Object.getOwnPropertyDescriptors(value);
34
- path.add(value);
35
- try {
36
- const out = list ? [] : {};
37
- for (const key of Object.keys(own)) {
38
- if (list && key === "length")
39
- continue;
40
- const d = own[key];
41
- if (!Object.hasOwn(d, "value") || d.enumerable !== true)
42
- return { ok: false };
43
- const inner = ownedData(d.value, depth + 1, path);
44
- if (!inner.ok)
45
- return { ok: false };
46
- Object.defineProperty(out, key, { value: inner.value, enumerable: true, writable: true, configurable: true });
47
- }
48
- return { ok: true, value: out };
49
- }
50
- finally {
51
- path.delete(value);
52
- }
53
- }
24
+ const OWNED_DATA_MODE = Object.freeze({ accessors: "refuse", depth: 8, others: "refuse", isProxy: (v) => types.isProxy(v) });
54
25
  function receivedAnswer(answer, call) {
55
26
  if (typeof answer !== "object" || answer === null)
56
27
  return undefined;
@@ -65,7 +36,7 @@ function receivedAnswer(answer, call) {
65
36
  if (kind === "word" ? typeof v !== "string" : kind === "mark" ? v !== true : kind === "face" ? v !== "write" : kind === "admission" ? typeof v !== "function" || types.isProxy(v) : false)
66
37
  return undefined;
67
38
  if (kind === "record") {
68
- const data = ownedData(v, 0, new Set());
39
+ const data = receive(v, OWNED_DATA_MODE);
69
40
  if (!data.ok || typeof data.value !== "object" || data.value === null || Array.isArray(data.value))
70
41
  return undefined;
71
42
  v = data.value;
@@ -63,9 +63,11 @@ export interface SideQuerySpec {
63
63
  * Per-model auth — MIRRORS {@link TaskSpec.getApiKeyAndHeaders} (same signature, resolved per
64
64
  * call against the RESOLVED model, exactly like the task path's per-call hook). The brain
65
65
  * contract is the route pairing law (route-adjudicator.ts): a per-model credential (this hook,
66
- * or an auth header on `Model.headers`) always rides; the deployment credential rides only to the
67
- * deployment's own root — a model carrying a root of its own is refused
68
- * (`route.credential_mismatch` / `route.credential_missing`), never silently followed.
66
+ * or a credential header on `Model.headers`) rides to the origin it first went out to and is
67
+ * refused anywhere else (`route.credential_origin_mismatch`); an answer handing the key beside
68
+ * the lane's own key header is refused as one verdict (`route.credential_answer_conflict`); the
69
+ * deployment credential rides only to the deployment's own root — a model carrying a root of its
70
+ * own is refused (`route.credential_mismatch` / `route.credential_missing`), never silently followed.
69
71
  * Historically the fallback was unconditional (`options.apiKey ?? config.apiKey`, with a model's
70
72
  * own `baseUrl` outranking the brain's), so a side query routed to a model carrying its own
71
73
  * `baseUrl` + per-model key sent the GATEWAY key to the per-model (possibly external) URL — the
@@ -69,8 +69,9 @@ export declare function skillGrantBindingMembers(call: {
69
69
  * bytes — the row stays pending: deny it, then activate again) and the resumed execution boundary (`site: "execution"`, the
70
70
  * bytes that will run, after every station that can rewrite them — the approval is spent: the call does not run). The
71
71
  * sentence says which side of the digest differs, read off the row's own arguments: the arguments name another skill than
72
- * the card's (`row.args`), or the card's skill no longer declares the lists the card showed (or is not served). Names the
73
- * skills, never a path; says the way out.
72
+ * the card's (`row.args`), or the card's skill's lists no longer bind to the card's digest (or it is not served) — its
73
+ * definition changed, or this engine reads an entry of it differently than the engine that parked the row (an entry that
74
+ * reads as nothing: the digest is the list reader's reading, not the text). Names the skills, never a path; says the way out.
74
75
  */
75
76
  export declare function parkedSkillApprovalRefusal(row: {
76
77
  readonly toolName: string;
@@ -1,25 +1,33 @@
1
1
  import { wireOf } from "./tool-name-readings.js";
2
- import { parsePermissionRule } from "./permission-rules.js";
2
+ import { ruleSpellingOf } from "./permission-rule-syntax.js";
3
3
  import { mountedToolPermissionSeat } from "./mode-policy.js";
4
4
  import { inlineUntrusted } from "./untrusted-text.js";
5
5
  import { boundInputHashOf } from "./canonical-json.js";
6
6
  import { NOT_BINDABLE } from "./skill-grant-binding.js";
7
7
  import { readToolNameList } from "./tool-name-list.js";
8
- import { allowingRows, autoStripped, compileAllowList, readRuleEntry, inertEntryPhrase, judgeAllowEntries, nonListValueLabel, nonStringMemberLabel, renderListEntry } from "./allow-entry.js";
8
+ import { allowingRows, autoStripped, compileAllowList, readRuleEntry, inertEntryPhrase, judgeAllowEntries, nonListValueLabel, renderListEntry, spelledListEntry } from "./allow-entry.js";
9
9
  export const SKILL_GRANT_ORIGINS = Object.freeze(["model", "user", "inherited"]);
10
10
  const FROZEN_SKILL_GRANT_ORIGINS = new Set(["model", "inherited"]);
11
11
  export function skillNamedBy(args, skills) {
12
12
  const named = args?.skill;
13
13
  return typeof named === "string" ? skills.find((s) => s.name === named) : undefined;
14
14
  }
15
+ function readsAsNothingLabel(inert) {
16
+ return `<reads as nothing (${inert.reason}): ${spelledListEntry(inert.entry)}>`;
17
+ }
15
18
  function skillGrantBinding(skill) {
16
- const canonical = (entries) => [...new Set((entries ?? []).map((e) => {
17
- if (typeof e !== "string")
18
- return nonStringMemberLabel(e);
19
- const p = parsePermissionRule(e);
19
+ const canonical = (entries, behavior) => {
20
+ const read = compileAllowList((entries ?? []), behavior);
21
+ const spelled = read.rows.map((r) => {
22
+ const p = ruleSpellingOf(r.entry);
23
+ if (p === undefined)
24
+ return readsAsNothingLabel({ entry: r.entry, reason: "grammar" });
20
25
  return p.ruleContent === undefined ? p.toolName : `${p.toolName}(${p.ruleContent})`;
21
- }))].sort();
22
- const boundGrants = { skill: skill?.name ?? null, allowedTools: canonical(skill?.allowedTools), disallowedTools: canonical(skill?.disallowedTools) };
26
+ });
27
+ const unread = read.inert.map((i) => (i.valueType !== undefined ? i.entry : readsAsNothingLabel(i)));
28
+ return [...new Set([...spelled, ...unread])].sort();
29
+ };
30
+ const boundGrants = { skill: skill?.name ?? null, allowedTools: canonical(skill?.allowedTools, "allow"), disallowedTools: canonical(skill?.disallowedTools, "deny") };
23
31
  return { boundGrantDigest: boundInputHashOf(boundGrants), boundGrants };
24
32
  }
25
33
  export function skillGrantDigest(skill) {
@@ -43,7 +51,7 @@ export function parkedSkillApprovalRefusal(row, args, skills, skillToolName, sit
43
51
  ? `this parked Skill activation of "${chosen}" carries no digest of the allowed-tools / disallowed-tools it was asked under (it was parked before Skill approvals bound them), so it cannot cover what the activation grants or refuses`
44
52
  : nameIn(args) !== nameIn(row.args)
45
53
  ? `this Skill approval was given on the card for "${shown(nameIn(row.args))}", and ${site === "claim" ? "this decision selects" : "the call would now activate"} "${chosen}" — not the Skill that card asked about, so the approval does not cover activating it`
46
- : `"${chosen}" no longer declares the allowed-tools / disallowed-tools this Skill approval was asked under — as this resume serves it (one it does not load counts as absent), it changed after the approval was given, so the approval does not cover what activating it would now grant or refuse`;
54
+ : `the allowed-tools / disallowed-tools of "${chosen}", as this resume serves it (one it does not load counts as absent), no longer bind to the digest this Skill approval was asked under — its definition changed, or this engine reads one of its entries differently than the engine that approved it (an entry that reads as nothing) — so the approval does not cover what activating it would now grant or refuse`;
47
55
  return site === "claim"
48
56
  ? `${why} — refusing pre-CAS (nothing ran; the checkpoint stays pending). Decide this checkpoint as deny and re-activate it: the new card shows the current lists.`
49
57
  : `The approved Skill call was not executed: ${why}. This approval is spent — to use "${chosen}", have the model activate it again: the new card shows its lists.`;
@@ -18,25 +18,45 @@
18
18
  * directory third parties can write to is the same as pasting their text into the system prompt. The
19
19
  * loader validates SHAPE (spec conformance), never intent.
20
20
  *
21
- * **`allowed-tools` / `disallowed-tools` are COPIED, never judged.** Each frontmatter list reaches
21
+ * **`allowed-tools` / `disallowed-tools` are COPIED, never judged for what they mean.** Each frontmatter list reaches
22
22
  * {@link SkillSpec.allowedTools} / {@link SkillSpec.disallowedTools} entry by entry as the author wrote it — no
23
- * intersection with a roster, no projection onto a tool name, no refusal, no de-duplication. What an entry means
24
- * (a tool, a namespace, a command rule, a path, a wildcard that grants nothing) is read in ONE place, the run's
25
- * grant table, which announces what it cannot read; a loader that also judged would be a second reader of the grammar.
23
+ * intersection with a roster, no projection onto a tool name, no de-duplication. Each entry must be a rule SPELLING — the
24
+ * rule syntax's own test, `ruleSpellingOf` (a tool name, then at most one parenthesized rule) — or the skill is refused;
25
+ * what a spelled entry means (a tool, a namespace, a command rule, a path, a wildcard that grants nothing) is read in ONE
26
+ * place, the run's grant table, which announces what it cannot read. The deny list has a second spelling,
27
+ * `disallowedTools`, read where `disallowed-tools` is absent or null (YAML's null is the key's absence); a hyphen key with
28
+ * a value is the list and the alias is not looked at. The allow list has none. **Each list is read whole by ONE scanner,
29
+ * {@link scanValue} (its JSDoc states the rules), or the skill is refused by name (`no_frontmatter`)** — never installed
30
+ * shorter: a `disallowed-tools` that lost entries is a looser skill than the one its author wrote. The scanner reads YAML's
31
+ * structure as YAML does — a comment, a flow's `,` / `]` and a mapping's `: ` are YAML's inside a rule's parentheses too —
32
+ * and departs from what YAML and the reference's reader give only where the list is cut into entries, on purpose: the cut
33
+ * counts a rule's parentheses by depth and refuses an entry whose parentheses are not one group closing it — unmatched
34
+ * either way, or followed by more text (the reference's zone is boolean and installs such an entry); a `*` entry is
35
+ * copied, the list not collapsed to `*` alone; tab-indented block items are read (the reference's retry reads them too);
36
+ * and a block scalar's text is cut line by line — each line of a literal one (`|`) as written, a folded one (`>`) after
37
+ * YAML's folding, so `>` lines that hold one rule read as that rule (YAML keeps the line breaks of `|` in one string, which
38
+ * the reference installs as one entry no rule reads).
26
39
  *
27
- * Frontmatter is parsed by a deliberately closed subset reader (no YAML dependency — the runtime
28
- * dependency budget is a standing constraint): top-level `key: value` scalars, optional surrounding
29
- * quotes, `#` comment lines, block scalars (`|` / `>` with their indentation + chomping indicators),
30
- * flow sequences (`[a, b]`, including the multi-line form), and indented mapping bodies skipped
31
- * wholesale. That covers every field the spec defines, and the same posture the memory-engine
32
- * frontmatter reader already takes.
40
+ * Frontmatter is read by a deliberately closed subset reader (no YAML dependency — the runtime dependency budget is a
41
+ * standing constraint): top-level key lines ({@link yamlKeyLineOf}), `#` comment lines, block scalars (`|` / `>` with their
42
+ * indentation and chomping indicators), the tool lists, and nested blocks (a mapping or a sequence under a key with nothing
43
+ * on its line) skipped wholesale but for a line that opens a tool-list key (below). A key other than the tool lists keeps its
44
+ * value as written on its line (a value that is one quoted scalar, the scanner's own judgment, is that scalar decoded, over
45
+ * every line it runs); a value of it that opens a quote, a `[` or a `{` and does not
46
+ * close it refuses the skill too — the same scanner, over the same lines, says so. Every value takes its own lines and no
47
+ * more, and every line no value takes is judged in one place: an indented `key:` line under a value that is not a nested
48
+ * block — plain, quoted, a flow, a block scalar — refuses the skill naming both keys (YAML reads no key there), and a
49
+ * flush-left line that is no key line refuses it naming the line (YAML reads no key there either). A tool list is read only
50
+ * as a key of the frontmatter, written flush left: a tool-list key written in another key's nested block (at any depth), or
51
+ * after another key's closing quote or `]` on its line, or as a key of a flow mapping (or a pair in a flow sequence) that
52
+ * value holds, refuses the skill naming both keys — its list would be lost.
33
53
  */
34
54
  import type { SkillSpec } from "./types.js";
35
55
  /** Why one skill directory (or one of its parts) was not taken as-declared. */
36
56
  export type SkillsDirectoryWarningCode =
37
57
  /** The directory holds no `SKILL.md`. */
38
58
  "no_skill_file"
39
- /** `SKILL.md` has no well-formed `---` fenced frontmatter block. */
59
+ /** `SKILL.md` has no well-formed `---` fenced frontmatter block: no opening fence, an opening fence never closed by a whole-line `---`, a tool list (`allowed-tools` / `disallowed-tools`) whose value the reader cannot read as a list, a key whose value opens a quote, a `[` or a `{` it does not close, a value that is not a nested block followed by an indented `key:` line, a tool-list key written anywhere but flush left (in a nested block, or after a closing quote or `]` on a key's line), or a flush-left line that is no key line. */
40
60
  | "no_frontmatter"
41
61
  /** Required `name` absent. */
42
62
  | "missing_name"