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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/CHANGELOG.md +674 -9
  2. package/README.md +133 -26
  3. package/bin/warden.ts +51 -0
  4. package/package.json +28 -5
  5. package/src/adapter-check.ts +136 -0
  6. package/src/ast.ts +137 -0
  7. package/src/cli.ts +1451 -104
  8. package/src/command.ts +966 -0
  9. package/src/config.ts +193 -0
  10. package/src/draft.ts +22 -0
  11. package/src/drift.ts +122 -22
  12. package/src/fix.ts +120 -0
  13. package/src/formatters.ts +79 -9
  14. package/src/guide.ts +245 -0
  15. package/src/index.ts +217 -14
  16. package/src/project-context.ts +192 -0
  17. package/src/project-rules.ts +290 -0
  18. package/src/resolve.ts +531 -0
  19. package/src/rules/activation-orphan.ts +97 -0
  20. package/src/rules/ast.ts +4008 -86
  21. package/src/rules/circular-refs.ts +154 -0
  22. package/src/rules/cli-command-route-coherence.ts +177 -0
  23. package/src/rules/composes-declarations.ts +837 -0
  24. package/src/rules/context-no-surface-types.ts +78 -15
  25. package/src/rules/contour-exists.ts +251 -0
  26. package/src/rules/contour-ids.ts +15 -0
  27. package/src/rules/dead-internal-trail.ts +161 -0
  28. package/src/rules/dead-public-trail.ts +258 -0
  29. package/src/rules/draft-file-marking.ts +160 -0
  30. package/src/rules/draft-visible-debt.ts +87 -0
  31. package/src/rules/duplicate-public-contract.ts +91 -0
  32. package/src/rules/error-mapping-completeness.ts +290 -0
  33. package/src/rules/example-valid.ts +395 -0
  34. package/src/rules/fires-declarations.ts +735 -0
  35. package/src/rules/implementation-returns-result.ts +1409 -166
  36. package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
  37. package/src/rules/incomplete-crud.ts +581 -0
  38. package/src/rules/index.ts +235 -18
  39. package/src/rules/intent-propagation.ts +127 -0
  40. package/src/rules/layer-field-name-drift.ts +102 -0
  41. package/src/rules/library-projection-coherence.ts +100 -0
  42. package/src/rules/metadata.ts +755 -0
  43. package/src/rules/missing-reconcile.ts +98 -0
  44. package/src/rules/missing-visibility.ts +110 -0
  45. package/src/rules/no-destructured-compose.ts +194 -0
  46. package/src/rules/no-dev-permit-in-source.ts +99 -0
  47. package/src/rules/no-direct-implementation-call.ts +7 -7
  48. package/src/rules/no-legacy-layer-imports.ts +211 -0
  49. package/src/rules/no-native-error-result.ts +118 -0
  50. package/src/rules/no-redundant-result-error-wrap.ts +384 -0
  51. package/src/rules/no-retired-cross-vocabulary.ts +194 -0
  52. package/src/rules/no-sync-result-assumption.ts +1135 -98
  53. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  54. package/src/rules/no-throw-in-implementation.ts +10 -9
  55. package/src/rules/no-top-level-surface.ts +371 -0
  56. package/src/rules/on-references-exist.ts +194 -0
  57. package/src/rules/orphaned-signal.ts +150 -0
  58. package/src/rules/owner-projection-parity.ts +143 -0
  59. package/src/rules/permit-governance.ts +25 -0
  60. package/src/rules/public-export-example-coverage.ts +562 -0
  61. package/src/rules/public-internal-deep-imports.ts +457 -0
  62. package/src/rules/public-output-schema.ts +29 -0
  63. package/src/rules/public-union-output-discriminants.ts +150 -0
  64. package/src/rules/read-intent-fires.ts +187 -0
  65. package/src/rules/reference-exists.ts +98 -0
  66. package/src/rules/registry-names.ts +155 -0
  67. package/src/rules/resolved-import-boundary.ts +146 -0
  68. package/src/rules/resource-declarations.ts +693 -0
  69. package/src/rules/resource-exists.ts +179 -0
  70. package/src/rules/resource-id-grammar.ts +65 -0
  71. package/src/rules/resource-mock-coverage.ts +115 -0
  72. package/src/rules/scan.ts +38 -25
  73. package/src/rules/scheduled-destroy-intent.ts +44 -0
  74. package/src/rules/signal-graph-coaching.ts +191 -0
  75. package/src/rules/specs.ts +9 -5
  76. package/src/rules/static-resource-accessor-preference.ts +649 -0
  77. package/src/rules/surface-facet-coherence.ts +362 -0
  78. package/src/rules/trail-fork-coaching.ts +616 -0
  79. package/src/rules/trail-versioning-source.ts +1072 -0
  80. package/src/rules/trail-versioning-topo.ts +172 -0
  81. package/src/rules/types.ts +297 -8
  82. package/src/rules/unmaterialized-activation-source.ts +84 -0
  83. package/src/rules/unreachable-detour-shadowing.ts +339 -0
  84. package/src/rules/valid-describe-refs.ts +162 -32
  85. package/src/rules/valid-detour-contract.ts +78 -0
  86. package/src/rules/warden-export-symmetry.ts +540 -0
  87. package/src/rules/warden-rules-use-ast.ts +1114 -0
  88. package/src/rules/webhook-route-collision.ts +243 -0
  89. package/src/trails/activation-orphan.trail.ts +84 -0
  90. package/src/trails/circular-refs.trail.ts +29 -0
  91. package/src/trails/cli-command-route-coherence.trail.ts +47 -0
  92. package/src/trails/composes-declarations.trail.ts +22 -0
  93. package/src/trails/context-no-surface-types.trail.ts +21 -0
  94. package/src/trails/contour-exists.trail.ts +21 -0
  95. package/src/trails/dead-internal-trail.trail.ts +26 -0
  96. package/src/trails/dead-public-trail.trail.ts +31 -0
  97. package/src/trails/deprecation-without-guidance.trail.ts +21 -0
  98. package/src/trails/draft-file-marking.trail.ts +16 -0
  99. package/src/trails/draft-visible-debt.trail.ts +16 -0
  100. package/src/trails/duplicate-public-contract.trail.ts +47 -0
  101. package/src/trails/error-mapping-completeness.trail.ts +30 -0
  102. package/src/trails/example-valid.trail.ts +25 -0
  103. package/src/trails/fires-declarations.trail.ts +23 -0
  104. package/src/trails/fork-without-preserved-blaze.trail.ts +31 -0
  105. package/src/trails/implementation-returns-result.trail.ts +20 -0
  106. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  107. package/src/trails/incomplete-crud.trail.ts +39 -0
  108. package/src/trails/index.ts +83 -0
  109. package/src/trails/intent-propagation.trail.ts +30 -0
  110. package/src/trails/layer-field-name-drift.trail.ts +39 -0
  111. package/src/trails/library-projection-coherence.trail.ts +43 -0
  112. package/src/trails/marker-schema-unsupported.trail.ts +23 -0
  113. package/src/trails/missing-reconcile.trail.ts +33 -0
  114. package/src/trails/missing-visibility.trail.ts +22 -0
  115. package/src/trails/no-destructured-compose.trail.ts +44 -0
  116. package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
  117. package/src/trails/no-direct-implementation-call.trail.ts +16 -0
  118. package/src/trails/no-legacy-layer-imports.trail.ts +41 -0
  119. package/src/trails/no-native-error-result.trail.ts +18 -0
  120. package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
  121. package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
  122. package/src/trails/no-sync-result-assumption.trail.ts +19 -0
  123. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  124. package/src/trails/no-throw-in-implementation.trail.ts +20 -0
  125. package/src/trails/no-top-level-surface.trail.ts +43 -0
  126. package/src/trails/on-references-exist.trail.ts +21 -0
  127. package/src/trails/orphaned-signal.trail.ts +36 -0
  128. package/src/trails/owner-projection-parity.trail.ts +26 -0
  129. package/src/trails/pending-force.trail.ts +21 -0
  130. package/src/trails/permit-governance.trail.ts +51 -0
  131. package/src/trails/prefer-schema-inference.trail.ts +21 -0
  132. package/src/trails/public-export-example-coverage.trail.ts +16 -0
  133. package/src/trails/public-internal-deep-imports.trail.ts +94 -0
  134. package/src/trails/public-output-schema.trail.ts +55 -0
  135. package/src/trails/public-union-output-discriminants.trail.ts +33 -0
  136. package/src/trails/read-intent-fires.trail.ts +20 -0
  137. package/src/trails/reference-exists.trail.ts +25 -0
  138. package/src/trails/resolved-import-boundary.trail.ts +109 -0
  139. package/src/trails/resource-declarations.trail.ts +25 -0
  140. package/src/trails/resource-exists.trail.ts +27 -0
  141. package/src/trails/resource-id-grammar.trail.ts +39 -0
  142. package/src/trails/resource-mock-coverage.trail.ts +40 -0
  143. package/src/trails/run.ts +162 -0
  144. package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
  145. package/src/trails/schema.ts +198 -0
  146. package/src/trails/signal-graph-coaching.trail.ts +77 -0
  147. package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
  148. package/src/trails/surface-facet-coherence.trail.ts +25 -0
  149. package/src/trails/topo.ts +6 -0
  150. package/src/trails/trail-fork-coaching.trail.ts +42 -0
  151. package/src/trails/unmaterialized-activation-source.trail.ts +72 -0
  152. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  153. package/src/trails/valid-describe-refs.trail.ts +18 -0
  154. package/src/trails/valid-detour-contract.trail.ts +71 -0
  155. package/src/trails/version-gap.trail.ts +35 -0
  156. package/src/trails/version-pinned-compose.trail.ts +23 -0
  157. package/src/trails/version-without-examples.trail.ts +38 -0
  158. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  159. package/src/trails/warden-rules-use-ast.trail.ts +64 -0
  160. package/src/trails/webhook-route-collision.trail.ts +50 -0
  161. package/src/trails/wrap-rule.ts +214 -0
  162. package/src/workspaces.ts +199 -0
  163. package/.turbo/turbo-build.log +0 -1
  164. package/.turbo/turbo-lint.log +0 -3
  165. package/.turbo/turbo-typecheck.log +0 -1
  166. package/dist/cli.d.ts +0 -46
  167. package/dist/cli.d.ts.map +0 -1
  168. package/dist/cli.js +0 -221
  169. package/dist/cli.js.map +0 -1
  170. package/dist/drift.d.ts +0 -26
  171. package/dist/drift.d.ts.map +0 -1
  172. package/dist/drift.js +0 -27
  173. package/dist/drift.js.map +0 -1
  174. package/dist/formatters.d.ts +0 -29
  175. package/dist/formatters.d.ts.map +0 -1
  176. package/dist/formatters.js +0 -87
  177. package/dist/formatters.js.map +0 -1
  178. package/dist/index.d.ts +0 -26
  179. package/dist/index.d.ts.map +0 -1
  180. package/dist/index.js +0 -26
  181. package/dist/index.js.map +0 -1
  182. package/dist/rules/ast.d.ts +0 -41
  183. package/dist/rules/ast.d.ts.map +0 -1
  184. package/dist/rules/ast.js +0 -163
  185. package/dist/rules/ast.js.map +0 -1
  186. package/dist/rules/context-no-surface-types.d.ts +0 -12
  187. package/dist/rules/context-no-surface-types.d.ts.map +0 -1
  188. package/dist/rules/context-no-surface-types.js +0 -96
  189. package/dist/rules/context-no-surface-types.js.map +0 -1
  190. package/dist/rules/implementation-returns-result.d.ts +0 -13
  191. package/dist/rules/implementation-returns-result.d.ts.map +0 -1
  192. package/dist/rules/implementation-returns-result.js +0 -277
  193. package/dist/rules/implementation-returns-result.js.map +0 -1
  194. package/dist/rules/index.d.ts +0 -22
  195. package/dist/rules/index.d.ts.map +0 -1
  196. package/dist/rules/index.js +0 -41
  197. package/dist/rules/index.js.map +0 -1
  198. package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
  199. package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
  200. package/dist/rules/no-direct-impl-in-route.js +0 -46
  201. package/dist/rules/no-direct-impl-in-route.js.map +0 -1
  202. package/dist/rules/no-direct-implementation-call.d.ts +0 -12
  203. package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
  204. package/dist/rules/no-direct-implementation-call.js +0 -39
  205. package/dist/rules/no-direct-implementation-call.js.map +0 -1
  206. package/dist/rules/no-sync-result-assumption.d.ts +0 -6
  207. package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
  208. package/dist/rules/no-sync-result-assumption.js +0 -98
  209. package/dist/rules/no-sync-result-assumption.js.map +0 -1
  210. package/dist/rules/no-throw-in-detour-target.d.ts +0 -12
  211. package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
  212. package/dist/rules/no-throw-in-detour-target.js +0 -87
  213. package/dist/rules/no-throw-in-detour-target.js.map +0 -1
  214. package/dist/rules/no-throw-in-implementation.d.ts +0 -9
  215. package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
  216. package/dist/rules/no-throw-in-implementation.js +0 -34
  217. package/dist/rules/no-throw-in-implementation.js.map +0 -1
  218. package/dist/rules/prefer-schema-inference.d.ts +0 -7
  219. package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
  220. package/dist/rules/prefer-schema-inference.js +0 -86
  221. package/dist/rules/prefer-schema-inference.js.map +0 -1
  222. package/dist/rules/scan.d.ts +0 -8
  223. package/dist/rules/scan.d.ts.map +0 -1
  224. package/dist/rules/scan.js +0 -32
  225. package/dist/rules/scan.js.map +0 -1
  226. package/dist/rules/specs.d.ts +0 -29
  227. package/dist/rules/specs.d.ts.map +0 -1
  228. package/dist/rules/specs.js +0 -192
  229. package/dist/rules/specs.js.map +0 -1
  230. package/dist/rules/structure.d.ts +0 -13
  231. package/dist/rules/structure.d.ts.map +0 -1
  232. package/dist/rules/structure.js +0 -142
  233. package/dist/rules/structure.js.map +0 -1
  234. package/dist/rules/types.d.ts +0 -52
  235. package/dist/rules/types.d.ts.map +0 -1
  236. package/dist/rules/types.js +0 -2
  237. package/dist/rules/types.js.map +0 -1
  238. package/dist/rules/valid-describe-refs.d.ts +0 -7
  239. package/dist/rules/valid-describe-refs.d.ts.map +0 -1
  240. package/dist/rules/valid-describe-refs.js +0 -51
  241. package/dist/rules/valid-describe-refs.js.map +0 -1
  242. package/dist/rules/valid-detour-refs.d.ts +0 -6
  243. package/dist/rules/valid-detour-refs.d.ts.map +0 -1
  244. package/dist/rules/valid-detour-refs.js +0 -116
  245. package/dist/rules/valid-detour-refs.js.map +0 -1
  246. package/src/__tests__/cli.test.ts +0 -198
  247. package/src/__tests__/drift.test.ts +0 -74
  248. package/src/__tests__/formatters.test.ts +0 -157
  249. package/src/__tests__/implementation-returns-result.test.ts +0 -128
  250. package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
  251. package/src/__tests__/no-sync-result-assumption.test.ts +0 -85
  252. package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
  253. package/src/__tests__/prefer-schema-inference.test.ts +0 -84
  254. package/src/__tests__/rules.test.ts +0 -215
  255. package/src/__tests__/valid-describe-refs.test.ts +0 -60
  256. package/src/rules/no-direct-impl-in-route.ts +0 -77
  257. package/src/rules/no-throw-in-detour-target.ts +0 -150
  258. package/src/rules/valid-detour-refs.ts +0 -187
  259. package/tsconfig.json +0 -9
  260. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,755 @@
1
+ import type {
2
+ WardenFixClass,
3
+ WardenFixSafety,
4
+ WardenRule,
5
+ WardenRuleConcern,
6
+ WardenRuleLifecycleState,
7
+ WardenRuleMetadata,
8
+ WardenRuleScope,
9
+ WardenRuleTier,
10
+ } from './types.js';
11
+
12
+ export const wardenRuleTiers = [
13
+ 'source-static',
14
+ 'project-static',
15
+ 'topo-aware',
16
+ 'drift',
17
+ 'advisory',
18
+ ] as const satisfies readonly WardenRuleTier[];
19
+
20
+ export const wardenRuleScopes = [
21
+ 'external',
22
+ 'extension',
23
+ 'internal',
24
+ 'repo-local',
25
+ 'temporary',
26
+ 'advisory',
27
+ ] as const satisfies readonly WardenRuleScope[];
28
+
29
+ export const wardenRuleConcerns = [
30
+ 'composition',
31
+ 'general',
32
+ 'lifecycle',
33
+ 'meta',
34
+ 'permits',
35
+ 'resources',
36
+ 'results',
37
+ 'signals',
38
+ ] as const satisfies readonly WardenRuleConcern[];
39
+
40
+ export const wardenRuleLifecycleStates = [
41
+ 'durable',
42
+ 'temporary',
43
+ 'deprecated',
44
+ ] as const satisfies readonly WardenRuleLifecycleState[];
45
+
46
+ export const wardenFixClasses = [
47
+ 'term-rewrite',
48
+ ] as const satisfies readonly WardenFixClass[];
49
+
50
+ export const wardenFixSafeties = [
51
+ 'review',
52
+ 'safe',
53
+ ] as const satisfies readonly WardenFixSafety[];
54
+
55
+ type BuiltinWardenRuleMetadataInput = Omit<
56
+ WardenRuleMetadata,
57
+ 'concern' | 'depth'
58
+ > &
59
+ Partial<Pick<WardenRuleMetadata, 'concern' | 'depth'>>;
60
+
61
+ const depthByTier = {
62
+ advisory: 'all',
63
+ drift: 'all',
64
+ 'project-static': 'project',
65
+ 'source-static': 'source',
66
+ 'topo-aware': 'topo',
67
+ } as const satisfies Record<WardenRuleTier, WardenRuleMetadata['depth']>;
68
+
69
+ const concernByRuleName: Partial<Record<string, WardenRuleConcern>> = {
70
+ 'activation-orphan': 'signals',
71
+ 'cli-command-route-coherence': 'meta',
72
+ 'composes-declarations': 'composition',
73
+ 'context-no-surface-types': 'composition',
74
+ 'dead-internal-trail': 'composition',
75
+ 'dead-public-trail': 'composition',
76
+ 'deprecation-without-guidance': 'lifecycle',
77
+ 'draft-file-marking': 'lifecycle',
78
+ 'draft-visible-debt': 'lifecycle',
79
+ 'duplicate-public-contract': 'meta',
80
+ 'error-mapping-completeness': 'results',
81
+ 'fires-declarations': 'signals',
82
+ 'fork-without-preserved-blaze': 'lifecycle',
83
+ 'implementation-returns-result': 'results',
84
+ 'intent-propagation': 'composition',
85
+ 'library-projection-coherence': 'meta',
86
+ 'marker-schema-unsupported': 'lifecycle',
87
+ 'missing-reconcile': 'resources',
88
+ 'missing-visibility': 'composition',
89
+ 'no-destructured-compose': 'composition',
90
+ 'no-dev-permit-in-source': 'permits',
91
+ 'no-direct-implementation-call': 'composition',
92
+ 'no-native-error-result': 'results',
93
+ 'no-redundant-result-error-wrap': 'results',
94
+ 'no-retired-cross-vocabulary': 'composition',
95
+ 'no-sync-result-assumption': 'results',
96
+ 'no-throw-in-detour-recover': 'results',
97
+ 'no-throw-in-implementation': 'results',
98
+ 'on-references-exist': 'signals',
99
+ 'orphaned-signal': 'signals',
100
+ 'pending-force': 'lifecycle',
101
+ 'permit-governance': 'permits',
102
+ 'public-output-schema': 'results',
103
+ 'read-intent-fires': 'signals',
104
+ 'resolved-import-boundary': 'composition',
105
+ 'resource-declarations': 'resources',
106
+ 'resource-exists': 'resources',
107
+ 'resource-id-grammar': 'resources',
108
+ 'resource-mock-coverage': 'resources',
109
+ 'scheduled-destroy-intent': 'lifecycle',
110
+ 'signal-graph-coaching': 'signals',
111
+ 'static-resource-accessor-preference': 'resources',
112
+ 'surface-facet-coherence': 'meta',
113
+ 'trail-fork-coaching': 'meta',
114
+ 'unmaterialized-activation-source': 'lifecycle',
115
+ 'valid-detour-contract': 'results',
116
+ 'version-gap': 'lifecycle',
117
+ 'version-pinned-compose': 'composition',
118
+ 'version-without-examples': 'lifecycle',
119
+ 'webhook-route-collision': 'composition',
120
+ };
121
+
122
+ const durableExternal = {
123
+ lifecycle: { state: 'durable' },
124
+ scope: 'external',
125
+ } as const;
126
+
127
+ const durableExtension = {
128
+ lifecycle: { state: 'durable' },
129
+ scope: 'extension',
130
+ } as const;
131
+
132
+ const durableRepoLocal = {
133
+ lifecycle: { state: 'durable' },
134
+ scope: 'repo-local',
135
+ } as const;
136
+
137
+ const trailContractDocs = {
138
+ label: 'Trail Rules',
139
+ path: 'AGENTS.md#trail-rules',
140
+ } as const;
141
+
142
+ const wardenDocs = {
143
+ label: 'Warden',
144
+ path: 'docs/warden.md',
145
+ } as const;
146
+
147
+ const builtinWardenRuleMetadataInput = {
148
+ 'activation-orphan': {
149
+ ...durableExternal,
150
+ invariant:
151
+ 'Signal activation consumers reference sources with producer declarations.',
152
+ tier: 'topo-aware',
153
+ },
154
+ 'circular-refs': {
155
+ ...durableExternal,
156
+ invariant: 'Contour reference graphs must be acyclic.',
157
+ tier: 'project-static',
158
+ },
159
+ 'cli-command-route-coherence': {
160
+ ...durableExternal,
161
+ guidance: {
162
+ docs: [
163
+ {
164
+ label: 'CLI command routes ADR',
165
+ path: 'docs/adr/drafts/20260613-cli-command-routes.md',
166
+ },
167
+ ],
168
+ relatedRules: ['webhook-route-collision'],
169
+ summary:
170
+ 'Keep every CLI command route and alias normalized into one trail contract.',
171
+ },
172
+ invariant:
173
+ 'CLI command routes and aliases resolve to one coherent trail contract.',
174
+ tier: 'topo-aware',
175
+ },
176
+ 'composes-declarations': {
177
+ ...durableExternal,
178
+ invariant: 'Declared composes stay aligned with ctx.compose() usage.',
179
+ tier: 'source-static',
180
+ },
181
+ 'context-no-surface-types': {
182
+ ...durableExternal,
183
+ invariant: 'Trail logic stays surface-agnostic.',
184
+ tier: 'source-static',
185
+ },
186
+ 'contour-exists': {
187
+ ...durableExternal,
188
+ invariant: 'Declared contour references resolve to known contours.',
189
+ tier: 'project-static',
190
+ },
191
+ 'dead-internal-trail': {
192
+ ...durableExternal,
193
+ invariant: 'Internal trails should be reachable through declared composes.',
194
+ tier: 'project-static',
195
+ },
196
+ 'dead-public-trail': {
197
+ ...durableExternal,
198
+ guidance: {
199
+ relatedRules: ['dead-internal-trail', 'duplicate-public-contract'],
200
+ summary:
201
+ 'Anchor exported public trails in a topo, composition edge, or activation source.',
202
+ },
203
+ invariant:
204
+ 'Exported public trails are anchored in configured app topos, composition, or activation.',
205
+ tier: 'project-static',
206
+ },
207
+ 'deprecation-without-guidance': {
208
+ ...durableExternal,
209
+ invariant:
210
+ 'Deprecated trail version entries carry successor, migration, or note guidance.',
211
+ tier: 'topo-aware',
212
+ },
213
+ 'draft-file-marking': {
214
+ ...durableExternal,
215
+ invariant: 'Draft-authored state is visibly marked in filenames.',
216
+ tier: 'source-static',
217
+ },
218
+ 'draft-visible-debt': {
219
+ ...durableExternal,
220
+ invariant: 'Draft-authored IDs remain visible debt.',
221
+ tier: 'source-static',
222
+ },
223
+ 'duplicate-public-contract': {
224
+ ...durableExternal,
225
+ guidance: {
226
+ docs: [
227
+ {
228
+ label: 'Surface accommodations',
229
+ path: 'docs/surfaces/surface-accommodations.md',
230
+ },
231
+ ],
232
+ relatedRules: ['cli-command-route-coherence', 'trail-fork-coaching'],
233
+ summary:
234
+ 'Keep duplicate public contract facts from drifting into separate capabilities.',
235
+ },
236
+ invariant:
237
+ 'Public surface trails should not expose duplicate normalized contract facts.',
238
+ tier: 'topo-aware',
239
+ },
240
+ 'error-mapping-completeness': {
241
+ ...durableExtension,
242
+ invariant: 'Registered surface error mappers cover every error category.',
243
+ tier: 'source-static',
244
+ },
245
+ 'example-valid': {
246
+ ...durableExternal,
247
+ guidance: {
248
+ docs: [trailContractDocs],
249
+ steps: [
250
+ 'Update the example input, expected output, or schema so they describe the same contract.',
251
+ 'Run the package tests that exercise the affected trail examples.',
252
+ ],
253
+ summary: 'Keep trail examples synchronized with their authored schemas.',
254
+ },
255
+ invariant: 'Trail examples remain valid against their authored schema.',
256
+ tier: 'source-static',
257
+ },
258
+ 'fires-declarations': {
259
+ ...durableExternal,
260
+ invariant: 'Declared fires stay aligned with signal firing usage.',
261
+ tier: 'source-static',
262
+ },
263
+ 'fork-without-preserved-blaze': {
264
+ ...durableExternal,
265
+ invariant: 'Fork version entries preserve their historical blaze.',
266
+ tier: 'source-static',
267
+ },
268
+ 'implementation-returns-result': {
269
+ ...durableExternal,
270
+ invariant: 'Blazes return Result values.',
271
+ tier: 'source-static',
272
+ },
273
+ 'incomplete-accessor-for-standard-op': {
274
+ ...durableExternal,
275
+ invariant: 'Standard CRUD operations expose the expected accessor shape.',
276
+ tier: 'topo-aware',
277
+ },
278
+ 'incomplete-crud': {
279
+ ...durableExternal,
280
+ invariant: 'Versioned CRUD entities expose complete operation coverage.',
281
+ tier: 'project-static',
282
+ },
283
+ 'intent-propagation': {
284
+ ...durableExternal,
285
+ invariant: 'Composite trail intent cannot be safer than composed trails.',
286
+ tier: 'project-static',
287
+ },
288
+ 'layer-field-name-drift': {
289
+ ...durableExternal,
290
+ invariant:
291
+ 'Layer input field reserved names are shared across surface projections.',
292
+ tier: 'source-static',
293
+ },
294
+ 'library-projection-coherence': {
295
+ ...durableExternal,
296
+ guidance: {
297
+ docs: [
298
+ {
299
+ label: 'Library Surface ADR',
300
+ path: 'docs/adr/drafts/20260612-library-surface-and-compiler.md',
301
+ },
302
+ ],
303
+ relatedRules: ['cli-command-route-coherence', 'surface-facet-coherence'],
304
+ steps: [
305
+ 'Rename one source trail or add an explicit library export override before generating a package.',
306
+ 'Keep serialized library projection exports attached to existing trail IDs.',
307
+ 'Run the generated-package smoke after repairing projection drift.',
308
+ ],
309
+ summary:
310
+ 'Keep resolved library projection exports collision-free and attached to one trail contract.',
311
+ },
312
+ invariant:
313
+ 'Resolved library projection exports are collision-free and target existing trails.',
314
+ tier: 'topo-aware',
315
+ },
316
+ 'marker-schema-unsupported': {
317
+ ...durableExternal,
318
+ invariant:
319
+ 'Versioned schemas stay inside the supported marker projection subset.',
320
+ tier: 'source-static',
321
+ },
322
+ 'missing-reconcile': {
323
+ ...durableExternal,
324
+ invariant: 'Versioned CRUD store tables provide reconcile coverage.',
325
+ tier: 'project-static',
326
+ },
327
+ 'missing-visibility': {
328
+ ...durableExternal,
329
+ invariant: 'Composition-only trails declare internal visibility.',
330
+ tier: 'project-static',
331
+ },
332
+ 'no-destructured-compose': {
333
+ ...durableExternal,
334
+ invariant:
335
+ 'Trail blazes compose through ctx.compose() directly instead of destructuring compose from the context.',
336
+ tier: 'source-static',
337
+ },
338
+ 'no-dev-permit-in-source': {
339
+ ...durableExternal,
340
+ invariant:
341
+ 'The `--dev-permit` CLI flag string never appears in committed source.',
342
+ tier: 'source-static',
343
+ },
344
+ 'no-direct-implementation-call': {
345
+ ...durableExternal,
346
+ invariant: 'Application code composes trails through ctx.compose().',
347
+ tier: 'source-static',
348
+ },
349
+ 'no-legacy-layer-imports': {
350
+ fix: { class: 'term-rewrite', safety: 'review' },
351
+ invariant:
352
+ 'Legacy layer exports removed across TRL-475/TRL-476 (authLayer, autoIterateLayer, dateShortcutsLayer) do not reappear in committed source.',
353
+ lifecycle: {
354
+ retireWhen:
355
+ 'Layer Evolution legacy layer migration window closes (one minor release after the legacy exports are removed).',
356
+ state: 'temporary',
357
+ },
358
+ scope: 'external',
359
+ tier: 'source-static',
360
+ },
361
+ 'no-native-error-result': {
362
+ ...durableExternal,
363
+ invariant: 'Result error boundaries carry specific TrailsError subclasses.',
364
+ tier: 'source-static',
365
+ },
366
+ 'no-redundant-result-error-wrap': {
367
+ ...durableExternal,
368
+ invariant:
369
+ 'Result error pass-throughs preserve the original Result boundary.',
370
+ tier: 'source-static',
371
+ },
372
+ 'no-retired-cross-vocabulary': {
373
+ fix: { class: 'term-rewrite', safety: 'safe' },
374
+ invariant:
375
+ 'Retired cross composition vocabulary does not remain in downstream source after the beta.19 compose cutover.',
376
+ lifecycle: {
377
+ retireWhen:
378
+ 'Downstream beta.19 cross-to-compose migration window closes and supported apps have adopted compose vocabulary.',
379
+ state: 'temporary',
380
+ },
381
+ scope: 'external',
382
+ tier: 'source-static',
383
+ },
384
+ 'no-sync-result-assumption': {
385
+ ...durableExternal,
386
+ invariant:
387
+ 'Result accessors are not used before async results are awaited.',
388
+ tier: 'source-static',
389
+ },
390
+ 'no-throw-in-detour-recover': {
391
+ ...durableExternal,
392
+ invariant: 'Detour recovery returns Result instead of throwing.',
393
+ tier: 'source-static',
394
+ },
395
+ 'no-throw-in-implementation': {
396
+ ...durableExternal,
397
+ guidance: {
398
+ docs: [trailContractDocs],
399
+ relatedRules: ['implementation-returns-result', 'no-native-error-result'],
400
+ steps: [
401
+ 'Return Result.err() with the most specific TrailsError subclass available.',
402
+ 'Use detours for recoverable runtime strategies instead of throwing inside the blaze.',
403
+ ],
404
+ summary:
405
+ 'Convert thrown failures in blazes into explicit Result.err() outcomes.',
406
+ },
407
+ invariant: 'Blazes return Result.err() instead of throwing.',
408
+ tier: 'source-static',
409
+ },
410
+ 'no-top-level-surface': {
411
+ ...durableExternal,
412
+ guidance: {
413
+ docs: [{ label: 'Architecture', path: 'docs/architecture.md' }],
414
+ relatedRules: ['context-no-surface-types'],
415
+ steps: [
416
+ 'Keep the topo-export module focused on exporting `topo(...)` as `default`, `graph`, or `app`.',
417
+ 'Move `surface(...)`, `connectStdio(...)`, server start, or `.listen(...)` calls into a separate entry or bin module.',
418
+ ],
419
+ summary:
420
+ 'Keep topo entry modules side-effect-free for survey, guide, compile, and lock generation.',
421
+ },
422
+ invariant: 'Topo export modules do not open surfaces at module top level.',
423
+ tier: 'source-static',
424
+ },
425
+ 'on-references-exist': {
426
+ ...durableExternal,
427
+ invariant: 'Trail on: declarations resolve to known signals.',
428
+ tier: 'project-static',
429
+ },
430
+ 'orphaned-signal': {
431
+ ...durableExternal,
432
+ invariant:
433
+ 'Derived store signals are consumed by matching trail on: consumers.',
434
+ tier: 'project-static',
435
+ },
436
+ 'owner-projection-parity': {
437
+ invariant: 'Framework projections stay aligned with owner exports.',
438
+ lifecycle: { state: 'durable' },
439
+ scope: 'internal',
440
+ tier: 'source-static',
441
+ },
442
+ 'pending-force': {
443
+ ...durableExternal,
444
+ invariant:
445
+ 'Forced topo break audit events do not remain pending indefinitely.',
446
+ tier: 'topo-aware',
447
+ },
448
+ 'permit-governance': {
449
+ ...durableExternal,
450
+ guidance: {
451
+ docs: [
452
+ trailContractDocs,
453
+ { label: 'Permits', path: 'packages/permits/README.md' },
454
+ ],
455
+ steps: [
456
+ 'Declare the permit required for the destructive trail.',
457
+ 'If the write is intentionally development-only, keep the dev permit out of committed runtime source.',
458
+ ],
459
+ summary:
460
+ 'Make destructive trail authorization visible on the trail contract.',
461
+ },
462
+ invariant: 'Destroy trails declare explicit permit requirements.',
463
+ tier: 'topo-aware',
464
+ },
465
+ 'prefer-schema-inference': {
466
+ guidance: {
467
+ docs: [trailContractDocs],
468
+ steps: [
469
+ 'Remove field overrides that only repeat labels or enum options already inferable from the schema.',
470
+ 'Keep field metadata only when it adds meaning the schema cannot derive.',
471
+ ],
472
+ summary:
473
+ 'Let schemas remain the owner for field metadata unless an override adds new information.',
474
+ },
475
+ invariant: 'Trail schemas should be inferred unless overrides add meaning.',
476
+ lifecycle: { state: 'durable' },
477
+ scope: 'advisory',
478
+ tier: 'source-static',
479
+ },
480
+ 'public-export-example-coverage': {
481
+ ...durableRepoLocal,
482
+ invariant:
483
+ 'Public API barrel exports carry leading @example TSDoc coverage.',
484
+ tier: 'source-static',
485
+ },
486
+ 'public-internal-deep-imports': {
487
+ invariant: 'Cross-package imports stay on package-owned public exports.',
488
+ lifecycle: { state: 'durable' },
489
+ scope: 'internal',
490
+ tier: 'project-static',
491
+ },
492
+ 'public-output-schema': {
493
+ ...durableExternal,
494
+ guidance: {
495
+ docs: [trailContractDocs, wardenDocs],
496
+ relatedRules: ['public-union-output-discriminants'],
497
+ steps: [
498
+ 'Add an explicit output schema to public trails that can be projected onto MCP or HTTP surfaces.',
499
+ 'If the trail is composition-only, mark it visibility: "internal" instead of exposing it by default.',
500
+ ],
501
+ summary:
502
+ 'Make public surface result contracts explicit before MCP/HTTP projection.',
503
+ },
504
+ invariant: 'Public MCP/HTTP surface trails declare output schemas.',
505
+ tier: 'topo-aware',
506
+ },
507
+ 'public-union-output-discriminants': {
508
+ ...durableExternal,
509
+ invariant: 'Public output object unions expose branch discriminants.',
510
+ tier: 'topo-aware',
511
+ },
512
+ 'read-intent-fires': {
513
+ ...durableExternal,
514
+ invariant: 'Read trails should not declare signal fires side effects.',
515
+ tier: 'source-static',
516
+ },
517
+ 'reference-exists': {
518
+ ...durableExternal,
519
+ invariant: 'Reference declarations resolve to known contours.',
520
+ tier: 'project-static',
521
+ },
522
+ 'resolved-import-boundary': {
523
+ ...durableExternal,
524
+ invariant: 'Cross-package imports resolve through public export maps.',
525
+ tier: 'project-static',
526
+ },
527
+ 'resource-declarations': {
528
+ ...durableExternal,
529
+ guidance: {
530
+ docs: [trailContractDocs],
531
+ relatedRules: ['resource-exists'],
532
+ steps: [
533
+ 'Declare each external dependency in the trail resources array.',
534
+ 'Access statically known resources through the resource definition helper rather than constructing dependencies inline.',
535
+ ],
536
+ summary:
537
+ 'Keep infrastructure dependencies declared on the trail contract.',
538
+ },
539
+ invariant: 'Resource usage is declared on the trail contract.',
540
+ tier: 'source-static',
541
+ },
542
+ 'resource-exists': {
543
+ ...durableExternal,
544
+ guidance: {
545
+ docs: [trailContractDocs, wardenDocs],
546
+ relatedRules: ['resource-declarations'],
547
+ steps: [
548
+ 'Define the referenced resource in project source or import the existing resource definition.',
549
+ 'When a resource is testable, include a mock factory so contract tests can run without real infrastructure.',
550
+ ],
551
+ summary:
552
+ 'Make declared resources resolve to authored resource definitions.',
553
+ },
554
+ invariant: 'Declared resources resolve to known resource definitions.',
555
+ tier: 'project-static',
556
+ },
557
+ 'resource-id-grammar': {
558
+ ...durableExternal,
559
+ invariant: 'Resource identifiers stay out of the scope separator grammar.',
560
+ tier: 'source-static',
561
+ },
562
+ 'resource-mock-coverage': {
563
+ ...durableExternal,
564
+ guidance: {
565
+ docs: [trailContractDocs],
566
+ relatedRules: ['resource-declarations', 'resource-exists'],
567
+ steps: [
568
+ 'Add a mock() factory so testAll(app) can provision the resource without production configuration.',
569
+ 'If the resource genuinely cannot be mocked, declare unmockable: { reason } to record that intent.',
570
+ ],
571
+ summary:
572
+ 'Make each resource declare a test mock or an explicit unmockable reason.',
573
+ },
574
+ invariant:
575
+ 'Resource definitions declare a mock factory or an explicit unmockable reason.',
576
+ tier: 'source-static',
577
+ },
578
+ 'scheduled-destroy-intent': {
579
+ ...durableExternal,
580
+ invariant:
581
+ 'Schedule-activated destroy trails make unattended destructive work visible for review.',
582
+ tier: 'topo-aware',
583
+ },
584
+ 'signal-graph-coaching': {
585
+ ...durableExternal,
586
+ invariant:
587
+ 'Typed signal contracts either declare a producer or participate in reactive consumption.',
588
+ tier: 'topo-aware',
589
+ },
590
+ 'static-resource-accessor-preference': {
591
+ ...durableExternal,
592
+ guidance: {
593
+ docs: [trailContractDocs],
594
+ relatedRules: ['resource-declarations', 'resource-exists'],
595
+ steps: [
596
+ 'Replace ctx.resource(db) or ctx.resource("id") with db.from(ctx) when the resource definition is statically in scope.',
597
+ 'Move external client construction behind resource() and declare that resource on the trail contract.',
598
+ 'Keep ctx.resource(...) for dynamic IDs, generic framework code, or cases where the definition is not statically available.',
599
+ ],
600
+ summary:
601
+ 'Use statically scoped resource helpers when the resource definition is already available.',
602
+ },
603
+ invariant:
604
+ 'Trail logic should prefer static resource helpers over dynamic accessors.',
605
+ scope: 'advisory',
606
+ tier: 'source-static',
607
+ },
608
+ 'surface-facet-coherence': {
609
+ ...durableExternal,
610
+ guidance: {
611
+ docs: [
612
+ {
613
+ label: 'Trailheads ADR',
614
+ path: 'docs/adr/drafts/20260603-surface-facets-shape-dense-topos.md',
615
+ },
616
+ ],
617
+ steps: [
618
+ 'Keep facet selectors as explicit string literals or literal arrays when possible.',
619
+ 'Ensure each public trail belongs to one facet owner.',
620
+ 'Record explicit visibility-widening acceptance and stable-description metadata when a facet intentionally widens visibility.',
621
+ ],
622
+ summary:
623
+ 'Keep trailhead maps reviewable before they reach MCP projection.',
624
+ },
625
+ invariant:
626
+ 'Trailhead maps avoid selector overlap, hidden visibility widening, and drift-prone dynamic selectors.',
627
+ tier: 'source-static',
628
+ },
629
+ 'trail-fork-coaching': {
630
+ ...durableExternal,
631
+ guidance: {
632
+ docs: [
633
+ {
634
+ label: 'ADR-0050 Surface Accommodations',
635
+ path: 'docs/adr/0050-surface-accommodations-preserve-trail-identity.md',
636
+ },
637
+ {
638
+ label: 'Surface Accommodations',
639
+ path: 'docs/surfaces/surface-accommodations.md',
640
+ },
641
+ ],
642
+ relatedRules: ['surface-facet-coherence', 'cli-command-route-coherence'],
643
+ steps: [
644
+ 'Check the semantic fork boundary: intent, permits, outputs, errors, lifecycle, and side effects.',
645
+ 'Check the structural fork boundary: selected trail identity stays visible instead of hiding behind action vocabulary.',
646
+ 'Split real capability forks into distinct trails or a composing trail.',
647
+ 'Use a facet only when one surface entry needs to group multiple trails while preserving selected member identity.',
648
+ ],
649
+ summary:
650
+ 'Keep surface accommodations from hiding several capabilities behind one branching trail input.',
651
+ },
652
+ invariant:
653
+ 'Trails avoid hiding distinct capabilities behind branching action or operation inputs.',
654
+ scope: 'advisory',
655
+ tier: 'source-static',
656
+ },
657
+ 'unmaterialized-activation-source': {
658
+ ...durableExternal,
659
+ invariant:
660
+ 'Activation sources have an available runtime materializer before runtime delivery is assumed.',
661
+ tier: 'topo-aware',
662
+ },
663
+ 'unreachable-detour-shadowing': {
664
+ ...durableExternal,
665
+ invariant: 'Specific detours are not shadowed by earlier broader detours.',
666
+ tier: 'source-static',
667
+ },
668
+ 'valid-describe-refs': {
669
+ invariant: 'Describe references point at known Trails concepts.',
670
+ lifecycle: { state: 'durable' },
671
+ scope: 'advisory',
672
+ tier: 'project-static',
673
+ },
674
+ 'valid-detour-contract': {
675
+ ...durableExternal,
676
+ invariant:
677
+ 'Runtime detour contracts use error constructors and recover functions.',
678
+ tier: 'topo-aware',
679
+ },
680
+ 'version-gap': {
681
+ ...durableExternal,
682
+ invariant:
683
+ 'Trail version coverage remains contiguous through the current version.',
684
+ tier: 'topo-aware',
685
+ },
686
+ 'version-pinned-compose': {
687
+ ...durableExternal,
688
+ invariant:
689
+ 'Version-pinned ctx.compose() calls stay visible migration debt.',
690
+ tier: 'source-static',
691
+ },
692
+ 'version-without-examples': {
693
+ ...durableExternal,
694
+ invariant: 'Live historical version entries include examples.',
695
+ tier: 'topo-aware',
696
+ },
697
+ 'warden-export-symmetry': {
698
+ ...durableRepoLocal,
699
+ invariant: 'The Warden package exports trail wrappers, not raw rules.',
700
+ tier: 'source-static',
701
+ },
702
+ 'warden-rules-use-ast': {
703
+ ...durableRepoLocal,
704
+ invariant:
705
+ 'Warden source rules use AST helpers instead of ad hoc parsing or raw node-field casts.',
706
+ tier: 'source-static',
707
+ },
708
+ 'webhook-route-collision': {
709
+ ...durableExternal,
710
+ invariant:
711
+ 'Webhook routes do not collide with each other or direct HTTP trail routes.',
712
+ tier: 'topo-aware',
713
+ },
714
+ } as const satisfies Record<string, BuiltinWardenRuleMetadataInput>;
715
+
716
+ export type BuiltinWardenRuleName = keyof typeof builtinWardenRuleMetadataInput;
717
+
718
+ const withFacetDefaults = (
719
+ name: string,
720
+ metadata: BuiltinWardenRuleMetadataInput
721
+ ): WardenRuleMetadata => ({
722
+ concern: concernByRuleName[name] ?? 'general',
723
+ depth: metadata.scope === 'advisory' ? 'all' : depthByTier[metadata.tier],
724
+ ...metadata,
725
+ });
726
+
727
+ export const builtinWardenRuleMetadata = Object.fromEntries(
728
+ Object.entries(builtinWardenRuleMetadataInput).map(([name, metadata]) => [
729
+ name,
730
+ withFacetDefaults(name, metadata),
731
+ ])
732
+ ) as Readonly<Record<BuiltinWardenRuleName, WardenRuleMetadata>>;
733
+
734
+ const metadataByName: Readonly<Record<string, WardenRuleMetadata>> =
735
+ builtinWardenRuleMetadata;
736
+
737
+ export const getWardenRuleMetadata = (
738
+ rule: Pick<WardenRule, 'metadata' | 'name'> | string
739
+ ): WardenRuleMetadata | null => {
740
+ if (typeof rule !== 'string' && rule.metadata) {
741
+ return rule.metadata;
742
+ }
743
+
744
+ const name = typeof rule === 'string' ? rule : rule.name;
745
+ return metadataByName[name] ?? null;
746
+ };
747
+
748
+ export const listWardenRuleMetadata = (): readonly (readonly [
749
+ BuiltinWardenRuleName,
750
+ WardenRuleMetadata,
751
+ ])[] =>
752
+ Object.entries(builtinWardenRuleMetadata) as readonly (readonly [
753
+ BuiltinWardenRuleName,
754
+ WardenRuleMetadata,
755
+ ])[];