@telorun/analyzer 0.47.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 (58) 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 +121 -83
  7. package/dist/artifact-layer-index.d.ts +55 -0
  8. package/dist/artifact-layer-index.d.ts.map +1 -0
  9. package/dist/artifact-layer-index.js +116 -0
  10. package/dist/artifact-selector.d.ts +81 -0
  11. package/dist/artifact-selector.d.ts.map +1 -0
  12. package/dist/artifact-selector.js +122 -0
  13. package/dist/builtins.d.ts.map +1 -1
  14. package/dist/builtins.js +130 -20
  15. package/dist/extends-resolution.d.ts +41 -0
  16. package/dist/extends-resolution.d.ts.map +1 -1
  17. package/dist/extends-resolution.js +68 -0
  18. package/dist/index.d.ts +9 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +5 -1
  21. package/dist/invocation-contract.d.ts +100 -0
  22. package/dist/invocation-contract.d.ts.map +1 -0
  23. package/dist/invocation-contract.js +208 -0
  24. package/dist/schema-compat.d.ts +12 -4
  25. package/dist/schema-compat.d.ts.map +1 -1
  26. package/dist/schema-compat.js +185 -9
  27. package/dist/validate-base-mapping.js +11 -1
  28. package/dist/validate-cel-context.d.ts +0 -6
  29. package/dist/validate-cel-context.d.ts.map +1 -1
  30. package/dist/validate-cel-context.js +51 -4
  31. package/dist/validate-invocation-contract.d.ts +30 -0
  32. package/dist/validate-invocation-contract.d.ts.map +1 -0
  33. package/dist/validate-invocation-contract.js +394 -0
  34. package/dist/validate-module-artifact.d.ts +27 -0
  35. package/dist/validate-module-artifact.d.ts.map +1 -0
  36. package/dist/validate-module-artifact.js +131 -0
  37. package/dist/validate-step-inputs.d.ts +24 -0
  38. package/dist/validate-step-inputs.d.ts.map +1 -0
  39. package/dist/validate-step-inputs.js +87 -0
  40. package/dist/validate-throws-coverage.d.ts +1 -1
  41. package/dist/validate-throws-coverage.d.ts.map +1 -1
  42. package/dist/validate-throws-coverage.js +9 -1
  43. package/package.json +2 -2
  44. package/src/analysis-registry.ts +44 -34
  45. package/src/analyzer.ts +177 -100
  46. package/src/artifact-layer-index.ts +162 -0
  47. package/src/artifact-selector.ts +171 -0
  48. package/src/builtins.ts +135 -20
  49. package/src/extends-resolution.ts +86 -0
  50. package/src/index.ts +38 -1
  51. package/src/invocation-contract.ts +275 -0
  52. package/src/schema-compat.ts +191 -8
  53. package/src/validate-base-mapping.ts +14 -1
  54. package/src/validate-cel-context.ts +49 -4
  55. package/src/validate-invocation-contract.ts +450 -0
  56. package/src/validate-module-artifact.ts +141 -0
  57. package/src/validate-step-inputs.ts +117 -0
  58. 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":"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"}
@@ -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;AAyyB/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;IA4hCvB,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,11 +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";
28
+ import { validateModuleArtifact } from "./validate-module-artifact.js";
27
29
  import { validateBaseMapping } from "./validate-base-mapping.js";
30
+ import { validateInvocationContract } from "./validate-invocation-contract.js";
31
+ import { collectStepInputIssues } from "./validate-step-inputs.js";
28
32
  import { validateNestedInlineResources } from "./validate-nested-inline.js";
29
33
  import { validateProviderCoherence } from "./validate-provider-coherence.js";
30
34
  import { validateReferences } from "./validate-references.js";
@@ -74,17 +78,19 @@ function resolveSelfOrAlias(value, ownModule, scopeResolver) {
74
78
  }
75
79
  return scopeResolver.resolveKind(value);
76
80
  }
77
- /** Look up a top-level field (`outputType`, `inputType`) on a kind's
78
- * `Telo.Definition`. Used as a fallback by `buildStepContextSchema` when the
79
- * invoked resource manifest doesn't carry the field inline most kinds
80
- * declare result shape on the definition, not the resource. */
81
- function lookupDefinitionTypeField(invokedKind, fieldName, defs, aliases, allManifests) {
82
- const canonical = aliases.resolveKind(invokedKind) ?? invokedKind;
83
- const def = defs.resolve(canonical);
84
- if (!def)
85
- return undefined;
86
- const value = def[fieldName];
87
- 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
+ };
88
94
  }
89
95
  const SOURCE = "telo-analyzer";
90
96
  /** Build a closed JSON Schema for the `self` CEL variable available inside a
@@ -118,50 +124,66 @@ function buildSelfSchema(definition, defs, aliases) {
118
124
  };
119
125
  }
120
126
  /** Build the JSON Schema for the `inputs` CEL variable available inside an
121
- * invocable template body. Three-layer fallback mirroring the runtime's
122
- * caller-supplied inputs:
123
- * 1. The definition's own `inputType:` field (preferred).
124
- * 2. The `extends:`-declared abstract's `inputType:` (so a concrete
125
- * definition inheriting a contract gets typed inputs without
126
- * redeclaring them).
127
- * 3. Undefined caller signals opaque `map<string, dyn>` upstream. */
128
- function lookupTemplateInputsSchema(definition, defs, aliases, allManifests) {
129
- const own = resolveTypeFieldToSchema(definition.inputType, allManifests);
130
- if (own)
131
- return own;
132
- const ext = definition.extends;
133
- if (typeof ext === "string" && ext.length > 0) {
134
- const canonical = aliases.resolveKind(ext) ?? ext;
135
- const abstractDef = defs.resolve(canonical);
136
- if (abstractDef) {
137
- const inherited = resolveTypeFieldToSchema(abstractDef.inputType, allManifests);
138
- if (inherited)
139
- return inherited;
140
- }
141
- }
142
- 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;
143
135
  }
144
136
  /** Returns a "resolver-facing" view of the manifest where the fields used as
145
137
  * navigation roots by Telo.Definition's `x-telo-context-from-root` annotations
146
138
  * have been pre-augmented:
147
139
  * - `schema` → augmented `self` schema (synthetic `name`/`kind`/metadata).
148
- * - `inputType` → resolved with extends fallback when the field isn't
149
- * 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.
150
146
  *
151
147
  * For non-definition manifests the original object is returned. */
152
- function manifestRootForResolver(m, defs, aliases, allManifests) {
148
+ function manifestRootForResolver(m, defs, aliases, allManifests, scopes) {
153
149
  if (m.kind !== "Telo.Definition")
154
150
  return m;
155
- const inputs = lookupTemplateInputsSchema(m, defs, aliases, allManifests);
151
+ const inputs = lookupTemplateInputsSchema(m, defs, aliases, allManifests, scopes);
156
152
  return {
157
153
  ...m,
158
154
  schema: buildSelfSchema(m, defs, aliases),
159
155
  ...(inputs ? { inputType: inputs } : {}),
160
156
  };
161
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
+ }
162
184
  /** Resolve a local `$ref` (only `#/$defs/<name>` form) against the root schema.
163
185
  * Non-refs and unresolved refs pass through unchanged. */
164
- function resolveLocalRef(schema, root) {
186
+ export function resolveLocalRef(schema, root) {
165
187
  if (!schema)
166
188
  return undefined;
167
189
  const ref = schema.$ref;
@@ -175,7 +197,7 @@ function resolveLocalRef(schema, root) {
175
197
  }
176
198
  /** Gather property schemas from a (possibly variant-bearing) object schema:
177
199
  * top-level `properties` plus every `oneOf` / `anyOf` / `allOf` branch. */
178
- function gatherPropertySchemas(schema) {
200
+ export function gatherPropertySchemas(schema) {
179
201
  const out = [];
180
202
  if (schema.properties && typeof schema.properties === "object") {
181
203
  for (const [k, v] of Object.entries(schema.properties)) {
@@ -206,7 +228,7 @@ function gatherPropertySchemas(schema) {
206
228
  * role or nesting form updates both consumers at once. No resource kind is
207
229
  * hardcoded; recursion is driven entirely by the schema annotations.
208
230
  */
209
- function walkStepArray(steps, stepItemSchema, rootSchema, basePath, visit) {
231
+ export function walkStepArray(steps, stepItemSchema, rootSchema, basePath, visit) {
210
232
  const dispatchRole = (data, role, itemsSchema, path) => {
211
233
  if (role === "branch" && Array.isArray(data)) {
212
234
  walkStepArray(data, stepItemSchema, rootSchema, path, visit);
@@ -251,20 +273,18 @@ function walkStepArray(steps, stepItemSchema, rootSchema, basePath, visit) {
251
273
  }
252
274
  /**
253
275
  * Build a `steps` context schema from `x-telo-step-context` annotation.
254
- * Walks each step in the manifest array, resolves the invoked resource's outputType,
255
- * 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.
256
278
  *
257
- * outputType resolution falls through three layers:
258
- * 1. The invoked resource manifest's own `outputType` field (rare most
259
- * resources don't declare outputType inline).
260
- * 2. The kind's `Telo.Definition` outputType (the common case for kinds that
261
- * declare a stable result shape, e.g. `Ai.TextStream` ↦ `{output: stream}`).
262
- * 3. Permissive `{type: object, additionalProperties: true}` if neither
263
- * 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.
264
284
  *
265
- * Layer 2 is what makes `x-telo-stream` properties on definitions actually
266
- * govern step-result chain validation — without it, the validator falls back
267
- * 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.
268
288
  *
269
289
  * Recursion into nested step arrays is annotation-driven via
270
290
  * `x-telo-topology-role`. The analyzer recognises three role values:
@@ -275,10 +295,12 @@ function walkStepArray(steps, stepItemSchema, rootSchema, basePath, visit) {
275
295
  * No specific Run.Sequence field name is hardcoded; any kind that uses
276
296
  * `x-telo-step-context` and tags its branch fields with these roles works.
277
297
  */
278
- function buildStepContextSchema(manifest, defSchema, allManifests, defs, aliases) {
298
+ function buildStepContextSchema(manifest, defSchema, allManifests, defs, aliases, scopes) {
279
299
  const props = defSchema.properties;
280
300
  if (!props)
281
301
  return undefined;
302
+ const contractScope = analyzerContractScope(defs, aliases, scopes, allManifests);
303
+ const readingModule = manifest.metadata?.module;
282
304
  for (const [fieldName, fieldSchema] of Object.entries(props)) {
283
305
  const stepCtx = fieldSchema["x-telo-step-context"];
284
306
  if (!stepCtx)
@@ -301,28 +323,22 @@ function buildStepContextSchema(manifest, defSchema, allManifests, defs, aliases
301
323
  // or unknown step references slip through chain validation.
302
324
  if (typeof name !== "string" || !invoke || typeof invoke !== "object")
303
325
  return;
304
- let outputSchema;
305
326
  const invokedKind = invoke.kind;
306
327
  const invokedName = invoke.name;
307
- if (invokedName) {
308
- const invokedManifest = allManifests.find((m) => m.metadata?.name === invokedName &&
309
- (!invokedKind || m.kind === invokedKind));
310
- if (invokedManifest) {
311
- outputSchema = resolveTypeFieldToSchema(invokedManifest[outputTypeField], allManifests);
312
- }
313
- }
314
- else {
315
- outputSchema = resolveTypeFieldToSchema(invoke[outputTypeField], allManifests);
316
- }
317
- // Fallback: pull outputType from the kind's Telo.Definition. The
318
- // resource manifest typically doesn't carry outputType; the def does.
319
- if (!outputSchema && invokedKind) {
320
- outputSchema = lookupDefinitionTypeField(invokedKind, outputTypeField, defs, aliases, allManifests);
321
- }
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;
322
338
  stepProperties[name] = {
323
339
  type: "object",
324
340
  properties: {
325
- result: outputSchema ?? { type: "object", additionalProperties: true },
341
+ result: outputSchema ?? PERMISSIVE_CONTRACT,
326
342
  },
327
343
  };
328
344
  });
@@ -1015,6 +1031,11 @@ export class StaticAnalyzer {
1015
1031
  // §14.1 / §10.3: redaction paths and `on_full: block` are statically
1016
1032
  // detectable, so they fail `telo check` rather than only at boot.
1017
1033
  diagnostics.push(...validateLogging(allManifests, defs, aliases, aliasesByModule));
1034
+ // Module-artifact surface: bundled-controller selector qualifiers and the
1035
+ // published `layers:` index. Every case is decidable from the manifest and
1036
+ // would otherwise fail on a consumer's machine — or, for a mistyped platform
1037
+ // axis, silently offer one platform's binary to every host.
1038
+ diagnostics.push(...validateModuleArtifact(allManifests));
1018
1039
  }
1019
1040
  resolveSchemaTypeRefs(allManifests, aliases, aliasesByModule);
1020
1041
  // Trusted-input fast path: when the caller has already attested that
@@ -1312,6 +1333,7 @@ export class StaticAnalyzer {
1312
1333
  // the abstract this definition `extends`. CEL fields inside the templated
1313
1334
  // values are replaced with type-appropriate placeholders before AJV runs —
1314
1335
  // same pattern as the per-resource schema validation above.
1336
+ const contractScope = analyzerContractScope(defs, aliases, { aliasesByModule, rootModules }, allManifests);
1315
1337
  for (const m of allManifests) {
1316
1338
  if (m.kind !== "Telo.Definition")
1317
1339
  continue;
@@ -1354,25 +1376,23 @@ export class StaticAnalyzer {
1354
1376
  // values passed to the dispatch target's invoke(). Validate against the
1355
1377
  // target's declared `inputType` when both sides have one.
1356
1378
  if (dispatchKind && md.inputs && typeof md.inputs === "object") {
1357
- const targetSchema = lookupDefinitionTypeField(dispatchKind, "inputType", defs, aliases, allManifests);
1379
+ const targetSchema = resolveContract("inputType", undefined, contractScope.resolveIn(dispatchKind, md.metadata?.module), contractScope)?.schema;
1358
1380
  if (targetSchema) {
1359
1381
  emitTargetMismatch(dispatchKind, targetSchema, md.inputs, "inputs");
1360
1382
  }
1361
1383
  }
1362
- // Top-level `result:` is a post-call mapping that must satisfy the abstract
1363
- // 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
1364
1387
  // dispatch entry-point declared a kind-typed target (`provide:` or
1365
1388
  // `invoke:`). The target's outputType lives on the dispatcher's `kind`
1366
1389
  // and is what `result` is typed against *inside* CEL — separate role.
1367
1390
  const hasDispatchObject = (provide && typeof provide === "object" && !Array.isArray(provide)) ||
1368
1391
  (invoke && typeof invoke === "object" && !Array.isArray(invoke));
1369
1392
  if (hasDispatchObject && md.result && typeof md.result === "object") {
1370
- const extendsValue = md.extends;
1371
- if (typeof extendsValue === "string" && extendsValue.length > 0) {
1372
- const abstractSchema = lookupDefinitionTypeField(extendsValue, "outputType", defs, aliases, allManifests);
1373
- if (abstractSchema) {
1374
- emitTargetMismatch(extendsValue, abstractSchema, md.result, "result");
1375
- }
1393
+ const contract = resolveContract("outputType", undefined, md, contractScope);
1394
+ if (contract) {
1395
+ emitTargetMismatch(contractOwnerLabel(md, contract), contract.schema, md.result, "result");
1376
1396
  }
1377
1397
  }
1378
1398
  }
@@ -1404,8 +1424,25 @@ export class StaticAnalyzer {
1404
1424
  const m = e.source;
1405
1425
  celInvocationContext = m.metadata?.xTeloInvocationContext;
1406
1426
  celStepContextSchema = e.definition?.schema
1407
- ? buildStepContextSchema(m, e.definition.schema, allManifests, defs, aliases)
1427
+ ? buildStepContextSchema(m, e.definition.schema, allManifests, defs, aliases, { aliasesByModule, rootModules })
1408
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
+ }
1409
1446
  celErrorScopes = collectErrorContextScopes(e.definition?.schema);
1410
1447
  // The non-eval-field check only applies to runtime resource instances:
1411
1448
  // structural / templating kinds (capability `Telo.Template`, or no
@@ -1521,7 +1558,7 @@ export class StaticAnalyzer {
1521
1558
  const manifestItem = matchedScope
1522
1559
  ? getManifestItem(path, matchedScope, m)
1523
1560
  : m;
1524
- const rootForResolver = manifestRootForResolver(m, defs, aliases, allManifests);
1561
+ const rootForResolver = manifestRootForResolver(m, defs, aliases, allManifests, { aliasesByModule, rootModules });
1525
1562
  const resolvedContext = resolveContextAnnotations(matchedContext, manifestItem, {
1526
1563
  manifestRoot: rootForResolver,
1527
1564
  defs,
@@ -1619,6 +1656,7 @@ export class StaticAnalyzer {
1619
1656
  // Validate `extends` fields and flag legacy `capability: <UserAbstract>` overload.
1620
1657
  diagnostics.push(...validateExtends(allManifests, defs, aliases));
1621
1658
  diagnostics.push(...validateBaseMapping(allManifests, defs, aliases));
1659
+ diagnostics.push(...validateInvocationContract(allManifests, defs, aliases, aliasesByModule));
1622
1660
  // Validate provider coherence rules for `provide:` template-target definitions.
1623
1661
  diagnostics.push(...validateProviderCoherence(allManifests, defs, aliases));
1624
1662
  // Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7)