@obsunified/collector 2.0.1 → 2.2.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 (126) hide show
  1. package/dist/auth.d.ts +1 -1
  2. package/dist/auth.js +1 -1
  3. package/dist/{chunk-UGWDOGGH.js → chunk-KMEZKGSO.js} +22 -10
  4. package/dist/chunk-KMEZKGSO.js.map +1 -0
  5. package/dist/{chunk-XKQTVIRE.js → chunk-ZSF5XP3B.js} +8 -2
  6. package/dist/chunk-ZSF5XP3B.js.map +1 -0
  7. package/dist/index.d.ts +630 -27
  8. package/dist/index.js +5819 -853
  9. package/dist/index.js.map +1 -1
  10. package/dist/{ingest-auth-HFVO1zIp.d.ts → ingest-auth-C5-8Mly5.d.ts} +9 -0
  11. package/dist/{openai-FJYOQ7WO.js → openai-GSJZGFGL.js} +2 -2
  12. package/package.json +5 -5
  13. package/src/__tests__/autosre-closed-loop.test.ts +1275 -0
  14. package/src/analyses/derive.ts +26 -25
  15. package/src/analyses/investigations.ts +8 -8
  16. package/src/analyses/tier0.ts +22 -22
  17. package/src/auth/dashboard-auth.test.ts +791 -0
  18. package/src/auth/dashboard-auth.ts +187 -38
  19. package/src/auth/login-rate-limit.test.ts +129 -0
  20. package/src/auth/login-rate-limit.ts +143 -0
  21. package/src/framework/body-limit.test.ts +165 -0
  22. package/src/framework/collector.ts +355 -115
  23. package/src/framework/env.ts +9 -0
  24. package/src/framework/limits.ts +31 -0
  25. package/src/framework/scheduled-handlers.test.ts +342 -0
  26. package/src/index.ts +59 -1
  27. package/src/lib/action-retention.test.ts +472 -0
  28. package/src/lib/action-retention.ts +420 -0
  29. package/src/lib/actions-store.ts +6 -12
  30. package/src/lib/ai-store/calls.ts +2 -2
  31. package/src/lib/ai-store/sessions.ts +3 -3
  32. package/src/lib/ai-store/spans.ts +1 -1
  33. package/src/lib/ai-store/types.ts +2 -1
  34. package/src/lib/alerts-store.test.ts +153 -0
  35. package/src/lib/alerts-store.ts +122 -21
  36. package/src/lib/analyses-runner.test.ts +75 -0
  37. package/src/lib/analyses-runner.ts +69 -3
  38. package/src/lib/analyses-store-latest.test.ts +61 -0
  39. package/src/lib/analyses-store.ts +17 -10
  40. package/src/lib/autosre-change-correlation.ts +780 -0
  41. package/src/lib/autosre-findings.ts +725 -0
  42. package/src/lib/autosre-ontology.ts +914 -0
  43. package/src/lib/autosre-remediation-gate.ts +1663 -0
  44. package/src/lib/blob-store.ts +1 -1
  45. package/src/lib/eval-cases-store.ts +63 -1
  46. package/src/lib/evidence-references.ts +6 -0
  47. package/src/lib/housekeeping.test.ts +153 -0
  48. package/src/lib/housekeeping.ts +193 -0
  49. package/src/lib/identity-index/action-lookups.ts +6 -6
  50. package/src/lib/identity-index/key-lookups.ts +2 -2
  51. package/src/lib/identity-index/user-lookup.ts +8 -5
  52. package/src/lib/identity-index.test.ts +2 -3
  53. package/src/lib/metrics-store-series.test.ts +99 -0
  54. package/src/lib/metrics-store.test.ts +23 -0
  55. package/src/lib/metrics-store.ts +27 -3
  56. package/src/lib/projects-store.test.ts +25 -0
  57. package/src/lib/projects-store.ts +4 -3
  58. package/src/lib/sql-db-dialect.test.ts +12 -3
  59. package/src/lib/sql-db-postgres.integration.test.ts +1225 -0
  60. package/src/lib/sql-db-postgres.test.ts +314 -0
  61. package/src/lib/sql-db-postgres.ts +392 -54
  62. package/src/lib/sql-db-sqlite-time-windows.test.ts +194 -0
  63. package/src/lib/sql-db.ts +40 -6
  64. package/src/lib/store/ingest.ts +94 -6
  65. package/src/lib/test-utils/action-graph-fixtures.ts +307 -0
  66. package/src/lib/test-utils/node-sqlite-db.ts +188 -0
  67. package/src/lib/webhook-url.test.ts +122 -0
  68. package/src/lib/webhook-url.ts +155 -0
  69. package/src/migrations/041_ingest_key_hash_text.sql +5 -0
  70. package/src/migrations/042_session_replay_project_composite_pk.sql +52 -0
  71. package/src/migrations/043_metric_exemplars_text_timestamps.sql +5 -0
  72. package/src/migrations/044_actions_started_at_index.sql +10 -0
  73. package/src/migrations/045_user_profiles_project_composite_pk.sql +40 -0
  74. package/src/migrations/046_action_graph_project_composite_pk.sql +255 -0
  75. package/src/migrations/047_ai_span_payloads_project_composite_pk.sql +61 -0
  76. package/src/migrations/048_telemetry_spans_project_composite_pk.sql +92 -0
  77. package/src/migrations/049_alert_state_notified.sql +17 -0
  78. package/src/migrations/050_autosre_causal_ontology.sql +216 -0
  79. package/src/migrations/alert-state-notified.test.ts +111 -0
  80. package/src/migrations/parity.test.ts +68 -0
  81. package/src/migrations/project-scoped-keys.test.ts +432 -0
  82. package/src/migrations-postgres/009_projects_and_keys.sql +4 -4
  83. package/src/migrations-postgres/026_obs_dashboard_project_seed.sql +1 -1
  84. package/src/migrations-postgres/035_metric_exemplars.sql +5 -2
  85. package/src/migrations-postgres/041_ingest_key_hash_text.sql +32 -0
  86. package/src/migrations-postgres/042_session_replay_project_composite_pk.sql +16 -0
  87. package/src/migrations-postgres/043_metric_exemplars_text_timestamps.sql +47 -0
  88. package/src/migrations-postgres/044_actions_started_at_index.sql +8 -0
  89. package/src/migrations-postgres/045_user_profiles_project_composite_pk.sql +33 -0
  90. package/src/migrations-postgres/046_action_graph_project_composite_pk.sql +39 -0
  91. package/src/migrations-postgres/047_ai_span_payloads_project_composite_pk.sql +30 -0
  92. package/src/migrations-postgres/048_telemetry_spans_project_composite_pk.sql +37 -0
  93. package/src/migrations-postgres/049_alert_state_notified.sql +10 -0
  94. package/src/migrations-postgres/050_autosre_causal_ontology.sql +216 -0
  95. package/src/migrations-postgres/README.md +58 -5
  96. package/src/otlp/decode/body.test.ts +76 -0
  97. package/src/otlp/decode/body.ts +67 -6
  98. package/src/plugins/_json.ts +21 -0
  99. package/src/plugins/action-graph-processor.ts +6 -12
  100. package/src/plugins/ai-receiver.ts +5 -12
  101. package/src/plugins/ai-span-payloads-processor.ts +1 -2
  102. package/src/plugins/alerts-evaluator-retention.test.ts +170 -0
  103. package/src/plugins/alerts-evaluator.test.ts +464 -3
  104. package/src/plugins/alerts-evaluator.ts +181 -41
  105. package/src/plugins/alerts-routes.ts +10 -6
  106. package/src/plugins/ask-routes.ts +4 -5
  107. package/src/plugins/autosre-routes.ts +1019 -0
  108. package/src/plugins/connected-routes/manifest.ts +13 -1
  109. package/src/plugins/connected-routes.test.ts +106 -0
  110. package/src/plugins/connected-routes.ts +285 -5
  111. package/src/plugins/dashboard-routes.test.ts +120 -0
  112. package/src/plugins/dashboard-routes.ts +11 -2
  113. package/src/plugins/default-span-enrichment.ts +13 -1
  114. package/src/plugins/eval-cases-routes.ts +65 -19
  115. package/src/plugins/evidence-retrieval-routes.ts +300 -4
  116. package/src/plugins/identity-receiver.ts +9 -11
  117. package/src/plugins/json-body.test.ts +91 -0
  118. package/src/plugins/onboarding-routes.test.ts +105 -0
  119. package/src/plugins/onboarding-routes.ts +26 -11
  120. package/src/plugins/projects-routes.ts +2 -1
  121. package/src/plugins/replay-isolation.test.ts +922 -0
  122. package/src/plugins/replay-receiver.ts +8 -8
  123. package/src/plugins/usage-receiver.ts +3 -6
  124. package/dist/chunk-UGWDOGGH.js.map +0 -1
  125. package/dist/chunk-XKQTVIRE.js.map +0 -1
  126. /package/dist/{openai-FJYOQ7WO.js.map → openai-GSJZGFGL.js.map} +0 -0
@@ -0,0 +1,105 @@
1
+ import type { StoredSpan } from "@obsunified/types";
2
+ import type { MiddlewareHandler } from "hono";
3
+ import { describe, expect, it } from "vitest";
4
+ import { createTelemetryCollectorApp } from "../framework/collector";
5
+ import type { CollectorEnv } from "../framework/env";
6
+ import { ingestTelemetrySpans } from "../lib/store/ingest";
7
+ import { createMigratedSqliteDb } from "../lib/test-utils/node-sqlite-db";
8
+ import { onboardingRoutesPlugin } from "./onboarding-routes";
9
+
10
+ const NOW = new Date().toISOString();
11
+ const LATER = new Date(Date.now() + 3_600_000).toISOString();
12
+
13
+ const span = (
14
+ projectId: string,
15
+ spanId: string,
16
+ interactionId: string | null,
17
+ ) =>
18
+ ({
19
+ projectId,
20
+ traceId: `trace-${projectId}`,
21
+ spanId,
22
+ parentSpanId: null,
23
+ traceState: null,
24
+ serviceName: "web",
25
+ scopeName: null,
26
+ scopeVersion: null,
27
+ spanName: "GET /",
28
+ spanKind: 2,
29
+ statusCode: 1,
30
+ statusMessage: null,
31
+ startTime: NOW,
32
+ endTime: NOW,
33
+ durationMs: 1,
34
+ attributesJson: "{}",
35
+ droppedAttributesCount: 0,
36
+ resourceAttributesJson: "{}",
37
+ eventsJson: "[]",
38
+ droppedEventsCount: 0,
39
+ linksJson: "[]",
40
+ droppedLinksCount: 0,
41
+ receivedAt: NOW,
42
+ expiresAt: LATER,
43
+ sessionId: null,
44
+ interactionId,
45
+ }) satisfies StoredSpan;
46
+
47
+ describe("/internal/onboarding/counts", () => {
48
+ it("counts only the caller's project", async () => {
49
+ const db = createMigratedSqliteDb();
50
+ await ingestTelemetrySpans(db, [
51
+ span("alpha", "a1", "click-1"),
52
+ span("alpha", "a2", null),
53
+ span("beta", "b1", null),
54
+ ]);
55
+ db.raw
56
+ .prepare(
57
+ `INSERT INTO user_profiles (project_id, user_id, visitor_id, first_seen_at, last_seen_at)
58
+ VALUES ('beta', 'u1', 'v1', ?, ?)`,
59
+ )
60
+ .run(NOW, NOW);
61
+
62
+ const setProject: MiddlewareHandler<{
63
+ Bindings: CollectorEnv;
64
+ Variables: { projectId: string };
65
+ }> = async (c, next) => {
66
+ const header = c.req.header("x-project-id");
67
+ if (header) c.set("projectId", header);
68
+ await next();
69
+ };
70
+ const app = createTelemetryCollectorApp({
71
+ plugins: [onboardingRoutesPlugin],
72
+ dashboardAuth: { middleware: setProject, registerRoutes: () => {} },
73
+ });
74
+ const counts = async (projectId: string) => {
75
+ const res = await app.request(
76
+ "/internal/onboarding/counts",
77
+ { headers: { "x-project-id": projectId } },
78
+ { DB: db as unknown as D1Database } as CollectorEnv,
79
+ );
80
+ expect(res.status).toBe(200);
81
+ return res.json();
82
+ };
83
+
84
+ expect(await counts("alpha")).toMatchObject({
85
+ spans: 2,
86
+ spansWithInteraction: 1,
87
+ identifiedUsers: 0,
88
+ });
89
+ expect(await counts("beta")).toMatchObject({
90
+ spans: 1,
91
+ spansWithInteraction: 0,
92
+ identifiedUsers: 1,
93
+ });
94
+ expect(await counts("gamma")).toMatchObject({
95
+ spans: 0,
96
+ logs: 0,
97
+ usageEvents: 0,
98
+ identifiedUsers: 0,
99
+ aiCalls: 0,
100
+ replayChunks: 0,
101
+ spansWithInteraction: 0,
102
+ });
103
+ db.close();
104
+ });
105
+ });
@@ -1,5 +1,6 @@
1
1
  import type { CollectorPlugin } from "../framework/collector";
2
2
  import { sqlDbFor } from "../lib/sql-db";
3
+ import { getProjectId } from "./_context";
3
4
 
4
5
  /**
5
6
  * Drives the in-dashboard Onboarding tab — returns counts of signal
@@ -14,17 +15,23 @@ export const onboardingRoutesPlugin: CollectorPlugin = {
14
15
  register(app) {
15
16
  app.get("/internal/onboarding/counts", async (c) => {
16
17
  const db = sqlDbFor(c.env);
17
- const counts = await Promise.all([
18
- safeCount(db, "SELECT COUNT(*) AS n FROM telemetry_spans"),
19
- safeCount(db, "SELECT COUNT(*) AS n FROM logs"),
20
- safeCount(db, "SELECT COUNT(*) AS n FROM usage_events"),
21
- safeCount(db, "SELECT COUNT(*) AS n FROM user_profiles"),
22
- safeCount(db, "SELECT COUNT(*) AS n FROM ai_calls"),
23
- safeCount(db, "SELECT COUNT(*) AS n FROM session_replay_metadata"),
18
+ // Every signal table is project-scoped; counting across tenants
19
+ // would leak other projects' volumes and hide an empty project.
20
+ const projectId = getProjectId(c);
21
+ const count = (table: string, extra = "") =>
24
22
  safeCount(
25
23
  db,
26
- "SELECT COUNT(*) AS n FROM telemetry_spans WHERE interaction_id IS NOT NULL",
27
- ),
24
+ `SELECT COUNT(*) AS n FROM ${table} WHERE project_id = ?${extra}`,
25
+ projectId,
26
+ );
27
+ const counts = await Promise.all([
28
+ count("telemetry_spans"),
29
+ count("logs"),
30
+ count("usage_events"),
31
+ count("user_profiles"),
32
+ count("ai_calls"),
33
+ count("session_replay_metadata"),
34
+ count("telemetry_spans", " AND interaction_id IS NOT NULL"),
28
35
  ]);
29
36
 
30
37
  return c.json({
@@ -41,11 +48,19 @@ export const onboardingRoutesPlugin: CollectorPlugin = {
41
48
  };
42
49
 
43
50
  async function safeCount(
44
- db: { prepare: (sql: string) => { first: <T>() => Promise<T | null> } },
51
+ db: {
52
+ prepare: (sql: string) => {
53
+ bind: (...args: unknown[]) => { first: <T>() => Promise<T | null> };
54
+ };
55
+ },
45
56
  sql: string,
57
+ ...binds: unknown[]
46
58
  ): Promise<number> {
47
59
  try {
48
- const row = await db.prepare(sql).first<{ n: number }>();
60
+ const row = await db
61
+ .prepare(sql)
62
+ .bind(...binds)
63
+ .first<{ n: number }>();
49
64
  return Number(row?.n ?? 0);
50
65
  } catch {
51
66
  // Table doesn't exist yet — fresh DB, no signals of this type.
@@ -81,9 +81,10 @@ export const projectsRoutesPlugin: CollectorPlugin = {
81
81
  });
82
82
 
83
83
  app.delete("/internal/projects/:id/keys/:keyId", async (c) => {
84
+ const projectId = c.req.param("id");
84
85
  const keyId = c.req.param("keyId");
85
86
  const store = new ProjectsStore(sqlDbFor(c.env));
86
- const revoked = await store.revokeKey(keyId);
87
+ const revoked = await store.revokeKey(keyId, projectId);
87
88
  if (!revoked)
88
89
  return c.json({ error: "Key not found or already revoked" }, 404);
89
90
  return c.json({ success: true });