@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
@@ -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,148 @@
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 { 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';
10
14
  import type { RuleOutput } from './schema.js';
11
15
  import { wardenTopo } from './topo.js';
12
16
 
13
17
  /**
14
- * Run all warden rule trails for a given file and collect diagnostics.
18
+ * Run all file-scoped warden rule trails for a given file and collect diagnostics.
15
19
  *
16
20
  * Each rule trail runs independently. Errors from individual trails are
17
21
  * silently skipped so that one broken rule does not block the rest.
18
22
  */
19
- export const runWardenTrails = async (
23
+ const appendDiagnostics = (
24
+ target: WardenDiagnostic[],
25
+ diagnostics: readonly WardenDiagnostic[]
26
+ ): void => {
27
+ for (const diagnostic of diagnostics) {
28
+ target.push(diagnostic);
29
+ }
30
+ };
31
+
32
+ type TrailIntentMap = Readonly<Record<string, 'destroy' | 'read' | 'write'>>;
33
+
34
+ interface ProjectRuleOptions {
35
+ readonly contourReferencesByName?: Readonly<
36
+ Record<string, readonly string[]>
37
+ >;
38
+ readonly crossTargetTrailIds?: readonly string[];
39
+ readonly crudTableIds?: readonly string[];
40
+ readonly crudCoverageByEntity?: Readonly<Record<string, readonly string[]>>;
41
+ readonly knownContourIds?: readonly string[];
42
+ readonly knownResourceIds?: readonly string[];
43
+ readonly knownSignalIds?: readonly string[];
44
+ readonly knownTrailIds?: readonly string[];
45
+ readonly onTargetSignalIds?: readonly string[];
46
+ readonly reconcileTableIds?: readonly string[];
47
+ readonly trailIntentsById?: TrailIntentMap;
48
+ }
49
+
50
+ const PROJECT_OPTION_KEYS = [
51
+ 'contourReferencesByName',
52
+ 'crossTargetTrailIds',
53
+ 'crudTableIds',
54
+ 'crudCoverageByEntity',
55
+ 'knownContourIds',
56
+ 'knownResourceIds',
57
+ 'knownSignalIds',
58
+ 'knownTrailIds',
59
+ 'onTargetSignalIds',
60
+ 'reconcileTableIds',
61
+ 'trailIntentsById',
62
+ ] as const satisfies readonly (keyof ProjectRuleOptions)[];
63
+
64
+ const hasProjectOptions = (options?: ProjectRuleOptions): boolean =>
65
+ Boolean(
66
+ options && PROJECT_OPTION_KEYS.some((key) => options[key] !== undefined)
67
+ );
68
+
69
+ const collectProjectOptions = (
70
+ options?: ProjectRuleOptions
71
+ ): ProjectRuleOptions => {
72
+ if (!options) {
73
+ return {};
74
+ }
75
+
76
+ return Object.fromEntries(
77
+ PROJECT_OPTION_KEYS.flatMap((key) => {
78
+ const value = options[key];
79
+ return value === undefined ? [] : [[key, value] as const];
80
+ })
81
+ ) as ProjectRuleOptions;
82
+ };
83
+
84
+ const buildRuleInput = (
20
85
  filePath: string,
21
86
  sourceCode: string,
22
- options?: {
23
- readonly knownProvisionIds?: readonly string[];
24
- readonly knownTrailIds?: readonly string[];
87
+ options?: ProjectRuleOptions
88
+ ): {
89
+ readonly filePath: string;
90
+ readonly sourceCode: string;
91
+ } & ProjectRuleOptions => {
92
+ const base = { filePath, sourceCode };
93
+ if (!hasProjectOptions(options)) {
94
+ return base;
25
95
  }
96
+
97
+ return { ...base, ...collectProjectOptions(options) };
98
+ };
99
+
100
+ const topoAwareTrailIds = new Set(
101
+ [...wardenTopoRules.keys()].map((ruleName) => `warden.rule.${ruleName}`)
102
+ );
103
+
104
+ export const runWardenTrails = async (
105
+ filePath: string,
106
+ sourceCode: string,
107
+ options?: ProjectRuleOptions
26
108
  ): Promise<readonly WardenDiagnostic[]> => {
27
109
  const allDiagnostics: WardenDiagnostic[] = [];
110
+ const input = buildRuleInput(filePath, sourceCode, options);
28
111
 
29
112
  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 };
113
+ if (topoAwareTrailIds.has(id)) {
114
+ continue;
115
+ }
43
116
  const result = await run(wardenTopo, id, input);
44
117
  if (result.isOk()) {
45
- const { diagnostics } = result.value as RuleOutput;
46
- for (const d of diagnostics) {
47
- allDiagnostics.push(d);
48
- }
118
+ appendDiagnostics(
119
+ allDiagnostics,
120
+ (result.value as RuleOutput).diagnostics
121
+ );
122
+ }
123
+ }
124
+
125
+ return allDiagnostics;
126
+ };
127
+
128
+ /**
129
+ * Run the built-in topo-aware warden rule trails once against a resolved topo.
130
+ *
131
+ * Unlike `runWardenTrails()`, which is file-scoped, topo-aware rules inspect
132
+ * the compiled graph and should only be dispatched once per topo.
133
+ */
134
+ export const runTopoAwareWardenTrails = async (
135
+ topo: Topo
136
+ ): Promise<readonly WardenDiagnostic[]> => {
137
+ const allDiagnostics: WardenDiagnostic[] = [];
138
+
139
+ for (const id of topoAwareTrailIds) {
140
+ const result = await run(wardenTopo, id, { topo });
141
+ if (result.isOk()) {
142
+ appendDiagnostics(
143
+ allDiagnostics,
144
+ (result.value as RuleOutput).diagnostics
145
+ );
49
146
  }
50
147
  }
51
148
 
@@ -5,6 +5,7 @@
5
5
  * (array of diagnostics) shape.
6
6
  */
7
7
 
8
+ import type { Topo } from '@ontrails/core';
8
9
  import { z } from 'zod';
9
10
 
10
11
  /** A single diagnostic emitted by a warden rule trail. */
@@ -26,18 +27,78 @@ export const ruleInput = z.object({
26
27
  * Extended input for project-aware warden rule trails.
27
28
  *
28
29
  * 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.
30
+ * false positives for `@see` references or cross-file contour relationships.
31
31
  */
32
32
  export const projectAwareRuleInput = ruleInput.extend({
33
- knownProvisionIds: z
33
+ contourReferencesByName: z
34
+ .record(z.string(), z.array(z.string()))
35
+ .optional()
36
+ .describe('Declared contour references keyed by source contour name'),
37
+ crossTargetTrailIds: z
38
+ .array(z.string())
39
+ .optional()
40
+ .describe('Trail IDs referenced by crosses arrays across the project'),
41
+ crudCoverageByEntity: z
42
+ .record(z.string(), z.array(z.string()))
43
+ .optional()
44
+ .describe(
45
+ 'CRUD operation coverage per entity aggregated across the project'
46
+ ),
47
+ crudTableIds: z
48
+ .array(z.string())
49
+ .optional()
50
+ .describe('Store table IDs used with CRUD factories across the project'),
51
+ knownContourIds: z
52
+ .array(z.string())
53
+ .optional()
54
+ .describe('Contour names known across the project'),
55
+ knownResourceIds: z
56
+ .array(z.string())
57
+ .optional()
58
+ .describe('Resource IDs known across the project'),
59
+ knownSignalIds: z
34
60
  .array(z.string())
35
61
  .optional()
36
- .describe('Provision IDs known across the project'),
62
+ .describe('Signal IDs known across the project'),
37
63
  knownTrailIds: z
38
64
  .array(z.string())
39
65
  .optional()
40
66
  .describe('Trail IDs known across the project'),
67
+ onTargetSignalIds: z
68
+ .array(z.string())
69
+ .optional()
70
+ .describe('Signal IDs referenced by trail on arrays across the project'),
71
+ reconcileTableIds: z
72
+ .array(z.string())
73
+ .optional()
74
+ .describe('Store table IDs used with reconcile trails across the project'),
75
+ trailIntentsById: z
76
+ .record(z.string(), z.enum(['read', 'write', 'destroy']))
77
+ .optional()
78
+ .describe('Normalized trail intents keyed by trail ID'),
79
+ });
80
+
81
+ /**
82
+ * Input for topo-aware warden rule trails.
83
+ *
84
+ * The `Topo` graph is not a serializable value, so the schema accepts it
85
+ * as an opaque `z.custom`. Topo-aware rules are invoked from the warden
86
+ * runtime with a live, resolved topo reference — they are not expected
87
+ * to be called across a network boundary.
88
+ */
89
+ export const topoAwareRuleInput = z.object({
90
+ topo: z
91
+ .custom<Topo>(
92
+ (value) =>
93
+ typeof value === 'object' &&
94
+ value !== null &&
95
+ 'trails' in value &&
96
+ 'resources' in value &&
97
+ 'contours' in value &&
98
+ 'signals' in value,
99
+ { message: 'Expected a resolved Topo instance' }
100
+ )
101
+ .describe('Resolved topo graph under inspection'),
41
102
  });
42
103
 
43
104
  /** Output returned by every warden rule trail. */
@@ -47,4 +108,5 @@ export const ruleOutput = z.object({
47
108
 
48
109
  export type RuleInput = z.infer<typeof ruleInput>;
49
110
  export type ProjectAwareRuleInput = z.infer<typeof projectAwareRuleInput>;
111
+ export type TopoAwareRuleInput = z.infer<typeof topoAwareRuleInput>;
50
112
  export type RuleOutput = z.infer<typeof ruleOutput>;
@@ -0,0 +1,45 @@
1
+ import { unreachableDetourShadowing } from '../rules/unreachable-detour-shadowing.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const unreachableDetourShadowingTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'clean.ts',
10
+ sourceCode: `trail("entity.save", {
11
+ detours: [
12
+ { on: ConflictError, recover: async () => Result.ok({ winner: "specific" }) },
13
+ { on: TrailsError, recover: async () => Result.ok({ winner: "broad" }) },
14
+ ],
15
+ });`,
16
+ },
17
+ name: 'Specific detours can precede broader ones',
18
+ },
19
+ {
20
+ expected: {
21
+ diagnostics: [
22
+ {
23
+ filePath: 'shadowed.ts',
24
+ line: 4,
25
+ message:
26
+ 'Trail "entity.save" declares detour on "ConflictError" after earlier detour on "TrailsError". Because "TrailsError" matches "ConflictError" first, the later detour is unreachable.',
27
+ rule: 'unreachable-detour-shadowing',
28
+ severity: 'error',
29
+ },
30
+ ],
31
+ },
32
+ input: {
33
+ filePath: 'shadowed.ts',
34
+ sourceCode: `trail("entity.save", {
35
+ detours: [
36
+ { on: TrailsError, recover: async () => Result.ok({ winner: "broad" }) },
37
+ { on: ConflictError, recover: async () => Result.ok({ winner: "specific" }) },
38
+ ],
39
+ });`,
40
+ },
41
+ name: 'Broader detours declared first shadow later specific ones',
42
+ },
43
+ ],
44
+ rule: unreachableDetourShadowing,
45
+ });
@@ -0,0 +1,71 @@
1
+ import { ConflictError, Result, topo, trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import { validDetourContract } from '../rules/valid-detour-contract.js';
5
+ import { wrapTopoRule } from './wrap-rule.js';
6
+
7
+ const validTrail = trail('entity.save', {
8
+ blaze: () => Result.ok({ ok: true }),
9
+ detours: [
10
+ {
11
+ on: ConflictError,
12
+ recover: async () => {
13
+ const result = await Promise.resolve(Result.ok({ ok: true }));
14
+ return result;
15
+ },
16
+ },
17
+ ],
18
+ input: z.object({}),
19
+ output: z.object({ ok: z.boolean() }),
20
+ });
21
+
22
+ const invalidContractTrail = {
23
+ ...validTrail,
24
+ detours: [
25
+ {
26
+ on: 'ConflictError',
27
+ recover: 'not a function',
28
+ },
29
+ ],
30
+ } as unknown as typeof validTrail;
31
+
32
+ export const validDetourContractTrail = wrapTopoRule({
33
+ examples: [
34
+ {
35
+ expected: { diagnostics: [] },
36
+ input: {
37
+ topo: topo('trl-380-valid-detour-contract', { validTrail }),
38
+ },
39
+ name: 'Detours with an error constructor and recover function stay clean',
40
+ },
41
+ {
42
+ expected: {
43
+ diagnostics: [
44
+ {
45
+ filePath: '<topo>',
46
+ line: 1,
47
+ message:
48
+ 'Trail "entity.save" detour[0] must declare an error constructor in on:. Received ConflictError.',
49
+ rule: 'valid-detour-contract',
50
+ severity: 'error',
51
+ },
52
+ {
53
+ filePath: '<topo>',
54
+ line: 1,
55
+ message:
56
+ 'Trail "entity.save" detour[0] must declare a callable recover function.',
57
+ rule: 'valid-detour-contract',
58
+ severity: 'error',
59
+ },
60
+ ],
61
+ },
62
+ input: {
63
+ topo: topo('trl-380-invalid-detour-contract', {
64
+ invalidContractTrail,
65
+ } as Record<string, unknown>),
66
+ },
67
+ name: 'Malformed detour contracts emit diagnostics',
68
+ },
69
+ ],
70
+ rule: validDetourContract,
71
+ });
@@ -0,0 +1,16 @@
1
+ import { wardenExportSymmetry } from '../rules/warden-export-symmetry.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const wardenExportSymmetryTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'packages/other-pkg/src/index.ts',
10
+ sourceCode: `export { somethingElse } from './other.js';\n`,
11
+ },
12
+ name: 'Ignores files outside the warden barrel',
13
+ },
14
+ ],
15
+ rule: wardenExportSymmetry,
16
+ });
@@ -0,0 +1,45 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { wardenRulesUseAst } from '../rules/warden-rules-use-ast.js';
3
+ import { wrapRule } from './wrap-rule.js';
4
+
5
+ /**
6
+ * Resolve a filePath inside this package's `src/rules/` directory so the
7
+ * positive example fires the path-anchored scope check. Anchoring via
8
+ * `import.meta.url` keeps the example robust to the cwd under which tests run.
9
+ */
10
+ const fakeRulePath = fileURLToPath(
11
+ new URL('../rules/fake-rule.ts', import.meta.url)
12
+ );
13
+
14
+ export const wardenRulesUseAstTrail = wrapRule({
15
+ examples: [
16
+ {
17
+ expected: { diagnostics: [] },
18
+ input: {
19
+ filePath: 'packages/other-pkg/src/index.ts',
20
+ sourceCode: `const lines = sourceCode.split('\\n');\n`,
21
+ },
22
+ name: 'Ignores files outside the warden rules directory',
23
+ },
24
+ {
25
+ expected: {
26
+ diagnostics: [
27
+ {
28
+ filePath: fakeRulePath,
29
+ line: 1,
30
+ message:
31
+ 'warden-rules-use-ast: sourceCode.split(...) treats source text as a string. Warden rules must inspect the AST via packages/warden/src/rules/ast.ts helpers, not regex-scan raw source text. Use findStringLiterals, findTrailDefinitions, findConfigProperty, or a similar AST walker. Raw-text scanning produces false positives on string literals, template payloads, and docstrings — see TRL-335, ADR-0036.',
32
+ rule: 'warden-rules-use-ast',
33
+ severity: 'error',
34
+ },
35
+ ],
36
+ },
37
+ input: {
38
+ filePath: fakeRulePath,
39
+ sourceCode: `export const r = { check(sourceCode: string) { return sourceCode.split('\\n'); } };\n`,
40
+ },
41
+ name: 'Flags sourceCode.split(...) in a rule file',
42
+ },
43
+ ],
44
+ rule: wardenRulesUseAst,
45
+ });
@@ -4,16 +4,27 @@
4
4
  * Keeps each rule trail file minimal — just the import + examples.
5
5
  */
6
6
 
7
- import { trail, Result } from '@ontrails/core';
7
+ import { InternalError, trail, Result } from '@ontrails/core';
8
8
  import type { Trail } from '@ontrails/core';
9
9
 
10
10
  import type {
11
11
  ProjectAwareWardenRule,
12
12
  ProjectContext,
13
+ TopoAwareWardenRule,
13
14
  WardenRule,
14
15
  } from '../rules/types.js';
15
- import { projectAwareRuleInput, ruleInput, ruleOutput } from './schema.js';
16
- import type { ProjectAwareRuleInput, RuleInput, RuleOutput } from './schema.js';
16
+ import {
17
+ projectAwareRuleInput,
18
+ ruleInput,
19
+ ruleOutput,
20
+ topoAwareRuleInput,
21
+ } from './schema.js';
22
+ import type {
23
+ ProjectAwareRuleInput,
24
+ RuleInput,
25
+ RuleOutput,
26
+ TopoAwareRuleInput,
27
+ } from './schema.js';
17
28
 
18
29
  interface WrapRuleOptions {
19
30
  /** The existing warden rule to wrap. */
@@ -29,6 +40,53 @@ interface WrapProjectAwareRuleOptions {
29
40
  readonly examples: Trail<ProjectAwareRuleInput, RuleOutput>['examples'];
30
41
  }
31
42
 
43
+ const buildProjectContext = (input: ProjectAwareRuleInput): ProjectContext => ({
44
+ ...(input.contourReferencesByName
45
+ ? {
46
+ contourReferencesByName: new Map(
47
+ Object.entries(input.contourReferencesByName)
48
+ ),
49
+ }
50
+ : {}),
51
+ ...(input.crudTableIds ? { crudTableIds: new Set(input.crudTableIds) } : {}),
52
+ ...(input.crudCoverageByEntity
53
+ ? {
54
+ crudCoverageByEntity: new Map(
55
+ Object.entries(input.crudCoverageByEntity).map(
56
+ ([entityId, operations]) => [
57
+ entityId,
58
+ new Set(operations) as ReadonlySet<string>,
59
+ ]
60
+ )
61
+ ),
62
+ }
63
+ : {}),
64
+ ...(input.knownContourIds
65
+ ? { knownContourIds: new Set(input.knownContourIds) }
66
+ : {}),
67
+ knownTrailIds: input.knownTrailIds
68
+ ? new Set(input.knownTrailIds)
69
+ : new Set<string>(),
70
+ ...(input.crossTargetTrailIds
71
+ ? { crossTargetTrailIds: new Set(input.crossTargetTrailIds) }
72
+ : {}),
73
+ ...(input.knownResourceIds
74
+ ? { knownResourceIds: new Set(input.knownResourceIds) }
75
+ : {}),
76
+ ...(input.knownSignalIds
77
+ ? { knownSignalIds: new Set(input.knownSignalIds) }
78
+ : {}),
79
+ ...(input.onTargetSignalIds
80
+ ? { onTargetSignalIds: new Set(input.onTargetSignalIds) }
81
+ : {}),
82
+ ...(input.reconcileTableIds
83
+ ? { reconcileTableIds: new Set(input.reconcileTableIds) }
84
+ : {}),
85
+ ...(input.trailIntentsById
86
+ ? { trailIntentsById: new Map(Object.entries(input.trailIntentsById)) }
87
+ : {}),
88
+ });
89
+
32
90
  /**
33
91
  * Wrap an existing `WardenRule` as a trail with typed input/output.
34
92
  *
@@ -50,18 +108,10 @@ export function wrapRule(
50
108
  const projectAwareRule = rule as ProjectAwareWardenRule;
51
109
  return trail(`warden.rule.${rule.name}`, {
52
110
  blaze: (input: ProjectAwareRuleInput) => {
53
- const context = {
54
- knownProvisionIds: input.knownProvisionIds
55
- ? new Set(input.knownProvisionIds)
56
- : undefined,
57
- knownTrailIds: input.knownTrailIds
58
- ? new Set(input.knownTrailIds)
59
- : new Set<string>(),
60
- } as ProjectContext;
61
111
  const diagnostics = projectAwareRule.checkWithContext(
62
112
  input.sourceCode,
63
113
  input.filePath,
64
- context
114
+ buildProjectContext(input)
65
115
  );
66
116
  return Result.ok({ diagnostics: [...diagnostics] });
67
117
  },
@@ -90,3 +140,45 @@ export function wrapRule(
90
140
  output: ruleOutput,
91
141
  });
92
142
  }
143
+
144
+ interface WrapTopoRuleOptions {
145
+ /** The existing topo-aware warden rule to wrap. */
146
+ readonly rule: TopoAwareWardenRule;
147
+ /** Trail examples for testing and documentation. */
148
+ readonly examples: Trail<TopoAwareRuleInput, RuleOutput>['examples'];
149
+ }
150
+
151
+ /**
152
+ * Wrap an existing `TopoAwareWardenRule` as a trail.
153
+ *
154
+ * Mirrors `wrapRule` for the per-topo dispatch path. Topo-aware rules run
155
+ * once per topo against the compiled runtime graph rather than per file,
156
+ * so the trail accepts the live `Topo` as input.
157
+ */
158
+ export const wrapTopoRule = (
159
+ options: WrapTopoRuleOptions
160
+ ): Trail<TopoAwareRuleInput, RuleOutput> => {
161
+ const { rule, examples } = options;
162
+ return trail(`warden.rule.${rule.name}`, {
163
+ blaze: async (input: TopoAwareRuleInput) => {
164
+ try {
165
+ const diagnostics = await rule.checkTopo(input.topo);
166
+ return Result.ok({ diagnostics: [...diagnostics] });
167
+ } catch (error) {
168
+ const cause = error instanceof Error ? error : new Error(String(error));
169
+ return Result.err(
170
+ new InternalError(
171
+ `Topo-aware rule "${rule.name}" threw while inspecting topo: ${cause.message}`,
172
+ { cause }
173
+ )
174
+ );
175
+ }
176
+ },
177
+ description: rule.description,
178
+ examples,
179
+ input: topoAwareRuleInput,
180
+ intent: 'read',
181
+ meta: { category: 'governance', severity: rule.severity },
182
+ output: ruleOutput,
183
+ });
184
+ };
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "rootDir": "./src",
6
+ "types": ["bun"]
7
+ },
8
+ "include": ["src/**/*.test.ts", "src/__tests__/**/*.ts"],
9
+ "exclude": []
10
+ }