← All source files

V17 / d6d373561692

ReleaseRuntime.Approvals.cs

Interfaces/Gui/LilithGui.Worker/ReleaseRuntime.Approvals.cs

Unchanged from the preceding version
CS
using System.Text.Json;
using System.Security.Cryptography;
using System.Text;
using static Lilith.Gui.Worker.ReflectionApi;

namespace Lilith.Gui.Worker;

internal sealed partial class ReleaseRuntime
{
    private sealed record Approval(string Id,string Service,string Title,JsonElement Review,string Fingerprint,string Approve,string? Reject);
    private readonly HashSet<string> _declinedExecution=new();
    private static JsonElement Field(JsonElement value,string name)=>value.EnumerateObject().FirstOrDefault(p=>p.Name.Equals(name,StringComparison.OrdinalIgnoreCase)).Value;
    private static string Fingerprint(JsonElement review)=>Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(review.GetRawText())));
    private async Task<List<Approval>> Approvals(CancellationToken ct)
    {
        var list=new List<Approval>();
        var boardItems=Get(Get(_provider,"Kanban"),"Store") is {} boardStore&&await Call(boardStore,"List") is System.Collections.IEnumerable savedBoards?savedBoards.Cast<object>().ToArray():[];
        var boardDrafts=boardItems.Select(b=>Get(b,"DraftId")?.ToString()).Where(id=>id is not null).ToHashSet();
        async Task Add(string service,string pending,string review,string approve,string reject)
        {
            var commands=Get(_agent,"ChatCommands");if(commands is null)return;
            var result=await Call(commands,"ExecuteAsync",new Dictionary<string,object?>{{"input",pending},{"cancellationToken",ct}});
            if(Get(result,"Handled") is not true||Get(result,"Output") is not string output)return;
            JsonElement data;try{data=JsonDocument.Parse(output).RootElement.Clone();}catch(JsonException){return;}
            if(data.ValueKind==JsonValueKind.Object&&data.TryGetProperty("Data",out var items))data=items;
            if(data.ValueKind!=JsonValueKind.Array)return;
            foreach(var item in data.EnumerateArray())
            {
                if(!item.TryGetProperty("Id",out var idValue))continue;string id=idValue.GetString()!;
                var exact=review.Length==0?item.Clone():JsonDocument.Parse((string)Get((await Call(commands,"ExecuteAsync",new Dictionary<string,object?>{{"input",review+id},{"cancellationToken",ct}}))!,"Output")!).RootElement.Clone();
                string fingerprint=Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(exact.GetRawText())));
                list.Add(new(id,service,"Review "+service+" change",exact,fingerprint,approve+id,reject+id));
            }
        }
        if(Get(_provider,"Workspace") is not null)await Add("workspace","/workspace","/workspace review ","/approve ","/reject ");
        if(Get(_provider,"Schedules") is not null)await Add("schedules","/schedules pending","/schedules review ","/schedule-approve ","/schedule-reject ");
        if(Get(_provider,"Calendar") is not null)await Add("calendar","/calendar pending","/calendar review ","/calendar-approve ","/calendar-reject ");
        if(Get(_provider,"Home") is not null)await Add("home","/home-pending","","/home-approve ","/home-reject ");
        if(core.GetType("Lilith.Core.Projects.ProjectDraftStore") is {} draftType&&Get(_provider,"Workspace") is {} workspace)
        {
            object store=Activator.CreateInstance(draftType,workspace)!;
            if(await Call(store,"List") is System.Collections.IEnumerable drafts)foreach(var draft in drafts.Cast<object>())
            {
                if(Get(draft,"State")?.ToString()!="awaiting_test_approval")continue;
                string id=Get(draft,"Id")!.ToString()!;if(boardDrafts.Contains(id))continue;var review=JsonSerializer.SerializeToElement(await Call(store,"Load",new Dictionary<string,object?>{{"id",id}}));
                string fingerprint=Fingerprint(review);if(_declinedExecution.Contains(id+fingerprint))continue;
                list.Add(new(id,"project execution","Execute generated project and bounded test/repair rounds",review,fingerprint,"/project-test "+id,null));
            }
        }
        foreach(var board in boardItems)
        {
            string state=Get(board,"State")!.ToString()!,id=Get(board,"Id")!.ToString()!;
            if(state is not("waiting_plan" or "waiting_test"))continue;
            var review=JsonSerializer.SerializeToElement(board);string fingerprint=Fingerprint(review);
            if(_declinedExecution.Contains(id+fingerprint))continue;
            list.Add(new(id,"kanban",state=="waiting_plan"?"Approve the exact Kanban plan":"Execute generated Kanban project and bounded tests",review,fingerprint,state=="waiting_plan"?"/kanban-approve "+id+" "+Get(board,"Revision"):"/kanban-test "+id,state=="waiting_plan"?"/kanban-cancel "+id:null));
        }
        var bridge=core.GetType("Lilith.Core.Messaging.MessageBridgeClient");
        bool pendingMessages=(bool?)(_provider?.GetType().GetField("PendingMessageApproval",System.Reflection.BindingFlags.Instance|System.Reflection.BindingFlags.NonPublic)?.GetValue(_provider))==true;
        if(bridge?.GetProperty("Configured")?.GetValue(null) is true&&pendingMessages)
        {
            async Task<JsonElement> Request(string action,object payload){var task=(Task<string>)bridge.GetMethod("RequestAsync")!.Invoke(null,[action,payload,ct])!;return JsonDocument.Parse(await task).RootElement.Clone();}
            var channels=await Request("channels",new{key=""});
            foreach(var channel in Field(channels,"channels").EnumerateArray())
            {
                string nickname=Field(channel,"nickname").GetString()!;var status=await Request("status",new{nickname});
                foreach(var proposal in Field(status,"proposals").EnumerateArray())
                {string id=Field(proposal,"id").GetString()!;var review=JsonSerializer.SerializeToElement(new{nickname,proposal});list.Add(new(id,"messaging","Review exact destination and outgoing message",review,Fingerprint(review),"/message-approve "+nickname+" "+id,"/message-reject "+nickname+" "+id));}
            }
        }
        return list;
    }
    internal async Task PublishApprovals(CancellationToken ct)=>emit("approvals",await Approvals(ct));
    private async Task<object?> DecideApproval(JsonElement payload,CancellationToken ct)
    {
        var proposal=(await Approvals(ct)).SingleOrDefault(p=>p.Id==Text(payload,"id")&&p.Service==Text(payload,"service"))??throw new InvalidOperationException("This proposal is no longer pending. Refresh and review again.");
        if(proposal.Fingerprint!=Text(payload,"fingerprint"))throw new InvalidOperationException("The proposal changed. Review it again before deciding.");
        bool approve=payload.GetProperty("approve").GetBoolean();
        if(!approve&&proposal.Reject is null){_declinedExecution.Add(proposal.Id+proposal.Fingerprint);return new{message="Execution was not approved. The private draft remains untested and unpublished."};}
        return await Command(approve?proposal.Approve:proposal.Reject!,ct);
    }
}