From 6cd8139da0b07819ee250a1d856c5b80c5ba52c9 Mon Sep 17 00:00:00 2001 From: caballeto Date: Wed, 29 Apr 2026 09:42:11 +0200 Subject: [PATCH] feat(forensics): forensic CLI commands (timeline / trace / snapshot / evaluations / transitions) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surfaces the read-only forensic model from the API: - forensics timeline — full state-transition / triggering-evaluation / policy-snapshot timeline for a single incident - forensics trace — everything the engine recorded for one check - forensics snapshot — fetch a content-addressed policy snapshot - forensics evaluations --monitor-id [--rule-type --region --only-matched --from --to --page --size] — paginated rule evaluations - forensics transitions --monitor-id [--from --to --page --size] — paginated state transitions All commands honour --output table|json|yaml. Schemas regenerated from the latest monitoring-api.json so api.generated.ts and api-zod.generated.ts pick up the new IncidentTimelineDto / CheckTraceDto / PolicySnapshotDto / RuleEvaluationDto / IncidentStateTransitionDto types. Made-with: Cursor --- docs/openapi/monitoring-api.json | 1308 +++++++++++++++++++++++-- src/commands/forensics/evaluations.ts | 64 ++ src/commands/forensics/snapshot.ts | 45 + src/commands/forensics/timeline.ts | 56 ++ src/commands/forensics/trace.ts | 49 + src/commands/forensics/transitions.ts | 58 ++ src/lib/api-zod.generated.ts | 111 ++- src/lib/api.generated.ts | 777 ++++++++++++++- 8 files changed, 2362 insertions(+), 106 deletions(-) create mode 100644 src/commands/forensics/evaluations.ts create mode 100644 src/commands/forensics/snapshot.ts create mode 100644 src/commands/forensics/timeline.ts create mode 100644 src/commands/forensics/trace.ts create mode 100644 src/commands/forensics/transitions.ts diff --git a/docs/openapi/monitoring-api.json b/docs/openapi/monitoring-api.json index d25734a..0ce2ea6 100644 --- a/docs/openapi/monitoring-api.json +++ b/docs/openapi/monitoring-api.json @@ -58,6 +58,10 @@ "name": "Environments", "description": "Variable namespace management for monitors" }, + { + "name": "Forensics", + "description": "Detection engine event-sourced history (policy snapshots, rule evaluations, state transitions)" + }, { "name": "Heartbeat", "description": "Public ping endpoint for heartbeat monitors" @@ -2900,51 +2904,659 @@ } } }, - "502": { - "description": "Bad gateway — an upstream provider returned an error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } + "502": { + "description": "Bad gateway — an upstream provider returned an error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "503": { + "description": "Service unavailable — try again shortly", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "Environments" + ], + "summary": "Create environment", + "operationId": "create_13", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateEnvironmentRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Created", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/SingleValueResponseEnvironmentDto" + } + } + } + }, + "400": { + "description": "Bad request — the payload failed validation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid credentials", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden — the actor lacks permission for this resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not found — the requested resource does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "Conflict — the request collides with current resource state", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal server error — see the message field for details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "502": { + "description": "Bad gateway — an upstream provider returned an error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "503": { + "description": "Service unavailable — try again shortly", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/environments/{slug}": { + "get": { + "tags": [ + "Environments" + ], + "summary": "Get environment by slug", + "operationId": "get_7", + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/SingleValueResponseEnvironmentDto" + } + } + } + }, + "400": { + "description": "Bad request — the payload failed validation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid credentials", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden — the actor lacks permission for this resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not found — the requested resource does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "Conflict — the request collides with current resource state", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal server error — see the message field for details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "502": { + "description": "Bad gateway — an upstream provider returned an error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "503": { + "description": "Service unavailable — try again shortly", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "put": { + "tags": [ + "Environments" + ], + "summary": "Update environment", + "operationId": "update_13", + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateEnvironmentRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/SingleValueResponseEnvironmentDto" + } + } + } + }, + "400": { + "description": "Bad request — the payload failed validation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid credentials", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden — the actor lacks permission for this resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not found — the requested resource does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "Conflict — the request collides with current resource state", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal server error — see the message field for details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "502": { + "description": "Bad gateway — an upstream provider returned an error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "503": { + "description": "Service unavailable — try again shortly", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Environments" + ], + "summary": "Delete environment", + "operationId": "delete_9", + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad request — the payload failed validation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid credentials", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden — the actor lacks permission for this resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not found — the requested resource does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "Conflict — the request collides with current resource state", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal server error — see the message field for details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "502": { + "description": "Bad gateway — an upstream provider returned an error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "503": { + "description": "Service unavailable — try again shortly", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/forensics/incidents/{id}/timeline": { + "get": { + "tags": [ + "Forensics" + ], + "summary": "Full forensic timeline for an incident", + "description": "Returns every state-machine transition for this incident plus the rule evaluations that caused each transition, plus the policy snapshot that triggered confirmation. Correlate evaluations to transitions via evaluation.triggeringTransitionId == transition.id.", + "operationId": "getTimeline", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/SingleValueResponseIncidentTimelineDto" + } + } + } + }, + "400": { + "description": "Bad request — the payload failed validation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid credentials", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden — the actor lacks permission for this resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not found — the requested resource does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "Conflict — the request collides with current resource state", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal server error — see the message field for details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "502": { + "description": "Bad gateway — an upstream provider returned an error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "503": { + "description": "Service unavailable — try again shortly", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/forensics/monitors/{id}/rule-evaluations": { + "get": { + "tags": [ + "Forensics" + ], + "summary": "Paged list of rule evaluations for a monitor", + "description": "Filter by ruleType (e.g. consecutive_failures), region, onlyMatched=true to narrow to firing evaluations, and occurredAt window.", + "operationId": "listMonitorRuleEvaluations", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "ruleType", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "region", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "onlyMatched", + "in": "query", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "from", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "to", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" } }, - "503": { - "description": "Service unavailable — try again shortly", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } + { + "name": "pageable", + "in": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/Pageable" } } - } - }, - "post": { - "tags": [ - "Environments" ], - "summary": "Create environment", - "operationId": "create_13", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateEnvironmentRequest" - } - } - }, - "required": true - }, "responses": { - "201": { - "description": "Created", + "200": { + "description": "OK", "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/SingleValueResponseEnvironmentDto" + "$ref": "#/components/schemas/TableValueResultRuleEvaluationDto" } } } @@ -3032,20 +3644,47 @@ } } }, - "/api/v1/environments/{slug}": { + "/api/v1/forensics/monitors/{id}/transitions": { "get": { "tags": [ - "Environments" + "Forensics" ], - "summary": "Get environment by slug", - "operationId": "get_7", + "summary": "Paged list of state transitions for a monitor (optionally time-bounded)", + "operationId": "listMonitorTransitions", "parameters": [ { - "name": "slug", + "name": "id", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" + } + }, + { + "name": "from", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "to", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "pageable", + "in": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/Pageable" } } ], @@ -3055,7 +3694,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/SingleValueResponseEnvironmentDto" + "$ref": "#/components/schemas/TableValueResultIncidentStateTransitionDto" } } } @@ -3141,16 +3780,19 @@ } } } - }, - "put": { + } + }, + "/api/v1/forensics/policy-snapshots/{hashHex}": { + "get": { "tags": [ - "Environments" + "Forensics" ], - "summary": "Update environment", - "operationId": "update_13", + "summary": "Fetch a policy snapshot by its content hash", + "description": "Hash is SHA-256 over canonical policy JSON, hex-encoded. Access is gated: caller's org must have evaluated against this hash at least once.", + "operationId": "getPolicySnapshot", "parameters": [ { - "name": "slug", + "name": "hashHex", "in": "path", "required": true, "schema": { @@ -3158,23 +3800,13 @@ } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateEnvironmentRequest" - } - } - }, - "required": true - }, "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/SingleValueResponseEnvironmentDto" + "$ref": "#/components/schemas/SingleValueResponsePolicySnapshotDto" } } } @@ -3260,26 +3892,37 @@ } } } - }, - "delete": { + } + }, + "/api/v1/forensics/traces/{checkId}": { + "get": { "tags": [ - "Environments" + "Forensics" ], - "summary": "Delete environment", - "operationId": "delete_9", + "summary": "Replay a single check execution", + "description": "Returns every rule evaluation and state transition emitted for this scheduler-minted check_id (V92), plus the policy snapshot that governed them.", + "operationId": "getTrace", "parameters": [ { - "name": "slug", + "name": "checkId", "in": "path", "required": true, "schema": { - "type": "string" + "type": "string", + "format": "uuid" } } ], "responses": { - "204": { - "description": "No Content" + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/SingleValueResponseCheckTraceDto" + } + } + } }, "400": { "description": "Bad request — the payload failed validation", @@ -21502,14 +22145,12 @@ "nullable": true }, "metadata": { - "type": "object", - "additionalProperties": { - "type": "object", - "description": "Additional context about the action", - "nullable": true - }, - "description": "Additional context about the action", - "nullable": true + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/AuditMetadata" + } + ] }, "createdAt": { "type": "string", @@ -21518,6 +22159,21 @@ } } }, + "AuditMetadata": { + "description": "Typed metadata payload attached to an audit event; null for actions that carry no extra context.", + "nullable": true, + "discriminator": { + "propertyName": "kind", + "mapping": { + "member_role_changed": "#/components/schemas/MemberRoleChangedMetadata" + } + }, + "oneOf": [ + { + "$ref": "#/components/schemas/MemberRoleChangedMetadata" + } + ] + }, "AuthMeResponse": { "required": [ "key", @@ -21938,6 +22594,43 @@ }, "description": "A single check result from a monitor run" }, + "CheckTraceDto": { + "required": [ + "checkId", + "evaluations", + "transitions" + ], + "type": "object", + "properties": { + "checkId": { + "type": "string", + "description": "The check execution ID this trace is keyed by", + "format": "uuid" + }, + "evaluations": { + "type": "array", + "description": "All rule evaluations that ran for this check", + "items": { + "$ref": "#/components/schemas/RuleEvaluationDto" + } + }, + "transitions": { + "type": "array", + "description": "State-machine transitions this check caused (may be empty if nothing fired)", + "items": { + "$ref": "#/components/schemas/IncidentStateTransitionDto" + } + }, + "policySnapshot": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/PolicySnapshotDto" + } + ] + } + } + }, "CheckTypeDetailsDto": { "description": "Check-type-specific details — polymorphic by check_type discriminator", "discriminator": { @@ -24935,6 +25628,28 @@ "type": "string", "description": "Name of the resource group; populated on list responses. Omitted from JSON (undefined to SDKs) on detail responses, treat missing as null.", "nullable": true + }, + "triggeringCheckId": { + "type": "string", + "description": "Scheduler-minted check execution ID whose result confirmed this incident; joins to check_results, rule_evaluations, and incident_state_transitions", + "format": "uuid", + "nullable": true + }, + "triggeredByRuleSnapshotHashHex": { + "type": "string", + "description": "Hex SHA-256 of the canonical policy snapshot that fired; combined with triggeredByRuleIndex points to the exact TriggerRule", + "nullable": true + }, + "triggeredByRuleIndex": { + "type": "integer", + "description": "Index of the fired rule inside the policy's trigger_rules array", + "format": "int32", + "nullable": true + }, + "engineVersion": { + "type": "string", + "description": "Detection engine semver that evaluated the rule", + "nullable": true } }, "description": "Incident triggered by a monitor check failure or manual creation" @@ -25108,42 +25823,160 @@ "properties": { "id": { "type": "string", - "description": "Internal incident ID — UUID for status-page incidents, service incident UUID for catalog", - "format": "uuid" + "description": "Internal incident ID — UUID for status-page incidents, service incident UUID for catalog", + "format": "uuid" + }, + "title": { + "type": "string", + "description": "Incident title at the time of the overlap" + }, + "impact": { + "type": "string", + "description": "Incident impact level (e.g. minor, major, critical for catalog; NONE/MINOR/MAJOR/CRITICAL for status pages)" + } + }, + "description": "Lightweight reference to an incident overlapping a given uptime day" + }, + "IncidentsSummaryDto": { + "type": "object", + "properties": { + "active": { + "type": "integer", + "format": "int64" + }, + "resolvedToday": { + "type": "integer", + "format": "int64" + }, + "mttr30d": { + "type": "number", + "format": "double", + "nullable": true + } + }, + "description": "Incident summary counters", + "required": [ + "active", + "resolvedToday" + ] + }, + "IncidentStateTransitionDto": { + "required": [ + "affectedRegions", + "checkId", + "engineVersion", + "fromStatus", + "id", + "monitorId", + "occurredAt", + "policySnapshotHashHex", + "reason", + "toStatus", + "triggeringEvaluationIds" + ], + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Forensic row UUID", + "format": "uuid" + }, + "occurredAt": { + "type": "string", + "description": "When the state transition occurred", + "format": "date-time" + }, + "monitorId": { + "type": "string", + "description": "Monitor this transition pertains to", + "format": "uuid" + }, + "incidentId": { + "type": "string", + "description": "Incident this transition belongs to; null for pre-incident (auto-cleared) transitions", + "format": "uuid", + "nullable": true + }, + "fromStatus": { + "minLength": 1, + "type": "string", + "description": "Previous status (WATCHING | TRIGGERED | CONFIRMED | RESOLVED)" + }, + "toStatus": { + "minLength": 1, + "type": "string", + "description": "New status (WATCHING | TRIGGERED | CONFIRMED | RESOLVED)" + }, + "reason": { + "minLength": 1, + "type": "string", + "description": "Why the transition fired (rule_matched | confirmation_met | auto_cleared_by_timeout | recovery_met | reopened | manually_resolved | policy_changed)" }, - "title": { + "triggeringEvaluationIds": { + "type": "array", + "description": "rule_evaluation ids that caused this transition (may be empty for timeout-driven edges)", + "items": { + "type": "string", + "description": "rule_evaluation ids that caused this transition (may be empty for timeout-driven edges)", + "format": "uuid" + } + }, + "affectedRegions": { + "type": "array", + "description": "Regions whose evaluations contributed to this transition", + "items": { + "type": "string", + "description": "Regions whose evaluations contributed to this transition" + } + }, + "policySnapshotHashHex": { + "minLength": 1, "type": "string", - "description": "Incident title at the time of the overlap" + "description": "Hex-encoded hash of the policy snapshot that governed this transition" }, - "impact": { + "engineVersion": { + "minLength": 1, "type": "string", - "description": "Incident impact level (e.g. minor, major, critical for catalog; NONE/MINOR/MAJOR/CRITICAL for status pages)" + "description": "Detection engine version that emitted this transition" + }, + "checkId": { + "type": "string", + "description": "Scheduler-minted check execution ID (V92) of the triggering result", + "format": "uuid" } }, - "description": "Lightweight reference to an incident overlapping a given uptime day" + "description": "State-machine transitions this check caused (may be empty if nothing fired)" }, - "IncidentsSummaryDto": { + "IncidentTimelineDto": { + "required": [ + "transitions", + "triggeringEvaluations" + ], "type": "object", "properties": { - "active": { - "type": "integer", - "format": "int64" + "transitions": { + "type": "array", + "description": "State-machine transitions in chronological order", + "items": { + "$ref": "#/components/schemas/IncidentStateTransitionDto" + } }, - "resolvedToday": { - "type": "integer", - "format": "int64" + "triggeringEvaluations": { + "type": "array", + "description": "Rule evaluations that caused any of the transitions above. Correlate via evaluation.triggeringTransitionId == transition.id", + "items": { + "$ref": "#/components/schemas/RuleEvaluationDto" + } }, - "mttr30d": { - "type": "number", - "format": "double", - "nullable": true + "policySnapshot": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/PolicySnapshotDto" + } + ] } - }, - "description": "Incident summary counters", - "required": [ - "active", - "resolvedToday" - ] + } }, "IncidentUpdateDto": { "required": [ @@ -25965,6 +26798,41 @@ }, "description": "Organization member with role and status" }, + "MemberRoleChangedMetadata": { + "required": [ + "kind", + "oldRole", + "newRole" + ], + "type": "object", + "description": "Role transition recorded when an organization member's role changes.", + "properties": { + "kind": { + "type": "string", + "enum": [ + "member_role_changed" + ] + }, + "oldRole": { + "type": "string", + "description": "Role the member held before the change", + "enum": [ + "OWNER", + "ADMIN", + "MEMBER" + ] + }, + "newRole": { + "type": "string", + "description": "Role the member holds after the change", + "enum": [ + "OWNER", + "ADMIN", + "MEMBER" + ] + } + } + }, "MonitorAssertionDto": { "required": [ "assertionType", @@ -27096,6 +27964,48 @@ }, "description": "Billing plan and entitlement state" }, + "PolicySnapshotDto": { + "required": [ + "engineVersion", + "firstSeenAt", + "hashHex", + "lastSeenAt", + "policy" + ], + "type": "object", + "properties": { + "hashHex": { + "minLength": 1, + "type": "string", + "description": "Hex-encoded SHA-256 of the canonical policy JSON" + }, + "policy": { + "type": "object", + "additionalProperties": { + "type": "object", + "description": "Canonical policy document (snake_case, sorted keys)" + }, + "description": "Canonical policy document (snake_case, sorted keys)" + }, + "engineVersion": { + "minLength": 1, + "type": "string", + "description": "Detection engine version that observed this policy" + }, + "firstSeenAt": { + "type": "string", + "description": "First time the detection engine evaluated against this policy bytes", + "format": "date-time" + }, + "lastSeenAt": { + "type": "string", + "description": "Most recent time the engine evaluated against this policy bytes", + "format": "date-time" + } + }, + "description": "Policy snapshot used during this check (all evaluations of a single check are against one policy)", + "nullable": true + }, "PollChartBucketDto": { "required": [ "bucket", @@ -27860,6 +28770,105 @@ }, "description": "Default retry strategy for member monitors; null clears" }, + "RuleEvaluationDto": { + "required": [ + "checkId", + "engineVersion", + "evaluationDetails", + "id", + "inputResultIds", + "monitorId", + "occurredAt", + "policySnapshotHashHex", + "region", + "ruleScope", + "ruleType", + "ruleIndex", + "outputMatched" + ], + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Forensic row UUID", + "format": "uuid" + }, + "occurredAt": { + "type": "string", + "description": "When the evaluation ran", + "format": "date-time" + }, + "monitorId": { + "type": "string", + "description": "Monitor that produced the input check result", + "format": "uuid" + }, + "region": { + "minLength": 1, + "type": "string", + "description": "Probe region of the input check result" + }, + "policySnapshotHashHex": { + "minLength": 1, + "type": "string", + "description": "Hex-encoded hash of the policy snapshot this rule came from" + }, + "ruleIndex": { + "type": "integer", + "description": "Index into the policy's triggerRules array (0-based)", + "format": "int32" + }, + "ruleType": { + "minLength": 1, + "type": "string", + "description": "Rule type (e.g. consecutive_failures, failures_in_window)" + }, + "ruleScope": { + "minLength": 1, + "type": "string", + "description": "Rule scope (per_region | multi_region)" + }, + "inputResultIds": { + "minItems": 1, + "type": "array", + "description": "check_results IDs that were inputs to this evaluation (newest first)", + "items": { + "type": "string", + "description": "check_results IDs that were inputs to this evaluation (newest first)", + "format": "uuid" + } + }, + "outputMatched": { + "type": "boolean", + "description": "Whether the rule fired on this evaluation" + }, + "evaluationDetails": { + "type": "object", + "additionalProperties": { + "type": "object", + "description": "Structured details (e.g. failure counts, response-time aggregates)" + }, + "description": "Structured details (e.g. failure counts, response-time aggregates)" + }, + "engineVersion": { + "minLength": 1, + "type": "string", + "description": "Detection engine version that ran this evaluation" + }, + "checkId": { + "type": "string", + "description": "Scheduler-minted check execution ID (V92) — the causal chain identifier", + "format": "uuid" + }, + "triggeringTransitionId": { + "type": "string", + "description": "If this evaluation caused a state transition, points to that transition's id", + "format": "uuid", + "nullable": true + } + }, + "description": "All rule evaluations that ran for this check" + }, "ScheduledMaintenanceDto": { "required": [ "affectedComponents", @@ -28981,6 +29990,17 @@ } } }, + "SingleValueResponseCheckTraceDto": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/CheckTraceDto" + } + } + }, "SingleValueResponseDashboardOverviewDto": { "required": [ "data" @@ -29058,6 +30078,17 @@ } } }, + "SingleValueResponseIncidentTimelineDto": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/IncidentTimelineDto" + } + } + }, "SingleValueResponseInviteDto": { "required": [ "data" @@ -29195,6 +30226,17 @@ } } }, + "SingleValueResponsePolicySnapshotDto": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/PolicySnapshotDto" + } + } + }, "SingleValueResponseResourceGroupDto": { "required": [ "data" @@ -30525,6 +31567,38 @@ } } }, + "TableValueResultIncidentStateTransitionDto": { + "required": [ + "data", + "hasNext", + "hasPrev" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IncidentStateTransitionDto" + } + }, + "hasNext": { + "type": "boolean" + }, + "hasPrev": { + "type": "boolean" + }, + "totalElements": { + "type": "integer", + "format": "int64", + "nullable": true + }, + "totalPages": { + "type": "integer", + "format": "int32", + "nullable": true + } + } + }, "TableValueResultIntegrationDto": { "required": [ "data", @@ -30845,6 +31919,38 @@ } } }, + "TableValueResultRuleEvaluationDto": { + "required": [ + "data", + "hasNext", + "hasPrev" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RuleEvaluationDto" + } + }, + "hasNext": { + "type": "boolean" + }, + "hasPrev": { + "type": "boolean" + }, + "totalElements": { + "type": "integer", + "format": "int64", + "nullable": true + }, + "totalPages": { + "type": "integer", + "format": "int32", + "nullable": true + } + } + }, "TableValueResultScheduledMaintenanceDto": { "required": [ "data", diff --git a/src/commands/forensics/evaluations.ts b/src/commands/forensics/evaluations.ts new file mode 100644 index 0000000..35e7a71 --- /dev/null +++ b/src/commands/forensics/evaluations.ts @@ -0,0 +1,64 @@ +import {Command, Flags} from '@oclif/core' +import {apiGetPage} from '../../lib/api-client.js' +import {schemas as apiSchemas} from '../../lib/api-zod.generated.js' +import {buildClient, display, globalFlags} from '../../lib/base-command.js' +import type {ColumnDef} from '../../lib/output.js' + +interface EvalRow { + occurredAt: string + ruleType: string + region: string + outputMatched: boolean + checkId: string + policySnapshotHashHex: string +} + +const COLUMNS: ColumnDef[] = [ + {header: 'WHEN', get: (r) => r.occurredAt}, + {header: 'RULE', get: (r) => r.ruleType}, + {header: 'REGION', get: (r) => r.region}, + {header: 'MATCHED', get: (r) => (r.outputMatched ? 'yes' : 'no')}, + {header: 'CHECK', get: (r) => r.checkId.slice(0, 8)}, + {header: 'POLICY', get: (r) => r.policySnapshotHashHex.slice(0, 12)}, +] + +export default class ForensicsEvaluations extends Command { + static description = 'List rule evaluations produced for a monitor (paginated)' + static examples = [ + '<%= config.bin %> forensics evaluations --monitor-id 5f4…', + '<%= config.bin %> forensics evaluations --monitor-id 5f4… --only-matched', + '<%= config.bin %> forensics evaluations --monitor-id 5f4… --rule-type consecutive_failures --region us-east', + ] + static flags = { + ...globalFlags, + 'monitor-id': Flags.string({description: 'Monitor ID (UUID)', required: true}), + 'rule-type': Flags.string({description: 'Filter by rule type'}), + region: Flags.string({description: 'Filter by probe region'}), + 'only-matched': Flags.boolean({description: 'Return only evaluations that fired'}), + from: Flags.string({description: 'ISO-8601 lower bound (occurredAt >= from)'}), + to: Flags.string({description: 'ISO-8601 upper bound (occurredAt < to)'}), + page: Flags.integer({description: 'Page index (0-based)', default: 0}), + size: Flags.integer({description: 'Page size', default: 50}), + } + + async run() { + const {flags} = await this.parse(ForensicsEvaluations) + const client = buildClient(flags) + + const params: Record = {page: flags.page, size: flags.size} + if (flags['rule-type']) params.ruleType = flags['rule-type'] + if (flags.region) params.region = flags.region + if (flags['only-matched']) params.onlyMatched = true + if (flags.from) params.from = flags.from + if (flags.to) params.to = flags.to + + const result = await apiGetPage( + client, + `/api/v1/forensics/monitors/${flags['monitor-id']}/rule-evaluations`, + apiSchemas.RuleEvaluationDto, + params, + ) + + display(this, result.data, flags.output, COLUMNS as ColumnDef[]) + } +} diff --git a/src/commands/forensics/snapshot.ts b/src/commands/forensics/snapshot.ts new file mode 100644 index 0000000..80e7890 --- /dev/null +++ b/src/commands/forensics/snapshot.ts @@ -0,0 +1,45 @@ +import {Args, Command} from '@oclif/core' +import {apiGetSingle} from '../../lib/api-client.js' +import {schemas as apiSchemas} from '../../lib/api-zod.generated.js' +import {buildClient, globalFlags} from '../../lib/base-command.js' +import {formatOutput, OutputFormat} from '../../lib/output.js' + +export default class ForensicsSnapshot extends Command { + static description = 'Fetch a policy snapshot by its content-addressed SHA-256 hash' + static examples = ['<%= config.bin %> forensics snapshot 5a1f…'] + static flags = {...globalFlags} + static args = { + hash: Args.string({description: 'Snapshot hash (lowercase hex, SHA-256)', required: true}), + } + + async run() { + const {args, flags} = await this.parse(ForensicsSnapshot) + const client = buildClient(flags) + const snapshot = await apiGetSingle( + client, + `/api/v1/forensics/policy-snapshots/${args.hash}`, + apiSchemas.PolicySnapshotDto, + ) + + const format = flags.output as OutputFormat + if (format === 'json' || format === 'yaml') { + this.log(formatOutput(snapshot, format)) + return + } + + this.log('') + this.log(` Hash: ${snapshot.hashHex}`) + this.log(` Engine: ${snapshot.engineVersion}`) + this.log(` First seen at: ${snapshot.firstSeenAt}`) + this.log(` Last seen at: ${snapshot.lastSeenAt}`) + this.log('') + this.log(' Policy') + this.log( + JSON.stringify(snapshot.policy, null, 2) + .split('\n') + .map((line) => ` ${line}`) + .join('\n'), + ) + this.log('') + } +} diff --git a/src/commands/forensics/timeline.ts b/src/commands/forensics/timeline.ts new file mode 100644 index 0000000..aa33e31 --- /dev/null +++ b/src/commands/forensics/timeline.ts @@ -0,0 +1,56 @@ +import {Args, Command} from '@oclif/core' +import {apiGetSingle} from '../../lib/api-client.js' +import {schemas as apiSchemas} from '../../lib/api-zod.generated.js' +import {buildClient, globalFlags} from '../../lib/base-command.js' +import {formatOutput, OutputFormat} from '../../lib/output.js' + +export default class ForensicsTimeline extends Command { + static description = "Show the full forensic timeline for an incident (state transitions, triggering evaluations, active policy snapshot)" + static examples = ['<%= config.bin %> forensics timeline 5f4…'] + static flags = {...globalFlags} + static args = { + id: Args.string({description: 'Incident ID (UUID)', required: true}), + } + + async run() { + const {args, flags} = await this.parse(ForensicsTimeline) + const client = buildClient(flags) + const timeline = await apiGetSingle( + client, + `/api/v1/forensics/incidents/${args.id}/timeline`, + apiSchemas.IncidentTimelineDto, + ) + + const format = flags.output as OutputFormat + if (format === 'json' || format === 'yaml') { + this.log(formatOutput(timeline, format)) + return + } + + this.log('') + this.log(` Incident ${args.id}`) + this.log('') + this.log(' Transitions') + for (const t of timeline.transitions) { + const evalIds = t.triggeringEvaluationIds?.length + ? ` evals=${t.triggeringEvaluationIds.length}` + : '' + this.log( + ` ${t.occurredAt} ${t.fromStatus} → ${t.toStatus} reason=${t.reason} check=${t.checkId}${evalIds}`, + ) + } + this.log('') + this.log(` Triggering evaluations (${timeline.triggeringEvaluations.length})`) + for (const e of timeline.triggeringEvaluations) { + const matched = e.outputMatched ? 'MATCH' : 'miss ' + this.log( + ` ${e.occurredAt} ${matched} rule=${e.ruleType} region=${e.region} check=${e.checkId}`, + ) + } + if (timeline.policySnapshot) { + this.log('') + this.log(` Policy snapshot: ${timeline.policySnapshot.hashHex.slice(0, 16)}…`) + } + this.log('') + } +} diff --git a/src/commands/forensics/trace.ts b/src/commands/forensics/trace.ts new file mode 100644 index 0000000..87da767 --- /dev/null +++ b/src/commands/forensics/trace.ts @@ -0,0 +1,49 @@ +import {Args, Command} from '@oclif/core' +import {apiGetSingle} from '../../lib/api-client.js' +import {schemas as apiSchemas} from '../../lib/api-zod.generated.js' +import {buildClient, globalFlags} from '../../lib/base-command.js' +import {formatOutput, OutputFormat} from '../../lib/output.js' + +export default class ForensicsTrace extends Command { + static description = 'Show everything the detection engine recorded for a single check execution' + static examples = ['<%= config.bin %> forensics trace a1b2c3d4-…'] + static flags = {...globalFlags} + static args = { + 'check-id': Args.string({description: 'Check execution ID (UUID, minted by the scheduler)', required: true}), + } + + async run() { + const {args, flags} = await this.parse(ForensicsTrace) + const client = buildClient(flags) + const trace = await apiGetSingle( + client, + `/api/v1/forensics/traces/${args['check-id']}`, + apiSchemas.CheckTraceDto, + ) + + const format = flags.output as OutputFormat + if (format === 'json' || format === 'yaml') { + this.log(formatOutput(trace, format)) + return + } + + this.log('') + this.log(` Check ${trace.checkId}`) + this.log('') + this.log(` Evaluations (${trace.evaluations.length})`) + for (const e of trace.evaluations) { + const matched = e.outputMatched ? 'MATCH' : 'miss ' + this.log(` ${e.occurredAt} ${matched} rule=${e.ruleType} region=${e.region}`) + } + this.log('') + this.log(` Transitions (${trace.transitions.length})`) + for (const t of trace.transitions) { + this.log(` ${t.occurredAt} ${t.fromStatus} → ${t.toStatus} reason=${t.reason}`) + } + if (trace.policySnapshot) { + this.log('') + this.log(` Policy snapshot: ${trace.policySnapshot.hashHex.slice(0, 16)}…`) + } + this.log('') + } +} diff --git a/src/commands/forensics/transitions.ts b/src/commands/forensics/transitions.ts new file mode 100644 index 0000000..4bb15f5 --- /dev/null +++ b/src/commands/forensics/transitions.ts @@ -0,0 +1,58 @@ +import {Command, Flags} from '@oclif/core' +import {apiGetPage} from '../../lib/api-client.js' +import {schemas as apiSchemas} from '../../lib/api-zod.generated.js' +import {buildClient, display, globalFlags} from '../../lib/base-command.js' +import type {ColumnDef} from '../../lib/output.js' + +interface TransitionRow { + occurredAt: string + fromStatus: string + toStatus: string + reason: string + incidentId?: string | null + checkId: string + policySnapshotHashHex: string +} + +const COLUMNS: ColumnDef[] = [ + {header: 'WHEN', get: (r) => r.occurredAt}, + {header: 'FROM → TO', get: (r) => `${r.fromStatus} → ${r.toStatus}`}, + {header: 'REASON', get: (r) => r.reason}, + {header: 'INCIDENT', get: (r) => (r.incidentId ? r.incidentId.slice(0, 8) : '–')}, + {header: 'CHECK', get: (r) => r.checkId.slice(0, 8)}, + {header: 'POLICY', get: (r) => r.policySnapshotHashHex.slice(0, 12)}, +] + +export default class ForensicsTransitions extends Command { + static description = 'List state transitions recorded for a monitor (paginated)' + static examples = [ + '<%= config.bin %> forensics transitions --monitor-id 5f4…', + '<%= config.bin %> forensics transitions --monitor-id 5f4… --from 2026-01-01T00:00:00Z', + ] + static flags = { + ...globalFlags, + 'monitor-id': Flags.string({description: 'Monitor ID (UUID)', required: true}), + from: Flags.string({description: 'ISO-8601 lower bound (occurredAt >= from)'}), + to: Flags.string({description: 'ISO-8601 upper bound (occurredAt < to)'}), + page: Flags.integer({description: 'Page index (0-based)', default: 0}), + size: Flags.integer({description: 'Page size', default: 50}), + } + + async run() { + const {flags} = await this.parse(ForensicsTransitions) + const client = buildClient(flags) + + const params: Record = {page: flags.page, size: flags.size} + if (flags.from) params.from = flags.from + if (flags.to) params.to = flags.to + + const result = await apiGetPage( + client, + `/api/v1/forensics/monitors/${flags['monitor-id']}/transitions`, + apiSchemas.IncidentStateTransitionDto, + params, + ) + + display(this, result.data, flags.output, COLUMNS as ColumnDef[]) + } +} diff --git a/src/lib/api-zod.generated.ts b/src/lib/api-zod.generated.ts index bcc5c83..a7f5d68 100644 --- a/src/lib/api-zod.generated.ts +++ b/src/lib/api-zod.generated.ts @@ -1403,6 +1403,14 @@ const AssertionTestResultDto = z actual: z.string().nullish(), }) .strict(); +const MemberRoleChangedMetadata = z + .object({ + kind: z.literal("member_role_changed"), + oldRole: z.enum(["OWNER", "ADMIN", "MEMBER"]), + newRole: z.enum(["OWNER", "ADMIN", "MEMBER"]), + }) + .strict(); +const AuditMetadata = MemberRoleChangedMetadata; const AuditEventDto = z .object({ id: z.number().int(), @@ -1412,7 +1420,7 @@ const AuditEventDto = z resourceType: z.string().nullish(), resourceId: z.string().nullish(), resourceName: z.string().nullish(), - metadata: z.record(z.object({}).partial().strict().nullable()).nullish(), + metadata: AuditMetadata.nullish(), createdAt: z.string().datetime({ offset: true }), }) .strict(); @@ -1618,6 +1626,57 @@ const CheckResultDto = z checkId: z.string().uuid().nullish(), }) .strict(); +const RuleEvaluationDto = z + .object({ + id: z.string().uuid(), + occurredAt: z.string().datetime({ offset: true }), + monitorId: z.string().uuid(), + region: z.string().min(1), + policySnapshotHashHex: z.string().min(1), + ruleIndex: z.number().int(), + ruleType: z.string().min(1), + ruleScope: z.string().min(1), + inputResultIds: z.array(z.string().uuid()).min(1), + outputMatched: z.boolean(), + evaluationDetails: z.record(z.object({}).partial().strict()), + engineVersion: z.string().min(1), + checkId: z.string().uuid(), + triggeringTransitionId: z.string().uuid().nullish(), + }) + .strict(); +const IncidentStateTransitionDto = z + .object({ + id: z.string().uuid(), + occurredAt: z.string().datetime({ offset: true }), + monitorId: z.string().uuid(), + incidentId: z.string().uuid().nullish(), + fromStatus: z.string().min(1), + toStatus: z.string().min(1), + reason: z.string().min(1), + triggeringEvaluationIds: z.array(z.string().uuid()), + affectedRegions: z.array(z.string()), + policySnapshotHashHex: z.string().min(1), + engineVersion: z.string().min(1), + checkId: z.string().uuid(), + }) + .strict(); +const PolicySnapshotDto = z + .object({ + hashHex: z.string().min(1), + policy: z.record(z.object({}).partial().strict()), + engineVersion: z.string().min(1), + firstSeenAt: z.string().datetime({ offset: true }), + lastSeenAt: z.string().datetime({ offset: true }), + }) + .strict(); +const CheckTraceDto = z + .object({ + checkId: z.string().uuid(), + evaluations: z.array(RuleEvaluationDto), + transitions: z.array(IncidentStateTransitionDto), + policySnapshot: PolicySnapshotDto.nullish(), + }) + .strict(); const ComponentImpact = z .object({ componentId: z.string().uuid(), @@ -1841,6 +1900,10 @@ const IncidentDto = z monitorType: z.string().nullish(), resourceGroupId: z.string().uuid().nullish(), resourceGroupName: z.string().nullish(), + triggeringCheckId: z.string().uuid().nullish(), + triggeredByRuleSnapshotHashHex: z.string().nullish(), + triggeredByRuleIndex: z.number().int().nullish(), + engineVersion: z.string().nullish(), }) .strict(); const IncidentUpdateDto = z @@ -1918,6 +1981,13 @@ const IncidentPolicyDto = z checkFrequencySeconds: z.number().int().nullish(), }) .strict(); +const IncidentTimelineDto = z + .object({ + transitions: z.array(IncidentStateTransitionDto), + triggeringEvaluations: z.array(RuleEvaluationDto), + policySnapshot: PolicySnapshotDto.nullish(), + }) + .strict(); const IntegrationFieldDto = z .object({ key: z.string(), @@ -2554,6 +2624,9 @@ const SingleValueResponseBatchComponentUptimeDto = z const SingleValueResponseBulkMonitorActionResult = z .object({ data: BulkMonitorActionResult }) .strict(); +const SingleValueResponseCheckTraceDto = z + .object({ data: CheckTraceDto }) + .strict(); const SingleValueResponseDashboardOverviewDto = z .object({ data: DashboardOverviewDto }) .strict(); @@ -2575,6 +2648,9 @@ const SingleValueResponseIncidentDetailDto = z const SingleValueResponseIncidentPolicyDto = z .object({ data: IncidentPolicyDto }) .strict(); +const SingleValueResponseIncidentTimelineDto = z + .object({ data: IncidentTimelineDto }) + .strict(); const SingleValueResponseInviteDto = z.object({ data: InviteDto }).strict(); const SingleValueResponseListUUID = z .object({ data: z.array(z.string().uuid()) }) @@ -2605,6 +2681,9 @@ const SingleValueResponseNotificationPolicyDto = z const SingleValueResponseOrganizationDto = z .object({ data: OrganizationDto }) .strict(); +const SingleValueResponsePolicySnapshotDto = z + .object({ data: PolicySnapshotDto.nullable() }) + .strict(); const SingleValueResponseResourceGroupDto = z .object({ data: ResourceGroupDto }) .strict(); @@ -2961,6 +3040,15 @@ const TableValueResultIncidentDto = z totalPages: z.number().int().nullish(), }) .strict(); +const TableValueResultIncidentStateTransitionDto = z + .object({ + data: z.array(IncidentStateTransitionDto), + hasNext: z.boolean(), + hasPrev: z.boolean(), + totalElements: z.number().int().nullish(), + totalPages: z.number().int().nullish(), + }) + .strict(); const TableValueResultIntegrationDto = z .object({ data: z.array(IntegrationDto), @@ -3051,6 +3139,15 @@ const TableValueResultResourceGroupDto = z totalPages: z.number().int().nullish(), }) .strict(); +const TableValueResultRuleEvaluationDto = z + .object({ + data: z.array(RuleEvaluationDto), + hasNext: z.boolean(), + hasPrev: z.boolean(), + totalElements: z.number().int().nullish(), + totalPages: z.number().int().nullish(), + }) + .strict(); const TableValueResultScheduledMaintenanceDto = z .object({ data: z.array(ScheduledMaintenanceDto), @@ -3355,6 +3452,8 @@ export const schemas = { ApiKeyDto, AssertionResultDto, AssertionTestResultDto, + MemberRoleChangedMetadata, + AuditMetadata, AuditEventDto, KeyInfo, OrgInfo, @@ -3379,6 +3478,10 @@ export const schemas = { CheckTypeDetailsDto, CheckResultDetailsDto, CheckResultDto, + RuleEvaluationDto, + IncidentStateTransitionDto, + PolicySnapshotDto, + CheckTraceDto, ComponentImpact, ComponentsSummaryDto, ComponentStatusDto, @@ -3405,6 +3508,7 @@ export const schemas = { IncidentDetailDto, IncidentFilterParams, IncidentPolicyDto, + IncidentTimelineDto, IntegrationFieldDto, IntegrationConfigSchemaDto, IntegrationDto, @@ -3454,6 +3558,7 @@ export const schemas = { SingleValueResponseAuthMeResponse, SingleValueResponseBatchComponentUptimeDto, SingleValueResponseBulkMonitorActionResult, + SingleValueResponseCheckTraceDto, SingleValueResponseDashboardOverviewDto, SingleValueResponseDekRotationResultDto, SingleValueResponseDeployLockDto, @@ -3461,6 +3566,7 @@ export const schemas = { SingleValueResponseGlobalStatusSummaryDto, SingleValueResponseIncidentDetailDto, SingleValueResponseIncidentPolicyDto, + SingleValueResponseIncidentTimelineDto, SingleValueResponseInviteDto, SingleValueResponseListUUID, SingleValueResponseLong, @@ -3473,6 +3579,7 @@ export const schemas = { SingleValueResponseNotificationDispatchDto, SingleValueResponseNotificationPolicyDto, SingleValueResponseOrganizationDto, + SingleValueResponsePolicySnapshotDto, SingleValueResponseResourceGroupDto, SingleValueResponseResourceGroupHealthDto, SingleValueResponseResourceGroupMemberDto, @@ -3524,6 +3631,7 @@ export const schemas = { TableValueResultDeliveryAttemptDto, TableValueResultEnvironmentDto, TableValueResultIncidentDto, + TableValueResultIncidentStateTransitionDto, TableValueResultIntegrationDto, TableValueResultInviteDto, TableValueResultMaintenanceWindowDto, @@ -3534,6 +3642,7 @@ export const schemas = { TableValueResultNotificationDto, TableValueResultNotificationPolicyDto, TableValueResultResourceGroupDto, + TableValueResultRuleEvaluationDto, TableValueResultScheduledMaintenanceDto, TableValueResultSecretDto, TableValueResultServiceComponentDto, diff --git a/src/lib/api.generated.ts b/src/lib/api.generated.ts index bb7edce..06dea22 100644 --- a/src/lib/api.generated.ts +++ b/src/lib/api.generated.ts @@ -377,6 +377,103 @@ export interface paths { patch?: never; trace?: never; }; + "/api/v1/forensics/incidents/{id}/timeline": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Full forensic timeline for an incident + * @description Returns every state-machine transition for this incident plus the rule evaluations that caused each transition, plus the policy snapshot that triggered confirmation. Correlate evaluations to transitions via evaluation.triggeringTransitionId == transition.id. + */ + get: operations["getTimeline"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/forensics/monitors/{id}/rule-evaluations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Paged list of rule evaluations for a monitor + * @description Filter by ruleType (e.g. consecutive_failures), region, onlyMatched=true to narrow to firing evaluations, and occurredAt window. + */ + get: operations["listMonitorRuleEvaluations"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/forensics/monitors/{id}/transitions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Paged list of state transitions for a monitor (optionally time-bounded) */ + get: operations["listMonitorTransitions"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/forensics/policy-snapshots/{hashHex}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Fetch a policy snapshot by its content hash + * @description Hash is SHA-256 over canonical policy JSON, hex-encoded. Access is gated: caller's org must have evaluated against this hash at least once. + */ + get: operations["getPolicySnapshot"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/forensics/traces/{checkId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Replay a single check execution + * @description Returns every rule evaluation and state transition emitted for this scheduler-minted check_id (V92), plus the policy snapshot that governed them. + */ + get: operations["getTrace"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/v1/heartbeat/{token}": { parameters: { query?: never; @@ -2622,16 +2719,15 @@ export interface components { resourceId?: string | null; /** @description Human-readable name of the affected resource */ resourceName?: string | null; - /** @description Additional context about the action */ - metadata?: { - [key: string]: Record | null; - } | null; + metadata?: Omit | null; /** * Format: date-time * @description Timestamp when the action was performed */ createdAt: string; }; + /** @description Typed metadata payload attached to an audit event; null for actions that carry no extra context. */ + AuditMetadata: components["schemas"]["MemberRoleChangedMetadata"] | null; /** @description Identity, organization, plan, and rate-limit info for the authenticated API key */ AuthMeResponse: { key: components["schemas"]["KeyInfo"]; @@ -2828,6 +2924,18 @@ export interface components { */ checkId?: string | null; }; + CheckTraceDto: { + /** + * Format: uuid + * @description The check execution ID this trace is keyed by + */ + checkId: string; + /** @description All rule evaluations that ran for this check */ + evaluations: components["schemas"]["RuleEvaluationDto"][]; + /** @description State-machine transitions this check caused (may be empty if nothing fired) */ + transitions: components["schemas"]["IncidentStateTransitionDto"][]; + policySnapshot?: components["schemas"]["PolicySnapshotDto"] | null; + }; /** @description Check-type-specific details — polymorphic by check_type discriminator */ CheckTypeDetailsDto: components["schemas"]["Http"] | components["schemas"]["Tcp"] | components["schemas"]["Icmp"] | components["schemas"]["Dns"] | components["schemas"]["McpServer"]; /** @description One component's uptime contribution for the day */ @@ -4135,6 +4243,20 @@ export interface components { resourceGroupId?: string | null; /** @description Name of the resource group; populated on list responses. Omitted from JSON (undefined to SDKs) on detail responses, treat missing as null. */ resourceGroupName?: string | null; + /** + * Format: uuid + * @description Scheduler-minted check execution ID whose result confirmed this incident; joins to check_results, rule_evaluations, and incident_state_transitions + */ + triggeringCheckId?: string | null; + /** @description Hex SHA-256 of the canonical policy snapshot that fired; combined with triggeredByRuleIndex points to the exact TriggerRule */ + triggeredByRuleSnapshotHashHex?: string | null; + /** + * Format: int32 + * @description Index of the fired rule inside the policy's trigger_rules array + */ + triggeredByRuleIndex?: number | null; + /** @description Detection engine semver that evaluated the rule */ + engineVersion?: string | null; }; IncidentFilterParams: { /** @@ -4258,6 +4380,55 @@ export interface components { /** Format: double */ mttr30d?: number | null; }; + /** @description State-machine transitions this check caused (may be empty if nothing fired) */ + IncidentStateTransitionDto: { + /** + * Format: uuid + * @description Forensic row UUID + */ + id: string; + /** + * Format: date-time + * @description When the state transition occurred + */ + occurredAt: string; + /** + * Format: uuid + * @description Monitor this transition pertains to + */ + monitorId: string; + /** + * Format: uuid + * @description Incident this transition belongs to; null for pre-incident (auto-cleared) transitions + */ + incidentId?: string | null; + /** @description Previous status (WATCHING | TRIGGERED | CONFIRMED | RESOLVED) */ + fromStatus: string; + /** @description New status (WATCHING | TRIGGERED | CONFIRMED | RESOLVED) */ + toStatus: string; + /** @description Why the transition fired (rule_matched | confirmation_met | auto_cleared_by_timeout | recovery_met | reopened | manually_resolved | policy_changed) */ + reason: string; + /** @description rule_evaluation ids that caused this transition (may be empty for timeout-driven edges) */ + triggeringEvaluationIds: string[]; + /** @description Regions whose evaluations contributed to this transition */ + affectedRegions: string[]; + /** @description Hex-encoded hash of the policy snapshot that governed this transition */ + policySnapshotHashHex: string; + /** @description Detection engine version that emitted this transition */ + engineVersion: string; + /** + * Format: uuid + * @description Scheduler-minted check execution ID (V92) of the triggering result + */ + checkId: string; + }; + IncidentTimelineDto: { + /** @description State-machine transitions in chronological order */ + transitions: components["schemas"]["IncidentStateTransitionDto"][]; + /** @description Rule evaluations that caused any of the transitions above. Correlate via evaluation.triggeringTransitionId == transition.id */ + triggeringEvaluations: components["schemas"]["RuleEvaluationDto"][]; + policySnapshot?: components["schemas"]["PolicySnapshotDto"] | null; + }; IncidentUpdateDto: { /** Format: uuid */ id: string; @@ -4595,6 +4766,24 @@ export interface components { */ createdAt: string; }; + /** @description Role transition recorded when an organization member's role changes. */ + MemberRoleChangedMetadata: { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + kind: "member_role_changed"; + /** + * @description Role the member held before the change + * @enum {string} + */ + oldRole: "OWNER" | "ADMIN" | "MEMBER"; + /** + * @description Role the member holds after the change + * @enum {string} + */ + newRole: "OWNER" | "ADMIN" | "MEMBER"; + }; MonitorAssertionDto: { /** Format: uuid */ id: string; @@ -5011,6 +5200,27 @@ export interface components { [key: string]: number; }; }; + /** @description Policy snapshot used during this check (all evaluations of a single check are against one policy) */ + PolicySnapshotDto: { + /** @description Hex-encoded SHA-256 of the canonical policy JSON */ + hashHex: string; + /** @description Canonical policy document (snake_case, sorted keys) */ + policy: { + [key: string]: Record; + }; + /** @description Detection engine version that observed this policy */ + engineVersion: string; + /** + * Format: date-time + * @description First time the detection engine evaluated against this policy bytes + */ + firstSeenAt: string; + /** + * Format: date-time + * @description Most recent time the engine evaluated against this policy bytes + */ + lastSeenAt: string; + } | null; /** @description Aggregated poll metrics for a time bucket */ PollChartBucketDto: { /** @@ -5408,6 +5618,57 @@ export interface components { */ interval: number; }; + /** @description All rule evaluations that ran for this check */ + RuleEvaluationDto: { + /** + * Format: uuid + * @description Forensic row UUID + */ + id: string; + /** + * Format: date-time + * @description When the evaluation ran + */ + occurredAt: string; + /** + * Format: uuid + * @description Monitor that produced the input check result + */ + monitorId: string; + /** @description Probe region of the input check result */ + region: string; + /** @description Hex-encoded hash of the policy snapshot this rule came from */ + policySnapshotHashHex: string; + /** + * Format: int32 + * @description Index into the policy's triggerRules array (0-based) + */ + ruleIndex: number; + /** @description Rule type (e.g. consecutive_failures, failures_in_window) */ + ruleType: string; + /** @description Rule scope (per_region | multi_region) */ + ruleScope: string; + /** @description check_results IDs that were inputs to this evaluation (newest first) */ + inputResultIds: string[]; + /** @description Whether the rule fired on this evaluation */ + outputMatched: boolean; + /** @description Structured details (e.g. failure counts, response-time aggregates) */ + evaluationDetails: { + [key: string]: Record; + }; + /** @description Detection engine version that ran this evaluation */ + engineVersion: string; + /** + * Format: uuid + * @description Scheduler-minted check execution ID (V92) — the causal chain identifier + */ + checkId: string; + /** + * Format: uuid + * @description If this evaluation caused a state transition, points to that transition's id + */ + triggeringTransitionId?: string | null; + }; /** @description A scheduled maintenance window from a vendor status page */ ScheduledMaintenanceDto: { /** @@ -5882,6 +6143,9 @@ export interface components { SingleValueResponseBulkMonitorActionResult: { data: components["schemas"]["BulkMonitorActionResult"]; }; + SingleValueResponseCheckTraceDto: { + data: components["schemas"]["CheckTraceDto"]; + }; SingleValueResponseDashboardOverviewDto: { data: components["schemas"]["DashboardOverviewDto"]; }; @@ -5903,6 +6167,9 @@ export interface components { SingleValueResponseIncidentPolicyDto: { data: components["schemas"]["IncidentPolicyDto"]; }; + SingleValueResponseIncidentTimelineDto: { + data: components["schemas"]["IncidentTimelineDto"]; + }; SingleValueResponseInviteDto: { data: components["schemas"]["InviteDto"]; }; @@ -5940,6 +6207,9 @@ export interface components { SingleValueResponseOrganizationDto: { data: components["schemas"]["OrganizationDto"]; }; + SingleValueResponsePolicySnapshotDto: { + data: components["schemas"]["PolicySnapshotDto"]; + }; SingleValueResponseResourceGroupDto: { data: components["schemas"]["ResourceGroupDto"]; }; @@ -6331,6 +6601,15 @@ export interface components { /** Format: int32 */ totalPages?: number | null; }; + TableValueResultIncidentStateTransitionDto: { + data: components["schemas"]["IncidentStateTransitionDto"][]; + hasNext: boolean; + hasPrev: boolean; + /** Format: int64 */ + totalElements?: number | null; + /** Format: int32 */ + totalPages?: number | null; + }; TableValueResultIntegrationDto: { data: components["schemas"]["IntegrationDto"][]; hasNext: boolean; @@ -6421,6 +6700,15 @@ export interface components { /** Format: int32 */ totalPages?: number | null; }; + TableValueResultRuleEvaluationDto: { + data: components["schemas"]["RuleEvaluationDto"][]; + hasNext: boolean; + hasPrev: boolean; + /** Format: int64 */ + totalElements?: number | null; + /** Format: int32 */ + totalPages?: number | null; + }; TableValueResultScheduledMaintenanceDto: { data: components["schemas"]["ScheduledMaintenanceDto"][]; hasNext: boolean; @@ -10007,6 +10295,487 @@ export interface operations { }; }; }; + getTimeline: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["SingleValueResponseIncidentTimelineDto"]; + }; + }; + /** @description Bad request — the payload failed validation */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Unauthorized — missing or invalid credentials */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Forbidden — the actor lacks permission for this resource */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Not found — the requested resource does not exist */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Conflict — the request collides with current resource state */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Internal server error — see the message field for details */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Bad gateway — an upstream provider returned an error */ + 502: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service unavailable — try again shortly */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listMonitorRuleEvaluations: { + parameters: { + query: { + ruleType?: string; + region?: string; + onlyMatched?: boolean; + from?: string; + to?: string; + pageable: components["schemas"]["Pageable"]; + }; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["TableValueResultRuleEvaluationDto"]; + }; + }; + /** @description Bad request — the payload failed validation */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Unauthorized — missing or invalid credentials */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Forbidden — the actor lacks permission for this resource */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Not found — the requested resource does not exist */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Conflict — the request collides with current resource state */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Internal server error — see the message field for details */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Bad gateway — an upstream provider returned an error */ + 502: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service unavailable — try again shortly */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listMonitorTransitions: { + parameters: { + query: { + from?: string; + to?: string; + pageable: components["schemas"]["Pageable"]; + }; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["TableValueResultIncidentStateTransitionDto"]; + }; + }; + /** @description Bad request — the payload failed validation */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Unauthorized — missing or invalid credentials */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Forbidden — the actor lacks permission for this resource */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Not found — the requested resource does not exist */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Conflict — the request collides with current resource state */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Internal server error — see the message field for details */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Bad gateway — an upstream provider returned an error */ + 502: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service unavailable — try again shortly */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getPolicySnapshot: { + parameters: { + query?: never; + header?: never; + path: { + hashHex: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["SingleValueResponsePolicySnapshotDto"]; + }; + }; + /** @description Bad request — the payload failed validation */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Unauthorized — missing or invalid credentials */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Forbidden — the actor lacks permission for this resource */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Not found — the requested resource does not exist */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Conflict — the request collides with current resource state */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Internal server error — see the message field for details */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Bad gateway — an upstream provider returned an error */ + 502: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service unavailable — try again shortly */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getTrace: { + parameters: { + query?: never; + header?: never; + path: { + checkId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["SingleValueResponseCheckTraceDto"]; + }; + }; + /** @description Bad request — the payload failed validation */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Unauthorized — missing or invalid credentials */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Forbidden — the actor lacks permission for this resource */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Not found — the requested resource does not exist */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Conflict — the request collides with current resource state */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Internal server error — see the message field for details */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Bad gateway — an upstream provider returned an error */ + 502: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service unavailable — try again shortly */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; pingGet: { parameters: { query?: never;