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

This commit is contained in:
StellaOps Bot
2025-12-13 00:20:26 +02:00
parent e1f1bef4c1
commit 564df71bfb
2376 changed files with 334389 additions and 328032 deletions

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -1,5 +1,5 @@
namespace StellaOps.Signals.Models;
namespace StellaOps.Signals.Models;
/// <summary>
/// Response returned after callgraph ingestion.
/// </summary>

View File

@@ -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,

View File

@@ -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";
}

View File

@@ -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.");
}
}
}

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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; }
}

View File

@@ -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)
{
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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
};
}
}

View File

@@ -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

View File

@@ -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";
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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,