feat: phase 3 observability — IMS telemetry merge

Port IMS telemetry into Primora conventions:

- core.components/core.events (migration 00004 + sqlc) scoped to projects
- POST /api/v1/ingest — single or <=500-event batches via project prm_
  keys (X-Primora-Key alias), auto-registering components, permissive CORS
- Issues: fingerprint-grouped errors with count/severity/first+last seen
- Reads: events w/ filters + before-cursor, components, stats (1h/24h/7d/30d
  bucketed series + component health + totals + metric names), metric
  series (avg/p50/p95/max)
- GET .../telemetry/stream — project-scoped SSE through an in-process hub
  (buffered subs, drop-on-slow, 25s keepalives); ?api_key=/?token= auth
  for EventSource
- Telemetry page: health/issues/events/metrics/integrate tabs, live badge,
  390px-clean; demo stubs
- @primora/client SDK (createPrimoraClient: capture*/heartbeat/installAuto,
  queued keepalive flushes)
- MCP: primora_list_issues/list_events/telemetry_stats/metric_series
- IMS README deprecation note pointing here

Also fixes workspace pins to 0.3.0 — npm ci in the frontend image was
resolving the unpublished 0.2.0 spec from the registry.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-16 16:21:03 +02:00
1 parent 6507eee606
commit 8cfddf9184
60 files changed
+3694 -30

No files matched your search

@@ -0,0 +1,15 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ComponentHealth = {
id: string;
name: string;
kind: string;
last_seen_at?: string | null;
last_status?: string;
errors_24h: number;
events_24h: number;
meta?: Record<string, any>;
};
@@ -0,0 +1,5 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ComponentID = string;
@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type EventSeriesBucket = {
ts: string;
counts: Record<string, number>;
};
@@ -0,0 +1,39 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type IngestEvent = {
type: IngestEvent.type;
/**
* Auto-registered under the project on first use
*/
component?: string;
kind?: IngestEvent.kind;
severity?: string;
message?: string;
payload?: Record<string, any>;
/**
* Error grouping key; derived from message when omitted
*/
fingerprint?: string;
ts?: string;
};
export namespace IngestEvent {
export enum type {
ERROR = 'error',
METRIC = 'metric',
LOG = 'log',
HEARTBEAT = 'heartbeat',
EVENT = 'event',
}
export enum kind {
FRONTEND = 'frontend',
BACKEND = 'backend',
DATABASE = 'database',
ANDROID = 'android',
DESKTOP = 'desktop',
WEB = 'web',
OTHER = 'other',
}
}
@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { IngestEvent } from './IngestEvent';
export type IngestRequest = (IngestEvent | {
events: Array<IngestEvent>;
});
@@ -0,0 +1,8 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type IngestResponse = {
accepted: number;
};
@@ -0,0 +1,13 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type MetricPoint = {
ts: string;
avg?: number | null;
p50?: number | null;
p95?: number | null;
max?: number | null;
count: number;
};
@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { MetricPoint } from './MetricPoint';
export type MetricSeriesResponse = {
items: Array<MetricPoint>;
};
@@ -0,0 +1,15 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type TelemetryComponent = {
id: string;
name: string;
kind: string;
last_seen_at?: string | null;
last_status?: string;
errors_24h: number;
events_24h: number;
meta?: Record<string, any>;
};
@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { TelemetryComponent } from './TelemetryComponent';
export type TelemetryComponentListResponse = {
items: Array<TelemetryComponent>;
};
@@ -0,0 +1,17 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type TelemetryEvent = {
id: number;
project_id: string;
component_id?: string | null;
component_name?: string;
type: string;
severity: string;
message: string;
payload?: Record<string, any>;
fingerprint?: string;
ts: string;
};
@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { TelemetryEvent } from './TelemetryEvent';
export type TelemetryEventListResponse = {
items: Array<TelemetryEvent>;
};
@@ -0,0 +1,15 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type TelemetryIssue = {
fingerprint: string;
message: string;
component_id?: string | null;
component_name?: string;
count: number;
first_seen: string;
last_seen: string;
severity: string;
};
@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { TelemetryIssue } from './TelemetryIssue';
export type TelemetryIssueListResponse = {
items: Array<TelemetryIssue>;
};
@@ -0,0 +1,17 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ComponentHealth } from './ComponentHealth';
import type { EventSeriesBucket } from './EventSeriesBucket';
export type TelemetryStats = {
window: number;
bucket_sec: number;
series: Array<EventSeriesBucket>;
components: Array<ComponentHealth>;
errors: number;
events: number;
metrics: number;
metric_names: Array<string>;
};