@telorun/analyzer 0.64.0 → 0.66.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 (114) hide show
  1. package/dist/analyzer.d.ts +1 -1
  2. package/dist/analyzer.d.ts.map +1 -1
  3. package/dist/analyzer.js +175 -65
  4. package/dist/call-graph.d.ts.map +1 -1
  5. package/dist/call-graph.js +7 -1
  6. package/dist/cel-environment.d.ts +12 -0
  7. package/dist/cel-environment.d.ts.map +1 -1
  8. package/dist/cel-environment.js +35 -19
  9. package/dist/cel-scope.d.ts.map +1 -1
  10. package/dist/cel-scope.js +24 -4
  11. package/dist/definition-registry.d.ts +47 -6
  12. package/dist/definition-registry.d.ts.map +1 -1
  13. package/dist/definition-registry.js +82 -13
  14. package/dist/eval-paths.d.ts +68 -0
  15. package/dist/eval-paths.d.ts.map +1 -1
  16. package/dist/eval-paths.js +138 -0
  17. package/dist/flatten-for-analyzer.d.ts +1 -1
  18. package/dist/flatten-for-analyzer.js +1 -1
  19. package/dist/import-resolution-diagnostics.d.ts +1 -1
  20. package/dist/import-resolution-diagnostics.js +1 -1
  21. package/dist/index.d.ts +13 -9
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +8 -6
  24. package/dist/manifest-visitor.d.ts.map +1 -1
  25. package/dist/manifest-visitor.js +8 -1
  26. package/dist/migrations/entries/index.d.ts.map +1 -1
  27. package/dist/migrations/entries/index.js +2 -0
  28. package/dist/migrations/entries/schema-prepare-bucket.json +27 -0
  29. package/dist/peer-binding.d.ts +232 -0
  30. package/dist/peer-binding.d.ts.map +1 -0
  31. package/dist/peer-binding.js +418 -0
  32. package/dist/ref-slot.d.ts +13 -0
  33. package/dist/ref-slot.d.ts.map +1 -1
  34. package/dist/ref-slot.js +26 -0
  35. package/dist/reference-field-map.d.ts +28 -0
  36. package/dist/reference-field-map.d.ts.map +1 -1
  37. package/dist/reference-field-map.js +25 -0
  38. package/dist/referrer-rule.d.ts +17 -0
  39. package/dist/referrer-rule.d.ts.map +1 -1
  40. package/dist/referrer-rule.js +4 -1
  41. package/dist/release/index.d.ts +1 -1
  42. package/dist/release/index.js +1 -1
  43. package/dist/resource-rule.d.ts +40 -27
  44. package/dist/resource-rule.d.ts.map +1 -1
  45. package/dist/resource-rule.js +61 -7
  46. package/dist/rule-condition.d.ts +11 -0
  47. package/dist/rule-condition.d.ts.map +1 -1
  48. package/dist/rule-condition.js +13 -0
  49. package/dist/schema-compat.d.ts +59 -22
  50. package/dist/schema-compat.d.ts.map +1 -1
  51. package/dist/schema-compat.js +60 -75
  52. package/dist/schema-error-report.d.ts +68 -0
  53. package/dist/schema-error-report.d.ts.map +1 -0
  54. package/dist/schema-error-report.js +356 -0
  55. package/dist/schema-projection.d.ts +106 -4
  56. package/dist/schema-projection.d.ts.map +1 -1
  57. package/dist/schema-projection.js +206 -18
  58. package/dist/telo-version.d.ts +1 -1
  59. package/dist/telo-version.js +1 -1
  60. package/dist/validate-cel-context.d.ts +1 -17
  61. package/dist/validate-cel-context.d.ts.map +1 -1
  62. package/dist/validate-cel-context.js +5 -70
  63. package/dist/validate-nested-inline.d.ts +22 -1
  64. package/dist/validate-nested-inline.d.ts.map +1 -1
  65. package/dist/validate-nested-inline.js +17 -9
  66. package/dist/validate-reference-forms.d.ts +10 -0
  67. package/dist/validate-reference-forms.d.ts.map +1 -1
  68. package/dist/validate-reference-forms.js +23 -0
  69. package/dist/validate-references.d.ts.map +1 -1
  70. package/dist/validate-references.js +9 -2
  71. package/dist/validate-referrer-rules.d.ts +38 -4
  72. package/dist/validate-referrer-rules.d.ts.map +1 -1
  73. package/dist/validate-referrer-rules.js +139 -31
  74. package/dist/validate-resource-rules.d.ts +11 -2
  75. package/dist/validate-resource-rules.d.ts.map +1 -1
  76. package/dist/validate-resource-rules.js +18 -12
  77. package/dist/validate-schema-projection.d.ts.map +1 -1
  78. package/dist/validate-schema-projection.js +33 -2
  79. package/dist/validate-step-inputs.js +11 -3
  80. package/dist/with-synthetic-positions.d.ts +1 -1
  81. package/dist/with-synthetic-positions.js +1 -1
  82. package/package.json +3 -3
  83. package/src/analyzer.ts +211 -68
  84. package/src/call-graph.ts +7 -1
  85. package/src/cel-environment.ts +48 -19
  86. package/src/cel-scope.ts +24 -6
  87. package/src/definition-registry.ts +90 -11
  88. package/src/eval-paths.ts +152 -0
  89. package/src/flatten-for-analyzer.ts +1 -1
  90. package/src/import-resolution-diagnostics.ts +1 -1
  91. package/src/index.ts +55 -6
  92. package/src/manifest-visitor.ts +8 -1
  93. package/src/migrations/entries/index.ts +2 -0
  94. package/src/migrations/entries/schema-prepare-bucket.json +27 -0
  95. package/src/peer-binding.ts +641 -0
  96. package/src/ref-slot.ts +36 -0
  97. package/src/reference-field-map.ts +42 -0
  98. package/src/referrer-rule.ts +20 -1
  99. package/src/release/index.ts +1 -1
  100. package/src/resource-rule.ts +69 -7
  101. package/src/rule-condition.ts +15 -0
  102. package/src/schema-compat.ts +92 -79
  103. package/src/schema-error-report.ts +417 -0
  104. package/src/schema-projection.ts +283 -20
  105. package/src/telo-version.ts +1 -1
  106. package/src/validate-cel-context.ts +6 -71
  107. package/src/validate-nested-inline.ts +35 -14
  108. package/src/validate-reference-forms.ts +22 -0
  109. package/src/validate-references.ts +15 -2
  110. package/src/validate-referrer-rules.ts +215 -28
  111. package/src/validate-resource-rules.ts +26 -12
  112. package/src/validate-schema-projection.ts +50 -1
  113. package/src/validate-step-inputs.ts +10 -4
  114. package/src/with-synthetic-positions.ts +1 -1
@@ -1,26 +1,3 @@
1
- /**
2
- * The single reader for `x-telo-resource-rules` — a kind declaring, as data,
3
- * relationships between the fields of one resource that JSON Schema cannot
4
- * state: an index naming a column its table does not declare, a foreign key
5
- * whose two sides differ in length, a rename whose source is still declared.
6
- *
7
- * The predicate is CEL over `self` (the whole resource) and `this` (the element
8
- * under test), so correlating two collections is a comprehension closure rather
9
- * than a path language with wildcard bindings to design. `in:` names the
10
- * collection to iterate and IS the diagnostic anchor: iterating what the pointer
11
- * names is what makes a reported path exist by construction.
12
- *
13
- * Vocabulary borrowed from `Telo.JsonSchema.rules` — `condition` true when the
14
- * rule HOLDS, the subject bound as `this`, plus `code` and `message` — because
15
- * two CEL rule vocabularies with opposite polarity is a trap an author falls
16
- * into once per rule. The two mechanisms stay separate: a `Telo.JsonSchema` rule
17
- * runs at dispatch against a value, this one at `telo check` against a manifest.
18
- *
19
- * Lenient by design, the `ref-slot.ts` precedent: anything unreadable here reads
20
- * as absent, and `validate-resource-rules.ts` is the strict half that reports it.
21
- *
22
- * Browser-safe: no Node built-ins.
23
- */
24
1
  export declare const RESOURCE_RULES_ANNOTATION = "x-telo-resource-rules";
25
2
  export type ResourceRuleSeverity = "error" | "warning";
26
3
  export interface ResourceRule {
@@ -45,6 +22,22 @@ export interface RuleSubject {
45
22
  /** Present when the collection is a map: the entry's key. */
46
23
  readonly key?: string;
47
24
  }
25
+ /** The engine of a non-CEL tagged sentinel — a `!ref`, an `!include-*` — or
26
+ * `undefined`. A reference is comparable and never blocks a rule; the other
27
+ * tags hold a value only known once the resource is created, so they do, and
28
+ * the diagnostic has to name the tag rather than claim CEL. */
29
+ export declare function deferredTagOf(value: unknown): string | undefined;
30
+ /**
31
+ * True when a condition was written with the `!cel` tag.
32
+ *
33
+ * The readers stay lenient and take a bare string — a rule still runs either
34
+ * way. What an untagged condition loses is everything outside evaluation: to the
35
+ * editor's colouring, completion and hover it is a plain string, so a rule author
36
+ * writes CEL with no help and gets none of the checks a `!cel` scalar gets.
37
+ * Losing that silently is exactly what a strict half exists to move earlier, so
38
+ * the tag is reported by the strict halves and never enforced by the readers.
39
+ */
40
+ export declare function isTaggedCondition(value: unknown): boolean;
48
41
  /** A precompiled `!cel` node keeps its author-written text on `source`; a plain
49
42
  * string is taken verbatim so a rule reads the same however the loader was
50
43
  * configured (a round-trip view runs with `compile` off). */
@@ -73,15 +66,35 @@ export declare function pointerToPath(pointer: string): string;
73
66
  * declaration defect the strict half reports, and an empty list would hide it.
74
67
  */
75
68
  export declare function resolveRuleSubjects(config: unknown, pointer: string): RuleSubject[] | undefined;
69
+ /** The first leaf a rule cannot compare, and what it is. `what` is a noun
70
+ * phrase the diagnostic quotes verbatim, because "a CEL expression" printed
71
+ * over an `!include-bytes` embed sends its author looking for an expression
72
+ * that is not there. */
73
+ export interface DynamicLeaf {
74
+ readonly path: string;
75
+ readonly what: string;
76
+ }
77
+ /** Classify ONE node, without descending. Exported because a caller that draws
78
+ * its own bound on how far to look (`peer-binding`'s top-level-scalar scan)
79
+ * must classify by the same rule as the recursive walk, or a `!ref` is a
80
+ * reference to one of them and an expression to the other. */
81
+ export declare function dynamicNode(value: unknown, path: string): DynamicLeaf | undefined;
76
82
  /**
77
- * Path of the first CEL leaf inside a value, or `undefined` when every leaf is
78
- * literal. A rule reading an expression would be evaluating a placeholder, so
79
- * the subject is skipped — and the skip is reported, never silent.
83
+ * The first leaf inside a value whose contents are not known until the resource
84
+ * is created, or `undefined` when every leaf is literal. A rule reading one
85
+ * would be comparing against a placeholder, so the subject is skipped — and the
86
+ * skip is reported, never silent.
87
+ *
88
+ * A `!ref` is NOT one of them. It is a tagged sentinel like `!cel`, and testing
89
+ * `__tagged` alone read every reference as an expression: a column whose `type:`
90
+ * holds a `!ref` switched off every rule touching `self.columns` and reported a
91
+ * CEL expression in a manifest containing none. A reference names a declaration
92
+ * — a value a rule compares perfectly well, and the one peer rules are built on.
80
93
  *
81
94
  * Stops at nested inline `{ kind }` declarations for the reason every other walk
82
95
  * does: that CEL belongs to the nested kind, evaluated in its own scope.
83
96
  */
84
- export declare function findDynamicLeaf(value: unknown, base?: string): string | undefined;
97
+ export declare function findDynamicLeaf(value: unknown, base?: string): DynamicLeaf | undefined;
85
98
  /**
86
99
  * The nodes a condition actually READS, resolved against the resource and the
87
100
  * element under test — the input to the dynamic-leaf check.
@@ -1 +1 @@
1
- {"version":3,"file":"resource-rule.d.ts","sourceRoot":"","sources":["../src/resource-rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,eAAO,MAAM,yBAAyB,0BAA0B,CAAC;AAEjE,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,SAAS,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B;yEACqE;IACrE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;qEACiE;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,+CAA+C;AAC/C,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAeD;;8DAE8D;AAC9D,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI9D;AAED;;6CAE6C;AAC7C,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAG7D;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,YAAY,EAAE,CA0BjE;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAOrE;AAED;;yEAEyE;AACzE,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAiBvE;AAED;+EAC+E;AAC/E,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMrD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,MAAM,GACd,WAAW,EAAE,GAAG,SAAS,CAe3B;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,SAAK,GAAG,MAAM,GAAG,SAAS,CAiB7E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE;AACtC;;2BAE2B;AAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,EAAE,CAqBX"}
1
+ {"version":3,"file":"resource-rule.d.ts","sourceRoot":"","sources":["../src/resource-rule.ts"],"names":[],"mappings":"AAyBA,eAAO,MAAM,yBAAyB,0BAA0B,CAAC;AAEjE,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,SAAS,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B;yEACqE;IACrE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;qEACiE;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,+CAA+C;AAC/C,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAyBD;;;gEAGgE;AAChE,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAGhE;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEzD;AAED;;8DAE8D;AAC9D,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI9D;AAED;;6CAE6C;AAC7C,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAG7D;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,YAAY,EAAE,CA0BjE;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAOrE;AAED;;yEAEyE;AACzE,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAiBvE;AAED;+EAC+E;AAC/E,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMrD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,MAAM,GACd,WAAW,EAAE,GAAG,SAAS,CAe3B;AAED;;;yBAGyB;AACzB,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;+DAG+D;AAC/D,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAKjF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,SAAK,GAAG,WAAW,GAAG,SAAS,CAkBlF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE;AACtC;;2BAE2B;AAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,EAAE,CAqBX"}
@@ -21,6 +21,7 @@
21
21
  *
22
22
  * Browser-safe: no Node built-ins.
23
23
  */
24
+ import { CEL_ENGINE, isRefSentinel, isTaggedSentinel } from "@telorun/templating";
24
25
  export const RESOURCE_RULES_ANNOTATION = "x-telo-resource-rules";
25
26
  function isObject(value) {
26
27
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -29,9 +30,43 @@ function isObject(value) {
29
30
  * Both markers are tested because they are not always both present: a
30
31
  * registered definition's schema reaches the analyzer with `call` and
31
32
  * `__compiled` dropped, keeping only `__tagged` + `source`. Testing one would
32
- * make a rule readable on some paths and invisible on others. */
33
+ * make a rule readable on some paths and invisible on others.
34
+ *
35
+ * A tagged sentinel of ANOTHER engine is not one. `__tagged` marks every tag
36
+ * the loader parses — `!ref` above all — so testing it alone read a reference
37
+ * as an expression: a column whose `type:` holds a `!ref` skipped every rule
38
+ * that touched `self.columns`, and said "the value holds a CEL expression" about
39
+ * a manifest containing none. A reference names a declaration and is a
40
+ * perfectly comparable value; what a rule cannot compare is a value COMPUTED at
41
+ * create time, which is what this predicate exists to find. */
33
42
  function isCelNode(value) {
34
- return isObject(value) && (value.__compiled === true || value.__tagged === true);
43
+ if (!isObject(value))
44
+ return false;
45
+ if (value.__compiled === true)
46
+ return true;
47
+ return value.__tagged === true && value.engine === CEL_ENGINE;
48
+ }
49
+ /** The engine of a non-CEL tagged sentinel — a `!ref`, an `!include-*` — or
50
+ * `undefined`. A reference is comparable and never blocks a rule; the other
51
+ * tags hold a value only known once the resource is created, so they do, and
52
+ * the diagnostic has to name the tag rather than claim CEL. */
53
+ export function deferredTagOf(value) {
54
+ if (!isTaggedSentinel(value) || isRefSentinel(value))
55
+ return undefined;
56
+ return value.engine === CEL_ENGINE ? undefined : value.engine;
57
+ }
58
+ /**
59
+ * True when a condition was written with the `!cel` tag.
60
+ *
61
+ * The readers stay lenient and take a bare string — a rule still runs either
62
+ * way. What an untagged condition loses is everything outside evaluation: to the
63
+ * editor's colouring, completion and hover it is a plain string, so a rule author
64
+ * writes CEL with no help and gets none of the checks a `!cel` scalar gets.
65
+ * Losing that silently is exactly what a strict half exists to move earlier, so
66
+ * the tag is reported by the strict halves and never enforced by the readers.
67
+ */
68
+ export function isTaggedCondition(value) {
69
+ return isCelNode(value);
35
70
  }
36
71
  /** A precompiled `!cel` node keeps its author-written text on `source`; a plain
37
72
  * string is taken verbatim so a rule reads the same however the loader was
@@ -156,18 +191,37 @@ export function resolveRuleSubjects(config, pointer) {
156
191
  }
157
192
  return undefined;
158
193
  }
194
+ /** Classify ONE node, without descending. Exported because a caller that draws
195
+ * its own bound on how far to look (`peer-binding`'s top-level-scalar scan)
196
+ * must classify by the same rule as the recursive walk, or a `!ref` is a
197
+ * reference to one of them and an expression to the other. */
198
+ export function dynamicNode(value, path) {
199
+ const at = path || "(value)";
200
+ if (isCelNode(value))
201
+ return { path: at, what: "a CEL expression" };
202
+ const tag = deferredTagOf(value);
203
+ return tag ? { path: at, what: `an !${tag} embed` } : undefined;
204
+ }
159
205
  /**
160
- * Path of the first CEL leaf inside a value, or `undefined` when every leaf is
161
- * literal. A rule reading an expression would be evaluating a placeholder, so
162
- * the subject is skipped — and the skip is reported, never silent.
206
+ * The first leaf inside a value whose contents are not known until the resource
207
+ * is created, or `undefined` when every leaf is literal. A rule reading one
208
+ * would be comparing against a placeholder, so the subject is skipped — and the
209
+ * skip is reported, never silent.
210
+ *
211
+ * A `!ref` is NOT one of them. It is a tagged sentinel like `!cel`, and testing
212
+ * `__tagged` alone read every reference as an expression: a column whose `type:`
213
+ * holds a `!ref` switched off every rule touching `self.columns` and reported a
214
+ * CEL expression in a manifest containing none. A reference names a declaration
215
+ * — a value a rule compares perfectly well, and the one peer rules are built on.
163
216
  *
164
217
  * Stops at nested inline `{ kind }` declarations for the reason every other walk
165
218
  * does: that CEL belongs to the nested kind, evaluated in its own scope.
166
219
  */
167
220
  export function findDynamicLeaf(value, base = "") {
168
221
  if (isObject(value)) {
169
- if (isCelNode(value))
170
- return base || "(value)";
222
+ const own = dynamicNode(value, base);
223
+ if (own)
224
+ return own;
171
225
  if (typeof value.kind === "string" && base !== "")
172
226
  return undefined;
173
227
  for (const [key, child] of Object.entries(value)) {
@@ -25,6 +25,17 @@ import { buildCelEnvironment } from "@telorun/templating";
25
25
  * as defective rather than truncating coverage silently.
26
26
  */
27
27
  export declare const RULE_BUDGET_MS = 50;
28
+ /**
29
+ * The one message for an untagged `condition:`, shared by every rule family so
30
+ * all of them say the same thing about the same defect.
31
+ *
32
+ * The readers stay lenient and a bare string still runs. What it loses is
33
+ * everything outside evaluation — to the editor's colouring, completion and
34
+ * hover an untagged condition is a plain string, so its author writes CEL with
35
+ * no help and none of the checks a `!cel` scalar gets. Losing that silently is
36
+ * the failure a strict half exists to move earlier.
37
+ */
38
+ export declare const UNTAGGED_CONDITION: string;
28
39
  /** The analyzer's own environment — no host handlers, so every `hostBacked`
29
40
  * entry is a throwing stub. Built once; it is stateless. */
30
41
  export declare function ruleEnv(): ReturnType<typeof buildCelEnvironment>;
@@ -1 +1 @@
1
- {"version":3,"file":"rule-condition.d.ts","sourceRoot":"","sources":["../src/rule-condition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAEL,mBAAmB,EAGpB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,KAAK,CAAC;AAQjC;6DAC6D;AAC7D,wBAAgB,OAAO,IAAI,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAGhE;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IAAC,MAAM,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAA;CAAE,GAC7F;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAsBvB,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM;AACjB;wEACwE;AACxE,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,YAAY,CAoBd;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAmB7D"}
1
+ {"version":3,"file":"rule-condition.d.ts","sourceRoot":"","sources":["../src/rule-condition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAEL,mBAAmB,EAGpB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,QAGgD,CAAC;AAQhF;6DAC6D;AAC7D,wBAAgB,OAAO,IAAI,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAGhE;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IAAC,MAAM,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAA;CAAE,GAC7F;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAsBvB,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM;AACjB;wEACwE;AACxE,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,YAAY,CAoBd;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAmB7D"}
@@ -25,6 +25,19 @@ import { CEL_FUNCTIONS, buildCelEnvironment, celEngine, extractAccessChains, } f
25
25
  * as defective rather than truncating coverage silently.
26
26
  */
27
27
  export const RULE_BUDGET_MS = 50;
28
+ /**
29
+ * The one message for an untagged `condition:`, shared by every rule family so
30
+ * all of them say the same thing about the same defect.
31
+ *
32
+ * The readers stay lenient and a bare string still runs. What it loses is
33
+ * everything outside evaluation — to the editor's colouring, completion and
34
+ * hover an untagged condition is a plain string, so its author writes CEL with
35
+ * no help and none of the checks a `!cel` scalar gets. Losing that silently is
36
+ * the failure a strict half exists to move earlier.
37
+ */
38
+ export const UNTAGGED_CONDITION = "Write 'condition' with the !cel tag. The reader is lenient and a bare string still " +
39
+ "runs, but untagged the expression is not CEL to the editor's colouring, completion " +
40
+ "or hover, so a rule silently stops being CEL to every surface but this one.";
28
41
  const HOST_BACKED = new Set(CEL_FUNCTIONS.filter((f) => f.hostBacked).map((f) => f.name));
29
42
  const NON_DETERMINISTIC = new Set(CEL_FUNCTIONS.filter((f) => !f.deterministic).map((f) => f.name));
30
43
  let sharedEnv;
@@ -1,3 +1,4 @@
1
+ import { type SchemaIssue } from "./schema-error-report.js";
1
2
  declare const Ajv: any;
2
3
  /** Creates a configured AJV instance (allErrors, strict: false, with formats).
3
4
  * Also registers the kernel manifest root schema under `telo://manifest` so
@@ -35,14 +36,8 @@ export interface CompatibilityResult {
35
36
  * {@link withLiveValuesSkipped} takes one.
36
37
  */
37
38
  export declare function checkSchemaCompatibility(source: Record<string, any>, target: Record<string, any>, resolveRef?: (ref: string) => Record<string, any> | undefined): CompatibilityResult;
38
- export declare function formatSingleError(err: any): string;
39
- export declare function formatAjvErrors(errors: any[] | null | undefined): string;
40
- /** A schema validation issue with a dotted-path pointer to the offending field. */
41
- export interface SchemaIssue {
42
- message: string;
43
- /** Dotted path to the field (e.g. "config.handler"). Empty string means root. */
44
- path: string;
45
- }
39
+ export { formatAjvErrors, formatSingleError } from "./schema-error-report.js";
40
+ export type { SchemaIssue } from "./schema-error-report.js";
46
41
  /** Validate actual data against a JSON Schema. Returns issues with path info, or empty array if valid. */
47
42
  export declare function validateAgainstSchema(data: unknown, schema: Record<string, any>): SchemaIssue[];
48
43
  /** Resolves a JSON Pointer (RFC 6901, must start with "/") into a schema object.
@@ -76,8 +71,36 @@ export declare function jsonSchemaToCelType(schema: Record<string, any> | undefi
76
71
  /** Check whether a CEL return type is compatible with a JSON Schema type constraint. */
77
72
  export declare function celTypeSatisfiesJsonSchema(celType: string, schema: Record<string, any>): boolean;
78
73
  export declare function celPlaceholderForSchema(rawSchema: Record<string, any>): unknown;
79
- /** Resolve a `$ref` (only `#/$defs/...` form) against the root schema. */
80
- export declare function resolveRef(schema: Record<string, any>, root: Record<string, any>): Record<string, any>;
74
+ /**
75
+ * Resolve a `$ref` the document-local `#/$defs/...` form against `root`, and
76
+ * anything else through `external` when a caller supplies one.
77
+ *
78
+ * A named shape is addressed by a registered id (`telo:<module>/<Type>`), which
79
+ * lives in a schema store rather than in this document, so without the hook a
80
+ * walk stops at the reference and treats a described value as undescribed:
81
+ * every CEL leaf under it is handed the schema-unaware `""` placeholder and
82
+ * then rejected against a branch it was never measured against. The caller
83
+ * supplies the store because only the caller has one.
84
+ */
85
+ export declare function resolveRef(schema: Record<string, any>, root: Record<string, any>, external?: ExternalSchemaResolver): Record<string, any>;
86
+ /**
87
+ * {@link resolveRef}, reporting the ROOT the result's own `#/...` references
88
+ * resolve against.
89
+ *
90
+ * Following an external reference enters another document, and a `$ref` inside
91
+ * it is relative to THAT document — which is the whole of how a shape declares
92
+ * its own vocabulary (`anyOf: [{$ref: "#/$defs/Text"}, …]`). Resolving those
93
+ * against the referring document finds nothing, and a walker that then treats
94
+ * the branches as unconstrained accepts every one of them, resolves the union
95
+ * to nothing, and hands the values underneath an untyped stand-in. So the base
96
+ * travels with the schema.
97
+ */
98
+ export declare function resolveRefIn(schema: Record<string, any>, root: Record<string, any>, external?: ExternalSchemaResolver): {
99
+ schema: Record<string, any>;
100
+ root: Record<string, any>;
101
+ };
102
+ /** Looks a registered schema up by its `$id`. */
103
+ export type ExternalSchemaResolver = (ref: string) => Record<string, any> | undefined;
81
104
  /** Collect property schemas from top-level `properties` and all `oneOf`/`anyOf` sub-schemas. */
82
105
  /**
83
106
  * The `oneOf` / `anyOf` branch a value is written against, when exactly one fits.
@@ -95,20 +118,34 @@ export declare function resolveRef(schema: Record<string, any>, root: Record<str
95
118
  * — an ambiguous union is one the analyzer should not resolve on the author's
96
119
  * behalf.
97
120
  */
98
- export declare function selectUnionBranch(schema: Record<string, any>, data: unknown, root: Record<string, any>): Record<string, any>;
121
+ export declare function selectUnionBranch(schema: Record<string, any>, data: unknown, root: Record<string, any>, external?: ExternalSchemaResolver): Record<string, any>;
99
122
  export declare function collectProperties(schema: Record<string, any>): Record<string, any>;
100
123
  /** Deep-clone `data`, replacing every pure CEL template string (`${{ expr }}`) with a
101
124
  * schema-appropriate placeholder so AJV can validate non-CEL fields without false positives. */
102
- export declare function substituteCelFields(data: unknown, schema: Record<string, any>, rootSchema?: Record<string, any>,
103
- /** Called with the dotted path of every value replaced by a placeholder.
125
+ /** Everything {@link substituteCelFields} does beyond walking the value.
104
126
  *
105
- * A placeholder is a stand-in for something only known at runtime, so its
106
- * VALUE says nothing: a caller that judges constraints at these paths reports
107
- * against a value no author wrote. Some constraints cannot be satisfied by
108
- * construction at all (`pattern`, `format`, a `oneOf` of unrelated shapes),
109
- * so making every placeholder acceptable is not achievable in general —
110
- * knowing where not to look is. Structural findings survive because they are
111
- * located at the CONTAINER, not at the substituted leaf. */
112
- onSubstitute?: (path: string) => void, path?: string): unknown;
113
- export {};
127
+ * One object rather than trailing positionals: the resolver is the parameter a
128
+ * caller most needs and was the LAST of six, so reaching it meant counting
129
+ * `undefined`s — and a caller that stopped counting one short simply got the
130
+ * old blind behaviour, silently. Two of them did. */
131
+ export interface SubstituteOptions {
132
+ /** Called with the dotted path of every value replaced by a placeholder.
133
+ *
134
+ * A placeholder is a stand-in for something only known at runtime, so its
135
+ * VALUE says nothing: a caller that judges constraints at these paths reports
136
+ * against a value no author wrote. Some constraints cannot be satisfied by
137
+ * construction at all (`pattern`, `format`, a `oneOf` of unrelated shapes),
138
+ * so making every placeholder acceptable is not achievable in general —
139
+ * knowing where not to look is. Structural findings survive because they are
140
+ * located at the CONTAINER, not at the substituted leaf. */
141
+ onSubstitute?: (path: string) => void;
142
+ /** Dotted path of `data` within the resource, for `onSubstitute`. */
143
+ path?: string;
144
+ /** Resolves a named shape (`telo:<module>/<Type>`) to its schema. Without it
145
+ * a slot described by one reads as undescribed and every CEL leaf beneath it
146
+ * is handed the typeless `""` stand-in — which the shape then rejects, so a
147
+ * perfectly valid expression is reported as a violation. */
148
+ external?: ExternalSchemaResolver;
149
+ }
150
+ export declare function substituteCelFields(data: unknown, schema: Record<string, any>, rootSchema?: Record<string, any>, options?: SubstituteOptions): unknown;
114
151
  //# sourceMappingURL=schema-compat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ import addFormats from "ajv-formats";
3
3
  import { isRefSentinel, isTaggedSentinel, producedTypeOf, } from "@telorun/templating";
4
4
  import { celBaseOfValueType, celTypeOfValueType, readValueTypeSlot, valueBrandBases, valueTypeOf, valueTypePlaceholder, } from "@telorun/sdk";
5
5
  import { ManifestRootSchema } from "./manifest-schemas.js";
6
+ import { schemaIssues } from "./schema-error-report.js";
6
7
  import { registerTeloKeywords } from "./value-type-keyword.js";
7
8
  const Ajv = AjvModule.default ?? AjvModule;
8
9
  /** Creates a configured AJV instance (allErrors, strict: false, with formats).
@@ -193,48 +194,7 @@ function compare(rawSource, rawTarget, path, issues, resolveRef, seen) {
193
194
  }
194
195
  }
195
196
  }
196
- export function formatSingleError(err) {
197
- const p = err.instancePath || "/";
198
- const params = err.params ?? {};
199
- switch (err.keyword) {
200
- case "additionalProperties":
201
- return `${p} must NOT have additional properties ('${params.additionalProperty}' is not allowed)`;
202
- case "required":
203
- return `${p} is missing required property '${params.missingProperty}'`;
204
- case "enum":
205
- return `${p} ${err.message ?? "is invalid"} (${params.allowedValues?.join(" | ")})`;
206
- case "type":
207
- return `${p} must be ${params.type} (got ${typeof err.data})`;
208
- default:
209
- return `${p} ${err.message ?? "is invalid"}`;
210
- }
211
- }
212
- export function formatAjvErrors(errors) {
213
- if (!errors || errors.length === 0)
214
- return "Unknown schema error";
215
- return errors.map(formatSingleError).join("; ");
216
- }
217
- /** Converts an AJV error object to a dotted path string compatible with PositionIndex keys.
218
- * e.g. instancePath "/config/routes/0/handler" → "config.routes[0].handler"
219
- * For "required" keyword errors, appends the missing property to the parent path. */
220
- function ajvErrorToPath(err) {
221
- const instancePath = (err.instancePath ?? "");
222
- const parts = instancePath.split("/").filter((p) => p !== "");
223
- let result = "";
224
- for (const part of parts) {
225
- if (/^\d+$/.test(part)) {
226
- result += `[${part}]`;
227
- }
228
- else {
229
- result += result ? `.${part}` : part;
230
- }
231
- }
232
- if (err.keyword === "required" && err.params?.missingProperty) {
233
- const missing = err.params.missingProperty;
234
- result += result ? `.${missing}` : missing;
235
- }
236
- return result;
237
- }
197
+ export { formatAjvErrors, formatSingleError } from "./schema-error-report.js";
238
198
  /** Does `schema` compile as-authored? Used to tell a malformed module schema
239
199
  * (the author's problem) apart from a fault we introduced while normalizing it. */
240
200
  function schemaCompiles(schema) {
@@ -268,10 +228,7 @@ export function validateAgainstSchema(data, schema) {
268
228
  }
269
229
  if (validate(data))
270
230
  return [];
271
- return (validate.errors ?? []).map((err) => ({
272
- message: formatSingleError(err),
273
- path: ajvErrorToPath(err),
274
- }));
231
+ return schemaIssues(validate.errors);
275
232
  }
276
233
  /** Resolves a JSON Pointer (RFC 6901, must start with "/") into a schema object.
277
234
  * Returns undefined when any segment along the path is missing or not an object. */
@@ -590,15 +547,43 @@ function objectPlaceholder(schema) {
590
547
  return out;
591
548
  }
592
549
  const CEL_PURE_RE = /^\s*\$\{\{[^}]*\}\}\s*$/;
593
- /** Resolve a `$ref` (only `#/$defs/...` form) against the root schema. */
594
- export function resolveRef(schema, root) {
595
- if (schema.$ref && typeof schema.$ref === "string" && schema.$ref.startsWith("#/$defs/")) {
596
- const defName = schema.$ref.slice("#/$defs/".length);
597
- const resolved = root.$defs?.[defName];
598
- if (resolved)
599
- return resolved;
600
- }
601
- return schema;
550
+ /**
551
+ * Resolve a `$ref` — the document-local `#/$defs/...` form against `root`, and
552
+ * anything else through `external` when a caller supplies one.
553
+ *
554
+ * A named shape is addressed by a registered id (`telo:<module>/<Type>`), which
555
+ * lives in a schema store rather than in this document, so without the hook a
556
+ * walk stops at the reference and treats a described value as undescribed:
557
+ * every CEL leaf under it is handed the schema-unaware `""` placeholder and
558
+ * then rejected against a branch it was never measured against. The caller
559
+ * supplies the store because only the caller has one.
560
+ */
561
+ export function resolveRef(schema, root, external) {
562
+ return resolveRefIn(schema, root, external).schema;
563
+ }
564
+ /**
565
+ * {@link resolveRef}, reporting the ROOT the result's own `#/...` references
566
+ * resolve against.
567
+ *
568
+ * Following an external reference enters another document, and a `$ref` inside
569
+ * it is relative to THAT document — which is the whole of how a shape declares
570
+ * its own vocabulary (`anyOf: [{$ref: "#/$defs/Text"}, …]`). Resolving those
571
+ * against the referring document finds nothing, and a walker that then treats
572
+ * the branches as unconstrained accepts every one of them, resolves the union
573
+ * to nothing, and hands the values underneath an untyped stand-in. So the base
574
+ * travels with the schema.
575
+ */
576
+ export function resolveRefIn(schema, root, external) {
577
+ if (!schema.$ref || typeof schema.$ref !== "string")
578
+ return { schema, root };
579
+ if (schema.$ref === "#")
580
+ return { schema: root, root };
581
+ if (schema.$ref.startsWith("#/$defs/")) {
582
+ const resolved = root.$defs?.[schema.$ref.slice("#/$defs/".length)];
583
+ return resolved ? { schema: resolved, root } : { schema, root };
584
+ }
585
+ const target = external?.(schema.$ref);
586
+ return target ? { schema: target, root: target } : { schema, root };
602
587
  }
603
588
  /** Collect property schemas from top-level `properties` and all `oneOf`/`anyOf` sub-schemas. */
604
589
  /**
@@ -617,7 +602,7 @@ export function resolveRef(schema, root) {
617
602
  * — an ambiguous union is one the analyzer should not resolve on the author's
618
603
  * behalf.
619
604
  */
620
- export function selectUnionBranch(schema, data, root) {
605
+ export function selectUnionBranch(schema, data, root, external) {
621
606
  const branches = (schema.oneOf ?? schema.anyOf);
622
607
  if (!Array.isArray(branches) || branches.length === 0)
623
608
  return schema;
@@ -639,7 +624,7 @@ export function selectUnionBranch(schema, data, root) {
639
624
  if (!kind)
640
625
  return schema;
641
626
  const fits = branches
642
- .map((b) => resolveRef(b, root))
627
+ .map((b) => resolveRef(b, root, external))
643
628
  .filter((b) => {
644
629
  const types = Array.isArray(b.type) ? b.type : b.type ? [b.type] : [];
645
630
  if (types.length > 0 && !types.includes(kind))
@@ -680,21 +665,13 @@ export function collectProperties(schema) {
680
665
  }
681
666
  return props;
682
667
  }
683
- /** Deep-clone `data`, replacing every pure CEL template string (`${{ expr }}`) with a
684
- * schema-appropriate placeholder so AJV can validate non-CEL fields without false positives. */
685
- export function substituteCelFields(data, schema, rootSchema,
686
- /** Called with the dotted path of every value replaced by a placeholder.
687
- *
688
- * A placeholder is a stand-in for something only known at runtime, so its
689
- * VALUE says nothing: a caller that judges constraints at these paths reports
690
- * against a value no author wrote. Some constraints cannot be satisfied by
691
- * construction at all (`pattern`, `format`, a `oneOf` of unrelated shapes),
692
- * so making every placeholder acceptable is not achievable in general —
693
- * knowing where not to look is. Structural findings survive because they are
694
- * located at the CONTAINER, not at the substituted leaf. */
695
- onSubstitute, path = "") {
696
- const root = rootSchema ?? schema;
697
- const resolved = selectUnionBranch(resolveRef(schema, root), data, root);
668
+ export function substituteCelFields(data, schema, rootSchema, options = {}) {
669
+ const { onSubstitute, external } = options;
670
+ const path = options.path ?? "";
671
+ const base = rootSchema ?? schema;
672
+ const entered = resolveRefIn(schema, base, external);
673
+ const root = entered.root;
674
+ const resolved = selectUnionBranch(entered.schema, data, root, external);
698
675
  const mark = () => onSubstitute?.(path);
699
676
  if (typeof data === "string" && CEL_PURE_RE.test(data)) {
700
677
  mark();
@@ -733,8 +710,12 @@ onSubstitute, path = "") {
733
710
  return celPlaceholderForSchema(resolved);
734
711
  }
735
712
  if (Array.isArray(data)) {
736
- const itemSchema = resolveRef((resolved.items ?? {}), root);
737
- return data.map((item, i) => substituteCelFields(item, itemSchema, root, onSubstitute, `${path}[${i}]`));
713
+ const item = resolveRefIn((resolved.items ?? {}), root, external);
714
+ return data.map((element, i) => substituteCelFields(element, item.schema, item.root, {
715
+ onSubstitute,
716
+ path: `${path}[${i}]`,
717
+ external,
718
+ }));
738
719
  }
739
720
  if (data !== null && typeof data === "object") {
740
721
  const props = collectProperties(resolved);
@@ -743,7 +724,11 @@ onSubstitute, path = "") {
743
724
  : undefined;
744
725
  const result = {};
745
726
  for (const [k, v] of Object.entries(data)) {
746
- result[k] = substituteCelFields(v, (props[k] ?? addlProps ?? {}), root, onSubstitute, path ? `${path}.${k}` : k);
727
+ result[k] = substituteCelFields(v, (props[k] ?? addlProps ?? {}), root, {
728
+ onSubstitute,
729
+ path: path ? `${path}.${k}` : k,
730
+ external,
731
+ });
747
732
  }
748
733
  return result;
749
734
  }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The one renderer for AJV validation failures.
3
+ *
4
+ * Browser-safe and re-imported by the kernel — the split `buildEvalPaths` and
5
+ * the redaction path parser already use — so a failure is phrased identically
6
+ * under `telo check` and at runtime. Three implementations used to answer this
7
+ * (the analyzer's keyword prose, the kernel's raw `instancePath + message`
8
+ * join, and observed state's own inline variant), so a developer who fixed what
9
+ * the analyzer told them met a different sentence describing the same thing.
10
+ *
11
+ * UNION REDUCTION is the second half. A union must attempt every branch, and
12
+ * AJV cannot know which one was intended — `discriminator: true` works only
13
+ * against an explicit OpenAPI-style discriminator property, which would mean
14
+ * changing what every module's authors write. So branch selection is a
15
+ * reporting concern and lives here.
16
+ *
17
+ * It narrows the error SET, never just the sentence: every consumer maps the
18
+ * surviving errors to manifest paths to anchor a diagnostic, so reducing at the
19
+ * prose layer alone would move the soup out of the message and into the
20
+ * problems list, one entry per branch on a different line.
21
+ *
22
+ * Selection is made from the ERRORS ALONE, never from the schema. A branch
23
+ * whose discriminating key is present emits no complaint at the union's own
24
+ * instancePath; one whose key is absent says `required`, and one that forbids a
25
+ * key the value carries says `additionalProperties`. That is the whole signal,
26
+ * and reading it off the errors is what lets reduction work across a `$ref`
27
+ * into another registered schema, where navigating to the branch subschema
28
+ * would mean re-implementing AJV's resolution.
29
+ */
30
+ /** An AJV error object. Structurally typed — the analyzer and the kernel hand
31
+ * over errors from their own AJV instances. */
32
+ export interface AjvErrorLike {
33
+ keyword?: string;
34
+ instancePath?: string;
35
+ schemaPath?: string;
36
+ message?: string;
37
+ params?: Record<string, any>;
38
+ data?: unknown;
39
+ }
40
+ /** A schema validation issue with a dotted-path pointer to the offending field. */
41
+ export interface SchemaIssue {
42
+ message: string;
43
+ /** Dotted path to the field (e.g. "config.handler"). Empty string means root. */
44
+ path: string;
45
+ }
46
+ export declare function formatSingleError(err: AjvErrorLike): string;
47
+ /**
48
+ * Replace each failing union with the errors of the branch the author plainly
49
+ * meant, recursively, outside in.
50
+ *
51
+ * Attribution runs to the DEEPEST occurrence that could own an error, which is
52
+ * what keeps a container's own complaint apart from its child's when both carry
53
+ * the same `schemaPath`. An occurrence reached through a branch becomes a
54
+ * candidate branch of its own — it raised nothing at the parent's node, so it is
55
+ * plausible exactly when the value really did take that shape and fail further
56
+ * in, and reducing it recursively is what stops an inner union's alternatives
57
+ * from surviving inside the outer one's selection.
58
+ */
59
+ export declare function reduceSchemaErrors(errors: AjvErrorLike[] | null | undefined): AjvErrorLike[];
60
+ /** Converts an AJV error to a dotted path compatible with PositionIndex keys.
61
+ * e.g. instancePath "/config/routes/0/handler" → "config.routes[0].handler"
62
+ * For "required" keyword errors, appends the missing property to the parent path. */
63
+ export declare function ajvErrorToPath(err: AjvErrorLike): string;
64
+ /** Reduced, path-anchored issues — what a diagnostic list is built from. */
65
+ export declare function schemaIssues(errors: AjvErrorLike[] | null | undefined): SchemaIssue[];
66
+ /** Reduced, rendered as one sentence — what a thrown runtime error carries. */
67
+ export declare function formatAjvErrors(errors: AjvErrorLike[] | null | undefined): string;
68
+ //# sourceMappingURL=schema-error-report.d.ts.map