@ontrails/warden 1.0.0-beta.39 → 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 (134) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +5 -5
  3. package/package.json +10 -12
  4. package/src/cli.ts +49 -44
  5. package/src/command.ts +2 -2
  6. package/src/drift.ts +4 -3
  7. package/src/index.ts +3 -2
  8. package/src/project-context.ts +3 -3
  9. package/src/resolve.ts +2 -2
  10. package/src/rules/captured-kernel.ts +375 -0
  11. package/src/rules/circular-refs.ts +12 -16
  12. package/src/rules/cli-command-route-coherence.ts +1 -1
  13. package/src/rules/composes-declarations.ts +15 -13
  14. package/src/rules/context-no-surface-types.ts +4 -3
  15. package/src/rules/dead-internal-trail.ts +3 -3
  16. package/src/rules/dead-public-trail.ts +3 -3
  17. package/src/rules/draft-file-marking.ts +4 -9
  18. package/src/rules/draft-visible-debt.ts +3 -8
  19. package/src/rules/duplicate-public-contract.ts +50 -5
  20. package/src/rules/{contour-exists.ts → entity-exists.ts} +67 -64
  21. package/src/rules/entity-ids.ts +15 -0
  22. package/src/rules/error-mapping-completeness.ts +2 -2
  23. package/src/rules/example-valid.ts +84 -84
  24. package/src/rules/fires-declarations.ts +27 -22
  25. package/src/rules/governed-symbol-residue.ts +339 -32
  26. package/src/rules/implementation-returns-result.ts +8 -8
  27. package/src/rules/incomplete-accessor-for-standard-op.ts +15 -15
  28. package/src/rules/incomplete-crud.ts +42 -40
  29. package/src/rules/index.ts +9 -6
  30. package/src/rules/intent-propagation.ts +3 -5
  31. package/src/rules/layer-field-name-drift.ts +2 -2
  32. package/src/rules/library-projection-coherence.ts +2 -2
  33. package/src/rules/metadata.ts +37 -15
  34. package/src/rules/missing-reconcile.ts +3 -4
  35. package/src/rules/missing-visibility.ts +2 -1
  36. package/src/rules/no-destructured-compose.ts +14 -12
  37. package/src/rules/no-direct-implementation-call.ts +12 -7
  38. package/src/rules/no-legacy-cli-alias-export.ts +2 -2
  39. package/src/rules/no-native-error-result.ts +2 -2
  40. package/src/rules/no-redundant-result-error-wrap.ts +10 -10
  41. package/src/rules/no-sync-result-assumption.ts +84 -78
  42. package/src/rules/no-throw-in-detour-recover.ts +2 -2
  43. package/src/rules/no-throw-in-implementation.ts +13 -7
  44. package/src/rules/no-top-level-surface.ts +2 -2
  45. package/src/rules/on-references-exist.ts +3 -3
  46. package/src/rules/orphaned-signal.ts +4 -5
  47. package/src/rules/owner-projection-parity.ts +2 -2
  48. package/src/rules/public-export-example-coverage.ts +10 -4
  49. package/src/rules/public-internal-deep-imports.ts +2 -3
  50. package/src/rules/read-intent-fires.ts +4 -3
  51. package/src/rules/reference-exists.ts +20 -21
  52. package/src/rules/registry-names.ts +6 -4
  53. package/src/rules/resolved-import-boundary.ts +1 -1
  54. package/src/rules/resource-declarations.ts +21 -17
  55. package/src/rules/resource-exists.ts +4 -2
  56. package/src/rules/resource-id-grammar.ts +2 -2
  57. package/src/rules/resource-mock-coverage.ts +2 -2
  58. package/src/rules/retired-vocabulary.ts +242 -5
  59. package/src/rules/source/composition.ts +165 -0
  60. package/src/rules/source/drafts.ts +164 -0
  61. package/src/rules/source/entities.ts +618 -0
  62. package/src/rules/source/pragmas.ts +45 -0
  63. package/src/rules/source/resources.ts +64 -0
  64. package/src/rules/source/signals.ts +397 -0
  65. package/src/rules/source/stores.ts +310 -0
  66. package/src/rules/static-resource-accessor-preference.ts +15 -11
  67. package/src/rules/surface-overlay-coherence.ts +1 -1
  68. package/src/rules/surface-trailhead-coherence.ts +2 -2
  69. package/src/rules/trail-fork-coaching.ts +27 -18
  70. package/src/rules/trail-versioning-source.ts +16 -12
  71. package/src/rules/trail-versioning-topo.ts +2 -2
  72. package/src/rules/trailhead-override-divergence.ts +2 -2
  73. package/src/rules/types.ts +7 -7
  74. package/src/rules/unmaterialized-activation-source.ts +1 -0
  75. package/src/rules/unreachable-detour-shadowing.ts +2 -2
  76. package/src/rules/valid-describe-refs.ts +2 -2
  77. package/src/rules/warden-export-symmetry.ts +3 -3
  78. package/src/rules/warden-rules-use-ast.ts +12 -17
  79. package/src/trails/activation-orphan.trail.ts +5 -5
  80. package/src/trails/captured-kernel.trail.ts +108 -0
  81. package/src/trails/circular-refs.trail.ts +6 -6
  82. package/src/trails/cli-command-route-coherence.trail.ts +2 -2
  83. package/src/trails/composes-declarations.trail.ts +1 -1
  84. package/src/trails/context-no-surface-types.trail.ts +1 -1
  85. package/src/trails/dead-internal-trail.trail.ts +2 -2
  86. package/src/trails/dead-public-trail.trail.ts +1 -1
  87. package/src/trails/deprecation-without-guidance.trail.ts +1 -1
  88. package/src/trails/duplicate-public-contract.trail.ts +3 -3
  89. package/src/trails/entity-exists.trail.ts +21 -0
  90. package/src/trails/example-valid.trail.ts +3 -3
  91. package/src/trails/fires-declarations.trail.ts +1 -1
  92. package/src/trails/{fork-without-preserved-blaze.trail.ts → fork-without-preserved-implementation.trail.ts} +4 -4
  93. package/src/trails/implementation-returns-result.trail.ts +1 -1
  94. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +4 -4
  95. package/src/trails/index.ts +3 -2
  96. package/src/trails/intent-propagation.trail.ts +2 -2
  97. package/src/trails/library-projection-coherence.trail.ts +2 -2
  98. package/src/trails/marker-schema-unsupported.trail.ts +1 -1
  99. package/src/trails/missing-visibility.trail.ts +1 -1
  100. package/src/trails/no-destructured-compose.trail.ts +5 -5
  101. package/src/trails/no-direct-implementation-call.trail.ts +1 -1
  102. package/src/trails/no-redundant-result-error-wrap.trail.ts +2 -2
  103. package/src/trails/no-sync-result-assumption.trail.ts +2 -2
  104. package/src/trails/no-throw-in-detour-recover.trail.ts +1 -1
  105. package/src/trails/no-throw-in-implementation.trail.ts +1 -1
  106. package/src/trails/on-references-exist.trail.ts +1 -1
  107. package/src/trails/pending-force.trail.ts +1 -1
  108. package/src/trails/permit-governance.trail.ts +2 -2
  109. package/src/trails/prefer-schema-inference.trail.ts +1 -1
  110. package/src/trails/public-output-schema.trail.ts +2 -2
  111. package/src/trails/public-union-output-discriminants.trail.ts +1 -1
  112. package/src/trails/read-intent-fires.trail.ts +1 -1
  113. package/src/trails/reference-exists.trail.ts +5 -5
  114. package/src/trails/resource-declarations.trail.ts +1 -1
  115. package/src/trails/resource-exists.trail.ts +1 -1
  116. package/src/trails/run.ts +4 -6
  117. package/src/trails/scheduled-destroy-intent.trail.ts +2 -2
  118. package/src/trails/schema.ts +9 -9
  119. package/src/trails/signal-graph-coaching.trail.ts +2 -2
  120. package/src/trails/static-resource-accessor-preference.trail.ts +1 -1
  121. package/src/trails/surface-overlay-coherence.trail.ts +1 -1
  122. package/src/trails/trail-fork-coaching.trail.ts +1 -1
  123. package/src/trails/unmaterialized-activation-source.trail.ts +5 -5
  124. package/src/trails/valid-detour-contract.trail.ts +1 -1
  125. package/src/trails/version-gap.trail.ts +1 -1
  126. package/src/trails/version-pinned-compose.trail.ts +1 -1
  127. package/src/trails/version-without-examples.trail.ts +1 -1
  128. package/src/trails/warden-rules-use-ast.trail.ts +2 -2
  129. package/src/trails/webhook-route-collision.trail.ts +2 -2
  130. package/src/trails/wrap-rule.ts +17 -17
  131. package/src/ast.ts +0 -137
  132. package/src/rules/ast.ts +0 -4143
  133. package/src/rules/contour-ids.ts +0 -15
  134. package/src/trails/contour-exists.trail.ts +0 -21
@@ -0,0 +1,310 @@
1
+ /** Warden-private store/factory pattern helpers. */
2
+
3
+ import {
4
+ extractBindingName,
5
+ findConfigProperty,
6
+ getPropertyName,
7
+ identifierName,
8
+ walk,
9
+ } from '@ontrails/source';
10
+ import type { AstNode } from '@ontrails/source';
11
+
12
+ export interface StoreTableDefinition {
13
+ /** Table name declared inside store({ ... }). */
14
+ readonly name: string;
15
+ /**
16
+ * Local binding name of the enclosing `store(...)` declaration, if the
17
+ * `store(...)` call is bound to a `const`/`let`/`var` (e.g. `db` in
18
+ * `const db = store({ ... })`). Null for anonymous stores.
19
+ */
20
+ readonly storeBinding: string | null;
21
+ /**
22
+ * Stable composite key for this table in the form `${storeBinding}:${name}`,
23
+ * falling back to the bare `name` when the store is anonymous. Use this for
24
+ * compose-rule / compose-file keying so two stores with the same table name
25
+ * never collide.
26
+ */
27
+ readonly key: string;
28
+ /** Start offset of the table property declaration. */
29
+ readonly start: number;
30
+ /** Whether the authored table opts into version tracking. */
31
+ readonly versioned: boolean;
32
+ }
33
+
34
+ /**
35
+ * Build a composite key for a store table: `${storeBinding}:${tableName}`,
36
+ * falling back to the bare `tableName` when the enclosing store has no local
37
+ * binding. Centralized so rule keying stays stable.
38
+ *
39
+ * @remarks
40
+ * The key is intentionally file-local (no module path prefix). Compose-file
41
+ * aggregation in `ProjectContext` merges keys from all files, so two files
42
+ * with `const db = store({ notes: ... })` both produce `db:notes` — this is
43
+ * the desired behavior because the warden checks for *pattern completeness*
44
+ * across the project and matching keys signals that the same logical table
45
+ * is covered. If two genuinely different tables share a binding and name,
46
+ * that is a code-level naming collision the developer should resolve.
47
+ */
48
+ export const makeStoreTableKey = (
49
+ storeBinding: string | null,
50
+ tableName: string
51
+ ): string => (storeBinding ? `${storeBinding}:${tableName}` : tableName);
52
+
53
+ const isBooleanLiteral = (node: AstNode | undefined): boolean =>
54
+ Boolean(
55
+ node &&
56
+ ((node.type === 'BooleanLiteral' &&
57
+ (node as unknown as { value?: unknown }).value === true) ||
58
+ (node.type === 'Literal' &&
59
+ (node as unknown as { value?: unknown }).value === true))
60
+ );
61
+
62
+ /**
63
+ * Check if a node is a `CallExpression` to the identifier `name`.
64
+ *
65
+ * e.g. `isNamedCall(node, 'store')` matches `store({...})` but not
66
+ * `someObj.store()` or `storeAlt()`.
67
+ */
68
+ export const isNamedCall = (node: AstNode | undefined, name: string): boolean =>
69
+ !!node &&
70
+ node.type === 'CallExpression' &&
71
+ identifierName((node as unknown as { callee?: AstNode }).callee) === name;
72
+
73
+ /**
74
+ * Narrow a member-expression node (`a.b` or `a['b']`) to its `object` /
75
+ * `property` pair, returning `null` for anything else.
76
+ */
77
+ export const getMemberExpression = (
78
+ node: AstNode | undefined
79
+ ): { readonly object?: AstNode; readonly property?: AstNode } | null => {
80
+ if (
81
+ !node ||
82
+ (node.type !== 'MemberExpression' && node.type !== 'StaticMemberExpression')
83
+ ) {
84
+ return null;
85
+ }
86
+
87
+ return node as unknown as {
88
+ readonly object?: AstNode;
89
+ readonly property?: AstNode;
90
+ };
91
+ };
92
+
93
+ /**
94
+ * Resolve a `<store>.tables.<name>` member expression to its store binding
95
+ * and table name.
96
+ *
97
+ * Returns `null` for anything that isn't a two-level member access ending in
98
+ * `.tables.<name>`. The store binding is the identifier of the object owning
99
+ * `.tables` — typically the local binding from `const db = store(...)`.
100
+ */
101
+ export const extractStoreTableFromMember = (
102
+ node: AstNode | undefined
103
+ ): {
104
+ readonly storeBinding: string | null;
105
+ readonly tableName: string;
106
+ } | null => {
107
+ const member = getMemberExpression(node);
108
+ const tableName = member ? getPropertyName(member.property) : null;
109
+ const tablesMember = member ? getMemberExpression(member.object) : null;
110
+ if (!tableName || !tablesMember) {
111
+ return null;
112
+ }
113
+
114
+ if (getPropertyName(tablesMember.property) !== 'tables') {
115
+ return null;
116
+ }
117
+
118
+ const storeBinding = identifierName(tablesMember.object) ?? null;
119
+ return { storeBinding, tableName };
120
+ };
121
+
122
+ /**
123
+ * Collect `const foo = <store>.tables.<name>` bindings from a parsed file,
124
+ * keyed by the local binding name. Values are the composite table key
125
+ * (`${storeBinding}:${tableName}`) so callers can dedupe across stores that
126
+ * share a table name.
127
+ */
128
+ export const collectNamedStoreTableIds = (
129
+ ast: AstNode
130
+ ): ReadonlyMap<string, string> => {
131
+ const ids = new Map<string, string>();
132
+
133
+ walk(ast, (node) => {
134
+ if (node.type !== 'VariableDeclarator') {
135
+ return;
136
+ }
137
+
138
+ const { id, init } = node as unknown as {
139
+ readonly id?: AstNode;
140
+ readonly init?: AstNode;
141
+ };
142
+ const name = extractBindingName(id);
143
+ const table = extractStoreTableFromMember(init);
144
+ if (name && table) {
145
+ ids.set(name, makeStoreTableKey(table.storeBinding, table.tableName));
146
+ }
147
+ });
148
+
149
+ return ids;
150
+ };
151
+
152
+ /**
153
+ * Resolve an argument node to a composite store-table key
154
+ * (`${storeBinding}:${tableName}` or bare `tableName` when anonymous).
155
+ *
156
+ * Handles the two authoring patterns:
157
+ * - direct member access: `db.tables.notes`
158
+ * - identifier reference: `const notesTable = db.tables.notes; crud(notesTable, …)`
159
+ */
160
+ export const deriveStoreTableId = (
161
+ node: AstNode | undefined,
162
+ namedStoreTableIds: ReadonlyMap<string, string>
163
+ ): string | null => {
164
+ if (!node) {
165
+ return null;
166
+ }
167
+
168
+ if (node.type === 'Identifier') {
169
+ const name = identifierName(node);
170
+ return name ? (namedStoreTableIds.get(name) ?? null) : null;
171
+ }
172
+
173
+ const member = extractStoreTableFromMember(node);
174
+ return member
175
+ ? makeStoreTableKey(member.storeBinding, member.tableName)
176
+ : null;
177
+ };
178
+
179
+ const extractStoreTableDefinitions = (
180
+ node: AstNode,
181
+ storeBinding: string | null
182
+ ): readonly StoreTableDefinition[] => {
183
+ if (!isNamedCall(node, 'store')) {
184
+ return [];
185
+ }
186
+
187
+ const [tablesArg] = ((node as unknown as { arguments?: readonly AstNode[] })
188
+ .arguments ?? []) as readonly AstNode[];
189
+ if (!tablesArg || tablesArg.type !== 'ObjectExpression') {
190
+ return [];
191
+ }
192
+
193
+ const properties = tablesArg['properties'] as readonly AstNode[] | undefined;
194
+ if (!properties) {
195
+ return [];
196
+ }
197
+
198
+ return properties.flatMap((property) => {
199
+ if (property.type !== 'Property') {
200
+ return [];
201
+ }
202
+
203
+ const name = getPropertyName(property.key);
204
+ const value = property.value as AstNode | undefined;
205
+ if (!name || value?.type !== 'ObjectExpression') {
206
+ return [];
207
+ }
208
+
209
+ const versionedProp = findConfigProperty(value, 'versioned');
210
+ return [
211
+ {
212
+ key: makeStoreTableKey(storeBinding, name),
213
+ name,
214
+ start: property.start,
215
+ storeBinding,
216
+ versioned: isBooleanLiteral(
217
+ versionedProp?.value as AstNode | undefined
218
+ ),
219
+ },
220
+ ];
221
+ });
222
+ };
223
+
224
+ export const findStoreTableDefinitions = (
225
+ ast: AstNode
226
+ ): readonly StoreTableDefinition[] => {
227
+ const definitions: StoreTableDefinition[] = [];
228
+ const seenStoreCalls = new WeakSet<AstNode>();
229
+
230
+ // First pass: bound stores (walk VariableDeclarators so we know the binding).
231
+ walk(ast, (node) => {
232
+ if (node.type !== 'VariableDeclarator') {
233
+ return;
234
+ }
235
+
236
+ const { id, init } = node as unknown as {
237
+ readonly id?: AstNode;
238
+ readonly init?: AstNode;
239
+ };
240
+ if (!init || !isNamedCall(init, 'store')) {
241
+ return;
242
+ }
243
+
244
+ seenStoreCalls.add(init);
245
+ const storeBinding = extractBindingName(id);
246
+ definitions.push(...extractStoreTableDefinitions(init, storeBinding));
247
+ });
248
+
249
+ // Second pass: anonymous `store({...})` calls not bound to a variable
250
+ // (e.g. an inline default export). Use the bare table name as the key.
251
+ walk(ast, (node) => {
252
+ if (!isNamedCall(node, 'store') || seenStoreCalls.has(node)) {
253
+ return;
254
+ }
255
+ definitions.push(...extractStoreTableDefinitions(node, null));
256
+ });
257
+
258
+ return definitions;
259
+ };
260
+
261
+ export const collectCrudTableIds = (ast: AstNode): ReadonlySet<string> => {
262
+ const ids = new Set<string>();
263
+ const namedStoreTableIds = collectNamedStoreTableIds(ast);
264
+
265
+ walk(ast, (node) => {
266
+ if (!isNamedCall(node, 'crud')) {
267
+ return;
268
+ }
269
+
270
+ const [tableArg] = ((node as unknown as { arguments?: readonly AstNode[] })
271
+ .arguments ?? []) as readonly AstNode[];
272
+ const tableId = deriveStoreTableId(tableArg, namedStoreTableIds);
273
+ if (tableId) {
274
+ ids.add(tableId);
275
+ }
276
+ });
277
+
278
+ return ids;
279
+ };
280
+
281
+ export const collectReconcileTableIds = (ast: AstNode): ReadonlySet<string> => {
282
+ const ids = new Set<string>();
283
+ const namedStoreTableIds = collectNamedStoreTableIds(ast);
284
+
285
+ walk(ast, (node) => {
286
+ if (!isNamedCall(node, 'reconcile')) {
287
+ return;
288
+ }
289
+
290
+ const [configArg] = ((
291
+ node as unknown as {
292
+ arguments?: readonly AstNode[];
293
+ }
294
+ ).arguments ?? []) as readonly AstNode[];
295
+ if (!configArg || configArg.type !== 'ObjectExpression') {
296
+ return;
297
+ }
298
+
299
+ const tableProp = findConfigProperty(configArg, 'table');
300
+ const tableId = deriveStoreTableId(
301
+ tableProp?.value as AstNode | undefined,
302
+ namedStoreTableIds
303
+ );
304
+ if (tableId) {
305
+ ids.add(tableId);
306
+ }
307
+ });
308
+
309
+ return ids;
310
+ };
@@ -6,11 +6,11 @@
6
6
  * Dynamic IDs and generic framework internals remain outside its scope.
7
7
  */
8
8
 
9
+ import { collectNamedResourceIds } from './source/resources.js';
9
10
  import {
10
- collectNamedResourceIds,
11
11
  extractFirstStringArg,
12
- findBlazeBodies,
13
12
  findConfigProperty,
13
+ findImplementationBodies,
14
14
  findTrailDefinitions,
15
15
  getNodeCallee,
16
16
  getNodeId,
@@ -32,8 +32,8 @@ import {
32
32
  walk,
33
33
  walkScope,
34
34
  walkWithScopes,
35
- } from './ast.js';
36
- import type { AstNode } from './ast.js';
35
+ } from '@ontrails/source';
36
+ import type { AstNode } from '@ontrails/source';
37
37
  import { isFrameworkInternalFile, isTestFile } from './scan.js';
38
38
  import type { WardenDiagnostic, WardenRule } from './types.js';
39
39
 
@@ -125,16 +125,20 @@ const extractDeclaredStaticResources = (
125
125
  return name ? [{ id: resourceIdsByName.get(name) ?? null, name }] : [];
126
126
  });
127
127
 
128
- const extractContextParamNode = (blazeBody: AstNode): AstNode | null => {
129
- const params = blazeBody['params'] as readonly AstNode[] | undefined;
128
+ const extractContextParamNode = (
129
+ implementationBody: AstNode
130
+ ): AstNode | null => {
131
+ const params = implementationBody['params'] as readonly AstNode[] | undefined;
130
132
  if (!params || params.length < 2) {
131
133
  return null;
132
134
  }
133
135
  return params[1] ?? null;
134
136
  };
135
137
 
136
- const extractContextParamName = (blazeBody: AstNode): string | null => {
137
- const param = extractContextParamNode(blazeBody);
138
+ const extractContextParamName = (
139
+ implementationBody: AstNode
140
+ ): string | null => {
141
+ const param = extractContextParamNode(implementationBody);
138
142
  if (!param) {
139
143
  return null;
140
144
  }
@@ -340,7 +344,7 @@ const collectResourceLookups = (
340
344
  ): readonly ResourceLookup[] => {
341
345
  const lookups: ResourceLookup[] = [];
342
346
 
343
- for (const body of findBlazeBodies(config)) {
347
+ for (const body of findImplementationBodies(config)) {
344
348
  const ctxNames = buildCtxNames(body);
345
349
  const resourceAliases = new Set([
346
350
  ...collectParamResourceAliases(body),
@@ -465,7 +469,7 @@ const collectInlineDependencyConstructions = (
465
469
  ): readonly InlineDependencyConstruction[] => {
466
470
  const constructions: InlineDependencyConstruction[] = [];
467
471
 
468
- for (const body of findBlazeBodies(config)) {
472
+ for (const body of findImplementationBodies(config)) {
469
473
  walkWithScopes(
470
474
  body,
471
475
  (node, scopes) => {
@@ -517,7 +521,7 @@ const buildInlineDependencyDiagnostic = (
517
521
  line: offsetToLine(sourceCode, construction.start),
518
522
  message:
519
523
  `Trail "${trailId}": ${construction.rendered} constructs an external dependency ` +
520
- 'inside blaze logic. Move the client behind a resource definition and declare it in resources.',
524
+ 'inside implementation logic. Move the client behind a resource definition and declare it in resources.',
521
525
  rule: RULE_NAME,
522
526
  severity: 'warn',
523
527
  });
@@ -27,7 +27,7 @@ import type {
27
27
  SurfaceOverlayBindings,
28
28
  Topo,
29
29
  } from '@ontrails/core';
30
- import type { TopoGraph } from '@ontrails/topographer';
30
+ import type { TopoGraph } from '@ontrails/topography';
31
31
 
32
32
  import type { TopoAwareWardenRule, WardenDiagnostic } from './types.js';
33
33
 
@@ -18,8 +18,8 @@ import {
18
18
  offsetToLine,
19
19
  parse,
20
20
  walk,
21
- } from './ast.js';
22
- import type { AstNode } from './ast.js';
21
+ } from '@ontrails/source';
22
+ import type { AstNode } from '@ontrails/source';
23
23
  import type { WardenDiagnostic, WardenRule } from './types.js';
24
24
 
25
25
  const RULE_NAME = 'surface-trailhead-coherence';
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  extractStringOrTemplateLiteral,
3
- findBlazeBodies,
4
3
  findConfigProperty,
4
+ findImplementationBodies,
5
5
  findTrailDefinitions,
6
6
  getNodeArgument,
7
7
  getNodeArguments,
@@ -26,8 +26,8 @@ import {
26
26
  offsetToLine,
27
27
  parse,
28
28
  walkScope,
29
- } from './ast.js';
30
- import type { AstNode } from './ast.js';
29
+ } from '@ontrails/source';
30
+ import type { AstNode } from '@ontrails/source';
31
31
  import type { WardenDiagnostic, WardenRule } from './types.js';
32
32
 
33
33
  const RULE_NAME = 'trail-fork-coaching';
@@ -49,7 +49,7 @@ interface SchemaBindingRecord {
49
49
 
50
50
  type SchemaBindings = ReadonlyMap<string, readonly SchemaBindingRecord[]>;
51
51
 
52
- interface BlazeInputBindings {
52
+ interface ImplementationInputBindings {
53
53
  readonly aliases: ReadonlyMap<string, string>;
54
54
  readonly inputParamName: string | null;
55
55
  }
@@ -344,8 +344,8 @@ const findControlFields = (
344
344
  return fields;
345
345
  };
346
346
 
347
- const blazeParams = (blaze: AstNode): readonly AstNode[] =>
348
- getNodeParams(blaze) ?? [];
347
+ const implementationParams = (implementation: AstNode): readonly AstNode[] =>
348
+ getNodeParams(implementation) ?? [];
349
349
 
350
350
  const memberFieldName = (
351
351
  node: AstNode | undefined,
@@ -397,13 +397,13 @@ const collectDestructuredFieldAliasesFromPattern = (
397
397
  };
398
398
 
399
399
  const collectDestructuredFieldAliases = (
400
- blaze: AstNode,
400
+ implementation: AstNode,
401
401
  inputParamName: string,
402
402
  fields: ReadonlySet<string>
403
403
  ): ReadonlyMap<string, string> => {
404
404
  const aliases = new Map<string, string>();
405
405
 
406
- walkScope(blaze, (node) => {
406
+ walkScope(implementation, (node) => {
407
407
  if (node.type !== 'VariableDeclarator') {
408
408
  return;
409
409
  }
@@ -427,15 +427,19 @@ const collectDestructuredFieldAliases = (
427
427
  return aliases;
428
428
  };
429
429
 
430
- const collectBlazeInputBindings = (
431
- blaze: AstNode,
430
+ const collectImplementationInputBindings = (
431
+ implementation: AstNode,
432
432
  fields: ReadonlySet<string>
433
- ): BlazeInputBindings | null => {
434
- const [inputParam] = blazeParams(blaze);
433
+ ): ImplementationInputBindings | null => {
434
+ const [inputParam] = implementationParams(implementation);
435
435
  const inputParamName = identifierName(inputParam);
436
436
  if (inputParamName) {
437
437
  return {
438
- aliases: collectDestructuredFieldAliases(blaze, inputParamName, fields),
438
+ aliases: collectDestructuredFieldAliases(
439
+ implementation,
440
+ inputParamName,
441
+ fields
442
+ ),
439
443
  inputParamName,
440
444
  };
441
445
  }
@@ -511,13 +515,13 @@ const comparisonBranchesOnField = (
511
515
  };
512
516
 
513
517
  const branchesOnField = (
514
- blaze: AstNode,
518
+ implementation: AstNode,
515
519
  inputParamName: string | null,
516
520
  fieldName: string,
517
521
  aliases: ReadonlyMap<string, string>
518
522
  ): AstNode | null => {
519
523
  let found: AstNode | null = null;
520
- walkScope(blaze, (node) => {
524
+ walkScope(implementation, (node) => {
521
525
  if (found) {
522
526
  return;
523
527
  }
@@ -579,15 +583,20 @@ export const trailForkCoaching: WardenRule = {
579
583
  }
580
584
  const fieldNames = new Set(fields.map((field) => field.name));
581
585
 
582
- for (const blaze of findBlazeBodies(definition.config)) {
583
- const inputBindings = collectBlazeInputBindings(blaze, fieldNames);
586
+ for (const implementation of findImplementationBodies(
587
+ definition.config
588
+ )) {
589
+ const inputBindings = collectImplementationInputBindings(
590
+ implementation,
591
+ fieldNames
592
+ );
584
593
  if (!inputBindings) {
585
594
  continue;
586
595
  }
587
596
 
588
597
  for (const field of fields) {
589
598
  const branchNode = branchesOnField(
590
- blaze,
599
+ implementation,
591
600
  inputBindings.inputParamName,
592
601
  field.name,
593
602
  inputBindings.aliases
@@ -1,6 +1,6 @@
1
1
  import {
2
- findBlazeBodies,
3
2
  findConfigProperty,
3
+ findImplementationBodies,
4
4
  findTrailDefinitions,
5
5
  getNodeArgument,
6
6
  getNodeArguments,
@@ -23,12 +23,13 @@ import {
23
23
  parse,
24
24
  walk,
25
25
  walkScope,
26
- } from './ast.js';
27
- import type { AstNode } from './ast.js';
26
+ } from '@ontrails/source';
27
+ import type { AstNode } from '@ontrails/source';
28
28
  import type { WardenDiagnostic, WardenRule } from './types.js';
29
29
 
30
30
  const VERSION_PINNED_COMPOSE = 'version-pinned-compose';
31
- const FORK_WITHOUT_PRESERVED_BLAZE = 'fork-without-preserved-blaze';
31
+ const FORK_WITHOUT_PRESERVED_IMPLEMENTATION =
32
+ 'fork-without-preserved-implementation';
32
33
  const MARKER_SCHEMA_UNSUPPORTED = 'marker-schema-unsupported';
33
34
 
34
35
  interface SchemaBindingRecord {
@@ -893,8 +894,8 @@ export const versionPinnedCompose: WardenRule = {
893
894
  }
894
895
 
895
896
  const diagnostics: WardenDiagnostic[] = [];
896
- for (const blaze of findBlazeBodies(ast)) {
897
- walk(blaze, (node) => {
897
+ for (const implementation of findImplementationBodies(ast)) {
898
+ walk(implementation, (node) => {
898
899
  if (!composeCallHasVersionPin(node)) {
899
900
  return;
900
901
  }
@@ -918,7 +919,7 @@ export const versionPinnedCompose: WardenRule = {
918
919
  severity: 'warn',
919
920
  };
920
921
 
921
- export const forkWithoutPreservedBlaze: WardenRule = {
922
+ export const forkWithoutPreservedImplementation: WardenRule = {
922
923
  check(sourceCode, filePath) {
923
924
  const ast = parse(filePath, sourceCode);
924
925
  if (!ast) {
@@ -931,17 +932,20 @@ export const forkWithoutPreservedBlaze: WardenRule = {
931
932
  continue;
932
933
  }
933
934
  for (const entry of versionEntries(definition.config)) {
934
- if (hasProperty(entry, 'transpose') || hasProperty(entry, 'blaze')) {
935
+ if (
936
+ hasProperty(entry, 'transpose') ||
937
+ hasProperty(entry, 'implementation')
938
+ ) {
935
939
  continue;
936
940
  }
937
941
  diagnostics.push(
938
942
  diagnostic(
939
- FORK_WITHOUT_PRESERVED_BLAZE,
943
+ FORK_WITHOUT_PRESERVED_IMPLEMENTATION,
940
944
  'error',
941
945
  filePath,
942
946
  sourceCode,
943
947
  entry,
944
- `Trail "${definition.id}" has a historical version entry without transpose or blaze. Add transpose for a revision entry, or preserve the historical blaze for a fork entry.`
948
+ `Trail "${definition.id}" has a historical version entry without transpose or implementation. Add transpose for a revision entry, or preserve the historical implementation for a fork entry.`
945
949
  )
946
950
  );
947
951
  }
@@ -949,8 +953,8 @@ export const forkWithoutPreservedBlaze: WardenRule = {
949
953
  return diagnostics;
950
954
  },
951
955
  description:
952
- 'Require historical fork version entries to preserve a blaze, while revision entries declare transpose.',
953
- name: FORK_WITHOUT_PRESERVED_BLAZE,
956
+ 'Require historical fork version entries to preserve an implementation, while revision entries declare transpose.',
957
+ name: FORK_WITHOUT_PRESERVED_IMPLEMENTATION,
954
958
  severity: 'error',
955
959
  };
956
960
 
@@ -1,10 +1,10 @@
1
- import { deriveTopoGraph } from '@ontrails/topographer';
1
+ import { deriveTopoGraph } from '@ontrails/topography';
2
2
  import type {
3
3
  TopoGraph,
4
4
  TopoGraphEntry,
5
5
  TopoGraphForceEntry,
6
6
  TopoGraphVersionEntry,
7
- } from '@ontrails/topographer';
7
+ } from '@ontrails/topography';
8
8
  import type { Topo } from '@ontrails/core';
9
9
 
10
10
  import type { TopoAwareWardenRule, WardenDiagnostic } from './types.js';
@@ -39,8 +39,8 @@ import {
39
39
  offsetToLine,
40
40
  parse,
41
41
  walk,
42
- } from './ast.js';
43
- import type { AstNode } from './ast.js';
42
+ } from '@ontrails/source';
43
+ import type { AstNode } from '@ontrails/source';
44
44
  import type {
45
45
  AuthoredMcpSurfaceBindingSet,
46
46
  ProjectAwareWardenRule,
@@ -6,7 +6,7 @@ import type {
6
6
  SurfaceBindings,
7
7
  Topo,
8
8
  } from '@ontrails/core';
9
- import type { TopoGraph } from '@ontrails/topographer';
9
+ import type { TopoGraph } from '@ontrails/topography';
10
10
 
11
11
  import type { WardenDepth } from '../config.js';
12
12
  import type { WardenImportResolution } from '../resolve.js';
@@ -314,16 +314,16 @@ export interface ProjectContext {
314
314
  readonly authoredMcpSurfaceBindingSets?:
315
315
  | readonly AuthoredMcpSurfaceBindingSet[]
316
316
  | undefined;
317
- /** All known contour names in the project. */
318
- readonly knownContourIds?: ReadonlySet<string>;
317
+ /** All known entity names in the project. */
318
+ readonly knownEntityIds?: ReadonlySet<string>;
319
319
  /** Store table IDs used with the CRUD factory across the project. */
320
320
  readonly crudTableIds?: ReadonlySet<string>;
321
321
  /** All known trail IDs in the project */
322
322
  readonly knownTrailIds: ReadonlySet<string>;
323
323
  /** Trail IDs registered in configured app topo targets. */
324
324
  readonly topoTrailIds?: ReadonlySet<string>;
325
- /** Declared contour references keyed by source contour name. */
326
- readonly contourReferencesByName?: ReadonlyMap<string, readonly string[]>;
325
+ /** Declared entity references keyed by source entity name. */
326
+ readonly entityReferencesByName?: ReadonlyMap<string, readonly string[]>;
327
327
  /** All known resource IDs in the project */
328
328
  readonly knownResourceIds?: ReadonlySet<string>;
329
329
  /** All known signal IDs in the project */
@@ -356,8 +356,8 @@ export interface ProjectContext {
356
356
  /**
357
357
  * CRUD operation coverage per entity aggregated across the project.
358
358
  *
359
- * Keys are stable entity IDs (authored contour names, `imported:<local>`
360
- * sentinels for contours imported from another module, or store-table IDs
359
+ * Keys are stable entity IDs (authored entity names, `imported:<local>`
360
+ * sentinels for entities imported from another module, or store-table IDs
361
361
  * produced by `deriveStoreTableId`). Values are the set of CRUD operations
362
362
  * (`create`, `read`, `update`, `delete`, `list`) observed for that entity.
363
363
  *
@@ -6,6 +6,7 @@ const RULE_NAME = 'unmaterialized-activation-source';
6
6
  const TOPO_FILE = '<topo>';
7
7
 
8
8
  const MATERIALIZED_SOURCE_KINDS = new Set<ActivationSourceKind>([
9
+ 'queue',
9
10
  'schedule',
10
11
  'signal',
11
12
  'webhook',
@@ -15,8 +15,8 @@ import {
15
15
  offsetToLine,
16
16
  parse,
17
17
  walk,
18
- } from './ast.js';
19
- import type { AstNode } from './ast.js';
18
+ } from '@ontrails/source';
19
+ import type { AstNode } from '@ontrails/source';
20
20
  import { isTestFile } from './scan.js';
21
21
  import type { WardenDiagnostic, WardenRule } from './types.js';
22
22