@ontrails/warden 1.0.0-beta.14 → 1.0.0-beta.16

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 (351) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/README.md +77 -34
  3. package/bin/warden.ts +22 -0
  4. package/package.json +26 -6
  5. package/src/ast.ts +28 -0
  6. package/src/cli.ts +1169 -116
  7. package/src/command.ts +927 -0
  8. package/src/config.ts +184 -0
  9. package/src/drift.ts +76 -34
  10. package/src/formatters.ts +54 -7
  11. package/src/guide.ts +234 -0
  12. package/src/index.ts +151 -38
  13. package/src/project-context.ts +163 -0
  14. package/src/resolve.ts +530 -0
  15. package/src/rules/activation-orphan.ts +97 -0
  16. package/src/rules/ast.ts +2884 -124
  17. package/src/rules/circular-refs.ts +154 -0
  18. package/src/rules/{context-no-trailhead-types.ts → context-no-surface-types.ts} +72 -12
  19. package/src/rules/contour-exists.ts +251 -0
  20. package/src/rules/contour-ids.ts +15 -0
  21. package/src/rules/cross-declarations.ts +277 -69
  22. package/src/rules/dead-internal-trail.ts +141 -0
  23. package/src/rules/draft-file-marking.ts +51 -3
  24. package/src/rules/draft-visible-debt.ts +40 -15
  25. package/src/rules/error-mapping-completeness.ts +288 -0
  26. package/src/rules/example-valid.ts +401 -0
  27. package/src/rules/fires-declarations.ts +758 -0
  28. package/src/rules/implementation-returns-result.ts +1042 -122
  29. package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
  30. package/src/rules/incomplete-crud.ts +580 -0
  31. package/src/rules/index.ts +156 -19
  32. package/src/rules/intent-propagation.ts +127 -0
  33. package/src/rules/layer-field-name-drift.ts +96 -0
  34. package/src/rules/metadata.ts +508 -0
  35. package/src/rules/missing-reconcile.ts +98 -0
  36. package/src/rules/missing-visibility.ts +110 -0
  37. package/src/rules/no-dev-permit-in-source.ts +99 -0
  38. package/src/rules/no-direct-implementation-call.ts +1 -1
  39. package/src/rules/no-legacy-layer-imports.ts +193 -0
  40. package/src/rules/no-native-error-result.ts +111 -0
  41. package/src/rules/no-sync-result-assumption.ts +1131 -96
  42. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  43. package/src/rules/no-throw-in-implementation.ts +6 -4
  44. package/src/rules/on-references-exist.ts +194 -0
  45. package/src/rules/orphaned-signal.ts +150 -0
  46. package/src/rules/owner-projection-parity.ts +146 -0
  47. package/src/rules/permit-governance.ts +25 -0
  48. package/src/rules/public-internal-deep-imports.ts +517 -0
  49. package/src/rules/public-output-schema.ts +29 -0
  50. package/src/rules/public-union-output-discriminants.ts +150 -0
  51. package/src/rules/read-intent-fires.ts +187 -0
  52. package/src/rules/reference-exists.ts +98 -0
  53. package/src/rules/registry-names.ts +113 -0
  54. package/src/rules/resolved-import-boundary.ts +146 -0
  55. package/src/rules/{provision-declarations.ts → resource-declarations.ts} +208 -138
  56. package/src/rules/{provision-exists.ts → resource-exists.ts} +46 -51
  57. package/src/rules/resource-id-grammar.ts +65 -0
  58. package/src/rules/scan.ts +0 -26
  59. package/src/rules/scheduled-destroy-intent.ts +44 -0
  60. package/src/rules/signal-graph-coaching.ts +191 -0
  61. package/src/rules/specs.ts +5 -1
  62. package/src/rules/static-resource-accessor-preference.ts +657 -0
  63. package/src/rules/types.ts +187 -7
  64. package/src/rules/unmaterialized-activation-source.ts +84 -0
  65. package/src/rules/unreachable-detour-shadowing.ts +344 -0
  66. package/src/rules/valid-describe-refs.ts +160 -32
  67. package/src/rules/valid-detour-contract.ts +78 -0
  68. package/src/rules/warden-export-symmetry.ts +533 -0
  69. package/src/rules/warden-rules-use-ast.ts +996 -0
  70. package/src/rules/webhook-route-collision.ts +243 -0
  71. package/src/trails/activation-orphan.trail.ts +81 -0
  72. package/src/trails/circular-refs.trail.ts +29 -0
  73. package/src/trails/{context-no-trailhead-types.trail.ts → context-no-surface-types.trail.ts} +4 -4
  74. package/src/trails/contour-exists.trail.ts +21 -0
  75. package/src/trails/dead-internal-trail.trail.ts +26 -0
  76. package/src/trails/draft-file-marking.trail.ts +16 -0
  77. package/src/trails/draft-visible-debt.trail.ts +16 -0
  78. package/src/trails/error-mapping-completeness.trail.ts +29 -0
  79. package/src/trails/example-valid.trail.ts +25 -0
  80. package/src/trails/fires-declarations.trail.ts +23 -0
  81. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  82. package/src/trails/incomplete-crud.trail.ts +39 -0
  83. package/src/trails/index.ts +56 -8
  84. package/src/trails/intent-propagation.trail.ts +30 -0
  85. package/src/trails/layer-field-name-drift.trail.ts +39 -0
  86. package/src/trails/missing-reconcile.trail.ts +33 -0
  87. package/src/trails/missing-visibility.trail.ts +22 -0
  88. package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
  89. package/src/trails/no-legacy-layer-imports.trail.ts +35 -0
  90. package/src/trails/no-native-error-result.trail.ts +18 -0
  91. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  92. package/src/trails/on-references-exist.trail.ts +21 -0
  93. package/src/trails/orphaned-signal.trail.ts +36 -0
  94. package/src/trails/owner-projection-parity.trail.ts +26 -0
  95. package/src/trails/permit-governance.trail.ts +51 -0
  96. package/src/trails/public-internal-deep-imports.trail.ts +94 -0
  97. package/src/trails/public-output-schema.trail.ts +55 -0
  98. package/src/trails/public-union-output-discriminants.trail.ts +33 -0
  99. package/src/trails/read-intent-fires.trail.ts +20 -0
  100. package/src/trails/reference-exists.trail.ts +25 -0
  101. package/src/trails/resolved-import-boundary.trail.ts +109 -0
  102. package/src/trails/{provision-declarations.trail.ts → resource-declarations.trail.ts} +6 -6
  103. package/src/trails/{provision-exists.trail.ts → resource-exists.trail.ts} +7 -7
  104. package/src/trails/resource-id-grammar.trail.ts +39 -0
  105. package/src/trails/run.ts +133 -24
  106. package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
  107. package/src/trails/schema.ts +122 -4
  108. package/src/trails/signal-graph-coaching.trail.ts +74 -0
  109. package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
  110. package/src/trails/unmaterialized-activation-source.trail.ts +69 -0
  111. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  112. package/src/trails/valid-detour-contract.trail.ts +71 -0
  113. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  114. package/src/trails/warden-rules-use-ast.trail.ts +45 -0
  115. package/src/trails/webhook-route-collision.trail.ts +50 -0
  116. package/src/trails/wrap-rule.ts +133 -14
  117. package/src/workspaces.ts +238 -0
  118. package/.turbo/turbo-build.log +0 -1
  119. package/.turbo/turbo-lint.log +0 -3
  120. package/.turbo/turbo-typecheck.log +0 -1
  121. package/dist/cli.d.ts +0 -46
  122. package/dist/cli.d.ts.map +0 -1
  123. package/dist/cli.js +0 -247
  124. package/dist/cli.js.map +0 -1
  125. package/dist/draft.d.ts +0 -5
  126. package/dist/draft.d.ts.map +0 -1
  127. package/dist/draft.js +0 -16
  128. package/dist/draft.js.map +0 -1
  129. package/dist/drift.d.ts +0 -29
  130. package/dist/drift.d.ts.map +0 -1
  131. package/dist/drift.js +0 -61
  132. package/dist/drift.js.map +0 -1
  133. package/dist/formatters.d.ts +0 -30
  134. package/dist/formatters.d.ts.map +0 -1
  135. package/dist/formatters.js +0 -98
  136. package/dist/formatters.js.map +0 -1
  137. package/dist/index.d.ts +0 -37
  138. package/dist/index.d.ts.map +0 -1
  139. package/dist/index.js +0 -38
  140. package/dist/index.js.map +0 -1
  141. package/dist/rules/ast.d.ts +0 -82
  142. package/dist/rules/ast.d.ts.map +0 -1
  143. package/dist/rules/ast.js +0 -363
  144. package/dist/rules/ast.js.map +0 -1
  145. package/dist/rules/context-no-surface-types.d.ts +0 -12
  146. package/dist/rules/context-no-surface-types.d.ts.map +0 -1
  147. package/dist/rules/context-no-surface-types.js +0 -96
  148. package/dist/rules/context-no-surface-types.js.map +0 -1
  149. package/dist/rules/context-no-trailhead-types.d.ts +0 -12
  150. package/dist/rules/context-no-trailhead-types.d.ts.map +0 -1
  151. package/dist/rules/context-no-trailhead-types.js +0 -96
  152. package/dist/rules/context-no-trailhead-types.js.map +0 -1
  153. package/dist/rules/cross-declarations.d.ts +0 -13
  154. package/dist/rules/cross-declarations.d.ts.map +0 -1
  155. package/dist/rules/cross-declarations.js +0 -264
  156. package/dist/rules/cross-declarations.js.map +0 -1
  157. package/dist/rules/draft-file-marking.d.ts +0 -6
  158. package/dist/rules/draft-file-marking.d.ts.map +0 -1
  159. package/dist/rules/draft-file-marking.js +0 -65
  160. package/dist/rules/draft-file-marking.js.map +0 -1
  161. package/dist/rules/draft-visible-debt.d.ts +0 -12
  162. package/dist/rules/draft-visible-debt.d.ts.map +0 -1
  163. package/dist/rules/draft-visible-debt.js +0 -45
  164. package/dist/rules/draft-visible-debt.js.map +0 -1
  165. package/dist/rules/follow-declarations.d.ts +0 -13
  166. package/dist/rules/follow-declarations.d.ts.map +0 -1
  167. package/dist/rules/follow-declarations.js +0 -264
  168. package/dist/rules/follow-declarations.js.map +0 -1
  169. package/dist/rules/implementation-returns-result.d.ts +0 -13
  170. package/dist/rules/implementation-returns-result.d.ts.map +0 -1
  171. package/dist/rules/implementation-returns-result.js +0 -277
  172. package/dist/rules/implementation-returns-result.js.map +0 -1
  173. package/dist/rules/index.d.ts +0 -20
  174. package/dist/rules/index.d.ts.map +0 -1
  175. package/dist/rules/index.js +0 -49
  176. package/dist/rules/index.js.map +0 -1
  177. package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
  178. package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
  179. package/dist/rules/no-direct-impl-in-route.js +0 -47
  180. package/dist/rules/no-direct-impl-in-route.js.map +0 -1
  181. package/dist/rules/no-direct-implementation-call.d.ts +0 -12
  182. package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
  183. package/dist/rules/no-direct-implementation-call.js +0 -39
  184. package/dist/rules/no-direct-implementation-call.js.map +0 -1
  185. package/dist/rules/no-sync-result-assumption.d.ts +0 -6
  186. package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
  187. package/dist/rules/no-sync-result-assumption.js +0 -98
  188. package/dist/rules/no-sync-result-assumption.js.map +0 -1
  189. package/dist/rules/no-throw-in-detour-target.d.ts +0 -12
  190. package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
  191. package/dist/rules/no-throw-in-detour-target.js +0 -87
  192. package/dist/rules/no-throw-in-detour-target.js.map +0 -1
  193. package/dist/rules/no-throw-in-implementation.d.ts +0 -9
  194. package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
  195. package/dist/rules/no-throw-in-implementation.js +0 -34
  196. package/dist/rules/no-throw-in-implementation.js.map +0 -1
  197. package/dist/rules/prefer-schema-inference.d.ts +0 -7
  198. package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
  199. package/dist/rules/prefer-schema-inference.js +0 -86
  200. package/dist/rules/prefer-schema-inference.js.map +0 -1
  201. package/dist/rules/provision-declarations.d.ts +0 -14
  202. package/dist/rules/provision-declarations.d.ts.map +0 -1
  203. package/dist/rules/provision-declarations.js +0 -344
  204. package/dist/rules/provision-declarations.js.map +0 -1
  205. package/dist/rules/provision-exists.d.ts +0 -6
  206. package/dist/rules/provision-exists.d.ts.map +0 -1
  207. package/dist/rules/provision-exists.js +0 -90
  208. package/dist/rules/provision-exists.js.map +0 -1
  209. package/dist/rules/scan.d.ts +0 -8
  210. package/dist/rules/scan.d.ts.map +0 -1
  211. package/dist/rules/scan.js +0 -32
  212. package/dist/rules/scan.js.map +0 -1
  213. package/dist/rules/service-declarations.d.ts +0 -16
  214. package/dist/rules/service-declarations.d.ts.map +0 -1
  215. package/dist/rules/service-declarations.js +0 -346
  216. package/dist/rules/service-declarations.js.map +0 -1
  217. package/dist/rules/service-exists.d.ts +0 -8
  218. package/dist/rules/service-exists.d.ts.map +0 -1
  219. package/dist/rules/service-exists.js +0 -91
  220. package/dist/rules/service-exists.js.map +0 -1
  221. package/dist/rules/specs.d.ts +0 -29
  222. package/dist/rules/specs.d.ts.map +0 -1
  223. package/dist/rules/specs.js +0 -192
  224. package/dist/rules/specs.js.map +0 -1
  225. package/dist/rules/structure.d.ts +0 -13
  226. package/dist/rules/structure.d.ts.map +0 -1
  227. package/dist/rules/structure.js +0 -142
  228. package/dist/rules/structure.js.map +0 -1
  229. package/dist/rules/types.d.ts +0 -54
  230. package/dist/rules/types.d.ts.map +0 -1
  231. package/dist/rules/types.js +0 -2
  232. package/dist/rules/types.js.map +0 -1
  233. package/dist/rules/valid-describe-refs.d.ts +0 -7
  234. package/dist/rules/valid-describe-refs.d.ts.map +0 -1
  235. package/dist/rules/valid-describe-refs.js +0 -51
  236. package/dist/rules/valid-describe-refs.js.map +0 -1
  237. package/dist/rules/valid-detour-refs.d.ts +0 -6
  238. package/dist/rules/valid-detour-refs.d.ts.map +0 -1
  239. package/dist/rules/valid-detour-refs.js +0 -117
  240. package/dist/rules/valid-detour-refs.js.map +0 -1
  241. package/dist/trails/context-no-surface-types.trail.d.ts +0 -13
  242. package/dist/trails/context-no-surface-types.trail.d.ts.map +0 -1
  243. package/dist/trails/context-no-surface-types.trail.js +0 -21
  244. package/dist/trails/context-no-surface-types.trail.js.map +0 -1
  245. package/dist/trails/context-no-trailhead-types.trail.d.ts +0 -13
  246. package/dist/trails/context-no-trailhead-types.trail.d.ts.map +0 -1
  247. package/dist/trails/context-no-trailhead-types.trail.js +0 -21
  248. package/dist/trails/context-no-trailhead-types.trail.js.map +0 -1
  249. package/dist/trails/cross-declarations.trail.d.ts +0 -13
  250. package/dist/trails/cross-declarations.trail.d.ts.map +0 -1
  251. package/dist/trails/cross-declarations.trail.js +0 -22
  252. package/dist/trails/cross-declarations.trail.js.map +0 -1
  253. package/dist/trails/follow-declarations.trail.d.ts +0 -13
  254. package/dist/trails/follow-declarations.trail.d.ts.map +0 -1
  255. package/dist/trails/follow-declarations.trail.js +0 -22
  256. package/dist/trails/follow-declarations.trail.js.map +0 -1
  257. package/dist/trails/implementation-returns-result.trail.d.ts +0 -13
  258. package/dist/trails/implementation-returns-result.trail.d.ts.map +0 -1
  259. package/dist/trails/implementation-returns-result.trail.js +0 -20
  260. package/dist/trails/implementation-returns-result.trail.js.map +0 -1
  261. package/dist/trails/index.d.ts +0 -16
  262. package/dist/trails/index.d.ts.map +0 -1
  263. package/dist/trails/index.js +0 -15
  264. package/dist/trails/index.js.map +0 -1
  265. package/dist/trails/no-direct-impl-in-route.trail.d.ts +0 -13
  266. package/dist/trails/no-direct-impl-in-route.trail.d.ts.map +0 -1
  267. package/dist/trails/no-direct-impl-in-route.trail.js +0 -22
  268. package/dist/trails/no-direct-impl-in-route.trail.js.map +0 -1
  269. package/dist/trails/no-direct-implementation-call.trail.d.ts +0 -13
  270. package/dist/trails/no-direct-implementation-call.trail.d.ts.map +0 -1
  271. package/dist/trails/no-direct-implementation-call.trail.js +0 -16
  272. package/dist/trails/no-direct-implementation-call.trail.js.map +0 -1
  273. package/dist/trails/no-sync-result-assumption.trail.d.ts +0 -13
  274. package/dist/trails/no-sync-result-assumption.trail.d.ts.map +0 -1
  275. package/dist/trails/no-sync-result-assumption.trail.js +0 -19
  276. package/dist/trails/no-sync-result-assumption.trail.js.map +0 -1
  277. package/dist/trails/no-throw-in-detour-target.trail.d.ts +0 -15
  278. package/dist/trails/no-throw-in-detour-target.trail.d.ts.map +0 -1
  279. package/dist/trails/no-throw-in-detour-target.trail.js +0 -20
  280. package/dist/trails/no-throw-in-detour-target.trail.js.map +0 -1
  281. package/dist/trails/no-throw-in-implementation.trail.d.ts +0 -13
  282. package/dist/trails/no-throw-in-implementation.trail.d.ts.map +0 -1
  283. package/dist/trails/no-throw-in-implementation.trail.js +0 -20
  284. package/dist/trails/no-throw-in-implementation.trail.js.map +0 -1
  285. package/dist/trails/prefer-schema-inference.trail.d.ts +0 -13
  286. package/dist/trails/prefer-schema-inference.trail.d.ts.map +0 -1
  287. package/dist/trails/prefer-schema-inference.trail.js +0 -21
  288. package/dist/trails/prefer-schema-inference.trail.js.map +0 -1
  289. package/dist/trails/provision-declarations.trail.d.ts +0 -13
  290. package/dist/trails/provision-declarations.trail.d.ts.map +0 -1
  291. package/dist/trails/provision-declarations.trail.js +0 -25
  292. package/dist/trails/provision-declarations.trail.js.map +0 -1
  293. package/dist/trails/provision-exists.trail.d.ts +0 -15
  294. package/dist/trails/provision-exists.trail.d.ts.map +0 -1
  295. package/dist/trails/provision-exists.trail.js +0 -27
  296. package/dist/trails/provision-exists.trail.js.map +0 -1
  297. package/dist/trails/run.d.ts +0 -17
  298. package/dist/trails/run.d.ts.map +0 -1
  299. package/dist/trails/run.js +0 -39
  300. package/dist/trails/run.js.map +0 -1
  301. package/dist/trails/schema.d.ts +0 -53
  302. package/dist/trails/schema.d.ts.map +0 -1
  303. package/dist/trails/schema.js +0 -42
  304. package/dist/trails/schema.js.map +0 -1
  305. package/dist/trails/service-declarations.trail.d.ts +0 -26
  306. package/dist/trails/service-declarations.trail.d.ts.map +0 -1
  307. package/dist/trails/service-declarations.trail.js +0 -27
  308. package/dist/trails/service-declarations.trail.js.map +0 -1
  309. package/dist/trails/service-exists.trail.d.ts +0 -32
  310. package/dist/trails/service-exists.trail.d.ts.map +0 -1
  311. package/dist/trails/service-exists.trail.js +0 -29
  312. package/dist/trails/service-exists.trail.js.map +0 -1
  313. package/dist/trails/topo.d.ts +0 -3
  314. package/dist/trails/topo.d.ts.map +0 -1
  315. package/dist/trails/topo.js +0 -5
  316. package/dist/trails/topo.js.map +0 -1
  317. package/dist/trails/valid-describe-refs.trail.d.ts +0 -15
  318. package/dist/trails/valid-describe-refs.trail.d.ts.map +0 -1
  319. package/dist/trails/valid-describe-refs.trail.js +0 -18
  320. package/dist/trails/valid-describe-refs.trail.js.map +0 -1
  321. package/dist/trails/valid-detour-refs.trail.d.ts +0 -15
  322. package/dist/trails/valid-detour-refs.trail.d.ts.map +0 -1
  323. package/dist/trails/valid-detour-refs.trail.js +0 -24
  324. package/dist/trails/valid-detour-refs.trail.js.map +0 -1
  325. package/dist/trails/wrap-rule.d.ts +0 -29
  326. package/dist/trails/wrap-rule.d.ts.map +0 -1
  327. package/dist/trails/wrap-rule.js +0 -47
  328. package/dist/trails/wrap-rule.js.map +0 -1
  329. package/src/__tests__/cli.test.ts +0 -311
  330. package/src/__tests__/cross-declarations.test.ts +0 -303
  331. package/src/__tests__/drift.test.ts +0 -144
  332. package/src/__tests__/formatters.test.ts +0 -157
  333. package/src/__tests__/implementation-returns-result.test.ts +0 -129
  334. package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
  335. package/src/__tests__/no-sync-result-assumption.test.ts +0 -85
  336. package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
  337. package/src/__tests__/prefer-schema-inference.test.ts +0 -84
  338. package/src/__tests__/provision-declarations.test.ts +0 -318
  339. package/src/__tests__/provision-exists.test.ts +0 -122
  340. package/src/__tests__/rules.test.ts +0 -227
  341. package/src/__tests__/trails.test.ts +0 -19
  342. package/src/__tests__/valid-describe-refs.test.ts +0 -60
  343. package/src/__tests__/wrap-rule.test.ts +0 -41
  344. package/src/rules/no-direct-impl-in-route.ts +0 -81
  345. package/src/rules/no-throw-in-detour-target.ts +0 -150
  346. package/src/rules/valid-detour-refs.ts +0 -189
  347. package/src/trails/no-direct-impl-in-route.trail.ts +0 -22
  348. package/src/trails/no-throw-in-detour-target.trail.ts +0 -20
  349. package/src/trails/valid-detour-refs.trail.ts +0 -24
  350. package/tsconfig.json +0 -9
  351. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,109 @@
1
+ import { resolvedImportBoundary } from '../rules/resolved-import-boundary.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const resolvedImportBoundaryTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: {
8
+ diagnostics: [
9
+ {
10
+ filePath: 'packages/app/src/index.ts',
11
+ line: 1,
12
+ message:
13
+ 'Import "@fixture/core/internal/secret" is not exported by @fixture/core. Import the package root or an exported subpath instead.',
14
+ rule: 'resolved-import-boundary',
15
+ severity: 'error',
16
+ },
17
+ ],
18
+ },
19
+ input: {
20
+ filePath: 'packages/app/src/index.ts',
21
+ importResolutionsByFile: {
22
+ 'packages/app/src/index.ts': [
23
+ {
24
+ crossesPackageBoundary: true,
25
+ errorKind: 'package-path-not-exported',
26
+ importSource: '@fixture/core/internal/secret',
27
+ importerPath: 'packages/app/src/index.ts',
28
+ isInternalTarget: false,
29
+ line: 1,
30
+ packageName: '@fixture/core',
31
+ usesPublicExport: false,
32
+ },
33
+ ],
34
+ },
35
+ knownTrailIds: [],
36
+ sourceCode: "import { secret } from '@fixture/core/internal/secret';\n",
37
+ },
38
+ name: 'Cross-package imports must use exported package subpaths',
39
+ },
40
+ {
41
+ expected: {
42
+ diagnostics: [
43
+ {
44
+ filePath: 'packages/app/src/index.ts',
45
+ line: 1,
46
+ message:
47
+ 'Local import "../../core/src/public" crosses into @fixture/core. Import the target package public surface instead.',
48
+ rule: 'resolved-import-boundary',
49
+ severity: 'error',
50
+ },
51
+ ],
52
+ },
53
+ input: {
54
+ filePath: 'packages/app/src/index.ts',
55
+ importResolutionsByFile: {
56
+ 'packages/app/src/index.ts': [
57
+ {
58
+ crossesPackageBoundary: true,
59
+ importSource: '../../core/src/public',
60
+ importerPath: 'packages/app/src/index.ts',
61
+ isInternalTarget: false,
62
+ line: 1,
63
+ packageName: '@fixture/core',
64
+ usesPublicExport: false,
65
+ },
66
+ ],
67
+ },
68
+ knownTrailIds: [],
69
+ sourceCode: "import { pub } from '../../core/src/public';\n",
70
+ },
71
+ name: 'Relative imports must not cross package boundaries',
72
+ },
73
+ {
74
+ expected: {
75
+ diagnostics: [
76
+ {
77
+ filePath: 'packages/app/src/index.ts',
78
+ line: 1,
79
+ message:
80
+ 'Import "../../core/src/internal/secret" targets internal/private files in @fixture/core. Import the target package public surface instead.',
81
+ rule: 'resolved-import-boundary',
82
+ severity: 'error',
83
+ },
84
+ ],
85
+ },
86
+ input: {
87
+ filePath: 'packages/app/src/index.ts',
88
+ importResolutionsByFile: {
89
+ 'packages/app/src/index.ts': [
90
+ {
91
+ crossesPackageBoundary: true,
92
+ importSource: '../../core/src/internal/secret',
93
+ importerPath: 'packages/app/src/index.ts',
94
+ isInternalTarget: true,
95
+ line: 1,
96
+ packageName: '@fixture/core',
97
+ usesPublicExport: false,
98
+ },
99
+ ],
100
+ },
101
+ knownTrailIds: [],
102
+ sourceCode:
103
+ "import { secret } from '../../core/src/internal/secret';\n",
104
+ },
105
+ name: 'Cross-package imports must not target internals',
106
+ },
107
+ ],
108
+ rule: resolvedImportBoundary,
109
+ });
@@ -1,25 +1,25 @@
1
- import { provisionDeclarations } from '../rules/provision-declarations.js';
1
+ import { resourceDeclarations } from '../rules/resource-declarations.js';
2
2
  import { wrapRule } from './wrap-rule.js';
3
3
 
4
- export const provisionDeclarationsTrail = wrapRule({
4
+ export const resourceDeclarationsTrail = wrapRule({
5
5
  examples: [
6
6
  {
7
7
  expected: { diagnostics: [] },
8
8
  input: {
9
9
  filePath: 'clean.ts',
10
- sourceCode: `const db = provision("db.main", {
10
+ sourceCode: `const db = resource("db.main", {
11
11
  create: () => Result.ok({ source: "factory" }),
12
12
  });
13
13
 
14
14
  trail("entity.show", {
15
- provisions: [db],
15
+ resources: [db],
16
16
  blaze: async (_input, ctx) => {
17
17
  return Result.ok(db.from(ctx));
18
18
  }
19
19
  })`,
20
20
  },
21
- name: 'Matched provision declarations and usage',
21
+ name: 'Matched resource declarations and usage',
22
22
  },
23
23
  ],
24
- rule: provisionDeclarations,
24
+ rule: resourceDeclarations,
25
25
  });
@@ -1,27 +1,27 @@
1
- import { provisionExists } from '../rules/provision-exists.js';
1
+ import { resourceExists } from '../rules/resource-exists.js';
2
2
  import { wrapRule } from './wrap-rule.js';
3
3
 
4
- export const provisionExistsTrail = wrapRule({
4
+ export const resourceExistsTrail = wrapRule({
5
5
  examples: [
6
6
  {
7
7
  expected: { diagnostics: [] },
8
8
  input: {
9
9
  filePath: 'clean.ts',
10
- knownProvisionIds: ['db.main'],
10
+ knownResourceIds: ['db.main'],
11
11
  knownTrailIds: ['entity.show'],
12
- sourceCode: `const db = provision("db.main", {
12
+ sourceCode: `const db = resource("db.main", {
13
13
  create: () => Result.ok({ source: "factory" }),
14
14
  });
15
15
 
16
16
  trail("entity.show", {
17
- provisions: [db],
17
+ resources: [db],
18
18
  blaze: async (_input, ctx) => {
19
19
  return Result.ok(db.from(ctx));
20
20
  }
21
21
  })`,
22
22
  },
23
- name: 'Declared provisions resolve to known project provisions',
23
+ name: 'Declared resources resolve to known project resources',
24
24
  },
25
25
  ],
26
- rule: provisionExists,
26
+ rule: resourceExists,
27
27
  });
@@ -0,0 +1,39 @@
1
+ import { resourceIdGrammar } from '../rules/resource-id-grammar.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const resourceIdGrammarTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'clean.ts',
10
+ sourceCode: `const db = resource("db.main", {
11
+ create: () => Result.ok({}),
12
+ });`,
13
+ },
14
+ name: 'Resource ids stay free of the scope separator',
15
+ },
16
+ {
17
+ expected: {
18
+ diagnostics: [
19
+ {
20
+ filePath: 'invalid.ts',
21
+ line: 1,
22
+ message:
23
+ 'Resource "billing:primary" is invalid because resource ids may not contain ":".',
24
+ rule: 'resource-id-grammar',
25
+ severity: 'error',
26
+ },
27
+ ],
28
+ },
29
+ input: {
30
+ filePath: 'invalid.ts',
31
+ sourceCode: `const db = resource("billing:primary", {
32
+ create: () => Result.ok({}),
33
+ });`,
34
+ },
35
+ name: 'Colon-separated resource ids are rejected',
36
+ },
37
+ ],
38
+ rule: resourceIdGrammar,
39
+ });
package/src/trails/run.ts CHANGED
@@ -1,51 +1,160 @@
1
1
  /**
2
- * Run all warden rule trails against a single source file.
2
+ * Run file-scoped warden rule trails against a single source file.
3
3
  *
4
- * Returns a flat array of diagnostics from every rule.
4
+ * Returns a flat array of diagnostics from every source-aware rule. Built-in
5
+ * topo-aware rules are dispatched separately via `runTopoAwareWardenTrails()`
6
+ * so callers that loop files do not duplicate graph-level findings.
5
7
  */
6
8
 
9
+ import type { Intent, Topo } from '@ontrails/core';
7
10
  import { run } from '@ontrails/core';
8
11
 
12
+ import { wardenTopoRules } from '../rules/index.js';
9
13
  import type { WardenDiagnostic } from '../rules/types.js';
14
+ import type { WardenImportResolution } from '../resolve.js';
15
+ import type { WardenPublicWorkspace } from '../workspaces.js';
10
16
  import type { RuleOutput } from './schema.js';
11
17
  import { wardenTopo } from './topo.js';
12
18
 
13
19
  /**
14
- * Run all warden rule trails for a given file and collect diagnostics.
20
+ * Run all file-scoped warden rule trails for a given file and collect diagnostics.
15
21
  *
16
22
  * Each rule trail runs independently. Errors from individual trails are
17
23
  * silently skipped so that one broken rule does not block the rest.
18
24
  */
19
- export const runWardenTrails = async (
25
+ const appendDiagnostics = (
26
+ target: WardenDiagnostic[],
27
+ diagnostics: readonly WardenDiagnostic[]
28
+ ): void => {
29
+ for (const diagnostic of diagnostics) {
30
+ target.push(diagnostic);
31
+ }
32
+ };
33
+
34
+ type TrailIntentMap = Readonly<Record<string, Intent>>;
35
+
36
+ interface ProjectRuleOptions {
37
+ readonly contourReferencesByName?: Readonly<
38
+ Record<string, readonly string[]>
39
+ >;
40
+ readonly crossTargetTrailIds?: readonly string[];
41
+ readonly crudTableIds?: readonly string[];
42
+ readonly crudCoverageByEntity?: Readonly<Record<string, readonly string[]>>;
43
+ readonly knownContourIds?: readonly string[];
44
+ readonly importResolutionsByFile?: Readonly<
45
+ Record<string, readonly WardenImportResolution[]>
46
+ >;
47
+ readonly documentedImportResolutionsByFile?: Readonly<
48
+ Record<string, readonly WardenImportResolution[]>
49
+ >;
50
+ readonly knownResourceIds?: readonly string[];
51
+ readonly knownSignalIds?: readonly string[];
52
+ readonly knownTrailIds?: readonly string[];
53
+ readonly onTargetSignalIds?: readonly string[];
54
+ readonly publicWorkspaces?: Readonly<Record<string, WardenPublicWorkspace>>;
55
+ readonly reconcileTableIds?: readonly string[];
56
+ readonly trailIntentsById?: TrailIntentMap;
57
+ }
58
+
59
+ const PROJECT_OPTION_KEYS = [
60
+ 'contourReferencesByName',
61
+ 'crossTargetTrailIds',
62
+ 'crudTableIds',
63
+ 'crudCoverageByEntity',
64
+ 'knownContourIds',
65
+ 'importResolutionsByFile',
66
+ 'documentedImportResolutionsByFile',
67
+ 'knownResourceIds',
68
+ 'knownSignalIds',
69
+ 'knownTrailIds',
70
+ 'onTargetSignalIds',
71
+ 'publicWorkspaces',
72
+ 'reconcileTableIds',
73
+ 'trailIntentsById',
74
+ ] as const satisfies readonly (keyof ProjectRuleOptions)[];
75
+
76
+ const hasProjectOptions = (options?: ProjectRuleOptions): boolean =>
77
+ Boolean(
78
+ options && PROJECT_OPTION_KEYS.some((key) => options[key] !== undefined)
79
+ );
80
+
81
+ const collectProjectOptions = (
82
+ options?: ProjectRuleOptions
83
+ ): ProjectRuleOptions => {
84
+ if (!options) {
85
+ return {};
86
+ }
87
+
88
+ return Object.fromEntries(
89
+ PROJECT_OPTION_KEYS.flatMap((key) => {
90
+ const value = options[key];
91
+ return value === undefined ? [] : [[key, value] as const];
92
+ })
93
+ ) as ProjectRuleOptions;
94
+ };
95
+
96
+ const buildRuleInput = (
20
97
  filePath: string,
21
98
  sourceCode: string,
22
- options?: {
23
- readonly knownProvisionIds?: readonly string[];
24
- readonly knownTrailIds?: readonly string[];
99
+ options?: ProjectRuleOptions
100
+ ): {
101
+ readonly filePath: string;
102
+ readonly sourceCode: string;
103
+ } & ProjectRuleOptions => {
104
+ const base = { filePath, sourceCode };
105
+ if (!hasProjectOptions(options)) {
106
+ return base;
25
107
  }
108
+
109
+ return { ...base, ...collectProjectOptions(options) };
110
+ };
111
+
112
+ const topoAwareTrailIds = new Set(
113
+ [...wardenTopoRules.keys()].map((ruleName) => `warden.rule.${ruleName}`)
114
+ );
115
+
116
+ export const runWardenTrails = async (
117
+ filePath: string,
118
+ sourceCode: string,
119
+ options?: ProjectRuleOptions
26
120
  ): Promise<readonly WardenDiagnostic[]> => {
27
121
  const allDiagnostics: WardenDiagnostic[] = [];
122
+ const input = buildRuleInput(filePath, sourceCode, options);
28
123
 
29
124
  for (const id of wardenTopo.ids()) {
30
- const input =
31
- options?.knownTrailIds || options?.knownProvisionIds
32
- ? {
33
- filePath,
34
- ...(options?.knownProvisionIds
35
- ? { knownProvisionIds: options.knownProvisionIds }
36
- : {}),
37
- ...(options?.knownTrailIds
38
- ? { knownTrailIds: options.knownTrailIds }
39
- : {}),
40
- sourceCode,
41
- }
42
- : { filePath, sourceCode };
125
+ if (topoAwareTrailIds.has(id)) {
126
+ continue;
127
+ }
43
128
  const result = await run(wardenTopo, id, input);
44
129
  if (result.isOk()) {
45
- const { diagnostics } = result.value as RuleOutput;
46
- for (const d of diagnostics) {
47
- allDiagnostics.push(d);
48
- }
130
+ appendDiagnostics(
131
+ allDiagnostics,
132
+ (result.value as RuleOutput).diagnostics
133
+ );
134
+ }
135
+ }
136
+
137
+ return allDiagnostics;
138
+ };
139
+
140
+ /**
141
+ * Run the built-in topo-aware warden rule trails once against a resolved topo.
142
+ *
143
+ * Unlike `runWardenTrails()`, which is file-scoped, topo-aware rules inspect
144
+ * the compiled graph and should only be dispatched once per topo.
145
+ */
146
+ export const runTopoAwareWardenTrails = async (
147
+ topo: Topo
148
+ ): Promise<readonly WardenDiagnostic[]> => {
149
+ const allDiagnostics: WardenDiagnostic[] = [];
150
+
151
+ for (const id of topoAwareTrailIds) {
152
+ const result = await run(wardenTopo, id, { topo });
153
+ if (result.isOk()) {
154
+ appendDiagnostics(
155
+ allDiagnostics,
156
+ (result.value as RuleOutput).diagnostics
157
+ );
49
158
  }
50
159
  }
51
160
 
@@ -0,0 +1,56 @@
1
+ import { Result, schedule, topo, trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import { scheduledDestroyIntent } from '../rules/scheduled-destroy-intent.js';
5
+ import { wrapTopoRule } from './wrap-rule.js';
6
+
7
+ export const scheduledDestroyTrail = trail('billing.purge-expired', {
8
+ blaze: () => Result.ok({ ok: true }),
9
+ input: z.object({}),
10
+ intent: 'destroy',
11
+ on: [
12
+ schedule('schedule.billing.purge-expired', {
13
+ cron: '0 2 * * *',
14
+ }),
15
+ ],
16
+ output: z.object({ ok: z.boolean() }),
17
+ permit: { scopes: ['billing:purge'] },
18
+ });
19
+
20
+ const scheduledWriteTrail = trail('billing.reconcile', {
21
+ blaze: () => Result.ok({ ok: true }),
22
+ input: z.object({}),
23
+ on: [schedule('schedule.billing.reconcile', { cron: '0 * * * *' })],
24
+ output: z.object({ ok: z.boolean() }),
25
+ });
26
+
27
+ export const scheduledDestroyIntentTrail = wrapTopoRule({
28
+ examples: [
29
+ {
30
+ expected: {
31
+ diagnostics: [
32
+ {
33
+ filePath: '<topo>',
34
+ line: 1,
35
+ message:
36
+ 'Trail "billing.purge-expired" declares intent: \'destroy\' and is activated by schedule source "schedule.billing.purge-expired". Scheduled destroy work should make cadence, permit scope, idempotency, and recovery explicit before it runs unattended.',
37
+ rule: 'scheduled-destroy-intent',
38
+ severity: 'warn',
39
+ },
40
+ ],
41
+ },
42
+ input: {
43
+ topo: topo('trl-457-scheduled-destroy', { scheduledDestroyTrail }),
44
+ },
45
+ name: 'Scheduled destroy trails emit coaching',
46
+ },
47
+ {
48
+ expected: { diagnostics: [] },
49
+ input: {
50
+ topo: topo('trl-457-scheduled-write', { scheduledWriteTrail }),
51
+ },
52
+ name: 'Scheduled write trails do not emit destroy coaching',
53
+ },
54
+ ],
55
+ rule: scheduledDestroyIntent,
56
+ });
@@ -5,11 +5,31 @@
5
5
  * (array of diagnostics) shape.
6
6
  */
7
7
 
8
+ import { intentValues } from '@ontrails/core';
9
+ import type { Topo } from '@ontrails/core';
8
10
  import { z } from 'zod';
11
+ import { wardenImportResolutionErrorKinds } from '../resolve.js';
12
+
13
+ export const guidanceLinkSchema = z.object({
14
+ label: z.string(),
15
+ path: z.string().optional(),
16
+ url: z.string().optional(),
17
+ });
18
+
19
+ export const guidanceSchema = z.object({
20
+ commands: z.array(z.string()).readonly().optional(),
21
+ docs: z.array(guidanceLinkSchema).readonly().optional(),
22
+ relatedRules: z.array(z.string()).readonly().optional(),
23
+ steps: z.array(z.string()).readonly().optional(),
24
+ summary: z.string(),
25
+ });
9
26
 
10
27
  /** A single diagnostic emitted by a warden rule trail. */
11
28
  export const diagnosticSchema = z.object({
12
29
  filePath: z.string().describe('File path that was analyzed'),
30
+ guidance: guidanceSchema
31
+ .optional()
32
+ .describe('Structured remediation guidance'),
13
33
  line: z.number().describe('1-based line number'),
14
34
  message: z.string().describe('Human-readable diagnostic message'),
15
35
  rule: z.string().describe('Rule name'),
@@ -22,22 +42,119 @@ export const ruleInput = z.object({
22
42
  sourceCode: z.string().describe('Source code content'),
23
43
  });
24
44
 
45
+ export const importResolutionSchema = z.object({
46
+ builtinModule: z.string().optional(),
47
+ crossesPackageBoundary: z.boolean(),
48
+ errorKind: z.enum(wardenImportResolutionErrorKinds).optional(),
49
+ errorMessage: z.string().optional(),
50
+ importSource: z.string(),
51
+ importerPath: z.string(),
52
+ isInternalTarget: z.boolean(),
53
+ line: z.number(),
54
+ packageName: z.string().optional(),
55
+ packageRoot: z.string().optional(),
56
+ resolvedPath: z.string().optional(),
57
+ usesPublicExport: z.boolean(),
58
+ });
59
+
60
+ export const publicWorkspaceSchema = z.object({
61
+ bin: z.record(z.string(), z.string()).optional(),
62
+ exportTargets: z.record(z.string(), z.string()).optional(),
63
+ files: z.array(z.string()).optional(),
64
+ hasExports: z.boolean(),
65
+ name: z.string(),
66
+ packageJsonPath: z.string(),
67
+ rootDir: z.string(),
68
+ });
69
+
25
70
  /**
26
71
  * Extended input for project-aware warden rule trails.
27
72
  *
28
73
  * Adds `knownTrailIds` so the caller can supply cross-file context and avoid
29
- * false positives for detour targets or `@see` references defined in other
30
- * files.
74
+ * false positives for `@see` references or cross-file contour relationships.
31
75
  */
32
76
  export const projectAwareRuleInput = ruleInput.extend({
33
- knownProvisionIds: z
77
+ contourReferencesByName: z
78
+ .record(z.string(), z.array(z.string()))
79
+ .optional()
80
+ .describe('Declared contour references keyed by source contour name'),
81
+ crossTargetTrailIds: z
82
+ .array(z.string())
83
+ .optional()
84
+ .describe('Trail IDs referenced by crosses arrays across the project'),
85
+ crudCoverageByEntity: z
86
+ .record(z.string(), z.array(z.string()))
87
+ .optional()
88
+ .describe(
89
+ 'CRUD operation coverage per entity aggregated across the project'
90
+ ),
91
+ crudTableIds: z
92
+ .array(z.string())
93
+ .optional()
94
+ .describe('Store table IDs used with CRUD factories across the project'),
95
+ documentedImportResolutionsByFile: z
96
+ .record(z.string(), z.array(importResolutionSchema))
97
+ .optional()
98
+ .describe('Resolved docs/specifier facts keyed by documentation file path'),
99
+ importResolutionsByFile: z
100
+ .record(z.string(), z.array(importResolutionSchema))
101
+ .optional()
102
+ .describe('Resolved import facts keyed by importer file path'),
103
+ knownContourIds: z
104
+ .array(z.string())
105
+ .optional()
106
+ .describe('Contour names known across the project'),
107
+ knownResourceIds: z
108
+ .array(z.string())
109
+ .optional()
110
+ .describe('Resource IDs known across the project'),
111
+ knownSignalIds: z
34
112
  .array(z.string())
35
113
  .optional()
36
- .describe('Provision IDs known across the project'),
114
+ .describe('Signal IDs known across the project'),
37
115
  knownTrailIds: z
38
116
  .array(z.string())
39
117
  .optional()
40
118
  .describe('Trail IDs known across the project'),
119
+ onTargetSignalIds: z
120
+ .array(z.string())
121
+ .optional()
122
+ .describe('Signal IDs referenced by trail on arrays across the project'),
123
+ publicWorkspaces: z
124
+ .record(z.string(), publicWorkspaceSchema)
125
+ .optional()
126
+ .describe('Non-private published @ontrails workspaces by package name'),
127
+ reconcileTableIds: z
128
+ .array(z.string())
129
+ .optional()
130
+ .describe('Store table IDs used with reconcile trails across the project'),
131
+ trailIntentsById: z
132
+ .record(z.string(), z.enum(intentValues))
133
+ .optional()
134
+ .describe('Normalized trail intents keyed by trail ID'),
135
+ });
136
+
137
+ /**
138
+ * Input for topo-aware warden rule trails.
139
+ *
140
+ * The `Topo` graph is not a serializable value, so the schema accepts it
141
+ * as an opaque `z.custom`. Topo-aware rules are invoked from the warden
142
+ * runtime with a live, resolved topo reference — they are not expected
143
+ * to be called across a network boundary.
144
+ */
145
+ export const topoAwareRuleInput = z.object({
146
+ topo: z
147
+ .custom<Topo>(
148
+ (value) =>
149
+ typeof value === 'object' &&
150
+ value !== null &&
151
+ 'trails' in value &&
152
+ 'resources' in value &&
153
+ 'contours' in value &&
154
+ 'signals' in value,
155
+ { message: 'Expected a resolved Topo instance' }
156
+ )
157
+ .describe('Resolved topo graph under inspection'),
41
158
  });
42
159
 
43
160
  /** Output returned by every warden rule trail. */
@@ -47,4 +164,5 @@ export const ruleOutput = z.object({
47
164
 
48
165
  export type RuleInput = z.infer<typeof ruleInput>;
49
166
  export type ProjectAwareRuleInput = z.infer<typeof projectAwareRuleInput>;
167
+ export type TopoAwareRuleInput = z.infer<typeof topoAwareRuleInput>;
50
168
  export type RuleOutput = z.infer<typeof ruleOutput>;
@@ -0,0 +1,74 @@
1
+ import { Result, resource, signal, topo, trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import { signalGraphCoaching } from '../rules/signal-graph-coaching.js';
5
+ import { wrapTopoRule } from './wrap-rule.js';
6
+
7
+ const unusedSignal = signal('invoice.unused', {
8
+ payload: z.object({ invoiceId: z.string() }),
9
+ });
10
+
11
+ const producedSignal = signal('invoice.created', {
12
+ payload: z.object({ invoiceId: z.string() }),
13
+ });
14
+
15
+ const producerTrail = trail('invoice.create', {
16
+ blaze: () => Result.ok({ invoiceId: 'inv_1' }),
17
+ fires: [producedSignal],
18
+ input: z.object({}),
19
+ output: z.object({ invoiceId: z.string() }),
20
+ });
21
+
22
+ const resourceSignal = signal('store:invoice.created', {
23
+ payload: z.object({ invoiceId: z.string() }),
24
+ });
25
+
26
+ const invoiceStore = resource('store', {
27
+ create: () => Result.ok({ ok: true }),
28
+ signals: [resourceSignal],
29
+ });
30
+
31
+ export const signalGraphCoachingTrail = wrapTopoRule({
32
+ examples: [
33
+ {
34
+ expected: {
35
+ diagnostics: [
36
+ {
37
+ filePath: '<topo>',
38
+ line: 1,
39
+ message:
40
+ 'Signal "invoice.created" is produced by producer trail "invoice.create" but has no consumer trails. Add an on: consumer if the signal is meant to drive reactive work, or remove the unused fires:/producer declaration.',
41
+ rule: 'signal-graph-coaching',
42
+ severity: 'warn',
43
+ },
44
+ {
45
+ filePath: '<topo>',
46
+ line: 1,
47
+ message:
48
+ 'Signal "invoice.unused" is declared in the topo but has no producer trails, producer resources, or consumer trails. Add fires:/on: edges, attach producer metadata, or remove the unused signal contract.',
49
+ rule: 'signal-graph-coaching',
50
+ severity: 'warn',
51
+ },
52
+ {
53
+ filePath: '<topo>',
54
+ line: 1,
55
+ message:
56
+ 'Signal "store:invoice.created" is produced by producer resource "store" but has no consumer trails. Add an on: consumer if the signal is meant to drive reactive work, or remove the unused fires:/producer declaration.',
57
+ rule: 'signal-graph-coaching',
58
+ severity: 'warn',
59
+ },
60
+ ],
61
+ },
62
+ input: {
63
+ topo: topo('trl-447-signal-graph-coaching', {
64
+ invoiceStore,
65
+ producedSignal,
66
+ producerTrail,
67
+ unusedSignal,
68
+ }),
69
+ },
70
+ name: 'Declared and produced signals without consumers get coaching',
71
+ },
72
+ ],
73
+ rule: signalGraphCoaching,
74
+ });