@ontrails/warden 1.0.0-beta.2 → 1.0.0-beta.21

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 (249) hide show
  1. package/CHANGELOG.md +497 -6
  2. package/README.md +77 -26
  3. package/bin/warden.ts +50 -0
  4. package/package.json +27 -5
  5. package/src/adapter-check.ts +136 -0
  6. package/src/ast.ts +28 -0
  7. package/src/cli.ts +1374 -103
  8. package/src/command.ts +953 -0
  9. package/src/config.ts +184 -0
  10. package/src/draft.ts +22 -0
  11. package/src/drift.ts +106 -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 +206 -14
  16. package/src/project-context.ts +163 -0
  17. package/src/resolve.ts +530 -0
  18. package/src/rules/activation-orphan.ts +97 -0
  19. package/src/rules/ast.ts +3176 -85
  20. package/src/rules/circular-refs.ts +154 -0
  21. package/src/rules/composes-declarations.ts +704 -0
  22. package/src/rules/context-no-surface-types.ts +68 -8
  23. package/src/rules/contour-exists.ts +251 -0
  24. package/src/rules/contour-ids.ts +15 -0
  25. package/src/rules/dead-internal-trail.ts +154 -0
  26. package/src/rules/draft-file-marking.ts +160 -0
  27. package/src/rules/draft-visible-debt.ts +87 -0
  28. package/src/rules/error-mapping-completeness.ts +288 -0
  29. package/src/rules/example-valid.ts +401 -0
  30. package/src/rules/fires-declarations.ts +758 -0
  31. package/src/rules/implementation-returns-result.ts +1265 -95
  32. package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
  33. package/src/rules/incomplete-crud.ts +580 -0
  34. package/src/rules/index.ts +219 -18
  35. package/src/rules/intent-propagation.ts +127 -0
  36. package/src/rules/layer-field-name-drift.ts +96 -0
  37. package/src/rules/metadata.ts +654 -0
  38. package/src/rules/missing-reconcile.ts +98 -0
  39. package/src/rules/missing-visibility.ts +110 -0
  40. package/src/rules/no-destructured-compose.ts +192 -0
  41. package/src/rules/no-dev-permit-in-source.ts +99 -0
  42. package/src/rules/no-direct-implementation-call.ts +7 -7
  43. package/src/rules/no-legacy-layer-imports.ts +211 -0
  44. package/src/rules/no-native-error-result.ts +111 -0
  45. package/src/rules/no-redundant-result-error-wrap.ts +331 -0
  46. package/src/rules/no-retired-cross-vocabulary.ts +194 -0
  47. package/src/rules/no-sync-result-assumption.ts +1134 -99
  48. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  49. package/src/rules/no-throw-in-implementation.ts +10 -9
  50. package/src/rules/no-top-level-surface.ts +389 -0
  51. package/src/rules/on-references-exist.ts +194 -0
  52. package/src/rules/orphaned-signal.ts +150 -0
  53. package/src/rules/owner-projection-parity.ts +146 -0
  54. package/src/rules/permit-governance.ts +25 -0
  55. package/src/rules/public-export-example-coverage.ts +553 -0
  56. package/src/rules/public-internal-deep-imports.ts +517 -0
  57. package/src/rules/public-output-schema.ts +29 -0
  58. package/src/rules/public-union-output-discriminants.ts +150 -0
  59. package/src/rules/read-intent-fires.ts +187 -0
  60. package/src/rules/reference-exists.ts +98 -0
  61. package/src/rules/registry-names.ts +145 -0
  62. package/src/rules/resolved-import-boundary.ts +146 -0
  63. package/src/rules/resource-declarations.ts +704 -0
  64. package/src/rules/resource-exists.ts +179 -0
  65. package/src/rules/resource-id-grammar.ts +65 -0
  66. package/src/rules/resource-mock-coverage.ts +115 -0
  67. package/src/rules/scan.ts +38 -25
  68. package/src/rules/scheduled-destroy-intent.ts +44 -0
  69. package/src/rules/signal-graph-coaching.ts +191 -0
  70. package/src/rules/specs.ts +9 -5
  71. package/src/rules/static-resource-accessor-preference.ts +657 -0
  72. package/src/rules/surface-facet-coherence.ts +370 -0
  73. package/src/rules/trail-versioning-source.ts +1094 -0
  74. package/src/rules/trail-versioning-topo.ts +172 -0
  75. package/src/rules/types.ts +270 -6
  76. package/src/rules/unmaterialized-activation-source.ts +84 -0
  77. package/src/rules/unreachable-detour-shadowing.ts +344 -0
  78. package/src/rules/valid-describe-refs.ts +160 -32
  79. package/src/rules/valid-detour-contract.ts +78 -0
  80. package/src/rules/warden-export-symmetry.ts +533 -0
  81. package/src/rules/warden-rules-use-ast.ts +996 -0
  82. package/src/rules/webhook-route-collision.ts +243 -0
  83. package/src/trails/activation-orphan.trail.ts +84 -0
  84. package/src/trails/circular-refs.trail.ts +29 -0
  85. package/src/trails/composes-declarations.trail.ts +22 -0
  86. package/src/trails/context-no-surface-types.trail.ts +21 -0
  87. package/src/trails/contour-exists.trail.ts +21 -0
  88. package/src/trails/dead-internal-trail.trail.ts +26 -0
  89. package/src/trails/deprecation-without-guidance.trail.ts +21 -0
  90. package/src/trails/draft-file-marking.trail.ts +16 -0
  91. package/src/trails/draft-visible-debt.trail.ts +16 -0
  92. package/src/trails/error-mapping-completeness.trail.ts +29 -0
  93. package/src/trails/example-valid.trail.ts +25 -0
  94. package/src/trails/fires-declarations.trail.ts +23 -0
  95. package/src/trails/fork-without-preserved-blaze.trail.ts +31 -0
  96. package/src/trails/implementation-returns-result.trail.ts +20 -0
  97. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  98. package/src/trails/incomplete-crud.trail.ts +39 -0
  99. package/src/trails/index.ts +78 -0
  100. package/src/trails/intent-propagation.trail.ts +30 -0
  101. package/src/trails/layer-field-name-drift.trail.ts +39 -0
  102. package/src/trails/marker-schema-unsupported.trail.ts +23 -0
  103. package/src/trails/missing-reconcile.trail.ts +33 -0
  104. package/src/trails/missing-visibility.trail.ts +22 -0
  105. package/src/trails/no-destructured-compose.trail.ts +44 -0
  106. package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
  107. package/src/trails/no-direct-implementation-call.trail.ts +16 -0
  108. package/src/trails/no-legacy-layer-imports.trail.ts +41 -0
  109. package/src/trails/no-native-error-result.trail.ts +18 -0
  110. package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
  111. package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
  112. package/src/trails/no-sync-result-assumption.trail.ts +19 -0
  113. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  114. package/src/trails/no-throw-in-implementation.trail.ts +20 -0
  115. package/src/trails/no-top-level-surface.trail.ts +43 -0
  116. package/src/trails/on-references-exist.trail.ts +21 -0
  117. package/src/trails/orphaned-signal.trail.ts +36 -0
  118. package/src/trails/owner-projection-parity.trail.ts +26 -0
  119. package/src/trails/pending-force.trail.ts +21 -0
  120. package/src/trails/permit-governance.trail.ts +51 -0
  121. package/src/trails/prefer-schema-inference.trail.ts +21 -0
  122. package/src/trails/public-export-example-coverage.trail.ts +16 -0
  123. package/src/trails/public-internal-deep-imports.trail.ts +94 -0
  124. package/src/trails/public-output-schema.trail.ts +55 -0
  125. package/src/trails/public-union-output-discriminants.trail.ts +33 -0
  126. package/src/trails/read-intent-fires.trail.ts +20 -0
  127. package/src/trails/reference-exists.trail.ts +25 -0
  128. package/src/trails/resolved-import-boundary.trail.ts +109 -0
  129. package/src/trails/resource-declarations.trail.ts +25 -0
  130. package/src/trails/resource-exists.trail.ts +27 -0
  131. package/src/trails/resource-id-grammar.trail.ts +39 -0
  132. package/src/trails/resource-mock-coverage.trail.ts +40 -0
  133. package/src/trails/run.ts +162 -0
  134. package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
  135. package/src/trails/schema.ts +194 -0
  136. package/src/trails/signal-graph-coaching.trail.ts +77 -0
  137. package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
  138. package/src/trails/surface-facet-coherence.trail.ts +25 -0
  139. package/src/trails/topo.ts +6 -0
  140. package/src/trails/unmaterialized-activation-source.trail.ts +72 -0
  141. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  142. package/src/trails/valid-describe-refs.trail.ts +18 -0
  143. package/src/trails/valid-detour-contract.trail.ts +71 -0
  144. package/src/trails/version-gap.trail.ts +35 -0
  145. package/src/trails/version-pinned-compose.trail.ts +23 -0
  146. package/src/trails/version-without-examples.trail.ts +38 -0
  147. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  148. package/src/trails/warden-rules-use-ast.trail.ts +45 -0
  149. package/src/trails/webhook-route-collision.trail.ts +50 -0
  150. package/src/trails/wrap-rule.ts +213 -0
  151. package/src/workspaces.ts +238 -0
  152. package/.turbo/turbo-build.log +0 -1
  153. package/.turbo/turbo-lint.log +0 -3
  154. package/.turbo/turbo-typecheck.log +0 -1
  155. package/dist/cli.d.ts +0 -46
  156. package/dist/cli.d.ts.map +0 -1
  157. package/dist/cli.js +0 -221
  158. package/dist/cli.js.map +0 -1
  159. package/dist/drift.d.ts +0 -26
  160. package/dist/drift.d.ts.map +0 -1
  161. package/dist/drift.js +0 -27
  162. package/dist/drift.js.map +0 -1
  163. package/dist/formatters.d.ts +0 -29
  164. package/dist/formatters.d.ts.map +0 -1
  165. package/dist/formatters.js +0 -87
  166. package/dist/formatters.js.map +0 -1
  167. package/dist/index.d.ts +0 -26
  168. package/dist/index.d.ts.map +0 -1
  169. package/dist/index.js +0 -26
  170. package/dist/index.js.map +0 -1
  171. package/dist/rules/ast.d.ts +0 -41
  172. package/dist/rules/ast.d.ts.map +0 -1
  173. package/dist/rules/ast.js +0 -163
  174. package/dist/rules/ast.js.map +0 -1
  175. package/dist/rules/context-no-surface-types.d.ts +0 -12
  176. package/dist/rules/context-no-surface-types.d.ts.map +0 -1
  177. package/dist/rules/context-no-surface-types.js +0 -96
  178. package/dist/rules/context-no-surface-types.js.map +0 -1
  179. package/dist/rules/implementation-returns-result.d.ts +0 -13
  180. package/dist/rules/implementation-returns-result.d.ts.map +0 -1
  181. package/dist/rules/implementation-returns-result.js +0 -231
  182. package/dist/rules/implementation-returns-result.js.map +0 -1
  183. package/dist/rules/index.d.ts +0 -22
  184. package/dist/rules/index.d.ts.map +0 -1
  185. package/dist/rules/index.js +0 -41
  186. package/dist/rules/index.js.map +0 -1
  187. package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
  188. package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
  189. package/dist/rules/no-direct-impl-in-route.js +0 -46
  190. package/dist/rules/no-direct-impl-in-route.js.map +0 -1
  191. package/dist/rules/no-direct-implementation-call.d.ts +0 -12
  192. package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
  193. package/dist/rules/no-direct-implementation-call.js +0 -39
  194. package/dist/rules/no-direct-implementation-call.js.map +0 -1
  195. package/dist/rules/no-sync-result-assumption.d.ts +0 -6
  196. package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
  197. package/dist/rules/no-sync-result-assumption.js +0 -98
  198. package/dist/rules/no-sync-result-assumption.js.map +0 -1
  199. package/dist/rules/no-throw-in-detour-target.d.ts +0 -12
  200. package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
  201. package/dist/rules/no-throw-in-detour-target.js +0 -87
  202. package/dist/rules/no-throw-in-detour-target.js.map +0 -1
  203. package/dist/rules/no-throw-in-implementation.d.ts +0 -9
  204. package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
  205. package/dist/rules/no-throw-in-implementation.js +0 -34
  206. package/dist/rules/no-throw-in-implementation.js.map +0 -1
  207. package/dist/rules/prefer-schema-inference.d.ts +0 -7
  208. package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
  209. package/dist/rules/prefer-schema-inference.js +0 -86
  210. package/dist/rules/prefer-schema-inference.js.map +0 -1
  211. package/dist/rules/scan.d.ts +0 -8
  212. package/dist/rules/scan.d.ts.map +0 -1
  213. package/dist/rules/scan.js +0 -32
  214. package/dist/rules/scan.js.map +0 -1
  215. package/dist/rules/specs.d.ts +0 -29
  216. package/dist/rules/specs.d.ts.map +0 -1
  217. package/dist/rules/specs.js +0 -192
  218. package/dist/rules/specs.js.map +0 -1
  219. package/dist/rules/structure.d.ts +0 -13
  220. package/dist/rules/structure.d.ts.map +0 -1
  221. package/dist/rules/structure.js +0 -142
  222. package/dist/rules/structure.js.map +0 -1
  223. package/dist/rules/types.d.ts +0 -52
  224. package/dist/rules/types.d.ts.map +0 -1
  225. package/dist/rules/types.js +0 -2
  226. package/dist/rules/types.js.map +0 -1
  227. package/dist/rules/valid-describe-refs.d.ts +0 -7
  228. package/dist/rules/valid-describe-refs.d.ts.map +0 -1
  229. package/dist/rules/valid-describe-refs.js +0 -51
  230. package/dist/rules/valid-describe-refs.js.map +0 -1
  231. package/dist/rules/valid-detour-refs.d.ts +0 -6
  232. package/dist/rules/valid-detour-refs.d.ts.map +0 -1
  233. package/dist/rules/valid-detour-refs.js +0 -116
  234. package/dist/rules/valid-detour-refs.js.map +0 -1
  235. package/src/__tests__/cli.test.ts +0 -198
  236. package/src/__tests__/drift.test.ts +0 -74
  237. package/src/__tests__/formatters.test.ts +0 -157
  238. package/src/__tests__/implementation-returns-result.test.ts +0 -75
  239. package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
  240. package/src/__tests__/no-sync-result-assumption.test.ts +0 -85
  241. package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
  242. package/src/__tests__/prefer-schema-inference.test.ts +0 -84
  243. package/src/__tests__/rules.test.ts +0 -188
  244. package/src/__tests__/valid-describe-refs.test.ts +0 -60
  245. package/src/rules/no-direct-impl-in-route.ts +0 -77
  246. package/src/rules/no-throw-in-detour-target.ts +0 -150
  247. package/src/rules/valid-detour-refs.ts +0 -187
  248. package/tsconfig.json +0 -9
  249. package/tsconfig.tsbuildinfo +0 -1
@@ -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
+ });
@@ -0,0 +1,109 @@
1
+ import { resolvedImportBoundary } from '../rules/resolved-import-boundary.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const resolvedImportBoundaryTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: {
8
+ diagnostics: [
9
+ {
10
+ filePath: 'packages/app/src/index.ts',
11
+ line: 1,
12
+ message:
13
+ 'Import "@fixture/core/internal/secret" is not exported by @fixture/core. Import the package root or an exported subpath instead.',
14
+ rule: 'resolved-import-boundary',
15
+ severity: 'error',
16
+ },
17
+ ],
18
+ },
19
+ input: {
20
+ filePath: 'packages/app/src/index.ts',
21
+ importResolutionsByFile: {
22
+ 'packages/app/src/index.ts': [
23
+ {
24
+ crossesPackageBoundary: true,
25
+ errorKind: 'package-path-not-exported',
26
+ importSource: '@fixture/core/internal/secret',
27
+ importerPath: 'packages/app/src/index.ts',
28
+ isInternalTarget: false,
29
+ line: 1,
30
+ packageName: '@fixture/core',
31
+ usesPublicExport: false,
32
+ },
33
+ ],
34
+ },
35
+ knownTrailIds: [],
36
+ sourceCode: "import { secret } from '@fixture/core/internal/secret';\n",
37
+ },
38
+ name: 'Compose-package imports must use exported package subpaths',
39
+ },
40
+ {
41
+ expected: {
42
+ diagnostics: [
43
+ {
44
+ filePath: 'packages/app/src/index.ts',
45
+ line: 1,
46
+ message:
47
+ 'Local import "../../core/src/public" composes into @fixture/core. Import the target package public surface instead.',
48
+ rule: 'resolved-import-boundary',
49
+ severity: 'error',
50
+ },
51
+ ],
52
+ },
53
+ input: {
54
+ filePath: 'packages/app/src/index.ts',
55
+ importResolutionsByFile: {
56
+ 'packages/app/src/index.ts': [
57
+ {
58
+ crossesPackageBoundary: true,
59
+ importSource: '../../core/src/public',
60
+ importerPath: 'packages/app/src/index.ts',
61
+ isInternalTarget: false,
62
+ line: 1,
63
+ packageName: '@fixture/core',
64
+ usesPublicExport: false,
65
+ },
66
+ ],
67
+ },
68
+ knownTrailIds: [],
69
+ sourceCode: "import { pub } from '../../core/src/public';\n",
70
+ },
71
+ name: 'Relative imports must not compose package boundaries',
72
+ },
73
+ {
74
+ expected: {
75
+ diagnostics: [
76
+ {
77
+ filePath: 'packages/app/src/index.ts',
78
+ line: 1,
79
+ message:
80
+ 'Import "../../core/src/internal/secret" targets internal/private files in @fixture/core. Import the target package public surface instead.',
81
+ rule: 'resolved-import-boundary',
82
+ severity: 'error',
83
+ },
84
+ ],
85
+ },
86
+ input: {
87
+ filePath: 'packages/app/src/index.ts',
88
+ importResolutionsByFile: {
89
+ 'packages/app/src/index.ts': [
90
+ {
91
+ crossesPackageBoundary: true,
92
+ importSource: '../../core/src/internal/secret',
93
+ importerPath: 'packages/app/src/index.ts',
94
+ isInternalTarget: true,
95
+ line: 1,
96
+ packageName: '@fixture/core',
97
+ usesPublicExport: false,
98
+ },
99
+ ],
100
+ },
101
+ knownTrailIds: [],
102
+ sourceCode:
103
+ "import { secret } from '../../core/src/internal/secret';\n",
104
+ },
105
+ name: 'Compose-package imports must not target internals',
106
+ },
107
+ ],
108
+ rule: resolvedImportBoundary,
109
+ });
@@ -0,0 +1,25 @@
1
+ import { resourceDeclarations } from '../rules/resource-declarations.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const resourceDeclarationsTrail = 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: 'Matched resource declarations and usage',
22
+ },
23
+ ],
24
+ rule: resourceDeclarations,
25
+ });
@@ -0,0 +1,27 @@
1
+ import { resourceExists } from '../rules/resource-exists.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const resourceExistsTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'clean.ts',
10
+ knownResourceIds: ['db.main'],
11
+ knownTrailIds: ['entity.show'],
12
+ sourceCode: `const db = resource("db.main", {
13
+ create: () => Result.ok({ source: "factory" }),
14
+ });
15
+
16
+ trail("entity.show", {
17
+ resources: [db],
18
+ blaze: async (_input, ctx) => {
19
+ return Result.ok(db.from(ctx));
20
+ }
21
+ })`,
22
+ },
23
+ name: 'Declared resources resolve to known project resources',
24
+ },
25
+ ],
26
+ rule: resourceExists,
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
+ });