tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

io_v2_file_host.zig (51825B)


      1 //! Synchronous native compiler/file profile, shared by the CLI and private
      2 //! integration probes. Explicit capabilities; no standard streams or broad OS parity.
      3 const std = @import("std");
      4 const Protocol = @import("io_v2_protocol.zig");
      5 const Core = @import("io_v2_core.zig");
      6 const F = @import("io_v2_files.zig");
      7 const Paths = @import("io_v2_paths.zig");
      8 const UTF8 = @import("io_v2_utf8.zig");
      9 const Stage = @import("io_v2_stage.zig");
     10 const Digest = @import("io_v2_digest.zig").Digest;
     11 const Snapshot = @import("io_v2_snapshot.zig");
     12 const Indexed = @import("indexed.zig");
     13 const C = @import("codecs.zig");
     14 const Text = @import("source_text.zig");
     15 pub const Host = struct {
     16     // Fixed-size host diagnostics remain inspectable even when a reducer/allocator
     17     // failure prevents producing a tree Result. No allocation or dangling node IDs.
     18     algorithm_detail: [512]u8 = undefined,
     19     algorithm_detail_len: usize = 0,
     20     utf8_detail: [96]u8 = undefined,
     21     utf8_detail_len: usize = 0,
     22     snapshot_cleanup_failed: bool = false,
     23     stage_operation: bool = false,
     24     stage_status: Stage.Status = .{},
     25     published: usize = 0,
     26     opened: usize = 0,
     27     closed: usize = 0,
     28     close_errors: [16]F.Status = undefined,
     29     close_error_count: usize = 0,
     30     close_errors_omitted: usize = 0,
     31     fn recordCloseError(self: *Host, status: F.Status) void {
     32         if (self.close_error_count < self.close_errors.len) {
     33             self.close_errors[self.close_error_count] = status;
     34             self.close_error_count += 1;
     35         } else self.close_errors_omitted += 1;
     36     }
     37     pub const Backend = union(enum) { placeholder, file: *F.File, digest: *Digest, stage: *Stage.Stage };
     38     pub const standard = [3]Backend{ .placeholder, .placeholder, .placeholder };
     39     fn fileBackend(file: *F.File) Backend {
     40         return .{ .file = file };
     41     }
     42     pub fn close(self: *Host, registry: anytype, backend: Backend) !void {
     43         var status: F.Status = .{};
     44         return self.closeWithStatus(registry, backend, &status);
     45     }
     46     fn closeWithStatus(self: *Host, registry: anytype, backend: Backend, status: *F.Status) !void {
     47         defer self.closed += 1;
     48         switch (backend) {
     49             .placeholder => unreachable,
     50             .digest => |digest| registry.allocator.destroy(digest),
     51             .file => |file| {
     52                 defer registry.allocator.destroy(file);
     53                 file.close(status) catch |err| {
     54                     self.recordCloseError(status.*);
     55                     return err;
     56                 };
     57             },
     58             .stage => |stage| {
     59                 defer registry.allocator.destroy(stage);
     60                 defer stage.deinit();
     61                 var released: Stage.Status = .{};
     62                 stage.release(&released) catch |err| {
     63                     status.* = .{ .code = released.code, .cleanup_code = released.secondary };
     64                     self.recordCloseError(status.*);
     65                     return err;
     66                 };
     67             },
     68         }
     69     }
     70     const Op = enum { open_reader, open_writer, open_appender, open_update, read, write, close, seek, position, size, resize, sync, stat, lstat, create_directory, remove_directory, remove_file, rename_path, sync_directory, open_digest, update_digest, finish_digest, open_stage, seal_stage, publish_stage, encode_utf8, decode_utf8, hash_indexed, write_indexed, verify_indexed, read_indexed, capabilities, inspect };
     71     const entries = .{
     72         .{ "runtime", "capabilities", Op.capabilities }, .{ "runtime", "inspect", Op.inspect },
     73         .{ "indexed-tree", "read", Op.read_indexed },    .{ "indexed-tree", "hash", Op.hash_indexed },
     74         .{ "indexed-tree", "write", Op.write_indexed },  .{ "indexed-tree", "verify", Op.verify_indexed },
     75         .{ "utf8", "encode", Op.encode_utf8 },           .{ "utf8", "decode", Op.decode_utf8 },
     76         .{ "stage", "open", Op.open_stage },             .{ "stage", "seal", Op.seal_stage },
     77         .{ "stage", "publish", Op.publish_stage },       .{ "digest", "open", Op.open_digest },
     78         .{ "digest", "update", Op.update_digest },       .{ "digest", "finish", Op.finish_digest },
     79         .{ "path", "stat", Op.stat },                    .{ "path", "lstat", Op.lstat },
     80         .{ "path", "remove-file", Op.remove_file },      .{ "path", "rename", Op.rename_path },
     81         .{ "directory", "create", Op.create_directory }, .{ "directory", "remove", Op.remove_directory },
     82         .{ "directory", "sync", Op.sync_directory },     .{ "file", "open-reader", Op.open_reader },
     83         .{ "file", "open-writer", Op.open_writer },      .{ "file", "open-appender", Op.open_appender },
     84         .{ "file", "open-update", Op.open_update },      .{ "stream", "read", Op.read },
     85         .{ "stream", "write", Op.write },                .{ "resource", "close", Op.close },
     86         .{ "file", "seek", Op.seek },                    .{ "file", "position", Op.position },
     87         .{ "file", "size", Op.size },                    .{ "file", "resize", Op.resize },
     88         .{ "file", "sync", Op.sync },
     89     };
     90     fn resolve(group: []const u8, operation: []const u8) ?Op {
     91         inline for (entries) |entry| if (std.mem.eql(u8, group, entry[0]) and std.mem.eql(u8, operation, entry[1])) return entry[2];
     92         return null;
     93     }
     94     pub fn request(self: *Host, engine: anytype, scope: u64, req: anytype) !u32 {
     95         if (req.revision != 1) return Core.unsupported(engine, req);
     96         const op = resolve(req.group.slice(), req.operation.slice()) orelse return Core.unsupported(engine, req);
     97         var name_buffer: [129]u8 = undefined;
     98         const name = try std.fmt.bufPrint(&name_buffer, "{s}/{s}", .{ req.group.slice(), req.operation.slice() });
     99         var status: F.Status = .{};
    100         self.stage_operation = false;
    101         self.snapshot_cleanup_failed = false;
    102         self.stage_status = .{};
    103         const value = self.dispatch(engine, scope, op, req.arguments, &status) catch |err| {
    104             if (err == error.InvalidAlgorithm) return engine.result(false, try engine.list(&.{
    105                 try engine.text("unsupported"),                                                       try engine.text(name),
    106                 try Text.fromUtf8(engine.arena, self.algorithm_detail[0..self.algorithm_detail_len]), try engine.text("none"),
    107             }));
    108             if (self.stage_operation) status.code = self.stage_status.code;
    109             const category = switch (err) {
    110                 error.InvalidArgument, error.EmptySnapshotDirectory => "invalid-argument",
    111                 error.InvalidData, error.SnapshotTruncated, error.SnapshotInvalid => "invalid-data",
    112                 error.SnapshotBound => "invalid-bound",
    113                 error.SnapshotDigestMismatch => "integrity-failed",
    114                 error.SnapshotCleanup => "cleanup-failed",
    115                 error.InvalidState => "invalid-state",
    116                 error.InvalidDestination => "invalid-destination",
    117                 error.InvalidBound, error.EncodedLimit => "invalid-bound",
    118                 error.DigestMismatch, error.FrameMismatch => "integrity-failed",
    119                 error.UnsupportedFileType, error.UnsupportedMode => "unsupported",
    120                 error.WrongAccess => "wrong-kind",
    121                 error.NotDirectory => "not-directory",
    122                 error.Closed, error.ClosedHandle => "closed-handle",
    123                 error.StaleHandle => "stale-handle",
    124                 error.OutOfScope => "out-of-scope",
    125                 error.Busy => "busy",
    126                 error.ClosedScope => "closed-scope",
    127                 error.BorrowedResource => "borrowed-resource",
    128                 error.InternalResource => "internal-resource",
    129                 error.HostFailure => F.errorCategory(status.code),
    130                 else => return err,
    131             };
    132             const detail = if (err == error.EmptySnapshotDirectory) "empty snapshot directory" else if (err == error.SnapshotBound) "invalid snapshot size limit" else if (err == error.SnapshotTruncated) "incomplete indexed frame" else if (err == error.SnapshotInvalid) "invalid indexed frame" else if (err == error.SnapshotDigestMismatch) "CAS digest mismatch" else if (err == error.SnapshotCleanup) "snapshot release failed" else if (err == error.EncodedLimit) "encoded tree exceeds limit" else if (err == error.DigestMismatch) "CAS digest mismatch" else if (err == error.FrameMismatch) "indexed frame differs from canonical tree" else if (err == error.InvalidData) self.utf8_detail[0..self.utf8_detail_len] else if (err == error.HostFailure) F.errorText(status.code) else if (err == error.UnsupportedFileType) "path-based files support regular files only" else if (err == error.UnsupportedMode) "operation unavailable in this file mode" else if (err == error.NotDirectory) "path is not a directory" else if (err == error.InvalidDestination) "destination must name a file outside the private staging namespace" else if (err == error.InvalidState) (if (self.stage_operation) "stage is not in the required phase" else "digest already finished") else "";
    133             // Mutating opens can create/truncate before a later failure. Never
    134             // claim absence of effects; this is not transactional rollback.
    135             const progress = if (err == error.SnapshotTruncated or err == error.SnapshotInvalid or err == error.SnapshotDigestMismatch) "unknown" else if (err == error.FrameMismatch) "unknown" else if (self.stage_operation) switch (self.stage_status.progress) {
    136                 .none => "none",
    137                 .not_published => "not-published",
    138                 .unknown => "unknown",
    139                 .published => "published",
    140             } else if (err == error.HostFailure and op != .open_reader and op != .size and op != .position and op != .stat and op != .lstat) "unknown" else "none";
    141             var detail_buffer: [1024]u8 = undefined;
    142             const reported = if (self.snapshot_cleanup_failed) try std.fmt.bufPrint(&detail_buffer, "{s}; cleanup: snapshot release failed", .{detail}) else detail;
    143             return engine.result(false, try engine.list(&.{ try engine.text(category), try engine.text(name), try engine.text(reported), try engine.text(progress) }));
    144         };
    145         return engine.result(true, value);
    146     }
    147     fn decodeText(engine: anytype, input: u32) ![]u8 {
    148         var output: std.ArrayList(u8) = .empty;
    149         errdefer output.deinit(engine.arena.allocator);
    150         var rest = input;
    151         const reader = engine.reader();
    152         while (try reader.node(rest) != .leaf) {
    153             const cell = try reader.pair(rest) orelse return error.InvalidArgument;
    154             const scalar = try reader.nat(cell.first, 21) orelse return error.InvalidArgument;
    155             if (scalar > 0x10ffff or (scalar >= 0xd800 and scalar <= 0xdfff)) return error.InvalidArgument;
    156             var encoded: [4]u8 = undefined;
    157             const count = try std.unicode.utf8Encode(@intCast(scalar), &encoded);
    158             try output.appendSlice(engine.arena.allocator, encoded[0..count]);
    159             rest = cell.second;
    160         }
    161         return output.toOwnedSlice(engine.arena.allocator);
    162     }
    163     fn decodeChunk(engine: anytype, input: u32, bytes: *[4096]u8) !usize {
    164         var count: usize = 0;
    165         var rest = input;
    166         const reader = engine.reader();
    167         while (try reader.node(rest) != .leaf) {
    168             // Match the protocol decoder (invalid argument), not the lower-level
    169             // byte-IO bound check. Observe excess spine, never excess head/tail.
    170             if (count == bytes.len) return error.InvalidArgument;
    171             const cell = try reader.pair(rest) orelse return error.InvalidArgument;
    172             bytes[count] = @intCast(try reader.nat(cell.first, 8) orelse return error.InvalidArgument);
    173             count += 1;
    174             rest = cell.second;
    175         }
    176         return count;
    177     }
    178     fn readIndexed(self: *Host, engine: anytype, scope: u64, arguments: u32, status: *F.Status) !u32 {
    179         const r = engine.reader();
    180         var fields: [5]u32 = undefined;
    181         var rest = arguments;
    182         for (&fields) |*field| {
    183             const cell = try r.pair(rest) orelse return error.InvalidArgument;
    184             field.* = cell.first;
    185             rest = cell.second;
    186         }
    187         if (try r.node(rest) != .leaf) return error.InvalidArgument;
    188         const token = try engine.token(fields[0], "io.v2.resource") orelse return error.InvalidArgument;
    189         const directory = try decodeText(engine, fields[1]);
    190         defer engine.arena.allocator.free(directory);
    191         const path = try engine.arena.allocator.dupeZ(u8, directory);
    192         defer engine.arena.allocator.free(path);
    193         const limit = try r.nat(fields[2], 63) orelse return error.InvalidArgument;
    194         const size = try r.nat(fields[3], 63) orelse return error.InvalidArgument;
    195         var digest: [64]u8 = undefined;
    196         rest = fields[4];
    197         for (&digest) |*byte| {
    198             const cell = try r.pair(rest) orelse return error.InvalidArgument;
    199             const scalar = try r.nat(cell.first, 7) orelse return error.InvalidArgument;
    200             if (!((scalar >= '0' and scalar <= '9') or (scalar >= 'a' and scalar <= 'f'))) return error.InvalidArgument;
    201             byte.* = @intCast(scalar);
    202             rest = cell.second;
    203         }
    204         if (try r.node(rest) != .leaf) return error.InvalidArgument;
    205         if (size > limit) return error.SnapshotBound;
    206         if (directory.len == 0) return error.EmptySnapshotDirectory;
    207         const registry = engine.scheduler.registry;
    208         const backend = (try registry.lookup(scope, token)).backend;
    209         if (backend != .file or (backend.file.mode != .reader and backend.file.mode != .update)) return error.WrongAccess;
    210         const lease = try registry.beginLease(scope, token, .exclusive, false);
    211         defer registry.endLease(lease.token) catch unreachable;
    212         const temporary = blk: {
    213             const admission = try registry.beginAcquire(scope);
    214             errdefer registry.abortAcquire(admission) catch unreachable;
    215             const file = try registry.allocator.create(F.File);
    216             errdefer registry.allocator.destroy(file);
    217             file.* = Snapshot.create(registry.allocator, status, path) catch |err| {
    218                 if (status.cleanup_code != 0) self.recordCloseError(.{ .code = status.cleanup_code });
    219                 return err;
    220             };
    221             errdefer {
    222                 var cleanup: F.Status = .{};
    223                 file.close(&cleanup) catch self.recordCloseError(cleanup);
    224             }
    225             break :blk try registry.publish(admission, .{ .file = file }, .{ .internal = true });
    226         };
    227         self.opened += 1;
    228         const file = (try registry.lookup(scope, temporary)).backend.file;
    229         const result = blk: {
    230             Snapshot.authenticate(file, backend.file, status, size, &digest) catch |err| break :blk @as(anyerror!u32, err);
    231             var snapshot = Snapshot.Snapshot{ .file = file, .status = status, .length = size };
    232             break :blk Indexed.loadSnapshot(engine.arena, &snapshot) catch |err| switch (err) {
    233                 error.InvalidData, error.ExportNotFound => error.SnapshotInvalid,
    234                 else => err,
    235             };
    236         };
    237         // All host work is synchronous. Retire the internal descriptor even on
    238         // ordinary Result failure or OOM; cleanup must not replace a primary fault.
    239         const owned = (try registry.claimClose(scope, temporary, true)).?;
    240         defer registry.finishClose(temporary) catch unreachable;
    241         var cleanup: F.Status = .{};
    242         self.closeWithStatus(registry, owned.backend, &cleanup) catch {
    243             if (result) |_| return error.SnapshotCleanup else |primary| {
    244                 self.snapshot_cleanup_failed = true;
    245                 return primary;
    246             }
    247         };
    248         return result;
    249     }
    250     fn indexed(self: *Host, engine: anytype, scope: u64, op: Op, arguments: u32, status: *F.Status) !u32 {
    251         const r = engine.reader();
    252         const first = try r.pair(arguments) orelse return error.InvalidArgument;
    253         if (op == .hash_indexed) {
    254             const limit = try r.nat(first.first, 63) orelse return error.InvalidArgument;
    255             var plan = try Indexed.prepare(engine.arena, first.second);
    256             defer plan.deinit();
    257             if (plan.encodedLength() > limit) return error.EncodedLimit;
    258             const hash = try plan.hash();
    259             return engine.list(&.{ try C.ofNumber(engine.arena, plan.encodedLength()), try engine.text(&hash) });
    260         }
    261         const token = try engine.token(first.first, "io.v2.resource") orelse return error.InvalidArgument;
    262         const second = try r.pair(first.second) orelse return error.InvalidArgument;
    263         const limit = try r.nat(second.first, 63) orelse return error.InvalidArgument;
    264         const third = try r.pair(second.second) orelse return error.InvalidArgument;
    265         const tree_field = if (op == .write_indexed) try r.pair(third.second) orelse return error.InvalidArgument else third;
    266         if (try r.node(tree_field.second) != .leaf) return error.InvalidArgument;
    267         var expected: [64]u8 = undefined;
    268         if (op == .write_indexed) {
    269             var rest = third.first;
    270             for (&expected) |*byte| {
    271                 const cell = try r.pair(rest) orelse return error.InvalidArgument;
    272                 const scalar = try r.nat(cell.first, 7) orelse return error.InvalidArgument;
    273                 if (!((scalar >= '0' and scalar <= '9') or (scalar >= 'a' and scalar <= 'f'))) return error.InvalidArgument;
    274                 byte.* = @intCast(scalar);
    275                 rest = cell.second;
    276             }
    277             if (try r.node(rest) != .leaf) return error.InvalidArgument;
    278         }
    279         const registry = engine.scheduler.registry;
    280         const backend = (try registry.lookup(scope, token)).backend;
    281         if (op == .verify_indexed) {
    282             if (backend != .file or (backend.file.mode != .reader and backend.file.mode != .update)) return error.WrongAccess;
    283         } else switch (backend) {
    284             .file => |file| if (file.mode == .reader) return error.WrongAccess,
    285             .stage => {},
    286             else => return error.WrongAccess,
    287         }
    288         const lease = try registry.beginLease(scope, token, .exclusive, false);
    289         defer registry.endLease(lease.token) catch unreachable;
    290         var plan = try Indexed.prepare(engine.arena, tree_field.first);
    291         defer plan.deinit();
    292         if (plan.encodedLength() > limit) return error.EncodedLimit;
    293         const hash = try plan.hash();
    294         if (op == .write_indexed and !std.mem.eql(u8, &hash, &expected)) return error.DigestMismatch;
    295         var sink = IndexedSink{ .host = self, .backend = backend, .status = status, .verify = op == .verify_indexed };
    296         try plan.emit(&sink);
    297         return engine.list(&.{ try C.ofNumber(engine.arena, plan.encodedLength()), try engine.text(&hash) });
    298     }
    299     const IndexedSink = struct {
    300         host: *Host,
    301         backend: Backend,
    302         status: *F.Status,
    303         verify: bool,
    304         pub fn writeAll(self: *@This(), bytes: []const u8) !void {
    305             var offset: usize = 0;
    306             while (offset < bytes.len) {
    307                 const count = if (self.verify) blk: {
    308                     var actual: [4096]u8 = undefined;
    309                     const n = try self.backend.file.read(self.status, actual[0 .. bytes.len - offset]);
    310                     if (n == 0 or !std.mem.eql(u8, actual[0..n], bytes[offset..][0..n])) return error.FrameMismatch;
    311                     break :blk n;
    312                 } else switch (self.backend) {
    313                     .file => |file| try file.write(self.status, bytes[offset..]),
    314                     .stage => |stage| blk: {
    315                         self.host.stage_operation = true;
    316                         break :blk try stage.write(&self.host.stage_status, bytes[offset..]);
    317                     },
    318                     else => unreachable,
    319                 };
    320                 std.debug.assert(count > 0 and count <= bytes.len - offset);
    321                 offset += count;
    322             }
    323         }
    324     };
    325     const core_names = [_][]const u8{ "pure", "bind", "fail", "scope", "current-scope", "spawn", "join", "discard", "cancel", "cancelled" };
    326     fn strings(engine: anytype, values: []const []const u8) !u32 {
    327         var ids: std.ArrayList(u32) = .empty;
    328         defer ids.deinit(engine.arena.allocator);
    329         for (values) |value| try ids.append(engine.arena.allocator, try engine.text(value));
    330         return engine.list(ids.items);
    331     }
    332     fn row(engine: anytype, key: []const u8, value: u32) !u32 {
    333         return engine.list(&.{ try engine.text(key), value });
    334     }
    335     fn capabilities(engine: anytype) !u32 {
    336         var groups: std.ArrayList(u32) = .empty;
    337         defer groups.deinit(engine.arena.allocator);
    338         inline for (.{ "runtime", "resource", "stream", "file", "path", "directory", "stage", "digest", "indexed-tree", "utf8" }) |group| {
    339             var operations: std.ArrayList(u32) = .empty;
    340             defer operations.deinit(engine.arena.allocator);
    341             inline for (entries) |entry| {
    342                 if (comptime std.mem.eql(u8, group, entry[0])) try operations.append(engine.arena.allocator, try engine.text(entry[1]));
    343             }
    344             try groups.append(engine.arena.allocator, try engine.list(&.{ try engine.text(group), try C.ofNumber(engine.arena, 1), try engine.list(operations.items) }));
    345         }
    346         const limits = try engine.list(&.{
    347             try row(engine, "chunk-bytes", try C.ofNumber(engine.arena, 4096)),
    348             try row(engine, "transfer-handles", try C.ofNumber(engine.arena, 256)),
    349             try row(engine, "file-offset-max", try C.ofNumber(engine.arena, F.limit)),
    350             try row(engine, "indexed-sections", try C.ofNumber(engine.arena, 64)),
    351             try row(engine, "indexed-manifest-bytes", try C.ofNumber(engine.arena, 65536)),
    352             try row(engine, "utf8-encode-scalars", try C.ofNumber(engine.arena, 1024)),
    353         });
    354         return engine.list(&.{
    355             try row(engine, "schema", try C.ofNumber(engine.arena, 1)),
    356             try row(engine, "io-version", try C.ofNumber(engine.arena, 2)),
    357             try row(engine, "backend", try engine.text("zig-native")),
    358             try row(engine, "authority", try engine.text("os-process")),
    359             try row(engine, "core", try strings(engine, &core_names)),
    360             try row(engine, "groups", try engine.list(groups.items)),
    361             try row(engine, "limits", limits),
    362             try row(engine, "digest-algorithms", try strings(engine, &.{"sha256"})),
    363             try row(engine, "restrictions", try strings(engine, &.{
    364                 "action-boundary-cancellation",    "no-hard-deadlines",              "synchronous-host-calls",
    365                 "path-files-regular-only",         "standard-streams-unsupported",   "directory-cursors-unsupported",
    366                 "file-single-cursor-lease",        "append-no-position-or-resize",   "stage-same-filesystem",
    367                 "stage-no-copy-fallback",          "stage-private-namespace",        "publish-new-requires-hard-links",
    368                 "no-crash-recovery",               "indexed-authenticated-snapshot", "indexed-caller-frame-and-disk-limit",
    369                 "indexed-dag-resident",            "indexed-writer-preflight",       "indexed-budget-not-heap-quota",
    370                 "sync-host-filesystem-guarantees", "utf8-strict-scalars",
    371             })),
    372         });
    373     }
    374     fn dispatch(self: *Host, engine: anytype, scope: u64, op: Op, arguments: u32, status: *F.Status) !u32 {
    375         const r = engine.reader();
    376         const registry = engine.scheduler.registry;
    377         if (op == .capabilities) {
    378             if (try r.node(arguments) != .leaf) return error.InvalidArgument;
    379             return capabilities(engine);
    380         }
    381         if (op == .inspect) {
    382             const action = Protocol.decode(engine.arena, arguments, r.fuel) catch |err| switch (err) {
    383                 error.InvalidComputationShape, error.UnknownComputationConstructor, error.InvalidFacilityHeader => return error.InvalidArgument,
    384                 else => return err,
    385             };
    386             return switch (action) {
    387                 .core => |value| engine.list(&.{ try engine.text("core"), try engine.text(core_names[@intFromEnum(value.code)]) }),
    388                 .request => |value| engine.list(&.{ try engine.text("facility"), try engine.text(value.group.slice()), try C.ofNumber(engine.arena, value.revision), try engine.text(value.operation.slice()), try engine.text(if (value.revision == 1 and resolve(value.group.slice(), value.operation.slice()) != null) "available" else "unsupported") }),
    389             };
    390         }
    391         if (op == .read_indexed) return self.readIndexed(engine, scope, arguments, status);
    392         if (op == .hash_indexed or op == .write_indexed or op == .verify_indexed) return self.indexed(engine, scope, op, arguments, status);
    393         if (op == .encode_utf8) {
    394             var bytes: [4096]u8 = undefined;
    395             var scalars: usize = 0;
    396             var used: usize = 0;
    397             var rest = arguments;
    398             while (try r.node(rest) != .leaf) {
    399                 if (scalars == 1024) return error.InvalidArgument;
    400                 const cell = try r.pair(rest) orelse return error.InvalidArgument;
    401                 const scalar = try r.nat(cell.first, 21) orelse return error.InvalidArgument;
    402                 if (scalar > 0x10ffff or (scalar >= 0xd800 and scalar <= 0xdfff)) return error.InvalidArgument;
    403                 used += try std.unicode.utf8Encode(@intCast(scalar), bytes[used..][0..4]);
    404                 scalars += 1;
    405                 rest = cell.second;
    406             }
    407             return C.ofBytes(engine.arena, bytes[0..used]);
    408         }
    409         if (op == .decode_utf8) {
    410             const final = try r.pair(arguments) orelse return error.InvalidArgument;
    411             const input = try r.pair(final.second) orelse return error.InvalidArgument;
    412             if (try r.node(input.second) != .leaf) return error.InvalidArgument;
    413             const ended = try r.nat(final.first, 1) orelse return error.InvalidArgument;
    414             var bytes: [4096]u8 = undefined;
    415             const count = try decodeChunk(engine, input.first, &bytes);
    416             return switch (UTF8.scan(bytes[0..count], ended == 1)) {
    417                 .too_large => unreachable, // argument decoder already bounds it
    418                 .fault => |fault| blk: {
    419                     const why: []const u8 = switch (fault.kind) {
    420                         .leading => "invalid UTF-8 leading byte",
    421                         .sequence => "invalid UTF-8 sequence",
    422                         .incomplete => "incomplete UTF-8 sequence",
    423                     };
    424                     const detail = try std.fmt.bufPrint(&self.utf8_detail, "{s} at byte {d}", .{ why, fault.offset });
    425                     self.utf8_detail_len = detail.len;
    426                     break :blk error.InvalidData;
    427                 },
    428                 .prefix => |prefix| try engine.list(&.{ try Text.fromUtf8(engine.arena, bytes[0..prefix]), try C.ofBytes(engine.arena, bytes[prefix..count]) }),
    429             };
    430         }
    431         switch (op) {
    432             .stat, .lstat, .create_directory, .remove_directory, .remove_file, .rename_path, .sync_directory => {
    433                 const paths = if (op == .rename_path) try r.pair(arguments) orelse return error.InvalidArgument else null;
    434                 const raw = try decodeText(engine, if (paths) |p| p.first else arguments);
    435                 defer engine.arena.allocator.free(raw);
    436                 const path = try engine.arena.allocator.dupeZ(u8, raw);
    437                 defer engine.arena.allocator.free(path);
    438                 if (op == .stat or op == .lstat) {
    439                     const info = try Paths.stat(status, path, op == .stat);
    440                     return switch (info) {
    441                         .regular => |size| try engine.list(&.{ try engine.text("file"), try C.ofNumber(engine.arena, size) }),
    442                         .directory => try engine.list(&.{try engine.text("directory")}),
    443                         .symlink => try engine.list(&.{try engine.text("symlink")}),
    444                         .other => try engine.list(&.{try engine.text("other")}),
    445                     };
    446                 }
    447                 if (op == .sync_directory) {
    448                     Paths.syncDirectory(status, path) catch |err| {
    449                         if (status.cleanup_code != 0) self.recordCloseError(.{ .code = status.cleanup_code });
    450                         return err;
    451                     };
    452                     return engine.leaf;
    453                 }
    454                 if (op == .rename_path) {
    455                     const other_raw = try decodeText(engine, paths.?.second);
    456                     defer engine.arena.allocator.free(other_raw);
    457                     const other = try engine.arena.allocator.dupeZ(u8, other_raw);
    458                     defer engine.arena.allocator.free(other);
    459                     try Paths.mutate(status, .rename, path, other);
    460                 } else {
    461                     try Paths.mutate(status, switch (op) {
    462                         .create_directory => .create_directory,
    463                         .remove_directory => .remove_directory,
    464                         .remove_file => .remove_file,
    465                         else => unreachable,
    466                     }, path, null);
    467                 }
    468                 return engine.leaf;
    469             },
    470             else => {},
    471         }
    472         switch (op) {
    473             .open_reader, .open_writer, .open_appender, .open_update, .open_digest, .open_stage => {
    474                 const pair = try r.pair(arguments) orelse return error.InvalidArgument;
    475                 const owner = try engine.token(pair.first, "io.v2.scope") orelse return error.InvalidArgument;
    476                 if (owner.run != registry.identity or !registry.within(scope, owner.key)) return error.InvalidArgument;
    477                 const text = try decodeText(engine, pair.second);
    478                 defer engine.arena.allocator.free(text);
    479                 const path = try engine.arena.allocator.dupeZ(u8, text);
    480                 defer engine.arena.allocator.free(path);
    481                 if (op == .open_digest) {
    482                     if (!std.mem.eql(u8, text, "sha256")) {
    483                         const view = try std.unicode.Utf8View.init(text);
    484                         var it = view.iterator();
    485                         var scalars: usize = 0;
    486                         while (scalars < 128 and it.nextCodepoint() != null) : (scalars += 1) {}
    487                         self.algorithm_detail_len = it.i;
    488                         @memcpy(self.algorithm_detail[0..it.i], text[0..it.i]);
    489                         return error.InvalidAlgorithm;
    490                     }
    491                     // End acquisition rollback BEFORE encoding the source token.
    492                     // Once published, only scope teardown owns this state, even
    493                     // when reply allocation fails immediately afterwards.
    494                     const token = acquired: {
    495                         const admission = try registry.beginAcquire(owner.key);
    496                         errdefer registry.abortAcquire(admission) catch unreachable;
    497                         const digest = try registry.allocator.create(Digest);
    498                         errdefer registry.allocator.destroy(digest);
    499                         digest.* = .{};
    500                         break :acquired try registry.publish(admission, .{ .digest = digest }, .{});
    501                     };
    502                     self.opened += 1;
    503                     return engine.marker("io.v2.resource", token.key);
    504                 }
    505                 if (op == .open_stage) {
    506                     self.stage_operation = true;
    507                     const token = acquired: {
    508                         const admission = try registry.beginAcquire(owner.key);
    509                         errdefer registry.abortAcquire(admission) catch unreachable;
    510                         const stage = try registry.allocator.create(Stage.Stage);
    511                         errdefer registry.allocator.destroy(stage);
    512                         stage.* = try Stage.Stage.init(registry.allocator, &self.stage_status, path);
    513                         errdefer {
    514                             var released: Stage.Status = .{};
    515                             stage.release(&released) catch self.recordCloseError(.{ .code = released.code, .cleanup_code = released.secondary });
    516                             stage.deinit();
    517                         }
    518                         break :acquired try registry.publish(admission, .{ .stage = stage }, .{});
    519                     };
    520                     self.opened += 1;
    521                     return engine.marker("io.v2.resource", token.key);
    522                 }
    523                 const mode: F.Mode = switch (op) {
    524                     .open_reader => .reader,
    525                     .open_writer => .writer,
    526                     .open_appender => .appender,
    527                     .open_update => .update,
    528                     else => unreachable,
    529                 };
    530                 const token = F.acquireWrapped(registry, owner.key, status, path, mode, fileBackend) catch |err| {
    531                     if (status.cleanup_code != 0) self.recordCloseError(.{ .code = status.cleanup_code });
    532                     return err;
    533                 };
    534                 self.opened += 1;
    535                 return engine.marker("io.v2.resource", token.key);
    536             },
    537             else => {},
    538         }
    539         const paired = op == .read or op == .write or op == .update_digest or op == .seek or op == .resize or op == .publish_stage;
    540         const pair = if (paired) try r.pair(arguments) orelse return error.InvalidArgument else null;
    541         const token = try engine.token(if (pair) |p| p.first else arguments, "io.v2.resource") orelse return error.InvalidArgument;
    542         var number: u64 = 0;
    543         var bytes: [4096]u8 = undefined;
    544         var count: usize = 0;
    545         switch (op) {
    546             .read, .seek, .resize => {
    547                 number = try r.nat(pair.?.second, if (op == .read) 16 else 63) orelse return error.InvalidArgument;
    548                 if (op == .read and (number == 0 or number > 4096)) return error.InvalidBound;
    549             },
    550             .write, .update_digest => {
    551                 count = try decodeChunk(engine, pair.?.second, &bytes);
    552                 if (count == 0) return error.InvalidBound;
    553             },
    554             else => {},
    555         }
    556         var destination: ?[:0]u8 = null;
    557         defer if (destination) |path| engine.arena.allocator.free(path);
    558         if (op == .publish_stage) {
    559             const fields = try r.pair(pair.?.second) orelse return error.InvalidArgument;
    560             number = try r.nat(fields.first, 1) orelse return error.InvalidArgument;
    561             const raw = try decodeText(engine, fields.second);
    562             defer engine.arena.allocator.free(raw);
    563             destination = try engine.arena.allocator.dupeZ(u8, raw);
    564         }
    565         if (op == .close) {
    566             const resource = try registry.claimClose(scope, token, false) orelse return error.Busy;
    567             defer registry.finishClose(token) catch unreachable;
    568             try self.closeWithStatus(registry, resource.backend, status);
    569             return engine.leaf;
    570         }
    571         const backend = (try registry.lookup(scope, token)).backend;
    572         if (op == .seal_stage or op == .publish_stage or (op == .write and backend == .stage)) {
    573             if (backend != .stage) return error.WrongAccess;
    574             const lease = try registry.beginLease(scope, token, .exclusive, false);
    575             defer registry.endLease(lease.token) catch unreachable;
    576             self.stage_operation = true;
    577             if (op == .write) return C.ofNumber(engine.arena, try backend.stage.write(&self.stage_status, bytes[0..count]));
    578             if (op == .seal_stage) {
    579                 try backend.stage.seal(&self.stage_status);
    580                 return engine.leaf;
    581             }
    582             const published = try backend.stage.publish(&self.stage_status, number == 1, destination.?);
    583             if (published == .published) self.published += 1;
    584             return engine.text(if (published == .published) "published" else "exists");
    585         }
    586         if (op == .update_digest or op == .finish_digest) {
    587             if (backend != .digest) return error.WrongAccess;
    588             const lease = try registry.beginLease(scope, token, .exclusive, false);
    589             defer registry.endLease(lease.token) catch unreachable;
    590             if (op == .update_digest) {
    591                 try backend.digest.update(bytes[0..count]);
    592                 return engine.leaf;
    593             }
    594             const hex = try backend.digest.finish();
    595             return engine.text(&hex);
    596         }
    597         if (backend != .file) return error.WrongAccess;
    598         const file = backend.file;
    599         const lease = try registry.beginLease(scope, token, .exclusive, false);
    600         defer registry.endLease(lease.token) catch unreachable;
    601         return switch (op) {
    602             .read => blk: {
    603                 const n = try file.read(status, bytes[0..@intCast(number)]);
    604                 const label = try engine.text(if (n == 0) "eof" else "data");
    605                 break :blk try engine.arena.alloc(.{ .fork = .{ .left = label, .right = if (n == 0) engine.leaf else try C.ofBytes(engine.arena, bytes[0..n]) } });
    606             },
    607             .write => try C.ofNumber(engine.arena, try file.write(status, bytes[0..count])),
    608             .seek => blk: {
    609                 try file.seek(number);
    610                 break :blk engine.leaf;
    611             },
    612             .resize => blk: {
    613                 try file.resize(status, number);
    614                 break :blk engine.leaf;
    615             },
    616             .position => try C.ofNumber(engine.arena, try file.position()),
    617             .size => try C.ofNumber(engine.arena, try file.size(status)),
    618             .sync => blk: {
    619                 try file.sync(status);
    620                 break :blk engine.leaf;
    621             },
    622             else => unreachable,
    623         };
    624     }
    625 };
    626 
    627 fn testProgram(a: *@import("arena.zig").Arena, path: []const u8, terminal: ?u64) !u32 {
    628     return testOpenProgram(a, path, terminal, "file", "open-reader");
    629 }
    630 fn testOpenProgram(a: *@import("arena.zig").Arena, path: []const u8, terminal: ?u64, group: []const u8, operation: []const u8) !u32 {
    631     const leaf = try a.alloc(.leaf);
    632     const scope = try C.ofList(a, &.{ try C.ofString(a, "io.v2.scope"), try C.ofNumber(a, 1), leaf });
    633     const args = try a.alloc(.{ .fork = .{ .left = scope, .right = try Text.fromUtf8(a, path) } });
    634     const named = try a.alloc(.{ .fork = .{ .left = try C.ofString(a, group), .right = try a.alloc(.{ .fork = .{ .left = try C.ofNumber(a, 1), .right = try a.alloc(.{ .fork = .{ .left = try C.ofString(a, operation), .right = args } }) } }) } });
    635     const request = try a.alloc(.{ .fork = .{ .left = try C.ofNumber(a, 10), .right = named } });
    636     if (terminal) |tag| {
    637         const next = try a.alloc(.{ .fork = .{ .left = try C.ofNumber(a, tag), .right = leaf } });
    638         const constant = try a.alloc(.{ .fork = .{ .left = leaf, .right = next } });
    639         return a.alloc(.{ .fork = .{ .left = try C.ofNumber(a, 1), .right = try a.alloc(.{ .fork = .{ .left = request, .right = constant } }) } });
    640     }
    641     return request;
    642 }
    643 fn failureProbe(allocator: std.mem.Allocator, path: []const u8) !void {
    644     var a = @import("arena.zig").Arena.init(allocator);
    645     defer a.deinit();
    646     const program = try testProgram(&a, path, null);
    647     a.gc_enabled = true;
    648     try a.setGcPolicy(1, 1);
    649     var host: Host = .{};
    650     defer std.debug.assert(host.opened == host.closed);
    651     _ = try Core.runWithHost(Host, &host, &a, program, 1, 10000);
    652     try std.testing.expectEqual(@as(usize, 1), host.opened);
    653 }
    654 test "physical resources settle on success, fail, cancellation, protocol fault and allocation failure" {
    655     const testing = std.testing;
    656     var tmp = testing.tmpDir(.{});
    657     defer tmp.cleanup();
    658     try tmp.dir.writeFile(testing.io, .{ .sub_path = "file", .data = "abc" });
    659     const path = try tmp.dir.realPathFileAlloc(testing.io, "file", testing.allocator);
    660     defer testing.allocator.free(path);
    661     for ([_]u64{ 0, 2, 9, 11 }) |tag| {
    662         var a = @import("arena.zig").Arena.init(testing.allocator);
    663         defer a.deinit();
    664         const program = try testProgram(&a, path, tag);
    665         var host: Host = .{};
    666         const outcome = try Core.runWithHost(Host, &host, &a, program, 1, 10000);
    667         try testing.expectEqual(@as(usize, 1), host.opened);
    668         try testing.expectEqual(host.opened, host.closed);
    669         try testing.expectEqual(@as(usize, 0), host.close_error_count);
    670         const expected: @TypeOf(outcome.kind) = if (tag == 0) .completed else if (tag == 9) .cancelled else .failed;
    671         try testing.expectEqual(expected, outcome.kind);
    672     }
    673     try testing.checkAllAllocationFailures(testing.allocator, failureProbe, .{path});
    674 }
    675 
    676 const CloseFailureHost = struct {
    677     pub const Backend = Host.Backend;
    678     pub const standard = Host.standard;
    679     inner: Host = .{},
    680     pub fn request(self: *@This(), engine: anytype, scope: u64, req: anytype) !u32 {
    681         return self.inner.request(engine, scope, req);
    682     }
    683     pub fn close(self: *@This(), registry: anytype, backend: Backend) !void {
    684         // Simulate a close that has released its descriptor but reports failure.
    685         // No external FD is closed/reused, and every real descriptor is released.
    686         var original = backend.file.*;
    687         var status: F.Status = .{};
    688         try original.close(&status);
    689         backend.file.fd = -1;
    690         try self.inner.close(registry, backend);
    691     }
    692 };
    693 test "physical close failures defeat success but retain primary failure and cancellation" {
    694     const testing = std.testing;
    695     var tmp = testing.tmpDir(.{});
    696     defer tmp.cleanup();
    697     try tmp.dir.writeFile(testing.io, .{ .sub_path = "file", .data = "abc" });
    698     const path = try tmp.dir.realPathFileAlloc(testing.io, "file", testing.allocator);
    699     defer testing.allocator.free(path);
    700     for ([_]u64{ 0, 2, 9 }) |tag| {
    701         var a = @import("arena.zig").Arena.init(testing.allocator);
    702         defer a.deinit();
    703         const program = try testProgram(&a, path, tag);
    704         var host: CloseFailureHost = .{};
    705         const outcome = try Core.runWithHost(CloseFailureHost, &host, &a, program, 1, 10000);
    706         try testing.expectEqual(@as(usize, 1), host.inner.closed);
    707         try testing.expectEqual(@as(usize, 1), host.inner.close_error_count);
    708         const expected: @TypeOf(outcome.kind) = if (tag == 9) .cancelled else .failed;
    709         try testing.expectEqual(expected, outcome.kind);
    710         if (tag != 0) {
    711             try testing.expect(a.get(outcome.value).* == .leaf);
    712             try testing.expectEqual(@as(usize, 1), outcome.cleanup.len);
    713         }
    714     }
    715 }
    716 
    717 fn digestAllocationProbe(allocator: std.mem.Allocator) !void {
    718     var a = @import("arena.zig").Arena.init(allocator);
    719     defer a.deinit();
    720     const program = try testOpenProgram(&a, "sha256", null, "digest", "open");
    721     a.gc_enabled = true;
    722     try a.setGcPolicy(1, 1);
    723     var host: Host = .{};
    724     defer std.debug.assert(host.opened == host.closed);
    725     _ = try Core.runWithHost(Host, &host, &a, program, 1, 10000);
    726     try std.testing.expectEqual(@as(usize, 1), host.opened);
    727 }
    728 test "digest acquisition and reply allocation failures retire every admitted state" {
    729     try std.testing.checkAllAllocationFailures(std.testing.allocator, digestAllocationProbe, .{});
    730 }
    731 
    732 fn stageAllocationProbe(allocator: std.mem.Allocator, directory: []const u8, dir: std.Io.Dir) !void {
    733     var a = @import("arena.zig").Arena.init(allocator);
    734     defer a.deinit();
    735     const program = try testOpenProgram(&a, directory, null, "stage", "open");
    736     a.gc_enabled = true;
    737     try a.setGcPolicy(1, 1);
    738     var host: Host = .{};
    739     defer std.debug.assert(host.opened == host.closed);
    740     const outcome = Core.runWithHost(Host, &host, &a, program, 1, 10000);
    741     var iterator = dir.iterate();
    742     try std.testing.expect((try iterator.next(std.testing.io)) == null);
    743     _ = try outcome;
    744     try std.testing.expectEqual(@as(usize, 1), host.opened);
    745 }
    746 test "stage acquisition and reply allocation failures remove every private name" {
    747     const testing = std.testing;
    748     var tmp = testing.tmpDir(.{ .iterate = true });
    749     defer tmp.cleanup();
    750     const directory = try tmp.dir.realPathFileAlloc(testing.io, ".", testing.allocator);
    751     defer testing.allocator.free(directory);
    752     try testing.checkAllAllocationFailures(testing.allocator, stageAllocationProbe, .{ directory, tmp.dir });
    753 }
    754 
    755 fn testRequest(a: *@import("arena.zig").Arena, group: []const u8, operation: []const u8, args: u32) !u32 {
    756     const payload = try a.alloc(.{ .fork = .{ .left = try C.ofString(a, group), .right = try a.alloc(.{ .fork = .{ .left = try C.ofNumber(a, 1), .right = try a.alloc(.{ .fork = .{ .left = try C.ofString(a, operation), .right = args } }) } }) } });
    757     return a.alloc(.{ .fork = .{ .left = try C.ofNumber(a, 10), .right = payload } });
    758 }
    759 fn then(a: *@import("arena.zig").Arena, left: u32, right: u32) !u32 {
    760     const constant = try a.alloc(.{ .fork = .{ .left = try a.alloc(.leaf), .right = right } });
    761     return a.alloc(.{ .fork = .{ .left = try C.ofNumber(a, 1), .right = try a.alloc(.{ .fork = .{ .left = left, .right = constant } }) } });
    762 }
    763 fn publicationAllocationProbe(allocator: std.mem.Allocator, directory: []const u8, destination: []const u8, dir: std.Io.Dir) !void {
    764     var a = @import("arena.zig").Arena.init(allocator);
    765     defer a.deinit();
    766     const opened = try testOpenProgram(&a, directory, null, "stage", "open");
    767     const token = try C.ofList(&a, &.{ try C.ofString(&a, "io.v2.resource"), try C.ofNumber(&a, 1), try C.ofNumber(&a, 5) });
    768     const sealed = try testRequest(&a, "stage", "seal", token);
    769     const args = try a.alloc(.{ .fork = .{ .left = token, .right = try a.alloc(.{ .fork = .{ .left = try C.ofNumber(&a, 1), .right = try Text.fromUtf8(&a, destination) } }) } });
    770     const publish = try testRequest(&a, "stage", "publish", args);
    771     const program = try then(&a, opened, try then(&a, sealed, publish));
    772     a.gc_enabled = true;
    773     try a.setGcPolicy(1, 1);
    774     var host: Host = .{};
    775     const result = Core.runWithHost(Host, &host, &a, program, 1, 10000);
    776     try std.testing.expectEqual(host.opened, host.closed);
    777     var iterator = dir.iterate();
    778     var visible: usize = 0;
    779     while (try iterator.next(std.testing.io)) |entry| {
    780         try std.testing.expectEqualStrings("out", entry.name); // never a private name
    781         visible += 1;
    782     }
    783     try std.testing.expectEqual(host.published, visible); // OOM cannot roll back visibility
    784     if (visible != 0) try dir.deleteFile(std.testing.io, "out");
    785     _ = try result;
    786     try std.testing.expectEqual(@as(usize, 1), host.published);
    787 }
    788 test "allocation failures before and after publication clean private names without rollback" {
    789     const testing = std.testing;
    790     var tmp = testing.tmpDir(.{ .iterate = true });
    791     defer tmp.cleanup();
    792     const directory = try tmp.dir.realPathFileAlloc(testing.io, ".", testing.allocator);
    793     defer testing.allocator.free(directory);
    794     const destination = try std.fmt.allocPrint(testing.allocator, "{s}/out", .{directory});
    795     defer testing.allocator.free(destination);
    796     try testing.checkAllAllocationFailures(testing.allocator, publicationAllocationProbe, .{ directory, destination, tmp.dir });
    797 }
    798 
    799 fn indexedWriteAllocationProbe(allocator: std.mem.Allocator, path: []const u8, dir: std.Io.Dir) !void {
    800     defer dir.deleteFile(std.testing.io, "out") catch {};
    801     var a = @import("arena.zig").Arena.init(allocator);
    802     defer a.deinit();
    803     const opened = try testOpenProgram(&a, path, null, "file", "open-writer");
    804     const token = try C.ofList(&a, &.{ try C.ofString(&a, "io.v2.resource"), try C.ofNumber(&a, 1), try C.ofNumber(&a, 5) });
    805     const args = try C.ofList(&a, &.{ token, try C.ofNumber(&a, 431), try C.ofString(&a, "6f93378feaa8bf5ab7cefc071175a654c871bfd53f165d7d96795fb2b38483f1"), try a.alloc(.leaf) });
    806     const program = try then(&a, opened, try testRequest(&a, "indexed-tree", "write", args));
    807     a.gc_enabled = true;
    808     try a.setGcPolicy(1, 1);
    809     var host: Host = .{};
    810     const outcome = Core.runWithHost(Host, &host, &a, program, 1, 10000);
    811     try std.testing.expectEqual(host.opened, host.closed);
    812     if (host.opened != 0) {
    813         const bytes = try dir.readFileAlloc(std.testing.io, "out", std.testing.allocator, .limited(1024));
    814         defer std.testing.allocator.free(bytes);
    815         // Preflight never writes; a failure encoding the reply cannot undo the
    816         // completed write. There are no allocations inside native emission.
    817         try std.testing.expect(bytes.len == 0 or bytes.len == 431);
    818         if (bytes.len != 0) try std.testing.expectEqualStrings("6f93378feaa8bf5ab7cefc071175a654c871bfd53f165d7d96795fb2b38483f1", &Indexed.digest(bytes));
    819         if (outcome) |_| try std.testing.expectEqual(@as(usize, 431), bytes.len) else |_| {}
    820     }
    821     _ = try outcome;
    822     try std.testing.expectEqual(@as(usize, 1), host.opened);
    823 }
    824 test "indexed writer preflight and reply OOM settle leases without rolling back writes" {
    825     var tmp = std.testing.tmpDir(.{});
    826     defer tmp.cleanup();
    827     const directory = try tmp.dir.realPathFileAlloc(std.testing.io, ".", std.testing.allocator);
    828     defer std.testing.allocator.free(directory);
    829     const path = try std.fmt.allocPrint(std.testing.allocator, "{s}/out", .{directory});
    830     defer std.testing.allocator.free(path);
    831     try std.testing.checkAllAllocationFailures(std.testing.allocator, indexedWriteAllocationProbe, .{ path, tmp.dir });
    832 }
    833 
    834 fn snapshotAllocationProbe(allocator: std.mem.Allocator, path: []const u8, directory: []const u8, dir: std.Io.Dir) !void {
    835     var a = @import("arena.zig").Arena.init(allocator);
    836     defer a.deinit();
    837     const opened = try testOpenProgram(&a, path, null, "file", "open-reader");
    838     const token = try C.ofList(&a, &.{ try C.ofString(&a, "io.v2.resource"), try C.ofNumber(&a, 1), try C.ofNumber(&a, 5) });
    839     const args = try C.ofList(&a, &.{ token, try Text.fromUtf8(&a, directory), try C.ofNumber(&a, 431), try C.ofNumber(&a, 431), try C.ofString(&a, "6f93378feaa8bf5ab7cefc071175a654c871bfd53f165d7d96795fb2b38483f1") });
    840     const program = try then(&a, opened, try testRequest(&a, "indexed-tree", "read", args));
    841     a.gc_enabled = true;
    842     try a.setGcPolicy(1, 1);
    843     var host: Host = .{};
    844     const outcome = Core.runWithHost(Host, &host, &a, program, 1, 10000);
    845     try std.testing.expectEqual(host.opened, host.closed);
    846     var iterator = dir.iterate();
    847     while (try iterator.next(std.testing.io)) |entry| try std.testing.expectEqualStrings("in", entry.name);
    848     _ = try outcome;
    849     try std.testing.expectEqual(@as(usize, 2), host.opened);
    850 }
    851 test "snapshot acquisition decode and reply OOM close every file without private names" {
    852     var tmp = std.testing.tmpDir(.{ .iterate = true });
    853     defer tmp.cleanup();
    854     const directory = try tmp.dir.realPathFileAlloc(std.testing.io, ".", std.testing.allocator);
    855     defer std.testing.allocator.free(directory);
    856     const path = try std.fmt.allocPrint(std.testing.allocator, "{s}/in", .{directory});
    857     defer std.testing.allocator.free(path);
    858     var arena = @import("arena.zig").Arena.init(std.testing.allocator);
    859     defer arena.deinit();
    860     const bytes = try Indexed.encode(&arena, try arena.alloc(.leaf));
    861     defer std.testing.allocator.free(bytes);
    862     try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "in", .data = bytes });
    863     try std.testing.checkAllAllocationFailures(std.testing.allocator, snapshotAllocationProbe, .{ path, directory, tmp.dir });
    864 }