save progress

This commit is contained in:
StellaOps Bot
2026-01-04 19:08:47 +02:00
parent f7d27c6fda
commit 75611a505f
97 changed files with 4531 additions and 293 deletions

View File

@@ -7,6 +7,7 @@
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using StellaOps.Determinism.Abstractions;
using StellaOps.Scanner.Storage.Models;
using StellaOps.Scanner.Storage.Repositories;
@@ -20,6 +21,8 @@ public sealed class ScanMetricsCollector : IDisposable
{
private readonly IScanMetricsRepository _repository;
private readonly ILogger<ScanMetricsCollector> _logger;
private readonly TimeProvider _timeProvider;
private readonly IGuidProvider _guidProvider;
private readonly Guid _scanId;
private readonly Guid _tenantId;
@@ -58,7 +61,9 @@ public sealed class ScanMetricsCollector : IDisposable
Guid tenantId,
string artifactDigest,
string artifactType,
string scannerVersion)
string scannerVersion,
TimeProvider? timeProvider = null,
IGuidProvider? guidProvider = null)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -67,7 +72,9 @@ public sealed class ScanMetricsCollector : IDisposable
_artifactDigest = artifactDigest ?? throw new ArgumentNullException(nameof(artifactDigest));
_artifactType = artifactType ?? throw new ArgumentNullException(nameof(artifactType));
_scannerVersion = scannerVersion ?? throw new ArgumentNullException(nameof(scannerVersion));
_metricsId = Guid.NewGuid();
_timeProvider = timeProvider ?? TimeProvider.System;
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
_metricsId = _guidProvider.NewGuid();
}
/// <summary>
@@ -80,7 +87,7 @@ public sealed class ScanMetricsCollector : IDisposable
/// </summary>
public void Start()
{
_startedAt = DateTimeOffset.UtcNow;
_startedAt = _timeProvider.GetUtcNow();
_totalStopwatch.Start();
_logger.LogDebug("Started metrics collection for scan {ScanId}", _scanId);
}
@@ -98,7 +105,7 @@ public sealed class ScanMetricsCollector : IDisposable
return NoOpDisposable.Instance;
}
var tracker = new PhaseTracker(this, phaseName, DateTimeOffset.UtcNow);
var tracker = new PhaseTracker(this, phaseName, _timeProvider.GetUtcNow());
_phases[phaseName] = tracker;
_logger.LogDebug("Started phase {PhaseName} for scan {ScanId}", phaseName, _scanId);
return tracker;
@@ -138,7 +145,7 @@ public sealed class ScanMetricsCollector : IDisposable
_phases.Remove(phaseName);
var finishedAt = DateTimeOffset.UtcNow;
var finishedAt = _timeProvider.GetUtcNow();
var phase = new ExecutionPhase
{
MetricsId = _metricsId,
@@ -214,7 +221,7 @@ public sealed class ScanMetricsCollector : IDisposable
public async Task CompleteAsync(CancellationToken cancellationToken = default)
{
_totalStopwatch.Stop();
var finishedAt = DateTimeOffset.UtcNow;
var finishedAt = _timeProvider.GetUtcNow();
// Calculate phase timings
var phases = BuildPhaseTimings();

View File

@@ -3,6 +3,7 @@
using System.Collections.Immutable;
using Microsoft.Extensions.Logging;
using StellaOps.Determinism;
using StellaOps.Scanner.Explainability.Assumptions;
namespace StellaOps.Scanner.Explainability.Falsifiability;
@@ -60,10 +61,17 @@ public interface IFalsifiabilityGenerator
public sealed class FalsifiabilityGenerator : IFalsifiabilityGenerator
{
private readonly ILogger<FalsifiabilityGenerator> _logger;
private readonly TimeProvider _timeProvider;
private readonly IGuidProvider _guidProvider;
public FalsifiabilityGenerator(ILogger<FalsifiabilityGenerator> logger)
public FalsifiabilityGenerator(
ILogger<FalsifiabilityGenerator> logger,
TimeProvider? timeProvider = null,
IGuidProvider? guidProvider = null)
{
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
}
/// <inheritdoc />
@@ -164,12 +172,12 @@ public sealed class FalsifiabilityGenerator : IFalsifiabilityGenerator
return new FalsifiabilityCriteria
{
Id = Guid.NewGuid().ToString("N"),
Id = _guidProvider.NewGuid().ToString("N"),
FindingId = input.FindingId,
Criteria = [.. criteria],
Status = status,
Summary = summary,
GeneratedAt = DateTimeOffset.UtcNow
GeneratedAt = _timeProvider.GetUtcNow()
};
}

View File

@@ -2,6 +2,7 @@
// Copyright (c) StellaOps
using System.Collections.Immutable;
using StellaOps.Determinism;
using StellaOps.Scanner.Explainability.Assumptions;
using StellaOps.Scanner.Explainability.Confidence;
using StellaOps.Scanner.Explainability.Falsifiability;
@@ -118,10 +119,17 @@ public sealed class RiskReportGenerator : IRiskReportGenerator
private const string EngineVersionValue = "1.0.0";
private readonly IEvidenceDensityScorer _scorer;
private readonly TimeProvider _timeProvider;
private readonly IGuidProvider _guidProvider;
public RiskReportGenerator(IEvidenceDensityScorer scorer)
public RiskReportGenerator(
IEvidenceDensityScorer scorer,
TimeProvider? timeProvider = null,
IGuidProvider? guidProvider = null)
{
_scorer = scorer;
_timeProvider = timeProvider ?? TimeProvider.System;
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
}
/// <inheritdoc />
@@ -140,7 +148,7 @@ public sealed class RiskReportGenerator : IRiskReportGenerator
return new RiskReport
{
Id = Guid.NewGuid().ToString("N"),
Id = _guidProvider.NewGuid().ToString("N"),
FindingId = input.FindingId,
VulnerabilityId = input.VulnerabilityId,
PackageName = input.PackageName,
@@ -151,7 +159,7 @@ public sealed class RiskReportGenerator : IRiskReportGenerator
Explanation = explanation,
DetailedNarrative = narrative,
RecommendedActions = [.. actions],
GeneratedAt = DateTimeOffset.UtcNow,
GeneratedAt = _timeProvider.GetUtcNow(),
EngineVersion = EngineVersionValue
};
}

View File

@@ -12,4 +12,8 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../../__Libraries/StellaOps.Determinism.Abstractions/StellaOps.Determinism.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -16,6 +16,7 @@ public sealed class DockerConnectionTester : ISourceTypeConnectionTester
private readonly IHttpClientFactory _httpClientFactory;
private readonly ICredentialResolver _credentialResolver;
private readonly ILogger<DockerConnectionTester> _logger;
private readonly TimeProvider _timeProvider;
private static readonly JsonSerializerOptions JsonOptions = new()
{
@@ -28,11 +29,13 @@ public sealed class DockerConnectionTester : ISourceTypeConnectionTester
public DockerConnectionTester(
IHttpClientFactory httpClientFactory,
ICredentialResolver credentialResolver,
ILogger<DockerConnectionTester> logger)
ILogger<DockerConnectionTester> logger,
TimeProvider? timeProvider = null)
{
_httpClientFactory = httpClientFactory;
_credentialResolver = credentialResolver;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<ConnectionTestResult> TestAsync(
@@ -47,7 +50,7 @@ public sealed class DockerConnectionTester : ISourceTypeConnectionTester
{
Success = false,
Message = "Invalid configuration format",
TestedAt = DateTimeOffset.UtcNow
TestedAt = _timeProvider.GetUtcNow()
};
}
@@ -100,7 +103,7 @@ public sealed class DockerConnectionTester : ISourceTypeConnectionTester
{
Success = false,
Message = $"Registry accessible but image test failed: {imageTestResult.Message}",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
};
}
@@ -112,7 +115,7 @@ public sealed class DockerConnectionTester : ISourceTypeConnectionTester
{
Success = true,
Message = "Successfully connected to Docker registry",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
};
}
@@ -125,21 +128,21 @@ public sealed class DockerConnectionTester : ISourceTypeConnectionTester
{
Success = false,
Message = "Authentication required - configure credentials",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
},
HttpStatusCode.Forbidden => new ConnectionTestResult
{
Success = false,
Message = "Access denied - check permissions",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
},
_ => new ConnectionTestResult
{
Success = false,
Message = $"Registry returned {response.StatusCode}",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
}
};
@@ -151,7 +154,7 @@ public sealed class DockerConnectionTester : ISourceTypeConnectionTester
{
Success = false,
Message = $"Connection failed: {ex.Message}",
TestedAt = DateTimeOffset.UtcNow
TestedAt = _timeProvider.GetUtcNow()
};
}
catch (TaskCanceledException) when (!ct.IsCancellationRequested)
@@ -160,7 +163,7 @@ public sealed class DockerConnectionTester : ISourceTypeConnectionTester
{
Success = false,
Message = "Connection timed out",
TestedAt = DateTimeOffset.UtcNow
TestedAt = _timeProvider.GetUtcNow()
};
}
}

View File

@@ -16,6 +16,7 @@ public sealed class GitConnectionTester : ISourceTypeConnectionTester
private readonly IHttpClientFactory _httpClientFactory;
private readonly ICredentialResolver _credentialResolver;
private readonly ILogger<GitConnectionTester> _logger;
private readonly TimeProvider _timeProvider;
private static readonly JsonSerializerOptions JsonOptions = new()
{
@@ -28,11 +29,13 @@ public sealed class GitConnectionTester : ISourceTypeConnectionTester
public GitConnectionTester(
IHttpClientFactory httpClientFactory,
ICredentialResolver credentialResolver,
ILogger<GitConnectionTester> logger)
ILogger<GitConnectionTester> logger,
TimeProvider? timeProvider = null)
{
_httpClientFactory = httpClientFactory;
_credentialResolver = credentialResolver;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<ConnectionTestResult> TestAsync(
@@ -47,7 +50,7 @@ public sealed class GitConnectionTester : ISourceTypeConnectionTester
{
Success = false,
Message = "Invalid configuration format",
TestedAt = DateTimeOffset.UtcNow
TestedAt = _timeProvider.GetUtcNow()
};
}
@@ -126,7 +129,7 @@ public sealed class GitConnectionTester : ISourceTypeConnectionTester
{
Success = true,
Message = "Successfully connected to Git repository",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
};
}
@@ -139,28 +142,28 @@ public sealed class GitConnectionTester : ISourceTypeConnectionTester
{
Success = false,
Message = "Authentication required - configure credentials",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
},
HttpStatusCode.Forbidden => new ConnectionTestResult
{
Success = false,
Message = "Access denied - check token permissions",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
},
HttpStatusCode.NotFound => new ConnectionTestResult
{
Success = false,
Message = "Repository not found - check URL and access",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
},
_ => new ConnectionTestResult
{
Success = false,
Message = $"Server returned {response.StatusCode}",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
}
};
@@ -172,7 +175,7 @@ public sealed class GitConnectionTester : ISourceTypeConnectionTester
{
Success = false,
Message = $"Connection failed: {ex.Message}",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = new Dictionary<string, object>
{
["repositoryUrl"] = config.RepositoryUrl
@@ -185,7 +188,7 @@ public sealed class GitConnectionTester : ISourceTypeConnectionTester
{
Success = false,
Message = "Connection timed out",
TestedAt = DateTimeOffset.UtcNow
TestedAt = _timeProvider.GetUtcNow()
};
}
}
@@ -202,7 +205,7 @@ public sealed class GitConnectionTester : ISourceTypeConnectionTester
{
Success = true,
Message = "SSH configuration accepted - connection will be validated on first scan",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = new Dictionary<string, object>
{
["repositoryUrl"] = config.RepositoryUrl,

View File

@@ -17,6 +17,7 @@ public sealed class ZastavaConnectionTester : ISourceTypeConnectionTester
private readonly IHttpClientFactory _httpClientFactory;
private readonly ICredentialResolver _credentialResolver;
private readonly ILogger<ZastavaConnectionTester> _logger;
private readonly TimeProvider _timeProvider;
private static readonly JsonSerializerOptions JsonOptions = new()
{
@@ -29,11 +30,13 @@ public sealed class ZastavaConnectionTester : ISourceTypeConnectionTester
public ZastavaConnectionTester(
IHttpClientFactory httpClientFactory,
ICredentialResolver credentialResolver,
ILogger<ZastavaConnectionTester> logger)
ILogger<ZastavaConnectionTester> logger,
TimeProvider? timeProvider = null)
{
_httpClientFactory = httpClientFactory;
_credentialResolver = credentialResolver;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<ConnectionTestResult> TestAsync(
@@ -48,7 +51,7 @@ public sealed class ZastavaConnectionTester : ISourceTypeConnectionTester
{
Success = false,
Message = "Invalid configuration format",
TestedAt = DateTimeOffset.UtcNow
TestedAt = _timeProvider.GetUtcNow()
};
}
@@ -90,7 +93,7 @@ public sealed class ZastavaConnectionTester : ISourceTypeConnectionTester
{
Success = true,
Message = "Successfully connected to registry",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
};
}
@@ -104,28 +107,28 @@ public sealed class ZastavaConnectionTester : ISourceTypeConnectionTester
{
Success = false,
Message = "Authentication failed - check credentials",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
},
HttpStatusCode.Forbidden => new ConnectionTestResult
{
Success = false,
Message = "Access denied - insufficient permissions",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
},
HttpStatusCode.NotFound => new ConnectionTestResult
{
Success = false,
Message = "Registry endpoint not found - check URL",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
},
_ => new ConnectionTestResult
{
Success = false,
Message = $"Registry returned {response.StatusCode}",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = details
}
};
@@ -137,7 +140,7 @@ public sealed class ZastavaConnectionTester : ISourceTypeConnectionTester
{
Success = false,
Message = $"Connection failed: {ex.Message}",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = new Dictionary<string, object>
{
["registryUrl"] = config.RegistryUrl,
@@ -151,7 +154,7 @@ public sealed class ZastavaConnectionTester : ISourceTypeConnectionTester
{
Success = false,
Message = "Connection timed out",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = new Dictionary<string, object>
{
["registryUrl"] = config.RegistryUrl,

View File

@@ -24,6 +24,7 @@ public sealed class CliSourceHandler : ISourceTypeHandler
{
private readonly ISourceConfigValidator _configValidator;
private readonly ILogger<CliSourceHandler> _logger;
private readonly TimeProvider _timeProvider;
private static readonly JsonSerializerOptions JsonOptions = new()
{
@@ -38,10 +39,12 @@ public sealed class CliSourceHandler : ISourceTypeHandler
public CliSourceHandler(
ISourceConfigValidator configValidator,
ILogger<CliSourceHandler> logger)
ILogger<CliSourceHandler> logger,
TimeProvider? timeProvider = null)
{
_configValidator = configValidator;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <summary>
@@ -102,7 +105,7 @@ public sealed class CliSourceHandler : ISourceTypeHandler
{
Success = false,
Message = "Invalid configuration",
TestedAt = DateTimeOffset.UtcNow
TestedAt = _timeProvider.GetUtcNow()
});
}
@@ -112,7 +115,7 @@ public sealed class CliSourceHandler : ISourceTypeHandler
{
Success = true,
Message = "CLI source configuration is valid",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = new Dictionary<string, object>
{
["allowedTools"] = config.AllowedTools,
@@ -242,8 +245,8 @@ public sealed class CliSourceHandler : ISourceTypeHandler
Token = token,
TokenHash = Convert.ToHexString(tokenHash).ToLowerInvariant(),
SourceId = source.SourceId,
ExpiresAt = DateTimeOffset.UtcNow.Add(validity),
CreatedAt = DateTimeOffset.UtcNow
ExpiresAt = _timeProvider.GetUtcNow().Add(validity),
CreatedAt = _timeProvider.GetUtcNow()
};
}

View File

@@ -21,6 +21,7 @@ public sealed class DockerSourceHandler : ISourceTypeHandler
private readonly ISourceConfigValidator _configValidator;
private readonly IImageDiscoveryService _discoveryService;
private readonly ILogger<DockerSourceHandler> _logger;
private readonly TimeProvider _timeProvider;
private static readonly JsonSerializerOptions JsonOptions = new()
{
@@ -38,13 +39,15 @@ public sealed class DockerSourceHandler : ISourceTypeHandler
ICredentialResolver credentialResolver,
ISourceConfigValidator configValidator,
IImageDiscoveryService discoveryService,
ILogger<DockerSourceHandler> logger)
ILogger<DockerSourceHandler> logger,
TimeProvider? timeProvider = null)
{
_clientFactory = clientFactory;
_credentialResolver = credentialResolver;
_configValidator = configValidator;
_discoveryService = discoveryService;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<IReadOnlyList<ScanTarget>> DiscoverTargetsAsync(
@@ -136,7 +139,7 @@ public sealed class DockerSourceHandler : ISourceTypeHandler
// Apply age filter if specified
if (imageSpec.MaxAgeHours.HasValue)
{
var cutoff = DateTimeOffset.UtcNow.AddHours(-imageSpec.MaxAgeHours.Value);
var cutoff = _timeProvider.GetUtcNow().AddHours(-imageSpec.MaxAgeHours.Value);
sortedTags = sortedTags
.Where(t => t.LastUpdated == null || t.LastUpdated >= cutoff)
.ToList();
@@ -181,7 +184,7 @@ public sealed class DockerSourceHandler : ISourceTypeHandler
{
Success = false,
Message = "Invalid configuration",
TestedAt = DateTimeOffset.UtcNow
TestedAt = _timeProvider.GetUtcNow()
};
}
@@ -198,7 +201,7 @@ public sealed class DockerSourceHandler : ISourceTypeHandler
{
Success = false,
Message = "Registry ping failed",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = new Dictionary<string, object>
{
["registryUrl"] = config.RegistryUrl
@@ -216,7 +219,7 @@ public sealed class DockerSourceHandler : ISourceTypeHandler
{
Success = true,
Message = "Successfully connected to registry",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = new Dictionary<string, object>
{
["registryUrl"] = config.RegistryUrl,
@@ -230,7 +233,7 @@ public sealed class DockerSourceHandler : ISourceTypeHandler
{
Success = true,
Message = "Successfully connected to registry",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = new Dictionary<string, object>
{
["registryUrl"] = config.RegistryUrl
@@ -244,7 +247,7 @@ public sealed class DockerSourceHandler : ISourceTypeHandler
{
Success = false,
Message = $"Connection failed: {ex.Message}",
TestedAt = DateTimeOffset.UtcNow
TestedAt = _timeProvider.GetUtcNow()
};
}
}

View File

@@ -19,6 +19,7 @@ public sealed class GitSourceHandler : ISourceTypeHandler, IWebhookCapableHandle
private readonly ICredentialResolver _credentialResolver;
private readonly ISourceConfigValidator _configValidator;
private readonly ILogger<GitSourceHandler> _logger;
private readonly TimeProvider _timeProvider;
private static readonly JsonSerializerOptions JsonOptions = new()
{
@@ -35,12 +36,14 @@ public sealed class GitSourceHandler : ISourceTypeHandler, IWebhookCapableHandle
IGitClientFactory gitClientFactory,
ICredentialResolver credentialResolver,
ISourceConfigValidator configValidator,
ILogger<GitSourceHandler> logger)
ILogger<GitSourceHandler> logger,
TimeProvider? timeProvider = null)
{
_gitClientFactory = gitClientFactory;
_credentialResolver = credentialResolver;
_configValidator = configValidator;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<IReadOnlyList<ScanTarget>> DiscoverTargetsAsync(
@@ -160,7 +163,7 @@ public sealed class GitSourceHandler : ISourceTypeHandler, IWebhookCapableHandle
{
Success = false,
Message = "Invalid configuration",
TestedAt = DateTimeOffset.UtcNow
TestedAt = _timeProvider.GetUtcNow()
};
}
@@ -176,7 +179,7 @@ public sealed class GitSourceHandler : ISourceTypeHandler, IWebhookCapableHandle
{
Success = false,
Message = "Repository not found or inaccessible",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = new Dictionary<string, object>
{
["repositoryUrl"] = config.RepositoryUrl,
@@ -189,7 +192,7 @@ public sealed class GitSourceHandler : ISourceTypeHandler, IWebhookCapableHandle
{
Success = true,
Message = "Successfully connected to repository",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = new Dictionary<string, object>
{
["repositoryUrl"] = config.RepositoryUrl,
@@ -206,7 +209,7 @@ public sealed class GitSourceHandler : ISourceTypeHandler, IWebhookCapableHandle
{
Success = false,
Message = $"Connection failed: {ex.Message}",
TestedAt = DateTimeOffset.UtcNow
TestedAt = _timeProvider.GetUtcNow()
};
}
}
@@ -270,7 +273,7 @@ public sealed class GitSourceHandler : ISourceTypeHandler, IWebhookCapableHandle
sender.TryGetProperty("login", out var login)
? login.GetString()
: null,
Timestamp = DateTimeOffset.UtcNow
Timestamp = _timeProvider.GetUtcNow()
};
}
@@ -303,7 +306,7 @@ public sealed class GitSourceHandler : ISourceTypeHandler, IWebhookCapableHandle
? num.GetInt32().ToString()
: ""
},
Timestamp = DateTimeOffset.UtcNow
Timestamp = _timeProvider.GetUtcNow()
};
}
@@ -330,7 +333,7 @@ public sealed class GitSourceHandler : ISourceTypeHandler, IWebhookCapableHandle
Actor = root.TryGetProperty("user_name", out var userName)
? userName.GetString()
: null,
Timestamp = DateTimeOffset.UtcNow
Timestamp = _timeProvider.GetUtcNow()
};
}
@@ -361,7 +364,7 @@ public sealed class GitSourceHandler : ISourceTypeHandler, IWebhookCapableHandle
? mrAction.GetString() ?? ""
: ""
},
Timestamp = DateTimeOffset.UtcNow
Timestamp = _timeProvider.GetUtcNow()
};
}
}
@@ -371,7 +374,7 @@ public sealed class GitSourceHandler : ISourceTypeHandler, IWebhookCapableHandle
{
EventType = "unknown",
Reference = "",
Timestamp = DateTimeOffset.UtcNow
Timestamp = _timeProvider.GetUtcNow()
};
}

View File

@@ -20,6 +20,7 @@ public sealed class ZastavaSourceHandler : ISourceTypeHandler, IWebhookCapableHa
private readonly ICredentialResolver _credentialResolver;
private readonly ISourceConfigValidator _configValidator;
private readonly ILogger<ZastavaSourceHandler> _logger;
private readonly TimeProvider _timeProvider;
private static readonly JsonSerializerOptions JsonOptions = new()
{
@@ -36,12 +37,14 @@ public sealed class ZastavaSourceHandler : ISourceTypeHandler, IWebhookCapableHa
IRegistryClientFactory clientFactory,
ICredentialResolver credentialResolver,
ISourceConfigValidator configValidator,
ILogger<ZastavaSourceHandler> logger)
ILogger<ZastavaSourceHandler> logger,
TimeProvider? timeProvider = null)
{
_clientFactory = clientFactory;
_credentialResolver = credentialResolver;
_configValidator = configValidator;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<IReadOnlyList<ScanTarget>> DiscoverTargetsAsync(
@@ -167,7 +170,7 @@ public sealed class ZastavaSourceHandler : ISourceTypeHandler, IWebhookCapableHa
{
Success = false,
Message = "Invalid configuration",
TestedAt = DateTimeOffset.UtcNow
TestedAt = _timeProvider.GetUtcNow()
};
}
@@ -183,7 +186,7 @@ public sealed class ZastavaSourceHandler : ISourceTypeHandler, IWebhookCapableHa
{
Success = false,
Message = "Registry ping failed",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = new Dictionary<string, object>
{
["registryUrl"] = config.RegistryUrl,
@@ -199,7 +202,7 @@ public sealed class ZastavaSourceHandler : ISourceTypeHandler, IWebhookCapableHa
{
Success = true,
Message = "Successfully connected to registry",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = new Dictionary<string, object>
{
["registryUrl"] = config.RegistryUrl,
@@ -215,7 +218,7 @@ public sealed class ZastavaSourceHandler : ISourceTypeHandler, IWebhookCapableHa
{
Success = false,
Message = $"Connection failed: {ex.Message}",
TestedAt = DateTimeOffset.UtcNow
TestedAt = _timeProvider.GetUtcNow()
};
}
}
@@ -281,7 +284,7 @@ public sealed class ZastavaSourceHandler : ISourceTypeHandler, IWebhookCapableHa
: repository.GetProperty("name").GetString()!,
Tag = pushData.TryGetProperty("tag", out var tag) ? tag.GetString() : "latest",
Actor = pushData.TryGetProperty("pusher", out var pusher) ? pusher.GetString() : null,
Timestamp = DateTimeOffset.UtcNow
Timestamp = _timeProvider.GetUtcNow()
};
}
@@ -309,7 +312,7 @@ public sealed class ZastavaSourceHandler : ISourceTypeHandler, IWebhookCapableHa
? digest.GetString()
: null,
Actor = eventData.TryGetProperty("operator", out var op) ? op.GetString() : null,
Timestamp = DateTimeOffset.UtcNow
Timestamp = _timeProvider.GetUtcNow()
};
}
@@ -338,7 +341,7 @@ public sealed class ZastavaSourceHandler : ISourceTypeHandler, IWebhookCapableHa
actor.TryGetProperty("name", out var actorName)
? actorName.GetString()
: null,
Timestamp = DateTimeOffset.UtcNow
Timestamp = _timeProvider.GetUtcNow()
};
}
@@ -347,7 +350,7 @@ public sealed class ZastavaSourceHandler : ISourceTypeHandler, IWebhookCapableHa
{
EventType = "unknown",
Reference = "",
Timestamp = DateTimeOffset.UtcNow
Timestamp = _timeProvider.GetUtcNow()
};
}

View File

@@ -17,12 +17,15 @@ public sealed class SbomSourceRepository : RepositoryBase<ScannerSourcesDataSour
private const string Schema = "scanner";
private const string Table = "sbom_sources";
private const string FullTable = $"{Schema}.{Table}";
private readonly TimeProvider _timeProvider;
public SbomSourceRepository(
ScannerSourcesDataSource dataSource,
ILogger<SbomSourceRepository> logger)
ILogger<SbomSourceRepository> logger,
TimeProvider? timeProvider = null)
: base(dataSource, logger)
{
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<SbomSource?> GetByIdAsync(string tenantId, Guid sourceId, CancellationToken ct = default)
@@ -317,7 +320,7 @@ public sealed class SbomSourceRepository : RepositoryBase<ScannerSourcesDataSour
public Task<IReadOnlyList<SbomSource>> GetDueForScheduledRunAsync(CancellationToken ct = default)
{
return GetDueScheduledSourcesAsync(DateTimeOffset.UtcNow, 100, ct);
return GetDueScheduledSourcesAsync(_timeProvider.GetUtcNow(), 100, ct);
}
private void ConfigureSourceParams(NpgsqlCommand cmd, SbomSource source)

View File

@@ -16,12 +16,15 @@ public sealed class SbomSourceRunRepository : RepositoryBase<ScannerSourcesDataS
private const string Schema = "scanner";
private const string Table = "sbom_source_runs";
private const string FullTable = $"{Schema}.{Table}";
private readonly TimeProvider _timeProvider;
public SbomSourceRunRepository(
ScannerSourcesDataSource dataSource,
ILogger<SbomSourceRunRepository> logger)
ILogger<SbomSourceRunRepository> logger,
TimeProvider? timeProvider = null)
: base(dataSource, logger)
{
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<SbomSourceRun?> GetByIdAsync(Guid runId, CancellationToken ct = default)
@@ -188,7 +191,7 @@ public sealed class SbomSourceRunRepository : RepositoryBase<ScannerSourcesDataS
sql,
cmd =>
{
AddParameter(cmd, "threshold", DateTimeOffset.UtcNow - olderThan);
AddParameter(cmd, "threshold", _timeProvider.GetUtcNow() - olderThan);
AddParameter(cmd, "limit", limit);
},
MapRun,

View File

@@ -17,19 +17,22 @@ public sealed class SbomSourceService : ISbomSourceService
private readonly ISourceConfigValidator _configValidator;
private readonly ISourceConnectionTester _connectionTester;
private readonly ILogger<SbomSourceService> _logger;
private readonly TimeProvider _timeProvider;
public SbomSourceService(
ISbomSourceRepository sourceRepository,
ISbomSourceRunRepository runRepository,
ISourceConfigValidator configValidator,
ISourceConnectionTester connectionTester,
ILogger<SbomSourceService> logger)
ILogger<SbomSourceService> logger,
TimeProvider? timeProvider = null)
{
_sourceRepository = sourceRepository;
_runRepository = runRepository;
_configValidator = configValidator;
_connectionTester = connectionTester;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<SourceResponse?> GetAsync(string tenantId, Guid sourceId, CancellationToken ct = default)
@@ -215,7 +218,7 @@ public sealed class SbomSourceService : ISbomSourceService
}
// Touch updated fields
SetProperty(source, "UpdatedAt", DateTimeOffset.UtcNow);
SetProperty(source, "UpdatedAt", _timeProvider.GetUtcNow());
SetProperty(source, "UpdatedBy", updatedBy);
await _sourceRepository.UpdateAsync(source, ct);

View File

@@ -12,13 +12,16 @@ public sealed class SourceConnectionTester : ISourceConnectionTester
{
private readonly IEnumerable<ISourceTypeConnectionTester> _testers;
private readonly ILogger<SourceConnectionTester> _logger;
private readonly TimeProvider _timeProvider;
public SourceConnectionTester(
IEnumerable<ISourceTypeConnectionTester> testers,
ILogger<SourceConnectionTester> logger)
ILogger<SourceConnectionTester> logger,
TimeProvider? timeProvider = null)
{
_testers = testers;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public Task<ConnectionTestResult> TestAsync(SbomSource source, CancellationToken ct = default)
@@ -42,7 +45,7 @@ public sealed class SourceConnectionTester : ISourceConnectionTester
{
Success = false,
Message = $"No connection tester available for source type {source.SourceType}",
TestedAt = DateTimeOffset.UtcNow
TestedAt = _timeProvider.GetUtcNow()
};
}
@@ -74,7 +77,7 @@ public sealed class SourceConnectionTester : ISourceConnectionTester
{
Success = false,
Message = $"Connection test error: {ex.Message}",
TestedAt = DateTimeOffset.UtcNow,
TestedAt = _timeProvider.GetUtcNow(),
Details = new Dictionary<string, object>
{
["exceptionType"] = ex.GetType().Name

View File

@@ -22,5 +22,6 @@
<ItemGroup>
<ProjectReference Include="../../../__Libraries/StellaOps.Infrastructure.Postgres/StellaOps.Infrastructure.Postgres.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Determinism.Abstractions/StellaOps.Determinism.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
using StellaOps.Determinism;
using StellaOps.Scanner.Sources.Domain;
using StellaOps.Scanner.Sources.Handlers;
using StellaOps.Scanner.Sources.Persistence;
@@ -15,19 +16,25 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
private readonly IEnumerable<ISourceTypeHandler> _handlers;
private readonly IScanJobQueue _scanJobQueue;
private readonly ILogger<SourceTriggerDispatcher> _logger;
private readonly TimeProvider _timeProvider;
private readonly IGuidProvider _guidProvider;
public SourceTriggerDispatcher(
ISbomSourceRepository sourceRepository,
ISbomSourceRunRepository runRepository,
IEnumerable<ISourceTypeHandler> handlers,
IScanJobQueue scanJobQueue,
ILogger<SourceTriggerDispatcher> logger)
ILogger<SourceTriggerDispatcher> logger,
TimeProvider? timeProvider = null,
IGuidProvider? guidProvider = null)
{
_sourceRepository = sourceRepository;
_runRepository = runRepository;
_handlers = handlers;
_scanJobQueue = scanJobQueue;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
}
public Task<TriggerDispatchResult> DispatchAsync(
@@ -40,7 +47,7 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
{
Trigger = trigger,
TriggerDetails = triggerDetails,
CorrelationId = Guid.NewGuid().ToString("N")
CorrelationId = _guidProvider.NewGuid().ToString("N")
};
return DispatchAsync(sourceId, context, ct);
@@ -128,7 +135,7 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
{
run.Complete();
await _runRepository.UpdateAsync(run, ct);
source.RecordSuccessfulRun(DateTimeOffset.UtcNow);
source.RecordSuccessfulRun(_timeProvider.GetUtcNow());
await _sourceRepository.UpdateAsync(source, ct);
return new TriggerDispatchResult
@@ -170,12 +177,12 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
if (run.ItemsFailed == run.ItemsDiscovered)
{
run.Fail("All targets failed to queue");
source.RecordFailedRun(DateTimeOffset.UtcNow, run.ErrorMessage!);
source.RecordFailedRun(_timeProvider.GetUtcNow(), run.ErrorMessage!);
}
else
{
run.Complete();
source.RecordSuccessfulRun(DateTimeOffset.UtcNow);
source.RecordSuccessfulRun(_timeProvider.GetUtcNow());
}
await _runRepository.UpdateAsync(run, ct);
@@ -195,7 +202,7 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
run.Fail(ex.Message);
await _runRepository.UpdateAsync(run, ct);
source.RecordFailedRun(DateTimeOffset.UtcNow, ex.Message);
source.RecordFailedRun(_timeProvider.GetUtcNow(), ex.Message);
await _sourceRepository.UpdateAsync(source, ct);
return new TriggerDispatchResult
@@ -247,7 +254,7 @@ public sealed class SourceTriggerDispatcher : ISourceTriggerDispatcher
{
Trigger = originalRun.Trigger,
TriggerDetails = $"Retry of run {originalRunId}",
CorrelationId = Guid.NewGuid().ToString("N"),
CorrelationId = _guidProvider.NewGuid().ToString("N"),
Metadata = new() { ["originalRunId"] = originalRunId.ToString() }
};

View File

@@ -61,6 +61,7 @@ public sealed class SlicePullService : IDisposable
private readonly OciRegistryAuthorization _authorization;
private readonly SlicePullOptions _options;
private readonly ILogger<SlicePullService> _logger;
private readonly TimeProvider _timeProvider;
private readonly Dictionary<string, CachedSlice> _cache = new(StringComparer.Ordinal);
private readonly Lock _cacheLock = new();
@@ -70,12 +71,14 @@ public sealed class SlicePullService : IDisposable
HttpClient httpClient,
OciRegistryAuthorization authorization,
SlicePullOptions? options = null,
ILogger<SlicePullService>? logger = null)
ILogger<SlicePullService>? logger = null,
TimeProvider? timeProvider = null)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_authorization = authorization ?? throw new ArgumentNullException(nameof(authorization));
_options = options ?? new SlicePullOptions();
_logger = logger ?? Microsoft.Extensions.Logging.Abstractions.NullLogger<SlicePullService>.Instance;
_timeProvider = timeProvider ?? TimeProvider.System;
_httpClient.Timeout = _options.RequestTimeout;
}
@@ -211,7 +214,7 @@ public sealed class SlicePullService : IDisposable
var dsseLayer = manifest.Layers?.FirstOrDefault(l =>
l.MediaType == OciMediaTypes.DsseEnvelope);
if (dsseLayer != null && _options.VerifySignature)
if (dsseLayer?.Digest != null && _options.VerifySignature)
{
var dsseResult = await FetchAndVerifyDsseAsync(reference, dsseLayer.Digest, sliceBytes, cancellationToken)
.ConfigureAwait(false);
@@ -227,7 +230,7 @@ public sealed class SlicePullService : IDisposable
SliceData = sliceData,
DsseEnvelope = dsseEnvelope,
SignatureVerified = signatureVerified,
ExpiresAt = DateTimeOffset.UtcNow.Add(_options.CacheTtl)
ExpiresAt = _timeProvider.GetUtcNow().Add(_options.CacheTtl)
});
}
@@ -411,7 +414,7 @@ public sealed class SlicePullService : IDisposable
{
if (_cache.TryGetValue(key, out cached))
{
if (cached.ExpiresAt > DateTimeOffset.UtcNow)
if (cached.ExpiresAt > _timeProvider.GetUtcNow())
{
return true;
}

View File

@@ -8,6 +8,7 @@
using System.Text.Json;
using Npgsql;
using NpgsqlTypes;
using StellaOps.Determinism;
using StellaOps.Scanner.Storage.Entities;
namespace StellaOps.Scanner.Storage.Postgres;
@@ -64,10 +65,17 @@ public interface IFuncProofRepository
public sealed class PostgresFuncProofRepository : IFuncProofRepository
{
private readonly NpgsqlDataSource _dataSource;
private readonly TimeProvider _timeProvider;
private readonly IGuidProvider _guidProvider;
public PostgresFuncProofRepository(NpgsqlDataSource dataSource)
public PostgresFuncProofRepository(
NpgsqlDataSource dataSource,
TimeProvider? timeProvider = null,
IGuidProvider? guidProvider = null)
{
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
_timeProvider = timeProvider ?? TimeProvider.System;
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
}
public async Task<Guid> StoreAsync(FuncProofDocumentRow document, CancellationToken ct = default)
@@ -94,7 +102,7 @@ public sealed class PostgresFuncProofRepository : IFuncProofRepository
await using var conn = await _dataSource.OpenConnectionAsync(ct);
await using var cmd = new NpgsqlCommand(sql, conn);
var id = document.Id == Guid.Empty ? Guid.NewGuid() : document.Id;
var id = document.Id == Guid.Empty ? _guidProvider.NewGuid() : document.Id;
cmd.Parameters.AddWithValue("id", id);
cmd.Parameters.AddWithValue("scan_id", document.ScanId);
@@ -118,7 +126,7 @@ public sealed class PostgresFuncProofRepository : IFuncProofRepository
document.RekorEntryId is null ? DBNull.Value : document.RekorEntryId);
cmd.Parameters.AddWithValue("generator_version", document.GeneratorVersion);
cmd.Parameters.AddWithValue("generated_at_utc", document.GeneratedAtUtc);
cmd.Parameters.AddWithValue("created_at_utc", DateTimeOffset.UtcNow);
cmd.Parameters.AddWithValue("created_at_utc", _timeProvider.GetUtcNow());
var result = await cmd.ExecuteScalarAsync(ct);
return result is Guid returnedId ? returnedId : id;

View File

@@ -8,6 +8,7 @@
using Dapper;
using Microsoft.Extensions.Logging;
using Npgsql;
using StellaOps.Determinism;
using StellaOps.Scanner.Storage.Entities;
using StellaOps.Scanner.Storage.Repositories;
@@ -20,14 +21,17 @@ public sealed class PostgresIdempotencyKeyRepository : IIdempotencyKeyRepository
{
private readonly ScannerDataSource _dataSource;
private readonly ILogger<PostgresIdempotencyKeyRepository> _logger;
private readonly IGuidProvider _guidProvider;
private string SchemaName => _dataSource.SchemaName ?? ScannerDataSource.DefaultSchema;
public PostgresIdempotencyKeyRepository(
ScannerDataSource dataSource,
ILogger<PostgresIdempotencyKeyRepository> logger)
ILogger<PostgresIdempotencyKeyRepository> logger,
IGuidProvider? guidProvider = null)
{
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
}
/// <inheritdoc />
@@ -68,7 +72,7 @@ public sealed class PostgresIdempotencyKeyRepository : IIdempotencyKeyRepository
{
if (key.KeyId == Guid.Empty)
{
key.KeyId = Guid.NewGuid();
key.KeyId = _guidProvider.NewGuid();
}
var sql = $"""

View File

@@ -2,6 +2,7 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Npgsql;
using StellaOps.Determinism;
using StellaOps.Infrastructure.Postgres.Repositories;
using StellaOps.Replay.Core;
using StellaOps.Scanner.ProofSpine;
@@ -28,14 +29,17 @@ public sealed class PostgresProofSpineRepository : RepositoryBase<ScannerDataSou
};
private readonly TimeProvider _timeProvider;
private readonly IGuidProvider _guidProvider;
public PostgresProofSpineRepository(
ScannerDataSource dataSource,
ILogger<PostgresProofSpineRepository> logger,
TimeProvider? timeProvider = null)
TimeProvider? timeProvider = null,
IGuidProvider? guidProvider = null)
: base(dataSource, logger)
{
_timeProvider = timeProvider ?? TimeProvider.System;
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
}
public Task<ProofSpineModel?> GetByIdAsync(string spineId, CancellationToken cancellationToken = default)
@@ -249,7 +253,7 @@ public sealed class PostgresProofSpineRepository : RepositoryBase<ScannerDataSou
await using (var command = CreateCommand(insertHistory, connection))
{
command.Transaction = transaction;
AddParameter(command, "id", Guid.NewGuid().ToString("N"));
AddParameter(command, "id", _guidProvider.NewGuid().ToString("N"));
AddParameter(command, "old_spine_id", oldSpineId.Trim());
AddParameter(command, "new_spine_id", newSpineId.Trim());
AddParameter(command, "reason", reason);

View File

@@ -8,6 +8,7 @@
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Npgsql;
using StellaOps.Determinism;
using StellaOps.Scanner.Storage.Models;
namespace StellaOps.Scanner.Storage.Repositories;
@@ -19,13 +20,16 @@ public sealed class PostgresScanMetricsRepository : IScanMetricsRepository
{
private readonly NpgsqlDataSource _dataSource;
private readonly ILogger<PostgresScanMetricsRepository> _logger;
private readonly IGuidProvider _guidProvider;
public PostgresScanMetricsRepository(
NpgsqlDataSource dataSource,
ILogger<PostgresScanMetricsRepository> logger)
ILogger<PostgresScanMetricsRepository> logger,
IGuidProvider? guidProvider = null)
{
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
}
/// <inheritdoc/>
@@ -67,7 +71,7 @@ public sealed class PostgresScanMetricsRepository : IScanMetricsRepository
await using var cmd = _dataSource.CreateCommand(sql);
var metricsId = metrics.MetricsId == Guid.Empty ? Guid.NewGuid() : metrics.MetricsId;
var metricsId = metrics.MetricsId == Guid.Empty ? _guidProvider.NewGuid() : metrics.MetricsId;
cmd.Parameters.AddWithValue("metricsId", metricsId);
cmd.Parameters.AddWithValue("scanId", metrics.ScanId);

View File

@@ -1,6 +1,7 @@
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Npgsql;
using StellaOps.Determinism;
using StellaOps.Infrastructure.Postgres.Repositories;
using StellaOps.Scanner.Storage.Catalog;
using StellaOps.Scanner.Storage.Postgres;
@@ -16,10 +17,15 @@ public sealed class RuntimeEventRepository : RepositoryBase<ScannerDataSource>
private string Table => $"{SchemaName}.runtime_events";
private string SchemaName => DataSource.SchemaName ?? ScannerDataSource.DefaultSchema;
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
private readonly IGuidProvider _guidProvider;
public RuntimeEventRepository(ScannerDataSource dataSource, ILogger<RuntimeEventRepository> logger)
public RuntimeEventRepository(
ScannerDataSource dataSource,
ILogger<RuntimeEventRepository> logger,
IGuidProvider? guidProvider = null)
: base(dataSource, logger)
{
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
}
public async Task<RuntimeEventInsertResult> InsertAsync(
@@ -52,7 +58,7 @@ public sealed class RuntimeEventRepository : RepositoryBase<ScannerDataSource>
foreach (var document in documents)
{
cancellationToken.ThrowIfCancellationRequested();
var id = string.IsNullOrWhiteSpace(document.Id) ? Guid.NewGuid().ToString("N") : document.Id;
var id = string.IsNullOrWhiteSpace(document.Id) ? _guidProvider.NewGuid().ToString("N") : document.Id;
var rows = await ExecuteAsync(
Tenant,

View File

@@ -28,5 +28,6 @@
<ProjectReference Include="..\\StellaOps.Scanner.SmartDiff\\StellaOps.Scanner.SmartDiff.csproj" />
<ProjectReference Include="..\\..\\..\\__Libraries\\StellaOps.Infrastructure.Postgres\\StellaOps.Infrastructure.Postgres.csproj" />
<ProjectReference Include="..\\..\\..\\Router\\__Libraries\\StellaOps.Messaging\\StellaOps.Messaging.csproj" />
<ProjectReference Include="..\\..\\..\\__Libraries\\StellaOps.Determinism.Abstractions\\StellaOps.Determinism.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -21,5 +21,6 @@
<ItemGroup>
<ProjectReference Include="..\StellaOps.Scanner.Core\StellaOps.Scanner.Core.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Determinism.Abstractions/StellaOps.Determinism.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -7,6 +7,7 @@
using Microsoft.Extensions.Logging;
using Npgsql;
using StellaOps.Determinism;
using StellaOps.Scanner.VulnSurfaces.Models;
namespace StellaOps.Scanner.VulnSurfaces.Storage;
@@ -18,15 +19,18 @@ public sealed class PostgresVulnSurfaceRepository : IVulnSurfaceRepository
{
private readonly NpgsqlDataSource _dataSource;
private readonly ILogger<PostgresVulnSurfaceRepository> _logger;
private readonly IGuidProvider _guidProvider;
private readonly int _commandTimeoutSeconds;
public PostgresVulnSurfaceRepository(
NpgsqlDataSource dataSource,
ILogger<PostgresVulnSurfaceRepository> logger,
IGuidProvider? guidProvider = null,
int commandTimeoutSeconds = 30)
{
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
_commandTimeoutSeconds = commandTimeoutSeconds;
}
@@ -45,7 +49,7 @@ public sealed class PostgresVulnSurfaceRepository : IVulnSurfaceRepository
string? attestationDigest,
CancellationToken cancellationToken = default)
{
var id = Guid.NewGuid();
var id = _guidProvider.NewGuid();
const string sql = """
INSERT INTO scanner.vuln_surfaces (
@@ -106,7 +110,7 @@ public sealed class PostgresVulnSurfaceRepository : IVulnSurfaceRepository
string? fixedHash,
CancellationToken cancellationToken = default)
{
var id = Guid.NewGuid();
var id = _guidProvider.NewGuid();
const string sql = """
INSERT INTO scanner.vuln_surface_sinks (
@@ -148,7 +152,7 @@ public sealed class PostgresVulnSurfaceRepository : IVulnSurfaceRepository
double confidence,
CancellationToken cancellationToken = default)
{
var id = Guid.NewGuid();
var id = _guidProvider.NewGuid();
const string sql = """
INSERT INTO scanner.vuln_surface_triggers (