feat(diagnostics): add types.ts with session, trace, log, screenshot schemas

This commit is contained in:
saravanakumardb1 2026-03-02 23:32:27 -08:00
parent 4163e1410a
commit f51c352452

View File

@ -0,0 +1,381 @@
/**
* Diagnostics types remote debug session management.
*
* Cosmos containers:
* - debug_sessions (pk: /id, TTL: 7 days)
* - debug_traces (pk: /pk composite ${productId}:${sessionId}, TTL: 7 days)
* - debug_logs (pk: /pk composite ${productId}:${sessionId}, TTL: 3 days)
* - debug_screenshots (pk: /sessionId) metadata only, images in Azure Blob
*
* @module diagnostics
*/
import { z } from 'zod';
// ─────────────────────────────────────────────────────────────────────────────
// Enums
// ─────────────────────────────────────────────────────────────────────────────
export const SessionStatusEnum = z.enum(['pending', 'active', 'paused', 'completed', 'cancelled']);
export type SessionStatus = z.infer<typeof SessionStatusEnum>;
export const CollectionLevelEnum = z.enum(['standard', 'debug', 'trace']);
export type CollectionLevel = z.infer<typeof CollectionLevelEnum>;
export const LogLevelEnum = z.enum(['debug', 'info', 'warn', 'error', 'fatal']);
export type LogLevel = z.infer<typeof LogLevelEnum>;
export const ScreenshotTriggerEnum = z.enum(['manual', 'error', 'interval', 'user_request']);
export type ScreenshotTrigger = z.infer<typeof ScreenshotTriggerEnum>;
export const SpanStatusEnum = z.enum(['ok', 'error', 'unset']);
export type SpanStatus = z.infer<typeof SpanStatusEnum>;
export const SpanKindEnum = z.enum(['internal', 'server', 'client', 'producer', 'consumer']);
export type SpanKind = z.infer<typeof SpanKindEnum>;
// ─────────────────────────────────────────────────────────────────────────────
// DebugSessionDoc
// ─────────────────────────────────────────────────────────────────────────────
export interface DebugSessionDoc {
id: string;
productId: string;
// Target (at least one required)
targetUserId?: string;
targetAnonymousId?: string;
targetDeviceId?: string;
targetSessionId?: string;
// Status
status: SessionStatus;
// Collection config
collectionLevel: CollectionLevel;
captureLogs: boolean;
captureNetwork: boolean;
captureScreenshots: boolean;
screenshotOnError: boolean;
maxDurationMinutes: number;
// Timestamps
createdAt: string;
updatedAt: string;
startedAt?: string;
endedAt?: string;
expiresAt: string;
// Stats (denormalized)
logCount: number;
traceCount: number;
screenshotCount: number;
// Audit
createdBy: string;
updatedBy?: string;
// Consent tracking
userConsent?: {
consentedAt: string;
consentMethod: 'prompt' | 'pre_consent' | 'auto';
};
}
// ─────────────────────────────────────────────────────────────────────────────
// DebugTraceDoc (OpenTelemetry-compatible)
// ─────────────────────────────────────────────────────────────────────────────
export interface DebugTraceDoc {
id: string;
pk: string; // Composite: ${productId}:${sessionId}
sessionId: string;
productId: string;
// OTel context
traceId: string;
parentId?: string;
spanId: string;
name: string;
kind?: SpanKind;
// Timing
startTime: string;
endTime?: string;
durationMs?: number;
// Context
attributes: Record<string, unknown>;
status: SpanStatus;
statusMessage?: string;
// Events within span
events?: Array<{
name: string;
timestamp: string;
attributes?: Record<string, unknown>;
}>;
// Links to other traces
links?: Array<{
traceId: string;
spanId: string;
attributes?: Record<string, unknown>;
}>;
}
// ─────────────────────────────────────────────────────────────────────────────
// DebugLogEntryDoc
// ─────────────────────────────────────────────────────────────────────────────
export interface DebugLogEntryDoc {
id: string;
pk: string; // Composite: ${productId}:${sessionId}
sessionId: string;
productId: string;
level: LogLevel;
message: string;
messageHash?: string;
// Timestamps
timestamp: string;
receivedAt?: string;
// Source context
module: string;
file?: string;
line?: number;
function?: string;
// Thread/task context
threadId?: string;
correlationId?: string;
// Context (PII-scanned)
context: Record<string, unknown>;
// PII redaction metadata
redaction?: {
fieldsRedacted: string[];
patternsMatched: string[];
};
}
// ─────────────────────────────────────────────────────────────────────────────
// DebugScreenshotDoc (metadata only)
// ─────────────────────────────────────────────────────────────────────────────
export interface DebugScreenshotDoc {
id: string;
sessionId: string;
productId: string;
// Blob storage reference
blobUrl: string;
blobPath: string;
containerName: string;
// Metadata
capturedAt: string;
trigger: ScreenshotTrigger;
// Dimensions
width: number;
height: number;
format: 'png' | 'jpeg' | 'webp';
sizeBytes: number;
// Privacy
sensitiveViewsBlurred: boolean;
blurRegions?: Array<{ x: number; y: number; w: number; h: number }>;
// Context
screenName?: string;
breadcrumbAtCapture?: string;
}
// ─────────────────────────────────────────────────────────────────────────────
// Input Schemas
// ─────────────────────────────────────────────────────────────────────────────
export const CreateDebugSessionSchema = z.object({
productId: z.string().min(1),
targetUserId: z.string().optional(),
targetAnonymousId: z.string().optional(),
targetDeviceId: z.string().optional(),
targetSessionId: z.string().optional(),
collectionLevel: CollectionLevelEnum.default('debug'),
captureLogs: z.boolean().default(true),
captureNetwork: z.boolean().default(true),
captureScreenshots: z.boolean().default(false),
screenshotOnError: z.boolean().default(true),
maxDurationMinutes: z.number().min(5).max(1440).default(60),
});
export const UpdateDebugSessionSchema = z.object({
status: SessionStatusEnum.optional(),
collectionLevel: CollectionLevelEnum.optional(),
captureLogs: z.boolean().optional(),
captureNetwork: z.boolean().optional(),
captureScreenshots: z.boolean().optional(),
screenshotOnError: z.boolean().optional(),
maxDurationMinutes: z.number().min(5).max(1440).optional(),
});
export const ListDebugSessionsQuerySchema = z.object({
productId: z.string().optional(),
status: SessionStatusEnum.optional(),
targetUserId: z.string().optional(),
from: z.string().datetime().optional(),
to: z.string().datetime().optional(),
limit: z.coerce.number().min(1).max(100).default(20),
offset: z.coerce.number().min(0).default(0),
});
export const IngestTracesSchema = z.object({
sessionId: z.string().min(1),
traces: z
.array(
z.object({
traceId: z.string().min(1),
spanId: z.string().min(1),
parentId: z.string().optional(),
name: z.string().min(1),
kind: SpanKindEnum.optional(),
startTime: z.string().datetime(),
endTime: z.string().datetime().optional(),
durationMs: z.number().optional(),
attributes: z.record(z.unknown()).default({}),
status: SpanStatusEnum,
statusMessage: z.string().optional(),
events: z
.array(
z.object({
name: z.string(),
timestamp: z.string().datetime(),
attributes: z.record(z.unknown()).optional(),
})
)
.optional(),
links: z
.array(
z.object({
traceId: z.string(),
spanId: z.string(),
attributes: z.record(z.unknown()).optional(),
})
)
.optional(),
})
)
.min(1)
.max(50),
});
export const IngestLogsSchema = z.object({
sessionId: z.string().min(1),
logs: z
.array(
z.object({
level: LogLevelEnum,
message: z.string().max(4096),
timestamp: z.string().datetime(),
module: z.string().min(1),
file: z.string().optional(),
line: z.number().optional(),
function: z.string().optional(),
threadId: z.string().optional(),
correlationId: z.string().optional(),
context: z.record(z.unknown()).default({}),
})
)
.min(1)
.max(50),
});
export const CreateScreenshotMetadataSchema = z.object({
sessionId: z.string().min(1),
capturedAt: z.string().datetime(),
trigger: ScreenshotTriggerEnum,
width: z.number().positive(),
height: z.number().positive(),
format: z.enum(['png', 'jpeg', 'webp']),
sizeBytes: z.number().positive(),
sensitiveViewsBlurred: z.boolean(),
blurRegions: z
.array(z.object({ x: z.number(), y: z.number(), w: z.number(), h: z.number() }))
.optional(),
screenName: z.string().optional(),
breadcrumbAtCapture: z.string().optional(),
});
export const QueryTracesSchema = z.object({
limit: z.coerce.number().min(1).max(200).default(50),
continuationToken: z.string().optional(),
});
export const QueryLogsSchema = z.object({
level: LogLevelEnum.optional(),
from: z.string().datetime().optional(),
to: z.string().datetime().optional(),
search: z.string().max(256).optional(),
limit: z.coerce.number().min(1).max(200).default(50),
continuationToken: z.string().optional(),
});
// ─────────────────────────────────────────────────────────────────────────────
// Inferred Types
// ─────────────────────────────────────────────────────────────────────────────
export type CreateDebugSessionInput = z.infer<typeof CreateDebugSessionSchema>;
export type UpdateDebugSessionInput = z.infer<typeof UpdateDebugSessionSchema>;
export type ListDebugSessionsQuery = z.infer<typeof ListDebugSessionsQuerySchema>;
export type IngestTracesInput = z.infer<typeof IngestTracesSchema>;
export type IngestLogsInput = z.infer<typeof IngestLogsSchema>;
export type CreateScreenshotMetadataInput = z.infer<typeof CreateScreenshotMetadataSchema>;
export type QueryTracesInput = z.infer<typeof QueryTracesSchema>;
export type QueryLogsInput = z.infer<typeof QueryLogsSchema>;
// ─────────────────────────────────────────────────────────────────────────────
// Event Bus Event Types
// ─────────────────────────────────────────────────────────────────────────────
export interface DiagnosticsSessionCreatedEvent {
sessionId: string;
productId: string;
targetUserId?: string;
createdBy: string;
}
export interface DiagnosticsSessionUpdatedEvent {
sessionId: string;
productId: string;
changes: Partial<DebugSessionDoc>;
updatedBy: string;
}
export interface DiagnosticsSessionCancelledEvent {
sessionId: string;
productId: string;
reason?: string;
cancelledBy: string;
}
export interface DiagnosticsSessionCompletedEvent {
sessionId: string;
productId: string;
stats: {
logCount: number;
traceCount: number;
screenshotCount: number;
};
endedAt: string;
}
export interface DiagnosticsIngestFatalEvent {
sessionId: string;
productId: string;
logEntry: DebugLogEntryDoc;
timestamp: string;
}