@secondlayer/shared 13.12.0 → 13.13.1

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 (43) hide show
  1. package/dist/src/archive/range-digest.d.ts +53 -0
  2. package/dist/src/archive/semantic-digest-builder.d.ts +53 -0
  3. package/dist/src/coverage/index.d.ts +53 -0
  4. package/dist/src/db/index.d.ts +66 -2
  5. package/dist/src/db/index.js +5 -1
  6. package/dist/src/db/index.js.map +3 -3
  7. package/dist/src/db/queries/chain-reorgs.d.ts +53 -0
  8. package/dist/src/db/queries/chain-reorgs.js +5 -1
  9. package/dist/src/db/queries/chain-reorgs.js.map +3 -3
  10. package/dist/src/db/queries/contracts.d.ts +53 -0
  11. package/dist/src/db/queries/instance.d.ts +53 -0
  12. package/dist/src/db/queries/integrity.d.ts +53 -0
  13. package/dist/src/db/queries/subgraph-gaps.d.ts +53 -0
  14. package/dist/src/db/queries/subgraph-operations.d.ts +53 -0
  15. package/dist/src/db/queries/subgraphs.d.ts +53 -0
  16. package/dist/src/db/queries/sync-scope.d.ts +53 -0
  17. package/dist/src/db/queries/webhook-replays.d.ts +1431 -0
  18. package/dist/src/db/queries/webhook-replays.js +156 -0
  19. package/dist/src/db/queries/webhook-replays.js.map +10 -0
  20. package/dist/src/db/queries/webhooks.d.ts +53 -0
  21. package/dist/src/db/schema.d.ts +62 -2
  22. package/dist/src/email/index.d.ts +27 -6
  23. package/dist/src/email/index.js +135 -29
  24. package/dist/src/email/index.js.map +3 -3
  25. package/dist/src/index.d.ts +66 -2
  26. package/dist/src/index.js +9 -1
  27. package/dist/src/index.js.map +4 -4
  28. package/dist/src/insights/index.d.ts +70 -3
  29. package/dist/src/insights/index.js +40 -3
  30. package/dist/src/insights/index.js.map +3 -3
  31. package/dist/src/node/local-client.d.ts +53 -0
  32. package/dist/src/queue/listener.js +5 -1
  33. package/dist/src/queue/listener.js.map +3 -3
  34. package/dist/src/schemas/index.js +5 -1
  35. package/dist/src/schemas/index.js.map +3 -3
  36. package/dist/src/schemas/webhooks.d.ts +45 -1
  37. package/dist/src/schemas/webhooks.js +6 -1
  38. package/dist/src/schemas/webhooks.js.map +3 -3
  39. package/migrations/0178_insight_snooze.ts +22 -0
  40. package/migrations/0179_webhook_replays.ts +56 -0
  41. package/migrations/0180_push.ts +54 -0
  42. package/migrations/0181_push_sent.ts +30 -0
  43. package/package.json +5 -1
@@ -597,6 +597,7 @@ interface InsightsTable {
597
597
  clear_streak: Generated<number>;
598
598
  resolved_at: Date | null;
599
599
  dismissed_at: Date | null;
600
+ snoozed_until: Date | null;
600
601
  }
601
602
  /** "Expected": mutes one rule for one resource until removed. */
602
603
  interface InsightMutesTable {
@@ -1090,6 +1091,7 @@ interface Database {
1090
1091
  webhooks: WebhooksTable;
1091
1092
  webhook_outbox: WebhookOutboxTable;
1092
1093
  webhook_deliveries: WebhookDeliveriesTable;
1094
+ webhook_replays: WebhookReplaysTable;
1093
1095
  trigger_evaluator_state: TriggerEvaluatorStateTable;
1094
1096
  decoded_events: DecodedEventsTable;
1095
1097
  decoder_checkpoints: DecoderCheckpointsTable;
@@ -1138,6 +1140,9 @@ interface Database {
1138
1140
  insights: InsightsTable;
1139
1141
  insight_mutes: InsightMutesTable;
1140
1142
  feedback_tickets: FeedbackTicketsTable;
1143
+ push_subscriptions: PushSubscriptionsTable;
1144
+ push_prefs: PushPrefsTable;
1145
+ push_sent: PushSentTable;
1141
1146
  }
1142
1147
  interface ServiceHeartbeatsTable {
1143
1148
  name: string;
@@ -1230,6 +1235,36 @@ interface WebhookIncidentAlertsTable {
1230
1235
  email_attempts: Generated<number>;
1231
1236
  resolved_at: Date | null;
1232
1237
  }
1238
+ /** One browser/device push subscription; the endpoint is unique. */
1239
+ interface PushSubscriptionsTable {
1240
+ id: Generated<string>;
1241
+ account_id: string;
1242
+ endpoint: string;
1243
+ p256dh: string;
1244
+ auth: string;
1245
+ user_agent: string | null;
1246
+ created_at: Generated<Date>;
1247
+ last_success_at: Date | null;
1248
+ failed_count: Generated<number>;
1249
+ }
1250
+ /** Per-account push toggles. A missing row means the column defaults. */
1251
+ interface PushPrefsTable {
1252
+ account_id: string;
1253
+ webhook_failing: Generated<boolean>;
1254
+ subgraph_stopped: Generated<boolean>;
1255
+ subgraph_not_serving: Generated<boolean>;
1256
+ replay_done: Generated<boolean>;
1257
+ spend_cap: Generated<boolean>;
1258
+ key_repeat: Generated<boolean>;
1259
+ updated_at: Generated<Date>;
1260
+ }
1261
+ /** A push already sent, keyed by what it was about; debounces repeat signals. */
1262
+ interface PushSentTable {
1263
+ account_id: string;
1264
+ key: string;
1265
+ kind: string;
1266
+ sent_at: Generated<Date>;
1267
+ }
1233
1268
  type CliDeviceLoginStatus = "pending" | "approved" | "denied" | "consumed";
1234
1269
  /** The product that started a CLI sign-in; null is `sl login`. */
1235
1270
  type CliDeviceProduct = "sentinel";
@@ -1373,6 +1408,8 @@ interface WebhookOutboxTable {
1373
1408
  next_attempt_at: Generated<Date>;
1374
1409
  status: ColumnType<OutboxStatus, OutboxStatus | undefined, OutboxStatus>;
1375
1410
  is_replay: Generated<boolean>;
1411
+ /** The replay that created this row; null for live rows. */
1412
+ replay_id: string | null;
1376
1413
  /** The chain block_time of the matched event/tx. Null for rows written
1377
1414
  * before this column existed, and for the settlement scan path (not yet
1378
1415
  * wired). Lets `delivered_at - block_time` measure end-to-end webhook
@@ -1388,6 +1425,22 @@ interface WebhookOutboxTable {
1388
1425
  last_error: string | null;
1389
1426
  created_at: Generated<Date>;
1390
1427
  }
1428
+ type WebhookReplayStatus = "scanning" | "running" | "done" | "failed";
1429
+ interface WebhookReplaysTable {
1430
+ id: string;
1431
+ webhook_id: string;
1432
+ from_block: number | bigint;
1433
+ to_block: number | bigint;
1434
+ status: WebhookReplayStatus;
1435
+ scanned_count: Generated<number>;
1436
+ enqueued_count: Generated<number>;
1437
+ total: number | null;
1438
+ delivered: number | null;
1439
+ gave_up: number | null;
1440
+ error: string | null;
1441
+ started_at: Generated<Date>;
1442
+ finished_at: Date | null;
1443
+ }
1391
1444
  interface WebhookDeliveriesTable {
1392
1445
  id: Generated<string>;
1393
1446
  /** Nullable after migration 0077 — outbox row may be cleaned up while
@@ -1546,8 +1599,16 @@ interface InsightRule<Input> {
1546
1599
  * and resolves once the streak reaches `CLEAR_STREAK_TO_RESOLVE`. A rule that
1547
1600
  * was skipped (missing data) must stay out of `evaluatedKeys` so it never
1548
1601
  * counts toward clearing.
1602
+ *
1603
+ * Returns the insights this pass newly opened (inserted, not updated). A
1604
+ * re-fire after a resolve is a fresh insert and counts. Nothing stored or
1605
+ * listed depends on the return value.
1549
1606
  */
1550
- declare function recordEvaluation(db: Kysely<Database>, scope: InsightScope, fired: DetectedInsight[], evaluatedKeys: EvaluatedKey[], now?: Date): Promise<void>;
1607
+ declare function recordEvaluation(db: Kysely<Database>, scope: InsightScope, fired: DetectedInsight[], evaluatedKeys: EvaluatedKey[], now?: Date): Promise<{
1608
+ opened: Array<DetectedInsight & {
1609
+ id: string
1610
+ }>
1611
+ }>;
1551
1612
  /** (kind, id) pairs that have an open insight in this scope. */
1552
1613
  declare function listOpenResources(db: Kysely<Database>, scope: InsightScope): Promise<{
1553
1614
  kind: Insight["resource"]["kind"]
@@ -1556,7 +1617,7 @@ declare function listOpenResources(db: Kysely<Database>, scope: InsightScope): P
1556
1617
  }[]>;
1557
1618
  /**
1558
1619
  * Open rows plus rows resolved within `RECOVERED_VISIBLE_MS`, minus dismissed
1559
- * and muted ones. Resolved rows come back as `recovered`. One row per
1620
+ * snoozed and muted ones. Resolved rows come back as `recovered`. One row per
1560
1621
  * (resource, rule): the open incident, else the latest recovered one. Ranked.
1561
1622
  */
1562
1623
  declare function listInsights(db: Kysely<Database>, scope: InsightScope, now?: Date, filter?: {
@@ -1565,6 +1626,12 @@ declare function listInsights(db: Kysely<Database>, scope: InsightScope, now?: D
1565
1626
  }): Promise<Insight[]>;
1566
1627
  /** Hide the current incident. Returns false when the id is not in scope. */
1567
1628
  declare function dismissInsight(db: Kysely<Database>, scope: InsightScope, id: string, now?: Date): Promise<boolean>;
1629
+ /** Hide the current incident until `until`. Returns false when the id is not in scope. */
1630
+ declare function snoozeInsight(db: Kysely<Database>, scope: InsightScope, id: string, until: Date): Promise<boolean>;
1631
+ /** Show a snoozed insight again now. Returns false when the id is not in scope. */
1632
+ declare function unsnoozeInsight(db: Kysely<Database>, scope: InsightScope, id: string): Promise<boolean>;
1633
+ /** Show a dismissed insight again. Returns false when the id is not in scope. */
1634
+ declare function undismissInsight(db: Kysely<Database>, scope: InsightScope, id: string): Promise<boolean>;
1568
1635
  /** Mute the insight's rule for its resource until un-muted. */
1569
1636
  declare function muteRule(db: Kysely<Database>, scope: InsightScope, id: string): Promise<boolean>;
1570
1637
  declare function unmuteRule(db: Kysely<Database>, scope: InsightScope, id: string): Promise<boolean>;
@@ -1679,4 +1746,4 @@ interface TenantEvaluateDeps extends Omit<LabelDeps, "now"> {
1679
1746
  * exist resolve through the normal two-pass clear.
1680
1747
  */
1681
1748
  declare function evaluateTenantInsights(db: Kysely3<Database>, deps?: TenantEvaluateDeps): Promise<void>;
1682
- export { BACKFILL_DAY_SHARE, BACKFILL_FULL_PAGE_RATIO, BACKFILL_MIN_DAY_ROWS, CLEAR_STREAK_TO_RESOLVE, CREDITS_RUNWAY_WARN_DAYS, DEAD_LETTER_DELETE_AGE_DAYS, DEAD_LETTER_WARN_AGE_DAYS, DOWN_MIN_CONSECUTIVE, DUPLICATE_READS_LOOKBACK_MS, DUPLICATE_READS_MIN_PAGES, DUPLICATE_READS_WINDOW_MS, DetectedInsight, EvaluatedKey, HANDLER_ERRORS_MIN_24H, InsightRule, InsightScope, KEY_UNUSED_DAYS, LAG_MIN_SAMPLES, LAG_THRESHOLD_MS, LAG_WINDOW, LOGIN_WALL_MIN_CONSECUTIVE, LOGIN_WALL_MIN_PRIOR, LOGIN_WALL_PRIOR_JSON_RATIO, PAGE_CAP_ROWS, POLLING_FOLLOW_GAP_MS, POLLING_LOOKBACK_MS, POLLING_MAX_STDDEV_RATIO, POLLING_MIN_DURATION_MS, POLLING_MIN_EMPTY_RATIO, POLLING_MIN_REQUESTS, POLLING_STALE_INTERVALS, RATE_LIMITED_MIN_ATTEMPTS, RATE_LIMITED_MIN_RATIO, RECOVERED_VISIBLE_MS, REJECTS_MIN_CONSECUTIVE, REVOKED_KEY_ACTIVE_MS, SERVER_ERRORS_MIN_5XX, SLOWING_DAYS, SLOWING_MIN_R2, SLOWING_MIN_RISING_DAYS, SLOW_MIN_SAMPLES, SLOW_TIMEOUT_RATIO, SLOW_WINDOW, STACK_UNREACHABLE_ACTIVE_MS, STACK_UNREACHABLE_MIN_FAILURES, SUBGRAPH_LAG_BLOCKS, SUBGRAPH_LAG_LOSING_BLOCKS, SUBGRAPH_LAG_SNAPSHOTS, TEST_ENDPOINT_MIN_AGE_DAYS, TLS_WARN_DAYS, TenantEvaluateDeps, dismissInsight, evaluateTenantInsights, handoffPrompt, hostOf, listInsights, listOpenResources, maybeLabel, muteRule, plainSnippet, plural, purgeResolved, rankInsights, recordEvaluation, runRules, unmuteRule, utcDateTime, utcDay, utcTime };
1749
+ export { BACKFILL_DAY_SHARE, BACKFILL_FULL_PAGE_RATIO, BACKFILL_MIN_DAY_ROWS, CLEAR_STREAK_TO_RESOLVE, CREDITS_RUNWAY_WARN_DAYS, DEAD_LETTER_DELETE_AGE_DAYS, DEAD_LETTER_WARN_AGE_DAYS, DOWN_MIN_CONSECUTIVE, DUPLICATE_READS_LOOKBACK_MS, DUPLICATE_READS_MIN_PAGES, DUPLICATE_READS_WINDOW_MS, DetectedInsight, EvaluatedKey, HANDLER_ERRORS_MIN_24H, InsightRule, InsightScope, KEY_UNUSED_DAYS, LAG_MIN_SAMPLES, LAG_THRESHOLD_MS, LAG_WINDOW, LOGIN_WALL_MIN_CONSECUTIVE, LOGIN_WALL_MIN_PRIOR, LOGIN_WALL_PRIOR_JSON_RATIO, PAGE_CAP_ROWS, POLLING_FOLLOW_GAP_MS, POLLING_LOOKBACK_MS, POLLING_MAX_STDDEV_RATIO, POLLING_MIN_DURATION_MS, POLLING_MIN_EMPTY_RATIO, POLLING_MIN_REQUESTS, POLLING_STALE_INTERVALS, RATE_LIMITED_MIN_ATTEMPTS, RATE_LIMITED_MIN_RATIO, RECOVERED_VISIBLE_MS, REJECTS_MIN_CONSECUTIVE, REVOKED_KEY_ACTIVE_MS, SERVER_ERRORS_MIN_5XX, SLOWING_DAYS, SLOWING_MIN_R2, SLOWING_MIN_RISING_DAYS, SLOW_MIN_SAMPLES, SLOW_TIMEOUT_RATIO, SLOW_WINDOW, STACK_UNREACHABLE_ACTIVE_MS, STACK_UNREACHABLE_MIN_FAILURES, SUBGRAPH_LAG_BLOCKS, SUBGRAPH_LAG_LOSING_BLOCKS, SUBGRAPH_LAG_SNAPSHOTS, TEST_ENDPOINT_MIN_AGE_DAYS, TLS_WARN_DAYS, TenantEvaluateDeps, dismissInsight, evaluateTenantInsights, handoffPrompt, hostOf, listInsights, listOpenResources, maybeLabel, muteRule, plainSnippet, plural, purgeResolved, rankInsights, recordEvaluation, runRules, snoozeInsight, undismissInsight, unmuteRule, unsnoozeInsight, utcDateTime, utcDay, utcTime };
@@ -234,9 +234,10 @@ function toPayload(insight) {
234
234
  };
235
235
  }
236
236
  async function recordEvaluation(db, scope, fired, evaluatedKeys, now = new Date) {
237
- await db.transaction().execute(async (trx) => {
237
+ return db.transaction().execute(async (trx) => {
238
+ const opened = [];
238
239
  for (const insight of fired) {
239
- await sql2`
240
+ const { rows } = await sql2`
240
241
  INSERT INTO insights
241
242
  (account_id, resource_kind, resource_id, rule, severity, impact,
242
243
  payload, first_seen_at, last_seen_at)
@@ -253,7 +254,11 @@ async function recordEvaluation(db, scope, fired, evaluatedKeys, now = new Date)
253
254
  impact = EXCLUDED.impact,
254
255
  last_seen_at = EXCLUDED.last_seen_at,
255
256
  clear_streak = 0
257
+ RETURNING id, (xmax = 0) AS inserted
256
258
  `.execute(trx);
259
+ const row = rows[0];
260
+ if (row?.inserted)
261
+ opened.push({ ...insight, id: row.id });
257
262
  }
258
263
  const firedKeys = new Set(fired.map((i) => `${i.resource.kind}\x00${i.resource.id}\x00${i.rule}`));
259
264
  for (const key of evaluatedKeys) {
@@ -274,6 +279,7 @@ async function recordEvaluation(db, scope, fired, evaluatedKeys, now = new Date)
274
279
  AND resolved_at IS NULL
275
280
  `.execute(trx);
276
281
  }
282
+ return { opened };
277
283
  });
278
284
  }
279
285
  async function listOpenResources(db, scope) {
@@ -301,6 +307,7 @@ async function listInsights(db, scope, now = new Date, filter = {}) {
301
307
  ORDER BY resource_kind, resource_id, rule, resolved_at DESC NULLS FIRST
302
308
  ) i
303
309
  WHERE i.dismissed_at IS NULL
310
+ AND (i.snoozed_until IS NULL OR i.snoozed_until <= ${now})
304
311
  AND NOT EXISTS (
305
312
  SELECT 1 FROM insight_mutes m
306
313
  WHERE ${scopeWhere(scope, sql2.ref("m.account_id"))}
@@ -331,6 +338,33 @@ async function dismissInsight(db, scope, id, now = new Date) {
331
338
  `.execute(db);
332
339
  return Number(result.numAffectedRows ?? 0) > 0;
333
340
  }
341
+ async function snoozeInsight(db, scope, id, until) {
342
+ if (!UUID_RE.test(id))
343
+ return false;
344
+ const result = await sql2`
345
+ UPDATE insights SET snoozed_until = ${until}
346
+ WHERE id = ${id}::uuid AND ${scopeWhere(scope)}
347
+ `.execute(db);
348
+ return Number(result.numAffectedRows ?? 0) > 0;
349
+ }
350
+ async function unsnoozeInsight(db, scope, id) {
351
+ if (!UUID_RE.test(id))
352
+ return false;
353
+ const result = await sql2`
354
+ UPDATE insights SET snoozed_until = NULL
355
+ WHERE id = ${id}::uuid AND ${scopeWhere(scope)}
356
+ `.execute(db);
357
+ return Number(result.numAffectedRows ?? 0) > 0;
358
+ }
359
+ async function undismissInsight(db, scope, id) {
360
+ if (!UUID_RE.test(id))
361
+ return false;
362
+ const result = await sql2`
363
+ UPDATE insights SET dismissed_at = NULL
364
+ WHERE id = ${id}::uuid AND ${scopeWhere(scope)}
365
+ `.execute(db);
366
+ return Number(result.numAffectedRows ?? 0) > 0;
367
+ }
334
368
  async function muteRule(db, scope, id) {
335
369
  if (!UUID_RE.test(id))
336
370
  return false;
@@ -2301,11 +2335,14 @@ export {
2301
2335
  rankInsights,
2302
2336
  recordEvaluation,
2303
2337
  runRules2 as runRules,
2338
+ snoozeInsight,
2339
+ undismissInsight,
2304
2340
  unmuteRule,
2341
+ unsnoozeInsight,
2305
2342
  utcDateTime,
2306
2343
  utcDay,
2307
2344
  utcTime
2308
2345
  };
2309
2346
 
2310
- //# debugId=44F809365B6CC2B464756E2164756E21
2347
+ //# debugId=43EF1744AA425D2F64756E2164756E21
2311
2348
  //# sourceMappingURL=index.js.map