465 lines · 15.1 KB · Zig
RawDownload
const std = @import("std");const client = @import("client.zig");const contracts = @import("contracts.zig");const debug_trace = @import("../shared/debug_trace.zig");const operation = @import("operation.zig");const shell_resolver = @import("shell_resolver.zig");const protocol = @import("protocol.zig"); const Allocator = std.mem.Allocator;const max_pending = 32;pub const start_wait_ceiling_ms: u64 = 20_000; pub const OpenIntentAdmission = enum {    accepted,    occupied,}; pub const DeinitDisposition = enum {    settled,    abnormal,}; pub const Admission = struct {    alloc: Allocator,    profile_user: []const u8,    durable_session_id: []const u8,    workspace_root: []const u8,    sandbox_backend: @FieldType(contracts.Principal, "sandbox_backend"),    command: []const u8,}; const Pending = struct {    correlation_id: contracts.CorrelationId,    command: []u8,    starting_pending: bool = true,    completion: ?client.Completion = null,     fn deinit(self: *Pending, alloc: Allocator) void {        if (self.completion) |*completion| completion.deinit();        alloc.free(self.command);        self.* = undefined;    }}; pub const NoticePhase = enum { starting, final }; pub const Notice = union(enum) {    starting: struct {        correlation_id: contracts.CorrelationId,        command: []const u8,    },    running: struct {        correlation_id: contracts.CorrelationId,        command: []const u8,        session_id: []const u8,    },    failed: struct {        correlation_id: contracts.CorrelationId,        command: []const u8,        code: contracts.StructuredErrorCode,    },     pub fn correlationId(self: Notice) contracts.CorrelationId {        return switch (self) {            inline else => |value| value.correlation_id,        };    }     pub fn phase(self: Notice) NoticePhase {        return switch (self) {            .starting => .starting,            .running, .failed => .final,        };    }}; pub const Runtime = struct {    pending: [max_pending]?Pending = @splat(null),    len: usize = 0,    open_intent: ?[]u8 = null,     pub fn deinitSettled(        self: *Runtime,        alloc: Allocator,    ) DeinitDisposition {        if (self.len != 0) {            self.deinitAbnormal(alloc, "graceful_exit_invariant");            return .abnormal;        }        self.deinitEmpty(alloc);        return .settled;    }     pub fn deinitAbnormal(        self: *Runtime,        alloc: Allocator,        reason: []const u8,    ) void {        for (self.pending[0..self.len]) |*entry| {            if (entry.*) |*pending| {                debug_trace.logf(                    "terminal",                    "direct pending dropped correlation={d} phase={s} reason={s}",                    .{                        pending.correlation_id.value,                        pendingPhase(pending),                        reason,                    },                );                pending.deinit(alloc);                entry.* = null;            }        }        self.len = 0;        self.deinitEmpty(alloc);    }     fn deinitEmpty(self: *Runtime, alloc: Allocator) void {        if (self.open_intent) |session_id| alloc.free(session_id);        self.* = .{};    }     pub fn requestOpen(        self: *Runtime,        alloc: Allocator,        session_id: []const u8,    ) Allocator.Error!OpenIntentAdmission {        if (self.open_intent != null) return .occupied;        const owned = try alloc.dupe(u8, session_id);        self.open_intent = owned;        return .accepted;    }     pub fn pendingOpenIntent(self: *const Runtime) ?[]const u8 {        return self.open_intent;    }     pub fn takeOpenIntent(self: *Runtime) ?[]u8 {        const session_id = self.open_intent orelse return null;        self.open_intent = null;        return session_id;    }     pub fn admit(        self: *Runtime,        terminal_client: *client.Runtime,        input: Admission,    ) !contracts.CorrelationId {        if (self.len == self.pending.len) return error.QueueFull;        const command = try input.alloc.dupe(u8, input.command);        errdefer input.alloc.free(command);        var persistence = try operation.prepareStartPersistence(input.alloc, .{            .profile_user = input.profile_user,            .durable_session_id = input.durable_session_id,            .workspace_root = input.workspace_root,            .cwd = input.workspace_root,            .transport_role = .interactive,            .sandbox_backend = input.sandbox_backend,            .backend = .native,            .actor = .human,            .controls = .full(),            .lifetime = .session,            .direct_human_model_read_only = true,        });        defer persistence.deinit();        const request = contracts.ActionRequest{ .start = .{            .cwd = input.workspace_root,            .command = input.command,            .shell = try shell_resolver.profileShell(input.alloc, null, .user),            .backend = .native,            .return_when = .started,            .wait_ceiling_ms = start_wait_ceiling_ms,            .persistence = persistence.view(),        } };        try operation.validate(request);        const correlation_id = terminal_client.nextCorrelationId();        try terminal_client.admit(input.alloc, correlation_id, request);        self.pending[self.len] = .{            .correlation_id = correlation_id,            .command = command,        };        self.len += 1;        return correlation_id;    }     pub fn nextNotice(        self: *Runtime,        terminal_client: *client.Runtime,    ) ?Notice {        for (self.pending[0..self.len]) |*entry| {            if (entry.*) |*pending| {                if (pending.starting_pending) return .{ .starting = .{                    .correlation_id = pending.correlation_id,                    .command = pending.command,                } };                if (pending.completion == null) {                    pending.completion = terminal_client.takeCompletionFor(                        pending.correlation_id,                    ) orelse continue;                }                if (finalNotice(pending)) |notice| return notice;            }        }        return null;    }     pub fn acknowledgeNotice(        self: *Runtime,        alloc: Allocator,        correlation_id: contracts.CorrelationId,        phase: NoticePhase,    ) void {        for (self.pending[0..self.len], 0..) |entry, index| {            const pending = entry.?;            if (pending.correlation_id.value != correlation_id.value) continue;            switch (phase) {                .starting => {                    std.debug.assert(pending.starting_pending);                    self.pending[index].?.starting_pending = false;                },                .final => {                    std.debug.assert(!pending.starting_pending);                    std.debug.assert(pending.completion != null);                    var removed = self.removeAt(index);                    removed.deinit(alloc);                },            }            return;        }        unreachable;    }     pub fn hasAcceptedPending(self: *const Runtime) bool {        return self.len != 0;    }     pub fn pendingCount(self: *const Runtime) usize {        return self.len;    }     fn removeAt(self: *Runtime, index: usize) Pending {        const result = self.pending[index].?;        var current = index;        while (current + 1 < self.len) : (current += 1) {            self.pending[current] = self.pending[current + 1];        }        self.len -= 1;        self.pending[self.len] = null;        return result;    }}; fn finalNotice(pending: *const Pending) ?Notice {    const completion = &pending.completion.?;    if (completion.kind != .response) return null;    if (completion.frame) |*frame| {        switch (frame.message().payload) {            .response => |response| switch (response) {                .success => |success| switch (success) {                    .start => |start| return .{ .running = .{                        .correlation_id = pending.correlation_id,                        .command = pending.command,                        .session_id = start.session.session_id,                    } },                    else => {},                },                .failure => |failure| return .{ .failed = .{                    .correlation_id = pending.correlation_id,                    .command = pending.command,                    .code = failure.code,                } },            },            else => {},        }    }    return null;} fn pendingPhase(pending: *const Pending) []const u8 {    if (pending.starting_pending) return "starting_notice";    if (pending.completion == null) return "awaiting_host_result";    return if (finalNotice(pending) != null)        "final_notice"    else        "indeterminate_completion";} test "terminal open intent is first wins and reports occupied without mutation" {    const alloc = std.testing.allocator;    var runtime: Runtime = .{};    defer _ = runtime.deinitSettled(alloc);     try std.testing.expectEqual(        OpenIntentAdmission.accepted,        try runtime.requestOpen(alloc, "terminal-a"),    );    try std.testing.expectEqualStrings("terminal-a", runtime.pendingOpenIntent().?);    try std.testing.expectEqual(        OpenIntentAdmission.occupied,        try runtime.requestOpen(alloc, "terminal-b"),    );    try std.testing.expectEqualStrings("terminal-a", runtime.pendingOpenIntent().?);    const taken = runtime.takeOpenIntent().?;    defer alloc.free(taken);    try std.testing.expectEqualStrings("terminal-a", taken);    try std.testing.expect(runtime.pendingOpenIntent() == null);} test "terminal open intent allocation failure preserves exact ownership" {    const alloc = std.testing.allocator;    var runtime: Runtime = .{};    defer _ = runtime.deinitSettled(alloc);    var failing = std.testing.FailingAllocator.init(        alloc,        .{ .fail_index = 0 },    );     try std.testing.expectError(        error.OutOfMemory,        runtime.requestOpen(failing.allocator(), "terminal-a"),    );    try std.testing.expect(runtime.pendingOpenIntent() == null);     try std.testing.expectEqual(        OpenIntentAdmission.accepted,        try runtime.requestOpen(alloc, "terminal-a"),    );    const original = runtime.pendingOpenIntent().?;    try std.testing.expectEqual(        OpenIntentAdmission.occupied,        try runtime.requestOpen(failing.allocator(), "terminal-b"),    );    try std.testing.expectEqual(original.ptr, runtime.pendingOpenIntent().?.ptr);    try std.testing.expectEqualStrings("terminal-a", runtime.pendingOpenIntent().?);} test "direct lifecycle retains indeterminate completion without fabricating failure" {    const alloc = std.testing.allocator;    var runtime: Runtime = .{};    defer runtime.deinitAbnormal(alloc, "test_cleanup");    var terminal_client: client.Runtime = .{};    defer terminal_client.deinit();     runtime.pending[0] = .{        .correlation_id = .{ .value = 7 },        .command = try alloc.dupe(u8, "zig build test"),    };    runtime.len = 1;     const first_start = runtime.nextNotice(&terminal_client).?;    try std.testing.expectEqual(NoticePhase.starting, first_start.phase());    try std.testing.expectEqual(NoticePhase.starting, runtime.nextNotice(&terminal_client).?.phase());    runtime.acknowledgeNotice(alloc, first_start.correlationId(), first_start.phase());    try std.testing.expect(runtime.nextNotice(&terminal_client) == null);     runtime.pending[0].?.completion = .{        .kind = .disconnected,        .correlation_id = .{ .value = 7 },    };    try std.testing.expect(runtime.nextNotice(&terminal_client) == null);    try std.testing.expect(runtime.hasAcceptedPending());    try std.testing.expectEqual(        client.CompletionKind.disconnected,        runtime.pending[0].?.completion.?.kind,    );} test "indeterminate completion does not starve a later authoritative result" {    const alloc = std.testing.allocator;    var runtime: Runtime = .{};    defer runtime.deinitAbnormal(alloc, "test_cleanup");    var terminal_client: client.Runtime = .{};    defer terminal_client.deinit();     var encoded = try protocol.encodeFrame(        alloc,        contracts.current_protocol_revision,        0,        .{ .value = 8 },        .{ .response = .{ .success = .{ .start = .{            .session = .{                .session_id = "terminal-b",                .lifecycle = .running,                .attention = .{},                .backend = .native,                .output_cursor = .{ .segment = 1, .offset = 0 },                .screen_recovery = .{ .unavailable = .missing },            },            .outcome = .started,        } } } },    );    defer encoded.deinit(alloc);     runtime.pending[0] = .{        .correlation_id = .{ .value = 7 },        .command = try alloc.dupe(u8, "indeterminate"),        .starting_pending = false,        .completion = .{            .kind = .disconnected,            .correlation_id = .{ .value = 7 },        },    };    runtime.pending[1] = .{        .correlation_id = .{ .value = 8 },        .command = try alloc.dupe(u8, "authoritative"),        .starting_pending = false,        .completion = .{            .kind = .response,            .correlation_id = .{ .value = 8 },            .frame = try protocol.decodeFrame(alloc, encoded.bytes),        },    };    runtime.len = 2;     const notice = runtime.nextNotice(&terminal_client).?;    switch (notice) {        .running => |running| {            try std.testing.expectEqual(@as(u64, 8), running.correlation_id.value);            try std.testing.expectEqualStrings("terminal-b", running.session_id);        },        else => return error.TestUnexpectedResult,    }    runtime.acknowledgeNotice(alloc, notice.correlationId(), notice.phase());     try std.testing.expectEqual(@as(usize, 1), runtime.pendingCount());    try std.testing.expectEqual(@as(u64, 7), runtime.pending[0].?.correlation_id.value);    try std.testing.expect(runtime.nextNotice(&terminal_client) == null);} test "abnormal direct cleanup releases accepted unresolved outcomes" {    const alloc = std.testing.allocator;    var runtime: Runtime = .{};    runtime.pending[0] = .{        .correlation_id = .{ .value = 8 },        .command = try alloc.dupe(u8, "unresolved"),        .starting_pending = false,    };    runtime.len = 1;     runtime.deinitAbnormal(alloc, "input_closed");    try std.testing.expect(!runtime.hasAcceptedPending());} test "settled direct cleanup distinguishes empty from abnormal" {    const alloc = std.testing.allocator;    var settled: Runtime = .{};    try std.testing.expectEqual(        DeinitDisposition.settled,        settled.deinitSettled(alloc),    );     var runtime: Runtime = .{};    runtime.pending[0] = .{        .correlation_id = .{ .value = 9 },        .command = try alloc.dupe(u8, "unsettled"),        .starting_pending = false,    };    runtime.len = 1;     try std.testing.expectEqual(        DeinitDisposition.abnormal,        runtime.deinitSettled(alloc),    );    try std.testing.expect(!runtime.hasAcceptedPending());}