lib/alloc/phase/src/input/one.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 //! Provides bounded input admission into a single reusable byte buffer.
  2 //!
  3 //! Callers configure an upper limit on acceptable input size. Because
  4 //! distinguishing an exactly full input from an oversized payload without a
  5 //! second growing buffer requires lookahead, standard capacity derivation
  6 //! reserves one extra byte beyond the configured limit.
  7 //!
  8 //! The allocation lifecycle proceeds across distinct phases. During
  9 //! initialization, the owner derives capacity and allocates its byte slice
 10 //! through a caller-provided allocator. Activation transitions the owner into
 11 //! steady operation. Steady-state callers borrow byte slices during read
 12 //! operations, consume or copy the borrowed data, and release the buffer before
 13 //! initiating the next read. Teardown frees the backing storage through the
 14 //! allocator used for initialization. External callers must serialize
 15 //! operations because the owner maintains mutable internal state without
 16 //! internal synchronization.
 17 
 18 const std = @import("std");
 19 const alloc_phase = @import("../root.zig");
 20 
 21 /// Error set returned when capacity derivation fails.
 22 ///
 23 /// Members:
 24 ///
 25 /// * `CapacityOverflow`: Adding one lookahead byte to the configured byte limit
 26 /// overflowed the range of `usize`.
 27 pub const DeriveError = error{CapacityOverflow};
 28 
 29 /// Packed boolean structure that enables specific input capabilities on an
 30 /// owner at compile time. All four fields default to `false`:
 31 ///
 32 /// * `reader`: Enables stream acquisition through `read`.
 33 ///
 34 /// * `file`: Enables filesystem acquisition through `readFile` and
 35 /// `readFileWindow`.
 36 /// * `preflight`: Enables size admission checks through `preflight`.
 37 /// * `map_file_too_big`: Maps `error.FileTooBig` from an underlying `readFile`
 38 /// call into a capacity rejection instead of propagating the raw I/O error.
 39 ///
 40 /// Because an owner must support at least one input mechanism, specification
 41 /// validation requires either `reader` or `file` to be enabled. Mapping
 42 /// file-size errors requires filesystem support, so `map_file_too_big` can be
 43 /// enabled only when `file` is also set to `true`. Individual acquisition
 44 /// methods verify their corresponding flags at compile time and raise a compile
 45 /// error when invoked without the required capability enabled.
 46 pub const Acquisition = packed struct {
 47     reader: bool = false,
 48     file: bool = false,
 49     preflight: bool = false,
 50     map_file_too_big: bool = false,
 51 };
 52 
 53 /// Defines the policy applied when an input exceeds the configured capacity
 54 /// limit.
 55 ///
 56 /// Tags:
 57 ///
 58 /// * `terminal`: Permanently seals the owner upon capacity rejection. Any
 59 /// subsequent acquisition attempt returns the capacity error immediately
 60 /// without performing I/O or incrementing rejection counters.
 61 /// * `recoverable`: Preserves owner availability across rejections. Oversized
 62 /// inputs increment the rejection counter but leave the underlying storage free
 63 /// for subsequent attempts.
 64 ///
 65 /// Recovery designates the ability to reuse the allocated storage for later
 66 /// inputs. It does not provide source-position rollback, record framing, or
 67 /// automatic input drainage.
 68 pub const Overload = enum {
 69     terminal,
 70     recoverable,
 71 };
 72 
 73 /// Derives an instance of `Capacity` from a given `Limits` structure by adding
 74 /// one lookahead byte to the configured input limit.
 75 ///
 76 /// The input limit is retrieved from the field named by `limit_field` on
 77 /// `limits`. Both the limit field on `Limits` and `Capacity` and the
 78 /// `storage_bytes` field on `Capacity` must be typed as `usize`. When addition
 79 /// overflows `usize`, the function returns `error.CapacityOverflow`. A limit of
 80 /// zero derives a storage size of one byte.
 81 ///
 82 /// The returned `Capacity` structure initializes only the field named by
 83 /// `limit_field` and `storage_bytes`. Any additional fields present on
 84 /// `Capacity` remain undefined. Callers must keep `limit_field` distinct from
 85 /// `storage_bytes` so that writing `storage_bytes` does not overwrite the limit
 86 /// field.
 87 pub fn deriveCapacity(
 88     comptime Limits: type,
 89     comptime Capacity: type,
 90     comptime limit_field: []const u8,
 91     limits: Limits,
 92 ) DeriveError!Capacity {
 93     comptime requireCapacityTypes(Limits, Capacity, limit_field);
 94     const limit_bytes = @field(limits, limit_field);
 95     const storage_bytes = try alloc_phase.capacity.add(usize, limit_bytes, 1);
 96     std.debug.assert(storage_bytes > limit_bytes);
 97     std.debug.assert(storage_bytes != 0);
 98     var capacity: Capacity = undefined;
 99     @field(capacity, limit_field) = limit_bytes;
100     capacity.storage_bytes = storage_bytes;
101     return capacity;
102 }
103 
104 /// Constructs an allocator-backed owner type for a single reusable input buffer
105 /// based on a static specification structure.
106 ///
107 /// The `Spec` type parameter must satisfy several structural requirements:
108 ///
109 /// * `Limits`: Structure defining input limits. It must contain the field
110 /// designated by `limit_field` typed as `usize`.
111 /// * `Capacity`: Structure defining storage requirements. It must expose a
112 /// `derive(limits: Limits) DeriveError!Capacity` function, the field named by
113 /// `limit_field` typed as `usize`, and `storage_bytes` typed as `usize`.
114 /// * `DeriveError`: Error set returned by `Capacity.derive`.
115 /// * `Exhaustion`: Error set returned when storage cannot admit an input.
116 /// * `Status`: Reporting structure returned by `status`. It must be a struct
117 /// containing exactly seven fields for recoverable owners or eight fields for
118 /// terminal owners: `phase: alloc_phase.capacity.Phase`, `in_use: bool`,
119 /// `storage_bytes: usize`, the mapped limit, loaded, and high-water fields
120 /// typed as `usize`, the mapped rejection field typed as `u64`, and
121 /// conditionally `terminal: bool` when `overload` is `terminal`.
122 /// * `acquisition`: An instance of `Acquisition` specifying enabled I/O
123 /// capabilities.
124 /// * `overload`: An instance of `Overload` setting either `.terminal` or
125 /// `.recoverable` behavior.
126 /// * Field mappings: `limit_field`, `loaded_field`, `high_water_field`, and
127 /// `rejected_field` specify field names on `Capacity` or `Status`. Callers must
128 /// supply distinct names for these fields. Compile-time validation verifies
129 /// field presence and total field counts, but this validation does not prove
130 /// that mapped field names are mutually distinct.
131 /// * Error instances: `in_use_error` and `capacity_error` must both have type
132 /// `Spec.Exhaustion`.
133 /// * `claim`: A static `alloc_phase.capacity.Declaration` metadata descriptor.
134 ///
135 /// The owner asserts that `storage_bytes` strictly exceeds the configured byte
136 /// limit. While standard `deriveCapacity` derivation provides an exact `limit +
137 /// 1` layout, custom `Capacity.derive` implementations may describe larger
138 /// storage: derivation computes a `Capacity` value, while `init` allocates.
139 ///
140 /// A successful read operation returns a borrowed `[]const u8` slice pointing
141 /// directly into the owned buffer and marks the owner as in use, including when
142 /// the input is empty. Borrowers must consume or copy these bytes elsewhere
143 /// before calling `release`. Slices must not be accessed after releasing the
144 /// owner.
145 ///
146 /// A struct copy shares allocated storage but duplicates internal bookkeeping.
147 /// Copies are not independent owners: transferring exclusive ownership requires
148 /// callers to stop using the original. Callers must serialize operations and
149 /// ensure cleanup occurs exactly once.
150 ///
151 /// The owner is not thread-safe and performs no internal synchronization.
152 /// Callers accessing an owner across threads must provide external mutual
153 /// exclusion.
154 ///
155 /// Lifecycle checks across initialization, steady operation, and teardown are
156 /// enforced through debug assertions rather than explicit always-executed phase
157 /// panics.
158 pub fn OneRegion(comptime Spec: type) type {
159     comptime requireSpec(Spec);
160     return struct {
161         phase: alloc_phase.capacity.Phase,
162         capacity: Spec.Capacity,
163         bytes: []u8,
164         in_use: bool = false,
165         terminal: bool = false,
166         loaded_bytes: usize = 0,
167         high_water_bytes: usize = 0,
168         rejected_count: u64 = 0,
169 
170         const Self = @This();
171 
172         /// Result structure returned by `readFileWindow` representing a bounded
173         /// slice of a file read at a positional offset.
174         ///
175         /// Fields:
176         ///
177         /// * `bytes`: Borrowed const byte slice containing admitted data. Its
178         /// length is at most the requested window size.
179         /// * `complete`: Boolean flag set to `true` when `read_bytes <=
180         /// window_bytes`.
181         ///
182         /// The `complete` flag describes only this positional read and does not
183         /// make an immutable snapshot against concurrent modification during or
184         /// after it.
185         pub const FileWindow = struct {
186             bytes: []const u8,
187             complete: bool,
188         };
189 
190         /// Type alias for `Spec.Limits`. Defines the input limit constraints
191         /// supplied by the caller.
192         pub const Limits: type = Spec.Limits;
193         /// Type alias for `Spec.Capacity`. Holds derived storage dimensions,
194         /// including the mandatory `storage_bytes` field.
195         pub const Capacity: type = Spec.Capacity;
196         /// Type alias for `Spec.Exhaustion`. Represents the error set returned
197         /// when input operations encounter a busy buffer or exceed configured
198         /// capacity.
199         pub const Exhaustion: type = Spec.Exhaustion;
200         /// Error set representing failures that can occur during
201         /// initialization. Combines allocator allocation errors with
202         /// `Spec.DeriveError`.
203         pub const InitError = std.mem.Allocator.Error || Spec.DeriveError;
204         /// Static capacity declaration describing the memory allocation
205         /// semantics of the generated owner.
206         ///
207         /// The declaration retains the complete `Spec.claim.source` envelope
208         /// while establishing fresh bindings for the generated owner:
209         ///
210         /// * `owner`: Binds the generated owner type `Self`.
211         /// * `source`: Binds the originating `Spec` configuration type.
212         /// * `seal`: Binds the `activate` method selector under the
213         /// `checked_semantic_fact` class with `checker` authority.
214         /// * `teardown`: Binds the `deinit` method selector under the
215         /// `checked_semantic_fact` class with `checker` authority.
216         ///
217         /// The bindings defined on `Spec.claim.bindings` are not reused.
218         /// Assigning premise labels to these new bindings does not prove their
219         /// truth. Local specification checks verify only that `claim` has type
220         /// `alloc_phase.capacity.Declaration`. Separate structural and
221         /// declaration checking verifies lifecycle declaration shape, but that
222         /// check does not prove whole-program allocation freedom.
223         pub const claim: alloc_phase.capacity.Declaration = .{
224             .source = Spec.claim.source,
225             .bindings = .{
226                 .owner = Self,
227                 .source = Spec,
228                 .seal = .{
229                     .family = alloc_phase.capacity.selector(Self.activate),
230                     .premise = .{
231                         .class = .checked_semantic_fact,
232                         .authority = .checker,
233                     },
234                 },
235                 .teardown = .{
236                     .family = alloc_phase.capacity.selector(Self.deinit),
237                     .premise = .{
238                         .class = .checked_semantic_fact,
239                         .authority = .checker,
240                     },
241                 },
242             },
243         };
244 
245         /// Initializes a new input region by deriving capacity and allocating
246         /// the backing byte slice.
247         ///
248         /// The function derives capacity by evaluating
249         /// `Capacity.derive(limits)`. When derivation succeeds, it allocates
250         /// `capacity.storage_bytes` through the provided allocator. The
251         /// initialized owner enters the `.initialization` phase and retains the
252         /// allocated slice. The allocator handle itself is not stored on the
253         /// instance, so callers must provide the same allocator during
254         /// deinitialization.
255         ///
256         /// Initialization fails with `Spec.DeriveError` if capacity derivation
257         /// fails, or with `error.OutOfMemory` if memory allocation fails.
258         /// Before returning, debug assertions verify that the allocated slice
259         /// length equals `storage_bytes` and strictly exceeds the configured
260         /// byte limit.
261         pub fn init(allocator: std.mem.Allocator, limits: Limits) InitError!Self {
262             const capacity = try Capacity.derive(limits);
263             const bytes = try allocator.alloc(u8, capacity.storage_bytes);
264             const storage = Self{
265                 .phase = .initialization,
266                 .capacity = capacity,
267                 .bytes = bytes,
268             };
269             storage.assertRegion();
270             return storage;
271         }
272 
273         /// Transitions the owner from the initialization phase into steady
274         /// operation.
275         ///
276         /// Callers must activate the owner before performing input operations.
277         /// The function verifies through debug assertions that the owner
278         /// resides in the `.initialization` phase and that the backing buffer
279         /// satisfies region invariants. Once activated, `phase` transitions to
280         /// `.steady`, permitting acquisition calls.
281         pub fn activate(self: *Self) void {
282             std.debug.assert(self.phase == .initialization);
283             self.assertRegion();
284             self.phase = .steady;
285         }
286 
287         /// Performs a size admission check for an expected input length without
288         /// executing I/O or modifying buffer contents.
289         ///
290         /// This method requires `Spec.acquisition.preflight` to be enabled at
291         /// compile time. Before evaluating the size, it verifies that the owner
292         /// is available in steady operation. If an operation is currently
293         /// active, it returns `Spec.in_use_error`. If the owner was previously
294         /// sealed in a terminal state, it returns `Spec.capacity_error`.
295         ///
296         /// The proposed length `input_bytes` is cast to `usize`. If the value
297         /// overflows `usize` or exceeds the configured byte limit, the method
298         /// invokes internal rejection, incrementing the saturating rejection
299         /// counter and applying the configured overload policy before returning
300         /// `Spec.capacity_error`.
301         ///
302         /// A successful preflight check confirms only that the proposed size
303         /// fits within the configured limit. It does not reserve storage, lock
304         /// the buffer, or establish an atomic guarantee between checking a size
305         /// and executing a later read.
306         pub fn preflight(self: *Self, input_bytes: u64) Exhaustion!void {
307             comptime requireAcquisition(Spec.acquisition.preflight, "preflight");
308             try self.requireAvailable();
309             const bounded = std.math.cast(usize, input_bytes) orelse
310                 return self.reject();
311             if (bounded > self.limitBytes()) return self.reject();
312         }
313 
314         /// Reads data from a stream reader into the owned buffer and admits the
315         /// resulting slice if its length falls within the configured limit.
316         ///
317         /// This method requires `Spec.acquisition.reader` to be enabled at
318         /// compile time. It requires the owner to be available in steady
319         /// operation, returning `Spec.in_use_error` if an active borrow exists
320         /// or `Spec.capacity_error` if the owner is terminal.
321         ///
322         /// The method invokes `reader.readSliceShort` over the allocated
323         /// storage slice. If reading from the underlying stream returns a
324         /// `ShortError` such as `ReadFailed`, that error propagates directly to
325         /// the caller. A read failure can occur after writing bytes into the
326         /// buffer and consuming bytes from the stream, without establishing a
327         /// borrow or recording a rejection. The method promises neither buffer
328         /// rollback nor stream rewind nor record framing.
329         ///
330         /// When reading completes without stream error, the read byte count is
331         /// evaluated:
332         ///
333         /// * If the count is at most the configured limit, the slice
334         /// `self.bytes[0..input_bytes]` is admitted. The owner records the
335         /// loaded length, updates the high-water mark, sets `in_use` to `true`,
336         /// and returns the borrowed slice.
337         /// * If the count exceeds the configured limit, the method rejects the
338         /// input. It increments the rejection counter, transitions to terminal
339         /// if configured, and returns `Spec.capacity_error`.
340         ///
341         /// When using helper-based capacity derivation through
342         /// `deriveCapacity`, an oversized input fills the `limit + 1` storage
343         /// bytes and triggers a capacity rejection after reading. This consumes
344         /// `limit + 1` bytes from the stream without draining trailing input.
345         /// Because reader implementations may buffer additional data,
346         /// subsequent reads cannot assume framing alignment with a subsequent
347         /// record.
348         pub fn read(
349             self: *Self,
350             reader: *std.Io.Reader,
351         ) (Exhaustion || std.Io.Reader.ShortError)![]const u8 {
352             comptime requireAcquisition(Spec.acquisition.reader, "reader");
353             try self.requireAvailable();
354             const input_bytes = try reader.readSliceShort(self.bytes);
355             return try self.admit(input_bytes);
356         }
357 
358         /// Reads a file from a directory into the bounded buffer and admits the
359         /// resulting slice if the read length does not exceed the configured
360         /// limit.
361         ///
362         /// This method requires `Spec.acquisition.file` to be enabled at
363         /// compile time. It checks that the owner is available in steady
364         /// operation before opening the file.
365         ///
366         /// Reading is executed through `dir.readFile` into the allocated buffer
367         /// slice. Under the pinned standard library implementation, an ordinary
368         /// oversized file fills the buffer prefix before admission rejects the
369         /// input. Opening errors propagate directly, while a read error can
370         /// occur after partial buffer writes and no borrow is admitted.
371         ///
372         /// Handling of `error.FileTooBig` depends on compile-time
373         /// configuration:
374         ///
375         /// * When `Spec.acquisition.map_file_too_big` is `true`,
376         /// `error.FileTooBig` is converted into a capacity rejection. This
377         /// increments the rejection counter, transitions to terminal if
378         /// configured, and returns `Spec.capacity_error`.
379         /// * When `Spec.acquisition.map_file_too_big` is `false`,
380         /// `error.FileTooBig` propagates directly to the caller without
381         /// incrementing the rejection counter or modifying terminal state.
382         ///
383         /// All other filesystem I/O errors propagate directly to the caller
384         /// without a capacity rejection or terminal transition under either
385         /// setting. On success, the method returns a borrowed slice of the
386         /// admitted bytes and marks the owner as in use.
387         pub fn readFile(
388             self: *Self,
389             dir: std.Io.Dir,
390             io: std.Io,
391             file_path: []const u8,
392         ) ![]const u8 {
393             comptime requireAcquisition(Spec.acquisition.file, "file");
394             try self.requireAvailable();
395             const file = if (comptime Spec.acquisition.map_file_too_big)
396                 dir.readFile(io, file_path, self.bytes) catch |err| switch (err) {
397                     error.FileTooBig => return self.reject(),
398                     else => return err,
399                 }
400             else
401                 try dir.readFile(io, file_path, self.bytes);
402             return try self.admit(file.len);
403         }
404 
405         /// Reads a bounded positional window from a file at a specified offset
406         /// without treating trailing bytes as a capacity rejection.
407         ///
408         /// This method requires `Spec.acquisition.file` to be enabled at
409         /// compile time. Callers must supply a window size satisfying `0 <
410         /// window_bytes <= limit`, which debug assertions verify. The owner
411         /// must be available in steady operation before opening the target
412         /// file.
413         ///
414         /// The method opens the file and reads up to `window_bytes + 1` bytes
415         /// at the specified offset using positional I/O into `self.bytes[0 ..
416         /// window_bytes + 1]`. Any filesystem I/O error during open or read
417         /// propagates directly to the caller without a capacity rejection or
418         /// borrow, though the buffer may be partially written.
419         ///
420         /// The read byte count determines completion and admission:
421         ///
422         /// * If the number of read bytes is less than or equal to
423         /// `window_bytes`, `complete` is set to `true`.
424         /// * If reading produces `window_bytes + 1` bytes, additional data
425         /// exists beyond the window, setting `complete` to `false`. This
426         /// condition does not trigger a capacity rejection or modify terminal
427         /// status, even on terminal owners.
428         ///
429         /// The method admits `@min(read_bytes, window_bytes)`, which marks the
430         /// owner as in use and returns a borrowed slice. An immediate EOF
431         /// returning zero bytes still establishes an admitted zero-byte borrow.
432         /// The `complete` flag describes only this positional read and is not
433         /// an instantaneous snapshot against concurrent modifications.
434         pub fn readFileWindow(
435             self: *Self,
436             dir: std.Io.Dir,
437             io: std.Io,
438             file_path: []const u8,
439             offset: u64,
440             window_bytes: usize,
441         ) !FileWindow {
442             comptime requireAcquisition(Spec.acquisition.file, "file");
443             try self.requireAvailable();
444             std.debug.assert(window_bytes > 0);
445             std.debug.assert(window_bytes <= self.limitBytes());
446             var file = try dir.openFile(io, file_path, .{});
447             defer file.close(io);
448             const read_bytes = try file.readPositionalAll(
449                 io,
450                 self.bytes[0 .. window_bytes + 1],
451                 offset,
452             );
453             const complete = read_bytes <= window_bytes;
454             const admitted = try self.admit(@min(read_bytes, window_bytes));
455             return .{ .bytes = admitted, .complete = complete };
456         }
457 
458         /// Releases an active borrow, allowing the internal buffer to be reused
459         /// by future input operations.
460         ///
461         /// Callers must call this method only when the owner is in the
462         /// `.steady` phase, holds an active borrow (`in_use` is `true`), and is
463         /// not in a terminal state. Debug assertions verify these
464         /// preconditions.
465         ///
466         /// Calling release sets `in_use` to `false` and resets `loaded_bytes`
467         /// to zero. It does not overwrite or sanitize existing byte values in
468         /// the buffer. Historical tracking metrics, including the high-water
469         /// mark and the cumulative rejection count, remain intact across
470         /// release calls.
471         pub fn release(self: *Self) void {
472             std.debug.assert(self.phase == .steady);
473             std.debug.assert(self.in_use);
474             std.debug.assert(!self.terminal);
475             self.in_use = false;
476             self.loaded_bytes = 0;
477         }
478 
479         /// Constructs and returns a snapshot of owner metrics and lifecycle
480         /// state as a `Spec.Status` structure.
481         ///
482         /// The method copies configured status fields by value under caller
483         /// serialization. Because the owner does not use atomic operations or
484         /// internal synchronization, this method does not produce a coherent
485         /// snapshot across concurrent unsynchronized access.
486         ///
487         /// The returned snapshot contains the following fields:
488         ///
489         /// * `phase`: Current lifecycle phase (`.initialization`, `.steady`, or
490         /// `.teardown`).
491         /// * `in_use`: Boolean flag indicating whether a borrowed buffer slice
492         /// is currently outstanding.
493         /// * `terminal`: Present on terminal owners to indicate whether
494         /// capacity exhaustion permanently sealed the owner.
495         /// * Configured limit field: Configured maximum byte limit derived from
496         /// capacity.
497         /// * `storage_bytes`: Total declared capacity of the buffer. Because
498         /// this value reflects declared specification capacity, it does not
499         /// represent a live allocation measurement and remains readable after
500         /// teardown.
501         /// * Configured loaded field: Number of admitted bytes currently held
502         /// by an active borrow. This field is zero whenever the owner is not in
503         /// use.
504         /// * Configured high-water field: Largest admitted byte count observed
505         /// across all successful reads since initialization. Oversized inputs
506         /// rejected during admission do not update this value.
507         /// * Configured rejected field: Cumulative saturating count of capacity
508         /// rejections.
509         pub fn status(self: *const Self) Spec.Status {
510             var result: Spec.Status = undefined;
511             result.phase = self.phase;
512             result.in_use = self.in_use;
513             if (comptime @hasField(Spec.Status, "terminal")) {
514                 result.terminal = self.terminal;
515             }
516             @field(result, Spec.limit_field) = self.limitBytes();
517             result.storage_bytes = self.capacity.storage_bytes;
518             @field(result, Spec.loaded_field) = self.loaded_bytes;
519             @field(result, Spec.high_water_field) = self.high_water_bytes;
520             @field(result, Spec.rejected_field) = self.rejected_count;
521             return result;
522         }
523 
524         /// Deinitializes the owner and frees its allocated byte buffer using
525         /// the provided allocator.
526         ///
527         /// Callers must supply the same allocator used for `init`. The method
528         /// verifies through debug assertions that the owner has not already
529         /// entered teardown and that no borrowed slice is currently in use
530         /// (`in_use` is `false`).
531         ///
532         /// Deinitialization transitions `phase` to `.teardown`, frees the
533         /// allocated memory slice, clears the internal buffer slice to empty,
534         /// and resets `loaded_bytes` to zero. Callers may invoke this method
535         /// while the owner is still in the `.initialization` phase to clean up
536         /// resources after a failed startup sequence. Cumulative metrics and
537         /// capacity declarations remain stored on the instance after
538         /// deinitialization completes.
539         pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
540             std.debug.assert(self.phase != .teardown);
541             std.debug.assert(!self.in_use);
542             self.assertRegion();
543             self.phase = .teardown;
544             allocator.free(self.bytes);
545             self.bytes = &.{};
546             self.loaded_bytes = 0;
547         }
548 
549         /// Verifies that the owner is ready to accept a new input operation.
550         ///
551         /// Debug assertions confirm that the owner resides in the `.steady`
552         /// phase, that buffer invariants hold, and that previous loaded bytes
553         /// did not exceed the limit. If the owner is marked terminal, the
554         /// function returns `Spec.capacity_error` without incrementing the
555         /// rejection counter. If a borrow is currently active, it returns
556         /// `Spec.in_use_error` without incrementing the rejection counter. When
557         /// available, debug assertions verify that `loaded_bytes` is zero.
558         fn requireAvailable(self: *Self) Exhaustion!void {
559             std.debug.assert(self.phase == .steady);
560             self.assertRegion();
561             std.debug.assert(self.loaded_bytes <= self.limitBytes());
562             if (self.terminal) return Spec.capacity_error;
563             if (self.in_use) return Spec.in_use_error;
564             std.debug.assert(self.loaded_bytes == 0);
565         }
566 
567         /// Admits a specified number of bytes into the region or rejects the
568         /// payload if it exceeds the configured limit.
569         ///
570         /// Debug assertions verify that the owner is in steady operation
571         /// without an active borrow or terminal state, and that the requested
572         /// byte count does not exceed allocated storage. If `input_bytes`
573         /// exceeds the configured byte limit, the function invokes `reject`.
574         /// Otherwise, it marks the buffer as in use, records `loaded_bytes`,
575         /// updates `high_water_bytes` if the new length exceeds previous
576         /// admissions, and returns a borrowed slice over the admitted bytes.
577         fn admit(self: *Self, input_bytes: usize) Exhaustion![]const u8 {
578             std.debug.assert(self.phase == .steady);
579             std.debug.assert(!self.in_use);
580             std.debug.assert(!self.terminal);
581             std.debug.assert(input_bytes <= self.capacity.storage_bytes);
582             if (input_bytes > self.limitBytes()) return self.reject();
583             self.in_use = true;
584             self.loaded_bytes = input_bytes;
585             self.high_water_bytes = @max(self.high_water_bytes, input_bytes);
586             return self.bytes[0..input_bytes];
587         }
588 
589         /// Records an input capacity rejection and updates owner overload
590         /// state.
591         ///
592         /// Debug assertions verify that the owner is in steady operation, is
593         /// not in use, is not already terminal, and holds zero loaded bytes. If
594         /// `Spec.overload` is configured as `.terminal`, the owner enters the
595         /// terminal state. The cumulative rejection counter is incremented
596         /// using saturating addition, and the function returns
597         /// `Spec.capacity_error`.
598         fn reject(self: *Self) Exhaustion {
599             std.debug.assert(self.phase == .steady);
600             std.debug.assert(!self.in_use);
601             std.debug.assert(!self.terminal);
602             std.debug.assert(self.loaded_bytes == 0);
603             self.terminal = Spec.overload == .terminal;
604             self.rejected_count +|= 1;
605             return Spec.capacity_error;
606         }
607 
608         /// Helper function returning the configured byte limit stored on
609         /// `self.capacity` using the field name defined by `Spec.limit_field`.
610         fn limitBytes(self: *const Self) usize {
611             return @field(self.capacity, Spec.limit_field);
612         }
613 
614         /// Helper function asserting that the backing byte slice length matches
615         /// `storage_bytes` and strictly exceeds the configured byte limit.
616         fn assertRegion(self: *const Self) void {
617             std.debug.assert(self.bytes.len == self.capacity.storage_bytes);
618             std.debug.assert(self.capacity.storage_bytes > self.limitBytes());
619         }
620     };
621 }
622 
623 /// Compile-time validation helper that verifies field types for capacity
624 /// derivation.
625 ///
626 /// The function verifies that `Limits` contains the field specified by
627 /// `limit_field` typed as `usize`. It also verifies that `Capacity` contains
628 /// both the field specified by `limit_field` and `storage_bytes`, requiring
629 /// each to be typed as `usize`. If any requirement is not met, compilation
630 /// fails with an explanatory message.
631 fn requireCapacityTypes(
632     comptime Limits: type,
633     comptime Capacity: type,
634     comptime limit_field: []const u8,
635 ) void {
636     if (!@hasField(Limits, limit_field)) @compileError("Limits lacks byte limit");
637     if (@FieldType(Limits, limit_field) != usize) @compileError("limit must be usize");
638     if (!@hasField(Capacity, limit_field)) @compileError("Capacity lacks byte limit");
639     if (@FieldType(Capacity, limit_field) != usize) @compileError("limit must be usize");
640     if (!@hasField(Capacity, "storage_bytes")) @compileError("Capacity lacks storage_bytes");
641     if (@FieldType(Capacity, "storage_bytes") != usize) {
642         @compileError("storage_bytes must be usize");
643     }
644 }
645 
646 /// Compile-time validation helper enforcing structural requirements on the
647 /// `Spec` configuration type.
648 ///
649 /// The function validates type signatures and configuration values:
650 ///
651 /// * Delegates limit and capacity layout verification to
652 /// `requireCapacityTypes`.
653 /// * Verifies that `claim` has type `alloc_phase.capacity.Declaration`.
654 /// * Verifies that `in_use_error` and `capacity_error` both belong to
655 /// `Spec.Exhaustion`.
656 /// * Confirms that `Status` is a struct type possessing exactly seven fields
657 /// for recoverable owners or eight fields for terminal owners.
658 /// * Verifies presence and exact typing for `phase`
659 /// (`alloc_phase.capacity.Phase`), `in_use` (`bool`), `storage_bytes`
660 /// (`usize`), the mapped limit field (`usize`), the mapped loaded field
661 /// (`usize`), the mapped high-water field (`usize`), the mapped rejection field
662 /// (`u64`), and `terminal` (`bool`) when terminal overload is enabled.
663 /// * Confirms that `map_file_too_big` is enabled only when `file` acquisition
664 /// is active.
665 /// * Verifies that at least one acquisition capability (`reader` or `file`) is
666 /// enabled.
667 ///
668 /// Validating field count, named presence, and types does not establish a
669 /// distinct, complete semantic mapping. Because validation does not enforce
670 /// that mapped names are distinct, duplicate names can leave unrelated fields
671 /// uninitialized.
672 fn requireSpec(comptime Spec: type) void {
673     requireCapacityTypes(Spec.Limits, Spec.Capacity, Spec.limit_field);
674     if (@TypeOf(Spec.claim) != alloc_phase.capacity.Declaration) {
675         @compileError("invalid claim");
676     }
677     if (@TypeOf(Spec.in_use_error) != Spec.Exhaustion) @compileError("invalid in-use error");
678     if (@TypeOf(Spec.capacity_error) != Spec.Exhaustion) @compileError("invalid capacity error");
679     if (@typeInfo(Spec.Status) != .@"struct") @compileError("Status must be a struct");
680     const field_count = @typeInfo(Spec.Status).@"struct".field_names.len;
681     const expected_fields: usize = if (Spec.overload == .terminal) 8 else 7;
682     if (field_count != expected_fields) @compileError("Status has unexpected fields");
683     if (!@hasField(Spec.Status, "phase")) @compileError("Status lacks phase");
684     if (@FieldType(Spec.Status, "phase") != alloc_phase.capacity.Phase) {
685         @compileError("Status phase has invalid type");
686     }
687     if (!@hasField(Spec.Status, "in_use")) @compileError("Status lacks in_use");
688     if (@FieldType(Spec.Status, "in_use") != bool) @compileError("Status in_use has invalid type");
689     if (!@hasField(Spec.Status, Spec.limit_field)) @compileError("Status lacks byte limit");
690     if (@FieldType(Spec.Status, Spec.limit_field) != usize) {
691         @compileError("Status byte limit has invalid type");
692     }
693     if (!@hasField(Spec.Status, "storage_bytes")) @compileError("Status lacks storage_bytes");
694     if (@FieldType(Spec.Status, "storage_bytes") != usize) {
695         @compileError("Status storage_bytes has invalid type");
696     }
697     if (!@hasField(Spec.Status, Spec.loaded_field)) @compileError("Status lacks loaded bytes");
698     if (@FieldType(Spec.Status, Spec.loaded_field) != usize) {
699         @compileError("Status loaded bytes has invalid type");
700     }
701     if (!@hasField(Spec.Status, Spec.high_water_field)) @compileError("Status lacks high water");
702     if (@FieldType(Spec.Status, Spec.high_water_field) != usize) {
703         @compileError("Status high water has invalid type");
704     }
705     if (!@hasField(Spec.Status, Spec.rejected_field)) @compileError("Status lacks rejection count");
706     if (@FieldType(Spec.Status, Spec.rejected_field) != u64) {
707         @compileError("Status rejection count has invalid type");
708     }
709     if (Spec.overload == .terminal and !@hasField(Spec.Status, "terminal")) {
710         @compileError("terminal Status lacks terminal field");
711     }
712     if (Spec.overload == .terminal and @FieldType(Spec.Status, "terminal") != bool) {
713         @compileError("Status terminal has invalid type");
714     }
715     if (Spec.acquisition.map_file_too_big and !Spec.acquisition.file) {
716         @compileError("FileTooBig mapping requires file acquisition");
717     }
718     if (!Spec.acquisition.reader and !Spec.acquisition.file) {
719         @compileError("one-region input requires reader or file acquisition");
720     }
721 }
722 
723 /// Compile-time validation helper that verifies whether a requested acquisition
724 /// capability is enabled on the owner. If the capability flag is `false`,
725 /// compilation fails with a message identifying the unsupported operation.
726 fn requireAcquisition(comptime enabled: bool, comptime name: []const u8) void {
727     if (!enabled) @compileError("one-region input does not support " ++ name);
728 }
729 
730 test "one-region capacity derives one lookahead byte" {
731     const Limits = struct { input_bytes: usize };
732     const Capacity = struct { input_bytes: usize, storage_bytes: usize };
733     try std.testing.expectEqual(
734         Capacity{ .input_bytes = 31, .storage_bytes = 32 },
735         try deriveCapacity(Limits, Capacity, "input_bytes", .{ .input_bytes = 31 }),
736     );
737     try std.testing.expectError(
738         error.CapacityOverflow,
739         deriveCapacity(
740             Limits,
741             Capacity,
742             "input_bytes",
743             .{ .input_bytes = std.math.maxInt(usize) },
744         ),
745     );
746 }
747 
748 test "one-region terminal owner acquires once and seals overload" {
749     const Limits = struct { input_bytes: usize };
750     const Capacity = testCapacity(Limits);
751     const Status = struct {
752         phase: alloc_phase.capacity.Phase,
753         in_use: bool,
754         terminal: bool,
755         input_bytes: usize,
756         storage_bytes: usize,
757         loaded_input_bytes: usize,
758         high_water_input_bytes: usize,
759         rejected_input_count: u64,
760     };
761     const Spec = testSpec(Limits, Capacity, Status, .terminal);
762     const Storage = OneRegion(Spec);
763     const failure = struct {
764         fn run(allocator: std.mem.Allocator) !void {
765             var storage = try Storage.init(allocator, .{ .input_bytes = 5 });
766             storage.deinit(allocator);
767         }
768     };
769     try std.testing.checkAllAllocationFailures(
770         std.testing.allocator,
771         failure.run,
772         .{},
773     );
774 
775     var counting = std.testing.FailingAllocator.init(std.testing.allocator, .{});
776     var storage = try Storage.init(counting.allocator(), .{ .input_bytes = 5 });
777     defer storage.deinit(counting.allocator());
778     try std.testing.expectEqual(@as(usize, 1), counting.alloc_index);
779     try std.testing.expectEqual(@as(usize, 6), counting.allocated_bytes);
780     storage.activate();
781     const allocation_count = counting.alloc_index;
782     var exact_reader: std.Io.Reader = .fixed("abcde");
783     const exact = try storage.read(&exact_reader);
784     try std.testing.expectEqualStrings("abcde", exact);
785     try std.testing.expectError(error.InputStorageInUse, storage.preflight(0));
786     storage.release();
787     storage.rejected_count = std.math.maxInt(u64);
788     var oversized_reader: std.Io.Reader = .fixed("abcdef");
789     try std.testing.expectError(
790         error.InputCapacityExceeded,
791         storage.read(&oversized_reader),
792     );
793     const status = storage.status();
794     try std.testing.expect(status.terminal);
795     try std.testing.expectEqual(std.math.maxInt(u64), status.rejected_input_count);
796     try std.testing.expectEqual(allocation_count, counting.alloc_index);
797 }
798 
799 test "one-region recoverable owner preserves availability after rejection" {
800     const Limits = struct { input_bytes: usize };
801     const Capacity = testCapacity(Limits);
802     const Status = struct {
803         phase: alloc_phase.capacity.Phase,
804         in_use: bool,
805         input_bytes: usize,
806         storage_bytes: usize,
807         loaded_input_bytes: usize,
808         high_water_input_bytes: usize,
809         rejected_input_count: u64,
810     };
811     const Spec = testSpec(Limits, Capacity, Status, .recoverable);
812     const Storage = OneRegion(Spec);
813     var storage = try Storage.init(std.testing.allocator, .{ .input_bytes = 3 });
814     defer storage.deinit(std.testing.allocator);
815     storage.activate();
816     try std.testing.expectError(error.InputCapacityExceeded, storage.preflight(4));
817     try std.testing.expectEqual(@as(u64, 1), storage.status().rejected_input_count);
818     var reader: std.Io.Reader = .fixed("new");
819     const input = try storage.read(&reader);
820     try std.testing.expectEqualStrings("new", input);
821     try std.testing.expectEqual(@intFromPtr(storage.bytes.ptr), @intFromPtr(input.ptr));
822     storage.release();
823 }
824 
825 fn testCapacity(comptime Limits: type) type {
826     return struct {
827         input_bytes: usize,
828         storage_bytes: usize,
829 
830         pub fn derive(limits: Limits) DeriveError!@This() {
831             return deriveCapacity(Limits, @This(), "input_bytes", limits);
832         }
833     };
834 }
835 
836 fn testSpec(
837     comptime LimitsType: type,
838     comptime CapacityType: type,
839     comptime StatusType: type,
840     comptime overload_policy: Overload,
841 ) type {
842     return struct {
843         pub const Limits = LimitsType;
844         pub const Capacity = CapacityType;
845         pub const Status = StatusType;
846         pub const Exhaustion = error{ InputStorageInUse, InputCapacityExceeded };
847         pub const DeriveError = @import("one.zig").DeriveError;
848         pub const acquisition: Acquisition = .{
849             .reader = true,
850             .preflight = true,
851         };
852         pub const overload: Overload = overload_policy;
853         pub const limit_field = "input_bytes";
854         pub const loaded_field = "loaded_input_bytes";
855         pub const high_water_field = "high_water_input_bytes";
856         pub const rejected_field = "rejected_input_count";
857         pub const in_use_error: Exhaustion = error.InputStorageInUse;
858         pub const capacity_error: Exhaustion = error.InputCapacityExceeded;
859         pub const claim: alloc_phase.capacity.Declaration = undefined;
860     };
861 }