StatifierBlocks.Core (StatifierBlocks v0.8.0)

Copy Markdown View Source

The core.* structural vocabulary: the block types this package ships itself (ADR-0002 decision 10).

Every one of them is an ordinary StatifierBlocks.BlockType - there is no privileged path for a core type, and a host that wants a container this package does not ship writes one the same way these are written. They are reached through StatifierBlocks.Palette.core/0, and a host merges them with its own entries through StatifierBlocks.Palette.core_types/0.

Block typeModuleSlots
core.sequenceStatifierBlocks.Core.Sequencebody
core.groupStatifierBlocks.Core.Groupbody, interrupts
core.branchStatifierBlocks.Core.Branchone per arm, then otherwise
core.parallelStatifierBlocks.Core.Parallelone per lane
core.waitStatifierBlocks.Core.Waitnone
core.resumable_groupStatifierBlocks.Core.ResumableGroupbody, interrupts
core.on_eventStatifierBlocks.Core.OnEventnone
core.invokeStatifierBlocks.Core.Invokeon_error
core.raiseStatifierBlocks.Core.Raisenone
core.assignStatifierBlocks.Core.Assignnone
core.sendStatifierBlocks.Core.Sendnone
core.subchartStatifierBlocks.Core.Subchartone per declared outcome, on_error last
core.foreachStatifierBlocks.Core.Foreachbody

Structure, not domain

None of these types knows a host's domain, and none of them declares a type expression (ADR-0003 decision 1). They arrange other blocks; the blocks they arrange are the host's. core.invoke is structural in the same sense - it names an invoke type and never runs one, which is ADR-0002 decision 2's two-registry seam rather than domain knowledge, and core.subchart is that seam again: it names another chart and the host-registered invoke type that runs one. So every core io/1 declares kinds, every core type that has slots declares slot_accepts for them, and no core type declares consumes at all - inbound type is the host's business, and ADR-0003 decision 5's permissive default is the honest answer.

produces is declared by five of the core types. core.sequence declares {:passthrough, "body"}: it is transparent to type flow, so whatever its last step produces is what the sequence produces, computed by ADR-0003 decision 4 rather than by anything here. core.branch, core.parallel, core.invoke and core.subchart declare :unknown outright rather than combining their arms', lanes' or outcomes' outputs, because combining them is the type lattice ADR-0003 decision 4 refuses to build - and spelling the default out is how that refusal stays visible to a reader. The rest leave it absent.

Placement is kind tags, and nothing else

core.on_event may appear inside an interrupts slot and nowhere else, and nothing but an interrupt handler may appear inside one. Both directions fall out of two declarations - core.on_event tagging itself kinds: [:interrupt_handler], and the group types naming "interrupts" => [:interrupt_handler] in slot_accepts - evaluated by ADR-0003 decision 3's intersection rule.

There is deliberately no special-cased validation rule for it here. ADR-0002 decision 10 originally recorded one and withdrew it at acceptance; a host group type with an interrupts slot gets the same constraint by declaring the same thing, and core.on_event never has to enumerate the groups it is allowed inside.

What they compile to

Every core type emits through StatifierBlocks.Core.Emit, which is where the SCXML shapes and the one convention they share are written down: a block is one compound state carrying a <final>, and completion is done.state.<state id> (ADR-0004 decision 2). Read that module before writing a host block type - a type that follows the same convention composes with these without either side knowing about the other, and a type that does not is a type no container can sequence after.