Files
git.stella-ops.org/src/Signals/StellaOps.Signals/Program.cs
master 00d2c99af9 feat: add Attestation Chain and Triage Evidence API clients and models
- Implemented Attestation Chain API client with methods for verifying, fetching, and managing attestation chains.
- Created models for Attestation Chain, including DSSE envelope structures and verification results.
- Developed Triage Evidence API client for fetching finding evidence, including methods for evidence retrieval by CVE and component.
- Added models for Triage Evidence, encapsulating evidence responses, entry points, boundary proofs, and VEX evidence.
- Introduced mock implementations for both API clients to facilitate testing and development.
2025-12-18 13:15:13 +02:00

938 lines
34 KiB
C#

using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NetEscapades.Configuration.Yaml;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Configuration;
using StellaOps.Signals.Authentication;
using StellaOps.Signals.Hosting;
using StellaOps.Signals.Models;
using StellaOps.Signals.Options;
using StellaOps.Signals.Parsing;
using StellaOps.Signals.Persistence;
using StellaOps.Signals.Routing;
using StellaOps.Signals.Services;
using StellaOps.Signals.Storage;
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddStellaOpsDefaults(options =>
{
options.BasePath = builder.Environment.ContentRootPath;
options.EnvironmentPrefix = "SIGNALS_";
options.ConfigureBuilder = configurationBuilder =>
{
var contentRoot = builder.Environment.ContentRootPath;
foreach (var relative in new[]
{
"../etc/signals.yaml",
"../etc/signals.local.yaml",
"signals.yaml",
"signals.local.yaml"
})
{
var path = Path.Combine(contentRoot, relative);
configurationBuilder.AddYamlFile(path, optional: true);
}
};
});
var bootstrap = builder.Configuration.BindOptions<SignalsOptions>(
SignalsOptions.SectionName,
static (options, _) =>
{
SignalsAuthorityOptionsConfigurator.ApplyDefaults(options.Authority);
options.Validate();
});
builder.Services.AddOptions<SignalsOptions>()
.Bind(builder.Configuration.GetSection(SignalsOptions.SectionName))
.PostConfigure(static options =>
{
SignalsAuthorityOptionsConfigurator.ApplyDefaults(options.Authority);
options.Validate();
})
.Validate(static options =>
{
try
{
options.Validate();
return true;
}
catch (Exception ex)
{
throw new OptionsValidationException(
SignalsOptions.SectionName,
typeof(SignalsOptions),
new[] { ex.Message });
}
})
.ValidateOnStart();
builder.Services.AddSingleton(sp => sp.GetRequiredService<IOptions<SignalsOptions>>().Value);
builder.Services.AddSingleton<SignalsStartupState>();
builder.Services.AddSingleton(TimeProvider.System);
builder.Services.AddSingleton<SignalsSealedModeMonitor>();
builder.Services.AddProblemDetails();
builder.Services.AddHealthChecks();
builder.Services.AddRouting(options => options.LowercaseUrls = true);
builder.Services.AddSingleton<ICallgraphRepository, InMemoryCallgraphRepository>();
builder.Services.AddSingleton<ICallgraphNormalizationService, CallgraphNormalizationService>();
builder.Services.AddSingleton<ICallGraphProjectionRepository, InMemoryCallGraphProjectionRepository>();
// Configure callgraph artifact storage based on driver
if (bootstrap.Storage.IsRustFsDriver())
{
// Configure HttpClient for RustFS
builder.Services.AddHttpClient(RustFsCallgraphArtifactStore.HttpClientName, (sp, client) =>
{
var opts = sp.GetRequiredService<IOptions<SignalsOptions>>().Value;
client.Timeout = opts.Storage.RustFs.Timeout;
})
.ConfigurePrimaryHttpMessageHandler(sp =>
{
var opts = sp.GetRequiredService<IOptions<SignalsOptions>>().Value;
var handler = new HttpClientHandler();
if (opts.Storage.RustFs.AllowInsecureTls)
{
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
return handler;
});
builder.Services.AddSingleton<ICallgraphArtifactStore, RustFsCallgraphArtifactStore>();
}
else
{
builder.Services.AddSingleton<ICallgraphArtifactStore, FileSystemCallgraphArtifactStore>();
}
builder.Services.AddSingleton<ICallgraphParser>(new SimpleJsonCallgraphParser("java"));
builder.Services.AddSingleton<ICallgraphParser>(new SimpleJsonCallgraphParser("nodejs"));
builder.Services.AddSingleton<ICallgraphParser>(new SimpleJsonCallgraphParser("python"));
builder.Services.AddSingleton<ICallgraphParser>(new SimpleJsonCallgraphParser("go"));
builder.Services.AddSingleton<ICallgraphParserResolver, CallgraphParserResolver>();
builder.Services.AddSingleton<ICallgraphIngestionService, CallgraphIngestionService>();
builder.Services.AddSingleton<ICallGraphSyncService, CallGraphSyncService>();
builder.Services.AddSingleton<IReachabilityCache>(sp =>
{
var options = sp.GetRequiredService<IOptions<SignalsOptions>>().Value;
return new RedisReachabilityCache(options.Cache);
});
builder.Services.AddSingleton<IRedisConnectionFactory, RedisConnectionFactory>();
builder.Services.AddSingleton<ReachabilityFactEventBuilder>();
builder.Services.AddSingleton<InMemoryReachabilityFactRepository>();
builder.Services.AddSingleton<IReachabilityFactRepository>(sp =>
{
var inner = sp.GetRequiredService<InMemoryReachabilityFactRepository>();
var cache = sp.GetRequiredService<IReachabilityCache>();
return new ReachabilityFactCacheDecorator(inner, cache);
});
builder.Services.AddSingleton<IUnknownsRepository, InMemoryUnknownsRepository>();
builder.Services.AddOptions<UnknownsScoringOptions>()
.Bind(builder.Configuration.GetSection(UnknownsScoringOptions.SectionName));
builder.Services.AddOptions<UnknownsDecayOptions>()
.Bind(builder.Configuration.GetSection(UnknownsDecayOptions.SectionName));
builder.Services.AddSingleton<IDeploymentRefsRepository, InMemoryDeploymentRefsRepository>();
builder.Services.AddSingleton<IGraphMetricsRepository, InMemoryGraphMetricsRepository>();
builder.Services.AddSingleton<IUnknownsScoringService, UnknownsScoringService>();
builder.Services.AddSingleton<IUnknownsDecayService, UnknownsDecayService>();
builder.Services.AddSingleton<ISignalRefreshService, SignalRefreshService>();
builder.Services.AddHostedService<NightlyDecayWorker>();
builder.Services.AddSingleton<IReachabilityStoreRepository, InMemoryReachabilityStoreRepository>();
builder.Services.AddHttpClient<RouterEventsPublisher>((sp, client) =>
{
var opts = sp.GetRequiredService<SignalsOptions>().Events.Router;
if (Uri.TryCreate(opts.BaseUrl, UriKind.Absolute, out var baseUri))
{
client.BaseAddress = baseUri;
}
if (opts.TimeoutSeconds > 0)
{
client.Timeout = TimeSpan.FromSeconds(opts.TimeoutSeconds);
}
client.DefaultRequestHeaders.ConnectionClose = false;
}).ConfigurePrimaryHttpMessageHandler(sp =>
{
var opts = sp.GetRequiredService<SignalsOptions>().Events.Router;
var handler = new HttpClientHandler();
if (opts.AllowInsecureTls)
{
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
return handler;
});
builder.Services.AddSingleton<IEventsPublisher>(sp =>
{
var options = sp.GetRequiredService<SignalsOptions>();
var eventBuilder = sp.GetRequiredService<ReachabilityFactEventBuilder>();
if (!options.Events.Enabled)
{
return new NullEventsPublisher();
}
if (string.Equals(options.Events.Driver, "redis", StringComparison.OrdinalIgnoreCase))
{
return new RedisEventsPublisher(
options,
sp.GetRequiredService<IRedisConnectionFactory>(),
eventBuilder,
sp.GetRequiredService<ILogger<RedisEventsPublisher>>());
}
if (string.Equals(options.Events.Driver, "router", StringComparison.OrdinalIgnoreCase))
{
return sp.GetRequiredService<RouterEventsPublisher>();
}
return new InMemoryEventsPublisher(
sp.GetRequiredService<ILogger<InMemoryEventsPublisher>>(),
eventBuilder);
});
builder.Services.AddSingleton<IReachabilityScoringService, ReachabilityScoringService>();
builder.Services.AddSingleton<IScoreExplanationService, ScoreExplanationService>(); // Sprint: SPRINT_3800_0001_0002
builder.Services.AddSingleton<IRuntimeFactsProvenanceNormalizer, RuntimeFactsProvenanceNormalizer>();
builder.Services.AddSingleton<IRuntimeFactsIngestionService, RuntimeFactsIngestionService>();
builder.Services.AddSingleton<IReachabilityUnionIngestionService, ReachabilityUnionIngestionService>();
builder.Services.AddSingleton<IUnknownsIngestionService, UnknownsIngestionService>();
builder.Services.AddSingleton<SyntheticRuntimeProbeBuilder>();
if (bootstrap.Authority.Enabled)
{
builder.Services.AddHttpContextAccessor();
builder.Services.AddStellaOpsScopeHandler();
builder.Services.AddAuthorization(options =>
{
options.AddStellaOpsScopePolicy(SignalsPolicies.Read, SignalsPolicies.Read);
options.AddStellaOpsScopePolicy(SignalsPolicies.Write, SignalsPolicies.Write);
options.AddStellaOpsScopePolicy(SignalsPolicies.Admin, SignalsPolicies.Admin);
});
builder.Services.AddStellaOpsResourceServerAuthentication(
builder.Configuration,
configurationSection: $"{SignalsOptions.SectionName}:Authority",
configure: resourceOptions =>
{
resourceOptions.Authority = bootstrap.Authority.Issuer;
resourceOptions.RequireHttpsMetadata = bootstrap.Authority.RequireHttpsMetadata;
resourceOptions.MetadataAddress = bootstrap.Authority.MetadataAddress;
resourceOptions.BackchannelTimeout = TimeSpan.FromSeconds(bootstrap.Authority.BackchannelTimeoutSeconds);
resourceOptions.TokenClockSkew = TimeSpan.FromSeconds(bootstrap.Authority.TokenClockSkewSeconds);
resourceOptions.Audiences.Clear();
foreach (var audience in bootstrap.Authority.Audiences)
{
resourceOptions.Audiences.Add(audience);
}
resourceOptions.RequiredScopes.Clear();
foreach (var scope in bootstrap.Authority.RequiredScopes)
{
resourceOptions.RequiredScopes.Add(scope);
}
foreach (var tenant in bootstrap.Authority.RequiredTenants)
{
resourceOptions.RequiredTenants.Add(tenant);
}
foreach (var network in bootstrap.Authority.BypassNetworks)
{
resourceOptions.BypassNetworks.Add(network);
}
});
}
else
{
builder.Services.AddAuthorization();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Anonymous";
options.DefaultChallengeScheme = "Anonymous";
}).AddScheme<AuthenticationSchemeOptions, AnonymousAuthenticationHandler>("Anonymous", static _ => { });
}
var app = builder.Build();
if (!bootstrap.Authority.Enabled)
{
app.Logger.LogWarning("Signals Authority authentication is disabled; relying on header-based development fallback.");
}
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").AllowAnonymous();
app.MapGet("/readyz", (SignalsStartupState state, SignalsSealedModeMonitor sealedModeMonitor) =>
{
if (!sealedModeMonitor.IsCompliant(out var reason))
{
return Results.Json(
new { status = "sealed-mode-blocked", reason },
statusCode: StatusCodes.Status503ServiceUnavailable);
}
return state.IsReady
? Results.Ok(new { status = "ready" })
: Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}).AllowAnonymous();
var signalsGroup = app.MapGroup("/signals");
signalsGroup.MapGet("/ping", (HttpContext context, SignalsOptions options, SignalsSealedModeMonitor sealedModeMonitor) =>
{
if (!Program.TryAuthorize(context, requiredScope: SignalsPolicies.Read, fallbackAllowed: options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
return Results.NoContent();
}).WithName("SignalsPing");
signalsGroup.MapGet("/status", (HttpContext context, SignalsOptions options, SignalsSealedModeMonitor sealedModeMonitor) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Read, options.Authority.AllowAnonymousFallback, out var failure))
{
return failure ?? Results.Unauthorized();
}
var sealedCompliant = sealedModeMonitor.IsCompliant(out var sealedReason);
return Results.Ok(new
{
service = "signals",
version = typeof(Program).Assembly.GetName().Version?.ToString() ?? "unknown",
sealedMode = new
{
enforced = sealedModeMonitor.EnforcementEnabled,
compliant = sealedCompliant,
reason = sealedCompliant ? null : sealedReason
}
});
}).WithName("SignalsStatus");
signalsGroup.MapPost("/callgraphs", async Task<IResult> (
HttpContext context,
SignalsOptions options,
CallgraphIngestRequest request,
ICallgraphIngestionService ingestionService,
SignalsSealedModeMonitor sealedModeMonitor,
CancellationToken cancellationToken) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Write, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
try
{
var result = await ingestionService.IngestAsync(request, cancellationToken).ConfigureAwait(false);
return Results.Accepted($"/signals/callgraphs/{result.CallgraphId}", result);
}
catch (CallgraphIngestionValidationException ex)
{
return Results.BadRequest(new { error = ex.Message });
}
catch (CallgraphParserNotFoundException ex)
{
return Results.BadRequest(new { error = ex.Message });
}
catch (CallgraphParserValidationException ex)
{
return Results.UnprocessableEntity(new { error = ex.Message });
}
catch (FormatException ex)
{
return Results.BadRequest(new { error = ex.Message });
}
}).WithName("SignalsCallgraphIngest");
signalsGroup.MapGet("/callgraphs/{callgraphId}", async Task<IResult> (
HttpContext context,
SignalsOptions options,
string callgraphId,
ICallgraphRepository callgraphRepository,
SignalsSealedModeMonitor sealedModeMonitor,
CancellationToken cancellationToken) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Read, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
if (string.IsNullOrWhiteSpace(callgraphId))
{
return Results.BadRequest(new { error = "callgraphId is required." });
}
var document = await callgraphRepository.GetByIdAsync(callgraphId.Trim(), cancellationToken).ConfigureAwait(false);
return document is null ? Results.NotFound() : Results.Ok(document);
}).WithName("SignalsCallgraphGet");
signalsGroup.MapGet("/callgraphs/{callgraphId}/manifest", async Task<IResult> (
HttpContext context,
SignalsOptions options,
string callgraphId,
ICallgraphRepository callgraphRepository,
SignalsSealedModeMonitor sealedModeMonitor,
CancellationToken cancellationToken) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Read, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
if (string.IsNullOrWhiteSpace(callgraphId))
{
return Results.BadRequest(new { error = "callgraphId is required." });
}
var document = await callgraphRepository.GetByIdAsync(callgraphId.Trim(), cancellationToken).ConfigureAwait(false);
if (document is null || string.IsNullOrWhiteSpace(document.Artifact.ManifestPath))
{
return Results.NotFound();
}
var manifestPath = Path.Combine(options.Storage.RootPath, document.Artifact.ManifestPath);
if (!File.Exists(manifestPath))
{
return Results.NotFound(new { error = "manifest not found" });
}
var bytes = await File.ReadAllBytesAsync(manifestPath, cancellationToken).ConfigureAwait(false);
return Results.File(bytes, "application/json");
}).WithName("SignalsCallgraphManifestGet");
signalsGroup.MapPost("/runtime-facts", async Task<IResult> (
HttpContext context,
SignalsOptions options,
RuntimeFactsIngestRequest request,
IRuntimeFactsIngestionService ingestionService,
SignalsSealedModeMonitor sealedModeMonitor,
CancellationToken cancellationToken) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Write, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
try
{
var response = await ingestionService.IngestAsync(request, cancellationToken).ConfigureAwait(false);
return Results.Accepted($"/signals/runtime-facts/{response.SubjectKey}", response);
}
catch (RuntimeFactsValidationException ex)
{
return Results.BadRequest(new { error = ex.Message });
}
}).WithName("SignalsRuntimeIngest");
signalsGroup.MapPost("/runtime-facts/synthetic", async Task<IResult> (
HttpContext context,
SignalsOptions options,
SyntheticRuntimeProbeRequest request,
ICallgraphRepository callgraphRepository,
IRuntimeFactsIngestionService ingestionService,
SyntheticRuntimeProbeBuilder probeBuilder,
SignalsSealedModeMonitor sealedModeMonitor,
CancellationToken cancellationToken) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Write, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
if (string.IsNullOrWhiteSpace(request.CallgraphId))
{
return Results.BadRequest(new { error = "callgraphId is required." });
}
var callgraph = await callgraphRepository.GetByIdAsync(request.CallgraphId.Trim(), cancellationToken).ConfigureAwait(false);
if (callgraph is null)
{
return Results.NotFound(new { error = "callgraph not found." });
}
var subject = request.Subject ?? new ReachabilitySubject { ScanId = $"synthetic-{callgraph.Id}" };
var events = probeBuilder.BuildEvents(callgraph, request.EventCount);
var metadata = request.Metadata is null
? new Dictionary<string, string?>(StringComparer.Ordinal)
: new Dictionary<string, string?>(request.Metadata, StringComparer.Ordinal);
metadata.TryAdd("source", "synthetic-probe");
var ingestRequest = new RuntimeFactsIngestRequest
{
CallgraphId = callgraph.Id,
Subject = subject,
Events = events,
Metadata = metadata
};
var response = await ingestionService.IngestAsync(ingestRequest, cancellationToken).ConfigureAwait(false);
return Results.Accepted($"/signals/runtime-facts/{response.SubjectKey}", response);
}).WithName("SignalsRuntimeIngestSynthetic");
signalsGroup.MapPost("/reachability/union", async Task<IResult> (
HttpContext context,
SignalsOptions options,
[FromHeader(Name = "X-Analysis-Id")] string? analysisId,
IReachabilityUnionIngestionService ingestionService,
SignalsSealedModeMonitor sealedModeMonitor,
CancellationToken cancellationToken) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Write, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
var id = string.IsNullOrWhiteSpace(analysisId) ? Guid.NewGuid().ToString("N") : analysisId.Trim();
if (!string.Equals(context.Request.ContentType, "application/zip", StringComparison.OrdinalIgnoreCase))
{
return Results.BadRequest(new { error = "Content-Type must be application/zip" });
}
try
{
var response = await ingestionService.IngestAsync(id, context.Request.Body, cancellationToken).ConfigureAwait(false);
return Results.Accepted($"/signals/reachability/union/{response.AnalysisId}/meta", response);
}
catch (Exception ex)
{
return Results.BadRequest(new { error = ex.Message });
}
}).WithName("SignalsReachabilityUnionIngest");
signalsGroup.MapGet("/reachability/union/{analysisId}/meta", async Task<IResult> (
HttpContext context,
SignalsOptions options,
string analysisId,
SignalsSealedModeMonitor sealedModeMonitor,
CancellationToken cancellationToken) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Read, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
if (string.IsNullOrWhiteSpace(analysisId))
{
return Results.BadRequest(new { error = "analysisId is required." });
}
var path = Path.Combine(options.Storage.RootPath, "reachability_graphs", analysisId.Trim(), "meta.json");
if (!File.Exists(path))
{
return Results.NotFound();
}
var bytes = await File.ReadAllBytesAsync(path, cancellationToken).ConfigureAwait(false);
return Results.File(bytes, "application/json");
}).WithName("SignalsReachabilityUnionMeta");
signalsGroup.MapGet("/reachability/union/{analysisId}/files/{fileName}", async Task<IResult> (
HttpContext context,
SignalsOptions options,
string analysisId,
string fileName,
SignalsSealedModeMonitor sealedModeMonitor,
CancellationToken cancellationToken) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Read, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
if (string.IsNullOrWhiteSpace(analysisId) || string.IsNullOrWhiteSpace(fileName))
{
return Results.BadRequest(new { error = "analysisId and fileName are required." });
}
var root = Path.Combine(options.Storage.RootPath, "reachability_graphs", analysisId.Trim());
var path = Path.Combine(root, fileName.Replace('/', Path.DirectorySeparatorChar));
if (!File.Exists(path))
{
return Results.NotFound();
}
var contentType = fileName.EndsWith(".json", StringComparison.OrdinalIgnoreCase) ? "application/json" : "application/x-ndjson";
var bytes = await File.ReadAllBytesAsync(path, cancellationToken).ConfigureAwait(false);
return Results.File(bytes, contentType);
}).WithName("SignalsReachabilityUnionFile");
signalsGroup.MapPost("/runtime-facts/ndjson", async Task<IResult> (
HttpContext context,
SignalsOptions options,
[AsParameters] RuntimeFactsStreamMetadata metadata,
IRuntimeFactsIngestionService ingestionService,
SignalsSealedModeMonitor sealedModeMonitor,
CancellationToken cancellationToken) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Write, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
if (metadata is null || string.IsNullOrWhiteSpace(metadata.CallgraphId))
{
return Results.BadRequest(new { error = "callgraphId is required." });
}
var subject = metadata.ToSubject();
var isGzip = string.Equals(context.Request.Headers.ContentEncoding, "gzip", StringComparison.OrdinalIgnoreCase);
var events = await RuntimeFactsNdjsonReader.ReadAsync(context.Request.Body, isGzip, cancellationToken).ConfigureAwait(false);
if (events.Count == 0)
{
return Results.BadRequest(new { error = "runtime fact stream was empty." });
}
var request = new RuntimeFactsIngestRequest
{
Subject = subject,
CallgraphId = metadata.CallgraphId,
Events = events
};
try
{
var response = await ingestionService.IngestAsync(request, cancellationToken).ConfigureAwait(false);
return Results.Accepted($"/signals/runtime-facts/{response.SubjectKey}", response);
}
catch (RuntimeFactsValidationException ex)
{
return Results.BadRequest(new { error = ex.Message });
}
}).WithName("SignalsRuntimeIngestNdjson");
signalsGroup.MapGet("/facts/{subjectKey}", async Task<IResult> (
HttpContext context,
SignalsOptions options,
string subjectKey,
IReachabilityFactRepository factRepository,
SignalsSealedModeMonitor sealedModeMonitor,
CancellationToken cancellationToken) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Read, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
if (string.IsNullOrWhiteSpace(subjectKey))
{
return Results.BadRequest(new { error = "subjectKey is required." });
}
var fact = await factRepository.GetBySubjectAsync(subjectKey.Trim(), cancellationToken).ConfigureAwait(false);
return fact is null ? Results.NotFound() : Results.Ok(fact);
}).WithName("SignalsFactsGet");
signalsGroup.MapPost("/unknowns", async Task<IResult> (
HttpContext context,
SignalsOptions options,
UnknownsIngestRequest request,
IUnknownsIngestionService ingestionService,
SignalsSealedModeMonitor sealedModeMonitor,
CancellationToken cancellationToken) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Write, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
try
{
var response = await ingestionService.IngestAsync(request, cancellationToken).ConfigureAwait(false);
return Results.Accepted($"/signals/unknowns/{response.SubjectKey}", response);
}
catch (UnknownsValidationException ex)
{
return Results.BadRequest(new { error = ex.Message });
}
}).WithName("SignalsUnknownsIngest");
signalsGroup.MapGet("/unknowns/{subjectKey}", async Task<IResult> (
HttpContext context,
SignalsOptions options,
string subjectKey,
IUnknownsRepository repository,
SignalsSealedModeMonitor sealedModeMonitor,
CancellationToken cancellationToken) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Read, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
if (string.IsNullOrWhiteSpace(subjectKey))
{
return Results.BadRequest(new { error = "subjectKey is required." });
}
var items = await repository.GetBySubjectAsync(subjectKey.Trim(), cancellationToken).ConfigureAwait(false);
return items.Count == 0 ? Results.NotFound() : Results.Ok(items);
}).WithName("SignalsUnknownsGet");
signalsGroup.MapGet("/unknowns", async Task<IResult> (
HttpContext context,
SignalsOptions options,
IUnknownsRepository repository,
SignalsSealedModeMonitor sealedModeMonitor,
[FromQuery] string? band,
[FromQuery] int limit = 100,
[FromQuery] int offset = 0,
CancellationToken cancellationToken = default) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Read, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
limit = Math.Clamp(limit, 1, 1000);
offset = Math.Max(0, offset);
UnknownsBand? bandFilter = null;
if (!string.IsNullOrWhiteSpace(band) && Enum.TryParse<UnknownsBand>(band, ignoreCase: true, out var parsedBand))
{
bandFilter = parsedBand;
}
var items = await repository.QueryAsync(bandFilter, limit, offset, cancellationToken).ConfigureAwait(false);
return Results.Ok(new
{
items,
count = items.Count,
limit,
offset,
band = bandFilter?.ToString().ToLowerInvariant()
});
}).WithName("SignalsUnknownsQuery");
signalsGroup.MapGet("/unknowns/{id}/explain", async Task<IResult> (
HttpContext context,
SignalsOptions options,
string id,
IUnknownsRepository repository,
IUnknownsScoringService scoringService,
SignalsSealedModeMonitor sealedModeMonitor,
CancellationToken cancellationToken) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Read, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
if (string.IsNullOrWhiteSpace(id))
{
return Results.BadRequest(new { error = "id is required." });
}
var unknown = await repository.GetByIdAsync(id.Trim(), cancellationToken).ConfigureAwait(false);
if (unknown is null)
{
return Results.NotFound(new { error = $"Unknown with id '{id}' not found." });
}
return Results.Ok(new
{
id = unknown.Id,
subjectKey = unknown.SubjectKey,
band = unknown.Band.ToString().ToLowerInvariant(),
score = unknown.Score,
normalizationTrace = unknown.NormalizationTrace,
flags = unknown.Flags,
nextScheduledRescan = unknown.NextScheduledRescan,
rescanAttempts = unknown.RescanAttempts,
createdAt = unknown.CreatedAt,
updatedAt = unknown.UpdatedAt
});
}).WithName("SignalsUnknownsExplain");
signalsGroup.MapPost("/reachability/recompute", async Task<IResult> (
HttpContext context,
SignalsOptions options,
ReachabilityRecomputeRequest request,
IReachabilityScoringService scoringService,
SignalsSealedModeMonitor sealedModeMonitor,
CancellationToken cancellationToken) =>
{
if (!Program.TryAuthorize(context, SignalsPolicies.Admin, options.Authority.AllowAnonymousFallback, out var authFailure))
{
return authFailure ?? Results.Unauthorized();
}
if (!Program.TryEnsureSealedMode(sealedModeMonitor, out var sealedFailure))
{
return sealedFailure ?? Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
try
{
var fact = await scoringService.RecomputeAsync(request, cancellationToken).ConfigureAwait(false);
return Results.Ok(new
{
fact.Id,
fact.CallgraphId,
subject = fact.Subject,
fact.EntryPoints,
fact.States,
fact.ComputedAt
});
}
catch (ReachabilityScoringValidationException ex)
{
return Results.BadRequest(new { error = ex.Message });
}
catch (ReachabilityCallgraphNotFoundException ex)
{
return Results.NotFound(new { error = ex.Message });
}
}).WithName("SignalsReachabilityRecompute");
app.Run();
public partial class Program
{
internal static bool TryAuthorize(HttpContext httpContext, string requiredScope, bool fallbackAllowed, out IResult? failure)
{
if (httpContext.User?.Identity?.IsAuthenticated == true)
{
if (TokenScopeAuthorizer.HasScope(httpContext.User, requiredScope))
{
failure = null;
return true;
}
failure = Results.StatusCode(StatusCodes.Status403Forbidden);
return false;
}
if (!fallbackAllowed)
{
failure = Results.Unauthorized();
return false;
}
if (!httpContext.Request.Headers.TryGetValue("X-Scopes", out var scopesHeader) ||
string.IsNullOrWhiteSpace(scopesHeader.ToString()))
{
failure = Results.Unauthorized();
return false;
}
var principal = HeaderScopeAuthorizer.CreatePrincipal(scopesHeader.ToString());
if (HeaderScopeAuthorizer.HasScope(principal, requiredScope))
{
failure = null;
return true;
}
failure = Results.StatusCode(StatusCodes.Status403Forbidden);
return false;
}
internal static bool TryEnsureSealedMode(SignalsSealedModeMonitor monitor, out IResult? failure)
{
if (!monitor.EnforcementEnabled)
{
failure = null;
return true;
}
if (monitor.IsCompliant(out var reason))
{
failure = null;
return true;
}
failure = Results.Json(
new { error = "sealed-mode evidence invalid", reason },
statusCode: StatusCodes.Status503ServiceUnavailable);
return false;
}
}