@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.
Files changed (52) hide show
  1. package/dist/analysis-registry.d.ts +24 -0
  2. package/dist/analysis-registry.d.ts.map +1 -1
  3. package/dist/analysis-registry.js +35 -0
  4. package/dist/analyzer.d.ts +3 -37
  5. package/dist/analyzer.d.ts.map +1 -1
  6. package/dist/analyzer.js +57 -467
  7. package/dist/cel-scope-query.d.ts +109 -0
  8. package/dist/cel-scope-query.d.ts.map +1 -0
  9. package/dist/cel-scope-query.js +270 -0
  10. package/dist/cel-scope.d.ts +166 -0
  11. package/dist/cel-scope.d.ts.map +1 -0
  12. package/dist/cel-scope.js +377 -0
  13. package/dist/definition-registry.d.ts +15 -0
  14. package/dist/definition-registry.d.ts.map +1 -1
  15. package/dist/definition-registry.js +25 -9
  16. package/dist/find-manifest.d.ts +10 -0
  17. package/dist/find-manifest.d.ts.map +1 -0
  18. package/dist/find-manifest.js +12 -0
  19. package/dist/index.d.ts +7 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +9 -0
  22. package/dist/invocation-contract.d.ts +11 -0
  23. package/dist/invocation-contract.d.ts.map +1 -1
  24. package/dist/invocation-contract.js +15 -0
  25. package/dist/manifest-analysis.d.ts +73 -0
  26. package/dist/manifest-analysis.d.ts.map +1 -0
  27. package/dist/manifest-analysis.js +78 -0
  28. package/dist/manifest-path.d.ts +18 -0
  29. package/dist/manifest-path.d.ts.map +1 -0
  30. package/dist/manifest-path.js +37 -0
  31. package/dist/schema-walk.d.ts +25 -0
  32. package/dist/schema-walk.d.ts.map +1 -0
  33. package/dist/schema-walk.js +126 -0
  34. package/dist/telo-version.d.ts +1 -1
  35. package/dist/telo-version.js +1 -1
  36. package/dist/validate-step-inputs.d.ts +17 -0
  37. package/dist/validate-step-inputs.d.ts.map +1 -1
  38. package/dist/validate-step-inputs.js +97 -6
  39. package/package.json +2 -2
  40. package/src/analysis-registry.ts +37 -0
  41. package/src/analyzer.ts +61 -585
  42. package/src/cel-scope-query.ts +337 -0
  43. package/src/cel-scope.ts +570 -0
  44. package/src/definition-registry.ts +31 -13
  45. package/src/find-manifest.ts +19 -0
  46. package/src/index.ts +13 -0
  47. package/src/invocation-contract.ts +22 -0
  48. package/src/manifest-analysis.ts +132 -0
  49. package/src/manifest-path.ts +34 -0
  50. package/src/schema-walk.ts +144 -0
  51. package/src/telo-version.ts +1 -1
  52. package/src/validate-step-inputs.ts +143 -7
@@ -0,0 +1,109 @@
1
+ /**
2
+ * **Ask what a CEL site sees, from outside the analysis pass.**
3
+ *
4
+ * The pass resolves a scope per expression as it walks. An IDE has no walk: it
5
+ * has a cursor, and needs the same answer for one address — often for an
6
+ * expression the last analysis never saw, because the user is typing it. So the
7
+ * query is driven by (manifest, path) rather than by a visitor event, and the
8
+ * `x-telo-context` match is recomputed here exactly as the visitor computes it
9
+ * (`extractContextsFromSchema` + `pathMatchesScope`, the same two functions).
10
+ *
11
+ * The scope RULE itself is not re-implemented — {@link CelScopeResolver} is the
12
+ * one that answers, here and in the pass. What this module adds is the way in.
13
+ */
14
+ import type { ResourceManifest } from "@telorun/sdk";
15
+ import type { Environment } from "@marcbachmann/cel-js";
16
+ import { AliasResolver } from "./alias-resolver.js";
17
+ import { type CelScope } from "./cel-scope.js";
18
+ import { DefinitionRegistry } from "./definition-registry.js";
19
+ /** Where a CEL context binding was declared: the manifest that declares it,
20
+ * by identity, plus the concrete path within it. Identity rather than the
21
+ * object because a host locates a manifest in its own loaded files, which is
22
+ * what carries the source ranges. */
23
+ export interface ContextDeclarationSite {
24
+ kind: string;
25
+ name: string;
26
+ path: string;
27
+ }
28
+ /** The analyzer state a query resolves against — what an `AnalysisRegistry`
29
+ * already holds, plus the manifest set the caller analyzed. */
30
+ export interface CelScopeQueryContext {
31
+ defs: DefinitionRegistry;
32
+ aliases: AliasResolver;
33
+ aliasesByModule: Map<string, AliasResolver>;
34
+ }
35
+ /**
36
+ * A reusable scope query over one manifest set.
37
+ *
38
+ * Built once per analysis rather than per keystroke: the observed-state index
39
+ * and the kernel globals are a function of the whole set, and rebuilding them
40
+ * for every cursor move would put a full-set walk on the hover path.
41
+ */
42
+ export declare class CelScopeQuery {
43
+ private readonly manifests;
44
+ private readonly resolver;
45
+ /** The resource the resolver is currently entered on — `enterResource` is the
46
+ * per-resource half of the rule, so re-entering per query is only needed when
47
+ * the cursor moves to a different resource. */
48
+ private entered;
49
+ /** Resolved scopes, keyed by resource and path. A context-matched site builds
50
+ * a fresh typed environment by design (a clone plus a re-registration of
51
+ * every variable), which is affordable once per site in a batch pass and not
52
+ * once per site per KEYSTROKE — which is what a whole-file colourizer asks
53
+ * for. The lifetime is this query's, which is the analysis's. */
54
+ private readonly scopeCache;
55
+ constructor(manifests: ResourceManifest[], ctx: CelScopeQueryContext, celEnv?: Environment);
56
+ private readonly ctx;
57
+ /** The manifest a cursor's document addresses — {@link findManifest}, the one
58
+ * implementation `ManifestAnalysis` also answers from. */
59
+ resourceFor(kind: string | undefined, name: string | undefined): ResourceManifest | undefined;
60
+ /**
61
+ * What CEL at `path` in `resource` is typed against.
62
+ *
63
+ * `path` is the CONCRETE path, indices and all (`routes[0].handler.url`) —
64
+ * an `x-telo-context` region, an error-bearing branch and a step's identity
65
+ * are each addressed per item, so an index-erased path resolves the wrong
66
+ * scope or none.
67
+ */
68
+ scopeAt(resource: ResourceManifest, path: string): CelScope;
69
+ /**
70
+ * Where a CEL context binding was DECLARED — the manifest node the
71
+ * `x-telo-context-*` annotation derived it from.
72
+ *
73
+ * The mirror of {@link scopeAt}: that one resolves the annotation into a
74
+ * schema and the provenance is gone by the time it returns, while
75
+ * go-to-declaration wants the path and not the schema. Re-walking the
76
+ * annotation is what keeps `CelScope` free of an origin field every type
77
+ * consumer would have to ignore.
78
+ *
79
+ * Generic over the annotation, so `request.query` lands on the route's own
80
+ * `request.schema.query`, `self.<field>` on the definition's `schema`, and
81
+ * `result.<field>` on the INVOKED resource's `outputType` — one walk, no
82
+ * transport and no resource kind named here.
83
+ *
84
+ * Every candidate path is checked against the manifest before it is returned,
85
+ * so a binding the author never declared (a context annotation's static
86
+ * fallback properties) resolves to nothing rather than to a guessed node.
87
+ */
88
+ contextDeclarationSite(resource: ResourceManifest, sitePath: string, parts: string[]): ContextDeclarationSite | undefined;
89
+ /** The manifest and path an annotated context property is derived from, and
90
+ * whether that node holds a property MAP (names directly) or a JSON Schema. */
91
+ private originOf;
92
+ /**
93
+ * Where the step named `stepName` is declared in `resource`, as a concrete
94
+ * path (`steps[2]`), or undefined when the resource declares no step body or
95
+ * holds no such step.
96
+ *
97
+ * For go-to-declaration on `steps.<name>.result`, which is the one CEL scope
98
+ * whose members ARE written somewhere in the manifest but are reached through
99
+ * no reference slot. Driven by the kind's own step-body annotation and the
100
+ * shared nesting walk, so a step inside a `try:` inside a `catch:` is found
101
+ * and no resource kind is named here.
102
+ */
103
+ stepDeclarationPath(resource: ResourceManifest, stepName: string): string | undefined;
104
+ private definitionFor;
105
+ /** The `x-telo-context` region this path falls in, matched exactly as the
106
+ * manifest visitor matches it for an expression it walked onto. */
107
+ private matchContext;
108
+ }
109
+ //# sourceMappingURL=cel-scope-query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cel-scope-query.d.ts","sourceRoot":"","sources":["../src/cel-scope-query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAqB,MAAM,qBAAqB,CAAC;AAEvE,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAiB9D;;;sCAGsC;AACtC,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAoBD;gEACgE;AAChE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,aAAa,CAAC;IACvB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAC7C;AAED;;;;;;GAMG;AACH,qBAAa,aAAa;IActB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAb5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C;;oDAEgD;IAChD,OAAO,CAAC,OAAO,CAA+B;IAC9C;;;;sEAIkE;IAClE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsD;gBAG9D,SAAS,EAAE,gBAAgB,EAAE,EAC9C,GAAG,EAAE,oBAAoB,EACzB,MAAM,CAAC,EAAE,WAAW;IAgCtB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAuB;IAE3C;+DAC2D;IAC3D,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS;IAI7F;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ;IAe3D;;;;;;;;;;;;;;;;;;OAkBG;IACH,sBAAsB,CACpB,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EAAE,GACd,sBAAsB,GAAG,SAAS;IAqCrC;oFACgF;IAChF,OAAO,CAAC,QAAQ;IAgEhB;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAqBrF,OAAO,CAAC,aAAa;IAMrB;wEACoE;IACpE,OAAO,CAAC,YAAY;CAarB"}
@@ -0,0 +1,270 @@
1
+ import { buildCelEnvironment } from "./cel-environment.js";
2
+ import { CelScopeResolver } from "./cel-scope.js";
3
+ import { buildKernelGlobalsIndex } from "./kernel-globals.js";
4
+ import { isModuleKind } from "./module-kinds.js";
5
+ import { navigateConcretePath } from "./manifest-path.js";
6
+ import { findManifest } from "./find-manifest.js";
7
+ import { resolveLocalRef, walkStepArray } from "./schema-walk.js";
8
+ import { readStepSlot } from "./step-slot.js";
9
+ import { buildObservedStateIndex, buildObservedStateResourcesSchema, } from "./validate-observed-state.js";
10
+ import { extractContextsFromSchema, getManifestItem, pathMatchesScope, } from "./validate-cel-context.js";
11
+ /** Join a concrete path segment, tolerating an empty base (the manifest root). */
12
+ function joinPath(base, segment) {
13
+ return base ? `${base}.${segment}` : segment;
14
+ }
15
+ /** The concrete path of the array ITEM an `x-telo-context` scope matched — the
16
+ * path half of `getManifestItem`, which returns only the value. Empty when the
17
+ * scope is not per-item. */
18
+ function manifestItemPath(exprPath, scope) {
19
+ if (!scope)
20
+ return "";
21
+ const stripped = scope.startsWith("$.") ? scope.slice(2) : scope;
22
+ const wildcard = stripped.indexOf("[*]");
23
+ if (wildcard === -1)
24
+ return "";
25
+ const arrayProp = stripped.slice(0, wildcard);
26
+ const match = exprPath.match(new RegExp(`^${arrayProp}\\[(\\d+)\\]`));
27
+ return match ? `${arrayProp}[${match[1]}]` : "";
28
+ }
29
+ /**
30
+ * A reusable scope query over one manifest set.
31
+ *
32
+ * Built once per analysis rather than per keystroke: the observed-state index
33
+ * and the kernel globals are a function of the whole set, and rebuilding them
34
+ * for every cursor move would put a full-set walk on the hover path.
35
+ */
36
+ export class CelScopeQuery {
37
+ manifests;
38
+ resolver;
39
+ /** The resource the resolver is currently entered on — `enterResource` is the
40
+ * per-resource half of the rule, so re-entering per query is only needed when
41
+ * the cursor moves to a different resource. */
42
+ entered;
43
+ /** Resolved scopes, keyed by resource and path. A context-matched site builds
44
+ * a fresh typed environment by design (a clone plus a re-registration of
45
+ * every variable), which is affordable once per site in a batch pass and not
46
+ * once per site per KEYSTROKE — which is what a whole-file colourizer asks
47
+ * for. The lifetime is this query's, which is the analysis's. */
48
+ scopeCache = new Map();
49
+ constructor(manifests, ctx, celEnv) {
50
+ this.manifests = manifests;
51
+ const { defs, aliases, aliasesByModule } = ctx;
52
+ const rootModules = new Set();
53
+ for (const m of manifests) {
54
+ if (isModuleKind(m.kind) && m.metadata?.name)
55
+ rootModules.add(m.metadata.name);
56
+ }
57
+ const scopes = { aliasesByModule, rootModules };
58
+ const observedState = buildObservedStateIndex(manifests, defs, aliases, scopes);
59
+ const reportsObservedState = [...observedState.values()].some((r) => r.status);
60
+ this.resolver = new CelScopeResolver({
61
+ celEnv: celEnv ?? buildCelEnvironment(),
62
+ defs,
63
+ aliases,
64
+ scopes,
65
+ allManifests: manifests,
66
+ kernelGlobals: buildKernelGlobalsIndex(manifests, observedState),
67
+ moduleManifest: manifests.find((mm) => mm.kind === "Telo.Application") ??
68
+ manifests.find((mm) => mm.kind === "Telo.Library"),
69
+ observedStateContext: reportsObservedState
70
+ ? {
71
+ type: "object",
72
+ additionalProperties: true,
73
+ properties: { resources: buildObservedStateResourcesSchema(observedState, true) },
74
+ }
75
+ : null,
76
+ });
77
+ this.ctx = ctx;
78
+ }
79
+ ctx;
80
+ /** The manifest a cursor's document addresses — {@link findManifest}, the one
81
+ * implementation `ManifestAnalysis` also answers from. */
82
+ resourceFor(kind, name) {
83
+ return findManifest(this.manifests, kind, name);
84
+ }
85
+ /**
86
+ * What CEL at `path` in `resource` is typed against.
87
+ *
88
+ * `path` is the CONCRETE path, indices and all (`routes[0].handler.url`) —
89
+ * an `x-telo-context` region, an error-bearing branch and a step's identity
90
+ * are each addressed per item, so an index-erased path resolves the wrong
91
+ * scope or none.
92
+ */
93
+ scopeAt(resource, path) {
94
+ let byPath = this.scopeCache.get(resource);
95
+ if (!byPath)
96
+ this.scopeCache.set(resource, (byPath = new Map()));
97
+ const cached = byPath.get(path);
98
+ if (cached)
99
+ return cached;
100
+ if (this.entered !== resource) {
101
+ this.resolver.enterResource(resource, this.definitionFor(resource));
102
+ this.entered = resource;
103
+ }
104
+ const { contextSchema, matchedScope } = this.matchContext(resource, path);
105
+ const scope = this.resolver.scopeFor({ source: resource, path, contextSchema, matchedScope });
106
+ byPath.set(path, scope);
107
+ return scope;
108
+ }
109
+ /**
110
+ * Where a CEL context binding was DECLARED — the manifest node the
111
+ * `x-telo-context-*` annotation derived it from.
112
+ *
113
+ * The mirror of {@link scopeAt}: that one resolves the annotation into a
114
+ * schema and the provenance is gone by the time it returns, while
115
+ * go-to-declaration wants the path and not the schema. Re-walking the
116
+ * annotation is what keeps `CelScope` free of an origin field every type
117
+ * consumer would have to ignore.
118
+ *
119
+ * Generic over the annotation, so `request.query` lands on the route's own
120
+ * `request.schema.query`, `self.<field>` on the definition's `schema`, and
121
+ * `result.<field>` on the INVOKED resource's `outputType` — one walk, no
122
+ * transport and no resource kind named here.
123
+ *
124
+ * Every candidate path is checked against the manifest before it is returned,
125
+ * so a binding the author never declared (a context annotation's static
126
+ * fallback properties) resolves to nothing rather than to a guessed node.
127
+ */
128
+ contextDeclarationSite(resource, sitePath, parts) {
129
+ if (parts.length < 2)
130
+ return undefined;
131
+ const { contextSchema, matchedScope } = this.matchContext(resource, sitePath);
132
+ const annotated = contextSchema?.properties?.[parts[0]];
133
+ if (!annotated)
134
+ return undefined;
135
+ const origin = this.originOf(annotated, resource, sitePath, matchedScope);
136
+ if (!origin)
137
+ return undefined;
138
+ // The ORIGIN decides the shape rather than positional fallthrough. A
139
+ // property-map origin holds the names themselves, so its first hop is direct
140
+ // and ONLY direct — trying `properties` there would let an author's own
141
+ // `properties:` key win over the name they wrote. Every deeper hop, and
142
+ // every hop of a JSON-Schema origin, goes through `properties`, with the
143
+ // inline `{ kind, schema }` wrapper as the one alternative a type field is
144
+ // routinely written as.
145
+ let base = origin.path;
146
+ for (let i = 1; i < parts.length; i++) {
147
+ const candidates = i === 1 && origin.propertyMap
148
+ ? [joinPath(base, parts[i])]
149
+ : [
150
+ joinPath(joinPath(base, "properties"), parts[i]),
151
+ joinPath(joinPath(joinPath(base, "schema"), "properties"), parts[i]),
152
+ ];
153
+ const hit = candidates.find((candidate) => navigateConcretePath(origin.manifest, candidate) !== undefined);
154
+ if (!hit)
155
+ return undefined;
156
+ base = hit;
157
+ }
158
+ const metadata = origin.manifest.metadata;
159
+ if (!origin.manifest.kind || !metadata?.name)
160
+ return undefined;
161
+ return { kind: origin.manifest.kind, name: metadata.name, path: base };
162
+ }
163
+ /** The manifest and path an annotated context property is derived from, and
164
+ * whether that node holds a property MAP (names directly) or a JSON Schema. */
165
+ originOf(annotated, resource, sitePath, matchedScope) {
166
+ const root = resource;
167
+ // Per-scope: the annotation navigates the enclosing ARRAY ITEM, so the path
168
+ // it yields is relative to that item rather than to the resource.
169
+ const from = annotated["x-telo-context-from"];
170
+ if (typeof from === "string") {
171
+ const itemPath = manifestItemPath(sitePath, matchedScope);
172
+ return { manifest: root, path: joinPath(itemPath, from.split("/").join(".")), propertyMap: true };
173
+ }
174
+ const fromRoot = annotated["x-telo-context-from-root"];
175
+ if (typeof fromRoot === "string") {
176
+ return { manifest: root, path: fromRoot.split("/").join("."), propertyMap: false };
177
+ }
178
+ // Cross-manifest: the binding is declared by whatever this slot REFERENCES,
179
+ // which is the node a reader wants when a result's members do not resolve.
180
+ const refFrom = annotated["x-telo-context-ref-from"];
181
+ if (typeof refFrom === "string") {
182
+ const slash = refFrom.indexOf("/");
183
+ if (slash === -1)
184
+ return undefined;
185
+ const item = matchedScope
186
+ ? getManifestItem(sitePath, matchedScope, root)
187
+ : root;
188
+ const ref = item[refFrom.slice(0, slash)];
189
+ if (!ref?.kind || !ref.name)
190
+ return undefined;
191
+ const target = this.manifests.find((m) => m.kind === ref.kind && m.metadata?.name === ref.name);
192
+ if (!target)
193
+ return undefined;
194
+ return { manifest: target, path: refFrom.slice(slash + 1).split("/").join("."), propertyMap: false };
195
+ }
196
+ // A kind's own declaration: the target is the `Telo.Definition` document,
197
+ // which is an ordinary manifest in the set.
198
+ const fromRefKind = annotated["x-telo-context-from-ref-kind"];
199
+ const first = Array.isArray(fromRefKind) ? fromRefKind[0] : fromRefKind;
200
+ if (typeof first === "string") {
201
+ const hash = first.indexOf("#");
202
+ if (hash <= 0)
203
+ return undefined;
204
+ const kindValue = navigateConcretePath(root, first.slice(0, hash).split("/").join("."));
205
+ if (typeof kindValue !== "string")
206
+ return undefined;
207
+ const canonical = this.ctx.aliases.resolveKind(kindValue) ?? kindValue;
208
+ const suffix = canonical.slice(canonical.indexOf(".") + 1);
209
+ const target = this.manifests.find((m) => (m.kind === "Telo.Definition" || m.kind === "Telo.Abstract") &&
210
+ m.metadata?.name === suffix);
211
+ if (!target)
212
+ return undefined;
213
+ return { manifest: target, path: first.slice(hash + 1), propertyMap: false };
214
+ }
215
+ // `x-telo-context-element-from` / `-collection-from` type a binding from an
216
+ // EXPRESSION, so there is no declaration to navigate to.
217
+ return undefined;
218
+ }
219
+ /**
220
+ * Where the step named `stepName` is declared in `resource`, as a concrete
221
+ * path (`steps[2]`), or undefined when the resource declares no step body or
222
+ * holds no such step.
223
+ *
224
+ * For go-to-declaration on `steps.<name>.result`, which is the one CEL scope
225
+ * whose members ARE written somewhere in the manifest but are reached through
226
+ * no reference slot. Driven by the kind's own step-body annotation and the
227
+ * shared nesting walk, so a step inside a `try:` inside a `catch:` is found
228
+ * and no resource kind is named here.
229
+ */
230
+ stepDeclarationPath(resource, stepName) {
231
+ const schema = this.definitionFor(resource)?.schema;
232
+ const props = schema?.properties;
233
+ if (!schema || !props)
234
+ return undefined;
235
+ for (const [fieldName, fieldSchema] of Object.entries(props)) {
236
+ if (!readStepSlot(fieldSchema))
237
+ continue;
238
+ const steps = resource[fieldName];
239
+ if (!Array.isArray(steps))
240
+ continue;
241
+ const itemSchema = resolveLocalRef(fieldSchema.items, schema);
242
+ let found;
243
+ walkStepArray(steps, itemSchema, schema, fieldName, (step, stepPath) => {
244
+ if (found === undefined && step.name === stepName)
245
+ found = stepPath;
246
+ });
247
+ if (found)
248
+ return found;
249
+ }
250
+ return undefined;
251
+ }
252
+ definitionFor(resource) {
253
+ const { defs, aliases } = this.ctx;
254
+ const canonical = aliases.resolveKind(resource.kind);
255
+ return defs.resolve(resource.kind) ?? (canonical ? defs.resolve(canonical) : undefined);
256
+ }
257
+ /** The `x-telo-context` region this path falls in, matched exactly as the
258
+ * manifest visitor matches it for an expression it walked onto. */
259
+ matchContext(resource, path) {
260
+ const schema = this.definitionFor(resource)?.schema;
261
+ if (!schema)
262
+ return {};
263
+ for (const ctx of extractContextsFromSchema(schema)) {
264
+ if (pathMatchesScope(path, ctx.scope)) {
265
+ return { contextSchema: ctx.schema, matchedScope: ctx.scope };
266
+ }
267
+ }
268
+ return {};
269
+ }
270
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * **What is in scope for CEL at one expression site.**
3
+ *
4
+ * A CEL expression is typed against an environment and a resolved context
5
+ * schema that depend on WHERE it sits: the kind's step body contributes
6
+ * `steps.<name>.result`, an error-bearing branch contributes `error`, a
7
+ * `x-telo-bindings-from` field contributes its named bindings, an
8
+ * `x-telo-context` region contributes its own scope resolved against the
9
+ * enclosing array item, and the kernel globals are merged in per declaring
10
+ * module. That assembly used to live inline in the analysis pass, built for one
11
+ * `engine.analyze` call and discarded — so nothing outside the pass could ask
12
+ * what a cursor sees.
13
+ *
14
+ * It is a QUERY here, and the pass is one of its callers. The other is the IDE:
15
+ * completion, hover and signature help must offer exactly the names
16
+ * `telo check` accepts, and two implementations of an open, growing scope rule
17
+ * cannot be held in agreement by tests.
18
+ *
19
+ * Nothing here reports a diagnostic. The pass keeps every check it ever had;
20
+ * what moved is the answer both halves need first.
21
+ */
22
+ import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
23
+ import type { Environment } from "@marcbachmann/cel-js";
24
+ import { AliasResolver, type ModuleScopes } from "./alias-resolver.js";
25
+ import { DefinitionRegistry } from "./definition-registry.js";
26
+ import { type KernelGlobalsIndex } from "./kernel-globals.js";
27
+ /** Returns a "resolver-facing" view of the manifest where the fields used as
28
+ * navigation roots by Telo.Definition's `x-telo-context-from-root` annotations
29
+ * have been pre-augmented:
30
+ * - `schema` → augmented `self` schema (synthetic `name`/`kind`/metadata).
31
+ * - `inputType` → resolved through the shared contract resolver, so
32
+ * `x-telo-context-from-root: inputType` substitutes the
33
+ * real signature. Without it the annotation would replace
34
+ * the node verbatim with the inline `{kind, schema}` wrapper
35
+ * the standard library writes everywhere, typing `inputs` as
36
+ * `{kind, schema}` instead of the declared properties.
37
+ *
38
+ * For non-definition manifests the original object is returned. */
39
+ export declare function manifestRootForResolver(m: Record<string, any>, defs: DefinitionRegistry, aliases: AliasResolver, allManifests: Record<string, any>[], scopes: ModuleScopes): Record<string, any>;
40
+ /**
41
+ * Build a `steps` context schema for a kind's step body.
42
+ * Walks each step in the manifest array, resolves the invoked resource's output
43
+ * contract, and builds `steps.<name>.result` context entries.
44
+ *
45
+ * Resolution is the shared {@link resolveContract} — the invoked resource
46
+ * manifest's own declaration, then the kind's, resolved to the nearest
47
+ * declaration along `extends`, then permissive. Sharing it with the kernel is
48
+ * what stops `telo check` from typing `steps.X.result` against one contract
49
+ * while dispatch validates against another.
50
+ *
51
+ * The kind layer is what makes `x-telo-stream` properties on definitions
52
+ * actually govern step-result chain validation — without it, the validator falls
53
+ * back to permissive and the stream-opacity rule never fires.
54
+ *
55
+ * Recursion into nested step arrays is annotation-driven via
56
+ * `x-telo-topology-role`. The analyzer recognises three role values:
57
+ * - `branch` — value is an array of steps (e.g. then / else / do / catch).
58
+ * - `branch-list`— value is an array of objects each carrying further roled
59
+ * sub-properties (e.g. elseif: [{ if, then }]).
60
+ * - `case-map` — value is an object whose values are step arrays (e.g. cases).
61
+ * No specific Run.Sequence field name is hardcoded; any kind that uses
62
+ * a step body and tags its branch fields with these roles works.
63
+ */
64
+ export declare function buildStepContextSchema(manifest: Record<string, any>, defSchema: Record<string, any>, allManifests: Record<string, any>[], defs: DefinitionRegistry, aliases: AliasResolver, scopes: ModuleScopes): Record<string, any> | undefined;
65
+ export declare function collectErrorContextScopes(defSchema: Record<string, any> | undefined): Map<string, Record<string, any>>;
66
+ /**
67
+ * Return the error-context schema for a CEL `path` when the path lies within
68
+ * (any depth under) one of the error-bearing fields, else undefined. A path is
69
+ * "within" field `f` when it contains a segment `f[<index>]`. When multiple
70
+ * error-bearing fields match (e.g. a `finally` nested inside a `catch`), the
71
+ * deepest — the one whose segment appears latest in the path — wins, so the
72
+ * innermost branch's schema governs.
73
+ */
74
+ export declare function errorContextForPath(path: string, scopes: Map<string, Record<string, any>>): Record<string, any> | undefined;
75
+ /** Add a kind's named bindings to a resolved context, when the context declares
76
+ * a bindings region. They go UNDER the context's own properties: a scope
77
+ * variable wins over a same-named binding at runtime, so static typing has to
78
+ * agree (the collision itself is `BINDING_NAME_RESERVED`). */
79
+ export declare function withBindingNames(contextSchema: Record<string, any>, resource: Record<string, any>): Record<string, any>;
80
+ /**
81
+ * What one CEL expression is typed against.
82
+ *
83
+ * Both halves, never a flattened name list: the environment answers "what
84
+ * names exist and what type does this expression have", the context schema
85
+ * answers "what shape does that name carry" — which is what a hover tooltip and
86
+ * a member completion are made of, and what a name list throws away.
87
+ */
88
+ export interface CelScope {
89
+ /** The environment typed for this expression's path. */
90
+ env: Environment;
91
+ /** The resolved `x-telo-context` schema merged with the kernel globals, or
92
+ * null when no context applied (the environment alone types the site). */
93
+ contextSchema: Record<string, any> | null;
94
+ }
95
+ /** The analyzer state a scope is resolved against — everything a manifest set
96
+ * contributes, gathered once per analysis. */
97
+ export interface CelScopeInputs {
98
+ /** The base (untyped) CEL environment. */
99
+ celEnv: Environment;
100
+ defs: DefinitionRegistry;
101
+ aliases: AliasResolver;
102
+ scopes: ModuleScopes;
103
+ allManifests: ResourceManifest[];
104
+ kernelGlobals: KernelGlobalsIndex;
105
+ /** The module doc carrying the Application-only `ports` namespace. */
106
+ moduleManifest?: ResourceManifest;
107
+ /** The observed-state-only context, used where no `x-telo-context` matched. */
108
+ observedStateContext: Record<string, any> | null;
109
+ }
110
+ /** One CEL site, as the manifest visitor reports it. Structural on purpose —
111
+ * the resolver takes the fields it reads, not the visitor's event type, so a
112
+ * caller that located a site some other way (a cursor in an editor buffer) can
113
+ * ask the same question. */
114
+ export interface CelSiteRef {
115
+ source: ResourceManifest;
116
+ path: string;
117
+ contextSchema?: Record<string, any>;
118
+ matchedScope?: string;
119
+ }
120
+ /**
121
+ * The CEL scope rule, applied per resource and then per expression.
122
+ *
123
+ * Stateful across a resource because the per-resource half — the step context,
124
+ * the error-bearing regions, the invocation context — is derived from the kind's
125
+ * schema once and read by every expression in that resource. `enterResource`
126
+ * establishes it; `scopeFor` answers for one path.
127
+ */
128
+ export declare class CelScopeResolver {
129
+ private readonly inputs;
130
+ private readonly typedEnvByManifest;
131
+ /** Per-resource state, replaced at each `enterResource`. */
132
+ private stepContext;
133
+ private invocationContext;
134
+ private errorScopes;
135
+ constructor(inputs: CelScopeInputs);
136
+ /** The `steps` context schema for the current resource, or undefined when its
137
+ * kind declares no step body. Exposed because the step-inputs check needs the
138
+ * same schema this resource's expressions are typed against — recomputing it
139
+ * there is how the two would come to disagree. */
140
+ get stepContextSchema(): Record<string, any> | undefined;
141
+ /** The error-bearing regions the current resource's kind declares. */
142
+ get errorContextScopes(): ReadonlyMap<string, Record<string, any>>;
143
+ /** The resource-wide invocation context, when this resource is an inline
144
+ * declaration that carries one. Read by the "CEL in a non-eval field" check:
145
+ * such a resource's CEL is evaluated by the enclosing kind, not by an
146
+ * `x-telo-eval` annotation of its own. */
147
+ get invocationContextSchema(): Record<string, any> | undefined;
148
+ /** Establish the per-resource half of the scope. */
149
+ enterResource(m: ResourceManifest, definition: ResourceDefinition | undefined): void;
150
+ /**
151
+ * What the expression at `site` is typed against.
152
+ *
153
+ * The environment is cached per manifest when no context applied, which is
154
+ * most expressions: it then depends only on the manifest, so rebuilding it per
155
+ * expression is pure waste — a clone plus a re-registration of every variable,
156
+ * on every keystroke in the IDE. A matched context makes it path-specific (its
157
+ * schema is resolved against the enclosing array item), so those build fresh
158
+ * rather than risk one item's types leaking into another's.
159
+ */
160
+ scopeFor(site: CelSiteRef): CelScope;
161
+ /** The context schema in force at `site`: the matched `x-telo-context` (or the
162
+ * resource-wide invocation context), plus the step and error regions this
163
+ * path falls in, resolved and merged with the kernel globals. */
164
+ private resolveContextFor;
165
+ }
166
+ //# sourceMappingURL=cel-scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cel-scope.d.ts","sourceRoot":"","sources":["../src/cel-scope.ts"],"names":[],"mappings":"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"}