- Implemented `MongoIndexModelTests` to verify index models for various stores. - Created `OpenApiMetadataFactory` with methods to generate OpenAPI metadata. - Added tests for `OpenApiMetadataFactory` to ensure expected defaults and URL overrides. - Introduced `ObserverSurfaceSecrets` and `WebhookSurfaceSecrets` for managing secrets. - Developed `RuntimeSurfaceFsClient` and `WebhookSurfaceFsClient` for manifest retrieval. - Added dependency injection tests for `SurfaceEnvironmentRegistration` in both Observer and Webhook contexts. - Implemented tests for secret resolution in `ObserverSurfaceSecretsTests` and `WebhookSurfaceSecretsTests`. - Created `EnsureLinkNotMergeCollectionsMigrationTests` to validate MongoDB migration logic. - Added project files for MongoDB tests and NuGet package mirroring.
502 lines
15 KiB
YAML
502 lines
15 KiB
YAML
# OpenAPI 3.1 specification for StellaOps Notifier WebService (draft)
|
|
openapi: 3.1.0
|
|
info:
|
|
title: StellaOps Notifier API
|
|
version: 0.6.0-draft
|
|
description: |
|
|
Contract for Notifications Studio (Notifier) covering rules, templates, incidents,
|
|
and quiet hours. Uses the platform error envelope and tenant header `X-StellaOps-Tenant`.
|
|
servers:
|
|
- url: https://api.stellaops.example.com
|
|
description: Production
|
|
- url: https://api.dev.stellaops.example.com
|
|
description: Development
|
|
security:
|
|
- oauth2: [notify.viewer]
|
|
- oauth2: [notify.operator]
|
|
- oauth2: [notify.admin]
|
|
paths:
|
|
/api/v1/notify/rules:
|
|
get:
|
|
summary: List notification rules
|
|
tags: [Rules]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Tenant'
|
|
- $ref: '#/components/parameters/PageSize'
|
|
- $ref: '#/components/parameters/PageToken'
|
|
responses:
|
|
'200':
|
|
description: Paginated rule list
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
items:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/NotifyRule' }
|
|
nextPageToken:
|
|
type: string
|
|
examples:
|
|
default:
|
|
value:
|
|
items:
|
|
- ruleId: rule-critical
|
|
tenantId: tenant-dev
|
|
name: Critical scanner verdicts
|
|
enabled: true
|
|
match:
|
|
eventKinds: [scanner.report.ready]
|
|
minSeverity: critical
|
|
actions:
|
|
- actionId: act-slack-critical
|
|
channel: chn-slack-soc
|
|
template: tmpl-critical
|
|
digest: instant
|
|
nextPageToken: null
|
|
default:
|
|
$ref: '#/components/responses/Error'
|
|
post:
|
|
summary: Create a notification rule
|
|
tags: [Rules]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Tenant'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/NotifyRule' }
|
|
examples:
|
|
create-rule:
|
|
value:
|
|
ruleId: rule-attest-fail
|
|
tenantId: tenant-dev
|
|
name: Attestation failures → SOC
|
|
enabled: true
|
|
match:
|
|
eventKinds: [attestor.verification.failed]
|
|
actions:
|
|
- actionId: act-soc
|
|
channel: chn-webhook-soc
|
|
template: tmpl-attest-verify-fail
|
|
responses:
|
|
'201':
|
|
description: Rule created
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/NotifyRule' }
|
|
default:
|
|
$ref: '#/components/responses/Error'
|
|
|
|
/api/v1/notify/rules/{ruleId}:
|
|
get:
|
|
summary: Fetch a rule
|
|
tags: [Rules]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Tenant'
|
|
- $ref: '#/components/parameters/RuleId'
|
|
responses:
|
|
'200':
|
|
description: Rule
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/NotifyRule' }
|
|
default:
|
|
$ref: '#/components/responses/Error'
|
|
patch:
|
|
summary: Update a rule (partial)
|
|
tags: [Rules]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Tenant'
|
|
- $ref: '#/components/parameters/RuleId'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
description: JSON Merge Patch
|
|
responses:
|
|
'200':
|
|
description: Updated rule
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/NotifyRule' }
|
|
default:
|
|
$ref: '#/components/responses/Error'
|
|
|
|
/api/v1/notify/templates:
|
|
get:
|
|
summary: List templates
|
|
tags: [Templates]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Tenant'
|
|
- name: key
|
|
in: query
|
|
description: Filter by template key
|
|
schema: { type: string }
|
|
responses:
|
|
'200':
|
|
description: Templates
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/NotifyTemplate' }
|
|
default:
|
|
$ref: '#/components/responses/Error'
|
|
post:
|
|
summary: Create a template
|
|
tags: [Templates]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Tenant'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/NotifyTemplate' }
|
|
responses:
|
|
'201':
|
|
description: Template created
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/NotifyTemplate' }
|
|
default:
|
|
$ref: '#/components/responses/Error'
|
|
|
|
/api/v1/notify/templates/{templateId}:
|
|
get:
|
|
summary: Fetch a template
|
|
tags: [Templates]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Tenant'
|
|
- $ref: '#/components/parameters/TemplateId'
|
|
responses:
|
|
'200':
|
|
description: Template
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/NotifyTemplate' }
|
|
default:
|
|
$ref: '#/components/responses/Error'
|
|
patch:
|
|
summary: Update a template (partial)
|
|
tags: [Templates]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Tenant'
|
|
- $ref: '#/components/parameters/TemplateId'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
description: JSON Merge Patch
|
|
responses:
|
|
'200':
|
|
description: Updated template
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/NotifyTemplate' }
|
|
default:
|
|
$ref: '#/components/responses/Error'
|
|
|
|
/api/v1/notify/incidents:
|
|
get:
|
|
summary: List incidents (paged)
|
|
tags: [Incidents]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Tenant'
|
|
- $ref: '#/components/parameters/PageSize'
|
|
- $ref: '#/components/parameters/PageToken'
|
|
responses:
|
|
'200':
|
|
description: Incident page
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
items:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/Incident' }
|
|
nextPageToken: { type: string }
|
|
default:
|
|
$ref: '#/components/responses/Error'
|
|
post:
|
|
summary: Raise an incident (ops/toggle/override)
|
|
tags: [Incidents]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Tenant'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/Incident' }
|
|
examples:
|
|
start-incident:
|
|
value:
|
|
incidentId: inc-telemetry-outage
|
|
kind: outage
|
|
severity: major
|
|
startedAt: 2025-11-17T04:02:00Z
|
|
shortDescription: "Telemetry pipeline degraded; burn-rate breach"
|
|
metadata:
|
|
source: slo-evaluator
|
|
responses:
|
|
'202':
|
|
description: Incident accepted
|
|
default:
|
|
$ref: '#/components/responses/Error'
|
|
|
|
/api/v1/notify/incidents/{incidentId}/ack:
|
|
post:
|
|
summary: Acknowledge an incident notification
|
|
tags: [Incidents]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Tenant'
|
|
- $ref: '#/components/parameters/IncidentId'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
ackToken:
|
|
type: string
|
|
description: DSSE-signed acknowledgement token
|
|
responses:
|
|
'204':
|
|
description: Acknowledged
|
|
default:
|
|
$ref: '#/components/responses/Error'
|
|
|
|
/api/v1/notify/quiet-hours:
|
|
get:
|
|
summary: Get quiet-hours schedule
|
|
tags: [QuietHours]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Tenant'
|
|
responses:
|
|
'200':
|
|
description: Quiet hours schedule
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/QuietHours' }
|
|
examples:
|
|
current:
|
|
value:
|
|
quietHoursId: qh-default
|
|
windows:
|
|
- timezone: UTC
|
|
days: [Mon, Tue, Wed, Thu, Fri]
|
|
start: "22:00"
|
|
end: "06:00"
|
|
exemptions:
|
|
- eventKinds: [attestor.verification.failed]
|
|
reason: "Always alert for attestation failures"
|
|
default:
|
|
$ref: '#/components/responses/Error'
|
|
post:
|
|
summary: Set quiet-hours schedule
|
|
tags: [QuietHours]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Tenant'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/QuietHours' }
|
|
responses:
|
|
'200':
|
|
description: Updated quiet hours
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/QuietHours' }
|
|
default:
|
|
$ref: '#/components/responses/Error'
|
|
|
|
components:
|
|
securitySchemes:
|
|
oauth2:
|
|
type: oauth2
|
|
flows:
|
|
clientCredentials:
|
|
tokenUrl: https://auth.stellaops.example.com/oauth/token
|
|
scopes:
|
|
notify.viewer: Read-only Notifier access
|
|
notify.operator: Manage rules/templates/incidents within tenant
|
|
notify.admin: Tenant-scoped administration
|
|
parameters:
|
|
Tenant:
|
|
name: X-StellaOps-Tenant
|
|
in: header
|
|
required: true
|
|
description: Tenant slug
|
|
schema: { type: string }
|
|
PageSize:
|
|
name: pageSize
|
|
in: query
|
|
schema: { type: integer, minimum: 1, maximum: 200, default: 50 }
|
|
PageToken:
|
|
name: pageToken
|
|
in: query
|
|
schema: { type: string }
|
|
RuleId:
|
|
name: ruleId
|
|
in: path
|
|
required: true
|
|
schema: { type: string }
|
|
TemplateId:
|
|
name: templateId
|
|
in: path
|
|
required: true
|
|
schema: { type: string }
|
|
IncidentId:
|
|
name: incidentId
|
|
in: path
|
|
required: true
|
|
schema: { type: string }
|
|
|
|
responses:
|
|
Error:
|
|
description: Standard error envelope
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ErrorEnvelope' }
|
|
examples:
|
|
validation:
|
|
value:
|
|
error:
|
|
code: validation_failed
|
|
message: "quietHours.windows[0].start must be HH:mm"
|
|
traceId: "f62f3c2b9c8e4c53"
|
|
|
|
schemas:
|
|
ErrorEnvelope:
|
|
type: object
|
|
required: [error]
|
|
properties:
|
|
error:
|
|
type: object
|
|
required: [code, message, traceId]
|
|
properties:
|
|
code: { type: string }
|
|
message: { type: string }
|
|
traceId: { type: string }
|
|
|
|
NotifyRule:
|
|
type: object
|
|
required: [ruleId, tenantId, name, match, actions]
|
|
properties:
|
|
ruleId: { type: string }
|
|
tenantId: { type: string }
|
|
name: { type: string }
|
|
description: { type: string }
|
|
enabled: { type: boolean, default: true }
|
|
match: { $ref: '#/components/schemas/RuleMatch' }
|
|
actions:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/RuleAction' }
|
|
labels:
|
|
type: object
|
|
additionalProperties: { type: string }
|
|
metadata:
|
|
type: object
|
|
additionalProperties: { type: string }
|
|
|
|
RuleMatch:
|
|
type: object
|
|
properties:
|
|
eventKinds:
|
|
type: array
|
|
items: { type: string }
|
|
minSeverity: { type: string, enum: [info, low, medium, high, critical] }
|
|
verdicts:
|
|
type: array
|
|
items: { type: string }
|
|
labels:
|
|
type: array
|
|
items: { type: string }
|
|
kevOnly: { type: boolean }
|
|
|
|
RuleAction:
|
|
type: object
|
|
required: [actionId, channel]
|
|
properties:
|
|
actionId: { type: string }
|
|
channel: { type: string }
|
|
template: { type: string }
|
|
digest: { type: string, description: "Digest window key e.g. instant|5m|15m|1h|1d" }
|
|
throttle: { type: string, description: "ISO-8601 duration, e.g. PT5M" }
|
|
locale: { type: string }
|
|
enabled: { type: boolean, default: true }
|
|
metadata:
|
|
type: object
|
|
additionalProperties: { type: string }
|
|
|
|
NotifyTemplate:
|
|
type: object
|
|
required: [templateId, tenantId, key, channelType, locale, body, renderMode, format]
|
|
properties:
|
|
templateId: { type: string }
|
|
tenantId: { type: string }
|
|
key: { type: string }
|
|
channelType: { type: string, enum: [slack, teams, email, webhook, custom] }
|
|
locale: { type: string, description: "BCP-47, lower-case" }
|
|
renderMode: { type: string, enum: [Markdown, Html, AdaptiveCard, PlainText, Json] }
|
|
format: { type: string, enum: [slack, teams, email, webhook, json] }
|
|
description: { type: string }
|
|
body: { type: string }
|
|
metadata:
|
|
type: object
|
|
additionalProperties: { type: string }
|
|
|
|
Incident:
|
|
type: object
|
|
required: [incidentId, kind, severity, startedAt]
|
|
properties:
|
|
incidentId: { type: string }
|
|
kind: { type: string, description: "outage|degradation|security|ops-drill" }
|
|
severity: { type: string, enum: [minor, major, critical] }
|
|
startedAt: { type: string, format: date-time }
|
|
endedAt: { type: string, format: date-time }
|
|
shortDescription: { type: string }
|
|
description: { type: string }
|
|
metadata:
|
|
type: object
|
|
additionalProperties: { type: string }
|
|
|
|
QuietHours:
|
|
type: object
|
|
required: [quietHoursId, windows]
|
|
properties:
|
|
quietHoursId: { type: string }
|
|
windows:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/QuietHoursWindow' }
|
|
exemptions:
|
|
type: array
|
|
description: Event kinds that bypass quiet hours
|
|
items:
|
|
type: object
|
|
properties:
|
|
eventKinds:
|
|
type: array
|
|
items: { type: string }
|
|
reason: { type: string }
|
|
|
|
QuietHoursWindow:
|
|
type: object
|
|
required: [timezone, days, start, end]
|
|
properties:
|
|
timezone: { type: string, description: "IANA TZ, e.g., UTC" }
|
|
days:
|
|
type: array
|
|
items:
|
|
type: string
|
|
enum: [Mon, Tue, Wed, Thu, Fri, Sat, Sun]
|
|
start: { type: string, description: "HH:mm" }
|
|
end: { type: string, description: "HH:mm" }
|