namespace AIStudio.Assistants.VisualBriefing; internal sealed partial class VisualBriefingBuildOrchestrator { /// /// Marks an intentionally reused stage as skipped. /// /// The build record. /// The stage. /// The reused output hash. private static void MarkSkipped( VisualBriefingBuildRecord build, VisualBriefingBuildStage stage, string outputHash) { var record = GetStage(build, stage); record.Status = VisualBriefingBuildStageStatus.SKIPPED; record.StartedAtUtc ??= DateTimeOffset.UtcNow; record.FinishedAtUtc = DateTimeOffset.UtcNow; record.InputFingerprint = outputHash; record.OutputHash = outputHash; record.Failure = null; build.UpdatedAtUtc = DateTimeOffset.UtcNow; } /// /// Gets or creates one stage record. /// /// The build record. /// The desired stage. /// The stage record. private static VisualBriefingBuildStageRecord GetStage( VisualBriefingBuildRecord build, VisualBriefingBuildStage stage) { var record = build.Stages.FirstOrDefault(candidate => candidate.Stage == stage); if (record is not null) return record; record = new() { Stage = stage }; build.Stages.Add(record); return record; } /// /// Persists a terminal build failure. /// /// The build record. /// The terminal status. /// The safe failure. /// The cancellation token. private async Task SaveTerminalStateAsync( VisualBriefingBuildRecord build, VisualBriefingBuildStatus status, VisualBriefingFailure failure, CancellationToken token) { var stage = GetStage(build, failure.Stage); var terminalStageStatus = status is VisualBriefingBuildStatus.CANCELED ? VisualBriefingBuildStageStatus.CANCELED : VisualBriefingBuildStageStatus.FAILED; foreach (var runningStage in build.Stages.Where(item => item.Status is VisualBriefingBuildStageStatus.RUNNING)) { runningStage.Status = terminalStageStatus; runningStage.FinishedAtUtc = DateTimeOffset.UtcNow; runningStage.Failure = failure; } if (stage.Status is not (VisualBriefingBuildStageStatus.COMPLETED or VisualBriefingBuildStageStatus.SKIPPED)) { stage.Status = terminalStageStatus; stage.StartedAtUtc ??= DateTimeOffset.UtcNow; stage.FinishedAtUtc = DateTimeOffset.UtcNow; stage.Failure = failure; } build.Status = status; build.Failure = failure; build.UpdatedAtUtc = DateTimeOffset.UtcNow; await this.store.SaveBuildAsync(build, token); this.progressService.Publish(build); } /// /// Finishes diagnostics and creates a failed result. /// /// The operation diagnostics. /// The optional persisted build. /// The safe failure. /// Whether content can continue as a rebuild. /// The failed result. private static VisualBriefingBuildResult FinishFailure( VisualBriefingOperationDiagnostics diagnostics, VisualBriefingBuildRecord? build, VisualBriefingFailure failure, bool canContinueAsRebuild) { diagnostics.BuildId = build?.BuildId ?? diagnostics.BuildId; diagnostics.Stage = failure.Stage; diagnostics.FailureCode = failure.Code; diagnostics.ValidationRule = failure.ValidationRule; diagnostics.StructuredResponse = failure.StructuredResponse; diagnostics.FinishedAtUtc = DateTimeOffset.UtcNow; return new( false, null, failure.UserMessage, failure.Code, diagnostics, canContinueAsRebuild); } /// /// Creates a logging event from a stable identifier. /// /// The stable event identifier. /// The logging event. private static EventId Event(VisualBriefingLogEventId eventId) => new((int)eventId, eventId.ToString()); }