@genn-inc/cluebase-frontend-sdk 0.0.3 → 0.0.8

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 (94) hide show
  1. package/README.md +21 -0
  2. package/dist/_generated/correlation-attribute-keys.d.ts +3 -0
  3. package/dist/_generated/correlation-attribute-keys.js +34 -0
  4. package/dist/_generated/diagnostic-codes.d.ts +4 -0
  5. package/dist/_generated/diagnostic-codes.js +48 -0
  6. package/dist/_generated/drop-reasons.d.ts +2 -0
  7. package/dist/_generated/drop-reasons.js +25 -0
  8. package/dist/_generated/{identity-pii-policy.d.ts → identity-pii-policy-text-contains.d.ts} +1 -1
  9. package/dist/_generated/{identity-pii-policy.js → identity-pii-policy-text-contains.js} +1 -1
  10. package/dist/_generated/identity-pii-policy-whole-value.d.ts +37 -0
  11. package/dist/_generated/identity-pii-policy-whole-value.js +106 -0
  12. package/dist/_generated/ingest-routes.d.ts +5 -0
  13. package/dist/_generated/ingest-routes.js +12 -0
  14. package/dist/_generated/otel-span-conversion.js +1 -9
  15. package/dist/context/browser-storage.js +1 -2
  16. package/dist/context/environment.js +5 -3
  17. package/dist/context/identity.js +5 -9
  18. package/dist/context/organization.d.ts +1 -1
  19. package/dist/context/organization.js +6 -8
  20. package/dist/context/session.js +6 -8
  21. package/dist/context/tab.js +8 -12
  22. package/dist/context/trace.js +4 -5
  23. package/dist/exporter/batch.d.ts +26 -27
  24. package/dist/exporter/batch.js +248 -294
  25. package/dist/exporter/browser-outbox-records.d.ts +8 -25
  26. package/dist/exporter/browser-outbox-records.js +21 -100
  27. package/dist/exporter/browser-outbox.d.ts +23 -25
  28. package/dist/exporter/browser-outbox.js +106 -181
  29. package/dist/exporter/client.d.ts +25 -35
  30. package/dist/exporter/client.js +107 -335
  31. package/dist/exporter/delivery-pipeline.d.ts +71 -0
  32. package/dist/exporter/delivery-pipeline.js +196 -0
  33. package/dist/exporter/delivery-response-contract.d.ts +1 -81
  34. package/dist/exporter/delivery-response-contract.js +0 -118
  35. package/dist/exporter/delivery-response.d.ts +9 -4
  36. package/dist/exporter/delivery-response.js +59 -122
  37. package/dist/exporter/diagnostics.d.ts +3 -2
  38. package/dist/exporter/diagnostics.js +5 -26
  39. package/dist/exporter/event-id.d.ts +1 -0
  40. package/dist/exporter/event-id.js +4 -3
  41. package/dist/exporter/frontend-resource.js +5 -4
  42. package/dist/exporter/observation-source-event-exporter.d.ts +2 -1
  43. package/dist/exporter/observation-source-event-exporter.js +7 -4
  44. package/dist/exporter/path-template.js +3 -1
  45. package/dist/exporter/raw-event-to-source-signal.js +4 -2
  46. package/dist/exporter/sanitize.d.ts +3 -2
  47. package/dist/exporter/sanitize.js +4 -3
  48. package/dist/exporter/sdk-core-delivery.d.ts +225 -0
  49. package/dist/exporter/sdk-core-delivery.js +97 -0
  50. package/dist/exporter/sdk-core-wasm.d.ts +11 -0
  51. package/dist/exporter/sdk-core-wasm.js +39 -0
  52. package/dist/exporter/source-event.d.ts +3 -0
  53. package/dist/exporter/source-event.js +17 -0
  54. package/dist/exporter/span-to-source-signal.d.ts +2 -2
  55. package/dist/exporter/span-to-source-signal.js +45 -125
  56. package/dist/index.d.ts +5 -4
  57. package/dist/index.js +18 -6
  58. package/dist/instrumentation/action.js +9 -8
  59. package/dist/instrumentation/dom.js +13 -21
  60. package/dist/instrumentation/drag.js +0 -3
  61. package/dist/instrumentation/error.js +3 -1
  62. package/dist/instrumentation/form.js +5 -6
  63. package/dist/instrumentation/input.js +4 -8
  64. package/dist/instrumentation/location.js +3 -1
  65. package/dist/instrumentation/network-request.js +1 -1
  66. package/dist/instrumentation/network-span-attributes.js +2 -2
  67. package/dist/instrumentation/network.d.ts +1 -1
  68. package/dist/instrumentation/network.js +24 -3
  69. package/dist/instrumentation/performance.js +1 -2
  70. package/dist/internal/internal-log.js +2 -1
  71. package/dist/provider/_version.d.ts +1 -1
  72. package/dist/provider/_version.js +1 -1
  73. package/dist/provider/config.js +16 -4
  74. package/dist/provider/constants.d.ts +1 -1
  75. package/dist/provider/constants.js +0 -2
  76. package/dist/provider/lifecycle-event-dedup.d.ts +7 -0
  77. package/dist/provider/lifecycle-event-dedup.js +31 -0
  78. package/dist/provider/sdk-event-pipeline.d.ts +3 -4
  79. package/dist/provider/sdk-event-pipeline.js +5 -17
  80. package/dist/provider/sdk-input-normalization.js +2 -2
  81. package/dist/provider/sdk-otel-pipeline.d.ts +2 -1
  82. package/dist/provider/sdk-otel-pipeline.js +7 -15
  83. package/dist/provider/sdk-runtime-surfaces.d.ts +17 -13
  84. package/dist/provider/sdk-runtime-surfaces.js +15 -13
  85. package/dist/provider/sdk.js +137 -48
  86. package/dist/provider/types.d.ts +18 -24
  87. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.d.ts +44 -0
  88. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.js +355 -0
  89. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm +0 -0
  90. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm.d.ts +11 -0
  91. package/dist/wasm/pkg-web/package.json +15 -0
  92. package/package.json +62 -53
  93. package/dist/provider/usage-governor.d.ts +0 -7
  94. package/dist/provider/usage-governor.js +0 -114
@@ -0,0 +1,196 @@
1
+ import { applyTransportResult, attemptRequest, beginClose, capturePermit, closeDriver, adoptRestoredBatch as coreAdoptRestoredBatch, setConsent as coreSetConsent, enqueueEvent, fenceQueue, openConsent, openDriver, pendingEventCount, planNextStep, queueDepth, setBrowserToken, } from "./sdk-core-delivery.js";
2
+ const RELEASED_PERMIT = Object.freeze({
3
+ generation: "",
4
+ gate_id: "",
5
+ permitted_at_start: false,
6
+ capture_open_at_start: false,
7
+ });
8
+ const RELEASED_ENQUEUE_REPORT = Object.freeze({
9
+ retained: false,
10
+ overflowed_event_keys: [],
11
+ diagnostics: [],
12
+ });
13
+ const RELEASED_RESTORE_REPORT = Object.freeze({
14
+ adopted: false,
15
+ event_keys: [],
16
+ });
17
+ const RELEASED_QUEUE_DEPTH = Object.freeze({
18
+ draft_count: 0,
19
+ draft_body_bytes: 0,
20
+ pending_bytes: 0,
21
+ });
22
+ const RELEASED_FENCE_REPORT = Object.freeze({
23
+ target_ordinal: 0,
24
+ oversized_event_keys: [],
25
+ frame_admits_no_event: true,
26
+ diagnostics: [],
27
+ });
28
+ const RELEASED_RUN_SUMMARY = Object.freeze({
29
+ observations: [],
30
+ attempts: 0,
31
+ stop: "queue_drained",
32
+ });
33
+ export class DeliveryPipeline {
34
+ constructor(options) {
35
+ this.released = false;
36
+ this.consentHandle = openConsent(options.initialConsent);
37
+ this.driverOptions = {
38
+ transport: {
39
+ surface: "frontend",
40
+ endpoint: options.endpoint,
41
+ project_key: options.projectKey,
42
+ sdk_request: options.sdkRequest,
43
+ sdk_version: options.sdkVersion,
44
+ source_schema_version: options.sourceSchemaVersion,
45
+ },
46
+ queue_limits: options.queueLimits,
47
+ ...(options.requestTimeoutMs === undefined
48
+ ? {}
49
+ : { request_timeout_ms: options.requestTimeoutMs }),
50
+ };
51
+ this.handle = openDriver(this.driverOptions, this.consentHandle);
52
+ this.transport = options.transport;
53
+ this.clock = options.clock;
54
+ }
55
+ reset() {
56
+ if (this.released)
57
+ return;
58
+ closeDriver(this.handle);
59
+ this.handle = openDriver(this.driverOptions, this.consentHandle);
60
+ }
61
+ permit() {
62
+ return this.released ? RELEASED_PERMIT : capturePermit(this.handle);
63
+ }
64
+ admit(event, permit) {
65
+ if (this.released)
66
+ return RELEASED_ENQUEUE_REPORT;
67
+ return enqueueEvent(this.handle, {
68
+ event_key: event.eventKey,
69
+ byte_length: event.byteLength,
70
+ permit,
71
+ ...(event.diagnosticCanonicalBytes === undefined
72
+ ? {}
73
+ : { diagnostic_canonical_bytes: event.diagnosticCanonicalBytes }),
74
+ });
75
+ }
76
+ adoptRestoredBatch(ordinal, events) {
77
+ if (this.released)
78
+ return RELEASED_RESTORE_REPORT;
79
+ return coreAdoptRestoredBatch(this.handle, ordinal, events);
80
+ }
81
+ depth(envelopeOverheadBytes) {
82
+ if (this.released)
83
+ return RELEASED_QUEUE_DEPTH;
84
+ return queueDepth(this.handle, envelopeOverheadBytes);
85
+ }
86
+ pendingCount() {
87
+ return this.released ? 0 : pendingEventCount(this.handle);
88
+ }
89
+ fence(envelopeOverheadBytes) {
90
+ if (this.released)
91
+ return RELEASED_FENCE_REPORT;
92
+ return fenceQueue(this.handle, envelopeOverheadBytes);
93
+ }
94
+ rememberBrowserToken(token) {
95
+ if (this.released)
96
+ return;
97
+ setBrowserToken(this.handle, token);
98
+ }
99
+ declareConsent(consent) {
100
+ return coreSetConsent(this.consentHandle, consent);
101
+ }
102
+ beginClose() {
103
+ if (this.released)
104
+ return;
105
+ beginClose(this.handle);
106
+ }
107
+ dispose() {
108
+ if (this.released)
109
+ return false;
110
+ this.released = true;
111
+ return closeDriver(this.handle);
112
+ }
113
+ async run(input) {
114
+ if (this.released)
115
+ return RELEASED_RUN_SUMMARY;
116
+ const observations = [];
117
+ let attempts = 0;
118
+ let stop = "queue_drained";
119
+ const deadlineMs = () => typeof input.deadlineMs === "function"
120
+ ? input.deadlineMs()
121
+ : input.deadlineMs;
122
+ while (true) {
123
+ if (this.released)
124
+ break;
125
+ const step = planNextStep(this.handle, this.clock.nowMs(), deadlineMs());
126
+ if (step.kind === "idle")
127
+ break;
128
+ if (step.kind === "blocked") {
129
+ stop = step.reason;
130
+ break;
131
+ }
132
+ if (step.kind === "wait_retry") {
133
+ await this.clock.sleep(step.delay_ms);
134
+ if (this.clock.nowMs() < step.resume_at_ms) {
135
+ stop = "retry_not_due";
136
+ break;
137
+ }
138
+ continue;
139
+ }
140
+ const batch = input.body(step.batch_id, step.event_keys);
141
+ if (!batch) {
142
+ stop = "batch_unavailable";
143
+ break;
144
+ }
145
+ attempts += 1;
146
+ const request = attemptRequest(this.handle, {
147
+ attemptId: step.attempt_id,
148
+ body: batch.body,
149
+ timeoutMs: step.timeout_ms,
150
+ ...(batch.diagnosticCanonicalBytes === undefined
151
+ ? {}
152
+ : { batchCanonicalBytes: batch.diagnosticCanonicalBytes }),
153
+ });
154
+ const outcome = await this.transport(request);
155
+ if (this.released)
156
+ break;
157
+ const report = applyTransportResult(this.handle, step.attempt_id, outcome, this.clock.nowMs());
158
+ const observation = observe(report);
159
+ observations.push(observation);
160
+ input.onObservation?.(observation);
161
+ if (report.progress.kind === "retries_exhausted") {
162
+ stop = "retries_exhausted";
163
+ break;
164
+ }
165
+ if (report.progress.kind === "consent_revoked") {
166
+ stop = "consent_revoked";
167
+ break;
168
+ }
169
+ }
170
+ return { observations, attempts, stop };
171
+ }
172
+ }
173
+ const observe = (report) => {
174
+ const progress = report.progress;
175
+ const settled = progress.kind === "settled" ? progress.settled_event_keys : [];
176
+ const dropped = progress.kind === "settled"
177
+ ? progress.dropped_event_keys
178
+ : progress.kind === "dropped_oversized"
179
+ ? [progress.event_key]
180
+ : [];
181
+ const droppedEventCount = Math.max(dropped.length, report.decision?.result.dropped_event_count ?? 0);
182
+ return {
183
+ progress,
184
+ diagnostics: report.diagnostics,
185
+ settledEventKeys: settled,
186
+ droppedEventKeys: dropped,
187
+ droppedEventCount,
188
+ httpStatus: report.decision?.http_status ?? null,
189
+ reason: report.decision?.result.reason ?? null,
190
+ ...(report.decision?.privacy_policy === undefined
191
+ ? {}
192
+ : { privacyPolicy: report.decision.privacy_policy }),
193
+ directiveUnreadable: report.decision?.directive_unreadable === true,
194
+ diagnosticCodes: report.decision?.diagnostic_codes ?? [],
195
+ };
196
+ };
@@ -6,6 +6,7 @@ declare const ingestRetryStrategySchema: z.ZodEnum<{
6
6
  mark_failed: "mark_failed";
7
7
  drop_event: "drop_event";
8
8
  }>;
9
+ export type IngestRetryStrategy = z.infer<typeof ingestRetryStrategySchema>;
9
10
  export declare const ingestPrivacyPolicySchema: z.ZodObject<{
10
11
  token: z.ZodString;
11
12
  entries: z.ZodArray<z.ZodObject<{
@@ -19,85 +20,4 @@ export declare const ingestPrivacyPolicySchema: z.ZodObject<{
19
20
  }, z.core.$strict>>;
20
21
  }, z.core.$strict>;
21
22
  export type IngestPrivacyPolicy = z.output<typeof ingestPrivacyPolicySchema>;
22
- export declare const observationSourceEventBatchResponseSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
23
- accepted: z.ZodLiteral<true>;
24
- observation_ingest_batch_id: z.ZodString;
25
- status: z.ZodLiteral<"received">;
26
- submitted_event_count: z.ZodNumber;
27
- accepted_event_count: z.ZodNumber;
28
- dropped_event_count: z.ZodNumber;
29
- dropped_events: z.ZodArray<z.ZodObject<{
30
- event_index: z.ZodNumber;
31
- reason: z.ZodString;
32
- source_event_payload_fingerprint: z.ZodNullable<z.ZodString>;
33
- }, z.core.$strict>>;
34
- duplicate: z.ZodBoolean;
35
- governor: z.ZodOptional<z.ZodObject<{
36
- disabledCapabilities: z.ZodArray<z.ZodEnum<{
37
- event_collection: "event_collection";
38
- replay_storage: "replay_storage";
39
- ai_generation: "ai_generation";
40
- raw_observation_storage: "raw_observation_storage";
41
- retention_growth: "retention_growth";
42
- }>>;
43
- limitReasons: z.ZodArray<z.ZodObject<{
44
- limitKind: z.ZodEnum<{
45
- monthly_overage_amount_limit: "monthly_overage_amount_limit";
46
- internal_cost_limit: "internal_cost_limit";
47
- internal_limit: "internal_limit";
48
- operator_global_monthly_cost_limit: "operator_global_monthly_cost_limit";
49
- }>;
50
- meterKey: z.ZodString;
51
- thresholdStage: z.ZodEnum<{
52
- info: "info";
53
- warning: "warning";
54
- urgent: "urgent";
55
- reached: "reached";
56
- }>;
57
- currentValue: z.ZodNumber;
58
- limitValue: z.ZodNumber;
59
- retryAfterAt: z.ZodNullable<z.ZodString>;
60
- }, z.core.$strip>>;
61
- retryAfterAt: z.ZodNullable<z.ZodString>;
62
- }, z.core.$strip>>;
63
- privacy_policy: z.ZodOptional<z.ZodObject<{
64
- token: z.ZodString;
65
- entries: z.ZodArray<z.ZodObject<{
66
- method: z.ZodString;
67
- path_template: z.ZodString;
68
- direction: z.ZodEnum<{
69
- request: "request";
70
- response: "response";
71
- }>;
72
- field_path: z.ZodString;
73
- }, z.core.$strict>>;
74
- }, z.core.$strict>>;
75
- }, z.core.$strip>, z.ZodObject<{
76
- accepted: z.ZodLiteral<false>;
77
- status: z.ZodEnum<{
78
- rejected: "rejected";
79
- failed: "failed";
80
- }>;
81
- reason: z.ZodString;
82
- retry_strategy: z.ZodEnum<{
83
- split_batch: "split_batch";
84
- retry_same_batch: "retry_same_batch";
85
- stop_and_report: "stop_and_report";
86
- mark_failed: "mark_failed";
87
- drop_event: "drop_event";
88
- }>;
89
- submitted_event_count: z.ZodOptional<z.ZodNumber>;
90
- accepted_event_count: z.ZodOptional<z.ZodNumber>;
91
- dropped_event_count: z.ZodOptional<z.ZodNumber>;
92
- dropped_events: z.ZodOptional<z.ZodArray<z.ZodObject<{
93
- event_index: z.ZodNumber;
94
- reason: z.ZodString;
95
- source_event_payload_fingerprint: z.ZodNullable<z.ZodString>;
96
- }, z.core.$strict>>>;
97
- limits: z.ZodOptional<z.ZodObject<{
98
- hard_max_payload_bytes: z.ZodNumber;
99
- max_event_bytes: z.ZodNumber;
100
- }, z.core.$strip>>;
101
- }, z.core.$strip>], "accepted">;
102
- export type IngestRetryStrategy = z.infer<typeof ingestRetryStrategySchema>;
103
23
  export {};
@@ -1,6 +1,4 @@
1
- import { COST_CAPABILITIES, LIMIT_KINDS } from "../_generated/usage-governor-tables.js";
2
1
  import { z } from "zod";
3
- const MAX_INGEST_EVENTS_PER_BATCH = 10000;
4
2
  const ingestRetryStrategySchema = z.enum([
5
3
  "split_batch",
6
4
  "retry_same_batch",
@@ -8,30 +6,6 @@ const ingestRetryStrategySchema = z.enum([
8
6
  "mark_failed",
9
7
  "drop_event",
10
8
  ]);
11
- const ingestDroppedEventSchema = z
12
- .object({
13
- event_index: z.number().int().nonnegative(),
14
- reason: z.string().trim().min(1).max(128),
15
- source_event_payload_fingerprint: z
16
- .string()
17
- .regex(/^[0-9a-f]{64}$/)
18
- .nullable(),
19
- })
20
- .strict();
21
- const ingestUsageGovernorFeedbackSchema = z.object({
22
- disabledCapabilities: z.array(z.enum(COST_CAPABILITIES)),
23
- limitReasons: z.array(z.object({
24
- limitKind: z.enum(LIMIT_KINDS),
25
- meterKey: z.string().trim().min(1),
26
- thresholdStage: z.enum(["info", "warning", "urgent", "reached"]),
27
- currentValue: z.number().nonnegative(),
28
- limitValue: z.number().nonnegative(),
29
- retryAfterAt: z.string().datetime({ offset: true }).nullable(),
30
- })),
31
- retryAfterAt: z.string().datetime({ offset: true }).nullable(),
32
- });
33
- const boundedCount = z.number().int().nonnegative().max(MAX_INGEST_EVENTS_PER_BATCH);
34
- const positiveCount = boundedCount.refine((count) => count > 0);
35
9
  export const ingestPrivacyPolicySchema = z
36
10
  .object({
37
11
  token: z.string().trim().min(1).max(200),
@@ -47,95 +21,3 @@ export const ingestPrivacyPolicySchema = z
47
21
  .max(500),
48
22
  })
49
23
  .strict();
50
- const observationSourceEventBatchAcceptedResponseSchema = z
51
- .object({
52
- accepted: z.literal(true),
53
- observation_ingest_batch_id: z.string().trim().min(1),
54
- status: z.literal("received"),
55
- submitted_event_count: positiveCount,
56
- accepted_event_count: positiveCount,
57
- dropped_event_count: boundedCount,
58
- dropped_events: z.array(ingestDroppedEventSchema).max(MAX_INGEST_EVENTS_PER_BATCH),
59
- duplicate: z.boolean(),
60
- governor: ingestUsageGovernorFeedbackSchema.optional(),
61
- privacy_policy: ingestPrivacyPolicySchema.optional(),
62
- })
63
- .superRefine((value, ctx) => {
64
- if (value.submitted_event_count !== value.accepted_event_count + value.dropped_event_count) {
65
- ctx.addIssue({
66
- code: z.ZodIssueCode.custom,
67
- path: ["submitted_event_count"],
68
- message: "submitted count must equal accepted plus dropped counts",
69
- });
70
- }
71
- if (!value.duplicate || value.dropped_events.length > 0) {
72
- validateDroppedEventDetails(value.submitted_event_count, value.dropped_event_count, value.dropped_events, ctx);
73
- }
74
- });
75
- const observationSourceEventBatchRejectedResponseSchema = z
76
- .object({
77
- accepted: z.literal(false),
78
- status: z.enum(["rejected", "failed"]),
79
- reason: z.string().trim().min(1),
80
- retry_strategy: ingestRetryStrategySchema,
81
- submitted_event_count: positiveCount.optional(),
82
- accepted_event_count: boundedCount.optional(),
83
- dropped_event_count: boundedCount.optional(),
84
- dropped_events: z.array(ingestDroppedEventSchema).max(MAX_INGEST_EVENTS_PER_BATCH).optional(),
85
- limits: z
86
- .object({
87
- hard_max_payload_bytes: z.number().int().positive(),
88
- max_event_bytes: z.number().int().positive(),
89
- })
90
- .refine((limits) => limits.max_event_bytes <= limits.hard_max_payload_bytes)
91
- .optional(),
92
- })
93
- .superRefine((value, ctx) => {
94
- const counts = [value.submitted_event_count, value.accepted_event_count, value.dropped_event_count];
95
- const hasAnyCount = counts.some((count) => count !== undefined);
96
- const hasAllCounts = counts.every((count) => count !== undefined);
97
- if (hasAnyCount !== hasAllCounts) {
98
- ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["submitted_event_count"], message: "rejected response counts must be all present or all absent" });
99
- return;
100
- }
101
- if (hasAllCounts) {
102
- const [submitted, accepted, dropped] = counts;
103
- if (accepted > submitted || dropped > submitted || accepted + dropped > submitted) {
104
- ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["dropped_event_count"], message: "rejected counts must not exceed submitted count" });
105
- }
106
- }
107
- if (value.retry_strategy === "drop_event") {
108
- if (!hasAllCounts) {
109
- ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["submitted_event_count"], message: "drop rejection requires all counts" });
110
- }
111
- else {
112
- const [submitted, accepted, dropped] = counts;
113
- if (accepted !== 0 || dropped !== submitted) {
114
- ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["dropped_event_count"], message: "drop rejection must reject every submitted event" });
115
- }
116
- validateDroppedEventDetails(submitted, dropped, value.dropped_events ?? [], ctx);
117
- }
118
- }
119
- else if ((value.dropped_events?.length ?? 0) > 0) {
120
- ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["dropped_events"], message: "non-drop rejection must not contain dropped event details" });
121
- }
122
- if ((value.retry_strategy === "split_batch") !== (value.limits !== undefined)) {
123
- ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["limits"], message: "byte limits are valid only for split rejection" });
124
- }
125
- });
126
- export const observationSourceEventBatchResponseSchema = z.discriminatedUnion("accepted", [
127
- observationSourceEventBatchAcceptedResponseSchema,
128
- observationSourceEventBatchRejectedResponseSchema,
129
- ]);
130
- function validateDroppedEventDetails(submittedCount, droppedCount, droppedEvents, ctx) {
131
- if (droppedEvents.length !== droppedCount) {
132
- ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["dropped_events"], message: "dropped event details must match dropped count" });
133
- }
134
- const indexes = new Set();
135
- for (const [detailIndex, detail] of droppedEvents.entries()) {
136
- if (detail.event_index >= submittedCount || indexes.has(detail.event_index)) {
137
- ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["dropped_events", detailIndex, "event_index"], message: "dropped event indexes must be valid and unique" });
138
- }
139
- indexes.add(detail.event_index);
140
- }
141
- }
@@ -1,21 +1,26 @@
1
- import { type IngestRetryStrategy } from "./delivery-response-contract.js";
2
- import type { IngestUsageGovernorFeedback } from "../provider/types.js";
3
- export declare const BROWSER_REJECTION_STRATEGIES: Readonly<Record<string, IngestRetryStrategy>>;
1
+ import type { IngestRetryStrategy } from "./delivery-response-contract.js";
2
+ import type { DiagnosticCode } from "./diagnostics.js";
3
+ import { type TransportOutcome } from "./sdk-core-delivery.js";
4
4
  export type { IngestRetryStrategy } from "./delivery-response-contract.js";
5
5
  export type BrowserDeliveryDecision = {
6
6
  readonly ok: boolean;
7
7
  readonly privacyPolicy?: unknown;
8
+ readonly directiveUnreadable: boolean;
8
9
  readonly retryStrategy: IngestRetryStrategy | null;
9
- readonly governor: IngestUsageGovernorFeedback | null;
10
10
  readonly observationIngestBatchId?: string;
11
11
  readonly droppedEventCount: number;
12
12
  readonly acceptedEventCount: number;
13
13
  readonly acceptedPartial: boolean;
14
+ readonly acceptedFiltered: boolean;
14
15
  readonly reason: string | null;
16
+ readonly diagnosticCodes: readonly DiagnosticCode[];
15
17
  };
16
18
  export declare const evaluateBrowserDeliveryResponse: (input: {
17
19
  readonly status: number;
18
20
  readonly bodyText: string;
19
21
  readonly localSubmittedCount: number;
20
22
  }) => BrowserDeliveryDecision;
23
+ export declare const evaluateBrowserTransportOutcome: (outcome: Exclude<TransportOutcome, {
24
+ kind: "response";
25
+ }>) => BrowserDeliveryDecision;
21
26
  export declare const classifyIngestHttpStatus: (status: number) => IngestRetryStrategy;
@@ -1,132 +1,69 @@
1
- import { observationSourceEventBatchResponseSchema, } from "./delivery-response-contract.js";
2
- import { parseGovernorFeedbackEnvelope } from "../provider/usage-governor.js";
3
- export const BROWSER_REJECTION_STRATEGIES = {
4
- invalid_request: "stop_and_report",
5
- unsupported_source_schema_version: "stop_and_report",
6
- missing_project_key: "stop_and_report",
7
- project_key_mismatch: "stop_and_report",
8
- unresolved_project_or_environment: "stop_and_report",
9
- missing_browser_token: "stop_and_report",
10
- invalid_browser_token: "stop_and_report",
11
- expired_browser_token: "retry_same_batch",
12
- stale_browser_token: "retry_same_batch",
13
- origin_not_allowed: "stop_and_report",
14
- authorization_denied: "stop_and_report",
15
- idempotency_conflict: "mark_failed",
16
- batch_too_large: "split_batch",
17
- all_events_dropped: "drop_event",
18
- usage_governor_denied: "retry_same_batch",
19
- all_events_rejected_by_ingest_controls: "retry_same_batch",
20
- route_throttled: "retry_same_batch",
21
- raw_ingest_produce_failed: "retry_same_batch",
22
- raw_ingest_ack_unknown: "retry_same_batch",
23
- raw_ingest_receipt_metadata_failed: "retry_same_batch",
24
- ingest_config_changed_retryable: "retry_same_batch",
25
- usage_meter_unavailable: "retry_same_batch",
1
+ import { evaluateResponse, } from "./sdk-core-delivery.js";
2
+ const RETRY_STRATEGY_BY_OUTCOME = {
3
+ accepted_complete: null,
4
+ accepted_filtered: null,
5
+ accepted_partial: null,
6
+ retry_same_batch: "retry_same_batch",
7
+ split_batch: "split_batch",
8
+ terminal_stop: "stop_and_report",
9
+ terminal_failed: "mark_failed",
10
+ terminal_drop: "drop_event",
11
+ invalid_response: "retry_same_batch",
26
12
  };
27
- export const evaluateBrowserDeliveryResponse = (input) => {
28
- const decoded = decode(input.bodyText);
29
- if (decoded !== null && hasAcceptedDiscriminator(decoded)) {
30
- const parsed = observationSourceEventBatchResponseSchema.safeParse(decoded);
31
- if (parsed.success &&
32
- submittedCountMatches(parsed.data, input.localSubmittedCount) &&
33
- hasPublicDropDetails(parsed.data)) {
34
- if (parsed.data.accepted) {
35
- if (input.status < 200 || input.status >= 300) {
36
- return invalidResponse();
37
- }
38
- const partial = parsed.data.dropped_event_count > 0;
39
- return {
40
- ok: true,
41
- retryStrategy: null,
42
- privacyPolicy: parsed.data.privacy_policy,
43
- governor: parseGovernorFeedbackEnvelope(parsed.data),
44
- observationIngestBatchId: parsed.data.observation_ingest_batch_id,
45
- droppedEventCount: parsed.data.dropped_event_count,
46
- acceptedEventCount: parsed.data.accepted_event_count,
47
- acceptedPartial: partial,
48
- reason: partial ? "accepted_partial" : null,
49
- };
50
- }
51
- if (BROWSER_REJECTION_STRATEGIES[parsed.data.reason] !==
52
- parsed.data.retry_strategy) {
53
- return invalidResponse();
54
- }
55
- return {
56
- ok: false,
57
- retryStrategy: parsed.data.retry_strategy,
58
- governor: null,
59
- droppedEventCount: parsed.data.dropped_event_count ?? 0,
60
- acceptedEventCount: parsed.data.accepted_event_count ?? 0,
61
- acceptedPartial: false,
62
- reason: parsed.data.reason,
63
- };
64
- }
65
- return invalidResponse();
13
+ const ACCEPTED_OUTCOMES = new Set([
14
+ "accepted_complete",
15
+ "accepted_filtered",
16
+ "accepted_partial",
17
+ ]);
18
+ export const evaluateBrowserDeliveryResponse = (input) => translate({ kind: "response", status: input.status, body: input.bodyText }, input.localSubmittedCount);
19
+ export const evaluateBrowserTransportOutcome = (outcome) => translate(outcome, 1);
20
+ export const classifyIngestHttpStatus = (status) => translate({ kind: "response", status, body: "" }, 1).retryStrategy ??
21
+ "retry_same_batch";
22
+ const translate = (outcome, localSubmittedCount) => {
23
+ const evaluation = evaluateResponse(outcome, localSubmittedCount);
24
+ if (evaluation.kind !== "decision") {
25
+ return consentRevoked();
66
26
  }
67
- return fallback(input.status);
27
+ return project(evaluation.decision);
68
28
  };
69
- const submittedCountMatches = (value, localSubmittedCount) => value.submitted_event_count === undefined ||
70
- value.submitted_event_count === localSubmittedCount;
71
- const hasPublicDropDetails = (value) => (value.dropped_events ?? []).every((detail) => detail.source_event_payload_fingerprint === null);
72
- const decode = (bodyText) => {
73
- if (!bodyText.trim())
74
- return null;
75
- try {
76
- return JSON.parse(bodyText);
77
- }
78
- catch {
79
- return null;
80
- }
29
+ const project = (decision) => {
30
+ const accepted = ACCEPTED_OUTCOMES.has(decision.outcome);
31
+ const batchId = decision.result.observation_ingest_batch_id;
32
+ return {
33
+ ok: accepted,
34
+ retryStrategy: accepted
35
+ ? null
36
+ : (RETRY_STRATEGY_BY_OUTCOME[decision.outcome] ?? "retry_same_batch"),
37
+ ...(decision.privacy_policy === undefined
38
+ ? {}
39
+ : { privacyPolicy: decision.privacy_policy }),
40
+ directiveUnreadable: decision.directive_unreadable === true,
41
+ ...(batchId === undefined ? {} : { observationIngestBatchId: batchId }),
42
+ droppedEventCount: decision.result.dropped_event_count ?? 0,
43
+ acceptedEventCount: decision.result.accepted_event_count ?? 0,
44
+ acceptedPartial: decision.outcome === "accepted_partial",
45
+ acceptedFiltered: decision.outcome === "accepted_filtered",
46
+ reason: reasonOf(decision),
47
+ diagnosticCodes: decision.diagnostic_codes,
48
+ };
81
49
  };
82
- const hasAcceptedDiscriminator = (value) => value !== null &&
83
- typeof value === "object" &&
84
- !Array.isArray(value) &&
85
- Object.prototype.hasOwnProperty.call(value, "accepted");
86
- const invalidResponse = () => ({
87
- ok: false,
88
- retryStrategy: "retry_same_batch",
89
- governor: null,
90
- droppedEventCount: 0,
91
- acceptedEventCount: 0,
92
- acceptedPartial: false,
93
- reason: "invalid_response",
94
- });
95
- export const classifyIngestHttpStatus = (status) => {
96
- if (status >= 200 && status < 300)
97
- return "retry_same_batch";
98
- if (status === 409)
99
- return "mark_failed";
100
- if (status === 413)
101
- return "split_batch";
102
- if ([400, 401, 403, 404, 405, 406, 410, 415].includes(status)) {
103
- return "stop_and_report";
104
- }
105
- return "retry_same_batch";
106
- };
107
- const fallback = (status) => {
108
- const retryStrategy = classifyIngestHttpStatus(status);
109
- if (status >= 200 && status < 300)
110
- return invalidResponse();
111
- if (retryStrategy === "retry_same_batch")
112
- return retry(status);
113
- return terminal(retryStrategy, status);
50
+ const reasonOf = (decision) => {
51
+ if (decision.outcome === "accepted_complete")
52
+ return null;
53
+ if (decision.outcome === "accepted_partial")
54
+ return "accepted_partial";
55
+ if (decision.outcome === "accepted_filtered")
56
+ return "accepted_filtered";
57
+ return decision.result.reason ?? decision.outcome;
114
58
  };
115
- const retry = (status) => ({
116
- ok: false,
117
- retryStrategy: "retry_same_batch",
118
- governor: null,
119
- droppedEventCount: 0,
120
- acceptedEventCount: 0,
121
- acceptedPartial: false,
122
- reason: `http_${status}`,
123
- });
124
- const terminal = (retryStrategy, status) => ({
59
+ const consentRevoked = () => ({
125
60
  ok: false,
126
- retryStrategy,
127
- governor: null,
61
+ retryStrategy: "drop_event",
62
+ directiveUnreadable: false,
128
63
  droppedEventCount: 0,
129
64
  acceptedEventCount: 0,
130
65
  acceptedPartial: false,
131
- reason: `http_${status}`,
66
+ acceptedFiltered: false,
67
+ reason: "consent_denied",
68
+ diagnosticCodes: [],
132
69
  });
@@ -1,5 +1,6 @@
1
- import type { IngestRetryStrategy } from "./delivery-response.js";
2
- export type DiagnosticCode = "batch_split" | "consent_persistence_failed" | "consent_purge" | "consent_purge_failed" | "consent_state_unavailable" | "durable_outbox_unavailable" | "duplicate_drop_details_unavailable" | "event_oversize" | "intentional_drop" | "invalid_response" | "legacy_pre_revocation_event_blocked" | "legacy_bridge_capacity_reached" | "legacy_writer_detected" | "non_canonical_success_status" | "outbox_key_collision" | "outbox_record_corrupt" | "protocol_redirect" | "queue_overflow" | "retry_exhausted" | "retry_scheduled" | "shutdown_failure" | "terminal_rejection" | "unknown_status";
1
+ import { type DiagnosticCode } from "../_generated/diagnostic-codes.js";
2
+ import type { IngestRetryStrategy } from "./delivery-response-contract.js";
3
+ export type { DiagnosticCode };
3
4
  export type DiagnosticLifecycle = "open" | "closing" | "closing_failed" | "closed";
4
5
  export type DiagnosticFlushOutcome = "clean" | "failed" | null;
5
6
  export type DiagnosticCloseOutcome = "clean" | "retryable_failure" | "terminal_failure" | null;