@juspay/neurolink 12.14.18 → 12.14.20

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.
@@ -7,6 +7,229 @@ import {
7
7
  validateProxyTelemetryBackend,
8
8
  } from "./proxy-telemetry-backend.mjs";
9
9
 
10
+ /** @param {string} kind @param {Record<string, unknown>} value @param {string | undefined} eventId */
11
+ function logicalMetadataIdentity(kind, value, eventId) {
12
+ if (kind === "request_final") {
13
+ return typeof value.requestId === "string" && value.requestId
14
+ ? `request:${value.requestId}`
15
+ : undefined;
16
+ }
17
+ if (kind === "body_capture_index") {
18
+ return typeof value.captureId === "string" && value.captureId
19
+ ? `capture:${value.captureId}`
20
+ : undefined;
21
+ }
22
+ if (kind === "telemetry_delivery") {
23
+ return typeof value.id === "string" && value.id
24
+ ? `delivery:${value.id}`
25
+ : undefined;
26
+ }
27
+ if (
28
+ kind === "lifecycle" &&
29
+ typeof value.processInstanceId === "string" &&
30
+ value.processInstanceId &&
31
+ Number.isSafeInteger(value.sequence)
32
+ ) {
33
+ return `lifecycle:${value.processInstanceId}:${value.sequence}`;
34
+ }
35
+ return eventId ? `event:${eventId}` : undefined;
36
+ }
37
+
38
+ /**
39
+ * Collapse only exact OTLP retries. A reused event identity with changed data,
40
+ * or two producer events for one logical record, remains a hard diagnostic.
41
+ * @param {string} kind
42
+ * @param {Array<Record<string, unknown>>} rows
43
+ */
44
+ function normalizeMetadataRows(kind, rows) {
45
+ const byEvent = new Map();
46
+ const byPhysicalRow = new Map();
47
+ const parsed = [];
48
+ const conflicts = [];
49
+ let exactRetryDuplicates = 0;
50
+ let missingEventIds = 0;
51
+ for (const row of rows) {
52
+ if (typeof row.body !== "string") {
53
+ throw new Error("Invalid stored metadata body");
54
+ }
55
+ const value = JSON.parse(row.body);
56
+ if (
57
+ !value ||
58
+ typeof value !== "object" ||
59
+ (kind === "telemetry_delivery"
60
+ ? typeof value.id !== "string" ||
61
+ typeof value.at !== "string" ||
62
+ !Number.isFinite(Date.parse(value.at))
63
+ : typeof value.requestId !== "string")
64
+ ) {
65
+ throw new Error("Stored telemetry metadata has an invalid schema");
66
+ }
67
+ const eventId =
68
+ typeof row.proxy_event_id === "string" && row.proxy_event_id
69
+ ? row.proxy_event_id
70
+ : undefined;
71
+ const physicalIdentity = JSON.stringify([
72
+ row._timestamp,
73
+ row.service_instance_id,
74
+ row.request_id,
75
+ row.body,
76
+ ]);
77
+ const priorEvent = eventId ? byEvent.get(eventId) : undefined;
78
+ if (priorEvent) {
79
+ if (priorEvent.physicalIdentity === physicalIdentity) {
80
+ exactRetryDuplicates++;
81
+ } else {
82
+ conflicts.push({
83
+ identity: `event:${eventId}`,
84
+ reason: "event_identity_conflict",
85
+ });
86
+ }
87
+ continue;
88
+ }
89
+ if (!eventId) {
90
+ missingEventIds++;
91
+ if (byPhysicalRow.has(physicalIdentity)) {
92
+ exactRetryDuplicates++;
93
+ continue;
94
+ }
95
+ byPhysicalRow.set(physicalIdentity, true);
96
+ } else {
97
+ byEvent.set(eventId, { physicalIdentity });
98
+ }
99
+ parsed.push({
100
+ eventId,
101
+ rawBody: row.body,
102
+ value: {
103
+ ...value,
104
+ recordedAtMicroseconds: Number(row._timestamp),
105
+ ...(eventId ? { proxyEventId: eventId } : {}),
106
+ },
107
+ });
108
+ }
109
+
110
+ const byLogicalIdentity = new Map();
111
+ const records = [];
112
+ let invalidLogicalIdentities = 0;
113
+ for (const item of parsed) {
114
+ const identity = logicalMetadataIdentity(kind, item.value, item.eventId);
115
+ if (!identity) {
116
+ invalidLogicalIdentities++;
117
+ continue;
118
+ }
119
+ const prior = byLogicalIdentity.get(identity);
120
+ if (prior) {
121
+ conflicts.push({
122
+ identity,
123
+ reason:
124
+ prior.rawBody === item.rawBody
125
+ ? "producer_duplicate"
126
+ : "logical_identity_conflict",
127
+ firstEventId: prior.eventId,
128
+ duplicateEventId: item.eventId,
129
+ });
130
+ continue;
131
+ }
132
+ byLogicalIdentity.set(identity, item);
133
+ records.push(item.value);
134
+ }
135
+ return {
136
+ records,
137
+ diagnostics: {
138
+ rawRecords: rows.length,
139
+ logicalRecords: records.length,
140
+ exactRetryDuplicates,
141
+ missingEventIds,
142
+ invalidLogicalIdentities,
143
+ conflicts,
144
+ },
145
+ };
146
+ }
147
+
148
+ /** @param {Array<Record<string, unknown>>} rows */
149
+ function normalizeBodyChunkRows(rows) {
150
+ const byEvent = new Map();
151
+ const byPhysicalRow = new Map();
152
+ const parsed = [];
153
+ const conflicts = [];
154
+ let exactRetryDuplicates = 0;
155
+ let missingEventIds = 0;
156
+ for (const row of rows) {
157
+ const eventId =
158
+ typeof row.proxy_event_id === "string" && row.proxy_event_id
159
+ ? row.proxy_event_id
160
+ : undefined;
161
+ const physicalIdentity = JSON.stringify([
162
+ row._timestamp,
163
+ row.body_capture_id,
164
+ row.body_chunk_index,
165
+ row.body_chunk_count,
166
+ row.body,
167
+ ]);
168
+ const priorEvent = eventId ? byEvent.get(eventId) : undefined;
169
+ if (priorEvent) {
170
+ if (priorEvent === physicalIdentity) {
171
+ exactRetryDuplicates++;
172
+ } else {
173
+ conflicts.push({
174
+ identity: `event:${eventId}`,
175
+ reason: "event_identity_conflict",
176
+ });
177
+ }
178
+ continue;
179
+ }
180
+ if (!eventId) {
181
+ missingEventIds++;
182
+ if (byPhysicalRow.has(physicalIdentity)) {
183
+ exactRetryDuplicates++;
184
+ continue;
185
+ }
186
+ byPhysicalRow.set(physicalIdentity, true);
187
+ } else {
188
+ byEvent.set(eventId, physicalIdentity);
189
+ }
190
+ parsed.push({ eventId, physicalIdentity, row });
191
+ }
192
+
193
+ const byChunkIndex = new Map();
194
+ const chunks = [];
195
+ for (const item of parsed) {
196
+ const index = Number(item.row.body_chunk_index);
197
+ if (!Number.isSafeInteger(index) || index < 0) {
198
+ conflicts.push({ identity: String(index), reason: "invalid_chunk_index" });
199
+ continue;
200
+ }
201
+ const prior = byChunkIndex.get(index);
202
+ if (prior) {
203
+ conflicts.push({
204
+ identity: `chunk:${index}`,
205
+ reason:
206
+ prior.physicalIdentity === item.physicalIdentity
207
+ ? "producer_duplicate"
208
+ : "logical_identity_conflict",
209
+ firstEventId: prior.eventId,
210
+ duplicateEventId: item.eventId,
211
+ });
212
+ continue;
213
+ }
214
+ byChunkIndex.set(index, item);
215
+ chunks.push(item.row);
216
+ }
217
+ chunks.sort(
218
+ (left, right) =>
219
+ Number(left.body_chunk_index) - Number(right.body_chunk_index),
220
+ );
221
+ return {
222
+ chunks,
223
+ diagnostics: {
224
+ rawChunks: rows.length,
225
+ storedChunks: chunks.length,
226
+ exactRetryDuplicates,
227
+ missingEventIds,
228
+ conflicts,
229
+ },
230
+ };
231
+ }
232
+
10
233
  /** Empty traffic, unavailable evidence, and partial queries cannot become a green check.
11
234
  * @param {import("../../src/lib/types/index.js").ProxyTelemetryDoctorOptions} options
12
235
  */
@@ -141,6 +364,8 @@ export async function checkProxyTelemetry({
141
364
  }
142
365
  /** @type {Record<string, import("../../src/lib/types/index.js").ProxyTelemetryStoredRecord[]>} */
143
366
  const history = Object.create(null);
367
+ /** @type {Record<string, {rawRecords: number, logicalRecords: number, exactRetryDuplicates: number, missingEventIds: number, invalidLogicalIdentities: number, conflicts: unknown[]}>} */
368
+ const historyDiagnostics = Object.create(null);
144
369
  for (const kind of [
145
370
  "request_final",
146
371
  "body_capture_index",
@@ -165,25 +390,37 @@ export async function checkProxyTelemetry({
165
390
  budget,
166
391
  });
167
392
  queries.push(...result.queries.map((q) => ({ ...q, kind })));
168
- history[kind] = result.records.map((row) => {
169
- if (typeof row.body !== "string") {
170
- throw new Error("Invalid stored metadata body");
171
- }
172
- const value = JSON.parse(row.body);
173
- if (
174
- !value ||
175
- typeof value !== "object" ||
176
- (kind === "telemetry_delivery"
177
- ? typeof value.id !== "string" ||
178
- typeof value.at !== "string" ||
179
- !Number.isFinite(Date.parse(value.at))
180
- : typeof value.requestId !== "string")
181
- ) {
182
- throw new Error("Stored telemetry metadata has an invalid schema");
183
- }
184
- return { ...value, recordedAtMicroseconds: Number(row._timestamp) };
185
- });
393
+ const normalized = normalizeMetadataRows(kind, result.records);
394
+ history[kind] = normalized.records;
395
+ historyDiagnostics[kind] = normalized.diagnostics;
186
396
  }
397
+ const identityConflicts = Object.values(historyDiagnostics).reduce(
398
+ (sum, diagnostic) =>
399
+ sum + diagnostic.conflicts.length + diagnostic.invalidLogicalIdentities,
400
+ 0,
401
+ );
402
+ const missingEventIds = Object.values(historyDiagnostics).reduce(
403
+ (sum, diagnostic) => sum + diagnostic.missingEventIds,
404
+ 0,
405
+ );
406
+ add(
407
+ "backend_row_identity",
408
+ identityConflicts
409
+ ? "fail"
410
+ : missingEventIds
411
+ ? "unverified"
412
+ : "pass",
413
+ {
414
+ scope: "selected metadata and bounded capture-settling windows",
415
+ exactRetryDuplicates: Object.values(historyDiagnostics).reduce(
416
+ (sum, diagnostic) => sum + diagnostic.exactRetryDuplicates,
417
+ 0,
418
+ ),
419
+ missingEventIds,
420
+ conflicts: identityConflicts,
421
+ byKind: historyDiagnostics,
422
+ },
423
+ );
187
424
  /** @param {{at?: unknown}} row */
188
425
  const failedInsideInterval = (row) => {
189
426
  const at = Date.parse(typeof row.at === "string" ? row.at : "");
@@ -312,14 +549,23 @@ export async function checkProxyTelemetry({
312
549
  );
313
550
  const finals = history.request_final,
314
551
  unique = new Set(finals.map((r) => r.requestId));
552
+ const finalDiagnostics = historyDiagnostics.request_final;
315
553
  add(
316
554
  "final_uniqueness",
317
555
  !finals.length
318
556
  ? "unverified"
319
- : unique.size === finals.length
557
+ : unique.size === finals.length &&
558
+ !finalDiagnostics.conflicts.length &&
559
+ !finalDiagnostics.invalidLogicalIdentities
320
560
  ? "pass"
321
561
  : "fail",
322
- { records: finals.length, uniqueRequestIds: unique.size },
562
+ {
563
+ records: finals.length,
564
+ rawRecords: finalDiagnostics.rawRecords,
565
+ exactRetryDuplicates: finalDiagnostics.exactRetryDuplicates,
566
+ uniqueRequestIds: unique.size,
567
+ producerOrConflictingDuplicates: finalDiagnostics.conflicts.length,
568
+ },
323
569
  );
324
570
  const terminalEvents = history.lifecycle.filter(
325
571
  (row) => row.event === "request_terminal",
@@ -493,8 +739,8 @@ export async function checkProxyTelemetry({
493
739
  ) {
494
740
  throw new Error("Capture verification exceeds the 8 MiB per-body bound");
495
741
  }
496
- const chunks = await query(
497
- `SELECT body_chunk_index, body_chunk_count, body FROM "${backend.stream}" WHERE proxy_record_kind='body' AND body_capture_id='${index.captureId}' ORDER BY body_chunk_index ASC`,
742
+ const rawChunks = await query(
743
+ `SELECT _timestamp, proxy_event_id, body_capture_id, body_chunk_index, body_chunk_count, body FROM "${backend.stream}" WHERE proxy_record_kind='body' AND body_capture_id='${index.captureId}' ORDER BY body_chunk_index ASC, proxy_event_id ASC`,
498
744
  "logs",
499
745
  {
500
746
  startTime: startTime - 120e6,
@@ -502,9 +748,11 @@ export async function checkProxyTelemetry({
502
748
  size: 1000,
503
749
  },
504
750
  );
505
- if (chunks.length >= 1000) {
751
+ if (rawChunks.length >= 1000) {
506
752
  throw new Error("Capture verification exceeded its chunk budget");
507
753
  }
754
+ const normalizedChunks = normalizeBodyChunkRows(rawChunks);
755
+ const chunks = normalizedChunks.chunks;
508
756
  const bytes = chunks.reduce(
509
757
  (sum, r) =>
510
758
  sum +
@@ -526,10 +774,17 @@ export async function checkProxyTelemetry({
526
774
  captureId: index.captureId,
527
775
  expectedChunks: expected,
528
776
  storedChunks: chunks.length,
777
+ rawStoredChunks: rawChunks.length,
778
+ exactRetryDuplicates:
779
+ normalizedChunks.diagnostics.exactRetryDuplicates,
780
+ missingEventIds: normalizedChunks.diagnostics.missingEventIds,
781
+ identityConflicts: normalizedChunks.diagnostics.conflicts,
529
782
  verified:
530
783
  expected > 0 &&
531
784
  expected === chunks.length &&
532
785
  contiguous &&
786
+ normalizedChunks.diagnostics.missingEventIds === 0 &&
787
+ normalizedChunks.diagnostics.conflicts.length === 0 &&
533
788
  Buffer.byteLength(raw) === index.redactedBodyBytes &&
534
789
  actualSha256 === index.bodySha256,
535
790
  });
@@ -601,6 +856,26 @@ export async function checkProxyTelemetry({
601
856
  return [match?.[1] ?? "invalid_sample", Number(match?.[2])];
602
857
  }),
603
858
  );
859
+ const counterEntries = Object.entries(counters);
860
+ const invalidCounters = counterEntries.filter(
861
+ ([, value]) => !Number.isFinite(value) || value < 0,
862
+ );
863
+ const consequentialFailures = counterEntries.filter(
864
+ ([key, value]) =>
865
+ value > 0 &&
866
+ (/^otelcol_(exporter_enqueue_failed|receiver_(refused|failed))/.test(
867
+ key,
868
+ ) ||
869
+ (/^otelcol_exporter_send_failed/.test(key) &&
870
+ !/error_permanent="false"/.test(key))),
871
+ );
872
+ const transientRetryAttempts = counterEntries
873
+ .filter(
874
+ ([key]) =>
875
+ /^otelcol_exporter_send_failed/.test(key) &&
876
+ /error_permanent="false"/.test(key),
877
+ )
878
+ .reduce((sum, [, value]) => sum + value, 0);
604
879
  add(
605
880
  "collector_delivery",
606
881
  ["log_records", "spans", "metric_points"].every((signal) =>
@@ -610,21 +885,19 @@ export async function checkProxyTelemetry({
610
885
  key.startsWith(`otelcol_exporter_sent_${signal}`),
611
886
  ),
612
887
  )
613
- ? Object.entries(counters).some(
614
- ([key, value]) =>
615
- !Number.isFinite(value) ||
616
- value < 0 ||
617
- (/^otelcol_(exporter_(send_failed|enqueue_failed)|receiver_(refused|failed))/.test(
618
- key,
619
- ) &&
620
- value > 0),
621
- )
888
+ ? invalidCounters.length || consequentialFailures.length
622
889
  ? "warn"
623
890
  : "pass"
624
891
  : "unverified",
625
892
  {
626
893
  scope:
627
- "collector lifetime delivery/failure counters; absent failure series are not required when sent series identify that signal; queue_size is an instantaneous gauge and a nonzero queue alone is not a failure",
894
+ "collector lifetime delivery/failure counters; transient retry attempts are historical pressure rather than proof of loss, while permanent, enqueue, receiver, and invalid counters remain warnings; selected-interval loss is checked against stored request and capture identities",
895
+ transientRetryAttempts,
896
+ consequentialFailures: consequentialFailures.map(([key, value]) => ({
897
+ key,
898
+ value,
899
+ })),
900
+ invalidCounters: invalidCounters.map(([key, value]) => ({ key, value })),
628
901
  counters,
629
902
  },
630
903
  );
@@ -96,7 +96,7 @@ export async function queryProxyHistory({
96
96
  query: {
97
97
  // The body is small metadata, not arbitrary request/response chunks.
98
98
  // Include a secondary key: timestamp-only paging loses equal-time events.
99
- sql: `SELECT _timestamp, service_instance_id, request_id, body FROM "${stream}" WHERE proxy_record_kind='${kind}' ORDER BY _timestamp ASC, service_instance_id ASC, request_id ASC, body ASC`,
99
+ sql: `SELECT _timestamp, proxy_event_id, service_instance_id, request_id, body FROM "${stream}" WHERE proxy_record_kind='${kind}' ORDER BY _timestamp ASC, proxy_event_id ASC, service_instance_id ASC, request_id ASC, body ASC`,
100
100
  start_time: start,
101
101
  end_time: end - 1,
102
102
  from: offset,