@telorun/analyzer 0.12.0 → 0.12.1

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.
package/README.md CHANGED
@@ -61,12 +61,12 @@ targets:
61
61
  kind: Telo.Import
62
62
  metadata:
63
63
  name: Http
64
- source: std/http-server@0.4.0
64
+ source: std/http-server@0.5.0
65
65
  ---
66
66
  kind: Telo.Import
67
67
  metadata:
68
68
  name: Sql
69
- source: std/sql@0.2.3
69
+ source: std/sql@0.3.0
70
70
  ---
71
71
  # SQLite database — swap driver/host/database for PostgreSQL with zero YAML changes
72
72
  kind: Sql.Connection
@@ -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;AAgB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAgf/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;IA8dvB,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;AAgB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAif/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;IAoiBvB,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"}
package/dist/analyzer.js CHANGED
@@ -14,6 +14,7 @@ import { celTypeSatisfiesJsonSchema, substituteCelFields, validateAgainstSchema,
14
14
  import { DiagnosticSeverity } from "./types.js";
15
15
  import { getManifestItem, pathMatchesScope, resolveContextAnnotations, resolveTypeFieldToSchema, } from "./validate-cel-context.js";
16
16
  import { validateExtends } from "./validate-extends.js";
17
+ import { validateNestedInlineResources } from "./validate-nested-inline.js";
17
18
  import { validateProviderCoherence } from "./validate-provider-coherence.js";
18
19
  import { validateReferences } from "./validate-references.js";
19
20
  import { validateThrowsCoverage } from "./validate-throws-coverage.js";
@@ -563,6 +564,34 @@ export class StaticAnalyzer {
563
564
  byName.set(m.metadata.name, m);
564
565
  }
565
566
  }
567
+ // Fail loud on definition schemas AJV cannot compile. `validateAgainstSchema`
568
+ // and `validateWithRefs` swallow compile failures (returning no issues),
569
+ // which would silently skip schema validation for every resource of that
570
+ // kind — surface the broken schema once, anchored on the definition itself.
571
+ for (const m of allManifests) {
572
+ if (m.kind !== "Telo.Definition" && m.kind !== "Telo.Abstract")
573
+ continue;
574
+ const schema = m.schema;
575
+ if (!schema || typeof schema !== "object")
576
+ continue;
577
+ const name = m.metadata?.name;
578
+ if (!name)
579
+ continue;
580
+ const compileError = defs.schemaCompileError(schema);
581
+ if (compileError) {
582
+ diagnostics.push({
583
+ severity: DiagnosticSeverity.Error,
584
+ code: "SCHEMA_COMPILE_ERROR",
585
+ source: SOURCE,
586
+ message: `${m.kind}/${name}: definition schema failed to compile: ${compileError}`,
587
+ data: {
588
+ resource: { kind: m.kind, name },
589
+ filePath: m.metadata?.source,
590
+ path: "schema",
591
+ },
592
+ });
593
+ }
594
+ }
566
595
  // Library env: rejection — `env:` on a Library `variables` / `secrets`
567
596
  // entry is forbidden. The Library entry schema is otherwise open so that
568
597
  // any JSON Schema property schema is valid; this targeted check produces
@@ -664,6 +693,33 @@ export class StaticAnalyzer {
664
693
  });
665
694
  }
666
695
  }
696
+ // Validate inline resources nested inside this resource's body (e.g. a
697
+ // Run.Sequence step's `invoke: { kind, ...config }`). These sit at
698
+ // x-telo-ref slots reached only through local `$ref`s, which the
699
+ // reference field map intentionally does not follow, so they escape both
700
+ // inline-extraction and the per-resource schema check above.
701
+ if (definition.schema) {
702
+ // Resolve inline kinds in the parent resource's scope: direct kind
703
+ // first, then the parent module's own aliases (for resources declared
704
+ // inside an imported module), then the root aliases. Mirrors how the
705
+ // analyzer resolves kinds elsewhere so module-scoped aliases don't
706
+ // produce false UNDEFINED_KIND diagnostics.
707
+ const ownModule = m.metadata?.module;
708
+ const scopeResolver = ownModule && !rootModules.has(ownModule) ? aliasesByModule.get(ownModule) : undefined;
709
+ diagnostics.push(...validateNestedInlineResources(m, definition.schema, (kind) => {
710
+ const direct = defs.resolve(kind);
711
+ if (direct)
712
+ return direct;
713
+ const viaScope = scopeResolver?.resolveKind(kind);
714
+ if (viaScope) {
715
+ const scoped = defs.resolve(viaScope);
716
+ if (scoped)
717
+ return scoped;
718
+ }
719
+ const viaRoot = aliases.resolveKind(kind);
720
+ return viaRoot ? defs.resolve(viaRoot) : undefined;
721
+ }));
722
+ }
667
723
  // (Invocation context compatibility check is handled via x-telo-context in the CEL pass below)
668
724
  }
669
725
  // Template-body structural validations: check that template entry-points produce
@@ -789,8 +845,18 @@ export class StaticAnalyzer {
789
845
  effectiveContext = mergeKernelGlobalsIntoContext(resolvedContext, kernelGlobals);
790
846
  }
791
847
  const engine = defaultRegistry().get(engineName);
792
- if (!engine)
848
+ if (!engine) {
849
+ // No registered engine owns this tag — the expression would go
850
+ // entirely unanalyzed. Surface it rather than skipping silently.
851
+ diagnostics.push({
852
+ severity: DiagnosticSeverity.Error,
853
+ code: "UNKNOWN_ENGINE",
854
+ source: SOURCE,
855
+ message: `${m.kind}/${resource.name}: no templating engine registered for '!${engineName}' at '${path}'.`,
856
+ data: { resource, filePath, path },
857
+ });
793
858
  return;
859
+ }
794
860
  const findings = engine.analyze(expr, { celEnv: this.celEnv, contextSchema: effectiveContext });
795
861
  for (const f of findings) {
796
862
  if (f.code === "CEL_SYNTAX_ERROR") {
@@ -31,8 +31,19 @@ export declare class DefinitionRegistry {
31
31
  * Returns undefined when the module identity is not yet registered. */
32
32
  computeId(moduleName: string, typeName: string): string | undefined;
33
33
  /** Validates data against a schema using this registry's AJV instance, which has all
34
- * registered definition schemas loaded — enabling cross-module $ref resolution. */
34
+ * registered definition schemas loaded — enabling cross-module $ref resolution.
35
+ * A compile failure returns `[]` here; it is surfaced loudly (once, on the
36
+ * owning definition) by `schemaCompileError` via the analyzer's
37
+ * definition-schema compile check, so resources are never silently skipped. */
35
38
  validateWithRefs(data: unknown, schema: Record<string, any>): string[];
39
+ /** Returns the AJV compile error for `schema`, or `undefined` when it compiles.
40
+ * Compiles on this registry's instance, which has every loaded module schema
41
+ * plus the manifest root registered, so local `#/$defs`, `telo://manifest`,
42
+ * and cross-module `$ref`s all resolve. Used to fail loud on a definition
43
+ * schema that AJV cannot compile — otherwise `validateAgainstSchema` /
44
+ * `validateWithRefs` would swallow the failure and silently skip every
45
+ * resource of that kind. */
46
+ schemaCompileError(schema: Record<string, any>): string | undefined;
36
47
  private tryRegisterSchema;
37
48
  /** Resolves an x-telo-ref string to a canonical registry kind key.
38
49
  * Splits on "#", looks up the left side in the identity table, and returns
@@ -1 +1 @@
1
- {"version":3,"file":"definition-registry.d.ts","sourceRoot":"","sources":["../src/definition-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAGlC,+EAA+E;AAC/E,qBAAa,kBAAkB;;IAK7B;;sFAEkF;IAClF,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyC;IAC9D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwC;IAClE,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA+B;IAC1D;6DACyD;IACzD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD;;0EAEsE;IACtE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAEhE,QAAQ,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI;IAiC9C,OAAO,CAAC,aAAa;IASrB;;;yFAGqF;IACrF,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IA+B1E;4EACwE;IACxE,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAMnE;wFACoF;IACpF,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE;IAWtE,OAAO,CAAC,iBAAiB;IAczB;;;;;;;;4FAQwF;IACxF,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAUhD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAIrD,+FAA+F;IAC/F,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAIxD,gGAAgG;IAChG,kBAAkB,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,GACvD,iBAAiB,GAAG,SAAS;IAOhC;;;;;;;;qFAQiF;IACjF,2BAA2B,CACzB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC1C,iBAAiB,GAAG,SAAS;IAuBhC,OAAO,CAAC,uBAAuB;IA+B/B;;qEAEiE;IACjE,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAgBxD,KAAK,IAAI,MAAM,EAAE;CAGlB"}
1
+ {"version":3,"file":"definition-registry.d.ts","sourceRoot":"","sources":["../src/definition-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAGlC,+EAA+E;AAC/E,qBAAa,kBAAkB;;IAK7B;;sFAEkF;IAClF,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyC;IAC9D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwC;IAClE,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA+B;IAC1D;6DACyD;IACzD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD;;0EAEsE;IACtE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAEhE,QAAQ,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI;IAiC9C,OAAO,CAAC,aAAa;IASrB;;;yFAGqF;IACrF,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IA+B1E;4EACwE;IACxE,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAMnE;;;;oFAIgF;IAChF,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE;IAWtE;;;;;;iCAM6B;IAC7B,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,GAAG,SAAS;IASnE,OAAO,CAAC,iBAAiB;IAczB;;;;;;;;4FAQwF;IACxF,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAUhD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAIrD,+FAA+F;IAC/F,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAIxD,gGAAgG;IAChG,kBAAkB,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,GACvD,iBAAiB,GAAG,SAAS;IAOhC;;;;;;;;qFAQiF;IACjF,2BAA2B,CACzB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC1C,iBAAiB,GAAG,SAAS;IAuBhC,OAAO,CAAC,uBAAuB;IA+B/B;;qEAEiE;IACjE,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAgBxD,KAAK,IAAI,MAAM,EAAE;CAGlB"}
@@ -105,7 +105,10 @@ export class DefinitionRegistry {
105
105
  return `${identity}/${typeName}`;
106
106
  }
107
107
  /** Validates data against a schema using this registry's AJV instance, which has all
108
- * registered definition schemas loaded — enabling cross-module $ref resolution. */
108
+ * registered definition schemas loaded — enabling cross-module $ref resolution.
109
+ * A compile failure returns `[]` here; it is surfaced loudly (once, on the
110
+ * owning definition) by `schemaCompileError` via the analyzer's
111
+ * definition-schema compile check, so resources are never silently skipped. */
109
112
  validateWithRefs(data, schema) {
110
113
  let validate;
111
114
  try {
@@ -118,6 +121,22 @@ export class DefinitionRegistry {
118
121
  return [];
119
122
  return (validate.errors ?? []).map(formatSingleError);
120
123
  }
124
+ /** Returns the AJV compile error for `schema`, or `undefined` when it compiles.
125
+ * Compiles on this registry's instance, which has every loaded module schema
126
+ * plus the manifest root registered, so local `#/$defs`, `telo://manifest`,
127
+ * and cross-module `$ref`s all resolve. Used to fail loud on a definition
128
+ * schema that AJV cannot compile — otherwise `validateAgainstSchema` /
129
+ * `validateWithRefs` would swallow the failure and silently skip every
130
+ * resource of that kind. */
131
+ schemaCompileError(schema) {
132
+ try {
133
+ this.ajv.compile(schema);
134
+ return undefined;
135
+ }
136
+ catch (err) {
137
+ return err instanceof Error ? err.message : String(err);
138
+ }
139
+ }
121
140
  tryRegisterSchema(moduleName, typeName, schema) {
122
141
  const id = this.computeId(moduleName, typeName);
123
142
  if (!id || this.registeredSchemaIds.has(id))
@@ -78,6 +78,7 @@ export declare function resolveFieldValues(obj: unknown, path: string): unknown[
78
78
  * - A node with `properties` → recurse into each property
79
79
  */
80
80
  export declare function buildReferenceFieldMap(schema: Record<string, any>): ReferenceFieldMap;
81
+ export declare function collectRefs(node: Record<string, any>): string[];
81
82
  /** Traverses an arbitrary JSON Schema starting at the given path prefix. Used to
82
83
  * expand x-telo-schema-from sub-schemas into nested ref/scope entries so Phase 2
83
84
  * inline normalization and Phase 5 injection see slots that the local field map
@@ -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;;;;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"}
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;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE,CAa/D;AAED;;;8CAG8C;AAC9C,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,EAAE,MAAM,GACjB,iBAAiB,CAInB"}
@@ -103,7 +103,7 @@ export function buildReferenceFieldMap(schema) {
103
103
  }
104
104
  return map;
105
105
  }
106
- function collectRefs(node) {
106
+ export function collectRefs(node) {
107
107
  const refs = [];
108
108
  if (typeof node["x-telo-ref"] === "string") {
109
109
  refs.push(node["x-telo-ref"]);
@@ -41,6 +41,10 @@ export declare function jsonSchemaToCelType(schema: Record<string, any> | undefi
41
41
  export declare function celTypeSatisfiesJsonSchema(celType: string, schema: Record<string, any>): boolean;
42
42
  /** Return a literal placeholder value of the correct schema type for AJV. */
43
43
  export declare function celPlaceholderForSchema(schema: Record<string, any>): unknown;
44
+ /** Resolve a `$ref` (only `#/$defs/...` form) against the root schema. */
45
+ export declare function resolveRef(schema: Record<string, any>, root: Record<string, any>): Record<string, any>;
46
+ /** Collect property schemas from top-level `properties` and all `oneOf`/`anyOf` sub-schemas. */
47
+ export declare function collectProperties(schema: Record<string, any>): Record<string, any>;
44
48
  /** Deep-clone `data`, replacing every pure CEL template string (`${{ expr }}`) with a
45
49
  * schema-appropriate placeholder so AJV can validate non-CEL fields without false positives. */
46
50
  export declare function substituteCelFields(data: unknown, schema: Record<string, any>, rootSchema?: Record<string, any>): unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAOpD;AAKD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;oEAEoE;AACpE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,mBAAmB,CAIrB;AAiDD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAelD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGxE;AAuBD,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CAe/F;AAED;qFACqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7E;AAED;;;;6DAI6D;AAC7D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CAuBnF;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAqBhG;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAiB5E;AA2BD;iGACiG;AACjG,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,OAAO,CAqCT"}
1
+ {"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAOpD;AAKD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;oEAEoE;AACpE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,mBAAmB,CAIrB;AAiDD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAelD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGxE;AAuBD,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CAmB/F;AAED;qFACqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7E;AAED;;;;6DAI6D;AAC7D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CAuBnF;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAqBhG;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAiB5E;AAID,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAOtG;AAED,gGAAgG;AAChG,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAUlF;AAED;iGACiG;AACjG,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,OAAO,CAqCT"}
@@ -109,6 +109,10 @@ export function validateAgainstSchema(data, schema) {
109
109
  compiledSchemaValidators.set(schema, validate);
110
110
  }
111
111
  catch {
112
+ // A schema that won't compile is reported loudly (once, on the owning
113
+ // definition) by the analyzer's definition-schema compile check
114
+ // (`DefinitionRegistry.schemaCompileError`), so returning `[]` here does
115
+ // not silently accept resources of that kind.
112
116
  return [];
113
117
  }
114
118
  }
@@ -247,7 +251,7 @@ export function celPlaceholderForSchema(schema) {
247
251
  }
248
252
  const CEL_PURE_RE = /^\s*\$\{\{[^}]*\}\}\s*$/;
249
253
  /** Resolve a `$ref` (only `#/$defs/...` form) against the root schema. */
250
- function resolveRef(schema, root) {
254
+ export function resolveRef(schema, root) {
251
255
  if (schema.$ref && typeof schema.$ref === "string" && schema.$ref.startsWith("#/$defs/")) {
252
256
  const defName = schema.$ref.slice("#/$defs/".length);
253
257
  const resolved = root.$defs?.[defName];
@@ -257,7 +261,7 @@ function resolveRef(schema, root) {
257
261
  return schema;
258
262
  }
259
263
  /** Collect property schemas from top-level `properties` and all `oneOf`/`anyOf` sub-schemas. */
260
- function collectProperties(schema) {
264
+ export function collectProperties(schema) {
261
265
  const props = { ...(schema.properties ?? {}) };
262
266
  for (const sub of schema.oneOf ?? schema.anyOf ?? []) {
263
267
  if (sub && typeof sub === "object" && sub.properties) {
@@ -0,0 +1,30 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import { type AnalysisDiagnostic } from "./types.js";
3
+ /** Minimal view of a definition needed to validate an inline resource's config. */
4
+ export interface InlineDefinitionLookup {
5
+ (kind: string): {
6
+ schema?: Record<string, any>;
7
+ } | undefined;
8
+ }
9
+ /**
10
+ * Validates inline resources nested inside a resource body against their kind's
11
+ * config schema. The per-resource walk in `analyze()` validates a resource's
12
+ * own top-level config; inline resources at `x-telo-ref` slots reachable only
13
+ * through a local `$ref` (notably `Run.Sequence`'s `steps[].invoke`, hidden
14
+ * behind `#/$defs/step`) never reach the reference field map, so they would
15
+ * otherwise escape schema validation — e.g. `invoke: { kind: Console.ReadLine,
16
+ * prompt: "…" }`, where `prompt` belongs in the step's `inputs`, not the config.
17
+ *
18
+ * Walks the manifest data together with its definition schema, resolving local
19
+ * `$ref`s (so step trees of arbitrary depth are covered). At each `x-telo-ref`
20
+ * slot holding an inline resource, the inline's config is validated against its
21
+ * own kind's schema, then recursed into so inline resources nested inside
22
+ * inline resources are covered.
23
+ *
24
+ * Non-mutating: reads `manifest` and emits diagnostics anchored to its identity
25
+ * and a concrete dotted path matching the position-index key format;
26
+ * `rewriteSyntheticOrigins` reroutes those on inline-extracted (synthetic)
27
+ * manifests back to the root doc.
28
+ */
29
+ export declare function validateNestedInlineResources(manifest: ResourceManifest, rootSchema: Record<string, any>, lookupDefinition: InlineDefinitionLookup): AnalysisDiagnostic[];
30
+ //# sourceMappingURL=validate-nested-inline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-nested-inline.d.ts","sourceRoot":"","sources":["../src/validate-nested-inline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAQrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE,mFAAmF;AACnF,MAAM,WAAW,sBAAsB;IACrC,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAAE,GAAG,SAAS,CAAC;CAC9D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,gBAAgB,EAC1B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,gBAAgB,EAAE,sBAAsB,GACvC,kBAAkB,EAAE,CAoHtB"}
@@ -0,0 +1,129 @@
1
+ import { collectRefs, isInlineResource } from "./reference-field-map.js";
2
+ import { collectProperties, resolveRef, substituteCelFields, validateAgainstSchema, } from "./schema-compat.js";
3
+ import { DiagnosticSeverity } from "./types.js";
4
+ const SOURCE = "telo-analyzer";
5
+ /**
6
+ * Validates inline resources nested inside a resource body against their kind's
7
+ * config schema. The per-resource walk in `analyze()` validates a resource's
8
+ * own top-level config; inline resources at `x-telo-ref` slots reachable only
9
+ * through a local `$ref` (notably `Run.Sequence`'s `steps[].invoke`, hidden
10
+ * behind `#/$defs/step`) never reach the reference field map, so they would
11
+ * otherwise escape schema validation — e.g. `invoke: { kind: Console.ReadLine,
12
+ * prompt: "…" }`, where `prompt` belongs in the step's `inputs`, not the config.
13
+ *
14
+ * Walks the manifest data together with its definition schema, resolving local
15
+ * `$ref`s (so step trees of arbitrary depth are covered). At each `x-telo-ref`
16
+ * slot holding an inline resource, the inline's config is validated against its
17
+ * own kind's schema, then recursed into so inline resources nested inside
18
+ * inline resources are covered.
19
+ *
20
+ * Non-mutating: reads `manifest` and emits diagnostics anchored to its identity
21
+ * and a concrete dotted path matching the position-index key format;
22
+ * `rewriteSyntheticOrigins` reroutes those on inline-extracted (synthetic)
23
+ * manifests back to the root doc.
24
+ */
25
+ export function validateNestedInlineResources(manifest, rootSchema, lookupDefinition) {
26
+ const diagnostics = [];
27
+ const resource = { kind: manifest.kind, name: manifest.metadata?.name };
28
+ const filePath = manifest.metadata?.source;
29
+ function validateInline(inline, path) {
30
+ const kind = inline.kind;
31
+ const def = lookupDefinition(kind);
32
+ // Unknown kind: these `$ref`-hidden slots are invisible to the field-map
33
+ // driven reference checks too, so nothing else would flag it — report here.
34
+ if (!def) {
35
+ diagnostics.push({
36
+ severity: DiagnosticSeverity.Error,
37
+ code: "UNDEFINED_KIND",
38
+ source: SOURCE,
39
+ message: `${resource.kind}/${resource.name}: inline ${kind} at '${path}': No Telo.Definition found for kind '${kind}'.`,
40
+ data: { resource, filePath, path: `${path}.kind` },
41
+ });
42
+ return;
43
+ }
44
+ // Kind exists but declares no config schema (e.g. a pure Telo.Type): no
45
+ // config to validate and no schema-declared slots to nest resources in.
46
+ if (!def.schema)
47
+ return;
48
+ const schema = def.schema;
49
+ // `kind` / `metadata` are implicit on every resource; inject them so a
50
+ // strict `additionalProperties: false` config schema doesn't reject them.
51
+ const effectiveSchema = schema.additionalProperties === false
52
+ ? {
53
+ ...schema,
54
+ properties: {
55
+ kind: { type: "string" },
56
+ metadata: { type: "object" },
57
+ ...schema.properties,
58
+ },
59
+ }
60
+ : schema;
61
+ // Inline resources omit `metadata` — it is synthesized when the kernel
62
+ // registers them (and by `normalizeInlineResources` for extracted slots,
63
+ // which assigns a derived `metadata.name`). Config schemas conventionally
64
+ // declare `required: ["metadata", …]` with `metadata.name` required, so add
65
+ // a placeholder before validating to mirror the post-registration shape.
66
+ const existingMeta = inline.metadata && typeof inline.metadata === "object"
67
+ ? inline.metadata
68
+ : {};
69
+ const data = { ...inline, metadata: { name: "__inline__", ...existingMeta } };
70
+ const substituted = substituteCelFields(data, effectiveSchema, effectiveSchema);
71
+ for (const issue of validateAgainstSchema(substituted, effectiveSchema)) {
72
+ diagnostics.push({
73
+ severity: DiagnosticSeverity.Error,
74
+ code: "SCHEMA_VIOLATION",
75
+ source: SOURCE,
76
+ message: `${resource.kind}/${resource.name}: inline ${kind} at '${path}': ${issue.message}`,
77
+ data: { resource, filePath, path: issue.path ? `${path}.${issue.path}` : path },
78
+ });
79
+ }
80
+ // Recurse into the inline body against its own schema so deeper inline
81
+ // resources (e.g. an inline Run.Sequence's own steps) are validated too.
82
+ walk(inline, schema, schema, path);
83
+ }
84
+ function walk(data, schema, schemaRoot, path) {
85
+ if (!schema || typeof schema !== "object")
86
+ return;
87
+ const resolved = resolveRef(schema, schemaRoot);
88
+ // Reference slot: the value is either a named reference (`{kind, name}`,
89
+ // validated as its own manifest) or an inline resource to validate here.
90
+ if (collectRefs(resolved).length > 0) {
91
+ if (data &&
92
+ typeof data === "object" &&
93
+ !Array.isArray(data) &&
94
+ isInlineResource(data)) {
95
+ validateInline(data, path);
96
+ }
97
+ return;
98
+ }
99
+ if (Array.isArray(data)) {
100
+ const itemSchema = resolved.items;
101
+ if (!itemSchema)
102
+ return;
103
+ for (let i = 0; i < data.length; i++) {
104
+ walk(data[i], itemSchema, schemaRoot, `${path}[${i}]`);
105
+ }
106
+ return;
107
+ }
108
+ if (data && typeof data === "object") {
109
+ const props = collectProperties(resolved);
110
+ const additional = resolved.additionalProperties &&
111
+ typeof resolved.additionalProperties === "object" &&
112
+ !Array.isArray(resolved.additionalProperties)
113
+ ? resolved.additionalProperties
114
+ : undefined;
115
+ // Descend only where the schema declares structure. Freeform fields
116
+ // (`additionalProperties: true`, e.g. step `inputs`) carry caller data
117
+ // that may coincidentally look like `{kind: …}`; not descending there
118
+ // keeps the inline-resource detection anchored to real ref slots.
119
+ for (const [key, value] of Object.entries(data)) {
120
+ const propSchema = props[key] ?? additional;
121
+ if (!propSchema)
122
+ continue;
123
+ walk(value, propSchema, schemaRoot, path ? `${path}.${key}` : key);
124
+ }
125
+ }
126
+ }
127
+ walk(manifest, rootSchema, rootSchema, "");
128
+ return diagnostics;
129
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Telo Analyzer - Static manifest validator for Telo manifests.",
5
5
  "keywords": [
6
6
  "telo",
package/src/analyzer.ts CHANGED
@@ -31,6 +31,7 @@ import {
31
31
  resolveTypeFieldToSchema,
32
32
  } from "./validate-cel-context.js";
33
33
  import { validateExtends } from "./validate-extends.js";
34
+ import { validateNestedInlineResources } from "./validate-nested-inline.js";
34
35
  import { validateProviderCoherence } from "./validate-provider-coherence.js";
35
36
  import { validateReferences } from "./validate-references.js";
36
37
  import { validateThrowsCoverage } from "./validate-throws-coverage.js";
@@ -698,6 +699,32 @@ export class StaticAnalyzer {
698
699
  }
699
700
  }
700
701
 
702
+ // Fail loud on definition schemas AJV cannot compile. `validateAgainstSchema`
703
+ // and `validateWithRefs` swallow compile failures (returning no issues),
704
+ // which would silently skip schema validation for every resource of that
705
+ // kind — surface the broken schema once, anchored on the definition itself.
706
+ for (const m of allManifests) {
707
+ if (m.kind !== "Telo.Definition" && m.kind !== "Telo.Abstract") continue;
708
+ const schema = (m as Record<string, any>).schema;
709
+ if (!schema || typeof schema !== "object") continue;
710
+ const name = m.metadata?.name as string | undefined;
711
+ if (!name) continue;
712
+ const compileError = defs.schemaCompileError(schema as Record<string, any>);
713
+ if (compileError) {
714
+ diagnostics.push({
715
+ severity: DiagnosticSeverity.Error,
716
+ code: "SCHEMA_COMPILE_ERROR",
717
+ source: SOURCE,
718
+ message: `${m.kind}/${name}: definition schema failed to compile: ${compileError}`,
719
+ data: {
720
+ resource: { kind: m.kind, name },
721
+ filePath: (m.metadata as { source?: string } | undefined)?.source,
722
+ path: "schema",
723
+ },
724
+ });
725
+ }
726
+ }
727
+
701
728
  // Library env: rejection — `env:` on a Library `variables` / `secrets`
702
729
  // entry is forbidden. The Library entry schema is otherwise open so that
703
730
  // any JSON Schema property schema is valid; this targeted check produces
@@ -805,6 +832,39 @@ export class StaticAnalyzer {
805
832
  }
806
833
  }
807
834
 
835
+ // Validate inline resources nested inside this resource's body (e.g. a
836
+ // Run.Sequence step's `invoke: { kind, ...config }`). These sit at
837
+ // x-telo-ref slots reached only through local `$ref`s, which the
838
+ // reference field map intentionally does not follow, so they escape both
839
+ // inline-extraction and the per-resource schema check above.
840
+ if (definition.schema) {
841
+ // Resolve inline kinds in the parent resource's scope: direct kind
842
+ // first, then the parent module's own aliases (for resources declared
843
+ // inside an imported module), then the root aliases. Mirrors how the
844
+ // analyzer resolves kinds elsewhere so module-scoped aliases don't
845
+ // produce false UNDEFINED_KIND diagnostics.
846
+ const ownModule = (m.metadata as { module?: string } | undefined)?.module;
847
+ const scopeResolver =
848
+ ownModule && !rootModules.has(ownModule) ? aliasesByModule.get(ownModule) : undefined;
849
+ diagnostics.push(
850
+ ...validateNestedInlineResources(
851
+ m,
852
+ definition.schema as Record<string, any>,
853
+ (kind: string) => {
854
+ const direct = defs.resolve(kind);
855
+ if (direct) return direct;
856
+ const viaScope = scopeResolver?.resolveKind(kind);
857
+ if (viaScope) {
858
+ const scoped = defs.resolve(viaScope);
859
+ if (scoped) return scoped;
860
+ }
861
+ const viaRoot = aliases.resolveKind(kind);
862
+ return viaRoot ? defs.resolve(viaRoot) : undefined;
863
+ },
864
+ ),
865
+ );
866
+ }
867
+
808
868
  // (Invocation context compatibility check is handled via x-telo-context in the CEL pass below)
809
869
  }
810
870
 
@@ -973,7 +1033,18 @@ export class StaticAnalyzer {
973
1033
  }
974
1034
 
975
1035
  const engine = defaultRegistry().get(engineName);
976
- if (!engine) return;
1036
+ if (!engine) {
1037
+ // No registered engine owns this tag — the expression would go
1038
+ // entirely unanalyzed. Surface it rather than skipping silently.
1039
+ diagnostics.push({
1040
+ severity: DiagnosticSeverity.Error,
1041
+ code: "UNKNOWN_ENGINE",
1042
+ source: SOURCE,
1043
+ message: `${m.kind}/${resource.name}: no templating engine registered for '!${engineName}' at '${path}'.`,
1044
+ data: { resource, filePath, path },
1045
+ });
1046
+ return;
1047
+ }
977
1048
  const findings = engine.analyze(expr, { celEnv: this.celEnv, contextSchema: effectiveContext });
978
1049
  for (const f of findings) {
979
1050
  if (f.code === "CEL_SYNTAX_ERROR") {
@@ -119,7 +119,10 @@ export class DefinitionRegistry {
119
119
  }
120
120
 
121
121
  /** Validates data against a schema using this registry's AJV instance, which has all
122
- * registered definition schemas loaded — enabling cross-module $ref resolution. */
122
+ * registered definition schemas loaded — enabling cross-module $ref resolution.
123
+ * A compile failure returns `[]` here; it is surfaced loudly (once, on the
124
+ * owning definition) by `schemaCompileError` via the analyzer's
125
+ * definition-schema compile check, so resources are never silently skipped. */
123
126
  validateWithRefs(data: unknown, schema: Record<string, any>): string[] {
124
127
  let validate: ReturnType<typeof this.ajv.compile>;
125
128
  try {
@@ -131,6 +134,22 @@ export class DefinitionRegistry {
131
134
  return (validate.errors ?? []).map(formatSingleError);
132
135
  }
133
136
 
137
+ /** Returns the AJV compile error for `schema`, or `undefined` when it compiles.
138
+ * Compiles on this registry's instance, which has every loaded module schema
139
+ * plus the manifest root registered, so local `#/$defs`, `telo://manifest`,
140
+ * and cross-module `$ref`s all resolve. Used to fail loud on a definition
141
+ * schema that AJV cannot compile — otherwise `validateAgainstSchema` /
142
+ * `validateWithRefs` would swallow the failure and silently skip every
143
+ * resource of that kind. */
144
+ schemaCompileError(schema: Record<string, any>): string | undefined {
145
+ try {
146
+ this.ajv.compile(schema);
147
+ return undefined;
148
+ } catch (err) {
149
+ return err instanceof Error ? err.message : String(err);
150
+ }
151
+ }
152
+
134
153
  private tryRegisterSchema(
135
154
  moduleName: string,
136
155
  typeName: string,
@@ -148,7 +148,7 @@ export function buildReferenceFieldMap(schema: Record<string, any>): ReferenceFi
148
148
  return map;
149
149
  }
150
150
 
151
- function collectRefs(node: Record<string, any>): string[] {
151
+ export function collectRefs(node: Record<string, any>): string[] {
152
152
  const refs: string[] = [];
153
153
  if (typeof node["x-telo-ref"] === "string") {
154
154
  refs.push(node["x-telo-ref"]);
@@ -146,6 +146,10 @@ export function validateAgainstSchema(data: unknown, schema: Record<string, any>
146
146
  validate = ajv.compile(schema);
147
147
  compiledSchemaValidators.set(schema, validate);
148
148
  } catch {
149
+ // A schema that won't compile is reported loudly (once, on the owning
150
+ // definition) by the analyzer's definition-schema compile check
151
+ // (`DefinitionRegistry.schemaCompileError`), so returning `[]` here does
152
+ // not silently accept resources of that kind.
149
153
  return [];
150
154
  }
151
155
  }
@@ -273,7 +277,7 @@ export function celPlaceholderForSchema(schema: Record<string, any>): unknown {
273
277
  const CEL_PURE_RE = /^\s*\$\{\{[^}]*\}\}\s*$/;
274
278
 
275
279
  /** Resolve a `$ref` (only `#/$defs/...` form) against the root schema. */
276
- function resolveRef(schema: Record<string, any>, root: Record<string, any>): Record<string, any> {
280
+ export function resolveRef(schema: Record<string, any>, root: Record<string, any>): Record<string, any> {
277
281
  if (schema.$ref && typeof schema.$ref === "string" && schema.$ref.startsWith("#/$defs/")) {
278
282
  const defName = schema.$ref.slice("#/$defs/".length);
279
283
  const resolved = root.$defs?.[defName];
@@ -283,7 +287,7 @@ function resolveRef(schema: Record<string, any>, root: Record<string, any>): Rec
283
287
  }
284
288
 
285
289
  /** Collect property schemas from top-level `properties` and all `oneOf`/`anyOf` sub-schemas. */
286
- function collectProperties(schema: Record<string, any>): Record<string, any> {
290
+ export function collectProperties(schema: Record<string, any>): Record<string, any> {
287
291
  const props: Record<string, any> = { ...(schema.properties ?? {}) };
288
292
  for (const sub of schema.oneOf ?? schema.anyOf ?? []) {
289
293
  if (sub && typeof sub === "object" && sub.properties) {
@@ -0,0 +1,158 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import { collectRefs, isInlineResource } from "./reference-field-map.js";
3
+ import {
4
+ collectProperties,
5
+ resolveRef,
6
+ substituteCelFields,
7
+ validateAgainstSchema,
8
+ } from "./schema-compat.js";
9
+ import { DiagnosticSeverity, type AnalysisDiagnostic } from "./types.js";
10
+
11
+ const SOURCE = "telo-analyzer";
12
+
13
+ /** Minimal view of a definition needed to validate an inline resource's config. */
14
+ export interface InlineDefinitionLookup {
15
+ (kind: string): { schema?: Record<string, any> } | undefined;
16
+ }
17
+
18
+ /**
19
+ * Validates inline resources nested inside a resource body against their kind's
20
+ * config schema. The per-resource walk in `analyze()` validates a resource's
21
+ * own top-level config; inline resources at `x-telo-ref` slots reachable only
22
+ * through a local `$ref` (notably `Run.Sequence`'s `steps[].invoke`, hidden
23
+ * behind `#/$defs/step`) never reach the reference field map, so they would
24
+ * otherwise escape schema validation — e.g. `invoke: { kind: Console.ReadLine,
25
+ * prompt: "…" }`, where `prompt` belongs in the step's `inputs`, not the config.
26
+ *
27
+ * Walks the manifest data together with its definition schema, resolving local
28
+ * `$ref`s (so step trees of arbitrary depth are covered). At each `x-telo-ref`
29
+ * slot holding an inline resource, the inline's config is validated against its
30
+ * own kind's schema, then recursed into so inline resources nested inside
31
+ * inline resources are covered.
32
+ *
33
+ * Non-mutating: reads `manifest` and emits diagnostics anchored to its identity
34
+ * and a concrete dotted path matching the position-index key format;
35
+ * `rewriteSyntheticOrigins` reroutes those on inline-extracted (synthetic)
36
+ * manifests back to the root doc.
37
+ */
38
+ export function validateNestedInlineResources(
39
+ manifest: ResourceManifest,
40
+ rootSchema: Record<string, any>,
41
+ lookupDefinition: InlineDefinitionLookup,
42
+ ): AnalysisDiagnostic[] {
43
+ const diagnostics: AnalysisDiagnostic[] = [];
44
+ const resource = { kind: manifest.kind, name: manifest.metadata?.name as string };
45
+ const filePath = (manifest.metadata as { source?: string } | undefined)?.source;
46
+
47
+ function validateInline(inline: Record<string, any>, path: string): void {
48
+ const kind = inline.kind as string;
49
+ const def = lookupDefinition(kind);
50
+ // Unknown kind: these `$ref`-hidden slots are invisible to the field-map
51
+ // driven reference checks too, so nothing else would flag it — report here.
52
+ if (!def) {
53
+ diagnostics.push({
54
+ severity: DiagnosticSeverity.Error,
55
+ code: "UNDEFINED_KIND",
56
+ source: SOURCE,
57
+ message: `${resource.kind}/${resource.name}: inline ${kind} at '${path}': No Telo.Definition found for kind '${kind}'.`,
58
+ data: { resource, filePath, path: `${path}.kind` },
59
+ });
60
+ return;
61
+ }
62
+ // Kind exists but declares no config schema (e.g. a pure Telo.Type): no
63
+ // config to validate and no schema-declared slots to nest resources in.
64
+ if (!def.schema) return;
65
+ const schema = def.schema;
66
+ // `kind` / `metadata` are implicit on every resource; inject them so a
67
+ // strict `additionalProperties: false` config schema doesn't reject them.
68
+ const effectiveSchema =
69
+ schema.additionalProperties === false
70
+ ? {
71
+ ...schema,
72
+ properties: {
73
+ kind: { type: "string" },
74
+ metadata: { type: "object" },
75
+ ...schema.properties,
76
+ },
77
+ }
78
+ : schema;
79
+ // Inline resources omit `metadata` — it is synthesized when the kernel
80
+ // registers them (and by `normalizeInlineResources` for extracted slots,
81
+ // which assigns a derived `metadata.name`). Config schemas conventionally
82
+ // declare `required: ["metadata", …]` with `metadata.name` required, so add
83
+ // a placeholder before validating to mirror the post-registration shape.
84
+ const existingMeta =
85
+ inline.metadata && typeof inline.metadata === "object"
86
+ ? (inline.metadata as Record<string, unknown>)
87
+ : {};
88
+ const data = { ...inline, metadata: { name: "__inline__", ...existingMeta } };
89
+ const substituted = substituteCelFields(data, effectiveSchema, effectiveSchema);
90
+ for (const issue of validateAgainstSchema(substituted, effectiveSchema)) {
91
+ diagnostics.push({
92
+ severity: DiagnosticSeverity.Error,
93
+ code: "SCHEMA_VIOLATION",
94
+ source: SOURCE,
95
+ message: `${resource.kind}/${resource.name}: inline ${kind} at '${path}': ${issue.message}`,
96
+ data: { resource, filePath, path: issue.path ? `${path}.${issue.path}` : path },
97
+ });
98
+ }
99
+ // Recurse into the inline body against its own schema so deeper inline
100
+ // resources (e.g. an inline Run.Sequence's own steps) are validated too.
101
+ walk(inline, schema, schema, path);
102
+ }
103
+
104
+ function walk(
105
+ data: unknown,
106
+ schema: Record<string, any> | undefined,
107
+ schemaRoot: Record<string, any>,
108
+ path: string,
109
+ ): void {
110
+ if (!schema || typeof schema !== "object") return;
111
+ const resolved = resolveRef(schema, schemaRoot);
112
+
113
+ // Reference slot: the value is either a named reference (`{kind, name}`,
114
+ // validated as its own manifest) or an inline resource to validate here.
115
+ if (collectRefs(resolved).length > 0) {
116
+ if (
117
+ data &&
118
+ typeof data === "object" &&
119
+ !Array.isArray(data) &&
120
+ isInlineResource(data as Record<string, unknown>)
121
+ ) {
122
+ validateInline(data as Record<string, any>, path);
123
+ }
124
+ return;
125
+ }
126
+
127
+ if (Array.isArray(data)) {
128
+ const itemSchema = resolved.items as Record<string, any> | undefined;
129
+ if (!itemSchema) return;
130
+ for (let i = 0; i < data.length; i++) {
131
+ walk(data[i], itemSchema, schemaRoot, `${path}[${i}]`);
132
+ }
133
+ return;
134
+ }
135
+
136
+ if (data && typeof data === "object") {
137
+ const props = collectProperties(resolved);
138
+ const additional =
139
+ resolved.additionalProperties &&
140
+ typeof resolved.additionalProperties === "object" &&
141
+ !Array.isArray(resolved.additionalProperties)
142
+ ? (resolved.additionalProperties as Record<string, any>)
143
+ : undefined;
144
+ // Descend only where the schema declares structure. Freeform fields
145
+ // (`additionalProperties: true`, e.g. step `inputs`) carry caller data
146
+ // that may coincidentally look like `{kind: …}`; not descending there
147
+ // keeps the inline-resource detection anchored to real ref slots.
148
+ for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
149
+ const propSchema = props[key] ?? additional;
150
+ if (!propSchema) continue;
151
+ walk(value, propSchema as Record<string, any>, schemaRoot, path ? `${path}.${key}` : key);
152
+ }
153
+ }
154
+ }
155
+
156
+ walk(manifest, rootSchema, rootSchema, "");
157
+ return diagnostics;
158
+ }
@@ -1,10 +0,0 @@
1
- import { type ManifestAdapter } from "../types.js";
2
- export declare class HttpAdapter implements ManifestAdapter {
3
- supports(url: string): boolean;
4
- read(url: string): Promise<{
5
- text: string;
6
- source: string;
7
- }>;
8
- resolveRelative(base: string, relative: string): string;
9
- }
10
- //# sourceMappingURL=http-adapter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"http-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/http-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9E,qBAAa,WAAY,YAAW,eAAe;IACjD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAWlE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;CAIxD"}
@@ -1,18 +0,0 @@
1
- import { DEFAULT_MANIFEST_FILENAME } from "../types.js";
2
- export class HttpAdapter {
3
- supports(url) {
4
- return url.startsWith("http://") || url.startsWith("https://");
5
- }
6
- async read(url) {
7
- const fetchUrl = url.includes(".yaml") ? url : `${url}/${DEFAULT_MANIFEST_FILENAME}`;
8
- const response = await fetch(fetchUrl);
9
- if (!response.ok) {
10
- throw new Error(`Failed to fetch manifest from ${fetchUrl}: ${response.status} ${response.statusText}`);
11
- }
12
- return { text: await response.text(), source: fetchUrl };
13
- }
14
- resolveRelative(base, relative) {
15
- const baseDir = base.endsWith("/") ? base : base.slice(0, base.lastIndexOf("/") + 1);
16
- return new URL(relative, baseDir).href;
17
- }
18
- }
@@ -1,17 +0,0 @@
1
- import { type ManifestAdapter } from "../types.js";
2
- /** Node.js fs-based ManifestAdapter for local files. Not browser-compatible. */
3
- export declare class NodeAdapter implements ManifestAdapter {
4
- private readonly cwd;
5
- constructor(cwd?: string);
6
- supports(url: string): boolean;
7
- read(url: string): Promise<{
8
- text: string;
9
- source: string;
10
- }>;
11
- resolveRelative(base: string, relative: string): string;
12
- expandGlob(base: string, patterns: string[]): Promise<string[]>;
13
- resolveOwnerOf(fileUrl: string): Promise<string | null>;
14
- }
15
- /** @deprecated Use `new NodeAdapter(cwd)` instead */
16
- export declare function createNodeAdapter(cwd?: string): ManifestAdapter;
17
- //# sourceMappingURL=node-adapter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"node-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/node-adapter.ts"],"names":[],"mappings":"AAIA,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAM9E,gFAAgF;AAChF,qBAAa,WAAY,YAAW,eAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,GAAE,MAAsB;IAExD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAUxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IASlE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAKjD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAc/D,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAoB9D;AAED,qDAAqD;AACrD,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAsB,GAAG,eAAe,CAE9E"}
@@ -1,71 +0,0 @@
1
- import * as fs from "fs/promises";
2
- import * as path from "path";
3
- import { fileURLToPath } from "url";
4
- import { minimatch } from "minimatch";
5
- import { DEFAULT_MANIFEST_FILENAME } from "../types.js";
6
- function toFilePath(url) {
7
- return url.startsWith("file://") ? fileURLToPath(url) : url;
8
- }
9
- /** Node.js fs-based ManifestAdapter for local files. Not browser-compatible. */
10
- export class NodeAdapter {
11
- cwd;
12
- constructor(cwd = process.cwd()) {
13
- this.cwd = cwd;
14
- }
15
- supports(url) {
16
- return (url.startsWith("file://") ||
17
- url.startsWith("/") ||
18
- url.startsWith("./") ||
19
- url.startsWith("../") ||
20
- (!url.includes("://") && !url.includes("@")));
21
- }
22
- async read(url) {
23
- const filePath = toFilePath(url);
24
- const stat = await fs.stat(filePath).catch(() => null);
25
- const resolvedPath = stat?.isDirectory() ? path.join(filePath, DEFAULT_MANIFEST_FILENAME) : filePath;
26
- const text = await fs.readFile(resolvedPath, "utf8");
27
- return { text, source: resolvedPath };
28
- }
29
- resolveRelative(base, relative) {
30
- const baseDir = path.dirname(path.resolve(this.cwd, toFilePath(base)));
31
- return path.resolve(baseDir, relative);
32
- }
33
- async expandGlob(base, patterns) {
34
- const baseDir = path.dirname(path.resolve(this.cwd, toFilePath(base)));
35
- const entries = await fs.readdir(baseDir, { recursive: true, encoding: "utf8" });
36
- const normalizedPatterns = patterns.map((p) => p.replace(/\\/g, "/").replace(/^\.\//, ""));
37
- const matched = [];
38
- for (const entry of entries) {
39
- const normalized = entry.replace(/\\/g, "/");
40
- if (normalizedPatterns.some((p) => minimatch(normalized, p))) {
41
- matched.push(path.resolve(baseDir, entry));
42
- }
43
- }
44
- return matched.sort();
45
- }
46
- async resolveOwnerOf(fileUrl) {
47
- const resolved = path.resolve(this.cwd, toFilePath(fileUrl));
48
- let dir = path.dirname(resolved);
49
- while (true) {
50
- const candidate = path.join(dir, DEFAULT_MANIFEST_FILENAME);
51
- if (candidate !== resolved) {
52
- try {
53
- await fs.access(candidate);
54
- return candidate;
55
- }
56
- catch {
57
- // telo.yaml not found at this level
58
- }
59
- }
60
- const parent = path.dirname(dir);
61
- if (parent === dir)
62
- break;
63
- dir = parent;
64
- }
65
- return null;
66
- }
67
- }
68
- /** @deprecated Use `new NodeAdapter(cwd)` instead */
69
- export function createNodeAdapter(cwd = process.cwd()) {
70
- return new NodeAdapter(cwd);
71
- }
@@ -1,15 +0,0 @@
1
- import { type ManifestAdapter } from "../types.js";
2
- export declare class RegistryAdapter implements ManifestAdapter {
3
- private registryUrl;
4
- constructor(registryUrl?: string);
5
- supports(url: string): boolean;
6
- read(moduleRef: string): Promise<{
7
- text: string;
8
- source: string;
9
- }>;
10
- resolveRelative(base: string, relative: string): string;
11
- private toRegistryModuleBase;
12
- private toRegistryUrl;
13
- private parseModuleRef;
14
- }
15
- //# sourceMappingURL=registry-adapter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/registry-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAI9E,qBAAa,eAAgB,YAAW,eAAe;IACzC,OAAO,CAAC,WAAW;gBAAX,WAAW,SAAuB;IAEtD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAWxB,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAWxE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAMvD,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,cAAc;CAmBvB"}
@@ -1,53 +0,0 @@
1
- import { DEFAULT_MANIFEST_FILENAME } from "../types.js";
2
- const DEFAULT_REGISTRY_URL = "https://registry.telo.run";
3
- export class RegistryAdapter {
4
- registryUrl;
5
- constructor(registryUrl = DEFAULT_REGISTRY_URL) {
6
- this.registryUrl = registryUrl;
7
- }
8
- supports(url) {
9
- return (!url.startsWith("http://") &&
10
- !url.startsWith("https://") &&
11
- !url.startsWith("/") &&
12
- !url.startsWith(".") &&
13
- url.includes("@") &&
14
- url.includes("/"));
15
- }
16
- async read(moduleRef) {
17
- const fetchUrl = this.toRegistryUrl(moduleRef);
18
- const response = await fetch(fetchUrl);
19
- if (!response.ok) {
20
- throw new Error(`Failed to fetch manifest ${moduleRef}: ${response.status} ${response.statusText}`);
21
- }
22
- return { text: await response.text(), source: fetchUrl };
23
- }
24
- resolveRelative(base, relative) {
25
- const baseUrl = this.supports(base) ? this.toRegistryModuleBase(base) : base;
26
- const baseWithSlash = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
27
- return new URL(relative, baseWithSlash).href;
28
- }
29
- toRegistryModuleBase(moduleRef) {
30
- const parsed = this.parseModuleRef(moduleRef);
31
- const normalizedBase = this.registryUrl.replace(/\/+$/, "");
32
- return `${normalizedBase}/${parsed.modulePath}/${parsed.version}`;
33
- }
34
- toRegistryUrl(moduleRef) {
35
- return `${this.toRegistryModuleBase(moduleRef)}/${DEFAULT_MANIFEST_FILENAME}`;
36
- }
37
- parseModuleRef(moduleRef) {
38
- const atIdx = moduleRef.lastIndexOf("@");
39
- if (atIdx <= 0 || atIdx === moduleRef.length - 1) {
40
- throw new Error(`Invalid module reference '${moduleRef}', expected namespace/name@version`);
41
- }
42
- const modulePath = moduleRef.slice(0, atIdx);
43
- if (!modulePath.includes("/")) {
44
- throw new Error(`Invalid module reference '${moduleRef}', expected namespace/name@version`);
45
- }
46
- const rawVersion = moduleRef.slice(atIdx + 1);
47
- const version = rawVersion.startsWith("v") ? rawVersion.substring(1) : rawVersion;
48
- if (!version) {
49
- throw new Error(`Invalid module reference '${moduleRef}', expected namespace/name@version`);
50
- }
51
- return { modulePath, version };
52
- }
53
- }