@telorun/analyzer 0.32.0 → 0.34.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 (47) hide show
  1. package/dist/analysis-registry.d.ts.map +1 -1
  2. package/dist/analysis-registry.js +10 -9
  3. package/dist/analyzer.d.ts.map +1 -1
  4. package/dist/analyzer.js +284 -80
  5. package/dist/builtins.d.ts.map +1 -1
  6. package/dist/builtins.js +15 -0
  7. package/dist/definition-registry.d.ts +4 -1
  8. package/dist/definition-registry.d.ts.map +1 -1
  9. package/dist/definition-registry.js +20 -3
  10. package/dist/extends-resolution.d.ts +33 -0
  11. package/dist/extends-resolution.d.ts.map +1 -0
  12. package/dist/extends-resolution.js +82 -0
  13. package/dist/index.d.ts +5 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +3 -1
  16. package/dist/sources/integrity.d.ts +7 -0
  17. package/dist/sources/integrity.d.ts.map +1 -1
  18. package/dist/sources/integrity.js +12 -2
  19. package/dist/sources/module-ref.d.ts +6 -2
  20. package/dist/sources/module-ref.d.ts.map +1 -1
  21. package/dist/sources/module-ref.js +7 -6
  22. package/dist/validate-base-mapping.d.ts +21 -0
  23. package/dist/validate-base-mapping.d.ts.map +1 -0
  24. package/dist/validate-base-mapping.js +130 -0
  25. package/dist/validate-extends.d.ts.map +1 -1
  26. package/dist/validate-extends.js +20 -9
  27. package/dist/validate-kind-descriptions.d.ts +20 -0
  28. package/dist/validate-kind-descriptions.d.ts.map +1 -0
  29. package/dist/validate-kind-descriptions.js +65 -0
  30. package/dist/validate-provider-coherence.d.ts.map +1 -1
  31. package/dist/validate-provider-coherence.js +8 -1
  32. package/dist/validate-references.d.ts.map +1 -1
  33. package/dist/validate-references.js +15 -9
  34. package/package.json +3 -3
  35. package/src/analysis-registry.ts +9 -8
  36. package/src/analyzer.ts +333 -91
  37. package/src/builtins.ts +15 -0
  38. package/src/definition-registry.ts +18 -3
  39. package/src/extends-resolution.ts +124 -0
  40. package/src/index.ts +13 -0
  41. package/src/sources/integrity.ts +13 -2
  42. package/src/sources/module-ref.ts +7 -6
  43. package/src/validate-base-mapping.ts +154 -0
  44. package/src/validate-extends.ts +24 -11
  45. package/src/validate-kind-descriptions.ts +65 -0
  46. package/src/validate-provider-coherence.ts +12 -2
  47. package/src/validate-references.ts +13 -9
@@ -0,0 +1,82 @@
1
+ import { mergeTypeSchemas } from "@telorun/sdk";
2
+ const body = (def) => (def ?? {});
3
+ /** The definition a given definition directly `extends`, or undefined when it
4
+ * extends nothing / the target can't be resolved. */
5
+ export function resolveParent(def, resolve) {
6
+ const ext = body(def).extends;
7
+ if (typeof ext !== "string" || ext.length === 0)
8
+ return undefined;
9
+ return resolve(ext);
10
+ }
11
+ /** The `extends` ancestor chain, nearest-first, excluding `def` itself.
12
+ * Cycle-guarded so a malformed self/mutual `extends` can't loop forever. */
13
+ export function ancestorChain(def, resolve) {
14
+ const chain = [];
15
+ const seen = new Set();
16
+ let cur = resolveParent(def, resolve);
17
+ while (cur && !seen.has(cur)) {
18
+ seen.add(cur);
19
+ chain.push(cur);
20
+ cur = resolveParent(cur, resolve);
21
+ }
22
+ return chain;
23
+ }
24
+ /** True when a definition carries its own controller (`controllers:`) or a
25
+ * template body (`invoke:` / `run:` / `provide:` / `mount:` / `resources:`). */
26
+ export function hasOwnControllerOrTemplate(def) {
27
+ const d = body(def);
28
+ return !!((d.controllers && d.controllers.length) ||
29
+ d.invoke ||
30
+ d.run ||
31
+ d.provide ||
32
+ d.mount ||
33
+ d.resources);
34
+ }
35
+ /** The nearest concrete ancestor that provides a controller (own `controllers:`
36
+ * or a template body) — the definition whose controller an inherited child
37
+ * delegates to. Undefined when no controller-bearing concrete ancestor exists. */
38
+ export function controllerBearingAncestor(def, resolve) {
39
+ for (const a of ancestorChain(def, resolve)) {
40
+ if (a.kind === "Telo.Abstract")
41
+ continue;
42
+ if (hasOwnControllerOrTemplate(a))
43
+ return a;
44
+ }
45
+ return undefined;
46
+ }
47
+ /** True when this definition inherits its controller by delegation: it declares
48
+ * `extends`, has no own controller/template body, and its nearest concrete
49
+ * ancestor is controller-bearing. */
50
+ export function isInheritedDelegation(def, resolve) {
51
+ if (!body(def).extends || hasOwnControllerOrTemplate(def))
52
+ return false;
53
+ return controllerBearingAncestor(def, resolve) !== undefined;
54
+ }
55
+ /** The effective (possibly inherited) capability: the nearest self-or-ancestor
56
+ * that declares a `capability`. Undefined when none in the chain does. */
57
+ export function inheritedCapability(def, resolve) {
58
+ if (body(def).capability)
59
+ return body(def).capability;
60
+ for (const a of ancestorChain(def, resolve)) {
61
+ if (body(a).capability)
62
+ return body(a).capability;
63
+ }
64
+ return undefined;
65
+ }
66
+ /** The author-facing schema for a definition:
67
+ * - with `base:` present → the definition's **own** schema (the parent's config
68
+ * fields are internal, set solely through `base:`).
69
+ * - without `base:` but with `extends` → `merge(parent-effective, own)` (a pure
70
+ * additive extension; child overrides on key conflicts), reusing the same
71
+ * `mergeTypeSchemas` that `Type.JsonSchema.extends` uses.
72
+ * - no `extends` → the own schema unchanged. */
73
+ export function effectiveAuthorSchema(def, resolve) {
74
+ const own = (body(def).schema ?? {});
75
+ const parent = resolveParent(def, resolve);
76
+ if (!parent)
77
+ return own;
78
+ if (body(def).base)
79
+ return own;
80
+ const parentSchema = effectiveAuthorSchema(parent, resolve);
81
+ return mergeTypeSchemas([parentSchema, own]);
82
+ }
package/dist/index.d.ts CHANGED
@@ -4,6 +4,10 @@ export { StaticAnalyzer } from "./analyzer.js";
4
4
  export type { GraphLoadError, ImportEdge, LoadedFile, LoadedGraph, LoadedModule, ParseError, } from "./loaded-types.js";
5
5
  export { flattenForAnalyzer, flattenLoadedModule, forwardReExportManifests, parseExportEntry, reExportSpecsFromExports, resolveExportedKinds, selectModuleManifestsForAnalysis, stampReExportedKinds, type ParsedExportEntry, type ReExportSpec, } from "./flatten-for-analyzer.js";
6
6
  export { buildEvalPaths, evalPathCovers } from "./eval-paths.js";
7
+ export { ancestorChain, controllerBearingAncestor, effectiveAuthorSchema, hasOwnControllerOrTemplate, inheritedCapability, isInheritedDelegation, resolveParent, } from "./extends-resolution.js";
8
+ export type { DefResolver } from "./extends-resolution.js";
9
+ export { buildReferenceFieldMap, isRefEntry, isScopeEntry } from "./reference-field-map.js";
10
+ export type { ReferenceFieldMap, RefFieldEntry } from "./reference-field-map.js";
7
11
  export { visitManifest } from "./manifest-visitor.js";
8
12
  export type { CelSiteEvent, ManifestVisitor, RefSiteEvent, ResourceEnterEvent, ResourceExitEvent, ScopeBoundaryEvent, SchemaFromSiteEvent, VisitOptions, } from "./manifest-visitor.js";
9
13
  export { Loader } from "./manifest-loader.js";
@@ -21,7 +25,7 @@ export type { DocumentPosition } from "./position-metadata.js";
21
25
  export { HttpSource } from "./sources/http-source.js";
22
26
  export { RegistrySource } from "./sources/registry-source.js";
23
27
  export { defaultSources } from "./sources/default-sources.js";
24
- export { splitIntegrity, foldIntegrity, verifyIntegrity, verifiedFetch, sha256Base64Url, } from "./sources/integrity.js";
28
+ export { splitIntegrity, foldIntegrity, verifyIntegrity, verifiedFetch, sha256Base64Url, IntegrityError, } from "./sources/integrity.js";
25
29
  export { parseModuleRef, isRegistryRef } from "./sources/module-ref.js";
26
30
  export type { ParsedModuleRef } from "./sources/module-ref.js";
27
31
  export { withSyntheticPositions } from "./with-synthetic-positions.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EACR,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,UAAU,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACH,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,gCAAgC,EAChC,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,YAAY,GACpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,YAAY,EACR,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,GACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,YAAY,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EACH,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EACL,cAAc,EACd,aAAa,EACb,eAAe,EACf,aAAa,EACb,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxE,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC3E,YAAY,EACR,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,QAAQ,EACR,aAAa,EACb,KAAK,EACR,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,6 +2,8 @@ export { AnalysisRegistry } from "./analysis-registry.js";
2
2
  export { StaticAnalyzer } from "./analyzer.js";
3
3
  export { flattenForAnalyzer, flattenLoadedModule, forwardReExportManifests, parseExportEntry, reExportSpecsFromExports, resolveExportedKinds, selectModuleManifestsForAnalysis, stampReExportedKinds, } from "./flatten-for-analyzer.js";
4
4
  export { buildEvalPaths, evalPathCovers } from "./eval-paths.js";
5
+ export { ancestorChain, controllerBearingAncestor, effectiveAuthorSchema, hasOwnControllerOrTemplate, inheritedCapability, isInheritedDelegation, resolveParent, } from "./extends-resolution.js";
6
+ export { buildReferenceFieldMap, isRefEntry, isScopeEntry } from "./reference-field-map.js";
5
7
  export { visitManifest } from "./manifest-visitor.js";
6
8
  export { Loader } from "./manifest-loader.js";
7
9
  export { isModuleKind, MODULE_KINDS } from "./module-kinds.js";
@@ -13,7 +15,7 @@ export { buildDocumentPositions, buildLineOffsets, buildPositionIndex, documentL
13
15
  export { HttpSource } from "./sources/http-source.js";
14
16
  export { RegistrySource } from "./sources/registry-source.js";
15
17
  export { defaultSources } from "./sources/default-sources.js";
16
- export { splitIntegrity, foldIntegrity, verifyIntegrity, verifiedFetch, sha256Base64Url, } from "./sources/integrity.js";
18
+ export { splitIntegrity, foldIntegrity, verifyIntegrity, verifiedFetch, sha256Base64Url, IntegrityError, } from "./sources/integrity.js";
17
19
  export { parseModuleRef, isRegistryRef } from "./sources/module-ref.js";
18
20
  export { withSyntheticPositions } from "./with-synthetic-positions.js";
19
21
  export { DEFAULT_MANIFEST_FILENAME, DiagnosticSeverity } from "./types.js";
@@ -5,6 +5,13 @@
5
5
  * The fragment is authoritative across every transport: a source's `read()`
6
6
  * hashes the fetched bytes and compares against it before the manifest is
7
7
  * parsed or cached. A mismatch is a terminal error — never a cache miss. */
8
+ /** A failed integrity/tamper check — always terminal, never best-effort. A
9
+ * distinct type so a caller doing best-effort network handling (e.g. the
10
+ * bundle extractor warning-and-skipping on a fetch blip) can still let a
11
+ * tamper error propagate rather than swallow it. */
12
+ export declare class IntegrityError extends Error {
13
+ constructor(message: string);
14
+ }
8
15
  /** Split a trailing integrity fragment off a ref/URL. Returns the bare ref in
9
16
  * `base` (safe to build fetch URLs and cache paths from) and the fragment in
10
17
  * `integrity` (e.g. `sha256-<base64url>`), or `undefined` when absent. */
@@ -1 +1 @@
1
- {"version":3,"file":"integrity.d.ts","sourceRoot":"","sources":["../../src/sources/integrity.ts"],"names":[],"mappings":"AAAA;;;;;;6EAM6E;AAO7E;;2EAE2E;AAC3E,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAIhF;AAED;;;kFAGkF;AAClF,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAIxE;AAcD,iFAAiF;AACjF,wBAAsB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAOxE;AAED;;gFAEgF;AAChF,wBAAsB,eAAe,CACnC,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED;;;;sCAIsC;AACtC,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAU9C"}
1
+ {"version":3,"file":"integrity.d.ts","sourceRoot":"","sources":["../../src/sources/integrity.ts"],"names":[],"mappings":"AAAA;;;;;;6EAM6E;AAO7E;;;qDAGqD;AACrD,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI5B;AAED;;2EAE2E;AAC3E,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAIhF;AAED;;;kFAGkF;AAClF,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAIxE;AAcD,iFAAiF;AACjF,wBAAsB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAOxE;AAED;;gFAEgF;AAChF,wBAAsB,eAAe,CACnC,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED;;;;sCAIsC;AACtC,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAU9C"}
@@ -9,6 +9,16 @@
9
9
  * fragments (rare in module refs) pass through untouched. `sha256` is the
10
10
  * only algorithm accepted today; the prefix leaves room to migrate. */
11
11
  const INTEGRITY_FRAGMENT = /#(sha256-[A-Za-z0-9_+/=-]+)$/;
12
+ /** A failed integrity/tamper check — always terminal, never best-effort. A
13
+ * distinct type so a caller doing best-effort network handling (e.g. the
14
+ * bundle extractor warning-and-skipping on a fetch blip) can still let a
15
+ * tamper error propagate rather than swallow it. */
16
+ export class IntegrityError extends Error {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = "IntegrityError";
20
+ }
21
+ }
12
22
  /** Split a trailing integrity fragment off a ref/URL. Returns the bare ref in
13
23
  * `base` (safe to build fetch URLs and cache paths from) and the fragment in
14
24
  * `integrity` (e.g. `sha256-<base64url>`), or `undefined` when absent. */
@@ -55,12 +65,12 @@ export async function verifyIntegrity(bytes, integrity, describe) {
55
65
  const algorithm = dash > 0 ? integrity.slice(0, dash) : "";
56
66
  const expected = dash > 0 ? integrity.slice(dash + 1) : "";
57
67
  if (algorithm !== "sha256") {
58
- throw new Error(`Unsupported integrity algorithm '${algorithm || integrity}' for ${describe}. ` +
68
+ throw new IntegrityError(`Unsupported integrity algorithm '${algorithm || integrity}' for ${describe}. ` +
59
69
  `Only sha256 is supported (sha256-<base64url>).`);
60
70
  }
61
71
  const actual = await sha256Base64Url(bytes);
62
72
  if (actual !== normalizeDigest(expected)) {
63
- throw new Error(`Integrity check failed for ${describe}: expected sha256-${normalizeDigest(expected)}, ` +
73
+ throw new IntegrityError(`Integrity check failed for ${describe}: expected sha256-${normalizeDigest(expected)}, ` +
64
74
  `got sha256-${actual}. The fetched bytes do not match the recorded hash — ` +
65
75
  `the module may have been tampered with or republished.`);
66
76
  }
@@ -7,8 +7,12 @@ export interface ParsedModuleRef {
7
7
  integrity?: string;
8
8
  }
9
9
  /** True when `url` has the bare registry-ref shape `namespace/name@version`
10
- * (no scheme, no leading `/` or `.`, contains both `@` and `/`). The integrity
11
- * fragment, if any, does not affect the classification. */
10
+ * (no scheme of any kind, no leading `/` or `.`, contains both `@` and `/`).
11
+ * The integrity fragment, if any, does not affect the classification.
12
+ *
13
+ * A registry ref never carries a `scheme://` — that guard is what keeps an
14
+ * `oci://…@ver` (or future `s3://…`) ref from being misrouted here, so a
15
+ * scheme-owning transport claims it instead. */
12
16
  export declare function isRegistryRef(url: string): boolean;
13
17
  /** Canonical parser for `namespace/name@version[#sha256-...]` refs. The single
14
18
  * source of truth shared by the registry source, the kernel manifest cache,
@@ -1 +1 @@
1
- {"version":3,"file":"module-ref.d.ts","sourceRoot":"","sources":["../../src/sources/module-ref.ts"],"names":[],"mappings":"AAEA;;yDAEyD;AACzD,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;4DAE4D;AAC5D,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAYlD;AAED;;2EAE2E;AAC3E,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAgB3D"}
1
+ {"version":3,"file":"module-ref.d.ts","sourceRoot":"","sources":["../../src/sources/module-ref.ts"],"names":[],"mappings":"AAEA;;yDAEyD;AACzD,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;iDAMiD;AACjD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CASlD;AAED;;2EAE2E;AAC3E,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAgB3D"}
@@ -1,15 +1,16 @@
1
1
  import { splitIntegrity } from "./integrity.js";
2
2
  /** True when `url` has the bare registry-ref shape `namespace/name@version`
3
- * (no scheme, no leading `/` or `.`, contains both `@` and `/`). The integrity
4
- * fragment, if any, does not affect the classification. */
3
+ * (no scheme of any kind, no leading `/` or `.`, contains both `@` and `/`).
4
+ * The integrity fragment, if any, does not affect the classification.
5
+ *
6
+ * A registry ref never carries a `scheme://` — that guard is what keeps an
7
+ * `oci://…@ver` (or future `s3://…`) ref from being misrouted here, so a
8
+ * scheme-owning transport claims it instead. */
5
9
  export function isRegistryRef(url) {
6
10
  const { base } = splitIntegrity(url);
7
- return (!base.startsWith("http://") &&
8
- !base.startsWith("https://") &&
11
+ return (!base.includes("://") &&
9
12
  !base.startsWith("/") &&
10
13
  !base.startsWith(".") &&
11
- !base.startsWith("file://") &&
12
- !base.startsWith("memory://") &&
13
14
  base.includes("@") &&
14
15
  base.includes("/"));
15
16
  }
@@ -0,0 +1,21 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import type { AliasResolver } from "./alias-resolver.js";
3
+ import type { DefinitionRegistry } from "./definition-registry.js";
4
+ import { type AnalysisDiagnostic } from "./types.js";
5
+ /**
6
+ * Phase 3c — Static validation of a definition's `base:` construction mapping
7
+ * against the parent kind's config schema. The kernel evaluates `base:` and
8
+ * passes the result to the inherited controller's `create()`, which validates it
9
+ * at boot; this mirrors that check statically so an omitted required field or a
10
+ * wrong literal type surfaces at `telo check`, not first boot.
11
+ *
12
+ * Diagnostics:
13
+ * - BASE_MISSING_REQUIRED: `base:` omits a field the parent schema requires.
14
+ * - BASE_UNKNOWN_FIELD: `base:` sets a field the parent schema (with
15
+ * `additionalProperties: false`) does not declare.
16
+ * - BASE_SCHEMA_MISMATCH: a CEL-free `base:` value violates the parent field's
17
+ * schema (wrong type / constraint). CEL-bearing values are skipped — their
18
+ * runtime value is unknown — but still count as present for required checks.
19
+ */
20
+ export declare function validateBaseMapping(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver): AnalysisDiagnostic[];
21
+ //# sourceMappingURL=validate-base-mapping.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-base-mapping.d.ts","sourceRoot":"","sources":["../src/validate-base-mapping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAezE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CAwCtB"}
@@ -0,0 +1,130 @@
1
+ import { isCompiledValue } from "@telorun/sdk";
2
+ import { effectiveAuthorSchema, resolveParent } from "./extends-resolution.js";
3
+ import { DiagnosticSeverity } from "./types.js";
4
+ const SOURCE = "telo-analyzer";
5
+ /** True when a value subtree contains a compiled CEL leaf — such a value can
6
+ * produce anything at runtime, so its type is not statically checkable. */
7
+ function containsCel(value) {
8
+ if (isCompiledValue(value))
9
+ return true;
10
+ if (Array.isArray(value))
11
+ return value.some(containsCel);
12
+ if (value && typeof value === "object") {
13
+ return Object.values(value).some(containsCel);
14
+ }
15
+ return false;
16
+ }
17
+ /**
18
+ * Phase 3c — Static validation of a definition's `base:` construction mapping
19
+ * against the parent kind's config schema. The kernel evaluates `base:` and
20
+ * passes the result to the inherited controller's `create()`, which validates it
21
+ * at boot; this mirrors that check statically so an omitted required field or a
22
+ * wrong literal type surfaces at `telo check`, not first boot.
23
+ *
24
+ * Diagnostics:
25
+ * - BASE_MISSING_REQUIRED: `base:` omits a field the parent schema requires.
26
+ * - BASE_UNKNOWN_FIELD: `base:` sets a field the parent schema (with
27
+ * `additionalProperties: false`) does not declare.
28
+ * - BASE_SCHEMA_MISMATCH: a CEL-free `base:` value violates the parent field's
29
+ * schema (wrong type / constraint). CEL-bearing values are skipped — their
30
+ * runtime value is unknown — but still count as present for required checks.
31
+ */
32
+ export function validateBaseMapping(manifests, registry, aliases) {
33
+ const diagnostics = [];
34
+ const resolveDef = (k) => registry.resolve(aliases.resolveKind(k) ?? k) ?? registry.resolve(k);
35
+ const importedModules = new Set();
36
+ for (const m of manifests) {
37
+ if (m.kind !== "Telo.Import")
38
+ continue;
39
+ const resolved = m.metadata?.resolvedModuleName;
40
+ if (resolved)
41
+ importedModules.add(resolved);
42
+ }
43
+ for (const m of manifests) {
44
+ if (m.kind !== "Telo.Definition")
45
+ continue;
46
+ const base = m.base;
47
+ if (!base || typeof base !== "object" || Array.isArray(base))
48
+ continue;
49
+ const name = m.metadata?.name;
50
+ if (!name)
51
+ continue;
52
+ const ownModule = m.metadata?.module;
53
+ if (ownModule && importedModules.has(ownModule))
54
+ continue;
55
+ const parent = resolveParent(m, resolveDef);
56
+ // Missing / unresolved `extends` is already reported by validateExtends.
57
+ if (!parent)
58
+ continue;
59
+ const parentSchema = effectiveAuthorSchema(parent, resolveDef);
60
+ if (!parentSchema || typeof parentSchema !== "object")
61
+ continue;
62
+ const filePath = m.metadata?.source;
63
+ const resource = { kind: m.kind, name };
64
+ const label = `${m.kind}/${name}`;
65
+ checkObject(base, parentSchema, "base", {
66
+ diagnostics,
67
+ registry,
68
+ label,
69
+ resource,
70
+ filePath,
71
+ });
72
+ }
73
+ return diagnostics;
74
+ }
75
+ function checkObject(value, schema, path, ctx) {
76
+ const properties = (schema.properties ?? {});
77
+ const required = Array.isArray(schema.required) ? schema.required : [];
78
+ const additionalFalse = schema.additionalProperties === false;
79
+ for (const req of required) {
80
+ if (!(req in value)) {
81
+ ctx.diagnostics.push({
82
+ severity: DiagnosticSeverity.Error,
83
+ code: "BASE_MISSING_REQUIRED",
84
+ source: SOURCE,
85
+ message: `${ctx.label}: '${path}' does not set required parent field '${req}'.`,
86
+ data: { resource: ctx.resource, filePath: ctx.filePath, path },
87
+ });
88
+ }
89
+ }
90
+ for (const [key, fieldValue] of Object.entries(value)) {
91
+ const fieldPath = `${path}.${key}`;
92
+ const propSchema = properties[key];
93
+ if (!propSchema) {
94
+ if (additionalFalse) {
95
+ ctx.diagnostics.push({
96
+ severity: DiagnosticSeverity.Error,
97
+ code: "BASE_UNKNOWN_FIELD",
98
+ source: SOURCE,
99
+ message: `${ctx.label}: '${fieldPath}' is not a field of the parent kind's schema.`,
100
+ data: { resource: ctx.resource, filePath: ctx.filePath, path: fieldPath },
101
+ });
102
+ }
103
+ continue;
104
+ }
105
+ // A CEL-bearing value produces its shape at runtime — not statically
106
+ // checkable. Recurse into a partially-CEL nested object so its literal
107
+ // sub-fields still get validated; fully-literal values validate directly.
108
+ if (containsCel(fieldValue)) {
109
+ if (fieldValue &&
110
+ typeof fieldValue === "object" &&
111
+ !Array.isArray(fieldValue) &&
112
+ !isCompiledValue(fieldValue) &&
113
+ propSchema.type === "object" &&
114
+ propSchema.properties) {
115
+ checkObject(fieldValue, propSchema, fieldPath, ctx);
116
+ }
117
+ continue;
118
+ }
119
+ const issues = ctx.registry.validateWithRefs(fieldValue, propSchema);
120
+ for (const issue of issues) {
121
+ ctx.diagnostics.push({
122
+ severity: DiagnosticSeverity.Error,
123
+ code: "BASE_SCHEMA_MISMATCH",
124
+ source: SOURCE,
125
+ message: `${ctx.label}: '${fieldPath}' does not match the parent field's schema: ${issue}`,
126
+ data: { resource: ctx.resource, filePath: ctx.filePath, path: fieldPath },
127
+ });
128
+ }
129
+ }
130
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"validate-extends.d.ts","sourceRoot":"","sources":["../src/validate-extends.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,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;AAOzE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CA4ItB"}
1
+ {"version":3,"file":"validate-extends.d.ts","sourceRoot":"","sources":["../src/validate-extends.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAOzE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CAwJtB"}
@@ -1,3 +1,4 @@
1
+ import { inheritedCapability } from "./extends-resolution.js";
1
2
  import { DiagnosticSeverity } from "./types.js";
2
3
  const SOURCE = "telo-analyzer";
3
4
  /** Alias-form pattern for `extends`: "<Alias>.<AbstractName>", two PascalCase segments. */
@@ -114,15 +115,25 @@ export function validateExtends(manifests, registry, aliases) {
114
115
  data: { resource, filePath, path: "extends" },
115
116
  });
116
117
  }
117
- else if (targetDef.kind !== "Telo.Abstract") {
118
- diagnostics.push({
119
- severity: DiagnosticSeverity.Error,
120
- code: "EXTENDS_NON_ABSTRACT",
121
- source: SOURCE,
122
- message: `${label}: 'extends' target '${extendsValue}' (resolved: '${canonical}') is a ${targetDef.kind}, not a Telo.Abstract. ` +
123
- `Only Telo.Abstract declarations may be extended.`,
124
- data: { resource, filePath, path: "extends" },
125
- });
118
+ else {
119
+ // General single inheritance: any concrete or abstract kind may be
120
+ // extended. What inheritance must NOT do is change the lifecycle
121
+ // role — a child that restates `capability` differently from an
122
+ // ancestor is a hard error (no silent capability change).
123
+ const resolveDef = (k) => registry.resolve(aliases.resolveKind(k) ?? k) ?? registry.resolve(k);
124
+ const ownCap = m.capability;
125
+ const ownCapResolved = typeof ownCap === "string" ? aliases.resolveKind(ownCap) ?? ownCap : undefined;
126
+ const ancestorCap = inheritedCapability(targetDef, resolveDef);
127
+ if (ownCapResolved && ancestorCap && ownCapResolved !== ancestorCap) {
128
+ diagnostics.push({
129
+ severity: DiagnosticSeverity.Error,
130
+ code: "EXTENDS_CAPABILITY_MISMATCH",
131
+ source: SOURCE,
132
+ message: `${label}: declares 'capability: ${ownCap}' but extends '${extendsValue}' whose inherited capability is '${ancestorCap}'. ` +
133
+ `Capability is inherited and immutable — omit 'capability' or restate it identically.`,
134
+ data: { resource, filePath, path: "capability" },
135
+ });
136
+ }
126
137
  }
127
138
  }
128
139
  }
@@ -0,0 +1,20 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import { type AnalysisDiagnostic } from "./types.js";
3
+ /**
4
+ * Warns when a library exports a kind (via `exports.kinds`) whose local
5
+ * `Telo.Definition` carries no `metadata.description`.
6
+ *
7
+ * The description is the primary human text the federated-discovery hub embeds
8
+ * for semantic search (`search_resources`), so an exported kind without one is
9
+ * undiscoverable by meaning. A warning (not an error) so the stdlib backfill is
10
+ * incremental and CI isn't blocked mid-migration.
11
+ *
12
+ * Scope: only kinds a library *exports* and *defines locally* are checked.
13
+ * Re-exported kinds (`exports.kinds: [Alias.Kind]`) belong to their owning
14
+ * module and are skipped; an exported name that isn't a local Telo.Definition
15
+ * (e.g. a Telo.Abstract) is skipped too. The check keys off the root library's
16
+ * own module doc, which is only present when that library is analyzed directly
17
+ * — so importing an under-described library never leaks warnings to its consumer.
18
+ */
19
+ export declare function validateKindDescriptions(manifests: ResourceManifest[]): AnalysisDiagnostic[];
20
+ //# sourceMappingURL=validate-kind-descriptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-kind-descriptions.d.ts","sourceRoot":"","sources":["../src/validate-kind-descriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,gBAAgB,EAAE,GAAG,kBAAkB,EAAE,CA2C5F"}
@@ -0,0 +1,65 @@
1
+ import { DiagnosticSeverity } from "./types.js";
2
+ const SOURCE = "telo-analyzer";
3
+ /**
4
+ * Warns when a library exports a kind (via `exports.kinds`) whose local
5
+ * `Telo.Definition` carries no `metadata.description`.
6
+ *
7
+ * The description is the primary human text the federated-discovery hub embeds
8
+ * for semantic search (`search_resources`), so an exported kind without one is
9
+ * undiscoverable by meaning. A warning (not an error) so the stdlib backfill is
10
+ * incremental and CI isn't blocked mid-migration.
11
+ *
12
+ * Scope: only kinds a library *exports* and *defines locally* are checked.
13
+ * Re-exported kinds (`exports.kinds: [Alias.Kind]`) belong to their owning
14
+ * module and are skipped; an exported name that isn't a local Telo.Definition
15
+ * (e.g. a Telo.Abstract) is skipped too. The check keys off the root library's
16
+ * own module doc, which is only present when that library is analyzed directly
17
+ * — so importing an under-described library never leaks warnings to its consumer.
18
+ */
19
+ export function validateKindDescriptions(manifests) {
20
+ const diagnostics = [];
21
+ // Local Telo.Definition docs keyed by `<module>/<name>`.
22
+ const definitions = new Map();
23
+ for (const m of manifests) {
24
+ if (m.kind !== "Telo.Definition")
25
+ continue;
26
+ const name = m.metadata?.name;
27
+ const mod = m.metadata?.module;
28
+ if (name && mod)
29
+ definitions.set(`${mod}/${name}`, m);
30
+ }
31
+ for (const m of manifests) {
32
+ if (m.kind !== "Telo.Library")
33
+ continue;
34
+ const moduleName = m.metadata?.name;
35
+ if (!moduleName)
36
+ continue;
37
+ const exportedKinds = m.exports?.kinds;
38
+ if (!Array.isArray(exportedKinds))
39
+ continue;
40
+ for (const entry of exportedKinds) {
41
+ // Re-export (`Alias.Kind`) — the owning module owns its description.
42
+ if (typeof entry !== "string" || entry.includes("."))
43
+ continue;
44
+ const def = definitions.get(`${moduleName}/${entry}`);
45
+ if (!def)
46
+ continue; // not a local Telo.Definition (e.g. an abstract)
47
+ const description = def.metadata?.description;
48
+ if (typeof description === "string" && description.trim() !== "")
49
+ continue;
50
+ diagnostics.push({
51
+ severity: DiagnosticSeverity.Warning,
52
+ code: "KIND_MISSING_DESCRIPTION",
53
+ source: SOURCE,
54
+ message: `${moduleName}.${entry}: exported kind has no 'metadata.description'. Add a one-line ` +
55
+ `description — it is the primary text indexed for semantic discovery (search_resources).`,
56
+ data: {
57
+ resource: { kind: "Telo.Definition", name: entry },
58
+ filePath: def.metadata?.source,
59
+ path: "metadata.description",
60
+ },
61
+ });
62
+ }
63
+ }
64
+ return diagnostics;
65
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"validate-provider-coherence.d.ts","sourceRoot":"","sources":["../src/validate-provider-coherence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,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;AAIzE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CAsNtB"}
1
+ {"version":3,"file":"validate-provider-coherence.d.ts","sourceRoot":"","sources":["../src/validate-provider-coherence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CA+NtB"}
@@ -1,3 +1,4 @@
1
+ import { controllerBearingAncestor } from "./extends-resolution.js";
1
2
  import { DiagnosticSeverity } from "./types.js";
2
3
  const SOURCE = "telo-analyzer";
3
4
  /**
@@ -214,7 +215,13 @@ export function validateProviderCoherence(manifests, registry, aliases) {
214
215
  }
215
216
  }
216
217
  }
217
- if (capability === "Telo.Provider" && !hasControllers && !hasProvide) {
218
+ // A definition that inherits a controller by delegation (concrete `extends`,
219
+ // no own controller/template) satisfies the implementation requirement
220
+ // through its parent — `base:` supplies the parent's config.
221
+ const resolveDef = (k) => registry.resolve(aliases.resolveKind(k) ?? k) ?? registry.resolve(k);
222
+ const inheritsController = typeof md.extends === "string" &&
223
+ controllerBearingAncestor(m, resolveDef) !== undefined;
224
+ if (capability === "Telo.Provider" && !hasControllers && !hasProvide && !inheritsController) {
218
225
  diagnostics.push({
219
226
  severity: DiagnosticSeverity.Error,
220
227
  code: "PROVIDER_MISSING_IMPLEMENTATION",
@@ -1 +1 @@
1
- {"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AA4C/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CA6ctB"}
1
+ {"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAgD/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CA6ctB"}
@@ -21,20 +21,26 @@ function checkKind(kind, entry, registry, aliases) {
21
21
  const targetDef = registry.resolve(targetKind);
22
22
  if (!targetDef)
23
23
  return [];
24
+ // Liskov substitutability: a value satisfies the slot when it transitively
25
+ // extends the target kind, or — for a CONCRETE target — IS that kind.
26
+ // `getByExtends` is the same transitive subtype index for abstract and
27
+ // concrete targets alike; an abstract is satisfied only by an implementer,
28
+ // never by the abstract kind itself (which is non-instantiable).
29
+ if (targetDef.kind !== "Telo.Abstract" && resolved === targetKind)
30
+ return [];
31
+ const subtypes = registry.getByExtends(targetKind);
32
+ const subtypeKinds = new Set(subtypes.map((d) => `${d.metadata.module}.${d.metadata.name}`));
33
+ if (subtypeKinds.has(resolved))
34
+ return [];
24
35
  if (targetDef.kind === "Telo.Abstract") {
25
- const implementing = registry.getByExtends(targetKind);
26
- if (implementing.length === 0)
36
+ if (subtypes.length === 0)
27
37
  return []; // partial context — no implementations loaded yet
28
- const implementingKinds = new Set(implementing.map((d) => `${d.metadata.module}.${d.metadata.name}`));
29
- if (implementingKinds.has(resolved))
30
- return [];
31
- const options = [...implementingKinds].join(", ");
38
+ const options = [...subtypeKinds].join(", ");
32
39
  errors.push(`'${kind}' does not implement '${targetKind}' (known implementations: ${options})`);
33
40
  }
34
41
  else {
35
- if (resolved === targetKind)
36
- return [];
37
- errors.push(`'${kind}' (resolved: '${resolved}') does not match required '${targetKind}'`);
42
+ const options = subtypeKinds.size > 0 ? ` or a subtype (${[...subtypeKinds].join(", ")})` : "";
43
+ errors.push(`'${kind}' (resolved: '${resolved}') does not match required '${targetKind}'${options}`);
38
44
  }
39
45
  }
40
46
  return errors;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "description": "Telo Analyzer - Static manifest validator for Telo manifests.",
5
5
  "keywords": [
6
6
  "telo",
@@ -42,13 +42,13 @@
42
42
  "ajv-formats": "^3.0.1",
43
43
  "jsonpath-plus": "^10.3.0",
44
44
  "yaml": "^2.8.3",
45
- "@telorun/templating": "0.10.0"
45
+ "@telorun/templating": "0.10.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^20.0.0",
49
49
  "typescript": "^5.0.0",
50
50
  "vitest": "^2.1.8",
51
- "@telorun/sdk": "0.41.0"
51
+ "@telorun/sdk": "0.44.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@telorun/sdk": "*"