@ontrails/warden 1.0.0-beta.13 → 1.0.0-beta.15

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 (474) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +30 -0
  3. package/README.md +31 -20
  4. package/dist/cli.d.ts +19 -2
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +261 -64
  7. package/dist/cli.js.map +1 -1
  8. package/dist/draft.d.ts +5 -0
  9. package/dist/draft.d.ts.map +1 -0
  10. package/dist/draft.js +16 -0
  11. package/dist/draft.js.map +1 -0
  12. package/dist/drift.d.ts +10 -7
  13. package/dist/drift.d.ts.map +1 -1
  14. package/dist/drift.js +50 -16
  15. package/dist/drift.js.map +1 -1
  16. package/dist/formatters.d.ts +2 -1
  17. package/dist/formatters.d.ts.map +1 -1
  18. package/dist/formatters.js +15 -4
  19. package/dist/formatters.js.map +1 -1
  20. package/dist/index.d.ts +9 -17
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +10 -17
  23. package/dist/index.js.map +1 -1
  24. package/dist/rules/ast.d.ts +412 -7
  25. package/dist/rules/ast.d.ts.map +1 -1
  26. package/dist/rules/ast.js +1847 -102
  27. package/dist/rules/ast.js.map +1 -1
  28. package/dist/rules/circular-refs.d.ts +6 -0
  29. package/dist/rules/circular-refs.d.ts.map +1 -0
  30. package/dist/rules/circular-refs.js +83 -0
  31. package/dist/rules/circular-refs.js.map +1 -0
  32. package/dist/rules/context-no-surface-types.d.ts.map +1 -1
  33. package/dist/rules/context-no-surface-types.js +59 -3
  34. package/dist/rules/context-no-surface-types.js.map +1 -1
  35. package/dist/rules/contour-exists.d.ts +7 -0
  36. package/dist/rules/contour-exists.d.ts.map +1 -0
  37. package/dist/rules/contour-exists.js +113 -0
  38. package/dist/rules/contour-exists.js.map +1 -0
  39. package/dist/rules/contour-ids.d.ts +10 -0
  40. package/dist/rules/contour-ids.d.ts.map +1 -0
  41. package/dist/rules/contour-ids.js +12 -0
  42. package/dist/rules/contour-ids.js.map +1 -0
  43. package/dist/rules/cross-declarations.d.ts.map +1 -1
  44. package/dist/rules/cross-declarations.js +171 -57
  45. package/dist/rules/cross-declarations.js.map +1 -1
  46. package/dist/rules/dead-internal-trail.d.ts +3 -0
  47. package/dist/rules/dead-internal-trail.d.ts.map +1 -0
  48. package/dist/rules/dead-internal-trail.js +80 -0
  49. package/dist/rules/dead-internal-trail.js.map +1 -0
  50. package/dist/rules/draft-file-marking.d.ts +6 -0
  51. package/dist/rules/draft-file-marking.d.ts.map +1 -0
  52. package/dist/rules/draft-file-marking.js +87 -0
  53. package/dist/rules/draft-file-marking.js.map +1 -0
  54. package/dist/rules/draft-visible-debt.d.ts +12 -0
  55. package/dist/rules/draft-visible-debt.d.ts.map +1 -0
  56. package/dist/rules/draft-visible-debt.js +50 -0
  57. package/dist/rules/draft-visible-debt.js.map +1 -0
  58. package/dist/rules/error-mapping-completeness.d.ts +13 -0
  59. package/dist/rules/error-mapping-completeness.d.ts.map +1 -0
  60. package/dist/rules/error-mapping-completeness.js +160 -0
  61. package/dist/rules/error-mapping-completeness.js.map +1 -0
  62. package/dist/rules/example-valid.d.ts +6 -0
  63. package/dist/rules/example-valid.d.ts.map +1 -0
  64. package/dist/rules/example-valid.js +203 -0
  65. package/dist/rules/example-valid.js.map +1 -0
  66. package/dist/rules/fires-declarations.d.ts +16 -0
  67. package/dist/rules/fires-declarations.d.ts.map +1 -0
  68. package/dist/rules/fires-declarations.js +444 -0
  69. package/dist/rules/fires-declarations.js.map +1 -0
  70. package/dist/rules/implementation-returns-result.d.ts +9 -0
  71. package/dist/rules/implementation-returns-result.d.ts.map +1 -1
  72. package/dist/rules/implementation-returns-result.js +638 -76
  73. package/dist/rules/implementation-returns-result.js.map +1 -1
  74. package/dist/rules/incomplete-accessor-for-standard-op.d.ts +30 -0
  75. package/dist/rules/incomplete-accessor-for-standard-op.d.ts.map +1 -0
  76. package/dist/rules/incomplete-accessor-for-standard-op.js +226 -0
  77. package/dist/rules/incomplete-accessor-for-standard-op.js.map +1 -0
  78. package/dist/rules/incomplete-crud.d.ts +21 -0
  79. package/dist/rules/incomplete-crud.d.ts.map +1 -0
  80. package/dist/rules/incomplete-crud.js +368 -0
  81. package/dist/rules/incomplete-crud.js.map +1 -0
  82. package/dist/rules/index.d.ts +40 -7
  83. package/dist/rules/index.d.ts.map +1 -1
  84. package/dist/rules/index.js +91 -15
  85. package/dist/rules/index.js.map +1 -1
  86. package/dist/rules/intent-propagation.d.ts +3 -0
  87. package/dist/rules/intent-propagation.d.ts.map +1 -0
  88. package/dist/rules/intent-propagation.js +57 -0
  89. package/dist/rules/intent-propagation.js.map +1 -0
  90. package/dist/rules/missing-reconcile.d.ts +3 -0
  91. package/dist/rules/missing-reconcile.d.ts.map +1 -0
  92. package/dist/rules/missing-reconcile.js +44 -0
  93. package/dist/rules/missing-reconcile.js.map +1 -0
  94. package/dist/rules/missing-visibility.d.ts +3 -0
  95. package/dist/rules/missing-visibility.d.ts.map +1 -0
  96. package/dist/rules/missing-visibility.js +63 -0
  97. package/dist/rules/missing-visibility.js.map +1 -0
  98. package/dist/rules/no-direct-impl-in-route.d.ts.map +1 -1
  99. package/dist/rules/no-direct-impl-in-route.js +0 -3
  100. package/dist/rules/no-direct-impl-in-route.js.map +1 -1
  101. package/dist/rules/no-direct-implementation-call.js +1 -1
  102. package/dist/rules/no-direct-implementation-call.js.map +1 -1
  103. package/dist/rules/no-sync-result-assumption.d.ts.map +1 -1
  104. package/dist/rules/no-sync-result-assumption.js +870 -61
  105. package/dist/rules/no-sync-result-assumption.js.map +1 -1
  106. package/dist/rules/no-throw-in-detour-recover.d.ts +3 -0
  107. package/dist/rules/no-throw-in-detour-recover.d.ts.map +1 -0
  108. package/dist/rules/no-throw-in-detour-recover.js +147 -0
  109. package/dist/rules/no-throw-in-detour-recover.js.map +1 -0
  110. package/dist/rules/no-throw-in-detour-target.d.ts +4 -1
  111. package/dist/rules/no-throw-in-detour-target.d.ts.map +1 -1
  112. package/dist/rules/no-throw-in-detour-target.js +6 -3
  113. package/dist/rules/no-throw-in-detour-target.js.map +1 -1
  114. package/dist/rules/no-throw-in-implementation.d.ts +4 -2
  115. package/dist/rules/no-throw-in-implementation.d.ts.map +1 -1
  116. package/dist/rules/no-throw-in-implementation.js +6 -4
  117. package/dist/rules/no-throw-in-implementation.js.map +1 -1
  118. package/dist/rules/on-references-exist.d.ts +14 -0
  119. package/dist/rules/on-references-exist.d.ts.map +1 -0
  120. package/dist/rules/on-references-exist.js +109 -0
  121. package/dist/rules/on-references-exist.js.map +1 -0
  122. package/dist/rules/orphaned-signal.d.ts +3 -0
  123. package/dist/rules/orphaned-signal.d.ts.map +1 -0
  124. package/dist/rules/orphaned-signal.js +67 -0
  125. package/dist/rules/orphaned-signal.js.map +1 -0
  126. package/dist/rules/permit-governance.d.ts +3 -0
  127. package/dist/rules/permit-governance.d.ts.map +1 -0
  128. package/dist/rules/permit-governance.js +15 -0
  129. package/dist/rules/permit-governance.js.map +1 -0
  130. package/dist/rules/reference-exists.d.ts +6 -0
  131. package/dist/rules/reference-exists.d.ts.map +1 -0
  132. package/dist/rules/reference-exists.js +47 -0
  133. package/dist/rules/reference-exists.js.map +1 -0
  134. package/dist/rules/registry-names.d.ts +8 -0
  135. package/dist/rules/registry-names.d.ts.map +1 -0
  136. package/dist/rules/registry-names.js +83 -0
  137. package/dist/rules/registry-names.js.map +1 -0
  138. package/dist/rules/resource-declarations.d.ts +14 -0
  139. package/dist/rules/resource-declarations.d.ts.map +1 -0
  140. package/dist/rules/resource-declarations.js +413 -0
  141. package/dist/rules/resource-declarations.js.map +1 -0
  142. package/dist/rules/resource-exists.d.ts +6 -0
  143. package/dist/rules/resource-exists.d.ts.map +1 -0
  144. package/dist/rules/resource-exists.js +90 -0
  145. package/dist/rules/resource-exists.js.map +1 -0
  146. package/dist/rules/resource-id-grammar.d.ts +3 -0
  147. package/dist/rules/resource-id-grammar.d.ts.map +1 -0
  148. package/dist/rules/resource-id-grammar.js +39 -0
  149. package/dist/rules/resource-id-grammar.js.map +1 -0
  150. package/dist/rules/specs.d.ts.map +1 -1
  151. package/dist/rules/specs.js +5 -1
  152. package/dist/rules/specs.js.map +1 -1
  153. package/dist/rules/types.d.ts +53 -4
  154. package/dist/rules/types.d.ts.map +1 -1
  155. package/dist/rules/unreachable-detour-shadowing.d.ts +3 -0
  156. package/dist/rules/unreachable-detour-shadowing.d.ts.map +1 -0
  157. package/dist/rules/unreachable-detour-shadowing.js +202 -0
  158. package/dist/rules/unreachable-detour-shadowing.js.map +1 -0
  159. package/dist/rules/valid-describe-refs.d.ts.map +1 -1
  160. package/dist/rules/valid-describe-refs.js +132 -16
  161. package/dist/rules/valid-describe-refs.js.map +1 -1
  162. package/dist/rules/valid-detour-contract.d.ts +3 -0
  163. package/dist/rules/valid-detour-contract.d.ts.map +1 -0
  164. package/dist/rules/valid-detour-contract.js +47 -0
  165. package/dist/rules/valid-detour-contract.js.map +1 -0
  166. package/dist/rules/valid-detour-refs.d.ts.map +1 -1
  167. package/dist/rules/valid-detour-refs.js +73 -82
  168. package/dist/rules/valid-detour-refs.js.map +1 -1
  169. package/dist/rules/warden-export-symmetry.d.ts +7 -0
  170. package/dist/rules/warden-export-symmetry.d.ts.map +1 -0
  171. package/dist/rules/warden-export-symmetry.js +352 -0
  172. package/dist/rules/warden-export-symmetry.js.map +1 -0
  173. package/dist/rules/warden-rules-use-ast.d.ts +17 -0
  174. package/dist/rules/warden-rules-use-ast.d.ts.map +1 -0
  175. package/dist/rules/warden-rules-use-ast.js +778 -0
  176. package/dist/rules/warden-rules-use-ast.js.map +1 -0
  177. package/dist/trails/circular-refs.trail.d.ts +24 -0
  178. package/dist/trails/circular-refs.trail.d.ts.map +1 -0
  179. package/dist/trails/circular-refs.trail.js +29 -0
  180. package/dist/trails/circular-refs.trail.js.map +1 -0
  181. package/dist/trails/context-no-surface-types.trail.d.ts +2 -2
  182. package/dist/trails/context-no-surface-types.trail.d.ts.map +1 -1
  183. package/dist/trails/context-no-trailhead-types.trail.d.ts +2 -2
  184. package/dist/trails/context-no-trailhead-types.trail.d.ts.map +1 -1
  185. package/dist/trails/contour-exists.trail.d.ts +24 -0
  186. package/dist/trails/contour-exists.trail.d.ts.map +1 -0
  187. package/dist/trails/contour-exists.trail.js +21 -0
  188. package/dist/trails/contour-exists.trail.js.map +1 -0
  189. package/dist/trails/cross-declarations.trail.d.ts +2 -2
  190. package/dist/trails/cross-declarations.trail.d.ts.map +1 -1
  191. package/dist/trails/dead-internal-trail.trail.d.ts +24 -0
  192. package/dist/trails/dead-internal-trail.trail.d.ts.map +1 -0
  193. package/dist/trails/dead-internal-trail.trail.js +26 -0
  194. package/dist/trails/dead-internal-trail.trail.js.map +1 -0
  195. package/dist/trails/{provision-declarations.trail.d.ts → draft-file-marking.trail.d.ts} +3 -3
  196. package/dist/trails/draft-file-marking.trail.d.ts.map +1 -0
  197. package/dist/trails/draft-file-marking.trail.js +16 -0
  198. package/dist/trails/draft-file-marking.trail.js.map +1 -0
  199. package/dist/trails/draft-visible-debt.trail.d.ts +13 -0
  200. package/dist/trails/draft-visible-debt.trail.d.ts.map +1 -0
  201. package/dist/trails/draft-visible-debt.trail.js +16 -0
  202. package/dist/trails/draft-visible-debt.trail.js.map +1 -0
  203. package/dist/trails/error-mapping-completeness.trail.d.ts +13 -0
  204. package/dist/trails/error-mapping-completeness.trail.d.ts.map +1 -0
  205. package/dist/trails/error-mapping-completeness.trail.js +29 -0
  206. package/dist/trails/error-mapping-completeness.trail.js.map +1 -0
  207. package/dist/trails/{follow-declarations.trail.d.ts → example-valid.trail.d.ts} +3 -3
  208. package/dist/trails/example-valid.trail.d.ts.map +1 -0
  209. package/dist/trails/example-valid.trail.js +25 -0
  210. package/dist/trails/example-valid.trail.js.map +1 -0
  211. package/dist/trails/fires-declarations.trail.d.ts +13 -0
  212. package/dist/trails/fires-declarations.trail.d.ts.map +1 -0
  213. package/dist/trails/fires-declarations.trail.js +22 -0
  214. package/dist/trails/fires-declarations.trail.js.map +1 -0
  215. package/dist/trails/implementation-returns-result.trail.d.ts +2 -2
  216. package/dist/trails/implementation-returns-result.trail.d.ts.map +1 -1
  217. package/dist/trails/incomplete-accessor-for-standard-op.trail.d.ts +12 -0
  218. package/dist/trails/incomplete-accessor-for-standard-op.trail.d.ts.map +1 -0
  219. package/dist/trails/incomplete-accessor-for-standard-op.trail.js +60 -0
  220. package/dist/trails/incomplete-accessor-for-standard-op.trail.js.map +1 -0
  221. package/dist/trails/incomplete-crud.trail.d.ts +24 -0
  222. package/dist/trails/incomplete-crud.trail.d.ts.map +1 -0
  223. package/dist/trails/incomplete-crud.trail.js +39 -0
  224. package/dist/trails/incomplete-crud.trail.js.map +1 -0
  225. package/dist/trails/index.d.ts +29 -7
  226. package/dist/trails/index.d.ts.map +1 -1
  227. package/dist/trails/index.js +28 -6
  228. package/dist/trails/index.js.map +1 -1
  229. package/dist/trails/intent-propagation.trail.d.ts +24 -0
  230. package/dist/trails/intent-propagation.trail.d.ts.map +1 -0
  231. package/dist/trails/intent-propagation.trail.js +30 -0
  232. package/dist/trails/intent-propagation.trail.js.map +1 -0
  233. package/dist/trails/missing-reconcile.trail.d.ts +24 -0
  234. package/dist/trails/missing-reconcile.trail.d.ts.map +1 -0
  235. package/dist/trails/missing-reconcile.trail.js +33 -0
  236. package/dist/trails/missing-reconcile.trail.js.map +1 -0
  237. package/dist/trails/missing-visibility.trail.d.ts +24 -0
  238. package/dist/trails/missing-visibility.trail.d.ts.map +1 -0
  239. package/dist/trails/missing-visibility.trail.js +22 -0
  240. package/dist/trails/missing-visibility.trail.js.map +1 -0
  241. package/dist/trails/no-direct-impl-in-route.trail.d.ts +2 -2
  242. package/dist/trails/no-direct-impl-in-route.trail.d.ts.map +1 -1
  243. package/dist/trails/no-direct-implementation-call.trail.d.ts +2 -2
  244. package/dist/trails/no-direct-implementation-call.trail.d.ts.map +1 -1
  245. package/dist/trails/no-sync-result-assumption.trail.d.ts +2 -2
  246. package/dist/trails/no-sync-result-assumption.trail.d.ts.map +1 -1
  247. package/dist/trails/no-throw-in-detour-recover.trail.d.ts +13 -0
  248. package/dist/trails/no-throw-in-detour-recover.trail.d.ts.map +1 -0
  249. package/dist/trails/no-throw-in-detour-recover.trail.js +24 -0
  250. package/dist/trails/no-throw-in-detour-recover.trail.js.map +1 -0
  251. package/dist/trails/no-throw-in-detour-target.trail.d.ts +13 -3
  252. package/dist/trails/no-throw-in-detour-target.trail.d.ts.map +1 -1
  253. package/dist/trails/no-throw-in-implementation.trail.d.ts +2 -2
  254. package/dist/trails/no-throw-in-implementation.trail.d.ts.map +1 -1
  255. package/dist/trails/on-references-exist.trail.d.ts +24 -0
  256. package/dist/trails/on-references-exist.trail.d.ts.map +1 -0
  257. package/dist/trails/on-references-exist.trail.js +21 -0
  258. package/dist/trails/on-references-exist.trail.js.map +1 -0
  259. package/dist/trails/orphaned-signal.trail.d.ts +24 -0
  260. package/dist/trails/orphaned-signal.trail.d.ts.map +1 -0
  261. package/dist/trails/orphaned-signal.trail.js +36 -0
  262. package/dist/trails/orphaned-signal.trail.js.map +1 -0
  263. package/dist/trails/permit-governance.trail.d.ts +12 -0
  264. package/dist/trails/permit-governance.trail.d.ts.map +1 -0
  265. package/dist/trails/permit-governance.trail.js +47 -0
  266. package/dist/trails/permit-governance.trail.js.map +1 -0
  267. package/dist/trails/prefer-schema-inference.trail.d.ts +2 -2
  268. package/dist/trails/prefer-schema-inference.trail.d.ts.map +1 -1
  269. package/dist/trails/reference-exists.trail.d.ts +24 -0
  270. package/dist/trails/reference-exists.trail.d.ts.map +1 -0
  271. package/dist/trails/reference-exists.trail.js +25 -0
  272. package/dist/trails/reference-exists.trail.js.map +1 -0
  273. package/dist/trails/resource-declarations.trail.d.ts +13 -0
  274. package/dist/trails/resource-declarations.trail.d.ts.map +1 -0
  275. package/dist/trails/{provision-declarations.trail.js → resource-declarations.trail.js} +7 -7
  276. package/dist/trails/resource-declarations.trail.js.map +1 -0
  277. package/dist/trails/resource-exists.trail.d.ts +24 -0
  278. package/dist/trails/resource-exists.trail.d.ts.map +1 -0
  279. package/dist/trails/{provision-exists.trail.js → resource-exists.trail.js} +8 -8
  280. package/dist/trails/resource-exists.trail.js.map +1 -0
  281. package/dist/trails/resource-id-grammar.trail.d.ts +13 -0
  282. package/dist/trails/resource-id-grammar.trail.d.ts.map +1 -0
  283. package/dist/trails/resource-id-grammar.trail.js +38 -0
  284. package/dist/trails/resource-id-grammar.trail.js.map +1 -0
  285. package/dist/trails/run.d.ts +25 -9
  286. package/dist/trails/run.d.ts.map +1 -1
  287. package/dist/trails/run.js +63 -19
  288. package/dist/trails/run.js.map +1 -1
  289. package/dist/trails/schema.d.ts +28 -3
  290. package/dist/trails/schema.d.ts.map +1 -1
  291. package/dist/trails/schema.js +57 -4
  292. package/dist/trails/schema.js.map +1 -1
  293. package/dist/trails/unreachable-detour-shadowing.trail.d.ts +13 -0
  294. package/dist/trails/unreachable-detour-shadowing.trail.d.ts.map +1 -0
  295. package/dist/trails/unreachable-detour-shadowing.trail.js +44 -0
  296. package/dist/trails/unreachable-detour-shadowing.trail.js.map +1 -0
  297. package/dist/trails/valid-describe-refs.trail.d.ts +12 -3
  298. package/dist/trails/valid-describe-refs.trail.d.ts.map +1 -1
  299. package/dist/trails/valid-detour-contract.trail.d.ts +12 -0
  300. package/dist/trails/valid-detour-contract.trail.d.ts.map +1 -0
  301. package/dist/trails/valid-detour-contract.trail.js +66 -0
  302. package/dist/trails/valid-detour-contract.trail.js.map +1 -0
  303. package/dist/trails/valid-detour-refs.trail.d.ts +13 -3
  304. package/dist/trails/valid-detour-refs.trail.d.ts.map +1 -1
  305. package/dist/trails/warden-export-symmetry.trail.d.ts +13 -0
  306. package/dist/trails/warden-export-symmetry.trail.d.ts.map +1 -0
  307. package/dist/trails/warden-export-symmetry.trail.js +16 -0
  308. package/dist/trails/warden-export-symmetry.trail.js.map +1 -0
  309. package/dist/trails/warden-rules-use-ast.trail.d.ts +13 -0
  310. package/dist/trails/warden-rules-use-ast.trail.d.ts.map +1 -0
  311. package/dist/trails/warden-rules-use-ast.trail.js +41 -0
  312. package/dist/trails/warden-rules-use-ast.trail.js.map +1 -0
  313. package/dist/trails/wrap-rule.d.ts +16 -2
  314. package/dist/trails/wrap-rule.d.ts.map +1 -1
  315. package/dist/trails/wrap-rule.js +71 -11
  316. package/dist/trails/wrap-rule.js.map +1 -1
  317. package/package.json +7 -4
  318. package/src/__tests__/ast.test.ts +613 -0
  319. package/src/__tests__/circular-refs.test.ts +121 -0
  320. package/src/__tests__/cli.test.ts +360 -32
  321. package/src/__tests__/contour-exists.test.ts +203 -0
  322. package/src/__tests__/cross-declarations.test.ts +245 -0
  323. package/src/__tests__/dead-internal-trail.test.ts +81 -0
  324. package/src/__tests__/draft-rules-context.test.ts +150 -0
  325. package/src/__tests__/drift.test.ts +75 -5
  326. package/src/__tests__/error-mapping-completeness.test.ts +56 -0
  327. package/src/__tests__/example-valid.test.ts +101 -0
  328. package/src/__tests__/fires-declarations-param-destructure.test.ts +54 -0
  329. package/src/__tests__/fires-declarations.test.ts +652 -0
  330. package/src/__tests__/formatters.test.ts +2 -2
  331. package/src/__tests__/implementation-returns-result.test.ts +1016 -2
  332. package/src/__tests__/incomplete-accessor-for-standard-op.test.ts +337 -0
  333. package/src/__tests__/incomplete-crud.test.ts +498 -0
  334. package/src/__tests__/intent-propagation.test.ts +116 -0
  335. package/src/__tests__/missing-reconcile.test.ts +154 -0
  336. package/src/__tests__/missing-visibility.test.ts +108 -0
  337. package/src/__tests__/no-sync-result-assumption.test.ts +870 -39
  338. package/src/__tests__/no-throw-in-detour-recover.test.ts +93 -0
  339. package/src/__tests__/no-throw-in-implementation.test.ts +88 -0
  340. package/src/__tests__/on-references-exist.test.ts +151 -0
  341. package/src/__tests__/orphaned-signal.test.ts +137 -0
  342. package/src/__tests__/permit-governance.test.ts +66 -0
  343. package/src/__tests__/reference-exists.test.ts +281 -0
  344. package/src/__tests__/resource-declarations.test.ts +448 -0
  345. package/src/__tests__/resource-exists.test.ts +122 -0
  346. package/src/__tests__/resource-id-grammar.test.ts +50 -0
  347. package/src/__tests__/rules.test.ts +17 -77
  348. package/src/__tests__/topo-aware-rule.test.ts +257 -0
  349. package/src/__tests__/trails.test.ts +2 -2
  350. package/src/__tests__/unreachable-detour-shadowing.test.ts +128 -0
  351. package/src/__tests__/valid-describe-refs.test.ts +183 -0
  352. package/src/__tests__/valid-detour-contract.test.ts +86 -0
  353. package/src/__tests__/warden-export-symmetry.test.ts +251 -0
  354. package/src/__tests__/warden-rules-use-ast.test.ts +468 -0
  355. package/src/__tests__/wrap-rule.test.ts +3 -3
  356. package/src/cli.ts +458 -91
  357. package/src/draft.ts +22 -0
  358. package/src/drift.ts +63 -21
  359. package/src/formatters.ts +15 -4
  360. package/src/index.ts +62 -23
  361. package/src/rules/ast.ts +2715 -119
  362. package/src/rules/circular-refs.ts +154 -0
  363. package/src/rules/{context-no-trailhead-types.ts → context-no-surface-types.ts} +72 -12
  364. package/src/rules/contour-exists.ts +251 -0
  365. package/src/rules/contour-ids.ts +15 -0
  366. package/src/rules/cross-declarations.ts +277 -69
  367. package/src/rules/dead-internal-trail.ts +141 -0
  368. package/src/rules/draft-file-marking.ts +160 -0
  369. package/src/rules/draft-visible-debt.ts +87 -0
  370. package/src/rules/error-mapping-completeness.ts +273 -0
  371. package/src/rules/example-valid.ts +401 -0
  372. package/src/rules/fires-declarations.ts +609 -0
  373. package/src/rules/implementation-returns-result.ts +1042 -122
  374. package/src/rules/incomplete-accessor-for-standard-op.ts +315 -0
  375. package/src/rules/incomplete-crud.ts +579 -0
  376. package/src/rules/index.ts +95 -16
  377. package/src/rules/intent-propagation.ts +142 -0
  378. package/src/rules/missing-reconcile.ts +98 -0
  379. package/src/rules/missing-visibility.ts +110 -0
  380. package/src/rules/no-direct-impl-in-route.ts +0 -4
  381. package/src/rules/no-direct-implementation-call.ts +1 -1
  382. package/src/rules/no-sync-result-assumption.ts +1134 -96
  383. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  384. package/src/rules/no-throw-in-implementation.ts +6 -4
  385. package/src/rules/on-references-exist.ts +194 -0
  386. package/src/rules/orphaned-signal.ts +150 -0
  387. package/src/rules/permit-governance.ts +25 -0
  388. package/src/rules/reference-exists.ts +98 -0
  389. package/src/rules/registry-names.ts +83 -0
  390. package/src/rules/{provision-declarations.ts → resource-declarations.ts} +208 -138
  391. package/src/rules/{provision-exists.ts → resource-exists.ts} +48 -51
  392. package/src/rules/resource-id-grammar.ts +65 -0
  393. package/src/rules/specs.ts +5 -1
  394. package/src/rules/types.ts +57 -4
  395. package/src/rules/unreachable-detour-shadowing.ts +375 -0
  396. package/src/rules/valid-describe-refs.ts +160 -32
  397. package/src/rules/valid-detour-contract.ts +78 -0
  398. package/src/rules/warden-export-symmetry.ts +533 -0
  399. package/src/rules/warden-rules-use-ast.ts +996 -0
  400. package/src/trails/circular-refs.trail.ts +29 -0
  401. package/src/trails/{context-no-trailhead-types.trail.ts → context-no-surface-types.trail.ts} +4 -4
  402. package/src/trails/contour-exists.trail.ts +21 -0
  403. package/src/trails/dead-internal-trail.trail.ts +26 -0
  404. package/src/trails/draft-file-marking.trail.ts +16 -0
  405. package/src/trails/draft-visible-debt.trail.ts +16 -0
  406. package/src/trails/error-mapping-completeness.trail.ts +29 -0
  407. package/src/trails/example-valid.trail.ts +25 -0
  408. package/src/trails/fires-declarations.trail.ts +22 -0
  409. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  410. package/src/trails/incomplete-crud.trail.ts +39 -0
  411. package/src/trails/index.ts +40 -7
  412. package/src/trails/intent-propagation.trail.ts +30 -0
  413. package/src/trails/missing-reconcile.trail.ts +33 -0
  414. package/src/trails/missing-visibility.trail.ts +22 -0
  415. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  416. package/src/trails/on-references-exist.trail.ts +21 -0
  417. package/src/trails/orphaned-signal.trail.ts +36 -0
  418. package/src/trails/permit-governance.trail.ts +51 -0
  419. package/src/trails/reference-exists.trail.ts +25 -0
  420. package/src/trails/{provision-declarations.trail.ts → resource-declarations.trail.ts} +6 -6
  421. package/src/trails/{provision-exists.trail.ts → resource-exists.trail.ts} +7 -7
  422. package/src/trails/resource-id-grammar.trail.ts +39 -0
  423. package/src/trails/run.ts +121 -24
  424. package/src/trails/schema.ts +66 -4
  425. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  426. package/src/trails/valid-detour-contract.trail.ts +71 -0
  427. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  428. package/src/trails/warden-rules-use-ast.trail.ts +45 -0
  429. package/src/trails/wrap-rule.ts +104 -12
  430. package/tsconfig.tests.json +10 -0
  431. package/tsconfig.tsbuildinfo +1 -1
  432. package/dist/rules/follow-declarations.d.ts +0 -13
  433. package/dist/rules/follow-declarations.d.ts.map +0 -1
  434. package/dist/rules/follow-declarations.js +0 -264
  435. package/dist/rules/follow-declarations.js.map +0 -1
  436. package/dist/rules/provision-declarations.d.ts +0 -14
  437. package/dist/rules/provision-declarations.d.ts.map +0 -1
  438. package/dist/rules/provision-declarations.js +0 -344
  439. package/dist/rules/provision-declarations.js.map +0 -1
  440. package/dist/rules/provision-exists.d.ts +0 -6
  441. package/dist/rules/provision-exists.d.ts.map +0 -1
  442. package/dist/rules/provision-exists.js +0 -89
  443. package/dist/rules/provision-exists.js.map +0 -1
  444. package/dist/rules/service-declarations.d.ts +0 -16
  445. package/dist/rules/service-declarations.d.ts.map +0 -1
  446. package/dist/rules/service-declarations.js +0 -346
  447. package/dist/rules/service-declarations.js.map +0 -1
  448. package/dist/rules/service-exists.d.ts +0 -8
  449. package/dist/rules/service-exists.d.ts.map +0 -1
  450. package/dist/rules/service-exists.js +0 -91
  451. package/dist/rules/service-exists.js.map +0 -1
  452. package/dist/trails/follow-declarations.trail.d.ts.map +0 -1
  453. package/dist/trails/follow-declarations.trail.js +0 -22
  454. package/dist/trails/follow-declarations.trail.js.map +0 -1
  455. package/dist/trails/provision-declarations.trail.d.ts.map +0 -1
  456. package/dist/trails/provision-declarations.trail.js.map +0 -1
  457. package/dist/trails/provision-exists.trail.d.ts +0 -15
  458. package/dist/trails/provision-exists.trail.d.ts.map +0 -1
  459. package/dist/trails/provision-exists.trail.js.map +0 -1
  460. package/dist/trails/service-declarations.trail.d.ts +0 -26
  461. package/dist/trails/service-declarations.trail.d.ts.map +0 -1
  462. package/dist/trails/service-declarations.trail.js +0 -27
  463. package/dist/trails/service-declarations.trail.js.map +0 -1
  464. package/dist/trails/service-exists.trail.d.ts +0 -32
  465. package/dist/trails/service-exists.trail.d.ts.map +0 -1
  466. package/dist/trails/service-exists.trail.js +0 -29
  467. package/dist/trails/service-exists.trail.js.map +0 -1
  468. package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
  469. package/src/__tests__/provision-declarations.test.ts +0 -318
  470. package/src/__tests__/provision-exists.test.ts +0 -122
  471. package/src/rules/no-throw-in-detour-target.ts +0 -150
  472. package/src/rules/valid-detour-refs.ts +0 -187
  473. package/src/trails/no-throw-in-detour-target.trail.ts +0 -20
  474. package/src/trails/valid-detour-refs.trail.ts +0 -24
@@ -0,0 +1,225 @@
1
+ import {
2
+ findConfigProperty,
3
+ findTrailDefinitions,
4
+ identifierName,
5
+ offsetToLine,
6
+ parse,
7
+ walk,
8
+ walkScope,
9
+ } from './ast.js';
10
+ import type { AstNode } from './ast.js';
11
+ import { isTestFile } from './scan.js';
12
+ import type { WardenDiagnostic, WardenRule } from './types.js';
13
+
14
+ const TRANSPARENT_WRAPPER_TYPES = new Set([
15
+ 'ParenthesizedExpression',
16
+ 'TSAsExpression',
17
+ 'TSSatisfiesExpression',
18
+ 'TSNonNullExpression',
19
+ 'TSTypeAssertion',
20
+ ]);
21
+
22
+ const FUNCTION_TYPES = new Set([
23
+ 'ArrowFunctionExpression',
24
+ 'FunctionDeclaration',
25
+ 'FunctionExpression',
26
+ ]);
27
+
28
+ const unwrapExpression = (node: AstNode | undefined): AstNode | undefined => {
29
+ let current = node;
30
+ while (current && TRANSPARENT_WRAPPER_TYPES.has(current.type)) {
31
+ current = current['expression'] as AstNode | undefined;
32
+ }
33
+ return current;
34
+ };
35
+
36
+ const getDetourElements = (config: AstNode): readonly (AstNode | null)[] => {
37
+ const detoursProp = findConfigProperty(config, 'detours');
38
+ if (!detoursProp) {
39
+ return [];
40
+ }
41
+
42
+ const detoursValue = unwrapExpression(
43
+ detoursProp.value as AstNode | undefined
44
+ );
45
+ if (!detoursValue || detoursValue.type !== 'ArrayExpression') {
46
+ return [];
47
+ }
48
+
49
+ return (
50
+ ((detoursValue as AstNode)['elements'] as readonly (AstNode | null)[]) ?? []
51
+ );
52
+ };
53
+
54
+ const getFunctionBody = (node: AstNode | undefined): AstNode | undefined => {
55
+ if (!node || !FUNCTION_TYPES.has(node.type)) {
56
+ return undefined;
57
+ }
58
+
59
+ const { body } = node;
60
+ return Array.isArray(body) ? undefined : (body as AstNode | undefined);
61
+ };
62
+
63
+ interface RecoverBodyMatch {
64
+ readonly index: number;
65
+ readonly trailId: string;
66
+ readonly body: AstNode;
67
+ }
68
+
69
+ const collectFunctionRecoverBinding = (
70
+ bindings: Map<string, AstNode>,
71
+ node: AstNode
72
+ ): boolean => {
73
+ if (node.type !== 'FunctionDeclaration') {
74
+ return false;
75
+ }
76
+
77
+ const name = identifierName(node['id'] as AstNode | undefined);
78
+ if (name) {
79
+ bindings.set(name, node);
80
+ }
81
+ return true;
82
+ };
83
+
84
+ const collectVariableRecoverBinding = (
85
+ bindings: Map<string, AstNode>,
86
+ node: AstNode
87
+ ): void => {
88
+ if (node.type !== 'VariableDeclarator') {
89
+ return;
90
+ }
91
+
92
+ const name = identifierName(node['id'] as AstNode | undefined);
93
+ const init = unwrapExpression(node['init'] as AstNode | undefined);
94
+ if (!name || !init || !FUNCTION_TYPES.has(init.type)) {
95
+ return;
96
+ }
97
+
98
+ bindings.set(name, init);
99
+ };
100
+
101
+ const collectRecoverBinding = (
102
+ bindings: Map<string, AstNode>,
103
+ node: AstNode
104
+ ): void => {
105
+ if (collectFunctionRecoverBinding(bindings, node)) {
106
+ return;
107
+ }
108
+
109
+ collectVariableRecoverBinding(bindings, node);
110
+ };
111
+
112
+ const collectRecoverBindings = (ast: AstNode): ReadonlyMap<string, AstNode> => {
113
+ const bindings = new Map<string, AstNode>();
114
+
115
+ walk(ast, (node) => {
116
+ collectRecoverBinding(bindings, node);
117
+ });
118
+
119
+ return bindings;
120
+ };
121
+
122
+ const resolveRecoverBody = (
123
+ node: AstNode | undefined,
124
+ bindings: ReadonlyMap<string, AstNode>
125
+ ): AstNode | undefined => {
126
+ const unwrapped = unwrapExpression(node);
127
+ if (!unwrapped) {
128
+ return undefined;
129
+ }
130
+
131
+ const inlineBody = getFunctionBody(unwrapped);
132
+ if (inlineBody) {
133
+ return inlineBody;
134
+ }
135
+
136
+ const bindingName = identifierName(unwrapped);
137
+ if (!bindingName) {
138
+ return undefined;
139
+ }
140
+
141
+ return getFunctionBody(bindings.get(bindingName));
142
+ };
143
+
144
+ const resolveRecoverBodyFromElement = (
145
+ element: AstNode | null,
146
+ bindings: ReadonlyMap<string, AstNode>
147
+ ): AstNode | undefined => {
148
+ if (!element || element.type !== 'ObjectExpression') {
149
+ return undefined;
150
+ }
151
+
152
+ const recoverProp = findConfigProperty(element, 'recover');
153
+ return resolveRecoverBody(
154
+ recoverProp?.value as AstNode | undefined,
155
+ bindings
156
+ );
157
+ };
158
+
159
+ const appendRecoverBodies = (
160
+ bodies: RecoverBodyMatch[],
161
+ definition: { readonly config: AstNode; readonly id: string },
162
+ bindings: ReadonlyMap<string, AstNode>
163
+ ): void => {
164
+ const detourElements = getDetourElements(definition.config);
165
+ for (const [index, element] of detourElements.entries()) {
166
+ const body = resolveRecoverBodyFromElement(element, bindings);
167
+ if (!body) {
168
+ continue;
169
+ }
170
+
171
+ bodies.push({ body, index, trailId: definition.id });
172
+ }
173
+ };
174
+
175
+ const findRecoverBodies = (ast: AstNode): readonly RecoverBodyMatch[] => {
176
+ const bindings = collectRecoverBindings(ast);
177
+ const bodies: RecoverBodyMatch[] = [];
178
+
179
+ for (const definition of findTrailDefinitions(ast)) {
180
+ if (definition.kind !== 'trail') {
181
+ continue;
182
+ }
183
+
184
+ appendRecoverBodies(bodies, definition, bindings);
185
+ }
186
+
187
+ return bodies;
188
+ };
189
+
190
+ export const noThrowInDetourRecover: WardenRule = {
191
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
192
+ if (isTestFile(filePath)) {
193
+ return [];
194
+ }
195
+
196
+ const ast = parse(filePath, sourceCode);
197
+ if (!ast) {
198
+ return [];
199
+ }
200
+
201
+ const diagnostics: WardenDiagnostic[] = [];
202
+
203
+ for (const recover of findRecoverBodies(ast)) {
204
+ walkScope(recover.body, (node) => {
205
+ if (node.type !== 'ThrowStatement') {
206
+ return;
207
+ }
208
+
209
+ diagnostics.push({
210
+ filePath,
211
+ line: offsetToLine(sourceCode, node.start),
212
+ message: `Trail "${recover.trailId}" detour[${recover.index}] recover must not throw. Return Result.err() instead.`,
213
+ rule: 'no-throw-in-detour-recover',
214
+ severity: 'error',
215
+ });
216
+ });
217
+ }
218
+
219
+ return diagnostics;
220
+ },
221
+ description:
222
+ 'Disallow throw statements inside detour recover functions. Use Result.err() instead.',
223
+ name: 'no-throw-in-detour-recover',
224
+ severity: 'error',
225
+ };
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * Finds `throw` statements inside `blaze:` function bodies.
3
3
  *
4
- * Uses AST parsing for accurate detection no false positives from
5
- * throw in comments, strings, or nested non-implementation functions.
4
+ * Uses scope-aware AST walking so throws inside nested callbacks
5
+ * (e.g. `.map()`, `.filter()`, inner helpers) are not attributed to
6
+ * the blaze body itself. ADR-0007 requires this class of false positive
7
+ * to be avoided — only throws in the blaze body scope should be flagged.
6
8
  */
7
9
 
8
- import { findBlazeBodies, offsetToLine, parse, walk } from './ast.js';
10
+ import { findBlazeBodies, offsetToLine, parse, walkScope } from './ast.js';
9
11
  import type { WardenDiagnostic, WardenRule } from './types.js';
10
12
 
11
13
  export const noThrowInImplementation: WardenRule = {
@@ -18,7 +20,7 @@ export const noThrowInImplementation: WardenRule = {
18
20
  const diagnostics: WardenDiagnostic[] = [];
19
21
 
20
22
  for (const body of findBlazeBodies(ast)) {
21
- walk(body, (node) => {
23
+ walkScope(body, (node) => {
22
24
  if (node.type === 'ThrowStatement') {
23
25
  diagnostics.push({
24
26
  filePath,
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Validates that every signal id declared in a trail's `on:` array resolves
3
+ * to a known signal definition somewhere in the project.
4
+ *
5
+ * Mirrors `resource-exists` structurally — collects local signal definitions
6
+ * for the standalone `check()` path and accepts a project-wide
7
+ * `knownSignalIds` set via `checkWithContext()`.
8
+ */
9
+
10
+ import { isDraftId } from '@ontrails/core';
11
+
12
+ import {
13
+ collectSignalDefinitionIds,
14
+ findConfigProperty,
15
+ findTrailDefinitions,
16
+ getStringValue,
17
+ identifierName,
18
+ isStringLiteral,
19
+ offsetToLine,
20
+ parse,
21
+ deriveConstString,
22
+ } from './ast.js';
23
+ import type { AstNode } from './ast.js';
24
+ import { isTestFile } from './scan.js';
25
+ import type {
26
+ ProjectAwareWardenRule,
27
+ ProjectContext,
28
+ WardenDiagnostic,
29
+ } from './types.js';
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Declared `on:` extraction
33
+ // ---------------------------------------------------------------------------
34
+
35
+ const getOnElements = (config: AstNode): readonly AstNode[] => {
36
+ const onProp = findConfigProperty(config, 'on');
37
+ if (!onProp) {
38
+ return [];
39
+ }
40
+
41
+ const arrayNode = onProp.value;
42
+ if (!arrayNode || (arrayNode as AstNode).type !== 'ArrayExpression') {
43
+ return [];
44
+ }
45
+
46
+ const elements = (arrayNode as AstNode)['elements'] as
47
+ | readonly AstNode[]
48
+ | undefined;
49
+ return elements ?? [];
50
+ };
51
+
52
+ /**
53
+ * Resolve an `on:` array element to a signal id when possible.
54
+ *
55
+ * Handles string literals and `const NAME = 'id'` identifier references.
56
+ * Object-form entries (e.g. `on: [someSignal]` where `someSignal` is a
57
+ * `Signal` value) cannot be statically resolved here and are skipped — the
58
+ * runtime normalizes them inside `trail()`, so skipping is safe. The tradeoff
59
+ * is that typo'd Signal imports won't be caught at lint time; the TypeScript
60
+ * compiler catches those instead.
61
+ */
62
+ const extractOnElementId = (
63
+ element: AstNode,
64
+ sourceCode: string
65
+ ): string | null => {
66
+ if (element.type === 'Identifier') {
67
+ const name = identifierName(element);
68
+ return name ? deriveConstString(name, sourceCode) : null;
69
+ }
70
+
71
+ if (isStringLiteral(element)) {
72
+ return getStringValue(element);
73
+ }
74
+
75
+ return null;
76
+ };
77
+
78
+ const extractDeclaredOnIds = (
79
+ config: AstNode,
80
+ sourceCode: string
81
+ ): readonly string[] => [
82
+ ...new Set(
83
+ getOnElements(config).flatMap((element) => {
84
+ const id = extractOnElementId(element, sourceCode);
85
+ return id ? [id] : [];
86
+ })
87
+ ),
88
+ ];
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Diagnostics
92
+ // ---------------------------------------------------------------------------
93
+
94
+ const buildMissingSignalDiagnostic = (
95
+ trailId: string,
96
+ signalId: string,
97
+ filePath: string,
98
+ line: number
99
+ ): WardenDiagnostic => ({
100
+ filePath,
101
+ line,
102
+ message: `Trail "${trailId}" declares on: "${signalId}" which is not a known signal in the project.`,
103
+ rule: 'on-references-exist',
104
+ severity: 'error',
105
+ });
106
+
107
+ const reportMissingSignals = (
108
+ def: { id: string; config: AstNode; start: number },
109
+ sourceCode: string,
110
+ filePath: string,
111
+ knownSignalIds: ReadonlySet<string>,
112
+ diagnostics: WardenDiagnostic[]
113
+ ): void => {
114
+ const line = offsetToLine(sourceCode, def.start);
115
+ for (const signalId of extractDeclaredOnIds(def.config, sourceCode)) {
116
+ if (!knownSignalIds.has(signalId) && !isDraftId(signalId)) {
117
+ diagnostics.push(
118
+ buildMissingSignalDiagnostic(def.id, signalId, filePath, line)
119
+ );
120
+ }
121
+ }
122
+ };
123
+
124
+ const buildSignalDiagnostics = (
125
+ ast: AstNode,
126
+ sourceCode: string,
127
+ filePath: string,
128
+ knownSignalIds: ReadonlySet<string>
129
+ ): readonly WardenDiagnostic[] => {
130
+ const diagnostics: WardenDiagnostic[] = [];
131
+ for (const def of findTrailDefinitions(ast)) {
132
+ if (def.kind !== 'trail') {
133
+ continue;
134
+ }
135
+ reportMissingSignals(
136
+ def,
137
+ sourceCode,
138
+ filePath,
139
+ knownSignalIds,
140
+ diagnostics
141
+ );
142
+ }
143
+ return diagnostics;
144
+ };
145
+
146
+ const checkOnReferences = (
147
+ ast: AstNode | null,
148
+ sourceCode: string,
149
+ filePath: string,
150
+ knownSignalIds: ReadonlySet<string>
151
+ ): readonly WardenDiagnostic[] => {
152
+ if (isTestFile(filePath) || !ast) {
153
+ return [];
154
+ }
155
+ return buildSignalDiagnostics(ast, sourceCode, filePath, knownSignalIds);
156
+ };
157
+
158
+ /**
159
+ * Checks that every `on:` reference resolves to a known signal definition.
160
+ */
161
+ export const onReferencesExist: ProjectAwareWardenRule = {
162
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
163
+ const ast = parse(filePath, sourceCode);
164
+ if (!ast) {
165
+ return [];
166
+ }
167
+ return checkOnReferences(
168
+ ast,
169
+ sourceCode,
170
+ filePath,
171
+ collectSignalDefinitionIds(ast)
172
+ );
173
+ },
174
+ checkWithContext(
175
+ sourceCode: string,
176
+ filePath: string,
177
+ context: ProjectContext
178
+ ): readonly WardenDiagnostic[] {
179
+ const ast = parse(filePath, sourceCode);
180
+ const localSignalIds = ast
181
+ ? collectSignalDefinitionIds(ast)
182
+ : new Set<string>();
183
+ return checkOnReferences(
184
+ ast,
185
+ sourceCode,
186
+ filePath,
187
+ context.knownSignalIds ?? localSignalIds
188
+ );
189
+ },
190
+ description:
191
+ 'Ensure every signal id declared in a trail on: array resolves to a known signal definition.',
192
+ name: 'on-references-exist',
193
+ severity: 'error',
194
+ };
@@ -0,0 +1,150 @@
1
+ import {
2
+ collectCrudTableIds,
3
+ collectOnTargetSignalIds,
4
+ findStoreTableDefinitions,
5
+ offsetToLine,
6
+ parse,
7
+ } from './ast.js';
8
+ import type { AstNode } from './ast.js';
9
+ import { isTestFile } from './scan.js';
10
+ import type {
11
+ ProjectAwareWardenRule,
12
+ ProjectContext,
13
+ WardenDiagnostic,
14
+ } from './types.js';
15
+
16
+ const CHANGE_SIGNAL_OPERATIONS = ['created', 'updated', 'removed'] as const;
17
+
18
+ const buildOrphanedSignalDiagnostic = (
19
+ tableId: string,
20
+ missingSignalIds: readonly string[],
21
+ filePath: string,
22
+ line: number
23
+ ): WardenDiagnostic => ({
24
+ filePath,
25
+ line,
26
+ message: `Store table "${tableId}" derives change signals with no trail listeners: ${missingSignalIds.join(', ')}. Add trail on: consumers or remove the unused reactive pattern.`,
27
+ rule: 'orphaned-signal',
28
+ severity: 'warn',
29
+ });
30
+
31
+ const getMissingSignalIds = (
32
+ tableId: string,
33
+ onTargetSignalIds: ReadonlySet<string>
34
+ ): readonly string[] =>
35
+ CHANGE_SIGNAL_OPERATIONS.map((operation) => `${tableId}.${operation}`).filter(
36
+ (signalId) =>
37
+ !onTargetSignalIds.has(signalId) &&
38
+ // Bare-name fallback: string-literal `on:` consumers store the signal
39
+ // without the composite `${storeBinding}:` prefix.
40
+ !onTargetSignalIds.has(signalId.replace(/^[^:]+:/, ''))
41
+ );
42
+
43
+ /**
44
+ * Strip the `${storeBinding}:` prefix from a composite signal id for display.
45
+ * Keeps diagnostic messages readable while keeping keys composite internally.
46
+ */
47
+ const stripStoreBinding = (
48
+ signalId: string,
49
+ storeBinding: string | null
50
+ ): string => {
51
+ if (!storeBinding) {
52
+ return signalId;
53
+ }
54
+ const prefix = `${storeBinding}:`;
55
+ return signalId.startsWith(prefix) ? signalId.slice(prefix.length) : signalId;
56
+ };
57
+
58
+ const buildDefinitionDiagnostic = (
59
+ definition: ReturnType<typeof findStoreTableDefinitions>[number],
60
+ sourceCode: string,
61
+ filePath: string,
62
+ crudTableIds: ReadonlySet<string>,
63
+ onTargetSignalIds: ReadonlySet<string>
64
+ ): WardenDiagnostic | null => {
65
+ if (!crudTableIds.has(definition.key)) {
66
+ return null;
67
+ }
68
+
69
+ const missingSignalIds = getMissingSignalIds(
70
+ definition.key,
71
+ onTargetSignalIds
72
+ );
73
+ return missingSignalIds.length === 0
74
+ ? null
75
+ : buildOrphanedSignalDiagnostic(
76
+ definition.name,
77
+ missingSignalIds.map((id) =>
78
+ stripStoreBinding(id, definition.storeBinding)
79
+ ),
80
+ filePath,
81
+ offsetToLine(sourceCode, definition.start)
82
+ );
83
+ };
84
+
85
+ const checkOrphanedSignals = (
86
+ ast: AstNode | null,
87
+ sourceCode: string,
88
+ filePath: string,
89
+ crudTableIds: ReadonlySet<string>,
90
+ onTargetSignalIds: ReadonlySet<string>
91
+ ): readonly WardenDiagnostic[] => {
92
+ if (isTestFile(filePath) || !ast) {
93
+ return [];
94
+ }
95
+
96
+ const diagnostics: WardenDiagnostic[] = [];
97
+
98
+ for (const definition of findStoreTableDefinitions(ast)) {
99
+ const diagnostic = buildDefinitionDiagnostic(
100
+ definition,
101
+ sourceCode,
102
+ filePath,
103
+ crudTableIds,
104
+ onTargetSignalIds
105
+ );
106
+ if (diagnostic) {
107
+ diagnostics.push(diagnostic);
108
+ }
109
+ }
110
+
111
+ return diagnostics;
112
+ };
113
+
114
+ export const orphanedSignal: ProjectAwareWardenRule = {
115
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
116
+ const ast = parse(filePath, sourceCode);
117
+ return checkOrphanedSignals(
118
+ ast,
119
+ sourceCode,
120
+ filePath,
121
+ ast ? collectCrudTableIds(ast) : new Set<string>(),
122
+ ast ? collectOnTargetSignalIds(ast, sourceCode) : new Set<string>()
123
+ );
124
+ },
125
+ checkWithContext(
126
+ sourceCode: string,
127
+ filePath: string,
128
+ context: ProjectContext
129
+ ): readonly WardenDiagnostic[] {
130
+ const ast = parse(filePath, sourceCode);
131
+ const localCrudTableIds = ast
132
+ ? collectCrudTableIds(ast)
133
+ : new Set<string>();
134
+ const localOnTargetSignalIds = ast
135
+ ? collectOnTargetSignalIds(ast, sourceCode)
136
+ : new Set<string>();
137
+
138
+ return checkOrphanedSignals(
139
+ ast,
140
+ sourceCode,
141
+ filePath,
142
+ context.crudTableIds ?? localCrudTableIds,
143
+ context.onTargetSignalIds ?? localOnTargetSignalIds
144
+ );
145
+ },
146
+ description:
147
+ 'Warn when CRUD-backed store change signals are never consumed by trail on: declarations.',
148
+ name: 'orphaned-signal',
149
+ severity: 'warn',
150
+ };
@@ -0,0 +1,25 @@
1
+ import type { Trail } from '@ontrails/core';
2
+ import { validatePermits } from '@ontrails/permits';
3
+
4
+ import type { TopoAwareWardenRule, WardenDiagnostic } from './types.js';
5
+
6
+ const toWardenDiagnostic = (
7
+ diagnostic: ReturnType<typeof validatePermits>[number]
8
+ ): WardenDiagnostic => ({
9
+ filePath: '<topo>',
10
+ line: 1,
11
+ message: diagnostic.message,
12
+ rule: `permit.${diagnostic.rule}`,
13
+ severity: diagnostic.severity === 'error' ? 'error' : 'warn',
14
+ });
15
+
16
+ export const permitGovernance: TopoAwareWardenRule = {
17
+ checkTopo: (topo) =>
18
+ validatePermits(
19
+ topo.list() as readonly Trail<unknown, unknown, unknown>[]
20
+ ).map(toWardenDiagnostic),
21
+ description:
22
+ 'Enforces permit declarations and scope hygiene across the compiled topo',
23
+ name: 'permit-governance',
24
+ severity: 'warn',
25
+ };