pub fn require_v0_servico_shape<E>(caixa: &Caixa) -> Result<(), E>Expand description
Compound V0-shape entry gate: the canonical two-line
require_kind(caixa, Servico)? + require_single_servico(caixa)?
prelude every per-Servico caixa-<target> renderer runs at its
entry-point, collapsed onto one call the caller reads as intent
(“gate the input on the V0 Servico shape”) rather than two
hand-spelled predicate calls.
The pair names one contract with two axes: :kind is Servico
(this is a per-Servico renderer’s input, not a Biblioteca /
Binario / Supervisor / Aplicacao mis-hand-off) and
:servicos.len() == 1 (the V0 contract every Servico caixa
satisfies — one ComputeUnit YAML pointer, matching the one Helm
chart / programs.yaml entry / cluster bundle each per-Servico
renderer emits). Both axes must hold together — a :kind Servico
caixa with two :servicos entries and a :kind Aplicacao caixa
with one :servicos entry are equally invalid at every per-Servico
renderer’s entry-point — so lifting the pair onto one helper names
the compound contract at each call site the way the M2 typed slots’
servico_m2_overlay names the compound :limits+:behavior+
:upgrade-from overlay contract at each call site.
Three production call sites previously carried the two-line pair inline:
caixa-flux’sprograms_yaml_entry(the aggregator-path programs.yaml entry emitter);caixa-flux’scluster_bundle(the standaloneGitRepository+HelmRelease+Kustomizationtrio emitter);caixa-helm’srender_chart_for_servico_with(the per-programlareira-<nome>Helm chart emitter).
Each site now reads caixa_core::require_v0_servico_shape(caixa)?
instead of the two-line pair. A future per-Servico renderer
(caixa-otel, the future per-Servico OCI packager, the future M4
wasm.pleme.io/v1alpha1/ComputeUnit CR materializer,
MESH-COMPOSITION §III.2 #5) gets the compound V0-shape gate for
free with one call, instead of re-inlining the two-line pair — and
a future change to the V0 contract (e.g. adding a
:kind Servico-only :computeunits-slot-shape gate when the
component-model multi-world boundary lands in M5) is one edit here,
not a coordinated rewrite of every renderer’s inline pair.
The generic error type E accepts every renderer’s local
thiserror Error enum that carries both KindMismatch and
ServicoCountMismatch via #[from] (caixa_flux::Error,
caixa_helm::Error, and every future per-Servico renderer that
wires both #[from] arms as the diagnostic-naming-the-offending-
caixa contract already requires). Type inference at the call site
resolves E from the caller’s ? return type, so the call reads
as caixa_core::require_v0_servico_shape(caixa)? with no explicit
turbofish — the same one-liner shape every peer require_kind /
require_single_servico call site already reads as.
Peer to require_kind on the single-axis kind gate and
require_single_servico on the single-axis count gate — both
primitives stay public because per-non-Servico renderers
(caixa-mesh’s per-Aplicacao gate, caixa-feira’s
first_servico_path per-verb gate that composes both predicates
with anyhow::Context) reach for the individual predicates rather
than the compound one. Peer to servico_m2_overlay on the
sibling per-Servico compound-contract surface: servico_m2_overlay
names the compound M2 emit-side contract, require_v0_servico_shape
names the compound V0 gate-side contract, both per-Servico shape.
§Errors
Returns the caller’s E wrapping a KindMismatch when
caixa.kind != CaixaKind::Servico, or a ServicoCountMismatch
when caixa.servicos.len() != 1. Order matches the two-line pair
this replaces: the kind gate fires first, so a
:kind Aplicacao caixa with zero :servicos entries surfaces the
kind mismatch (the more actionable diagnostic — the author has the
wrong :kind) rather than the count mismatch (a downstream
consequence of the mis-kinded input).