@ontrails/warden 1.0.0-beta.5 → 1.0.0-beta.50

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 (289) hide show
  1. package/CHANGELOG.md +929 -15
  2. package/README.md +133 -27
  3. package/bin/warden.ts +51 -0
  4. package/package.json +32 -6
  5. package/src/adapter-check.ts +136 -0
  6. package/src/cli.ts +1690 -114
  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 +260 -15
  15. package/src/project-context.ts +446 -0
  16. package/src/project-rules.ts +290 -0
  17. package/src/regrade-history.ts +188 -0
  18. package/src/regrade-receipt.ts +484 -0
  19. package/src/resolve.ts +531 -0
  20. package/src/rules/activation-orphan.ts +97 -0
  21. package/src/rules/captured-kernel.ts +375 -0
  22. package/src/rules/circular-refs.ts +150 -0
  23. package/src/rules/cli-command-route-coherence.ts +177 -0
  24. package/src/rules/composes-declarations.ts +839 -0
  25. package/src/rules/context-no-surface-types.ts +79 -15
  26. package/src/rules/dead-internal-trail.ts +161 -0
  27. package/src/rules/dead-public-trail.ts +258 -0
  28. package/src/rules/draft-file-marking.ts +155 -0
  29. package/src/rules/draft-visible-debt.ts +82 -0
  30. package/src/rules/duplicate-exported-symbol.ts +172 -0
  31. package/src/rules/duplicate-public-contract.ts +137 -0
  32. package/src/rules/entity-exists.ts +254 -0
  33. package/src/rules/entity-ids.ts +15 -0
  34. package/src/rules/error-mapping-completeness.ts +290 -0
  35. package/src/rules/example-valid.ts +395 -0
  36. package/src/rules/fires-declarations.ts +740 -0
  37. package/src/rules/governed-symbol-residue.ts +535 -0
  38. package/src/rules/governed-vocabulary-permutation-watch.ts +77 -0
  39. package/src/rules/implementation-returns-result.ts +1496 -163
  40. package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
  41. package/src/rules/incomplete-crud.ts +583 -0
  42. package/src/rules/index.ts +288 -13
  43. package/src/rules/intent-propagation.ts +125 -0
  44. package/src/rules/layer-field-name-drift.ts +102 -0
  45. package/src/rules/library-render-coherence.ts +97 -0
  46. package/src/rules/metadata.ts +908 -0
  47. package/src/rules/missing-reconcile.ts +97 -0
  48. package/src/rules/missing-visibility.ts +111 -0
  49. package/src/rules/no-destructured-compose.ts +196 -0
  50. package/src/rules/no-dev-permit-in-source.ts +99 -0
  51. package/src/rules/no-direct-implementation-call.ts +12 -7
  52. package/src/rules/no-legacy-cli-alias-export.ts +247 -0
  53. package/src/rules/no-legacy-layer-imports.ts +211 -0
  54. package/src/rules/no-native-error-result.ts +118 -0
  55. package/src/rules/no-redundant-result-error-wrap.ts +382 -0
  56. package/src/rules/no-retired-cross-vocabulary.ts +203 -0
  57. package/src/rules/no-sync-result-assumption.ts +1141 -98
  58. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  59. package/src/rules/no-throw-in-implementation.ts +15 -8
  60. package/src/rules/no-top-level-surface.ts +371 -0
  61. package/src/rules/on-references-exist.ts +194 -0
  62. package/src/rules/orphaned-signal.ts +149 -0
  63. package/src/rules/owner-render-parity.ts +143 -0
  64. package/src/rules/permit-governance.ts +25 -0
  65. package/src/rules/public-export-example-coverage.ts +561 -0
  66. package/src/rules/public-internal-deep-imports.ts +454 -0
  67. package/src/rules/public-output-schema.ts +29 -0
  68. package/src/rules/public-union-output-discriminants.ts +150 -0
  69. package/src/rules/read-intent-fires.ts +188 -0
  70. package/src/rules/reference-exists.ts +97 -0
  71. package/src/rules/registry-names.ts +169 -0
  72. package/src/rules/resolved-import-boundary.ts +146 -0
  73. package/src/rules/resource-declarations.ts +697 -0
  74. package/src/rules/resource-exists.ts +181 -0
  75. package/src/rules/resource-id-grammar.ts +65 -0
  76. package/src/rules/resource-mock-coverage.ts +115 -0
  77. package/src/rules/retired-vocabulary.ts +1407 -0
  78. package/src/rules/scan.ts +38 -25
  79. package/src/rules/scheduled-destroy-intent.ts +44 -0
  80. package/src/rules/signal-graph-coaching.ts +220 -0
  81. package/src/rules/source/composition.ts +165 -0
  82. package/src/rules/source/drafts.ts +164 -0
  83. package/src/rules/source/entities.ts +618 -0
  84. package/src/rules/source/pragmas.ts +45 -0
  85. package/src/rules/source/resources.ts +64 -0
  86. package/src/rules/source/signals.ts +397 -0
  87. package/src/rules/source/stores.ts +310 -0
  88. package/src/rules/specs.ts +9 -5
  89. package/src/rules/static-resource-accessor-preference.ts +653 -0
  90. package/src/rules/surface-overlay-coherence.ts +262 -0
  91. package/src/rules/surface-trailhead-coherence.ts +366 -0
  92. package/src/rules/trail-fork-coaching.ts +625 -0
  93. package/src/rules/trail-versioning-source.ts +1076 -0
  94. package/src/rules/trail-versioning-topo.ts +172 -0
  95. package/src/rules/trailhead-override-divergence.ts +356 -0
  96. package/src/rules/types.ts +403 -8
  97. package/src/rules/unmaterialized-activation-source.ts +85 -0
  98. package/src/rules/unreachable-detour-shadowing.ts +339 -0
  99. package/src/rules/valid-describe-refs.ts +162 -32
  100. package/src/rules/valid-detour-contract.ts +78 -0
  101. package/src/rules/warden-export-symmetry.ts +540 -0
  102. package/src/rules/warden-rules-use-ast.ts +1109 -0
  103. package/src/rules/webhook-route-collision.ts +306 -0
  104. package/src/trails/activation-orphan.trail.ts +84 -0
  105. package/src/trails/captured-kernel.trail.ts +108 -0
  106. package/src/trails/circular-refs.trail.ts +29 -0
  107. package/src/trails/cli-command-route-coherence.trail.ts +47 -0
  108. package/src/trails/composes-declarations.trail.ts +22 -0
  109. package/src/trails/context-no-surface-types.trail.ts +21 -0
  110. package/src/trails/dead-internal-trail.trail.ts +26 -0
  111. package/src/trails/dead-public-trail.trail.ts +31 -0
  112. package/src/trails/deprecation-without-guidance.trail.ts +21 -0
  113. package/src/trails/draft-file-marking.trail.ts +16 -0
  114. package/src/trails/draft-visible-debt.trail.ts +16 -0
  115. package/src/trails/duplicate-exported-symbol.trail.ts +48 -0
  116. package/src/trails/duplicate-public-contract.trail.ts +47 -0
  117. package/src/trails/entity-exists.trail.ts +21 -0
  118. package/src/trails/error-mapping-completeness.trail.ts +30 -0
  119. package/src/trails/example-valid.trail.ts +25 -0
  120. package/src/trails/fires-declarations.trail.ts +23 -0
  121. package/src/trails/fork-without-preserved-implementation.trail.ts +31 -0
  122. package/src/trails/governed-symbol-residue.trail.ts +24 -0
  123. package/src/trails/governed-vocabulary-permutation-watch.trail.ts +16 -0
  124. package/src/trails/implementation-returns-result.trail.ts +20 -0
  125. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  126. package/src/trails/incomplete-crud.trail.ts +39 -0
  127. package/src/trails/index.ts +90 -0
  128. package/src/trails/intent-propagation.trail.ts +30 -0
  129. package/src/trails/layer-field-name-drift.trail.ts +39 -0
  130. package/src/trails/library-render-coherence.trail.ts +43 -0
  131. package/src/trails/marker-schema-unsupported.trail.ts +23 -0
  132. package/src/trails/missing-reconcile.trail.ts +33 -0
  133. package/src/trails/missing-visibility.trail.ts +22 -0
  134. package/src/trails/no-destructured-compose.trail.ts +44 -0
  135. package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
  136. package/src/trails/no-direct-implementation-call.trail.ts +16 -0
  137. package/src/trails/no-legacy-cli-alias-export.trail.ts +41 -0
  138. package/src/trails/no-legacy-layer-imports.trail.ts +41 -0
  139. package/src/trails/no-native-error-result.trail.ts +18 -0
  140. package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
  141. package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
  142. package/src/trails/no-sync-result-assumption.trail.ts +19 -0
  143. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  144. package/src/trails/no-throw-in-implementation.trail.ts +20 -0
  145. package/src/trails/no-top-level-surface.trail.ts +43 -0
  146. package/src/trails/on-references-exist.trail.ts +21 -0
  147. package/src/trails/orphaned-signal.trail.ts +36 -0
  148. package/src/trails/owner-render-parity.trail.ts +26 -0
  149. package/src/trails/pending-force.trail.ts +21 -0
  150. package/src/trails/permit-governance.trail.ts +51 -0
  151. package/src/trails/prefer-schema-inference.trail.ts +21 -0
  152. package/src/trails/public-export-example-coverage.trail.ts +16 -0
  153. package/src/trails/public-internal-deep-imports.trail.ts +94 -0
  154. package/src/trails/public-output-schema.trail.ts +55 -0
  155. package/src/trails/public-union-output-discriminants.trail.ts +33 -0
  156. package/src/trails/read-intent-fires.trail.ts +20 -0
  157. package/src/trails/reference-exists.trail.ts +25 -0
  158. package/src/trails/resolved-import-boundary.trail.ts +109 -0
  159. package/src/trails/resource-declarations.trail.ts +25 -0
  160. package/src/trails/resource-exists.trail.ts +27 -0
  161. package/src/trails/resource-id-grammar.trail.ts +39 -0
  162. package/src/trails/resource-mock-coverage.trail.ts +40 -0
  163. package/src/trails/run.ts +202 -0
  164. package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
  165. package/src/trails/schema.ts +281 -0
  166. package/src/trails/signal-graph-coaching.trail.ts +77 -0
  167. package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
  168. package/src/trails/surface-overlay-coherence.trail.ts +24 -0
  169. package/src/trails/surface-trailhead-coherence.trail.ts +25 -0
  170. package/src/trails/topo.ts +6 -0
  171. package/src/trails/trail-fork-coaching.trail.ts +42 -0
  172. package/src/trails/trailhead-override-divergence.trail.ts +47 -0
  173. package/src/trails/unmaterialized-activation-source.trail.ts +72 -0
  174. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  175. package/src/trails/valid-describe-refs.trail.ts +18 -0
  176. package/src/trails/valid-detour-contract.trail.ts +71 -0
  177. package/src/trails/version-gap.trail.ts +35 -0
  178. package/src/trails/version-pinned-compose.trail.ts +23 -0
  179. package/src/trails/version-without-examples.trail.ts +38 -0
  180. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  181. package/src/trails/warden-rules-use-ast.trail.ts +64 -0
  182. package/src/trails/webhook-route-collision.trail.ts +50 -0
  183. package/src/trails/wrap-rule.ts +266 -0
  184. package/src/workspaces.ts +264 -0
  185. package/.turbo/turbo-build.log +0 -1
  186. package/.turbo/turbo-lint.log +0 -3
  187. package/.turbo/turbo-typecheck.log +0 -1
  188. package/dist/cli.d.ts +0 -46
  189. package/dist/cli.d.ts.map +0 -1
  190. package/dist/cli.js +0 -218
  191. package/dist/cli.js.map +0 -1
  192. package/dist/drift.d.ts +0 -26
  193. package/dist/drift.d.ts.map +0 -1
  194. package/dist/drift.js +0 -27
  195. package/dist/drift.js.map +0 -1
  196. package/dist/formatters.d.ts +0 -29
  197. package/dist/formatters.d.ts.map +0 -1
  198. package/dist/formatters.js +0 -87
  199. package/dist/formatters.js.map +0 -1
  200. package/dist/index.d.ts +0 -26
  201. package/dist/index.d.ts.map +0 -1
  202. package/dist/index.js +0 -26
  203. package/dist/index.js.map +0 -1
  204. package/dist/rules/ast.d.ts +0 -41
  205. package/dist/rules/ast.d.ts.map +0 -1
  206. package/dist/rules/ast.js +0 -161
  207. package/dist/rules/ast.js.map +0 -1
  208. package/dist/rules/context-no-surface-types.d.ts +0 -12
  209. package/dist/rules/context-no-surface-types.d.ts.map +0 -1
  210. package/dist/rules/context-no-surface-types.js +0 -96
  211. package/dist/rules/context-no-surface-types.js.map +0 -1
  212. package/dist/rules/follow-declarations.d.ts +0 -13
  213. package/dist/rules/follow-declarations.d.ts.map +0 -1
  214. package/dist/rules/follow-declarations.js +0 -195
  215. package/dist/rules/follow-declarations.js.map +0 -1
  216. package/dist/rules/implementation-returns-result.d.ts +0 -13
  217. package/dist/rules/implementation-returns-result.d.ts.map +0 -1
  218. package/dist/rules/implementation-returns-result.js +0 -277
  219. package/dist/rules/implementation-returns-result.js.map +0 -1
  220. package/dist/rules/index.d.ts +0 -16
  221. package/dist/rules/index.d.ts.map +0 -1
  222. package/dist/rules/index.js +0 -37
  223. package/dist/rules/index.js.map +0 -1
  224. package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
  225. package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
  226. package/dist/rules/no-direct-impl-in-route.js +0 -47
  227. package/dist/rules/no-direct-impl-in-route.js.map +0 -1
  228. package/dist/rules/no-direct-implementation-call.d.ts +0 -12
  229. package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
  230. package/dist/rules/no-direct-implementation-call.js +0 -39
  231. package/dist/rules/no-direct-implementation-call.js.map +0 -1
  232. package/dist/rules/no-sync-result-assumption.d.ts +0 -6
  233. package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
  234. package/dist/rules/no-sync-result-assumption.js +0 -98
  235. package/dist/rules/no-sync-result-assumption.js.map +0 -1
  236. package/dist/rules/no-throw-in-detour-target.d.ts +0 -12
  237. package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
  238. package/dist/rules/no-throw-in-detour-target.js +0 -87
  239. package/dist/rules/no-throw-in-detour-target.js.map +0 -1
  240. package/dist/rules/no-throw-in-implementation.d.ts +0 -9
  241. package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
  242. package/dist/rules/no-throw-in-implementation.js +0 -34
  243. package/dist/rules/no-throw-in-implementation.js.map +0 -1
  244. package/dist/rules/prefer-schema-inference.d.ts +0 -7
  245. package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
  246. package/dist/rules/prefer-schema-inference.js +0 -86
  247. package/dist/rules/prefer-schema-inference.js.map +0 -1
  248. package/dist/rules/scan.d.ts +0 -8
  249. package/dist/rules/scan.d.ts.map +0 -1
  250. package/dist/rules/scan.js +0 -32
  251. package/dist/rules/scan.js.map +0 -1
  252. package/dist/rules/specs.d.ts +0 -29
  253. package/dist/rules/specs.d.ts.map +0 -1
  254. package/dist/rules/specs.js +0 -192
  255. package/dist/rules/specs.js.map +0 -1
  256. package/dist/rules/structure.d.ts +0 -13
  257. package/dist/rules/structure.d.ts.map +0 -1
  258. package/dist/rules/structure.js +0 -142
  259. package/dist/rules/structure.js.map +0 -1
  260. package/dist/rules/types.d.ts +0 -52
  261. package/dist/rules/types.d.ts.map +0 -1
  262. package/dist/rules/types.js +0 -2
  263. package/dist/rules/types.js.map +0 -1
  264. package/dist/rules/valid-describe-refs.d.ts +0 -7
  265. package/dist/rules/valid-describe-refs.d.ts.map +0 -1
  266. package/dist/rules/valid-describe-refs.js +0 -51
  267. package/dist/rules/valid-describe-refs.js.map +0 -1
  268. package/dist/rules/valid-detour-refs.d.ts +0 -6
  269. package/dist/rules/valid-detour-refs.d.ts.map +0 -1
  270. package/dist/rules/valid-detour-refs.js +0 -116
  271. package/dist/rules/valid-detour-refs.js.map +0 -1
  272. package/src/__tests__/cli.test.ts +0 -198
  273. package/src/__tests__/drift.test.ts +0 -74
  274. package/src/__tests__/follow-declarations.test.ts +0 -151
  275. package/src/__tests__/formatters.test.ts +0 -157
  276. package/src/__tests__/implementation-returns-result.test.ts +0 -129
  277. package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
  278. package/src/__tests__/no-sync-result-assumption.test.ts +0 -85
  279. package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
  280. package/src/__tests__/prefer-schema-inference.test.ts +0 -84
  281. package/src/__tests__/rules.test.ts +0 -227
  282. package/src/__tests__/valid-describe-refs.test.ts +0 -60
  283. package/src/rules/ast.ts +0 -213
  284. package/src/rules/follow-declarations.ts +0 -283
  285. package/src/rules/no-direct-impl-in-route.ts +0 -81
  286. package/src/rules/no-throw-in-detour-target.ts +0 -150
  287. package/src/rules/valid-detour-refs.ts +0 -187
  288. package/tsconfig.json +0 -9
  289. package/tsconfig.tsbuildinfo +0 -1
@@ -1,14 +1,231 @@
1
+ import type {
2
+ DiagnosticSeverity,
3
+ Intent,
4
+ RuleDiagnosticBase,
5
+ ScanTargets,
6
+ SurfaceBindings,
7
+ Topo,
8
+ } from '@ontrails/core';
9
+ import type { TopoGraph } from '@ontrails/topography';
10
+
11
+ import type { WardenDepth } from '../config.js';
12
+ import type { WardenImportResolution } from '../resolve.js';
13
+ import type { WardenPublicWorkspace } from '../workspaces.js';
14
+ import type { GovernedVocabularyHistoryProvenance } from './retired-vocabulary.js';
15
+
1
16
  /**
2
17
  * Severity level for warden diagnostics.
3
18
  */
4
- export type WardenSeverity = 'error' | 'warn';
19
+ export type WardenSeverity = DiagnosticSeverity;
20
+
21
+ /**
22
+ * Execution tier for a Warden rule.
23
+ *
24
+ * Tier names describe the narrowest runtime shape that can answer the rule's
25
+ * question. They do not change ownership: source-static rules can still be
26
+ * durable public Warden doctrine.
27
+ */
28
+ export type WardenRuleTier =
29
+ | 'advisory'
30
+ | 'drift'
31
+ | 'project-static'
32
+ | 'source-static'
33
+ | 'topo-aware';
34
+
35
+ /**
36
+ * Context where a Warden rule applies.
37
+ */
38
+ export type WardenRuleScope =
39
+ | 'advisory'
40
+ | 'extension'
41
+ | 'external'
42
+ | 'internal'
43
+ | 'repo-local'
44
+ | 'temporary';
45
+
46
+ /**
47
+ * Lifecycle state for a Warden rule.
48
+ */
49
+ export type WardenRuleLifecycleState = 'deprecated' | 'durable' | 'temporary';
50
+
51
+ /**
52
+ * Queryable concern dimension for Warden rule metadata.
53
+ */
54
+ export type WardenRuleConcern =
55
+ | 'composition'
56
+ | 'general'
57
+ | 'lifecycle'
58
+ | 'meta'
59
+ | 'permits'
60
+ | 'resources'
61
+ | 'results'
62
+ | 'signals';
63
+
64
+ /**
65
+ * Lifecycle metadata for a Warden rule.
66
+ */
67
+ export interface WardenRuleLifecycle {
68
+ /** Current lifecycle state. */
69
+ readonly state: WardenRuleLifecycleState;
70
+ /** Required for temporary or deprecated rules. */
71
+ readonly retireWhen?: string | undefined;
72
+ }
73
+
74
+ /**
75
+ * Documentation or reference target for Warden remediation guidance.
76
+ */
77
+ export interface WardenGuidanceLink {
78
+ /** Human-readable link label. */
79
+ readonly label: string;
80
+ /** Repository-relative documentation path, when the target is in-tree. */
81
+ readonly path?: string | undefined;
82
+ /** External documentation URL, when the target is outside the repo. */
83
+ readonly url?: string | undefined;
84
+ }
85
+
86
+ /**
87
+ * Structured remediation guidance that can be rendered for humans or rendered
88
+ * into agent-facing manifests without scraping diagnostic prose.
89
+ */
90
+ export interface WardenGuidance {
91
+ /** Concise next step for the finding or rule. */
92
+ readonly summary: string;
93
+ /** Ordered remediation steps, when the rule benefits from more detail. */
94
+ readonly steps?: readonly string[] | undefined;
95
+ /** Reference docs that explain the invariant. */
96
+ readonly docs?: readonly WardenGuidanceLink[] | undefined;
97
+ /** Example commands. These are guidance examples, not autofix contracts. */
98
+ readonly commands?: readonly string[] | undefined;
99
+ /** Related rule identifiers that help agents navigate nearby doctrine. */
100
+ readonly relatedRules?: readonly string[] | undefined;
101
+ }
102
+
103
+ /**
104
+ * Transform class a fix belongs to.
105
+ *
106
+ * Names the kind of mechanical change so agents, the guide, and downstream
107
+ * regrades can route by class. `term-rewrite` is the durable name for retired
108
+ * vocabulary renames (`vocab-cutover` is historical wording only).
109
+ * `export-restructure` names structural export rewrites — the finding is not
110
+ * a rename but a reshaping of a module export (for example wrapping a legacy
111
+ * CLI alias map into a `surfaceOverlay({ cli })` entry inside
112
+ * `trailsOverlays`), so downstream Regrade routes it to a structural
113
+ * transform class instead of a span replacement.
114
+ */
115
+ export type WardenFixClass = 'export-restructure' | 'term-rewrite';
116
+
117
+ /**
118
+ * How safe a fix is to apply without human review.
119
+ *
120
+ * - `safe`: a deterministic, scope-correct source edit `warden --fix` may apply
121
+ * automatically.
122
+ * - `review`: the change is understood but needs human judgement (ambiguous
123
+ * span, removal with no mechanical replacement, semantic follow-up). The
124
+ * finding stays reported; `warden --fix` never applies it.
125
+ */
126
+ export type WardenFixSafety = 'review' | 'safe';
127
+
128
+ /**
129
+ * A concrete, half-open source edit `[start, end)` replaced by `replacement`.
130
+ *
131
+ * Offsets are JavaScript string indices into the exact analyzed source text,
132
+ * matching `String.prototype.slice()` and the `offsetToLine` helper the rules
133
+ * already use. Carrying an explicit span (not just a line) lets `warden --fix`
134
+ * apply edits deterministically and
135
+ * last-to-first without re-parsing.
136
+ */
137
+ export interface WardenFixEdit {
138
+ /** Inclusive start offset into the source. */
139
+ readonly start: number;
140
+ /** Exclusive end offset into the source. */
141
+ readonly end: number;
142
+ /** Text that replaces the `[start, end)` span. */
143
+ readonly replacement: string;
144
+ }
145
+
146
+ /**
147
+ * Source targets a fix class can inspect when rendered into downstream tools.
148
+ *
149
+ * Warden itself decides which committed files it scans. This metadata is for
150
+ * consumers such as Regrade that need to derive a narrower collection before
151
+ * invoking the rule against an explicit downstream root.
152
+ */
153
+ export type WardenFixScanTargets = ScanTargets & {
154
+ /**
155
+ * @deprecated Compatibility bridge for Warden-backed Regrade classes that
156
+ * predate PathScope. Prefer Regrade collection scope for new callers.
157
+ */
158
+ readonly ignoredDirectories?: readonly string[];
159
+ };
160
+
161
+ /**
162
+ * Per-finding fix metadata attached to a diagnostic.
163
+ *
164
+ * Authored on the diagnostic at construction because only the rule that matched
165
+ * knows the concrete span. `warden --fix` applies `edits` only when
166
+ * `safety` is `safe`; `review` fixes stay reported with their guidance so a
167
+ * human (or a downstream regrade) can resolve them.
168
+ */
169
+ export interface WardenFix {
170
+ /** Transform class this fix belongs to. */
171
+ readonly class: WardenFixClass;
172
+ /** Whether `warden --fix` may apply this automatically. */
173
+ readonly safety: WardenFixSafety;
174
+ /** Source edits to apply, when `safety` is `safe`. Empty for review-only. */
175
+ readonly edits?: readonly WardenFixEdit[] | undefined;
176
+ /** Why the fix is needed and what it changes, for humans and migration notes. */
177
+ readonly reason: string;
178
+ /** Optional pointer to a fixture or example demonstrating the fix. */
179
+ readonly fixture?: string | undefined;
180
+ }
181
+
182
+ /**
183
+ * Per-rule fix capability, rendered into the guide/manifest.
184
+ *
185
+ * Declares that a rule can emit {@link WardenFix} metadata and the default
186
+ * safety for its fixes, so `warden --help`, the guide, and agent surfaces can
187
+ * advertise fix availability without a finding in hand. Concrete edits still
188
+ * live on each diagnostic's {@link WardenFix}.
189
+ */
190
+ export interface WardenFixCapability {
191
+ /** Transform class the rule's fixes belong to. */
192
+ readonly class: WardenFixClass;
193
+ /** Default safety for fixes this rule emits. */
194
+ readonly safety: WardenFixSafety;
195
+ /** Downstream scan targets for tools that render this fix capability. */
196
+ readonly scanTargets?: WardenFixScanTargets | undefined;
197
+ }
198
+
199
+ /**
200
+ * Stable metadata used to classify Warden rules before dispatch filtering.
201
+ */
202
+ export interface WardenRuleMetadata {
203
+ /** Cumulative Warden depth where this rule first becomes relevant. */
204
+ readonly depth: WardenDepth;
205
+ /** Queryable rule concern for agent-facing surfaces. */
206
+ readonly concern: WardenRuleConcern;
207
+ /** One-line invariant the rule protects. */
208
+ readonly invariant: string;
209
+ /** Rule lifecycle. */
210
+ readonly lifecycle: WardenRuleLifecycle;
211
+ /** Where the rule applies. */
212
+ readonly scope: WardenRuleScope;
213
+ /** Narrowest Warden tier that can answer the rule. */
214
+ readonly tier: WardenRuleTier;
215
+ /** Structured remediation guidance for diagnostics emitted by this rule. */
216
+ readonly guidance?: WardenGuidance | undefined;
217
+ /** Declares that this rule can emit fix metadata, for guide rendering. */
218
+ readonly fix?: WardenFixCapability | undefined;
219
+ }
5
220
 
6
221
  /**
7
222
  * A single diagnostic reported by a warden rule.
8
223
  */
9
- export interface WardenDiagnostic {
224
+ export interface WardenDiagnostic extends RuleDiagnosticBase {
10
225
  /** Rule identifier, e.g. "no-throw-in-implementation" */
11
226
  readonly rule: string;
227
+ /** Optional rule-local diagnostic code for checks with multiple stable findings. */
228
+ readonly code?: string | undefined;
12
229
  /** Severity level */
13
230
  readonly severity: WardenSeverity;
14
231
  /** Human-readable message describing the violation */
@@ -17,13 +234,43 @@ export interface WardenDiagnostic {
17
234
  readonly line: number;
18
235
  /** File path that was analyzed */
19
236
  readonly filePath: string;
237
+ /** Topo/app identity for diagnostics emitted during multi-topo runs. */
238
+ readonly topoName?: string | undefined;
239
+ /** Optional finding-level guidance. Defaults from rule metadata when absent. */
240
+ readonly guidance?: WardenGuidance | undefined;
241
+ /** Optional structured fix for this finding. `warden --fix` applies safe edits. */
242
+ readonly fix?: WardenFix | undefined;
243
+ }
244
+
245
+ /** Public exported symbol observed from a first-party package export target. */
246
+ export interface WardenExportedSymbolDefinition {
247
+ /** Exported binding name. */
248
+ readonly name: string;
249
+ /** Export declaration kind that introduced the binding. */
250
+ readonly kind:
251
+ | 'class'
252
+ | 'const'
253
+ | 'enum'
254
+ | 'export'
255
+ | 'function'
256
+ | 'interface'
257
+ | 'type';
258
+ /** Absolute or runner-provided file path containing the definition. */
259
+ readonly filePath: string;
260
+ /** 1-based source line where the exported definition starts. */
261
+ readonly line: number;
262
+ /** First-party workspace package that owns the definition. */
263
+ readonly workspaceName: string;
264
+ /** Filesystem root of the owning workspace. */
265
+ readonly workspaceRoot: string;
20
266
  }
21
267
 
22
268
  /**
23
- * A warden rule is a function that analyzes source code and returns diagnostics.
269
+ * A warden rule analyzes one source file and returns diagnostics.
24
270
  *
25
- * Rules use string/regex analysis (not full AST parsing) to detect patterns
26
- * that violate Trails conventions.
271
+ * Rules should prefer structured AST helpers when they inspect TypeScript
272
+ * syntax. Simple string checks remain acceptable when the authored rule is
273
+ * explicitly about text that is not parseable syntax, such as generated output.
27
274
  */
28
275
  export interface WardenRule {
29
276
  /** Unique rule identifier */
@@ -32,6 +279,12 @@ export interface WardenRule {
32
279
  readonly severity: WardenSeverity;
33
280
  /** Human-readable description of what the rule enforces */
34
281
  readonly description: string;
282
+ /** Optional inline classification. Built-ins are classified by registry. */
283
+ readonly metadata?: WardenRuleMetadata | undefined;
284
+ /** Source file kinds this rule explicitly supports beyond TypeScript. */
285
+ readonly sourceKinds?:
286
+ | readonly ('documentation' | 'text' | 'typescript')[]
287
+ | undefined;
35
288
  /** Run the rule against source code and return any diagnostics */
36
289
  readonly check: (
37
290
  sourceCode: string,
@@ -40,19 +293,134 @@ export interface WardenRule {
40
293
  }
41
294
 
42
295
  /**
43
- * Options for cross-file rules that need knowledge of all trail IDs in a project.
296
+ * One app's authored `surfaces` overlay `mcp` bindings plus the trail ids
297
+ * that scope which source files the bindings can be compared against.
298
+ */
299
+ export interface AuthoredMcpSurfaceBindingSet {
300
+ /** Stable app/topo label the bindings were authored for. */
301
+ readonly appName: string;
302
+ /** The `mcp` bindings from the app's `surfaces` overlay. */
303
+ readonly bindings: SurfaceBindings;
304
+ /** Trail ids registered in the owning app's topo. */
305
+ readonly trailIds: readonly string[];
306
+ }
307
+
308
+ /**
309
+ * Options for compose-file rules that need knowledge of all trail IDs in a project.
44
310
  */
45
311
  export interface ProjectContext {
312
+ /** Whether this project owns the governed transition registry and must prove completed migrations. */
313
+ readonly governedVocabularyHistoryRequired?: boolean | undefined;
314
+ /** Validated committed Regrade history evidence keyed by governed transition. */
315
+ readonly governedVocabularyHistoryByTransitionId?:
316
+ | ReadonlyMap<string, GovernedVocabularyHistoryEvidence>
317
+ | undefined;
318
+ /** Invalid committed history artifacts observed while building context. */
319
+ readonly governedVocabularyHistoryIssues?:
320
+ | readonly GovernedVocabularyHistoryIssue[]
321
+ | undefined;
322
+ /**
323
+ * Per-app authored `mcp` surface bindings, resolved from the project's
324
+ * topo targets (the serialized graph overlays when available, else the
325
+ * app-module overlay registrations). Each set carries the owning app's
326
+ * trail ids so source rules can attribute a call-site surface option to
327
+ * the app whose overlay authored the default before comparing.
328
+ */
329
+ readonly authoredMcpSurfaceBindingSets?:
330
+ | readonly AuthoredMcpSurfaceBindingSet[]
331
+ | undefined;
332
+ /** All known entity names in the project. */
333
+ readonly knownEntityIds?: ReadonlySet<string>;
334
+ /** Store table IDs used with the CRUD factory across the project. */
335
+ readonly crudTableIds?: ReadonlySet<string>;
46
336
  /** All known trail IDs in the project */
47
337
  readonly knownTrailIds: ReadonlySet<string>;
48
- /** All trail IDs referenced as detour targets across the project */
49
- readonly detourTargetTrailIds?: ReadonlySet<string>;
338
+ /** Trail IDs registered in configured app topo targets. */
339
+ readonly topoTrailIds?: ReadonlySet<string>;
340
+ /** Declared entity references keyed by source entity name. */
341
+ readonly entityReferencesByName?: ReadonlyMap<string, readonly string[]>;
342
+ /** All known resource IDs in the project */
343
+ readonly knownResourceIds?: ReadonlySet<string>;
344
+ /** All known signal IDs in the project */
345
+ readonly knownSignalIds?: ReadonlySet<string>;
346
+ /** All trail IDs referenced by declared composes arrays across the project. */
347
+ readonly composeTargetTrailIds?: ReadonlySet<string>;
348
+ /** Signal IDs referenced by trail `on` arrays across the project. */
349
+ readonly onTargetSignalIds?: ReadonlySet<string>;
350
+ /** Store table IDs used with reconcile trails across the project. */
351
+ readonly reconcileTableIds?: ReadonlySet<string>;
352
+ /** Resolved import facts keyed by importer file path across the project. */
353
+ readonly importResolutionsByFile?: ReadonlyMap<
354
+ string,
355
+ readonly WardenImportResolution[]
356
+ >;
357
+ /** Resolved docs/specifier facts keyed by documentation file path. */
358
+ readonly documentedImportResolutionsByFile?: ReadonlyMap<
359
+ string,
360
+ readonly WardenImportResolution[]
361
+ >;
362
+ /** Non-private published @ontrails workspaces discovered from the root manifest. */
363
+ readonly publicWorkspaces?: ReadonlyMap<string, WardenPublicWorkspace>;
364
+ /** Public package export definitions grouped by exported name. */
365
+ readonly exportedSymbolDefinitionsByName?: ReadonlyMap<
366
+ string,
367
+ readonly WardenExportedSymbolDefinition[]
368
+ >;
369
+ /** Normalized trail intents by trail ID across the project. */
370
+ readonly trailIntentsById?: ReadonlyMap<string, Intent>;
371
+ /**
372
+ * CRUD operation coverage per entity aggregated across the project.
373
+ *
374
+ * Keys are stable entity IDs (authored entity names, `imported:<local>`
375
+ * sentinels for entities imported from another module, or store-table IDs
376
+ * produced by `deriveStoreTableId`). Values are the set of CRUD operations
377
+ * (`create`, `read`, `update`, `delete`, `list`) observed for that entity.
378
+ *
379
+ * Enables compose-file completeness evaluation so one-file-per-operation
380
+ * layouts (e.g. separate `create.ts`, `read.ts`) do not trip file-scoped
381
+ * coverage warnings.
382
+ */
383
+ readonly crudCoverageByEntity?: ReadonlyMap<string, ReadonlySet<string>>;
384
+ }
385
+
386
+ export interface GovernedVocabularyHistoryEvidence {
387
+ readonly caseSensitive: boolean;
388
+ readonly id: string;
389
+ readonly latestFormJudgments: readonly GovernedVocabularyHistoryFormJudgment[];
390
+ readonly path: string;
391
+ readonly provenance?: GovernedVocabularyHistoryProvenance;
392
+ readonly runCount: number;
393
+ readonly transitionId: string;
394
+ }
395
+
396
+ /** One compact vocabulary-form judgment preserved in committed Regrade history. */
397
+ export interface GovernedVocabularyHistoryFormJudgment {
398
+ readonly disposition: string;
399
+ readonly form: string;
400
+ readonly reason?: string | undefined;
401
+ readonly representative?:
402
+ | {
403
+ readonly line: number;
404
+ readonly path: string;
405
+ }
406
+ | undefined;
407
+ readonly target?: string | undefined;
408
+ }
409
+
410
+ export interface GovernedVocabularyHistoryIssue {
411
+ readonly message: string;
412
+ readonly path: string;
413
+ readonly transitionId?: string;
50
414
  }
51
415
 
52
416
  /**
53
417
  * A project-aware rule that requires knowledge of all trail IDs.
54
418
  */
55
419
  export interface ProjectAwareWardenRule extends WardenRule {
420
+ /** Run project-wide validation once, independently of any source file. */
421
+ readonly checkProject?: (
422
+ context: ProjectContext
423
+ ) => readonly WardenDiagnostic[];
56
424
  /** Run the rule with project-level context */
57
425
  readonly checkWithContext: (
58
426
  sourceCode: string,
@@ -60,3 +428,30 @@ export interface ProjectAwareWardenRule extends WardenRule {
60
428
  context: ProjectContext
61
429
  ) => readonly WardenDiagnostic[];
62
430
  }
431
+
432
+ /**
433
+ * A topo-aware warden rule inspects the compiled runtime trail graph —
434
+ * actual `Trail` objects with resolved types, accessor shapes, detour
435
+ * declarations, `pattern` field values, and other runtime-only data that
436
+ * is unavailable to AST-based rules.
437
+ *
438
+ * Unlike `WardenRule` and `ProjectAwareWardenRule`, which analyze source
439
+ * code on a per-file basis, a `TopoAwareWardenRule` runs once per topo
440
+ * and returns diagnostics spanning the whole graph. A rule file must
441
+ * implement exactly one of the three rule kinds.
442
+ */
443
+ export interface TopoAwareWardenRule {
444
+ /** Unique rule identifier */
445
+ readonly name: string;
446
+ /** Default severity */
447
+ readonly severity: WardenSeverity;
448
+ /** Human-readable description of what the rule enforces */
449
+ readonly description: string;
450
+ /** Optional inline classification. Built-ins are classified by registry. */
451
+ readonly metadata?: WardenRuleMetadata | undefined;
452
+ /** Run the rule against the resolved topo and return any diagnostics */
453
+ readonly checkTopo: (
454
+ topo: Topo,
455
+ context?: { readonly graph?: TopoGraph | undefined } | undefined
456
+ ) => readonly WardenDiagnostic[] | Promise<readonly WardenDiagnostic[]>;
457
+ }
@@ -0,0 +1,85 @@
1
+ import type { ActivationSourceKind, Topo } from '@ontrails/core';
2
+
3
+ import type { TopoAwareWardenRule, WardenDiagnostic } from './types.js';
4
+
5
+ const RULE_NAME = 'unmaterialized-activation-source';
6
+ const TOPO_FILE = '<topo>';
7
+
8
+ const MATERIALIZED_SOURCE_KINDS = new Set<ActivationSourceKind>([
9
+ 'queue',
10
+ 'schedule',
11
+ 'signal',
12
+ 'webhook',
13
+ ]);
14
+
15
+ const PENDING_SOURCE_KINDS = new Set<ActivationSourceKind>();
16
+
17
+ interface SourceConsumers {
18
+ readonly id: string;
19
+ readonly kind: ActivationSourceKind;
20
+ readonly key: string;
21
+ readonly trailIds: readonly string[];
22
+ }
23
+
24
+ const sourceKey = (kind: ActivationSourceKind, id: string): string =>
25
+ `${kind}:${id}`;
26
+
27
+ const sortedUnique = (values: Iterable<string>): readonly string[] =>
28
+ [...new Set(values)].toSorted();
29
+
30
+ const collectSourceConsumers = (topo: Topo): readonly SourceConsumers[] => {
31
+ const consumersBySource = new Map<
32
+ string,
33
+ {
34
+ readonly id: string;
35
+ readonly kind: ActivationSourceKind;
36
+ readonly trailIds: Set<string>;
37
+ }
38
+ >();
39
+
40
+ for (const trail of topo.list()) {
41
+ for (const activation of trail.activationSources) {
42
+ const key = sourceKey(activation.source.kind, activation.source.id);
43
+ const current =
44
+ consumersBySource.get(key) ??
45
+ ({
46
+ id: activation.source.id,
47
+ kind: activation.source.kind,
48
+ trailIds: new Set<string>(),
49
+ } as const);
50
+ current.trailIds.add(trail.id);
51
+ consumersBySource.set(key, current);
52
+ }
53
+ }
54
+
55
+ return [...consumersBySource.entries()]
56
+ .map(([key, source]) => ({
57
+ id: source.id,
58
+ key,
59
+ kind: source.kind,
60
+ trailIds: sortedUnique(source.trailIds),
61
+ }))
62
+ .toSorted((a, b) => a.key.localeCompare(b.key));
63
+ };
64
+
65
+ const isUnmaterialized = (kind: ActivationSourceKind): boolean =>
66
+ PENDING_SOURCE_KINDS.has(kind) && !MATERIALIZED_SOURCE_KINDS.has(kind);
67
+
68
+ const buildDiagnostic = (source: SourceConsumers): WardenDiagnostic => ({
69
+ filePath: TOPO_FILE,
70
+ line: 1,
71
+ message: `Activation source "${source.id}" of kind "${source.kind}" activates trail${source.trailIds.length === 1 ? '' : 's'} ${source.trailIds.map((id) => `"${id}"`).join(', ')} but no built-in materializer is available in this stack. Add the materializer before relying on runtime delivery, or defer the source declaration until the materializer lands.`,
72
+ rule: RULE_NAME,
73
+ severity: 'warn',
74
+ });
75
+
76
+ export const unmaterializedActivationSource: TopoAwareWardenRule = {
77
+ checkTopo: (topo) =>
78
+ collectSourceConsumers(topo)
79
+ .filter((source) => isUnmaterialized(source.kind))
80
+ .map((source) => buildDiagnostic(source)),
81
+ description:
82
+ 'Warn when declared activation sources do not have an available runtime materializer.',
83
+ name: RULE_NAME,
84
+ severity: 'warn',
85
+ };