PranitInitial commit439f83c3 months agoHistory
3376 lines · 120.7 KB · Zig
RawDownload
const std = @import("std");const builtin = @import("builtin");const diff_mod = @import("../output/diff.zig");const file_mutation_contract = @import("file_mutation_contract.zig");const io_mod = @import("../shared/io.zig");const pathing = @import("../workspace/pathing.zig");const text_utils = @import("../shared/text_utils.zig");const types = @import("../shared/types.zig"); const testing_permissions = if (builtin.is_test)    @import("../permissions/permissions.zig")else    struct {};const testing_builtin_tools = if (builtin.is_test)    @import("../../builtins/tools.zig")else    struct {};const testing_write_file = if (builtin.is_test)    @import("../../tools/filesystem/write_file.zig")else    struct {};const testing_edit_file = if (builtin.is_test)    @import("../../tools/filesystem/edit_file.zig")else    struct {}; const Allocator = std.mem.Allocator;const PreparedFileMutation = file_mutation_contract.PreparedFileMutation;const PolicyEvaluatedFileTargets = file_mutation_contract.PolicyEvaluatedFileTargets; const max_content_bytes: usize = 4 * 1024 * 1024;const max_total_content_bytes: usize = 8 * 1024 * 1024; pub const ParentCleanupResidueReason = enum {    not_empty,    identity_changed,    remove_failed,}; pub const CreatedParentResidue = struct {    component_index: usize,    path_end: usize,    permission_target_index: usize,    created_identity: file_mutation_contract.FileIdentity,    observed_identity: ?file_mutation_contract.FileIdentity,    reason: ParentCleanupResidueReason,}; pub const ApplyRejectionReason = enum {    binding_mismatch,    stale_preimage,    cancelled,    traversal_changed,    staged_source_changed,    io_failure,}; pub const ApplyRejection = struct {    reason: ApplyRejectionReason,    created_parent_residue: []const CreatedParentResidue,}; pub const ChangeTrackerHandoff = struct {    kind: file_mutation_contract.Kind,    raw_path: []const u8,    previous_content: ?[]const u8,    committed_at_ms: i64,}; pub const FullViewSnapshot = struct {    after_content: []const u8,    lifecycle_id: types.ToolLifecycleId,}; pub const CommittedFileHandoff = struct {    preview: diff_mod.FileChangePreview,    tracker: ChangeTrackerHandoff,    full_view: ?FullViewSnapshot = null,     pub fn init(        preview: diff_mod.FileChangePreview,        tracker: ChangeTrackerHandoff,    ) CommittedFileHandoff {        return .{            .preview = preview,            .tracker = tracker,        };    }     pub fn deinit(self: CommittedFileHandoff, alloc: Allocator) void {        alloc.free(@constCast(self.preview.path));        for (self.preview.lines) |line| alloc.free(@constCast(line.text));        alloc.free(@constCast(self.preview.lines));        alloc.free(@constCast(self.tracker.raw_path));        if (self.tracker.previous_content) |content| {            alloc.free(@constCast(content));        }        if (self.full_view) |full_view| {            alloc.free(@constCast(full_view.after_content));            alloc.free(@constCast(full_view.lifecycle_id.call_id));        }    }     pub fn attachFullView(        self: *CommittedFileHandoff,        alloc: Allocator,        after_content: []const u8,        lifecycle_id: types.ToolLifecycleId,    ) Allocator.Error!void {        const owned_after_content = try alloc.dupe(u8, after_content);        errdefer alloc.free(owned_after_content);        const owned_call_id = try alloc.dupe(u8, lifecycle_id.call_id);        errdefer alloc.free(owned_call_id);         self.full_view = .{            .after_content = owned_after_content,            .lifecycle_id = .{                .turn_id = lifecycle_id.turn_id,                .call_id = owned_call_id,            },        };    }}; pub const ApplyResult = union(enum) {    rejected: ApplyRejection,    committed: CommittedFileHandoff,}; const target_mismatch_message =    "file mutation preparation failed: approved target no longer matches the call";const identity_changed_message =    "file mutation preparation failed: approved filesystem identity changed";const preview_too_large_message =    "file mutation preparation failed: diff preview exceeds preparation limits";const total_content_too_large_message =    "file mutation preparation failed: content proof exceeds the 8 MiB preparation limit"; const FileMutationPrepareError = error{    OutOfMemory,    Canceled,    Unexpected,    SystemResources,    ProcessFdQuotaExceeded,    SystemFdQuotaExceeded,}; const FileMutationApplyError = error{    OutOfMemory,    ResultContractViolation,}; const PreimageResult = union(enum) {    preimage: file_mutation_contract.Preimage,    semantic_failure: []const u8,}; const PostimageResult = union(enum) {    content: []const u8,    semantic_failure: []const u8,}; const PreviewResult = union(enum) {    preview: diff_mod.FileChangePreview,    semantic_failure: []const u8,}; const CommitClaim = struct {    claimed: std.atomic.Value(bool) = .init(false),}; var target_resolution_check_count: usize = 0; pub fn resetTargetResolutionCheckCountForTest() void {    if (builtin.is_test) target_resolution_check_count = 0;} pub fn targetResolutionCheckCountForTest() usize {    return if (builtin.is_test) target_resolution_check_count else 0;} pub fn prepare(    call_alloc: std.mem.Allocator,    call: types.ToolCall,    input: file_mutation_contract.FileMutationInput,    policy_targets: file_mutation_contract.PolicyEvaluatedFileTargets,) FileMutationPrepareError!file_mutation_contract.PrepareResult {    return prepareInternal(call_alloc, call, input, policy_targets, true);} /// Prepares a mutation whose decoded input and canonical target proof were/// produced together by tool admission. Identity, proof, preimage, and diff/// validation remain unchanged; only duplicate canonical resolution is skipped.pub fn prepareResolvedTarget(    call_alloc: std.mem.Allocator,    call: types.ToolCall,    input: file_mutation_contract.FileMutationInput,    policy_targets: file_mutation_contract.PolicyEvaluatedFileTargets,) FileMutationPrepareError!file_mutation_contract.PrepareResult {    return prepareInternal(call_alloc, call, input, policy_targets, false);} fn prepareInternal(    call_alloc: std.mem.Allocator,    call: types.ToolCall,    input: file_mutation_contract.FileMutationInput,    policy_targets: file_mutation_contract.PolicyEvaluatedFileTargets,    verify_target_resolution: bool,) FileMutationPrepareError!file_mutation_contract.PrepareResult {    if (policyIdentityBindingMismatch(policy_targets)) {        return .{ .semantic_failure = identity_changed_message };    }    if (!policy_targets.proofValid()) {        return .{ .semantic_failure = target_mismatch_message };    }    if (verify_target_resolution and !try resolvedTargetMatches(input, policy_targets)) {        return .{ .semantic_failure = target_mismatch_message };    }     const preimage_result = try verifyAndReadPreimage(call_alloc, input, policy_targets);    const preimage = switch (preimage_result) {        .preimage => |preimage| preimage,        .semantic_failure => |message| return .{ .semantic_failure = message },    };     const postimage_result = try derivePostimage(call_alloc, input, preimage);    const after_content = switch (postimage_result) {        .content => |content| content,        .semantic_failure => |message| return .{ .semantic_failure = message },    };    const before_content = switch (preimage) {        .absent => "",        .present => |present| present.content,    };    const total_content_bytes = std.math.add(        usize,        before_content.len,        after_content.len,    ) catch return .{ .semantic_failure = total_content_too_large_message };    if (total_content_bytes > max_total_content_bytes) {        return .{ .semantic_failure = total_content_too_large_message };    }     // External anchors show the resolved target so a symlink redirect cannot look local.    const approval_path = switch (policy_targets.anchor.scope) {        .external => policy_targets.canonical_target_path,        .workspace => input.path(),    };    const encoded_path = text_utils.encodeTerminalSafePathTail(        call_alloc,        approval_path,        diff_mod.max_encoded_path_bytes,    ) catch |err| switch (err) {        error.OutOfMemory => return error.OutOfMemory,        error.PathBasenameTooLong => return .{            .semantic_failure = preview_too_large_message,        },    };    const preview_result = try buildPreview(        call_alloc,        encoded_path,        preimage,        before_content,        after_content,    );    const preview = switch (preview_result) {        .preview => |preview| preview,        .semantic_failure => |message| return .{ .semantic_failure = message },    };    const review = diff_mod.FileReview.init(        call_alloc,        before_content,        after_content,    ) catch |err| switch (err) {        error.OutOfMemory => return error.OutOfMemory,        error.ArithmeticOverflow => return .{            .semantic_failure = preview_too_large_message,        },    };     var arguments_hasher = std.crypto.hash.sha2.Sha256.init(.{});    arguments_hasher.update(call.arguments_json);    const commit_claim = try call_alloc.create(CommitClaim);    commit_claim.* = .{};    return .{ .prepared = .{        .call_id = call.id,        .tool_name = call.name,        .arguments_hash = arguments_hasher.finalResult(),        .target_path = policy_targets.canonical_target_path,        .display_path = encoded_path.bytes,        .policy_targets = policy_targets,        .kind = std.meta.activeTag(input),        .preimage = preimage,        .after_content = after_content,        .preview = preview,        .review = review,        .commit_token = commit_claim,    } };} const ApplyTestControls = struct {    ctx: ?*anyopaque = null,    after_write_chunk: ?*const fn (?*anyopaque, usize) anyerror!void = null,    after_stage: ?*const fn (?*anyopaque, std.Io.Dir, []const u8) anyerror!void = null,    after_final_preimage_read: ?*const fn (?*anyopaque, std.Io.Dir, []const u8) anyerror!void = null,    after_final_validation: ?*const fn (?*anyopaque) anyerror!void = null,}; const CreatedParentRecord = struct {    component_index: usize,    path_end: usize,    permission_target_index: usize,    identity: file_mutation_contract.FileIdentity,}; const PreimageValidation = union(enum) {    valid: ?std.Io.File.Permissions,    stale,    io_failure,}; const TraversalValidation = union(enum) {    parent: std.Io.Dir,    changed,    io_failure,}; const StagedSourceValidation = enum {    valid,    changed,    io_failure,}; const TargetEntryValidation = enum {    valid,    changed,    io_failure,}; const StagedWriteError = error{    Canceled,    IoFailure,}; const ApplyResources = struct {    policy: PolicyEvaluatedFileTargets,    created: *[file_mutation_contract.max_file_target_components]CreatedParentRecord,    realized: *[file_mutation_contract.max_file_target_components]file_mutation_contract.FileIdentity,    residue_storage: []CreatedParentResidue,    created_count: usize = 0,    parent: ?std.Io.Dir = null,    temp_name: ?[]const u8 = null,    temp_identity: ?file_mutation_contract.FileIdentity = null,     fn closeParent(self: *ApplyResources) void {        if (self.parent) |parent| parent.close(io_mod.getIo());        self.parent = null;    }     fn reject(        self: *ApplyResources,        reason: ApplyRejectionReason,    ) ApplyResult {        self.cleanupTemp();        self.closeParent();        const residues = self.cleanupCreatedParents();        return .{ .rejected = .{            .reason = reason,            .created_parent_residue = residues,        } };    }     fn cleanupTemp(self: *ApplyResources) void {        const parent = self.parent orelse return;        const temp_name = self.temp_name orelse return;        const expected_identity = self.temp_identity orelse return;        const actual_identity = observedFileIdentity(parent, temp_name) orelse return;        if (!identityEql(actual_identity, expected_identity)) return;        parent.deleteFile(io_mod.getIo(), temp_name) catch return;        self.temp_name = null;        self.temp_identity = null;    }     fn cleanupCreatedParents(        self: *ApplyResources,    ) []const CreatedParentResidue {        var residue_count: usize = 0;        var index = self.created_count;        while (index > 0) {            index -= 1;            const created = self.created[index];            if (cleanupCreatedParent(self.policy, self.realized, created)) |residue| {                self.residue_storage[residue_count] = residue;                residue_count += 1;            }        }        return self.residue_storage[0..residue_count];    }}; pub fn apply(    call_alloc: std.mem.Allocator,    result_alloc: std.mem.Allocator,    call: types.ToolCall,    authorization: file_mutation_contract.FileExecutionAuthorization,    cancel_flag: *std.atomic.Value(bool),) FileMutationApplyError!ApplyResult {    return applyWithTestControls(        call_alloc,        result_alloc,        call,        authorization,        cancel_flag,        .{},    );} fn applyWithTestControls(    call_alloc: std.mem.Allocator,    result_alloc: std.mem.Allocator,    call: types.ToolCall,    authorization: file_mutation_contract.FileExecutionAuthorization,    cancel_flag: *std.atomic.Value(bool),    test_controls: ApplyTestControls,) FileMutationApplyError!ApplyResult {    const prepared = authorization.prepared orelse {        return rejectedWithoutResidue(.binding_mismatch);    };    if (!applyBindingValid(call, authorization.policy_targets, prepared)) {        return rejectedWithoutResidue(.binding_mismatch);    }    const commit_claim = preparedCommitClaim(prepared);    if (commit_claim.claimed.load(.seq_cst)) {        return error.ResultContractViolation;    }    if (cancel_flag.load(.seq_cst)) return rejectedWithoutResidue(.cancelled);     const residue_storage = try call_alloc.alloc(        CreatedParentResidue,        prepared.policy_targets.traversal_directories.len,    );    const previous_content = switch (prepared.preimage) {        .absent => null,        .present => |present| present.content,    };    var handoff = try dupeCommittedFileHandoff(        result_alloc,        prepared.preview,        prepared.kind,        prepared.target_path,        previous_content,    );    var handoff_owned = true;    defer if (handoff_owned) handoff.deinit(result_alloc);     var created: [file_mutation_contract.max_file_target_components]CreatedParentRecord =        undefined;    var realized: [file_mutation_contract.max_file_target_components]file_mutation_contract.FileIdentity =        undefined;    var resources: ApplyResources = .{        .policy = prepared.policy_targets,        .created = &created,        .realized = &realized,        .residue_storage = residue_storage,    };    defer resources.closeParent();     io_mod.e2eFailIfDurableMutationAttempted();    if (realizeTraversal(&resources)) |reason| return resources.reject(reason);    const parent = resources.parent.?;    const target_name = prepared.policy_targets.component(        prepared.policy_targets.anchor.relative_components[            prepared.policy_targets.anchor.relative_components.len - 1        ],    );     const initial_preimage = validatePreimage(        parent,        target_name,        prepared,        null,        null,        null,    );    const destination_permissions = switch (initial_preimage) {        .valid => |permissions| permissions,        .stale => return resources.reject(.stale_preimage),        .io_failure => return resources.reject(.io_failure),    };     var random_bytes: [16]u8 = undefined;    io_mod.getIo().random(&random_bytes);    const suffix = std.fmt.bytesToHex(random_bytes, .lower);    var temp_name_buffer: [".fx-stage-".len + suffix.len]u8 = undefined;    @memcpy(temp_name_buffer[0..".fx-stage-".len], ".fx-stage-");    @memcpy(temp_name_buffer[".fx-stage-".len..], suffix[0..]);    const temp_name = temp_name_buffer[0..];     var stage = parent.createFile(io_mod.getIo(), temp_name, .{        .read = true,

Lines 1–500 of 3376.