V17 / d6d373561692
WorkspaceMutationExtensions.cs
Lilith/Core/Workspace/Extensions/WorkspaceMutationExtensions.cs
Unchanged from the preceding versionCS
using System.Text.Json;
namespace Lilith.Core.Workspace.Extensions;
internal static class WorkspaceMutationExtensions
{
private static readonly JsonSerializerOptions Json = new() { WriteIndented = true };
// Called only while the session holds its process gate and exclusive workspace lease.
internal static async Task<WorkspaceResult> CommitMutationAsync(this WorkspaceRequest request, WorkspaceOptions options, string? proposalId)
{
// After approval validation, complete the short filesystem commit even if cancellation arrives.
string operationId = proposalId ?? Guid.NewGuid().ToString("N");
string receiptPath = options.GetMetadataPath("receipts", operationId) + ".json";
if (File.Exists(receiptPath)) return JsonSerializer.Deserialize<WorkspaceResult>(File.ReadAllText(receiptPath))!;
await (options.GetMetadataPath("journal", operationId) + ".json").WriteAtomicallyAsync(JsonSerializer.Serialize(new { request, Before = request.Snapshot(options), Status = "prepared", CreatedAt = DateTimeOffset.UtcNow }, Json), CancellationToken.None);
string? binId = null;
string target = request.Operation == "restore" ? options.ResolvePath(options.GetOriginalBinPath(request.BinId)) : options.ResolvePath(request.Path);
switch (request.Operation)
{
case "project_update":
await request.UpdateProjectAsync(options, target, operationId);
break;
case "project_create":
await request.PublishProjectAsync(options, target, operationId);
break;
case "save":
await target.WriteAtomicallyAsync(request.Content, CancellationToken.None);
break;
case "mkdir":
Directory.CreateDirectory(target);
break;
case "move":
target.MoveTo(options.ResolvePath(request.Destination));
break;
case "bin":
binId = operationId;
string folder = options.GetMetadataPath("bin", binId);
Directory.CreateDirectory(folder);
await Path.Combine(folder, "original.json").WriteAtomicallyAsync(JsonSerializer.Serialize(request), CancellationToken.None);
await Path.Combine(folder, "deleted.json").WriteAtomicallyAsync(JsonSerializer.Serialize(new { OriginalPath = request.Path, DeletedAtUtc = DateTimeOffset.UtcNow }), CancellationToken.None);
target.MoveTo(Path.Combine(folder, "item"));
break;
case "restore":
Path.Combine(options.GetMetadataPath("bin", request.BinId), "item").MoveTo(target);
break;
default:
throw new ArgumentException("Unknown workspace operation.");
}
string message = request.Operation == "project_create"
? "Project published. Source validation/build passed; the generated application has not been run."
: $"Workspace {request.Operation} completed.";
var result = new WorkspaceResult("completed", message, Data: new { operation_id = operationId, path = Path.GetRelativePath(options.RootDirectory, target).Replace('\\', '/'), destination = request.Destination, bin_id = binId });
await receiptPath.WriteAtomicallyAsync(JsonSerializer.Serialize(result, Json), CancellationToken.None);
if (proposalId is not null) File.Delete(options.GetMetadataPath("pending", proposalId) + ".json");
return result;
}
internal static WorkspaceResult RejectProposal(this WorkspaceRequest request, WorkspaceOptions options)
{
string path = options.GetMetadataPath("pending", request.BinId) + ".json";
if (!File.Exists(path)) throw new IOException("Proposal not found.");
File.Delete(path);
return new("completed", "Proposal rejected. No workspace data changed.");
}
}