@namzu/sdk 0.4.2 → 0.4.4

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 (310) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/advisory/context.test.d.ts +16 -0
  3. package/dist/advisory/context.test.d.ts.map +1 -0
  4. package/dist/advisory/context.test.js +92 -0
  5. package/dist/advisory/context.test.js.map +1 -0
  6. package/dist/advisory/evaluator.test.d.ts +34 -0
  7. package/dist/advisory/evaluator.test.d.ts.map +1 -0
  8. package/dist/advisory/evaluator.test.js +172 -0
  9. package/dist/advisory/evaluator.test.js.map +1 -0
  10. package/dist/advisory/executor.test.d.ts +35 -0
  11. package/dist/advisory/executor.test.d.ts.map +1 -0
  12. package/dist/advisory/executor.test.js +233 -0
  13. package/dist/advisory/executor.test.js.map +1 -0
  14. package/dist/advisory/registry.test.d.ts +16 -0
  15. package/dist/advisory/registry.test.d.ts.map +1 -0
  16. package/dist/advisory/registry.test.js +62 -0
  17. package/dist/advisory/registry.test.js.map +1 -0
  18. package/dist/bridge/a2a/agent-card.test.d.ts +24 -0
  19. package/dist/bridge/a2a/agent-card.test.d.ts.map +1 -0
  20. package/dist/bridge/a2a/agent-card.test.js +118 -0
  21. package/dist/bridge/a2a/agent-card.test.js.map +1 -0
  22. package/dist/bridge/a2a/mapper.test.d.ts +29 -0
  23. package/dist/bridge/a2a/mapper.test.d.ts.map +1 -0
  24. package/dist/bridge/a2a/mapper.test.js +265 -0
  25. package/dist/bridge/a2a/mapper.test.js.map +1 -0
  26. package/dist/bridge/a2a/message.test.d.ts +20 -0
  27. package/dist/bridge/a2a/message.test.d.ts.map +1 -0
  28. package/dist/bridge/a2a/message.test.js +116 -0
  29. package/dist/bridge/a2a/message.test.js.map +1 -0
  30. package/dist/bridge/a2a/task.test.d.ts +29 -0
  31. package/dist/bridge/a2a/task.test.d.ts.map +1 -0
  32. package/dist/bridge/a2a/task.test.js +198 -0
  33. package/dist/bridge/a2a/task.test.js.map +1 -0
  34. package/dist/bridge/mcp/connector/adapter.test.d.ts +27 -0
  35. package/dist/bridge/mcp/connector/adapter.test.d.ts.map +1 -0
  36. package/dist/bridge/mcp/connector/adapter.test.js +203 -0
  37. package/dist/bridge/mcp/connector/adapter.test.js.map +1 -0
  38. package/dist/bridge/sse/mapper.test.d.ts +27 -0
  39. package/dist/bridge/sse/mapper.test.d.ts.map +1 -0
  40. package/dist/bridge/sse/mapper.test.js +271 -0
  41. package/dist/bridge/sse/mapper.test.js.map +1 -0
  42. package/dist/bridge/tools/connector/adapter.d.ts +2 -2
  43. package/dist/bridge/tools/connector/adapter.test.d.ts +28 -0
  44. package/dist/bridge/tools/connector/adapter.test.d.ts.map +1 -0
  45. package/dist/bridge/tools/connector/adapter.test.js +182 -0
  46. package/dist/bridge/tools/connector/adapter.test.js.map +1 -0
  47. package/dist/bridge/tools/connector/definitions.test.d.ts +23 -0
  48. package/dist/bridge/tools/connector/definitions.test.d.ts.map +1 -0
  49. package/dist/bridge/tools/connector/definitions.test.js +158 -0
  50. package/dist/bridge/tools/connector/definitions.test.js.map +1 -0
  51. package/dist/bridge/tools/connector/router.test.d.ts +21 -0
  52. package/dist/bridge/tools/connector/router.test.d.ts.map +1 -0
  53. package/dist/bridge/tools/connector/router.test.js +139 -0
  54. package/dist/bridge/tools/connector/router.test.js.map +1 -0
  55. package/dist/bus/breaker.test.d.ts +41 -0
  56. package/dist/bus/breaker.test.d.ts.map +1 -0
  57. package/dist/bus/breaker.test.js +242 -0
  58. package/dist/bus/breaker.test.js.map +1 -0
  59. package/dist/bus/index.d.ts +3 -1
  60. package/dist/bus/index.d.ts.map +1 -1
  61. package/dist/bus/index.js +18 -11
  62. package/dist/bus/index.js.map +1 -1
  63. package/dist/bus/index.test.d.ts +25 -0
  64. package/dist/bus/index.test.d.ts.map +1 -0
  65. package/dist/bus/index.test.js +151 -0
  66. package/dist/bus/index.test.js.map +1 -0
  67. package/dist/bus/lock.test.d.ts +44 -0
  68. package/dist/bus/lock.test.d.ts.map +1 -0
  69. package/dist/bus/lock.test.js +226 -0
  70. package/dist/bus/lock.test.js.map +1 -0
  71. package/dist/bus/ownership.test.d.ts +26 -0
  72. package/dist/bus/ownership.test.d.ts.map +1 -0
  73. package/dist/bus/ownership.test.js +205 -0
  74. package/dist/bus/ownership.test.js.map +1 -0
  75. package/dist/config/runtime.d.ts +28 -28
  76. package/dist/connector/BaseConnector.test.d.ts +21 -0
  77. package/dist/connector/BaseConnector.test.d.ts.map +1 -0
  78. package/dist/connector/BaseConnector.test.js +108 -0
  79. package/dist/connector/BaseConnector.test.js.map +1 -0
  80. package/dist/connector/builtins/http.test.d.ts +30 -0
  81. package/dist/connector/builtins/http.test.d.ts.map +1 -0
  82. package/dist/connector/builtins/http.test.js +232 -0
  83. package/dist/connector/builtins/http.test.js.map +1 -0
  84. package/dist/connector/builtins/webhook.test.d.ts +20 -0
  85. package/dist/connector/builtins/webhook.test.d.ts.map +1 -0
  86. package/dist/connector/builtins/webhook.test.js +113 -0
  87. package/dist/connector/builtins/webhook.test.js.map +1 -0
  88. package/dist/connector/execution/factory.test.d.ts +16 -0
  89. package/dist/connector/execution/factory.test.d.ts.map +1 -0
  90. package/dist/connector/execution/factory.test.js +64 -0
  91. package/dist/connector/execution/factory.test.js.map +1 -0
  92. package/dist/connector/execution/remote.test.d.ts +16 -0
  93. package/dist/connector/execution/remote.test.d.ts.map +1 -0
  94. package/dist/connector/execution/remote.test.js +53 -0
  95. package/dist/connector/execution/remote.test.js.map +1 -0
  96. package/dist/connector/mcp/adapter.test.d.ts +34 -0
  97. package/dist/connector/mcp/adapter.test.d.ts.map +1 -0
  98. package/dist/connector/mcp/adapter.test.js +199 -0
  99. package/dist/connector/mcp/adapter.test.js.map +1 -0
  100. package/dist/probe/context.d.ts +8 -0
  101. package/dist/probe/context.d.ts.map +1 -0
  102. package/dist/probe/context.js +7 -0
  103. package/dist/probe/context.js.map +1 -0
  104. package/dist/probe/errors.d.ts +12 -0
  105. package/dist/probe/errors.d.ts.map +1 -0
  106. package/dist/probe/errors.js +21 -0
  107. package/dist/probe/errors.js.map +1 -0
  108. package/dist/probe/index.d.ts +5 -0
  109. package/dist/probe/index.d.ts.map +1 -0
  110. package/dist/probe/index.js +4 -0
  111. package/dist/probe/index.js.map +1 -0
  112. package/dist/probe/registry.d.ts +24 -0
  113. package/dist/probe/registry.d.ts.map +1 -0
  114. package/dist/probe/registry.js +228 -0
  115. package/dist/probe/registry.js.map +1 -0
  116. package/dist/probe/registry.test.d.ts +7 -0
  117. package/dist/probe/registry.test.d.ts.map +1 -0
  118. package/dist/probe/registry.test.js +310 -0
  119. package/dist/probe/registry.test.js.map +1 -0
  120. package/dist/provider/instrumentation.d.ts +9 -0
  121. package/dist/provider/instrumentation.d.ts.map +1 -0
  122. package/dist/provider/instrumentation.js +104 -0
  123. package/dist/provider/instrumentation.js.map +1 -0
  124. package/dist/provider/instrumentation.test.d.ts +2 -0
  125. package/dist/provider/instrumentation.test.d.ts.map +1 -0
  126. package/dist/provider/instrumentation.test.js +152 -0
  127. package/dist/provider/instrumentation.test.js.map +1 -0
  128. package/dist/public-runtime.d.ts +5 -0
  129. package/dist/public-runtime.d.ts.map +1 -1
  130. package/dist/public-runtime.js +4 -0
  131. package/dist/public-runtime.js.map +1 -1
  132. package/dist/public-types.d.ts +3 -0
  133. package/dist/public-types.d.ts.map +1 -1
  134. package/dist/rag/chunking.test.d.ts +20 -0
  135. package/dist/rag/chunking.test.d.ts.map +1 -0
  136. package/dist/rag/chunking.test.js +92 -0
  137. package/dist/rag/chunking.test.js.map +1 -0
  138. package/dist/rag/context-assembler.test.d.ts +19 -0
  139. package/dist/rag/context-assembler.test.d.ts.map +1 -0
  140. package/dist/rag/context-assembler.test.js +98 -0
  141. package/dist/rag/context-assembler.test.js.map +1 -0
  142. package/dist/rag/embedding.test.d.ts +19 -0
  143. package/dist/rag/embedding.test.d.ts.map +1 -0
  144. package/dist/rag/embedding.test.js +115 -0
  145. package/dist/rag/embedding.test.js.map +1 -0
  146. package/dist/rag/ingestion.test.d.ts +22 -0
  147. package/dist/rag/ingestion.test.d.ts.map +1 -0
  148. package/dist/rag/ingestion.test.js +99 -0
  149. package/dist/rag/ingestion.test.js.map +1 -0
  150. package/dist/rag/knowledge-base.test.d.ts +17 -0
  151. package/dist/rag/knowledge-base.test.d.ts.map +1 -0
  152. package/dist/rag/knowledge-base.test.js +77 -0
  153. package/dist/rag/knowledge-base.test.js.map +1 -0
  154. package/dist/rag/rag-tool.test.d.ts +21 -0
  155. package/dist/rag/rag-tool.test.d.ts.map +1 -0
  156. package/dist/rag/rag-tool.test.js +149 -0
  157. package/dist/rag/rag-tool.test.js.map +1 -0
  158. package/dist/rag/retriever.test.d.ts +26 -0
  159. package/dist/rag/retriever.test.d.ts.map +1 -0
  160. package/dist/rag/retriever.test.js +180 -0
  161. package/dist/rag/retriever.test.js.map +1 -0
  162. package/dist/rag/vector-store.test.d.ts +38 -0
  163. package/dist/rag/vector-store.test.d.ts.map +1 -0
  164. package/dist/rag/vector-store.test.js +175 -0
  165. package/dist/rag/vector-store.test.js.map +1 -0
  166. package/dist/registry/ManagedRegistry.test.d.ts +21 -0
  167. package/dist/registry/ManagedRegistry.test.d.ts.map +1 -0
  168. package/dist/registry/ManagedRegistry.test.js +98 -0
  169. package/dist/registry/ManagedRegistry.test.js.map +1 -0
  170. package/dist/registry/Registry.test.d.ts +18 -0
  171. package/dist/registry/Registry.test.d.ts.map +1 -0
  172. package/dist/registry/Registry.test.js +79 -0
  173. package/dist/registry/Registry.test.js.map +1 -0
  174. package/dist/registry/agent/definitions.test.d.ts +15 -0
  175. package/dist/registry/agent/definitions.test.d.ts.map +1 -0
  176. package/dist/registry/agent/definitions.test.js +84 -0
  177. package/dist/registry/agent/definitions.test.js.map +1 -0
  178. package/dist/registry/connector/definitions.test.d.ts +13 -0
  179. package/dist/registry/connector/definitions.test.d.ts.map +1 -0
  180. package/dist/registry/connector/definitions.test.js +41 -0
  181. package/dist/registry/connector/definitions.test.js.map +1 -0
  182. package/dist/registry/connector/scoped.test.d.ts +21 -0
  183. package/dist/registry/connector/scoped.test.d.ts.map +1 -0
  184. package/dist/registry/connector/scoped.test.js +115 -0
  185. package/dist/registry/connector/scoped.test.js.map +1 -0
  186. package/dist/registry/plugin/index.test.d.ts +12 -0
  187. package/dist/registry/plugin/index.test.d.ts.map +1 -0
  188. package/dist/registry/plugin/index.test.js +69 -0
  189. package/dist/registry/plugin/index.test.js.map +1 -0
  190. package/dist/registry/tool/execute.test.d.ts +42 -0
  191. package/dist/registry/tool/execute.test.d.ts.map +1 -0
  192. package/dist/registry/tool/execute.test.js +281 -0
  193. package/dist/registry/tool/execute.test.js.map +1 -0
  194. package/dist/runtime/query/events.d.ts +3 -1
  195. package/dist/runtime/query/events.d.ts.map +1 -1
  196. package/dist/runtime/query/events.js +6 -1
  197. package/dist/runtime/query/events.js.map +1 -1
  198. package/dist/runtime/query/executor.d.ts +3 -1
  199. package/dist/runtime/query/executor.d.ts.map +1 -1
  200. package/dist/runtime/query/executor.js +30 -1
  201. package/dist/runtime/query/executor.js.map +1 -1
  202. package/dist/runtime/query/iteration/phases/advisory.test.d.ts +42 -0
  203. package/dist/runtime/query/iteration/phases/advisory.test.d.ts.map +1 -0
  204. package/dist/runtime/query/iteration/phases/advisory.test.js +334 -0
  205. package/dist/runtime/query/iteration/phases/advisory.test.js.map +1 -0
  206. package/dist/test-setup.d.ts +22 -0
  207. package/dist/test-setup.d.ts.map +1 -0
  208. package/dist/test-setup.js +23 -0
  209. package/dist/test-setup.js.map +1 -0
  210. package/dist/types/bus/index.d.ts +46 -2
  211. package/dist/types/bus/index.d.ts.map +1 -1
  212. package/dist/types/doctor/check.d.ts +41 -0
  213. package/dist/types/doctor/check.d.ts.map +1 -0
  214. package/dist/types/doctor/check.js +2 -0
  215. package/dist/types/doctor/check.js.map +1 -0
  216. package/dist/types/doctor/index.d.ts +2 -0
  217. package/dist/types/doctor/index.d.ts.map +1 -0
  218. package/dist/types/doctor/index.js +2 -0
  219. package/dist/types/doctor/index.js.map +1 -0
  220. package/dist/types/probe/event-kind.d.ts +6 -0
  221. package/dist/types/probe/event-kind.d.ts.map +1 -0
  222. package/dist/types/probe/event-kind.js +2 -0
  223. package/dist/types/probe/event-kind.js.map +1 -0
  224. package/dist/types/probe/event-of.d.ts +5 -0
  225. package/dist/types/probe/event-of.d.ts.map +1 -0
  226. package/dist/types/probe/event-of.js +2 -0
  227. package/dist/types/probe/event-of.js.map +1 -0
  228. package/dist/types/probe/index.d.ts +4 -0
  229. package/dist/types/probe/index.d.ts.map +1 -0
  230. package/dist/types/probe/index.js +2 -0
  231. package/dist/types/probe/index.js.map +1 -0
  232. package/dist/types/probe/registry.d.ts +27 -0
  233. package/dist/types/probe/registry.d.ts.map +1 -0
  234. package/dist/types/probe/registry.js +2 -0
  235. package/dist/types/probe/registry.js.map +1 -0
  236. package/dist/utils/logger.d.ts +1 -1
  237. package/dist/utils/logger.d.ts.map +1 -1
  238. package/dist/utils/logger.js +5 -0
  239. package/dist/utils/logger.js.map +1 -1
  240. package/dist/vault/instrumentation.d.ts +11 -0
  241. package/dist/vault/instrumentation.d.ts.map +1 -0
  242. package/dist/vault/instrumentation.js +32 -0
  243. package/dist/vault/instrumentation.js.map +1 -0
  244. package/dist/vault/instrumentation.test.d.ts +2 -0
  245. package/dist/vault/instrumentation.test.d.ts.map +1 -0
  246. package/dist/vault/instrumentation.test.js +80 -0
  247. package/dist/vault/instrumentation.test.js.map +1 -0
  248. package/package.json +4 -1
  249. package/src/advisory/context.test.ts +109 -0
  250. package/src/advisory/evaluator.test.ts +192 -0
  251. package/src/advisory/executor.test.ts +272 -0
  252. package/src/advisory/registry.test.ts +75 -0
  253. package/src/bridge/a2a/agent-card.test.ts +140 -0
  254. package/src/bridge/a2a/mapper.test.ts +293 -0
  255. package/src/bridge/a2a/message.test.ts +138 -0
  256. package/src/bridge/a2a/task.test.ts +235 -0
  257. package/src/bridge/mcp/connector/adapter.test.ts +230 -0
  258. package/src/bridge/sse/mapper.test.ts +422 -0
  259. package/src/bridge/tools/connector/adapter.test.ts +224 -0
  260. package/src/bridge/tools/connector/definitions.test.ts +183 -0
  261. package/src/bridge/tools/connector/router.test.ts +159 -0
  262. package/src/bus/breaker.test.ts +274 -0
  263. package/src/bus/index.test.ts +183 -0
  264. package/src/bus/index.ts +21 -10
  265. package/src/bus/lock.test.ts +265 -0
  266. package/src/bus/ownership.test.ts +243 -0
  267. package/src/connector/BaseConnector.test.ts +130 -0
  268. package/src/connector/builtins/http.test.ts +290 -0
  269. package/src/connector/builtins/webhook.test.ts +138 -0
  270. package/src/connector/execution/factory.test.ts +75 -0
  271. package/src/connector/execution/remote.test.ts +63 -0
  272. package/src/connector/mcp/adapter.test.ts +249 -0
  273. package/src/probe/context.ts +14 -0
  274. package/src/probe/errors.ts +27 -0
  275. package/src/probe/index.ts +4 -0
  276. package/src/probe/registry.test.ts +480 -0
  277. package/src/probe/registry.ts +276 -0
  278. package/src/provider/instrumentation.test.ts +192 -0
  279. package/src/provider/instrumentation.ts +139 -0
  280. package/src/public-runtime.ts +17 -0
  281. package/src/public-types.ts +3 -0
  282. package/src/rag/chunking.test.ts +107 -0
  283. package/src/rag/context-assembler.test.ts +114 -0
  284. package/src/rag/embedding.test.ts +130 -0
  285. package/src/rag/ingestion.test.ts +114 -0
  286. package/src/rag/knowledge-base.test.ts +106 -0
  287. package/src/rag/rag-tool.test.ts +167 -0
  288. package/src/rag/retriever.test.ts +210 -0
  289. package/src/rag/vector-store.test.ts +196 -0
  290. package/src/registry/ManagedRegistry.test.ts +118 -0
  291. package/src/registry/Registry.test.ts +91 -0
  292. package/src/registry/agent/definitions.test.ts +100 -0
  293. package/src/registry/connector/definitions.test.ts +51 -0
  294. package/src/registry/connector/scoped.test.ts +129 -0
  295. package/src/registry/plugin/index.test.ts +85 -0
  296. package/src/registry/tool/execute.test.ts +330 -0
  297. package/src/runtime/query/events.ts +6 -1
  298. package/src/runtime/query/executor.ts +34 -0
  299. package/src/runtime/query/iteration/phases/advisory.test.ts +412 -0
  300. package/src/test-setup.ts +24 -0
  301. package/src/types/bus/index.ts +54 -2
  302. package/src/types/doctor/check.ts +53 -0
  303. package/src/types/doctor/index.ts +9 -0
  304. package/src/types/probe/event-kind.ts +8 -0
  305. package/src/types/probe/event-of.ts +3 -0
  306. package/src/types/probe/index.ts +11 -0
  307. package/src/types/probe/registry.ts +36 -0
  308. package/src/utils/logger.ts +6 -1
  309. package/src/vault/instrumentation.test.ts +98 -0
  310. package/src/vault/instrumentation.ts +56 -0
@@ -0,0 +1,480 @@
1
+ /**
2
+ * Ratified §9 of docs.local/sessions/ses_007-probe-and-doctor/design.md.
3
+ * These tests pin the contract documented there — not an internal
4
+ * implementation detail. If the semantics change, update §9 first.
5
+ */
6
+
7
+ import { describe, expect, it, vi } from 'vitest'
8
+
9
+ import type { Logger } from '../utils/logger.js'
10
+
11
+ import { buildProbeContext } from './context.js'
12
+ import { ProbeNameCollisionError } from './errors.js'
13
+ import { createProbeRegistry } from './registry.js'
14
+
15
+ function makeLogger(): Logger {
16
+ const self = {
17
+ info: vi.fn(),
18
+ warn: vi.fn(),
19
+ error: vi.fn(),
20
+ debug: vi.fn(),
21
+ child: vi.fn(),
22
+ } as unknown as Logger
23
+ ;(self as { child: (ctx: unknown) => Logger }).child = vi.fn(() => self)
24
+ return self
25
+ }
26
+
27
+ describe('ProbeRegistry — typed dispatch', () => {
28
+ it('fires a typed probe when the event kind matches', () => {
29
+ const reg = createProbeRegistry()
30
+ const seen: string[] = []
31
+ reg.on('tool_executing', (event) => {
32
+ seen.push(event.toolName)
33
+ })
34
+ reg.dispatch(
35
+ {
36
+ type: 'tool_executing',
37
+ runId: 'run_1' as never,
38
+ toolName: 'fs.read',
39
+ input: {},
40
+ } as never,
41
+ buildProbeContext(),
42
+ )
43
+ expect(seen).toEqual(['fs.read'])
44
+ })
45
+
46
+ it('does not fire a typed probe for a different event kind', () => {
47
+ const reg = createProbeRegistry()
48
+ const handler = vi.fn()
49
+ reg.on('tool_executing', handler)
50
+ reg.dispatch(
51
+ { type: 'tool_completed', runId: 'r' as never, toolName: 't', result: 'ok' } as never,
52
+ buildProbeContext(),
53
+ )
54
+ expect(handler).not.toHaveBeenCalled()
55
+ })
56
+
57
+ it('supports array-of-kinds registration', () => {
58
+ const reg = createProbeRegistry()
59
+ const seen: string[] = []
60
+ reg.on(['tool_executing', 'tool_completed'], (event) => {
61
+ seen.push(event.type)
62
+ })
63
+ reg.dispatch(
64
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
65
+ buildProbeContext(),
66
+ )
67
+ reg.dispatch(
68
+ { type: 'tool_completed', runId: 'r' as never, toolName: 't', result: 'ok' } as never,
69
+ buildProbeContext(),
70
+ )
71
+ expect(seen).toEqual(['tool_executing', 'tool_completed'])
72
+ })
73
+
74
+ it('applies the where filter before the handler', () => {
75
+ const reg = createProbeRegistry()
76
+ const handler = vi.fn()
77
+ reg.on('tool_executing', handler, {
78
+ where: (event) => event.toolName === 'fs.write',
79
+ })
80
+ reg.dispatch(
81
+ { type: 'tool_executing', runId: 'r' as never, toolName: 'fs.read', input: {} } as never,
82
+ buildProbeContext(),
83
+ )
84
+ expect(handler).not.toHaveBeenCalled()
85
+ reg.dispatch(
86
+ { type: 'tool_executing', runId: 'r' as never, toolName: 'fs.write', input: {} } as never,
87
+ buildProbeContext(),
88
+ )
89
+ expect(handler).toHaveBeenCalledTimes(1)
90
+ })
91
+ })
92
+
93
+ describe('ProbeRegistry — ordering', () => {
94
+ it('fires probes in ascending priority; ties break by registration order', () => {
95
+ const reg = createProbeRegistry()
96
+ const order: string[] = []
97
+ reg.on('tool_executing', () => order.push('a'), { priority: 10, name: 'a' })
98
+ reg.on('tool_executing', () => order.push('b'), { priority: 5, name: 'b' })
99
+ reg.on('tool_executing', () => order.push('c'), { priority: 10, name: 'c' })
100
+ reg.on('tool_executing', () => order.push('d'), { priority: 0, name: 'd' })
101
+ reg.dispatch(
102
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
103
+ buildProbeContext(),
104
+ )
105
+ expect(order).toEqual(['d', 'b', 'a', 'c'])
106
+ })
107
+
108
+ it('typed probes fire BEFORE the between-tier callback; catch-all fires AFTER', () => {
109
+ const reg = createProbeRegistry()
110
+ const order: string[] = []
111
+ reg.on('tool_executing', () => order.push('typed'))
112
+ reg.onAny(() => order.push('any'))
113
+ reg.dispatch(
114
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
115
+ buildProbeContext(),
116
+ () => order.push('between'),
117
+ )
118
+ expect(order).toEqual(['typed', 'between', 'any'])
119
+ })
120
+ })
121
+
122
+ describe('ProbeRegistry — name collision + override', () => {
123
+ it('throws ProbeNameCollisionError on duplicate name without override', () => {
124
+ const reg = createProbeRegistry()
125
+ reg.on('tool_executing', () => {}, { name: 'dup' })
126
+ expect(() => reg.on('tool_executing', () => {}, { name: 'dup' })).toThrow(
127
+ ProbeNameCollisionError,
128
+ )
129
+ })
130
+
131
+ it('allows replacing when override: true', () => {
132
+ const reg = createProbeRegistry()
133
+ const first = vi.fn()
134
+ const second = vi.fn()
135
+ reg.on('tool_executing', first, { name: 'x' })
136
+ reg.on('tool_executing', second, { name: 'x', override: true })
137
+ reg.dispatch(
138
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
139
+ buildProbeContext(),
140
+ )
141
+ expect(first).not.toHaveBeenCalled()
142
+ expect(second).toHaveBeenCalledTimes(1)
143
+ })
144
+
145
+ it('unsubscribes the previous entry on override so the name can be reused', () => {
146
+ const reg = createProbeRegistry()
147
+ reg.on('tool_executing', () => {}, { name: 'x' })
148
+ reg.on('tool_executing', () => {}, { name: 'x', override: true })
149
+ expect(() => reg.on('tool_executing', () => {}, { name: 'x' })).toThrow(ProbeNameCollisionError)
150
+ })
151
+ })
152
+
153
+ describe('ProbeRegistry — throw isolation', () => {
154
+ it('a throwing probe does not suppress later probes', () => {
155
+ const reg = createProbeRegistry()
156
+ reg.setLogger(makeLogger())
157
+ const seen: string[] = []
158
+ reg.on(
159
+ 'tool_executing',
160
+ () => {
161
+ throw new Error('boom')
162
+ },
163
+ { priority: 0, name: 'bad' },
164
+ )
165
+ reg.on('tool_executing', () => seen.push('ran'), { priority: 10, name: 'good' })
166
+ reg.dispatch(
167
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
168
+ buildProbeContext(),
169
+ )
170
+ expect(seen).toEqual(['ran'])
171
+ })
172
+
173
+ it('a throwing probe does not suppress the between-tier callback or the catch-all', () => {
174
+ const reg = createProbeRegistry()
175
+ reg.setLogger(makeLogger())
176
+ const order: string[] = []
177
+ reg.on(
178
+ 'tool_executing',
179
+ () => {
180
+ throw new Error('boom')
181
+ },
182
+ { name: 'bad' },
183
+ )
184
+ reg.onAny(() => order.push('any'))
185
+ reg.dispatch(
186
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
187
+ buildProbeContext(),
188
+ () => order.push('between'),
189
+ )
190
+ expect(order).toEqual(['between', 'any'])
191
+ })
192
+ })
193
+
194
+ describe('ProbeRegistry — frozen event boundary', () => {
195
+ it('freezes the event before fan-out so probes cannot mutate it', () => {
196
+ const reg = createProbeRegistry()
197
+ reg.on('tool_executing', (event) => {
198
+ expect(() => {
199
+ ;(event as { toolName: string }).toolName = 'mutated'
200
+ }).toThrow()
201
+ })
202
+ reg.dispatch(
203
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
204
+ buildProbeContext(),
205
+ )
206
+ })
207
+
208
+ it('later probes see the original event value, not a mutation from an earlier probe', () => {
209
+ const reg = createProbeRegistry()
210
+ reg.setLogger(makeLogger())
211
+ const tampered: string[] = []
212
+ reg.on(
213
+ 'tool_executing',
214
+ (event) => {
215
+ try {
216
+ ;(event as { toolName: string }).toolName = 'mutated'
217
+ } catch {
218
+ // expected — frozen
219
+ }
220
+ },
221
+ { priority: 0 },
222
+ )
223
+ reg.on(
224
+ 'tool_executing',
225
+ (event) => {
226
+ tampered.push(event.toolName)
227
+ },
228
+ { priority: 10 },
229
+ )
230
+ reg.dispatch(
231
+ { type: 'tool_executing', runId: 'r' as never, toolName: 'original', input: {} } as never,
232
+ buildProbeContext(),
233
+ )
234
+ expect(tampered).toEqual(['original'])
235
+ })
236
+ })
237
+
238
+ describe('ProbeRegistry — unsubscribe', () => {
239
+ it('unsub removes the probe from subsequent dispatches', () => {
240
+ const reg = createProbeRegistry()
241
+ const handler = vi.fn()
242
+ const unsub = reg.on('tool_executing', handler)
243
+ reg.dispatch(
244
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
245
+ buildProbeContext(),
246
+ )
247
+ unsub()
248
+ reg.dispatch(
249
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
250
+ buildProbeContext(),
251
+ )
252
+ expect(handler).toHaveBeenCalledTimes(1)
253
+ })
254
+
255
+ it('unsub on an array-kind probe removes it from every kind it was registered for', () => {
256
+ const reg = createProbeRegistry()
257
+ const handler = vi.fn()
258
+ const unsub = reg.on(['tool_executing', 'tool_completed'], handler)
259
+ unsub()
260
+ reg.dispatch(
261
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
262
+ buildProbeContext(),
263
+ )
264
+ reg.dispatch(
265
+ { type: 'tool_completed', runId: 'r' as never, toolName: 't', result: 'ok' } as never,
266
+ buildProbeContext(),
267
+ )
268
+ expect(handler).not.toHaveBeenCalled()
269
+ })
270
+ })
271
+
272
+ describe('ProbeRegistry — catch-all', () => {
273
+ it('onAny receives every event regardless of kind', () => {
274
+ const reg = createProbeRegistry()
275
+ const seen: string[] = []
276
+ reg.onAny((event) => seen.push(event.type))
277
+ reg.dispatch(
278
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
279
+ buildProbeContext(),
280
+ )
281
+ reg.dispatch(
282
+ { type: 'lock_acquired', lockId: 'lock_1' as never, filePath: '/x', owner: 'r' as never },
283
+ buildProbeContext(),
284
+ )
285
+ expect(seen).toEqual(['tool_executing', 'lock_acquired'])
286
+ })
287
+ })
288
+
289
+ describe('ProbeRegistry — ctx.isReplay', () => {
290
+ it('defaults to false when unset', () => {
291
+ const reg = createProbeRegistry()
292
+ let seenReplay: boolean | undefined
293
+ reg.on('tool_executing', (_event, ctx) => {
294
+ seenReplay = ctx.isReplay
295
+ })
296
+ reg.dispatch(
297
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
298
+ buildProbeContext(),
299
+ )
300
+ expect(seenReplay).toBe(false)
301
+ })
302
+
303
+ it('carries isReplay:true when the event is from a replayed run', () => {
304
+ const reg = createProbeRegistry()
305
+ let seenReplay: boolean | undefined
306
+ reg.on('tool_executing', (_event, ctx) => {
307
+ seenReplay = ctx.isReplay
308
+ })
309
+ reg.dispatch(
310
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
311
+ buildProbeContext({ isReplay: true }),
312
+ )
313
+ expect(seenReplay).toBe(true)
314
+ })
315
+ })
316
+
317
+ describe('ProbeRegistry — veto API', () => {
318
+ it('returns allow when no veto handlers registered', () => {
319
+ const reg = createProbeRegistry()
320
+ const outcome = reg.queryVeto(
321
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
322
+ buildProbeContext(),
323
+ )
324
+ expect(outcome.action).toBe('allow')
325
+ expect(outcome.probeName).toBeUndefined()
326
+ })
327
+
328
+ it('returns deny when a veto handler returns "deny"', () => {
329
+ const reg = createProbeRegistry()
330
+ reg.veto('tool_executing', () => 'deny', { name: 'fs-guard' })
331
+ const outcome = reg.queryVeto(
332
+ { type: 'tool_executing', runId: 'r' as never, toolName: 'fs.write', input: {} } as never,
333
+ buildProbeContext(),
334
+ )
335
+ expect(outcome.action).toBe('deny')
336
+ expect(outcome.probeName).toBe('fs-guard')
337
+ })
338
+
339
+ it('returns deny + reason when handler returns { action: "deny", reason }', () => {
340
+ const reg = createProbeRegistry()
341
+ reg.veto('tool_executing', () => ({ action: 'deny', reason: 'outside workspace' }), {
342
+ name: 'sandbox',
343
+ })
344
+ const outcome = reg.queryVeto(
345
+ { type: 'tool_executing', runId: 'r' as never, toolName: 'fs.write', input: {} } as never,
346
+ buildProbeContext(),
347
+ )
348
+ expect(outcome.action).toBe('deny')
349
+ expect(outcome.probeName).toBe('sandbox')
350
+ expect(outcome.reason).toBe('outside workspace')
351
+ })
352
+
353
+ it('first-deny-wins by ascending priority; subsequent veto handlers still run for audit', () => {
354
+ const reg = createProbeRegistry()
355
+ const audit: string[] = []
356
+ reg.veto(
357
+ 'tool_executing',
358
+ () => {
359
+ audit.push('high-priority-allow')
360
+ return 'allow'
361
+ },
362
+ { priority: 0, name: 'p0-allow' },
363
+ )
364
+ reg.veto(
365
+ 'tool_executing',
366
+ () => {
367
+ audit.push('mid-priority-deny')
368
+ return { action: 'deny', reason: 'first' }
369
+ },
370
+ { priority: 5, name: 'p5-deny' },
371
+ )
372
+ reg.veto(
373
+ 'tool_executing',
374
+ () => {
375
+ audit.push('low-priority-deny')
376
+ return { action: 'deny', reason: 'second' }
377
+ },
378
+ { priority: 10, name: 'p10-deny' },
379
+ )
380
+
381
+ const outcome = reg.queryVeto(
382
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
383
+ buildProbeContext(),
384
+ )
385
+ expect(outcome.action).toBe('deny')
386
+ expect(outcome.probeName).toBe('p5-deny')
387
+ expect(outcome.reason).toBe('first')
388
+ expect(audit).toEqual(['high-priority-allow', 'mid-priority-deny', 'low-priority-deny'])
389
+ })
390
+
391
+ it('a throwing veto handler defaults to allow for that probe; aggregate unaffected', () => {
392
+ const reg = createProbeRegistry()
393
+ reg.setLogger(makeLogger())
394
+ const audit: string[] = []
395
+ reg.veto(
396
+ 'tool_executing',
397
+ () => {
398
+ audit.push('throw')
399
+ throw new Error('boom')
400
+ },
401
+ { priority: 0, name: 'bad' },
402
+ )
403
+ reg.veto(
404
+ 'tool_executing',
405
+ () => {
406
+ audit.push('allow')
407
+ return 'allow'
408
+ },
409
+ { priority: 5, name: 'good' },
410
+ )
411
+
412
+ const outcome = reg.queryVeto(
413
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
414
+ buildProbeContext(),
415
+ )
416
+ expect(outcome.action).toBe('allow')
417
+ expect(audit).toEqual(['throw', 'allow'])
418
+ })
419
+
420
+ it('observe-tier dispatch is independent — veto registration does not fire on dispatch', () => {
421
+ const reg = createProbeRegistry()
422
+ const vetoCalls: number[] = []
423
+ reg.veto(
424
+ 'tool_executing',
425
+ () => {
426
+ vetoCalls.push(1)
427
+ return 'deny'
428
+ },
429
+ { name: 'v' },
430
+ )
431
+ reg.dispatch(
432
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
433
+ buildProbeContext(),
434
+ )
435
+ expect(vetoCalls).toEqual([])
436
+ })
437
+
438
+ it('honors where filter on veto registrations', () => {
439
+ const reg = createProbeRegistry()
440
+ reg.veto('tool_executing', () => 'deny', {
441
+ name: 'writes-only',
442
+ where: (event) => event.toolName.startsWith('fs.write'),
443
+ })
444
+
445
+ const allow = reg.queryVeto(
446
+ { type: 'tool_executing', runId: 'r' as never, toolName: 'fs.read', input: {} } as never,
447
+ buildProbeContext(),
448
+ )
449
+ const deny = reg.queryVeto(
450
+ { type: 'tool_executing', runId: 'r' as never, toolName: 'fs.write.x', input: {} } as never,
451
+ buildProbeContext(),
452
+ )
453
+ expect(allow.action).toBe('allow')
454
+ expect(deny.action).toBe('deny')
455
+ })
456
+
457
+ it('unsubscribing a veto handler removes it from subsequent queries', () => {
458
+ const reg = createProbeRegistry()
459
+ const unsub = reg.veto('tool_executing', () => 'deny', { name: 'g' })
460
+ const denied = reg.queryVeto(
461
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
462
+ buildProbeContext(),
463
+ )
464
+ expect(denied.action).toBe('deny')
465
+ unsub()
466
+ const allowed = reg.queryVeto(
467
+ { type: 'tool_executing', runId: 'r' as never, toolName: 't', input: {} } as never,
468
+ buildProbeContext(),
469
+ )
470
+ expect(allowed.action).toBe('allow')
471
+ })
472
+
473
+ it('name collision applies across observe + veto tiers', () => {
474
+ const reg = createProbeRegistry()
475
+ reg.on('tool_executing', () => {}, { name: 'shared' })
476
+ expect(() => reg.veto('tool_executing', () => 'allow', { name: 'shared' })).toThrow(
477
+ ProbeNameCollisionError,
478
+ )
479
+ })
480
+ })