@usherlabs/cex-broker 0.2.49 → 0.2.51

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 (55) hide show
  1. package/dist/commands/cex-canonical-orderbook-export.js +7747 -883
  2. package/dist/commands/cex-canonical-orderbook-export.js.map +41 -10
  3. package/dist/commands/cli.js +127 -34
  4. package/dist/commands/market-data-vendor-backfill.js +8284 -6391
  5. package/dist/commands/market-data-vendor-backfill.js.map +16 -15
  6. package/dist/helpers/canonical-orderbook-export/parquet-projection.d.ts +19 -0
  7. package/dist/helpers/market-data-archive/canonical-orderbook.d.ts +1 -1
  8. package/dist/helpers/market-data-preparation/conformance-fixtures.d.ts +114 -2
  9. package/dist/helpers/market-data-preparation/contracts.d.ts +1298 -18
  10. package/dist/helpers/market-data-preparation/required-clock-qualification.d.ts +43 -0
  11. package/dist/helpers/market-data-preparation/source-tape-dependencies.d.ts +19 -0
  12. package/dist/helpers/market-data-preparation/source-tape-operation.d.ts +109 -0
  13. package/dist/helpers/market-data-source-forensics.d.ts +400 -0
  14. package/dist/helpers/market-data-vendor-backfill/conformance-fixtures.d.ts +3 -5
  15. package/dist/helpers/market-data-vendor-backfill/contracts.d.ts +21 -56
  16. package/dist/helpers/market-data-vendor-backfill/core.d.ts +1 -0
  17. package/dist/helpers/market-data-vendor-backfill/cryptohftdata.d.ts +45 -5
  18. package/dist/helpers/market-data-vendor-backfill/manifests.d.ts +31 -1083
  19. package/dist/helpers/market-data-vendor-backfill/promotion.d.ts +1 -0
  20. package/dist/helpers/source-tape-sandbox.d.ts +82 -0
  21. package/dist/helpers/source-tape.d.ts +165 -0
  22. package/dist/helpers/trace-context.d.ts +8 -0
  23. package/dist/index.js +128 -35
  24. package/dist/index.js.map +7 -6
  25. package/dist/market-data-preparation/fixtures/{conformance-v2.json → conformance-v3.json} +257 -60
  26. package/dist/market-data-preparation/policies/capability-policy.json +15 -4
  27. package/dist/market-data-preparation/policies/resource-policy.json +3 -3
  28. package/dist/market-data-preparation/policies/source-tape-capability-v1.json +35 -0
  29. package/dist/market-data-preparation/schema-manifest.json +30 -10
  30. package/dist/market-data-preparation/schemas/canonical-orderbook-export-request-v1.schema.json +5 -1
  31. package/dist/market-data-preparation/schemas/{canonical-orderbook-export-result-v1.schema.json → canonical-orderbook-export-result-v2.schema.json} +20 -6
  32. package/dist/market-data-preparation/schemas/order-book-depth-summary-parquet-projection-v1.schema.json +256 -0
  33. package/dist/market-data-preparation/schemas/order-book-levels-parquet-projection-v1.schema.json +220 -0
  34. package/dist/market-data-preparation/schemas/{preparation-product-pin-v1.schema.json → preparation-product-pin-v2.schema.json} +59 -9
  35. package/dist/market-data-preparation/schemas/promotion-receipt-v1.schema.json +5 -1
  36. package/dist/market-data-preparation/schemas/source-forensics-ledger-v1.schema.json +463 -0
  37. package/dist/market-data-preparation/schemas/source-qualification-record-v1.schema.json +394 -0
  38. package/dist/market-data-preparation.d.ts +7 -0
  39. package/dist/market-data-preparation.js +35443 -25203
  40. package/dist/market-data-preparation.js.map +118 -78
  41. package/dist/market-data-vendor-backfill.d.ts +8 -5
  42. package/dist/market-data-vendor-backfill.js +19444 -15553
  43. package/dist/market-data-vendor-backfill.js.map +65 -60
  44. package/package.json +1 -5
  45. package/dist/market-data-preparation/policies/capability-policy-v1.json +0 -28
  46. package/dist/market-data-vendor-backfill/fixtures/conformance-v1.json +0 -448
  47. package/dist/market-data-vendor-backfill/policies/capability-policy-v2.json +0 -28
  48. package/dist/market-data-vendor-backfill/policies/capability-policy.json +0 -28
  49. package/dist/market-data-vendor-backfill/policies/resource-policy.json +0 -16
  50. package/dist/market-data-vendor-backfill/schema-manifest.json +0 -31
  51. package/dist/market-data-vendor-backfill/schemas/archive-selection.schema.json +0 -224
  52. package/dist/market-data-vendor-backfill/schemas/promotion-receipt.schema.json +0 -171
  53. package/dist/market-data-vendor-backfill/schemas/request.schema.json +0 -166
  54. package/dist/market-data-vendor-backfill/schemas/required-clock.schema.json +0 -47
  55. package/dist/market-data-vendor-backfill/schemas/result.schema.json +0 -198
@@ -1,5 +1,6 @@
1
1
  import { type BackfillArchiveRow, type PromotionReceipt, type PromotionReceiptWire } from "./contracts";
2
2
  export type StablePromotionReceipt = Omit<PromotionReceipt, "receiptId" | "verificationTimeMs">;
3
+ export declare function promotionReceiptMatchesCurrentPolicies(receipt: PromotionReceiptWire): boolean;
3
4
  export declare function promotionReceiptId(receipt: StablePromotionReceipt): string;
4
5
  type UnfinalizedPromotionReceiptWire = Omit<PromotionReceiptWire, "receipt_id" | "promotion_identity_sha256"> & Partial<Pick<PromotionReceiptWire, "receipt_id" | "promotion_identity_sha256">>;
5
6
  export declare function finalizePromotionReceipt(receipt: UnfinalizedPromotionReceiptWire): PromotionReceiptWire;
@@ -0,0 +1,82 @@
1
+ import type { ArchiveQueryClient } from "./market-data-vendor-backfill/archive-reader";
2
+ import type { ArchiveSelectionWire, ForwarderBatch, MarketDataVendorBackfillRequest, ProviderObjectEvidence } from "./market-data-vendor-backfill/contracts";
3
+ import type { CandidateVerification } from "./market-data-vendor-backfill/core";
4
+ import { SOURCE_TAPE_CONSTRUCTION_MODE, SOURCE_TAPE_SANDBOX_TARGET, type SourceTapeArchiveSinkResult } from "./source-tape";
5
+ export declare function verifySourceTapeArchive(input: {
6
+ request: MarketDataVendorBackfillRequest;
7
+ sinkResult: SourceTapeArchiveSinkResult;
8
+ client: ArchiveQueryClient;
9
+ }): Promise<CandidateVerification>;
10
+ export type SourceTapeExportArtifact = {
11
+ file_name: string;
12
+ rows: number;
13
+ bytes: number;
14
+ sha256: string;
15
+ projection_schema_id: string;
16
+ projection_schema_sha256: string;
17
+ };
18
+ export type SourceTapeExportResult = {
19
+ promotionReceiptIds: string[];
20
+ levels: SourceTapeExportArtifact;
21
+ summary: SourceTapeExportArtifact;
22
+ };
23
+ export type SourceTapeSandboxEvidence = {
24
+ schema_id: "market-data-source-tape-sandbox-evidence/v1";
25
+ archive_target: typeof SOURCE_TAPE_SANDBOX_TARGET;
26
+ construction_mode: typeof SOURCE_TAPE_CONSTRUCTION_MODE;
27
+ normal_archive_path: true;
28
+ tape_capability: {
29
+ policy_id: string;
30
+ policy_sha256: string;
31
+ };
32
+ state_count: number;
33
+ promotion: {
34
+ receipt_id: string;
35
+ promotion_identity_sha256: string;
36
+ qualification_event_id: string;
37
+ };
38
+ selection: {
39
+ selection_sha256: string;
40
+ receipt_ids: string[];
41
+ };
42
+ export: {
43
+ levels: SourceTapeExportArtifact;
44
+ summary: SourceTapeExportArtifact;
45
+ };
46
+ projection_schema_pins: readonly {
47
+ schema_id: string;
48
+ schema_sha256: string;
49
+ }[];
50
+ };
51
+ export declare function promoteAndExportSourceTapeSandbox(input: {
52
+ request: MarketDataVendorBackfillRequest;
53
+ sinkResult: SourceTapeArchiveSinkResult;
54
+ verification: CandidateVerification;
55
+ datasetObjects: ProviderObjectEvidence[];
56
+ vendorSemanticDigest: string;
57
+ verifiedAt: string;
58
+ forwarder: {
59
+ submit(batch: ForwarderBatch): Promise<{
60
+ ok: boolean;
61
+ inserted: number;
62
+ }>;
63
+ };
64
+ archive: {
65
+ resolveSelection(request: MarketDataVendorBackfillRequest): Promise<ArchiveSelectionWire>;
66
+ };
67
+ exporter: {
68
+ export(request: {
69
+ schema_id: "https://schemas.usher.so/cex-canonical-orderbook-export-request/v1";
70
+ request_id: string;
71
+ target: {
72
+ environment: string;
73
+ cluster: string;
74
+ };
75
+ selection: ArchiveSelectionWire;
76
+ depth: 100;
77
+ construction_mode: typeof SOURCE_TAPE_CONSTRUCTION_MODE;
78
+ canonical_schema_version: string;
79
+ checksum_algorithm: "sha256-canonical-json-v1";
80
+ }): Promise<SourceTapeExportResult>;
81
+ };
82
+ }): Promise<SourceTapeSandboxEvidence>;
@@ -0,0 +1,165 @@
1
+ import type { BackfillArchiveRow, ForwarderBatch } from "./market-data-vendor-backfill/contracts";
2
+ import { type PolicyNeutralCryptoHftBookState, type PolicyNeutralTapeSink } from "./market-data-vendor-backfill/cryptohftdata";
3
+ export declare const SOURCE_TAPE_CAPABILITY: Readonly<{
4
+ policy_id: string;
5
+ provider: string;
6
+ adapter_version: string;
7
+ acquisition_policy_id: string;
8
+ exchange: string;
9
+ market_type: string;
10
+ feed: string;
11
+ trading_pairs: string[];
12
+ construction_mode: string;
13
+ canonical_schema_id: string;
14
+ depth: number;
15
+ source_window_scan: string;
16
+ provider_object_inventory: string;
17
+ state_emission: string;
18
+ state_window: string;
19
+ archive_target: {
20
+ environment: string;
21
+ cluster: string;
22
+ };
23
+ archive_path: string;
24
+ submission_bounds: {
25
+ max_states_per_yield: number;
26
+ max_rows_per_batch: number;
27
+ max_bytes_per_batch: number;
28
+ max_in_flight: number;
29
+ };
30
+ clickhouse_image: string;
31
+ clickhouse_server_version: string;
32
+ projection_schema_ids: string[];
33
+ policy_sha256: string;
34
+ }>;
35
+ export declare const SOURCE_TAPE_CAPABILITY_ID: string;
36
+ export declare const SOURCE_TAPE_CONSTRUCTION_MODE: "policy_neutral_top_n_state_change_tape/v1";
37
+ export declare const SOURCE_TAPE_DEPTH: 100;
38
+ export declare const SOURCE_TAPE_MAX_STATES_PER_YIELD: 4;
39
+ export declare const SOURCE_TAPE_MAX_BATCH_ROWS = 1000;
40
+ export declare const SOURCE_TAPE_MAX_BATCH_BYTES: number;
41
+ export declare const SOURCE_TAPE_MAX_IN_FLIGHT: 1;
42
+ export declare const SOURCE_TAPE_SANDBOX_TARGET: Readonly<{
43
+ environment: "sandbox";
44
+ cluster: "cex-archive-local";
45
+ }>;
46
+ export declare const SOURCE_TAPE_PROJECTION_PINS: readonly Readonly<{
47
+ schema_id: string;
48
+ schema_sha256: string;
49
+ }>[];
50
+ export type SourceTapeProviderObjectInventory = {
51
+ expected_identities: string[];
52
+ observed_identities: string[];
53
+ complete: boolean;
54
+ };
55
+ export declare function sourceTapeCaptureBundleId(input: {
56
+ idempotencyKey: string;
57
+ tradingPair: "ARB-USDC" | "ARB-USDT";
58
+ window: {
59
+ startTimeMs: number;
60
+ endTimeMs: number;
61
+ };
62
+ expectedObjectIdentities: readonly string[];
63
+ }): string;
64
+ /**
65
+ * `production` is the stable forwarder mutation-authorization class. It is not
66
+ * an environment assertion: source-tape qualification remains sandbox-local.
67
+ */
68
+ export declare function assertSourceTapeSandboxAuthorization(input: {
69
+ requestAuthorizationId: string;
70
+ requestTarget: {
71
+ environment: string;
72
+ cluster: string;
73
+ };
74
+ preflight: {
75
+ authorizationId: string;
76
+ scope: "production";
77
+ environment: string;
78
+ cluster: string;
79
+ credentialValidated: boolean;
80
+ };
81
+ }): void;
82
+ export type SourceTapeArchiveSinkResult = {
83
+ capture_bundle_id: string;
84
+ state_count: number;
85
+ level_row_count: number;
86
+ summary_row_count: number;
87
+ forwarder_batch_count: number;
88
+ forwarder_batch_identity_sha256: string;
89
+ initializer: {
90
+ canonical_snapshot_id: string;
91
+ source_time_ms: number;
92
+ sequence: string;
93
+ semantic_stream_position: 0;
94
+ };
95
+ expected_semantic_digest: string;
96
+ first_state_descriptor_sha256: string;
97
+ last_state_descriptor_sha256: string;
98
+ provider_object_inventory_complete: true;
99
+ max_states_per_yield: typeof SOURCE_TAPE_MAX_STATES_PER_YIELD;
100
+ max_batch_rows: typeof SOURCE_TAPE_MAX_BATCH_ROWS;
101
+ max_batch_bytes: typeof SOURCE_TAPE_MAX_BATCH_BYTES;
102
+ max_in_flight_submissions: typeof SOURCE_TAPE_MAX_IN_FLIGHT;
103
+ };
104
+ export type SourceTapeSemanticStateDescriptor = {
105
+ state_kind: "initialization" | "change";
106
+ semantic_stream_position: number;
107
+ canonical_snapshot_id: string;
108
+ source_time_ms: number;
109
+ sequence: string;
110
+ canonical_rows_sha256: string;
111
+ };
112
+ export declare class SourceTapeSemanticDigestAccumulator {
113
+ private readonly hash;
114
+ private count;
115
+ constructor();
116
+ append(descriptor: SourceTapeSemanticStateDescriptor): void;
117
+ digest(): string;
118
+ }
119
+ export declare function sourceTapeSemanticStateDescriptor(input: {
120
+ stateKind: "initialization" | "change";
121
+ semanticStreamPosition: number;
122
+ rows: readonly BackfillArchiveRow[];
123
+ }): SourceTapeSemanticStateDescriptor;
124
+ export type SourceTapeArchiveSink = PolicyNeutralTapeSink & {
125
+ result(): SourceTapeArchiveSinkResult;
126
+ };
127
+ /**
128
+ * Streams canonical tape rows to the existing archive-forwarder admission
129
+ * tables. Promotion, selection and exporter-v2 finalization remain later
130
+ * gates; this sink never creates a receipt or a success manifest by itself.
131
+ */
132
+ export declare function createSourceTapeArchiveSink(input: {
133
+ captureBundleId: string;
134
+ tradingPair: "ARB-USDC" | "ARB-USDT";
135
+ window: {
136
+ startTimeMs: number;
137
+ endTimeMs: number;
138
+ };
139
+ forwarder: {
140
+ submit(batch: ForwarderBatch): Promise<{
141
+ ok: boolean;
142
+ inserted: number;
143
+ }>;
144
+ };
145
+ }): SourceTapeArchiveSink;
146
+ export declare function providerObjectInventoryIsComplete(inventory: SourceTapeProviderObjectInventory): boolean;
147
+ export declare function evaluateSourceTapeEligibility(input: {
148
+ source_enumeration_eligible: boolean;
149
+ provider_object_inventory: SourceTapeProviderObjectInventory;
150
+ tape_complete: boolean;
151
+ capability_id: string;
152
+ capability_sha256: string;
153
+ construction_mode: string;
154
+ projection_schema_pins: readonly {
155
+ schema_id: string;
156
+ schema_sha256: string;
157
+ }[];
158
+ artifact_sha256s: readonly string[];
159
+ tape_manifest_sha256: string;
160
+ }): boolean;
161
+ export declare function normalizeSourceTapeStates(input: {
162
+ tape: readonly PolicyNeutralCryptoHftBookState[];
163
+ capture_bundle_id: string;
164
+ trading_pair: "ARB-USDC" | "ARB-USDT";
165
+ }): BackfillArchiveRow[];
@@ -0,0 +1,8 @@
1
+ import type { Metadata } from "@grpc/grpc-js";
2
+ /** gRPC metadata key for lightweight operational correlation with callers. */
3
+ export declare const TRACE_METADATA_KEY = "x-trace-id";
4
+ /**
5
+ * Returns a validated caller correlation ID without mutating inbound metadata.
6
+ * Invalid values are deliberately discarded before they can reach telemetry.
7
+ */
8
+ export declare function extractTraceId(metadata: Metadata): string | undefined;
package/dist/index.js CHANGED
@@ -294125,7 +294125,7 @@ function commonEvidenceFields(context2, rawCapture, input) {
294125
294125
  source_time_ms: input.eventTimeMs,
294126
294126
  received_time_ms: input.receivedTimeMs,
294127
294127
  snapshot_id: input.snapshotId,
294128
- construction_mode: "sampled_top_n_snapshot",
294128
+ construction_mode: input.constructionMode,
294129
294129
  gap_policy: "record_gap",
294130
294130
  depth_limit: input.depthLimit,
294131
294131
  sequence: input.sequence,
@@ -294179,7 +294179,8 @@ function buildCanonicalOrderBookRows(input) {
294179
294179
  sequence,
294180
294180
  depthLimit: input.depthLimit,
294181
294181
  eventTimeMs,
294182
- receivedTimeMs
294182
+ receivedTimeMs,
294183
+ constructionMode: input.constructionMode ?? "sampled_top_n_snapshot"
294183
294184
  });
294184
294185
  const levels = [
294185
294186
  ["bid", bids],
@@ -310668,6 +310669,24 @@ function selectBrokerAccountForCex(normalizedCex, brokers, metadata) {
310668
310669
  return selectBrokerAccount(brokers[normalizedCex], metadata) ?? undefined;
310669
310670
  }
310670
310671
 
310672
+ // src/helpers/trace-context.ts
310673
+ var TRACE_METADATA_KEY = "x-trace-id";
310674
+ var MAX_TRACE_METADATA_LENGTH = 128;
310675
+ var OTEL_TRACE_ID_PATTERN = /^[0-9a-f]{32}$/;
310676
+ var UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
310677
+ var ZERO_OTEL_TRACE_ID = "00000000000000000000000000000000";
310678
+ function extractTraceId(metadata) {
310679
+ const raw = metadata.get(TRACE_METADATA_KEY)[0];
310680
+ if (typeof raw !== "string" || raw.length > MAX_TRACE_METADATA_LENGTH) {
310681
+ return;
310682
+ }
310683
+ const traceId = raw.trim();
310684
+ if (OTEL_TRACE_ID_PATTERN.test(traceId) && traceId !== ZERO_OTEL_TRACE_ID || UUID_V4_PATTERN.test(traceId)) {
310685
+ return traceId;
310686
+ }
310687
+ return;
310688
+ }
310689
+
310671
310690
  // src/handlers/execute-action/order-book-call.ts
310672
310691
  import * as grpc4 from "@grpc/grpc-js";
310673
310692
  async function handleOrderBookCall(ctx) {
@@ -311971,6 +311990,15 @@ function isPublicMarketDataAction(action, payload) {
311971
311990
  return false;
311972
311991
  return isOrderBookCallMethod(payload?.method ?? payload?.functionName);
311973
311992
  }
311993
+ function grpcStatusName(error48) {
311994
+ if (!error48) {
311995
+ return "OK";
311996
+ }
311997
+ if (typeof error48.code !== "number") {
311998
+ return "UNKNOWN";
311999
+ }
312000
+ return grpc12.status[error48.code] ?? "UNKNOWN";
312001
+ }
311974
312002
  function createExecuteActionHandler(deps) {
311975
312003
  const {
311976
312004
  policy,
@@ -311987,12 +312015,28 @@ function createExecuteActionHandler(deps) {
311987
312015
  const startTime = Date.now();
311988
312016
  const { action: rawAction, cex: cex3, symbol: symbol2 } = call.request;
311989
312017
  const action = resolveAction(rawAction);
312018
+ const actionName = getActionName(action);
312019
+ const operationalCex = cex3?.trim().toLowerCase() || "unknown";
312020
+ const traceId = extractTraceId(call.metadata);
312021
+ const traceFields = traceId === undefined ? {} : { trace_id: traceId };
311990
312022
  let actionCompleted = false;
311991
312023
  const wrappedCallback = (error48, value) => {
311992
312024
  if (!actionCompleted) {
311993
312025
  actionCompleted = true;
311994
312026
  const latency = Date.now() - startTime;
311995
- const actionName = getActionName(action);
312027
+ const terminalFields = {
312028
+ action: actionName,
312029
+ cex: operationalCex,
312030
+ latency_ms: latency,
312031
+ outcome: error48 ? "error" : "success",
312032
+ grpc_status: grpcStatusName(error48),
312033
+ ...traceFields
312034
+ };
312035
+ if (error48) {
312036
+ log.withMetadata(terminalFields).error("ExecuteAction failed");
312037
+ } else {
312038
+ log.withMetadata(terminalFields).info("ExecuteAction completed");
312039
+ }
311996
312040
  otelMetrics?.recordHistogram("execute_action_duration_ms", latency, {
311997
312041
  action: actionName,
311998
312042
  cex: cex3 || "unknown"
@@ -312013,9 +312057,13 @@ function createExecuteActionHandler(deps) {
312013
312057
  callback(error48, value);
312014
312058
  };
312015
312059
  try {
312016
- log.info(`Request - ExecuteAction:`, { action, cex: cex3, symbol: symbol2 });
312060
+ log.withMetadata({
312061
+ action: actionName,
312062
+ cex: operationalCex,
312063
+ ...traceFields
312064
+ }).info("ExecuteAction started");
312017
312065
  otelMetrics?.recordCounter("execute_action_requests_total", 1, {
312018
- action: getActionName(action),
312066
+ action: actionName,
312019
312067
  cex: cex3 || "unknown"
312020
312068
  });
312021
312069
  if (!authenticateRequest(call, whitelistIps)) {
@@ -312253,6 +312301,9 @@ async function writeSubscribeError(call, isClosed, frame) {
312253
312301
  call.end();
312254
312302
  }
312255
312303
  }
312304
+ function grpcStatusName2(status14) {
312305
+ return typeof status14 === "number" ? grpc13.status[status14] ?? "UNKNOWN" : "UNKNOWN";
312306
+ }
312256
312307
  function getBinanceEventMarketId(event) {
312257
312308
  const value = event.s;
312258
312309
  return typeof value === "string" ? value : null;
@@ -312385,6 +312436,24 @@ function createSubscribeHandler(deps) {
312385
312436
  });
312386
312437
  return async (call) => {
312387
312438
  const subscribeStartTime = Date.now();
312439
+ const request = call.request;
312440
+ const { cex: cex3, symbol: symbol2, type: type2 } = request;
312441
+ const metadata = call.metadata;
312442
+ const traceId = extractTraceId(metadata);
312443
+ const traceFields = traceId === undefined ? {} : { trace_id: traceId };
312444
+ const operationalCex = cex3?.trim().toLowerCase() || "unknown";
312445
+ const operationalSymbol = symbol2?.trim() || "unknown";
312446
+ const subscriptionType2 = resolveSubscriptionType(type2);
312447
+ const subscriptionTypeName = getSubscriptionTypeName(subscriptionType2);
312448
+ const operationalFields = {
312449
+ cex: operationalCex,
312450
+ symbol: operationalSymbol,
312451
+ subscription_type: subscriptionTypeName,
312452
+ ...traceFields
312453
+ };
312454
+ let terminalLogged = false;
312455
+ let terminalOutcome = "completed";
312456
+ let terminalGrpcStatus = "OK";
312388
312457
  let streamClosed = false;
312389
312458
  let ownedBroker = null;
312390
312459
  let ownedBrokerClosePromise;
@@ -312407,12 +312476,41 @@ function createSubscribeHandler(deps) {
312407
312476
  const closeOwnedBrokerOnCallEnd = () => {
312408
312477
  closeOwnedBroker();
312409
312478
  };
312410
- call.once("cancelled", markStreamClosed);
312479
+ const logTerminalOutcome = (outcome) => {
312480
+ if (terminalLogged) {
312481
+ return;
312482
+ }
312483
+ terminalLogged = true;
312484
+ const fields = {
312485
+ ...operationalFields,
312486
+ duration_ms: Date.now() - subscribeStartTime,
312487
+ outcome,
312488
+ grpc_status: outcome === "cancelled" ? "CANCELLED" : terminalGrpcStatus
312489
+ };
312490
+ if (outcome === "error") {
312491
+ log.withMetadata(fields).error("Subscribe failed");
312492
+ } else if (outcome === "cancelled") {
312493
+ log.withMetadata(fields).info("Subscribe cancelled");
312494
+ } else {
312495
+ log.withMetadata(fields).info("Subscribe ended");
312496
+ }
312497
+ };
312498
+ const writeTerminalError = async (frame, status14 = grpc13.status.UNKNOWN) => {
312499
+ terminalOutcome = "error";
312500
+ terminalGrpcStatus = grpcStatusName2(status14);
312501
+ await writeSubscribeError(call, isStreamClosed, frame);
312502
+ };
312503
+ log.withMetadata(operationalFields).info("Subscribe started");
312504
+ call.once("cancelled", () => {
312505
+ terminalGrpcStatus = "CANCELLED";
312506
+ markStreamClosed();
312507
+ logTerminalOutcome("cancelled");
312508
+ });
312411
312509
  call.once("cancelled", closeOwnedBrokerOnCallEnd);
312412
312510
  call.once("error", closeOwnedBrokerOnCallEnd);
312413
312511
  call.once("end", () => {
312414
312512
  markStreamClosed();
312415
- log.info("Subscribe stream ended");
312513
+ logTerminalOutcome(terminalOutcome);
312416
312514
  const duration3 = Date.now() - subscribeStartTime;
312417
312515
  otelMetrics?.recordHistogram("subscribe_duration_ms", duration3, {
312418
312516
  cex: call.request?.cex || "unknown",
@@ -312421,12 +312519,16 @@ function createSubscribeHandler(deps) {
312421
312519
  });
312422
312520
  call.once("error", (error48) => {
312423
312521
  markStreamClosed();
312424
- log.error("Subscribe stream error:", error48);
312522
+ terminalOutcome = "error";
312523
+ terminalGrpcStatus = grpcStatusName2(typeof error48 === "object" && error48 !== null && "code" in error48 ? error48.code : undefined);
312524
+ logTerminalOutcome(call.cancelled ? "cancelled" : "error");
312425
312525
  otelMetrics?.recordCounter("subscribe_errors_total", 1, {
312426
312526
  error_type: error48 instanceof Error ? error48.message : "unknown"
312427
312527
  });
312428
312528
  });
312429
312529
  if (!authenticateRequest(call, whitelistIps)) {
312530
+ terminalOutcome = "error";
312531
+ terminalGrpcStatus = "PERMISSION_DENIED";
312430
312532
  otelMetrics?.recordCounter("subscribe_errors_total", 1, {
312431
312533
  error_type: "permission_denied"
312432
312534
  });
@@ -312437,32 +312539,22 @@ function createSubscribeHandler(deps) {
312437
312539
  call.destroy(new Error("Access denied: Unauthorized IP"));
312438
312540
  return;
312439
312541
  }
312440
- const metadata = call.metadata;
312441
- let subscriptionType2 = SubscriptionType.ORDERBOOK;
312442
312542
  try {
312443
- const request = call.request;
312444
- const { cex: cex3, symbol: symbol2, type: type2, options } = request;
312445
- subscriptionType2 = resolveSubscriptionType(type2);
312446
- log.info(`Request - Subscribe:`, {
312447
- cex: request.cex,
312448
- symbol: request.symbol,
312449
- type: subscriptionType2
312450
- });
312451
- const subscriptionTypeName = getSubscriptionTypeName(subscriptionType2);
312543
+ const { options } = request;
312452
312544
  otelMetrics?.recordCounter("subscribe_requests_total", 1, {
312453
312545
  cex: cex3 || "unknown",
312454
312546
  symbol: symbol2 || "unknown",
312455
312547
  type: subscriptionTypeName
312456
312548
  });
312457
312549
  if (!cex3 || !symbol2) {
312458
- await writeSubscribeError(call, isStreamClosed, {
312550
+ await writeTerminalError({
312459
312551
  data: JSON.stringify({
312460
312552
  error: "cex, symbol, and type are required"
312461
312553
  }),
312462
312554
  timestamp: Date.now(),
312463
312555
  symbol: symbol2 || "",
312464
312556
  type: subscriptionType2
312465
- });
312557
+ }, grpc13.status.INVALID_ARGUMENT);
312466
312558
  return;
312467
312559
  }
312468
312560
  if (isPublicMarketDataSubscription(subscriptionType2)) {
@@ -312491,7 +312583,7 @@ function createSubscribeHandler(deps) {
312491
312583
  } catch (error48) {
312492
312584
  const message = getErrorMessage(error48);
312493
312585
  if (!isStreamClosed()) {
312494
- await writeSubscribeError(call, isStreamClosed, {
312586
+ await writeTerminalError({
312495
312587
  data: JSON.stringify({ error: message }),
312496
312588
  timestamp: Date.now(),
312497
312589
  symbol: symbol2,
@@ -312512,14 +312604,14 @@ function createSubscribeHandler(deps) {
312512
312604
  const selectedBroker = selectedBrokerAccount?.exchange ?? createBroker(normalizedCex, metadata);
312513
312605
  const broker = selectedBroker ?? createPublicBroker(normalizedCex);
312514
312606
  if (!broker) {
312515
- await writeSubscribeError(call, isStreamClosed, {
312607
+ await writeTerminalError({
312516
312608
  data: JSON.stringify({
312517
312609
  error: "Exchange not registered and no API metadata found"
312518
312610
  }),
312519
312611
  timestamp: Date.now(),
312520
312612
  symbol: symbol2,
312521
312613
  type: subscriptionType2
312522
- });
312614
+ }, grpc13.status.NOT_FOUND);
312523
312615
  return;
312524
312616
  }
312525
312617
  if (!selectedBrokerAccount) {
@@ -312547,28 +312639,28 @@ function createSubscribeHandler(deps) {
312547
312639
  if (isBinanceSpotAccountSubscription(normalizedCex, subscriptionType2, options?.marketType)) {
312548
312640
  const accountBroker = selectedBrokerAccount?.exchange ?? selectedBroker;
312549
312641
  if (!accountBroker) {
312550
- await writeSubscribeError(call, isStreamClosed, {
312642
+ await writeTerminalError({
312551
312643
  data: JSON.stringify({
312552
312644
  error: "Binance account subscriptions require API credentials"
312553
312645
  }),
312554
312646
  timestamp: Date.now(),
312555
312647
  symbol: resolvedSymbol,
312556
312648
  type: subscriptionType2
312557
- });
312649
+ }, grpc13.status.FAILED_PRECONDITION);
312558
312650
  return;
312559
312651
  }
312560
312652
  const marketId = subscriptionType2 === SubscriptionType.ORDERS ? await getBinanceMarketId(accountBroker, resolvedSymbol) : undefined;
312561
312653
  let userDataSource;
312562
312654
  if (selectedBrokerAccount) {
312563
312655
  if (!userDataStreamSupervisor) {
312564
- await writeSubscribeError(call, isStreamClosed, {
312656
+ await writeTerminalError({
312565
312657
  data: JSON.stringify({
312566
312658
  error: "Configured account user-data supervisor is unavailable"
312567
312659
  }),
312568
312660
  timestamp: Date.now(),
312569
312661
  symbol: resolvedSymbol,
312570
312662
  type: subscriptionType2
312571
- });
312663
+ }, grpc13.status.FAILED_PRECONDITION);
312572
312664
  return;
312573
312665
  }
312574
312666
  userDataSource = userDataStreamSupervisor.subscribe({
@@ -312591,7 +312683,7 @@ function createSubscribeHandler(deps) {
312591
312683
  } catch (error48) {
312592
312684
  const message = getErrorMessage(error48);
312593
312685
  log.error(`Error fetching balance for ${cex3}:`, error48);
312594
- await writeSubscribeError(call, isStreamClosed, {
312686
+ await writeTerminalError({
312595
312687
  data: JSON.stringify({
312596
312688
  error: `Failed to fetch balance: ${message}`
312597
312689
  }),
@@ -312610,7 +312702,7 @@ function createSubscribeHandler(deps) {
312610
312702
  } catch (error48) {
312611
312703
  log.error(`Error fetching orders for ${resolvedSymbol} on ${cex3}:`, error48);
312612
312704
  const message = getErrorMessage(error48);
312613
- await writeSubscribeError(call, isStreamClosed, {
312705
+ await writeTerminalError({
312614
312706
  data: JSON.stringify({
312615
312707
  error: `Failed to fetch orders: ${message}`
312616
312708
  }),
@@ -312621,26 +312713,27 @@ function createSubscribeHandler(deps) {
312621
312713
  }
312622
312714
  break;
312623
312715
  default:
312624
- await writeSubscribeError(call, isStreamClosed, {
312716
+ await writeTerminalError({
312625
312717
  data: JSON.stringify({ error: "Invalid subscription type" }),
312626
312718
  timestamp: Date.now(),
312627
312719
  symbol: symbol2,
312628
312720
  type: subscriptionType2
312629
- });
312721
+ }, grpc13.status.INVALID_ARGUMENT);
312630
312722
  }
312631
312723
  } catch (error48) {
312632
312724
  log.error("Error in Subscribe stream:", error48);
312633
312725
  const message = getErrorMessage(error48);
312634
- await writeSubscribeError(call, isStreamClosed, {
312726
+ await writeTerminalError({
312635
312727
  data: JSON.stringify({ error: `Internal server error: ${message}` }),
312636
312728
  timestamp: Date.now(),
312637
312729
  symbol: "",
312638
312730
  type: subscriptionType2
312639
- });
312731
+ }, grpc13.status.INTERNAL);
312640
312732
  } finally {
312641
312733
  call.off("cancelled", closeOwnedBrokerOnCallEnd);
312642
312734
  call.off("error", closeOwnedBrokerOnCallEnd);
312643
312735
  await closeOwnedBroker();
312736
+ logTerminalOutcome(call.cancelled ? "cancelled" : terminalOutcome);
312644
312737
  }
312645
312738
  };
312646
312739
  }
@@ -313252,4 +313345,4 @@ export {
313252
313345
  CEXBroker as default
313253
313346
  };
313254
313347
 
313255
- //# debugId=D97F25782E1185C764756E2164756E21
313348
+ //# debugId=298884FD1F08284464756E2164756E21