@telorun/analyzer 0.10.1 → 1.1.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 (48) hide show
  1. package/README.md +3 -3
  2. package/dist/analysis-registry.d.ts +7 -0
  3. package/dist/analysis-registry.d.ts.map +1 -1
  4. package/dist/analysis-registry.js +38 -0
  5. package/dist/analyzer.d.ts.map +1 -1
  6. package/dist/analyzer.js +198 -6
  7. package/dist/builtins.d.ts.map +1 -1
  8. package/dist/builtins.js +158 -1
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -0
  12. package/dist/kernel-globals.d.ts.map +1 -1
  13. package/dist/kernel-globals.js +9 -11
  14. package/dist/normalize-inline-resources.d.ts.map +1 -1
  15. package/dist/normalize-inline-resources.js +26 -14
  16. package/dist/position-metadata.d.ts +5 -1
  17. package/dist/position-metadata.d.ts.map +1 -1
  18. package/dist/position-metadata.js +8 -1
  19. package/dist/reference-field-map.d.ts +21 -4
  20. package/dist/reference-field-map.d.ts.map +1 -1
  21. package/dist/reference-field-map.js +35 -19
  22. package/dist/residual-schema.d.ts +23 -0
  23. package/dist/residual-schema.d.ts.map +1 -0
  24. package/dist/residual-schema.js +45 -0
  25. package/dist/rewrite-synthetic-origins.d.ts +10 -0
  26. package/dist/rewrite-synthetic-origins.d.ts.map +1 -0
  27. package/dist/rewrite-synthetic-origins.js +55 -0
  28. package/dist/validate-cel-context.d.ts +61 -7
  29. package/dist/validate-cel-context.d.ts.map +1 -1
  30. package/dist/validate-cel-context.js +90 -8
  31. package/dist/validate-provider-coherence.d.ts +23 -0
  32. package/dist/validate-provider-coherence.d.ts.map +1 -0
  33. package/dist/validate-provider-coherence.js +148 -0
  34. package/dist/validate-references.js +24 -24
  35. package/package.json +5 -3
  36. package/src/analysis-registry.ts +37 -0
  37. package/src/analyzer.ts +240 -8
  38. package/src/builtins.ts +158 -1
  39. package/src/index.ts +1 -0
  40. package/src/kernel-globals.ts +9 -11
  41. package/src/normalize-inline-resources.ts +48 -13
  42. package/src/position-metadata.ts +8 -1
  43. package/src/reference-field-map.ts +46 -18
  44. package/src/residual-schema.ts +49 -0
  45. package/src/rewrite-synthetic-origins.ts +75 -0
  46. package/src/validate-cel-context.ts +111 -8
  47. package/src/validate-provider-coherence.ts +166 -0
  48. package/src/validate-references.ts +24 -24
@@ -60,7 +60,7 @@ export function normalizeInlineResources(resources, registry, aliases, aliasesBy
60
60
  fieldPath.startsWith(prefix + ".") ||
61
61
  fieldPath.startsWith(prefix + "["));
62
62
  const invocationContext = isRefEntry(entry) ? entry.context : undefined;
63
- const extracted = extractInlinesAtPath(resource, fieldPath, parentName, parentModule, invocationContext);
63
+ const extracted = extractInlinesAtPath(resource, fieldPath, parentName, resource.kind, parentModule, invocationContext);
64
64
  for (const manifest of extracted) {
65
65
  result.push(manifest);
66
66
  queue.push(manifest);
@@ -75,11 +75,22 @@ export function normalizeInlineResources(resources, registry, aliases, aliasesBy
75
75
  * Walks `resource` following `fieldPath` (dot notation, `[]` = array traversal).
76
76
  * Mutates the resource in-place: replaces each inline value with `{kind, name}`.
77
77
  * Returns the extracted manifests.
78
+ *
79
+ * Each extracted manifest carries `metadata.xTeloOrigin` so downstream
80
+ * diagnostics can be rerouted back to the parent doc's YAML position:
81
+ * - `parentKind` / `parentName` — the resource that owned the inline slot
82
+ * - `pathFromParent` — concrete dotted path with `[N]` indices, matching
83
+ * `buildPositionIndex` keys (e.g. `routes[0].handler`)
78
84
  */
79
- function extractInlinesAtPath(resource, fieldPath, parentName, parentModule, invocationContext) {
85
+ function extractInlinesAtPath(resource, fieldPath, parentName, parentKind, parentModule, invocationContext) {
80
86
  const extracted = [];
81
87
  const parts = fieldPath.split(".");
82
- function traverse(obj, partsLeft, nameParts) {
88
+ function emit(inline, nameSegments, concretePath) {
89
+ const name = sanitizeName([parentName, ...nameSegments].join("_"));
90
+ extracted.push(buildManifest(inline, name, parentKind, parentName, concretePath, parentModule, invocationContext));
91
+ return name;
92
+ }
93
+ function traverse(obj, partsLeft, nameParts, pathSoFar) {
83
94
  if (!obj || typeof obj !== "object" || partsLeft.length === 0)
84
95
  return;
85
96
  const [head, ...rest] = partsLeft;
@@ -92,15 +103,15 @@ function extractInlinesAtPath(resource, fieldPath, parentName, parentModule, inv
92
103
  if (!elem || typeof elem !== "object")
93
104
  continue;
94
105
  const sanitizedKey = sanitizeName(mapKey);
106
+ const childPath = pathSoFar ? `${pathSoFar}.${mapKey}` : mapKey;
95
107
  if (rest.length === 0) {
96
108
  if (isInlineResource(elem)) {
97
- const name = sanitizeName([parentName, ...nameParts, sanitizedKey].join("_"));
98
- extracted.push(buildManifest(elem, name, parentModule, invocationContext));
109
+ const name = emit(elem, [...nameParts, sanitizedKey], childPath);
99
110
  container[mapKey] = { kind: elem.kind, name };
100
111
  }
101
112
  }
102
113
  else {
103
- traverse(elem, rest, [...nameParts, sanitizedKey]);
114
+ traverse(elem, rest, [...nameParts, sanitizedKey], childPath);
104
115
  }
105
116
  }
106
117
  return;
@@ -111,6 +122,7 @@ function extractInlinesAtPath(resource, fieldPath, parentName, parentModule, inv
111
122
  const val = container[key];
112
123
  if (val == null)
113
124
  return;
125
+ const keyPath = pathSoFar ? `${pathSoFar}.${key}` : key;
114
126
  if (isArr) {
115
127
  if (!Array.isArray(val))
116
128
  return;
@@ -121,16 +133,16 @@ function extractInlinesAtPath(resource, fieldPath, parentName, parentModule, inv
121
133
  const elemId = typeof elem.name === "string"
122
134
  ? elem.name
123
135
  : String(idx);
136
+ const childPath = `${keyPath}[${idx}]`;
124
137
  if (rest.length === 0) {
125
138
  // Array element itself is the ref slot
126
139
  if (isInlineResource(elem)) {
127
- const name = sanitizeName([parentName, ...nameParts, key, elemId].join("_"));
128
- extracted.push(buildManifest(elem, name, parentModule, invocationContext));
140
+ const name = emit(elem, [...nameParts, key, elemId], childPath);
129
141
  val[idx] = { kind: elem.kind, name };
130
142
  }
131
143
  }
132
144
  else {
133
- traverse(elem, rest, [...nameParts, key, elemId]);
145
+ traverse(elem, rest, [...nameParts, key, elemId], childPath);
134
146
  }
135
147
  }
136
148
  }
@@ -138,20 +150,19 @@ function extractInlinesAtPath(resource, fieldPath, parentName, parentModule, inv
138
150
  if (rest.length === 0) {
139
151
  // val is the ref slot
140
152
  if (val && typeof val === "object" && !Array.isArray(val) && isInlineResource(val)) {
141
- const name = sanitizeName([parentName, ...nameParts, key].join("_"));
142
- extracted.push(buildManifest(val, name, parentModule, invocationContext));
153
+ const name = emit(val, [...nameParts, key], keyPath);
143
154
  container[key] = { kind: val.kind, name };
144
155
  }
145
156
  }
146
157
  else {
147
- traverse(val, rest, [...nameParts, key]);
158
+ traverse(val, rest, [...nameParts, key], keyPath);
148
159
  }
149
160
  }
150
161
  }
151
- traverse(resource, parts, []);
162
+ traverse(resource, parts, [], "");
152
163
  return extracted;
153
164
  }
154
- function buildManifest(inline, name, parentModule, invocationContext) {
165
+ function buildManifest(inline, name, parentKind, parentName, pathFromParent, parentModule, invocationContext) {
155
166
  const existingMeta = inline.metadata && typeof inline.metadata === "object"
156
167
  ? inline.metadata
157
168
  : {};
@@ -163,6 +174,7 @@ function buildManifest(inline, name, parentModule, invocationContext) {
163
174
  // Inherit parent module only if the inline doesn't already declare one
164
175
  ...(parentModule && !existingMeta.module ? { module: parentModule } : {}),
165
176
  ...(invocationContext ? { xTeloInvocationContext: invocationContext } : {}),
177
+ xTeloOrigin: { parentKind, parentName, pathFromParent },
166
178
  },
167
179
  };
168
180
  }
@@ -22,6 +22,10 @@ export declare function documentLineOffsets(text: string): number[];
22
22
  * yaml-AST node range into Range coordinates. */
23
23
  export declare function buildLineOffsets(text: string): number[];
24
24
  /** Walks the YAML AST and records source ranges for every field value, keyed
25
- * by dotted path (e.g. "kind", "config.handler", "config.routes[0].path"). */
25
+ * by dotted path (e.g. "kind", "config.handler", "config.routes[0].path").
26
+ * Map keys are also recorded under the `@key:<path>` namespace so diagnostic
27
+ * resolvers can squiggle just the key identifier instead of the full value
28
+ * block — used when a diagnostic targets a missing child property and the
29
+ * resolver has to fall back to the parent. */
26
30
  export declare function buildPositionIndex(doc: Document, lineOffsets: number[]): PositionIndex;
27
31
  //# sourceMappingURL=position-metadata.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"position-metadata.d.ts","sourceRoot":"","sources":["../src/position-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,QAAQ,EAAE,MAAM,MAAM,CAAC;AACrE,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;oBAKoB;AAEpB,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,kEAAkE;AAClE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,QAAQ,EAAE,GACrB,gBAAgB,EAAE,CAOpB;AAED;;8CAE8C;AAC9C,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ1D;AAED;;kDAEkD;AAClD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAaD;+EAC+E;AAC/E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,aAAa,CAuCtF"}
1
+ {"version":3,"file":"position-metadata.d.ts","sourceRoot":"","sources":["../src/position-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,QAAQ,EAAE,MAAM,MAAM,CAAC;AACrE,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;oBAKoB;AAEpB,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,kEAAkE;AAClE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,QAAQ,EAAE,GACrB,gBAAgB,EAAE,CAOpB;AAED;;8CAE8C;AAC9C,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ1D;AAED;;kDAEkD;AAClD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAaD;;;;;+CAK+C;AAC/C,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,aAAa,CA0CtF"}
@@ -45,7 +45,11 @@ function offsetToPosition(offset, lineOffsets) {
45
45
  return { line: lo, character: offset - lineOffsets[lo] };
46
46
  }
47
47
  /** Walks the YAML AST and records source ranges for every field value, keyed
48
- * by dotted path (e.g. "kind", "config.handler", "config.routes[0].path"). */
48
+ * by dotted path (e.g. "kind", "config.handler", "config.routes[0].path").
49
+ * Map keys are also recorded under the `@key:<path>` namespace so diagnostic
50
+ * resolvers can squiggle just the key identifier instead of the full value
51
+ * block — used when a diagnostic targets a missing child property and the
52
+ * resolver has to fall back to the parent. */
49
53
  export function buildPositionIndex(doc, lineOffsets) {
50
54
  const index = new Map();
51
55
  function recordNode(node, path) {
@@ -66,6 +70,9 @@ export function buildPositionIndex(doc, lineOffsets) {
66
70
  if (key == null)
67
71
  continue;
68
72
  const childPath = path ? `${path}.${key}` : key;
73
+ if (pair.key && pair.key.range) {
74
+ recordNode(pair.key, `@key:${childPath}`);
75
+ }
69
76
  if (pair.value != null) {
70
77
  recordNode(pair.value, childPath);
71
78
  walk(pair.value, childPath);
@@ -43,12 +43,29 @@ export declare const REFERENCE_KEYS: Set<string>;
43
43
  * A named reference (has string `name`) may carry extra keys (e.g. `inputs`)
44
44
  * that are runtime call parameters — those are never inline resources. */
45
45
  export declare function isInlineResource(val: Record<string, unknown>): boolean;
46
- /** Resolves all values at a field map path in a resource config.
47
- * Path-segment markers:
48
- * - `[]` iterate array values at this key
46
+ /** A value found at a field-map path, paired with the concrete path that
47
+ * produced it. `path` has every `[]` substituted with `[N]` and every `{}`
48
+ * substituted with the actual map key, matching the format produced by
49
+ * `buildPositionIndex`. Used so diagnostics emitted against a specific
50
+ * array element / map entry can be resolved back to a YAML range. */
51
+ export interface ResolvedFieldEntry {
52
+ value: unknown;
53
+ path: string;
54
+ }
55
+ /** Resolves all `{value, path}` entries at a field map path in a resource
56
+ * config. The returned `path` is the concrete dotted path produced by the
57
+ * substitutions below, matching the format `buildPositionIndex` keys on.
58
+ * Path-segment markers accepted in the input `path`:
59
+ * - `[]` iterate array values at this key, substituting `[N]` per item
60
+ * (e.g. `routes[]` → `routes[0]`, `routes[1]`, …).
49
61
  * - `{}` iterate map values (every value in an `additionalProperties`-typed
50
62
  * object — used for fields like `content[mime]` whose schema declares
51
- * a key-as-MIME map). The path is `<key>.{}.<rest>`. */
63
+ * a key-as-MIME map). Substituted with the literal map key joined by
64
+ * a dot, so the input `content.{}.encoder` yields concrete paths
65
+ * like `content.application/json.encoder`. */
66
+ export declare function resolveFieldEntries(obj: unknown, path: string): ResolvedFieldEntry[];
67
+ /** Backwards-compat wrapper that drops the concrete path. Prefer
68
+ * `resolveFieldEntries` for new code that wants positions. */
52
69
  export declare function resolveFieldValues(obj: unknown, path: string): unknown[];
53
70
  /**
54
71
  * Traverses a definition's JSON Schema once and returns a field map recording every
@@ -1 +1 @@
1
- {"version":3,"file":"reference-field-map.d.ts","sourceRoot":"","sources":["../src/reference-field-map.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B;sDACkD;IAClD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,0FAA0F;IAC1F,OAAO,EAAE,OAAO,CAAC;IACjB;8DAC0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B;2CACuC;IACvC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED;8CAC8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC;;qFAEiF;IACjF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAEnF;0FAC0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE3D,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,aAAa,CAEvE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,eAAe,CAE3E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,oBAAoB,CAErF;AAED,oGAAoG;AACpG,eAAO,MAAM,cAAc,aAAwC,CAAC;AAEpE;;;;;;;;;2EAS2E;AAC3E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAItE;AAED;;;;;kEAKkE;AAClE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CA6BxE;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,iBAAiB,CAQrF;AAiBD;;;8CAG8C;AAC9C,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,EAAE,MAAM,GACjB,iBAAiB,CAInB"}
1
+ {"version":3,"file":"reference-field-map.d.ts","sourceRoot":"","sources":["../src/reference-field-map.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B;sDACkD;IAClD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,0FAA0F;IAC1F,OAAO,EAAE,OAAO,CAAC;IACjB;8DAC0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B;2CACuC;IACvC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED;8CAC8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC;;qFAEiF;IACjF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAEnF;0FAC0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE3D,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,aAAa,CAEvE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,eAAe,CAE3E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,oBAAoB,CAErF;AAED,oGAAoG;AACpG,eAAO,MAAM,cAAc,aAAwC,CAAC;AAEpE;;;;;;;;;2EAS2E;AAC3E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAItE;AAED;;;;sEAIsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;wDAUwD;AACxD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAoCpF;AAED;+DAC+D;AAC/D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAExE;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,iBAAiB,CAQrF;AAiBD;;;8CAG8C;AAC9C,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,EAAE,MAAM,GACjB,iBAAiB,CAInB"}
@@ -26,25 +26,30 @@ export function isInlineResource(val) {
26
26
  return false;
27
27
  return true;
28
28
  }
29
- /** Resolves all values at a field map path in a resource config.
30
- * Path-segment markers:
31
- * - `[]` iterate array values at this key
29
+ /** Resolves all `{value, path}` entries at a field map path in a resource
30
+ * config. The returned `path` is the concrete dotted path produced by the
31
+ * substitutions below, matching the format `buildPositionIndex` keys on.
32
+ * Path-segment markers accepted in the input `path`:
33
+ * - `[]` iterate array values at this key, substituting `[N]` per item
34
+ * (e.g. `routes[]` → `routes[0]`, `routes[1]`, …).
32
35
  * - `{}` iterate map values (every value in an `additionalProperties`-typed
33
36
  * object — used for fields like `content[mime]` whose schema declares
34
- * a key-as-MIME map). The path is `<key>.{}.<rest>`. */
35
- export function resolveFieldValues(obj, path) {
37
+ * a key-as-MIME map). Substituted with the literal map key joined by
38
+ * a dot, so the input `content.{}.encoder` yields concrete paths
39
+ * like `content.application/json.encoder`. */
40
+ export function resolveFieldEntries(obj, path) {
36
41
  const parts = path.split(".");
37
- let current = [obj];
42
+ let current = [{ value: obj, path: "" }];
38
43
  for (const part of parts) {
39
44
  if (part === "{}") {
40
- // Iterate the values of every map currently in `current`.
41
45
  const next = [];
42
- for (const item of current) {
43
- if (!item || typeof item !== "object")
46
+ for (const entry of current) {
47
+ if (!entry.value || typeof entry.value !== "object")
44
48
  continue;
45
- for (const v of Object.values(item)) {
46
- if (v != null)
47
- next.push(v);
49
+ for (const [k, v] of Object.entries(entry.value)) {
50
+ if (v != null) {
51
+ next.push({ value: v, path: entry.path ? `${entry.path}.${k}` : k });
52
+ }
48
53
  }
49
54
  }
50
55
  current = next;
@@ -53,21 +58,32 @@ export function resolveFieldValues(obj, path) {
53
58
  const isArray = part.endsWith("[]");
54
59
  const key = isArray ? part.slice(0, -2) : part;
55
60
  const next = [];
56
- for (const item of current) {
57
- if (!item || typeof item !== "object")
61
+ for (const entry of current) {
62
+ if (!entry.value || typeof entry.value !== "object")
58
63
  continue;
59
- const val = item[key];
64
+ const val = entry.value[key];
60
65
  if (val == null)
61
66
  continue;
62
- if (isArray && Array.isArray(val))
63
- next.push(...val);
64
- else if (!isArray)
65
- next.push(val);
67
+ const basePath = entry.path ? `${entry.path}.${key}` : key;
68
+ if (isArray && Array.isArray(val)) {
69
+ for (let i = 0; i < val.length; i++) {
70
+ if (val[i] != null)
71
+ next.push({ value: val[i], path: `${basePath}[${i}]` });
72
+ }
73
+ }
74
+ else if (!isArray) {
75
+ next.push({ value: val, path: basePath });
76
+ }
66
77
  }
67
78
  current = next;
68
79
  }
69
80
  return current;
70
81
  }
82
+ /** Backwards-compat wrapper that drops the concrete path. Prefer
83
+ * `resolveFieldEntries` for new code that wants positions. */
84
+ export function resolveFieldValues(obj, path) {
85
+ return resolveFieldEntries(obj, path).map((e) => e.value);
86
+ }
71
87
  /**
72
88
  * Traverses a definition's JSON Schema once and returns a field map recording every
73
89
  * x-telo-ref slot and every x-telo-scope slot.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Build the residual JSON Schema for a `variables` / `secrets` entry.
3
+ *
4
+ * For Telo.Application env-binding entries (those with an `env:` key), strips
5
+ * the kernel-specific wrapper keys `env` and `default` — `default` here is
6
+ * the *fallback host value* the kernel coerces when the env var is unset, not
7
+ * a JSON Schema annotation, so it must not leak into the validator.
8
+ *
9
+ * For Telo.Library entries (no `env:`), passes the entry through unchanged.
10
+ * Library `default:` is a standard JSON Schema annotation and stays.
11
+ *
12
+ * Single source of truth for "residual schema" referenced by both the
13
+ * analyzer's CEL globals normalization and the kernel's runtime env-var
14
+ * resolver — keeping them aligned prevents the two surfaces from drifting.
15
+ */
16
+ export declare function residualEntrySchema(entry: Record<string, unknown> | null | undefined): Record<string, unknown>;
17
+ /**
18
+ * Apply `residualEntrySchema` to every entry in a `variables` / `secrets` map.
19
+ * Returns a property-map suitable for use as the inner schema of CEL's
20
+ * `variables` / `secrets` namespaces.
21
+ */
22
+ export declare function residualEntrySchemaMap(entries: Record<string, unknown> | null | undefined): Record<string, Record<string, unknown>>;
23
+ //# sourceMappingURL=residual-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"residual-schema.d.ts","sourceRoot":"","sources":["../src/residual-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GAChD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWzB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GAClD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAWzC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Build the residual JSON Schema for a `variables` / `secrets` entry.
3
+ *
4
+ * For Telo.Application env-binding entries (those with an `env:` key), strips
5
+ * the kernel-specific wrapper keys `env` and `default` — `default` here is
6
+ * the *fallback host value* the kernel coerces when the env var is unset, not
7
+ * a JSON Schema annotation, so it must not leak into the validator.
8
+ *
9
+ * For Telo.Library entries (no `env:`), passes the entry through unchanged.
10
+ * Library `default:` is a standard JSON Schema annotation and stays.
11
+ *
12
+ * Single source of truth for "residual schema" referenced by both the
13
+ * analyzer's CEL globals normalization and the kernel's runtime env-var
14
+ * resolver — keeping them aligned prevents the two surfaces from drifting.
15
+ */
16
+ export function residualEntrySchema(entry) {
17
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
18
+ return { type: "object", additionalProperties: true };
19
+ }
20
+ const isAppEnvBinding = "env" in entry;
21
+ const out = {};
22
+ for (const [key, value] of Object.entries(entry)) {
23
+ if (isAppEnvBinding && (key === "env" || key === "default"))
24
+ continue;
25
+ out[key] = value;
26
+ }
27
+ return out;
28
+ }
29
+ /**
30
+ * Apply `residualEntrySchema` to every entry in a `variables` / `secrets` map.
31
+ * Returns a property-map suitable for use as the inner schema of CEL's
32
+ * `variables` / `secrets` namespaces.
33
+ */
34
+ export function residualEntrySchemaMap(entries) {
35
+ const out = {};
36
+ if (!entries || typeof entries !== "object" || Array.isArray(entries)) {
37
+ return out;
38
+ }
39
+ for (const [name, value] of Object.entries(entries)) {
40
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
41
+ out[name] = residualEntrySchema(value);
42
+ }
43
+ }
44
+ return out;
45
+ }
@@ -0,0 +1,10 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import type { AnalysisDiagnostic } from "./types.js";
3
+ /** Diagnostics emitted on synthetic manifests (resources extracted by
4
+ * `normalizeInlineResources`) carry the synthetic's identity in
5
+ * `data.resource`, which has no YAML source. Rewrite each such diagnostic
6
+ * back to the chain root: walk up `metadata.xTeloOrigin` until a manifest
7
+ * with no origin is reached, and prepend each hop's `pathFromParent` to
8
+ * `data.path` so position-index lookups against the root doc resolve. */
9
+ export declare function rewriteSyntheticOrigins(diagnostics: AnalysisDiagnostic[], manifests: ResourceManifest[]): AnalysisDiagnostic[];
10
+ //# sourceMappingURL=rewrite-synthetic-origins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rewrite-synthetic-origins.d.ts","sourceRoot":"","sources":["../src/rewrite-synthetic-origins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAsBrD;;;;;0EAK0E;AAC1E,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,kBAAkB,EAAE,EACjC,SAAS,EAAE,gBAAgB,EAAE,GAC5B,kBAAkB,EAAE,CA0CtB"}
@@ -0,0 +1,55 @@
1
+ function readOrigin(manifest) {
2
+ if (!manifest)
3
+ return undefined;
4
+ const origin = manifest.metadata?.xTeloOrigin;
5
+ if (!origin ||
6
+ typeof origin.parentKind !== "string" ||
7
+ typeof origin.parentName !== "string" ||
8
+ typeof origin.pathFromParent !== "string") {
9
+ return undefined;
10
+ }
11
+ return origin;
12
+ }
13
+ /** Diagnostics emitted on synthetic manifests (resources extracted by
14
+ * `normalizeInlineResources`) carry the synthetic's identity in
15
+ * `data.resource`, which has no YAML source. Rewrite each such diagnostic
16
+ * back to the chain root: walk up `metadata.xTeloOrigin` until a manifest
17
+ * with no origin is reached, and prepend each hop's `pathFromParent` to
18
+ * `data.path` so position-index lookups against the root doc resolve. */
19
+ export function rewriteSyntheticOrigins(diagnostics, manifests) {
20
+ const byName = new Map();
21
+ for (const m of manifests) {
22
+ const name = m.metadata?.name;
23
+ if (typeof name === "string")
24
+ byName.set(name, m);
25
+ }
26
+ return diagnostics.map((d) => {
27
+ const data = d.data;
28
+ if (!data?.resource?.name)
29
+ return d;
30
+ let current = byName.get(data.resource.name);
31
+ let origin = readOrigin(current);
32
+ if (!origin)
33
+ return d;
34
+ let accumPath = typeof data.path === "string" ? data.path : "";
35
+ let rootKind = origin.parentKind;
36
+ let rootName = origin.parentName;
37
+ while (origin) {
38
+ accumPath = accumPath ? `${origin.pathFromParent}.${accumPath}` : origin.pathFromParent;
39
+ rootKind = origin.parentKind;
40
+ rootName = origin.parentName;
41
+ current = byName.get(origin.parentName);
42
+ origin = readOrigin(current);
43
+ }
44
+ const rootFilePath = current?.metadata?.source ?? data.filePath;
45
+ return {
46
+ ...d,
47
+ data: {
48
+ ...data,
49
+ resource: { kind: rootKind, name: rootName },
50
+ filePath: rootFilePath,
51
+ path: accumPath,
52
+ },
53
+ };
54
+ });
55
+ }
@@ -1,4 +1,19 @@
1
1
  export { extractAccessChains, validateChainAgainstSchema } from "@telorun/templating";
2
+ export interface ContextResolveOpts {
3
+ /** When provided, used to resolve `x-telo-context-from-root` annotations against the
4
+ * root manifest. When omitted, defaults to `manifestItem`. */
5
+ manifestRoot?: Record<string, any>;
6
+ /** When provided alongside `aliases`, used to resolve `x-telo-context-from-ref-kind`
7
+ * annotations: read a kind name from a path on `manifestRoot` and return the
8
+ * declared definition's `<field>` schema. */
9
+ defs?: {
10
+ resolve(kind: string): Record<string, any> | undefined;
11
+ };
12
+ aliases?: {
13
+ resolveKind(kind: string): string | undefined;
14
+ };
15
+ allManifests?: Record<string, any>[];
16
+ }
2
17
  /**
3
18
  * Resolve a type field value (string name, inline type, or raw schema) to a JSON Schema.
4
19
  * - String: look up the named type in allManifests (Type.JsonSchema resources)
@@ -15,15 +30,54 @@ export declare function resolveTypeFieldToSchema(value: unknown, allManifests: R
15
30
  */
16
31
  export declare function pathMatchesScope(exprPath: string, scope: string): boolean;
17
32
  /**
18
- * Resolves `x-telo-context-from` annotations in a context schema using the concrete
19
- * manifest item. Navigates the manifest item at the given slash-separated path and merges
20
- * the result as named properties into the annotated node (locking additionalProperties: false).
33
+ * Resolves `x-telo-context-*` annotations in a context schema using the concrete
34
+ * manifest item (per-scope) and the manifest root.
21
35
  *
22
- * Example: `x-telo-context-from: "request/schema"` on the `request` context node replaces
23
- * the open `request` schema with a closed schema whose properties are the keys of
24
- * `manifestItem.request.schema` (e.g. `query`, `body`, `params`, `headers`).
36
+ * Annotation forms:
37
+ *
38
+ * - `x-telo-context-from`: navigates `manifestItem.<path>` and treats the resolved
39
+ * value as a **property map** (keys → sub-schemas) that is merged into the
40
+ * annotated node's properties. Used for HTTP-style scopes where the navigated
41
+ * value is itself a map of variable names.
42
+ *
43
+ * Example: `x-telo-context-from: "request/schema"` reads `manifestItem.request.schema`
44
+ * (= `{ query: {...}, body: {...}, … }`) and merges those keys as named properties
45
+ * of the context node.
46
+ *
47
+ * - `x-telo-context-from-root`: navigates `manifestRoot.<path>` and **replaces** the
48
+ * annotated node's schema with the resolved value. Used on individual property
49
+ * schemas (e.g. `properties.self`) where the resolved value is a single variable's
50
+ * full schema, not a property map.
51
+ *
52
+ * Example: `properties.self.x-telo-context-from-root: "schema"` reads
53
+ * `manifestRoot.schema` and uses it as the schema of the `self` CEL variable.
54
+ *
55
+ * - `x-telo-context-from-ref-kind`: reads a kind name from `manifestRoot.<refPath>`,
56
+ * resolves it via the definition registry, and returns that kind's `<field>` schema
57
+ * (e.g. `outputType`/`inputType`). Used to type `result` against the dispatch
58
+ * target's declared output shape.
59
+ *
60
+ * Syntax: `<refPath>#<field>` — slashes traverse the manifest tree.
61
+ *
62
+ * Example: `x-telo-context-from-ref-kind: "provide/kind#outputType"` reads
63
+ * `manifestRoot.provide.kind` as a kind name, looks up the kind's Telo.Definition,
64
+ * and returns the `outputType` schema.
65
+ *
66
+ * Accepts either a single string or an array of strings. With an array, paths
67
+ * are tried in order and the first one that resolves to a usable schema wins —
68
+ * used by `result:` to find its dispatch target under whichever entry-point
69
+ * field (`provide:` or `invoke:`) the definition declares.
70
+ *
71
+ * - `x-telo-context-ref-from`: existing form — reads `{kind, name}` object from
72
+ * `manifestItem.<path>`, looks up the named manifest, returns its `<subpath>` field.
73
+ *
74
+ * **Fallback chain.** When both `x-telo-context-from-root` and
75
+ * `x-telo-context-from-ref-kind` are present on the same node, the resolver tries
76
+ * `from-root` first; if that produces no usable schema, it falls back to `from-ref-kind`.
77
+ * This lets a definition declare typing from its own field with a sibling-kind fallback
78
+ * (e.g. `inputType` direct → `extends`-declared abstract's `inputType`).
25
79
  */
26
- export declare function resolveContextAnnotations(schema: Record<string, any>, manifestItem: Record<string, any>, allManifests?: Record<string, any>[]): Record<string, any>;
80
+ export declare function resolveContextAnnotations(schema: Record<string, any>, manifestItem: Record<string, any>, opts?: ContextResolveOpts | Record<string, any>[]): Record<string, any>;
27
81
  /**
28
82
  * Extracts the concrete manifest array item for a given expression path + scope.
29
83
  * e.g. exprPath="routes[0].inputs.q", scope="$.routes[*].inputs" → manifest.routes[0]
@@ -1 +1 @@
1
- {"version":3,"file":"validate-cel-context.d.ts","sourceRoot":"","sources":["../src/validate-cel-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEtF;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAClC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA6BjC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAoBzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GACnC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAqDrB;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"}
1
+ {"version":3,"file":"validate-cel-context.d.ts","sourceRoot":"","sources":["../src/validate-cel-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEtF,MAAM,WAAW,kBAAkB;IACjC;mEAC+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC;;kDAE8C;IAC9C,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;KACxD,CAAC;IACF,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;KAC/C,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAClC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA6BjC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAoBzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,IAAI,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAChD,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAqGrB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAQrB"}
@@ -61,17 +61,61 @@ export function pathMatchesScope(exprPath, scope) {
61
61
  return remaining === "" || remaining[0] === "." || remaining[0] === "[";
62
62
  }
63
63
  /**
64
- * Resolves `x-telo-context-from` annotations in a context schema using the concrete
65
- * manifest item. Navigates the manifest item at the given slash-separated path and merges
66
- * the result as named properties into the annotated node (locking additionalProperties: false).
64
+ * Resolves `x-telo-context-*` annotations in a context schema using the concrete
65
+ * manifest item (per-scope) and the manifest root.
67
66
  *
68
- * Example: `x-telo-context-from: "request/schema"` on the `request` context node replaces
69
- * the open `request` schema with a closed schema whose properties are the keys of
70
- * `manifestItem.request.schema` (e.g. `query`, `body`, `params`, `headers`).
67
+ * Annotation forms:
68
+ *
69
+ * - `x-telo-context-from`: navigates `manifestItem.<path>` and treats the resolved
70
+ * value as a **property map** (keys → sub-schemas) that is merged into the
71
+ * annotated node's properties. Used for HTTP-style scopes where the navigated
72
+ * value is itself a map of variable names.
73
+ *
74
+ * Example: `x-telo-context-from: "request/schema"` reads `manifestItem.request.schema`
75
+ * (= `{ query: {...}, body: {...}, … }`) and merges those keys as named properties
76
+ * of the context node.
77
+ *
78
+ * - `x-telo-context-from-root`: navigates `manifestRoot.<path>` and **replaces** the
79
+ * annotated node's schema with the resolved value. Used on individual property
80
+ * schemas (e.g. `properties.self`) where the resolved value is a single variable's
81
+ * full schema, not a property map.
82
+ *
83
+ * Example: `properties.self.x-telo-context-from-root: "schema"` reads
84
+ * `manifestRoot.schema` and uses it as the schema of the `self` CEL variable.
85
+ *
86
+ * - `x-telo-context-from-ref-kind`: reads a kind name from `manifestRoot.<refPath>`,
87
+ * resolves it via the definition registry, and returns that kind's `<field>` schema
88
+ * (e.g. `outputType`/`inputType`). Used to type `result` against the dispatch
89
+ * target's declared output shape.
90
+ *
91
+ * Syntax: `<refPath>#<field>` — slashes traverse the manifest tree.
92
+ *
93
+ * Example: `x-telo-context-from-ref-kind: "provide/kind#outputType"` reads
94
+ * `manifestRoot.provide.kind` as a kind name, looks up the kind's Telo.Definition,
95
+ * and returns the `outputType` schema.
96
+ *
97
+ * Accepts either a single string or an array of strings. With an array, paths
98
+ * are tried in order and the first one that resolves to a usable schema wins —
99
+ * used by `result:` to find its dispatch target under whichever entry-point
100
+ * field (`provide:` or `invoke:`) the definition declares.
101
+ *
102
+ * - `x-telo-context-ref-from`: existing form — reads `{kind, name}` object from
103
+ * `manifestItem.<path>`, looks up the named manifest, returns its `<subpath>` field.
104
+ *
105
+ * **Fallback chain.** When both `x-telo-context-from-root` and
106
+ * `x-telo-context-from-ref-kind` are present on the same node, the resolver tries
107
+ * `from-root` first; if that produces no usable schema, it falls back to `from-ref-kind`.
108
+ * This lets a definition declare typing from its own field with a sibling-kind fallback
109
+ * (e.g. `inputType` direct → `extends`-declared abstract's `inputType`).
71
110
  */
72
- export function resolveContextAnnotations(schema, manifestItem, allManifests) {
111
+ export function resolveContextAnnotations(schema, manifestItem, opts) {
73
112
  if (!schema || typeof schema !== "object")
74
113
  return schema;
114
+ // Back-compat: third positional arg used to be `allManifests: Record<string, any>[]`.
115
+ const normalizedOpts = Array.isArray(opts)
116
+ ? { allManifests: opts }
117
+ : (opts ?? {});
118
+ const { manifestRoot = manifestItem, defs, aliases, allManifests } = normalizedOpts;
75
119
  const from = schema["x-telo-context-from"];
76
120
  if (from) {
77
121
  const resolved = navigatePath(manifestItem, from.split("/"));
@@ -82,6 +126,44 @@ export function resolveContextAnnotations(schema, manifestItem, allManifests) {
82
126
  additionalProperties: false,
83
127
  };
84
128
  }
129
+ const fromRoot = schema["x-telo-context-from-root"];
130
+ const fromRefKindRaw = schema["x-telo-context-from-ref-kind"];
131
+ const fromRefKinds = fromRefKindRaw == null
132
+ ? []
133
+ : Array.isArray(fromRefKindRaw)
134
+ ? fromRefKindRaw
135
+ : [fromRefKindRaw];
136
+ if (fromRoot || fromRefKinds.length > 0) {
137
+ if (fromRoot) {
138
+ const resolved = navigatePath(manifestRoot, fromRoot.split("/"));
139
+ if (resolved && typeof resolved === "object" && !Array.isArray(resolved)) {
140
+ return resolved;
141
+ }
142
+ }
143
+ if (defs) {
144
+ for (const fromRefKind of fromRefKinds) {
145
+ const hashIdx = fromRefKind.indexOf("#");
146
+ if (hashIdx <= 0)
147
+ continue;
148
+ const refPath = fromRefKind.slice(0, hashIdx);
149
+ const field = fromRefKind.slice(hashIdx + 1);
150
+ const kindValue = navigatePath(manifestRoot, refPath.split("/"));
151
+ if (typeof kindValue !== "string" || kindValue.length === 0)
152
+ continue;
153
+ const canonical = aliases?.resolveKind(kindValue) ?? kindValue;
154
+ const def = defs.resolve(canonical);
155
+ const typeField = def
156
+ ? def[field]
157
+ : undefined;
158
+ const resolved = resolveTypeFieldToSchema(typeField, allManifests ?? []);
159
+ if (resolved && typeof resolved === "object") {
160
+ return resolved;
161
+ }
162
+ }
163
+ }
164
+ // Open fallback so unresolved types never produce false-positive CEL diagnostics.
165
+ return { type: "object", additionalProperties: true };
166
+ }
85
167
  const refFrom = schema["x-telo-context-ref-from"];
86
168
  if (refFrom && allManifests) {
87
169
  const slashIdx = refFrom.indexOf("/");
@@ -107,7 +189,7 @@ export function resolveContextAnnotations(schema, manifestItem, allManifests) {
107
189
  if (schema.properties) {
108
190
  const props = {};
109
191
  for (const [k, v] of Object.entries(schema.properties)) {
110
- props[k] = resolveContextAnnotations(v, manifestItem, allManifests);
192
+ props[k] = resolveContextAnnotations(v, manifestItem, normalizedOpts);
111
193
  }
112
194
  return { ...schema, properties: props };
113
195
  }