@telorun/analyzer 0.11.0 → 1.2.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 (56) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +3 -3
  3. package/dist/analysis-registry.d.ts +7 -0
  4. package/dist/analysis-registry.d.ts.map +1 -1
  5. package/dist/analysis-registry.js +38 -0
  6. package/dist/analyzer.d.ts.map +1 -1
  7. package/dist/analyzer.js +53 -9
  8. package/dist/builtins.d.ts.map +1 -1
  9. package/dist/builtins.js +44 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -0
  13. package/dist/kernel-globals.d.ts.map +1 -1
  14. package/dist/kernel-globals.js +9 -11
  15. package/dist/manifest-loader.d.ts +23 -1
  16. package/dist/manifest-loader.d.ts.map +1 -1
  17. package/dist/manifest-loader.js +66 -3
  18. package/dist/normalize-inline-resources.d.ts.map +1 -1
  19. package/dist/normalize-inline-resources.js +26 -14
  20. package/dist/position-metadata.d.ts +5 -1
  21. package/dist/position-metadata.d.ts.map +1 -1
  22. package/dist/position-metadata.js +8 -1
  23. package/dist/reference-field-map.d.ts +21 -4
  24. package/dist/reference-field-map.d.ts.map +1 -1
  25. package/dist/reference-field-map.js +35 -19
  26. package/dist/residual-schema.d.ts +23 -0
  27. package/dist/residual-schema.d.ts.map +1 -0
  28. package/dist/residual-schema.js +45 -0
  29. package/dist/rewrite-synthetic-origins.d.ts +10 -0
  30. package/dist/rewrite-synthetic-origins.d.ts.map +1 -0
  31. package/dist/rewrite-synthetic-origins.js +55 -0
  32. package/dist/types.d.ts +12 -0
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/validate-cel-context.d.ts +5 -0
  35. package/dist/validate-cel-context.d.ts.map +1 -1
  36. package/dist/validate-cel-context.js +27 -15
  37. package/dist/validate-provider-coherence.d.ts +23 -0
  38. package/dist/validate-provider-coherence.d.ts.map +1 -0
  39. package/dist/validate-provider-coherence.js +148 -0
  40. package/dist/validate-references.js +24 -24
  41. package/package.json +7 -4
  42. package/src/analysis-registry.ts +37 -0
  43. package/src/analyzer.ts +55 -11
  44. package/src/builtins.ts +44 -1
  45. package/src/index.ts +1 -0
  46. package/src/kernel-globals.ts +9 -11
  47. package/src/manifest-loader.ts +69 -4
  48. package/src/normalize-inline-resources.ts +48 -13
  49. package/src/position-metadata.ts +8 -1
  50. package/src/reference-field-map.ts +46 -18
  51. package/src/residual-schema.ts +49 -0
  52. package/src/rewrite-synthetic-origins.ts +75 -0
  53. package/src/types.ts +12 -0
  54. package/src/validate-cel-context.ts +28 -15
  55. package/src/validate-provider-coherence.ts +166 -0
  56. package/src/validate-references.ts +24 -24
@@ -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
+ }
package/dist/types.d.ts CHANGED
@@ -73,6 +73,18 @@ export interface LoaderInitOptions {
73
73
  }
74
74
  export interface AnalysisOptions {
75
75
  strictContexts?: boolean;
76
+ /** When true, `analyze()` runs the state-mutating setup (module identity /
77
+ * alias / definition registration plus `normalizeInlineResources`) but
78
+ * skips every diagnostic-producing pass — per-resource validation, the
79
+ * Library `env:` check, `validateExtends`, `validateProviderCoherence`,
80
+ * and `validateThrowsCoverage`. Used by the kernel when a previous load
81
+ * has already stamped the manifest set as valid (by content hash), so
82
+ * the registry still gets populated without paying the validation walk
83
+ * on every cold start. The caller takes responsibility for the
84
+ * correctness guarantee — pass this only when something durable
85
+ * (on-disk stamp) attests that the manifests passed a real analyze
86
+ * pass at the same analyzer / kernel version. */
87
+ skipValidation?: boolean;
76
88
  }
77
89
  /** Pre-seeded state for incremental analysis. Passed to StaticAnalyzer.analyze() so it does
78
90
  * not rebuild from scratch on every call. The provided instances are mutated — new definitions
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;qHACqH;AACrH,eAAO,MAAM,kBAAkB;;;;;CAKrB,CAAC;AACX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC;AAE9F,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,cAAc,CAAC;AAErD,MAAM,WAAW,QAAQ;IACvB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,QAAQ,CAAC;IAChB,GAAG,EAAE,QAAQ,CAAC;CACf;AAED;;oDAEoD;AACpD,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAE/C;6EAC6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAExD;;qEAEiE;IACjE,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEjE;;qEAEiE;IACjE,cAAc,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,WAAW;IAC1B;;;+EAG2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,YAAY,CAAC,EAAE,cAAc,EAAE,CAAC;IAChC,qDAAqD;IACrD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,yDAAyD;IACzD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;6FACyF;IACzF,WAAW,CAAC,EAAE,OAAO,sBAAsB,EAAE,WAAW,CAAC;CAC1D;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;gEAKgE;AAChE,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC;IACtD,WAAW,CAAC,EAAE,OAAO,0BAA0B,EAAE,kBAAkB,CAAC;IACpE;;;;+EAI2E;IAC3E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC,CAAC;CAC5E"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;qHACqH;AACrH,eAAO,MAAM,kBAAkB;;;;;CAKrB,CAAC;AACX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC;AAE9F,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,cAAc,CAAC;AAErD,MAAM,WAAW,QAAQ;IACvB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,QAAQ,CAAC;IAChB,GAAG,EAAE,QAAQ,CAAC;CACf;AAED;;oDAEoD;AACpD,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAE/C;6EAC6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAExD;;qEAEiE;IACjE,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEjE;;qEAEiE;IACjE,cAAc,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,WAAW;IAC1B;;;+EAG2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,YAAY,CAAC,EAAE,cAAc,EAAE,CAAC;IAChC,qDAAqD;IACrD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,yDAAyD;IACzD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;6FACyF;IACzF,WAAW,CAAC,EAAE,OAAO,sBAAsB,EAAE,WAAW,CAAC;CAC1D;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;;;;sDAUkD;IAClD,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;gEAKgE;AAChE,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC;IACtD,WAAW,CAAC,EAAE,OAAO,0BAA0B,EAAE,kBAAkB,CAAC;IACpE;;;;+EAI2E;IAC3E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC,CAAC;CAC5E"}
@@ -63,6 +63,11 @@ export declare function pathMatchesScope(exprPath: string, scope: string): boole
63
63
  * `manifestRoot.provide.kind` as a kind name, looks up the kind's Telo.Definition,
64
64
  * and returns the `outputType` schema.
65
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
+ *
66
71
  * - `x-telo-context-ref-from`: existing form — reads `{kind, name}` object from
67
72
  * `manifestItem.<path>`, looks up the named manifest, returns its `<subpath>` field.
68
73
  *
@@ -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,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;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,CA6FrB;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"}
@@ -94,6 +94,11 @@ export function pathMatchesScope(exprPath, scope) {
94
94
  * `manifestRoot.provide.kind` as a kind name, looks up the kind's Telo.Definition,
95
95
  * and returns the `outputType` schema.
96
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
+ *
97
102
  * - `x-telo-context-ref-from`: existing form — reads `{kind, name}` object from
98
103
  * `manifestItem.<path>`, looks up the named manifest, returns its `<subpath>` field.
99
104
  *
@@ -122,30 +127,37 @@ export function resolveContextAnnotations(schema, manifestItem, opts) {
122
127
  };
123
128
  }
124
129
  const fromRoot = schema["x-telo-context-from-root"];
125
- const fromRefKind = schema["x-telo-context-from-ref-kind"];
126
- if (fromRoot || fromRefKind) {
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) {
127
137
  if (fromRoot) {
128
138
  const resolved = navigatePath(manifestRoot, fromRoot.split("/"));
129
139
  if (resolved && typeof resolved === "object" && !Array.isArray(resolved)) {
130
140
  return resolved;
131
141
  }
132
142
  }
133
- if (fromRefKind && defs) {
134
- const hashIdx = fromRefKind.indexOf("#");
135
- if (hashIdx > 0) {
143
+ if (defs) {
144
+ for (const fromRefKind of fromRefKinds) {
145
+ const hashIdx = fromRefKind.indexOf("#");
146
+ if (hashIdx <= 0)
147
+ continue;
136
148
  const refPath = fromRefKind.slice(0, hashIdx);
137
149
  const field = fromRefKind.slice(hashIdx + 1);
138
150
  const kindValue = navigatePath(manifestRoot, refPath.split("/"));
139
- if (typeof kindValue === "string" && kindValue.length > 0) {
140
- const canonical = aliases?.resolveKind(kindValue) ?? kindValue;
141
- const def = defs.resolve(canonical);
142
- const typeField = def
143
- ? def[field]
144
- : undefined;
145
- const resolved = resolveTypeFieldToSchema(typeField, allManifests ?? []);
146
- if (resolved && typeof resolved === "object") {
147
- return resolved;
148
- }
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;
149
161
  }
150
162
  }
151
163
  }
@@ -0,0 +1,23 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import type { AliasResolver } from "./alias-resolver.js";
3
+ import type { DefinitionRegistry } from "./definition-registry.js";
4
+ import { type AnalysisDiagnostic } from "./types.js";
5
+ /**
6
+ * Validates coherence rules for `Telo.Definition` documents that use the `provide:`
7
+ * template target, plus the implementation-presence rule on `Telo.Provider`
8
+ * definitions.
9
+ *
10
+ * Diagnostics:
11
+ * - PROVIDE_ON_NON_PROVIDER: `provide:` declared on a definition whose
12
+ * `capability` is not `Telo.Provider`.
13
+ * - PROVIDE_DISPATCHER_CONFLICT: `provide:` co-exists with `invoke:` or `run:`
14
+ * on the same definition.
15
+ * - PROVIDE_TARGET_UNKNOWN: `provide.name` does not resolve to an entry in
16
+ * `resources:`.
17
+ * - PROVIDE_TARGET_NOT_INVOCABLE: `provide.name` resolves to a resource whose
18
+ * kind is registered but not a `Telo.Invocable`.
19
+ * - PROVIDER_MISSING_IMPLEMENTATION: definition with `capability: Telo.Provider`
20
+ * declares neither `controllers:` (TS-backed) nor `provide:` (template-backed).
21
+ */
22
+ export declare function validateProviderCoherence(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver): AnalysisDiagnostic[];
23
+ //# sourceMappingURL=validate-provider-coherence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-provider-coherence.d.ts","sourceRoot":"","sources":["../src/validate-provider-coherence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CAyItB"}
@@ -0,0 +1,148 @@
1
+ import { DiagnosticSeverity } from "./types.js";
2
+ const SOURCE = "telo-analyzer";
3
+ /**
4
+ * Validates coherence rules for `Telo.Definition` documents that use the `provide:`
5
+ * template target, plus the implementation-presence rule on `Telo.Provider`
6
+ * definitions.
7
+ *
8
+ * Diagnostics:
9
+ * - PROVIDE_ON_NON_PROVIDER: `provide:` declared on a definition whose
10
+ * `capability` is not `Telo.Provider`.
11
+ * - PROVIDE_DISPATCHER_CONFLICT: `provide:` co-exists with `invoke:` or `run:`
12
+ * on the same definition.
13
+ * - PROVIDE_TARGET_UNKNOWN: `provide.name` does not resolve to an entry in
14
+ * `resources:`.
15
+ * - PROVIDE_TARGET_NOT_INVOCABLE: `provide.name` resolves to a resource whose
16
+ * kind is registered but not a `Telo.Invocable`.
17
+ * - PROVIDER_MISSING_IMPLEMENTATION: definition with `capability: Telo.Provider`
18
+ * declares neither `controllers:` (TS-backed) nor `provide:` (template-backed).
19
+ */
20
+ export function validateProviderCoherence(manifests, registry, aliases) {
21
+ const diagnostics = [];
22
+ const importedModules = new Set();
23
+ for (const m of manifests) {
24
+ if (m.kind !== "Telo.Import")
25
+ continue;
26
+ const resolved = m.metadata
27
+ ?.resolvedModuleName;
28
+ if (resolved)
29
+ importedModules.add(resolved);
30
+ }
31
+ for (const m of manifests) {
32
+ if (m.kind !== "Telo.Definition")
33
+ continue;
34
+ const name = m.metadata?.name;
35
+ if (!name)
36
+ continue;
37
+ const ownModule = m.metadata?.module;
38
+ if (ownModule && importedModules.has(ownModule))
39
+ continue;
40
+ const filePath = m.metadata?.source;
41
+ const resource = { kind: m.kind, name };
42
+ const label = `${m.kind}/${name}`;
43
+ const md = m;
44
+ const capability = typeof md.capability === "string" ? md.capability : undefined;
45
+ const provide = md.provide;
46
+ const invoke = md.invoke;
47
+ const run = md.run;
48
+ const controllers = md.controllers;
49
+ const resources = md.resources;
50
+ const hasProvide = provide !== undefined && provide !== null;
51
+ const hasInvoke = invoke !== undefined && invoke !== null;
52
+ const hasRun = run !== undefined && run !== null;
53
+ const hasControllers = Array.isArray(controllers) && controllers.length > 0;
54
+ if (hasProvide && capability !== "Telo.Provider") {
55
+ diagnostics.push({
56
+ severity: DiagnosticSeverity.Error,
57
+ code: "PROVIDE_ON_NON_PROVIDER",
58
+ source: SOURCE,
59
+ message: `${label}: 'provide:' is only valid on definitions with 'capability: Telo.Provider' ` +
60
+ `(found '${capability ?? "<unset>"}'). Use 'invoke:' or 'run:' for other capabilities.`,
61
+ data: { resource, filePath, path: "provide" },
62
+ });
63
+ }
64
+ if (hasProvide && (hasInvoke || hasRun)) {
65
+ const conflict = hasInvoke ? "invoke" : "run";
66
+ diagnostics.push({
67
+ severity: DiagnosticSeverity.Error,
68
+ code: "PROVIDE_DISPATCHER_CONFLICT",
69
+ source: SOURCE,
70
+ message: `${label}: 'provide:' cannot co-exist with '${conflict}:'. ` +
71
+ `A definition declares exactly one dispatch entry-point.`,
72
+ data: { resource, filePath, path: "provide" },
73
+ });
74
+ }
75
+ if (hasProvide && typeof provide === "object" && !Array.isArray(provide)) {
76
+ const provideObj = provide;
77
+ const providedName = typeof provideObj.name === "string" ? provideObj.name : undefined;
78
+ const providedKind = typeof provideObj.kind === "string" ? provideObj.kind : undefined;
79
+ if (providedName && Array.isArray(resources)) {
80
+ const match = resources.find((r) => {
81
+ const meta = r?.metadata;
82
+ return typeof meta?.name === "string" && meta.name === providedName;
83
+ });
84
+ if (!match) {
85
+ diagnostics.push({
86
+ severity: DiagnosticSeverity.Error,
87
+ code: "PROVIDE_TARGET_UNKNOWN",
88
+ source: SOURCE,
89
+ message: `${label}: 'provide.name: ${providedName}' does not match any entry's ` +
90
+ `metadata.name in 'resources:'.`,
91
+ data: { resource, filePath, path: "provide.name" },
92
+ });
93
+ }
94
+ else if (typeof match.kind === "string") {
95
+ // `provide.kind` is the type contract the analyzer uses to type
96
+ // `result` CEL against the target's `outputType`. The runtime
97
+ // dispatches on `provide.name` and ignores `provide.kind`, so a
98
+ // mismatch silently degrades `result` typing to an open schema
99
+ // (and at runtime quietly invokes the actually-matched resource).
100
+ // Flag the divergence so result-typing never lies.
101
+ if (providedKind) {
102
+ const providedCanonical = aliases.resolveKind(providedKind) ?? providedKind;
103
+ const matchCanonical = aliases.resolveKind(match.kind) ?? match.kind;
104
+ if (providedCanonical !== matchCanonical) {
105
+ diagnostics.push({
106
+ severity: DiagnosticSeverity.Error,
107
+ code: "PROVIDE_KIND_MISMATCH",
108
+ source: SOURCE,
109
+ message: `${label}: 'provide.kind: ${providedKind}' disagrees with the matched ` +
110
+ `'resources:' entry's kind '${match.kind}' (matched by metadata.name ` +
111
+ `'${providedName}'). The runtime dispatches by name, so 'provide.kind' ` +
112
+ `is decorative — but the analyzer types 'result:' against it, and a ` +
113
+ `mismatch silently turns off that typing.`,
114
+ data: { resource, filePath, path: "provide.kind" },
115
+ });
116
+ }
117
+ }
118
+ const resolvedKind = aliases.resolveKind(match.kind) ?? match.kind;
119
+ const targetDef = registry.resolve(resolvedKind) ?? registry.resolve(match.kind);
120
+ if (targetDef && targetDef.kind === "Telo.Definition") {
121
+ const targetCap = targetDef.capability;
122
+ if (typeof targetCap === "string" && targetCap !== "Telo.Invocable") {
123
+ diagnostics.push({
124
+ severity: DiagnosticSeverity.Error,
125
+ code: "PROVIDE_TARGET_NOT_INVOCABLE",
126
+ source: SOURCE,
127
+ message: `${label}: 'provide.name: ${providedName}' resolves to a ${match.kind} ` +
128
+ `(capability '${targetCap}'); 'provide:' requires a Telo.Invocable target.`,
129
+ data: { resource, filePath, path: "provide.name" },
130
+ });
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }
136
+ if (capability === "Telo.Provider" && !hasControllers && !hasProvide) {
137
+ diagnostics.push({
138
+ severity: DiagnosticSeverity.Error,
139
+ code: "PROVIDER_MISSING_IMPLEMENTATION",
140
+ source: SOURCE,
141
+ message: `${label}: 'capability: Telo.Provider' requires either 'controllers:' ` +
142
+ `(TS-backed) or 'provide:' (template-backed) to declare an implementation.`,
143
+ data: { resource, filePath, path: "capability" },
144
+ });
145
+ }
146
+ }
147
+ return diagnostics;
148
+ }