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