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

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