@telorun/analyzer 0.70.0 → 0.72.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 (122) hide show
  1. package/dist/analysis-registry.d.ts.map +1 -1
  2. package/dist/analysis-registry.js +4 -5
  3. package/dist/analyzer.d.ts.map +1 -1
  4. package/dist/analyzer.js +92 -10
  5. package/dist/call-graph.d.ts.map +1 -1
  6. package/dist/call-graph.js +10 -4
  7. package/dist/catch-scope.d.ts +72 -0
  8. package/dist/catch-scope.d.ts.map +1 -0
  9. package/dist/catch-scope.js +102 -0
  10. package/dist/cel-scope-query.d.ts +14 -0
  11. package/dist/cel-scope-query.d.ts.map +1 -1
  12. package/dist/cel-scope-query.js +36 -6
  13. package/dist/definition-registry.d.ts.map +1 -1
  14. package/dist/definition-registry.js +3 -4
  15. package/dist/deprecation.d.ts +21 -0
  16. package/dist/deprecation.d.ts.map +1 -0
  17. package/dist/deprecation.js +26 -0
  18. package/dist/flatten-for-analyzer.d.ts +2 -2
  19. package/dist/flatten-for-analyzer.d.ts.map +1 -1
  20. package/dist/flatten-for-analyzer.js +1 -1
  21. package/dist/index.d.ts +5 -3
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3 -2
  24. package/dist/manifest-visitor.d.ts +17 -1
  25. package/dist/manifest-visitor.d.ts.map +1 -1
  26. package/dist/manifest-visitor.js +16 -4
  27. package/dist/migrations/report.d.ts +1 -1
  28. package/dist/migrations/report.d.ts.map +1 -1
  29. package/dist/migrations/report.js +5 -0
  30. package/dist/module-alias-scope.d.ts +65 -0
  31. package/dist/module-alias-scope.d.ts.map +1 -0
  32. package/dist/module-alias-scope.js +25 -0
  33. package/dist/{zone-module-documents.d.ts → module-documents.d.ts} +11 -7
  34. package/dist/module-documents.d.ts.map +1 -0
  35. package/dist/ref-sentinel-target.d.ts +38 -0
  36. package/dist/ref-sentinel-target.d.ts.map +1 -0
  37. package/dist/ref-sentinel-target.js +13 -0
  38. package/dist/ref-slot.d.ts +15 -0
  39. package/dist/ref-slot.d.ts.map +1 -1
  40. package/dist/ref-slot.js +7 -0
  41. package/dist/resolve-schema-type-refs.d.ts.map +1 -1
  42. package/dist/resolve-schema-type-refs.js +2 -1
  43. package/dist/resolve-throws-union.d.ts +47 -2
  44. package/dist/resolve-throws-union.d.ts.map +1 -1
  45. package/dist/resolve-throws-union.js +199 -20
  46. package/dist/resolve-zone-requirements.d.ts +3 -3
  47. package/dist/resolve-zone-requirements.d.ts.map +1 -1
  48. package/dist/resolve-zone-requirements.js +12 -12
  49. package/dist/schema-compat.d.ts.map +1 -1
  50. package/dist/schema-compat.js +13 -1
  51. package/dist/schema-error-report.d.ts.map +1 -1
  52. package/dist/schema-error-report.js +48 -4
  53. package/dist/schema-keywords.d.ts.map +1 -1
  54. package/dist/schema-keywords.js +3 -1
  55. package/dist/schema-walk.d.ts +27 -0
  56. package/dist/schema-walk.d.ts.map +1 -1
  57. package/dist/schema-walk.js +44 -0
  58. package/dist/telo-version.d.ts +1 -1
  59. package/dist/telo-version.js +1 -1
  60. package/dist/template-body.d.ts.map +1 -1
  61. package/dist/template-body.js +2 -4
  62. package/dist/types.d.ts +20 -3
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/types.js +11 -0
  65. package/dist/validate-identifier-names.d.ts +2 -2
  66. package/dist/validate-identifier-names.d.ts.map +1 -1
  67. package/dist/validate-identifier-names.js +22 -7
  68. package/dist/validate-invocation-contract.d.ts +5 -0
  69. package/dist/validate-invocation-contract.d.ts.map +1 -1
  70. package/dist/validate-invocation-contract.js +114 -3
  71. package/dist/validate-logging.d.ts.map +1 -1
  72. package/dist/validate-logging.js +2 -2
  73. package/dist/validate-ref-slots.d.ts +1 -1
  74. package/dist/validate-ref-slots.d.ts.map +1 -1
  75. package/dist/validate-ref-slots.js +34 -0
  76. package/dist/validate-references.d.ts +16 -5
  77. package/dist/validate-references.d.ts.map +1 -1
  78. package/dist/validate-references.js +57 -15
  79. package/dist/validate-resource-inputs.d.ts +1 -26
  80. package/dist/validate-resource-inputs.d.ts.map +1 -1
  81. package/dist/validate-resource-inputs.js +12 -2
  82. package/dist/validate-schema-type-refs.d.ts.map +1 -1
  83. package/dist/validate-schema-type-refs.js +2 -1
  84. package/dist/validate-throws-coverage.d.ts +5 -1
  85. package/dist/validate-throws-coverage.d.ts.map +1 -1
  86. package/dist/validate-throws-coverage.js +241 -86
  87. package/package.json +3 -3
  88. package/src/analysis-registry.ts +4 -5
  89. package/src/analyzer.ts +115 -11
  90. package/src/call-graph.ts +9 -3
  91. package/src/catch-scope.ts +157 -0
  92. package/src/cel-scope-query.ts +45 -8
  93. package/src/definition-registry.ts +3 -6
  94. package/src/deprecation.ts +36 -0
  95. package/src/flatten-for-analyzer.ts +3 -3
  96. package/src/index.ts +10 -3
  97. package/src/manifest-visitor.ts +30 -5
  98. package/src/migrations/report.ts +5 -1
  99. package/src/module-alias-scope.ts +94 -0
  100. package/src/{zone-module-documents.ts → module-documents.ts} +10 -6
  101. package/src/ref-sentinel-target.ts +46 -0
  102. package/src/ref-slot.ts +19 -0
  103. package/src/resolve-schema-type-refs.ts +2 -1
  104. package/src/resolve-throws-union.ts +253 -20
  105. package/src/resolve-zone-requirements.ts +14 -14
  106. package/src/schema-compat.ts +13 -0
  107. package/src/schema-error-report.ts +50 -6
  108. package/src/schema-keywords.ts +4 -1
  109. package/src/schema-walk.ts +56 -0
  110. package/src/telo-version.ts +1 -1
  111. package/src/template-body.ts +2 -5
  112. package/src/types.ts +21 -3
  113. package/src/validate-identifier-names.ts +28 -9
  114. package/src/validate-invocation-contract.ts +128 -2
  115. package/src/validate-logging.ts +2 -3
  116. package/src/validate-ref-slots.ts +41 -1
  117. package/src/validate-references.ts +74 -14
  118. package/src/validate-resource-inputs.ts +26 -3
  119. package/src/validate-schema-type-refs.ts +2 -1
  120. package/src/validate-throws-coverage.ts +344 -92
  121. package/dist/zone-module-documents.d.ts.map +0 -1
  122. /package/dist/{zone-module-documents.js → module-documents.js} +0 -0
package/dist/ref-slot.js CHANGED
@@ -166,6 +166,7 @@ export function readRefSlot(node) {
166
166
  const uses = new Set();
167
167
  let useCases;
168
168
  let inputs;
169
+ let throwsThrough = false;
169
170
  for (const carrier of nodes) {
170
171
  const annotation = carrier["x-telo-ref"];
171
172
  for (const kind of readKinds(annotation)) {
@@ -180,6 +181,8 @@ export function readRefSlot(node) {
180
181
  useCases ??= readUseCases(obj.use);
181
182
  if (typeof obj.inputs === "string")
182
183
  inputs ??= obj.inputs;
184
+ if (obj.throwsThrough === true)
185
+ throwsThrough = true;
183
186
  }
184
187
  const slot = {
185
188
  kinds,
@@ -191,6 +194,8 @@ export function readRefSlot(node) {
191
194
  slot.useCases = useCases;
192
195
  if (inputs !== undefined)
193
196
  slot.inputs = inputs;
197
+ if (throwsThrough)
198
+ slot.throwsThrough = true;
194
199
  return slot;
195
200
  }
196
201
  /** True when the node declares a reference slot in any accepted shape. The
@@ -218,6 +223,8 @@ export function refSlotAnnotation(slot) {
218
223
  annotation.use = slot.uses;
219
224
  if (slot.inputs !== undefined)
220
225
  annotation.inputs = slot.inputs;
226
+ if (slot.throwsThrough)
227
+ annotation.throwsThrough = true;
221
228
  return annotation;
222
229
  }
223
230
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-schema-type-refs.d.ts","sourceRoot":"","sources":["../src/resolve-schema-type-refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAKzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,IAAI,CA6DN"}
1
+ {"version":3,"file":"resolve-schema-type-refs.d.ts","sourceRoot":"","sources":["../src/resolve-schema-type-refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAMzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,IAAI,CA6DN"}
@@ -1,4 +1,5 @@
1
1
  import { canonicalTypeSchemaId, parseTeloTypeRef } from "@telorun/sdk";
2
+ import { moduleAliasScope } from "./module-alias-scope.js";
2
3
  /** Schema-bearing fields on a Telo.Definition / Telo.Type resource. */
3
4
  const SCHEMA_FIELDS = ["schema", "inputType", "outputType"];
4
5
  /**
@@ -88,7 +89,7 @@ export function resolveSchemaTypeRefs(resources, aliases, aliasesByModule) {
88
89
  };
89
90
  for (const r of resources) {
90
91
  const ownModule = r.metadata?.module;
91
- const resolver = (ownModule ? aliasesByModule?.get(ownModule) : undefined) ?? aliases;
92
+ const resolver = moduleAliasScope(r.metadata, aliases, aliasesByModule);
92
93
  const resolveAuthority = (authority) => authority === "Self" ? ownModule : resolver?.moduleForAlias(authority);
93
94
  for (const field of SCHEMA_FIELDS) {
94
95
  walk(r[field], resolveAuthority, ownModule, true);
@@ -1,4 +1,4 @@
1
- import type { ResourceManifest } from "@telorun/sdk";
1
+ import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
2
2
  import { type AliasResolver } from "./alias-resolver.js";
3
3
  import type { DefinitionRegistry } from "./definition-registry.js";
4
4
  export interface ThrowsCodeMeta {
@@ -34,14 +34,59 @@ export interface ResolveCtx {
34
34
  aliasesByModule: Map<string, AliasResolver>;
35
35
  /** The consumer/root module names; resources owned by these resolve against `aliases`. */
36
36
  rootModules: Set<string>;
37
+ /**
38
+ * Every imported library's FULL manifest list, keyed by module name.
39
+ *
40
+ * A consumer's flat set holds a library's EXPORTED instances and nothing else,
41
+ * so the siblings an exported entry point invokes are not in it. Without this
42
+ * the walk stops at the first such hop, and the difference is not academic: a
43
+ * library whose entry point raises its own code through an internal guard
44
+ * presented an empty union to its consumer, which then had its `catches:`
45
+ * rejected for the very code the entry point documents.
46
+ *
47
+ * Consulted only as a FALLBACK, after the flat set — the flat set is what the
48
+ * consumer's own resources resolve against, and a library-internal name must
49
+ * never shadow one of them.
50
+ */
51
+ moduleManifests: Map<string, ResourceManifest[]>;
52
+ /** Keyed `<module>\0<name>`: resource names are module-scoped, so two
53
+ * libraries each declaring a `query` are two different unions. */
37
54
  memo: Map<string, ThrowsUnion>;
38
55
  inProgress: Set<string>;
39
56
  }
40
- export declare function createResolveCtx(allManifests: ResourceManifest[], defs: DefinitionRegistry, aliases: AliasResolver, aliasesByModule?: Map<string, AliasResolver>, rootModules?: Set<string>): ResolveCtx;
57
+ export declare function createResolveCtx(allManifests: ResourceManifest[], defs: DefinitionRegistry, aliases: AliasResolver, aliasesByModule?: Map<string, AliasResolver>, rootModules?: Set<string>, moduleManifests?: Map<string, ResourceManifest[]>): ResolveCtx;
41
58
  /** Resolve the effective throw union for a named manifest. The result combines
42
59
  * explicit `throws.codes`, `throws.inherit: true` dataflow (step-context
43
60
  * traversal with try/catch subtraction), and unbounded markers for
44
61
  * unresolvable passthrough call sites. Cycles short-circuit to an empty
45
62
  * result so resolution always terminates. */
46
63
  export declare function resolveThrowsUnion(manifest: ResourceManifest, ctx: ResolveCtx): ThrowsUnion;
64
+ /**
65
+ * The union a SCOPE-LEVEL `catches:` list can be asked to render — everything
66
+ * the resource it is written on drives, transitively.
67
+ *
68
+ * `x-telo-catches-for: ""` is itself the claim that this is the denominator, so
69
+ * nothing is inferred from a kind that did not opt in, and no definition has to
70
+ * declare a `throws:` block to carry a catch scope (the kernel forbids one on a
71
+ * `Telo.Service` and a `Telo.Mount`, and rightly: what a router renders is not
72
+ * what a router THROWS).
73
+ *
74
+ * Three edges, three answers. A **step body** contributes its own traversal,
75
+ * subtraction included. A **control-transferring ref** contributes the target's
76
+ * own declared union — a route handler is a leaf here, and asking what IT drives
77
+ * would credit this scope with codes the handler catches internally. A
78
+ * **`throwsThrough` ref** recurses, because the target is another scope on the
79
+ * same ladder: a server renders what its mounts' routes throw, not what the
80
+ * mounts themselves declare.
81
+ */
82
+ export declare function resolveScopeUnion(manifest: ResourceManifest, definition: ResourceDefinition, ctx: ResolveCtx, seen?: Set<ResourceManifest>): ThrowsUnion;
83
+ /**
84
+ * The manifest a resolved reference value names, in either shape it arrives in —
85
+ * a `{kind, name}` pair, or a `!ref` still carrying its parse-time sentinel.
86
+ *
87
+ * Exported because the catch-scope enclosure walk asks the same question about
88
+ * the same values; resolving a name twice by two rules is how two passes end up
89
+ * disagreeing about which resource a slot points at.
90
+ */
91
+ export declare function resolveRefManifest(refValue: unknown, ctx: ResolveCtx, ownerModule: string | undefined): ResourceManifest | undefined;
47
92
  //# sourceMappingURL=resolve-throws-union.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-throws-union.d.ts","sourceRoot":"","sources":["../src/resolve-throws-union.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAGnE,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC5B;AAED;;gFAEgF;AAChF,eAAO,MAAM,gBAAgB,mBAAmB,CAAC;AAEjD,MAAM,WAAW,WAAW;IAC1B,gFAAgF;IAChF,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACnC;;;8EAG0E;IAC1E,SAAS,EAAE,OAAO,CAAC;IACnB;;;;gDAI4C;IAC5C,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,aAAa,CAAC;IACvB;;;wFAGoF;IACpF,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC5C,0FAA0F;IAC1F,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACzB;AAED,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,gBAAgB,EAAE,EAChC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,EACvD,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,GACnC,UAAU,CAUZ;AA6CD;;;;8CAI8C;AAC9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,gBAAgB,EAC1B,GAAG,EAAE,UAAU,GACd,WAAW,CAiDb"}
1
+ {"version":3,"file":"resolve-throws-union.d.ts","sourceRoot":"","sources":["../src/resolve-throws-union.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAKnE,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC5B;AAED;;gFAEgF;AAChF,eAAO,MAAM,gBAAgB,mBAAmB,CAAC;AAEjD,MAAM,WAAW,WAAW;IAC1B,gFAAgF;IAChF,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACnC;;;8EAG0E;IAC1E,SAAS,EAAE,OAAO,CAAC;IACnB;;;;gDAI4C;IAC5C,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,aAAa,CAAC;IACvB;;;wFAGoF;IACpF,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC5C,0FAA0F;IAC1F,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB;;;;;;;;;;;;;OAaG;IACH,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACjD;uEACmE;IACnE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACzB;AAED,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,gBAAgB,EAAE,EAChC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,EACvD,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,EACpC,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAa,GAC3D,UAAU,CAWZ;AA2HD;;;;8CAI8C;AAC9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,gBAAgB,EAC1B,GAAG,EAAE,UAAU,GACd,WAAW,CAiDb;AAiCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,gBAAgB,EAC1B,UAAU,EAAE,kBAAkB,EAC9B,GAAG,EAAE,UAAU,EACf,IAAI,GAAE,GAAG,CAAC,gBAAgB,CAAa,GACtC,WAAW,CAmCb;AAkMD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,OAAO,EACjB,GAAG,EAAE,UAAU,EACf,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,gBAAgB,GAAG,SAAS,CAsB9B"}
@@ -1,21 +1,93 @@
1
1
  import { isTaggedSentinel } from "@telorun/templating";
2
2
  import { scopeResolverForModule } from "./alias-resolver.js";
3
+ import { resolveScopedName } from "./call-graph.js";
4
+ import { refSentinelTarget } from "./ref-sentinel-target.js";
5
+ import { possibleUses, transfersControl } from "./ref-slot.js";
3
6
  import { readStepSlot } from "./step-slot.js";
7
+ import { forEachDrivenSlot } from "./schema-walk.js";
4
8
  /** Code a non-`InvokeError` failure surfaces as inside a `catch` block. Mirrors
5
9
  * `PLAIN_ERROR_CODE` in `@telorun/run`'s `toSequenceError`: any invoke can throw
6
10
  * a plain error, which the catch sees as `error.code === "INTERNAL_ERROR"`. */
7
11
  export const PLAIN_ERROR_CODE = "INTERNAL_ERROR";
8
- export function createResolveCtx(allManifests, defs, aliases, aliasesByModule = new Map(), rootModules = new Set()) {
12
+ export function createResolveCtx(allManifests, defs, aliases, aliasesByModule = new Map(), rootModules = new Set(), moduleManifests = new Map()) {
9
13
  return {
10
14
  allManifests,
11
15
  defs,
12
16
  aliases,
13
17
  aliasesByModule,
14
18
  rootModules,
19
+ moduleManifests,
15
20
  memo: new Map(),
16
21
  inProgress: new Set(),
17
22
  };
18
23
  }
24
+ /**
25
+ * A dispatch target named by a resolved `{kind, name}` ref.
26
+ *
27
+ * The flat set is the scope such a ref was resolved in, so it is asked first;
28
+ * the owning library's own documents are the fallback for a name the flattened
29
+ * view dropped. `kindMatches` applies to BOTH — one function, one rule, or the
30
+ * next caller inherits whichever half it happened to hit.
31
+ */
32
+ function findTarget(ctx, name, ownerModule, kindMatches) {
33
+ const flat = ctx.allManifests.find((m) => m.metadata?.name === name && kindMatches(m));
34
+ if (flat)
35
+ return flat;
36
+ if (!ownerModule)
37
+ return undefined;
38
+ return ctx.moduleManifests
39
+ .get(ownerModule)
40
+ ?.find((m) => m.metadata?.name === name && kindMatches(m));
41
+ }
42
+ /**
43
+ * The target of a `!ref` that still carries its sentinel — a library-internal
44
+ * reference inside a manifest forwarded into a consumer's flat set, where Phase
45
+ * 2.5 had nothing to resolve it against.
46
+ *
47
+ * **The declaring library is asked FIRST**, and that ordering is the whole rule:
48
+ * a bare name in a library manifest is unambiguously library-internal, so
49
+ * searching the consumer's flat set first let any consumer resource that
50
+ * happened to share the name supply another library's throw union — the same
51
+ * false `{∅}` this branch exists to remove, arrived at from the other side.
52
+ *
53
+ * An ALIAS-qualified source is the cross-module case and is resolved through the
54
+ * declaring module's own alias table, never by dropping the alias and matching
55
+ * the bare name anywhere.
56
+ *
57
+ * Ambiguity resolves to NOTHING rather than to a guess (`resolveScopedName`'s
58
+ * rule), and the caller reads that as unbounded — the safe direction here, since
59
+ * a union that cannot be enumerated must not read as empty.
60
+ */
61
+ function findSentinelTarget(ctx, target, ownerModule) {
62
+ const named = (pool) => (pool ?? []).filter((m) => m.metadata?.name === target.name);
63
+ if (target.alias !== undefined && target.alias !== "Self") {
64
+ // A forwarded export: its module is whatever the DECLARING module aliases
65
+ // that prefix to, and it keeps its export name in the flat set.
66
+ // A root-owned manifest resolves aliases against the global table, which is
67
+ // what `scopeResolverForModule` returns undefined for — so fall back to it
68
+ // rather than reading a root's own alias as unresolvable.
69
+ const resolver = scopeResolverFor(ctx, ownerModule) ?? ctx.aliases;
70
+ const module = resolver.moduleForAlias(target.alias);
71
+ if (!module)
72
+ return undefined;
73
+ return named(ctx.allManifests).find((m) => declaringModuleOf(m) === module);
74
+ }
75
+ const own = named(ctx.moduleManifests.get(ownerModule ?? ""));
76
+ if (own.length === 1)
77
+ return own[0];
78
+ if (own.length > 1)
79
+ return undefined;
80
+ return resolveScopedName(named(ctx.allManifests), declaringModuleOf, ownerModule);
81
+ }
82
+ const declaringModuleOf = (m) => m.metadata?.module;
83
+ /** Memo key. Module-scoped, because resource names are. */
84
+ function memoKey(manifest) {
85
+ const name = manifest.metadata?.name;
86
+ if (!name)
87
+ return undefined;
88
+ const mod = manifest.metadata?.module ?? "";
89
+ return `${mod}\0${name}`;
90
+ }
19
91
  function emptyUnion() {
20
92
  return { codes: new Map(), unbounded: false };
21
93
  }
@@ -60,7 +132,7 @@ function codesFromDefinition(definition) {
60
132
  * unresolvable passthrough call sites. Cycles short-circuit to an empty
61
133
  * result so resolution always terminates. */
62
134
  export function resolveThrowsUnion(manifest, ctx) {
63
- const name = manifest.metadata?.name;
135
+ const name = memoKey(manifest);
64
136
  if (name) {
65
137
  const cached = ctx.memo.get(name);
66
138
  if (cached)
@@ -109,6 +181,16 @@ export function resolveThrowsUnion(manifest, ctx) {
109
181
  ctx.inProgress.delete(name);
110
182
  }
111
183
  }
184
+ /**
185
+ * `throws.inherit: true` — the union a composer's own STEP BODIES reach.
186
+ *
187
+ * Deliberately steps only, and not every slot the resource drives: `inherit` is
188
+ * a DECLARATION that a kind's union is the union of what it dispatches, and a
189
+ * kind that does not make that claim must not have it inferred — a kind holding
190
+ * a `call` ref it catches internally would silently gain codes it never lets
191
+ * escape. What a CATCH SCOPE needs is a different question with a different
192
+ * answer, and it has its own resolver below.
193
+ */
112
194
  function resolveInherited(manifest, definition, ctx, ownerModule) {
113
195
  const result = { codes: new Map(), unbounded: false };
114
196
  const props = definition.schema?.properties;
@@ -125,6 +207,54 @@ function resolveInherited(manifest, definition, ctx, ownerModule) {
125
207
  }
126
208
  return result;
127
209
  }
210
+ /**
211
+ * The union a SCOPE-LEVEL `catches:` list can be asked to render — everything
212
+ * the resource it is written on drives, transitively.
213
+ *
214
+ * `x-telo-catches-for: ""` is itself the claim that this is the denominator, so
215
+ * nothing is inferred from a kind that did not opt in, and no definition has to
216
+ * declare a `throws:` block to carry a catch scope (the kernel forbids one on a
217
+ * `Telo.Service` and a `Telo.Mount`, and rightly: what a router renders is not
218
+ * what a router THROWS).
219
+ *
220
+ * Three edges, three answers. A **step body** contributes its own traversal,
221
+ * subtraction included. A **control-transferring ref** contributes the target's
222
+ * own declared union — a route handler is a leaf here, and asking what IT drives
223
+ * would credit this scope with codes the handler catches internally. A
224
+ * **`throwsThrough` ref** recurses, because the target is another scope on the
225
+ * same ladder: a server renders what its mounts' routes throw, not what the
226
+ * mounts themselves declare.
227
+ */
228
+ export function resolveScopeUnion(manifest, definition, ctx, seen = new Set()) {
229
+ const result = { codes: new Map(), unbounded: false };
230
+ if (seen.has(manifest))
231
+ return result;
232
+ seen.add(manifest);
233
+ const ownerModule = manifest.metadata?.module;
234
+ forEachDrivenSlot(definition.schema, manifest, (driven) => {
235
+ if (driven.kind === "step") {
236
+ unionInto(result, collectStepArrayThrows(driven.data, driven.slot.invoke, undefined, ctx, ownerModule));
237
+ return;
238
+ }
239
+ if (driven.slot.throwsThrough) {
240
+ const target = resolveRefManifest(driven.data, ctx, ownerModule);
241
+ const targetDef = target
242
+ ? definitionFor(target.kind, ctx.defs, ctx.aliases, scopeResolverFor(ctx, target.metadata?.module))
243
+ : undefined;
244
+ if (target && targetDef)
245
+ unionInto(result, resolveScopeUnion(target, targetDef, ctx, seen));
246
+ // A target that cannot be resolved says nothing about what it throws, so
247
+ // the scope's union is no longer enumerable.
248
+ else
249
+ result.unbounded = true;
250
+ return;
251
+ }
252
+ if (!possibleUses(driven.slot).some(transfersControl))
253
+ return;
254
+ unionInto(result, resolveRefTargetThrows(driven.data, ctx, ownerModule));
255
+ });
256
+ return result;
257
+ }
128
258
  function collectStepArrayThrows(steps, invokeField, enclosingTryCodes, ctx, ownerModule) {
129
259
  const result = emptyUnion();
130
260
  for (const step of steps) {
@@ -219,34 +349,56 @@ function cloneUnion(u) {
219
349
  return out;
220
350
  }
221
351
  function resolveStepInvokeThrows(step, invokeField, enclosingTryCodes, ctx, ownerModule) {
222
- const invokeRef = step[invokeField];
223
- if (!invokeRef || typeof invokeRef !== "object")
224
- return emptyUnion();
225
- const invokedKind = invokeRef.kind;
226
- if (!invokedKind)
352
+ return resolveRefTargetThrows(step[invokeField], ctx, ownerModule, () => resolvePassthroughAtCallSite(step, enclosingTryCodes));
353
+ }
354
+ /**
355
+ * The effective throw union behind a resolved reference value.
356
+ *
357
+ * Shared by both ways a resource drives another: a step's `invoke:` and a
358
+ * reference slot that carries throws. The two used to differ only in where the
359
+ * ref value was read from, and keeping one copy is what stops a router's
360
+ * denominator and a sequence's from disagreeing about what a name resolves to.
361
+ *
362
+ * `onPassthrough` is the one genuine difference: a passthrough kind's union is a
363
+ * property of the CALL SITE (`inputs.code`), which only a step has. A reference
364
+ * slot has no such site, so the union is unbounded there rather than guessed.
365
+ */
366
+ function resolveRefTargetThrows(refValue, ctx, ownerModule, onPassthrough) {
367
+ if (!refValue || typeof refValue !== "object" || Array.isArray(refValue))
227
368
  return emptyUnion();
369
+ const ref = refValue;
370
+ const invokedKind = ref.kind;
371
+ // A reference that still carries its parse-time sentinel — a library-internal
372
+ // `!ref` inside a manifest forwarded into a consumer's flat set, where Phase
373
+ // 2.5 had nothing to resolve it against. The target is in the declaring
374
+ // library's own documents, so it is looked up there; only a name that is not
375
+ // there either is UNKNOWN, which is unbounded rather than empty. Reading it as
376
+ // empty is what made a library's exported entry point present a `{∅}` union to
377
+ // its consumer and get the consumer's `catches:` rejected for the code the
378
+ // entry point documents.
379
+ if (!invokedKind) {
380
+ const sentinel = refSentinelTarget(ref);
381
+ if (!sentinel)
382
+ return emptyUnion();
383
+ const target = findSentinelTarget(ctx, sentinel, ownerModule);
384
+ if (target)
385
+ return resolveThrowsUnion(target, ctx);
386
+ return { codes: new Map(), unbounded: true };
387
+ }
228
388
  // The invoked kind's alias resolves in the OWNER manifest's lexical scope (the
229
- // composer that declares the step), so a library's step referencing its own
389
+ // resource that declares the slot), so a library's step referencing its own
230
390
  // import resolves against that library, not the consumer.
231
391
  const scopeResolver = scopeResolverFor(ctx, ownerModule);
232
392
  const definition = definitionFor(invokedKind, ctx.defs, ctx.aliases, scopeResolver);
233
393
  if (!definition)
234
394
  return { codes: new Map(), unbounded: true };
235
395
  if (definition.throws?.passthrough) {
236
- return resolvePassthroughAtCallSite(step, enclosingTryCodes);
396
+ return onPassthrough ? onPassthrough() : { codes: new Map(), unbounded: true };
237
397
  }
238
398
  // Named manifest: resolve the full chain (covers transitive inherit).
239
- const invokeName = invokeRef.name;
240
- if (invokeName) {
241
- const scopedInvokedKind = scopeResolver?.resolveKind(invokedKind);
242
- const target = ctx.allManifests.find((m) => m.metadata?.name === invokeName &&
243
- (m.kind === invokedKind ||
244
- ctx.aliases.resolveKind(m.kind) === invokedKind ||
245
- m.kind === ctx.aliases.resolveKind(invokedKind) ||
246
- (scopedInvokedKind !== undefined && m.kind === scopedInvokedKind)));
247
- if (target)
248
- return resolveThrowsUnion(target, ctx);
249
- }
399
+ const target = resolveRefManifest(ref, ctx, ownerModule);
400
+ if (target)
401
+ return resolveThrowsUnion(target, ctx);
250
402
  // Fall back to the definition's own explicit codes. Mark unbounded when the
251
403
  // definition depends on call-site or transitive resolution we couldn't
252
404
  // perform (no specific target manifest to recurse into).
@@ -254,6 +406,33 @@ function resolveStepInvokeThrows(step, invokeField, enclosingTryCodes, ctx, owne
254
406
  const unbounded = definition.throws?.inherit === true || definition.throws?.passthrough === true;
255
407
  return { codes, unbounded };
256
408
  }
409
+ /**
410
+ * The manifest a resolved reference value names, in either shape it arrives in —
411
+ * a `{kind, name}` pair, or a `!ref` still carrying its parse-time sentinel.
412
+ *
413
+ * Exported because the catch-scope enclosure walk asks the same question about
414
+ * the same values; resolving a name twice by two rules is how two passes end up
415
+ * disagreeing about which resource a slot points at.
416
+ */
417
+ export function resolveRefManifest(refValue, ctx, ownerModule) {
418
+ if (!refValue || typeof refValue !== "object" || Array.isArray(refValue))
419
+ return undefined;
420
+ const ref = refValue;
421
+ const kind = ref.kind;
422
+ if (!kind) {
423
+ const sentinel = refSentinelTarget(ref);
424
+ return sentinel ? findSentinelTarget(ctx, sentinel, ownerModule) : undefined;
425
+ }
426
+ const name = ref.name;
427
+ if (!name)
428
+ return undefined;
429
+ const scopeResolver = scopeResolverFor(ctx, ownerModule);
430
+ const scopedKind = scopeResolver?.resolveKind(kind);
431
+ return findTarget(ctx, name, ownerModule, (m) => m.kind === kind ||
432
+ ctx.aliases.resolveKind(m.kind) === kind ||
433
+ m.kind === ctx.aliases.resolveKind(kind) ||
434
+ (scopedKind !== undefined && m.kind === scopedKind));
435
+ }
257
436
  /** Resolve a passthrough-style invocable at a specific call site. Recognised forms
258
437
  * (see "passthrough: true" in the plan):
259
438
  * - constant literal (no template) → `{ <literal> }`
@@ -17,7 +17,7 @@ import type { ResourceManifest } from "@telorun/sdk";
17
17
  import type { AliasResolver } from "./alias-resolver.js";
18
18
  import { type CallGraph, type ResourceGraphNode } from "./call-graph.js";
19
19
  import type { DefinitionRegistry } from "./definition-registry.js";
20
- import type { ZoneModuleDocuments } from "./zone-module-documents.js";
20
+ import type { ModuleDocuments } from "./module-documents.js";
21
21
  import { type AnalysisDiagnostic } from "./types.js";
22
22
  /** One open requirement on a library's exported resource — the contract an
23
23
  * importer must satisfy. Plain data, so it caches and crosses the
@@ -92,7 +92,7 @@ export declare function zoneDocumentsSignature(manifests: readonly ResourceManif
92
92
  * its full documents (which the flattened analysis view no longer holds), run
93
93
  * the projection derive-only, and keep what reaches an exported instance.
94
94
  */
95
- export declare function deriveLibraryExportRequirements(docs: ZoneModuleDocuments, defs: DefinitionRegistry, aliases: AliasResolver, aliasesByModule: Map<string, AliasResolver>, cache?: ZoneExportCache): ZoneExportRequirements;
95
+ export declare function deriveLibraryExportRequirements(docs: ModuleDocuments, defs: DefinitionRegistry, aliases: AliasResolver, aliasesByModule: Map<string, AliasResolver>, cache?: ZoneExportCache): ZoneExportRequirements;
96
96
  export interface ZoneAnalysisArgs {
97
97
  /** The entry analysis set (post inline-normalization + sentinel resolution). */
98
98
  manifests: ResourceManifest[];
@@ -101,7 +101,7 @@ export interface ZoneAnalysisArgs {
101
101
  aliases: AliasResolver;
102
102
  aliasesByModule: Map<string, AliasResolver>;
103
103
  rootModules: ReadonlySet<string>;
104
- moduleDocuments?: readonly ZoneModuleDocuments[];
104
+ moduleDocuments?: readonly ModuleDocuments[];
105
105
  cache?: ZoneExportCache;
106
106
  }
107
107
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-zone-requirements.d.ts","sourceRoot":"","sources":["../src/resolve-zone-requirements.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolve-zone-requirements.d.ts","sourceRoot":"","sources":["../src/resolve-zone-requirements.ts"],"names":[],"mappings":"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"}
@@ -1,15 +1,16 @@
1
- import { isRefSentinel } from "@telorun/templating";
1
+ import { refSentinelTarget } from "./ref-sentinel-target.js";
2
2
  import { buildCallGraph, } from "./call-graph.js";
3
3
  import { enclosingOf, propertySchemas, resolveLocalRef, } from "./manifest-navigation.js";
4
4
  import { readProvidesZone, readRequiresZone } from "./zone-slot.js";
5
5
  import { DiagnosticSeverity } from "./types.js";
6
+ import { moduleAliasScope } from "./module-alias-scope.js";
6
7
  const SOURCE = "telo-analyzer";
7
8
  /** Resolve a possibly alias-form kind to its definition in the DECLARING
8
9
  * module's scope — the same layering `AnalysisRegistry.resolveDefinitionIn`
9
10
  * uses. */
10
11
  function definitionResolver(defs, aliases, aliasesByModule) {
11
12
  return (kind, module) => {
12
- const scope = (module ? aliasesByModule.get(module) : undefined) ?? aliases;
13
+ const scope = moduleAliasScope({ module }, aliases, aliasesByModule);
13
14
  const canonical = scope.resolveKind(kind);
14
15
  return defs.resolve(kind) ?? (canonical ? defs.resolve(canonical) : undefined);
15
16
  };
@@ -67,18 +68,17 @@ function schemaNodeAt(rootSchema, slotPath) {
67
68
  * under-approximating direction the whole pass leans on. `Self.` is a local
68
69
  * name written the long way and does resolve.
69
70
  *
70
- * This deliberately differs from `call-graph`'s `refTargetName`, which answers a
71
- * different question (what an EDGE points at, cross-module included, for a graph
72
- * whose consumers tolerate an unresolved target) hence two functions rather
73
- * than one shared helper.
71
+ * This is this pass's REDUCTION over the shared `refSentinelTarget` parse, not a
72
+ * second reading of the tag: `call-graph`'s answers a different question (what
73
+ * an EDGE points at, cross-module included, for a graph whose consumers tolerate
74
+ * an unresolved target) and states its own reduction the same way.
74
75
  */
75
76
  function refName(value) {
76
- if (isRefSentinel(value)) {
77
- const source = value.source;
78
- const dot = source.indexOf(".");
79
- if (dot <= 0)
80
- return source;
81
- return source.slice(0, dot) === "Self" ? source.slice(dot + 1) : undefined;
77
+ const sentinel = refSentinelTarget(value);
78
+ if (sentinel) {
79
+ if (sentinel.alias === undefined)
80
+ return sentinel.name;
81
+ return sentinel.alias === "Self" ? sentinel.name : undefined;
82
82
  }
83
83
  if (!value || typeof value !== "object" || Array.isArray(value))
84
84
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAG1E,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAYpD;AAKD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAYD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAC5D,mBAAmB,CAIrB;AAuKD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC9E,YAAY,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAa5D,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CAmB/F;AAED;qFACqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7E;AAED;;;;6DAI6D;AAC7D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAqB,CAAC;AAE1E;;iEAEiE;AACjE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGzF;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CA6BnF;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAkDhG;AAkED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CA+D/E;AAqBD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,QAAQ,CAAC,EAAE,sBAAsB,GAChC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAErB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,QAAQ,CAAC,EAAE,sBAAsB,GAChC;IAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,CAS5D;AAED,iDAAiD;AACjD,MAAM,MAAM,sBAAsB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;AAEtF,gGAAgG;AAChG;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,QAAQ,CAAC,EAAE,sBAAsB,GAChC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAgCrB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAwBlF;AAED;iGACiG;AACjG;;;;;sDAKsD;AACtD,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;iEAQ6D;IAC7D,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;iEAG6D;IAC7D,QAAQ,CAAC,EAAE,sBAAsB,CAAC;CACnC;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAChC,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAuET"}
1
+ {"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import AjvModule from "ajv";
2
2
  import addFormats from "ajv-formats";
3
3
  import { isRefSentinel, isTaggedSentinel, producedTypeOf, } from "@telorun/templating";
4
- import { celBaseOfValueType, celTypeOfValueType, readValueTypeSlot, valueBrandBases, valueTypeOf, valueTypePlaceholder, } from "@telorun/sdk";
4
+ import { celBaseOfValueType, celTypeOfValueType, isCompiledValue, readValueTypeSlot, valueBrandBases, valueTypeOf, valueTypePlaceholder, } from "@telorun/sdk";
5
5
  import { ManifestRootSchema } from "./manifest-schemas.js";
6
6
  import { schemaIssues } from "./schema-error-report.js";
7
7
  import { registerTeloKeywords } from "./value-type-keyword.js";
@@ -709,6 +709,18 @@ export function substituteCelFields(data, schema, rootSchema, options = {}) {
709
709
  mark();
710
710
  return celPlaceholderForSchema(resolved);
711
711
  }
712
+ // The same fact in its third spelling. An expression reaches this walk as a
713
+ // `${{ … }}` string or a `!cel` sentinel BEFORE `precompileDoc`, and as a
714
+ // CompiledValue after — so a caller running under `compile: true` (every
715
+ // `telo run`, unlike `telo check`) handed one to AJV as a plain object, and a
716
+ // slot typed `boolean` rejected a `when:` the author wrote correctly. The
717
+ // kernel's `stripCompiledValues` has always substituted here; missing it on
718
+ // this side made one manifest mean two things depending on which command read
719
+ // it.
720
+ if (isCompiledValue(data)) {
721
+ mark();
722
+ return celPlaceholderForSchema(resolved);
723
+ }
712
724
  if (Array.isArray(data)) {
713
725
  const item = resolveRefIn((resolved.items ?? {}), root, external);
714
726
  return data.map((element, i) => substituteCelFields(element, item.schema, item.root, {
@@ -1 +1 @@
1
- {"version":3,"file":"schema-error-report.d.ts","sourceRoot":"","sources":["../src/schema-error-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;gDACgD;AAChD,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAWD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAe3D;AAkGD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY,EAAE,CAsF5F;AAqHD;;sFAEsF;AACtF,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAaxD;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,EAAE,CAKrF;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAIjF"}
1
+ {"version":3,"file":"schema-error-report.d.ts","sourceRoot":"","sources":["../src/schema-error-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;gDACgD;AAChD,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAuCD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CA6B3D;AAoGD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY,EAAE,CAsF5F;AAqHD;;sFAEsF;AACtF,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAaxD;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,EAAE,CAKrF;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAIjF"}
@@ -32,9 +32,38 @@ const UNION_KEYWORDS = new Set(["anyOf", "oneOf"]);
32
32
  * not of that branch's shape at all — as opposed to being that shape and wrong
33
33
  * further in. These are what make a branch implausible. */
34
34
  const SHAPE_KEYWORDS = new Set(["required", "type", "additionalProperties", "enum", "const"]);
35
+ /** Keywords that exist to DISCRIMINATE, so a mismatch is positive evidence that
36
+ * the value is not this branch — at any depth, not only at the union node.
37
+ *
38
+ * Depth is otherwise the tiebreak, and it inverts exactly here: a branch that
39
+ * agreed on the discriminator and failed one constraint reported at the union
40
+ * node loses to every branch that disagreed about the discriminator one level
41
+ * in. That is how a `capability: Telo.Service` document declaring a forbidden
42
+ * key was reported as `/capability must be equal to constant` — naming neither
43
+ * the key at fault nor a branch the value could ever have been. */
44
+ const DISCRIMINATOR_KEYWORDS = new Set(["const", "enum"]);
45
+ /** RFC 6901 escapes: `~1` is a literal `/` in the key, `~0` a literal `~`.
46
+ * Decoded wherever a segment is shown or matched, because encoded it is not the
47
+ * key the manifest holds — a content map (`application/json`) anchors nowhere
48
+ * and reads wrong in the sentence. `~0` is expanded LAST, or `~01` would decode
49
+ * to `/` instead of the literal `~1` it encodes. */
50
+ function unescapeSegment(part) {
51
+ return part.replace(/~1/g, "/").replace(/~0/g, "~");
52
+ }
53
+ /** The whole pointer, segment by segment, for prose that quotes it verbatim. */
54
+ function unescapePointer(pointer) {
55
+ return pointer
56
+ .split("/")
57
+ .map((segment, i) => (i === 0 ? segment : unescapeSegment(segment)))
58
+ .join("/");
59
+ }
35
60
  /* ------------------------------------------------------------------ prose */
36
61
  export function formatSingleError(err) {
37
- const p = err.instancePath || "/";
62
+ // Unescaped for the same reason the path is: a reader acting on this sentence
63
+ // needs the key the manifest holds (`application/json`), not its RFC 6901
64
+ // encoding. Reporting one form in `path` and the other in `message` made one
65
+ // diagnostic disagree with itself.
66
+ const p = unescapePointer(err.instancePath || "") || "/";
38
67
  const params = err.params ?? {};
39
68
  switch (err.keyword) {
40
69
  case "additionalProperties":
@@ -45,6 +74,16 @@ export function formatSingleError(err) {
45
74
  return `${p} ${err.message ?? "is invalid"} (${params.allowedValues?.join(" | ")})`;
46
75
  case "type":
47
76
  return `${p} must be ${params.type}${describeActual(err)}`;
77
+ // A `false` schema at a property is how a branch forbids a key it otherwise
78
+ // declares. AJV's own text ("boolean schema is false") describes the schema
79
+ // rather than the value, and the path is the only part a reader can act on.
80
+ case "false schema":
81
+ return `${p} is not allowed here`;
82
+ // `not:` says a forbidden shape matched, and AJV reports nothing about the
83
+ // inner schema — so the honest message says where, and no more. A branch
84
+ // that wants to name the key writes `properties: { <key>: false }` instead.
85
+ case "not":
86
+ return `${p} matches a shape that is not allowed here`;
48
87
  default:
49
88
  return `${p} ${err.message ?? "is invalid"}`;
50
89
  }
@@ -109,9 +148,14 @@ function describeAlternatives(errors, unionInstancePath) {
109
148
  return phrases.length > 0 ? phrases : ["another shape"];
110
149
  }
111
150
  /** Is this branch a plausible reading of the value — does it accept the value's
112
- * shape at the union node itself, and only disagree further in? */
151
+ * shape at the union node itself, and agree with every discriminator it pins? */
113
152
  function isPlausible(errors, unionInstancePath) {
114
- return !errors.some((e) => (e.instancePath || "") === unionInstancePath && SHAPE_KEYWORDS.has(e.keyword ?? ""));
153
+ return !errors.some((e) => {
154
+ const keyword = e.keyword ?? "";
155
+ if (DISCRIMINATOR_KEYWORDS.has(keyword))
156
+ return true;
157
+ return (e.instancePath || "") === unionInstancePath && SHAPE_KEYWORDS.has(keyword);
158
+ });
115
159
  }
116
160
  function isUnder(child, parent) {
117
161
  return parent === "" ? child !== "" : child.startsWith(parent + "/");
@@ -332,7 +376,7 @@ export function ajvErrorToPath(err) {
332
376
  if (/^\d+$/.test(part))
333
377
  result += `[${part}]`;
334
378
  else
335
- result += result ? `.${part}` : part;
379
+ result += result ? `.${unescapeSegment(part)}` : unescapeSegment(part);
336
380
  }
337
381
  if (err.keyword === "required" && err.params?.missingProperty) {
338
382
  const missing = err.params.missingProperty;