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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (351) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/README.md +77 -34
  3. package/bin/warden.ts +22 -0
  4. package/package.json +26 -6
  5. package/src/ast.ts +28 -0
  6. package/src/cli.ts +1169 -116
  7. package/src/command.ts +927 -0
  8. package/src/config.ts +184 -0
  9. package/src/drift.ts +76 -34
  10. package/src/formatters.ts +54 -7
  11. package/src/guide.ts +234 -0
  12. package/src/index.ts +151 -38
  13. package/src/project-context.ts +163 -0
  14. package/src/resolve.ts +530 -0
  15. package/src/rules/activation-orphan.ts +97 -0
  16. package/src/rules/ast.ts +2884 -124
  17. package/src/rules/circular-refs.ts +154 -0
  18. package/src/rules/{context-no-trailhead-types.ts → context-no-surface-types.ts} +72 -12
  19. package/src/rules/contour-exists.ts +251 -0
  20. package/src/rules/contour-ids.ts +15 -0
  21. package/src/rules/cross-declarations.ts +277 -69
  22. package/src/rules/dead-internal-trail.ts +141 -0
  23. package/src/rules/draft-file-marking.ts +51 -3
  24. package/src/rules/draft-visible-debt.ts +40 -15
  25. package/src/rules/error-mapping-completeness.ts +288 -0
  26. package/src/rules/example-valid.ts +401 -0
  27. package/src/rules/fires-declarations.ts +758 -0
  28. package/src/rules/implementation-returns-result.ts +1042 -122
  29. package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
  30. package/src/rules/incomplete-crud.ts +580 -0
  31. package/src/rules/index.ts +156 -19
  32. package/src/rules/intent-propagation.ts +127 -0
  33. package/src/rules/layer-field-name-drift.ts +96 -0
  34. package/src/rules/metadata.ts +508 -0
  35. package/src/rules/missing-reconcile.ts +98 -0
  36. package/src/rules/missing-visibility.ts +110 -0
  37. package/src/rules/no-dev-permit-in-source.ts +99 -0
  38. package/src/rules/no-direct-implementation-call.ts +1 -1
  39. package/src/rules/no-legacy-layer-imports.ts +193 -0
  40. package/src/rules/no-native-error-result.ts +111 -0
  41. package/src/rules/no-sync-result-assumption.ts +1131 -96
  42. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  43. package/src/rules/no-throw-in-implementation.ts +6 -4
  44. package/src/rules/on-references-exist.ts +194 -0
  45. package/src/rules/orphaned-signal.ts +150 -0
  46. package/src/rules/owner-projection-parity.ts +146 -0
  47. package/src/rules/permit-governance.ts +25 -0
  48. package/src/rules/public-internal-deep-imports.ts +517 -0
  49. package/src/rules/public-output-schema.ts +29 -0
  50. package/src/rules/public-union-output-discriminants.ts +150 -0
  51. package/src/rules/read-intent-fires.ts +187 -0
  52. package/src/rules/reference-exists.ts +98 -0
  53. package/src/rules/registry-names.ts +113 -0
  54. package/src/rules/resolved-import-boundary.ts +146 -0
  55. package/src/rules/{provision-declarations.ts → resource-declarations.ts} +208 -138
  56. package/src/rules/{provision-exists.ts → resource-exists.ts} +46 -51
  57. package/src/rules/resource-id-grammar.ts +65 -0
  58. package/src/rules/scan.ts +0 -26
  59. package/src/rules/scheduled-destroy-intent.ts +44 -0
  60. package/src/rules/signal-graph-coaching.ts +191 -0
  61. package/src/rules/specs.ts +5 -1
  62. package/src/rules/static-resource-accessor-preference.ts +657 -0
  63. package/src/rules/types.ts +187 -7
  64. package/src/rules/unmaterialized-activation-source.ts +84 -0
  65. package/src/rules/unreachable-detour-shadowing.ts +344 -0
  66. package/src/rules/valid-describe-refs.ts +160 -32
  67. package/src/rules/valid-detour-contract.ts +78 -0
  68. package/src/rules/warden-export-symmetry.ts +533 -0
  69. package/src/rules/warden-rules-use-ast.ts +996 -0
  70. package/src/rules/webhook-route-collision.ts +243 -0
  71. package/src/trails/activation-orphan.trail.ts +81 -0
  72. package/src/trails/circular-refs.trail.ts +29 -0
  73. package/src/trails/{context-no-trailhead-types.trail.ts → context-no-surface-types.trail.ts} +4 -4
  74. package/src/trails/contour-exists.trail.ts +21 -0
  75. package/src/trails/dead-internal-trail.trail.ts +26 -0
  76. package/src/trails/draft-file-marking.trail.ts +16 -0
  77. package/src/trails/draft-visible-debt.trail.ts +16 -0
  78. package/src/trails/error-mapping-completeness.trail.ts +29 -0
  79. package/src/trails/example-valid.trail.ts +25 -0
  80. package/src/trails/fires-declarations.trail.ts +23 -0
  81. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  82. package/src/trails/incomplete-crud.trail.ts +39 -0
  83. package/src/trails/index.ts +56 -8
  84. package/src/trails/intent-propagation.trail.ts +30 -0
  85. package/src/trails/layer-field-name-drift.trail.ts +39 -0
  86. package/src/trails/missing-reconcile.trail.ts +33 -0
  87. package/src/trails/missing-visibility.trail.ts +22 -0
  88. package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
  89. package/src/trails/no-legacy-layer-imports.trail.ts +35 -0
  90. package/src/trails/no-native-error-result.trail.ts +18 -0
  91. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  92. package/src/trails/on-references-exist.trail.ts +21 -0
  93. package/src/trails/orphaned-signal.trail.ts +36 -0
  94. package/src/trails/owner-projection-parity.trail.ts +26 -0
  95. package/src/trails/permit-governance.trail.ts +51 -0
  96. package/src/trails/public-internal-deep-imports.trail.ts +94 -0
  97. package/src/trails/public-output-schema.trail.ts +55 -0
  98. package/src/trails/public-union-output-discriminants.trail.ts +33 -0
  99. package/src/trails/read-intent-fires.trail.ts +20 -0
  100. package/src/trails/reference-exists.trail.ts +25 -0
  101. package/src/trails/resolved-import-boundary.trail.ts +109 -0
  102. package/src/trails/{provision-declarations.trail.ts → resource-declarations.trail.ts} +6 -6
  103. package/src/trails/{provision-exists.trail.ts → resource-exists.trail.ts} +7 -7
  104. package/src/trails/resource-id-grammar.trail.ts +39 -0
  105. package/src/trails/run.ts +133 -24
  106. package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
  107. package/src/trails/schema.ts +122 -4
  108. package/src/trails/signal-graph-coaching.trail.ts +74 -0
  109. package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
  110. package/src/trails/unmaterialized-activation-source.trail.ts +69 -0
  111. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  112. package/src/trails/valid-detour-contract.trail.ts +71 -0
  113. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  114. package/src/trails/warden-rules-use-ast.trail.ts +45 -0
  115. package/src/trails/webhook-route-collision.trail.ts +50 -0
  116. package/src/trails/wrap-rule.ts +133 -14
  117. package/src/workspaces.ts +238 -0
  118. package/.turbo/turbo-build.log +0 -1
  119. package/.turbo/turbo-lint.log +0 -3
  120. package/.turbo/turbo-typecheck.log +0 -1
  121. package/dist/cli.d.ts +0 -46
  122. package/dist/cli.d.ts.map +0 -1
  123. package/dist/cli.js +0 -247
  124. package/dist/cli.js.map +0 -1
  125. package/dist/draft.d.ts +0 -5
  126. package/dist/draft.d.ts.map +0 -1
  127. package/dist/draft.js +0 -16
  128. package/dist/draft.js.map +0 -1
  129. package/dist/drift.d.ts +0 -29
  130. package/dist/drift.d.ts.map +0 -1
  131. package/dist/drift.js +0 -61
  132. package/dist/drift.js.map +0 -1
  133. package/dist/formatters.d.ts +0 -30
  134. package/dist/formatters.d.ts.map +0 -1
  135. package/dist/formatters.js +0 -98
  136. package/dist/formatters.js.map +0 -1
  137. package/dist/index.d.ts +0 -37
  138. package/dist/index.d.ts.map +0 -1
  139. package/dist/index.js +0 -38
  140. package/dist/index.js.map +0 -1
  141. package/dist/rules/ast.d.ts +0 -82
  142. package/dist/rules/ast.d.ts.map +0 -1
  143. package/dist/rules/ast.js +0 -363
  144. package/dist/rules/ast.js.map +0 -1
  145. package/dist/rules/context-no-surface-types.d.ts +0 -12
  146. package/dist/rules/context-no-surface-types.d.ts.map +0 -1
  147. package/dist/rules/context-no-surface-types.js +0 -96
  148. package/dist/rules/context-no-surface-types.js.map +0 -1
  149. package/dist/rules/context-no-trailhead-types.d.ts +0 -12
  150. package/dist/rules/context-no-trailhead-types.d.ts.map +0 -1
  151. package/dist/rules/context-no-trailhead-types.js +0 -96
  152. package/dist/rules/context-no-trailhead-types.js.map +0 -1
  153. package/dist/rules/cross-declarations.d.ts +0 -13
  154. package/dist/rules/cross-declarations.d.ts.map +0 -1
  155. package/dist/rules/cross-declarations.js +0 -264
  156. package/dist/rules/cross-declarations.js.map +0 -1
  157. package/dist/rules/draft-file-marking.d.ts +0 -6
  158. package/dist/rules/draft-file-marking.d.ts.map +0 -1
  159. package/dist/rules/draft-file-marking.js +0 -65
  160. package/dist/rules/draft-file-marking.js.map +0 -1
  161. package/dist/rules/draft-visible-debt.d.ts +0 -12
  162. package/dist/rules/draft-visible-debt.d.ts.map +0 -1
  163. package/dist/rules/draft-visible-debt.js +0 -45
  164. package/dist/rules/draft-visible-debt.js.map +0 -1
  165. package/dist/rules/follow-declarations.d.ts +0 -13
  166. package/dist/rules/follow-declarations.d.ts.map +0 -1
  167. package/dist/rules/follow-declarations.js +0 -264
  168. package/dist/rules/follow-declarations.js.map +0 -1
  169. package/dist/rules/implementation-returns-result.d.ts +0 -13
  170. package/dist/rules/implementation-returns-result.d.ts.map +0 -1
  171. package/dist/rules/implementation-returns-result.js +0 -277
  172. package/dist/rules/implementation-returns-result.js.map +0 -1
  173. package/dist/rules/index.d.ts +0 -20
  174. package/dist/rules/index.d.ts.map +0 -1
  175. package/dist/rules/index.js +0 -49
  176. package/dist/rules/index.js.map +0 -1
  177. package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
  178. package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
  179. package/dist/rules/no-direct-impl-in-route.js +0 -47
  180. package/dist/rules/no-direct-impl-in-route.js.map +0 -1
  181. package/dist/rules/no-direct-implementation-call.d.ts +0 -12
  182. package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
  183. package/dist/rules/no-direct-implementation-call.js +0 -39
  184. package/dist/rules/no-direct-implementation-call.js.map +0 -1
  185. package/dist/rules/no-sync-result-assumption.d.ts +0 -6
  186. package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
  187. package/dist/rules/no-sync-result-assumption.js +0 -98
  188. package/dist/rules/no-sync-result-assumption.js.map +0 -1
  189. package/dist/rules/no-throw-in-detour-target.d.ts +0 -12
  190. package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
  191. package/dist/rules/no-throw-in-detour-target.js +0 -87
  192. package/dist/rules/no-throw-in-detour-target.js.map +0 -1
  193. package/dist/rules/no-throw-in-implementation.d.ts +0 -9
  194. package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
  195. package/dist/rules/no-throw-in-implementation.js +0 -34
  196. package/dist/rules/no-throw-in-implementation.js.map +0 -1
  197. package/dist/rules/prefer-schema-inference.d.ts +0 -7
  198. package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
  199. package/dist/rules/prefer-schema-inference.js +0 -86
  200. package/dist/rules/prefer-schema-inference.js.map +0 -1
  201. package/dist/rules/provision-declarations.d.ts +0 -14
  202. package/dist/rules/provision-declarations.d.ts.map +0 -1
  203. package/dist/rules/provision-declarations.js +0 -344
  204. package/dist/rules/provision-declarations.js.map +0 -1
  205. package/dist/rules/provision-exists.d.ts +0 -6
  206. package/dist/rules/provision-exists.d.ts.map +0 -1
  207. package/dist/rules/provision-exists.js +0 -90
  208. package/dist/rules/provision-exists.js.map +0 -1
  209. package/dist/rules/scan.d.ts +0 -8
  210. package/dist/rules/scan.d.ts.map +0 -1
  211. package/dist/rules/scan.js +0 -32
  212. package/dist/rules/scan.js.map +0 -1
  213. package/dist/rules/service-declarations.d.ts +0 -16
  214. package/dist/rules/service-declarations.d.ts.map +0 -1
  215. package/dist/rules/service-declarations.js +0 -346
  216. package/dist/rules/service-declarations.js.map +0 -1
  217. package/dist/rules/service-exists.d.ts +0 -8
  218. package/dist/rules/service-exists.d.ts.map +0 -1
  219. package/dist/rules/service-exists.js +0 -91
  220. package/dist/rules/service-exists.js.map +0 -1
  221. package/dist/rules/specs.d.ts +0 -29
  222. package/dist/rules/specs.d.ts.map +0 -1
  223. package/dist/rules/specs.js +0 -192
  224. package/dist/rules/specs.js.map +0 -1
  225. package/dist/rules/structure.d.ts +0 -13
  226. package/dist/rules/structure.d.ts.map +0 -1
  227. package/dist/rules/structure.js +0 -142
  228. package/dist/rules/structure.js.map +0 -1
  229. package/dist/rules/types.d.ts +0 -54
  230. package/dist/rules/types.d.ts.map +0 -1
  231. package/dist/rules/types.js +0 -2
  232. package/dist/rules/types.js.map +0 -1
  233. package/dist/rules/valid-describe-refs.d.ts +0 -7
  234. package/dist/rules/valid-describe-refs.d.ts.map +0 -1
  235. package/dist/rules/valid-describe-refs.js +0 -51
  236. package/dist/rules/valid-describe-refs.js.map +0 -1
  237. package/dist/rules/valid-detour-refs.d.ts +0 -6
  238. package/dist/rules/valid-detour-refs.d.ts.map +0 -1
  239. package/dist/rules/valid-detour-refs.js +0 -117
  240. package/dist/rules/valid-detour-refs.js.map +0 -1
  241. package/dist/trails/context-no-surface-types.trail.d.ts +0 -13
  242. package/dist/trails/context-no-surface-types.trail.d.ts.map +0 -1
  243. package/dist/trails/context-no-surface-types.trail.js +0 -21
  244. package/dist/trails/context-no-surface-types.trail.js.map +0 -1
  245. package/dist/trails/context-no-trailhead-types.trail.d.ts +0 -13
  246. package/dist/trails/context-no-trailhead-types.trail.d.ts.map +0 -1
  247. package/dist/trails/context-no-trailhead-types.trail.js +0 -21
  248. package/dist/trails/context-no-trailhead-types.trail.js.map +0 -1
  249. package/dist/trails/cross-declarations.trail.d.ts +0 -13
  250. package/dist/trails/cross-declarations.trail.d.ts.map +0 -1
  251. package/dist/trails/cross-declarations.trail.js +0 -22
  252. package/dist/trails/cross-declarations.trail.js.map +0 -1
  253. package/dist/trails/follow-declarations.trail.d.ts +0 -13
  254. package/dist/trails/follow-declarations.trail.d.ts.map +0 -1
  255. package/dist/trails/follow-declarations.trail.js +0 -22
  256. package/dist/trails/follow-declarations.trail.js.map +0 -1
  257. package/dist/trails/implementation-returns-result.trail.d.ts +0 -13
  258. package/dist/trails/implementation-returns-result.trail.d.ts.map +0 -1
  259. package/dist/trails/implementation-returns-result.trail.js +0 -20
  260. package/dist/trails/implementation-returns-result.trail.js.map +0 -1
  261. package/dist/trails/index.d.ts +0 -16
  262. package/dist/trails/index.d.ts.map +0 -1
  263. package/dist/trails/index.js +0 -15
  264. package/dist/trails/index.js.map +0 -1
  265. package/dist/trails/no-direct-impl-in-route.trail.d.ts +0 -13
  266. package/dist/trails/no-direct-impl-in-route.trail.d.ts.map +0 -1
  267. package/dist/trails/no-direct-impl-in-route.trail.js +0 -22
  268. package/dist/trails/no-direct-impl-in-route.trail.js.map +0 -1
  269. package/dist/trails/no-direct-implementation-call.trail.d.ts +0 -13
  270. package/dist/trails/no-direct-implementation-call.trail.d.ts.map +0 -1
  271. package/dist/trails/no-direct-implementation-call.trail.js +0 -16
  272. package/dist/trails/no-direct-implementation-call.trail.js.map +0 -1
  273. package/dist/trails/no-sync-result-assumption.trail.d.ts +0 -13
  274. package/dist/trails/no-sync-result-assumption.trail.d.ts.map +0 -1
  275. package/dist/trails/no-sync-result-assumption.trail.js +0 -19
  276. package/dist/trails/no-sync-result-assumption.trail.js.map +0 -1
  277. package/dist/trails/no-throw-in-detour-target.trail.d.ts +0 -15
  278. package/dist/trails/no-throw-in-detour-target.trail.d.ts.map +0 -1
  279. package/dist/trails/no-throw-in-detour-target.trail.js +0 -20
  280. package/dist/trails/no-throw-in-detour-target.trail.js.map +0 -1
  281. package/dist/trails/no-throw-in-implementation.trail.d.ts +0 -13
  282. package/dist/trails/no-throw-in-implementation.trail.d.ts.map +0 -1
  283. package/dist/trails/no-throw-in-implementation.trail.js +0 -20
  284. package/dist/trails/no-throw-in-implementation.trail.js.map +0 -1
  285. package/dist/trails/prefer-schema-inference.trail.d.ts +0 -13
  286. package/dist/trails/prefer-schema-inference.trail.d.ts.map +0 -1
  287. package/dist/trails/prefer-schema-inference.trail.js +0 -21
  288. package/dist/trails/prefer-schema-inference.trail.js.map +0 -1
  289. package/dist/trails/provision-declarations.trail.d.ts +0 -13
  290. package/dist/trails/provision-declarations.trail.d.ts.map +0 -1
  291. package/dist/trails/provision-declarations.trail.js +0 -25
  292. package/dist/trails/provision-declarations.trail.js.map +0 -1
  293. package/dist/trails/provision-exists.trail.d.ts +0 -15
  294. package/dist/trails/provision-exists.trail.d.ts.map +0 -1
  295. package/dist/trails/provision-exists.trail.js +0 -27
  296. package/dist/trails/provision-exists.trail.js.map +0 -1
  297. package/dist/trails/run.d.ts +0 -17
  298. package/dist/trails/run.d.ts.map +0 -1
  299. package/dist/trails/run.js +0 -39
  300. package/dist/trails/run.js.map +0 -1
  301. package/dist/trails/schema.d.ts +0 -53
  302. package/dist/trails/schema.d.ts.map +0 -1
  303. package/dist/trails/schema.js +0 -42
  304. package/dist/trails/schema.js.map +0 -1
  305. package/dist/trails/service-declarations.trail.d.ts +0 -26
  306. package/dist/trails/service-declarations.trail.d.ts.map +0 -1
  307. package/dist/trails/service-declarations.trail.js +0 -27
  308. package/dist/trails/service-declarations.trail.js.map +0 -1
  309. package/dist/trails/service-exists.trail.d.ts +0 -32
  310. package/dist/trails/service-exists.trail.d.ts.map +0 -1
  311. package/dist/trails/service-exists.trail.js +0 -29
  312. package/dist/trails/service-exists.trail.js.map +0 -1
  313. package/dist/trails/topo.d.ts +0 -3
  314. package/dist/trails/topo.d.ts.map +0 -1
  315. package/dist/trails/topo.js +0 -5
  316. package/dist/trails/topo.js.map +0 -1
  317. package/dist/trails/valid-describe-refs.trail.d.ts +0 -15
  318. package/dist/trails/valid-describe-refs.trail.d.ts.map +0 -1
  319. package/dist/trails/valid-describe-refs.trail.js +0 -18
  320. package/dist/trails/valid-describe-refs.trail.js.map +0 -1
  321. package/dist/trails/valid-detour-refs.trail.d.ts +0 -15
  322. package/dist/trails/valid-detour-refs.trail.d.ts.map +0 -1
  323. package/dist/trails/valid-detour-refs.trail.js +0 -24
  324. package/dist/trails/valid-detour-refs.trail.js.map +0 -1
  325. package/dist/trails/wrap-rule.d.ts +0 -29
  326. package/dist/trails/wrap-rule.d.ts.map +0 -1
  327. package/dist/trails/wrap-rule.js +0 -47
  328. package/dist/trails/wrap-rule.js.map +0 -1
  329. package/src/__tests__/cli.test.ts +0 -311
  330. package/src/__tests__/cross-declarations.test.ts +0 -303
  331. package/src/__tests__/drift.test.ts +0 -144
  332. package/src/__tests__/formatters.test.ts +0 -157
  333. package/src/__tests__/implementation-returns-result.test.ts +0 -129
  334. package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
  335. package/src/__tests__/no-sync-result-assumption.test.ts +0 -85
  336. package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
  337. package/src/__tests__/prefer-schema-inference.test.ts +0 -84
  338. package/src/__tests__/provision-declarations.test.ts +0 -318
  339. package/src/__tests__/provision-exists.test.ts +0 -122
  340. package/src/__tests__/rules.test.ts +0 -227
  341. package/src/__tests__/trails.test.ts +0 -19
  342. package/src/__tests__/valid-describe-refs.test.ts +0 -60
  343. package/src/__tests__/wrap-rule.test.ts +0 -41
  344. package/src/rules/no-direct-impl-in-route.ts +0 -81
  345. package/src/rules/no-throw-in-detour-target.ts +0 -150
  346. package/src/rules/valid-detour-refs.ts +0 -189
  347. package/src/trails/no-direct-impl-in-route.trail.ts +0 -22
  348. package/src/trails/no-throw-in-detour-target.trail.ts +0 -20
  349. package/src/trails/valid-detour-refs.trail.ts +0 -24
  350. package/tsconfig.json +0 -9
  351. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,25 @@
1
+ import { staticResourceAccessorPreference } from '../rules/static-resource-accessor-preference.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const staticResourceAccessorPreferenceTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'clean.ts',
10
+ sourceCode: `const db = resource("db.main", {
11
+ create: () => Result.ok({ source: "factory" }),
12
+ });
13
+
14
+ trail("entity.show", {
15
+ resources: [db],
16
+ blaze: async (_input, ctx) => {
17
+ return Result.ok(db.from(ctx));
18
+ }
19
+ })`,
20
+ },
21
+ name: 'Static resource helper access',
22
+ },
23
+ ],
24
+ rule: staticResourceAccessorPreference,
25
+ });
@@ -0,0 +1,69 @@
1
+ import { Result, schedule, signal, topo, trail, webhook } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import { unmaterializedActivationSource } from '../rules/unmaterialized-activation-source.js';
5
+ import { wrapTopoRule } from './wrap-rule.js';
6
+
7
+ const invoicePaidWebhook = webhook('webhook.invoice.paid', {
8
+ parse: z.object({ invoiceId: z.string() }),
9
+ path: '/webhooks/invoice/paid',
10
+ });
11
+
12
+ const webhookConsumer = trail('invoice.audit-webhook', {
13
+ blaze: () => Result.ok({ ok: true }),
14
+ input: z.object({ invoiceId: z.string() }),
15
+ on: [invoicePaidWebhook],
16
+ output: z.object({ ok: z.boolean() }),
17
+ });
18
+
19
+ const invoiceCreated = signal('invoice.created', {
20
+ from: ['invoice.create'],
21
+ payload: z.object({ invoiceId: z.string() }),
22
+ });
23
+
24
+ const signalProducer = trail('invoice.create', {
25
+ blaze: () => Result.ok({ invoiceId: 'inv_1' }),
26
+ fires: [invoiceCreated],
27
+ input: z.object({}),
28
+ output: z.object({ invoiceId: z.string() }),
29
+ });
30
+
31
+ const signalConsumer = trail('invoice.index', {
32
+ blaze: () => Result.ok({ ok: true }),
33
+ input: z.object({ invoiceId: z.string() }),
34
+ on: [invoiceCreated],
35
+ output: z.object({ ok: z.boolean() }),
36
+ });
37
+
38
+ const scheduleConsumer = trail('invoice.reconcile', {
39
+ blaze: () => Result.ok({ ok: true }),
40
+ input: z.object({}),
41
+ on: [schedule('schedule.invoice.reconcile', { cron: '0 * * * *' })],
42
+ output: z.object({ ok: z.boolean() }),
43
+ });
44
+
45
+ export const unmaterializedActivationSourceTrail = wrapTopoRule({
46
+ examples: [
47
+ {
48
+ expected: { diagnostics: [] },
49
+ input: {
50
+ topo: topo('trl-461-webhook-materialized', { webhookConsumer }),
51
+ },
52
+ name: 'Webhook activation sources are materialized by HTTP',
53
+ },
54
+ {
55
+ expected: { diagnostics: [] },
56
+ input: {
57
+ topo: topo('trl-496-materialized-sources', {
58
+ invoiceCreated,
59
+ scheduleConsumer,
60
+ signalConsumer,
61
+ signalProducer,
62
+ webhookConsumer,
63
+ }),
64
+ },
65
+ name: 'Signal, schedule, and webhook activation sources are materialized',
66
+ },
67
+ ],
68
+ rule: unmaterializedActivationSource,
69
+ });
@@ -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
+ });
@@ -0,0 +1,50 @@
1
+ import { Result, topo, trail, webhook } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import { webhookRouteCollision } from '../rules/webhook-route-collision.js';
5
+ import { wrapTopoRule } from './wrap-rule.js';
6
+
7
+ const paymentWebhook = webhook('webhook.payment.received', {
8
+ parse: z.object({ paymentId: z.string() }),
9
+ path: '/webhooks/payment',
10
+ });
11
+
12
+ const paymentReceiver = trail('payment.receive', {
13
+ blaze: () => Result.ok({ ok: true }),
14
+ input: z.object({ paymentId: z.string() }),
15
+ on: [paymentWebhook],
16
+ output: z.object({ ok: z.boolean() }),
17
+ });
18
+
19
+ const directRoute = trail('webhooks.payment', {
20
+ blaze: () => Result.ok({ ok: true }),
21
+ input: z.object({}),
22
+ output: z.object({ ok: z.boolean() }),
23
+ });
24
+
25
+ export const webhookRouteCollisionTrail = wrapTopoRule({
26
+ examples: [
27
+ {
28
+ expected: {
29
+ diagnostics: [
30
+ {
31
+ filePath: '<topo>',
32
+ line: 1,
33
+ message:
34
+ 'HTTP webhook route collision on POST /webhooks/payment: derived trail route "webhooks.payment", webhook source "webhook.payment.received" on trail "payment.receive". Give each webhook source a distinct method/path pair or move the direct trail route before materializing the HTTP surface.',
35
+ rule: 'webhook-route-collision',
36
+ severity: 'error',
37
+ },
38
+ ],
39
+ },
40
+ input: {
41
+ topo: topo('trl-461-webhook-route-collision', {
42
+ directRoute,
43
+ paymentReceiver,
44
+ }),
45
+ },
46
+ name: 'Webhook route colliding with a derived direct HTTP route',
47
+ },
48
+ ],
49
+ rule: webhookRouteCollision,
50
+ });
@@ -4,16 +4,28 @@
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 { getWardenRuleMetadata } from '../rules/metadata.js';
17
+ import {
18
+ projectAwareRuleInput,
19
+ ruleInput,
20
+ ruleOutput,
21
+ topoAwareRuleInput,
22
+ } from './schema.js';
23
+ import type {
24
+ ProjectAwareRuleInput,
25
+ RuleInput,
26
+ RuleOutput,
27
+ TopoAwareRuleInput,
28
+ } from './schema.js';
17
29
 
18
30
  interface WrapRuleOptions {
19
31
  /** The existing warden rule to wrap. */
@@ -29,6 +41,79 @@ interface WrapProjectAwareRuleOptions {
29
41
  readonly examples: Trail<ProjectAwareRuleInput, RuleOutput>['examples'];
30
42
  }
31
43
 
44
+ const buildRuleMeta = (rule: WardenRule | TopoAwareWardenRule) => {
45
+ const metadata = getWardenRuleMetadata(rule);
46
+ return {
47
+ category: 'governance',
48
+ ...(metadata ? { warden: metadata } : {}),
49
+ severity: rule.severity,
50
+ };
51
+ };
52
+
53
+ const buildProjectContext = (input: ProjectAwareRuleInput): ProjectContext => ({
54
+ ...(input.contourReferencesByName
55
+ ? {
56
+ contourReferencesByName: new Map(
57
+ Object.entries(input.contourReferencesByName)
58
+ ),
59
+ }
60
+ : {}),
61
+ ...(input.crudTableIds ? { crudTableIds: new Set(input.crudTableIds) } : {}),
62
+ ...(input.crudCoverageByEntity
63
+ ? {
64
+ crudCoverageByEntity: new Map(
65
+ Object.entries(input.crudCoverageByEntity).map(
66
+ ([entityId, operations]) => [
67
+ entityId,
68
+ new Set(operations) as ReadonlySet<string>,
69
+ ]
70
+ )
71
+ ),
72
+ }
73
+ : {}),
74
+ ...(input.knownContourIds
75
+ ? { knownContourIds: new Set(input.knownContourIds) }
76
+ : {}),
77
+ ...(input.importResolutionsByFile
78
+ ? {
79
+ importResolutionsByFile: new Map(
80
+ Object.entries(input.importResolutionsByFile)
81
+ ),
82
+ }
83
+ : {}),
84
+ ...(input.documentedImportResolutionsByFile
85
+ ? {
86
+ documentedImportResolutionsByFile: new Map(
87
+ Object.entries(input.documentedImportResolutionsByFile)
88
+ ),
89
+ }
90
+ : {}),
91
+ ...(input.publicWorkspaces
92
+ ? { publicWorkspaces: new Map(Object.entries(input.publicWorkspaces)) }
93
+ : {}),
94
+ knownTrailIds: input.knownTrailIds
95
+ ? new Set(input.knownTrailIds)
96
+ : new Set<string>(),
97
+ ...(input.crossTargetTrailIds
98
+ ? { crossTargetTrailIds: new Set(input.crossTargetTrailIds) }
99
+ : {}),
100
+ ...(input.knownResourceIds
101
+ ? { knownResourceIds: new Set(input.knownResourceIds) }
102
+ : {}),
103
+ ...(input.knownSignalIds
104
+ ? { knownSignalIds: new Set(input.knownSignalIds) }
105
+ : {}),
106
+ ...(input.onTargetSignalIds
107
+ ? { onTargetSignalIds: new Set(input.onTargetSignalIds) }
108
+ : {}),
109
+ ...(input.reconcileTableIds
110
+ ? { reconcileTableIds: new Set(input.reconcileTableIds) }
111
+ : {}),
112
+ ...(input.trailIntentsById
113
+ ? { trailIntentsById: new Map(Object.entries(input.trailIntentsById)) }
114
+ : {}),
115
+ });
116
+
32
117
  /**
33
118
  * Wrap an existing `WardenRule` as a trail with typed input/output.
34
119
  *
@@ -50,18 +135,10 @@ export function wrapRule(
50
135
  const projectAwareRule = rule as ProjectAwareWardenRule;
51
136
  return trail(`warden.rule.${rule.name}`, {
52
137
  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
138
  const diagnostics = projectAwareRule.checkWithContext(
62
139
  input.sourceCode,
63
140
  input.filePath,
64
- context
141
+ buildProjectContext(input)
65
142
  );
66
143
  return Result.ok({ diagnostics: [...diagnostics] });
67
144
  },
@@ -72,7 +149,7 @@ export function wrapRule(
72
149
  >['examples'],
73
150
  input: projectAwareRuleInput,
74
151
  intent: 'read',
75
- meta: { category: 'governance', severity: rule.severity },
152
+ meta: buildRuleMeta(rule),
76
153
  output: ruleOutput,
77
154
  });
78
155
  }
@@ -86,7 +163,49 @@ export function wrapRule(
86
163
  examples: examples as Trail<RuleInput, RuleOutput>['examples'],
87
164
  input: ruleInput,
88
165
  intent: 'read',
89
- meta: { category: 'governance', severity: rule.severity },
166
+ meta: buildRuleMeta(rule),
90
167
  output: ruleOutput,
91
168
  });
92
169
  }
170
+
171
+ interface WrapTopoRuleOptions {
172
+ /** The existing topo-aware warden rule to wrap. */
173
+ readonly rule: TopoAwareWardenRule;
174
+ /** Trail examples for testing and documentation. */
175
+ readonly examples: Trail<TopoAwareRuleInput, RuleOutput>['examples'];
176
+ }
177
+
178
+ /**
179
+ * Wrap an existing `TopoAwareWardenRule` as a trail.
180
+ *
181
+ * Mirrors `wrapRule` for the per-topo dispatch path. Topo-aware rules run
182
+ * once per topo against the compiled runtime graph rather than per file,
183
+ * so the trail accepts the live `Topo` as input.
184
+ */
185
+ export const wrapTopoRule = (
186
+ options: WrapTopoRuleOptions
187
+ ): Trail<TopoAwareRuleInput, RuleOutput> => {
188
+ const { rule, examples } = options;
189
+ return trail(`warden.rule.${rule.name}`, {
190
+ blaze: async (input: TopoAwareRuleInput) => {
191
+ try {
192
+ const diagnostics = await rule.checkTopo(input.topo);
193
+ return Result.ok({ diagnostics: [...diagnostics] });
194
+ } catch (error) {
195
+ const cause = error instanceof Error ? error : new Error(String(error));
196
+ return Result.err(
197
+ new InternalError(
198
+ `Topo-aware rule "${rule.name}" threw while inspecting topo: ${cause.message}`,
199
+ { cause }
200
+ )
201
+ );
202
+ }
203
+ },
204
+ description: rule.description,
205
+ examples,
206
+ input: topoAwareRuleInput,
207
+ intent: 'read',
208
+ meta: buildRuleMeta(rule),
209
+ output: ruleOutput,
210
+ });
211
+ };