@telorun/analyzer 0.46.0 → 0.47.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.
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AA6B9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAyyB/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IA4hCvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAmBrB,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,GACzB;QAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAsB5F"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AA6B9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAyyB/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IA4hCvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAyBrB,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,GACzB;QAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAsB5F"}
package/dist/analyzer.js CHANGED
@@ -973,7 +973,7 @@ export class StaticAnalyzer {
973
973
  // {kind, name} objects so downstream phases (validation, dependency graph,
974
974
  // kernel controllers) see a uniform shape. Runs after normalize so both
975
975
  // original and inline-extracted manifests have their sentinels resolved.
976
- resolveRefSentinels(allManifests, aliases, aliasesByModule);
976
+ resolveRefSentinels(allManifests, aliases, aliasesByModule, [], defs);
977
977
  // Phase 2.6: register each named `Telo.Type` resource's schema under its
978
978
  // canonical module-scoped id (`telo://<module>/<name>`), validate
979
979
  // `telo://Self|Alias/Type` schema refs resolve to one, then rewrite those
@@ -1642,7 +1642,7 @@ export class StaticAnalyzer {
1642
1642
  // Resolve !ref sentinels after normalize so both the original and
1643
1643
  // inline-extracted manifests get their refs canonicalized to
1644
1644
  // {kind, name} for the kernel that consumes this output.
1645
- resolveRefSentinels(normalized, ctx.aliases, ctx.aliasesByModule, crossModuleTargets ?? []);
1645
+ resolveRefSentinels(normalized, ctx.aliases, ctx.aliasesByModule, crossModuleTargets ?? [], ctx.definitions);
1646
1646
  // Canonicalize import-scoped schema `$ref`s (`telo://Self|Alias/Type`) so the
1647
1647
  // kernel that executes this output compiles inputs/outputs against the same
1648
1648
  // ids the type controllers register their schemas under.
@@ -1,5 +1,13 @@
1
1
  import type { ResourceManifest } from "@telorun/sdk";
2
2
  import type { AliasResolver } from "./alias-resolver.js";
3
+ import { type ReferenceFieldMap } from "./reference-field-map.js";
4
+ /** The slice of the definition registry this pass needs: a kind's field map, from
5
+ * which the `x-telo-scope` slots are read. */
6
+ export interface ScopeFieldMapSource {
7
+ getFieldMapForKind(kind: string, aliases?: {
8
+ resolveKind(k: string): string | undefined;
9
+ }): ReferenceFieldMap | undefined;
10
+ }
3
11
  /**
4
12
  * Rewrites every `!ref <name>` sentinel in each non-system resource's value tree
5
13
  * to `{kind, name}` (local) or `{kind, name, alias}` (cross-module), in place.
@@ -36,5 +44,9 @@ import type { AliasResolver } from "./alias-resolver.js";
36
44
  * `metadata.module` that isn't a root module) are resolution TARGETS only — they are not
37
45
  * re-walked as sources here, since their own ref slots belong to their own module scope.
38
46
  */
39
- export declare function resolveRefSentinels(resources: ResourceManifest[], aliases?: AliasResolver, aliasesByModule?: Map<string, AliasResolver>, crossModuleTargets?: ResourceManifest[]): void;
47
+ export declare function resolveRefSentinels(resources: ResourceManifest[], aliases?: AliasResolver, aliasesByModule?: Map<string, AliasResolver>, crossModuleTargets?: ResourceManifest[],
48
+ /** Supplies each kind's `x-telo-scope` slots. Without it a scoped name cannot be
49
+ * told from a module-level one, and a shadowed `!ref` resolves to the resource
50
+ * it shadows — so both call sites pass it. */
51
+ defs?: ScopeFieldMapSource): void;
40
52
  //# sourceMappingURL=resolve-ref-sentinels.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-ref-sentinels.d.ts","sourceRoot":"","sources":["../src/resolve-ref-sentinels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAOzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAK5C,kBAAkB,GAAE,gBAAgB,EAAO,GAC1C,IAAI,CAgFN"}
1
+ {"version":3,"file":"resolve-ref-sentinels.d.ts","sourceRoot":"","sources":["../src/resolve-ref-sentinels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAGlC;+CAC+C;AAC/C,MAAM,WAAW,mBAAmB;IAClC,kBAAkB,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,GACvD,iBAAiB,GAAG,SAAS,CAAC;CAClC;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAK5C,kBAAkB,GAAE,gBAAgB,EAAO;AAC3C;;+CAE+C;AAC/C,IAAI,CAAC,EAAE,mBAAmB,GACzB,IAAI,CA8HN"}
@@ -1,4 +1,5 @@
1
1
  import { isRefSentinel, isTaggedSentinel } from "@telorun/templating";
2
+ import { isScopeEntry, resolveFieldEntries, } from "./reference-field-map.js";
2
3
  import { REF_RESOLUTION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
3
4
  /**
4
5
  * Rewrites every `!ref <name>` sentinel in each non-system resource's value tree
@@ -41,7 +42,11 @@ export function resolveRefSentinels(resources, aliases, aliasesByModule,
41
42
  // walked as sources). The kernel passes the analyzer-flattened set here so the runtime
42
43
  // pass — which loads the entry module only — can still resolve `!ref Alias.name` against
43
44
  // imported libraries' exported instances.
44
- crossModuleTargets = []) {
45
+ crossModuleTargets = [],
46
+ /** Supplies each kind's `x-telo-scope` slots. Without it a scoped name cannot be
47
+ * told from a module-level one, and a shadowed `!ref` resolves to the resource
48
+ * it shadows — so both call sites pass it. */
49
+ defs) {
45
50
  const moduleOf = (r) => r.metadata?.module;
46
51
  // Forwarded exports are flagged by flattenForAnalyzer (`metadata.forwardedExport`); they're
47
52
  // cross-module resolution targets only — never walked as local ref sources here.
@@ -96,11 +101,50 @@ crossModuleTargets = []) {
96
101
  }
97
102
  return undefined;
98
103
  };
104
+ /** Names a resource declares in its own execution scopes, read from the kind's
105
+ * `x-telo-scope` slots — the analyzer's single definition of "scope", shared
106
+ * with `manifest-visitor`. Inferring it structurally instead (any array of
107
+ * named inline manifests) would give scope-local shadowing to the first kind
108
+ * that happens to carry such an array without asking for it, and this pass is
109
+ * shared with the kernel, so the guess would be baked into the runtime tree
110
+ * rather than merely reported. */
111
+ const declaredInScopes = (resource) => {
112
+ const fieldMap = defs?.getFieldMapForKind(resource.kind, aliases);
113
+ if (!fieldMap)
114
+ return undefined;
115
+ let declared;
116
+ for (const [fieldPath, entry] of fieldMap) {
117
+ if (!isScopeEntry(entry))
118
+ continue;
119
+ for (const { value } of resolveFieldEntries(resource, fieldPath)) {
120
+ for (const element of Array.isArray(value) ? value : [value]) {
121
+ if (!element || typeof element !== "object" || Array.isArray(element))
122
+ continue;
123
+ const manifest = element;
124
+ const name = manifest.metadata?.name;
125
+ if (typeof manifest.kind === "string" && typeof name === "string") {
126
+ (declared ??= new Map()).set(name, manifest);
127
+ }
128
+ }
129
+ }
130
+ }
131
+ return declared;
132
+ };
99
133
  // Resolve every `!ref` sentinel in the tree; leave opaque tagged / precompiled
100
134
  // nodes (e.g. `!cel`) untouched and don't descend into them.
101
- const walk = (value) => {
135
+ //
136
+ // `scoped` carries the names the enclosing resource declares in its `x-telo-scope`
137
+ // slots, and they SHADOW the module-level ones — the order the runtime resolves
138
+ // in. Baking the module-level kind into a shadowed reference would label traces
139
+ // and `getRefIdentity` with a resource that never runs.
140
+ const walk = (value, scoped) => {
102
141
  if (isRefSentinel(value)) {
103
- return resolveTarget(value.source) ?? value;
142
+ const source = value.source;
143
+ const bare = source.indexOf(".") === -1;
144
+ const shadow = bare ? scoped?.get(source) : undefined;
145
+ if (shadow)
146
+ return { kind: shadow.kind, name: source };
147
+ return resolveTarget(source) ?? value;
104
148
  }
105
149
  if (value === null || typeof value !== "object")
106
150
  return value;
@@ -110,12 +154,19 @@ crossModuleTargets = []) {
110
154
  return value;
111
155
  if (Array.isArray(value)) {
112
156
  for (let i = 0; i < value.length; i++)
113
- value[i] = walk(value[i]);
157
+ value[i] = walk(value[i], scoped);
114
158
  return value;
115
159
  }
116
160
  const obj = value;
161
+ // A nested inline resource may declare scopes of its own (a `Run.Sequence`
162
+ // inside another sequence's `with:`). Collected before descending, so the
163
+ // declarations are visible to every region of the resource that declares
164
+ // them — a sequence's `with:` names resolve in its `targets:` and `steps:`
165
+ // alike, not only inside `with:` itself.
166
+ const declared = typeof obj.kind === "string" ? declaredInScopes(obj) : undefined;
167
+ const inner = declared ? new Map([...(scoped ?? new Map()), ...declared]) : scoped;
117
168
  for (const key of Object.keys(obj))
118
- obj[key] = walk(obj[key]);
169
+ obj[key] = walk(obj[key], inner);
119
170
  return value;
120
171
  };
121
172
  for (const r of resources) {
@@ -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;AAGtF,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,EACnC,QAAQ,GAAE,WAAW,CAAC,MAAM,CAAa,GACxC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA0CjC;AAuFD;;;;;;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,CAgHrB;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;AAWD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,SAAM,GACT,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,CAAC,CAGvD;AAUD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,SAAM,GAAG,MAAM,EAAE,CAqBxF"}
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;AAGtF,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,EACnC,QAAQ,GAAE,WAAW,CAAC,MAAM,CAAa,GACxC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA0CjC;AAuFD;;;;;;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,CAmIrB;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;AAWD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,SAAM,GACT,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,CAAC,CAGvD;AAUD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,SAAM,GAAG,MAAM,EAAE,CAqBxF"}
@@ -276,13 +276,29 @@ export function resolveContextAnnotations(schema, manifestItem, opts) {
276
276
  typeof ref.kind === "string" &&
277
277
  typeof ref.name === "string" &&
278
278
  subpath) {
279
+ const segments = subpath.split("/");
279
280
  const refManifest = allManifests.find((m) => m.kind === ref.kind && m.metadata?.name === ref.name);
280
281
  if (refManifest) {
281
- const resolved = resolveTypeFieldToSchema(navigatePath(refManifest, subpath.split("/")), allManifests);
282
+ const resolved = resolveTypeFieldToSchema(navigatePath(refManifest, segments), allManifests);
282
283
  if (resolved && typeof resolved === "object") {
283
284
  return resolved;
284
285
  }
285
286
  }
287
+ // The instance declares nothing, so fall back to its KIND's declaration —
288
+ // the same layering `buildStepContextSchema` applies to `steps.<name>.result`,
289
+ // so a kind with one fixed output shape (declared once on its Telo.Definition)
290
+ // types the context, while a kind that exposes the field for per-instance
291
+ // narrowing keeps winning above.
292
+ if (defs) {
293
+ const canonical = aliases?.resolveKind(ref.kind) ?? ref.kind;
294
+ const def = defs.resolve(canonical);
295
+ if (def) {
296
+ const resolved = resolveTypeFieldToSchema(navigatePath(def, segments), allManifests);
297
+ if (resolved && typeof resolved === "object") {
298
+ return resolved;
299
+ }
300
+ }
301
+ }
286
302
  }
287
303
  // Fallback: open schema (no false errors when outputType is not declared)
288
304
  return { ...schema, additionalProperties: true };
@@ -1 +1 @@
1
- {"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAgD/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CA6dtB"}
1
+ {"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAgD/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CAmetB"}
@@ -254,8 +254,14 @@ export function validateReferences(resources, context) {
254
254
  }
255
255
  // Local reference (bare name or explicit `Self.`-qualified).
256
256
  const localName = aliasPrefix === "Self" ? refName.slice(dot + 1) : refName;
257
- const target = byName.get(localName) ??
258
- visibleScopeManifests.find((m) => m.metadata?.name === localName);
257
+ // Scope-local FIRST, enclosing module as the fallback — the order the
258
+ // runtime uses at every name-resolution site (`ScopeContext.getInstance`,
259
+ // `ResourceContext.resolveRef`, and the CEL `resources` layering). Module-first
260
+ // here would validate a shadowed name against the resource the kernel will
261
+ // never bind: a false pass when the outer kind fits and the scoped one does
262
+ // not, a false REFERENCE_KIND_MISMATCH when it is the other way round.
263
+ const target = visibleScopeManifests.find((m) => m.metadata?.name === localName) ??
264
+ byName.get(localName);
259
265
  if (!target) {
260
266
  diagnostics.push({
261
267
  severity: DiagnosticSeverity.Error,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "description": "Telo Analyzer - Static manifest validator for Telo manifests.",
5
5
  "keywords": [
6
6
  "telo",
@@ -48,7 +48,7 @@
48
48
  "@types/node": "^20.0.0",
49
49
  "typescript": "^5.0.0",
50
50
  "vitest": "^2.1.8",
51
- "@telorun/sdk": "0.58.0"
51
+ "@telorun/sdk": "0.59.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@telorun/sdk": "*"
package/src/analyzer.ts CHANGED
@@ -1153,7 +1153,7 @@ export class StaticAnalyzer {
1153
1153
  // {kind, name} objects so downstream phases (validation, dependency graph,
1154
1154
  // kernel controllers) see a uniform shape. Runs after normalize so both
1155
1155
  // original and inline-extracted manifests have their sentinels resolved.
1156
- resolveRefSentinels(allManifests, aliases, aliasesByModule);
1156
+ resolveRefSentinels(allManifests, aliases, aliasesByModule, [], defs);
1157
1157
 
1158
1158
  // Phase 2.6: register each named `Telo.Type` resource's schema under its
1159
1159
  // canonical module-scoped id (`telo://<module>/<name>`), validate
@@ -1957,7 +1957,13 @@ export class StaticAnalyzer {
1957
1957
  // Resolve !ref sentinels after normalize so both the original and
1958
1958
  // inline-extracted manifests get their refs canonicalized to
1959
1959
  // {kind, name} for the kernel that consumes this output.
1960
- resolveRefSentinels(normalized, ctx.aliases, ctx.aliasesByModule, crossModuleTargets ?? []);
1960
+ resolveRefSentinels(
1961
+ normalized,
1962
+ ctx.aliases,
1963
+ ctx.aliasesByModule,
1964
+ crossModuleTargets ?? [],
1965
+ ctx.definitions!,
1966
+ );
1961
1967
  // Canonicalize import-scoped schema `$ref`s (`telo://Self|Alias/Type`) so the
1962
1968
  // kernel that executes this output compiles inputs/outputs against the same
1963
1969
  // ids the type controllers register their schemas under.
@@ -1,8 +1,22 @@
1
1
  import type { ResourceManifest } from "@telorun/sdk";
2
2
  import { isRefSentinel, isTaggedSentinel } from "@telorun/templating";
3
3
  import type { AliasResolver } from "./alias-resolver.js";
4
+ import {
5
+ isScopeEntry,
6
+ resolveFieldEntries,
7
+ type ReferenceFieldMap,
8
+ } from "./reference-field-map.js";
4
9
  import { REF_RESOLUTION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
5
10
 
11
+ /** The slice of the definition registry this pass needs: a kind's field map, from
12
+ * which the `x-telo-scope` slots are read. */
13
+ export interface ScopeFieldMapSource {
14
+ getFieldMapForKind(
15
+ kind: string,
16
+ aliases?: { resolveKind(k: string): string | undefined },
17
+ ): ReferenceFieldMap | undefined;
18
+ }
19
+
6
20
  /** Resolved ref shape written in place of a `!ref` sentinel. `alias` is set only for
7
21
  * cross-module references (resolved into an imported library's exported instance). */
8
22
  type ResolvedRef = { kind: string; name: string; alias?: string };
@@ -52,6 +66,10 @@ export function resolveRefSentinels(
52
66
  // pass — which loads the entry module only — can still resolve `!ref Alias.name` against
53
67
  // imported libraries' exported instances.
54
68
  crossModuleTargets: ResourceManifest[] = [],
69
+ /** Supplies each kind's `x-telo-scope` slots. Without it a scoped name cannot be
70
+ * told from a module-level one, and a shadowed `!ref` resolves to the resource
71
+ * it shadows — so both call sites pass it. */
72
+ defs?: ScopeFieldMapSource,
55
73
  ): void {
56
74
  const moduleOf = (r: ResourceManifest): string | undefined =>
57
75
  (r.metadata as { module?: string } | undefined)?.module;
@@ -109,21 +127,67 @@ export function resolveRefSentinels(
109
127
  return undefined;
110
128
  };
111
129
 
130
+ /** Names a resource declares in its own execution scopes, read from the kind's
131
+ * `x-telo-scope` slots — the analyzer's single definition of "scope", shared
132
+ * with `manifest-visitor`. Inferring it structurally instead (any array of
133
+ * named inline manifests) would give scope-local shadowing to the first kind
134
+ * that happens to carry such an array without asking for it, and this pass is
135
+ * shared with the kernel, so the guess would be baked into the runtime tree
136
+ * rather than merely reported. */
137
+ const declaredInScopes = (
138
+ resource: ResourceManifest,
139
+ ): Map<string, ResourceManifest> | undefined => {
140
+ const fieldMap = defs?.getFieldMapForKind(resource.kind, aliases);
141
+ if (!fieldMap) return undefined;
142
+ let declared: Map<string, ResourceManifest> | undefined;
143
+ for (const [fieldPath, entry] of fieldMap) {
144
+ if (!isScopeEntry(entry)) continue;
145
+ for (const { value } of resolveFieldEntries(resource, fieldPath)) {
146
+ for (const element of Array.isArray(value) ? value : [value]) {
147
+ if (!element || typeof element !== "object" || Array.isArray(element)) continue;
148
+ const manifest = element as ResourceManifest;
149
+ const name = (manifest.metadata as { name?: string } | undefined)?.name;
150
+ if (typeof manifest.kind === "string" && typeof name === "string") {
151
+ (declared ??= new Map()).set(name, manifest);
152
+ }
153
+ }
154
+ }
155
+ }
156
+ return declared;
157
+ };
158
+
112
159
  // Resolve every `!ref` sentinel in the tree; leave opaque tagged / precompiled
113
160
  // nodes (e.g. `!cel`) untouched and don't descend into them.
114
- const walk = (value: unknown): unknown => {
161
+ //
162
+ // `scoped` carries the names the enclosing resource declares in its `x-telo-scope`
163
+ // slots, and they SHADOW the module-level ones — the order the runtime resolves
164
+ // in. Baking the module-level kind into a shadowed reference would label traces
165
+ // and `getRefIdentity` with a resource that never runs.
166
+ const walk = (value: unknown, scoped?: Map<string, ResourceManifest>): unknown => {
115
167
  if (isRefSentinel(value)) {
116
- return resolveTarget(value.source) ?? value;
168
+ const source = value.source;
169
+ const bare = source.indexOf(".") === -1;
170
+ const shadow = bare ? scoped?.get(source) : undefined;
171
+ if (shadow) return { kind: shadow.kind as string, name: source };
172
+ return resolveTarget(source) ?? value;
117
173
  }
118
174
  if (value === null || typeof value !== "object") return value;
119
175
  if (isTaggedSentinel(value)) return value;
120
176
  if ((value as { __compiled?: unknown }).__compiled) return value;
121
177
  if (Array.isArray(value)) {
122
- for (let i = 0; i < value.length; i++) value[i] = walk(value[i]);
178
+ for (let i = 0; i < value.length; i++) value[i] = walk(value[i], scoped);
123
179
  return value;
124
180
  }
125
181
  const obj = value as Record<string, unknown>;
126
- for (const key of Object.keys(obj)) obj[key] = walk(obj[key]);
182
+ // A nested inline resource may declare scopes of its own (a `Run.Sequence`
183
+ // inside another sequence's `with:`). Collected before descending, so the
184
+ // declarations are visible to every region of the resource that declares
185
+ // them — a sequence's `with:` names resolve in its `targets:` and `steps:`
186
+ // alike, not only inside `with:` itself.
187
+ const declared =
188
+ typeof obj.kind === "string" ? declaredInScopes(obj as ResourceManifest) : undefined;
189
+ const inner = declared ? new Map([...(scoped ?? new Map()), ...declared]) : scoped;
190
+ for (const key of Object.keys(obj)) obj[key] = walk(obj[key], inner);
127
191
  return value;
128
192
  };
129
193
 
@@ -323,18 +323,37 @@ export function resolveContextAnnotations(
323
323
  typeof ref.name === "string" &&
324
324
  subpath
325
325
  ) {
326
+ const segments = subpath.split("/");
326
327
  const refManifest = allManifests.find(
327
328
  (m) => m.kind === ref.kind && (m.metadata as any)?.name === ref.name,
328
329
  ) as Record<string, any> | undefined;
329
330
  if (refManifest) {
330
331
  const resolved = resolveTypeFieldToSchema(
331
- navigatePath(refManifest, subpath.split("/")) as unknown,
332
+ navigatePath(refManifest, segments) as unknown,
332
333
  allManifests,
333
334
  );
334
335
  if (resolved && typeof resolved === "object") {
335
336
  return resolved;
336
337
  }
337
338
  }
339
+ // The instance declares nothing, so fall back to its KIND's declaration —
340
+ // the same layering `buildStepContextSchema` applies to `steps.<name>.result`,
341
+ // so a kind with one fixed output shape (declared once on its Telo.Definition)
342
+ // types the context, while a kind that exposes the field for per-instance
343
+ // narrowing keeps winning above.
344
+ if (defs) {
345
+ const canonical = aliases?.resolveKind(ref.kind) ?? ref.kind;
346
+ const def = defs.resolve(canonical) as Record<string, unknown> | undefined;
347
+ if (def) {
348
+ const resolved = resolveTypeFieldToSchema(
349
+ navigatePath(def, segments) as unknown,
350
+ allManifests,
351
+ );
352
+ if (resolved && typeof resolved === "object") {
353
+ return resolved;
354
+ }
355
+ }
356
+ }
338
357
  }
339
358
  // Fallback: open schema (no false errors when outputType is not declared)
340
359
  return { ...schema, additionalProperties: true };
@@ -267,9 +267,15 @@ export function validateReferences(
267
267
 
268
268
  // Local reference (bare name or explicit `Self.`-qualified).
269
269
  const localName = aliasPrefix === "Self" ? refName.slice(dot + 1) : refName;
270
+ // Scope-local FIRST, enclosing module as the fallback — the order the
271
+ // runtime uses at every name-resolution site (`ScopeContext.getInstance`,
272
+ // `ResourceContext.resolveRef`, and the CEL `resources` layering). Module-first
273
+ // here would validate a shadowed name against the resource the kernel will
274
+ // never bind: a false pass when the outer kind fits and the scoped one does
275
+ // not, a false REFERENCE_KIND_MISMATCH when it is the other way round.
270
276
  const target =
271
- byName.get(localName) ??
272
- visibleScopeManifests.find((m) => m.metadata?.name === localName);
277
+ visibleScopeManifests.find((m) => m.metadata?.name === localName) ??
278
+ byName.get(localName);
273
279
  if (!target) {
274
280
  diagnostics.push({
275
281
  severity: DiagnosticSeverity.Error,