caixa_core/kind.rs
1use serde::{Deserialize, Serialize};
2
3/// What a caixa produces.
4///
5/// In `caixa.lisp`:
6///
7/// ```lisp
8/// :kind Biblioteca ; library (lib/<nome>.lisp entry)
9/// :kind Binario ; executable(s) under exe/
10/// :kind Servico ; long-running service under servicos/
11/// :kind Supervisor ; OTP-style typed supervisor tree (see supervisor.rs)
12/// ```
13///
14/// Authored as bare symbols (`Biblioteca` not `:biblioteca`) to match the
15/// tatara-lisp enum convention where symbols become enum discriminants via
16/// the serde `Deserialize` fallthrough.
17#[derive(
18 Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, gen_platform::IsVariant,
19)]
20pub enum CaixaKind {
21 /// Library — exports Lisp forms for other caixas to `(importar …)`.
22 Biblioteca,
23 /// Binary — one or more executables under `exe/`.
24 Binario,
25 /// Service — long-running daemon under `servicos/`.
26 Servico,
27 /// OTP-shaped supervisor — does not run any code itself; its
28 /// children are other caixas, restarted under a typed strategy.
29 /// See `supervisor.rs` for the full shape (`SupervisorSpec`).
30 Supervisor,
31 /// Typed application — composes multiple Servicos into a single
32 /// declarative mesh with WIT-typed `:contratos`, mesh-level
33 /// `:politicas`, and explicit `:placement`. See `aplicacao.rs`
34 /// (`AplicacaoSpec`) and `theory/MESH-COMPOSITION.md` for the
35 /// design frame.
36 Aplicacao,
37 /// Typed CI run — carries a `:ci` slot of
38 /// `canteiro_types::CiRun` (a repo's CI run as a set of typed
39 /// nodes + their dependency edges). Runs no code of its own and
40 /// owns no `lib`/`exe`/`servicos`/`children`/`membros` code
41 /// surface — its sole payload is the `ci` field on [`crate::Caixa`].
42 /// See CANTEIRO §7.1-C (`pleme-io/sui`'s `canteiro-types` crate)
43 /// for the DAG algebra (`decompose`/`affected_set`/`affected_waves`)
44 /// this slot feeds, and the `caixa-actions` renderer (currently
45 /// validate-only — see its crate docs) for the M0 consumer.
46 Acao,
47}
48
49impl CaixaKind {
50 /// Exhaustive iteration surface for every consumer that walks the
51 /// closed six-arm [`CaixaKind`] discriminator set (the future M4
52 /// `mesh.pleme.io/v1alpha1/Caixa` CR materializer's admission-webhook
53 /// rejection body naming the accepted-`:kind` list, a future
54 /// `feira --kind …` CLI arg-parse's "did you mean" hint via a
55 /// [`Self::from_wire`]-scan over the slice, the future
56 /// `feira app graph` per-Aplicacao `:kind`-histogram column, any
57 /// future round-trip fuzz harness that sweeps every arm). A future
58 /// variant addition (an `Actor` virtual-actor arm the
59 /// [`ABSORPTION-ROADMAP`](https://github.com/pleme-io/theory/blob/main/ABSORPTION-ROADMAP.md)
60 /// M5 Orleans-inspired kind reaches through — a candidate future
61 /// arm named in the sibling [`Self::from_wire`] doc block —
62 /// extends this slice as a single edit and every consumer picks up
63 /// the new entry by construction; the compiler-checked
64 /// exhaustiveness on the sibling method `match` arms
65 /// ([`Self::as_str`] / [`Self::wire_name`] / [`Self::from_wire`] /
66 /// the `requires_*` predicates) is the build-time guarantee that
67 /// no arm forgets to grow.
68 ///
69 /// Peer of the sibling closed-set typed enums'
70 /// [`crate::aplicacao::PlacementStrategy::ALL`] (18c7342) /
71 /// [`crate::aplicacao::RateLimitUnit::ALL`] (6bce03d) /
72 /// [`crate::dep::DepList::ALL`] (45ee563) exhaustive-iteration
73 /// surfaces — the fourth (and structurally most fundamental —
74 /// every caixa carries a `:kind`) closed-set typed enum on the
75 /// caixa surface to converge onto the same
76 /// one-canonical-arm-list-per-enum discipline.
77 pub const ALL: &'static [Self] = &[
78 Self::Biblioteca,
79 Self::Binario,
80 Self::Servico,
81 Self::Supervisor,
82 Self::Aplicacao,
83 Self::Acao,
84 ];
85
86 /// A `Biblioteca` is expected to have at least one `lib/` entry.
87 #[must_use]
88 pub const fn requires_lib(self) -> bool {
89 matches!(self, Self::Biblioteca)
90 }
91
92 /// A `Binario` is expected to have at least one `exe/` entry.
93 #[must_use]
94 pub const fn requires_exe(self) -> bool {
95 matches!(self, Self::Binario)
96 }
97
98 /// A `Servico` is expected to have at least one `servicos/` entry.
99 #[must_use]
100 pub const fn requires_servicos(self) -> bool {
101 matches!(self, Self::Servico)
102 }
103
104 /// A `Supervisor` is expected to have at least one `:children` entry
105 /// (or a `SimpleOneForOne` strategy that spawns children dynamically).
106 #[must_use]
107 pub const fn requires_children(self) -> bool {
108 matches!(self, Self::Supervisor)
109 }
110
111 /// An `Aplicacao` is expected to have at least one `:membros` entry.
112 #[must_use]
113 pub const fn requires_membros(self) -> bool {
114 matches!(self, Self::Aplicacao)
115 }
116
117 /// An `Acao` is expected to carry a `:ci` slot (a typed
118 /// `canteiro_types::CiRun`). Mirror of the sibling
119 /// [`Self::requires_lib`]/[`Self::requires_exe`]/
120 /// [`Self::requires_servicos`]/[`Self::requires_membros`] required-
121 /// slot predicates on the fifth [`CaixaKind`] arm.
122 #[must_use]
123 pub const fn requires_ci(self) -> bool {
124 matches!(self, Self::Acao)
125 }
126
127 /// Substrate-canonical per-[`CaixaKind`] PascalCase wire byte-string
128 /// every consumer that emits the Caixa's `:kind` axis onto a wire
129 /// surface outside the caixa-core boundary keys off — returns the
130 /// per-arm byte-string the paired
131 /// [`crate::render::CAIXA_KIND_WIRE_BIBLIOTECA`] /
132 /// [`crate::render::CAIXA_KIND_WIRE_BINARIO`] /
133 /// [`crate::render::CAIXA_KIND_WIRE_SERVICO`] /
134 /// [`crate::render::CAIXA_KIND_WIRE_SUPERVISOR`] /
135 /// [`crate::render::CAIXA_KIND_WIRE_APLICACAO`] /
136 /// [`crate::render::CAIXA_KIND_WIRE_ACAO`] lifted consts pin, and
137 /// [`Self::from_wire`] parses back into the typed [`CaixaKind`]
138 /// discriminator.
139 ///
140 /// Byte-identical to the un-`rename`d `Serialize` derive's per-arm
141 /// wire scalar (`serde_json::to_string(&kind).unwrap()` unquoted),
142 /// with the pin test
143 /// [`tests::caixa_kind_wire_name_matches_serialize_wire_byte_string`]
144 /// making the two paths' byte-agreement load-bearing so a future
145 /// `#[serde(rename_all = "…")]` attribute drift at the derive
146 /// surface trips at caixa-core build time rather than silently
147 /// splitting the wire byte-shape from every consumer that reaches
148 /// for this typed dispatch. The paired [`Self::from_wire`] returns
149 /// `Some` on every string [`Self::wire_name`] emits and `None` on
150 /// every other input — the round-trip pin
151 /// [`tests::caixa_kind_wire_round_trips_through_from_wire`] locks
152 /// the two paths' accept-sets together by construction.
153 ///
154 /// Distinct axis from the sibling [`Self::as_str`] — which returns
155 /// the lowercase Portuguese diagnostic byte-string (`"biblioteca"`
156 /// / `"binario"` / …) every consumer that formats the caixa's
157 /// typed shape as user-facing text lands on — by design, not by
158 /// drift: the two-axis split the load-bearing pin
159 /// [`tests::caixa_kind_display_matches_as_str_and_not_serialize_wire`]
160 /// already encodes. This wire accessor closes the third axis on
161 /// the [`CaixaKind`] closed-set discriminator (wire byte-string),
162 /// peer of [`Self::as_str`] (human-readable byte-string) and the
163 /// [`std::fmt::Display`] impl (routed through [`Self::as_str`]).
164 ///
165 /// Prior to this lift, the six [`caixa-crd::conversion`] +
166 /// [`caixa-feira`] + future-M4-CR-materializer consumers that
167 /// needed the PascalCase wire byte-shape reached for one of two
168 /// fragile paths: `format!("{:?}", kind)` (couples the wire format
169 /// to `Debug`'s stability guarantee, which Rust's own conventions
170 /// give as *no guarantee at all* — a `#[derive(Debug)]` swap for
171 /// a hand-rolled `impl Debug` that pretty-prints the variant with
172 /// extra context is a permitted mechanical edit whose apply-time
173 /// symptom would be every downstream K8s CR carrying a stale wire
174 /// byte-string), or `serde_json::to_string(&kind)` + string-trim of
175 /// the outer quotes (introduces an allocation + error-handling
176 /// path for a byte-shape the compiler knows verbatim at build
177 /// time). Lifting the resolver to a typed method on the substrate
178 /// primitive means every downstream consumer of the Caixa's
179 /// `:kind` wire surface reaches for exactly one typed dispatch —
180 /// the resolver's accept-set migrates as a unit on any future arm
181 /// addition (a future virtual-actor `Actor` arm the
182 /// `theory/ABSORPTION-ROADMAP.md` M5 Orleans-inspired kind reaches
183 /// through, a per-cluster kind-alias table the M4 CR materializer
184 /// resolves per-CR).
185 #[must_use]
186 pub const fn wire_name(self) -> &'static str {
187 match self {
188 Self::Biblioteca => crate::render::CAIXA_KIND_WIRE_BIBLIOTECA,
189 Self::Binario => crate::render::CAIXA_KIND_WIRE_BINARIO,
190 Self::Servico => crate::render::CAIXA_KIND_WIRE_SERVICO,
191 Self::Supervisor => crate::render::CAIXA_KIND_WIRE_SUPERVISOR,
192 Self::Aplicacao => crate::render::CAIXA_KIND_WIRE_APLICACAO,
193 Self::Acao => crate::render::CAIXA_KIND_WIRE_ACAO,
194 }
195 }
196
197 /// Substrate-canonical inverse of [`Self::wire_name`] — parses a
198 /// PascalCase wire byte-string into the typed [`CaixaKind`]
199 /// discriminator, returning `None` on any string not in the six-arm
200 /// accept-set the sibling [`Self::wire_name`] emits.
201 ///
202 /// The pair `(wire_name, from_wire)` forms a total round-trip
203 /// discipline on the six [`CaixaKind`] arms — every
204 /// [`Self::wire_name`] output parses back through this accessor
205 /// (pinned load-bearing by the sibling
206 /// [`tests::caixa_kind_wire_round_trips_through_from_wire`] test),
207 /// so consumers that emit a wire byte-string through
208 /// [`Self::wire_name`] and later re-parse it here (the K8s
209 /// [`caixa_crd`] `CaixaSpec.kind` `String`-carry round-trip through
210 /// `caixa_into_cr` + `caixa_from_cr`, the future M4
211 /// `mesh.pleme.io/v1alpha1/Caixa` CR materializer's admission-time
212 /// wire re-parse, the future `feira` CLI verb that accepts a
213 /// `--kind <Biblioteca|Servico|…>` arg and binds it into the typed
214 /// enum) reach for one typed dispatch on the substrate primitive
215 /// instead of the hand-rolled per-arm `match` cascade every
216 /// pre-lift consumer previously carried verbatim. A future arm
217 /// addition (a virtual-actor `Actor` arm the M5 Orleans-inspired
218 /// kind reaches through) lands one caixa-core edit — the parser's
219 /// arm-set migrates as a unit — rather than a coordinated rewrite
220 /// across every hand-rolled `match cr.spec.kind.as_str()` at every
221 /// downstream consumer site.
222 ///
223 /// Prior to this lift, the sole in-tree consumer of the reverse
224 /// parse — [`caixa_crd::conversion::caixa_from_cr`] — carried a
225 /// six-arm `match cr.spec.kind.as_str() { "Biblioteca" => …,
226 /// "Binario" => …, "Servico" => …, "Supervisor" => …, "Aplicacao"
227 /// => …, "Acao" => …, _ => CaixaKind::Biblioteca }` cascade that
228 /// hard-coded every wire byte-string as a per-arm string literal
229 /// with no compile-time link back to the typed
230 /// [`crate::CaixaKind`] enum. A future variant rename or a serde
231 /// attribute drift on the derive would silently split the wire
232 /// format the forward `caixa_into_cr` emits from the reverse
233 /// parser's arm-set — the CR would round-trip through JSON cleanly
234 /// but land on the `_ => CaixaKind::Biblioteca` silent fallback
235 /// on every non-Biblioteca variant, far from the derive-attribute
236 /// commit that caused the drift. Lifting the resolver to a typed
237 /// method on the substrate primitive closes the drift footgun by
238 /// construction: the parser's accept-set is the same set the
239 /// [`Self::wire_name`] emitter walks, so both halves of the
240 /// round-trip migrate through one caixa-core edit on any future
241 /// arm addition.
242 ///
243 /// Returns `Option<CaixaKind>` rather than `Result<CaixaKind, _>`
244 /// because the existing in-tree consumer [`caixa_from_cr`] carries
245 /// a hard-coded silent fallback (`_ => CaixaKind::Biblioteca`) —
246 /// the fallback's shape is preserved verbatim by the caller's
247 /// `.unwrap_or(CaixaKind::Biblioteca)` on the return value, so
248 /// this lift is a byte-equal behavioral swap on today's call site
249 /// (the CR round-trip is invariant), and future callers that want
250 /// a typed error (a future `feira --kind …` arg-parse that
251 /// surfaces `unknown kind: <arg>` at the CLI) can build one on top
252 /// without disturbing the existing consumer's contract.
253 #[must_use]
254 pub fn from_wire(s: &str) -> Option<Self> {
255 match s {
256 crate::render::CAIXA_KIND_WIRE_BIBLIOTECA => Some(Self::Biblioteca),
257 crate::render::CAIXA_KIND_WIRE_BINARIO => Some(Self::Binario),
258 crate::render::CAIXA_KIND_WIRE_SERVICO => Some(Self::Servico),
259 crate::render::CAIXA_KIND_WIRE_SUPERVISOR => Some(Self::Supervisor),
260 crate::render::CAIXA_KIND_WIRE_APLICACAO => Some(Self::Aplicacao),
261 crate::render::CAIXA_KIND_WIRE_ACAO => Some(Self::Acao),
262 _ => None,
263 }
264 }
265
266 /// The canonical human-readable name.
267 ///
268 /// The five arms route through the paired
269 /// [`crate::render::CAIXA_KIND_LABEL_BIBLIOTECA`] /
270 /// [`crate::render::CAIXA_KIND_LABEL_BINARIO`] /
271 /// [`crate::render::CAIXA_KIND_LABEL_SERVICO`] /
272 /// [`crate::render::CAIXA_KIND_LABEL_SUPERVISOR`] /
273 /// [`crate::render::CAIXA_KIND_LABEL_APLICACAO`] lifted constants so
274 /// every substrate consumer that formats a caixa's typed shape as
275 /// user-facing text (the future wasm-operator's per-caixa startup
276 /// log line, the future `feira app graph` per-member kind column,
277 /// the future M4 CR materializer's admission-webhook rejection
278 /// body) reads the same byte-string the [`std::fmt::Display`] impl
279 /// (routed through this helper) emits — the pin test
280 /// [`tests::caixa_kind_as_str_returns_lifted_peer_const`] asserts
281 /// the five paths agree. Peer of the sibling
282 /// [`crate::supervisor::RestartStrategy::as_str`] (09ffb2d),
283 /// [`crate::supervisor::RestartPolicy::as_str`] (ccdf955), and M3
284 /// [`crate::aplicacao::PlacementStrategy::as_str`] (cc8f749) on the
285 /// sibling closed-set typed-enum discriminator axes — the fourth
286 /// closed-set typed enum on the caixa surface to converge onto the
287 /// same drift-detection posture.
288 ///
289 /// The two axes ([`Self::as_str`] returning lowercase Portuguese
290 /// vs. the un-`rename`d `Serialize` derive emitting PascalCase
291 /// `"Biblioteca"` / `"Binario"` / `"Servico"` / `"Supervisor"` /
292 /// `"Aplicacao"`) are intentionally distinct: the wire format is
293 /// the tatara-lisp author surface (`:kind Biblioteca`), while
294 /// [`Self::as_str`] / [`std::fmt::Display`] emit the substrate's
295 /// canonical human-readable form for diagnostics + graph output +
296 /// audit views. Two paths on this enum surface (rather than three
297 /// as on the sibling OTP-shape and M3 enums where wire ==
298 /// human-readable), but the same "one canonical byte-string per
299 /// axis, routed through a lifted const" discipline.
300 #[must_use]
301 pub const fn as_str(self) -> &'static str {
302 match self {
303 Self::Biblioteca => crate::render::CAIXA_KIND_LABEL_BIBLIOTECA,
304 Self::Binario => crate::render::CAIXA_KIND_LABEL_BINARIO,
305 Self::Servico => crate::render::CAIXA_KIND_LABEL_SERVICO,
306 Self::Supervisor => crate::render::CAIXA_KIND_LABEL_SUPERVISOR,
307 Self::Aplicacao => crate::render::CAIXA_KIND_LABEL_APLICACAO,
308 Self::Acao => crate::render::CAIXA_KIND_LABEL_ACAO,
309 }
310 }
311}
312
313/// [`std::fmt::Display`] routed through [`CaixaKind::as_str`], so the
314/// pretty-printed byte-string every consumer that formats the caixa's
315/// typed shape as user-facing text lands on (the future wasm-operator's
316/// per-caixa startup log line, the future `feira app graph` per-member
317/// kind column, the future M4 `wasm.pleme.io/v1alpha1/ComputeUnit` /
318/// `mesh.pleme.io/v1alpha1/*` CR materializer's admission-webhook
319/// rejection body) reaches for the same lifted
320/// [`crate::render::CAIXA_KIND_LABEL_BIBLIOTECA`] /
321/// [`crate::render::CAIXA_KIND_LABEL_BINARIO`] /
322/// [`crate::render::CAIXA_KIND_LABEL_SERVICO`] /
323/// [`crate::render::CAIXA_KIND_LABEL_SUPERVISOR`] /
324/// [`crate::render::CAIXA_KIND_LABEL_APLICACAO`] const the
325/// [`CaixaKind::as_str`] helper already returns.
326///
327/// Pre-convergence [`CaixaKind`] carried no [`std::fmt::Display`]
328/// surface at all — every consumer past the wire format
329/// (`Serialize` → PascalCase) had to pick between two paths
330/// ([`CaixaKind::as_str`] returning the lowercase Portuguese label, or
331/// `format!("{v:?}")` on the `Debug` derive returning the PascalCase
332/// variant name), each with different bytes on every arm and no
333/// compile-time link between the two — with the failure surfacing as a
334/// downstream consumer's log / graph / diagnostic reading one spelling
335/// while a peer consumer emitted another, far from any single-site
336/// commit. Wiring [`std::fmt::Display`] through [`CaixaKind::as_str`]
337/// closes the drift footgun structurally: every `format!("{v}")` call
338/// reaches the same lifted [`crate::render::CAIXA_KIND_LABEL_*`] const
339/// [`CaixaKind::as_str`] returns, and a future rebrand (a per-consumer
340/// disambiguation of the `:kind` vocabulary, an English-canonical
341/// rebrand of `"biblioteca"` → `"library"` under an M4 substrate-wide-
342/// vocabulary shift) reaches every consumer through exactly one
343/// const-edit.
344///
345/// The wire format axis (`Serialize` derive, PascalCase, tatara-lisp
346/// author surface `:kind Biblioteca`) stays deliberately distinct from
347/// the human-readable axis (`Display` / `as_str`, lowercase Portuguese
348/// diagnostic form): the two-path split is by design, not drift. The
349/// pin test
350/// [`tests::caixa_kind_display_matches_as_str_and_not_serialize_wire`]
351/// makes the split load-bearing so a future accidental collapse of
352/// either axis onto the other (routing `Display` through the wire
353/// format via `serde_json::to_string`, or routing `Serialize` through
354/// [`CaixaKind::as_str`] via `#[serde(rename_all = "…")]`) trips at
355/// build time rather than silently merging the two axes into one at
356/// some future consumer.
357///
358/// Pin tests
359/// [`tests::caixa_kind_display_routes_through_as_str_helper`] and
360/// [`tests::caixa_kind_as_str_returns_lifted_peer_const`] assert the
361/// two paths agree byte-for-byte on every variant, so a future variant
362/// rename or per-arm serde attribute drift is a build error visible at
363/// caixa-core test time.
364///
365/// Mirrors the M3 [`crate::aplicacao::PlacementStrategy`] `Display`
366/// impl (aplicacao.rs:2306), the M2
367/// [`crate::supervisor::RestartStrategy`] `Display` impl
368/// (supervisor.rs:164), and the M2 [`crate::supervisor::RestartPolicy`]
369/// `Display` impl (supervisor.rs:306) on the sibling closed-set typed-
370/// enum discriminator axes — same as_str-through-Display convergence
371/// discipline, extended to close the fourth (and structurally most
372/// fundamental — every caixa carries a `:kind`) closed-set typed-enum
373/// discriminator axis on the caixa typed surface.
374impl std::fmt::Display for CaixaKind {
375 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376 f.write_str(self.as_str())
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383
384 #[test]
385 fn kind_requirements() {
386 assert!(CaixaKind::Biblioteca.requires_lib());
387 assert!(!CaixaKind::Biblioteca.requires_exe());
388 assert!(CaixaKind::Binario.requires_exe());
389 assert!(CaixaKind::Servico.requires_servicos());
390 assert!(CaixaKind::Supervisor.requires_children());
391 assert!(!CaixaKind::Servico.requires_children());
392 assert!(CaixaKind::Acao.requires_ci());
393 assert!(!CaixaKind::Servico.requires_ci());
394 }
395
396 #[test]
397 fn kind_deserializes_from_pascal_symbol() {
398 let v: CaixaKind = serde_json::from_str("\"Biblioteca\"").unwrap();
399 assert_eq!(v, CaixaKind::Biblioteca);
400 let v: CaixaKind = serde_json::from_str("\"Supervisor\"").unwrap();
401 assert_eq!(v, CaixaKind::Supervisor);
402 }
403
404 #[test]
405 fn supervisor_kind_has_canonical_name() {
406 assert_eq!(CaixaKind::Supervisor.as_str(), "supervisor");
407 }
408
409 #[test]
410 fn caixa_kind_as_str_returns_lifted_peer_const() {
411 // The fail-before-pass-after pin: pre-lift the five
412 // [`CaixaKind::as_str`] match arms each returned a hand-authored
413 // byte-string literal (`"biblioteca"` / `"binario"` /
414 // `"servico"` / `"supervisor"` / `"aplicacao"`) with no
415 // compile-time link to any lifted peer const on the sibling
416 // layout-diagnostic axis (whose bytes coincide on `Biblioteca` /
417 // `Servico` by design — see the existing alignment pin in
418 // `crate::layout::tests`). A future rebrand touching either
419 // endpoint (a per-consumer disambiguation of the `:kind`
420 // vocabulary, an English-canonical rename lands on one arm's
421 // byte-string without touching the peer axis) would silently
422 // desynchronize until a downstream consumer surfaced the drift
423 // at diagnostic / graph time. Pinning the five arms to the five
424 // lifted [`crate::render::CAIXA_KIND_LABEL_*`] consts makes any
425 // future drift a caixa-core-build-time failure. Peer of the
426 // sibling
427 // [`crate::supervisor::tests::restart_strategy_variants_serialize_to_lifted_scalar_values`]
428 // (09ffb2d) /
429 // [`crate::supervisor::tests::restart_policy_variants_serialize_to_lifted_scalar_values`]
430 // (ccdf955) /
431 // `placement_strategy_variants_serialize_to_lifted_scalar_values`
432 // (3f0e21c) pins on the sibling closed-set typed-enum
433 // discriminator axes.
434 for (variant, expected) in [
435 (
436 CaixaKind::Biblioteca,
437 crate::render::CAIXA_KIND_LABEL_BIBLIOTECA,
438 ),
439 (CaixaKind::Binario, crate::render::CAIXA_KIND_LABEL_BINARIO),
440 (CaixaKind::Servico, crate::render::CAIXA_KIND_LABEL_SERVICO),
441 (
442 CaixaKind::Supervisor,
443 crate::render::CAIXA_KIND_LABEL_SUPERVISOR,
444 ),
445 (
446 CaixaKind::Aplicacao,
447 crate::render::CAIXA_KIND_LABEL_APLICACAO,
448 ),
449 (CaixaKind::Acao, crate::render::CAIXA_KIND_LABEL_ACAO),
450 ] {
451 assert_eq!(
452 variant.as_str(),
453 expected,
454 "CaixaKind::{variant:?}.as_str() must return the lifted \
455 CAIXA_KIND_LABEL_* const"
456 );
457 }
458 }
459
460 #[test]
461 fn caixa_kind_display_routes_through_as_str_helper() {
462 // The fail-before-pass-after pin on the two-path convergence:
463 // pre-lift [`CaixaKind`] carried no [`std::fmt::Display`]
464 // surface at all — every consumer past the wire format had to
465 // pick between [`CaixaKind::as_str`] returning the lowercase
466 // Portuguese label or `format!("{v:?}")` on the `Debug` derive
467 // returning the PascalCase variant name. Wiring
468 // [`std::fmt::Display`] through [`CaixaKind::as_str`] closes
469 // the drift footgun: every `format!("{v}")` call reaches the
470 // same lifted [`crate::render::CAIXA_KIND_LABEL_*`] const the
471 // [`CaixaKind::as_str`] helper already returns, so a future
472 // variant rename lands at exactly one place. Pin the routing
473 // here so a future `impl std::fmt::Display for CaixaKind`
474 // reimplementation that hand-rolls the arms instead of
475 // delegating to [`CaixaKind::as_str`] fails at caixa-core
476 // build time. Peer of the sibling
477 // [`crate::supervisor::tests::restart_strategy_display_routes_through_as_str_helper`]
478 // /
479 // [`crate::supervisor::tests::restart_policy_display_routes_through_as_str_helper`]
480 // (15a1305) on the sibling M2 closed-set typed-enum
481 // discriminator axes.
482 for &variant in CaixaKind::ALL {
483 assert_eq!(
484 variant.to_string(),
485 variant.as_str(),
486 "CaixaKind::{variant:?} Display must route through \
487 CaixaKind::as_str (single source of truth: the lifted \
488 CAIXA_KIND_LABEL_* const)"
489 );
490 }
491 }
492
493 #[test]
494 fn caixa_kind_display_matches_as_str_and_not_serialize_wire() {
495 // The fail-before-pass-after pin on the two-axis split
496 // discipline: unlike the sibling OTP-shape / M3 typed enums
497 // ([`crate::supervisor::RestartStrategy`],
498 // [`crate::supervisor::RestartPolicy`],
499 // [`crate::aplicacao::PlacementStrategy`]) where the wire
500 // format and the human-readable format share bytes (both
501 // PascalCase / camelCase), [`CaixaKind`] carries two axes with
502 // *distinct* byte-shapes by design: the wire format is
503 // PascalCase (`"Biblioteca"` / `"Binario"` / `"Servico"` /
504 // `"Supervisor"` / `"Aplicacao"` — the tatara-lisp author
505 // surface `:kind Biblioteca`), while [`CaixaKind::as_str`] /
506 // [`std::fmt::Display`] emit the lowercase Portuguese
507 // diagnostic form (`"biblioteca"` etc.). The pin here makes
508 // the split load-bearing: a future accidental collapse of
509 // either axis onto the other (routing `Serialize` through
510 // [`CaixaKind::as_str`] via `#[serde(rename_all = "lowercase")]`,
511 // routing [`std::fmt::Display`] through the wire format
512 // directly) would trip here at caixa-core build time rather
513 // than silently merging the two axes at some future consumer's
514 // dispatch step.
515 for &variant in CaixaKind::ALL {
516 let wire = serde_json::to_string(&variant).unwrap();
517 let unquoted = wire
518 .strip_prefix('"')
519 .and_then(|s| s.strip_suffix('"'))
520 .expect("serialized CaixaKind is a JSON string");
521 let display = variant.to_string();
522 assert_eq!(
523 display,
524 variant.as_str(),
525 "CaixaKind::{variant:?} Display must byte-equal as_str"
526 );
527 assert_ne!(
528 display, unquoted,
529 "CaixaKind::{variant:?} Display / as_str (lowercase Portuguese \
530 diagnostic form) must stay structurally distinct from the \
531 Serialize wire format (PascalCase tatara-lisp author surface)"
532 );
533 assert!(
534 unquoted.chars().next().is_some_and(char::is_uppercase),
535 "CaixaKind::{variant:?} wire format must open with an \
536 uppercase byte (PascalCase tatara-lisp author surface)"
537 );
538 assert!(
539 display.chars().next().is_some_and(char::is_lowercase),
540 "CaixaKind::{variant:?} Display / as_str must open with a \
541 lowercase byte (Portuguese diagnostic form)"
542 );
543 }
544 }
545
546 #[test]
547 fn caixa_kind_label_consts_pin_canonical_bytes() {
548 // Drift-detection pin: the five
549 // [`crate::render::CAIXA_KIND_LABEL_*`] lifted consts must
550 // continue to carry their canonical byte-shapes. A rebrand
551 // touching one const's declaration would silently desynchronize
552 // every consumer this const routes through (the
553 // [`CaixaKind::as_str`] arm, the [`std::fmt::Display`] route,
554 // the byte-shape coincidence pins against
555 // [`crate::render::LAYOUT_MISSING_ENTRY_KIND_BIBLIOTECA`] /
556 // [`crate::render::LAYOUT_MISSING_ENTRY_KIND_SERVICO`] /
557 // [`crate::render::FLEET_PROGRAMS_KEY_APLICACAO`]) — pinning
558 // the five byte-shapes here makes any rebrand attempt a build
559 // error at this call, forcing the author to reason about every
560 // downstream consumer explicitly. Mirror of the peer
561 // drift-detection pins the sibling OTP-shape / M3 closed-set
562 // const families carry (`SUPERVISOR_ESTRATEGIA_*`,
563 // `SUPERVISOR_CHILD_RESTART_*`, `M3_PLACEMENT_ESTRATEGIA_*`).
564 assert_eq!(crate::render::CAIXA_KIND_LABEL_BIBLIOTECA, "biblioteca");
565 assert_eq!(crate::render::CAIXA_KIND_LABEL_BINARIO, "binario");
566 assert_eq!(crate::render::CAIXA_KIND_LABEL_SERVICO, "servico");
567 assert_eq!(crate::render::CAIXA_KIND_LABEL_SUPERVISOR, "supervisor");
568 assert_eq!(crate::render::CAIXA_KIND_LABEL_APLICACAO, "aplicacao");
569 assert_eq!(crate::render::CAIXA_KIND_LABEL_ACAO, "acao");
570 }
571
572 #[test]
573 fn caixa_kind_is_variant_predicates_partition_the_arm_set() {
574 // Fail-before-pass-after pin on the [`gen_platform::IsVariant`]
575 // derive: for each of the five variants, exactly one of the
576 // generated `is_biblioteca` / `is_binario` / `is_servico` /
577 // `is_supervisor` / `is_aplicacao` predicates returns `true` and
578 // the other four return `false`. Prior to this derive the ten
579 // production `caixa.kind() == CaixaKind::X` / `!=` sites in
580 // `layout.rs` (`SupervisorOwnsCode` / `AplicacaoOwnsCode` /
581 // `MeshSlotsOnNonAplicacao` / `SupervisorSlotsOnNonSupervisor` /
582 // `ServicoSlotsOnNonServico` / `MissingLib` biblioteca-fallback
583 // / Supervisor invariants / Aplicacao invariants) plus
584 // `manifest.rs` (`aplicacao_view` / `supervisor_view` kind
585 // gates) each open-coded a per-arm PartialEq compare against
586 // the enum variant — ten sites that expressed no compile-time
587 // link back to the closed-set typed dispatch a future sixth
588 // `:kind` (e.g. an `Actor` virtual-actor arm for the
589 // absorption-roadmap M5 Orleans-inspired kind) would have to
590 // thread through in lockstep or one gate would silently
591 // disagree with the others on which arms it treats as "runs
592 // no code" / "declares mesh slots" / etc. Peer of the sibling
593 // [`crate::supervisor::RestartStrategy`] / [`crate::supervisor::RestartPolicy`] /
594 // [`crate::upgrade::UpgradeInstruction`] `IsVariant` derives on
595 // the sibling closed-set typed-enum discriminator axes — extends
596 // the same one-typed-dispatch-per-variant discipline onto the
597 // fourth (and structurally most fundamental — every caixa
598 // carries a `:kind`) closed-set typed-enum discriminator axis.
599 let rows: [(CaixaKind, [bool; 6]); 6] = [
600 (
601 CaixaKind::Biblioteca,
602 [true, false, false, false, false, false],
603 ),
604 (
605 CaixaKind::Binario,
606 [false, true, false, false, false, false],
607 ),
608 (
609 CaixaKind::Servico,
610 [false, false, true, false, false, false],
611 ),
612 (
613 CaixaKind::Supervisor,
614 [false, false, false, true, false, false],
615 ),
616 (
617 CaixaKind::Aplicacao,
618 [false, false, false, false, true, false],
619 ),
620 (CaixaKind::Acao, [false, false, false, false, false, true]),
621 ];
622 for (variant, expected) in rows {
623 let observed = [
624 variant.is_biblioteca(),
625 variant.is_binario(),
626 variant.is_servico(),
627 variant.is_supervisor(),
628 variant.is_aplicacao(),
629 variant.is_acao(),
630 ];
631 assert_eq!(
632 observed, expected,
633 "CaixaKind::{variant:?} is_* predicates must partition \
634 the arm set (biblioteca, binario, servico, supervisor, \
635 aplicacao, acao); got {observed:?}"
636 );
637 }
638 }
639
640 #[test]
641 fn caixa_kind_is_variant_predicates_are_const_fn() {
642 // The [`gen_platform::IsVariant`] derive emits `const fn`
643 // predicates on the peer [`crate::upgrade::UpgradeInstruction`] +
644 // [`crate::supervisor::RestartStrategy`] +
645 // [`crate::supervisor::RestartPolicy`] closed-set typed enums —
646 // pin the same posture on [`CaixaKind`] so a future accidental
647 // downgrade to non-`const` (an added runtime helper reachable
648 // only from a non-`const` context, a manual hand-rolled `impl`
649 // that shadows the derive-generated method) trips at
650 // caixa-core build time rather than surfacing as a
651 // downstream `const`-context regression far from the derive
652 // declaration.
653 const IS_APLICACAO: bool = CaixaKind::Aplicacao.is_aplicacao();
654 const IS_BIBLIOTECA: bool = CaixaKind::Biblioteca.is_biblioteca();
655 const IS_SERVICO: bool = CaixaKind::Servico.is_servico();
656 const IS_SUPERVISOR: bool = CaixaKind::Supervisor.is_supervisor();
657 const IS_BINARIO: bool = CaixaKind::Binario.is_binario();
658 const IS_ACAO: bool = CaixaKind::Acao.is_acao();
659 assert!(IS_APLICACAO);
660 assert!(IS_BIBLIOTECA);
661 assert!(IS_SERVICO);
662 assert!(IS_SUPERVISOR);
663 assert!(IS_BINARIO);
664 assert!(IS_ACAO);
665 }
666
667 #[test]
668 fn caixa_kind_wire_name_returns_lifted_peer_const() {
669 // Fail-before-pass-after pin: the six [`CaixaKind::wire_name`]
670 // match arms each return one of the paired
671 // [`crate::render::CAIXA_KIND_WIRE_*`] lifted consts, and a
672 // future rebrand touching either endpoint (a per-consumer
673 // disambiguation of the `:kind` vocabulary, a rename lands on
674 // one arm's byte-string without touching the peer axis) would
675 // silently desynchronize the emitter from the paired
676 // [`CaixaKind::from_wire`] parser's accept-set. Pinning the six
677 // arms to the six lifted consts makes any future drift a
678 // caixa-core-build-time failure — peer of the sibling
679 // [`caixa_kind_as_str_returns_lifted_peer_const`] pin on the
680 // human-readable-label axis.
681 for (variant, expected) in [
682 (
683 CaixaKind::Biblioteca,
684 crate::render::CAIXA_KIND_WIRE_BIBLIOTECA,
685 ),
686 (CaixaKind::Binario, crate::render::CAIXA_KIND_WIRE_BINARIO),
687 (CaixaKind::Servico, crate::render::CAIXA_KIND_WIRE_SERVICO),
688 (
689 CaixaKind::Supervisor,
690 crate::render::CAIXA_KIND_WIRE_SUPERVISOR,
691 ),
692 (
693 CaixaKind::Aplicacao,
694 crate::render::CAIXA_KIND_WIRE_APLICACAO,
695 ),
696 (CaixaKind::Acao, crate::render::CAIXA_KIND_WIRE_ACAO),
697 ] {
698 assert_eq!(
699 variant.wire_name(),
700 expected,
701 "CaixaKind::{variant:?}.wire_name() must return the \
702 lifted CAIXA_KIND_WIRE_* const"
703 );
704 }
705 }
706
707 #[test]
708 fn caixa_kind_wire_name_matches_serialize_wire_byte_string() {
709 // Load-bearing pin on the derive-to-const identity: the six
710 // [`CaixaKind::wire_name`] outputs must byte-equal the
711 // un-`rename`d `Serialize` derive's per-arm wire scalar (the
712 // JSON quoted-string with its outer quotes stripped). A future
713 // accidental `#[serde(rename_all = "…")]` attribute drift at
714 // the derive surface would silently split the wire byte-shape
715 // every K8s-CR / tatara-lisp author-surface / future-M4-CR
716 // materializer consumer emits through this typed dispatch from
717 // the shape the un-`rename`d derive projects — the drift's
718 // apply-time symptom (a K8s Caixa CR whose `spec.kind` no
719 // longer round-trips through `caixa_from_cr` because the
720 // reverse [`CaixaKind::from_wire`] parser rejects the new
721 // rename-transformed byte-string) would surface far from the
722 // derive attribute's commit. Pinning the identity here makes
723 // any such drift a caixa-core-build-time failure. Peer of the
724 // sibling
725 // [`caixa_kind_display_matches_as_str_and_not_serialize_wire`]
726 // pin — that one keeps `Display` structurally *distinct* from
727 // the wire byte-shape (by design); this one keeps
728 // [`CaixaKind::wire_name`] structurally *aligned* with the
729 // wire byte-shape (by design).
730 for &variant in CaixaKind::ALL {
731 let json = serde_json::to_string(&variant).unwrap();
732 let unquoted = json
733 .strip_prefix('"')
734 .and_then(|s| s.strip_suffix('"'))
735 .expect("serialized CaixaKind is a JSON string");
736 assert_eq!(
737 variant.wire_name(),
738 unquoted,
739 "CaixaKind::{variant:?}.wire_name() must byte-equal the \
740 un-renamed Serialize derive's wire scalar — a mismatch \
741 means either the derive attributes drifted or the \
742 CAIXA_KIND_WIRE_* const family drifted; either way \
743 downstream K8s-CR round-trip through caixa_from_cr \
744 silently splits from the accessor-routed source of \
745 truth"
746 );
747 }
748 }
749
750 #[test]
751 fn caixa_kind_wire_round_trips_through_from_wire() {
752 // Total-round-trip pin on the (wire_name, from_wire) pair:
753 // every arm's [`CaixaKind::wire_name`] output must parse back
754 // through [`CaixaKind::from_wire`] to the same variant. Any
755 // future accessor extension that adds a new arm to one side of
756 // the pair without extending the other — a new `CaixaKind`
757 // variant whose `wire_name` arm lands but whose `from_wire`
758 // arm is forgotten, or a rename that touches `wire_name`'s
759 // per-arm const without threading through `from_wire`'s peer
760 // arm — trips here at caixa-core build time rather than
761 // surfacing as a downstream K8s-CR round-trip miss (a
762 // `caixa_into_cr` emit that lands a `spec.kind` byte-string
763 // the paired `caixa_from_cr`'s `CaixaKind::from_wire` cannot
764 // parse, silently falling through to the caller's
765 // `.unwrap_or(CaixaKind::Biblioteca)` fallback).
766 for &variant in CaixaKind::ALL {
767 let wire = variant.wire_name();
768 let parsed = CaixaKind::from_wire(wire).unwrap_or_else(|| {
769 panic!(
770 "CaixaKind::from_wire({wire:?}) must accept every \
771 CaixaKind::wire_name output — got None for the \
772 wire byte-string of {variant:?}"
773 )
774 });
775 assert_eq!(
776 parsed, variant,
777 "CaixaKind::from_wire(CaixaKind::{variant:?}.wire_name()) \
778 must return CaixaKind::{variant:?} — the (wire_name, \
779 from_wire) pair must form a total round-trip on the \
780 closed six-arm CaixaKind arm-set"
781 );
782 }
783 }
784
785 #[test]
786 fn caixa_kind_from_wire_rejects_unknown_byte_strings() {
787 // Rejection pin on the parser's accept-set: any string outside
788 // the six-arm [`CaixaKind::wire_name`] output set must return
789 // `None`. A future accidental widening of the accept-set (a
790 // case-insensitive match that accepts `"biblioteca"` on the
791 // wire axis, a hand-rolled Levenshtein-forgiving arm-lookup
792 // that admits `"Biblioteka"` typos) would silently drift the
793 // parser's accept-set from the emitter's — a K8s CR carrying
794 // a malformed `spec.kind` byte-string that today's parser
795 // rejects (letting the caller's `.unwrap_or(Biblioteca)`
796 // fallback fire on the operator-visible-drift path) would then
797 // land on a plausibly-wrong typed arm the caller does not
798 // route through the fallback, silently binding the CR to the
799 // wrong runtime contract. Also rejects the sibling
800 // lowercase-Portuguese diagnostic-form strings (`"biblioteca"`
801 // / `"servico"`), which are the *human-readable* form the
802 // [`CaixaKind::as_str`] axis emits — the two-axis split
803 // documented on the sibling
804 // [`caixa_kind_display_matches_as_str_and_not_serialize_wire`]
805 // pin explicitly forbids accepting one axis's byte-shapes as
806 // parseable on the other axis.
807 for bad in [
808 "",
809 "biblioteca",
810 "servico",
811 "aplicacao",
812 "ACao",
813 "unknown",
814 "actor",
815 "Biblioteka",
816 ] {
817 assert!(
818 CaixaKind::from_wire(bad).is_none(),
819 "CaixaKind::from_wire({bad:?}) must return None — the \
820 parser's accept-set is exactly the six CaixaKind::wire_name \
821 outputs; a widening would silently split the parser's \
822 accept-set from the emitter's"
823 );
824 }
825 }
826
827 #[test]
828 fn caixa_kind_wire_name_is_const_fn() {
829 // Const-context pin: [`CaixaKind::wire_name`] must remain
830 // `const fn` (its match arms return `pub const` byte-strings,
831 // so no non-const operation exists on the resolution path).
832 // Downstream consumers reaching for the accessor from a
833 // `const` context (a future substrate-wide const-fold-driven
834 // audit table that materializes every kind's wire byte-string
835 // at build time, a `const` gate on a per-arm CR-schema
836 // registration) rely on the const-ness. A future accidental
837 // downgrade to non-`const` (an added runtime helper reachable
838 // only from a non-`const` context, a manual hand-rolled `impl`
839 // that shadows this method) trips at caixa-core build time
840 // rather than surfacing as a downstream `const`-context
841 // regression far from the accessor declaration. Peer of the
842 // sibling [`caixa_kind_is_variant_predicates_are_const_fn`]
843 // pin on the [`gen_platform::IsVariant`] derive's per-arm
844 // predicates.
845 const BIBLIOTECA_WIRE: &str = CaixaKind::Biblioteca.wire_name();
846 const BINARIO_WIRE: &str = CaixaKind::Binario.wire_name();
847 const SERVICO_WIRE: &str = CaixaKind::Servico.wire_name();
848 const SUPERVISOR_WIRE: &str = CaixaKind::Supervisor.wire_name();
849 const APLICACAO_WIRE: &str = CaixaKind::Aplicacao.wire_name();
850 const ACAO_WIRE: &str = CaixaKind::Acao.wire_name();
851 assert_eq!(BIBLIOTECA_WIRE, "Biblioteca");
852 assert_eq!(BINARIO_WIRE, "Binario");
853 assert_eq!(SERVICO_WIRE, "Servico");
854 assert_eq!(SUPERVISOR_WIRE, "Supervisor");
855 assert_eq!(APLICACAO_WIRE, "Aplicacao");
856 assert_eq!(ACAO_WIRE, "Acao");
857 }
858
859 #[test]
860 fn caixa_kind_wire_consts_are_pairwise_distinct() {
861 // Distinctness pin: the six [`crate::render::CAIXA_KIND_WIRE_*`]
862 // consts must be pairwise distinct — an accidental copy-paste
863 // flip that reroutes one arm's byte-string to also match
864 // another silently collapses two per-kind wire arms onto one,
865 // so a downstream K8s CR carrying the collapsed byte-string
866 // round-trips through [`CaixaKind::from_wire`] to whichever
867 // arm the parser's match cascade lands on first (the collapse
868 // makes the outcome match-arm-ordering-dependent). Peer of the
869 // sibling [`caixa_kind_label_consts_are_pairwise_distinct`]
870 // pin on the human-readable-label axis.
871 let all = [
872 crate::render::CAIXA_KIND_WIRE_BIBLIOTECA,
873 crate::render::CAIXA_KIND_WIRE_BINARIO,
874 crate::render::CAIXA_KIND_WIRE_SERVICO,
875 crate::render::CAIXA_KIND_WIRE_SUPERVISOR,
876 crate::render::CAIXA_KIND_WIRE_APLICACAO,
877 crate::render::CAIXA_KIND_WIRE_ACAO,
878 ];
879 for (i, a) in all.iter().enumerate() {
880 for (j, b) in all.iter().enumerate() {
881 if i != j {
882 assert_ne!(
883 a, b,
884 "CAIXA_KIND_WIRE_* consts must be pairwise \
885 distinct — found duplicate byte-string {a:?} \
886 at indices {i} and {j}"
887 );
888 }
889 }
890 }
891 }
892
893 #[test]
894 fn caixa_kind_label_consts_are_pairwise_distinct() {
895 // Distinctness pin: the five
896 // [`crate::render::CAIXA_KIND_LABEL_*`] consts must be pairwise
897 // distinct — an accidental copy-paste flip that reroutes one
898 // label's byte-string to also match another silently collapses
899 // two per-kind labels onto one, so an operator reads
900 // `biblioteca` for what should have surfaced as `servico` (or
901 // vice versa). This pin catches any such flip at build time.
902 // Mirror of the peer distinctness pins on other closed-set
903 // typed axes (e.g.
904 // `layout_missing_entry_kind_consts_are_pairwise_distinct`).
905 let all = [
906 crate::render::CAIXA_KIND_LABEL_BIBLIOTECA,
907 crate::render::CAIXA_KIND_LABEL_BINARIO,
908 crate::render::CAIXA_KIND_LABEL_SERVICO,
909 crate::render::CAIXA_KIND_LABEL_SUPERVISOR,
910 crate::render::CAIXA_KIND_LABEL_APLICACAO,
911 crate::render::CAIXA_KIND_LABEL_ACAO,
912 ];
913 for (i, a) in all.iter().enumerate() {
914 for (j, b) in all.iter().enumerate() {
915 if i != j {
916 assert_ne!(
917 a, b,
918 "CAIXA_KIND_LABEL_* consts must be pairwise distinct \
919 — found duplicate byte-string {a:?} at indices {i} \
920 and {j}"
921 );
922 }
923 }
924 }
925 }
926
927 #[test]
928 fn caixa_kind_all_enumerates_every_variant_exactly_once() {
929 // Fail-before-pass-after pin on the [`CaixaKind::ALL`]
930 // exhaustive-iteration surface: the slice length matches the
931 // arm count of the closed six-arm set, every variant appears
932 // at least once, and no variant appears twice. A future arm
933 // addition (an `Actor` virtual-actor arm the M5 Orleans-
934 // inspired kind reaches through, per the sibling
935 // [`CaixaKind::from_wire`] doc block) that grows the enum but
936 // forgets to grow [`Self::ALL`] silently truncates every
937 // downstream consumer's accept-set at the pre-addition
938 // boundary — a future `feira --kind …` CLI-side "did you
939 // mean" scan that iterates the slice, the future M4
940 // admission-webhook's rejection body naming the accepted-
941 // `:kind` list, any future round-trip fuzz harness — all read
942 // through this slice, so a truncation there silently splits
943 // every accept-set from the arm-set the paired
944 // [`gen_platform::IsVariant`] predicates + [`Self::wire_name`]
945 // / [`Self::as_str`] / [`Self::from_wire`] siblings walk.
946 // Pinning the exhaustive-enumeration invariant here catches
947 // the drift at caixa-core build time.
948 //
949 // Peer of the sibling
950 // [`crate::aplicacao::tests::placement_strategy_all_enumerates_every_variant_once`]
951 // (18c7342) / `rate_limit_unit_all_enumerates_every_variant_once`
952 // (6bce03d) / `dep_list_all_enumerates_every_variant_once`
953 // (45ee563) pins on the peer closed-set typed-enum axes.
954 let all: &[CaixaKind] = CaixaKind::ALL;
955 assert_eq!(
956 all.len(),
957 6,
958 "CaixaKind::ALL must enumerate every variant of the \
959 six-arm closed set (Biblioteca, Binario, Servico, \
960 Supervisor, Aplicacao, Acao); got {all:?}"
961 );
962 for (i, a) in all.iter().enumerate() {
963 for (j, b) in all.iter().enumerate() {
964 if i != j {
965 assert_ne!(
966 a, b,
967 "CaixaKind::ALL must carry every variant \
968 exactly once — got duplicate {a:?} at \
969 indices {i} and {j}"
970 );
971 }
972 }
973 }
974 for variant in [
975 CaixaKind::Biblioteca,
976 CaixaKind::Binario,
977 CaixaKind::Servico,
978 CaixaKind::Supervisor,
979 CaixaKind::Aplicacao,
980 CaixaKind::Acao,
981 ] {
982 assert!(
983 all.contains(&variant),
984 "CaixaKind::ALL must contain {variant:?} — a future \
985 variant addition that grows the enum but forgets to \
986 grow the ALL slice silently truncates every \
987 downstream consumer's accept-set at the pre-addition \
988 boundary"
989 );
990 }
991 }
992
993 #[test]
994 fn caixa_kind_all_is_the_from_wire_accept_set() {
995 // Load-bearing pin on the two-axis identity: [`CaixaKind::ALL`]
996 // is exactly the variant image of the [`CaixaKind::from_wire`]
997 // accept-set — every arm in the slice parses back through
998 // `from_wire ∘ wire_name` to itself, and the paired
999 // [`caixa_kind_from_wire_rejects_unknown_byte_strings`] pin
1000 // guarantees `from_wire` rejects everything outside the six-
1001 // arm wire-string image. Together these two pins make the
1002 // slice the authoritative arm-set every consumer of the
1003 // closed six-arm [`CaixaKind`] discriminator reads through:
1004 // the future M4 admission-webhook can enumerate accepted
1005 // `:kind` values by walking [`Self::ALL`] and rendering each
1006 // arm's `wire_name`, the future `feira --kind …` "did you
1007 // mean" hint can score against the same slice, and no
1008 // consumer needs to re-inline a six-arm literal list. Any
1009 // future arm addition that grows one axis and forgets the
1010 // other trips here at caixa-core build time.
1011 for &variant in CaixaKind::ALL {
1012 let wire = variant.wire_name();
1013 assert_eq!(
1014 CaixaKind::from_wire(wire),
1015 Some(variant),
1016 "CaixaKind::from_wire(CaixaKind::{variant:?}.wire_name() = \
1017 {wire:?}) must return Some({variant:?}) — CaixaKind::ALL \
1018 must be a subset of the from_wire accept-set"
1019 );
1020 }
1021 }
1022
1023 #[test]
1024 fn caixa_kind_all_is_const_and_matches_iteration_count() {
1025 // Const-context pin: [`CaixaKind::ALL`] is a `pub const`
1026 // slice, so it materializes at build time. Downstream
1027 // consumers reaching for the slice from a `const` context (a
1028 // future substrate-wide const-fold-driven audit table that
1029 // materializes every kind's wire byte-string at build time, a
1030 // per-arm CR-schema registration in a `const` gate) rely on
1031 // the const-ness. A future accidental downgrade to a runtime
1032 // `fn ALL() -> Vec<Self>` reachable only from a non-`const`
1033 // context trips at caixa-core build time. Peer of the sibling
1034 // [`caixa_kind_wire_name_is_const_fn`] +
1035 // [`caixa_kind_is_variant_predicates_are_const_fn`] pins on
1036 // the peer accessor axes.
1037 const ALL: &[CaixaKind] = CaixaKind::ALL;
1038 const LEN: usize = ALL.len();
1039 assert_eq!(
1040 LEN, 6,
1041 "CaixaKind::ALL length must byte-equal the six-arm closed-set \
1042 cardinality at const-fold time"
1043 );
1044 }
1045}