Fanning state transitions out

sink and crucible/state are designed to compose
without either core importing the other. The state
kernel emits effects as pure data and performs no IO; sink is a natural place to
dispatch them. The optional crucible/sink/bridge module is the seam that joins
them, pulled in only when you want them together, forcing nothing on a service
that uses just one.
A transition fans out in one line
Section titled “A transition fans out in one line”bridge.Middleware wraps a machine’s Fire so every successful transition fans
out through a Manifold:
m := sink.NewManifold(sink.WithTracer(tracer)).Attach(auditOutlet, analyticsOutlet)
machine := state.Forge[string, string, *Order]("order"). Use(bridge.Middleware[string, string, *Order](m, bridge.WithTracer(tracer))). // ... states and transitions ... Quench(state.Strict())
machine.Cast(order).Fire(ctx, Submit) // the transition fans out through mEach transition is delivered as a bridge.Transition value (machine, event,
from, to); register transformers for it on your destinations just like any other
payload.
Spans nest, because Fire carries context
Section titled “Spans nest, because Fire carries context”Fire carries a context.Context, so the middleware starts a state.transition
span and propagates its context into Manifold.Sink. With the same tracer
wired into both the machine and the Manifold, the sink.Sink span nests under
the transition span, and each outlet’s write nests under that: the whole causal
chain in one trace.
flowchart TD
F["Fire(Submit)"] --> TR["state.transition span"]
TR --> SK["sink.Sink span"]
SK --> A["audit outlet"]
SK --> B["analytics outlet"]
This is the proof the two libraries compose cleanly: a state decision, its
fan-out, and the resulting writes all correlate through the shared
crucible/telemetry provider, with no import edge
between the kernels.
Two seams, one for each need
Section titled “Two seams, one for each need”bridge.Middleware: the context-propagating path. Use it whenever trace correlation matters (it almost always does).bridge.Inspector: adapts aManifoldtostate’sInspectorobserver (WithInspector), the one-line “fan every transition out” path. It carries no context, so emit spans do not nest under a transition; reach forMiddlewarewhen you want the nesting.
The same seam shape is intended to let a future broker mirror this, feeding a
machine from a stream, or publishing its transitions, without sink or state
changing. Compose the suite when it helps; never pay for what you do not use.
See also: Effects and purity and
Integrating Crucible on the state side, and
the sink/bridge reference.