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
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
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
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
This commit is contained in:
@@ -1,29 +1,29 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace StellaOps.Signals.Authentication;
|
||||
|
||||
/// <summary>
|
||||
/// Authentication handler used during development fallback.
|
||||
/// </summary>
|
||||
internal sealed class AnonymousAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
public AnonymousAuthenticationHandler(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder)
|
||||
: base(options, logger, encoder)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
var identity = new ClaimsIdentity();
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
var ticket = new AuthenticationTicket(principal, Scheme.Name);
|
||||
return Task.FromResult(AuthenticateResult.Success(ticket));
|
||||
}
|
||||
}
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace StellaOps.Signals.Authentication;
|
||||
|
||||
/// <summary>
|
||||
/// Authentication handler used during development fallback.
|
||||
/// </summary>
|
||||
internal sealed class AnonymousAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
public AnonymousAuthenticationHandler(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder)
|
||||
: base(options, logger, encoder)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
var identity = new ClaimsIdentity();
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
var ticket = new AuthenticationTicket(principal, Scheme.Name);
|
||||
return Task.FromResult(AuthenticateResult.Success(ticket));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
using System.Security.Claims;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
|
||||
namespace StellaOps.Signals.Authentication;
|
||||
|
||||
/// <summary>
|
||||
/// Header-based scope authorizer for development environments.
|
||||
/// </summary>
|
||||
internal static class HeaderScopeAuthorizer
|
||||
{
|
||||
internal static bool HasScope(ClaimsPrincipal principal, string requiredScope)
|
||||
{
|
||||
if (principal is null || string.IsNullOrWhiteSpace(requiredScope))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var claim in principal.FindAll(StellaOpsClaimTypes.Scope))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(claim.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var scopes = claim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
foreach (var scope in scopes)
|
||||
{
|
||||
if (string.Equals(scope, requiredScope, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var claim in principal.FindAll(StellaOpsClaimTypes.ScopeItem))
|
||||
{
|
||||
if (string.Equals(claim.Value, requiredScope, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static ClaimsPrincipal CreatePrincipal(string scopeBuffer)
|
||||
{
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(StellaOpsClaimTypes.Scope, scopeBuffer)
|
||||
};
|
||||
|
||||
foreach (var value in scopeBuffer.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||
{
|
||||
claims.Add(new Claim(StellaOpsClaimTypes.ScopeItem, value));
|
||||
}
|
||||
|
||||
var identity = new ClaimsIdentity(claims, authenticationType: "Header");
|
||||
return new ClaimsPrincipal(identity);
|
||||
}
|
||||
}
|
||||
using System.Security.Claims;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
|
||||
namespace StellaOps.Signals.Authentication;
|
||||
|
||||
/// <summary>
|
||||
/// Header-based scope authorizer for development environments.
|
||||
/// </summary>
|
||||
internal static class HeaderScopeAuthorizer
|
||||
{
|
||||
internal static bool HasScope(ClaimsPrincipal principal, string requiredScope)
|
||||
{
|
||||
if (principal is null || string.IsNullOrWhiteSpace(requiredScope))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var claim in principal.FindAll(StellaOpsClaimTypes.Scope))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(claim.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var scopes = claim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
foreach (var scope in scopes)
|
||||
{
|
||||
if (string.Equals(scope, requiredScope, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var claim in principal.FindAll(StellaOpsClaimTypes.ScopeItem))
|
||||
{
|
||||
if (string.Equals(claim.Value, requiredScope, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static ClaimsPrincipal CreatePrincipal(string scopeBuffer)
|
||||
{
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(StellaOpsClaimTypes.Scope, scopeBuffer)
|
||||
};
|
||||
|
||||
foreach (var value in scopeBuffer.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||
{
|
||||
claims.Add(new Claim(StellaOpsClaimTypes.ScopeItem, value));
|
||||
}
|
||||
|
||||
var identity = new ClaimsIdentity(claims, authenticationType: "Header");
|
||||
return new ClaimsPrincipal(identity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
using System.Security.Claims;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
|
||||
namespace StellaOps.Signals.Authentication;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for evaluating token scopes.
|
||||
/// </summary>
|
||||
internal static class TokenScopeAuthorizer
|
||||
{
|
||||
internal static bool HasScope(ClaimsPrincipal principal, string requiredScope)
|
||||
{
|
||||
foreach (var claim in principal.FindAll(StellaOpsClaimTypes.ScopeItem))
|
||||
{
|
||||
if (string.Equals(claim.Value, requiredScope, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var claim in principal.FindAll(StellaOpsClaimTypes.Scope))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(claim.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var parts = claim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var normalized = StellaOpsScopes.Normalize(part);
|
||||
if (normalized is not null && string.Equals(normalized, requiredScope, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
using System.Security.Claims;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
|
||||
namespace StellaOps.Signals.Authentication;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for evaluating token scopes.
|
||||
/// </summary>
|
||||
internal static class TokenScopeAuthorizer
|
||||
{
|
||||
internal static bool HasScope(ClaimsPrincipal principal, string requiredScope)
|
||||
{
|
||||
foreach (var claim in principal.FindAll(StellaOpsClaimTypes.ScopeItem))
|
||||
{
|
||||
if (string.Equals(claim.Value, requiredScope, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var claim in principal.FindAll(StellaOpsClaimTypes.Scope))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(claim.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var parts = claim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var normalized = StellaOpsScopes.Normalize(part);
|
||||
if (normalized is not null && string.Equals(normalized, requiredScope, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
namespace StellaOps.Signals.Hosting;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks Signals service readiness state.
|
||||
/// </summary>
|
||||
public sealed class SignalsStartupState
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether the service is ready to accept requests.
|
||||
/// </summary>
|
||||
public bool IsReady { get; set; } = true;
|
||||
}
|
||||
namespace StellaOps.Signals.Hosting;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks Signals service readiness state.
|
||||
/// </summary>
|
||||
public sealed class SignalsStartupState
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether the service is ready to accept requests.
|
||||
/// </summary>
|
||||
public bool IsReady { get; set; } = true;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Signals.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Normalized callgraph edge.
|
||||
/// </summary>
|
||||
|
||||
/// <summary>
|
||||
/// Normalized callgraph edge.
|
||||
/// </summary>
|
||||
public sealed record CallgraphEdge(
|
||||
string SourceId,
|
||||
string TargetId,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace StellaOps.Signals.Models;
|
||||
|
||||
/// <summary>
|
||||
/// API request payload for callgraph ingestion.
|
||||
/// </summary>
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace StellaOps.Signals.Models;
|
||||
|
||||
/// <summary>
|
||||
/// API request payload for callgraph ingestion.
|
||||
/// </summary>
|
||||
public sealed record CallgraphIngestRequest(
|
||||
[property: Required] string Language,
|
||||
[property: Required] string Component,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Response returned after callgraph ingestion.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Signals.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Normalized callgraph node.
|
||||
/// </summary>
|
||||
|
||||
/// <summary>
|
||||
/// Normalized callgraph node.
|
||||
/// </summary>
|
||||
public sealed record CallgraphNode(
|
||||
string Id,
|
||||
string Name,
|
||||
|
||||
@@ -1,153 +1,153 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace StellaOps.Signals.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Artifact storage configuration for Signals callgraph ingestion.
|
||||
/// </summary>
|
||||
public sealed class SignalsArtifactStorageOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Storage driver: "filesystem" (default) or "rustfs".
|
||||
/// </summary>
|
||||
public string Driver { get; set; } = SignalsStorageDrivers.FileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Root directory used to persist raw callgraph artifacts (filesystem driver).
|
||||
/// </summary>
|
||||
public string RootPath { get; set; } = Path.Combine(AppContext.BaseDirectory, "callgraph-artifacts");
|
||||
|
||||
/// <summary>
|
||||
/// Bucket name for CAS storage (RustFS driver).
|
||||
/// Per CAS contract, signals uses "signals-data" bucket.
|
||||
/// </summary>
|
||||
public string BucketName { get; set; } = "signals-data";
|
||||
|
||||
/// <summary>
|
||||
/// Root prefix within the bucket for callgraph artifacts.
|
||||
/// </summary>
|
||||
public string RootPrefix { get; set; } = "callgraphs";
|
||||
|
||||
/// <summary>
|
||||
/// RustFS-specific options.
|
||||
/// </summary>
|
||||
public SignalsRustFsOptions RustFs { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Additional headers to include in storage requests.
|
||||
/// </summary>
|
||||
public IDictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the filesystem driver is configured.
|
||||
/// </summary>
|
||||
public bool IsFileSystemDriver()
|
||||
=> string.Equals(Driver, SignalsStorageDrivers.FileSystem, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the RustFS driver is configured.
|
||||
/// </summary>
|
||||
public bool IsRustFsDriver()
|
||||
=> string.Equals(Driver, SignalsStorageDrivers.RustFs, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Validates the configured values.
|
||||
/// </summary>
|
||||
public void Validate()
|
||||
{
|
||||
if (!IsFileSystemDriver() && !IsRustFsDriver())
|
||||
{
|
||||
throw new InvalidOperationException($"Signals storage driver '{Driver}' is not supported. Use '{SignalsStorageDrivers.FileSystem}' or '{SignalsStorageDrivers.RustFs}'.");
|
||||
}
|
||||
|
||||
if (IsFileSystemDriver() && string.IsNullOrWhiteSpace(RootPath))
|
||||
{
|
||||
throw new InvalidOperationException("Signals artifact storage path must be configured for filesystem driver.");
|
||||
}
|
||||
|
||||
if (IsRustFsDriver())
|
||||
{
|
||||
RustFs ??= new SignalsRustFsOptions();
|
||||
RustFs.Validate();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(BucketName))
|
||||
{
|
||||
throw new InvalidOperationException("Signals storage bucket name must be configured for RustFS driver.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RustFS-specific configuration options.
|
||||
/// </summary>
|
||||
public sealed class SignalsRustFsOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Base URL for the RustFS service (e.g., http://localhost:8180/api/v1).
|
||||
/// </summary>
|
||||
public string BaseUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Allow insecure TLS connections (development only).
|
||||
/// </summary>
|
||||
public bool AllowInsecureTls { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// API key for authentication.
|
||||
/// </summary>
|
||||
public string? ApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Header name for the API key (e.g., "X-API-Key").
|
||||
/// </summary>
|
||||
public string ApiKeyHeader { get; set; } = "X-API-Key";
|
||||
|
||||
/// <summary>
|
||||
/// HTTP request timeout.
|
||||
/// </summary>
|
||||
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>
|
||||
/// Validates the configured values.
|
||||
/// </summary>
|
||||
public void Validate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(BaseUrl))
|
||||
{
|
||||
throw new InvalidOperationException("RustFS baseUrl must be configured.");
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(BaseUrl, UriKind.Absolute, out var uri))
|
||||
{
|
||||
throw new InvalidOperationException("RustFS baseUrl must be an absolute URI.");
|
||||
}
|
||||
|
||||
if (!string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("RustFS baseUrl must use HTTP or HTTPS.");
|
||||
}
|
||||
|
||||
if (Timeout <= TimeSpan.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("RustFS timeout must be greater than zero.");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ApiKeyHeader) && string.IsNullOrWhiteSpace(ApiKey))
|
||||
{
|
||||
throw new InvalidOperationException("RustFS API key header name requires a non-empty API key.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Supported storage driver names.
|
||||
/// </summary>
|
||||
public static class SignalsStorageDrivers
|
||||
{
|
||||
public const string FileSystem = "filesystem";
|
||||
public const string RustFs = "rustfs";
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace StellaOps.Signals.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Artifact storage configuration for Signals callgraph ingestion.
|
||||
/// </summary>
|
||||
public sealed class SignalsArtifactStorageOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Storage driver: "filesystem" (default) or "rustfs".
|
||||
/// </summary>
|
||||
public string Driver { get; set; } = SignalsStorageDrivers.FileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Root directory used to persist raw callgraph artifacts (filesystem driver).
|
||||
/// </summary>
|
||||
public string RootPath { get; set; } = Path.Combine(AppContext.BaseDirectory, "callgraph-artifacts");
|
||||
|
||||
/// <summary>
|
||||
/// Bucket name for CAS storage (RustFS driver).
|
||||
/// Per CAS contract, signals uses "signals-data" bucket.
|
||||
/// </summary>
|
||||
public string BucketName { get; set; } = "signals-data";
|
||||
|
||||
/// <summary>
|
||||
/// Root prefix within the bucket for callgraph artifacts.
|
||||
/// </summary>
|
||||
public string RootPrefix { get; set; } = "callgraphs";
|
||||
|
||||
/// <summary>
|
||||
/// RustFS-specific options.
|
||||
/// </summary>
|
||||
public SignalsRustFsOptions RustFs { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Additional headers to include in storage requests.
|
||||
/// </summary>
|
||||
public IDictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the filesystem driver is configured.
|
||||
/// </summary>
|
||||
public bool IsFileSystemDriver()
|
||||
=> string.Equals(Driver, SignalsStorageDrivers.FileSystem, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the RustFS driver is configured.
|
||||
/// </summary>
|
||||
public bool IsRustFsDriver()
|
||||
=> string.Equals(Driver, SignalsStorageDrivers.RustFs, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Validates the configured values.
|
||||
/// </summary>
|
||||
public void Validate()
|
||||
{
|
||||
if (!IsFileSystemDriver() && !IsRustFsDriver())
|
||||
{
|
||||
throw new InvalidOperationException($"Signals storage driver '{Driver}' is not supported. Use '{SignalsStorageDrivers.FileSystem}' or '{SignalsStorageDrivers.RustFs}'.");
|
||||
}
|
||||
|
||||
if (IsFileSystemDriver() && string.IsNullOrWhiteSpace(RootPath))
|
||||
{
|
||||
throw new InvalidOperationException("Signals artifact storage path must be configured for filesystem driver.");
|
||||
}
|
||||
|
||||
if (IsRustFsDriver())
|
||||
{
|
||||
RustFs ??= new SignalsRustFsOptions();
|
||||
RustFs.Validate();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(BucketName))
|
||||
{
|
||||
throw new InvalidOperationException("Signals storage bucket name must be configured for RustFS driver.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RustFS-specific configuration options.
|
||||
/// </summary>
|
||||
public sealed class SignalsRustFsOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Base URL for the RustFS service (e.g., http://localhost:8180/api/v1).
|
||||
/// </summary>
|
||||
public string BaseUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Allow insecure TLS connections (development only).
|
||||
/// </summary>
|
||||
public bool AllowInsecureTls { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// API key for authentication.
|
||||
/// </summary>
|
||||
public string? ApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Header name for the API key (e.g., "X-API-Key").
|
||||
/// </summary>
|
||||
public string ApiKeyHeader { get; set; } = "X-API-Key";
|
||||
|
||||
/// <summary>
|
||||
/// HTTP request timeout.
|
||||
/// </summary>
|
||||
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>
|
||||
/// Validates the configured values.
|
||||
/// </summary>
|
||||
public void Validate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(BaseUrl))
|
||||
{
|
||||
throw new InvalidOperationException("RustFS baseUrl must be configured.");
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(BaseUrl, UriKind.Absolute, out var uri))
|
||||
{
|
||||
throw new InvalidOperationException("RustFS baseUrl must be an absolute URI.");
|
||||
}
|
||||
|
||||
if (!string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("RustFS baseUrl must use HTTP or HTTPS.");
|
||||
}
|
||||
|
||||
if (Timeout <= TimeSpan.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("RustFS timeout must be greater than zero.");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ApiKeyHeader) && string.IsNullOrWhiteSpace(ApiKey))
|
||||
{
|
||||
throw new InvalidOperationException("RustFS API key header name requires a non-empty API key.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Supported storage driver names.
|
||||
/// </summary>
|
||||
public static class SignalsStorageDrivers
|
||||
{
|
||||
public const string FileSystem = "filesystem";
|
||||
public const string RustFs = "rustfs";
|
||||
}
|
||||
|
||||
@@ -1,101 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Signals.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Authority configuration for the Signals service.
|
||||
/// </summary>
|
||||
public sealed class SignalsAuthorityOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables Authority-backed authentication.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Allows header-based development fallback when Authority is disabled.
|
||||
/// </summary>
|
||||
public bool AllowAnonymousFallback { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Authority issuer URL.
|
||||
/// </summary>
|
||||
public string Issuer { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether HTTPS metadata is required.
|
||||
/// </summary>
|
||||
public bool RequireHttpsMetadata { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Optional metadata address override.
|
||||
/// </summary>
|
||||
public string? MetadataAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Back-channel timeout (seconds).
|
||||
/// </summary>
|
||||
public int BackchannelTimeoutSeconds { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Token clock skew allowance (seconds).
|
||||
/// </summary>
|
||||
public int TokenClockSkewSeconds { get; set; } = 60;
|
||||
|
||||
/// <summary>
|
||||
/// Accepted token audiences.
|
||||
/// </summary>
|
||||
public IList<string> Audiences { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Required scopes.
|
||||
/// </summary>
|
||||
public IList<string> RequiredScopes { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Required tenants.
|
||||
/// </summary>
|
||||
public IList<string> RequiredTenants { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Networks allowed to bypass scope enforcement.
|
||||
/// </summary>
|
||||
public IList<string> BypassNetworks { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Validates the configured options.
|
||||
/// </summary>
|
||||
public void Validate()
|
||||
{
|
||||
if (!Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Issuer))
|
||||
{
|
||||
throw new InvalidOperationException("Signals Authority issuer must be configured when Authority integration is enabled.");
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(Issuer.Trim(), UriKind.Absolute, out var issuerUri))
|
||||
{
|
||||
throw new InvalidOperationException("Signals Authority issuer must be an absolute URI.");
|
||||
}
|
||||
|
||||
if (RequireHttpsMetadata && !issuerUri.IsLoopback && !string.Equals(issuerUri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("Signals Authority issuer must use HTTPS unless running on loopback.");
|
||||
}
|
||||
|
||||
if (BackchannelTimeoutSeconds <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Signals Authority back-channel timeout must be greater than zero seconds.");
|
||||
}
|
||||
|
||||
if (TokenClockSkewSeconds < 0 || TokenClockSkewSeconds > 300)
|
||||
{
|
||||
throw new InvalidOperationException("Signals Authority token clock skew must be between 0 and 300 seconds.");
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Signals.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Authority configuration for the Signals service.
|
||||
/// </summary>
|
||||
public sealed class SignalsAuthorityOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables Authority-backed authentication.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Allows header-based development fallback when Authority is disabled.
|
||||
/// </summary>
|
||||
public bool AllowAnonymousFallback { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Authority issuer URL.
|
||||
/// </summary>
|
||||
public string Issuer { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether HTTPS metadata is required.
|
||||
/// </summary>
|
||||
public bool RequireHttpsMetadata { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Optional metadata address override.
|
||||
/// </summary>
|
||||
public string? MetadataAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Back-channel timeout (seconds).
|
||||
/// </summary>
|
||||
public int BackchannelTimeoutSeconds { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Token clock skew allowance (seconds).
|
||||
/// </summary>
|
||||
public int TokenClockSkewSeconds { get; set; } = 60;
|
||||
|
||||
/// <summary>
|
||||
/// Accepted token audiences.
|
||||
/// </summary>
|
||||
public IList<string> Audiences { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Required scopes.
|
||||
/// </summary>
|
||||
public IList<string> RequiredScopes { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Required tenants.
|
||||
/// </summary>
|
||||
public IList<string> RequiredTenants { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Networks allowed to bypass scope enforcement.
|
||||
/// </summary>
|
||||
public IList<string> BypassNetworks { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Validates the configured options.
|
||||
/// </summary>
|
||||
public void Validate()
|
||||
{
|
||||
if (!Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Issuer))
|
||||
{
|
||||
throw new InvalidOperationException("Signals Authority issuer must be configured when Authority integration is enabled.");
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(Issuer.Trim(), UriKind.Absolute, out var issuerUri))
|
||||
{
|
||||
throw new InvalidOperationException("Signals Authority issuer must be an absolute URI.");
|
||||
}
|
||||
|
||||
if (RequireHttpsMetadata && !issuerUri.IsLoopback && !string.Equals(issuerUri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("Signals Authority issuer must use HTTPS unless running on loopback.");
|
||||
}
|
||||
|
||||
if (BackchannelTimeoutSeconds <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Signals Authority back-channel timeout must be greater than zero seconds.");
|
||||
}
|
||||
|
||||
if (TokenClockSkewSeconds < 0 || TokenClockSkewSeconds > 300)
|
||||
{
|
||||
throw new InvalidOperationException("Signals Authority token clock skew must be between 0 and 300 seconds.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using StellaOps.Signals.Routing;
|
||||
|
||||
namespace StellaOps.Signals.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Applies Signals-specific defaults to <see cref="SignalsAuthorityOptions"/>.
|
||||
/// </summary>
|
||||
internal static class SignalsAuthorityOptionsConfigurator
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures required defaults are populated.
|
||||
/// </summary>
|
||||
public static void ApplyDefaults(SignalsAuthorityOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
if (!options.Audiences.Any())
|
||||
{
|
||||
options.Audiences.Add("api://signals");
|
||||
}
|
||||
|
||||
EnsureScope(options, SignalsPolicies.Read);
|
||||
EnsureScope(options, SignalsPolicies.Write);
|
||||
EnsureScope(options, SignalsPolicies.Admin);
|
||||
}
|
||||
|
||||
private static void EnsureScope(SignalsAuthorityOptions options, string scope)
|
||||
{
|
||||
if (options.RequiredScopes.Any(existing => string.Equals(existing, scope, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
options.RequiredScopes.Add(scope);
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Linq;
|
||||
using StellaOps.Signals.Routing;
|
||||
|
||||
namespace StellaOps.Signals.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Applies Signals-specific defaults to <see cref="SignalsAuthorityOptions"/>.
|
||||
/// </summary>
|
||||
internal static class SignalsAuthorityOptionsConfigurator
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures required defaults are populated.
|
||||
/// </summary>
|
||||
public static void ApplyDefaults(SignalsAuthorityOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
if (!options.Audiences.Any())
|
||||
{
|
||||
options.Audiences.Add("api://signals");
|
||||
}
|
||||
|
||||
EnsureScope(options, SignalsPolicies.Read);
|
||||
EnsureScope(options, SignalsPolicies.Write);
|
||||
EnsureScope(options, SignalsPolicies.Admin);
|
||||
}
|
||||
|
||||
private static void EnsureScope(SignalsAuthorityOptions options, string scope)
|
||||
{
|
||||
if (options.RequiredScopes.Any(existing => string.Equals(existing, scope, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
options.RequiredScopes.Add(scope);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Parsing;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Parsing;
|
||||
|
||||
/// <summary>
|
||||
/// Result produced by a callgraph parser.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace StellaOps.Signals.Parsing;
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when a parser is not registered for the requested language.
|
||||
/// </summary>
|
||||
public sealed class CallgraphParserNotFoundException : Exception
|
||||
{
|
||||
public CallgraphParserNotFoundException(string language)
|
||||
: base($"No callgraph parser registered for language '{language}'.")
|
||||
{
|
||||
Language = language;
|
||||
}
|
||||
|
||||
public string Language { get; }
|
||||
}
|
||||
using System;
|
||||
|
||||
namespace StellaOps.Signals.Parsing;
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when a parser is not registered for the requested language.
|
||||
/// </summary>
|
||||
public sealed class CallgraphParserNotFoundException : Exception
|
||||
{
|
||||
public CallgraphParserNotFoundException(string language)
|
||||
: base($"No callgraph parser registered for language '{language}'.")
|
||||
{
|
||||
Language = language;
|
||||
}
|
||||
|
||||
public string Language { get; }
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace StellaOps.Signals.Parsing;
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when a callgraph artifact is invalid.
|
||||
/// </summary>
|
||||
public sealed class CallgraphParserValidationException : Exception
|
||||
{
|
||||
public CallgraphParserValidationException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
using System;
|
||||
|
||||
namespace StellaOps.Signals.Parsing;
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when a callgraph artifact is invalid.
|
||||
/// </summary>
|
||||
public sealed class CallgraphParserValidationException : Exception
|
||||
{
|
||||
public CallgraphParserValidationException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Signals.Parsing;
|
||||
|
||||
/// <summary>
|
||||
/// Parses raw callgraph artifacts into normalized structures.
|
||||
/// </summary>
|
||||
public interface ICallgraphParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Language identifier handled by the parser (e.g., java, nodejs).
|
||||
/// </summary>
|
||||
string Language { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Parses the supplied artifact stream.
|
||||
/// </summary>
|
||||
Task<CallgraphParseResult> ParseAsync(Stream artifactStream, CancellationToken cancellationToken);
|
||||
}
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Signals.Parsing;
|
||||
|
||||
/// <summary>
|
||||
/// Parses raw callgraph artifacts into normalized structures.
|
||||
/// </summary>
|
||||
public interface ICallgraphParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Language identifier handled by the parser (e.g., java, nodejs).
|
||||
/// </summary>
|
||||
string Language { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Parses the supplied artifact stream.
|
||||
/// </summary>
|
||||
Task<CallgraphParseResult> ParseAsync(Stream artifactStream, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Signals.Parsing;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves callgraph parsers for specific languages.
|
||||
/// </summary>
|
||||
public interface ICallgraphParserResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves a parser for the supplied language.
|
||||
/// </summary>
|
||||
ICallgraphParser Resolve(string language);
|
||||
}
|
||||
|
||||
internal sealed class CallgraphParserResolver : ICallgraphParserResolver
|
||||
{
|
||||
private readonly IReadOnlyDictionary<string, ICallgraphParser> parsersByLanguage;
|
||||
|
||||
public CallgraphParserResolver(IEnumerable<ICallgraphParser> parsers)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(parsers);
|
||||
|
||||
var map = new Dictionary<string, ICallgraphParser>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var parser in parsers)
|
||||
{
|
||||
map[parser.Language] = parser;
|
||||
}
|
||||
|
||||
parsersByLanguage = map;
|
||||
}
|
||||
|
||||
public ICallgraphParser Resolve(string language)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(language);
|
||||
|
||||
if (parsersByLanguage.TryGetValue(language, out var parser))
|
||||
{
|
||||
return parser;
|
||||
}
|
||||
|
||||
throw new CallgraphParserNotFoundException(language);
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Signals.Parsing;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves callgraph parsers for specific languages.
|
||||
/// </summary>
|
||||
public interface ICallgraphParserResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves a parser for the supplied language.
|
||||
/// </summary>
|
||||
ICallgraphParser Resolve(string language);
|
||||
}
|
||||
|
||||
internal sealed class CallgraphParserResolver : ICallgraphParserResolver
|
||||
{
|
||||
private readonly IReadOnlyDictionary<string, ICallgraphParser> parsersByLanguage;
|
||||
|
||||
public CallgraphParserResolver(IEnumerable<ICallgraphParser> parsers)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(parsers);
|
||||
|
||||
var map = new Dictionary<string, ICallgraphParser>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var parser in parsers)
|
||||
{
|
||||
map[parser.Language] = parser;
|
||||
}
|
||||
|
||||
parsersByLanguage = map;
|
||||
}
|
||||
|
||||
public ICallgraphParser Resolve(string language)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(language);
|
||||
|
||||
if (parsersByLanguage.TryGetValue(language, out var parser))
|
||||
{
|
||||
return parser;
|
||||
}
|
||||
|
||||
throw new CallgraphParserNotFoundException(language);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Parsing;
|
||||
|
||||
/// <summary>
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Parsing;
|
||||
|
||||
/// <summary>
|
||||
/// Simple JSON-based callgraph parser used for initial language coverage.
|
||||
/// </summary>
|
||||
public sealed class SimpleJsonCallgraphParser : ICallgraphParser
|
||||
{
|
||||
private readonly JsonSerializerOptions serializerOptions;
|
||||
|
||||
public SimpleJsonCallgraphParser(string language)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(language);
|
||||
Language = language;
|
||||
serializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
}
|
||||
|
||||
public string Language { get; }
|
||||
|
||||
{
|
||||
private readonly JsonSerializerOptions serializerOptions;
|
||||
|
||||
public SimpleJsonCallgraphParser(string language)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(language);
|
||||
Language = language;
|
||||
serializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
}
|
||||
|
||||
public string Language { get; }
|
||||
|
||||
public async Task<CallgraphParseResult> ParseAsync(Stream artifactStream, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(artifactStream);
|
||||
@@ -321,5 +321,5 @@ public sealed class SimpleJsonCallgraphParser : ICallgraphParser
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Persists normalized callgraphs.
|
||||
/// </summary>
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Persists normalized callgraphs.
|
||||
/// </summary>
|
||||
public interface ICallgraphRepository
|
||||
{
|
||||
Task<CallgraphDocument> UpsertAsync(CallgraphDocument document, CancellationToken cancellationToken);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,22 @@
|
||||
namespace StellaOps.Signals.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// Signals service authorization policy names and scope constants.
|
||||
/// </summary>
|
||||
public static class SignalsPolicies
|
||||
{
|
||||
/// <summary>
|
||||
/// Scope required for read operations.
|
||||
/// </summary>
|
||||
public const string Read = "signals:read";
|
||||
|
||||
/// <summary>
|
||||
/// Scope required for write operations.
|
||||
/// </summary>
|
||||
public const string Write = "signals:write";
|
||||
|
||||
/// <summary>
|
||||
/// Scope required for administrative operations.
|
||||
/// </summary>
|
||||
public const string Admin = "signals:admin";
|
||||
}
|
||||
namespace StellaOps.Signals.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// Signals service authorization policy names and scope constants.
|
||||
/// </summary>
|
||||
public static class SignalsPolicies
|
||||
{
|
||||
/// <summary>
|
||||
/// Scope required for read operations.
|
||||
/// </summary>
|
||||
public const string Read = "signals:read";
|
||||
|
||||
/// <summary>
|
||||
/// Scope required for write operations.
|
||||
/// </summary>
|
||||
public const string Write = "signals:write";
|
||||
|
||||
/// <summary>
|
||||
/// Scope required for administrative operations.
|
||||
/// </summary>
|
||||
public const string Admin = "signals:admin";
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Handles ingestion of callgraph artifacts.
|
||||
/// </summary>
|
||||
public interface ICallgraphIngestionService
|
||||
{
|
||||
/// <summary>
|
||||
/// Ingests the supplied callgraph request.
|
||||
/// </summary>
|
||||
Task<CallgraphIngestResponse> IngestAsync(CallgraphIngestRequest request, CancellationToken cancellationToken);
|
||||
}
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Handles ingestion of callgraph artifacts.
|
||||
/// </summary>
|
||||
public interface ICallgraphIngestionService
|
||||
{
|
||||
/// <summary>
|
||||
/// Ingests the supplied callgraph request.
|
||||
/// </summary>
|
||||
Task<CallgraphIngestResponse> IngestAsync(CallgraphIngestRequest request, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Signals.Storage.Models;
|
||||
|
||||
namespace StellaOps.Signals.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// Persists and retrieves raw callgraph artifacts from content-addressable storage.
|
||||
/// </summary>
|
||||
public interface ICallgraphArtifactStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores a callgraph artifact.
|
||||
/// </summary>
|
||||
/// <param name="request">Metadata about the artifact to store.</param>
|
||||
/// <param name="content">The artifact content stream.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Information about the stored artifact.</returns>
|
||||
Task<StoredCallgraphArtifact> SaveAsync(CallgraphArtifactSaveRequest request, Stream content, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a callgraph artifact by its hash.
|
||||
/// </summary>
|
||||
/// <param name="hash">The SHA-256 hash of the artifact.</param>
|
||||
/// <param name="fileName">Optional file name (defaults to callgraph.json).</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The artifact content stream, or null if not found.</returns>
|
||||
Task<Stream?> GetAsync(string hash, string? fileName = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a callgraph manifest by artifact hash.
|
||||
/// </summary>
|
||||
/// <param name="hash">The SHA-256 hash of the artifact.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The manifest content stream, or null if not found.</returns>
|
||||
Task<Stream?> GetManifestAsync(string hash, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an artifact exists.
|
||||
/// </summary>
|
||||
/// <param name="hash">The SHA-256 hash of the artifact.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the artifact exists.</returns>
|
||||
Task<bool> ExistsAsync(string hash, CancellationToken cancellationToken = default);
|
||||
}
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Signals.Storage.Models;
|
||||
|
||||
namespace StellaOps.Signals.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// Persists and retrieves raw callgraph artifacts from content-addressable storage.
|
||||
/// </summary>
|
||||
public interface ICallgraphArtifactStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores a callgraph artifact.
|
||||
/// </summary>
|
||||
/// <param name="request">Metadata about the artifact to store.</param>
|
||||
/// <param name="content">The artifact content stream.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Information about the stored artifact.</returns>
|
||||
Task<StoredCallgraphArtifact> SaveAsync(CallgraphArtifactSaveRequest request, Stream content, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a callgraph artifact by its hash.
|
||||
/// </summary>
|
||||
/// <param name="hash">The SHA-256 hash of the artifact.</param>
|
||||
/// <param name="fileName">Optional file name (defaults to callgraph.json).</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The artifact content stream, or null if not found.</returns>
|
||||
Task<Stream?> GetAsync(string hash, string? fileName = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a callgraph manifest by artifact hash.
|
||||
/// </summary>
|
||||
/// <param name="hash">The SHA-256 hash of the artifact.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The manifest content stream, or null if not found.</returns>
|
||||
Task<Stream?> GetManifestAsync(string hash, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an artifact exists.
|
||||
/// </summary>
|
||||
/// <param name="hash">The SHA-256 hash of the artifact.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the artifact exists.</returns>
|
||||
Task<bool> ExistsAsync(string hash, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System.IO;
|
||||
|
||||
namespace StellaOps.Signals.Storage.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Context required to persist a callgraph artifact.
|
||||
/// </summary>
|
||||
|
||||
/// <summary>
|
||||
/// Context required to persist a callgraph artifact.
|
||||
/// </summary>
|
||||
public sealed record CallgraphArtifactSaveRequest(
|
||||
string Language,
|
||||
string Component,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
namespace StellaOps.Signals.Storage.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Result returned after storing an artifact.
|
||||
/// </summary>
|
||||
namespace StellaOps.Signals.Storage.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Result returned after storing an artifact.
|
||||
/// </summary>
|
||||
public sealed record StoredCallgraphArtifact(
|
||||
string Path,
|
||||
long Length,
|
||||
|
||||
Reference in New Issue
Block a user