namespace StellaOps.Scanner.Sources.Domain;
#pragma warning disable CA1062 // Validate arguments of public methods - TimeProvider validated at DI boundary
///
/// Represents a single execution run of an SBOM source.
/// Tracks status, timing, item counts, and any errors.
///
public sealed class SbomSourceRun
{
/// Unique run identifier.
public Guid RunId { get; init; }
/// Source that was run.
public Guid SourceId { get; init; }
/// Tenant owning the source.
public string TenantId { get; init; } = null!;
/// What triggered this run.
public SbomSourceRunTrigger Trigger { get; init; }
/// Additional trigger details (webhook payload digest, cron expression, etc.).
public string? TriggerDetails { get; init; }
/// Current status of the run.
public SbomSourceRunStatus Status { get; private set; } = SbomSourceRunStatus.Running;
/// When the run started.
public DateTimeOffset StartedAt { get; init; }
/// When the run completed (if finished).
public DateTimeOffset? CompletedAt { get; private set; }
///
/// Duration in milliseconds. Pass a TimeProvider to get the live duration for in-progress runs.
///
public long GetDurationMs(TimeProvider? timeProvider = null)
{
if (CompletedAt.HasValue)
return (long)(CompletedAt.Value - StartedAt).TotalMilliseconds;
var now = timeProvider?.GetUtcNow() ?? DateTimeOffset.UtcNow;
return (long)(now - StartedAt).TotalMilliseconds;
}
/// Number of items discovered to scan.
public int ItemsDiscovered { get; private set; }
/// Number of items that were scanned.
public int ItemsScanned { get; private set; }
/// Number of items that succeeded.
public int ItemsSucceeded { get; private set; }
/// Number of items that failed.
public int ItemsFailed { get; private set; }
/// Number of items that were skipped.
public int ItemsSkipped { get; private set; }
/// IDs of scan jobs created by this run.
public List ScanJobIds { get; init; } = [];
/// Error message if failed.
public string? ErrorMessage { get; private set; }
/// Error stack trace if failed.
public string? ErrorStackTrace { get; private set; }
/// Correlation ID for distributed tracing.
public string CorrelationId { get; init; } = null!;
// -------------------------------------------------------------------------
// Factory Methods
// -------------------------------------------------------------------------
///
/// Create a new source run.
///
public static SbomSourceRun Create(
Guid sourceId,
string tenantId,
SbomSourceRunTrigger trigger,
string correlationId,
TimeProvider timeProvider,
string? triggerDetails = null)
{
return new SbomSourceRun
{
RunId = Guid.NewGuid(),
SourceId = sourceId,
TenantId = tenantId,
Trigger = trigger,
TriggerDetails = triggerDetails,
Status = SbomSourceRunStatus.Running,
StartedAt = timeProvider.GetUtcNow(),
CorrelationId = correlationId
};
}
// -------------------------------------------------------------------------
// Progress Updates
// -------------------------------------------------------------------------
///
/// Set the number of discovered items.
///
public void SetDiscoveredItems(int count)
{
ItemsDiscovered = count;
}
///
/// Record a successfully scanned item.
///
public void RecordItemSuccess(Guid scanJobId)
{
ItemsScanned++;
ItemsSucceeded++;
ScanJobIds.Add(scanJobId);
}
///
/// Record a failed item.
///
public void RecordItemFailure()
{
ItemsScanned++;
ItemsFailed++;
}
///
/// Record a skipped item.
///
public void RecordItemSkipped()
{
ItemsSkipped++;
}
// -------------------------------------------------------------------------
// Completion
// -------------------------------------------------------------------------
///
/// Complete the run successfully.
///
public void Complete(TimeProvider timeProvider)
{
Status = ItemsFailed > 0
? SbomSourceRunStatus.PartialSuccess
: ItemsSucceeded > 0
? SbomSourceRunStatus.Succeeded
: SbomSourceRunStatus.Skipped;
CompletedAt = timeProvider.GetUtcNow();
}
///
/// Fail the run with an error.
///
public void Fail(string message, TimeProvider timeProvider, string? stackTrace = null)
{
Status = SbomSourceRunStatus.Failed;
ErrorMessage = message;
ErrorStackTrace = stackTrace;
CompletedAt = timeProvider.GetUtcNow();
}
///
/// Cancel the run.
///
public void Cancel(string reason, TimeProvider timeProvider)
{
Status = SbomSourceRunStatus.Cancelled;
ErrorMessage = reason;
CompletedAt = timeProvider.GetUtcNow();
}
}