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

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