ADR-0002 decision 6's two properties, checked over a whole document
against a palette: declared slots are the complete set a block may carry
(:undeclared_slot), and a slot's child count must satisfy the arity
its type declares for it (:slot_arity_violated).
One implementation, consulted by both the editor and the compiler
(sb-iwz), the same way StatifierBlocks.Assignability is - both are
passed the same palette and the same document, and neither owns a
private copy of either rule.
Ordering
validate/2 walks Document.blocks/1 (pre-order, root first) and
concatenates each block's findings, so the result is already in the
order a caller wants to report them. Within one block, arity findings
come first, in slots/1's own declaration order; undeclared-slot
findings come after, in UTF-8-sorted slot-name order (matching
Document.blocks/1's own sorted slot traversal).
Degradation
A block whose type fails Palette.resolve/2 contributes no findings of
either kind - the same permissive default Assignability.produces/4
uses, because unresolvability is ADR-0002 decision 3's own finding,
reported by the walk that owns it, not this one.
Totality, and where the slots/1 precondition holds
module.slots(resolved.config) is called with no rescue, exactly as
Assignability.valid_targets/4 calls module.slots/1: this package's
rule is that nothing is rescued to a default. ADR-0002 decision 6
guarantees slots/1 returns without raising only for a config
validate_config/1 accepts, so validate/2 is total over any document
and palette whose block types honour that guarantee for the configs
they are handed. In the compiler this precondition holds by
construction - the Config stage runs validate_config/1 over every
block and stops the pipeline before Structure runs - but a caller
outside the compiler (an editor, mid-edit) wanting the same guarantee
runs validate_config/1 first.
Summary
Functions
Whether count children satisfies arity, per ADR-0002 decision 6's
four-row table: :any admits any count; :at_least_one needs count >= 1; :exactly_one needs count == 1; :zero_or_one needs count <= 1.
Every slot-arity and undeclared-slot finding in document against
palette, in Document.blocks/1 pre-order. :ok when the list is
empty.
Types
@type finding() :: {:slot_arity_violated, StatifierBlocks.Block.id(), StatifierBlocks.Block.slot_name(), StatifierBlocks.BlockType.slot_arity(), non_neg_integer()} | {:undeclared_slot, StatifierBlocks.Block.id(), StatifierBlocks.Block.slot_name(), non_neg_integer()}
Functions
@spec arity_satisfied?(StatifierBlocks.BlockType.slot_arity(), non_neg_integer()) :: boolean()
Whether count children satisfies arity, per ADR-0002 decision 6's
four-row table: :any admits any count; :at_least_one needs count >= 1; :exactly_one needs count == 1; :zero_or_one needs count <= 1.
@spec validate(StatifierBlocks.Palette.t(), StatifierBlocks.Document.t()) :: :ok | {:error, [finding()]}
Every slot-arity and undeclared-slot finding in document against
palette, in Document.blocks/1 pre-order. :ok when the list is
empty.
Per block: Palette.resolve(palette, block). On {:error, _} the
block contributes nothing. On {:ok, module, resolved},
module.slots(resolved.config) is the declaration list checked -
resolved.config, not block.config, because the migrated config is
what the type's own declarations are read against (ADR-0002 decision
8), and it is what the compiler's Resolve stage already uses.