@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
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="./assets/telo.png" alt="Telo" width="200" />
2
+ <img src="https://raw.githubusercontent.com/telorun/telo/main/assets/telo.png" alt="Telo" width="200" />
3
3
  </p>
4
4
 
5
5
  <h1 align="center">Telo</h1>
@@ -61,12 +61,12 @@ targets:
61
61
  kind: Telo.Import
62
62
  metadata:
63
63
  name: Http
64
- source: ../modules/http-server
64
+ source: std/http-server@0.4.0
65
65
  ---
66
66
  kind: Telo.Import
67
67
  metadata:
68
68
  name: Sql
69
- source: ../modules/sql
69
+ source: std/sql@0.2.3
70
70
  ---
71
71
  # SQLite database — swap driver/host/database for PostgreSQL with zero YAML changes
72
72
  kind: Sql.Connection
@@ -41,6 +41,13 @@ export declare class AnalysisRegistry {
41
41
  /** Returns the closest user-facing kind to `badKind`, or undefined when nothing
42
42
  * is close enough (or multiple candidates tie). Case-sensitive. */
43
43
  suggestKind(badKind: string): string | undefined;
44
+ /** Returns every user-facing (alias-form) kind that satisfies the given
45
+ * `x-telo-ref` constraint string (e.g. `"telo#Invocable"`, `"std/sql#Connection"`).
46
+ * Resolution mirrors `validateReferences.checkKind`: abstract targets expand to
47
+ * the set of definitions extending them; concrete targets yield just themselves.
48
+ * Returns `undefined` when the ref can't be resolved (e.g. unregistered identity),
49
+ * so callers can fall back to the unfiltered kind list. */
50
+ userFacingKindsForRef(xTeloRef: string): string[] | undefined;
44
51
  /** @internal Bridge for StaticAnalyzer — do not use outside the analyzer package. */
45
52
  _context(): AnalysisContext;
46
53
  }
@@ -1 +1 @@
1
- {"version":3,"file":"analysis-registry.d.ts","sourceRoot":"","sources":["../src/analysis-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;;GAIG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;IAEpE,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI;IAIjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAIpE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAIpE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI7C;;;;;;;OAOG;IACH,mBAAmB,CACjB,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EAClC,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GACnC,IAAI;IAkBP;;;;OAIG;IACH,kBAAkB,IAAI,kBAAkB,EAAE;IAI1C,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAM/D,QAAQ,IAAI,MAAM,EAAE;IAIpB;mEAC+D;IAC/D,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAIxC;;;gCAG4B;IAC5B,oBAAoB,IAAI,MAAM,EAAE;IAIhC;wEACoE;IACpE,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIhD,qFAAqF;IACrF,QAAQ,IAAI,eAAe;CAG5B"}
1
+ {"version":3,"file":"analysis-registry.d.ts","sourceRoot":"","sources":["../src/analysis-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;;GAIG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;IAEpE,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI;IAIjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAIpE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAIpE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI7C;;;;;;;OAOG;IACH,mBAAmB,CACjB,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EAClC,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GACnC,IAAI;IAkBP;;;;OAIG;IACH,kBAAkB,IAAI,kBAAkB,EAAE;IAI1C,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAM/D,QAAQ,IAAI,MAAM,EAAE;IAIpB;mEAC+D;IAC/D,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAIxC;;;gCAG4B;IAC5B,oBAAoB,IAAI,MAAM,EAAE;IAIhC;wEACoE;IACpE,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIhD;;;;;gEAK4D;IAC5D,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IA+B7D,qFAAqF;IACrF,QAAQ,IAAI,eAAe;CAG5B"}
@@ -79,6 +79,44 @@ export class AnalysisRegistry {
79
79
  suggestKind(badKind) {
80
80
  return computeSuggestKind(badKind, this.aliases, this.defs);
81
81
  }
82
+ /** Returns every user-facing (alias-form) kind that satisfies the given
83
+ * `x-telo-ref` constraint string (e.g. `"telo#Invocable"`, `"std/sql#Connection"`).
84
+ * Resolution mirrors `validateReferences.checkKind`: abstract targets expand to
85
+ * the set of definitions extending them; concrete targets yield just themselves.
86
+ * Returns `undefined` when the ref can't be resolved (e.g. unregistered identity),
87
+ * so callers can fall back to the unfiltered kind list. */
88
+ userFacingKindsForRef(xTeloRef) {
89
+ const targetKind = this.defs.resolveRef(xTeloRef);
90
+ if (!targetKind)
91
+ return undefined;
92
+ const targetDef = this.defs.resolve(targetKind);
93
+ if (!targetDef)
94
+ return undefined;
95
+ const canonicalKinds = [];
96
+ if (targetDef.kind === "Telo.Abstract") {
97
+ for (const def of this.defs.getByExtends(targetKind)) {
98
+ const module = def.metadata?.module;
99
+ if (module && def.metadata?.name) {
100
+ canonicalKinds.push(`${module}.${def.metadata.name}`);
101
+ }
102
+ }
103
+ }
104
+ else {
105
+ canonicalKinds.push(targetKind);
106
+ }
107
+ const out = new Set();
108
+ for (const kind of canonicalKinds) {
109
+ const dot = kind.indexOf(".");
110
+ if (dot === -1)
111
+ continue;
112
+ const moduleName = kind.slice(0, dot);
113
+ const typeName = kind.slice(dot + 1);
114
+ for (const alias of this.aliases.aliasesFor(moduleName)) {
115
+ out.add(`${alias}.${typeName}`);
116
+ }
117
+ }
118
+ return Array.from(out);
119
+ }
82
120
  /** @internal Bridge for StaticAnalyzer — do not use outside the analyzer package. */
83
121
  _context() {
84
122
  return { aliases: this.aliases, definitions: this.defs, aliasesByModule: this.aliasesByModule };
@@ -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;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAa9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAgX/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,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAqUvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CAAC,SAAS,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,gBAAgB,GAAG,gBAAgB,EAAE;IAUxF,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;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAc9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AA+c/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,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IA6cvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CAAC,SAAS,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,gBAAgB,GAAG,gBAAgB,EAAE;IAUxF,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
@@ -7,10 +7,12 @@ import { buildKernelGlobalsSchema, mergeKernelGlobalsIntoContext } from "./kerne
7
7
  import { computeSuggestKind } from "./kind-suggest.js";
8
8
  import { isModuleKind } from "./module-kinds.js";
9
9
  import { normalizeInlineResources } from "./normalize-inline-resources.js";
10
+ import { rewriteSyntheticOrigins } from "./rewrite-synthetic-origins.js";
10
11
  import { celTypeSatisfiesJsonSchema, substituteCelFields, validateAgainstSchema, } from "./schema-compat.js";
11
12
  import { DiagnosticSeverity } from "./types.js";
12
13
  import { getManifestItem, pathMatchesScope, resolveContextAnnotations, resolveTypeFieldToSchema, } from "./validate-cel-context.js";
13
14
  import { validateExtends } from "./validate-extends.js";
15
+ import { validateProviderCoherence } from "./validate-provider-coherence.js";
14
16
  import { validateReferences } from "./validate-references.js";
15
17
  import { validateThrowsCoverage } from "./validate-throws-coverage.js";
16
18
  const SELF_PREFIX = "Self.";
@@ -37,12 +39,89 @@ function lookupDefinitionTypeField(invokedKind, fieldName, defs, aliases, allMan
37
39
  return resolveTypeFieldToSchema(value, allManifests);
38
40
  }
39
41
  const SOURCE = "telo-analyzer";
42
+ /** Build a closed JSON Schema for the `self` CEL variable available inside a
43
+ * `Telo.Definition` template body. Mirrors the runtime template controller's
44
+ * `const self = { ...resource, name: resource.metadata.name };` — every
45
+ * property the user declared in `schema:` plus synthetic `name` / `kind` and
46
+ * the metadata sub-object (kept open since metadata legitimately carries
47
+ * arbitrary user-added fields). */
48
+ function buildSelfSchema(definition) {
49
+ const userSchema = (definition.schema ?? {});
50
+ const userProps = (userSchema.properties ?? {});
51
+ const userRequired = Array.isArray(userSchema.required) ? userSchema.required : [];
52
+ return {
53
+ type: "object",
54
+ additionalProperties: false,
55
+ properties: {
56
+ ...userProps,
57
+ name: { type: "string" },
58
+ kind: { type: "string" },
59
+ metadata: {
60
+ type: "object",
61
+ additionalProperties: true,
62
+ properties: { name: { type: "string" } },
63
+ },
64
+ },
65
+ required: [...userRequired, "name", "kind"],
66
+ };
67
+ }
68
+ /** Build the JSON Schema for the `inputs` CEL variable available inside an
69
+ * invocable template body. Three-layer fallback mirroring the runtime's
70
+ * caller-supplied inputs:
71
+ * 1. The definition's own `inputType:` field (preferred).
72
+ * 2. The `extends:`-declared abstract's `inputType:` (so a concrete
73
+ * definition inheriting a contract gets typed inputs without
74
+ * redeclaring them).
75
+ * 3. Undefined — caller signals opaque `map<string, dyn>` upstream. */
76
+ function lookupTemplateInputsSchema(definition, defs, aliases, allManifests) {
77
+ const own = resolveTypeFieldToSchema(definition.inputType, allManifests);
78
+ if (own)
79
+ return own;
80
+ const ext = definition.extends;
81
+ if (typeof ext === "string" && ext.length > 0) {
82
+ const canonical = aliases.resolveKind(ext) ?? ext;
83
+ const abstractDef = defs.resolve(canonical);
84
+ if (abstractDef) {
85
+ const inherited = resolveTypeFieldToSchema(abstractDef.inputType, allManifests);
86
+ if (inherited)
87
+ return inherited;
88
+ }
89
+ }
90
+ return undefined;
91
+ }
92
+ /** Returns a "resolver-facing" view of the manifest where the fields used as
93
+ * navigation roots by Telo.Definition's `x-telo-context-from-root` annotations
94
+ * have been pre-augmented:
95
+ * - `schema` → augmented `self` schema (synthetic `name`/`kind`/metadata).
96
+ * - `inputType` → resolved with extends fallback when the field isn't
97
+ * declared directly on the definition.
98
+ *
99
+ * For non-definition manifests the original object is returned. */
100
+ function manifestRootForResolver(m, defs, aliases, allManifests) {
101
+ if (m.kind !== "Telo.Definition")
102
+ return m;
103
+ const inputs = lookupTemplateInputsSchema(m, defs, aliases, allManifests);
104
+ return {
105
+ ...m,
106
+ schema: buildSelfSchema(m),
107
+ ...(inputs ? { inputType: inputs } : {}),
108
+ };
109
+ }
40
110
  /**
41
111
  * Walk a JSON Schema tree and collect all `x-telo-context` annotations,
42
112
  * returning them as `{ scope, schema }` pairs using JSONPath-style scopes —
43
113
  * the same format the analyzer uses for CEL context validation.
114
+ *
115
+ * Result is sorted by scope specificity (longer scope first) so that the
116
+ * per-expression resolver's first-match-wins logic picks the most-specific
117
+ * context. Without this, a broader ancestor scope (e.g. `$.resources[*]`)
118
+ * could shadow a narrower descendant scope whose activation differs.
44
119
  */
45
120
  function extractContextsFromSchema(schema, path = "$") {
121
+ const all = collectContexts(schema, path);
122
+ return all.sort((a, b) => b.scope.length - a.scope.length);
123
+ }
124
+ function collectContexts(schema, path) {
46
125
  if (!schema || typeof schema !== "object")
47
126
  return [];
48
127
  const results = [];
@@ -51,16 +130,16 @@ function extractContextsFromSchema(schema, path = "$") {
51
130
  }
52
131
  if (schema.properties) {
53
132
  for (const [key, value] of Object.entries(schema.properties)) {
54
- results.push(...extractContextsFromSchema(value, `${path}.${key}`));
133
+ results.push(...collectContexts(value, `${path}.${key}`));
55
134
  }
56
135
  }
57
136
  if (schema.items && typeof schema.items === "object") {
58
- results.push(...extractContextsFromSchema(schema.items, `${path}[*]`));
137
+ results.push(...collectContexts(schema.items, `${path}[*]`));
59
138
  }
60
139
  for (const key of ["oneOf", "anyOf", "allOf"]) {
61
140
  if (Array.isArray(schema[key])) {
62
141
  for (const subschema of schema[key]) {
63
- results.push(...extractContextsFromSchema(subschema, path));
142
+ results.push(...collectContexts(subschema, path));
64
143
  }
65
144
  }
66
145
  }
@@ -420,6 +499,36 @@ export class StaticAnalyzer {
420
499
  byName.set(m.metadata.name, m);
421
500
  }
422
501
  }
502
+ // Library env: rejection — `env:` on a Library `variables` / `secrets`
503
+ // entry is forbidden. The Library entry schema is otherwise open so that
504
+ // any JSON Schema property schema is valid; this targeted check produces
505
+ // a clear diagnostic instead of a generic "additional property" error.
506
+ for (const m of allManifests) {
507
+ if (m.kind !== "Telo.Library")
508
+ continue;
509
+ const filePath = m.metadata?.source;
510
+ const moduleName = m.metadata?.name;
511
+ const resource = moduleName ? { kind: m.kind, name: moduleName } : undefined;
512
+ for (const block of ["variables", "secrets"]) {
513
+ const entries = m[block];
514
+ if (!entries || typeof entries !== "object" || Array.isArray(entries))
515
+ continue;
516
+ for (const [entryName, entry] of Object.entries(entries)) {
517
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
518
+ continue;
519
+ if ("env" in entry) {
520
+ diagnostics.push({
521
+ severity: DiagnosticSeverity.Error,
522
+ code: "LIBRARY_ENV_KEY_REJECTED",
523
+ source: SOURCE,
524
+ message: `Telo.Library ${block}/${entryName}: 'env:' is only permitted on Telo.Application entries. ` +
525
+ `Libraries must receive values from importers via the parent manifest's variables / secrets block.`,
526
+ data: { resource, filePath, path: `${block}.${entryName}.env` },
527
+ });
528
+ }
529
+ }
530
+ }
531
+ }
423
532
  // Build typed kernel globals schema so x-telo-context chain validation
424
533
  // recognises variables, secrets, resources, env automatically
425
534
  const kernelGlobals = buildKernelGlobalsSchema(allManifests);
@@ -436,7 +545,11 @@ export class StaticAnalyzer {
436
545
  });
437
546
  continue;
438
547
  }
439
- if (m.kind === "Telo.Definition" || m.kind === "Telo.Abstract") {
548
+ // Abstracts carry only inputType / outputType schema fields and no template
549
+ // body — nothing for the per-resource walk to validate. Definitions are now
550
+ // walked: their template bodies (`resources` / `invoke` / `run` / `provide`)
551
+ // contain CEL that must be checked against `self` / `inputs` / `result`.
552
+ if (m.kind === "Telo.Abstract") {
440
553
  continue;
441
554
  }
442
555
  const resource = { kind: m.kind, name: m.metadata?.name };
@@ -489,6 +602,75 @@ export class StaticAnalyzer {
489
602
  }
490
603
  // (Invocation context compatibility check is handled via x-telo-context in the CEL pass below)
491
604
  }
605
+ // Template-body structural validations: check that template entry-points produce
606
+ // values matching the contract of their dispatch target and (for `provide:`)
607
+ // the abstract this definition `extends`. CEL fields inside the templated
608
+ // values are replaced with type-appropriate placeholders before AJV runs —
609
+ // same pattern as the per-resource schema validation above.
610
+ for (const m of allManifests) {
611
+ if (m.kind !== "Telo.Definition")
612
+ continue;
613
+ const filePath = m.metadata?.source;
614
+ const name = m.metadata?.name;
615
+ if (!name)
616
+ continue;
617
+ const resource = { kind: m.kind, name };
618
+ const md = m;
619
+ const emitTargetMismatch = (targetKind, valueSchema, value, path) => {
620
+ const substituted = substituteCelFields(value, valueSchema);
621
+ const issues = validateAgainstSchema(substituted, valueSchema);
622
+ for (const issue of issues) {
623
+ diagnostics.push({
624
+ severity: DiagnosticSeverity.Error,
625
+ code: "TEMPLATE_TARGET_MISMATCH",
626
+ source: SOURCE,
627
+ message: `${m.kind}/${name}: ${path} does not satisfy ${targetKind}'s contract: ${issue.message}`,
628
+ data: { resource, filePath, path: issue.path ? `${path}.${issue.path}` : path },
629
+ });
630
+ }
631
+ };
632
+ // Resolve the dispatch target's kind, if statically known. Object-form
633
+ // `invoke: { kind, name }` and `provide: { kind, name }` carry it; the
634
+ // string-form `invoke: "name"` does not (the matching resource entry would
635
+ // need to be located by expanded name — out of scope here).
636
+ const invoke = md.invoke;
637
+ const provide = md.provide;
638
+ let dispatchKind;
639
+ if (invoke && typeof invoke === "object" && !Array.isArray(invoke) && typeof invoke.kind === "string") {
640
+ dispatchKind = invoke.kind;
641
+ }
642
+ else if (provide &&
643
+ typeof provide === "object" &&
644
+ !Array.isArray(provide) &&
645
+ typeof provide.kind === "string") {
646
+ dispatchKind = provide.kind;
647
+ }
648
+ // Top-level `inputs:` (sibling of `invoke:` / `provide:`) carries the
649
+ // values passed to the dispatch target's invoke(). Validate against the
650
+ // target's declared `inputType` when both sides have one.
651
+ if (dispatchKind && md.inputs && typeof md.inputs === "object") {
652
+ const targetSchema = lookupDefinitionTypeField(dispatchKind, "inputType", defs, aliases, allManifests);
653
+ if (targetSchema) {
654
+ emitTargetMismatch(dispatchKind, targetSchema, md.inputs, "inputs");
655
+ }
656
+ }
657
+ // Top-level `result:` is a post-call mapping that must satisfy the abstract
658
+ // this definition `extends` (`outputType`). It's a sibling of whichever
659
+ // dispatch entry-point declared a kind-typed target (`provide:` or
660
+ // `invoke:`). The target's outputType lives on the dispatcher's `kind`
661
+ // and is what `result` is typed against *inside* CEL — separate role.
662
+ const hasDispatchObject = (provide && typeof provide === "object" && !Array.isArray(provide)) ||
663
+ (invoke && typeof invoke === "object" && !Array.isArray(invoke));
664
+ if (hasDispatchObject && md.result && typeof md.result === "object") {
665
+ const extendsValue = md.extends;
666
+ if (typeof extendsValue === "string" && extendsValue.length > 0) {
667
+ const abstractSchema = lookupDefinitionTypeField(extendsValue, "outputType", defs, aliases, allManifests);
668
+ if (abstractSchema) {
669
+ emitTargetMismatch(extendsValue, abstractSchema, md.result, "result");
670
+ }
671
+ }
672
+ }
673
+ }
492
674
  // Validate CEL syntax and context variable access in all manifests
493
675
  for (const m of allManifests) {
494
676
  const resource = { kind: m.kind, name: m.metadata?.name };
@@ -533,7 +715,13 @@ export class StaticAnalyzer {
533
715
  const manifestItem = matchedScope
534
716
  ? getManifestItem(path, matchedScope, m)
535
717
  : m;
536
- const resolvedContext = resolveContextAnnotations(matchedContext, manifestItem, allManifests);
718
+ const rootForResolver = manifestRootForResolver(m, defs, aliases, allManifests);
719
+ const resolvedContext = resolveContextAnnotations(matchedContext, manifestItem, {
720
+ manifestRoot: rootForResolver,
721
+ defs,
722
+ aliases,
723
+ allManifests: allManifests,
724
+ });
537
725
  effectiveContext = mergeKernelGlobalsIntoContext(resolvedContext, kernelGlobals);
538
726
  }
539
727
  const engine = defaultRegistry().get(engineName);
@@ -578,9 +766,13 @@ export class StaticAnalyzer {
578
766
  diagnostics.push(...validateReferences(allManifests, { aliases, definitions: defs, aliasesByModule }));
579
767
  // Validate `extends` fields and flag legacy `capability: <UserAbstract>` overload.
580
768
  diagnostics.push(...validateExtends(allManifests, defs, aliases));
769
+ // Validate provider coherence rules for `provide:` template-target definitions.
770
+ diagnostics.push(...validateProviderCoherence(allManifests, defs, aliases));
581
771
  // Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7)
582
772
  diagnostics.push(...validateThrowsCoverage(allManifests, defs, aliases, this.celEnv));
583
- return diagnostics;
773
+ // Reroute diagnostics on synthetic (inline-extracted) resources back to
774
+ // the chain root so position-index lookups land on the parent doc.
775
+ return rewriteSyntheticOrigins(diagnostics, allManifests);
584
776
  }
585
777
  analyzeErrors(manifests, options, registry) {
586
778
  return this.analyze(manifests, options, registry).filter((d) => d.severity === DiagnosticSeverity.Error);
@@ -1 +1 @@
1
- {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EAsJ/C,CAAC"}
1
+ {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EAmT/C,CAAC"}
package/dist/builtins.js CHANGED
@@ -38,7 +38,126 @@ export const KERNEL_BUILTINS = [
38
38
  kind: "Telo.Definition",
39
39
  metadata: { name: "Definition", module: "Telo" },
40
40
  capability: "Telo.Template",
41
- schema: { type: "object" },
41
+ // Top-level shape stays open (`additionalProperties: true`) so this change
42
+ // attaches x-telo-context annotations to known template-body fields without
43
+ // tightening the Telo.Definition shape itself. The annotations drive
44
+ // static CEL validation of expressions inside `resources:` / `invoke:` /
45
+ // `run:` / `provide:` / top-level `inputs:` / top-level `result:` against
46
+ // `self` (typed from `schema:`) and `inputs` (typed from `inputType:`,
47
+ // falling back to the extends-declared abstract).
48
+ //
49
+ // `inputs:` and `result:` live as top-level siblings of `invoke:` / `provide:`,
50
+ // matching how Run.Sequence steps factor dispatch from data. The dispatch
51
+ // entry-point (`invoke` / `provide` / `run`) determines how `inputs`/`result`
52
+ // are interpreted at runtime. See analyzer/nodejs/plans/template-internal-cel-validation.md.
53
+ schema: {
54
+ type: "object",
55
+ additionalProperties: true,
56
+ properties: {
57
+ resources: {
58
+ type: "array",
59
+ items: {
60
+ type: "object",
61
+ additionalProperties: true,
62
+ "x-telo-context": {
63
+ type: "object",
64
+ additionalProperties: false,
65
+ properties: {
66
+ self: { "x-telo-context-from-root": "schema" },
67
+ inputs: { "x-telo-context-from-root": "inputType" },
68
+ },
69
+ },
70
+ },
71
+ },
72
+ invoke: {
73
+ oneOf: [
74
+ {
75
+ type: "string",
76
+ "x-telo-context": {
77
+ type: "object",
78
+ additionalProperties: false,
79
+ properties: {
80
+ self: { "x-telo-context-from-root": "schema" },
81
+ },
82
+ },
83
+ },
84
+ {
85
+ type: "object",
86
+ additionalProperties: true,
87
+ properties: {
88
+ kind: { type: "string" },
89
+ name: {
90
+ type: "string",
91
+ "x-telo-context": {
92
+ type: "object",
93
+ additionalProperties: false,
94
+ properties: {
95
+ self: { "x-telo-context-from-root": "schema" },
96
+ },
97
+ },
98
+ },
99
+ },
100
+ },
101
+ ],
102
+ },
103
+ provide: {
104
+ type: "object",
105
+ additionalProperties: true,
106
+ properties: {
107
+ kind: { type: "string" },
108
+ name: {
109
+ type: "string",
110
+ "x-telo-context": {
111
+ type: "object",
112
+ additionalProperties: false,
113
+ properties: {
114
+ self: { "x-telo-context-from-root": "schema" },
115
+ },
116
+ },
117
+ },
118
+ },
119
+ },
120
+ run: {
121
+ type: "string",
122
+ "x-telo-context": {
123
+ type: "object",
124
+ additionalProperties: false,
125
+ properties: {
126
+ self: { "x-telo-context-from-root": "schema" },
127
+ },
128
+ },
129
+ },
130
+ inputs: {
131
+ type: "object",
132
+ additionalProperties: true,
133
+ "x-telo-context": {
134
+ type: "object",
135
+ additionalProperties: false,
136
+ properties: {
137
+ self: { "x-telo-context-from-root": "schema" },
138
+ inputs: { "x-telo-context-from-root": "inputType" },
139
+ },
140
+ },
141
+ },
142
+ result: {
143
+ type: "object",
144
+ additionalProperties: true,
145
+ "x-telo-context": {
146
+ type: "object",
147
+ additionalProperties: false,
148
+ properties: {
149
+ self: { "x-telo-context-from-root": "schema" },
150
+ result: {
151
+ "x-telo-context-from-ref-kind": [
152
+ "provide/kind#outputType",
153
+ "invoke/kind#outputType",
154
+ ],
155
+ },
156
+ },
157
+ },
158
+ },
159
+ },
160
+ },
42
161
  },
43
162
  {
44
163
  kind: "Telo.Definition",
@@ -106,6 +225,44 @@ export const KERNEL_BUILTINS = [
106
225
  type: "array",
107
226
  items: { type: "string" },
108
227
  },
228
+ // Application-level environment contract. Each entry layers `env:`
229
+ // (required, names the source env var) and `default:` (optional, used
230
+ // when the env var is unset) on top of an open JSON Schema property
231
+ // schema. `type:` constrains the coercion rule applied to the raw env
232
+ // string (scalars per-type; `object` / `array` via JSON.parse with the
233
+ // matching top-level type). All other JSON Schema keywords are passed
234
+ // through unchanged and applied to the coerced value via the standard
235
+ // schema validator. See kernel/nodejs/src/application-env.ts.
236
+ variables: {
237
+ type: "object",
238
+ additionalProperties: {
239
+ type: "object",
240
+ required: ["env", "type"],
241
+ properties: {
242
+ env: { type: "string" },
243
+ type: {
244
+ type: "string",
245
+ enum: ["string", "integer", "number", "boolean", "object", "array"],
246
+ },
247
+ default: {},
248
+ },
249
+ },
250
+ },
251
+ secrets: {
252
+ type: "object",
253
+ additionalProperties: {
254
+ type: "object",
255
+ required: ["env", "type"],
256
+ properties: {
257
+ env: { type: "string" },
258
+ type: {
259
+ type: "string",
260
+ enum: ["string", "integer", "number", "boolean", "object", "array"],
261
+ },
262
+ default: {},
263
+ },
264
+ },
265
+ },
109
266
  },
110
267
  required: ["metadata"],
111
268
  additionalProperties: false,
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export { isModuleKind, MODULE_KINDS } from "./module-kinds.js";
7
7
  export type { ModuleKind } from "./module-kinds.js";
8
8
  export { parseLoadedFile } from "./parse-loaded-file.js";
9
9
  export type { ParseOptions } from "./parse-loaded-file.js";
10
+ export { residualEntrySchema, residualEntrySchemaMap } from "./residual-schema.js";
10
11
  export { buildDocumentPositions, buildLineOffsets, buildPositionIndex, documentLineOffsets, } from "./position-metadata.js";
11
12
  export type { DocumentPosition } from "./position-metadata.js";
12
13
  export { HttpSource } from "./sources/http-source.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EACR,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,UAAU,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACH,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC3E,YAAY,EACR,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,QAAQ,EACR,aAAa,EACb,KAAK,EACR,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EACR,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,UAAU,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EACH,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC3E,YAAY,EACR,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,QAAQ,EACR,aAAa,EACb,KAAK,EACR,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ export { flattenForAnalyzer, flattenLoadedModule } from "./flatten-for-analyzer.
4
4
  export { Loader } from "./manifest-loader.js";
5
5
  export { isModuleKind, MODULE_KINDS } from "./module-kinds.js";
6
6
  export { parseLoadedFile } from "./parse-loaded-file.js";
7
+ export { residualEntrySchema, residualEntrySchemaMap } from "./residual-schema.js";
7
8
  export { buildDocumentPositions, buildLineOffsets, buildPositionIndex, documentLineOffsets, } from "./position-metadata.js";
8
9
  export { HttpSource } from "./sources/http-source.js";
9
10
  export { RegistrySource } from "./sources/registry-source.js";
@@ -1 +1 @@
1
- {"version":3,"file":"kernel-globals.d.ts","sourceRoot":"","sources":["../src/kernel-globals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,uDAAwD,CAAC;AASzF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,gBAAgB,EAAE,GAC5B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAiCrB;AA2BD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAClC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACjC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CASrB"}
1
+ {"version":3,"file":"kernel-globals.d.ts","sourceRoot":"","sources":["../src/kernel-globals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,uDAAwD,CAAC;AASzF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,gBAAgB,EAAE,GAC5B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAiCrB;AAwBD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAClC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACjC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CASrB"}
@@ -1,3 +1,4 @@
1
+ import { residualEntrySchemaMap } from "./residual-schema.js";
1
2
  /**
2
3
  * Kernel global names available in every CEL evaluation context at runtime.
3
4
  * Both `buildKernelGlobalsSchema` (chain-access validation) and
@@ -59,18 +60,15 @@ export function buildKernelGlobalsSchema(manifests) {
59
60
  };
60
61
  }
61
62
  /** Wrap a JSON Schema property map (like `Telo.Application.variables`) into a
62
- * closed object schema suitable for chain-access validation. Falls back to
63
- * an open map when the module declares no variables/secrets. */
63
+ * closed object schema suitable for chain-access validation. For Application
64
+ * entries the per-entry shape carries kernel-specific keys (`env`, `default`)
65
+ * on top of an otherwise-standard JSON Schema property schema; those keys are
66
+ * stripped via `residualEntrySchemaMap` so CEL sees the coerced shape, not
67
+ * the env-binding wrapper. Library entries are pure JSON Schema property
68
+ * schemas and pass through the same call unchanged. Falls back to an open map
69
+ * when the module declares no variables/secrets. */
64
70
  function buildSchemaMapSchema(schemaMap) {
65
- if (!schemaMap || typeof schemaMap !== "object" || Array.isArray(schemaMap)) {
66
- return { type: "object", additionalProperties: true };
67
- }
68
- const props = {};
69
- for (const [key, value] of Object.entries(schemaMap)) {
70
- if (value !== null && typeof value === "object" && !Array.isArray(value)) {
71
- props[key] = value;
72
- }
73
- }
71
+ const props = residualEntrySchemaMap(schemaMap);
74
72
  if (Object.keys(props).length === 0) {
75
73
  return { type: "object", additionalProperties: true };
76
74
  }
@@ -1 +1 @@
1
- {"version":3,"file":"normalize-inline-resources.d.ts","sourceRoot":"","sources":["../src/normalize-inline-resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAczD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,gBAAgB,EAAE,CAwDpB"}
1
+ {"version":3,"file":"normalize-inline-resources.d.ts","sourceRoot":"","sources":["../src/normalize-inline-resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAczD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,gBAAgB,EAAE,CA+DpB"}