@ontrails/warden 0.2.0

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 (188) hide show
  1. package/CHANGELOG.md +1556 -0
  2. package/README.md +200 -0
  3. package/bin/warden.ts +51 -0
  4. package/package.json +51 -0
  5. package/src/adapter-check.ts +136 -0
  6. package/src/cli.ts +2159 -0
  7. package/src/command.ts +1167 -0
  8. package/src/config.ts +193 -0
  9. package/src/draft.ts +22 -0
  10. package/src/drift.ts +260 -0
  11. package/src/fix.ts +126 -0
  12. package/src/formatters.ts +178 -0
  13. package/src/guide.ts +245 -0
  14. package/src/index.ts +295 -0
  15. package/src/project-context.ts +446 -0
  16. package/src/project-rules.ts +290 -0
  17. package/src/regrade-history.ts +188 -0
  18. package/src/regrade-receipt.ts +484 -0
  19. package/src/resolve.ts +531 -0
  20. package/src/rules/activation-orphan.ts +97 -0
  21. package/src/rules/captured-kernel.ts +375 -0
  22. package/src/rules/circular-refs.ts +150 -0
  23. package/src/rules/cli-command-route-coherence.ts +177 -0
  24. package/src/rules/composes-declarations.ts +839 -0
  25. package/src/rules/context-no-surface-types.ts +214 -0
  26. package/src/rules/dead-internal-trail.ts +161 -0
  27. package/src/rules/dead-public-trail.ts +258 -0
  28. package/src/rules/draft-file-marking.ts +155 -0
  29. package/src/rules/draft-visible-debt.ts +83 -0
  30. package/src/rules/duplicate-exported-symbol.ts +172 -0
  31. package/src/rules/duplicate-public-contract.ts +137 -0
  32. package/src/rules/entity-exists.ts +254 -0
  33. package/src/rules/entity-ids.ts +15 -0
  34. package/src/rules/error-mapping-completeness.ts +290 -0
  35. package/src/rules/example-valid.ts +395 -0
  36. package/src/rules/fires-declarations.ts +740 -0
  37. package/src/rules/governed-symbol-residue.ts +535 -0
  38. package/src/rules/governed-vocabulary-permutation-watch.ts +77 -0
  39. package/src/rules/implementation-returns-result.ts +1733 -0
  40. package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
  41. package/src/rules/incomplete-crud.ts +583 -0
  42. package/src/rules/index.ts +328 -0
  43. package/src/rules/intent-propagation.ts +125 -0
  44. package/src/rules/layer-field-name-drift.ts +102 -0
  45. package/src/rules/library-render-coherence.ts +97 -0
  46. package/src/rules/metadata.ts +929 -0
  47. package/src/rules/missing-reconcile.ts +97 -0
  48. package/src/rules/missing-visibility.ts +111 -0
  49. package/src/rules/no-destructured-compose.ts +196 -0
  50. package/src/rules/no-dev-permit-in-source.ts +99 -0
  51. package/src/rules/no-direct-implementation-call.ts +52 -0
  52. package/src/rules/no-legacy-cli-alias-export.ts +247 -0
  53. package/src/rules/no-legacy-layer-imports.ts +211 -0
  54. package/src/rules/no-native-error-result.ts +118 -0
  55. package/src/rules/no-redundant-result-error-wrap.ts +382 -0
  56. package/src/rules/no-retired-cross-vocabulary.ts +203 -0
  57. package/src/rules/no-sync-result-assumption.ts +1199 -0
  58. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  59. package/src/rules/no-throw-in-implementation.ts +48 -0
  60. package/src/rules/no-top-level-surface.ts +371 -0
  61. package/src/rules/on-references-exist.ts +194 -0
  62. package/src/rules/orphaned-signal.ts +149 -0
  63. package/src/rules/owner-render-parity.ts +143 -0
  64. package/src/rules/permit-governance.ts +25 -0
  65. package/src/rules/prefer-schema-inference.ts +141 -0
  66. package/src/rules/public-export-example-coverage.ts +561 -0
  67. package/src/rules/public-internal-deep-imports.ts +454 -0
  68. package/src/rules/public-output-schema.ts +29 -0
  69. package/src/rules/public-union-output-discriminants.ts +150 -0
  70. package/src/rules/read-intent-fires.ts +188 -0
  71. package/src/rules/reference-exists.ts +97 -0
  72. package/src/rules/registry-names.ts +171 -0
  73. package/src/rules/resolved-import-boundary.ts +146 -0
  74. package/src/rules/resource-declarations.ts +697 -0
  75. package/src/rules/resource-exists.ts +181 -0
  76. package/src/rules/resource-id-grammar.ts +65 -0
  77. package/src/rules/resource-mock-coverage.ts +115 -0
  78. package/src/rules/retired-vocabulary.ts +1407 -0
  79. package/src/rules/scan.ts +59 -0
  80. package/src/rules/scheduled-destroy-intent.ts +44 -0
  81. package/src/rules/signal-graph-coaching.ts +220 -0
  82. package/src/rules/source/composition.ts +165 -0
  83. package/src/rules/source/drafts.ts +164 -0
  84. package/src/rules/source/entities.ts +618 -0
  85. package/src/rules/source/pragmas.ts +45 -0
  86. package/src/rules/source/resources.ts +64 -0
  87. package/src/rules/source/signals.ts +397 -0
  88. package/src/rules/source/stores.ts +310 -0
  89. package/src/rules/specs.ts +388 -0
  90. package/src/rules/static-resource-accessor-preference.ts +654 -0
  91. package/src/rules/structure.ts +234 -0
  92. package/src/rules/surface-overlay-coherence.ts +262 -0
  93. package/src/rules/surface-trailhead-coherence.ts +366 -0
  94. package/src/rules/trail-fork-coaching.ts +625 -0
  95. package/src/rules/trail-versioning-source.ts +1076 -0
  96. package/src/rules/trail-versioning-topo.ts +172 -0
  97. package/src/rules/trailhead-override-divergence.ts +356 -0
  98. package/src/rules/types.ts +464 -0
  99. package/src/rules/unmaterialized-activation-source.ts +85 -0
  100. package/src/rules/unreachable-detour-shadowing.ts +343 -0
  101. package/src/rules/valid-describe-refs.ts +224 -0
  102. package/src/rules/valid-detour-contract.ts +78 -0
  103. package/src/rules/warden-export-symmetry.ts +540 -0
  104. package/src/rules/warden-rules-use-ast.ts +1109 -0
  105. package/src/rules/webhook-route-collision.ts +306 -0
  106. package/src/rules/workspace-lock-ownership.ts +34 -0
  107. package/src/trails/activation-orphan.trail.ts +84 -0
  108. package/src/trails/captured-kernel.trail.ts +108 -0
  109. package/src/trails/circular-refs.trail.ts +29 -0
  110. package/src/trails/cli-command-route-coherence.trail.ts +47 -0
  111. package/src/trails/composes-declarations.trail.ts +22 -0
  112. package/src/trails/context-no-surface-types.trail.ts +21 -0
  113. package/src/trails/dead-internal-trail.trail.ts +26 -0
  114. package/src/trails/dead-public-trail.trail.ts +31 -0
  115. package/src/trails/deprecation-without-guidance.trail.ts +21 -0
  116. package/src/trails/draft-file-marking.trail.ts +16 -0
  117. package/src/trails/draft-visible-debt.trail.ts +16 -0
  118. package/src/trails/duplicate-exported-symbol.trail.ts +48 -0
  119. package/src/trails/duplicate-public-contract.trail.ts +47 -0
  120. package/src/trails/entity-exists.trail.ts +21 -0
  121. package/src/trails/error-mapping-completeness.trail.ts +30 -0
  122. package/src/trails/example-valid.trail.ts +25 -0
  123. package/src/trails/fires-declarations.trail.ts +23 -0
  124. package/src/trails/fork-without-preserved-implementation.trail.ts +31 -0
  125. package/src/trails/governed-symbol-residue.trail.ts +24 -0
  126. package/src/trails/governed-vocabulary-permutation-watch.trail.ts +16 -0
  127. package/src/trails/implementation-returns-result.trail.ts +20 -0
  128. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  129. package/src/trails/incomplete-crud.trail.ts +39 -0
  130. package/src/trails/index.ts +91 -0
  131. package/src/trails/intent-propagation.trail.ts +30 -0
  132. package/src/trails/layer-field-name-drift.trail.ts +39 -0
  133. package/src/trails/library-render-coherence.trail.ts +43 -0
  134. package/src/trails/marker-schema-unsupported.trail.ts +23 -0
  135. package/src/trails/missing-reconcile.trail.ts +33 -0
  136. package/src/trails/missing-visibility.trail.ts +22 -0
  137. package/src/trails/no-destructured-compose.trail.ts +44 -0
  138. package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
  139. package/src/trails/no-direct-implementation-call.trail.ts +16 -0
  140. package/src/trails/no-legacy-cli-alias-export.trail.ts +41 -0
  141. package/src/trails/no-legacy-layer-imports.trail.ts +41 -0
  142. package/src/trails/no-native-error-result.trail.ts +18 -0
  143. package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
  144. package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
  145. package/src/trails/no-sync-result-assumption.trail.ts +19 -0
  146. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  147. package/src/trails/no-throw-in-implementation.trail.ts +20 -0
  148. package/src/trails/no-top-level-surface.trail.ts +43 -0
  149. package/src/trails/on-references-exist.trail.ts +21 -0
  150. package/src/trails/orphaned-signal.trail.ts +36 -0
  151. package/src/trails/owner-render-parity.trail.ts +26 -0
  152. package/src/trails/pending-force.trail.ts +21 -0
  153. package/src/trails/permit-governance.trail.ts +51 -0
  154. package/src/trails/prefer-schema-inference.trail.ts +21 -0
  155. package/src/trails/public-export-example-coverage.trail.ts +16 -0
  156. package/src/trails/public-internal-deep-imports.trail.ts +94 -0
  157. package/src/trails/public-output-schema.trail.ts +55 -0
  158. package/src/trails/public-union-output-discriminants.trail.ts +33 -0
  159. package/src/trails/read-intent-fires.trail.ts +20 -0
  160. package/src/trails/reference-exists.trail.ts +25 -0
  161. package/src/trails/resolved-import-boundary.trail.ts +109 -0
  162. package/src/trails/resource-declarations.trail.ts +25 -0
  163. package/src/trails/resource-exists.trail.ts +27 -0
  164. package/src/trails/resource-id-grammar.trail.ts +39 -0
  165. package/src/trails/resource-mock-coverage.trail.ts +40 -0
  166. package/src/trails/run.ts +205 -0
  167. package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
  168. package/src/trails/schema.ts +295 -0
  169. package/src/trails/signal-graph-coaching.trail.ts +77 -0
  170. package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
  171. package/src/trails/surface-overlay-coherence.trail.ts +24 -0
  172. package/src/trails/surface-trailhead-coherence.trail.ts +25 -0
  173. package/src/trails/topo.ts +6 -0
  174. package/src/trails/trail-fork-coaching.trail.ts +42 -0
  175. package/src/trails/trailhead-override-divergence.trail.ts +47 -0
  176. package/src/trails/unmaterialized-activation-source.trail.ts +72 -0
  177. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  178. package/src/trails/valid-describe-refs.trail.ts +18 -0
  179. package/src/trails/valid-detour-contract.trail.ts +71 -0
  180. package/src/trails/version-gap.trail.ts +35 -0
  181. package/src/trails/version-pinned-compose.trail.ts +23 -0
  182. package/src/trails/version-without-examples.trail.ts +38 -0
  183. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  184. package/src/trails/warden-rules-use-ast.trail.ts +64 -0
  185. package/src/trails/webhook-route-collision.trail.ts +50 -0
  186. package/src/trails/workspace-lock-ownership.trail.ts +16 -0
  187. package/src/trails/wrap-rule.ts +269 -0
  188. package/src/workspaces.ts +264 -0
@@ -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
+ implementation: 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
+ implementation: () => 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
+ implementation: () => 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 rendering 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
+ implementation: () => 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
+ implementation: 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: 'entities.ts',
10
+ knownEntityIds: ['gist', 'user'],
11
+ knownTrailIds: [],
12
+ sourceCode: `const user = entity("user", {
13
+ id: z.string().uuid(),
14
+ }, { identity: "id" });
15
+
16
+ const gist = entity("gist", {
17
+ id: z.string().uuid(),
18
+ ownerId: user.id(),
19
+ }, { identity: "id" });`,
20
+ },
21
+ name: 'Entity references resolve to known project entities',
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
+ implementation: 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
+ implementation: 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 ":" — it is reserved as the scope separator. Rename the resource using "." segments instead, e.g. resource("billing.primary", ...).',
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
+ });
@@ -0,0 +1,40 @@
1
+ import { resourceMockCoverage } from '../rules/resource-mock-coverage.js';
2
+ import { wrapRule } from './wrap-rule.js';
3
+
4
+ export const resourceMockCoverageTrail = wrapRule({
5
+ examples: [
6
+ {
7
+ expected: { diagnostics: [] },
8
+ input: {
9
+ filePath: 'clean.ts',
10
+ sourceCode: `const db = resource("db.main", {
11
+ create: () => Result.ok(openDatabase()),
12
+ mock: () => createInMemoryDb(),
13
+ });`,
14
+ },
15
+ name: 'Resource declaring a mock factory passes',
16
+ },
17
+ {
18
+ expected: {
19
+ diagnostics: [
20
+ {
21
+ filePath: 'missing-mock.ts',
22
+ line: 1,
23
+ message:
24
+ 'Resource "db.main" declares no mock factory. Add a mock() so testAll(app) runs without configuration, or declare unmockable: { reason } if it intentionally cannot be mocked.',
25
+ rule: 'resource-mock-coverage',
26
+ severity: 'warn',
27
+ },
28
+ ],
29
+ },
30
+ input: {
31
+ filePath: 'missing-mock.ts',
32
+ sourceCode: `const db = resource("db.main", {
33
+ create: () => Result.ok(openDatabase()),
34
+ });`,
35
+ },
36
+ name: 'Resource without a mock factory or unmockable reason is flagged',
37
+ },
38
+ ],
39
+ rule: resourceMockCoverage,
40
+ });