using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using StellaOps.Configuration; using StellaOps.Scanner.Core.Contracts; namespace StellaOps.Scanner.Worker.Options; public sealed class ScannerWorkerOptions { public const string SectionName = "Scanner:Worker"; public int MaxConcurrentJobs { get; set; } = 2; public QueueOptions Queue { get; } = new(); public PollingOptions Polling { get; } = new(); public AuthorityOptions Authority { get; } = new(); public TelemetryOptions Telemetry { get; } = new(); public ShutdownOptions Shutdown { get; } = new(); public AnalyzerOptions Analyzers { get; } = new(); public StellaOpsCryptoOptions Crypto { get; } = new(); public SigningOptions Signing { get; } = new(); public DeterminismOptions Determinism { get; } = new(); public sealed class QueueOptions { public int MaxAttempts { get; set; } = 5; public double HeartbeatSafetyFactor { get; set; } = 3.0; public int MaxHeartbeatJitterMilliseconds { get; set; } = 750; public IReadOnlyList HeartbeatRetryDelays => _heartbeatRetryDelays; public TimeSpan MinHeartbeatInterval { get; set; } = TimeSpan.FromSeconds(10); public TimeSpan MaxHeartbeatInterval { get; set; } = TimeSpan.FromSeconds(30); public void SetHeartbeatRetryDelays(IEnumerable delays) { _heartbeatRetryDelays = NormalizeDelays(delays); } internal IReadOnlyList NormalizedHeartbeatRetryDelays => _heartbeatRetryDelays; private static IReadOnlyList NormalizeDelays(IEnumerable delays) { var buffer = new List(); foreach (var delay in delays) { if (delay <= TimeSpan.Zero) { continue; } buffer.Add(delay); } buffer.Sort(); return new ReadOnlyCollection(buffer); } private IReadOnlyList _heartbeatRetryDelays = new ReadOnlyCollection(new TimeSpan[] { TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), }); } public sealed class PollingOptions { public TimeSpan InitialDelay { get; set; } = TimeSpan.FromMilliseconds(200); public TimeSpan MaxDelay { get; set; } = TimeSpan.FromSeconds(5); public double JitterRatio { get; set; } = 0.2; } public sealed class AuthorityOptions { public bool Enabled { get; set; } public string? Issuer { get; set; } public string? ClientId { get; set; } public string? ClientSecret { get; set; } public bool RequireHttpsMetadata { get; set; } = true; public string? MetadataAddress { get; set; } public int BackchannelTimeoutSeconds { get; set; } = 20; public int TokenClockSkewSeconds { get; set; } = 30; public IList Scopes { get; } = new List { "scanner.scan" }; public ResilienceOptions Resilience { get; } = new(); } public sealed class ResilienceOptions { public bool? EnableRetries { get; set; } public IList RetryDelays { get; } = new List { TimeSpan.FromMilliseconds(250), TimeSpan.FromMilliseconds(500), TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), }; public bool? AllowOfflineCacheFallback { get; set; } public TimeSpan? OfflineCacheTolerance { get; set; } } public sealed class TelemetryOptions { public bool EnableLogging { get; set; } = true; public bool EnableTelemetry { get; set; } = true; public bool EnableTracing { get; set; } public bool EnableMetrics { get; set; } = true; public string ServiceName { get; set; } = "stellaops-scanner-worker"; public string? OtlpEndpoint { get; set; } public bool ExportConsole { get; set; } public IDictionary ResourceAttributes { get; } = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); } public sealed class ShutdownOptions { public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30); } public sealed class AnalyzerOptions { public AnalyzerOptions() { PluginDirectories = new List { Path.Combine("plugins", "scanner", "analyzers", "os"), }; LanguagePluginDirectories = new List { Path.Combine("plugins", "scanner", "analyzers", "lang"), }; } public IList PluginDirectories { get; } public IList LanguagePluginDirectories { get; } public string RootFilesystemMetadataKey { get; set; } = ScanMetadataKeys.RootFilesystemPath; public string WorkspaceMetadataKey { get; set; } = ScanMetadataKeys.WorkspacePath; public string EntryTraceConfigMetadataKey { get; set; } = ScanMetadataKeys.ImageConfigPath; public string EntryTraceLayerDirectoriesMetadataKey { get; set; } = ScanMetadataKeys.LayerDirectories; public string EntryTraceLayerArchivesMetadataKey { get; set; } = ScanMetadataKeys.LayerArchives; public string EntryTraceProcRootMetadataKey { get; set; } = ScanMetadataKeys.RuntimeProcRoot; } public sealed class DeterminismOptions { /// /// If true, the worker uses a fixed clock to ensure deterministic timestamps. /// public bool FixedClock { get; set; } /// /// Fixed UTC timestamp to emit when FixedClock is enabled. Defaults to Unix epoch. /// public DateTimeOffset FixedInstantUtc { get; set; } = DateTimeOffset.UnixEpoch; /// /// Optional seed for RNG-based components when determinism is required. /// public int? RngSeed { get; set; } /// /// If true, trims noisy log fields (duration, PIDs) to stable placeholders. /// public bool FilterLogs { get; set; } /// /// Optional hard cap for in-flight jobs to keep replay runs hermetic. /// When set, the worker will clamp MaxConcurrentJobs to this value. /// public int? ConcurrencyLimit { get; set; } } public sealed class SigningOptions { /// /// Enable DSSE signing for surface artifacts (composition recipe, layer fragments). /// When disabled, the worker will fall back to deterministic hash envelopes. /// public bool EnableDsseSigning { get; set; } /// /// Identifier recorded in DSSE signatures. /// public string KeyId { get; set; } = "scanner-hmac"; /// /// Shared secret material for HMAC-based DSSE signatures (base64 or hex). /// Prefer for file-based loading. /// public string? SharedSecret { get; set; } /// /// Optional path to a file containing the shared secret (base64 or hex). /// public string? SharedSecretFile { get; set; } /// /// Allow deterministic fallback when signing is enabled but no secret is provided. /// Keeps offline determinism while avoiding hard failures in sealed-mode runs. /// public bool AllowDeterministicFallback { get; set; } = true; } }