@telorun/analyzer 0.57.0 → 0.58.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 (116) hide show
  1. package/dist/analyzer.d.ts.map +1 -1
  2. package/dist/analyzer.js +91 -9
  3. package/dist/cel-environment.d.ts.map +1 -1
  4. package/dist/cel-environment.js +6 -3
  5. package/dist/definition-registry.d.ts +5 -0
  6. package/dist/definition-registry.d.ts.map +1 -1
  7. package/dist/definition-registry.js +9 -0
  8. package/dist/index.d.ts +10 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +6 -2
  11. package/dist/invocation-contract.d.ts +21 -13
  12. package/dist/invocation-contract.d.ts.map +1 -1
  13. package/dist/invocation-contract.js +43 -46
  14. package/dist/loaded-types.d.ts +13 -0
  15. package/dist/loaded-types.d.ts.map +1 -1
  16. package/dist/manifest-loader.d.ts +8 -1
  17. package/dist/manifest-loader.d.ts.map +1 -1
  18. package/dist/manifest-loader.js +33 -6
  19. package/dist/migrations/driver.d.ts +69 -0
  20. package/dist/migrations/driver.d.ts.map +1 -0
  21. package/dist/migrations/driver.js +200 -0
  22. package/dist/migrations/entries/index.d.ts +3 -0
  23. package/dist/migrations/entries/index.d.ts.map +1 -0
  24. package/dist/migrations/entries/index.js +9 -0
  25. package/dist/migrations/entries/normalize-value-types.json +48 -0
  26. package/dist/migrations/entries/ref-slot-scalar-type.json +20 -0
  27. package/dist/migrations/entry-data.d.ts +25 -0
  28. package/dist/migrations/entry-data.d.ts.map +1 -0
  29. package/dist/migrations/entry-data.js +171 -0
  30. package/dist/migrations/index.d.ts +22 -0
  31. package/dist/migrations/index.d.ts.map +1 -0
  32. package/dist/migrations/index.js +19 -0
  33. package/dist/migrations/match.d.ts +162 -0
  34. package/dist/migrations/match.d.ts.map +1 -0
  35. package/dist/migrations/match.js +295 -0
  36. package/dist/migrations/patch.d.ts +68 -0
  37. package/dist/migrations/patch.d.ts.map +1 -0
  38. package/dist/migrations/patch.js +236 -0
  39. package/dist/migrations/provenance.d.ts +25 -0
  40. package/dist/migrations/provenance.d.ts.map +1 -0
  41. package/dist/migrations/provenance.js +118 -0
  42. package/dist/migrations/registry.d.ts +35 -0
  43. package/dist/migrations/registry.d.ts.map +1 -0
  44. package/dist/migrations/registry.js +35 -0
  45. package/dist/migrations/report.d.ts +28 -0
  46. package/dist/migrations/report.d.ts.map +1 -0
  47. package/dist/migrations/report.js +144 -0
  48. package/dist/migrations/types.d.ts +115 -0
  49. package/dist/migrations/types.d.ts.map +1 -0
  50. package/dist/migrations/types.js +24 -0
  51. package/dist/migrations/yaml-edit.d.ts +21 -0
  52. package/dist/migrations/yaml-edit.d.ts.map +1 -0
  53. package/dist/migrations/yaml-edit.js +245 -0
  54. package/dist/parse-loaded-file.d.ts +8 -0
  55. package/dist/parse-loaded-file.d.ts.map +1 -1
  56. package/dist/parse-loaded-file.js +22 -13
  57. package/dist/resolve-schema-type-refs.d.ts +29 -12
  58. package/dist/resolve-schema-type-refs.d.ts.map +1 -1
  59. package/dist/resolve-schema-type-refs.js +89 -16
  60. package/dist/schema-compat.d.ts +35 -9
  61. package/dist/schema-compat.d.ts.map +1 -1
  62. package/dist/schema-compat.js +180 -64
  63. package/dist/schema-region.d.ts +33 -0
  64. package/dist/schema-region.d.ts.map +1 -0
  65. package/dist/schema-region.js +45 -0
  66. package/dist/types.d.ts +17 -0
  67. package/dist/types.d.ts.map +1 -1
  68. package/dist/validate-cel-context.d.ts.map +1 -1
  69. package/dist/validate-cel-context.js +29 -3
  70. package/dist/validate-step-inputs.d.ts +8 -1
  71. package/dist/validate-step-inputs.d.ts.map +1 -1
  72. package/dist/validate-step-inputs.js +47 -2
  73. package/dist/validate-value-type-slots.d.ts +43 -0
  74. package/dist/validate-value-type-slots.d.ts.map +1 -0
  75. package/dist/validate-value-type-slots.js +142 -0
  76. package/dist/value-type-keyword.d.ts +60 -0
  77. package/dist/value-type-keyword.d.ts.map +1 -0
  78. package/dist/value-type-keyword.js +119 -0
  79. package/dist/yaml-source-edit.d.ts +56 -0
  80. package/dist/yaml-source-edit.d.ts.map +1 -0
  81. package/dist/yaml-source-edit.js +93 -0
  82. package/package.json +3 -3
  83. package/src/analyzer.ts +99 -6
  84. package/src/cel-environment.ts +6 -3
  85. package/src/definition-registry.ts +10 -0
  86. package/src/index.ts +26 -2
  87. package/src/invocation-contract.ts +43 -48
  88. package/src/loaded-types.ts +13 -0
  89. package/src/manifest-loader.ts +37 -6
  90. package/src/migrations/driver.ts +262 -0
  91. package/src/migrations/entries/index.ts +10 -0
  92. package/src/migrations/entries/normalize-value-types.json +48 -0
  93. package/src/migrations/entries/ref-slot-scalar-type.json +20 -0
  94. package/src/migrations/entry-data.ts +207 -0
  95. package/src/migrations/index.ts +22 -0
  96. package/src/migrations/match.ts +382 -0
  97. package/src/migrations/patch.ts +265 -0
  98. package/src/migrations/provenance.ts +167 -0
  99. package/src/migrations/registry.ts +40 -0
  100. package/src/migrations/report.ts +169 -0
  101. package/src/migrations/types.ts +144 -0
  102. package/src/migrations/yaml-edit.ts +260 -0
  103. package/src/parse-loaded-file.ts +31 -12
  104. package/src/resolve-schema-type-refs.ts +93 -16
  105. package/src/schema-compat.ts +212 -74
  106. package/src/schema-region.ts +46 -0
  107. package/src/types.ts +17 -0
  108. package/src/validate-cel-context.ts +36 -3
  109. package/src/validate-step-inputs.ts +56 -1
  110. package/src/validate-value-type-slots.ts +182 -0
  111. package/src/value-type-keyword.ts +135 -0
  112. package/src/yaml-source-edit.ts +106 -0
  113. package/dist/binary-slot.d.ts +0 -39
  114. package/dist/binary-slot.d.ts.map +0 -1
  115. package/dist/binary-slot.js +0 -58
  116. package/src/binary-slot.ts +0 -71
@@ -1,7 +1,8 @@
1
1
  import AjvModule from "ajv";
2
2
  import addFormats from "ajv-formats";
3
- import { INCLUDE_BYTES_ENGINE, INCLUDE_ENGINE_NAMES, isRefSentinel, isTaggedSentinel, ManifestRootSchema, normalizeRefSlots, } from "@telorun/templating";
4
- import { binaryKeyword, isBinarySlot } from "./binary-slot.js";
3
+ import { isRefSentinel, isTaggedSentinel, ManifestRootSchema, producedTypeOf, } from "@telorun/templating";
4
+ import { celBaseOfValueType, celTypeOfValueType, readValueTypeSlot, valueBrandBases, valueTypeOf, valueTypePlaceholder, } from "@telorun/sdk";
5
+ import { registerTeloKeywords } from "./value-type-keyword.js";
5
6
  const Ajv = AjvModule.default ?? AjvModule;
6
7
  /** Creates a configured AJV instance (allErrors, strict: false, with formats).
7
8
  * Also registers the kernel manifest root schema under `telo://manifest` so
@@ -16,53 +17,144 @@ export function createAjv() {
16
17
  addFormats.default
17
18
  ? addFormats.default(instance)
18
19
  : addFormats(instance);
19
- // Bytes have no JSON Schema type, so the annotation carries the check. Registered
20
- // here and in the kernel's validators from one definition, so a literal at a byte
21
- // slot is rejected statically and at dispatch by the identical rule.
22
- instance.addKeyword(binaryKeyword());
20
+ // One registration site for every Telo keyword the annotations as no-ops and
21
+ // `x-telo-type` as the one that checks. Registered here and in the kernel's
22
+ // validators from one definition, so a literal at an instance-typed slot is
23
+ // rejected statically and at dispatch by the identical rule.
24
+ registerTeloKeywords(instance);
23
25
  instance.addSchema(ManifestRootSchema);
24
26
  return instance;
25
27
  }
26
28
  const ajv = createAjv();
27
29
  const compiledSchemaValidators = new WeakMap();
28
- /** Conservative structural JSON Schema compatibility check.
29
- * Only flags definite mismatches: missing required fields and primitive type conflicts.
30
- * Ambiguous cases (anyOf/oneOf/etc.) are treated as compatible. */
31
- export function checkSchemaCompatibility(source, target) {
30
+ /**
31
+ * Conservative structural JSON Schema compatibility check is a value shaped
32
+ * like `source` acceptable where `target` is declared?
33
+ *
34
+ * COVARIANT, because the values this compares are consumed by reading: a
35
+ * narrower element satisfies a slot declaring a wider one. Only DEFINITE
36
+ * mismatches are flagged — a missing required field, a primitive type conflict,
37
+ * a disagreeing type argument. Anything ambiguous (`anyOf` / `oneOf` / `allOf`,
38
+ * an absent `type`, an undeclared argument) is treated as compatible, so an
39
+ * unmigrated producer and consumer keep checking exactly as they did.
40
+ *
41
+ * The traversal is written here rather than reused: the function this replaced
42
+ * compared only `type` for the names in `target.required` and descended only
43
+ * into objects, so a stream of arrays of strings and a stream of arrays of
44
+ * integers both read as `array` and passed — leaving argument checking inert on
45
+ * exactly the nested shapes it exists for. What survives from it is its posture.
46
+ *
47
+ * `resolveRef` sees through a named shape. Declaring a shape once and
48
+ * referencing it is the sanctioned way to reuse one, so without it two such
49
+ * arguments present as opaque nodes carrying no information — the same reason
50
+ * {@link withLiveValuesSkipped} takes one.
51
+ */
52
+ export function checkSchemaCompatibility(source, target, resolveRef) {
32
53
  const issues = [];
33
- checkObject(source, target, "", issues);
54
+ compare(source, target, "", issues, resolveRef, new Set());
34
55
  return { compatible: issues.length === 0, issues };
35
56
  }
36
- function checkObject(source, target, path, issues) {
37
- const targetRequired = target.required ?? [];
57
+ function deref(schema, resolveRef) {
58
+ if (!resolveRef || typeof schema.$ref !== "string")
59
+ return schema;
60
+ return resolveRef(schema.$ref) ?? schema;
61
+ }
62
+ function compare(rawSource, rawTarget, path, issues, resolveRef, seen) {
63
+ if (!rawSource || !rawTarget || typeof rawSource !== "object" || typeof rawTarget !== "object") {
64
+ return;
65
+ }
66
+ // A recursive shape reached through the same pair of references twice is the
67
+ // same question again; answering it once terminates and loses nothing.
68
+ //
69
+ // The key is the REFERENCE PAIR and deliberately not the path. A path grows on
70
+ // every descent, so a key containing it is new every time and the guard never
71
+ // fires — which is a stack overflow on the first self-referential shape, taking
72
+ // every other diagnostic in the file with it. It also has to be this way to be
73
+ // correct rather than merely terminating: comparing two schemas gives the same
74
+ // answer wherever they are reached from, so the second visit has nothing to add.
75
+ if (typeof rawSource.$ref === "string" && typeof rawTarget.$ref === "string") {
76
+ const key = `${rawSource.$ref}|${rawTarget.$ref}`;
77
+ if (seen.has(key))
78
+ return;
79
+ seen.add(key);
80
+ }
81
+ const source = deref(rawSource, resolveRef);
82
+ const target = deref(rawTarget, resolveRef);
83
+ // Value types first: an `instance` representation has no JSON `type` to
84
+ // compare, so its identity IS the comparison — and its arguments are where the
85
+ // real information lives.
86
+ const sourceType = readValueTypeSlot(source);
87
+ const targetType = readValueTypeSlot(target);
88
+ if (sourceType && targetType) {
89
+ if (sourceType.name !== targetType.name) {
90
+ issues.push(`${path || "/"}: value type mismatch — source is '${sourceType.name}', target expects '${targetType.name}'`);
91
+ return;
92
+ }
93
+ for (const [argument, targetArg] of Object.entries(targetType.args)) {
94
+ const sourceArg = sourceType.args[argument];
95
+ // An omitted argument is *any*, in BOTH directions. That is what keeps a
96
+ // bare `Telo.Stream` flowing into a typed slot and vice versa, so nothing
97
+ // that does not declare its element is forced to.
98
+ if (sourceArg === undefined)
99
+ continue;
100
+ compare(sourceArg, targetArg, `${path}<${argument}>`, issues, resolveRef, seen);
101
+ }
102
+ return;
103
+ }
104
+ // One side declares a value type and the other does not. A `json`
105
+ // representation refines a base type, so it is compared through that base — a
106
+ // `Telo.TcpPort` into a plain `integer` slot is gradual typing working. An
107
+ // `instance` is not JSON at all, so ANY declared JSON type on the other side is
108
+ // a definite conflict; a side declaring no type at all is still saying nothing
109
+ // and stays compatible.
110
+ if (Boolean(sourceType) !== Boolean(targetType)) {
111
+ const declared = (sourceType ?? targetType);
112
+ const other = sourceType ? target : source;
113
+ if (declared.entry && typeof other.type === "string") {
114
+ const base = celBaseOfValueType(declared.entry);
115
+ const asJson = base === undefined ? undefined : declared.entry.base;
116
+ if (asJson !== other.type) {
117
+ issues.push(`${path || "/"}: value type mismatch — ${sourceType ? "source is" : "target expects"} '${declared.name}', ${sourceType ? "target expects" : "source is"} '${other.type}'`);
118
+ return;
119
+ }
120
+ }
121
+ }
122
+ // Only flag definite primitive type clashes; an absent or union `type` says
123
+ // too little to judge.
124
+ if (typeof source.type === "string" &&
125
+ typeof target.type === "string" &&
126
+ source.type !== target.type) {
127
+ issues.push(`${path || "/"}: type mismatch — source is '${source.type}', target expects '${target.type}'`);
128
+ return;
129
+ }
130
+ if (source.anyOf || source.oneOf || source.allOf)
131
+ return;
132
+ if (target.anyOf || target.oneOf || target.allOf)
133
+ return;
134
+ // An array's element, which the old comparison never looked at — so every
135
+ // nested shape passed regardless of what it contained.
136
+ if (target.items && source.items) {
137
+ compare(source.items, target.items, `${path}[]`, issues, resolveRef, seen);
138
+ }
139
+ const targetRequired = Array.isArray(target.required) ? target.required : [];
38
140
  const sourceProps = source.properties ?? {};
39
141
  const targetProps = target.properties ?? {};
40
142
  for (const field of targetRequired) {
41
143
  if (!(field in sourceProps)) {
144
+ // Only when the source describes an object at all: a schema with no
145
+ // `properties` is saying nothing about its shape, not saying it is empty.
146
+ if (source.properties === undefined)
147
+ continue;
42
148
  issues.push(`${path}/${field}: required by target but missing from source`);
43
149
  continue;
44
150
  }
45
151
  const srcProp = sourceProps[field];
46
152
  const tgtProp = targetProps[field];
47
153
  if (tgtProp && srcProp) {
48
- checkProperty(srcProp, tgtProp, `${path}/${field}`, issues);
154
+ compare(srcProp, tgtProp, `${path}/${field}`, issues, resolveRef, seen);
49
155
  }
50
156
  }
51
157
  }
52
- function checkProperty(source, target, path, issues) {
53
- // Only flag definite primitive type clashes; skip anyOf/oneOf/allOf
54
- if (source.type &&
55
- target.type &&
56
- typeof source.type === "string" &&
57
- typeof target.type === "string" &&
58
- source.type !== target.type) {
59
- issues.push(`${path}: type mismatch — source is '${source.type}', target expects '${target.type}'`);
60
- return;
61
- }
62
- if (target.type === "object" && source.type === "object") {
63
- checkObject(source, target, path, issues);
64
- }
65
- }
66
158
  export function formatSingleError(err) {
67
159
  const p = err.instancePath || "/";
68
160
  const params = err.params ?? {};
@@ -120,13 +212,8 @@ function schemaCompiles(schema) {
120
212
  export function validateAgainstSchema(data, schema) {
121
213
  let validate = compiledSchemaValidators.get(schema);
122
214
  if (!validate) {
123
- // Normalize outside the try: a fault in our own ref-slot normalization must
124
- // surface, never be mistaken for the module author's schema being malformed.
125
- // Drop the legacy scalar `type` an older published module may still pin on
126
- // its `x-telo-ref` slots so a resolved reference object validates.
127
- const normalized = normalizeRefSlots(schema);
128
215
  try {
129
- validate = ajv.compile(normalized);
216
+ validate = ajv.compile(schema);
130
217
  }
131
218
  catch (err) {
132
219
  // The normalized schema didn't compile. If the original schema is itself
@@ -198,28 +285,36 @@ export function navigateSchemaToExprPath(schema, path) {
198
285
  return current;
199
286
  }
200
287
  /**
201
- * Recognized `x-telo-type` value brands and the CEL primitive each refines.
288
+ * Every `json`-represented value type's CEL brand → the primitive it refines.
289
+ *
202
290
  * A brand is a nominal type the analyzer registers (see cel-environment.ts) so
203
- * structurally-identical values (a `TcpPort` and a `UdpPort` are both integers)
204
- * stay distinct for static wiring checks. Brands carry no runtime effect — the
205
- * value flows as its base type. Add new brands here (e.g. `Url: "string"`).
291
+ * structurally-identical values (a `Telo.TcpPort` and a `Telo.UdpPort` are both
292
+ * integers) stay distinct for static wiring checks. Brands carry no runtime
293
+ * effect — the value flows as its base type.
294
+ *
295
+ * DERIVED from the value-type vocabulary, never hand-written: a new brand is a
296
+ * new entry file, and a table here would be a second place to edit that could
297
+ * silently disagree with the one the runtime reads.
206
298
  */
207
- export const VALUE_BRAND_BASE = {
208
- TcpPort: "int",
209
- UdpPort: "int",
210
- };
211
- /** Read a recognized `x-telo-type` brand off a schema, or undefined. */
299
+ export const VALUE_BRAND_BASE = valueBrandBases();
300
+ /** Read a `json`-represented value type's brand off a schema, or undefined.
301
+ * An `instance` type is not a brand — it replaces the JSON layer rather than
302
+ * refining it, so it carries its binding's CEL type instead. */
212
303
  export function brandOfSchema(schema) {
213
- const brand = schema?.["x-telo-type"];
214
- return typeof brand === "string" && brand in VALUE_BRAND_BASE ? brand : undefined;
304
+ const entry = valueTypeOf(schema);
305
+ return entry && entry.representation === "json" ? entry.name : undefined;
215
306
  }
216
307
  /** Map a JSON Schema type annotation to a CEL type string. */
217
308
  export function jsonSchemaToCelType(schema) {
218
309
  if (!schema || typeof schema !== "object")
219
310
  return "dyn";
220
- const brand = brandOfSchema(schema);
221
- if (brand)
222
- return brand;
311
+ // A declared value type IS the type — for an `instance` representation it is
312
+ // the only thing that says so, since bytes and streams have no JSON Schema
313
+ // type at all. Before the three annotations were unified, a byte slot's
314
+ // expression typed as `dyn` because nothing here consulted `x-telo-binary`.
315
+ const entry = valueTypeOf(schema);
316
+ if (entry)
317
+ return celTypeOfValueType(entry);
223
318
  if (schema.anyOf || schema.oneOf || schema.allOf)
224
319
  return "dyn";
225
320
  if (Array.isArray(schema.type))
@@ -263,6 +358,16 @@ export function celTypeSatisfiesJsonSchema(celType, schema) {
263
358
  return fieldBrand === celType;
264
359
  celType = sourceBase;
265
360
  }
361
+ // An `instance` representation has no JSON Schema type to compare against, so
362
+ // an expression carrying its binding's CEL type is accepted on that ground
363
+ // alone. This ADDS a case and never removes one: a mismatch falls through to
364
+ // the rules below rather than being rejected here, so nothing that checks
365
+ // today stops checking, and the `bytes` row still accepts a byte expression at
366
+ // a plain `type: string` slot.
367
+ const slotEntry = valueTypeOf(schema);
368
+ if (slotEntry?.representation === "instance" && celTypeOfValueType(slotEntry) === celType) {
369
+ return true;
370
+ }
266
371
  if (!schema.type && !schema.anyOf && !schema.oneOf && !schema.allOf)
267
372
  return true;
268
373
  if (schema.anyOf || schema.oneOf || schema.allOf)
@@ -357,12 +462,16 @@ function foldedConstraints(schema) {
357
462
  }
358
463
  export function celPlaceholderForSchema(rawSchema) {
359
464
  const schema = foldedConstraints(rawSchema);
360
- // A byte slot's placeholder must BE bytes: the same keyword validates statically
361
- // and at dispatch, so a CEL leaf standing in for a runtime buffer has to satisfy
362
- // it. This is what keeps the rule single — a literal is rejected because no YAML
363
- // literal is a Uint8Array, while a value arriving by reference passes.
364
- if (isBinarySlot(schema))
365
- return new Uint8Array();
465
+ // An instance-typed slot's placeholder must BE an instance: the same keyword
466
+ // validates statically and at dispatch, so a CEL leaf standing in for a runtime
467
+ // value has to satisfy it. This is what keeps the rule single — a literal is
468
+ // rejected because no YAML literal is a byte buffer, while a value arriving by
469
+ // reference passes. The stand-in comes from the binding table, so a new
470
+ // instance type brings its own rather than adding a branch here; a `live` type
471
+ // declares none, because nothing validates it.
472
+ const placeholder = valueTypePlaceholder(schema);
473
+ if (placeholder !== undefined)
474
+ return placeholder;
366
475
  if (schema.default !== undefined)
367
476
  return schema.default;
368
477
  // An enum-constrained field needs a placeholder drawn from the enum: the
@@ -537,16 +646,23 @@ onSubstitute, path = "") {
537
646
  if (isRefSentinel(data)) {
538
647
  return data;
539
648
  }
540
- // A file embed's type is a CONSTANT of the tag, not a function of the slot:
541
- // `!include-text` always produces a string and `!include-bytes` always
542
- // produces bytes. Collapsing them to a slot-shaped placeholder like a CEL
543
- // expression would make every slot accept both, so a byte embed at a
544
- // `type: string` field passed `telo check` and failed at resource creation
545
- // and the reverse (text at an `x-telo-binary` slot) did too. Substituting the
546
- // real type lets AJV and the `x-telo-binary` keyword reject both directions
547
- // statically, with no new diagnostic code.
548
- if (isTaggedSentinel(data) && INCLUDE_ENGINE_NAMES.has(data.engine)) {
549
- return data.engine === INCLUDE_BYTES_ENGINE ? new Uint8Array() : "";
649
+ // A tag whose produced type is a CONSTANT of the tag rather than a function of
650
+ // the slot substitutes a placeholder of THAT type: `!include-text` always
651
+ // produces a string and `!include-bytes` always produces bytes. Collapsing
652
+ // them to a slot-shaped placeholder like a CEL expression would make every
653
+ // slot accept both, so a byte embed at a `type: string` field passed
654
+ // `telo check` and failed at resource creation and the reverse did too.
655
+ // Substituting the real type lets AJV and the `x-telo-type` keyword reject
656
+ // both directions statically, with no new diagnostic code.
657
+ //
658
+ // The engine is what says so. This used to branch on two tag names, which was
659
+ // the only place a tag's produced type was written down and it was written in
660
+ // the consumer — so a future tag producing bytes had to be added to a set here
661
+ // rather than declaring it.
662
+ if (isTaggedSentinel(data)) {
663
+ const produced = producedTypeOf(data.engine);
664
+ if (produced)
665
+ return celPlaceholderForSchema(produced);
550
666
  }
551
667
  if (isTaggedSentinel(data)) {
552
668
  mark();
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Where author-written JSON Schema lives in a manifest — one definition, read by
3
+ * every surface that has to bound itself to schema.
4
+ *
5
+ * The keys are the KERNEL's own schema-valued manifest keys, which no resource
6
+ * kind owns. That is what makes the rule generic: a surface using it learns no
7
+ * resource kind, and a module that invents a schema-bearing field of its own
8
+ * reaches it through one of these or not at all — against the topology-driven
9
+ * constraint, an enumeration of the standard library's kinds would be both
10
+ * incomplete and knowledge the analyzer must not hold.
11
+ *
12
+ * A schema fragment is NOT confined to kind documents. An inline `inputType:` /
13
+ * `outputType:` sits on any kind that declares one, an API route carries
14
+ * `request.schema.body`, a `Telo.JsonSchema` carries `schema`. So a check that
15
+ * walks a manifest's ROOT keys covers a fraction of the sites an author writes —
16
+ * which is a silent hole in exactly the checks that exist to stop a silent
17
+ * degrade. Containment is by ANCESTRY instead: a node is in a schema region when
18
+ * some key on the path to it is one of these.
19
+ *
20
+ * Browser-safe: no Node built-ins.
21
+ */
22
+ /** The kernel's schema-valued manifest keys. */
23
+ export declare const SCHEMA_REGION_KEYS: readonly string[];
24
+ /**
25
+ * True when `path` reaches into a schema region — some ANCESTOR segment is a
26
+ * schema-valued key.
27
+ *
28
+ * Ancestors only, so a rule keyed on a region key itself still means "inside a
29
+ * schema" rather than "is one". `path` is the walk's own segment list; numeric
30
+ * segments (array indices) never equal a key name, so they need no special case.
31
+ */
32
+ export declare function isInSchemaRegion(path: readonly (string | number)[]): boolean;
33
+ //# sourceMappingURL=schema-region.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-region.d.ts","sourceRoot":"","sources":["../src/schema-region.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,gDAAgD;AAChD,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAM/C,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,OAAO,CAM5E"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Where author-written JSON Schema lives in a manifest — one definition, read by
3
+ * every surface that has to bound itself to schema.
4
+ *
5
+ * The keys are the KERNEL's own schema-valued manifest keys, which no resource
6
+ * kind owns. That is what makes the rule generic: a surface using it learns no
7
+ * resource kind, and a module that invents a schema-bearing field of its own
8
+ * reaches it through one of these or not at all — against the topology-driven
9
+ * constraint, an enumeration of the standard library's kinds would be both
10
+ * incomplete and knowledge the analyzer must not hold.
11
+ *
12
+ * A schema fragment is NOT confined to kind documents. An inline `inputType:` /
13
+ * `outputType:` sits on any kind that declares one, an API route carries
14
+ * `request.schema.body`, a `Telo.JsonSchema` carries `schema`. So a check that
15
+ * walks a manifest's ROOT keys covers a fraction of the sites an author writes —
16
+ * which is a silent hole in exactly the checks that exist to stop a silent
17
+ * degrade. Containment is by ANCESTRY instead: a node is in a schema region when
18
+ * some key on the path to it is one of these.
19
+ *
20
+ * Browser-safe: no Node built-ins.
21
+ */
22
+ /** The kernel's schema-valued manifest keys. */
23
+ export const SCHEMA_REGION_KEYS = [
24
+ "schema",
25
+ "status",
26
+ "inputType",
27
+ "outputType",
28
+ "itemType",
29
+ ];
30
+ /**
31
+ * True when `path` reaches into a schema region — some ANCESTOR segment is a
32
+ * schema-valued key.
33
+ *
34
+ * Ancestors only, so a rule keyed on a region key itself still means "inside a
35
+ * schema" rather than "is one". `path` is the walk's own segment list; numeric
36
+ * segments (array indices) never equal a key name, so they need no special case.
37
+ */
38
+ export function isInSchemaRegion(path) {
39
+ for (let i = 0; i < path.length - 1; i++) {
40
+ const segment = path[i];
41
+ if (typeof segment === "string" && SCHEMA_REGION_KEYS.includes(segment))
42
+ return true;
43
+ }
44
+ return false;
45
+ }
package/dist/types.d.ts CHANGED
@@ -101,11 +101,28 @@ export interface LoadOptions {
101
101
  * pairs manifests to YAML nodes by index. Folded into the file cache key so a
102
102
  * desugared and a raw load of the same file never collide. */
103
103
  desugarImports?: boolean;
104
+ /** When true, the loader's migration phase rewrites legacy spellings in each
105
+ * parsed document before anything else reads the tree. On for every resolved
106
+ * consumer — the kernel's analysis and runtime loads, `telo check`, the
107
+ * analyzer — so one rewrite serves the definition registry, the runtime and
108
+ * the editor's analysis alike.
109
+ *
110
+ * **Off for a round-trip view.** The editor pairs manifests to YAML nodes by
111
+ * index and writes the pair back on save; migrating one half of that pair
112
+ * would silently change the author's file. `telo migrate` is likewise a raw
113
+ * consumer — it rewrites the YAML itself and must see the legacy spelling to
114
+ * find it. Folded into the file cache key so a migrated and a raw load of
115
+ * the same file never collide. */
116
+ migrate?: boolean;
104
117
  }
105
118
  export interface LoaderInitOptions {
106
119
  /** Handlers for CEL stdlib functions (e.g. `sha256`). Analyzer-only callers may
107
120
  * omit this and get throwing stubs; runtime callers (kernel) must supply real impls. */
108
121
  celHandlers?: import("./cel-environment.js").CelHandlers;
122
+ /** Migration set for `LoadOptions.migrate` loads. Defaults to the analyzer's
123
+ * own `CORE_MIGRATIONS`. A host supplies its own once module-shipped entries
124
+ * are aggregated alongside the core ones. */
125
+ migrations?: readonly import("./migrations/types.js").MigrationEntry[];
109
126
  }
110
127
  export interface AnalysisOptions {
111
128
  strictContexts?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"validate-cel-context.d.ts","sourceRoot":"","sources":["../src/validate-cel-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAItF,MAAM,WAAW,kBAAkB;IACjC;mEAC+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC;;kDAE8C;IAC9C,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;KACxD,CAAC;IACF,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;KAC/C,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;CACtC;AA4CD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,QAAQ,GAAE,WAAW,CAAC,MAAM,CAAa,GACxC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAyCjC;AAuFD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAoBzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,IAAI,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAChD,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAiJrB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAQrB;AAWD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,SAAM,GACT,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,CAAC,CAGvD;AAUD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,SAAM,GAAG,MAAM,EAAE,CAqBxF"}
1
+ {"version":3,"file":"validate-cel-context.d.ts","sourceRoot":"","sources":["../src/validate-cel-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAItF,MAAM,WAAW,kBAAkB;IACjC;mEAC+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC;;kDAE8C;IAC9C,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;KACxD,CAAC;IACF,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;KAC/C,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;CACtC;AA4CD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,QAAQ,GAAE,WAAW,CAAC,MAAM,CAAa,GACxC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA0EjC;AAuFD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAoBzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,IAAI,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAChD,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAiJrB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAQrB;AAWD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,SAAM,GACT,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,CAAC,CAGvD;AAUD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,SAAM,GAAG,MAAM,EAAE,CAqBxF"}
@@ -1,5 +1,5 @@
1
1
  export { extractAccessChains, validateChainAgainstSchema } from "@telorun/templating";
2
- import { mergeTypeSchemas } from "@telorun/sdk";
2
+ import { mergeTypeSchemas, parseCanonicalTypeSchemaId } from "@telorun/sdk";
3
3
  import { KERNEL_BUILTINS } from "./builtins.js";
4
4
  /**
5
5
  * Resolve a type field value (string name, inline type, or raw schema) to a JSON Schema.
@@ -63,6 +63,31 @@ export function resolveTypeFieldToSchema(value, allManifests, ancestry = new Set
63
63
  }
64
64
  if (typeof value === "object" && value !== null) {
65
65
  const obj = value;
66
+ // A canonical, module-scoped id — what `resolveSchemaTypeRefs` normalizes
67
+ // both the reference tag and the legacy `telo://` authority form into.
68
+ //
69
+ // This is the ALIAS-AWARE path, and it is why the canonical form exists:
70
+ // the id names the OWNING MODULE, so two libraries declaring a shape of the
71
+ // same name stay distinct. The bare-name lookup below cannot do that — it
72
+ // matches the first manifest with that `metadata.name` in a flattened list,
73
+ // which is how an alias got silently dropped.
74
+ const canonical = parseCanonicalTypeSchemaId(obj.$ref);
75
+ if (canonical) {
76
+ if (ancestry.has(obj.$ref))
77
+ return undefined;
78
+ const owned = allManifests.find((m) => m.metadata?.name === canonical.typeName &&
79
+ m.metadata?.module === canonical.moduleName &&
80
+ isTypeKind(m.kind, allManifests) &&
81
+ typeof m.schema === "object" &&
82
+ m.schema !== null);
83
+ if (owned) {
84
+ return applyExtends(owned.schema, owned.extends, allManifests, new Set(ancestry).add(obj.$ref));
85
+ }
86
+ // A module that is not in this set — a single-file analysis, or an import
87
+ // whose library was not forwarded. Fall through to the bare name, which is
88
+ // what this resolved before the canonical form existed.
89
+ return resolveTypeFieldToSchema(canonical.typeName, allManifests, ancestry);
90
+ }
66
91
  // Inline type resource: { kind: "Type.JsonSchema", schema: {...} }
67
92
  if (obj.schema && typeof obj.schema === "object") {
68
93
  return applyExtends(obj.schema, obj.extends, allManifests, ancestry);
@@ -71,8 +96,9 @@ export function resolveTypeFieldToSchema(value, allManifests, ancestry = new Set
71
96
  if (obj.type || obj.properties) {
72
97
  return obj;
73
98
  }
74
- // Named type reference resolved from a `!ref` → { kind, name } resolve the
75
- // named Telo.Type the same way as the bare-string form.
99
+ // Named type reference resolved from a `!ref` → { kind, name }. Reached only
100
+ // where the canonical rewrite could not run (a scope whose aliases are not in
101
+ // hand); resolves by bare name, as it always has.
76
102
  if (typeof obj.name === "string") {
77
103
  return resolveTypeFieldToSchema(obj.name, allManifests, ancestry);
78
104
  }
@@ -4,6 +4,9 @@ export interface StepInputIssue {
4
4
  path: string;
5
5
  targetLabel: string;
6
6
  message: string;
7
+ /** Set when the issue is a type-argument disagreement rather than a contract
8
+ * shape violation — the two read differently and deserve their own code. */
9
+ code?: "CEL_TYPE_ARGUMENT_MISMATCH";
7
10
  }
8
11
  /**
9
12
  * Validate every step's `inputs:` against the invoked target's declared input
@@ -20,5 +23,9 @@ export interface StepInputIssue {
20
23
  * from `x-telo-step-context`, and the paired inputs field from whichever sibling
21
24
  * property carries `x-telo-topology-role: inputs`.
22
25
  */
23
- export declare function collectStepInputIssues(manifest: Record<string, any>, defSchema: Record<string, any>, allManifests: Record<string, any>[], defs: DefinitionRegistry, aliases: AliasResolver, scopes: ModuleScopes): StepInputIssue[];
26
+ export declare function collectStepInputIssues(manifest: Record<string, any>, defSchema: Record<string, any>, allManifests: Record<string, any>[], defs: DefinitionRegistry, aliases: AliasResolver, scopes: ModuleScopes,
27
+ /** The typed `steps.<name>.result` context for this resource. Supplied by the
28
+ * caller because building it is analyzer state; without it the contract check
29
+ * still runs and only the type-argument comparison is skipped. */
30
+ stepContext?: Record<string, any>): StepInputIssue[];
24
31
  //# sourceMappingURL=validate-step-inputs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-step-inputs.d.ts","sourceRoot":"","sources":["../src/validate-step-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAanE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,YAAY,GACnB,cAAc,EAAE,CAyElB"}
1
+ {"version":3,"file":"validate-step-inputs.d.ts","sourceRoot":"","sources":["../src/validate-step-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAoBnE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB;iFAC6E;IAC7E,IAAI,CAAC,EAAE,4BAA4B,CAAC;CACrC;AAGD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,YAAY;AACpB;;mEAEmE;AACnE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAChC,cAAc,EAAE,CAiHlB"}
@@ -1,5 +1,7 @@
1
1
  import { resolveContract } from "./invocation-contract.js";
2
- import { substituteCelFields, validateAgainstSchema } from "./schema-compat.js";
2
+ import { checkSchemaCompatibility, navigateSchemaToExprPath, substituteCelFields, validateAgainstSchema, } from "./schema-compat.js";
3
+ import { plainChainOf } from "@telorun/templating";
4
+ import { valueTypeOf } from "@telorun/sdk";
3
5
  import { analyzerContractScope, containerOf, gatherPropertySchemas, missingRequired, resolveLocalRef, walkStepArray, } from "./analyzer.js";
4
6
  /**
5
7
  * Validate every step's `inputs:` against the invoked target's declared input
@@ -16,7 +18,11 @@ import { analyzerContractScope, containerOf, gatherPropertySchemas, missingRequi
16
18
  * from `x-telo-step-context`, and the paired inputs field from whichever sibling
17
19
  * property carries `x-telo-topology-role: inputs`.
18
20
  */
19
- export function collectStepInputIssues(manifest, defSchema, allManifests, defs, aliases, scopes) {
21
+ export function collectStepInputIssues(manifest, defSchema, allManifests, defs, aliases, scopes,
22
+ /** The typed `steps.<name>.result` context for this resource. Supplied by the
23
+ * caller because building it is analyzer state; without it the contract check
24
+ * still runs and only the type-argument comparison is skipped. */
25
+ stepContext) {
20
26
  const out = [];
21
27
  const props = defSchema.properties;
22
28
  if (!props)
@@ -67,6 +73,45 @@ export function collectStepInputIssues(manifest, defSchema, allManifests, defs,
67
73
  // container and survive the filter.
68
74
  const celPaths = new Set();
69
75
  const substituted = substituteCelFields(values, contract.schema, undefined, (p) => celPaths.add(p));
76
+ // The type-argument check, at the one site where a produced value's schema
77
+ // meets a consuming slot's. A CEL leaf's placeholder says nothing about
78
+ // what the expression yields, so AJV above is silent here by design — and
79
+ // that silence is exactly where a stream of the wrong element used to
80
+ // flow. The comparison is covariant and gradual: an omitted argument is
81
+ // *any* in both directions, so only a definite conflict is reported.
82
+ if (stepContext) {
83
+ for (const [inputName, inputValue] of Object.entries(values)) {
84
+ const chain = plainChainOf(inputValue);
85
+ // The step context is rooted at the STEP MAP, so a `steps.` prefix is
86
+ // the namespace name and not a property of it. Only that namespace is
87
+ // navigated: `inputs.` and a named binding resolve elsewhere, and
88
+ // guessing at a root this does not hold would compare the wrong schema.
89
+ if (!chain?.startsWith("steps."))
90
+ continue;
91
+ const produced = navigateSchemaToExprPath(stepContext, chain.slice("steps.".length));
92
+ const slotSchema = contract.schema.properties?.[inputName];
93
+ if (!produced || !slotSchema)
94
+ continue;
95
+ // ONLY a type-argument disagreement, which is what the code says. The
96
+ // comparator is a general structural comparison, so running it on any
97
+ // pair would report a missing required property as "disagreeing type
98
+ // arguments" — and would turn every plain-chain wiring site into a
99
+ // broad new Error-severity check hidden behind an argument-specific
100
+ // name. Both sides must declare a value type for the question to be
101
+ // about arguments at all.
102
+ if (!valueTypeOf(produced) || !valueTypeOf(slotSchema))
103
+ continue;
104
+ const { compatible, issues } = checkSchemaCompatibility(produced, slotSchema, (ref) => defs.schemaForId(ref));
105
+ if (compatible)
106
+ continue;
107
+ out.push({
108
+ path: `${stepPath}.${inputsField}.${inputName}`,
109
+ targetLabel: invokedName ?? invokedKind ?? "the invoked resource",
110
+ message: issues.join("; "),
111
+ code: "CEL_TYPE_ARGUMENT_MISMATCH",
112
+ });
113
+ }
114
+ }
70
115
  for (const issue of validateAgainstSchema(substituted, contract.schema)) {
71
116
  if (celPaths.has(issue.path))
72
117
  continue;