@ontrails/warden 1.0.0-beta.3 → 1.0.0-beta.32

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 (260) hide show
  1. package/CHANGELOG.md +674 -9
  2. package/README.md +133 -26
  3. package/bin/warden.ts +51 -0
  4. package/package.json +28 -5
  5. package/src/adapter-check.ts +136 -0
  6. package/src/ast.ts +137 -0
  7. package/src/cli.ts +1451 -104
  8. package/src/command.ts +966 -0
  9. package/src/config.ts +193 -0
  10. package/src/draft.ts +22 -0
  11. package/src/drift.ts +122 -22
  12. package/src/fix.ts +120 -0
  13. package/src/formatters.ts +79 -9
  14. package/src/guide.ts +245 -0
  15. package/src/index.ts +217 -14
  16. package/src/project-context.ts +192 -0
  17. package/src/project-rules.ts +290 -0
  18. package/src/resolve.ts +531 -0
  19. package/src/rules/activation-orphan.ts +97 -0
  20. package/src/rules/ast.ts +4008 -86
  21. package/src/rules/circular-refs.ts +154 -0
  22. package/src/rules/cli-command-route-coherence.ts +177 -0
  23. package/src/rules/composes-declarations.ts +837 -0
  24. package/src/rules/context-no-surface-types.ts +78 -15
  25. package/src/rules/contour-exists.ts +251 -0
  26. package/src/rules/contour-ids.ts +15 -0
  27. package/src/rules/dead-internal-trail.ts +161 -0
  28. package/src/rules/dead-public-trail.ts +258 -0
  29. package/src/rules/draft-file-marking.ts +160 -0
  30. package/src/rules/draft-visible-debt.ts +87 -0
  31. package/src/rules/duplicate-public-contract.ts +91 -0
  32. package/src/rules/error-mapping-completeness.ts +290 -0
  33. package/src/rules/example-valid.ts +395 -0
  34. package/src/rules/fires-declarations.ts +735 -0
  35. package/src/rules/implementation-returns-result.ts +1409 -166
  36. package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
  37. package/src/rules/incomplete-crud.ts +581 -0
  38. package/src/rules/index.ts +235 -18
  39. package/src/rules/intent-propagation.ts +127 -0
  40. package/src/rules/layer-field-name-drift.ts +102 -0
  41. package/src/rules/library-projection-coherence.ts +100 -0
  42. package/src/rules/metadata.ts +755 -0
  43. package/src/rules/missing-reconcile.ts +98 -0
  44. package/src/rules/missing-visibility.ts +110 -0
  45. package/src/rules/no-destructured-compose.ts +194 -0
  46. package/src/rules/no-dev-permit-in-source.ts +99 -0
  47. package/src/rules/no-direct-implementation-call.ts +7 -7
  48. package/src/rules/no-legacy-layer-imports.ts +211 -0
  49. package/src/rules/no-native-error-result.ts +118 -0
  50. package/src/rules/no-redundant-result-error-wrap.ts +384 -0
  51. package/src/rules/no-retired-cross-vocabulary.ts +194 -0
  52. package/src/rules/no-sync-result-assumption.ts +1135 -98
  53. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  54. package/src/rules/no-throw-in-implementation.ts +10 -9
  55. package/src/rules/no-top-level-surface.ts +371 -0
  56. package/src/rules/on-references-exist.ts +194 -0
  57. package/src/rules/orphaned-signal.ts +150 -0
  58. package/src/rules/owner-projection-parity.ts +143 -0
  59. package/src/rules/permit-governance.ts +25 -0
  60. package/src/rules/public-export-example-coverage.ts +562 -0
  61. package/src/rules/public-internal-deep-imports.ts +457 -0
  62. package/src/rules/public-output-schema.ts +29 -0
  63. package/src/rules/public-union-output-discriminants.ts +150 -0
  64. package/src/rules/read-intent-fires.ts +187 -0
  65. package/src/rules/reference-exists.ts +98 -0
  66. package/src/rules/registry-names.ts +155 -0
  67. package/src/rules/resolved-import-boundary.ts +146 -0
  68. package/src/rules/resource-declarations.ts +693 -0
  69. package/src/rules/resource-exists.ts +179 -0
  70. package/src/rules/resource-id-grammar.ts +65 -0
  71. package/src/rules/resource-mock-coverage.ts +115 -0
  72. package/src/rules/scan.ts +38 -25
  73. package/src/rules/scheduled-destroy-intent.ts +44 -0
  74. package/src/rules/signal-graph-coaching.ts +191 -0
  75. package/src/rules/specs.ts +9 -5
  76. package/src/rules/static-resource-accessor-preference.ts +649 -0
  77. package/src/rules/surface-facet-coherence.ts +362 -0
  78. package/src/rules/trail-fork-coaching.ts +616 -0
  79. package/src/rules/trail-versioning-source.ts +1072 -0
  80. package/src/rules/trail-versioning-topo.ts +172 -0
  81. package/src/rules/types.ts +297 -8
  82. package/src/rules/unmaterialized-activation-source.ts +84 -0
  83. package/src/rules/unreachable-detour-shadowing.ts +339 -0
  84. package/src/rules/valid-describe-refs.ts +162 -32
  85. package/src/rules/valid-detour-contract.ts +78 -0
  86. package/src/rules/warden-export-symmetry.ts +540 -0
  87. package/src/rules/warden-rules-use-ast.ts +1114 -0
  88. package/src/rules/webhook-route-collision.ts +243 -0
  89. package/src/trails/activation-orphan.trail.ts +84 -0
  90. package/src/trails/circular-refs.trail.ts +29 -0
  91. package/src/trails/cli-command-route-coherence.trail.ts +47 -0
  92. package/src/trails/composes-declarations.trail.ts +22 -0
  93. package/src/trails/context-no-surface-types.trail.ts +21 -0
  94. package/src/trails/contour-exists.trail.ts +21 -0
  95. package/src/trails/dead-internal-trail.trail.ts +26 -0
  96. package/src/trails/dead-public-trail.trail.ts +31 -0
  97. package/src/trails/deprecation-without-guidance.trail.ts +21 -0
  98. package/src/trails/draft-file-marking.trail.ts +16 -0
  99. package/src/trails/draft-visible-debt.trail.ts +16 -0
  100. package/src/trails/duplicate-public-contract.trail.ts +47 -0
  101. package/src/trails/error-mapping-completeness.trail.ts +30 -0
  102. package/src/trails/example-valid.trail.ts +25 -0
  103. package/src/trails/fires-declarations.trail.ts +23 -0
  104. package/src/trails/fork-without-preserved-blaze.trail.ts +31 -0
  105. package/src/trails/implementation-returns-result.trail.ts +20 -0
  106. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  107. package/src/trails/incomplete-crud.trail.ts +39 -0
  108. package/src/trails/index.ts +83 -0
  109. package/src/trails/intent-propagation.trail.ts +30 -0
  110. package/src/trails/layer-field-name-drift.trail.ts +39 -0
  111. package/src/trails/library-projection-coherence.trail.ts +43 -0
  112. package/src/trails/marker-schema-unsupported.trail.ts +23 -0
  113. package/src/trails/missing-reconcile.trail.ts +33 -0
  114. package/src/trails/missing-visibility.trail.ts +22 -0
  115. package/src/trails/no-destructured-compose.trail.ts +44 -0
  116. package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
  117. package/src/trails/no-direct-implementation-call.trail.ts +16 -0
  118. package/src/trails/no-legacy-layer-imports.trail.ts +41 -0
  119. package/src/trails/no-native-error-result.trail.ts +18 -0
  120. package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
  121. package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
  122. package/src/trails/no-sync-result-assumption.trail.ts +19 -0
  123. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  124. package/src/trails/no-throw-in-implementation.trail.ts +20 -0
  125. package/src/trails/no-top-level-surface.trail.ts +43 -0
  126. package/src/trails/on-references-exist.trail.ts +21 -0
  127. package/src/trails/orphaned-signal.trail.ts +36 -0
  128. package/src/trails/owner-projection-parity.trail.ts +26 -0
  129. package/src/trails/pending-force.trail.ts +21 -0
  130. package/src/trails/permit-governance.trail.ts +51 -0
  131. package/src/trails/prefer-schema-inference.trail.ts +21 -0
  132. package/src/trails/public-export-example-coverage.trail.ts +16 -0
  133. package/src/trails/public-internal-deep-imports.trail.ts +94 -0
  134. package/src/trails/public-output-schema.trail.ts +55 -0
  135. package/src/trails/public-union-output-discriminants.trail.ts +33 -0
  136. package/src/trails/read-intent-fires.trail.ts +20 -0
  137. package/src/trails/reference-exists.trail.ts +25 -0
  138. package/src/trails/resolved-import-boundary.trail.ts +109 -0
  139. package/src/trails/resource-declarations.trail.ts +25 -0
  140. package/src/trails/resource-exists.trail.ts +27 -0
  141. package/src/trails/resource-id-grammar.trail.ts +39 -0
  142. package/src/trails/resource-mock-coverage.trail.ts +40 -0
  143. package/src/trails/run.ts +162 -0
  144. package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
  145. package/src/trails/schema.ts +198 -0
  146. package/src/trails/signal-graph-coaching.trail.ts +77 -0
  147. package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
  148. package/src/trails/surface-facet-coherence.trail.ts +25 -0
  149. package/src/trails/topo.ts +6 -0
  150. package/src/trails/trail-fork-coaching.trail.ts +42 -0
  151. package/src/trails/unmaterialized-activation-source.trail.ts +72 -0
  152. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  153. package/src/trails/valid-describe-refs.trail.ts +18 -0
  154. package/src/trails/valid-detour-contract.trail.ts +71 -0
  155. package/src/trails/version-gap.trail.ts +35 -0
  156. package/src/trails/version-pinned-compose.trail.ts +23 -0
  157. package/src/trails/version-without-examples.trail.ts +38 -0
  158. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  159. package/src/trails/warden-rules-use-ast.trail.ts +64 -0
  160. package/src/trails/webhook-route-collision.trail.ts +50 -0
  161. package/src/trails/wrap-rule.ts +214 -0
  162. package/src/workspaces.ts +199 -0
  163. package/.turbo/turbo-build.log +0 -1
  164. package/.turbo/turbo-lint.log +0 -3
  165. package/.turbo/turbo-typecheck.log +0 -1
  166. package/dist/cli.d.ts +0 -46
  167. package/dist/cli.d.ts.map +0 -1
  168. package/dist/cli.js +0 -221
  169. package/dist/cli.js.map +0 -1
  170. package/dist/drift.d.ts +0 -26
  171. package/dist/drift.d.ts.map +0 -1
  172. package/dist/drift.js +0 -27
  173. package/dist/drift.js.map +0 -1
  174. package/dist/formatters.d.ts +0 -29
  175. package/dist/formatters.d.ts.map +0 -1
  176. package/dist/formatters.js +0 -87
  177. package/dist/formatters.js.map +0 -1
  178. package/dist/index.d.ts +0 -26
  179. package/dist/index.d.ts.map +0 -1
  180. package/dist/index.js +0 -26
  181. package/dist/index.js.map +0 -1
  182. package/dist/rules/ast.d.ts +0 -41
  183. package/dist/rules/ast.d.ts.map +0 -1
  184. package/dist/rules/ast.js +0 -163
  185. package/dist/rules/ast.js.map +0 -1
  186. package/dist/rules/context-no-surface-types.d.ts +0 -12
  187. package/dist/rules/context-no-surface-types.d.ts.map +0 -1
  188. package/dist/rules/context-no-surface-types.js +0 -96
  189. package/dist/rules/context-no-surface-types.js.map +0 -1
  190. package/dist/rules/implementation-returns-result.d.ts +0 -13
  191. package/dist/rules/implementation-returns-result.d.ts.map +0 -1
  192. package/dist/rules/implementation-returns-result.js +0 -277
  193. package/dist/rules/implementation-returns-result.js.map +0 -1
  194. package/dist/rules/index.d.ts +0 -22
  195. package/dist/rules/index.d.ts.map +0 -1
  196. package/dist/rules/index.js +0 -41
  197. package/dist/rules/index.js.map +0 -1
  198. package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
  199. package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
  200. package/dist/rules/no-direct-impl-in-route.js +0 -46
  201. package/dist/rules/no-direct-impl-in-route.js.map +0 -1
  202. package/dist/rules/no-direct-implementation-call.d.ts +0 -12
  203. package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
  204. package/dist/rules/no-direct-implementation-call.js +0 -39
  205. package/dist/rules/no-direct-implementation-call.js.map +0 -1
  206. package/dist/rules/no-sync-result-assumption.d.ts +0 -6
  207. package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
  208. package/dist/rules/no-sync-result-assumption.js +0 -98
  209. package/dist/rules/no-sync-result-assumption.js.map +0 -1
  210. package/dist/rules/no-throw-in-detour-target.d.ts +0 -12
  211. package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
  212. package/dist/rules/no-throw-in-detour-target.js +0 -87
  213. package/dist/rules/no-throw-in-detour-target.js.map +0 -1
  214. package/dist/rules/no-throw-in-implementation.d.ts +0 -9
  215. package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
  216. package/dist/rules/no-throw-in-implementation.js +0 -34
  217. package/dist/rules/no-throw-in-implementation.js.map +0 -1
  218. package/dist/rules/prefer-schema-inference.d.ts +0 -7
  219. package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
  220. package/dist/rules/prefer-schema-inference.js +0 -86
  221. package/dist/rules/prefer-schema-inference.js.map +0 -1
  222. package/dist/rules/scan.d.ts +0 -8
  223. package/dist/rules/scan.d.ts.map +0 -1
  224. package/dist/rules/scan.js +0 -32
  225. package/dist/rules/scan.js.map +0 -1
  226. package/dist/rules/specs.d.ts +0 -29
  227. package/dist/rules/specs.d.ts.map +0 -1
  228. package/dist/rules/specs.js +0 -192
  229. package/dist/rules/specs.js.map +0 -1
  230. package/dist/rules/structure.d.ts +0 -13
  231. package/dist/rules/structure.d.ts.map +0 -1
  232. package/dist/rules/structure.js +0 -142
  233. package/dist/rules/structure.js.map +0 -1
  234. package/dist/rules/types.d.ts +0 -52
  235. package/dist/rules/types.d.ts.map +0 -1
  236. package/dist/rules/types.js +0 -2
  237. package/dist/rules/types.js.map +0 -1
  238. package/dist/rules/valid-describe-refs.d.ts +0 -7
  239. package/dist/rules/valid-describe-refs.d.ts.map +0 -1
  240. package/dist/rules/valid-describe-refs.js +0 -51
  241. package/dist/rules/valid-describe-refs.js.map +0 -1
  242. package/dist/rules/valid-detour-refs.d.ts +0 -6
  243. package/dist/rules/valid-detour-refs.d.ts.map +0 -1
  244. package/dist/rules/valid-detour-refs.js +0 -116
  245. package/dist/rules/valid-detour-refs.js.map +0 -1
  246. package/src/__tests__/cli.test.ts +0 -198
  247. package/src/__tests__/drift.test.ts +0 -74
  248. package/src/__tests__/formatters.test.ts +0 -157
  249. package/src/__tests__/implementation-returns-result.test.ts +0 -128
  250. package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
  251. package/src/__tests__/no-sync-result-assumption.test.ts +0 -85
  252. package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
  253. package/src/__tests__/prefer-schema-inference.test.ts +0 -84
  254. package/src/__tests__/rules.test.ts +0 -215
  255. package/src/__tests__/valid-describe-refs.test.ts +0 -60
  256. package/src/rules/no-direct-impl-in-route.ts +0 -77
  257. package/src/rules/no-throw-in-detour-target.ts +0 -150
  258. package/src/rules/valid-detour-refs.ts +0 -187
  259. package/tsconfig.json +0 -9
  260. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,55 @@
1
+ import { noRedundantResultErrorWrap } from '../rules/no-redundant-result-error-wrap.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const noRedundantResultErrorWrapTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'entity.ts',
10
+ sourceCode: `import { Result, trail } from "@ontrails/core";
11
+
12
+ trail("entity.load", {
13
+ blaze: async (input, ctx) => {
14
+ const loaded = await ctx.compose("entity.fetch", input);
15
+ if (loaded.isErr()) {
16
+ return loaded;
17
+ }
18
+ return Result.ok(loaded.value);
19
+ },
20
+ });`,
21
+ },
22
+ name: 'Existing Result values pass through directly',
23
+ },
24
+ {
25
+ expected: {
26
+ diagnostics: [
27
+ {
28
+ filePath: 'entity.ts',
29
+ line: 7,
30
+ message:
31
+ 'Trail "entity.load": Result.err(loaded.error) re-wraps a Result that already carries that error. Return loaded directly to preserve the original Result boundary.',
32
+ rule: 'no-redundant-result-error-wrap',
33
+ severity: 'warn',
34
+ },
35
+ ],
36
+ },
37
+ input: {
38
+ filePath: 'entity.ts',
39
+ sourceCode: `import { Result, trail } from "@ontrails/core";
40
+
41
+ trail("entity.load", {
42
+ blaze: async (input, ctx) => {
43
+ const loaded = await ctx.compose("entity.fetch", input);
44
+ if (loaded.isErr()) {
45
+ return Result.err(loaded.error);
46
+ }
47
+ return Result.ok(loaded.value);
48
+ },
49
+ });`,
50
+ },
51
+ name: 'Warns when an existing Result error is re-wrapped',
52
+ },
53
+ ],
54
+ rule: noRedundantResultErrorWrap,
55
+ });
@@ -0,0 +1,42 @@
1
+ import { noRetiredCrossVocabulary } from '../rules/no-retired-cross-vocabulary.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const noRetiredCrossVocabularyTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'apps/example/src/trails/play.ts',
10
+ sourceCode: 'export const play = trail("play", { composes: [] });\n',
11
+ },
12
+ name: 'Source files using compose vocabulary are clean',
13
+ },
14
+ {
15
+ expected: {
16
+ diagnostics: [
17
+ {
18
+ filePath: 'apps/example/src/trails/play.ts',
19
+ fix: {
20
+ class: 'term-rewrite',
21
+ edits: [{ end: 43, replacement: 'composes', start: 36 }],
22
+ reason:
23
+ "Retired composition vocabulary 'crosses' has a mechanical beta.19 replacement 'composes'.",
24
+ safety: 'safe',
25
+ },
26
+ line: 1,
27
+ message:
28
+ "Retired composition vocabulary 'crosses' should be 'composes' after the beta.19 compose cutover.",
29
+ rule: 'no-retired-cross-vocabulary',
30
+ severity: 'error',
31
+ },
32
+ ],
33
+ },
34
+ input: {
35
+ filePath: 'apps/example/src/trails/play.ts',
36
+ sourceCode: 'export const play = trail("play", { crosses: [] });\n',
37
+ },
38
+ name: 'Retired crosses declarations produce safe migration diagnostics',
39
+ },
40
+ ],
41
+ rule: noRetiredCrossVocabulary,
42
+ });
@@ -0,0 +1,19 @@
1
+ import { noSyncResultAssumption } from '../rules/no-sync-result-assumption.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const noSyncResultAssumptionTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'clean.ts',
10
+ sourceCode: `const result = await myTrail.blaze(input, ctx);
11
+ if (result.isOk()) {
12
+ console.log(result.value);
13
+ }`,
14
+ },
15
+ name: 'Properly awaited .blaze() call',
16
+ },
17
+ ],
18
+ rule: noSyncResultAssumption,
19
+ });
@@ -0,0 +1,24 @@
1
+ import { noThrowInDetourRecover } from '../rules/no-throw-in-detour-recover.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const noThrowInDetourRecoverTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'clean.ts',
10
+ sourceCode: `trail("entity.save", {
11
+ detours: [
12
+ {
13
+ on: ConflictError,
14
+ recover: async () => Result.ok({ recovered: true }),
15
+ },
16
+ ],
17
+ blaze: () => Result.err(new ConflictError("conflict")),
18
+ })`,
19
+ },
20
+ name: 'Detour recover without throw',
21
+ },
22
+ ],
23
+ rule: noThrowInDetourRecover,
24
+ });
@@ -0,0 +1,20 @@
1
+ import { noThrowInImplementation } from '../rules/no-throw-in-implementation.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const noThrowInImplementationTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'clean.ts',
10
+ sourceCode: `trail("entity.show", {
11
+ blaze: async (input, ctx) => {
12
+ return Result.ok({ name: "test" });
13
+ }
14
+ })`,
15
+ },
16
+ name: 'Clean implementation without throw',
17
+ },
18
+ ],
19
+ rule: noThrowInImplementation,
20
+ });
@@ -0,0 +1,43 @@
1
+ import { noTopLevelSurface } from '../rules/no-top-level-surface.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const noTopLevelSurfaceTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'src/mcp.ts',
10
+ sourceCode: `import { surface } from "@ontrails/mcp";
11
+ import graph from "./app";
12
+
13
+ await surface(graph);`,
14
+ },
15
+ name: 'Allows dedicated surface entry modules',
16
+ },
17
+ {
18
+ expected: {
19
+ diagnostics: [
20
+ {
21
+ filePath: 'src/app.ts',
22
+ line: 6,
23
+ message:
24
+ 'This module exports a topo and opens a surface at module top level. Trails introspection commands (`survey`, `guide`, `compile`) import topo entry modules, so opening a surface here can trigger sockets or transports during introspection. Move surface-opening to a separate entry/bin and keep the topo-export module side-effect-free.',
25
+ rule: 'no-top-level-surface',
26
+ severity: 'warn',
27
+ },
28
+ ],
29
+ },
30
+ input: {
31
+ filePath: 'src/app.ts',
32
+ sourceCode: `import { topo } from "@ontrails/core";
33
+ import { surface } from "@ontrails/mcp";
34
+ import * as trails from "./trails";
35
+
36
+ export const graph = topo("app", trails);
37
+ await surface(graph);`,
38
+ },
39
+ name: 'Warns when a topo export module opens a surface',
40
+ },
41
+ ],
42
+ rule: noTopLevelSurface,
43
+ });
@@ -0,0 +1,21 @@
1
+ import { onReferencesExist } from '../rules/on-references-exist.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const onReferencesExistTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'consumer.ts',
10
+ knownSignalIds: ['entity.created'],
11
+ knownTrailIds: ['notify'],
12
+ sourceCode: `trail("notify", {
13
+ on: ["entity.created"],
14
+ blaze: async (input, ctx) => Result.ok({}),
15
+ })`,
16
+ },
17
+ name: 'Resolved on: reference',
18
+ },
19
+ ],
20
+ rule: onReferencesExist,
21
+ });
@@ -0,0 +1,36 @@
1
+ import { orphanedSignal } from '../rules/orphaned-signal.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const orphanedSignalTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ // Composite keys: `${storeBinding}:${tableName}` so two stores with
10
+ // the same table name don't collide.
11
+ crudTableIds: ['definition:notes'],
12
+ filePath: 'clean.ts',
13
+ knownTrailIds: ['notes.notify'],
14
+ onTargetSignalIds: [
15
+ 'definition:notes.created',
16
+ 'definition:notes.updated',
17
+ 'definition:notes.removed',
18
+ ],
19
+ sourceCode: `import { store } from '@ontrails/store';
20
+ import { z } from 'zod';
21
+
22
+ const definition = store({
23
+ notes: {
24
+ identity: 'id',
25
+ schema: z.object({
26
+ id: z.string(),
27
+ title: z.string(),
28
+ }),
29
+ },
30
+ });`,
31
+ },
32
+ name: 'Derived store signals stay quiet when trail on consumers exist',
33
+ },
34
+ ],
35
+ rule: orphanedSignal,
36
+ });
@@ -0,0 +1,26 @@
1
+ import { fileURLToPath } from 'node:url';
2
+
3
+ import { ownerProjectionParity } from '../rules/owner-projection-parity.js';
4
+ import { wrapRule } from './wrap-rule.js';
5
+
6
+ export const ownerProjectionParityTrail = wrapRule({
7
+ examples: [
8
+ {
9
+ expected: { diagnostics: [] },
10
+ input: {
11
+ filePath: fileURLToPath(
12
+ new URL('../../../http/src/method.ts', import.meta.url)
13
+ ),
14
+ sourceCode: `import type { Intent } from '@ontrails/core';
15
+
16
+ export const httpMethodByIntent = {
17
+ destroy: 'DELETE',
18
+ read: 'GET',
19
+ write: 'POST',
20
+ } as const satisfies Record<Intent, 'GET' | 'POST' | 'DELETE'>;`,
21
+ },
22
+ name: 'HTTP method projection covers core intent values',
23
+ },
24
+ ],
25
+ rule: ownerProjectionParity,
26
+ });
@@ -0,0 +1,21 @@
1
+ import { topo } from '@ontrails/core';
2
+ import { deriveTopoGraph } from '@ontrails/topographer';
3
+
4
+ import { pendingForce } from '../rules/trail-versioning-topo.js';
5
+ import { wrapTopoRule } from './wrap-rule.js';
6
+
7
+ const emptyTopo = topo('pending-force-clean', {});
8
+
9
+ export const pendingForceTrail = wrapTopoRule({
10
+ examples: [
11
+ {
12
+ expected: { diagnostics: [] },
13
+ input: {
14
+ graph: deriveTopoGraph(emptyTopo),
15
+ topo: emptyTopo,
16
+ },
17
+ name: 'No pending force audit events passes',
18
+ },
19
+ ],
20
+ rule: pendingForce,
21
+ });
@@ -0,0 +1,51 @@
1
+ import { Result, topo, trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import { permitGovernance } from '../rules/permit-governance.js';
5
+ import { wrapTopoRule } from './wrap-rule.js';
6
+
7
+ const destroyWithoutPermit = trail('entity.delete', {
8
+ blaze: () => Result.ok({ ok: true }),
9
+ input: z.object({}),
10
+ intent: 'destroy',
11
+ output: z.object({ ok: z.boolean() }),
12
+ });
13
+
14
+ const scopedDestroy = trail('entity.delete', {
15
+ blaze: () => Result.ok({ ok: true }),
16
+ input: z.object({}),
17
+ intent: 'destroy',
18
+ output: z.object({ ok: z.boolean() }),
19
+ permit: { scopes: ['entity:delete'] },
20
+ });
21
+
22
+ export const permitGovernanceTrail = wrapTopoRule({
23
+ examples: [
24
+ {
25
+ expected: {
26
+ diagnostics: [
27
+ {
28
+ filePath: '<topo>',
29
+ line: 1,
30
+ message:
31
+ 'Trail "entity.delete" has intent \'destroy\' but no permit declaration',
32
+ rule: 'permit.destroyWithoutPermit',
33
+ severity: 'error',
34
+ },
35
+ ],
36
+ },
37
+ input: {
38
+ topo: topo('trl-377-missing-permit', { destroyWithoutPermit }),
39
+ },
40
+ name: 'Destroy trails without permits emit an error',
41
+ },
42
+ {
43
+ expected: { diagnostics: [] },
44
+ input: {
45
+ topo: topo('trl-377-scoped-destroy', { scopedDestroy }),
46
+ },
47
+ name: 'Scoped destroy permits keep permit governance clean',
48
+ },
49
+ ],
50
+ rule: permitGovernance,
51
+ });
@@ -0,0 +1,21 @@
1
+ import { preferSchemaInference } from '../rules/prefer-schema-inference.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const preferSchemaInferenceTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'clean.ts',
10
+ sourceCode: `trail("entity.show", {
11
+ input: z.object({ name: z.string() }),
12
+ blaze: async (input, ctx) => {
13
+ return Result.ok({ name: input.name });
14
+ }
15
+ })`,
16
+ },
17
+ name: 'Trail without redundant field overrides',
18
+ },
19
+ ],
20
+ rule: preferSchemaInference,
21
+ });
@@ -0,0 +1,16 @@
1
+ import { publicExportExampleCoverage } from '../rules/public-export-example-coverage.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const publicExportExampleCoverageTrail = 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 barrels outside the public API example policy',
13
+ },
14
+ ],
15
+ rule: publicExportExampleCoverage,
16
+ });
@@ -0,0 +1,94 @@
1
+ import { publicInternalDeepImports } from '../rules/public-internal-deep-imports.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const publicInternalDeepImportsTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'packages/store/src/trails/example.ts',
10
+ importResolutionsByFile: {
11
+ 'packages/store/src/trails/example.ts': [
12
+ {
13
+ crossesPackageBoundary: true,
14
+ importSource: '@ontrails/core/trails',
15
+ importerPath: 'packages/store/src/trails/example.ts',
16
+ isInternalTarget: false,
17
+ line: 1,
18
+ packageName: '@ontrails/core',
19
+ packageRoot: 'packages/core',
20
+ resolvedPath: 'packages/core/src/trails/index.ts',
21
+ usesPublicExport: true,
22
+ },
23
+ ],
24
+ },
25
+ knownTrailIds: [],
26
+ publicWorkspaces: {
27
+ '@ontrails/core': {
28
+ hasExports: true,
29
+ name: '@ontrails/core',
30
+ packageJsonPath: 'packages/core/package.json',
31
+ rootDir: 'packages/core',
32
+ },
33
+ '@ontrails/store': {
34
+ hasExports: true,
35
+ name: '@ontrails/store',
36
+ packageJsonPath: 'packages/store/package.json',
37
+ rootDir: 'packages/store',
38
+ },
39
+ },
40
+ sourceCode: `import { deriveTrail } from '@ontrails/core/trails';\n`,
41
+ },
42
+ name: 'Allows exported package subpaths',
43
+ },
44
+ {
45
+ expected: {
46
+ diagnostics: [
47
+ {
48
+ filePath: 'packages/store/src/trails/example.ts',
49
+ line: 1,
50
+ message:
51
+ '@ontrails specifier "@ontrails/core/src/internal/hidden" is not exported by @ontrails/core. Use the package root or an exported subpath; if the API is missing, add an owner export follow-up instead of importing internals.',
52
+ rule: 'public-internal-deep-imports',
53
+ severity: 'error',
54
+ },
55
+ ],
56
+ },
57
+ input: {
58
+ filePath: 'packages/store/src/trails/example.ts',
59
+ importResolutionsByFile: {
60
+ 'packages/store/src/trails/example.ts': [
61
+ {
62
+ crossesPackageBoundary: true,
63
+ errorKind: 'package-path-not-exported',
64
+ importSource: '@ontrails/core/src/internal/hidden',
65
+ importerPath: 'packages/store/src/trails/example.ts',
66
+ isInternalTarget: false,
67
+ line: 1,
68
+ packageName: '@ontrails/core',
69
+ usesPublicExport: false,
70
+ },
71
+ ],
72
+ },
73
+ knownTrailIds: [],
74
+ publicWorkspaces: {
75
+ '@ontrails/core': {
76
+ hasExports: true,
77
+ name: '@ontrails/core',
78
+ packageJsonPath: 'packages/core/package.json',
79
+ rootDir: 'packages/core',
80
+ },
81
+ '@ontrails/store': {
82
+ hasExports: true,
83
+ name: '@ontrails/store',
84
+ packageJsonPath: 'packages/store/package.json',
85
+ rootDir: 'packages/store',
86
+ },
87
+ },
88
+ sourceCode: `import { hidden } from '@ontrails/core/src/internal/hidden';\n`,
89
+ },
90
+ name: 'Flags compose-package imports into owner internals',
91
+ },
92
+ ],
93
+ rule: publicInternalDeepImports,
94
+ });
@@ -0,0 +1,55 @@
1
+ import { Result, topo, trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import { publicOutputSchema } from '../rules/public-output-schema.js';
5
+ import { wrapTopoRule } from './wrap-rule.js';
6
+
7
+ const cleanTrail = trail('report.read', {
8
+ blaze: () => Result.ok({ ok: true }),
9
+ input: z.object({}),
10
+ output: z.object({ ok: z.boolean() }),
11
+ });
12
+
13
+ const cleanTopo = topo('public-output-schema-clean', {
14
+ cleanTrail,
15
+ });
16
+
17
+ const missingOutputTrail = trail('report.missing', {
18
+ blaze: () => Result.ok({ ok: true }),
19
+ input: z.object({}),
20
+ });
21
+
22
+ const missingOutputTopo = topo('public-output-schema-missing', {
23
+ missingOutputTrail,
24
+ });
25
+
26
+ export const publicOutputSchemaTrail = wrapTopoRule({
27
+ examples: [
28
+ {
29
+ expected: { diagnostics: [] },
30
+ input: {
31
+ topo: cleanTopo,
32
+ },
33
+ name: 'Public surface trails declare output schemas',
34
+ },
35
+ {
36
+ expected: {
37
+ diagnostics: [
38
+ {
39
+ filePath: '<topo>',
40
+ line: 1,
41
+ message:
42
+ 'Trail "report.missing" is visible to public MCP/HTTP surface projection but does not declare an output schema. Add an explicit output schema, or mark the trail visibility as internal if it is composition-only.',
43
+ rule: 'public-output-schema',
44
+ severity: 'error',
45
+ },
46
+ ],
47
+ },
48
+ input: {
49
+ topo: missingOutputTopo,
50
+ },
51
+ name: 'Public surface trails without output schemas are flagged',
52
+ },
53
+ ],
54
+ rule: publicOutputSchema,
55
+ });
@@ -0,0 +1,33 @@
1
+ import { Result, topo, trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import { publicUnionOutputDiscriminants } from '../rules/public-union-output-discriminants.js';
5
+ import { wrapTopoRule } from './wrap-rule.js';
6
+
7
+ const cleanOutput = z.discriminatedUnion('kind', [
8
+ z.object({ kind: z.literal('message'), message: z.string() }),
9
+ z.object({ count: z.number(), kind: z.literal('count') }),
10
+ ]);
11
+
12
+ const cleanTrail = trail('report.read', {
13
+ blaze: () => Result.ok({ kind: 'message' as const, message: 'ok' }),
14
+ input: z.object({}),
15
+ output: cleanOutput,
16
+ });
17
+
18
+ const cleanTopo = topo('public-union-output-discriminants-clean', {
19
+ cleanTrail,
20
+ });
21
+
22
+ export const publicUnionOutputDiscriminantsTrail = wrapTopoRule({
23
+ examples: [
24
+ {
25
+ expected: { diagnostics: [] },
26
+ input: {
27
+ topo: cleanTopo,
28
+ },
29
+ name: 'Public output object unions expose a literal discriminator',
30
+ },
31
+ ],
32
+ rule: publicUnionOutputDiscriminants,
33
+ });
@@ -0,0 +1,20 @@
1
+ import { readIntentFires } from '../rules/read-intent-fires.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const readIntentFiresTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'clean.ts',
10
+ sourceCode: `const entityLoaded = signal('entity.loaded', { payload: z.object({}) });
11
+ trail('entity.read', {
12
+ intent: 'read',
13
+ blaze: async () => Result.ok({}),
14
+ });`,
15
+ },
16
+ name: 'Read trails without fires stay quiet',
17
+ },
18
+ ],
19
+ rule: readIntentFires,
20
+ });
@@ -0,0 +1,25 @@
1
+ import { referenceExists } from '../rules/reference-exists.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const referenceExistsTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'contours.ts',
10
+ knownContourIds: ['gist', 'user'],
11
+ knownTrailIds: [],
12
+ sourceCode: `const user = contour("user", {
13
+ id: z.string().uuid(),
14
+ }, { identity: "id" });
15
+
16
+ const gist = contour("gist", {
17
+ id: z.string().uuid(),
18
+ ownerId: user.id(),
19
+ }, { identity: "id" });`,
20
+ },
21
+ name: 'Contour references resolve to known project contours',
22
+ },
23
+ ],
24
+ rule: referenceExists,
25
+ });