@secondlayer/shared 13.11.0 → 13.13.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 (41) hide show
  1. package/dist/src/archive/range-digest.d.ts +125 -0
  2. package/dist/src/archive/semantic-digest-builder.d.ts +125 -0
  3. package/dist/src/coverage/index.d.ts +125 -0
  4. package/dist/src/db/index.d.ts +146 -2
  5. package/dist/src/db/index.js +11 -1
  6. package/dist/src/db/index.js.map +3 -3
  7. package/dist/src/db/queries/chain-reorgs.d.ts +125 -0
  8. package/dist/src/db/queries/chain-reorgs.js +11 -1
  9. package/dist/src/db/queries/chain-reorgs.js.map +3 -3
  10. package/dist/src/db/queries/contracts.d.ts +125 -0
  11. package/dist/src/db/queries/instance.d.ts +125 -0
  12. package/dist/src/db/queries/integrity.d.ts +125 -0
  13. package/dist/src/db/queries/subgraph-gaps.d.ts +125 -0
  14. package/dist/src/db/queries/subgraph-operations.d.ts +125 -0
  15. package/dist/src/db/queries/subgraphs.d.ts +125 -0
  16. package/dist/src/db/queries/sync-scope.d.ts +125 -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 +125 -0
  21. package/dist/src/db/schema.d.ts +136 -2
  22. package/dist/src/index.d.ts +146 -2
  23. package/dist/src/index.js +15 -1
  24. package/dist/src/index.js.map +4 -4
  25. package/dist/src/insights/index.d.ts +142 -3
  26. package/dist/src/insights/index.js +40 -3
  27. package/dist/src/insights/index.js.map +3 -3
  28. package/dist/src/node/local-client.d.ts +125 -0
  29. package/dist/src/queue/listener.js +11 -1
  30. package/dist/src/queue/listener.js.map +3 -3
  31. package/dist/src/schemas/index.js +5 -1
  32. package/dist/src/schemas/index.js.map +3 -3
  33. package/dist/src/schemas/webhooks.d.ts +45 -1
  34. package/dist/src/schemas/webhooks.js +6 -1
  35. package/dist/src/schemas/webhooks.js.map +3 -3
  36. package/migrations/0177_oauth_mcp.ts +122 -0
  37. package/migrations/0178_insight_snooze.ts +22 -0
  38. package/migrations/0179_webhook_replays.ts +56 -0
  39. package/migrations/0180_push.ts +54 -0
  40. package/migrations/0181_push_sent.ts +30 -0
  41. 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;
@@ -1123,6 +1125,12 @@ interface Database {
1123
1125
  account_balance_alerts: AccountBalanceAlertsTable;
1124
1126
  webhook_incident_alerts: WebhookIncidentAlertsTable;
1125
1127
  cli_device_logins: CliDeviceLoginsTable;
1128
+ oauth_clients: OauthClientsTable;
1129
+ oauth_requests: OauthRequestsTable;
1130
+ oauth_codes: OauthCodesTable;
1131
+ oauth_grants: OauthGrantsTable;
1132
+ oauth_tokens: OauthTokensTable;
1133
+ oauth_refresh_retired: OauthRefreshRetiredTable;
1126
1134
  sentinel_accounts: SentinelAccountsTable;
1127
1135
  waitlists: WaitlistsTable;
1128
1136
  waitlist_signups: WaitlistSignupsTable;
@@ -1132,6 +1140,9 @@ interface Database {
1132
1140
  insights: InsightsTable;
1133
1141
  insight_mutes: InsightMutesTable;
1134
1142
  feedback_tickets: FeedbackTicketsTable;
1143
+ push_subscriptions: PushSubscriptionsTable;
1144
+ push_prefs: PushPrefsTable;
1145
+ push_sent: PushSentTable;
1135
1146
  }
1136
1147
  interface ServiceHeartbeatsTable {
1137
1148
  name: string;
@@ -1224,6 +1235,36 @@ interface WebhookIncidentAlertsTable {
1224
1235
  email_attempts: Generated<number>;
1225
1236
  resolved_at: Date | null;
1226
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
+ }
1227
1268
  type CliDeviceLoginStatus = "pending" | "approved" | "denied" | "consumed";
1228
1269
  /** The product that started a CLI sign-in; null is `sl login`. */
1229
1270
  type CliDeviceProduct = "sentinel";
@@ -1244,6 +1285,72 @@ interface CliDeviceLoginsTable {
1244
1285
  approved_at: Date | null;
1245
1286
  consumed_at: Date | null;
1246
1287
  }
1288
+ /** An app that registered itself (RFC 7591). Public clients only: no secret. */
1289
+ interface OauthClientsTable {
1290
+ id: string;
1291
+ /** The app's own claim, validated at registration; shown quoted. */
1292
+ name: string;
1293
+ redirect_uris: string[];
1294
+ created_ip: string | null;
1295
+ created_at: Generated<Date>;
1296
+ /** Set when it first gets tokens; never-used clients are purged. */
1297
+ last_used_at: Date | null;
1298
+ }
1299
+ type OauthRequestStatus = "pending" | "approved" | "denied";
1300
+ /** One authorize request waiting for (or answered on) the consent page. */
1301
+ interface OauthRequestsTable {
1302
+ id: string;
1303
+ client_id: string;
1304
+ redirect_uri: string;
1305
+ state: string | null;
1306
+ code_challenge: string;
1307
+ resource: string;
1308
+ status: Generated<OauthRequestStatus>;
1309
+ account_id: string | null;
1310
+ created_at: Generated<Date>;
1311
+ expires_at: Date;
1312
+ answered_at: Date | null;
1313
+ }
1314
+ /** A one-time authorization code, bound to its client, redirect, PKCE
1315
+ * challenge, resource and account. */
1316
+ interface OauthCodesTable {
1317
+ code_hash: string;
1318
+ client_id: string;
1319
+ account_id: string;
1320
+ redirect_uri: string;
1321
+ code_challenge: string;
1322
+ resource: string;
1323
+ /** The grant its one use made, so a replay can revoke it. */
1324
+ grant_id: string | null;
1325
+ created_at: Generated<Date>;
1326
+ expires_at: Date;
1327
+ used_at: Date | null;
1328
+ }
1329
+ /** What a person allowed one app to do: use the hosted MCP as them. */
1330
+ interface OauthGrantsTable {
1331
+ id: Generated<string>;
1332
+ account_id: string;
1333
+ client_id: string;
1334
+ scope: Generated<string>;
1335
+ resource: string;
1336
+ created_at: Generated<Date>;
1337
+ last_used_at: Date | null;
1338
+ revoked_at: Date | null;
1339
+ }
1340
+ type OauthTokenKind = "access" | "refresh";
1341
+ interface OauthTokensTable {
1342
+ token_hash: string;
1343
+ grant_id: string;
1344
+ kind: OauthTokenKind;
1345
+ created_at: Generated<Date>;
1346
+ expires_at: Date;
1347
+ }
1348
+ /** A refresh token a rotation replaced: presented again, it revokes the grant. */
1349
+ interface OauthRefreshRetiredTable {
1350
+ token_hash: string;
1351
+ grant_id: string;
1352
+ retired_at: Generated<Date>;
1353
+ }
1247
1354
  /** Accounts Sentinel's service key may touch: created by Sentinel, or the
1248
1355
  * owner opted in. */
1249
1356
  interface SentinelAccountsTable {
@@ -1301,6 +1408,8 @@ interface WebhookOutboxTable {
1301
1408
  next_attempt_at: Generated<Date>;
1302
1409
  status: ColumnType<OutboxStatus, OutboxStatus | undefined, OutboxStatus>;
1303
1410
  is_replay: Generated<boolean>;
1411
+ /** The replay that created this row; null for live rows. */
1412
+ replay_id: string | null;
1304
1413
  /** The chain block_time of the matched event/tx. Null for rows written
1305
1414
  * before this column existed, and for the settlement scan path (not yet
1306
1415
  * wired). Lets `delivered_at - block_time` measure end-to-end webhook
@@ -1316,6 +1425,22 @@ interface WebhookOutboxTable {
1316
1425
  last_error: string | null;
1317
1426
  created_at: Generated<Date>;
1318
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
+ }
1319
1444
  interface WebhookDeliveriesTable {
1320
1445
  id: Generated<string>;
1321
1446
  /** Nullable after migration 0077 — outbox row may be cleaned up while
@@ -1474,8 +1599,16 @@ interface InsightRule<Input> {
1474
1599
  * and resolves once the streak reaches `CLEAR_STREAK_TO_RESOLVE`. A rule that
1475
1600
  * was skipped (missing data) must stay out of `evaluatedKeys` so it never
1476
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.
1477
1606
  */
1478
- 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
+ }>;
1479
1612
  /** (kind, id) pairs that have an open insight in this scope. */
1480
1613
  declare function listOpenResources(db: Kysely<Database>, scope: InsightScope): Promise<{
1481
1614
  kind: Insight["resource"]["kind"]
@@ -1484,7 +1617,7 @@ declare function listOpenResources(db: Kysely<Database>, scope: InsightScope): P
1484
1617
  }[]>;
1485
1618
  /**
1486
1619
  * Open rows plus rows resolved within `RECOVERED_VISIBLE_MS`, minus dismissed
1487
- * 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
1488
1621
  * (resource, rule): the open incident, else the latest recovered one. Ranked.
1489
1622
  */
1490
1623
  declare function listInsights(db: Kysely<Database>, scope: InsightScope, now?: Date, filter?: {
@@ -1493,6 +1626,12 @@ declare function listInsights(db: Kysely<Database>, scope: InsightScope, now?: D
1493
1626
  }): Promise<Insight[]>;
1494
1627
  /** Hide the current incident. Returns false when the id is not in scope. */
1495
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>;
1496
1635
  /** Mute the insight's rule for its resource until un-muted. */
1497
1636
  declare function muteRule(db: Kysely<Database>, scope: InsightScope, id: string): Promise<boolean>;
1498
1637
  declare function unmuteRule(db: Kysely<Database>, scope: InsightScope, id: string): Promise<boolean>;
@@ -1607,4 +1746,4 @@ interface TenantEvaluateDeps extends Omit<LabelDeps, "now"> {
1607
1746
  * exist resolve through the normal two-pass clear.
1608
1747
  */
1609
1748
  declare function evaluateTenantInsights(db: Kysely3<Database>, deps?: TenantEvaluateDeps): Promise<void>;
1610
- 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