@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.
- package/dist/src/archive/range-digest.d.ts +53 -0
- package/dist/src/archive/semantic-digest-builder.d.ts +53 -0
- package/dist/src/coverage/index.d.ts +53 -0
- package/dist/src/db/index.d.ts +66 -2
- package/dist/src/db/index.js +5 -1
- package/dist/src/db/index.js.map +3 -3
- package/dist/src/db/queries/chain-reorgs.d.ts +53 -0
- package/dist/src/db/queries/chain-reorgs.js +5 -1
- package/dist/src/db/queries/chain-reorgs.js.map +3 -3
- package/dist/src/db/queries/contracts.d.ts +53 -0
- package/dist/src/db/queries/instance.d.ts +53 -0
- package/dist/src/db/queries/integrity.d.ts +53 -0
- package/dist/src/db/queries/subgraph-gaps.d.ts +53 -0
- package/dist/src/db/queries/subgraph-operations.d.ts +53 -0
- package/dist/src/db/queries/subgraphs.d.ts +53 -0
- package/dist/src/db/queries/sync-scope.d.ts +53 -0
- package/dist/src/db/queries/webhook-replays.d.ts +1431 -0
- package/dist/src/db/queries/webhook-replays.js +156 -0
- package/dist/src/db/queries/webhook-replays.js.map +10 -0
- package/dist/src/db/queries/webhooks.d.ts +53 -0
- package/dist/src/db/schema.d.ts +62 -2
- package/dist/src/email/index.d.ts +27 -6
- package/dist/src/email/index.js +135 -29
- package/dist/src/email/index.js.map +3 -3
- package/dist/src/index.d.ts +66 -2
- package/dist/src/index.js +9 -1
- package/dist/src/index.js.map +4 -4
- package/dist/src/insights/index.d.ts +70 -3
- package/dist/src/insights/index.js +40 -3
- package/dist/src/insights/index.js.map +3 -3
- package/dist/src/node/local-client.d.ts +53 -0
- package/dist/src/queue/listener.js +5 -1
- package/dist/src/queue/listener.js.map +3 -3
- package/dist/src/schemas/index.js +5 -1
- package/dist/src/schemas/index.js.map +3 -3
- package/dist/src/schemas/webhooks.d.ts +45 -1
- package/dist/src/schemas/webhooks.js +6 -1
- package/dist/src/schemas/webhooks.js.map +3 -3
- package/migrations/0178_insight_snooze.ts +22 -0
- package/migrations/0179_webhook_replays.ts +56 -0
- package/migrations/0180_push.ts +54 -0
- package/migrations/0181_push_sent.ts +30 -0
- 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<
|
|
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
|
-
|
|
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=
|
|
2347
|
+
//# debugId=43EF1744AA425D2F64756E2164756E21
|
|
2311
2348
|
//# sourceMappingURL=index.js.map
|