autotel 3.4.4 → 3.6.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 (100) hide show
  1. package/dist/{chunk-O4JZUCUE.js → chunk-66YJ66GG.js} +5 -158
  2. package/dist/chunk-66YJ66GG.js.map +1 -0
  3. package/dist/{chunk-Z6HRSM2Y.cjs → chunk-B7SWBE4P.cjs} +5 -5
  4. package/dist/{chunk-Z6HRSM2Y.cjs.map → chunk-B7SWBE4P.cjs.map} +1 -1
  5. package/dist/{chunk-DQEHQNQE.js → chunk-D4TM63S3.js} +3 -3
  6. package/dist/{chunk-DQEHQNQE.js.map → chunk-D4TM63S3.js.map} +1 -1
  7. package/dist/chunk-DCEDJQGG.js +28 -0
  8. package/dist/chunk-DCEDJQGG.js.map +1 -0
  9. package/dist/chunk-E6TERL5O.cjs +23 -0
  10. package/dist/chunk-E6TERL5O.cjs.map +1 -0
  11. package/dist/chunk-EE6CPXKH.cjs +164 -0
  12. package/dist/chunk-EE6CPXKH.cjs.map +1 -0
  13. package/dist/{chunk-GBFTC7Q7.cjs → chunk-EOFB7XCL.cjs} +6 -6
  14. package/dist/{chunk-GBFTC7Q7.cjs.map → chunk-EOFB7XCL.cjs.map} +1 -1
  15. package/dist/{chunk-Z7PW3KHL.cjs → chunk-FMTHVSYY.cjs} +4 -163
  16. package/dist/chunk-FMTHVSYY.cjs.map +1 -0
  17. package/dist/{chunk-VG2ABKJX.cjs → chunk-KYXZS3EA.cjs} +7 -7
  18. package/dist/{chunk-VG2ABKJX.cjs.map → chunk-KYXZS3EA.cjs.map} +1 -1
  19. package/dist/chunk-LVIPBYFE.js +157 -0
  20. package/dist/chunk-LVIPBYFE.js.map +1 -0
  21. package/dist/{chunk-NVGPMGI4.js → chunk-N25JDZSC.js} +3 -3
  22. package/dist/{chunk-NVGPMGI4.js.map → chunk-N25JDZSC.js.map} +1 -1
  23. package/dist/{chunk-AC5GNZKB.cjs → chunk-NENU7E6V.cjs} +5 -5
  24. package/dist/{chunk-AC5GNZKB.cjs.map → chunk-NENU7E6V.cjs.map} +1 -1
  25. package/dist/{chunk-URHPSJW2.js → chunk-QF7ARNUM.js} +3 -3
  26. package/dist/{chunk-URHPSJW2.js.map → chunk-QF7ARNUM.js.map} +1 -1
  27. package/dist/chunk-T5WRA76K.cjs +32 -0
  28. package/dist/chunk-T5WRA76K.cjs.map +1 -0
  29. package/dist/{chunk-YWCESU4Y.js → chunk-T7JO2TCP.js} +3 -3
  30. package/dist/{chunk-YWCESU4Y.js.map → chunk-T7JO2TCP.js.map} +1 -1
  31. package/dist/{chunk-O7JOKRN2.js → chunk-UIKYE2QZ.js} +3 -3
  32. package/dist/{chunk-O7JOKRN2.js.map → chunk-UIKYE2QZ.js.map} +1 -1
  33. package/dist/chunk-UNPLAVE7.js +21 -0
  34. package/dist/chunk-UNPLAVE7.js.map +1 -0
  35. package/dist/{chunk-FGNDN2FD.cjs → chunk-V7UBMJAB.cjs} +18 -18
  36. package/dist/{chunk-FGNDN2FD.cjs.map → chunk-V7UBMJAB.cjs.map} +1 -1
  37. package/dist/correlation-id.cjs +10 -9
  38. package/dist/correlation-id.js +2 -1
  39. package/dist/decorators.cjs +4 -3
  40. package/dist/decorators.cjs.map +1 -1
  41. package/dist/decorators.js +3 -2
  42. package/dist/decorators.js.map +1 -1
  43. package/dist/define-event-BL6Li7CM.d.ts +23 -0
  44. package/dist/define-event-ClP3T1Jx.d.cts +23 -0
  45. package/dist/event.cjs +6 -5
  46. package/dist/event.js +3 -2
  47. package/dist/functional.cjs +11 -10
  48. package/dist/functional.js +3 -2
  49. package/dist/http.cjs +3 -2
  50. package/dist/http.cjs.map +1 -1
  51. package/dist/http.js +2 -1
  52. package/dist/http.js.map +1 -1
  53. package/dist/index.cjs +91 -102
  54. package/dist/index.cjs.map +1 -1
  55. package/dist/index.d.cts +2 -21
  56. package/dist/index.d.ts +2 -21
  57. package/dist/index.js +17 -27
  58. package/dist/index.js.map +1 -1
  59. package/dist/messaging.cjs +7 -6
  60. package/dist/messaging.js +4 -3
  61. package/dist/security-schema.cjs +69 -0
  62. package/dist/security-schema.cjs.map +1 -0
  63. package/dist/security-schema.d.cts +67 -0
  64. package/dist/security-schema.d.ts +67 -0
  65. package/dist/security-schema.js +59 -0
  66. package/dist/security-schema.js.map +1 -0
  67. package/dist/semantic-helpers.cjs +8 -7
  68. package/dist/semantic-helpers.js +4 -3
  69. package/dist/validate.cjs +138 -0
  70. package/dist/validate.cjs.map +1 -0
  71. package/dist/validate.d.cts +129 -0
  72. package/dist/validate.d.ts +129 -0
  73. package/dist/validate.js +133 -0
  74. package/dist/validate.js.map +1 -0
  75. package/dist/validation-attributes.cjs +20 -0
  76. package/dist/validation-attributes.cjs.map +1 -0
  77. package/dist/validation-attributes.d.cts +40 -0
  78. package/dist/validation-attributes.d.ts +40 -0
  79. package/dist/validation-attributes.js +3 -0
  80. package/dist/validation-attributes.js.map +1 -0
  81. package/dist/webhook.cjs +5 -4
  82. package/dist/webhook.cjs.map +1 -1
  83. package/dist/webhook.js +3 -2
  84. package/dist/webhook.js.map +1 -1
  85. package/dist/workflow-distributed.cjs +5 -4
  86. package/dist/workflow-distributed.cjs.map +1 -1
  87. package/dist/workflow-distributed.js +3 -2
  88. package/dist/workflow-distributed.js.map +1 -1
  89. package/dist/workflow.cjs +8 -7
  90. package/dist/workflow.js +4 -3
  91. package/package.json +23 -8
  92. package/src/define-event.ts +2 -21
  93. package/src/security-schema.test.ts +45 -0
  94. package/src/security-schema.ts +107 -0
  95. package/src/stable-hash.ts +27 -0
  96. package/src/validate.test.ts +285 -0
  97. package/src/validate.ts +301 -0
  98. package/src/validation-attributes.ts +43 -0
  99. package/dist/chunk-O4JZUCUE.js.map +0 -1
  100. package/dist/chunk-Z7PW3KHL.cjs.map +0 -1
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Security telemetry wire schema — the single source of truth for the
3
+ * `security.*` span-attribute contract emitted by `autotel-audit`
4
+ * (`securityEvent()`, `withSecurity()`, `createSecuritySignalProcessor()`)
5
+ * and consumed by `autotel-subscribers`, `autotel-devtools`, and the
6
+ * `autotel security` CLI commands.
7
+ *
8
+ * Dependency-free and side-effect-free by design: safe to import from
9
+ * browser bundles (devtools widget) and anything else that only needs
10
+ * the constants, without pulling in the OpenTelemetry SDK.
11
+ */
12
+ type SecuritySeverity = 'info' | 'warning' | 'error' | 'critical';
13
+ /** All severities, lowest first. */
14
+ declare const SECURITY_SEVERITIES: readonly SecuritySeverity[];
15
+ /** Numeric rank per severity for threshold comparisons. */
16
+ declare const SECURITY_SEVERITY_RANK: Record<SecuritySeverity, number>;
17
+ /**
18
+ * Parse an untrusted value (span attribute, event payload field) into a
19
+ * severity, falling back when it is missing or malformed.
20
+ */
21
+ declare function parseSecuritySeverity(value: unknown, fallback?: SecuritySeverity): SecuritySeverity;
22
+ /** `true` when `severity` meets or exceeds `min`. */
23
+ declare function securitySeverityAtLeast(severity: SecuritySeverity, min: SecuritySeverity): boolean;
24
+ /** The higher-ranked of two severities (e.g. escalate failures to ≥ error). */
25
+ declare function escalateSecuritySeverity(severity: SecuritySeverity, floor: SecuritySeverity): SecuritySeverity;
26
+ /**
27
+ * Span attribute keys of the security schema. Emitters and consumers must
28
+ * reference these instead of re-typing the strings.
29
+ */
30
+ declare const SECURITY_ATTR: {
31
+ /** Marker set on every span carrying a security event. */
32
+ readonly marker: "autotel.security";
33
+ /** Set when the event was force-kept through tail sampling. */
34
+ readonly forceKeep: "autotel.security.force_keep";
35
+ readonly event: "security.event";
36
+ readonly category: "security.category";
37
+ readonly outcome: "security.outcome";
38
+ readonly severity: "security.severity";
39
+ readonly actorId: "security.actor_id";
40
+ readonly targetType: "security.target_type";
41
+ readonly targetId: "security.target_id";
42
+ readonly tenantId: "security.tenant_id";
43
+ readonly reason: "security.reason";
44
+ /** Custom metadata keys dropped because they looked credential-shaped. */
45
+ readonly droppedKeys: "security.dropped_keys";
46
+ /** Set by the signal processor on suspicious request paths. */
47
+ readonly suspiciousRequest: "security.suspicious_request";
48
+ /** Pattern name that flagged a suspicious request, e.g. `path_traversal`. */
49
+ readonly signal: "security.signal";
50
+ };
51
+ /** Metric names emitted by the security instrumentation. */
52
+ declare const SECURITY_METRICS: {
53
+ readonly events: "autotel.security.events";
54
+ readonly httpSuspicious: "autotel.security.http.suspicious";
55
+ readonly httpDenied: "autotel.security.http.denied";
56
+ readonly anomaly: "autotel.security.anomaly";
57
+ readonly heartbeat: "autotel.security.heartbeat";
58
+ };
59
+ /** HTTP statuses counted as denied responses by default. */
60
+ declare const SECURITY_DENIED_STATUSES: readonly number[];
61
+ /**
62
+ * Span attributes carrying the HTTP response status, current semconv
63
+ * first, legacy fallback second.
64
+ */
65
+ declare const HTTP_STATUS_ATTRIBUTES: readonly string[];
66
+
67
+ export { HTTP_STATUS_ATTRIBUTES, SECURITY_ATTR, SECURITY_DENIED_STATUSES, SECURITY_METRICS, SECURITY_SEVERITIES, SECURITY_SEVERITY_RANK, type SecuritySeverity, escalateSecuritySeverity, parseSecuritySeverity, securitySeverityAtLeast };
@@ -0,0 +1,59 @@
1
+ // src/security-schema.ts
2
+ var SECURITY_SEVERITIES = [
3
+ "info",
4
+ "warning",
5
+ "error",
6
+ "critical"
7
+ ];
8
+ var SECURITY_SEVERITY_RANK = {
9
+ info: 0,
10
+ warning: 1,
11
+ error: 2,
12
+ critical: 3
13
+ };
14
+ function parseSecuritySeverity(value, fallback = "info") {
15
+ return typeof value === "string" && value in SECURITY_SEVERITY_RANK ? value : fallback;
16
+ }
17
+ function securitySeverityAtLeast(severity, min) {
18
+ return SECURITY_SEVERITY_RANK[severity] >= SECURITY_SEVERITY_RANK[min];
19
+ }
20
+ function escalateSecuritySeverity(severity, floor) {
21
+ return SECURITY_SEVERITY_RANK[severity] >= SECURITY_SEVERITY_RANK[floor] ? severity : floor;
22
+ }
23
+ var SECURITY_ATTR = {
24
+ /** Marker set on every span carrying a security event. */
25
+ marker: "autotel.security",
26
+ /** Set when the event was force-kept through tail sampling. */
27
+ forceKeep: "autotel.security.force_keep",
28
+ event: "security.event",
29
+ category: "security.category",
30
+ outcome: "security.outcome",
31
+ severity: "security.severity",
32
+ actorId: "security.actor_id",
33
+ targetType: "security.target_type",
34
+ targetId: "security.target_id",
35
+ tenantId: "security.tenant_id",
36
+ reason: "security.reason",
37
+ /** Custom metadata keys dropped because they looked credential-shaped. */
38
+ droppedKeys: "security.dropped_keys",
39
+ /** Set by the signal processor on suspicious request paths. */
40
+ suspiciousRequest: "security.suspicious_request",
41
+ /** Pattern name that flagged a suspicious request, e.g. `path_traversal`. */
42
+ signal: "security.signal"
43
+ };
44
+ var SECURITY_METRICS = {
45
+ events: "autotel.security.events",
46
+ httpSuspicious: "autotel.security.http.suspicious",
47
+ httpDenied: "autotel.security.http.denied",
48
+ anomaly: "autotel.security.anomaly",
49
+ heartbeat: "autotel.security.heartbeat"
50
+ };
51
+ var SECURITY_DENIED_STATUSES = [401, 403, 429];
52
+ var HTTP_STATUS_ATTRIBUTES = [
53
+ "http.response.status_code",
54
+ "http.status_code"
55
+ ];
56
+
57
+ export { HTTP_STATUS_ATTRIBUTES, SECURITY_ATTR, SECURITY_DENIED_STATUSES, SECURITY_METRICS, SECURITY_SEVERITIES, SECURITY_SEVERITY_RANK, escalateSecuritySeverity, parseSecuritySeverity, securitySeverityAtLeast };
58
+ //# sourceMappingURL=security-schema.js.map
59
+ //# sourceMappingURL=security-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/security-schema.ts"],"names":[],"mappings":";AAeO,IAAM,mBAAA,GAAmD;AAAA,EAC9D,MAAA;AAAA,EACA,SAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF;AAGO,IAAM,sBAAA,GAA2D;AAAA,EACtE,IAAA,EAAM,CAAA;AAAA,EACN,OAAA,EAAS,CAAA;AAAA,EACT,KAAA,EAAO,CAAA;AAAA,EACP,QAAA,EAAU;AACZ;AAMO,SAAS,qBAAA,CACd,KAAA,EACA,QAAA,GAA6B,MAAA,EACX;AAClB,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,IAAS,yBACxC,KAAA,GACD,QAAA;AACN;AAGO,SAAS,uBAAA,CACd,UACA,GAAA,EACS;AACT,EAAA,OAAO,sBAAA,CAAuB,QAAQ,CAAA,IAAK,sBAAA,CAAuB,GAAG,CAAA;AACvE;AAGO,SAAS,wBAAA,CACd,UACA,KAAA,EACkB;AAClB,EAAA,OAAO,uBAAuB,QAAQ,CAAA,IAAK,sBAAA,CAAuB,KAAK,IACnE,QAAA,GACA,KAAA;AACN;AAMO,IAAM,aAAA,GAAgB;AAAA;AAAA,EAE3B,MAAA,EAAQ,kBAAA;AAAA;AAAA,EAER,SAAA,EAAW,6BAAA;AAAA,EACX,KAAA,EAAO,gBAAA;AAAA,EACP,QAAA,EAAU,mBAAA;AAAA,EACV,OAAA,EAAS,kBAAA;AAAA,EACT,QAAA,EAAU,mBAAA;AAAA,EACV,OAAA,EAAS,mBAAA;AAAA,EACT,UAAA,EAAY,sBAAA;AAAA,EACZ,QAAA,EAAU,oBAAA;AAAA,EACV,QAAA,EAAU,oBAAA;AAAA,EACV,MAAA,EAAQ,iBAAA;AAAA;AAAA,EAER,WAAA,EAAa,uBAAA;AAAA;AAAA,EAEb,iBAAA,EAAmB,6BAAA;AAAA;AAAA,EAEnB,MAAA,EAAQ;AACV;AAGO,IAAM,gBAAA,GAAmB;AAAA,EAC9B,MAAA,EAAQ,yBAAA;AAAA,EACR,cAAA,EAAgB,kCAAA;AAAA,EAChB,UAAA,EAAY,8BAAA;AAAA,EACZ,OAAA,EAAS,0BAAA;AAAA,EACT,SAAA,EAAW;AACb;AAGO,IAAM,wBAAA,GAA8C,CAAC,GAAA,EAAK,GAAA,EAAK,GAAG;AAMlE,IAAM,sBAAA,GAA4C;AAAA,EACvD,2BAAA;AAAA,EACA;AACF","file":"security-schema.js","sourcesContent":["/**\n * Security telemetry wire schema — the single source of truth for the\n * `security.*` span-attribute contract emitted by `autotel-audit`\n * (`securityEvent()`, `withSecurity()`, `createSecuritySignalProcessor()`)\n * and consumed by `autotel-subscribers`, `autotel-devtools`, and the\n * `autotel security` CLI commands.\n *\n * Dependency-free and side-effect-free by design: safe to import from\n * browser bundles (devtools widget) and anything else that only needs\n * the constants, without pulling in the OpenTelemetry SDK.\n */\n\nexport type SecuritySeverity = 'info' | 'warning' | 'error' | 'critical';\n\n/** All severities, lowest first. */\nexport const SECURITY_SEVERITIES: readonly SecuritySeverity[] = [\n 'info',\n 'warning',\n 'error',\n 'critical',\n];\n\n/** Numeric rank per severity for threshold comparisons. */\nexport const SECURITY_SEVERITY_RANK: Record<SecuritySeverity, number> = {\n info: 0,\n warning: 1,\n error: 2,\n critical: 3,\n};\n\n/**\n * Parse an untrusted value (span attribute, event payload field) into a\n * severity, falling back when it is missing or malformed.\n */\nexport function parseSecuritySeverity(\n value: unknown,\n fallback: SecuritySeverity = 'info',\n): SecuritySeverity {\n return typeof value === 'string' && value in SECURITY_SEVERITY_RANK\n ? (value as SecuritySeverity)\n : fallback;\n}\n\n/** `true` when `severity` meets or exceeds `min`. */\nexport function securitySeverityAtLeast(\n severity: SecuritySeverity,\n min: SecuritySeverity,\n): boolean {\n return SECURITY_SEVERITY_RANK[severity] >= SECURITY_SEVERITY_RANK[min];\n}\n\n/** The higher-ranked of two severities (e.g. escalate failures to ≥ error). */\nexport function escalateSecuritySeverity(\n severity: SecuritySeverity,\n floor: SecuritySeverity,\n): SecuritySeverity {\n return SECURITY_SEVERITY_RANK[severity] >= SECURITY_SEVERITY_RANK[floor]\n ? severity\n : floor;\n}\n\n/**\n * Span attribute keys of the security schema. Emitters and consumers must\n * reference these instead of re-typing the strings.\n */\nexport const SECURITY_ATTR = {\n /** Marker set on every span carrying a security event. */\n marker: 'autotel.security',\n /** Set when the event was force-kept through tail sampling. */\n forceKeep: 'autotel.security.force_keep',\n event: 'security.event',\n category: 'security.category',\n outcome: 'security.outcome',\n severity: 'security.severity',\n actorId: 'security.actor_id',\n targetType: 'security.target_type',\n targetId: 'security.target_id',\n tenantId: 'security.tenant_id',\n reason: 'security.reason',\n /** Custom metadata keys dropped because they looked credential-shaped. */\n droppedKeys: 'security.dropped_keys',\n /** Set by the signal processor on suspicious request paths. */\n suspiciousRequest: 'security.suspicious_request',\n /** Pattern name that flagged a suspicious request, e.g. `path_traversal`. */\n signal: 'security.signal',\n} as const;\n\n/** Metric names emitted by the security instrumentation. */\nexport const SECURITY_METRICS = {\n events: 'autotel.security.events',\n httpSuspicious: 'autotel.security.http.suspicious',\n httpDenied: 'autotel.security.http.denied',\n anomaly: 'autotel.security.anomaly',\n heartbeat: 'autotel.security.heartbeat',\n} as const;\n\n/** HTTP statuses counted as denied responses by default. */\nexport const SECURITY_DENIED_STATUSES: readonly number[] = [401, 403, 429];\n\n/**\n * Span attributes carrying the HTTP response status, current semconv\n * first, legacy fallback second.\n */\nexport const HTTP_STATUS_ATTRIBUTES: readonly string[] = [\n 'http.response.status_code',\n 'http.status_code',\n];\n"]}
@@ -1,10 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var chunkVG2ABKJX_cjs = require('./chunk-VG2ABKJX.cjs');
4
- require('./chunk-FGNDN2FD.cjs');
3
+ var chunkKYXZS3EA_cjs = require('./chunk-KYXZS3EA.cjs');
4
+ require('./chunk-V7UBMJAB.cjs');
5
5
  require('./chunk-OPPXYVEZ.cjs');
6
6
  require('./chunk-VQTCQKHQ.cjs');
7
- require('./chunk-Z7PW3KHL.cjs');
7
+ require('./chunk-FMTHVSYY.cjs');
8
+ require('./chunk-EE6CPXKH.cjs');
8
9
  require('./chunk-R7QYGZUP.cjs');
9
10
  require('./chunk-QWW3E3JM.cjs');
10
11
  require('./chunk-CEAQK2QY.cjs');
@@ -23,19 +24,19 @@ require('./chunk-YREV3LGG.cjs');
23
24
 
24
25
  Object.defineProperty(exports, "traceDB", {
25
26
  enumerable: true,
26
- get: function () { return chunkVG2ABKJX_cjs.traceDB; }
27
+ get: function () { return chunkKYXZS3EA_cjs.traceDB; }
27
28
  });
28
29
  Object.defineProperty(exports, "traceHTTP", {
29
30
  enumerable: true,
30
- get: function () { return chunkVG2ABKJX_cjs.traceHTTP; }
31
+ get: function () { return chunkKYXZS3EA_cjs.traceHTTP; }
31
32
  });
32
33
  Object.defineProperty(exports, "traceLLM", {
33
34
  enumerable: true,
34
- get: function () { return chunkVG2ABKJX_cjs.traceLLM; }
35
+ get: function () { return chunkKYXZS3EA_cjs.traceLLM; }
35
36
  });
36
37
  Object.defineProperty(exports, "traceMessaging", {
37
38
  enumerable: true,
38
- get: function () { return chunkVG2ABKJX_cjs.traceMessaging; }
39
+ get: function () { return chunkKYXZS3EA_cjs.traceMessaging; }
39
40
  });
40
41
  //# sourceMappingURL=semantic-helpers.cjs.map
41
42
  //# sourceMappingURL=semantic-helpers.cjs.map
@@ -1,8 +1,9 @@
1
- export { traceDB, traceHTTP, traceLLM, traceMessaging } from './chunk-NVGPMGI4.js';
2
- import './chunk-YWCESU4Y.js';
1
+ export { traceDB, traceHTTP, traceLLM, traceMessaging } from './chunk-N25JDZSC.js';
2
+ import './chunk-T7JO2TCP.js';
3
3
  import './chunk-HT5JQKN2.js';
4
4
  import './chunk-SEO6NAQT.js';
5
- import './chunk-O4JZUCUE.js';
5
+ import './chunk-66YJ66GG.js';
6
+ import './chunk-LVIPBYFE.js';
6
7
  import './chunk-ALPYR2GC.js';
7
8
  import './chunk-CMHVQR6P.js';
8
9
  import './chunk-A4E5AQFK.js';
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ var chunkT5WRA76K_cjs = require('./chunk-T5WRA76K.cjs');
4
+ var chunkE6TERL5O_cjs = require('./chunk-E6TERL5O.cjs');
5
+ var chunkWJH6IYU2_cjs = require('./chunk-WJH6IYU2.cjs');
6
+ var chunkEE6CPXKH_cjs = require('./chunk-EE6CPXKH.cjs');
7
+ require('./chunk-ESLWRGAG.cjs');
8
+ require('./chunk-YREV3LGG.cjs');
9
+ var api = require('@opentelemetry/api');
10
+
11
+ var mismatchCounter;
12
+ function counter() {
13
+ if (!mismatchCounter) {
14
+ mismatchCounter = chunkWJH6IYU2_cjs.createCounter(chunkT5WRA76K_cjs.VALIDATION_METRICS.mismatches, {
15
+ description: "Input payloads that did not match their declared shape"
16
+ });
17
+ }
18
+ return mismatchCounter;
19
+ }
20
+ var listeners = /* @__PURE__ */ new Set();
21
+ function onValidationMismatch(handler) {
22
+ listeners.add(handler);
23
+ return () => {
24
+ listeners.delete(handler);
25
+ };
26
+ }
27
+ var truncate = (values) => values.slice(0, chunkT5WRA76K_cjs.VALIDATION_ISSUE_CAP).join(",");
28
+ function recordValidationMismatch(mismatch) {
29
+ try {
30
+ const paths = mismatch.issues.map((i) => i.path).filter(Boolean);
31
+ const codes = [...new Set(mismatch.issues.map((i) => i.code))];
32
+ const span = api.trace.getActiveSpan();
33
+ if (span) {
34
+ span.setAttributes({
35
+ [chunkT5WRA76K_cjs.VALIDATION_ATTR.name]: mismatch.name,
36
+ [chunkT5WRA76K_cjs.VALIDATION_ATTR.boundary]: mismatch.boundary,
37
+ [chunkT5WRA76K_cjs.VALIDATION_ATTR.mode]: mismatch.mode,
38
+ [chunkT5WRA76K_cjs.VALIDATION_ATTR.issueCount]: mismatch.issues.length,
39
+ [chunkT5WRA76K_cjs.VALIDATION_ATTR.issuePaths]: truncate(paths),
40
+ [chunkT5WRA76K_cjs.VALIDATION_ATTR.issueCodes]: truncate(codes),
41
+ ...mismatch.hash ? { [chunkT5WRA76K_cjs.VALIDATION_ATTR.hash]: mismatch.hash } : {},
42
+ ...mismatch.severity ? { [chunkT5WRA76K_cjs.VALIDATION_ATTR.severity]: mismatch.severity } : {}
43
+ });
44
+ }
45
+ try {
46
+ counter().add(1, {
47
+ boundary: mismatch.boundary,
48
+ validation: mismatch.name,
49
+ mode: mismatch.mode
50
+ });
51
+ } catch {
52
+ }
53
+ for (const listener of listeners) {
54
+ try {
55
+ listener(mismatch);
56
+ } catch {
57
+ }
58
+ }
59
+ } catch {
60
+ }
61
+ }
62
+ function formatValidationIssues(error) {
63
+ const raw = extractRawIssues(error);
64
+ return raw.map((issue) => toSafeIssue(issue));
65
+ }
66
+ function extractRawIssues(error) {
67
+ if (error && typeof error === "object") {
68
+ const candidate = error.issues ?? error.errors;
69
+ if (Array.isArray(candidate)) {
70
+ return candidate.filter(
71
+ (i) => i !== null && typeof i === "object"
72
+ );
73
+ }
74
+ }
75
+ return [];
76
+ }
77
+ function toSafeIssue(issue) {
78
+ const rawPath = issue.path;
79
+ const path = Array.isArray(rawPath) ? rawPath.map(String).join(".") : typeof rawPath === "string" ? rawPath : "";
80
+ const code = typeof issue.code === "string" ? issue.code : "invalid";
81
+ const expected = typeof issue.expected === "string" ? issue.expected : void 0;
82
+ return expected ? { path, code, expected } : { path, code };
83
+ }
84
+ function defaultRejectError(issues, name) {
85
+ return chunkEE6CPXKH_cjs.createStructuredError({
86
+ name: "ValidationError",
87
+ status: 400,
88
+ code: "validation_failed",
89
+ message: `Input for "${name}" did not match its declared shape.`,
90
+ why: `${issues.length} field(s) failed validation: ${issues.map((i) => i.path || "(root)").slice(0, chunkT5WRA76K_cjs.VALIDATION_ISSUE_CAP).join(", ")}.`,
91
+ fix: "Send a payload that matches the schema, or switch this validator to observe mode while you investigate.",
92
+ // PII-safe: paths + codes only, no received values.
93
+ details: { validation: name, issues }
94
+ });
95
+ }
96
+ function defineValidator(name, schema, options = {}) {
97
+ const mode = options.onMismatch ?? "reject";
98
+ const boundary = options.boundary ?? "input";
99
+ const hash = options.toJsonSchema ? chunkE6TERL5O_cjs.hashJson(options.toJsonSchema(schema)) : void 0;
100
+ const record = (issues) => {
101
+ recordValidationMismatch({
102
+ name,
103
+ boundary,
104
+ mode,
105
+ issues,
106
+ hash,
107
+ severity: options.severity
108
+ });
109
+ };
110
+ return {
111
+ name,
112
+ mode,
113
+ safeParse(input) {
114
+ const parsed = schema.safeParse(input);
115
+ if (parsed.success) return { success: true, data: parsed.data };
116
+ const issues = formatValidationIssues(parsed.error);
117
+ record(issues);
118
+ return { success: false, issues };
119
+ },
120
+ parse(input) {
121
+ const parsed = schema.safeParse(input);
122
+ if (parsed.success) return parsed.data;
123
+ const issues = formatValidationIssues(parsed.error);
124
+ record(issues);
125
+ if (mode === "reject") {
126
+ throw options.onReject?.(issues, name) ?? defaultRejectError(issues, name);
127
+ }
128
+ return input;
129
+ }
130
+ };
131
+ }
132
+
133
+ exports.defineValidator = defineValidator;
134
+ exports.formatValidationIssues = formatValidationIssues;
135
+ exports.onValidationMismatch = onValidationMismatch;
136
+ exports.recordValidationMismatch = recordValidationMismatch;
137
+ //# sourceMappingURL=validate.cjs.map
138
+ //# sourceMappingURL=validate.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/validate.ts"],"names":["createCounter","VALIDATION_METRICS","VALIDATION_ISSUE_CAP","trace","VALIDATION_ATTR","createStructuredError","hashJson"],"mappings":"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","file":"validate.cjs","sourcesContent":["/**\n * Validation telemetry — connect runtime input validation (Zod or any\n * `safeParse` schema) to your traces and metrics at the boundaries where bad\n * data actually enters: HTTP bodies, events, messages.\n *\n * Today a `safeParse` failure either throws (no span, no metric, no alert) or\n * is silently swallowed in a handler. `defineValidator` makes the mismatch\n * **observable** — a `validation.*` span attribute set and a counter\n * incremented — with a per-validator `observe` vs `reject` mode:\n *\n * - `reject` (default): record telemetry, then throw a structured 400-shaped\n * error so the boundary can fail cleanly.\n * - `observe`: record telemetry, return the raw input so the handler continues\n * — useful for measuring real-world drift before you enforce it.\n *\n * **Not a security feature by default.** A malformed body is usually a bug or\n * version skew, not an attack. Validation telemetry is first-class on its own\n * metric; escalation to the security path is a deliberate opt-in via\n * {@link onValidationMismatch} (e.g. wired by `autotel-audit`), never automatic.\n *\n * **PII-safe by construction.** Only field *paths*, issue *codes*, and the\n * declared *type* are ever recorded — never the offending value, and never a\n * validator's error `message` (which routinely embeds the received value).\n */\n\nimport { trace } from '@opentelemetry/api';\nimport { createCounter } from './metric-helpers';\nimport { createStructuredError, type StructuredError } from './structured-error';\nimport { hashJson } from './stable-hash';\nimport type { SchemaLike } from './define-event';\nimport {\n VALIDATION_ATTR,\n VALIDATION_ISSUE_CAP,\n VALIDATION_METRICS,\n} from './validation-attributes';\n\nexport type { SchemaLike } from './define-event';\n\nexport type ValidationMode = 'observe' | 'reject';\nexport type ValidationSeverity = 'info' | 'warning' | 'error';\n\n/** A single failing field, stripped of any payload values. */\nexport interface ValidationIssue {\n /** Dotted field path, e.g. `items.0.price`. Never a value. */\n path: string;\n /** Issue code (e.g. Zod's `invalid_type`, `too_small`). Never a value. */\n code: string;\n /** Declared type/constraint summary, e.g. `string`. Never a received value. */\n expected?: string;\n}\n\n/** Everything the recorder needs — already PII-stripped by the caller. */\nexport interface ValidationMismatch {\n /** Contract id, e.g. `POST /orders` or `order.placed`. */\n name: string;\n boundary: string;\n mode: ValidationMode;\n issues: ValidationIssue[];\n hash?: string;\n severity?: ValidationSeverity;\n}\n\nlet mismatchCounter: ReturnType<typeof createCounter> | undefined;\nfunction counter(): ReturnType<typeof createCounter> {\n if (!mismatchCounter) {\n mismatchCounter = createCounter(VALIDATION_METRICS.mismatches, {\n description: 'Input payloads that did not match their declared shape',\n });\n }\n return mismatchCounter;\n}\n\ntype MismatchListener = (mismatch: ValidationMismatch) => void;\nconst listeners = new Set<MismatchListener>();\n\n/**\n * Register an explicit handler called on every recorded mismatch — the opt-in\n * seam for escalating to security events, a webhook, or a custom sink. There is\n * no automatic, package-presence-driven escalation: nothing fires here unless\n * you (or a package you wire up) register a handler.\n *\n * Multiple subscribers coexist: a package (e.g. `autotel-audit` bridging to\n * security events) and your own app code (a webhook, a logger) can both\n * register and all fire. Returns an unsubscribe fn that removes only this\n * handler; registering the same function twice is a no-op (Set semantics).\n */\nexport function onValidationMismatch(handler: MismatchListener): () => void {\n listeners.add(handler);\n return () => {\n listeners.delete(handler);\n };\n}\n\nconst truncate = (values: string[]): string =>\n values.slice(0, VALIDATION_ISSUE_CAP).join(',');\n\n/**\n * Record a validation mismatch as telemetry: `validation.*` attributes on the\n * active span (if any) and an increment on `autotel.validation.mismatches`.\n * Fail-open — never throws, so instrumentation can't break the boundary.\n */\nexport function recordValidationMismatch(mismatch: ValidationMismatch): void {\n try {\n const paths = mismatch.issues.map((i) => i.path).filter(Boolean);\n const codes = [...new Set(mismatch.issues.map((i) => i.code))];\n\n const span = trace.getActiveSpan();\n if (span) {\n span.setAttributes({\n [VALIDATION_ATTR.name]: mismatch.name,\n [VALIDATION_ATTR.boundary]: mismatch.boundary,\n [VALIDATION_ATTR.mode]: mismatch.mode,\n [VALIDATION_ATTR.issueCount]: mismatch.issues.length,\n [VALIDATION_ATTR.issuePaths]: truncate(paths),\n [VALIDATION_ATTR.issueCodes]: truncate(codes),\n ...(mismatch.hash ? { [VALIDATION_ATTR.hash]: mismatch.hash } : {}),\n ...(mismatch.severity\n ? { [VALIDATION_ATTR.severity]: mismatch.severity }\n : {}),\n });\n }\n\n try {\n counter().add(1, {\n boundary: mismatch.boundary,\n validation: mismatch.name,\n mode: mismatch.mode,\n });\n } catch {\n // meter not initialised yet — skip the count, keep the span attrs\n }\n\n // Dispatch to every subscriber with per-listener fault isolation: one\n // throwing subscriber must not starve its peers or break the boundary.\n // Set iteration tolerates concurrent (un)subscription safely.\n for (const listener of listeners) {\n try {\n listener(mismatch);\n } catch {\n // a misbehaving subscriber must not break the boundary or its peers\n }\n }\n } catch {\n // fail-open: telemetry must never break the validated boundary\n }\n}\n\n/**\n * Normalise an arbitrary validation error into PII-safe issues. Reads only\n * `path`, `code`, and (when it is a declared type name) `expected` — and never\n * `message`, `received`, or any value-bearing field. Understands the Zod shape\n * (`error.issues`) and a generic `error.errors` fallback; returns `[]` for\n * anything unrecognised.\n */\nexport function formatValidationIssues(error: unknown): ValidationIssue[] {\n const raw = extractRawIssues(error);\n return raw.map((issue) => toSafeIssue(issue));\n}\n\nfunction extractRawIssues(error: unknown): Array<Record<string, unknown>> {\n if (error && typeof error === 'object') {\n const candidate =\n (error as { issues?: unknown }).issues ??\n (error as { errors?: unknown }).errors;\n if (Array.isArray(candidate)) {\n return candidate.filter(\n (i): i is Record<string, unknown> => i !== null && typeof i === 'object',\n );\n }\n }\n return [];\n}\n\nfunction toSafeIssue(issue: Record<string, unknown>): ValidationIssue {\n const rawPath = issue.path;\n const path = Array.isArray(rawPath)\n ? rawPath.map(String).join('.')\n : typeof rawPath === 'string'\n ? rawPath\n : '';\n const code = typeof issue.code === 'string' ? issue.code : 'invalid';\n // `expected` is a declared type name in Zod (e.g. 'string'); safe. We never\n // read `received`/`message`/`value`, which can carry the offending payload.\n const expected =\n typeof issue.expected === 'string' ? issue.expected : undefined;\n return expected ? { path, code, expected } : { path, code };\n}\n\nexport interface DefineValidatorOptions<S> {\n /** Where validation runs. Defaults to `input`. */\n boundary?: string;\n /** `reject` (default): record then throw. `observe`: record then continue. */\n onMismatch?: ValidationMode;\n /** Project the schema to JSON Schema for a stable `validation.hash`. */\n toJsonSchema?: (schema: S) => unknown;\n severity?: ValidationSeverity;\n /** Build the error thrown in `reject` mode (defaults to a 400 structured error). */\n onReject?: (issues: ValidationIssue[], name: string) => Error;\n}\n\nexport type ValidatorResult<T> =\n | { success: true; data: T }\n | { success: false; issues: ValidationIssue[] };\n\nexport interface Validator<T> {\n readonly name: string;\n readonly mode: ValidationMode;\n /** Validate and record on failure; never throws. */\n safeParse(input: unknown): ValidatorResult<T>;\n /**\n * Validate, record on failure, then apply the mode: `reject` throws,\n * `observe` returns the raw input so the handler can continue.\n */\n parse(input: unknown): T;\n}\n\nfunction defaultRejectError(\n issues: ValidationIssue[],\n name: string,\n): StructuredError {\n return createStructuredError({\n name: 'ValidationError',\n status: 400,\n code: 'validation_failed',\n message: `Input for \"${name}\" did not match its declared shape.`,\n why: `${issues.length} field(s) failed validation: ${issues\n .map((i) => i.path || '(root)')\n .slice(0, VALIDATION_ISSUE_CAP)\n .join(', ')}.`,\n fix: 'Send a payload that matches the schema, or switch this validator to observe mode while you investigate.',\n // PII-safe: paths + codes only, no received values.\n details: { validation: name, issues },\n });\n}\n\n/**\n * Declare an expected input shape once and get a validator that records every\n * mismatch as telemetry.\n *\n * @example\n * ```ts\n * import { z } from 'zod';\n * import { defineValidator } from 'autotel/validate';\n *\n * const OrderBody = defineValidator('POST /orders', z.object({\n * items: z.array(z.object({ sku: z.string(), qty: z.number().int() })),\n * }), { boundary: 'http', toJsonSchema: (s) => z.toJSONSchema(s) });\n *\n * // reject mode (default): records + throws a 400-shaped structured error\n * const order = OrderBody.parse(req.body);\n *\n * // observe mode: records, returns the result, never throws\n * const result = OrderBody.safeParse(req.body);\n * if (!result.success) metrics.onDrift(result.issues);\n * ```\n */\nexport function defineValidator<T, S extends SchemaLike<T>>(\n name: string,\n schema: S,\n options: DefineValidatorOptions<S> = {},\n): Validator<T> {\n const mode = options.onMismatch ?? 'reject';\n const boundary = options.boundary ?? 'input';\n const hash = options.toJsonSchema\n ? hashJson(options.toJsonSchema(schema))\n : undefined;\n\n const record = (issues: ValidationIssue[]): void => {\n recordValidationMismatch({\n name,\n boundary,\n mode,\n issues,\n hash,\n severity: options.severity,\n });\n };\n\n return {\n name,\n mode,\n safeParse(input: unknown): ValidatorResult<T> {\n const parsed = schema.safeParse(input);\n if (parsed.success) return { success: true, data: parsed.data };\n const issues = formatValidationIssues(parsed.error);\n record(issues);\n return { success: false, issues };\n },\n parse(input: unknown): T {\n const parsed = schema.safeParse(input);\n if (parsed.success) return parsed.data;\n const issues = formatValidationIssues(parsed.error);\n record(issues);\n if (mode === 'reject') {\n throw options.onReject?.(issues, name) ?? defaultRejectError(issues, name);\n }\n // observe: continue with the raw input (documented type caveat)\n return input as T;\n },\n };\n}\n"]}
@@ -0,0 +1,129 @@
1
+ import { S as SchemaLike } from './define-event-ClP3T1Jx.cjs';
2
+ import './event-subscriber.cjs';
3
+
4
+ /**
5
+ * Validation telemetry — connect runtime input validation (Zod or any
6
+ * `safeParse` schema) to your traces and metrics at the boundaries where bad
7
+ * data actually enters: HTTP bodies, events, messages.
8
+ *
9
+ * Today a `safeParse` failure either throws (no span, no metric, no alert) or
10
+ * is silently swallowed in a handler. `defineValidator` makes the mismatch
11
+ * **observable** — a `validation.*` span attribute set and a counter
12
+ * incremented — with a per-validator `observe` vs `reject` mode:
13
+ *
14
+ * - `reject` (default): record telemetry, then throw a structured 400-shaped
15
+ * error so the boundary can fail cleanly.
16
+ * - `observe`: record telemetry, return the raw input so the handler continues
17
+ * — useful for measuring real-world drift before you enforce it.
18
+ *
19
+ * **Not a security feature by default.** A malformed body is usually a bug or
20
+ * version skew, not an attack. Validation telemetry is first-class on its own
21
+ * metric; escalation to the security path is a deliberate opt-in via
22
+ * {@link onValidationMismatch} (e.g. wired by `autotel-audit`), never automatic.
23
+ *
24
+ * **PII-safe by construction.** Only field *paths*, issue *codes*, and the
25
+ * declared *type* are ever recorded — never the offending value, and never a
26
+ * validator's error `message` (which routinely embeds the received value).
27
+ */
28
+
29
+ type ValidationMode = 'observe' | 'reject';
30
+ type ValidationSeverity = 'info' | 'warning' | 'error';
31
+ /** A single failing field, stripped of any payload values. */
32
+ interface ValidationIssue {
33
+ /** Dotted field path, e.g. `items.0.price`. Never a value. */
34
+ path: string;
35
+ /** Issue code (e.g. Zod's `invalid_type`, `too_small`). Never a value. */
36
+ code: string;
37
+ /** Declared type/constraint summary, e.g. `string`. Never a received value. */
38
+ expected?: string;
39
+ }
40
+ /** Everything the recorder needs — already PII-stripped by the caller. */
41
+ interface ValidationMismatch {
42
+ /** Contract id, e.g. `POST /orders` or `order.placed`. */
43
+ name: string;
44
+ boundary: string;
45
+ mode: ValidationMode;
46
+ issues: ValidationIssue[];
47
+ hash?: string;
48
+ severity?: ValidationSeverity;
49
+ }
50
+ type MismatchListener = (mismatch: ValidationMismatch) => void;
51
+ /**
52
+ * Register an explicit handler called on every recorded mismatch — the opt-in
53
+ * seam for escalating to security events, a webhook, or a custom sink. There is
54
+ * no automatic, package-presence-driven escalation: nothing fires here unless
55
+ * you (or a package you wire up) register a handler.
56
+ *
57
+ * Multiple subscribers coexist: a package (e.g. `autotel-audit` bridging to
58
+ * security events) and your own app code (a webhook, a logger) can both
59
+ * register and all fire. Returns an unsubscribe fn that removes only this
60
+ * handler; registering the same function twice is a no-op (Set semantics).
61
+ */
62
+ declare function onValidationMismatch(handler: MismatchListener): () => void;
63
+ /**
64
+ * Record a validation mismatch as telemetry: `validation.*` attributes on the
65
+ * active span (if any) and an increment on `autotel.validation.mismatches`.
66
+ * Fail-open — never throws, so instrumentation can't break the boundary.
67
+ */
68
+ declare function recordValidationMismatch(mismatch: ValidationMismatch): void;
69
+ /**
70
+ * Normalise an arbitrary validation error into PII-safe issues. Reads only
71
+ * `path`, `code`, and (when it is a declared type name) `expected` — and never
72
+ * `message`, `received`, or any value-bearing field. Understands the Zod shape
73
+ * (`error.issues`) and a generic `error.errors` fallback; returns `[]` for
74
+ * anything unrecognised.
75
+ */
76
+ declare function formatValidationIssues(error: unknown): ValidationIssue[];
77
+ interface DefineValidatorOptions<S> {
78
+ /** Where validation runs. Defaults to `input`. */
79
+ boundary?: string;
80
+ /** `reject` (default): record then throw. `observe`: record then continue. */
81
+ onMismatch?: ValidationMode;
82
+ /** Project the schema to JSON Schema for a stable `validation.hash`. */
83
+ toJsonSchema?: (schema: S) => unknown;
84
+ severity?: ValidationSeverity;
85
+ /** Build the error thrown in `reject` mode (defaults to a 400 structured error). */
86
+ onReject?: (issues: ValidationIssue[], name: string) => Error;
87
+ }
88
+ type ValidatorResult<T> = {
89
+ success: true;
90
+ data: T;
91
+ } | {
92
+ success: false;
93
+ issues: ValidationIssue[];
94
+ };
95
+ interface Validator<T> {
96
+ readonly name: string;
97
+ readonly mode: ValidationMode;
98
+ /** Validate and record on failure; never throws. */
99
+ safeParse(input: unknown): ValidatorResult<T>;
100
+ /**
101
+ * Validate, record on failure, then apply the mode: `reject` throws,
102
+ * `observe` returns the raw input so the handler can continue.
103
+ */
104
+ parse(input: unknown): T;
105
+ }
106
+ /**
107
+ * Declare an expected input shape once and get a validator that records every
108
+ * mismatch as telemetry.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * import { z } from 'zod';
113
+ * import { defineValidator } from 'autotel/validate';
114
+ *
115
+ * const OrderBody = defineValidator('POST /orders', z.object({
116
+ * items: z.array(z.object({ sku: z.string(), qty: z.number().int() })),
117
+ * }), { boundary: 'http', toJsonSchema: (s) => z.toJSONSchema(s) });
118
+ *
119
+ * // reject mode (default): records + throws a 400-shaped structured error
120
+ * const order = OrderBody.parse(req.body);
121
+ *
122
+ * // observe mode: records, returns the result, never throws
123
+ * const result = OrderBody.safeParse(req.body);
124
+ * if (!result.success) metrics.onDrift(result.issues);
125
+ * ```
126
+ */
127
+ declare function defineValidator<T, S extends SchemaLike<T>>(name: string, schema: S, options?: DefineValidatorOptions<S>): Validator<T>;
128
+
129
+ export { type DefineValidatorOptions, SchemaLike, type ValidationIssue, type ValidationMismatch, type ValidationMode, type ValidationSeverity, type Validator, type ValidatorResult, defineValidator, formatValidationIssues, onValidationMismatch, recordValidationMismatch };
@@ -0,0 +1,129 @@
1
+ import { S as SchemaLike } from './define-event-BL6Li7CM.js';
2
+ import './event-subscriber.js';
3
+
4
+ /**
5
+ * Validation telemetry — connect runtime input validation (Zod or any
6
+ * `safeParse` schema) to your traces and metrics at the boundaries where bad
7
+ * data actually enters: HTTP bodies, events, messages.
8
+ *
9
+ * Today a `safeParse` failure either throws (no span, no metric, no alert) or
10
+ * is silently swallowed in a handler. `defineValidator` makes the mismatch
11
+ * **observable** — a `validation.*` span attribute set and a counter
12
+ * incremented — with a per-validator `observe` vs `reject` mode:
13
+ *
14
+ * - `reject` (default): record telemetry, then throw a structured 400-shaped
15
+ * error so the boundary can fail cleanly.
16
+ * - `observe`: record telemetry, return the raw input so the handler continues
17
+ * — useful for measuring real-world drift before you enforce it.
18
+ *
19
+ * **Not a security feature by default.** A malformed body is usually a bug or
20
+ * version skew, not an attack. Validation telemetry is first-class on its own
21
+ * metric; escalation to the security path is a deliberate opt-in via
22
+ * {@link onValidationMismatch} (e.g. wired by `autotel-audit`), never automatic.
23
+ *
24
+ * **PII-safe by construction.** Only field *paths*, issue *codes*, and the
25
+ * declared *type* are ever recorded — never the offending value, and never a
26
+ * validator's error `message` (which routinely embeds the received value).
27
+ */
28
+
29
+ type ValidationMode = 'observe' | 'reject';
30
+ type ValidationSeverity = 'info' | 'warning' | 'error';
31
+ /** A single failing field, stripped of any payload values. */
32
+ interface ValidationIssue {
33
+ /** Dotted field path, e.g. `items.0.price`. Never a value. */
34
+ path: string;
35
+ /** Issue code (e.g. Zod's `invalid_type`, `too_small`). Never a value. */
36
+ code: string;
37
+ /** Declared type/constraint summary, e.g. `string`. Never a received value. */
38
+ expected?: string;
39
+ }
40
+ /** Everything the recorder needs — already PII-stripped by the caller. */
41
+ interface ValidationMismatch {
42
+ /** Contract id, e.g. `POST /orders` or `order.placed`. */
43
+ name: string;
44
+ boundary: string;
45
+ mode: ValidationMode;
46
+ issues: ValidationIssue[];
47
+ hash?: string;
48
+ severity?: ValidationSeverity;
49
+ }
50
+ type MismatchListener = (mismatch: ValidationMismatch) => void;
51
+ /**
52
+ * Register an explicit handler called on every recorded mismatch — the opt-in
53
+ * seam for escalating to security events, a webhook, or a custom sink. There is
54
+ * no automatic, package-presence-driven escalation: nothing fires here unless
55
+ * you (or a package you wire up) register a handler.
56
+ *
57
+ * Multiple subscribers coexist: a package (e.g. `autotel-audit` bridging to
58
+ * security events) and your own app code (a webhook, a logger) can both
59
+ * register and all fire. Returns an unsubscribe fn that removes only this
60
+ * handler; registering the same function twice is a no-op (Set semantics).
61
+ */
62
+ declare function onValidationMismatch(handler: MismatchListener): () => void;
63
+ /**
64
+ * Record a validation mismatch as telemetry: `validation.*` attributes on the
65
+ * active span (if any) and an increment on `autotel.validation.mismatches`.
66
+ * Fail-open — never throws, so instrumentation can't break the boundary.
67
+ */
68
+ declare function recordValidationMismatch(mismatch: ValidationMismatch): void;
69
+ /**
70
+ * Normalise an arbitrary validation error into PII-safe issues. Reads only
71
+ * `path`, `code`, and (when it is a declared type name) `expected` — and never
72
+ * `message`, `received`, or any value-bearing field. Understands the Zod shape
73
+ * (`error.issues`) and a generic `error.errors` fallback; returns `[]` for
74
+ * anything unrecognised.
75
+ */
76
+ declare function formatValidationIssues(error: unknown): ValidationIssue[];
77
+ interface DefineValidatorOptions<S> {
78
+ /** Where validation runs. Defaults to `input`. */
79
+ boundary?: string;
80
+ /** `reject` (default): record then throw. `observe`: record then continue. */
81
+ onMismatch?: ValidationMode;
82
+ /** Project the schema to JSON Schema for a stable `validation.hash`. */
83
+ toJsonSchema?: (schema: S) => unknown;
84
+ severity?: ValidationSeverity;
85
+ /** Build the error thrown in `reject` mode (defaults to a 400 structured error). */
86
+ onReject?: (issues: ValidationIssue[], name: string) => Error;
87
+ }
88
+ type ValidatorResult<T> = {
89
+ success: true;
90
+ data: T;
91
+ } | {
92
+ success: false;
93
+ issues: ValidationIssue[];
94
+ };
95
+ interface Validator<T> {
96
+ readonly name: string;
97
+ readonly mode: ValidationMode;
98
+ /** Validate and record on failure; never throws. */
99
+ safeParse(input: unknown): ValidatorResult<T>;
100
+ /**
101
+ * Validate, record on failure, then apply the mode: `reject` throws,
102
+ * `observe` returns the raw input so the handler can continue.
103
+ */
104
+ parse(input: unknown): T;
105
+ }
106
+ /**
107
+ * Declare an expected input shape once and get a validator that records every
108
+ * mismatch as telemetry.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * import { z } from 'zod';
113
+ * import { defineValidator } from 'autotel/validate';
114
+ *
115
+ * const OrderBody = defineValidator('POST /orders', z.object({
116
+ * items: z.array(z.object({ sku: z.string(), qty: z.number().int() })),
117
+ * }), { boundary: 'http', toJsonSchema: (s) => z.toJSONSchema(s) });
118
+ *
119
+ * // reject mode (default): records + throws a 400-shaped structured error
120
+ * const order = OrderBody.parse(req.body);
121
+ *
122
+ * // observe mode: records, returns the result, never throws
123
+ * const result = OrderBody.safeParse(req.body);
124
+ * if (!result.success) metrics.onDrift(result.issues);
125
+ * ```
126
+ */
127
+ declare function defineValidator<T, S extends SchemaLike<T>>(name: string, schema: S, options?: DefineValidatorOptions<S>): Validator<T>;
128
+
129
+ export { type DefineValidatorOptions, SchemaLike, type ValidationIssue, type ValidationMismatch, type ValidationMode, type ValidationSeverity, type Validator, type ValidatorResult, defineValidator, formatValidationIssues, onValidationMismatch, recordValidationMismatch };