@clementine-solutions/jane-io 1.0.1 → 1.0.2

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 (204) hide show
  1. package/dist/core/analysis/diff.js +88 -0
  2. package/dist/core/analysis/explain.js +117 -0
  3. package/dist/core/analysis/index.js +26 -0
  4. package/dist/core/analysis/replay.js +68 -0
  5. package/dist/core/analysis/telemetry.js +123 -0
  6. package/dist/core/boundary-rules/at-most-one.js +38 -0
  7. package/dist/core/boundary-rules/conditionally-required.js +44 -0
  8. package/dist/core/boundary-rules/date-range.js +39 -0
  9. package/dist/core/boundary-rules/index.js +21 -0
  10. package/dist/core/boundary-rules/mutually-exclusive.js +38 -0
  11. package/dist/core/boundary-rules/no-unknown-fields.js +39 -0
  12. package/dist/core/boundary-rules/require-all.js +39 -0
  13. package/dist/core/boundary-rules/require-one.js +41 -0
  14. package/dist/core/common/events.js +82 -0
  15. package/dist/core/common/fluent.js +429 -0
  16. package/dist/core/common/index.js +31 -0
  17. package/dist/core/common/policy.js +550 -0
  18. package/dist/core/common/utilities.js +177 -0
  19. package/dist/core/common/wildcard.js +63 -0
  20. package/dist/core/field-path/construct.js +189 -0
  21. package/dist/core/field-path/format.js +154 -0
  22. package/dist/core/field-path/index.js +26 -0
  23. package/dist/core/field-path/utilities.js +138 -0
  24. package/dist/core/field-path/walk.js +80 -0
  25. package/dist/core/fluent-registry.js +151 -0
  26. package/dist/core/normalizers/array/compact-sparse-array.js +40 -0
  27. package/dist/core/normalizers/array/flatten-one-level.js +45 -0
  28. package/dist/core/normalizers/array/remove-empty-string-items.js +34 -0
  29. package/dist/core/normalizers/array/remove-null-items.js +34 -0
  30. package/dist/core/normalizers/array/remove-undefined-items.js +34 -0
  31. package/dist/core/normalizers/date/invalid-date-to-undefined.js +35 -0
  32. package/dist/core/normalizers/index.js +46 -0
  33. package/dist/core/normalizers/normalizer-register.js +41 -0
  34. package/dist/core/normalizers/number/infinity-to-undefined.js +35 -0
  35. package/dist/core/normalizers/number/nan-to-undefined.js +34 -0
  36. package/dist/core/normalizers/number/normalize-negative-zero.js +33 -0
  37. package/dist/core/normalizers/object/remove-empty-array-keys.js +38 -0
  38. package/dist/core/normalizers/object/remove-empty-object-keys.js +42 -0
  39. package/dist/core/normalizers/object/remove-empty-string-keys.js +37 -0
  40. package/dist/core/normalizers/object/remove-null-keys.js +37 -0
  41. package/dist/core/normalizers/object/remove-undefined-keys.js +37 -0
  42. package/dist/core/normalizers/string/collapse-whitespace.js +35 -0
  43. package/dist/core/normalizers/string/empty-to-undefined.js +33 -0
  44. package/dist/core/normalizers/string/trim.js +34 -0
  45. package/dist/core/parsers/index.js +43 -0
  46. package/dist/core/parsers/parse-array-string.js +36 -0
  47. package/dist/core/parsers/parse-bigint-string.js +33 -0
  48. package/dist/core/parsers/parse-binary-string.js +33 -0
  49. package/dist/core/parsers/parse-boolean-string.js +27 -0
  50. package/dist/core/parsers/parse-date-string.js +30 -0
  51. package/dist/core/parsers/parse-duration-string.js +37 -0
  52. package/dist/core/parsers/parse-hex-string.js +29 -0
  53. package/dist/core/parsers/parse-integer-string.js +30 -0
  54. package/dist/core/parsers/parse-json-string.js +35 -0
  55. package/dist/core/parsers/parse-numeric-string.js +29 -0
  56. package/dist/core/parsers/parse-object-string.js +36 -0
  57. package/dist/core/parsers/parse-octal-string.js +33 -0
  58. package/dist/core/parsers/parse-scientific-notation-string.js +30 -0
  59. package/dist/core/parsers/parse-url-string.js +36 -0
  60. package/dist/core/pipeline/boundary.js +256 -0
  61. package/dist/core/pipeline/contain.js +339 -0
  62. package/dist/core/pipeline/index.js +37 -0
  63. package/dist/core/pipeline/normalize.js +76 -0
  64. package/dist/core/pipeline/parse.js +91 -0
  65. package/dist/core/pipeline/pipeline.js +418 -0
  66. package/dist/core/pipeline/scan.js +115 -0
  67. package/dist/core/pipeline/validate.js +74 -0
  68. package/dist/core/scanners/any/scan-for-sentinels.js +35 -0
  69. package/dist/core/scanners/array/array-is-deep.js +38 -0
  70. package/dist/core/scanners/array/array-is-heterogenous.js +39 -0
  71. package/dist/core/scanners/array/array-is-large.js +32 -0
  72. package/dist/core/scanners/bigint/bigint-is-large.js +34 -0
  73. package/dist/core/scanners/bigint/bigint-not-safe.js +34 -0
  74. package/dist/core/scanners/date/date-is-before-epoch.js +32 -0
  75. package/dist/core/scanners/date/date-is-far-future.js +32 -0
  76. package/dist/core/scanners/date/date-is-invalid.js +31 -0
  77. package/dist/core/scanners/index.js +58 -0
  78. package/dist/core/scanners/number/number-is-infinite.js +31 -0
  79. package/dist/core/scanners/number/number-is-nan.js +31 -0
  80. package/dist/core/scanners/number/number-is-too-large.js +33 -0
  81. package/dist/core/scanners/number/number-is-unsafe-integer.js +31 -0
  82. package/dist/core/scanners/object/object-has-circular-references.js +43 -0
  83. package/dist/core/scanners/object/object-has-many-keys.js +33 -0
  84. package/dist/core/scanners/object/object-is-deep.js +38 -0
  85. package/dist/core/scanners/scanner-registry.js +36 -0
  86. package/dist/core/scanners/string/string-has-unsafe-unicode.js +32 -0
  87. package/dist/core/scanners/string/string-has-whitespace-edges.js +31 -0
  88. package/dist/core/scanners/string/string-is-long.js +32 -0
  89. package/dist/core/scanners/unknown/unknown-not-scannable.js +34 -0
  90. package/dist/core/shapes/analysis.js +11 -0
  91. package/dist/core/shapes/boundary.js +11 -0
  92. package/dist/core/shapes/events.js +10 -0
  93. package/dist/core/shapes/field-path.js +10 -0
  94. package/dist/core/shapes/index.js +11 -0
  95. package/dist/core/shapes/normalize.js +11 -0
  96. package/dist/core/shapes/parse.js +11 -0
  97. package/dist/core/shapes/pipeline.js +11 -0
  98. package/dist/core/shapes/policy.js +11 -0
  99. package/dist/core/shapes/public.js +10 -0
  100. package/dist/core/shapes/scan.js +11 -0
  101. package/dist/core/shapes/validate.js +11 -0
  102. package/dist/core/validators/array/array-max-items.js +42 -0
  103. package/dist/core/validators/array/array-min-items.js +42 -0
  104. package/dist/core/validators/array/array.js +34 -0
  105. package/dist/core/validators/array/excludes.js +47 -0
  106. package/dist/core/validators/array/has-unique-items.js +46 -0
  107. package/dist/core/validators/array/includes.js +46 -0
  108. package/dist/core/validators/array/items-equal.js +42 -0
  109. package/dist/core/validators/array/no-empty-string-items.js +46 -0
  110. package/dist/core/validators/array/no-null-items.js +46 -0
  111. package/dist/core/validators/array/no-undefined-items.js +45 -0
  112. package/dist/core/validators/array/non-empty-array.js +45 -0
  113. package/dist/core/validators/array/not-sparse.js +44 -0
  114. package/dist/core/validators/bigint/bigint-equals.js +57 -0
  115. package/dist/core/validators/bigint/bigint-max.js +63 -0
  116. package/dist/core/validators/bigint/bigint-min.js +87 -0
  117. package/dist/core/validators/bigint/bigint-negative.js +73 -0
  118. package/dist/core/validators/bigint/bigint-non-negative.js +72 -0
  119. package/dist/core/validators/bigint/bigint-non-positive.js +72 -0
  120. package/dist/core/validators/bigint/bigint-positive.js +72 -0
  121. package/dist/core/validators/bigint/bigint-safe.js +75 -0
  122. package/dist/core/validators/bigint/bigint.js +38 -0
  123. package/dist/core/validators/boolean/boolean.js +39 -0
  124. package/dist/core/validators/boolean/is-false.js +48 -0
  125. package/dist/core/validators/boolean/is-true.js +48 -0
  126. package/dist/core/validators/common/is-country-code.js +36 -0
  127. package/dist/core/validators/common/is-currency-code.js +36 -0
  128. package/dist/core/validators/common/is-email-strict.js +36 -0
  129. package/dist/core/validators/common/is-email.js +36 -0
  130. package/dist/core/validators/common/is-ip.js +37 -0
  131. package/dist/core/validators/common/is-phone-strict.js +36 -0
  132. package/dist/core/validators/common/is-phone.js +36 -0
  133. package/dist/core/validators/common/is-port.js +35 -0
  134. package/dist/core/validators/common/is-postal-code.js +36 -0
  135. package/dist/core/validators/common/is-url.js +38 -0
  136. package/dist/core/validators/common/is-uuid.js +36 -0
  137. package/dist/core/validators/date/before-epoch.js +56 -0
  138. package/dist/core/validators/date/date-now-required.js +48 -0
  139. package/dist/core/validators/date/is-date.js +47 -0
  140. package/dist/core/validators/date/is-far-future.js +46 -0
  141. package/dist/core/validators/date/is-future.js +59 -0
  142. package/dist/core/validators/date/is-past.js +59 -0
  143. package/dist/core/validators/date/not-after.js +66 -0
  144. package/dist/core/validators/date/not-before.js +66 -0
  145. package/dist/core/validators/date/same-day.js +60 -0
  146. package/dist/core/validators/date/same-month.js +59 -0
  147. package/dist/core/validators/date/same-year.js +56 -0
  148. package/dist/core/validators/date/too-early.js +57 -0
  149. package/dist/core/validators/date/too-late.js +57 -0
  150. package/dist/core/validators/date/weekday.js +65 -0
  151. package/dist/core/validators/date/weekend.js +56 -0
  152. package/dist/core/validators/index.js +139 -0
  153. package/dist/core/validators/nullish/is-null-or-undefined.js +40 -0
  154. package/dist/core/validators/nullish/is-null.js +39 -0
  155. package/dist/core/validators/nullish/is-undefined.js +39 -0
  156. package/dist/core/validators/number/finite.js +40 -0
  157. package/dist/core/validators/number/integer.js +40 -0
  158. package/dist/core/validators/number/less-than.js +39 -0
  159. package/dist/core/validators/number/max.js +39 -0
  160. package/dist/core/validators/number/min.js +39 -0
  161. package/dist/core/validators/number/more-than.js +39 -0
  162. package/dist/core/validators/number/negative.js +38 -0
  163. package/dist/core/validators/number/non-negative.js +37 -0
  164. package/dist/core/validators/number/non-positive.js +37 -0
  165. package/dist/core/validators/number/number.js +31 -0
  166. package/dist/core/validators/number/positive.js +38 -0
  167. package/dist/core/validators/number/safe-integer.js +42 -0
  168. package/dist/core/validators/object/deep-equals.js +47 -0
  169. package/dist/core/validators/object/has-key.js +42 -0
  170. package/dist/core/validators/object/has-value.js +59 -0
  171. package/dist/core/validators/object/keys-equal.js +47 -0
  172. package/dist/core/validators/object/max-keys.js +43 -0
  173. package/dist/core/validators/object/min-keys.js +43 -0
  174. package/dist/core/validators/object/missing-key.js +42 -0
  175. package/dist/core/validators/object/no-empty-array-values.js +44 -0
  176. package/dist/core/validators/object/no-empty-object-values.js +44 -0
  177. package/dist/core/validators/object/no-null-values.js +44 -0
  178. package/dist/core/validators/object/no-undefined-values.js +44 -0
  179. package/dist/core/validators/object/non-empty-object.js +40 -0
  180. package/dist/core/validators/object/only-keys.js +43 -0
  181. package/dist/core/validators/object/plain-object.js +35 -0
  182. package/dist/core/validators/string/alpha-num.js +50 -0
  183. package/dist/core/validators/string/alpha.js +51 -0
  184. package/dist/core/validators/string/chars-equal.js +49 -0
  185. package/dist/core/validators/string/ends-with.js +50 -0
  186. package/dist/core/validators/string/is-ascii.js +53 -0
  187. package/dist/core/validators/string/is-printable.js +53 -0
  188. package/dist/core/validators/string/matches.js +50 -0
  189. package/dist/core/validators/string/max-length.js +50 -0
  190. package/dist/core/validators/string/min-length.js +50 -0
  191. package/dist/core/validators/string/no-lead-space.js +50 -0
  192. package/dist/core/validators/string/no-repeat-space.js +52 -0
  193. package/dist/core/validators/string/no-space.js +51 -0
  194. package/dist/core/validators/string/no-trail-space.js +50 -0
  195. package/dist/core/validators/string/non-empty.js +48 -0
  196. package/dist/core/validators/string/not-one-of.js +51 -0
  197. package/dist/core/validators/string/num-string.js +50 -0
  198. package/dist/core/validators/string/one-of.js +50 -0
  199. package/dist/core/validators/string/starts-with.js +50 -0
  200. package/dist/core/validators/string/string.js +39 -0
  201. package/dist/core/validators/string/trimmed.js +51 -0
  202. package/dist/index.js +26 -0
  203. package/dist/test.js +12 -0
  204. package/package.json +1 -1
@@ -0,0 +1,550 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Common | Policy
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Core types and helpers for defining, normalizing, and
7
+ * resolving policies that guide how Jane interprets events
8
+ * across all pipeline stages.
9
+ * @see https://jane-io.com
10
+ * ----------------------------------------------------------------------------
11
+ */
12
+ import { createEvent, getCompiledWildcard, matchesWildcard } from '.';
13
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
14
+ |* Apply Escalate *|
15
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
16
+ /**
17
+ * Raises an event’s severity by the given number of levels.
18
+ *
19
+ * Escalation moves the severity index upward and clamps it to the valid
20
+ * range. This helper is used by policy transforms to harden events without
21
+ * altering their underlying meaning or code.
22
+ */
23
+ export function applyEscalate(kind, levels) {
24
+ const idx = severityIndex(kind);
25
+ const next = clampSeverityIndex(idx + levels);
26
+ return SEVERITY_ORDER[next];
27
+ }
28
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
29
+ |* Apply Override *|
30
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
31
+ /**
32
+ * Lowers an event’s severity by the given number of levels.
33
+ *
34
+ * Override moves the severity index downward and clamps it to the valid
35
+ * range. This helper is used by policy transforms to soften events without
36
+ * changing their underlying meaning or code.
37
+ */
38
+ export function applyOverride(kind, levels) {
39
+ const idx = severityIndex(kind);
40
+ const next = clampSeverityIndex(idx - levels);
41
+ return SEVERITY_ORDER[next];
42
+ }
43
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
44
+ |* Clamp Severity Index *|
45
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
46
+ /**
47
+ * Clamps a severity index to the valid range.
48
+ *
49
+ * Severity levels are stored as an ordered list. This helper ensures that
50
+ * arithmetic on the index—such as escalation or override—never produces an
51
+ * out‑of‑bounds value. No interpretation occurs here; it simply enforces the
52
+ * valid numeric range.
53
+ */
54
+ export function clampSeverityIndex(i) {
55
+ if (i < 0)
56
+ return 0;
57
+ if (i >= SEVERITY_ORDER.length)
58
+ return SEVERITY_ORDER.length - 1;
59
+ return i;
60
+ }
61
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
62
+ |* Compile Severity *|
63
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
64
+ /**
65
+ * Compiles a user‑supplied severity map into override/escalation deltas.
66
+ *
67
+ * Each entry expresses a desired target severity for an event code. This
68
+ * helper compares the target against the baseline severity (“error”) and
69
+ * converts the difference into numeric levels suitable for the override and
70
+ * escalate policy transforms.
71
+ *
72
+ * Negative deltas become override levels; positive deltas become escalation
73
+ * levels. No interpretation occurs here—this function only computes the
74
+ * distance between severities.
75
+ */
76
+ export function compileSeverityMap(severity) {
77
+ const override = {};
78
+ const escalate = {};
79
+ for (const [code, targetKind] of Object.entries(severity)) {
80
+ const targetIndex = severityIndex(targetKind);
81
+ const currentIndex = severityIndex('error'); // baseline assumption
82
+ const delta = targetIndex - currentIndex;
83
+ if (delta < 0)
84
+ override[code] = Math.abs(delta);
85
+ if (delta > 0)
86
+ escalate[code] = delta;
87
+ }
88
+ return { override, escalate };
89
+ }
90
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
91
+ |* Decide Event *|
92
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
93
+ /**
94
+ * Creates a `decide`‑phase event.
95
+ *
96
+ * Decision events are emitted after validation when policy logic evaluates
97
+ * severity, overrides, escalation, and boundary rules. This helper ensures
98
+ * all decision events share a consistent shape and metadata.
99
+ */
100
+ export const decideEvent = (kind, code, path, message, userMessage, meta) => createEvent('decide', kind, code, path, message, userMessage, meta);
101
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
102
+ |* Default Policy *|
103
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
104
+ /**
105
+ * The baseline policy used when no user configuration is provided.
106
+ *
107
+ * Moderate mode applies no automatic rejects, reviews, or severity transforms.
108
+ * Scan and analysis features are disabled, and no structural exceptions are
109
+ * granted. This preset defines Jane’s neutral, predictable behavior and serves
110
+ * as the foundation for all policy merging.
111
+ */
112
+ export const defaultPolicy = {
113
+ mode: 'moderate',
114
+ decide: {
115
+ reject: [],
116
+ review: [],
117
+ warn: [],
118
+ override: {},
119
+ escalate: {},
120
+ },
121
+ analysis: {
122
+ diff: false,
123
+ explain: false,
124
+ replay: false,
125
+ telemetry: false,
126
+ },
127
+ scan: false,
128
+ exception: {
129
+ bigint: false,
130
+ map: false,
131
+ set: false,
132
+ },
133
+ };
134
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
135
+ |* Lax Policy *|
136
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
137
+ /**
138
+ * A permissive policy preset optimized for developer‑friendly behavior.
139
+ *
140
+ * Lax mode applies no rejects or reviews, downgrades all severities by one
141
+ * level via a wildcard override, disables scan and analysis features, and
142
+ * allows structural exceptions for bigint, map, and set. This preset favors
143
+ * flexibility and forgiveness during development and experimentation.
144
+ */
145
+ export const laxPolicy = {
146
+ mode: 'lax',
147
+ scan: false,
148
+ decide: {
149
+ reject: [],
150
+ review: [],
151
+ override: {
152
+ '*': 1,
153
+ },
154
+ escalate: {},
155
+ warn: [],
156
+ },
157
+ analysis: {
158
+ diff: false,
159
+ explain: false,
160
+ replay: false,
161
+ telemetry: false,
162
+ },
163
+ exception: {
164
+ bigint: true,
165
+ map: true,
166
+ set: true,
167
+ },
168
+ };
169
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
170
+ |* Boundary Strict Policy *|
171
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
172
+ /**
173
+ * A strict boundary preset that rejects on any issue.
174
+ *
175
+ * This configuration forces boundary acceptance to “strict”, applies a
176
+ * wildcard reject to treat every issue as fatal, and hides nothing from the
177
+ * event or issue streams. It provides the most conservative, compliance‑grade
178
+ * boundary behavior.
179
+ */
180
+ export const boundaryPolicyStrict = {
181
+ accept: 'strict',
182
+ reject: ['*'], // any issue is a reject
183
+ review: [],
184
+ hideEvents: [], // show everything
185
+ hideIssues: [],
186
+ };
187
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
188
+ |* Boundary Policy Default *|
189
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
190
+ /**
191
+ * The neutral boundary preset used when no boundary configuration is supplied.
192
+ *
193
+ * Default mode requires all fields to be accepted, applies no reject or review
194
+ * patterns, and hides nothing from the event or issue streams. It provides a
195
+ * transparent, assumption‑free baseline for boundary evaluation.
196
+ */
197
+ export const boundaryPolicyDefault = {
198
+ accept: 'all',
199
+ ignore: [],
200
+ hideEvents: [],
201
+ hideIssues: [],
202
+ reject: [],
203
+ review: [],
204
+ };
205
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
206
+ |* Boundary Policy Lax *|
207
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
208
+ /**
209
+ * A permissive boundary preset that accepts if any field succeeds.
210
+ *
211
+ * Lax mode ignores all boundary‑level events, hides both events and issues
212
+ * from the result, and applies no reject or review patterns. It provides a
213
+ * forgiving, low‑visibility boundary configuration suited for exploratory or
214
+ * developer‑focused workflows.
215
+ */
216
+ export const boundaryPolicyLax = {
217
+ accept: 'any',
218
+ ignore: ['*'], // ignore all events
219
+ hideEvents: ['*'], // hide everything
220
+ hideIssues: ['*'],
221
+ reject: [],
222
+ review: [],
223
+ };
224
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
225
+ |* Strict Policy *|
226
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
227
+ /**
228
+ * A maximum‑scrutiny policy preset for safety‑critical pipelines.
229
+ *
230
+ * Strict mode enables scan, rejects high‑risk conditions outright, escalates
231
+ * all severities by one level, and requires review for large or complex
232
+ * structures. All analysis features are enabled, and no structural exceptions
233
+ * are granted. This preset enforces the most conservative, defensive behavior.
234
+ */
235
+ export const strictPolicy = {
236
+ mode: 'strict',
237
+ scan: true,
238
+ decide: {
239
+ reject: [
240
+ 'value.was.contained',
241
+ 'object.has.circular-references',
242
+ 'number.not.finite',
243
+ 'number.not.number',
244
+ 'date.is.invalid',
245
+ 'string.has.unsafe-unicode',
246
+ 'array.is.deep',
247
+ 'object.is.deep',
248
+ ],
249
+ review: ['array.is.large', 'object.has.many-keys', 'string.is.long', 'bigint.is.large'],
250
+ escalate: {
251
+ '*': 1,
252
+ },
253
+ override: {},
254
+ warn: [],
255
+ },
256
+ analysis: {
257
+ diff: true,
258
+ explain: true,
259
+ replay: true,
260
+ telemetry: true,
261
+ },
262
+ exception: {
263
+ bigint: false,
264
+ map: false,
265
+ set: false,
266
+ },
267
+ };
268
+ export function mergeBoundaryPolicies(base, ...overrides) {
269
+ let result = { ...(base ?? {}) };
270
+ for (const o of overrides) {
271
+ if (!o)
272
+ continue;
273
+ result = {
274
+ ...result,
275
+ // primitives (last one wins)
276
+ ...(o.accept !== undefined ? { accept: o.accept } : {}),
277
+ // arrays (concatenate)
278
+ ...(o.ignore !== undefined ? { ignore: [...(result.ignore ?? []), ...o.ignore] } : {}),
279
+ ...(o.hideEvents !== undefined
280
+ ? { hideEvents: [...(result.hideEvents ?? []), ...o.hideEvents] }
281
+ : {}),
282
+ ...(o.hideIssues !== undefined
283
+ ? { hideIssues: [...(result.hideIssues ?? []), ...o.hideIssues] }
284
+ : {}),
285
+ // ⭐ rules (concatenate)
286
+ ...(o.rules !== undefined ? { rules: [...(result.rules ?? []), ...o.rules] } : {}),
287
+ // nested objects (shallow merge)
288
+ ...(o.values !== undefined
289
+ ? { values: { ...(result.values ?? {}), ...o.values } }
290
+ : {}),
291
+ ...(o.metadata !== undefined
292
+ ? { metadata: { ...(result.metadata ?? {}), ...o.metadata } }
293
+ : {}),
294
+ // functions (last one wins)
295
+ ...(o.values?.transform !== undefined ? { transform: o.values?.transform } : {}),
296
+ ...(o.reducer !== undefined ? { reducer: o.reducer } : {}),
297
+ };
298
+ }
299
+ return result;
300
+ }
301
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
302
+ |* Normalize Policy *|
303
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
304
+ /**
305
+ * Merges a base policy with user‑supplied input into a final, normalized policy.
306
+ *
307
+ * This function applies field‑by‑field overrides, compiles public severity
308
+ * settings into internal override/escalation maps, and merges reject/review/warn
309
+ * patterns in a predictable order. It preserves all unspecified fields from the
310
+ * base policy and never introduces defaults beyond those already present.
311
+ */
312
+ export function normalizePolicy(base, input = {}) {
313
+ const next = {
314
+ ...base,
315
+ };
316
+ if (input.mode !== undefined) {
317
+ next.mode = input.mode;
318
+ }
319
+ if (input.analysis !== undefined) {
320
+ next.analysis = {
321
+ ...next.analysis,
322
+ ...input.analysis,
323
+ };
324
+ }
325
+ if (input.scan !== undefined) {
326
+ next.scan = input.scan;
327
+ }
328
+ if (input.exception !== undefined) {
329
+ next.exception = {
330
+ ...next.exception,
331
+ ...input.exception,
332
+ };
333
+ }
334
+ if (input.boundaryName !== undefined) {
335
+ next.boundaryName = input.boundaryName;
336
+ }
337
+ if (input.pipelineName !== undefined) {
338
+ next.pipelineName = input.pipelineName;
339
+ }
340
+ if (input.severity !== undefined) {
341
+ const compiled = compileSeverityMap(input.severity);
342
+ next.decide = {
343
+ ...(next.decide ?? {}),
344
+ override: {
345
+ ...(next.decide?.override ?? {}),
346
+ ...compiled.override,
347
+ },
348
+ escalate: {
349
+ ...(next.decide?.escalate ?? {}),
350
+ ...compiled.escalate,
351
+ },
352
+ };
353
+ }
354
+ if (input.reject !== undefined || input.review !== undefined || input.warn !== undefined) {
355
+ next.decide = {
356
+ ...(next.decide ?? {}),
357
+ reject: [...(next.decide?.reject ?? []), ...(input.reject ?? [])],
358
+ review: [...(next.decide?.review ?? []), ...(input.review ?? [])],
359
+ warn: [...(next.decide?.warn ?? []), ...(input.warn ?? [])],
360
+ };
361
+ }
362
+ if (input.decide !== undefined) {
363
+ next.decide = {
364
+ ...(next.decide ?? {}),
365
+ ...input.decide,
366
+ };
367
+ }
368
+ if (input.boundary !== undefined) {
369
+ next.boundary = {
370
+ ...(next.boundary ?? {}),
371
+ ...input.boundary,
372
+ };
373
+ }
374
+ return next;
375
+ }
376
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
377
+ |* Resolve Level *|
378
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
379
+ /**
380
+ * Resolves a numeric level for an event code from a sparse lookup table.
381
+ *
382
+ * Exact matches take priority. If none exist, wildcard patterns in the table
383
+ * are tested in insertion order using their compiled regular expressions.
384
+ * Returns the matched level or `undefined` when no pattern applies.
385
+ */
386
+ export function resolveLevel(code, table) {
387
+ if (code in table)
388
+ return table[code];
389
+ for (const pattern of Object.keys(table)) {
390
+ if (pattern.includes('*')) {
391
+ const re = getCompiledWildcard(pattern);
392
+ if (re.test(code)) {
393
+ return table[pattern];
394
+ }
395
+ }
396
+ }
397
+ return undefined;
398
+ }
399
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
400
+ |* Policy Decision *|
401
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
402
+ /**
403
+ * Applies policy transforms to events and produces the final pipeline decision.
404
+ *
405
+ * This function reshapes events using override, escalation, and warn rules,
406
+ * derives issues from the resulting severities, and evaluates reject/review
407
+ * patterns according to the active policy mode. It returns the full pipeline
408
+ * result with shaped events, extracted issues, and a final accept/review/reject
409
+ * decision. No data is modified—only event interpretation changes.
410
+ */
411
+ export function policyDecision(ctx, metadata) {
412
+ const { events, policy } = ctx;
413
+ let shapedEvents = [...events];
414
+ if (policy.decide?.override) {
415
+ shapedEvents = shapedEvents.map((ev) => {
416
+ const level = resolveLevel(ev.code, policy.decide.override);
417
+ return level === undefined ? ev : { ...ev, kind: applyOverride(ev.kind, level) };
418
+ });
419
+ }
420
+ if (policy.decide?.escalate) {
421
+ shapedEvents = shapedEvents.map((ev) => {
422
+ const level = resolveLevel(ev.code, policy.decide.escalate);
423
+ return level === undefined ? ev : { ...ev, kind: applyEscalate(ev.kind, level) };
424
+ });
425
+ }
426
+ if (policy.decide?.warn) {
427
+ shapedEvents = shapedEvents.map((ev) => {
428
+ if (matchesWildcard(ev.code, policy.decide.warn)) {
429
+ return { ...ev, kind: 'warn' };
430
+ }
431
+ return ev;
432
+ });
433
+ }
434
+ const shapedIssues = shapedEvents.filter((ev) => (ev.kind === 'error' || ev.kind === 'fatal') &&
435
+ typeof ev.code === 'string' &&
436
+ ev.code.trim().length > 0);
437
+ const rejectPatterns = policy.decide?.reject ?? [];
438
+ const reviewPatterns = policy.decide?.review ?? [];
439
+ let hasRejectEvents = false;
440
+ let hasReviewEvents = false;
441
+ for (const ev of shapedEvents) {
442
+ if (ev.kind === 'fatal') {
443
+ hasRejectEvents = true;
444
+ continue;
445
+ }
446
+ if (matchesWildcard(ev.code, rejectPatterns)) {
447
+ hasRejectEvents = true;
448
+ continue;
449
+ }
450
+ if (matchesWildcard(ev.code, reviewPatterns)) {
451
+ hasReviewEvents = true;
452
+ }
453
+ }
454
+ let decision;
455
+ switch (policy.mode) {
456
+ case 'strict':
457
+ decision =
458
+ hasRejectEvents || hasReviewEvents || shapedIssues.length > 0
459
+ ? { code: 'reject', hasRejectEvents, hasReviewEvents }
460
+ : { code: 'accept', hasRejectEvents, hasReviewEvents };
461
+ break;
462
+ case 'moderate':
463
+ decision = hasRejectEvents
464
+ ? { code: 'reject', hasRejectEvents, hasReviewEvents }
465
+ : hasReviewEvents
466
+ ? { code: 'review', hasRejectEvents, hasReviewEvents }
467
+ : { code: 'accept', hasRejectEvents, hasReviewEvents };
468
+ break;
469
+ case 'lax':
470
+ default:
471
+ decision = hasRejectEvents
472
+ ? { code: 'reject', hasRejectEvents, hasReviewEvents }
473
+ : { code: 'accept', hasRejectEvents, hasReviewEvents };
474
+ break;
475
+ }
476
+ return {
477
+ ...ctx,
478
+ events: shapedEvents,
479
+ issues: shapedIssues,
480
+ decision,
481
+ metadata,
482
+ };
483
+ }
484
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
485
+ |* Resolve Policy *|
486
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
487
+ /**
488
+ * Resolves the effective policy by merging defaults, explicit policy, and
489
+ * fluent‑API overrides.
490
+ *
491
+ * The default and explicit policies are combined first, then builder overrides
492
+ * are applied field‑by‑field with strict precedence. Only fields exposed to the
493
+ * fluent API are overrideable; all other structure is taken directly from the
494
+ * merged base. No inference or normalization occurs here—this function performs
495
+ * a final, literal merge for pipeline execution.
496
+ */
497
+ export function resolvePolicy(defaultPolicy, janePolicy, overrides) {
498
+ const base = {
499
+ ...defaultPolicy,
500
+ ...(janePolicy ?? {}),
501
+ };
502
+ const o = overrides ?? {};
503
+ return {
504
+ boundaryName: base.boundaryName,
505
+ pipelineName: base.pipelineName,
506
+ mode: o.mode ?? base.mode,
507
+ boundary: base.boundary,
508
+ decide: base.decide,
509
+ analysis: {
510
+ diff: o.analysis?.diff ?? base.analysis?.diff,
511
+ explain: o.analysis?.explain ?? base.analysis?.explain,
512
+ replay: o.analysis?.replay ?? base.analysis?.replay,
513
+ telemetry: o.analysis?.telemetry ?? base.analysis?.telemetry,
514
+ },
515
+ scan: o.scan ?? base.scan,
516
+ exception: {
517
+ bigint: o.exception?.bigint ?? base.exception?.bigint,
518
+ map: o.exception?.map ?? base.exception?.map,
519
+ set: o.exception?.set ?? base.exception?.set,
520
+ },
521
+ };
522
+ }
523
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
524
+ |* Severity Index *|
525
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
526
+ /**
527
+ * Returns the numeric index for a severity kind.
528
+ *
529
+ * Event kinds are stored in a fixed ordered list. Unknown kinds fall back to
530
+ * index 0 to guarantee a safe, deterministic baseline for all severity math.
531
+ * No interpretation occurs here—this is a simple lookup with a fallback.
532
+ */
533
+ export function severityIndex(kind) {
534
+ let idx = SEVERITY_ORDER.indexOf(kind);
535
+ if (idx === -1) {
536
+ idx = 0;
537
+ }
538
+ return idx;
539
+ }
540
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
541
+ |* Severity Order *|
542
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
543
+ /**
544
+ * Fixed severity ladder from least to most severe.
545
+ *
546
+ * Policies, escalation math, and explain output rely on this exact ordering.
547
+ * Changing it is a breaking change because severity indices are used
548
+ * throughout the pipeline.
549
+ */
550
+ export const SEVERITY_ORDER = ['info', 'warn', 'error', 'fatal'];