@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.
- package/CHANGELOG.md +125 -0
- package/README.md +5 -5
- package/package.json +10 -12
- package/src/cli.ts +49 -44
- package/src/command.ts +2 -2
- package/src/drift.ts +4 -3
- package/src/index.ts +3 -2
- package/src/project-context.ts +3 -3
- package/src/resolve.ts +2 -2
- package/src/rules/captured-kernel.ts +375 -0
- package/src/rules/circular-refs.ts +12 -16
- package/src/rules/cli-command-route-coherence.ts +1 -1
- package/src/rules/composes-declarations.ts +15 -13
- package/src/rules/context-no-surface-types.ts +4 -3
- package/src/rules/dead-internal-trail.ts +3 -3
- package/src/rules/dead-public-trail.ts +3 -3
- package/src/rules/draft-file-marking.ts +4 -9
- package/src/rules/draft-visible-debt.ts +3 -8
- package/src/rules/duplicate-public-contract.ts +50 -5
- package/src/rules/{contour-exists.ts → entity-exists.ts} +67 -64
- package/src/rules/entity-ids.ts +15 -0
- package/src/rules/error-mapping-completeness.ts +2 -2
- package/src/rules/example-valid.ts +84 -84
- package/src/rules/fires-declarations.ts +27 -22
- package/src/rules/governed-symbol-residue.ts +339 -32
- package/src/rules/implementation-returns-result.ts +8 -8
- package/src/rules/incomplete-accessor-for-standard-op.ts +15 -15
- package/src/rules/incomplete-crud.ts +42 -40
- package/src/rules/index.ts +9 -6
- package/src/rules/intent-propagation.ts +3 -5
- package/src/rules/layer-field-name-drift.ts +2 -2
- package/src/rules/library-projection-coherence.ts +2 -2
- package/src/rules/metadata.ts +37 -15
- package/src/rules/missing-reconcile.ts +3 -4
- package/src/rules/missing-visibility.ts +2 -1
- package/src/rules/no-destructured-compose.ts +14 -12
- package/src/rules/no-direct-implementation-call.ts +12 -7
- package/src/rules/no-legacy-cli-alias-export.ts +2 -2
- package/src/rules/no-native-error-result.ts +2 -2
- package/src/rules/no-redundant-result-error-wrap.ts +10 -10
- package/src/rules/no-sync-result-assumption.ts +84 -78
- package/src/rules/no-throw-in-detour-recover.ts +2 -2
- package/src/rules/no-throw-in-implementation.ts +13 -7
- package/src/rules/no-top-level-surface.ts +2 -2
- package/src/rules/on-references-exist.ts +3 -3
- package/src/rules/orphaned-signal.ts +4 -5
- package/src/rules/owner-projection-parity.ts +2 -2
- package/src/rules/public-export-example-coverage.ts +10 -4
- package/src/rules/public-internal-deep-imports.ts +2 -3
- package/src/rules/read-intent-fires.ts +4 -3
- package/src/rules/reference-exists.ts +20 -21
- package/src/rules/registry-names.ts +6 -4
- package/src/rules/resolved-import-boundary.ts +1 -1
- package/src/rules/resource-declarations.ts +21 -17
- package/src/rules/resource-exists.ts +4 -2
- package/src/rules/resource-id-grammar.ts +2 -2
- package/src/rules/resource-mock-coverage.ts +2 -2
- package/src/rules/retired-vocabulary.ts +242 -5
- package/src/rules/source/composition.ts +165 -0
- package/src/rules/source/drafts.ts +164 -0
- package/src/rules/source/entities.ts +618 -0
- package/src/rules/source/pragmas.ts +45 -0
- package/src/rules/source/resources.ts +64 -0
- package/src/rules/source/signals.ts +397 -0
- package/src/rules/source/stores.ts +310 -0
- package/src/rules/static-resource-accessor-preference.ts +15 -11
- package/src/rules/surface-overlay-coherence.ts +1 -1
- package/src/rules/surface-trailhead-coherence.ts +2 -2
- package/src/rules/trail-fork-coaching.ts +27 -18
- package/src/rules/trail-versioning-source.ts +16 -12
- package/src/rules/trail-versioning-topo.ts +2 -2
- package/src/rules/trailhead-override-divergence.ts +2 -2
- package/src/rules/types.ts +7 -7
- package/src/rules/unmaterialized-activation-source.ts +1 -0
- package/src/rules/unreachable-detour-shadowing.ts +2 -2
- package/src/rules/valid-describe-refs.ts +2 -2
- package/src/rules/warden-export-symmetry.ts +3 -3
- package/src/rules/warden-rules-use-ast.ts +12 -17
- package/src/trails/activation-orphan.trail.ts +5 -5
- package/src/trails/captured-kernel.trail.ts +108 -0
- package/src/trails/circular-refs.trail.ts +6 -6
- package/src/trails/cli-command-route-coherence.trail.ts +2 -2
- package/src/trails/composes-declarations.trail.ts +1 -1
- package/src/trails/context-no-surface-types.trail.ts +1 -1
- package/src/trails/dead-internal-trail.trail.ts +2 -2
- package/src/trails/dead-public-trail.trail.ts +1 -1
- package/src/trails/deprecation-without-guidance.trail.ts +1 -1
- package/src/trails/duplicate-public-contract.trail.ts +3 -3
- package/src/trails/entity-exists.trail.ts +21 -0
- package/src/trails/example-valid.trail.ts +3 -3
- package/src/trails/fires-declarations.trail.ts +1 -1
- package/src/trails/{fork-without-preserved-blaze.trail.ts → fork-without-preserved-implementation.trail.ts} +4 -4
- package/src/trails/implementation-returns-result.trail.ts +1 -1
- package/src/trails/incomplete-accessor-for-standard-op.trail.ts +4 -4
- package/src/trails/index.ts +3 -2
- package/src/trails/intent-propagation.trail.ts +2 -2
- package/src/trails/library-projection-coherence.trail.ts +2 -2
- package/src/trails/marker-schema-unsupported.trail.ts +1 -1
- package/src/trails/missing-visibility.trail.ts +1 -1
- package/src/trails/no-destructured-compose.trail.ts +5 -5
- package/src/trails/no-direct-implementation-call.trail.ts +1 -1
- package/src/trails/no-redundant-result-error-wrap.trail.ts +2 -2
- package/src/trails/no-sync-result-assumption.trail.ts +2 -2
- package/src/trails/no-throw-in-detour-recover.trail.ts +1 -1
- package/src/trails/no-throw-in-implementation.trail.ts +1 -1
- package/src/trails/on-references-exist.trail.ts +1 -1
- package/src/trails/pending-force.trail.ts +1 -1
- package/src/trails/permit-governance.trail.ts +2 -2
- package/src/trails/prefer-schema-inference.trail.ts +1 -1
- package/src/trails/public-output-schema.trail.ts +2 -2
- package/src/trails/public-union-output-discriminants.trail.ts +1 -1
- package/src/trails/read-intent-fires.trail.ts +1 -1
- package/src/trails/reference-exists.trail.ts +5 -5
- package/src/trails/resource-declarations.trail.ts +1 -1
- package/src/trails/resource-exists.trail.ts +1 -1
- package/src/trails/run.ts +4 -6
- package/src/trails/scheduled-destroy-intent.trail.ts +2 -2
- package/src/trails/schema.ts +9 -9
- package/src/trails/signal-graph-coaching.trail.ts +2 -2
- package/src/trails/static-resource-accessor-preference.trail.ts +1 -1
- package/src/trails/surface-overlay-coherence.trail.ts +1 -1
- package/src/trails/trail-fork-coaching.trail.ts +1 -1
- package/src/trails/unmaterialized-activation-source.trail.ts +5 -5
- package/src/trails/valid-detour-contract.trail.ts +1 -1
- package/src/trails/version-gap.trail.ts +1 -1
- package/src/trails/version-pinned-compose.trail.ts +1 -1
- package/src/trails/version-without-examples.trail.ts +1 -1
- package/src/trails/warden-rules-use-ast.trail.ts +2 -2
- package/src/trails/webhook-route-collision.trail.ts +2 -2
- package/src/trails/wrap-rule.ts +17 -17
- package/src/ast.ts +0 -137
- package/src/rules/ast.ts +0 -4143
- package/src/rules/contour-ids.ts +0 -15
- package/src/trails/contour-exists.trail.ts +0 -21
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { crudOperations } from '@ontrails/store';
|
|
2
2
|
|
|
3
|
+
import { collectNamedEntityIds } from './source/entities.js';
|
|
3
4
|
import {
|
|
4
|
-
collectNamedContourIds,
|
|
5
5
|
collectNamedStoreTableIds,
|
|
6
6
|
deriveStoreTableId,
|
|
7
|
+
isNamedCall,
|
|
8
|
+
} from './source/stores.js';
|
|
9
|
+
import {
|
|
7
10
|
getNodeArguments,
|
|
8
11
|
getNodeElements,
|
|
9
12
|
getNodeId,
|
|
@@ -14,13 +17,12 @@ import {
|
|
|
14
17
|
getNodeSpecifiers,
|
|
15
18
|
getStringValue,
|
|
16
19
|
identifierName,
|
|
17
|
-
isNamedCall,
|
|
18
20
|
isStringLiteral,
|
|
19
21
|
offsetToLine,
|
|
20
22
|
parse,
|
|
21
23
|
walk,
|
|
22
|
-
} from '
|
|
23
|
-
import type { AstNode } from '
|
|
24
|
+
} from '@ontrails/source';
|
|
25
|
+
import type { AstNode } from '@ontrails/source';
|
|
24
26
|
import { isTestFile } from './scan.js';
|
|
25
27
|
import type {
|
|
26
28
|
ProjectAwareWardenRule,
|
|
@@ -30,10 +32,10 @@ import type {
|
|
|
30
32
|
|
|
31
33
|
const CRUD_OPERATION_SET = new Set<string>(crudOperations);
|
|
32
34
|
|
|
33
|
-
/** Sentinel entity id prefix for
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
35
|
+
/** Sentinel entity id prefix for entities imported from another module. */
|
|
36
|
+
const IMPORTED_ENTITY_PREFIX = 'imported:';
|
|
37
|
+
const IMPORTED_ENTITY_SOURCE_SEPARATOR = '#';
|
|
38
|
+
const ENTITY_BINDING_SUFFIX = 'Entity';
|
|
37
39
|
|
|
38
40
|
interface CrudCoverage {
|
|
39
41
|
readonly entityId: string;
|
|
@@ -103,8 +105,8 @@ const collectImportDeclarationAliases = (
|
|
|
103
105
|
}
|
|
104
106
|
};
|
|
105
107
|
|
|
106
|
-
const
|
|
107
|
-
if (!isNamedCall(node, '
|
|
108
|
+
const extractInlineEntityId = (node: AstNode | undefined): string | null => {
|
|
109
|
+
if (!isNamedCall(node, 'entity')) {
|
|
108
110
|
return null;
|
|
109
111
|
}
|
|
110
112
|
|
|
@@ -127,18 +129,18 @@ const collectImportAliasResolutions = (
|
|
|
127
129
|
return aliases;
|
|
128
130
|
};
|
|
129
131
|
|
|
130
|
-
const
|
|
131
|
-
`${
|
|
132
|
+
const buildImportedEntityId = (source: string, importedName: string): string =>
|
|
133
|
+
`${IMPORTED_ENTITY_PREFIX}${source}${IMPORTED_ENTITY_SOURCE_SEPARATOR}${importedName}`;
|
|
132
134
|
|
|
133
|
-
const
|
|
135
|
+
const parseImportedEntityId = (
|
|
134
136
|
entityId: string
|
|
135
137
|
): { readonly bindingName: string; readonly source?: string } | null => {
|
|
136
|
-
if (!entityId.startsWith(
|
|
138
|
+
if (!entityId.startsWith(IMPORTED_ENTITY_PREFIX)) {
|
|
137
139
|
return null;
|
|
138
140
|
}
|
|
139
141
|
|
|
140
|
-
const remainder = entityId.slice(
|
|
141
|
-
const separator = remainder.lastIndexOf(
|
|
142
|
+
const remainder = entityId.slice(IMPORTED_ENTITY_PREFIX.length);
|
|
143
|
+
const separator = remainder.lastIndexOf(IMPORTED_ENTITY_SOURCE_SEPARATOR);
|
|
142
144
|
if (separator === -1) {
|
|
143
145
|
return { bindingName: remainder };
|
|
144
146
|
}
|
|
@@ -150,38 +152,38 @@ const parseImportedContourId = (
|
|
|
150
152
|
};
|
|
151
153
|
|
|
152
154
|
/**
|
|
153
|
-
* Resolve an identifier reference (bound
|
|
155
|
+
* Resolve an identifier reference (bound entity or imported alias) to a
|
|
154
156
|
* stable entity id. Imported identifiers return a `pending-resolution`
|
|
155
157
|
* sentinel so coverage is still tracked instead of silently dropped.
|
|
156
158
|
*/
|
|
157
|
-
const
|
|
159
|
+
const resolveEntityIdentifier = (
|
|
158
160
|
name: string,
|
|
159
|
-
|
|
161
|
+
namedEntityIds: ReadonlyMap<string, string>,
|
|
160
162
|
importAliases: ReadonlyMap<string, ImportAliasResolution>
|
|
161
163
|
): string | null => {
|
|
162
|
-
const local =
|
|
164
|
+
const local = namedEntityIds.get(name);
|
|
163
165
|
if (local) {
|
|
164
166
|
return local;
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
const imported = importAliases.get(name);
|
|
168
170
|
if (imported) {
|
|
169
|
-
return
|
|
171
|
+
return buildImportedEntityId(imported.source, imported.importedName);
|
|
170
172
|
}
|
|
171
173
|
|
|
172
174
|
return null;
|
|
173
175
|
};
|
|
174
176
|
|
|
175
|
-
const
|
|
176
|
-
entityId.endsWith(
|
|
177
|
-
? entityId.slice(0, -
|
|
177
|
+
const stripEntityBindingSuffix = (entityId: string): string =>
|
|
178
|
+
entityId.endsWith(ENTITY_BINDING_SUFFIX)
|
|
179
|
+
? entityId.slice(0, -ENTITY_BINDING_SUFFIX.length)
|
|
178
180
|
: entityId;
|
|
179
181
|
|
|
180
182
|
const normalizeProjectEntityId = (
|
|
181
183
|
entityId: string,
|
|
182
184
|
projectEntityIds: ReadonlySet<string>
|
|
183
185
|
): string => {
|
|
184
|
-
const imported =
|
|
186
|
+
const imported = parseImportedEntityId(entityId);
|
|
185
187
|
if (!imported) {
|
|
186
188
|
return entityId;
|
|
187
189
|
}
|
|
@@ -190,11 +192,11 @@ const normalizeProjectEntityId = (
|
|
|
190
192
|
if (projectEntityIds.has(localId)) {
|
|
191
193
|
return localId;
|
|
192
194
|
}
|
|
193
|
-
const strippedId =
|
|
195
|
+
const strippedId = stripEntityBindingSuffix(localId);
|
|
194
196
|
if (
|
|
195
197
|
strippedId !== localId &&
|
|
196
198
|
(projectEntityIds.has(strippedId) ||
|
|
197
|
-
projectEntityIds.has(`${
|
|
199
|
+
projectEntityIds.has(`${IMPORTED_ENTITY_PREFIX}${strippedId}`))
|
|
198
200
|
) {
|
|
199
201
|
return strippedId;
|
|
200
202
|
}
|
|
@@ -218,19 +220,19 @@ const normalizeProjectCoverage = (
|
|
|
218
220
|
};
|
|
219
221
|
|
|
220
222
|
/**
|
|
221
|
-
* Resolve a `deriveTrail`
|
|
223
|
+
* Resolve a `deriveTrail` entity argument to a stable entity id.
|
|
222
224
|
*
|
|
223
225
|
* Resolution order:
|
|
224
|
-
* 1. Inline `
|
|
225
|
-
* 2. Local identifier bound to `
|
|
226
|
+
* 1. Inline `entity('name', …)` call — use the authored name.
|
|
227
|
+
* 2. Local identifier bound to `entity('name', …)` via `namedEntityIds`.
|
|
226
228
|
* 3. Identifier imported from another module — mark as a pending
|
|
227
229
|
* `imported:<local>` coverage observation so the rule still tracks the
|
|
228
230
|
* entity across the file instead of silently dropping it. The prefix is
|
|
229
231
|
* stripped from diagnostic output for readability.
|
|
230
232
|
*/
|
|
231
|
-
const
|
|
233
|
+
const resolveEntityId = (
|
|
232
234
|
node: AstNode | undefined,
|
|
233
|
-
|
|
235
|
+
namedEntityIds: ReadonlyMap<string, string>,
|
|
234
236
|
importAliases: ReadonlyMap<string, ImportAliasResolution>
|
|
235
237
|
): string | null => {
|
|
236
238
|
if (!node) {
|
|
@@ -240,11 +242,11 @@ const resolveContourId = (
|
|
|
240
242
|
if (node.type === 'Identifier') {
|
|
241
243
|
const name = identifierName(node);
|
|
242
244
|
return name
|
|
243
|
-
?
|
|
245
|
+
? resolveEntityIdentifier(name, namedEntityIds, importAliases)
|
|
244
246
|
: null;
|
|
245
247
|
}
|
|
246
248
|
|
|
247
|
-
return
|
|
249
|
+
return extractInlineEntityId(node);
|
|
248
250
|
};
|
|
249
251
|
|
|
250
252
|
const ensureCoverage = (
|
|
@@ -277,16 +279,16 @@ const extractCrudOperation = (node: AstNode | undefined): string | null => {
|
|
|
277
279
|
|
|
278
280
|
const extractDerivedCrudEntry = (
|
|
279
281
|
node: AstNode,
|
|
280
|
-
|
|
282
|
+
namedEntityIds: ReadonlyMap<string, string>,
|
|
281
283
|
importAliases: ReadonlyMap<string, ImportAliasResolution>
|
|
282
284
|
): { readonly entityId: string; readonly operation: string } | null => {
|
|
283
285
|
if (!isNamedCall(node, 'deriveTrail')) {
|
|
284
286
|
return null;
|
|
285
287
|
}
|
|
286
288
|
|
|
287
|
-
const [
|
|
289
|
+
const [entityArg, operationArg] = getNodeArguments(node);
|
|
288
290
|
const operation = extractCrudOperation(operationArg);
|
|
289
|
-
const entityId =
|
|
291
|
+
const entityId = resolveEntityId(entityArg, namedEntityIds, importAliases);
|
|
290
292
|
return operation && entityId ? { entityId, operation } : null;
|
|
291
293
|
};
|
|
292
294
|
|
|
@@ -295,11 +297,11 @@ const collectDerivedCrudCoverage = (
|
|
|
295
297
|
sourceCode: string
|
|
296
298
|
): ReadonlyMap<string, CrudCoverage> => {
|
|
297
299
|
const coverageByEntityId = new Map<string, CrudCoverage>();
|
|
298
|
-
const
|
|
300
|
+
const namedEntityIds = collectNamedEntityIds(ast);
|
|
299
301
|
const importAliases = collectImportAliasResolutions(ast);
|
|
300
302
|
|
|
301
303
|
walk(ast, (node) => {
|
|
302
|
-
const entry = extractDerivedCrudEntry(node,
|
|
304
|
+
const entry = extractDerivedCrudEntry(node, namedEntityIds, importAliases);
|
|
303
305
|
if (!entry) {
|
|
304
306
|
return;
|
|
305
307
|
}
|
|
@@ -504,7 +506,7 @@ const collectIncompleteEntities = (
|
|
|
504
506
|
};
|
|
505
507
|
|
|
506
508
|
const formatEntityLabel = (entityId: string): string => {
|
|
507
|
-
const imported =
|
|
509
|
+
const imported = parseImportedEntityId(entityId);
|
|
508
510
|
return imported
|
|
509
511
|
? `${imported.bindingName} (imported, pending-resolution)`
|
|
510
512
|
: entityId;
|
package/src/rules/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { activationOrphan } from './activation-orphan.js';
|
|
2
|
+
import { capturedKernel } from './captured-kernel.js';
|
|
2
3
|
import { cliCommandRouteCoherence } from './cli-command-route-coherence.js';
|
|
3
4
|
import { circularRefs } from './circular-refs.js';
|
|
4
|
-
import {
|
|
5
|
+
import { entityExists } from './entity-exists.js';
|
|
5
6
|
import { contextNoSurfaceTypes } from './context-no-surface-types.js';
|
|
6
7
|
import { composesDeclarations } from './composes-declarations.js';
|
|
7
8
|
import { deadInternalTrail } from './dead-internal-trail.js';
|
|
@@ -58,7 +59,7 @@ import { surfaceTrailheadCoherence } from './surface-trailhead-coherence.js';
|
|
|
58
59
|
import { trailForkCoaching } from './trail-fork-coaching.js';
|
|
59
60
|
import { trailheadOverrideDivergence } from './trailhead-override-divergence.js';
|
|
60
61
|
import {
|
|
61
|
-
|
|
62
|
+
forkWithoutPreservedImplementation,
|
|
62
63
|
markerSchemaUnsupported,
|
|
63
64
|
versionPinnedCompose,
|
|
64
65
|
} from './trail-versioning-source.js';
|
|
@@ -139,10 +140,11 @@ export {
|
|
|
139
140
|
export type { BuiltinWardenRuleName } from './metadata.js';
|
|
140
141
|
|
|
141
142
|
export { activationOrphan } from './activation-orphan.js';
|
|
143
|
+
export { capturedKernel } from './captured-kernel.js';
|
|
142
144
|
export { cliCommandRouteCoherence } from './cli-command-route-coherence.js';
|
|
143
145
|
export { noThrowInImplementation } from './no-throw-in-implementation.js';
|
|
144
146
|
export { circularRefs } from './circular-refs.js';
|
|
145
|
-
export {
|
|
147
|
+
export { entityExists } from './entity-exists.js';
|
|
146
148
|
export { contextNoSurfaceTypes } from './context-no-surface-types.js';
|
|
147
149
|
export { composesDeclarations } from './composes-declarations.js';
|
|
148
150
|
export { deadInternalTrail } from './dead-internal-trail.js';
|
|
@@ -197,7 +199,7 @@ export { surfaceTrailheadCoherence } from './surface-trailhead-coherence.js';
|
|
|
197
199
|
export { trailForkCoaching } from './trail-fork-coaching.js';
|
|
198
200
|
export { trailheadOverrideDivergence } from './trailhead-override-divergence.js';
|
|
199
201
|
export {
|
|
200
|
-
|
|
202
|
+
forkWithoutPreservedImplementation,
|
|
201
203
|
markerSchemaUnsupported,
|
|
202
204
|
versionPinnedCompose,
|
|
203
205
|
} from './trail-versioning-source.js';
|
|
@@ -219,8 +221,9 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
|
|
|
219
221
|
WardenRule
|
|
220
222
|
>([
|
|
221
223
|
[noThrowInImplementation.name, noThrowInImplementation],
|
|
224
|
+
[capturedKernel.name, capturedKernel],
|
|
222
225
|
[circularRefs.name, circularRefs],
|
|
223
|
-
[
|
|
226
|
+
[entityExists.name, entityExists],
|
|
224
227
|
[contextNoSurfaceTypes.name, contextNoSurfaceTypes],
|
|
225
228
|
[composesDeclarations.name, composesDeclarations],
|
|
226
229
|
[deadInternalTrail.name, deadInternalTrail],
|
|
@@ -270,7 +273,7 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
|
|
|
270
273
|
[unreachableDetourShadowing.name, unreachableDetourShadowing],
|
|
271
274
|
[wardenExportSymmetry.name, wardenExportSymmetry],
|
|
272
275
|
[wardenRulesUseAst.name, wardenRulesUseAst],
|
|
273
|
-
[
|
|
276
|
+
[forkWithoutPreservedImplementation.name, forkWithoutPreservedImplementation],
|
|
274
277
|
[markerSchemaUnsupported.name, markerSchemaUnsupported],
|
|
275
278
|
[versionPinnedCompose.name, versionPinnedCompose],
|
|
276
279
|
]);
|
|
@@ -3,11 +3,9 @@ import {
|
|
|
3
3
|
collectNamedTrailIds,
|
|
4
4
|
collectTrailIntentsById,
|
|
5
5
|
extractDefinitionComposeTargetIds,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from './ast.js';
|
|
10
|
-
import type { AstNode } from './ast.js';
|
|
6
|
+
} from './source/composition.js';
|
|
7
|
+
import { findTrailDefinitions, offsetToLine, parse } from '@ontrails/source';
|
|
8
|
+
import type { AstNode } from '@ontrails/source';
|
|
11
9
|
import { isTestFile } from './scan.js';
|
|
12
10
|
import type {
|
|
13
11
|
ProjectAwareWardenRule,
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
offsetToLine,
|
|
9
9
|
parse,
|
|
10
10
|
walk,
|
|
11
|
-
} from '
|
|
12
|
-
import type { AstNode } from '
|
|
11
|
+
} from '@ontrails/source';
|
|
12
|
+
import type { AstNode } from '@ontrails/source';
|
|
13
13
|
import type { WardenDiagnostic, WardenRule } from './types.js';
|
|
14
14
|
|
|
15
15
|
const RULE_NAME = 'layer-field-name-drift';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { deriveTopoGraph } from '@ontrails/
|
|
1
|
+
import { deriveTopoGraph } from '@ontrails/topography';
|
|
2
2
|
import type {
|
|
3
3
|
TopoGraph,
|
|
4
4
|
TopoGraphLibraryProjection,
|
|
5
|
-
} from '@ontrails/
|
|
5
|
+
} from '@ontrails/topography';
|
|
6
6
|
|
|
7
7
|
import type { TopoAwareWardenRule, WardenDiagnostic } from './types.js';
|
|
8
8
|
|
package/src/rules/metadata.ts
CHANGED
|
@@ -69,6 +69,7 @@ const depthByTier = {
|
|
|
69
69
|
|
|
70
70
|
const concernByRuleName: Partial<Record<string, WardenRuleConcern>> = {
|
|
71
71
|
'activation-orphan': 'signals',
|
|
72
|
+
'captured-kernel': 'general',
|
|
72
73
|
'cli-command-route-coherence': 'meta',
|
|
73
74
|
'composes-declarations': 'composition',
|
|
74
75
|
'context-no-surface-types': 'composition',
|
|
@@ -81,7 +82,7 @@ const concernByRuleName: Partial<Record<string, WardenRuleConcern>> = {
|
|
|
81
82
|
'duplicate-public-contract': 'meta',
|
|
82
83
|
'error-mapping-completeness': 'results',
|
|
83
84
|
'fires-declarations': 'signals',
|
|
84
|
-
'fork-without-preserved-
|
|
85
|
+
'fork-without-preserved-implementation': 'lifecycle',
|
|
85
86
|
'governed-symbol-residue': 'lifecycle',
|
|
86
87
|
'implementation-returns-result': 'results',
|
|
87
88
|
'intent-propagation': 'composition',
|
|
@@ -157,9 +158,30 @@ const builtinWardenRuleMetadataInput = {
|
|
|
157
158
|
'Signal activation consumers reference sources with producer declarations.',
|
|
158
159
|
tier: 'topo-aware',
|
|
159
160
|
},
|
|
161
|
+
'captured-kernel': {
|
|
162
|
+
guidance: {
|
|
163
|
+
relatedRules: [
|
|
164
|
+
'duplicate-exported-symbol',
|
|
165
|
+
'public-internal-deep-imports',
|
|
166
|
+
'resolved-import-boundary',
|
|
167
|
+
],
|
|
168
|
+
steps: [
|
|
169
|
+
'Review whether the public subpath should become an owned package surface, move back behind the package root, or be split into a better-owned package.',
|
|
170
|
+
'If the imported capability is reusable source-code machinery, serves at least two independently owned toolchain capabilities, exposes one genuinely shared contract, and owns no verdict, migration plan, graph query, or surface rendering, consider relocating it to @ontrails/source.',
|
|
171
|
+
'Otherwise, preserve the current owner or choose another doctrinal owner.',
|
|
172
|
+
],
|
|
173
|
+
summary:
|
|
174
|
+
'Review ownership before an internal re-exported kernel hardens into a public package seam.',
|
|
175
|
+
},
|
|
176
|
+
invariant:
|
|
177
|
+
'Public subpath exports that re-export internal kernels receive ownership review after multiple production packages consume them.',
|
|
178
|
+
lifecycle: { state: 'durable' },
|
|
179
|
+
scope: 'advisory',
|
|
180
|
+
tier: 'project-static',
|
|
181
|
+
},
|
|
160
182
|
'circular-refs': {
|
|
161
183
|
...durableExternal,
|
|
162
|
-
invariant: '
|
|
184
|
+
invariant: 'Entity reference graphs must be acyclic.',
|
|
163
185
|
tier: 'project-static',
|
|
164
186
|
},
|
|
165
187
|
'cli-command-route-coherence': {
|
|
@@ -189,11 +211,6 @@ const builtinWardenRuleMetadataInput = {
|
|
|
189
211
|
invariant: 'Trail logic stays surface-agnostic.',
|
|
190
212
|
tier: 'source-static',
|
|
191
213
|
},
|
|
192
|
-
'contour-exists': {
|
|
193
|
-
...durableExternal,
|
|
194
|
-
invariant: 'Declared contour references resolve to known contours.',
|
|
195
|
-
tier: 'project-static',
|
|
196
|
-
},
|
|
197
214
|
'dead-internal-trail': {
|
|
198
215
|
...durableExternal,
|
|
199
216
|
invariant: 'Internal trails should be reachable through declared composes.',
|
|
@@ -263,6 +280,11 @@ const builtinWardenRuleMetadataInput = {
|
|
|
263
280
|
'Public surface trails should not expose duplicate normalized contract facts.',
|
|
264
281
|
tier: 'topo-aware',
|
|
265
282
|
},
|
|
283
|
+
'entity-exists': {
|
|
284
|
+
...durableExternal,
|
|
285
|
+
invariant: 'Declared entity references resolve to known entities.',
|
|
286
|
+
tier: 'project-static',
|
|
287
|
+
},
|
|
266
288
|
'error-mapping-completeness': {
|
|
267
289
|
...durableExtension,
|
|
268
290
|
invariant: 'Registered surface error mappers cover every error category.',
|
|
@@ -286,9 +308,9 @@ const builtinWardenRuleMetadataInput = {
|
|
|
286
308
|
invariant: 'Declared fires stay aligned with signal firing usage.',
|
|
287
309
|
tier: 'source-static',
|
|
288
310
|
},
|
|
289
|
-
'fork-without-preserved-
|
|
311
|
+
'fork-without-preserved-implementation': {
|
|
290
312
|
...durableExternal,
|
|
291
|
-
invariant: 'Fork version entries preserve their historical
|
|
313
|
+
invariant: 'Fork version entries preserve their historical implementation.',
|
|
292
314
|
tier: 'source-static',
|
|
293
315
|
},
|
|
294
316
|
'governed-symbol-residue': {
|
|
@@ -300,7 +322,7 @@ const builtinWardenRuleMetadataInput = {
|
|
|
300
322
|
},
|
|
301
323
|
'implementation-returns-result': {
|
|
302
324
|
...durableExternal,
|
|
303
|
-
invariant: '
|
|
325
|
+
invariant: 'Implementations return Result values.',
|
|
304
326
|
tier: 'source-static',
|
|
305
327
|
},
|
|
306
328
|
'incomplete-accessor-for-standard-op': {
|
|
@@ -368,7 +390,7 @@ const builtinWardenRuleMetadataInput = {
|
|
|
368
390
|
'no-destructured-compose': {
|
|
369
391
|
...durableExternal,
|
|
370
392
|
invariant:
|
|
371
|
-
'Trail
|
|
393
|
+
'Trail implementations compose through ctx.compose() directly instead of destructuring compose from the context.',
|
|
372
394
|
tier: 'source-static',
|
|
373
395
|
},
|
|
374
396
|
'no-dev-permit-in-source': {
|
|
@@ -447,12 +469,12 @@ const builtinWardenRuleMetadataInput = {
|
|
|
447
469
|
relatedRules: ['implementation-returns-result', 'no-native-error-result'],
|
|
448
470
|
steps: [
|
|
449
471
|
'Return Result.err() with the most specific TrailsError subclass available.',
|
|
450
|
-
'Use detours for recoverable runtime strategies instead of throwing inside the
|
|
472
|
+
'Use detours for recoverable runtime strategies instead of throwing inside the implementation.',
|
|
451
473
|
],
|
|
452
474
|
summary:
|
|
453
|
-
'Convert thrown failures in
|
|
475
|
+
'Convert thrown failures in implementations into explicit Result.err() outcomes.',
|
|
454
476
|
},
|
|
455
|
-
invariant: '
|
|
477
|
+
invariant: 'Implementations return Result.err() instead of throwing.',
|
|
456
478
|
tier: 'source-static',
|
|
457
479
|
},
|
|
458
480
|
'no-top-level-surface': {
|
|
@@ -564,7 +586,7 @@ const builtinWardenRuleMetadataInput = {
|
|
|
564
586
|
},
|
|
565
587
|
'reference-exists': {
|
|
566
588
|
...durableExternal,
|
|
567
|
-
invariant: 'Reference declarations resolve to known
|
|
589
|
+
invariant: 'Reference declarations resolve to known entities.',
|
|
568
590
|
tier: 'project-static',
|
|
569
591
|
},
|
|
570
592
|
'resolved-import-boundary': {
|
|
@@ -2,10 +2,9 @@ import {
|
|
|
2
2
|
collectCrudTableIds,
|
|
3
3
|
collectReconcileTableIds,
|
|
4
4
|
findStoreTableDefinitions,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from '
|
|
8
|
-
import type { AstNode } from './ast.js';
|
|
5
|
+
} from './source/stores.js';
|
|
6
|
+
import { offsetToLine, parse } from '@ontrails/source';
|
|
7
|
+
import type { AstNode } from '@ontrails/source';
|
|
9
8
|
import { isTestFile } from './scan.js';
|
|
10
9
|
import type {
|
|
11
10
|
ProjectAwareWardenRule,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
findImplementationBodies,
|
|
3
3
|
findTrailDefinitions,
|
|
4
4
|
getNodeComputed,
|
|
5
5
|
getNodeId,
|
|
@@ -16,15 +16,15 @@ import {
|
|
|
16
16
|
offsetToLine,
|
|
17
17
|
parse,
|
|
18
18
|
walkWithScopes,
|
|
19
|
-
} from '
|
|
19
|
+
} from '@ontrails/source';
|
|
20
|
+
import type { AstNode } from '@ontrails/source';
|
|
20
21
|
import { isFrameworkInternalFile, isTestFile } from './scan.js';
|
|
21
|
-
import type { AstNode } from './ast.js';
|
|
22
22
|
import type { WardenDiagnostic, WardenRule } from './types.js';
|
|
23
23
|
|
|
24
24
|
const RULE_NAME = 'no-destructured-compose';
|
|
25
25
|
|
|
26
26
|
const diagnosticMessage = (trailId: string): string =>
|
|
27
|
-
`Trail "${trailId}" destructures compose from the
|
|
27
|
+
`Trail "${trailId}" destructures compose from the implementation context. Use ctx.compose(...) directly so composition stays visible and Warden can recognize composed Result values.`;
|
|
28
28
|
|
|
29
29
|
const propertyKeyName = (property: AstNode): string | null => {
|
|
30
30
|
if (getNodeComputed(property) === true) {
|
|
@@ -60,8 +60,8 @@ const findComposeBinding = (pattern: AstNode | undefined): AstNode | null => {
|
|
|
60
60
|
return null;
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const
|
|
64
|
-
getNodeParams(
|
|
63
|
+
const implementationParams = (implementation: AstNode): readonly AstNode[] =>
|
|
64
|
+
getNodeParams(implementation) ?? [];
|
|
65
65
|
|
|
66
66
|
const destructuredComposeFromVariableDeclarator = (
|
|
67
67
|
node: AstNode,
|
|
@@ -104,13 +104,13 @@ const checkBodyDestructuring = (
|
|
|
104
104
|
sourceCode: string,
|
|
105
105
|
filePath: string,
|
|
106
106
|
trailId: string,
|
|
107
|
-
|
|
107
|
+
implementation: AstNode,
|
|
108
108
|
contextName: string
|
|
109
109
|
): WardenDiagnostic[] => {
|
|
110
110
|
const diagnostics: WardenDiagnostic[] = [];
|
|
111
111
|
|
|
112
112
|
walkWithScopes(
|
|
113
|
-
|
|
113
|
+
implementation,
|
|
114
114
|
(node, scopes) => {
|
|
115
115
|
if (isShadowed(contextName, scopes)) {
|
|
116
116
|
return;
|
|
@@ -155,8 +155,10 @@ export const noDestructuredCompose: WardenRule = {
|
|
|
155
155
|
continue;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
for (const
|
|
159
|
-
|
|
158
|
+
for (const implementation of findImplementationBodies(
|
|
159
|
+
definition.config
|
|
160
|
+
)) {
|
|
161
|
+
const params = implementationParams(implementation);
|
|
160
162
|
const [, contextParam] = params;
|
|
161
163
|
const paramComposeBinding = findComposeBinding(contextParam);
|
|
162
164
|
|
|
@@ -177,7 +179,7 @@ export const noDestructuredCompose: WardenRule = {
|
|
|
177
179
|
sourceCode,
|
|
178
180
|
filePath,
|
|
179
181
|
definition.id,
|
|
180
|
-
|
|
182
|
+
implementation,
|
|
181
183
|
contextName
|
|
182
184
|
)
|
|
183
185
|
);
|
|
@@ -188,7 +190,7 @@ export const noDestructuredCompose: WardenRule = {
|
|
|
188
190
|
return diagnostics;
|
|
189
191
|
},
|
|
190
192
|
description:
|
|
191
|
-
'Coach trail
|
|
193
|
+
'Coach trail implementations to compose with ctx.compose(...) directly instead of destructuring compose from the context.',
|
|
192
194
|
name: RULE_NAME,
|
|
193
195
|
severity: 'warn',
|
|
194
196
|
};
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Flags direct `.
|
|
2
|
+
* Flags direct `.implementation()` calls in application code.
|
|
3
3
|
*
|
|
4
|
-
* Uses AST parsing to find `.
|
|
4
|
+
* Uses AST parsing to find `.implementation()` call expressions,
|
|
5
5
|
* ignoring occurrences in strings and comments.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
isImplementationCall,
|
|
10
|
+
offsetToLine,
|
|
11
|
+
parse,
|
|
12
|
+
walk,
|
|
13
|
+
} from '@ontrails/source';
|
|
9
14
|
import { isFrameworkInternalFile, isTestFile } from './scan.js';
|
|
10
15
|
import type { WardenDiagnostic, WardenRule } from './types.js';
|
|
11
16
|
|
|
12
17
|
/**
|
|
13
|
-
* Flags direct `.
|
|
18
|
+
* Flags direct `.implementation()` calls in application code.
|
|
14
19
|
*/
|
|
15
20
|
export const noDirectImplementationCall: WardenRule = {
|
|
16
21
|
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
@@ -26,12 +31,12 @@ export const noDirectImplementationCall: WardenRule = {
|
|
|
26
31
|
const diagnostics: WardenDiagnostic[] = [];
|
|
27
32
|
|
|
28
33
|
walk(ast, (node) => {
|
|
29
|
-
if (
|
|
34
|
+
if (isImplementationCall(node)) {
|
|
30
35
|
diagnostics.push({
|
|
31
36
|
filePath,
|
|
32
37
|
line: offsetToLine(sourceCode, node.start),
|
|
33
38
|
message:
|
|
34
|
-
'Use ctx.compose("trailId", input) instead of direct .
|
|
39
|
+
'Use ctx.compose("trailId", input) instead of direct .implementation() calls. Direct implementation access bypasses validation, tracing, and layers.',
|
|
35
40
|
rule: 'no-direct-implementation-call',
|
|
36
41
|
severity: 'warn',
|
|
37
42
|
});
|
|
@@ -41,7 +46,7 @@ export const noDirectImplementationCall: WardenRule = {
|
|
|
41
46
|
return diagnostics;
|
|
42
47
|
},
|
|
43
48
|
description:
|
|
44
|
-
'Disallow direct .
|
|
49
|
+
'Disallow direct .implementation() calls in application code. Use ctx.compose() instead.',
|
|
45
50
|
name: 'no-direct-implementation-call',
|
|
46
51
|
severity: 'warn',
|
|
47
52
|
};
|
|
@@ -40,8 +40,8 @@ import {
|
|
|
40
40
|
isStringLiteral,
|
|
41
41
|
offsetToLine,
|
|
42
42
|
parse,
|
|
43
|
-
} from '
|
|
44
|
-
import type { AstNode } from '
|
|
43
|
+
} from '@ontrails/source';
|
|
44
|
+
import type { AstNode } from '@ontrails/source';
|
|
45
45
|
import type { WardenDiagnostic, WardenFix, WardenRule } from './types.js';
|
|
46
46
|
|
|
47
47
|
const RULE_NAME = 'no-legacy-cli-alias-export';
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
offsetToLine,
|
|
8
8
|
parse,
|
|
9
9
|
walk,
|
|
10
|
-
} from '
|
|
10
|
+
} from '@ontrails/source';
|
|
11
|
+
import type { AstNode } from '@ontrails/source';
|
|
11
12
|
import { isFrameworkInternalFile } from './scan.js';
|
|
12
|
-
import type { AstNode } from './ast.js';
|
|
13
13
|
import type { WardenDiagnostic, WardenRule } from './types.js';
|
|
14
14
|
|
|
15
15
|
const RULE_NAME = 'no-native-error-result';
|