@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
@@ -282,7 +282,76 @@ export function celTypeSatisfiesJsonSchema(celType, schema) {
282
282
  return compatibleWith.some((t) => schemaTypes.includes(t));
283
283
  }
284
284
  /** Return a literal placeholder value of the correct schema type for AJV. */
285
- export function celPlaceholderForSchema(schema) {
285
+ /** A number inside the schema's declared bounds. The placeholder stands in for a
286
+ * value only known at runtime, so its single job is to be ACCEPTABLE — a bare 0
287
+ * into an `exclusiveMinimum: 0` field (a scale, a positive dimension) would
288
+ * report a violation against a value the author never wrote. Bounds are read in
289
+ * the order that pins the value: an inclusive minimum is usable as-is, an
290
+ * exclusive one needs a step past it, and a wholly-negative range needs the
291
+ * maximum end instead. */
292
+ function numericPlaceholder(schema) {
293
+ const isInteger = schema.type === "integer";
294
+ // One step past an exclusive bound. Integral for both `integer` and `number`:
295
+ // any value inside the band will do, and a whole number is inside it whenever
296
+ // a fractional one is (the narrow-band case below handles when it is not).
297
+ const step = 1;
298
+ if (typeof schema.minimum === "number")
299
+ return schema.minimum;
300
+ if (typeof schema.exclusiveMinimum === "number") {
301
+ const candidate = schema.exclusiveMinimum + step;
302
+ if (typeof schema.maximum === "number" && candidate > schema.maximum) {
303
+ // A narrow band (0 < x <= 0.5) has no integral step; take the midpoint,
304
+ // which the band's own definition guarantees is inside it.
305
+ return isInteger ? schema.maximum : (schema.exclusiveMinimum + schema.maximum) / 2;
306
+ }
307
+ return candidate;
308
+ }
309
+ if (typeof schema.maximum === "number" && schema.maximum < 0)
310
+ return schema.maximum;
311
+ if (typeof schema.exclusiveMaximum === "number" && schema.exclusiveMaximum <= 0) {
312
+ return schema.exclusiveMaximum - step;
313
+ }
314
+ return 0;
315
+ }
316
+ /** The constraints a placeholder must satisfy, folded across `allOf` branches.
317
+ * Inheritance between types is expressed by intersecting `allOf`, so a bound a
318
+ * parent declared lives in a branch rather than on the property itself — a
319
+ * placeholder that reads only the top level would violate it and report against
320
+ * a value the author never wrote. The tightest bound wins, which is what the
321
+ * intersection means. */
322
+ function foldedConstraints(schema) {
323
+ const branches = Array.isArray(schema.allOf) ? schema.allOf : [];
324
+ if (branches.length === 0)
325
+ return schema;
326
+ const out = { ...schema };
327
+ for (const branch of branches) {
328
+ const folded = foldedConstraints(branch);
329
+ for (const key of ["minimum", "exclusiveMinimum", "minLength", "minItems"]) {
330
+ if (typeof folded[key] === "number" && (typeof out[key] !== "number" || folded[key] > out[key])) {
331
+ out[key] = folded[key];
332
+ }
333
+ }
334
+ for (const key of ["maximum", "exclusiveMaximum"]) {
335
+ if (typeof folded[key] === "number" && (typeof out[key] !== "number" || folded[key] < out[key])) {
336
+ out[key] = folded[key];
337
+ }
338
+ }
339
+ if (out.type === undefined && folded.type !== undefined)
340
+ out.type = folded.type;
341
+ if (out.enum === undefined && folded.enum !== undefined)
342
+ out.enum = folded.enum;
343
+ if (out.default === undefined && folded.default !== undefined)
344
+ out.default = folded.default;
345
+ if (folded.required) {
346
+ out.required = [...new Set([...(out.required ?? []), ...folded.required])];
347
+ }
348
+ if (folded.properties)
349
+ out.properties = { ...folded.properties, ...(out.properties ?? {}) };
350
+ }
351
+ return out;
352
+ }
353
+ export function celPlaceholderForSchema(rawSchema) {
354
+ const schema = foldedConstraints(rawSchema);
286
355
  if (schema.default !== undefined)
287
356
  return schema.default;
288
357
  // An enum-constrained field needs a placeholder drawn from the enum: the
@@ -296,19 +365,46 @@ export function celPlaceholderForSchema(schema) {
296
365
  switch (schema.type) {
297
366
  case "integer":
298
367
  case "number":
299
- return schema.minimum ?? 0;
368
+ return numericPlaceholder(schema);
300
369
  case "string":
301
- return "";
370
+ // `minLength` is the string analogue of `minimum`: a bare "" into a
371
+ // `minLength: 1` field would report a violation against a value the author
372
+ // never wrote. Any string of the right length will do.
373
+ return typeof schema.minLength === "number" && schema.minLength > 0
374
+ ? "x".repeat(schema.minLength)
375
+ : "";
302
376
  case "boolean":
303
377
  return false;
304
378
  case "array":
305
- return [];
379
+ // `minItems` is the array analogue of `minimum` / `minLength`: an empty
380
+ // array into a `minItems: 1` field would report a violation against a
381
+ // value the author never wrote.
382
+ return typeof schema.minItems === "number" && schema.minItems > 0
383
+ ? Array.from({ length: schema.minItems }, () => celPlaceholderForSchema((schema.items ?? {})))
384
+ : [];
306
385
  case "object":
307
- return {};
386
+ return objectPlaceholder(schema);
308
387
  default:
309
388
  return null;
310
389
  }
311
390
  }
391
+ /** An object satisfying the schema's `required` list. A bare `{}` would report
392
+ * every required property as missing against a value the author never wrote —
393
+ * the case where a whole map is produced by one expression (`inputs: !cel
394
+ * "buildRequest(...)"`), which is exactly when the analyzer knows least and
395
+ * should say least. Members are filled recursively by the same rule, so a
396
+ * required nested object is satisfied too. */
397
+ function objectPlaceholder(schema) {
398
+ const required = Array.isArray(schema.required) ? schema.required : [];
399
+ if (required.length === 0)
400
+ return {};
401
+ const properties = (schema.properties ?? {});
402
+ const out = {};
403
+ for (const key of required) {
404
+ out[key] = celPlaceholderForSchema(properties[key] ?? {});
405
+ }
406
+ return out;
407
+ }
312
408
  const CEL_PURE_RE = /^\s*\$\{\{[^}]*\}\}\s*$/;
313
409
  /** Resolve a `$ref` (only `#/$defs/...` form) against the root schema. */
314
410
  export function resolveRef(schema, root) {
@@ -321,8 +417,75 @@ export function resolveRef(schema, root) {
321
417
  return schema;
322
418
  }
323
419
  /** Collect property schemas from top-level `properties` and all `oneOf`/`anyOf` sub-schemas. */
420
+ /**
421
+ * The `oneOf` / `anyOf` branch a value is written against, when exactly one fits.
422
+ *
423
+ * A union carries no `type` / `properties` / `items` of its own, so a walker that
424
+ * ignores it descends with an empty schema and hands every CEL leaf underneath a
425
+ * `null` placeholder — which then fails every branch and reports a pile of
426
+ * violations against a value that is perfectly valid. Picking the branch first
427
+ * is what lets the leaves be typed.
428
+ *
429
+ * Selection is structural and conservative: a branch must agree with the data's
430
+ * kind, and for an object every `required` key must be present (which is what
431
+ * separates a `{type, text}` part from a `{type, data, mediaType}` one). If that
432
+ * leaves anything other than exactly one branch, the union is returned unchanged
433
+ * — an ambiguous union is one the analyzer should not resolve on the author's
434
+ * behalf.
435
+ */
436
+ function selectUnionBranch(schema, data, root) {
437
+ const branches = (schema.oneOf ?? schema.anyOf);
438
+ if (!Array.isArray(branches) || branches.length === 0)
439
+ return schema;
440
+ if (schema.type !== undefined || schema.properties !== undefined)
441
+ return schema;
442
+ const kind = Array.isArray(data)
443
+ ? "array"
444
+ : data === null
445
+ ? "null"
446
+ : typeof data === "object"
447
+ ? "object"
448
+ : typeof data === "string"
449
+ ? "string"
450
+ : typeof data === "number"
451
+ ? "number"
452
+ : typeof data === "boolean"
453
+ ? "boolean"
454
+ : undefined;
455
+ if (!kind)
456
+ return schema;
457
+ const fits = branches
458
+ .map((b) => resolveRef(b, root))
459
+ .filter((b) => {
460
+ const types = Array.isArray(b.type) ? b.type : b.type ? [b.type] : [];
461
+ if (types.length > 0 && !types.includes(kind))
462
+ return false;
463
+ if (kind === "object" && Array.isArray(b.required)) {
464
+ const keys = Object.keys(data);
465
+ if (!b.required.every((r) => keys.includes(r)))
466
+ return false;
467
+ }
468
+ return true;
469
+ });
470
+ return fits.length === 1 ? fits[0] : schema;
471
+ }
324
472
  export function collectProperties(schema) {
325
473
  const props = { ...(schema.properties ?? {}) };
474
+ // `allOf` INTERSECTS, so a branch constraining a property constrains the
475
+ // property itself — type inheritance expresses an inherited bound exactly this
476
+ // way (`allOf: [{ properties: { score: { minimum: 10 } } }]`). Merging the
477
+ // branch's constraints into the property is what lets a placeholder for that
478
+ // property be built from the bound the value must actually satisfy; reading
479
+ // only the top level would produce one that violates it.
480
+ for (const sub of (schema.allOf ?? [])) {
481
+ if (!sub || typeof sub !== "object" || !sub.properties)
482
+ continue;
483
+ for (const [k, v] of Object.entries(sub.properties)) {
484
+ props[k] = k in props ? { ...props[k], ...v } : v;
485
+ }
486
+ }
487
+ // `oneOf` / `anyOf` are alternatives, not constraints: a property seen in one
488
+ // branch is contributed only when no branch already declared it.
326
489
  for (const sub of schema.oneOf ?? schema.anyOf ?? []) {
327
490
  if (sub && typeof sub === "object" && sub.properties) {
328
491
  for (const [k, v] of Object.entries(sub.properties)) {
@@ -335,10 +498,22 @@ export function collectProperties(schema) {
335
498
  }
336
499
  /** Deep-clone `data`, replacing every pure CEL template string (`${{ expr }}`) with a
337
500
  * schema-appropriate placeholder so AJV can validate non-CEL fields without false positives. */
338
- export function substituteCelFields(data, schema, rootSchema) {
501
+ export function substituteCelFields(data, schema, rootSchema,
502
+ /** Called with the dotted path of every value replaced by a placeholder.
503
+ *
504
+ * A placeholder is a stand-in for something only known at runtime, so its
505
+ * VALUE says nothing: a caller that judges constraints at these paths reports
506
+ * against a value no author wrote. Some constraints cannot be satisfied by
507
+ * construction at all (`pattern`, `format`, a `oneOf` of unrelated shapes),
508
+ * so making every placeholder acceptable is not achievable in general —
509
+ * knowing where not to look is. Structural findings survive because they are
510
+ * located at the CONTAINER, not at the substituted leaf. */
511
+ onSubstitute, path = "") {
339
512
  const root = rootSchema ?? schema;
340
- const resolved = resolveRef(schema, root);
513
+ const resolved = selectUnionBranch(resolveRef(schema, root), data, root);
514
+ const mark = () => onSubstitute?.(path);
341
515
  if (typeof data === "string" && CEL_PURE_RE.test(data)) {
516
+ mark();
342
517
  return celPlaceholderForSchema(resolved);
343
518
  }
344
519
  // `!ref <name>` sentinels are identity markers, not runtime values —
@@ -352,11 +527,12 @@ export function substituteCelFields(data, schema, rootSchema) {
352
527
  return data;
353
528
  }
354
529
  if (isTaggedSentinel(data)) {
530
+ mark();
355
531
  return celPlaceholderForSchema(resolved);
356
532
  }
357
533
  if (Array.isArray(data)) {
358
534
  const itemSchema = resolveRef((resolved.items ?? {}), root);
359
- return data.map((item) => substituteCelFields(item, itemSchema, root));
535
+ return data.map((item, i) => substituteCelFields(item, itemSchema, root, onSubstitute, `${path}[${i}]`));
360
536
  }
361
537
  if (data !== null && typeof data === "object") {
362
538
  const props = collectProperties(resolved);
@@ -365,7 +541,7 @@ export function substituteCelFields(data, schema, rootSchema) {
365
541
  : undefined;
366
542
  const result = {};
367
543
  for (const [k, v] of Object.entries(data)) {
368
- result[k] = substituteCelFields(v, (props[k] ?? addlProps ?? {}), root);
544
+ result[k] = substituteCelFields(v, (props[k] ?? addlProps ?? {}), root, onSubstitute, path ? `${path}.${k}` : k);
369
545
  }
370
546
  return result;
371
547
  }
@@ -78,9 +78,19 @@ export function validateBaseMapping(manifests, registry, aliases) {
78
78
  }
79
79
  return diagnostics;
80
80
  }
81
+ /** Keys the inherited controller supplies itself when it builds the parent
82
+ * manifest (`{ kind, metadata, ...base }`), so `base:` neither has to set them
83
+ * nor may. Without this exclusion a parent whose schema lists `metadata` in
84
+ * `required` — `JS.Script` does, the most natural parent for a remapping child —
85
+ * is unusable: omitting it is BASE_MISSING_REQUIRED and setting it is
86
+ * BASE_UNKNOWN_FIELD, since `metadata` is required but never a declared
87
+ * property. Only at the top level; a nested `metadata` field is the parent's
88
+ * own and is checked normally. */
89
+ const CONTROLLER_SUPPLIED_KEYS = new Set(["kind", "metadata"]);
81
90
  function checkObject(value, schema, path, ctx) {
82
91
  const properties = (schema.properties ?? {});
83
- const required = Array.isArray(schema.required) ? schema.required : [];
92
+ const isRoot = path === "base";
93
+ const required = (Array.isArray(schema.required) ? schema.required : []).filter((name) => !(isRoot && CONTROLLER_SUPPLIED_KEYS.has(name)));
84
94
  const additionalFalse = schema.additionalProperties === false;
85
95
  for (const req of required) {
86
96
  if (!(req in value)) {
@@ -14,12 +14,6 @@ export interface ContextResolveOpts {
14
14
  };
15
15
  allManifests?: Record<string, any>[];
16
16
  }
17
- /**
18
- * Resolve a type field value (string name, inline type, or raw schema) to a JSON Schema.
19
- * - String: look up the named type in allManifests (Type.JsonSchema resources)
20
- * - Object with `kind` + `schema`: inline type definition → return the `schema`
21
- * - Object with `type` or `properties`: raw JSON Schema, return as-is
22
- */
23
17
  export declare function resolveTypeFieldToSchema(value: unknown, allManifests: Record<string, any>[], ancestry?: ReadonlySet<string>): Record<string, any> | undefined;
24
18
  /**
25
19
  * Returns true when a CEL expression path (from walkCelExpressions, e.g. "routes[0].inputs.q")
@@ -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;AAGtF,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;AAED;;;;;GAKG;AACH,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,CA0CjC;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,CAmIrB;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,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,CA4IrB;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,11 +1,50 @@
1
1
  export { extractAccessChains, validateChainAgainstSchema } from "@telorun/templating";
2
2
  import { mergeTypeSchemas } from "@telorun/sdk";
3
+ import { KERNEL_BUILTINS } from "./builtins.js";
3
4
  /**
4
5
  * Resolve a type field value (string name, inline type, or raw schema) to a JSON Schema.
5
6
  * - String: look up the named type in allManifests (Type.JsonSchema resources)
6
7
  * - Object with `kind` + `schema`: inline type definition → return the `schema`
7
8
  * - Object with `type` or `properties`: raw JSON Schema, return as-is
8
9
  */
10
+ /**
11
+ * Kind names that DECLARE `capability: Telo.Type` — the kernel built-ins plus
12
+ * every definition in scope.
13
+ *
14
+ * Derived from the declared capability, never from the kind's spelling: which
15
+ * kinds are data shapes is a topology fact the analyzer must read off
16
+ * `Telo.Definition` docs, not guess from a name. A name test would silently miss
17
+ * any third-party type kind and would have to be edited every time one is added.
18
+ *
19
+ * Names, not fully-qualified kinds, because a resource writes its kind through
20
+ * whatever alias its file declares (`Type.JsonSchema`, `Telo.JsonSchema`,
21
+ * `Shapes.JsonSchema`) while the definition knows only its own module and name.
22
+ * Memoized per manifest list — this runs on every type-field resolution.
23
+ */
24
+ const typeKindNames = new WeakMap();
25
+ function typeCapableNames(allManifests) {
26
+ const cached = typeKindNames.get(allManifests);
27
+ if (cached)
28
+ return cached;
29
+ const names = new Set();
30
+ for (const def of [...KERNEL_BUILTINS, ...allManifests]) {
31
+ if (def?.kind !== "Telo.Definition" && def?.kind !== "Telo.Abstract")
32
+ continue;
33
+ if (def.capability !== "Telo.Type")
34
+ continue;
35
+ const name = def.metadata?.name;
36
+ if (typeof name === "string")
37
+ names.add(name);
38
+ }
39
+ typeKindNames.set(allManifests, names);
40
+ return names;
41
+ }
42
+ function isTypeKind(kind, allManifests) {
43
+ if (typeof kind !== "string")
44
+ return false;
45
+ const suffix = kind.slice(kind.lastIndexOf(".") + 1);
46
+ return typeCapableNames(allManifests).has(suffix);
47
+ }
9
48
  export function resolveTypeFieldToSchema(value, allManifests, ancestry = new Set()) {
10
49
  if (!value)
11
50
  return undefined;
@@ -15,8 +54,7 @@ export function resolveTypeFieldToSchema(value, allManifests, ancestry = new Set
15
54
  return undefined;
16
55
  // Named type reference — find a Telo.Type resource by name
17
56
  const typeManifest = allManifests.find((m) => m.metadata?.name === value &&
18
- typeof m.kind === "string" &&
19
- /\bType\b/.test(m.kind) &&
57
+ isTypeKind(m.kind, allManifests) &&
20
58
  typeof m.schema === "object" &&
21
59
  m.schema !== null);
22
60
  if (!typeManifest)
@@ -209,11 +247,20 @@ export function resolveContextAnnotations(schema, manifestItem, opts) {
209
247
  const { manifestRoot = manifestItem, defs, aliases, allManifests } = normalizedOpts;
210
248
  const from = schema["x-telo-context-from"];
211
249
  if (from) {
212
- const resolved = navigatePath(manifestItem, from.split("/"));
213
- // `resolved` is a map of property names → sub-schemas (e.g. { query: {...}, body: {...} })
250
+ const navigated = navigatePath(manifestItem, from.split("/"));
251
+ // The navigated value is either a plain map of property names → sub-schemas
252
+ // (a transport scope's `request/schema` → `{ query, body, params }`), or a
253
+ // `telo#Type` field naming one contract (`inputType:`). The second form has
254
+ // to be resolved first: the standard library writes it as the inline
255
+ // `{ kind: Type.JsonSchema, schema: … }` wrapper, so merging it verbatim
256
+ // would type the variable as `{ kind, schema }` instead of its properties.
257
+ const asType = resolveTypeFieldToSchema(navigated, allManifests ?? []);
258
+ const resolved = asType?.properties ?? navigated;
259
+ const required = Array.isArray(asType?.required) ? asType.required : undefined;
214
260
  return {
215
261
  ...schema,
216
262
  properties: { ...(schema.properties ?? {}), ...(resolved ?? {}) },
263
+ ...(required ? { required } : {}),
217
264
  additionalProperties: false,
218
265
  };
219
266
  }
@@ -0,0 +1,30 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import type { AliasResolver } from "./alias-resolver.js";
3
+ import type { DefinitionRegistry } from "./definition-registry.js";
4
+ import { type AnalysisDiagnostic } from "./types.js";
5
+ /**
6
+ * Phase 3 — static checks on declared invocation contracts.
7
+ *
8
+ * The runtime binds a contract to every instance and enforces it at dispatch;
9
+ * these are the failures worth catching before anything runs, and the ones the
10
+ * runtime cannot see at all (a declaration that is inert, an input nobody can
11
+ * supply).
12
+ *
13
+ * Diagnostics:
14
+ * - CONTRACT_MISSING_MAPPING: a definition that inherits its controller declares
15
+ * its own `inputType` / `outputType` without the `inputs:` / `result:` mapping
16
+ * that bridges it back to the inherited controller.
17
+ * - CONTRACT_INPUTS_SCHEMA_FORM: a leftover `inputs:` property map on a kind
18
+ * whose input contract is now `inputType:`.
19
+ * - CONTRACT_TYPE_NOT_FOUND: a contract names a type that is not declared in
20
+ * scope, so every call through it would fail at dispatch.
21
+ *
22
+ * Deliberately NOT diagnosed: an input that is neither `required:` nor
23
+ * defaulted. It is indistinguishable from a genuinely optional one — `Ai.Text`
24
+ * takes `prompt` OR `messages`, and `system` is optional on purpose — so the
25
+ * check fired ~40 times across the standard library on correct manifests with no
26
+ * way for an author to record the intent. A warning that cannot be silenced on
27
+ * correct code teaches people to ignore warnings.
28
+ */
29
+ export declare function validateInvocationContract(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver, aliasesByModule?: Map<string, AliasResolver>): AnalysisDiagnostic[];
30
+ //# sourceMappingURL=validate-invocation-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-invocation-contract.d.ts","sourceRoot":"","sources":["../src/validate-invocation-contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AASnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,GACtD,kBAAkB,EAAE,CA4CtB"}