@telorun/analyzer 0.13.0 → 0.15.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.
@@ -4,6 +4,12 @@ export declare class AliasResolver {
4
4
  private readonly importAliases;
5
5
  private readonly importedKinds;
6
6
  registerImport(alias: string, targetModule: string, exportedKinds: string[]): void;
7
+ /** Real module name an alias points at (e.g. "Console" → "console"), or undefined.
8
+ * Used to resolve an alias-qualified instance reference "Console.writeLine" to the
9
+ * forwarded resource declared in that module. The `exports.resources` gate is enforced
10
+ * upstream by `flattenForAnalyzer` (only exported instances are forwarded), so a name
11
+ * that isn't exported simply won't be found. */
12
+ moduleForAlias(alias: string): string | undefined;
7
13
  /** Resolves "Http.Api" → "http-server.Api". Returns undefined if alias is unknown. */
8
14
  resolveKind(kind: string): string | undefined;
9
15
  hasAlias(alias: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"alias-resolver.d.ts","sourceRoot":"","sources":["../src/alias-resolver.ts"],"names":[],"mappings":"AAAA;gFACgF;AAChF,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAEhE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,IAAI;IAOlF,sFAAsF;IACtF,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAe7C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIhC,YAAY,IAAI,MAAM,EAAE;IAIxB;;qEAEiE;IACjE,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE;CAO3C"}
1
+ {"version":3,"file":"alias-resolver.d.ts","sourceRoot":"","sources":["../src/alias-resolver.ts"],"names":[],"mappings":"AAAA;gFACgF;AAChF,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAEhE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,IAAI;IAOlF;;;;qDAIiD;IACjD,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIjD,sFAAsF;IACtF,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAe7C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIhC,YAAY,IAAI,MAAM,EAAE;IAIxB;;qEAEiE;IACjE,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE;CAO3C"}
@@ -9,6 +9,14 @@ export class AliasResolver {
9
9
  this.importedKinds.set(alias, new Set(exportedKinds));
10
10
  }
11
11
  }
12
+ /** Real module name an alias points at (e.g. "Console" → "console"), or undefined.
13
+ * Used to resolve an alias-qualified instance reference "Console.writeLine" to the
14
+ * forwarded resource declared in that module. The `exports.resources` gate is enforced
15
+ * upstream by `flattenForAnalyzer` (only exported instances are forwarded), so a name
16
+ * that isn't exported simply won't be found. */
17
+ moduleForAlias(alias) {
18
+ return this.importAliases.get(alias);
19
+ }
12
20
  /** Resolves "Http.Api" → "http-server.Api". Returns undefined if alias is unknown. */
13
21
  resolveKind(kind) {
14
22
  if (!kind) {
@@ -33,6 +33,7 @@ export declare class AnalysisRegistry {
33
33
  visitManifest(resources: ResourceManifest[], visitor: ManifestVisitor, opts?: {
34
34
  skipKinds?: ReadonlySet<string>;
35
35
  expand?: boolean;
36
+ discoverNestedRefs?: boolean;
36
37
  }): void;
37
38
  /**
38
39
  * Returns the built-in kernel definitions. The underlying DefinitionRegistry already
@@ -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;AAKzE,OAAO,EAAqC,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEhG,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;;;;;;OAMG;IACH,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,EACxB,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAC3D,IAAI;IAQP;;;;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"}
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;AAKzE,OAAO,EAAqC,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEhG,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;;;;;;OAMG;IACH,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,EACxB,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAAE,GACzF,IAAI;IAQP;;;;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"}
@@ -25,7 +25,7 @@ export declare class StaticAnalyzer {
25
25
  */
26
26
  analyze(manifests: ResourceManifest[], options?: AnalysisOptions, registry?: AnalysisRegistry): AnalysisDiagnostic[];
27
27
  analyzeErrors(manifests: ResourceManifest[], options?: AnalysisOptions, registry?: AnalysisRegistry): AnalysisDiagnostic[];
28
- normalize(manifests: ResourceManifest[], registry: AnalysisRegistry): ResourceManifest[];
28
+ normalize(manifests: ResourceManifest[], registry: AnalysisRegistry, crossModuleTargets?: ResourceManifest[]): ResourceManifest[];
29
29
  prepare(manifests: ResourceManifest[], registry: AnalysisRegistry): {
30
30
  diagnostics: AnalysisDiagnostic[];
31
31
  order: string[] | null;
@@ -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;AAiB9B,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;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAyjBvB,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;IAexF,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;AAiB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAuhB/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAimBvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAqBrB,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
@@ -307,6 +307,73 @@ function buildStepContextSchema(manifest, defSchema, allManifests, defs, aliases
307
307
  }
308
308
  return undefined;
309
309
  }
310
+ /**
311
+ * Collect every field annotated with `x-telo-error-context` anywhere in a
312
+ * definition schema (resolving local `$ref`s into `$defs`, cycle-safe), mapping
313
+ * the annotated field name to its declared error-shape schema. The field name
314
+ * is matched against CEL paths so the context applies at any nesting depth under
315
+ * that field — e.g. `error` inside a `catch:` nested inside another `try:`. No
316
+ * specific field name (or `Run.Sequence`) is hardcoded; any composer that tags
317
+ * its error-bearing branch fields opts in the same way.
318
+ */
319
+ function collectErrorContextScopes(defSchema) {
320
+ const out = new Map();
321
+ if (!defSchema || typeof defSchema !== "object")
322
+ return out;
323
+ const seen = new Set();
324
+ const walk = (schema) => {
325
+ if (!schema || typeof schema !== "object" || seen.has(schema))
326
+ return;
327
+ seen.add(schema);
328
+ const props = schema.properties;
329
+ if (props) {
330
+ for (const [fieldName, fieldSchema] of Object.entries(props)) {
331
+ if (fieldSchema && typeof fieldSchema === "object") {
332
+ const errCtx = fieldSchema["x-telo-error-context"];
333
+ if (errCtx && typeof errCtx === "object" && !out.has(fieldName)) {
334
+ out.set(fieldName, errCtx);
335
+ }
336
+ }
337
+ walk(resolveLocalRef(fieldSchema, defSchema));
338
+ }
339
+ }
340
+ if (schema.items)
341
+ walk(resolveLocalRef(schema.items, defSchema));
342
+ for (const key of ["oneOf", "anyOf", "allOf"]) {
343
+ const arr = schema[key];
344
+ if (Array.isArray(arr))
345
+ for (const sub of arr)
346
+ walk(resolveLocalRef(sub, defSchema));
347
+ }
348
+ if (schema.$defs && typeof schema.$defs === "object") {
349
+ for (const sub of Object.values(schema.$defs)) {
350
+ walk(sub);
351
+ }
352
+ }
353
+ };
354
+ walk(defSchema);
355
+ return out;
356
+ }
357
+ /**
358
+ * Return the error-context schema for a CEL `path` when the path lies within
359
+ * (any depth under) one of the error-bearing fields, else undefined. A path is
360
+ * "within" field `f` when it contains a segment `f[<index>]`. When multiple
361
+ * error-bearing fields match (e.g. a `finally` nested inside a `catch`), the
362
+ * deepest — the one whose segment appears latest in the path — wins, so the
363
+ * innermost branch's schema governs.
364
+ */
365
+ function errorContextForPath(path, scopes) {
366
+ let best;
367
+ for (const [fieldName, schema] of scopes) {
368
+ const escaped = fieldName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
369
+ for (const match of path.matchAll(new RegExp(`(^|\\.)${escaped}\\[\\d+\\]`, "g"))) {
370
+ if (best === undefined || match.index > best.index) {
371
+ best = { index: match.index, schema };
372
+ }
373
+ }
374
+ }
375
+ return best?.schema;
376
+ }
310
377
  const CEL_PURE_RE = /^\s*\$\{\{[^}]*\}\}\s*$/;
311
378
  const CEL_EXPR_RE = /\$\{\{\s*([^}]+?)\s*\}\}/;
312
379
  /** Recursively walk `data`+`schema` together, type-checking every pure CEL template
@@ -444,9 +511,12 @@ export class StaticAnalyzer {
444
511
  // through the same alias machinery as user-declared Telo.Imports —
445
512
  // honours the library's `exports.kinds` list, no special cases.
446
513
  if (moduleName) {
447
- const exportedKinds = m.exports?.kinds ?? [];
514
+ // `Self` resolves the library's own kinds UNGATED — a library may reference
515
+ // its own kinds regardless of `exports.kinds`, which gates importers, not
516
+ // internal use. This is what lets a library declare an instance of a kind it
517
+ // does not export (e.g. console's `writeLine`) to enforce a singleton.
448
518
  if (rootModules.has(moduleName)) {
449
- aliases.registerImport("Self", moduleName, exportedKinds);
519
+ aliases.registerImport("Self", moduleName, []);
450
520
  }
451
521
  else {
452
522
  let libResolver = aliasesByModule.get(moduleName);
@@ -454,7 +524,7 @@ export class StaticAnalyzer {
454
524
  libResolver = new AliasResolver();
455
525
  aliasesByModule.set(moduleName, libResolver);
456
526
  }
457
- libResolver.registerImport("Self", moduleName, exportedKinds);
527
+ libResolver.registerImport("Self", moduleName, []);
458
528
  }
459
529
  }
460
530
  }
@@ -626,6 +696,14 @@ export class StaticAnalyzer {
626
696
  if (m.kind === "Telo.Abstract") {
627
697
  continue;
628
698
  }
699
+ // Forwarded exports (flagged by flattenForAnalyzer) are an imported library's exported
700
+ // instances, already validated in their own module's standalone analysis; their
701
+ // `kind`/CEL are authored in that module's scope (e.g. `Self.X` → that module, not the
702
+ // consumer). Re-validating against the consumer's scope yields false UNDEFINED_KIND /
703
+ // scope-mismatch errors, so skip — they participate here only as resolution targets.
704
+ if (m.metadata?.forwardedExport === true) {
705
+ continue;
706
+ }
629
707
  const resource = { kind: m.kind, name: m.metadata?.name };
630
708
  // Resolve kind through alias if needed; direct lookup takes priority so that
631
709
  // aliases whose name matches the module name (the common case) work without
@@ -784,6 +862,7 @@ export class StaticAnalyzer {
784
862
  // context — which require analyzer state to build — are stashed here.
785
863
  let celStepContextSchema;
786
864
  let celInvocationContext;
865
+ let celErrorScopes = new Map();
787
866
  visitManifest(allManifests, defs, {
788
867
  onResourceEnter: (e) => {
789
868
  const m = e.source;
@@ -791,6 +870,7 @@ export class StaticAnalyzer {
791
870
  celStepContextSchema = e.definition?.schema
792
871
  ? buildStepContextSchema(m, e.definition.schema, allManifests, defs, aliases)
793
872
  : undefined;
873
+ celErrorScopes = collectErrorContextScopes(e.definition?.schema);
794
874
  },
795
875
  onCel: (e) => {
796
876
  const m = e.source;
@@ -808,6 +888,19 @@ export class StaticAnalyzer {
808
888
  },
809
889
  };
810
890
  }
891
+ // `error` is only in scope inside an error-bearing branch (e.g. a
892
+ // `catch:` / `finally:`), so it's merged per-path, not resource-wide.
893
+ const errorSchema = celErrorScopes.size > 0 ? errorContextForPath(path, celErrorScopes) : undefined;
894
+ if (errorSchema) {
895
+ const base = matchedContext ?? { type: "object", properties: {}, additionalProperties: true };
896
+ matchedContext = {
897
+ ...base,
898
+ properties: {
899
+ ...(base.properties ?? {}),
900
+ error: errorSchema,
901
+ },
902
+ };
903
+ }
811
904
  let effectiveContext = null;
812
905
  if (matchedContext) {
813
906
  const manifestItem = matchedScope
@@ -855,6 +948,15 @@ export class StaticAnalyzer {
855
948
  data: { resource, filePath, path },
856
949
  });
857
950
  }
951
+ else if (f.code === "CEL_NULLABLE_ACCESS") {
952
+ diagnostics.push({
953
+ severity: DiagnosticSeverity.Error,
954
+ code: "CEL_NULLABLE_ACCESS",
955
+ source: SOURCE,
956
+ message: `${m.kind}/${resource.name}: CEL at '${path}': ${f.message}`,
957
+ data: { resource, filePath, path },
958
+ });
959
+ }
858
960
  else {
859
961
  // Unknown code from a future engine — pass the message through,
860
962
  // tagged with a generic ENGINE_DIAGNOSTIC code so downstream
@@ -887,13 +989,17 @@ export class StaticAnalyzer {
887
989
  analyzeErrors(manifests, options, registry) {
888
990
  return this.analyze(manifests, options, registry).filter((d) => d.severity === DiagnosticSeverity.Error);
889
991
  }
890
- normalize(manifests, registry) {
992
+ normalize(manifests, registry,
993
+ // Forwarded foreign exports used only as cross-module resolution targets (see
994
+ // resolveRefSentinels). The kernel passes its analyzer-flattened set so the
995
+ // entry-only runtime pass can still resolve `!ref Alias.name`.
996
+ crossModuleTargets) {
891
997
  const ctx = registry._context();
892
998
  const normalized = normalizeInlineResources(manifests, ctx.definitions, ctx.aliases, ctx.aliasesByModule);
893
999
  // Resolve !ref sentinels after normalize so both the original and
894
1000
  // inline-extracted manifests get their refs canonicalized to
895
1001
  // {kind, name} for the kernel that consumes this output.
896
- resolveRefSentinels(normalized, ctx.definitions, ctx.aliases, ctx.aliasesByModule);
1002
+ resolveRefSentinels(normalized, ctx.definitions, ctx.aliases, ctx.aliasesByModule, crossModuleTargets ?? []);
897
1003
  return normalized;
898
1004
  }
899
1005
  prepare(manifests, registry) {
@@ -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,EA0V/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,EA4Z/C,CAAC"}
package/dist/builtins.js CHANGED
@@ -232,6 +232,66 @@ export const KERNEL_BUILTINS = [
232
232
  },
233
233
  additionalProperties: true,
234
234
  },
235
+ // Gated reference: run() a Runnable/Service only when the
236
+ // `when` CEL guard holds. Discriminated by the `ref` key. `ref`
237
+ // is a bare name or a resolved `!ref` (`{ kind, name }`).
238
+ {
239
+ type: "object",
240
+ required: ["ref"],
241
+ properties: {
242
+ ref: {
243
+ anyOf: [
244
+ { type: "string", "x-telo-ref": "telo#Runnable" },
245
+ { type: "string", "x-telo-ref": "telo#Service" },
246
+ {
247
+ type: "object",
248
+ required: ["kind", "name"],
249
+ properties: {
250
+ kind: { type: "string" },
251
+ name: { type: "string" },
252
+ },
253
+ additionalProperties: true,
254
+ },
255
+ ],
256
+ },
257
+ when: { type: "string" },
258
+ },
259
+ additionalProperties: false,
260
+ },
261
+ // Inline flat invoke step: invoke an Invocable / Runnable on boot
262
+ // with an optional `name` (for steps.<name>.result plumbing),
263
+ // `when` guard, and `inputs`. Discriminated by the `invoke` key.
264
+ // Control flow (if/while/switch/try) is not available here —
265
+ // reach for Run.Sequence. `invoke` is ref-only and must resolve
266
+ // to a `{ kind, name }` reference (a `!ref` / `{kind,name}`):
267
+ // requiring `name` rejects an inline `{ kind }` definition (no
268
+ // name) at analysis instead of failing at boot with an undefined
269
+ // resource name. The Invocable/Runnable kind set mirrors
270
+ // Run.Sequence invoke steps.
271
+ {
272
+ type: "object",
273
+ required: ["invoke"],
274
+ properties: {
275
+ name: { type: "string" },
276
+ invoke: {
277
+ "x-telo-topology-role": "invoke",
278
+ type: "object",
279
+ required: ["kind", "name"],
280
+ properties: {
281
+ kind: { type: "string" },
282
+ name: { type: "string" },
283
+ },
284
+ additionalProperties: true,
285
+ anyOf: [
286
+ { "x-telo-ref": "telo#Invocable" },
287
+ { "x-telo-ref": "telo#Runnable" },
288
+ ],
289
+ },
290
+ inputs: { type: "object", additionalProperties: true },
291
+ when: { type: "string" },
292
+ },
293
+ additionalProperties: false,
294
+ },
235
295
  ],
236
296
  },
237
297
  },
@@ -336,6 +396,12 @@ export const KERNEL_BUILTINS = [
336
396
  type: "object",
337
397
  properties: {
338
398
  kinds: { type: "array", items: { type: "string" } },
399
+ // `variables` / `secrets` are reserved on the resources.<Alias> value-flow
400
+ // surface, so a library may not export instances under those names.
401
+ resources: {
402
+ type: "array",
403
+ items: { type: "string", not: { enum: ["variables", "secrets"] } },
404
+ },
339
405
  },
340
406
  additionalProperties: true,
341
407
  },
@@ -1 +1 @@
1
- {"version":3,"file":"flatten-for-analyzer.d.ts","sourceRoot":"","sources":["../src/flatten-for-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAc,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAG/E;;;;;;;;;;;;;;;;;;6EAkB6E;AAC7E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,gBAAgB,EAAE,CAyDzE;AAED;;;;;6BAK6B;AAC7B,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,gBAAgB,EAAE,CAEzE"}
1
+ {"version":3,"file":"flatten-for-analyzer.d.ts","sourceRoot":"","sources":["../src/flatten-for-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAc,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAG/E;;;;;;;;;;;;;;;;;;6EAkB6E;AAC7E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,gBAAgB,EAAE,CA6EzE;AAED;;;;;6BAK6B;AAC7B,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,gBAAgB,EAAE,CAEzE"}
@@ -38,12 +38,29 @@ export function flattenForAnalyzer(graph) {
38
38
  if (!targetModule)
39
39
  continue;
40
40
  const stamped = collectModuleManifests(targetModule);
41
+ const libDoc = stamped.find((m) => isModuleKind(m.kind));
42
+ const exportedResources = new Set(libDoc?.exports?.resources ?? []);
41
43
  for (const m of stamped) {
42
44
  if (m.kind === "Telo.Definition" ||
43
45
  m.kind === "Telo.Abstract" ||
44
46
  m.kind === "Telo.Import") {
45
47
  result.push(m);
46
48
  }
49
+ else if (!isModuleKind(m.kind) &&
50
+ typeof m.metadata?.name === "string" &&
51
+ exportedResources.has(m.metadata.name)) {
52
+ // Forward instances listed in the library's `exports.resources` so the
53
+ // consumer's analyzer can resolve, gate, kind-check, and draw topology edges
54
+ // for cross-module `!ref Alias.name`. The gate IS this forwarding — only
55
+ // exported names cross the boundary. `metadata.forwardedExport` marks them as
56
+ // cross-module resolution targets only (keyed by `metadata.module`), so ref
57
+ // resolution / validation treats them as targets, never as local sources to
58
+ // re-validate or walk.
59
+ result.push({
60
+ ...m,
61
+ metadata: { ...m.metadata, forwardedExport: true },
62
+ });
63
+ }
47
64
  }
48
65
  }
49
66
  }
@@ -65,6 +65,13 @@ export interface RefSiteEvent {
65
65
  inScope: boolean;
66
66
  /** Scope manifests visible to this ref path (non-empty only when `inScope`). */
67
67
  visibleScopeManifests: ResourceManifest[];
68
+ /** True when the site was found by value-tree scanning rather than the field
69
+ * map (only when `discoverNestedRefs` is set) — a ref nested behind a `$ref`
70
+ * the field map doesn't descend (e.g. `Run.Sequence` `steps[].invoke`).
71
+ * Nested sites carry no x-telo-ref constraint (`entry.refs` is empty) and no
72
+ * scope info; `concretePath` still points at the exact location, so consumers
73
+ * can anchor to it. */
74
+ nested?: boolean;
68
75
  }
69
76
  export interface SchemaFromSiteEvent {
70
77
  source: ResourceManifest;
@@ -104,6 +111,14 @@ export interface VisitOptions {
104
111
  * so refs nested behind `x-telo-schema-from` are surfaced. `SchemaFromSite`
105
112
  * events are always emitted from the base map regardless of this flag. */
106
113
  expand?: boolean;
114
+ /** When true, additionally discover refs by scanning each resource's value
115
+ * tree for `!ref` sentinels and `{kind, name}` reference objects — surfacing
116
+ * refs the field map never lists because they sit behind a `$ref` it doesn't
117
+ * descend (notably `Run.Sequence` step `invoke`s). Emitted as `RefSite`s with
118
+ * `nested: true`, deduped against the field-map sites by concrete path.
119
+ * Opt-in: the validators / dependency graph must NOT enable it (those refs
120
+ * are runtime-resolved, not boot dependencies). */
121
+ discoverNestedRefs?: boolean;
107
122
  }
108
123
  export declare function visitManifest(resources: ResourceManifest[], registry: DefinitionRegistry, visitor: ManifestVisitor, options?: VisitOptions): void;
109
124
  //# sourceMappingURL=manifest-visitor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"manifest-visitor.d.ts","sourceRoot":"","sources":["../src/manifest-visitor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"manifest-visitor.d.ts","sourceRoot":"","sources":["../src/manifest-visitor.ts"],"names":[],"mappings":"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"}
@@ -1,16 +1,59 @@
1
- import { walkCelExpressions } from "@telorun/templating";
1
+ import { isRefSentinel, isTaggedSentinel, walkCelExpressions } from "@telorun/templating";
2
2
  import { isRefEntry, isSchemaFromEntry, isScopeEntry, resolveFieldEntries, resolveFieldValues, } from "./reference-field-map.js";
3
3
  import { extractContextsFromSchema, pathMatchesScope } from "./validate-cel-context.js";
4
+ /** Synthetic entry for a value-tree-discovered ref — these carry no declared
5
+ * x-telo-ref constraint. */
6
+ const NESTED_REF_ENTRY = { refs: [], isArray: false };
7
+ /** Scans a value tree for ref-shaped values, emitting each with its concrete
8
+ * path. Recognizes `!ref <name>` sentinels and named `{kind, name}` reference
9
+ * objects. Other tagged sentinels (`!cel`, `!literal`) and precompiled nodes
10
+ * are leaves. Path format matches `resolveFieldEntries` / `walkCelExpressions`
11
+ * (`a.b[0].c`).
12
+ *
13
+ * Stops at every `{kind, …}` resource boundary: a named ref is emitted, an
14
+ * inline resource (`{kind}` with no name) is left alone, and **neither is
15
+ * descended into**. A nested resource's own refs belong to its inner topology,
16
+ * not the enclosing node — e.g. an inline `Sql.Exec` step's `connection` is the
17
+ * Exec's dependency, not the surrounding `Run.Sequence`'s. The scan is started
18
+ * per top-level field (not on the resource object) so the resource's own
19
+ * `kind` doesn't trip this boundary. */
20
+ function walkRefValues(value, path, cb) {
21
+ if (isRefSentinel(value)) {
22
+ cb(value, path);
23
+ return;
24
+ }
25
+ if (isTaggedSentinel(value))
26
+ return;
27
+ if (Array.isArray(value)) {
28
+ value.forEach((v, i) => walkRefValues(v, `${path}[${i}]`, cb));
29
+ return;
30
+ }
31
+ if (value === null || typeof value !== "object")
32
+ return;
33
+ if (value.__compiled)
34
+ return;
35
+ const obj = value;
36
+ if (typeof obj.kind === "string") {
37
+ // Resource boundary — emit if it's a named ref, then stop descending.
38
+ if (typeof obj.name === "string")
39
+ cb(value, path);
40
+ return;
41
+ }
42
+ for (const [k, v] of Object.entries(obj)) {
43
+ walkRefValues(v, path ? `${path}.${k}` : k, cb);
44
+ }
45
+ }
4
46
  const scopePrefixOf = (pointer) => pointer.replace(/^\//, "").replace(/\//g, ".");
5
47
  const pathUnderPrefix = (fieldPath, prefix) => fieldPath === prefix ||
6
48
  fieldPath.startsWith(prefix + ".") ||
7
49
  fieldPath.startsWith(prefix + "[");
8
50
  export function visitManifest(resources, registry, visitor, options = {}) {
9
- const { aliases, aliasesByModule, skipKinds, expand } = options;
51
+ const { aliases, aliasesByModule, skipKinds, expand, discoverNestedRefs } = options;
10
52
  const wantsRefs = !!visitor.onRef;
11
53
  const wantsScope = !!visitor.onScope;
12
54
  const wantsSchemaFrom = !!visitor.onSchemaFrom;
13
55
  const wantsCel = !!visitor.onCel;
56
+ const wantsNested = wantsRefs && !!discoverNestedRefs;
14
57
  for (const r of resources) {
15
58
  if (!r.metadata?.name || !r.kind)
16
59
  continue;
@@ -20,6 +63,9 @@ export function visitManifest(resources, registry, visitor, options = {}) {
20
63
  const definition = registry.resolve(r.kind) ??
21
64
  (resolvedKind ? registry.resolve(resolvedKind) : undefined);
22
65
  visitor.onResourceEnter?.({ source: r, definition });
66
+ // Concrete paths emitted from the field map — so the value-tree scan below
67
+ // doesn't re-emit a ref the field map already covered.
68
+ const emittedRefPaths = wantsNested ? new Set() : null;
23
69
  if (wantsRefs || wantsScope || wantsSchemaFrom) {
24
70
  const baseMap = aliases
25
71
  ? registry.getFieldMapForKind(r.kind, aliases)
@@ -69,6 +115,7 @@ export function visitManifest(resources, registry, visitor, options = {}) {
69
115
  for (const { value, path: concretePath } of resolveFieldEntries(r, fieldPath)) {
70
116
  if (!value)
71
117
  continue;
118
+ emittedRefPaths?.add(concretePath);
72
119
  visitor.onRef({
73
120
  source: r,
74
121
  fieldPath,
@@ -90,6 +137,30 @@ export function visitManifest(resources, registry, visitor, options = {}) {
90
137
  }
91
138
  }
92
139
  }
140
+ // Value-tree-driven nested ref discovery — refs the field map can't reach
141
+ // because they sit behind a `$ref` it doesn't descend (e.g. Run.Sequence
142
+ // step `invoke`s). Deduped against the field-map sites by concrete path.
143
+ // Scanned per top-level field so the resource's own `kind` isn't treated as
144
+ // a resource boundary by `walkRefValues`.
145
+ if (wantsNested) {
146
+ const emitNested = (value, path) => {
147
+ if (emittedRefPaths.has(path))
148
+ return;
149
+ visitor.onRef({
150
+ source: r,
151
+ fieldPath: path,
152
+ concretePath: path,
153
+ value,
154
+ entry: NESTED_REF_ENTRY,
155
+ inScope: false,
156
+ visibleScopeManifests: [],
157
+ nested: true,
158
+ });
159
+ };
160
+ for (const [key, value] of Object.entries(r)) {
161
+ walkRefValues(value, key, emitNested);
162
+ }
163
+ }
93
164
  if (wantsCel) {
94
165
  const contexts = definition?.schema ? extractContextsFromSchema(definition.schema) : [];
95
166
  walkCelExpressions(r, "", (expr, path, engineName) => {
@@ -157,6 +157,22 @@ function traverseNode(node, path, map, root, visitedRefs = new Set()) {
157
157
  if (node["x-telo-context"])
158
158
  entry.context = node["x-telo-context"];
159
159
  map.set(path, entry);
160
+ // A node can mix item-level ref branches (a bare string / `{kind, name}`)
161
+ // with object branches that carry their OWN nested refs — e.g. Application
162
+ // `targets`: a bare ref vs inline `{ invoke }` vs gated `{ ref }`. Descend
163
+ // into the variant objects so those nested slots register too (and their
164
+ // `!ref` sentinels resolve). Pure x-telo-ref branches have no properties
165
+ // and contribute nothing here.
166
+ for (const variantKey of ["oneOf", "anyOf", "allOf"]) {
167
+ const variants = node[variantKey];
168
+ if (!Array.isArray(variants))
169
+ continue;
170
+ for (const variant of variants) {
171
+ if (!variant || typeof variant !== "object")
172
+ continue;
173
+ traverseVariant(variant, path, map, root, visitedRefs);
174
+ }
175
+ }
160
176
  return;
161
177
  }
162
178
  // Array — recurse into items
@@ -3,25 +3,23 @@ import type { AliasResolver } from "./alias-resolver.js";
3
3
  import type { DefinitionRegistry } from "./definition-registry.js";
4
4
  /**
5
5
  * Walks every `x-telo-ref` slot in every non-system resource and rewrites
6
- * `!ref <name>` sentinels in-place to `{kind: <resolved-kind>, name}`.
6
+ * `!ref <name>` sentinels in-place to `{kind, name}` (local) or
7
+ * `{kind, name, alias}` (cross-module).
7
8
  *
8
- * The downstream pipeline (inline normalization, dependency graph, kernel
9
- * controllers) expects every ref-slot value to be either a `{kind, name}`
10
- * object, an inline-definition object, or a legacy bare string — resolving
11
- * sentinels here keeps that contract intact so each consumer doesn't need
12
- * its own sentinel branch.
9
+ * Reference grammar the tag's source string is split on the FIRST dot:
10
+ * - `!ref writeLine` → local resource `writeLine`
11
+ * - `!ref Self.writeLine` → local resource `writeLine` (explicit self-qualifier)
12
+ * - `!ref Console.writeLine` → instance `writeLine` exported by the import aliased
13
+ * `Console`, resolved against the forwarded foreign set
13
14
  *
14
- * The walker assigns `kind` by name lookup (resource names are unique
15
- * within a manifest scope). When the name doesn't resolve in the local
16
- * `byName` map, the sentinel is left in place so `validateReferences`
17
- * can emit the `UNRESOLVED_REFERENCE` diagnostic with full context.
15
+ * Aliases are PascalCase identifiers without dots and resource names carry no dots
16
+ * (enforced as a hard diagnostic), so the first-dot split is unambiguous. When the
17
+ * name doesn't resolve, the sentinel is left in place so `validateReferences` emits the
18
+ * `UNRESOLVED_REFERENCE` diagnostic with full context.
18
19
  *
19
- * Mutation strategy: the field-path walker descends the resource tree
20
- * directly and replaces the sentinel on its parent container. Re-parsing
21
- * a string-encoded concrete path (the earlier shape) coupled the writer
22
- * to the path-encoding rules of `resolveFieldEntries` — any new path
23
- * marker would silently break this writer. Descending directly avoids
24
- * that coupling.
20
+ * Forwarded foreign resources (an imported library's exported instances, carrying a
21
+ * `metadata.module` that isn't a root module) are resolution TARGETS only — they are not
22
+ * re-walked as sources here, since their own ref slots belong to their own module scope.
25
23
  */
26
- export declare function resolveRefSentinels(resources: ResourceManifest[], registry: DefinitionRegistry, aliases?: AliasResolver, aliasesByModule?: Map<string, AliasResolver>): void;
24
+ export declare function resolveRefSentinels(resources: ResourceManifest[], registry: DefinitionRegistry, aliases?: AliasResolver, aliasesByModule?: Map<string, AliasResolver>, crossModuleTargets?: ResourceManifest[]): void;
27
25
  //# sourceMappingURL=resolve-ref-sentinels.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-ref-sentinels.d.ts","sourceRoot":"","sources":["../src/resolve-ref-sentinels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAInE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,IAAI,CAsBN"}
1
+ {"version":3,"file":"resolve-ref-sentinels.d.ts","sourceRoot":"","sources":["../src/resolve-ref-sentinels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAQnE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAK5C,kBAAkB,GAAE,gBAAgB,EAAO,GAC1C,IAAI,CAwEN"}