@telorun/analyzer 0.62.0 → 0.63.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 (146) hide show
  1. package/dist/analysis-registry.d.ts.map +1 -1
  2. package/dist/analysis-registry.js +6 -0
  3. package/dist/analyzer.d.ts +1 -1
  4. package/dist/analyzer.d.ts.map +1 -1
  5. package/dist/analyzer.js +343 -13
  6. package/dist/call-graph.d.ts.map +1 -1
  7. package/dist/call-graph.js +2 -7
  8. package/dist/flatten-for-analyzer.js +1 -1
  9. package/dist/import-resolution-diagnostics.d.ts +9 -2
  10. package/dist/import-resolution-diagnostics.d.ts.map +1 -1
  11. package/dist/import-resolution-diagnostics.js +26 -7
  12. package/dist/index.d.ts +15 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +13 -1
  15. package/dist/invocation-contract.d.ts +6 -1
  16. package/dist/invocation-contract.d.ts.map +1 -1
  17. package/dist/invocation-contract.js +27 -4
  18. package/dist/loaded-types.d.ts +12 -3
  19. package/dist/loaded-types.d.ts.map +1 -1
  20. package/dist/manifest-loader.d.ts +0 -1
  21. package/dist/manifest-loader.d.ts.map +1 -1
  22. package/dist/manifest-loader.js +62 -41
  23. package/dist/manifest-schemas.d.ts +699 -0
  24. package/dist/manifest-schemas.d.ts.map +1 -1
  25. package/dist/manifest-schemas.js +306 -14
  26. package/dist/module-compatibility.d.ts +40 -0
  27. package/dist/module-compatibility.d.ts.map +1 -0
  28. package/dist/module-compatibility.js +52 -0
  29. package/dist/ref-slot-reading.d.ts +30 -0
  30. package/dist/ref-slot-reading.d.ts.map +1 -0
  31. package/dist/ref-slot-reading.js +139 -0
  32. package/dist/referrer-rule.d.ts +67 -0
  33. package/dist/referrer-rule.d.ts.map +1 -0
  34. package/dist/referrer-rule.js +96 -0
  35. package/dist/release/index.d.ts +1 -1
  36. package/dist/release/index.d.ts.map +1 -1
  37. package/dist/release/index.js +1 -1
  38. package/dist/release/payload-digest.d.ts +7 -3
  39. package/dist/release/payload-digest.d.ts.map +1 -1
  40. package/dist/release/payload-digest.js +7 -3
  41. package/dist/release/version-stamp.d.ts +16 -0
  42. package/dist/release/version-stamp.d.ts.map +1 -1
  43. package/dist/release/version-stamp.js +52 -0
  44. package/dist/release/workspace-config.d.ts +10 -6
  45. package/dist/release/workspace-config.d.ts.map +1 -1
  46. package/dist/release/workspace-config.js +10 -6
  47. package/dist/resolve-schema-ref-kinds.d.ts +4 -3
  48. package/dist/resolve-schema-ref-kinds.d.ts.map +1 -1
  49. package/dist/resolve-schema-ref-kinds.js +16 -0
  50. package/dist/resolve-throws-union.d.ts.map +1 -1
  51. package/dist/resolve-throws-union.js +4 -3
  52. package/dist/resolve-zone-containment.d.ts +99 -0
  53. package/dist/resolve-zone-containment.d.ts.map +1 -0
  54. package/dist/resolve-zone-containment.js +212 -0
  55. package/dist/resolve-zone-requirements.d.ts +5 -0
  56. package/dist/resolve-zone-requirements.d.ts.map +1 -1
  57. package/dist/resolve-zone-requirements.js +48 -7
  58. package/dist/resource-rule.d.ts +106 -0
  59. package/dist/resource-rule.d.ts.map +1 -0
  60. package/dist/resource-rule.js +236 -0
  61. package/dist/rule-condition.d.ts +50 -0
  62. package/dist/rule-condition.d.ts.map +1 -0
  63. package/dist/rule-condition.js +108 -0
  64. package/dist/schema-keywords.d.ts.map +1 -1
  65. package/dist/schema-keywords.js +20 -0
  66. package/dist/schema-projection.d.ts +171 -0
  67. package/dist/schema-projection.d.ts.map +1 -0
  68. package/dist/schema-projection.js +317 -0
  69. package/dist/step-slot.d.ts +39 -0
  70. package/dist/step-slot.d.ts.map +1 -0
  71. package/dist/step-slot.js +49 -0
  72. package/dist/telo-version.d.ts +1 -1
  73. package/dist/telo-version.js +1 -1
  74. package/dist/validate-cel-context.d.ts.map +1 -1
  75. package/dist/validate-cel-context.js +17 -3
  76. package/dist/validate-durable-regions.d.ts +15 -0
  77. package/dist/validate-durable-regions.d.ts.map +1 -0
  78. package/dist/validate-durable-regions.js +336 -0
  79. package/dist/validate-references.d.ts.map +1 -1
  80. package/dist/validate-references.js +44 -2
  81. package/dist/validate-referrer-rules.d.ts +120 -0
  82. package/dist/validate-referrer-rules.d.ts.map +1 -0
  83. package/dist/validate-referrer-rules.js +255 -0
  84. package/dist/validate-resource-rules.d.ts +114 -0
  85. package/dist/validate-resource-rules.d.ts.map +1 -0
  86. package/dist/validate-resource-rules.js +283 -0
  87. package/dist/validate-schema-projection.d.ts +32 -0
  88. package/dist/validate-schema-projection.d.ts.map +1 -0
  89. package/dist/validate-schema-projection.js +110 -0
  90. package/dist/validate-step-inputs.d.ts +2 -1
  91. package/dist/validate-step-inputs.d.ts.map +1 -1
  92. package/dist/validate-step-inputs.js +5 -3
  93. package/dist/validate-throws-coverage.d.ts.map +1 -1
  94. package/dist/validate-throws-coverage.js +11 -5
  95. package/dist/validate-zone-slots.d.ts +2 -2
  96. package/dist/validate-zone-slots.d.ts.map +1 -1
  97. package/dist/validate-zone-slots.js +202 -6
  98. package/dist/validate-zone-violations.d.ts +12 -0
  99. package/dist/validate-zone-violations.d.ts.map +1 -0
  100. package/dist/validate-zone-violations.js +169 -0
  101. package/dist/value-type-keyword.d.ts +1 -1
  102. package/dist/value-type-keyword.d.ts.map +1 -1
  103. package/dist/value-type-keyword.js +4 -0
  104. package/dist/zone-slot.d.ts +61 -0
  105. package/dist/zone-slot.d.ts.map +1 -1
  106. package/dist/zone-slot.js +86 -5
  107. package/package.json +2 -2
  108. package/src/analysis-registry.ts +6 -0
  109. package/src/analyzer.ts +395 -14
  110. package/src/call-graph.ts +2 -8
  111. package/src/flatten-for-analyzer.ts +1 -1
  112. package/src/import-resolution-diagnostics.ts +25 -7
  113. package/src/index.ts +73 -0
  114. package/src/invocation-contract.ts +45 -2
  115. package/src/loaded-types.ts +12 -3
  116. package/src/manifest-loader.ts +87 -55
  117. package/src/manifest-schemas.ts +328 -14
  118. package/src/module-compatibility.ts +72 -0
  119. package/src/ref-slot-reading.ts +156 -0
  120. package/src/referrer-rule.ts +116 -0
  121. package/src/release/index.ts +1 -0
  122. package/src/release/payload-digest.ts +7 -3
  123. package/src/release/version-stamp.ts +58 -0
  124. package/src/release/workspace-config.ts +10 -6
  125. package/src/resolve-schema-ref-kinds.ts +28 -4
  126. package/src/resolve-throws-union.ts +4 -3
  127. package/src/resolve-zone-containment.ts +329 -0
  128. package/src/resolve-zone-requirements.ts +67 -7
  129. package/src/resource-rule.ts +252 -0
  130. package/src/rule-condition.ts +126 -0
  131. package/src/schema-keywords.ts +24 -0
  132. package/src/schema-projection.ts +403 -0
  133. package/src/step-slot.ts +64 -0
  134. package/src/telo-version.ts +1 -1
  135. package/src/validate-cel-context.ts +17 -3
  136. package/src/validate-durable-regions.ts +401 -0
  137. package/src/validate-references.ts +57 -2
  138. package/src/validate-referrer-rules.ts +373 -0
  139. package/src/validate-resource-rules.ts +401 -0
  140. package/src/validate-schema-projection.ts +184 -0
  141. package/src/validate-step-inputs.ts +5 -3
  142. package/src/validate-throws-coverage.ts +12 -5
  143. package/src/validate-zone-slots.ts +192 -8
  144. package/src/validate-zone-violations.ts +190 -0
  145. package/src/value-type-keyword.ts +4 -0
  146. package/src/zone-slot.ts +114 -5
@@ -1 +1 @@
1
- {"version":3,"file":"manifest-schemas.d.ts","sourceRoot":"","sources":["../src/manifest-schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD,sEAAsE;AACtE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yEA8ByE;AACzE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoB7B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwD7B,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB;;;;;;CAM/B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8C5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,iBAAiB;;;;;;CAM7B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB;;;;;;CAO5B,CAAC;AAUF;;;WAGW;AACX,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAElE;AAiCD;0DAC0D;AAC1D,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAU9B,CAAC;AAIF;;;0EAG0E;AAC1E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmBtE;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,cAAoB,GAAG,IAAI,CAGrF;AA0HD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAGnF;AA+DD;iEACiE;AACjE,eAAO,MAAM,eAAe,oBAAoB,CAAC;AAEjD;uDACuD;AACvD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAItE"}
1
+ {"version":3,"file":"manifest-schemas.d.ts","sourceRoot":"","sources":["../src/manifest-schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD,sEAAsE;AACtE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yEA8ByE;AACzE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoB7B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwD7B,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB;;;;;;CAM/B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+E5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,iBAAiB;;;;;;CAM7B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB;;;;;;CAO5B,CAAC;AA4BF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+KtB,CAAC;AAeF;;;WAGW;AACX,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAElE;AAiCD;0DAC0D;AAC1D,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAW9B,CAAC;AAiDF;;;0EAG0E;AAE1E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkBtE;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,cAAoB,GAAG,IAAI,CAGrF;AAiID;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAGnF;AA8DD;iEACiE;AACjE,eAAO,MAAM,eAAe,oBAAoB,CAAC;AAEjD;uDACuD;AACvD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAItE"}
@@ -235,6 +235,37 @@ export const InvokeStepSchema = {
235
235
  retry: {
236
236
  title: "Retry",
237
237
  $ref: `${MANIFEST_SCHEMA_URI}#/$defs/RetryPolicy`,
238
+ // A sibling of the shared policy rather than a member of it, the way
239
+ // `Http.Request.retry` adds `honorRetryAfter` — fragment expansion merges
240
+ // siblings, so the policy stays closed and each consumer's surface stays
241
+ // honest. `nonRetryable` matches an ERROR CODE, which is what a step's
242
+ // failures carry; an HTTP retry classifies on a response STATUS and has
243
+ // `retryOn` for it, so putting this on the shared shape would advertise a
244
+ // field there that nothing could read.
245
+ properties: {
246
+ nonRetryable: {
247
+ title: "Non-retryable codes",
248
+ description: "Error codes that end the loop immediately instead of consuming the budget. " +
249
+ "The built-in exclusions are the ones decidable without judgement — a " +
250
+ "cancellation, and the kernel's verdicts on the shape of the call; whether a " +
251
+ "DOMAIN failure is worth re-attempting only the author knows, and without " +
252
+ "this every terminal one is retried to exhaustion — for a non-idempotent " +
253
+ "target, N extra attempts at a side effect.",
254
+ type: "array",
255
+ items: { type: "string" },
256
+ default: [],
257
+ },
258
+ },
259
+ },
260
+ timeout: {
261
+ title: "Timeout",
262
+ description: "How long ONE attempt may take, in milliseconds; on elapse the dispatch is " +
263
+ "cancelled and the step fails ERR_STEP_TIMEOUT. Per attempt rather than for " +
264
+ "the whole retry loop, so what a single call is allowed to take does not " +
265
+ "depend on how slow the earlier attempts were. It belongs to the step rather " +
266
+ "than the target because the target does not know who is waiting.",
267
+ type: "integer",
268
+ minimum: 1,
238
269
  },
239
270
  },
240
271
  };
@@ -291,12 +322,226 @@ export const KindSchemaSchema = {
291
322
  properties: jsonSchemaKeywords(hoistedDefKey("KindSchema")),
292
323
  additionalProperties: true,
293
324
  };
325
+ /** The `error` variable a `catch:` / `finally:` branch binds. One object, used
326
+ * twice with different nullability — `finally` runs on the success path too. */
327
+ const stepErrorProperties = {
328
+ code: {
329
+ description: "Structured error code; INTERNAL_ERROR for plain errors.",
330
+ type: "string",
331
+ },
332
+ message: { description: "Human-readable error message.", type: "string" },
333
+ step: { description: "Name of the step that threw.", type: "string" },
334
+ data: { description: "Structured payload from an InvokeError, else undefined." },
335
+ };
336
+ /** A step list's own items, written as the self-reference the localizer leaves
337
+ * behind. A recursive fragment states its self-references ALREADY localized:
338
+ * `hoistFragmentDef` copies the body verbatim into the consuming schema's
339
+ * `$defs`, so a foreign `telo://manifest#/…` written here would survive into a
340
+ * document the editor's local-only resolver throws on. */
341
+ const stepList = (title, description) => ({
342
+ title,
343
+ description,
344
+ "x-telo-topology-role": "branch",
345
+ type: "array",
346
+ items: { $ref: `#/$defs/${hoistedDefKey("Step")}` },
347
+ });
348
+ /**
349
+ * One step of a step body: a dispatch, a computed value, or a control-flow block.
350
+ *
351
+ * THE GRAMMAR, owned here rather than by `modules/run`. It was declared four
352
+ * times in one file — `$defs` are local to the schema that declares them, so
353
+ * four kinds in one module could not share one, let alone four kinds in four
354
+ * modules — and that is why a kind wanting a body had to take a `!ref` to an
355
+ * executable instead. A fragment is what makes `Sql.Transaction` or a durable
356
+ * `Workflow` able to carry one directly.
357
+ *
358
+ * RECURSIVE (a branch holds steps), so a reference to it is localized and
359
+ * hoisted rather than expanded in place — see {@link localizeRecursiveFragment}.
360
+ *
361
+ * `while/do` is admitted in EVERY body. Three of the four copies dropped it
362
+ * ("the kind is itself the loop"), which is an editorial nudge rather than a
363
+ * soundness rule — a nested `while` inside a for-each body is ordinary control
364
+ * flow the same engine already runs — and a fragment cannot be narrowed by its
365
+ * consumer anyway: draft-07 makes `$ref` exclusive, so a sibling restating a
366
+ * subset would be silently ignored by AJV while still reaching completion. One
367
+ * grammar, or two fragments and the duplication back where it started.
368
+ */
369
+ export const StepSchema = {
370
+ title: "Step",
371
+ description: "Single executable step or control-flow block.",
372
+ type: "object",
373
+ properties: {
374
+ name: { title: "Name", description: "Unique step name.", type: "string" },
375
+ },
376
+ oneOf: [
377
+ // THE dispatch site, shared with an Application's `targets:` rather than
378
+ // restated. `name` is hoisted to the enclosing step object above, which is
379
+ // why this branch carries only what a dispatch itself needs.
380
+ {
381
+ title: "invoke",
382
+ description: "Invokes an invocable or runnable resource.",
383
+ $ref: `${MANIFEST_SCHEMA_URI}#/$defs/InvokeStep`,
384
+ },
385
+ {
386
+ title: "if/then/else",
387
+ description: "Conditional branch; executes then or else based on a boolean expression.",
388
+ properties: {
389
+ if: {
390
+ title: "If",
391
+ description: "CEL boolean expression; true executes then, false executes else.",
392
+ "x-telo-topology-role": "predicate",
393
+ type: "boolean",
394
+ },
395
+ elseif: {
396
+ title: "Else If",
397
+ description: "Additional condition-branch pairs evaluated when if is false.",
398
+ "x-telo-topology-role": "branch-list",
399
+ type: "array",
400
+ items: {
401
+ type: "object",
402
+ properties: {
403
+ if: {
404
+ title: "If",
405
+ description: "CEL boolean expression for this else-if branch.",
406
+ "x-telo-topology-role": "predicate",
407
+ type: "boolean",
408
+ },
409
+ then: stepList("Then", "Steps executed when this else-if condition is true."),
410
+ },
411
+ required: ["if", "then"],
412
+ },
413
+ },
414
+ then: stepList("Then", "Steps executed when if evaluates true."),
415
+ else: stepList("Else", "Steps executed when if (and all elseif conditions) evaluate false."),
416
+ },
417
+ required: ["if", "then"],
418
+ },
419
+ {
420
+ title: "while/do",
421
+ description: "Loop; executes do steps repeatedly while condition is true.",
422
+ properties: {
423
+ while: {
424
+ title: "While",
425
+ description: "CEL boolean expression; evaluated before each iteration.",
426
+ "x-telo-topology-role": "predicate",
427
+ type: "boolean",
428
+ },
429
+ do: stepList("Do", "Steps executed on each iteration."),
430
+ },
431
+ required: ["while", "do"],
432
+ },
433
+ {
434
+ title: "switch/cases/default",
435
+ description: "Multi-branch dispatch; matches a value expression against case keys.",
436
+ properties: {
437
+ switch: {
438
+ title: "Switch",
439
+ description: "CEL expression; result matched against case keys.",
440
+ "x-telo-topology-role": "discriminator",
441
+ type: "string",
442
+ },
443
+ cases: {
444
+ title: "Cases",
445
+ description: "Map of value to step list; executed when switch matches the key.",
446
+ "x-telo-topology-role": "case-map",
447
+ type: "object",
448
+ additionalProperties: {
449
+ type: "array",
450
+ items: { $ref: `#/$defs/${hoistedDefKey("Step")}` },
451
+ },
452
+ },
453
+ default: stepList("Default", "Steps executed when no case matches."),
454
+ },
455
+ required: ["switch", "cases"],
456
+ },
457
+ {
458
+ title: "try/catch/finally",
459
+ description: "Error boundary; executes try steps and handles failure via catch and finally.",
460
+ properties: {
461
+ try: stepList("Try", "Steps executed; halts on first failure and jumps to catch."),
462
+ catch: {
463
+ ...stepList("Catch", "Steps executed when try fails; receives error context."),
464
+ "x-telo-error-context": {
465
+ type: "object",
466
+ description: "The caught failure (always present inside catch).",
467
+ properties: stepErrorProperties,
468
+ additionalProperties: false,
469
+ },
470
+ },
471
+ finally: {
472
+ ...stepList("Finally", "Steps always executed after try/catch regardless of outcome."),
473
+ // error is null on the success path and the caught failure when a
474
+ // try/catch failure propagates. Typed nullable so the analyzer flags
475
+ // unguarded field access (CEL_NULLABLE_ACCESS).
476
+ "x-telo-error-context": {
477
+ type: ["object", "null"],
478
+ description: "The caught failure, or null when try (and catch) succeeded.",
479
+ properties: stepErrorProperties,
480
+ additionalProperties: false,
481
+ },
482
+ },
483
+ },
484
+ required: ["try"],
485
+ },
486
+ {
487
+ title: "throw",
488
+ description: "Throws an InvokeError unconditionally. Works inside catch blocks via CEL " +
489
+ '(e.g. code set to `!cel "error.code"`).',
490
+ properties: {
491
+ throw: {
492
+ title: "Throw",
493
+ description: "InvokeError descriptor; code is required, message/data optional.",
494
+ type: "object",
495
+ properties: {
496
+ code: {
497
+ title: "Code",
498
+ description: "Structured error code; uppercase SNAKE_CASE by convention.",
499
+ type: "string",
500
+ },
501
+ message: {
502
+ title: "Message",
503
+ description: "Human-readable error message. Defaults to `code` when omitted.",
504
+ type: "string",
505
+ },
506
+ data: {
507
+ title: "Data",
508
+ description: "Optional structured payload attached to the error.",
509
+ },
510
+ },
511
+ required: ["code"],
512
+ },
513
+ },
514
+ required: ["throw"],
515
+ },
516
+ {
517
+ title: "value",
518
+ description: "Computes a value and publishes it as `steps.<name>.result`, with no dispatch — " +
519
+ "no resource, no span, no topology node. For an intermediate derived from an " +
520
+ "earlier step: reshape a response, total some rows, build the next call's inputs.",
521
+ properties: {
522
+ value: {
523
+ title: "Value",
524
+ description: "A CEL expression — or a structure (map / array) with `!cel` leaves — " +
525
+ "evaluated in the step scope: `inputs`, the results of the steps before it, " +
526
+ "and whatever the enclosing kind binds (`item` / `index` / `items`, " +
527
+ "`iteration` / `previous`).",
528
+ },
529
+ },
530
+ required: ["value"],
531
+ },
532
+ ],
533
+ required: ["name"],
534
+ };
535
+ /** The fragments that CONTAIN THEMSELVES — a schema's properties hold schemas, a
536
+ * step's branches hold steps. A reference to one is localized and hoisted
537
+ * rather than expanded in place ({@link localizeRecursiveFragment}), because
538
+ * inlining cannot terminate. */
539
+ const RECURSIVE_FRAGMENTS = new Set(["JsonSchema7", "KindSchema", "Step"]);
294
540
  /** The fragments that describe author-written JSON Schema, whichever vocabulary
295
- * they admit. They are also exactly the RECURSIVE ones — a schema is the only
296
- * shape in this set that contains itself so a reference to one is localized
297
- * and hoisted rather than expanded in place ({@link localizeRecursiveFragment});
298
- * should a self-containing fragment that is not a schema ever land, the two
299
- * ideas split and this set stays the one about schemas. */
541
+ * they admit. Split from {@link RECURSIVE_FRAGMENTS} when `Step` landed: being
542
+ * recursive is a fact about how a reference is RESOLVED, being a schema is a
543
+ * fact about which vocabulary a slot admits, and `Step` is the first fragment
544
+ * that is one without the other. */
300
545
  const SCHEMA_FRAGMENTS = new Set(["JsonSchema7", "KindSchema"]);
301
546
  /** True when a slot's `x-telo-fragment` stamp says it holds author-written JSON
302
547
  * Schema — a kind's `schema:`, a `status:` block, a `Telo.JsonSchema`'s own
@@ -344,11 +589,53 @@ export const ManifestRootSchema = {
344
589
  RetryPolicy: RetryPolicySchema,
345
590
  RetryAttempts: RetryAttemptsSchema,
346
591
  InvokeStep: InvokeStepSchema,
592
+ Step: StepSchema,
347
593
  JsonSchema7: JsonSchema7Schema,
348
594
  KindSchema: KindSchemaSchema,
349
595
  },
350
596
  };
351
597
  deepFreeze(ManifestRootSchema);
598
+ /**
599
+ * One expanded template per fragment, cloned per consumer.
600
+ *
601
+ * Expansion is a full walk of the body and the result is identical every time —
602
+ * `Step` alone is a couple of hundred nodes and `modules/run` hoists it four
603
+ * times, on the kernel's boot path. The copy handed out is still fresh, because
604
+ * downstream passes (`resolveSchemaRefKinds`, migrations) rewrite schemas in
605
+ * place.
606
+ */
607
+ const expandedFragments = new Map();
608
+ /**
609
+ * A fresh, fully expanded copy of a fragment body.
610
+ *
611
+ * TARGET-INDEPENDENT, which is what makes caching it correct: the walk is given
612
+ * a scratch hoist target and REFUSED if anything lands in it. A localized copy
613
+ * belongs in the schema a validator compiles, never inside another `$defs`
614
+ * entry, so a body that needed one could not have a single cached form — the
615
+ * first consumer would receive the entry and every later one would silently get
616
+ * a pointer to nothing. Nothing exercises that today: a recursive fragment
617
+ * writes its own self-references already localized, and a cross-reference
618
+ * between two recursive fragments is hoisted at the top level by
619
+ * {@link hoistFragmentDef}'s `pending` loop. If one ever does, this throws at
620
+ * the write instead of resolving by accident.
621
+ */
622
+ function expandedFragment(name) {
623
+ let template = expandedFragments.get(name);
624
+ if (!template) {
625
+ const fragment = ManifestRootSchema.$defs[name];
626
+ template = structuredClone(fragment);
627
+ const scratch = {};
628
+ walkFragments(template, new Set(), { hoistTarget: scratch, depth: 1 });
629
+ if (scratch.$defs !== undefined) {
630
+ throw new Error(`Manifest fragment '${name}' references a recursive fragment from inside its body. ` +
631
+ `Its expanded form is no longer independent of where it is hoisted — localize the ` +
632
+ `reference in the fragment source (write '#/$defs/telo:<Name>' directly) or teach ` +
633
+ `expandedFragment to take the hoist target.`);
634
+ }
635
+ expandedFragments.set(name, template);
636
+ }
637
+ return structuredClone(template);
638
+ }
352
639
  /** A private, expanded copy of a fragment, for a consumer that must EMBED one
353
640
  * rather than `$ref` it — `builtins.ts` is not a manifest and never passes
354
641
  * through the loader, so its dispatch site has to arrive already resolved and
@@ -358,7 +645,7 @@ export function manifestFragment(name) {
358
645
  if (!fragment || typeof fragment !== "object") {
359
646
  throw new Error(`Unknown manifest fragment '${name}'`);
360
647
  }
361
- if (SCHEMA_FRAGMENTS.has(name)) {
648
+ if (RECURSIVE_FRAGMENTS.has(name)) {
362
649
  // A recursive fragment has no expanded form — that is the whole reason it is
363
650
  // localized instead. Embedding one would hand the consumer a body whose
364
651
  // `#/$defs` pointers resolve against nothing.
@@ -366,8 +653,7 @@ export function manifestFragment(name) {
366
653
  `point a slot at it with $ref: "${manifestFragmentRef(name)}" and pass the ` +
367
654
  `enclosing schema through withSchemaFragments().`);
368
655
  }
369
- const copy = structuredClone(fragment);
370
- expandManifestFragments(copy);
656
+ const copy = expandedFragment(name);
371
657
  copy[X_TELO_FRAGMENT] = name;
372
658
  return copy;
373
659
  }
@@ -469,7 +755,7 @@ function localizeRecursiveFragment(value, ctx) {
469
755
  if (typeof ref !== "string" || !ref.startsWith(FRAGMENT_PREFIX))
470
756
  return undefined;
471
757
  const name = ref.slice(FRAGMENT_PREFIX.length);
472
- if (!SCHEMA_FRAGMENTS.has(name))
758
+ if (!RECURSIVE_FRAGMENTS.has(name))
473
759
  return undefined;
474
760
  hoistFragmentDef(ctx.hoistTarget, name);
475
761
  return { ...value, $ref: `#/$defs/${hoistedDefKey(name)}`, [X_TELO_FRAGMENT]: name };
@@ -492,8 +778,15 @@ function hoistFragmentDef(target, name) {
492
778
  // fragment reached through its own self-reference, with no slot in sight.
493
779
  // That node is exactly where an annotation like `x-telo-eval` is written,
494
780
  // so it is where the stamp has to be readable.
495
- defs[key] = { ...structuredClone(fragment), [X_TELO_FRAGMENT]: next };
496
- for (const nested of SCHEMA_FRAGMENTS) {
781
+ // A hoisted body may point at a NON-recursive fragment (`Step`'s dispatch
782
+ // branch is `InvokeStep`), and a copy left unexpanded carries a foreign
783
+ // `telo://manifest#/…` reference into a document the editor's resolver
784
+ // throws on — the same failure gating fragment expansion once caused. Where
785
+ // a nested LOCALIZED copy would belong is not this body; see
786
+ // {@link expandedFragment}, which refuses that case rather than guessing.
787
+ const body = expandedFragment(next);
788
+ defs[key] = { ...body, [X_TELO_FRAGMENT]: next };
789
+ for (const nested of RECURSIVE_FRAGMENTS) {
497
790
  if (nested !== next && referencesLocalDef(defs[key], nested))
498
791
  pending.push(nested);
499
792
  }
@@ -562,10 +855,9 @@ function fragmentFor(value) {
562
855
  const fragment = ManifestRootSchema.$defs[name];
563
856
  if (!fragment || typeof fragment !== "object")
564
857
  return undefined;
565
- const expanded = structuredClone(fragment);
566
858
  // A fragment may reference another (InvokeStep holds a RetryPolicy); the copy
567
- // is expanded too, so one pass leaves no reference behind.
568
- expandManifestFragments(expanded);
859
+ // arrives expanded, so one pass leaves no reference behind.
860
+ const expanded = expandedFragment(name);
569
861
  for (const [key, own] of Object.entries(node)) {
570
862
  if (key === "$ref")
571
863
  continue;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Can this runtime host that module version? One question, one answer, one
3
+ * implementation — the CLI's `upgrade` and every IDE's upgrade affordance all
4
+ * ask it of a candidate version's `telo.yaml` text.
5
+ *
6
+ * It lives beside {@link readRequires} because the `requires:` grammar has
7
+ * exactly one reader by rule, and the moment a second host learned to filter
8
+ * candidates by compatibility that rule needed a shared verdict, not a second
9
+ * parser. Browser-safe: text in, verdict out, no transport and no filesystem —
10
+ * fetching the candidate manifest is the caller's job, since only the caller
11
+ * knows which transport (or cache) addresses it.
12
+ */
13
+ import { type HostVersions } from "./requires-block.js";
14
+ /**
15
+ * How a candidate version answered the compatibility question.
16
+ *
17
+ * `unknown` — the manifest could not be read or names no module document — is
18
+ * never treated as incompatible, since an unreachable registry must not
19
+ * silently freeze a consumer's imports.
20
+ *
21
+ * The two rejecting answers are kept APART because they call for different
22
+ * actions and the user is told which one applies: `too-new` is fixed by
23
+ * upgrading telo, `unreadable` cannot be fixed by the consumer at all.
24
+ * Collapsing them into one "no" and then printing "requires a newer telo" would
25
+ * assert a cause the check never established, and point at a runtime upgrade
26
+ * that will not help.
27
+ */
28
+ export type ModuleCompatibility = "yes" | "too-new" | "unreadable" | "unknown";
29
+ /**
30
+ * Read a module manifest's declared `requires:` and decide whether the runtime
31
+ * described by `teloVersion` / `host` can host it.
32
+ *
33
+ * A module that declares nothing is compatible — the bootstrap rule, permanent
34
+ * for everything published before the mechanism existed. A host that reports no
35
+ * version for an axis skips it rather than guessing, which is exactly the
36
+ * editor case: an IDE is not the machine that will run the manifest, so it
37
+ * speaks for the telo surface and for nothing else.
38
+ */
39
+ export declare function manifestCompatibility(manifestText: string, teloVersion: string | undefined, host?: HostVersions): ModuleCompatibility;
40
+ //# sourceMappingURL=module-compatibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-compatibility.d.ts","sourceRoot":"","sources":["../src/module-compatibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAkC,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExF;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,CAAC;AAE/E;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,IAAI,GAAE,YAAiB,GACtB,mBAAmB,CAuBrB"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Can this runtime host that module version? One question, one answer, one
3
+ * implementation — the CLI's `upgrade` and every IDE's upgrade affordance all
4
+ * ask it of a candidate version's `telo.yaml` text.
5
+ *
6
+ * It lives beside {@link readRequires} because the `requires:` grammar has
7
+ * exactly one reader by rule, and the moment a second host learned to filter
8
+ * candidates by compatibility that rule needed a shared verdict, not a second
9
+ * parser. Browser-safe: text in, verdict out, no transport and no filesystem —
10
+ * fetching the candidate manifest is the caller's job, since only the caller
11
+ * knows which transport (or cache) addresses it.
12
+ */
13
+ import { defaultCustomTags } from "@telorun/templating";
14
+ import { parseAllDocuments } from "yaml";
15
+ import { isModuleKind } from "./module-kinds.js";
16
+ import { evaluateRequires, readRequires } from "./requires-block.js";
17
+ /**
18
+ * Read a module manifest's declared `requires:` and decide whether the runtime
19
+ * described by `teloVersion` / `host` can host it.
20
+ *
21
+ * A module that declares nothing is compatible — the bootstrap rule, permanent
22
+ * for everything published before the mechanism existed. A host that reports no
23
+ * version for an axis skips it rather than guessing, which is exactly the
24
+ * editor case: an IDE is not the machine that will run the manifest, so it
25
+ * speaks for the telo surface and for nothing else.
26
+ */
27
+ export function manifestCompatibility(manifestText, teloVersion, host = {}) {
28
+ let doc;
29
+ try {
30
+ for (const parsed of parseAllDocuments(manifestText, { customTags: defaultCustomTags() })) {
31
+ const value = parsed.toJS();
32
+ const kind = value?.kind;
33
+ if (typeof kind === "string" && isModuleKind(kind)) {
34
+ doc = value;
35
+ break;
36
+ }
37
+ }
38
+ }
39
+ catch {
40
+ return "unknown";
41
+ }
42
+ if (!doc)
43
+ return "unknown";
44
+ const { block, issues } = readRequires(doc);
45
+ // A malformed declaration is not a licence to install: the module claims a
46
+ // requirement it failed to state, and guessing which way it pointed is how a
47
+ // consumer ends up on a version that cannot load. The load gate warns about
48
+ // this same manifest, so the two halves agree.
49
+ if (issues.some((i) => !i.unknownAxis))
50
+ return "unreadable";
51
+ return evaluateRequires(block, teloVersion, host).satisfied ? "yes" : "too-new";
52
+ }
@@ -0,0 +1,30 @@
1
+ export interface RefReadingScope {
2
+ /** The definition a canonical kind names. */
3
+ resolve(kind: string): Record<string, any> | undefined;
4
+ /** Canonicalizes an alias-qualified kind, for a slot whose constraint has not
5
+ * been rewritten yet. */
6
+ resolveKind?(kind: string): string | undefined;
7
+ }
8
+ /**
9
+ * The reading schema for one ref slot, or undefined when the slot's constraint
10
+ * resolves to nothing — in which case the slot is left exactly as it was, the
11
+ * degrade-to-today direction every unresolved annotation takes here.
12
+ *
13
+ * A slot constrained to SEVERAL kinds types `status` from the kinds that agree
14
+ * and drops the rest: a read valid against one permitted target must not be
15
+ * rejected because a sibling kind does not declare that field.
16
+ */
17
+ export declare function refSlotReadingSchema(slot: Record<string, any>, scope: RefReadingScope): Record<string, any> | undefined;
18
+ /**
19
+ * Rewrite every ref slot in a kind's own `schema:` to what reading it yields, so
20
+ * the `self` CEL variable answers about published state rather than about the
21
+ * annotation node.
22
+ *
23
+ * A SCHEMA walk, so it recurses through `properties` / `items` /
24
+ * `additionalProperties` and nothing else — there is no inline-declaration
25
+ * boundary to stop at here, because a `kind` key inside a schema is a property
26
+ * NAMED kind, not a nested resource. Untouched subtrees keep their identity, so
27
+ * a schema declaring no ref slot is returned as it was.
28
+ */
29
+ export declare function withRefSlotsAsReadings(schema: unknown, scope: RefReadingScope): unknown;
30
+ //# sourceMappingURL=ref-slot-reading.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref-slot-reading.d.ts","sourceRoot":"","sources":["../src/ref-slot-reading.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;IACvD;8BAC0B;IAC1B,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAChD;AAMD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,KAAK,EAAE,eAAe,GACrB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAuBjC;AA6BD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,EACf,KAAK,EAAE,eAAe,GACrB,OAAO,CAqBT"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * What a `self.<refSlot>` READS as.
3
+ *
4
+ * A ref slot holds the live instance after Phase-5 injection, and the kernel
5
+ * substitutes each one with its last published reading before evaluating a
6
+ * template body (`celSelfView`) — so `self.table.name` and `resources.users.name`
7
+ * name the same fact. This is the static half of that equivalence: without it a
8
+ * ref slot's schema is whatever the kind wrote at the slot (a title, a
9
+ * description and the annotation), which types nothing at all.
10
+ *
11
+ * A published reading has two halves and they are typed differently, exactly as
12
+ * `resources.<name>` is:
13
+ *
14
+ * - the FLAT half is what `snapshot()` returned, which no manifest declares, so
15
+ * it stays open. Closing it would reject reads that are correct today, and
16
+ * there is nothing to close it against.
17
+ * - `status` is DECLARED (`status:` on the kind, folded along `extends`), so it
18
+ * is typed and a typo below it is `CEL_UNKNOWN_FIELD`.
19
+ *
20
+ * Deliberately no attempt to type the flat half from the kind's own `schema:`:
21
+ * a snapshot is what the controller chose to publish, not the config it was
22
+ * given, and the two coincide only by convention. Typing one as the other would
23
+ * invent errors on a kind that publishes anything else.
24
+ *
25
+ * Browser-safe: no Node built-ins.
26
+ */
27
+ import { effectiveStatusSchema } from "./extends-resolution.js";
28
+ import { isRefSlot, readRefSlot } from "./ref-slot.js";
29
+ function isObject(value) {
30
+ return !!value && typeof value === "object" && !Array.isArray(value);
31
+ }
32
+ /**
33
+ * The reading schema for one ref slot, or undefined when the slot's constraint
34
+ * resolves to nothing — in which case the slot is left exactly as it was, the
35
+ * degrade-to-today direction every unresolved annotation takes here.
36
+ *
37
+ * A slot constrained to SEVERAL kinds types `status` from the kinds that agree
38
+ * and drops the rest: a read valid against one permitted target must not be
39
+ * rejected because a sibling kind does not declare that field.
40
+ */
41
+ export function refSlotReadingSchema(slot, scope) {
42
+ const ref = readRefSlot(slot);
43
+ if (!ref || ref.kinds.length === 0)
44
+ return undefined;
45
+ const statuses = [];
46
+ for (const kind of ref.kinds) {
47
+ const canonical = scope.resolveKind?.(kind) ?? kind;
48
+ const definition = scope.resolve(canonical) ?? scope.resolve(kind);
49
+ if (!definition)
50
+ continue;
51
+ const status = effectiveStatusSchema(definition, (k) => scope.resolve(k));
52
+ if (isObject(status) && isObject(status.properties))
53
+ statuses.push(status);
54
+ }
55
+ if (statuses.length === 0)
56
+ return openReading(slot);
57
+ const shared = statuses.length === 1 ? statuses[0].properties : sharedProperties(statuses);
58
+ return {
59
+ ...describedBy(slot),
60
+ type: "object",
61
+ additionalProperties: true,
62
+ properties: {
63
+ status: { type: "object", additionalProperties: false, properties: shared },
64
+ },
65
+ };
66
+ }
67
+ /** Properties every candidate kind declares. A field only some of them report
68
+ * is not one a read can rely on, and rejecting it would be wrong for the kinds
69
+ * that do — so it degrades to the open half rather than to an error. */
70
+ function sharedProperties(statuses) {
71
+ const [first, ...rest] = statuses;
72
+ const out = {};
73
+ for (const [name, schema] of Object.entries(first.properties)) {
74
+ if (rest.every((other) => name in other.properties)) {
75
+ out[name] = schema;
76
+ }
77
+ }
78
+ return out;
79
+ }
80
+ /** A reading whose `status` cannot be typed: open, so nothing new is rejected. */
81
+ function openReading(slot) {
82
+ return { ...describedBy(slot), type: "object", additionalProperties: true };
83
+ }
84
+ /** The slot's own prose, kept so hover and completion still say what it is. */
85
+ function describedBy(slot) {
86
+ const out = {};
87
+ if (typeof slot.title === "string")
88
+ out.title = slot.title;
89
+ if (typeof slot.description === "string")
90
+ out.description = slot.description;
91
+ return out;
92
+ }
93
+ /**
94
+ * Rewrite every ref slot in a kind's own `schema:` to what reading it yields, so
95
+ * the `self` CEL variable answers about published state rather than about the
96
+ * annotation node.
97
+ *
98
+ * A SCHEMA walk, so it recurses through `properties` / `items` /
99
+ * `additionalProperties` and nothing else — there is no inline-declaration
100
+ * boundary to stop at here, because a `kind` key inside a schema is a property
101
+ * NAMED kind, not a nested resource. Untouched subtrees keep their identity, so
102
+ * a schema declaring no ref slot is returned as it was.
103
+ */
104
+ export function withRefSlotsAsReadings(schema, scope) {
105
+ if (Array.isArray(schema))
106
+ return schema.map((item) => withRefSlotsAsReadings(item, scope));
107
+ if (!isObject(schema))
108
+ return schema;
109
+ if (isRefSlot(schema))
110
+ return refSlotReadingSchema(schema, scope) ?? schema;
111
+ let changed = false;
112
+ const out = { ...schema };
113
+ for (const key of ["properties", "items", "additionalProperties"]) {
114
+ const node = schema[key];
115
+ if (node === undefined)
116
+ continue;
117
+ const next = key === "properties"
118
+ ? mapValues(node, (value) => withRefSlotsAsReadings(value, scope))
119
+ : withRefSlotsAsReadings(node, scope);
120
+ if (next !== node) {
121
+ out[key] = next;
122
+ changed = true;
123
+ }
124
+ }
125
+ return changed ? out : schema;
126
+ }
127
+ function mapValues(node, fn) {
128
+ if (!isObject(node))
129
+ return node;
130
+ let changed = false;
131
+ const out = {};
132
+ for (const [key, value] of Object.entries(node)) {
133
+ const next = fn(value);
134
+ if (next !== value)
135
+ changed = true;
136
+ out[key] = next;
137
+ }
138
+ return changed ? out : node;
139
+ }