@ancplua/qyl-api-schema 0.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 (54) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +89 -0
  3. package/VERSIONING.md +74 -0
  4. package/api/routes.tsp +1052 -0
  5. package/api/streaming.tsp +335 -0
  6. package/common/errors.tsp +206 -0
  7. package/common/pagination.tsp +254 -0
  8. package/common/types.tsp +345 -0
  9. package/generated/README.md +26 -0
  10. package/generated/otel-keys.gen.tsp +1648 -0
  11. package/index.tsp +55 -0
  12. package/intelligence/causal-rules.tsp +34 -0
  13. package/intelligence/diagnostic-patterns.tsp +54 -0
  14. package/intelligence/investigation-strategies.tsp +37 -0
  15. package/intelligence/main.tsp +19 -0
  16. package/intelligence/seed/patterns.tsp +172 -0
  17. package/intelligence/seed/rules.tsp +44 -0
  18. package/intelligence/seed/strategies.tsp +56 -0
  19. package/intelligence/signals.tsp +60 -0
  20. package/models/agent/agent-run.tsp +154 -0
  21. package/models/agent/tool-call.tsp +122 -0
  22. package/models/agent/workflow-checkpoint.tsp +50 -0
  23. package/models/agent/workflow-execution.tsp +136 -0
  24. package/models/alerting.tsp +436 -0
  25. package/models/configurator.tsp +433 -0
  26. package/models/control-graph.tsp +197 -0
  27. package/models/db.tsp +810 -0
  28. package/models/deployment.tsp +365 -0
  29. package/models/error.tsp +433 -0
  30. package/models/genai.tsp +1368 -0
  31. package/models/http.tsp +600 -0
  32. package/models/identity.tsp +213 -0
  33. package/models/issues.tsp +484 -0
  34. package/models/log.tsp +140 -0
  35. package/models/messaging.tsp +304 -0
  36. package/models/otel-config.tsp +455 -0
  37. package/models/retention.tsp +240 -0
  38. package/models/rpc.tsp +309 -0
  39. package/models/search.tsp +243 -0
  40. package/models/session.tsp +274 -0
  41. package/models/system.tsp +400 -0
  42. package/models/test.tsp +346 -0
  43. package/models/triage.tsp +113 -0
  44. package/models/workflow.tsp +396 -0
  45. package/models/workspace.tsp +435 -0
  46. package/otel/enums.tsp +392 -0
  47. package/otel/logs.tsp +135 -0
  48. package/otel/metrics.tsp +358 -0
  49. package/otel/otel-conventions.tsp +14 -0
  50. package/otel/profiles.tsp +335 -0
  51. package/otel/resource.tsp +257 -0
  52. package/otel/span.tsp +307 -0
  53. package/package.json +88 -0
  54. package/tspconfig.yaml +49 -0
package/api/routes.tsp ADDED
@@ -0,0 +1,1052 @@
1
+ // =============================================================================
2
+ // qyl API routes
3
+ // =============================================================================
4
+ // RESTful API routes with proper pagination, streaming, and error handling.
5
+ // =============================================================================
6
+
7
+ import "@typespec/http";
8
+ import "@typespec/rest";
9
+ import "@typespec/openapi";
10
+ import "@typespec/versioning";
11
+
12
+ import "../common/types.tsp";
13
+ import "../common/errors.tsp";
14
+ import "../common/pagination.tsp";
15
+ import "../otel/span.tsp";
16
+ import "../otel/logs.tsp";
17
+ import "../otel/metrics.tsp";
18
+ import "../otel/profiles.tsp";
19
+ import "../models/session.tsp";
20
+ import "../models/log.tsp";
21
+ import "../models/error.tsp";
22
+ import "../models/deployment.tsp";
23
+ import "../models/identity.tsp";
24
+
25
+ // New domain models
26
+ import "../models/workspace.tsp";
27
+ import "../models/configurator.tsp";
28
+ import "../models/issues.tsp";
29
+ import "../models/alerting.tsp";
30
+ import "../models/workflow.tsp";
31
+ import "../models/search.tsp";
32
+
33
+ using TypeSpec.Http;
34
+ using TypeSpec.OpenAPI;
35
+ using TypeSpec.Versioning;
36
+
37
+ using Qyl.Api.Contracts.Common;
38
+ using Qyl.Api.Contracts.Common.Errors;
39
+ using Qyl.Api.Contracts.Common.Pagination;
40
+ using Qyl.Api.Contracts.OTel.Traces;
41
+ using Qyl.Api.Contracts.OTel.Logs;
42
+ using Qyl.Api.Contracts.OTel.Enums;
43
+ using Qyl.Api.Contracts.OTel.Profiles;
44
+ using Qyl.Api.Contracts.Domains.Observe.Session;
45
+ using Qyl.Api.Contracts.Domains.Observe.Log;
46
+ using Qyl.Api.Contracts.Domains.Observe.Error;
47
+ using Qyl.Api.Contracts.Domains.Ops.Deployment;
48
+ using Qyl.Api.Contracts.Domains.Identity;
49
+ using Qyl.Api.Contracts.Domains.Workspace;
50
+ using Qyl.Api.Contracts.Domains.Configurator;
51
+ using Qyl.Api.Contracts.Domains.Issues;
52
+ using Qyl.Api.Contracts.Domains.Alerting;
53
+ using Qyl.Api.Contracts.Domains.Workflow;
54
+ using Qyl.Api.Contracts.Domains.Search;
55
+
56
+ // =============================================================================
57
+ // API Versioning
58
+ // =============================================================================
59
+
60
+ @versioned(ApiVersions)
61
+ @service(#{
62
+ title: "qyl Observability API",
63
+ })
64
+ @server("https://api.qyl.dev", "qyl production API")
65
+ @server("https://api.staging.qyl.dev", "qyl staging API")
66
+ namespace Qyl.Api.Contracts;
67
+
68
+ enum ApiVersions {
69
+ @doc("Initial API version")
70
+ v1: "2025-12-01",
71
+
72
+ @doc("GenAI observability additions")
73
+ v2: "2026-01-15",
74
+
75
+ @doc("Full OTel v1.40 compliance")
76
+ v3: "2026-01-26",
77
+ }
78
+
79
+ // =============================================================================
80
+ // Common Response Models
81
+ // =============================================================================
82
+
83
+ @doc("Health status for component-level reporting (HealthUiResponse, dashboard).")
84
+ enum HealthStatus {
85
+ healthy: "healthy",
86
+ degraded: "degraded",
87
+ unhealthy: "unhealthy",
88
+ }
89
+
90
+ @doc("Result of clearing telemetry data from the local collector store.")
91
+ model ClearTelemetryResponse {
92
+ @doc("Deleted span row count.")
93
+ spansDeleted: int32;
94
+
95
+ @doc("Deleted log row count.")
96
+ logsDeleted: int32;
97
+
98
+ @doc("Deleted profile row count.")
99
+ profilesDeleted: int32;
100
+
101
+ @doc("Deleted session row count.")
102
+ sessionsDeleted: int32;
103
+
104
+ @doc("Deleted console row count.")
105
+ consoleCleared: int32;
106
+
107
+ @doc("Cleared signal type.")
108
+ type: string;
109
+ }
110
+
111
+ @doc("Collector metadata response.")
112
+ model MetaResponse {
113
+ @doc("Collector informational version.")
114
+ version: string;
115
+
116
+ @doc("Runtime identifier.")
117
+ runtime: string;
118
+
119
+ @doc("Build metadata.")
120
+ build: MetaBuild;
121
+
122
+ @doc("Enabled collector capabilities.")
123
+ capabilities: MetaCapabilities;
124
+
125
+ @doc("Runtime status.")
126
+ status: MetaStatus;
127
+
128
+ @doc("Local endpoint links.")
129
+ links: MetaLinks;
130
+
131
+ @doc("Local listener ports.")
132
+ ports: MetaPorts;
133
+ }
134
+
135
+ @doc("Collector build metadata.")
136
+ model MetaBuild {
137
+ commit?: string;
138
+ informationalVersion?: string;
139
+ dashboardBuildId?: string;
140
+ dashboardEntryAsset?: string;
141
+ dashboardBuiltAtUtc?: string;
142
+ }
143
+
144
+ @doc("Collector capability flags.")
145
+ model MetaCapabilities {
146
+ tracing: boolean;
147
+ grpc: boolean;
148
+ genAi: boolean;
149
+ profiles: boolean;
150
+ copilot: boolean;
151
+ embeddedDashboard: boolean;
152
+ }
153
+
154
+ @doc("Collector runtime status.")
155
+ model MetaStatus {
156
+ grpcEnabled: boolean;
157
+ authMode: string;
158
+ }
159
+
160
+ @doc("Collector local links.")
161
+ model MetaLinks {
162
+ dashboard?: string;
163
+ otlpHttp?: string;
164
+ otlpGrpc?: string;
165
+ }
166
+
167
+ @doc("Collector local ports.")
168
+ model MetaPorts {
169
+ http: int32;
170
+ grpc: int32;
171
+ otlpHttp: int32;
172
+ }
173
+
174
+ // =============================================================================
175
+ // Traces API
176
+ // =============================================================================
177
+
178
+ @route("/api/v1/traces")
179
+ @tag("Traces")
180
+ interface TracesApi {
181
+ @doc("List traces with pagination")
182
+ @get
183
+ list(
184
+ @query @doc("Page size") @minValue(1) @maxValue(1000) limit?: int32 = 100,
185
+ ): CursorPage<Trace> | NotFoundError | ValidationError | InternalServerError;
186
+
187
+ @doc("Get a specific trace by ID")
188
+ @get
189
+ @route("/{traceId}")
190
+ get(@path traceId: TraceId): Trace | NotFoundError | InternalServerError;
191
+
192
+ @doc("Get spans for a trace")
193
+ @get
194
+ @route("/{traceId}/spans")
195
+ getSpans(
196
+ @path traceId: TraceId,
197
+ ): CursorPage<Span> | NotFoundError | InternalServerError;
198
+ }
199
+
200
+ // =============================================================================
201
+ // Logs API
202
+ // =============================================================================
203
+
204
+ @route("/api/v1/logs")
205
+ @tag("Logs")
206
+ interface LogsApi {
207
+ @doc("Query logs with filtering and pagination")
208
+ @get
209
+ list(
210
+ @query @doc("Session ID filter") sessionId?: SessionId,
211
+ @query @doc("Service name filter") serviceName?: string,
212
+ @query @doc("Severity text filter") level?: string,
213
+ @query @doc("Minimum severity") severityMin?: SeverityNumber,
214
+ @query @doc("Trace ID filter") traceId?: TraceId,
215
+ @query @doc("Start time") startTime?: utcDateTime,
216
+ @query @doc("End time") endTime?: utcDateTime,
217
+ @query @doc("Free text search") query?: string,
218
+ @query @doc("Page size") @minValue(1) @maxValue(10000) limit?: int32 = 100,
219
+ ): CursorPage<LogRecord> | ValidationError | InternalServerError;
220
+ }
221
+
222
+ // =============================================================================
223
+ // Metrics API
224
+ // =============================================================================
225
+
226
+ @route("/api/v1/metrics")
227
+ @tag("Metrics")
228
+ interface MetricsApi {
229
+ @doc("List available metrics")
230
+ @get
231
+ list(
232
+ @query @doc("Service name filter") serviceName?: string,
233
+ @query @doc("Metric name pattern") namePattern?: string,
234
+ @query @doc("Page size") @minValue(1) @maxValue(1000) limit?: int32 = 100,
235
+ @query @doc("Cursor") cursor?: string,
236
+ ): CursorPage<MetricMetadata> | InternalServerError;
237
+
238
+ @doc("Query metric data points")
239
+ @post
240
+ @route("/query")
241
+ query(@body request: MetricQueryRequest): MetricQueryResponse | ValidationError | InternalServerError;
242
+
243
+ @doc("Get metric metadata")
244
+ @get
245
+ @route("/{metricName}")
246
+ getMetadata(@path metricName: string): MetricMetadata | NotFoundError | InternalServerError;
247
+ }
248
+
249
+ @doc("Metric metadata")
250
+ model MetricMetadata {
251
+ @doc("Metric name")
252
+ name: string;
253
+
254
+ @doc("Metric description")
255
+ description?: string;
256
+
257
+ @doc("Metric unit")
258
+ unit?: string;
259
+
260
+ @doc("Metric type")
261
+ type: MetricType;
262
+
263
+ @doc("Available label keys")
264
+ @encodedName("application/json", "label_keys")
265
+ labelKeys: string[];
266
+
267
+ @doc("Services reporting this metric")
268
+ services: string[];
269
+ }
270
+
271
+ @doc("Metric query request")
272
+ model MetricQueryRequest {
273
+ @doc("Metric name")
274
+ @encodedName("application/json", "metric_name")
275
+ metricName: string;
276
+
277
+ @doc("Label filters")
278
+ filters?: Record<string>;
279
+
280
+ @doc("Start time")
281
+ @encodedName("application/json", "start_time")
282
+ startTime: utcDateTime;
283
+
284
+ @doc("End time")
285
+ @encodedName("application/json", "end_time")
286
+ endTime: utcDateTime;
287
+
288
+ @doc("Step interval")
289
+ step?: Qyl.Api.Contracts.Common.Pagination.TimeBucket;
290
+
291
+ @doc("Aggregation function")
292
+ aggregation?: Qyl.Api.Contracts.OTel.Metrics.AggregationFunction;
293
+
294
+ @doc("Group by labels")
295
+ @encodedName("application/json", "group_by")
296
+ groupBy?: string[];
297
+ }
298
+
299
+ @doc("Metric query response")
300
+ model MetricQueryResponse {
301
+ @doc("Metric name")
302
+ @encodedName("application/json", "metric_name")
303
+ metricName: string;
304
+
305
+ @doc("Time series data")
306
+ series: MetricTimeSeries[];
307
+ }
308
+
309
+ @doc("Metric time series")
310
+ model MetricTimeSeries {
311
+ @doc("Labels")
312
+ labels: Record<string>;
313
+
314
+ @doc("Data points")
315
+ points: MetricDataPoint[];
316
+ }
317
+
318
+ @doc("Metric data point")
319
+ model MetricDataPoint {
320
+ @doc("Timestamp")
321
+ timestamp: utcDateTime;
322
+
323
+ @doc("Value")
324
+ value: float64;
325
+ }
326
+
327
+ // =============================================================================
328
+ // Profiles API (v1development, Development stability)
329
+ // =============================================================================
330
+
331
+ @route("/api/v1/profiles")
332
+ @tag("Profiles")
333
+ interface ProfilesApi {
334
+ @doc("List profiles with filtering")
335
+ @get
336
+ list(
337
+ @query @doc("Session ID filter") sessionId?: SessionId,
338
+ @query @doc("Trace ID filter") traceId?: TraceId,
339
+ @query @doc("Service name filter") serviceName?: string,
340
+ @query @doc("Sample type filter (e.g., cpu, alloc_objects, wall)") sampleType?: string,
341
+ @query @doc("Page size") @minValue(1) @maxValue(1000) limit?: int32 = 100,
342
+ ): Profile[] | ValidationError | InternalServerError;
343
+
344
+ @doc("Get a specific profile by ID")
345
+ @get
346
+ @route("/{profileId}")
347
+ get(@path profileId: string): Profile | NotFoundError | InternalServerError;
348
+
349
+ @doc("Get profiles for a trace")
350
+ @get
351
+ @route("/by-trace/{traceId}")
352
+ getByTrace(
353
+ @path traceId: TraceId,
354
+ @query @doc("Page size") @minValue(1) @maxValue(1000) limit?: int32 = 100,
355
+ ): Profile[] | InternalServerError;
356
+
357
+ @doc("Get profiles for a span")
358
+ @get
359
+ @route("/by-span/{spanId}")
360
+ getBySpan(
361
+ @path spanId: SpanId,
362
+ @query @doc("Page size") @minValue(1) @maxValue(1000) limit?: int32 = 100,
363
+ ): Profile[] | InternalServerError;
364
+ }
365
+
366
+ // =============================================================================
367
+ // Sessions API
368
+ // =============================================================================
369
+
370
+ @route("/api/v1/sessions")
371
+ @tag("Sessions")
372
+ interface SessionsApi {
373
+ @doc("List sessions")
374
+ @get
375
+ list(
376
+ @query @doc("Is active filter") isActive?: boolean,
377
+ @query @doc("Start time") startTime?: utcDateTime,
378
+ @query @doc("End time") endTime?: utcDateTime,
379
+ @query @doc("Page size") @minValue(1) @maxValue(1000) limit?: int32 = 100,
380
+ @query @doc("Cursor") cursor?: string,
381
+ ): CursorPage<SessionEntity> | ValidationError | InternalServerError;
382
+
383
+ @doc("Get session by ID")
384
+ @get
385
+ @route("/{sessionId}")
386
+ get(@path sessionId: string): SessionEntity | NotFoundError | InternalServerError;
387
+
388
+ @doc("Get session traces")
389
+ @get
390
+ @route("/{sessionId}/traces")
391
+ getTraces(
392
+ @path sessionId: string,
393
+ ): CursorPage<Trace> | NotFoundError | InternalServerError;
394
+
395
+ @doc("Get session statistics")
396
+ @get
397
+ @route("/stats")
398
+ getStats(
399
+ @query @doc("Start time") startTime?: utcDateTime,
400
+ @query @doc("End time") endTime?: utcDateTime,
401
+ ): SessionStats | InternalServerError;
402
+ }
403
+
404
+ // =============================================================================
405
+ // Errors API
406
+ // =============================================================================
407
+
408
+ @route("/api/v1/errors")
409
+ @tag("Errors")
410
+ interface ErrorsApi {
411
+ @doc("List error groups")
412
+ @get
413
+ list(
414
+ @query @doc("Service name filter") serviceName?: string,
415
+ @query @doc("Status filter") status?: ErrorStatus,
416
+ @query @doc("Category filter") category?: ErrorCategory,
417
+ @query @doc("Start time") startTime?: utcDateTime,
418
+ @query @doc("End time") endTime?: utcDateTime,
419
+ @query @doc("Page size") @minValue(1) @maxValue(1000) limit?: int32 = 100,
420
+ @query @doc("Cursor") cursor?: string,
421
+ ): CursorPage<ErrorEntity> | ValidationError | InternalServerError;
422
+
423
+ @doc("Get error by ID")
424
+ @get
425
+ @route("/{errorId}")
426
+ get(@path errorId: string): ErrorEntity | NotFoundError | InternalServerError;
427
+
428
+ @doc("Update error status")
429
+ @patch
430
+ @route("/{errorId}")
431
+ update(
432
+ @path errorId: string,
433
+ @body update: MergePatchUpdate<ErrorEntity>,
434
+ ): ErrorEntity | NotFoundError | ValidationError | InternalServerError;
435
+
436
+ @doc("Get error statistics")
437
+ @get
438
+ @route("/stats")
439
+ getStats(
440
+ @query @doc("Service name filter") serviceName?: string,
441
+ @query @doc("Start time") startTime?: utcDateTime,
442
+ @query @doc("End time") endTime?: utcDateTime,
443
+ ): ErrorStats | InternalServerError;
444
+
445
+ @doc("Get error correlations")
446
+ @get
447
+ @route("/{errorId}/correlations")
448
+ getCorrelations(@path errorId: string): ErrorCorrelation | NotFoundError | InternalServerError;
449
+ }
450
+
451
+
452
+ // =============================================================================
453
+ // Deployments API
454
+ // =============================================================================
455
+
456
+ @route("/api/v1/deployments")
457
+ @tag("Deployments")
458
+ interface DeploymentsApi {
459
+ @doc("List deployments")
460
+ @get
461
+ list(
462
+ @query @doc("Service name filter") serviceName?: string,
463
+ @query @doc("Environment filter") environment?: DeploymentEnvironment,
464
+ @query @doc("Status filter") status?: DeploymentStatus,
465
+ @query @doc("Start time") startTime?: utcDateTime,
466
+ @query @doc("End time") endTime?: utcDateTime,
467
+ @query @doc("Page size") @minValue(1) @maxValue(1000) limit?: int32 = 100,
468
+ @query @doc("Cursor") cursor?: string,
469
+ ): CursorPage<DeploymentEntity> | ValidationError | InternalServerError;
470
+
471
+ @doc("Get deployment by ID")
472
+ @get
473
+ @route("/{deploymentId}")
474
+ get(@path deploymentId: string): DeploymentEntity | NotFoundError | InternalServerError;
475
+
476
+ @doc("Record new deployment")
477
+ @post
478
+ create(@body deployment: Create<DeploymentEntity>): DeploymentEntity | ValidationError | InternalServerError;
479
+
480
+ @doc("Update deployment status")
481
+ @patch
482
+ @route("/{deploymentId}")
483
+ update(
484
+ @path deploymentId: string,
485
+ @body update: MergePatchUpdate<DeploymentEntity>,
486
+ ): DeploymentEntity | NotFoundError | ValidationError | InternalServerError;
487
+
488
+ @doc("Get DORA metrics")
489
+ @get
490
+ @route("/metrics/dora")
491
+ getDoraMetrics(
492
+ @query @doc("Service name filter") serviceName?: string,
493
+ @query @doc("Environment filter") environment?: DeploymentEnvironment,
494
+ @query @doc("Start time") startTime?: utcDateTime,
495
+ @query @doc("End time") endTime?: utcDateTime,
496
+ ): DoraMetrics | InternalServerError;
497
+ }
498
+
499
+ @doc("DORA metrics response")
500
+ model DoraMetrics {
501
+ @doc("Deployment frequency (per day)")
502
+ @encodedName("application/json", "deployment_frequency")
503
+ deploymentFrequency: float64;
504
+
505
+ @doc("Lead time for changes (hours)")
506
+ @encodedName("application/json", "lead_time_hours")
507
+ leadTimeHours: float64;
508
+
509
+ @doc("Change failure rate")
510
+ @encodedName("application/json", "change_failure_rate")
511
+ changeFailureRate: Ratio;
512
+
513
+ @doc("Mean time to recovery (hours)")
514
+ @encodedName("application/json", "mttr_hours")
515
+ mttrHours: float64;
516
+
517
+ @doc("Performance level")
518
+ @encodedName("application/json", "performance_level")
519
+ performanceLevel: DoraPerformanceLevel;
520
+ }
521
+
522
+ @doc("DORA performance levels")
523
+ enum DoraPerformanceLevel {
524
+ @doc("Elite performer")
525
+ elite: "elite",
526
+
527
+ @doc("High performer")
528
+ high: "high",
529
+
530
+ @doc("Medium performer")
531
+ medium: "medium",
532
+
533
+ @doc("Low performer")
534
+ low: "low",
535
+ }
536
+
537
+ // =============================================================================
538
+ // Services API
539
+ // =============================================================================
540
+
541
+ @route("/api/v1/services")
542
+ @tag("Services")
543
+ interface ServicesApi {
544
+ @doc("List discovered services")
545
+ @get
546
+ list(
547
+ @query @doc("Namespace filter") namespaceName?: string,
548
+ @query @doc("Page size") @minValue(1) @maxValue(1000) limit?: int32 = 100,
549
+ @query @doc("Cursor") cursor?: string,
550
+ ): CursorPage<ServiceInfo> | InternalServerError;
551
+
552
+ @doc("Get service details")
553
+ @get
554
+ @route("/{serviceName}")
555
+ get(@path serviceName: string): ServiceDetails | NotFoundError | InternalServerError;
556
+
557
+ @doc("Get service dependencies")
558
+ @get
559
+ @route("/{serviceName}/dependencies")
560
+ getDependencies(@path serviceName: string): ServiceDependency[] | NotFoundError | InternalServerError;
561
+
562
+ @doc("Get service operations")
563
+ @get
564
+ @route("/{serviceName}/operations")
565
+ getOperations(
566
+ @path serviceName: string,
567
+ @query @doc("Page size") limit?: int32 = 100,
568
+ @query @doc("Cursor") cursor?: string,
569
+ ): CursorPage<OperationInfo> | NotFoundError | InternalServerError;
570
+ }
571
+
572
+ @doc("Service information")
573
+ model ServiceInfo {
574
+ @doc("Service name")
575
+ name: string;
576
+
577
+ @doc("Service namespace")
578
+ @encodedName("application/json", "namespace_name")
579
+ namespaceName?: string;
580
+
581
+ @doc("Service version")
582
+ version?: SemVer;
583
+
584
+ @doc("Instance count")
585
+ @encodedName("application/json", "instance_count")
586
+ instanceCount: int32;
587
+
588
+ @doc("Last seen")
589
+ @encodedName("application/json", "last_seen")
590
+ lastSeen: utcDateTime;
591
+ }
592
+
593
+ @doc("Service details")
594
+ model ServiceDetails {
595
+ ...ServiceInfo;
596
+
597
+ @doc("Resource attributes")
598
+ @encodedName("application/json", "resource_attributes")
599
+ resourceAttributes: Attribute[];
600
+
601
+ @doc("Instrumentation libraries")
602
+ @encodedName("application/json", "instrumentation_libraries")
603
+ instrumentationLibraries: InstrumentationScope[];
604
+
605
+ @doc("Request rate (per second)")
606
+ @encodedName("application/json", "request_rate")
607
+ requestRate: float64;
608
+
609
+ @doc("Error rate")
610
+ @encodedName("application/json", "error_rate")
611
+ errorRate: Ratio;
612
+
613
+ @doc("Average latency in milliseconds")
614
+ @encodedName("application/json", "avg_latency_ms")
615
+ avgLatencyMs: float64;
616
+
617
+ @doc("P99 latency in milliseconds")
618
+ @encodedName("application/json", "p99_latency_ms")
619
+ p99LatencyMs: float64;
620
+ }
621
+
622
+ @doc("Operation information")
623
+ model OperationInfo {
624
+ @doc("Operation name")
625
+ name: string;
626
+
627
+ @doc("Span kind")
628
+ @encodedName("application/json", "span_kind")
629
+ spanKind: SpanKind;
630
+
631
+ @doc("Request count")
632
+ @encodedName("application/json", "request_count")
633
+ requestCount: Count;
634
+
635
+ @doc("Error count")
636
+ @encodedName("application/json", "error_count")
637
+ errorCount: Count;
638
+
639
+ @doc("Average duration in milliseconds")
640
+ @encodedName("application/json", "avg_duration_ms")
641
+ avgDurationMs: float64;
642
+
643
+ @doc("P99 duration in milliseconds")
644
+ @encodedName("application/json", "p99_duration_ms")
645
+ p99DurationMs: float64;
646
+ }
647
+
648
+ // =============================================================================
649
+ // Health API — Aspire-shape probes. /alive is cheap liveness (live-tagged checks),
650
+ // /health runs all ready-tagged checks. Collector exposes a rich /health/ui DTO for
651
+ // the dashboard, modelled separately as HealthUiResponse.
652
+ // =============================================================================
653
+
654
+ @tag("Health")
655
+ interface HealthApi {
656
+ @doc("Liveness probe — runs live-tagged health checks.")
657
+ @route("/alive")
658
+ @get
659
+ @operationId("health_alive")
660
+ alive(): {
661
+ @statusCode statusCode: 200;
662
+ } | {
663
+ @statusCode statusCode: 503;
664
+ };
665
+
666
+ @doc("Readiness probe — runs all ready-tagged health checks.")
667
+ @route("/health")
668
+ @get
669
+ @operationId("health_ready")
670
+ ready(): {
671
+ @statusCode statusCode: 200;
672
+ } | {
673
+ @statusCode statusCode: 503;
674
+ };
675
+ }
676
+
677
+ // =============================================================================
678
+ // Workspace API
679
+ // =============================================================================
680
+
681
+ @route("/api/v1/workspaces")
682
+ @tag("Workspaces")
683
+ interface WorkspacesApi {
684
+ @doc("Get current workspace envelope")
685
+ @get
686
+ @route("/current")
687
+ getCurrent(): WorkspaceEnvelopeEntity | NotFoundError | InternalServerError;
688
+
689
+ @doc("Register workspace heartbeat")
690
+ @post
691
+ @route("/current/heartbeat")
692
+ heartbeat(): WorkspaceEnvelopeEntity | NotFoundError | InternalServerError;
693
+
694
+ @doc("List projects")
695
+ @get
696
+ @route("/projects")
697
+ listProjects(
698
+ @query @doc("Page size") @minValue(1) @maxValue(100) limit?: int32 = 20,
699
+ @query @doc("Cursor") cursor?: string,
700
+ ): CursorPage<ProjectEntity> | InternalServerError;
701
+
702
+ @doc("Get project by ID")
703
+ @get
704
+ @route("/projects/{projectId}")
705
+ getProject(@path projectId: string): ProjectEntity | NotFoundError | InternalServerError;
706
+
707
+ @doc("Create a new project")
708
+ @post
709
+ @route("/projects")
710
+ createProject(@body project: Create<ProjectEntity>): ProjectEntity | ValidationError | InternalServerError;
711
+
712
+ @doc("List environments for a project")
713
+ @get
714
+ @route("/projects/{projectId}/environments")
715
+ listEnvironments(@path projectId: string): ProjectEnvironmentEntity[] | NotFoundError | InternalServerError;
716
+ }
717
+
718
+ // =============================================================================
719
+ // Onboarding API
720
+ // =============================================================================
721
+
722
+ @route("/api/v1/onboarding")
723
+ @tag("Onboarding")
724
+ interface OnboardingApi {
725
+ @doc("Start a new handshake session")
726
+ @post
727
+ @route("/handshake")
728
+ startHandshake(@body request: HandshakeStartRequest): HandshakeSessionEntity | ValidationError | InternalServerError;
729
+
730
+ @doc("Complete handshake verification")
731
+ @post
732
+ @route("/handshake/{sessionId}/verify")
733
+ verifyHandshake(
734
+ @path sessionId: string,
735
+ @body request: HandshakeVerifyRequest,
736
+ ): HandshakeVerifyResponse | NotFoundError | ValidationError | InternalServerError;
737
+
738
+ @doc("Get handshake session status")
739
+ @get
740
+ @route("/handshake/{sessionId}")
741
+ getHandshake(@path sessionId: string): HandshakeSessionEntity | NotFoundError | InternalServerError;
742
+ }
743
+
744
+ @doc("Handshake start request")
745
+ model HandshakeStartRequest {
746
+ @doc("PKCE code challenge")
747
+ @encodedName("application/json", "code_challenge")
748
+ codeChallenge: string;
749
+
750
+ @doc("Client identifier")
751
+ @encodedName("application/json", "client_id")
752
+ clientId: string;
753
+ }
754
+
755
+ @doc("Handshake verification request")
756
+ model HandshakeVerifyRequest {
757
+ @doc("PKCE code verifier")
758
+ @encodedName("application/json", "code_verifier")
759
+ codeVerifier: string;
760
+
761
+ @doc("Authorization code")
762
+ code: string;
763
+ }
764
+
765
+ @doc("Handshake verification response")
766
+ model HandshakeVerifyResponse {
767
+ @doc("Access token")
768
+ @encodedName("application/json", "access_token")
769
+ accessToken: string;
770
+
771
+ @doc("Token expiration")
772
+ @encodedName("application/json", "expires_at")
773
+ expiresAt: utcDateTime;
774
+
775
+ @doc("Workspace ID")
776
+ @encodedName("application/json", "workspace_id")
777
+ workspaceId: string;
778
+ }
779
+
780
+ // =============================================================================
781
+ // Configurator API
782
+ // =============================================================================
783
+
784
+ @route("/api/v1/configurator")
785
+ @tag("Configurator")
786
+ interface ConfiguratorApi {
787
+ @doc("List generation profiles")
788
+ @get
789
+ @route("/profiles")
790
+ listProfiles(
791
+ @query @doc("Page size") @minValue(1) @maxValue(100) limit?: int32 = 20,
792
+ @query @doc("Cursor") cursor?: string,
793
+ ): CursorPage<GenerationProfileEntity> | InternalServerError;
794
+
795
+ @doc("Get generation profile by ID")
796
+ @get
797
+ @route("/profiles/{profileId}")
798
+ getProfile(@path profileId: string): GenerationProfileEntity | NotFoundError | InternalServerError;
799
+
800
+ @doc("Create generation profile")
801
+ @post
802
+ @route("/profiles")
803
+ createProfile(@body profile: Create<GenerationProfileEntity>): GenerationProfileEntity | ValidationError | InternalServerError;
804
+
805
+ @doc("Get generation selections for a workspace")
806
+ @get
807
+ @route("/selections")
808
+ getSelections(
809
+ @query @doc("Workspace ID") workspaceId: string,
810
+ ): GenerationSelectionEntity[] | InternalServerError;
811
+
812
+ @doc("Save generation selections")
813
+ @post
814
+ @route("/selections")
815
+ saveSelections(@body selections: GenerationSelectionSaveRequest): GenerationSelectionEntity | ValidationError | InternalServerError;
816
+
817
+ @doc("Create a generation job")
818
+ @post
819
+ @route("/jobs")
820
+ createJob(@body job: Create<GenerationJobEntity>): GenerationJobEntity | ValidationError | InternalServerError;
821
+
822
+ @doc("Get generation job status")
823
+ @get
824
+ @route("/jobs/{jobId}")
825
+ getJob(@path jobId: string): GenerationJobEntity | NotFoundError | InternalServerError;
826
+ }
827
+
828
+ @doc("Batch upsert request — workspace+profile scope from GenerationSelectionEntity, plus an array of selection keys")
829
+ model GenerationSelectionSaveRequest {
830
+ ...PickProperties<GenerationSelectionEntity, "workspaceId" | "profileId">;
831
+
832
+ @doc("Array of selection keys to enable in a single round-trip")
833
+ @encodedName("application/json", "selected_keys")
834
+ selectedKeys: string[];
835
+ }
836
+
837
+ // =============================================================================
838
+ // Issues API (Sentry-style error grouping)
839
+ // =============================================================================
840
+
841
+ @route("/api/v1/issues")
842
+ @tag("Issues")
843
+ interface IssuesApi {
844
+ @doc("List error issues")
845
+ @get
846
+ list(
847
+ @query @doc("Project ID filter") projectId?: string,
848
+ @query @doc("Status filter") status?: IssueStatus,
849
+ @query @doc("Priority filter") priority?: IssuePriority,
850
+ @query @doc("Level filter") level?: IssueLevel,
851
+ @query @doc("Start time") startTime?: utcDateTime,
852
+ @query @doc("End time") endTime?: utcDateTime,
853
+ @query @doc("Page size") @minValue(1) @maxValue(100) limit?: int32 = 20,
854
+ @query @doc("Cursor") cursor?: string,
855
+ ): CursorPage<ErrorIssueEntity> | ValidationError | InternalServerError;
856
+
857
+ @doc("Get issue by ID")
858
+ @get
859
+ @route("/{issueId}")
860
+ get(@path issueId: string): ErrorIssueEntity | NotFoundError | InternalServerError;
861
+
862
+ @doc("Update issue status")
863
+ @patch
864
+ @route("/{issueId}")
865
+ update(
866
+ @path issueId: string,
867
+ @body update: MergePatchUpdate<ErrorIssueEntity>,
868
+ ): ErrorIssueEntity | NotFoundError | ValidationError | InternalServerError;
869
+
870
+ @doc("Get issue events")
871
+ @get
872
+ @route("/{issueId}/events")
873
+ getEvents(
874
+ @path issueId: string,
875
+ @query @doc("Page size") @minValue(1) @maxValue(100) limit?: int32 = 20,
876
+ @query @doc("Cursor") cursor?: string,
877
+ ): CursorPage<ErrorIssueEventEntity> | NotFoundError | InternalServerError;
878
+
879
+ @doc("Get issue breadcrumbs")
880
+ @get
881
+ @route("/{issueId}/breadcrumbs")
882
+ getBreadcrumbs(
883
+ @path issueId: string,
884
+ @query @doc("Page size") @minValue(1) @maxValue(50) limit?: int32 = 20,
885
+ ): ErrorBreadcrumbEntity[] | NotFoundError | InternalServerError;
886
+ }
887
+
888
+
889
+ // =============================================================================
890
+ // Workflow API
891
+ // =============================================================================
892
+
893
+ @route("/api/v1/workflows")
894
+ @tag("Workflows")
895
+ interface WorkflowsApi {
896
+ @doc("List workflow runs")
897
+ @get
898
+ @route("/runs")
899
+ listRuns(
900
+ @query @doc("Project ID filter") projectId?: string,
901
+ @query @doc("Workflow ID filter") workflowId?: string,
902
+ @query @doc("Status filter") status?: WorkflowRunStatus,
903
+ @query @doc("Start time") startTime?: utcDateTime,
904
+ @query @doc("End time") endTime?: utcDateTime,
905
+ @query @doc("Page size") @minValue(1) @maxValue(100) limit?: int32 = 20,
906
+ @query @doc("Cursor") cursor?: string,
907
+ ): CursorPage<WorkflowRunEntity> | ValidationError | InternalServerError;
908
+
909
+ @doc("Get workflow run by ID")
910
+ @get
911
+ @route("/runs/{runId}")
912
+ getRun(@path runId: string): WorkflowRunEntity | NotFoundError | InternalServerError;
913
+
914
+ @doc("Get workflow run nodes")
915
+ @get
916
+ @route("/runs/{runId}/nodes")
917
+ getRunNodes(
918
+ @path runId: string,
919
+ @query @doc("Page size") @minValue(1) @maxValue(100) limit?: int32 = 50,
920
+ @query @doc("Cursor") cursor?: string,
921
+ ): CursorPage<WorkflowNodeEntity> | NotFoundError | InternalServerError;
922
+
923
+ @doc("Get workflow events")
924
+ @get
925
+ @route("/runs/{runId}/events")
926
+ getRunEvents(
927
+ @path runId: string,
928
+ @query @doc("After sequence number") afterSequence?: int64,
929
+ @query @doc("Page size") @minValue(1) @maxValue(100) limit?: int32 = 50,
930
+ ): WorkflowEventEntity[] | NotFoundError | InternalServerError;
931
+
932
+ @doc("Resume a paused workflow")
933
+ @post
934
+ @route("/runs/{runId}/resume")
935
+ resumeRun(@path runId: string): WorkflowRunEntity | NotFoundError | ValidationError | InternalServerError;
936
+
937
+ @doc("Approve a pending workflow step")
938
+ @post
939
+ @route("/runs/{runId}/nodes/{nodeId}/approve")
940
+ approveStep(
941
+ @path runId: string,
942
+ @path nodeId: string,
943
+ ): WorkflowNodeEntity | NotFoundError | ValidationError | InternalServerError;
944
+ }
945
+
946
+ // =============================================================================
947
+ // Search API
948
+ // =============================================================================
949
+
950
+ @route("/api/v1/search")
951
+ @tag("Search")
952
+ interface SearchApi {
953
+ @doc("Unified search across all entity types")
954
+ @post
955
+ search(@body request: SearchRequest): SearchResponse | ValidationError | InternalServerError;
956
+
957
+ @doc("Get search suggestions")
958
+ @get
959
+ @route("/suggestions")
960
+ getSuggestions(
961
+ @query @doc("Query prefix") query: string,
962
+ @query @doc("Maximum suggestions") @minValue(1) @maxValue(20) limit?: int32 = 5,
963
+ ): string[] | InternalServerError;
964
+ }
965
+
966
+ // =============================================================================
967
+ // Alerts API
968
+ // =============================================================================
969
+
970
+ @route("/api/v1/alerts")
971
+ @tag("Alerts")
972
+ interface AlertsApi {
973
+ @doc("List alert rules")
974
+ @get
975
+ @route("/rules")
976
+ listRules(
977
+ @query @doc("Project ID filter") projectId?: string,
978
+ @query @doc("Enabled filter") enabled?: boolean,
979
+ @query @doc("Page size") @minValue(1) @maxValue(100) limit?: int32 = 20,
980
+ @query @doc("Cursor") cursor?: string,
981
+ ): CursorPage<AlertRuleEntity> | InternalServerError;
982
+
983
+ @doc("Get alert rule by ID")
984
+ @get
985
+ @route("/rules/{ruleId}")
986
+ getRule(@path ruleId: string): AlertRuleEntity | NotFoundError | InternalServerError;
987
+
988
+ @doc("Create a new alert rule")
989
+ @post
990
+ @route("/rules")
991
+ createRule(@body rule: AlertRuleEntity): AlertRuleEntity | ValidationError | InternalServerError;
992
+
993
+ @doc("Update an existing alert rule")
994
+ @put
995
+ @route("/rules/{ruleId}")
996
+ updateRule(
997
+ @path ruleId: string,
998
+ @body rule: AlertRuleEntity,
999
+ ): AlertRuleEntity | NotFoundError | ValidationError | InternalServerError;
1000
+
1001
+ @doc("Delete an alert rule")
1002
+ @delete
1003
+ @route("/rules/{ruleId}")
1004
+ deleteRule(@path ruleId: string): NoContentResponse | NotFoundError | InternalServerError;
1005
+
1006
+ @doc("List alert firings")
1007
+ @get
1008
+ @route("/firings")
1009
+ listFirings(
1010
+ @query @doc("Rule ID filter") ruleId?: string,
1011
+ @query @doc("Status filter") status?: AlertFiringStatus,
1012
+ @query @doc("Start time") startTime?: utcDateTime,
1013
+ @query @doc("End time") endTime?: utcDateTime,
1014
+ @query @doc("Page size") @minValue(1) @maxValue(100) limit?: int32 = 20,
1015
+ @query @doc("Cursor") cursor?: string,
1016
+ ): CursorPage<AlertFiringEntity> | ValidationError | InternalServerError;
1017
+
1018
+ @doc("Acknowledge an alert firing")
1019
+ @post
1020
+ @route("/firings/{firingId}/acknowledge")
1021
+ acknowledgeFiring(
1022
+ @path firingId: string,
1023
+ @body acknowledgement: AlertFiringAcknowledgement,
1024
+ ): AlertFiringEntity | NotFoundError | InternalServerError;
1025
+
1026
+ @doc("Resolve an alert firing")
1027
+ @post
1028
+ @route("/firings/{firingId}/resolve")
1029
+ resolveFiring(@path firingId: string): AlertFiringEntity | NotFoundError | InternalServerError;
1030
+
1031
+ @doc("List fix runs")
1032
+ @get
1033
+ @route("/fixes")
1034
+ listFixRuns(
1035
+ @query @doc("Issue ID filter") issueId?: string,
1036
+ @query @doc("Status filter") status?: FixRunStatus,
1037
+ @query @doc("Page size") @minValue(1) @maxValue(100) limit?: int32 = 20,
1038
+ @query @doc("Cursor") cursor?: string,
1039
+ ): CursorPage<FixRunEntity> | ValidationError | InternalServerError;
1040
+
1041
+ @doc("Get fix run by ID")
1042
+ @get
1043
+ @route("/fixes/{fixId}")
1044
+ getFixRun(@path fixId: string): FixRunEntity | NotFoundError | InternalServerError;
1045
+ }
1046
+
1047
+ @doc("Acknowledge request body for an alert firing")
1048
+ model AlertFiringAcknowledgement {
1049
+ @doc("User or service that acknowledges the firing")
1050
+ @encodedName("application/json", "acknowledged_by")
1051
+ acknowledgedBy: string;
1052
+ }