diff --git a/.devhelm/state.json b/.devhelm/state.json new file mode 100644 index 0000000..adc1ad0 --- /dev/null +++ b/.devhelm/state.json @@ -0,0 +1,14 @@ +{ + "version": "2", + "serial": 1, + "lastDeployedAt": "2026-04-16T18:27:40.629Z", + "resources": { + "tags.cli-surface-test": { + "resourceType": "tag", + "attributes": { + "name": "cli-surface-test" + }, + "children": {} + } + } +} \ No newline at end of file diff --git a/docs/openapi/monitoring-api.json b/docs/openapi/monitoring-api.json index 595946f..3dea12c 100644 --- a/docs/openapi/monitoring-api.json +++ b/docs/openapi/monitoring-api.json @@ -4392,6 +4392,102 @@ } } }, + "/api/v1/services/{slugOrId}/poll-results": { + "get": { + "tags": [ + "Status Data" + ], + "summary": "List poll results for a service (cursor-paginated)", + "operationId": "listPollResults", + "parameters": [ + { + "name": "slugOrId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "cursor", + "in": "query", + "description": "ISO 8601 timestamp cursor from a previous response", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "Page size (1–100, default 50)", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/CursorPageServicePollResultDto" + } + } + } + } + } + } + }, + "/api/v1/services/{slugOrId}/poll-summary": { + "get": { + "tags": [ + "Status Data" + ], + "summary": "Get aggregated poll metrics and chart data for a service", + "operationId": "getPollSummary", + "parameters": [ + { + "name": "slugOrId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "window", + "in": "query", + "description": "Time window", + "required": false, + "schema": { + "type": "string", + "enum": [ + "24h", + "7d", + "30d" + ] + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/SingleValueResponseServicePollSummaryDto" + } + } + } + } + } + } + }, "/api/v1/services/{slugOrId}/uptime": { "get": { "tags": [ @@ -6449,8 +6545,7 @@ "items": { "type": "string", "description": "IDs of existing org tags to attach", - "format": "uuid", - "nullable": true + "format": "uuid" } }, "newTags": { @@ -6462,11 +6557,7 @@ } } }, - "description": "Request body for adding tags to a monitor. Provide existing tag IDs, inline new tags, or both.", - "required": [ - "tagIds", - "newTags" - ] + "description": "Request body for adding tags to a monitor. Provide existing tag IDs, inline new tags, or both." }, "AddResourceGroupMemberRequest": { "required": [ @@ -6529,6 +6620,46 @@ }, "description": "Updated affected components; null preserves current" }, + "AlertChannelDisplayConfig": { + "type": "object", + "properties": { + "recipients": { + "type": "array", + "description": "Email recipients list (email channels)", + "nullable": true, + "items": { + "type": "string", + "description": "Email recipients list (email channels)" + } + }, + "region": { + "type": "string", + "description": "OpsGenie API region: us or eu", + "nullable": true + }, + "severityOverride": { + "type": "string", + "description": "PagerDuty severity override (critical, error, warning, info)", + "nullable": true + }, + "mentionRoleId": { + "type": "string", + "description": "Discord role ID to mention in notifications", + "nullable": true + }, + "customHeaders": { + "type": "object", + "additionalProperties": { + "type": "string", + "description": "Custom HTTP headers for webhook requests", + "nullable": true + }, + "description": "Custom HTTP headers for webhook requests", + "nullable": true + } + }, + "description": "Non-sensitive alert channel configuration metadata" + }, "AlertChannelDto": { "required": [ "channelType", @@ -6562,14 +6693,12 @@ ] }, "displayConfig": { - "type": "object", - "additionalProperties": { - "type": "object", - "description": "Non-sensitive display metadata; null for older channels", - "nullable": true - }, - "description": "Non-sensitive display metadata; null for older channels", - "nullable": true + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/AlertChannelDisplayConfig" + } + ] }, "createdAt": { "type": "string", @@ -6698,7 +6827,6 @@ "required": [ "id", "incidentId", - "dispatchId", "channelId", "channel", "channelType", @@ -6707,10 +6835,6 @@ "stepNumber", "fireCount", "attemptCount", - "lastAttemptAt", - "nextRetryAt", - "deliveredAt", - "errorMessage", "createdAt" ] }, @@ -6738,7 +6862,10 @@ "format": "uuid", "nullable": true } - } + }, + "required": [ + "headerName" + ] } ] }, @@ -6775,8 +6902,7 @@ "id", "name", "key", - "createdAt", - "expiresAt" + "createdAt" ] }, "ApiKeyDto": { @@ -6830,10 +6956,7 @@ "name", "key", "createdAt", - "updatedAt", - "lastUsedAt", - "revokedAt", - "expiresAt" + "updatedAt" ] }, "AssertionConfig": { @@ -6848,7 +6971,51 @@ }, "description": "New assertion configuration (full replacement)", "discriminator": { - "propertyName": "type" + "propertyName": "type", + "mapping": { + "status_code": "#/components/schemas/StatusCodeAssertion", + "response_time": "#/components/schemas/ResponseTimeAssertion", + "body_contains": "#/components/schemas/BodyContainsAssertion", + "json_path": "#/components/schemas/JsonPathAssertion", + "header_value": "#/components/schemas/HeaderValueAssertion", + "regex_body": "#/components/schemas/RegexBodyAssertion", + "dns_resolves": "#/components/schemas/DnsResolvesAssertion", + "dns_response_time": "#/components/schemas/DnsResponseTimeAssertion", + "dns_expected_ips": "#/components/schemas/DnsExpectedIpsAssertion", + "dns_expected_cname": "#/components/schemas/DnsExpectedCnameAssertion", + "dns_record_contains": "#/components/schemas/DnsRecordContainsAssertion", + "dns_record_equals": "#/components/schemas/DnsRecordEqualsAssertion", + "dns_txt_contains": "#/components/schemas/DnsTxtContainsAssertion", + "dns_min_answers": "#/components/schemas/DnsMinAnswersAssertion", + "dns_max_answers": "#/components/schemas/DnsMaxAnswersAssertion", + "dns_response_time_warn": "#/components/schemas/DnsResponseTimeWarnAssertion", + "dns_ttl_low": "#/components/schemas/DnsTtlLowAssertion", + "dns_ttl_high": "#/components/schemas/DnsTtlHighAssertion", + "mcp_connects": "#/components/schemas/McpConnectsAssertion", + "mcp_response_time": "#/components/schemas/McpResponseTimeAssertion", + "mcp_has_capability": "#/components/schemas/McpHasCapabilityAssertion", + "mcp_tool_available": "#/components/schemas/McpToolAvailableAssertion", + "mcp_min_tools": "#/components/schemas/McpMinToolsAssertion", + "mcp_protocol_version": "#/components/schemas/McpProtocolVersionAssertion", + "mcp_response_time_warn": "#/components/schemas/McpResponseTimeWarnAssertion", + "mcp_tool_count_changed": "#/components/schemas/McpToolCountChangedAssertion", + "ssl_expiry": "#/components/schemas/SslExpiryAssertion", + "response_size": "#/components/schemas/ResponseSizeAssertion", + "redirect_count": "#/components/schemas/RedirectCountAssertion", + "redirect_target": "#/components/schemas/RedirectTargetAssertion", + "response_time_warn": "#/components/schemas/ResponseTimeWarnAssertion", + "tcp_connects": "#/components/schemas/TcpConnectsAssertion", + "tcp_response_time": "#/components/schemas/TcpResponseTimeAssertion", + "tcp_response_time_warn": "#/components/schemas/TcpResponseTimeWarnAssertion", + "icmp_reachable": "#/components/schemas/IcmpReachableAssertion", + "icmp_response_time": "#/components/schemas/IcmpResponseTimeAssertion", + "icmp_response_time_warn": "#/components/schemas/IcmpResponseTimeWarnAssertion", + "icmp_packet_loss": "#/components/schemas/IcmpPacketLossAssertion", + "heartbeat_received": "#/components/schemas/HeartbeatReceivedAssertion", + "heartbeat_max_interval": "#/components/schemas/HeartbeatMaxIntervalAssertion", + "heartbeat_interval_drift": "#/components/schemas/HeartbeatIntervalDriftAssertion", + "heartbeat_payload_contains": "#/components/schemas/HeartbeatPayloadContainsAssertion" + } } }, "AssertionResultDto": { @@ -6893,10 +7060,7 @@ "required": [ "type", "passed", - "severity", - "message", - "expected", - "actual" + "severity" ] }, "AssertionTestResultDto": { @@ -6910,8 +7074,8 @@ "response_time", "body_contains", "json_path", - "header", - "regex", + "header_value", + "regex_body", "dns_resolves", "dns_response_time", "dns_expected_ips", @@ -6981,9 +7145,7 @@ "assertionType", "passed", "severity", - "message", - "expected", - "actual" + "message" ] }, "AuditEventDto": { @@ -7042,13 +7204,7 @@ }, "required": [ "id", - "actorId", - "actorEmail", "action", - "resourceType", - "resourceId", - "resourceName", - "metadata", "createdAt" ] }, @@ -7131,7 +7287,10 @@ "type": "string", "description": "Substring that must appear in the response body" } - } + }, + "required": [ + "substring" + ] } ] }, @@ -7171,8 +7330,7 @@ "items": { "type": "string", "description": "Tag IDs to attach or detach (required for ADD_TAG and REMOVE_TAG)", - "format": "uuid", - "nullable": true + "format": "uuid" } }, "newTags": { @@ -7282,7 +7440,16 @@ }, "description": "New channel configuration (full replacement, not partial update)", "discriminator": { - "propertyName": "channelType" + "propertyName": "channelType", + "mapping": { + "email": "#/components/schemas/EmailChannelConfig", + "slack": "#/components/schemas/SlackChannelConfig", + "webhook": "#/components/schemas/WebhookChannelConfig", + "pagerduty": "#/components/schemas/PagerDutyChannelConfig", + "opsgenie": "#/components/schemas/OpsGenieChannelConfig", + "teams": "#/components/schemas/TeamsChannelConfig", + "discord": "#/components/schemas/DiscordChannelConfig" + } } }, "ChartBucketDto": { @@ -7325,11 +7492,7 @@ }, "description": "Aggregated metrics for a time bucket", "required": [ - "bucket", - "uptimePercent", - "avgLatencyMs", - "p95LatencyMs", - "p99LatencyMs" + "bucket" ] }, "CheckResultDetailsDto": { @@ -7371,7 +7534,12 @@ } }, "tlsInfo": { - "$ref": "#/components/schemas/TlsInfoDto" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TlsInfoDto" + } + ] }, "redirectCount": { "type": "integer", @@ -7393,36 +7561,15 @@ "example": 4096 }, "checkDetails": { - "oneOf": [ - { - "$ref": "#/components/schemas/Dns" - }, - { - "$ref": "#/components/schemas/Http" - }, - { - "$ref": "#/components/schemas/Icmp" - }, - { - "$ref": "#/components/schemas/McpServer" - }, + "nullable": true, + "allOf": [ { - "$ref": "#/components/schemas/Tcp" + "$ref": "#/components/schemas/CheckTypeDetailsDto" } ] } }, - "description": "Type-specific details captured during a check execution", - "required": [ - "statusCode", - "responseHeaders", - "responseBodySnapshot", - "assertionResults", - "tlsInfo", - "redirectCount", - "redirectTarget", - "responseSizeBytes" - ] + "description": "Type-specific details captured during a check execution" }, "CheckResultDto": { "type": "object", @@ -7465,7 +7612,12 @@ "nullable": true }, "details": { - "$ref": "#/components/schemas/CheckResultDetailsDto" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/CheckResultDetailsDto" + } + ] }, "checkId": { "type": "string", @@ -7479,12 +7631,7 @@ "id", "timestamp", "region", - "responseTimeMs", - "passed", - "failureReason", - "severityHint", - "details", - "checkId" + "passed" ] }, "CheckTypeDetailsDto": { @@ -7586,8 +7733,7 @@ "date", "partialOutageSeconds", "majorOutageSeconds", - "uptimePercentage", - "incidents" + "uptimePercentage" ] }, "ComponentUptimeSummaryDto": { @@ -7622,9 +7768,6 @@ }, "description": "Inline uptime percentages for 24h, 7d, 30d", "required": [ - "day", - "week", - "month", "source" ] }, @@ -8059,8 +8202,7 @@ "nullable": true, "items": { "type": "string", - "description": "Probe regions to run checks from, e.g. us-east, eu-west", - "nullable": true + "description": "Probe regions to run checks from, e.g. us-east, eu-west" } }, "managedBy": { @@ -8087,23 +8229,20 @@ } }, "auth": { - "oneOf": [ - { - "$ref": "#/components/schemas/ApiKeyAuthConfig" - }, - { - "$ref": "#/components/schemas/BasicAuthConfig" - }, - { - "$ref": "#/components/schemas/BearerAuthConfig" - }, + "nullable": true, + "allOf": [ { - "$ref": "#/components/schemas/HeaderAuthConfig" + "$ref": "#/components/schemas/MonitorAuthConfig" } ] }, "incidentPolicy": { - "$ref": "#/components/schemas/UpdateIncidentPolicyRequest" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/UpdateIncidentPolicyRequest" + } + ] }, "alertChannelIds": { "type": "array", @@ -8112,12 +8251,16 @@ "items": { "type": "string", "description": "Alert channels to notify when this monitor triggers", - "format": "uuid", - "nullable": true + "format": "uuid" } }, "tags": { - "$ref": "#/components/schemas/AddMonitorTagsRequest" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/AddMonitorTagsRequest" + } + ] } } }, @@ -8195,12 +8338,16 @@ "nullable": true, "items": { "type": "string", - "description": "Default regions applied to member monitors", - "nullable": true + "description": "Default regions applied to member monitors" } }, "defaultRetryStrategy": { - "$ref": "#/components/schemas/RetryStrategy" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RetryStrategy" + } + ] }, "defaultAlertChannels": { "type": "array", @@ -8209,8 +8356,7 @@ "items": { "type": "string", "description": "Default alert channel IDs applied to member monitors", - "format": "uuid", - "nullable": true + "format": "uuid" } }, "defaultEnvironmentId": { @@ -8527,7 +8673,12 @@ "nullable": true }, "branding": { - "$ref": "#/components/schemas/StatusPageBranding" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/StatusPageBranding" + } + ] }, "visibility": { "type": "string", @@ -8646,7 +8797,6 @@ "description": "Cursor-paginated response for time-series and append-only data", "required": [ "data", - "nextCursor", "hasMore" ] }, @@ -8673,7 +8823,6 @@ "description": "Cursor-paginated response for time-series and append-only data", "required": [ "data", - "nextCursor", "hasMore" ] }, @@ -8700,7 +8849,32 @@ "description": "Cursor-paginated response for time-series and append-only data", "required": [ "data", - "nextCursor", + "hasMore" + ] + }, + "CursorPageServicePollResultDto": { + "type": "object", + "properties": { + "data": { + "type": "array", + "description": "Items on this page", + "items": { + "$ref": "#/components/schemas/ServicePollResultDto" + } + }, + "nextCursor": { + "type": "string", + "description": "Opaque cursor for the next page; null when there are no more results", + "nullable": true + }, + "hasMore": { + "type": "boolean", + "description": "Whether more results exist beyond this page" + } + }, + "description": "Cursor-paginated response for time-series and append-only data", + "required": [ + "data", "hasMore" ] }, @@ -8851,13 +9025,6 @@ "deliveryId", "attemptNumber", "status", - "responseStatusCode", - "requestPayload", - "responseBody", - "errorMessage", - "responseTimeMs", - "externalId", - "requestHeaders", "attemptedAt" ] }, @@ -8914,102 +9081,34 @@ "description": "Optional Discord role ID to mention in notifications", "nullable": true } - } + }, + "required": [ + "webhookUrl" + ] } ] }, - "Dns": { + "DnsExpectedCnameAssertion": { + "required": [ + "value" + ], "type": "object", - "description": "DNS check-type-specific details", "allOf": [ { - "$ref": "#/components/schemas/CheckTypeDetailsDto" + "$ref": "#/components/schemas/AssertionConfig" }, { "type": "object", "properties": { - "hostname": { - "type": "string", - "description": "Target hostname", - "nullable": true - }, - "requestedTypes": { - "type": "array", - "description": "Requested DNS record types", - "nullable": true, - "items": { - "type": "string", - "description": "Requested DNS record types", - "nullable": true - } - }, - "usedResolver": { - "type": "string", - "description": "Resolver used for lookup", - "nullable": true - }, - "records": { - "type": "object", - "additionalProperties": { - "type": "array", - "description": "Resolved DNS records keyed by record type", - "nullable": true, - "items": { - "type": "object", - "additionalProperties": { - "type": "object", - "description": "Resolved DNS records keyed by record type", - "nullable": true - }, - "description": "Resolved DNS records keyed by record type", - "nullable": true - } - }, - "description": "Resolved DNS records keyed by record type", - "nullable": true - }, - "attempts": { - "type": "array", - "description": "DNS resolution attempts", - "nullable": true, - "items": { - "type": "object", - "additionalProperties": { - "type": "object", - "description": "DNS resolution attempts", - "nullable": true - }, - "description": "DNS resolution attempts", - "nullable": true - } - }, - "failureKind": { - "type": "string", - "description": "Kind of DNS failure, if any", - "nullable": true - } - } - } - ] - }, - "DnsExpectedCnameAssertion": { - "required": [ - "value" - ], - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/AssertionConfig" - }, - { - "type": "object", - "properties": { - "value": { - "minLength": 1, + "value": { + "minLength": 1, "type": "string", "description": "Expected CNAME target the resolution must include" } - } + }, + "required": [ + "value" + ] } ] }, @@ -9034,7 +9133,10 @@ "description": "Allowed IP addresses; at least one resolved address must match" } } - } + }, + "required": [ + "ips" + ] } ] }, @@ -9060,7 +9162,10 @@ "description": "Maximum number of answers allowed for that record type", "format": "int32" } - } + }, + "required": [ + "recordType" + ] } ] }, @@ -9086,7 +9191,10 @@ "description": "Minimum number of answers required for that record type", "format": "int32" } - } + }, + "required": [ + "recordType" + ] } ] }, @@ -9151,7 +9259,10 @@ "format": "int32", "nullable": true } - } + }, + "required": [ + "hostname" + ] } ] }, @@ -9178,7 +9289,11 @@ "type": "string", "description": "Substring that must appear in a matching record value" } - } + }, + "required": [ + "recordType", + "substring" + ] } ] }, @@ -9205,7 +9320,11 @@ "type": "string", "description": "Expected DNS record value for an exact match" } - } + }, + "required": [ + "recordType", + "value" + ] } ] }, @@ -9306,7 +9425,10 @@ "type": "string", "description": "Substring that must appear in at least one TXT record" } - } + }, + "required": [ + "substring" + ] } ] }, @@ -9332,7 +9454,10 @@ "format": "email" } } - } + }, + "required": [ + "recipients" + ] } ] }, @@ -9621,7 +9746,10 @@ "format": "uuid", "nullable": true } - } + }, + "required": [ + "headerName" + ] } ] }, @@ -9661,7 +9789,12 @@ "range" ] } - } + }, + "required": [ + "expected", + "headerName", + "operator" + ] } ] }, @@ -9684,7 +9817,10 @@ "description": "Max percent drift from expected ping interval before warning (non-fatal)", "format": "int32" } - } + }, + "required": [ + "maxDeviationPercent" + ] } ] }, @@ -9706,7 +9842,10 @@ "description": "Maximum allowed gap in seconds between consecutive heartbeat pings", "format": "int32" } - } + }, + "required": [ + "maxSeconds" + ] } ] }, @@ -9736,7 +9875,11 @@ "description": "Grace period in seconds before marking as down", "format": "int32" } - } + }, + "required": [ + "expectedInterval", + "gracePeriod" + ] } ] }, @@ -9762,7 +9905,11 @@ "type": "string", "description": "Expected value to compare against at that path" } - } + }, + "required": [ + "path", + "value" + ] } ] }, @@ -9774,35 +9921,6 @@ } ] }, - "Http": { - "type": "object", - "description": "HTTP check-type-specific details", - "allOf": [ - { - "$ref": "#/components/schemas/CheckTypeDetailsDto" - }, - { - "type": "object", - "properties": { - "timing": { - "type": "object", - "additionalProperties": { - "type": "object", - "description": "Request phase timing breakdown", - "nullable": true - }, - "description": "Request phase timing breakdown", - "nullable": true - }, - "bodyTruncated": { - "type": "boolean", - "description": "Whether the response body was truncated before storage", - "nullable": true - } - } - } - ] - }, "HttpMonitorConfig": { "required": [ "method", @@ -9858,69 +9976,11 @@ "description": "Whether to verify TLS certificates (default: true)", "nullable": true } - } - } - ] - }, - "Icmp": { - "type": "object", - "description": "ICMP (ping) check-type-specific details", - "allOf": [ - { - "$ref": "#/components/schemas/CheckTypeDetailsDto" - }, - { - "type": "object", - "properties": { - "host": { - "type": "string", - "description": "Target host", - "example": "1.1.1.1" - }, - "packetsSent": { - "type": "integer", - "description": "Number of ICMP packets sent", - "format": "int32", - "nullable": true - }, - "packetsReceived": { - "type": "integer", - "description": "Number of ICMP packets received", - "format": "int32", - "nullable": true - }, - "packetLoss": { - "type": "number", - "description": "Packet loss percentage", - "format": "double", - "nullable": true, - "example": 0 - }, - "avgRttMs": { - "type": "number", - "description": "Average round-trip time in ms", - "format": "double", - "nullable": true - }, - "minRttMs": { - "type": "number", - "description": "Minimum round-trip time in ms", - "format": "double", - "nullable": true - }, - "maxRttMs": { - "type": "number", - "description": "Maximum round-trip time in ms", - "format": "double", - "nullable": true - }, - "jitterMs": { - "type": "number", - "description": "Jitter in ms", - "format": "double", - "nullable": true - } - } + }, + "required": [ + "method", + "url" + ] } ] }, @@ -9955,7 +10015,10 @@ "format": "int32", "nullable": true } - } + }, + "required": [ + "host" + ] } ] }, @@ -10047,8 +10110,7 @@ }, "required": [ "incident", - "updates", - "statusPageIncidents" + "updates" ] }, "IncidentDto": { @@ -10156,8 +10218,7 @@ "nullable": true, "items": { "type": "string", - "description": "Service components affected by this incident", - "nullable": true + "description": "Service components affected by this incident" } }, "shortlink": { @@ -10244,35 +10305,15 @@ "description": "Incident triggered by a monitor check failure or manual creation", "required": [ "id", - "monitorId", "organizationId", "source", "status", "severity", - "title", - "triggeredByRule", "affectedRegions", "reopenCount", - "createdByUserId", "statusPageVisible", - "serviceIncidentId", - "serviceId", - "externalRef", - "affectedComponents", - "shortlink", - "resolutionReason", - "startedAt", - "confirmedAt", - "resolvedAt", - "cooldownUntil", "createdAt", - "updatedAt", - "monitorName", - "serviceName", - "serviceSlug", - "monitorType", - "resourceGroupId", - "resourceGroupName" + "updatedAt" ] }, "IncidentFilterParams": { @@ -10356,10 +10397,6 @@ "monitorId", "serviceId", "resourceGroupId", - "tagId", - "environmentId", - "startedFrom", - "startedTo", "page", "size" ] @@ -10421,9 +10458,7 @@ "confirmation", "recovery", "createdAt", - "updatedAt", - "monitorRegionCount", - "checkFrequencySeconds" + "updatedAt" ] }, "IncidentRef": { @@ -10470,8 +10505,7 @@ "description": "Incident summary counters", "required": [ "active", - "resolvedToday", - "mttr30d" + "resolvedToday" ] }, "IncidentUpdateDto": { @@ -10527,9 +10561,6 @@ "required": [ "id", "incidentId", - "oldStatus", - "newStatus", - "body", "createdBy", "notifySubscribers", "createdAt" @@ -10644,8 +10675,7 @@ "type": "array", "nullable": true, "items": { - "type": "string", - "nullable": true + "type": "string" } }, "default": { @@ -10698,9 +10728,7 @@ "inviteId", "email", "roleOffered", - "expiresAt", - "consumedAt", - "revokedAt" + "expiresAt" ] }, "JsonPathAssertion": { @@ -10739,7 +10767,12 @@ "range" ] } - } + }, + "required": [ + "expected", + "operator", + "path" + ] } ] }, @@ -10777,9 +10810,7 @@ "required": [ "id", "name", - "createdAt", - "expiresAt", - "lastUsedAt" + "createdAt" ] }, "LinkedStatusPageIncidentDto": { @@ -10837,8 +10868,7 @@ "title", "status", "impact", - "scheduled", - "publishedAt" + "scheduled" ] }, "MaintenanceComponentRef": { @@ -10892,9 +10922,7 @@ "description": "A status update within a scheduled maintenance lifecycle", "required": [ "id", - "status", - "body", - "displayAt" + "status" ] }, "MaintenanceWindowDto": { @@ -10949,12 +10977,9 @@ "description": "Scheduled maintenance window for a monitor", "required": [ "id", - "monitorId", "organizationId", "startsAt", "endsAt", - "repeatRule", - "reason", "suppressAlerts", "createdAt" ] @@ -10981,8 +11006,7 @@ "items": { "type": "string", "description": "Monitor UUIDs to match for monitor_id_in rules", - "format": "uuid", - "nullable": true + "format": "uuid" } }, "regions": { @@ -10991,8 +11015,7 @@ "nullable": true, "items": { "type": "string", - "description": "Region codes to match for region_in rules", - "nullable": true + "description": "Region codes to match for region_in rules" } }, "values": { @@ -11001,8 +11024,7 @@ "nullable": true, "items": { "type": "string", - "description": "Values list for multi-value rules like monitor_type_in", - "nullable": true + "description": "Values list for multi-value rules like monitor_type_in" } } }, @@ -11033,7 +11055,10 @@ "type": "string", "description": "Capability name the server must advertise, e.g. tools or resources" } - } + }, + "required": [ + "capability" + ] } ] }, @@ -11072,7 +11097,10 @@ "type": "string", "description": "Expected MCP protocol version string from the server handshake" } - } + }, + "required": [ + "version" + ] } ] }, @@ -11112,58 +11140,6 @@ } ] }, - "McpServer": { - "type": "object", - "description": "MCP server check-type-specific details", - "allOf": [ - { - "$ref": "#/components/schemas/CheckTypeDetailsDto" - }, - { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "MCP server URL", - "nullable": true - }, - "protocolVersion": { - "type": "string", - "description": "MCP protocol version", - "nullable": true - }, - "serverInfo": { - "type": "object", - "additionalProperties": { - "type": "object", - "description": "MCP server info (name, version, etc.)", - "nullable": true - }, - "description": "MCP server info (name, version, etc.)", - "nullable": true - }, - "toolCount": { - "type": "integer", - "description": "Number of tools exposed", - "format": "int32", - "nullable": true - }, - "resourceCount": { - "type": "integer", - "description": "Number of resources exposed", - "format": "int32", - "nullable": true - }, - "promptCount": { - "type": "integer", - "description": "Number of prompts exposed", - "format": "int32", - "nullable": true - } - } - } - ] - }, "McpServerMonitorConfig": { "required": [ "command" @@ -11201,7 +11177,10 @@ "description": "Environment variables to pass to the MCP server process", "nullable": true } - } + }, + "required": [ + "command" + ] } ] }, @@ -11222,7 +11201,10 @@ "type": "string", "description": "MCP tool name that must appear in the server's tool list" } - } + }, + "required": [ + "toolName" + ] } ] }, @@ -11292,7 +11274,6 @@ "required": [ "userId", "email", - "name", "orgRole", "status", "createdAt" @@ -11316,8 +11297,8 @@ "response_time", "body_contains", "json_path", - "header", - "regex", + "header_value", + "regex_body", "dns_resolves", "dns_response_time", "dns_expected_ips", @@ -11498,6 +11479,7 @@ "id", "monitorId", "assertionType", + "config", "severity" ] }, @@ -11513,7 +11495,13 @@ }, "description": "New authentication configuration (full replacement)", "discriminator": { - "propertyName": "type" + "propertyName": "type", + "mapping": { + "bearer": "#/components/schemas/BearerAuthConfig", + "basic": "#/components/schemas/BasicAuthConfig", + "header": "#/components/schemas/HeaderAuthConfig", + "api_key": "#/components/schemas/ApiKeyAuthConfig" + } } }, "MonitorAuthDto": { @@ -11556,12 +11544,13 @@ "required": [ "id", "monitorId", - "authType" + "authType", + "config" ] }, "MonitorConfig": { "type": "object", - "description": "Updated protocol-specific configuration; null preserves current" + "description": "Protocol-specific monitor configuration" }, "MonitorDto": { "type": "object", @@ -11671,26 +11660,28 @@ "nullable": true }, "environment": { - "$ref": "#/components/schemas/Summary" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/Summary" + } + ] }, "auth": { - "oneOf": [ - { - "$ref": "#/components/schemas/ApiKeyAuthConfig" - }, - { - "$ref": "#/components/schemas/BasicAuthConfig" - }, - { - "$ref": "#/components/schemas/BearerAuthConfig" - }, + "nullable": true, + "allOf": [ { - "$ref": "#/components/schemas/HeaderAuthConfig" + "$ref": "#/components/schemas/MonitorAuthConfig" } ] }, "incidentPolicy": { - "$ref": "#/components/schemas/IncidentPolicyDto" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/IncidentPolicyDto" + } + ] }, "alertChannelIds": { "type": "array", @@ -11699,8 +11690,7 @@ "items": { "type": "string", "description": "Alert channel IDs linked to this monitor; populated on single-monitor responses", - "format": "uuid", - "nullable": true + "format": "uuid" } } }, @@ -11710,18 +11700,13 @@ "organizationId", "name", "type", + "config", "frequencySeconds", "enabled", "regions", "managedBy", "createdAt", - "updatedAt", - "assertions", - "tags", - "pingUrl", - "environment", - "incidentPolicy", - "alertChannelIds" + "updatedAt" ] }, "MonitorReference": { @@ -11790,9 +11775,7 @@ "up", "down", "degraded", - "paused", - "avgUptime24h", - "avgUptime30d" + "paused" ] }, "MonitorTestRequest": { @@ -11906,23 +11889,13 @@ "type": "array", "nullable": true, "items": { - "type": "string", - "nullable": true + "type": "string" } } }, "required": [ "passed", - "error", - "statusCode", - "responseTimeMs", - "responseHeaders", - "bodyPreview", - "responseSizeBytes", - "redirectCount", - "finalUrl", - "assertionResults", - "warnings" + "assertionResults" ] }, "MonitorVersionDto": { @@ -11979,9 +11952,7 @@ "monitorId", "version", "snapshot", - "changedById", "changedVia", - "changeSummary", "createdAt" ] }, @@ -12103,14 +12074,8 @@ "id", "incidentId", "policyId", - "policyName", "status", - "completionReason", "currentStep", - "totalSteps", - "acknowledgedAt", - "nextEscalationAt", - "lastNotifiedAt", "deliveries", "createdAt", "updatedAt" @@ -12162,9 +12127,6 @@ "id", "type", "title", - "body", - "resourceType", - "resourceId", "read", "createdAt" ] @@ -12251,7 +12213,10 @@ "description": "OpsGenie API region: us or eu", "nullable": true } - } + }, + "required": [ + "apiKey" + ] } ] }, @@ -12291,11 +12256,7 @@ "description": "Organization account details", "required": [ "id", - "name", - "email", - "size", - "industry", - "websiteUrl" + "name" ] }, "OrgInfo": { @@ -12365,7 +12326,10 @@ "description": "Override PagerDuty severity mapping", "nullable": true } - } + }, + "required": [ + "routingKey" + ] } ] }, @@ -12392,8 +12356,6 @@ }, "description": "A top-level page section (either a group or an ungrouped component)", "required": [ - "groupId", - "componentId", "pageOrder" ] }, @@ -12447,13 +12409,46 @@ "description": "Billing plan and entitlement state", "required": [ "tier", - "subscriptionStatus", "trialActive", - "trialExpiresAt", "entitlements", "usage" ] }, + "PollChartBucketDto": { + "type": "object", + "properties": { + "bucket": { + "type": "string", + "description": "Start of the time bucket (ISO 8601)", + "format": "date-time" + }, + "uptimePercent": { + "type": "number", + "description": "Uptime percentage for this bucket; null when no data", + "format": "double", + "nullable": true, + "example": 100 + }, + "avgResponseTimeMs": { + "type": "number", + "description": "Average response time in milliseconds for this bucket", + "format": "double", + "nullable": true, + "example": 245.3 + }, + "totalPolls": { + "type": "integer", + "description": "Total polls in this bucket", + "format": "int64", + "example": 60 + } + }, + "description": "Aggregated poll metrics for a time bucket", + "required": [ + "bucket", + "totalPolls" + ] + }, "PublishStatusPageIncidentRequest": { "type": "object", "properties": { @@ -12504,15 +12499,7 @@ "description": "Whether to notify subscribers (default: true)", "nullable": true } - }, - "required": [ - "title", - "impact", - "status", - "body", - "affectedComponents", - "notifySubscribers" - ] + } }, "RateLimitInfo": { "type": "object", @@ -12614,7 +12601,11 @@ "range" ] } - } + }, + "required": [ + "expected", + "operator" + ] } ] }, @@ -12635,7 +12626,10 @@ "type": "string", "description": "Regular expression the response body must match" } - } + }, + "required": [ + "pattern" + ] } ] }, @@ -12674,9 +12668,7 @@ "required": [ "region", "passed", - "responseTimeMs", - "timestamp", - "severityHint" + "timestamp" ] }, "RemoveMonitorTagsRequest": { @@ -12796,12 +12788,16 @@ "nullable": true, "items": { "type": "string", - "description": "Default regions for member monitors", - "nullable": true + "description": "Default regions for member monitors" } }, "defaultRetryStrategy": { - "$ref": "#/components/schemas/RetryStrategy" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RetryStrategy" + } + ] }, "defaultAlertChannels": { "type": "array", @@ -12810,8 +12806,7 @@ "items": { "type": "string", "description": "Default alert channel IDs for member monitors", - "format": "uuid", - "nullable": true + "format": "uuid" } }, "defaultEnvironmentId": { @@ -12878,20 +12873,8 @@ "organizationId", "name", "slug", - "description", - "alertPolicyId", - "defaultFrequency", - "defaultRegions", - "defaultRetryStrategy", - "defaultAlertChannels", - "defaultEnvironmentId", - "healthThresholdType", - "healthThresholdValue", "suppressMemberAlerts", - "confirmationDelaySeconds", - "recoveryCooldownMinutes", "health", - "members", "createdAt", "updatedAt" ] @@ -12946,9 +12929,7 @@ "status", "totalMembers", "operationalCount", - "activeIncidents", - "thresholdStatus", - "failingCount" + "activeIncidents" ] }, "ResourceGroupMemberDto": { @@ -13029,8 +13010,7 @@ "items": { "type": "number", "description": "Uptime tick values (0-100) for last-24h mini chart; populated when includeMetrics=true", - "format": "double", - "nullable": true + "format": "double" } }, "avgLatencyMs": { @@ -13067,21 +13047,8 @@ "id", "groupId", "memberType", - "monitorId", - "serviceId", - "name", - "slug", - "subscriptionId", "status", - "effectiveFrequency", - "createdAt", - "uptime24h", - "chartData", - "avgLatencyMs", - "p95LatencyMs", - "lastCheckedAt", - "monitorType", - "environmentName" + "createdAt" ] }, "ResponseSizeAssertion": { @@ -13184,9 +13151,7 @@ "required": [ "currentStatus", "latestPerRegion", - "chartData", - "uptime24h", - "uptimeWindow" + "chartData" ] }, "RetryStrategy": { @@ -13287,12 +13252,6 @@ "externalId", "title", "status", - "impact", - "shortlink", - "scheduledFor", - "scheduledUntil", - "startedAt", - "completedAt", "affectedComponents", "updates" ] @@ -13344,8 +13303,7 @@ "dekVersion", "valueHash", "createdAt", - "updatedAt", - "usedByMonitors" + "updatedAt" ] }, "SeoMetadataDto": { @@ -13367,12 +13325,7 @@ "nullable": true } }, - "description": "Admin-editable SEO metadata for pSEO pages", - "required": [ - "shortDescription", - "description", - "about" - ] + "description": "Admin-editable SEO metadata for pSEO pages" }, "ServiceCatalogDto": { "type": "object", @@ -13451,21 +13404,15 @@ "id", "slug", "name", - "category", - "officialStatusUrl", - "developerContext", - "logoUrl", "adapterType", "pollingIntervalSeconds", "enabled", "published", - "overallStatus", "createdAt", "updatedAt", "componentCount", "activeIncidentCount", - "dataCompleteness", - "uptime30d" + "dataCompleteness" ] }, "ServiceComponentDto": { @@ -13537,7 +13484,12 @@ "nullable": true }, "uptime": { - "$ref": "#/components/schemas/ComponentUptimeSummaryDto" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/ComponentUptimeSummaryDto" + } + ] }, "statusChangedAt": { "type": "string", @@ -13562,20 +13514,11 @@ "externalId", "name", "status", - "description", - "groupId", - "position", "showcase", "onlyShowIfDegraded", - "startDate", - "vendorCreatedAt", "lifecycleStatus", "dataType", "hasUptime", - "region", - "groupName", - "uptime", - "statusChangedAt", "firstSeenAt", "lastSeenAt", "group" @@ -13629,7 +13572,12 @@ "format": "date-time" }, "currentStatus": { - "$ref": "#/components/schemas/ServiceStatusDto" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/ServiceStatusDto" + } + ] }, "recentIncidents": { "type": "array", @@ -13644,7 +13592,12 @@ } }, "uptime": { - "$ref": "#/components/schemas/ComponentUptimeSummaryDto" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/ComponentUptimeSummaryDto" + } + ] }, "activeMaintenances": { "type": "array", @@ -13656,7 +13609,12 @@ "type": "string" }, "seoMetadata": { - "$ref": "#/components/schemas/SeoMetadataDto" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/SeoMetadataDto" + } + ] }, "relatedServices": { "type": "array", @@ -13670,23 +13628,15 @@ "id", "slug", "name", - "category", - "officialStatusUrl", - "developerContext", - "logoUrl", "adapterType", "pollingIntervalSeconds", "enabled", "createdAt", "updatedAt", - "currentStatus", "recentIncidents", "components", - "uptime", "activeMaintenances", - "dataCompleteness", - "seoMetadata", - "relatedServices" + "dataCompleteness" ] }, "ServiceIncidentDetailDto": { @@ -13729,8 +13679,7 @@ "type": "array", "nullable": true, "items": { - "type": "string", - "nullable": true + "type": "string" } }, "updates": { @@ -13744,12 +13693,6 @@ "id", "title", "status", - "impact", - "startedAt", - "resolvedAt", - "detectedAt", - "shortlink", - "affectedComponents", "updates" ] }, @@ -13819,18 +13762,8 @@ "required": [ "id", "serviceId", - "serviceSlug", - "serviceName", - "externalId", "title", - "status", - "impact", - "startedAt", - "resolvedAt", - "updatedAt", - "shortlink", - "detectedAt", - "vendorCreatedAt" + "status" ] }, "ServiceIncidentUpdateDto": { @@ -13850,42 +13783,160 @@ } }, "required": [ - "status", - "body", - "displayAt" + "status" + ] + }, + "ServiceLiveStatusDto": { + "type": "object", + "properties": { + "overallStatus": { + "type": "string", + "description": "Current overall status of the service, e.g. operational, degraded_performance", + "nullable": true + }, + "componentStatuses": { + "type": "array", + "description": "Current status of each active component", + "items": { + "$ref": "#/components/schemas/ComponentStatusDto" + } + }, + "activeIncidentCount": { + "type": "integer", + "description": "Number of currently unresolved incidents", + "format": "int32" + }, + "lastPolledAt": { + "type": "string", + "description": "ISO 8601 timestamp of the last status poll", + "nullable": true + } + }, + "required": [ + "componentStatuses", + "activeIncidentCount" + ] + }, + "ServicePollResultDto": { + "type": "object", + "properties": { + "serviceId": { + "type": "string", + "description": "Service ID", + "format": "uuid" + }, + "timestamp": { + "type": "string", + "description": "Timestamp when the poll was executed (ISO 8601)", + "format": "date-time" + }, + "overallStatus": { + "type": "string", + "description": "Overall status of the service at time of poll", + "nullable": true, + "example": "operational" + }, + "responseTimeMs": { + "type": "integer", + "description": "Response time of the poll in milliseconds", + "format": "int32", + "nullable": true, + "example": 245 + }, + "httpStatusCode": { + "type": "integer", + "description": "HTTP status code from the upstream status page", + "format": "int32", + "nullable": true, + "example": 200 + }, + "passed": { + "type": "boolean", + "description": "Whether the poll succeeded", + "example": true + }, + "failureReason": { + "type": "string", + "description": "Reason for failure when passed=false", + "nullable": true + }, + "componentCount": { + "type": "integer", + "description": "Number of components reported by the service", + "format": "int32", + "example": 12 + }, + "degradedCount": { + "type": "integer", + "description": "Number of degraded or non-operational components", + "format": "int32", + "example": 1 + } + }, + "description": "A single poll result from the status poller", + "required": [ + "serviceId", + "timestamp", + "passed", + "componentCount", + "degradedCount" ] }, - "ServiceLiveStatusDto": { + "ServicePollSummaryDto": { "type": "object", "properties": { - "overallStatus": { - "type": "string", - "description": "Current overall status of the service, e.g. operational, degraded_performance", - "nullable": true + "uptimePercentage": { + "type": "number", + "description": "Uptime percentage over the requested window; null when no data", + "format": "double", + "nullable": true, + "example": 99.95 }, - "componentStatuses": { - "type": "array", - "description": "Current status of each active component", - "items": { - "$ref": "#/components/schemas/ComponentStatusDto" - } + "totalPolls": { + "type": "integer", + "description": "Total number of polls executed", + "format": "int64", + "example": 4320 }, - "activeIncidentCount": { + "passedPolls": { "type": "integer", - "description": "Number of currently unresolved incidents", - "format": "int32" + "description": "Number of polls that succeeded", + "format": "int64", + "example": 4318 }, - "lastPolledAt": { + "avgResponseTimeMs": { + "type": "number", + "description": "Average response time in milliseconds; null when no data", + "format": "double", + "nullable": true, + "example": 312.5 + }, + "p95ResponseTimeMs": { + "type": "number", + "description": "95th-percentile response time in milliseconds; null when no data", + "format": "double", + "nullable": true, + "example": 580 + }, + "window": { "type": "string", - "description": "ISO 8601 timestamp of the last status poll", - "nullable": true + "description": "Time window used for the summary", + "example": "30d" + }, + "chartData": { + "type": "array", + "description": "Time-bucketed chart data for response time and uptime", + "items": { + "$ref": "#/components/schemas/PollChartBucketDto" + } } }, + "description": "Aggregated poll metrics and chart data for a service", "required": [ - "overallStatus", - "componentStatuses", - "activeIncidentCount", - "lastPolledAt" + "totalPolls", + "passedPolls", + "window", + "chartData" ] }, "ServiceStatusDto": { @@ -13901,8 +13952,7 @@ } }, "required": [ - "overallStatus", - "lastPolledAt" + "overallStatus" ] }, "ServiceSubscribeRequest": { @@ -13920,11 +13970,7 @@ "nullable": true } }, - "description": "Optional body for subscribing to a specific component of a service", - "required": [ - "componentId", - "alertSensitivity" - ] + "description": "Optional body for subscribing to a specific component of a service" }, "ServiceSubscriptionDto": { "type": "object", @@ -13980,7 +14026,12 @@ "nullable": true }, "component": { - "$ref": "#/components/schemas/ServiceComponentDto" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/ServiceComponentDto" + } + ] }, "alertSensitivity": { "type": "string", @@ -14003,15 +14054,9 @@ "serviceId", "slug", "name", - "category", - "officialStatusUrl", "adapterType", "pollingIntervalSeconds", "enabled", - "logoUrl", - "overallStatus", - "componentId", - "component", "alertSensitivity", "subscribedAt" ] @@ -14052,11 +14097,9 @@ }, "description": "Uptime response with per-bucket breakdown and overall percentage for the period", "required": [ - "overallUptimePct", "period", "granularity", - "buckets", - "source" + "buckets" ] }, "SetAlertChannelsRequest": { @@ -14260,11 +14303,9 @@ "properties": { "data": { "type": "array", - "nullable": true, "items": { "type": "string", - "format": "uuid", - "nullable": true + "format": "uuid" } } }, @@ -14277,8 +14318,7 @@ "properties": { "data": { "type": "integer", - "format": "int64", - "nullable": true + "format": "int64" } }, "required": [ @@ -14303,12 +14343,10 @@ "type": "object", "additionalProperties": { "type": "array", - "nullable": true, "items": { "$ref": "#/components/schemas/ComponentUptimeDayDto" } - }, - "nullable": true + } } }, "required": [ @@ -14491,6 +14529,17 @@ "data" ] }, + "SingleValueResponseServicePollSummaryDto": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ServicePollSummaryDto" + } + }, + "required": [ + "data" + ] + }, "SingleValueResponseServiceSubscriptionDto": { "type": "object", "properties": { @@ -14583,8 +14632,7 @@ "type": "object", "properties": { "data": { - "type": "string", - "nullable": true + "type": "string" } }, "required": [ @@ -14701,7 +14749,10 @@ "description": "Optional mention text included in notifications, e.g. @channel", "nullable": true } - } + }, + "required": [ + "webhookUrl" + ] } ] }, @@ -14753,7 +14804,11 @@ "range" ] } - } + }, + "required": [ + "expected", + "operator" + ] } ] }, @@ -14859,19 +14914,7 @@ }, "description": "Updated branding configuration; null preserves current", "required": [ - "logoUrl", - "faviconUrl", - "brandColor", - "pageBackground", - "cardBackground", - "textColor", - "borderColor", - "headerStyle", - "theme", - "reportUrl", - "hidePoweredBy", - "customCss", - "customHeadHtml" + "hidePoweredBy" ] }, "StatusPageComponentDto": { @@ -14956,18 +14999,13 @@ "required": [ "id", "statusPageId", - "groupId", "name", - "description", "type", - "monitorId", - "resourceGroupId", "currentStatus", "showUptime", "displayOrder", "pageOrder", "excludeFromOverall", - "startDate", "createdAt", "updatedAt" ] @@ -15021,11 +15059,9 @@ "id", "statusPageId", "name", - "description", "displayOrder", "pageOrder", "collapsed", - "components", "createdAt", "updatedAt" ] @@ -15093,8 +15129,6 @@ "verificationMethod", "verificationToken", "verificationCnameTarget", - "verifiedAt", - "verificationError", "createdAt", "updatedAt", "primary" @@ -15183,14 +15217,10 @@ "workspaceId", "name", "slug", - "description", "branding", "visibility", "enabled", "incidentMode", - "componentCount", - "subscriberCount", - "overallStatus", "createdAt", "updatedAt" ] @@ -15332,18 +15362,8 @@ "status", "impact", "scheduled", - "scheduledFor", - "scheduledUntil", "autoResolve", - "incidentId", "startedAt", - "publishedAt", - "resolvedAt", - "createdByUserId", - "postmortemBody", - "postmortemUrl", - "affectedComponents", - "updates", "createdAt", "updatedAt" ] @@ -15392,7 +15412,6 @@ "status", "body", "createdBy", - "createdByUserId", "notifySubscribers", "createdAt" ] @@ -15472,9 +15491,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultAlertDeliveryDto": { @@ -15506,9 +15523,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultApiKeyDto": { @@ -15540,9 +15555,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultAuditEventDto": { @@ -15574,9 +15587,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultCategoryDto": { @@ -15608,9 +15619,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultComponentUptimeDayDto": { @@ -15642,9 +15651,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultDeliveryAttemptDto": { @@ -15676,9 +15683,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultEnvironmentDto": { @@ -15710,9 +15715,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultIncidentDto": { @@ -15744,9 +15747,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultIntegrationDto": { @@ -15778,9 +15779,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultInviteDto": { @@ -15812,9 +15811,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultMaintenanceWindowDto": { @@ -15846,9 +15843,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultMemberDto": { @@ -15880,9 +15875,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultMonitorDto": { @@ -15914,9 +15907,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultMonitorVersionDto": { @@ -15948,9 +15939,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultNotificationDispatchDto": { @@ -15982,9 +15971,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultNotificationDto": { @@ -16016,9 +16003,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultNotificationPolicyDto": { @@ -16050,9 +16035,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultResourceGroupDto": { @@ -16084,9 +16067,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultScheduledMaintenanceDto": { @@ -16118,9 +16099,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultSecretDto": { @@ -16152,9 +16131,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultServiceComponentDto": { @@ -16186,9 +16163,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultServiceIncidentDto": { @@ -16220,9 +16195,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultServiceSubscriptionDto": { @@ -16254,9 +16227,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultStatusPageComponentDto": { @@ -16288,9 +16259,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultStatusPageComponentGroupDto": { @@ -16322,9 +16291,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultStatusPageCustomDomainDto": { @@ -16356,9 +16323,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultStatusPageDto": { @@ -16390,9 +16355,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultStatusPageIncidentDto": { @@ -16424,9 +16387,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultStatusPageSubscriberDto": { @@ -16458,9 +16419,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultTagDto": { @@ -16492,9 +16451,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultWebhookDeliveryDto": { @@ -16526,9 +16483,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultWebhookEndpointDto": { @@ -16560,9 +16515,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TableValueResultWorkspaceDto": { @@ -16594,9 +16547,7 @@ "required": [ "data", "hasNext", - "hasPrev", - "totalElements", - "totalPages" + "hasPrev" ] }, "TagDto": { @@ -16641,35 +16592,6 @@ "updatedAt" ] }, - "Tcp": { - "type": "object", - "description": "TCP check-type-specific details", - "allOf": [ - { - "$ref": "#/components/schemas/CheckTypeDetailsDto" - }, - { - "type": "object", - "properties": { - "host": { - "type": "string", - "description": "Target host", - "example": "db.example.com" - }, - "port": { - "type": "integer", - "description": "Target port", - "format": "int32", - "example": 5432 - }, - "connected": { - "type": "boolean", - "description": "Whether a TCP connection was established" - } - } - } - ] - }, "TcpConnectsAssertion": { "type": "object", "allOf": [ @@ -16708,7 +16630,10 @@ "format": "int32", "nullable": true } - } + }, + "required": [ + "host" + ] } ] }, @@ -16765,7 +16690,10 @@ "type": "string", "description": "Microsoft Teams incoming webhook URL" } - } + }, + "required": [ + "webhookUrl" + ] } ] }, @@ -16869,8 +16797,7 @@ "nullable": true, "items": { "type": "string", - "description": "Affected region identifiers to test against (monitoring events)", - "nullable": true + "description": "Affected region identifiers to test against (monitoring events)" } }, "eventType": { @@ -16901,22 +16828,11 @@ "items": { "type": "string", "description": "Resource group UUIDs the entity belongs to, for resource_group_id_in rules", - "format": "uuid", - "nullable": true + "format": "uuid" } } }, - "description": "Event context for a dry-run match evaluation against a notification policy", - "required": [ - "severity", - "monitorId", - "regions", - "eventType", - "monitorType", - "serviceId", - "componentName", - "resourceGroupIds" - ] + "description": "Event context for a dry-run match evaluation against a notification policy" }, "TestWebhookEndpointRequest": { "type": "object", @@ -16927,10 +16843,7 @@ "nullable": true } }, - "description": "Event type to use for a test webhook delivery", - "required": [ - "eventType" - ] + "description": "Event type to use for a test webhook delivery" }, "TlsInfoDto": { "type": "object", @@ -16947,8 +16860,7 @@ "nullable": true, "items": { "type": "string", - "description": "Subject Alternative Names", - "nullable": true + "description": "Subject Alternative Names" } }, "issuerCn": { @@ -16995,19 +16907,7 @@ "nullable": true } }, - "description": "TLS/SSL certificate details for HTTPS targets", - "required": [ - "subjectCn", - "subjectSan", - "issuerCn", - "issuerOrg", - "notBefore", - "notAfter", - "serialNumber", - "tlsVersion", - "cipherSuite", - "chainValid" - ] + "description": "TLS/SSL certificate details for HTTPS targets" }, "TriggerRule": { "required": [ @@ -17315,12 +17215,7 @@ "description": "Whether this is the default environment; null preserves current", "nullable": true } - }, - "required": [ - "name", - "variables", - "isDefault" - ] + } }, "UpdateIncidentPolicyRequest": { "required": [ @@ -17420,24 +17315,10 @@ "nullable": true }, "config": { - "oneOf": [ - { - "$ref": "#/components/schemas/DnsMonitorConfig" - }, - { - "$ref": "#/components/schemas/HeartbeatMonitorConfig" - }, - { - "$ref": "#/components/schemas/HttpMonitorConfig" - }, - { - "$ref": "#/components/schemas/IcmpMonitorConfig" - }, - { - "$ref": "#/components/schemas/McpServerMonitorConfig" - }, + "nullable": true, + "allOf": [ { - "$ref": "#/components/schemas/TcpMonitorConfig" + "$ref": "#/components/schemas/MonitorConfig" } ] }, @@ -17458,8 +17339,7 @@ "nullable": true, "items": { "type": "string", - "description": "New probe regions; null preserves current", - "nullable": true + "description": "New probe regions; null preserves current" } }, "managedBy": { @@ -17492,18 +17372,10 @@ } }, "auth": { - "oneOf": [ - { - "$ref": "#/components/schemas/ApiKeyAuthConfig" - }, - { - "$ref": "#/components/schemas/BasicAuthConfig" - }, - { - "$ref": "#/components/schemas/BearerAuthConfig" - }, + "nullable": true, + "allOf": [ { - "$ref": "#/components/schemas/HeaderAuthConfig" + "$ref": "#/components/schemas/MonitorAuthConfig" } ] }, @@ -17513,7 +17385,12 @@ "nullable": true }, "incidentPolicy": { - "$ref": "#/components/schemas/UpdateIncidentPolicyRequest" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/UpdateIncidentPolicyRequest" + } + ] }, "alertChannelIds": { "type": "array", @@ -17522,28 +17399,18 @@ "items": { "type": "string", "description": "Replace alert channel list; null preserves current", - "format": "uuid", - "nullable": true + "format": "uuid" } }, "tags": { - "$ref": "#/components/schemas/AddMonitorTagsRequest" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/AddMonitorTagsRequest" + } + ] } - }, - "required": [ - "name", - "frequencySeconds", - "enabled", - "regions", - "managedBy", - "environmentId", - "clearEnvironmentId", - "assertions", - "clearAuth", - "incidentPolicy", - "alertChannelIds", - "tags" - ] + } }, "UpdateNotificationPolicyRequest": { "type": "object", @@ -17659,12 +17526,16 @@ "nullable": true, "items": { "type": "string", - "description": "Default regions for member monitors; null clears", - "nullable": true + "description": "Default regions for member monitors; null clears" } }, "defaultRetryStrategy": { - "$ref": "#/components/schemas/RetryStrategy" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RetryStrategy" + } + ] }, "defaultAlertChannels": { "type": "array", @@ -17673,8 +17544,7 @@ "items": { "type": "string", "description": "Default alert channel IDs for member monitors; null clears", - "format": "uuid", - "nullable": true + "format": "uuid" } }, "defaultEnvironmentId": { @@ -17767,13 +17637,7 @@ "description": "Whether the group is collapsed by default; null preserves current", "nullable": true } - }, - "required": [ - "name", - "description", - "displayOrder", - "collapsed" - ] + } }, "UpdateStatusPageComponentRequest": { "type": "object", @@ -17825,17 +17689,7 @@ "format": "date", "nullable": true } - }, - "required": [ - "name", - "description", - "groupId", - "removeFromGroup", - "showUptime", - "displayOrder", - "excludeFromOverall", - "startDate" - ] + } }, "UpdateStatusPageIncidentRequest": { "type": "object", @@ -17890,15 +17744,7 @@ "description": "URL to an external postmortem document; empty string clears", "nullable": true } - }, - "required": [ - "title", - "status", - "impact", - "affectedComponents", - "postmortemBody", - "postmortemUrl" - ] + } }, "UpdateStatusPageRequest": { "type": "object", @@ -17918,7 +17764,12 @@ "nullable": true }, "branding": { - "$ref": "#/components/schemas/StatusPageBranding" + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/StatusPageBranding" + } + ] }, "visibility": { "type": "string", @@ -17945,15 +17796,7 @@ "AUTOMATIC" ] } - }, - "required": [ - "name", - "description", - "branding", - "visibility", - "enabled", - "incidentMode" - ] + } }, "UpdateTagRequest": { "type": "object", @@ -17972,11 +17815,7 @@ "nullable": true } }, - "description": "Request body for updating a tag; null fields are left unchanged", - "required": [ - "name", - "color" - ] + "description": "Request body for updating a tag; null fields are left unchanged" }, "UpdateWebhookEndpointRequest": { "type": "object", @@ -18001,8 +17840,7 @@ "nullable": true, "items": { "type": "string", - "description": "Replace subscribed events; null preserves current", - "nullable": true + "description": "Replace subscribed events; null preserves current" } }, "enabled": { @@ -18010,13 +17848,7 @@ "description": "Enable or disable delivery; null preserves current", "nullable": true } - }, - "required": [ - "url", - "description", - "subscribedEvents", - "enabled" - ] + } }, "UpdateWorkspaceRequest": { "required": [ @@ -18059,7 +17891,6 @@ "description": "Uptime statistics for a single time bucket", "required": [ "timestamp", - "uptimePct", "totalPolls" ] }, @@ -18102,11 +17933,8 @@ }, "description": "Uptime statistics aggregated from continuous aggregates", "required": [ - "uptimePercentage", "totalChecks", - "passedChecks", - "avgLatencyMs", - "p95LatencyMs" + "passedChecks" ] }, "WebhookChannelConfig": { @@ -18141,7 +17969,10 @@ "description": "Additional HTTP headers to include in webhook requests", "nullable": true } - } + }, + "required": [ + "url" + ] } ] }, @@ -18215,12 +18046,6 @@ "status", "attemptCount", "maxAttempts", - "responseStatus", - "responseLatencyMs", - "errorMessage", - "deliveredAt", - "failedAt", - "nextRetryAt", "createdAt" ] }, @@ -18284,12 +18109,9 @@ "required": [ "id", "url", - "description", "subscribedEvents", "enabled", "consecutiveFailures", - "disabledReason", - "disabledAt", "createdAt", "updatedAt" ] @@ -18344,8 +18166,7 @@ } }, "required": [ - "configured", - "maskedSecret" + "configured" ] }, "WebhookTestResult": { @@ -18370,9 +18191,7 @@ }, "required": [ "success", - "statusCode", - "message", - "durationMs" + "message" ] }, "WorkspaceDto": { diff --git a/package-lock.json b/package-lock.json index 5cdda25..12a9656 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,8 @@ "cli-table3": "^0.6.5", "lodash-es": "^4.18.1", "openapi-fetch": "^0.17.0", - "yaml": "^2.8.3" + "yaml": "^2.8.3", + "zod": "^3.25.76" }, "bin": { "devhelm": "bin/run.js" @@ -6680,6 +6681,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index d4108b2..071813c 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,8 @@ "cli-table3": "^0.6.5", "lodash-es": "^4.18.1", "openapi-fetch": "^0.17.0", - "yaml": "^2.8.3" + "yaml": "^2.8.3", + "zod": "^3.25.76" }, "devDependencies": { "@types/lodash-es": "^4.17.12", diff --git a/src/commands/deploy/index.ts b/src/commands/deploy/index.ts index b443f89..f48ea1c 100644 --- a/src/commands/deploy/index.ts +++ b/src/commands/deploy/index.ts @@ -3,7 +3,7 @@ import {Command, Flags} from '@oclif/core' import {createApiClient, apiPost, apiDelete} from '../../lib/api-client.js' import {resolveToken, resolveApiUrl} from '../../lib/auth.js' import {EXIT_CODES} from '../../lib/errors.js' -import {loadConfig, validate, fetchAllRefs, diff, formatPlan, changesetToJson, apply, writeState, buildState} from '../../lib/yaml/index.js' +import {loadConfig, validate, validatePlanRefs, fetchAllRefs, registerYamlPendingRefs, diff, formatPlan, changesetToJson, apply, writeState, buildStateV2, readState, emptyState, processMovedBlocks, resourceAddress, StateFileCorruptError} from '../../lib/yaml/index.js' import {checkEntitlements, formatEntitlementWarnings} from '../../lib/yaml/entitlements.js' const DEFAULT_LOCK_TTL = 30 @@ -81,7 +81,7 @@ export default class Deploy extends Command { try { config = loadConfig(flags.file) } catch (err) { - this.error((err as Error).message, {exit: 1}) + this.error(err instanceof Error ? err.message : String(err), {exit: 1}) } const result = validate(config) @@ -104,10 +104,55 @@ export default class Deploy extends Command { verbose: flags.verbose, }) + let currentState + try { + currentState = readState() ?? emptyState() + } catch (err) { + if (err instanceof StateFileCorruptError) { + this.error(err.message, {exit: 1}) + } + throw err + } + + if (config.moved && config.moved.length > 0) { + const moveWarnings = processMovedBlocks(currentState, config.moved) + for (const w of moveWarnings) { + if (!isJson) this.warn(w) + } + writeState(currentState) + } + if (!isJson) this.log('Fetching current state from API...') - const refs = await fetchAllRefs(client) + const refs = await fetchAllRefs(client, currentState) + // Pre-register YAML-only resources with placeholder IDs so existing + // resources can resolve references to brand-new peers during snapshot + // computation. The applier overwrites placeholders with real IDs as + // creates execute, so dependent updates pick up the right ID. + registerYamlPendingRefs(refs, config) + + if (refs.collisions.length > 0 && !isJson) { + for (const c of refs.collisions) { + this.warn( + `Duplicate ${c.refType} reference "${c.refKey}" — ${c.apiIds.length} API resources share this name. ` + + `Using ${c.winnerApiId}. Rename one in the API or use a \`moved\` block to disambiguate.`, + ) + } + } + + const planResult = validatePlanRefs(config, refs) + if (planResult.errors.length > 0) { + this.log(`\nValidation failed: ${planResult.errors.length} error(s)\n`) + for (const e of planResult.errors) { + this.log(` ✗ ${e.path}: ${e.message}`) + } + this.error('Fix validation errors before deploying', {exit: 4}) + } - const changeset = diff(config, refs, {prune: flags.prune || flags['prune-all'], pruneAll: flags['prune-all']}) + const changeset = diff( + config, refs, + {prune: flags.prune || flags['prune-all'], pruneAll: flags['prune-all']}, + currentState, + ) const entitlementCheck = await checkEntitlements(client, changeset) @@ -168,7 +213,7 @@ export default class Deploy extends Command { try { if (!isJson) this.log('Applying changes...') - const applyResult = await apply(changeset, refs, client) + const applyResult = await apply(changeset, refs, client, currentState) if (isJson) { this.log(JSON.stringify({ @@ -177,8 +222,9 @@ export default class Deploy extends Command { }, null, 2)) } else { for (const s of applyResult.succeeded) { - const icon = s.action === 'delete' ? '-' : s.action === 'update' ? '~' : '+' - this.log(` ${icon} ${s.resourceType} "${s.refKey}" — ${s.action}d`) + const icon = iconForAction(s.action) + const pastTense = pastTenseForAction(s.action) + this.log(` ${icon} ${s.resourceType} "${s.refKey}" — ${pastTense}`) } if (applyResult.failed.length > 0) { @@ -191,7 +237,16 @@ export default class Deploy extends Command { this.log(`\nDone: ${applyResult.succeeded.length} succeeded, ${applyResult.failed.length} failed.`) } - writeState(buildState(applyResult.stateEntries)) + const deletedAddresses = new Set( + applyResult.deletedRefKeys.map((d) => resourceAddress(d.resourceType, d.refKey)), + ) + const newState = buildStateV2(applyResult.stateEntries, currentState.serial) + for (const [addr, entry] of Object.entries(currentState.resources)) { + if (!(addr in newState.resources) && !deletedAddresses.has(addr)) { + newState.resources[addr] = entry + } + } + writeState(newState) if (applyResult.failed.length > 0) { this.exit(EXIT_CODES.PARTIAL_FAILURE) @@ -257,6 +312,35 @@ export default class Deploy extends Command { private async releaseLock(client: ReturnType, lockId: string): Promise { try { await apiDelete(client, `/api/v1/deploy/lock/${lockId}`) - } catch { /* best-effort release */ } + } catch (err) { + // Best-effort: lock auto-expires via TTL, but surface the failure so users + // can investigate if locks accumulate (e.g. API connectivity issues). + const msg = err instanceof Error ? err.message : String(err) + this.warn(`Failed to release deploy lock ${lockId}: ${msg} (lock will auto-expire)`) + } + } +} + +/** + * Icon + past-tense label for an apply action. Supports core CRUD plus + * membership-style actions (`add`, `remove`). Unknown actions get a neutral `•`. + */ +function iconForAction(action: string): string { + switch (action) { + case 'create': case 'add': return '+' + case 'update': return '~' + case 'delete': case 'remove': return '-' + default: return '•' + } +} + +function pastTenseForAction(action: string): string { + switch (action) { + case 'create': return 'created' + case 'update': return 'updated' + case 'delete': return 'deleted' + case 'add': return 'added' + case 'remove': return 'removed' + default: return action } } diff --git a/src/commands/import.ts b/src/commands/import.ts new file mode 100644 index 0000000..6bd94df --- /dev/null +++ b/src/commands/import.ts @@ -0,0 +1,141 @@ +import {Command, Args, Flags} from '@oclif/core' +import type {components} from '../lib/api.generated.js' +import {createApiClient} from '../lib/api-client.js' +import {resolveToken, resolveApiUrl} from '../lib/auth.js' +import {fetchAllRefs} from '../lib/yaml/resolver.js' +import {allHandlers} from '../lib/yaml/handlers.js' +import {fetchPaginated} from '../lib/typed-api.js' +import {readState, writeState, emptyState, upsertStateEntry, resourceAddress, StateFileCorruptError} from '../lib/yaml/state.js' +import type {ChildStateEntry} from '../lib/yaml/state.js' +import type {HandledResourceType} from '../lib/yaml/types.js' + +type Schemas = components['schemas'] + +const VALID_TYPES = [ + 'monitor', 'tag', 'environment', 'secret', 'alertChannel', + 'notificationPolicy', 'webhook', 'resourceGroup', 'dependency', 'statusPage', +] as const + +export default class Import extends Command { + static description = 'Import an existing API resource into the deploy state (adopt without recreating)' + + static examples = [ + '<%= config.bin %> import monitor "API Health Check"', + '<%= config.bin %> import statusPage devhelm', + '<%= config.bin %> import tag production', + ] + + static args = { + type: Args.string({ + description: `Resource type (${VALID_TYPES.join(', ')})`, + required: true, + }), + name: Args.string({ + description: 'Resource name or slug to import', + required: true, + }), + } + + static flags = { + 'api-url': Flags.string({description: 'Override API base URL'}), + 'api-token': Flags.string({description: 'Override API token'}), + verbose: Flags.boolean({char: 'v', description: 'Show verbose output', default: false}), + } + + async run() { + const {args, flags} = await this.parse(Import) + + if (!VALID_TYPES.includes(args.type as typeof VALID_TYPES[number])) { + this.error(`Unknown resource type "${args.type}". Valid types: ${VALID_TYPES.join(', ')}`, {exit: 1}) + } + + const resourceType = args.type as HandledResourceType + + const token = flags['api-token'] ?? resolveToken() + if (!token) { + this.error('No API token configured. Run "devhelm auth login" or set DEVHELM_API_TOKEN.', {exit: 1}) + } + + const client = createApiClient({ + baseUrl: flags['api-url'] ?? resolveApiUrl(), + token, + verbose: flags.verbose, + }) + + const handler = allHandlers().find((h) => h.resourceType === resourceType) + if (!handler) { + this.error(`No handler for resource type "${resourceType}"`, {exit: 1}) + } + + this.log(`Fetching ${resourceType} resources...`) + const refs = await fetchAllRefs(client) + + const entry = refs.get(handler.refType, args.name) + if (!entry) { + const available = refs.allEntries(handler.refType).map((e) => e.refKey) + this.error( + `${resourceType} "${args.name}" not found in API.\n` + + (available.length > 0 + ? `Available: ${available.join(', ')}` + : 'No resources of this type exist.'), + {exit: 1}, + ) + } + + let state + try { + state = readState() ?? emptyState() + } catch (err) { + if (err instanceof StateFileCorruptError) { + this.error(err.message, {exit: 1}) + } + throw err + } + const addr = resourceAddress(resourceType, args.name) + + if (state.resources[addr]) { + this.log(`"${addr}" is already in state (API ID: ${state.resources[addr].apiId}).`) + return + } + + let children: Record = {} + if (resourceType === 'statusPage') { + children = await this.fetchStatusPageChildren(client, entry.id) + } + + upsertStateEntry(state, resourceType, args.name, entry.id, {name: args.name}, children) + writeState(state) + + const childCount = Object.keys(children).length + this.log(`Imported "${addr}" → ${entry.id}${childCount > 0 ? ` (with ${childCount} children)` : ''}`) + this.log('The resource is now tracked in state. Add it to your devhelm.yml and run "devhelm plan" to verify.') + } + + private async fetchStatusPageChildren( + client: ReturnType, + pageId: string, + ): Promise> { + const children: Record = {} + try { + const groups = await fetchPaginated( + client, `/api/v1/status-pages/${pageId}/groups`, + ) + for (const g of groups) { + const name = g.name ?? '' + if (!name) continue + children[`groups.${name}`] = {apiId: String(g.id ?? ''), attributes: {name}} + } + const components = await fetchPaginated( + client, `/api/v1/status-pages/${pageId}/components`, + ) + for (const c of components) { + const name = c.name ?? '' + if (!name) continue + children[`components.${name}`] = {apiId: String(c.id ?? ''), attributes: {name}} + } + } catch (err) { + this.warn(`Failed to fetch status page children: ${err instanceof Error ? err.message : String(err)}`) + } + return children + } +} diff --git a/src/commands/plan.ts b/src/commands/plan.ts index 1b3d019..e3ca9fd 100644 --- a/src/commands/plan.ts +++ b/src/commands/plan.ts @@ -2,7 +2,7 @@ import {Command, Flags} from '@oclif/core' import {createApiClient} from '../lib/api-client.js' import {resolveToken, resolveApiUrl} from '../lib/auth.js' import {EXIT_CODES} from '../lib/errors.js' -import {loadConfig, validate, fetchAllRefs, diff, formatPlan, changesetToJson} from '../lib/yaml/index.js' +import {loadConfig, validate, validatePlanRefs, fetchAllRefs, registerYamlPendingRefs, diff, formatPlan, changesetToJson, readState, emptyState, previewMovedBlocks, StateFileCorruptError} from '../lib/yaml/index.js' import {checkEntitlements, formatEntitlementWarnings} from '../lib/yaml/entitlements.js' export default class Plan extends Command { @@ -54,7 +54,7 @@ export default class Plan extends Command { try { config = loadConfig(flags.file) } catch (err) { - this.error((err as Error).message, {exit: 1}) + this.error(err instanceof Error ? err.message : String(err), {exit: 1}) } const result = validate(config) @@ -77,10 +77,56 @@ export default class Plan extends Command { verbose: flags.verbose, }) + let currentState + try { + currentState = readState() ?? emptyState() + } catch (err) { + if (err instanceof StateFileCorruptError) { + this.error(err.message, {exit: 1}) + } + throw err + } + + // Preview moved-block effects (warnings only) without persisting state. + // `devhelm plan` is read-only; the actual rewrite happens during `deploy`. + if (config.moved && config.moved.length > 0) { + const {state: previewState, warnings} = previewMovedBlocks(currentState, config.moved) + currentState = previewState + for (const w of warnings) { + if (flags.output !== 'json') this.warn(w) + } + } + if (flags.output !== 'json') this.log('Fetching current state from API...') - const refs = await fetchAllRefs(client) + const refs = await fetchAllRefs(client, currentState) + // Pre-register YAML-only resources with placeholder IDs so existing + // resources can resolve references to brand-new peers during snapshot + // computation (e.g. notification policy escalating into a new channel). + registerYamlPendingRefs(refs, config) + + if (refs.collisions.length > 0 && flags.output !== 'json') { + for (const c of refs.collisions) { + this.warn( + `Duplicate ${c.refType} reference "${c.refKey}" — ${c.apiIds.length} API resources share this name. ` + + `Using ${c.winnerApiId}. Rename one in the API or use a \`moved\` block to disambiguate.`, + ) + } + } + + const planResult = validatePlanRefs(config, refs) + if (planResult.errors.length > 0) { + this.log(`\nValidation failed: ${planResult.errors.length} error(s)\n`) + for (const e of planResult.errors) { + this.log(` ✗ ${e.path}: ${e.message}`) + } + this.error('Fix validation errors first', {exit: 4}) + } - const changeset = diff(config, refs, {prune: flags.prune || flags['prune-all'], pruneAll: flags['prune-all']}) + const changeset = diff( + config, refs, + {prune: flags.prune || flags['prune-all'], pruneAll: flags['prune-all']}, + currentState, + ) const entitlementCheck = await checkEntitlements(client, changeset) diff --git a/src/commands/state/pull.ts b/src/commands/state/pull.ts new file mode 100644 index 0000000..e885bb5 --- /dev/null +++ b/src/commands/state/pull.ts @@ -0,0 +1,128 @@ +import {Command, Flags} from '@oclif/core' +import type {components} from '../../lib/api.generated.js' +import {createApiClient} from '../../lib/api-client.js' +import {resolveToken, resolveApiUrl} from '../../lib/auth.js' +import {fetchAllRefs} from '../../lib/yaml/resolver.js' +import {allHandlers} from '../../lib/yaml/handlers.js' +import {fetchPaginated} from '../../lib/typed-api.js' +import {writeState, buildStateV2, readState, StateFileCorruptError} from '../../lib/yaml/state.js' +import type {ChildStateEntry} from '../../lib/yaml/state.js' + +type Schemas = components['schemas'] + +export default class StatePull extends Command { + static description = 'Reconstruct the state file from the current API state' + + static examples = [ + '<%= config.bin %> state pull', + '<%= config.bin %> state pull --dry-run', + ] + + static flags = { + 'dry-run': Flags.boolean({description: 'Show what would be written without saving', default: false}), + 'api-url': Flags.string({description: 'Override API base URL'}), + 'api-token': Flags.string({description: 'Override API token'}), + verbose: Flags.boolean({char: 'v', description: 'Show verbose output', default: false}), + } + + async run() { + const {flags} = await this.parse(StatePull) + + const token = flags['api-token'] ?? resolveToken() + if (!token) { + this.error('No API token configured. Run "devhelm auth login" or set DEVHELM_API_TOKEN.', {exit: 1}) + } + + const client = createApiClient({ + baseUrl: flags['api-url'] ?? resolveApiUrl(), + token, + verbose: flags.verbose, + }) + + this.log('Fetching all resources from API...') + const refs = await fetchAllRefs(client) + const handlers = allHandlers() + + const entries: Array<{ + resourceType: Parameters[0][number]['resourceType'] + refKey: string + apiId: string + attributes: Record + children?: Record + }> = [] + + for (const handler of handlers) { + for (const entry of refs.allEntries(handler.refType)) { + let children: Record | undefined + if (handler.resourceType === 'statusPage') { + children = await this.pullStatusPageChildren(client, entry.id) + } + entries.push({ + resourceType: handler.resourceType, + refKey: entry.refKey, + apiId: entry.id, + attributes: {name: entry.refKey}, + children, + }) + } + } + + let prevState + try { + prevState = readState() + } catch (err) { + if (err instanceof StateFileCorruptError) { + this.warn(`Existing state file is corrupt (${err.cause instanceof Error ? err.cause.message : String(err.cause)}); rebuilding from scratch.`) + prevState = undefined + } else { + throw err + } + } + const state = buildStateV2(entries, prevState?.serial ?? 0) + + if (flags['dry-run']) { + this.log(`\nWould write ${entries.length} resources to state:`) + for (const [addr, entry] of Object.entries(state.resources)) { + const childCount = Object.keys(entry.children).length + this.log(` ${addr}${childCount > 0 ? ` (${childCount} children)` : ''}`) + } + return + } + + writeState(state) + this.log(`State rebuilt with ${entries.length} resources (serial ${state.serial}).`) + } + + /** + * Fetch groups and components for a status page and flatten them into the + * `children` shape used by StateEntry. Keys are `groups.` / + * `components.` to match the child-reconciler's identity scheme. + */ + private async pullStatusPageChildren( + client: ReturnType, + pageId: string, + ): Promise> { + const children: Record = {} + try { + const groups = await fetchPaginated( + client, `/api/v1/status-pages/${pageId}/groups`, + ) + for (const g of groups) { + const name = g.name ?? '' + if (!name) continue + children[`groups.${name}`] = {apiId: String(g.id ?? ''), attributes: {name}} + } + const components = await fetchPaginated( + client, `/api/v1/status-pages/${pageId}/components`, + ) + for (const c of components) { + const name = c.name ?? '' + if (!name) continue + children[`components.${name}`] = {apiId: String(c.id ?? ''), attributes: {name}} + } + } catch (err) { + this.warn(`Failed to fetch children for status page ${pageId}: ${err instanceof Error ? err.message : String(err)}`) + } + return children + } +} diff --git a/src/commands/state/rm.ts b/src/commands/state/rm.ts new file mode 100644 index 0000000..c52da4c --- /dev/null +++ b/src/commands/state/rm.ts @@ -0,0 +1,43 @@ +import {Command, Args} from '@oclif/core' +import {readState, writeState, removeStateEntry, StateFileCorruptError} from '../../lib/yaml/state.js' + +export default class StateRm extends Command { + static description = 'Remove a resource from the state file (does not delete from API)' + + static examples = [ + '<%= config.bin %> state rm monitors.API', + '<%= config.bin %> state rm statusPages.devhelm', + ] + + static args = { + address: Args.string({ + description: 'Resource address to remove (e.g. monitors.API)', + required: true, + }), + } + + async run() { + const {args} = await this.parse(StateRm) + let state + try { + state = readState() + } catch (err) { + if (err instanceof StateFileCorruptError) { + this.error(err.message, {exit: 1}) + } + throw err + } + + if (!state) { + this.error('No state file found.', {exit: 1}) + } + + const removed = removeStateEntry(state, args.address) + if (!removed) { + this.error(`Address "${args.address}" not found in state.`, {exit: 1}) + } + + writeState(state) + this.log(`Removed "${args.address}" from state.`) + } +} diff --git a/src/commands/state/show.ts b/src/commands/state/show.ts new file mode 100644 index 0000000..fd636b7 --- /dev/null +++ b/src/commands/state/show.ts @@ -0,0 +1,59 @@ +import {Command, Flags} from '@oclif/core' +import {readState, StateFileCorruptError} from '../../lib/yaml/state.js' + +export default class StateShow extends Command { + static description = 'Display the current deploy state file' + + static examples = [ + '<%= config.bin %> state show', + '<%= config.bin %> state show --json', + ] + + static flags = { + json: Flags.boolean({description: 'Output raw JSON', default: false}), + } + + async run() { + const {flags} = await this.parse(StateShow) + let state + try { + state = readState() + } catch (err) { + if (err instanceof StateFileCorruptError) { + this.error(err.message, {exit: 1}) + } + throw err + } + + if (!state) { + this.log('No state file found. Run "devhelm deploy" to create one.') + return + } + + if (flags.json) { + this.log(JSON.stringify(state, null, 2)) + return + } + + this.log(`State version: ${state.version}`) + this.log(`Serial: ${state.serial}`) + this.log(`Last deployed: ${state.lastDeployedAt}`) + + const entries = Object.entries(state.resources) + if (entries.length === 0) { + this.log('\nNo resources tracked.') + return + } + + this.log(`\nResources (${entries.length}):`) + for (const [address, entry] of entries) { + this.log(` ${address} → ${entry.apiId}`) + const childKeys = Object.keys(entry.children) + if (childKeys.length > 0) { + for (const ck of childKeys) { + this.log(` ${ck} → ${entry.children[ck].apiId}`) + } + } + } + } +} diff --git a/src/commands/status-pages/components/create.ts b/src/commands/status-pages/components/create.ts index 089260a..87e09ad 100644 --- a/src/commands/status-pages/components/create.ts +++ b/src/commands/status-pages/components/create.ts @@ -17,6 +17,7 @@ export default class StatusPagesComponentsCreate extends Command { 'display-order': Flags.integer({description: 'Position in the component list'}), 'exclude-from-overall': Flags.boolean({description: 'Exclude from overall status calculation'}), 'show-uptime': Flags.boolean({description: 'Whether to show the uptime bar', allowNo: true}), + 'start-date': Flags.string({description: 'Date (YYYY-MM-DD) from which to start showing uptime data'}), } async run() { @@ -30,6 +31,7 @@ export default class StatusPagesComponentsCreate extends Command { if (flags['display-order'] !== undefined) body.displayOrder = flags['display-order'] if (flags['exclude-from-overall'] !== undefined) body.excludeFromOverall = flags['exclude-from-overall'] if (flags['show-uptime'] !== undefined) body.showUptime = flags['show-uptime'] + if (flags['start-date'] !== undefined) body.startDate = flags['start-date'] const resp = await apiPost<{data?: unknown}>(client, `/api/v1/status-pages/${args.id}/components`, body) display(this, resp.data ?? resp, flags.output) } diff --git a/src/commands/status-pages/components/list.ts b/src/commands/status-pages/components/list.ts index 80608f4..72c53e2 100644 --- a/src/commands/status-pages/components/list.ts +++ b/src/commands/status-pages/components/list.ts @@ -1,7 +1,10 @@ import {Command, Args} from '@oclif/core' +import type {components} from '../../../lib/api.generated.js' import {globalFlags, buildClient, display} from '../../../lib/base-command.js' import {fetchPaginated} from '../../../lib/typed-api.js' +type StatusPageComponent = components['schemas']['StatusPageComponentDto'] + export default class StatusPagesComponentsList extends Command { static description = 'List components on a status page' static examples = ['<%= config.bin %> status-pages components list '] @@ -11,13 +14,13 @@ export default class StatusPagesComponentsList extends Command { async run() { const {args, flags} = await this.parse(StatusPagesComponentsList) const client = buildClient(flags) - const items = await fetchPaginated(client, `/api/v1/status-pages/${args.id}/components`) + const items = await fetchPaginated(client, `/api/v1/status-pages/${args.id}/components`) display(this, items, flags.output, [ - {header: 'ID', get: (r: any) => r.id ?? ''}, - {header: 'NAME', get: (r: any) => r.name ?? ''}, - {header: 'TYPE', get: (r: any) => r.type ?? ''}, - {header: 'STATUS', get: (r: any) => r.currentStatus ?? ''}, - {header: 'GROUP', get: (r: any) => r.groupId ?? ''}, + {header: 'ID', get: (r: StatusPageComponent) => r.id ?? ''}, + {header: 'NAME', get: (r: StatusPageComponent) => r.name ?? ''}, + {header: 'TYPE', get: (r: StatusPageComponent) => r.type ?? ''}, + {header: 'STATUS', get: (r: StatusPageComponent) => r.currentStatus ?? ''}, + {header: 'GROUP', get: (r: StatusPageComponent) => r.groupId ?? ''}, ]) } } diff --git a/src/commands/status-pages/components/update.ts b/src/commands/status-pages/components/update.ts index 484c85e..1c32c4b 100644 --- a/src/commands/status-pages/components/update.ts +++ b/src/commands/status-pages/components/update.ts @@ -18,6 +18,7 @@ export default class StatusPagesComponentsUpdate extends Command { 'display-order': Flags.integer({description: 'Position in the component list'}), 'exclude-from-overall': Flags.boolean({description: 'Exclude from overall status calculation', allowNo: true}), 'show-uptime': Flags.boolean({description: 'Whether to show the uptime bar', allowNo: true}), + 'start-date': Flags.string({description: 'Date (YYYY-MM-DD) from which to start showing uptime data'}), } async run() { @@ -31,6 +32,7 @@ export default class StatusPagesComponentsUpdate extends Command { if (flags['display-order'] !== undefined) body.displayOrder = flags['display-order'] if (flags['exclude-from-overall'] !== undefined) body.excludeFromOverall = flags['exclude-from-overall'] if (flags['show-uptime'] !== undefined) body.showUptime = flags['show-uptime'] + if (flags['start-date'] !== undefined) body.startDate = flags['start-date'] const resp = await apiPut<{data?: unknown}>(client, `/api/v1/status-pages/${args.id}/components/${args['component-id']}`, body) display(this, resp.data ?? resp, flags.output) } diff --git a/src/commands/status-pages/domains/list.ts b/src/commands/status-pages/domains/list.ts index d1254c8..4c3118f 100644 --- a/src/commands/status-pages/domains/list.ts +++ b/src/commands/status-pages/domains/list.ts @@ -1,7 +1,10 @@ import {Command, Args} from '@oclif/core' +import type {components} from '../../../lib/api.generated.js' import {globalFlags, buildClient, display} from '../../../lib/base-command.js' import {fetchPaginated} from '../../../lib/typed-api.js' +type StatusPageCustomDomain = components['schemas']['StatusPageCustomDomainDto'] + export default class StatusPagesDomainsList extends Command { static description = 'List custom domains on a status page' static examples = ['<%= config.bin %> status-pages domains list '] @@ -11,12 +14,12 @@ export default class StatusPagesDomainsList extends Command { async run() { const {args, flags} = await this.parse(StatusPagesDomainsList) const client = buildClient(flags) - const items = await fetchPaginated(client, `/api/v1/status-pages/${args.id}/domains`) + const items = await fetchPaginated(client, `/api/v1/status-pages/${args.id}/domains`) display(this, items, flags.output, [ - {header: 'ID', get: (r: any) => r.id ?? ''}, - {header: 'HOSTNAME', get: (r: any) => r.hostname ?? ''}, - {header: 'VERIFIED', get: (r: any) => String(r.verified ?? '')}, - {header: 'STATUS', get: (r: any) => r.verificationStatus ?? ''}, + {header: 'ID', get: (r: StatusPageCustomDomain) => r.id ?? ''}, + {header: 'HOSTNAME', get: (r: StatusPageCustomDomain) => r.hostname ?? ''}, + {header: 'VERIFIED', get: (r: StatusPageCustomDomain) => r.verifiedAt ?? ''}, + {header: 'STATUS', get: (r: StatusPageCustomDomain) => r.status ?? ''}, ]) } } diff --git a/src/commands/status-pages/groups/list.ts b/src/commands/status-pages/groups/list.ts index 57b485a..8a9048e 100644 --- a/src/commands/status-pages/groups/list.ts +++ b/src/commands/status-pages/groups/list.ts @@ -1,7 +1,10 @@ import {Command, Args} from '@oclif/core' +import type {components} from '../../../lib/api.generated.js' import {globalFlags, buildClient, display} from '../../../lib/base-command.js' import {fetchPaginated} from '../../../lib/typed-api.js' +type StatusPageComponentGroup = components['schemas']['StatusPageComponentGroupDto'] + export default class StatusPagesGroupsList extends Command { static description = 'List component groups on a status page' static examples = ['<%= config.bin %> status-pages groups list '] @@ -11,11 +14,11 @@ export default class StatusPagesGroupsList extends Command { async run() { const {args, flags} = await this.parse(StatusPagesGroupsList) const client = buildClient(flags) - const items = await fetchPaginated(client, `/api/v1/status-pages/${args.id}/groups`) + const items = await fetchPaginated(client, `/api/v1/status-pages/${args.id}/groups`) display(this, items, flags.output, [ - {header: 'ID', get: (r: any) => r.id ?? ''}, - {header: 'NAME', get: (r: any) => r.name ?? ''}, - {header: 'ORDER', get: (r: any) => String(r.displayOrder ?? '')}, + {header: 'ID', get: (r: StatusPageComponentGroup) => r.id ?? ''}, + {header: 'NAME', get: (r: StatusPageComponentGroup) => r.name ?? ''}, + {header: 'ORDER', get: (r: StatusPageComponentGroup) => String(r.displayOrder ?? '')}, ]) } } diff --git a/src/commands/status-pages/incidents/list.ts b/src/commands/status-pages/incidents/list.ts index 5acd51c..7ad023d 100644 --- a/src/commands/status-pages/incidents/list.ts +++ b/src/commands/status-pages/incidents/list.ts @@ -1,7 +1,10 @@ import {Command, Args, Flags} from '@oclif/core' +import type {components} from '../../../lib/api.generated.js' import {globalFlags, buildClient, display} from '../../../lib/base-command.js' import {fetchPaginated} from '../../../lib/typed-api.js' +type StatusPageIncident = components['schemas']['StatusPageIncidentDto'] + export default class StatusPagesIncidentsList extends Command { static description = 'List incidents on a status page' static examples = ['<%= config.bin %> status-pages incidents list '] @@ -14,13 +17,13 @@ export default class StatusPagesIncidentsList extends Command { async run() { const {args, flags} = await this.parse(StatusPagesIncidentsList) const client = buildClient(flags) - const items = await fetchPaginated(client, `/api/v1/status-pages/${args.id}/incidents`, flags.limit) + const items = await fetchPaginated(client, `/api/v1/status-pages/${args.id}/incidents`, flags.limit) display(this, items, flags.output, [ - {header: 'ID', get: (r: any) => r.id ?? ''}, - {header: 'TITLE', get: (r: any) => r.title ?? ''}, - {header: 'IMPACT', get: (r: any) => r.impact ?? ''}, - {header: 'STATUS', get: (r: any) => r.status ?? ''}, - {header: 'PUBLISHED', get: (r: any) => r.publishedAt ?? ''}, + {header: 'ID', get: (r: StatusPageIncident) => r.id ?? ''}, + {header: 'TITLE', get: (r: StatusPageIncident) => r.title ?? ''}, + {header: 'IMPACT', get: (r: StatusPageIncident) => r.impact ?? ''}, + {header: 'STATUS', get: (r: StatusPageIncident) => r.status ?? ''}, + {header: 'PUBLISHED', get: (r: StatusPageIncident) => r.publishedAt ?? ''}, ]) } } diff --git a/src/commands/status-pages/subscribers/list.ts b/src/commands/status-pages/subscribers/list.ts index c97759c..11f728d 100644 --- a/src/commands/status-pages/subscribers/list.ts +++ b/src/commands/status-pages/subscribers/list.ts @@ -1,7 +1,10 @@ import {Command, Args, Flags} from '@oclif/core' +import type {components} from '../../../lib/api.generated.js' import {globalFlags, buildClient, display} from '../../../lib/base-command.js' import {fetchPaginated} from '../../../lib/typed-api.js' +type StatusPageSubscriber = components['schemas']['StatusPageSubscriberDto'] + export default class StatusPagesSubscribersList extends Command { static description = 'List subscribers on a status page' static examples = ['<%= config.bin %> status-pages subscribers list '] @@ -14,12 +17,12 @@ export default class StatusPagesSubscribersList extends Command { async run() { const {args, flags} = await this.parse(StatusPagesSubscribersList) const client = buildClient(flags) - const items = await fetchPaginated(client, `/api/v1/status-pages/${args.id}/subscribers`, flags.limit) + const items = await fetchPaginated(client, `/api/v1/status-pages/${args.id}/subscribers`, flags.limit) display(this, items, flags.output, [ - {header: 'ID', get: (r: any) => r.id ?? ''}, - {header: 'EMAIL', get: (r: any) => r.email ?? ''}, - {header: 'CONFIRMED', get: (r: any) => String(r.confirmed ?? '')}, - {header: 'CREATED', get: (r: any) => r.createdAt ?? ''}, + {header: 'ID', get: (r: StatusPageSubscriber) => r.id ?? ''}, + {header: 'EMAIL', get: (r: StatusPageSubscriber) => r.email ?? ''}, + {header: 'CONFIRMED', get: (r: StatusPageSubscriber) => String(r.confirmed ?? '')}, + {header: 'CREATED', get: (r: StatusPageSubscriber) => r.createdAt ?? ''}, ]) } } diff --git a/src/commands/validate.ts b/src/commands/validate.ts index f3a374d..31488ce 100644 --- a/src/commands/validate.ts +++ b/src/commands/validate.ts @@ -40,11 +40,12 @@ export default class Validate extends Command { try { config = parseConfigFile(args.file, !flags['skip-env']) } catch (err) { + const msg = err instanceof Error ? err.message : String(err) if (isJson) { - this.log(JSON.stringify({valid: false, errors: [{path: '', message: (err as Error).message}], warnings: []}, null, 2)) + this.log(JSON.stringify({valid: false, errors: [{path: '', message: msg}], warnings: []}, null, 2)) this.exit(1) } - this.error((err as Error).message, {exit: 1}) + this.error(msg, {exit: 1}) } const result = validate(config) diff --git a/src/lib/api.generated.ts b/src/lib/api.generated.ts index 983279b..72e5bc9 100644 --- a/src/lib/api.generated.ts +++ b/src/lib/api.generated.ts @@ -1601,6 +1601,40 @@ export interface paths { patch?: never; trace?: never; }; + "/api/v1/services/{slugOrId}/poll-results": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List poll results for a service (cursor-paginated) */ + get: operations["listPollResults"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/services/{slugOrId}/poll-summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get aggregated poll metrics and chart data for a service */ + get: operations["getPollSummary"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/v1/services/{slugOrId}/uptime": { parameters: { query?: never; @@ -2248,9 +2282,9 @@ export interface components { /** @description Request body for adding tags to a monitor. Provide existing tag IDs, inline new tags, or both. */ AddMonitorTagsRequest: { /** @description IDs of existing org tags to attach */ - tagIds: (string | null)[] | null; + tagIds?: string[] | null; /** @description New tags to create (if not already present) and attach */ - newTags: components["schemas"]["NewTagRequest"][] | null; + newTags?: components["schemas"]["NewTagRequest"][] | null; }; /** @description Request body for adding a member to a resource group */ AddResourceGroupMemberRequest: { @@ -2282,6 +2316,21 @@ export interface components { */ status: "OPERATIONAL" | "DEGRADED_PERFORMANCE" | "PARTIAL_OUTAGE" | "MAJOR_OUTAGE" | "UNDER_MAINTENANCE"; }; + /** @description Non-sensitive alert channel configuration metadata */ + AlertChannelDisplayConfig: { + /** @description Email recipients list (email channels) */ + recipients?: string[] | null; + /** @description OpsGenie API region: us or eu */ + region?: string | null; + /** @description PagerDuty severity override (critical, error, warning, info) */ + severityOverride?: string | null; + /** @description Discord role ID to mention in notifications */ + mentionRoleId?: string | null; + /** @description Custom HTTP headers for webhook requests */ + customHeaders?: { + [key: string]: string | null; + } | null; + }; /** @description Alert channel with non-sensitive configuration metadata */ AlertChannelDto: { /** @@ -2296,10 +2345,7 @@ export interface components { * @enum {string} */ channelType: "email" | "webhook" | "slack" | "pagerduty" | "opsgenie" | "teams" | "discord"; - /** @description Non-sensitive display metadata; null for older channels */ - displayConfig?: { - [key: string]: Record | null; - } | null; + displayConfig?: components["schemas"]["AlertChannelDisplayConfig"] | null; /** * Format: date-time * @description Timestamp when the channel was created @@ -2333,7 +2379,7 @@ export interface components { * Format: uuid * @description Notification dispatch that created this delivery */ - dispatchId: string | null; + dispatchId?: string | null; /** * Format: uuid * @description Alert channel ID @@ -2372,25 +2418,23 @@ export interface components { * Format: date-time * @description When the last attempt was made */ - lastAttemptAt: string | null; + lastAttemptAt?: string | null; /** * Format: date-time * @description When the next retry is scheduled (null if not retrying) */ - nextRetryAt: string | null; + nextRetryAt?: string | null; /** * Format: date-time * @description Timestamp when the delivery was confirmed (null if not yet delivered) */ - deliveredAt: string | null; + deliveredAt?: string | null; /** @description Error message from the last failed attempt */ - errorMessage: string | null; + errorMessage?: string | null; /** Format: date-time */ createdAt: string; }; - ApiKeyAuthConfig: { - type: "ApiKeyAuthConfig"; - } & (Omit & { + ApiKeyAuthConfig: Omit & { /** @description HTTP header name that carries the API key */ headerName: string; /** @@ -2398,7 +2442,13 @@ export interface components { * @description Vault secret ID for the API key value */ vaultSecretId?: string | null; - }); + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "api_key"; + }; /** @description Created API key with the full key value — store it now, it won't be shown again */ ApiKeyCreateResponse: { /** @@ -2419,7 +2469,7 @@ export interface components { * Format: date-time * @description Timestamp when the key expires; null if no expiration */ - expiresAt: string | null; + expiresAt?: string | null; }; /** @description API key for programmatic access to the DevHelm API */ ApiKeyDto: { @@ -2446,17 +2496,17 @@ export interface components { * Format: date-time * @description Timestamp of the most recent API call; null if never used */ - lastUsedAt: string | null; + lastUsedAt?: string | null; /** * Format: date-time * @description Timestamp when the key was revoked; null if active */ - revokedAt: string | null; + revokedAt?: string | null; /** * Format: date-time * @description Timestamp when the key expires; null if no expiration */ - expiresAt: string | null; + expiresAt?: string | null; }; /** @description New assertion configuration (full replacement) */ AssertionConfig: { @@ -2477,24 +2527,24 @@ export interface components { */ severity: "fail" | "warn"; /** @description Human-readable result message */ - message: string | null; + message?: string | null; /** * @description Expected value * @example 200 */ - expected: string | null; + expected?: string | null; /** * @description Actual value observed * @example 503 */ - actual: string | null; + actual?: string | null; }; AssertionTestResultDto: { /** * @description Assertion type evaluated * @enum {string} */ - assertionType: "status_code" | "response_time" | "body_contains" | "json_path" | "header" | "regex" | "dns_resolves" | "dns_response_time" | "dns_expected_ips" | "dns_expected_cname" | "dns_record_contains" | "dns_record_equals" | "dns_txt_contains" | "dns_min_answers" | "dns_max_answers" | "dns_response_time_warn" | "dns_ttl_low" | "dns_ttl_high" | "mcp_connects" | "mcp_response_time" | "mcp_has_capability" | "mcp_tool_available" | "mcp_min_tools" | "mcp_protocol_version" | "mcp_response_time_warn" | "mcp_tool_count_changed" | "ssl_expiry" | "response_size" | "redirect_count" | "redirect_target" | "response_time_warn" | "tcp_connects" | "tcp_response_time" | "tcp_response_time_warn" | "icmp_reachable" | "icmp_response_time" | "icmp_response_time_warn" | "icmp_packet_loss" | "heartbeat_received" | "heartbeat_max_interval" | "heartbeat_interval_drift" | "heartbeat_payload_contains"; + assertionType: "status_code" | "response_time" | "body_contains" | "json_path" | "header_value" | "regex_body" | "dns_resolves" | "dns_response_time" | "dns_expected_ips" | "dns_expected_cname" | "dns_record_contains" | "dns_record_equals" | "dns_txt_contains" | "dns_min_answers" | "dns_max_answers" | "dns_response_time_warn" | "dns_ttl_low" | "dns_ttl_high" | "mcp_connects" | "mcp_response_time" | "mcp_has_capability" | "mcp_tool_available" | "mcp_min_tools" | "mcp_protocol_version" | "mcp_response_time_warn" | "mcp_tool_count_changed" | "ssl_expiry" | "response_size" | "redirect_count" | "redirect_target" | "response_time_warn" | "tcp_connects" | "tcp_response_time" | "tcp_response_time_warn" | "icmp_reachable" | "icmp_response_time" | "icmp_response_time_warn" | "icmp_packet_loss" | "heartbeat_received" | "heartbeat_max_interval" | "heartbeat_interval_drift" | "heartbeat_payload_contains"; /** @description Whether the assertion passed */ passed: boolean; /** @@ -2505,9 +2555,9 @@ export interface components { /** @description Human-readable result description */ message: string; /** @description Expected value */ - expected: string | null; + expected?: string | null; /** @description Actual value observed during the test */ - actual: string | null; + actual?: string | null; }; AuditEventDto: { /** @@ -2519,19 +2569,19 @@ export interface components { * Format: int32 * @description User ID who performed the action; null for system actions */ - actorId: number | null; + actorId?: number | null; /** @description Email of the actor; null for system actions */ - actorEmail: string | null; + actorEmail?: string | null; /** @description Audit action type (e.g. monitor.created, api_key.revoked) */ action: string; /** @description Type of resource affected (e.g. monitor, api_key) */ - resourceType: string | null; + resourceType?: string | null; /** @description ID of the affected resource */ - resourceId: string | null; + resourceId?: string | null; /** @description Human-readable name of the affected resource */ - resourceName: string | null; + resourceName?: string | null; /** @description Additional context about the action */ - metadata: { + metadata?: { [key: string]: Record | null; } | null; /** @@ -2547,30 +2597,42 @@ export interface components { plan: components["schemas"]["PlanInfo"]; rateLimits: components["schemas"]["RateLimitInfo"]; }; - BasicAuthConfig: { - type: "BasicAuthConfig"; - } & (Omit & { + BasicAuthConfig: Omit & { /** * Format: uuid * @description Vault secret ID holding Basic auth username and password */ vaultSecretId?: string | null; - }); - BearerAuthConfig: { - type: "BearerAuthConfig"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "basic"; + }; + BearerAuthConfig: Omit & { /** * Format: uuid * @description Vault secret ID holding the bearer token value */ vaultSecretId?: string | null; - }); - BodyContainsAssertion: { - type: "BodyContainsAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "bearer"; + }; + BodyContainsAssertion: Omit & { /** @description Substring that must appear in the response body */ substring: string; - }); + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "body_contains"; + }; /** @description Request body for performing a bulk action on multiple monitors */ BulkMonitorActionRequest: { /** @description IDs of monitors to act on (max 200) */ @@ -2581,7 +2643,7 @@ export interface components { */ action: "PAUSE" | "RESUME" | "DELETE" | "ADD_TAG" | "REMOVE_TAG"; /** @description Tag IDs to attach or detach (required for ADD_TAG and REMOVE_TAG) */ - tagIds?: (string | null)[] | null; + tagIds?: string[] | null; /** @description New tags to create and attach (only for ADD_TAG) */ newTags?: components["schemas"]["NewTagRequest"][] | null; }; @@ -2635,25 +2697,25 @@ export interface components { * @description Uptime percentage for this bucket; null when no data * @example 100 */ - uptimePercent: number | null; + uptimePercent?: number | null; /** * Format: double * @description Weighted average latency in milliseconds for this bucket * @example 120.3 */ - avgLatencyMs: number | null; + avgLatencyMs?: number | null; /** * Format: double * @description 95th percentile latency in milliseconds (max across regions) * @example 250 */ - p95LatencyMs: number | null; + p95LatencyMs?: number | null; /** * Format: double * @description 99th percentile latency in milliseconds (max across regions) * @example 480 */ - p99LatencyMs: number | null; + p99LatencyMs?: number | null; }; /** @description Type-specific details captured during a check execution */ CheckResultDetailsDto: { @@ -2662,31 +2724,31 @@ export interface components { * @description HTTP status code of the response * @example 200 */ - statusCode: number | null; + statusCode?: number | null; /** @description HTTP response headers */ - responseHeaders: { + responseHeaders?: { [key: string]: (string | null)[] | null; } | null; /** @description Raw response body snapshot (may be HTML, XML, JSON, or plain text) */ - responseBodySnapshot: string | null; + responseBodySnapshot?: string | null; /** @description Individual assertion evaluation results */ - assertionResults: components["schemas"]["AssertionResultDto"][] | null; - tlsInfo: components["schemas"]["TlsInfoDto"]; + assertionResults?: components["schemas"]["AssertionResultDto"][] | null; + tlsInfo?: components["schemas"]["TlsInfoDto"] | null; /** * Format: int32 * @description Number of HTTP redirects followed * @example 2 */ - redirectCount: number | null; + redirectCount?: number | null; /** @description Final URL after redirects */ - redirectTarget: string | null; + redirectTarget?: string | null; /** * Format: int32 * @description Response body size in bytes * @example 4096 */ - responseSizeBytes: number | null; - checkDetails?: components["schemas"]["Dns"] | components["schemas"]["Http"] | components["schemas"]["Icmp"] | components["schemas"]["McpServer"] | components["schemas"]["Tcp"]; + responseSizeBytes?: number | null; + checkDetails?: Omit | null; }; /** @description A single check result from a monitor run */ CheckResultDto: { @@ -2710,22 +2772,22 @@ export interface components { * @description Response time in milliseconds * @example 123 */ - responseTimeMs: number | null; + responseTimeMs?: number | null; /** * @description Whether the check passed * @example true */ passed: boolean; /** @description Reason for failure when passed=false */ - failureReason: string | null; + failureReason?: string | null; /** @description Severity hint: 'down' for hard failures, 'degraded' for warn-only failures, null when passing */ - severityHint: string | null; - details: components["schemas"]["CheckResultDetailsDto"]; + severityHint?: string | null; + details?: components["schemas"]["CheckResultDetailsDto"] | null; /** * Format: uuid * @description Unique execution trace ID for cross-service correlation */ - checkId: string | null; + checkId?: string | null; }; /** @description Check-type-specific details — polymorphic by check_type discriminator */ CheckTypeDetailsDto: { @@ -2781,7 +2843,7 @@ export interface components { */ uptimePercentage: number; /** @description Incidents that overlapped this day */ - incidents: components["schemas"]["IncidentRef"][] | null; + incidents?: components["schemas"]["IncidentRef"][] | null; }; /** @description Inline uptime percentages for 24h, 7d, 30d */ ComponentUptimeSummaryDto: { @@ -2790,19 +2852,19 @@ export interface components { * @description Uptime percentage over the last 24 hours * @example 99.95 */ - day: number | null; + day?: number | null; /** * Format: double * @description Uptime percentage over the last 7 days * @example 99.98 */ - week: number | null; + week?: number | null; /** * Format: double * @description Uptime percentage over the last 30 days * @example 99.92 */ - month: number | null; + month?: number | null; /** * @description Data source: vendor_reported or incident_derived * @example vendor_reported @@ -2931,7 +2993,7 @@ export interface components { /** @description Whether the monitor is active (default: true) */ enabled?: boolean | null; /** @description Probe regions to run checks from, e.g. us-east, eu-west */ - regions?: (string | null)[] | null; + regions?: string[] | null; /** * @description Who manages this monitor: DASHBOARD or CLI * @enum {string} @@ -2944,11 +3006,11 @@ export interface components { environmentId?: string | null; /** @description Assertions to evaluate against each check result */ assertions?: components["schemas"]["CreateAssertionRequest"][] | null; - auth?: components["schemas"]["ApiKeyAuthConfig"] | components["schemas"]["BasicAuthConfig"] | components["schemas"]["BearerAuthConfig"] | components["schemas"]["HeaderAuthConfig"]; - incidentPolicy?: components["schemas"]["UpdateIncidentPolicyRequest"]; + auth?: Omit | null; + incidentPolicy?: components["schemas"]["UpdateIncidentPolicyRequest"] | null; /** @description Alert channels to notify when this monitor triggers */ - alertChannelIds?: (string | null)[] | null; - tags?: components["schemas"]["AddMonitorTagsRequest"]; + alertChannelIds?: string[] | null; + tags?: components["schemas"]["AddMonitorTagsRequest"] | null; }; /** @description Request body for creating a notification policy */ CreateNotificationPolicyRequest: { @@ -2986,10 +3048,10 @@ export interface components { */ defaultFrequency?: number | null; /** @description Default regions applied to member monitors */ - defaultRegions?: (string | null)[] | null; - defaultRetryStrategy?: components["schemas"]["RetryStrategy"]; + defaultRegions?: string[] | null; + defaultRetryStrategy?: components["schemas"]["RetryStrategy"] | null; /** @description Default alert channel IDs applied to member monitors */ - defaultAlertChannels?: (string | null)[] | null; + defaultAlertChannels?: string[] | null; /** * Format: uuid * @description Default environment ID applied to member monitors @@ -3128,7 +3190,7 @@ export interface components { slug: string; /** @description Optional description shown below the page header */ description?: string | null; - branding?: components["schemas"]["StatusPageBranding"]; + branding?: components["schemas"]["StatusPageBranding"] | null; /** * @description Page visibility: PUBLIC, PASSWORD, or IP_RESTRICTED (default: PUBLIC) * @enum {string|null} @@ -3167,7 +3229,7 @@ export interface components { /** @description Items on this page */ data: Record[]; /** @description Opaque cursor for the next page; null when there are no more results */ - nextCursor: string | null; + nextCursor?: string | null; /** @description Whether more results exist beyond this page */ hasMore: boolean; }; @@ -3176,7 +3238,7 @@ export interface components { /** @description Items on this page */ data: components["schemas"]["CheckResultDto"][]; /** @description Opaque cursor for the next page; null when there are no more results */ - nextCursor: string | null; + nextCursor?: string | null; /** @description Whether more results exist beyond this page */ hasMore: boolean; }; @@ -3185,7 +3247,16 @@ export interface components { /** @description Items on this page */ data: components["schemas"]["ServiceCatalogDto"][]; /** @description Opaque cursor for the next page; null when there are no more results */ - nextCursor: string | null; + nextCursor?: string | null; + /** @description Whether more results exist beyond this page */ + hasMore: boolean; + }; + /** @description Cursor-paginated response for time-series and append-only data */ + CursorPageServicePollResultDto: { + /** @description Items on this page */ + data: components["schemas"]["ServicePollResultDto"][]; + /** @description Opaque cursor for the next page; null when there are no more results */ + nextCursor?: string | null; /** @description Whether more results exist beyond this page */ hasMore: boolean; }; @@ -3252,22 +3323,22 @@ export interface components { * Format: int32 * @description HTTP response status code from the external service */ - responseStatusCode: number | null; + responseStatusCode?: number | null; /** @description JSON payload sent to the external service */ - requestPayload: string | null; + requestPayload?: string | null; /** @description Response body from the external service (truncated) */ - responseBody: string | null; + responseBody?: string | null; /** @description Error message if the attempt failed */ - errorMessage: string | null; + errorMessage?: string | null; /** * Format: int32 * @description Round-trip time in milliseconds */ - responseTimeMs: number | null; + responseTimeMs?: number | null; /** @description External identifier (e.g. PagerDuty dedup_key, SES MessageId, webhook delivery UUID) */ - externalId: string | null; + externalId?: string | null; /** @description HTTP request headers sent to the external service */ - requestHeaders: { + requestHeaders?: { [key: string]: string | null; } | null; /** Format: date-time */ @@ -3293,52 +3364,39 @@ export interface components { */ expiresAt: string; }; - DiscordChannelConfig: { - channelType: "DiscordChannelConfig"; - } & (Omit & { + DiscordChannelConfig: Omit & { /** @description Discord webhook URL */ webhookUrl: string; /** @description Optional Discord role ID to mention in notifications */ mentionRoleId?: string | null; - }); - /** @description DNS check-type-specific details */ - Dns: { - check_type: "Dns"; - } & (Omit & { - /** @description Target hostname */ - hostname?: string | null; - /** @description Requested DNS record types */ - requestedTypes?: (string | null)[] | null; - /** @description Resolver used for lookup */ - usedResolver?: string | null; - /** @description Resolved DNS records keyed by record type */ - records?: { - [key: string]: ({ - [key: string]: Record | null; - } | null)[] | null; - } | null; - /** @description DNS resolution attempts */ - attempts?: ({ - [key: string]: Record | null; - } | null)[] | null; - /** @description Kind of DNS failure, if any */ - failureKind?: string | null; - }); - DnsExpectedCnameAssertion: { - type: "DnsExpectedCnameAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + channelType: "discord"; + }; + DnsExpectedCnameAssertion: Omit & { /** @description Expected CNAME target the resolution must include */ value: string; - }); - DnsExpectedIpsAssertion: { - type: "DnsExpectedIpsAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "dns_expected_cname"; + }; + DnsExpectedIpsAssertion: Omit & { /** @description Allowed IP addresses; at least one resolved address must match */ ips: string[]; - }); - DnsMaxAnswersAssertion: { - type: "DnsMaxAnswersAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "dns_expected_ips"; + }; + DnsMaxAnswersAssertion: Omit & { /** @description DNS record type whose answer count is checked */ recordType: string; /** @@ -3346,10 +3404,14 @@ export interface components { * @description Maximum number of answers allowed for that record type */ max?: number; - }); - DnsMinAnswersAssertion: { - type: "DnsMinAnswersAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "dns_max_answers"; + }; + DnsMinAnswersAssertion: Omit & { /** @description DNS record type whose answer count is checked */ recordType: string; /** @@ -3357,7 +3419,13 @@ export interface components { * @description Minimum number of answers required for that record type */ min?: number; - }); + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "dns_min_answers"; + }; DnsMonitorConfig: components["schemas"]["MonitorConfig"] & { /** @description Domain name to resolve */ hostname: string; @@ -3376,73 +3444,109 @@ export interface components { */ totalTimeoutMs?: number | null; }; - DnsRecordContainsAssertion: { - type: "DnsRecordContainsAssertion"; - } & (Omit & { + DnsRecordContainsAssertion: Omit & { /** @description DNS record type to assert on (A, AAAA, CNAME, MX, TXT) */ recordType: string; /** @description Substring that must appear in a matching record value */ substring: string; - }); - DnsRecordEqualsAssertion: { - type: "DnsRecordEqualsAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "dns_record_contains"; + }; + DnsRecordEqualsAssertion: Omit & { /** @description DNS record type to assert on (A, AAAA, CNAME, MX, TXT) */ recordType: string; /** @description Expected DNS record value for an exact match */ value: string; - }); - DnsResolvesAssertion: { - type: "DnsResolvesAssertion"; - } & Omit; - DnsResponseTimeAssertion: { - type: "DnsResponseTimeAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "dns_record_equals"; + }; + DnsResolvesAssertion: Omit & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "dns_resolves"; + }; + DnsResponseTimeAssertion: Omit & { /** * Format: int32 * @description Maximum allowed DNS resolution time in milliseconds */ maxMs?: number; - }); - DnsResponseTimeWarnAssertion: { - type: "DnsResponseTimeWarnAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "dns_response_time"; + }; + DnsResponseTimeWarnAssertion: Omit & { /** * Format: int32 * @description DNS resolution time in milliseconds that triggers a warning only */ warnMs?: number; - }); - DnsTtlHighAssertion: { - type: "DnsTtlHighAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "dns_response_time_warn"; + }; + DnsTtlHighAssertion: Omit & { /** * Format: int32 * @description Maximum TTL in seconds before a high-TTL warning is raised */ maxTtl?: number; - }); - DnsTtlLowAssertion: { - type: "DnsTtlLowAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "dns_ttl_high"; + }; + DnsTtlLowAssertion: Omit & { /** * Format: int32 * @description Minimum acceptable TTL in seconds before a warning is raised */ minTtl?: number; - }); - DnsTxtContainsAssertion: { - type: "DnsTxtContainsAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "dns_ttl_low"; + }; + DnsTxtContainsAssertion: Omit & { /** @description Substring that must appear in at least one TXT record */ substring: string; - }); - EmailChannelConfig: { - channelType: "EmailChannelConfig"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "dns_txt_contains"; + }; + EmailChannelConfig: Omit & { /** @description Email addresses to send notifications to */ recipients: string[]; - }); + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + channelType: "email"; + }; /** @description A single resolved entitlement for the organization */ EntitlementDto: { /** @description Entitlement key */ @@ -3589,9 +3693,7 @@ export interface components { /** @description Ordered component IDs with their within-group display order */ positions: components["schemas"]["ComponentPosition"][]; }; - HeaderAuthConfig: { - type: "HeaderAuthConfig"; - } & (Omit & { + HeaderAuthConfig: Omit & { /** @description Custom HTTP header name for the secret value */ headerName: string; /** @@ -3599,10 +3701,14 @@ export interface components { * @description Vault secret ID for the header value */ vaultSecretId?: string | null; - }); - HeaderValueAssertion: { - type: "HeaderValueAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "header"; + }; + HeaderValueAssertion: Omit & { /** @description HTTP header name to assert on */ headerName: string; /** @description Expected value to compare against */ @@ -3612,25 +3718,39 @@ export interface components { * @enum {string} */ operator: "equals" | "contains" | "less_than" | "greater_than" | "matches" | "range"; - }); - HeartbeatIntervalDriftAssertion: { - type: "HeartbeatIntervalDriftAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "header_value"; + }; + HeartbeatIntervalDriftAssertion: Omit & { /** * Format: int32 * @description Max percent drift from expected ping interval before warning (non-fatal) */ maxDeviationPercent: number; - }); - HeartbeatMaxIntervalAssertion: { - type: "HeartbeatMaxIntervalAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "heartbeat_interval_drift"; + }; + HeartbeatMaxIntervalAssertion: Omit & { /** * Format: int32 * @description Maximum allowed gap in seconds between consecutive heartbeat pings */ maxSeconds: number; - }); + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "heartbeat_max_interval"; + }; HeartbeatMonitorConfig: components["schemas"]["MonitorConfig"] & { /** * Format: int32 @@ -3643,28 +3763,25 @@ export interface components { */ gracePeriod: number; }; - HeartbeatPayloadContainsAssertion: { - type: "HeartbeatPayloadContainsAssertion"; - } & (Omit & { + HeartbeatPayloadContainsAssertion: Omit & { /** @description JSONPath expression into the heartbeat ping JSON payload */ path: string; /** @description Expected value to compare against at that path */ value: string; - }); - HeartbeatReceivedAssertion: { - type: "HeartbeatReceivedAssertion"; - } & Omit; - /** @description HTTP check-type-specific details */ - Http: { - check_type: "Http"; - } & (Omit & { - /** @description Request phase timing breakdown */ - timing?: { - [key: string]: Record | null; - } | null; - /** @description Whether the response body was truncated before storage */ - bodyTruncated?: boolean | null; - }); + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "heartbeat_payload_contains"; + }; + HeartbeatReceivedAssertion: Omit & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "heartbeat_received"; + }; HttpMonitorConfig: components["schemas"]["MonitorConfig"] & { /** @description Target URL to send requests to */ url: string; @@ -3684,52 +3801,6 @@ export interface components { /** @description Whether to verify TLS certificates (default: true) */ verifyTls?: boolean | null; }; - /** @description ICMP (ping) check-type-specific details */ - Icmp: { - check_type: "Icmp"; - } & (Omit & { - /** - * @description Target host - * @example 1.1.1.1 - */ - host?: string; - /** - * Format: int32 - * @description Number of ICMP packets sent - */ - packetsSent?: number | null; - /** - * Format: int32 - * @description Number of ICMP packets received - */ - packetsReceived?: number | null; - /** - * Format: double - * @description Packet loss percentage - * @example 0 - */ - packetLoss?: number | null; - /** - * Format: double - * @description Average round-trip time in ms - */ - avgRttMs?: number | null; - /** - * Format: double - * @description Minimum round-trip time in ms - */ - minRttMs?: number | null; - /** - * Format: double - * @description Maximum round-trip time in ms - */ - maxRttMs?: number | null; - /** - * Format: double - * @description Jitter in ms - */ - jitterMs?: number | null; - }); IcmpMonitorConfig: components["schemas"]["MonitorConfig"] & { /** @description Target hostname or IP address to ping */ host: string; @@ -3744,40 +3815,56 @@ export interface components { */ timeoutMs?: number | null; }; - IcmpPacketLossAssertion: { - type: "IcmpPacketLossAssertion"; - } & (Omit & { + IcmpPacketLossAssertion: Omit & { /** * Format: double * @description Maximum allowed packet loss percentage before the check fails (0–100) */ maxPercent?: number; - }); - IcmpReachableAssertion: { - type: "IcmpReachableAssertion"; - } & Omit; - IcmpResponseTimeAssertion: { - type: "IcmpResponseTimeAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "icmp_packet_loss"; + }; + IcmpReachableAssertion: Omit & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "icmp_reachable"; + }; + IcmpResponseTimeAssertion: Omit & { /** * Format: int32 * @description Maximum average ICMP round-trip time in milliseconds */ maxMs?: number; - }); - IcmpResponseTimeWarnAssertion: { - type: "IcmpResponseTimeWarnAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "icmp_response_time"; + }; + IcmpResponseTimeWarnAssertion: Omit & { /** * Format: int32 * @description ICMP round-trip time in milliseconds that triggers a warning only */ warnMs?: number; - }); + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "icmp_response_time_warn"; + }; IncidentDetailDto: { incident: components["schemas"]["IncidentDto"]; updates: components["schemas"]["IncidentUpdateDto"][]; - statusPageIncidents: components["schemas"]["LinkedStatusPageIncidentDto"][] | null; + statusPageIncidents?: components["schemas"]["LinkedStatusPageIncidentDto"][] | null; }; /** @description Incident triggered by a monitor check failure or manual creation */ IncidentDto: { @@ -3790,7 +3877,7 @@ export interface components { * Format: uuid * @description Monitor that triggered the incident; null for service or manual incidents */ - monitorId: string | null; + monitorId?: string | null; /** * Format: int32 * @description Organization this incident belongs to @@ -3812,9 +3899,9 @@ export interface components { */ severity: "DOWN" | "DEGRADED" | "MAINTENANCE"; /** @description Short summary of the incident; null for auto-generated incidents */ - title: string | null; + title?: string | null; /** @description Human-readable description of the trigger rule that fired */ - triggeredByRule: string | null; + triggeredByRule?: string | null; /** @description Probe regions that observed the failure */ affectedRegions: string[]; /** @@ -3826,50 +3913,50 @@ export interface components { * Format: int32 * @description User who created the incident (manual incidents only) */ - createdByUserId: number | null; + createdByUserId?: number | null; /** @description Whether this incident is visible on the status page */ statusPageVisible: boolean; /** * Format: uuid * @description Linked vendor service incident ID; null for monitor incidents */ - serviceIncidentId: string | null; + serviceIncidentId?: string | null; /** * Format: uuid * @description Linked service catalog ID; null for monitor incidents */ - serviceId: string | null; + serviceId?: string | null; /** @description External reference ID (e.g. PagerDuty incident ID) */ - externalRef: string | null; + externalRef?: string | null; /** @description Service components affected by this incident */ - affectedComponents: (string | null)[] | null; + affectedComponents?: string[] | null; /** @description Short URL linking to the incident details */ - shortlink: string | null; + shortlink?: string | null; /** * @description How the incident was resolved (AUTO_RECOVERED, MANUAL, etc.) * @enum {string|null} */ - resolutionReason: "MANUAL" | "AUTO_RECOVERED" | "AUTO_RESOLVED" | null; + resolutionReason?: "MANUAL" | "AUTO_RECOVERED" | "AUTO_RESOLVED" | null; /** * Format: date-time * @description Timestamp when the incident was detected or created */ - startedAt: string | null; + startedAt?: string | null; /** * Format: date-time * @description Timestamp when the incident was confirmed (multi-region confirmation) */ - confirmedAt: string | null; + confirmedAt?: string | null; /** * Format: date-time * @description Timestamp when the incident was resolved */ - resolvedAt: string | null; + resolvedAt?: string | null; /** * Format: date-time * @description Cooldown window end; new incidents suppressed until this time */ - cooldownUntil: string | null; + cooldownUntil?: string | null; /** * Format: date-time * @description Timestamp when the incident record was created @@ -3881,20 +3968,20 @@ export interface components { */ updatedAt: string; /** @description Name of the associated monitor; populated on list responses */ - monitorName: string | null; + monitorName?: string | null; /** @description Name of the associated service; populated on list responses */ - serviceName: string | null; + serviceName?: string | null; /** @description Slug of the associated service; populated on list responses */ - serviceSlug: string | null; + serviceSlug?: string | null; /** @description Type of the associated monitor; populated on list responses */ - monitorType: string | null; + monitorType?: string | null; /** * Format: uuid * @description Resource group that owns this incident; null when not group-managed */ - resourceGroupId: string | null; + resourceGroupId?: string | null; /** @description Name of the resource group; populated on list responses */ - resourceGroupName: string | null; + resourceGroupName?: string | null; }; IncidentFilterParams: { /** @enum {string} */ @@ -3910,13 +3997,13 @@ export interface components { /** Format: uuid */ resourceGroupId: string; /** Format: uuid */ - tagId: string | null; + tagId?: string | null; /** Format: uuid */ - environmentId: string | null; + environmentId?: string | null; /** Format: date-time */ - startedFrom: string | null; + startedFrom?: string | null; /** Format: date-time */ - startedTo: string | null; + startedTo?: string | null; /** Format: int32 */ page: number; /** Format: int32 */ @@ -3952,12 +4039,12 @@ export interface components { * Format: int32 * @description Number of regions configured on the monitor (only set in internal API responses) */ - monitorRegionCount: number | null; + monitorRegionCount?: number | null; /** * Format: int32 * @description Monitor check frequency in seconds (only set in internal API responses) */ - checkFrequencySeconds: number | null; + checkFrequencySeconds?: number | null; }; /** @description Lightweight reference to an incident overlapping this day */ IncidentRef: { @@ -3978,7 +4065,7 @@ export interface components { /** Format: int64 */ resolvedToday: number; /** Format: double */ - mttr30d: number | null; + mttr30d?: number | null; }; IncidentUpdateDto: { /** Format: uuid */ @@ -3986,10 +4073,10 @@ export interface components { /** Format: uuid */ incidentId: string; /** @enum {string|null} */ - oldStatus: "WATCHING" | "TRIGGERED" | "CONFIRMED" | "RESOLVED" | null; + oldStatus?: "WATCHING" | "TRIGGERED" | "CONFIRMED" | "RESOLVED" | null; /** @enum {string|null} */ - newStatus: "WATCHING" | "TRIGGERED" | "CONFIRMED" | "RESOLVED" | null; - body: string | null; + newStatus?: "WATCHING" | "TRIGGERED" | "CONFIRMED" | "RESOLVED" | null; + body?: string | null; /** @enum {string} */ createdBy: "SYSTEM" | "USER"; notifySubscribers: boolean; @@ -4020,7 +4107,7 @@ export interface components { sensitive: boolean; placeholder?: string | null; helpText?: string | null; - options?: (string | null)[] | null; + options?: string[] | null; default?: string | null; }; /** @description Organization invite sent to an email address */ @@ -4046,16 +4133,14 @@ export interface components { * Format: date-time * @description Timestamp when the invite was accepted; null if not yet used */ - consumedAt: string | null; + consumedAt?: string | null; /** * Format: date-time * @description Timestamp when the invite was revoked; null if active */ - revokedAt: string | null; + revokedAt?: string | null; }; - JsonPathAssertion: { - type: "JsonPathAssertion"; - } & (Omit & { + JsonPathAssertion: Omit & { /** @description JSONPath expression to extract a value from the response body */ path: string; /** @description Expected value to compare against */ @@ -4065,7 +4150,13 @@ export interface components { * @enum {string} */ operator: "equals" | "contains" | "less_than" | "greater_than" | "matches" | "range"; - }); + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "json_path"; + }; /** @description API key metadata */ KeyInfo: { /** @@ -4084,12 +4175,12 @@ export interface components { * Format: date-time * @description When the key expires (null = never) */ - expiresAt: string | null; + expiresAt?: string | null; /** * Format: date-time * @description Last time the key was used */ - lastUsedAt: string | null; + lastUsedAt?: string | null; }; LinkedStatusPageIncidentDto: { /** Format: uuid */ @@ -4105,7 +4196,7 @@ export interface components { impact: "NONE" | "MINOR" | "MAJOR" | "CRITICAL"; scheduled: boolean; /** Format: date-time */ - publishedAt: string | null; + publishedAt?: string | null; }; /** @description A component affected by a scheduled maintenance window */ MaintenanceComponentRef: { @@ -4129,12 +4220,12 @@ export interface components { /** @description Status at the time of this update */ status: string; /** @description Update message from the vendor */ - body: string | null; + body?: string | null; /** * Format: date-time * @description Timestamp when this update was posted */ - displayAt: string | null; + displayAt?: string | null; }; /** @description Scheduled maintenance window for a monitor */ MaintenanceWindowDto: { @@ -4147,7 +4238,7 @@ export interface components { * Format: uuid * @description Monitor this window applies to; null for org-wide windows */ - monitorId: string | null; + monitorId?: string | null; /** * Format: int32 * @description Organization this maintenance window belongs to @@ -4164,9 +4255,9 @@ export interface components { */ endsAt: string; /** @description iCal RRULE for recurring windows; null for one-time */ - repeatRule: string | null; + repeatRule?: string | null; /** @description Human-readable reason for the maintenance */ - reason: string | null; + reason?: string | null; /** @description Whether alerts are suppressed during this window */ suppressAlerts: boolean; /** @@ -4182,82 +4273,78 @@ export interface components { /** @description Comparison value for single-value rules like severity_gte */ value?: string | null; /** @description Monitor UUIDs to match for monitor_id_in rules */ - monitorIds?: (string | null)[] | null; + monitorIds?: string[] | null; /** @description Region codes to match for region_in rules */ - regions?: (string | null)[] | null; + regions?: string[] | null; /** @description Values list for multi-value rules like monitor_type_in */ - values?: (string | null)[] | null; - }; - McpConnectsAssertion: { - type: "McpConnectsAssertion"; - } & Omit; - McpHasCapabilityAssertion: { - type: "McpHasCapabilityAssertion"; - } & (Omit & { + values?: string[] | null; + }; + McpConnectsAssertion: Omit & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mcp_connects"; + }; + McpHasCapabilityAssertion: Omit & { /** @description Capability name the server must advertise, e.g. tools or resources */ capability: string; - }); - McpMinToolsAssertion: { - type: "McpMinToolsAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mcp_has_capability"; + }; + McpMinToolsAssertion: Omit & { /** * Format: int32 * @description Minimum number of tools the server must expose */ min?: number; - }); - McpProtocolVersionAssertion: { - type: "McpProtocolVersionAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mcp_min_tools"; + }; + McpProtocolVersionAssertion: Omit & { /** @description Expected MCP protocol version string from the server handshake */ version: string; - }); - McpResponseTimeAssertion: { - type: "McpResponseTimeAssertion"; - } & (Omit & { + } & { /** - * Format: int32 - * @description Maximum allowed MCP check duration in milliseconds + * @description discriminator enum property added by openapi-typescript + * @enum {string} */ - maxMs?: number; - }); - McpResponseTimeWarnAssertion: { - type: "McpResponseTimeWarnAssertion"; - } & (Omit & { + type: "mcp_protocol_version"; + }; + McpResponseTimeAssertion: Omit & { /** * Format: int32 - * @description MCP check duration in milliseconds that triggers a warning only + * @description Maximum allowed MCP check duration in milliseconds */ - warnMs?: number; - }); - /** @description MCP server check-type-specific details */ - McpServer: { - check_type: "McpServer"; - } & (Omit & { - /** @description MCP server URL */ - url?: string | null; - /** @description MCP protocol version */ - protocolVersion?: string | null; - /** @description MCP server info (name, version, etc.) */ - serverInfo?: { - [key: string]: Record | null; - } | null; + maxMs?: number; + } & { /** - * Format: int32 - * @description Number of tools exposed + * @description discriminator enum property added by openapi-typescript + * @enum {string} */ - toolCount?: number | null; + type: "mcp_response_time"; + }; + McpResponseTimeWarnAssertion: Omit & { /** * Format: int32 - * @description Number of resources exposed + * @description MCP check duration in milliseconds that triggers a warning only */ - resourceCount?: number | null; + warnMs?: number; + } & { /** - * Format: int32 - * @description Number of prompts exposed + * @description discriminator enum property added by openapi-typescript + * @enum {string} */ - promptCount?: number | null; - }); + type: "mcp_response_time_warn"; + }; McpServerMonitorConfig: components["schemas"]["MonitorConfig"] & { /** @description Command to execute to start the MCP server */ command: string; @@ -4268,21 +4355,29 @@ export interface components { [key: string]: string | null; } | null; }; - McpToolAvailableAssertion: { - type: "McpToolAvailableAssertion"; - } & (Omit & { + McpToolAvailableAssertion: Omit & { /** @description MCP tool name that must appear in the server's tool list */ toolName: string; - }); - McpToolCountChangedAssertion: { - type: "McpToolCountChangedAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mcp_tool_available"; + }; + McpToolCountChangedAssertion: Omit & { /** * Format: int32 * @description Expected tool count; warns when the live count differs */ expectedCount?: number; - }); + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "mcp_tool_count_changed"; + }; /** @description Organization member with role and status */ MemberDto: { /** @@ -4293,7 +4388,7 @@ export interface components { /** @description Member email address */ email: string; /** @description Member display name; null if not set */ - name: string | null; + name?: string | null; /** * @description Member role within this organization (OWNER, ADMIN, MEMBER) * @enum {string} @@ -4316,8 +4411,8 @@ export interface components { /** Format: uuid */ monitorId: string; /** @enum {string} */ - assertionType: "status_code" | "response_time" | "body_contains" | "json_path" | "header" | "regex" | "dns_resolves" | "dns_response_time" | "dns_expected_ips" | "dns_expected_cname" | "dns_record_contains" | "dns_record_equals" | "dns_txt_contains" | "dns_min_answers" | "dns_max_answers" | "dns_response_time_warn" | "dns_ttl_low" | "dns_ttl_high" | "mcp_connects" | "mcp_response_time" | "mcp_has_capability" | "mcp_tool_available" | "mcp_min_tools" | "mcp_protocol_version" | "mcp_response_time_warn" | "mcp_tool_count_changed" | "ssl_expiry" | "response_size" | "redirect_count" | "redirect_target" | "response_time_warn" | "tcp_connects" | "tcp_response_time" | "tcp_response_time_warn" | "icmp_reachable" | "icmp_response_time" | "icmp_response_time_warn" | "icmp_packet_loss" | "heartbeat_received" | "heartbeat_max_interval" | "heartbeat_interval_drift" | "heartbeat_payload_contains"; - config?: components["schemas"]["BodyContainsAssertion"] | components["schemas"]["DnsExpectedCnameAssertion"] | components["schemas"]["DnsExpectedIpsAssertion"] | components["schemas"]["DnsMaxAnswersAssertion"] | components["schemas"]["DnsMinAnswersAssertion"] | components["schemas"]["DnsRecordContainsAssertion"] | components["schemas"]["DnsRecordEqualsAssertion"] | components["schemas"]["DnsResolvesAssertion"] | components["schemas"]["DnsResponseTimeAssertion"] | components["schemas"]["DnsResponseTimeWarnAssertion"] | components["schemas"]["DnsTtlHighAssertion"] | components["schemas"]["DnsTtlLowAssertion"] | components["schemas"]["DnsTxtContainsAssertion"] | components["schemas"]["HeaderValueAssertion"] | components["schemas"]["HeartbeatIntervalDriftAssertion"] | components["schemas"]["HeartbeatMaxIntervalAssertion"] | components["schemas"]["HeartbeatPayloadContainsAssertion"] | components["schemas"]["HeartbeatReceivedAssertion"] | components["schemas"]["IcmpPacketLossAssertion"] | components["schemas"]["IcmpReachableAssertion"] | components["schemas"]["IcmpResponseTimeAssertion"] | components["schemas"]["IcmpResponseTimeWarnAssertion"] | components["schemas"]["JsonPathAssertion"] | components["schemas"]["McpConnectsAssertion"] | components["schemas"]["McpHasCapabilityAssertion"] | components["schemas"]["McpMinToolsAssertion"] | components["schemas"]["McpProtocolVersionAssertion"] | components["schemas"]["McpResponseTimeAssertion"] | components["schemas"]["McpResponseTimeWarnAssertion"] | components["schemas"]["McpToolAvailableAssertion"] | components["schemas"]["McpToolCountChangedAssertion"] | components["schemas"]["RedirectCountAssertion"] | components["schemas"]["RedirectTargetAssertion"] | components["schemas"]["RegexBodyAssertion"] | components["schemas"]["ResponseSizeAssertion"] | components["schemas"]["ResponseTimeAssertion"] | components["schemas"]["ResponseTimeWarnAssertion"] | components["schemas"]["SslExpiryAssertion"] | components["schemas"]["StatusCodeAssertion"] | components["schemas"]["TcpConnectsAssertion"] | components["schemas"]["TcpResponseTimeAssertion"] | components["schemas"]["TcpResponseTimeWarnAssertion"]; + assertionType: "status_code" | "response_time" | "body_contains" | "json_path" | "header_value" | "regex_body" | "dns_resolves" | "dns_response_time" | "dns_expected_ips" | "dns_expected_cname" | "dns_record_contains" | "dns_record_equals" | "dns_txt_contains" | "dns_min_answers" | "dns_max_answers" | "dns_response_time_warn" | "dns_ttl_low" | "dns_ttl_high" | "mcp_connects" | "mcp_response_time" | "mcp_has_capability" | "mcp_tool_available" | "mcp_min_tools" | "mcp_protocol_version" | "mcp_response_time_warn" | "mcp_tool_count_changed" | "ssl_expiry" | "response_size" | "redirect_count" | "redirect_target" | "response_time_warn" | "tcp_connects" | "tcp_response_time" | "tcp_response_time_warn" | "icmp_reachable" | "icmp_response_time" | "icmp_response_time_warn" | "icmp_packet_loss" | "heartbeat_received" | "heartbeat_max_interval" | "heartbeat_interval_drift" | "heartbeat_payload_contains"; + config: components["schemas"]["BodyContainsAssertion"] | components["schemas"]["DnsExpectedCnameAssertion"] | components["schemas"]["DnsExpectedIpsAssertion"] | components["schemas"]["DnsMaxAnswersAssertion"] | components["schemas"]["DnsMinAnswersAssertion"] | components["schemas"]["DnsRecordContainsAssertion"] | components["schemas"]["DnsRecordEqualsAssertion"] | components["schemas"]["DnsResolvesAssertion"] | components["schemas"]["DnsResponseTimeAssertion"] | components["schemas"]["DnsResponseTimeWarnAssertion"] | components["schemas"]["DnsTtlHighAssertion"] | components["schemas"]["DnsTtlLowAssertion"] | components["schemas"]["DnsTxtContainsAssertion"] | components["schemas"]["HeaderValueAssertion"] | components["schemas"]["HeartbeatIntervalDriftAssertion"] | components["schemas"]["HeartbeatMaxIntervalAssertion"] | components["schemas"]["HeartbeatPayloadContainsAssertion"] | components["schemas"]["HeartbeatReceivedAssertion"] | components["schemas"]["IcmpPacketLossAssertion"] | components["schemas"]["IcmpReachableAssertion"] | components["schemas"]["IcmpResponseTimeAssertion"] | components["schemas"]["IcmpResponseTimeWarnAssertion"] | components["schemas"]["JsonPathAssertion"] | components["schemas"]["McpConnectsAssertion"] | components["schemas"]["McpHasCapabilityAssertion"] | components["schemas"]["McpMinToolsAssertion"] | components["schemas"]["McpProtocolVersionAssertion"] | components["schemas"]["McpResponseTimeAssertion"] | components["schemas"]["McpResponseTimeWarnAssertion"] | components["schemas"]["McpToolAvailableAssertion"] | components["schemas"]["McpToolCountChangedAssertion"] | components["schemas"]["RedirectCountAssertion"] | components["schemas"]["RedirectTargetAssertion"] | components["schemas"]["RegexBodyAssertion"] | components["schemas"]["ResponseSizeAssertion"] | components["schemas"]["ResponseTimeAssertion"] | components["schemas"]["ResponseTimeWarnAssertion"] | components["schemas"]["SslExpiryAssertion"] | components["schemas"]["StatusCodeAssertion"] | components["schemas"]["TcpConnectsAssertion"] | components["schemas"]["TcpResponseTimeAssertion"] | components["schemas"]["TcpResponseTimeWarnAssertion"]; /** @enum {string} */ severity: "fail" | "warn"; }; @@ -4332,9 +4427,9 @@ export interface components { monitorId: string; /** @enum {string} */ authType: "bearer" | "basic" | "header" | "api_key"; - config?: components["schemas"]["ApiKeyAuthConfig"] | components["schemas"]["BasicAuthConfig"] | components["schemas"]["BearerAuthConfig"] | components["schemas"]["HeaderAuthConfig"]; + config: components["schemas"]["ApiKeyAuthConfig"] | components["schemas"]["BasicAuthConfig"] | components["schemas"]["BearerAuthConfig"] | components["schemas"]["HeaderAuthConfig"]; }; - /** @description Updated protocol-specific configuration; null preserves current */ + /** @description Protocol-specific monitor configuration */ MonitorConfig: Record; /** @description Full monitor representation */ MonitorDto: { @@ -4352,7 +4447,7 @@ export interface components { name: string; /** @enum {string} */ type: "HTTP" | "DNS" | "MCP_SERVER" | "TCP" | "ICMP" | "HEARTBEAT"; - config?: components["schemas"]["DnsMonitorConfig"] | components["schemas"]["HeartbeatMonitorConfig"] | components["schemas"]["HttpMonitorConfig"] | components["schemas"]["IcmpMonitorConfig"] | components["schemas"]["McpServerMonitorConfig"] | components["schemas"]["TcpMonitorConfig"]; + config: components["schemas"]["DnsMonitorConfig"] | components["schemas"]["HeartbeatMonitorConfig"] | components["schemas"]["HttpMonitorConfig"] | components["schemas"]["IcmpMonitorConfig"] | components["schemas"]["McpServerMonitorConfig"] | components["schemas"]["TcpMonitorConfig"]; /** * Format: int32 * @description Check frequency in seconds (30–86400) @@ -4378,16 +4473,16 @@ export interface components { */ updatedAt: string; /** @description Assertions evaluated against each check result; null on list responses */ - assertions: components["schemas"]["MonitorAssertionDto"][] | null; + assertions?: components["schemas"]["MonitorAssertionDto"][] | null; /** @description Tags applied to this monitor */ - tags: components["schemas"]["TagDto"][] | null; + tags?: components["schemas"]["TagDto"][] | null; /** @description Heartbeat ping URL; populated for HEARTBEAT monitors only */ - pingUrl: string | null; - environment: components["schemas"]["Summary"]; - auth?: components["schemas"]["ApiKeyAuthConfig"] | components["schemas"]["BasicAuthConfig"] | components["schemas"]["BearerAuthConfig"] | components["schemas"]["HeaderAuthConfig"]; - incidentPolicy: components["schemas"]["IncidentPolicyDto"]; + pingUrl?: string | null; + environment?: components["schemas"]["Summary"] | null; + auth?: Omit | null; + incidentPolicy?: components["schemas"]["IncidentPolicyDto"] | null; /** @description Alert channel IDs linked to this monitor; populated on single-monitor responses */ - alertChannelIds: (string | null)[] | null; + alertChannelIds?: string[] | null; }; /** @description Monitors that reference this secret; null on create/update responses */ MonitorReference: { @@ -4430,12 +4525,12 @@ export interface components { * Format: double * @description Average uptime percentage across all monitors over last 24h */ - avgUptime24h: number | null; + avgUptime24h?: number | null; /** * Format: double * @description Average uptime percentage across all monitors over last 30 days */ - avgUptime30d: number | null; + avgUptime30d?: number | null; }; MonitorTestRequest: { /** @@ -4449,22 +4544,22 @@ export interface components { }; MonitorTestResultDto: { passed: boolean; - error: string | null; + error?: string | null; /** Format: int32 */ - statusCode: number | null; + statusCode?: number | null; /** Format: int64 */ - responseTimeMs: number | null; - responseHeaders: { + responseTimeMs?: number | null; + responseHeaders?: { [key: string]: (string | null)[] | null; } | null; - bodyPreview: string | null; + bodyPreview?: string | null; /** Format: int64 */ - responseSizeBytes: number | null; + responseSizeBytes?: number | null; /** Format: int32 */ - redirectCount: number | null; - finalUrl: string | null; + redirectCount?: number | null; + finalUrl?: string | null; assertionResults: components["schemas"]["AssertionTestResultDto"][]; - warnings: (string | null)[] | null; + warnings?: string[] | null; }; /** @description A point-in-time version snapshot of a monitor configuration */ MonitorVersionDto: { @@ -4488,14 +4583,14 @@ export interface components { * Format: int32 * @description User ID who made the change; null for automated changes */ - changedById: number | null; + changedById?: number | null; /** * @description Change source (DASHBOARD, CLI, API) * @enum {string} */ changedVia: "API" | "DASHBOARD" | "CLI" | "TERRAFORM"; /** @description Human-readable description of what changed */ - changeSummary: string | null; + changeSummary?: string | null; /** * Format: date-time * @description Timestamp when this version was recorded @@ -4527,7 +4622,7 @@ export interface components { */ policyId: string; /** @description Human-readable name of the matched policy (null if policy has been deleted) */ - policyName: string | null; + policyName?: string | null; /** * @description Current dispatch state * @enum {string} @@ -4537,7 +4632,7 @@ export interface components { * @description Why the dispatch reached COMPLETED: EXHAUSTED (all steps ran, no ack), RESOLVED (incident resolved), NO_STEPS (policy had no steps). Null for non-terminal states. * @enum {string|null} */ - completionReason: "EXHAUSTED" | "RESOLVED" | "NO_STEPS" | null; + completionReason?: "EXHAUSTED" | "RESOLVED" | "NO_STEPS" | null; /** * Format: int32 * @description 1-based index of the currently active escalation step @@ -4547,22 +4642,22 @@ export interface components { * Format: int32 * @description Total number of escalation steps in the policy (null if policy has been deleted) */ - totalSteps: number | null; + totalSteps?: number | null; /** * Format: date-time * @description Timestamp when this dispatch was acknowledged (null if not acknowledged) */ - acknowledgedAt: string | null; + acknowledgedAt?: string | null; /** * Format: date-time * @description Timestamp when the next escalation step will fire (null if not scheduled) */ - nextEscalationAt: string | null; + nextEscalationAt?: string | null; /** * Format: date-time * @description Timestamp of the most recent notification delivery */ - lastNotifiedAt: string | null; + lastNotifiedAt?: string | null; /** @description Delivery records for all channels associated with this dispatch */ deliveries: components["schemas"]["AlertDeliveryDto"][]; /** @@ -4588,11 +4683,11 @@ export interface components { /** @description Short notification title */ title: string; /** @description Full notification body; null for title-only notifications */ - body: string | null; + body?: string | null; /** @description Type of the resource this notification is about */ - resourceType: string | null; + resourceType?: string | null; /** @description ID of the resource this notification is about */ - resourceId: string | null; + resourceId?: string | null; /** @description Whether the notification has been read */ read: boolean; /** @@ -4636,14 +4731,18 @@ export interface components { */ updatedAt: string; }; - OpsGenieChannelConfig: { - channelType: "OpsGenieChannelConfig"; - } & (Omit & { + OpsGenieChannelConfig: Omit & { /** @description OpsGenie API key for alert creation */ apiKey: string; /** @description OpsGenie API region: us or eu */ region?: string | null; - }); + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + channelType: "opsgenie"; + }; /** @description Organization account details */ OrganizationDto: { /** @@ -4654,13 +4753,13 @@ export interface components { /** @description Organization name */ name: string; /** @description Billing and contact email */ - email: string | null; + email?: string | null; /** @description Team size range (e.g. 1-10, 11-50) */ - size: string | null; + size?: string | null; /** @description Industry vertical (e.g. SaaS, Fintech) */ - industry: string | null; + industry?: string | null; /** @description Organization website URL */ - websiteUrl: string | null; + websiteUrl?: string | null; }; /** @description Organization the key belongs to */ OrgInfo: { @@ -4679,26 +4778,30 @@ export interface components { size: number; sort: string[]; }; - PagerDutyChannelConfig: { - channelType: "PagerDutyChannelConfig"; - } & (Omit & { + PagerDutyChannelConfig: Omit & { /** @description PagerDuty Events API v2 routing (integration) key */ routingKey: string; /** @description Override PagerDuty severity mapping */ severityOverride?: string | null; - }); + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + channelType: "pagerduty"; + }; /** @description A top-level page section (either a group or an ungrouped component) */ PageSection: { /** * Format: uuid * @description Group ID when this section is a group */ - groupId: string | null; + groupId?: string | null; /** * Format: uuid * @description Component ID when this section is an ungrouped component */ - componentId: string | null; + componentId?: string | null; /** * Format: int32 * @description Position on the page (0-based) @@ -4713,14 +4816,14 @@ export interface components { */ tier: "FREE" | "STARTER" | "PRO" | "TEAM" | "BUSINESS" | "ENTERPRISE"; /** @description Subscription status (null if no subscription) */ - subscriptionStatus: string | null; + subscriptionStatus?: string | null; /** @description Whether the org is on a trial */ trialActive: boolean; /** * Format: date-time * @description Trial expiry (null if not trialing) */ - trialExpiresAt: string | null; + trialExpiresAt?: string | null; /** @description Entitlement limits keyed by entitlement name */ entitlements: { [key: string]: components["schemas"]["EntitlementDto"]; @@ -4730,25 +4833,51 @@ export interface components { [key: string]: number; }; }; + /** @description Aggregated poll metrics for a time bucket */ + PollChartBucketDto: { + /** + * Format: date-time + * @description Start of the time bucket (ISO 8601) + */ + bucket: string; + /** + * Format: double + * @description Uptime percentage for this bucket; null when no data + * @example 100 + */ + uptimePercent?: number | null; + /** + * Format: double + * @description Average response time in milliseconds for this bucket + * @example 245.3 + */ + avgResponseTimeMs?: number | null; + /** + * Format: int64 + * @description Total polls in this bucket + * @example 60 + */ + totalPolls: number; + }; PublishStatusPageIncidentRequest: { /** @description Customer-facing title; null keeps draft value */ - title: string | null; + title?: string | null; /** * @description Impact level; null keeps draft value * @enum {string|null} */ - impact: "NONE" | "MINOR" | "MAJOR" | "CRITICAL" | null; + impact?: "NONE" | "MINOR" | "MAJOR" | "CRITICAL" | null; /** * @description Incident status; null keeps draft value (must be an active status) * @enum {string|null} */ - status: "INVESTIGATING" | "IDENTIFIED" | "MONITORING" | "RESOLVED" | null; + status?: "INVESTIGATING" | "IDENTIFIED" | "MONITORING" | "RESOLVED" | null; /** @description Initial update body; null keeps draft value */ - body: string | null; + body?: string | null; /** @description Affected components; null keeps draft value */ - affectedComponents: components["schemas"]["AffectedComponent"][] | null; + affectedComponents?: components["schemas"]["AffectedComponent"][] | null; /** @description Whether to notify subscribers (default: true) */ - notifySubscribers: boolean | null; + notifySubscribers?: boolean | null; }; /** @description Rate-limit quota for the current sliding window */ RateLimitInfo: { @@ -4786,18 +4915,20 @@ export interface components { */ cooldownMinutes: number; }; - RedirectCountAssertion: { - type: "RedirectCountAssertion"; - } & (Omit & { + RedirectCountAssertion: Omit & { /** * Format: int32 * @description Maximum number of HTTP redirects allowed before the check fails */ maxCount?: number; - }); - RedirectTargetAssertion: { - type: "RedirectTargetAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "redirect_count"; + }; + RedirectTargetAssertion: Omit & { /** @description Expected final URL after following redirects */ expected: string; /** @@ -4805,13 +4936,23 @@ export interface components { * @enum {string} */ operator: "equals" | "contains" | "less_than" | "greater_than" | "matches" | "range"; - }); - RegexBodyAssertion: { - type: "RegexBodyAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "redirect_target"; + }; + RegexBodyAssertion: Omit & { /** @description Regular expression the response body must match */ pattern: string; - }); + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "regex_body"; + }; /** @description Latest check result for a single region */ RegionStatusDto: { /** @@ -4829,14 +4970,14 @@ export interface components { * @description Response time in milliseconds for the last check * @example 95 */ - responseTimeMs: number | null; + responseTimeMs?: number | null; /** * Format: date-time * @description Timestamp of the last check in this region (ISO 8601) */ timestamp: string; /** @description Severity hint: 'down' for hard failures, 'degraded' for warn-only failures, null when passing */ - severityHint: string | null; + severityHint?: string | null; }; /** @description Request body for removing tags from a monitor */ RemoveMonitorTagsRequest: { @@ -4876,49 +5017,49 @@ export interface components { /** @description URL-safe group identifier */ slug: string; /** @description Optional group description */ - description: string | null; + description?: string | null; /** * Format: uuid * @description Notification policy applied to this group */ - alertPolicyId: string | null; + alertPolicyId?: string | null; /** * Format: int32 * @description Default check frequency in seconds for member monitors */ - defaultFrequency: number | null; + defaultFrequency?: number | null; /** @description Default regions for member monitors */ - defaultRegions: (string | null)[] | null; - defaultRetryStrategy: components["schemas"]["RetryStrategy"]; + defaultRegions?: string[] | null; + defaultRetryStrategy?: components["schemas"]["RetryStrategy"] | null; /** @description Default alert channel IDs for member monitors */ - defaultAlertChannels: (string | null)[] | null; + defaultAlertChannels?: string[] | null; /** * Format: uuid * @description Default environment ID for member monitors */ - defaultEnvironmentId: string | null; + defaultEnvironmentId?: string | null; /** * @description Health threshold type: COUNT or PERCENTAGE * @enum {string|null} */ - healthThresholdType: "COUNT" | "PERCENTAGE" | null; + healthThresholdType?: "COUNT" | "PERCENTAGE" | null; /** @description Health threshold value */ - healthThresholdValue: number | null; + healthThresholdValue?: number | null; /** @description When true, member-level incidents skip notification dispatch; only group alerts fire */ suppressMemberAlerts: boolean; /** * Format: int32 * @description Seconds to wait after health threshold breach before creating group incident */ - confirmationDelaySeconds: number | null; + confirmationDelaySeconds?: number | null; /** * Format: int32 * @description Cooldown minutes after group incident resolves before a new one can open */ - recoveryCooldownMinutes: number | null; + recoveryCooldownMinutes?: number | null; health: components["schemas"]["ResourceGroupHealthDto"]; /** @description Member list with individual statuses; populated on detail GET only */ - members: components["schemas"]["ResourceGroupMemberDto"][] | null; + members?: components["schemas"]["ResourceGroupMemberDto"][] | null; /** * Format: date-time * @description Timestamp when the group was created @@ -4956,12 +5097,12 @@ export interface components { * @description Computed group health status based on threshold: 'healthy', 'degraded', or 'down'. Null when no health threshold is configured. * @enum {string|null} */ - thresholdStatus: "healthy" | "degraded" | "down" | null; + thresholdStatus?: "healthy" | "degraded" | "down" | null; /** * Format: int32 * @description Number of failing members at time of last evaluation */ - failingCount: number | null; + failingCount?: number | null; }; /** @description A single member of a resource group with its computed health status */ ResourceGroupMemberDto: { @@ -4981,28 +5122,28 @@ export interface components { * Format: uuid * @description Monitor ID; set when memberType is 'monitor' */ - monitorId: string | null; + monitorId?: string | null; /** * Format: uuid * @description Service ID; set when memberType is 'service' */ - serviceId: string | null; + serviceId?: string | null; /** @description Display name of the referenced monitor or service */ - name: string | null; + name?: string | null; /** @description Slug identifier for the service (services only); used for icons and uptime API calls */ - slug: string | null; + slug?: string | null; /** * Format: uuid * @description Subscription ID for the service (services only); used to link to the dependency detail page */ - subscriptionId: string | null; + subscriptionId?: string | null; /** * @description Computed health status for this member * @enum {string} */ status: "operational" | "maintenance" | "degraded" | "down"; /** @description Effective check frequency label showing the group default when the monitor inherits it; null for services or when no group default is configured */ - effectiveFrequency: string | null; + effectiveFrequency?: string | null; /** * Format: date-time * @description Timestamp when the member was added to the group @@ -5012,56 +5153,68 @@ export interface components { * Format: double * @description 24h uptime percentage; populated when includeMetrics=true */ - uptime24h: number | null; + uptime24h?: number | null; /** @description Uptime tick values (0-100) for last-24h mini chart; populated when includeMetrics=true */ - chartData: (number | null)[] | null; + chartData?: number[] | null; /** * Format: double * @description Average latency in ms (monitors only); populated when includeMetrics=true */ - avgLatencyMs: number | null; + avgLatencyMs?: number | null; /** * Format: double * @description P95 latency in ms (monitors only); populated when includeMetrics=true */ - p95LatencyMs: number | null; + p95LatencyMs?: number | null; /** * Format: date-time * @description Timestamp of the most recent health check; populated when includeMetrics=true */ - lastCheckedAt: string | null; + lastCheckedAt?: string | null; /** @description Monitor type (HTTP, DNS, TCP, ICMP, HEARTBEAT, MCP); monitors only */ - monitorType: string | null; + monitorType?: string | null; /** @description Environment name; monitors only */ - environmentName: string | null; + environmentName?: string | null; }; - ResponseSizeAssertion: { - type: "ResponseSizeAssertion"; - } & (Omit & { + ResponseSizeAssertion: Omit & { /** * Format: int32 * @description Maximum response body size in bytes before the check fails */ maxBytes?: number; - }); - ResponseTimeAssertion: { - type: "ResponseTimeAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "response_size"; + }; + ResponseTimeAssertion: Omit & { /** * Format: int32 * @description Maximum allowed response time in milliseconds before the check fails */ thresholdMs?: number; - }); - ResponseTimeWarnAssertion: { - type: "ResponseTimeWarnAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "response_time"; + }; + ResponseTimeWarnAssertion: Omit & { /** * Format: int32 * @description HTTP response time in milliseconds that triggers a warning only */ warnMs?: number; - }); + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "response_time_warn"; + }; /** @description Dashboard summary: current status, per-region latest results, and chart data */ ResultSummaryDto: { /** @@ -5078,13 +5231,13 @@ export interface components { * @description Uptime percentage over the last 24 hours; null when no data * @example 99.95 */ - uptime24h: number | null; + uptime24h?: number | null; /** * Format: double * @description Uptime percentage for the selected chart window; null when no data * @example 99.8 */ - uptimeWindow: number | null; + uptimeWindow?: number | null; }; /** @description Default retry strategy for member monitors; null clears */ RetryStrategy: { @@ -5115,29 +5268,29 @@ export interface components { /** @description Current maintenance status (scheduled, in_progress, completed) */ status: string; /** @description Reported impact level */ - impact: string | null; + impact?: string | null; /** @description Vendor-provided short URL to the maintenance page */ - shortlink: string | null; + shortlink?: string | null; /** * Format: date-time * @description Timestamp when the maintenance is scheduled to begin */ - scheduledFor: string | null; + scheduledFor?: string | null; /** * Format: date-time * @description Timestamp when the maintenance is scheduled to end */ - scheduledUntil: string | null; + scheduledUntil?: string | null; /** * Format: date-time * @description Timestamp when the maintenance actually started */ - startedAt: string | null; + startedAt?: string | null; /** * Format: date-time * @description Timestamp when the maintenance was completed */ - completedAt: string | null; + completedAt?: string | null; /** @description Components affected by this maintenance */ affectedComponents: components["schemas"]["MaintenanceComponentRef"][]; /** @description Status updates posted during the maintenance lifecycle */ @@ -5170,16 +5323,16 @@ export interface components { */ updatedAt: string; /** @description Monitors that reference this secret; null on create/update responses */ - usedByMonitors: components["schemas"]["MonitorReference"][] | null; + usedByMonitors?: components["schemas"]["MonitorReference"][] | null; }; /** @description Admin-editable SEO metadata for pSEO pages */ SeoMetadataDto: { /** @description Short description for meta tags (max 160 chars) */ - shortDescription: string | null; + shortDescription?: string | null; /** @description Full description for the service page */ - description: string | null; + description?: string | null; /** @description Long-form about text for the About section on pSEO pages */ - about: string | null; + about?: string | null; }; /** @description Related services */ ServiceCatalogDto: { @@ -5187,16 +5340,16 @@ export interface components { id: string; slug: string; name: string; - category: string | null; - officialStatusUrl: string | null; - developerContext: string | null; - logoUrl: string | null; + category?: string | null; + officialStatusUrl?: string | null; + developerContext?: string | null; + logoUrl?: string | null; adapterType: string; /** Format: int32 */ pollingIntervalSeconds: number; enabled: boolean; published: boolean; - overallStatus: string | null; + overallStatus?: string | null; /** Format: date-time */ createdAt: string; /** Format: date-time */ @@ -5210,7 +5363,7 @@ export interface components { * Format: double * @description Aggregated 30-day uptime percentage across all components */ - uptime30d: number | null; + uptime30d?: number | null; }; /** @description A first-class service component with lifecycle and uptime data */ ServiceComponentDto: { @@ -5219,17 +5372,17 @@ export interface components { externalId: string; name: string; status: string; - description: string | null; + description?: string | null; /** Format: uuid */ - groupId: string | null; + groupId?: string | null; /** Format: int32 */ - position: number | null; + position?: number | null; showcase: boolean; onlyShowIfDegraded: boolean; /** Format: date-time */ - startDate: string | null; + startDate?: string | null; /** Format: date-time */ - vendorCreatedAt: string | null; + vendorCreatedAt?: string | null; lifecycleStatus: string; /** * @description Data classification: full, status_only, or metric_only @@ -5239,12 +5392,12 @@ export interface components { /** @description Whether uptime data is available for this component */ hasUptime: boolean; /** @description Geographic region for regional components (AWS, GCP, Azure) */ - region: string | null; + region?: string | null; /** @description Display name of the parent group */ - groupName: string | null; - uptime: components["schemas"]["ComponentUptimeSummaryDto"]; + groupName?: string | null; + uptime?: components["schemas"]["ComponentUptimeSummaryDto"] | null; /** Format: date-time */ - statusChangedAt: string | null; + statusChangedAt?: string | null; /** Format: date-time */ firstSeenAt: string; /** Format: date-time */ @@ -5256,10 +5409,10 @@ export interface components { id: string; slug: string; name: string; - category: string | null; - officialStatusUrl: string | null; - developerContext: string | null; - logoUrl: string | null; + category?: string | null; + officialStatusUrl?: string | null; + developerContext?: string | null; + logoUrl?: string | null; adapterType: string; /** Format: int32 */ pollingIntervalSeconds: number; @@ -5268,29 +5421,29 @@ export interface components { createdAt: string; /** Format: date-time */ updatedAt: string; - currentStatus: components["schemas"]["ServiceStatusDto"]; + currentStatus?: components["schemas"]["ServiceStatusDto"] | null; recentIncidents: components["schemas"]["ServiceIncidentDto"][]; components: components["schemas"]["ServiceComponentDto"][]; - uptime: components["schemas"]["ComponentUptimeSummaryDto"]; + uptime?: components["schemas"]["ComponentUptimeSummaryDto"] | null; activeMaintenances: components["schemas"]["ScheduledMaintenanceDto"][]; dataCompleteness: string; - seoMetadata: components["schemas"]["SeoMetadataDto"]; - relatedServices: components["schemas"]["ServiceCatalogDto"][] | null; + seoMetadata?: components["schemas"]["SeoMetadataDto"] | null; + relatedServices?: components["schemas"]["ServiceCatalogDto"][] | null; }; ServiceIncidentDetailDto: { /** Format: uuid */ id: string; title: string; status: string; - impact: string | null; + impact?: string | null; /** Format: date-time */ - startedAt: string | null; + startedAt?: string | null; /** Format: date-time */ - resolvedAt: string | null; + resolvedAt?: string | null; /** Format: date-time */ - detectedAt: string | null; - shortlink: string | null; - affectedComponents: (string | null)[] | null; + detectedAt?: string | null; + shortlink?: string | null; + affectedComponents?: string[] | null; updates: components["schemas"]["ServiceIncidentUpdateDto"][]; }; ServiceIncidentDto: { @@ -5298,33 +5451,33 @@ export interface components { id: string; /** Format: uuid */ serviceId: string; - serviceSlug: string | null; - serviceName: string | null; - externalId: string | null; + serviceSlug?: string | null; + serviceName?: string | null; + externalId?: string | null; title: string; status: string; - impact: string | null; + impact?: string | null; /** Format: date-time */ - startedAt: string | null; + startedAt?: string | null; /** Format: date-time */ - resolvedAt: string | null; + resolvedAt?: string | null; /** Format: date-time */ - updatedAt: string | null; - shortlink: string | null; + updatedAt?: string | null; + shortlink?: string | null; /** Format: date-time */ - detectedAt: string | null; + detectedAt?: string | null; /** Format: date-time */ - vendorCreatedAt: string | null; + vendorCreatedAt?: string | null; }; ServiceIncidentUpdateDto: { status: string; - body: string | null; + body?: string | null; /** Format: date-time */ - displayAt: string | null; + displayAt?: string | null; }; ServiceLiveStatusDto: { /** @description Current overall status of the service, e.g. operational, degraded_performance */ - overallStatus: string | null; + overallStatus?: string | null; /** @description Current status of each active component */ componentStatuses: components["schemas"]["ComponentStatusDto"][]; /** @@ -5333,12 +5486,101 @@ export interface components { */ activeIncidentCount: number; /** @description ISO 8601 timestamp of the last status poll */ - lastPolledAt: string | null; + lastPolledAt?: string | null; + }; + /** @description A single poll result from the status poller */ + ServicePollResultDto: { + /** + * Format: uuid + * @description Service ID + */ + serviceId: string; + /** + * Format: date-time + * @description Timestamp when the poll was executed (ISO 8601) + */ + timestamp: string; + /** + * @description Overall status of the service at time of poll + * @example operational + */ + overallStatus?: string | null; + /** + * Format: int32 + * @description Response time of the poll in milliseconds + * @example 245 + */ + responseTimeMs?: number | null; + /** + * Format: int32 + * @description HTTP status code from the upstream status page + * @example 200 + */ + httpStatusCode?: number | null; + /** + * @description Whether the poll succeeded + * @example true + */ + passed: boolean; + /** @description Reason for failure when passed=false */ + failureReason?: string | null; + /** + * Format: int32 + * @description Number of components reported by the service + * @example 12 + */ + componentCount: number; + /** + * Format: int32 + * @description Number of degraded or non-operational components + * @example 1 + */ + degradedCount: number; + }; + /** @description Aggregated poll metrics and chart data for a service */ + ServicePollSummaryDto: { + /** + * Format: double + * @description Uptime percentage over the requested window; null when no data + * @example 99.95 + */ + uptimePercentage?: number | null; + /** + * Format: int64 + * @description Total number of polls executed + * @example 4320 + */ + totalPolls: number; + /** + * Format: int64 + * @description Number of polls that succeeded + * @example 4318 + */ + passedPolls: number; + /** + * Format: double + * @description Average response time in milliseconds; null when no data + * @example 312.5 + */ + avgResponseTimeMs?: number | null; + /** + * Format: double + * @description 95th-percentile response time in milliseconds; null when no data + * @example 580 + */ + p95ResponseTimeMs?: number | null; + /** + * @description Time window used for the summary + * @example 30d + */ + window: string; + /** @description Time-bucketed chart data for response time and uptime */ + chartData: components["schemas"]["PollChartBucketDto"][]; }; ServiceStatusDto: { overallStatus: string; /** Format: date-time */ - lastPolledAt: string | null; + lastPolledAt?: string | null; }; /** @description Optional body for subscribing to a specific component of a service */ ServiceSubscribeRequest: { @@ -5346,9 +5588,9 @@ export interface components { * Format: uuid * @description ID of the component to subscribe to. Omit or null for whole-service subscription. */ - componentId: string | null; + componentId?: string | null; /** @description Alert sensitivity level. Defaults to INCIDENTS_ONLY when not provided. */ - alertSensitivity: string | null; + alertSensitivity?: string | null; }; /** @description An org-level service subscription with current status information */ ServiceSubscriptionDto: { @@ -5364,22 +5606,22 @@ export interface components { serviceId: string; slug: string; name: string; - category: string | null; - officialStatusUrl: string | null; + category?: string | null; + officialStatusUrl?: string | null; adapterType: string; /** Format: int32 */ pollingIntervalSeconds: number; enabled: boolean; /** @description Logo URL from the service catalog */ - logoUrl: string | null; + logoUrl?: string | null; /** @description Current overall status; null when the service has never been polled */ - overallStatus: string | null; + overallStatus?: string | null; /** * Format: uuid * @description Subscribed component id; null for whole-service subscription */ - componentId: string | null; - component: components["schemas"]["ServiceComponentDto"]; + componentId?: string | null; + component?: components["schemas"]["ServiceComponentDto"] | null; /** * @description Alert sensitivity: ALL (synthetic + real incidents), INCIDENTS_ONLY (real vendor incidents, default), MAJOR_ONLY (real + DOWN severity) * @enum {string} @@ -5398,7 +5640,7 @@ export interface components { * @description Overall uptime percentage across the entire period; null when no polling data exists * @example 99.95 */ - overallUptimePct: number | null; + overallUptimePct?: number | null; /** * @description Requested period * @example 7d @@ -5415,7 +5657,7 @@ export interface components { * @description Data source: vendor_reported, incident_derived, or poll_derived * @example vendor_reported */ - source: string | null; + source?: string | null; }; /** @description Replace the alert channels linked to a monitor */ SetAlertChannelsRequest: { @@ -5468,19 +5710,19 @@ export interface components { data: components["schemas"]["InviteDto"]; }; SingleValueResponseListUUID: { - data: (string | null)[] | null; + data: string[]; }; SingleValueResponseLong: { /** Format: int64 */ - data: number | null; + data: number; }; SingleValueResponseMaintenanceWindowDto: { data: components["schemas"]["MaintenanceWindowDto"]; }; SingleValueResponseMapStringListComponentUptimeDayDto: { data: { - [key: string]: components["schemas"]["ComponentUptimeDayDto"][] | null; - } | null; + [key: string]: components["schemas"]["ComponentUptimeDayDto"][]; + }; }; SingleValueResponseMonitorAssertionDto: { data: components["schemas"]["MonitorAssertionDto"]; @@ -5530,6 +5772,9 @@ export interface components { SingleValueResponseServiceLiveStatusDto: { data: components["schemas"]["ServiceLiveStatusDto"]; }; + SingleValueResponseServicePollSummaryDto: { + data: components["schemas"]["ServicePollSummaryDto"]; + }; SingleValueResponseServiceSubscriptionDto: { data: components["schemas"]["ServiceSubscriptionDto"]; }; @@ -5555,7 +5800,7 @@ export interface components { data: components["schemas"]["StatusPageSubscriberDto"]; }; SingleValueResponseString: { - data: string | null; + data: string; }; SingleValueResponseTagDto: { data: components["schemas"]["TagDto"]; @@ -5581,26 +5826,32 @@ export interface components { SingleValueResponseWorkspaceDto: { data: components["schemas"]["WorkspaceDto"]; }; - SlackChannelConfig: { - channelType: "SlackChannelConfig"; - } & (Omit & { + SlackChannelConfig: Omit & { /** @description Slack incoming webhook URL */ webhookUrl: string; /** @description Optional mention text included in notifications, e.g. @channel */ mentionText?: string | null; - }); - SslExpiryAssertion: { - type: "SslExpiryAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + channelType: "slack"; + }; + SslExpiryAssertion: Omit & { /** * Format: int32 * @description Minimum days before TLS certificate expiry; fails or warns below this threshold */ minDaysRemaining?: number; - }); - StatusCodeAssertion: { - type: "StatusCodeAssertion"; - } & (Omit & { + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "ssl_expiry"; + }; + StatusCodeAssertion: Omit & { /** @description Expected status code, range pattern, or wildcard such as 2xx */ expected: string; /** @@ -5608,35 +5859,41 @@ export interface components { * @enum {string} */ operator: "equals" | "contains" | "less_than" | "greater_than" | "matches" | "range"; - }); + } & { + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "status_code"; + }; /** @description Updated branding configuration; null preserves current */ StatusPageBranding: { /** @description URL for the logo image displayed in the header */ - logoUrl: string | null; + logoUrl?: string | null; /** @description URL for the browser tab favicon */ - faviconUrl: string | null; + faviconUrl?: string | null; /** @description Primary brand color as hex, e.g. #4F46E5; drives accent/links/buttons */ - brandColor: string | null; + brandColor?: string | null; /** @description Page body background color as hex, e.g. #FAFAFA */ - pageBackground: string | null; + pageBackground?: string | null; /** @description Card/surface background color as hex, e.g. #FFFFFF */ - cardBackground: string | null; + cardBackground?: string | null; /** @description Primary text color as hex, e.g. #09090B */ - textColor: string | null; + textColor?: string | null; /** @description Card border color as hex, e.g. #E4E4E7 */ - borderColor: string | null; + borderColor?: string | null; /** @description Header layout style (reserved for future use) */ - headerStyle: string | null; + headerStyle?: string | null; /** @description Color theme: light or dark (default: light) */ - theme: string | null; + theme?: string | null; /** @description URL where visitors can report a problem */ - reportUrl: string | null; + reportUrl?: string | null; /** @description Whether to hide the 'Powered by DevHelm' footer badge */ hidePoweredBy: boolean; /** @description Custom CSS injected via