@telorun/analyzer 0.63.0 → 0.64.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 +24 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +35 -0
- package/dist/analyzer.d.ts +3 -37
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +57 -467
- package/dist/cel-scope-query.d.ts +109 -0
- package/dist/cel-scope-query.d.ts.map +1 -0
- package/dist/cel-scope-query.js +270 -0
- package/dist/cel-scope.d.ts +166 -0
- package/dist/cel-scope.d.ts.map +1 -0
- package/dist/cel-scope.js +377 -0
- package/dist/definition-registry.d.ts +15 -0
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +25 -9
- package/dist/find-manifest.d.ts +10 -0
- package/dist/find-manifest.d.ts.map +1 -0
- package/dist/find-manifest.js +12 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/invocation-contract.d.ts +11 -0
- package/dist/invocation-contract.d.ts.map +1 -1
- package/dist/invocation-contract.js +15 -0
- package/dist/manifest-analysis.d.ts +73 -0
- package/dist/manifest-analysis.d.ts.map +1 -0
- package/dist/manifest-analysis.js +78 -0
- package/dist/manifest-path.d.ts +18 -0
- package/dist/manifest-path.d.ts.map +1 -0
- package/dist/manifest-path.js +37 -0
- package/dist/schema-walk.d.ts +25 -0
- package/dist/schema-walk.d.ts.map +1 -0
- package/dist/schema-walk.js +126 -0
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/validate-step-inputs.d.ts +17 -0
- package/dist/validate-step-inputs.d.ts.map +1 -1
- package/dist/validate-step-inputs.js +97 -6
- package/package.json +2 -2
- package/src/analysis-registry.ts +37 -0
- package/src/analyzer.ts +61 -585
- package/src/cel-scope-query.ts +337 -0
- package/src/cel-scope.ts +570 -0
- package/src/definition-registry.ts +31 -13
- package/src/find-manifest.ts +19 -0
- package/src/index.ts +13 -0
- package/src/invocation-contract.ts +22 -0
- package/src/manifest-analysis.ts +132 -0
- package/src/manifest-path.ts +34 -0
- package/src/schema-walk.ts +144 -0
- package/src/telo-version.ts +1 -1
- package/src/validate-step-inputs.ts +143 -7
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **One analyzed manifest set, and the questions asked of it.**
|
|
3
|
+
*
|
|
4
|
+
* Several answers a host needs require the SAME two things: the registry's
|
|
5
|
+
* definitions and aliases, and the manifest set they were resolved against. The
|
|
6
|
+
* registry deliberately holds no manifests — it is populated per analysis and
|
|
7
|
+
* reused across them — so each such answer would otherwise become another
|
|
8
|
+
* factory on the registry and another optional parameter on every IDE entry
|
|
9
|
+
* point. Four of those arrived in short order (CEL scope, step declarations,
|
|
10
|
+
* context-binding declarations, invocation contracts) and the next one is not
|
|
11
|
+
* hypothetical.
|
|
12
|
+
*
|
|
13
|
+
* So the pairing is named once and the questions hang off it. A host threads ONE
|
|
14
|
+
* object and gains later questions for free; each facet keeps its own honest
|
|
15
|
+
* name rather than accreting onto whichever one happened to exist first.
|
|
16
|
+
*
|
|
17
|
+
* Nothing here re-implements an answer. `contractFor` is the shared
|
|
18
|
+
* {@link resolveContract} — the one `telo check` runs and the kernel binds at
|
|
19
|
+
* dispatch — given the scope to run in; `celScope` is the same
|
|
20
|
+
* {@link CelScopeQuery} the analysis pass's rule is built from. That is the
|
|
21
|
+
* whole point: a completion list is a claim about what the checker accepts, and
|
|
22
|
+
* a second implementation of any of these could not be held to it.
|
|
23
|
+
*/
|
|
24
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
25
|
+
import { AliasResolver } from "./alias-resolver.js";
|
|
26
|
+
import { CelScopeQuery, type CelScopeQueryContext } from "./cel-scope-query.js";
|
|
27
|
+
import { DefinitionRegistry } from "./definition-registry.js";
|
|
28
|
+
import type { ContractDirection } from "./extends-resolution.js";
|
|
29
|
+
/**
|
|
30
|
+
* A reference as the loader leaves it — the internal `{kind, name, alias?}`
|
|
31
|
+
* shape `resolveRefSentinels` rewrites `!ref` into.
|
|
32
|
+
*/
|
|
33
|
+
export interface ManifestRef {
|
|
34
|
+
kind?: string;
|
|
35
|
+
name?: string;
|
|
36
|
+
alias?: string;
|
|
37
|
+
}
|
|
38
|
+
export declare class ManifestAnalysis {
|
|
39
|
+
readonly manifests: ResourceManifest[];
|
|
40
|
+
private readonly ctx;
|
|
41
|
+
private readonly scopes;
|
|
42
|
+
private celScopeQuery;
|
|
43
|
+
constructor(manifests: ResourceManifest[], ctx: CelScopeQueryContext);
|
|
44
|
+
/** What CEL sees, per site. Built on first use — its indices are a function of
|
|
45
|
+
* the whole set, and a host that never opens a CEL body should not pay for
|
|
46
|
+
* them. */
|
|
47
|
+
get celScope(): CelScopeQuery;
|
|
48
|
+
/** The manifest a `(kind, name)` pair addresses. */
|
|
49
|
+
resourceFor(kind: string | undefined, name: string | undefined): ResourceManifest | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* The invocation contract of the resource a reference names.
|
|
52
|
+
*
|
|
53
|
+
* The shared resolver, so an editor offering a target's input keys is offering
|
|
54
|
+
* exactly what `telo check` validates that call site against and what the
|
|
55
|
+
* kernel binds at dispatch. Layered instance-first: a resource declaring its
|
|
56
|
+
* own `inputType:` narrows the kind's, which is the common case for a
|
|
57
|
+
* `Run.Sequence` used as a handler.
|
|
58
|
+
*/
|
|
59
|
+
contractFor(ref: ManifestRef, direction: ContractDirection): Record<string, any> | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* The manifest a reference names.
|
|
62
|
+
*
|
|
63
|
+
* An ALIAS narrows before the name does: a flattened set carries every
|
|
64
|
+
* imported library's exported instances, so two libraries exporting a `store`
|
|
65
|
+
* are two manifests with one name. Matching the alias to its target module
|
|
66
|
+
* picks the right one; where the alias resolves to nothing the name alone is
|
|
67
|
+
* used, which is what a local reference needs anyway.
|
|
68
|
+
*/
|
|
69
|
+
private resolveRef;
|
|
70
|
+
private definitionFor;
|
|
71
|
+
}
|
|
72
|
+
export type { CelScopeQueryContext, AliasResolver, DefinitionRegistry };
|
|
73
|
+
//# sourceMappingURL=manifest-analysis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-analysis.d.ts","sourceRoot":"","sources":["../src/manifest-analysis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,aAAa,EAAqB,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAKjE;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,gBAAgB;IAKzB,QAAQ,CAAC,SAAS,EAAE,gBAAgB,EAAE;IACtC,OAAO,CAAC,QAAQ,CAAC,GAAG;IALtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,aAAa,CAA4B;gBAGtC,SAAS,EAAE,gBAAgB,EAAE,EACrB,GAAG,EAAE,oBAAoB;IAS5C;;gBAEY;IACZ,IAAI,QAAQ,IAAI,aAAa,CAE5B;IAED,oDAAoD;IACpD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS;IAI7F;;;;;;;;OAQG;IACH,WAAW,CAAC,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS;IAiB5F;;;;;;;;OAQG;IACH,OAAO,CAAC,UAAU;IAqBlB,OAAO,CAAC,aAAa;CAItB;AAED,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,kBAAkB,EAAE,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { CelScopeQuery } from "./cel-scope-query.js";
|
|
2
|
+
import { analyzerContractScope, resolveContract } from "./invocation-contract.js";
|
|
3
|
+
import { findManifest } from "./find-manifest.js";
|
|
4
|
+
import { isModuleKind } from "./module-kinds.js";
|
|
5
|
+
export class ManifestAnalysis {
|
|
6
|
+
manifests;
|
|
7
|
+
ctx;
|
|
8
|
+
scopes;
|
|
9
|
+
celScopeQuery;
|
|
10
|
+
constructor(manifests, ctx) {
|
|
11
|
+
this.manifests = manifests;
|
|
12
|
+
this.ctx = ctx;
|
|
13
|
+
const rootModules = new Set();
|
|
14
|
+
for (const m of manifests) {
|
|
15
|
+
if (isModuleKind(m.kind) && m.metadata?.name)
|
|
16
|
+
rootModules.add(m.metadata.name);
|
|
17
|
+
}
|
|
18
|
+
this.scopes = { aliasesByModule: ctx.aliasesByModule, rootModules };
|
|
19
|
+
}
|
|
20
|
+
/** What CEL sees, per site. Built on first use — its indices are a function of
|
|
21
|
+
* the whole set, and a host that never opens a CEL body should not pay for
|
|
22
|
+
* them. */
|
|
23
|
+
get celScope() {
|
|
24
|
+
return (this.celScopeQuery ??= new CelScopeQuery(this.manifests, this.ctx));
|
|
25
|
+
}
|
|
26
|
+
/** The manifest a `(kind, name)` pair addresses. */
|
|
27
|
+
resourceFor(kind, name) {
|
|
28
|
+
return findManifest(this.manifests, kind, name);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The invocation contract of the resource a reference names.
|
|
32
|
+
*
|
|
33
|
+
* The shared resolver, so an editor offering a target's input keys is offering
|
|
34
|
+
* exactly what `telo check` validates that call site against and what the
|
|
35
|
+
* kernel binds at dispatch. Layered instance-first: a resource declaring its
|
|
36
|
+
* own `inputType:` narrows the kind's, which is the common case for a
|
|
37
|
+
* `Run.Sequence` used as a handler.
|
|
38
|
+
*/
|
|
39
|
+
contractFor(ref, direction) {
|
|
40
|
+
const target = this.resolveRef(ref);
|
|
41
|
+
const definition = ref.kind ? this.definitionFor(ref.kind) : undefined;
|
|
42
|
+
if (!target && !definition)
|
|
43
|
+
return undefined;
|
|
44
|
+
return resolveContract(direction, target, definition, analyzerContractScope(this.ctx.defs, this.ctx.aliases, this.scopes, this.manifests))?.schema;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The manifest a reference names.
|
|
48
|
+
*
|
|
49
|
+
* An ALIAS narrows before the name does: a flattened set carries every
|
|
50
|
+
* imported library's exported instances, so two libraries exporting a `store`
|
|
51
|
+
* are two manifests with one name. Matching the alias to its target module
|
|
52
|
+
* picks the right one; where the alias resolves to nothing the name alone is
|
|
53
|
+
* used, which is what a local reference needs anyway.
|
|
54
|
+
*/
|
|
55
|
+
resolveRef(ref) {
|
|
56
|
+
if (!ref.name)
|
|
57
|
+
return undefined;
|
|
58
|
+
const byName = this.manifests.filter((m) => m.metadata?.name === ref.name);
|
|
59
|
+
if (byName.length === 0)
|
|
60
|
+
return undefined;
|
|
61
|
+
if (byName.length === 1)
|
|
62
|
+
return byName[0];
|
|
63
|
+
const targetModule = ref.alias ? this.ctx.aliases.moduleForAlias?.(ref.alias) : undefined;
|
|
64
|
+
if (targetModule) {
|
|
65
|
+
const scoped = byName.find((m) => m.metadata?.module === targetModule);
|
|
66
|
+
if (scoped)
|
|
67
|
+
return scoped;
|
|
68
|
+
}
|
|
69
|
+
// Several candidates and nothing to choose between them: refusing is the
|
|
70
|
+
// honest answer, since typing a call site against the wrong resource's
|
|
71
|
+
// contract is worse than typing it against none.
|
|
72
|
+
return ref.kind ? byName.find((m) => m.kind === ref.kind) : undefined;
|
|
73
|
+
}
|
|
74
|
+
definitionFor(kind) {
|
|
75
|
+
const canonical = this.ctx.aliases.resolveKind(kind);
|
|
76
|
+
return this.ctx.defs.resolve(kind) ?? (canonical ? this.ctx.defs.resolve(canonical) : undefined);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigating a MANIFEST by a concrete path (`routes[0].request.schema.query`).
|
|
3
|
+
*
|
|
4
|
+
* The counterpart to `schema-walk.ts`, which navigates a schema: this addresses
|
|
5
|
+
* the author's own document, indices and all. Its own module because three
|
|
6
|
+
* places needed it independently — the CEL scope query resolving a context
|
|
7
|
+
* binding's declaration, the call-site checker resolving an argument map, and
|
|
8
|
+
* the IDE resolving the same map for completion — and three copies of one
|
|
9
|
+
* traversal is exactly what the shared-answer rule exists to prevent.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The value at `path`, or `undefined` when any segment is absent.
|
|
13
|
+
*
|
|
14
|
+
* Absence is what makes a candidate path a CHECK rather than a guess: a caller
|
|
15
|
+
* offering several possible shapes can try each and know a hit is a real node.
|
|
16
|
+
*/
|
|
17
|
+
export declare function navigateConcretePath(root: Record<string, any>, path: string): unknown;
|
|
18
|
+
//# sourceMappingURL=manifest-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-path.d.ts","sourceRoot":"","sources":["../src/manifest-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAgBrF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigating a MANIFEST by a concrete path (`routes[0].request.schema.query`).
|
|
3
|
+
*
|
|
4
|
+
* The counterpart to `schema-walk.ts`, which navigates a schema: this addresses
|
|
5
|
+
* the author's own document, indices and all. Its own module because three
|
|
6
|
+
* places needed it independently — the CEL scope query resolving a context
|
|
7
|
+
* binding's declaration, the call-site checker resolving an argument map, and
|
|
8
|
+
* the IDE resolving the same map for completion — and three copies of one
|
|
9
|
+
* traversal is exactly what the shared-answer rule exists to prevent.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The value at `path`, or `undefined` when any segment is absent.
|
|
13
|
+
*
|
|
14
|
+
* Absence is what makes a candidate path a CHECK rather than a guess: a caller
|
|
15
|
+
* offering several possible shapes can try each and know a hit is a real node.
|
|
16
|
+
*/
|
|
17
|
+
export function navigateConcretePath(root, path) {
|
|
18
|
+
let current = root;
|
|
19
|
+
for (const segment of path.split(".")) {
|
|
20
|
+
if (!segment)
|
|
21
|
+
continue;
|
|
22
|
+
const match = segment.match(/^([^[]*)((?:\[\d+\])*)$/);
|
|
23
|
+
if (!match)
|
|
24
|
+
return undefined;
|
|
25
|
+
if (match[1]) {
|
|
26
|
+
if (current === null || typeof current !== "object")
|
|
27
|
+
return undefined;
|
|
28
|
+
current = current[match[1]];
|
|
29
|
+
}
|
|
30
|
+
for (const index of match[2].matchAll(/\[(\d+)\]/g)) {
|
|
31
|
+
if (!Array.isArray(current))
|
|
32
|
+
return undefined;
|
|
33
|
+
current = current[Number(index[1])];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return current;
|
|
37
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Resolve a local `$ref` (only `#/$defs/<name>` form) against the root schema.
|
|
2
|
+
* Non-refs and unresolved refs pass through unchanged. */
|
|
3
|
+
export declare function resolveLocalRef(schema: Record<string, any> | undefined, root: Record<string, any>): Record<string, any> | undefined;
|
|
4
|
+
/** Gather property schemas from a (possibly variant-bearing) object schema:
|
|
5
|
+
* top-level `properties` plus every `oneOf` / `anyOf` / `allOf` branch.
|
|
6
|
+
*
|
|
7
|
+
* Each branch is resolved through {@link resolveLocalRef} first, so a branch
|
|
8
|
+
* that points at a shared shape — a `oneOf` arm that IS the kernel's dispatch
|
|
9
|
+
* site — contributes its properties like an inline one. Without that, pointing a
|
|
10
|
+
* composer at a shared shape would silently empty every role-driven lookup that
|
|
11
|
+
* reads this (the inputs slot, the retry policy, the eval paths), which is a
|
|
12
|
+
* failure with no diagnostic attached to it. */
|
|
13
|
+
export declare function gatherPropertySchemas(schema: Record<string, any>, root?: Record<string, any>): Array<[string, Record<string, any>]>;
|
|
14
|
+
/**
|
|
15
|
+
* Generic, role-driven walk over a step array. Calls
|
|
16
|
+
* `visit(step, stepPath)` for every step — top-level and nested through the
|
|
17
|
+
* `x-telo-topology-role` forms (`branch`, `branch-list`, `case-map`). This is
|
|
18
|
+
* the single definition of how steps nest, shared by `buildStepContextSchema`
|
|
19
|
+
* (which types `steps.<name>.result`) and `validateStepInvokeReferences` (which
|
|
20
|
+
* checks invoke refs), so the topology contract lives in one place — adding a
|
|
21
|
+
* role or nesting form updates both consumers at once. No resource kind is
|
|
22
|
+
* hardcoded; recursion is driven entirely by the schema annotations.
|
|
23
|
+
*/
|
|
24
|
+
export declare function walkStepArray(steps: unknown[], stepItemSchema: Record<string, any> | undefined, rootSchema: Record<string, any>, basePath: string, visit: (step: Record<string, any>, stepPath: string) => void): void;
|
|
25
|
+
//# sourceMappingURL=schema-walk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-walk.d.ts","sourceRoot":"","sources":["../src/schema-walk.ts"],"names":[],"mappings":"AAYA;2DAC2D;AAC3D,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EACvC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACxB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAqBjC;AAID;;;;;;;;iDAQiD;AACjD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACzB,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAsBtC;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,EAAE,EAChB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EAC/C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,GAC3D,IAAI,CAiDN"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural traversal over a kind's JSON Schema and the step arrays it
|
|
3
|
+
* declares. Nothing here analyzes: these answer "what does this schema node
|
|
4
|
+
* point at" and "how do steps nest", the two questions every analyzer pass
|
|
5
|
+
* asks before it can say anything.
|
|
6
|
+
*
|
|
7
|
+
* Its own file so the CEL scope rule (`cel-scope.ts`) and the analysis pass
|
|
8
|
+
* (`analyzer.ts`) can both reach it without either importing the other — the
|
|
9
|
+
* scope rule is consumed by the IDE, which must not pull the pass in behind it.
|
|
10
|
+
*/
|
|
11
|
+
import { MANIFEST_SCHEMA_URI, ManifestRootSchema } from "./manifest-schemas.js";
|
|
12
|
+
/** Resolve a local `$ref` (only `#/$defs/<name>` form) against the root schema.
|
|
13
|
+
* Non-refs and unresolved refs pass through unchanged. */
|
|
14
|
+
export function resolveLocalRef(schema, root) {
|
|
15
|
+
if (!schema)
|
|
16
|
+
return undefined;
|
|
17
|
+
const ref = schema.$ref;
|
|
18
|
+
if (typeof ref === "string" && ref.startsWith("#/$defs/")) {
|
|
19
|
+
const defName = ref.slice("#/$defs/".length);
|
|
20
|
+
const resolved = root.$defs?.[defName];
|
|
21
|
+
if (resolved && typeof resolved === "object")
|
|
22
|
+
return resolved;
|
|
23
|
+
}
|
|
24
|
+
// A kernel-owned structural fragment (`telo://manifest#/$defs/InvokeStep`).
|
|
25
|
+
// Resolved HERE rather than by each walker: this is the one chokepoint every
|
|
26
|
+
// structural walk already goes through — the step-array walks, the call graph,
|
|
27
|
+
// the zone projection, the eval-path collector — so a composer that points at a
|
|
28
|
+
// shared shape stays legible to all of them at once. Nothing is inlined into
|
|
29
|
+
// the stored schema, which keeps validator-cache identity stable and matches
|
|
30
|
+
// what `resolveSchemaTypeRefs` does for a named user type.
|
|
31
|
+
if (typeof ref === "string" && ref.startsWith(BUILTIN_FRAGMENT_PREFIX)) {
|
|
32
|
+
const defName = ref.slice(BUILTIN_FRAGMENT_PREFIX.length);
|
|
33
|
+
const resolved = ManifestRootSchema.$defs[defName];
|
|
34
|
+
if (resolved && typeof resolved === "object")
|
|
35
|
+
return resolved;
|
|
36
|
+
}
|
|
37
|
+
return schema;
|
|
38
|
+
}
|
|
39
|
+
const BUILTIN_FRAGMENT_PREFIX = `${MANIFEST_SCHEMA_URI}#/$defs/`;
|
|
40
|
+
/** Gather property schemas from a (possibly variant-bearing) object schema:
|
|
41
|
+
* top-level `properties` plus every `oneOf` / `anyOf` / `allOf` branch.
|
|
42
|
+
*
|
|
43
|
+
* Each branch is resolved through {@link resolveLocalRef} first, so a branch
|
|
44
|
+
* that points at a shared shape — a `oneOf` arm that IS the kernel's dispatch
|
|
45
|
+
* site — contributes its properties like an inline one. Without that, pointing a
|
|
46
|
+
* composer at a shared shape would silently empty every role-driven lookup that
|
|
47
|
+
* reads this (the inputs slot, the retry policy, the eval paths), which is a
|
|
48
|
+
* failure with no diagnostic attached to it. */
|
|
49
|
+
export function gatherPropertySchemas(schema, root) {
|
|
50
|
+
const out = [];
|
|
51
|
+
const base = resolveLocalRef(schema, root ?? schema) ?? schema;
|
|
52
|
+
if (base.properties && typeof base.properties === "object") {
|
|
53
|
+
for (const [k, v] of Object.entries(base.properties)) {
|
|
54
|
+
out.push([k, v]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const variantKey of ["oneOf", "anyOf", "allOf"]) {
|
|
58
|
+
const arr = base[variantKey];
|
|
59
|
+
if (!Array.isArray(arr))
|
|
60
|
+
continue;
|
|
61
|
+
for (const raw of arr) {
|
|
62
|
+
if (!raw || typeof raw !== "object")
|
|
63
|
+
continue;
|
|
64
|
+
const variant = resolveLocalRef(raw, root ?? schema) ?? raw;
|
|
65
|
+
if (variant.properties) {
|
|
66
|
+
for (const [k, v] of Object.entries(variant.properties)) {
|
|
67
|
+
out.push([k, v]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Generic, role-driven walk over a step array. Calls
|
|
76
|
+
* `visit(step, stepPath)` for every step — top-level and nested through the
|
|
77
|
+
* `x-telo-topology-role` forms (`branch`, `branch-list`, `case-map`). This is
|
|
78
|
+
* the single definition of how steps nest, shared by `buildStepContextSchema`
|
|
79
|
+
* (which types `steps.<name>.result`) and `validateStepInvokeReferences` (which
|
|
80
|
+
* checks invoke refs), so the topology contract lives in one place — adding a
|
|
81
|
+
* role or nesting form updates both consumers at once. No resource kind is
|
|
82
|
+
* hardcoded; recursion is driven entirely by the schema annotations.
|
|
83
|
+
*/
|
|
84
|
+
export function walkStepArray(steps, stepItemSchema, rootSchema, basePath, visit) {
|
|
85
|
+
const dispatchRole = (data, role, itemsSchema, path) => {
|
|
86
|
+
if (role === "branch" && Array.isArray(data)) {
|
|
87
|
+
walkStepArray(data, stepItemSchema, rootSchema, path, visit);
|
|
88
|
+
}
|
|
89
|
+
else if (role === "case-map" && data && typeof data === "object" && !Array.isArray(data)) {
|
|
90
|
+
for (const [caseKey, arr] of Object.entries(data)) {
|
|
91
|
+
if (Array.isArray(arr))
|
|
92
|
+
walkStepArray(arr, stepItemSchema, rootSchema, `${path}.${caseKey}`, visit);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (role === "branch-list" && Array.isArray(data)) {
|
|
96
|
+
const entrySchema = resolveLocalRef(itemsSchema, rootSchema);
|
|
97
|
+
if (!entrySchema)
|
|
98
|
+
return;
|
|
99
|
+
data.forEach((entry, i) => {
|
|
100
|
+
if (!entry || typeof entry !== "object")
|
|
101
|
+
return;
|
|
102
|
+
for (const [subKey, subSchema] of gatherPropertySchemas(entrySchema)) {
|
|
103
|
+
const subRole = subSchema["x-telo-topology-role"];
|
|
104
|
+
if (typeof subRole !== "string")
|
|
105
|
+
continue;
|
|
106
|
+
dispatchRole(entry[subKey], subRole, subSchema.items, `${path}[${i}].${subKey}`);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
steps.forEach((step, i) => {
|
|
112
|
+
if (!step || typeof step !== "object")
|
|
113
|
+
return;
|
|
114
|
+
const s = step;
|
|
115
|
+
const stepPath = `${basePath}[${i}]`;
|
|
116
|
+
visit(s, stepPath);
|
|
117
|
+
if (!stepItemSchema)
|
|
118
|
+
return;
|
|
119
|
+
for (const [propKey, propSchema] of gatherPropertySchemas(stepItemSchema)) {
|
|
120
|
+
const role = propSchema["x-telo-topology-role"];
|
|
121
|
+
if (typeof role !== "string")
|
|
122
|
+
continue;
|
|
123
|
+
dispatchRole(s[propKey], role, propSchema.items, `${stepPath}.${propKey}`);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
package/dist/telo-version.d.ts
CHANGED
package/dist/telo-version.js
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
// its release identity, this is the scale a module's `requires.telo` range is
|
|
6
6
|
// written against, and every kernel in every language reports the same scale.
|
|
7
7
|
/** The surface generation this analyzer implements. */
|
|
8
|
-
export const TELO_SURFACE_VERSION = "0.
|
|
8
|
+
export const TELO_SURFACE_VERSION = "0.80.0";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AliasResolver, ModuleScopes } from "./alias-resolver.js";
|
|
2
2
|
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
3
|
+
import { type ReferenceFieldMap } from "./reference-field-map.js";
|
|
3
4
|
export interface StepInputIssue {
|
|
4
5
|
path: string;
|
|
5
6
|
targetLabel: string;
|
|
@@ -29,4 +30,20 @@ export declare function collectStepInputIssues(manifest: Record<string, any>, de
|
|
|
29
30
|
* caller because building it is analyzer state; without it the contract check
|
|
30
31
|
* still runs and only the type-argument comparison is skipped. */
|
|
31
32
|
stepContext?: Record<string, any>): StepInputIssue[];
|
|
33
|
+
/**
|
|
34
|
+
* Validate the argument map of every call this resource makes through a
|
|
35
|
+
* REFERENCE SLOT, as opposed to a step.
|
|
36
|
+
*
|
|
37
|
+
* A slot that transfers control names its argument slot on its own `x-telo-ref`
|
|
38
|
+
* (`inputs:`, a JSON Pointer relative to the object enclosing the slot). That
|
|
39
|
+
* annotation is the only thing tying an otherwise-open `inputs:` map to the
|
|
40
|
+
* resource it holds arguments for — an HTTP route's `handler:` + `inputs:` pair
|
|
41
|
+
* is exactly this shape, and nothing about it is a step.
|
|
42
|
+
*
|
|
43
|
+
* Discovery is driven by the annotation rather than by any kind's topology, so
|
|
44
|
+
* a composer that names its argument slot gets its call sites checked without
|
|
45
|
+
* the analyzer learning what a route is. It is the same check the step driver
|
|
46
|
+
* runs, because it is the same question.
|
|
47
|
+
*/
|
|
48
|
+
export declare function collectRefInputIssues(manifest: Record<string, any>, fieldMap: ReferenceFieldMap | undefined, allManifests: Record<string, any>[], defs: DefinitionRegistry, aliases: AliasResolver, scopes: ModuleScopes): StepInputIssue[];
|
|
32
49
|
//# sourceMappingURL=validate-step-inputs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-step-inputs.d.ts","sourceRoot":"","sources":["../src/validate-step-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAsBnE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB;iFAC6E;IAC7E,IAAI,CAAC,EAAE,4BAA4B,GAAG,oBAAoB,CAAC;CAC5D;AAGD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,YAAY;AACpB;;mEAEmE;AACnE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAChC,cAAc,EAAE,
|
|
1
|
+
{"version":3,"file":"validate-step-inputs.d.ts","sourceRoot":"","sources":["../src/validate-step-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAsBnE,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAElC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB;iFAC6E;IAC7E,IAAI,CAAC,EAAE,4BAA4B,GAAG,oBAAoB,CAAC;CAC5D;AAGD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,YAAY;AACpB;;mEAEmE;AACnE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAChC,cAAc,EAAE,CA4DlB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,QAAQ,EAAE,iBAAiB,GAAG,SAAS,EACvC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,YAAY,GACnB,cAAc,EAAE,CAoClB"}
|
|
@@ -5,6 +5,8 @@ import { isLiveSlot, valueTypeOf } from "@telorun/sdk";
|
|
|
5
5
|
import { manifestFragmentOf } from "./manifest-schemas.js";
|
|
6
6
|
import { analyzerContractScope, containerOf, gatherPropertySchemas, missingRequired, resolveLocalRef, walkStepArray, } from "./analyzer.js";
|
|
7
7
|
import { readStepSlot } from "./step-slot.js";
|
|
8
|
+
import { navigateConcretePath } from "./manifest-path.js";
|
|
9
|
+
import { isRefEntry, resolveFieldEntries, } from "./reference-field-map.js";
|
|
8
10
|
/**
|
|
9
11
|
* Validate every step's `inputs:` against the invoked target's declared input
|
|
10
12
|
* contract — the static half of what the kernel enforces at dispatch.
|
|
@@ -32,6 +34,14 @@ stepContext) {
|
|
|
32
34
|
return out;
|
|
33
35
|
const contractScope = analyzerContractScope(defs, aliases, scopes, allManifests);
|
|
34
36
|
const readingModule = manifest.metadata?.module;
|
|
37
|
+
const ctx = {
|
|
38
|
+
manifest,
|
|
39
|
+
allManifests,
|
|
40
|
+
defs,
|
|
41
|
+
contractScope,
|
|
42
|
+
readingModule,
|
|
43
|
+
stepContext,
|
|
44
|
+
};
|
|
35
45
|
for (const [fieldName, fieldSchema] of Object.entries(props)) {
|
|
36
46
|
const stepCtx = readStepSlot(fieldSchema);
|
|
37
47
|
if (!stepCtx)
|
|
@@ -58,6 +68,87 @@ stepContext) {
|
|
|
58
68
|
return;
|
|
59
69
|
if (!values || typeof values !== "object" || Array.isArray(values))
|
|
60
70
|
return;
|
|
71
|
+
out.push(...checkCallSite({
|
|
72
|
+
inputsPath: `${stepPath}.${inputsField}`,
|
|
73
|
+
values: values,
|
|
74
|
+
invoke,
|
|
75
|
+
// Only a step declares a re-attempt policy, so only a step can carry
|
|
76
|
+
// the live-value-retried finding.
|
|
77
|
+
declaredRetryFor: (invokedManifest, invokedDef) => declaredRetry(step, stepItemSchema, invokedManifest, invokedDef),
|
|
78
|
+
}, ctx));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Validate the argument map of every call this resource makes through a
|
|
85
|
+
* REFERENCE SLOT, as opposed to a step.
|
|
86
|
+
*
|
|
87
|
+
* A slot that transfers control names its argument slot on its own `x-telo-ref`
|
|
88
|
+
* (`inputs:`, a JSON Pointer relative to the object enclosing the slot). That
|
|
89
|
+
* annotation is the only thing tying an otherwise-open `inputs:` map to the
|
|
90
|
+
* resource it holds arguments for — an HTTP route's `handler:` + `inputs:` pair
|
|
91
|
+
* is exactly this shape, and nothing about it is a step.
|
|
92
|
+
*
|
|
93
|
+
* Discovery is driven by the annotation rather than by any kind's topology, so
|
|
94
|
+
* a composer that names its argument slot gets its call sites checked without
|
|
95
|
+
* the analyzer learning what a route is. It is the same check the step driver
|
|
96
|
+
* runs, because it is the same question.
|
|
97
|
+
*/
|
|
98
|
+
export function collectRefInputIssues(manifest, fieldMap, allManifests, defs, aliases, scopes) {
|
|
99
|
+
const out = [];
|
|
100
|
+
if (!fieldMap)
|
|
101
|
+
return out;
|
|
102
|
+
const contractScope = analyzerContractScope(defs, aliases, scopes, allManifests);
|
|
103
|
+
const ctx = {
|
|
104
|
+
manifest,
|
|
105
|
+
allManifests,
|
|
106
|
+
defs,
|
|
107
|
+
contractScope,
|
|
108
|
+
readingModule: manifest.metadata?.module,
|
|
109
|
+
};
|
|
110
|
+
for (const [fieldPath, entry] of fieldMap) {
|
|
111
|
+
if (!isRefEntry(entry) || !entry.inputs)
|
|
112
|
+
continue;
|
|
113
|
+
const pointer = pointerSegments(entry.inputs);
|
|
114
|
+
if (!pointer)
|
|
115
|
+
continue;
|
|
116
|
+
for (const { value: invoke, path: slotPath } of resolveFieldEntries(manifest, fieldPath)) {
|
|
117
|
+
if (!invoke || typeof invoke !== "object" || Array.isArray(invoke))
|
|
118
|
+
continue;
|
|
119
|
+
// Relative to the object ENCLOSING the slot, which is the annotation's
|
|
120
|
+
// documented anchor.
|
|
121
|
+
const enclosing = slotPath.slice(0, Math.max(0, slotPath.lastIndexOf(".")));
|
|
122
|
+
const inputsPath = [enclosing, ...pointer].filter(Boolean).join(".");
|
|
123
|
+
const values = navigateConcretePath(manifest, inputsPath);
|
|
124
|
+
if (!values || typeof values !== "object" || Array.isArray(values))
|
|
125
|
+
continue;
|
|
126
|
+
out.push(...checkCallSite({ inputsPath, values: values, invoke: invoke }, ctx));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
/** A JSON Pointer naming a sibling FIELD path. An array index is not a field,
|
|
132
|
+
* so a pointer carrying one names nothing this can resolve. */
|
|
133
|
+
function pointerSegments(pointer) {
|
|
134
|
+
if (!pointer.startsWith("/"))
|
|
135
|
+
return undefined;
|
|
136
|
+
const segments = pointer
|
|
137
|
+
.slice(1)
|
|
138
|
+
.split("/")
|
|
139
|
+
.map((s) => s.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
140
|
+
return segments.every((s) => s.length > 0 && !/^\d+$/.test(s)) ? segments : undefined;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The check itself, shared by both drivers: the arguments written at a call site
|
|
144
|
+
* against the contract the target declares.
|
|
145
|
+
*/
|
|
146
|
+
function checkCallSite(site, ctx) {
|
|
147
|
+
const out = [];
|
|
148
|
+
const { manifest, allManifests, defs, contractScope, readingModule, stepContext } = ctx;
|
|
149
|
+
const { invoke, values } = site;
|
|
150
|
+
{
|
|
151
|
+
{
|
|
61
152
|
const invokedKind = invoke.kind;
|
|
62
153
|
const invokedName = invoke.name;
|
|
63
154
|
const invokedManifest = invokedName
|
|
@@ -68,7 +159,7 @@ stepContext) {
|
|
|
68
159
|
: undefined;
|
|
69
160
|
const contract = resolveContract("inputType", invokedManifest, invokedDef, contractScope);
|
|
70
161
|
if (!contract)
|
|
71
|
-
return;
|
|
162
|
+
return out;
|
|
72
163
|
// Findings AT a substituted path are about a placeholder, not about
|
|
73
164
|
// anything the author wrote — a `pattern`-constrained string or a `oneOf`
|
|
74
165
|
// of unrelated shapes cannot be satisfied by any stand-in. Structural
|
|
@@ -120,10 +211,10 @@ stepContext) {
|
|
|
120
211
|
// already declared: the value's liveness by its value type, and the
|
|
121
212
|
// re-attempt by the retry policy. No kind is named.
|
|
122
213
|
if (isLiveSlot(produced)) {
|
|
123
|
-
const retry =
|
|
214
|
+
const retry = site.declaredRetryFor?.(invokedManifest, invokedDef);
|
|
124
215
|
if (retry !== undefined) {
|
|
125
216
|
out.push({
|
|
126
|
-
path: `${
|
|
217
|
+
path: `${site.inputsPath}.${inputName}`,
|
|
127
218
|
targetLabel: invokedName ?? invokedKind ?? "the invoked resource",
|
|
128
219
|
message: `'${inputName}' is a live value, which is consumed by reading and so exists ` +
|
|
129
220
|
`once — but ${retry} re-attempts the dispatch, and a re-attempt would pass ` +
|
|
@@ -140,7 +231,7 @@ stepContext) {
|
|
|
140
231
|
if (compatible)
|
|
141
232
|
continue;
|
|
142
233
|
out.push({
|
|
143
|
-
path: `${
|
|
234
|
+
path: `${site.inputsPath}.${inputName}`,
|
|
144
235
|
targetLabel: invokedName ?? invokedKind ?? "the invoked resource",
|
|
145
236
|
message: issues.join("; "),
|
|
146
237
|
code: "CEL_TYPE_ARGUMENT_MISMATCH",
|
|
@@ -156,12 +247,12 @@ stepContext) {
|
|
|
156
247
|
// container that should have held it, which does exist.
|
|
157
248
|
const anchor = missingRequired(issue) ? containerOf(issue.path) : issue.path;
|
|
158
249
|
out.push({
|
|
159
|
-
path: anchor ? `${
|
|
250
|
+
path: anchor ? `${site.inputsPath}.${anchor}` : site.inputsPath,
|
|
160
251
|
targetLabel: invokedName ?? invokedKind ?? "the invoked resource",
|
|
161
252
|
message: issue.message,
|
|
162
253
|
});
|
|
163
254
|
}
|
|
164
|
-
}
|
|
255
|
+
}
|
|
165
256
|
}
|
|
166
257
|
return out;
|
|
167
258
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.64.0",
|
|
4
4
|
"description": "Telo Analyzer - Static manifest validator for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@types/node": "^20.0.0",
|
|
50
50
|
"typescript": "^5.0.0",
|
|
51
51
|
"vitest": "^2.1.8",
|
|
52
|
-
"@telorun/sdk": "0.
|
|
52
|
+
"@telorun/sdk": "0.80.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@telorun/sdk": "*"
|
package/src/analysis-registry.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
|
|
2
2
|
import { AliasResolver } from "./alias-resolver.js";
|
|
3
3
|
import { KERNEL_BUILTINS } from "./builtins.js";
|
|
4
|
+
import { ManifestAnalysis } from "./manifest-analysis.js";
|
|
4
5
|
import { DefinitionRegistry } from "./definition-registry.js";
|
|
5
6
|
import { computeSuggestKind, computeValidUserFacingKinds } from "./kind-suggest.js";
|
|
6
7
|
import { visitManifest as runVisitManifest, type ManifestVisitor } from "./manifest-visitor.js";
|
|
@@ -242,6 +243,42 @@ export class AnalysisRegistry {
|
|
|
242
243
|
});
|
|
243
244
|
}
|
|
244
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Resolves an `x-telo-schema-from` annotation to the schema node it derives,
|
|
248
|
+
* in the scope of the kind that DECLARED it — anchors are alias-qualified, and
|
|
249
|
+
* the declaring definition's module is where those aliases mean something.
|
|
250
|
+
*
|
|
251
|
+
* The seam an IDE walks a schema through: a slot shaped entirely by this
|
|
252
|
+
* annotation (an `Http.Api` route's `request:`) carries no `properties` of its
|
|
253
|
+
* own, so a walker that only reads `properties` finds nothing there and
|
|
254
|
+
* silently offers no keys and no hover — a whole field of the standard library
|
|
255
|
+
* looking like an unknown one.
|
|
256
|
+
*/
|
|
257
|
+
resolveSchemaFrom(schemaFrom: string, declaringKind: string): Record<string, any> | undefined {
|
|
258
|
+
const def = this.resolveDefinition(declaringKind);
|
|
259
|
+
const ownerModule = (def?.metadata as { module?: string } | undefined)?.module;
|
|
260
|
+
const scope = (ownerModule ? this.aliasesByModule.get(ownerModule) : undefined) ?? this.aliases;
|
|
261
|
+
return this.defs.resolveSchemaFromNode(schemaFrom, scope);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* The queries that need this registry AND a manifest set: what CEL sees at a
|
|
266
|
+
* site, where a binding was declared, what a reference's contract is.
|
|
267
|
+
*
|
|
268
|
+
* The seam an IDE reaches all of them through, and one object rather than a
|
|
269
|
+
* factory per question — each answer has to be the one the analysis pass
|
|
270
|
+
* computed, or a completion list stops being a claim about what `telo check`
|
|
271
|
+
* accepts. Built per analysis, not per keystroke: the indices behind it are a
|
|
272
|
+
* function of the whole manifest set.
|
|
273
|
+
*/
|
|
274
|
+
analysisOf(manifests: ResourceManifest[]): ManifestAnalysis {
|
|
275
|
+
return new ManifestAnalysis(manifests, {
|
|
276
|
+
defs: this.defs,
|
|
277
|
+
aliases: this.aliases,
|
|
278
|
+
aliasesByModule: this.aliasesByModule,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
245
282
|
/**
|
|
246
283
|
* Returns the built-in kernel definitions. The underlying DefinitionRegistry already
|
|
247
284
|
* seeds these on construction; this method exposes them so callers (e.g. the kernel's
|