StatifierBlocks.Compiler.SelfReference (StatifierBlocks v0.23.0)

Copy Markdown View Source

The direct self-reference refusal: a document may not run itself (ADR-0004's 2026-08-29 amendment on what core.subchart's src resolves against).

A subchart names another document by its document id and the host's handler resolves that id to a chart at run time. When the id it names is the id of the document the block sits in, the run the handler starts is a fresh session of the same chart, which starts the same subchart, which starts another session. Nothing downstream can tell that apart from work the author meant to do, so the compiler refuses it here, where the two ids are both in hand and no session has been spent.

The criterion, and why it is not a block-type name

The pass never asks which block type emitted anything, for StatifierBlocks.Compiler.SensitivePaths' reason: a hard-coded list of core.* types would have to be edited every time the vocabulary grew, and a type added without that edit would recurse silently. It walks the emission and classifies by SCXML's own semantics: src on <invoke> is the attribute that names the resource an invocation runs, so an <invoke> whose src is this document's id is a document invoking itself, whichever type wrote it.

srcexpr is deliberately not checked. It names a source computed at run time out of the datamodel, so there is no id here to compare against the document's, and refusing an expression because the document id appears somewhere inside it would refuse charts that are correct.

Cross-document cycles are host-side, and named rather than built

A -> B -> A is the same defect one document further out, and this package cannot see it. emit/2 is a pure function of one block and its context (ADR-0004 decision 4); a compile is handed one document and a palette, and neither carries another document. Deciding a cycle needs the document graph - who references whom across the host's whole library - which only the host has. So the host's resolver owns that check, under the same st-ADR-0051 registry key that resolves a document id to a chart, and this module owns exactly the half a single compile can decide. Refusing the case in reach is worth more than declining both because one of them is out of reach.

Stage and fault

The finding carries the :emit stage, like every other refusal that reads the assembled emission, and it adds no stage to decision 10's table. It anchors on the offending block with config_key: "chart" - the author's verbatim value, stamped onto the attribute by StatifierBlocks.Emission.attribute_from_config/3 - so decision 9's split reads it as fault: :author, which is right: a one-field edit fixes it.

Severity is :error. A warning would let the document through and publish the recursion it exists to prevent.

Presenting one

StatifierBlocks.Finding.from_compiler/2's default derivation cannot reach :lint for this finding, for the reason SensitivePaths records: its rule 2 maps only a non-error to :lint, and an :emit stage at :error severity maps to :compile under ADR-0005 amendment 11h, so a bare adaptation presents it as a compile error rather than as the lint it is. That is what opts[:source] exists for, and a caller adapting these for the editor passes it:

{presentation, []} =
  StatifierBlocks.Finding.from_compiler_all(findings, source: :lint)

Widening the default derivation would mean switching on code, which StatifierBlocks.Finding forbids by construction; the seam is named here rather than bent, exactly as the sensitive-path mapping names it.

Summary

Functions

Every self-reference emission earns against document_id, in the order the emission is walked.

Functions

check(emission, document_id)

Every self-reference emission earns against document_id, in the order the emission is walked.

Returns [] when nothing invokes the document itself, which is the overwhelming majority of compiles.