@ontrails/warden 1.0.0-beta.4 → 1.0.0-beta.42

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 (279) hide show
  1. package/CHANGELOG.md +837 -13
  2. package/README.md +133 -26
  3. package/bin/warden.ts +51 -0
  4. package/package.json +27 -6
  5. package/src/adapter-check.ts +136 -0
  6. package/src/cli.ts +1570 -105
  7. package/src/command.ts +986 -0
  8. package/src/config.ts +193 -0
  9. package/src/draft.ts +22 -0
  10. package/src/drift.ts +233 -23
  11. package/src/fix.ts +126 -0
  12. package/src/formatters.ts +78 -13
  13. package/src/guide.ts +245 -0
  14. package/src/index.ts +247 -15
  15. package/src/project-context.ts +446 -0
  16. package/src/project-rules.ts +290 -0
  17. package/src/resolve.ts +531 -0
  18. package/src/rules/activation-orphan.ts +97 -0
  19. package/src/rules/captured-kernel.ts +375 -0
  20. package/src/rules/circular-refs.ts +150 -0
  21. package/src/rules/cli-command-route-coherence.ts +177 -0
  22. package/src/rules/composes-declarations.ts +839 -0
  23. package/src/rules/context-no-surface-types.ts +79 -15
  24. package/src/rules/dead-internal-trail.ts +161 -0
  25. package/src/rules/dead-public-trail.ts +258 -0
  26. package/src/rules/draft-file-marking.ts +155 -0
  27. package/src/rules/draft-visible-debt.ts +82 -0
  28. package/src/rules/duplicate-exported-symbol.ts +211 -0
  29. package/src/rules/duplicate-public-contract.ts +137 -0
  30. package/src/rules/entity-exists.ts +254 -0
  31. package/src/rules/entity-ids.ts +15 -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 +740 -0
  35. package/src/rules/governed-symbol-residue.ts +438 -0
  36. package/src/rules/implementation-returns-result.ts +1409 -166
  37. package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
  38. package/src/rules/incomplete-crud.ts +583 -0
  39. package/src/rules/index.ts +277 -10
  40. package/src/rules/intent-propagation.ts +125 -0
  41. package/src/rules/layer-field-name-drift.ts +102 -0
  42. package/src/rules/library-projection-coherence.ts +100 -0
  43. package/src/rules/metadata.ts +871 -0
  44. package/src/rules/missing-reconcile.ts +97 -0
  45. package/src/rules/missing-visibility.ts +111 -0
  46. package/src/rules/no-destructured-compose.ts +196 -0
  47. package/src/rules/no-dev-permit-in-source.ts +99 -0
  48. package/src/rules/no-direct-implementation-call.ts +12 -7
  49. package/src/rules/no-legacy-cli-alias-export.ts +247 -0
  50. package/src/rules/no-legacy-layer-imports.ts +211 -0
  51. package/src/rules/no-native-error-result.ts +118 -0
  52. package/src/rules/no-redundant-result-error-wrap.ts +384 -0
  53. package/src/rules/no-retired-cross-vocabulary.ts +204 -0
  54. package/src/rules/no-sync-result-assumption.ts +1141 -98
  55. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  56. package/src/rules/no-throw-in-implementation.ts +15 -8
  57. package/src/rules/no-top-level-surface.ts +371 -0
  58. package/src/rules/on-references-exist.ts +194 -0
  59. package/src/rules/orphaned-signal.ts +149 -0
  60. package/src/rules/owner-projection-parity.ts +143 -0
  61. package/src/rules/permit-governance.ts +25 -0
  62. package/src/rules/public-export-example-coverage.ts +573 -0
  63. package/src/rules/public-internal-deep-imports.ts +456 -0
  64. package/src/rules/public-output-schema.ts +29 -0
  65. package/src/rules/public-union-output-discriminants.ts +150 -0
  66. package/src/rules/read-intent-fires.ts +188 -0
  67. package/src/rules/reference-exists.ts +97 -0
  68. package/src/rules/registry-names.ts +167 -0
  69. package/src/rules/resolved-import-boundary.ts +146 -0
  70. package/src/rules/resource-declarations.ts +697 -0
  71. package/src/rules/resource-exists.ts +181 -0
  72. package/src/rules/resource-id-grammar.ts +65 -0
  73. package/src/rules/resource-mock-coverage.ts +115 -0
  74. package/src/rules/retired-vocabulary.ts +633 -0
  75. package/src/rules/scan.ts +38 -25
  76. package/src/rules/scheduled-destroy-intent.ts +44 -0
  77. package/src/rules/signal-graph-coaching.ts +220 -0
  78. package/src/rules/source/composition.ts +165 -0
  79. package/src/rules/source/drafts.ts +164 -0
  80. package/src/rules/source/entities.ts +618 -0
  81. package/src/rules/source/pragmas.ts +45 -0
  82. package/src/rules/source/resources.ts +64 -0
  83. package/src/rules/source/signals.ts +397 -0
  84. package/src/rules/source/stores.ts +310 -0
  85. package/src/rules/specs.ts +9 -5
  86. package/src/rules/static-resource-accessor-preference.ts +653 -0
  87. package/src/rules/surface-overlay-coherence.ts +262 -0
  88. package/src/rules/surface-trailhead-coherence.ts +366 -0
  89. package/src/rules/trail-fork-coaching.ts +625 -0
  90. package/src/rules/trail-versioning-source.ts +1076 -0
  91. package/src/rules/trail-versioning-topo.ts +172 -0
  92. package/src/rules/trailhead-override-divergence.ts +356 -0
  93. package/src/rules/types.ts +354 -8
  94. package/src/rules/unmaterialized-activation-source.ts +85 -0
  95. package/src/rules/unreachable-detour-shadowing.ts +339 -0
  96. package/src/rules/valid-describe-refs.ts +162 -32
  97. package/src/rules/valid-detour-contract.ts +78 -0
  98. package/src/rules/warden-export-symmetry.ts +540 -0
  99. package/src/rules/warden-rules-use-ast.ts +1109 -0
  100. package/src/rules/webhook-route-collision.ts +306 -0
  101. package/src/trails/activation-orphan.trail.ts +84 -0
  102. package/src/trails/captured-kernel.trail.ts +108 -0
  103. package/src/trails/circular-refs.trail.ts +29 -0
  104. package/src/trails/cli-command-route-coherence.trail.ts +47 -0
  105. package/src/trails/composes-declarations.trail.ts +22 -0
  106. package/src/trails/context-no-surface-types.trail.ts +21 -0
  107. package/src/trails/dead-internal-trail.trail.ts +26 -0
  108. package/src/trails/dead-public-trail.trail.ts +31 -0
  109. package/src/trails/deprecation-without-guidance.trail.ts +21 -0
  110. package/src/trails/draft-file-marking.trail.ts +16 -0
  111. package/src/trails/draft-visible-debt.trail.ts +16 -0
  112. package/src/trails/duplicate-exported-symbol.trail.ts +48 -0
  113. package/src/trails/duplicate-public-contract.trail.ts +47 -0
  114. package/src/trails/entity-exists.trail.ts +21 -0
  115. package/src/trails/error-mapping-completeness.trail.ts +30 -0
  116. package/src/trails/example-valid.trail.ts +25 -0
  117. package/src/trails/fires-declarations.trail.ts +23 -0
  118. package/src/trails/fork-without-preserved-implementation.trail.ts +31 -0
  119. package/src/trails/governed-symbol-residue.trail.ts +24 -0
  120. package/src/trails/implementation-returns-result.trail.ts +20 -0
  121. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  122. package/src/trails/incomplete-crud.trail.ts +39 -0
  123. package/src/trails/index.ts +89 -0
  124. package/src/trails/intent-propagation.trail.ts +30 -0
  125. package/src/trails/layer-field-name-drift.trail.ts +39 -0
  126. package/src/trails/library-projection-coherence.trail.ts +43 -0
  127. package/src/trails/marker-schema-unsupported.trail.ts +23 -0
  128. package/src/trails/missing-reconcile.trail.ts +33 -0
  129. package/src/trails/missing-visibility.trail.ts +22 -0
  130. package/src/trails/no-destructured-compose.trail.ts +44 -0
  131. package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
  132. package/src/trails/no-direct-implementation-call.trail.ts +16 -0
  133. package/src/trails/no-legacy-cli-alias-export.trail.ts +41 -0
  134. package/src/trails/no-legacy-layer-imports.trail.ts +41 -0
  135. package/src/trails/no-native-error-result.trail.ts +18 -0
  136. package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
  137. package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
  138. package/src/trails/no-sync-result-assumption.trail.ts +19 -0
  139. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  140. package/src/trails/no-throw-in-implementation.trail.ts +20 -0
  141. package/src/trails/no-top-level-surface.trail.ts +43 -0
  142. package/src/trails/on-references-exist.trail.ts +21 -0
  143. package/src/trails/orphaned-signal.trail.ts +36 -0
  144. package/src/trails/owner-projection-parity.trail.ts +26 -0
  145. package/src/trails/pending-force.trail.ts +21 -0
  146. package/src/trails/permit-governance.trail.ts +51 -0
  147. package/src/trails/prefer-schema-inference.trail.ts +21 -0
  148. package/src/trails/public-export-example-coverage.trail.ts +16 -0
  149. package/src/trails/public-internal-deep-imports.trail.ts +94 -0
  150. package/src/trails/public-output-schema.trail.ts +55 -0
  151. package/src/trails/public-union-output-discriminants.trail.ts +33 -0
  152. package/src/trails/read-intent-fires.trail.ts +20 -0
  153. package/src/trails/reference-exists.trail.ts +25 -0
  154. package/src/trails/resolved-import-boundary.trail.ts +109 -0
  155. package/src/trails/resource-declarations.trail.ts +25 -0
  156. package/src/trails/resource-exists.trail.ts +27 -0
  157. package/src/trails/resource-id-grammar.trail.ts +39 -0
  158. package/src/trails/resource-mock-coverage.trail.ts +40 -0
  159. package/src/trails/run.ts +160 -0
  160. package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
  161. package/src/trails/schema.ts +237 -0
  162. package/src/trails/signal-graph-coaching.trail.ts +77 -0
  163. package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
  164. package/src/trails/surface-overlay-coherence.trail.ts +24 -0
  165. package/src/trails/surface-trailhead-coherence.trail.ts +25 -0
  166. package/src/trails/topo.ts +6 -0
  167. package/src/trails/trail-fork-coaching.trail.ts +42 -0
  168. package/src/trails/trailhead-override-divergence.trail.ts +47 -0
  169. package/src/trails/unmaterialized-activation-source.trail.ts +72 -0
  170. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  171. package/src/trails/valid-describe-refs.trail.ts +18 -0
  172. package/src/trails/valid-detour-contract.trail.ts +71 -0
  173. package/src/trails/version-gap.trail.ts +35 -0
  174. package/src/trails/version-pinned-compose.trail.ts +23 -0
  175. package/src/trails/version-without-examples.trail.ts +38 -0
  176. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  177. package/src/trails/warden-rules-use-ast.trail.ts +64 -0
  178. package/src/trails/webhook-route-collision.trail.ts +50 -0
  179. package/src/trails/wrap-rule.ts +224 -0
  180. package/src/workspaces.ts +199 -0
  181. package/.turbo/turbo-build.log +0 -1
  182. package/.turbo/turbo-lint.log +0 -3
  183. package/.turbo/turbo-typecheck.log +0 -1
  184. package/dist/cli.d.ts +0 -46
  185. package/dist/cli.d.ts.map +0 -1
  186. package/dist/cli.js +0 -218
  187. package/dist/cli.js.map +0 -1
  188. package/dist/drift.d.ts +0 -26
  189. package/dist/drift.d.ts.map +0 -1
  190. package/dist/drift.js +0 -27
  191. package/dist/drift.js.map +0 -1
  192. package/dist/formatters.d.ts +0 -29
  193. package/dist/formatters.d.ts.map +0 -1
  194. package/dist/formatters.js +0 -87
  195. package/dist/formatters.js.map +0 -1
  196. package/dist/index.d.ts +0 -26
  197. package/dist/index.d.ts.map +0 -1
  198. package/dist/index.js +0 -26
  199. package/dist/index.js.map +0 -1
  200. package/dist/rules/ast.d.ts +0 -41
  201. package/dist/rules/ast.d.ts.map +0 -1
  202. package/dist/rules/ast.js +0 -161
  203. package/dist/rules/ast.js.map +0 -1
  204. package/dist/rules/context-no-surface-types.d.ts +0 -12
  205. package/dist/rules/context-no-surface-types.d.ts.map +0 -1
  206. package/dist/rules/context-no-surface-types.js +0 -96
  207. package/dist/rules/context-no-surface-types.js.map +0 -1
  208. package/dist/rules/implementation-returns-result.d.ts +0 -13
  209. package/dist/rules/implementation-returns-result.d.ts.map +0 -1
  210. package/dist/rules/implementation-returns-result.js +0 -277
  211. package/dist/rules/implementation-returns-result.js.map +0 -1
  212. package/dist/rules/index.d.ts +0 -15
  213. package/dist/rules/index.d.ts.map +0 -1
  214. package/dist/rules/index.js +0 -34
  215. package/dist/rules/index.js.map +0 -1
  216. package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
  217. package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
  218. package/dist/rules/no-direct-impl-in-route.js +0 -47
  219. package/dist/rules/no-direct-impl-in-route.js.map +0 -1
  220. package/dist/rules/no-direct-implementation-call.d.ts +0 -12
  221. package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
  222. package/dist/rules/no-direct-implementation-call.js +0 -39
  223. package/dist/rules/no-direct-implementation-call.js.map +0 -1
  224. package/dist/rules/no-sync-result-assumption.d.ts +0 -6
  225. package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
  226. package/dist/rules/no-sync-result-assumption.js +0 -98
  227. package/dist/rules/no-sync-result-assumption.js.map +0 -1
  228. package/dist/rules/no-throw-in-detour-target.d.ts +0 -12
  229. package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
  230. package/dist/rules/no-throw-in-detour-target.js +0 -87
  231. package/dist/rules/no-throw-in-detour-target.js.map +0 -1
  232. package/dist/rules/no-throw-in-implementation.d.ts +0 -9
  233. package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
  234. package/dist/rules/no-throw-in-implementation.js +0 -34
  235. package/dist/rules/no-throw-in-implementation.js.map +0 -1
  236. package/dist/rules/prefer-schema-inference.d.ts +0 -7
  237. package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
  238. package/dist/rules/prefer-schema-inference.js +0 -86
  239. package/dist/rules/prefer-schema-inference.js.map +0 -1
  240. package/dist/rules/scan.d.ts +0 -8
  241. package/dist/rules/scan.d.ts.map +0 -1
  242. package/dist/rules/scan.js +0 -32
  243. package/dist/rules/scan.js.map +0 -1
  244. package/dist/rules/specs.d.ts +0 -29
  245. package/dist/rules/specs.d.ts.map +0 -1
  246. package/dist/rules/specs.js +0 -192
  247. package/dist/rules/specs.js.map +0 -1
  248. package/dist/rules/structure.d.ts +0 -13
  249. package/dist/rules/structure.d.ts.map +0 -1
  250. package/dist/rules/structure.js +0 -142
  251. package/dist/rules/structure.js.map +0 -1
  252. package/dist/rules/types.d.ts +0 -52
  253. package/dist/rules/types.d.ts.map +0 -1
  254. package/dist/rules/types.js +0 -2
  255. package/dist/rules/types.js.map +0 -1
  256. package/dist/rules/valid-describe-refs.d.ts +0 -7
  257. package/dist/rules/valid-describe-refs.d.ts.map +0 -1
  258. package/dist/rules/valid-describe-refs.js +0 -51
  259. package/dist/rules/valid-describe-refs.js.map +0 -1
  260. package/dist/rules/valid-detour-refs.d.ts +0 -6
  261. package/dist/rules/valid-detour-refs.d.ts.map +0 -1
  262. package/dist/rules/valid-detour-refs.js +0 -116
  263. package/dist/rules/valid-detour-refs.js.map +0 -1
  264. package/src/__tests__/cli.test.ts +0 -198
  265. package/src/__tests__/drift.test.ts +0 -74
  266. package/src/__tests__/formatters.test.ts +0 -157
  267. package/src/__tests__/implementation-returns-result.test.ts +0 -129
  268. package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
  269. package/src/__tests__/no-sync-result-assumption.test.ts +0 -85
  270. package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
  271. package/src/__tests__/prefer-schema-inference.test.ts +0 -84
  272. package/src/__tests__/rules.test.ts +0 -227
  273. package/src/__tests__/valid-describe-refs.test.ts +0 -60
  274. package/src/rules/ast.ts +0 -213
  275. package/src/rules/no-direct-impl-in-route.ts +0 -81
  276. package/src/rules/no-throw-in-detour-target.ts +0 -150
  277. package/src/rules/valid-detour-refs.ts +0 -187
  278. package/tsconfig.json +0 -9
  279. package/tsconfig.tsbuildinfo +0 -1
@@ -1,33 +1,219 @@
1
+ import { activationOrphan } from './activation-orphan.js';
2
+ import { capturedKernel } from './captured-kernel.js';
3
+ import { cliCommandRouteCoherence } from './cli-command-route-coherence.js';
4
+ import { circularRefs } from './circular-refs.js';
5
+ import { entityExists } from './entity-exists.js';
1
6
  import { contextNoSurfaceTypes } from './context-no-surface-types.js';
7
+ import { composesDeclarations } from './composes-declarations.js';
8
+ import { deadInternalTrail } from './dead-internal-trail.js';
9
+ import { deadPublicTrail } from './dead-public-trail.js';
10
+ import { draftFileMarking } from './draft-file-marking.js';
11
+ import { draftVisibleDebt } from './draft-visible-debt.js';
12
+ import { duplicateExportedSymbol } from './duplicate-exported-symbol.js';
13
+ import { duplicatePublicContract } from './duplicate-public-contract.js';
14
+ import { errorMappingCompleteness } from './error-mapping-completeness.js';
15
+ import { exampleValid } from './example-valid.js';
16
+ import { firesDeclarations } from './fires-declarations.js';
17
+ import { governedSymbolResidue } from './governed-symbol-residue.js';
2
18
  import { implementationReturnsResult } from './implementation-returns-result.js';
3
- import { noDirectImplInRoute } from './no-direct-impl-in-route.js';
19
+ import { incompleteAccessorForStandardOp } from './incomplete-accessor-for-standard-op.js';
20
+ import { incompleteCrud } from './incomplete-crud.js';
21
+ import { intentPropagation } from './intent-propagation.js';
22
+ import { layerFieldNameDrift } from './layer-field-name-drift.js';
23
+ import { libraryProjectionCoherence } from './library-projection-coherence.js';
24
+ import { missingVisibility } from './missing-visibility.js';
25
+ import { missingReconcile } from './missing-reconcile.js';
26
+ import { noDevPermitInSource } from './no-dev-permit-in-source.js';
27
+ import { noDestructuredCompose } from './no-destructured-compose.js';
28
+ import { noLegacyCliAliasExport } from './no-legacy-cli-alias-export.js';
29
+ import { noLegacyLayerImports } from './no-legacy-layer-imports.js';
4
30
  import { noDirectImplementationCall } from './no-direct-implementation-call.js';
31
+ import { noNativeErrorResult } from './no-native-error-result.js';
32
+ import { noRedundantResultErrorWrap } from './no-redundant-result-error-wrap.js';
33
+ import { noRetiredCrossVocabulary } from './no-retired-cross-vocabulary.js';
5
34
  import { noSyncResultAssumption } from './no-sync-result-assumption.js';
6
- import { noThrowInDetourTarget } from './no-throw-in-detour-target.js';
35
+ import { noThrowInDetourRecover } from './no-throw-in-detour-recover.js';
7
36
  import { noThrowInImplementation } from './no-throw-in-implementation.js';
37
+ import { noTopLevelSurface } from './no-top-level-surface.js';
38
+ import { onReferencesExist } from './on-references-exist.js';
39
+ import { orphanedSignal } from './orphaned-signal.js';
40
+ import { ownerProjectionParity } from './owner-projection-parity.js';
41
+ import { permitGovernance } from './permit-governance.js';
8
42
  import { preferSchemaInference } from './prefer-schema-inference.js';
9
- import type { WardenRule } from './types.js';
43
+ import { publicExportExampleCoverage } from './public-export-example-coverage.js';
44
+ import { publicInternalDeepImports } from './public-internal-deep-imports.js';
45
+ import { publicOutputSchema } from './public-output-schema.js';
46
+ import { publicUnionOutputDiscriminants } from './public-union-output-discriminants.js';
47
+ import { readIntentFires } from './read-intent-fires.js';
48
+ import { referenceExists } from './reference-exists.js';
49
+ import { resolvedImportBoundary } from './resolved-import-boundary.js';
50
+ import { resourceDeclarations } from './resource-declarations.js';
51
+ import { resourceExists } from './resource-exists.js';
52
+ import { resourceIdGrammar } from './resource-id-grammar.js';
53
+ import { resourceMockCoverage } from './resource-mock-coverage.js';
54
+ import { scheduledDestroyIntent } from './scheduled-destroy-intent.js';
55
+ import { signalGraphCoaching } from './signal-graph-coaching.js';
56
+ import { staticResourceAccessorPreference } from './static-resource-accessor-preference.js';
57
+ import { surfaceOverlayCoherence } from './surface-overlay-coherence.js';
58
+ import { surfaceTrailheadCoherence } from './surface-trailhead-coherence.js';
59
+ import { trailForkCoaching } from './trail-fork-coaching.js';
60
+ import { trailheadOverrideDivergence } from './trailhead-override-divergence.js';
61
+ import {
62
+ forkWithoutPreservedImplementation,
63
+ markerSchemaUnsupported,
64
+ versionPinnedCompose,
65
+ } from './trail-versioning-source.js';
66
+ import {
67
+ deprecationWithoutGuidance,
68
+ pendingForce,
69
+ versionGap,
70
+ versionWithoutExamples,
71
+ } from './trail-versioning-topo.js';
72
+ import type { TopoAwareWardenRule, WardenRule } from './types.js';
73
+ import { unmaterializedActivationSource } from './unmaterialized-activation-source.js';
74
+ import { unreachableDetourShadowing } from './unreachable-detour-shadowing.js';
75
+ import { validDetourContract } from './valid-detour-contract.js';
10
76
  import { validDescribeRefs } from './valid-describe-refs.js';
11
- import { validDetourRefs } from './valid-detour-refs.js';
77
+ import { wardenExportSymmetry } from './warden-export-symmetry.js';
78
+ import { wardenRulesUseAst } from './warden-rules-use-ast.js';
79
+ import { webhookRouteCollision } from './webhook-route-collision.js';
12
80
 
13
81
  export type {
82
+ WardenFix,
83
+ WardenFixCapability,
84
+ WardenFixClass,
85
+ WardenFixEdit,
86
+ WardenFixScanTargets,
87
+ WardenFixSafety,
88
+ WardenGuidance,
89
+ WardenGuidanceLink,
90
+ WardenExportedSymbolDefinition,
91
+ WardenRuleLifecycle,
92
+ WardenRuleLifecycleState,
93
+ WardenRuleConcern,
94
+ WardenRuleMetadata,
95
+ WardenRuleScope,
14
96
  ProjectAwareWardenRule,
15
97
  ProjectContext,
98
+ TopoAwareWardenRule,
16
99
  WardenDiagnostic,
17
100
  WardenRule,
101
+ WardenRuleTier,
18
102
  WardenSeverity,
19
103
  } from './types.js';
20
104
 
105
+ export {
106
+ formatGovernedVocabularyTransitionGuide,
107
+ getGovernedVocabularyTransition,
108
+ governedVocabularyLiteralRenameSchema,
109
+ governedVocabularyPreserveRuleSchema,
110
+ governedVocabularyRegistrySchema,
111
+ governedVocabularyScopeSchema,
112
+ governedVocabularySymbolRenameSchema,
113
+ governedVocabularyTargetSchema,
114
+ governedVocabularyTransitionSchema,
115
+ governedVocabularyTransitionStatuses,
116
+ governedVocabularyTransitions,
117
+ listGovernedVocabularyTransitions,
118
+ requireGovernedVocabularyTransition,
119
+ } from './retired-vocabulary.js';
120
+ export type {
121
+ GovernedVocabularyLiteralRename,
122
+ GovernedVocabularyPreserveRule,
123
+ GovernedVocabularyScope,
124
+ GovernedVocabularySymbolRename,
125
+ GovernedVocabularyTarget,
126
+ GovernedVocabularyTransition,
127
+ GovernedVocabularyTransitionInput,
128
+ } from './retired-vocabulary.js';
129
+ export {
130
+ builtinWardenRuleMetadata,
131
+ getWardenRuleMetadata,
132
+ listWardenRuleMetadata,
133
+ wardenFixClasses,
134
+ wardenFixSafeties,
135
+ wardenRuleConcerns,
136
+ wardenRuleLifecycleStates,
137
+ wardenRuleScopes,
138
+ wardenRuleTiers,
139
+ } from './metadata.js';
140
+ export type { BuiltinWardenRuleName } from './metadata.js';
141
+
142
+ export { activationOrphan } from './activation-orphan.js';
143
+ export { capturedKernel } from './captured-kernel.js';
144
+ export { cliCommandRouteCoherence } from './cli-command-route-coherence.js';
21
145
  export { noThrowInImplementation } from './no-throw-in-implementation.js';
146
+ export { circularRefs } from './circular-refs.js';
147
+ export { entityExists } from './entity-exists.js';
22
148
  export { contextNoSurfaceTypes } from './context-no-surface-types.js';
23
- export { validDetourRefs } from './valid-detour-refs.js';
24
- export { noDirectImplInRoute } from './no-direct-impl-in-route.js';
149
+ export { composesDeclarations } from './composes-declarations.js';
150
+ export { deadInternalTrail } from './dead-internal-trail.js';
151
+ export { deadPublicTrail } from './dead-public-trail.js';
152
+ export { draftFileMarking } from './draft-file-marking.js';
153
+ export { draftVisibleDebt } from './draft-visible-debt.js';
154
+ export { duplicateExportedSymbol } from './duplicate-exported-symbol.js';
155
+ export { duplicatePublicContract } from './duplicate-public-contract.js';
156
+ export { errorMappingCompleteness } from './error-mapping-completeness.js';
157
+ export { exampleValid } from './example-valid.js';
158
+ export { firesDeclarations } from './fires-declarations.js';
159
+ export { governedSymbolResidue } from './governed-symbol-residue.js';
160
+ export { incompleteAccessorForStandardOp } from './incomplete-accessor-for-standard-op.js';
161
+ export { incompleteCrud } from './incomplete-crud.js';
162
+ export { intentPropagation } from './intent-propagation.js';
163
+ export { libraryProjectionCoherence } from './library-projection-coherence.js';
164
+ export { layerFieldNameDrift } from './layer-field-name-drift.js';
165
+ export { missingVisibility } from './missing-visibility.js';
166
+ export { missingReconcile } from './missing-reconcile.js';
167
+ export { onReferencesExist } from './on-references-exist.js';
168
+ export { noDevPermitInSource } from './no-dev-permit-in-source.js';
169
+ export { noDestructuredCompose } from './no-destructured-compose.js';
170
+ export { noLegacyCliAliasExport } from './no-legacy-cli-alias-export.js';
171
+ export { noLegacyLayerImports } from './no-legacy-layer-imports.js';
25
172
  export { noDirectImplementationCall } from './no-direct-implementation-call.js';
173
+ export { noNativeErrorResult } from './no-native-error-result.js';
174
+ export { noRedundantResultErrorWrap } from './no-redundant-result-error-wrap.js';
175
+ export { noRetiredCrossVocabulary } from './no-retired-cross-vocabulary.js';
26
176
  export { noSyncResultAssumption } from './no-sync-result-assumption.js';
27
177
  export { implementationReturnsResult } from './implementation-returns-result.js';
28
- export { noThrowInDetourTarget } from './no-throw-in-detour-target.js';
178
+ export { noThrowInDetourRecover } from './no-throw-in-detour-recover.js';
179
+ export { noTopLevelSurface } from './no-top-level-surface.js';
180
+ export { orphanedSignal } from './orphaned-signal.js';
181
+ export { ownerProjectionParity } from './owner-projection-parity.js';
182
+ export { permitGovernance } from './permit-governance.js';
29
183
  export { preferSchemaInference } from './prefer-schema-inference.js';
184
+ export { publicInternalDeepImports } from './public-internal-deep-imports.js';
185
+ export { publicOutputSchema } from './public-output-schema.js';
186
+ export { publicUnionOutputDiscriminants } from './public-union-output-discriminants.js';
187
+ export { readIntentFires } from './read-intent-fires.js';
188
+ export { referenceExists } from './reference-exists.js';
189
+ export { resolvedImportBoundary } from './resolved-import-boundary.js';
190
+ export { resourceDeclarations } from './resource-declarations.js';
191
+ export { resourceExists } from './resource-exists.js';
192
+ export { resourceIdGrammar } from './resource-id-grammar.js';
193
+ export { resourceMockCoverage } from './resource-mock-coverage.js';
194
+ export { scheduledDestroyIntent } from './scheduled-destroy-intent.js';
195
+ export { signalGraphCoaching } from './signal-graph-coaching.js';
196
+ export { staticResourceAccessorPreference } from './static-resource-accessor-preference.js';
197
+ export { surfaceOverlayCoherence } from './surface-overlay-coherence.js';
198
+ export { surfaceTrailheadCoherence } from './surface-trailhead-coherence.js';
199
+ export { trailForkCoaching } from './trail-fork-coaching.js';
200
+ export { trailheadOverrideDivergence } from './trailhead-override-divergence.js';
201
+ export {
202
+ forkWithoutPreservedImplementation,
203
+ markerSchemaUnsupported,
204
+ versionPinnedCompose,
205
+ } from './trail-versioning-source.js';
206
+ export {
207
+ deprecationWithoutGuidance,
208
+ pendingForce,
209
+ versionGap,
210
+ versionWithoutExamples,
211
+ } from './trail-versioning-topo.js';
212
+ export { unmaterializedActivationSource } from './unmaterialized-activation-source.js';
213
+ export { unreachableDetourShadowing } from './unreachable-detour-shadowing.js';
214
+ export { validDetourContract } from './valid-detour-contract.js';
30
215
  export { validDescribeRefs } from './valid-describe-refs.js';
216
+ export { webhookRouteCollision } from './webhook-route-collision.js';
31
217
 
32
218
  /** All built-in warden rules, keyed by rule name. */
33
219
  export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
@@ -35,13 +221,94 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
35
221
  WardenRule
36
222
  >([
37
223
  [noThrowInImplementation.name, noThrowInImplementation],
224
+ [capturedKernel.name, capturedKernel],
225
+ [circularRefs.name, circularRefs],
226
+ [entityExists.name, entityExists],
38
227
  [contextNoSurfaceTypes.name, contextNoSurfaceTypes],
228
+ [composesDeclarations.name, composesDeclarations],
229
+ [deadInternalTrail.name, deadInternalTrail],
230
+ [deadPublicTrail.name, deadPublicTrail],
231
+ [draftFileMarking.name, draftFileMarking],
232
+ [draftVisibleDebt.name, draftVisibleDebt],
233
+ [duplicateExportedSymbol.name, duplicateExportedSymbol],
234
+ [errorMappingCompleteness.name, errorMappingCompleteness],
235
+ [exampleValid.name, exampleValid],
236
+ [firesDeclarations.name, firesDeclarations],
237
+ [governedSymbolResidue.name, governedSymbolResidue],
238
+ [incompleteCrud.name, incompleteCrud],
239
+ [intentPropagation.name, intentPropagation],
240
+ [layerFieldNameDrift.name, layerFieldNameDrift],
241
+ [missingVisibility.name, missingVisibility],
242
+ [missingReconcile.name, missingReconcile],
243
+ [onReferencesExist.name, onReferencesExist],
244
+ [orphanedSignal.name, orphanedSignal],
245
+ [ownerProjectionParity.name, ownerProjectionParity],
246
+ [publicExportExampleCoverage.name, publicExportExampleCoverage],
247
+ [publicInternalDeepImports.name, publicInternalDeepImports],
248
+ [resourceDeclarations.name, resourceDeclarations],
249
+ [readIntentFires.name, readIntentFires],
250
+ [referenceExists.name, referenceExists],
251
+ [resolvedImportBoundary.name, resolvedImportBoundary],
252
+ [resourceIdGrammar.name, resourceIdGrammar],
253
+ [resourceExists.name, resourceExists],
254
+ [resourceMockCoverage.name, resourceMockCoverage],
39
255
  [preferSchemaInference.name, preferSchemaInference],
256
+ [staticResourceAccessorPreference.name, staticResourceAccessorPreference],
257
+ [surfaceTrailheadCoherence.name, surfaceTrailheadCoherence],
258
+ [trailForkCoaching.name, trailForkCoaching],
259
+ [trailheadOverrideDivergence.name, trailheadOverrideDivergence],
40
260
  [validDescribeRefs.name, validDescribeRefs],
41
- [validDetourRefs.name, validDetourRefs],
261
+ [noDevPermitInSource.name, noDevPermitInSource],
262
+ [noDestructuredCompose.name, noDestructuredCompose],
42
263
  [noDirectImplementationCall.name, noDirectImplementationCall],
264
+ [noLegacyCliAliasExport.name, noLegacyCliAliasExport],
265
+ [noLegacyLayerImports.name, noLegacyLayerImports],
266
+ [noNativeErrorResult.name, noNativeErrorResult],
267
+ [noRedundantResultErrorWrap.name, noRedundantResultErrorWrap],
268
+ [noRetiredCrossVocabulary.name, noRetiredCrossVocabulary],
43
269
  [noSyncResultAssumption.name, noSyncResultAssumption],
44
270
  [implementationReturnsResult.name, implementationReturnsResult],
45
- [noThrowInDetourTarget.name, noThrowInDetourTarget],
46
- [noDirectImplInRoute.name, noDirectImplInRoute],
271
+ [noThrowInDetourRecover.name, noThrowInDetourRecover],
272
+ [noTopLevelSurface.name, noTopLevelSurface],
273
+ [unreachableDetourShadowing.name, unreachableDetourShadowing],
274
+ [wardenExportSymmetry.name, wardenExportSymmetry],
275
+ [wardenRulesUseAst.name, wardenRulesUseAst],
276
+ [forkWithoutPreservedImplementation.name, forkWithoutPreservedImplementation],
277
+ [markerSchemaUnsupported.name, markerSchemaUnsupported],
278
+ [versionPinnedCompose.name, versionPinnedCompose],
47
279
  ]);
280
+
281
+ /**
282
+ * Built-in topo-aware warden rules, keyed by rule name.
283
+ *
284
+ * These rules inspect the compiled runtime trail graph once per topo,
285
+ * rather than scanning source files. Kept in a separate registry because
286
+ * their dispatch shape differs from `WardenRule` / `ProjectAwareWardenRule`.
287
+ *
288
+ * @remarks
289
+ * Topo-aware rules only fire when the warden runtime is invoked with a
290
+ * resolved `Topo` (see `WardenOptions.topo`). Runs without a topo — e.g.
291
+ * pure source-directory lints — silently skip this registry. Rules
292
+ * registered here must tolerate non-execution when no topo is available.
293
+ */
294
+ export const wardenTopoRules: ReadonlyMap<string, TopoAwareWardenRule> =
295
+ new Map<string, TopoAwareWardenRule>([
296
+ [activationOrphan.name, activationOrphan],
297
+ [cliCommandRouteCoherence.name, cliCommandRouteCoherence],
298
+ [duplicatePublicContract.name, duplicatePublicContract],
299
+ [incompleteAccessorForStandardOp.name, incompleteAccessorForStandardOp],
300
+ [libraryProjectionCoherence.name, libraryProjectionCoherence],
301
+ [permitGovernance.name, permitGovernance],
302
+ [publicOutputSchema.name, publicOutputSchema],
303
+ [publicUnionOutputDiscriminants.name, publicUnionOutputDiscriminants],
304
+ [scheduledDestroyIntent.name, scheduledDestroyIntent],
305
+ [signalGraphCoaching.name, signalGraphCoaching],
306
+ [surfaceOverlayCoherence.name, surfaceOverlayCoherence],
307
+ [unmaterializedActivationSource.name, unmaterializedActivationSource],
308
+ [validDetourContract.name, validDetourContract],
309
+ [deprecationWithoutGuidance.name, deprecationWithoutGuidance],
310
+ [pendingForce.name, pendingForce],
311
+ [versionGap.name, versionGap],
312
+ [versionWithoutExamples.name, versionWithoutExamples],
313
+ [webhookRouteCollision.name, webhookRouteCollision],
314
+ ]);
@@ -0,0 +1,125 @@
1
+ import type { Intent } from '@ontrails/core';
2
+ import {
3
+ collectNamedTrailIds,
4
+ collectTrailIntentsById,
5
+ extractDefinitionComposeTargetIds,
6
+ } from './source/composition.js';
7
+ import { findTrailDefinitions, offsetToLine, parse } from '@ontrails/source';
8
+ import type { AstNode } from '@ontrails/source';
9
+ import { isTestFile } from './scan.js';
10
+ import type {
11
+ ProjectAwareWardenRule,
12
+ ProjectContext,
13
+ WardenDiagnostic,
14
+ } from './types.js';
15
+
16
+ const buildIntentPropagationDiagnostic = (
17
+ trailId: string,
18
+ targetTrailId: string,
19
+ targetIntent: Exclude<Intent, 'read'>,
20
+ filePath: string,
21
+ line: number
22
+ ): WardenDiagnostic => ({
23
+ filePath,
24
+ line,
25
+ message: `Trail "${trailId}" declares intent: 'read' but composes "${targetTrailId}" with intent: '${targetIntent}'. Read trails must not compose write or destroy side effects.`,
26
+ rule: 'intent-propagation',
27
+ severity: 'warn',
28
+ });
29
+
30
+ const buildDiagnosticsForComposeTargets = (
31
+ trailId: string,
32
+ targetTrailIds: readonly string[],
33
+ filePath: string,
34
+ line: number,
35
+ trailIntentsById: ReadonlyMap<string, Intent>
36
+ ): readonly WardenDiagnostic[] =>
37
+ targetTrailIds.flatMap((targetTrailId) => {
38
+ const targetIntent = trailIntentsById.get(targetTrailId);
39
+ if (!targetIntent || targetIntent === 'read') {
40
+ return [];
41
+ }
42
+
43
+ return [
44
+ buildIntentPropagationDiagnostic(
45
+ trailId,
46
+ targetTrailId,
47
+ targetIntent,
48
+ filePath,
49
+ line
50
+ ),
51
+ ];
52
+ });
53
+
54
+ const buildDiagnosticsForTrail = (
55
+ def: ReturnType<typeof findTrailDefinitions>[number],
56
+ sourceCode: string,
57
+ filePath: string,
58
+ namedTrailIds: ReadonlyMap<string, string>,
59
+ trailIntentsById: ReadonlyMap<string, Intent>
60
+ ): readonly WardenDiagnostic[] => {
61
+ if (def.kind !== 'trail' || trailIntentsById.get(def.id) !== 'read') {
62
+ return [];
63
+ }
64
+
65
+ return buildDiagnosticsForComposeTargets(
66
+ def.id,
67
+ extractDefinitionComposeTargetIds(def.config, sourceCode, namedTrailIds),
68
+ filePath,
69
+ offsetToLine(sourceCode, def.start),
70
+ trailIntentsById
71
+ );
72
+ };
73
+
74
+ const checkIntentPropagation = (
75
+ ast: AstNode | null,
76
+ sourceCode: string,
77
+ filePath: string,
78
+ trailIntentsById: ReadonlyMap<string, Intent>
79
+ ): readonly WardenDiagnostic[] => {
80
+ if (isTestFile(filePath) || !ast) {
81
+ return [];
82
+ }
83
+
84
+ const namedTrailIds = collectNamedTrailIds(ast);
85
+ return findTrailDefinitions(ast).flatMap((def) =>
86
+ buildDiagnosticsForTrail(
87
+ def,
88
+ sourceCode,
89
+ filePath,
90
+ namedTrailIds,
91
+ trailIntentsById
92
+ )
93
+ );
94
+ };
95
+
96
+ export const intentPropagation: ProjectAwareWardenRule = {
97
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
98
+ const ast = parse(filePath, sourceCode);
99
+ return checkIntentPropagation(
100
+ ast,
101
+ sourceCode,
102
+ filePath,
103
+ ast ? collectTrailIntentsById(ast) : new Map<string, Intent>()
104
+ );
105
+ },
106
+ checkWithContext(
107
+ sourceCode: string,
108
+ filePath: string,
109
+ context: ProjectContext
110
+ ): readonly WardenDiagnostic[] {
111
+ const ast = parse(filePath, sourceCode);
112
+ const localTrailIntentsById = ast
113
+ ? collectTrailIntentsById(ast)
114
+ : new Map<string, Intent>();
115
+ const trailIntentsById = new Map(localTrailIntentsById);
116
+ for (const [trailId, intent] of context.trailIntentsById ?? []) {
117
+ trailIntentsById.set(trailId, intent);
118
+ }
119
+ return checkIntentPropagation(ast, sourceCode, filePath, trailIntentsById);
120
+ },
121
+ description:
122
+ "Warn when a trail declaring intent: 'read' composes a trail whose normalized intent is write or destroy.",
123
+ name: 'intent-propagation',
124
+ severity: 'warn',
125
+ };
@@ -0,0 +1,102 @@
1
+ import { sep } from 'node:path';
2
+
3
+ import {
4
+ getNodeCallee,
5
+ getNodeId,
6
+ getNodeInit,
7
+ identifierName,
8
+ offsetToLine,
9
+ parse,
10
+ walk,
11
+ } from '@ontrails/source';
12
+ import type { AstNode } from '@ontrails/source';
13
+ import type { WardenDiagnostic, WardenRule } from './types.js';
14
+
15
+ const RULE_NAME = 'layer-field-name-drift';
16
+
17
+ const LEGACY_RESERVED_SET_NAMES = new Set(['META_FLAG_CANDIDATES']);
18
+
19
+ const normalizePath = (filePath: string): string =>
20
+ filePath.split(sep).join('/');
21
+
22
+ const isSurfaceSourceFile = (filePath: string): boolean => {
23
+ const normalized = normalizePath(filePath);
24
+ return /(^|\/)packages\/(cli|http|mcp)\/src\//.test(normalized);
25
+ };
26
+
27
+ const isTestFile = (filePath: string): boolean => {
28
+ const normalized = normalizePath(filePath);
29
+ return (
30
+ normalized.includes('/__tests__/') || /\.test\.[cm]?tsx?$/.test(normalized)
31
+ );
32
+ };
33
+
34
+ const looksLikeLayerReservedNameSet = (name: string): boolean => {
35
+ const lower = name.toLowerCase();
36
+ return (
37
+ LEGACY_RESERVED_SET_NAMES.has(name) ||
38
+ (lower.includes('layer') &&
39
+ lower.includes('reserved') &&
40
+ (lower.includes('name') || lower.includes('flag')))
41
+ );
42
+ };
43
+
44
+ const isSetConstruction = (node: AstNode | undefined): boolean => {
45
+ if (node?.type !== 'NewExpression') {
46
+ return false;
47
+ }
48
+ const callee = getNodeCallee(node);
49
+ return identifierName(callee) === 'Set';
50
+ };
51
+
52
+ const isLocalReservedSetInitializer = (node: AstNode | undefined): boolean =>
53
+ node?.type === 'ArrayExpression' || isSetConstruction(node);
54
+
55
+ const buildDiagnostic = (
56
+ sourceCode: string,
57
+ filePath: string,
58
+ node: AstNode,
59
+ name: string
60
+ ): WardenDiagnostic => ({
61
+ filePath,
62
+ line: offsetToLine(sourceCode, node.start),
63
+ message: `layer-field-name-drift: surface-local reserved name set "${name}" can make layer input fields project differently across surfaces. Import LAYER_FIELD_RESERVED_NAMES from @ontrails/core instead.`,
64
+ rule: RULE_NAME,
65
+ severity: 'error',
66
+ });
67
+
68
+ export const layerFieldNameDrift: WardenRule = {
69
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
70
+ if (!isSurfaceSourceFile(filePath) || isTestFile(filePath)) {
71
+ return [];
72
+ }
73
+
74
+ const ast = parse(filePath, sourceCode);
75
+ if (!ast) {
76
+ return [];
77
+ }
78
+
79
+ const diagnostics: WardenDiagnostic[] = [];
80
+ walk(ast, (node) => {
81
+ if (node.type !== 'VariableDeclarator') {
82
+ return;
83
+ }
84
+ const id = getNodeId(node);
85
+ const init = getNodeInit(node);
86
+ const name = identifierName(id);
87
+ if (
88
+ name &&
89
+ looksLikeLayerReservedNameSet(name) &&
90
+ isLocalReservedSetInitializer(init)
91
+ ) {
92
+ diagnostics.push(buildDiagnostic(sourceCode, filePath, node, name));
93
+ }
94
+ });
95
+
96
+ return diagnostics;
97
+ },
98
+ description:
99
+ 'Prevent surface-local reserved-name sets from drifting layer input field projection across surfaces.',
100
+ name: RULE_NAME,
101
+ severity: 'error',
102
+ };
@@ -0,0 +1,100 @@
1
+ import { deriveTopoGraph } from '@ontrails/topography';
2
+ import type {
3
+ TopoGraph,
4
+ TopoGraphLibraryProjection,
5
+ } from '@ontrails/topography';
6
+
7
+ import type { TopoAwareWardenRule, WardenDiagnostic } from './types.js';
8
+
9
+ const RULE_NAME = 'library-projection-coherence';
10
+ const TOPO_FILE = '<topo>';
11
+
12
+ const resolveGraph = (
13
+ topo: Parameters<TopoAwareWardenRule['checkTopo']>[0],
14
+ graph: TopoGraph | undefined
15
+ ): TopoGraph => graph ?? deriveTopoGraph(topo);
16
+
17
+ const renderTrailIds = (trailIds: readonly string[]): string =>
18
+ trailIds
19
+ .toSorted((left, right) => left.localeCompare(right))
20
+ .map((trailId) => `"${trailId}"`)
21
+ .join(', ');
22
+
23
+ const collisionDiagnostic = (
24
+ exportName: string,
25
+ trailIds: readonly string[]
26
+ ): WardenDiagnostic => ({
27
+ filePath: TOPO_FILE,
28
+ line: 1,
29
+ message: `Library projection export collision on "${exportName}": trails ${renderTrailIds(trailIds)} derive the same package export. Rename one trail or add a library export override before materializing the generated package.`,
30
+ rule: RULE_NAME,
31
+ severity: 'error',
32
+ });
33
+
34
+ const missingTargetDiagnostic = (
35
+ exportName: string,
36
+ trailId: string
37
+ ): WardenDiagnostic => ({
38
+ filePath: TOPO_FILE,
39
+ line: 1,
40
+ message: `Library projection export "${exportName}" targets unknown trail "${trailId}". Resolved library exports must stay attached to existing trail contracts.`,
41
+ rule: RULE_NAME,
42
+ severity: 'error',
43
+ });
44
+
45
+ const duplicateExportDiagnostic = (
46
+ exportName: string,
47
+ trailIds: readonly string[]
48
+ ): WardenDiagnostic => ({
49
+ filePath: TOPO_FILE,
50
+ line: 1,
51
+ message: `Library projection contains duplicate export "${exportName}" for trails ${renderTrailIds(trailIds)}. The resolved projection should record the collision and keep only one emitted export.`,
52
+ rule: RULE_NAME,
53
+ severity: 'error',
54
+ });
55
+
56
+ const collectDuplicateExportDiagnostics = (
57
+ projection: TopoGraphLibraryProjection
58
+ ): readonly WardenDiagnostic[] => {
59
+ const trailIdsByExport = new Map<string, string[]>();
60
+ for (const entry of projection.exports) {
61
+ const trailIds = trailIdsByExport.get(entry.exportName) ?? [];
62
+ trailIds.push(entry.trailId);
63
+ trailIdsByExport.set(entry.exportName, trailIds);
64
+ }
65
+ return [...trailIdsByExport.entries()]
66
+ .filter(([, trailIds]) => trailIds.length > 1)
67
+ .map(([exportName, trailIds]) =>
68
+ duplicateExportDiagnostic(exportName, trailIds)
69
+ );
70
+ };
71
+
72
+ const collectMissingTargetDiagnostics = (
73
+ projection: TopoGraphLibraryProjection,
74
+ knownTrailIds: ReadonlySet<string>
75
+ ): readonly WardenDiagnostic[] =>
76
+ projection.exports
77
+ .filter((entry) => !knownTrailIds.has(entry.trailId))
78
+ .map((entry) => missingTargetDiagnostic(entry.exportName, entry.trailId));
79
+
80
+ export const libraryProjectionCoherence: TopoAwareWardenRule = {
81
+ checkTopo(topo, context) {
82
+ const graph = resolveGraph(topo, context?.graph);
83
+ if (!graph.library) {
84
+ return [];
85
+ }
86
+
87
+ const knownTrailIds = new Set(topo.trails.keys());
88
+ return [
89
+ ...graph.library.collisions.map((collision) =>
90
+ collisionDiagnostic(collision.exportName, collision.trailIds)
91
+ ),
92
+ ...collectDuplicateExportDiagnostics(graph.library),
93
+ ...collectMissingTargetDiagnostics(graph.library, knownTrailIds),
94
+ ];
95
+ },
96
+ description:
97
+ 'Ensure resolved library projection exports are collision-free and target existing trails.',
98
+ name: RULE_NAME,
99
+ severity: 'error',
100
+ };