docs re-org, audit fixes, build fixes

This commit is contained in:
StellaOps Bot
2026-01-05 09:35:33 +02:00
parent eca4e964d3
commit dfab8a29c3
173 changed files with 1276 additions and 560 deletions

View File

@@ -9,13 +9,16 @@ public sealed class ExportRetentionService : IExportRetentionService
{
private readonly IExportRetentionStore _retentionStore;
private readonly ILogger<ExportRetentionService> _logger;
private readonly TimeProvider _timeProvider;
public ExportRetentionService(
IExportRetentionStore retentionStore,
ILogger<ExportRetentionService> logger)
ILogger<ExportRetentionService> logger,
TimeProvider? timeProvider = null)
{
_retentionStore = retentionStore ?? throw new ArgumentNullException(nameof(retentionStore));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <inheritdoc />
@@ -26,7 +29,7 @@ public sealed class ExportRetentionService : IExportRetentionService
ArgumentNullException.ThrowIfNull(request);
var retention = request.OverrideRetention ?? new ExportRetentionConfig();
var now = DateTimeOffset.UtcNow;
var now = _timeProvider.GetUtcNow();
_logger.LogInformation(
"Starting retention prune for tenant {TenantId}, profile {ProfileId}, execute={Execute}",

View File

@@ -11,6 +11,12 @@ public sealed class InMemoryExportScheduleStore : IExportScheduleStore
private readonly ConcurrentDictionary<Guid, Guid> _runToProfile = new();
private readonly ConcurrentDictionary<Guid, List<ScheduledProfileInfo>> _profilesByTenant = new();
private readonly object _lock = new();
private readonly TimeProvider _timeProvider;
public InMemoryExportScheduleStore(TimeProvider? timeProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <summary>
/// Adds a profile for testing.
@@ -106,7 +112,7 @@ public sealed class InMemoryExportScheduleStore : IExportScheduleStore
{
if (_statusByProfile.TryGetValue(profileId, out var existing))
{
var now = DateTimeOffset.UtcNow;
var now = _timeProvider.GetUtcNow();
var newFailureCount = success ? 0 : existing.ConsecutiveFailures + 1;
_statusByProfile[profileId] = existing with

View File

@@ -32,12 +32,14 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
};
private readonly ILogger<LineageEvidencePackService> _logger;
private readonly TimeProvider _timeProvider;
private readonly ConcurrentDictionary<Guid, CachedPack> _packCache = new();
private readonly string _tempDirectory;
public LineageEvidencePackService(ILogger<LineageEvidencePackService> logger)
public LineageEvidencePackService(ILogger<LineageEvidencePackService> logger, TimeProvider? timeProvider = null)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? TimeProvider.System;
_tempDirectory = Path.Combine(Path.GetTempPath(), "stellaops-evidence-packs");
Directory.CreateDirectory(_tempDirectory);
}
@@ -187,7 +189,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
Entries = entries.ToImmutableArray(),
TotalSizeBytes = entries.Sum(e => e.SizeBytes),
FileCount = entries.Count,
CreatedAt = DateTimeOffset.UtcNow
CreatedAt = _timeProvider.GetUtcNow()
};
// Write manifest
@@ -205,7 +207,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
VexVerdictDigests = vexDocuments.Select(v => v.Digest).ToImmutableArray(),
PolicyVerdictDigest = policyVerdict?.Digest,
ReplayHash = ComputeReplayHash(artifactDigest, sbomDigest, manifest.MerkleRoot),
GeneratedAt = DateTimeOffset.UtcNow,
GeneratedAt = _timeProvider.GetUtcNow(),
Attestations = attestations.ToImmutableArray(),
SbomDocuments = sbomDocuments.ToImmutableArray(),
VexDocuments = vexDocuments.ToImmutableArray(),
@@ -224,7 +226,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
{
Pack = pack,
ZipPath = zipPath,
ExpiresAt = DateTimeOffset.UtcNow.AddHours(24)
ExpiresAt = _timeProvider.GetUtcNow().AddHours(24)
};
// Clean up temp directory
@@ -246,7 +248,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
Success = true,
Pack = pack,
DownloadUrl = $"/api/v1/lineage/export/{packId}/download",
ExpiresAt = DateTimeOffset.UtcNow.AddHours(24),
ExpiresAt = _timeProvider.GetUtcNow().AddHours(24),
SizeBytes = zipInfo.Length,
Warnings = warnings.ToImmutableArray()
};
@@ -268,7 +270,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
string tenantId,
CancellationToken ct = default)
{
if (_packCache.TryGetValue(packId, out var cached) && cached.ExpiresAt > DateTimeOffset.UtcNow)
if (_packCache.TryGetValue(packId, out var cached) && cached.ExpiresAt > _timeProvider.GetUtcNow())
{
if (cached.Pack.TenantId == tenantId)
{
@@ -285,7 +287,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
string tenantId,
CancellationToken ct = default)
{
if (_packCache.TryGetValue(packId, out var cached) && cached.ExpiresAt > DateTimeOffset.UtcNow)
if (_packCache.TryGetValue(packId, out var cached) && cached.ExpiresAt > _timeProvider.GetUtcNow())
{
if (cached.Pack.TenantId == tenantId && File.Exists(cached.ZipPath))
{
@@ -347,7 +349,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
bomFormat = "CycloneDX",
specVersion = "1.6",
version = 1,
metadata = new { timestamp = DateTimeOffset.UtcNow.ToString("O") },
metadata = new { timestamp = _timeProvider.GetUtcNow().ToString("O") },
components = Array.Empty<object>()
}, JsonOptions);
@@ -383,7 +385,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
dataLicense = "CC0-1.0",
name = artifactDigest,
documentNamespace = $"https://stellaops.io/spdx/{artifactDigest}",
creationInfo = new { created = DateTimeOffset.UtcNow.ToString("O") },
creationInfo = new { created = _timeProvider.GetUtcNow().ToString("O") },
packages = Array.Empty<object>()
}, JsonOptions);
@@ -418,7 +420,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
context = "https://openvex.dev/ns/v0.2.0",
id = $"urn:stellaops:vex:{artifactDigest}",
author = "StellaOps",
timestamp = DateTimeOffset.UtcNow.ToString("O"),
timestamp = _timeProvider.GetUtcNow().ToString("O"),
statements = Array.Empty<object>()
}, JsonOptions);
@@ -457,7 +459,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
tenantId,
verdict = "pass",
policyVersion = "1.0.0",
evaluatedAt = DateTimeOffset.UtcNow.ToString("O"),
evaluatedAt = _timeProvider.GetUtcNow().ToString("O"),
rules = new { total = 0, passed = 0, failed = 0, warned = 0 }
}, JsonOptions);
@@ -477,7 +479,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
RulesPassed = 0,
RulesFailed = 0,
RulesWarned = 0,
EvaluatedAt = DateTimeOffset.UtcNow,
EvaluatedAt = _timeProvider.GetUtcNow(),
FileName = fileName
};
}
@@ -528,9 +530,9 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
return ComputeHash(combined);
}
private static string ComputeReplayHash(string artifactDigest, string sbomDigest, string merkleRoot)
private string ComputeReplayHash(string artifactDigest, string sbomDigest, string merkleRoot)
{
var input = $"{artifactDigest}|{sbomDigest}|{merkleRoot}|{DateTimeOffset.UtcNow:O}";
var input = $"{artifactDigest}|{sbomDigest}|{merkleRoot}|{_timeProvider.GetUtcNow():O}";
return $"sha256:{ComputeHash(input)}";
}

View File

@@ -149,14 +149,15 @@ public sealed record ExportVerificationResult
/// <summary>
/// When verification was performed.
/// </summary>
public DateTimeOffset VerifiedAt { get; init; } = DateTimeOffset.UtcNow;
public required DateTimeOffset VerifiedAt { get; init; }
public static ExportVerificationResult Failed(Guid runId, params VerificationError[] errors)
public static ExportVerificationResult Failed(Guid runId, DateTimeOffset verifiedAt, params VerificationError[] errors)
=> new()
{
Status = VerificationStatus.Invalid,
RunId = runId,
Errors = errors
Errors = errors,
VerifiedAt = verifiedAt
};
}

View File

@@ -14,22 +14,26 @@ public sealed class ExportVerificationService : IExportVerificationService
private readonly IExportArtifactStore _artifactStore;
private readonly IPackRunAttestationStore? _packRunStore;
private readonly ILogger<ExportVerificationService> _logger;
private readonly TimeProvider _timeProvider;
public ExportVerificationService(
IExportArtifactStore artifactStore,
ILogger<ExportVerificationService> logger)
: this(artifactStore, null, logger)
ILogger<ExportVerificationService> logger,
TimeProvider? timeProvider = null)
: this(artifactStore, null, logger, timeProvider)
{
}
public ExportVerificationService(
IExportArtifactStore artifactStore,
IPackRunAttestationStore? packRunStore,
ILogger<ExportVerificationService> logger)
ILogger<ExportVerificationService> logger,
TimeProvider? timeProvider = null)
{
_artifactStore = artifactStore ?? throw new ArgumentNullException(nameof(artifactStore));
_packRunStore = packRunStore;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <inheritdoc />
@@ -52,6 +56,7 @@ public sealed class ExportVerificationService : IExportVerificationService
{
return ExportVerificationResult.Failed(
request.RunId,
_timeProvider.GetUtcNow(),
new VerificationError
{
Code = VerificationErrorCodes.ManifestNotFound,
@@ -64,6 +69,7 @@ public sealed class ExportVerificationService : IExportVerificationService
{
return ExportVerificationResult.Failed(
request.RunId,
_timeProvider.GetUtcNow(),
new VerificationError
{
Code = VerificationErrorCodes.TenantMismatch,
@@ -234,7 +240,8 @@ public sealed class ExportVerificationService : IExportVerificationService
Encryption = encryptionResult,
Attestation = attestationStatus,
Errors = errors,
Warnings = warnings
Warnings = warnings,
VerifiedAt = _timeProvider.GetUtcNow()
};
}

View File

@@ -19,6 +19,7 @@ public sealed class ExceptionReportGenerator : IExceptionReportGenerator
private readonly IExceptionApplicationRepository _applicationRepository;
private readonly ConcurrentDictionary<string, ReportJob> _jobs = new();
private readonly ILogger<ExceptionReportGenerator> _logger;
private readonly TimeProvider _timeProvider;
private static readonly JsonSerializerOptions JsonOptions = new()
{
@@ -30,11 +31,13 @@ public sealed class ExceptionReportGenerator : IExceptionReportGenerator
public ExceptionReportGenerator(
IExceptionRepository exceptionRepository,
IExceptionApplicationRepository applicationRepository,
ILogger<ExceptionReportGenerator> logger)
ILogger<ExceptionReportGenerator> logger,
TimeProvider? timeProvider = null)
{
_exceptionRepository = exceptionRepository;
_applicationRepository = applicationRepository;
_logger = logger;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<ExceptionReportJobResponse> CreateReportAsync(
@@ -42,7 +45,7 @@ public sealed class ExceptionReportGenerator : IExceptionReportGenerator
CancellationToken cancellationToken = default)
{
var jobId = $"exc-rpt-{Guid.NewGuid():N}";
var now = DateTimeOffset.UtcNow;
var now = _timeProvider.GetUtcNow();
var job = new ReportJob
{
@@ -151,7 +154,7 @@ public sealed class ExceptionReportGenerator : IExceptionReportGenerator
try
{
job.Status = "running";
job.StartedAt = DateTimeOffset.UtcNow;
job.StartedAt = _timeProvider.GetUtcNow();
var filter = job.Request.Filter ?? new ExceptionFilter
{
@@ -232,7 +235,7 @@ public sealed class ExceptionReportGenerator : IExceptionReportGenerator
var document = new ExceptionReportDocument
{
ReportId = job.JobId,
GeneratedAt = DateTimeOffset.UtcNow,
GeneratedAt = _timeProvider.GetUtcNow(),
TenantId = job.TenantId,
RequesterId = job.RequesterId,
Title = job.Request.Title ?? "Exception Report",
@@ -289,7 +292,7 @@ public sealed class ExceptionReportGenerator : IExceptionReportGenerator
job.ContentHash = $"sha256:{Convert.ToHexString(SHA256.HashData(content)).ToLowerInvariant()}";
job.Progress = 100;
job.Status = "completed";
job.CompletedAt = DateTimeOffset.UtcNow;
job.CompletedAt = _timeProvider.GetUtcNow();
_logger.LogInformation(
"Completed exception report {JobId} with {Count} exceptions, {Size} bytes",
@@ -300,7 +303,7 @@ public sealed class ExceptionReportGenerator : IExceptionReportGenerator
_logger.LogError(ex, "Failed to generate exception report {JobId}", job.JobId);
job.Status = "failed";
job.ErrorMessage = ex.Message;
job.CompletedAt = DateTimeOffset.UtcNow;
job.CompletedAt = _timeProvider.GetUtcNow();
}
}