@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
@@ -1,6 +1,8 @@
1
- import { AUTH_CARRIER_NAMES } from "./request-params.js";
1
+ import { createHash } from "node:crypto";
2
2
  import { BrainError } from "./errors.js";
3
+ import { reportCredentialOriginRefusal } from "./status-sink.js";
3
4
  import { receiptRefusal, receiveOwnProperties } from "../core/own-property-receipt.js";
5
+ import { credentialHeaderValues, holdOwnCredentialValues, isCredentialHeaderName, isSchemedCredentialHeader } from "../core/secret-env.js";
4
6
  const REDACTED_VALUE = "«redacted»";
5
7
  const REDACTED_CREDENTIALS = "[redacted-credentials]";
6
8
  function servableParse(raw) {
@@ -191,23 +193,96 @@ export function resolveRequestRootOf(modelBaseUrl, configBaseUrl, laneDefault =
191
193
  export function hasAuthCarrier(headers) {
192
194
  if (!headers)
193
195
  return false;
194
- for (const k of Object.keys(headers)) {
195
- if (AUTH_CARRIER_NAMES.has(k.toLowerCase()))
196
- return true;
196
+ return Object.keys(headers).some((name) => isCredentialHeaderName(name));
197
+ }
198
+ function routeOriginOf(rootKey) {
199
+ if (rootKey === "")
200
+ return "";
201
+ const u = servableParse(rootKey);
202
+ return u === undefined ? rootKey : `${u.protocol}//${u.host}`;
203
+ }
204
+ const CREDENTIAL_ORIGIN_BINDINGS_MAX = 16_384;
205
+ const CREDENTIAL_ORIGIN_TABLE = Symbol.for("sema.core.credentialOriginTable");
206
+ function credentialOriginTable() {
207
+ const held = Reflect.get(globalThis, CREDENTIAL_ORIGIN_TABLE);
208
+ if (held instanceof Map)
209
+ return held;
210
+ const table = new Map();
211
+ Reflect.set(globalThis, CREDENTIAL_ORIGIN_TABLE, table);
212
+ return table;
213
+ }
214
+ export function __resetCredentialOriginBindings() {
215
+ credentialOriginTable().clear();
216
+ }
217
+ const resolvedCredentialFacts = new WeakMap();
218
+ const HEADER_VALUE_ENDS = /^[\t\n\r ]+|[\t\n\r ]+$/g;
219
+ const AUTH_SCHEME_CREDENTIALS = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+[\t ]+(\S[\s\S]*)$/;
220
+ function perModelCredential(credential, apiKey, bag, conflict) {
221
+ const headers = {};
222
+ for (const [name, value] of Object.entries(bag ?? {})) {
223
+ if (isCredentialHeaderName(name) && typeof value === "string")
224
+ headers[name] = value;
197
225
  }
198
- return false;
226
+ const headerValues = Object.entries(headers).map(([name, value]) => {
227
+ const wire = value.replace(HEADER_VALUE_ENDS, "");
228
+ return isSchemedCredentialHeader(name) ? (AUTH_SCHEME_CREDENTIALS.exec(wire)?.[1] ?? wire) : wire;
229
+ });
230
+ const values = apiKey !== undefined ? [apiKey, ...headerValues] : headerValues;
231
+ const secrets = [...new Set(values.map((v) => v.replace(HEADER_VALUE_ENDS, "")).filter((v) => v !== ""))];
232
+ if (secrets.length === 0 && Object.keys(headers).length === 0 && conflict === undefined)
233
+ return credential;
234
+ const proof = Object.freeze(credential);
235
+ resolvedCredentialFacts.set(proof, Object.freeze({
236
+ identities: Object.freeze(secrets.map((s) => createHash("sha256").update(s).digest("base64url"))),
237
+ headers: Object.freeze(headers),
238
+ ...(conflict !== undefined ? { keyHeaderConflict: Object.freeze(conflict) } : {}),
239
+ }));
240
+ return proof;
241
+ }
242
+ function boundElsewhere(identities, requestOrigin) {
243
+ const table = credentialOriginTable();
244
+ return identities.map((id) => table.get(id)).find((bound) => bound !== undefined && bound !== requestOrigin);
245
+ }
246
+ function bindCredentialOrigin(identities, requestOrigin) {
247
+ const table = credentialOriginTable();
248
+ for (const id of identities) {
249
+ table.delete(id);
250
+ if (table.size >= CREDENTIAL_ORIGIN_BINDINGS_MAX) {
251
+ const oldest = table.keys().next();
252
+ if (oldest.done !== true)
253
+ table.delete(oldest.value);
254
+ }
255
+ table.set(id, requestOrigin);
256
+ }
257
+ }
258
+ function routeHalves(model, config) {
259
+ const entry = routeKeyOf(model.baseUrl);
260
+ const declared = routeKeyOf(config.baseUrl);
261
+ const deploymentRoot = declared || routeKeyOf(config.laneDefault);
262
+ return { entry, declared, deploymentRoot, request: entry || deploymentRoot };
263
+ }
264
+ function keyHeaderConflict(apiKey, headers, keyHeader) {
265
+ if (apiKey === undefined || apiKey === "" || keyHeader === undefined || headers === undefined)
266
+ return undefined;
267
+ const fold = keyHeader.toLowerCase();
268
+ const written = Object.keys(headers).find((name) => name.toLowerCase() === fold);
269
+ return written === undefined ? undefined : { header: written, keyHeader };
199
270
  }
200
271
  export function resolveRouteCredential(input) {
201
- const { optionsApiKey, optionsHeaders, modelHeaders, configApiKey, configHeaders } = input;
202
- if (optionsApiKey !== undefined) {
203
- if (typeof optionsApiKey !== "string")
272
+ const { optionsApiKey, optionsHeaders, modelHeaders, configApiKey, configHeaders, keyHeader } = input;
273
+ holdOwnCredentialValues(credentialHeaderValues(modelHeaders));
274
+ const answerHeadersBorne = hasAuthCarrier(optionsHeaders);
275
+ if (optionsApiKey !== undefined || answerHeadersBorne) {
276
+ if (optionsApiKey !== undefined && typeof optionsApiKey !== "string")
204
277
  throw new TypeError(`route pairing: apiKey must be a string (got ${typeof optionsApiKey})`);
205
- return { source: "per-model", apiKey: optionsApiKey };
278
+ return perModelCredential({
279
+ source: "per-model",
280
+ ...(optionsApiKey !== undefined ? { apiKey: optionsApiKey } : {}),
281
+ ...(answerHeadersBorne ? { headersBorne: true, carrierBag: "options" } : {}),
282
+ }, optionsApiKey, answerHeadersBorne ? optionsHeaders : undefined, keyHeaderConflict(optionsApiKey, optionsHeaders, keyHeader));
206
283
  }
207
- if (hasAuthCarrier(optionsHeaders))
208
- return { source: "per-model", headersBorne: true, carrierBag: "options" };
209
284
  if (hasAuthCarrier(modelHeaders))
210
- return { source: "per-model", headersBorne: true, carrierBag: "model" };
285
+ return perModelCredential({ source: "per-model", headersBorne: true, carrierBag: "model" }, undefined, modelHeaders);
211
286
  const configHeadersBorne = hasAuthCarrier(configHeaders);
212
287
  if (configApiKey !== undefined || configHeadersBorne) {
213
288
  if (configApiKey !== undefined && typeof configApiKey !== "string") {
@@ -222,15 +297,53 @@ export function resolveRouteCredential(input) {
222
297
  return { source: "absent" };
223
298
  }
224
299
  export function adjudicateModelRoute(model, credential, config) {
225
- const entry = routeKeyOf(model.baseUrl);
226
- const declared = routeKeyOf(config.baseUrl);
227
- const deploymentRoot = declared || routeKeyOf(config.laneDefault);
228
- const request = entry || deploymentRoot;
300
+ const { entry, declared, deploymentRoot, request } = routeHalves(model, config);
229
301
  const entryShown = displayUrlOf(entry);
230
302
  const declaredShown = displayUrlOf(declared);
231
303
  const requestShown = displayUrlOf(request);
232
- if (credential.source === "per-model")
233
- return { ok: true, posture: "per-model" };
304
+ if (credential.source === "per-model") {
305
+ const facts = resolvedCredentialFacts.get(credential);
306
+ const conflict = facts?.keyHeaderConflict;
307
+ if (conflict !== undefined) {
308
+ return {
309
+ ok: false,
310
+ code: "route.credential_answer_conflict",
311
+ detail: {
312
+ modelId: model.id,
313
+ entryBaseUrl: entryShown,
314
+ requestBaseUrl: requestShown,
315
+ configBaseUrl: declaredShown,
316
+ fixHint: `model "${model.id}": the request carries both an apiKey and a "${conflict.header}" header, and this lane writes the apiKey in ` +
317
+ `"${conflict.keyHeader}" — one header cannot hold two credentials; hand the key as apiKey or as the header, not both`,
318
+ },
319
+ };
320
+ }
321
+ const identities = facts?.identities;
322
+ const requestOrigin = routeOriginOf(request);
323
+ if (identities === undefined || requestOrigin === "")
324
+ return { ok: true, posture: "per-model" };
325
+ const boundOrigin = boundElsewhere(identities, requestOrigin);
326
+ if (boundOrigin === undefined)
327
+ return { ok: true, posture: "per-model" };
328
+ const boundShown = displayUrlOf(boundOrigin);
329
+ const requestOriginShown = displayUrlOf(requestOrigin);
330
+ return {
331
+ ok: false,
332
+ code: "route.credential_origin_mismatch",
333
+ detail: {
334
+ modelId: model.id,
335
+ entryBaseUrl: entryShown,
336
+ requestBaseUrl: requestShown,
337
+ configBaseUrl: declaredShown,
338
+ boundOrigin: boundShown,
339
+ requestOrigin: requestOriginShown,
340
+ fixHint: `model "${model.id}" targets ${requestShown} (origin ${requestOriginShown}) but its own credential is bound to ${boundShown}, ` +
341
+ `the origin it was first sent to — a credential does not follow its entry to another host: change the entry's credential ` +
342
+ `reference in the same step as its baseUrl (a new credential is a new binding), or point the entry back at ${boundShown}; ` +
343
+ `a binding lasts as long as the engine process, so a provider that only moved domains and accepts the same credential there needs a restart`,
344
+ },
345
+ };
346
+ }
234
347
  if (credential.source === "deployment-config") {
235
348
  if (request !== "" && request === deploymentRoot)
236
349
  return { ok: true, posture: "paired" };
@@ -273,22 +386,40 @@ export function adjudicateModelRoute(model, credential, config) {
273
386
  export function routeRefusalText(verdict) {
274
387
  return `${verdict.code}: ${verdict.detail.fixHint}`;
275
388
  }
276
- export function applyRouteCredentialHeaders(merged, credential, bags) {
389
+ export function routeRefusalError(verdict) {
390
+ const { modelId, boundOrigin, requestOrigin } = verdict.detail;
391
+ if (verdict.code === "route.credential_origin_mismatch" && boundOrigin !== undefined && requestOrigin !== undefined) {
392
+ reportCredentialOriginRefusal({ modelId, boundOrigin, requestOrigin });
393
+ }
394
+ return new BrainError("invalid_request", routeRefusalText(verdict));
395
+ }
396
+ export function admitRouteRequest(model, credential, config) {
397
+ const judged = { id: model.id, baseUrl: model.baseUrl };
398
+ const { baseUrl, laneDefault } = config;
399
+ const pairing = { ...(baseUrl !== undefined ? { baseUrl } : {}), ...(laneDefault !== undefined ? { laneDefault } : {}) };
400
+ const judgeOrRefuse = () => {
401
+ const verdict = adjudicateModelRoute(judged, credential, pairing);
402
+ if (!verdict.ok)
403
+ throw routeRefusalError(verdict);
404
+ };
405
+ judgeOrRefuse();
406
+ const identities = credential.source === "per-model" ? resolvedCredentialFacts.get(credential)?.identities : undefined;
407
+ const requestOrigin = routeOriginOf(routeHalves(judged, pairing).request);
408
+ if (identities === undefined || identities.length === 0 || requestOrigin === "")
409
+ return () => { };
410
+ return () => {
411
+ judgeOrRefuse();
412
+ bindCredentialOrigin(identities, requestOrigin);
413
+ };
414
+ }
415
+ export function applyRouteCredentialHeaders(merged, credential) {
277
416
  if (credential.source !== "per-model")
278
417
  return;
279
418
  for (const k of Object.keys(merged)) {
280
- if (AUTH_CARRIER_NAMES.has(k.toLowerCase()))
419
+ if (isCredentialHeaderName(k))
281
420
  delete merged[k];
282
421
  }
283
- if (credential.headersBorne !== true)
284
- return;
285
- const winner = credential.carrierBag === "options" ? bags.options : bags.model;
286
- if (!winner)
287
- return;
288
- for (const [k, v] of Object.entries(winner)) {
289
- if (AUTH_CARRIER_NAMES.has(k.toLowerCase()))
290
- merged[k] = v;
291
- }
422
+ Object.assign(merged, resolvedCredentialFacts.get(credential)?.headers);
292
423
  }
293
424
  export function brainConfigSnapshot(given, subject) {
294
425
  const words = { subject, unit: "member", moment: "at construction" };
@@ -299,7 +430,9 @@ export function brainConfigSnapshot(given, subject) {
299
430
  throw receiptRefusal(`${subject}.headers must be an object of header names to values`);
300
431
  Object.defineProperty(received, "headers", { value: Object.freeze(receiveOwnProperties(headers, { ...words, subject: `${subject}.headers`, unit: "header" })), enumerable: true });
301
432
  }
302
- return Object.freeze(received);
433
+ const snapshot = Object.freeze(received);
434
+ holdOwnCredentialValues([snapshot.apiKey, ...credentialHeaderValues(snapshot.headers)]);
435
+ return snapshot;
303
436
  }
304
437
  export function createBrainRouteJudge(config) {
305
438
  return (model, perModelAuth) => {
@@ -309,17 +442,24 @@ export function createBrainRouteJudge(config) {
309
442
  modelHeaders: model.headers,
310
443
  configApiKey: config.apiKey,
311
444
  configHeaders: config.headers,
445
+ keyHeader: config.keyHeader,
312
446
  });
313
447
  return adjudicateModelRoute(model, credential, { baseUrl: config.baseUrl, laneDefault: config.laneDefault });
314
448
  };
315
449
  }
450
+ const BROKEN_PAIRING_STATUS = {
451
+ "route.credential_mismatch": "broken:credential_mismatch",
452
+ "route.credential_missing": "broken:credential_missing",
453
+ "route.credential_origin_mismatch": "broken:credential_origin_mismatch",
454
+ "route.credential_answer_conflict": "broken:credential_answer_conflict",
455
+ };
316
456
  export function routePairingStatus(brain, model, perModelAuth) {
317
457
  const verdict = brain.adjudicateRoute?.(model, perModelAuth);
318
458
  if (verdict === undefined)
319
459
  return "unknown";
320
460
  if (verdict.ok)
321
461
  return `ok:${verdict.posture}`;
322
- return verdict.code === "route.credential_mismatch" ? "broken:credential_mismatch" : "broken:credential_missing";
462
+ return BROKEN_PAIRING_STATUS[verdict.code];
323
463
  }
324
464
  export const DERIVED_ROUTE_PREFLIGHT_HOOK_BOUND_MS = 5_000;
325
465
  export async function adjudicateDerivedRoute(args) {
@@ -391,14 +531,3 @@ export function unroutableSeatNotice(args) {
391
531
  },
392
532
  };
393
533
  }
394
- export function authCarrierFingerprint(headers) {
395
- if (!headers)
396
- return "";
397
- const rows = [];
398
- for (const [k, v] of Object.entries(headers)) {
399
- const fold = k.toLowerCase();
400
- if (AUTH_CARRIER_NAMES.has(fold))
401
- rows.push(`${fold}=${v}`);
402
- }
403
- return rows.sort().join("\n");
404
- }
@@ -1,7 +1,7 @@
1
1
  export const ROUTE_ADJUDICATION_CONFORMANCE_CORPUS = [
2
2
  {
3
3
  name: "per-model-on-root",
4
- intent: "a per-model credential is paired by construction, on the deployment root",
4
+ intent: "a per-model credential not bound yet is servable, on the deployment root",
5
5
  model: { id: "m-main", baseUrl: "https://gw.example.com" },
6
6
  credential: { source: "per-model", apiKey: "k-main" },
7
7
  config: { baseUrl: "https://gw.example.com" },
@@ -9,7 +9,7 @@ export const ROUTE_ADJUDICATION_CONFORMANCE_CORPUS = [
9
9
  },
10
10
  {
11
11
  name: "per-model-off-root",
12
- intent: "a per-model credential is paired by construction even off the deployment root (the resolver was asked about THIS entry)",
12
+ intent: "a per-model credential not bound yet is servable off the deployment root too (the resolver was asked about THIS entry)",
13
13
  model: { id: "m-third", baseUrl: "https://third-party.example.net/v1" },
14
14
  credential: { source: "per-model", apiKey: "k-third" },
15
15
  config: { baseUrl: "https://gw.example.com" },
@@ -150,3 +150,21 @@ export declare function runWithExtraBodyDropped<T>(observe: (r: ExtraBodyDropped
150
150
  /** Report the strip of the request the engine has SELECTED to send, to the active per-run sink, if any (called at the
151
151
  * engine's commit points, never from `buildRequest`, which is also built speculatively). */
152
152
  export declare function reportExtraBodyDropped(report: ExtraBodyDroppedReport): void;
153
+ /** A request refused because the per-model credential it would carry is bound to another origin: the catalog id it was
154
+ * for and the two origins, each in its DISPLAY spelling. Never a value, never a digest of one. */
155
+ export interface CredentialOriginRefusalReport {
156
+ modelId: string;
157
+ boundOrigin: string;
158
+ requestOrigin: string;
159
+ }
160
+ /**
161
+ * Run `fn` with a per-run credential-origin refusal sink in scope — the FIFTH brain→runner ALS channel, with the fourth's
162
+ * shape and for its reasons: the WHOLE leg (a refused compaction summary, side query or classifier call is refused for the
163
+ * same reason as a turn), not shielded around the leg's internal calls, not revoked on settlement, no-op outside the scope.
164
+ * The refusal is already decided when it is reported.
165
+ */
166
+ export declare function runWithCredentialOriginRefusals<T>(observe: (r: CredentialOriginRefusalReport) => void, fn: () => Promise<T>): Promise<T>;
167
+ /** Report a credential-origin refusal to the active per-run sink, if any (called by the request gate's refusal,
168
+ * `routeRefusalError`; a judgment that only advises reports nothing). The
169
+ * observer is the Runner's own seat, whose one delivery is the guarded `deliverEngineNotice` — nothing is swallowed here. */
170
+ export declare function reportCredentialOriginRefusal(report: CredentialOriginRefusalReport): void;
@@ -68,3 +68,10 @@ export function reportExtraBodyDropped(report) {
68
68
  catch {
69
69
  }
70
70
  }
71
+ const credentialOriginRefusalSinkStore = new AsyncLocalStorage();
72
+ export function runWithCredentialOriginRefusals(observe, fn) {
73
+ return credentialOriginRefusalSinkStore.run({ observe }, fn);
74
+ }
75
+ export function reportCredentialOriginRefusal(report) {
76
+ credentialOriginRefusalSinkStore.getStore()?.observe(report);
77
+ }
@@ -8,8 +8,9 @@ import { type EngineNotice } from "./engine-notice.js";
8
8
  /** Why an entry grants (or refuses) nothing — the closed set the start-of-run notice names, for every list. `tool_name`: the
9
9
  * entry's tool name is one the name judgment reads as stale against the run's roster (`verdict` says which); `near_miss`: the
10
10
  * run mounts no tool of that exact name but mounts one that differs only by case (`currentName` is its spelling);
11
- * `earlier_grammar`: a session's stored rules are written in an earlier rule grammar, in which the entry named no tool (the
12
- * session store's list reading, `compileSessionRuleBucket` — this reader never answers it). */
11
+ * `earlier_grammar`: a session's stored rules are written in an earlier rule grammar, in which the entry named no tool, or which
12
+ * read an entry this grammar reads as no rule (the session store's list reading, `compileSessionRuleBucket` — this reader never
13
+ * answers it). */
13
14
  export declare const ALLOW_ENTRY_INERT_REASONS: readonly ["wildcard_all", "namespace_wildcard", "content_unsupported", "grammar", "tool_name", "near_miss", "earlier_grammar"];
14
15
  export type AllowEntryInertReason = (typeof ALLOW_ENTRY_INERT_REASONS)[number];
15
16
  /** The facts beside a reason: for `tool_name` / `near_miss` the judgment's word, the judgment's own one-line sentence when it has
@@ -21,7 +22,13 @@ export interface AllowEntryInertDetail {
21
22
  readonly guidance?: string;
22
23
  readonly currentName?: string;
23
24
  readonly valueType?: string;
25
+ /** Where the spelling of a `near_miss` comes from, when the station that read it reads more than the run's roster
26
+ * ({@link NearMissCarrier}); absent ⇒ the roster the run mounts. A sentence only — never a field of a wire detail. */
27
+ readonly carriedBy?: NearMissCarrier;
24
28
  }
29
+ /** Who carries the spelling a near miss reads as another spelling of: the roster the run mounts, a delegation tool's pool, or
30
+ * only the engine's tool catalog (a tool the engine may mount, which this run's roster does not carry). */
31
+ export type NearMissCarrier = "run" | "pool" | "catalog";
25
32
  /**
26
33
  * How a list member that is not a string is named wherever a list's entries are named (the inert record's `entry`, the Skill
27
34
  * card's binding): its TYPE, in a spelling no readable entry can have — a readable entry's name holds no whitespace and a
@@ -64,6 +71,25 @@ export type CompiledAllowEntry = {
64
71
  readonly kind: "domain";
65
72
  readonly rule: PersistedRule;
66
73
  };
74
+ /**
75
+ * THE near-miss reading of a tool name the judgment calls current: the spelling the run mounts when the run mounts no tool of this
76
+ * exact name but one that differs from it only by case (`BASH` beside `Bash`), else `undefined`. Two kinds of name are never a near
77
+ * miss, because the roster they would be read against is not the final one: an engine tool's (the engine mounts some of its tools
78
+ * after a list is read) and a protocol tool's (a refresh mounts and unmounts them while the run goes on) — in both the exact
79
+ * spelling can be the right one by the time it matters. Every station that asks "is this name a case twin of a mounted tool" reads
80
+ * this answer: an entry list ({@link readRuleEntry}), a tool policy's name lists (the prepare-time policy-name audit).
81
+ */
82
+ export declare function nearMissSpellingOf(name: string, roster: ReadonlySet<string>): string | undefined;
83
+ /**
84
+ * THE universe a ROOT-facing tool-name list is read against: every name the engine's catalog carries, and the names the reader knows
85
+ * are carried (`carried`). A list is root-facing when it bounds the tools of the run that holds it — the run's own `allowTools` /
86
+ * `excludeTools` (also the ceiling of its children), a tool policy's name lists, a session's stored rules — and its reader is that
87
+ * run (`carried`: its roster, the caller's own tools, and the exact names of the pools its reachable delegation tools declare and can
88
+ * list) or the store that writes the rules (`carried`: the tools its deployment declared). Another spelling of a carried name is
89
+ * always refused there, whatever a delegation tool injects per spawn: the run's own tools are known, and an entry that is a case
90
+ * twin of one of them names the other tool by mistake. A delegation tool's pool that cannot be listed carries nothing here.
91
+ */
92
+ export declare function rootFacingToolNames(carried: Iterable<string>): Set<string>;
67
93
  /** What one rule entry means under one direction ({@link readRuleEntry}): the row the grammar compiles it to, or the reason it
68
94
  * reads as nothing with the facts a sentence about it names. */
69
95
  export type RuleEntryReading = {
@@ -87,9 +113,10 @@ export declare function compiledEntryName(compiled: CompiledAllowEntry): string;
87
113
  /**
88
114
  * Compile one list WHOLE through {@link readRuleEntry}: the entries it reads and the ones it reads as nothing (each with its
89
115
  * reason and, for a name the roster refuses, the judgment's word and the spelling to write). An allow list holding `*` voids
90
- * the list (every other entry on it reads as nothing too: the list grants nothing — the safe direction). A `*` on a deny list
91
- * reads as nothing by itself and the rest of that list still refuses: a deny list never loses an entry to a wildcard beside
92
- * it. `roster` as in {@link readRuleEntry}.
116
+ * the list (every other entry on it reads as nothing too: the list grants nothing — the safe direction) — `*` as the lenient
117
+ * rule parser names it ({@link namesWholeRoster}), so an entry the stricter entry grammar refuses (`* (*)`) still voids. A
118
+ * `*` on a deny list reads as nothing by itself and the rest of that list still refuses: a deny list never loses an entry to
119
+ * a wildcard beside it. `roster` as in {@link readRuleEntry}.
93
120
  */
94
121
  export declare function compileAllowList(entries: readonly string[], behavior: "allow" | "deny", roster?: ReadonlySet<string>): {
95
122
  rows: Array<{
@@ -294,11 +321,16 @@ export type AllowEntryInertNote = ({
294
321
  } & AllowEntryInert) | ({
295
322
  readonly source: AllowEntrySource;
296
323
  } & AllowEntryInert);
324
+ /**
325
+ * A list entry spelled so a person reads what the engine read: the entry as a JSON string — quotes, backslashes and the usual
326
+ * escapes — with every character that prints as nothing, or as a space, written as `\uXXXX` (`\u{…}` beyond the BMP) and a
327
+ * run of spaces kept space by space. Uncapped and one-to-one (two entries never share a spelling), so a value that BINDS an
328
+ * entry may hold it ({@link renderListEntry} is this, capped for a sentence).
329
+ */
330
+ export declare function spelledListEntry(entry: string): string;
297
331
  /**
298
332
  * THE ONE WRITER of a list entry into a sentence (the start-of-run notice, a skill's refusal and its decisions, a sub-agent
299
- * list's refusals and advisories): the entry as a JSON string — quotes, backslashes and the usual escapes — with every
300
- * character that prints as nothing, or as a space, written as `\uXXXX` (`\u{…}` beyond the BMP) and a run of spaces kept
301
- * space by space, so what a person reads is what the engine read. Capped on what is printed.
333
+ * list's refusals and advisories): {@link spelledListEntry}, capped on what is printed.
302
334
  */
303
335
  export declare function renderListEntry(entry: string): string;
304
336
  /**
@@ -1,5 +1,6 @@
1
1
  import { heldBy } from "./tool-name-readings.js";
2
- import { isNamespacedCoveringRuleName, namespacedRuleNameCovers, parsePermissionRule } from "./permission-rules.js";
2
+ import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "./permission-rules.js";
3
+ import { parsePermissionRule, ruleSpellingOf } from "./permission-rule-syntax.js";
3
4
  import { judgeRuleToolName, splitNamespacedName } from "./tool-name-aliases.js";
4
5
  import { catalogToolNames, isShellFaceToolName } from "./tool-registry.js";
5
6
  import { ALLOW_RULE_MEMBERS } from "./allow-rule-shadow.js";
@@ -12,6 +13,7 @@ import { inlineUntrusted } from "./untrusted-text.js";
12
13
  import { valueTypeLabel } from "./tool-name-list.js";
13
14
  import { deliverEngineNotice } from "./engine-notice.js";
14
15
  export const ALLOW_ENTRY_INERT_REASONS = Object.freeze(["wildcard_all", "namespace_wildcard", "content_unsupported", "grammar", "tool_name", "near_miss", "earlier_grammar"]);
16
+ const NEAR_MISS_CARRIER = { run: "the mounted spelling is", pool: "a delegation pool carries", catalog: "the engine's tool catalog names" };
15
17
  export function nonStringMemberLabel(value) {
16
18
  return `<not a string: ${valueTypeLabel(value)}>`;
17
19
  }
@@ -21,7 +23,6 @@ export function nonListValueLabel(read) {
21
23
  function ruleRowOf(parsed) {
22
24
  return { ...parsed, scope: { kind: "global" }, adds: [{ dot: { actor: "list", counter: 1 }, origin: "user", createdAt: "" }] };
23
25
  }
24
- const TOOL_NAME_SPELLING = /^[^\s()\p{Cc}\p{Cf}]+$/u;
25
26
  function mountedSpellingOf(name, roster) {
26
27
  const folded = name.toLowerCase();
27
28
  for (const mounted of roster)
@@ -29,6 +30,14 @@ function mountedSpellingOf(name, roster) {
29
30
  return mounted;
30
31
  return undefined;
31
32
  }
33
+ export function nearMissSpellingOf(name, roster) {
34
+ if (roster.has(name) || catalogToolNames().has(name) || splitNamespacedName(name) !== undefined)
35
+ return undefined;
36
+ return mountedSpellingOf(name, roster);
37
+ }
38
+ export function rootFacingToolNames(carried) {
39
+ return new Set([...catalogToolNames(), ...carried]);
40
+ }
32
41
  function toolNameReading(name, roster) {
33
42
  const verdict = judgeRuleToolName(name, roster);
34
43
  if (verdict.kind === "unprefixed_namespace")
@@ -36,9 +45,7 @@ function toolNameReading(name, roster) {
36
45
  if (verdict.kind !== "current") {
37
46
  return { kind: "inert", reason: "tool_name", verdict: verdict.kind, guidance: verdict.guidance, ...(verdict.currentName !== undefined ? { currentName: verdict.currentName } : {}) };
38
47
  }
39
- if (roster.has(name) || catalogToolNames().has(name) || splitNamespacedName(name) !== undefined)
40
- return undefined;
41
- const mounted = mountedSpellingOf(name, roster);
48
+ const mounted = nearMissSpellingOf(name, roster);
42
49
  return mounted === undefined ? undefined : { kind: "inert", reason: "near_miss", currentName: mounted };
43
50
  }
44
51
  export function readRuleEntry(raw, behavior, roster) {
@@ -46,12 +53,10 @@ export function readRuleEntry(raw, behavior, roster) {
46
53
  const row = (compiled) => ({ kind: "row", compiled });
47
54
  if (typeof raw !== "string")
48
55
  return { kind: "inert", reason: "grammar", valueType: valueTypeLabel(raw) };
49
- if (raw !== raw.trim())
56
+ const parsed = ruleSpellingOf(raw);
57
+ if (parsed === undefined)
50
58
  return inert("grammar");
51
- const parsed = parsePermissionRule(raw);
52
59
  const name = parsed.toolName;
53
- if (!TOOL_NAME_SPELLING.test(name))
54
- return inert("grammar");
55
60
  if (name === "*")
56
61
  return inert("wildcard_all");
57
62
  if (roster !== undefined) {
@@ -98,7 +103,7 @@ function inertReasonOf(code) {
98
103
  return code === "unsupported.tool" ? "content_unsupported" : "grammar";
99
104
  }
100
105
  export function compileAllowList(entries, behavior, roster) {
101
- const voided = behavior === "allow" && entries.some((e) => isWildcardAll(readRuleEntry(e, behavior)));
106
+ const voided = behavior === "allow" && entries.some(namesWholeRoster);
102
107
  const rows = [];
103
108
  const inert = [];
104
109
  for (const entry of entries) {
@@ -113,8 +118,8 @@ export function compileAllowList(entries, behavior, roster) {
113
118
  }
114
119
  return { rows, inert };
115
120
  }
116
- function isWildcardAll(read) {
117
- return read.kind === "inert" && read.reason === "wildcard_all";
121
+ function namesWholeRoster(entry) {
122
+ return typeof entry === "string" && entry === entry.trim() && parsePermissionRule(entry).toolName === "*";
118
123
  }
119
124
  function answersTo(req, has) {
120
125
  return heldBy("allowEntry.names", { has }, req);
@@ -415,11 +420,13 @@ function escapedCodePoint(ch) {
415
420
  const hex = cp.toString(16).padStart(4, "0");
416
421
  return cp > 0xffff ? `\\u{${hex}}` : `\\u${hex}`;
417
422
  }
418
- export function renderListEntry(entry) {
419
- const quoted = JSON.stringify(entry)
423
+ export function spelledListEntry(entry) {
424
+ return JSON.stringify(entry)
420
425
  .replace(/ {2,}/g, (run) => "\\u0020".repeat(run.length))
421
426
  .replace(UNSEEN_IN_ENTRY, (ch) => (ch === " " ? ch : escapedCodePoint(ch)));
422
- return inlineUntrusted(quoted, ENTRY_RENDER_MAX);
427
+ }
428
+ export function renderListEntry(entry) {
429
+ return inlineUntrusted(spelledListEntry(entry), ENTRY_RENDER_MAX);
423
430
  }
424
431
  export function inertEntryPhrase(e) {
425
432
  if (e.valueType !== undefined)
@@ -427,7 +434,7 @@ export function inertEntryPhrase(e) {
427
434
  const spelling = e.currentName !== undefined ? inlineUntrusted(e.currentName, 120) : undefined;
428
435
  const why = (() => {
429
436
  if (e.reason === "near_miss")
430
- return `near_miss: the mounted spelling is "${spelling}"`;
437
+ return `near_miss: ${NEAR_MISS_CARRIER[e.carriedBy ?? "run"]} "${spelling}"`;
431
438
  if (e.reason !== "tool_name" || e.verdict === undefined)
432
439
  return e.reason;
433
440
  switch (e.verdict) {
@@ -1,7 +1,7 @@
1
1
  import { type AgentMessage, type CompactionSettings, type Session, type ThinkingLevel } from "../internal/harness.js";
2
2
  import type { Message, Model, Tool } from "../internal/llm.js";
3
3
  import type { ContextWindowOverlay } from "./context-settings.js";
4
- import type { Hooks } from "./hooks.js";
4
+ import type { PostCompactContext, PreCompactContext, PreCompactResult } from "./hooks.js";
5
5
  import type { TraceEvent } from "./trace.js";
6
6
  import type { Brain } from "./types.js";
7
7
  import type { TaskEvent, TaskEventIdentity } from "./task-event.js";
@@ -108,25 +108,27 @@ export interface MaybeCompactOptions {
108
108
  /** The session to compact. Typed on the INTERFACE face (typecheck 车 2026-07-24): every member this
109
109
  * module touches lives on `Session` (the optional `getPromptEpoch` degrades honestly via `?.()`). */
110
110
  session: Session;
111
- /** This run's provider-declared prompt sections (the epoch declaration axis):
112
- * the compaction-boundary epoch restatement/advance must hash the SAME artifact identity as the
113
- * prepare-time pin, or every compaction under a declaration-carrying run would flap the pin. */
114
- epochDeclaredSections?: import("../prompt-assembly/epoch.js").EpochDeclaredSections;
115
- /** A compaction-boundary CENTER CANDIDATE ADOPTION, pre-assembled by the
116
- * caller (prepare's seam): the descriptor advances with the candidate's declarations + provenance
117
- * in the SAME CAS append as the new baseline, and `apply()` (the live prefix/fingerprint swap)
118
- * runs ONLY after that append succeeded — a failed/blocked compaction must swap nothing. */
111
+ /** This run's prompt identity inputs (and the pinned center layer's provenance): the compaction restates the
112
+ * session's epoch when the identity is unchanged and advances it otherwise — the same identity the prepare-time
113
+ * record names, so a compaction never flaps the record. Absent ⇒ no restatement (a host compacting outside a run). */
114
+ promptIdentity?: {
115
+ readonly layers: import("../prompt-assembly/epoch.js").PromptIdentityLayers;
116
+ readonly sourceRevision?: string;
117
+ };
118
+ /** A compaction-boundary CENTER LAYER CHANGE, pre-assembled by the caller (prepare's seam): an adopted candidate,
119
+ * or an emptied center layer after an explicit revocation. The epoch advances to the identity after the change in
120
+ * the SAME CAS append as the new baseline, and `apply()` (the live prefix / fingerprint swap) runs ONLY after that
121
+ * append succeeded — a failed or blocked compaction swaps nothing. */
119
122
  centerAdoption?: {
120
- /** Absent together with `clear` ⇒ never happens; adoption carries the new digest, ROLLBACK
121
- * (explicit center disable) carries `clear:true` and no digest. */
122
- centerArtifactDigest?: string;
123
- sourceRevision?: string;
124
- clear?: true;
125
- declaredSections: import("../prompt-assembly/epoch.js").EpochDeclaredSections;
123
+ /** The run's identity inputs after the change (`layers.center` absent ⇒ the center layer is emptied). */
124
+ promptIdentity: {
125
+ readonly layers: import("../prompt-assembly/epoch.js").PromptIdentityLayers;
126
+ readonly sourceRevision?: string;
127
+ };
126
128
  /** the post-swap prompt overhead in the trigger estimate's coordinate: used for THIS
127
129
  * compaction's post-measurement so PTL recovery/anti-thrash see the swapped prefix size. */
128
130
  overheadTokensAfter: number;
129
- apply: (committedArtifactDigest?: string) => void;
131
+ apply: (committedIdentity: string) => void;
130
132
  };
131
133
  /** Model whose context window bounds the budget (the model that runs the next turn). */
132
134
  model: Model;
@@ -196,8 +198,9 @@ export interface MaybeCompactOptions {
196
198
  contextInstructionFiles?: string;
197
199
  signal?: AbortSignal;
198
200
  /**
199
- * Manual compact() withdrawal signal — the composed cancel signals of the manual callers THIS
200
- * pass claimed (a strict SUBSET of `signal`, which additionally carries the task's own abort).
201
+ * The pass's WITHDRAWAL signal — the run's caller-stop gate (the stop verbs and the task's own
202
+ * `signal`), composed with the cancel signals of the manual callers THIS pass claimed (a SUBSET
203
+ * of `signal`, which may additionally carry the leg's own abort).
201
204
  * Used only for failure ATTRIBUTION: when the summary call's failure is its abort outcome and
202
205
  * this signal has fired, the thrown error is stamped so {@link isCompactionManualCancel} holds —
203
206
  * the caller then treats the pass as withdrawn ("mooted") instead of summarizer evidence. It
@@ -488,13 +491,13 @@ export interface MaybeCompactOptions {
488
491
  * no-block/no-instructions (the caller's wrapper owns tracing; an observer bug must never feed the
489
492
  * caller's breaker or kill PTL recovery).
490
493
  */
491
- preCompact?: Hooks["preCompact"];
494
+ preCompact?: (ctx: PreCompactContext) => PreCompactResult | undefined | Promise<PreCompactResult | undefined>;
492
495
  /**
493
496
  * design/134 (CC PostCompact parity) — observe-only, fired after the compaction LANDED
494
497
  * (appendCompaction done, post measurement taken). A throw is swallowed (same isolation contract
495
498
  * as {@link onCompaction}: the compaction already succeeded).
496
499
  */
497
- postCompact?: Hooks["postCompact"];
500
+ postCompact?: (ctx: PostCompactContext) => void | Promise<void>;
498
501
  /**
499
502
  * MF-18 修② — observe-only: fired when a summary call's serialized conversation INPUT was
500
503
  * truncated to fit the compaction model's context window (the ~300K-session shape that previously