@telorun/analyzer 0.48.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/analysis-registry.d.ts +22 -11
  2. package/dist/analysis-registry.d.ts.map +1 -1
  3. package/dist/analysis-registry.js +36 -39
  4. package/dist/analyzer.d.ts +38 -1
  5. package/dist/analyzer.d.ts.map +1 -1
  6. package/dist/analyzer.js +115 -83
  7. package/dist/builtins.d.ts.map +1 -1
  8. package/dist/builtins.js +72 -1
  9. package/dist/extends-resolution.d.ts +41 -0
  10. package/dist/extends-resolution.d.ts.map +1 -1
  11. package/dist/extends-resolution.js +68 -0
  12. package/dist/index.d.ts +4 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +2 -1
  15. package/dist/invocation-contract.d.ts +100 -0
  16. package/dist/invocation-contract.d.ts.map +1 -0
  17. package/dist/invocation-contract.js +208 -0
  18. package/dist/schema-compat.d.ts +12 -4
  19. package/dist/schema-compat.d.ts.map +1 -1
  20. package/dist/schema-compat.js +185 -9
  21. package/dist/validate-base-mapping.js +11 -1
  22. package/dist/validate-cel-context.d.ts +0 -6
  23. package/dist/validate-cel-context.d.ts.map +1 -1
  24. package/dist/validate-cel-context.js +51 -4
  25. package/dist/validate-invocation-contract.d.ts +30 -0
  26. package/dist/validate-invocation-contract.d.ts.map +1 -0
  27. package/dist/validate-invocation-contract.js +394 -0
  28. package/dist/validate-step-inputs.d.ts +24 -0
  29. package/dist/validate-step-inputs.d.ts.map +1 -0
  30. package/dist/validate-step-inputs.js +87 -0
  31. package/dist/validate-throws-coverage.d.ts +1 -1
  32. package/dist/validate-throws-coverage.d.ts.map +1 -1
  33. package/dist/validate-throws-coverage.js +9 -1
  34. package/package.json +2 -2
  35. package/src/analysis-registry.ts +44 -34
  36. package/src/analyzer.ts +171 -100
  37. package/src/builtins.ts +74 -1
  38. package/src/extends-resolution.ts +86 -0
  39. package/src/index.ts +13 -1
  40. package/src/invocation-contract.ts +275 -0
  41. package/src/schema-compat.ts +191 -8
  42. package/src/validate-base-mapping.ts +14 -1
  43. package/src/validate-cel-context.ts +49 -4
  44. package/src/validate-invocation-contract.ts +450 -0
  45. package/src/validate-step-inputs.ts +117 -0
  46. package/src/validate-throws-coverage.ts +12 -2
@@ -62,20 +62,25 @@ export declare class AnalysisRegistry {
62
62
  * unresolvable. */
63
63
  capabilityForRef(xTeloRef: string): string | undefined;
64
64
  /** Resolves the JSON Schema for a kind's `invoke()` inputs, for editor hosts
65
- * that render a typed inputs form. Two-layer fallback mirroring the analyzer's
66
- * template inputs typing: the definition's own `inputType`, then the
67
- * `extends`-declared abstract's `inputType`. Resolves the inline
68
- * (`{ kind: Type.JsonSchema, schema }`) and raw-schema forms; a bare named
69
- * type reference is left unresolved (returns undefined) so the caller can fall
70
- * back to a freeform map. Undefined when the kind declares no input contract. */
65
+ * that render a typed inputs form. A thin wrapper over the shared contract
66
+ * resolver, so the form an editor renders is the contract `telo check` and the
67
+ * kernel enforce — nearest declaration along `extends`, replacing rather than
68
+ * merging. A bare named type reference is left unresolved (returns undefined,
69
+ * no manifests are in scope here) so the caller can fall back to a freeform
70
+ * map. Undefined when the kind declares no input contract. */
71
71
  inputTypeForKind(kind: string): Record<string, unknown> | undefined;
72
- /** Resolves the JSON Schema for a kind's `invoke()` / `run()` output, for
72
+ /** Resolves the JSON Schema for a kind's `invoke()` / `provide()` output, for
73
73
  * editor hosts that render a typed output signature. Mirrors
74
- * {@link inputTypeForKind}: the definition's own `outputType`, then the
75
- * `extends`-declared abstract's `outputType`. Resolves the inline and
76
- * raw-schema forms; a bare named type reference is left unresolved. Undefined
77
- * when the kind declares no output contract. */
74
+ * {@link inputTypeForKind}. Undefined when the kind declares no output
75
+ * contract. */
78
76
  outputTypeForKind(kind: string): Record<string, unknown> | undefined;
77
+ private contractForKind;
78
+ /** A resolver that re-scopes at every hop of an `extends` chain: each kind is
79
+ * resolved in the module that DECLARED the definition it was read off, since
80
+ * `extends` aliases are lexical. `resolveParent` always passes that definition
81
+ * as `from`, so one resolver serves a chain of any depth crossing any number
82
+ * of modules. */
83
+ private scopedDefResolver;
79
84
  private capabilitiesForRefs;
80
85
  /**
81
86
  * Walks a manifest's annotation sites (refs, scopes, schema-from, CEL) via
@@ -96,6 +101,12 @@ export declare class AnalysisRegistry {
96
101
  */
97
102
  builtinDefinitions(): ResourceDefinition[];
98
103
  resolveDefinition(kind: string): ResourceDefinition | undefined;
104
+ /** Resolve a kind in a named module's scope. The entry point for walking an
105
+ * `extends` chain from outside it (the kernel's contract binding): aliases are
106
+ * lexical, so each hop must resolve in the module that declared the definition
107
+ * the kind was read off. Falls back to the global table when the module is
108
+ * unknown or is a root. */
109
+ resolveDefinitionIn(kind: string, module?: string): ResourceDefinition | undefined;
99
110
  /** A resolver scoped to `def`'s OWN module, for resolving that definition's
100
111
  * `extends` target.
101
112
  *
@@ -1 +1 @@
1
- {"version":3,"file":"analysis-registry.d.ts","sourceRoot":"","sources":["../src/analysis-registry.ts"],"names":[],"mappings":"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"}
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;AAIhG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;wDAEwD;AACxD,MAAM,WAAW,YAAY;IAC3B;0CACsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,sFAAsF;IACtF,IAAI,EAAE,MAAM,EAAE,CAAC;IACf;;;8EAG0E;IAC1E,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;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;;+EAE2E;IAC3E,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAI9E,wFAAwF;IACxF,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAIzD,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,oBAAoB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,YAAY,EAAE;IAoBhE;;;;;;wBAMoB;IACpB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAQtD;;;;;;mEAM+D;IAC/D,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAInE;;;oBAGgB;IAChB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAIpE,OAAO,CAAC,eAAe;IAYvB;;;;sBAIkB;IAClB,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,mBAAmB;IAS3B;;;;;;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;;;;gCAI4B;IAC5B,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAMlF;;;;;;;;yCAQqC;IACrC,qBAAqB,CAAC,GAAG,EAAE;QACzB,QAAQ,CAAC,EAAE;YAAE,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KAChC,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,kBAAkB,GAAG,SAAS;IASpD;;qFAEiF;IACjF,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE;IAUzC,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;;;;;;;;iDAQ6C;IAC7C,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS;IAqB9D;;;;;gEAK4D;IAC5D,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IAiB7D,qFAAqF;IACrF,QAAQ,IAAI,eAAe;CAG5B"}
@@ -3,8 +3,8 @@ import { KERNEL_BUILTINS } from "./builtins.js";
3
3
  import { DefinitionRegistry } from "./definition-registry.js";
4
4
  import { computeSuggestKind, computeValidUserFacingKinds } from "./kind-suggest.js";
5
5
  import { visitManifest as runVisitManifest } from "./manifest-visitor.js";
6
+ import { resolveContract } from "./invocation-contract.js";
6
7
  import { isRefEntry, isScopeEntry } from "./reference-field-map.js";
7
- import { resolveTypeFieldToSchema } from "./validate-cel-context.js";
8
8
  /**
9
9
  * Accumulates type and alias knowledge for a running kernel or analysis session.
10
10
  * Wraps AliasResolver and DefinitionRegistry into a single domain-level interface
@@ -96,51 +96,38 @@ export class AnalysisRegistry {
96
96
  return def.capability ?? kind;
97
97
  }
98
98
  /** Resolves the JSON Schema for a kind's `invoke()` inputs, for editor hosts
99
- * that render a typed inputs form. Two-layer fallback mirroring the analyzer's
100
- * template inputs typing: the definition's own `inputType`, then the
101
- * `extends`-declared abstract's `inputType`. Resolves the inline
102
- * (`{ kind: Type.JsonSchema, schema }`) and raw-schema forms; a bare named
103
- * type reference is left unresolved (returns undefined) so the caller can fall
104
- * back to a freeform map. Undefined when the kind declares no input contract. */
99
+ * that render a typed inputs form. A thin wrapper over the shared contract
100
+ * resolver, so the form an editor renders is the contract `telo check` and the
101
+ * kernel enforce — nearest declaration along `extends`, replacing rather than
102
+ * merging. A bare named type reference is left unresolved (returns undefined,
103
+ * no manifests are in scope here) so the caller can fall back to a freeform
104
+ * map. Undefined when the kind declares no input contract. */
105
105
  inputTypeForKind(kind) {
106
- const def = this.resolveDefinition(kind);
107
- if (!def)
108
- return undefined;
109
- const own = resolveTypeFieldToSchema(def.inputType, []);
110
- if (own)
111
- return own;
112
- if (def.extends) {
113
- const abstractDef = this.resolveDefinition(def.extends);
114
- if (abstractDef) {
115
- const inherited = resolveTypeFieldToSchema(abstractDef.inputType, []);
116
- if (inherited)
117
- return inherited;
118
- }
119
- }
120
- return undefined;
106
+ return this.contractForKind(kind, "inputType");
121
107
  }
122
- /** Resolves the JSON Schema for a kind's `invoke()` / `run()` output, for
108
+ /** Resolves the JSON Schema for a kind's `invoke()` / `provide()` output, for
123
109
  * editor hosts that render a typed output signature. Mirrors
124
- * {@link inputTypeForKind}: the definition's own `outputType`, then the
125
- * `extends`-declared abstract's `outputType`. Resolves the inline and
126
- * raw-schema forms; a bare named type reference is left unresolved. Undefined
127
- * when the kind declares no output contract. */
110
+ * {@link inputTypeForKind}. Undefined when the kind declares no output
111
+ * contract. */
128
112
  outputTypeForKind(kind) {
113
+ return this.contractForKind(kind, "outputType");
114
+ }
115
+ contractForKind(kind, direction) {
129
116
  const def = this.resolveDefinition(kind);
130
117
  if (!def)
131
118
  return undefined;
132
- const own = resolveTypeFieldToSchema(def.outputType, []);
133
- if (own)
134
- return own;
135
- if (def.extends) {
136
- const abstractDef = this.resolveDefinition(def.extends);
137
- if (abstractDef) {
138
- const inherited = resolveTypeFieldToSchema(abstractDef.outputType, []);
139
- if (inherited)
140
- return inherited;
141
- }
142
- }
143
- return undefined;
119
+ return resolveContract(direction, undefined, def, {
120
+ resolveDefinition: this.scopedDefResolver(),
121
+ typeManifestsFor: () => [],
122
+ })?.schema;
123
+ }
124
+ /** A resolver that re-scopes at every hop of an `extends` chain: each kind is
125
+ * resolved in the module that DECLARED the definition it was read off, since
126
+ * `extends` aliases are lexical. `resolveParent` always passes that definition
127
+ * as `from`, so one resolver serves a chain of any depth crossing any number
128
+ * of modules. */
129
+ scopedDefResolver() {
130
+ return (kind, from) => this.resolveDefinitionIn(kind, from?.metadata?.module);
144
131
  }
145
132
  capabilitiesForRefs(refs) {
146
133
  const out = [];
@@ -178,6 +165,16 @@ export class AnalysisRegistry {
178
165
  const resolved = ctx.aliases?.resolveKind(kind);
179
166
  return ctx.definitions?.resolve(kind) ?? (resolved ? ctx.definitions?.resolve(resolved) : undefined);
180
167
  }
168
+ /** Resolve a kind in a named module's scope. The entry point for walking an
169
+ * `extends` chain from outside it (the kernel's contract binding): aliases are
170
+ * lexical, so each hop must resolve in the module that declared the definition
171
+ * the kind was read off. Falls back to the global table when the module is
172
+ * unknown or is a root. */
173
+ resolveDefinitionIn(kind, module) {
174
+ const scope = (module ? this.aliasesByModule.get(module) : undefined) ?? this.aliases;
175
+ const canonical = scope.resolveKind(kind);
176
+ return this.defs.resolve(kind) ?? (canonical ? this.defs.resolve(canonical) : undefined);
177
+ }
181
178
  /** A resolver scoped to `def`'s OWN module, for resolving that definition's
182
179
  * `extends` target.
183
180
  *
@@ -1,7 +1,44 @@
1
- import type { ResourceManifest } from "@telorun/sdk";
1
+ import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
2
+ import { AliasResolver, type ModuleScopes } from "./alias-resolver.js";
2
3
  import { AnalysisRegistry } from "./analysis-registry.js";
3
4
  import { type CelHandlers } from "./cel-environment.js";
5
+ import { DefinitionRegistry } from "./definition-registry.js";
6
+ import { type ContractScope } from "./invocation-contract.js";
4
7
  import { type AnalysisDiagnostic, type AnalysisOptions } from "./types.js";
8
+ /** The {@link ContractScope} the analyzer resolves invocation contracts in: kinds
9
+ * resolve in the module that declared the definition they were read off (so an
10
+ * `extends` chain crossing module boundaries re-scopes at every hop), and named
11
+ * `telo#Type` references resolve against the flattened manifest list. `resolveIn`
12
+ * is the top-level entry point, where the kind was written by the READING
13
+ * module and there is no declaring definition yet. */
14
+ export declare function analyzerContractScope(defs: DefinitionRegistry, aliases: AliasResolver, scopes: ModuleScopes, allManifests: Record<string, any>[]): ContractScope & {
15
+ resolveIn(kind: string, module?: string): ResourceDefinition | undefined;
16
+ };
17
+ /** True when an issue reports a property that is absent — its path points at a
18
+ * node the manifest does not contain. */
19
+ export declare const missingRequired: (issue: {
20
+ message: string;
21
+ }) => boolean;
22
+ /** The path minus its last segment: the node that should have contained the
23
+ * missing property. Empty for a top-level miss, which anchors on the map. */
24
+ export declare function containerOf(path: string): string;
25
+ /** Resolve a local `$ref` (only `#/$defs/<name>` form) against the root schema.
26
+ * Non-refs and unresolved refs pass through unchanged. */
27
+ export declare function resolveLocalRef(schema: Record<string, any> | undefined, root: Record<string, any>): Record<string, any> | undefined;
28
+ /** Gather property schemas from a (possibly variant-bearing) object schema:
29
+ * top-level `properties` plus every `oneOf` / `anyOf` / `allOf` branch. */
30
+ export declare function gatherPropertySchemas(schema: Record<string, any>): Array<[string, Record<string, any>]>;
31
+ /**
32
+ * Generic, role-driven walk over an `x-telo-step-context` step array. Calls
33
+ * `visit(step, stepPath)` for every step — top-level and nested through the
34
+ * `x-telo-topology-role` forms (`branch`, `branch-list`, `case-map`). This is
35
+ * the single definition of how steps nest, shared by `buildStepContextSchema`
36
+ * (which types `steps.<name>.result`) and `validateStepInvokeReferences` (which
37
+ * checks invoke refs), so the topology contract lives in one place — adding a
38
+ * role or nesting form updates both consumers at once. No resource kind is
39
+ * hardcoded; recursion is driven entirely by the schema annotations.
40
+ */
41
+ export declare function walkStepArray(steps: unknown[], stepItemSchema: Record<string, any> | undefined, rootSchema: Record<string, any>, basePath: string, visit: (step: Record<string, any>, stepPath: string) => void): void;
5
42
  export interface StaticAnalyzerOptions {
6
43
  celHandlers?: CelHandlers;
7
44
  }
@@ -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;AAKzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AA6B9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AA0yB/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;IAiiCvB,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;IAyBrB,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":"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"}
package/dist/analyzer.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { canonicalTypeSchemaId, OBSERVED_STATE_KEY } from "@telorun/sdk";
2
2
  import { defaultRegistry, isRefSentinel, isTaggedSentinel } from "@telorun/templating";
3
- import { AliasResolver, scopeResolverForModule } from "./alias-resolver.js";
3
+ import { AliasResolver, moduleScopedDefResolver, scopeResolverForModule, } from "./alias-resolver.js";
4
4
  import { buildCelEnvironment, buildImportInputCelEnvironment, buildTypedCelEnvironment, } from "./cel-environment.js";
5
5
  import { DefinitionRegistry } from "./definition-registry.js";
6
6
  import { effectiveAuthorSchema } from "./extends-resolution.js";
7
+ import { PERMISSIVE_CONTRACT, resolveContract, } from "./invocation-contract.js";
7
8
  import { buildDependencyGraph, formatCycle } from "./dependency-graph.js";
8
9
  import { buildKernelGlobalsSchema, mergeKernelGlobalsIntoContext } from "./kernel-globals.js";
9
10
  import { buildObservedStateIndex, buildObservedStateResourcesSchema, collectRunReachableNames, observedStateRead, validateObservedStateDeclarations, } from "./validate-observed-state.js";
@@ -20,12 +21,14 @@ import { rewriteSyntheticOrigins } from "./rewrite-synthetic-origins.js";
20
21
  import { celTypeSatisfiesJsonSchema, substituteCelFields, validateAgainstSchema, } from "./schema-compat.js";
21
22
  import { collectValueSchemaIssues } from "./validate-value-schema.js";
22
23
  import { DiagnosticSeverity } from "./types.js";
23
- import { extractAccessChains, extractCelRegionScopes, extractContextsFromSchema, getManifestItem, pathMatchesScope, resolveContextAnnotations, resolveTypeFieldToSchema, } from "./validate-cel-context.js";
24
+ import { extractAccessChains, extractCelRegionScopes, extractContextsFromSchema, getManifestItem, pathMatchesScope, resolveContextAnnotations, } from "./validate-cel-context.js";
24
25
  import { buildEvalPaths, evalPathsCover } from "./eval-paths.js";
25
26
  import { validateExtends } from "./validate-extends.js";
26
27
  import { validateLogging } from "./validate-logging.js";
27
28
  import { validateModuleArtifact } from "./validate-module-artifact.js";
28
29
  import { validateBaseMapping } from "./validate-base-mapping.js";
30
+ import { validateInvocationContract } from "./validate-invocation-contract.js";
31
+ import { collectStepInputIssues } from "./validate-step-inputs.js";
29
32
  import { validateNestedInlineResources } from "./validate-nested-inline.js";
30
33
  import { validateProviderCoherence } from "./validate-provider-coherence.js";
31
34
  import { validateReferences } from "./validate-references.js";
@@ -75,17 +78,19 @@ function resolveSelfOrAlias(value, ownModule, scopeResolver) {
75
78
  }
76
79
  return scopeResolver.resolveKind(value);
77
80
  }
78
- /** Look up a top-level field (`outputType`, `inputType`) on a kind's
79
- * `Telo.Definition`. Used as a fallback by `buildStepContextSchema` when the
80
- * invoked resource manifest doesn't carry the field inline most kinds
81
- * declare result shape on the definition, not the resource. */
82
- function lookupDefinitionTypeField(invokedKind, fieldName, defs, aliases, allManifests) {
83
- const canonical = aliases.resolveKind(invokedKind) ?? invokedKind;
84
- const def = defs.resolve(canonical);
85
- if (!def)
86
- return undefined;
87
- const value = def[fieldName];
88
- return resolveTypeFieldToSchema(value, allManifests);
81
+ /** The {@link ContractScope} the analyzer resolves invocation contracts in: kinds
82
+ * resolve in the module that declared the definition they were read off (so an
83
+ * `extends` chain crossing module boundaries re-scopes at every hop), and named
84
+ * `telo#Type` references resolve against the flattened manifest list. `resolveIn`
85
+ * is the top-level entry point, where the kind was written by the READING
86
+ * module and there is no declaring definition yet. */
87
+ export function analyzerContractScope(defs, aliases, scopes, allManifests) {
88
+ const resolve = moduleScopedDefResolver(defs, aliases, scopes);
89
+ return {
90
+ resolveDefinition: resolve,
91
+ resolveIn: resolve.in,
92
+ typeManifestsFor: () => allManifests,
93
+ };
89
94
  }
90
95
  const SOURCE = "telo-analyzer";
91
96
  /** Build a closed JSON Schema for the `self` CEL variable available inside a
@@ -119,50 +124,66 @@ function buildSelfSchema(definition, defs, aliases) {
119
124
  };
120
125
  }
121
126
  /** Build the JSON Schema for the `inputs` CEL variable available inside an
122
- * invocable template body. Three-layer fallback mirroring the runtime's
123
- * caller-supplied inputs:
124
- * 1. The definition's own `inputType:` field (preferred).
125
- * 2. The `extends:`-declared abstract's `inputType:` (so a concrete
126
- * definition inheriting a contract gets typed inputs without
127
- * redeclaring them).
128
- * 3. Undefined caller signals opaque `map<string, dyn>` upstream. */
129
- function lookupTemplateInputsSchema(definition, defs, aliases, allManifests) {
130
- const own = resolveTypeFieldToSchema(definition.inputType, allManifests);
131
- if (own)
132
- return own;
133
- const ext = definition.extends;
134
- if (typeof ext === "string" && ext.length > 0) {
135
- const canonical = aliases.resolveKind(ext) ?? ext;
136
- const abstractDef = defs.resolve(canonical);
137
- if (abstractDef) {
138
- const inherited = resolveTypeFieldToSchema(abstractDef.inputType, allManifests);
139
- if (inherited)
140
- return inherited;
141
- }
142
- }
143
- return undefined;
127
+ * invocable template body the shared contract resolver applied to the
128
+ * definition itself, so a body is typed against the exact signature callers are
129
+ * checked against and dispatch enforces. Walks the whole `extends` chain rather
130
+ * than one hop, so a definition two levels below the declaration still gets
131
+ * typed inputs. Undefined when nothing in the chain declares a contract —
132
+ * the caller signals opaque `map<string, dyn>` upstream. */
133
+ function lookupTemplateInputsSchema(definition, defs, aliases, allManifests, scopes) {
134
+ return resolveContract("inputType", undefined, definition, analyzerContractScope(defs, aliases, scopes, allManifests))?.schema;
144
135
  }
145
136
  /** Returns a "resolver-facing" view of the manifest where the fields used as
146
137
  * navigation roots by Telo.Definition's `x-telo-context-from-root` annotations
147
138
  * have been pre-augmented:
148
139
  * - `schema` → augmented `self` schema (synthetic `name`/`kind`/metadata).
149
- * - `inputType` → resolved with extends fallback when the field isn't
150
- * declared directly on the definition.
140
+ * - `inputType` → resolved through the shared contract resolver, so
141
+ * `x-telo-context-from-root: inputType` substitutes the
142
+ * real signature. Without it the annotation would replace
143
+ * the node verbatim with the inline `{kind, schema}` wrapper
144
+ * the standard library writes everywhere, typing `inputs` as
145
+ * `{kind, schema}` instead of the declared properties.
151
146
  *
152
147
  * For non-definition manifests the original object is returned. */
153
- function manifestRootForResolver(m, defs, aliases, allManifests) {
148
+ function manifestRootForResolver(m, defs, aliases, allManifests, scopes) {
154
149
  if (m.kind !== "Telo.Definition")
155
150
  return m;
156
- const inputs = lookupTemplateInputsSchema(m, defs, aliases, allManifests);
151
+ const inputs = lookupTemplateInputsSchema(m, defs, aliases, allManifests, scopes);
157
152
  return {
158
153
  ...m,
159
154
  schema: buildSelfSchema(m, defs, aliases),
160
155
  ...(inputs ? { inputType: inputs } : {}),
161
156
  };
162
157
  }
158
+ /** True when an issue reports a property that is absent — its path points at a
159
+ * node the manifest does not contain. */
160
+ export const missingRequired = (issue) => /is missing required property/.test(issue.message);
161
+ /** The path minus its last segment: the node that should have contained the
162
+ * missing property. Empty for a top-level miss, which anchors on the map. */
163
+ export function containerOf(path) {
164
+ const dot = path.lastIndexOf(".");
165
+ return dot === -1 ? "" : path.slice(0, dot);
166
+ }
167
+ /** How to name the owner of a resolved contract in a diagnostic. When the
168
+ * contract came from the definition's direct parent, echo the author's own
169
+ * spelling (`extends: Mcp.SessionProvider`) — that is the text they can find in
170
+ * their file. A contract inherited from further up the chain isn't written
171
+ * anywhere in this file, so the canonical `module.Kind` is what locates it. */
172
+ function contractOwnerLabel(definition, contract) {
173
+ const declaredBy = contract.declaredBy;
174
+ if (!declaredBy || declaredBy === definition) {
175
+ return String(definition.metadata?.name ?? definition.kind);
176
+ }
177
+ const parentSpelling = definition.extends;
178
+ const canonical = `${declaredBy.metadata.module}.${declaredBy.metadata.name}`;
179
+ if (typeof parentSpelling === "string" && parentSpelling.endsWith(`.${declaredBy.metadata.name}`)) {
180
+ return parentSpelling;
181
+ }
182
+ return canonical;
183
+ }
163
184
  /** Resolve a local `$ref` (only `#/$defs/<name>` form) against the root schema.
164
185
  * Non-refs and unresolved refs pass through unchanged. */
165
- function resolveLocalRef(schema, root) {
186
+ export function resolveLocalRef(schema, root) {
166
187
  if (!schema)
167
188
  return undefined;
168
189
  const ref = schema.$ref;
@@ -176,7 +197,7 @@ function resolveLocalRef(schema, root) {
176
197
  }
177
198
  /** Gather property schemas from a (possibly variant-bearing) object schema:
178
199
  * top-level `properties` plus every `oneOf` / `anyOf` / `allOf` branch. */
179
- function gatherPropertySchemas(schema) {
200
+ export function gatherPropertySchemas(schema) {
180
201
  const out = [];
181
202
  if (schema.properties && typeof schema.properties === "object") {
182
203
  for (const [k, v] of Object.entries(schema.properties)) {
@@ -207,7 +228,7 @@ function gatherPropertySchemas(schema) {
207
228
  * role or nesting form updates both consumers at once. No resource kind is
208
229
  * hardcoded; recursion is driven entirely by the schema annotations.
209
230
  */
210
- function walkStepArray(steps, stepItemSchema, rootSchema, basePath, visit) {
231
+ export function walkStepArray(steps, stepItemSchema, rootSchema, basePath, visit) {
211
232
  const dispatchRole = (data, role, itemsSchema, path) => {
212
233
  if (role === "branch" && Array.isArray(data)) {
213
234
  walkStepArray(data, stepItemSchema, rootSchema, path, visit);
@@ -252,20 +273,18 @@ function walkStepArray(steps, stepItemSchema, rootSchema, basePath, visit) {
252
273
  }
253
274
  /**
254
275
  * Build a `steps` context schema from `x-telo-step-context` annotation.
255
- * Walks each step in the manifest array, resolves the invoked resource's outputType,
256
- * and builds `steps.<name>.result` context entries.
276
+ * Walks each step in the manifest array, resolves the invoked resource's output
277
+ * contract, and builds `steps.<name>.result` context entries.
257
278
  *
258
- * outputType resolution falls through three layers:
259
- * 1. The invoked resource manifest's own `outputType` field (rare most
260
- * resources don't declare outputType inline).
261
- * 2. The kind's `Telo.Definition` outputType (the common case for kinds that
262
- * declare a stable result shape, e.g. `Ai.TextStream` ↦ `{output: stream}`).
263
- * 3. Permissive `{type: object, additionalProperties: true}` if neither
264
- * yields a schema.
279
+ * Resolution is the shared {@link resolveContract} — the invoked resource
280
+ * manifest's own declaration, then the kind's, resolved to the nearest
281
+ * declaration along `extends`, then permissive. Sharing it with the kernel is
282
+ * what stops `telo check` from typing `steps.X.result` against one contract
283
+ * while dispatch validates against another.
265
284
  *
266
- * Layer 2 is what makes `x-telo-stream` properties on definitions actually
267
- * govern step-result chain validation — without it, the validator falls back
268
- * to permissive and the stream-opacity rule never fires.
285
+ * The kind layer is what makes `x-telo-stream` properties on definitions
286
+ * actually govern step-result chain validation — without it, the validator falls
287
+ * back to permissive and the stream-opacity rule never fires.
269
288
  *
270
289
  * Recursion into nested step arrays is annotation-driven via
271
290
  * `x-telo-topology-role`. The analyzer recognises three role values:
@@ -276,10 +295,12 @@ function walkStepArray(steps, stepItemSchema, rootSchema, basePath, visit) {
276
295
  * No specific Run.Sequence field name is hardcoded; any kind that uses
277
296
  * `x-telo-step-context` and tags its branch fields with these roles works.
278
297
  */
279
- function buildStepContextSchema(manifest, defSchema, allManifests, defs, aliases) {
298
+ function buildStepContextSchema(manifest, defSchema, allManifests, defs, aliases, scopes) {
280
299
  const props = defSchema.properties;
281
300
  if (!props)
282
301
  return undefined;
302
+ const contractScope = analyzerContractScope(defs, aliases, scopes, allManifests);
303
+ const readingModule = manifest.metadata?.module;
283
304
  for (const [fieldName, fieldSchema] of Object.entries(props)) {
284
305
  const stepCtx = fieldSchema["x-telo-step-context"];
285
306
  if (!stepCtx)
@@ -302,28 +323,22 @@ function buildStepContextSchema(manifest, defSchema, allManifests, defs, aliases
302
323
  // or unknown step references slip through chain validation.
303
324
  if (typeof name !== "string" || !invoke || typeof invoke !== "object")
304
325
  return;
305
- let outputSchema;
306
326
  const invokedKind = invoke.kind;
307
327
  const invokedName = invoke.name;
308
- if (invokedName) {
309
- const invokedManifest = allManifests.find((m) => m.metadata?.name === invokedName &&
310
- (!invokedKind || m.kind === invokedKind));
311
- if (invokedManifest) {
312
- outputSchema = resolveTypeFieldToSchema(invokedManifest[outputTypeField], allManifests);
313
- }
314
- }
315
- else {
316
- outputSchema = resolveTypeFieldToSchema(invoke[outputTypeField], allManifests);
317
- }
318
- // Fallback: pull outputType from the kind's Telo.Definition. The
319
- // resource manifest typically doesn't carry outputType; the def does.
320
- if (!outputSchema && invokedKind) {
321
- outputSchema = lookupDefinitionTypeField(invokedKind, outputTypeField, defs, aliases, allManifests);
322
- }
328
+ // A named `!ref` carries the target's own manifest (which may narrow the
329
+ // contract for this one instance); an inline `{ kind, ... }` step IS the
330
+ // manifest. Either way the kind layer resolves through `extends`.
331
+ const invokedManifest = invokedName
332
+ ? allManifests.find((m) => m.metadata?.name === invokedName && (!invokedKind || m.kind === invokedKind))
333
+ : invoke;
334
+ const invokedDef = invokedKind
335
+ ? contractScope.resolveIn(invokedKind, readingModule)
336
+ : undefined;
337
+ const outputSchema = resolveContract(outputTypeField, invokedManifest, invokedDef, contractScope)?.schema;
323
338
  stepProperties[name] = {
324
339
  type: "object",
325
340
  properties: {
326
- result: outputSchema ?? { type: "object", additionalProperties: true },
341
+ result: outputSchema ?? PERMISSIVE_CONTRACT,
327
342
  },
328
343
  };
329
344
  });
@@ -1318,6 +1333,7 @@ export class StaticAnalyzer {
1318
1333
  // the abstract this definition `extends`. CEL fields inside the templated
1319
1334
  // values are replaced with type-appropriate placeholders before AJV runs —
1320
1335
  // same pattern as the per-resource schema validation above.
1336
+ const contractScope = analyzerContractScope(defs, aliases, { aliasesByModule, rootModules }, allManifests);
1321
1337
  for (const m of allManifests) {
1322
1338
  if (m.kind !== "Telo.Definition")
1323
1339
  continue;
@@ -1360,25 +1376,23 @@ export class StaticAnalyzer {
1360
1376
  // values passed to the dispatch target's invoke(). Validate against the
1361
1377
  // target's declared `inputType` when both sides have one.
1362
1378
  if (dispatchKind && md.inputs && typeof md.inputs === "object") {
1363
- const targetSchema = lookupDefinitionTypeField(dispatchKind, "inputType", defs, aliases, allManifests);
1379
+ const targetSchema = resolveContract("inputType", undefined, contractScope.resolveIn(dispatchKind, md.metadata?.module), contractScope)?.schema;
1364
1380
  if (targetSchema) {
1365
1381
  emitTargetMismatch(dispatchKind, targetSchema, md.inputs, "inputs");
1366
1382
  }
1367
1383
  }
1368
- // Top-level `result:` is a post-call mapping that must satisfy the abstract
1369
- // this definition `extends` (`outputType`). It's a sibling of whichever
1384
+ // Top-level `result:` is a post-call mapping that must satisfy THIS
1385
+ // definition's output contract — its own `outputType` when it declares
1386
+ // one, otherwise the nearest ancestor's. It's a sibling of whichever
1370
1387
  // dispatch entry-point declared a kind-typed target (`provide:` or
1371
1388
  // `invoke:`). The target's outputType lives on the dispatcher's `kind`
1372
1389
  // and is what `result` is typed against *inside* CEL — separate role.
1373
1390
  const hasDispatchObject = (provide && typeof provide === "object" && !Array.isArray(provide)) ||
1374
1391
  (invoke && typeof invoke === "object" && !Array.isArray(invoke));
1375
1392
  if (hasDispatchObject && md.result && typeof md.result === "object") {
1376
- const extendsValue = md.extends;
1377
- if (typeof extendsValue === "string" && extendsValue.length > 0) {
1378
- const abstractSchema = lookupDefinitionTypeField(extendsValue, "outputType", defs, aliases, allManifests);
1379
- if (abstractSchema) {
1380
- emitTargetMismatch(extendsValue, abstractSchema, md.result, "result");
1381
- }
1393
+ const contract = resolveContract("outputType", undefined, md, contractScope);
1394
+ if (contract) {
1395
+ emitTargetMismatch(contractOwnerLabel(md, contract), contract.schema, md.result, "result");
1382
1396
  }
1383
1397
  }
1384
1398
  }
@@ -1410,8 +1424,25 @@ export class StaticAnalyzer {
1410
1424
  const m = e.source;
1411
1425
  celInvocationContext = m.metadata?.xTeloInvocationContext;
1412
1426
  celStepContextSchema = e.definition?.schema
1413
- ? buildStepContextSchema(m, e.definition.schema, allManifests, defs, aliases)
1427
+ ? buildStepContextSchema(m, e.definition.schema, allManifests, defs, aliases, { aliasesByModule, rootModules })
1414
1428
  : undefined;
1429
+ if (e.definition?.schema) {
1430
+ const stepName = m.metadata?.name;
1431
+ const stepFile = m.metadata?.source;
1432
+ for (const issue of collectStepInputIssues(m, e.definition.schema, allManifests, defs, aliases, { aliasesByModule, rootModules })) {
1433
+ diagnostics.push({
1434
+ severity: DiagnosticSeverity.Error,
1435
+ code: "CONTRACT_INPUTS_MISMATCH",
1436
+ source: SOURCE,
1437
+ message: `${m.kind}/${stepName}: inputs at '${issue.path}' do not satisfy ${issue.targetLabel}'s declared inputType: ${issue.message}`,
1438
+ data: {
1439
+ resource: { kind: m.kind, name: stepName ?? "" },
1440
+ filePath: stepFile,
1441
+ path: issue.path,
1442
+ },
1443
+ });
1444
+ }
1445
+ }
1415
1446
  celErrorScopes = collectErrorContextScopes(e.definition?.schema);
1416
1447
  // The non-eval-field check only applies to runtime resource instances:
1417
1448
  // structural / templating kinds (capability `Telo.Template`, or no
@@ -1527,7 +1558,7 @@ export class StaticAnalyzer {
1527
1558
  const manifestItem = matchedScope
1528
1559
  ? getManifestItem(path, matchedScope, m)
1529
1560
  : m;
1530
- const rootForResolver = manifestRootForResolver(m, defs, aliases, allManifests);
1561
+ const rootForResolver = manifestRootForResolver(m, defs, aliases, allManifests, { aliasesByModule, rootModules });
1531
1562
  const resolvedContext = resolveContextAnnotations(matchedContext, manifestItem, {
1532
1563
  manifestRoot: rootForResolver,
1533
1564
  defs,
@@ -1625,6 +1656,7 @@ export class StaticAnalyzer {
1625
1656
  // Validate `extends` fields and flag legacy `capability: <UserAbstract>` overload.
1626
1657
  diagnostics.push(...validateExtends(allManifests, defs, aliases));
1627
1658
  diagnostics.push(...validateBaseMapping(allManifests, defs, aliases));
1659
+ diagnostics.push(...validateInvocationContract(allManifests, defs, aliases, aliasesByModule));
1628
1660
  // Validate provider coherence rules for `provide:` template-target definitions.
1629
1661
  diagnostics.push(...validateProviderCoherence(allManifests, defs, aliases));
1630
1662
  // Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7)
@@ -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;AA2JvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EA0nB/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;AA2JvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EAmsB/C,CAAC"}