@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.
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +10 -9
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +284 -80
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +15 -0
- package/dist/definition-registry.d.ts +4 -1
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +20 -3
- package/dist/extends-resolution.d.ts +33 -0
- package/dist/extends-resolution.d.ts.map +1 -0
- package/dist/extends-resolution.js +82 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/sources/integrity.d.ts +7 -0
- package/dist/sources/integrity.d.ts.map +1 -1
- package/dist/sources/integrity.js +12 -2
- package/dist/sources/module-ref.d.ts +6 -2
- package/dist/sources/module-ref.d.ts.map +1 -1
- package/dist/sources/module-ref.js +7 -6
- package/dist/validate-base-mapping.d.ts +21 -0
- package/dist/validate-base-mapping.d.ts.map +1 -0
- package/dist/validate-base-mapping.js +130 -0
- package/dist/validate-extends.d.ts.map +1 -1
- package/dist/validate-extends.js +20 -9
- package/dist/validate-kind-descriptions.d.ts +20 -0
- package/dist/validate-kind-descriptions.d.ts.map +1 -0
- package/dist/validate-kind-descriptions.js +65 -0
- package/dist/validate-provider-coherence.d.ts.map +1 -1
- package/dist/validate-provider-coherence.js +8 -1
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +15 -9
- package/package.json +3 -3
- package/src/analysis-registry.ts +9 -8
- package/src/analyzer.ts +333 -91
- package/src/builtins.ts +15 -0
- package/src/definition-registry.ts +18 -3
- package/src/extends-resolution.ts +124 -0
- package/src/index.ts +13 -0
- package/src/sources/integrity.ts +13 -2
- package/src/sources/module-ref.ts +7 -6
- package/src/validate-base-mapping.ts +154 -0
- package/src/validate-extends.ts +24 -11
- package/src/validate-kind-descriptions.ts +65 -0
- package/src/validate-provider-coherence.ts +12 -2
- 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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
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 `/`).
|
|
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
|
|
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 `/`).
|
|
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.
|
|
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,
|
|
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"}
|
package/dist/validate-extends.js
CHANGED
|
@@ -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
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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,
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
26
|
-
if (implementing.length === 0)
|
|
36
|
+
if (subtypes.length === 0)
|
|
27
37
|
return []; // partial context — no implementations loaded yet
|
|
28
|
-
const
|
|
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
|
-
|
|
36
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
51
|
+
"@telorun/sdk": "0.44.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"@telorun/sdk": "*"
|