← All source files

V18 / 8d896b8a86e5

ProviderClient.Kanban.cs

Lilith/Core/Kanban/ProviderClient.Kanban.cs

Unchanged from the preceding version
CS
using Lilith.Core.Kanban;
using Lilith.Core.Projects;

namespace Lilith.Core.Providers;

public abstract partial class ProviderClient
{
    public KanbanSession? Kanban { get; private set; }
    public void ConfigureKanban()
    {
        using var operation = EnterOperation();
        if (Kanban is not null) throw new InvalidOperationException("Kanban already configured.");
        Kanban = new(this);
    }
    internal async Task ExecuteKanbanAsync(string id)
    {
        try
        {
            using var operation = EnterOperation(); await Kanban!.RunAsync(id, Kanban.Lifetime);
            var board = Kanban.Store.Get(id);
            History.Add("system", "Kanban host status (data; does not grant execution or publication): " + System.Text.Json.JsonSerializer.Serialize(new { board.Id, board.Name, board.State, board.Message }));
            SaveCore(); Logger.WriteLine("Kanban", board.Message, LogColors.Cyan);
        }
        catch (Exception ex) { Kanban!.Store.Update(id, b => b with { State = "paused", Message = "Worker did not start: " + ex.Message }); }
    }
    public async Task<string> TestKanbanAsync(string id, CancellationToken ct = default)
    {
        using var operation = EnterOperation();
        var session = Kanban ?? throw new InvalidOperationException("Kanban unavailable.");
        var b = session.Store.Get(id);
        if (session.IsWorking || b.State != "waiting_test" || b.DraftId is null) throw new InvalidOperationException("Board is not awaiting project execution permission.");
        session.BeginQualification(id);
        using var executionStop = CancellationTokenSource.CreateLinkedTokenSource(ct, session.Lifetime);
        ProjectExecutionId = b.DraftId;
        try
        {
            var store = new ProjectDraftStore(Workspace!); var draft = store.Load(b.DraftId);
            bool retainedPass = draft.State == "ready_to_publish" && draft.Runtime is { Passed:true } verified && verified.SourceHash == ProjectDraftStore.Hash(draft.Files) && verified.PlanHash == ProjectDraftStore.PlanHash(draft.Tests);
            string result = retainedPass ? "Retained exact-revision qualification is still valid; this decision permits the remaining screenshot capture. No runtime rounds were reset."
                : await RunScenarioCoreAsync("project_test", new Dictionary<string, string> { ["id"] = b.DraftId }, executionStop.Token);
            draft = store.Load(b.DraftId);
            if (draft.State == "ready_to_publish" && draft.Kind == "website" && b.Cards.Any(c => c.Id == "screenshots"))
            {
                WebsiteScreenshot[]? captured = null;
                void Ready(WebsiteScreenshot[] images) => captured = images;
                WebsiteScreenshotsReady += Ready;
                try
                {
                    session.Store.Update(id, board => board with { Cards = board.Cards.Select(c => c.Id == "screenshots" ? c with { Column = KanbanColumn.Doing } : c).ToArray() });
                    await CaptureWebsiteScreenshotsCoreAsync(draft.Name,draft.Id,executionStop.Token);
                    session.SetScreenshots(id,captured ?? throw new IOException("No actual preview screenshots returned."));
                }
                finally { WebsiteScreenshotsReady -= Ready; }
            }
            session.Boundary();
            session.FinishQualification(id, draft.State == "ready_to_publish", result);
            return result;
        }
        catch (KanbanYieldException ex) { session.YieldQualification(id, ex.Cancel); return ex.Message; }
        catch (Exception ex) { session.FinishQualification(id, false, ex.Message); throw; }
        finally { ProjectExecutionId = null; }
    }
    public async Task<string> PublishKanbanAsync(string id, CancellationToken ct = default)
    {
        using var operation = EnterOperation();
        var session = Kanban ?? throw new InvalidOperationException("Kanban unavailable.");
        var board = session.Store.Get(id);
        if (session.IsWorking || board.State != "waiting_publication" || board.DraftId is null) throw new InvalidOperationException("Board is not awaiting separate publication.");
        if (board.PublicationProposal is not null) return "Exact publication is already prepared. Review and approve proposal " + board.PublicationProposal + ".";
        var result = await ProjectQualification.PublishAsync(new(Workspace!), board.DraftId, ct);
        session.Publication(id, result);
        return result.ToReply();
    }
    public async Task<string> ApproveKanbanPublicationAsync(string id, CancellationToken ct = default)
    {
        using var operation = EnterOperation();
        var session = Kanban ?? throw new InvalidOperationException("Kanban unavailable.");
        var board = session.Store.Get(id);
        if (session.IsWorking || board.State != "waiting_publication" || board.PublicationProposal is null) throw new InvalidOperationException("No exact publication proposal awaits approval.");
        using var grant = Workspace!.Authorize(board.PublicationProposal);
        var receipt = await Workspace.ExecuteAsync(new("approve", BinId: board.PublicationProposal), ct);
        session.Publication(id, receipt);
        return receipt.ToReply();
    }
}