tapimo 0.10.1 → 0.11.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 (225) hide show
  1. package/dist/App.d.ts +5249 -3572
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js.map +1 -1
  4. package/dist/Webhooks.d.ts +1 -76
  5. package/dist/Webhooks.d.ts.map +1 -1
  6. package/dist/Webhooks.js +2 -144
  7. package/dist/Webhooks.js.map +1 -1
  8. package/dist/admin/App.d.ts +74 -74
  9. package/dist/admin/apps/earn-vaults.d.ts +1 -1
  10. package/dist/admin/apps/organizations.d.ts +3 -3
  11. package/dist/admin/apps/verified-tokens.d.ts +74 -74
  12. package/dist/analytics/Analytics.d.ts.map +1 -1
  13. package/dist/analytics/Analytics.js +206 -0
  14. package/dist/analytics/Analytics.js.map +1 -1
  15. package/dist/analytics/tables/requestEvents.d.ts +4 -0
  16. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  17. package/dist/analytics/tables/requestEvents.js +2 -0
  18. package/dist/analytics/tables/requestEvents.js.map +1 -1
  19. package/dist/apps/data/App.d.ts +3618 -2954
  20. package/dist/apps/data/App.d.ts.map +1 -1
  21. package/dist/apps/data/App.js +7 -2
  22. package/dist/apps/data/App.js.map +1 -1
  23. package/dist/apps/data/routes/activities.d.ts +1 -1
  24. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  25. package/dist/apps/data/routes/activities.js +55 -27
  26. package/dist/apps/data/routes/activities.js.map +1 -1
  27. package/dist/apps/data/routes/balances.d.ts +11 -11
  28. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  29. package/dist/apps/data/routes/balances.js +2 -6
  30. package/dist/apps/data/routes/balances.js.map +1 -1
  31. package/dist/apps/data/routes/blocks.d.ts +5 -5
  32. package/dist/apps/data/routes/earn.d.ts +4 -4
  33. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  34. package/dist/apps/data/routes/earn.js +63 -34
  35. package/dist/apps/data/routes/earn.js.map +1 -1
  36. package/dist/apps/data/routes/exchanges.d.ts +8 -4
  37. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  38. package/dist/apps/data/routes/exchanges.js +115 -77
  39. package/dist/apps/data/routes/exchanges.js.map +1 -1
  40. package/dist/apps/data/routes/fee-amm.d.ts +26 -26
  41. package/dist/apps/data/routes/receipts.d.ts +240 -239
  42. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  43. package/dist/apps/data/routes/receipts.js +15 -14
  44. package/dist/apps/data/routes/receipts.js.map +1 -1
  45. package/dist/apps/data/routes/tokenlist.d.ts +12 -12
  46. package/dist/apps/data/routes/tokens.d.ts +4 -4
  47. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  48. package/dist/apps/data/routes/tokens.js +4 -3
  49. package/dist/apps/data/routes/tokens.js.map +1 -1
  50. package/dist/apps/data/routes/transactions.d.ts +282 -282
  51. package/dist/apps/data/routes/transfers.d.ts +13 -13
  52. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  53. package/dist/apps/data/routes/transfers.js +25 -7
  54. package/dist/apps/data/routes/transfers.js.map +1 -1
  55. package/dist/apps/data/routes/valuation.d.ts +21 -21
  56. package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
  57. package/dist/apps/data/routes/webhooks.d.ts +1519 -643
  58. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  59. package/dist/apps/data/routes/webhooks.js +134 -740
  60. package/dist/apps/data/routes/webhooks.js.map +1 -1
  61. package/dist/apps/data/routes/zones.d.ts +3 -3
  62. package/dist/apps/funding/App.d.ts +233 -0
  63. package/dist/apps/funding/App.d.ts.map +1 -1
  64. package/dist/apps/funding/routes/deposit-addresses.d.ts +331 -1
  65. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  66. package/dist/apps/funding/routes/deposit-addresses.js +207 -11
  67. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  68. package/dist/apps/funding/routes/transfers.d.ts +33 -33
  69. package/dist/apps/management/App.d.ts +793 -16
  70. package/dist/apps/management/App.d.ts.map +1 -1
  71. package/dist/apps/management/routes/api-keys.d.ts +50 -50
  72. package/dist/apps/management/routes/faucet.d.ts +2 -2
  73. package/dist/apps/management/routes/invite-links.d.ts +42 -42
  74. package/dist/apps/management/routes/members.d.ts +2 -2
  75. package/dist/apps/management/routes/projects.d.ts +40 -40
  76. package/dist/apps/management/routes/webhooks.d.ts +1031 -59
  77. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  78. package/dist/apps/management/routes/webhooks.js +27 -4
  79. package/dist/apps/management/routes/webhooks.js.map +1 -1
  80. package/dist/db/Db.d.ts.map +1 -1
  81. package/dist/db/Db.js +53 -0
  82. package/dist/db/Db.js.map +1 -1
  83. package/dist/db/Schema.d.ts +7 -1
  84. package/dist/db/Schema.d.ts.map +1 -1
  85. package/dist/db/Schema.js +9 -0
  86. package/dist/db/Schema.js.map +1 -1
  87. package/dist/db/tables/fundingDepositAddresses.d.ts +20 -0
  88. package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
  89. package/dist/db/tables/fundingDepositAddresses.js +17 -0
  90. package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
  91. package/dist/db/tables/fundingDeposits.d.ts +41 -0
  92. package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
  93. package/dist/db/tables/fundingDeposits.js +69 -0
  94. package/dist/db/tables/fundingDeposits.js.map +1 -1
  95. package/dist/db/tables/webhookQueueEvents.d.ts +3 -1
  96. package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -1
  97. package/dist/db/tables/webhookQueueEvents.js +4 -0
  98. package/dist/db/tables/webhookQueueEvents.js.map +1 -1
  99. package/dist/db/tables/webhookSubscriptions.d.ts +33 -69
  100. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  101. package/dist/db/tables/webhookSubscriptions.js +63 -100
  102. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  103. package/dist/internal/Auth.d.ts +0 -26
  104. package/dist/internal/Auth.d.ts.map +1 -1
  105. package/dist/internal/Auth.js +0 -33
  106. package/dist/internal/Auth.js.map +1 -1
  107. package/dist/internal/Credential.d.ts +27 -0
  108. package/dist/internal/Credential.d.ts.map +1 -0
  109. package/dist/internal/Credential.js +34 -0
  110. package/dist/internal/Credential.js.map +1 -0
  111. package/dist/internal/EarnRates.js +11 -7
  112. package/dist/internal/EarnRates.js.map +1 -1
  113. package/dist/internal/Log.d.ts +8 -0
  114. package/dist/internal/Log.d.ts.map +1 -1
  115. package/dist/internal/Log.js +4 -0
  116. package/dist/internal/Log.js.map +1 -1
  117. package/dist/internal/MetricSink.d.ts +50 -116
  118. package/dist/internal/MetricSink.d.ts.map +1 -1
  119. package/dist/internal/MetricSink.js +60 -106
  120. package/dist/internal/MetricSink.js.map +1 -1
  121. package/dist/internal/Schema.d.ts +3 -1
  122. package/dist/internal/Schema.d.ts.map +1 -1
  123. package/dist/internal/Schema.js +4 -2
  124. package/dist/internal/Schema.js.map +1 -1
  125. package/dist/internal/Tidx.d.ts +4 -3
  126. package/dist/internal/Tidx.d.ts.map +1 -1
  127. package/dist/internal/Tidx.js +2 -2
  128. package/dist/internal/Tidx.js.map +1 -1
  129. package/dist/internal/Viem.d.ts +4 -3
  130. package/dist/internal/Viem.d.ts.map +1 -1
  131. package/dist/internal/Viem.js +3 -2
  132. package/dist/internal/Viem.js.map +1 -1
  133. package/dist/internal/WebhookTransfer.d.ts +1 -55
  134. package/dist/internal/WebhookTransfer.d.ts.map +1 -1
  135. package/dist/internal/WebhookTransfer.js +1 -239
  136. package/dist/internal/WebhookTransfer.js.map +1 -1
  137. package/dist/internal/Webhooks.d.ts +85 -44
  138. package/dist/internal/Webhooks.d.ts.map +1 -1
  139. package/dist/internal/Webhooks.js +94 -73
  140. package/dist/internal/Webhooks.js.map +1 -1
  141. package/dist/internal/funding/DepositWebhook.d.ts +25 -0
  142. package/dist/internal/funding/DepositWebhook.d.ts.map +1 -0
  143. package/dist/internal/funding/DepositWebhook.js +74 -0
  144. package/dist/internal/funding/DepositWebhook.js.map +1 -0
  145. package/dist/internal/funding/Reconciliation.d.ts +6 -0
  146. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  147. package/dist/internal/funding/Reconciliation.js +42 -10
  148. package/dist/internal/funding/Reconciliation.js.map +1 -1
  149. package/dist/internal/funding/index.d.ts +1 -0
  150. package/dist/internal/funding/index.d.ts.map +1 -1
  151. package/dist/internal/funding/index.js +1 -0
  152. package/dist/internal/funding/index.js.map +1 -1
  153. package/dist/internal/index.d.ts +1 -0
  154. package/dist/internal/index.d.ts.map +1 -1
  155. package/dist/internal/index.js +1 -0
  156. package/dist/internal/index.js.map +1 -1
  157. package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
  158. package/dist/internal/webhookDestinations/slack.js +16 -0
  159. package/dist/internal/webhookDestinations/slack.js.map +1 -1
  160. package/package.json +1 -1
  161. package/src/App.test.ts +30 -0
  162. package/src/App.ts +3 -0
  163. package/src/Client.test-d.ts +55 -2
  164. package/src/Webhooks.ts +2 -221
  165. package/src/analytics/Analytics.test.ts +34 -0
  166. package/src/analytics/Analytics.ts +206 -0
  167. package/src/analytics/tables/requestEvents.ts +6 -0
  168. package/src/apps/composition.test.ts +1 -0
  169. package/src/apps/data/App.ts +10 -2
  170. package/src/apps/data/routes/activities.test.ts +107 -11
  171. package/src/apps/data/routes/activities.ts +64 -32
  172. package/src/apps/data/routes/balances.ts +2 -6
  173. package/src/apps/data/routes/earn.ts +64 -34
  174. package/src/apps/data/routes/exchanges.test.ts +8 -3
  175. package/src/apps/data/routes/exchanges.ts +134 -82
  176. package/src/apps/data/routes/receipts.ts +16 -14
  177. package/src/apps/data/routes/tokens.ts +3 -2
  178. package/src/apps/data/routes/transactions.bench.ts +8 -0
  179. package/src/apps/data/routes/transfers.ts +36 -10
  180. package/src/apps/data/routes/webhooks.test.ts +179 -1497
  181. package/src/apps/data/routes/webhooks.ts +172 -975
  182. package/src/apps/funding/routes/deposit-addresses.test.ts +216 -2
  183. package/src/apps/funding/routes/deposit-addresses.ts +254 -18
  184. package/src/apps/funding/routes/transfers.test.ts +17 -2
  185. package/src/apps/management/routes/webhooks.test.ts +79 -1
  186. package/src/apps/management/routes/webhooks.ts +40 -4
  187. package/src/db/Db.test.ts +1 -5
  188. package/src/db/Db.ts +59 -0
  189. package/src/db/Schema.ts +13 -0
  190. package/src/db/tables/fundingDepositAddresses.test.ts +28 -0
  191. package/src/db/tables/fundingDepositAddresses.ts +39 -0
  192. package/src/db/tables/fundingDeposits.test.ts +82 -0
  193. package/src/db/tables/fundingDeposits.ts +130 -0
  194. package/src/db/tables/organizations.test.ts +5 -2
  195. package/src/db/tables/webhookQueueEvents.ts +7 -1
  196. package/src/db/tables/webhookSubscriptions.ts +89 -138
  197. package/src/internal/Auth.test.ts +0 -48
  198. package/src/internal/Auth.ts +0 -59
  199. package/src/internal/Credential.test.ts +51 -0
  200. package/src/internal/Credential.ts +58 -0
  201. package/src/internal/EarnRates.ts +13 -7
  202. package/src/internal/Log.test.ts +3 -1
  203. package/src/internal/Log.ts +14 -0
  204. package/src/internal/MetricSink.test.ts +75 -358
  205. package/src/internal/MetricSink.ts +117 -246
  206. package/src/internal/Schema.test.ts +18 -0
  207. package/src/internal/Schema.ts +13 -2
  208. package/src/internal/Tidx.ts +5 -4
  209. package/src/internal/Viem.test.ts +4 -0
  210. package/src/internal/Viem.ts +6 -4
  211. package/src/internal/WebhookTransfer.ts +1 -351
  212. package/src/internal/Webhooks.test.ts +111 -139
  213. package/src/internal/Webhooks.ts +181 -105
  214. package/src/internal/funding/DepositWebhook.test.ts +131 -0
  215. package/src/internal/funding/DepositWebhook.ts +101 -0
  216. package/src/internal/funding/Reconciliation.test.ts +14 -0
  217. package/src/internal/funding/Reconciliation.ts +161 -86
  218. package/src/internal/funding/index.test.ts +1 -0
  219. package/src/internal/funding/index.ts +1 -0
  220. package/src/internal/index.test.ts +1 -0
  221. package/src/internal/index.ts +1 -0
  222. package/src/internal/webhookDestinations/slack.ts +15 -0
  223. package/src/Webhooks.bench.ts +0 -714
  224. package/src/Webhooks.memory.bench.ts +0 -250
  225. package/src/Webhooks.test.ts +0 -146
@@ -10,10 +10,14 @@ import type * as Timing from './Timing.js'
10
10
 
11
11
  /** Hono variables read by the request logger and written by other layers. */
12
12
  export type Variables = {
13
+ /** Tempo chain selected for the request. */
14
+ chainId?: number | undefined
13
15
  /** Whether the original request exception was already reported. */
14
16
  errorCaptured?: boolean | undefined
15
17
  /** Stable error code recorded by `Response.error` for the request log entry. */
16
18
  errorCode?: string | undefined
19
+ /** Whether the optional funding deposit count query failed. */
20
+ fundingDepositCountFailed?: true | undefined
17
21
  /** Bounded MPP relay outcome emitted by the credential routes. */
18
22
  mpp?: Entry['mpp'] | undefined
19
23
  /** Bounded outcomes for funding providers attempted by the request. */
@@ -125,12 +129,16 @@ export type Sponsorship = {
125
129
  export type Entry = {
126
130
  /** `'hit'` when the edge response cache served the request without reaching the origin handlers. */
127
131
  cache?: 'hit' | undefined
132
+ /** Tempo chain selected for the request. */
133
+ chainId?: number | undefined
128
134
  /** Total request duration in milliseconds. */
129
135
  duration: number
130
136
  /** Whether the original request exception was already reported. */
131
137
  errorCaptured?: boolean | undefined
132
138
  /** Stable error code when the response is a JSON error envelope (e.g. `not_found`, `api_key_invalid`). */
133
139
  errorCode?: string | undefined
140
+ /** Whether the optional funding deposit count query failed. */
141
+ fundingDepositCountFailed?: true | undefined
134
142
  /** Bounded funding provider attempts made while serving the request. */
135
143
  fundingProviderAttempts?: readonly FundingProviderAttempt[] | undefined
136
144
  /** Whether the optional funding transfer count query failed. */
@@ -253,7 +261,11 @@ export function middleware<environment extends Environment = Environment>(
253
261
  const error = c.error as ErrorMetadata | undefined
254
262
  const principal = c.get('principal' as never) as Auth.Principal | undefined
255
263
  const errorCaptured = c.get('errorCaptured' as never) as boolean | undefined
264
+ const chainId = c.get('chainId' as never) as number | undefined
256
265
  const errorCode = c.get('errorCode' as never) as string | undefined
266
+ const fundingDepositCountFailed = c.get('fundingDepositCountFailed' as never) as
267
+ | true
268
+ | undefined
257
269
  const fundingProviderAttempts =
258
270
  error?.fundingProviderAttempts ??
259
271
  (c.get('fundingProviderAttempts' as never) as Entry['fundingProviderAttempts'] | undefined)
@@ -300,9 +312,11 @@ export function middleware<environment extends Environment = Environment>(
300
312
 
301
313
  const result = fn({
302
314
  ...(c.get('edgeCacheStatus' as never) === 'hit' ? { cache: 'hit' as const } : {}),
315
+ ...(chainId === undefined ? {} : { chainId }),
303
316
  duration: round(duration),
304
317
  ...(errorCaptured === undefined ? {} : { errorCaptured }),
305
318
  ...(errorCode === undefined ? {} : { errorCode }),
319
+ ...(fundingDepositCountFailed === undefined ? {} : { fundingDepositCountFailed }),
306
320
  ...(fundingProviderAttempts === undefined ? {} : { fundingProviderAttempts }),
307
321
  ...(fundingTransferCountFailed === undefined ? {} : { fundingTransferCountFailed }),
308
322
  level:
@@ -11,7 +11,7 @@ type MetricEvent = {
11
11
  }
12
12
 
13
13
  describe('requests', () => {
14
- test('records optional funding transfer count failures on successful pages', () => {
14
+ test('records optional funding count failures on successful pages', () => {
15
15
  const events: MetricEvent[] = []
16
16
  const metrics = Metrics.from({
17
17
  count(name, value, tags) {
@@ -25,6 +25,7 @@ describe('requests', () => {
25
25
 
26
26
  record({
27
27
  duration: 10,
28
+ fundingDepositCountFailed: true,
28
29
  fundingTransferCountFailed: true,
29
30
  level: 'info',
30
31
  method: 'GET',
@@ -34,6 +35,22 @@ describe('requests', () => {
34
35
  status: 200,
35
36
  })
36
37
 
38
+ expect(events.find((event) => event.name === 'funding_deposit_count_failure_count'))
39
+ .toMatchInlineSnapshot(`
40
+ {
41
+ "name": "funding_deposit_count_failure_count",
42
+ "tags": {
43
+ "cache": "miss",
44
+ "method": "GET",
45
+ "principal_environment": "none",
46
+ "principal_type": "unknown",
47
+ "route": "/v1/funding/transfers",
48
+ },
49
+ "type": "count",
50
+ "value": 1,
51
+ }
52
+ `)
53
+
37
54
  expect(events.find((event) => event.name === 'funding_transfer_count_failure_count'))
38
55
  .toMatchInlineSnapshot(`
39
56
  {
@@ -456,7 +473,7 @@ describe('requests', () => {
456
473
  })
457
474
 
458
475
  describe('webhooks', () => {
459
- test('records recovery, poll-cycle, poll-stage, and failed-tick telemetry with bounded tags', () => {
476
+ test('records scan pipeline blocks, backlog, and finality lag', () => {
460
477
  const events: MetricEvent[] = []
461
478
  const metrics = Metrics.from({
462
479
  count(name, value, tags) {
@@ -466,175 +483,69 @@ describe('webhooks', () => {
466
483
  gauge(name, value, tags) {
467
484
  events.push({ name, tags, type: 'gauge', value })
468
485
  },
486
+ histogram() {},
487
+ })
488
+ const sink = MetricSink.webhooks(metrics)
489
+
490
+ sink.record({ chainId: 42431, deferred: false, matches: 3, origin: 'socket', outcome: 'dispatched', type: 'webhook:scan-block', workMs: 12 }) // prettier-ignore
491
+ sink.record({ chainId: 42431, deferred: true, matches: 0, origin: 'poll', outcome: 'deferred', type: 'webhook:scan-block', workMs: 3 }) // prettier-ignore
492
+ sink.record({ chainId: 42431, oldestPendingAgeMs: 5_000, pendingCount: 2, type: 'webhook:scan-backlog', watermarkLagBlocks: 7 }) // prettier-ignore
493
+ sink.record({ chainId: 42431, lagBlocks: 1, type: 'webhook:scan-finality' })
494
+
495
+ expect(events.map(({ name, tags, value }) => `${name}=${value} ${JSON.stringify(tags)}`))
496
+ .toMatchInlineSnapshot(`
497
+ [
498
+ "webhook_scan_block_count=1 {"chain_id":"42431","origin":"socket","outcome":"dispatched"}",
499
+ "webhook_scan_match_count=3 {"chain_id":"42431","origin":"socket","outcome":"dispatched"}",
500
+ "webhook_scan_block_count=1 {"chain_id":"42431","origin":"poll","outcome":"deferred"}",
501
+ "webhook_scan_pending_count=2 {"chain_id":"42431"}",
502
+ "webhook_scan_oldest_pending_age_ms=5000 {"chain_id":"42431"}",
503
+ "webhook_scan_watermark_lag_blocks=7 {"chain_id":"42431"}",
504
+ "webhook_scan_finality_lag_blocks=1 {"chain_id":"42431"}",
505
+ ]
506
+ `)
507
+ })
508
+
509
+ test('records scan timing spans and per-step attribution', () => {
510
+ const events: MetricEvent[] = []
511
+ const metrics = Metrics.from({
512
+ count(name, value, tags) {
513
+ events.push({ name, tags, type: 'count', value })
514
+ },
515
+ flush() {},
516
+ gauge() {},
469
517
  histogram(name, value, tags) {
470
518
  events.push({ name, tags, type: 'histogram', value })
471
519
  },
472
520
  })
473
521
  const sink = MetricSink.webhooks(metrics)
474
522
 
475
- sink.record({
476
- chainId: 4217,
477
- dequeuedAt: 1_300,
478
- enqueuedAt: 1_100,
479
- trigger: 'alarm',
480
- type: 'webhook:head-recovery',
481
- })
482
- sink.record({
483
- chainId: 4217,
484
- lagBlocks: 2,
485
- type: 'webhook:head-progress',
486
- })
487
- sink.record({
488
- chainId: 4217,
489
- delayMs: 200,
490
- trigger: 'direct',
491
- type: 'webhook:poll-start',
492
- })
493
- sink.record({
494
- chainId: 4217,
495
- outcome: 'ok',
496
- requestedBoundaryAdvanceBlocks: 10,
497
- trigger: 'direct',
498
- type: 'webhook:poll-cycle',
499
- })
500
- sink.record({
501
- chainId: 4217,
502
- failure: 'remote',
503
- stage: 'finish',
504
- trigger: 'direct',
505
- type: 'webhook:poll-failure',
506
- })
507
- sink.record({
508
- chainId: 4217,
509
- durationMs: 42,
510
- eventType: 'token:transfer',
511
- outcome: 'ok',
512
- stage: 'scan',
513
- type: 'webhook:poll',
514
- })
515
- sink.record({
516
- cursorLagBlocks: 7,
517
- meta: { chainId: 4217, durationMs: 50, outcome: 'error' },
518
- result: undefined,
519
- type: 'webhook:tick',
520
- })
523
+ sink.record({ chainId: 42431, deferred: false, matches: 1, observationLagMs: 600, origin: 'socket', outcome: 'dispatched', queueWaitMs: 900, scanLagMs: 1_500, type: 'webhook:scan-block', workMs: 400 }) // prettier-ignore
524
+ // A replay carries no observation, so only the block-derived spans exist.
525
+ sink.record({ chainId: 42431, deferred: false, matches: 0, origin: 'replay', outcome: 'empty', scanLagMs: 30_000_000, type: 'webhook:scan-block', workMs: 250 }) // prettier-ignore
526
+ // Batch totals: the count is what makes 180ms across two calls readable
527
+ // as a per-invocation mean rather than a slow single read.
528
+ sink.record({ chainId: 42431, count: 2, durationMs: 180, step: 'finalized_rpc', type: 'webhook:scan-step' }) // prettier-ignore
529
+ sink.record({ chainId: 42431, count: 1, durationMs: 95, step: 'subscription_read', type: 'webhook:scan-step' }) // prettier-ignore
521
530
 
522
- expect(events).toMatchInlineSnapshot(`
523
- [
524
- {
525
- "name": "webhook_head_recovery_queue_count",
526
- "tags": {
527
- "chain_id": "4217",
528
- "trigger": "alarm",
529
- },
530
- "type": "count",
531
- "value": 1,
532
- },
533
- {
534
- "name": "webhook_head_recovery_queue_wait_ms",
535
- "tags": {
536
- "chain_id": "4217",
537
- "trigger": "alarm",
538
- },
539
- "type": "histogram",
540
- "value": 200,
541
- },
542
- {
543
- "name": "webhook_head_progress_lag_blocks",
544
- "tags": {
545
- "chain_id": "4217",
546
- },
547
- "type": "gauge",
548
- "value": 2,
549
- },
550
- {
551
- "name": "webhook_poll_start_count",
552
- "tags": {
553
- "chain_id": "4217",
554
- "trigger": "direct",
555
- },
556
- "type": "count",
557
- "value": 1,
558
- },
559
- {
560
- "name": "webhook_poll_start_delay_ms",
561
- "tags": {
562
- "chain_id": "4217",
563
- "trigger": "direct",
564
- },
565
- "type": "histogram",
566
- "value": 200,
567
- },
568
- {
569
- "name": "webhook_poll_cycle_count",
570
- "tags": {
571
- "chain_id": "4217",
572
- "outcome": "ok",
573
- "trigger": "direct",
574
- },
575
- "type": "count",
576
- "value": 1,
577
- },
578
- {
579
- "name": "webhook_poll_requested_boundary_advance_blocks",
580
- "tags": {
581
- "chain_id": "4217",
582
- "outcome": "ok",
583
- "trigger": "direct",
584
- },
585
- "type": "histogram",
586
- "value": 10,
587
- },
588
- {
589
- "name": "webhook_poll_failure_count",
590
- "tags": {
591
- "chain_id": "4217",
592
- "failure": "remote",
593
- "stage": "finish",
594
- "trigger": "direct",
595
- },
596
- "type": "count",
597
- "value": 1,
598
- },
599
- {
600
- "name": "webhook_poller_stage_duration_ms",
601
- "tags": {
602
- "chain_id": "4217",
603
- "event_type": "token:transfer",
604
- "outcome": "ok",
605
- "stage": "scan",
606
- },
607
- "type": "histogram",
608
- "value": 42,
609
- },
610
- {
611
- "name": "webhook_poller_tick_count",
612
- "tags": {
613
- "chain_id": "4217",
614
- "outcome": "error",
615
- },
616
- "type": "count",
617
- "value": 1,
618
- },
619
- {
620
- "name": "webhook_poller_tick_duration_ms",
621
- "tags": {
622
- "chain_id": "4217",
623
- "outcome": "error",
624
- },
625
- "type": "histogram",
626
- "value": 50,
627
- },
628
- {
629
- "name": "webhook_subscription_cursor_lag_blocks",
630
- "tags": {
631
- "chain_id": "4217",
632
- },
633
- "type": "gauge",
634
- "value": 7,
635
- },
636
- ]
637
- `)
531
+ expect(events.map(({ name, tags, value }) => `${name}=${value} ${JSON.stringify(tags)}`))
532
+ .toMatchInlineSnapshot(`
533
+ [
534
+ "webhook_scan_block_count=1 {"chain_id":"42431","origin":"socket","outcome":"dispatched"}",
535
+ "webhook_scan_match_count=1 {"chain_id":"42431","origin":"socket","outcome":"dispatched"}",
536
+ "webhook_scan_work_ms=400 {"chain_id":"42431","origin":"socket","outcome":"dispatched"}",
537
+ "webhook_scan_queue_wait_ms=900 {"chain_id":"42431","origin":"socket","outcome":"dispatched","deferred":"false"}",
538
+ "webhook_scan_observation_lag_ms=600 {"chain_id":"42431","origin":"socket"}",
539
+ "webhook_scan_lag_ms=1500 {"chain_id":"42431"}",
540
+ "webhook_scan_block_count=1 {"chain_id":"42431","origin":"replay","outcome":"empty"}",
541
+ "webhook_scan_work_ms=250 {"chain_id":"42431","origin":"replay","outcome":"empty"}",
542
+ "webhook_scan_lag_ms=30000000 {"chain_id":"42431"}",
543
+ "webhook_scan_step_duration_ms=180 {"chain_id":"42431","step":"finalized_rpc"}",
544
+ "webhook_scan_step_count=2 {"chain_id":"42431","step":"finalized_rpc"}",
545
+ "webhook_scan_step_duration_ms=95 {"chain_id":"42431","step":"subscription_read"}",
546
+ "webhook_scan_step_count=1 {"chain_id":"42431","step":"subscription_read"}",
547
+ ]
548
+ `)
638
549
  })
639
550
 
640
551
  test('records obligation staging and claim outcomes', () => {
@@ -692,200 +603,6 @@ describe('webhooks', () => {
692
603
  `)
693
604
  })
694
605
 
695
- test('records scan boundaries with bounded workload dimensions', () => {
696
- const events: MetricEvent[] = []
697
- const metrics = Metrics.from({
698
- count(name, value, tags) {
699
- events.push({ name, tags, type: 'count', value })
700
- },
701
- flush() {},
702
- gauge() {},
703
- histogram(name, value, tags) {
704
- events.push({ name, tags, type: 'histogram', value })
705
- },
706
- })
707
- const sink = MetricSink.webhooks(metrics)
708
-
709
- sink.record({
710
- chainId: 4217,
711
- concurrency: 8,
712
- eventType: 'log:emitted',
713
- maxWindowBlocks: 1_000,
714
- phase: 'start',
715
- rpcMethod: 'eth_getLogs',
716
- subscriptionCount: 8,
717
- subscriptionLimit: 256,
718
- totalWindowBlocks: 8_000,
719
- type: 'webhook:poll-scan',
720
- })
721
- sink.record({
722
- chainId: 4217,
723
- concurrency: 8,
724
- durationMs: 250,
725
- eventType: 'log:emitted',
726
- failedSubscriptions: 1,
727
- matchedItems: 700,
728
- maxWindowBlocks: 1_000,
729
- outcome: 'error',
730
- phase: 'complete',
731
- rpcMethod: 'eth_getLogs',
732
- subscriptionCount: 8,
733
- subscriptionLimit: 256,
734
- totalWindowBlocks: 8_000,
735
- type: 'webhook:poll-scan',
736
- })
737
-
738
- expect(events.map(({ name, tags, value }) => ({ name, tags, value }))).toMatchInlineSnapshot(`
739
- [
740
- {
741
- "name": "webhook_poller_scan_checkpoint_count",
742
- "tags": {
743
- "chain_id": "4217",
744
- "event_type": "log:emitted",
745
- "phase": "start",
746
- },
747
- "value": 1,
748
- },
749
- {
750
- "name": "webhook_poller_scan_concurrency",
751
- "tags": {
752
- "chain_id": "4217",
753
- "event_type": "log:emitted",
754
- "phase": "start",
755
- },
756
- "value": 8,
757
- },
758
- {
759
- "name": "webhook_poller_scan_subscription_count",
760
- "tags": {
761
- "chain_id": "4217",
762
- "event_type": "log:emitted",
763
- "phase": "start",
764
- },
765
- "value": 8,
766
- },
767
- {
768
- "name": "webhook_poller_scan_subscription_limit",
769
- "tags": {
770
- "chain_id": "4217",
771
- "event_type": "log:emitted",
772
- "phase": "start",
773
- },
774
- "value": 256,
775
- },
776
- {
777
- "name": "webhook_poller_scan_max_window_blocks",
778
- "tags": {
779
- "chain_id": "4217",
780
- "event_type": "log:emitted",
781
- "phase": "start",
782
- },
783
- "value": 1000,
784
- },
785
- {
786
- "name": "webhook_poller_scan_total_window_blocks",
787
- "tags": {
788
- "chain_id": "4217",
789
- "event_type": "log:emitted",
790
- "phase": "start",
791
- },
792
- "value": 8000,
793
- },
794
- {
795
- "name": "webhook_poller_scan_checkpoint_count",
796
- "tags": {
797
- "chain_id": "4217",
798
- "event_type": "log:emitted",
799
- "phase": "complete",
800
- },
801
- "value": 1,
802
- },
803
- {
804
- "name": "webhook_poller_scan_concurrency",
805
- "tags": {
806
- "chain_id": "4217",
807
- "event_type": "log:emitted",
808
- "phase": "complete",
809
- },
810
- "value": 8,
811
- },
812
- {
813
- "name": "webhook_poller_scan_subscription_count",
814
- "tags": {
815
- "chain_id": "4217",
816
- "event_type": "log:emitted",
817
- "phase": "complete",
818
- },
819
- "value": 8,
820
- },
821
- {
822
- "name": "webhook_poller_scan_subscription_limit",
823
- "tags": {
824
- "chain_id": "4217",
825
- "event_type": "log:emitted",
826
- "phase": "complete",
827
- },
828
- "value": 256,
829
- },
830
- {
831
- "name": "webhook_poller_scan_max_window_blocks",
832
- "tags": {
833
- "chain_id": "4217",
834
- "event_type": "log:emitted",
835
- "phase": "complete",
836
- },
837
- "value": 1000,
838
- },
839
- {
840
- "name": "webhook_poller_scan_total_window_blocks",
841
- "tags": {
842
- "chain_id": "4217",
843
- "event_type": "log:emitted",
844
- "phase": "complete",
845
- },
846
- "value": 8000,
847
- },
848
- {
849
- "name": "webhook_poller_scan_duration_ms",
850
- "tags": {
851
- "chain_id": "4217",
852
- "event_type": "log:emitted",
853
- "phase": "complete",
854
- },
855
- "value": 250,
856
- },
857
- {
858
- "name": "webhook_poller_scan_failed_subscription_count",
859
- "tags": {
860
- "chain_id": "4217",
861
- "event_type": "log:emitted",
862
- "phase": "complete",
863
- },
864
- "value": 1,
865
- },
866
- {
867
- "name": "webhook_poller_scan_matched_item_count",
868
- "tags": {
869
- "chain_id": "4217",
870
- "event_type": "log:emitted",
871
- "phase": "complete",
872
- },
873
- "value": 700,
874
- },
875
- {
876
- "name": "webhook_poller_rpc_scan_count",
877
- "tags": {
878
- "chain_id": "4217",
879
- "event_type": "log:emitted",
880
- "outcome": "error",
881
- "rpc_method": "eth_getLogs",
882
- },
883
- "value": 1,
884
- },
885
- ]
886
- `)
887
- })
888
-
889
606
  test('records delivery stages without subscription identity', () => {
890
607
  const events: MetricEvent[] = []
891
608
  const metrics = Metrics.from({