@principal-ai/subsystems-core 0.29.0 → 0.31.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 (558) hide show
  1. package/README.md +15 -110
  2. package/dist/index.d.ts +7 -75
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +21 -205
  5. package/dist/index.js.map +1 -1
  6. package/dist/node.d.ts +5 -61
  7. package/dist/node.d.ts.map +1 -1
  8. package/dist/node.js +9 -131
  9. package/dist/node.js.map +1 -1
  10. package/dist/types/index.d.ts +3 -348
  11. package/dist/types/index.d.ts.map +1 -1
  12. package/dist/types/index.js +3 -19
  13. package/dist/types/index.js.map +1 -1
  14. package/dist/types/subsystem-model.d.ts +34 -3
  15. package/dist/types/subsystem-model.d.ts.map +1 -1
  16. package/dist/types/subsystem-model.js.map +1 -1
  17. package/package.json +7 -24
  18. package/schemas/subsystem-model.schema.json +52 -1
  19. package/src/index.ts +7 -580
  20. package/src/node.ts +21 -262
  21. package/src/types/index.ts +3 -456
  22. package/src/types/subsystem-model.ts +39 -3
  23. package/dist/ConfigurationLoader.d.ts +0 -76
  24. package/dist/ConfigurationLoader.d.ts.map +0 -1
  25. package/dist/ConfigurationLoader.js +0 -148
  26. package/dist/ConfigurationLoader.js.map +0 -1
  27. package/dist/ConfigurationValidator.d.ts +0 -31
  28. package/dist/ConfigurationValidator.d.ts.map +0 -1
  29. package/dist/ConfigurationValidator.js +0 -239
  30. package/dist/ConfigurationValidator.js.map +0 -1
  31. package/dist/EventProcessor.d.ts +0 -49
  32. package/dist/EventProcessor.d.ts.map +0 -1
  33. package/dist/EventProcessor.js +0 -220
  34. package/dist/EventProcessor.js.map +0 -1
  35. package/dist/EventRecorderService.d.ts +0 -305
  36. package/dist/EventRecorderService.d.ts.map +0 -1
  37. package/dist/EventRecorderService.js +0 -467
  38. package/dist/EventRecorderService.js.map +0 -1
  39. package/dist/LibraryLoader.d.ts +0 -65
  40. package/dist/LibraryLoader.d.ts.map +0 -1
  41. package/dist/LibraryLoader.js +0 -197
  42. package/dist/LibraryLoader.js.map +0 -1
  43. package/dist/PathBasedEventProcessor.d.ts +0 -77
  44. package/dist/PathBasedEventProcessor.d.ts.map +0 -1
  45. package/dist/PathBasedEventProcessor.js +0 -136
  46. package/dist/PathBasedEventProcessor.js.map +0 -1
  47. package/dist/SessionManager.d.ts +0 -194
  48. package/dist/SessionManager.d.ts.map +0 -1
  49. package/dist/SessionManager.js +0 -302
  50. package/dist/SessionManager.js.map +0 -1
  51. package/dist/ValidationEngine.d.ts +0 -31
  52. package/dist/ValidationEngine.d.ts.map +0 -1
  53. package/dist/ValidationEngine.js +0 -162
  54. package/dist/ValidationEngine.js.map +0 -1
  55. package/dist/auxiliary/AuxiliaryManifestValidator.d.ts +0 -41
  56. package/dist/auxiliary/AuxiliaryManifestValidator.d.ts.map +0 -1
  57. package/dist/auxiliary/AuxiliaryManifestValidator.js +0 -100
  58. package/dist/auxiliary/AuxiliaryManifestValidator.js.map +0 -1
  59. package/dist/auxiliary/index.d.ts +0 -9
  60. package/dist/auxiliary/index.d.ts.map +0 -1
  61. package/dist/auxiliary/index.js +0 -14
  62. package/dist/auxiliary/index.js.map +0 -1
  63. package/dist/auxiliary/validateAreaScopeDisjoint.d.ts +0 -24
  64. package/dist/auxiliary/validateAreaScopeDisjoint.d.ts.map +0 -1
  65. package/dist/auxiliary/validateAreaScopeDisjoint.js +0 -61
  66. package/dist/auxiliary/validateAreaScopeDisjoint.js.map +0 -1
  67. package/dist/cli/codegen.d.ts +0 -41
  68. package/dist/cli/codegen.d.ts.map +0 -1
  69. package/dist/cli/codegen.js +0 -257
  70. package/dist/cli/codegen.js.map +0 -1
  71. package/dist/codegen/type-generator.d.ts +0 -123
  72. package/dist/codegen/type-generator.d.ts.map +0 -1
  73. package/dist/codegen/type-generator.js +0 -433
  74. package/dist/codegen/type-generator.js.map +0 -1
  75. package/dist/codegen/usage-example.d.ts +0 -103
  76. package/dist/codegen/usage-example.d.ts.map +0 -1
  77. package/dist/codegen/usage-example.js +0 -258
  78. package/dist/codegen/usage-example.js.map +0 -1
  79. package/dist/dashboard/DashboardValidator.d.ts +0 -85
  80. package/dist/dashboard/DashboardValidator.d.ts.map +0 -1
  81. package/dist/dashboard/DashboardValidator.js +0 -775
  82. package/dist/dashboard/DashboardValidator.js.map +0 -1
  83. package/dist/dashboard/index.d.ts +0 -6
  84. package/dist/dashboard/index.d.ts.map +0 -1
  85. package/dist/dashboard/index.js +0 -10
  86. package/dist/dashboard/index.js.map +0 -1
  87. package/dist/discovery/CanvasDiscovery.d.ts +0 -160
  88. package/dist/discovery/CanvasDiscovery.d.ts.map +0 -1
  89. package/dist/discovery/CanvasDiscovery.js +0 -865
  90. package/dist/discovery/CanvasDiscovery.js.map +0 -1
  91. package/dist/discovery/CanvasFileManifest.d.ts +0 -86
  92. package/dist/discovery/CanvasFileManifest.d.ts.map +0 -1
  93. package/dist/discovery/CanvasFileManifest.js +0 -338
  94. package/dist/discovery/CanvasFileManifest.js.map +0 -1
  95. package/dist/discovery/LibraryDiscovery.d.ts +0 -108
  96. package/dist/discovery/LibraryDiscovery.d.ts.map +0 -1
  97. package/dist/discovery/LibraryDiscovery.js +0 -263
  98. package/dist/discovery/LibraryDiscovery.js.map +0 -1
  99. package/dist/discovery/types.d.ts +0 -403
  100. package/dist/discovery/types.d.ts.map +0 -1
  101. package/dist/discovery/types.js +0 -6
  102. package/dist/discovery/types.js.map +0 -1
  103. package/dist/events/EventsCanvasValidator.d.ts +0 -139
  104. package/dist/events/EventsCanvasValidator.d.ts.map +0 -1
  105. package/dist/events/EventsCanvasValidator.js +0 -281
  106. package/dist/events/EventsCanvasValidator.js.map +0 -1
  107. package/dist/events/NamespacePathIndex.d.ts +0 -61
  108. package/dist/events/NamespacePathIndex.d.ts.map +0 -1
  109. package/dist/events/NamespacePathIndex.js +0 -74
  110. package/dist/events/NamespacePathIndex.js.map +0 -1
  111. package/dist/events/OtelEventPathsValidator.d.ts +0 -75
  112. package/dist/events/OtelEventPathsValidator.d.ts.map +0 -1
  113. package/dist/events/OtelEventPathsValidator.js +0 -126
  114. package/dist/events/OtelEventPathsValidator.js.map +0 -1
  115. package/dist/events/ScopeEventsValidator.d.ts +0 -68
  116. package/dist/events/ScopeEventsValidator.d.ts.map +0 -1
  117. package/dist/events/ScopeEventsValidator.js +0 -91
  118. package/dist/events/ScopeEventsValidator.js.map +0 -1
  119. package/dist/events/index.d.ts +0 -9
  120. package/dist/events/index.d.ts.map +0 -1
  121. package/dist/events/index.js +0 -12
  122. package/dist/events/index.js.map +0 -1
  123. package/dist/events/path-helpers.d.ts +0 -33
  124. package/dist/events/path-helpers.d.ts.map +0 -1
  125. package/dist/events/path-helpers.js +0 -59
  126. package/dist/events/path-helpers.js.map +0 -1
  127. package/dist/execution/ExecutionLoader.d.ts +0 -135
  128. package/dist/execution/ExecutionLoader.d.ts.map +0 -1
  129. package/dist/execution/ExecutionLoader.js +0 -266
  130. package/dist/execution/ExecutionLoader.js.map +0 -1
  131. package/dist/execution/ExecutionValidator.d.ts +0 -94
  132. package/dist/execution/ExecutionValidator.d.ts.map +0 -1
  133. package/dist/execution/ExecutionValidator.js +0 -503
  134. package/dist/execution/ExecutionValidator.js.map +0 -1
  135. package/dist/generated/graph-converter-execution.types.d.ts +0 -177
  136. package/dist/generated/graph-converter-execution.types.d.ts.map +0 -1
  137. package/dist/generated/graph-converter-execution.types.js +0 -12
  138. package/dist/generated/graph-converter-execution.types.js.map +0 -1
  139. package/dist/helpers/GraphInstrumentationHelper.d.ts +0 -93
  140. package/dist/helpers/GraphInstrumentationHelper.d.ts.map +0 -1
  141. package/dist/helpers/GraphInstrumentationHelper.js +0 -252
  142. package/dist/helpers/GraphInstrumentationHelper.js.map +0 -1
  143. package/dist/matchers/SpanMatcher.d.ts +0 -107
  144. package/dist/matchers/SpanMatcher.d.ts.map +0 -1
  145. package/dist/matchers/SpanMatcher.js +0 -274
  146. package/dist/matchers/SpanMatcher.js.map +0 -1
  147. package/dist/orchestration/TraceOrchestrator.d.ts +0 -71
  148. package/dist/orchestration/TraceOrchestrator.d.ts.map +0 -1
  149. package/dist/orchestration/TraceOrchestrator.js +0 -416
  150. package/dist/orchestration/TraceOrchestrator.js.map +0 -1
  151. package/dist/parsers/OtlpTraceParser.d.ts +0 -131
  152. package/dist/parsers/OtlpTraceParser.d.ts.map +0 -1
  153. package/dist/parsers/OtlpTraceParser.js +0 -310
  154. package/dist/parsers/OtlpTraceParser.js.map +0 -1
  155. package/dist/registry/CompositeRegistry.d.ts +0 -71
  156. package/dist/registry/CompositeRegistry.d.ts.map +0 -1
  157. package/dist/registry/CompositeRegistry.js +0 -113
  158. package/dist/registry/CompositeRegistry.js.map +0 -1
  159. package/dist/registry/EventRegistry.d.ts +0 -79
  160. package/dist/registry/EventRegistry.d.ts.map +0 -1
  161. package/dist/registry/EventRegistry.js +0 -165
  162. package/dist/registry/EventRegistry.js.map +0 -1
  163. package/dist/registry/LocalRegistry.d.ts +0 -111
  164. package/dist/registry/LocalRegistry.d.ts.map +0 -1
  165. package/dist/registry/LocalRegistry.js +0 -335
  166. package/dist/registry/LocalRegistry.js.map +0 -1
  167. package/dist/registry/RemoteRegistry.d.ts +0 -122
  168. package/dist/registry/RemoteRegistry.d.ts.map +0 -1
  169. package/dist/registry/RemoteRegistry.js +0 -399
  170. package/dist/registry/RemoteRegistry.js.map +0 -1
  171. package/dist/registry/VersionRegistry.d.ts +0 -51
  172. package/dist/registry/VersionRegistry.d.ts.map +0 -1
  173. package/dist/registry/VersionRegistry.js +0 -105
  174. package/dist/registry/VersionRegistry.js.map +0 -1
  175. package/dist/rules/config.d.ts +0 -57
  176. package/dist/rules/config.d.ts.map +0 -1
  177. package/dist/rules/config.js +0 -382
  178. package/dist/rules/config.js.map +0 -1
  179. package/dist/rules/engine.d.ts +0 -70
  180. package/dist/rules/engine.d.ts.map +0 -1
  181. package/dist/rules/engine.js +0 -373
  182. package/dist/rules/engine.js.map +0 -1
  183. package/dist/rules/implementations/connection-type-references.d.ts +0 -7
  184. package/dist/rules/implementations/connection-type-references.d.ts.map +0 -1
  185. package/dist/rules/implementations/connection-type-references.js +0 -107
  186. package/dist/rules/implementations/connection-type-references.js.map +0 -1
  187. package/dist/rules/implementations/dead-end-states.d.ts +0 -17
  188. package/dist/rules/implementations/dead-end-states.d.ts.map +0 -1
  189. package/dist/rules/implementations/dead-end-states.js +0 -75
  190. package/dist/rules/implementations/dead-end-states.js.map +0 -1
  191. package/dist/rules/implementations/index.d.ts +0 -23
  192. package/dist/rules/implementations/index.d.ts.map +0 -1
  193. package/dist/rules/implementations/index.js +0 -73
  194. package/dist/rules/implementations/index.js.map +0 -1
  195. package/dist/rules/implementations/minimum-node-sources.d.ts +0 -24
  196. package/dist/rules/implementations/minimum-node-sources.d.ts.map +0 -1
  197. package/dist/rules/implementations/minimum-node-sources.js +0 -59
  198. package/dist/rules/implementations/minimum-node-sources.js.map +0 -1
  199. package/dist/rules/implementations/no-unknown-fields.d.ts +0 -7
  200. package/dist/rules/implementations/no-unknown-fields.d.ts.map +0 -1
  201. package/dist/rules/implementations/no-unknown-fields.js +0 -216
  202. package/dist/rules/implementations/no-unknown-fields.js.map +0 -1
  203. package/dist/rules/implementations/orphaned-edge-types.d.ts +0 -7
  204. package/dist/rules/implementations/orphaned-edge-types.d.ts.map +0 -1
  205. package/dist/rules/implementations/orphaned-edge-types.js +0 -50
  206. package/dist/rules/implementations/orphaned-edge-types.js.map +0 -1
  207. package/dist/rules/implementations/orphaned-node-types.d.ts +0 -7
  208. package/dist/rules/implementations/orphaned-node-types.d.ts.map +0 -1
  209. package/dist/rules/implementations/orphaned-node-types.js +0 -53
  210. package/dist/rules/implementations/orphaned-node-types.js.map +0 -1
  211. package/dist/rules/implementations/required-metadata.d.ts +0 -7
  212. package/dist/rules/implementations/required-metadata.d.ts.map +0 -1
  213. package/dist/rules/implementations/required-metadata.js +0 -48
  214. package/dist/rules/implementations/required-metadata.js.map +0 -1
  215. package/dist/rules/implementations/state-transition-references.d.ts +0 -7
  216. package/dist/rules/implementations/state-transition-references.d.ts.map +0 -1
  217. package/dist/rules/implementations/state-transition-references.js +0 -138
  218. package/dist/rules/implementations/state-transition-references.js.map +0 -1
  219. package/dist/rules/implementations/unreachable-states.d.ts +0 -7
  220. package/dist/rules/implementations/unreachable-states.d.ts.map +0 -1
  221. package/dist/rules/implementations/unreachable-states.js +0 -83
  222. package/dist/rules/implementations/unreachable-states.js.map +0 -1
  223. package/dist/rules/implementations/valid-action-patterns.d.ts +0 -17
  224. package/dist/rules/implementations/valid-action-patterns.d.ts.map +0 -1
  225. package/dist/rules/implementations/valid-action-patterns.js +0 -112
  226. package/dist/rules/implementations/valid-action-patterns.js.map +0 -1
  227. package/dist/rules/implementations/valid-color-format.d.ts +0 -7
  228. package/dist/rules/implementations/valid-color-format.d.ts.map +0 -1
  229. package/dist/rules/implementations/valid-color-format.js +0 -94
  230. package/dist/rules/implementations/valid-color-format.js.map +0 -1
  231. package/dist/rules/implementations/valid-edge-types.d.ts +0 -7
  232. package/dist/rules/implementations/valid-edge-types.d.ts.map +0 -1
  233. package/dist/rules/implementations/valid-edge-types.js +0 -248
  234. package/dist/rules/implementations/valid-edge-types.js.map +0 -1
  235. package/dist/rules/implementations/valid-node-types.d.ts +0 -7
  236. package/dist/rules/implementations/valid-node-types.d.ts.map +0 -1
  237. package/dist/rules/implementations/valid-node-types.js +0 -187
  238. package/dist/rules/implementations/valid-node-types.js.map +0 -1
  239. package/dist/rules/index.d.ts +0 -28
  240. package/dist/rules/index.d.ts.map +0 -1
  241. package/dist/rules/index.js +0 -70
  242. package/dist/rules/index.js.map +0 -1
  243. package/dist/rules/types.d.ts +0 -309
  244. package/dist/rules/types.d.ts.map +0 -1
  245. package/dist/rules/types.js +0 -40
  246. package/dist/rules/types.js.map +0 -1
  247. package/dist/scopes/ScopePathIndex.d.ts +0 -56
  248. package/dist/scopes/ScopePathIndex.d.ts.map +0 -1
  249. package/dist/scopes/ScopePathIndex.js +0 -67
  250. package/dist/scopes/ScopePathIndex.js.map +0 -1
  251. package/dist/scopes/ScopesCanvasValidator.d.ts +0 -76
  252. package/dist/scopes/ScopesCanvasValidator.d.ts.map +0 -1
  253. package/dist/scopes/ScopesCanvasValidator.js +0 -221
  254. package/dist/scopes/ScopesCanvasValidator.js.map +0 -1
  255. package/dist/scopes/index.d.ts +0 -12
  256. package/dist/scopes/index.d.ts.map +0 -1
  257. package/dist/scopes/index.js +0 -30
  258. package/dist/scopes/index.js.map +0 -1
  259. package/dist/scopes/utils.d.ts +0 -85
  260. package/dist/scopes/utils.d.ts.map +0 -1
  261. package/dist/scopes/utils.js +0 -143
  262. package/dist/scopes/utils.js.map +0 -1
  263. package/dist/scopes/validateScopeNamespaceNesting.d.ts +0 -38
  264. package/dist/scopes/validateScopeNamespaceNesting.d.ts.map +0 -1
  265. package/dist/scopes/validateScopeNamespaceNesting.js +0 -69
  266. package/dist/scopes/validateScopeNamespaceNesting.js.map +0 -1
  267. package/dist/spans/index.d.ts +0 -12
  268. package/dist/spans/index.d.ts.map +0 -1
  269. package/dist/spans/index.js +0 -25
  270. package/dist/spans/index.js.map +0 -1
  271. package/dist/spans/utils.d.ts +0 -70
  272. package/dist/spans/utils.d.ts.map +0 -1
  273. package/dist/spans/utils.js +0 -205
  274. package/dist/spans/utils.js.map +0 -1
  275. package/dist/storyboard/builder.d.ts +0 -135
  276. package/dist/storyboard/builder.d.ts.map +0 -1
  277. package/dist/storyboard/builder.js +0 -235
  278. package/dist/storyboard/builder.js.map +0 -1
  279. package/dist/storyboard/index.d.ts +0 -9
  280. package/dist/storyboard/index.d.ts.map +0 -1
  281. package/dist/storyboard/index.js +0 -18
  282. package/dist/storyboard/index.js.map +0 -1
  283. package/dist/storyboard/types.d.ts +0 -101
  284. package/dist/storyboard/types.d.ts.map +0 -1
  285. package/dist/storyboard/types.js +0 -10
  286. package/dist/storyboard/types.js.map +0 -1
  287. package/dist/telemetry/coverage.d.ts +0 -92
  288. package/dist/telemetry/coverage.d.ts.map +0 -1
  289. package/dist/telemetry/coverage.js +0 -335
  290. package/dist/telemetry/coverage.js.map +0 -1
  291. package/dist/telemetry/event-validator.d.ts +0 -86
  292. package/dist/telemetry/event-validator.d.ts.map +0 -1
  293. package/dist/telemetry/event-validator.js +0 -257
  294. package/dist/telemetry/event-validator.js.map +0 -1
  295. package/dist/types/audit.d.ts +0 -236
  296. package/dist/types/audit.d.ts.map +0 -1
  297. package/dist/types/audit.js +0 -53
  298. package/dist/types/audit.js.map +0 -1
  299. package/dist/types/auxiliary.d.ts +0 -54
  300. package/dist/types/auxiliary.d.ts.map +0 -1
  301. package/dist/types/auxiliary.js +0 -27
  302. package/dist/types/auxiliary.js.map +0 -1
  303. package/dist/types/canvas-scope.d.ts +0 -156
  304. package/dist/types/canvas-scope.d.ts.map +0 -1
  305. package/dist/types/canvas-scope.js +0 -62
  306. package/dist/types/canvas-scope.js.map +0 -1
  307. package/dist/types/canvas.d.ts +0 -1138
  308. package/dist/types/canvas.d.ts.map +0 -1
  309. package/dist/types/canvas.js +0 -178
  310. package/dist/types/canvas.js.map +0 -1
  311. package/dist/types/dashboard.d.ts +0 -170
  312. package/dist/types/dashboard.d.ts.map +0 -1
  313. package/dist/types/dashboard.js +0 -14
  314. package/dist/types/dashboard.js.map +0 -1
  315. package/dist/types/library.d.ts +0 -227
  316. package/dist/types/library.d.ts.map +0 -1
  317. package/dist/types/library.js +0 -16
  318. package/dist/types/library.js.map +0 -1
  319. package/dist/types/otel.d.ts +0 -465
  320. package/dist/types/otel.d.ts.map +0 -1
  321. package/dist/types/otel.js +0 -163
  322. package/dist/types/otel.js.map +0 -1
  323. package/dist/types/path-based-config.d.ts +0 -235
  324. package/dist/types/path-based-config.d.ts.map +0 -1
  325. package/dist/types/path-based-config.js +0 -11
  326. package/dist/types/path-based-config.js.map +0 -1
  327. package/dist/types/registered-trace.d.ts +0 -337
  328. package/dist/types/registered-trace.d.ts.map +0 -1
  329. package/dist/types/registered-trace.js +0 -16
  330. package/dist/types/registered-trace.js.map +0 -1
  331. package/dist/types/resource-match.d.ts +0 -207
  332. package/dist/types/resource-match.d.ts.map +0 -1
  333. package/dist/types/resource-match.js +0 -74
  334. package/dist/types/resource-match.js.map +0 -1
  335. package/dist/types/version-registry.d.ts +0 -137
  336. package/dist/types/version-registry.d.ts.map +0 -1
  337. package/dist/types/version-registry.js +0 -10
  338. package/dist/types/version-registry.js.map +0 -1
  339. package/dist/utils/CanvasConverter.d.ts +0 -148
  340. package/dist/utils/CanvasConverter.d.ts.map +0 -1
  341. package/dist/utils/CanvasConverter.js +0 -530
  342. package/dist/utils/CanvasConverter.js.map +0 -1
  343. package/dist/utils/GraphConverter.d.ts +0 -18
  344. package/dist/utils/GraphConverter.d.ts.map +0 -1
  345. package/dist/utils/GraphConverter.js +0 -80
  346. package/dist/utils/GraphConverter.js.map +0 -1
  347. package/dist/utils/PathMatcher.d.ts +0 -55
  348. package/dist/utils/PathMatcher.d.ts.map +0 -1
  349. package/dist/utils/PathMatcher.js +0 -178
  350. package/dist/utils/PathMatcher.js.map +0 -1
  351. package/dist/utils/TraceToCanvas.d.ts +0 -83
  352. package/dist/utils/TraceToCanvas.d.ts.map +0 -1
  353. package/dist/utils/TraceToCanvas.js +0 -336
  354. package/dist/utils/TraceToCanvas.js.map +0 -1
  355. package/dist/utils/YamlParser.d.ts +0 -36
  356. package/dist/utils/YamlParser.d.ts.map +0 -1
  357. package/dist/utils/YamlParser.js +0 -92
  358. package/dist/utils/YamlParser.js.map +0 -1
  359. package/dist/utils/registeredTraceHelpers.d.ts +0 -76
  360. package/dist/utils/registeredTraceHelpers.d.ts.map +0 -1
  361. package/dist/utils/registeredTraceHelpers.js +0 -203
  362. package/dist/utils/registeredTraceHelpers.js.map +0 -1
  363. package/dist/utils/traceAggregation.d.ts +0 -25
  364. package/dist/utils/traceAggregation.d.ts.map +0 -1
  365. package/dist/utils/traceAggregation.js +0 -140
  366. package/dist/utils/traceAggregation.js.map +0 -1
  367. package/dist/validation/OtelCanvasValidator.d.ts +0 -40
  368. package/dist/validation/OtelCanvasValidator.d.ts.map +0 -1
  369. package/dist/validation/OtelCanvasValidator.js +0 -59
  370. package/dist/validation/OtelCanvasValidator.js.map +0 -1
  371. package/dist/validation/index.d.ts +0 -8
  372. package/dist/validation/index.d.ts.map +0 -1
  373. package/dist/validation/index.js +0 -13
  374. package/dist/validation/index.js.map +0 -1
  375. package/dist/validation/libraryValidation.d.ts +0 -53
  376. package/dist/validation/libraryValidation.d.ts.map +0 -1
  377. package/dist/validation/libraryValidation.js +0 -115
  378. package/dist/validation/libraryValidation.js.map +0 -1
  379. package/dist/workflow/edge-derivation.d.ts +0 -111
  380. package/dist/workflow/edge-derivation.d.ts.map +0 -1
  381. package/dist/workflow/edge-derivation.js +0 -174
  382. package/dist/workflow/edge-derivation.js.map +0 -1
  383. package/dist/workflow/edge-validation.d.ts +0 -170
  384. package/dist/workflow/edge-validation.d.ts.map +0 -1
  385. package/dist/workflow/edge-validation.js +0 -332
  386. package/dist/workflow/edge-validation.js.map +0 -1
  387. package/dist/workflow/example.d.ts +0 -11
  388. package/dist/workflow/example.d.ts.map +0 -1
  389. package/dist/workflow/example.js +0 -310
  390. package/dist/workflow/example.js.map +0 -1
  391. package/dist/workflow/index.d.ts +0 -20
  392. package/dist/workflow/index.d.ts.map +0 -1
  393. package/dist/workflow/index.js +0 -49
  394. package/dist/workflow/index.js.map +0 -1
  395. package/dist/workflow/scenario-matcher.d.ts +0 -63
  396. package/dist/workflow/scenario-matcher.d.ts.map +0 -1
  397. package/dist/workflow/scenario-matcher.js +0 -215
  398. package/dist/workflow/scenario-matcher.js.map +0 -1
  399. package/dist/workflow/template-parser.d.ts +0 -73
  400. package/dist/workflow/template-parser.d.ts.map +0 -1
  401. package/dist/workflow/template-parser.js +0 -280
  402. package/dist/workflow/template-parser.js.map +0 -1
  403. package/dist/workflow/template-renderer.d.ts +0 -45
  404. package/dist/workflow/template-renderer.d.ts.map +0 -1
  405. package/dist/workflow/template-renderer.js +0 -401
  406. package/dist/workflow/template-renderer.js.map +0 -1
  407. package/dist/workflow/types.d.ts +0 -393
  408. package/dist/workflow/types.d.ts.map +0 -1
  409. package/dist/workflow/types.js +0 -40
  410. package/dist/workflow/types.js.map +0 -1
  411. package/dist/workflow/validator.d.ts +0 -363
  412. package/dist/workflow/validator.d.ts.map +0 -1
  413. package/dist/workflow/validator.js +0 -2283
  414. package/dist/workflow/validator.js.map +0 -1
  415. package/schemas/auxiliary.manifest.schema.json +0 -56
  416. package/src/ConfigurationLoader.test.ts +0 -466
  417. package/src/ConfigurationLoader.ts +0 -184
  418. package/src/ConfigurationValidator.test.ts +0 -200
  419. package/src/ConfigurationValidator.ts +0 -273
  420. package/src/EventProcessor.test.ts +0 -405
  421. package/src/EventProcessor.ts +0 -256
  422. package/src/EventRecorderService.test.ts +0 -543
  423. package/src/EventRecorderService.ts +0 -741
  424. package/src/LibraryLoader.ts +0 -194
  425. package/src/PathBasedEventProcessor.test.ts +0 -336
  426. package/src/PathBasedEventProcessor.ts +0 -196
  427. package/src/SessionManager.test.ts +0 -424
  428. package/src/SessionManager.ts +0 -471
  429. package/src/ValidationEngine.test.ts +0 -520
  430. package/src/ValidationEngine.ts +0 -200
  431. package/src/auxiliary/AuxiliaryManifestValidator.test.ts +0 -93
  432. package/src/auxiliary/AuxiliaryManifestValidator.ts +0 -132
  433. package/src/auxiliary/index.ts +0 -18
  434. package/src/auxiliary/validateAreaScopeDisjoint.test.ts +0 -85
  435. package/src/auxiliary/validateAreaScopeDisjoint.ts +0 -72
  436. package/src/cli/codegen.ts +0 -286
  437. package/src/codegen/type-generator.test.ts +0 -219
  438. package/src/codegen/type-generator.ts +0 -560
  439. package/src/codegen/usage-example.ts +0 -357
  440. package/src/dashboard/DashboardValidator.ts +0 -904
  441. package/src/dashboard/index.ts +0 -14
  442. package/src/discovery/CanvasDiscovery.test.ts +0 -691
  443. package/src/discovery/CanvasDiscovery.ts +0 -1114
  444. package/src/discovery/CanvasFileManifest.test.ts +0 -521
  445. package/src/discovery/CanvasFileManifest.ts +0 -419
  446. package/src/discovery/LibraryDiscovery.test.ts +0 -329
  447. package/src/discovery/LibraryDiscovery.ts +0 -386
  448. package/src/discovery/types.ts +0 -453
  449. package/src/events/EventsCanvasValidator.test.ts +0 -191
  450. package/src/events/EventsCanvasValidator.ts +0 -428
  451. package/src/events/NamespacePathIndex.test.ts +0 -97
  452. package/src/events/NamespacePathIndex.ts +0 -100
  453. package/src/events/OtelEventPathsValidator.test.ts +0 -243
  454. package/src/events/OtelEventPathsValidator.ts +0 -191
  455. package/src/events/ScopeEventsValidator.test.ts +0 -173
  456. package/src/events/ScopeEventsValidator.ts +0 -158
  457. package/src/events/index.ts +0 -27
  458. package/src/events/path-helpers.ts +0 -53
  459. package/src/execution/ExecutionLoader.test.ts +0 -378
  460. package/src/execution/ExecutionLoader.ts +0 -331
  461. package/src/execution/ExecutionValidator.test.ts +0 -503
  462. package/src/execution/ExecutionValidator.ts +0 -617
  463. package/src/generated/graph-converter-execution.types.ts +0 -193
  464. package/src/helpers/GraphInstrumentationHelper.test.ts +0 -364
  465. package/src/helpers/GraphInstrumentationHelper.ts +0 -323
  466. package/src/matchers/SpanMatcher.test.ts +0 -1105
  467. package/src/matchers/SpanMatcher.ts +0 -381
  468. package/src/orchestration/TraceOrchestrator.ts +0 -560
  469. package/src/orchestration/__tests__/TraceOrchestrator.test.ts +0 -1098
  470. package/src/parsers/OtlpTraceParser.ts +0 -401
  471. package/src/parsers/__tests__/OtlpTraceParser.test.ts +0 -197
  472. package/src/registry/CompositeRegistry.ts +0 -140
  473. package/src/registry/EventRegistry.test.ts +0 -387
  474. package/src/registry/EventRegistry.ts +0 -191
  475. package/src/registry/LocalRegistry.ts +0 -416
  476. package/src/registry/RemoteRegistry.ts +0 -499
  477. package/src/registry/VersionRegistry.test.ts +0 -223
  478. package/src/registry/VersionRegistry.ts +0 -121
  479. package/src/rules/config.test.ts +0 -277
  480. package/src/rules/config.ts +0 -450
  481. package/src/rules/engine.test.ts +0 -331
  482. package/src/rules/engine.ts +0 -448
  483. package/src/rules/implementations/connection-type-references.ts +0 -117
  484. package/src/rules/implementations/dead-end-states.ts +0 -101
  485. package/src/rules/implementations/index.ts +0 -66
  486. package/src/rules/implementations/minimum-node-sources.ts +0 -84
  487. package/src/rules/implementations/no-unknown-fields.ts +0 -372
  488. package/src/rules/implementations/orphaned-edge-types.ts +0 -55
  489. package/src/rules/implementations/orphaned-node-types.ts +0 -58
  490. package/src/rules/implementations/required-metadata.ts +0 -52
  491. package/src/rules/implementations/state-transition-references.ts +0 -155
  492. package/src/rules/implementations/unreachable-states.ts +0 -94
  493. package/src/rules/implementations/valid-action-patterns.ts +0 -140
  494. package/src/rules/implementations/valid-color-format.ts +0 -152
  495. package/src/rules/implementations/valid-edge-types.ts +0 -273
  496. package/src/rules/implementations/valid-node-types.ts +0 -200
  497. package/src/rules/index.ts +0 -92
  498. package/src/rules/types.ts +0 -426
  499. package/src/scopes/ScopePathIndex.test.ts +0 -94
  500. package/src/scopes/ScopePathIndex.ts +0 -89
  501. package/src/scopes/ScopesCanvasValidator.test.ts +0 -127
  502. package/src/scopes/ScopesCanvasValidator.ts +0 -305
  503. package/src/scopes/__tests__/utils.test.ts +0 -383
  504. package/src/scopes/index.ts +0 -42
  505. package/src/scopes/utils.ts +0 -182
  506. package/src/scopes/validateScopeNamespaceNesting.test.ts +0 -127
  507. package/src/scopes/validateScopeNamespaceNesting.ts +0 -88
  508. package/src/spans/index.ts +0 -24
  509. package/src/spans/utils.ts +0 -249
  510. package/src/storyboard/builder.ts +0 -308
  511. package/src/storyboard/index.ts +0 -26
  512. package/src/storyboard/types.ts +0 -117
  513. package/src/telemetry/coverage.ts +0 -480
  514. package/src/telemetry/event-validator.ts +0 -312
  515. package/src/types/audit.ts +0 -314
  516. package/src/types/auxiliary.ts +0 -63
  517. package/src/types/canvas-scope.ts +0 -190
  518. package/src/types/canvas.test.ts +0 -199
  519. package/src/types/canvas.ts +0 -1395
  520. package/src/types/dashboard.ts +0 -257
  521. package/src/types/library.ts +0 -283
  522. package/src/types/otel.test.ts +0 -219
  523. package/src/types/otel.ts +0 -620
  524. package/src/types/path-based-config.ts +0 -285
  525. package/src/types/registered-trace.ts +0 -432
  526. package/src/types/resource-match.ts +0 -263
  527. package/src/types/version-registry.ts +0 -150
  528. package/src/utils/CanvasConverter.ts +0 -670
  529. package/src/utils/GraphConverter.test.ts +0 -322
  530. package/src/utils/GraphConverter.ts +0 -84
  531. package/src/utils/PathMatcher.test.ts +0 -139
  532. package/src/utils/PathMatcher.ts +0 -185
  533. package/src/utils/TraceToCanvas.test.ts +0 -310
  534. package/src/utils/TraceToCanvas.ts +0 -510
  535. package/src/utils/YamlParser.ts +0 -77
  536. package/src/utils/registeredTraceHelpers.ts +0 -251
  537. package/src/utils/traceAggregation.test.ts +0 -385
  538. package/src/utils/traceAggregation.ts +0 -173
  539. package/src/validation/OtelCanvasValidator.ts +0 -86
  540. package/src/validation/index.ts +0 -17
  541. package/src/validation/libraryValidation.ts +0 -206
  542. package/src/workflow/README.md +0 -408
  543. package/src/workflow/__tests__/edge-derivation.test.ts +0 -449
  544. package/src/workflow/__tests__/edge-validation.test.ts +0 -417
  545. package/src/workflow/__tests__/scenario-matcher.test.ts +0 -225
  546. package/src/workflow/__tests__/template-parser.test.ts +0 -276
  547. package/src/workflow/__tests__/template-renderer.test.ts +0 -509
  548. package/src/workflow/__tests__/types.test.ts +0 -187
  549. package/src/workflow/__tests__/validator.test.ts +0 -3225
  550. package/src/workflow/edge-derivation.ts +0 -265
  551. package/src/workflow/edge-validation.ts +0 -503
  552. package/src/workflow/example.ts +0 -330
  553. package/src/workflow/index.ts +0 -86
  554. package/src/workflow/scenario-matcher.ts +0 -253
  555. package/src/workflow/template-parser.ts +0 -350
  556. package/src/workflow/template-renderer.ts +0 -453
  557. package/src/workflow/types.ts +0 -511
  558. package/src/workflow/validator.ts +0 -2737
@@ -1,3225 +0,0 @@
1
- /**
2
- * Tests for workflow template validator
3
- */
4
-
5
- import { WorkflowValidator } from '../validator';
6
- import type { WorkflowTemplate, WorkflowValidationContext } from '../validator';
7
- import type { ExtendedCanvas } from '../../types/canvas';
8
- import type { ComponentLibrary } from '../../types/library';
9
- import { EventRegistry } from '../../registry/EventRegistry';
10
- import { NodeFileSystemAdapter } from '@principal-ai/repository-abstraction/node';
11
- import { mkdtempSync, writeFileSync, rmSync } from 'fs';
12
- import { join } from 'path';
13
- import { tmpdir } from 'os';
14
-
15
- describe('WorkflowValidator', () => {
16
- let validator: WorkflowValidator;
17
- let tempDir: string;
18
-
19
- beforeEach(() => {
20
- validator = new WorkflowValidator(new NodeFileSystemAdapter());
21
- // Create a temporary directory for test files
22
- tempDir = mkdtempSync(join(tmpdir(), 'workflow-validator-test-'));
23
- });
24
-
25
- afterEach(() => {
26
- // Clean up temp directory
27
- if (tempDir) {
28
- rmSync(tempDir, { recursive: true, force: true });
29
- }
30
- });
31
-
32
- // ============================================================================
33
- // Helper Functions
34
- // ============================================================================
35
-
36
- function createValidWorkflow(): WorkflowTemplate {
37
- return {
38
- version: '1.0.0',
39
- canvas: 'test.otel.canvas',
40
- name: 'Test Workflow',
41
- description: 'A test workflow template',
42
- spanPattern: 'test.execution',
43
- scenarioSelection: 'first-match',
44
- scenarios: [
45
- {
46
- id: 'test-passed',
47
- priority: 1,
48
- description: 'Test passed successfully',
49
- template: {
50
- introduction: 'Test passed',
51
- events: {
52
- 'test.started': 'Started test',
53
- 'test.complete': 'Test completed',
54
- },
55
- summary: 'Success',
56
- },
57
- },
58
- {
59
- id: 'test-failed',
60
- priority: 2,
61
- description: 'Test failed',
62
- template: {
63
- introduction: 'Test failed',
64
- events: {
65
- 'test.started': 'Test started',
66
- 'test.failed': 'Test failed',
67
- },
68
- },
69
- },
70
- ],
71
- };
72
- }
73
-
74
- function createValidCanvas(): ExtendedCanvas {
75
- return {
76
- nodes: [
77
- {
78
- id: 'test-started',
79
- type: 'otel-event',
80
- x: 0,
81
- y: 0,
82
- width: 200,
83
- height: 100,
84
- eventRef: 'test.started',
85
- },
86
- {
87
- id: 'test-complete',
88
- type: 'otel-event',
89
- x: 250,
90
- y: 0,
91
- width: 200,
92
- height: 100,
93
- eventRef: 'test.complete',
94
- },
95
- {
96
- id: 'test-error',
97
- type: 'otel-event',
98
- x: 500,
99
- y: 0,
100
- width: 200,
101
- height: 100,
102
- eventRef: 'test.error',
103
- },
104
- ],
105
- edges: [
106
- {
107
- id: 'edge1',
108
- fromNode: 'test-started',
109
- toNode: 'test-complete',
110
- fromSide: 'right',
111
- toSide: 'left',
112
- },
113
- {
114
- id: 'edge2',
115
- fromNode: 'test-started',
116
- toNode: 'test-error',
117
- fromSide: 'right',
118
- toSide: 'left',
119
- },
120
- ],
121
- pv: {
122
- version: '1.0.0',
123
- name: 'Test Canvas',
124
- markdown: 'test.md',
125
- },
126
- };
127
- }
128
-
129
- function createContext(
130
- workflow: Partial<WorkflowTemplate>,
131
- options: {
132
- canvas?: ExtendedCanvas;
133
- canvasPath?: string;
134
- } = {}
135
- ): WorkflowValidationContext {
136
- const fullWorkflow = {
137
- ...createValidWorkflow(),
138
- ...workflow,
139
- } as WorkflowTemplate;
140
-
141
- return {
142
- workflow: fullWorkflow,
143
- workflowPath: 'test.workflow.json',
144
- canvas: options.canvas,
145
- canvasPath: options.canvasPath,
146
- basePath: tempDir,
147
- };
148
- }
149
-
150
- // ============================================================================
151
- // Schema Validation Tests
152
- // ============================================================================
153
-
154
- describe('checkSchema', () => {
155
- it('should pass for valid workflow template', async () => {
156
- // Create canvas file so canvas-exists check passes
157
- const canvasPath = join(tempDir, 'test.otel.canvas');
158
- const markdownPath = join(tempDir, 'test.md');
159
- writeFileSync(canvasPath, JSON.stringify(createValidCanvas()));
160
- writeFileSync(markdownPath, '# Test Documentation');
161
-
162
- const context = createContext({}, { canvasPath });
163
- const result = await validator.validate(context);
164
-
165
- expect(result.errorCount).toBe(0);
166
- expect(result.warningCount).toBe(0);
167
- });
168
-
169
- it('should flag missing version', async () => {
170
- const context = createContext({ version: undefined as unknown });
171
- const result = await validator.validate(context);
172
-
173
- expect(result.errorCount).toBeGreaterThan(0);
174
- expect(result.violations).toContainEqual(
175
- expect.objectContaining({
176
- ruleId: 'workflow-schema-valid',
177
- path: 'version',
178
- message: expect.stringContaining('Missing required field "version"'),
179
- })
180
- );
181
- });
182
-
183
- it('should flag invalid semver version', async () => {
184
- const context = createContext({ version: 'not-a-version' });
185
- const result = await validator.validate(context);
186
-
187
- expect(result.errorCount).toBeGreaterThan(0);
188
- expect(result.violations).toContainEqual(
189
- expect.objectContaining({
190
- ruleId: 'workflow-schema-valid',
191
- path: 'version',
192
- message: expect.stringContaining('Invalid version format'),
193
- })
194
- );
195
- });
196
-
197
- it('should flag missing canvas reference', async () => {
198
- const context = createContext({ canvas: undefined as unknown });
199
- const result = await validator.validate(context);
200
-
201
- expect(result.errorCount).toBeGreaterThan(0);
202
- expect(result.violations).toContainEqual(
203
- expect.objectContaining({
204
- ruleId: 'workflow-schema-valid',
205
- path: 'canvas',
206
- message: expect.stringContaining('Missing required field "canvas"'),
207
- })
208
- );
209
- });
210
-
211
- it('should flag missing name', async () => {
212
- const context = createContext({ name: undefined as unknown });
213
- const result = await validator.validate(context);
214
-
215
- expect(result.errorCount).toBeGreaterThan(0);
216
- expect(result.violations).toContainEqual(
217
- expect.objectContaining({
218
- ruleId: 'workflow-schema-valid',
219
- path: 'name',
220
- message: expect.stringContaining('Missing required field "name"'),
221
- })
222
- );
223
- });
224
-
225
- it('should flag missing description', async () => {
226
- const context = createContext({ description: undefined as unknown });
227
- const result = await validator.validate(context);
228
-
229
- expect(result.errorCount).toBeGreaterThan(0);
230
- expect(result.violations).toContainEqual(
231
- expect.objectContaining({
232
- ruleId: 'workflow-schema-valid',
233
- path: 'description',
234
- })
235
- );
236
- });
237
-
238
- it('should flag invalid scenarioSelection', async () => {
239
- const context = createContext({ scenarioSelection: 'invalid' as unknown });
240
- const result = await validator.validate(context);
241
-
242
- expect(result.errorCount).toBeGreaterThan(0);
243
- expect(result.violations).toContainEqual(
244
- expect.objectContaining({
245
- ruleId: 'workflow-schema-valid',
246
- path: 'scenarioSelection',
247
- })
248
- );
249
- });
250
-
251
- it('should flag empty scenarios array', async () => {
252
- const context = createContext({ scenarios: [] });
253
- const result = await validator.validate(context);
254
-
255
- expect(result.errorCount).toBeGreaterThan(0);
256
- expect(result.violations).toContainEqual(
257
- expect.objectContaining({
258
- ruleId: 'workflow-schema-valid',
259
- path: 'scenarios',
260
- message: expect.stringContaining('empty'),
261
- })
262
- );
263
- });
264
- });
265
-
266
- // ============================================================================
267
- // Canvas Existence Tests
268
- // ============================================================================
269
-
270
- describe('checkCanvasExists', () => {
271
- it('should pass when canvas file exists', async () => {
272
- // Create a canvas file in temp directory
273
- const canvasPath = join(tempDir, 'test.otel.canvas');
274
- const markdownPath = join(tempDir, 'test.md');
275
- writeFileSync(canvasPath, JSON.stringify(createValidCanvas()));
276
- writeFileSync(markdownPath, '# Test Documentation');
277
-
278
- const context = createContext(
279
- { canvas: 'test.otel.canvas' },
280
- { canvasPath }
281
- );
282
- const result = await validator.validate(context);
283
-
284
- const canvasViolations = result.violations.filter(
285
- (v) => v.ruleId === 'workflow-canvas-exists'
286
- );
287
- expect(canvasViolations).toHaveLength(0);
288
- });
289
-
290
- it('should flag when canvas file does not exist', async () => {
291
- const context = createContext(
292
- { canvas: 'nonexistent.otel.canvas' },
293
- { canvasPath: join(tempDir, 'nonexistent.otel.canvas') }
294
- );
295
- const result = await validator.validate(context);
296
-
297
- expect(result.errorCount).toBeGreaterThan(0);
298
- expect(result.violations).toContainEqual(
299
- expect.objectContaining({
300
- ruleId: 'workflow-canvas-exists',
301
- message: expect.stringContaining('does not exist'),
302
- })
303
- );
304
- });
305
- });
306
-
307
- // ============================================================================
308
- // Implementation Files Existence Tests (workflow-files-exist)
309
- // ============================================================================
310
-
311
- describe('workflow-files-exist', () => {
312
- // Create the referenced canvas so unrelated canvas-exists violations stay silent.
313
- function setupCanvas(): string {
314
- const canvasPath = join(tempDir, 'test.otel.canvas');
315
- writeFileSync(canvasPath, JSON.stringify(createValidCanvas()));
316
- writeFileSync(join(tempDir, 'test.md'), '# Test');
317
- return canvasPath;
318
- }
319
-
320
- it('should pass when status is implemented and all files exist', async () => {
321
- const canvasPath = setupCanvas();
322
- writeFileSync(join(tempDir, 'impl.ts'), '// impl');
323
-
324
- const context = createContext(
325
- { status: 'implemented', scope: 'test-scope', files: ['impl.ts'] },
326
- { canvasPath }
327
- );
328
- const result = await validator.validate(context);
329
-
330
- const fileViolations = result.violations.filter(
331
- (v) => v.ruleId === 'workflow-files-exist'
332
- );
333
- expect(fileViolations).toHaveLength(0);
334
- });
335
-
336
- it('should flag each missing file when status is implemented', async () => {
337
- const canvasPath = setupCanvas();
338
-
339
- const context = createContext(
340
- {
341
- status: 'implemented',
342
- scope: 'test-scope',
343
- files: ['missing-one.ts', 'missing-two.ts'],
344
- },
345
- { canvasPath }
346
- );
347
- const result = await validator.validate(context);
348
-
349
- const fileViolations = result.violations.filter(
350
- (v) => v.ruleId === 'workflow-files-exist'
351
- );
352
- expect(fileViolations).toHaveLength(2);
353
- expect(fileViolations).toContainEqual(
354
- expect.objectContaining({
355
- ruleId: 'workflow-files-exist',
356
- severity: 'error',
357
- path: 'files',
358
- message: expect.stringContaining('missing-one.ts'),
359
- })
360
- );
361
- expect(fileViolations).toContainEqual(
362
- expect.objectContaining({
363
- ruleId: 'workflow-files-exist',
364
- message: expect.stringContaining('missing-two.ts'),
365
- })
366
- );
367
- });
368
-
369
- it('should flag only the missing files when some exist and some do not', async () => {
370
- const canvasPath = setupCanvas();
371
- writeFileSync(join(tempDir, 'present.ts'), '// present');
372
-
373
- const context = createContext(
374
- {
375
- status: 'implemented',
376
- scope: 'test-scope',
377
- files: ['present.ts', 'absent.ts'],
378
- },
379
- { canvasPath }
380
- );
381
- const result = await validator.validate(context);
382
-
383
- const fileViolations = result.violations.filter(
384
- (v) => v.ruleId === 'workflow-files-exist'
385
- );
386
- expect(fileViolations).toHaveLength(1);
387
- expect(fileViolations[0].message).toContain('absent.ts');
388
- });
389
-
390
- it('should not check file existence when status is draft', async () => {
391
- const canvasPath = setupCanvas();
392
-
393
- const context = createContext(
394
- { status: 'draft', files: ['does-not-exist.ts'] },
395
- { canvasPath }
396
- );
397
- const result = await validator.validate(context);
398
-
399
- const fileViolations = result.violations.filter(
400
- (v) => v.ruleId === 'workflow-files-exist'
401
- );
402
- expect(fileViolations).toHaveLength(0);
403
- });
404
-
405
- it('should not check file existence when status is approved', async () => {
406
- const canvasPath = setupCanvas();
407
-
408
- const context = createContext(
409
- {
410
- status: 'approved',
411
- scope: 'test-scope',
412
- files: ['does-not-exist.ts'],
413
- },
414
- { canvasPath }
415
- );
416
- const result = await validator.validate(context);
417
-
418
- const fileViolations = result.violations.filter(
419
- (v) => v.ruleId === 'workflow-files-exist'
420
- );
421
- expect(fileViolations).toHaveLength(0);
422
- });
423
- });
424
-
425
- // ============================================================================
426
- // Scenario Validation Tests
427
- // ============================================================================
428
-
429
- describe('checkScenarios', () => {
430
- it('should pass for valid scenarios', async () => {
431
- const context = createContext({});
432
- const result = await validator.validate(context);
433
-
434
- const scenarioViolations = result.violations.filter(
435
- (v) => v.ruleId === 'workflow-scenario-valid'
436
- );
437
- expect(scenarioViolations).toHaveLength(0);
438
- });
439
-
440
- it('should flag missing scenario ID', async () => {
441
- const context = createContext({
442
- scenarios: [
443
- {
444
- id: undefined as unknown,
445
- priority: 1,
446
- description: 'Test',
447
- template: { introduction: 'Test' },
448
- },
449
- ],
450
- });
451
- const result = await validator.validate(context);
452
-
453
- expect(result.errorCount).toBeGreaterThan(0);
454
- expect(result.violations).toContainEqual(
455
- expect.objectContaining({
456
- ruleId: 'workflow-scenario-valid',
457
- message: expect.stringContaining('missing required "id"'),
458
- })
459
- );
460
- });
461
-
462
- it('should flag duplicate scenario IDs', async () => {
463
- const context = createContext({
464
- scenarios: [
465
- {
466
- id: 'duplicate',
467
- priority: 1,
468
- description: 'First',
469
- template: { introduction: 'First' },
470
- },
471
- {
472
- id: 'duplicate',
473
- priority: 2,
474
- description: 'Second',
475
- template: { introduction: 'Second' },
476
- },
477
- ],
478
- });
479
- const result = await validator.validate(context);
480
-
481
- expect(result.errorCount).toBeGreaterThan(0);
482
- expect(result.violations).toContainEqual(
483
- expect.objectContaining({
484
- ruleId: 'workflow-scenario-valid',
485
- message: expect.stringContaining('Duplicate scenario ID'),
486
- })
487
- );
488
- });
489
-
490
- it('should flag missing priority', async () => {
491
- const context = createContext({
492
- scenarios: [
493
- {
494
- id: 'test',
495
- priority: undefined as unknown,
496
- description: 'Test',
497
- template: { introduction: 'Test' },
498
- },
499
- ],
500
- });
501
- const result = await validator.validate(context);
502
-
503
- expect(result.errorCount).toBeGreaterThan(0);
504
- expect(result.violations).toContainEqual(
505
- expect.objectContaining({
506
- ruleId: 'workflow-scenario-valid',
507
- message: expect.stringContaining('missing required "priority"'),
508
- })
509
- );
510
- });
511
-
512
- it('should flag negative priority', async () => {
513
- const context = createContext({
514
- scenarios: [
515
- {
516
- id: 'test',
517
- priority: -1,
518
- description: 'Test',
519
- template: { introduction: 'Test' },
520
- },
521
- ],
522
- });
523
- const result = await validator.validate(context);
524
-
525
- expect(result.errorCount).toBeGreaterThan(0);
526
- expect(result.violations).toContainEqual(
527
- expect.objectContaining({
528
- ruleId: 'workflow-scenario-valid',
529
- message: expect.stringContaining('non-negative'),
530
- })
531
- );
532
- });
533
-
534
- it('should flag duplicate priorities', async () => {
535
- const context = createContext({
536
- scenarios: [
537
- {
538
- id: 'first',
539
- priority: 1,
540
- description: 'First',
541
- template: { introduction: 'First' },
542
- },
543
- {
544
- id: 'second',
545
- priority: 1,
546
- description: 'Second',
547
- template: { introduction: 'Second' },
548
- },
549
- ],
550
- });
551
- const result = await validator.validate(context);
552
-
553
- expect(result.errorCount).toBeGreaterThan(0);
554
- expect(result.violations).toContainEqual(
555
- expect.objectContaining({
556
- ruleId: 'workflow-scenario-valid',
557
- message: expect.stringContaining('Duplicate priority'),
558
- })
559
- );
560
- });
561
-
562
- it('should flag missing description', async () => {
563
- const context = createContext({
564
- scenarios: [
565
- {
566
- id: 'test',
567
- priority: 1,
568
- description: undefined as unknown,
569
- template: { introduction: 'Test' },
570
- },
571
- ],
572
- });
573
- const result = await validator.validate(context);
574
-
575
- expect(result.errorCount).toBeGreaterThan(0);
576
- expect(result.violations).toContainEqual(
577
- expect.objectContaining({
578
- ruleId: 'workflow-scenario-valid',
579
- message: expect.stringContaining('missing required "description"'),
580
- })
581
- );
582
- });
583
-
584
- it('should flag missing template', async () => {
585
- const context = createContext({
586
- scenarios: [
587
- {
588
- id: 'test',
589
- priority: 1,
590
- description: 'Test',
591
- template: undefined as unknown,
592
- },
593
- ],
594
- });
595
- const result = await validator.validate(context);
596
-
597
- expect(result.errorCount).toBeGreaterThan(0);
598
- expect(result.violations).toContainEqual(
599
- expect.objectContaining({
600
- ruleId: 'workflow-scenario-valid',
601
- message: expect.stringContaining('missing required "template"'),
602
- })
603
- );
604
- });
605
- });
606
-
607
- // ============================================================================
608
- // Template Syntax Tests
609
- // ============================================================================
610
-
611
- describe('checkTemplateSyntax', () => {
612
- it('should pass for valid template strings', async () => {
613
- const context = createContext({
614
- scenarios: [
615
- {
616
- id: 'test',
617
- priority: 1,
618
- description: 'Test',
619
- template: {
620
- introduction: 'Started {test.name}',
621
- events: {
622
- 'test.started': 'Test {test.id} started',
623
- 'test.complete': '{result.status === "passed" ? "✅ Passed" : "❌ Failed"}',
624
- },
625
- summary: 'Completed in {duration}ms',
626
- },
627
- },
628
- ],
629
- });
630
- const result = await validator.validate(context);
631
-
632
- const syntaxViolations = result.violations.filter(
633
- (v) => v.ruleId === 'workflow-template-syntax'
634
- );
635
- expect(syntaxViolations).toHaveLength(0);
636
- });
637
-
638
- it('should flag unbalanced braces (missing closing)', async () => {
639
- const context = createContext({
640
- scenarios: [
641
- {
642
- id: 'test',
643
- priority: 1,
644
- description: 'Test',
645
- template: {
646
- introduction: 'Test {test.name',
647
- },
648
- },
649
- ],
650
- });
651
- const result = await validator.validate(context);
652
-
653
- expect(result.errorCount).toBeGreaterThan(0);
654
- expect(result.violations).toContainEqual(
655
- expect.objectContaining({
656
- ruleId: 'workflow-template-syntax',
657
- message: expect.stringContaining('Unbalanced braces'),
658
- })
659
- );
660
- });
661
-
662
- it('should flag unbalanced braces (extra closing)', async () => {
663
- const context = createContext({
664
- scenarios: [
665
- {
666
- id: 'test',
667
- priority: 1,
668
- description: 'Test',
669
- template: {
670
- introduction: 'Test {test.name}}',
671
- },
672
- },
673
- ],
674
- });
675
- const result = await validator.validate(context);
676
-
677
- expect(result.errorCount).toBeGreaterThan(0);
678
- expect(result.violations).toContainEqual(
679
- expect.objectContaining({
680
- ruleId: 'workflow-template-syntax',
681
- message: expect.stringContaining('Unbalanced braces'),
682
- })
683
- );
684
- });
685
-
686
- it('should flag incomplete conditional expression', async () => {
687
- const context = createContext({
688
- scenarios: [
689
- {
690
- id: 'test',
691
- priority: 1,
692
- description: 'Test',
693
- template: {
694
- introduction: '{result.passed ? "Success"}',
695
- },
696
- },
697
- ],
698
- });
699
- const result = await validator.validate(context);
700
-
701
- expect(result.errorCount).toBeGreaterThan(0);
702
- expect(result.violations).toContainEqual(
703
- expect.objectContaining({
704
- ruleId: 'workflow-template-syntax',
705
- message: expect.stringContaining('Incomplete conditional'),
706
- })
707
- );
708
- });
709
-
710
- it('should handle quotes in template strings', async () => {
711
- const context = createContext({
712
- scenarios: [
713
- {
714
- id: 'test',
715
- priority: 1,
716
- description: 'Test',
717
- template: {
718
- introduction: '{test.status === "passed" ? "Success" : "Failed"}',
719
- },
720
- },
721
- ],
722
- });
723
- const result = await validator.validate(context);
724
-
725
- const syntaxViolations = result.violations.filter(
726
- (v) => v.ruleId === 'workflow-template-syntax'
727
- );
728
- expect(syntaxViolations).toHaveLength(0);
729
- });
730
-
731
- it('should validate event templates', async () => {
732
- const context = createContext({
733
- scenarios: [
734
- {
735
- id: 'test',
736
- priority: 1,
737
- description: 'Test',
738
- template: {
739
- events: {
740
- 'test.started': 'Started {test.name',
741
- 'test.complete': 'Completed',
742
- },
743
- },
744
- },
745
- ],
746
- });
747
- const result = await validator.validate(context);
748
-
749
- expect(result.errorCount).toBeGreaterThan(0);
750
- expect(result.violations).toContainEqual(
751
- expect.objectContaining({
752
- ruleId: 'workflow-template-syntax',
753
- path: expect.stringContaining('events.test.started'),
754
- })
755
- );
756
- });
757
-
758
- it('should flag Handlebars {{#if}} conditionals', async () => {
759
- const context = createContext({
760
- scenarios: [
761
- {
762
- id: 'test',
763
- priority: 1,
764
- description: 'Test',
765
- template: {
766
- introduction: '{{#if hasError}}Error occurred{{/if}}',
767
- },
768
- },
769
- ],
770
- });
771
- const result = await validator.validate(context);
772
-
773
- expect(result.errorCount).toBeGreaterThan(0);
774
- expect(result.violations).toContainEqual(
775
- expect.objectContaining({
776
- ruleId: 'workflow-template-conditional',
777
- message: expect.stringContaining('Conditional syntax detected'),
778
- suggestion: expect.stringContaining('Split into separate scenarios'),
779
- })
780
- );
781
- });
782
-
783
- it('should flag Handlebars {{#unless}} conditionals', async () => {
784
- const context = createContext({
785
- scenarios: [
786
- {
787
- id: 'test',
788
- priority: 1,
789
- description: 'Test',
790
- template: {
791
- summary: '{{#unless success}}Failed{{/unless}}',
792
- },
793
- },
794
- ],
795
- });
796
- const result = await validator.validate(context);
797
-
798
- expect(result.errorCount).toBeGreaterThan(0);
799
- expect(result.violations).toContainEqual(
800
- expect.objectContaining({
801
- ruleId: 'workflow-template-conditional',
802
- message: expect.stringContaining('{{#unless'),
803
- })
804
- );
805
- });
806
-
807
- it('should flag Handlebars {{#each}} iteration', async () => {
808
- const context = createContext({
809
- scenarios: [
810
- {
811
- id: 'test',
812
- priority: 1,
813
- description: 'Test',
814
- template: {
815
- introduction: '{{#each items}}{{name}}{{/each}}',
816
- },
817
- },
818
- ],
819
- });
820
- const result = await validator.validate(context);
821
-
822
- expect(result.errorCount).toBeGreaterThan(0);
823
- expect(result.violations).toContainEqual(
824
- expect.objectContaining({
825
- ruleId: 'workflow-template-conditional',
826
- message: expect.stringContaining('{{#each'),
827
- })
828
- );
829
- });
830
-
831
- it('should flag Handlebars {{else}} clause', async () => {
832
- const context = createContext({
833
- scenarios: [
834
- {
835
- id: 'test',
836
- priority: 1,
837
- description: 'Test',
838
- template: {
839
- events: {
840
- 'test.started': '{{#if fast}}Quick{{else}}Slow{{/if}}',
841
- },
842
- },
843
- },
844
- ],
845
- });
846
- const result = await validator.validate(context);
847
-
848
- expect(result.errorCount).toBeGreaterThan(0);
849
- expect(result.violations).toContainEqual(
850
- expect.objectContaining({
851
- ruleId: 'workflow-template-conditional',
852
- })
853
- );
854
- });
855
-
856
- it('should allow simple variable interpolation without conditionals', async () => {
857
- const context = createContext({
858
- scenarios: [
859
- {
860
- id: 'test',
861
- priority: 1,
862
- description: 'Test',
863
- template: {
864
- introduction: 'Test {{name}} completed in {{duration}}ms',
865
- events: {
866
- 'test.started': 'Started {{testName}}',
867
- },
868
- summary: 'Result: {{status}}',
869
- },
870
- },
871
- ],
872
- });
873
- const result = await validator.validate(context);
874
-
875
- const conditionalViolations = result.violations.filter(
876
- (v) => v.ruleId === 'workflow-template-conditional'
877
- );
878
- expect(conditionalViolations).toHaveLength(0);
879
- });
880
- });
881
-
882
- // ============================================================================
883
- // Emoji Validation Tests
884
- // ============================================================================
885
-
886
- describe('checkTemplateEmojis', () => {
887
- it('should flag emojis in event templates', async () => {
888
- const context = createContext({
889
- scenarios: [
890
- {
891
- id: 'test',
892
- priority: 1,
893
- description: 'Test',
894
- template: {
895
- events: {
896
- 'test.started': '🚀 Starting test',
897
- },
898
- },
899
- },
900
- ],
901
- });
902
- const result = await validator.validate(context);
903
-
904
- expect(result.errorCount).toBeGreaterThan(0);
905
- expect(result.violations).toContainEqual(
906
- expect.objectContaining({
907
- ruleId: 'workflow-template-emoji',
908
- message: expect.stringContaining('emoji'),
909
- suggestion: expect.stringContaining('Remove emoji'),
910
- })
911
- );
912
- });
913
-
914
- it('should flag emojis in introduction', async () => {
915
- const context = createContext({
916
- scenarios: [
917
- {
918
- id: 'test',
919
- priority: 1,
920
- description: 'Test',
921
- template: {
922
- introduction: '✅ Test Started',
923
- events: {
924
- 'test.started': 'Started',
925
- },
926
- },
927
- },
928
- ],
929
- });
930
- const result = await validator.validate(context);
931
-
932
- expect(result.violations).toContainEqual(
933
- expect.objectContaining({
934
- ruleId: 'workflow-template-emoji',
935
- })
936
- );
937
- });
938
-
939
- it('should flag emojis in summary', async () => {
940
- const context = createContext({
941
- scenarios: [
942
- {
943
- id: 'test',
944
- priority: 1,
945
- description: 'Test',
946
- template: {
947
- events: {
948
- 'test.started': 'Started',
949
- },
950
- summary: '❌ Failed',
951
- },
952
- },
953
- ],
954
- });
955
- const result = await validator.validate(context);
956
-
957
- expect(result.violations).toContainEqual(
958
- expect.objectContaining({
959
- ruleId: 'workflow-template-emoji',
960
- })
961
- );
962
- });
963
-
964
- it('should flag multiple different emojis', async () => {
965
- const context = createContext({
966
- scenarios: [
967
- {
968
- id: 'test',
969
- priority: 1,
970
- description: 'Test',
971
- template: {
972
- introduction: '🔄 Loading...',
973
- events: {
974
- 'test.started': '▶️ Started',
975
- 'test.complete': '✅ Done',
976
- },
977
- summary: '🎉 Success!',
978
- },
979
- },
980
- ],
981
- });
982
- const result = await validator.validate(context);
983
-
984
- const emojiViolations = result.violations.filter(
985
- (v) => v.ruleId === 'workflow-template-emoji'
986
- );
987
- expect(emojiViolations.length).toBeGreaterThan(0);
988
- });
989
-
990
- it('should allow templates without emojis', async () => {
991
- const context = createContext({
992
- scenarios: [
993
- {
994
- id: 'test',
995
- priority: 1,
996
- description: 'Test',
997
- template: {
998
- introduction: '[START] Test Beginning',
999
- events: {
1000
- 'test.started': 'Started: {{name}}',
1001
- 'test.complete': 'Completed in {{duration}}ms',
1002
- },
1003
- summary: '[DONE] Test finished successfully',
1004
- },
1005
- },
1006
- ],
1007
- });
1008
- const result = await validator.validate(context);
1009
-
1010
- const emojiViolations = result.violations.filter(
1011
- (v) => v.ruleId === 'workflow-template-emoji'
1012
- );
1013
- expect(emojiViolations).toHaveLength(0);
1014
- });
1015
-
1016
- it('should allow special characters that are not emojis', async () => {
1017
- const context = createContext({
1018
- scenarios: [
1019
- {
1020
- id: 'test',
1021
- priority: 1,
1022
- description: 'Test',
1023
- template: {
1024
- introduction: '>>> Test Output <<<',
1025
- events: {
1026
- 'test.started': '* Started @ {{timestamp}}',
1027
- 'test.complete': '# Complete - Duration: {{duration}}ms',
1028
- },
1029
- summary: '=== Summary ===',
1030
- },
1031
- },
1032
- ],
1033
- });
1034
- const result = await validator.validate(context);
1035
-
1036
- const emojiViolations = result.violations.filter(
1037
- (v) => v.ruleId === 'workflow-template-emoji'
1038
- );
1039
- expect(emojiViolations).toHaveLength(0);
1040
- });
1041
- });
1042
-
1043
- // ============================================================================
1044
- // Formatting Options Tests
1045
- // ============================================================================
1046
-
1047
- describe('checkFormattingOptions', () => {
1048
- it('should pass for valid formatting options', async () => {
1049
- const context = createContext({
1050
- formatting: {
1051
- indentPerLevel: ' ',
1052
- timestampFormat: 'HH:mm:ss.SSS',
1053
- showTimestamps: true,
1054
- showDuration: true,
1055
- showSpanIds: false,
1056
- showAttributes: 'matched',
1057
- },
1058
- });
1059
- const result = await validator.validate(context);
1060
-
1061
- const formattingViolations = result.violations.filter(
1062
- (v) => v.ruleId === 'workflow-formatting-options'
1063
- );
1064
- expect(formattingViolations).toHaveLength(0);
1065
- });
1066
-
1067
- it('should flag invalid showAttributes value', async () => {
1068
- const context = createContext({
1069
- formatting: {
1070
- showAttributes: 'invalid' as unknown,
1071
- },
1072
- });
1073
- const result = await validator.validate(context);
1074
-
1075
- expect(result.warningCount).toBeGreaterThan(0);
1076
- expect(result.violations).toContainEqual(
1077
- expect.objectContaining({
1078
- ruleId: 'workflow-formatting-options',
1079
- severity: 'warn',
1080
- message: expect.stringContaining('Invalid showAttributes'),
1081
- })
1082
- );
1083
- });
1084
-
1085
- it('should accept valid showAttributes values', async () => {
1086
- const values = ['none', 'matched', 'all'];
1087
-
1088
- for (const value of values) {
1089
- const context = createContext({
1090
- formatting: {
1091
- showAttributes: value as unknown,
1092
- },
1093
- });
1094
- const result = await validator.validate(context);
1095
-
1096
- const formattingViolations = result.violations.filter(
1097
- (v) => v.ruleId === 'workflow-formatting-options' && v.path === 'formatting.showAttributes'
1098
- );
1099
- expect(formattingViolations).toHaveLength(0);
1100
- }
1101
- });
1102
- });
1103
-
1104
- // ============================================================================
1105
- // Aggregate Results Tests
1106
- // ============================================================================
1107
-
1108
- describe('aggregateResults', () => {
1109
- it('should correctly count errors and warnings', async () => {
1110
- const context = createContext({
1111
- version: undefined as unknown,
1112
- name: undefined as unknown,
1113
- formatting: {
1114
- showAttributes: 'invalid' as unknown,
1115
- },
1116
- });
1117
- const result = await validator.validate(context);
1118
-
1119
- expect(result.errorCount).toBeGreaterThan(0);
1120
- expect(result.warningCount).toBeGreaterThan(0);
1121
- expect(result.violations.length).toBe(result.errorCount + result.warningCount);
1122
- });
1123
-
1124
- it('should count fixable violations', async () => {
1125
- // Currently no violations are fixable, but test the infrastructure
1126
- const context = createContext({});
1127
- const result = await validator.validate(context);
1128
-
1129
- const fixable = result.violations.filter((v) => v.fixable);
1130
- expect(result.fixableCount).toBe(fixable.length);
1131
- });
1132
- });
1133
-
1134
- // ============================================================================
1135
- // Integration Tests
1136
- // ============================================================================
1137
-
1138
- describe('integration tests', () => {
1139
- it('should validate a complete valid workflow template', async () => {
1140
- const canvasPath = join(tempDir, 'test.otel.canvas');
1141
- const markdownPath = join(tempDir, 'test.md');
1142
- writeFileSync(canvasPath, JSON.stringify(createValidCanvas()));
1143
- writeFileSync(markdownPath, '# Test Documentation');
1144
-
1145
- const workflow: WorkflowTemplate = {
1146
- version: '1.0.0',
1147
- canvas: 'test.otel.canvas',
1148
- name: 'Complete Test Workflow',
1149
- description: 'A complete workflow template for testing',
1150
- spanPattern: 'test.integration',
1151
- scenarioSelection: 'first-match',
1152
- scenarios: [
1153
- {
1154
- id: 'error',
1155
- priority: 1,
1156
- description: 'Error scenario',
1157
- template: {
1158
- introduction: '[ERROR] Error Occurred',
1159
- events: {
1160
- 'test.started': 'Started: {test.name}',
1161
- 'test.error': 'Error: {error.message}',
1162
- },
1163
- summary: '[FAILED]',
1164
- },
1165
- },
1166
- {
1167
- id: 'success',
1168
- priority: 2,
1169
- description: 'Success scenario',
1170
- template: {
1171
- introduction: '[SUCCESS] Test Passed',
1172
- events: {
1173
- 'test.started': 'Started: {test.name}',
1174
- 'test.complete': 'Completed in {duration}ms',
1175
- },
1176
- summary: '[SUCCESS]',
1177
- },
1178
- },
1179
- ],
1180
- formatting: {
1181
- indentPerLevel: ' ',
1182
- showTimestamps: false,
1183
- showDuration: true,
1184
- showAttributes: 'matched',
1185
- },
1186
- };
1187
-
1188
- const context: WorkflowValidationContext = {
1189
- workflow,
1190
- workflowPath: 'test.workflow.json',
1191
- canvas: createValidCanvas(),
1192
- canvasPath,
1193
- basePath: tempDir,
1194
- };
1195
-
1196
- const result = await validator.validate(context);
1197
-
1198
- expect(result.errorCount).toBe(0);
1199
- expect(result.warningCount).toBe(0);
1200
- expect(result.violations).toHaveLength(0);
1201
- });
1202
-
1203
- it('should collect multiple violations from different rules', async () => {
1204
- const workflow = {
1205
- version: 'invalid-version',
1206
- canvas: 'nonexistent.canvas',
1207
- name: '',
1208
- scenarios: [
1209
- {
1210
- id: 'test',
1211
- priority: -1,
1212
- template: {
1213
- introduction: '{unclosed',
1214
- },
1215
- },
1216
- ],
1217
- formatting: {
1218
- showAttributes: 'invalid',
1219
- },
1220
- } as unknown as WorkflowTemplate;
1221
-
1222
- const context: WorkflowValidationContext = {
1223
- workflow,
1224
- workflowPath: 'test.workflow.json',
1225
- basePath: tempDir,
1226
- };
1227
-
1228
- const result = await validator.validate(context);
1229
-
1230
- expect(result.violations.length).toBeGreaterThan(5);
1231
-
1232
- const ruleIds = new Set(result.violations.map((v) => v.ruleId));
1233
- expect(ruleIds.size).toBeGreaterThan(3); // Multiple different rules triggered
1234
- });
1235
- });
1236
-
1237
- // ============================================================================
1238
- // EventRegistry Enhanced Error Messages Tests
1239
- // ============================================================================
1240
-
1241
- describe('checkEventReferences with EventRegistry', () => {
1242
- function createLibrary(eventSchemas: Record<string, { description: string; attributes: Record<string, unknown> }>): ComponentLibrary {
1243
- return {
1244
- version: '1.0.0',
1245
- name: 'Test Library',
1246
- nodeComponents: {},
1247
- edgeComponents: {},
1248
- eventSchemas,
1249
- };
1250
- }
1251
-
1252
- function createCanvasWithEvents(events: string[]): ExtendedCanvas {
1253
- return {
1254
- nodes: events.map((eventName, i) => ({
1255
- id: `node-${i}`,
1256
- type: 'text' as const,
1257
- x: i * 100,
1258
- y: 0,
1259
- width: 100,
1260
- height: 50,
1261
- pv: {
1262
- eventRef: eventName,
1263
- },
1264
- })),
1265
- edges: [],
1266
- pv: {
1267
- version: '1.0.0',
1268
- name: 'Test Canvas',
1269
- markdown: 'test.md',
1270
- },
1271
- };
1272
- }
1273
-
1274
- it('should show library suggestion when event exists in library but not canvas', async () => {
1275
- // Create library with the event
1276
- const library = createLibrary({
1277
- 'cleanup.started': { description: 'Cleanup started', attributes: {} },
1278
- });
1279
-
1280
- // Create a canvas WITHOUT the event
1281
- const canvas = createCanvasWithEvents(['other.event']);
1282
-
1283
- // Create another canvas that HAS the event (for the registry)
1284
- const otherCanvas = createCanvasWithEvents(['cleanup.started']);
1285
-
1286
- // Build registry with library
1287
- const canvases = new Map<string, ExtendedCanvas>();
1288
- canvases.set('other.otel.canvas', otherCanvas);
1289
- const eventRegistry = EventRegistry.build(library, canvases, 'library.yaml');
1290
-
1291
- // Workflow references an event not in its canvas
1292
- const workflow: WorkflowTemplate = {
1293
- version: '1.0.0',
1294
- canvas: 'test.otel.canvas',
1295
- name: 'Test Workflow',
1296
- description: 'Test',
1297
- mode: 'span-tree',
1298
- scenarioSelection: 'first-match',
1299
- scenarios: [
1300
- {
1301
- id: 'default',
1302
- priority: 1,
1303
- description: 'Default',
1304
- template: {
1305
- events: {
1306
- 'cleanup.started': 'Cleanup started', // Not in canvas
1307
- },
1308
- },
1309
- },
1310
- ],
1311
- };
1312
-
1313
- const context: WorkflowValidationContext = {
1314
- workflow,
1315
- workflowPath: 'test.workflow.json',
1316
- canvas,
1317
- canvasPath: join(tempDir, 'test.otel.canvas'),
1318
- basePath: tempDir,
1319
- eventRegistry,
1320
- };
1321
-
1322
- const result = await validator.validate(context);
1323
-
1324
- // Should have an error about the missing event
1325
- const eventViolation = result.violations.find(
1326
- v => v.ruleId === 'workflow-event-sync' && v.message.includes('cleanup.started')
1327
- );
1328
-
1329
- expect(eventViolation).toBeDefined();
1330
- // Should suggest using eventRef since it's in the library
1331
- expect(eventViolation!.message).toContain('available in library');
1332
- expect(eventViolation!.suggestion).toContain('eventRef');
1333
- });
1334
-
1335
- it('should show canvas location when event exists in another canvas but not library', async () => {
1336
- // No library events
1337
- const canvases = new Map<string, ExtendedCanvas>();
1338
-
1339
- // Create another canvas that has the event we're looking for
1340
- const cleanupCanvas = createCanvasWithEvents(['cleanup.started']);
1341
- canvases.set('cleanup-operations.otel.canvas', cleanupCanvas);
1342
-
1343
- // Build registry without library
1344
- const eventRegistry = EventRegistry.build(undefined, canvases);
1345
-
1346
- // Create a canvas WITHOUT the event
1347
- const canvas = createCanvasWithEvents(['other.event']);
1348
-
1349
- // Workflow references an event not in its canvas
1350
- const workflow: WorkflowTemplate = {
1351
- version: '1.0.0',
1352
- canvas: 'test.otel.canvas',
1353
- name: 'Test Workflow',
1354
- description: 'Test',
1355
- mode: 'span-tree',
1356
- scenarioSelection: 'first-match',
1357
- scenarios: [
1358
- {
1359
- id: 'default',
1360
- priority: 1,
1361
- description: 'Default',
1362
- template: {
1363
- events: {
1364
- 'cleanup.started': 'Cleanup started', // Not in canvas but in another canvas
1365
- },
1366
- },
1367
- },
1368
- ],
1369
- };
1370
-
1371
- const context: WorkflowValidationContext = {
1372
- workflow,
1373
- workflowPath: 'test.workflow.json',
1374
- canvas,
1375
- canvasPath: join(tempDir, 'test.otel.canvas'),
1376
- basePath: tempDir,
1377
- eventRegistry,
1378
- };
1379
-
1380
- const result = await validator.validate(context);
1381
-
1382
- // Should have an error about the missing event
1383
- const eventViolation = result.violations.find(
1384
- v => v.ruleId === 'workflow-event-sync' && v.message.includes('cleanup.started')
1385
- );
1386
-
1387
- expect(eventViolation).toBeDefined();
1388
- // Should mention where the event was found
1389
- expect(eventViolation!.message).toContain('cleanup-operations.otel.canvas');
1390
- // Should suggest adding to library
1391
- expect(eventViolation!.suggestion).toContain('library.yaml');
1392
- });
1393
-
1394
- it('should show generic error when event not found anywhere', async () => {
1395
- // Empty registry
1396
- const eventRegistry = EventRegistry.build(undefined, new Map());
1397
-
1398
- // Create a canvas without the event
1399
- const canvas = createCanvasWithEvents(['other.event']);
1400
-
1401
- // Workflow references an event that doesn't exist anywhere
1402
- const workflow: WorkflowTemplate = {
1403
- version: '1.0.0',
1404
- canvas: 'test.otel.canvas',
1405
- name: 'Test Workflow',
1406
- description: 'Test',
1407
- mode: 'span-tree',
1408
- scenarioSelection: 'first-match',
1409
- scenarios: [
1410
- {
1411
- id: 'default',
1412
- priority: 1,
1413
- description: 'Default',
1414
- template: {
1415
- events: {
1416
- 'nonexistent.event': 'Does not exist anywhere',
1417
- },
1418
- },
1419
- },
1420
- ],
1421
- };
1422
-
1423
- const context: WorkflowValidationContext = {
1424
- workflow,
1425
- workflowPath: 'test.workflow.json',
1426
- canvas,
1427
- canvasPath: join(tempDir, 'test.otel.canvas'),
1428
- basePath: tempDir,
1429
- eventRegistry,
1430
- };
1431
-
1432
- const result = await validator.validate(context);
1433
-
1434
- // Should have an error about the missing event
1435
- const eventViolation = result.violations.find(
1436
- v => v.ruleId === 'workflow-event-sync' && v.message.includes('nonexistent.event')
1437
- );
1438
-
1439
- expect(eventViolation).toBeDefined();
1440
- // Should show the standard error message
1441
- expect(eventViolation!.message).toContain('not defined in canvas');
1442
- });
1443
-
1444
- it('should work without eventRegistry (backward compatible)', async () => {
1445
- // Create a canvas without the event
1446
- const canvas = createCanvasWithEvents(['other.event']);
1447
-
1448
- // Workflow references an event not in canvas
1449
- const workflow: WorkflowTemplate = {
1450
- version: '1.0.0',
1451
- canvas: 'test.otel.canvas',
1452
- name: 'Test Workflow',
1453
- description: 'Test',
1454
- mode: 'span-tree',
1455
- scenarioSelection: 'first-match',
1456
- scenarios: [
1457
- {
1458
- id: 'default',
1459
- priority: 1,
1460
- description: 'Default',
1461
- template: {
1462
- events: {
1463
- 'missing.event': 'Not in canvas',
1464
- },
1465
- },
1466
- },
1467
- ],
1468
- };
1469
-
1470
- // No eventRegistry provided
1471
- const context: WorkflowValidationContext = {
1472
- workflow,
1473
- workflowPath: 'test.workflow.json',
1474
- canvas,
1475
- canvasPath: join(tempDir, 'test.otel.canvas'),
1476
- basePath: tempDir,
1477
- // eventRegistry: undefined
1478
- };
1479
-
1480
- const result = await validator.validate(context);
1481
-
1482
- // Should still report the error, just without enhanced suggestions
1483
- const eventViolation = result.violations.find(
1484
- v => v.ruleId === 'workflow-event-sync' && v.message.includes('missing.event')
1485
- );
1486
-
1487
- expect(eventViolation).toBeDefined();
1488
- expect(eventViolation!.message).toContain('not defined in canvas');
1489
- });
1490
- });
1491
-
1492
- // ============================================================================
1493
- // Event Connectivity Validation Tests
1494
- // ============================================================================
1495
-
1496
- describe('checkEventConnectivity', () => {
1497
- it('should pass when all events are connected', async () => {
1498
- const workflow: WorkflowTemplate = {
1499
- version: '1.0.0',
1500
- name: 'Test',
1501
- description: 'Test workflow',
1502
- mode: 'span-tree',
1503
- canvas: 'test.otel.canvas',
1504
- scenarios: [{
1505
- id: 'test',
1506
- priority: 1,
1507
- description: 'Test scenario',
1508
- template: {
1509
- events: {
1510
- 'event.started': 'Started',
1511
- 'event.processing': 'Processing',
1512
- 'event.complete': 'Complete'
1513
- }
1514
- }
1515
- }]
1516
- };
1517
-
1518
- const canvas: ExtendedCanvas = {
1519
- nodes: [
1520
- { id: 'n1', type: 'otel-event', x: 0, y: 0, width: 100, height: 50, event: { name: 'event.started', attributes: {} } },
1521
- { id: 'n2', type: 'otel-event', x: 150, y: 0, width: 100, height: 50, event: { name: 'event.processing', attributes: {} } },
1522
- { id: 'n3', type: 'otel-event', x: 300, y: 0, width: 100, height: 50, event: { name: 'event.complete', attributes: {} } }
1523
- ],
1524
- edges: [
1525
- { id: 'e1', fromNode: 'n1', toNode: 'n2' },
1526
- { id: 'e2', fromNode: 'n2', toNode: 'n3' }
1527
- ],
1528
- pv: {
1529
- version: '1.0.0',
1530
- name: 'Test Canvas',
1531
- markdown: 'test.md'
1532
- }
1533
- };
1534
-
1535
- const context: WorkflowValidationContext = {
1536
- workflow,
1537
- workflowPath: 'test.workflow.json',
1538
- canvas,
1539
- basePath: tempDir
1540
- };
1541
-
1542
- const result = await validator.validate(context);
1543
- const connectivityViolations = result.violations.filter(v => v.ruleId === 'workflow-event-connectivity');
1544
- expect(connectivityViolations).toHaveLength(0);
1545
- });
1546
-
1547
- it('should error when events are disconnected', async () => {
1548
- const workflow: WorkflowTemplate = {
1549
- version: '1.0.0',
1550
- name: 'Test',
1551
- description: 'Test workflow',
1552
- mode: 'span-tree',
1553
- canvas: 'test.otel.canvas',
1554
- scenarios: [{
1555
- id: 'test',
1556
- priority: 1,
1557
- description: 'Test scenario',
1558
- template: {
1559
- events: {
1560
- 'event.started': 'Started',
1561
- 'event.complete': 'Complete',
1562
- 'event.error': 'Error' // Disconnected - no path from started/complete to error
1563
- }
1564
- }
1565
- }]
1566
- };
1567
-
1568
- const canvas: ExtendedCanvas = {
1569
- nodes: [
1570
- { id: 'n1', type: 'otel-event', x: 0, y: 0, width: 100, height: 50, event: { name: 'event.started', attributes: {} } },
1571
- { id: 'n2', type: 'otel-event', x: 150, y: 0, width: 100, height: 50, event: { name: 'event.complete', attributes: {} } },
1572
- { id: 'n3', type: 'otel-event', x: 0, y: 100, width: 100, height: 50, event: { name: 'event.error', attributes: {} } }
1573
- ],
1574
- edges: [
1575
- { id: 'e1', fromNode: 'n1', toNode: 'n2' }
1576
- // n3 is isolated - no edges connecting it to n1 or n2
1577
- ],
1578
- pv: {
1579
- version: '1.0.0',
1580
- name: 'Test Canvas',
1581
- markdown: 'test.md'
1582
- }
1583
- };
1584
-
1585
- const context: WorkflowValidationContext = {
1586
- workflow,
1587
- workflowPath: 'test.workflow.json',
1588
- canvas,
1589
- basePath: tempDir
1590
- };
1591
-
1592
- const result = await validator.validate(context);
1593
- const connectivityViolations = result.violations.filter(v => v.ruleId === 'workflow-event-connectivity');
1594
- expect(connectivityViolations).toHaveLength(1);
1595
- expect(connectivityViolations[0].severity).toBe('error');
1596
- expect(connectivityViolations[0].message).toContain('disconnected');
1597
- });
1598
-
1599
- it('should error when canvas has no edges but workflow has multi-event scenarios', async () => {
1600
- const workflow: WorkflowTemplate = {
1601
- version: '1.0.0',
1602
- name: 'Test',
1603
- description: 'Test workflow',
1604
- mode: 'span-tree',
1605
- canvas: 'test.otel.canvas',
1606
- scenarios: [{
1607
- id: 'test',
1608
- priority: 1,
1609
- description: 'Test scenario',
1610
- template: {
1611
- events: {
1612
- 'event.started': 'Started',
1613
- 'event.complete': 'Complete'
1614
- }
1615
- }
1616
- }]
1617
- };
1618
-
1619
- const canvas: ExtendedCanvas = {
1620
- nodes: [
1621
- { id: 'n1', type: 'otel-event', x: 0, y: 0, width: 100, height: 50, event: { name: 'event.started', attributes: {} } },
1622
- { id: 'n2', type: 'otel-event', x: 150, y: 0, width: 100, height: 50, event: { name: 'event.complete', attributes: {} } }
1623
- ],
1624
- edges: [], // No edges defined
1625
- pv: {
1626
- version: '1.0.0',
1627
- name: 'Test Canvas',
1628
- markdown: 'test.md'
1629
- }
1630
- };
1631
-
1632
- const context: WorkflowValidationContext = {
1633
- workflow,
1634
- workflowPath: 'test.workflow.json',
1635
- canvas,
1636
- basePath: tempDir
1637
- };
1638
-
1639
- const result = await validator.validate(context);
1640
- // Should produce connectivity violation when canvas has no edges but scenario has multiple events
1641
- const connectivityViolations = result.violations.filter(v => v.ruleId === 'workflow-event-connectivity');
1642
- expect(connectivityViolations).toHaveLength(1);
1643
- expect(connectivityViolations[0].severity).toBe('error');
1644
- expect(connectivityViolations[0].message).toContain('has no edges');
1645
- });
1646
-
1647
- it('should skip check when scenario has less than 2 events', async () => {
1648
- const workflow: WorkflowTemplate = {
1649
- version: '1.0.0',
1650
- name: 'Test',
1651
- description: 'Test workflow',
1652
- mode: 'span-tree',
1653
- canvas: 'test.otel.canvas',
1654
- scenarios: [{
1655
- id: 'test',
1656
- priority: 1,
1657
- description: 'Test scenario',
1658
- template: {
1659
- events: {
1660
- 'event.started': 'Started'
1661
- }
1662
- }
1663
- }]
1664
- };
1665
-
1666
- const canvas: ExtendedCanvas = {
1667
- nodes: [
1668
- { id: 'n1', type: 'otel-event', x: 0, y: 0, width: 100, height: 50, event: { name: 'event.started', attributes: {} } }
1669
- ],
1670
- edges: [],
1671
- pv: {
1672
- version: '1.0.0',
1673
- name: 'Test Canvas',
1674
- markdown: 'test.md'
1675
- }
1676
- };
1677
-
1678
- const context: WorkflowValidationContext = {
1679
- workflow,
1680
- workflowPath: 'test.workflow.json',
1681
- canvas,
1682
- basePath: tempDir
1683
- };
1684
-
1685
- const result = await validator.validate(context);
1686
- const connectivityViolations = result.violations.filter(v => v.ruleId === 'workflow-event-connectivity');
1687
- expect(connectivityViolations).toHaveLength(0);
1688
- });
1689
-
1690
- it('should handle events connected through intermediate nodes', async () => {
1691
- const workflow: WorkflowTemplate = {
1692
- version: '1.0.0',
1693
- name: 'Test',
1694
- description: 'Test workflow',
1695
- mode: 'span-tree',
1696
- canvas: 'test.otel.canvas',
1697
- scenarios: [{
1698
- id: 'test',
1699
- priority: 1,
1700
- description: 'Test scenario',
1701
- template: {
1702
- events: {
1703
- 'event.start': 'Start',
1704
- 'event.end': 'End'
1705
- }
1706
- }
1707
- }]
1708
- };
1709
-
1710
- const canvas: ExtendedCanvas = {
1711
- nodes: [
1712
- { id: 'n1', type: 'otel-event', x: 0, y: 0, width: 100, height: 50, event: { name: 'event.start', attributes: {} } },
1713
- { id: 'n2', type: 'otel-event', x: 150, y: 0, width: 100, height: 50, event: { name: 'event.middle1', attributes: {} } },
1714
- { id: 'n3', type: 'otel-event', x: 300, y: 0, width: 100, height: 50, event: { name: 'event.middle2', attributes: {} } },
1715
- { id: 'n4', type: 'otel-event', x: 450, y: 0, width: 100, height: 50, event: { name: 'event.end', attributes: {} } }
1716
- ],
1717
- edges: [
1718
- { id: 'e1', fromNode: 'n1', toNode: 'n2' },
1719
- { id: 'e2', fromNode: 'n2', toNode: 'n3' },
1720
- { id: 'e3', fromNode: 'n3', toNode: 'n4' }
1721
- ],
1722
- pv: {
1723
- version: '1.0.0',
1724
- name: 'Test Canvas',
1725
- markdown: 'test.md'
1726
- }
1727
- };
1728
-
1729
- const context: WorkflowValidationContext = {
1730
- workflow,
1731
- workflowPath: 'test.workflow.json',
1732
- canvas,
1733
- basePath: tempDir
1734
- };
1735
-
1736
- const result = await validator.validate(context);
1737
- const connectivityViolations = result.violations.filter(v => v.ruleId === 'workflow-event-connectivity');
1738
- // Should pass because events are connected through intermediate nodes
1739
- expect(connectivityViolations).toHaveLength(0);
1740
- });
1741
- });
1742
-
1743
- // ============================================================================
1744
- // Subset Validation Tests
1745
- // ============================================================================
1746
-
1747
- describe('checkScenarioSubsets', () => {
1748
- it('should pass when scenarios have mutually exclusive event sets', async () => {
1749
- const workflow: WorkflowTemplate = {
1750
- version: '1.0.0',
1751
- name: 'Test',
1752
- description: 'Test workflow',
1753
- mode: 'span-tree',
1754
- canvas: 'test.otel.canvas',
1755
- scenarioSelection: 'first-match',
1756
- scenarios: [
1757
- {
1758
- id: 'success',
1759
- priority: 1,
1760
- description: 'Success scenario',
1761
- template: {
1762
- events: {
1763
- 'payment.authorized': 'Payment authorized',
1764
- 'order.confirmed': 'Order confirmed'
1765
- }
1766
- }
1767
- },
1768
- {
1769
- id: 'failure',
1770
- priority: 2,
1771
- description: 'Failure scenario',
1772
- template: {
1773
- events: {
1774
- 'payment.declined': 'Payment declined',
1775
- 'error.logged': 'Error logged'
1776
- }
1777
- }
1778
- }
1779
- ]
1780
- };
1781
-
1782
- const context: WorkflowValidationContext = {
1783
- workflow,
1784
- workflowPath: 'test.workflow.json',
1785
- basePath: tempDir
1786
- };
1787
-
1788
- const result = await validator.validate(context);
1789
- const subsetViolations = result.violations.filter(v => v.ruleId === 'workflow-scenario-subset');
1790
- expect(subsetViolations).toHaveLength(0);
1791
- });
1792
-
1793
- it('should pass when scenarios have overlapping but not subset event sets', async () => {
1794
- const workflow: WorkflowTemplate = {
1795
- version: '1.0.0',
1796
- name: 'Test',
1797
- description: 'Test workflow',
1798
- mode: 'span-tree',
1799
- canvas: 'test.otel.canvas',
1800
- scenarioSelection: 'first-match',
1801
- scenarios: [
1802
- {
1803
- id: 'google-oauth',
1804
- priority: 1,
1805
- description: 'Google OAuth',
1806
- template: {
1807
- events: {
1808
- 'oauth.google': 'Google auth',
1809
- 'oauth.complete': 'OAuth complete',
1810
- 'user.created': 'User created'
1811
- }
1812
- }
1813
- },
1814
- {
1815
- id: 'email-password',
1816
- priority: 2,
1817
- description: 'Email/password signup',
1818
- template: {
1819
- events: {
1820
- 'email.verified': 'Email verified',
1821
- 'password.set': 'Password set',
1822
- 'user.created': 'User created'
1823
- }
1824
- }
1825
- }
1826
- ]
1827
- };
1828
-
1829
- const context: WorkflowValidationContext = {
1830
- workflow,
1831
- workflowPath: 'test.workflow.json',
1832
- basePath: tempDir
1833
- };
1834
-
1835
- const result = await validator.validate(context);
1836
- const subsetViolations = result.violations.filter(v => v.ruleId === 'workflow-scenario-subset');
1837
- expect(subsetViolations).toHaveLength(0);
1838
- });
1839
-
1840
- it('should error when one scenario is a strict subset of another', async () => {
1841
- const workflow: WorkflowTemplate = {
1842
- version: '1.0.0',
1843
- name: 'Test',
1844
- description: 'Test workflow',
1845
- mode: 'span-tree',
1846
- canvas: 'test.otel.canvas',
1847
- scenarioSelection: 'first-match',
1848
- scenarios: [
1849
- {
1850
- id: 'basic-checkout',
1851
- priority: 1,
1852
- description: 'Basic checkout',
1853
- template: {
1854
- events: {
1855
- 'cart.viewed': 'Cart viewed',
1856
- 'checkout.started': 'Checkout started'
1857
- }
1858
- }
1859
- },
1860
- {
1861
- id: 'complete-checkout',
1862
- priority: 2,
1863
- description: 'Complete checkout',
1864
- template: {
1865
- events: {
1866
- 'cart.viewed': 'Cart viewed',
1867
- 'checkout.started': 'Checkout started',
1868
- 'payment.complete': 'Payment complete'
1869
- }
1870
- }
1871
- }
1872
- ]
1873
- };
1874
-
1875
- const context: WorkflowValidationContext = {
1876
- workflow,
1877
- workflowPath: 'test.workflow.json',
1878
- basePath: tempDir
1879
- };
1880
-
1881
- const result = await validator.validate(context);
1882
- const subsetViolations = result.violations.filter(v => v.ruleId === 'workflow-scenario-subset');
1883
-
1884
- expect(subsetViolations).toHaveLength(1);
1885
- expect(subsetViolations[0].severity).toBe('error');
1886
- expect(subsetViolations[0].message).toContain('basic-checkout');
1887
- expect(subsetViolations[0].message).toContain('strict subset');
1888
- expect(subsetViolations[0].message).toContain('complete-checkout');
1889
- expect(subsetViolations[0].impact).toContain('ambiguous');
1890
- expect(subsetViolations[0].suggestion).toContain('Merge into one scenario');
1891
- expect(subsetViolations[0].suggestion).toContain('mutually exclusive');
1892
- });
1893
-
1894
- it('should detect reverse subset relationship', async () => {
1895
- const workflow: WorkflowTemplate = {
1896
- version: '1.0.0',
1897
- name: 'Test',
1898
- description: 'Test workflow',
1899
- mode: 'span-tree',
1900
- canvas: 'test.otel.canvas',
1901
- scenarioSelection: 'first-match',
1902
- scenarios: [
1903
- {
1904
- id: 'complete-flow',
1905
- priority: 1,
1906
- description: 'Complete flow',
1907
- template: {
1908
- events: {
1909
- 'step.one': 'Step 1',
1910
- 'step.two': 'Step 2',
1911
- 'step.three': 'Step 3'
1912
- }
1913
- }
1914
- },
1915
- {
1916
- id: 'partial-flow',
1917
- priority: 2,
1918
- description: 'Partial flow',
1919
- template: {
1920
- events: {
1921
- 'step.one': 'Step 1',
1922
- 'step.two': 'Step 2'
1923
- }
1924
- }
1925
- }
1926
- ]
1927
- };
1928
-
1929
- const context: WorkflowValidationContext = {
1930
- workflow,
1931
- workflowPath: 'test.workflow.json',
1932
- basePath: tempDir
1933
- };
1934
-
1935
- const result = await validator.validate(context);
1936
- const subsetViolations = result.violations.filter(v => v.ruleId === 'workflow-scenario-subset');
1937
-
1938
- expect(subsetViolations).toHaveLength(1);
1939
- expect(subsetViolations[0].message).toContain('partial-flow');
1940
- expect(subsetViolations[0].message).toContain('strict subset');
1941
- expect(subsetViolations[0].message).toContain('complete-flow');
1942
- });
1943
-
1944
- it('should pass when scenarios have same number of events but different events', async () => {
1945
- const workflow: WorkflowTemplate = {
1946
- version: '1.0.0',
1947
- name: 'Test',
1948
- description: 'Test workflow',
1949
- mode: 'span-tree',
1950
- canvas: 'test.otel.canvas',
1951
- scenarioSelection: 'first-match',
1952
- scenarios: [
1953
- {
1954
- id: 'scenario-a',
1955
- priority: 1,
1956
- description: 'Scenario A',
1957
- template: {
1958
- events: {
1959
- 'event.a': 'Event A',
1960
- 'event.b': 'Event B'
1961
- }
1962
- }
1963
- },
1964
- {
1965
- id: 'scenario-b',
1966
- priority: 2,
1967
- description: 'Scenario B',
1968
- template: {
1969
- events: {
1970
- 'event.c': 'Event C',
1971
- 'event.d': 'Event D'
1972
- }
1973
- }
1974
- }
1975
- ]
1976
- };
1977
-
1978
- const context: WorkflowValidationContext = {
1979
- workflow,
1980
- workflowPath: 'test.workflow.json',
1981
- basePath: tempDir
1982
- };
1983
-
1984
- const result = await validator.validate(context);
1985
- const subsetViolations = result.violations.filter(v => v.ruleId === 'workflow-scenario-subset');
1986
- expect(subsetViolations).toHaveLength(0);
1987
- });
1988
-
1989
- it('should pass when workflow has only one scenario', async () => {
1990
- const workflow: WorkflowTemplate = {
1991
- version: '1.0.0',
1992
- name: 'Test',
1993
- description: 'Test workflow',
1994
- mode: 'span-tree',
1995
- canvas: 'test.otel.canvas',
1996
- scenarioSelection: 'first-match',
1997
- scenarios: [
1998
- {
1999
- id: 'only-scenario',
2000
- priority: 1,
2001
- description: 'Only scenario',
2002
- template: {
2003
- events: {
2004
- 'event.one': 'Event 1'
2005
- }
2006
- }
2007
- }
2008
- ]
2009
- };
2010
-
2011
- const context: WorkflowValidationContext = {
2012
- workflow,
2013
- workflowPath: 'test.workflow.json',
2014
- basePath: tempDir
2015
- };
2016
-
2017
- const result = await validator.validate(context);
2018
- const subsetViolations = result.violations.filter(v => v.ruleId === 'workflow-scenario-subset');
2019
- expect(subsetViolations).toHaveLength(0);
2020
- });
2021
-
2022
- it('should handle scenarios with empty event sets', async () => {
2023
- const workflow: WorkflowTemplate = {
2024
- version: '1.0.0',
2025
- name: 'Test',
2026
- description: 'Test workflow',
2027
- mode: 'span-tree',
2028
- canvas: 'test.otel.canvas',
2029
- scenarioSelection: 'first-match',
2030
- scenarios: [
2031
- {
2032
- id: 'empty-events',
2033
- priority: 1,
2034
- description: 'Empty events',
2035
- template: {
2036
- introduction: 'Test',
2037
- events: {}
2038
- }
2039
- },
2040
- {
2041
- id: 'with-events',
2042
- priority: 2,
2043
- description: 'With events',
2044
- template: {
2045
- events: {
2046
- 'event.one': 'Event 1'
2047
- }
2048
- }
2049
- }
2050
- ]
2051
- };
2052
-
2053
- const context: WorkflowValidationContext = {
2054
- workflow,
2055
- workflowPath: 'test.workflow.json',
2056
- basePath: tempDir
2057
- };
2058
-
2059
- const result = await validator.validate(context);
2060
- const subsetViolations = result.violations.filter(v => v.ruleId === 'workflow-scenario-subset');
2061
-
2062
- // Empty set is a strict subset of any non-empty set
2063
- expect(subsetViolations).toHaveLength(1);
2064
- expect(subsetViolations[0].message).toContain('empty-events');
2065
- expect(subsetViolations[0].message).toContain('strict subset');
2066
- });
2067
-
2068
- it('should detect multiple subset violations in a workflow', async () => {
2069
- const workflow: WorkflowTemplate = {
2070
- version: '1.0.0',
2071
- name: 'Test',
2072
- description: 'Test workflow',
2073
- mode: 'span-tree',
2074
- canvas: 'test.otel.canvas',
2075
- scenarioSelection: 'first-match',
2076
- scenarios: [
2077
- {
2078
- id: 'minimal',
2079
- priority: 1,
2080
- description: 'Minimal',
2081
- template: {
2082
- events: {
2083
- 'event.a': 'A'
2084
- }
2085
- }
2086
- },
2087
- {
2088
- id: 'medium',
2089
- priority: 2,
2090
- description: 'Medium',
2091
- template: {
2092
- events: {
2093
- 'event.a': 'A',
2094
- 'event.b': 'B'
2095
- }
2096
- }
2097
- },
2098
- {
2099
- id: 'complete',
2100
- priority: 3,
2101
- description: 'Complete',
2102
- template: {
2103
- events: {
2104
- 'event.a': 'A',
2105
- 'event.b': 'B',
2106
- 'event.c': 'C'
2107
- }
2108
- }
2109
- }
2110
- ]
2111
- };
2112
-
2113
- const context: WorkflowValidationContext = {
2114
- workflow,
2115
- workflowPath: 'test.workflow.json',
2116
- basePath: tempDir
2117
- };
2118
-
2119
- const result = await validator.validate(context);
2120
- const subsetViolations = result.violations.filter(v => v.ruleId === 'workflow-scenario-subset');
2121
-
2122
- // Should detect:
2123
- // 1. minimal ⊂ medium
2124
- // 2. minimal ⊂ complete
2125
- // 3. medium ⊂ complete
2126
- expect(subsetViolations.length).toBeGreaterThanOrEqual(3);
2127
- });
2128
-
2129
- it('should provide helpful fix suggestions in error messages', async () => {
2130
- const workflow: WorkflowTemplate = {
2131
- version: '1.0.0',
2132
- name: 'Test',
2133
- description: 'Test workflow',
2134
- mode: 'span-tree',
2135
- canvas: 'test.otel.canvas',
2136
- scenarioSelection: 'first-match',
2137
- scenarios: [
2138
- {
2139
- id: 'abandoned',
2140
- priority: 1,
2141
- description: 'Abandoned cart',
2142
- template: {
2143
- events: {
2144
- 'cart.created': 'Cart created',
2145
- 'items.added': 'Items added'
2146
- }
2147
- }
2148
- },
2149
- {
2150
- id: 'completed',
2151
- priority: 2,
2152
- description: 'Completed purchase',
2153
- template: {
2154
- events: {
2155
- 'cart.created': 'Cart created',
2156
- 'items.added': 'Items added',
2157
- 'checkout.complete': 'Checkout complete'
2158
- }
2159
- }
2160
- }
2161
- ]
2162
- };
2163
-
2164
- const context: WorkflowValidationContext = {
2165
- workflow,
2166
- workflowPath: 'test.workflow.json',
2167
- basePath: tempDir
2168
- };
2169
-
2170
- const result = await validator.validate(context);
2171
- const subsetViolations = result.violations.filter(v => v.ruleId === 'workflow-scenario-subset');
2172
-
2173
- expect(subsetViolations).toHaveLength(1);
2174
-
2175
- const violation = subsetViolations[0];
2176
- expect(violation.suggestion).toContain('cart.created');
2177
- expect(violation.suggestion).toContain('items.added');
2178
- expect(violation.suggestion).toContain('checkout.complete');
2179
- expect(violation.suggestion).toContain('Merge into one scenario');
2180
- expect(violation.suggestion).toContain('template conditionals');
2181
- expect(violation.suggestion).toContain('mutually exclusive');
2182
- expect(violation.suggestion).toContain('distinguishing events');
2183
- });
2184
-
2185
- it('should error when two scenarios have identical event sets', async () => {
2186
- const workflow: WorkflowTemplate = {
2187
- version: '1.0.0',
2188
- name: 'Test',
2189
- description: 'Test workflow',
2190
- mode: 'span-tree',
2191
- canvas: 'test.otel.canvas',
2192
- spanPattern: 'test.*',
2193
- scenarios: [
2194
- {
2195
- id: 'scenario-a',
2196
- priority: 1,
2197
- description: 'First scenario',
2198
- template: {
2199
- summary: 'Test A',
2200
- events: {
2201
- 'event.start': 'Started',
2202
- 'event.complete': 'Completed'
2203
- }
2204
- }
2205
- },
2206
- {
2207
- id: 'scenario-b',
2208
- priority: 2,
2209
- description: 'Second scenario with same events',
2210
- template: {
2211
- summary: 'Test B',
2212
- events: {
2213
- 'event.start': 'Started',
2214
- 'event.complete': 'Completed'
2215
- }
2216
- }
2217
- }
2218
- ]
2219
- };
2220
-
2221
- const context: WorkflowValidationContext = {
2222
- workflow,
2223
- workflowPath: 'test.workflow.json',
2224
- basePath: tempDir
2225
- };
2226
-
2227
- const result = await validator.validate(context);
2228
- const identicalViolations = result.violations.filter(v => v.ruleId === 'workflow-scenario-identical');
2229
-
2230
- expect(identicalViolations).toHaveLength(1);
2231
- expect(identicalViolations[0].severity).toBe('error');
2232
- expect(identicalViolations[0].message).toContain('scenario-a');
2233
- expect(identicalViolations[0].message).toContain('scenario-b');
2234
- expect(identicalViolations[0].message).toContain('identical event sets');
2235
- expect(identicalViolations[0].impact).toContain('arbitrary');
2236
- expect(identicalViolations[0].suggestion).toContain('Merge into a single scenario');
2237
- });
2238
-
2239
- it('should pass when scenarios have same size but different events', async () => {
2240
- const workflow: WorkflowTemplate = {
2241
- version: '1.0.0',
2242
- name: 'Test',
2243
- description: 'Test workflow',
2244
- mode: 'span-tree',
2245
- canvas: 'test.otel.canvas',
2246
- spanPattern: 'test.*',
2247
- scenarios: [
2248
- {
2249
- id: 'oauth-flow',
2250
- priority: 1,
2251
- description: 'OAuth login',
2252
- template: {
2253
- summary: 'OAuth',
2254
- events: {
2255
- 'oauth.start': 'Started',
2256
- 'oauth.complete': 'Completed'
2257
- }
2258
- }
2259
- },
2260
- {
2261
- id: 'password-flow',
2262
- priority: 2,
2263
- description: 'Password login',
2264
- template: {
2265
- summary: 'Password',
2266
- events: {
2267
- 'password.start': 'Started',
2268
- 'password.complete': 'Completed'
2269
- }
2270
- }
2271
- }
2272
- ]
2273
- };
2274
-
2275
- const context: WorkflowValidationContext = {
2276
- workflow,
2277
- workflowPath: 'test.workflow.json',
2278
- basePath: tempDir
2279
- };
2280
-
2281
- const result = await validator.validate(context);
2282
- const identicalViolations = result.violations.filter(v => v.ruleId === 'workflow-scenario-identical');
2283
- const subsetViolations = result.violations.filter(v => v.ruleId === 'workflow-scenario-subset');
2284
-
2285
- expect(identicalViolations).toHaveLength(0);
2286
- expect(subsetViolations).toHaveLength(0);
2287
- });
2288
- });
2289
-
2290
- // ============================================================================
2291
- // Span Pattern Validation Tests
2292
- // ============================================================================
2293
-
2294
- describe('validateSpanPatterns (CLI-level validation)', () => {
2295
- it('should pass when all workflows have unique spanPatterns', () => {
2296
- const workflows = [
2297
- {
2298
- workflow: {
2299
- version: '1.0.0',
2300
- canvas: 'test.otel.canvas',
2301
- name: 'Payment Workflow',
2302
- description: 'Payment processing',
2303
- spanPattern: 'payment.authorize',
2304
- scenarioSelection: 'first-match' as const,
2305
- scenarios: [],
2306
- },
2307
- workflowPath: 'workflows/payment.workflow.json',
2308
- },
2309
- {
2310
- workflow: {
2311
- version: '1.0.0',
2312
- canvas: 'test.otel.canvas',
2313
- name: 'Inventory Workflow',
2314
- description: 'Inventory management',
2315
- spanPattern: 'inventory.reserve',
2316
- scenarioSelection: 'first-match' as const,
2317
- scenarios: [],
2318
- },
2319
- workflowPath: 'workflows/inventory.workflow.json',
2320
- },
2321
- ];
2322
-
2323
- const violations = WorkflowValidator.validateSpanPatterns(workflows);
2324
- expect(violations).toHaveLength(0);
2325
- });
2326
-
2327
- it('should error when workflows have duplicate spanPatterns', () => {
2328
- const workflows = [
2329
- {
2330
- workflow: {
2331
- version: '1.0.0',
2332
- canvas: 'test1.otel.canvas',
2333
- name: 'Payment Workflow 1',
2334
- description: 'Payment processing',
2335
- spanPattern: 'payment.authorize',
2336
- scenarioSelection: 'first-match' as const,
2337
- scenarios: [],
2338
- },
2339
- workflowPath: 'workflows/payment1.workflow.json',
2340
- },
2341
- {
2342
- workflow: {
2343
- version: '1.0.0',
2344
- canvas: 'test2.otel.canvas',
2345
- name: 'Payment Workflow 2',
2346
- description: 'Payment processing alternative',
2347
- spanPattern: 'payment.authorize',
2348
- scenarioSelection: 'first-match' as const,
2349
- scenarios: [],
2350
- },
2351
- workflowPath: 'workflows/payment2.workflow.json',
2352
- },
2353
- ];
2354
-
2355
- const violations = WorkflowValidator.validateSpanPatterns(workflows);
2356
-
2357
- expect(violations).toHaveLength(2); // One for each workflow
2358
- expect(violations[0].ruleId).toBe('workflow-span-pattern-duplicate');
2359
- expect(violations[0].severity).toBe('error');
2360
- expect(violations[0].message).toContain('payment.authorize');
2361
- expect(violations[0].message).toContain('Duplicate spanPattern');
2362
- expect(violations[0].suggestion).toContain('payment2.workflow.json');
2363
- });
2364
-
2365
- it('should detect multiple duplicates across many workflows', () => {
2366
- const workflows = [
2367
- {
2368
- workflow: {
2369
- version: '1.0.0',
2370
- canvas: 'test.otel.canvas',
2371
- name: 'Workflow A1',
2372
- description: 'Test',
2373
- spanPattern: 'span.a',
2374
- scenarioSelection: 'first-match' as const,
2375
- scenarios: [],
2376
- },
2377
- workflowPath: 'a1.workflow.json',
2378
- },
2379
- {
2380
- workflow: {
2381
- version: '1.0.0',
2382
- canvas: 'test.otel.canvas',
2383
- name: 'Workflow A2',
2384
- description: 'Test',
2385
- spanPattern: 'span.a',
2386
- scenarioSelection: 'first-match' as const,
2387
- scenarios: [],
2388
- },
2389
- workflowPath: 'a2.workflow.json',
2390
- },
2391
- {
2392
- workflow: {
2393
- version: '1.0.0',
2394
- canvas: 'test.otel.canvas',
2395
- name: 'Workflow B',
2396
- description: 'Test',
2397
- spanPattern: 'span.b',
2398
- scenarioSelection: 'first-match' as const,
2399
- scenarios: [],
2400
- },
2401
- workflowPath: 'b.workflow.json',
2402
- },
2403
- {
2404
- workflow: {
2405
- version: '1.0.0',
2406
- canvas: 'test.otel.canvas',
2407
- name: 'Workflow C1',
2408
- description: 'Test',
2409
- spanPattern: 'span.c',
2410
- scenarioSelection: 'first-match' as const,
2411
- scenarios: [],
2412
- },
2413
- workflowPath: 'c1.workflow.json',
2414
- },
2415
- {
2416
- workflow: {
2417
- version: '1.0.0',
2418
- canvas: 'test.otel.canvas',
2419
- name: 'Workflow C2',
2420
- description: 'Test',
2421
- spanPattern: 'span.c',
2422
- scenarioSelection: 'first-match' as const,
2423
- scenarios: [],
2424
- },
2425
- workflowPath: 'c2.workflow.json',
2426
- },
2427
- ];
2428
-
2429
- const violations = WorkflowValidator.validateSpanPatterns(workflows);
2430
-
2431
- // Should have violations for: a1, a2, c1, c2 (4 workflows with duplicates)
2432
- expect(violations.length).toBe(4);
2433
-
2434
- // Check that span.b has no violations
2435
- const spanBViolations = violations.filter(v => v.file === 'b.workflow.json');
2436
- expect(spanBViolations).toHaveLength(0);
2437
- });
2438
-
2439
- it('should handle workflows without spanPattern gracefully', () => {
2440
- const workflows = [
2441
- {
2442
- workflow: {
2443
- version: '1.0.0',
2444
- canvas: 'test.otel.canvas',
2445
- name: 'Valid Workflow',
2446
- description: 'Test',
2447
- spanPattern: 'payment.authorize',
2448
- scenarioSelection: 'first-match' as const,
2449
- scenarios: [],
2450
- },
2451
- workflowPath: 'valid.workflow.json',
2452
- },
2453
- {
2454
- workflow: {
2455
- version: '1.0.0',
2456
- canvas: 'test.otel.canvas',
2457
- name: 'Invalid Workflow',
2458
- description: 'Test',
2459
- // spanPattern missing - will be caught by individual validation
2460
- scenarioSelection: 'first-match' as const,
2461
- scenarios: [],
2462
- } as Partial<WorkflowTemplate> as WorkflowTemplate,
2463
- workflowPath: 'invalid.workflow.json',
2464
- },
2465
- ];
2466
-
2467
- const violations = WorkflowValidator.validateSpanPatterns(workflows);
2468
-
2469
- // Should not error - just skip the workflow without spanPattern
2470
- expect(violations).toHaveLength(0);
2471
- });
2472
- });
2473
-
2474
- // ============================================================================
2475
- // Span Pattern Schema Validation Tests
2476
- // ============================================================================
2477
-
2478
- describe('checkSchema - spanPattern', () => {
2479
- it('should error when spanPattern is missing', async () => {
2480
- const context = createContext({ spanPattern: undefined });
2481
- const result = await validator.validate(context);
2482
-
2483
- const spanPatternViolations = result.violations.filter(
2484
- v => v.ruleId === 'workflow-schema-valid' && v.path === 'spanPattern'
2485
- );
2486
-
2487
- expect(spanPatternViolations).toHaveLength(1);
2488
- expect(spanPatternViolations[0].message).toContain('Missing required field "spanPattern"');
2489
- expect(spanPatternViolations[0].suggestion).toContain('exact span name');
2490
- });
2491
-
2492
- it('should error when spanPattern is empty string', async () => {
2493
- const context = createContext({ spanPattern: '' });
2494
- const result = await validator.validate(context);
2495
-
2496
- const spanPatternViolations = result.violations.filter(
2497
- v => v.ruleId === 'workflow-schema-valid' && v.path === 'spanPattern'
2498
- );
2499
-
2500
- expect(spanPatternViolations).toHaveLength(1);
2501
- expect(spanPatternViolations[0].message).toContain('Missing required field');
2502
- });
2503
-
2504
- it('should pass when spanPattern is a valid string', async () => {
2505
- const canvasPath = join(tempDir, 'test.otel.canvas');
2506
- writeFileSync(canvasPath, JSON.stringify(createValidCanvas()));
2507
-
2508
- const context = createContext({ spanPattern: 'payment.authorize' }, { canvasPath });
2509
- const result = await validator.validate(context);
2510
-
2511
- const spanPatternViolations = result.violations.filter(
2512
- v => v.ruleId === 'workflow-schema-valid' && v.path === 'spanPattern'
2513
- );
2514
-
2515
- expect(spanPatternViolations).toHaveLength(0);
2516
- });
2517
-
2518
- it('should allow parentheses in spanPattern (Next.js style span names)', async () => {
2519
- const canvasPath = join(tempDir, 'test.otel.canvas');
2520
- writeFileSync(canvasPath, JSON.stringify(createValidCanvas()));
2521
-
2522
- // Next.js creates span names like "executing api route (app) /api/auth/me/route"
2523
- const context = createContext({
2524
- spanPattern: 'executing api route (app) /api/auth/me/route'
2525
- }, { canvasPath });
2526
- const result = await validator.validate(context);
2527
-
2528
- const spanPatternViolations = result.violations.filter(
2529
- v => v.ruleId === 'workflow-span-pattern-exact' && v.path === 'spanPattern'
2530
- );
2531
-
2532
- expect(spanPatternViolations).toHaveLength(0);
2533
- });
2534
-
2535
- it('should error when spanPattern contains wildcard *', async () => {
2536
- const context = createContext({ spanPattern: 'payment.*' });
2537
- const result = await validator.validate(context);
2538
-
2539
- const spanPatternViolations = result.violations.filter(
2540
- v => v.ruleId === 'workflow-span-pattern-exact' && v.path === 'spanPattern'
2541
- );
2542
-
2543
- expect(spanPatternViolations).toHaveLength(1);
2544
- expect(spanPatternViolations[0].message).toContain('wildcard');
2545
- });
2546
-
2547
- it('should error when spanPattern contains regex special characters', async () => {
2548
- const regexPatterns = [
2549
- 'payment[type]', // square brackets
2550
- 'payment{1,2}', // curly braces (quantifier)
2551
- 'payment^start', // caret
2552
- 'payment$end', // dollar
2553
- 'payment|refund', // pipe (alternation)
2554
- 'payment\\d+', // backslash
2555
- 'payment+', // plus
2556
- 'payment?', // question mark
2557
- ];
2558
-
2559
- for (const pattern of regexPatterns) {
2560
- const context = createContext({ spanPattern: pattern });
2561
- const result = await validator.validate(context);
2562
-
2563
- const spanPatternViolations = result.violations.filter(
2564
- v => v.ruleId === 'workflow-span-pattern-exact' && v.path === 'spanPattern'
2565
- );
2566
-
2567
- expect(spanPatternViolations).toHaveLength(1);
2568
- expect(spanPatternViolations[0].message).toContain('regex special characters');
2569
- }
2570
- });
2571
- });
2572
-
2573
- // ============================================================================
2574
- // Event Attribute Requirements
2575
- // ============================================================================
2576
-
2577
- describe('Event Attribute Requirements', () => {
2578
- it('should error when attribute is not used in any template', async () => {
2579
- const workflow: WorkflowTemplate = {
2580
- version: '1.0.0',
2581
- name: 'Test',
2582
- description: 'Test workflow',
2583
- mode: 'span-tree',
2584
- canvas: 'test.otel.canvas',
2585
- scenarios: [{
2586
- id: 'test',
2587
- priority: 1,
2588
- description: 'Test scenario',
2589
- template: {
2590
- events: {
2591
- 'order.completed': 'Order {{orderId}} completed' // total is not used
2592
- }
2593
- }
2594
- }]
2595
- };
2596
-
2597
- const canvas: ExtendedCanvas = {
2598
- nodes: [
2599
- {
2600
- id: 'n1',
2601
- type: 'otel-event',
2602
- x: 0,
2603
- y: 0,
2604
- width: 100,
2605
- height: 50,
2606
- event: {
2607
- name: 'order.completed',
2608
- description: 'Order completion event',
2609
- attributes: {
2610
- orderId: { type: 'string' },
2611
- total: { type: 'number' } // not used in template
2612
- }
2613
- }
2614
- }
2615
- ],
2616
- edges: [],
2617
- pv: { version: '1.0.0', name: 'Test Canvas', markdown: 'test.md' }
2618
- };
2619
-
2620
- const context: WorkflowValidationContext = {
2621
- workflow,
2622
- workflowPath: 'test.workflow.json',
2623
- canvas,
2624
- basePath: tempDir
2625
- };
2626
-
2627
- const result = await validator.validate(context);
2628
- const violations = result.violations.filter(v => v.ruleId === 'workflow-attribute-unused');
2629
-
2630
- expect(violations).toHaveLength(1);
2631
- expect(violations[0].message).toContain('order.completed');
2632
- expect(violations[0].message).toContain('total');
2633
- expect(violations[0].message).toContain('not used in any template');
2634
- });
2635
-
2636
- it('should pass when all attributes are used', async () => {
2637
- const workflow: WorkflowTemplate = {
2638
- version: '1.0.0',
2639
- name: 'Test',
2640
- description: 'Test workflow',
2641
- mode: 'span-tree',
2642
- canvas: 'test.otel.canvas',
2643
- scenarios: [{
2644
- id: 'test',
2645
- priority: 1,
2646
- description: 'Test scenario',
2647
- template: {
2648
- events: {
2649
- 'order.completed': 'Order {{orderId}} for {{total}}'
2650
- }
2651
- }
2652
- }]
2653
- };
2654
-
2655
- const canvas: ExtendedCanvas = {
2656
- nodes: [
2657
- {
2658
- id: 'n1',
2659
- type: 'otel-event',
2660
- x: 0,
2661
- y: 0,
2662
- width: 100,
2663
- height: 50,
2664
- event: {
2665
- name: 'order.completed',
2666
- description: 'Order completion event',
2667
- attributes: {
2668
- orderId: { type: 'string' },
2669
- total: { type: 'number' }
2670
- }
2671
- }
2672
- }
2673
- ],
2674
- edges: [],
2675
- pv: { version: '1.0.0', name: 'Test Canvas', markdown: 'test.md' }
2676
- };
2677
-
2678
- const context: WorkflowValidationContext = {
2679
- workflow,
2680
- workflowPath: 'test.workflow.json',
2681
- canvas,
2682
- basePath: tempDir
2683
- };
2684
-
2685
- const result = await validator.validate(context);
2686
- const violations = result.violations.filter(v => v.ruleId === 'workflow-attribute-unused');
2687
-
2688
- expect(violations).toHaveLength(0);
2689
- });
2690
-
2691
- it('should consider nested property access as using the parent attribute', async () => {
2692
- const workflow: WorkflowTemplate = {
2693
- version: '1.0.0',
2694
- name: 'Test',
2695
- description: 'Test workflow',
2696
- mode: 'span-tree',
2697
- canvas: 'test.otel.canvas',
2698
- scenarios: [{
2699
- id: 'test',
2700
- priority: 1,
2701
- description: 'Test scenario',
2702
- template: {
2703
- events: {
2704
- 'config.loaded': 'Loaded {{config.timeout}} ms timeout'
2705
- }
2706
- }
2707
- }]
2708
- };
2709
-
2710
- const canvas: ExtendedCanvas = {
2711
- nodes: [
2712
- {
2713
- id: 'n1',
2714
- type: 'otel-event',
2715
- x: 0,
2716
- y: 0,
2717
- width: 100,
2718
- height: 50,
2719
- event: {
2720
- name: 'config.loaded',
2721
- description: 'Configuration loaded',
2722
- attributes: {
2723
- config: { type: 'object' } // used via nested path {{config.timeout}}
2724
- }
2725
- }
2726
- }
2727
- ],
2728
- edges: [],
2729
- pv: { version: '1.0.0', name: 'Test Canvas', markdown: 'test.md' }
2730
- };
2731
-
2732
- const context: WorkflowValidationContext = {
2733
- workflow,
2734
- workflowPath: 'test.workflow.json',
2735
- canvas,
2736
- basePath: tempDir
2737
- };
2738
-
2739
- const result = await validator.validate(context);
2740
- const violations = result.violations.filter(v => v.ruleId === 'workflow-attribute-unused');
2741
-
2742
- expect(violations).toHaveLength(0);
2743
- });
2744
-
2745
- it('should check library event schemas via eventRef', async () => {
2746
- const workflow: WorkflowTemplate = {
2747
- version: '1.0.0',
2748
- name: 'Test',
2749
- description: 'Test workflow',
2750
- mode: 'span-tree',
2751
- canvas: 'test.otel.canvas',
2752
- scenarios: [{
2753
- id: 'test',
2754
- priority: 1,
2755
- description: 'Test scenario',
2756
- template: {
2757
- events: {
2758
- 'user.signup': 'User {{email}} signed up' // name is not used
2759
- }
2760
- }
2761
- }]
2762
- };
2763
-
2764
- const canvas: ExtendedCanvas = {
2765
- nodes: [
2766
- {
2767
- id: 'n1',
2768
- type: 'otel-event',
2769
- x: 0,
2770
- y: 0,
2771
- width: 100,
2772
- height: 50,
2773
- eventRef: 'user.signup'
2774
- }
2775
- ],
2776
- edges: [],
2777
- pv: { version: '1.0.0', name: 'Test Canvas', markdown: 'test.md' }
2778
- };
2779
-
2780
- const library: ComponentLibrary = {
2781
- version: '1.0.0',
2782
- eventSchemas: {
2783
- 'user.signup': {
2784
- description: 'User signup event',
2785
- attributes: {
2786
- email: { type: 'string' },
2787
- name: { type: 'string' } // not used in template
2788
- }
2789
- }
2790
- }
2791
- };
2792
-
2793
- const canvases = new Map<string, ExtendedCanvas>();
2794
- canvases.set('test.otel.canvas', canvas);
2795
- const eventRegistry = EventRegistry.build(library, canvases, 'library.yaml');
2796
-
2797
- const context: WorkflowValidationContext = {
2798
- workflow,
2799
- workflowPath: 'test.workflow.json',
2800
- canvas,
2801
- basePath: tempDir,
2802
- eventRegistry
2803
- };
2804
-
2805
- const result = await validator.validate(context);
2806
- const violations = result.violations.filter(v => v.ruleId === 'workflow-attribute-unused');
2807
-
2808
- expect(violations).toHaveLength(1);
2809
- expect(violations[0].message).toContain('user.signup');
2810
- expect(violations[0].message).toContain('name');
2811
- });
2812
-
2813
- it('should not validate attributes for events not included in workflow (disconnected subgraph support)', async () => {
2814
- // This test verifies the fix for Issue 3: Conflicting Validation Rules for Shared Canvases with Disconnected Subgraphs
2815
- // A canvas may have multiple disconnected flows, and workflows may focus on only one flow.
2816
- // We should NOT report "attribute not used" errors for events that aren't even part of the workflow.
2817
-
2818
- const workflow: WorkflowTemplate = {
2819
- version: '1.0.0',
2820
- name: 'Navigation Flow',
2821
- description: 'Workflow focusing only on navigation events (subgraph 1)',
2822
- mode: 'span-tree',
2823
- canvas: 'multi-flow.otel.canvas',
2824
- scenarios: [{
2825
- id: 'navigation',
2826
- priority: 1,
2827
- description: 'Navigation flow',
2828
- template: {
2829
- events: {
2830
- // Only uses events from the navigation subgraph
2831
- 'panel.opened': 'Panel {{panelName}} opened',
2832
- 'panel.closed': 'Panel {{panelName}} closed'
2833
- }
2834
- }
2835
- }]
2836
- };
2837
-
2838
- // Canvas with TWO disconnected subgraphs:
2839
- // Subgraph 1 (navigation): panel.opened -> panel.closed
2840
- // Subgraph 2 (tool execution): tool.started -> tool.completed
2841
- const canvas: ExtendedCanvas = {
2842
- nodes: [
2843
- // Subgraph 1: Navigation (included in workflow)
2844
- {
2845
- id: 'nav1',
2846
- type: 'otel-event',
2847
- x: 0,
2848
- y: 0,
2849
- width: 100,
2850
- height: 50,
2851
- event: {
2852
- name: 'panel.opened',
2853
- description: 'Panel opened event',
2854
- attributes: {
2855
- panelName: { type: 'string' }
2856
- }
2857
- }
2858
- },
2859
- {
2860
- id: 'nav2',
2861
- type: 'otel-event',
2862
- x: 200,
2863
- y: 0,
2864
- width: 100,
2865
- height: 50,
2866
- event: {
2867
- name: 'panel.closed',
2868
- description: 'Panel closed event',
2869
- attributes: {
2870
- panelName: { type: 'string' }
2871
- }
2872
- }
2873
- },
2874
- // Subgraph 2: Tool execution (NOT included in workflow)
2875
- {
2876
- id: 'tool1',
2877
- type: 'otel-event',
2878
- x: 0,
2879
- y: 200,
2880
- width: 100,
2881
- height: 50,
2882
- event: {
2883
- name: 'tool.started',
2884
- description: 'Tool started event',
2885
- attributes: {
2886
- toolName: { type: 'string' },
2887
- toolArgs: { type: 'string' } // These attributes are NOT used in the workflow
2888
- }
2889
- }
2890
- },
2891
- {
2892
- id: 'tool2',
2893
- type: 'otel-event',
2894
- x: 200,
2895
- y: 200,
2896
- width: 100,
2897
- height: 50,
2898
- event: {
2899
- name: 'tool.completed',
2900
- description: 'Tool completed event',
2901
- attributes: {
2902
- toolName: { type: 'string' },
2903
- result: { type: 'string' } // These attributes are NOT used in the workflow
2904
- }
2905
- }
2906
- }
2907
- ],
2908
- edges: [
2909
- // Subgraph 1 edges
2910
- { id: 'e1', fromNode: 'nav1', fromSide: 'right', toNode: 'nav2', toSide: 'left' },
2911
- // Subgraph 2 edges (disconnected from subgraph 1)
2912
- { id: 'e2', fromNode: 'tool1', fromSide: 'right', toNode: 'tool2', toSide: 'left' }
2913
- ],
2914
- pv: { version: '1.0.0', name: 'Multi-Flow Canvas', markdown: 'test.md' }
2915
- };
2916
-
2917
- const context: WorkflowValidationContext = {
2918
- workflow,
2919
- workflowPath: 'navigation.workflow.json',
2920
- canvas,
2921
- basePath: tempDir
2922
- };
2923
-
2924
- const result = await validator.validate(context);
2925
- const attributeViolations = result.violations.filter(v => v.ruleId === 'workflow-attribute-unused');
2926
- const connectivityViolations = result.violations.filter(v => v.ruleId === 'workflow-event-connectivity');
2927
-
2928
- // Should NOT have any "attribute not used" errors for tool.started or tool.completed
2929
- // because those events are not part of this workflow
2930
- expect(attributeViolations).toHaveLength(0);
2931
-
2932
- // Should NOT have connectivity errors because the workflow only uses connected events
2933
- expect(connectivityViolations).toHaveLength(0);
2934
- });
2935
- });
2936
-
2937
- describe('workflow-template-attribute-not-in-schema', () => {
2938
- it('should error when template references attribute not in event schema', async () => {
2939
- const workflow: WorkflowTemplate = {
2940
- version: '1.0.0',
2941
- name: 'Test',
2942
- description: 'Test workflow',
2943
- mode: 'span-tree',
2944
- canvas: 'test.otel.canvas',
2945
- scenarios: [{
2946
- id: 'test',
2947
- priority: 1,
2948
- description: 'Test scenario',
2949
- template: {
2950
- events: {
2951
- // Template references context.projectRoot which is NOT in the schema
2952
- 'task.delete.error': 'Task {{input.taskId}} not found in {{context.projectRoot}}'
2953
- }
2954
- }
2955
- }]
2956
- };
2957
-
2958
- const canvas: ExtendedCanvas = {
2959
- nodes: [
2960
- {
2961
- id: 'n1',
2962
- type: 'otel-event',
2963
- x: 0,
2964
- y: 0,
2965
- width: 100,
2966
- height: 50,
2967
- event: {
2968
- name: 'task.delete.error',
2969
- description: 'Task deletion error',
2970
- attributes: {
2971
- 'error.type': { type: 'string' },
2972
- 'error.message': { type: 'string' },
2973
- 'input.taskId': { type: 'string' }
2974
- // Note: context.projectRoot is NOT defined here
2975
- }
2976
- }
2977
- }
2978
- ],
2979
- edges: [],
2980
- pv: { version: '1.0.0', name: 'Test Canvas', markdown: 'test.md' }
2981
- };
2982
-
2983
- const context: WorkflowValidationContext = {
2984
- workflow,
2985
- workflowPath: 'test.workflow.json',
2986
- canvas,
2987
- basePath: tempDir
2988
- };
2989
-
2990
- const result = await validator.validate(context);
2991
- const violations = result.violations.filter(v => v.ruleId === 'workflow-template-attribute-not-in-schema');
2992
-
2993
- expect(violations).toHaveLength(1);
2994
- expect(violations[0].message).toContain('context.projectRoot');
2995
- expect(violations[0].message).toContain('not defined in the event schema');
2996
- expect(violations[0].message).toContain('task.delete.error');
2997
- });
2998
-
2999
- it('should pass when all template attributes are in event schema', async () => {
3000
- const workflow: WorkflowTemplate = {
3001
- version: '1.0.0',
3002
- name: 'Test',
3003
- description: 'Test workflow',
3004
- mode: 'span-tree',
3005
- canvas: 'test.otel.canvas',
3006
- scenarios: [{
3007
- id: 'test',
3008
- priority: 1,
3009
- description: 'Test scenario',
3010
- template: {
3011
- events: {
3012
- 'task.create.complete': 'Created task {{output.taskId}} in {{duration.ms}}ms'
3013
- }
3014
- }
3015
- }]
3016
- };
3017
-
3018
- const canvas: ExtendedCanvas = {
3019
- nodes: [
3020
- {
3021
- id: 'n1',
3022
- type: 'otel-event',
3023
- x: 0,
3024
- y: 0,
3025
- width: 100,
3026
- height: 50,
3027
- event: {
3028
- name: 'task.create.complete',
3029
- description: 'Task creation complete',
3030
- attributes: {
3031
- 'output.taskId': { type: 'string' },
3032
- 'duration.ms': { type: 'number' }
3033
- }
3034
- }
3035
- }
3036
- ],
3037
- edges: [],
3038
- pv: { version: '1.0.0', name: 'Test Canvas', markdown: 'test.md' }
3039
- };
3040
-
3041
- const context: WorkflowValidationContext = {
3042
- workflow,
3043
- workflowPath: 'test.workflow.json',
3044
- canvas,
3045
- basePath: tempDir
3046
- };
3047
-
3048
- const result = await validator.validate(context);
3049
- const violations = result.violations.filter(v => v.ruleId === 'workflow-template-attribute-not-in-schema');
3050
-
3051
- expect(violations).toHaveLength(0);
3052
- });
3053
-
3054
- it('should error for multiple undefined attributes', async () => {
3055
- const workflow: WorkflowTemplate = {
3056
- version: '1.0.0',
3057
- name: 'Test',
3058
- description: 'Test workflow',
3059
- mode: 'span-tree',
3060
- canvas: 'test.otel.canvas',
3061
- scenarios: [{
3062
- id: 'test',
3063
- priority: 1,
3064
- description: 'Test scenario',
3065
- template: {
3066
- events: {
3067
- 'task.delete.error': 'Task {{input.taskId}} not found in {{context.projectRoot}} ({{context.taskCount}} tasks)'
3068
- }
3069
- }
3070
- }]
3071
- };
3072
-
3073
- const canvas: ExtendedCanvas = {
3074
- nodes: [
3075
- {
3076
- id: 'n1',
3077
- type: 'otel-event',
3078
- x: 0,
3079
- y: 0,
3080
- width: 100,
3081
- height: 50,
3082
- event: {
3083
- name: 'task.delete.error',
3084
- description: 'Task deletion error',
3085
- attributes: {
3086
- 'input.taskId': { type: 'string' }
3087
- // context.projectRoot and context.taskCount are NOT defined
3088
- }
3089
- }
3090
- }
3091
- ],
3092
- edges: [],
3093
- pv: { version: '1.0.0', name: 'Test Canvas', markdown: 'test.md' }
3094
- };
3095
-
3096
- const context: WorkflowValidationContext = {
3097
- workflow,
3098
- workflowPath: 'test.workflow.json',
3099
- canvas,
3100
- basePath: tempDir
3101
- };
3102
-
3103
- const result = await validator.validate(context);
3104
- const violations = result.violations.filter(v => v.ruleId === 'workflow-template-attribute-not-in-schema');
3105
-
3106
- expect(violations).toHaveLength(2);
3107
- expect(violations.some(v => v.message.includes('context.projectRoot'))).toBe(true);
3108
- expect(violations.some(v => v.message.includes('context.taskCount'))).toBe(true);
3109
- });
3110
-
3111
- it('should handle nested attribute access when parent is defined', async () => {
3112
- const workflow: WorkflowTemplate = {
3113
- version: '1.0.0',
3114
- name: 'Test',
3115
- description: 'Test workflow',
3116
- mode: 'span-tree',
3117
- canvas: 'test.otel.canvas',
3118
- scenarios: [{
3119
- id: 'test',
3120
- priority: 1,
3121
- description: 'Test scenario',
3122
- template: {
3123
- events: {
3124
- // Accessing nested path input.taskId when input.taskId is defined
3125
- 'task.update.started': 'Updating task {{input.taskId}}'
3126
- }
3127
- }
3128
- }]
3129
- };
3130
-
3131
- const canvas: ExtendedCanvas = {
3132
- nodes: [
3133
- {
3134
- id: 'n1',
3135
- type: 'otel-event',
3136
- x: 0,
3137
- y: 0,
3138
- width: 100,
3139
- height: 50,
3140
- event: {
3141
- name: 'task.update.started',
3142
- description: 'Task update started',
3143
- attributes: {
3144
- 'input.taskId': { type: 'string' },
3145
- 'input.hasTitle': { type: 'boolean' }
3146
- }
3147
- }
3148
- }
3149
- ],
3150
- edges: [],
3151
- pv: { version: '1.0.0', name: 'Test Canvas', markdown: 'test.md' }
3152
- };
3153
-
3154
- const context: WorkflowValidationContext = {
3155
- workflow,
3156
- workflowPath: 'test.workflow.json',
3157
- canvas,
3158
- basePath: tempDir
3159
- };
3160
-
3161
- const result = await validator.validate(context);
3162
- const violations = result.violations.filter(v => v.ruleId === 'workflow-template-attribute-not-in-schema');
3163
-
3164
- expect(violations).toHaveLength(0);
3165
- });
3166
-
3167
- it('should suggest similar attributes when typo detected', async () => {
3168
- const workflow: WorkflowTemplate = {
3169
- version: '1.0.0',
3170
- name: 'Test',
3171
- description: 'Test workflow',
3172
- mode: 'span-tree',
3173
- canvas: 'test.otel.canvas',
3174
- scenarios: [{
3175
- id: 'test',
3176
- priority: 1,
3177
- description: 'Test scenario',
3178
- template: {
3179
- events: {
3180
- // Typo: input.takId instead of input.taskId
3181
- 'task.delete.started': 'Deleting task {{input.takId}}'
3182
- }
3183
- }
3184
- }]
3185
- };
3186
-
3187
- const canvas: ExtendedCanvas = {
3188
- nodes: [
3189
- {
3190
- id: 'n1',
3191
- type: 'otel-event',
3192
- x: 0,
3193
- y: 0,
3194
- width: 100,
3195
- height: 50,
3196
- event: {
3197
- name: 'task.delete.started',
3198
- description: 'Task deletion started',
3199
- attributes: {
3200
- 'input.taskId': { type: 'string' }
3201
- }
3202
- }
3203
- }
3204
- ],
3205
- edges: [],
3206
- pv: { version: '1.0.0', name: 'Test Canvas', markdown: 'test.md' }
3207
- };
3208
-
3209
- const context: WorkflowValidationContext = {
3210
- workflow,
3211
- workflowPath: 'test.workflow.json',
3212
- canvas,
3213
- basePath: tempDir
3214
- };
3215
-
3216
- const result = await validator.validate(context);
3217
- const violations = result.violations.filter(v => v.ruleId === 'workflow-template-attribute-not-in-schema');
3218
-
3219
- expect(violations).toHaveLength(1);
3220
- expect(violations[0].message).toContain('input.takId');
3221
- // Should suggest the similar attribute
3222
- expect(violations[0].suggestion).toContain('input.taskId');
3223
- });
3224
- });
3225
- });