StatifierBlocks.Graph (StatifierBlocks v0.40.0)

Copy Markdown View Source

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

  1. Every child the parent names resolves (A4). A resolver answering {:error, :not_published} is a finding on the referencing block's chart field.
  2. Every outcome the parent routes on is declared by the child (A1, A2). A core.subchart routes on the outcomes its author listed, or on done when the author listed none. error is 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's outcomes field.
  3. Every done-data key the parent reads is declared by the child (A1, A2). A core.map reads the members its collect_type marks required; a core.subchart reads none. Anchored on the block's collect_type field. Only presence is checked: whether a declared key has the type the parent expects stays StatifierBlocks.BlockType.agrees?/3's dormant advisory. A collect_type that is a type name the parent was compiled with no :datamodel to resolve has no keys to check: the reference records the name as unresolved, 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

resolver()

@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

check(compiled, resolver)

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, error exempt - 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's collect_type names a type the parent was compiled with no :datamodel to 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.

consumers_broken(child_next, parents)

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.