The publish-time check between a parent document and the children it names (ADR-0008's amendment of 2026-09-22).
A compile of one document cannot see the child a core.subchart or a
core.map names, and at runtime a disagreement between the two is not
refused: a child outcome the parent routes on but the child no longer
finishes with leaves the parent's conditioned arm dead, and the child's
answer falls to the parent's unconditioned arm. The document graph is the
host's, so the host is what walks it. This module ships the pairwise
check the walk calls, over two StatifierBlocks.Compiled artifacts, in
the two directions A2 names:
check/2- forward, when a parent is published: every child the parent names is resolved through the host's resolver and judged against the parent.consumers_broken/2- reverse, when a child is republished: the next child artifact is judged against every parent the host says currently names it (A3).
Both read only StatifierBlocks.Compiled's interface field (A5). They
hold no store, start no process and perform no IO; the resolver is the
host's function and the only IO either direction touches.
The three checks
- Every child the parent names resolves (A4). A resolver answering
{:error, :not_published}is a finding on the referencing block'schartfield. - Every outcome the parent routes on is declared by the child (A1,
A2). A
core.subchartroutes on the outcomes its author listed, or ondonewhen the author listed none.erroris exempt: the parent routes on it whether or not the author listed it, and a child reports it without declaring it. Anchored on the block'soutcomesfield. - Every done-data key the parent reads is declared by the child
(A1, A2). A
core.mapreads the members itscollect_typemarks required; acore.subchartreads none. Anchored on the block'scollect_typefield. Only presence is checked: whether a declared key has the type the parent expects staysStatifierBlocks.BlockType.agrees?/3's dormant advisory. Acollect_typethat is a type name the parent was compiled with no:datamodelto resolve has no keys to check: the reference records the name asunresolved, and the pair reports the read as unchecked rather than passing it (ADR-0008's second amendment of 2026-09-22).
The other direction of the outcome rule - a child outcome the parent has
no arm for, such as one a child revision adds - is not refused here.
It falls to the parent's unconditioned arm, and
StatifierBlocks.ViewModel.outcome_findings/3 stays the editor's
:warning about it.
The findings
Every finding is a StatifierBlocks.Finding with source :graph,
anchored {:config, block_id, key} on the parent's referencing block,
under the field its author would change. The anchor has no document arm,
so consumers_broken/2 returns each finding paired with the parent's
document id, and its message names that document.
Every finding is severity :error but one: a done-data read left
unchecked because the parent's collect_type names a type and the parent
was compiled without :datamodel is a :warning on the block's
collect_type field. The host chose not to pass that option, which is
not the author's error, so a publish step refusing on :error findings
does not refuse on it; passing :datamodel, or writing the
collect_type inline, lets the keys be checked.
Summary
Types
The host's publish-time resolver: a document id to that document's
currently published artifact, or {:error, :not_published}. It is not
the durable handler's start-time resolve_chart/2, and it is the only IO
check/2 performs. check/2 calls it once per distinct document id the
parent names, in the order the parent first names each.
Functions
The forward check (ADR-0008 amendment A2, A4): judges parent against
every child it names, each resolved through resolver.
The reverse check (ADR-0008 amendment A2, A3): judges child_next, the
child revision about to be published, against each of parents, the
published artifacts the host says currently name that child.
Types
@type resolver() :: (String.t() -> {:ok, StatifierBlocks.Compiled.t()} | {:error, :not_published})
The host's publish-time resolver: a document id to that document's
currently published artifact, or {:error, :not_published}. It is not
the durable handler's start-time resolve_chart/2, and it is the only IO
check/2 performs. check/2 calls it once per distinct document id the
parent names, in the order the parent first names each.
Functions
@spec check(StatifierBlocks.Compiled.t(), resolver()) :: [StatifierBlocks.Finding.t()]
The forward check (ADR-0008 amendment A2, A4): judges parent against
every child it names, each resolved through resolver.
Returns [] when every child resolves, declares every outcome the parent
routes on and every done-data key the parent reads, and no read is left
unchecked. Otherwise one finding, source :graph, per failed rule, in the
parent's document order - each an :error but the last kind below:
- a child the resolver answers
{:error, :not_published}for - anchored{:config, block_id, "chart"}; - an outcome the parent routes on that the child does not declare,
errorexempt - anchored{:config, block_id, "outcomes"}; - a done-data key the parent reads that the child does not declare -
anchored
{:config, block_id, "collect_type"}; - a
:warning, when the child resolves and the reference'scollect_typenames a type the parent was compiled with no:datamodelto resolve, so its keys are unchecked - anchored{:config, block_id, "collect_type"}.
block_id is always the parent's referencing block. A resolver answering
anything else breaks its contract, and the call raises rather than
guessing what the host meant.
@spec consumers_broken(StatifierBlocks.Compiled.t(), [StatifierBlocks.Compiled.t()]) :: [ {String.t(), StatifierBlocks.Finding.t()} ]
The reverse check (ADR-0008 amendment A2, A3): judges child_next, the
child revision about to be published, against each of parents, the
published artifacts the host says currently name that child.
The child's document id is child_next.record.document_id, and only a
parent's references to that id are judged, so a parent that names other
documents only contributes nothing. Returns [] when every such parent
still finds every outcome it routes on and every done-data key it reads
declared by child_next, and no read is left unchecked; otherwise one
{parent_document_id, finding} pair per failed rule, parents in the
order given and each parent's findings in its document order. Each
finding is source :graph, anchored on the parent's referencing block -
{:config, block_id, "outcomes"} for an outcome, {:config, block_id, "collect_type"} for a done-data key - and its message names the parent
document. Each is severity :error, except a :warning on
"collect_type" for a reference whose collect_type names a type the
parent was compiled with no :datamodel to resolve, so its keys are
unchecked. The resolver plays no part: both sides are in hand.