@ancplua/qyl-api-schema 0.4.0 → 0.5.1

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 (61) hide show
  1. package/README.md +52 -64
  2. package/api/routes.tsp +90 -943
  3. package/api/runner.tsp +99 -0
  4. package/api/streaming.tsp +13 -299
  5. package/common/errors.tsp +14 -1
  6. package/common/pagination.tsp +1 -1
  7. package/common/types.tsp +2 -41
  8. package/generated/json-schema/qyl-api-schema.json +3868 -0
  9. package/generated/openapi/qyl.openapi.json +6919 -0
  10. package/generated/ts-runtime/api.d.ts +886 -0
  11. package/generated/ts-runtime/api.js +237 -0
  12. package/index.tsp +8 -34
  13. package/models/health.tsp +22 -0
  14. package/models/mcp-tools.tsp +217 -0
  15. package/models/runner-mcp.tsp +76 -0
  16. package/models/runner.tsp +67 -0
  17. package/models/session.tsp +1 -1
  18. package/otel/enums.tsp +1 -1
  19. package/otel/logs.tsp +1 -1
  20. package/otel/otel-conventions.tsp +0 -1
  21. package/otel/profiles.tsp +1 -1
  22. package/otel/resource.tsp +1 -1
  23. package/otel/span.tsp +9 -6
  24. package/package.json +56 -36
  25. package/VERSIONING.md +0 -88
  26. package/generated/README.md +0 -35
  27. package/intelligence/causal-rules.tsp +0 -34
  28. package/intelligence/diagnostic-patterns.tsp +0 -54
  29. package/intelligence/investigation-strategies.tsp +0 -37
  30. package/intelligence/main.tsp +0 -17
  31. package/intelligence/seed/patterns.tsp +0 -171
  32. package/intelligence/seed/rules.tsp +0 -43
  33. package/intelligence/seed/strategies.tsp +0 -55
  34. package/intelligence/signals.tsp +0 -60
  35. package/models/agent/agent-run.tsp +0 -154
  36. package/models/agent/tool-call.tsp +0 -122
  37. package/models/agent/workflow-checkpoint.tsp +0 -50
  38. package/models/agent/workflow-execution.tsp +0 -136
  39. package/models/alerting.tsp +0 -436
  40. package/models/configurator.tsp +0 -433
  41. package/models/control-graph.tsp +0 -197
  42. package/models/db.tsp +0 -767
  43. package/models/deployment.tsp +0 -365
  44. package/models/error.tsp +0 -433
  45. package/models/genai.tsp +0 -1305
  46. package/models/http.tsp +0 -547
  47. package/models/identity.tsp +0 -213
  48. package/models/issues.tsp +0 -484
  49. package/models/log.tsp +0 -140
  50. package/models/messaging.tsp +0 -304
  51. package/models/otel-config.tsp +0 -455
  52. package/models/retention.tsp +0 -240
  53. package/models/rpc.tsp +0 -309
  54. package/models/search.tsp +0 -243
  55. package/models/system.tsp +0 -400
  56. package/models/test.tsp +0 -346
  57. package/models/triage.tsp +0 -113
  58. package/models/workflow.tsp +0 -396
  59. package/models/workspace.tsp +0 -435
  60. package/otel/metrics.tsp +0 -358
  61. package/tspconfig.yaml +0 -49
package/api/routes.tsp CHANGED
@@ -1,38 +1,17 @@
1
- // =============================================================================
2
- // qyl API routes
3
- // =============================================================================
4
- // RESTful API routes with proper pagination, streaming, and error handling.
5
- // =============================================================================
6
-
7
1
  import "@typespec/http";
8
- import "@typespec/rest";
9
2
  import "@typespec/openapi";
10
- import "@typespec/versioning";
11
3
 
12
4
  import "../common/types.tsp";
13
5
  import "../common/errors.tsp";
14
6
  import "../common/pagination.tsp";
15
7
  import "../otel/span.tsp";
16
8
  import "../otel/logs.tsp";
17
- import "../otel/metrics.tsp";
18
9
  import "../otel/profiles.tsp";
19
10
  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";
11
+ import "../models/health.tsp";
32
12
 
33
13
  using TypeSpec.Http;
34
14
  using TypeSpec.OpenAPI;
35
- using TypeSpec.Versioning;
36
15
 
37
16
  using Qyl.Api.Contracts.Common;
38
17
  using Qyl.Api.Contracts.Common.Errors;
@@ -42,1011 +21,179 @@ using Qyl.Api.Contracts.OTel.Logs;
42
21
  using Qyl.Api.Contracts.OTel.Enums;
43
22
  using Qyl.Api.Contracts.OTel.Profiles;
44
23
  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;
24
+ using Qyl.Api.Contracts.Health;
55
25
 
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")
26
+ @service(#{ title: "qyl API" })
66
27
  namespace Qyl.Api.Contracts;
67
28
 
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;
29
+ alias QylApiKeyHeaderAuth = ApiKeyAuth<ApiKeyLocation.header, "x-otlp-api-key">;
121
30
 
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;
31
+ @doc("Optional project scope applied to collector query operations.")
32
+ model ProjectScopeHeader {
33
+ @header("X-Qyl-Project")
34
+ projectId?: string;
133
35
  }
134
36
 
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
- // =============================================================================
37
+ // -----------------------------------------------------------------------------
38
+ // Traces
39
+ // -----------------------------------------------------------------------------
177
40
 
178
41
  @route("/api/v1/traces")
179
42
  @tag("Traces")
43
+ @useAuth(QylApiKeyHeaderAuth)
180
44
  interface TracesApi {
181
- @doc("List traces with pagination")
182
45
  @get
183
46
  list(
184
- @query @doc("Page size") @minValue(1) @maxValue(1000) limit?: int32 = 100,
185
- ): CursorPage<Trace> | NotFoundError | ValidationError | InternalServerError;
47
+ ...ProjectScopeHeader,
48
+ @query @minValue(1) @maxValue(1000) limit?: int32 = 100,
49
+ ): CursorPage<Trace> | ValidationError | UnauthorizedError | InternalServerError;
186
50
 
187
- @doc("Get a specific trace by ID")
188
51
  @get
189
52
  @route("/{traceId}")
190
- get(@path traceId: TraceId): Trace | NotFoundError | InternalServerError;
53
+ get(
54
+ ...ProjectScopeHeader,
55
+ @path traceId: TraceId,
56
+ ): Trace | NotFoundError | UnauthorizedError | InternalServerError;
191
57
 
192
- @doc("Get spans for a trace")
193
58
  @get
194
59
  @route("/{traceId}/spans")
195
60
  getSpans(
61
+ ...ProjectScopeHeader,
196
62
  @path traceId: TraceId,
197
- ): CursorPage<Span> | NotFoundError | InternalServerError;
63
+ ): CursorPage<Span> | NotFoundError | UnauthorizedError | InternalServerError;
198
64
  }
199
65
 
200
- // =============================================================================
201
- // Logs API
202
- // =============================================================================
66
+ // -----------------------------------------------------------------------------
67
+ // Logs
68
+ // -----------------------------------------------------------------------------
203
69
 
204
70
  @route("/api/v1/logs")
205
71
  @tag("Logs")
72
+ @useAuth(QylApiKeyHeaderAuth)
206
73
  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
74
  @get
231
75
  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
- // =============================================================================
76
+ ...ProjectScopeHeader,
77
+ @query sessionId?: SessionId,
78
+ @query serviceName?: string,
79
+ @query level?: string,
80
+ @query severityMin?: SeverityNumber,
81
+ @query traceId?: TraceId,
82
+ @query startTime?: utcDateTime,
83
+ @query endTime?: utcDateTime,
84
+ @query query?: string,
85
+ @query @minValue(1) @maxValue(10000) limit?: int32 = 100,
86
+ ): CursorPage<LogRecord> | ValidationError | UnauthorizedError | InternalServerError;
87
+ }
88
+
89
+ // -----------------------------------------------------------------------------
90
+ // Profiles (OpenTelemetry development signal)
91
+ // -----------------------------------------------------------------------------
330
92
 
331
93
  @route("/api/v1/profiles")
332
94
  @tag("Profiles")
95
+ @useAuth(QylApiKeyHeaderAuth)
333
96
  interface ProfilesApi {
334
- @doc("List profiles with filtering")
335
97
  @get
336
98
  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;
99
+ ...ProjectScopeHeader,
100
+ @query sessionId?: SessionId,
101
+ @query traceId?: TraceId,
102
+ @query serviceName?: string,
103
+ @query sampleType?: string,
104
+ @query @minValue(1) @maxValue(1000) limit?: int32 = 100,
105
+ ): Profile[] | ValidationError | UnauthorizedError | InternalServerError;
343
106
 
344
- @doc("Get a specific profile by ID")
345
107
  @get
346
108
  @route("/{profileId}")
347
- get(@path profileId: string): Profile | NotFoundError | InternalServerError;
109
+ get(
110
+ ...ProjectScopeHeader,
111
+ @path profileId: string,
112
+ ): Profile | NotFoundError | UnauthorizedError | InternalServerError;
348
113
 
349
- @doc("Get profiles for a trace")
350
114
  @get
351
115
  @route("/by-trace/{traceId}")
352
116
  getByTrace(
117
+ ...ProjectScopeHeader,
353
118
  @path traceId: TraceId,
354
- @query @doc("Page size") @minValue(1) @maxValue(1000) limit?: int32 = 100,
355
- ): Profile[] | InternalServerError;
119
+ @query @minValue(1) @maxValue(1000) limit?: int32 = 100,
120
+ ): Profile[] | ValidationError | UnauthorizedError | InternalServerError;
356
121
 
357
- @doc("Get profiles for a span")
358
122
  @get
359
123
  @route("/by-span/{spanId}")
360
124
  getBySpan(
125
+ ...ProjectScopeHeader,
361
126
  @path spanId: SpanId,
362
- @query @doc("Page size") @minValue(1) @maxValue(1000) limit?: int32 = 100,
363
- ): Profile[] | InternalServerError;
127
+ @query @minValue(1) @maxValue(1000) limit?: int32 = 100,
128
+ ): Profile[] | ValidationError | UnauthorizedError | InternalServerError;
364
129
  }
365
130
 
366
- // =============================================================================
367
- // Sessions API
368
- // =============================================================================
131
+ // -----------------------------------------------------------------------------
132
+ // Sessions
133
+ // -----------------------------------------------------------------------------
369
134
 
370
135
  @route("/api/v1/sessions")
371
136
  @tag("Sessions")
137
+ @useAuth(QylApiKeyHeaderAuth)
372
138
  interface SessionsApi {
373
- @doc("List sessions")
374
139
  @get
375
140
  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;
141
+ ...ProjectScopeHeader,
142
+ @query isActive?: boolean,
143
+ @query startTime?: utcDateTime,
144
+ @query endTime?: utcDateTime,
145
+ @query @minValue(1) @maxValue(1000) limit?: int32 = 100,
146
+ @query cursor?: string,
147
+ ): CursorPage<SessionEntity> | ValidationError | UnauthorizedError | InternalServerError;
382
148
 
383
- @doc("Get session by ID")
384
149
  @get
385
150
  @route("/{sessionId}")
386
- get(@path sessionId: string): SessionEntity | NotFoundError | InternalServerError;
151
+ get(
152
+ ...ProjectScopeHeader,
153
+ @path sessionId: SessionId,
154
+ ): SessionEntity | NotFoundError | UnauthorizedError | InternalServerError;
387
155
 
388
- @doc("Get session traces")
389
156
  @get
390
157
  @route("/{sessionId}/traces")
391
158
  getTraces(
392
- @path sessionId: string,
393
- ): CursorPage<Trace> | NotFoundError | InternalServerError;
159
+ ...ProjectScopeHeader,
160
+ @path sessionId: SessionId,
161
+ ): CursorPage<Trace> | NotFoundError | UnauthorizedError | InternalServerError;
394
162
 
395
- @doc("Get session statistics")
396
163
  @get
397
164
  @route("/stats")
398
165
  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;
166
+ ...ProjectScopeHeader,
167
+ @query startTime?: utcDateTime,
168
+ @query endTime?: utcDateTime,
169
+ ): SessionStats | UnauthorizedError | InternalServerError;
497
170
  }
498
171
 
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
- // =============================================================================
172
+ // -----------------------------------------------------------------------------
173
+ // Health
174
+ // -----------------------------------------------------------------------------
653
175
 
654
176
  @tag("Health")
655
177
  interface HealthApi {
656
- @doc("Liveness probe — runs live-tagged health checks.")
657
- @route("/alive")
658
178
  @get
179
+ @route("/alive")
659
180
  @operationId("health_alive")
660
181
  alive(): {
661
182
  @statusCode statusCode: 200;
183
+ @body body: HealthReport;
662
184
  } | {
663
185
  @statusCode statusCode: 503;
186
+ @body body: HealthReport;
664
187
  };
665
188
 
666
- @doc("Readiness probe — runs all ready-tagged health checks.")
667
- @route("/health")
668
189
  @get
190
+ @route("/health")
669
191
  @operationId("health_ready")
670
192
  ready(): {
671
193
  @statusCode statusCode: 200;
194
+ @body body: HealthReport;
672
195
  } | {
673
196
  @statusCode statusCode: 503;
197
+ @body body: HealthReport;
674
198
  };
675
199
  }
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
- }