Skip to main content

caixa_core/
limits.rs

1//! Lunatic-style per-process resource limits — the typed slot of
2//! `caixa.lisp` that wasm-engine consumes at component instantiation.
3//!
4//! See `theory/INSPIRATIONS.md` §III.1 for the prior-art frame: every
5//! caixa Servico runs sandboxed by default; no "trust the author".
6//!
7//! ```lisp
8//! (defcaixa
9//!   :nome   "my-service"
10//!   :versao "0.1.0"
11//!   :kind   Servico
12//!   :limits ((:memory     "64MiB")     ;; max linear memory per instance
13//!            (:fuel       1000000)     ;; max wasm-instructions per request
14//!            (:wall-clock "30s")       ;; max wall-clock per request
15//!            (:cpu        "500m"))     ;; soft cgroup CPU share (millicores)
16//!   :servicos ("servicos/my-service.computeunit.yaml"))
17//! ```
18//!
19//! Authors omit the slot for "no limits" (today's behavior). When set,
20//! wasm-engine M2 wires:
21//!
22//!   - [`LimitsSpec::memory`]      → `wasmtime::StoreLimits::memory_size`
23//!   - [`LimitsSpec::fuel`]        → `Store::set_fuel` + per-tick refill
24//!   - [`LimitsSpec::wall_clock`]  → epoch deadline cancellation
25//!   - [`LimitsSpec::cpu`]         → cgroup-v2 hint propagated via the pod spec
26
27use std::time::Duration;
28
29use serde::{Deserialize, Deserializer, Serialize, Serializer};
30use thiserror::Error;
31
32/// Hard upper bound for `:limits :memory`, in bytes — the
33/// `wasm32-wasip2` linear-memory ceiling. The canonical caixa Servico
34/// compilation target ([`theory/CAIXA-SDLC.md` §V — *Substrate /
35/// Nix*][sdlc-v]) is `wasm32-wasip2`, whose linear memory is 32-bit-
36/// addressed at a 64 KiB page size; the in-spec maximum is
37/// `2^16 pages × 2^16 bytes/page = 2^32` bytes = 4 GiB exactly.
38/// A `:limits :memory` value above this bound is structurally
39/// unreachable under wasm32: wasmtime's `Store::limiter` cannot grow
40/// past the 32-bit address space, so an authored `"8GiB"` either
41/// silently saturates at the engine's effective cap or surfaces as a
42/// `memory.grow` trap at runtime, far from the source caixa.lisp.
43///
44/// Pairs with [`LimitsError::MemoryZero`] (the zero-floor gate added
45/// by the prior typed-shape lift on this axis) to bracket the valid
46/// `:memory` set top-to-bottom: every validated value lies in
47/// `1..=LIMITS_MEMORY_WASM32_MAX_BYTES` (inclusive on both ends).
48/// Renderers ([`crate::render::servico_m2_overlay`] and the M2.5
49/// `wasm-engine` instantiator the ABSORPTION-ROADMAP names as the
50/// downstream wiring) consume the typed value with no re-validation
51/// — the value-shape gate is the structural contract.
52///
53/// Lifted as a typed `pub const` (rather than an inline literal at
54/// the [`LimitsSpec::validate`] call site) so the bound has exactly
55/// one source of truth — a future axis reaching for the same value
56/// (a future `memory64`-target opt-in raising the cap to 2^64, a
57/// wasm-engine smoke test asserting the engine's effective limit
58/// matches the typed bound, the M4 `mesh.pleme.io/v1alpha1/Caixa`
59/// CR materializer's per-`:limits :memory` admission webhook)
60/// reads from one place. Same shape every other typed bound in this
61/// crate carries ([`crate::render::DNS_1123_LABEL_MAX_LEN`],
62/// [`crate::render::GATEWAY_API_HTTP_PATH_MAX_LEN`],
63/// [`crate::render::NATS_SUBJECT_MAX_LEN`]).
64///
65/// [sdlc-v]: https://github.com/pleme-io/theory/blob/main/CAIXA-SDLC.md
66pub const LIMITS_MEMORY_WASM32_MAX_BYTES: u64 = 4 * 1024 * 1024 * 1024;
67
68/// Structural floor for `:limits :memory`, in bytes — the
69/// `wasm32-wasip2` linear-memory page size. The wasm spec defines
70/// linear memory in fixed 64 KiB pages (`2^16` bytes); every typed
71/// memory cap is consumed by `wasmtime::StoreLimits::memory_size` as a
72/// per-component byte ceiling against which the engine checks every
73/// `memory.grow` request. A cap below one page (`< 65536` bytes) is
74/// structurally a "no wasm linear memory allowed" cap — instantiation
75/// of any wasm component that declares `(memory 1)` (i.e. min=1 page,
76/// the canonical default for every cdylib-shaped wasm component cargo
77/// emits) fails immediately with `memory minimum size of 1 pages
78/// exceeds memory limits`; a min=0 component traps the first
79/// `memory.grow(1)` because the next-page allocation would cross the
80/// sub-page cap. Either way the typed value the wasm-engine consumes
81/// is operationally indistinguishable from [`LimitsError::MemoryZero`]
82/// (no memory at all), but the diagnostic surfaces at engine-load
83/// time rather than at caixa-build time, far from the source
84/// caixa.lisp.
85///
86/// Pairs with [`LIMITS_MEMORY_WASM32_MAX_BYTES`] (the 4 GiB upper
87/// cap added by the prior typed-shape lift on this axis) to bracket
88/// the valid `:memory` set top-to-bottom in *operational* units, not
89/// just byte units: every validated value lies in
90/// `LIMITS_MEMORY_WASM32_PAGE_BYTES..=LIMITS_MEMORY_WASM32_MAX_BYTES`
91/// inclusive on both ends — i.e. at least one wasm32 linear memory
92/// page can be allocated, and at most the wasm32 address-space
93/// ceiling fits.
94///
95/// Lifted as a typed `pub const` (rather than an inline literal at
96/// the [`LimitsSpec::validate`] call site) so the bound has exactly
97/// one source of truth — a future axis reaching for the same value
98/// (a future `memory64`-target opt-in raising the page size, the M4
99/// `mesh.pleme.io/v1alpha1/Caixa` CR materializer's per-`:limits
100/// :memory` admission webhook, a wasm-engine smoke test asserting
101/// every instantiated component can fit one page within its
102/// configured cap) reads from one place. Same single-source-of-truth
103/// shape every typed bound in this crate carries
104/// ([`LIMITS_MEMORY_WASM32_MAX_BYTES`],
105/// [`crate::render::DNS_1123_LABEL_MAX_LEN`]).
106pub const LIMITS_MEMORY_WASM32_PAGE_BYTES: u64 = 64 * 1024;
107
108/// Upper-bound ceiling on the `:limits :wall-clock` axis — every
109/// validated [`LimitsSpec::wall_clock`] past [`LimitsSpec::validate`]
110/// lies in `1ms..=LIMITS_WALL_CLOCK_MAX` (inclusive on both ends,
111/// integer-millisecond magnitudes by the canonical-form gate
112/// immediately preceding).
113///
114/// The typed field is `Option<Duration>` (the zero-floor arm
115/// [`LimitsError::WallClockZero`] already rejects `Duration::ZERO`, and
116/// the canonical-form arm [`LimitsError::WallClockNotCanonical`]
117/// already rejects sub-millisecond residue), so a programmatic struct
118/// literal (`LimitsSpec { wall_clock: Some(Duration::from_secs(86_400)),
119/// .. }` — 24h) and the equivalent author-surface form
120/// (`(:limits (:wall-clock "24h"))` — the codec emits `"<n>h"` for any
121/// integer-hour magnitude) both round-trip cleanly through serde — a
122/// structurally unbounded `Duration` ceiling. A `:wall-clock` value far
123/// above the per-process production band (Lunatic / Wasmtime documented
124/// per-call deadlines sit in the seconds-to-minutes range; Kubernetes
125/// activeDeadlineSeconds typical `≤ 3600s`; the longest per-request
126/// timeout any upstream HTTP runtime documents — Kubernetes
127/// ingress-nginx `proxy_read_timeout` — caps at the same 3600s) turns
128/// the typed per-process deadline into a nominal-only contract: the
129/// wasm-engine's epoch-deadline cancellation reaches for a `Duration`
130/// so long no realistic synchronous wasm call can hit it, the runaway-
131/// process invariant the MESH-COMPOSITION §V "no infinite blocking" CSE
132/// invariant pins at the per-Servico layer degenerates to a runtime,
133/// not build-time, contract. Pairs with the
134/// [`crate::POLICY_TIMEOUT_MAX`] cap on the sibling `:politicas :timeout`
135/// mesh-edge axis and the [`crate::POLICY_BREAKER_WINDOW_MAX`] cap on
136/// the sibling `:politicas :circuit-breaker :window` rolling-window
137/// axis — all three close the "structurally unbounded `Duration`
138/// ceiling on a typed slot" footgun the prior zero-floor-and-canonical-
139/// form-only checks left open.
140///
141/// The 1h (3600s = `3_600_000` ms) ceiling matches the largest unit
142/// the shared duration codec emits (`"<n>h"` for any integer-hour
143/// magnitude) — every value in the canonical authoring form's
144/// `<integer><unit>` grammar at or below this cap renders to a clean
145/// canonical string — and matches the two sibling typed-`Duration`
146/// caps already lifted to this surface
147/// ([`crate::POLICY_TIMEOUT_MAX`], [`crate::POLICY_BREAKER_WINDOW_MAX`]).
148/// The three typed-`Duration` axes — per-process `:limits :wall-clock`,
149/// per-edge `:politicas :timeout`, per-breaker `:politicas
150/// :circuit-breaker :window` — now share a single uniform top edge so
151/// the next typed-slot wiring (the wasm-engine M2.5 epoch-deadline
152/// cancellation hook, the future caixa-helm `pleme-computeunit` chart's
153/// `:limits` value mapping, the M4 `mesh.pleme.io/v1alpha1/Caixa` CR
154/// materializer's per-`:limits :wall-clock` admission webhook) reaches
155/// for any of the three knowing the value is in `1ms..=1h` without
156/// re-validating at the renderer layer. The cap sits above the
157/// documented per-request playbook band (Envoy / Istio / Linkerd
158/// production `≤ 60s`, AWS App Mesh / ingress-nginx typical `≤ 300s`,
159/// Kubernetes activeDeadlineSeconds typical `≤ 3600s`) and below the
160/// clearly-pathological "effectively no deadline" floor (`24h`, `7d`,
161/// `Duration::MAX`): a value the author can plausibly want for a
162/// long-running synchronous workflow, but a hard wall above which the
163/// per-process deadline is structurally a non-deadline.
164///
165/// Lifted as a typed `pub const` so the bound has exactly one source
166/// of truth — the wasm-engine M2.5 epoch-deadline wiring, a wasm-engine
167/// smoke test asserting the engine's epoch interrupt fires within the
168/// typed bound, the M4 `mesh.pleme.io/v1alpha1/Caixa` CR materializer's
169/// per-`:limits :wall-clock` admission webhook all read from one place.
170/// Same shape every other typed upper bound in this crate carries
171/// ([`LIMITS_MEMORY_WASM32_MAX_BYTES`], [`crate::POLICY_TIMEOUT_MAX`],
172/// [`crate::POLICY_BREAKER_WINDOW_MAX`],
173/// [`crate::render::DNS_1123_LABEL_MAX_LEN`],
174/// [`crate::render::NATS_SUBJECT_MAX_LEN`]).
175pub const LIMITS_WALL_CLOCK_MAX: Duration = Duration::from_secs(3600);
176
177/// Upper-bound ceiling on the `:limits :cpu` axis, in Kubernetes
178/// millicores — every validated [`LimitsSpec::cpu`] past
179/// [`LimitsSpec::validate`] lies in `1..=LIMITS_CPU_MILLICORES_MAX`
180/// (inclusive on both ends).
181///
182/// The typed field is `Option<u32>` (the zero-floor arm
183/// [`LimitsError::CpuZero`] already rejects `Some(0)` — a zero cgroup
184/// share starves the process), so a programmatic struct literal
185/// (`LimitsSpec { cpu: Some(u32::MAX), .. }` — ≈ 4.3 million cores)
186/// and the equivalent author-surface form (`(:limits (:cpu
187/// "1000000m"))` — the millicore codec parses any `u32`-shaped
188/// magnitude) both round-trip cleanly through serde — a structurally
189/// unbounded `u32` ceiling. The runtime substrate consuming the value
190/// ([`crate::render::servico_m2_overlay`]'s `pleme-computeunit.limits.cpu`
191/// projection, the M2.5 `wasm-engine` instantiator the
192/// `ABSORPTION-ROADMAP` names as the downstream wiring, the future
193/// M4 `mesh.pleme.io/v1alpha1/Caixa` CR materializer's admission
194/// webhook) lands the value verbatim as the K8s pod's
195/// `resources.requests.cpu`. A value far above the largest commodity
196/// node's vCPU count turns the typed slot into an unschedulable hint:
197/// the Kubernetes scheduler refuses to bind the pod to any node
198/// (insufficient `cpu` available), the Servico sits `Pending`
199/// indefinitely, and the per-process CSE invariant (every typed
200/// `:cpu` reaches a node) is a runtime, not build-time, contract —
201/// the canonical declared-but-unschedulable footgun the sibling
202/// `:limits :memory` wasm32-cap arm closes on its peer "cannot be
203/// honored" shape.
204///
205/// The `128_000` (128 cores) ceiling matches the largest commercially
206/// common non-metal cloud Kubernetes node vCPU count (AWS m7i.32xlarge
207/// / c7i.32xlarge = 128 vCPU; Azure HBv3-128rs = 128 vCPU; GCP
208/// c3-standard-128 = 128 vCPU — every major managed-Kubernetes provider
209/// tops out at 128 vCPU on its general-purpose non-metal SKUs) and sits
210/// two orders of magnitude above every realistic per-Servico
211/// production-playbook band (the canonical caixa Servico runs in the
212/// 100m–2000m band; the in-tree
213/// `limits_slot_propagates_into_values_block` smoke test pins
214/// `cpu: Some(500)` = 500m as the load-bearing example, peer to the
215/// `caixa-flux` projector's identical 500m default). A value above this
216/// cap is structurally unschedulable on any commercial managed
217/// Kubernetes node pool: GKE Standard / EKS managed / AKS default
218/// node-group SKU ladders cap at 128 vCPU per node for general-purpose
219/// instance families, so a `:cpu` request above `128_000m` cannot bind to
220/// any node the operator can provision through the standard
221/// cloud-provider control plane. The wasm32-wasip2 single-threaded
222/// execution model the canonical caixa Servico targets
223/// ([`theory/CAIXA-SDLC.md` §V][sdlc-v]) reinforces the structural
224/// argument: a single wasm component cannot saturate more than one
225/// core, so even the Lunatic-style supervised-multi-process host
226/// (`theory/INSPIRATIONS.md` §III.1) — which fans wasm processes across
227/// the host runtime's Tokio thread pool — bounds its useful CPU request
228/// to the host node's vCPU count, never higher.
229///
230/// Lifted as a typed `pub const` (rather than an inline literal at the
231/// [`LimitsSpec::validate`] call site) so the bound has exactly one
232/// source of truth — the future M4
233/// `mesh.pleme.io/v1alpha1/Caixa` CR materializer's per-`:limits :cpu`
234/// admission webhook, the caixa-helm `pleme-computeunit` chart's
235/// resource-request mapping, the M2.5 `wasm-engine` host-runtime
236/// thread-pool sizing hint all read from one place. Same shape every
237/// other typed upper bound in this crate carries
238/// ([`LIMITS_MEMORY_WASM32_MAX_BYTES`], [`LIMITS_WALL_CLOCK_MAX`],
239/// [`crate::POLICY_TIMEOUT_MAX`], [`crate::POLICY_BREAKER_WINDOW_MAX`],
240/// [`crate::POLICY_RATE_LIMIT_MAX`],
241/// [`crate::render::DNS_1123_LABEL_MAX_LEN`]).
242///
243/// [sdlc-v]: https://github.com/pleme-io/theory/blob/main/CAIXA-SDLC.md
244pub const LIMITS_CPU_MILLICORES_MAX: u32 = 128_000;
245
246/// Upper-bound ceiling on the `:limits :fuel` axis, in wasm
247/// instructions per outermost call — every validated
248/// [`LimitsSpec::fuel`] past [`LimitsSpec::validate`] lies in
249/// `1..=LIMITS_FUEL_MAX` (inclusive on both ends).
250///
251/// The typed field is `Option<u64>` (the zero-floor arm
252/// [`LimitsError::FuelZero`] already rejects `Some(0)` — wasmtime
253/// traps the first instruction at `fuel=0`), so a programmatic
254/// struct literal (`LimitsSpec { fuel: Some(u64::MAX), .. }` —
255/// ≈ 1.8 × 10¹⁹ instructions) and the equivalent author-surface
256/// form (`(:limits (:fuel 18446744073709551615))`) both
257/// round-trip cleanly through serde — a structurally unbounded
258/// `u64` ceiling. The runtime substrate consuming the value
259/// ([`crate::render::servico_m2_overlay`]'s
260/// `pleme-computeunit.limits.fuel` projection, the M2.5
261/// `wasm-engine` `Store::set_fuel` call the
262/// `ABSORPTION-ROADMAP` names as the downstream wiring, the
263/// future M4 `mesh.pleme.io/v1alpha1/Caixa` CR materializer's
264/// admission webhook) lands the value verbatim as the
265/// wasmtime store's per-call fuel budget. A value far above any
266/// reachable wasm execution count turns the typed slot into a
267/// no-op budget: the sibling [`LIMITS_WALL_CLOCK_MAX`] (1h)
268/// cap fires before the fuel counter ever drains, the per-call
269/// fuel-tracking contract degenerates to "rely on `:wall-clock`
270/// instead" enforcement, and the per-process CSE invariant
271/// (every typed `:fuel` is a meaningful budget the wasm-engine
272/// can actually consume) is a runtime, not build-time, contract
273/// on every above-cap input — the canonical declared-but-no-op
274/// footgun the sibling `:wall-clock` / `:cpu` / `:memory` cap
275/// arms close on the peer "cannot be honored" /
276/// "unschedulable hint" / "no-op budget" shapes, and the peer
277/// `:politicas :rate-limit` / `:politicas :timeout` /
278/// `:politicas :circuit-breaker :window` /
279/// `:supervisor :max-restarts` cap arms close on every other
280/// `Option<numeric>` axis on the typed Caixa surface.
281///
282/// The `1_000_000_000_000` (10¹² = 1 trillion wasm instructions)
283/// ceiling matches the operational envelope the sibling
284/// [`LIMITS_WALL_CLOCK_MAX`] cap pins: at wasmtime's documented
285/// fuel-tracked execution rate (~10⁸–10⁹ fuel-units per second
286/// on modern x86_64 / aarch64 hosts running wasmtime through
287/// Cranelift — the substrate's wasm32-wasip2 default backend per
288/// the `caixa-feira` runner), the largest realistic per-call
289/// fuel budget reachable within `LIMITS_WALL_CLOCK_MAX` (1h)
290/// sits at ~3.6 × 10¹¹–3.6 × 10¹² fuel-units. The 10¹² cap is
291/// the round-number ceiling above this operational envelope,
292/// sits six orders of magnitude above the canonical fixture
293/// (the in-tree `Caixa::template` documentation and
294/// `caixa-feira` examples carry `:fuel 1_000_000` = 10⁶,
295/// peer to wasmtime's official `Store::set_fuel(1_000_000)`
296/// example in the `wasmtime` book), and surfaces every
297/// paste-from-binary / overflow / u64-magnitude-typo footgun
298/// (`u64::MAX`, `0xFFFF_FFFF_FFFF_FFFF`, large hex literals
299/// confused for instruction-count budgets) at validate time.
300/// A value above this cap is operationally a no-op fuel
301/// counter: the wall-clock deadline ([`LIMITS_WALL_CLOCK_MAX`]
302/// = 3600s × ~10⁹ fuel/sec ≈ 3.6 × 10¹² instructions reachable)
303/// fires before the fuel counter could ever be drained,
304/// so the typed `:fuel` slot becomes a no-op budget far from
305/// the source caixa.lisp. The wasm32-wasip2 single-threaded
306/// execution model the canonical caixa Servico targets
307/// ([`theory/CAIXA-SDLC.md` §V][sdlc-v]) reinforces the
308/// structural argument: a single wasm component cannot
309/// out-execute its host's CPU clock, so even the Lunatic-style
310/// supervised-multi-process host (`theory/INSPIRATIONS.md`
311/// §III.1) bounds its useful fuel-per-call budget to a
312/// per-clock-tick magnitude, never higher.
313///
314/// Lifted as a typed `pub const` (rather than an inline literal
315/// at the [`LimitsSpec::validate`] call site) so the bound has
316/// exactly one source of truth — the future M4
317/// `mesh.pleme.io/v1alpha1/Caixa` CR materializer's per-`:limits
318/// :fuel` admission webhook, the caixa-helm `pleme-computeunit`
319/// chart's fuel-budget mapping, the M2.5 `wasm-engine` host-
320/// runtime `Store::set_fuel` propagation all read from one
321/// place. Same shape every other typed upper bound in this
322/// crate carries ([`LIMITS_MEMORY_WASM32_MAX_BYTES`],
323/// [`LIMITS_WALL_CLOCK_MAX`], [`LIMITS_CPU_MILLICORES_MAX`],
324/// [`crate::POLICY_TIMEOUT_MAX`],
325/// [`crate::POLICY_BREAKER_WINDOW_MAX`],
326/// [`crate::POLICY_RATE_LIMIT_MAX`],
327/// [`crate::SUPERVISOR_MAX_RESTARTS_MAX`],
328/// [`crate::render::DNS_1123_LABEL_MAX_LEN`]).
329///
330/// [sdlc-v]: https://github.com/pleme-io/theory/blob/main/CAIXA-SDLC.md
331pub const LIMITS_FUEL_MAX: u64 = 1_000_000_000_000;
332
333/// Per-process limits. All fields optional — `None` = unbounded for that axis.
334#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)]
335#[serde(rename_all = "camelCase")]
336pub struct LimitsSpec {
337    /// Max linear memory in bytes. Authored as a byte-size string
338    /// (`"64MiB"`, `"1GiB"`, `"512KB"`). Round-trips back to the same
339    /// canonical string on serialize.
340    #[serde(
341        default,
342        skip_serializing_if = "Option::is_none",
343        serialize_with = "ser_byte_size",
344        deserialize_with = "de_byte_size"
345    )]
346    pub memory: Option<u64>,
347
348    /// Max wasm instructions per outermost call (`wasmtime` fuel).
349    /// Plain integer; `None` = unbounded.
350    #[serde(default, skip_serializing_if = "Option::is_none")]
351    pub fuel: Option<u64>,
352
353    /// Wall-clock cap per outermost call. Authored as a duration
354    /// string (`"30s"`, `"500ms"`, `"2m"`).
355    #[serde(
356        default,
357        skip_serializing_if = "Option::is_none",
358        serialize_with = "ser_duration",
359        deserialize_with = "de_duration"
360    )]
361    pub wall_clock: Option<Duration>,
362
363    /// Soft CPU share. Authored as a Kubernetes-style millicore string
364    /// (`"500m"` for half a core, `"2"` or `"2000m"` for two cores).
365    /// Stored as millicores (u32).
366    #[serde(
367        default,
368        skip_serializing_if = "Option::is_none",
369        serialize_with = "ser_millicores",
370        deserialize_with = "de_millicores"
371    )]
372    pub cpu: Option<u32>,
373}
374
375impl LimitsSpec {
376    /// True when no axis is bounded.
377    #[must_use]
378    pub const fn is_empty(&self) -> bool {
379        self.memory().is_none()
380            && self.fuel().is_none()
381            && self.wall_clock().is_none()
382            && self.cpu().is_none()
383    }
384
385    /// Substrate-canonical per-`:limits` `:memory` Lunatic-per-process
386    /// wasm32-linear-memory byte-cap scalar accessor every consumer of
387    /// the Servico's `wasmtime::StoreLimits::memory_size` propagation
388    /// keys off — returns the author-declared `:limits :memory` typed
389    /// byte-cap verbatim as an `Option<u64>`, copied out of the typed
390    /// slot's own `Option<u64>` storage (`Option<u64>` is `Copy`, so
391    /// the accessor returns by value; no borrow of `&self` past the
392    /// call). `None` when the slot is absent (the "no memory cap
393    /// declared — engine-default applies, today the pre-M2 unbounded-
394    /// linear-memory shape" arm the module-level docstring names on
395    /// [`LimitsSpec::memory`] itself — [`LimitsSpec::is_empty`]'s
396    /// `memory().is_none()` arm reads this predicate too, so an
397    /// authored-but-unset `:limits (:memory ())` round-trips to a
398    /// `servico_m2_overlay` emission structurally identical to one
399    /// that omits the slot entirely).
400    ///
401    /// The `:limits :memory` slot carries the "per-process wasm32
402    /// linear-memory byte-cap" Lunatic-shaped sandboxing contract
403    /// (`theory/INSPIRATIONS.md` §III.1) — the typed slot's
404    /// `Option<u64>` accept-set (zero-floor rejected through
405    /// [`LimitsError::MemoryZero`], wasm32-page-floor rejected through
406    /// [`LimitsError::MemoryBelowWasm32Page`], upper-bounded by
407    /// [`LIMITS_MEMORY_WASM32_MAX_BYTES`], authored as a byte-size
408    /// string that round-trips back to the canonical form through
409    /// [`ser_byte_size`] / [`de_byte_size`]) maps onto the wasmtime
410    /// `Store::limiter`-side `memory_size` projection the wasm-engine
411    /// M2 wires and, via [`crate::render::servico_m2_overlay`], onto
412    /// the `pleme-computeunit` Helm-library-chart values sub-block's
413    /// `limits.memory` key that lands as the ComputeUnit CR's
414    /// `spec.limits.memory` field.
415    ///
416    /// Prior to this lift the `.memory` field was accessed inline at
417    /// four sites inside `impl LimitsSpec` — [`LimitsSpec::is_empty`]'s
418    /// `self.memory.is_none()` arm and three [`LimitsSpec::validate`]
419    /// arms (the numeric zero-floor arm at line 397, the wasm32-page
420    /// structural floor arm at line 427, and the wasm32 upper-cap
421    /// arm at line 449) — four open-coded field-accesses that
422    /// expressed no compile-time link back to the typed slot. A
423    /// future extension of the `:limits :memory` axis to a richer
424    /// author surface — a per-instance memory-declaration override
425    /// the operator pins through a future ComputeUnit CR-side
426    /// `spec.limits.memory` overlay, a split of the single `u64`
427    /// byte-cap into a `{min, max}` pair once wasm32's `(memory M N)`
428    /// two-arg form promotes past its current single-`max` typed
429    /// bound, a wasm64 promotion once the wasm-engine grows past the
430    /// wasm32 4 GiB structural ceiling — would have had to be
431    /// threaded through every open-coded copy in lockstep or the
432    /// emptiness predicate and the validate call would silently
433    /// disagree on which cap a given [`LimitsSpec`] resolves to.
434    /// Lifting the resolution to a typed method on the substrate
435    /// primitive means every downstream consumer of the Servico's
436    /// per-`:limits` byte-cap surface reaches for exactly one typed
437    /// dispatch — the resolver's accept-set migrates as a unit on any
438    /// future axis addition.
439    ///
440    /// First `Option<Copy-T>`-return accessor on the M2 slot family
441    /// (peer of the sibling per-`:politicas` [`crate::MeshPolicy::mtls_required`]
442    /// c0110f1 `Option<bool>` accessor, per-`:politicas`
443    /// [`crate::MeshPolicy::retries`] bdfb399 `Option<u32>` accessor,
444    /// and per-`:politicas` [`crate::MeshPolicy::timeout`] 7073d0f
445    /// `Option<Duration>` accessor on the M3 mesh-slot family — same
446    /// "one typed dispatch on the substrate primitive, thin
447    /// projections at each consumer" discipline extended onto the
448    /// peer per-`:limits` typed-`u64` optional-scalar axis; opens the
449    /// "optional per-slot Copy-T scalar" projection pattern the
450    /// sibling per-`:limits` `:fuel` (Option<u64>) / `:wall-clock`
451    /// (Option<Duration>) / `:cpu` (Option<u32>) future lifts fold
452    /// on). Named `memory()` to match the storage field's name; the
453    /// accessor's identity maps onto the canonical Lunatic-shaped
454    /// `theory/INSPIRATIONS.md` §III.1 vocabulary the slot's docstring
455    /// already carries.
456    #[must_use]
457    pub const fn memory(&self) -> Option<u64> {
458        self.memory
459    }
460
461    /// Substrate-canonical per-`:limits` `:fuel` wasmtime-per-call
462    /// wasm-instruction budget scalar accessor every consumer of the
463    /// Servico's `wasmtime::Store::set_fuel` propagation keys off —
464    /// returns the author-declared `:limits :fuel` typed
465    /// wasm-instruction budget verbatim as an `Option<u64>`, copied
466    /// out of the typed slot's own `Option<u64>` storage
467    /// (`Option<u64>` is `Copy`, so the accessor returns by value; no
468    /// borrow of `&self` past the call). `None` when the slot is
469    /// absent (the "no fuel budget declared — engine-default applies,
470    /// today the pre-M2 unbounded-fuel-counter shape" arm the
471    /// module-level docstring names on [`LimitsSpec::fuel`] itself —
472    /// [`LimitsSpec::is_empty`]'s `fuel().is_none()` arm reads this
473    /// predicate too, so an authored-but-unset `:limits (:fuel ())`
474    /// round-trips to a `servico_m2_overlay` emission structurally
475    /// identical to one that omits the slot entirely).
476    ///
477    /// The `:limits :fuel` slot carries the "per-call wasm-instruction
478    /// budget" wasmtime-shaped sandboxing contract
479    /// (`theory/INSPIRATIONS.md` §III.1 — Lunatic's supervised
480    /// wasm-`Store`-per-process fuel accounting, translated onto
481    /// pleme-io's typed `:limits` slot) — the typed slot's
482    /// `Option<u64>` accept-set (zero-floor rejected through
483    /// [`LimitsError::FuelZero`] because wasmtime traps the first
484    /// instruction at `fuel=0`, upper-bounded by [`LIMITS_FUEL_MAX`]
485    /// (10¹² wasm instructions — the operationally-reachable
486    /// per-call budget within the sibling [`LIMITS_WALL_CLOCK_MAX`]
487    /// 1h ceiling)) maps onto the wasmtime `Store::set_fuel` call
488    /// the M2.5 wasm-engine wires per outermost call and, via
489    /// [`crate::render::servico_m2_overlay`], onto the
490    /// `pleme-computeunit` Helm-library-chart values sub-block's
491    /// `limits.fuel` key that lands as the `ComputeUnit` CR's
492    /// `spec.limits.fuel` field.
493    ///
494    /// Prior to this lift the `.fuel` field was accessed inline at
495    /// two sites inside `impl LimitsSpec` — [`LimitsSpec::is_empty`]'s
496    /// `self.fuel.is_none()` arm and [`LimitsSpec::validate`]'s
497    /// `if let Some(f) = self.fuel { … }` zero-floor + upper-cap
498    /// bracket arm — two open-coded field-accesses that expressed no
499    /// compile-time link back to the typed slot. A future extension
500    /// of the `:limits :fuel` axis to a richer author surface — a
501    /// per-instance `ComputeUnit` CR-side `spec.limits.fuel` overlay
502    /// the operator pins per-cluster, a wasm-instruction-count →
503    /// wasmtime-fuel-unit rescale once the fuel-tracking backend
504    /// switches from Cranelift's implicit 1:1 count to a
505    /// per-opcode-weighted budget, a split of the single
506    /// per-outermost-call `u64` budget into a `{per_call, per_second}`
507    /// pair once the wasm-engine grows a sustained-throughput cap —
508    /// would have had to be threaded through every open-coded copy in
509    /// lockstep or the emptiness predicate and the validate call
510    /// would silently disagree on which fuel budget a given
511    /// [`LimitsSpec`] resolves to. Lifting the resolution to a typed
512    /// method on the substrate primitive means every downstream
513    /// consumer of the Servico's per-`:limits` fuel-budget surface
514    /// reaches for exactly one typed dispatch — the resolver's
515    /// accept-set migrates as a unit on any future axis addition.
516    ///
517    /// Second `Option<Copy-T>`-return accessor on the M2 slot family
518    /// (peer of the sibling per-`:limits` [`LimitsSpec::memory`]
519    /// (620c067) `Option<u64>` accessor — same typed-`u64`
520    /// optional-scalar shape, extended to the peer per-`:limits`
521    /// wasm-instruction-budget axis; sibling to
522    /// [`crate::MeshPolicy::mtls_required`] (c0110f1) / [`crate::MeshPolicy::retries`]
523    /// (bdfb399) / [`crate::MeshPolicy::timeout`] (7073d0f) on the
524    /// closed M3 mesh-slot `Option<Copy-T>` accessor family). The
525    /// pair `(memory(), fuel())` jointly projects the two `Option<u64>`
526    /// axes every M2 `:limits` consumer that fans on
527    /// wasm-linear-memory-cap + wasm-fuel-budget keys off. Two of the
528    /// four `:limits` axes now route through a typed dispatch on the
529    /// substrate primitive; the two remaining (`wall_clock:
530    /// Option<Duration>`, `cpu: Option<u32>`) fold on the same
531    /// one-line accessor + is_empty-arm-route + validate-arm-route +
532    /// three-test pattern. Named `fuel()` to match the storage field's
533    /// name; the accessor's identity maps onto the canonical
534    /// wasmtime-`Store::set_fuel`-shaped vocabulary the slot's
535    /// docstring already carries.
536    #[must_use]
537    pub const fn fuel(&self) -> Option<u64> {
538        self.fuel
539    }
540
541    /// Substrate-canonical per-`:limits` `:wall-clock` wasmtime-per-call
542    /// wall-clock deadline scalar accessor every consumer of the
543    /// Servico's `wasmtime::Store::epoch_deadline_*` / `wasi:clocks`
544    /// propagation keys off — returns the author-declared `:limits
545    /// :wall-clock` typed `Duration` verbatim as an `Option<Duration>`,
546    /// copied out of the typed slot's own `Option<Duration>` storage
547    /// (`Duration` is `Copy`, so `Option<Duration>` is `Copy` and the
548    /// accessor returns by value; no borrow of `&self` past the call).
549    /// `None` when the slot is absent (the "no wall-clock deadline
550    /// declared — engine-default applies, today the pre-M2
551    /// unbounded-wall-clock shape" arm the module-level docstring names
552    /// on [`LimitsSpec::wall_clock`] itself — [`LimitsSpec::is_empty`]'s
553    /// `wall_clock().is_none()` arm reads this predicate too, so an
554    /// authored-but-unset `:limits (:wall-clock ())` round-trips to a
555    /// `servico_m2_overlay` emission structurally identical to one that
556    /// omits the slot entirely).
557    ///
558    /// The `:limits :wall-clock` slot carries the "per-outermost-call
559    /// wall-clock deadline" wasmtime-shaped sandboxing contract
560    /// (`theory/INSPIRATIONS.md` §III.1 — Lunatic's supervised
561    /// wasm-`Store`-per-process epoch-deadline accounting, translated
562    /// onto pleme-io's typed `:limits` slot) — the typed slot's
563    /// `Option<Duration>` accept-set (zero-floor rejected through
564    /// [`LimitsError::WallClockZero`] because a zero deadline traps the
565    /// first instruction; integer-millisecond granularity enforced
566    /// through [`LimitsError::WallClockNotCanonical`] because the
567    /// duration codec's canonical form emits `"1500ms"` not `"1.5s"`
568    /// and the operator's wall-clock scheduler quantizes at
569    /// milliseconds; upper-bounded by [`LIMITS_WALL_CLOCK_MAX`] (1h —
570    /// the coarsest per-call deadline any operationally-reachable
571    /// Servico can honor without spanning multiple scheduler epochs))
572    /// maps onto the wasmtime `Store::epoch_deadline_*` call the M2.5
573    /// wasm-engine wires per outermost call and, via
574    /// [`crate::render::servico_m2_overlay`], onto the
575    /// `pleme-computeunit` Helm-library-chart values sub-block's
576    /// `limits.wallClock` key that lands as the `ComputeUnit` CR's
577    /// `spec.limits.wallClock` field.
578    ///
579    /// Prior to this lift the `.wall_clock` field was accessed inline at
580    /// two sites inside `impl LimitsSpec` — [`LimitsSpec::is_empty`]'s
581    /// `self.wall_clock.is_none()` arm and [`LimitsSpec::validate`]'s
582    /// `if let Some(w) = self.wall_clock { … }` zero-floor +
583    /// canonical-form + upper-cap bracket arm — two open-coded
584    /// field-accesses that expressed no compile-time link back to the
585    /// typed slot. A future extension of the `:limits :wall-clock` axis
586    /// to a richer author surface — a per-instance `ComputeUnit`
587    /// CR-side `spec.limits.wallClock` overlay the operator pins
588    /// per-cluster, a wall-clock-vs-monotonic-clock discriminator once
589    /// the wasm-engine grows a `:limits (:wall-clock (:kind monotonic
590    /// …))` axis, a split of the single per-outermost-call `Duration`
591    /// budget into a `{deadline, warn_at}` pair once the wasm-engine
592    /// grows a soft-deadline warning surface — would have had to be
593    /// threaded through every open-coded copy in lockstep or the
594    /// emptiness predicate and the validate call would silently
595    /// disagree on which deadline a given [`LimitsSpec`] resolves to.
596    /// Lifting the resolution to a typed method on the substrate
597    /// primitive means every downstream consumer of the Servico's
598    /// per-`:limits` wall-clock-deadline surface reaches for exactly
599    /// one typed dispatch — the resolver's accept-set migrates as a
600    /// unit on any future axis addition.
601    ///
602    /// Third `Option<Copy-T>`-return accessor on the M2 slot family
603    /// (peer of the sibling per-`:limits` [`LimitsSpec::memory`]
604    /// (620c067) `Option<u64>` accessor and per-`:limits`
605    /// [`LimitsSpec::fuel`] (795dee7) `Option<u64>` accessor — same
606    /// typed-optional-scalar shape extended to the peer per-`:limits`
607    /// wall-clock-deadline axis; sibling to [`crate::MeshPolicy::timeout`]
608    /// (7073d0f) on the closed M3 mesh-slot `Option<Duration>` accessor
609    /// axis — same typed-`Duration` shape extended from the M3
610    /// per-call-timeout to the M2 per-outermost-call deadline). The
611    /// triple `(memory(), fuel(), wall_clock())` jointly projects three
612    /// of the four `Option<Copy-T>` axes every M2 `:limits` consumer
613    /// that fans on wasm-linear-memory-cap + wasm-fuel-budget +
614    /// wall-clock-deadline keys off. Three of the four `:limits` axes
615    /// now route through a typed dispatch on the substrate primitive;
616    /// the one remaining (`cpu: Option<u32>`) folds on the same
617    /// one-line accessor + is_empty-arm-route + validate-arm-route +
618    /// three-test pattern in the next run, closing the M2 `:limits`
619    /// slot family's `Option<Copy-T>` accessor axis. Named `wall_clock()`
620    /// to match the storage field's name; the accessor's identity maps
621    /// onto the canonical wasmtime-`Store::epoch_deadline_*`-shaped
622    /// vocabulary the slot's docstring already carries.
623    #[must_use]
624    pub const fn wall_clock(&self) -> Option<Duration> {
625        self.wall_clock
626    }
627
628    /// Substrate-canonical per-`:limits` `:cpu` Kubernetes-millicore
629    /// soft cgroup-share scalar accessor every consumer of the Servico's
630    /// pod-spec `resources.requests.cpu` propagation keys off — returns
631    /// the author-declared `:limits :cpu` typed millicore magnitude
632    /// verbatim as an `Option<u32>`, copied out of the typed slot's own
633    /// `Option<u32>` storage (`Option<u32>` is `Copy`, so the accessor
634    /// returns by value; no borrow of `&self` past the call). `None`
635    /// when the slot is absent (the "no cpu share declared —
636    /// scheduler-default applies, today the pre-M2 unbounded-cpu-share
637    /// shape" arm the module-level docstring names on
638    /// [`LimitsSpec::cpu`] itself — [`LimitsSpec::is_empty`]'s
639    /// `cpu().is_none()` arm reads this predicate too, so an
640    /// authored-but-unset `:limits (:cpu ())` round-trips to a
641    /// `servico_m2_overlay` emission structurally identical to one that
642    /// omits the slot entirely).
643    ///
644    /// The `:limits :cpu` slot carries the "per-process soft cgroup-v2
645    /// CPU share" Kubernetes-scheduler-shaped sandboxing hint
646    /// (`theory/INSPIRATIONS.md` §III.1 — Lunatic's supervised
647    /// wasm-`Store`-per-process host-runtime CPU accounting, translated
648    /// onto pleme-io's typed `:limits` slot as a scheduler-facing
649    /// millicore request the pod's kubelet propagates to the container's
650    /// cgroup) — the typed slot's `Option<u32>` accept-set (zero-floor
651    /// rejected through [`LimitsError::CpuZero`] because a zero cgroup
652    /// share starves the process; upper-bounded by
653    /// [`LIMITS_CPU_MILLICORES_MAX`] (128 cores — the largest commercially-
654    /// common non-metal cloud Kubernetes node vCPU count on managed GKE
655    /// / EKS / AKS general-purpose SKUs)) maps onto the K8s pod spec's
656    /// `spec.containers[].resources.requests.cpu` field the
657    /// M2.5 `wasm-engine` host-runtime lands on the `ComputeUnit` CR-side
658    /// pod template and, via [`crate::render::servico_m2_overlay`], onto
659    /// the `pleme-computeunit` Helm-library-chart values sub-block's
660    /// `limits.cpu` key that lands as the `ComputeUnit` CR's
661    /// `spec.limits.cpu` field.
662    ///
663    /// Prior to this lift the `.cpu` field was accessed inline at two
664    /// sites inside `impl LimitsSpec` — [`LimitsSpec::is_empty`]'s
665    /// `self.cpu.is_none()` arm and [`LimitsSpec::validate`]'s
666    /// `if let Some(m) = self.cpu { … }` zero-floor + upper-cap bracket
667    /// arm — two open-coded field-accesses that expressed no
668    /// compile-time link back to the typed slot. A future extension of
669    /// the `:limits :cpu` axis to a richer author surface — a
670    /// per-instance `ComputeUnit` CR-side `spec.limits.cpu` overlay the
671    /// operator pins per-cluster, a split of the single `u32` millicore
672    /// request into a `{request, limit}` pair once the pod spec's
673    /// `resources.requests.cpu` / `resources.limits.cpu` distinction
674    /// promotes past its current single-request author surface, a
675    /// millicore → cgroup-v2 `cpu.weight` rescale once the operator's
676    /// scheduler-facing translation lands past its current kubelet
677    /// passthrough — would have had to be threaded through every
678    /// open-coded copy in lockstep or the emptiness predicate and the
679    /// validate call would silently disagree on which cgroup share a
680    /// given [`LimitsSpec`] resolves to. Lifting the resolution to a
681    /// typed method on the substrate primitive means every downstream
682    /// consumer of the Servico's per-`:limits` cpu-share surface reaches
683    /// for exactly one typed dispatch — the resolver's accept-set
684    /// migrates as a unit on any future axis addition.
685    ///
686    /// Fourth and final `Option<Copy-T>`-return accessor on the M2 slot
687    /// family (peer of the sibling per-`:limits` [`LimitsSpec::memory`]
688    /// (620c067) `Option<u64>` accessor, per-`:limits`
689    /// [`LimitsSpec::fuel`] (795dee7) `Option<u64>` accessor, and
690    /// per-`:limits` [`LimitsSpec::wall_clock`] (8cb717b)
691    /// `Option<Duration>` accessor — same typed-optional-scalar shape
692    /// extended to the peer per-`:limits` cgroup-cpu-share axis; sibling
693    /// to [`crate::MeshPolicy::mtls_required`] (c0110f1) /
694    /// [`crate::MeshPolicy::retries`] (bdfb399) /
695    /// [`crate::MeshPolicy::timeout`] (7073d0f) on the closed M3
696    /// mesh-slot `Option<Copy-T>` accessor family). The four-tuple
697    /// `(memory(), fuel(), wall_clock(), cpu())` jointly projects every
698    /// `Option<Copy-T>` axis on the M2 `:limits` slot every consumer
699    /// that fans on wasm-linear-memory-cap + wasm-fuel-budget +
700    /// wall-clock-deadline + cgroup-cpu-share keys off — closes the M2
701    /// `:limits` slot family's `Option<Copy-T>` accessor axis (the
702    /// last unlifted `:limits` field-access site on the M2 slot family;
703    /// every axis now routes through a typed dispatch on the substrate
704    /// primitive, with no open-coded field access anywhere on the impl).
705    /// Named `cpu()` to match the storage field's name; the accessor's
706    /// identity maps onto the canonical Kubernetes-`resources.requests.cpu`-
707    /// shaped vocabulary the slot's docstring already carries.
708    #[must_use]
709    pub const fn cpu(&self) -> Option<u32> {
710        self.cpu
711    }
712
713    /// Reject operationally-meaningless zero values on every declared
714    /// axis. Each axis remains optional — omitting a field expresses
715    /// "no bound on this axis"; the bug being closed is *carrying* a
716    /// zero value, which the wasm-engine consumes as "trap the first
717    /// instruction" / "instantiation refused" / "immediate timeout"
718    /// rather than the author's intended "an unspecified bound".
719    ///
720    /// Mirrors the discipline applied to `:politicas` axes in
721    /// `AplicacaoSpec::validate` and to `SupervisorSpec::max_restarts`
722    /// — every typed value carried by a slot is either absent or
723    /// meaningfully non-zero.
724    pub fn validate(&self) -> Result<(), LimitsError> {
725        // Route the `:memory` axis's four value-shape gates
726        // (zero-floor → wasm32-page-floor → wasm32-address-cap →
727        // page-multiple) through the substrate helper
728        // [`crate::render::require_positive_quantum_multiple_bounded_u64`]
729        // rather than four sequential inline
730        // `if let Some(m) = self.memory()` guards each restating one
731        // arm. Brings the `:memory` axis onto the same "one substrate
732        // helper per typed axis" discipline the peer `:fuel` (routed
733        // through [`crate::render::require_positive_bounded_u64`]),
734        // `:wall-clock` (through
735        // [`crate::render::require_positive_canonical_bounded_duration`]),
736        // and `:cpu` (through
737        // [`crate::render::require_positive_bounded_u32`]) axes
738        // already carry — every `LimitsSpec::validate` axis is now
739        // exactly one typed-helper dispatch, with the four-arm
740        // ordering (zero → below-quantum → cap → not-multiple)
741        // promoted from a per-site convention four inline blocks
742        // re-derived by hand to a structural contract on the
743        // substrate primitive. Byte-equal today: the helper fires the
744        // same four arms in the same canonical order at the same
745        // boundary values, threading the offending byte count into
746        // the same `MemoryBelowWasm32Page` / `MemoryExceedsWasm32Cap`
747        // / `MemoryNotPageMultiple` discriminator fields the four
748        // pre-lift inline arms already carried, so every existing
749        // per-arm test in this module continues to pin the same
750        // shape unchanged. Pinned end-to-end by
751        // `validate_memory_axis_routes_through_quantum_multiple_bounded_helper`.
752        if let Some(m) = self.memory() {
753            crate::render::require_positive_quantum_multiple_bounded_u64(
754                m,
755                LIMITS_MEMORY_WASM32_PAGE_BYTES,
756                LIMITS_MEMORY_WASM32_MAX_BYTES,
757                || LimitsError::MemoryZero,
758                LimitsError::memory_below_wasm32_page,
759                LimitsError::memory_exceeds_wasm32_cap,
760                LimitsError::memory_not_page_multiple,
761            )?;
762        }
763        // Zero-floor + upper-cap bracket on the typed `:fuel` axis. See
764        // [`crate::render::require_positive_bounded_u64`] for the
765        // ordering discipline (zero-floor arm strictly precedes cap arm
766        // so `Some(0)` surfaces the self-locating `FuelZero` diagnostic
767        // with its omit-axis remediation directly named, not the
768        // misleading `0 > LIMITS_FUEL_MAX == false` cap-arm miss).
769        // Until this bracket landed the `Option<u64>` slot accepted any
770        // value past zero (the parser's only upper bound was `u64::MAX`),
771        // so `(:fuel 18446744073709551615)` round-tripped cleanly
772        // through serde and the per-process CSE invariant (no value the
773        // wasm-engine's fuel counter can't honor as a meaningful budget
774        // before the sibling `:wall-clock` deadline fires) was a
775        // runtime, not build-time, contract on every above-cap input
776        // — the canonical declared-but-no-op footgun the sibling
777        // [`LimitsError::MemoryExceedsWasm32Cap`] /
778        // [`LimitsError::WallClockExceedsCap`] /
779        // [`LimitsError::CpuExceedsCap`] arms close on the peer
780        // "cannot be honored" / "unschedulable hint" /
781        // "nominal-only deadline" shapes, the peer
782        // [`crate::AplicacaoError::PolicyTimeoutExceedsCap`] /
783        // [`crate::AplicacaoError::PolicyBreakerWindowExceedsCap`] /
784        // [`crate::AplicacaoError::PolicyRateLimitExceedsCap`] arms
785        // close on the no-op-deadline / lifetime-counter / no-op-limiter
786        // shapes, and the
787        // [`crate::SupervisorError::MaxRestartsExceedsCap`] arm closes
788        // on the no-op-supervisor shape. The four `:limits` axes are
789        // now uniformly bracketed top and bottom (`:memory` in
790        // `LIMITS_MEMORY_WASM32_PAGE_BYTES..=LIMITS_MEMORY_WASM32_MAX_BYTES`,
791        // `:fuel` in `1..=LIMITS_FUEL_MAX`, `:wall-clock` in
792        // `1ms..=LIMITS_WALL_CLOCK_MAX`, `:cpu` in
793        // `1..=LIMITS_CPU_MILLICORES_MAX`).
794        if let Some(f) = self.fuel() {
795            crate::render::require_positive_bounded_u64(
796                f,
797                LIMITS_FUEL_MAX,
798                || LimitsError::FuelZero,
799                LimitsError::fuel_exceeds_cap,
800            )?;
801        }
802        if let Some(w) = self.wall_clock() {
803            // Zero-floor + integer-millisecond canonical-form +
804            // upper-cap bracket on the typed `:wall-clock` axis. See
805            // [`crate::render::require_positive_canonical_bounded_duration`]
806            // for the full three-arm ordering discipline (zero-floor
807            // strictly precedes canonical-form so `Duration::ZERO`
808            // surfaces the self-locating `WallClockZero` diagnostic;
809            // canonical-form strictly precedes the cap arm so a
810            // sub-millisecond above-cap value surfaces the more
811            // fundamental round-trip-shape diagnostic first) and the
812            // three peer typed-`Duration` sites that share this
813            // canonical bracket ([`crate::MeshPolicy::timeout`],
814            // [`crate::CircuitBreaker::window`],
815            // [`crate::SupervisorSpec::restart_window`]). Every
816            // validated value lies in `1ms..=LIMITS_WALL_CLOCK_MAX`
817            // (1ms..=1h), integer-millisecond granularity.
818            crate::render::require_positive_canonical_bounded_duration(
819                w,
820                LIMITS_WALL_CLOCK_MAX,
821                || LimitsError::WallClockZero,
822                LimitsError::wall_clock_not_canonical,
823                LimitsError::wall_clock_exceeds_cap,
824            )?;
825        }
826        // Zero-floor + upper-cap bracket on the typed `:cpu` axis. See
827        // [`crate::render::require_positive_bounded_u32`] for the
828        // ordering discipline (zero-floor arm strictly precedes cap arm
829        // so `Some(0)` surfaces the self-locating `CpuZero` diagnostic
830        // with its omit-axis remediation directly named, not the
831        // misleading `0 > LIMITS_CPU_MILLICORES_MAX == false` cap-arm
832        // miss). The bracket set is `1..=LIMITS_CPU_MILLICORES_MAX`
833        // (128 cores = 128_000 millicores — the largest commercially-
834        // common non-metal cloud Kubernetes node vCPU count). Until
835        // this bracket landed the millicore codec accepted any
836        // `Option<u32>` past zero (the prior numeric-zero arm's only
837        // floor), so `(:cpu "1000000m")` (1000 cores) round-tripped
838        // cleanly through serde and the per-axis CSE invariant (no
839        // value the Kubernetes scheduler can't honor) was a runtime,
840        // not build-time, contract on every above-cap input: the
841        // `pleme-computeunit` chart's `resources.requests.cpu` landed
842        // verbatim, the pod sat `Pending` indefinitely with a `0/N
843        // nodes are available: N Insufficient cpu` event, and the
844        // typed `:cpu` slot became an unschedulable hint far from the
845        // source caixa.lisp. Closes the same gap the wasm32-wasip2
846        // upper ceiling closes on the `:memory` axis — the typed `:cpu`
847        // axis is now operationally bracketed. Peer with every sibling
848        // cap arm on this surface ([`LimitsError::MemoryExceedsWasm32Cap`],
849        // [`LimitsError::WallClockExceedsCap`],
850        // [`crate::AplicacaoError::PolicyTimeoutExceedsCap`],
851        // [`crate::AplicacaoError::PolicyRetriesExceedsCap`],
852        // [`crate::AplicacaoError::PolicyBreakerMaxFailuresExceedsCap`],
853        // [`crate::AplicacaoError::PolicyBreakerWindowExceedsCap`],
854        // [`crate::AplicacaoError::PolicyRateLimitExceedsCap`],
855        // [`crate::SupervisorError::MaxRestartsExceedsCap`]).
856        if let Some(m) = self.cpu() {
857            crate::render::require_positive_bounded_u32(
858                m,
859                LIMITS_CPU_MILLICORES_MAX,
860                || LimitsError::CpuZero,
861                LimitsError::cpu_exceeds_cap,
862            )?;
863        }
864        Ok(())
865    }
866}
867
868#[derive(Debug, Error, PartialEq, Eq)]
869pub enum LimitsError {
870    #[error("byte-size: missing magnitude in {0:?}")]
871    EmptyByteSize(String),
872    #[error("byte-size: unknown unit {unit:?} (expected one of B, KB, MB, GB, KiB, MiB, GiB)")]
873    UnknownByteUnit { unit: String },
874    #[error("byte-size: failed to parse magnitude {0:?}")]
875    BadByteMagnitude(String),
876    #[error(
877        "byte-size: magnitude {value:?} is not a non-negative integer — the canonical \
878         authoring form for `:limits :memory` is `<integer><unit>` (e.g. `\"1024\"`, \
879         `\"64MiB\"`, `\"1GiB\"`) with no decimal point and no leading `+` sign. A \
880         fractional / decimal-shaped magnitude (`\"1.5KiB\"`, `\"1.0MiB\"`, `\"0.5GiB\"`, \
881         `\"+1024\"`) round-trips through `render_byte_size` to a *different* canonical \
882         form (`\"1536\"`, `\"1MiB\"`, `\"512MiB\"`, `\"1KiB\"`) on first serialize — \
883         breaking the THEORY.md §V.2.7 render-determinism contract every typed slot \
884         carries. Pick an integer magnitude in the unit that divides cleanly (write \
885         `\"1536\"` instead of `\"1.5KiB\"`; `\"512MiB\"` instead of `\"0.5GiB\"`)"
886    )]
887    NonIntegerByteMagnitude { value: String },
888    #[error(
889        "byte-size: magnitude {value:?} has a non-canonical leading zero — the canonical \
890         authoring form for `:limits :memory` is `<integer><unit>` (e.g. `\"64MiB\"`, \
891         `\"1GiB\"`, `\"512KiB\"`, `\"1024\"`) with no leading-zero padding on the magnitude. \
892         A leading-zero magnitude (`\"064MiB\"`, `\"01024\"`, `\"00KiB\"`, `\"0500MB\"`) round-trips \
893         through `render_byte_size` to a *different* canonical form (`\"64MiB\"`, `\"1KiB\"`, \
894         `\"0\"`, `\"500MB\"`) on first serialize — breaking the THEORY.md Part V \
895         render-determinism contract every typed slot carries. Strip the leading zeros \
896         (write `\"64MiB\"` instead of `\"064MiB\"`)"
897    )]
898    LeadingZeroByteMagnitude { value: String },
899    #[error(
900        "byte-size: value {value:?} contains whitespace byte 0x{byte:02x} — the canonical \
901         authoring form for `:limits :memory` is `<integer><unit>` (e.g. `\"64MiB\"`, \
902         `\"1GiB\"`, `\"512KiB\"`, `\"1024\"`) with no whitespace bytes anywhere. A \
903         whitespace-carrying shape (`\" 64MiB\"`, `\"64MiB \"`, `\"64 MiB\"`, `\"\\t64MiB\"`, \
904         `\"64MiB\\n\"`) round-trips through `render_byte_size` to a *different* canonical \
905         form (`\"64MiB\"`) on first serialize — breaking the THEORY.md Part V \
906         render-determinism contract every typed slot carries. Strip every whitespace byte \
907         (write `\"64MiB\"` verbatim)"
908    )]
909    WhitespaceInByteSize { value: String, byte: u8 },
910    #[error(
911        "byte-size: value {value:?} contains a non-ASCII Unicode whitespace character \
912         {ch:?} (U+{codepoint:04X}) — the canonical authoring form for `:limits :memory` \
913         is `<integer><unit>` (e.g. `\"64MiB\"`, `\"1GiB\"`, `\"512KiB\"`, `\"1024\"`) \
914         with no whitespace characters anywhere (ASCII or Unicode). A non-ASCII-whitespace-\
915         carrying shape (`\"\\u{{00A0}}64MiB\"` — paste-from-typography NBSP prefix; \
916         `\"64MiB\\u{{2028}}\"` — paste-from-web-doc line-separator suffix; \
917         `\"64\\u{{2003}}MiB\"` — paste-from-typography EM-SPACE between magnitude and \
918         unit) survives the pre-existing `u8::is_ascii_whitespace` byte-scan (none of \
919         its bytes match the ASCII whitespace set) but `str::trim` (which uses \
920         `char::is_whitespace` — the Unicode `White_Space` property, strictly wider than \
921         the ASCII byte set) silently strips it at parse entry, and the value round-trips \
922         through `render_byte_size` to a *different* canonical form (`\"64MiB\"`) on \
923         first serialize — breaking the THEORY.md Part V render-determinism contract \
924         every typed slot carries. Strip every non-ASCII whitespace character (write \
925         `\"64MiB\"` verbatim with only ASCII bytes)"
926    )]
927    NonAsciiWhitespaceInByteSize {
928        value: String,
929        ch: char,
930        codepoint: u32,
931    },
932    #[error("duration: missing magnitude in {0:?}")]
933    EmptyDuration(String),
934    #[error("duration: unknown unit {unit:?} (expected one of ms, s, m, h)")]
935    UnknownDurationUnit { unit: String },
936    #[error("duration: failed to parse magnitude {0:?}")]
937    BadDurationMagnitude(String),
938    #[error(
939        "duration: magnitude {value:?} is not a non-negative integer — the canonical \
940         authoring form for `:limits :wall-clock` is `<integer><unit>` (e.g. `\"30s\"`, \
941         `\"500ms\"`, `\"2m\"`, `\"1h\"`) with no decimal point and no leading `+` sign. A \
942         fractional / decimal-shaped magnitude (`\"1.5s\"`, `\"1.0s\"`, `\"0.5m\"`, \
943         `\"+30s\"`, `\"-30s\"`) round-trips through `render_duration` to a *different* \
944         canonical form (`\"1500ms\"`, `\"1s\"`, `\"30s\"`, `\"30s\"`) on first serialize \
945         — breaking the THEORY.md Part V render-determinism contract every typed slot \
946         carries. Pick an integer magnitude in the unit that divides cleanly (write \
947         `\"1500ms\"` instead of `\"1.5s\"`; `\"30s\"` instead of `\"0.5m\"`)"
948    )]
949    NonIntegerDurationMagnitude { value: String },
950    #[error(
951        "duration: magnitude {value:?} has a non-canonical leading zero — the canonical \
952         authoring form for `:limits :wall-clock` is `<integer><unit>` (e.g. `\"30s\"`, \
953         `\"500ms\"`, `\"2m\"`, `\"1h\"`) with no leading-zero padding on the magnitude. \
954         A leading-zero magnitude (`\"030s\"`, `\"00s\"`, `\"01h\"`, `\"0500ms\"`) round-trips \
955         through `render_duration` to a *different* canonical form (`\"30s\"`, `\"0s\"`, \
956         `\"1h\"`, `\"500ms\"`) on first serialize — breaking the THEORY.md Part V \
957         render-determinism contract every typed slot carries. Strip the leading zeros \
958         (write `\"30s\"` instead of `\"030s\"`)"
959    )]
960    LeadingZeroDurationMagnitude { value: String },
961    #[error(
962        "duration: value {value:?} contains whitespace byte 0x{byte:02x} — the canonical \
963         authoring form for `:limits :wall-clock` is `<integer><unit>` (e.g. `\"30s\"`, \
964         `\"500ms\"`, `\"2m\"`, `\"1h\"`) with no whitespace bytes anywhere. A \
965         whitespace-carrying shape (`\" 30s\"`, `\"30s \"`, `\"30 s\"`, `\"\\t30s\"`, \
966         `\"30s\\n\"`) round-trips through `render_duration` to a *different* canonical form \
967         (`\"30s\"`) on first serialize — breaking the THEORY.md Part V render-determinism \
968         contract every typed slot carries. Strip every whitespace byte (write `\"30s\"` \
969         verbatim)"
970    )]
971    WhitespaceInDuration { value: String, byte: u8 },
972    #[error(
973        "duration: value {value:?} contains a non-ASCII Unicode whitespace character \
974         {ch:?} (U+{codepoint:04X}) — the canonical authoring form for `:limits :wall-clock` \
975         is `<integer><unit>` (e.g. `\"30s\"`, `\"500ms\"`, `\"2m\"`, `\"1h\"`) with no \
976         whitespace characters anywhere (ASCII or Unicode). A non-ASCII-whitespace-\
977         carrying shape (`\"\\u{{00A0}}30s\"` — paste-from-typography NBSP prefix; \
978         `\"30s\\u{{2028}}\"` — paste-from-web-doc line-separator suffix; \
979         `\"30\\u{{2003}}s\"` — paste-from-typography EM-SPACE between magnitude and \
980         unit) survives the pre-existing `u8::is_ascii_whitespace` byte-scan (none of \
981         its bytes match the ASCII whitespace set) but `str::trim` (which uses \
982         `char::is_whitespace` — the Unicode `White_Space` property, strictly wider than \
983         the ASCII byte set) silently strips it at parse entry, and the value round-trips \
984         through `render_duration` to a *different* canonical form (`\"30s\"`) on first \
985         serialize — breaking the THEORY.md Part V render-determinism contract every \
986         typed slot carries. Strip every non-ASCII whitespace character (write `\"30s\"` \
987         verbatim with only ASCII bytes)"
988    )]
989    NonAsciiWhitespaceInDuration {
990        value: String,
991        ch: char,
992        codepoint: u32,
993    },
994    #[error("millicores: bad value {0:?} (expected `<int>m` or `<int>`)")]
995    BadMillicores(String),
996    #[error(
997        "millicores: magnitude {value:?} is not a non-negative integer — the canonical \
998         authoring form for `:limits :cpu` is `<integer>m` (Kubernetes millicores, e.g. \
999         `\"500m\"` for half a core, `\"2000m\"` for two cores) or the bare-core \
1000         shorthand `<integer>` (e.g. `\"2\"` = `\"2000m\"`), with no decimal point and \
1001         no leading `+` sign. A fractional / decimal-shaped magnitude (`\"1.5\"`, \
1002         `\"500.0m\"`, `\"+500m\"`, `\"-100m\"`) round-trips through `render_millicores` \
1003         to a *different* canonical form (`\"1500m\"`, `\"500m\"`, `\"500m\"`, \
1004         parse-rejection) on first serialize — breaking the THEORY.md Part V \
1005         render-determinism contract every typed slot carries. Pick an integer magnitude \
1006         in millicores (write `\"1500m\"` instead of `\"1.5\"`; `\"500m\"` instead of \
1007         `\"500.0m\"`)"
1008    )]
1009    NonIntegerMillicoreMagnitude { value: String },
1010    #[error(
1011        "millicores: magnitude {value:?} has a non-canonical leading zero — the canonical \
1012         authoring form for `:limits :cpu` is `<integer>m` (Kubernetes millicores, e.g. \
1013         `\"500m\"` for half a core, `\"2000m\"` for two cores) or the bare-core shorthand \
1014         `<integer>` (e.g. `\"2\"` = `\"2000m\"`) with no leading-zero padding on the \
1015         magnitude. A leading-zero magnitude (`\"0500m\"`, `\"00m\"`, `\"02\"`, `\"01500m\"`) \
1016         round-trips through `render_millicores` to a *different* canonical form (`\"500m\"`, \
1017         `\"0m\"`, `\"2000m\"`, `\"1500m\"`) on first serialize — breaking the THEORY.md Part \
1018         V render-determinism contract every typed slot carries. Strip the leading zeros \
1019         (write `\"500m\"` instead of `\"0500m\"`; `\"2\"` instead of `\"02\"`)"
1020    )]
1021    LeadingZeroMillicoreMagnitude { value: String },
1022    #[error(
1023        "millicores: value {value:?} contains whitespace byte 0x{byte:02x} — the canonical \
1024         authoring form for `:limits :cpu` is `<integer>m` (Kubernetes millicores, e.g. \
1025         `\"500m\"`, `\"2000m\"`) or the bare-core shorthand `<integer>` (e.g. `\"2\"`) \
1026         with no whitespace bytes anywhere. A whitespace-carrying shape (`\" 500m\"`, \
1027         `\"500m \"`, `\"500 m\"`, `\"\\t500m\"`, `\"500m\\n\"`) round-trips through \
1028         `render_millicores` to a *different* canonical form (`\"500m\"`) on first \
1029         serialize — breaking the THEORY.md Part V render-determinism contract every \
1030         typed slot carries. Strip every whitespace byte (write `\"500m\"` verbatim)"
1031    )]
1032    WhitespaceInMillicores { value: String, byte: u8 },
1033    #[error(
1034        "millicores: value {value:?} contains a non-ASCII Unicode whitespace character \
1035         {ch:?} (U+{codepoint:04X}) — the canonical authoring form for `:limits :cpu` is \
1036         `<integer>m` (Kubernetes millicores, e.g. `\"500m\"`, `\"2000m\"`) or the \
1037         bare-core shorthand `<integer>` (e.g. `\"2\"`) with no whitespace characters \
1038         anywhere (ASCII or Unicode). A non-ASCII-whitespace-carrying shape \
1039         (`\"\\u{{00A0}}500m\"` — paste-from-typography NBSP prefix; \
1040         `\"500m\\u{{2028}}\"` — paste-from-web-doc line-separator suffix; \
1041         `\"500\\u{{2003}}m\"` — paste-from-typography EM-SPACE between magnitude and \
1042         unit) survives the pre-existing `u8::is_ascii_whitespace` byte-scan (none of \
1043         its bytes match the ASCII whitespace set) but `str::trim` (which uses \
1044         `char::is_whitespace` — the Unicode `White_Space` property, strictly wider than \
1045         the ASCII byte set) silently strips it at parse entry, and the value round-trips \
1046         through `render_millicores` to a *different* canonical form (`\"500m\"`) on \
1047         first serialize — breaking the THEORY.md Part V render-determinism contract \
1048         every typed slot carries. Strip every non-ASCII whitespace character (write \
1049         `\"500m\"` verbatim with only ASCII bytes)"
1050    )]
1051    NonAsciiWhitespaceInMillicores {
1052        value: String,
1053        ch: char,
1054        codepoint: u32,
1055    },
1056    #[error(
1057        ":limits :memory must be > 0 — wasmtime StoreLimits refuses a zero memory cap; omit the field for unbounded"
1058    )]
1059    MemoryZero,
1060    #[error(
1061        ":limits :memory ({bytes} bytes) is below the wasm32-wasip2 linear-memory page size (64 KiB = 65536 bytes) — a sub-page cap cannot hold a single wasm linear memory page, so instantiation of any component declaring `(memory 1)` traps with `memory minimum size of 1 pages exceeds memory limits` and a `(memory 0)` component traps the first `memory.grow(1)`. Pin a value ≥ 64 KiB (e.g. `\"64KiB\"`, `\"1MiB\"`, `\"64MiB\"`) or omit the field for unbounded"
1062    )]
1063    MemoryBelowWasm32Page { bytes: u64 },
1064    #[error(
1065        ":limits :memory ({bytes} bytes) exceeds the wasm32-wasip2 linear-memory ceiling (4 GiB = 4294967296 bytes); pin a value ≤ 4 GiB or omit the field for unbounded"
1066    )]
1067    MemoryExceedsWasm32Cap { bytes: u64 },
1068    #[error(
1069        ":limits :memory ({bytes} bytes) carries a sub-page residue the wasm32-wasip2 \
1070         linear-memory model cannot honor — the wasm spec defines linear memory in \
1071         fixed 64 KiB pages (LIMITS_MEMORY_WASM32_PAGE_BYTES = 65536 bytes) and \
1072         wasmtime's StoreLimits::memory_size is consumed as a page-quantized ceiling: \
1073         the engine can grow at most floor({bytes} / 65536) pages, and the bytes in \
1074         [floor({bytes} / 65536) * 65536, {bytes}] are structural dead space the \
1075         runtime cannot honor. Pin a page-aligned value in 64KiB..=4GiB \
1076         (the canonical authoring magnitudes — `\"64KiB\"`, `\"128KiB\"`, `\"1MiB\"`, \
1077         `\"64MiB\"`, `\"1GiB\"`, `\"4GiB\"` — every power-of-1024 unit the byte-size \
1078         codec emits divides cleanly by the page size) or omit the field for unbounded"
1079    )]
1080    MemoryNotPageMultiple { bytes: u64 },
1081    #[error(
1082        ":limits :fuel must be > 0 — wasmtime traps the first instruction at fuel=0; omit the field for unbounded"
1083    )]
1084    FuelZero,
1085    #[error(
1086        ":limits :fuel ({fuel} instructions) exceeds the per-process ceiling \
1087         (LIMITS_FUEL_MAX = 1_000_000_000_000 = 10^12 wasm instructions) — a value \
1088         above this cap turns the typed per-call fuel counter into a no-op budget: \
1089         the sibling `:wall-clock` cap (LIMITS_WALL_CLOCK_MAX = 1h = 3600s) fires \
1090         before the fuel counter could ever be drained (wasmtime's documented \
1091         fuel-tracked execution rate sits at ~10^8–10^9 fuel-units per second on \
1092         modern x86_64 / aarch64 hosts running wasmtime through Cranelift, so the \
1093         largest realistic per-call fuel budget reachable within 1h sits at ~3.6 × \
1094         10^11–3.6 × 10^12 fuel-units, and a value above 10^12 is structurally \
1095         unreachable as a per-call counter), so the typed `:fuel` slot becomes a \
1096         declared-but-no-op contract far from the source caixa.lisp. Pin a value \
1097         in 1..=1_000_000_000_000 (the canonical caixa Servico runs in the \
1098         10^6..=10^9 fuel band — the in-tree `Caixa::template` documentation and \
1099         `caixa-feira` examples carry `:fuel 1_000_000` = 10^6, peer to \
1100         wasmtime's official `Store::set_fuel(1_000_000)` example in the wasmtime \
1101         book; production-shape per-request fuel budgets sit in the 10^7..=10^9 \
1102         band for compute-bound workloads) or omit :fuel to express `no per-call \
1103         fuel budget on this axis` (the wasm-engine then relies entirely on the \
1104         sibling `:wall-clock` cgroup / Kubernetes activeDeadlineSeconds deadline)"
1105    )]
1106    FuelExceedsCap { fuel: u64 },
1107    #[error(
1108        ":limits :wall-clock must be > 0 — a zero deadline expires before the call starts; omit the field for unbounded"
1109    )]
1110    WallClockZero,
1111    #[error(
1112        ":limits :wall-clock ({wall_clock:?}) carries a sub-millisecond residue the typed `:wall-clock` duration codec cannot round-trip — \
1113         the codec truncates to `as_millis()` before picking the canonical unit, so a value with `subsec_nanos() % 1_000_000 != 0` either \
1114         truncates on first serialize (e.g. `Duration::from_micros(1500)` → \"1ms\" → `Duration::from_millis(1)` ≠ original) or renders \
1115         as \"0s\" the `WallClockZero` arm then rejects on re-validate. Pin an integer-millisecond magnitude in the canonical authoring form \
1116         (`<integer><unit>` for unit ∈ {{ms, s, m, h}}, e.g. `\"500ms\"`, `\"30s\"`, `\"2m\"`, `\"1h\"`) or omit the field for unbounded"
1117    )]
1118    WallClockNotCanonical { wall_clock: Duration },
1119    #[error(
1120        ":limits :wall-clock ({wall_clock:?}) exceeds the per-process ceiling \
1121         (LIMITS_WALL_CLOCK_MAX = 1h = 3600s) — a value above this cap turns the typed \
1122         per-call deadline into a nominal-only contract (the wasm-engine's epoch-deadline \
1123         cancellation reaches for a `Duration` so long no realistic synchronous wasm call \
1124         can hit it), and the MESH-COMPOSITION §V \"no infinite blocking\" CSE invariant \
1125         degenerates to enforcement only at the per-Servico cgroup / Kubernetes \
1126         activeDeadlineSeconds layer — far above the per-call granularity the typed \
1127         `:limits :wall-clock` slot is meant to express. Pin a value in 1ms..=1h \
1128         (Envoy / Istio / Linkerd production per-request playbooks all recommend ≤ 60s; \
1129         AWS App Mesh / ingress-nginx typical ≤ 300s; the longest per-request \
1130         `proxy_read_timeout` ingress-nginx documents maxes out at the same 3600s ceiling) \
1131         or omit :wall-clock to express `no per-process deadline on this axis` (the \
1132         deadline then relies entirely on the cluster-level cgroup / pod \
1133         activeDeadlineSeconds bound)"
1134    )]
1135    WallClockExceedsCap { wall_clock: Duration },
1136    #[error(
1137        ":limits :cpu must be > 0m — a zero cgroup share starves the process; omit the field for unbounded"
1138    )]
1139    CpuZero,
1140    #[error(
1141        ":limits :cpu ({millicores}m) exceeds the per-process ceiling \
1142         (LIMITS_CPU_MILLICORES_MAX = 128_000m = 128 cores) — a value above this cap is \
1143         structurally unschedulable on every commercially-common managed-Kubernetes node \
1144         pool (GKE Standard / EKS managed / AKS default general-purpose SKU ladders top out \
1145         at 128 vCPU per node; AWS m7i.32xlarge / c7i.32xlarge, Azure HBv3-128rs, GCP \
1146         c3-standard-128 all sit at the same 128-vCPU ceiling), so the resulting \
1147         `pleme-computeunit` chart's `resources.requests.cpu` lands as a hint the \
1148         Kubernetes scheduler cannot bind to any node — the pod sits `Pending` indefinitely \
1149         with a `0/N nodes are available: N Insufficient cpu` event, and the typed `:cpu` \
1150         slot becomes an unschedulable contract far from the source caixa.lisp. The \
1151         wasm32-wasip2 single-threaded execution model the canonical caixa Servico targets \
1152         reinforces the structural argument: a single wasm component cannot saturate more \
1153         than one core, so even the Lunatic-style supervised-multi-process host bounds its \
1154         useful CPU request to the host node's vCPU count. Pin a value in 1m..=128000m \
1155         (the canonical caixa Servico runs in the 100m..=2000m band — every in-tree \
1156         example uses 500m; AWS App Mesh / Envoy / Istio per-pod CPU production playbooks \
1157         all sit ≤ 8000m / 8 cores; the longest documented per-Servico CPU request any \
1158         pleme-io substrate playbook recommends maxes at ~16 cores) or omit :cpu to \
1159         express `no per-process CPU hint on this axis` (the cgroup share then defaults to \
1160         the cluster-level `LimitRange` / `ResourceQuota` policy the operator pins on the \
1161         host namespace)"
1162    )]
1163    CpuExceedsCap { millicores: u32 },
1164}
1165
1166// ── byte-size codec ────────────────────────────────────────────────────
1167
1168fn parse_byte_size(s: &str) -> Result<u64, LimitsError> {
1169    // Paired whitespace-rejection arm — the ASCII byte-scan
1170    // (paste-from-aligned-doc leading space, shell-history trailing
1171    // space, typography space between magnitude and unit, block-scalar
1172    // tab, multi-line trailing newline) closes the WhatWG-conformant
1173    // ASCII whitespace bytes (`0x20`, `0x09`, `0x0A`, `0x0C`, `0x0D`);
1174    // the non-ASCII `char::is_whitespace` scan closes the strictly-
1175    // complementary Unicode `White_Space` class (NBSP `\u{00A0}`, LINE
1176    // SEPARATOR `\u{2028}`, EM-SPACE `\u{2003}`, and the peer
1177    // typography codepoints) that `str::trim` at parse entry silently
1178    // strips. Either drift class would round-trip through
1179    // `render_byte_size` to a *different* canonical form on next emit
1180    // — breaking the THEORY.md Part V render-determinism contract every
1181    // typed slot carries. Diagnostics stay typed at
1182    // `WhitespaceInByteSize` / `NonAsciiWhitespaceInByteSize` so the
1183    // failing byte / char + U+XXXX codepoint reaches the author verbatim
1184    // rather than being value-laundered through a downstream
1185    // `BadByteMagnitude` arm.
1186    //
1187    // Routed through the lifted [`crate::render::reject_whitespace`]
1188    // primitive — the substrate-side single-owner gate every typed-
1189    // magnitude codec in caixa-core (`parse_byte_size` /
1190    // `parse_duration` / `parse_millicores` /
1191    // `supervisor::duration_codec` / `rate_limit_codec`) shares. Drift
1192    // between any two codec sites' paired-arm rejection set becomes a
1193    // single-edit fix at the composed predicate rather than five
1194    // independent paired-arm re-inlines diverging over time.
1195    crate::render::reject_whitespace(
1196        s,
1197        |byte| LimitsError::whitespace_in_byte_size(s, byte),
1198        |ch| LimitsError::non_ascii_whitespace_in_byte_size(s, ch),
1199    )?;
1200    let s = s.trim();
1201    if s.is_empty() {
1202        return Err(LimitsError::empty_byte_size(s));
1203    }
1204    // Route the `<integer><ASCII-alphabetic-unit>` split through the
1205    // lifted [`crate::render::split_magnitude_and_alpha_unit`] primitive
1206    // — the substrate-side single-owner split every ASCII-alphabetic-unit
1207    // typed-magnitude codec in caixa-core (`parse_byte_size` /
1208    // `parse_duration` / `supervisor::duration_codec::parse`) shares.
1209    // Drift between any two codec sites' magnitude/unit split rule
1210    // becomes a single-edit fix at the composed helper rather than three
1211    // independent `s.find(|c: char| c.is_ascii_alphabetic())` re-inlines
1212    // diverging over time.
1213    let (num_part, unit) = crate::render::split_magnitude_and_alpha_unit(s);
1214    let num_trim = num_part.trim();
1215    // The canonical authoring form for `:limits :memory` is
1216    // `<integer><unit>` — every magnitude `render_byte_size` emits is a
1217    // non-negative integer with no decimal point and no leading sign,
1218    // so the parser's accepted set must match for serialize/deserialize
1219    // to round-trip without canonical-form drift. Until this gate
1220    // landed the parser accepted any `f64`-shaped magnitude
1221    // (`"1.5KiB"` → 1536 bytes, `"1.0MiB"` → 1MiB, `"0.5GiB"` → 512MiB,
1222    // `"+1024"` → 1024) and serde silently round-tripped the value to
1223    // a *different* canonical string on the next emit (`"1.5KiB"` →
1224    // 1536 → `"1536"`, `"1.0MiB"` → 1048576 → `"1MiB"`, `"0.5GiB"` →
1225    // 536870912 → `"512MiB"`, `"+1024"` → 1024 → `"1KiB"`) — breaking
1226    // the THEORY.md §V.2.7 render-determinism contract every typed slot
1227    // carries.
1228    //
1229    // Strict canonical form: every byte of the magnitude is an ASCII
1230    // digit (no `.`, no `+`, no `-`). On current Rust `u64::from_str`
1231    // permissively accepts a leading `+` (`"+1024"` → 1024) — that's a
1232    // canonical-drift shape `render_byte_size` never emits, so the
1233    // digit-only check is what closes the leading-sign class; relying
1234    // on `u64::from_str`'s strictness alone would silently admit it.
1235    // On non-digit-only inputs the gate distinguishes "non-canonical-
1236    // but-numeric" (parses as f64 or i64, so it's an authoring-shape
1237    // footgun) from "garbage" (parses as neither, so it's not a
1238    // numeric input at all) — the diagnostic names the offending
1239    // magnitude shape verbatim rather than collapsing both authoring
1240    // footguns into a single opaque `BadByteMagnitude`.
1241    //
1242    // Same canonical-form discipline
1243    // [`crate::AplicacaoSpec::validate_politicas`]'s
1244    // [`is_canonical_rate_limit_window`] gate (808017c) applies to the
1245    // rate-limit `:window` axis — the codec's accepted set matches its
1246    // emitted set, structurally.
1247    //
1248    // (Scientific-notation magnitudes like `"1e3KiB"` are also rejected,
1249    // but on a different arm: the parser splits on the first ASCII-
1250    // alphabetic byte, so the `e` is read as a unit prefix and the
1251    // input falls into the `UnknownByteUnit { unit: "e3KiB" }` branch
1252    // before this gate is consulted — that's the existing diagnostic
1253    // for the scientific-shape footgun, and this gate is additive to
1254    // it.)
1255    //
1256    // Routed through the lifted
1257    // [`crate::render::is_digit_only_magnitude`] predicate — the
1258    // single source of truth every typed-magnitude codec in
1259    // caixa-core (`parse_byte_size` / `parse_duration` /
1260    // `parse_millicores` / `supervisor::duration_codec` /
1261    // `rate_limit_codec`) shares. Drift between any two codec sites'
1262    // digit-only rejection set becomes a single-edit fix at the
1263    // shared predicate rather than five independent
1264    // `!<var>.is_empty() && <var>.bytes().all(|b| b.is_ascii_digit())`
1265    // scans diverging over time — same "single lifted source of truth"
1266    // discipline the peer canonical-form predicates
1267    // ([`crate::render::find_ascii_whitespace_byte`] /
1268    // [`crate::render::find_non_ascii_whitespace_char`] /
1269    // [`crate::render::is_leading_zero_padded_magnitude`]) carry on
1270    // the whitespace and leading-zero-padding drift-class axes.
1271    let digit_only = crate::render::is_digit_only_magnitude(num_trim);
1272    if !digit_only {
1273        // Distinguish "non-canonical-but-numeric" (`"1.5"`, `"1.0"`,
1274        // `"+1024"`, `"-1"`) from "garbage" (`"abc"`, `"--1"`) so the
1275        // diagnostic names the offending magnitude shape verbatim.
1276        // Use f64 + i64 fallbacks for the "numeric" detection so every
1277        // non-digit-only-but-parseable input lands on
1278        // `NonIntegerByteMagnitude` regardless of sign or fractionality.
1279        let numeric = num_trim.parse::<f64>().is_ok() || num_trim.parse::<i64>().is_ok();
1280        if numeric {
1281            return Err(LimitsError::non_integer_byte_magnitude(num_trim));
1282        }
1283        return Err(LimitsError::bad_byte_magnitude(num_part));
1284    }
1285    // Leading-zero arm — peer with the `parse_duration` leading-zero
1286    // arm (39762d7), the `supervisor::duration_codec` leading-zero arm
1287    // (9178904) and the `rate_limit_codec` leading-zero arm (4f46830)
1288    // on the same canonical-form render-determinism axis. The
1289    // digit-only gate accepts `"0064MiB"`, `"01024"`, `"00KiB"`,
1290    // `"0500MB"` as `u64::from_str` parses them losslessly (= 64, 1024,
1291    // 0, 500), but `render_byte_size` emits the leading-zero-stripped
1292    // form (`"64MiB"`, `"1KiB"`, `"0"`, `"500MB"`) — a *different*
1293    // canonical string on the next emit, breaking the THEORY.md Part V
1294    // render-determinism contract the same way `"+1024"` did before the
1295    // leading-`+` arm landed. The single-byte magnitude `"0"` (or
1296    // `"0B"` / `"0KiB"`) round-trips losslessly through
1297    // `render_byte_size` (`render_byte_size(0)` emits `"0"`) — the
1298    // downstream semantic-zero gate [`LimitsError::MemoryZero`] refuses
1299    // zero-magnitude authoring at the typed-validate layer above, so
1300    // the single-byte `"0"` stays in the accepted set at this codec
1301    // layer and the diagnostic partitioning between canonical-form
1302    // drift (this arm) and semantic-zero (the downstream gate) remains
1303    // stable. Same codec-layer / typed-validate-layer partition the
1304    // peer codecs preserve.
1305    //
1306    // Routed through the lifted
1307    // [`crate::render::is_leading_zero_padded_magnitude`] predicate —
1308    // the single source of truth every typed-magnitude codec in
1309    // caixa-core (`parse_byte_size` / `parse_duration` /
1310    // `parse_millicores` / `supervisor::duration_codec` /
1311    // `rate_limit_codec`) shares. Drift between any two codec sites'
1312    // leading-zero rejection set becomes a single-edit fix at the
1313    // shared predicate rather than five independent
1314    // `s.len() > 1 && s.as_bytes()[0] == b'0'` scans diverging over
1315    // time — same "single lifted source of truth" discipline the
1316    // peer whitespace predicates
1317    // ([`crate::render::find_ascii_whitespace_byte`] /
1318    // [`crate::render::find_non_ascii_whitespace_char`]) carry on
1319    // their strictly-complementary axes.
1320    if crate::render::is_leading_zero_padded_magnitude(num_trim) {
1321        return Err(LimitsError::leading_zero_byte_magnitude(num_trim));
1322    }
1323    // `digit_only` guarantees every byte is `[0-9]`, so the only way
1324    // u64::from_str can fail here is overflow (the magnitude exceeds
1325    // u64::MAX). Surface that as `BadByteMagnitude` with an overflow-
1326    // shaped wording so the diagnostic names the offending magnitude
1327    // verbatim rather than collapsing onto the non-canonical arm.
1328    let num: u64 = num_trim.parse::<u64>().map_err(|_| {
1329        LimitsError::bad_byte_magnitude(format!("{num_trim} (digit-only magnitude overflows u64)"))
1330    })?;
1331    let multiplier: u64 = match unit.trim() {
1332        "" | "B" => 1,
1333        "KB" => 1_000,
1334        "MB" => 1_000_000,
1335        "GB" => 1_000_000_000,
1336        "KiB" => 1024,
1337        "MiB" => 1024 * 1024,
1338        "GiB" => 1024 * 1024 * 1024,
1339        other => {
1340            return Err(LimitsError::unknown_byte_unit(other));
1341        }
1342    };
1343    // Overflow surfaces as `BadByteMagnitude` (a u64-saturating
1344    // multiply would silently truncate to `u64::MAX` and then the
1345    // wasm32-cap gate at validate time would catch it — but a u64
1346    // overflow is a parse-shaped failure on the author's input, not a
1347    // domain-cap rejection on a well-formed value, so it surfaces here
1348    // as a parser diagnostic naming the offending magnitude × unit
1349    // pair rather than as `MemoryExceedsWasm32Cap { bytes: u64::MAX }`
1350    // far from the author's intent).
1351    num.checked_mul(multiplier).ok_or_else(|| {
1352        LimitsError::bad_byte_magnitude(format!(
1353            "{num_trim}{unit_trim} overflows u64 (magnitude × unit > 2^64-1)",
1354            unit_trim = unit.trim()
1355        ))
1356    })
1357}
1358
1359fn render_byte_size(n: u64) -> String {
1360    // Prefer the largest power-of-1024 unit that divides cleanly; fall
1361    // back to bytes if nothing matches.
1362    const UNITS: &[(u64, &str)] = &[
1363        (1024 * 1024 * 1024, "GiB"),
1364        (1024 * 1024, "MiB"),
1365        (1024, "KiB"),
1366    ];
1367    for (mult, label) in UNITS {
1368        if n >= *mult && n.is_multiple_of(*mult) {
1369            return format!("{}{label}", n / mult);
1370        }
1371    }
1372    format!("{n}")
1373}
1374
1375fn ser_byte_size<S: Serializer>(v: &Option<u64>, s: S) -> Result<S::Ok, S::Error> {
1376    // Route through the canonical [`crate::render::serialize_option_via_str`]
1377    // — the substrate-side single-owner primitive for the forward arm
1378    // of the typed-magnitude codec family. See its docstring for the
1379    // full sibling roster and the compounding rationale that pins this
1380    // lift; load-bearing pinned by
1381    // `tests::ser_byte_size_routes_through_render_serialize_option_via_str_canonical`.
1382    crate::render::serialize_option_via_str(v, s, render_byte_size)
1383}
1384
1385fn de_byte_size<'de, D: Deserializer<'de>>(d: D) -> Result<Option<u64>, D::Error> {
1386    // Route through the canonical [`crate::render::deserialize_option_via_str`]
1387    // — the substrate-side single-owner primitive for the reverse arm
1388    // of the typed-magnitude codec family. See its docstring for the
1389    // full sibling roster and the compounding rationale that pins this
1390    // lift; load-bearing pinned by
1391    // `tests::de_byte_size_routes_through_render_deserialize_option_via_str_canonical`.
1392    crate::render::deserialize_option_via_str(d, parse_byte_size)
1393}
1394
1395// ── duration codec ─────────────────────────────────────────────────────
1396
1397fn parse_duration(s: &str) -> Result<Duration, LimitsError> {
1398    // Paired whitespace-rejection arm — same canonical-form
1399    // render-determinism discipline as the peer `parse_byte_size` /
1400    // `parse_millicores` / `supervisor::duration_codec::parse` /
1401    // `rate_limit_codec::parse` sites: the ASCII byte-scan closes the
1402    // WhatWG-conformant whitespace bytes every downstream YAML / JSON /
1403    // TOML parser can feed through a quoted-scalar value verbatim
1404    // (`0x20`, `0x09`, `0x0A`, `0x0C`, `0x0D`), the non-ASCII
1405    // `char::is_whitespace` scan closes the strictly-complementary
1406    // Unicode `White_Space` class (NBSP `\u{00A0}`, LINE SEPARATOR
1407    // `\u{2028}`, EM-SPACE `\u{2003}`, and the peer typography
1408    // codepoints) that `str::trim` at parse entry silently strips.
1409    // Either drift class would round-trip through `render_duration` to
1410    // a *different* canonical form on next emit — breaking the
1411    // THEORY.md Part V render-determinism contract. Diagnostics stay
1412    // typed at `WhitespaceInDuration` / `NonAsciiWhitespaceInDuration`.
1413    //
1414    // Routed through the lifted [`crate::render::reject_whitespace`]
1415    // primitive — the substrate-side single-owner paired-arm gate every
1416    // typed-magnitude codec in caixa-core shares.
1417    crate::render::reject_whitespace(
1418        s,
1419        |byte| LimitsError::whitespace_in_duration(s, byte),
1420        |ch| LimitsError::non_ascii_whitespace_in_duration(s, ch),
1421    )?;
1422    let s = s.trim();
1423    if s.is_empty() {
1424        return Err(LimitsError::empty_duration(s));
1425    }
1426    // Routed through the lifted
1427    // [`crate::render::split_magnitude_and_alpha_unit`] primitive — the
1428    // single-owner split every ASCII-alphabetic-unit typed-magnitude
1429    // codec in caixa-core shares. See its docstring for the full
1430    // sibling roster on the same primitive altitude.
1431    let (num_part, unit) = crate::render::split_magnitude_and_alpha_unit(s);
1432    let num_trim = num_part.trim();
1433    // The canonical authoring form for `:limits :wall-clock` is
1434    // `<integer><unit>` — every magnitude `render_duration` emits is a
1435    // non-negative integer with no decimal point and no leading sign,
1436    // so the parser's accepted set must match for serialize/deserialize
1437    // to round-trip without canonical-form drift. Until this gate
1438    // landed the parser accepted any `f64`-shaped magnitude
1439    // (`"1.5s"` → 1500ms, `"1.0s"` → 1s, `"0.5m"` → 30s, `"+30s"` →
1440    // 30s) and serde silently round-tripped the value to a *different*
1441    // canonical string on the next emit (`"1.5s"` → 1500ms →
1442    // `"1500ms"`, `"1.0s"` → 1s → `"1s"`, `"0.5m"` → 30s → `"30s"`,
1443    // `"+30s"` → 30s → `"30s"`) — breaking the THEORY.md Part V
1444    // render-determinism contract every typed slot carries. The same
1445    // canonical-form discipline `parse_byte_size`'s integer-magnitude
1446    // gate (the immediate predecessor on the peer `:limits :memory`
1447    // codec) applies; this gate is the direct successor on the
1448    // `:limits :wall-clock` codec.
1449    //
1450    // Strict canonical form: every byte of the magnitude is an ASCII
1451    // digit (no `.`, no `+`, no `-`). On current Rust `u64::from_str`
1452    // permissively accepts a leading `+` (`"+30"` → 30) — that's a
1453    // canonical-drift shape `render_duration` never emits, so the
1454    // digit-only check is what closes the leading-sign class; relying
1455    // on `u64::from_str`'s strictness alone would silently admit it.
1456    // On non-digit-only inputs the gate distinguishes "non-canonical-
1457    // but-numeric" (parses as f64 or i64 — surfaced as the new
1458    // `NonIntegerDurationMagnitude` variant with a self-locating
1459    // diagnostic) from "garbage" (parses as neither — surfaced as the
1460    // existing `BadDurationMagnitude` so its narrower diagnostic
1461    // remains load-bearing).
1462    //
1463    // Routed through the lifted
1464    // [`crate::render::is_digit_only_magnitude`] predicate — the same
1465    // source of truth the four peer typed-magnitude codec sites share.
1466    let digit_only = crate::render::is_digit_only_magnitude(num_trim);
1467    if !digit_only {
1468        let numeric = num_trim.parse::<f64>().is_ok() || num_trim.parse::<i64>().is_ok();
1469        if numeric {
1470            return Err(LimitsError::non_integer_duration_magnitude(num_trim));
1471        }
1472        return Err(LimitsError::bad_duration_magnitude(num_part));
1473    }
1474    // Leading-zero arm — peer with the `supervisor::duration_codec`
1475    // leading-zero arm (9178904) and the `rate_limit_codec`
1476    // leading-zero arm (4f46830) on the same canonical-form
1477    // render-determinism axis. The digit-only gate accepts `"030s"`,
1478    // `"00s"`, `"01h"`, `"0500ms"` as `u64::from_str` parses them
1479    // losslessly (= 30, 0, 1, 500), but `render_duration` emits the
1480    // leading-zero-stripped form (`"30s"`, `"0s"`, `"1h"`, `"500ms"`)
1481    // — a *different* canonical string on the next emit, breaking the
1482    // THEORY.md Part V render-determinism contract the same way
1483    // `"+30s"` did before the leading-`+` arm landed. The single-byte
1484    // magnitude `"0"` (or `"0s"` / `"0ms"`) round-trips losslessly
1485    // through `render_duration` (`render_duration(Duration::ZERO)`
1486    // emits `"0s"`) — the downstream semantic-zero gate
1487    // [`LimitsError::WallClockZero`] refuses zero-magnitude authoring
1488    // at the typed-validate layer above, so the single-byte `"0"`
1489    // stays in the accepted set at this codec layer and the
1490    // diagnostic partitioning between canonical-form drift (this arm)
1491    // and semantic-zero (the downstream gate) remains stable. Same
1492    // codec-layer / typed-validate-layer partition the peer codecs
1493    // preserve.
1494    //
1495    // Routed through the lifted
1496    // [`crate::render::is_leading_zero_padded_magnitude`] predicate —
1497    // the same source of truth the four peer typed-magnitude codec
1498    // sites share.
1499    if crate::render::is_leading_zero_padded_magnitude(num_trim) {
1500        return Err(LimitsError::leading_zero_duration_magnitude(num_trim));
1501    }
1502    // The digit-only gate guarantees every byte is `[0-9]`, and the
1503    // leading-zero arm above guarantees the magnitude is either the
1504    // single byte `"0"` or starts with `[1-9]`, so the only way
1505    // `u64::from_str` can fail here is overflow.
1506    let num: u64 = num_trim.parse::<u64>().map_err(|_| {
1507        LimitsError::bad_duration_magnitude(format!(
1508            "{num_trim} (digit-only magnitude overflows u64)"
1509        ))
1510    })?;
1511    // Route the `{"ms" | "s" | "" | "m" | "h"} → Duration` unit-arm
1512    // dispatch through the canonical
1513    // [`crate::render::duration_from_integer_magnitude_and_unit`]
1514    // primitive — the substrate-side single-owner unit-dispatch table
1515    // every typed-duration codec in caixa-core routes through
1516    // (peer: `supervisor::duration_codec::parse` backing the shared
1517    // `:supervisor :restart-window` / `:politicas :timeout` /
1518    // `:politicas :circuit-breaker :window` slots). Every unit
1519    // conversion is integer-exact for an integer magnitude; overflow
1520    // surfaces via the typed `DurationUnitError::Overflow { multiplier }`
1521    // discriminant so this arm reconstructs the pre-lift
1522    // `"…overflows u64 (magnitude × 60 > 2^64-1)"` /
1523    // `"…overflows u64 (magnitude × 3600 > 2^64-1)"` wording verbatim
1524    // from `num_trim` / `unit_trim` / the returned `multiplier`, and
1525    // the unknown-unit arm reconstructs the pre-lift
1526    // `LimitsError::UnknownDurationUnit { unit }` variant from the
1527    // caller-scoped `unit_trim`. Load-bearing pinned by
1528    // `crate::render::tests::duration_from_integer_magnitude_and_unit_matches_pre_lift_unit_dispatch_table`.
1529    let unit_trim = unit.trim();
1530    let dur = crate::render::duration_from_integer_magnitude_and_unit(num, unit_trim).map_err(
1531        |e| match e {
1532            crate::render::DurationUnitError::Overflow { multiplier } => {
1533                LimitsError::bad_duration_magnitude(format!(
1534                    "{num_trim}{unit_trim} overflows u64 (magnitude × {multiplier} > 2^64-1)"
1535                ))
1536            }
1537            crate::render::DurationUnitError::UnknownUnit => {
1538                LimitsError::unknown_duration_unit(unit_trim)
1539            }
1540        },
1541    )?;
1542    Ok(dur)
1543}
1544
1545fn ser_duration<S: Serializer>(v: &Option<Duration>, s: S) -> Result<S::Ok, S::Error> {
1546    // Route through the canonical [`crate::render::serialize_option_via_str`]
1547    // — the substrate-side single-owner primitive for the forward arm
1548    // of the typed-magnitude codec family — around the canonical
1549    // [`crate::supervisor::duration_codec::render`] duration-byte
1550    // dispatch. The `render` dispatch is itself the load-bearing
1551    // single-owner primitive for duration bytes across every caixa
1552    // typed-duration surface (`:limits :wall-clock`,
1553    // `:politicas :timeout`, `:circuit-breaker :window`, future OTP
1554    // `gen_server` per-call timeouts); the outer
1555    // `serialize_option_via_str` closes the `Some(_) => serialize_str`
1556    // / `None => serialize_none` `Option`-arm dispatch every peer
1557    // typed-magnitude serializer shares. Load-bearing pinned by
1558    // `tests::ser_duration_routes_through_supervisor_duration_codec_render_canonical`.
1559    crate::render::serialize_option_via_str(v, s, crate::supervisor::duration_codec::render)
1560}
1561
1562fn de_duration<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Duration>, D::Error> {
1563    // Route through the canonical [`crate::render::deserialize_option_via_str`]
1564    // — the substrate-side single-owner primitive for the reverse arm
1565    // of the typed-magnitude codec family. See its docstring for the
1566    // full sibling roster and the compounding rationale that pins this
1567    // lift.
1568    crate::render::deserialize_option_via_str(d, parse_duration)
1569}
1570
1571// ── millicores codec ───────────────────────────────────────────────────
1572
1573fn parse_millicores(s: &str) -> Result<u32, LimitsError> {
1574    // Paired whitespace-rejection arm — same canonical-form
1575    // render-determinism discipline as the peer `parse_byte_size` /
1576    // `parse_duration` / `supervisor::duration_codec::parse` /
1577    // `rate_limit_codec::parse` sites: the ASCII byte-scan closes the
1578    // WhatWG-conformant whitespace bytes (`0x20`, `0x09`, `0x0A`,
1579    // `0x0C`, `0x0D`), the non-ASCII `char::is_whitespace` scan closes
1580    // the strictly-complementary Unicode `White_Space` class (NBSP
1581    // `\u{00A0}`, LINE SEPARATOR `\u{2028}`, EM-SPACE `\u{2003}`, and
1582    // the peer typography codepoints) that `str::trim` at parse entry
1583    // silently strips. Either drift class would round-trip through
1584    // `render_millicores` to a *different* canonical form on next emit
1585    // — breaking the THEORY.md Part V render-determinism contract.
1586    // Diagnostics stay typed at `WhitespaceInMillicores` /
1587    // `NonAsciiWhitespaceInMillicores` — peer with every prior
1588    // canonical-form-drift arm on this codec
1589    // (`NonIntegerMillicoreMagnitude`, `LeadingZeroMillicoreMagnitude`).
1590    //
1591    // Routed through the lifted [`crate::render::reject_whitespace`]
1592    // primitive — the substrate-side single-owner paired-arm gate every
1593    // typed-magnitude codec in caixa-core shares.
1594    crate::render::reject_whitespace(
1595        s,
1596        |byte| LimitsError::whitespace_in_millicores(s, byte),
1597        |ch| LimitsError::non_ascii_whitespace_in_millicores(s, ch),
1598    )?;
1599    let s_trim = s.trim();
1600    if s_trim.is_empty() {
1601        return Err(LimitsError::bad_millicores(s));
1602    }
1603    let (magnitude, has_m_suffix) = match s_trim.strip_suffix('m') {
1604        Some(stripped) => (stripped.trim(), true),
1605        None => (s_trim, false),
1606    };
1607    if magnitude.is_empty() {
1608        // Bare `"m"` (or `" m "`) — no magnitude was authored. The
1609        // canonical millicores authoring form requires a magnitude in
1610        // front of the unit (`"500m"`, not `"m"`). Surface as
1611        // `BadMillicores` so the existing narrower-arm wording stays
1612        // load-bearing for "no recognizable magnitude" inputs.
1613        return Err(LimitsError::bad_millicores(s));
1614    }
1615    // The canonical authoring form for `:limits :cpu` is `<integer>m`
1616    // (Kubernetes millicores) or the bare-core shorthand `<integer>`
1617    // (`"2"` = 2000 millicores). Every magnitude `render_millicores`
1618    // emits is a non-negative integer (`format!("{m}m")`) — no decimal
1619    // point, no leading sign — so the parser's accepted set must match
1620    // for serialize/deserialize to round-trip without canonical-form
1621    // drift. Until this gate landed the parser accepted any
1622    // `u32::from_str`-shaped magnitude (`"+500m"` → 500, `"+2"` →
1623    // 2000) and serde silently round-tripped the value to a *different*
1624    // canonical string on the next emit (`"+500m"` → `"500m"`, `"+2"`
1625    // → `"2000m"`) — breaking the THEORY.md Part V render-determinism
1626    // contract every typed slot carries. Closes the sixth (and last)
1627    // typed-codec surface in caixa-core on the integer-magnitude
1628    // canonical-form axis, peer with the five duration / byte-size /
1629    // rate-limit codecs the prior trajectory (1c55a2a / 818dd38 /
1630    // d1fd67b / f479c41 / d53c922) covered.
1631    //
1632    // Strict canonical form: every byte of the magnitude is an ASCII
1633    // digit (no `.`, no `+`, no `-`). On current Rust `u32::from_str`
1634    // permissively accepts a leading `+` (`"+500"` → 500) — that's a
1635    // canonical-drift shape `render_millicores` never emits, so the
1636    // digit-only check is what closes the leading-sign class; relying
1637    // on `u32::from_str`'s strictness alone would silently admit it.
1638    // On non-digit-only inputs the gate distinguishes "non-canonical-
1639    // but-numeric" (parses as f64 or i64 — surfaced as the new
1640    // `NonIntegerMillicoreMagnitude` variant naming the offending
1641    // magnitude verbatim with the canonical-form remediation) from
1642    // "garbage" (parses as neither — surfaced as the existing
1643    // `BadMillicores` so its narrower diagnostic shape remains
1644    // load-bearing for the not-a-numeric-input class).
1645    //
1646    // Routed through the lifted
1647    // [`crate::render::is_digit_only_magnitude`] predicate — the same
1648    // source of truth the four peer typed-magnitude codec sites share.
1649    // The predicate carries a `!<var>.is_empty()` gate that is
1650    // strictly no-op here (the `magnitude.is_empty()` arm above
1651    // already surfaces an empty magnitude as
1652    // [`LimitsError::BadMillicores`] before this line is reached), so
1653    // the semantics are preserved verbatim: on every reachable input
1654    // the predicate returns `magnitude.bytes().all(|b|
1655    // b.is_ascii_digit())`, byte-for-byte what the removed inline
1656    // expression computed.
1657    let digit_only = crate::render::is_digit_only_magnitude(magnitude);
1658    if !digit_only {
1659        let numeric = magnitude.parse::<f64>().is_ok() || magnitude.parse::<i64>().is_ok();
1660        if numeric {
1661            return Err(LimitsError::non_integer_millicore_magnitude(magnitude));
1662        }
1663        return Err(LimitsError::bad_millicores(s));
1664    }
1665    // Leading-zero arm — peer with the `parse_byte_size` leading-zero
1666    // arm (cea9a78), the `parse_duration` leading-zero arm (39762d7),
1667    // the `supervisor::duration_codec` leading-zero arm (9178904) and
1668    // the `rate_limit_codec` leading-zero arm (4f46830) on the same
1669    // canonical-form render-determinism axis. The digit-only gate
1670    // accepts `"0500m"`, `"00m"`, `"02"`, `"01500m"` as `u32::from_str`
1671    // parses them losslessly (= 500, 0, 2, 1500), but `render_millicores`
1672    // emits the leading-zero-stripped form (`"500m"`, `"0m"`, `"2000m"`,
1673    // `"1500m"`) — a *different* canonical string on the next emit,
1674    // breaking the THEORY.md Part V render-determinism contract the
1675    // same way `"+500m"` did before the leading-`+` arm landed. The
1676    // single-byte magnitude `"0"` (or `"0m"`) round-trips losslessly
1677    // through `render_millicores` (`render_millicores(0)` emits `"0m"`)
1678    // — the downstream semantic-zero gate [`LimitsError::CpuZero`]
1679    // refuses zero-magnitude authoring at the typed-validate layer
1680    // above, so the single-byte `"0"` stays in the accepted set at this
1681    // codec layer and the diagnostic partitioning between canonical-
1682    // form drift (this arm) and semantic-zero (the downstream gate)
1683    // remains stable. Same codec-layer / typed-validate-layer partition
1684    // the peer codecs preserve. Closes the sixth (and last) typed
1685    // numeric-codec surface in caixa-core on the integer-magnitude
1686    // leading-zero axis — the trajectory the prior `parse_byte_size`
1687    // arm (cea9a78) explicitly named.
1688    //
1689    // Routed through the lifted
1690    // [`crate::render::is_leading_zero_padded_magnitude`] predicate —
1691    // the same source of truth the four peer typed-magnitude codec
1692    // sites share.
1693    if crate::render::is_leading_zero_padded_magnitude(magnitude) {
1694        return Err(LimitsError::leading_zero_millicore_magnitude(magnitude));
1695    }
1696    // The digit-only gate guarantees every byte is `[0-9]`, and the
1697    // leading-zero arm above guarantees the magnitude is either the
1698    // single byte `"0"` or starts with `[1-9]`, so the only way
1699    // `u32::from_str` can fail here is overflow (the magnitude exceeds
1700    // `u32::MAX`). Surface that as `BadMillicores` with an overflow-
1701    // shaped wording so the diagnostic names the offending magnitude
1702    // verbatim rather than collapsing onto the non-canonical arm —
1703    // matches `parse_byte_size` / `parse_duration` / `rate_limit_codec`
1704    // overflow-arm shape on the peer typed codecs.
1705    let num: u32 = magnitude.parse::<u32>().map_err(|_| {
1706        LimitsError::bad_millicores(format!("{magnitude} (digit-only magnitude overflows u32)"))
1707    })?;
1708    if has_m_suffix {
1709        Ok(num)
1710    } else {
1711        // Bare-core shorthand: `"2"` = 2000 millicores. Use
1712        // `checked_mul` (not the prior `saturating_mul`) so a
1713        // magnitude that overflows u32 on the × 1000 conversion
1714        // surfaces a parser-shaped diagnostic at parse time rather
1715        // than silently saturating to `u32::MAX` (which would land
1716        // as the cap value far from the author's intent and bypass
1717        // any future validate-time upper-bound gate the `:cpu` axis
1718        // grows). Matches `parse_byte_size`'s overflow-arm shape on
1719        // the magnitude × unit multiply.
1720        num.checked_mul(1000).ok_or_else(|| {
1721            LimitsError::bad_millicores(format!(
1722                "{magnitude} cores × 1000 overflows u32 (write the value in millicores: max \"{}m\")",
1723                u32::MAX
1724            ))
1725        })
1726    }
1727}
1728
1729fn render_millicores(m: u32) -> String {
1730    format!("{m}m")
1731}
1732
1733fn ser_millicores<S: Serializer>(v: &Option<u32>, s: S) -> Result<S::Ok, S::Error> {
1734    // Route through the canonical [`crate::render::serialize_option_via_str`]
1735    // — see peer `ser_byte_size` / `ser_duration` routing notes above.
1736    crate::render::serialize_option_via_str(v, s, render_millicores)
1737}
1738
1739fn de_millicores<'de, D: Deserializer<'de>>(d: D) -> Result<Option<u32>, D::Error> {
1740    // Route through the canonical [`crate::render::deserialize_option_via_str`]
1741    // — see peer `de_byte_size` / `de_duration` routing notes above.
1742    crate::render::deserialize_option_via_str(d, parse_millicores)
1743}
1744
1745// Fold the six `LimitsError::{NonInteger,LeadingZero}<Kind>Magnitude
1746// { value: <val>.into() }` wire-up sites on the three typed-magnitude
1747// codec surfaces (`parse_byte_size` / `parse_duration` /
1748// `parse_millicores`) onto one substrate-primitive family per typed
1749// variant — the paired `{ value: String }` single-slot family on
1750// [`LimitsError`]. First fold family on [`LimitsError`], peer of the
1751// four `LayoutError` ctor macro families (`layout_violation_ctors!`
1752// 131ca0d — 16 `{ caixa, issue }` variants; `layout_slot_kind_ctors!`
1753// 0419438 — 4 `{ caixa, kind, slots }` variants;
1754// `LayoutError::missing_entry` 1b09f9d — 1 `{ kind, path }` variant;
1755// `layout_nome_only_ctors!` 3fe3dd7 — 6 `<Variant>(String)` variants)
1756// on the sibling layout-side envelopes, and of the four `AplicacaoError`
1757// ctor macro families (`aplicacao_field_reason_ctors!` 981060b — 7
1758// `{ <field>, reason }` variants; `contrato_target_ctors!` 14b81d5 — 2
1759// `{ de, para, wit, expected }` variants; `contrato_empty_pair_ctors!`
1760// 8580068 — 4 `{ de, para }` variants; `contrato_pair_value_reason_ctors!`
1761// 14e13f1 — 3 `{ de, para, <field>, reason }` variants) on the sibling
1762// mesh-side envelopes.
1763//
1764// Every one of the six wire-up sites — the `NonInteger` / `LeadingZero`
1765// arms inside [`parse_byte_size`], [`parse_duration`], and
1766// [`parse_millicores`] — opened the identical three-line
1767// `return Err(LimitsError::<Variant> { value: <val>.into() });` block
1768// against the per-codec local magnitude binding (`num_trim` on the two
1769// alpha-unit codecs, `magnitude` on the millicores codec) — the exact
1770// "same block re-inlined at every consumer" shape the PRIME DIRECTIVE
1771// names as a bug, on the same altitude the peer four `LayoutError` and
1772// four `AplicacaoError` constructor families each closed on their
1773// sibling envelopes.
1774//
1775// The macro below generates one `#[must_use]` inherent constructor per
1776// variant of shape `fn <ctor>(value: &str) -> LimitsError`, collapsing
1777// the six sites onto one dispatch per arm:
1778// `return Err(LimitsError::<ctor>(<val>));`, byte-equal to the pre-lift
1779// struct-literal on the same `value` argument. The uniform single-field
1780// construction (`value: value.to_string()`) is spelled once — inside the
1781// macro — rather than at every wire-up site. `#[must_use]` fires a
1782// compile warning at any wire-up that mistakenly discards the
1783// constructed error.
1784//
1785// Every future consumer that wants to construct one of these six
1786// variants outside the three current codec surfaces (a deferred
1787// `feira lint --canonical-magnitudes` per-caixa admission verb probing
1788// each authored `:memory` / `:wall-clock` / `:cpu` value against the
1789// same canonical-form gate, an M4 typed `mesh.pleme.io/v1alpha1/Servico`
1790// CR materializer's per-`:limits` admission validators, a per-
1791// `computeunit.yaml` value-shape pre-emitter probing each declared
1792// magnitude ahead of the operator's admit-cycle) reaches the variant
1793// through one call rather than re-inlining the three-line struct-literal
1794// in lockstep with the pre-existing six sites.
1795macro_rules! limits_codec_value_only_ctors {
1796    ($($ctor:ident => $variant:ident),* $(,)?) => {
1797        impl LimitsError {
1798            $(
1799                #[doc = concat!(
1800                    "Construct a [`LimitsError::",
1801                    stringify!($variant),
1802                    "`] naming the offending magnitude `value`. Folds the ",
1803                    "uniform `{ value: value.to_string() }` single-slot ",
1804                    "construction onto one substrate primitive so every ",
1805                    "wire-up on this variant reads through one dispatch ",
1806                    "rather than the pre-lift three-line struct-literal ",
1807                    "block."
1808                )]
1809                #[must_use]
1810                pub fn $ctor(value: &str) -> Self {
1811                    Self::$variant { value: value.to_string() }
1812                }
1813            )*
1814        }
1815    };
1816}
1817
1818limits_codec_value_only_ctors! {
1819    non_integer_byte_magnitude => NonIntegerByteMagnitude,
1820    leading_zero_byte_magnitude => LeadingZeroByteMagnitude,
1821    non_integer_duration_magnitude => NonIntegerDurationMagnitude,
1822    leading_zero_duration_magnitude => LeadingZeroDurationMagnitude,
1823    non_integer_millicore_magnitude => NonIntegerMillicoreMagnitude,
1824    leading_zero_millicore_magnitude => LeadingZeroMillicoreMagnitude,
1825}
1826
1827// Fold the two `LimitsError::Unknown<Kind>Unit { unit: <val>.into() }`
1828// wire-up sites on the two alpha-unit typed-magnitude codec surfaces
1829// (`parse_byte_size` at the `KB | MB | GB | KiB | MiB | GiB | "" | B`
1830// unit-dispatch table's fallthrough arm; `parse_duration` at the
1831// `crate::render::DurationUnitError::UnknownUnit` reverse-map arm of the
1832// `ms | s | "" | m | h` unit-dispatch table) onto one substrate-primitive
1833// family per typed variant — the paired `{ unit: String }` single-slot
1834// family on [`LimitsError`]. Direct peer of the sibling
1835// [`limits_codec_value_only_ctors!`] single-slot family on the same
1836// [`LimitsError`] envelope (6 variants on the `{ value: String }` axis
1837// of the codec surface) and of the peer [`limits_codec_value_byte_ctors!`]
1838// / [`limits_codec_value_char_ctors!`] families on the wider two-slot /
1839// three-slot whitespace-class axes of the same three codec surfaces.
1840//
1841// Every one of the two wire-up sites — the fallthrough of
1842// [`parse_byte_size`]'s unit-dispatch `match` on the caller-scoped
1843// `other: &str` binding; the [`crate::render::DurationUnitError::UnknownUnit`]
1844// reverse-map arm of [`parse_duration`]'s codec-scoped `unit_trim: &str`
1845// binding — opened the identical two-line
1846// `LimitsError::Unknown<Kind>Unit { unit: <val>.into() }` block against
1847// the codec-scoped unit binding — the exact "same block re-inlined at
1848// every consumer" shape the PRIME DIRECTIVE names as a bug, on the same
1849// altitude the peer [`limits_codec_value_only_ctors!`] family closed on
1850// the sibling `{ value: String }` axis of the same codec surface.
1851//
1852// The macro below generates one `#[must_use]` inherent constructor per
1853// variant of shape `fn <ctor>(unit: &str) -> LimitsError`, collapsing
1854// the two sites onto one dispatch per arm: `LimitsError::<ctor>(<val>)`,
1855// byte-equal to the pre-lift struct-literal on the same `unit` argument.
1856// The uniform single-field construction (`unit: unit.to_string()`) is
1857// spelled once — inside the macro — rather than at every wire-up site.
1858// `#[must_use]` fires a compile warning at any wire-up that mistakenly
1859// discards the constructed error.
1860//
1861// Every future consumer that wants to construct one of these two
1862// variants outside the two current codec surfaces (a deferred
1863// `feira lint --canonical-units` per-caixa admission verb probing each
1864// authored `:memory` / `:wall-clock` value against the same
1865// unit-dispatch table, an M4 typed `mesh.pleme.io/v1alpha1/Servico` CR
1866// materializer's per-`:limits` admission validators pre-checking a
1867// per-slot unit alphabet against a cluster-local snapshot, a future
1868// unit-alphabet widening on either codec that shares the same
1869// unknown-unit fallthrough shape) now reaches each variant through one
1870// call rather than re-inlining the two-line struct-literal in lockstep
1871// with the pre-existing two sites.
1872macro_rules! limits_codec_unit_only_ctors {
1873    ($($ctor:ident => $variant:ident),* $(,)?) => {
1874        impl LimitsError {
1875            $(
1876                #[doc = concat!(
1877                    "Construct a [`LimitsError::",
1878                    stringify!($variant),
1879                    "`] naming the offending magnitude `unit`. Folds the ",
1880                    "uniform `{ unit: unit.to_string() }` single-slot ",
1881                    "construction onto one substrate primitive so every ",
1882                    "wire-up on this variant reads through one dispatch ",
1883                    "rather than the pre-lift two-line struct-literal ",
1884                    "block."
1885                )]
1886                #[must_use]
1887                pub fn $ctor(unit: &str) -> Self {
1888                    Self::$variant { unit: unit.to_string() }
1889                }
1890            )*
1891        }
1892    };
1893}
1894
1895limits_codec_unit_only_ctors! {
1896    unknown_byte_unit => UnknownByteUnit,
1897    unknown_duration_unit => UnknownDurationUnit,
1898}
1899
1900// Fold the three `LimitsError::WhitespaceIn<Kind> { value: <val>.into(),
1901// byte }` wire-up sites on the three typed-magnitude codec surfaces
1902// (`parse_byte_size` / `parse_duration` / `parse_millicores`) onto one
1903// substrate-primitive family per typed variant — the paired
1904// `{ value: String, byte: u8 }` two-slot family on [`LimitsError`].
1905// Sibling of the peer [`limits_codec_value_only_ctors!`] single-slot
1906// family on the same three codec surfaces, and of the peer
1907// [`limits_codec_value_char_ctors!`] three-slot family on the
1908// strictly-complementary non-ASCII whitespace class.
1909//
1910// Every one of the three wire-up sites — the ASCII-whitespace-rejection
1911// arm of the paired [`crate::render::reject_whitespace`] closure at
1912// each codec — opened the identical four-line
1913// `|byte| LimitsError::WhitespaceIn<Kind> { value: <s>.into(), byte }`
1914// block against the codec-scoped `<s>: &str` binding.
1915//
1916// The macro below generates one `#[must_use]` inherent constructor per
1917// variant of shape `fn <ctor>(value: &str, byte: u8) -> LimitsError`,
1918// collapsing the three sites onto one dispatch per arm:
1919// `|byte| LimitsError::<ctor>(s, byte)`, byte-equal to the pre-lift
1920// struct-literal on the same `(value, byte)` pair. The uniform two-field
1921// construction (`value: value.to_string()`, `byte`) is spelled once —
1922// inside the macro — rather than at every wire-up site.
1923macro_rules! limits_codec_value_byte_ctors {
1924    ($($ctor:ident => $variant:ident),* $(,)?) => {
1925        impl LimitsError {
1926            $(
1927                #[doc = concat!(
1928                    "Construct a [`LimitsError::",
1929                    stringify!($variant),
1930                    "`] naming the offending magnitude `value` and the ",
1931                    "raw ASCII-whitespace `byte` that fell inside it. ",
1932                    "Folds the uniform `{ value: value.to_string(), byte }` ",
1933                    "two-slot construction onto one substrate primitive so ",
1934                    "every wire-up on this variant reads through one dispatch ",
1935                    "rather than the pre-lift four-line struct-literal block."
1936                )]
1937                #[must_use]
1938                pub fn $ctor(value: &str, byte: u8) -> Self {
1939                    Self::$variant { value: value.to_string(), byte }
1940                }
1941            )*
1942        }
1943    };
1944}
1945
1946limits_codec_value_byte_ctors! {
1947    whitespace_in_byte_size => WhitespaceInByteSize,
1948    whitespace_in_duration => WhitespaceInDuration,
1949    whitespace_in_millicores => WhitespaceInMillicores,
1950}
1951
1952// Fold the three `LimitsError::NonAsciiWhitespaceIn<Kind>
1953// { value: <val>.into(), ch, codepoint: ch as u32 }` wire-up sites on
1954// the three typed-magnitude codec surfaces (`parse_byte_size` /
1955// `parse_duration` / `parse_millicores`) onto one substrate-primitive
1956// family per typed variant — the paired `{ value: String, ch: char,
1957// codepoint: u32 }` three-slot family on [`LimitsError`]. Sibling of
1958// the peer [`limits_codec_value_only_ctors!`] single-slot family on the
1959// same three codec surfaces, and of the peer
1960// [`limits_codec_value_byte_ctors!`] two-slot family on the strictly-
1961// complementary ASCII whitespace class.
1962//
1963// Every one of the three wire-up sites — the Unicode-`White_Space`-
1964// rejection arm of the paired [`crate::render::reject_whitespace`]
1965// closure at each codec — opened the identical five-line
1966// `|ch| LimitsError::NonAsciiWhitespaceIn<Kind> { value: <s>.into(),
1967// ch, codepoint: ch as u32 }` block against the codec-scoped
1968// `<s>: &str` binding, with the load-bearing `codepoint: ch as u32`
1969// derivation open-coded at every wire-up. The macro pulls the
1970// derivation inside the ctor body so every wire-up now reads
1971// `|ch| LimitsError::<ctor>(s, ch)` and every future consumer of the
1972// variant is guaranteed to carry the derivation through one canonical
1973// path rather than re-open-coding it in lockstep with the pre-existing
1974// three sites.
1975//
1976// The macro below generates one `#[must_use]` inherent constructor per
1977// variant of shape `fn <ctor>(value: &str, ch: char) -> LimitsError`,
1978// collapsing the three sites onto one dispatch per arm:
1979// `|ch| LimitsError::<ctor>(s, ch)`, byte-equal to the pre-lift
1980// struct-literal on the same `(value, ch, ch as u32)` triple.
1981macro_rules! limits_codec_value_char_ctors {
1982    ($($ctor:ident => $variant:ident),* $(,)?) => {
1983        impl LimitsError {
1984            $(
1985                #[doc = concat!(
1986                    "Construct a [`LimitsError::",
1987                    stringify!($variant),
1988                    "`] naming the offending magnitude `value` and the ",
1989                    "non-ASCII Unicode whitespace `ch` that fell inside it. ",
1990                    "Folds the uniform `{ value: value.to_string(), ch, ",
1991                    "codepoint: ch as u32 }` three-slot construction onto ",
1992                    "one substrate primitive so every wire-up on this ",
1993                    "variant reads through one dispatch rather than the ",
1994                    "pre-lift five-line struct-literal block. The load-",
1995                    "bearing `codepoint = ch as u32` derivation is pulled ",
1996                    "inside the ctor body so every future consumer of the ",
1997                    "variant carries it through one canonical path."
1998                )]
1999                #[must_use]
2000                pub fn $ctor(value: &str, ch: char) -> Self {
2001                    Self::$variant {
2002                        value: value.to_string(),
2003                        ch,
2004                        codepoint: ch as u32,
2005                    }
2006                }
2007            )*
2008        }
2009    };
2010}
2011
2012limits_codec_value_char_ctors! {
2013    non_ascii_whitespace_in_byte_size => NonAsciiWhitespaceInByteSize,
2014    non_ascii_whitespace_in_duration => NonAsciiWhitespaceInDuration,
2015    non_ascii_whitespace_in_millicores => NonAsciiWhitespaceInMillicores,
2016}
2017
2018// Fold the seven `LimitsError::<Variant> { <field>: <Copy> }` one-field
2019// `Copy`-scalar struct-variant wire-up sites at [`LimitsSpec::validate`]'s
2020// four typed-axis bracket cascades — three closure-slots at the
2021// [`crate::render::require_positive_quantum_multiple_bounded_u64`] `:memory`
2022// axis (`MemoryBelowWasm32Page { bytes }`, `MemoryExceedsWasm32Cap { bytes }`,
2023// `MemoryNotPageMultiple { bytes }`), one at the
2024// [`crate::render::require_positive_bounded_u64`] `:fuel` axis
2025// (`FuelExceedsCap { fuel }`), two at the
2026// [`crate::render::require_positive_canonical_bounded_duration`]
2027// `:wall-clock` axis (`WallClockNotCanonical { wall_clock }`,
2028// `WallClockExceedsCap { wall_clock }`), and one at the
2029// [`crate::render::require_positive_bounded_u32`] `:cpu` axis
2030// (`CpuExceedsCap { millicores }`) — onto one substrate primitive per typed
2031// variant, matching the sibling
2032// [`crate::supervisor::supervisor_scalar_ctors!`] macro (f0f77a2, 4 variants
2033// on the same `{ <field>: RestartStrategy | u32 | Duration }` shape) and the
2034// peer [`crate::aplicacao::aplicacao_policy_scalar_ctors!`] macro (7ef425e,
2035// 8 variants on the same `{ <field>: Duration | u32 }` shape) at that
2036// discipline on the sibling `SupervisorError` per-`:supervisor` scalar axis
2037// and the peer `AplicacaoError` per-`:politicas` scalar axis. Every variant
2038// is a one-field `Copy`-pass-through struct-literal — `u64 | u32 |
2039// Duration` — so the fold routes each wire-up site through one dispatch per
2040// typed variant without a runtime-work delta. Last unlifted per-`:limits`
2041// scalar `LimitsError` variant family folded onto a substrate primitive;
2042// every M2 `LimitsSpec::validate` per-axis bracket-closure slot now reaches
2043// for a bare-function-pointer `LimitsError::<ctor>` in place of the pre-lift
2044// open-coded `|<field>| LimitsError::<Variant> { <field> }` one-line
2045// closure over the same one-field struct-literal.
2046//
2047// Each of the seven wire-up sites opened the identical
2048// `|<field>| LimitsError::<Variant> { <field> }` bracket-closure — the exact
2049// "same block re-inlined at every consumer" shape the PRIME DIRECTIVE names
2050// as a bug, on the same altitude the peer `supervisor_scalar_ctors!` /
2051// `aplicacao_policy_scalar_ctors!` folds each closed on the sibling
2052// `SupervisorError` / `AplicacaoError` envelopes' per-axis cap /
2053// canonical-form / below-quantum arms. The seven variants share one
2054// `{ <field>: <Copy> }` shape, so the fold routes each wire-up site through
2055// one dispatch per typed variant.
2056//
2057// The macro below generates one static constructor per variant of shape
2058// `const fn <ctor>(<field>: <ty>) -> LimitsError`, so every wire-up site
2059// collapses onto one dispatch: `LimitsError::<ctor>(<val>)`, byte-equal to
2060// the pre-lift struct-literal on the same `Copy`-`<ty>` fixture — as a bare
2061// function pointer in the `impl FnOnce(<ty>) -> LimitsError` bracket-
2062// closure slot every [`crate::render::require_positive_bounded_u32`] /
2063// [`crate::render::require_positive_bounded_u64`] /
2064// [`crate::render::require_positive_canonical_bounded_duration`] /
2065// [`crate::render::require_positive_quantum_multiple_bounded_u64`] gate
2066// carries — rather than the pre-lift open-coded one-line closure over the
2067// same one-field struct-literal. `const fn` preserves the `Copy`-pass-
2068// through's zero-runtime-work property verbatim. Every constructor is
2069// `#[must_use]` so a caller who mistakenly discards the constructed error
2070// trips a compile warning at the wire-up site.
2071//
2072// Every future consumer that wants to construct one of these seven variants
2073// outside `LimitsSpec::validate` — a deferred
2074// `mesh.pleme.io/v1alpha1/Servico` CR materializer's admission webhook
2075// re-checking one edited `:memory` / `:fuel` / `:wall-clock` / `:cpu` slot
2076// against the below-quantum + cap + canonical-form cascade, a future
2077// `feira validate --limits` per-caixa admission verb re-running the shape
2078// gates on demand, a per-Servico overlay resolver rejecting an author-
2079// supplied slot against a cluster-local snapshot — now reaches each variant
2080// through one call rather than re-inlining the per-shape struct-literal
2081// block in lockstep with the seven in-crate wire-up sites.
2082macro_rules! limits_scalar_ctors {
2083    ($($ctor:ident => $variant:ident { $field:ident: $ty:ty }),* $(,)?) => {
2084        impl LimitsError {
2085            $(
2086                #[doc = concat!(
2087                    "Construct a [`LimitsError::",
2088                    stringify!($variant),
2089                    "`] naming the offending per-`:limits` `",
2090                    stringify!($field),
2091                    "` scalar. Folds the uniform `Self::",
2092                    stringify!($variant),
2093                    " { ",
2094                    stringify!($field),
2095                    " }` one-field `Copy`-pass-through struct-literal onto ",
2096                    "one substrate primitive so every per-axis wire-up on ",
2097                    "this variant reads through one dispatch — as a bare ",
2098                    "function pointer in the `impl FnOnce(",
2099                    stringify!($ty),
2100                    ") -> LimitsError` bracket-closure slot every ",
2101                    "`crate::render::require_positive_bounded_*` / ",
2102                    "`crate::render::require_positive_canonical_bounded_*` / ",
2103                    "`crate::render::require_positive_quantum_multiple_bounded_*` ",
2104                    "gate carries — rather than the pre-lift open-coded ",
2105                    "one-line closure over the same one-field struct-literal. ",
2106                    "`const fn` preserves the `Copy`-pass-through's ",
2107                    "zero-runtime-work property verbatim."
2108                )]
2109                #[must_use]
2110                pub const fn $ctor($field: $ty) -> Self {
2111                    Self::$variant { $field }
2112                }
2113            )*
2114        }
2115    };
2116}
2117
2118limits_scalar_ctors! {
2119    memory_below_wasm32_page => MemoryBelowWasm32Page { bytes: u64 },
2120    memory_exceeds_wasm32_cap => MemoryExceedsWasm32Cap { bytes: u64 },
2121    memory_not_page_multiple => MemoryNotPageMultiple { bytes: u64 },
2122    fuel_exceeds_cap => FuelExceedsCap { fuel: u64 },
2123    wall_clock_not_canonical => WallClockNotCanonical { wall_clock: Duration },
2124    wall_clock_exceeds_cap => WallClockExceedsCap { wall_clock: Duration },
2125    cpu_exceeds_cap => CpuExceedsCap { millicores: u32 },
2126}
2127
2128// Fold the five `LimitsError::BadMillicores(<into-String-expr>)` wire-up
2129// sites on the [`parse_millicores`] codec surface onto one substrate
2130// primitive per typed variant — the paired `(String)` single-slot
2131// tuple-newtype [`LimitsError::BadMillicores`] on the millicores codec
2132// surface. Peer of the sibling [`limits_codec_value_only_ctors!`] /
2133// [`limits_codec_unit_only_ctors!`] / [`limits_codec_value_byte_ctors!`]
2134// / [`limits_codec_value_char_ctors!`] families on the same
2135// [`LimitsError`] envelope (the paired `{ value: String }` /
2136// `{ unit: String }` / `{ value: String, byte: u8 }` /
2137// `{ value: String, ch: char, codepoint: u32 }` struct-shaped families
2138// on the same codec surface) and of the peer [`limits_scalar_ctors!`]
2139// family on the wider `Copy`-`{ <field>: <ty> }` typed-scalar axis of
2140// the same [`LimitsError`] envelope. Closes the widest un-lifted variant
2141// on [`LimitsError`] — every one of the five wire-up sites opened the
2142// identical `LimitsError::BadMillicores(<into-String-expr>)` block
2143// against the codec-scoped `&str` (`s`) or `String` (`format!(...)`)
2144// binding, so the fold routes each site through one dispatch on a
2145// uniform `impl Into<String>` param, byte-equal to the pre-lift tuple-
2146// newtype construction on the same argument. The `impl Into<String>`
2147// bound covers both wire-up shapes — the three `s.into()` `&str` sites
2148// (empty-`:cpu`, bare-`m`-magnitude fallthrough, non-digit-only garbage
2149// fallthrough) and the two `format!(...)` `String` sites (digit-only
2150// magnitude overflows u32, bare-core-shorthand × 1000 overflow) —
2151// without forcing either caller to spell the conversion at the wire-up
2152// site. `#[must_use]` fires a compile warning at any wire-up that
2153// mistakenly discards the constructed error.
2154//
2155// Every future consumer that wants to construct this variant outside
2156// [`parse_millicores`] (a deferred `feira lint --canonical-magnitudes`
2157// per-caixa admission verb probing each authored `:cpu` value against
2158// the same canonical-form gate, an M4 typed
2159// `mesh.pleme.io/v1alpha1/Servico` CR materializer's per-`:limits`
2160// admission validator re-checking one edited `:cpu` slot against the
2161// codec's parser floor, a per-`computeunit.yaml` value-shape pre-emitter
2162// probing each declared millicores magnitude ahead of the operator's
2163// admit-cycle) now reaches the variant through one call rather than
2164// re-inlining the tuple-newtype block in lockstep with the pre-existing
2165// five sites — same discipline the peer per-variant lifts on
2166// [`AplicacaoError`] / [`SupervisorError`] / [`UpgradeError`] /
2167// [`LayoutError`] / [`DepError`] / [`ManifestError`] have converged
2168// through the "one substrate primitive per emit-site variant" ratchet.
2169impl LimitsError {
2170    /// Construct a [`LimitsError::BadMillicores`] carrying the offending
2171    /// millicores authoring string `value` verbatim in the variant's
2172    /// tuple-newtype payload. Folds the uniform
2173    /// `Self::BadMillicores(value.into())` tuple-newtype construction
2174    /// onto one substrate primitive so every wire-up on the variant
2175    /// reads through one dispatch rather than the pre-lift open-coded
2176    /// `LimitsError::BadMillicores(<into-String-expr>)` block. The
2177    /// `impl Into<String>` bound covers both wire-up shapes on
2178    /// [`parse_millicores`] — a `&str` binding (`s.into()`) and a
2179    /// `String` binding (`format!(...)`) — without forcing the caller
2180    /// to spell the conversion at the wire-up site.
2181    #[must_use]
2182    pub fn bad_millicores(value: impl Into<String>) -> Self {
2183        Self::BadMillicores(value.into())
2184    }
2185}
2186
2187// Fold the three `LimitsError::BadByteMagnitude(<into-String-expr>)`
2188// wire-up sites on the [`parse_byte_size`] codec surface onto one
2189// substrate primitive — the paired `(String)` single-slot tuple-newtype
2190// [`LimitsError::BadByteMagnitude`] on the byte-size codec surface, the
2191// direct sibling to the [`LimitsError::bad_millicores`] fold above on
2192// the peer [`parse_millicores`] codec surface (da7602f). Same
2193// discipline the peer per-variant lifts on [`AplicacaoError`] /
2194// [`SupervisorError`] / [`UpgradeError`] / [`LayoutError`] /
2195// [`DepError`] / [`ManifestError`] have converged through the
2196// "one substrate primitive per emit-site variant" ratchet: the three
2197// wire-up sites open the identical
2198// `LimitsError::BadByteMagnitude(<into-String-expr>)` block against
2199// the codec-scoped `&str` (`num_part.into()` — non-digit-only garbage
2200// fallthrough after the numeric-shape gate) or `String`
2201// (`format!(...)` — digit-only magnitude overflows u64, magnitude ×
2202// unit overflows u64) binding, so the fold routes each site through
2203// one dispatch on a uniform `impl Into<String>` param, byte-equal to
2204// the pre-lift tuple-newtype construction on the same argument.
2205//
2206// Every future consumer that wants to construct this variant outside
2207// [`parse_byte_size`] (a deferred `feira lint --canonical-magnitudes`
2208// per-caixa admission verb probing each authored `:memory` value
2209// against the same canonical-form gate, an M4 typed
2210// `mesh.pleme.io/v1alpha1/Servico` CR materializer's per-`:limits`
2211// admission validator re-checking one edited `:memory` slot against
2212// the codec's parser floor, a per-`computeunit.yaml` value-shape pre-
2213// emitter probing each declared byte-size magnitude ahead of the
2214// operator's admit-cycle) now reaches the variant through one call
2215// rather than re-inlining the tuple-newtype block in lockstep with
2216// the pre-existing three sites.
2217impl LimitsError {
2218    /// Construct a [`LimitsError::BadByteMagnitude`] carrying the
2219    /// offending byte-size authoring string `value` verbatim in the
2220    /// variant's tuple-newtype payload. Folds the uniform
2221    /// `Self::BadByteMagnitude(value.into())` tuple-newtype
2222    /// construction onto one substrate primitive so every wire-up on
2223    /// the variant reads through one dispatch rather than the pre-lift
2224    /// open-coded `LimitsError::BadByteMagnitude(<into-String-expr>)`
2225    /// block. The `impl Into<String>` bound covers both wire-up shapes
2226    /// on [`parse_byte_size`] — a `&str` binding (`num_part.into()`)
2227    /// and a `String` binding (`format!(...)`) — without forcing the
2228    /// caller to spell the conversion at the wire-up site. Direct
2229    /// sibling to [`LimitsError::bad_millicores`] on the peer
2230    /// [`parse_millicores`] codec surface.
2231    #[must_use]
2232    pub fn bad_byte_magnitude(value: impl Into<String>) -> Self {
2233        Self::BadByteMagnitude(value.into())
2234    }
2235}
2236
2237// Fold the sole `LimitsError::EmptyByteSize(<into-String-expr>)` wire-up
2238// site on the [`parse_byte_size`] codec surface onto one substrate
2239// primitive — the paired `(String)` single-slot tuple-newtype
2240// [`LimitsError::EmptyByteSize`] on the byte-size codec surface, the
2241// peer to the sibling [`LimitsError::bad_byte_magnitude`] fold above on
2242// the same [`parse_byte_size`] codec surface (837babc) but on the
2243// empty-shape axis rather than the bad-magnitude axis of the same
2244// `(String)` tuple-newtype codec-magnitude family. Same discipline the
2245// peer per-variant lifts on [`AplicacaoError`] / [`SupervisorError`] /
2246// [`UpgradeError`] / [`LayoutError`] / [`DepError`] / [`ManifestError`]
2247// have converged through the "one substrate primitive per emit-site
2248// variant" ratchet: the sole wire-up site opens the identical
2249// `LimitsError::EmptyByteSize(<into-String-expr>)` block against the
2250// codec-scoped `&str` (`s.into()`) binding after the outer `s.trim()` /
2251// `is_empty()` gate on the codec entry surface, so the fold routes the
2252// site through one dispatch on a uniform `impl Into<String>` param,
2253// byte-equal to the pre-lift tuple-newtype construction on the same
2254// argument. The `impl Into<String>` bound covers the pre-lift `&str`
2255// binding without forcing the caller to spell the `.into()` conversion
2256// at the wire-up site — same shape the peer [`LimitsError::bad_millicores`]
2257// / [`LimitsError::bad_byte_magnitude`] / [`LimitsError::bad_duration_magnitude`]
2258// folds carry on the peer bad-magnitude axis of the same paired codec-
2259// magnitude family. `#[must_use]` fires a compile warning at any
2260// wire-up that mistakenly discards the constructed error.
2261//
2262// Every future consumer that wants to construct this variant outside
2263// [`parse_byte_size`] (a deferred `feira lint --canonical-magnitudes`
2264// per-caixa admission verb probing each authored `:memory` value
2265// against the same empty-shape gate, an M4 typed
2266// `mesh.pleme.io/v1alpha1/Servico` CR materializer's per-`:limits`
2267// admission validator re-checking one edited `:memory` slot against
2268// the codec's parser floor, a per-`computeunit.yaml` value-shape
2269// pre-emitter probing each declared byte-size magnitude ahead of the
2270// operator's admit-cycle) now reaches the variant through one call
2271// rather than re-inlining the tuple-newtype block in lockstep with
2272// the pre-existing wire-up.
2273impl LimitsError {
2274    /// Construct a [`LimitsError::EmptyByteSize`] carrying the offending
2275    /// empty-magnitude authoring string `value` verbatim in the variant's
2276    /// tuple-newtype payload. Folds the uniform
2277    /// `Self::EmptyByteSize(value.into())` tuple-newtype construction
2278    /// onto one substrate primitive so every wire-up on the variant
2279    /// reads through one dispatch rather than the pre-lift open-coded
2280    /// `LimitsError::EmptyByteSize(<into-String-expr>)` block. The
2281    /// `impl Into<String>` bound covers the pre-lift `&str` wire-up
2282    /// shape on [`parse_byte_size`] (`s.into()` on the codec-scoped
2283    /// `s: &str` binding after the outer `s.trim()` / `is_empty()` gate)
2284    /// without forcing the caller to spell the conversion at the wire-up
2285    /// site. Peer to the sibling [`LimitsError::bad_byte_magnitude`] on
2286    /// the same [`parse_byte_size`] codec surface but on the empty-shape
2287    /// axis rather than the bad-magnitude axis of the same `(String)`
2288    /// tuple-newtype codec-magnitude family.
2289    #[must_use]
2290    pub fn empty_byte_size(value: impl Into<String>) -> Self {
2291        Self::EmptyByteSize(value.into())
2292    }
2293}
2294
2295// Fold the three `LimitsError::BadDurationMagnitude(<into-String-expr>)`
2296// wire-up sites on the [`parse_duration`] codec surface onto one
2297// substrate primitive — the paired `(String)` single-slot tuple-newtype
2298// [`LimitsError::BadDurationMagnitude`] on the duration codec surface,
2299// the direct sibling to the [`LimitsError::bad_millicores`] (da7602f)
2300// and [`LimitsError::bad_byte_magnitude`] (837babc) folds above on the
2301// peer [`parse_millicores`] / [`parse_byte_size`] codec surfaces. Same
2302// discipline the peer per-variant lifts on [`AplicacaoError`] /
2303// [`SupervisorError`] / [`UpgradeError`] / [`LayoutError`] /
2304// [`DepError`] / [`ManifestError`] have converged through the
2305// "one substrate primitive per emit-site variant" ratchet: the three
2306// wire-up sites open the identical
2307// `LimitsError::BadDurationMagnitude(<into-String-expr>)` block against
2308// the codec-scoped `&str` (`num_part.into()` — non-digit-only garbage
2309// fallthrough after the numeric-shape gate) or `String`
2310// (`format!(...)` — digit-only magnitude overflows u64, magnitude ×
2311// unit overflows u64) binding, so the fold routes each site through
2312// one dispatch on a uniform `impl Into<String>` param, byte-equal to
2313// the pre-lift tuple-newtype construction on the same argument. Closes
2314// the last un-lifted variant of the paired `(String)` tuple-newtype
2315// codec-magnitude family across the three typed-magnitude codec
2316// surfaces the peer folds already own.
2317//
2318// Every future consumer that wants to construct this variant outside
2319// [`parse_duration`] (a deferred `feira lint --canonical-magnitudes`
2320// per-caixa admission verb probing each authored `:wall-clock` /
2321// `:restart-window` / `:politicas :timeout` /
2322// `:politicas :circuit-breaker :window` value against the same
2323// canonical-form gate, an M4 typed `mesh.pleme.io/v1alpha1/Servico` CR
2324// materializer's per-`:limits` admission validator re-checking one
2325// edited `:wall-clock` slot against the codec's parser floor, a
2326// per-`computeunit.yaml` value-shape pre-emitter probing each declared
2327// duration magnitude ahead of the operator's admit-cycle) now reaches
2328// the variant through one call rather than re-inlining the tuple-
2329// newtype block in lockstep with the pre-existing three sites.
2330impl LimitsError {
2331    /// Construct a [`LimitsError::BadDurationMagnitude`] carrying the
2332    /// offending duration authoring string `value` verbatim in the
2333    /// variant's tuple-newtype payload. Folds the uniform
2334    /// `Self::BadDurationMagnitude(value.into())` tuple-newtype
2335    /// construction onto one substrate primitive so every wire-up on
2336    /// the variant reads through one dispatch rather than the pre-lift
2337    /// open-coded `LimitsError::BadDurationMagnitude(<into-String-expr>)`
2338    /// block. The `impl Into<String>` bound covers both wire-up shapes
2339    /// on [`parse_duration`] — a `&str` binding (`num_part.into()`)
2340    /// and a `String` binding (`format!(...)`) — without forcing the
2341    /// caller to spell the conversion at the wire-up site. Direct
2342    /// sibling to [`LimitsError::bad_millicores`] on the peer
2343    /// [`parse_millicores`] codec surface and to
2344    /// [`LimitsError::bad_byte_magnitude`] on the peer [`parse_byte_size`]
2345    /// codec surface — closes the last un-lifted `(String)` tuple-
2346    /// newtype variant on the paired codec-magnitude family.
2347    #[must_use]
2348    pub fn bad_duration_magnitude(value: impl Into<String>) -> Self {
2349        Self::BadDurationMagnitude(value.into())
2350    }
2351}
2352
2353// Fold the sole `LimitsError::EmptyDuration(<into-String-expr>)` wire-up
2354// site on the [`parse_duration`] codec surface onto one substrate
2355// primitive — the paired `(String)` single-slot tuple-newtype
2356// [`LimitsError::EmptyDuration`] on the duration codec surface, the
2357// peer to the sibling [`LimitsError::empty_byte_size`] fold above
2358// (7a4b003) on the [`parse_byte_size`] codec surface but on the
2359// duration axis rather than the byte-size axis of the same `(String)`
2360// tuple-newtype codec empty-shape family. Same discipline the peer
2361// per-variant lifts on [`AplicacaoError`] / [`SupervisorError`] /
2362// [`UpgradeError`] / [`LayoutError`] / [`DepError`] / [`ManifestError`]
2363// have converged through the "one substrate primitive per emit-site
2364// variant" ratchet: the sole wire-up site opens the identical
2365// `LimitsError::EmptyDuration(<into-String-expr>)` block against the
2366// codec-scoped `&str` (`s.into()`) binding after the outer `s.trim()` /
2367// `is_empty()` gate on the codec entry surface, so the fold routes the
2368// site through one dispatch on a uniform `impl Into<String>` param,
2369// byte-equal to the pre-lift tuple-newtype construction on the same
2370// argument. The `impl Into<String>` bound covers the pre-lift `&str`
2371// binding without forcing the caller to spell the `.into()` conversion
2372// at the wire-up site — same shape the peer [`LimitsError::empty_byte_size`]
2373// / [`LimitsError::bad_duration_magnitude`] / [`LimitsError::bad_byte_magnitude`]
2374// / [`LimitsError::bad_millicores`] folds carry on the peer bad-magnitude
2375// and empty-shape axes of the same paired codec-magnitude family.
2376// `#[must_use]` fires a compile warning at any wire-up that mistakenly
2377// discards the constructed error.
2378//
2379// Every future consumer that wants to construct this variant outside
2380// [`parse_duration`] (a deferred `feira lint --canonical-magnitudes`
2381// per-caixa admission verb probing each authored `:wall-clock` value
2382// against the same empty-shape gate, an M4 typed
2383// `mesh.pleme.io/v1alpha1/Servico` CR materializer's per-`:limits`
2384// admission validator re-checking one edited `:wall-clock` slot against
2385// the codec's parser floor, a per-`computeunit.yaml` value-shape
2386// pre-emitter probing each declared duration magnitude ahead of the
2387// operator's admit-cycle) now reaches the variant through one call
2388// rather than re-inlining the tuple-newtype block in lockstep with
2389// the pre-existing wire-up. Closes the last un-lifted `(String)`
2390// tuple-newtype empty-shape variant on the paired codec-magnitude
2391// family (`parse_byte_size` and `parse_duration` — `parse_millicores`
2392// has no empty-shape peer; its bad-shape axis rejects an empty
2393// magnitude through the digit-shape gate on the same codec surface).
2394impl LimitsError {
2395    /// Construct a [`LimitsError::EmptyDuration`] carrying the offending
2396    /// empty-magnitude authoring string `value` verbatim in the variant's
2397    /// tuple-newtype payload. Folds the uniform
2398    /// `Self::EmptyDuration(value.into())` tuple-newtype construction
2399    /// onto one substrate primitive so every wire-up on the variant
2400    /// reads through one dispatch rather than the pre-lift open-coded
2401    /// `LimitsError::EmptyDuration(<into-String-expr>)` block. The
2402    /// `impl Into<String>` bound covers the pre-lift `&str` wire-up
2403    /// shape on [`parse_duration`] (`s.into()` on the codec-scoped
2404    /// `s: &str` binding after the outer `s.trim()` / `is_empty()` gate)
2405    /// without forcing the caller to spell the conversion at the wire-up
2406    /// site. Peer to the sibling [`LimitsError::empty_byte_size`] on the
2407    /// [`parse_byte_size`] codec surface — the same empty-shape axis of
2408    /// the paired `(String)` tuple-newtype codec empty-shape family, but
2409    /// on the duration axis rather than the byte-size axis.
2410    #[must_use]
2411    pub fn empty_duration(value: impl Into<String>) -> Self {
2412        Self::EmptyDuration(value.into())
2413    }
2414}
2415
2416#[cfg(test)]
2417mod tests {
2418    use super::*;
2419
2420    #[test]
2421    fn parse_byte_size_known_units() {
2422        assert_eq!(parse_byte_size("64MiB").unwrap(), 64 * 1024 * 1024);
2423        assert_eq!(parse_byte_size("1GiB").unwrap(), 1024 * 1024 * 1024);
2424        assert_eq!(parse_byte_size("512KiB").unwrap(), 512 * 1024);
2425        assert_eq!(parse_byte_size("1KB").unwrap(), 1_000);
2426        assert_eq!(parse_byte_size("1024").unwrap(), 1024);
2427    }
2428
2429    #[test]
2430    fn parse_byte_size_rejects_unknown() {
2431        assert!(matches!(
2432            parse_byte_size("1YiB"),
2433            Err(LimitsError::UnknownByteUnit { .. })
2434        ));
2435        assert!(matches!(
2436            parse_byte_size("not-a-number"),
2437            Err(LimitsError::BadByteMagnitude(_))
2438        ));
2439    }
2440
2441    #[test]
2442    fn parse_duration_known_units() {
2443        assert_eq!(parse_duration("30s").unwrap(), Duration::from_secs(30));
2444        assert_eq!(parse_duration("500ms").unwrap(), Duration::from_millis(500));
2445        assert_eq!(parse_duration("2m").unwrap(), Duration::from_secs(120));
2446        assert_eq!(parse_duration("1h").unwrap(), Duration::from_secs(3600));
2447    }
2448
2449    #[test]
2450    fn parse_millicores_both_forms() {
2451        assert_eq!(parse_millicores("500m").unwrap(), 500);
2452        assert_eq!(parse_millicores("2").unwrap(), 2000);
2453    }
2454
2455    #[test]
2456    fn render_byte_size_canonical() {
2457        assert_eq!(render_byte_size(64 * 1024 * 1024), "64MiB");
2458        assert_eq!(render_byte_size(1024 * 1024 * 1024), "1GiB");
2459        assert_eq!(render_byte_size(1024), "1KiB");
2460        assert_eq!(render_byte_size(123), "123");
2461    }
2462
2463    #[test]
2464    fn ser_byte_size_routes_through_render_serialize_option_via_str_canonical() {
2465        // Routing pin: `ser_byte_size` (the `#[serde(serialize_with = …)]`
2466        // hook on `LimitsSpec::memory`) MUST emit exactly the bytes the
2467        // canonical `crate::render::serialize_option_via_str` primitive
2468        // produces when threaded through the peer `render_byte_size`
2469        // dispatch. Any future accidental re-inline of a bespoke
2470        // `match v { Some(_) => s.serialize_str(_), None =>
2471        // s.serialize_none() }` block inside this module — the shape
2472        // this lift removed — surfaces here as a byte-value drift on
2473        // the very first canonical form the two implementations
2474        // disagree on. Peer of
2475        // `ser_duration_routes_through_supervisor_duration_codec_render_canonical`
2476        // on the sibling `LimitsSpec::wall_clock` axis; same "one
2477        // canonical dispatch per axis, thin projections at each
2478        // consumer" discipline the sibling caixa-core substrate
2479        // primitives already carry.
2480        for n in [
2481            0u64,
2482            1,
2483            1023,
2484            1024,
2485            64 * 1024 * 1024,
2486            4 * 1024 * 1024 * 1024,
2487        ] {
2488            let limits = LimitsSpec {
2489                memory: Some(n),
2490                fuel: None,
2491                wall_clock: None,
2492                cpu: None,
2493            };
2494            let json: serde_json::Value =
2495                serde_json::from_str(&serde_json::to_string(&limits).unwrap()).unwrap();
2496            let emitted = json[crate::render::M2_LIMITS_KEY_MEMORY]
2497                .as_str()
2498                .expect("memory must serialize to a string");
2499            let canonical = render_byte_size(n);
2500            assert_eq!(
2501                emitted, canonical,
2502                "ser_byte_size drifted from render_byte_size via \
2503                 serialize_option_via_str on {n} bytes",
2504            );
2505        }
2506    }
2507
2508    #[test]
2509    fn de_byte_size_routes_through_render_deserialize_option_via_str_canonical() {
2510        // Routing pin: `de_byte_size` (the
2511        // `#[serde(deserialize_with = …)]` hook on
2512        // `LimitsSpec::memory`) MUST accept exactly the canonical
2513        // string set the peer `parse_byte_size` function accepts, and
2514        // reject everything else with the parser's typed `LimitsError`
2515        // surfaced through `serde::de::Error::custom` — the shape the
2516        // lifted `crate::render::deserialize_option_via_str` primitive
2517        // enforces. A future accidental re-inline of a bespoke `let
2518        // opt: Option<String> = Option::deserialize(d)?; match opt {
2519        // … }` block inside this module — the shape this lift removed
2520        // — that drifted on either arm (silently accepting a value the
2521        // parser rejects, or swallowing a parser error as `Ok(None)`)
2522        // surfaces here.
2523        for raw in ["64MiB", "1024", "0", "4GiB"] {
2524            let field = crate::render::M2_LIMITS_KEY_MEMORY;
2525            let payload = format!("{{\"{field}\":\"{raw}\"}}");
2526            let limits: LimitsSpec =
2527                serde_json::from_str(&payload).expect("canonical memory string must round-trip");
2528            let canonical = parse_byte_size(raw).expect("parse_byte_size accepts canonical form");
2529            assert_eq!(
2530                limits.memory,
2531                Some(canonical),
2532                "de_byte_size drifted from parse_byte_size via \
2533                 deserialize_option_via_str on {raw:?}",
2534            );
2535        }
2536        // Null-arm pin: `null` folds to `None` without invoking the
2537        // parser — the exact contract the lifted primitive's null-arm
2538        // test pins.
2539        let field = crate::render::M2_LIMITS_KEY_MEMORY;
2540        let null_payload = format!("{{\"{field}\":null}}");
2541        let empty: LimitsSpec = serde_json::from_str(&null_payload)
2542            .expect("null memory field must fold to LimitsSpec::memory = None");
2543        assert_eq!(
2544            empty.memory, None,
2545            "de_byte_size must fold null → None via \
2546             deserialize_option_via_str's null-arm",
2547        );
2548        // Reject-arm pin: a bogus string surfaces the parser's error
2549        // through `serde::de::Error::custom` — not `Ok(None)`.
2550        let bad_payload = format!("{{\"{field}\":\"64XiB\"}}");
2551        let err = serde_json::from_str::<LimitsSpec>(&bad_payload)
2552            .expect_err("bogus memory string must surface the parser's error");
2553        let err_text = err.to_string();
2554        assert!(
2555            err_text.contains("64XiB") || err_text.contains("XiB"),
2556            "de_byte_size must surface parse_byte_size's typed \
2557             LimitsError through serde::de::Error::custom — got \
2558             {err_text:?}",
2559        );
2560    }
2561
2562    #[test]
2563    fn ser_duration_routes_through_supervisor_duration_codec_render_canonical() {
2564        // Routing pin: `ser_duration` (the `#[serde(serialize_with = …)]`
2565        // hook on `LimitsSpec::wall_clock`) MUST emit exactly the bytes
2566        // the canonical `crate::supervisor::duration_codec::render`
2567        // primitive produces. Any future accidental re-introduction of a
2568        // sibling free-function `render_duration` shadow inside this
2569        // module — or a per-slot `serialize_with` closure that inlines
2570        // its own magnitude/unit decision tree — surfaces here as a
2571        // byte-value drift on the very first canonical form the two
2572        // implementations disagree on, well before the drift reaches any
2573        // downstream renderer's `wall_clock:` overlay. Same "one
2574        // canonical dispatch per axis, thin projections at each consumer"
2575        // discipline the sibling caixa-core substrate primitives already
2576        // carry on the peer WIT-shape / M2 supervisor-strategy / M3
2577        // mesh-slot free-function classifier families.
2578        for d in [
2579            Duration::from_secs(30),
2580            Duration::from_millis(500),
2581            Duration::from_secs(120),
2582            Duration::from_secs(3600),
2583            Duration::from_millis(0),
2584            Duration::from_millis(1500),
2585        ] {
2586            let limits = LimitsSpec {
2587                memory: None,
2588                fuel: None,
2589                wall_clock: Some(d),
2590                cpu: None,
2591            };
2592            let json: serde_json::Value =
2593                serde_json::from_str(&serde_json::to_string(&limits).unwrap()).unwrap();
2594            let emitted = json[crate::render::M2_LIMITS_KEY_WALL_CLOCK]
2595                .as_str()
2596                .expect("wall_clock must serialize to a string");
2597            let canonical = crate::supervisor::duration_codec::render(d);
2598            assert_eq!(
2599                emitted, canonical,
2600                "ser_duration drifted from supervisor::duration_codec::render on {d:?}",
2601            );
2602        }
2603    }
2604
2605    #[test]
2606    fn parse_byte_size_routes_whitespace_through_render_reject_whitespace_canonical() {
2607        // Routing pin: the paired whitespace-rejection block at the
2608        // top of `parse_byte_size` MUST route through the substrate-
2609        // side [`crate::render::reject_whitespace`] primitive — the
2610        // single-owner paired-arm gate every typed-magnitude codec
2611        // in caixa-core shares. Any future accidental re-inline of a
2612        // bespoke
2613        //
2614        // ```ignore
2615        // if let Some(byte) = find_ascii_whitespace_byte(s) { … }
2616        // if let Some(ch)   = find_non_ascii_whitespace_char(s)  { … }
2617        // ```
2618        //
2619        // block inside this module — the shape this lift removed —
2620        // that drifted on either arm surfaces here as a variant-shape
2621        // drift on the very first canonical form the two
2622        // implementations disagree on. Byte-shape pins cover the
2623        // ASCII WhatWG-conformant set (space / tab / LF / FF / CR)
2624        // and the strictly-complementary non-ASCII Unicode
2625        // `White_Space` class (NBSP / LINE SEPARATOR / EM-SPACE /
2626        // IDEOGRAPHIC SPACE) on the exemplar `:limits :memory` axis
2627        // — peer of the pre-existing `ser_byte_size_routes_through_
2628        // render_serialize_option_via_str_canonical` /
2629        // `de_byte_size_routes_through_render_deserialize_option_
2630        // via_str_canonical` pins on the sibling codec-hook axis.
2631        for (raw, byte) in [
2632            (" 64MiB", 0x20u8),
2633            ("64MiB ", 0x20u8),
2634            ("64 MiB", 0x20u8),
2635            ("\t64MiB", 0x09u8),
2636            ("64MiB\n", 0x0Au8),
2637        ] {
2638            let err = parse_byte_size(raw)
2639                .expect_err("ASCII-whitespace-carrying byte-size input must be rejected");
2640            let via_primitive = crate::render::reject_whitespace::<LimitsError, _, _>(
2641                raw,
2642                |b| LimitsError::WhitespaceInByteSize {
2643                    value: raw.into(),
2644                    byte: b,
2645                },
2646                |ch| LimitsError::NonAsciiWhitespaceInByteSize {
2647                    value: raw.into(),
2648                    ch,
2649                    codepoint: ch as u32,
2650                },
2651            )
2652            .expect_err("primitive must reject the same ASCII-whitespace shape");
2653            assert_eq!(
2654                err, via_primitive,
2655                "parse_byte_size drifted from crate::render::reject_whitespace \
2656                 on ASCII-whitespace input {raw:?}"
2657            );
2658            assert!(
2659                matches!(
2660                    err,
2661                    LimitsError::WhitespaceInByteSize { value: ref v, byte: b } if v == raw && b == byte
2662                ),
2663                "parse_byte_size must surface WhitespaceInByteSize {{ value: {raw:?}, byte: 0x{byte:02x} }}"
2664            );
2665        }
2666        for (raw, expected_ch) in [
2667            ("\u{00A0}64MiB", '\u{00A0}'),
2668            ("64\u{2003}MiB", '\u{2003}'),
2669            ("64MiB\u{2028}", '\u{2028}'),
2670            ("\u{3000}64MiB", '\u{3000}'),
2671        ] {
2672            let err = parse_byte_size(raw)
2673                .expect_err("non-ASCII-whitespace-carrying byte-size input must be rejected");
2674            let via_primitive = crate::render::reject_whitespace::<LimitsError, _, _>(
2675                raw,
2676                |b| LimitsError::WhitespaceInByteSize {
2677                    value: raw.into(),
2678                    byte: b,
2679                },
2680                |ch| LimitsError::NonAsciiWhitespaceInByteSize {
2681                    value: raw.into(),
2682                    ch,
2683                    codepoint: ch as u32,
2684                },
2685            )
2686            .expect_err("primitive must reject the same non-ASCII-whitespace shape");
2687            assert_eq!(
2688                err, via_primitive,
2689                "parse_byte_size drifted from crate::render::reject_whitespace \
2690                 on non-ASCII-whitespace input {raw:?}"
2691            );
2692            assert!(
2693                matches!(
2694                    err,
2695                    LimitsError::NonAsciiWhitespaceInByteSize { value: ref v, ch, codepoint }
2696                        if v == raw && ch == expected_ch && codepoint == expected_ch as u32
2697                ),
2698                "parse_byte_size must surface NonAsciiWhitespaceInByteSize \
2699                 {{ value: {raw:?}, ch: {expected_ch:?}, codepoint: {cp:#06X} }}",
2700                cp = expected_ch as u32
2701            );
2702        }
2703    }
2704
2705    #[test]
2706    fn limits_round_trip_through_json() {
2707        let limits = LimitsSpec {
2708            memory: Some(64 * 1024 * 1024),
2709            fuel: Some(1_000_000),
2710            wall_clock: Some(Duration::from_secs(30)),
2711            cpu: Some(500),
2712        };
2713        let json = serde_json::to_string(&limits).unwrap();
2714        let back: LimitsSpec = serde_json::from_str(&json).unwrap();
2715        assert_eq!(limits, back);
2716    }
2717
2718    #[test]
2719    fn empty_limits_serialises_to_empty_object() {
2720        let limits = LimitsSpec::default();
2721        assert!(limits.is_empty());
2722        let json = serde_json::to_string(&limits).unwrap();
2723        assert_eq!(json, "{}");
2724    }
2725
2726    // ── drift-detection: serde-derive-to-M2_LIMITS_KEY_* identity ────────
2727
2728    #[test]
2729    fn limits_spec_serde_keys_match_lifted_m2_limits_key_consts() {
2730        // Load-bearing invariant: the four `M2_LIMITS_KEY_*` consts
2731        // (`M2_LIMITS_KEY_MEMORY` / `M2_LIMITS_KEY_FUEL` /
2732        // `M2_LIMITS_KEY_WALL_CLOCK` / `M2_LIMITS_KEY_CPU`) name the
2733        // exact camelCase JSON keys the `#[serde(rename_all = "camelCase")]`
2734        // attribute on `LimitsSpec` emits, and every test-side probe
2735        // across the caixa-core / caixa-flux / caixa-helm renderer test
2736        // fixtures navigates into the rendered `:limits` overlay
2737        // sub-block by consulting one of these four `&'static str`s.
2738        // Serialize a fully-populated LimitsSpec and pin that each
2739        // canonical byte-sequence appears verbatim in the JSON — a
2740        // future accidental `rename_all = "snake_case"` /
2741        // `"kebab-case"` / verbatim-field-name flip at the derive
2742        // attribute (any of which would silently break every test-side
2743        // probe that reaches for one of the four consts) surfaces here
2744        // as a build-time test failure at `limits.rs`, not as an
2745        // apply-time `.get(<stale-canonical-const>)` returning `None`
2746        // far from the derive-attr drift's commit. Same discipline the
2747        // sibling M3 `PlacementStrategy::as_str` lift (0a2f653)
2748        // established on the peer per-`:placement :estrategia` axis:
2749        // one canonical byte-string per typed sub-key axis, pinned to
2750        // the load-bearing serde derivation at the type itself.
2751        let limits = LimitsSpec {
2752            memory: Some(64 * 1024 * 1024),
2753            fuel: Some(1_000_000),
2754            wall_clock: Some(Duration::from_secs(30)),
2755            cpu: Some(500),
2756        };
2757        let json = serde_json::to_string(&limits).unwrap();
2758        for key in [
2759            crate::render::M2_LIMITS_KEY_MEMORY,
2760            crate::render::M2_LIMITS_KEY_FUEL,
2761            crate::render::M2_LIMITS_KEY_WALL_CLOCK,
2762            crate::render::M2_LIMITS_KEY_CPU,
2763        ] {
2764            let quoted = format!("\"{key}\"");
2765            assert!(
2766                json.contains(&quoted),
2767                "serialized LimitsSpec must carry the lifted \
2768                 M2_LIMITS_KEY_* byte-sequence {quoted} verbatim in \
2769                 the JSON emission (got: {json})",
2770            );
2771        }
2772    }
2773
2774    #[test]
2775    fn m2_limits_key_consts_are_pairwise_distinct() {
2776        // Cross-axis drift-detection pin: a future collapse of two
2777        // canonical sub-key byte-strings onto the same value (e.g. an
2778        // accidental copy-paste flip of `M2_LIMITS_KEY_CPU` to also
2779        // read `"memory"`) would silently reroute every test-side
2780        // probe on one axis onto the sibling axis's overlay entry and
2781        // pass every propagation-probe test that expected only the
2782        // stale axis's value. Peer of the sibling three-way distinct
2783        // pin on the `FLUX_GITREPOSITORY_REF_KEY_*` trio (7d40380).
2784        let all = [
2785            crate::render::M2_LIMITS_KEY_MEMORY,
2786            crate::render::M2_LIMITS_KEY_FUEL,
2787            crate::render::M2_LIMITS_KEY_WALL_CLOCK,
2788            crate::render::M2_LIMITS_KEY_CPU,
2789        ];
2790        for (i, a) in all.iter().enumerate() {
2791            for b in all.iter().skip(i + 1) {
2792                assert_ne!(
2793                    a, b,
2794                    "M2_LIMITS_KEY_* consts must be pairwise-distinct \
2795                     canonical byte-sequences — got `{a}` == `{b}`",
2796                );
2797            }
2798        }
2799    }
2800
2801    #[test]
2802    fn m2_limits_key_consts_are_lower_camel_case_shape() {
2803        // Shape-pin: every `M2_LIMITS_KEY_*` const must be a
2804        // lowerCamelCase byte-sequence (no `snake_case` underscores,
2805        // no `kebab-case` hyphens, no `PascalCase` leading capital, no
2806        // whitespace / colons / dots) — the canonical shape the
2807        // `#[serde(rename_all = "camelCase")]` derive produces on
2808        // `LimitsSpec`. A future flip to a non-camelCase attribute at
2809        // the derive surfaces both here (this test fails on the
2810        // stale-constant shape) and at
2811        // `limits_spec_serde_keys_match_lifted_m2_limits_key_consts`
2812        // (that test fails on the mismatch between const and derive).
2813        for key in [
2814            crate::render::M2_LIMITS_KEY_MEMORY,
2815            crate::render::M2_LIMITS_KEY_FUEL,
2816            crate::render::M2_LIMITS_KEY_WALL_CLOCK,
2817            crate::render::M2_LIMITS_KEY_CPU,
2818        ] {
2819            assert!(
2820                !key.is_empty(),
2821                "M2_LIMITS_KEY_* must be non-empty (got {key:?})"
2822            );
2823            let first = key.chars().next().unwrap();
2824            assert!(
2825                first.is_ascii_lowercase(),
2826                "M2_LIMITS_KEY_* must lead with an ASCII-lowercase byte \
2827                 (got {key:?}, leads with {first:?})",
2828            );
2829            assert!(
2830                key.chars().all(|c| c.is_ascii_alphanumeric()),
2831                "M2_LIMITS_KEY_* must be ASCII-alphanumeric only \
2832                 — no `_` / `-` / `:` / `.` / whitespace (got {key:?})",
2833            );
2834        }
2835    }
2836
2837    // ── value-shape: zero on any declared axis is rejected ────────────────
2838
2839    #[test]
2840    fn validate_accepts_default_unbounded_limits() {
2841        // Every axis None → "no bound declared" is the omit-the-slot
2842        // shape and stays valid. This is the pre-M2 default behaviour.
2843        LimitsSpec::default().validate().unwrap();
2844    }
2845
2846    #[test]
2847    fn validate_accepts_full_nonzero_limits() {
2848        let l = LimitsSpec {
2849            memory: Some(64 * 1024 * 1024),
2850            fuel: Some(1_000_000),
2851            wall_clock: Some(Duration::from_secs(30)),
2852            cpu: Some(500),
2853        };
2854        l.validate().unwrap();
2855    }
2856
2857    #[test]
2858    fn validate_rejects_zero_memory() {
2859        let l = LimitsSpec {
2860            memory: Some(0),
2861            ..Default::default()
2862        };
2863        assert_eq!(l.validate().unwrap_err(), LimitsError::MemoryZero);
2864    }
2865
2866    #[test]
2867    fn validate_rejects_zero_fuel() {
2868        let l = LimitsSpec {
2869            fuel: Some(0),
2870            ..Default::default()
2871        };
2872        assert_eq!(l.validate().unwrap_err(), LimitsError::FuelZero);
2873    }
2874
2875    #[test]
2876    fn validate_rejects_zero_wall_clock() {
2877        let l = LimitsSpec {
2878            wall_clock: Some(Duration::ZERO),
2879            ..Default::default()
2880        };
2881        assert_eq!(l.validate().unwrap_err(), LimitsError::WallClockZero);
2882    }
2883
2884    #[test]
2885    fn validate_rejects_zero_cpu() {
2886        let l = LimitsSpec {
2887            cpu: Some(0),
2888            ..Default::default()
2889        };
2890        assert_eq!(l.validate().unwrap_err(), LimitsError::CpuZero);
2891    }
2892
2893    #[test]
2894    fn validate_rejects_first_zero_axis_deterministically() {
2895        // Memory is checked first; with multiple zero axes, the
2896        // diagnostic names :memory rather than reporting some other
2897        // axis non-deterministically.
2898        let l = LimitsSpec {
2899            memory: Some(0),
2900            fuel: Some(0),
2901            wall_clock: Some(Duration::ZERO),
2902            cpu: Some(0),
2903        };
2904        assert_eq!(l.validate().unwrap_err(), LimitsError::MemoryZero);
2905    }
2906
2907    // ── value-shape: :memory upper bound — wasm32-wasip2 4 GiB ceiling ────
2908
2909    #[test]
2910    fn wasm32_memory_cap_matches_parsed_4_gib() {
2911        // The cap constant tracks the canonical "4 GiB" byte-size
2912        // codec output structurally — drift between the codec's
2913        // accepted magnitude for `"4GiB"` and the validate gate's
2914        // accepted upper bound would surface here, not as a silent
2915        // round-trip break at the renderer layer. Same single-source-
2916        // of-truth shape the is_canonical_rate_limit_window predicate
2917        // gives the rate-limit window set.
2918        assert_eq!(
2919            parse_byte_size("4GiB").unwrap(),
2920            LIMITS_MEMORY_WASM32_MAX_BYTES
2921        );
2922        assert_eq!(LIMITS_MEMORY_WASM32_MAX_BYTES, 4 * 1024 * 1024 * 1024);
2923        assert_eq!(LIMITS_MEMORY_WASM32_MAX_BYTES, 1u64 << 32);
2924    }
2925
2926    #[test]
2927    fn validate_accepts_memory_at_wasm32_cap() {
2928        // 4 GiB exactly is the wasm32 in-spec maximum — `2^16 pages ×
2929        // 2^16 bytes/page`. The validate gate is inclusive on the
2930        // upper end (mirrors the inclusive lower-end rejection: zero
2931        // is *out*, one is *in*; 4 GiB+1 is *out*, 4 GiB is *in*).
2932        let l = LimitsSpec {
2933            memory: Some(LIMITS_MEMORY_WASM32_MAX_BYTES),
2934            ..Default::default()
2935        };
2936        l.validate().unwrap();
2937    }
2938
2939    #[test]
2940    fn validate_rejects_memory_one_byte_above_wasm32_cap() {
2941        // Boundary case: exactly 1 byte past the cap. Catches a
2942        // future "strictly less than" half-measure and pins the
2943        // diagnostic to name the offending byte count verbatim.
2944        let bytes = LIMITS_MEMORY_WASM32_MAX_BYTES + 1;
2945        let l = LimitsSpec {
2946            memory: Some(bytes),
2947            ..Default::default()
2948        };
2949        assert_eq!(
2950            l.validate().unwrap_err(),
2951            LimitsError::MemoryExceedsWasm32Cap { bytes }
2952        );
2953    }
2954
2955    #[test]
2956    fn validate_rejects_memory_8_gib() {
2957        // The "obvious authoring footgun" case: a value the byte-size
2958        // codec accepts cleanly (`"8GiB"` → 8 * 1024^3 bytes) and
2959        // serde round-trips silently, but no wasm32 component can
2960        // honor. Until this gate landed `validate` accepted it.
2961        let bytes = parse_byte_size("8GiB").unwrap();
2962        let l = LimitsSpec {
2963            memory: Some(bytes),
2964            ..Default::default()
2965        };
2966        assert_eq!(
2967            l.validate().unwrap_err(),
2968            LimitsError::MemoryExceedsWasm32Cap { bytes }
2969        );
2970    }
2971
2972    #[test]
2973    fn validate_memory_zero_takes_precedence_over_cap_check() {
2974        // Memory zero is structurally meaningless under *any* wasm
2975        // engine (zero-cap traps the first allocation); above-cap is
2976        // wasm32-specific. The zero arm fires first so the canonical
2977        // "omit the slot for unbounded" remediation in the existing
2978        // MemoryZero diagnostic still leads — pinning this precedence
2979        // guards against a future re-ordering that would surface the
2980        // wasm32-specific message in the case where the simpler
2981        // zero-floor message is more actionable.
2982        let l = LimitsSpec {
2983            memory: Some(0),
2984            ..Default::default()
2985        };
2986        assert_eq!(l.validate().unwrap_err(), LimitsError::MemoryZero);
2987    }
2988
2989    #[test]
2990    fn validate_rejects_memory_cap_before_other_axes() {
2991        // With both an above-cap :memory and a zero :fuel, the
2992        // diagnostic names :memory rather than :fuel — peer of the
2993        // existing `validate_rejects_first_zero_axis_deterministically`
2994        // ordering pin.
2995        let bytes = LIMITS_MEMORY_WASM32_MAX_BYTES + 1024;
2996        let l = LimitsSpec {
2997            memory: Some(bytes),
2998            fuel: Some(0),
2999            wall_clock: Some(Duration::ZERO),
3000            cpu: Some(0),
3001        };
3002        assert_eq!(
3003            l.validate().unwrap_err(),
3004            LimitsError::MemoryExceedsWasm32Cap { bytes }
3005        );
3006    }
3007
3008    #[test]
3009    fn above_cap_value_still_round_trips_through_serde() {
3010        // The byte-size codec accepts the above-cap value (the cap
3011        // lives in the validate gate, not the codec). This pins that
3012        // the structural property is "above-cap is rejected by
3013        // validate" — not "above-cap is unparseable by the codec";
3014        // the latter would prevent the diagnostic from naming the
3015        // offending byte count at all, since deserialize would fail
3016        // first.
3017        let l = LimitsSpec {
3018            memory: Some(LIMITS_MEMORY_WASM32_MAX_BYTES + 1),
3019            ..Default::default()
3020        };
3021        let json = serde_json::to_string(&l).unwrap();
3022        let back: LimitsSpec = serde_json::from_str(&json).unwrap();
3023        assert_eq!(l, back);
3024        assert!(back.validate().is_err());
3025    }
3026
3027    // ── value-shape: :memory lower bound — wasm32-wasip2 64 KiB page floor ─
3028
3029    #[test]
3030    fn wasm32_memory_page_matches_parsed_64_kib() {
3031        // The page-floor constant tracks the canonical "64 KiB"
3032        // byte-size codec output structurally — drift between the
3033        // codec's accepted magnitude for `"64KiB"` and the validate
3034        // gate's accepted lower bound would surface here, not as a
3035        // silent round-trip break at the renderer layer. Same single-
3036        // source-of-truth shape `wasm32_memory_cap_matches_parsed_4_gib`
3037        // pins on the peer upper-cap bound and
3038        // `is_canonical_rate_limit_window` gives the rate-limit window
3039        // set. The page-size identities (2^16, integer-divides the
3040        // upper cap exactly 2^16 times) are pinned alongside so a
3041        // future memory64-target opt-in raising one bound surfaces
3042        // here if the other bound's relationship to it drifts.
3043        assert_eq!(
3044            parse_byte_size("64KiB").unwrap(),
3045            LIMITS_MEMORY_WASM32_PAGE_BYTES
3046        );
3047        assert_eq!(LIMITS_MEMORY_WASM32_PAGE_BYTES, 64 * 1024);
3048        assert_eq!(LIMITS_MEMORY_WASM32_PAGE_BYTES, 1u64 << 16);
3049        assert_eq!(
3050            LIMITS_MEMORY_WASM32_MAX_BYTES / LIMITS_MEMORY_WASM32_PAGE_BYTES,
3051            1u64 << 16,
3052            "the wasm32 page count cap is 2^16 pages exactly",
3053        );
3054        assert_eq!(
3055            LIMITS_MEMORY_WASM32_MAX_BYTES % LIMITS_MEMORY_WASM32_PAGE_BYTES,
3056            0
3057        );
3058    }
3059
3060    #[test]
3061    fn validate_rejects_memory_below_wasm32_page() {
3062        // The fail-before-pass-after pin: until this gate landed a
3063        // `(:memory "32KiB")` (or any programmatic struct literal with
3064        // a sub-page byte count — `LimitsSpec { memory: Some(50000),
3065        // .. }`) silently passed validate, the byte-size codec
3066        // round-tripped cleanly through serde, and the wasm-engine
3067        // either refused instantiation (`memory minimum size of 1
3068        // pages exceeds memory limits` on any cdylib-shaped component
3069        // declaring `(memory 1)`) or trapped the first `memory.grow(1)`
3070        // far from the source caixa.lisp.
3071        let bytes = parse_byte_size("32KiB").unwrap();
3072        let l = LimitsSpec {
3073            memory: Some(bytes),
3074            ..Default::default()
3075        };
3076        assert_eq!(
3077            l.validate().unwrap_err(),
3078            LimitsError::MemoryBelowWasm32Page { bytes }
3079        );
3080    }
3081
3082    #[test]
3083    fn validate_rejects_memory_one_byte_below_page() {
3084        // Boundary case: exactly 1 byte below the page-size floor
3085        // (`LIMITS_MEMORY_WASM32_PAGE_BYTES - 1` = 65535 bytes). Pins
3086        // the inclusive-upper-end / strict-lower-end relationship on
3087        // the page-floor arm: 65535 is *out*, 65536 is *in*. Catches a
3088        // future "strictly greater than" half-measure and matches the
3089        // peer `validate_rejects_memory_one_byte_above_wasm32_cap`
3090        // shape on the top edge.
3091        let bytes = LIMITS_MEMORY_WASM32_PAGE_BYTES - 1;
3092        let l = LimitsSpec {
3093            memory: Some(bytes),
3094            ..Default::default()
3095        };
3096        assert_eq!(
3097            l.validate().unwrap_err(),
3098            LimitsError::MemoryBelowWasm32Page { bytes }
3099        );
3100    }
3101
3102    #[test]
3103    fn validate_rejects_memory_one_byte() {
3104        // The far-floor case: a `(:memory "1")` cap is non-zero (so
3105        // `MemoryZero` doesn't fire) but structurally cannot hold any
3106        // wasm linear memory page. The page-floor gate at this layer
3107        // surfaces a self-locating diagnostic naming the offending
3108        // byte count verbatim rather than a downstream wasm-engine
3109        // instantiation failure whose error message points at the
3110        // engine's internals, not the caixa.lisp `:memory` slot.
3111        let l = LimitsSpec {
3112            memory: Some(1),
3113            ..Default::default()
3114        };
3115        assert_eq!(
3116            l.validate().unwrap_err(),
3117            LimitsError::MemoryBelowWasm32Page { bytes: 1 }
3118        );
3119    }
3120
3121    #[test]
3122    fn validate_accepts_memory_at_wasm32_page() {
3123        // 64 KiB exactly is the wasm32 linear-memory page size — the
3124        // smallest cap that admits one wasm `(memory 1)` page. The
3125        // page-floor gate is inclusive on the lower end (mirrors the
3126        // inclusive upper-end acceptance: 4 GiB is *in*, 4 GiB+1 is
3127        // *out*; 64 KiB is *in*, 64 KiB-1 is *out*).
3128        let l = LimitsSpec {
3129            memory: Some(LIMITS_MEMORY_WASM32_PAGE_BYTES),
3130            ..Default::default()
3131        };
3132        l.validate().unwrap();
3133    }
3134
3135    #[test]
3136    fn validate_accepts_multi_page_memory() {
3137        // The positive-control sweep: every typed `:memory` cap that
3138        // admits at least one wasm linear memory page (i.e. ≥
3139        // `LIMITS_MEMORY_WASM32_PAGE_BYTES`) passes `validate`. Sweeps
3140        // single-page, two-page, the canonical 64 MiB / 1 GiB / 4 GiB
3141        // upper-bound boundary so a future tightening of either edge
3142        // surfaces here. Peer of
3143        // `validate_accepts_integer_millisecond_wall_clock_values` on
3144        // the sibling `:wall-clock` axis.
3145        for bytes in [
3146            LIMITS_MEMORY_WASM32_PAGE_BYTES,
3147            2 * LIMITS_MEMORY_WASM32_PAGE_BYTES,
3148            64 * 1024 * 1024,
3149            1024 * 1024 * 1024,
3150            LIMITS_MEMORY_WASM32_MAX_BYTES,
3151        ] {
3152            let l = LimitsSpec {
3153                memory: Some(bytes),
3154                ..Default::default()
3155            };
3156            l.validate()
3157                .unwrap_or_else(|e| panic!("multi-page {bytes} must validate, got {e:?}"));
3158        }
3159    }
3160
3161    #[test]
3162    fn validate_memory_zero_takes_precedence_over_page_floor() {
3163        // Cross-arm ordering pin: `Some(0)` would otherwise pass the
3164        // page-floor arm's `m < PAGE_BYTES` check (0 < 65536), but the
3165        // zero-floor arm strictly precedes the page-floor arm so the
3166        // more self-locating `MemoryZero` diagnostic (with its omit-
3167        // axis remediation directly named, applicable under *any* wasm
3168        // engine not just wasm32) leads. Same posture every peer
3169        // zero-then-shape gate uses on this surface
3170        // (`PolicyTimeoutZero` → `PolicyTimeoutNotCanonical`,
3171        // `PolicyBreakerZeroWindow` → `PolicyBreakerWindowNotCanonical`,
3172        // `WallClockZero` → `WallClockNotCanonical`).
3173        let l = LimitsSpec {
3174            memory: Some(0),
3175            ..Default::default()
3176        };
3177        assert_eq!(l.validate().unwrap_err(), LimitsError::MemoryZero);
3178    }
3179
3180    #[test]
3181    fn validate_memory_page_floor_takes_precedence_over_other_axes() {
3182        // With a sub-page `:memory` and zero values on every other
3183        // axis, the diagnostic names `:memory` rather than `:fuel` /
3184        // `:wall-clock` / `:cpu` — peer of the existing
3185        // `validate_rejects_first_zero_axis_deterministically` and
3186        // `validate_rejects_memory_cap_before_other_axes` ordering
3187        // pins. Memory is the first axis the validate cascade checks,
3188        // so a sub-page value surfaces before any other-axis
3189        // diagnostic regardless of how many other axes are
3190        // simultaneously invalid.
3191        let bytes = LIMITS_MEMORY_WASM32_PAGE_BYTES / 2;
3192        let l = LimitsSpec {
3193            memory: Some(bytes),
3194            fuel: Some(0),
3195            wall_clock: Some(Duration::ZERO),
3196            cpu: Some(0),
3197        };
3198        assert_eq!(
3199            l.validate().unwrap_err(),
3200            LimitsError::MemoryBelowWasm32Page { bytes }
3201        );
3202    }
3203
3204    #[test]
3205    fn memory_page_floor_diagnostic_carries_offending_bytes() {
3206        // Diagnostic-shape pin: the page-floor arm names the
3207        // offending byte count verbatim so the author's grep lands on
3208        // the field's value, not a generic "memory too small" message.
3209        // Same shape every other typed-cap arm on this surface
3210        // carries (`MemoryExceedsWasm32Cap` carries the offending byte
3211        // count verbatim, `WallClockNotCanonical` carries the
3212        // offending `Duration` verbatim, `PolicyRetriesExceedsCap`
3213        // carries the offending retry count verbatim).
3214        let l = LimitsSpec {
3215            memory: Some(50_000),
3216            ..Default::default()
3217        };
3218        let err = l.validate().unwrap_err();
3219        let msg = err.to_string();
3220        assert!(
3221            msg.contains("50000"),
3222            "diagnostic must carry the offending byte count verbatim (got {msg:?})"
3223        );
3224        assert!(
3225            msg.contains("64 KiB") || msg.contains("65536"),
3226            "diagnostic must name the page-size floor (got {msg:?})"
3227        );
3228    }
3229
3230    #[test]
3231    fn below_page_value_still_round_trips_through_serde() {
3232        // The byte-size codec accepts the sub-page value (the floor
3233        // lives in the validate gate, not the codec) — peer of
3234        // `above_cap_value_still_round_trips_through_serde` on the top
3235        // edge. Pins that the structural property is "sub-page is
3236        // rejected by validate" — not "sub-page is unparseable by the
3237        // codec"; the latter would prevent the diagnostic from naming
3238        // the offending byte count at all, since deserialize would
3239        // fail first.
3240        let l = LimitsSpec {
3241            memory: Some(LIMITS_MEMORY_WASM32_PAGE_BYTES - 1),
3242            ..Default::default()
3243        };
3244        let json = serde_json::to_string(&l).unwrap();
3245        let back: LimitsSpec = serde_json::from_str(&json).unwrap();
3246        assert_eq!(l, back);
3247        assert!(back.validate().is_err());
3248    }
3249
3250    // ── value-shape: :memory page-multiple granularity gate ───────────────
3251
3252    #[test]
3253    fn validate_rejects_memory_one_byte_above_page() {
3254        // The fail-before-pass-after pin: until this gate landed a
3255        // `LimitsSpec { memory: Some(LIMITS_MEMORY_WASM32_PAGE_BYTES +
3256        // 1), .. }` (65537 bytes — one wasm32 page plus a 1-byte
3257        // unreachable residue) silently passed validate, the byte-size
3258        // codec round-tripped cleanly through serde (`render_byte_size`
3259        // falls through to `"65537"` on any non-power-of-1024 magnitude),
3260        // and wasmtime's `StoreLimits::memory_size` consumed the value
3261        // verbatim as a page-quantized ceiling — the engine grew at
3262        // most floor(65537 / 65536) = 1 page, and the byte at offset
3263        // 65536 became structural dead space the runtime cannot honor.
3264        let bytes = LIMITS_MEMORY_WASM32_PAGE_BYTES + 1;
3265        let l = LimitsSpec {
3266            memory: Some(bytes),
3267            ..Default::default()
3268        };
3269        assert_eq!(
3270            l.validate().unwrap_err(),
3271            LimitsError::MemoryNotPageMultiple { bytes }
3272        );
3273    }
3274
3275    #[test]
3276    fn validate_rejects_memory_just_below_two_pages() {
3277        // Boundary case: exactly 1 byte below two pages (`2 *
3278        // LIMITS_MEMORY_WASM32_PAGE_BYTES - 1` = 131071 bytes). Pins
3279        // the inclusive-page-boundary / strict-sub-page-residue
3280        // relationship on the page-multiple arm: 131071 is *out*
3281        // (sub-page residue), 131072 is *in* (exactly two pages).
3282        // Matches the peer `validate_rejects_memory_one_byte_below_page`
3283        // / `validate_rejects_memory_one_byte_above_wasm32_cap` shape
3284        // on the surrounding edges.
3285        let bytes = 2 * LIMITS_MEMORY_WASM32_PAGE_BYTES - 1;
3286        let l = LimitsSpec {
3287            memory: Some(bytes),
3288            ..Default::default()
3289        };
3290        assert_eq!(
3291            l.validate().unwrap_err(),
3292            LimitsError::MemoryNotPageMultiple { bytes }
3293        );
3294    }
3295
3296    #[test]
3297    fn validate_rejects_memory_100000_bytes() {
3298        // The "obvious authoring footgun" case: a magnitude the
3299        // byte-size codec accepts cleanly (`"100000"` → 100000 bytes
3300        // ≈ 97.65 KiB) and serde round-trips silently, but no wasm32
3301        // engine can honor as a meaningful ceiling — the engine grows
3302        // at most floor(100000 / 65536) = 1 page, and the 34464 bytes
3303        // between offsets 65536 and 100000 are structural dead space.
3304        // Until this gate landed `validate` accepted it. Peer of
3305        // `validate_rejects_memory_8_gib` on the cap arm.
3306        let bytes = parse_byte_size("100000").unwrap();
3307        let l = LimitsSpec {
3308            memory: Some(bytes),
3309            ..Default::default()
3310        };
3311        assert_eq!(
3312            l.validate().unwrap_err(),
3313            LimitsError::MemoryNotPageMultiple { bytes }
3314        );
3315    }
3316
3317    #[test]
3318    fn validate_accepts_every_page_aligned_value_through_serde() {
3319        // Positive-control sweep through the byte-size codec: every
3320        // canonical magnitude `render_byte_size` emits at or above
3321        // the page floor divides cleanly by the page size, so the
3322        // page-multiple gate accepts the entire canonical-output
3323        // domain at and above the page floor. The sweep walks
3324        // single-page (`"64KiB"`), two-page (`"128KiB"`), every
3325        // power-of-1024 unit (`"1MiB"`, `"64MiB"`, `"1GiB"`, `"4GiB"`),
3326        // and the cap (`"4GiB"`) — pinning that the codec's
3327        // emitted-canonical-form set is a structural subset of the
3328        // validate gate's accepted set. Drift between the codec's
3329        // emit alphabet and the validate gate would surface here
3330        // rather than at a future serializer round trip.
3331        for s in ["64KiB", "128KiB", "1MiB", "64MiB", "1GiB", "4GiB"] {
3332            let bytes = parse_byte_size(s).unwrap();
3333            assert_eq!(
3334                bytes % LIMITS_MEMORY_WASM32_PAGE_BYTES,
3335                0,
3336                "canonical byte-size codec output {s:?} ({bytes}) must be page-aligned",
3337            );
3338            let l = LimitsSpec {
3339                memory: Some(bytes),
3340                ..Default::default()
3341            };
3342            l.validate()
3343                .unwrap_or_else(|e| panic!("canonical {s:?} = {bytes} must validate, got {e:?}"));
3344        }
3345    }
3346
3347    #[test]
3348    fn validate_memory_below_page_takes_precedence_over_page_multiple() {
3349        // Cross-arm ordering pin: `Some(1)` would otherwise pass the
3350        // page-multiple arm's `m % PAGE_BYTES != 0` check (1 % 65536
3351        // == 1 ≠ 0), but the page-floor arm strictly precedes the
3352        // page-multiple arm so the more self-locating
3353        // `MemoryBelowWasm32Page` diagnostic (with its "single page
3354        // cannot fit" remediation, applicable to every sub-page
3355        // value uniformly) leads. Peer of `MemoryZero` →
3356        // `MemoryBelowWasm32Page` precedence on the zero edge:
3357        // every value `m` in the range `1..=PAGE_BYTES-1` satisfies
3358        // both `m < PAGE_BYTES` and `m % PAGE_BYTES != 0`, but the
3359        // structurally-narrower diagnostic (page-floor) leads.
3360        let l = LimitsSpec {
3361            memory: Some(1),
3362            ..Default::default()
3363        };
3364        assert_eq!(
3365            l.validate().unwrap_err(),
3366            LimitsError::MemoryBelowWasm32Page { bytes: 1 }
3367        );
3368    }
3369
3370    #[test]
3371    fn validate_memory_cap_takes_precedence_over_page_multiple() {
3372        // Cross-arm ordering pin: `LIMITS_MEMORY_WASM32_MAX_BYTES + 1`
3373        // (4 GiB + 1 byte) is *both* above-cap and not page-aligned.
3374        // The cap arm strictly precedes the page-multiple arm so the
3375        // more aggressive cap-shape diagnostic leads (the page-multiple
3376        // remediation would be misleading when the offending value
3377        // exceeds the wasm32 address-space ceiling anyway — the
3378        // canonical fix collapses both into "pin a page-aligned value
3379        // ≤ 4 GiB"). Peer of `WallClockNotCanonical` →
3380        // `WallClockExceedsCap` ordering on the sibling `:wall-clock`
3381        // axis (with the inverse polarity — there the granularity
3382        // gate leads because sub-millisecond residue breaks serde
3383        // round-trip; here the cap leads because both gates' offending
3384        // values round-trip cleanly through serde and the broader
3385        // magnitude constraint is the more aggressive one).
3386        let bytes = LIMITS_MEMORY_WASM32_MAX_BYTES + 1;
3387        let l = LimitsSpec {
3388            memory: Some(bytes),
3389            ..Default::default()
3390        };
3391        assert_eq!(
3392            l.validate().unwrap_err(),
3393            LimitsError::MemoryExceedsWasm32Cap { bytes }
3394        );
3395    }
3396
3397    #[test]
3398    fn validate_rejects_memory_page_multiple_before_other_axes() {
3399        // With a sub-page-residue `:memory` and zero values on every
3400        // other axis, the diagnostic names `:memory` rather than
3401        // `:fuel` / `:wall-clock` / `:cpu` — peer of the existing
3402        // `validate_memory_page_floor_takes_precedence_over_other_axes`
3403        // and `validate_rejects_memory_cap_before_other_axes` ordering
3404        // pins. Memory is the first axis the validate cascade checks,
3405        // so a sub-page-residue value surfaces before any other-axis
3406        // diagnostic regardless of how many other axes are
3407        // simultaneously invalid.
3408        let bytes = LIMITS_MEMORY_WASM32_PAGE_BYTES + 1;
3409        let l = LimitsSpec {
3410            memory: Some(bytes),
3411            fuel: Some(0),
3412            wall_clock: Some(Duration::ZERO),
3413            cpu: Some(0),
3414        };
3415        assert_eq!(
3416            l.validate().unwrap_err(),
3417            LimitsError::MemoryNotPageMultiple { bytes }
3418        );
3419    }
3420
3421    #[test]
3422    fn memory_page_multiple_diagnostic_carries_offending_bytes() {
3423        // Diagnostic-shape pin: the page-multiple arm names the
3424        // offending byte count verbatim so the author's grep lands on
3425        // the field's value, not a generic "memory not aligned"
3426        // message. Same shape every other typed-cap arm on this
3427        // surface carries (`MemoryExceedsWasm32Cap` carries the
3428        // offending byte count verbatim, `WallClockNotCanonical`
3429        // carries the offending `Duration` verbatim).
3430        let bytes = LIMITS_MEMORY_WASM32_PAGE_BYTES + 12345;
3431        let l = LimitsSpec {
3432            memory: Some(bytes),
3433            ..Default::default()
3434        };
3435        let err = l.validate().unwrap_err();
3436        let msg = err.to_string();
3437        assert!(
3438            msg.contains(&bytes.to_string()),
3439            "diagnostic must carry the offending byte count verbatim (got {msg:?})"
3440        );
3441        assert!(
3442            msg.contains("64 KiB") || msg.contains("65536") || msg.contains("page"),
3443            "diagnostic must name the page-size granularity (got {msg:?})"
3444        );
3445    }
3446
3447    #[test]
3448    fn sub_page_residue_value_still_round_trips_through_serde() {
3449        // The byte-size codec accepts the sub-page-residue value (the
3450        // page-multiple gate lives in validate, not in the codec) —
3451        // peer of `above_cap_value_still_round_trips_through_serde`
3452        // and `below_page_value_still_round_trips_through_serde`.
3453        // Pins that the structural property is "sub-page-residue is
3454        // rejected by validate" — not "sub-page-residue is
3455        // unparseable by the codec"; the latter would prevent the
3456        // diagnostic from naming the offending byte count at all,
3457        // since deserialize would fail first. The render-then-parse
3458        // round trip also pins the codec's flow-through-to-bytes
3459        // shape on non-power-of-1024 magnitudes: `render_byte_size`
3460        // falls through every `(mult, label)` arm whose `n % mult !=
3461        // 0` and emits the bare byte count.
3462        let bytes = LIMITS_MEMORY_WASM32_PAGE_BYTES + 1;
3463        let l = LimitsSpec {
3464            memory: Some(bytes),
3465            ..Default::default()
3466        };
3467        let json = serde_json::to_string(&l).unwrap();
3468        let back: LimitsSpec = serde_json::from_str(&json).unwrap();
3469        assert_eq!(l, back);
3470        assert!(back.validate().is_err());
3471    }
3472
3473    #[test]
3474    fn validate_memory_axis_routes_through_quantum_multiple_bounded_helper() {
3475        // Byte-parity pin on the pre-lift `if self.memory() == Some(0)
3476        // { … } if let Some(m) = self.memory() { if m <
3477        // LIMITS_MEMORY_WASM32_PAGE_BYTES { … } } if let Some(m) =
3478        // self.memory() { if m > LIMITS_MEMORY_WASM32_MAX_BYTES { … } }
3479        // if let Some(m) = self.memory() && m %
3480        // LIMITS_MEMORY_WASM32_PAGE_BYTES != 0 { … }` four-sequential-
3481        // `if let` shape the `LimitsSpec::validate` `:memory` axis
3482        // routed through today via
3483        // `crate::render::require_positive_quantum_multiple_bounded_u64`.
3484        // Refuses a future accidental split between the helper's
3485        // four-arm ordering (zero → below-quantum → cap → not-multiple)
3486        // and the four typed `LimitsError::Memory*` variants each arm
3487        // threads its offending byte count into — a swap of any two
3488        // arms in the helper, or a partial widening (e.g. removing the
3489        // page-multiple arm), or a widening of the `on_below_quantum`
3490        // arm's closure to the `MemoryExceedsWasm32Cap` variant instead
3491        // of `MemoryBelowWasm32Page` — would break exactly one row of
3492        // this pin, matching the pre-lift shape the four consumer sites
3493        // route through today. Same shape as
3494        // `as_seq_body_partitions_the_same_arm_set_as_seq_delims` in
3495        // caixa-ast and the peer `require_positive_bounded_u64` tests
3496        // in the sibling render.rs test module.
3497        //
3498        // (Some(bytes) → expected LimitsError)
3499        let quantum = LIMITS_MEMORY_WASM32_PAGE_BYTES;
3500        let cap = LIMITS_MEMORY_WASM32_MAX_BYTES;
3501        let cases: &[(u64, LimitsError)] = &[
3502            (0, LimitsError::MemoryZero),
3503            (1, LimitsError::MemoryBelowWasm32Page { bytes: 1 }),
3504            (
3505                quantum - 1,
3506                LimitsError::MemoryBelowWasm32Page { bytes: quantum - 1 },
3507            ),
3508            (
3509                cap + 1,
3510                LimitsError::MemoryExceedsWasm32Cap { bytes: cap + 1 },
3511            ),
3512            (
3513                cap + quantum,
3514                LimitsError::MemoryExceedsWasm32Cap {
3515                    bytes: cap + quantum,
3516                },
3517            ),
3518            (
3519                quantum + 1,
3520                LimitsError::MemoryNotPageMultiple { bytes: quantum + 1 },
3521            ),
3522            (
3523                quantum + 12_345,
3524                LimitsError::MemoryNotPageMultiple {
3525                    bytes: quantum + 12_345,
3526                },
3527            ),
3528        ];
3529        for (bytes, expected) in cases {
3530            let l = LimitsSpec {
3531                memory: Some(*bytes),
3532                ..Default::default()
3533            };
3534            assert_eq!(
3535                l.validate().unwrap_err(),
3536                *expected,
3537                "memory={bytes} must surface the {expected:?} arm via the substrate helper",
3538            );
3539        }
3540        // Positive-control: every quantum-multiple in `quantum..=cap`
3541        // passes, closing the four-arm cascade with an `Ok(())` shape.
3542        for bytes in [quantum, quantum * 2, quantum * 100, cap] {
3543            let l = LimitsSpec {
3544                memory: Some(bytes),
3545                ..Default::default()
3546            };
3547            l.validate().unwrap();
3548        }
3549    }
3550
3551    // ── canonical-form: integer-magnitude byte-size codec gate ────────────
3552    //
3553    // Every magnitude `render_byte_size` emits is a non-negative integer
3554    // (no decimal point, no leading sign, no scientific notation). The
3555    // parser's accepted set must match for parse → render → parse to
3556    // round-trip without canonical-form drift. The tests below pin every
3557    // canonical-drift shape — fractional (`"1.5KiB"`), decimal-shaped-
3558    // integer (`"1.0MiB"`), half-unit (`"0.5GiB"`), leading-`+`
3559    // (`"+1024"`) — plus the scientific-notation dispatch path (caught
3560    // by `UnknownByteUnit` on a different arm), the two complement-side
3561    // pins (the integer happy paths the gate must continue to accept),
3562    // the round-trip convergence property (parse → render → parse must
3563    // converge on a single canonical form for every accepted input),
3564    // the BadByteMagnitude-precedence pin (genuinely unparseable inputs
3565    // keep their narrower diagnostic), the overflow-surface pin
3566    // (u64-overflow on magnitude × unit surfaces at parse time), and
3567    // the serde-path pin (the gate fires at deserialize, before any
3568    // validate gate runs).
3569
3570    #[test]
3571    fn parse_byte_size_rejects_fractional_kib() {
3572        // The fail-before-pass-after pin: `"1.5KiB"` parsed cleanly on
3573        // every pre-gate codebase (f64::parse accepts the decimal), the
3574        // codec produced 1536 bytes, and `render_byte_size(1536)`
3575        // emitted `"1536"` on the next serialize — silently drifting
3576        // the canonical form away from the author's intent. The new
3577        // gate surfaces the round-trip break at the parser layer with
3578        // a self-locating diagnostic (the offending magnitude verbatim,
3579        // the canonical-form remediation in the wording).
3580        let err = parse_byte_size("1.5KiB").unwrap_err();
3581        assert!(
3582            matches!(err, LimitsError::NonIntegerByteMagnitude { ref value } if value == "1.5"),
3583            "got {err:?}"
3584        );
3585    }
3586
3587    #[test]
3588    fn parse_byte_size_rejects_decimal_shaped_integer() {
3589        // The canonical-drift case where the *value* is integer but
3590        // the *form* carries a redundant decimal point — `"1.0MiB"`
3591        // parses to 1 MiB (integer), but the renderer emits `"1MiB"`
3592        // on the next serialize (no decimal point). The parse-shape
3593        // gate fires here too so the codec's accepted set is exactly
3594        // the renderer's emitted set — no `"1.0MiB"` ↔ `"1MiB"` drift
3595        // surviving a round-trip silently.
3596        let err = parse_byte_size("1.0MiB").unwrap_err();
3597        assert!(
3598            matches!(err, LimitsError::NonIntegerByteMagnitude { ref value } if value == "1.0"),
3599            "got {err:?}"
3600        );
3601    }
3602
3603    #[test]
3604    fn parse_byte_size_rejects_half_gib() {
3605        // `"0.5GiB"` parses to 536870912 bytes = 512MiB; the renderer
3606        // emits `"512MiB"` on the next serialize. Pin the round-trip
3607        // drift on the explicitly-fractional case sized to land on a
3608        // unit boundary, so the gate's coverage includes both the
3609        // "doesn't land on a boundary" (1.5KiB → 1536) and "lands on
3610        // a smaller-unit boundary" (0.5GiB → 512MiB) drift shapes.
3611        let err = parse_byte_size("0.5GiB").unwrap_err();
3612        assert!(
3613            matches!(err, LimitsError::NonIntegerByteMagnitude { ref value } if value == "0.5"),
3614            "got {err:?}"
3615        );
3616    }
3617
3618    #[test]
3619    fn parse_byte_size_rejects_scientific_notation_via_unit_arm() {
3620        // Scientific-notation magnitudes are canonical-form drift too
3621        // — the renderer never emits `"1e3KiB"` for any value. But
3622        // they're caught on a *different* arm than the fractional /
3623        // leading-`+` shapes: the parser's split-on-first-alphabetic-
3624        // byte heuristic reads the `e` as a unit prefix, so the input
3625        // falls into the existing `UnknownByteUnit { unit: "e3KiB" }`
3626        // diagnostic before the `NonIntegerByteMagnitude` gate is
3627        // consulted. Pin this dispatch path so a future relaxation of
3628        // the split heuristic (e.g. recognizing `e` as part of a
3629        // scientific-notation magnitude) surfaces here as a test
3630        // failure — at which point the `NonIntegerByteMagnitude` gate
3631        // would correctly take over, and this test would flip to that
3632        // arm with no other change required.
3633        let err = parse_byte_size("1e3KiB").unwrap_err();
3634        assert!(
3635            matches!(err, LimitsError::UnknownByteUnit { ref unit } if unit == "e3KiB"),
3636            "got {err:?}"
3637        );
3638    }
3639
3640    #[test]
3641    fn parse_byte_size_rejects_leading_plus() {
3642        // `"+1024"` parses through f64 as 1024 bytes; the renderer
3643        // emits `"1KiB"` on the next serialize. The leading `+` is
3644        // not a renderer-emitted shape, so it falls in the same
3645        // canonical-drift class as the fractional / scientific forms
3646        // — surfacing under the same diagnostic keeps the gate's
3647        // coverage uniform across every non-canonical-but-numeric
3648        // input shape the parser would otherwise accept.
3649        let err = parse_byte_size("+1024").unwrap_err();
3650        assert!(
3651            matches!(err, LimitsError::NonIntegerByteMagnitude { ref value } if value == "+1024"),
3652            "got {err:?}"
3653        );
3654    }
3655
3656    #[test]
3657    fn parse_byte_size_continues_to_accept_integer_magnitudes() {
3658        // The complement-side pin: every canonical integer-magnitude
3659        // form the renderer emits must continue to parse to the same
3660        // value the renderer produced. Sweep the five canonical
3661        // authoring shapes (unitless integer, KiB, MiB, GiB, KB) so a
3662        // future tightening of the parser surfaces here as a test
3663        // failure rather than a silent regression in the canonical
3664        // authoring set.
3665        assert_eq!(parse_byte_size("1024").unwrap(), 1024);
3666        assert_eq!(parse_byte_size("1KiB").unwrap(), 1024);
3667        assert_eq!(parse_byte_size("64MiB").unwrap(), 64 * 1024 * 1024);
3668        assert_eq!(parse_byte_size("1GiB").unwrap(), 1024 * 1024 * 1024);
3669        assert_eq!(parse_byte_size("1000KB").unwrap(), 1_000_000);
3670    }
3671
3672    #[test]
3673    fn parse_byte_size_round_trips_through_render_for_every_canonical_form() {
3674        // The structural property the gate makes load-bearing: every
3675        // value the parser accepts round-trips through `render_byte_size`
3676        // to a string the parser also accepts — and to the *same* value.
3677        // Sweep the values the renderer emits canonically (1024 / 1MiB
3678        // / 1GiB / 1536 / 64MiB) so a future codec change that breaks
3679        // round-trip convergence surfaces here, not at a downstream
3680        // renderer that double-emits a typed slot.
3681        for n in [1u64, 1023, 1024, 1536, 64 * 1024 * 1024, 1024 * 1024 * 1024] {
3682            let rendered = render_byte_size(n);
3683            let reparsed = parse_byte_size(&rendered)
3684                .unwrap_or_else(|e| panic!("render({n}) = {rendered:?} must reparse, got {e:?}"));
3685            assert_eq!(
3686                reparsed, n,
3687                "round-trip drift on {n}: rendered={rendered:?}, reparsed={reparsed}",
3688            );
3689        }
3690    }
3691
3692    #[test]
3693    fn parse_byte_size_keeps_bad_magnitude_for_unparseable_input() {
3694        // The precedence pin: the new `NonIntegerByteMagnitude` arm
3695        // distinguishes *non-canonical-but-numeric* (`"1.5"`, `"1.0"`,
3696        // `"+1024"`, `"-1"`) from *genuinely-unparseable* (`"abc"`,
3697        // `"--1"`) so the existing `BadByteMagnitude` diagnostic's
3698        // wording remains load-bearing for the latter class — the gate
3699        // is additive, not replacing. Pin both arms so a future
3700        // relaxation that collapses them surfaces here.
3701        let err = parse_byte_size("abc").unwrap_err();
3702        assert!(
3703            matches!(err, LimitsError::BadByteMagnitude(_)),
3704            "got {err:?}"
3705        );
3706        let err = parse_byte_size("--1").unwrap_err();
3707        assert!(
3708            matches!(err, LimitsError::BadByteMagnitude(_)),
3709            "got {err:?}"
3710        );
3711    }
3712
3713    #[test]
3714    fn parse_byte_size_overflow_surfaces_as_bad_magnitude() {
3715        // `u64::MAX KiB` overflows the u64 result; the parser surfaces
3716        // the overflow as a `BadByteMagnitude` (not as a saturated
3717        // `u64::MAX` value that the wasm32-cap validate gate then
3718        // catches), so the diagnostic names the offending magnitude ×
3719        // unit pair at parse time rather than as
3720        // `MemoryExceedsWasm32Cap { bytes: u64::MAX }` far from the
3721        // author's intent. (`u64::MAX` itself parses cleanly with no
3722        // unit since `u64::MAX × 1 = u64::MAX` fits.)
3723        let err = parse_byte_size("18446744073709551615KiB").unwrap_err();
3724        let LimitsError::BadByteMagnitude(reason) = err else {
3725            panic!("expected BadByteMagnitude(overflow), got other variant");
3726        };
3727        assert!(
3728            reason.contains("overflow"),
3729            "overflow diagnostic must mention overflow (got {reason:?})"
3730        );
3731    }
3732
3733    // ── canonical-form: leading-zero byte-size codec gate ─────────────────
3734    //
3735    // Direct successor to the `parse_duration` leading-zero arm (39762d7),
3736    // the `supervisor::duration_codec` leading-zero arm (9178904), and the
3737    // `rate_limit_codec` leading-zero arm (4f46830) — the same canonical-
3738    // form render-determinism axis applied to the last typed-numeric codec
3739    // that still admitted leading-zero magnitudes. The digit-only gate
3740    // immediately above accepts every `u64::from_str`-parseable magnitude
3741    // including leading-zero padding, but `render_byte_size` always emits
3742    // the stripped form (`64MiB`, never `064MiB`) — silently drifting the
3743    // canonical string across a parse/render round-trip. Pins each
3744    // canonical leading-zero shape across the unit-set the codec admits
3745    // (KB / MB / GB / KiB / MiB / GiB / bare-integer), the all-zero
3746    // degenerate case, the codec-vs-validate-layer partition (single-byte
3747    // `"0"` stays accepted at the codec because the typed-validate gate
3748    // `MemoryZero` refuses semantic-zero authoring), the complement-side
3749    // pin (`1`..=`9`-led magnitudes stay accepted), and the serde-path pin
3750    // (the gate fires at deserialize, before any validate gate runs).
3751
3752    #[test]
3753    fn parse_byte_size_rejects_leading_zero_magnitude() {
3754        // The fail-before-pass-after pin: `"064MiB"` parsed cleanly on
3755        // every pre-gate codebase (`u64::from_str` accepts the leading
3756        // zero), the codec produced 64 MiB, and
3757        // `render_byte_size(64*1024*1024)` emitted `"64MiB"` on the next
3758        // serialize — silently dropping the leading zero and drifting
3759        // the canonical form away from the author's intent. The new
3760        // gate surfaces the round-trip break at the parser layer with a
3761        // self-locating diagnostic, peer with
3762        // `parse_duration_rejects_leading_zero_magnitude` on the sibling
3763        // codec.
3764        let err = parse_byte_size("064MiB").unwrap_err();
3765        assert!(
3766            matches!(err, LimitsError::LeadingZeroByteMagnitude { ref value } if value == "064"),
3767            "got {err:?}"
3768        );
3769    }
3770
3771    #[test]
3772    fn parse_byte_size_rejects_multi_digit_zero_magnitude() {
3773        // `"00MiB"` is the degenerate leading-zero case — every byte is
3774        // `0`. `u64::from_str("00")` = 0, and the codec produces 0;
3775        // `render_byte_size(0)` emits `"0"` on the next serialize —
3776        // drift from `"00MiB"` to `"0"`. The leading-zero arm refuses
3777        // the drift class at the codec layer while leaving the
3778        // canonical single-byte `"0"` accepted. Peer with
3779        // `parse_duration_rejects_multi_digit_zero_magnitude` on the
3780        // sibling codec.
3781        let err = parse_byte_size("00MiB").unwrap_err();
3782        assert!(
3783            matches!(err, LimitsError::LeadingZeroByteMagnitude { ref value } if value == "00"),
3784            "got {err:?}"
3785        );
3786    }
3787
3788    #[test]
3789    fn parse_byte_size_rejects_leading_zero_in_gib_unit() {
3790        // `"01GiB"` parses to 1 GiB; the renderer emits `"1GiB"` on the
3791        // next serialize. The leading-zero class is a property of the
3792        // magnitude, not the unit — pin a per-GiB magnitude alongside
3793        // the per-MiB / per-KiB / bare-integer pins so the gate's
3794        // coverage is structural across every canonical unit suffix
3795        // the codec accepts. Mirrors the per-hour pin
3796        // `parse_duration_rejects_leading_zero_in_hour_window` carries
3797        // on the sibling codec.
3798        let err = parse_byte_size("01GiB").unwrap_err();
3799        assert!(
3800            matches!(err, LimitsError::LeadingZeroByteMagnitude { ref value } if value == "01"),
3801            "got {err:?}"
3802        );
3803    }
3804
3805    #[test]
3806    fn parse_byte_size_rejects_leading_zero_in_kib_unit() {
3807        // `"0512KiB"` parses to 512 KiB; the renderer emits `"512KiB"`
3808        // on the next serialize. Pin the per-KiB magnitude alongside
3809        // the per-MiB / per-GiB pins so the gate's coverage extends to
3810        // the smallest-unit power-of-1024 suffix the codec admits.
3811        let err = parse_byte_size("0512KiB").unwrap_err();
3812        assert!(
3813            matches!(err, LimitsError::LeadingZeroByteMagnitude { ref value } if value == "0512"),
3814            "got {err:?}"
3815        );
3816    }
3817
3818    #[test]
3819    fn parse_byte_size_rejects_leading_zero_in_decimal_units() {
3820        // `"0500MB"` parses to 500 MB (decimal-unit family — `KB` /
3821        // `MB` / `GB` powers of 1000, distinct from the `KiB` / `MiB` /
3822        // `GiB` powers-of-1024 family); the renderer emits the
3823        // appropriate canonical form on the next serialize. Pin the
3824        // decimal-unit family alongside the power-of-1024 family so the
3825        // gate's coverage is structural across both unit families the
3826        // codec admits.
3827        for (s, expected) in [("0500MB", "0500"), ("01KB", "01"), ("00GB", "00")] {
3828            let err = parse_byte_size(s).unwrap_err();
3829            assert!(
3830                matches!(err, LimitsError::LeadingZeroByteMagnitude { value: ref v } if v == expected),
3831                "got {err:?} for {s:?}"
3832            );
3833        }
3834    }
3835
3836    #[test]
3837    fn parse_byte_size_rejects_leading_zero_bare_integer() {
3838        // The bare-integer (no unit) shorthand inherits the leading-
3839        // zero arm: `"01024"` parses losslessly to 1024 bytes but
3840        // `render_byte_size(1024)` emits `"1KiB"` on the next serialize.
3841        // Pin the bare-integer path so a future relaxation that
3842        // special-cases the unitless shorthand surfaces here as a test
3843        // failure. Mirrors the bare-integer pin
3844        // `parse_duration_rejects_leading_zero_bare_integer_as_seconds`
3845        // carries on the sibling codec.
3846        let err = parse_byte_size("01024").unwrap_err();
3847        assert!(
3848            matches!(err, LimitsError::LeadingZeroByteMagnitude { ref value } if value == "01024"),
3849            "got {err:?}"
3850        );
3851    }
3852
3853    #[test]
3854    fn parse_byte_size_accepts_single_zero_magnitude_at_codec_layer() {
3855        // The codec-layer / typed-validate-layer boundary pin: the
3856        // single-byte `"0"` magnitude round-trips losslessly through
3857        // `render_byte_size` (`render_byte_size(0)` emits `"0"`), so it
3858        // stays accepted at this codec layer across every canonical
3859        // unit suffix. The downstream `LimitsError::MemoryZero` gate is
3860        // what refuses zero-magnitude authoring at the typed-validate
3861        // layer above — the partition keeps the canonical-form-drift
3862        // diagnostic (this arm) and the semantic-zero diagnostic (the
3863        // validate gate) disjoint. Mirrors the
3864        // `parse_duration_accepts_single_zero_magnitude_at_codec_layer`
3865        // partition pin on the sibling codec.
3866        assert_eq!(parse_byte_size("0").unwrap(), 0);
3867        assert_eq!(parse_byte_size("0B").unwrap(), 0);
3868        assert_eq!(parse_byte_size("0KiB").unwrap(), 0);
3869        assert_eq!(parse_byte_size("0MiB").unwrap(), 0);
3870        assert_eq!(parse_byte_size("0GiB").unwrap(), 0);
3871        assert_eq!(parse_byte_size("0KB").unwrap(), 0);
3872    }
3873
3874    #[test]
3875    fn parse_byte_size_accepts_canonical_magnitude_with_leading_one() {
3876        // The complement-side pin on the leading-zero arm: magnitudes
3877        // beginning with `1`..=`9` stay accepted across every canonical
3878        // unit suffix the codec accepts. Pin this so a future
3879        // tightening cannot drift into rejecting valid canonical
3880        // magnitudes — peer with the
3881        // `parse_duration_accepts_canonical_magnitude_with_leading_one`
3882        // pin on the sibling codec.
3883        assert_eq!(parse_byte_size("1").unwrap(), 1);
3884        assert_eq!(parse_byte_size("1KiB").unwrap(), 1024);
3885        assert_eq!(parse_byte_size("1MiB").unwrap(), 1024 * 1024);
3886        assert_eq!(parse_byte_size("1GiB").unwrap(), 1024 * 1024 * 1024);
3887        assert_eq!(parse_byte_size("64MiB").unwrap(), 64 * 1024 * 1024);
3888        assert_eq!(parse_byte_size("9").unwrap(), 9);
3889    }
3890
3891    #[test]
3892    fn de_byte_size_rejects_leading_zero_through_serde() {
3893        // The serde-path pin: a `:limits :memory` carrying a
3894        // leading-zero magnitude (`"064MiB"`) must fail at deserialize
3895        // time, not silently round-trip the value through the parser.
3896        // The gate fires at deserialize, before any validate gate runs
3897        // — peer with `de_duration_rejects_leading_zero_through_serde`
3898        // on the sibling codec.
3899        let json = r#"{"memory":"064MiB"}"#;
3900        let err = serde_json::from_str::<LimitsSpec>(json).unwrap_err();
3901        let msg = err.to_string();
3902        assert!(
3903            msg.contains("leading zero"),
3904            "serde diagnostic must surface the leading-zero reason verbatim (got {msg:?})"
3905        );
3906    }
3907
3908    // ── canonical-form: whitespace-rejection byte-size codec gate ─────────
3909    //
3910    // Direct successor to the `parse_duration` whitespace-rejection arm
3911    // (ebc3a75), the `supervisor::duration_codec` whitespace-rejection
3912    // arm (a7ae622), and the `rate_limit_codec` whitespace-rejection arm
3913    // (1ad7755) on the same canonical-form render-determinism axis. The
3914    // pre-gate top-level `s.trim()` at parse entry and the per-part
3915    // `num_part.trim()` / `unit.trim()` calls silently ate leading /
3916    // trailing / internal whitespace, so every whitespace-carrying
3917    // shape parsed to the same byte magnitude and round-tripped through
3918    // `render_byte_size` to a *different* canonical string on next
3919    // serialize — the same canonical-form-drift class the leading-`+` /
3920    // fractional / leading-zero arms already close on this codec.
3921    // `u8::is_ascii_whitespace` covers the five WhatWG-conformant ASCII
3922    // whitespace bytes (space `0x20`, tab `0x09`, LF `0x0A`, FF `0x0C`,
3923    // CR `0x0D`). Closes the whitespace-rejection axis across every
3924    // typed-magnitude codec in caixa-core.
3925
3926    #[test]
3927    fn parse_byte_size_rejects_leading_whitespace() {
3928        // The fail-before-pass-after pin: `" 64MiB"` — the canonical
3929        // paste-from-aligned-doc / paste-from-YAML-quoted-plain-scalar
3930        // footgun. Before this gate the top-level `s.trim()` at parse
3931        // entry silently ate the leading space and parsed the value to
3932        // 64 * 1024 * 1024 bytes, which then round-tripped through
3933        // `render_byte_size` to `"64MiB"` (a *different* canonical
3934        // string on the next emit) — the exact canonical-form-drift
3935        // class the leading-`+` / leading-zero arms already close,
3936        // extended to the whitespace-byte class. Peer with the sibling
3937        // `parse_duration_rejects_leading_whitespace` arm (ebc3a75) on
3938        // the shared canonical-form-drift trajectory.
3939        let err = parse_byte_size(" 64MiB").unwrap_err();
3940        assert!(
3941            matches!(err, LimitsError::WhitespaceInByteSize { ref value, byte } if value == " 64MiB" && byte == 0x20),
3942            "got {err:?}"
3943        );
3944        let msg = err.to_string();
3945        assert!(
3946            msg.contains("whitespace byte 0x20"),
3947            "diagnostic must surface the offending byte verbatim (got {msg:?})"
3948        );
3949        assert!(
3950            msg.contains("THEORY.md"),
3951            "diagnostic must cite the render-determinism contract (got {msg:?})"
3952        );
3953    }
3954
3955    #[test]
3956    fn parse_byte_size_rejects_trailing_whitespace() {
3957        // `"64MiB "` — the canonical shell-history / trailing-space
3958        // paste footgun. Before this gate the top-level `s.trim()`
3959        // silently ate the trailing space and parsed to 64 * 1024 *
3960        // 1024 bytes, round-tripping to `"64MiB"` on the next emit —
3961        // same canonical-form drift as the leading-space sibling,
3962        // closed on the same whitespace-byte arm.
3963        let err = parse_byte_size("64MiB ").unwrap_err();
3964        assert!(
3965            matches!(err, LimitsError::WhitespaceInByteSize { ref value, byte } if value == "64MiB " && byte == 0x20),
3966            "got {err:?}"
3967        );
3968    }
3969
3970    #[test]
3971    fn parse_byte_size_rejects_internal_whitespace_between_magnitude_and_unit() {
3972        // `"64 MiB"` — the canonical typographically-spaced author
3973        // shape (the same idiom every prose reference to a byte-size
3974        // renders as, mistakenly retained when the value is pasted
3975        // into a codec-shaped slot). Before this gate the per-part
3976        // `num_part.trim()` / `unit.trim()` calls silently ate the
3977        // whitespace between the magnitude and the unit and parsed the
3978        // value to 64 * 1024 * 1024 bytes, round-tripping to `"64MiB"`
3979        // — the codec's *internal* whitespace-tolerance vector,
3980        // orthogonal to the leading / trailing surface but the same
3981        // canonical-form-drift class. Pins the arm as strictly
3982        // stronger than the pre-existing top-level `s.trim()`
3983        // behavior: it fires on whitespace anywhere in the value, not
3984        // just at the string boundary.
3985        let err = parse_byte_size("64 MiB").unwrap_err();
3986        assert!(
3987            matches!(err, LimitsError::WhitespaceInByteSize { ref value, byte } if value == "64 MiB" && byte == 0x20),
3988            "got {err:?}"
3989        );
3990    }
3991
3992    #[test]
3993    fn parse_byte_size_rejects_tab_byte() {
3994        // `"\t64MiB"` — the canonical paste-from-indented-doc /
3995        // paste-from-YAML-block-scalar footgun where a tab byte leads
3996        // the magnitude. Pins that the gate covers tab (`0x09`) as
3997        // well as space (`0x20`) — both are `u8::is_ascii_whitespace`
3998        // members and both would be silently swallowed by `s.trim()`
3999        // pre-gate. The `is_ascii_whitespace` coverage extends beyond
4000        // space alone to the full ASCII-whitespace set (space `0x20`,
4001        // tab `0x09`, LF `0x0A`, FF `0x0C`, CR `0x0D`); this test pins
4002        // the tab arm as a representative of the non-space members.
4003        let err = parse_byte_size("\t64MiB").unwrap_err();
4004        assert!(
4005            matches!(err, LimitsError::WhitespaceInByteSize { ref value, byte } if value == "\t64MiB" && byte == 0x09),
4006            "got {err:?}"
4007        );
4008    }
4009
4010    #[test]
4011    fn parse_byte_size_rejects_trailing_newline() {
4012        // `"64MiB\n"` — the canonical multi-line-paste footgun where
4013        // a trailing LF byte survives the paste. Pins the LF member
4014        // (`0x0A`) of the `is_ascii_whitespace` set as a peer to the
4015        // space and tab pins above — every non-space non-tab
4016        // whitespace byte the WhatWG ASCII-whitespace set covers is
4017        // refused by the same arm.
4018        let err = parse_byte_size("64MiB\n").unwrap_err();
4019        assert!(
4020            matches!(err, LimitsError::WhitespaceInByteSize { ref value, byte } if value == "64MiB\n" && byte == 0x0a),
4021            "got {err:?}"
4022        );
4023    }
4024
4025    #[test]
4026    fn parse_byte_size_accepts_whitespace_free_canonical_forms() {
4027        // The complement-side pin: every canonical whitespace-free
4028        // authoring form the renderer emits stays accepted post-gate.
4029        // Sweep the canonical unit suffixes plus the bare-integer
4030        // shorthand so a future tightening of the whitespace arm that
4031        // over-fires on the accepted set surfaces here as a test
4032        // failure. Peer with the
4033        // `parse_duration_accepts_whitespace_free_canonical_forms` pin
4034        // on the sibling codec.
4035        assert_eq!(parse_byte_size("64MiB").unwrap(), 64 * 1024 * 1024);
4036        assert_eq!(parse_byte_size("1GiB").unwrap(), 1024 * 1024 * 1024);
4037        assert_eq!(parse_byte_size("512KiB").unwrap(), 512 * 1024);
4038        assert_eq!(parse_byte_size("1KB").unwrap(), 1_000);
4039        assert_eq!(parse_byte_size("1024").unwrap(), 1024);
4040        assert_eq!(parse_byte_size("0").unwrap(), 0);
4041    }
4042
4043    #[test]
4044    fn de_byte_size_rejects_whitespace_through_serde() {
4045        // The serde-path pin: a `:limits :memory` carrying a
4046        // whitespace-byte-carrying value (`" 64MiB"`) must fail at
4047        // deserialize time, not silently round-trip the value through
4048        // the pre-existing top-level `s.trim()`. The gate fires at
4049        // deserialize, before any validate gate runs — peer with the
4050        // existing `de_byte_size_rejects_leading_zero_through_serde` /
4051        // `de_duration_rejects_whitespace_through_serde` pins on the
4052        // same canonical-form-drift axis.
4053        let json = r#"{"memory":" 64MiB"}"#;
4054        let err = serde_json::from_str::<LimitsSpec>(json).unwrap_err();
4055        let msg = err.to_string();
4056        assert!(
4057            msg.contains("whitespace byte"),
4058            "serde diagnostic must surface the whitespace reason verbatim (got {msg:?})"
4059        );
4060        assert!(
4061            msg.contains("0x20"),
4062            "serde diagnostic must name the offending byte (got {msg:?})"
4063        );
4064
4065        // The whitespace-free complement — same author-side intent,
4066        // written in the canonical form the renderer would emit,
4067        // deserializes cleanly.
4068        let json = r#"{"memory":"64MiB"}"#;
4069        let l: LimitsSpec = serde_json::from_str(json).unwrap();
4070        assert_eq!(l.memory, Some(64 * 1024 * 1024));
4071    }
4072
4073    // ── canonical-form: non-ASCII Unicode `White_Space` byte-size gate ────
4074    //
4075    // Direct successor to the `parse_byte_size` ASCII-whitespace arm
4076    // (24a8ad4) — closes the strictly-complementary class the byte-scan
4077    // above cannot see. `str::trim` uses `char::is_whitespace` (Unicode
4078    // `White_Space`, strictly wider than the ASCII byte set); a leading /
4079    // trailing / internal NBSP (`\u{00A0}`) / LINE SEPARATOR (`\u{2028}`)
4080    // / EM-SPACE (`\u{2003}`) survives the byte-scan but is silently
4081    // stripped by the top-level trim, drifting to canonical `"64MiB"` on
4082    // round-trip. Pins the arm through the lifted
4083    // [`crate::render::find_non_ascii_whitespace_char`] predicate.
4084
4085    #[test]
4086    fn parse_byte_size_rejects_leading_nbsp() {
4087        // NBSP (`\u{00A0}` = UTF-8 `0xC2 0xA0`) — the canonical
4088        // paste-from-typography / paste-from-word-processor footgun.
4089        // Before this arm landed the byte-scan missed it (neither `0xC2`
4090        // nor `0xA0` is `is_ascii_whitespace`) and `str::trim` at parse
4091        // entry silently stripped it, yielding the same `64 * 1024 *
4092        // 1024` bytes as the whitespace-free canonical form and drifting
4093        // to `"64MiB"` on next serialize.
4094        let s = "\u{00A0}64MiB";
4095        let err = parse_byte_size(s).unwrap_err();
4096        assert!(
4097            matches!(err, LimitsError::NonAsciiWhitespaceInByteSize { ref value, ch, codepoint } if value == s && ch == '\u{00A0}' && codepoint == 0x00A0),
4098            "got {err:?}"
4099        );
4100        let msg = err.to_string();
4101        assert!(
4102            msg.contains("U+00A0"),
4103            "diagnostic must surface the codepoint verbatim (got {msg:?})"
4104        );
4105        assert!(
4106            msg.contains("THEORY.md"),
4107            "diagnostic must cite the render-determinism contract (got {msg:?})"
4108        );
4109    }
4110
4111    #[test]
4112    fn parse_byte_size_rejects_internal_line_separator() {
4113        // LINE SEPARATOR (`\u{2028}`) between magnitude and unit — the
4114        // canonical paste-from-web-doc footgun (many rendering engines
4115        // insert `\u{2028}` at soft-wrap boundaries in RTF/HTML → plain
4116        // text conversion). Pins the arm on a non-space non-NBSP Unicode
4117        // `White_Space` member.
4118        let s = "64\u{2028}MiB";
4119        let err = parse_byte_size(s).unwrap_err();
4120        assert!(
4121            matches!(err, LimitsError::NonAsciiWhitespaceInByteSize { ref value, ch, codepoint } if value == s && ch == '\u{2028}' && codepoint == 0x2028),
4122            "got {err:?}"
4123        );
4124    }
4125
4126    #[test]
4127    fn parse_byte_size_rejects_trailing_ideographic_space() {
4128        // IDEOGRAPHIC SPACE (`\u{3000}`) — the CJK-typography paste
4129        // footgun (canonical U+3000 is the full-width space that
4130        // Japanese / Chinese IMEs emit when input is auto-widened). Pins
4131        // the arm at the top edge of the `char::is_whitespace` set.
4132        let s = "64MiB\u{3000}";
4133        let err = parse_byte_size(s).unwrap_err();
4134        assert!(
4135            matches!(err, LimitsError::NonAsciiWhitespaceInByteSize { ref value, ch, codepoint } if value == s && ch == '\u{3000}' && codepoint == 0x3000),
4136            "got {err:?}"
4137        );
4138    }
4139
4140    #[test]
4141    fn parse_byte_size_accepts_ascii_only_canonical_forms_after_unicode_arm() {
4142        // Positive-control pin: every ASCII-only canonical form the
4143        // renderer emits stays accepted through the new arm — the
4144        // lifted predicate is a strict no-op on ASCII input.
4145        assert_eq!(parse_byte_size("64MiB").unwrap(), 64 * 1024 * 1024);
4146        assert_eq!(parse_byte_size("1GiB").unwrap(), 1024 * 1024 * 1024);
4147        assert_eq!(parse_byte_size("512KiB").unwrap(), 512 * 1024);
4148        assert_eq!(parse_byte_size("1024").unwrap(), 1024);
4149    }
4150
4151    // ── canonical-form: integer-magnitude duration codec gate ─────────────
4152    //
4153    // Direct successor to the `parse_byte_size` integer-magnitude gate on
4154    // the peer `:limits :memory` codec — every magnitude `render_duration`
4155    // emits is a non-negative integer (no decimal point, no leading sign,
4156    // no scientific notation). The parser's accepted set must match for
4157    // parse → render → parse to round-trip without canonical-form drift.
4158    // Pins every canonical-drift shape — fractional (`"1.5s"`),
4159    // decimal-shaped-integer (`"1.0s"`), half-unit (`"0.5m"`),
4160    // leading-`+` (`"+30s"`), leading-`-` (`"-30s"`) — plus the
4161    // complement-side pin (integer happy paths), the round-trip
4162    // convergence property, the BadDurationMagnitude-precedence pin
4163    // (genuinely unparseable inputs keep their narrower diagnostic), the
4164    // overflow-surface pin (u64-overflow on magnitude × unit surfaces at
4165    // parse time), and the serde-path pin (the gate fires at deserialize,
4166    // before any validate gate runs).
4167
4168    #[test]
4169    fn parse_duration_rejects_fractional_seconds() {
4170        // The fail-before-pass-after pin: `"1.5s"` parsed cleanly on
4171        // every pre-gate codebase (f64::parse accepts the decimal), the
4172        // codec produced 1500ms, and `render_duration(1500ms)` emitted
4173        // `"1500ms"` on the next serialize — silently drifting the
4174        // canonical form away from the author's intent. The new gate
4175        // surfaces the round-trip break at the parser layer with a
4176        // self-locating diagnostic.
4177        let err = parse_duration("1.5s").unwrap_err();
4178        assert!(
4179            matches!(err, LimitsError::NonIntegerDurationMagnitude { ref value } if value == "1.5"),
4180            "got {err:?}"
4181        );
4182    }
4183
4184    #[test]
4185    fn parse_duration_rejects_decimal_shaped_integer() {
4186        // The canonical-drift case where the *value* is integer but the
4187        // *form* carries a redundant decimal point — `"1.0s"` parses to
4188        // 1s (integer), but the renderer emits `"1s"` on the next
4189        // serialize (no decimal point). The parse-shape gate fires here
4190        // too so the codec's accepted set is exactly the renderer's
4191        // emitted set.
4192        let err = parse_duration("1.0s").unwrap_err();
4193        assert!(
4194            matches!(err, LimitsError::NonIntegerDurationMagnitude { ref value } if value == "1.0"),
4195            "got {err:?}"
4196        );
4197    }
4198
4199    #[test]
4200    fn parse_duration_rejects_half_minute() {
4201        // `"0.5m"` parses to 30s; the renderer emits `"30s"` on the
4202        // next serialize. Pin the round-trip drift on the explicitly-
4203        // fractional case sized to land on a smaller-unit boundary, so
4204        // the gate's coverage includes both the "doesn't land on a
4205        // boundary" (1.5s → 1500ms) and "lands on a smaller-unit
4206        // boundary" (0.5m → 30s) drift shapes — the same two-shape
4207        // pattern the byte-size gate covers (1.5KiB → 1536, 0.5GiB →
4208        // 512MiB).
4209        let err = parse_duration("0.5m").unwrap_err();
4210        assert!(
4211            matches!(err, LimitsError::NonIntegerDurationMagnitude { ref value } if value == "0.5"),
4212            "got {err:?}"
4213        );
4214    }
4215
4216    #[test]
4217    fn parse_duration_rejects_leading_plus() {
4218        // `"+30s"` parses through f64 as 30s; the renderer emits `"30s"`
4219        // on the next serialize. The leading `+` is not a renderer-
4220        // emitted shape, so it falls in the same canonical-drift class
4221        // as the fractional forms — surfacing under the same diagnostic
4222        // keeps the gate's coverage uniform across every non-canonical-
4223        // but-numeric input shape the parser would otherwise accept.
4224        let err = parse_duration("+30s").unwrap_err();
4225        assert!(
4226            matches!(err, LimitsError::NonIntegerDurationMagnitude { ref value } if value == "+30"),
4227            "got {err:?}"
4228        );
4229    }
4230
4231    #[test]
4232    fn parse_duration_rejects_negative_seconds_via_integer_gate() {
4233        // The negative-magnitude class — pre-gate the parser routed
4234        // negatives through the `num < 0.0` check to `BadDurationMagnitude`;
4235        // the new digit-only gate fires earlier and routes the same
4236        // input to `NonIntegerDurationMagnitude` (negatives are not
4237        // digit-only). Pin the new diagnostic so a future relaxation
4238        // that re-routes negatives back to the old arm surfaces here.
4239        let err = parse_duration("-30s").unwrap_err();
4240        assert!(
4241            matches!(err, LimitsError::NonIntegerDurationMagnitude { ref value } if value == "-30"),
4242            "got {err:?}"
4243        );
4244    }
4245
4246    #[test]
4247    fn parse_duration_continues_to_accept_integer_magnitudes() {
4248        // The complement-side pin: every canonical integer-magnitude
4249        // form the renderer emits must continue to parse to the same
4250        // value the renderer produced. Sweep the canonical authoring
4251        // shapes (ms, bare-s, s, m, h, and the bare-integer "0" zero-
4252        // shape) so a future tightening of the parser surfaces here as
4253        // a test failure rather than a silent regression.
4254        assert_eq!(parse_duration("0s").unwrap(), Duration::ZERO);
4255        assert_eq!(parse_duration("500ms").unwrap(), Duration::from_millis(500));
4256        assert_eq!(parse_duration("30s").unwrap(), Duration::from_secs(30));
4257        assert_eq!(parse_duration("2m").unwrap(), Duration::from_secs(120));
4258        assert_eq!(parse_duration("1h").unwrap(), Duration::from_secs(3600));
4259        assert_eq!(parse_duration("3600").unwrap(), Duration::from_secs(3600));
4260    }
4261
4262    #[test]
4263    fn parse_duration_round_trips_through_render_for_every_canonical_form() {
4264        // The structural property the gate makes load-bearing: every
4265        // value the parser accepts round-trips through the canonical
4266        // [`crate::supervisor::duration_codec::render`] primitive to a
4267        // string the parser also accepts — and to the *same* value.
4268        // Sweep the values the renderer emits canonically (ms / s / m /
4269        // h boundaries plus a non-aligned millisecond) so a future
4270        // codec change that breaks round-trip convergence surfaces here.
4271        for d in [
4272            Duration::from_millis(1),
4273            Duration::from_millis(500),
4274            Duration::from_millis(1500),
4275            Duration::from_secs(1),
4276            Duration::from_secs(30),
4277            Duration::from_secs(60),
4278            Duration::from_secs(120),
4279            Duration::from_secs(3600),
4280        ] {
4281            let rendered = crate::supervisor::duration_codec::render(d);
4282            let reparsed = parse_duration(&rendered)
4283                .unwrap_or_else(|e| panic!("render({d:?}) = {rendered:?} must reparse, got {e:?}"));
4284            assert_eq!(
4285                reparsed, d,
4286                "round-trip drift on {d:?}: rendered={rendered:?}, reparsed={reparsed:?}",
4287            );
4288        }
4289    }
4290
4291    #[test]
4292    fn parse_duration_keeps_bad_magnitude_for_unparseable_input() {
4293        // The precedence pin: the new `NonIntegerDurationMagnitude` arm
4294        // distinguishes *non-canonical-but-numeric* (`"1.5"`, `"+30"`,
4295        // `"-30"`) from *genuinely-unparseable* (`"abc"`, `"--1"`) so
4296        // the existing `BadDurationMagnitude` diagnostic's wording
4297        // remains load-bearing for the latter class — the gate is
4298        // additive, not replacing.
4299        let err = parse_duration("abcs").unwrap_err();
4300        assert!(
4301            matches!(err, LimitsError::BadDurationMagnitude(_)),
4302            "got {err:?}"
4303        );
4304        let err = parse_duration("--1s").unwrap_err();
4305        assert!(
4306            matches!(err, LimitsError::BadDurationMagnitude(_)),
4307            "got {err:?}"
4308        );
4309    }
4310
4311    #[test]
4312    fn parse_duration_overflow_surfaces_as_bad_magnitude() {
4313        // `u64::MAX h` overflows the seconds computation (magnitude ×
4314        // 3600); the parser surfaces the overflow as a
4315        // `BadDurationMagnitude` with an overflow-shaped wording so the
4316        // diagnostic names the offending magnitude × unit pair at parse
4317        // time. Matches `parse_byte_size`'s overflow-surface arm
4318        // structurally.
4319        let err = parse_duration("18446744073709551615h").unwrap_err();
4320        let LimitsError::BadDurationMagnitude(reason) = err else {
4321            panic!("expected BadDurationMagnitude(overflow), got other variant");
4322        };
4323        assert!(
4324            reason.contains("overflow"),
4325            "overflow diagnostic must mention overflow (got {reason:?})"
4326        );
4327    }
4328
4329    // ── canonical-form: leading-zero duration codec gate ─────────────────
4330    //
4331    // Direct successor to the `supervisor::duration_codec` leading-zero
4332    // arm (9178904) and the `rate_limit_codec` leading-zero arm (4f46830)
4333    // — closes the leading-zero canonical-form-drift class on the
4334    // `:limits :wall-clock` codec. Every magnitude `render_duration`
4335    // emits is a non-negative integer with no leading-zero padding; the
4336    // parser's accepted set must match for parse → render → parse to
4337    // round-trip without canonical-form drift. The single-byte `"0"`
4338    // round-trips losslessly (`render_duration(Duration::ZERO)` emits
4339    // `"0s"`) and the downstream [`LimitsError::WallClockZero`] gate
4340    // refuses zero-magnitude authoring at the typed-validate layer above
4341    // — the codec-layer / typed-validate-layer partition is what keeps
4342    // the diagnostic partitioning stable.
4343
4344    #[test]
4345    fn parse_duration_rejects_leading_zero_magnitude() {
4346        // The fail-before-pass-after pin: `"030s"` parsed cleanly on
4347        // every pre-gate codebase (`u64::from_str` accepts the leading
4348        // zero), the codec produced 30s, and `render_duration(30s)`
4349        // emitted `"30s"` on the next serialize — silently dropping
4350        // the leading zero and drifting the canonical form away from
4351        // the author's intent. The new gate surfaces the round-trip
4352        // break at the parser layer with a self-locating diagnostic.
4353        let err = parse_duration("030s").unwrap_err();
4354        assert!(
4355            matches!(err, LimitsError::LeadingZeroDurationMagnitude { ref value } if value == "030"),
4356            "got {err:?}"
4357        );
4358    }
4359
4360    #[test]
4361    fn parse_duration_rejects_multi_digit_zero_magnitude() {
4362        // `"00s"` is the degenerate leading-zero case — every byte is
4363        // `0`. `u64::from_str("00")` = 0, and the codec produces
4364        // `Duration::ZERO`; `render_duration(Duration::ZERO)` emits
4365        // `"0s"` on the next serialize — drift from `"00s"` to `"0s"`.
4366        // The leading-zero arm refuses the drift class at the codec
4367        // layer while leaving the canonical single-byte `"0s"` accepted.
4368        let err = parse_duration("00s").unwrap_err();
4369        assert!(
4370            matches!(err, LimitsError::LeadingZeroDurationMagnitude { ref value } if value == "00"),
4371            "got {err:?}"
4372        );
4373    }
4374
4375    #[test]
4376    fn parse_duration_rejects_leading_zero_in_hour_window() {
4377        // `"01h"` parses to 1h; the renderer emits `"1h"` on the next
4378        // serialize. The leading-zero class is a property of the
4379        // magnitude, not the unit — pin a per-hour magnitude alongside
4380        // the per-second / per-ms pins so the gate's coverage is
4381        // structural across every canonical unit suffix the codec
4382        // accepts. Mirrors the `_per_hour_window` pin the
4383        // `supervisor::duration_codec` and `rate_limit_codec` leading-
4384        // zero arms carry on the peer codecs.
4385        let err = parse_duration("01h").unwrap_err();
4386        assert!(
4387            matches!(err, LimitsError::LeadingZeroDurationMagnitude { ref value } if value == "01"),
4388            "got {err:?}"
4389        );
4390    }
4391
4392    #[test]
4393    fn parse_duration_rejects_leading_zero_bare_integer_as_seconds() {
4394        // The bare-integer-as-seconds shorthand (`"30"` → 30s, no unit
4395        // suffix because the parser routes the empty `unit` slot to
4396        // `Duration::from_secs`) inherits the leading-zero arm: `"030"`
4397        // parses losslessly to 30s but `render_duration(30s)` emits
4398        // `"30s"` on the next serialize. Pin the bare-integer path so a
4399        // future relaxation that special-cases the unitless shorthand
4400        // surfaces here as a test failure.
4401        let err = parse_duration("030").unwrap_err();
4402        assert!(
4403            matches!(err, LimitsError::LeadingZeroDurationMagnitude { ref value } if value == "030"),
4404            "got {err:?}"
4405        );
4406    }
4407
4408    #[test]
4409    fn parse_duration_accepts_single_zero_magnitude_at_codec_layer() {
4410        // The codec-layer / typed-validate-layer boundary pin: the
4411        // single-byte `"0"` magnitude round-trips losslessly through
4412        // `render_duration` (`render_duration(Duration::ZERO)` emits
4413        // `"0s"`), so it stays accepted at this codec layer across
4414        // every canonical unit suffix. The downstream
4415        // `LimitsError::WallClockZero` gate is what refuses
4416        // zero-magnitude authoring at the typed-validate layer above
4417        // — the partition keeps the canonical-form-drift diagnostic
4418        // (this arm) and the semantic-zero diagnostic (the validate
4419        // gate) disjoint.
4420        assert_eq!(parse_duration("0s").unwrap(), Duration::ZERO);
4421        assert_eq!(parse_duration("0ms").unwrap(), Duration::ZERO);
4422        assert_eq!(parse_duration("0m").unwrap(), Duration::ZERO);
4423        assert_eq!(parse_duration("0h").unwrap(), Duration::ZERO);
4424        assert_eq!(parse_duration("0").unwrap(), Duration::ZERO);
4425    }
4426
4427    #[test]
4428    fn parse_duration_accepts_canonical_magnitude_with_leading_one() {
4429        // The complement-side pin on the leading-zero arm: magnitudes
4430        // beginning with `1`..=`9` stay accepted across every canonical
4431        // unit suffix the codec accepts. Pin this so a future
4432        // tightening cannot drift into rejecting valid canonical
4433        // magnitudes — peer with the `_accepts_canonical_magnitude_with_leading_one`
4434        // pin the `supervisor::duration_codec` and `rate_limit_codec`
4435        // leading-zero arms carry.
4436        assert_eq!(parse_duration("1ms").unwrap(), Duration::from_millis(1));
4437        assert_eq!(parse_duration("1s").unwrap(), Duration::from_secs(1));
4438        assert_eq!(parse_duration("1m").unwrap(), Duration::from_secs(60));
4439        assert_eq!(parse_duration("1h").unwrap(), Duration::from_secs(3600));
4440        assert_eq!(parse_duration("100ms").unwrap(), Duration::from_millis(100));
4441        assert_eq!(parse_duration("500ms").unwrap(), Duration::from_millis(500));
4442    }
4443
4444    // ── canonical-form: whitespace-rejection duration codec gate ─────────
4445    //
4446    // Direct successor to the `supervisor::duration_codec` whitespace-
4447    // rejection arm (a7ae622) and the `rate_limit_codec` whitespace-
4448    // rejection arm (1ad7755) on the same canonical-form
4449    // render-determinism axis. The pre-gate top-level `s.trim()` at
4450    // parse entry and the per-part `num_part.trim()` / `unit.trim()`
4451    // calls silently ate leading / trailing / internal whitespace, so
4452    // every whitespace-carrying shape parsed to the same integer
4453    // magnitude and round-tripped through `render_duration` to a
4454    // *different* canonical string on next serialize — the same
4455    // canonical-form-drift class the leading-`+` / fractional /
4456    // leading-zero arms already close on this codec. `u8::is_ascii_whitespace`
4457    // covers the five WhatWG-conformant ASCII whitespace bytes
4458    // (space `0x20`, tab `0x09`, LF `0x0A`, FF `0x0C`, CR `0x0D`).
4459
4460    #[test]
4461    fn parse_duration_rejects_leading_whitespace() {
4462        // The fail-before-pass-after pin: `" 30s"` — the canonical
4463        // paste-from-aligned-doc / paste-from-YAML-quoted-plain-scalar
4464        // footgun. Before this gate the top-level `s.trim()` at parse
4465        // entry silently ate the leading space and parsed the value to
4466        // `Duration::from_secs(30)`, which then round-tripped through
4467        // `render_duration` to `"30s"` (a *different* canonical string
4468        // on the next emit) — the exact canonical-form-drift class the
4469        // leading-`+` / leading-zero arms already close, extended to
4470        // the whitespace-byte class. Peer with the sibling
4471        // `supervisor::duration_codec` `parse_rejects_leading_whitespace`
4472        // arm (a7ae622) on the shared duration-codec trajectory.
4473        let err = parse_duration(" 30s").unwrap_err();
4474        assert!(
4475            matches!(err, LimitsError::WhitespaceInDuration { ref value, byte } if value == " 30s" && byte == 0x20),
4476            "got {err:?}"
4477        );
4478        let msg = err.to_string();
4479        assert!(
4480            msg.contains("whitespace byte 0x20"),
4481            "diagnostic must surface the offending byte verbatim (got {msg:?})"
4482        );
4483        assert!(
4484            msg.contains("THEORY.md"),
4485            "diagnostic must cite the render-determinism contract (got {msg:?})"
4486        );
4487    }
4488
4489    #[test]
4490    fn parse_duration_rejects_trailing_whitespace() {
4491        // `"30s "` — the canonical shell-history / trailing-space paste
4492        // footgun. Before this gate the top-level `s.trim()` silently
4493        // ate the trailing space and parsed to `Duration::from_secs(30)`,
4494        // round-tripping to `"30s"` on the next emit — same canonical-
4495        // form drift as the leading-space sibling, closed on the same
4496        // whitespace-byte arm.
4497        let err = parse_duration("30s ").unwrap_err();
4498        assert!(
4499            matches!(err, LimitsError::WhitespaceInDuration { ref value, byte } if value == "30s " && byte == 0x20),
4500            "got {err:?}"
4501        );
4502    }
4503
4504    #[test]
4505    fn parse_duration_rejects_internal_whitespace_between_magnitude_and_unit() {
4506        // `"30 s"` — the canonical typographically-spaced author shape
4507        // (the same idiom every prose reference to a duration renders as,
4508        // mistakenly retained when the value is pasted into a codec-
4509        // shaped slot). Before this gate the per-part `num_part.trim()`
4510        // / `unit.trim()` calls silently ate the whitespace between the
4511        // magnitude and the unit and parsed the value to
4512        // `Duration::from_secs(30)`, round-tripping to `"30s"` — the
4513        // codec's *internal* whitespace-tolerance vector, orthogonal
4514        // to the leading / trailing surface but the same canonical-
4515        // form-drift class. Pins the arm as strictly stronger than the
4516        // pre-existing top-level `s.trim()` behavior: it fires on
4517        // whitespace anywhere in the value, not just at the string
4518        // boundary.
4519        let err = parse_duration("30 s").unwrap_err();
4520        assert!(
4521            matches!(err, LimitsError::WhitespaceInDuration { ref value, byte } if value == "30 s" && byte == 0x20),
4522            "got {err:?}"
4523        );
4524    }
4525
4526    #[test]
4527    fn parse_duration_rejects_tab_byte() {
4528        // `"\t30s"` — the canonical paste-from-indented-doc /
4529        // paste-from-YAML-block-scalar footgun where a tab byte leads
4530        // the magnitude. Pins that the gate covers tab (`0x09`) as well
4531        // as space (`0x20`) — both are `u8::is_ascii_whitespace` members
4532        // and both would be silently swallowed by `s.trim()` pre-gate.
4533        // The `is_ascii_whitespace` coverage extends beyond space alone
4534        // to the full ASCII-whitespace set (space `0x20`, tab `0x09`,
4535        // LF `0x0A`, FF `0x0C`, CR `0x0D`); this test pins the tab arm
4536        // as a representative of the non-space members.
4537        let err = parse_duration("\t30s").unwrap_err();
4538        assert!(
4539            matches!(err, LimitsError::WhitespaceInDuration { ref value, byte } if value == "\t30s" && byte == 0x09),
4540            "got {err:?}"
4541        );
4542    }
4543
4544    #[test]
4545    fn parse_duration_rejects_trailing_newline() {
4546        // `"30s\n"` — the canonical multi-line-paste footgun where a
4547        // trailing LF byte survives the paste. Pins the LF member
4548        // (`0x0A`) of the `is_ascii_whitespace` set as a peer to the
4549        // space and tab pins above — every non-space non-tab whitespace
4550        // byte the WhatWG ASCII-whitespace set covers is refused by
4551        // the same arm.
4552        let err = parse_duration("30s\n").unwrap_err();
4553        assert!(
4554            matches!(err, LimitsError::WhitespaceInDuration { ref value, byte } if value == "30s\n" && byte == 0x0a),
4555            "got {err:?}"
4556        );
4557    }
4558
4559    #[test]
4560    fn parse_duration_accepts_whitespace_free_canonical_forms() {
4561        // The complement-side pin: every canonical whitespace-free
4562        // authoring form the renderer emits stays accepted post-gate.
4563        // Sweep the canonical unit suffixes plus the bare-integer
4564        // shorthand so a future tightening of the whitespace arm that
4565        // over-fires on the accepted set surfaces here as a test
4566        // failure. Peer with the `parse_duration_continues_to_accept_integer_magnitudes`
4567        // pin the fractional / leading-`+` gate carries.
4568        assert_eq!(parse_duration("30s").unwrap(), Duration::from_secs(30));
4569        assert_eq!(parse_duration("500ms").unwrap(), Duration::from_millis(500));
4570        assert_eq!(parse_duration("2m").unwrap(), Duration::from_secs(120));
4571        assert_eq!(parse_duration("1h").unwrap(), Duration::from_secs(3600));
4572        assert_eq!(parse_duration("0s").unwrap(), Duration::ZERO);
4573        assert_eq!(parse_duration("3600").unwrap(), Duration::from_secs(3600));
4574    }
4575
4576    #[test]
4577    fn de_duration_rejects_whitespace_through_serde() {
4578        // The serde-path pin: a `:limits :wall-clock` carrying a
4579        // whitespace-byte-carrying value (`" 30s"`) must fail at
4580        // deserialize time, not silently round-trip the value through
4581        // the pre-existing top-level `s.trim()`. The gate fires at
4582        // deserialize, before any validate gate runs — peer with the
4583        // existing `de_duration_rejects_leading_zero_through_serde` /
4584        // `de_duration_rejects_fractional_value_through_serde` pins on
4585        // the same canonical-form-drift axis.
4586        let json = r#"{"wallClock":" 30s"}"#;
4587        let err = serde_json::from_str::<LimitsSpec>(json).unwrap_err();
4588        let msg = err.to_string();
4589        assert!(
4590            msg.contains("whitespace byte"),
4591            "serde diagnostic must surface the whitespace reason verbatim (got {msg:?})"
4592        );
4593        assert!(
4594            msg.contains("0x20"),
4595            "serde diagnostic must name the offending byte (got {msg:?})"
4596        );
4597
4598        // The whitespace-free complement — same author-side intent,
4599        // written in the canonical form the renderer would emit,
4600        // deserializes cleanly.
4601        let json = r#"{"wallClock":"30s"}"#;
4602        let l: LimitsSpec = serde_json::from_str(json).unwrap();
4603        assert_eq!(l.wall_clock, Some(Duration::from_secs(30)));
4604    }
4605
4606    // ── canonical-form: non-ASCII Unicode `White_Space` duration gate ─────
4607    //
4608    // Successor to the `parse_duration` ASCII-whitespace arm (ebc3a75)
4609    // — closes the strictly-complementary class the byte-scan cannot
4610    // see, through the lifted
4611    // [`crate::render::find_non_ascii_whitespace_char`] predicate.
4612
4613    #[test]
4614    fn parse_duration_rejects_leading_nbsp() {
4615        // NBSP prefix — paste-from-typography footgun. Byte-scan misses,
4616        // `str::trim` strips silently, drifting to `"30s"` on next
4617        // emit.
4618        let s = "\u{00A0}30s";
4619        let err = parse_duration(s).unwrap_err();
4620        assert!(
4621            matches!(err, LimitsError::NonAsciiWhitespaceInDuration { ref value, ch, codepoint } if value == s && ch == '\u{00A0}' && codepoint == 0x00A0),
4622            "got {err:?}"
4623        );
4624        let msg = err.to_string();
4625        assert!(
4626            msg.contains("U+00A0"),
4627            "diagnostic must name codepoint (got {msg:?})"
4628        );
4629    }
4630
4631    #[test]
4632    fn parse_duration_rejects_internal_em_space() {
4633        // EM-SPACE (`\u{2003}`) between magnitude and unit — canonical
4634        // paste-from-typography footgun on the `<integer><unit>` shape.
4635        let s = "30\u{2003}s";
4636        let err = parse_duration(s).unwrap_err();
4637        assert!(
4638            matches!(err, LimitsError::NonAsciiWhitespaceInDuration { ref value, ch, codepoint } if value == s && ch == '\u{2003}' && codepoint == 0x2003),
4639            "got {err:?}"
4640        );
4641    }
4642
4643    #[test]
4644    fn parse_duration_accepts_ascii_only_canonical_forms_after_unicode_arm() {
4645        // Positive-control pin: every ASCII-only canonical form the
4646        // renderer emits stays accepted through the new arm.
4647        assert_eq!(parse_duration("30s").unwrap(), Duration::from_secs(30));
4648        assert_eq!(parse_duration("500ms").unwrap(), Duration::from_millis(500));
4649        assert_eq!(parse_duration("1h").unwrap(), Duration::from_secs(3600));
4650    }
4651
4652    #[test]
4653    fn de_duration_rejects_leading_zero_through_serde() {
4654        // The serde-path pin: a `:limits :wall-clock` carrying a
4655        // leading-zero magnitude (`"030s"`) must fail at deserialize
4656        // time, not silently round-trip the value through the parser.
4657        // The gate fires at deserialize, before any validate gate runs
4658        // — peer with the existing `de_duration_rejects_fractional_value_through_serde`
4659        // pin on the same canonical-form-drift axis.
4660        let json = r#"{"wallClock":"030s"}"#;
4661        let err = serde_json::from_str::<LimitsSpec>(json).unwrap_err();
4662        let msg = err.to_string();
4663        assert!(
4664            msg.contains("leading zero"),
4665            "serde diagnostic must surface the leading-zero reason verbatim (got {msg:?})"
4666        );
4667
4668        let json = r#"{"wallClock":"30s"}"#;
4669        let l: LimitsSpec = serde_json::from_str(json).unwrap();
4670        assert_eq!(l.wall_clock, Some(Duration::from_secs(30)));
4671    }
4672
4673    #[test]
4674    fn de_duration_rejects_fractional_value_through_serde() {
4675        // The serde-path pin: a `:limits :wall-clock` carrying a
4676        // fractional magnitude (`"1.5s"`) must fail at deserialize time,
4677        // not silently round-trip the value through the f64 parser. Pin
4678        // both the success-on-canonical path (the integer form
4679        // deserializes cleanly) and the failure-on-non-canonical path
4680        // (the fractional form is rejected by the codec before any
4681        // validate gate runs).
4682        let json = r#"{"wallClock":"1.5s"}"#;
4683        let err = serde_json::from_str::<LimitsSpec>(json).unwrap_err();
4684        let msg = err.to_string();
4685        assert!(
4686            msg.contains("non-negative integer"),
4687            "serde diagnostic must surface the integer-magnitude reason verbatim \
4688             (got {msg:?})"
4689        );
4690
4691        // The integer-form complement — same author-side intent
4692        // (1.5s = 1500ms), written in the canonical form the renderer
4693        // would emit, deserializes cleanly.
4694        let json = r#"{"wallClock":"1500ms"}"#;
4695        let l: LimitsSpec = serde_json::from_str(json).unwrap();
4696        assert_eq!(l.wall_clock, Some(Duration::from_millis(1500)));
4697    }
4698
4699    #[test]
4700    fn de_byte_size_rejects_fractional_value_through_serde() {
4701        // The serde-path pin: a `:limits :memory` carrying a fractional
4702        // magnitude (`"1.5KiB"`) must fail at deserialize time, not
4703        // silently round-trip the value through the f64 parser. Pin
4704        // both the success-on-canonical path (the integer form
4705        // deserializes cleanly) and the failure-on-non-canonical path
4706        // (the fractional form is rejected by the codec before any
4707        // validate gate runs).
4708        let json = r#"{"memory":"1.5KiB"}"#;
4709        let err = serde_json::from_str::<LimitsSpec>(json).unwrap_err();
4710        let msg = err.to_string();
4711        assert!(
4712            msg.contains("non-negative integer"),
4713            "serde diagnostic must surface the integer-magnitude reason verbatim (got {msg:?})"
4714        );
4715
4716        // The integer-form complement — same author-side intent
4717        // (1.5KiB = 1536 bytes), written in the canonical form the
4718        // renderer would emit, deserializes cleanly.
4719        let json = r#"{"memory":"1536"}"#;
4720        let l: LimitsSpec = serde_json::from_str(json).unwrap();
4721        assert_eq!(l.memory, Some(1536));
4722    }
4723
4724    // ── canonical-form: integer-magnitude millicores codec gate ───────────
4725    //
4726    // Direct successor to the `parse_byte_size` / `parse_duration` /
4727    // shared `supervisor::duration_codec` / `rate_limit_codec`
4728    // integer-magnitude gates on the four peer typed codecs in
4729    // caixa-core — closes the sixth (and last) typed-codec surface in
4730    // the crate. Every magnitude `render_millicores` emits is a
4731    // non-negative integer (`format!("{m}m")`) — no decimal point, no
4732    // leading sign, no scientific notation. The parser's accepted set
4733    // must match for parse → render → parse to round-trip without
4734    // canonical-form drift. Pins every canonical-drift shape —
4735    // leading-`+` (`"+500m"` / `"+2"`, the load-bearing class the
4736    // digit-only gate closes beyond `u32::from_str` strictness),
4737    // leading-`-` (`"-100m"`), fractional (`"1.5"`), decimal-shaped-
4738    // integer on both authoring paths (`"500.0m"` / `"2.0"`), the
4739    // bare-`m`-with-no-magnitude pin, the empty-string pin, the
4740    // garbage-precedence pin (genuinely unparseable inputs keep the
4741    // narrower `BadMillicores` diagnostic), the u32-overflow surface
4742    // pin on both the `m`-suffix and bare-core multiply paths, the
4743    // complement-side pin (every integer happy path the gate must
4744    // continue to accept), the round-trip convergence property, and
4745    // the serde-path pin (the gate fires at deserialize, before any
4746    // validate gate runs).
4747
4748    #[test]
4749    fn parse_millicores_rejects_fractional_magnitude() {
4750        // The fail-before-pass-after pin on the bare-core path:
4751        // `"1.5"` parsed cleanly on no pre-gate codebase (`u32::from_str`
4752        // rejects the decimal), but the diagnostic was value-laundered
4753        // (the bare `BadMillicores("1.5")` wording didn't name the
4754        // canonical-form remediation or the round-trip drift the next
4755        // emit would produce — `1.5 cores × 1000 = 1500 millicores` →
4756        // `"1500m"` on the renderer). The gate routes the same input to
4757        // `NonIntegerMillicoreMagnitude` with the canonical-form wording.
4758        let err = parse_millicores("1.5").unwrap_err();
4759        assert!(
4760            matches!(err, LimitsError::NonIntegerMillicoreMagnitude { ref value } if value == "1.5"),
4761            "got {err:?}"
4762        );
4763    }
4764
4765    #[test]
4766    fn parse_millicores_rejects_decimal_shaped_integer_with_suffix() {
4767        // The canonical-drift case on the `m`-suffix path where the
4768        // *value* is integer but the *form* carries a redundant decimal
4769        // point — `"500.0m"` parses to 500 millicores (integer), but
4770        // the renderer emits `"500m"` on the next serialize (no decimal
4771        // point). The parse-shape gate fires here too so the codec's
4772        // accepted set is exactly the renderer's emitted set — same
4773        // shape as `parse_byte_size`'s `"1.0MiB"` case.
4774        let err = parse_millicores("500.0m").unwrap_err();
4775        assert!(
4776            matches!(err, LimitsError::NonIntegerMillicoreMagnitude { ref value } if value == "500.0"),
4777            "got {err:?}"
4778        );
4779    }
4780
4781    #[test]
4782    fn parse_millicores_rejects_decimal_shaped_integer_bare_core() {
4783        // The decimal-shaped-integer pin on the bare-core path —
4784        // `"2.0"` would be 2000 millicores (the canonical `"2000m"`),
4785        // but the redundant decimal point is not a renderer-emitted
4786        // shape. Surfaces under the same diagnostic as the `m`-suffix
4787        // path so the gate's coverage is uniform across both authoring
4788        // paths.
4789        let err = parse_millicores("2.0").unwrap_err();
4790        assert!(
4791            matches!(err, LimitsError::NonIntegerMillicoreMagnitude { ref value } if value == "2.0"),
4792            "got {err:?}"
4793        );
4794    }
4795
4796    #[test]
4797    fn parse_millicores_rejects_leading_plus_sign_with_suffix() {
4798        // The load-bearing class the digit-only gate closes beyond
4799        // `u32::from_str`'s strictness: current Rust `u32::from_str`
4800        // permissively accepts `"+500"` → 500, so `"+500m"` parsed
4801        // cleanly through the pre-gate codec to `RateLimit`-shaped
4802        // 500 millicores and serde silently round-tripped to `"500m"`
4803        // on the next emit — a *different* canonical string. Same
4804        // shape as `parse_byte_size`'s `"+1024"` (875 commit) and
4805        // `parse_duration`'s `"+30s"` (1027 commit) cases on the peer
4806        // codecs.
4807        let err = parse_millicores("+500m").unwrap_err();
4808        assert!(
4809            matches!(err, LimitsError::NonIntegerMillicoreMagnitude { ref value } if value == "+500"),
4810            "got {err:?}"
4811        );
4812    }
4813
4814    #[test]
4815    fn parse_millicores_rejects_leading_plus_sign_bare_core() {
4816        // The leading-`+` pin on the bare-core path — `"+2"` parsed
4817        // through `u32::from_str` as 2 → 2000 millicores → `"2000m"`
4818        // on the renderer; canonical-drift. The digit-only gate routes
4819        // the same input to `NonIntegerMillicoreMagnitude`, peer with
4820        // the `m`-suffix path.
4821        let err = parse_millicores("+2").unwrap_err();
4822        assert!(
4823            matches!(err, LimitsError::NonIntegerMillicoreMagnitude { ref value } if value == "+2"),
4824            "got {err:?}"
4825        );
4826    }
4827
4828    #[test]
4829    fn parse_millicores_rejects_leading_minus_sign() {
4830        // The negative-magnitude class — pre-gate `u32::from_str`
4831        // rejected negatives but the diagnostic collapsed onto the
4832        // opaque `BadMillicores("-100m")` wording. The digit-only gate
4833        // fires earlier and routes the same input to
4834        // `NonIntegerMillicoreMagnitude` (negatives are not digit-only,
4835        // and `i64::from_str` accepts the leading sign so the numeric
4836        // arm matches). Pin the new diagnostic so a future relaxation
4837        // that re-routes negatives back to the old arm surfaces here.
4838        let err = parse_millicores("-100m").unwrap_err();
4839        assert!(
4840            matches!(err, LimitsError::NonIntegerMillicoreMagnitude { ref value } if value == "-100"),
4841            "got {err:?}"
4842        );
4843    }
4844
4845    #[test]
4846    fn parse_millicores_rejects_empty_string() {
4847        // The empty-input pin — `""` is not a magnitude at all. Pre-
4848        // gate this fell through to `s.parse::<u32>()` and surfaced as
4849        // a generic parse failure with the same `BadMillicores("")`
4850        // wording; the explicit empty-check at the top of the codec
4851        // surfaces the same diagnostic earlier and makes the empty-
4852        // input class structurally distinct from the digit-only /
4853        // numeric / garbage arms below.
4854        let err = parse_millicores("").unwrap_err();
4855        assert!(matches!(err, LimitsError::BadMillicores(_)), "got {err:?}");
4856    }
4857
4858    #[test]
4859    fn parse_millicores_rejects_bare_unit_with_no_magnitude() {
4860        // The bare-`m`-with-no-magnitude pin — `"m"` strips to `""`,
4861        // which is not a magnitude at all. The canonical millicores
4862        // authoring form requires a magnitude in front of the unit
4863        // (`"500m"`, not `"m"`). Surface as `BadMillicores` so the
4864        // narrower-arm wording stays load-bearing for this class.
4865        let err = parse_millicores("m").unwrap_err();
4866        assert!(matches!(err, LimitsError::BadMillicores(_)), "got {err:?}");
4867    }
4868
4869    #[test]
4870    fn parse_millicores_garbage_still_falls_through_to_bad_millicores() {
4871        // The precedence pin: the new `NonIntegerMillicoreMagnitude`
4872        // arm distinguishes *non-canonical-but-numeric* (`"1.5"`,
4873        // `"+500m"`, `"-100m"`, `"500.0m"`) from *genuinely-
4874        // unparseable* (`"abc"`, `"--1m"`, `"foo"`) so the existing
4875        // `BadMillicores` diagnostic's wording remains load-bearing
4876        // for the latter class — the gate is additive, not replacing.
4877        // Pin both arms so a future relaxation that collapses them
4878        // surfaces here.
4879        let err = parse_millicores("abc").unwrap_err();
4880        assert!(matches!(err, LimitsError::BadMillicores(_)), "got {err:?}");
4881        let err = parse_millicores("--1m").unwrap_err();
4882        assert!(matches!(err, LimitsError::BadMillicores(_)), "got {err:?}");
4883        let err = parse_millicores("foo").unwrap_err();
4884        assert!(matches!(err, LimitsError::BadMillicores(_)), "got {err:?}");
4885    }
4886
4887    #[test]
4888    fn parse_millicores_u32_overflow_with_suffix_surfaces_as_overflow() {
4889        // The u32-overflow surface pin on the `m`-suffix path: a
4890        // magnitude exceeding `u32::MAX` (4294967296 = u32::MAX + 1)
4891        // surfaces as `BadMillicores` with an overflow-shaped wording
4892        // naming the offending magnitude verbatim. The digit-only
4893        // guard guarantees every byte is `[0-9]`, so overflow is the
4894        // only remaining `u32::from_str` failure mode — the overflow
4895        // arm is no longer in unreachable-by-prior-gate territory.
4896        // Matches the overflow-arm shape on `parse_byte_size` /
4897        // `parse_duration` / `rate_limit_codec`.
4898        let err = parse_millicores("4294967296m").unwrap_err();
4899        let LimitsError::BadMillicores(reason) = err else {
4900            panic!("expected BadMillicores(overflow), got other variant");
4901        };
4902        assert!(
4903            reason.contains("overflow"),
4904            "overflow diagnostic must mention overflow (got {reason:?})"
4905        );
4906    }
4907
4908    #[test]
4909    fn parse_millicores_bare_core_overflow_surfaces_as_overflow() {
4910        // The u32-overflow surface pin on the bare-core path: a
4911        // magnitude that fits u32 on its own but overflows on the
4912        // `× 1000` conversion to millicores surfaces as
4913        // `BadMillicores` with an overflow-shaped wording. Pre-gate
4914        // the codec used `saturating_mul(1000)` which silently
4915        // saturated the result at `u32::MAX` — landing as the cap
4916        // value far from the author's intent and bypassing any
4917        // future validate-time upper-bound gate the `:cpu` axis
4918        // grows. The `checked_mul` rewrite surfaces the overflow at
4919        // parse time. (4294968 cores × 1000 = 4294968000 > u32::MAX
4920        // = 4294967295 — the smallest digit-string that overflows
4921        // u32 on the × 1000 multiply while fitting u32 on its own.)
4922        let err = parse_millicores("4294968").unwrap_err();
4923        let LimitsError::BadMillicores(reason) = err else {
4924            panic!("expected BadMillicores(× 1000 overflow), got other variant");
4925        };
4926        assert!(
4927            reason.contains("overflow"),
4928            "× 1000 overflow diagnostic must mention overflow (got {reason:?})"
4929        );
4930    }
4931
4932    #[test]
4933    fn parse_millicores_continues_to_accept_canonical_forms() {
4934        // The complement-side pin: every canonical integer-magnitude
4935        // form the renderer emits must continue to parse to the same
4936        // value the renderer produced. Sweep the canonical authoring
4937        // shapes on both paths (the `m`-suffix path: `"0m"`, `"500m"`,
4938        // `"2000m"`; the bare-core shorthand: `"0"`, `"2"`, `"4"`) so
4939        // a future tightening of the parser surfaces here as a test
4940        // failure rather than a silent regression. The `0` case is at
4941        // the codec layer only; `validate_rejects_zero_cpu` rejects
4942        // `Some(0)` one level up.
4943        assert_eq!(parse_millicores("0m").unwrap(), 0);
4944        assert_eq!(parse_millicores("500m").unwrap(), 500);
4945        assert_eq!(parse_millicores("1500m").unwrap(), 1500);
4946        assert_eq!(parse_millicores("2000m").unwrap(), 2000);
4947        assert_eq!(parse_millicores("0").unwrap(), 0);
4948        assert_eq!(parse_millicores("2").unwrap(), 2000);
4949        assert_eq!(parse_millicores("4").unwrap(), 4000);
4950    }
4951
4952    #[test]
4953    fn parse_millicores_round_trips_through_render_for_every_canonical_form() {
4954        // The structural property the gate makes load-bearing: every
4955        // value the parser accepts round-trips through
4956        // `render_millicores` to a string the parser also accepts —
4957        // and to the *same* value. Sweep the values the renderer emits
4958        // canonically (zero, sub-core, single-core boundary, multi-
4959        // core, and a non-1000-multiple millicore value) so a future
4960        // codec change that breaks round-trip convergence surfaces
4961        // here, not at a downstream renderer that double-emits a
4962        // typed slot.
4963        for m in [0u32, 1, 100, 500, 1000, 1500, 2000, 12345] {
4964            let rendered = render_millicores(m);
4965            let reparsed = parse_millicores(&rendered)
4966                .unwrap_or_else(|e| panic!("render({m}) = {rendered:?} must reparse, got {e:?}"));
4967            assert_eq!(
4968                reparsed, m,
4969                "round-trip drift on {m}: rendered={rendered:?}, reparsed={reparsed}",
4970            );
4971        }
4972    }
4973
4974    #[test]
4975    fn de_millicores_rejects_leading_plus_through_serde() {
4976        // The serde-path pin: a `:limits :cpu` carrying a leading-`+`
4977        // magnitude (`"+500m"`) must fail at deserialize time, not
4978        // silently round-trip the value through `u32::from_str`'s
4979        // permissive sign-acceptance. Pin both the success-on-canonical
4980        // path (the integer form deserializes cleanly) and the
4981        // failure-on-non-canonical path (the leading-`+` form is
4982        // rejected by the codec before any validate gate runs).
4983        let json = r#"{"cpu":"+500m"}"#;
4984        let err = serde_json::from_str::<LimitsSpec>(json).unwrap_err();
4985        let msg = err.to_string();
4986        assert!(
4987            msg.contains("non-negative integer"),
4988            "serde diagnostic must surface the integer-magnitude reason verbatim \
4989             (got {msg:?})"
4990        );
4991
4992        // The integer-form complement — same author-side intent
4993        // (500 millicores), written in the canonical form the renderer
4994        // would emit, deserializes cleanly.
4995        let json = r#"{"cpu":"500m"}"#;
4996        let l: LimitsSpec = serde_json::from_str(json).unwrap();
4997        assert_eq!(l.cpu, Some(500));
4998    }
4999
5000    // ── canonical-form: leading-zero millicores codec gate ────────────────
5001    //
5002    // Direct successor to the `parse_byte_size` / `parse_duration` /
5003    // `supervisor::duration_codec` / `rate_limit_codec` leading-zero
5004    // arms (cea9a78 / 39762d7 / 9178904 / 4f46830) — closes the sixth
5005    // (and last) typed numeric-codec surface in caixa-core on the
5006    // integer-magnitude leading-zero axis. Every magnitude
5007    // `render_millicores` emits is the leading-zero-stripped form
5008    // (`format!("{m}m")` — no leading-zero padding), so a digit-only-
5009    // but-leading-zero magnitude parses losslessly through `u32::from_str`
5010    // and serde silently round-trips the value to a *different*
5011    // canonical string on the next emit. Pins every canonical-drift
5012    // shape on the `m`-suffix and bare-core paths, the codec-vs-
5013    // typed-validate-layer boundary (the single-byte `"0"` stays in the
5014    // codec's accepted set; `CpuZero` refuses it at validate), the
5015    // complement-side pin (every canonical leading-`[1-9]` magnitude
5016    // continues to parse cleanly), and the serde-path pin.
5017
5018    #[test]
5019    fn parse_millicores_rejects_leading_zero_magnitude_with_suffix() {
5020        // The fail-before-pass-after pin on the `m`-suffix path:
5021        // `"0500m"` parsed cleanly on no pre-gate codebase
5022        // (`u32::from_str` accepts `"0500"` → 500), then `render_millicores`
5023        // emitted `"500m"` on the next serialize — canonical-form drift.
5024        // The leading-zero arm routes the same input to
5025        // `LeadingZeroMillicoreMagnitude` with the canonical-form
5026        // remediation wording. Peer with the `parse_byte_size` `"064MiB"`
5027        // case and the `parse_duration` `"030s"` case.
5028        let err = parse_millicores("0500m").unwrap_err();
5029        assert!(
5030            matches!(err, LimitsError::LeadingZeroMillicoreMagnitude { ref value } if value == "0500"),
5031            "got {err:?}"
5032        );
5033    }
5034
5035    #[test]
5036    fn parse_millicores_rejects_multi_digit_zero_magnitude_with_suffix() {
5037        // The multi-zero pin on the `m`-suffix path: `"00m"` parses to 0
5038        // millicores at the codec, but the renderer emits `"0m"` on the
5039        // next serialize — the single canonical zero form on this axis.
5040        // The leading-zero arm rejects multi-byte leading-zero shapes
5041        // even when the value is zero; the single-byte `"0m"` /
5042        // bare-`"0"` stays in the codec's accepted set per the boundary
5043        // pin below. Peer with the `parse_byte_size` `"00MiB"` case and
5044        // the `parse_duration` `"00s"` case.
5045        let err = parse_millicores("00m").unwrap_err();
5046        assert!(
5047            matches!(err, LimitsError::LeadingZeroMillicoreMagnitude { ref value } if value == "00"),
5048            "got {err:?}"
5049        );
5050    }
5051
5052    #[test]
5053    fn parse_millicores_rejects_leading_zero_bare_core() {
5054        // The leading-zero pin on the bare-core path: `"02"` parsed to
5055        // 2 cores → 2000 millicores at the codec, but `render_millicores`
5056        // emits `"2000m"` on the next serialize — canonical-form drift.
5057        // The bare-core shorthand carries the same leading-zero discipline
5058        // as the `m`-suffix path; both authoring paths converge to the
5059        // same gate. Peer with the `parse_byte_size` bare-integer
5060        // `"01024"` case.
5061        let err = parse_millicores("02").unwrap_err();
5062        assert!(
5063            matches!(err, LimitsError::LeadingZeroMillicoreMagnitude { ref value } if value == "02"),
5064            "got {err:?}"
5065        );
5066    }
5067
5068    #[test]
5069    fn parse_millicores_rejects_leading_zero_multi_digit_with_suffix() {
5070        // The multi-digit leading-zero pin on the `m`-suffix path:
5071        // `"01500m"` parses to 1500 millicores at the codec, but the
5072        // renderer emits `"1500m"` on the next serialize — canonical-form
5073        // drift on a non-zero magnitude. Sweeps a different magnitude
5074        // shape than the `"0500m"` case so a future tightening that
5075        // misses the multi-digit-leading-zero class surfaces here.
5076        let err = parse_millicores("01500m").unwrap_err();
5077        assert!(
5078            matches!(err, LimitsError::LeadingZeroMillicoreMagnitude { ref value } if value == "01500"),
5079            "got {err:?}"
5080        );
5081    }
5082
5083    #[test]
5084    fn parse_millicores_accepts_single_zero_magnitude_at_codec_layer() {
5085        // The codec-layer / typed-validate-layer boundary pin: the
5086        // single-byte magnitude `"0"` (bare) and `"0m"` (with suffix)
5087        // round-trip losslessly through `render_millicores` (which
5088        // emits `"0m"` for 0 millicores), so they stay in the codec's
5089        // accepted set. The downstream `CpuZero` gate refuses
5090        // semantic-zero authoring at the typed-validate layer above —
5091        // the diagnostic partitioning between canonical-form drift
5092        // (the leading-zero arm) and semantic-zero (the `CpuZero` gate)
5093        // remains stable. Same codec-layer / typed-validate-layer
5094        // partition the peer codecs preserve.
5095        assert_eq!(parse_millicores("0").unwrap(), 0);
5096        assert_eq!(parse_millicores("0m").unwrap(), 0);
5097    }
5098
5099    #[test]
5100    fn parse_millicores_accepts_canonical_magnitude_with_leading_one() {
5101        // The complement-side pin: every canonical leading-`[1-9]`
5102        // magnitude continues to parse cleanly through the leading-zero
5103        // arm, on both the `m`-suffix and bare-core paths. Sweep the
5104        // canonical values the renderer emits across the unit-multiplier
5105        // boundary (sub-core, single-core, multi-core) so a future
5106        // tightening cannot drift into rejecting valid canonical
5107        // magnitudes. Same complement-side discipline the peer
5108        // `parse_byte_size_accepts_canonical_magnitude_with_leading_one`
5109        // and `parse_duration_accepts_canonical_magnitude_with_leading_one`
5110        // pins enforce on the sibling codecs.
5111        assert_eq!(parse_millicores("1m").unwrap(), 1);
5112        assert_eq!(parse_millicores("500m").unwrap(), 500);
5113        assert_eq!(parse_millicores("1500m").unwrap(), 1500);
5114        assert_eq!(parse_millicores("9000m").unwrap(), 9000);
5115        assert_eq!(parse_millicores("1").unwrap(), 1000);
5116        assert_eq!(parse_millicores("2").unwrap(), 2000);
5117        assert_eq!(parse_millicores("9").unwrap(), 9000);
5118    }
5119
5120    #[test]
5121    fn de_millicores_rejects_leading_zero_through_serde() {
5122        // The serde-path pin: a `:limits :cpu` carrying a leading-zero
5123        // magnitude (`"0500m"`) must fail at deserialize time, not
5124        // silently round-trip the value through `u32::from_str`'s
5125        // leading-zero-permissive accepting. Pin both the success-on-
5126        // canonical path (the leading-zero-stripped form deserializes
5127        // cleanly) and the failure-on-non-canonical path (the leading-
5128        // zero form is rejected by the codec before any validate gate
5129        // runs). Peer with the
5130        // `de_byte_size_rejects_leading_zero_through_serde` and
5131        // `de_duration_rejects_leading_zero_through_serde` pins on the
5132        // sibling codecs.
5133        let json = r#"{"cpu":"0500m"}"#;
5134        let err = serde_json::from_str::<LimitsSpec>(json).unwrap_err();
5135        let msg = err.to_string();
5136        assert!(
5137            msg.contains("leading zero"),
5138            "serde diagnostic must surface the leading-zero reason verbatim \
5139             (got {msg:?})"
5140        );
5141
5142        // The integer-form complement — same author-side intent
5143        // (500 millicores), written in the canonical form the renderer
5144        // would emit, deserializes cleanly.
5145        let json = r#"{"cpu":"500m"}"#;
5146        let l: LimitsSpec = serde_json::from_str(json).unwrap();
5147        assert_eq!(l.cpu, Some(500));
5148    }
5149
5150    // ── canonical-form: whitespace-rejection millicores codec gate ────────
5151    //
5152    // Direct successor to the `parse_byte_size` (24a8ad4), `parse_duration`
5153    // (ebc3a75), `supervisor::duration_codec` (a7ae622), and
5154    // `rate_limit_codec` (1ad7755) whitespace-rejection arms — closes the
5155    // fifth (and last) typed-magnitude codec surface in caixa-core on the
5156    // ASCII-whitespace axis. The pre-gate top-level `s.trim()` at parse
5157    // entry and the per-part `magnitude.trim()` calls silently ate leading
5158    // / trailing / internal whitespace, so every whitespace-carrying shape
5159    // parsed to the same millicore value and round-tripped through
5160    // `render_millicores` to a *different* canonical string on next
5161    // serialize — the same canonical-form-drift class the leading-`+` /
5162    // fractional / leading-zero arms already close on this codec.
5163
5164    #[test]
5165    fn parse_millicores_rejects_leading_whitespace() {
5166        // `" 500m"` — the canonical paste-from-aligned-doc / YAML-quoted-
5167        // plain-scalar footgun. Before this gate the top-level `s.trim()`
5168        // at parse entry silently ate the leading space and parsed the
5169        // value to 500 millicores, round-tripping to `"500m"` on next
5170        // serialize.
5171        let err = parse_millicores(" 500m").unwrap_err();
5172        assert!(
5173            matches!(err, LimitsError::WhitespaceInMillicores { ref value, byte } if value == " 500m" && byte == 0x20),
5174            "got {err:?}"
5175        );
5176        let msg = err.to_string();
5177        assert!(
5178            msg.contains("whitespace byte 0x20"),
5179            "diagnostic must surface the offending byte verbatim (got {msg:?})"
5180        );
5181        assert!(
5182            msg.contains("THEORY.md"),
5183            "diagnostic must cite the render-determinism contract (got {msg:?})"
5184        );
5185    }
5186
5187    #[test]
5188    fn parse_millicores_rejects_trailing_whitespace() {
5189        // `"500m "` — the canonical shell-history trailing-space footgun.
5190        let err = parse_millicores("500m ").unwrap_err();
5191        assert!(
5192            matches!(err, LimitsError::WhitespaceInMillicores { ref value, byte } if value == "500m " && byte == 0x20),
5193            "got {err:?}"
5194        );
5195    }
5196
5197    #[test]
5198    fn parse_millicores_rejects_internal_whitespace_between_magnitude_and_unit() {
5199        // `"500 m"` — the typographically-spaced author shape (the same
5200        // idiom every prose reference to millicores renders as). Before
5201        // this gate the per-part `magnitude.trim()` silently ate the
5202        // internal space and parsed the value to 500 millicores.
5203        let err = parse_millicores("500 m").unwrap_err();
5204        assert!(
5205            matches!(err, LimitsError::WhitespaceInMillicores { ref value, byte } if value == "500 m" && byte == 0x20),
5206            "got {err:?}"
5207        );
5208    }
5209
5210    #[test]
5211    fn parse_millicores_rejects_tab_byte() {
5212        // `"\t500m"` — the paste-from-indented-doc / YAML-block-scalar tab
5213        // footgun. Pins the tab (`0x09`) arm alongside the space arm above.
5214        let err = parse_millicores("\t500m").unwrap_err();
5215        assert!(
5216            matches!(err, LimitsError::WhitespaceInMillicores { ref value, byte } if value == "\t500m" && byte == 0x09),
5217            "got {err:?}"
5218        );
5219    }
5220
5221    #[test]
5222    fn parse_millicores_rejects_trailing_newline() {
5223        // `"500m\n"` — the multi-line-paste footgun where a trailing LF
5224        // byte survives the paste. Pins the LF member (`0x0A`) of the
5225        // `is_ascii_whitespace` set.
5226        let err = parse_millicores("500m\n").unwrap_err();
5227        assert!(
5228            matches!(err, LimitsError::WhitespaceInMillicores { ref value, byte } if value == "500m\n" && byte == 0x0a),
5229            "got {err:?}"
5230        );
5231    }
5232
5233    #[test]
5234    fn parse_millicores_accepts_whitespace_free_canonical_forms() {
5235        // The complement-side pin: every canonical whitespace-free
5236        // authoring form the renderer emits stays accepted post-gate.
5237        // Sweep the canonical `m`-suffix path plus the bare-core shorthand
5238        // so a future tightening of the whitespace arm that over-fires on
5239        // the accepted set surfaces here as a test failure.
5240        assert_eq!(parse_millicores("500m").unwrap(), 500);
5241        assert_eq!(parse_millicores("2000m").unwrap(), 2000);
5242        assert_eq!(parse_millicores("1m").unwrap(), 1);
5243        assert_eq!(parse_millicores("0m").unwrap(), 0);
5244        assert_eq!(parse_millicores("2").unwrap(), 2000);
5245        assert_eq!(parse_millicores("0").unwrap(), 0);
5246    }
5247
5248    #[test]
5249    fn de_millicores_rejects_whitespace_through_serde() {
5250        // The serde-path pin: a `:limits :cpu` carrying a whitespace-byte-
5251        // carrying value (`" 500m"`) must fail at deserialize time, not
5252        // silently round-trip the value through the pre-existing top-level
5253        // `s.trim()`. Peer with the
5254        // `de_byte_size_rejects_whitespace_through_serde` and
5255        // `de_duration_rejects_whitespace_through_serde` pins on the
5256        // sibling codecs.
5257        let json = r#"{"cpu":" 500m"}"#;
5258        let err = serde_json::from_str::<LimitsSpec>(json).unwrap_err();
5259        let msg = err.to_string();
5260        assert!(
5261            msg.contains("whitespace byte"),
5262            "serde diagnostic must surface the whitespace reason verbatim (got {msg:?})"
5263        );
5264        assert!(
5265            msg.contains("0x20"),
5266            "serde diagnostic must name the offending byte (got {msg:?})"
5267        );
5268
5269        // The whitespace-free complement — same author-side intent,
5270        // written in the canonical form the renderer would emit,
5271        // deserializes cleanly.
5272        let json = r#"{"cpu":"500m"}"#;
5273        let l: LimitsSpec = serde_json::from_str(json).unwrap();
5274        assert_eq!(l.cpu, Some(500));
5275    }
5276
5277    // ── canonical-form: non-ASCII Unicode `White_Space` millicores gate ───
5278    //
5279    // Direct successor to the ASCII-whitespace arm above — closes the
5280    // strictly-complementary class the byte-scan cannot see. `str::trim`
5281    // uses `char::is_whitespace` (Unicode `White_Space`, strictly wider
5282    // than the ASCII byte set); a leading / trailing / internal NBSP
5283    // (`\u{00A0}`) / LINE SEPARATOR (`\u{2028}`) / EM-SPACE (`\u{2003}`)
5284    // survives the byte-scan but is silently stripped by the top-level
5285    // trim, drifting to canonical `"500m"` on round-trip. Pins the arm
5286    // through the lifted [`crate::render::find_non_ascii_whitespace_char`]
5287    // predicate — the same shared predicate 1b75b38 landed on the four
5288    // peer typed-magnitude codecs, extended here to the fifth.
5289
5290    #[test]
5291    fn parse_millicores_rejects_leading_nbsp() {
5292        // NBSP (`\u{00A0}` = UTF-8 `0xC2 0xA0`) — the paste-from-typography
5293        // / paste-from-word-processor footgun. Before this arm landed the
5294        // byte-scan missed it (neither `0xC2` nor `0xA0` is
5295        // `is_ascii_whitespace`) and `str::trim` at parse entry silently
5296        // stripped it, yielding the same 500 millicores as the whitespace-
5297        // free canonical form and drifting to `"500m"` on next serialize.
5298        let s = "\u{00A0}500m";
5299        let err = parse_millicores(s).unwrap_err();
5300        assert!(
5301            matches!(err, LimitsError::NonAsciiWhitespaceInMillicores { ref value, ch, codepoint } if value == s && ch == '\u{00A0}' && codepoint == 0x00A0),
5302            "got {err:?}"
5303        );
5304        let msg = err.to_string();
5305        assert!(
5306            msg.contains("U+00A0"),
5307            "diagnostic must surface the codepoint verbatim (got {msg:?})"
5308        );
5309        assert!(
5310            msg.contains("THEORY.md"),
5311            "diagnostic must cite the render-determinism contract (got {msg:?})"
5312        );
5313    }
5314
5315    #[test]
5316    fn parse_millicores_rejects_internal_em_space() {
5317        // EM-SPACE (`\u{2003}`) between magnitude and unit — pins the arm
5318        // on an internal-position non-NBSP Unicode `White_Space` member.
5319        let s = "500\u{2003}m";
5320        let err = parse_millicores(s).unwrap_err();
5321        assert!(
5322            matches!(err, LimitsError::NonAsciiWhitespaceInMillicores { ref value, ch, codepoint } if value == s && ch == '\u{2003}' && codepoint == 0x2003),
5323            "got {err:?}"
5324        );
5325    }
5326
5327    #[test]
5328    fn parse_millicores_rejects_trailing_line_separator() {
5329        // LINE SEPARATOR (`\u{2028}`) — the canonical paste-from-web-doc
5330        // footgun (many rendering engines insert `\u{2028}` at soft-wrap
5331        // boundaries in RTF/HTML → plain text conversion). Pins the arm on
5332        // a trailing-position Unicode `White_Space` member.
5333        let s = "500m\u{2028}";
5334        let err = parse_millicores(s).unwrap_err();
5335        assert!(
5336            matches!(err, LimitsError::NonAsciiWhitespaceInMillicores { ref value, ch, codepoint } if value == s && ch == '\u{2028}' && codepoint == 0x2028),
5337            "got {err:?}"
5338        );
5339    }
5340
5341    #[test]
5342    fn parse_millicores_accepts_ascii_only_canonical_forms_after_unicode_arm() {
5343        // Positive-control pin: every ASCII-only canonical form the
5344        // renderer emits stays accepted through the new arm — the lifted
5345        // predicate is a strict no-op on ASCII input.
5346        assert_eq!(parse_millicores("500m").unwrap(), 500);
5347        assert_eq!(parse_millicores("2000m").unwrap(), 2000);
5348        assert_eq!(parse_millicores("1m").unwrap(), 1);
5349        assert_eq!(parse_millicores("2").unwrap(), 2000);
5350    }
5351
5352    // ── canonical-form: integer-millisecond :wall-clock gate ──────────────
5353    //
5354    // The peer typed-`Duration` axes routed through
5355    // `supervisor::duration_codec` (`:politicas :timeout` a4ae535,
5356    // `:circuit-breaker :window` a4ae535) already gate on
5357    // `is_integer_millisecond_duration` because the codec's `render`
5358    // truncates to `as_millis()` and parses with integer-ms granularity;
5359    // this crate's in-module `render_duration` / `parse_duration` pair
5360    // carries the same `as_millis()`-truncation shape, so the same sub-
5361    // millisecond-residue footgun lived on this axis until this gate
5362    // landed. The tests below pin the fail-before-pass-after boundary,
5363    // the diagnostic shape, the cross-arm zero-then-canonical ordering
5364    // matching the `:politicas` peer, the integer-ms happy-path sweep,
5365    // and the codec round-trip property (every validated `wall_clock`
5366    // survives serialize → deserialize equality).
5367
5368    #[test]
5369    fn validate_rejects_sub_millisecond_wall_clock() {
5370        // The fail-before-pass-after pin: a programmatic
5371        // `Duration::from_micros(1500)` (= 1_500_000 ns) silently passed
5372        // validate on every pre-gate codebase, then truncated to
5373        // `as_millis() == 1` on first serialize — `render_duration`
5374        // emits `"1ms"`, the codec parses it back to
5375        // `Duration::from_millis(1)` = 1_000_000 ns, the typed
5376        // `wall_clock` no longer matches its rendered form.
5377        let l = LimitsSpec {
5378            wall_clock: Some(Duration::from_micros(1500)),
5379            ..Default::default()
5380        };
5381        match l.validate().unwrap_err() {
5382            LimitsError::WallClockNotCanonical { wall_clock } => {
5383                assert_eq!(wall_clock, Duration::from_micros(1500));
5384            }
5385            other => panic!("expected WallClockNotCanonical, got {other:?}"),
5386        }
5387    }
5388
5389    #[test]
5390    fn validate_rejects_one_nanosecond_wall_clock() {
5391        // The far-sub-ms case: `Duration::from_nanos(1)` is non-zero
5392        // (so `WallClockZero` doesn't fire) but `as_millis() == 0`, so
5393        // `render_duration` emits the literal `"0s"` — the next serde
5394        // round-trip would parse back to `Duration::ZERO`, which the
5395        // `WallClockZero` arm then rejects on re-validate. The
5396        // canonical-form gate at this layer surfaces a self-locating
5397        // diagnostic naming the offending Duration verbatim rather
5398        // than a downstream `WallClockZero` whose remediation points
5399        // at omitting the slot.
5400        let l = LimitsSpec {
5401            wall_clock: Some(Duration::from_nanos(1)),
5402            ..Default::default()
5403        };
5404        match l.validate().unwrap_err() {
5405            LimitsError::WallClockNotCanonical { wall_clock } => {
5406                assert_eq!(wall_clock, Duration::from_nanos(1));
5407            }
5408            other => panic!("expected WallClockNotCanonical, got {other:?}"),
5409        }
5410    }
5411
5412    #[test]
5413    fn validate_rejects_nanosecond_past_canonical_boundary() {
5414        // The 1-ns-past-1ms boundary case: a `Duration` carrying
5415        // 1_000_001 ns is structurally past the integer-ms granularity
5416        // floor — `subsec_nanos() % 1_000_000 == 1`. The codec
5417        // round-trip would truncate to `1ms` and the consumer would
5418        // observe a 1-ns drift on every emit. Same boundary the peer
5419        // `is_integer_millisecond_duration_predicate_tracks_codec` test
5420        // in aplicacao.rs pins for the `:politicas` axes.
5421        let w = Duration::from_nanos(1_000_001);
5422        let l = LimitsSpec {
5423            wall_clock: Some(w),
5424            ..Default::default()
5425        };
5426        assert_eq!(
5427            l.validate().unwrap_err(),
5428            LimitsError::WallClockNotCanonical { wall_clock: w }
5429        );
5430    }
5431
5432    #[test]
5433    fn validate_accepts_integer_millisecond_wall_clock_values() {
5434        // The positive-control sweep: every `Duration` the codec can
5435        // round-trip losslessly — the canonical `<integer>{ms,s,m,h}`
5436        // set the `render_duration` / `parse_duration` pair emits and
5437        // accepts — passes `validate` without surfacing the new
5438        // canonical-form arm. Mirrors
5439        // `accepts_policy_retries_typical_values` /
5440        // `accepts_circuit_breaker_max_failures_typical_values` on
5441        // sibling axes.
5442        for w in [
5443            Duration::from_millis(1),
5444            Duration::from_millis(500),
5445            Duration::from_millis(1500),
5446            Duration::from_secs(1),
5447            Duration::from_secs(30),
5448            Duration::from_secs(60),
5449            Duration::from_secs(120),
5450            Duration::from_secs(3600),
5451        ] {
5452            let l = LimitsSpec {
5453                wall_clock: Some(w),
5454                ..Default::default()
5455            };
5456            l.validate()
5457                .unwrap_or_else(|e| panic!("integer-ms {w:?} must validate, got {e:?}"));
5458        }
5459    }
5460
5461    #[test]
5462    fn validate_wall_clock_zero_takes_precedence_over_canonical_gate() {
5463        // Cross-arm ordering pin: `Duration::ZERO` has
5464        // `subsec_nanos() == 0` and would otherwise pass the
5465        // canonical-form arm — the zero-floor arm must fire first so
5466        // the more self-locating `WallClockZero` diagnostic (with its
5467        // omit-axis remediation directly named) leads. Same posture
5468        // every peer zero-then-shape gate uses
5469        // (`PolicyTimeoutZero` → `PolicyTimeoutNotCanonical`,
5470        // `PolicyBreakerZeroWindow` → `PolicyBreakerWindowNotCanonical`).
5471        let l = LimitsSpec {
5472            wall_clock: Some(Duration::ZERO),
5473            ..Default::default()
5474        };
5475        assert_eq!(l.validate().unwrap_err(), LimitsError::WallClockZero);
5476    }
5477
5478    #[test]
5479    fn wall_clock_canonical_diagnostic_carries_offending_duration() {
5480        // Diagnostic-shape pin: the canonical-form arm names the
5481        // offending `Duration` verbatim so the author's grep lands on
5482        // the field's value, not a generic "duration not canonical"
5483        // message. Same shape every other typed-cap arm on this
5484        // surface carries (`MemoryExceedsWasm32Cap` carries the
5485        // offending byte count verbatim, `PolicyRetriesExceedsCap`
5486        // carries the offending retry count verbatim,
5487        // `PolicyBreakerMaxFailuresExceedsCap` carries the offending
5488        // u32 verbatim).
5489        let w = Duration::from_micros(500);
5490        let l = LimitsSpec {
5491            wall_clock: Some(w),
5492            ..Default::default()
5493        };
5494        let err = l.validate().unwrap_err();
5495        let msg = err.to_string();
5496        assert!(
5497            msg.contains("500"),
5498            "diagnostic must carry the offending magnitude verbatim (got {msg:?})"
5499        );
5500    }
5501
5502    #[test]
5503    fn wall_clock_validated_value_round_trips_through_codec() {
5504        // The structural property the canonical-ms gate enforces:
5505        // every `LimitsSpec::wall_clock` past `LimitsSpec::validate`
5506        // round-trips losslessly through the in-module duration codec
5507        // (serialize → string → deserialize → equal value). Pin this
5508        // end-to-end so a future change to either side (the validate
5509        // gate's accepted granularity, the codec's parse/render unit
5510        // set) that breaks the alignment surfaces here. Peer of
5511        // `policy_timeout_validated_value_round_trips_through_codec` /
5512        // `circuit_breaker_window_validated_value_round_trips_through_codec`
5513        // on the sibling `:politicas` axes.
5514        for w in [
5515            Duration::from_millis(1),
5516            Duration::from_millis(1500),
5517            Duration::from_secs(30),
5518            Duration::from_secs(3600),
5519        ] {
5520            let l = LimitsSpec {
5521                wall_clock: Some(w),
5522                ..Default::default()
5523            };
5524            l.validate().unwrap();
5525            let json = serde_json::to_string(&l).unwrap();
5526            let back: LimitsSpec = serde_json::from_str(&json).unwrap();
5527            assert_eq!(
5528                back.wall_clock, l.wall_clock,
5529                "every validated :wall-clock must round-trip losslessly through the codec"
5530            );
5531        }
5532    }
5533
5534    // ── value-shape: :wall-clock upper bound — 1h ceiling ──────────────────
5535    //
5536    // The third typed-`Duration` axis brought to the uniform top edge
5537    // `LIMITS_WALL_CLOCK_MAX` = 1h established by the prior cap lifts
5538    // on `:politicas :timeout` (POLICY_TIMEOUT_MAX) and
5539    // `:politicas :circuit-breaker :window` (POLICY_BREAKER_WINDOW_MAX).
5540    // Mirrors the test discipline those peers carry: the
5541    // fail-before-pass-after pin, the 1ms-boundary pin, the
5542    // far-above-cap sweep (24h / 7d / ~11.5d — the values a
5543    // `(:wall-clock "24h")` typo or copy-paste typically lands), the
5544    // inclusive-at-cap positive control, the production-band positive-
5545    // control sweep, the cross-arm zero-then-cap and
5546    // canonical-then-cap ordering pins, the diagnostic-shape pin
5547    // carrying the offending `Duration` verbatim, and the cap-value
5548    // literal-identity + codec-round-trip pins anchoring the constant
5549    // to the codec's largest emitted unit and to its peer constants.
5550
5551    #[test]
5552    fn validate_rejects_wall_clock_above_cap() {
5553        // The fail-before-pass-after pin: 3601s = 1h + 1s is
5554        // structurally one canonical-tick past the
5555        // [`LIMITS_WALL_CLOCK_MAX`] ceiling (1h = 3600s) — an
5556        // integer-millisecond magnitude the canonical-form arm above
5557        // accepts cleanly, that the in-module duration codec
5558        // round-trips losslessly as `"3601s"`, and that silently
5559        // passed validate on every pre-gate codebase because the typed
5560        // slot's only checks were the zero-floor and canonical-form
5561        // arms. The wasm-engine consuming the value (the M2.5
5562        // `wasm-engine`'s epoch-deadline cancellation hook, the future
5563        // caixa-helm `pleme-computeunit` chart's `:limits` value
5564        // mapping) reaches for a `Duration` so long no realistic
5565        // synchronous wasm call hits it, far from the source
5566        // caixa.lisp.
5567        let w = LIMITS_WALL_CLOCK_MAX + Duration::from_secs(1);
5568        let l = LimitsSpec {
5569            wall_clock: Some(w),
5570            ..Default::default()
5571        };
5572        assert_eq!(
5573            l.validate().unwrap_err(),
5574            LimitsError::WallClockExceedsCap { wall_clock: w }
5575        );
5576    }
5577
5578    #[test]
5579    fn validate_rejects_wall_clock_one_millisecond_above_cap() {
5580        // Boundary case: exactly 1ms past the cap (the granularity the
5581        // canonical-form gate enforces). Catches a future "strictly
5582        // less than" half-measure and pins the diagnostic to name the
5583        // offending `Duration` verbatim. Peer of
5584        // `rejects_policy_timeout_one_millisecond_above_cap` /
5585        // `rejects_circuit_breaker_window_one_millisecond_above_cap`
5586        // on the sibling typed-`Duration` axes' top edges.
5587        let w = LIMITS_WALL_CLOCK_MAX + Duration::from_millis(1);
5588        let l = LimitsSpec {
5589            wall_clock: Some(w),
5590            ..Default::default()
5591        };
5592        assert_eq!(
5593            l.validate().unwrap_err(),
5594            LimitsError::WallClockExceedsCap { wall_clock: w }
5595        );
5596    }
5597
5598    #[test]
5599    fn validate_rejects_wall_clock_far_above_cap() {
5600        // The "obvious authoring footgun" case: a `(:wall-clock "24h")`
5601        // or `(:wall-clock "7d")` — values the canonical-form arm
5602        // accepts as integer-millisecond magnitudes, the codec
5603        // round-trips losslessly through serde, but the wasm-engine
5604        // cannot honor as a meaningful per-call deadline. Until this
5605        // gate landed validate accepted them. Pin the common
5606        // above-cap values (24h, 7d, ~11.5d) so a future relaxation
5607        // that drops the upper bound surfaces here.
5608        for w in [
5609            Duration::from_secs(86_400),    // 24h
5610            Duration::from_secs(604_800),   // 7d
5611            Duration::from_secs(1_000_000), // ~11.5 days
5612        ] {
5613            let l = LimitsSpec {
5614                wall_clock: Some(w),
5615                ..Default::default()
5616            };
5617            assert_eq!(
5618                l.validate().unwrap_err(),
5619                LimitsError::WallClockExceedsCap { wall_clock: w }
5620            );
5621        }
5622    }
5623
5624    #[test]
5625    fn validate_accepts_wall_clock_at_cap() {
5626        // The boundary value — exactly [`LIMITS_WALL_CLOCK_MAX`] (1h)
5627        // — must validate. The cap is inclusive on the top edge,
5628        // matching the [`crate::POLICY_TIMEOUT_MAX`] /
5629        // [`crate::POLICY_BREAKER_WINDOW_MAX`] /
5630        // [`LIMITS_MEMORY_WASM32_MAX_BYTES`] discipline on the sibling
5631        // capped axes. Pin the boundary explicitly so a future
5632        // off-by-one tightening (`>= LIMITS_WALL_CLOCK_MAX` instead of
5633        // `>`) surfaces here as a test failure rather than a silent
5634        // contract narrowing.
5635        let l = LimitsSpec {
5636            wall_clock: Some(LIMITS_WALL_CLOCK_MAX),
5637            ..Default::default()
5638        };
5639        l.validate()
5640            .expect("wall_clock == LIMITS_WALL_CLOCK_MAX must validate");
5641    }
5642
5643    #[test]
5644    fn validate_accepts_wall_clock_typical_values() {
5645        // The documented per-request production-playbook band positive-
5646        // control sweep — every value Envoy / Istio / Linkerd / AWS
5647        // App Mesh / Kubernetes ingress-nginx recommend
5648        // (1ms..=3600s) must pass, plus a sweep through the
5649        // long-running-workflow band (5m, 15m, 30m, 1h) the cap
5650        // accepts. Mirrors `accepts_policy_timeout_typical_values` on
5651        // the sibling `:politicas :timeout` axis.
5652        for w in [
5653            Duration::from_millis(1),
5654            Duration::from_millis(500),
5655            Duration::from_secs(1),
5656            Duration::from_secs(10),
5657            Duration::from_secs(15), // Envoy default
5658            Duration::from_secs(30),
5659            Duration::from_secs(60),  // AWS App Mesh typical
5660            Duration::from_secs(300), // 5m
5661            Duration::from_secs(900), // 15m
5662            Duration::from_secs(1800),
5663            Duration::from_secs(3600), // exactly 1h, the cap
5664        ] {
5665            let l = LimitsSpec {
5666                wall_clock: Some(w),
5667                ..Default::default()
5668            };
5669            l.validate()
5670                .unwrap_or_else(|e| panic!("wall_clock={w:?} must validate; got {e:?}"));
5671        }
5672    }
5673
5674    #[test]
5675    fn wall_clock_zero_takes_precedence_over_cap() {
5676        // The cross-arm ordering pin: `Duration::ZERO` is structurally
5677        // outside both `>= 1ms` (zero-floor) and `<= LIMITS_WALL_CLOCK_MAX`
5678        // (cap), but the zero-floor diagnostic is the more
5679        // self-locating one (it directly names the omit-axis
5680        // remediation), so the validate gate must fire on zero first.
5681        // Same shape every other zero-then-shape ordering on this
5682        // surface uses (`MemoryZero` then `MemoryExceedsWasm32Cap`,
5683        // `PolicyTimeoutZero` then `PolicyTimeoutExceedsCap`).
5684        let l = LimitsSpec {
5685            wall_clock: Some(Duration::ZERO),
5686            ..Default::default()
5687        };
5688        assert_eq!(
5689            l.validate().unwrap_err(),
5690            LimitsError::WallClockZero,
5691            "Duration::ZERO must surface the zero-floor diagnostic, not the cap diagnostic"
5692        );
5693    }
5694
5695    #[test]
5696    fn wall_clock_canonical_takes_precedence_over_cap() {
5697        // The cross-arm ordering pin: a `Duration` that is *both*
5698        // sub-millisecond (non-canonical-form) and structurally above
5699        // the cap surfaces the canonical-form diagnostic first,
5700        // because the round-trip-shape break is the more fundamental
5701        // issue (the value can't even round-trip through the codec, so
5702        // the cap diagnostic naming `1ms..=1h` would be misleading —
5703        // there's no integer-ms form of the offending value). Pin the
5704        // order so a future refactor that reorders the arms surfaces
5705        // here as a test failure rather than a silent diagnostic
5706        // regression. Peer of
5707        // `policy_timeout_canonical_takes_precedence_over_cap`.
5708        let w = LIMITS_WALL_CLOCK_MAX + Duration::from_nanos(1);
5709        let l = LimitsSpec {
5710            wall_clock: Some(w),
5711            ..Default::default()
5712        };
5713        assert_eq!(
5714            l.validate().unwrap_err(),
5715            LimitsError::WallClockNotCanonical { wall_clock: w },
5716            "sub-ms above-cap value must surface the canonical-form diagnostic, not the cap diagnostic"
5717        );
5718    }
5719
5720    #[test]
5721    fn wall_clock_cap_diagnostic_carries_offending_value() {
5722        // The diagnostic-shape pin: the offending `Duration` is
5723        // carried verbatim into the
5724        // [`LimitsError::WallClockExceedsCap`] variant so the surfaced
5725        // error message names the value the author wrote, not just
5726        // the cap. Same self-locating diagnostic shape every other
5727        // typed-cap arm on this surface carries
5728        // (`MemoryExceedsWasm32Cap` carries the offending byte count
5729        // verbatim, `PolicyTimeoutExceedsCap` carries the offending
5730        // `Duration` verbatim).
5731        let w = Duration::from_secs(7200); // 2h
5732        let l = LimitsSpec {
5733            wall_clock: Some(w),
5734            ..Default::default()
5735        };
5736        let err = l.validate().unwrap_err();
5737        assert!(
5738            matches!(err, LimitsError::WallClockExceedsCap { wall_clock } if wall_clock == w),
5739            "got {err:?}"
5740        );
5741        let msg = err.to_string();
5742        assert!(
5743            msg.contains("7200"),
5744            ":limits :wall-clock cap diagnostic must carry the offending value verbatim (got: {msg})"
5745        );
5746    }
5747
5748    #[test]
5749    fn wall_clock_cap_pins_canonical_value() {
5750        // The [`LIMITS_WALL_CLOCK_MAX`] constant pins the value at
5751        // exactly 1 hour (3600s = 3_600_000ms) — the largest unit the
5752        // shared duration codec emits as a clean canonical string
5753        // (`"<n>h"`). Pinning the literal value here surfaces a future
5754        // drift (a relaxation to 24h, a tightening to 5m) as a
5755        // deliberate test edit, not a silent contract narrowing.
5756        //
5757        // The three typed-`Duration` caps on the validation surface
5758        // (`LIMITS_WALL_CLOCK_MAX` per-process, `POLICY_TIMEOUT_MAX`
5759        // per-edge, `POLICY_BREAKER_WINDOW_MAX` per-breaker) share a
5760        // single uniform top edge at the codec's largest emitted unit
5761        // — a structural-property invariant the equality assertions
5762        // here enshrine, so a future drift on any of the three
5763        // surfaces as a deliberate test edit. Same shape every other
5764        // typed-cap value pin uses
5765        // (`policy_timeout_cap_pins_canonical_value`,
5766        // `circuit_breaker_window_cap_pins_canonical_value`).
5767        assert_eq!(LIMITS_WALL_CLOCK_MAX, Duration::from_secs(3600));
5768        assert_eq!(LIMITS_WALL_CLOCK_MAX.as_millis(), 3_600_000);
5769        assert_eq!(LIMITS_WALL_CLOCK_MAX, crate::POLICY_TIMEOUT_MAX);
5770        assert_eq!(LIMITS_WALL_CLOCK_MAX, crate::POLICY_BREAKER_WINDOW_MAX);
5771    }
5772
5773    #[test]
5774    fn wall_clock_cap_value_round_trips_through_codec() {
5775        // The codec round-trip property the cap arm preserves: the
5776        // [`LIMITS_WALL_CLOCK_MAX`] constant itself round-trips through
5777        // the in-module duration codec — every value at the cap
5778        // renders to a clean canonical string (`"1h"`) and parses back
5779        // to the same `Duration`. Pin this so a future drift between
5780        // the cap constant and the codec's largest emitted unit
5781        // surfaces here. Same shape every other typed boundary pin on
5782        // this surface uses
5783        // (`wasm32_memory_cap_matches_parsed_4_gib`,
5784        // `policy_timeout_cap_value_round_trips_through_codec`).
5785        let l = LimitsSpec {
5786            wall_clock: Some(LIMITS_WALL_CLOCK_MAX),
5787            ..Default::default()
5788        };
5789        let json = serde_json::to_string(&l).unwrap();
5790        assert!(
5791            json.contains("\"1h\""),
5792            "the LIMITS_WALL_CLOCK_MAX value must render to the canonical \"1h\" form (got: {json})"
5793        );
5794        let back: LimitsSpec = serde_json::from_str(&json).unwrap();
5795        assert_eq!(back.wall_clock, Some(LIMITS_WALL_CLOCK_MAX));
5796        l.validate()
5797            .expect("LIMITS_WALL_CLOCK_MAX itself must pass validate");
5798    }
5799
5800    // ── value-shape: :cpu upper bound — 128-core schedulability ceiling ─────
5801    //
5802    // The third `LimitsSpec` axis brought to a top-edge cap, peer to
5803    // the `:memory` wasm32 ceiling and the `:wall-clock` 1h ceiling.
5804    // Mirrors the test discipline those peers carry: the
5805    // fail-before-pass-after pin, the one-millicore-boundary pin, the
5806    // far-above-cap sweep, the inclusive-at-cap positive control, the
5807    // production-band positive-control sweep, the cross-arm zero-then-
5808    // cap ordering pin, the diagnostic-shape pin carrying the offending
5809    // value verbatim, and the cap-value literal-identity + codec
5810    // round-trip pins anchoring the constant.
5811
5812    #[test]
5813    fn validate_rejects_cpu_above_cap() {
5814        // The fail-before-pass-after pin: 128_001m = 128 cores + 1
5815        // millicore is structurally one canonical-tick past the
5816        // [`LIMITS_CPU_MILLICORES_MAX`] ceiling — a `u32` magnitude the
5817        // millicore codec round-trips losslessly as `"128001m"`, and
5818        // that silently passed validate on every pre-gate codebase
5819        // because the typed slot's only check was the zero-floor arm.
5820        // The Kubernetes scheduler consuming the value (via the
5821        // `pleme-computeunit` chart's `resources.requests.cpu`
5822        // projection) cannot bind the pod to any node, far from the
5823        // source caixa.lisp.
5824        let m = LIMITS_CPU_MILLICORES_MAX + 1;
5825        let l = LimitsSpec {
5826            cpu: Some(m),
5827            ..Default::default()
5828        };
5829        assert_eq!(
5830            l.validate().unwrap_err(),
5831            LimitsError::CpuExceedsCap { millicores: m }
5832        );
5833    }
5834
5835    #[test]
5836    fn validate_rejects_cpu_far_above_cap() {
5837        // The "obvious authoring footgun" case: a `(:cpu "1000000m")`
5838        // (1000 cores) or `(:cpu "4294967295m")` (≈ u32::MAX) — values
5839        // the millicore codec accepts cleanly, the codec round-trips
5840        // losslessly through serde, but the Kubernetes scheduler
5841        // cannot bind to any node. Until this gate landed validate
5842        // accepted them. Pin the common above-cap values (1000 cores,
5843        // 10_000 cores, u32::MAX) so a future relaxation that drops
5844        // the upper bound surfaces here. Peer of
5845        // `validate_rejects_memory_8_gib` /
5846        // `validate_rejects_wall_clock_far_above_cap`.
5847        for m in [1_000_000_u32, 10_000_000, u32::MAX] {
5848            let l = LimitsSpec {
5849                cpu: Some(m),
5850                ..Default::default()
5851            };
5852            assert_eq!(
5853                l.validate().unwrap_err(),
5854                LimitsError::CpuExceedsCap { millicores: m }
5855            );
5856        }
5857    }
5858
5859    #[test]
5860    fn validate_accepts_cpu_at_cap() {
5861        // The boundary value — exactly [`LIMITS_CPU_MILLICORES_MAX`]
5862        // (128 cores = 128_000m) — must validate. The cap is inclusive
5863        // on the top edge, matching the discipline on every sibling
5864        // capped axis ([`LIMITS_MEMORY_WASM32_MAX_BYTES`],
5865        // [`LIMITS_WALL_CLOCK_MAX`], [`crate::POLICY_TIMEOUT_MAX`],
5866        // [`crate::POLICY_BREAKER_WINDOW_MAX`],
5867        // [`crate::POLICY_RATE_LIMIT_MAX`]). Pin the boundary
5868        // explicitly so a future off-by-one tightening
5869        // (`>= LIMITS_CPU_MILLICORES_MAX` instead of `>`) surfaces here
5870        // as a test failure rather than a silent contract narrowing.
5871        let l = LimitsSpec {
5872            cpu: Some(LIMITS_CPU_MILLICORES_MAX),
5873            ..Default::default()
5874        };
5875        l.validate()
5876            .expect("cpu == LIMITS_CPU_MILLICORES_MAX must validate");
5877    }
5878
5879    #[test]
5880    fn validate_accepts_cpu_typical_values() {
5881        // The documented production-playbook band positive-control
5882        // sweep — every value the canonical caixa Servico runs in
5883        // (100m..=2000m) must pass, plus a sweep through the larger
5884        // burstable / multi-component-host band (4000m, 8000m, 16000m,
5885        // 32000m, 64000m, 128000m) the cap accepts. Mirrors
5886        // `accepts_wall_clock_typical_values` on the sibling
5887        // `:wall-clock` axis.
5888        for m in [
5889            1_u32,   // smallest non-zero
5890            100,     // typical small worker
5891            500,     // canonical test default (peer to limits/flux/helm)
5892            1_000,   // 1 core, single-threaded wasm32 saturation
5893            2_000,   // 2 cores
5894            4_000,   // typical burstable
5895            8_000,   // upper realistic per-Servico band
5896            16_000,  // documented heavy-Servico ceiling
5897            32_000,  // wide-node multi-component-host
5898            64_000,  // half the cap
5899            128_000, // exactly at cap
5900        ] {
5901            let l = LimitsSpec {
5902                cpu: Some(m),
5903                ..Default::default()
5904            };
5905            l.validate()
5906                .unwrap_or_else(|e| panic!("cpu={m}m must validate; got {e:?}"));
5907        }
5908    }
5909
5910    #[test]
5911    fn cpu_zero_takes_precedence_over_cap() {
5912        // The cross-arm ordering pin: `Some(0)` is structurally outside
5913        // both `>= 1` (zero-floor) and `<= LIMITS_CPU_MILLICORES_MAX`
5914        // (cap), but the zero-floor diagnostic is the more
5915        // self-locating one (it directly names the omit-axis
5916        // remediation), so the validate gate must fire on zero first.
5917        // Same shape every other zero-then-cap ordering on this surface
5918        // uses (`MemoryZero` then `MemoryExceedsWasm32Cap`,
5919        // `WallClockZero` then `WallClockExceedsCap`).
5920        let l = LimitsSpec {
5921            cpu: Some(0),
5922            ..Default::default()
5923        };
5924        assert_eq!(
5925            l.validate().unwrap_err(),
5926            LimitsError::CpuZero,
5927            "Some(0) must surface the zero-floor diagnostic, not the cap diagnostic"
5928        );
5929    }
5930
5931    #[test]
5932    fn validate_rejects_cpu_cap_after_earlier_axes() {
5933        // Cross-axis ordering: when both an above-cap `:cpu` and an
5934        // earlier-axis violation are present, the earlier axis must
5935        // fire first. The validate sequence is :memory → :fuel →
5936        // :wall-clock → :cpu, so a paired memory-zero + cpu-above-cap
5937        // input surfaces `MemoryZero`, never the cpu-cap diagnostic.
5938        // Pins the canonical axis order so a future refactor that
5939        // reorders the arms surfaces here as a test failure rather
5940        // than a silent diagnostic regression. Peer of
5941        // `validate_rejects_first_zero_axis_deterministically` and
5942        // `validate_rejects_memory_cap_before_other_axes`.
5943        let l = LimitsSpec {
5944            memory: Some(0),
5945            fuel: None,
5946            wall_clock: None,
5947            cpu: Some(LIMITS_CPU_MILLICORES_MAX + 1),
5948        };
5949        assert_eq!(
5950            l.validate().unwrap_err(),
5951            LimitsError::MemoryZero,
5952            "earlier-axis violation must take precedence over later-axis cap violation"
5953        );
5954    }
5955
5956    #[test]
5957    fn cpu_cap_diagnostic_carries_offending_value() {
5958        // The diagnostic-shape pin: the offending millicore count is
5959        // carried verbatim into the [`LimitsError::CpuExceedsCap`]
5960        // variant so the surfaced error message names the value the
5961        // author wrote, not just the cap. Same self-locating
5962        // diagnostic shape every other typed-cap arm on this surface
5963        // carries (`MemoryExceedsWasm32Cap` carries the offending byte
5964        // count verbatim, `WallClockExceedsCap` carries the offending
5965        // `Duration` verbatim).
5966        let m = 256_000_u32; // 256 cores — double the cap
5967        let l = LimitsSpec {
5968            cpu: Some(m),
5969            ..Default::default()
5970        };
5971        let err = l.validate().unwrap_err();
5972        assert!(
5973            matches!(err, LimitsError::CpuExceedsCap { millicores } if millicores == m),
5974            "got {err:?}"
5975        );
5976        let msg = err.to_string();
5977        assert!(
5978            msg.contains("256000"),
5979            ":limits :cpu cap diagnostic must carry the offending value verbatim (got: {msg})"
5980        );
5981    }
5982
5983    #[test]
5984    fn cpu_cap_pins_canonical_value() {
5985        // The [`LIMITS_CPU_MILLICORES_MAX`] constant pins the value at
5986        // exactly 128 cores (128_000 millicores) — the largest
5987        // commercially-common non-metal cloud Kubernetes node vCPU
5988        // count. Pinning the literal value here surfaces a future
5989        // drift (a relaxation to 256 cores, a tightening to 64 cores)
5990        // as a deliberate test edit, not a silent contract narrowing.
5991        // Same shape every other typed-cap value pin uses
5992        // (`wall_clock_cap_pins_canonical_value`,
5993        // `wasm32_memory_cap_matches_parsed_4_gib`).
5994        assert_eq!(LIMITS_CPU_MILLICORES_MAX, 128_000);
5995        assert_eq!(LIMITS_CPU_MILLICORES_MAX, 128 * 1000);
5996    }
5997
5998    #[test]
5999    fn cpu_cap_value_round_trips_through_codec() {
6000        // The codec round-trip property the cap arm preserves: the
6001        // [`LIMITS_CPU_MILLICORES_MAX`] constant itself round-trips
6002        // through the in-module millicore codec — the cap value
6003        // renders to a clean canonical string (`"128000m"`) and parses
6004        // back to the same `u32`. Pin this so a future drift between
6005        // the cap constant and the codec's accepted magnitude surfaces
6006        // here. Same shape every other typed boundary pin on this
6007        // surface uses (`wasm32_memory_cap_matches_parsed_4_gib`,
6008        // `wall_clock_cap_value_round_trips_through_codec`).
6009        let l = LimitsSpec {
6010            cpu: Some(LIMITS_CPU_MILLICORES_MAX),
6011            ..Default::default()
6012        };
6013        let json = serde_json::to_string(&l).unwrap();
6014        assert!(
6015            json.contains("\"128000m\""),
6016            "the LIMITS_CPU_MILLICORES_MAX value must render to the canonical \"128000m\" form (got: {json})"
6017        );
6018        let back: LimitsSpec = serde_json::from_str(&json).unwrap();
6019        assert_eq!(back.cpu, Some(LIMITS_CPU_MILLICORES_MAX));
6020        l.validate()
6021            .expect("LIMITS_CPU_MILLICORES_MAX itself must pass validate");
6022    }
6023
6024    // ── value-shape: :fuel upper bound — 10^12 no-op-budget ceiling ────────
6025    //
6026    // The fourth and final `LimitsSpec` axis brought to a top-edge
6027    // cap, closing the open edge the 857dfcc CPU-cap commit body
6028    // explicitly named: "three of the four axes carry a top-and-bottom
6029    // edge gate; only `:fuel` remains with a zero-floor-only shape."
6030    // Mirrors the test discipline every sibling capped axis carries:
6031    // the fail-before-pass-after pin, the one-instruction-boundary
6032    // pin, the far-above-cap sweep, the inclusive-at-cap positive
6033    // control, the production-band positive-control sweep, the
6034    // cross-arm zero-then-cap ordering pin, the cross-axis
6035    // earlier-then-later precedence pin, the diagnostic-shape pin
6036    // carrying the offending value verbatim, and the cap-value
6037    // literal-identity + codec round-trip pins anchoring the
6038    // constant.
6039
6040    #[test]
6041    fn validate_rejects_fuel_above_cap() {
6042        // The fail-before-pass-after pin: `LIMITS_FUEL_MAX + 1` =
6043        // one wasm-instruction past the structural ceiling — a `u64`
6044        // magnitude the typed slot round-trips losslessly through
6045        // serde, and that silently passed validate on every pre-gate
6046        // codebase because the typed slot's only check was the
6047        // zero-floor arm. The wasm-engine consuming the value (via
6048        // `Store::set_fuel` projection in the M2.5 host runtime)
6049        // accepts the magnitude but the sibling `:wall-clock` 1h cap
6050        // fires before the fuel counter could ever drain — the typed
6051        // `:fuel` slot becomes a no-op budget far from the source
6052        // caixa.lisp.
6053        let f = LIMITS_FUEL_MAX + 1;
6054        let l = LimitsSpec {
6055            fuel: Some(f),
6056            ..Default::default()
6057        };
6058        assert_eq!(
6059            l.validate().unwrap_err(),
6060            LimitsError::FuelExceedsCap { fuel: f }
6061        );
6062    }
6063
6064    #[test]
6065    fn validate_rejects_fuel_far_above_cap() {
6066        // The "obvious authoring footgun" case: a `(:fuel
6067        // 1000000000000000)` (10^15 instructions), a paste-from-binary
6068        // `u64::MAX`, or a hex-literal-confused-for-decimal magnitude
6069        // — values the `u64` slot accepts cleanly, the codec
6070        // round-trips losslessly through serde, but the wasm-engine
6071        // can never honor as a meaningful counter. Until this gate
6072        // landed validate accepted them. Pin the common above-cap
6073        // values (10x cap, 1000x cap, `u64::MAX`) so a future
6074        // relaxation that drops the upper bound surfaces here. Peer
6075        // of `validate_rejects_cpu_far_above_cap` /
6076        // `validate_rejects_memory_8_gib` /
6077        // `validate_rejects_wall_clock_far_above_cap`.
6078        for f in [LIMITS_FUEL_MAX * 10, LIMITS_FUEL_MAX * 1_000, u64::MAX] {
6079            let l = LimitsSpec {
6080                fuel: Some(f),
6081                ..Default::default()
6082            };
6083            assert_eq!(
6084                l.validate().unwrap_err(),
6085                LimitsError::FuelExceedsCap { fuel: f }
6086            );
6087        }
6088    }
6089
6090    #[test]
6091    fn validate_accepts_fuel_at_cap() {
6092        // The boundary value — exactly [`LIMITS_FUEL_MAX`] (10^12
6093        // wasm instructions) — must validate. The cap is inclusive
6094        // on the top edge, matching the discipline on every sibling
6095        // capped axis ([`LIMITS_MEMORY_WASM32_MAX_BYTES`],
6096        // [`LIMITS_WALL_CLOCK_MAX`], [`LIMITS_CPU_MILLICORES_MAX`],
6097        // [`crate::POLICY_TIMEOUT_MAX`],
6098        // [`crate::POLICY_BREAKER_WINDOW_MAX`],
6099        // [`crate::POLICY_RATE_LIMIT_MAX`]). Pin the boundary
6100        // explicitly so a future off-by-one tightening
6101        // (`>= LIMITS_FUEL_MAX` instead of `>`) surfaces here as a
6102        // test failure rather than a silent contract narrowing.
6103        let l = LimitsSpec {
6104            fuel: Some(LIMITS_FUEL_MAX),
6105            ..Default::default()
6106        };
6107        l.validate().expect("fuel == LIMITS_FUEL_MAX must validate");
6108    }
6109
6110    #[test]
6111    fn validate_accepts_fuel_typical_values() {
6112        // The documented production-playbook band positive-control
6113        // sweep — every value the canonical caixa Servico runs in
6114        // (10^6..=10^9 fuel-units) must pass, plus a sweep through
6115        // the larger compute-bound-Servico band (10^10, 10^11) the
6116        // cap accepts. The canonical fixture is `1_000_000` =
6117        // wasmtime's documented `Store::set_fuel(1_000_000)` example.
6118        // Mirrors `validate_accepts_cpu_typical_values` on the
6119        // sibling `:cpu` axis.
6120        for f in [
6121            1_u64,             // smallest non-zero
6122            1_000,             // tiny per-call budget
6123            1_000_000,         // canonical fixture (10^6) — wasmtime book example
6124            10_000_000,        // typical small-Servico (10^7)
6125            100_000_000,       // typical heavier-Servico (10^8)
6126            1_000_000_000,     // 1 billion — upper realistic per-call (10^9)
6127            100_000_000_000,   // 10^11 — heavy compute-bound (10x below cap)
6128            500_000_000_000,   // half the cap
6129            1_000_000_000_000, // exactly at cap (10^12)
6130        ] {
6131            let l = LimitsSpec {
6132                fuel: Some(f),
6133                ..Default::default()
6134            };
6135            l.validate()
6136                .unwrap_or_else(|e| panic!("fuel={f} must validate; got {e:?}"));
6137        }
6138    }
6139
6140    #[test]
6141    fn fuel_zero_takes_precedence_over_cap() {
6142        // The cross-arm ordering pin: `Some(0)` is structurally
6143        // outside both `>= 1` (zero-floor) and `<= LIMITS_FUEL_MAX`
6144        // (cap), but the zero-floor diagnostic is the more
6145        // self-locating one (it directly names the omit-axis
6146        // remediation and the wasmtime-traps-at-zero semantics), so
6147        // the validate gate must fire on zero first. Same shape every
6148        // other zero-then-cap ordering on this surface uses
6149        // (`MemoryZero` then `MemoryExceedsWasm32Cap`,
6150        // `WallClockZero` then `WallClockExceedsCap`, `CpuZero` then
6151        // `CpuExceedsCap`).
6152        let l = LimitsSpec {
6153            fuel: Some(0),
6154            ..Default::default()
6155        };
6156        assert_eq!(
6157            l.validate().unwrap_err(),
6158            LimitsError::FuelZero,
6159            "Some(0) must surface the zero-floor diagnostic, not the cap diagnostic"
6160        );
6161    }
6162
6163    #[test]
6164    fn validate_rejects_fuel_cap_after_earlier_axes() {
6165        // Cross-axis ordering: when both an above-cap `:fuel` and an
6166        // earlier-axis violation are present, the earlier axis must
6167        // fire first. The validate sequence is :memory → :fuel →
6168        // :wall-clock → :cpu, so a paired memory-zero + fuel-above-
6169        // cap input surfaces `MemoryZero`, never the fuel-cap
6170        // diagnostic. Pins the canonical axis order so a future
6171        // refactor that reorders the arms surfaces here as a test
6172        // failure rather than a silent diagnostic regression. Peer
6173        // of `validate_rejects_cpu_cap_after_earlier_axes`.
6174        let l = LimitsSpec {
6175            memory: Some(0),
6176            fuel: Some(LIMITS_FUEL_MAX + 1),
6177            wall_clock: None,
6178            cpu: None,
6179        };
6180        assert_eq!(
6181            l.validate().unwrap_err(),
6182            LimitsError::MemoryZero,
6183            "earlier-axis violation must take precedence over later-axis cap violation"
6184        );
6185    }
6186
6187    #[test]
6188    fn validate_rejects_fuel_cap_before_later_axes() {
6189        // Cross-axis ordering on the other side: when both an
6190        // above-cap `:fuel` and a later-axis violation are present,
6191        // the `:fuel` cap must fire before the `:wall-clock` /
6192        // `:cpu` zero-floor diagnostics. The validate sequence is
6193        // :memory → :fuel → :wall-clock → :cpu, so a paired
6194        // fuel-above-cap + wall-clock-zero input surfaces
6195        // `FuelExceedsCap`, not `WallClockZero`. Pins the canonical
6196        // axis order on the new arm's downstream side, peer to the
6197        // upstream pin `validate_rejects_fuel_cap_after_earlier_axes`.
6198        let l = LimitsSpec {
6199            memory: None,
6200            fuel: Some(LIMITS_FUEL_MAX + 1),
6201            wall_clock: Some(Duration::ZERO),
6202            cpu: Some(0),
6203        };
6204        assert_eq!(
6205            l.validate().unwrap_err(),
6206            LimitsError::FuelExceedsCap {
6207                fuel: LIMITS_FUEL_MAX + 1
6208            },
6209            ":fuel cap diagnostic must take precedence over later-axis zero-floor diagnostics"
6210        );
6211    }
6212
6213    #[test]
6214    fn fuel_cap_diagnostic_carries_offending_value() {
6215        // The diagnostic-shape pin: the offending fuel count is
6216        // carried verbatim into the [`LimitsError::FuelExceedsCap`]
6217        // variant so the surfaced error message names the value the
6218        // author wrote, not just the cap. Same self-locating
6219        // diagnostic shape every other typed-cap arm on this surface
6220        // carries (`MemoryExceedsWasm32Cap` carries the offending
6221        // byte count verbatim, `WallClockExceedsCap` carries the
6222        // offending `Duration` verbatim, `CpuExceedsCap` carries the
6223        // offending millicore count verbatim).
6224        let f = 5_000_000_000_000_u64; // 5 trillion — 5x the cap
6225        let l = LimitsSpec {
6226            fuel: Some(f),
6227            ..Default::default()
6228        };
6229        let err = l.validate().unwrap_err();
6230        assert!(
6231            matches!(err, LimitsError::FuelExceedsCap { fuel } if fuel == f),
6232            "got {err:?}"
6233        );
6234        let msg = err.to_string();
6235        assert!(
6236            msg.contains("5000000000000"),
6237            ":limits :fuel cap diagnostic must carry the offending value verbatim (got: {msg})"
6238        );
6239    }
6240
6241    #[test]
6242    fn fuel_cap_pins_canonical_value() {
6243        // The [`LIMITS_FUEL_MAX`] constant pins the value at exactly
6244        // 10^12 (1 trillion wasm instructions) — the round-number
6245        // ceiling above the operational envelope the sibling
6246        // [`LIMITS_WALL_CLOCK_MAX`] (1h) × wasmtime's fuel-tracked
6247        // execution rate (~10^9 fuel/sec) yields. Pinning the
6248        // literal value here surfaces a future drift (a relaxation
6249        // to 10^15, a tightening to 10^9) as a deliberate test edit,
6250        // not a silent contract narrowing. Same shape every other
6251        // typed-cap value pin uses (`cpu_cap_pins_canonical_value`,
6252        // `wall_clock_cap_pins_canonical_value`,
6253        // `wasm32_memory_cap_matches_parsed_4_gib`).
6254        assert_eq!(LIMITS_FUEL_MAX, 1_000_000_000_000);
6255        assert_eq!(LIMITS_FUEL_MAX, 10_u64.pow(12));
6256    }
6257
6258    #[test]
6259    fn fuel_cap_value_round_trips_through_serde() {
6260        // The serde round-trip property the cap arm preserves: the
6261        // [`LIMITS_FUEL_MAX`] constant itself round-trips through
6262        // the in-module `u64` serde codec — the cap value renders as
6263        // the bare integer literal and parses back to the same
6264        // `u64`. Pin this so a future drift between the cap constant
6265        // and the codec's accepted magnitude (a future custom u64
6266        // serializer that introduces lossy formatting) surfaces
6267        // here. Same shape every other typed boundary pin on this
6268        // surface uses (`wasm32_memory_cap_matches_parsed_4_gib`,
6269        // `wall_clock_cap_value_round_trips_through_codec`,
6270        // `cpu_cap_value_round_trips_through_codec`).
6271        let l = LimitsSpec {
6272            fuel: Some(LIMITS_FUEL_MAX),
6273            ..Default::default()
6274        };
6275        let json = serde_json::to_string(&l).unwrap();
6276        assert!(
6277            json.contains("1000000000000"),
6278            "the LIMITS_FUEL_MAX value must render verbatim as the bare integer 10^12 \
6279             (got: {json})"
6280        );
6281        let back: LimitsSpec = serde_json::from_str(&json).unwrap();
6282        assert_eq!(back.fuel, Some(LIMITS_FUEL_MAX));
6283        l.validate()
6284            .expect("LIMITS_FUEL_MAX itself must pass validate");
6285    }
6286
6287    // ── per-`:limits :memory` accessor pins (LimitsSpec::memory) ─────────
6288
6289    #[test]
6290    fn limits_memory_returns_option_u64_byte_equal_across_permutations() {
6291        // The canonical per-`:limits` `:memory` Lunatic-per-process
6292        // wasm32-linear-memory byte-cap scalar pin: [`LimitsSpec::memory`]
6293        // must return the `:limits :memory` typed `u64` verbatim as an
6294        // `Option<u64>`, byte-equal to the raw field access across the
6295        // three canonical shape-arms — `None` (no cap declared —
6296        // engine-default applies), `Some(LIMITS_MEMORY_WASM32_PAGE_BYTES)`
6297        // (the structural minimum a validated `:limits :memory` may
6298        // carry, one wasm32 linear-memory page), `Some(64 * 1024 *
6299        // 1024)` (the canonical 64 MiB byte-cap the module-level
6300        // docstring names).
6301        //
6302        // Peer of the sibling per-`:politicas` [`crate::MeshPolicy::mtls_required`]
6303        // (c0110f1) / [`crate::MeshPolicy::retries`] (bdfb399) /
6304        // [`crate::MeshPolicy::timeout`] (7073d0f) accessor pin trio on
6305        // the sibling `Option<Copy-T>`-return axis, extended to the
6306        // peer per-`:limits` typed-`u64` optional-scalar shape —
6307        // first `Option<Copy-T>`-return accessor on the M2 slot family.
6308        // Pins against a future silent detour that re-derived the cap
6309        // from a peer axis (an accidental `.fuel`-collapse that
6310        // assumed the two `Option<u64>` axes carry the same value), a
6311        // `None` → `Some(0)` "zero means unbounded" collapse (the
6312        // canonical `Option<u64>` → `u64` collapse footgun the
6313        // [`LimitsError::MemoryZero`] validate arm guards on the peer
6314        // zero-floor axis), or a per-arm variant swap that landed on
6315        // one consumer without the other.
6316        for memory in [
6317            None,
6318            Some(LIMITS_MEMORY_WASM32_PAGE_BYTES),
6319            Some(64 * 1024 * 1024),
6320        ] {
6321            let l = LimitsSpec {
6322                memory,
6323                ..LimitsSpec::default()
6324            };
6325            assert_eq!(
6326                l.memory(),
6327                memory,
6328                "LimitsSpec::memory must return :limits :memory verbatim \
6329                 (got {:?}, expected {memory:?})",
6330                l.memory(),
6331            );
6332            assert_eq!(
6333                l.memory(),
6334                l.memory,
6335                "LimitsSpec::memory must byte-equal the raw .memory \
6336                 field access across every value in the accept-set",
6337            );
6338        }
6339    }
6340
6341    #[test]
6342    fn limits_is_empty_memory_arm_routes_through_accessor() {
6343        // Composition pin: [`LimitsSpec::is_empty`]'s `memory` arm
6344        // must key off [`LimitsSpec::memory`], not the raw `.memory`
6345        // field access. Structurally: setting ONLY the `memory` slot
6346        // on an otherwise-default LimitsSpec must flip `is_empty()`
6347        // from `true` (all-`None`) to `false` (one axis carries a
6348        // value); the flip must be observed across every value in the
6349        // accept-set since the emptiness semantic reads "any axis
6350        // carries a value" — not "any axis carries a value above a
6351        // threshold" — the same non-collapsing shape the sibling M3
6352        // [`crate::MeshPolicy::is_empty`] predicate carries on its
6353        // peer `Option<Copy-T>`-typed slot surfaces.
6354        //
6355        // Pins against a future silent detour that re-derived the
6356        // emptiness predicate off a peer axis (an accidental
6357        // `.fuel.is_none()`-only chain that dropped the `memory` arm
6358        // entirely), an accessor-side detour that no longer names the
6359        // substrate-primitive typed dispatch (an accidental
6360        // `self.memory.unwrap_or(0) == 0` fallback in the accessor
6361        // that would silently classify both `None` and `Some(0)` as
6362        // the same value), or a threshold collapse (a
6363        // `self.memory().is_some_and(|m| m > 0)` that would silently
6364        // classify `Some(0)` as unset).
6365        //
6366        // Peer of the sibling per-`:politicas`
6367        // [`crate::MeshPolicy::is_empty`] `mtls_required` arm
6368        // accessor-composition pin (c0110f1) on the sibling optional-
6369        // scalar axis — same "the emptiness / shape-gate predicate
6370        // must route through the substrate-primitive typed dispatch"
6371        // discipline extended onto the peer per-`:limits` emptiness
6372        // predicate.
6373        let empty = LimitsSpec::default();
6374        assert!(
6375            empty.is_empty(),
6376            "LimitsSpec::default() must be is_empty() — every axis \
6377             defaults to None",
6378        );
6379        for memory in [
6380            Some(LIMITS_MEMORY_WASM32_PAGE_BYTES),
6381            Some(64 * 1024 * 1024),
6382            Some(LIMITS_MEMORY_WASM32_MAX_BYTES),
6383        ] {
6384            let l = LimitsSpec {
6385                memory,
6386                ..LimitsSpec::default()
6387            };
6388            assert!(
6389                !l.is_empty(),
6390                "LimitsSpec::is_empty must return false when :memory \
6391                 is {memory:?} — the emptiness predicate reads \"any \
6392                 axis carries a value\", not \"any axis carries a \
6393                 value above a threshold\"",
6394            );
6395            assert_eq!(
6396                l.memory().is_none(),
6397                l.is_empty(),
6398                "when :memory is the only set axis, is_empty() must \
6399                 equal memory().is_none() — the accessor and the \
6400                 emptiness predicate must route through the same \
6401                 substrate-primitive typed dispatch on the :memory \
6402                 arm",
6403            );
6404        }
6405    }
6406
6407    #[test]
6408    fn limits_memory_projects_option_u64_by_copy() {
6409        // The by-copy pin: [`LimitsSpec::memory`] returns `Option<u64>`
6410        // by copy — `Option<u64>` is `Copy` and the accessor must
6411        // return by value, not by reference. Peer of the sibling per-
6412        // `:politicas` [`crate::MeshPolicy::mtls_required`] (c0110f1)
6413        // borrow-invariant pin on the peer `Option<bool>` shape,
6414        // extended onto the peer `Option<u64>` copy-invariant shape —
6415        // the accessor's returned `Option<u64>` must outlive `&self`
6416        // (multiple calls must return equal values from a dropped-
6417        // `&self` copy, since the returned Option carries no borrow),
6418        // and calling the accessor twice on the same LimitsSpec must
6419        // yield the same `Option<u64>` verbatim (idempotent, no side
6420        // effects on `&self`).
6421        //
6422        // Pins against a future silent detour that returned
6423        // `Option<&u64>` (which would type-check but silently break
6424        // every downstream caller — the future `wasmtime::Store::limiter`
6425        // wire path consumes `Option<u64>` by value and `&u64` would
6426        // fold to a detached copy at the call site), an accidental
6427        // `Option::as_ref()` projection (`self.memory.as_ref()` would
6428        // also type-check but return `Option<&u64>`), or a one-arm-
6429        // only accessor that reads `Some(*m)` in the Some arm but
6430        // reads a fresh `Default::default()` in the None arm.
6431        for memory in [
6432            None,
6433            Some(LIMITS_MEMORY_WASM32_PAGE_BYTES),
6434            Some(64 * 1024 * 1024),
6435            Some(LIMITS_MEMORY_WASM32_MAX_BYTES),
6436        ] {
6437            let l = LimitsSpec {
6438                memory,
6439                ..LimitsSpec::default()
6440            };
6441            let first = l.memory();
6442            let second = l.memory();
6443            assert_eq!(
6444                first, second,
6445                "LimitsSpec::memory must be idempotent — two \
6446                 successive calls on the same &self must return the \
6447                 same Option<u64>",
6448            );
6449            assert_eq!(
6450                first, memory,
6451                "LimitsSpec::memory must return :limits :memory \
6452                 verbatim by copy — got {first:?}, expected {memory:?}",
6453            );
6454        }
6455    }
6456
6457    #[test]
6458    #[allow(clippy::too_many_lines)]
6459    fn validate_memory_arms_route_through_lifted_memory_accessor() {
6460        // Composition pin: every value-shape gate in
6461        // [`LimitsSpec::validate`] on the `:memory` axis (the
6462        // zero-floor `MemoryZero` arm, the sub-page `MemoryBelowWasm32Page`
6463        // arm, the above-cap `MemoryExceedsWasm32Cap` arm, the
6464        // non-page-multiple `MemoryNotPageMultiple` arm) must key off
6465        // [`LimitsSpec::memory`], not the raw `self.memory` field
6466        // access. Peer of the sibling per-`:politicas`
6467        // [`crate::AplicacaoSpec::validate_politicas`] `:timeout` /
6468        // `:retries` arm converge pin (1017b9d) on the sibling M3
6469        // mesh-slot family, extended onto the M2 per-`:limits`
6470        // `:memory` axis; peer of the sibling per-`:limits` `:fuel` /
6471        // `:wall-clock` / `:cpu` arms in the same fan-out that
6472        // already route through `self.fuel()` / `self.wall_clock()`
6473        // / `self.cpu()` at :880 / :888 / :942.
6474        //
6475        // Assertion shape: for each memory value in the
6476        // accept-and-refuse set, `LimitsSpec::memory()` must byte-
6477        // equal the raw `.memory` field it borrows from, and the
6478        // validate call on a `LimitsSpec { memory: <v>, ..default() }`
6479        // fixture must surface the same variant/Ok discriminant the
6480        // accessor-composed spec surfaces. Together they catch any
6481        // future silent detour — an accessor drift that no longer
6482        // shipped the raw slot verbatim, a validate-branch rebrand to
6483        // a peer-axis field read, an accidental `Option`-collapse in
6484        // any of the four arms — at caixa-core build time rather than
6485        // at a downstream runtime declared-but-inert-limits divergence
6486        // at the wasmtime `Store::limiter` boundary.
6487        //
6488        // `#[allow(clippy::too_many_lines)]` per the same discipline
6489        // peer over-100-line composition pins in this module accept
6490        // (see e.g. `limits_is_empty_memory_arm_routes_through_accessor`,
6491        // `limits_memory_returns_option_u64_byte_equal_across_permutations`).
6492        for memory in [
6493            None,
6494            Some(0),                                   // → MemoryZero
6495            Some(1),                                   // → MemoryBelowWasm32Page (sub-page)
6496            Some(LIMITS_MEMORY_WASM32_PAGE_BYTES - 1), // → MemoryBelowWasm32Page (at-under-page)
6497            Some(LIMITS_MEMORY_WASM32_PAGE_BYTES),     // → Ok (at-page-floor)
6498            Some(LIMITS_MEMORY_WASM32_PAGE_BYTES + 1), // → MemoryNotPageMultiple (one-past-page)
6499            Some(2 * LIMITS_MEMORY_WASM32_PAGE_BYTES), // → Ok (multi-page)
6500            Some(LIMITS_MEMORY_WASM32_MAX_BYTES),      // → Ok (at-cap)
6501            Some(LIMITS_MEMORY_WASM32_MAX_BYTES + 1),  // → MemoryExceedsWasm32Cap (one-past-cap)
6502        ] {
6503            let l = LimitsSpec {
6504                memory,
6505                ..LimitsSpec::default()
6506            };
6507            // (1) The accessor must byte-equal the raw field it wraps.
6508            assert_eq!(
6509                l.memory(),
6510                l.memory,
6511                "LimitsSpec::memory() must byte-equal the raw \
6512                 .memory field for {memory:?} — an accessor detour \
6513                 that dropped the raw slot's Option<u64> verbatim \
6514                 would silently split validate's :memory arms from \
6515                 every peer emit-site consumer that also routes \
6516                 through the accessor (the future wasmtime \
6517                 Store::limiter wire path, the caixa-helm \
6518                 resources.limits.memory materializer)",
6519            );
6520            // (2) Two successive validate() calls must yield the same
6521            // variant/Ok discriminant — the accessor-projected reads
6522            // and the raw-projected reads must produce identical
6523            // validation outcomes.
6524            let first = l.validate();
6525            let second = l.validate();
6526            assert_eq!(
6527                first, second,
6528                "LimitsSpec::validate must be idempotent on :memory \
6529                 {memory:?} — two successive calls must surface the \
6530                 same variant/Ok discriminant, catching any accessor \
6531                 detour that would introduce a value-dependent side \
6532                 effect on the &self projection",
6533            );
6534        }
6535        // (3) The specific arm-order shape the four converged sites
6536        // encode: `MemoryZero` (raw-`Some(0)`) precedes the page-floor
6537        // arm, which precedes the cap arm, which precedes the page-
6538        // multiple arm. Each arm must fire off the accessor-projected
6539        // read on its specific fixture value.
6540        assert_eq!(
6541            LimitsSpec {
6542                memory: Some(0),
6543                ..LimitsSpec::default()
6544            }
6545            .validate(),
6546            Err(LimitsError::MemoryZero),
6547            "MemoryZero must fire on Some(0) via the accessor projection",
6548        );
6549        assert_eq!(
6550            LimitsSpec {
6551                memory: Some(1),
6552                ..LimitsSpec::default()
6553            }
6554            .validate(),
6555            Err(LimitsError::MemoryBelowWasm32Page { bytes: 1 }),
6556            "MemoryBelowWasm32Page must fire on Some(1) via the accessor projection",
6557        );
6558        assert_eq!(
6559            LimitsSpec {
6560                memory: Some(LIMITS_MEMORY_WASM32_MAX_BYTES + 1),
6561                ..LimitsSpec::default()
6562            }
6563            .validate(),
6564            Err(LimitsError::MemoryExceedsWasm32Cap {
6565                bytes: LIMITS_MEMORY_WASM32_MAX_BYTES + 1
6566            }),
6567            "MemoryExceedsWasm32Cap must fire on one-past-cap via the accessor projection",
6568        );
6569        assert_eq!(
6570            LimitsSpec {
6571                memory: Some(LIMITS_MEMORY_WASM32_PAGE_BYTES + 1),
6572                ..LimitsSpec::default()
6573            }
6574            .validate(),
6575            Err(LimitsError::MemoryNotPageMultiple {
6576                bytes: LIMITS_MEMORY_WASM32_PAGE_BYTES + 1
6577            }),
6578            "MemoryNotPageMultiple must fire on one-past-page-floor via the accessor projection",
6579        );
6580        assert_eq!(
6581            LimitsSpec {
6582                memory: Some(LIMITS_MEMORY_WASM32_PAGE_BYTES),
6583                ..LimitsSpec::default()
6584            }
6585            .validate(),
6586            Ok(()),
6587            "at-page-floor must pass validate via the accessor projection",
6588        );
6589        assert_eq!(
6590            LimitsSpec {
6591                memory: Some(LIMITS_MEMORY_WASM32_MAX_BYTES),
6592                ..LimitsSpec::default()
6593            }
6594            .validate(),
6595            Ok(()),
6596            "at-cap must pass validate via the accessor projection",
6597        );
6598    }
6599
6600    // ── per-`:limits :fuel` accessor pins (LimitsSpec::fuel) ─────────
6601
6602    #[test]
6603    fn limits_fuel_returns_option_u64_byte_equal_across_permutations() {
6604        // The canonical per-`:limits` `:fuel` wasmtime-per-call
6605        // wasm-instruction budget scalar pin: [`LimitsSpec::fuel`]
6606        // must return the `:limits :fuel` typed `u64` verbatim as an
6607        // `Option<u64>`, byte-equal to the raw field access across
6608        // the three canonical shape-arms — `None` (no fuel budget
6609        // declared — engine-default applies), `Some(1)` (the
6610        // structural minimum a validated `:limits :fuel` may carry,
6611        // one wasm instruction; wasmtime traps the first instruction
6612        // at `fuel=0`, so `Some(1)` is the smallest budget that
6613        // executes any code), `Some(1_000_000)` (the canonical 10⁶
6614        // fuel-unit budget the in-tree `Caixa::template` and the
6615        // wasmtime book's `Store::set_fuel(1_000_000)` example both
6616        // carry).
6617        //
6618        // Peer of the sibling per-`:limits` [`LimitsSpec::memory`]
6619        // (620c067) accessor byte-equality pin on the peer typed-`u64`
6620        // optional-scalar axis, extended to the wasm-instruction-budget
6621        // shape — second `Option<Copy-T>`-return accessor on the M2
6622        // slot family. Pins against a future silent detour that
6623        // re-derived the fuel budget from a peer axis (an accidental
6624        // `.memory`-collapse that assumed the two `Option<u64>` axes
6625        // carry the same value — the two axes share a shape but not
6626        // a semantic, `:memory` counts linear-memory bytes and `:fuel`
6627        // counts wasm instructions), a `None` → `Some(0)` "zero means
6628        // unbounded" collapse (the canonical `Option<u64>` → `u64`
6629        // collapse footgun the [`LimitsError::FuelZero`] validate arm
6630        // guards on the peer zero-floor axis; wasmtime interprets
6631        // `fuel=0` as "trap the first instruction" not "no bound"), or
6632        // a per-arm variant swap that landed on one consumer without
6633        // the other.
6634        for fuel in [None, Some(1_u64), Some(1_000_000_u64)] {
6635            let l = LimitsSpec {
6636                fuel,
6637                ..LimitsSpec::default()
6638            };
6639            assert_eq!(
6640                l.fuel(),
6641                fuel,
6642                "LimitsSpec::fuel must return :limits :fuel verbatim \
6643                 (got {:?}, expected {fuel:?})",
6644                l.fuel(),
6645            );
6646            assert_eq!(
6647                l.fuel(),
6648                l.fuel,
6649                "LimitsSpec::fuel must byte-equal the raw .fuel \
6650                 field access across every value in the accept-set",
6651            );
6652        }
6653    }
6654
6655    #[test]
6656    fn limits_is_empty_fuel_arm_routes_through_accessor() {
6657        // Composition pin: [`LimitsSpec::is_empty`]'s `fuel` arm
6658        // must key off [`LimitsSpec::fuel`], not the raw `.fuel`
6659        // field access. Structurally: setting ONLY the `fuel` slot
6660        // on an otherwise-default LimitsSpec must flip `is_empty()`
6661        // from `true` (all-`None`) to `false` (one axis carries a
6662        // value); the flip must be observed across every value in
6663        // the accept-set since the emptiness semantic reads "any
6664        // axis carries a value" — not "any axis carries a value
6665        // above a threshold" — the same non-collapsing shape the
6666        // sibling M3 [`crate::MeshPolicy::is_empty`] predicate
6667        // carries on its peer `Option<Copy-T>`-typed slot surfaces
6668        // and the sibling per-`:limits` [`LimitsSpec::memory`]
6669        // (620c067) `is_empty()` accessor-composition pin carries on
6670        // the peer `Option<u64>` axis.
6671        //
6672        // Pins against a future silent detour that re-derived the
6673        // emptiness predicate off a peer axis (an accidental
6674        // `.memory.is_none()`-only chain that dropped the `fuel` arm
6675        // entirely), an accessor-side detour that no longer names the
6676        // substrate-primitive typed dispatch (an accidental
6677        // `self.fuel.unwrap_or(0) == 0` fallback in the accessor
6678        // that would silently classify both `None` and `Some(0)` as
6679        // the same value — a footgun the [`LimitsError::FuelZero`]
6680        // validate arm explicitly closes since `fuel=0` traps rather
6681        // than expresses "unbounded"), or a threshold collapse (a
6682        // `self.fuel().is_some_and(|f| f > 0)` that would silently
6683        // classify `Some(0)` as unset).
6684        //
6685        // Peer of the sibling per-`:limits` [`LimitsSpec::memory`]
6686        // (620c067) `is_empty` composition pin on the peer
6687        // `Option<u64>` axis — same "the emptiness predicate must
6688        // route through the substrate-primitive typed dispatch"
6689        // discipline extended onto the peer per-`:limits` `:fuel`
6690        // arm.
6691        let empty = LimitsSpec::default();
6692        assert!(
6693            empty.is_empty(),
6694            "LimitsSpec::default() must be is_empty() — every axis \
6695             defaults to None",
6696        );
6697        for fuel in [Some(1_u64), Some(1_000_000_u64), Some(LIMITS_FUEL_MAX)] {
6698            let l = LimitsSpec {
6699                fuel,
6700                ..LimitsSpec::default()
6701            };
6702            assert!(
6703                !l.is_empty(),
6704                "LimitsSpec::is_empty must return false when :fuel \
6705                 is {fuel:?} — the emptiness predicate reads \"any \
6706                 axis carries a value\", not \"any axis carries a \
6707                 value above a threshold\"",
6708            );
6709            assert_eq!(
6710                l.fuel().is_none(),
6711                l.is_empty(),
6712                "when :fuel is the only set axis, is_empty() must \
6713                 equal fuel().is_none() — the accessor and the \
6714                 emptiness predicate must route through the same \
6715                 substrate-primitive typed dispatch on the :fuel \
6716                 arm",
6717            );
6718        }
6719    }
6720
6721    #[test]
6722    fn limits_fuel_projects_option_u64_by_copy() {
6723        // The by-copy pin: [`LimitsSpec::fuel`] returns `Option<u64>`
6724        // by copy — `Option<u64>` is `Copy` and the accessor must
6725        // return by value, not by reference. Peer of the sibling per-
6726        // `:limits` [`LimitsSpec::memory`] (620c067) copy-invariant
6727        // pin on the peer `Option<u64>` shape — the accessor's
6728        // returned `Option<u64>` must outlive `&self` (multiple calls
6729        // must return equal values from a dropped-`&self` copy, since
6730        // the returned Option carries no borrow), and calling the
6731        // accessor twice on the same LimitsSpec must yield the same
6732        // `Option<u64>` verbatim (idempotent, no side effects on
6733        // `&self`).
6734        //
6735        // Pins against a future silent detour that returned
6736        // `Option<&u64>` (which would type-check but silently break
6737        // every downstream caller — the future `wasmtime::Store::set_fuel`
6738        // wire path consumes `u64` by value and `&u64` would fold to
6739        // a detached copy at the call site), an accidental
6740        // `Option::as_ref()` projection (`self.fuel.as_ref()` would
6741        // also type-check but return `Option<&u64>`), or a one-arm-
6742        // only accessor that reads `Some(*f)` in the Some arm but
6743        // reads a fresh `Default::default()` in the None arm.
6744        for fuel in [
6745            None,
6746            Some(1_u64),
6747            Some(1_000_000_u64),
6748            Some(LIMITS_FUEL_MAX),
6749        ] {
6750            let l = LimitsSpec {
6751                fuel,
6752                ..LimitsSpec::default()
6753            };
6754            let first = l.fuel();
6755            let second = l.fuel();
6756            assert_eq!(
6757                first, second,
6758                "LimitsSpec::fuel must be idempotent — two \
6759                 successive calls on the same &self must return the \
6760                 same Option<u64>",
6761            );
6762            assert_eq!(
6763                first, fuel,
6764                "LimitsSpec::fuel must return :limits :fuel \
6765                 verbatim by copy — got {first:?}, expected {fuel:?}",
6766            );
6767        }
6768    }
6769
6770    // ── per-`:limits :wall-clock` accessor pins (LimitsSpec::wall_clock) ─
6771
6772    #[test]
6773    fn limits_wall_clock_returns_option_duration_byte_equal_across_permutations() {
6774        // The canonical per-`:limits` `:wall-clock` wasmtime-per-call
6775        // wall-clock deadline scalar pin: [`LimitsSpec::wall_clock`]
6776        // must return the `:limits :wall-clock` typed `Duration`
6777        // verbatim as an `Option<Duration>`, byte-equal to the raw
6778        // field access across the three canonical shape-arms — `None`
6779        // (no wall-clock deadline declared — engine-default applies),
6780        // `Some(Duration::from_millis(1))` (the structural minimum a
6781        // validated `:limits :wall-clock` may carry, the
6782        // integer-millisecond floor
6783        // [`LimitsError::WallClockNotCanonical`] rejects everything
6784        // sub-ms; `Duration::ZERO` is separately rejected by
6785        // [`LimitsError::WallClockZero`]), `Some(Duration::from_secs(30))`
6786        // (the canonical 30s deadline the module-level docstring
6787        // names).
6788        //
6789        // Peer of the sibling per-`:limits` [`LimitsSpec::memory`]
6790        // (620c067) / [`LimitsSpec::fuel`] (795dee7) accessor
6791        // byte-equality pins on the peer typed-`u64` optional-scalar
6792        // axes, extended to the wall-clock-deadline `Option<Duration>`
6793        // shape — third `Option<Copy-T>`-return accessor on the M2 slot
6794        // family. Sibling to [`crate::MeshPolicy::timeout`] (7073d0f) on
6795        // the M3 mesh-slot family's peer `Option<Duration>` accessor
6796        // axis — same typed-`Duration` shape extended from the M3
6797        // per-call-timeout axis to the M2 per-outermost-call-deadline
6798        // axis. Pins against a future silent detour that re-derived the
6799        // wall-clock deadline from a peer axis (an accidental
6800        // `.fuel`-collapse that assumed the wall-clock deadline and
6801        // the fuel budget carry the same value — the two axes serve
6802        // different sandboxing purposes, wall-clock tracks scheduler
6803        // real time and fuel tracks wasm instructions), a `None` →
6804        // `Some(Duration::ZERO)` "zero means unbounded" collapse (the
6805        // canonical `Option<Duration>` → `Duration` collapse footgun
6806        // the [`LimitsError::WallClockZero`] validate arm guards on the
6807        // peer zero-floor axis; a zero deadline traps the first
6808        // instruction), or a per-arm variant swap that landed on one
6809        // consumer without the other.
6810        for wall_clock in [
6811            None,
6812            Some(Duration::from_millis(1)),
6813            Some(Duration::from_secs(30)),
6814        ] {
6815            let l = LimitsSpec {
6816                wall_clock,
6817                ..LimitsSpec::default()
6818            };
6819            assert_eq!(
6820                l.wall_clock(),
6821                wall_clock,
6822                "LimitsSpec::wall_clock must return :limits :wall-clock verbatim \
6823                 (got {:?}, expected {wall_clock:?})",
6824                l.wall_clock(),
6825            );
6826            assert_eq!(
6827                l.wall_clock(),
6828                l.wall_clock,
6829                "LimitsSpec::wall_clock must byte-equal the raw .wall_clock \
6830                 field access across every value in the accept-set",
6831            );
6832        }
6833    }
6834
6835    #[test]
6836    fn limits_is_empty_wall_clock_arm_routes_through_accessor() {
6837        // Composition pin: [`LimitsSpec::is_empty`]'s `wall_clock` arm
6838        // must key off [`LimitsSpec::wall_clock`], not the raw
6839        // `.wall_clock` field access. Structurally: setting ONLY the
6840        // `wall_clock` slot on an otherwise-default LimitsSpec must
6841        // flip `is_empty()` from `true` (all-`None`) to `false` (one
6842        // axis carries a value); the flip must be observed across every
6843        // value in the accept-set since the emptiness semantic reads
6844        // "any axis carries a value" — not "any axis carries a value
6845        // above a threshold" — the same non-collapsing shape the
6846        // sibling M3 [`crate::MeshPolicy::is_empty`] predicate carries
6847        // on its peer `Option<Copy-T>`-typed slot surfaces and the
6848        // sibling per-`:limits` [`LimitsSpec::memory`] (620c067) /
6849        // [`LimitsSpec::fuel`] (795dee7) `is_empty()` accessor-
6850        // composition pins carry on the peer `Option<u64>` axes.
6851        //
6852        // Pins against a future silent detour that re-derived the
6853        // emptiness predicate off a peer axis (an accidental
6854        // `.memory.is_none()`-only chain that dropped the `wall_clock`
6855        // arm entirely), an accessor-side detour that no longer names
6856        // the substrate-primitive typed dispatch (an accidental
6857        // `self.wall_clock.unwrap_or(Duration::ZERO).is_zero()` fallback
6858        // in the accessor that would silently classify both `None` and
6859        // `Some(Duration::ZERO)` as the same value — a footgun the
6860        // [`LimitsError::WallClockZero`] validate arm explicitly closes
6861        // since a zero deadline traps rather than expresses
6862        // "unbounded"), or a threshold collapse (a
6863        // `self.wall_clock().is_some_and(|w| !w.is_zero())` that would
6864        // silently classify `Some(Duration::ZERO)` as unset).
6865        //
6866        // Peer of the sibling per-`:limits` [`LimitsSpec::memory`]
6867        // (620c067) / [`LimitsSpec::fuel`] (795dee7) `is_empty`
6868        // composition pins on the peer `Option<u64>` axes — same "the
6869        // emptiness predicate must route through the substrate-
6870        // primitive typed dispatch" discipline extended onto the peer
6871        // per-`:limits` `:wall-clock` arm.
6872        let empty = LimitsSpec::default();
6873        assert!(
6874            empty.is_empty(),
6875            "LimitsSpec::default() must be is_empty() — every axis \
6876             defaults to None",
6877        );
6878        for wall_clock in [
6879            Some(Duration::from_millis(1)),
6880            Some(Duration::from_secs(30)),
6881            Some(LIMITS_WALL_CLOCK_MAX),
6882        ] {
6883            let l = LimitsSpec {
6884                wall_clock,
6885                ..LimitsSpec::default()
6886            };
6887            assert!(
6888                !l.is_empty(),
6889                "LimitsSpec::is_empty must return false when :wall-clock \
6890                 is {wall_clock:?} — the emptiness predicate reads \"any \
6891                 axis carries a value\", not \"any axis carries a \
6892                 value above a threshold\"",
6893            );
6894            assert_eq!(
6895                l.wall_clock().is_none(),
6896                l.is_empty(),
6897                "when :wall-clock is the only set axis, is_empty() must \
6898                 equal wall_clock().is_none() — the accessor and the \
6899                 emptiness predicate must route through the same \
6900                 substrate-primitive typed dispatch on the :wall-clock \
6901                 arm",
6902            );
6903        }
6904    }
6905
6906    #[test]
6907    fn limits_wall_clock_projects_option_duration_by_copy() {
6908        // The by-copy pin: [`LimitsSpec::wall_clock`] returns
6909        // `Option<Duration>` by copy — `Duration` is `Copy` (so
6910        // `Option<Duration>` is `Copy`) and the accessor must return by
6911        // value, not by reference. Peer of the sibling per-`:limits`
6912        // [`LimitsSpec::memory`] (620c067) / [`LimitsSpec::fuel`]
6913        // (795dee7) copy-invariant pins on the peer `Option<u64>`
6914        // shape, extended onto the peer `Option<Duration>` shape — the
6915        // accessor's returned `Option<Duration>` must outlive `&self`
6916        // (multiple calls must return equal values from a dropped-
6917        // `&self` copy, since the returned Option carries no borrow),
6918        // and calling the accessor twice on the same LimitsSpec must
6919        // yield the same `Option<Duration>` verbatim (idempotent, no
6920        // side effects on `&self`).
6921        //
6922        // Pins against a future silent detour that returned
6923        // `Option<&Duration>` (which would type-check but silently
6924        // break every downstream caller — the future
6925        // `wasmtime::Store::epoch_deadline_*` wire path consumes
6926        // `Duration` by value and `&Duration` would fold to a detached
6927        // copy at the call site), an accidental `Option::as_ref()`
6928        // projection (`self.wall_clock.as_ref()` would also type-check
6929        // but return `Option<&Duration>`), or a one-arm-only accessor
6930        // that reads `Some(*w)` in the Some arm but reads a fresh
6931        // `Default::default()` (which would collapse to
6932        // `Duration::ZERO`, not `None`) in the None arm.
6933        for wall_clock in [
6934            None,
6935            Some(Duration::from_millis(1)),
6936            Some(Duration::from_secs(30)),
6937            Some(LIMITS_WALL_CLOCK_MAX),
6938        ] {
6939            let l = LimitsSpec {
6940                wall_clock,
6941                ..LimitsSpec::default()
6942            };
6943            let first = l.wall_clock();
6944            let second = l.wall_clock();
6945            assert_eq!(
6946                first, second,
6947                "LimitsSpec::wall_clock must be idempotent — two \
6948                 successive calls on the same &self must return the \
6949                 same Option<Duration>",
6950            );
6951            assert_eq!(
6952                first, wall_clock,
6953                "LimitsSpec::wall_clock must return :limits :wall-clock \
6954                 verbatim by copy — got {first:?}, expected {wall_clock:?}",
6955            );
6956        }
6957    }
6958
6959    // ── per-`:limits :cpu` accessor pins (LimitsSpec::cpu) ───────────
6960
6961    #[test]
6962    fn limits_cpu_returns_option_u32_byte_equal_across_permutations() {
6963        // The canonical per-`:limits` `:cpu` Kubernetes-millicore
6964        // soft cgroup-share scalar pin: [`LimitsSpec::cpu`] must return
6965        // the `:limits :cpu` typed `u32` verbatim as an `Option<u32>`,
6966        // byte-equal to the raw field access across the three canonical
6967        // shape-arms — `None` (no cgroup share declared —
6968        // scheduler-default applies), `Some(1)` (the structural minimum
6969        // a validated `:limits :cpu` may carry, one millicore; a zero
6970        // cgroup share is separately rejected by
6971        // [`LimitsError::CpuZero`]), `Some(500)` (the canonical 500m
6972        // half-a-core share the in-tree
6973        // `limits_slot_propagates_into_values_block` smoke test carries
6974        // as the load-bearing example, peer to the `caixa-flux`
6975        // projector's identical 500m default).
6976        //
6977        // Peer of the sibling per-`:limits` [`LimitsSpec::memory`]
6978        // (620c067) / [`LimitsSpec::fuel`] (795dee7) /
6979        // [`LimitsSpec::wall_clock`] (8cb717b) accessor byte-equality
6980        // pins on the peer typed-`u64` / `u64` / `Duration`
6981        // optional-scalar axes, extended to the cgroup-cpu-share
6982        // `Option<u32>` shape — fourth and final `Option<Copy-T>`-return
6983        // accessor on the M2 slot family, closing the M2 `:limits`
6984        // `Option<Copy-T>` accessor axis. Sibling to
6985        // [`crate::MeshPolicy::retries`] (bdfb399) on the M3 mesh-slot
6986        // family's peer `Option<u32>` accessor axis — same typed-`u32`
6987        // shape extended from the M3 per-edge-transient-failure-retry-
6988        // budget axis to the M2 per-process-cgroup-cpu-share axis.
6989        // Pins against a future silent detour that re-derived the cpu
6990        // share from a peer axis (an accidental `.retries`-collapse that
6991        // assumed the two `Option<u32>` axes carry the same value — the
6992        // two axes share a shape but not a semantic, M2 `:cpu` counts
6993        // millicores of soft cgroup share and M3 `:retries` counts
6994        // per-edge transient-failure retry budget), a `None` → `Some(0)`
6995        // "zero means unbounded" collapse (the canonical `Option<u32>` →
6996        // `u32` collapse footgun the [`LimitsError::CpuZero`] validate
6997        // arm guards on the peer zero-floor axis; a zero cgroup share
6998        // starves the process rather than expressing "unbounded"), or a
6999        // per-arm variant swap that landed on one consumer without the
7000        // other.
7001        for cpu in [None, Some(1_u32), Some(500_u32)] {
7002            let l = LimitsSpec {
7003                cpu,
7004                ..LimitsSpec::default()
7005            };
7006            assert_eq!(
7007                l.cpu(),
7008                cpu,
7009                "LimitsSpec::cpu must return :limits :cpu verbatim \
7010                 (got {:?}, expected {cpu:?})",
7011                l.cpu(),
7012            );
7013            assert_eq!(
7014                l.cpu(),
7015                l.cpu,
7016                "LimitsSpec::cpu must byte-equal the raw .cpu \
7017                 field access across every value in the accept-set",
7018            );
7019        }
7020    }
7021
7022    #[test]
7023    fn limits_is_empty_cpu_arm_routes_through_accessor() {
7024        // Composition pin: [`LimitsSpec::is_empty`]'s `cpu` arm must key
7025        // off [`LimitsSpec::cpu`], not the raw `.cpu` field access.
7026        // Structurally: setting ONLY the `cpu` slot on an
7027        // otherwise-default LimitsSpec must flip `is_empty()` from
7028        // `true` (all-`None`) to `false` (one axis carries a value);
7029        // the flip must be observed across every value in the
7030        // accept-set since the emptiness semantic reads "any axis
7031        // carries a value" — not "any axis carries a value above a
7032        // threshold" — the same non-collapsing shape the sibling M3
7033        // [`crate::MeshPolicy::is_empty`] predicate carries on its
7034        // peer `Option<Copy-T>`-typed slot surfaces and the sibling
7035        // per-`:limits` [`LimitsSpec::memory`] (620c067) /
7036        // [`LimitsSpec::fuel`] (795dee7) / [`LimitsSpec::wall_clock`]
7037        // (8cb717b) `is_empty()` accessor-composition pins carry on the
7038        // peer `Option<u64>` / `Option<u64>` / `Option<Duration>` axes.
7039        //
7040        // Pins against a future silent detour that re-derived the
7041        // emptiness predicate off a peer axis (an accidental
7042        // `.memory.is_none()`-only chain that dropped the `cpu` arm
7043        // entirely), an accessor-side detour that no longer names the
7044        // substrate-primitive typed dispatch (an accidental
7045        // `self.cpu.unwrap_or(0) == 0` fallback in the accessor that
7046        // would silently classify both `None` and `Some(0)` as the same
7047        // value — a footgun the [`LimitsError::CpuZero`] validate arm
7048        // explicitly closes since a zero cgroup share starves the
7049        // process rather than expressing "unbounded"), or a threshold
7050        // collapse (a `self.cpu().is_some_and(|m| m > 0)` that would
7051        // silently classify `Some(0)` as unset).
7052        //
7053        // Peer of the sibling per-`:limits` [`LimitsSpec::memory`]
7054        // (620c067) / [`LimitsSpec::fuel`] (795dee7) /
7055        // [`LimitsSpec::wall_clock`] (8cb717b) `is_empty` composition
7056        // pins on the peer `Option<u64>` / `Option<u64>` /
7057        // `Option<Duration>` axes — same "the emptiness predicate must
7058        // route through the substrate-primitive typed dispatch"
7059        // discipline extended onto the peer per-`:limits` `:cpu` arm.
7060        // Closes the M2 `:limits` `is_empty`-composition family — every
7061        // arm now routes through its typed accessor, no open-coded
7062        // field access remains.
7063        let empty = LimitsSpec::default();
7064        assert!(
7065            empty.is_empty(),
7066            "LimitsSpec::default() must be is_empty() — every axis \
7067             defaults to None",
7068        );
7069        for cpu in [Some(1_u32), Some(500_u32), Some(LIMITS_CPU_MILLICORES_MAX)] {
7070            let l = LimitsSpec {
7071                cpu,
7072                ..LimitsSpec::default()
7073            };
7074            assert!(
7075                !l.is_empty(),
7076                "LimitsSpec::is_empty must return false when :cpu \
7077                 is {cpu:?} — the emptiness predicate reads \"any \
7078                 axis carries a value\", not \"any axis carries a \
7079                 value above a threshold\"",
7080            );
7081            assert_eq!(
7082                l.cpu().is_none(),
7083                l.is_empty(),
7084                "when :cpu is the only set axis, is_empty() must \
7085                 equal cpu().is_none() — the accessor and the \
7086                 emptiness predicate must route through the same \
7087                 substrate-primitive typed dispatch on the :cpu \
7088                 arm",
7089            );
7090        }
7091    }
7092
7093    #[test]
7094    fn limits_cpu_projects_option_u32_by_copy() {
7095        // The by-copy pin: [`LimitsSpec::cpu`] returns `Option<u32>` by
7096        // copy — `Option<u32>` is `Copy` and the accessor must return
7097        // by value, not by reference. Peer of the sibling per-`:limits`
7098        // [`LimitsSpec::memory`] (620c067) / [`LimitsSpec::fuel`]
7099        // (795dee7) / [`LimitsSpec::wall_clock`] (8cb717b)
7100        // copy-invariant pins on the peer `Option<u64>` / `Option<u64>`
7101        // / `Option<Duration>` shapes, extended onto the peer
7102        // `Option<u32>` copy-invariant shape — the accessor's returned
7103        // `Option<u32>` must outlive `&self` (multiple calls must
7104        // return equal values from a dropped-`&self` copy, since the
7105        // returned Option carries no borrow), and calling the accessor
7106        // twice on the same LimitsSpec must yield the same
7107        // `Option<u32>` verbatim (idempotent, no side effects on
7108        // `&self`).
7109        //
7110        // Pins against a future silent detour that returned
7111        // `Option<&u32>` (which would type-check but silently break
7112        // every downstream caller — the future K8s pod-spec
7113        // `resources.requests.cpu` wire path consumes `u32` by value
7114        // and `&u32` would fold to a detached copy at the call site),
7115        // an accidental `Option::as_ref()` projection
7116        // (`self.cpu.as_ref()` would also type-check but return
7117        // `Option<&u32>`), or a one-arm-only accessor that reads
7118        // `Some(*m)` in the Some arm but reads a fresh
7119        // `Default::default()` in the None arm.
7120        for cpu in [
7121            None,
7122            Some(1_u32),
7123            Some(500_u32),
7124            Some(LIMITS_CPU_MILLICORES_MAX),
7125        ] {
7126            let l = LimitsSpec {
7127                cpu,
7128                ..LimitsSpec::default()
7129            };
7130            let first = l.cpu();
7131            let second = l.cpu();
7132            assert_eq!(
7133                first, second,
7134                "LimitsSpec::cpu must be idempotent — two \
7135                 successive calls on the same &self must return the \
7136                 same Option<u32>",
7137            );
7138            assert_eq!(
7139                first, cpu,
7140                "LimitsSpec::cpu must return :limits :cpu \
7141                 verbatim by copy — got {first:?}, expected {cpu:?}",
7142            );
7143        }
7144    }
7145
7146    // ── LimitsError ctor macro-family equivalence pins ───────────────
7147    //
7148    // Peer discipline of the sibling `layout_violation_ctors!`
7149    // `*_ctor_matches_struct_literal_wrap` pin family (131ca0d) on
7150    // [`LayoutError`], and the sibling `aplicacao_field_reason_ctors!`
7151    // / `contrato_target_ctors!` / `contrato_empty_pair_ctors!` /
7152    // `contrato_pair_value_reason_ctors!` `*_ctor_matches_struct_literal_wrap`
7153    // pin families (981060b / 14b81d5 / 8580068 / 14e13f1) on
7154    // [`AplicacaoError`]. A silent regression that de-folded one variant
7155    // and re-inlined the pre-lift struct-literal at one wire-up site
7156    // (or dropped a field, or diverged the string conversion on one
7157    // arm) trips the affected variant's pin first, so every future edit
7158    // to a variant on the three shared envelopes lands in exactly one
7159    // place.
7160
7161    #[test]
7162    fn non_integer_byte_magnitude_ctor_matches_struct_literal_wrap() {
7163        let value = "1.5KiB";
7164        assert_eq!(
7165            LimitsError::non_integer_byte_magnitude(value),
7166            LimitsError::NonIntegerByteMagnitude {
7167                value: value.to_string(),
7168            },
7169        );
7170    }
7171
7172    #[test]
7173    fn leading_zero_byte_magnitude_ctor_matches_struct_literal_wrap() {
7174        let value = "064MiB";
7175        assert_eq!(
7176            LimitsError::leading_zero_byte_magnitude(value),
7177            LimitsError::LeadingZeroByteMagnitude {
7178                value: value.to_string(),
7179            },
7180        );
7181    }
7182
7183    #[test]
7184    fn non_integer_duration_magnitude_ctor_matches_struct_literal_wrap() {
7185        let value = "1.5s";
7186        assert_eq!(
7187            LimitsError::non_integer_duration_magnitude(value),
7188            LimitsError::NonIntegerDurationMagnitude {
7189                value: value.to_string(),
7190            },
7191        );
7192    }
7193
7194    #[test]
7195    fn leading_zero_duration_magnitude_ctor_matches_struct_literal_wrap() {
7196        let value = "030s";
7197        assert_eq!(
7198            LimitsError::leading_zero_duration_magnitude(value),
7199            LimitsError::LeadingZeroDurationMagnitude {
7200                value: value.to_string(),
7201            },
7202        );
7203    }
7204
7205    #[test]
7206    fn non_integer_millicore_magnitude_ctor_matches_struct_literal_wrap() {
7207        let value = "1.5";
7208        assert_eq!(
7209            LimitsError::non_integer_millicore_magnitude(value),
7210            LimitsError::NonIntegerMillicoreMagnitude {
7211                value: value.to_string(),
7212            },
7213        );
7214    }
7215
7216    #[test]
7217    fn leading_zero_millicore_magnitude_ctor_matches_struct_literal_wrap() {
7218        let value = "0500m";
7219        assert_eq!(
7220            LimitsError::leading_zero_millicore_magnitude(value),
7221            LimitsError::LeadingZeroMillicoreMagnitude {
7222                value: value.to_string(),
7223            },
7224        );
7225    }
7226
7227    #[test]
7228    fn unknown_byte_unit_ctor_matches_struct_literal_wrap() {
7229        // Per-variant byte-equality pin on the `limits_codec_unit_only_ctors!`
7230        // macro's `unknown_byte_unit => UnknownByteUnit` arm. Pins the ctor's
7231        // byte-identity against the open-coded pre-lift struct-literal on the
7232        // same `unit: &str` fixture (the `parse_byte_size` unit-dispatch
7233        // fallthrough hits this arm on any authored unit outside the
7234        // `KB | MB | GB | KiB | MiB | GiB | "" | B` alphabet — pick a
7235        // typography-space suffix so the pin exercises the same Unicode-
7236        // whitespace-in-alpha class the two codecs share). A silent regression
7237        // that de-folded the variant and re-inlined the struct-literal at the
7238        // wire-up (or swapped `.to_string()` for a different `String`
7239        // conversion, or dropped the field) trips the assertion under
7240        // `PartialEq`.
7241        let unit = "TiB";
7242        assert_eq!(
7243            LimitsError::unknown_byte_unit(unit),
7244            LimitsError::UnknownByteUnit {
7245                unit: unit.to_string(),
7246            },
7247        );
7248    }
7249
7250    #[test]
7251    fn unknown_duration_unit_ctor_matches_struct_literal_wrap() {
7252        // Per-variant byte-equality pin on the `limits_codec_unit_only_ctors!`
7253        // macro's `unknown_duration_unit => UnknownDurationUnit` arm. Pins the
7254        // ctor's byte-identity against the open-coded pre-lift struct-literal
7255        // on the same `unit: &str` fixture (the `parse_duration` reverse-map
7256        // arm on [`crate::render::DurationUnitError::UnknownUnit`] hits this
7257        // arm on any authored unit outside the `ms | s | "" | m | h`
7258        // alphabet). Peer of the sibling `unknown_byte_unit` pin above on the
7259        // same shared `{ unit: String }` envelope.
7260        let unit = "d";
7261        assert_eq!(
7262            LimitsError::unknown_duration_unit(unit),
7263            LimitsError::UnknownDurationUnit {
7264                unit: unit.to_string(),
7265            },
7266        );
7267    }
7268
7269    #[test]
7270    fn limits_codec_unit_only_ctors_route_unit_verbatim_across_every_variant() {
7271        // Cross-variant sweep: routes each per-variant `unit: &str` scalar
7272        // through the sole `$ctor => $variant` axis the
7273        // `limits_codec_unit_only_ctors!` macro exposes across a boundary-
7274        // covering fixture set (empty string; the ASCII fallthrough shape the
7275        // two codec wire-up sites actually raise; a Unicode-whitespace-in-
7276        // alpha shape covered by the sibling `parse_*` reject-whitespace
7277        // primitive but plausibly reachable from a future consumer that
7278        // pre-strips whitespace before invoking the ctor directly; a
7279        // multi-byte non-ASCII unit alphabet extension). Any wrapper-side
7280        // truncation, silent `.into()` divergence, per-arm constant
7281        // substitution, or accidental cross-variant field swap on either
7282        // ctor surfaces here on the first fixture the two implementations
7283        // disagree on rather than at a downstream diagnostic-shape drift
7284        // (`LimitsError::to_string()` embeds the offending unit verbatim
7285        // through the `Display`/`Error` derive — a divergence at the ctor
7286        // layer flows straight to the surface diagnostic).
7287        for unit in ["", "TiB", "\u{00A0}", "μs"] {
7288            assert_eq!(
7289                LimitsError::unknown_byte_unit(unit),
7290                LimitsError::UnknownByteUnit {
7291                    unit: unit.to_string(),
7292                },
7293            );
7294            assert_eq!(
7295                LimitsError::unknown_duration_unit(unit),
7296                LimitsError::UnknownDurationUnit {
7297                    unit: unit.to_string(),
7298                },
7299            );
7300        }
7301    }
7302
7303    #[test]
7304    fn whitespace_in_byte_size_ctor_matches_struct_literal_wrap() {
7305        let value = " 64MiB";
7306        let byte: u8 = 0x20;
7307        assert_eq!(
7308            LimitsError::whitespace_in_byte_size(value, byte),
7309            LimitsError::WhitespaceInByteSize {
7310                value: value.to_string(),
7311                byte,
7312            },
7313        );
7314    }
7315
7316    #[test]
7317    fn whitespace_in_duration_ctor_matches_struct_literal_wrap() {
7318        let value = " 30s";
7319        let byte: u8 = 0x09;
7320        assert_eq!(
7321            LimitsError::whitespace_in_duration(value, byte),
7322            LimitsError::WhitespaceInDuration {
7323                value: value.to_string(),
7324                byte,
7325            },
7326        );
7327    }
7328
7329    #[test]
7330    fn whitespace_in_millicores_ctor_matches_struct_literal_wrap() {
7331        let value = " 500m";
7332        let byte: u8 = 0x0A;
7333        assert_eq!(
7334            LimitsError::whitespace_in_millicores(value, byte),
7335            LimitsError::WhitespaceInMillicores {
7336                value: value.to_string(),
7337                byte,
7338            },
7339        );
7340    }
7341
7342    #[test]
7343    fn non_ascii_whitespace_in_byte_size_ctor_matches_struct_literal_wrap() {
7344        let value = "\u{00A0}64MiB";
7345        let ch = '\u{00A0}';
7346        assert_eq!(
7347            LimitsError::non_ascii_whitespace_in_byte_size(value, ch),
7348            LimitsError::NonAsciiWhitespaceInByteSize {
7349                value: value.to_string(),
7350                ch,
7351                codepoint: ch as u32,
7352            },
7353        );
7354    }
7355
7356    #[test]
7357    fn non_ascii_whitespace_in_duration_ctor_matches_struct_literal_wrap() {
7358        let value = "30s\u{2028}";
7359        let ch = '\u{2028}';
7360        assert_eq!(
7361            LimitsError::non_ascii_whitespace_in_duration(value, ch),
7362            LimitsError::NonAsciiWhitespaceInDuration {
7363                value: value.to_string(),
7364                ch,
7365                codepoint: ch as u32,
7366            },
7367        );
7368    }
7369
7370    #[test]
7371    fn non_ascii_whitespace_in_millicores_ctor_matches_struct_literal_wrap() {
7372        let value = "500\u{2003}m";
7373        let ch = '\u{2003}';
7374        assert_eq!(
7375            LimitsError::non_ascii_whitespace_in_millicores(value, ch),
7376            LimitsError::NonAsciiWhitespaceInMillicores {
7377                value: value.to_string(),
7378                ch,
7379                codepoint: ch as u32,
7380            },
7381        );
7382    }
7383
7384    #[test]
7385    fn limits_codec_value_char_ctors_route_codepoint_through_ch_as_u32_uniformly() {
7386        // Cross-family sweep: the load-bearing `codepoint = ch as u32`
7387        // derivation is now spelled once — inside the
7388        // `limits_codec_value_char_ctors!` macro body — rather than
7389        // three times at each wire-up. A silent regression that
7390        // de-folded one variant and re-inlined the derivation with a
7391        // different width (`ch as u16`, `ch as i32`) or dropped it
7392        // entirely trips here on the very first codepoint the two
7393        // implementations disagree on. Every non-ASCII Unicode
7394        // whitespace codepoint the sibling
7395        // `crate::render::find_non_ascii_whitespace_char` predicate
7396        // yields is a valid `char`, so `ch as u32` covers the full
7397        // domain the wire-ups reach.
7398        for ch in [
7399            '\u{00A0}', // NBSP
7400            '\u{2028}', // LINE SEPARATOR
7401            '\u{2003}', // EM SPACE
7402            '\u{202F}', // NARROW NO-BREAK SPACE
7403            '\u{3000}', // IDEOGRAPHIC SPACE
7404        ] {
7405            let value = format!("prefix{ch}suffix");
7406            let expected_codepoint = ch as u32;
7407            assert!(matches!(
7408                LimitsError::non_ascii_whitespace_in_byte_size(&value, ch),
7409                LimitsError::NonAsciiWhitespaceInByteSize { codepoint, .. } if codepoint == expected_codepoint,
7410            ));
7411            assert!(matches!(
7412                LimitsError::non_ascii_whitespace_in_duration(&value, ch),
7413                LimitsError::NonAsciiWhitespaceInDuration { codepoint, .. } if codepoint == expected_codepoint,
7414            ));
7415            assert!(matches!(
7416                LimitsError::non_ascii_whitespace_in_millicores(&value, ch),
7417                LimitsError::NonAsciiWhitespaceInMillicores { codepoint, .. } if codepoint == expected_codepoint,
7418            ));
7419        }
7420    }
7421
7422    // ── limits_scalar_ctors! per-variant + cross-axis pins ──────────────────
7423    //
7424    // Per-variant byte-equality pins guaranteeing every generated ctor arm in
7425    // the [`limits_scalar_ctors!`] macro produces a `LimitsError` structurally
7426    // identical to the pre-lift `Self::<variant> { <field>: <val> }` one-line
7427    // struct-literal on the same `Copy`-`u64 | u32 | Duration` fixture, plus
7428    // one cross-axis sweep that routes each per-variant `<field>: <ty>` scalar
7429    // through the sole `$field:ident: $ty:ty` axis the macro exposes so any
7430    // wrapper-side truncation / re-order / silent `.into()` / silent constant-
7431    // substitution on any one variant surfaces here rather than at a
7432    // downstream per-`:limits` diagnostic-shape drift, plus one `const`-eval
7433    // pin that fires at compile time if any future edit silently drops the
7434    // `const` qualifier from the macro body. Peer of the sibling per-variant
7435    // pins on [`crate::supervisor::supervisor_scalar_ctors!`] (f0f77a2, the
7436    // 4-variant `SupervisorError` `{ <field>: RestartStrategy | u32 |
7437    // Duration }` fold on the per-`:supervisor` scalar axis) and the peer
7438    // [`crate::aplicacao::aplicacao_policy_scalar_ctors!`] (7ef425e, the
7439    // 8-variant `AplicacaoError` `{ <field>: Duration | u32 }` fold on the
7440    // per-`:politicas` per-axis cap / canonical-form arms).
7441    #[test]
7442    fn memory_below_wasm32_page_ctor_matches_struct_literal_wrap() {
7443        let bytes = LIMITS_MEMORY_WASM32_PAGE_BYTES - 1;
7444        assert_eq!(
7445            LimitsError::memory_below_wasm32_page(bytes),
7446            LimitsError::MemoryBelowWasm32Page { bytes },
7447            "generated memory_below_wasm32_page ctor must produce byte-equal \
7448             `LimitsError::MemoryBelowWasm32Page` to the pre-lift struct-literal \
7449             wrap on the same `Copy`-`u64` fixture",
7450        );
7451    }
7452
7453    #[test]
7454    fn memory_exceeds_wasm32_cap_ctor_matches_struct_literal_wrap() {
7455        let bytes = LIMITS_MEMORY_WASM32_MAX_BYTES + LIMITS_MEMORY_WASM32_PAGE_BYTES;
7456        assert_eq!(
7457            LimitsError::memory_exceeds_wasm32_cap(bytes),
7458            LimitsError::MemoryExceedsWasm32Cap { bytes },
7459            "generated memory_exceeds_wasm32_cap ctor must produce byte-equal \
7460             `LimitsError::MemoryExceedsWasm32Cap` to the pre-lift struct-literal \
7461             wrap on the same `Copy`-`u64` fixture",
7462        );
7463    }
7464
7465    #[test]
7466    fn memory_not_page_multiple_ctor_matches_struct_literal_wrap() {
7467        let bytes = LIMITS_MEMORY_WASM32_PAGE_BYTES + 1;
7468        assert_eq!(
7469            LimitsError::memory_not_page_multiple(bytes),
7470            LimitsError::MemoryNotPageMultiple { bytes },
7471            "generated memory_not_page_multiple ctor must produce byte-equal \
7472             `LimitsError::MemoryNotPageMultiple` to the pre-lift struct-literal \
7473             wrap on the same `Copy`-`u64` fixture",
7474        );
7475    }
7476
7477    #[test]
7478    fn fuel_exceeds_cap_ctor_matches_struct_literal_wrap() {
7479        let fuel = LIMITS_FUEL_MAX + 1;
7480        assert_eq!(
7481            LimitsError::fuel_exceeds_cap(fuel),
7482            LimitsError::FuelExceedsCap { fuel },
7483            "generated fuel_exceeds_cap ctor must produce byte-equal \
7484             `LimitsError::FuelExceedsCap` to the pre-lift struct-literal wrap \
7485             on the same `Copy`-`u64` fixture",
7486        );
7487    }
7488
7489    #[test]
7490    fn wall_clock_not_canonical_ctor_matches_struct_literal_wrap() {
7491        let wall_clock = Duration::from_micros(1_500);
7492        assert_eq!(
7493            LimitsError::wall_clock_not_canonical(wall_clock),
7494            LimitsError::WallClockNotCanonical { wall_clock },
7495            "generated wall_clock_not_canonical ctor must produce byte-equal \
7496             `LimitsError::WallClockNotCanonical` to the pre-lift struct-literal \
7497             wrap on the same `Copy`-`Duration` fixture",
7498        );
7499    }
7500
7501    #[test]
7502    fn wall_clock_exceeds_cap_ctor_matches_struct_literal_wrap() {
7503        let wall_clock = LIMITS_WALL_CLOCK_MAX + Duration::from_millis(1);
7504        assert_eq!(
7505            LimitsError::wall_clock_exceeds_cap(wall_clock),
7506            LimitsError::WallClockExceedsCap { wall_clock },
7507            "generated wall_clock_exceeds_cap ctor must produce byte-equal \
7508             `LimitsError::WallClockExceedsCap` to the pre-lift struct-literal \
7509             wrap on the same `Copy`-`Duration` fixture",
7510        );
7511    }
7512
7513    #[test]
7514    fn cpu_exceeds_cap_ctor_matches_struct_literal_wrap() {
7515        let millicores = LIMITS_CPU_MILLICORES_MAX + 1;
7516        assert_eq!(
7517            LimitsError::cpu_exceeds_cap(millicores),
7518            LimitsError::CpuExceedsCap { millicores },
7519            "generated cpu_exceeds_cap ctor must produce byte-equal \
7520             `LimitsError::CpuExceedsCap` to the pre-lift struct-literal wrap \
7521             on the same `Copy`-`u32` fixture",
7522        );
7523    }
7524
7525    #[test]
7526    fn limits_scalar_ctors_route_field_through_copy_uniformly() {
7527        // Cross-axis routing pin: sweep each generated `<field>: <ty>`
7528        // constructor input axis through a non-default `Copy` fixture against
7529        // every arm in the [`limits_scalar_ctors!`] macro, so any wrapper-
7530        // side silent `.into()` / silent constant-substitution / silent field
7531        // re-name away from the canonical `bytes | fuel | wall_clock |
7532        // millicores` axes on any one variant, or a `u64 | u32 | Duration`
7533        // axis silently rerouted through some other `Copy` coercion, surfaces
7534        // here rather than at a downstream per-`:limits` diagnostic-shape
7535        // drift. Peer of the sibling
7536        // `supervisor_scalar_ctors_route_field_through_copy_uniformly`
7537        // (f0f77a2) and
7538        // `aplicacao_policy_scalar_ctors_route_field_through_copy_uniformly`
7539        // (7ef425e) cross-axis routing pins on the sibling `SupervisorError`
7540        // / `AplicacaoError` envelopes' per-axis ctor families.
7541        //
7542        // Fixtures picked out of each variant's accept-set boundary rather
7543        // than the default value so a silent constant-substitution to a
7544        // per-variant sentinel surfaces here on the structural-equality
7545        // assertion: the three `:memory` axes pick the below-page / above-cap
7546        // / page-plus-one shapes; the `:fuel` cap picks the above-cap shape;
7547        // the two `:wall-clock` axes pick sub-millisecond and above-cap
7548        // `Duration` shapes; the `:cpu` cap picks the above-cap millicore
7549        // shape.
7550        let below_page = LIMITS_MEMORY_WASM32_PAGE_BYTES - 137;
7551        let above_mem_cap = LIMITS_MEMORY_WASM32_MAX_BYTES + LIMITS_MEMORY_WASM32_PAGE_BYTES;
7552        let page_plus_one = LIMITS_MEMORY_WASM32_PAGE_BYTES + 1;
7553        let above_fuel_cap = LIMITS_FUEL_MAX + 137;
7554        let sub_ms = Duration::from_micros(1_500);
7555        let above_hour = LIMITS_WALL_CLOCK_MAX + Duration::from_secs(1);
7556        let above_cpu_cap = LIMITS_CPU_MILLICORES_MAX + 137;
7557        assert_eq!(
7558            LimitsError::memory_below_wasm32_page(below_page),
7559            LimitsError::MemoryBelowWasm32Page { bytes: below_page },
7560        );
7561        assert_eq!(
7562            LimitsError::memory_exceeds_wasm32_cap(above_mem_cap),
7563            LimitsError::MemoryExceedsWasm32Cap {
7564                bytes: above_mem_cap,
7565            },
7566        );
7567        assert_eq!(
7568            LimitsError::memory_not_page_multiple(page_plus_one),
7569            LimitsError::MemoryNotPageMultiple {
7570                bytes: page_plus_one,
7571            },
7572        );
7573        assert_eq!(
7574            LimitsError::fuel_exceeds_cap(above_fuel_cap),
7575            LimitsError::FuelExceedsCap {
7576                fuel: above_fuel_cap,
7577            },
7578        );
7579        assert_eq!(
7580            LimitsError::wall_clock_not_canonical(sub_ms),
7581            LimitsError::WallClockNotCanonical { wall_clock: sub_ms },
7582        );
7583        assert_eq!(
7584            LimitsError::wall_clock_exceeds_cap(above_hour),
7585            LimitsError::WallClockExceedsCap {
7586                wall_clock: above_hour,
7587            },
7588        );
7589        assert_eq!(
7590            LimitsError::cpu_exceeds_cap(above_cpu_cap),
7591            LimitsError::CpuExceedsCap {
7592                millicores: above_cpu_cap,
7593            },
7594        );
7595    }
7596
7597    #[test]
7598    fn limits_scalar_ctors_are_const_zero_runtime_work() {
7599        // Const-eval pin: the [`limits_scalar_ctors!`] macro spells every
7600        // generated ctor `const fn` so a caller can pin a `LimitsError` at
7601        // compile time — the same zero-runtime-work property the pre-lift
7602        // `|<field>| LimitsError::<Variant> { <field> }` closure carried on
7603        // its `Copy`-pass-through construction path (no `.to_string()` /
7604        // `.into()` allocation, no branching). If any future edit silently
7605        // drops the `const` qualifier from the macro body the per-arm `const`
7606        // bindings below fail to compile, which surfaces the regression at
7607        // the substrate-primitive definition rather than at some downstream
7608        // consumer that had come to rely on the `const`-constructibility.
7609        // Peer of the sibling
7610        // `supervisor_scalar_ctors_are_const_zero_runtime_work` (f0f77a2) and
7611        // `aplicacao_policy_scalar_ctors_are_const_zero_runtime_work`
7612        // (7ef425e) const-eval pins on the sibling `SupervisorError` /
7613        // `AplicacaoError` envelopes' per-axis ctor families.
7614        const MEM_BELOW: LimitsError = LimitsError::memory_below_wasm32_page(1);
7615        const MEM_CAP: LimitsError =
7616            LimitsError::memory_exceeds_wasm32_cap(LIMITS_MEMORY_WASM32_MAX_BYTES + 1);
7617        const MEM_NOT_MULTIPLE: LimitsError =
7618            LimitsError::memory_not_page_multiple(LIMITS_MEMORY_WASM32_PAGE_BYTES + 1);
7619        const FUEL_CAP: LimitsError = LimitsError::fuel_exceeds_cap(LIMITS_FUEL_MAX + 1);
7620        const WALL_NC: LimitsError =
7621            LimitsError::wall_clock_not_canonical(Duration::from_micros(1));
7622        const WALL_CAP: LimitsError =
7623            LimitsError::wall_clock_exceeds_cap(Duration::from_secs(3_601));
7624        const CPU_CAP: LimitsError = LimitsError::cpu_exceeds_cap(LIMITS_CPU_MILLICORES_MAX + 1);
7625        assert!(matches!(
7626            MEM_BELOW,
7627            LimitsError::MemoryBelowWasm32Page { .. }
7628        ));
7629        assert!(matches!(
7630            MEM_CAP,
7631            LimitsError::MemoryExceedsWasm32Cap { .. }
7632        ));
7633        assert!(matches!(
7634            MEM_NOT_MULTIPLE,
7635            LimitsError::MemoryNotPageMultiple { .. }
7636        ));
7637        assert!(matches!(FUEL_CAP, LimitsError::FuelExceedsCap { .. }));
7638        assert!(matches!(WALL_NC, LimitsError::WallClockNotCanonical { .. }));
7639        assert!(matches!(WALL_CAP, LimitsError::WallClockExceedsCap { .. }));
7640        assert!(matches!(CPU_CAP, LimitsError::CpuExceedsCap { .. }));
7641    }
7642
7643    #[test]
7644    fn bad_millicores_ctor_matches_tuple_literal_wrap_on_str_binding() {
7645        // Per-variant byte-equality pin on the newly lifted
7646        // [`LimitsError::bad_millicores`] tuple-newtype ctor over its `&str`
7647        // wire-up shape — the three [`parse_millicores`] sites that opened
7648        // the pre-lift `LimitsError::BadMillicores(s.into())` block against
7649        // the codec-scoped `s: &str` binding (empty-`:cpu`, bare-`m`-
7650        // magnitude fallthrough, non-digit-only garbage fallthrough). A
7651        // silent regression that de-folded the variant and re-inlined the
7652        // tuple-newtype block at one of the three wire-ups (or swapped
7653        // `.into()` for a divergent `String` conversion, or routed one arm
7654        // through a peer variant) trips the assertion under `PartialEq`.
7655        // Peer of the sibling `*_ctor_matches_struct_literal_wrap` pin
7656        // family on the same [`LimitsError`] envelope.
7657        let value = "500x";
7658        assert_eq!(
7659            LimitsError::bad_millicores(value),
7660            LimitsError::BadMillicores(value.to_string()),
7661            "generated bad_millicores ctor over a `&str` binding must \
7662             produce byte-equal `LimitsError::BadMillicores` to the \
7663             pre-lift tuple-newtype wrap on the same `&str` fixture",
7664        );
7665    }
7666
7667    #[test]
7668    fn bad_millicores_ctor_matches_tuple_literal_wrap_on_string_binding() {
7669        // Peer to the sibling `&str`-binding pin above, on the
7670        // `String` wire-up shape — the two [`parse_millicores`] sites that
7671        // opened the pre-lift `LimitsError::BadMillicores(format!(...))`
7672        // block against a codec-scoped `String` binding (digit-only
7673        // magnitude overflows u32, bare-core-shorthand × 1000 overflow).
7674        // Pins that the `impl Into<String>` bound routes both wire-up
7675        // shapes through the same substrate primitive without silently
7676        // rerouting one arm through a divergent conversion. A silent
7677        // regression that de-folded one of the two sites trips this pin
7678        // under `PartialEq`.
7679        let value: String = format!("{} (digit-only magnitude overflows u32)", u32::MAX);
7680        assert_eq!(
7681            LimitsError::bad_millicores(value.clone()),
7682            LimitsError::BadMillicores(value.clone()),
7683            "generated bad_millicores ctor over a `String` binding must \
7684             produce byte-equal `LimitsError::BadMillicores` to the \
7685             pre-lift tuple-newtype wrap on the same `String` fixture",
7686        );
7687    }
7688
7689    #[test]
7690    fn bad_byte_magnitude_ctor_matches_tuple_literal_wrap_on_str_binding() {
7691        // Per-variant byte-equality pin on the newly lifted
7692        // [`LimitsError::bad_byte_magnitude`] tuple-newtype ctor over its
7693        // `&str` wire-up shape — the sole [`parse_byte_size`] site that
7694        // opened the pre-lift `LimitsError::BadByteMagnitude(num_part.into())`
7695        // block against a codec-scoped `&str` binding (non-digit-only garbage
7696        // fallthrough after the numeric-shape gate). A silent regression
7697        // that de-folded the variant and re-inlined the tuple-newtype block
7698        // at the wire-up (or swapped `.into()` for a divergent `String`
7699        // conversion, or routed one arm through a peer variant) trips the
7700        // assertion under `PartialEq`. Direct sibling to the peer
7701        // `bad_millicores_ctor_matches_tuple_literal_wrap_on_str_binding`
7702        // pin on the [`parse_millicores`] codec surface.
7703        let value = "abc";
7704        assert_eq!(
7705            LimitsError::bad_byte_magnitude(value),
7706            LimitsError::BadByteMagnitude(value.to_string()),
7707            "generated bad_byte_magnitude ctor over a `&str` binding must \
7708             produce byte-equal `LimitsError::BadByteMagnitude` to the \
7709             pre-lift tuple-newtype wrap on the same `&str` fixture",
7710        );
7711    }
7712
7713    #[test]
7714    fn bad_byte_magnitude_ctor_matches_tuple_literal_wrap_on_string_binding() {
7715        // Peer to the sibling `&str`-binding pin above, on the
7716        // `String` wire-up shape — the two [`parse_byte_size`] sites that
7717        // opened the pre-lift `LimitsError::BadByteMagnitude(format!(...))`
7718        // block against a codec-scoped `String` binding (digit-only
7719        // magnitude overflows u64, magnitude × unit overflows u64). Pins
7720        // that the `impl Into<String>` bound routes both wire-up shapes
7721        // through the same substrate primitive without silently rerouting
7722        // one arm through a divergent conversion. A silent regression
7723        // that de-folded one of the two sites trips this pin under
7724        // `PartialEq`. Direct sibling to the peer
7725        // `bad_millicores_ctor_matches_tuple_literal_wrap_on_string_binding`
7726        // pin on the [`parse_millicores`] codec surface.
7727        let value: String = format!("{} (digit-only magnitude overflows u64)", u64::MAX);
7728        assert_eq!(
7729            LimitsError::bad_byte_magnitude(value.clone()),
7730            LimitsError::BadByteMagnitude(value.clone()),
7731            "generated bad_byte_magnitude ctor over a `String` binding must \
7732             produce byte-equal `LimitsError::BadByteMagnitude` to the \
7733             pre-lift tuple-newtype wrap on the same `String` fixture",
7734        );
7735    }
7736
7737    #[test]
7738    fn bad_duration_magnitude_ctor_matches_tuple_literal_wrap_on_str_binding() {
7739        // Per-variant byte-equality pin on the newly lifted
7740        // [`LimitsError::bad_duration_magnitude`] tuple-newtype ctor over its
7741        // `&str` wire-up shape — the sole [`parse_duration`] site that opened
7742        // the pre-lift `LimitsError::BadDurationMagnitude(num_part.into())`
7743        // block against a codec-scoped `&str` binding (non-digit-only garbage
7744        // fallthrough after the numeric-shape gate). A silent regression that
7745        // de-folded the variant and re-inlined the tuple-newtype block at the
7746        // wire-up (or swapped `.into()` for a divergent `String` conversion,
7747        // or routed one arm through a peer variant) trips the assertion under
7748        // `PartialEq`. Direct sibling to the peer
7749        // `bad_millicores_ctor_matches_tuple_literal_wrap_on_str_binding` /
7750        // `bad_byte_magnitude_ctor_matches_tuple_literal_wrap_on_str_binding`
7751        // pins on the [`parse_millicores`] / [`parse_byte_size`] codec
7752        // surfaces — closes the last un-lifted `(String)` tuple-newtype
7753        // variant on the paired codec-magnitude family.
7754        let value = "abc";
7755        assert_eq!(
7756            LimitsError::bad_duration_magnitude(value),
7757            LimitsError::BadDurationMagnitude(value.to_string()),
7758            "generated bad_duration_magnitude ctor over a `&str` binding must \
7759             produce byte-equal `LimitsError::BadDurationMagnitude` to the \
7760             pre-lift tuple-newtype wrap on the same `&str` fixture",
7761        );
7762    }
7763
7764    #[test]
7765    fn bad_duration_magnitude_ctor_matches_tuple_literal_wrap_on_string_binding() {
7766        // Peer to the sibling `&str`-binding pin above, on the
7767        // `String` wire-up shape — the two [`parse_duration`] sites that
7768        // opened the pre-lift `LimitsError::BadDurationMagnitude(format!(...))`
7769        // block against a codec-scoped `String` binding (digit-only magnitude
7770        // overflows u64, magnitude × unit overflows u64). Pins that the
7771        // `impl Into<String>` bound routes both wire-up shapes through the
7772        // same substrate primitive without silently rerouting one arm through
7773        // a divergent conversion. A silent regression that de-folded one of
7774        // the two sites trips this pin under `PartialEq`. Direct sibling to
7775        // the peer `bad_millicores_ctor_matches_tuple_literal_wrap_on_string_binding`
7776        // / `bad_byte_magnitude_ctor_matches_tuple_literal_wrap_on_string_binding`
7777        // pins on the [`parse_millicores`] / [`parse_byte_size`] codec
7778        // surfaces.
7779        let value: String = format!("{} (digit-only magnitude overflows u64)", u64::MAX);
7780        assert_eq!(
7781            LimitsError::bad_duration_magnitude(value.clone()),
7782            LimitsError::BadDurationMagnitude(value.clone()),
7783            "generated bad_duration_magnitude ctor over a `String` binding must \
7784             produce byte-equal `LimitsError::BadDurationMagnitude` to the \
7785             pre-lift tuple-newtype wrap on the same `String` fixture",
7786        );
7787    }
7788
7789    #[test]
7790    fn empty_duration_ctor_matches_tuple_literal_wrap_on_str_binding() {
7791        // Per-variant byte-equality pin on the newly lifted
7792        // [`LimitsError::empty_duration`] tuple-newtype ctor over its `&str`
7793        // wire-up shape — the sole [`parse_duration`] site that opened the
7794        // pre-lift `LimitsError::EmptyDuration(s.into())` block against the
7795        // codec-scoped `s: &str` binding after the outer `s.trim()` /
7796        // `is_empty()` gate on the codec entry surface. A silent regression
7797        // that de-folded the variant and re-inlined the tuple-newtype block
7798        // at the wire-up (or swapped `.into()` for a divergent `String`
7799        // conversion, or routed the arm through a peer variant) trips the
7800        // assertion under `PartialEq`. Direct sibling to the peer
7801        // `empty_byte_size_ctor_matches_tuple_literal_wrap_on_str_binding`
7802        // pin on the [`parse_byte_size`] codec surface — the same
7803        // empty-shape axis of the paired `(String)` tuple-newtype codec
7804        // empty-shape family, but on the duration axis rather than the
7805        // byte-size axis.
7806        let value = "";
7807        assert_eq!(
7808            LimitsError::empty_duration(value),
7809            LimitsError::EmptyDuration(value.to_string()),
7810            "generated empty_duration ctor over a `&str` binding must \
7811             produce byte-equal `LimitsError::EmptyDuration` to the \
7812             pre-lift tuple-newtype wrap on the same `&str` fixture",
7813        );
7814    }
7815
7816    #[test]
7817    fn empty_byte_size_ctor_matches_tuple_literal_wrap_on_str_binding() {
7818        // Per-variant byte-equality pin on the newly lifted
7819        // [`LimitsError::empty_byte_size`] tuple-newtype ctor over its `&str`
7820        // wire-up shape — the sole [`parse_byte_size`] site that opened the
7821        // pre-lift `LimitsError::EmptyByteSize(s.into())` block against the
7822        // codec-scoped `s: &str` binding after the outer `s.trim()` /
7823        // `is_empty()` gate on the codec entry surface. A silent regression
7824        // that de-folded the variant and re-inlined the tuple-newtype block
7825        // at the wire-up (or swapped `.into()` for a divergent `String`
7826        // conversion, or routed the arm through a peer variant) trips the
7827        // assertion under `PartialEq`. Direct sibling to the peer
7828        // `bad_byte_magnitude_ctor_matches_tuple_literal_wrap_on_str_binding`
7829        // pin on the same [`parse_byte_size`] codec surface but on the
7830        // bad-magnitude axis rather than the empty-shape axis of the same
7831        // `(String)` tuple-newtype codec-magnitude family.
7832        let value = "";
7833        assert_eq!(
7834            LimitsError::empty_byte_size(value),
7835            LimitsError::EmptyByteSize(value.to_string()),
7836            "generated empty_byte_size ctor over a `&str` binding must \
7837             produce byte-equal `LimitsError::EmptyByteSize` to the \
7838             pre-lift tuple-newtype wrap on the same `&str` fixture",
7839        );
7840    }
7841}