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