@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.
- package/dist/auth.d.ts +1 -1
- package/dist/auth.js +1 -1
- package/dist/{chunk-UGWDOGGH.js → chunk-KMEZKGSO.js} +22 -10
- package/dist/chunk-KMEZKGSO.js.map +1 -0
- package/dist/{chunk-XKQTVIRE.js → chunk-ZSF5XP3B.js} +8 -2
- package/dist/chunk-ZSF5XP3B.js.map +1 -0
- package/dist/index.d.ts +630 -27
- package/dist/index.js +5819 -853
- package/dist/index.js.map +1 -1
- package/dist/{ingest-auth-HFVO1zIp.d.ts → ingest-auth-C5-8Mly5.d.ts} +9 -0
- package/dist/{openai-FJYOQ7WO.js → openai-GSJZGFGL.js} +2 -2
- package/package.json +5 -5
- package/src/__tests__/autosre-closed-loop.test.ts +1275 -0
- package/src/analyses/derive.ts +26 -25
- package/src/analyses/investigations.ts +8 -8
- package/src/analyses/tier0.ts +22 -22
- package/src/auth/dashboard-auth.test.ts +791 -0
- package/src/auth/dashboard-auth.ts +187 -38
- package/src/auth/login-rate-limit.test.ts +129 -0
- package/src/auth/login-rate-limit.ts +143 -0
- package/src/framework/body-limit.test.ts +165 -0
- package/src/framework/collector.ts +355 -115
- package/src/framework/env.ts +9 -0
- package/src/framework/limits.ts +31 -0
- package/src/framework/scheduled-handlers.test.ts +342 -0
- package/src/index.ts +59 -1
- package/src/lib/action-retention.test.ts +472 -0
- package/src/lib/action-retention.ts +420 -0
- package/src/lib/actions-store.ts +6 -12
- package/src/lib/ai-store/calls.ts +2 -2
- package/src/lib/ai-store/sessions.ts +3 -3
- package/src/lib/ai-store/spans.ts +1 -1
- package/src/lib/ai-store/types.ts +2 -1
- package/src/lib/alerts-store.test.ts +153 -0
- package/src/lib/alerts-store.ts +122 -21
- package/src/lib/analyses-runner.test.ts +75 -0
- package/src/lib/analyses-runner.ts +69 -3
- package/src/lib/analyses-store-latest.test.ts +61 -0
- package/src/lib/analyses-store.ts +17 -10
- package/src/lib/autosre-change-correlation.ts +780 -0
- package/src/lib/autosre-findings.ts +725 -0
- package/src/lib/autosre-ontology.ts +914 -0
- package/src/lib/autosre-remediation-gate.ts +1663 -0
- package/src/lib/blob-store.ts +1 -1
- package/src/lib/eval-cases-store.ts +63 -1
- package/src/lib/evidence-references.ts +6 -0
- package/src/lib/housekeeping.test.ts +153 -0
- package/src/lib/housekeeping.ts +193 -0
- package/src/lib/identity-index/action-lookups.ts +6 -6
- package/src/lib/identity-index/key-lookups.ts +2 -2
- package/src/lib/identity-index/user-lookup.ts +8 -5
- package/src/lib/identity-index.test.ts +2 -3
- package/src/lib/metrics-store-series.test.ts +99 -0
- package/src/lib/metrics-store.test.ts +23 -0
- package/src/lib/metrics-store.ts +27 -3
- package/src/lib/projects-store.test.ts +25 -0
- package/src/lib/projects-store.ts +4 -3
- package/src/lib/sql-db-dialect.test.ts +12 -3
- package/src/lib/sql-db-postgres.integration.test.ts +1225 -0
- package/src/lib/sql-db-postgres.test.ts +314 -0
- package/src/lib/sql-db-postgres.ts +392 -54
- package/src/lib/sql-db-sqlite-time-windows.test.ts +194 -0
- package/src/lib/sql-db.ts +40 -6
- package/src/lib/store/ingest.ts +94 -6
- package/src/lib/test-utils/action-graph-fixtures.ts +307 -0
- package/src/lib/test-utils/node-sqlite-db.ts +188 -0
- package/src/lib/webhook-url.test.ts +122 -0
- package/src/lib/webhook-url.ts +155 -0
- package/src/migrations/041_ingest_key_hash_text.sql +5 -0
- package/src/migrations/042_session_replay_project_composite_pk.sql +52 -0
- package/src/migrations/043_metric_exemplars_text_timestamps.sql +5 -0
- package/src/migrations/044_actions_started_at_index.sql +10 -0
- package/src/migrations/045_user_profiles_project_composite_pk.sql +40 -0
- package/src/migrations/046_action_graph_project_composite_pk.sql +255 -0
- package/src/migrations/047_ai_span_payloads_project_composite_pk.sql +61 -0
- package/src/migrations/048_telemetry_spans_project_composite_pk.sql +92 -0
- package/src/migrations/049_alert_state_notified.sql +17 -0
- package/src/migrations/050_autosre_causal_ontology.sql +216 -0
- package/src/migrations/alert-state-notified.test.ts +111 -0
- package/src/migrations/parity.test.ts +68 -0
- package/src/migrations/project-scoped-keys.test.ts +432 -0
- package/src/migrations-postgres/009_projects_and_keys.sql +4 -4
- package/src/migrations-postgres/026_obs_dashboard_project_seed.sql +1 -1
- package/src/migrations-postgres/035_metric_exemplars.sql +5 -2
- package/src/migrations-postgres/041_ingest_key_hash_text.sql +32 -0
- package/src/migrations-postgres/042_session_replay_project_composite_pk.sql +16 -0
- package/src/migrations-postgres/043_metric_exemplars_text_timestamps.sql +47 -0
- package/src/migrations-postgres/044_actions_started_at_index.sql +8 -0
- package/src/migrations-postgres/045_user_profiles_project_composite_pk.sql +33 -0
- package/src/migrations-postgres/046_action_graph_project_composite_pk.sql +39 -0
- package/src/migrations-postgres/047_ai_span_payloads_project_composite_pk.sql +30 -0
- package/src/migrations-postgres/048_telemetry_spans_project_composite_pk.sql +37 -0
- package/src/migrations-postgres/049_alert_state_notified.sql +10 -0
- package/src/migrations-postgres/050_autosre_causal_ontology.sql +216 -0
- package/src/migrations-postgres/README.md +58 -5
- package/src/otlp/decode/body.test.ts +76 -0
- package/src/otlp/decode/body.ts +67 -6
- package/src/plugins/_json.ts +21 -0
- package/src/plugins/action-graph-processor.ts +6 -12
- package/src/plugins/ai-receiver.ts +5 -12
- package/src/plugins/ai-span-payloads-processor.ts +1 -2
- package/src/plugins/alerts-evaluator-retention.test.ts +170 -0
- package/src/plugins/alerts-evaluator.test.ts +464 -3
- package/src/plugins/alerts-evaluator.ts +181 -41
- package/src/plugins/alerts-routes.ts +10 -6
- package/src/plugins/ask-routes.ts +4 -5
- package/src/plugins/autosre-routes.ts +1019 -0
- package/src/plugins/connected-routes/manifest.ts +13 -1
- package/src/plugins/connected-routes.test.ts +106 -0
- package/src/plugins/connected-routes.ts +285 -5
- package/src/plugins/dashboard-routes.test.ts +120 -0
- package/src/plugins/dashboard-routes.ts +11 -2
- package/src/plugins/default-span-enrichment.ts +13 -1
- package/src/plugins/eval-cases-routes.ts +65 -19
- package/src/plugins/evidence-retrieval-routes.ts +300 -4
- package/src/plugins/identity-receiver.ts +9 -11
- package/src/plugins/json-body.test.ts +91 -0
- package/src/plugins/onboarding-routes.test.ts +105 -0
- package/src/plugins/onboarding-routes.ts +26 -11
- package/src/plugins/projects-routes.ts +2 -1
- package/src/plugins/replay-isolation.test.ts +922 -0
- package/src/plugins/replay-receiver.ts +8 -8
- package/src/plugins/usage-receiver.ts +3 -6
- package/dist/chunk-UGWDOGGH.js.map +0 -1
- package/dist/chunk-XKQTVIRE.js.map +0 -1
- /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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
|
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 });
|