@telorun/analyzer 0.69.0 → 0.71.0

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 (98) hide show
  1. package/dist/analysis-registry.d.ts.map +1 -1
  2. package/dist/analysis-registry.js +4 -5
  3. package/dist/analyzer.d.ts.map +1 -1
  4. package/dist/analyzer.js +50 -8
  5. package/dist/call-graph.d.ts.map +1 -1
  6. package/dist/call-graph.js +10 -4
  7. package/dist/canonical-json.d.ts +18 -0
  8. package/dist/canonical-json.d.ts.map +1 -0
  9. package/dist/canonical-json.js +26 -0
  10. package/dist/cel-access-chains.d.ts +14 -0
  11. package/dist/cel-access-chains.d.ts.map +1 -0
  12. package/dist/cel-access-chains.js +45 -0
  13. package/dist/cel-scope-query.d.ts +14 -0
  14. package/dist/cel-scope-query.d.ts.map +1 -1
  15. package/dist/cel-scope-query.js +36 -6
  16. package/dist/definition-registry.d.ts.map +1 -1
  17. package/dist/definition-registry.js +3 -4
  18. package/dist/flatten-for-analyzer.d.ts +2 -2
  19. package/dist/flatten-for-analyzer.d.ts.map +1 -1
  20. package/dist/flatten-for-analyzer.js +1 -1
  21. package/dist/index.d.ts +5 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3 -1
  24. package/dist/manifest-diff.d.ts +111 -0
  25. package/dist/manifest-diff.d.ts.map +1 -0
  26. package/dist/manifest-diff.js +130 -0
  27. package/dist/manifest-visitor.d.ts.map +1 -1
  28. package/dist/manifest-visitor.js +11 -3
  29. package/dist/module-alias-scope.d.ts +65 -0
  30. package/dist/module-alias-scope.d.ts.map +1 -0
  31. package/dist/module-alias-scope.js +25 -0
  32. package/dist/{zone-module-documents.d.ts → module-documents.d.ts} +11 -7
  33. package/dist/module-documents.d.ts.map +1 -0
  34. package/dist/module-graph.d.ts.map +1 -1
  35. package/dist/module-graph.js +3 -25
  36. package/dist/ref-sentinel-target.d.ts +38 -0
  37. package/dist/ref-sentinel-target.d.ts.map +1 -0
  38. package/dist/ref-sentinel-target.js +13 -0
  39. package/dist/resolve-schema-type-refs.d.ts.map +1 -1
  40. package/dist/resolve-schema-type-refs.js +2 -1
  41. package/dist/resolve-throws-union.d.ts +18 -1
  42. package/dist/resolve-throws-union.d.ts.map +1 -1
  43. package/dist/resolve-throws-union.js +93 -9
  44. package/dist/resolve-zone-requirements.d.ts +3 -3
  45. package/dist/resolve-zone-requirements.d.ts.map +1 -1
  46. package/dist/resolve-zone-requirements.js +12 -12
  47. package/dist/telo-version.d.ts +1 -1
  48. package/dist/telo-version.js +1 -1
  49. package/dist/template-body.d.ts.map +1 -1
  50. package/dist/template-body.js +2 -4
  51. package/dist/types.d.ts +3 -3
  52. package/dist/types.d.ts.map +1 -1
  53. package/dist/validate-invocation-contract.d.ts +5 -0
  54. package/dist/validate-invocation-contract.d.ts.map +1 -1
  55. package/dist/validate-invocation-contract.js +114 -3
  56. package/dist/validate-logging.d.ts.map +1 -1
  57. package/dist/validate-logging.js +2 -2
  58. package/dist/validate-references.d.ts +16 -5
  59. package/dist/validate-references.d.ts.map +1 -1
  60. package/dist/validate-references.js +30 -12
  61. package/dist/validate-resource-inputs.d.ts +1 -26
  62. package/dist/validate-resource-inputs.d.ts.map +1 -1
  63. package/dist/validate-resource-inputs.js +12 -2
  64. package/dist/validate-schema-type-refs.d.ts.map +1 -1
  65. package/dist/validate-schema-type-refs.js +2 -1
  66. package/dist/validate-throws-coverage.d.ts +5 -1
  67. package/dist/validate-throws-coverage.d.ts.map +1 -1
  68. package/dist/validate-throws-coverage.js +6 -2
  69. package/package.json +2 -2
  70. package/src/analysis-registry.ts +4 -5
  71. package/src/analyzer.ts +61 -10
  72. package/src/call-graph.ts +9 -3
  73. package/src/canonical-json.ts +24 -0
  74. package/src/cel-access-chains.ts +47 -0
  75. package/src/cel-scope-query.ts +45 -8
  76. package/src/definition-registry.ts +3 -6
  77. package/src/flatten-for-analyzer.ts +3 -3
  78. package/src/index.ts +10 -2
  79. package/src/manifest-diff.ts +219 -0
  80. package/src/manifest-visitor.ts +11 -3
  81. package/src/module-alias-scope.ts +94 -0
  82. package/src/{zone-module-documents.ts → module-documents.ts} +10 -6
  83. package/src/module-graph.ts +2 -24
  84. package/src/ref-sentinel-target.ts +46 -0
  85. package/src/resolve-schema-type-refs.ts +2 -1
  86. package/src/resolve-throws-union.ts +123 -8
  87. package/src/resolve-zone-requirements.ts +14 -14
  88. package/src/telo-version.ts +1 -1
  89. package/src/template-body.ts +2 -5
  90. package/src/types.ts +3 -3
  91. package/src/validate-invocation-contract.ts +128 -2
  92. package/src/validate-logging.ts +2 -3
  93. package/src/validate-references.ts +41 -11
  94. package/src/validate-resource-inputs.ts +26 -3
  95. package/src/validate-schema-type-refs.ts +2 -1
  96. package/src/validate-throws-coverage.ts +12 -1
  97. package/dist/zone-module-documents.d.ts.map +0 -1
  98. /package/dist/{zone-module-documents.js → module-documents.js} +0 -0
@@ -5,6 +5,8 @@ import { navigateJsonPointer, substituteCelFields } from "./schema-compat.js";
5
5
  import { REF_VALIDATION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
6
6
  import { resolveTypeFieldToSchema } from "./validate-cel-context.js";
7
7
  import { DiagnosticSeverity } from "./types.js";
8
+ import { moduleAliasScope } from "./module-alias-scope.js";
9
+ import { isInjectedDeclaration } from "./resource-input.js";
8
10
  const SOURCE = "telo-analyzer";
9
11
  /**
10
12
  * Liskov substitutability at a kind constraint: is `resolved` (a canonical
@@ -16,17 +18,28 @@ const SOURCE = "telo-analyzer";
16
18
  *
17
19
  * A value satisfies the slot when it transitively extends the target kind, or —
18
20
  * for a CONCRETE target — IS that kind; `getByExtends` is the same transitive
19
- * subtype index for both, and an abstract is satisfied only by an implementer,
20
- * never by the abstract itself (which is non-instantiable). Accepts a constraint
21
- * that resolves to nothing, and an abstract with no loaded implementations:
22
- * partial context, where a rejection would be a guess.
21
+ * subtype index for both. Accepts a constraint that resolves to nothing, and an
22
+ * abstract with no loaded implementations: partial context, where a rejection
23
+ * would be a guess.
24
+ *
25
+ * An abstract is satisfied only by an implementer, never by the abstract itself,
26
+ * because a resource declared `kind: <some abstract>` is refused at `create()`
27
+ * and reported as `ABSTRACT_KIND_INSTANTIATED` at its declaration — so accepting
28
+ * it here would leave the reference sites silent about a manifest that cannot
29
+ * run. `isDeclaration` is the ONE exception and it is not an instance: a
30
+ * library's `resources:` entry is constrained by kind alone, so its kind-only
31
+ * stand-in routinely IS an abstract (`connection: {kind: Sql.Connection}`), and
32
+ * every use of that name inside the library is a reference to it.
23
33
  */
24
- export function kindSatisfies(resolved, targetKind, registry) {
34
+ export function kindSatisfies(resolved, targetKind, registry,
35
+ /** The referent is a kind-only stand-in for a `resources:` entry rather than a
36
+ * resource anything instantiates, so identity satisfies an abstract slot. */
37
+ isDeclaration = false) {
25
38
  const canonical = registry.resolveRef(targetKind) ?? targetKind;
26
39
  const targetDef = registry.resolve(canonical);
27
40
  if (!targetDef)
28
41
  return true;
29
- if (targetDef.kind !== "Telo.Abstract" && resolved === canonical)
42
+ if ((targetDef.kind !== "Telo.Abstract" || isDeclaration) && resolved === canonical)
30
43
  return true;
31
44
  const subtypes = registry.getByExtends(canonical);
32
45
  if (subtypes.some((d) => `${d.metadata.module}.${d.metadata.name}` === resolved))
@@ -53,7 +66,9 @@ export function kindSatisfies(resolved, targetKind, registry) {
53
66
  /** {@link kindSatisfies} at every kind a slot accepts, rendered as messages.
54
67
  * The acceptance rule is not restated here — only what to say when it fails,
55
68
  * which needs the slot's alternatives and the target's flavour. */
56
- function checkKind(kind, entry, registry, aliases) {
69
+ function checkKind(kind, entry, registry, aliases,
70
+ /** See {@link kindSatisfies}: the referent is a `resources:` stand-in. */
71
+ isDeclaration = false) {
57
72
  const resolved = aliases.resolveKind(kind) ?? kind;
58
73
  // A qualified kind whose prefix names no import in this scope is a bad NAME
59
74
  // rather than a bad type — `{kind: NotAnAlias.Script}` where only `Lib` is
@@ -76,7 +91,7 @@ function checkKind(kind, entry, registry, aliases) {
76
91
  const targetDef = registry.resolve(targetKind);
77
92
  if (!targetDef)
78
93
  return [];
79
- if (!unknownAlias && kindSatisfies(resolved, targetKind, registry))
94
+ if (!unknownAlias && kindSatisfies(resolved, targetKind, registry, isDeclaration))
80
95
  return [];
81
96
  const subtypes = registry.getByExtends(targetKind);
82
97
  const subtypeKinds = new Set(subtypes.map((d) => `${d.metadata.module}.${d.metadata.name}`));
@@ -311,7 +326,7 @@ export function validateReferences(resources, context) {
311
326
  });
312
327
  return;
313
328
  }
314
- const kindErrors = checkKind(target.kind, entry, registry, aliases);
329
+ const kindErrors = checkKind(target.kind, entry, registry, aliases, isInjectedDeclaration(target));
315
330
  if (kindErrors.length > 0) {
316
331
  diagnostics.push({
317
332
  severity: DiagnosticSeverity.Error,
@@ -359,7 +374,11 @@ export function validateReferences(resources, context) {
359
374
  return;
360
375
  }
361
376
  // 2. Kind check
362
- const kindErrors = checkKind(refVal.kind, entry, registry, aliases);
377
+ const objectTarget = typeof refVal.name === "string"
378
+ ? (visibleScopeManifests.find((m) => m.metadata?.name === refVal.name) ??
379
+ byName.get(refVal.name))
380
+ : undefined;
381
+ const kindErrors = checkKind(refVal.kind, entry, registry, aliases, isInjectedDeclaration(objectTarget));
363
382
  if (kindErrors.length > 0) {
364
383
  diagnostics.push({
365
384
  severity: DiagnosticSeverity.Error,
@@ -448,8 +467,7 @@ export function validateReferences(resources, context) {
448
467
  if (!isAbsolute && anchorName.includes(".")) {
449
468
  const resolvedResourceKind = aliases.resolveKind(r.kind) ?? r.kind;
450
469
  const resourceDef = registry.resolve(r.kind) ?? registry.resolve(resolvedResourceKind);
451
- const owningModule = resourceDef?.metadata?.module;
452
- const ownerScope = (owningModule ? aliasesByModule?.get(owningModule) : undefined) ?? aliases;
470
+ const ownerScope = moduleAliasScope(resourceDef?.metadata, aliases, aliasesByModule);
453
471
  const targetKind = ownerScope.resolveKind(anchorName);
454
472
  if (!targetKind) {
455
473
  // Names the ALIAS, not the whole kind path: the alias is what an
@@ -2,34 +2,9 @@ import type { ResourceManifest } from "@telorun/sdk";
2
2
  import type { AliasResolver } from "./alias-resolver.js";
3
3
  import type { DefinitionRegistry } from "./definition-registry.js";
4
4
  import { type AnalysisDiagnostic } from "./types.js";
5
- /**
6
- * The strict half of {@link readResourceInputs} — the `validate-ref-slots.ts`
7
- * split applied to the resource-input boundary, and not optional for the same
8
- * reason: the two sides fail in OPPOSITE directions. An unreadable DECLARATION
9
- * leaves the library referencing a name nothing stands behind; an unchecked
10
- * INJECTION hands a library an instance of a kind it never asked for, which
11
- * surfaces as a method-missing failure inside someone else's module.
12
- *
13
- * Two surfaces, scoped the same way every other declaration check is — to the
14
- * entry's own modules, since a published dependency's block is not the
15
- * consumer's to fix:
16
- *
17
- * - **The library's own declaration.** Each entry's alias-qualified `kind:` must
18
- * resolve in the DECLARING module's scope (`RESOURCE_INPUT_KIND_UNRESOLVED`).
19
- * A constraint that resolves to nothing accepts anything, which is the same
20
- * hole `X_TELO_REF_UNRESOLVED` exists to close one level down.
21
- * - **The injection site.** Every declared entry must be supplied
22
- * (`RESOURCE_INPUT_MISSING` — the same failure as a missing required
23
- * variable), nothing beyond them may be (`RESOURCE_INPUT_UNKNOWN`), the value
24
- * must name a resource that exists (`RESOURCE_INPUT_UNRESOLVED`), and its
25
- * kind must satisfy the declared constraint transitively
26
- * (`RESOURCE_INPUT_KIND_MISMATCH`).
27
- *
28
- * Browser-safe.
29
- */
30
5
  export declare function validateResourceInputs(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver, rootModules: Set<string>,
31
6
  /** Kind acceptance, transitively — the `checkKind` rule `validate-references`
32
7
  * applies at an ordinary ref slot, passed in rather than re-derived so the
33
8
  * two cannot disagree about what satisfies a constraint. */
34
- acceptsKind: (suppliedKind: string, requiredKind: string) => boolean): AnalysisDiagnostic[];
9
+ acceptsKind: (suppliedKind: string, requiredKind: string, suppliedIsDeclaration: boolean) => boolean): AnalysisDiagnostic[];
35
10
  //# sourceMappingURL=validate-resource-inputs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-resource-inputs.d.ts","sourceRoot":"","sources":["../src/validate-resource-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAGnE,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,YAAY,CAAC;AASzE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC;AACxB;;6DAE6D;AAC7D,WAAW,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GACnE,kBAAkB,EAAE,CA6LtB"}
1
+ {"version":3,"file":"validate-resource-inputs.d.ts","sourceRoot":"","sources":["../src/validate-resource-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,YAAY,CAAC;AAqCzE,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC;AACxB;;6DAE6D;AAC7D,WAAW,EAAE,CACX,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,qBAAqB,EAAE,OAAO,KAC3B,OAAO,GACX,kBAAkB,EAAE,CAyMtB"}
@@ -1,6 +1,6 @@
1
1
  import { isRefSentinel } from "@telorun/templating";
2
2
  import { findDynamicLeaf } from "./resource-rule.js";
3
- import { readResourceInputs, readSuppliedResources } from "./resource-input.js";
3
+ import { isInjectedDeclaration, readResourceInputs, readSuppliedResources, } from "./resource-input.js";
4
4
  import { DiagnosticSeverity } from "./types.js";
5
5
  const SOURCE = "telo-analyzer";
6
6
  /**
@@ -28,6 +28,7 @@ const SOURCE = "telo-analyzer";
28
28
  *
29
29
  * Browser-safe.
30
30
  */
31
+ const declaringModule = (m) => m.metadata?.module;
31
32
  export function validateResourceInputs(manifests, registry, aliases, rootModules,
32
33
  /** Kind acceptance, transitively — the `checkKind` rule `validate-references`
33
34
  * applies at an ordinary ref slot, passed in rather than re-derived so the
@@ -186,7 +187,16 @@ acceptsKind) {
186
187
  if (typeof suppliedKind !== "string")
187
188
  continue;
188
189
  const canonical = aliases.resolveKind(suppliedKind) ?? suppliedKind;
189
- if (acceptsKind(canonical, entry))
190
+ // A library forwarding an input it was itself handed supplies a kind-only
191
+ // stand-in, whose kind is routinely the abstract the target declares. That
192
+ // is identity against an abstract, which an ordinary reference is refused
193
+ // — the referent there would be a resource nothing can instantiate, while
194
+ // here it is another declaration.
195
+ const suppliedName = value.name;
196
+ const supplied = typeof suppliedName === "string"
197
+ ? manifests.find((c) => c.metadata?.name === suppliedName && isOwn(declaringModule(c)))
198
+ : undefined;
199
+ if (acceptsKind(canonical, entry, isInjectedDeclaration(supplied)))
190
200
  continue;
191
201
  out.push({
192
202
  severity: DiagnosticSeverity.Error,
@@ -1 +1 @@
1
- {"version":3,"file":"validate-schema-type-refs.d.ts","sourceRoot":"","sources":["../src/validate-schema-type-refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAKzE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAC3C,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GACvB,kBAAkB,EAAE,CAuDtB"}
1
+ {"version":3,"file":"validate-schema-type-refs.d.ts","sourceRoot":"","sources":["../src/validate-schema-type-refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAMzE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAC3C,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GACvB,kBAAkB,EAAE,CAuDtB"}
@@ -1,5 +1,6 @@
1
1
  import { canonicalTypeSchemaId, parseTeloTypeRef } from "@telorun/sdk";
2
2
  import { DiagnosticSeverity } from "./types.js";
3
+ import { moduleAliasScope } from "./module-alias-scope.js";
3
4
  const SOURCE = "telo-analyzer";
4
5
  const SCHEMA_FIELDS = ["schema", "inputType", "outputType"];
5
6
  /**
@@ -28,7 +29,7 @@ export function validateSchemaTypeRefs(manifests, registry, aliases, aliasesByMo
28
29
  // validated against their own library when it's analyzed as a root.
29
30
  if (ownModule && !rootModules.has(ownModule))
30
31
  continue;
31
- const resolver = (ownModule ? aliasesByModule.get(ownModule) : undefined) ?? aliases;
32
+ const resolver = moduleAliasScope(m.metadata, aliases, aliasesByModule);
32
33
  const filePath = m.metadata?.source;
33
34
  const label = `${m.kind}/${name}`;
34
35
  const walk = (value, path) => {
@@ -4,5 +4,9 @@ import { type AliasResolver } from "./alias-resolver.js";
4
4
  import type { DefinitionRegistry } from "./definition-registry.js";
5
5
  import { type AnalysisDiagnostic } from "./types.js";
6
6
  /** Entry point — invoked once per analyze() run. */
7
- export declare function validateThrowsCoverage(manifests: ResourceManifest[], defs: DefinitionRegistry, aliases: AliasResolver, env: Environment, aliasesByModule?: Map<string, AliasResolver>, rootModules?: Set<string>): AnalysisDiagnostic[];
7
+ export declare function validateThrowsCoverage(manifests: ResourceManifest[], defs: DefinitionRegistry, aliases: AliasResolver, env: Environment, aliasesByModule?: Map<string, AliasResolver>, rootModules?: Set<string>,
8
+ /** Each imported library's full document set, so the walk can follow an
9
+ * exported entry point into the siblings it invokes — which a consumer's
10
+ * flat set does not carry. */
11
+ moduleManifests?: Map<string, ResourceManifest[]>): AnalysisDiagnostic[];
8
12
  //# sourceMappingURL=validate-throws-coverage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-throws-coverage.d.ts","sourceRoot":"","sources":["../src/validate-throws-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAsfzE,oDAAoD;AACpD,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,WAAW,EAChB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,EACvD,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,GACnC,kBAAkB,EAAE,CAkDtB"}
1
+ {"version":3,"file":"validate-throws-coverage.d.ts","sourceRoot":"","sources":["../src/validate-throws-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAsfzE,oDAAoD;AACpD,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,WAAW,EAChB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,EACvD,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa;AACpC;;+BAE+B;AAC/B,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAa,GAC3D,kBAAkB,EAAE,CAyDtB"}
@@ -431,10 +431,14 @@ function schemaHasStepContext(schema) {
431
431
  return false;
432
432
  }
433
433
  /** Entry point — invoked once per analyze() run. */
434
- export function validateThrowsCoverage(manifests, defs, aliases, env, aliasesByModule = new Map(), rootModules = new Set()) {
434
+ export function validateThrowsCoverage(manifests, defs, aliases, env, aliasesByModule = new Map(), rootModules = new Set(),
435
+ /** Each imported library's full document set, so the walk can follow an
436
+ * exported entry point into the siblings it invokes — which a consumer's
437
+ * flat set does not carry. */
438
+ moduleManifests = new Map()) {
435
439
  const diagnostics = [];
436
440
  diagnostics.push(...validateThrowsDeclarations(manifests));
437
- const resolveCtx = createResolveCtx(manifests, defs, aliases, aliasesByModule, rootModules);
441
+ const resolveCtx = createResolveCtx(manifests, defs, aliases, aliasesByModule, rootModules, moduleManifests);
438
442
  // The alias resolver for a manifest's own lexical scope — an imported library's
439
443
  // resolver when it owns the manifest, else undefined (fall back to root aliases).
440
444
  const scopeResolverFor = (m) => scopeResolverForModule(m.metadata?.module, rootModules, aliasesByModule);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.69.0",
3
+ "version": "0.71.0",
4
4
  "description": "Telo Analyzer - Static manifest validator for Telo manifests.",
5
5
  "keywords": [
6
6
  "telo",
@@ -49,7 +49,7 @@
49
49
  "@types/node": "^20.0.0",
50
50
  "typescript": "^5.0.0",
51
51
  "vitest": "^2.1.8",
52
- "@telorun/sdk": "0.83.0"
52
+ "@telorun/sdk": "0.87.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@telorun/sdk": "*"
@@ -9,6 +9,7 @@ import { visitManifest as runVisitManifest, type ManifestVisitor } from "./manif
9
9
  import type { ContractDirection, DefResolver } from "./extends-resolution.js";
10
10
  import { resolveContract } from "./invocation-contract.js";
11
11
  import { createResolveCtx, resolveThrowsUnion } from "./resolve-throws-union.js";
12
+ import { moduleAliasScope } from "./module-alias-scope.js";
12
13
  import { isRefEntry, isScopeEntry } from "./reference-field-map.js";
13
14
  import { resolveSchemaTypeRefs as resolveSchemaTypeRefsIn } from "./resolve-schema-type-refs.js";
14
15
  import type { AnalysisContext } from "./types.js";
@@ -258,8 +259,7 @@ export class AnalysisRegistry {
258
259
  */
259
260
  resolveSchemaFrom(schemaFrom: string, declaringKind: string): Record<string, any> | undefined {
260
261
  const def = this.resolveDefinition(declaringKind);
261
- const ownerModule = (def?.metadata as { module?: string } | undefined)?.module;
262
- const scope = (ownerModule ? this.aliasesByModule.get(ownerModule) : undefined) ?? this.aliases;
262
+ const scope = moduleAliasScope(def?.metadata, this.aliases, this.aliasesByModule);
263
263
  return this.defs.resolveSchemaFromNode(schemaFrom, scope);
264
264
  }
265
265
 
@@ -337,7 +337,7 @@ export class AnalysisRegistry {
337
337
  * the kind was read off. Falls back to the global table when the module is
338
338
  * unknown or is a root. */
339
339
  resolveDefinitionIn(kind: string, module?: string): ResourceDefinition | undefined {
340
- const scope = (module ? this.aliasesByModule.get(module) : undefined) ?? this.aliases;
340
+ const scope = moduleAliasScope({ module }, this.aliases, this.aliasesByModule);
341
341
  const canonical = scope.resolveKind(kind);
342
342
  return this.defs.resolve(kind) ?? (canonical ? this.defs.resolve(canonical) : undefined);
343
343
  }
@@ -354,8 +354,7 @@ export class AnalysisRegistry {
354
354
  resolverForDefinition(def: {
355
355
  metadata?: { module?: string };
356
356
  }): (kind: string) => ResourceDefinition | undefined {
357
- const ownModule = def?.metadata?.module;
358
- const scope = (ownModule ? this.aliasesByModule.get(ownModule) : undefined) ?? this.aliases;
357
+ const scope = moduleAliasScope(def?.metadata, this.aliases, this.aliasesByModule);
359
358
  return (kind) => {
360
359
  const canonical = scope.resolveKind(kind);
361
360
  return this.defs.resolve(kind) ?? (canonical ? this.defs.resolve(canonical) : undefined);
package/src/analyzer.ts CHANGED
@@ -47,6 +47,7 @@ import {
47
47
  } from "./validate-observed-state.js";
48
48
  import { computeSuggestKind } from "./kind-suggest.js";
49
49
  import { visitManifest } from "./manifest-visitor.js";
50
+ import { declaringModuleScope, moduleAliasScope } from "./module-alias-scope.js";
50
51
  import { isModuleKind } from "./module-kinds.js";
51
52
  import { normalizeInlineResources } from "./normalize-inline-resources.js";
52
53
  import { REF_VALIDATION_SKIP_KINDS } from "./system-kinds.js";
@@ -999,10 +1000,10 @@ export class StaticAnalyzer {
999
1000
  if (m.kind !== "Telo.Definition" && m.kind !== "Telo.Abstract") continue;
1000
1001
  const def = m as unknown as ResourceDefinition;
1001
1002
  const ownModule = (def.metadata as { module?: string } | undefined)?.module;
1002
- const scopeResolver =
1003
- ownModule && !rootModules.has(ownModule)
1004
- ? (aliasesByModule.get(ownModule) ?? new AliasResolver())
1005
- : aliases;
1003
+ const scopeResolver = declaringModuleScope(ownModule, aliases, {
1004
+ aliasesByModule,
1005
+ rootModules,
1006
+ });
1006
1007
  // Canonicalize alias-form `x-telo-ref` constraints in the DECLARING module's
1007
1008
  // scope, before the schema reaches `register()` and the lazily-built field
1008
1009
  // maps. Same pre-resolution `capability` / `extends` get below.
@@ -1425,7 +1426,7 @@ export class StaticAnalyzer {
1425
1426
  // analyzer knowing it exists: going native costs a module, not a change
1426
1427
  // here.
1427
1428
  const resolveRegionDef = (kind: string, module?: string) => {
1428
- const scope = (module ? aliasesByModule.get(module) : undefined) ?? aliases;
1429
+ const scope = moduleAliasScope({ module }, aliases, aliasesByModule);
1429
1430
  const canonical = scope.resolveKind(kind);
1430
1431
  return defs.resolve(kind) ?? (canonical ? defs.resolve(canonical) : undefined);
1431
1432
  };
@@ -1456,8 +1457,10 @@ export class StaticAnalyzer {
1456
1457
  if (!ownModule || !m.metadata?.name || typeof m.schema !== "object" || m.schema === null) {
1457
1458
  continue;
1458
1459
  }
1459
- const scopeResolver =
1460
- rootModules.has(ownModule) ? aliases : (aliasesByModule.get(ownModule) ?? new AliasResolver());
1460
+ const scopeResolver = declaringModuleScope(ownModule, aliases, {
1461
+ aliasesByModule,
1462
+ rootModules,
1463
+ });
1461
1464
  const canonicalKind = scopeResolver.resolveKind(m.kind as string) ?? (m.kind as string);
1462
1465
  if (defs.resolve(canonicalKind)?.capability !== "Telo.Type") continue;
1463
1466
  const typeName = m.metadata.name as string;
@@ -1877,6 +1880,37 @@ export class StaticAnalyzer {
1877
1880
  continue;
1878
1881
  }
1879
1882
 
1883
+ // An abstract names a CONTRACT for reference slots, not something to
1884
+ // instantiate — the kernel refuses it outright at `create()`. Reported
1885
+ // here, at the declaration, because that is the line the author has to
1886
+ // change and because a runtime-only refusal is exactly the shape static
1887
+ // analysis exists to remove. The hint mirrors the kernel's: the concrete
1888
+ // implementations this analysis holds, or the reason there are none.
1889
+ //
1890
+ // Injected declarations never reach this loop (skipped above), which is
1891
+ // what keeps a `resources:` entry — kind-only and routinely abstract by
1892
+ // design — out of it.
1893
+ if (definition.kind === "Telo.Abstract") {
1894
+ const canonical = resolvedKind ?? m.kind;
1895
+ const impls = defs
1896
+ .getByExtends(canonical)
1897
+ .filter((d) => d.kind !== "Telo.Abstract")
1898
+ .map((d) => `${d.metadata.module}.${d.metadata.name}`);
1899
+ const kindInfo =
1900
+ canonical !== m.kind ? `'${m.kind}' (resolved to '${canonical}')` : `'${m.kind}'`;
1901
+ const hint = impls.length
1902
+ ? `instantiate a concrete implementation: ${impls.join(", ")}`
1903
+ : "no concrete implementations are registered — import a module that provides one";
1904
+ diagnostics.push({
1905
+ severity: DiagnosticSeverity.Error,
1906
+ code: "ABSTRACT_KIND_INSTANTIATED",
1907
+ source: SOURCE,
1908
+ message: `Kind ${kindInfo} is abstract and cannot be instantiated directly; ${hint}.`,
1909
+ data: { resource, filePath, path: "kind" },
1910
+ });
1911
+ continue;
1912
+ }
1913
+
1880
1914
  // Validate resource config against the definition's AUTHOR-FACING schema —
1881
1915
  // inheritance-resolved, with `kind` / `metadata` injected. See
1882
1916
  // `validationSchemaFor`, which is where both derivations and the reason
@@ -2690,9 +2724,25 @@ export class StaticAnalyzer {
2690
2724
  // Validate provider coherence rules for `provide:` template-target definitions.
2691
2725
  diagnostics.push(...validateProviderCoherence(allManifests, defs, aliases));
2692
2726
 
2693
- // Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7)
2727
+ // Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7).
2728
+ // The library document sets collected for the zone stage serve here too: a
2729
+ // library's exported entry point is forwarded into the flat set while the
2730
+ // siblings it invokes are not, so without them the walk stops at the
2731
+ // boundary and reports an empty union for a body that plainly throws.
2732
+ const libraryManifests = new Map<string, ResourceManifest[]>();
2733
+ for (const doc of options?.moduleDocuments ?? []) {
2734
+ libraryManifests.set(doc.module, doc.manifests);
2735
+ }
2694
2736
  diagnostics.push(
2695
- ...validateThrowsCoverage(allManifests, defs, aliases, this.celEnv, aliasesByModule, rootModules),
2737
+ ...validateThrowsCoverage(
2738
+ allManifests,
2739
+ defs,
2740
+ aliases,
2741
+ this.celEnv,
2742
+ aliasesByModule,
2743
+ rootModules,
2744
+ libraryManifests,
2745
+ ),
2696
2746
  );
2697
2747
 
2698
2748
  // Warn about declared variables / secrets / ports that no CEL references.
@@ -2710,7 +2760,8 @@ export class StaticAnalyzer {
2710
2760
  defs,
2711
2761
  aliases,
2712
2762
  rootModules,
2713
- (supplied, required) => kindSatisfies(supplied, required, defs),
2763
+ (supplied, required, isDeclaration) =>
2764
+ kindSatisfies(supplied, required, defs, isDeclaration),
2714
2765
  ),
2715
2766
  );
2716
2767
 
package/src/call-graph.ts CHANGED
@@ -40,6 +40,7 @@
40
40
  */
41
41
  import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
42
42
  import { isRefSentinel, isTaggedSentinel } from "@telorun/templating";
43
+ import { refSentinelTarget } from "./ref-sentinel-target.js";
43
44
  import type { AliasResolver } from "./alias-resolver.js";
44
45
  import type { DefinitionRegistry } from "./definition-registry.js";
45
46
  import {
@@ -63,6 +64,7 @@ import {
63
64
  type RefFieldEntry,
64
65
  } from "./reference-field-map.js";
65
66
  import { DEPENDENCY_GRAPH_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
67
+ import { moduleAliasScope } from "./module-alias-scope.js";
66
68
 
67
69
  export interface ResourceGraphNode {
68
70
  type: "resource";
@@ -693,8 +695,7 @@ export function buildCallGraph(
693
695
  const definitionFor = (manifest: ResourceManifest): ResourceDefinition | undefined => {
694
696
  const direct = registry.resolve(manifest.kind as string);
695
697
  if (direct) return direct;
696
- const module = (manifest.metadata as { module?: string } | undefined)?.module;
697
- const scope = (module ? options.aliasesByModule?.get(module) : undefined) ?? options.aliases;
698
+ const scope = moduleAliasScope(manifest.metadata, options.aliases, options.aliasesByModule);
698
699
  const canonical = scope?.resolveKind(manifest.kind as string);
699
700
  return canonical ? registry.resolve(canonical) : undefined;
700
701
  };
@@ -962,7 +963,12 @@ export function buildCallGraph(
962
963
  * here: an unresolved `!ref <name>` sentinel and the `{kind, name}` object
963
964
  * `resolveRefSentinels` rewrites it into. */
964
965
  function refTargetName(value: unknown): string | undefined {
965
- if (isRefSentinel(value)) return value.source;
966
+ // An edge's target is taken VERBATIM — `resolveScopedName` and the node index
967
+ // are what resolve it, and a cross-module `Alias.name` is a target this graph
968
+ // legitimately carries unresolved. That is this pass's reduction over the
969
+ // shared parse, not a second reading of the tag.
970
+ const sentinel = refSentinelTarget(value);
971
+ if (sentinel) return sentinel.source;
966
972
  if (!value || typeof value !== "object") return undefined;
967
973
  const name = (value as Record<string, unknown>).name;
968
974
  return typeof name === "string" ? name : undefined;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Canonical JSON: a rendering under which two structurally equal values produce
3
+ * the same string — object keys sorted, `undefined`-valued entries dropped.
4
+ *
5
+ * Shared rather than duplicated because both readers compare the RESULT for
6
+ * equality: `contentKey` anchors a module-graph row's identity to its content so
7
+ * an inserted sibling does not move it, and `declarationSignature` decides
8
+ * whether a resource has to be rebuilt. Two implementations would eventually
9
+ * disagree about key order or about `undefined`, and the disagreement surfaces
10
+ * as a row that silently changes identity, or as a resource that silently fails
11
+ * to restart.
12
+ *
13
+ * Browser-safe: no hashing, no `node:crypto`. Callers that only need equality
14
+ * compare the strings, which cannot collide; a caller that needs a short key
15
+ * hashes it itself.
16
+ */
17
+ export function canonicalJson(value: unknown): string {
18
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
19
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
20
+ const entries = Object.entries(value as Record<string, unknown>)
21
+ .filter(([, v]) => v !== undefined)
22
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
23
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
24
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * What a manifest's CEL READS, as opposed to what its slots reference.
3
+ *
4
+ * A dependency a manifest states without a slot: `!cel "resources.db.dsn"` makes
5
+ * the reader depend on `db` as surely as a `!ref` would, and no reference walk
6
+ * can see it. Two consumers ask for it — the module graph draws it as a `data`
7
+ * edge, and the kernel needs it to know who becomes invalid when a resource is
8
+ * rebuilt — so the extraction is here rather than in either of them.
9
+ *
10
+ * The expensive half is the CEL environment used to parse, which is built once
11
+ * per process and evaluates nothing.
12
+ */
13
+ import { extractAccessChains } from "@telorun/templating";
14
+ import type { ResourceManifest } from "@telorun/sdk";
15
+ import { walkCelExpressions } from "@telorun/templating";
16
+ import { buildCelEnvironment } from "./cel-environment.js";
17
+
18
+ let parseEnv: ReturnType<typeof buildCelEnvironment> | undefined;
19
+
20
+ /** Access chains an expression reads, or none when it does not parse — a syntax
21
+ * error is the engine pass's to report, not this one's. */
22
+ export function accessChains(source: string): string[][] {
23
+ try {
24
+ parseEnv ??= buildCelEnvironment();
25
+ return extractAccessChains(parseEnv.parse(source).ast);
26
+ } catch {
27
+ return [];
28
+ }
29
+ }
30
+
31
+ /**
32
+ * The bare resource names a manifest's expressions read through
33
+ * `resources.<name>`, deduplicated.
34
+ *
35
+ * Read from the DECLARATION: a compile-eval field is expanded before a
36
+ * controller sees it, so by then `resources.db.dsn` is a literal with nothing
37
+ * left to say where it came from.
38
+ */
39
+ export function celResourceReads(manifest: ResourceManifest): string[] {
40
+ const names = new Set<string>();
41
+ walkCelExpressions(manifest as Record<string, unknown>, "", (source: string) => {
42
+ for (const chain of accessChains(source)) {
43
+ if (chain[0] === "resources" && chain.length >= 2 && chain[1]) names.add(chain[1]);
44
+ }
45
+ });
46
+ return [...names];
47
+ }
@@ -18,6 +18,7 @@ import { buildCelEnvironment } from "./cel-environment.js";
18
18
  import { CelScopeResolver, type CelScope } from "./cel-scope.js";
19
19
  import { DefinitionRegistry } from "./definition-registry.js";
20
20
  import { buildKernelGlobalsIndex } from "./kernel-globals.js";
21
+ import { moduleAliasScope } from "./module-alias-scope.js";
21
22
  import { isModuleKind } from "./module-kinds.js";
22
23
  import { navigateConcretePath } from "./manifest-path.js";
23
24
  import { findManifest } from "./find-manifest.js";
@@ -41,6 +42,12 @@ export interface ContextDeclarationSite {
41
42
  kind: string;
42
43
  name: string;
43
44
  path: string;
45
+ /** The declaring module, when the manifest carries one. Part of the identity,
46
+ * not decoration: a resource name is unique inside its module and not across a
47
+ * flattened set, so `(kind, name)` alone lets a host locating this site land on
48
+ * another library's same-named document — which for go-to-declaration means
49
+ * jumping to the wrong file with nothing to indicate it. */
50
+ module?: string;
44
51
  }
45
52
 
46
53
  /** Join a concrete path segment, tolerating an empty base (the manifest root). */
@@ -210,9 +217,14 @@ export class CelScopeQuery {
210
217
  base = hit;
211
218
  }
212
219
 
213
- const metadata = origin.manifest.metadata as { name?: string } | undefined;
220
+ const metadata = origin.manifest.metadata as { name?: string; module?: string } | undefined;
214
221
  if (!origin.manifest.kind || !metadata?.name) return undefined;
215
- return { kind: origin.manifest.kind, name: metadata.name, path: base };
222
+ return {
223
+ kind: origin.manifest.kind,
224
+ name: metadata.name,
225
+ path: base,
226
+ ...(metadata.module === undefined ? {} : { module: metadata.module }),
227
+ };
216
228
  }
217
229
 
218
230
  /** The manifest and path an annotated context property is derived from, and
@@ -265,13 +277,29 @@ export class CelScopeQuery {
265
277
  if (hash <= 0) return undefined;
266
278
  const kindValue = navigateConcretePath(root, first.slice(0, hash).split("/").join("."));
267
279
  if (typeof kindValue !== "string") return undefined;
268
- const canonical = this.ctx.aliases.resolveKind(kindValue) ?? kindValue;
269
- const suffix = canonical.slice(canonical.indexOf(".") + 1);
270
- const target = this.manifests.find(
280
+ // The kind was read off THIS resource, so it is spelled in the alias scope
281
+ // of the module that declared it — not the entry's.
282
+ const scope = moduleAliasScope(resource.metadata, this.ctx.aliases, this.ctx.aliasesByModule);
283
+ const canonical = scope.resolveKind(kindValue) ?? kindValue;
284
+ const dot = canonical.indexOf(".");
285
+ const owningModule = dot === -1 ? undefined : canonical.slice(0, dot);
286
+ const suffix = canonical.slice(dot + 1);
287
+ // Matched by MODULE AND NAME once the canonical form carries a module: a
288
+ // definition name is unique inside its module and not across a flattened
289
+ // set, so two libraries each declaring an `Api` would otherwise resolve to
290
+ // whichever came first — and the editor would type a context region off the
291
+ // wrong kind's annotations, silently. Falls back to name alone only when the
292
+ // kind did not resolve to a canonical form, where there is nothing to narrow
293
+ // by and a best-effort match is still better than none.
294
+ const named = this.manifests.filter(
271
295
  (m) =>
272
296
  (m.kind === "Telo.Definition" || m.kind === "Telo.Abstract") &&
273
297
  (m.metadata as { name?: string } | undefined)?.name === suffix,
274
- ) as Record<string, any> | undefined;
298
+ ) as Record<string, any>[];
299
+ const target =
300
+ (owningModule
301
+ ? named.find((m) => (m.metadata as { module?: string } | undefined)?.module === owningModule)
302
+ : undefined) ?? (owningModule && named.length > 1 ? undefined : named[0]);
275
303
  if (!target) return undefined;
276
304
  return { manifest: target, path: first.slice(hash + 1), propertyMap: false };
277
305
  }
@@ -313,9 +341,18 @@ export class CelScopeQuery {
313
341
  return undefined;
314
342
  }
315
343
 
344
+ /** The kind's declaration, resolved in the scope of the module that DECLARED
345
+ * this resource — the same rule the manifest visitor applies, and the reason
346
+ * it has to be the same one: everything this query offers is read off the
347
+ * definition, so resolving a forwarded library resource's `kind: Http.Api`
348
+ * through the entry's aliases alone found nothing and the editor silently
349
+ * offered no `request` / `result` at a site `telo check` accepts. A
350
+ * completion list is a claim that the name it offers will pass the checker,
351
+ * so the two must resolve a kind identically. */
316
352
  private definitionFor(resource: ResourceManifest): ResourceDefinition | undefined {
317
- const { defs, aliases } = this.ctx;
318
- const canonical = aliases.resolveKind(resource.kind);
353
+ const { defs, aliases, aliasesByModule } = this.ctx;
354
+ const scope = moduleAliasScope(resource.metadata, aliases, aliasesByModule);
355
+ const canonical = scope.resolveKind(resource.kind);
319
356
  return defs.resolve(resource.kind) ?? (canonical ? defs.resolve(canonical) : undefined);
320
357
  }
321
358