up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled

This commit is contained in:
master
2025-11-27 15:05:48 +02:00
parent 4831c7fcb0
commit e950474a77
278 changed files with 81498 additions and 672 deletions

View File

@@ -9,7 +9,15 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using StellaOps.Notifier.WebService.Contracts;
using StellaOps.Notifier.WebService.Endpoints;
using StellaOps.Notifier.WebService.Setup;
using StellaOps.Notifier.Worker.Dispatch;
using StellaOps.Notifier.Worker.Escalation;
using StellaOps.Notifier.Worker.Observability;
using StellaOps.Notifier.Worker.Security;
using StellaOps.Notifier.Worker.StormBreaker;
using StellaOps.Notifier.Worker.Templates;
using StellaOps.Notifier.Worker.Tenancy;
using StellaOps.Notify.Storage.Mongo;
using StellaOps.Notify.Storage.Mongo.Documents;
using StellaOps.Notify.Storage.Mongo.Repositories;
@@ -39,12 +47,46 @@ if (!isTesting)
// Fallback no-op event queue for environments that do not configure a real backend.
builder.Services.TryAddSingleton<INotifyEventQueue, NullNotifyEventQueue>();
// Template service for v2 API preview endpoint
builder.Services.AddTemplateServices(options =>
{
var provenanceUrl = builder.Configuration["notifier:provenance:baseUrl"];
if (!string.IsNullOrWhiteSpace(provenanceUrl))
{
options.ProvenanceBaseUrl = provenanceUrl;
}
});
// Escalation and on-call services
builder.Services.AddEscalationServices(builder.Configuration);
// Storm breaker, localization, and fallback services
builder.Services.AddStormBreakerServices(builder.Configuration);
// Security services (signing, webhook validation, HTML sanitization, tenant isolation)
builder.Services.AddNotifierSecurityServices(builder.Configuration);
// Observability services (metrics, tracing, dead-letter, chaos testing, retention)
builder.Services.AddNotifierObservabilityServices(builder.Configuration);
// Tenancy services (context accessor, RLS enforcement, channel resolution, notification enrichment)
builder.Services.AddNotifierTenancy(builder.Configuration);
builder.Services.AddHealthChecks();
var app = builder.Build();
// Enable WebSocket support for live incident feed
app.UseWebSockets(new WebSocketOptions
{
KeepAliveInterval = TimeSpan.FromSeconds(30)
});
app.MapHealthChecks("/healthz");
// Tenant context middleware (extracts and validates tenant from headers/query)
app.UseTenantContext();
// Deprecation headers for retiring v1 APIs (RFC 8594 / IETF Sunset)
app.Use(async (context, next) =>
{
@@ -320,17 +362,26 @@ app.MapPost("/api/v1/notify/pack-approvals/{packId}/ack", async (
return Results.StatusCode(StatusCodes.Status200OK);
}
// Use actor from request or fall back to endpoint name
var actor = !string.IsNullOrWhiteSpace(request.Actor) ? request.Actor : "pack-approvals-ack";
try
{
var auditEntry = new NotifyAuditEntryDocument
{
TenantId = tenantId,
Actor = "pack-approvals-ack",
Actor = actor,
Action = "pack.approval.acknowledged",
EntityId = packId,
EntityType = "pack-approval",
Timestamp = timeProvider.GetUtcNow(),
Payload = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<MongoDB.Bson.BsonDocument>(JsonSerializer.Serialize(request))
Payload = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<MongoDB.Bson.BsonDocument>(JsonSerializer.Serialize(new
{
request.AckToken,
request.Decision,
request.Comment,
request.Actor
}))
};
await audit.AppendAsync(auditEntry, context.RequestAborted).ConfigureAwait(false);
@@ -343,6 +394,25 @@ app.MapPost("/api/v1/notify/pack-approvals/{packId}/ack", async (
return Results.NoContent();
});
// v2 REST APIs (/api/v2/notify/... for existing consumers)
app.MapNotifyApiV2();
// v2 REST APIs (/api/v2/... simplified paths)
app.MapRuleEndpoints();
app.MapTemplateEndpoints();
app.MapIncidentEndpoints();
app.MapIncidentLiveFeed();
app.MapSimulationEndpoints();
app.MapQuietHoursEndpoints();
app.MapThrottleEndpoints();
app.MapOperatorOverrideEndpoints();
app.MapEscalationEndpoints();
app.MapStormBreakerEndpoints();
app.MapLocalizationEndpoints();
app.MapFallbackEndpoints();
app.MapSecurityEndpoints();
app.MapObservabilityEndpoints();
app.MapGet("/.well-known/openapi", (HttpContext context) =>
{
context.Response.Headers["X-OpenAPI-Scope"] = "notify";
@@ -356,6 +426,13 @@ info:
paths:
/api/v1/notify/quiet-hours: {}
/api/v1/notify/incidents: {}
/api/v2/notify/rules: {}
/api/v2/notify/templates: {}
/api/v2/notify/incidents: {}
/api/v2/rules: {}
/api/v2/templates: {}
/api/v2/incidents: {}
/api/v2/incidents/live: {}
""";
return Results.Text(stub, "application/yaml", Encoding.UTF8);