@telorun/analyzer 0.10.1 → 0.12.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/LICENSE +2 -2
- package/README.md +3 -3
- package/dist/adapters/http-adapter.d.ts +10 -0
- package/dist/adapters/http-adapter.d.ts.map +1 -0
- package/dist/adapters/http-adapter.js +18 -0
- package/dist/adapters/node-adapter.d.ts +17 -0
- package/dist/adapters/node-adapter.d.ts.map +1 -0
- package/dist/adapters/node-adapter.js +71 -0
- package/dist/adapters/registry-adapter.d.ts +15 -0
- package/dist/adapters/registry-adapter.d.ts.map +1 -0
- package/dist/adapters/registry-adapter.js +53 -0
- package/dist/analysis-registry.d.ts +7 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +38 -0
- package/dist/analyzer.d.ts +15 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +268 -7
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +172 -1
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +16 -0
- package/dist/dependency-graph.d.ts.map +1 -1
- package/dist/dependency-graph.js +27 -13
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/kernel-globals.d.ts.map +1 -1
- package/dist/kernel-globals.js +9 -11
- package/dist/manifest-loader.d.ts +23 -1
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +66 -3
- package/dist/normalize-inline-resources.d.ts.map +1 -1
- package/dist/normalize-inline-resources.js +26 -14
- package/dist/position-metadata.d.ts +11 -2
- package/dist/position-metadata.d.ts.map +1 -1
- package/dist/position-metadata.js +18 -3
- package/dist/precompile.d.ts.map +1 -1
- package/dist/precompile.js +9 -1
- package/dist/reference-field-map.d.ts +21 -4
- package/dist/reference-field-map.d.ts.map +1 -1
- package/dist/reference-field-map.js +93 -25
- package/dist/residual-schema.d.ts +23 -0
- package/dist/residual-schema.d.ts.map +1 -0
- package/dist/residual-schema.js +45 -0
- package/dist/resolve-ref-sentinels.d.ts +27 -0
- package/dist/resolve-ref-sentinels.d.ts.map +1 -0
- package/dist/resolve-ref-sentinels.js +114 -0
- package/dist/rewrite-synthetic-origins.d.ts +10 -0
- package/dist/rewrite-synthetic-origins.d.ts.map +1 -0
- package/dist/rewrite-synthetic-origins.js +55 -0
- package/dist/schema-compat.d.ts +7 -1
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +19 -2
- package/dist/system-kinds.d.ts +25 -0
- package/dist/system-kinds.d.ts.map +1 -0
- package/dist/system-kinds.js +34 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validate-cel-context.d.ts +61 -7
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +90 -8
- package/dist/validate-provider-coherence.d.ts +23 -0
- package/dist/validate-provider-coherence.d.ts.map +1 -0
- package/dist/validate-provider-coherence.js +148 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +141 -36
- package/dist/with-synthetic-positions.d.ts +28 -0
- package/dist/with-synthetic-positions.d.ts.map +1 -0
- package/dist/with-synthetic-positions.js +45 -0
- package/package.json +7 -4
- package/src/analysis-registry.ts +37 -0
- package/src/analyzer.ts +313 -9
- package/src/builtins.ts +172 -1
- package/src/definition-registry.ts +15 -0
- package/src/dependency-graph.ts +27 -14
- package/src/index.ts +2 -0
- package/src/kernel-globals.ts +9 -11
- package/src/manifest-loader.ts +69 -4
- package/src/normalize-inline-resources.ts +48 -13
- package/src/position-metadata.ts +18 -3
- package/src/precompile.ts +8 -1
- package/src/reference-field-map.ts +129 -24
- package/src/residual-schema.ts +49 -0
- package/src/resolve-ref-sentinels.ts +127 -0
- package/src/rewrite-synthetic-origins.ts +75 -0
- package/src/schema-compat.ts +19 -2
- package/src/system-kinds.ts +37 -0
- package/src/types.ts +12 -0
- package/src/validate-cel-context.ts +111 -8
- package/src/validate-provider-coherence.ts +166 -0
- package/src/validate-references.ts +138 -35
- package/src/with-synthetic-positions.ts +48 -0
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
export { extractAccessChains, validateChainAgainstSchema } from "@telorun/templating";
|
|
2
|
+
export interface ContextResolveOpts {
|
|
3
|
+
/** When provided, used to resolve `x-telo-context-from-root` annotations against the
|
|
4
|
+
* root manifest. When omitted, defaults to `manifestItem`. */
|
|
5
|
+
manifestRoot?: Record<string, any>;
|
|
6
|
+
/** When provided alongside `aliases`, used to resolve `x-telo-context-from-ref-kind`
|
|
7
|
+
* annotations: read a kind name from a path on `manifestRoot` and return the
|
|
8
|
+
* declared definition's `<field>` schema. */
|
|
9
|
+
defs?: {
|
|
10
|
+
resolve(kind: string): Record<string, any> | undefined;
|
|
11
|
+
};
|
|
12
|
+
aliases?: {
|
|
13
|
+
resolveKind(kind: string): string | undefined;
|
|
14
|
+
};
|
|
15
|
+
allManifests?: Record<string, any>[];
|
|
16
|
+
}
|
|
2
17
|
/**
|
|
3
18
|
* Resolve a type field value (string name, inline type, or raw schema) to a JSON Schema.
|
|
4
19
|
* - String: look up the named type in allManifests (Type.JsonSchema resources)
|
|
@@ -15,15 +30,54 @@ export declare function resolveTypeFieldToSchema(value: unknown, allManifests: R
|
|
|
15
30
|
*/
|
|
16
31
|
export declare function pathMatchesScope(exprPath: string, scope: string): boolean;
|
|
17
32
|
/**
|
|
18
|
-
* Resolves `x-telo-context
|
|
19
|
-
* manifest item
|
|
20
|
-
* the result as named properties into the annotated node (locking additionalProperties: false).
|
|
33
|
+
* Resolves `x-telo-context-*` annotations in a context schema using the concrete
|
|
34
|
+
* manifest item (per-scope) and the manifest root.
|
|
21
35
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* `
|
|
36
|
+
* Annotation forms:
|
|
37
|
+
*
|
|
38
|
+
* - `x-telo-context-from`: navigates `manifestItem.<path>` and treats the resolved
|
|
39
|
+
* value as a **property map** (keys → sub-schemas) that is merged into the
|
|
40
|
+
* annotated node's properties. Used for HTTP-style scopes where the navigated
|
|
41
|
+
* value is itself a map of variable names.
|
|
42
|
+
*
|
|
43
|
+
* Example: `x-telo-context-from: "request/schema"` reads `manifestItem.request.schema`
|
|
44
|
+
* (= `{ query: {...}, body: {...}, … }`) and merges those keys as named properties
|
|
45
|
+
* of the context node.
|
|
46
|
+
*
|
|
47
|
+
* - `x-telo-context-from-root`: navigates `manifestRoot.<path>` and **replaces** the
|
|
48
|
+
* annotated node's schema with the resolved value. Used on individual property
|
|
49
|
+
* schemas (e.g. `properties.self`) where the resolved value is a single variable's
|
|
50
|
+
* full schema, not a property map.
|
|
51
|
+
*
|
|
52
|
+
* Example: `properties.self.x-telo-context-from-root: "schema"` reads
|
|
53
|
+
* `manifestRoot.schema` and uses it as the schema of the `self` CEL variable.
|
|
54
|
+
*
|
|
55
|
+
* - `x-telo-context-from-ref-kind`: reads a kind name from `manifestRoot.<refPath>`,
|
|
56
|
+
* resolves it via the definition registry, and returns that kind's `<field>` schema
|
|
57
|
+
* (e.g. `outputType`/`inputType`). Used to type `result` against the dispatch
|
|
58
|
+
* target's declared output shape.
|
|
59
|
+
*
|
|
60
|
+
* Syntax: `<refPath>#<field>` — slashes traverse the manifest tree.
|
|
61
|
+
*
|
|
62
|
+
* Example: `x-telo-context-from-ref-kind: "provide/kind#outputType"` reads
|
|
63
|
+
* `manifestRoot.provide.kind` as a kind name, looks up the kind's Telo.Definition,
|
|
64
|
+
* and returns the `outputType` schema.
|
|
65
|
+
*
|
|
66
|
+
* Accepts either a single string or an array of strings. With an array, paths
|
|
67
|
+
* are tried in order and the first one that resolves to a usable schema wins —
|
|
68
|
+
* used by `result:` to find its dispatch target under whichever entry-point
|
|
69
|
+
* field (`provide:` or `invoke:`) the definition declares.
|
|
70
|
+
*
|
|
71
|
+
* - `x-telo-context-ref-from`: existing form — reads `{kind, name}` object from
|
|
72
|
+
* `manifestItem.<path>`, looks up the named manifest, returns its `<subpath>` field.
|
|
73
|
+
*
|
|
74
|
+
* **Fallback chain.** When both `x-telo-context-from-root` and
|
|
75
|
+
* `x-telo-context-from-ref-kind` are present on the same node, the resolver tries
|
|
76
|
+
* `from-root` first; if that produces no usable schema, it falls back to `from-ref-kind`.
|
|
77
|
+
* This lets a definition declare typing from its own field with a sibling-kind fallback
|
|
78
|
+
* (e.g. `inputType` direct → `extends`-declared abstract's `inputType`).
|
|
25
79
|
*/
|
|
26
|
-
export declare function resolveContextAnnotations(schema: Record<string, any>, manifestItem: Record<string, any>,
|
|
80
|
+
export declare function resolveContextAnnotations(schema: Record<string, any>, manifestItem: Record<string, any>, opts?: ContextResolveOpts | Record<string, any>[]): Record<string, any>;
|
|
27
81
|
/**
|
|
28
82
|
* Extracts the concrete manifest array item for a given expression path + scope.
|
|
29
83
|
* e.g. exprPath="routes[0].inputs.q", scope="$.routes[*].inputs" → manifest.routes[0]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-cel-context.d.ts","sourceRoot":"","sources":["../src/validate-cel-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEtF;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAClC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA6BjC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAoBzE;AAED
|
|
1
|
+
{"version":3,"file":"validate-cel-context.d.ts","sourceRoot":"","sources":["../src/validate-cel-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEtF,MAAM,WAAW,kBAAkB;IACjC;mEAC+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC;;kDAE8C;IAC9C,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;KACxD,CAAC;IACF,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;KAC/C,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAClC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA6BjC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAoBzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,IAAI,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAChD,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAqGrB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAQrB"}
|
|
@@ -61,17 +61,61 @@ export function pathMatchesScope(exprPath, scope) {
|
|
|
61
61
|
return remaining === "" || remaining[0] === "." || remaining[0] === "[";
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
|
-
* Resolves `x-telo-context
|
|
65
|
-
* manifest item
|
|
66
|
-
* the result as named properties into the annotated node (locking additionalProperties: false).
|
|
64
|
+
* Resolves `x-telo-context-*` annotations in a context schema using the concrete
|
|
65
|
+
* manifest item (per-scope) and the manifest root.
|
|
67
66
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* `
|
|
67
|
+
* Annotation forms:
|
|
68
|
+
*
|
|
69
|
+
* - `x-telo-context-from`: navigates `manifestItem.<path>` and treats the resolved
|
|
70
|
+
* value as a **property map** (keys → sub-schemas) that is merged into the
|
|
71
|
+
* annotated node's properties. Used for HTTP-style scopes where the navigated
|
|
72
|
+
* value is itself a map of variable names.
|
|
73
|
+
*
|
|
74
|
+
* Example: `x-telo-context-from: "request/schema"` reads `manifestItem.request.schema`
|
|
75
|
+
* (= `{ query: {...}, body: {...}, … }`) and merges those keys as named properties
|
|
76
|
+
* of the context node.
|
|
77
|
+
*
|
|
78
|
+
* - `x-telo-context-from-root`: navigates `manifestRoot.<path>` and **replaces** the
|
|
79
|
+
* annotated node's schema with the resolved value. Used on individual property
|
|
80
|
+
* schemas (e.g. `properties.self`) where the resolved value is a single variable's
|
|
81
|
+
* full schema, not a property map.
|
|
82
|
+
*
|
|
83
|
+
* Example: `properties.self.x-telo-context-from-root: "schema"` reads
|
|
84
|
+
* `manifestRoot.schema` and uses it as the schema of the `self` CEL variable.
|
|
85
|
+
*
|
|
86
|
+
* - `x-telo-context-from-ref-kind`: reads a kind name from `manifestRoot.<refPath>`,
|
|
87
|
+
* resolves it via the definition registry, and returns that kind's `<field>` schema
|
|
88
|
+
* (e.g. `outputType`/`inputType`). Used to type `result` against the dispatch
|
|
89
|
+
* target's declared output shape.
|
|
90
|
+
*
|
|
91
|
+
* Syntax: `<refPath>#<field>` — slashes traverse the manifest tree.
|
|
92
|
+
*
|
|
93
|
+
* Example: `x-telo-context-from-ref-kind: "provide/kind#outputType"` reads
|
|
94
|
+
* `manifestRoot.provide.kind` as a kind name, looks up the kind's Telo.Definition,
|
|
95
|
+
* and returns the `outputType` schema.
|
|
96
|
+
*
|
|
97
|
+
* Accepts either a single string or an array of strings. With an array, paths
|
|
98
|
+
* are tried in order and the first one that resolves to a usable schema wins —
|
|
99
|
+
* used by `result:` to find its dispatch target under whichever entry-point
|
|
100
|
+
* field (`provide:` or `invoke:`) the definition declares.
|
|
101
|
+
*
|
|
102
|
+
* - `x-telo-context-ref-from`: existing form — reads `{kind, name}` object from
|
|
103
|
+
* `manifestItem.<path>`, looks up the named manifest, returns its `<subpath>` field.
|
|
104
|
+
*
|
|
105
|
+
* **Fallback chain.** When both `x-telo-context-from-root` and
|
|
106
|
+
* `x-telo-context-from-ref-kind` are present on the same node, the resolver tries
|
|
107
|
+
* `from-root` first; if that produces no usable schema, it falls back to `from-ref-kind`.
|
|
108
|
+
* This lets a definition declare typing from its own field with a sibling-kind fallback
|
|
109
|
+
* (e.g. `inputType` direct → `extends`-declared abstract's `inputType`).
|
|
71
110
|
*/
|
|
72
|
-
export function resolveContextAnnotations(schema, manifestItem,
|
|
111
|
+
export function resolveContextAnnotations(schema, manifestItem, opts) {
|
|
73
112
|
if (!schema || typeof schema !== "object")
|
|
74
113
|
return schema;
|
|
114
|
+
// Back-compat: third positional arg used to be `allManifests: Record<string, any>[]`.
|
|
115
|
+
const normalizedOpts = Array.isArray(opts)
|
|
116
|
+
? { allManifests: opts }
|
|
117
|
+
: (opts ?? {});
|
|
118
|
+
const { manifestRoot = manifestItem, defs, aliases, allManifests } = normalizedOpts;
|
|
75
119
|
const from = schema["x-telo-context-from"];
|
|
76
120
|
if (from) {
|
|
77
121
|
const resolved = navigatePath(manifestItem, from.split("/"));
|
|
@@ -82,6 +126,44 @@ export function resolveContextAnnotations(schema, manifestItem, allManifests) {
|
|
|
82
126
|
additionalProperties: false,
|
|
83
127
|
};
|
|
84
128
|
}
|
|
129
|
+
const fromRoot = schema["x-telo-context-from-root"];
|
|
130
|
+
const fromRefKindRaw = schema["x-telo-context-from-ref-kind"];
|
|
131
|
+
const fromRefKinds = fromRefKindRaw == null
|
|
132
|
+
? []
|
|
133
|
+
: Array.isArray(fromRefKindRaw)
|
|
134
|
+
? fromRefKindRaw
|
|
135
|
+
: [fromRefKindRaw];
|
|
136
|
+
if (fromRoot || fromRefKinds.length > 0) {
|
|
137
|
+
if (fromRoot) {
|
|
138
|
+
const resolved = navigatePath(manifestRoot, fromRoot.split("/"));
|
|
139
|
+
if (resolved && typeof resolved === "object" && !Array.isArray(resolved)) {
|
|
140
|
+
return resolved;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (defs) {
|
|
144
|
+
for (const fromRefKind of fromRefKinds) {
|
|
145
|
+
const hashIdx = fromRefKind.indexOf("#");
|
|
146
|
+
if (hashIdx <= 0)
|
|
147
|
+
continue;
|
|
148
|
+
const refPath = fromRefKind.slice(0, hashIdx);
|
|
149
|
+
const field = fromRefKind.slice(hashIdx + 1);
|
|
150
|
+
const kindValue = navigatePath(manifestRoot, refPath.split("/"));
|
|
151
|
+
if (typeof kindValue !== "string" || kindValue.length === 0)
|
|
152
|
+
continue;
|
|
153
|
+
const canonical = aliases?.resolveKind(kindValue) ?? kindValue;
|
|
154
|
+
const def = defs.resolve(canonical);
|
|
155
|
+
const typeField = def
|
|
156
|
+
? def[field]
|
|
157
|
+
: undefined;
|
|
158
|
+
const resolved = resolveTypeFieldToSchema(typeField, allManifests ?? []);
|
|
159
|
+
if (resolved && typeof resolved === "object") {
|
|
160
|
+
return resolved;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Open fallback so unresolved types never produce false-positive CEL diagnostics.
|
|
165
|
+
return { type: "object", additionalProperties: true };
|
|
166
|
+
}
|
|
85
167
|
const refFrom = schema["x-telo-context-ref-from"];
|
|
86
168
|
if (refFrom && allManifests) {
|
|
87
169
|
const slashIdx = refFrom.indexOf("/");
|
|
@@ -107,7 +189,7 @@ export function resolveContextAnnotations(schema, manifestItem, allManifests) {
|
|
|
107
189
|
if (schema.properties) {
|
|
108
190
|
const props = {};
|
|
109
191
|
for (const [k, v] of Object.entries(schema.properties)) {
|
|
110
|
-
props[k] = resolveContextAnnotations(v, manifestItem,
|
|
192
|
+
props[k] = resolveContextAnnotations(v, manifestItem, normalizedOpts);
|
|
111
193
|
}
|
|
112
194
|
return { ...schema, properties: props };
|
|
113
195
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
* Validates coherence rules for `Telo.Definition` documents that use the `provide:`
|
|
7
|
+
* template target, plus the implementation-presence rule on `Telo.Provider`
|
|
8
|
+
* definitions.
|
|
9
|
+
*
|
|
10
|
+
* Diagnostics:
|
|
11
|
+
* - PROVIDE_ON_NON_PROVIDER: `provide:` declared on a definition whose
|
|
12
|
+
* `capability` is not `Telo.Provider`.
|
|
13
|
+
* - PROVIDE_DISPATCHER_CONFLICT: `provide:` co-exists with `invoke:` or `run:`
|
|
14
|
+
* on the same definition.
|
|
15
|
+
* - PROVIDE_TARGET_UNKNOWN: `provide.name` does not resolve to an entry in
|
|
16
|
+
* `resources:`.
|
|
17
|
+
* - PROVIDE_TARGET_NOT_INVOCABLE: `provide.name` resolves to a resource whose
|
|
18
|
+
* kind is registered but not a `Telo.Invocable`.
|
|
19
|
+
* - PROVIDER_MISSING_IMPLEMENTATION: definition with `capability: Telo.Provider`
|
|
20
|
+
* declares neither `controllers:` (TS-backed) nor `provide:` (template-backed).
|
|
21
|
+
*/
|
|
22
|
+
export declare function validateProviderCoherence(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver): AnalysisDiagnostic[];
|
|
23
|
+
//# sourceMappingURL=validate-provider-coherence.d.ts.map
|
|
@@ -0,0 +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;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CAyItB"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { DiagnosticSeverity } from "./types.js";
|
|
2
|
+
const SOURCE = "telo-analyzer";
|
|
3
|
+
/**
|
|
4
|
+
* Validates coherence rules for `Telo.Definition` documents that use the `provide:`
|
|
5
|
+
* template target, plus the implementation-presence rule on `Telo.Provider`
|
|
6
|
+
* definitions.
|
|
7
|
+
*
|
|
8
|
+
* Diagnostics:
|
|
9
|
+
* - PROVIDE_ON_NON_PROVIDER: `provide:` declared on a definition whose
|
|
10
|
+
* `capability` is not `Telo.Provider`.
|
|
11
|
+
* - PROVIDE_DISPATCHER_CONFLICT: `provide:` co-exists with `invoke:` or `run:`
|
|
12
|
+
* on the same definition.
|
|
13
|
+
* - PROVIDE_TARGET_UNKNOWN: `provide.name` does not resolve to an entry in
|
|
14
|
+
* `resources:`.
|
|
15
|
+
* - PROVIDE_TARGET_NOT_INVOCABLE: `provide.name` resolves to a resource whose
|
|
16
|
+
* kind is registered but not a `Telo.Invocable`.
|
|
17
|
+
* - PROVIDER_MISSING_IMPLEMENTATION: definition with `capability: Telo.Provider`
|
|
18
|
+
* declares neither `controllers:` (TS-backed) nor `provide:` (template-backed).
|
|
19
|
+
*/
|
|
20
|
+
export function validateProviderCoherence(manifests, registry, aliases) {
|
|
21
|
+
const diagnostics = [];
|
|
22
|
+
const importedModules = new Set();
|
|
23
|
+
for (const m of manifests) {
|
|
24
|
+
if (m.kind !== "Telo.Import")
|
|
25
|
+
continue;
|
|
26
|
+
const resolved = m.metadata
|
|
27
|
+
?.resolvedModuleName;
|
|
28
|
+
if (resolved)
|
|
29
|
+
importedModules.add(resolved);
|
|
30
|
+
}
|
|
31
|
+
for (const m of manifests) {
|
|
32
|
+
if (m.kind !== "Telo.Definition")
|
|
33
|
+
continue;
|
|
34
|
+
const name = m.metadata?.name;
|
|
35
|
+
if (!name)
|
|
36
|
+
continue;
|
|
37
|
+
const ownModule = m.metadata?.module;
|
|
38
|
+
if (ownModule && importedModules.has(ownModule))
|
|
39
|
+
continue;
|
|
40
|
+
const filePath = m.metadata?.source;
|
|
41
|
+
const resource = { kind: m.kind, name };
|
|
42
|
+
const label = `${m.kind}/${name}`;
|
|
43
|
+
const md = m;
|
|
44
|
+
const capability = typeof md.capability === "string" ? md.capability : undefined;
|
|
45
|
+
const provide = md.provide;
|
|
46
|
+
const invoke = md.invoke;
|
|
47
|
+
const run = md.run;
|
|
48
|
+
const controllers = md.controllers;
|
|
49
|
+
const resources = md.resources;
|
|
50
|
+
const hasProvide = provide !== undefined && provide !== null;
|
|
51
|
+
const hasInvoke = invoke !== undefined && invoke !== null;
|
|
52
|
+
const hasRun = run !== undefined && run !== null;
|
|
53
|
+
const hasControllers = Array.isArray(controllers) && controllers.length > 0;
|
|
54
|
+
if (hasProvide && capability !== "Telo.Provider") {
|
|
55
|
+
diagnostics.push({
|
|
56
|
+
severity: DiagnosticSeverity.Error,
|
|
57
|
+
code: "PROVIDE_ON_NON_PROVIDER",
|
|
58
|
+
source: SOURCE,
|
|
59
|
+
message: `${label}: 'provide:' is only valid on definitions with 'capability: Telo.Provider' ` +
|
|
60
|
+
`(found '${capability ?? "<unset>"}'). Use 'invoke:' or 'run:' for other capabilities.`,
|
|
61
|
+
data: { resource, filePath, path: "provide" },
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (hasProvide && (hasInvoke || hasRun)) {
|
|
65
|
+
const conflict = hasInvoke ? "invoke" : "run";
|
|
66
|
+
diagnostics.push({
|
|
67
|
+
severity: DiagnosticSeverity.Error,
|
|
68
|
+
code: "PROVIDE_DISPATCHER_CONFLICT",
|
|
69
|
+
source: SOURCE,
|
|
70
|
+
message: `${label}: 'provide:' cannot co-exist with '${conflict}:'. ` +
|
|
71
|
+
`A definition declares exactly one dispatch entry-point.`,
|
|
72
|
+
data: { resource, filePath, path: "provide" },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (hasProvide && typeof provide === "object" && !Array.isArray(provide)) {
|
|
76
|
+
const provideObj = provide;
|
|
77
|
+
const providedName = typeof provideObj.name === "string" ? provideObj.name : undefined;
|
|
78
|
+
const providedKind = typeof provideObj.kind === "string" ? provideObj.kind : undefined;
|
|
79
|
+
if (providedName && Array.isArray(resources)) {
|
|
80
|
+
const match = resources.find((r) => {
|
|
81
|
+
const meta = r?.metadata;
|
|
82
|
+
return typeof meta?.name === "string" && meta.name === providedName;
|
|
83
|
+
});
|
|
84
|
+
if (!match) {
|
|
85
|
+
diagnostics.push({
|
|
86
|
+
severity: DiagnosticSeverity.Error,
|
|
87
|
+
code: "PROVIDE_TARGET_UNKNOWN",
|
|
88
|
+
source: SOURCE,
|
|
89
|
+
message: `${label}: 'provide.name: ${providedName}' does not match any entry's ` +
|
|
90
|
+
`metadata.name in 'resources:'.`,
|
|
91
|
+
data: { resource, filePath, path: "provide.name" },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
else if (typeof match.kind === "string") {
|
|
95
|
+
// `provide.kind` is the type contract the analyzer uses to type
|
|
96
|
+
// `result` CEL against the target's `outputType`. The runtime
|
|
97
|
+
// dispatches on `provide.name` and ignores `provide.kind`, so a
|
|
98
|
+
// mismatch silently degrades `result` typing to an open schema
|
|
99
|
+
// (and at runtime quietly invokes the actually-matched resource).
|
|
100
|
+
// Flag the divergence so result-typing never lies.
|
|
101
|
+
if (providedKind) {
|
|
102
|
+
const providedCanonical = aliases.resolveKind(providedKind) ?? providedKind;
|
|
103
|
+
const matchCanonical = aliases.resolveKind(match.kind) ?? match.kind;
|
|
104
|
+
if (providedCanonical !== matchCanonical) {
|
|
105
|
+
diagnostics.push({
|
|
106
|
+
severity: DiagnosticSeverity.Error,
|
|
107
|
+
code: "PROVIDE_KIND_MISMATCH",
|
|
108
|
+
source: SOURCE,
|
|
109
|
+
message: `${label}: 'provide.kind: ${providedKind}' disagrees with the matched ` +
|
|
110
|
+
`'resources:' entry's kind '${match.kind}' (matched by metadata.name ` +
|
|
111
|
+
`'${providedName}'). The runtime dispatches by name, so 'provide.kind' ` +
|
|
112
|
+
`is decorative — but the analyzer types 'result:' against it, and a ` +
|
|
113
|
+
`mismatch silently turns off that typing.`,
|
|
114
|
+
data: { resource, filePath, path: "provide.kind" },
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const resolvedKind = aliases.resolveKind(match.kind) ?? match.kind;
|
|
119
|
+
const targetDef = registry.resolve(resolvedKind) ?? registry.resolve(match.kind);
|
|
120
|
+
if (targetDef && targetDef.kind === "Telo.Definition") {
|
|
121
|
+
const targetCap = targetDef.capability;
|
|
122
|
+
if (typeof targetCap === "string" && targetCap !== "Telo.Invocable") {
|
|
123
|
+
diagnostics.push({
|
|
124
|
+
severity: DiagnosticSeverity.Error,
|
|
125
|
+
code: "PROVIDE_TARGET_NOT_INVOCABLE",
|
|
126
|
+
source: SOURCE,
|
|
127
|
+
message: `${label}: 'provide.name: ${providedName}' resolves to a ${match.kind} ` +
|
|
128
|
+
`(capability '${targetCap}'); 'provide:' requires a Telo.Invocable target.`,
|
|
129
|
+
data: { resource, filePath, path: "provide.name" },
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (capability === "Telo.Provider" && !hasControllers && !hasProvide) {
|
|
137
|
+
diagnostics.push({
|
|
138
|
+
severity: DiagnosticSeverity.Error,
|
|
139
|
+
code: "PROVIDER_MISSING_IMPLEMENTATION",
|
|
140
|
+
source: SOURCE,
|
|
141
|
+
message: `${label}: 'capability: Telo.Provider' requires either 'controllers:' ` +
|
|
142
|
+
`(TS-backed) or 'provide:' (template-backed) to declare an implementation.`,
|
|
143
|
+
data: { resource, filePath, path: "capability" },
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return diagnostics;
|
|
148
|
+
}
|
|
@@ -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;
|
|
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;AAKrD,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,CAsbtB"}
|