lib/choir/src/repro/input/storage.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const alloc_phase = @import("alloc_phase");
3 const capacity_mod = @import("capacity.zig");
4
5 pub const Exhaustion = error{
6 ReproducerInputStorageInUse,
7 ReproducerInputCapacityExceeded,
8 };
9
10 pub const Status = struct {
11 phase: alloc_phase.capacity.Phase,
12 in_use: bool,
13 terminal: bool,
14 file_bytes: usize,
15 storage_bytes: usize,
16 loaded_file_bytes: usize,
17 high_water_file_bytes: usize,
18 rejected_file_count: u64,
19 };
20
21 pub fn exactLimitsForFile(
22 file_bytes: u64,
23 maximum: capacity_mod.Limits,
24 ) Exhaustion!capacity_mod.Limits {
25 const exact = std.math.cast(usize, file_bytes) orelse
26 return error.ReproducerInputCapacityExceeded;
27 if (exact > maximum.file_bytes) return error.ReproducerInputCapacityExceeded;
28 return .{ .file_bytes = exact };
29 }
30
31 const Spec = struct {
32 pub const Limits: type = capacity_mod.Limits;
33 pub const Capacity: type = capacity_mod.Capacity;
34 pub const Exhaustion: type = @import("storage.zig").Exhaustion;
35 pub const Status: type = @import("storage.zig").Status;
36 pub const DeriveError: type = capacity_mod.DeriveError;
37 pub const acquisition: alloc_phase.input.Acquisition = .{ .file = true };
38 pub const overload: alloc_phase.input.Overload = .terminal;
39 pub const limit_field = "file_bytes";
40 pub const loaded_field = "loaded_file_bytes";
41 pub const high_water_field = "high_water_file_bytes";
42 pub const rejected_field = "rejected_file_count";
43 pub const in_use_error: @import("storage.zig").Exhaustion =
44 error.ReproducerInputStorageInUse;
45 pub const capacity_error: @import("storage.zig").Exhaustion =
46 error.ReproducerInputCapacityExceeded;
47
48 pub const claim: alloc_phase.capacity.Declaration = .{
49 .source = .{
50 .id = "choir.pass_reproducer_input_storage",
51 .kind = .phase_static,
52 .limit_source = .caller,
53 .storage = .{
54 .covered = &.{
55 .{
56 .id = "one_pass_reproducer_file_and_overload_lookahead_region",
57 .lifetime = .steady,
58 .detail = "one pass reproducer file and overload-lookahead region",
59 },
60 },
61 .excluded = &.{
62 "reproducer parser output and owned field copies",
63 "Choir registry, IR context, pass manager, worker, and replay storage",
64 "process arguments, file path, report rendering, stdout, and stderr storage",
65 "filesystem implementation, kernel file state, and terminal teardown",
66 },
67 },
68 .capacity = .{
69 .inputs = &.{
70 alloc_phase.capacity.bindInput(Limits, "file_bytes", "file_bytes"),
71 },
72 .type_selectors = &.{},
73 .nodes = &.{
74 .{ .input = 0 },
75 .{ .constant = 1 },
76 .{ .add = .{ .left = 0, .right = 1 } },
77 },
78 .assertions = &.{.{
79 .scope = .closure_total,
80 .measure = .retained,
81 .relation = .exact,
82 .expression = 2,
83 }},
84 },
85 .overload = .{
86 .kind = .terminal,
87 .detail = "stat preflight or max plus one byte ends acquisition before reproducer parsing or replay",
88 },
89 .risks = .{
90 .transitive = .{
91 .status = .witnessed,
92 .detail = "activated file reads use only the preacquired region before synchronous parser consumption",
93 },
94 .foreign = .{
95 .status = .excluded,
96 .detail = "filesystem implementation and kernel file state remain outside pass reproducer input storage",
97 },
98 },
99 .obligations = &.{
100 .{ .key = "choir_pass_reproducer_input_capacity", .role = .capacity_model },
101 .{ .key = "choir_pass_reproducer_input_preflight", .role = .overload },
102 .{ .key = "choir_pass_reproducer_input_file_boundary_overload", .role = .overload },
103 .{ .key = "choir_pass_reproducer_input_file_boundary_transitive_risk", .role = .transitive_risk },
104 .{ .key = "choir_pass_reproducer_input_file_boundary_foreign_risk", .role = .foreign_risk },
105 .{ .key = "choir_pass_reproducer_input_byte_boundary", .role = .custom },
106 .{ .key = "choir_pass_reproducer_input_command_composition", .role = .custom },
107 .{ .key = "choir_pass_reproducer_input_root", .role = .custom },
108 },
109 },
110 .bindings = .{},
111 };
112 };
113
114 pub const Storage = alloc_phase.input.OneRegion(Spec);
115
116 test "Choir pass reproducer input maps files beyond lookahead to capacity exhaustion" {
117 comptime {
118 @stardustClaim(
119 @import("alloc_phase").capacity.witness(Storage, "choir_pass_reproducer_input_capacity"),
120 null,
121 null,
122 null,
123 null,
124 null,
125 null,
126 );
127 }
128
129 comptime {
130 @stardustClaim(
131 @import("alloc_phase").capacity.witness(Storage, "choir_pass_reproducer_input_file_boundary_overload"),
132 null,
133 null,
134 null,
135 null,
136 null,
137 null,
138 );
139 }
140 comptime {
141 @stardustClaim(
142 @import("alloc_phase").capacity.witness(Storage, "choir_pass_reproducer_input_file_boundary_transitive_risk"),
143 null,
144 null,
145 null,
146 null,
147 null,
148 null,
149 );
150 }
151 comptime {
152 @stardustClaim(
153 @import("alloc_phase").capacity.witness(Storage, "choir_pass_reproducer_input_file_boundary_foreign_risk"),
154 null,
155 null,
156 null,
157 null,
158 null,
159 null,
160 );
161 }
162
163 var temporary = std.testing.tmpDir(.{});
164 defer temporary.cleanup();
165 const io = std.Options.debug_io;
166 var storage = try Storage.init(std.testing.allocator, .{ .file_bytes = 5 });
167 defer storage.deinit(std.testing.allocator);
168 storage.activate();
169 try temporary.dir.writeFile(
170 io,
171 .{ .sub_path = "pass.choirrepro", .data = "abcde" },
172 );
173 const exact = try storage.readFile(temporary.dir, io, "pass.choirrepro");
174 try std.testing.expectEqualStrings("abcde", exact);
175 storage.release();
176 try temporary.dir.writeFile(
177 io,
178 .{ .sub_path = "pass.choirrepro", .data = "abcdefgh" },
179 );
180 try std.testing.expectError(
181 error.ReproducerInputCapacityExceeded,
182 storage.readFile(temporary.dir, io, "pass.choirrepro"),
183 );
184 try std.testing.expect(storage.status().terminal);
185 }
186
187 comptime {
188 alloc_phase.capacity.requireAllocatorRejectingOwnerShape(Storage);
189 }