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 //
654 // The pin lives inside a `const { assert!(..) }` block so the
655 // compiler enforces both halves (arm predicate is `const`-
656 // callable AND returns `true` for the matching arm) at
657 // caixa-core compile time — a `#[test]` body without the
658 // `const { .. }` wrapper would enforce const-callability at
659 // compile time (through the named `const` binding) but the
660 // arm-value pin only at test-run time, splitting the
661 // enforcement axis into two windows.
662 const {
663 assert!(CaixaKind::Aplicacao.is_aplicacao());
664 assert!(CaixaKind::Biblioteca.is_biblioteca());
665 assert!(CaixaKind::Servico.is_servico());
666 assert!(CaixaKind::Supervisor.is_supervisor());
667 assert!(CaixaKind::Binario.is_binario());
668 assert!(CaixaKind::Acao.is_acao());
669 }
670 }
671
672 #[test]
673 fn caixa_kind_wire_name_returns_lifted_peer_const() {
674 // Fail-before-pass-after pin: the six [`CaixaKind::wire_name`]
675 // match arms each return one of the paired
676 // [`crate::render::CAIXA_KIND_WIRE_*`] lifted consts, and a
677 // future rebrand touching either endpoint (a per-consumer
678 // disambiguation of the `:kind` vocabulary, a rename lands on
679 // one arm's byte-string without touching the peer axis) would
680 // silently desynchronize the emitter from the paired
681 // [`CaixaKind::from_wire`] parser's accept-set. Pinning the six
682 // arms to the six lifted consts makes any future drift a
683 // caixa-core-build-time failure — peer of the sibling
684 // [`caixa_kind_as_str_returns_lifted_peer_const`] pin on the
685 // human-readable-label axis.
686 for (variant, expected) in [
687 (
688 CaixaKind::Biblioteca,
689 crate::render::CAIXA_KIND_WIRE_BIBLIOTECA,
690 ),
691 (CaixaKind::Binario, crate::render::CAIXA_KIND_WIRE_BINARIO),
692 (CaixaKind::Servico, crate::render::CAIXA_KIND_WIRE_SERVICO),
693 (
694 CaixaKind::Supervisor,
695 crate::render::CAIXA_KIND_WIRE_SUPERVISOR,
696 ),
697 (
698 CaixaKind::Aplicacao,
699 crate::render::CAIXA_KIND_WIRE_APLICACAO,
700 ),
701 (CaixaKind::Acao, crate::render::CAIXA_KIND_WIRE_ACAO),
702 ] {
703 assert_eq!(
704 variant.wire_name(),
705 expected,
706 "CaixaKind::{variant:?}.wire_name() must return the \
707 lifted CAIXA_KIND_WIRE_* const"
708 );
709 }
710 }
711
712 #[test]
713 fn caixa_kind_wire_name_matches_serialize_wire_byte_string() {
714 // Load-bearing pin on the derive-to-const identity: the six
715 // [`CaixaKind::wire_name`] outputs must byte-equal the
716 // un-`rename`d `Serialize` derive's per-arm wire scalar (the
717 // JSON quoted-string with its outer quotes stripped). A future
718 // accidental `#[serde(rename_all = "…")]` attribute drift at
719 // the derive surface would silently split the wire byte-shape
720 // every K8s-CR / tatara-lisp author-surface / future-M4-CR
721 // materializer consumer emits through this typed dispatch from
722 // the shape the un-`rename`d derive projects — the drift's
723 // apply-time symptom (a K8s Caixa CR whose `spec.kind` no
724 // longer round-trips through `caixa_from_cr` because the
725 // reverse [`CaixaKind::from_wire`] parser rejects the new
726 // rename-transformed byte-string) would surface far from the
727 // derive attribute's commit. Pinning the identity here makes
728 // any such drift a caixa-core-build-time failure. Peer of the
729 // sibling
730 // [`caixa_kind_display_matches_as_str_and_not_serialize_wire`]
731 // pin — that one keeps `Display` structurally *distinct* from
732 // the wire byte-shape (by design); this one keeps
733 // [`CaixaKind::wire_name`] structurally *aligned* with the
734 // wire byte-shape (by design).
735 for &variant in CaixaKind::ALL {
736 let json = serde_json::to_string(&variant).unwrap();
737 let unquoted = json
738 .strip_prefix('"')
739 .and_then(|s| s.strip_suffix('"'))
740 .expect("serialized CaixaKind is a JSON string");
741 assert_eq!(
742 variant.wire_name(),
743 unquoted,
744 "CaixaKind::{variant:?}.wire_name() must byte-equal the \
745 un-renamed Serialize derive's wire scalar — a mismatch \
746 means either the derive attributes drifted or the \
747 CAIXA_KIND_WIRE_* const family drifted; either way \
748 downstream K8s-CR round-trip through caixa_from_cr \
749 silently splits from the accessor-routed source of \
750 truth"
751 );
752 }
753 }
754
755 #[test]
756 fn caixa_kind_wire_round_trips_through_from_wire() {
757 // Total-round-trip pin on the (wire_name, from_wire) pair:
758 // every arm's [`CaixaKind::wire_name`] output must parse back
759 // through [`CaixaKind::from_wire`] to the same variant. Any
760 // future accessor extension that adds a new arm to one side of
761 // the pair without extending the other — a new `CaixaKind`
762 // variant whose `wire_name` arm lands but whose `from_wire`
763 // arm is forgotten, or a rename that touches `wire_name`'s
764 // per-arm const without threading through `from_wire`'s peer
765 // arm — trips here at caixa-core build time rather than
766 // surfacing as a downstream K8s-CR round-trip miss (a
767 // `caixa_into_cr` emit that lands a `spec.kind` byte-string
768 // the paired `caixa_from_cr`'s `CaixaKind::from_wire` cannot
769 // parse, silently falling through to the caller's
770 // `.unwrap_or(CaixaKind::Biblioteca)` fallback).
771 for &variant in CaixaKind::ALL {
772 let wire = variant.wire_name();
773 let parsed = CaixaKind::from_wire(wire).unwrap_or_else(|| {
774 panic!(
775 "CaixaKind::from_wire({wire:?}) must accept every \
776 CaixaKind::wire_name output — got None for the \
777 wire byte-string of {variant:?}"
778 )
779 });
780 assert_eq!(
781 parsed, variant,
782 "CaixaKind::from_wire(CaixaKind::{variant:?}.wire_name()) \
783 must return CaixaKind::{variant:?} — the (wire_name, \
784 from_wire) pair must form a total round-trip on the \
785 closed six-arm CaixaKind arm-set"
786 );
787 }
788 }
789
790 #[test]
791 fn caixa_kind_from_wire_rejects_unknown_byte_strings() {
792 // Rejection pin on the parser's accept-set: any string outside
793 // the six-arm [`CaixaKind::wire_name`] output set must return
794 // `None`. A future accidental widening of the accept-set (a
795 // case-insensitive match that accepts `"biblioteca"` on the
796 // wire axis, a hand-rolled Levenshtein-forgiving arm-lookup
797 // that admits `"Biblioteka"` typos) would silently drift the
798 // parser's accept-set from the emitter's — a K8s CR carrying
799 // a malformed `spec.kind` byte-string that today's parser
800 // rejects (letting the caller's `.unwrap_or(Biblioteca)`
801 // fallback fire on the operator-visible-drift path) would then
802 // land on a plausibly-wrong typed arm the caller does not
803 // route through the fallback, silently binding the CR to the
804 // wrong runtime contract. Also rejects the sibling
805 // lowercase-Portuguese diagnostic-form strings (`"biblioteca"`
806 // / `"servico"`), which are the *human-readable* form the
807 // [`CaixaKind::as_str`] axis emits — the two-axis split
808 // documented on the sibling
809 // [`caixa_kind_display_matches_as_str_and_not_serialize_wire`]
810 // pin explicitly forbids accepting one axis's byte-shapes as
811 // parseable on the other axis.
812 for bad in [
813 "",
814 "biblioteca",
815 "servico",
816 "aplicacao",
817 "ACao",
818 "unknown",
819 "actor",
820 "Biblioteka",
821 ] {
822 assert!(
823 CaixaKind::from_wire(bad).is_none(),
824 "CaixaKind::from_wire({bad:?}) must return None — the \
825 parser's accept-set is exactly the six CaixaKind::wire_name \
826 outputs; a widening would silently split the parser's \
827 accept-set from the emitter's"
828 );
829 }
830 }
831
832 #[test]
833 fn caixa_kind_wire_name_is_const_fn() {
834 // Const-context pin: [`CaixaKind::wire_name`] must remain
835 // `const fn` (its match arms return `pub const` byte-strings,
836 // so no non-const operation exists on the resolution path).
837 // Downstream consumers reaching for the accessor from a
838 // `const` context (a future substrate-wide const-fold-driven
839 // audit table that materializes every kind's wire byte-string
840 // at build time, a `const` gate on a per-arm CR-schema
841 // registration) rely on the const-ness. A future accidental
842 // downgrade to non-`const` (an added runtime helper reachable
843 // only from a non-`const` context, a manual hand-rolled `impl`
844 // that shadows this method) trips at caixa-core build time
845 // rather than surfacing as a downstream `const`-context
846 // regression far from the accessor declaration. Peer of the
847 // sibling [`caixa_kind_is_variant_predicates_are_const_fn`]
848 // pin on the [`gen_platform::IsVariant`] derive's per-arm
849 // predicates.
850 const BIBLIOTECA_WIRE: &str = CaixaKind::Biblioteca.wire_name();
851 const BINARIO_WIRE: &str = CaixaKind::Binario.wire_name();
852 const SERVICO_WIRE: &str = CaixaKind::Servico.wire_name();
853 const SUPERVISOR_WIRE: &str = CaixaKind::Supervisor.wire_name();
854 const APLICACAO_WIRE: &str = CaixaKind::Aplicacao.wire_name();
855 const ACAO_WIRE: &str = CaixaKind::Acao.wire_name();
856 assert_eq!(BIBLIOTECA_WIRE, "Biblioteca");
857 assert_eq!(BINARIO_WIRE, "Binario");
858 assert_eq!(SERVICO_WIRE, "Servico");
859 assert_eq!(SUPERVISOR_WIRE, "Supervisor");
860 assert_eq!(APLICACAO_WIRE, "Aplicacao");
861 assert_eq!(ACAO_WIRE, "Acao");
862 }
863
864 #[test]
865 fn caixa_kind_wire_consts_are_pairwise_distinct() {
866 // Distinctness pin: the six [`crate::render::CAIXA_KIND_WIRE_*`]
867 // consts must be pairwise distinct — an accidental copy-paste
868 // flip that reroutes one arm's byte-string to also match
869 // another silently collapses two per-kind wire arms onto one,
870 // so a downstream K8s CR carrying the collapsed byte-string
871 // round-trips through [`CaixaKind::from_wire`] to whichever
872 // arm the parser's match cascade lands on first (the collapse
873 // makes the outcome match-arm-ordering-dependent). Peer of the
874 // sibling [`caixa_kind_label_consts_are_pairwise_distinct`]
875 // pin on the human-readable-label axis.
876 let all = [
877 crate::render::CAIXA_KIND_WIRE_BIBLIOTECA,
878 crate::render::CAIXA_KIND_WIRE_BINARIO,
879 crate::render::CAIXA_KIND_WIRE_SERVICO,
880 crate::render::CAIXA_KIND_WIRE_SUPERVISOR,
881 crate::render::CAIXA_KIND_WIRE_APLICACAO,
882 crate::render::CAIXA_KIND_WIRE_ACAO,
883 ];
884 for (i, a) in all.iter().enumerate() {
885 for (j, b) in all.iter().enumerate() {
886 if i != j {
887 assert_ne!(
888 a, b,
889 "CAIXA_KIND_WIRE_* consts must be pairwise \
890 distinct — found duplicate byte-string {a:?} \
891 at indices {i} and {j}"
892 );
893 }
894 }
895 }
896 }
897
898 #[test]
899 fn caixa_kind_label_consts_are_pairwise_distinct() {
900 // Distinctness pin: the five
901 // [`crate::render::CAIXA_KIND_LABEL_*`] consts must be pairwise
902 // distinct — an accidental copy-paste flip that reroutes one
903 // label's byte-string to also match another silently collapses
904 // two per-kind labels onto one, so an operator reads
905 // `biblioteca` for what should have surfaced as `servico` (or
906 // vice versa). This pin catches any such flip at build time.
907 // Mirror of the peer distinctness pins on other closed-set
908 // typed axes (e.g.
909 // `layout_missing_entry_kind_consts_are_pairwise_distinct`).
910 let all = [
911 crate::render::CAIXA_KIND_LABEL_BIBLIOTECA,
912 crate::render::CAIXA_KIND_LABEL_BINARIO,
913 crate::render::CAIXA_KIND_LABEL_SERVICO,
914 crate::render::CAIXA_KIND_LABEL_SUPERVISOR,
915 crate::render::CAIXA_KIND_LABEL_APLICACAO,
916 crate::render::CAIXA_KIND_LABEL_ACAO,
917 ];
918 for (i, a) in all.iter().enumerate() {
919 for (j, b) in all.iter().enumerate() {
920 if i != j {
921 assert_ne!(
922 a, b,
923 "CAIXA_KIND_LABEL_* consts must be pairwise distinct \
924 — found duplicate byte-string {a:?} at indices {i} \
925 and {j}"
926 );
927 }
928 }
929 }
930 }
931
932 #[test]
933 fn caixa_kind_all_enumerates_every_variant_exactly_once() {
934 // Fail-before-pass-after pin on the [`CaixaKind::ALL`]
935 // exhaustive-iteration surface: the slice length matches the
936 // arm count of the closed six-arm set, every variant appears
937 // at least once, and no variant appears twice. A future arm
938 // addition (an `Actor` virtual-actor arm the M5 Orleans-
939 // inspired kind reaches through, per the sibling
940 // [`CaixaKind::from_wire`] doc block) that grows the enum but
941 // forgets to grow [`Self::ALL`] silently truncates every
942 // downstream consumer's accept-set at the pre-addition
943 // boundary — a future `feira --kind …` CLI-side "did you
944 // mean" scan that iterates the slice, the future M4
945 // admission-webhook's rejection body naming the accepted-
946 // `:kind` list, any future round-trip fuzz harness — all read
947 // through this slice, so a truncation there silently splits
948 // every accept-set from the arm-set the paired
949 // [`gen_platform::IsVariant`] predicates + [`Self::wire_name`]
950 // / [`Self::as_str`] / [`Self::from_wire`] siblings walk.
951 // Pinning the exhaustive-enumeration invariant here catches
952 // the drift at caixa-core build time.
953 //
954 // Peer of the sibling
955 // [`crate::aplicacao::tests::placement_strategy_all_enumerates_every_variant_once`]
956 // (18c7342) / `rate_limit_unit_all_enumerates_every_variant_once`
957 // (6bce03d) / `dep_list_all_enumerates_every_variant_once`
958 // (45ee563) pins on the peer closed-set typed-enum axes.
959 let all: &[CaixaKind] = CaixaKind::ALL;
960 assert_eq!(
961 all.len(),
962 6,
963 "CaixaKind::ALL must enumerate every variant of the \
964 six-arm closed set (Biblioteca, Binario, Servico, \
965 Supervisor, Aplicacao, Acao); got {all:?}"
966 );
967 for (i, a) in all.iter().enumerate() {
968 for (j, b) in all.iter().enumerate() {
969 if i != j {
970 assert_ne!(
971 a, b,
972 "CaixaKind::ALL must carry every variant \
973 exactly once — got duplicate {a:?} at \
974 indices {i} and {j}"
975 );
976 }
977 }
978 }
979 for variant in [
980 CaixaKind::Biblioteca,
981 CaixaKind::Binario,
982 CaixaKind::Servico,
983 CaixaKind::Supervisor,
984 CaixaKind::Aplicacao,
985 CaixaKind::Acao,
986 ] {
987 assert!(
988 all.contains(&variant),
989 "CaixaKind::ALL must contain {variant:?} — a future \
990 variant addition that grows the enum but forgets to \
991 grow the ALL slice silently truncates every \
992 downstream consumer's accept-set at the pre-addition \
993 boundary"
994 );
995 }
996 }
997
998 #[test]
999 fn caixa_kind_all_is_the_from_wire_accept_set() {
1000 // Load-bearing pin on the two-axis identity: [`CaixaKind::ALL`]
1001 // is exactly the variant image of the [`CaixaKind::from_wire`]
1002 // accept-set — every arm in the slice parses back through
1003 // `from_wire ∘ wire_name` to itself, and the paired
1004 // [`caixa_kind_from_wire_rejects_unknown_byte_strings`] pin
1005 // guarantees `from_wire` rejects everything outside the six-
1006 // arm wire-string image. Together these two pins make the
1007 // slice the authoritative arm-set every consumer of the
1008 // closed six-arm [`CaixaKind`] discriminator reads through:
1009 // the future M4 admission-webhook can enumerate accepted
1010 // `:kind` values by walking [`Self::ALL`] and rendering each
1011 // arm's `wire_name`, the future `feira --kind …` "did you
1012 // mean" hint can score against the same slice, and no
1013 // consumer needs to re-inline a six-arm literal list. Any
1014 // future arm addition that grows one axis and forgets the
1015 // other trips here at caixa-core build time.
1016 for &variant in CaixaKind::ALL {
1017 let wire = variant.wire_name();
1018 assert_eq!(
1019 CaixaKind::from_wire(wire),
1020 Some(variant),
1021 "CaixaKind::from_wire(CaixaKind::{variant:?}.wire_name() = \
1022 {wire:?}) must return Some({variant:?}) — CaixaKind::ALL \
1023 must be a subset of the from_wire accept-set"
1024 );
1025 }
1026 }
1027
1028 #[test]
1029 fn caixa_kind_all_is_const_and_matches_iteration_count() {
1030 // Const-context pin: [`CaixaKind::ALL`] is a `pub const`
1031 // slice, so it materializes at build time. Downstream
1032 // consumers reaching for the slice from a `const` context (a
1033 // future substrate-wide const-fold-driven audit table that
1034 // materializes every kind's wire byte-string at build time, a
1035 // per-arm CR-schema registration in a `const` gate) rely on
1036 // the const-ness. A future accidental downgrade to a runtime
1037 // `fn ALL() -> Vec<Self>` reachable only from a non-`const`
1038 // context trips at caixa-core build time. Peer of the sibling
1039 // [`caixa_kind_wire_name_is_const_fn`] +
1040 // [`caixa_kind_is_variant_predicates_are_const_fn`] pins on
1041 // the peer accessor axes.
1042 const ALL: &[CaixaKind] = CaixaKind::ALL;
1043 const LEN: usize = ALL.len();
1044 assert_eq!(
1045 LEN, 6,
1046 "CaixaKind::ALL length must byte-equal the six-arm closed-set \
1047 cardinality at const-fold time"
1048 );
1049 }
1050}