Publish-time checks: every runtime refusal and its twin

Copy Markdown View Source

A runtime refusal for something a literal in the source could have told us is a bug in the publish check.

The packages in this family ship pure functions a host calls. The host's publish step is the gate: it runs those functions over a chart before the chart can start an execution, and it refuses the publish on what they report. An editor runs the same functions at edit time. The runtime checks described below stay in place as the backstop, and they are never the first line.

This page lists every refusal the family raises at run time, next to the publish-time function that finds the same defect first (its twin), or the word NONE when no function does. It covers three packages: statifier (this repo), statifier_router and statifier_blocks. Each package's section names the kinds of refusal it leaves out, and why. Every other refusal in the package is a row.

How to read the tables

Each row has five columns:

  • Refusal - what happens at run time, and the event or return value that reports it.
  • Raised by - the module (and, where it helps, the function) that raises it.
  • Record - the decision record that governs it, by path in its own repo. In the statifier table the paths are in this repo. In the router and blocks tables they are in that package's repo.
  • Literal? - whether a literal in the source decides the defect. "yes" means the defect is visible in the chart or configuration text, so a runtime refusal for it is a bug in the publish check. "no" means the defect depends on data, on another execution, or on a host's services, so the runtime check is the only possible check. "part" means the row splits, and the cell says where.
  • Twin - the publish-time function, its package, and the version that ships it, or NONE.

A twin marked "main, next release" is on that package's main branch and not yet on Hex.

Only a literal can be checked. A typeexpr, targetexpr, eventexpr, srcexpr, or a document id computed by an expr is resolved against the datamodel at run time. Every twin below reports such a value as unchecked (or leaves it out) rather than guessing at it. The runtime check is the only check for such values.

statifier

error.execution and error.communication are platform events. The engine puts them on the execution's internal queue, and the chart handles them like any other event (docs/architecture.md, design principle 3, "Errors are events").

Five kinds of refusal in this package have no row, because none is a refusal of a chart at run time:

The build tasks under lib/mix/ are tooling and are not part of the runtime.

The one function a host calls for this table is Statifier.Publish.findings/2 (statifier 2.9.0): it runs every check this package holds over a compiled chart and the host's declaration, and returns findings that each name their row here (docs/adr/0073-one-publish-findings-function-holds-every-publish-time-check.md). It composes the twins of S1 and S15 and holds one check each for S2, S3, S6, S9, S11 to S14 and S16 to S19. A row that gains a check lands inside it as one check, and its cell changes when it does.

#RefusalRaised byRecordLiteral?Twin
S1<send> names a type the session never registered: error.execution, data {:unsupported_type, type}, carrying the send's sendidStatifier.Machine.Content.Send, raised through Statifier.Interpreter.Content; Statifier.Session.Effects for an effect a caller injects through Statifier.Session.interpret/2docs/adr/0047-send-static-target-type-invalidity-rejects-in-the-core.md, docs/adr/0069-host-registered-send-types.mdpart: yes for type, no for typeexprStatifier.Send.Types.unsupported_sends/2 (statifier, since 2.6.0); the router composes it as StatifierRouter.Routes.unsupported_types/2 (statifier_router 0.2.0)
S2<send> with a built-in type writes a target the engine cannot parse: error.execution, data {:invalid_target, target}, carrying sendidStatifier.Machine.Content.Send, through Statifier.Interpreter.Content; Statifier.Session.Effects for an injected effectdocs/adr/0047-send-static-target-type-invalidity-rejects-in-the-core.mdpart: yes for target, no for targetexprStatifier.Publish.findings/2 (statifier 2.9.0)
S3<send> to a route that is missing from the snapshot the driver declared: error.communication, data {:unreachable_target, target}, carrying sendidStatifier.Machine.Content.Send, through Statifier.Interpreter.Contentdocs/adr/0048-send-reachability-judged-against-a-route-snapshot.mdpart: yes for a #_<invokeid> target that no <invoke id> in the chart declares; no for a session id or #_parent, which depend on who started the executionStatifier.Publish.findings/2 (statifier 2.9.0)
S4A send whose target session does not exist or cannot be reached, found at delivery (no snapshot declared, a delayed send when its timer fires, an injected effect): error.communication carrying sendidStatifier.Session (the deliver path and communication_error)docs/adr/0039-session-detected-send-failures-re-enter-the-core.md, docs/adr/0048-send-reachability-judged-against-a-route-snapshot.mdno, except a literal #_<invokeid> target, delayed or not, that no <invoke id> in the chart declares (an id the engine generates included): S3 reports itNONE
S5A host-registered send processor cannot deliver: error.communication carrying sendid, written by the hostStatifier.Session.failed_send/3; Statifier.Interpreter.deliver_internal/5 when no session process is runningdocs/adr/0069-host-registered-send-types.md (decision 5)depends on the processor: the router's are RT1 to RT9, RT18 to RT22, RT24 and RT25NONE in statifier, which cannot see a processor's routes; the router's twins are in those rows
S6<invoke> names a type the session never registered: error.execution with the invocation as its origin and no data; no child startsStatifier.Interpreter (reject_unregistered_type); Statifier.Session.Effects (plan_invoke)docs/adr/0051-invoke-handlers-are-registered-per-session.mdpart: yes for type, no for typeexprStatifier.Publish.findings/2 (statifier 2.9.0) for a chart; a block document reports it through StatifierBlocks.Compiled's invoke_types field and the :known_invoke_types lint of StatifierBlocks.Compiler.compile/3, a warning only (statifier_blocks 0.32.0)
S7A registered invoke handler's start/2 answers {:error, _}: error.execution with the invocation as its originStatifier.Session.Effects (plan_invoke)docs/adr/0051-invoke-handlers-are-registered-per-session.mdno: the handler decidesNONE
S8An <invoke> argument fails to evaluate: error.execution carrying the reason; the invocation is abandonedStatifier.Interpreter (abort_invocation)docs/adr/0031-invoke-argument-failure-aborts-the-invocation.mdpart: the argument's own failure is one of S12 to S14NONE; S12 to S14 say which of those failures a literal decides
S9A child fails to start (the built-in scxml invoke's src or <content> does not resolve or compile, or Statifier.start_session/2 fails): error.communication with the invocation as its originStatifier.Session (start_child, invoke_error); Statifier.Invoke.Source.resolve/2docs/adr/0038-invoke-source-resolves-at-the-session-boundary.mdpart: yes for an inline <content> child that does not compile; no for a src, which the host's resolver answersStatifier.Publish.findings/2 (statifier 2.9.0; for block documents see B1 to B3)
S10A started invocation fails for good: error.communication.invoke.<invokeid>Statifier.Invoke.Answer.failed/4, through Statifier.Session.failed_invocation/3docs/adr/0068-permanent-invoke-failure-is-a-suffixed-error-communication.mdnoNONE
S11A location write whose root the datamodel does not declare (<assign>, an idlocation, a <finalize> namelist write): error.execution, data {:unbound_location, location}; a root beginning with _ gives {:system_variable, root} insteadStatifier.Interpreter.Datamodel.write_location/4, called by Statifier.Machine.Content.Assign, Statifier.Machine.Content.Send and Statifier.Interpreterdocs/datamodel.md (the root of a written path must already exist)yes, when the location's root is a name and the chart's <data> ids are in the sourceStatifier.Publish.findings/2 (statifier 2.9.0) for a chart; for a block document, StatifierBlocks.Datamodel.findings/4, reached through StatifierBlocks.Publish.findings/3, reports it as an :info advisory and does not refuse (statifier_blocks main, next release)
S12A read of a root the datamodel does not declare: error.execution carrying predicator's undefined-variable error, wherever the expression sits (cond, executable content, <data>, <donedata>, a global <script>)the raise site of the expression: Statifier.Interpreter.Selection, Statifier.Interpreter.Content, Statifier.Interpreter.Datamodel, Statifier.Interpreter.ExitEntry, Statifier.Interpreterdocs/architecture.md (design principle 3), docs/datamodel.mdyes, when the root is neither declared by a <data> nor a system variableStatifier.Publish.findings/2 (statifier 2.9.0) for a chart; for a block document, StatifierBlocks.Publish.findings/3 reports an undeclared path as an :info advisory and does not refuse (statifier_blocks main, next release)
S13An expression that fails to compile, where the compiler defers the failure to run time (<data expr>, <assign expr>, <script> in content or at the top level, a namelist entry): error.execution carrying the compile errorStatifier.Compiler stores the failure as {:invalid, error} on the compiled node; the raise sites are Statifier.Interpreter.Datamodel, Statifier.Interpreter.Content and Statifier.Interpreterdocs/datamodel.md (spec 5.9.4 allows either a load-time or a run-time rejection, and the engine takes the run-time one); docs/adr/0026-script-as-predicator-statement-programs.md for <script>yesStatifier.Publish.findings/2 (statifier 2.9.0). Every other expression that fails to compile fails Statifier.compile/2 itself, which is its own publish-time check
S14An expression that compiles and then fails to evaluate on the data it meets (a type error, a <foreach> array that is not a list, a delayexpr of the wrong shape), or a <send> whose literal delay is not a duration: error.execution carrying the reasonStatifier.Interpreter.Selection, Statifier.Interpreter.Content, Statifier.Interpreter.Datamodel, Statifier.Interpreter.ExitEntry or Statifier.Interpreter, wherever the expression sitsdocs/architecture.md (design principle 3); docs/adr/0021-donedata-content-expr-failure-yields-no-data.md for <donedata>; docs/adr/0036-send-argument-failure-discards-the-message.md for <send>part: yes for a literal delay that is not a duration, which compiles and is refused only when the send runs, with data {:invalid_delay, delay}; no for the rest, which depends on the dataStatifier.Publish.findings/2 (statifier 2.9.0)
S15An event the chart has no transition for: not refused. The engine discards it, as the SCXML algorithm doesStatifier.Interpreter.Selection (no transition is selected)docs/adr/0071-chart-event-vocabulary-and-accepts-check.mdyes, when the sender's event name is a literalStatifier.Chart.check_accepts/2 and Statifier.Chart.events/1 (statifier 2.7.0); the router applies them in RT13
S16A macrostep that does not reach quiescence within the round budget (max_macrostep_rounds, default 10,000): the fold stops and appends a {:budget_exhausted, %Statifier.Effect.BudgetExhausted{}} effect, and a session halts with :budget_exhaustedStatifier.Interpreter (the macrostep fold); Statifier.Sessiondocs/adr/0019-macrostep-round-budget.mdpart: yes for a cycle of eventless transitions none of which carries a cond; no in general, where the data a cond reads decides whether the cycle endsStatifier.Publish.findings/2 (statifier 2.9.0)
S17A <foreach> whose item or index is not a legal variable name, or begins with _: error.execution, data {:illegal_item_name, name}, {:illegal_index_name, name} or {:system_variable, name}; the loop does not runStatifier.Machine.Content.Foreach (check_name, check_index), through Statifier.Interpreter.Contentno record names it; docs/datamodel.md and the module's own documentation (spec 4.6.3)yes: both names are literal attributes, and neither the compile pipeline nor Statifier.Validator checks themStatifier.Publish.findings/2 (statifier 2.9.0)
S18A <script>, in executable content or at the top level, that writes a root beginning with _: error.execution, data {:system_variable, root}Statifier.Evaluator.run_program/2, raised through Statifier.Interpreter.Content or Statifier.Interpreterdocs/adr/0026-script-as-predicator-statement-programs.md; docs/datamodel.md ("Upstreaming to predicator", item 4)yes: the script's assignment targets are names in its sourceStatifier.Publish.findings/2 (statifier 2.9.0)
S19A literal write location (<assign location>, an idlocation, a <finalize> namelist write) that is not an assignable location: it does not parse; or it names something that cannot be assigned: a literal, a string, a list, a function call, or an arithmetic, comparison, logical or unary expression (predicator's LocationError type :not_assignable), or a membership test, an object literal, a cast, a duration or a relative date (:invalid_node, for example copies in copies, {}, copies::integer, 3d, 3d ago); or a bracket key is neither a string, an integer nor a variable (:computed_key, copies[1 + 1] or copies[true]). It compiles, and the write fails with error.execution carrying the errorStatifier.Interpreter.Datamodel (resolve_location, through write_location/4)no record names it; docs/datamodel.mdyes: the location is a literal attribute, and none of these depends on the dataStatifier.Publish.findings/2 (statifier 2.9.0)

statifier_router

At the executor seam, a refusal on the send side reaches the sending execution as error.communication carrying the send's sendid (StatifierRouter.SendHandler). The step that made the send is still committed. The routing side's outcomes are what StatifierRouter.route/3 returns, and they are written to the binding's ledger.

A source invoke's refusals (RT15 to RT17) go back to the host's invoke handler, which decides how the chart hears of them.

A delivery's own errors (RT3, RT20, RT21 and RT24) are met on both sides. StatifierRouter.route/3 returns them, and a send to the execution target hears them as error.communication. That send's delivery rolls back to a savepoint of its own, and the sending step stands (StatifierRouter.Delivery.deliver_event/4).

Four kinds of router refusal have no row:

  • Configuration-time checks, made before any event is routed: the constructors StatifierRouter.Config.new/1, StatifierRouter.Binding.new/1 and StatifierRouter.Resolver.Static.new/1; the ArgumentErrors StatifierRouter.Broadway.start_link/1 raises for its options, and whatever Broadway.start_link/2 itself answers; and the ArgumentErrors StatifierRouter.Migrations.up/1 and down/1 raise for their options.
  • Each ArgumentError the router raises for a host callback that answers outside its contract (a resolver, a chart resolver, a delivery module, a StatifierRouter.Contracts.check/3 lookup), or for a schema handed to StatifierRouter.Config.queryable/2 or put_meta/2 that is not one of the package's. That is a host programming fault.
  • The maintenance sweeps. StatifierRouter.Addresses.reap/3 refuses bad options and passes on an error from StatifierPersistence.Storage.fetch_execution/2, and a StatifierRouter.PinSource raises when it cannot count. Neither is a chart or a message defect.
  • The publish-time functions themselves: StatifierRouter.Contracts and StatifierRouter.Routes.
#RefusalRaised byRecordLiteral?Twin
RT1A send of the router's type names a route the host never registered: {:error, {:unregistered_route, name}}, a send_refused ledger row with reason routeStatifierRouter.SendHandlerdocs/adr/0005-routes.md (section 7); docs/adr/0006-the-execution-target.md (the Note of 2026-09-21)part: yes for target, no for targetexprStatifierRouter.Routes.unregistered/2 (statifier_router 0.2.0), also composed by StatifierRouter.Contracts.check/3 (statifier_router main, next release)
RT2A send to the execution target with no usable document param: {:send_refused, :document}StatifierRouter.SendHandlerdocs/adr/0006-the-execution-target.md (section 6)part: yes when the param is absent or is a literal; no when it is an expressionStatifierRouter.Contracts.check/3 (statifier_router main, next release) lists the send under unchecked with reason :no_document or :document_expr. It reports and does not refuse
RT3A binding, or a send to the execution target, names a document the host has no published chart for: the delivery is rolled back and StatifierRouter.route/3 returns {:error, {:unresolved_document, document, reason}}, with no ledger rowStatifierRouter.Delivery (resolve/3), through the host's StatifierRouter.Resolverdocs/adr/0003-delivery-discipline.md (the Note of 2026-09-20); docs/adr/0008-the-receiver-contract-at-publish.md (decision 4)yes, for a binding's document and for a literal document paramStatifierRouter.Contracts.check/3, reason :not_published (statifier_router main, next release)
RT4A send to the execution target with no usable key param: {:send_refused, :key}StatifierRouter.SendHandlerdocs/adr/0006-the-execution-target.md (section 6)part: yes when the param is absent; no when it is an expressionNONE
RT5A send to the execution target whose create param is neither if_absent nor never: {:send_refused, :create}StatifierRouter.SendHandlerdocs/adr/0006-the-execution-target.md (sections 3 and 6)part: yes when the param is a literal; no when it is an expressionNONE
RT6A send to the execution target that resolves to the sender's own address: {:send_refused, :self_address}StatifierRouter.SendHandlerdocs/adr/0006-the-execution-target.md (section 6)noNONE
RT7A send to the execution target from an execution with no address row: {:send_refused, :unaddressed_sender}, with no ledger rowStatifierRouter.SendHandlerdocs/adr/0006-the-execution-target.md (section 6)no: it depends on how the sender was createdNONE
RT8A send to the execution target that finds nothing to deliver to: {:send_undelivered, :no_execution} or {:send_undelivered, :finished}StatifierRouter.SendHandlerdocs/adr/0006-the-execution-target.md (section 3)noNONE
RT9A delayed send of the router's type on the send-processor shape: {:delayed_send_unsupported, send_id}StatifierRouter.SendHandler.perform/2docs/adr/0005-routes.md (section 5)yes, given the host's shape: a literal delay on a send of the router's typeNONE
RT10A binding's match or key program refuses the event: {:key_refused, binding_id, reason}StatifierRouter.route/3docs/adr/0004-the-refusal-and-drop-vocabulary.md (section 1)no: the programs read the event payloadNONE
RT11A binding whose create is :never meets an address with no row: {:dropped, binding_id, :no_execution}StatifierRouter.Deliverydocs/adr/0004-the-refusal-and-drop-vocabulary.md (sections 1 and 2)noNONE
RT12The execution the event was for is terminal: {:dropped, binding_id, :finished}StatifierRouter.Deliverydocs/adr/0004-the-refusal-and-drop-vocabulary.md (sections 1 and 3)noNONE
RT13An event the receiving chart never listens for: not refused. It is delivered and selects no transition. Through a binding, StatifierRouter.route/3 answers {:dropped, binding_id, :unmatched_event} and the binding's ledger row reads dropped: unmatched_event (statifier_router 0.5.0); the execution still took the event. A send to the execution target keeps its outcomesStatifierRouter.Delivery; Statifier.Interpreter.Selection in the receiverdocs/adr/0004-the-refusal-and-drop-vocabulary.md (section 8); docs/adr/0008-the-receiver-contract-at-publish.md (decision 5)yes, for a binding's event and for a send's literal eventStatifierRouter.Contracts.check/3 (statifier_router 0.3.0), which falls back to Statifier.Chart.check_accepts/2 (statifier 2.7.0) for a receiver that declares nothing
RT14A route calls back into routing while it runs: {:error, {:reentrant_route, execution_id}}StatifierRouter.Delivery.deliver/4docs/adr/0005-routes.md (decision 5)no: a host route's code does this, not a chartNONE
RT15A source invoke carries no binding param: {:error, {:missing_binding_param, params}}, returned to the host's invoke handlerStatifierRouter.SourceInvoke.start/3docs/adr/0007-the-source-invoke.md (section 1)yes, when the <invoke> writes no binding paramNONE
RT16A source invoke names a binding the configuration does not hold: {:error, {:unknown_binding, binding_id}}, returned to the host's invoke handlerStatifierRouter.subscribe/3, reached through StatifierRouter.SourceInvoke.start/3docs/adr/0007-the-source-invoke.mdpart: yes when the binding param is a literal, given the host's configuration; no when it is an expressionNONE
RT17A source invoke from an execution with no address row: {:error, {:unaddressed_execution, execution_id}}, returned to the host's invoke handlerStatifierRouter.subscribe/3, reached through StatifierRouter.SourceInvoke.start/3docs/adr/0007-the-source-invoke.md (section 6)no: it depends on how the execution was createdNONE
RT18A delayed send of the router's type when the configuration has no timer queue: {:error, {:no_timer_queue, send_id}}StatifierRouter.SendHandler (schedule)docs/adr/0005-routes.md (section 5)yes, given the host's configuration: a literal delay on a send of the router's typeStatifierRouter.Contracts.check/3 (statifier_router 0.5.0) lists the send under unregistered_routes with reason :no_timer_queue
RT19A registered route's adapter answers {:error, reason}: the handler returns it, and at the executor seam it reaches the sender as error.communication carrying sendidStatifierRouter.SendHandler (hand_off)docs/adr/0005-routes.md (sections 3 and 7)no: the adapter's service decidesNONE
RT20The configuration's :on_complete route fails for an execution that has just finished: {:error, {:on_complete, route_name, reason}}; the delivery rolls back so it can be redrivenStatifierRouter.Delivery (complete)no record names it; docs/adr/0004-the-refusal-and-drop-vocabulary.md (section 7) governs an error that is not an outcome, and StatifierRouter.Config documents the optionnoNONE
RT21The host's chart resolver has no chart for the content hash an existing execution started on: {:error, {:chart_not_resolved, content_hash}}; the delivery rolls backStatifierRouter.Delivery (chart)docs/adr/0002-addressing.md ("A chart the host cannot resolve is an error, not an outcome"); docs/adr/0004-the-refusal-and-drop-vocabulary.md (section 7)no: it depends on the host's storeNONE
RT22The send handler runs with no configuration installed in the process: {:error, {:no_config, StatifierRouter.SendHandler}}StatifierRouter.SendHandler.fetch_config/0no record names it; StatifierRouter.SendHandler.fetch_config/0 documents itno: a host's code does this, not a chartNONE
RT23route/3 is handed a malformed message or options: {:error, :no_message_id}, {:error, {:invalid_event, event}}, {:error, {:invalid_opts, opts}}, {:error, {:unknown_key, name}} or {:error, {:invalid_value, :now, value}}; StatifierRouter.Webhook.handle/3 adds {:error, {:invalid_request, request}}StatifierRouter.route/3; StatifierRouter.Webhookdocs/adr/0004-the-refusal-and-drop-vocabulary.md (section 7)no: the message and the host's call decideNONE
RT24The delivery's persistence step answers {:error, reason}: StatifierPersistence.Executions.create/4, StatifierPersistence.Executions.step/5 or StatifierPersistence.Storage.fetch_execution/2 refuses, and the delivery rolls back and returns reason unchanged, whatever statifier_persistence answered. A host that configures its own delivery module in place of StatifierRouter.Delivery answers for that module's errors, which StatifierRouter.route/3 returns the same wayStatifierRouter.Delivery (create, existing, step); StatifierRouter.route/3 for a host's delivery moduledocs/adr/0004-the-refusal-and-drop-vocabulary.md (section 7)no: the host's store decidesNONE
RT25The configured timer queue refuses: its schedule/2 or cancel/3 answers {:error, reason}, and the handler returns it unchanged, whatever the queue answered. At the executor seam the sender hears error.communication, carrying sendid for a delayed send and none for a <cancel>StatifierRouter.SendHandler (schedule, dequeue), through the host's StatifierRouter.TimerQueuedocs/adr/0005-routes.md (section 5)no: the host's queue decidesNONE

statifier_blocks

A core.subchart names its child by a literal document id. When the child cannot start, the chart hears error.communication.invoke.<block id> carrying a reason. The in-memory handler, StatifierBlocks.Runtime.Subchart, raises that event itself with one of three reasons, and never answers {:error, _}, because the engine would turn that into an error.execution with no data and the reason would be lost. The durable handler, StatifierBlocks.Runtime.DurableSubchart, answers its dispatch with the same three reasons as a failure list, which statifier_persistence's driver turns into the same event; the driver adds a fourth reason of its own (B6).

Three kinds of refusal have no row. A core.map compiles to one <invoke>, and the fan-out handler that starts its children is not in this package (docs/adr/0009-fan-out-block-type.md): its refusals belong to the package that ships it. Every refusal outside lib/statifier_blocks/runtime/ belongs to authoring or to publishing: decoding, editing, compiling and the publish-time functions, which are the checks themselves, and StatifierBlocks.Runtime.FixtureRuns, which runs an author's fixtures. And each ArgumentError the two handlers raise for a host callback or a dispatch context outside its contract is a host programming fault.

#RefusalRaised byRecordLiteral?Twin
B1The child document id resolves to nothing: reason unknown_documentStatifierBlocks.Runtime.Subchart.Resolution.resolve/3docs/adr/0008-durable-subchart-handler.mdyesStatifierBlocks.Graph.check/2, a :graph error anchored on the block's chart field (statifier_blocks main, next release)
B2The child document does not compile for child use: reason child_compile_findingsStatifierBlocks.Runtime.Subchart.Resolution.resolve/3docs/adr/0008-durable-subchart-handler.mdyes: the child's own documentthe child's own publish, StatifierBlocks.Publish.findings/3 and then StatifierBlocks.Compiler.compile/3 (statifier_blocks main, next release)
B3The host's resolver reports a cycle across documents: reason cycle_refusedStatifierBlocks.Runtime.Subchart.Resolution.resolve/3docs/adr/0008-durable-subchart-handler.mdyes: every id in the cycle is a literalNONE. A document that names itself is refused by its own compile (StatifierBlocks.Compiler.SelfReference); a longer cycle is not checked at publish
B4A parent routes on an outcome the child does not declare: not refused. The child's answer falls to the parent's unconditioned armthe generated chartdocs/adr/0008-durable-subchart-handler.md (the Amendment of 2026-09-22)yesStatifierBlocks.Graph.check/2 when the parent is published and StatifierBlocks.Graph.consumers_broken/2 when the child is (statifier_blocks main, next release)
B5A parent reads a done-data key the child does not declare: not refusedthe generated chartdocs/adr/0008-durable-subchart-handler.md (the Amendment of 2026-09-22)yesStatifierBlocks.Graph.check/2 and StatifierBlocks.Graph.consumers_broken/2, an :error (statifier_blocks main, next release). When the parent's collect_type names a type and the parent was compiled without :datamodel, the keys are not known, and both report the read as unchecked with a :warning
B6The durable path cannot create the child's execution: reason child_execution_creation_failedStatifierPersistence.Driver in statifier_persistence, from its own start_child refusalsdocs/adr/0008-durable-subchart-handler.md (decision 5, which records this reason under an older spelling)no: the host's store decidesNONE

A block document is also a chart once it compiles, so rows S1 to S19 apply to it too. Where statifier_blocks has its own check for one of those rows, that row says so.

An example: a library loan

A loan chart sends overdue notices through a processor the host registers under library:notices. The loan document's declaration names patron.blocked as an event it accepts, but no transition listens for it.

<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"
       initial="on_loan" datamodel="predicator">
  <state id="on_loan">
    <transition event="loan.renew" target="on_loan"/>
    <transition event="loan.due" target="overdue">
      <send type="library:notices" target="overdue_notice" event="loan.overdue"/>
    </transition>
    <transition event="copy.returned" target="returned"/>
  </state>
  <state id="overdue">
    <transition event="copy.returned" target="returned"/>
  </state>
  <final id="returned"/>
</scxml>

A host whose publish step has not registered the processor, checking the chart against the names the loan document declares, gets two findings:

{:ok, machine} = Statifier.compile(loan_source)

Statifier.Send.Types.unsupported_sends(machine, nil) |> Enum.map(& &1.type)
#=> ["library:notices"]

Statifier.Chart.check_accepts(machine, ["loan.renew", "loan.due", "copy.returned", "patron.blocked"])
#=> %{unreachable: ["patron.blocked"], undeclared: []}

The first finding is S1. Without it the send would raise error.execution the first time a loan fell due. The second is S15. Without it a patron.blocked event sent to this execution would be discarded without a word.

Drop the type from the send and the chart still compiles, and unsupported_sends/2 returns []. The literal target="overdue_notice" is not a target the built-in processor can parse. The first time a loan falls due, the engine raises error.execution with data {:invalid_target, "overdue_notice"}. That is row S2, a runtime refusal that a literal could have prevented, and Statifier.Publish.findings/2 reports it before the chart runs: one finding of kind :invalid_target at the send's location.

Keeping this table true

The statifier rows come from this search over lib/, which finds every place the engine names error.execution or error.communication, in code or in prose:

grep -rn -E 'error\.(execution|communication)' lib/

and this one, which narrows it to the raise sites:

grep -rn -E 'raise_platform\(|\{:raise, :platform|error_name\(|Event\.(external|platform)\("error|"error\.communication"' lib/

Those two searches find the platform events. The census behind every table also ran this search over lib/ in each of the three packages, which matches every {:error in any form (a literal reason, a variable passed on, a callback's spec), every raise, every else clause of a with, and the router's and the blocks handlers' own outcome tags:

grep -rn -E '\{:error\b|\braise\b|^[[:space:]]*else\b|\{:(dropped|key_refused|send_refused|send_undelivered|refuse|failed)\b' lib/

A function that returns a callback's answer as its last expression carries no {:error of its own, so each callback whose spec the search matched was followed to the function that calls it. Every refusal the search led to is a row or falls in one of the kinds of refusal a section names as left out, except four that are open for a later revision of this page: a cond that evaluates to something other than a boolean; the refusals of the Statifier.Testing helpers; the write step's refusals from Predicator.ContextLocation.put/3 (predicator 9.0.0), :not_a_container and :invalid_index, which depend on the data because a negative index writes to a map and is refused only for a list; and a variable bracket key that is unbound or is neither a string nor an integer (:undefined_variable, :invalid_key).

A new raise site, a new refusal reason in a sibling package, or a new publish-time function changes a row here. So does a twin that moves from a main branch to a release.