work
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-25 08:01:23 +02:00
parent d92973d6fd
commit 6bee1fdcf5
207 changed files with 12816 additions and 2295 deletions

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
@@ -31,6 +32,8 @@ if (!isTesting)
builder.Services.AddNotifyMongoStorage(mongoSection);
builder.Services.AddHostedService<MongoInitializationHostedService>();
builder.Services.AddHostedService<PackApprovalTemplateSeeder>();
builder.Services.AddHostedService<AttestationTemplateSeeder>();
builder.Services.AddHostedService<RiskTemplateSeeder>();
}
// Fallback no-op event queue for environments that do not configure a real backend.
@@ -173,6 +176,122 @@ app.MapPost("/api/v1/notify/pack-approvals", async (
return Results.Accepted();
});
app.MapPost("/api/v1/notify/attestation-events", async (
HttpContext context,
AttestationEventRequest request,
INotifyEventQueue? eventQueue,
TimeProvider timeProvider) =>
{
var tenantId = context.Request.Headers["X-StellaOps-Tenant"].ToString();
if (string.IsNullOrWhiteSpace(tenantId))
{
return Results.BadRequest(Error("tenant_missing", "X-StellaOps-Tenant header is required.", context));
}
if (string.IsNullOrWhiteSpace(request.Kind))
{
return Results.BadRequest(Error("invalid_request", "kind is required.", context));
}
var eventId = request.EventId != Guid.Empty ? request.EventId : Guid.NewGuid();
var ts = request.Timestamp is { } tsValue && tsValue != default ? tsValue : timeProvider.GetUtcNow();
if (eventQueue is not null)
{
var payload = request.Payload ?? new JsonObject();
var notifyEvent = NotifyEvent.Create(
eventId: eventId,
kind: request.Kind!,
tenant: tenantId,
ts: ts,
payload: payload,
attributes: request.Attributes ?? new Dictionary<string, string>(),
actor: request.Actor,
version: "1");
var idempotencyKey = context.Request.Headers["Idempotency-Key"].ToString();
if (string.IsNullOrWhiteSpace(idempotencyKey))
{
idempotencyKey = $"attestation|{tenantId}|{notifyEvent.Kind}|{notifyEvent.EventId}";
}
await eventQueue.PublishAsync(
new NotifyQueueEventMessage(
notifyEvent,
stream: "notify:events",
idempotencyKey: idempotencyKey,
partitionKey: tenantId,
traceId: context.TraceIdentifier),
context.RequestAborted).ConfigureAwait(false);
}
if (!string.IsNullOrWhiteSpace(request.ResumeToken))
{
context.Response.Headers["X-Resume-After"] = request.ResumeToken;
}
return Results.Accepted();
});
app.MapPost("/api/v1/notify/risk-events", async (
HttpContext context,
RiskEventRequest request,
INotifyEventQueue? eventQueue,
TimeProvider timeProvider) =>
{
var tenantId = context.Request.Headers["X-StellaOps-Tenant"].ToString();
if (string.IsNullOrWhiteSpace(tenantId))
{
return Results.BadRequest(Error("tenant_missing", "X-StellaOps-Tenant header is required.", context));
}
if (string.IsNullOrWhiteSpace(request.Kind))
{
return Results.BadRequest(Error("invalid_request", "kind is required.", context));
}
var eventId = request.EventId != Guid.Empty ? request.EventId : Guid.NewGuid();
var ts = request.Timestamp is { } tsValue && tsValue != default ? tsValue : timeProvider.GetUtcNow();
if (eventQueue is not null)
{
var payload = request.Payload ?? new JsonObject();
var notifyEvent = NotifyEvent.Create(
eventId: eventId,
kind: request.Kind!,
tenant: tenantId,
ts: ts,
payload: payload,
attributes: request.Attributes ?? new Dictionary<string, string>(),
actor: request.Actor,
version: "1");
var idempotencyKey = context.Request.Headers["Idempotency-Key"].ToString();
if (string.IsNullOrWhiteSpace(idempotencyKey))
{
idempotencyKey = $"risk|{tenantId}|{notifyEvent.Kind}|{notifyEvent.EventId}";
}
await eventQueue.PublishAsync(
new NotifyQueueEventMessage(
notifyEvent,
stream: "notify:events",
idempotencyKey: idempotencyKey,
partitionKey: tenantId,
traceId: context.TraceIdentifier),
context.RequestAborted).ConfigureAwait(false);
}
if (!string.IsNullOrWhiteSpace(request.ResumeToken))
{
context.Response.Headers["X-Resume-After"] = request.ResumeToken;
}
return Results.Accepted();
});
app.MapPost("/api/v1/notify/pack-approvals/{packId}/ack", async (
HttpContext context,
string packId,