state
import "github.com/stablekernel/crucible/state"Package state is the pure, abstract state machine kernel of the Crucible suite — a portable, domain-agnostic engine for forging event-driven services in Go.
Import path: github.com/stablekernel/crucible/state
What this kernel is
Section titled “What this kernel is”state is an abstract, domain-agnostic state machine kernel built once and usable everywhere. It is generic over state, event, and context types (conceptually Machine[S, E, C]) and knows nothing about any particular application domain. The same machine definition runs unchanged from a unit test, a synchronous request handler, and an asynchronous event consumer.
The kernel is stdlib-only. It imports only the Go standard library and performs no injected IO. This is the extreme end of the suite’s “thin seams, no-op defaults, no forced dependencies” philosophy: a tiny dependency graph is a tiny attack surface, and the kernel stays a clean, extractable unit forever. The stdlib-only boundary is enforced mechanically by an import-graph test.
Pure-function step semantics
Section titled “Pure-function step semantics”Firing an event returns (newState, effects, trace) without performing any IO. The caller dispatches the effects however it likes — publish to a broker, write to a store, call an RPC. Effects are abstract at the kernel (the kernel never inspects the payload) and concrete at your domain layer. This is what makes one machine usable across tests, handlers, and consumers without change.
An effect is discriminated data: every kernel-emitted effect reports a stable, serializable Kind (the KindedEffect interface) and serializes to an EffectEnvelope (kind + payload + meta), so effects can be journaled, deduped, rendered, and routed across a serialization boundary by kind rather than by Go type. An EffectRegistry decodes an envelope back to a concrete effect; built-in kinds are pre-registered and a host registers its own through RegisterEffect. An unknown effect kind is preserved on load and rejected only at dispatch, never silently dropped. Effects stay data the host applies — the kernel never executes them.
The definition IR is the spec
Section titled “The definition IR is the spec”The canonical machine is a serializable definition IR: pure data, lossless to and from JSON. Behavior is not embedded as closures in the IR; every guard, action, and effect is a named reference with serializable params, bound to host-provided implementations through a registry at freeze time. Binding fails loudly if any reference does not resolve.
This is the config/implementation split: structure is dual-authored (code or, eventually, a visual UI) while behavior is always code, surfaced to authors as a named palette. The Go DSL and a future UI are two front-ends that emit the same IR; a machine authored in Go and a machine loaded from JSON are the same machine.
Foundry vocabulary
Section titled “Foundry vocabulary”The lifecycle API uses a small “foundry” verb vocabulary. The noun stays plain — the type is a Machine — only the verbs are themed:
- Forge — open the builder DSL.
- Temper — optional, non-failing dev-time diagnostics pass (lint / static analysis), chainable before Quench.
- Quench — freeze the definition into an immutable Machine; the always-call finalizer that binds refs and panics on misconfiguration.
- Cast — pour a running instance from the machine.
- Fire — send an event to an instance and advance it.
- Assay — check that an externally-constructed entity is legally in a given state.
Operations that favor discoverability over metaphor stay plain: PlanPath, Requirements, Trace, and the To*/LoadFromJSON serializers.
Context: assigns and value semantics
Section titled “Context: assigns and value semantics”Context (the C type) is updated only through an assign — a pure reducer, AssignFn[C], that takes the prior context by value, the triggering event, and the ref’s static params and returns the next context. This is the sole context-mutation site (the G1 contract): guards and actions receive context read-only, actions emit effects-as-data and never write context, and the kernel folds the assigns declared on a transition’s exit, transition, and entry phases — in that order, declaration order within each phase, each reducer seeing the prior result — committing the folded value to the instance at the end of the step. Wire an assign with the Assign transition verb or the OnEntryAssign / OnExitAssign state verbs; register the reducer with Builder.Reducer (or Registry.Assign). A service result or actor done-data reaches its onDone transition’s assign through the re-fired done event’s payload (AssignCtx.Event), delivered with the WithEventData fire option — no host side channel.
Use a VALUE context type (Machine[S, E, Order], not Machine[S, E, *Order]). Under a value C the kernel’s structural guarantees hold: a guard or action that writes the context copy it receives mutates a throwaway, so the instance is untouched (read-only falls out for free), and a service or actor observes a point-in-time snapshot value at invocation rather than an alias that could leak later mutations. A pointer C stays compilable as an ergonomics/performance escape hatch, but it forfeits these guarantees: the copy is a copied pointer to the same value, so a guard/action can mutate through the alias and a service can observe later mutations. With a pointer C the consumer owns that discipline; the structural read-only, clean-replay, and deterministic-analysis contracts hold only for a value C.
Determinism and ordering
Section titled “Determinism and ordering”The pure step is also a deterministic step: given the same machine, the same starting configuration, and the same event, a Fire produces the same effects, the same context, and the same Trace — byte-for-byte, every time. Purity keeps a Fire from reading the clock or doing IO; determinism additionally freezes the ORDER in which the step emits effects, folds assigns, and advances states. This is what makes a Trace journalable and a run replayable: a consumer that records the event stream can re-derive the identical effect/context sequence later.
The emission order is frozen as follows, and is golden-locked by a regression test so a reorder is a visible failure:
-
Cascade phases run exit -> transition -> entry, in that fixed order. The exit cascade runs innermost-first (the source leaf, then its ancestors up to but not including the least common ancestor); the entry cascade runs outermost-first (the least common ancestor’s child down to the target, then the descent into the target’s initial children). A reentering self/ancestor transition exits up to and including its target, then re-enters it.
-
Within a single state’s phase, effects (actions) run before assigns (reducers), each in declaration order. The folded context of a phase becomes the input to the next phase’s assigns; the value committed to the instance at the end of the step is the fold of every phase’s assigns in cascade order. Effects read the context as it stood at phase entry (read-only).
-
Parallel regions are broadcast in REGION DECLARATION ORDER. When several regions handle the same event in one macrostep, the earlier-declared region’s effects and assigns are emitted and folded before the later one’s, so a cross-region assign fold is deterministic and order-stable. Likewise a parallel target’s entry descends its regions in declaration order, and the active configuration lists region leaves in that same order.
-
The run-to-completion (RTC) microstep interleave is fixed: after the triggering transition settles, the macrostep drains raised internal events FIRST (FIFO, in the order they were raised), then fires one enabled eventless (“always”) transition, and repeats until the configuration is stable. Raised events always precede eventless transitions within a microstep. The internal queue is macrostep-local, so the interleave is reproducible and Fire stays pure. A cycle is bounded and fails fast with a typed overflow error rather than spinning.
-
Auto-emitted lifecycle effects keep their cascade slot: a ScheduleAfter / StartService / SpawnActor for an entered state is appended after that state’s entry effects and assigns; a CancelScheduled / StopService / StopActor for an exited state after its exit effects and assigns — all in exit/entry order.
The Trace records each of these in order: EffectsEmitted and AssignsApplied list the per-step effects and folds in emission order, ExitedStates and EnteredStates the cascade in execution order, and Microsteps the RTC interleave (each raised event and eventless step, plus per-region markers) as it happened. FireResult’s Effects slice carries the same effects, in the same order, as data.
The ordering is structural, not incidental. Every emission, fold, and cascade walk iterates declaration-ordered slices — states, transitions, regions, children, refs — never a Go map. The kernel’s maps (node and state indices, the behavior registry) are consulted only for keyed lookup, never iterated to drive order, so no map-iteration nondeterminism can leak into a Fire. This holds under a value context (see above); a pointer context forfeits the clean-replay guarantee because a guard or action can mutate through the shared alias.
Design
Section titled “Design”The public API follows the suite’s functional-options convention: every public constructor and operation takes a variadic option tail. Required inputs stay positional; everything optional or extensible is an option; a zero-option call reads clean. New capability arrives as a new option — additive-only, never a signature or breaking change. The kernel idiom is fail-fast by default, with resilience and aggregation available opt-in via options.
Observability is Trace-first: the structured Trace is the canonical surface, recording matched transitions, guard and policy evaluations, emitted effects, and the outcome as pure data. An optional WithLogger(*slog.Logger) (no-op by default) is the only logging seam; the kernel never logs unless asked and never imports a third-party logger. Determinism is preserved by injecting time and identifier seams rather than calling time.Now or rand directly.
As a library, the kernel never exits the process — it never calls os.Exit or log.Fatal on an operational error. Panics are reserved strictly for programmer error at construction time (Quench).
Status
Section titled “Status”The kernel implements the Forge/Temper/Quench build path, Cast/Fire pure step semantics with guards, actions, typed errors and an always-recorded Trace, Assay/Requirements, PlanPath (BFS), FireSeq/FireEach batch helpers, and lossless ToJSON/LoadFromJSON/Provide round-trip.
Hierarchical and orthogonal states extend the same surface: a state may declare nested substates with an initial child (compound states) or parallel regions (orthogonal states). Superstates nest to arbitrary depth — a SuperState block may contain another SuperState block — and parallel regions may contain nested compounds. Events resolve child-first and bubble to ancestors; orthogonal regions each receive the event and resolve independently; transitions run the standard exit/entry cascade across the hierarchy; and final states drive done-event completion, including the all-regions-final join for parallel states. The hierarchy serializes, so a nested machine round-trips through JSON losslessly.
History pseudo-states (shallow and deep) let a transition re-enter a compound state’s last active configuration rather than its initial child; the pseudo-states serialize while the recorded per-instance configuration is runtime state threaded through the pure Fire step.
Delayed (`after`) transitions are drivable: entering a state with an `after` transition emits a ScheduleAfter effect and exiting it a CancelScheduled effect (auto-cancel-on-exit), while Fire stays pure — a host Scheduler driver owns the real timer and re-fires the delayed event, with a deterministic FakeClock for testing.
Invoked services (`invoke`) are drivable: entering a state that declares an invoke emits a StartService effect and exiting it before the service completes emits a StopService effect (auto-stop-on-exit), while Fire stays pure — a host ServiceRunner runs the bound service and re-fires the invocation’s onDone (with the result) or onError (with the error) back through Fire, with a deterministic settle-by-id harness for testing.
Child-machine actors are live: a state may invoke another Machine as a sub-actor (InvokeActor) or spawn one dynamically (Spawn), driven by a host ActorSystem that runs the child, routes its done-data to the parent’s onDone and its failure to the parent’s onError, and carries inter-actor messages (SendTo / SendParent / Respond / Forward) between mailboxes — all as host-dispatched effects, so the pure Fire step still owns no mailbox and performs no IO. When a child fails and the parent declared no onError, the failure does not vanish: the default is escalate-to-parent — a typed *ActorEscalation recorded on the system (LastEscalation), surfaced to the inspector, climbed up the supervision chain, and optionally routed to a host EscalationHandler. Supervision STRATEGIES (restart / resume / backoff) layer additively on that frozen default.
Guard expressions
Section titled “Guard expressions”A transition guard is authored at one of three graduated tiers, all bindings of the same frozen Guard data contract (context + params -> bool), so a machine mixes tiers freely and the tier is a property of the guard, not the kernel:
- Core — a small, dependency-free expression built with the in-package builder (Field(”…”).Eq/Lt/In/…, And/Or/Not, StateIn) over a fixed vocabulary — boolean composition, typed compare, membership, and state-tests. It lowers to a serializable GuardNode tree (GuardKindCore) the kernel evaluates IN-KERNEL, adds no dependency, serializes losslessly, and stays transparent to tooling and analysis.
- Rich — a mature embedded expression engine (CEL) for cross-stack evaluation and richer logic (arithmetic, map construction) than Core admits. It lives in the opt-in github.com/stablekernel/crucible/state/expr module so the kernel itself stays stdlib-only; a Rich guard is checked against the ContextSchema at freeze time and serializes as a GuardKindRich node.
- Escape — a plain Go func registered as a named guard (Registry.Guard). It is the always-available, maximally-expressive tier; it is opaque to the analyzer and does not cross a serialization boundary, so reserve it for logic the declarative tiers cannot express.
Core and Rich guards are STRUCTURALLY read-only — an expression cannot mutate context. An Escape Go-func guard is read-only by CONTRACT (documented; under a value context the kernel’s value semantics make a mutation a throwaway anyway).
Context schema
Section titled “Context schema”A machine may declare a ContextSchema — a serializable description of the context type’s fields and their types. It is the type contract the declarative guard tiers check against: a Core or Rich expression that references a field is validated against the schema at freeze time rather than failing at run time, and the schema is the data contract a cross-language evaluator binds the same machine to. It is optional; an Escape Go-func guard needs none.
Versioning, snapshots, and journal seams
Section titled “Versioning, snapshots, and journal seams”A definition carries a SchemaVersion (the IR wire form), an optional machine ID and definition version, and serializes losslessly with unknown fields preserved, so a newer document round-trips through an older loader without corruption and a higher MAJOR schema version is refused rather than guessed at. An instance snapshots to a versioned Snapshot and restores under a lenient version posture (accept-and-upgrade within a compatible range, reject across a major boundary; strict machine-version checking is opt-in via RejectMachineVersionMismatch). The Trace records a structured EventPayload alongside the human Event label so a recorded event stream replays the exact event — the journal/durable-execution seam the deterministic step makes sound.
Example (Connection Lifecycle)
Example_connectionLifecycle drives the connection lifecycle exemplar end-to-end through the real host runtime — an ActorSystem, a Scheduler on a FakeClock, and a ServiceRunner wired around one instance. It shows a transient dial failure that backs off and retries on a timer, a guarded admission into a parallel Connected configuration, a worker actor that runs a task to completion, and an eventless run-to-completion shutdown. The connHarness (in exemplar_test.go) wires the three drivers and routes every Fire’s effects through them.
ctx := context.Background()h := newConnHarness()fmt.Println("start:", fmtConfig(h.inst.Configuration()))
// Connect arms the dial service; the first attempt fails, falling back to// Backoff, where a connect-timeout timer is armed.h.fire(ctx, Connect)h.settleDial(ctx, false)fmt.Println("dial failed:", fmtConfig(h.inst.Configuration()))
// Advancing the fake clock past the timeout fires the delayed Retry edge, which// re-enters Connecting; the second dial succeeds and the guarded Dialed edge// admits the instance into the parallel Connected configuration.h.advancePastTimeout(ctx)h.settleDial(ctx, true)fmt.Println("connected:", fmtConfig(h.inst.Configuration()))
// Assigning work spawns a worker actor; stepping it to completion routes the// result back through the parent, draining the Work region.h.fire(ctx, Assign)h.runWorkers(ctx)fmt.Println("work done:", fmtConfig(h.inst.Configuration()))
// Close runs to completion through the eventless edge into the final state.h.fire(ctx, Close)fmt.Println("closed:", fmtConfig(h.inst.Configuration()), "final:", h.inst.InFinal())
// Output:// start: Disconnected// dial failed: Backoff// connected: Beating,WorkIdle// work done: Beating,Drained// closed: Closed final: trueOutput
Section titled “Output”start: Disconnecteddial failed: Backoffconnected: Beating,WorkIdlework done: Beating,Drainedclosed: Closed final: true- Constants
- func ActorID[S comparable](machine string, from S, idx int) string
- func BindingTransportOf(d Descriptor) string
- func InvokeID[S comparable](machine string, from S, idx int) string
- func MarshalSnapshot[S comparable, E comparable, C any](snap Snapshot[S, E, C], opts …SnapshotCodecOption[C]) ([]byte, error)
- func ScheduleID[S comparable](machine string, from S, idx int) string
- type ActionBinding
- type ActionCtx
- type ActionFn
- type ActionRequest
- type ActionResult
- type ActorBehavior
- type ActorEscalation
- type ActorInstance
- type ActorKind
- type ActorPhase
- type ActorRef
- type ActorSystem
- func NewActorSystem[S comparable, E comparable, C any](parent *Instance[S, E, C]) *ActorSystem[S, E, C]
- func (s *ActorSystem[S, E, C]) Absorb(ctx context.Context, effects []Effect)
- func (s *ActorSystem[S, E, C]) AbsorbFor(ctx context.Context, event any, effects []Effect)
- func (s *ActorSystem[S, E, C]) Deliver(ctx context.Context, ref ActorRef, event any) bool
- func (s *ActorSystem[S, E, C]) DeliverByID(ctx context.Context, id string, event any) bool
- func (s *ActorSystem[S, E, C]) IDs() []string
- func (s *ActorSystem[S, E, C]) IsRunning(id string) bool
- func (s *ActorSystem[S, E, C]) LastError() error
- func (s *ActorSystem[S, E, C]) LastEscalation() *ActorEscalation
- func (s *ActorSystem[S, E, C]) LastOutput() (any, bool)
- func (s *ActorSystem[S, E, C]) Ref(id string) (ActorRef, bool)
- func (s *ActorSystem[S, E, C]) RefBySystemID(systemID string) (ActorRef, bool)
- func (s *ActorSystem[S, E, C]) Register(src string, behavior ActorBehavior) *ActorSystem[S, E, C]
- func (s *ActorSystem[S, E, C]) RestoreActors(ctx context.Context, actors map[string]json.RawMessage) error
- func (s *ActorSystem[S, E, C]) Running() int
- func (s *ActorSystem[S, E, C]) SettleError(ctx context.Context, id string, err error) (FireResult[S], bool)
- func (s *ActorSystem[S, E, C]) SnapshotActors() (map[string]json.RawMessage, error)
- func (s *ActorSystem[S, E, C]) Step(ctx context.Context, id string) []FireResult[S]
- func (s *ActorSystem[S, E, C]) Stop(ref ActorRef)
- func (s *ActorSystem[S, E, C]) WithActorInspector(insp Inspector) *ActorSystem[S, E, C]
- func (s *ActorSystem[S, E, C]) WithEscalationHandler(handler EscalationHandler) *ActorSystem[S, E, C]
- type AssayError
- type AssayOption
- type AssignBinding
- type AssignCtx
- type AssignFn
- type AssignRequest
- type AssignResult
- type BatchResult
- type BindingSpec
- type Builder
- func Forge[S comparable, E comparable, C any](name string, opts …ForgeOption) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Action(name string, fn ActionFn[C], opts …DescribeOption) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Actor(name string, opts …DescribeOption) *Builder[S, E, C]
- func (b *Builder[S, E, C]) After(delay time.Duration) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Always() *Builder[S, E, C]
- func (b *Builder[S, E, C]) Assign(assignName string, params …map[string]any) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Cancel(id string) *Builder[S, E, C]
- func (b *Builder[S, E, C]) CurrentStateFn(fn func(C) S) *Builder[S, E, C]
- func (b *Builder[S, E, C]) DefaultTo(target S) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Do(actionName string, params …map[string]any) *Builder[S, E, C]
- func (b *Builder[S, E, C]) EndRegion() *Builder[S, E, C]
- func (b *Builder[S, E, C]) EndSuperState() *Builder[S, E, C]
- func (b *Builder[S, E, C]) Final() *Builder[S, E, C]
- func (b *Builder[S, E, C]) Forbid(event E) *Builder[S, E, C]
- func (b *Builder[S, E, C]) ForbidAny() *Builder[S, E, C]
- func (b *Builder[S, E, C]) ForwardTo(targetID string, opts …SendOption) *Builder[S, E, C]
- func (b *Builder[S, E, C]) GoTo(to S) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Guard(name string, fn GuardFn[C], opts …DescribeOption) *Builder[S, E, C]
- func (b *Builder[S, E, C]) History(name S, kind HistoryType) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Initial(name S) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Invoke(src string, onDone, onError E, opts …InvokeOption) *Builder[S, E, C]
- func (b *Builder[S, E, C]) InvokeActor(src string, onDone, onError E, opts …InvokeOption) *Builder[S, E, C]
- func (b *Builder[S, E, C]) On(event E) *Builder[S, E, C]
- func (b *Builder[S, E, C]) OnAny() *Builder[S, E, C]
- func (b *Builder[S, E, C]) OnDone(actionName string, params …map[string]any) *Builder[S, E, C]
- func (b *Builder[S, E, C]) OnEntry(actionName string, params …map[string]any) *Builder[S, E, C]
- func (b *Builder[S, E, C]) OnEntryAssign(assignName string, params …map[string]any) *Builder[S, E, C]
- func (b *Builder[S, E, C]) OnExit(actionName string, params …map[string]any) *Builder[S, E, C]
- func (b *Builder[S, E, C]) OnExitAssign(assignName string, params …map[string]any) *Builder[S, E, C]
- func (b *Builder[S, E, C]) OwnedBy(owner string) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Palette() []Descriptor
- func (b *Builder[S, E, C]) Quench(opts …QuenchOption) *Machine[S, E, C]
- func (b *Builder[S, E, C]) Raise(events …E) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Reducer(name string, fn AssignFn[C], opts …DescribeOption) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Reenter() *Builder[S, E, C]
- func (b *Builder[S, E, C]) Region(name string) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Requires(req Requirement[C]) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Respond(event E) *Builder[S, E, C]
- func (b *Builder[S, E, C]) SendParent(event E) *Builder[S, E, C]
- func (b *Builder[S, E, C]) SendTo(targetID string, event E, opts …SendOption) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Service(name string, fn ServiceFn[C], opts …DescribeOption) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Spawn(src, id string, opts …SpawnOption) *Builder[S, E, C]
- func (b *Builder[S, E, C]) State(name S) *Builder[S, E, C]
- func (b *Builder[S, E, C]) StopActor(id string) *Builder[S, E, C]
- func (b *Builder[S, E, C]) StopChild(id string) *Builder[S, E, C]
- func (b *Builder[S, E, C]) SubState(name S) *Builder[S, E, C]
- func (b *Builder[S, E, C]) SuperState(name S) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Temper(opts …TemperOption) []Diagnostic
- func (b *Builder[S, E, C]) Transition(from S) *Builder[S, E, C]
- func (b *Builder[S, E, C]) Use(mw …Middleware[S, E, C]) *Builder[S, E, C]
- func (b *Builder[S, E, C]) WaitMode(m WaitMode) *Builder[S, E, C]
- func (b *Builder[S, E, C]) When(guardName string, params …map[string]any) *Builder[S, E, C]
- func (b *Builder[S, E, C]) WhenExpr(expr GuardNode[S]) *Builder[S, E, C]
- func (b *Builder[S, E, C]) WithContextSchema(schema ContextSchema) *Builder[S, E, C]
- type CancelScheduled
- type CastOption
- type Clock
- type ContextCodec
- type ContextSchema
- type ContextView
- type DescribeBuilder
- func Describe(description string) *DescribeBuilder
- func (d *DescribeBuilder) EnumParam(name string, allowed …string) *DescribeBuilder
- func (d *DescribeBuilder) OptionalParam(name string, typ ParamType) *DescribeBuilder
- func (d *DescribeBuilder) Param(name string, typ ParamType) *DescribeBuilder
- func (d *DescribeBuilder) ParamSpec(p ParamSpec) *DescribeBuilder
- func (d *DescribeBuilder) Reads(fields …string) *DescribeBuilder
- func (d *DescribeBuilder) Writes(fields …string) *DescribeBuilder
- type DescribeOption
- type Descriptor
- type DescriptorKind
- type Diagnostic
- type Effect
- type EffectEnvelope
- type EffectFactory
- type EffectRegistry
- type ErrActionFailed
- type ErrActorPanic
- type ErrAssignPanic
- type ErrGuardFailed
- type ErrGuardPanic
- type ErrInvalidTransition
- type ErrMicrostepOverflow
- type ErrNoInitialState
- type ErrNoPath
- type ErrPolicyDenied
- type ErrUnboundActor
- type ErrUnboundRef
- type ErrUndeclaredState
- type ErrUnknownBuiltin
- type ErrUnknownEffectKind
- type ErrUnsupportedSchema
- type EscalationHandler
- type FakeClock
- type FieldRef
- func Field[S comparable](path string) FieldRef[S]
- func (f FieldRef[S]) Eq(operand Operand[S]) GuardNode[S]
- func (f FieldRef[S]) Ge(operand Operand[S]) GuardNode[S]
- func (f FieldRef[S]) Gt(operand Operand[S]) GuardNode[S]
- func (f FieldRef[S]) In(values …Operand[S]) GuardNode[S]
- func (f FieldRef[S]) Le(operand Operand[S]) GuardNode[S]
- func (f FieldRef[S]) Lt(operand Operand[S]) GuardNode[S]
- func (f FieldRef[S]) Ne(operand Operand[S]) GuardNode[S]
- type FireFunc
- type FireOption
- type FireResult
- type ForgeOption
- type ForwardEvent
- type GuardBinding
- type GuardCtx
- type GuardFn
- type GuardKind
- type GuardNode
- func And[S comparable](nodes …GuardNode[S]) GuardNode[S]
- func Guard[S comparable](name string, params …map[string]any) GuardNode[S]
- func Not[S comparable](node GuardNode[S]) GuardNode[S]
- func Or[S comparable](nodes …GuardNode[S]) GuardNode[S]
- func StateIn[S comparable](state S) GuardNode[S]
- func (g *GuardNode[S]) LeafRefs() []Ref
- func (g GuardNode[S]) MarshalJSON() ([]byte, error)
- func (g *GuardNode[S]) StateInTargets() []S
- func (g *GuardNode[S]) UnmarshalJSON(data []byte) error
- type GuardOp
- type GuardRequest
- type GuardResult
- type HistoryType
- type IOSpec
- type IR
- type InFlightService
- type InspectKind
- type InspectionEvent
- type Inspector
- type InspectorFunc
- type Instance
- func (i *Instance[S, E, C]) Clock() Clock
- func (i *Instance[S, E, C]) Configuration() []S
- func (i *Instance[S, E, C]) Current() S
- func (i *Instance[S, E, C]) Entity() C
- func (i *Instance[S, E, C]) Fire(ctx context.Context, event E, opts …FireOption) FireResult[S]
- func (i *Instance[S, E, C]) FireSeq(ctx context.Context, events []E, opts …FireOption) BatchResult[S]
- func (i *Instance[S, E, C]) History() []Trace
- func (i *Instance[S, E, C]) InFinal() bool
- func (i *Instance[S, E, C]) ResumeEffects() []Effect
- func (i *Instance[S, E, C]) Snapshot() Snapshot[S, E, C]
- func (i *Instance[S, E, C]) StartEffects() []Effect
- type Invocation
- type InvokeOption
- type JournalEntry
- type JournalKind
- type KindedEffect
- type Literal
- type LoadOption
- type Machine
- func (m *Machine[S, E, C]) Assay(s S, entity C, opts …AssayOption) error
- func (m *Machine[S, E, C]) Cast(entity C, opts …CastOption[S]) *Instance[S, E, C]
- func (m *Machine[S, E, C]) Name() string
- func (m *Machine[S, E, C]) Palette() []Descriptor
- func (m *Machine[S, E, C]) PlanPath(from, to S, entity C, opts …PlanOption) ([]E, error)
- func (m *Machine[S, E, C]) Requirements(s S) []Requirement[C]
- func (m *Machine[S, E, C]) Restore(snap Snapshot[S, E, C], opts …RestoreOption[S]) (*Instance[S, E, C], error)
- func (m *Machine[S, E, C]) Services() map[string]ServiceFn[C]
- func (m *Machine[S, E, C]) ToDOT(opts …VizOption) string
- func (m *Machine[S, E, C]) ToJSON(opts …ToJSONOption) ([]byte, error)
- func (m *Machine[S, E, C]) ToMermaid(opts …VizOption) string
- type MessagePhase
- type Middleware
- type MultiRegionErr
- type Operand
- func Bool[S comparable](v bool) Operand[S]
- func Dur[S comparable](v time.Duration) Operand[S]
- func FieldOp[S comparable](f FieldRef[S]) Operand[S]
- func Float[S comparable](v float64) Operand[S]
- func Int[S comparable](v int64) Operand[S]
- func Param[S comparable](v string) Operand[S]
- func Str[S comparable](v string) Operand[S]
- type Outcome
- type P
- type ParamSpec
- type ParamType
- type PendingRefs
- type PlanOption
- type ProvideOption
- type QuenchOption
- type Ref
- type Region
- type RegisterEffectOption
- type Registry
- func NewRegistry[C any]() *Registry[C]
- func (r *Registry[C]) Action(name string, fn ActionFn[C], opts …DescribeOption) *Registry[C]
- func (r *Registry[C]) Actor(name string, opts …DescribeOption) *Registry[C]
- func (r *Registry[C]) Assign(name string, fn AssignFn[C], opts …DescribeOption) *Registry[C]
- func (r *Registry[C]) BindGuard(name string, b GuardBinding[C], opts …DescribeOption) *Registry[C]
- func (r *Registry[C]) Guard(name string, fn GuardFn[C], opts …DescribeOption) *Registry[C]
- func (r *Registry[C]) Palette() []Descriptor
- func (r *Registry[C]) Service(name string, fn ServiceFn[C], opts …DescribeOption) *Registry[C]
- type Requirement
- type RequirementFailure
- type RespondToSender
- type RestoreOption
- type ScheduleAfter
- type Scheduler
- func NewScheduler[S comparable, E comparable, C any](inst *Instance[S, E, C]) *Scheduler[S, E, C]
- func (s *Scheduler[S, E, C]) Absorb(ctx context.Context, effects []Effect)
- func (s *Scheduler[S, E, C]) HasPending(id string) bool
- func (s *Scheduler[S, E, C]) Pending() int
- func (s *Scheduler[S, E, C]) Tick(ctx context.Context) []FireResult[S]
- type SchemaField
- type SchemaKind
- type SendOption
- type SendParent
- type SendTo
- type ServiceBinding
- type ServiceCtx
- type ServiceFn
- type ServiceRequest
- type ServiceRunner
- func NewServiceRunner[S comparable, E comparable, C any](inst *Instance[S, E, C], reg *Registry[C]) *ServiceRunner[S, E, C]
- func (r *ServiceRunner[S, E, C]) Absorb(ctx context.Context, effects []Effect)
- func (r *ServiceRunner[S, E, C]) HasPending(id string) bool
- func (r *ServiceRunner[S, E, C]) LastError() error
- func (r *ServiceRunner[S, E, C]) LastResult() (any, bool)
- func (r *ServiceRunner[S, E, C]) Pending() int
- func (r *ServiceRunner[S, E, C]) PendingIDs() []string
- func (r *ServiceRunner[S, E, C]) Run(ctx context.Context, id string) (FireResult[S], bool)
- func (r *ServiceRunner[S, E, C]) SettleDone(ctx context.Context, id string, result any) (FireResult[S], bool)
- func (r *ServiceRunner[S, E, C]) SettleError(ctx context.Context, id string, err error) (FireResult[S], bool)
- type Snapshot
- func UnmarshalSnapshot[S comparable, E comparable, C any](b []byte, opts …SnapshotCodecOption[C]) (Snapshot[S, E, C], error)
- func WaitFor[S comparable, E comparable, C any](ctx context.Context, inst *Instance[S, E, C], predicate WaitPredicate[S, E, C], opts …WaitOption[S, E, C]) (Snapshot[S, E, C], error)
- func (snap Snapshot[S, E, C]) MarshalJSON() ([]byte, error)
- func (snap *Snapshot[S, E, C]) UnmarshalJSON(b []byte) error
- type SnapshotCodecOption
- type SnapshotError
- type SnapshotVersionError
- type Snapshotter
- type SpawnActor
- type SpawnOption
- type StartService
- type State
- type Status
- type StopActor
- type StopService
- type TemperOption
- type ToJSONOption
- type Trace
- type Transition
- type UnknownEffect
- type VizOption
- type WaitMode
- type WaitOption
- func WithWaitScheduler[S comparable, E comparable, C any](sch *Scheduler[S, E, C]) WaitOption[S, E, C]
- func WithWaitStep[S comparable, E comparable, C any](d time.Duration) WaitOption[S, E, C]
- func WithWaitStepFunc[S comparable, E comparable, C any](advance func(ctx context.Context, clock Clock, step time.Duration)) WaitOption[S, E, C]
- func WithWaitTimeout[S comparable, E comparable, C any](d time.Duration) WaitOption[S, E, C]
- type WaitPredicate
- type WaitTimeoutError
Constants
Section titled “Constants”Built-in effect kinds. Each is the stable discriminant the matching kernel effect reports from Kind() and carries on its serialized envelope. They share the reserved crucible. namespace so a host’s own effect kinds never collide with the kernel’s. These are part of the wire contract and are closed-enum extended per the unknown-variant policy: a decoder that meets an unrecognized kind preserves it (see UnknownEffect) and rejects it only at dispatch.
const ( EffectKindSpawnActor = "crucible.spawnActor" EffectKindStopActor = "crucible.stopActor" EffectKindStartService = "crucible.startService" EffectKindStopService = "crucible.stopService" EffectKindScheduleAfter = "crucible.scheduleAfter" EffectKindCancelScheduled = "crucible.cancelScheduled" EffectKindSendTo = "crucible.sendTo" EffectKindSendParent = "crucible.sendParent" EffectKindRespondToSender = "crucible.respondToSender" EffectKindForwardEvent = "crucible.forwardEvent")CurrentSchemaVersion is the IR wire-format version this build emits and accepts. It is a major.minor string: a higher minor (same major) loads with unknown fields preserved for forward-compat, while a higher major is refused by LoadFromJSON as *ErrUnsupportedSchema. Every document ToJSON emits is stamped with this version, so an IR on the wire is self-describing.
const CurrentSchemaVersion = "1.0"CurrentSnapshotVersion is the snapshot-format schema version stamped by Snapshot and validated by Restore. It is the major.minor schema generation of the Snapshot envelope encoded as major*1000 + minor, so a single int both orders versions and exposes the major for the restore-version posture: a snapshot is restorable within the same major (snapshotMajor), and a major mismatch is rejected. Version 1 is (1*1000 + 0); a future additive field bumps the minor, a breaking change bumps the major.
const CurrentSnapshotVersion = 1 * snapshotMajorScaleTransportInProcess is the v1 default binding transport: the behavior is a Go func held in the host registry and called in-process. It is the only transport the kernel dispatches at v1; every other transport is reserved.
const TransportInProcess = "in-process"func ActorID
Section titled “func ActorID”func ActorID[S comparable](machine string, from S, idx int) stringActorID returns the stable identifier the kernel assigns to the child-machine actor invocation at index idx on owning state `from` of machine `machine` when the invocation declares no explicit ID. A host or test uses it to correlate a SpawnActor with a later StopActor, to Deliver an event to the actor, or to assert which actor a StopActor targets.
func BindingTransportOf
Section titled “func BindingTransportOf”func BindingTransportOf(d Descriptor) stringBindingTransportOf returns the binding transport a descriptor declares, defaulting to in-process when the descriptor has no Binding (the common case) or an empty transport. It is the canonical reader of the reserved binding default.
func InvokeID
Section titled “func InvokeID”func InvokeID[S comparable](machine string, from S, idx int) stringInvokeID returns the stable identifier the kernel assigns to the invoked service at index idx on owning state `from` of machine `machine` when the invocation declares no explicit ID. A host or test uses it to correlate a StartService with a later StopService, or to assert which service a StopService targets.
func MarshalSnapshot
Section titled “func MarshalSnapshot”func MarshalSnapshot[S comparable, E comparable, C any](snap Snapshot[S, E, C], opts ...SnapshotCodecOption[C]) ([]byte, error)MarshalSnapshot serializes snap to JSON, encoding its context through codec (or the default JSON codec when codec is nil). It is the explicit serialization entry point when a non-JSON-marshalable context needs a custom codec; for a JSON-marshalable context, json.Marshal(snap) works directly via the snapshot’s own MarshalJSON.
func ScheduleID
Section titled “func ScheduleID”func ScheduleID[S comparable](machine string, from S, idx int) stringScheduleID returns the stable schedule identifier the kernel assigns to the delayed (`after`) transition at index idx on source state `from` of machine `machine`. A host or test uses it to correlate a ScheduleAfter with a later Cancel, or to assert which timer a CancelScheduled targets.
type ActionBinding
Section titled “type ActionBinding”ActionBinding turns an action request into emitted effects. The in-process binding wraps an ActionFn.
type ActionBinding[C any] interface { EvalAction(ctx context.Context, req ActionRequest[C]) (ActionResult, error)}type ActionCtx
Section titled “type ActionCtx”ActionCtx is passed to a bound action function at run time.
type ActionCtx[C any] struct { Entity C Params map[string]any}type ActionFn
Section titled “type ActionFn”ActionFn produces an effect (or error) for a transition.
type ActionFn[C any] func(ctx ActionCtx[C]) (Effect, error)type ActionRequest
Section titled “type ActionRequest”ActionRequest is the serializable invocation envelope for an action: the named ref, its params, and the read-only context projection.
type ActionRequest[C any] struct { Name string Params map[string]any Context ContextView}type ActionResult
Section titled “type ActionResult”ActionResult is the action’s serializable result. Effects carries the emitted effects-as-data (today an action emits exactly one). Actions never write context: under the value-semantics contract a context change is expressed only through an Assign, whose AssignResult.Context carries the new value. The channel an action formerly reserved for a context delta now lives on the assign binding, the sole context writer.
type ActionResult struct { Effects []Effect}type ActorBehavior
Section titled “type ActorBehavior”ActorBehavior creates a fresh child-machine actor instance bound to the given input. It is the actor-palette analog of a ServiceFn: a host registers one per child-machine src name, and the ActorSystem calls it to spawn an actor when it absorbs a SpawnActor effect for that src. The returned ActorInstance erases the child’s own (S, E, C) generic parameters behind the ActorInstance interface, so a parent of any type can host children of any type. The input is the SpawnActor Input is the actor input; a behavior typically Casts its child machine with a WithInitialState derived from input.
type ActorBehavior func(input map[string]any) (ActorInstance, error)type ActorEscalation
Section titled “type ActorEscalation”ActorEscalation is the typed failure an unhandled child-machine actor error raises to its parent. It is produced when an actor fails (an error settlement, a behavior that could not start, a panic recovered while the actor stepped, or an explicit SettleError) and the spawning parent declared no onError event for that actor: rather than swallow the failure, the ActorSystem escalates it.
It is the v1 default escalation signal — the actor-model analog of an unhandled crash propagating up a supervision hierarchy. It wraps the underlying child error (so errors.Unwrap and errors.As reach it) and identifies the failed actor.
type ActorEscalation struct { // ActorID is the registry id of the actor that failed. ActorID string // SystemID is the failed actor's system-scoped name, empty when it had none. SystemID string // Src is the actor ref name the failed actor was spawned from. Src string // ParentID is the id of the actor the failure escalated TO: the failed actor's // parent actor, or empty when it escalated to the parent instance (the system // root), which has no actor id of its own. ParentID string // Err is the underlying child failure that triggered the escalation. Err error}func (*ActorEscalation) Error
Section titled “func (*ActorEscalation) Error”func (e *ActorEscalation) Error() stringError renders the escalation, naming the failed actor and the wrapped cause.
func (*ActorEscalation) Unwrap
Section titled “func (*ActorEscalation) Unwrap”func (e *ActorEscalation) Unwrap() errorUnwrap returns the underlying child failure so errors.Is / errors.As reach the cause an escalation wraps.
type ActorInstance
Section titled “type ActorInstance”ActorInstance is a running child actor as the ActorSystem sees it, with the child’s own (S, E, C) generic parameters erased. A host obtains one by wrapping a Cast child *Instance with NewActor; the deterministic test driver and the production driver both drive actors purely through this interface.
type ActorInstance interface { // DeliverFire fires one event through the actor, returning whether the actor // reached its final state and the output it exposes on completion. The event is // the actor's own event type, passed type-erased; an implementation type-asserts // it and ignores an event of the wrong type (a no-op, mirroring the kernel's // effect-type guards). A backing *Instance implementation also surfaces the // SpawnActor / StopActor effects the child itself emitted, so the system can run // nested actors — those are returned via ChildEffects. DeliverFire(ctx context.Context, event any) (done bool, output any) // ChildEffects returns the actor effects the actor emitted on its most recent // DeliverFire (and on its initial entry): the SpawnActor / StopActor lifecycle // effects so the ActorSystem can spawn or stop the actor's own children, and the // SendTo / SendParent / RespondToSender / ForwardEvent communication effects so // the system can route the actor's outbound messages. It returns a fresh slice // each call and drains the buffer. ChildEffects() []Effect // Output returns the actor's completion output once it has reached its final // state, or nil before then. It lets a host expose a snapshot's output. Output() any}func NewActor
Section titled “func NewActor”func NewActor[S comparable, E comparable, C any](inst *Instance[S, E, C], output func(*Instance[S, E, C]) any) ActorInstanceNewActor adapts a Cast child *Instance into an ActorInstance an ActorSystem can run as a child-machine actor. output, when non-nil, extracts the actor’s v5 `output` from the child instance once it reaches its final state (typically reading the child entity); pass nil for an actor whose completion carries no output. The returned ActorInstance is what an ActorBehavior returns. The child’s initial-entry actor effects (StartEffects) are buffered immediately, so the system spawns any actors the child invokes on entry.
type ActorKind
Section titled “type ActorKind”ActorKind tags an Invocation as either a host-run service or a child-machine actor. The default (ActorKindService) preserves the invoked-services contract verbatim; ActorKindMachine marks the invocation as spawning a child MACHINE actor, so entering the owning state emits a SpawnActor effect instead of a StartService effect, and the host’s ActorSystem (not a ServiceRunner) runs it.
type ActorKind intActor kinds. ActorKindService is the invoked-services default (a host-run unit of work); ActorKindMachine invokes a child machine as an actor.
const ( ActorKindService ActorKind = iota ActorKindMachine)type ActorPhase
Section titled “type ActorPhase”ActorPhase distinguishes the lifecycle point of an InspectActor event.
type ActorPhase stringconst ( // ActorSpawned marks an actor created and started. ActorSpawned ActorPhase = "spawned" // ActorStopped marks an actor stopped (completed, errored, or auto-stopped on // exit). ActorStopped ActorPhase = "stopped" // ActorEscalated marks an unhandled child-actor failure escalating to the parent // because no onError was wired for it (the escalate-to-parent default). The // event's ActorID/ActorSrc name the failed actor; the typed failure itself is // retrievable through the ActorSystem's LastEscalation. ActorEscalated ActorPhase = "escalated")type ActorRef
Section titled “type ActorRef”ActorRef is the runtime handle a machine stores in its context to address a spawned actor later (an actor ref). It is created by the ActorSystem when the actor is spawned and surfaced to the spawning machine through the system’s API, never through the IR — refs are runtime, not serializable definition. A ref carries the actor’s ID (and optional system-scoped SystemID) so the holder can Deliver events to it or read its snapshot through the system.
A ref is an OPAQUE, structured handle, not a raw index or positional slot: a holder must treat it as opaque and resolve it only through the ActorSystem API (Ref / RefBySystemID / Deliver / Stop), never by constructing one from a slice position or relying on its ID as an externally-meaningful integer. Construction stays the system’s job. This keeps the ref remote-ready: a future ref that denotes an actor in another system, process, or host carries additional locator data (a system name, a transport address) additively, without breaking any holder that already treats the ref opaquely. {ID, SystemID, Node} is the in-process projection of that fuller locator shape; Node is empty for a local actor and names the owning node for a remote one.
type ActorRef struct { // ID is the actor's registry key in the ActorSystem. ID string // SystemID is the optional system-scoped name the actor registered under // (its systemId); empty when the actor was spawned without one. SystemID string // Src is the actor ref name the actor was spawned from, for diagnostics. Src string // Node is the locator of the host that owns the actor: empty for an actor in // the holder's own in-process ActorSystem, and the owning node's identifier // for an actor on another host. The in-process ActorSystem leaves it empty; // a distributed host (crucible/cluster) stamps it when it mints a remote ref // and routes delivery by it. It is the additive locator the opaque-ref // contract reserves, so adding it breaks no holder that treats the ref // opaquely. Node string}type ActorSystem
Section titled “type ActorSystem”ActorSystem is the reusable host-driver that turns the kernel’s SpawnActor / StopActor effects into running child-machine actors, owns each actor’s mailbox, routes delivered events into mailboxes, steps actors via Fire, and re-fires the parent’s onDone / onError when a child completes or fails. It is concurrency-safe. Construct one per parent instance with NewActorSystem, then Register the child-machine behaviors that resolve SpawnActor Src refs; drive it by passing each Fire’s effects (and the parent’s StartEffects) to Absorb, and step actors with Deliver / Step.
In the deterministic form the system records each spawned actor and steps it only when the test calls Deliver / Step, so actor machines are exercised with no real concurrency; a production host instead runs each actor’s Step on its own goroutine fed by the mailbox.
type ActorSystem[S comparable, E comparable, C any] struct { // contains filtered or unexported fields}func NewActorSystem
Section titled “func NewActorSystem”func NewActorSystem[S comparable, E comparable, C any](parent *Instance[S, E, C]) *ActorSystem[S, E, C]NewActorSystem returns an ActorSystem driving parent: the instance whose SpawnActor / StopActor effects spawn and stop child actors, and through whose Fire a completed child’s onDone / onError is routed. Register child-machine behaviors with Register before absorbing spawn effects.
func (*ActorSystem[S, E, C]) Absorb
Section titled “func (*ActorSystem[S, E, C]) Absorb”func (s *ActorSystem[S, E, C]) Absorb(ctx context.Context, effects []Effect)Absorb scans effects, spawning an actor for each SpawnActor (resolving its Src against the palette and running the child machine) and stopping the actor for each StopActor (auto-stop-on-exit, recursively stopping the actor’s children). It is how a host wires Fire’s output back into the system; call it with the effects of every Fire (and once with the parent’s StartEffects for the initial state). A SpawnActor whose OnDone/OnError is not the parent’s event type still spawns the actor (a fire-and-forget child) but routes no completion event.
A SpawnActor whose Src does not resolve against the palette is settled immediately as an error: its OnError (when usable) is fired through the parent so the parent routes onError rather than hanging, mirroring the ServiceRunner’s unbound-service handling.
func (*ActorSystem[S, E, C]) AbsorbFor
Section titled “func (*ActorSystem[S, E, C]) AbsorbFor”func (s *ActorSystem[S, E, C]) AbsorbFor(ctx context.Context, event any, effects []Effect)AbsorbFor is Absorb for the effects of a host-driven parent Fire(event): it additionally lets a ForwardEvent the parent emits forward event verbatim to a child. Use it (rather than Absorb) when the parent itself runs forwardTo on a host-injected event; Absorb suffices for sendTo / sendParent / respond and all lifecycle effects.
func (*ActorSystem[S, E, C]) Deliver
Section titled “func (*ActorSystem[S, E, C]) Deliver”func (s *ActorSystem[S, E, C]) Deliver(ctx context.Context, ref ActorRef, event any) boolDeliver routes event into the mailbox of the actor identified by ref, then drains the actor (Step) so the delivered event is processed and any resulting completion is routed to the parent. It returns whether the actor was found running. It is the delivery mechanism the sendTo / sendParent / respond / forwardTo action sugar routes through; a host (or a test) may also call it directly to inject an event into an actor from outside.
func (*ActorSystem[S, E, C]) DeliverByID
Section titled “func (*ActorSystem[S, E, C]) DeliverByID”func (s *ActorSystem[S, E, C]) DeliverByID(ctx context.Context, id string, event any) boolDeliverByID is Deliver keyed by raw actor id, for a host that tracks ids rather than refs.
func (*ActorSystem[S, E, C]) IDs
Section titled “func (*ActorSystem[S, E, C]) IDs”func (s *ActorSystem[S, E, C]) IDs() []stringIDs returns the ids of all live actors, sorted, for deterministic host iteration (e.g. delivering to or stepping every actor in a stable order).
func (*ActorSystem[S, E, C]) IsRunning
Section titled “func (*ActorSystem[S, E, C]) IsRunning”func (s *ActorSystem[S, E, C]) IsRunning(id string) boolIsRunning reports whether an actor with the given id is live.
func (*ActorSystem[S, E, C]) LastError
Section titled “func (*ActorSystem[S, E, C]) LastError”func (s *ActorSystem[S, E, C]) LastError() errorLastError returns the error the most recently settled actor produced, or nil when the last settlement was a success or none has occurred.
func (*ActorSystem[S, E, C]) LastEscalation
Section titled “func (*ActorSystem[S, E, C]) LastEscalation”func (s *ActorSystem[S, E, C]) LastEscalation() *ActorEscalationLastEscalation returns the most recent escalation the system recorded, or nil when no child failure has escalated. It is the always-on observable record of the escalate-to-parent default: even with no inspector and no handler wired, an unhandled child failure is retrievable here rather than silently lost.
It is LAST-WRITTEN-WINS, including across a single escalation that climbs the supervision chain: a child -> parent -> grandparent climb rewrites this field at each level, so after the climb it holds the topmost level reached, not the originating failure. Wire an inspector (or an EscalationHandler) when you need the FULL record — the inspector stream observes every level of every escalation in order; LastEscalation is the convenience snapshot of the most recent one.
func (*ActorSystem[S, E, C]) LastOutput
Section titled “func (*ActorSystem[S, E, C]) LastOutput”func (s *ActorSystem[S, E, C]) LastOutput() (any, bool)LastOutput returns the output the most recently settled actor produced, and true when that settlement was a success. The parent action bound to an actor’s onDone transition reads it to consume the child’s output; it is valid only during the synchronous parent Fire the settlement triggers. It returns false after a failure or before any settlement.
func (*ActorSystem[S, E, C]) Ref
Section titled “func (*ActorSystem[S, E, C]) Ref”func (s *ActorSystem[S, E, C]) Ref(id string) (ActorRef, bool)Ref returns the ActorRef for the running actor under id, and whether such an actor is running. The spawning machine stores the ref in its context (the host’s spawn action reads it from the system after Absorb) to address the actor later.
func (*ActorSystem[S, E, C]) RefBySystemID
Section titled “func (*ActorSystem[S, E, C]) RefBySystemID”func (s *ActorSystem[S, E, C]) RefBySystemID(systemID string) (ActorRef, bool)RefBySystemID returns the ActorRef for the actor registered under the given its systemId, and whether one is running. It lets a sibling address an actor by its well-known system name rather than by spawn id.
func (*ActorSystem[S, E, C]) Register
Section titled “func (*ActorSystem[S, E, C]) Register”func (s *ActorSystem[S, E, C]) Register(src string, behavior ActorBehavior) *ActorSystem[S, E, C]Register binds a child-machine behavior under src in the system’s actor palette, so a SpawnActor whose Src.Name is src resolves to behavior. It is the actor-model analog of Registry.Service: a host registers each child machine it can spawn. Registering returns the system for chaining.
func (*ActorSystem[S, E, C]) RestoreActors
Section titled “func (*ActorSystem[S, E, C]) RestoreActors”func (s *ActorSystem[S, E, C]) RestoreActors(ctx context.Context, actors map[string]json.RawMessage) errorRestoreActors re-establishes the system’s child actors from the snapshots SnapshotActors produced, recursively: each actor is re-spawned from the system’s palette under its original id, its captured state reloaded (resuming it in place without re-running entry actions) when it was resumable, and its nested children restored beneath it. A not-yet-done actor whose Src does not resolve against the palette is skipped (the host registered a different palette); a done actor is not re-spawned. Register the same child-machine behaviors before calling it, exactly as for the original Absorb.
An actor recorded as not resumable (its ActorInstance did not implement Snapshotter) is re-spawned fresh rather than resumed — the one deferred actor-tree depth, flagged on the snapshot’s Resumed field.
func (*ActorSystem[S, E, C]) Running
Section titled “func (*ActorSystem[S, E, C]) Running”func (s *ActorSystem[S, E, C]) Running() intRunning reports the number of live (spawned, not-stopped, not-completed) actors. A test asserts on it to confirm an actor spawned or was auto-stopped on exit.
func (*ActorSystem[S, E, C]) SettleError
Section titled “func (*ActorSystem[S, E, C]) SettleError”func (s *ActorSystem[S, E, C]) SettleError(ctx context.Context, id string, err error) (FireResult[S], bool)SettleError fails the running actor under id explicitly (e.g. a host-detected child crash), routing the parent’s onError. It returns the parent FireResult and true, or false when id is not running or routes no onError. When no onError was wired, the failure escalates to the parent as a typed ActorEscalation rather than being swallowed (the G3 default), so the returned false still means “no onError event fired” — not “the failure was lost”.
func (*ActorSystem[S, E, C]) SnapshotActors
Section titled “func (*ActorSystem[S, E, C]) SnapshotActors”func (s *ActorSystem[S, E, C]) SnapshotActors() (map[string]json.RawMessage, error)SnapshotActors captures the runtime state of every live child actor the system runs, recursively (each actor’s own spawned children are captured beneath it), as a JSON document keyed by actor id. It is the actor-tree companion to Instance.Snapshot: a host that persists a parent instance also calls SnapshotActors to persist the parent’s spawned children, and stores the result under the parent snapshot’s Actors map. It is a pure read of the system’s actor registry and never fires or mutates an actor.
Call it at a quiescent point (after draining mailboxes with Step), so no in-flight mailbox backlog is lost. An actor whose ActorInstance does not implement Snapshotter is recorded as present but not resumable (Resumed false) and is re-spawned fresh on RestoreActors.
func (*ActorSystem[S, E, C]) Step
Section titled “func (*ActorSystem[S, E, C]) Step”func (s *ActorSystem[S, E, C]) Step(ctx context.Context, id string) []FireResult[S]Step drains the mailbox of the actor under id, firing each queued event through the actor in order. When the actor reaches its final state it is settled: the parent’s onDone event (carrying the child’s output) is fired through the parent and the resulting effects absorbed; nested-child effects the actor emits are absorbed too. It returns the parent FireResults produced by completion routing, in order (empty when the actor did not complete). Step is safe to call with an empty mailbox (a no-op) and is how the deterministic driver advances an actor; a production driver runs it from the actor’s own goroutine.
func (*ActorSystem[S, E, C]) Stop
Section titled “func (*ActorSystem[S, E, C]) Stop”func (s *ActorSystem[S, E, C]) Stop(ref ActorRef)Stop stops the actor identified by ref (and its children), so a machine that holds an ActorRef can explicitly tear an actor down. Stopping an unknown actor is a no-op.
func (*ActorSystem[S, E, C]) WithActorInspector
Section titled “func (*ActorSystem[S, E, C]) WithActorInspector”func (s *ActorSystem[S, E, C]) WithActorInspector(insp Inspector) *ActorSystem[S, E, C]WithActorInspector wires a live observer sink fed the ActorSystem’s actor-lifecycle and inter-actor message inspection events — actor spawned / stopped, and message sent / delivered (the actor-to-actor flavor of an event). Pass the same Inspector also wired to the parent instance (WithInspector) to observe the whole system on one sink. It is off by default; an un-inspected system pays nothing.
func (*ActorSystem[S, E, C]) WithEscalationHandler
Section titled “func (*ActorSystem[S, E, C]) WithEscalationHandler”func (s *ActorSystem[S, E, C]) WithEscalationHandler(handler EscalationHandler) *ActorSystem[S, E, C]WithEscalationHandler registers handler as the system’s escalation handler, invoked for each child-actor failure that escalates because no onError was wired. It is off by default — the default escalation behavior (record on the system plus an InspectActor event when an inspector is present) needs no handler — so an unwired system still never swallows a failure. Registering returns the system for chaining.
type AssayError
Section titled “type AssayError”AssayError aggregates one or more failing requirements found by Assay.
type AssayError struct { Failures []RequirementFailure}func (*AssayError) Error
Section titled “func (*AssayError) Error”func (e *AssayError) Error() stringtype AssayOption
Section titled “type AssayOption”AssayOption configures Assay.
type AssayOption func(*assayConfig)func Aggregate
Section titled “func Aggregate”func Aggregate() AssayOptionAggregate makes Assay collect all failing requirements in one pass instead of failing fast at the first. It is a pure directive option (it carries no value), so it drops the With prefix that value-carrying options keep — matching Strict and CollectAll.
type AssignBinding
Section titled “type AssignBinding”AssignBinding turns an assign request into the next context value. The in-process binding wraps an AssignFn, reading the prior context off the in-process context projection; a future out-of-process binding marshals the request across its transport. EvalAssign is synchronous so the fold stays callable inside the pure commit step.
type AssignBinding[C any] interface { EvalAssign(ctx context.Context, req AssignRequest[C]) (AssignResult[C], error)}type AssignCtx
Section titled “type AssignCtx”AssignCtx is passed to a bound assign reducer at run time. Entity is the prior context by value (the reducer’s input); the reducer returns the next context. Event is the triggering event payload — the runtime event for an ordinary transition, or the service/actor result for a service/actor onDone transition. Params is the assign ref’s static configuration.
type AssignCtx[C any] struct { Entity C Event any Params map[string]any}type AssignFn
Section titled “type AssignFn”AssignFn is the sole context writer: a total pure reducer producing the next context from the prior context (by value), the triggering event, and the ref’s static params. It emits no effect and returns no error; it observes context read-only through the copy it receives and yields the new value as its return.
type AssignFn[C any] func(in AssignCtx[C]) Ctype AssignRequest
Section titled “type AssignRequest”AssignRequest is the serializable invocation envelope for an assign: the named ref, its params, the triggering event, and the read-only context projection the reducer folds.
type AssignRequest[C any] struct { Name string Params map[string]any Event any Context ContextView}type AssignResult
Section titled “type AssignResult”AssignResult is the assign’s serializable result: the new context value. It is the write-side mirror of the read-only ContextView and carries the full folded context (delta encoding is a later additive optimization on this envelope).
type AssignResult[C any] struct { Context C}type BatchResult
Section titled “type BatchResult”BatchResult is the result of a batch fire (FireSeq / FireEach).
type BatchResult[S comparable] struct { Steps []FireResult[S] Trace Trace Err error}type BindingSpec
Section titled “type BindingSpec”BindingSpec describes how a named behavior is backed. Transport names the invocation transport, defaulting to in-process when empty. Meta is a reserved per-binding extension namespace (e.g. a sandbox fuel budget, an endpoint).
Transport follows the closed-enum extension policy: a transport this build does not recognize is preserved verbatim on round-trip (so a newer producer’s binding survives an older client) and would be rejected only at dispatch — and no non-in-process dispatch path exists at v1. Unknown top-level keys are likewise preserved through extra.
type BindingSpec struct { Transport string `json:"transport,omitempty"` Meta map[string]any `json:"meta,omitempty"` // contains filtered or unexported fields}func (BindingSpec) MarshalJSON
Section titled “func (BindingSpec) MarshalJSON”func (s BindingSpec) MarshalJSON() ([]byte, error)MarshalJSON encodes a BindingSpec, merging its preserved unknown keys back in with stable key ordering.
func (*BindingSpec) UnmarshalJSON
Section titled “func (*BindingSpec) UnmarshalJSON”func (s *BindingSpec) UnmarshalJSON(data []byte) errorUnmarshalJSON decodes a BindingSpec and captures any unknown keys into extra so they survive re-serialization.
type Builder
Section titled “type Builder”Builder is the Forge DSL front-end. It builds the IR and registers implementations by name.
type Builder[S comparable, E comparable, C any] struct { // contains filtered or unexported fields}func Forge
Section titled “func Forge”func Forge[S comparable, E comparable, C any](name string, opts ...ForgeOption) *Builder[S, E, C]Forge opens a builder.
Example
ExampleForge builds a document-approval machine with the Forge DSL and fires a single event, showing the resulting state and the effect the transition emitted.
m := buildDocMachine()doc := &Document{Status: Draft}res := m.Cast(doc).Fire(context.Background(), Submit)
fmt.Println("state:", res.NewState)fmt.Println("effects:", res.Effects)// Output:// state: Submitted// effects: [{submitted}]Output
Section titled “Output”state: Submittedeffects: [{submitted}]func (*Builder[S, E, C]) Action
Section titled “func (*Builder[S, E, C]) Action”func (b *Builder[S, E, C]) Action(name string, fn ActionFn[C], opts ...DescribeOption) *Builder[S, E, C]Action registers a named action into the builder’s palette. An optional Describe option attaches palette metadata, mirroring Registry.Action.
func (*Builder[S, E, C]) Actor
Section titled “func (*Builder[S, E, C]) Actor”func (b *Builder[S, E, C]) Actor(name string, opts ...DescribeOption) *Builder[S, E, C]Actor declares a named actor behavior in the builder’s palette for discovery. Like Registry.Actor it records palette metadata only — the runnable behavior binds at the host ActorSystem — so it never affects Quench binding or lint.
func (*Builder[S, E, C]) After
Section titled “func (*Builder[S, E, C]) After”func (b *Builder[S, E, C]) After(delay time.Duration) *Builder[S, E, C]After opens a delayed (“after”) transition from the most-recent state: a transition that the host’s runtime fires once `delay` elapses while the source state stays active. Chain On(event).GoTo(target) to name the delayed event the host re-fires and the target it lands in (When/Do as usual). On entering the source state the kernel emits a ScheduleAfter effect; on exiting it before the delay elapses, a CancelScheduled effect (auto-cancel-on-exit). The kernel never sleeps — the host owns the timer and feeds the delayed event back through Fire. This is the DSL form of a delayed (after) transition.
func (*Builder[S, E, C]) Always
Section titled “func (*Builder[S, E, C]) Always”func (b *Builder[S, E, C]) Always() *Builder[S, E, C]Always opens an eventless (“always”) transition from the most-recent state. It carries no triggering event and is auto-fired by the run-to-completion loop whenever its guards pass and the state is active, within the firing macrostep. Chain GoTo/When/Do as usual. This is the DSL form of an eventless transition.
func (*Builder[S, E, C]) Assign
Section titled “func (*Builder[S, E, C]) Assign”func (b *Builder[S, E, C]) Assign(assignName string, params ...map[string]any) *Builder[S, E, C]Assign attaches a named context-reducer ref with params to the most-recent transition. The reducer folds onto the instance’s context when the transition fires — the sole context-mutation site under the value-semantics contract. It is distinct from Do: Do emits an effect, Assign computes the next context. The referenced reducer is registered separately by Builder.Reducer (alias of Registry.Assign); this WIRES a registered reducer by name onto the transition.
Example
ExampleBuilder_Assign demonstrates the assign reducer — the sole context writer. Under value-semantics context, a guard or action receives a copy of the context and cannot change the instance; only an Assign, a pure reducer returning the next context, updates it. The reducer reads the triggering event from AssignCtx.Event and its static configuration from AssignCtx.Params.
package main
import ( "context" "fmt"
"github.com/stablekernel/crucible/state")
// basket is a value-semantics context: an Assign returns a new basket, and guards and// actions receive a copy they cannot use to mutate the instance.type basket struct { Total int}
// ExampleBuilder_Assign demonstrates the assign reducer — the sole context writer.// Under value-semantics context, a guard or action receives a copy of the context// and cannot change the instance; only an Assign, a pure reducer returning the next// context, updates it. The reducer reads the triggering event from AssignCtx.Event// and its static configuration from AssignCtx.Params.func main() { m := state.Forge[string, string, basket]("checkout"). Reducer("addItem", func(in state.AssignCtx[basket]) basket { c := in.Entity if price, ok := in.Params["price"].(int); ok { c.Total += price } return c }). State("shopping"). State("paid"). Initial("shopping"). Transition("shopping").On("add").GoTo("shopping"). Assign("addItem", map[string]any{"price": 300}). Transition("shopping").On("checkout").GoTo("paid"). Quench()
inst := m.Cast(basket{}, state.WithInitialState[string]("shopping")) inst.Fire(context.Background(), "add") inst.Fire(context.Background(), "add")
fmt.Println(inst.Entity().Total)}Output
Section titled “Output”600func (*Builder[S, E, C]) Cancel
Section titled “func (*Builder[S, E, C]) Cancel”func (b *Builder[S, E, C]) Cancel(id string) *Builder[S, E, C]Cancel attaches the kernel Cancel built-in to the most-recent transition: when the transition fires, the kernel emits a CancelScheduled effect for the given schedule id, so a machine can explicitly cancel a pending delayed (`after`) event before its delay elapses. The id is the ScheduleAfter ID the host received; ScheduleID derives it for a known source state and delayed-edge index. Canceling an unknown id is a host-side no-op. The built-in needs no host registration, mirroring the stateIn guard built-in.
func (*Builder[S, E, C]) CurrentStateFn
Section titled “func (*Builder[S, E, C]) CurrentStateFn”func (b *Builder[S, E, C]) CurrentStateFn(fn func(C) S) *Builder[S, E, C]CurrentStateFn declares how to derive an instance’s current state.
func (*Builder[S, E, C]) DefaultTo
Section titled “func (*Builder[S, E, C]) DefaultTo”func (b *Builder[S, E, C]) DefaultTo(target S) *Builder[S, E, C]DefaultTo sets the fallback target of the most-recent history pseudo-state, entered when its owning compound has no recorded history yet. It is a no-op (recorded as a lint at Quench) when the most-recent state is not a history pseudo-state.
func (*Builder[S, E, C]) Do
Section titled “func (*Builder[S, E, C]) Do”func (b *Builder[S, E, C]) Do(actionName string, params ...map[string]any) *Builder[S, E, C]Do attaches a named action ref with params to the most-recent transition.
func (*Builder[S, E, C]) EndRegion
Section titled “func (*Builder[S, E, C]) EndRegion”func (b *Builder[S, E, C]) EndRegion() *Builder[S, E, C]EndRegion closes the most-recent Region block.
func (*Builder[S, E, C]) EndSuperState
Section titled “func (*Builder[S, E, C]) EndSuperState”func (b *Builder[S, E, C]) EndSuperState() *Builder[S, E, C]EndSuperState closes the most-recent SuperState block.
func (*Builder[S, E, C]) Final
Section titled “func (*Builder[S, E, C]) Final”func (b *Builder[S, E, C]) Final() *Builder[S, E, C]Final marks the most-recent state as terminal.
func (*Builder[S, E, C]) Forbid
Section titled “func (*Builder[S, E, C]) Forbid”func (b *Builder[S, E, C]) Forbid(event E) *Builder[S, E, C]Forbid declares that the most-recent state blocks the given event: the event is consumed and ignored there and does NOT bubble to ancestors, distinct from having no handler (which bubbles). This is the DSL form of a `on: { E: undefined }`. A forbidden transition takes no target, guards, or effects.
func (*Builder[S, E, C]) ForbidAny
Section titled “func (*Builder[S, E, C]) ForbidAny”func (b *Builder[S, E, C]) ForbidAny() *Builder[S, E, C]ForbidAny declares a forbidden wildcard: every event not otherwise handled is consumed and ignored at the most-recent state instead of bubbling. This is the DSL form of a forbidden wildcard transition.
func (*Builder[S, E, C]) ForwardTo
Section titled “func (*Builder[S, E, C]) ForwardTo”func (b *Builder[S, E, C]) ForwardTo(targetID string, opts ...SendOption) *Builder[S, E, C]ForwardTo attaches the kernel forwardTo built-in to the most-recent transition: when the transition fires, the kernel emits a ForwardEvent effect so the host’s ActorSystem forwards the event the emitting actor is currently handling, verbatim, to the actor registered under targetID. Address an actor by its system-scoped id instead with WithSendToSystemID. The built-in needs no host registration. This is the DSL form of forwarding the current event to another actor.
func (*Builder[S, E, C]) GoTo
Section titled “func (*Builder[S, E, C]) GoTo”func (b *Builder[S, E, C]) GoTo(to S) *Builder[S, E, C]GoTo sets the target of the most-recent transition.
func (*Builder[S, E, C]) Guard
Section titled “func (*Builder[S, E, C]) Guard”func (b *Builder[S, E, C]) Guard(name string, fn GuardFn[C], opts ...DescribeOption) *Builder[S, E, C]Guard registers a named guard into the builder’s palette. An optional Describe option attaches palette metadata, mirroring Registry.Guard.
func (*Builder[S, E, C]) History
Section titled “func (*Builder[S, E, C]) History”func (b *Builder[S, E, C]) History(name S, kind HistoryType) *Builder[S, E, C]History declares a history pseudo-state inside the current SuperState block. The pseudo-state remembers the owning compound’s last active configuration: HistoryShallow restores the compound’s last active direct child, HistoryDeep restores its full nested leaf configuration. Transition to it (by name) to re-enter the remembered configuration instead of the compound’s Initial. Use DefaultTo to declare the target entered when no history has been recorded yet; without it the resolver falls back to the compound’s Initial.
A history pseudo-state is structure, not a leaf: it never appears in the active configuration and is not eligible as a compound’s Initial. Declaring one outside a SuperState block is a Quench lint.
func (*Builder[S, E, C]) Initial
Section titled “func (*Builder[S, E, C]) Initial”func (b *Builder[S, E, C]) Initial(name S) *Builder[S, E, C]Initial sets the entry state. At the top level it sets the machine’s initial state; inside a SuperState or Region block it sets that block’s initial child.
func (*Builder[S, E, C]) Invoke
Section titled “func (*Builder[S, E, C]) Invoke”func (b *Builder[S, E, C]) Invoke(src string, onDone, onError E, opts ...InvokeOption) *Builder[S, E, C]Invoke declares an invoked service on the most-recent state (an `invoke`). src names the service in the registry (bind it with Service); onDone and onError name the events the host re-fires through Fire when the service completes or fails, routed by ordinary transitions from this state. Configure the input passed to the service and an explicit id with the variadic InvokeOptions (WithInput, WithInvokeID); omitting WithInvokeID derives a stable id via InvokeID. On entering this state the kernel emits a StartService effect; on exiting it before the service completes, a StopService effect (auto-stop-on-exit). The kernel never runs the service — a host ServiceRunner does, keeping Fire pure.
func (*Builder[S, E, C]) InvokeActor
Section titled “func (*Builder[S, E, C]) InvokeActor”func (b *Builder[S, E, C]) InvokeActor(src string, onDone, onError E, opts ...InvokeOption) *Builder[S, E, C]InvokeActor declares a child-MACHINE actor invoked while the most-recent state is active (invoke of a child machine). src names the child-machine factory registered in the host’s ActorSystem actor palette; onDone and onError name the events the host re-fires through the PARENT’s Fire when the child reaches its final state (carrying its output) or fails (carrying the error), routed by ordinary transitions from this state. Configure the input passed to the child, an explicit id, and a system-scoped id with WithInput / WithInvokeID / WithSystemID. On entering this state the kernel emits a SpawnActor effect; on exiting it before the child completes, a StopActor effect (auto-stop-on-exit). The kernel never runs the actor — a host ActorSystem does, keeping Fire pure. Unlike Invoke (a host-run service), the src here is bound at the ActorSystem, not the registry, so it is not subject to the registry’s unbound-ref lint.
func (*Builder[S, E, C]) On
Section titled “func (*Builder[S, E, C]) On”func (b *Builder[S, E, C]) On(event E) *Builder[S, E, C]On sets the triggering event of the most-recent transition. When no transition is currently open — or the open one already has its event set (a completed `.On(…).GoTo(…)` clause) — On opens a fresh transition from the most-recent state. This lets the hierarchical DSL read `.SubState(X).On(e1).GoTo(Y).On(e2).GoTo(Z)` and `.SubState(X).On(e).GoTo(Y)` without an explicit Transition call.
func (*Builder[S, E, C]) OnAny
Section titled “func (*Builder[S, E, C]) OnAny”func (b *Builder[S, E, C]) OnAny() *Builder[S, E, C]OnAny opens a wildcard (catch-all) transition from the most-recent state. It matches any event no specific On-keyed transition of the state handles, and is the lowest-priority candidate — tried only after every specific match fails, before the event bubbles to an ancestor. Chain GoTo/When/Do/Reenter/Raise as usual. This is the DSL form of a wildcard transition.
func (*Builder[S, E, C]) OnDone
Section titled “func (*Builder[S, E, C]) OnDone”func (b *Builder[S, E, C]) OnDone(actionName string, params ...map[string]any) *Builder[S, E, C]OnDone attaches a named done-action ref to the most-recent state. It runs when the state completes — a compound state when its active leaf is final, a parallel state when every region is final.
func (*Builder[S, E, C]) OnEntry
Section titled “func (*Builder[S, E, C]) OnEntry”func (b *Builder[S, E, C]) OnEntry(actionName string, params ...map[string]any) *Builder[S, E, C]OnEntry attaches a named entry-action ref to the most-recent state.
func (*Builder[S, E, C]) OnEntryAssign
Section titled “func (*Builder[S, E, C]) OnEntryAssign”func (b *Builder[S, E, C]) OnEntryAssign(assignName string, params ...map[string]any) *Builder[S, E, C]OnEntryAssign attaches a named context-reducer ref to the most-recent state’s entry phase. It folds onto the instance’s context when the state is entered, after the transition’s assigns — the assign sibling of OnEntry.
func (*Builder[S, E, C]) OnExit
Section titled “func (*Builder[S, E, C]) OnExit”func (b *Builder[S, E, C]) OnExit(actionName string, params ...map[string]any) *Builder[S, E, C]OnExit attaches a named exit-action ref to the most-recent state.
func (*Builder[S, E, C]) OnExitAssign
Section titled “func (*Builder[S, E, C]) OnExitAssign”func (b *Builder[S, E, C]) OnExitAssign(assignName string, params ...map[string]any) *Builder[S, E, C]OnExitAssign attaches a named context-reducer ref to the most-recent state’s exit phase. It folds onto the instance’s context when the state is exited, before the transition’s assigns — the assign sibling of OnExit.
func (*Builder[S, E, C]) OwnedBy
Section titled “func (*Builder[S, E, C]) OwnedBy”func (b *Builder[S, E, C]) OwnedBy(owner string) *Builder[S, E, C]OwnedBy tags the most-recent state’s ownership.
func (*Builder[S, E, C]) Palette
Section titled “func (*Builder[S, E, C]) Palette”func (b *Builder[S, E, C]) Palette() []DescriptorPalette returns the registry’s discoverable descriptor set — every registered guard, action, service, and declared actor behavior — sorted deterministically. It is the Builder-side convenience for Registry.Palette, surfacing the palette of a DSL-authored machine before Quench.
func (*Builder[S, E, C]) Quench
Section titled “func (*Builder[S, E, C]) Quench”func (b *Builder[S, E, C]) Quench(opts ...QuenchOption) *Machine[S, E, C]Quench binds refs, lints, and freezes into an immutable Machine. It panics on any misconfiguration (programmer error) with a file:line pointer.
func (*Builder[S, E, C]) Raise
Section titled “func (*Builder[S, E, C]) Raise”func (b *Builder[S, E, C]) Raise(events ...E) *Builder[S, E, C]Raise attaches internal events to the most-recent transition. After the transition’s effects run, each raised event is processed within the same Fire macrostep by the run-to-completion loop, before Fire returns. This is the DSL form of raising an internal event.
func (*Builder[S, E, C]) Reducer
Section titled “func (*Builder[S, E, C]) Reducer”func (b *Builder[S, E, C]) Reducer(name string, fn AssignFn[C], opts ...DescribeOption) *Builder[S, E, C]Reducer registers a named assign reducer into the builder’s palette — the sole context writer, wired onto a transition with the Assign DSL verb or onto a state with OnEntryAssign / OnExitAssign. It is the builder-side registration of an assign (the Do verb wires an Action that Action registers; the Assign verb wires a reducer that Reducer registers), forwarding to Registry.Assign. An optional Describe option attaches palette metadata.
func (*Builder[S, E, C]) Reenter
Section titled “func (*Builder[S, E, C]) Reenter”func (b *Builder[S, E, C]) Reenter() *Builder[S, E, C]Reenter marks the most-recent transition external: a self- or ancestor- targeted transition that would otherwise be internal (the v5 default) instead runs the full exit/entry cascade of its target. This is the DSL form of the v5 `reenter: true`.
func (*Builder[S, E, C]) Region
Section titled “func (*Builder[S, E, C]) Region”func (b *Builder[S, E, C]) Region(name string) *Builder[S, E, C]Region opens an orthogonal region inside the current SuperState block. States declared until the matching EndRegion belong to the region, and Initial names the region’s initial state.
func (*Builder[S, E, C]) Requires
Section titled “func (*Builder[S, E, C]) Requires”func (b *Builder[S, E, C]) Requires(req Requirement[C]) *Builder[S, E, C]Requires attaches a requirement to the most-recent state.
func (*Builder[S, E, C]) Respond
Section titled “func (*Builder[S, E, C]) Respond”func (b *Builder[S, E, C]) Respond(event E) *Builder[S, E, C]Respond attaches the kernel respond built-in to the most-recent transition: when the transition fires, the kernel emits a RespondToSender effect so the host’s ActorSystem delivers event back to the sender of the event currently being handled (the actor that sent it via SendTo / ForwardTo). When the current event has no identifiable sender it is a host-side no-op. The built-in needs no host registration. This is the DSL form of replying to an event’s origin (the `respond` / `sendBack`).
func (*Builder[S, E, C]) SendParent
Section titled “func (*Builder[S, E, C]) SendParent”func (b *Builder[S, E, C]) SendParent(event E) *Builder[S, E, C]SendParent attaches the kernel sendParent built-in to the most-recent transition: when the transition fires, the kernel emits a SendParent effect so the host’s ActorSystem delivers event to the emitting actor’s parent. Emitted by a top-level machine with no parent it is a host-side no-op. The built-in needs no host registration. This is the DSL form of sending an event to the parent.
func (*Builder[S, E, C]) SendTo
Section titled “func (*Builder[S, E, C]) SendTo”func (b *Builder[S, E, C]) SendTo(targetID string, event E, opts ...SendOption) *Builder[S, E, C]SendTo attaches the kernel sendTo built-in to the most-recent transition: when the transition fires, the kernel emits a SendTo effect so the host’s ActorSystem delivers event to the actor registered under targetID. Address an actor by its system-scoped id instead with WithSendToSystemID. The built-in needs no host registration, mirroring Spawn / Cancel. This is the DSL form of `sendTo(target, event)`.
func (*Builder[S, E, C]) Service
Section titled “func (*Builder[S, E, C]) Service”func (b *Builder[S, E, C]) Service(name string, fn ServiceFn[C], opts ...DescribeOption) *Builder[S, E, C]Service registers a named invoked-service implementation into the builder’s palette, bound by an invoke’s Src ref. An unbound service ref fails Quench with the typed *ErrUnboundRef, mirroring guards and actions. An optional Describe option attaches palette metadata.
func (*Builder[S, E, C]) Spawn
Section titled “func (*Builder[S, E, C]) Spawn”func (b *Builder[S, E, C]) Spawn(src, id string, opts ...SpawnOption) *Builder[S, E, C]Spawn attaches the kernel spawn built-in to the most-recent transition: when the transition fires, the kernel emits a SpawnActor effect so a machine creates an actor dynamically (spawn). src names the child-machine factory in the host’s ActorSystem actor palette; id is the actor’s registry key (the holder later stores the ActorSystem-returned ActorRef in its context to address it). Configure input and a system-scoped id with the SpawnOptions. The built-in needs no host registration, mirroring Cancel. The ActorSystem creates and runs the actor; routing the spawned actor’s done/error is configured with WithSpawnOnDone / WithSpawnOnError.
func (*Builder[S, E, C]) State
Section titled “func (*Builder[S, E, C]) State”func (b *Builder[S, E, C]) State(name S) *Builder[S, E, C]State declares a state node. Inside a SuperState or Region block it declares a substate of that block (equivalent to SubState); at the top level it declares a top-level state.
func (*Builder[S, E, C]) StopActor
Section titled “func (*Builder[S, E, C]) StopActor”func (b *Builder[S, E, C]) StopActor(id string) *Builder[S, E, C]StopActor attaches the kernel stop-actor built-in to the most-recent transition: when the transition fires, the kernel emits a StopActor effect for the given actor id, so a machine can explicitly stop a spawned actor before its natural completion (stopping an actor). Stopping an unknown id is a host-side no-op. The built-in needs no host registration, mirroring Cancel.
func (*Builder[S, E, C]) StopChild
Section titled “func (*Builder[S, E, C]) StopChild”func (b *Builder[S, E, C]) StopChild(id string) *Builder[S, E, C]StopChild attaches the kernel stopChild built-in to the most-recent transition: when the transition fires, the kernel emits a StopActor effect for the given actor id, so a machine can explicitly stop a spawned child actor (the `stopChild`). It is the action-level twin of StopActor and shares its effect; stopping an unknown id is a host-side no-op. The built-in needs no host registration.
func (*Builder[S, E, C]) SubState
Section titled “func (*Builder[S, E, C]) SubState”func (b *Builder[S, E, C]) SubState(name S) *Builder[S, E, C]SubState declares a substate of the current SuperState or Region block.
func (*Builder[S, E, C]) SuperState
Section titled “func (*Builder[S, E, C]) SuperState”func (b *Builder[S, E, C]) SuperState(name S) *Builder[S, E, C]SuperState declares a compound (hierarchical) state and opens its block. The substates declared until the matching EndSuperState become its children, and Initial inside the block names the child entered when the superstate is entered.
func (*Builder[S, E, C]) Temper
Section titled “func (*Builder[S, E, C]) Temper”func (b *Builder[S, E, C]) Temper(opts ...TemperOption) []DiagnosticTemper runs a non-failing diagnostics pass over the builder’s current definition, returning the same findings Quench would panic on — as data.
func (*Builder[S, E, C]) Transition
Section titled “func (*Builder[S, E, C]) Transition”func (b *Builder[S, E, C]) Transition(from S) *Builder[S, E, C]Transition opens a new edge from the given state.
func (*Builder[S, E, C]) Use
Section titled “func (*Builder[S, E, C]) Use”func (b *Builder[S, E, C]) Use(mw ...Middleware[S, E, C]) *Builder[S, E, C]Use installs middleware that wraps every Fire.
func (*Builder[S, E, C]) WaitMode
Section titled “func (*Builder[S, E, C]) WaitMode”func (b *Builder[S, E, C]) WaitMode(m WaitMode) *Builder[S, E, C]WaitMode tags the most-recent transition’s synchronization mode.
func (*Builder[S, E, C]) When
Section titled “func (*Builder[S, E, C]) When”func (b *Builder[S, E, C]) When(guardName string, params ...map[string]any) *Builder[S, E, C]When attaches a named guard ref with params to the most-recent transition.
func (*Builder[S, E, C]) WhenExpr
Section titled “func (*Builder[S, E, C]) WhenExpr”func (b *Builder[S, E, C]) WhenExpr(expr GuardNode[S]) *Builder[S, E, C]WhenExpr attaches a composite guard expression to the most-recent transition: a boolean tree over named-ref leaves (Guard), the stateIn built-in (StateIn), and the And/Or/Not combinators, with short-circuit semantics. It is evaluated alongside any When guards — the transition is enabled only when both pass. Use When for the common single-guard case and WhenExpr when a transition needs composition or stateIn.
func (*Builder[S, E, C]) WithContextSchema
Section titled “func (*Builder[S, E, C]) WithContextSchema”func (b *Builder[S, E, C]) WithContextSchema(schema ContextSchema) *Builder[S, E, C]WithContextSchema attaches a serializable description of the machine’s context data model to the IR envelope (the IR.Context slot), so a rehydrated machine re-emits it on ToJSON and an expression layer or studio can read the context’s shape. Pair it with SchemaOf to derive the schema from the Go context type:
state.Forge[S, E, *Order]("checkout"). WithContextSchema(state.SchemaOf[*Order]())It is opt-in and additive: deriving is never automatic at Forge, and a machine with no schema is valid and simply limits later type-checking. The schema is metadata only — the kernel never inspects it and Fire never reads it.
type CancelScheduled
Section titled “type CancelScheduled”CancelScheduled is the effect the kernel emits when an instance exits a state that had a pending delayed (`after`) timer, or when a Cancel action runs. The host cancels the timer registered under ID; canceling an unknown ID is a no-op. A state’s `after` timers are auto-canceled when the state is exited before the delay elapses.
type CancelScheduled struct { // ID identifies the timer to cancel. It matches the ID of the ScheduleAfter // that armed it (auto-cancel-on-exit), or an ID supplied to Cancel. ID string `json:"id"`}func (CancelScheduled) Kind
Section titled “func (CancelScheduled) Kind”func (CancelScheduled) Kind() stringKind reports the cancel-scheduled effect discriminant.
type CastOption
Section titled “type CastOption”CastOption configures Cast.
type CastOption[S comparable] func(*castConfig[S])func WithClock
Section titled “func WithClock”func WithClock[S comparable](c Clock) CastOption[S]WithClock injects the time seam an instance’s delayed-transition driver uses. It is consumed only by a Scheduler / host driver wired to the instance — never by the pure Fire step, which neither reads a clock nor sleeps. Supply SystemClock() in production or a fake clock in a test to drive `after` transitions deterministically. When omitted, an instance defaults to SystemClock().
func WithInitialState
Section titled “func WithInitialState”func WithInitialState[S comparable](s S) CastOption[S]WithInitialState supplies the instance’s starting state explicitly. Use it when the machine declares no CurrentStateFn (i.e. the current state cannot be derived from the entity). When both are present, the explicit initial state takes precedence over CurrentStateFn.
func WithInspector
Section titled “func WithInspector”func WithInspector[S comparable](insp Inspector) CastOption[S]WithInspector registers a live observer sink fed inspection events as the instance advances — event received, transition taken, snapshot update — mirroring the live inspection stream. It is off by default: with no inspector the instance never calls one, so inspection adds zero overhead and the pure Fire step performs no IO. The same inspector can be wired to an ActorSystem (WithActorInspector) so actor lifecycle and inter-actor messages are observed on the same sink. The inspector is notified synchronously and must not block or mutate the instance.
func WithLogger
Section titled “func WithLogger”func WithLogger[S comparable](l *slog.Logger) CastOption[S]WithLogger wires a structured-logging seam an instance writes a terse, fixed-shape record to as each Fire settles — distinct from the event-shaped Inspector. Where an Inspector receives the full, typed InspectionEvent stream for live observation and tooling, the logger is the conventional *slog.Logger a host already threads through its services, so a Fire’s outcome shows up in the host’s ordinary logs (machine, event, from, to, outcome) without the host adapting an Inspector. It is no-op by default: with no WithLogger the instance holds a nil logger and never logs, so the pure Fire step performs no IO and adds zero overhead. The logger is written to synchronously on the Fire path at slog.LevelDebug and must not block; it observes only and never mutates the instance. Wire both seams when you want host logs AND structured inspection — they are independent.
type Clock
Section titled “type Clock”Clock is the deterministic time seam used by host drivers (never by the kernel). A real host wires a wall-clock implementation; a test wires a fake clock so `after` machines are exercised deterministically. The kernel’s Fire step never calls a Clock — only effect-consuming drivers do.
type Clock interface { // Now reports the current time. Now() time.Time // After returns a channel that receives once the duration elapses, mirroring // time.After. A driver selects on it to learn when a delayed event is due. After(d time.Duration) <-chan time.Time}func SystemClock
Section titled “func SystemClock”func SystemClock() ClockSystemClock returns the wall-clock Clock backed by the standard library, for a production host driver.
type ContextCodec
Section titled “type ContextCodec”ContextCodec encodes and decodes an instance context C to and from bytes for a Snapshot, for a context type that is not directly JSON-marshalable (or needs a custom wire form). Encode is called by Snapshot.MarshalJSON; Decode by Snapshot.UnmarshalJSON. When no codec is supplied, the default codec marshals C with encoding/json, so C must be JSON-marshalable by default.
type ContextCodec[C any] interface { Encode(C) ([]byte, error) Decode([]byte) (C, error)}type ContextSchema
Section titled “type ContextSchema”ContextSchema is a serializable description of a machine’s context type: an object whose named fields each carry a SchemaField type. It is the root of the data model attached to the IR and reuses the closed-enum extension policy used across the envelope, so an unknown field kind a newer producer emitted survives a load -> save cycle verbatim.
type ContextSchema struct { // Fields are the context's named top-level fields, in declaration order for // objects derived by SchemaOf (struct field order) and as authored otherwise. Fields []SchemaField `json:"fields,omitempty"`
// Meta is the reserved per-schema extension namespace, round-tripped verbatim // like every other Meta in the IR. The kernel never inspects it. Meta map[string]any `json:"meta,omitempty"` // contains filtered or unexported fields}func SchemaOf
Section titled “func SchemaOf”func SchemaOf[C any]() ContextSchemaSchemaOf derives a ContextSchema from the Go type C by reflection. It is the opt-in helper a host pairs with WithContextSchema to attach a context’s shape to a machine; deriving is never automatic at Forge, so an absent schema stays valid.
The reflection mapping is:
- struct -> object; one field per exported field, named by its json tag (falling back to the Go field name), in declaration order. A field tagged `json:”-”` is skipped; an embedded (anonymous) struct is flattened, mirroring encoding/json.
- string -> string
- all integer kinds -> int
- float32/float64 -> float
- bool -> bool
- time.Time -> time
- time.Duration -> duration
- slice / array -> list, with the element type derived recursively
- map -> map, with key and value types derived recursively
- pointer -> the pointee’s type, marked Nullable
- interface{} / other kinds -> string (the conservative fallback; the kind cannot be reflected to anything narrower)
Enums cannot be reflected reliably: a Go enum is typically a named integer or string type whose allowed values live in package-level constants the reflect package cannot enumerate. SchemaOf therefore maps such a type to its underlying scalar (int or string); declare the allowed values explicitly with a SchemaField of Kind SchemaEnum to override the scalar — for example, by authoring the ContextSchema directly rather than deriving it.
func (ContextSchema) FieldAt
Section titled “func (ContextSchema) FieldAt”func (s ContextSchema) FieldAt(path string) (SchemaField, bool)FieldAt resolves the SchemaField at a dotted field path, descending object fields and unwrapping list/map element types when a path segment names a collection’s element. It returns the resolved field and true, or the zero field and false when any segment does not resolve. The lookup is the type-side helper an expression layer uses to type a guard/assign reference like “order.total”.
Path semantics: each segment names a field of the current object; to step into a list or map element, the segment names the list/map field and the next segment continues into its element type (lists and maps both descend through their Elem). An empty path returns the schema’s root object as an unnamed object field.
func (ContextSchema) MarshalJSON
Section titled “func (ContextSchema) MarshalJSON”func (s ContextSchema) MarshalJSON() ([]byte, error)MarshalJSON encodes a ContextSchema, merging its preserved unknown keys back in with stable key ordering.
func (*ContextSchema) UnmarshalJSON
Section titled “func (*ContextSchema) UnmarshalJSON”func (s *ContextSchema) UnmarshalJSON(data []byte) errorUnmarshalJSON decodes a ContextSchema and captures any unknown top-level keys into extra so they survive re-serialization.
type ContextView
Section titled “type ContextView”ContextView is the read-only projection of a machine’s context C as it crosses the behavior-invocation boundary. Raw returns the live value for the in-process fast path; JSON returns the serialized wire form an out-of-process binding consumes. It carries no mutator — context is read-only at this seam.
type ContextView interface { // Raw returns the underlying context value. For the in-process projection it is // the live entity itself (a zero-cost pass-through); a binding that knows it is // in-process may type-assert it back to its concrete C. Raw() any // JSON returns the serialized projection of the context — the wire form an // out-of-process binding receives. The in-process projection marshals the live // value with the context codec on demand. JSON() ([]byte, error)}type DescribeBuilder
Section titled “type DescribeBuilder”DescribeBuilder fluently accumulates a registration’s descriptor metadata — its description, parameter schema, and read/write hints. Obtain one with Describe, chain Param / OptionalParam / Reads / Writes, and pass it as the trailing option to a registration (Guard / Action / Service / Actor). A DescribeBuilder is itself a DescribeOption, so it drops straight into the options tail.
type DescribeBuilder struct { // contains filtered or unexported fields}func Describe
Section titled “func Describe”func Describe(description string) *DescribeBuilderDescribe opens a fluent descriptor builder with the given human description. Chain Param / OptionalParam / Reads / Writes to declare the parameter schema and data-flow hints, then pass the builder as the trailing option to a registration:
reg.Guard("minAmount", minAmount, state.Describe("Passes when the amount is at least min."). Param("min", state.IntParam). OptionalParam("currency", state.StringParam). Reads("Order"))func (*DescribeBuilder) EnumParam
Section titled “func (*DescribeBuilder) EnumParam”func (d *DescribeBuilder) EnumParam(name string, allowed ...string) *DescribeBuilderEnumParam declares a required enum parameter constrained to the given allowed values.
func (*DescribeBuilder) OptionalParam
Section titled “func (*DescribeBuilder) OptionalParam”func (d *DescribeBuilder) OptionalParam(name string, typ ParamType) *DescribeBuilderOptionalParam declares an optional parameter of the given type.
func (*DescribeBuilder) Param
Section titled “func (*DescribeBuilder) Param”func (d *DescribeBuilder) Param(name string, typ ParamType) *DescribeBuilderParam declares a required parameter of the given type.
func (*DescribeBuilder) ParamSpec
Section titled “func (*DescribeBuilder) ParamSpec”func (d *DescribeBuilder) ParamSpec(p ParamSpec) *DescribeBuilderParamSpec appends a fully-specified parameter, for cases needing a description, default, or enum values the shorthand Param/OptionalParam do not express.
func (*DescribeBuilder) Reads
Section titled “func (*DescribeBuilder) Reads”func (d *DescribeBuilder) Reads(fields ...string) *DescribeBuilderReads records the entity fields the implementation reads, a data-flow hint for a UI. Successive calls accumulate.
func (*DescribeBuilder) Writes
Section titled “func (*DescribeBuilder) Writes”func (d *DescribeBuilder) Writes(fields ...string) *DescribeBuilderWrites records the entity fields the implementation writes, a data-flow hint for a UI. Successive calls accumulate.
type DescribeOption
Section titled “type DescribeOption”DescribeOption configures the optional descriptor attached to a registration. A *DescribeBuilder is the canonical implementation; the option tail keeps registration backward-compatible — calling Guard/Action/Service/Actor with no option still works and yields a minimal descriptor.
type DescribeOption interface { // contains filtered or unexported methods}type Descriptor
Section titled “type Descriptor”Descriptor is the serializable palette entry for one registered implementation. It carries the implementation’s kind and name (always present), an optional human description, the parameter schema a UI renders a form from, and optional context read/write hints naming the entity fields the implementation reads or writes. A registration with no Describe yields a minimal Descriptor with only Kind and Name set.
type Descriptor struct { Kind DescriptorKind `json:"kind"` Name string `json:"name"` Description string `json:"description,omitempty"` Params []ParamSpec `json:"params,omitempty"` // Reads and Writes are optional type hints naming the entity fields the // implementation reads from or writes to, for a UI that surfaces data flow. Reads []string `json:"reads,omitempty"` Writes []string `json:"writes,omitempty"` // Binding is the reserved descriptor of how this named behavior is backed. It // is optional and absent by default; an absent binding means the behavior // resolves to the in-process Go registry entry (BindingTransportOf reads that // default). Reserving the slot now keeps a future out-of-process binding // (a sandboxed component, a remote service) an additive descriptor field rather // than a breaking change. The kernel never dispatches on it at v1. Binding *BindingSpec `json:"binding,omitempty"`}func BuiltinPalette
Section titled “func BuiltinPalette”func BuiltinPalette() []DescriptorBuiltinPalette returns descriptors for the language-level built-ins the kernel recognizes without host registration — the actor and scheduling actions (spawn, stop-actor/stop-child, send/forward/respond, send-parent, cancel) and the stateIn guard. They are excluded from Palette because they are part of the language, not the host’s registry; a builder lists them from this fixed set so the editor surfaces the full vocabulary. The returned slice is freshly allocated and sorted deterministically.
type DescriptorKind
Section titled “type DescriptorKind”DescriptorKind names the category of a registered implementation in the palette: a guard predicate, an action/effect, an invoked service, or an actor behavior. It serializes as its lowercase string for a stable wire form.
type DescriptorKind stringThe descriptor kinds, one per palette-eligible registration surface.
const ( // KindGuard marks a registered guard predicate. KindGuard DescriptorKind = "guard" // KindAction marks a registered action/effect. KindAction DescriptorKind = "action" // KindAssign marks a registered assign reducer — the sole context writer. KindAssign DescriptorKind = "assign" // KindService marks a registered invoked service. KindService DescriptorKind = "service" // KindActor marks a registered actor behavior. KindActor DescriptorKind = "actor")type Diagnostic
Section titled “type Diagnostic”Diagnostic is a non-failing finding from Temper — a lint/static-analysis result surfaced before Quench. Consumers pattern-match on it, so its field names are stable.
type Diagnostic struct { // Severity is the finding's level ("warning" | "error"); under Strict, Quench // rejects any finding, otherwise only "error". Severity string // Message is the human-readable description of the finding. Message string // SrcFile and SrcLine point at the builder call site that produced the finding, // captured via runtime.Caller. They are diagnostic-only and may be empty for a // finding with no single source position. SrcFile string SrcLine int}type Effect
Section titled “type Effect”Effect is an abstract, domain-defined payload. The kernel never inspects it.
type Effect = anytype EffectEnvelope
Section titled “type EffectEnvelope”EffectEnvelope is the serialized form of an effect: a discriminated kind, the effect’s JSON payload, and an optional extension namespace. It is the output half of the data boundary — the shape a host journals, dedupes, renders, or emits across a process boundary — mirroring the IR envelope on the input half.
EffectID is reserved: a later ordering contract assigns each emitted effect a stable, deterministic identity for journal dedup and replay. The field exists in the wire shape now so adding that identity later is non-breaking, but the kernel does not populate or stabilize it yet — an inbound EffectID round-trips verbatim and otherwise carries no meaning.
type EffectEnvelope struct { // Kind is the effect's stable discriminant (see KindedEffect.Kind). Kind string `json:"kind"` // Payload is the effect's marshaled body. It is opaque to the envelope; an // EffectRegistry decodes it into a concrete effect keyed by Kind. Payload json.RawMessage `json:"payload,omitempty"` // Meta is the reserved per-effect extension namespace — a schema hook and the // attachment point for host annotations. The kernel never inspects it; it // round-trips verbatim. Meta map[string]any `json:"meta,omitempty"` // EffectID is the reserved correlation/identity slot. NOT yet stable: the // kernel leaves it empty and a later ordering PR will populate it // deterministically. An inbound value is preserved on round-trip. EffectID string `json:"effectId,omitempty"` // contains filtered or unexported fields}func MarshalEffect
Section titled “func MarshalEffect”func MarshalEffect(eff KindedEffect) (EffectEnvelope, error)MarshalEffect serializes a KindedEffect into an EffectEnvelope. The effect’s Kind becomes the envelope discriminant and the effect marshals to the payload. An UnknownEffect re-emits its preserved kind, payload, and meta verbatim so a foreign effect survives a round-trip without the local build understanding it.
func (EffectEnvelope) MarshalJSON
Section titled “func (EffectEnvelope) MarshalJSON”func (e EffectEnvelope) MarshalJSON() ([]byte, error)MarshalJSON encodes an EffectEnvelope, merging its preserved unknown keys back in with stable key ordering.
func (*EffectEnvelope) UnmarshalJSON
Section titled “func (*EffectEnvelope) UnmarshalJSON”func (e *EffectEnvelope) UnmarshalJSON(data []byte) errorUnmarshalJSON decodes an EffectEnvelope and captures any unknown keys into extra so they survive re-serialization.
type EffectFactory
Section titled “type EffectFactory”EffectFactory builds a fresh, zero-valued concrete effect for a kind. The registry unmarshals an envelope’s payload into the value the factory returns, so a factory returns a pointer to a concrete effect type for json.Unmarshal to populate. Built-in factories are pre-registered; a host registers its own effect kinds through RegisterEffect.
type EffectFactory func() Effecttype EffectRegistry
Section titled “type EffectRegistry”EffectRegistry maps effect kinds to factories for envelope deserialization. It is the output-half counterpart to the host registry on the input half: the built-in effect kinds are pre-registered, and a host adds its own through the RegisterEffect functional option. Deserializing a kind the registry does not know does not fail — the envelope is preserved as an UnknownEffect — but such an effect is not Dispatchable, realizing the preserve-on-load, reject-on-dispatch closed-enum extension policy.
type EffectRegistry struct { // contains filtered or unexported fields}func NewEffectRegistry
Section titled “func NewEffectRegistry”func NewEffectRegistry(opts ...RegisterEffectOption) *EffectRegistryNewEffectRegistry returns an EffectRegistry with every built-in effect kind pre-registered, then applies the supplied options (host effect kinds) in order. Options registering a built-in kind override the pre-registration.
func (*EffectRegistry) Dispatchable
Section titled “func (*EffectRegistry) Dispatchable”func (r *EffectRegistry) Dispatchable(eff Effect) errorDispatchable reports whether an effect may be applied by a host. A nil result means the effect carries a kind the registry recognizes (or is not kinded at all — a bare domain effect the kernel never gated). An UnknownEffect, or any KindedEffect whose kind the registry does not know, is rejected with a typed *ErrUnknownEffectKind, completing the preserve-on-load, reject-on-dispatch policy: a foreign effect is never silently applied.
func (*EffectRegistry) Unmarshal
Section titled “func (*EffectRegistry) Unmarshal”func (r *EffectRegistry) Unmarshal(env EffectEnvelope) (Effect, error)Unmarshal decodes an EffectEnvelope into a concrete effect. A recognized kind is built by its registered factory and populated from the payload; an unrecognized kind is preserved verbatim as an UnknownEffect rather than dropped or rejected — the reject happens later at Dispatchable. The returned value implements KindedEffect.
type ErrActionFailed
Section titled “type ErrActionFailed”ErrActionFailed wraps a bound action that returned an error during emission.
type ErrActionFailed struct { TransitionName string ActionName string Cause error}func (*ErrActionFailed) Error
Section titled “func (*ErrActionFailed) Error”func (e *ErrActionFailed) Error() stringfunc (*ErrActionFailed) Unwrap
Section titled “func (*ErrActionFailed) Unwrap”func (e *ErrActionFailed) Unwrap() errortype ErrActorPanic
Section titled “type ErrActorPanic”ErrActorPanic is the typed failure raised when a child-machine actor panics while it steps an event. The ActorSystem recovers the panic so it never crashes the host driver, wraps the recovered value here, and settles the actor as a failure — routing its onError, or escalating to the parent when none is wired.
type ErrActorPanic struct { // ActorID is the registry id of the actor that panicked. ActorID string // Value is the recovered panic value, rendered for the error message. Value any}func (*ErrActorPanic) Error
Section titled “func (*ErrActorPanic) Error”func (e *ErrActorPanic) Error() stringError renders the recovered actor panic.
type ErrAssignPanic
Section titled “type ErrAssignPanic”ErrAssignPanic is returned when an assign reducer panicked and was recovered, or when an assign ref did not resolve at fire time. An assign is a total reducer, so a panic is a programmer error the kernel surfaces as a typed failure that stops the commit rather than leaving context partly folded.
type ErrAssignPanic struct { AssignName string Recovered any}func (*ErrAssignPanic) Error
Section titled “func (*ErrAssignPanic) Error”func (e *ErrAssignPanic) Error() stringtype ErrGuardFailed
Section titled “type ErrGuardFailed”ErrGuardFailed is returned when a named guard returned false.
type ErrGuardFailed struct { GuardName string Reason string}func (*ErrGuardFailed) Error
Section titled “func (*ErrGuardFailed) Error”func (e *ErrGuardFailed) Error() stringtype ErrGuardPanic
Section titled “type ErrGuardPanic”ErrGuardPanic is returned when a guard panicked and was recovered.
type ErrGuardPanic struct { GuardName string Recovered any}func (*ErrGuardPanic) Error
Section titled “func (*ErrGuardPanic) Error”func (e *ErrGuardPanic) Error() stringtype ErrInvalidTransition
Section titled “type ErrInvalidTransition”ErrInvalidTransition is returned when no transition matched (current, event), or all matching transitions had failing guards. From names the state the event was fired in, Event the rejected event, and Reason the specific cause (no declared transition, a final-state exit, an undeclared current state, …). To names the intended target when the rejected transition had one (a targeted transition whose guards all failed); it is empty for an unmatched event with no candidate target.
type ErrInvalidTransition struct { From string To string Event string Reason string}func (*ErrInvalidTransition) Error
Section titled “func (*ErrInvalidTransition) Error”func (e *ErrInvalidTransition) Error() stringtype ErrMicrostepOverflow
Section titled “type ErrMicrostepOverflow”ErrMicrostepOverflow is returned when a single Fire macrostep does not reach a stable configuration within the run-to-completion step budget. It indicates a cycle of raised internal events or eventless (“always”) transitions that never settles.
type ErrMicrostepOverflow struct { Limit int State string}func (*ErrMicrostepOverflow) Error
Section titled “func (*ErrMicrostepOverflow) Error”func (e *ErrMicrostepOverflow) Error() stringtype ErrNoInitialState
Section titled “type ErrNoInitialState”ErrNoInitialState is returned/panicked by Cast when neither a CurrentStateFn is declared on the machine nor an explicit initial state is supplied via WithInitialState — there is no way to derive the instance’s starting state. This is a programmer error, consistent with Quench’s panic-on-misuse posture.
type ErrNoInitialState struct { Machine string}func (*ErrNoInitialState) Error
Section titled “func (*ErrNoInitialState) Error”func (e *ErrNoInitialState) Error() stringtype ErrNoPath
Section titled “type ErrNoPath”ErrNoPath is returned by PlanPath when no event sequence connects from->to.
type ErrNoPath struct { From string To string}func (*ErrNoPath) Error
Section titled “func (*ErrNoPath) Error”func (e *ErrNoPath) Error() stringtype ErrPolicyDenied
Section titled “type ErrPolicyDenied”ErrPolicyDenied is returned when a policy returned Deny.
type ErrPolicyDenied struct { PolicyName string Reason string}func (*ErrPolicyDenied) Error
Section titled “func (*ErrPolicyDenied) Error”func (e *ErrPolicyDenied) Error() stringtype ErrUnboundActor
Section titled “type ErrUnboundActor”ErrUnboundActor is returned by an ActorSystem when a SpawnActor’s Src does not resolve against the system’s actor palette — no child-machine factory was registered under that name. The actor is settled as an error so the parent still routes its onError rather than hanging.
type ErrUnboundActor struct { Name string}func (*ErrUnboundActor) Error
Section titled “func (*ErrUnboundActor) Error”func (e *ErrUnboundActor) Error() stringtype ErrUnboundRef
Section titled “type ErrUnboundRef”ErrUnboundRef is returned when a guard/action/effect ref in the IR did not resolve against the registry (raised at Quench / Provide).
type ErrUnboundRef struct { Kind string // "guard" | "action" | "assign" | "service" Name string}func (*ErrUnboundRef) Error
Section titled “func (*ErrUnboundRef) Error”func (e *ErrUnboundRef) Error() stringtype ErrUndeclaredState
Section titled “type ErrUndeclaredState”ErrUndeclaredState is returned when a state value was never declared.
type ErrUndeclaredState struct { State string}func (*ErrUndeclaredState) Error
Section titled “func (*ErrUndeclaredState) Error”func (e *ErrUndeclaredState) Error() stringtype ErrUnknownBuiltin
Section titled “type ErrUnknownBuiltin”ErrUnknownBuiltin is returned when a ref names a kernel built-in action the kernel does not recognize. It is a defensive programmer-error signal: the DSL and lint only ever produce known built-in names, so this surfaces only a hand-constructed or corrupted ref.
type ErrUnknownBuiltin struct { Name string}func (*ErrUnknownBuiltin) Error
Section titled “func (*ErrUnknownBuiltin) Error”func (e *ErrUnknownBuiltin) Error() stringtype ErrUnknownEffectKind
Section titled “type ErrUnknownEffectKind”ErrUnknownEffectKind is returned by EffectRegistry.Dispatchable when an effect carries a kind the registry does not recognize. It realizes the reject half of the closed-enum extension policy for effect kinds: an unknown kind is preserved on load (as an UnknownEffect) so a foreign effect round-trips losslessly, but it is refused at dispatch rather than silently applied — the host must register the kind (RegisterEffect) or drop the effect deliberately.
type ErrUnknownEffectKind struct { // Kind is the unrecognized effect discriminant. Kind string}func (*ErrUnknownEffectKind) Error
Section titled “func (*ErrUnknownEffectKind) Error”func (e *ErrUnknownEffectKind) Error() stringtype ErrUnsupportedSchema
Section titled “type ErrUnsupportedSchema”ErrUnsupportedSchema is returned by LoadFromJSON when an IR document declares a schema major version newer than the loader supports. The reject-higher-major policy is the reserved compatibility seam: a higher minor (same major) loads, preserving unknown fields for forward-compat, but a higher major signals a wire form this build cannot safely interpret and is refused rather than guessed at.
type ErrUnsupportedSchema struct { // Got is the schemaVersion declared in the document. Got string // Supported is the loader's own schema version. Supported string}func (*ErrUnsupportedSchema) Error
Section titled “func (*ErrUnsupportedSchema) Error”func (e *ErrUnsupportedSchema) Error() stringtype EscalationHandler
Section titled “type EscalationHandler”EscalationHandler receives an actor failure that escalated to the parent because no onError was declared for it. It is the host-side opt-in for reacting to an unhandled child failure: a handler may fire a parent event, tear other actors down, propagate further, or record the failure. It is wired with WithEscalationHandler and is invoked once per escalation, outside the system mutex, so it may safely re-enter the ActorSystem.
Returning no error acknowledges the escalation (the default record + inspect still occurred). The handler does not replace the typed record or the inspector event — those always happen — it adds host policy on top of the frozen default.
type EscalationHandler func(ctx context.Context, esc *ActorEscalation)type FakeClock
Section titled “type FakeClock”FakeClock is a deterministic Clock for tests: time advances only when Advance is called. It implements Clock; pair it with a Scheduler (via WithClock at Cast) to drive `after` transitions with no real waiting. It is concurrency-safe.
type FakeClock struct { // contains filtered or unexported fields}func NewFakeClock
Section titled “func NewFakeClock”func NewFakeClock(start time.Time) *FakeClockNewFakeClock returns a FakeClock starting at the given instant. The zero instant is fine; only relative advances matter for delayed transitions.
func (*FakeClock) Advance
Section titled “func (*FakeClock) Advance”func (c *FakeClock) Advance(d time.Duration)Advance moves the fake clock forward by d. After advancing, call Scheduler.Tick to fire any now-due timers.
func (*FakeClock) After
Section titled “func (*FakeClock) After”func (c *FakeClock) After(d time.Duration) <-chan time.TimeAfter returns a channel that fires once the fake clock has advanced by at least d from the call. It is provided for Clock conformance; the Scheduler drives elapses through Now + Tick rather than this channel, so a test never blocks on it.
func (*FakeClock) Now
Section titled “func (*FakeClock) Now”func (c *FakeClock) Now() time.TimeNow returns the fake clock’s current instant.
type FieldRef
Section titled “type FieldRef”FieldRef is a Core field-ref operand under construction: a dotted context path that becomes either side of a comparison or the subject of a membership test. Obtain one with Field, then close it with a comparison (Eq/Ne/Lt/Le/Gt/Ge) or In to produce a GuardNode. FieldRef is parameterized by the state type so the produced node composes with And/Or/Not and StateIn over the same machine.
type FieldRef[S comparable] struct { // contains filtered or unexported fields}func Field
Section titled “func Field”func Field[S comparable](path string) FieldRef[S]Field opens a Core field-ref operand at the given dotted context path (e.g. “Status” or “order.total”). Close it with a comparison or In:
state.Field[string]("Status").In(state.Str("paid"), state.Str("settled"))state.Field[string]("Balance").Gte(state.Param("amount"))func (FieldRef[S]) Eq
Section titled “func (FieldRef[S]) Eq”func (f FieldRef[S]) Eq(operand Operand[S]) GuardNode[S]Eq builds a Core equality comparison between the field and the given operand.
func (FieldRef[S]) Ge
Section titled “func (FieldRef[S]) Ge”func (f FieldRef[S]) Ge(operand Operand[S]) GuardNode[S]Ge builds a Core greater-than-or-equal comparison: field >= operand.
func (FieldRef[S]) Gt
Section titled “func (FieldRef[S]) Gt”func (f FieldRef[S]) Gt(operand Operand[S]) GuardNode[S]Gt builds a Core greater-than comparison: field > operand.
func (FieldRef[S]) In
Section titled “func (FieldRef[S]) In”func (f FieldRef[S]) In(values ...Operand[S]) GuardNode[S]In builds a Core membership test true when the field’s value equals one of the given literal operands. Every operand must be a literal (Str/Int/Float/Bool/ Dur/Param); a field operand in a membership set is rejected at Quench.
func (FieldRef[S]) Le
Section titled “func (FieldRef[S]) Le”func (f FieldRef[S]) Le(operand Operand[S]) GuardNode[S]Le builds a Core less-than-or-equal comparison: field <= operand.
func (FieldRef[S]) Lt
Section titled “func (FieldRef[S]) Lt”func (f FieldRef[S]) Lt(operand Operand[S]) GuardNode[S]Lt builds a Core less-than comparison: field < operand.
func (FieldRef[S]) Ne
Section titled “func (FieldRef[S]) Ne”func (f FieldRef[S]) Ne(operand Operand[S]) GuardNode[S]Ne builds a Core inequality comparison between the field and the given operand.
type FireFunc
Section titled “type FireFunc”FireFunc is the inner step the middleware chain wraps.
type FireFunc[S comparable, E comparable, C any] func(ctx context.Context, event E) FireResult[S]type FireOption
Section titled “type FireOption”FireOption configures Fire / FireSeq / FireEach.
type FireOption func(*fireConfig)func CollectAll
Section titled “func CollectAll”func CollectAll() FireOptionCollectAll makes a batch fire run every step and gather all errors instead of stopping at the first.
func WithEventData
Section titled “func WithEventData”func WithEventData(data any) FireOptionWithEventData attaches a payload to a single Fire so the triggering transition’s Assign reads it from AssignCtx.Event. It is the channel by which a host delivers a service result, an actor’s done-data, or an error to the onDone/onError transition’s reducer: the ServiceRunner and ActorSystem re-fire the routing event with the result as the payload, so the reducer consumes it through AssignCtx.Event with no side channel. When omitted, AssignCtx.Event carries the boxed triggering event itself.
type FireResult
Section titled “type FireResult”FireResult is the result of a single Fire.
type FireResult[S comparable] struct { NewState S Effects []Effect Trace Trace Err error}func FireEach
Section titled “func FireEach”func FireEach[S comparable, E comparable, C any](ctx context.Context, instances []*Instance[S, E, C], event E, opts ...FireOption) []FireResult[S]FireEach fans one event across an explicit set of instances, preserving per-instance attribution.
type ForgeOption
Section titled “type ForgeOption”ForgeOption configures Forge.
type ForgeOption func(*forgeConfig)func WithMachineID
Section titled “func WithMachineID”func WithMachineID(id string) ForgeOptionWithMachineID stamps the machine DEFINITION id (the IR ID) onto a Forge-built machine, carried alongside the version so a migrator can resolve the source definition unambiguously. When omitted, a Forge-built machine has no definition id.
func WithMachineVersion
Section titled “func WithMachineVersion”func WithMachineVersion(version string) ForgeOptionWithMachineVersion stamps the machine DEFINITION version (the IR Version, a semver label) onto a Forge-built machine, so a Snapshot taken from it carries the version a restored instance self-identifies by — the precondition for live migration. It mirrors the version a machine rehydrated from a versioned IR already carries. When omitted, a Forge-built machine has no definition version.
type ForwardEvent
Section titled “type ForwardEvent”ForwardEvent is the effect the kernel emits for the forwardTo built-in: forward the event the emitting actor is currently handling, verbatim, to the actor addressed by TargetID (or SystemID). The kernel does not embed the forwarded event — the host already has it as the event it just delivered — so this effect carries only the target. The host’s ActorSystem routes the current event into the target’s mailbox; addressing an unknown actor is a no-op. This realizes forwards the current event verbatim to another actor.
type ForwardEvent struct { // TargetID is the registry id of the actor to forward the current event to. // Empty when the target is addressed by SystemID instead. TargetID string `json:"targetId,omitempty"` // SystemID is the system-scoped name of the target actor, used when TargetID is // empty. SystemID string `json:"systemId,omitempty"`}func (ForwardEvent) Kind
Section titled “func (ForwardEvent) Kind”func (ForwardEvent) Kind() stringKind reports the forward-event effect discriminant.
type GuardBinding
Section titled “type GuardBinding”GuardBinding turns a guard request into a verdict. The in-process binding wraps a GuardFn; a future out-of-process binding marshals the request across its transport. EvalGuard is synchronous so it remains callable inside the pure Fire step.
type GuardBinding[C any] interface { EvalGuard(ctx context.Context, req GuardRequest[C]) (GuardResult, error)}type GuardCtx
Section titled “type GuardCtx”GuardCtx is passed to a bound guard function at run time.
type GuardCtx[C any] struct { Entity C Params map[string]any}type GuardFn
Section titled “type GuardFn”GuardFn is a pure predicate on the entity.
type GuardFn[C any] func(ctx GuardCtx[C]) booltype GuardKind
Section titled “type GuardKind”GuardKind names the tier of a guard expression node: Core is the structured, dependency-free tree this kernel evaluates in-process; Rich is the reserved source-plus-checked-AST tier an opt-in expression module will evaluate. The boolean spine and the named-ref/stateIn leaves leave Kind empty — they predate the discriminant and are structurally Core. GuardKind follows the closed-enum extension policy: a kind this build does not recognize is preserved verbatim on round-trip (so a newer producer’s node survives an older client) and is rejected only at evaluation.
type GuardKind stringconst ( // GuardKindCore tags a node as the structured, in-kernel Core tier. It is set // on the Core expression leaves built by the Core builder; the legacy boolean // and named-ref nodes leave it empty and are treated as Core. GuardKindCore GuardKind = "core" // GuardKindRich reserves the Rich tier — a guard authored as source text with // a checked AST, evaluated by an opt-in expression module. No Rich evaluation // path exists in the kernel; the kind is reserved so adding the tier later is // additive rather than a breaking change. GuardKindRich GuardKind = "rich")type GuardNode
Section titled “type GuardNode”GuardNode is one node of a serializable guard expression tree. A leaf references a host-provided guard by name (with serializable params) or is the built-in stateIn guard; internal nodes compose children with and/or/not.
The tree is pure, serializable data: like every other behavioral reference in the IR, leaf guards are named — never embedded closures — so a UI- or JSON-authored composite guard binds against the host registry at Provide and round-trips to and from JSON without losing structure. Arbitrary nesting is supported, e.g. And(Or(g1, g2), Not(g3)).
The common case — a single named guard — stays the plain Transition.Guards slice; GuardNode is used only when a transition needs boolean composition or the stateIn built-in.
type GuardNode[S comparable] struct { Op GuardOp `json:"op"`
// Kind is the node's tier: empty (legacy boolean/named spine, structurally // Core), GuardKindCore for a Core expression leaf, or GuardKindRich for the // reserved Rich tier. An unrecognized kind is preserved on round-trip and // rejected only at evaluation. Kind GuardKind `json:"kind,omitempty"`
// Ref is the named-ref guard for a GuardLeaf node. Zero for every other op. Ref *Ref `json:"ref,omitempty"`
// In is the target state for a GuardStateIn node: the guard is true when this // state is in the instance's active configuration (its leaves and their // ancestor spine). Zero for every other op. In *S `json:"in,omitempty"`
// Path is the dotted context path for a GuardField operand node, resolved // against the context at evaluation and against the ContextSchema at Quench. // Zero for every other op. Path string `json:"path,omitempty"`
// Lit is the typed literal value for a GuardLit operand node. Zero for every // other op. Lit *Literal `json:"literal,omitempty"`
// Set is the literal membership set for a GuardIn node: the left operand (the // first child) passes when it equals one of these values. Zero for every other // op. Set []Literal `json:"set,omitempty"`
// Children are the operands of an internal node. And/Or take one or more; Not // takes exactly one; a compare (eq/ne/lt/le/gt/ge) takes exactly two operand // nodes (each a GuardField or GuardLit); membership (in) takes exactly one // operand node. Empty for leaf, stateIn, field, and literal nodes. Children []GuardNode[S] `json:"children,omitempty"` // contains filtered or unexported fields}func And
Section titled “func And”func And[S comparable](nodes ...GuardNode[S]) GuardNode[S]And composes guards into a node true only when every operand is true, short-circuiting at the first false — consistent with the AND short-circuit of a plain multi-guard transition. Operands may be named-ref leaves, stateIn, or other combinators, nested arbitrarily.
Example
ExampleAnd composes named-ref guards and the stateIn built-in into a single boolean guard expression on a transition with And/Or/Not, exercising the guard combinators. The transition fires only when the composite passes; And short-circuits at the first false and Or at the first true.
package main
import ( "context" "fmt"
"github.com/stablekernel/crucible/state")
// access is the entity the combinator example guards against.type access struct { admin bool auditor bool}
// ExampleAnd composes named-ref guards and the stateIn built-in into a single// boolean guard expression on a transition with And/Or/Not, exercising the// guard combinators. The transition fires only when the composite passes; And// short-circuits at the first false and Or at the first true.func main() { m := state.Forge[string, string, access]("door"). Guard("admin", func(c state.GuardCtx[access]) bool { return c.Entity.admin }). Guard("auditor", func(c state.GuardCtx[access]) bool { return c.Entity.auditor }). State("locked"). Transition("locked").On("open").GoTo("open"). // Enabled while in "locked" AND (admin OR auditor). WhenExpr(state.And( state.StateIn("locked"), state.Or(state.Guard[string]("admin"), state.Guard[string]("auditor")), )). State("open"). Initial("locked"). Quench()
denied := m.Cast(access{}, state.WithInitialState("locked")) denied.Fire(context.Background(), "open") fmt.Println("no role:", denied.Current())
allowed := m.Cast(access{auditor: true}, state.WithInitialState("locked")) allowed.Fire(context.Background(), "open") fmt.Println("auditor:", allowed.Current())}Output
Section titled “Output”no role: lockedauditor: openfunc Guard
Section titled “func Guard”func Guard[S comparable](name string, params ...map[string]any) GuardNode[S]Guard builds a named-ref guard leaf with optional serializable params, the composable form of a single transition guard. It is the leaf used inside And/Or/Not.
func Not
Section titled “func Not”func Not[S comparable](node GuardNode[S]) GuardNode[S]Not inverts a single guard.
func Or
Section titled “func Or”func Or[S comparable](nodes ...GuardNode[S]) GuardNode[S]Or composes guards into a node true when any operand is true, short-circuiting at the first true. Operands may be named-ref leaves, stateIn, or other combinators, nested arbitrarily.
func StateIn
Section titled “func StateIn”func StateIn[S comparable](state S) GuardNode[S]StateIn builds the built-in in-state guard leaf: true when the instance’s active configuration includes state. It is config-aware — it reads the live active leaves and their ancestors at evaluation time, so it works for atomic, compound, and parallel configurations (“in” means the state is somewhere in the active set/spine). It is a first-class built-in: the consumer never registers it. The name is stateIn for guard parity; renaming to In would break that documented parity contract.
func (*GuardNode[S]) LeafRefs
Section titled “func (*GuardNode[S]) LeafRefs”func (g *GuardNode[S]) LeafRefs() []RefLeafRefs returns the named-ref guard leaves of a guard expression tree, in left-to-right order. The stateIn built-in carries no host ref and is omitted. It lets tooling (e.g. evolution diffing) enumerate the host guards a composite expression depends on.
func (GuardNode[S]) MarshalJSON
Section titled “func (GuardNode[S]) MarshalJSON”func (g GuardNode[S]) MarshalJSON() ([]byte, error)MarshalJSON encodes a GuardNode, merging its preserved unknown keys back in with stable key ordering.
func (*GuardNode[S]) StateInTargets
Section titled “func (*GuardNode[S]) StateInTargets”func (g *GuardNode[S]) StateInTargets() []SStateInTargets returns the target states of every stateIn leaf in the tree, in left-to-right order, so tooling can account for in-state dependencies a composite guard introduces.
func (*GuardNode[S]) UnmarshalJSON
Section titled “func (*GuardNode[S]) UnmarshalJSON”func (g *GuardNode[S]) UnmarshalJSON(data []byte) errorUnmarshalJSON decodes a GuardNode and captures any unknown keys into extra so they survive re-serialization, keeping forward-compat structural for the nested guard tree.
type GuardOp
Section titled “type GuardOp”GuardOp tags the kind of a node in a guard expression tree.
type GuardOp stringGuard expression operators. A leaf is either a named-ref guard (resolved against the host registry), the built-in stateIn guard, or one of the Core expression leaves (compare/field/literal/membership) evaluated in-kernel against the context; the internal nodes compose child results with boolean and/or/not. The string form is stable so the tree round-trips losslessly through JSON. The op set follows the closed-enum extension policy: an op this build does not recognize is preserved verbatim on round-trip and rejected only at evaluation.
const ( // GuardLeaf is a named-ref guard leaf: it carries a Ref bound to a host // GuardFn at Provide/Quench time, exactly like a plain transition guard. GuardLeaf GuardOp = "leaf" // GuardStateIn is the built-in in-state guard leaf: it is true when the // instance's active configuration includes the named state. It needs no // registration — the kernel evaluates it directly against the live spine. GuardStateIn GuardOp = "stateIn" // GuardAnd is true when every child is true; it short-circuits at the first // false child. GuardAnd GuardOp = "and" // GuardOr is true when any child is true; it short-circuits at the first // true child. GuardOr GuardOp = "or" // GuardNot inverts its single child. GuardNot GuardOp = "not"
// GuardEq is true when its two operands compare equal. GuardEq GuardOp = "eq" // GuardNe is true when its two operands compare unequal. GuardNe GuardOp = "ne" // GuardLt is true when the left operand is less than the right. GuardLt GuardOp = "lt" // GuardLe is true when the left operand is less than or equal to the right. GuardLe GuardOp = "le" // GuardGt is true when the left operand is greater than the right. GuardGt GuardOp = "gt" // GuardGe is true when the left operand is greater than or equal to the right. GuardGe GuardOp = "ge" // GuardIn is true when the left operand is a member of the literal set carried // on Set. GuardIn GuardOp = "in" // GuardField is a field-ref operand: it resolves the dotted Path against the // context and yields the value there. It is an operand, valid only as a child // of a compare or membership node, never a standalone boolean. GuardField GuardOp = "field" // GuardLit is a typed literal operand carried on Lit. Like GuardField it is an // operand, valid only inside a compare or membership node. GuardLit GuardOp = "literal")type GuardRequest
Section titled “type GuardRequest”GuardRequest is the serializable invocation envelope for a guard: the named ref, its params, and the read-only context projection the guard evaluates against.
type GuardRequest[C any] struct { Name string Params map[string]any Context ContextView}type GuardResult
Section titled “type GuardResult”GuardResult is the guard’s serializable result: a boolean verdict. It is deliberately minimal so a guard stays a pure predicate evaluable inside Fire.
type GuardResult struct { OK bool}type HistoryType
Section titled “type HistoryType”HistoryType is the reserved drop-in surface for shallow/deep history states.
type HistoryType intHistory kinds. HistoryNone is the v1 default (no history); shallow and deep are reserved for the deferred history-state feature.
const ( HistoryNone HistoryType = iota HistoryShallow HistoryDeep)type IOSpec
Section titled “type IOSpec”IOSpec is the reserved declaration slot for a machine’s input or done-output shape. At v1 it is opaque: Schema is a free-form, namespace-reserved description of the shape and Description is human documentation. A later data-model/typing module can give Schema teeth without changing the wire field. Meta is the per-spec extension namespace, round-tripped verbatim like every other Meta in the IR.
type IOSpec struct { // Schema is an opaque declaration of the input/output shape. The kernel never // inspects it; it travels for tooling and a future typing layer. Schema map[string]any `json:"schema,omitempty"` // Description is human-readable documentation of the slot. Description string `json:"description,omitempty"` // Meta is the reserved extension namespace for this spec. Meta map[string]any `json:"meta,omitempty"`}type IR
Section titled “type IR”IR is the serializable definition produced and consumed by the data front-end. It is the canonical machine: pure, lossless data. Behavior lives in a host registry and is referenced by name (via Ref), never embedded, so the IR round-trips to and from JSON without losing structure or bindings’ identity.
Non-serializable concerns — CurrentStateFn, requirement predicates, and middleware — are pure-runtime and are intentionally absent from the IR; a machine rehydrated from JSON is Cast from an explicit state and bound to a registry via Provide. The envelope fields (SchemaVersion, ID, Version, Input, Output, Meta) are an additive, non-breaking superset of the v0 IR: a document without them still loads, and a tolerant loader round-trips a document carrying extension fields it does not model. SchemaVersion is stamped by ToJSON so every emitted document is self-describing; LoadFromJSON rejects a higher schema major and preserves unknown keys within a major line.
type IR[S comparable, E comparable, C any] struct { // SchemaVersion is the IR wire-format version (major.minor). ToJSON stamps it // with CurrentSchemaVersion; LoadFromJSON rejects a higher major. SchemaVersion string `json:"schemaVersion,omitempty"` // ID is a stable machine identity distinct from the human-facing Name, used to // pin a durable instance or a migration to the exact definition it derives from. ID string `json:"id,omitempty"`
Name string `json:"name"`
// Version is the machine definition version (a semver string), the label a // migration maps from/to and a durable runtime pins an instance against. A // content digest is reserved for later and is not computed here. Version string `json:"version,omitempty"`
// Input and Output are the machine's opaque input contract and done-output // shape — the symmetry actors already have (per-invocation Input) lifted to the // root machine. At v1 they are reserved declaration slots; the typing layer is // additive. Input *IOSpec `json:"input,omitempty"` Output *IOSpec `json:"output,omitempty"`
// Context is the optional, serializable description of the machine's context // data model — the L5 data contract an expression layer type-checks guards and // assigns against and a studio renders context-update forms from. It is opt-in // (set with Builder.WithContextSchema, helper SchemaOf); an absent schema is // valid and simply limits later type-checking. The kernel never inspects it; it // round-trips verbatim. Context *ContextSchema `json:"context,omitempty"`
States []State[S, E, C] `json:"states,omitempty"` Initial S `json:"initial"` HasInitial bool `json:"hasInitial"`
// Meta is the reserved extension namespace at machine granularity: studio // viewport, property specs, provenance, and codegen hints live here. The kernel // never inspects it; it round-trips verbatim. Meta map[string]any `json:"meta,omitempty"` // contains filtered or unexported fields}func LoadFromJSON
Section titled “func LoadFromJSON”func LoadFromJSON[S comparable, E comparable, C any](b []byte, opts ...LoadOption) (*IR[S, E, C], error)LoadFromJSON rehydrates an IR from JSON.
func (IR[S, E, C]) MarshalJSON
Section titled “func (IR[S, E, C]) MarshalJSON”func (ir IR[S, E, C]) MarshalJSON() ([]byte, error)MarshalJSON encodes an IR, merging its preserved unknown top-level keys back in with stable key ordering so the output is canonical for golden diffing.
func (*IR[S, E, C]) Provide
Section titled “func (*IR[S, E, C]) Provide”func (ir *IR[S, E, C]) Provide(reg *Registry[C], opts ...ProvideOption) *Builder[S, E, C]Provide binds every Ref in the IR against the host registry and returns a Builder ready to Quench. Refs that do not resolve are surfaced at Quench as the typed *ErrUnboundRef (the same failure the DSL raises for an unregistered ref), so a UI/JSON-authored machine and a DSL-authored machine fail identically.
func (*IR[S, E, C]) UnmarshalJSON
Section titled “func (*IR[S, E, C]) UnmarshalJSON”func (ir *IR[S, E, C]) UnmarshalJSON(data []byte) errorUnmarshalJSON decodes an IR and captures any unknown top-level keys into extra so they survive re-serialization.
type InFlightService
Section titled “type InFlightService”InFlightService is the reserved record of an invoked service started but not yet resolved at snapshot time, so a future distributed/async resume can re-establish it. It mirrors the StartService effect’s coordinates: the invocation id, the service src name, the input, and the OnDone/OnError routing event labels.
type InFlightService struct { // ID is the invocationID of the started service, the stable correlation id a // resolving JournalEntry reuses. ID string `json:"id"` // Src is the service src name (the registry key) the host re-starts. Src string `json:"src,omitempty"` // Input is the structured input the service was started with. Input json.RawMessage `json:"input,omitempty"` // OnDone and OnError are the routing event labels the host re-fires the result // through after the service resolves. OnDone string `json:"onDone,omitempty"` OnError string `json:"onError,omitempty"`}type InspectKind
Section titled “type InspectKind”InspectKind names a category of inspection event, covering the inspection event types.
type InspectKind stringconst ( // InspectEvent marks an event received by an instance. InspectEvent InspectKind = "event" // InspectTransition marks a transition taken — a macrostep that changed (or // re-entered) the configuration, carrying its from/to and the Trace detail // (guards, effects, exit/entry cascade). It is the kernel's microstep/transition // inspection surface. InspectTransition InspectKind = "transition" // InspectSnapshot marks a snapshot update: the instance's observable state after // an event settled. InspectSnapshot InspectKind = "snapshot" // InspectActor marks an actor lifecycle change — spawned or stopped // — an actor lifecycle change. InspectActor InspectKind = "actor" // InspectMessage marks a message sent from one actor to another and/or delivered // to its target (the actor-to-actor flavor of an event). InspectMessage InspectKind = "message")type InspectionEvent
Section titled “type InspectionEvent”InspectionEvent is one live observation of an instance’s runtime activity. It is an inspection event: a tagged record whose populated fields depend on Kind. A field that does not apply to a Kind is left zero.
The event is read-only; an Inspector must not retain references to mutable values it does not own. The Trace, when present, is the same structured record Fire records in History — surfaced live rather than after the fact.
type InspectionEvent struct { // Kind tags which observation this is and which fields are populated. Kind InspectKind
// Machine names the machine the observed instance was cast from. Always set. Machine string
// Event is the string rendering of the event that triggered this observation, // for InspectEvent, InspectTransition, and InspectSnapshot. Empty for actor // lifecycle events with no triggering instance event. Event string
// From and To name the configuration's primary leaf before and after a // transition (InspectTransition) or the settled leaf (InspectSnapshot). For an // InspectEvent, From is the leaf the event was received in and To is empty. From string To string
// Trace is the structured Fire record for an InspectTransition — the live twin // of the entry History() later reports. Nil for non-transition kinds. Trace *Trace
// Configuration is every active leaf after the observed step settled, for // InspectSnapshot and InspectTransition. It is a copy; an Inspector may retain // it. Configuration []string
// Status is the instance's lifecycle status for InspectSnapshot // (running/done/error), so an inspector can observe completion without polling. Status Status
// ActorID, ActorSrc, and ActorPhase describe an InspectActor lifecycle event: // the actor's registry id, the ref name it was spawned from, and whether it was // spawned or stopped. ActorID string ActorSrc string ActorPhase ActorPhase
// SenderID, TargetID, MessagePhase, and Message describe an InspectMessage // event: the originating actor (empty for a host-injected send), the target // actor, whether the message was observed on send or on delivery, and the // string rendering of the message event. SenderID string TargetID string MessagePhase MessagePhase Message string}type Inspector
Section titled “type Inspector”Inspector is the observer sink an instance (and its ActorSystem) feeds live inspection events to. It is registered at Cast with WithInspector and is off by default — a nil inspector is never called, so an un-inspected instance pays nothing. An Inspector must not mutate the instance or perform blocking IO on the hot path; it is the telemetry-style sink the kernel notifies synchronously, in the same spirit as the existing Trace/observer ethos.
All methods receive a by-value InspectionEvent so an implementation can retain it safely. Implement only the methods that matter and embed BaseInspector to no-op the rest.
type Inspector interface { // Inspect receives every inspection event. The event's Kind selects the // populated fields. A single entry point keeps the interface stable as new // kinds are added — a new InspectKind never changes this signature. Inspect(ev InspectionEvent)}type InspectorFunc
Section titled “type InspectorFunc”InspectorFunc adapts a plain function to the Inspector interface, for the common case of a single closure sink.
type InspectorFunc func(ev InspectionEvent)func (InspectorFunc) Inspect
Section titled “func (InspectorFunc) Inspect”func (f InspectorFunc) Inspect(ev InspectionEvent)Inspect calls the underlying function.
type Instance
Section titled “type Instance”Instance binds a Machine to one entity and carries trace history.
type Instance[S comparable, E comparable, C any] struct { // contains filtered or unexported fields}func (*Instance[S, E, C]) Clock
Section titled “func (*Instance[S, E, C]) Clock”func (i *Instance[S, E, C]) Clock() ClockClock returns the time seam wired to this instance at Cast (SystemClock() by default). A host driver reads it to schedule delayed (`after`) transitions; the pure Fire step never consults it.
func (*Instance[S, E, C]) Configuration
Section titled “func (*Instance[S, E, C]) Configuration”func (i *Instance[S, E, C]) Configuration() []SConfiguration returns all currently-active leaves, in declaration order. len == 1 for a flat or single-spine machine; len == N when N regions are active in parallel.
func (*Instance[S, E, C]) Current
Section titled “func (*Instance[S, E, C]) Current”func (i *Instance[S, E, C]) Current() SCurrent returns the primary (first) active leaf — the common “what state am I really in?” answer, back-compatible with flat machines.
func (*Instance[S, E, C]) Entity
Section titled “func (*Instance[S, E, C]) Entity”func (i *Instance[S, E, C]) Entity() CEntity returns the entity this instance is bound to.
func (*Instance[S, E, C]) Fire
Section titled “func (*Instance[S, E, C]) Fire”func (i *Instance[S, E, C]) Fire(ctx context.Context, event E, opts ...FireOption) FireResult[S]Fire runs the full transition pipeline for a single event.
func (*Instance[S, E, C]) FireSeq
Section titled “func (*Instance[S, E, C]) FireSeq”func (i *Instance[S, E, C]) FireSeq(ctx context.Context, events []E, opts ...FireOption) BatchResult[S]FireSeq drives a sequence of events into one instance, threading intermediate state and merging the per-step traces into one ordered Trace.
Example
ExampleInstance_FireSeq drives a machine through a sequence of events, walking a document from Draft to Published in one batch.
m := buildDocMachine()doc := &Document{Status: Draft, ReviewerID: strptr("rev-1")}batch := m.Cast(doc).FireSeq(context.Background(), []DocEvent{Submit, Approve, Publish})
fmt.Println("steps:", len(batch.Steps))fmt.Println("final:", batch.Steps[len(batch.Steps)-1].NewState)// Output:// steps: 3// final: PublishedOutput
Section titled “Output”steps: 3final: Publishedfunc (*Instance[S, E, C]) History
Section titled “func (*Instance[S, E, C]) History”func (i *Instance[S, E, C]) History() []TraceHistory returns the ordered traces recorded on this instance.
func (*Instance[S, E, C]) InFinal
Section titled “func (*Instance[S, E, C]) InFinal”func (i *Instance[S, E, C]) InFinal() boolInFinal reports whether the instance’s current primary leaf is a final state — the signal an ActorSystem reads to learn that a child-machine actor has reached completion and its parent’s onDone should be routed. It is a pure read of the active configuration against the machine definition; it never mutates the instance and consults no clock or IO. For a parallel active configuration it reports whether the whole configuration is complete (every region’s active leaf final), so a child whose root is parallel completes only when all regions do.
func (*Instance[S, E, C]) ResumeEffects
Section titled “func (*Instance[S, E, C]) ResumeEffects”func (i *Instance[S, E, C]) ResumeEffects() []EffectResumeEffects returns the re-arm effects a host absorbs after Restore to re-establish the instance’s pending timers, invoked services, and spawned actors for its restored configuration: a ScheduleAfter per pending delayed transition, a StartService per invoked service, and a SpawnActor per child-machine actor invocation active in the configuration. It is the restore twin of StartEffects (which arms an initial Cast configuration) extended with the delayed-timer effects, so a restored instance re-establishes its invoked/spawned children. Like StartEffects it is a pure read of the configuration and emits no IO; route the effects through the same Scheduler / ServiceRunner / ActorSystem the host drives for Fire.
Entry actions are NOT re-run: ResumeEffects emits only the lifecycle re-arm effects, never the states’ OnEntry actions, so a restored instance resumes rather than re-enters.
func (*Instance[S, E, C]) Snapshot
Section titled “func (*Instance[S, E, C]) Snapshot”func (i *Instance[S, E, C]) Snapshot() Snapshot[S, E, C]Snapshot captures the instance’s full runtime state into a serializable Snapshot: the active configuration, recorded history, context, lifecycle status, and the IDs of the pending timers / services / actors armed for the active configuration. It is a pure read — it never fires, mutates the instance, or consults a clock — so Fire stays pure and a snapshot may be taken at any quiescent point between Fires.
The returned Snapshot’s Context holds the live entity value; serialize the whole snapshot with MarshalSnapshot (or json.Marshal once the default codec suffices) to obtain the wire form. Status is derived from the active configuration (StatusDone when the whole configuration is final, else StatusRunning); a host that tracks an explicit failure sets StatusError and Error on the returned snapshot before persisting.
func (*Instance[S, E, C]) StartEffects
Section titled “func (*Instance[S, E, C]) StartEffects”func (i *Instance[S, E, C]) StartEffects() []EffectStartEffects returns the StartService effects for the invoked services declared on the instance’s initial active configuration, so a host can arm the services of the state(s) entered at Cast — the entry that Fire never observes because no event drove it. Call it once, right after Cast, and route the effects through the same ServiceRunner used for Fire’s effects. It is a pure read of the configuration and emits no IO, consistent with the kernel’s effects-as-data contract. A flat or single-spine instance reports its single starting state’s services; a parallel initial configuration reports every active region’s.
type Invocation
Section titled “type Invocation”Invocation is a declarative invoked service on a state. On entering the owning state the kernel emits a StartService effect carrying Src and Input; the host runs the bound service and re-fires OnDone with the result or OnError with the error back through Fire. On exiting the state before the service completes, the kernel emits a StopService effect so the host stops the in-flight service (auto-stop-on-exit). The whole struct serializes, so an invoke block round-trips losslessly through JSON.
type Invocation[S comparable, E comparable, C any] struct { // ID identifies this invocation for the lifetime of the owning state's // activation. It is stable per (machine, owning state, invoke index), so the // StartService emitted on entry and the StopService emitted on exit pair up, // and a host keys its running-service table by ID. When omitted in the DSL it // defaults to the derived InvokeID. ID string `json:"id,omitempty"` // Src is the named reference (plus serializable params) to the host-provided // service implementation, bound from the service registry at Provide/Quench // time exactly like a guard or action ref. An unbound Src fails Quench with // the typed *ErrUnboundRef (Kind "service"). Src Ref `json:"src"` // Input is the serializable input passed to the service when it starts, // surfaced on the StartService effect as input. It is data only; // the kernel never inspects it. Input map[string]any `json:"input,omitempty"` // OnDone is the event the host re-fires through Fire when the service // completes successfully; the service result rides along as the StartService // host contract's done payload. It routes the result through an ordinary // transition keyed on this event from the owning state. OnDone E `json:"onDone"` // OnError is the event the host re-fires through Fire when the service fails; // the error rides along as the host contract's error payload. It routes the // failure through an ordinary transition keyed on this event from the owning // state. OnError E `json:"onError"`
// Kind tags this invocation as a host-run service (the default, // ActorKindService) or a child-MACHINE actor (ActorKindMachine). A service // invocation emits StartService / StopService and is driven by a ServiceRunner; // an actor invocation emits SpawnActor / StopActor and is driven by an // ActorSystem that runs the child machine as an actor and routes its done/error // back through the parent. The field serializes, so the distinction round-trips // losslessly through JSON. Kind ActorKind `json:"kind,omitempty"` // SystemID is the optional system-scoped name a child-machine actor registers // under in the ActorSystem (its systemId), so a sibling can address it // by a well-known name. It is meaningful only for an ActorKindMachine // invocation and serializes for lossless round-trip. SystemID string `json:"systemId,omitempty"`}type InvokeOption
Section titled “type InvokeOption”InvokeOption configures a Builder.Invoke declaration.
type InvokeOption func(*invokeConfig)func WithInput
Section titled “func WithInput”func WithInput(input map[string]any) InvokeOptionWithInput sets the serializable input passed to an invoked service when it starts, surfaced as input on the StartService effect.
func WithInvokeID
Section titled “func WithInvokeID”func WithInvokeID(id string) InvokeOptionWithInvokeID sets an explicit, stable id for an invoked service instead of the derived InvokeID. Use it when a host or a Cancel-style coordination needs a known id independent of the invocation’s declaration order.
func WithServiceParams
Section titled “func WithServiceParams”func WithServiceParams(params map[string]any) InvokeOptionWithServiceParams sets the serializable params on an invoked service’s Src ref, available to the bound ServiceFn as ServiceCtx.Params — the per-ref configuration knob, distinct from the per-start Input.
func WithSystemID
Section titled “func WithSystemID”func WithSystemID(id string) InvokeOptionWithSystemID sets the system-scoped name a child-machine actor (InvokeActor) registers under in the ActorSystem (its systemId), so a sibling can address it by a well-known name rather than by ref. It is meaningful only for InvokeActor; on a plain service Invoke it is ignored.
type JournalEntry
Section titled “type JournalEntry”JournalEntry records one external, nondeterministic resolution so a future deterministic replay returns the recorded value rather than re-invoking its source. It is the unit of the reserved Snapshot.Journal.
The recording contract (locked here; the recording/replay runtime is host-side): any result that is NOT a pure function of (current configuration, context, event payload, machine definition) is nondeterministic and MUST be recordable as a JournalEntry so replay returns the recorded value. The nondeterministic sources are the invoked-service OnDone/OnError result payloads, actor message payloads, Clock.Now() reads, and host randomness — each correlated by a stable id reused from the effect that armed it (invocationID / actorInvocationID / scheduleID).
type JournalEntry struct { // Step is the Fire ordinal the result resolved at, indexing the instance's // recorded Traces, so replay applies the recorded value at the right step. Step int `json:"step"` // Kind classifies which nondeterministic source produced the result. Kind JournalKind `json:"kind"` // CorrelationID is the stable id of the source, reused from the arming effect // (invocationID / actorInvocationID / scheduleID), so replay matches the // recorded value to the resolution it stands in for. CorrelationID string `json:"correlationId,omitempty"` // Payload is the structured, JSON result the source produced (a service's // done-output, an actor message), returned verbatim on replay. Payload json.RawMessage `json:"payload,omitempty"` // ClockUnixNano is the recorded Clock.Now() reading (Unix nanoseconds) for a // JournalClockRead entry, returned on replay so time-dependent transitions // resolve identically. ClockUnixNano int64 `json:"clockUnixNano,omitempty"`}type JournalKind
Section titled “type JournalKind”JournalKind classifies a JournalEntry’s recorded nondeterministic result, so a replay routes each recorded value back to the source that produced it.
type JournalKind stringJournalKind values, one per nondeterministic source the replay contract covers.
const ( // JournalServiceResult records an invoked service's OnDone/OnError result // payload, correlated by its invocationID. JournalServiceResult JournalKind = "serviceResult" // JournalActorMessage records an actor message payload, correlated by the // actorInvocationID of the routed actor. JournalActorMessage JournalKind = "actorMessage" // JournalClockRead records a Clock.Now() reading consumed during a step. JournalClockRead JournalKind = "clockRead" // JournalRandom records a host randomness draw consumed during a step. JournalRandom JournalKind = "random")type KindedEffect
Section titled “type KindedEffect”KindedEffect is an effect that reports a stable, serializable discriminant without a Go type assertion. Every kernel-emitted built-in effect implements it, and a host effect opts in by adding a Kind() method, so effects can be journaled, deduped, rendered, and routed across a serialization boundary by kind rather than by Go type. The Effect alias stays free-form (Effect = any) so a domain may still emit bare values; only KindedEffect participates in the envelope round-trip and dispatch-time kind checks.
type KindedEffect interface { // Kind returns the stable string discriminant for this effect. It is part of // the wire contract: two builds must agree on the kind for an effect to route // across a serialization boundary, so a kind is never renamed once shipped. Kind() string}type Literal
Section titled “type Literal”Literal is a typed constant operand in a Core expression: a value tagged with the ParamType vocabulary the palette already uses for ref params, so a single type language spans param schemas, the context schema, and Core literals. It serializes cleanly for the IR round-trip.
type Literal struct { // Type tags the literal's value type, drawn from the ParamType vocabulary // (string/int/float/bool/duration/enum). It drives type-checking against the // ContextSchema and the comparison's coercion rules. Type ParamType `json:"type"` // Value is the literal's value. It is held as the natural Go value for the // type (string, int64, float64, bool, or a duration string) and round-trips // through JSON; a duration is carried as its Go duration string. Value any `json:"value"`}type LoadOption
Section titled “type LoadOption”LoadOption configures LoadFromJSON.
type LoadOption func(*loadConfig)type Machine
Section titled “type Machine”Machine is the immutable, Quenched definition.
type Machine[S comparable, E comparable, C any] struct { // contains filtered or unexported fields}func (*Machine[S, E, C]) Assay
Section titled “func (*Machine[S, E, C]) Assay”func (m *Machine[S, E, C]) Assay(s S, entity C, opts ...AssayOption) errorAssay checks that an externally-constructed entity legally satisfies a state’s declarative requirements, without firing. The default mode is fail-fast (the returned *AssayError carries the first failure); Aggregate collects every failure in one pass. The error type is uniform across modes.
Example
ExampleMachine_Assay checks an externally-built entity against a state’s declarative requirements without firing a transition.
m := buildDocMachine()
missing := m.Assay(Approved, &Document{Status: Approved})ok := m.Assay(Approved, &Document{Status: Approved, ReviewerID: strptr("rev-1")})
fmt.Println("missing reviewer:", missing != nil)fmt.Println("with reviewer:", ok)// Output:// missing reviewer: true// with reviewer: <nil>Output
Section titled “Output”missing reviewer: truewith reviewer: <nil>func (*Machine[S, E, C]) Cast
Section titled “func (*Machine[S, E, C]) Cast”func (m *Machine[S, E, C]) Cast(entity C, opts ...CastOption[S]) *Instance[S, E, C]Cast pours a fresh running instance from the machine, binding it to the given entity. The instance’s starting state is derived from the entity via the machine’s CurrentStateFn; if no CurrentStateFn was declared, an explicit initial state must be supplied via WithInitialState. When both are present, WithInitialState wins. With neither, Cast panics with *ErrNoInitialState — a programmer error, consistent with Quench’s panic-on-misuse posture.
The entity value is held on the Instance and supplied to guards and actions at Fire time; it is never threaded through context.
Example (Hierarchical)
ExampleMachine_Cast_hierarchical enters a hierarchical machine: casting into a compound state descends to its initial child, so the job starts in Starting under the Running superstate.
m := buildJobMachine()job := &Job{Status: Queued}inst := m.Cast(job)res := inst.Fire(context.Background(), Enqueue)
fmt.Println("state:", res.NewState)// Output:// state: StartingOutput
Section titled “Output”state: Startingfunc (*Machine[S, E, C]) Name
Section titled “func (*Machine[S, E, C]) Name”func (m *Machine[S, E, C]) Name() stringName returns the machine name.
func (*Machine[S, E, C]) Palette
Section titled “func (*Machine[S, E, C]) Palette”func (m *Machine[S, E, C]) Palette() []DescriptorPalette returns the discoverable descriptor set of the machine’s registry — every registered guard, action, service, and declared actor behavior — sorted deterministically. It mirrors Registry.Palette for a Quenched machine so a builder API can enumerate the host behavior a loaded machine binds against.
func (*Machine[S, E, C]) PlanPath
Section titled “func (*Machine[S, E, C]) PlanPath”func (m *Machine[S, E, C]) PlanPath(from, to S, entity C, opts ...PlanOption) ([]E, error)PlanPath returns the shortest event sequence that drives an instance from the `from` state to the `to` state, found by breadth-first search over the static transition graph. Guards are honored against the supplied entity, so the returned path is one the entity can actually traverse. The entity is never mutated. ErrNoPath is returned when no sequence connects from->to.
Example
ExampleMachine_PlanPath finds the shortest event sequence that drives a document from Draft to Published, honoring guards against the entity.
m := buildDocMachine()doc := &Document{Status: Draft, ReviewerID: strptr("rev-1")}path, err := m.PlanPath(Draft, Published, doc)
fmt.Println("err:", err)fmt.Println("steps:", len(path))// Output:// err: <nil>// steps: 3Output
Section titled “Output”err: <nil>steps: 3func (*Machine[S, E, C]) Requirements
Section titled “func (*Machine[S, E, C]) Requirements”func (m *Machine[S, E, C]) Requirements(s S) []Requirement[C]Requirements returns the declarative requirements for a state, or nil if the state declares none (or is undeclared).
func (*Machine[S, E, C]) Restore
Section titled “func (*Machine[S, E, C]) Restore”func (m *Machine[S, E, C]) Restore(snap Snapshot[S, E, C], opts ...RestoreOption[S]) (*Instance[S, E, C], error)Restore rebuilds a running Instance from snap, resuming at the snapshot’s configuration, context, and recorded history WITHOUT re-running any entry actions (resume, not re-enter). The restored instance picks up at the persisted snapshot. The snapshot’s Machine must match m’s name, every configuration leaf must be a declared state, and the configuration must be non-empty; a violation returns a typed *SnapshotError. The restored instance is wired to the supplied clock (WithRestoreClock) or SystemClock by default, exactly as Cast wires it.
After Restore, a host that drove timers/services/actors re-arms them by absorbing the instance’s ResumeEffects through the same drivers it uses for Fire — Restore itself fires nothing and performs no IO, so Fire stays pure.
func (*Machine[S, E, C]) Services
Section titled “func (*Machine[S, E, C]) Services”func (m *Machine[S, E, C]) Services() map[string]ServiceFn[C]Services returns the machine’s bound invoked-service palette by name, for a host that constructs a ServiceRunner from the machine’s own registry. The map is a copy; mutating it does not affect the machine.
func (*Machine[S, E, C]) ToDOT
Section titled “func (*Machine[S, E, C]) ToDOT”func (m *Machine[S, E, C]) ToDOT(opts ...VizOption) stringToDOT renders the machine as GraphViz DOT for richer SVG output — slides, docs sites, and large hierarchical machines where Mermaid grows unreadable.
Compound and parallel states become subgraph clusters, final states draw a double border, owners encode as node fillcolor, and the layout defaults to rankdir=LR (well suited to lifecycles).
func (*Machine[S, E, C]) ToJSON
Section titled “func (*Machine[S, E, C]) ToJSON”func (m *Machine[S, E, C]) ToJSON(opts ...ToJSONOption) ([]byte, error)ToJSON serializes the machine’s IR losslessly.
Example
ExampleMachine_ToJSON serializes a machine’s IR and reports that the canonical definition round-trips: loading the JSON and reserializing yields identical bytes.
m := buildDocMachine()data, _ := m.ToJSON()
ir, _ := state.LoadFromJSON[DocState, DocEvent, *Document](data)m2 := ir.Provide(docRegistry()).Quench()data2, _ := m2.ToJSON()
fmt.Println("stable:", string(data) == string(data2))// Output:// stable: trueOutput
Section titled “Output”stable: truefunc (*Machine[S, E, C]) ToMermaid
Section titled “func (*Machine[S, E, C]) ToMermaid”func (m *Machine[S, E, C]) ToMermaid(opts ...VizOption) stringToMermaid renders the machine as a GitHub-renderable Mermaid stateDiagram-v2.
Transitions render as labeled edges (Event, with guards as a bracketed suffix); the initial state is reached from the [*] start marker and final states point back to [*]. Compound states render as nested state blocks and parallel states use the — region divider. Owner tags render as classDef color-coding, since stateDiagram-v2 has no native swim lanes.
Example
ExampleMachine_ToMermaid renders a hierarchical machine as a Mermaid stateDiagram-v2: the initial marker, the Running superstate as a nested block with its own initial child, the cross-cutting Cancel transition, and the final-state markers.
fmt.Println(buildJobMachine().ToMermaid())// Output:// stateDiagram-v2// [*] --> Queued// state Running {// [*] --> Running__Starting// Running__Starting// Running__Executing// Running__Starting --> Running__Executing: Begin// }// JobDone --> [*]// Canceled --> [*]// Queued --> Running: Enqueue// Running --> Canceled: Cancel// Running__Executing --> JobDone: Finish// classDef owner_Scheduler fill:#f0d9ff// classDef owner_Worker fill:#d9f2f2// class Queued owner_Scheduler// class Running owner_WorkerOutput
Section titled “Output”stateDiagram-v2 [*] --> Queued state Running { [*] --> Running__Starting Running__Starting Running__Executing Running__Starting --> Running__Executing: Begin } JobDone --> [*] Canceled --> [*] Queued --> Running: Enqueue Running --> Canceled: Cancel Running__Executing --> JobDone: Finish classDef owner_Scheduler fill:#f0d9ff classDef owner_Worker fill:#d9f2f2 class Queued owner_Scheduler class Running owner_Workertype MessagePhase
Section titled “type MessagePhase”MessagePhase distinguishes the lifecycle point of an InspectMessage event: a message is observed when it is sent, and again when the host delivers it.
type MessagePhase stringconst ( // MessageSent marks a message emitted toward a target actor (a SendTo / // SendParent / Respond / Forward effect being routed). MessageSent MessagePhase = "sent" // MessageDelivered marks a message handed to its target actor's mailbox. MessageDelivered MessagePhase = "delivered")type Middleware
Section titled “type Middleware”Middleware wraps a Fire, outside-in.
type Middleware[S comparable, E comparable, C any] func(next FireFunc[S, E, C]) FireFunc[S, E, C]type MultiRegionErr
Section titled “type MultiRegionErr”MultiRegionErr aggregates the errors raised by more than one orthogonal region firing on a single event. Its Unwrap returns each region’s error so errors.As finds any region’s typed error.
type MultiRegionErr struct { Errors []error}func (*MultiRegionErr) Error
Section titled “func (*MultiRegionErr) Error”func (e *MultiRegionErr) Error() stringfunc (*MultiRegionErr) Unwrap
Section titled “func (*MultiRegionErr) Unwrap”func (e *MultiRegionErr) Unwrap() []errorUnwrap exposes the per-region errors for errors.As / errors.Is traversal.
type Operand
Section titled “type Operand”Operand is a Core comparison operand: either a field-ref or a typed literal. It is produced by Field (via FieldOp), Str/Int/Float/Bool/Dur, or Param, and consumed by the FieldRef comparison methods. The zero Operand is invalid.
type Operand[S comparable] struct { // contains filtered or unexported fields}func Bool
Section titled “func Bool”func Bool[S comparable](v bool) Operand[S]Bool builds a boolean literal operand.
func Dur
Section titled “func Dur”func Dur[S comparable](v time.Duration) Operand[S]Dur builds a duration literal operand, carried as its Go duration string.
func FieldOp
Section titled “func FieldOp”func FieldOp[S comparable](f FieldRef[S]) Operand[S]FieldOp wraps a field-ref as a comparison operand, so a comparison can put a field on either side (e.g. Field(“a”).Lt(FieldOp(Field(“b”)))).
func Float
Section titled “func Float”func Float[S comparable](v float64) Operand[S]Float builds a floating-point literal operand.
func Int
Section titled “func Int”func Int[S comparable](v int64) Operand[S]Int builds an integer literal operand.
func Param
Section titled “func Param”func Param[S comparable](v string) Operand[S]Param builds an enum-typed string literal operand — a named, schema-validated constant such as an order status. It is tagged EnumParam so a comparison against an enum-kinded context field type-checks, while still comparing as a string at evaluation.
func Str
Section titled “func Str”func Str[S comparable](v string) Operand[S]Str builds a string literal operand.
type Outcome
Section titled “type Outcome”Outcome classifies the result recorded in a Trace.
type Outcome intOutcomes recorded in a Trace, one per Fire: success or the specific failure class that stopped the transition. The values are a stable, ordered enumeration — new outcomes are appended, never reordered — so a recorded Trace stays comparable across versions and a consumer may switch on them safely.
const ( // OutcomeSuccess marks a Fire that matched a transition and settled cleanly. OutcomeSuccess Outcome = iota // OutcomeInvalidTransition marks a Fire where no transition matched (current, // event), or every matching transition had a failing guard. OutcomeInvalidTransition // OutcomeGuardFailed marks a Fire stopped because a named guard returned false. OutcomeGuardFailed // OutcomeGuardPanic marks a Fire stopped because a guard panicked and was // recovered. OutcomeGuardPanic // OutcomePolicyDenied marks a Fire stopped because a policy returned Deny. OutcomePolicyDenied // OutcomeEffectError marks a Fire stopped because a bound action returned an // error while emitting its effect. OutcomeEffectError // OutcomeAssignFailed marks a Fire stopped because an assign reducer panicked or // its ref did not resolve, so the context fold could not commit. OutcomeAssignFailed)func (Outcome) String
Section titled “func (Outcome) String”func (o Outcome) String() stringString renders the Outcome as its stable, lower-camel discriminant (“success”, “invalidTransition”, “guardFailed”, …) for logs, the structured- logging seam, and tooling. An unrecognized value renders as “outcome(N)”.
type P
Section titled “type P”P is a convenience alias for serializable params attached to a named Ref.
type P = map[string]anytype ParamSpec
Section titled “type ParamSpec”ParamSpec describes one parameter a ref accepts: its name, type, whether it is required, an optional human description, an optional default value, and — for EnumParam — the allowed values. It JSON-serializes cleanly for transport to a builder UI that renders a form control from it.
type ParamSpec struct { Name string `json:"name"` Type ParamType `json:"type"` Required bool `json:"required,omitempty"` Description string `json:"description,omitempty"` Default any `json:"default,omitempty"` // Enum lists the allowed values when Type is EnumParam; it is empty for every // other type. Enum []string `json:"enum,omitempty"`}type ParamType
Section titled “type ParamType”ParamType is the value type of a single ref parameter, used by a UI to pick the right form control. It is a minimal, stdlib-only set and serializes as its lowercase string so the schema travels cleanly over an API.
type ParamType stringThe parameter types. EnumParam additionally carries its allowed values on the owning ParamSpec via the Describe builder’s EnumParamOf helper.
const ( // StringParam is a free-form string. StringParam ParamType = "string" // IntParam is an integer. IntParam ParamType = "int" // FloatParam is a floating-point number. FloatParam ParamType = "float" // BoolParam is a boolean. BoolParam ParamType = "bool" // DurationParam is a time.Duration, conventionally carried as a Go duration // string (e.g. "1500ms"). DurationParam ParamType = "duration" // EnumParam is a string constrained to an enumerated set; the allowed values // live on the ParamSpec.Enum field. EnumParam ParamType = "enum")type PendingRefs
Section titled “type PendingRefs”PendingRefs is the descriptive inventory of an instance’s live timers, invoked services, and spawned actors at snapshot time, by stable ID. It mirrors what ResumeEffects re-arms; a host can assert on it or display it without replaying effects.
type PendingRefs struct { // Timers are the schedule IDs of the pending delayed (`after`) transitions // armed for the active configuration. Timers []string `json:"timers,omitempty"` // Services are the IDs of the invoked services running for the active // configuration. Services []string `json:"services,omitempty"` // Actors are the IDs of the child-machine actors invoked for the active // configuration. Actors []string `json:"actors,omitempty"`}type PlanOption
Section titled “type PlanOption”PlanOption configures PlanPath.
type PlanOption func(*planConfig)type ProvideOption
Section titled “type ProvideOption”ProvideOption configures Provide.
type ProvideOption func(*provideConfig)type QuenchOption
Section titled “type QuenchOption”QuenchOption configures Quench.
type QuenchOption func(*quenchConfig)func Strict
Section titled “func Strict”func Strict() QuenchOptionStrict makes Quench reject any lint warning, not just hard errors.
type Ref
Section titled “type Ref”Ref is a named reference to a host-provided implementation plus serializable params. The IR carries Refs; the registry binds Name -> func at Provide/Quench time.
Meta is the reserved extension namespace at ref granularity. It is the attachment point for a future polyglot binding descriptor (under the reserved crucible.binding key): absent any descriptor, a ref resolves to an in-process Go registry entry, today’s behavior unchanged. The kernel never inspects Meta; it round-trips verbatim. extra preserves any unknown JSON keys a newer producer emitted so they survive a load -> save cycle (forward-compat).
type Ref struct { Name string `json:"name"` Params map[string]any `json:"params,omitempty"` Meta map[string]any `json:"meta,omitempty"` // contains filtered or unexported fields}func (Ref) MarshalJSON
Section titled “func (Ref) MarshalJSON”func (r Ref) MarshalJSON() ([]byte, error)MarshalJSON encodes a Ref, merging its preserved unknown keys back in with stable key ordering.
func (*Ref) UnmarshalJSON
Section titled “func (*Ref) UnmarshalJSON”func (r *Ref) UnmarshalJSON(data []byte) errorUnmarshalJSON decodes a Ref and captures any unknown keys into extra so they survive re-serialization.
type Region
Section titled “type Region”Region is one orthogonal region of a parallel state: a self-contained set of substates with its own initial child. When the owning parallel state is active, every region is active simultaneously, each tracking its own leaf.
type Region[S comparable, E comparable, C any] struct { Name string `json:"name"` States []State[S, E, C] `json:"states,omitempty"` InitialChild *S `json:"initialChild,omitempty"`}type RegisterEffectOption
Section titled “type RegisterEffectOption”RegisterEffectOption configures a NewEffectRegistry call. New deserialization knobs arrive as new options, never as a signature change.
type RegisterEffectOption func(*EffectRegistry)func RegisterEffect
Section titled “func RegisterEffect”func RegisterEffect(kind string, factory EffectFactory) RegisterEffectOptionRegisterEffect registers a factory for an effect kind so the envelope decoder can route that kind back to a concrete effect. A later registration for the same kind overrides an earlier one (and overrides a built-in), letting a host swap a decoder while the kernel’s pre-registration stays the default.
type Registry
Section titled “type Registry”Registry holds the host behavior palette, by name.
type Registry[C any] struct { // contains filtered or unexported fields}func NewRegistry
Section titled “func NewRegistry”func NewRegistry[C any]() *Registry[C]NewRegistry returns an empty host registry.
func (*Registry[C]) Action
Section titled “func (*Registry[C]) Action”func (r *Registry[C]) Action(name string, fn ActionFn[C], opts ...DescribeOption) *Registry[C]Action registers a named action implementation. An optional Describe option adds palette metadata; registering without one still works and yields a minimal palette descriptor.
func (*Registry[C]) Actor
Section titled “func (*Registry[C]) Actor”func (r *Registry[C]) Actor(name string, opts ...DescribeOption) *Registry[C]Actor declares a named actor behavior in the registry’s palette. Actor behaviors bind at the host ActorSystem (Register), not at the registry, so this records only the palette metadata a builder needs to enumerate and configure the actor — it does not register a runnable behavior. An optional Describe option adds description, parameter schema, and read/write hints; declaring without one yields a minimal palette descriptor with just Kind and Name.
func (*Registry[C]) Assign
Section titled “func (*Registry[C]) Assign”func (r *Registry[C]) Assign(name string, fn AssignFn[C], opts ...DescribeOption) *Registry[C]Assign registers a named assign reducer — the sole context writer. The reducer takes the prior context by value, the triggering event, and the ref’s static params, and returns the next context; the kernel folds the assigns declared on a transition’s exit/transition/entry phases to produce the instance’s context. An optional Describe option adds palette metadata; registering without one still works and yields a minimal palette descriptor.
Naming: the assign verb appears three times with distinct roles. Registry.Assign (here) and its builder alias Builder.Reducer both REGISTER a reducer impl under a name; Builder.Assign WIRES a registered reducer (by name) onto a transition. So you register once (Reducer / Registry.Assign) and wire each use (.Assign(name)).
func (*Registry[C]) BindGuard
Section titled “func (*Registry[C]) BindGuard”func (r *Registry[C]) BindGuard(name string, b GuardBinding[C], opts ...DescribeOption) *Registry[C]BindGuard registers a guard under name from a GuardBinding directly, instead of from a plain GuardFn. It is the additive seam an opt-in expression module uses to register a guard whose verdict comes from a compiled expression program rather than a hand-written Go predicate: the module compiles its source once and hands the resulting evaluator in as the binding.
The binding is wired into the same name path Guard uses, so a guard registered this way is indistinguishable to the kernel from a Go-func guard — it resolves by name at Provide/Quench, evaluates synchronously inside the pure Fire step, and surfaces a panic as the same typed ErrGuardPanic. The binding’s EvalGuard is adapted to a GuardFn over the in-process context view so the fire-time fast path (which reads r.guards) finds it; the binding is also recorded on the parallel binding seam so a future out-of-process transport can swap it under the same name.
EvalGuard is called with a background context and the in-process context view; an error it returns is treated as a false verdict, matching how a Go guard that cannot decide yields false rather than transitioning. An optional Describe option adds palette metadata exactly as Guard does.
func (*Registry[C]) Guard
Section titled “func (*Registry[C]) Guard”func (r *Registry[C]) Guard(name string, fn GuardFn[C], opts ...DescribeOption) *Registry[C]Guard registers a named guard implementation. An optional Describe option adds palette metadata (description, parameter schema, read/write hints); registering without one still works and yields a minimal palette descriptor.
func (*Registry[C]) Palette
Section titled “func (*Registry[C]) Palette”func (r *Registry[C]) Palette() []DescriptorPalette returns a descriptor for every consumer-registered guard, action, service, and actor behavior in the registry, sorted deterministically by kind then name. Entries registered without a Describe descriptor still appear, carrying a minimal descriptor with just Kind and Name. Built-in actions (spawn/cancel/send/raise) and the stateIn guard are language-level, not registered, and are intentionally excluded; BuiltinPalette lists those.
The returned slice is freshly allocated each call and safe for the caller to retain or mutate.
Example
ExampleRegistry_Palette registers a described guard and action, then prints the discoverable palette a visual builder reads to render a form for each ref. The palette is sorted deterministically (by kind, then name) and JSON-serializes cleanly for transport over a builder API.
package main
import ( "encoding/json" "fmt"
"github.com/stablekernel/crucible/state")
// cart is the entity the palette example registers behavior against.type cart struct { amount int}
// ExampleRegistry_Palette registers a described guard and action, then prints the// discoverable palette a visual builder reads to render a form for each ref. The// palette is sorted deterministically (by kind, then name) and JSON-serializes// cleanly for transport over a builder API.func main() { reg := state.NewRegistry[cart]()
reg.Guard("minAmount", func(c state.GuardCtx[cart]) bool { return c.Entity.amount >= 1 }, state.Describe("Passes when the amount is at least min."). Param("min", state.IntParam). OptionalParam("currency", state.StringParam). Reads("Cart"))
reg.Action("charge", func(state.ActionCtx[cart]) (state.Effect, error) { return nil, nil }, state.Describe("Charges the cart through the named gateway."). Param("gateway", state.StringParam). Writes("Cart"))
out, _ := json.MarshalIndent(reg.Palette(), "", " ") fmt.Println(string(out))
}Output
Section titled “Output”[ { "kind": "action", "name": "charge", "description": "Charges the cart through the named gateway.", "params": [ { "name": "gateway", "type": "string", "required": true } ], "writes": [ "Cart" ] }, { "kind": "guard", "name": "minAmount", "description": "Passes when the amount is at least min.", "params": [ { "name": "min", "type": "int", "required": true }, { "name": "currency", "type": "string" } ], "reads": [ "Cart" ] }]func (*Registry[C]) Service
Section titled “func (*Registry[C]) Service”func (r *Registry[C]) Service(name string, fn ServiceFn[C], opts ...DescribeOption) *Registry[C]Service registers a named invoked-service implementation. An invoke’s Src ref binds to it at Provide/Quench time exactly like a guard or action ref; an unbound service ref fails Quench with the typed *ErrUnboundRef (Kind “service”). The runner resolves and runs it when the owning state is entered. An optional Describe option adds palette metadata; registering without one still works and yields a minimal palette descriptor.
type Requirement
Section titled “type Requirement”Requirement is a declarative condition for a state, used by Assay.
type Requirement[C any] struct { Name string Predicate func(C) bool Setter func(C) // optional: mutate a zero entity to satisfy Predicate}type RequirementFailure
Section titled “type RequirementFailure”RequirementFailure records one unmet requirement.
type RequirementFailure struct { Name string Reason string}type RespondToSender
Section titled “type RespondToSender”RespondToSender is the effect the kernel emits for the respond built-in: reply with Event to the sender of the event the emitting actor is currently handling. The kernel cannot know the sender (it is host routing state), so it emits this effect with only the reply Event; the host’s ActorSystem resolves the target from the routing context it recorded when it delivered the current event. When there is no identifiable sender the host treats it as a no-op. This realizes the reply-to-the-event’s-origin semantic.
type RespondToSender struct { // Event is the serializable reply delivered to the current event's sender, // type-erased for the abstract effect surface; an ActorSystem keeps it typed. Event any `json:"event,omitempty"`}func (RespondToSender) Kind
Section titled “func (RespondToSender) Kind”func (RespondToSender) Kind() stringKind reports the respond-to-sender effect discriminant.
type RestoreOption
Section titled “type RestoreOption”RestoreOption configures Machine.Restore.
type RestoreOption[S comparable] func(*restoreConfig[S])func RejectMachineVersionMismatch
Section titled “func RejectMachineVersionMismatch”func RejectMachineVersionMismatch[S comparable]() RestoreOption[S]RejectMachineVersionMismatch makes Restore enforce the machine DEFINITION version strictly: a snapshot whose MachineVersion differs from the target machine’s version is rejected with a typed *SnapshotVersionError instead of the default advisory (accept) posture. Use it when an instance must only resume against the exact machine version it was snapshotted from. The snapshot-format schema version is always validated regardless of this option.
func WithRestoreClock
Section titled “func WithRestoreClock”func WithRestoreClock[S comparable](c Clock) RestoreOption[S]WithRestoreClock wires the time seam a restored instance’s delayed-transition driver reads, mirroring WithClock at Cast. It is consumed only by a Scheduler / host driver, never by the pure Fire step. When omitted, a restored instance defaults to SystemClock().
type ScheduleAfter
Section titled “type ScheduleAfter”ScheduleAfter is the effect the kernel emits when an instance enters a state that declares a delayed (`after`) transition. The host’s runtime is expected to start a timer for Delay and, when it elapses, call Fire with Event. ID is stable per (instance, source state, delayed edge), so a later CancelScheduled with the same ID cancels exactly this timer.
The kernel never starts the timer itself: it emits this as data alongside the transition’s other effects, keeping Fire pure (no clock, no goroutine, no IO).
type ScheduleAfter struct { // ID identifies the pending timer. It is stable across the schedule/cancel // pair for one source state on one instance, so a host keys its timer table // by ID. ID string `json:"id"` // Delay is the wall-clock duration the host should wait before re-firing. Delay time.Duration `json:"delay"` // Event is the delayed event to feed back through Fire when Delay elapses. // It is the transition's On event, type-erased for the abstract effect // surface; a host driver built with NewScheduler keeps it typed. Event any `json:"event,omitempty"` // State names the source state whose entry scheduled this timer, for // diagnostics and host bookkeeping. State string `json:"state,omitempty"`}func (ScheduleAfter) Kind
Section titled “func (ScheduleAfter) Kind”func (ScheduleAfter) Kind() stringKind reports the schedule-after effect discriminant.
type Scheduler
Section titled “type Scheduler”Scheduler is the reusable host-driver that turns the kernel’s ScheduleAfter / CancelScheduled effects into real timers and re-fires delayed events through its instance. It is concurrency-safe. Construct one per instance with NewScheduler; drive it by passing each Fire’s effects to Absorb. With a FakeClock it is fully deterministic — timers fire only when the test advances the clock via FakeClock.Advance.
type Scheduler[S comparable, E comparable, C any] struct { // contains filtered or unexported fields}Example
ExampleScheduler drives a delayed (`after`) transition deterministically with a FakeClock and the reusable Scheduler host-driver. The kernel stays pure: entering “pending” emits a ScheduleAfter effect, the Scheduler arms a timer, and advancing the fake clock past the delay fires the delayed event back through Fire — driving a delayed (after) transition with no real waiting.
package main
import ( "context" "fmt" "time"
"github.com/stablekernel/crucible/state")
func main() { type cart struct{} m := state.Forge[string, string, cart]("checkout"). State("active"). State("pending"). State("expired"). Initial("active"). Transition("active").On("submit").GoTo("pending"). // After 15 minutes in "pending" with no action, the cart expires. Transition("pending").After(15 * time.Minute).On("timeout").GoTo("expired"). State("expired").Final(). Quench()
clk := state.NewFakeClock(time.Unix(0, 0)) inst := m.Cast(cart{}, state.WithInitialState("active"), state.WithClock[string](clk)) sch := state.NewScheduler(inst) ctx := context.Background()
// Entering "pending" emits the ScheduleAfter effect; the host absorbs every // Fire's effects into the Scheduler, which arms the timer. res := inst.Fire(ctx, "submit") sch.Absorb(ctx, res.Effects) fmt.Println("before:", inst.Current(), "pending timers:", sch.Pending())
// Nothing happens until the delay elapses; advancing the fake clock and // ticking the Scheduler fires the delayed "timeout" event. clk.Advance(15 * time.Minute) sch.Tick(ctx) fmt.Println("after: ", inst.Current(), "pending timers:", sch.Pending())}Output
Section titled “Output”before: pending pending timers: 1after: expired pending timers: 0func NewScheduler
Section titled “func NewScheduler”func NewScheduler[S comparable, E comparable, C any](inst *Instance[S, E, C]) *Scheduler[S, E, C]NewScheduler returns a Scheduler driving inst, reading the time seam wired to inst at Cast (WithClock). With a FakeClock the Scheduler is deterministic.
func (*Scheduler[S, E, C]) Absorb
Section titled “func (*Scheduler[S, E, C]) Absorb”func (s *Scheduler[S, E, C]) Absorb(ctx context.Context, effects []Effect)Absorb scans effects, arming a timer for each ScheduleAfter and dropping the timer for each CancelScheduled. It is how a host wires Fire’s output back into the scheduler; call it with the effects of every Fire (including those the Scheduler itself triggers — Fire-on-elapse re-enters Absorb automatically). A ScheduleAfter whose Event is not the instance’s event type is ignored, since the kernel cannot have produced it.
func (*Scheduler[S, E, C]) HasPending
Section titled “func (*Scheduler[S, E, C]) HasPending”func (s *Scheduler[S, E, C]) HasPending(id string) boolHasPending reports whether a timer with the given schedule id is armed.
func (*Scheduler[S, E, C]) Pending
Section titled “func (*Scheduler[S, E, C]) Pending”func (s *Scheduler[S, E, C]) Pending() intPending reports the number of armed (not-yet-fired, not-canceled) timers. A test asserts on it to confirm a timer was scheduled or auto-canceled on exit.
func (*Scheduler[S, E, C]) Tick
Section titled “func (*Scheduler[S, E, C]) Tick”func (s *Scheduler[S, E, C]) Tick(ctx context.Context) []FireResult[S]Tick fires every timer whose due time is at or before the Scheduler clock’s current time, in due-time order (ties broken by id for determinism). Each due timer is removed, then its delayed event is fired through the instance and the resulting effects are absorbed (so a chained `after` arms its successor). It returns the FireResults of the events it fired, in order. With a FakeClock a test calls FakeClock.Advance then Tick (or uses the Advance helper) to drive elapses deterministically; with SystemClock a host calls Tick from its own timer loop.
type SchemaField
Section titled “type SchemaField”SchemaField is one named field of a context object: its name, its type kind, whether it is nullable (a Go pointer or other nilable type), and the kind-specific shape carried on Fields (object), Elem (list element, map value), Key (map key), and Enum (enum values).
type SchemaField struct { // Name is the field's wire name — the JSON-tag name for a SchemaOf-derived // struct field, the Go field name when no JSON tag is present. Name string `json:"name"` // Kind is the field's type category. Kind SchemaKind `json:"kind"` // Nullable reports whether the field may be absent/nil (a Go pointer, or a // natively nilable map/slice). It is informational metadata; the kernel never // enforces it. Nullable bool `json:"nullable,omitempty"`
// Fields carries the nested named fields when Kind is SchemaObject. Fields []SchemaField `json:"fields,omitempty"` // Elem carries the element type when Kind is SchemaList, or the value type when // Kind is SchemaMap. Elem *SchemaField `json:"elem,omitempty"` // Key carries the key type when Kind is SchemaMap. Key *SchemaField `json:"key,omitempty"` // Enum lists the allowed values when Kind is SchemaEnum; it is empty otherwise. Enum []string `json:"enum,omitempty"` // contains filtered or unexported fields}func (SchemaField) MarshalJSON
Section titled “func (SchemaField) MarshalJSON”func (f SchemaField) MarshalJSON() ([]byte, error)MarshalJSON encodes a SchemaField, merging its preserved unknown keys back in with stable key ordering.
func (*SchemaField) UnmarshalJSON
Section titled “func (*SchemaField) UnmarshalJSON”func (f *SchemaField) UnmarshalJSON(data []byte) errorUnmarshalJSON decodes a SchemaField and captures any unknown keys into extra so they survive re-serialization.
type SchemaKind
Section titled “type SchemaKind”SchemaKind names the type category of a context field. The scalar kinds reuse the ParamType vocabulary verbatim (string/int/float/bool/duration, plus the time scalar and enum); the composite kinds — object, list, map — describe structured shapes that ParamType does not cover. It serializes as its lowercase string for a stable, language-neutral wire form.
type SchemaKind stringThe schema kinds. Scalars share their wire string with the matching ParamType so a single vocabulary spans both the param schema and the context schema.
const ( // SchemaString is a free-form string. SchemaString SchemaKind = "string" // SchemaInt is an integer. SchemaInt SchemaKind = "int" // SchemaFloat is a floating-point number. SchemaFloat SchemaKind = "float" // SchemaBool is a boolean. SchemaBool SchemaKind = "bool" // SchemaDuration is a time.Duration, conventionally carried as a Go duration // string (e.g. "1500ms"). SchemaDuration SchemaKind = "duration" // SchemaTime is a time.Time, conventionally carried as an RFC 3339 string. SchemaTime SchemaKind = "time" // SchemaObject is a nested object with named fields, carried on Fields. SchemaObject SchemaKind = "object" // SchemaList is an ordered list whose element type is carried on Elem. SchemaList SchemaKind = "list" // SchemaMap is a keyed map whose key and value types are carried on Key and // Elem. SchemaMap SchemaKind = "map" // SchemaEnum is a string constrained to an enumerated set carried on Enum. SchemaEnum SchemaKind = "enum")type SendOption
Section titled “type SendOption”SendOption configures a Builder.SendTo / Builder.ForwardTo declaration (the actor-communication send built-ins).
type SendOption func(*sendConfig)func WithSendToSystemID
Section titled “func WithSendToSystemID”func WithSendToSystemID(id string) SendOptionWithSendToSystemID addresses the send target by its system-scoped id (the `systemId`) instead of its registry id, so a sibling actor is addressed by a well-known name. When set it takes precedence over the positional target id.
type SendParent
Section titled “type SendParent”SendParent is the effect the kernel emits for the sendParent built-in: a child actor sends Event to its parent. The host’s ActorSystem routes it to the parent instance (the one driving the system). Emitted by a top-level machine with no parent it is a host-side no-op. It routes an event to the actor’s parent.
type SendParent struct { // Event is the serializable event delivered to the parent, type-erased for the // abstract effect surface; an ActorSystem keeps it typed. Event any `json:"event,omitempty"`}func (SendParent) Kind
Section titled “func (SendParent) Kind”func (SendParent) Kind() stringKind reports the send-parent effect discriminant.
type SendTo
Section titled “type SendTo”SendTo is the effect the kernel emits for the sendTo built-in: deliver Event to the actor addressed by TargetID (or SystemID when TargetID is empty). The host’s ActorSystem routes it into that actor’s mailbox; addressing an unknown actor is a no-op. It delivers an event to a named actor.
type SendTo struct { // TargetID is the registry id of the actor to deliver Event to. Empty when the // target is addressed by SystemID instead. TargetID string `json:"targetId,omitempty"` // SystemID is the system-scoped name of the target actor (its systemId), // used when TargetID is empty so a sibling can be addressed by a well-known name. SystemID string `json:"systemId,omitempty"` // Event is the serializable event delivered to the target actor's mailbox, // type-erased for the abstract effect surface; an ActorSystem keeps it typed. Event any `json:"event,omitempty"`}func (SendTo) Kind
Section titled “func (SendTo) Kind”func (SendTo) Kind() stringKind reports the send-to effect discriminant.
type ServiceBinding
Section titled “type ServiceBinding”ServiceBinding runs an invoked service. The in-process binding wraps a ServiceFn; the result is shuttled by the runner through the invocation’s onDone/onError event.
type ServiceBinding[C any] interface { RunService(ctx context.Context, req ServiceRequest[C]) (any, error)}type ServiceCtx
Section titled “type ServiceCtx”ServiceCtx is passed to a bound service at run time. It carries the entity the instance is bound to and the start contract the kernel emitted.
Under a value context type, Entity is a point-in-time snapshot taken when the service is invoked: it does not observe context updates that assigns apply on Fires running while the service is in flight. To act on newer context a service returns data, which the runner routes back through the onDone/onError event so a transition assign folds it — Fire, not the service, owns every context change. (Under a pointer context type the snapshot is a copied pointer to the same value, so a long-running service can observe later mutations through the alias; that is the documented escape-hatch tradeoff.)
type ServiceCtx[C any] struct { Entity C Params map[string]any Input map[string]any}type ServiceFn
Section titled “type ServiceFn”ServiceFn is a host-provided invoked-service implementation, bound by name into a Registry exactly like a guard or action. It receives the entity it is bound to and the StartService effect (Src params, Input) the kernel emitted, and returns its result on success or an error on failure. A one-shot (promise-style) service returns directly; a streaming service is a host-side wrapper that ultimately resolves to a single done/error through this contract. A ServiceFn never mutates the instance; it returns data, and the runner routes that data through the invocation’s onDone / onError event via Fire — so Fire, not the service, owns every state change.
type ServiceFn[C any] func(ctx context.Context, in ServiceCtx[C]) (any, error)type ServiceRequest
Section titled “type ServiceRequest”ServiceRequest is the serializable invocation envelope for an invoked service. The service result is routed back through the kernel’s existing onDone/onError event machinery (the StartService effect), so it needs no result envelope here.
type ServiceRequest[C any] struct { Name string Params map[string]any Input map[string]any}type ServiceRunner
Section titled “type ServiceRunner”ServiceRunner is the reusable host-driver that turns the kernel’s StartService / StopService effects into real service executions and re-fires each result through its instance via the invocation’s onDone / onError event. It is concurrency-safe. Construct one per instance with NewServiceRunner, binding the service registry that resolves Src refs; drive it by passing each Fire’s effects (and the instance’s StartEffects) to Absorb.
In the deterministic form the runner records each started service as pending and settles it only when the test calls SettleDone / SettleError, so invoke machines are exercised with no real IO; a production host instead resolves and runs the bound ServiceFn on its own goroutine and calls SettleDone / SettleError (or the convenience Run) when it finishes.
type ServiceRunner[S comparable, E comparable, C any] struct { // contains filtered or unexported fields}func NewServiceRunner
Section titled “func NewServiceRunner”func NewServiceRunner[S comparable, E comparable, C any](inst *Instance[S, E, C], reg *Registry[C]) *ServiceRunner[S, E, C]NewServiceRunner returns a ServiceRunner driving inst, resolving Src refs against reg’s service palette. reg may be nil for a pure deterministic driver that never resolves a ServiceFn (the test settles services directly by ID).
func (*ServiceRunner[S, E, C]) Absorb
Section titled “func (*ServiceRunner[S, E, C]) Absorb”func (r *ServiceRunner[S, E, C]) Absorb(ctx context.Context, effects []Effect)Absorb scans effects, recording a running service for each StartService and dropping the running service for each StopService (auto-stop-on-exit). It is how a host wires Fire’s output back into the runner; call it with the effects of every Fire (and once with the instance’s StartEffects for the initial state). A StartService whose OnDone/OnError is not the instance’s event type is ignored, since the kernel cannot have produced it.
func (*ServiceRunner[S, E, C]) HasPending
Section titled “func (*ServiceRunner[S, E, C]) HasPending”func (r *ServiceRunner[S, E, C]) HasPending(id string) boolHasPending reports whether a service with the given invoke id is in flight.
func (*ServiceRunner[S, E, C]) LastError
Section titled “func (*ServiceRunner[S, E, C]) LastError”func (r *ServiceRunner[S, E, C]) LastError() errorLastError returns the error the most recently settled service produced, or nil when the last settlement was a success or none has occurred. The host action bound to an onError transition reads it to consume the failure.
func (*ServiceRunner[S, E, C]) LastResult
Section titled “func (*ServiceRunner[S, E, C]) LastResult”func (r *ServiceRunner[S, E, C]) LastResult() (any, bool)LastResult returns the result the most recently settled service produced, and true when that settlement was a success (SettleDone). The host action bound to an onDone transition reads it to consume the service output; it is valid only during the synchronous Fire the settlement triggers. It returns false after a SettleError or before any settlement.
func (*ServiceRunner[S, E, C]) Pending
Section titled “func (*ServiceRunner[S, E, C]) Pending”func (r *ServiceRunner[S, E, C]) Pending() intPending reports the number of in-flight (started, not-yet-settled, not-stopped) services. A test asserts on it to confirm a service was started or auto-stopped on exit.
func (*ServiceRunner[S, E, C]) PendingIDs
Section titled “func (*ServiceRunner[S, E, C]) PendingIDs”func (r *ServiceRunner[S, E, C]) PendingIDs() []stringPendingIDs returns the ids of all in-flight services, sorted, for deterministic host iteration (e.g. running every armed service in a stable order).
func (*ServiceRunner[S, E, C]) Run
Section titled “func (*ServiceRunner[S, E, C]) Run”func (r *ServiceRunner[S, E, C]) Run(ctx context.Context, id string) (FireResult[S], bool)Run resolves and runs the in-flight service id against the bound registry, settling it with the ServiceFn’s result or error. It is the production convenience that couples resolve + run + settle: a host that arms services from Absorb and wants the runner to execute them calls Run(ctx, id) (typically from its own goroutine). It returns the routed FireResult and true, or false when id is not in flight or no registry / ServiceFn resolves it (in which case the service is settled as an error so the machine still routes onError rather than hanging).
func (*ServiceRunner[S, E, C]) SettleDone
Section titled “func (*ServiceRunner[S, E, C]) SettleDone”func (r *ServiceRunner[S, E, C]) SettleDone(ctx context.Context, id string, result any) (FireResult[S], bool)SettleDone completes the in-flight service id successfully: it drops the service and fires its OnDone event (carrying result) through the instance, then absorbs the resulting effects so a chained invoke arms its successor. It returns the FireResult and true, or the zero result and false when id names no in-flight service (already stopped or settled). result is delivered to the onDone transition’s effects through the instance entity by the host’s actions — the kernel routes the event; the action reads the result.
func (*ServiceRunner[S, E, C]) SettleError
Section titled “func (*ServiceRunner[S, E, C]) SettleError”func (r *ServiceRunner[S, E, C]) SettleError(ctx context.Context, id string, err error) (FireResult[S], bool)SettleError fails the in-flight service id: it drops the service and fires its OnError event (carrying err) through the instance, then absorbs the resulting effects. It returns the FireResult and true, or the zero result and false when id names no in-flight service.
type Snapshot
Section titled “type Snapshot”Snapshot is the serializable, deep runtime state of one Instance at a point in time. It captures the active configuration (all active leaves, in declaration order, plus the primary leaf), the recorded per-compound history (shallow and deep), the instance context, the lifecycle status and optional output/error, and the metadata of the pending timers, invoked services, and spawned actors so a host can re-arm them on restore. Child-actor snapshots are carried under Actors when an ActorSystem snapshots the instance’s spawned children recursively.
A Snapshot round-trips losslessly through JSON when the context type C is JSON-marshalable (the default requirement) or a context codec is supplied via WithContextCodec. The machine definition is NOT carried here — restore binds the snapshot back to a live Machine, exactly as Cast binds an entity — so a snapshot stays small and a definition change is detected at restore rather than silently absorbed.
type Snapshot[S comparable, E comparable, C any] struct { // Machine names the machine the snapshot was taken from. Restore rejects a // snapshot whose Machine does not match the target machine with a typed // *SnapshotError, so a snapshot is never restored against the wrong definition. Machine string `json:"machine"`
// Current is the primary (first) active leaf — the back-compatible // "what state am I in?" answer, equal to Configuration[0]. Current S `json:"current"` // Configuration is every currently-active leaf, in declaration order: length 1 // for a flat or single-spine instance, length N when N parallel regions are // active. Restore activates exactly this configuration without re-entering it. Configuration []S `json:"configuration"`
// Context is the instance's bound entity C at snapshot time. With the default // codec it must be JSON-marshalable; with WithContextCodec the supplied codec // owns its encoding. In JSON it is held as a raw message so the snapshot // envelope marshals once and the context decodes through the chosen codec. Context C `json:"-"` // ContextRaw is the JSON (or codec-encoded) form of Context, populated when the // snapshot is marshaled and consumed when it is unmarshaled. It is the wire // form of Context; callers read Context, not ContextRaw. ContextRaw json.RawMessage `json:"context,omitempty"`
// HistoryShallow records each compound's last-active direct child, and // HistoryDeep each compound's last-active leaf configuration, for history // pseudo-state restoration. Both are restored verbatim so a history-targeted // transition after restore behaves identically to before the snapshot. HistoryShallow map[S]S `json:"historyShallow,omitempty"` HistoryDeep map[S][]S `json:"historyDeep,omitempty"`
// Traces is the instance's recorded Fire history, preserved so History() // reports the same ordered traces after restore. Traces []Trace `json:"traces,omitempty"`
// Status is the instance's lifecycle status at snapshot time. Output carries an // instance's completion output (when StatusDone) and Error a settled instance's // failure message (when StatusError); both are optional and host-supplied. Status Status `json:"status"` Output json.RawMessage `json:"output,omitempty"` Error string `json:"error,omitempty"`
// Pending records the IDs/metadata of the timers, invoked services, and spawned // actors that were live for the active configuration, so a host can confirm // what ResumeEffects re-arms. It is descriptive: the authoritative re-arm is the // effect slice ResumeEffects returns, derived from the same configuration. Pending PendingRefs `json:"pending,omitempty"`
// Actors carries the recursively-captured snapshots of the instance's spawned // child actors, keyed by actor id, when an ActorSystem snapshots the instance. // Each entry is an opaque per-child snapshot envelope a matching ActorSystem // restores. It is empty for an instance with no spawned children, or when only // the instance core (not the actor tree) is snapshotted. Actors map[string]json.RawMessage `json:"actors,omitempty"`
// SnapshotVersion is the snapshot-format schema version of this envelope, so the // serialization contract can evolve with explicit, detectable versions. Snapshot // stamps it with CurrentSnapshotVersion; Restore validates it under the lenient // restore-version posture (accept within the current major, reject across a major // mismatch). A zero value is a pre-versioning snapshot and is treated as the // current version on restore. SnapshotVersion int `json:"snapshotVersion,omitempty"` // MachineVersion is the machine DEFINITION version (the IR Version) the snapshot // was taken from, stamped alongside the Machine name so a restored instance // self-identifies which version of the machine it belongs to — the precondition // for live migration. It is advisory by default at restore (recorded, surfaced, // not enforced) so version stamping is non-breaking; RejectMachineVersionMismatch // opts into strict rejection. MachineVersion string `json:"machineVersion,omitempty"` // MachineID is the machine definition id (the IR ID), carried alongside // MachineVersion so a migrator can resolve the source definition unambiguously. MachineID string `json:"machineId,omitempty"`
// Journal is the reserved replay journal: the per-step record of external, // nondeterministic results (invoked-service done-output, actor messages, clock // reads, randomness) so a future deterministic replay returns the recorded value // rather than re-invoking the source. It is empty at this version under the // recording contract documented on JournalEntry; the runtime that populates and // consumes it is host-side. Reserved and optional: it round-trips empty and // populated. Journal []JournalEntry `json:"journal,omitempty"`
// InFlightServices is the reserved slot for invoked services that were started // but not yet resolved at snapshot time (id + input + the OnDone/OnError routing // events), so a future distributed/async resume can re-establish them. Empty at // this version under the quiescence assumption; present so resume never needs a // new field. InFlightServices []InFlightService `json:"inFlightServices,omitempty"` // Mailboxes is the reserved slot for per-actor mailbox backlog (queued but // unprocessed envelopes), keyed by actor id, for a future distributed/async // resume where a node can crash mid-delivery. Empty at this version under the // quiescence assumption (mailboxes are drained at a snapshot point); present so a // backlog never needs a new field. This closes the documented mailbox-loss gap in // the actor-tree snapshot. Mailboxes map[string][]json.RawMessage `json:"mailboxes,omitempty"`}func UnmarshalSnapshot
Section titled “func UnmarshalSnapshot”func UnmarshalSnapshot[S comparable, E comparable, C any](b []byte, opts ...SnapshotCodecOption[C]) (Snapshot[S, E, C], error)UnmarshalSnapshot deserializes a snapshot from JSON, decoding its context through codec (or the default JSON codec when codec is nil). It is the inverse of MarshalSnapshot; for a JSON-marshalable context, json.Unmarshal into a Snapshot works directly via the snapshot’s own UnmarshalJSON.
func WaitFor
Section titled “func WaitFor”func WaitFor[S comparable, E comparable, C any](ctx context.Context, inst *Instance[S, E, C], predicate WaitPredicate[S, E, C], opts ...WaitOption[S, E, C]) (Snapshot[S, E, C], error)WaitFor drives inst until predicate holds over its Snapshot, or until the supplied context is canceled or the wait budget elapses. It returns the matching snapshot on success, or the zero snapshot and a typed *WaitTimeoutError when the budget elapses (or the wrapped context error when ctx is canceled) without the predicate ever holding.
The predicate is checked once immediately, before any advance, so an instance already in the desired state returns at once without driving. When it does not yet hold, WaitFor advances its driver one step at a time and rechecks: by default it ticks a Scheduler over a FakeClock (WithWaitScheduler), advancing the fake clock by a fixed step each iteration so `after` machines progress deterministically; a caller with a different driver supplies WithWaitStep.
With no driver option WaitFor cannot make progress (an undriven instance never changes on its own), so it checks the predicate once and, if unmet, waits out the budget and returns the typed timeout — the correct result for “the instance will never reach this state without being driven”.
WaitFor never reads the wall clock: time is measured by the driver’s clock (the Scheduler’s, a FakeClock in tests), so the whole helper is deterministic under a fake clock.
func (Snapshot[S, E, C]) MarshalJSON
Section titled “func (Snapshot[S, E, C]) MarshalJSON”func (snap Snapshot[S, E, C]) MarshalJSON() ([]byte, error)MarshalJSON serializes the snapshot, encoding its context with the default JSON codec. It is the convenient path for a JSON-marshalable context; a context that needs a custom codec is serialized with MarshalSnapshot(snap, WithContextCodec).
func (*Snapshot[S, E, C]) UnmarshalJSON
Section titled “func (*Snapshot[S, E, C]) UnmarshalJSON”func (snap *Snapshot[S, E, C]) UnmarshalJSON(b []byte) errorUnmarshalJSON deserializes the snapshot, decoding its context with the default JSON codec. The inverse of MarshalJSON.
type SnapshotCodecOption
Section titled “type SnapshotCodecOption”SnapshotCodecOption configures MarshalSnapshot / UnmarshalSnapshot.
type SnapshotCodecOption[C any] func(*snapshotCodecConfig[C])func WithContextCodec
Section titled “func WithContextCodec”func WithContextCodec[C any](codec ContextCodec[C]) SnapshotCodecOption[C]WithContextCodec supplies a custom ContextCodec for a snapshot context that is not directly JSON-marshalable (or needs a bespoke wire form). When omitted, the default codec marshals the context with encoding/json, so the context type must be JSON-marshalable by default. Pass it to MarshalSnapshot / UnmarshalSnapshot to override the default.
type SnapshotError
Section titled “type SnapshotError”SnapshotError is returned by Restore / MarshalSnapshot / UnmarshalSnapshot when an instance snapshot cannot be captured, serialized, or restored: a snapshot whose Machine does not match the target, a configuration leaf that is not a declared state, an empty configuration with an unknown current state, or a context encode/decode failure. Op names the failing operation (“restore” | “marshal” | “unmarshal”), State (when set) names the offending configuration leaf, and Reason carries the detail.
type SnapshotError struct { Op string State string Reason string}func (*SnapshotError) Error
Section titled “func (*SnapshotError) Error”func (e *SnapshotError) Error() stringtype SnapshotVersionError
Section titled “type SnapshotVersionError”SnapshotVersionError is returned by Restore when a snapshot’s version identity is incompatible with the target: a snapshot-format schema version across a major boundary (always rejected, under the lenient restore-version posture), or — only when RejectMachineVersionMismatch is set — a machine definition version that does not match the target machine. Kind discriminates the two (“snapshotFormat” | “machineVersion”); Machine names the target; Got and Want carry the offending and expected versions; Reason carries the detail. It is the typed signal a migrator or host keys version-mismatch handling on.
type SnapshotVersionError struct { Kind string Machine string Got string Want string Reason string}func (*SnapshotVersionError) Error
Section titled “func (*SnapshotVersionError) Error”func (e *SnapshotVersionError) Error() stringtype Snapshotter
Section titled “type Snapshotter”Snapshotter is implemented by an ActorInstance that can capture and reload its own runtime state as JSON, so an ActorSystem can persist it recursively. The actorAdapter (the standard wrapper for a child *Instance) satisfies it; a host’s bespoke ActorInstance may implement it to participate in deep persistence, and an ActorInstance that does not is re-spawned fresh on restore rather than resumed.
type Snapshotter interface { // SnapshotJSON captures the actor's runtime state as JSON. SnapshotJSON() ([]byte, error) // RestoreJSON reloads the actor's runtime state from JSON produced by // SnapshotJSON, resuming the actor in place without re-running entry actions. RestoreJSON([]byte) error}type SpawnActor
Section titled “type SpawnActor”SpawnActor is the effect the kernel emits when an instance enters a state that invokes a child MACHINE actor, or when the built-in spawn action runs. The host’s ActorSystem is expected to create the actor named by Src (resolved to a child machine factory against the system’s actor palette), run it with Input, register it under ID, and — when the child reaches its final state — re-fire OnDone (carrying the child’s output) through the PARENT’s Fire, or on the child’s failure re-fire OnError. ID is stable per (instance, owning state, invoke index) for a static invoke, or carried explicitly for a dynamic spawn, so a later StopActor with the same ID stops exactly this actor.
The kernel never runs the actor itself: it emits this as data alongside the transition’s other effects, keeping Fire pure (no goroutine, no mailbox, no IO).
type SpawnActor struct { // ID identifies the spawned actor. It is stable across the spawn/stop pair for // one owning state on one instance (static invoke) or supplied explicitly (a // dynamic spawn), so a host keys its actor registry by ID. ID string `json:"id"` // Src is the actor ref (name + params) the host resolves against its actor // palette to obtain the child machine to run. Src Ref `json:"src"` // Input is the serializable input passed to the child actor at spawn. It // is data only; the kernel never inspects it. Input map[string]any `json:"input,omitempty"` // OnDone is the event the host re-fires through the PARENT's Fire (carrying the // child's output) when the child actor reaches its final state, type-erased for // the abstract effect surface; an ActorSystem keeps it typed. OnDone any `json:"onDone,omitempty"` // OnError is the event the host re-fires through the PARENT's Fire (carrying the // error) when the child actor fails, type-erased for the abstract effect // surface. OnError any `json:"onError,omitempty"` // State names the owning state whose entry spawned this actor, for diagnostics // and host bookkeeping. Empty for a dynamic spawn emitted from a transition. State string `json:"state,omitempty"` // SystemID is the optional, stable system-scoped identifier the actor registers // under in the ActorSystem (its systemId), so a sibling can address it // by a well-known name rather than by ref. Empty when unset. SystemID string `json:"systemId,omitempty"`}func (SpawnActor) Kind
Section titled “func (SpawnActor) Kind”func (SpawnActor) Kind() stringKind reports the spawn-actor effect discriminant.
type SpawnOption
Section titled “type SpawnOption”SpawnOption configures a Builder.Spawn declaration (the dynamic spawn built-in).
type SpawnOption func(*spawnConfig)func WithSpawnInput
Section titled “func WithSpawnInput”func WithSpawnInput(input map[string]any) SpawnOptionWithSpawnInput sets the serializable input passed to a dynamically spawned actor when it is created, surfaced as input on the SpawnActor effect.
func WithSpawnOnDone
Section titled “func WithSpawnOnDone”func WithSpawnOnDone[E comparable](onDone E) SpawnOptionWithSpawnOnDone sets the event the host re-fires through the parent’s Fire when a dynamically spawned actor reaches its final state, routing the child’s output through an ordinary transition from the spawning state. Omit it for a fire-and-forget spawn whose completion the parent does not observe.
func WithSpawnOnError
Section titled “func WithSpawnOnError”func WithSpawnOnError[E comparable](onError E) SpawnOptionWithSpawnOnError sets the event the host re-fires through the parent’s Fire when a dynamically spawned actor fails, routing the error through an ordinary transition from the spawning state.
func WithSpawnSystemID
Section titled “func WithSpawnSystemID”func WithSpawnSystemID(id string) SpawnOptionWithSpawnSystemID sets the system-scoped name a dynamically spawned actor registers under in the ActorSystem (its systemId).
type StartService
Section titled “type StartService”StartService is the effect the kernel emits when an instance enters a state that declares an invoked service. The host is expected to run the service named by Src with Input and, on completion, re-fire OnDone with the result through Fire, or on failure re-fire OnError with the error. ID is stable per (instance, owning state, invoke index), so a later StopService with the same ID stops exactly this service.
The kernel never runs the service itself: it emits this as data alongside the transition’s other effects, keeping Fire pure (no goroutine, no IO).
type StartService struct { // ID identifies the running service. It is stable across the start/stop pair // for one owning state on one instance, so a host keys its service table by ID. ID string `json:"id"` // Src is the service ref (name + params) the host resolves against its service // registry to obtain the implementation to run. Src Ref `json:"src"` // Input is the serializable input passed to the service at start. Input map[string]any `json:"input,omitempty"` // OnDone is the event the host re-fires (with the service result) when the // service completes successfully, type-erased for the abstract effect surface; // a host driver built with NewServiceRunner keeps it typed. OnDone any `json:"onDone,omitempty"` // OnError is the event the host re-fires (with the error) when the service // fails, type-erased for the abstract effect surface. OnError any `json:"onError,omitempty"` // State names the owning state whose entry started this service, for // diagnostics and host bookkeeping. State string `json:"state,omitempty"`}func (StartService) Kind
Section titled “func (StartService) Kind”func (StartService) Kind() stringKind reports the start-service effect discriminant.
type State
Section titled “type State”State is a node in the machine graph.
A state is one of three shapes: a leaf (no Children, no Regions), a compound (hierarchical) state declaring Children plus an InitialChild, or a parallel state declaring Regions. A state is never both compound and parallel.
type State[S comparable, E comparable, C any] struct { Name S `json:"name"` OwnedBy string `json:"ownedBy,omitempty"` Transitions []Transition[S, E, C] `json:"transitions,omitempty"`
OnEntry []Ref `json:"onEntry,omitempty"` OnExit []Ref `json:"onExit,omitempty"` IsFinal bool `json:"isFinal,omitempty"` OnDone []Ref `json:"onDone,omitempty"`
// OnEntryAssign and OnExitAssign list the context-reducer refs folded on this // state's entry and exit respectively — the assign siblings of OnEntry/OnExit. // Exit assigns fold before transition assigns; entry assigns fold after, each // seeing the prior result. Both serialize and round-trip losslessly through JSON. OnEntryAssign []Ref `json:"onEntryAssign,omitempty"` OnExitAssign []Ref `json:"onExitAssign,omitempty"`
// Hierarchy. Children holds the nested substates of a compound state, and // InitialChild names the substate entered transitively when the compound // state is entered. Both serialize, so the hierarchy round-trips through // JSON. Parent is a runtime-only back-pointer rebuilt after Quench/Provide. Children []State[S, E, C] `json:"children,omitempty"` InitialChild *S `json:"initialChild,omitempty"`
// Regions holds the orthogonal regions of a parallel state. Mutually // exclusive with Children/InitialChild. Regions []Region[S, E, C] `json:"regions,omitempty"`
// History. HistoryType marks this node as a history pseudo-state (shallow or // deep) belonging to its parent compound; HistoryNone (the default) is an // ordinary state. HistoryDefault names the target entered when the owning // compound has no recorded history yet; nil falls back to the compound's // InitialChild. Both serialize, so history pseudo-states round-trip through // JSON; the per-instance recorded configuration is runtime state, not IR. HistoryType HistoryType `json:"historyType,omitempty"` HistoryDefault *S `json:"historyDefault,omitempty"`
// Invoke declares the services invoked while this state is active (the // `invoke`). Entering the state emits a StartService effect per invocation; // exiting it before a service completes emits a StopService effect // (auto-stop-on-exit). Each invocation routes its result through OnDone and its // error through OnError. The whole block serializes, so it round-trips // losslessly through JSON. A host's ServiceRunner runs the services and re-fires // onDone/onError through Fire, keeping Fire pure. Invoke []Invocation[S, E, C] `json:"invoke,omitempty"` // Parent is a runtime-only back-pointer to the compound state owning this node, // rebuilt after Quench/Provide; it never serializes. An ActorKindMachine entry // in Invoke marks a child-machine actor whose lifecycle the host ActorSystem // drives (the actor model); the per-instance actor mailboxes live on the host // ActorSystem, not on this definition. Parent *State[S, E, C] `json:"-"`
// Meta is the reserved extension namespace at state (node) granularity: studio // layout, documentation strings, tags, and codegen hints live here. The kernel // never inspects it; it round-trips verbatim. Meta map[string]any `json:"meta,omitempty"` // contains filtered or unexported fields}func (State[S, E, C]) MarshalJSON
Section titled “func (State[S, E, C]) MarshalJSON”func (s State[S, E, C]) MarshalJSON() ([]byte, error)MarshalJSON encodes a State, merging its preserved unknown keys back in with stable key ordering.
func (*State[S, E, C]) UnmarshalJSON
Section titled “func (*State[S, E, C]) UnmarshalJSON”func (s *State[S, E, C]) UnmarshalJSON(data []byte) errorUnmarshalJSON decodes a State and captures any unknown keys into extra so they survive re-serialization.
type Status
Section titled “type Status”Status classifies a snapshotted instance’s lifecycle. It mirrors the runtime status. StatusRunning is an instance still advancing; StatusDone is an instance whose active configuration is entirely final (every active leaf is a final state); StatusError is an instance the host settled as failed, carrying the error message on the snapshot.
type Status intInstance lifecycle statuses recorded on a Snapshot.
const ( // StatusRunning is the default: the instance has not reached completion. StatusRunning Status = iota // StatusDone marks an instance whose whole active configuration is final. StatusDone // StatusError marks an instance the host explicitly failed; Snapshot.Error // carries the message. StatusError)func (Status) String
Section titled “func (Status) String”func (s Status) String() stringString renders a Status for diagnostics and stable JSON.
type StopActor
Section titled “type StopActor”StopActor is the effect the kernel emits when an instance exits a state that had a running child-machine actor (auto-stop-on-exit), or when the built-in stop action runs. The host’s ActorSystem stops the actor registered under ID (and, transitively, that actor’s own children); stopping an unknown ID is a no-op. A state’s invoked actors are auto-stopped when the state is exited before they complete.
type StopActor struct { // ID identifies the actor to stop. It matches the ID of the SpawnActor that // began it (auto-stop-on-exit), or an ID supplied to the stop built-in. ID string `json:"id"`}func (StopActor) Kind
Section titled “func (StopActor) Kind”func (StopActor) Kind() stringKind reports the stop-actor effect discriminant.
type StopService
Section titled “type StopService”StopService is the effect the kernel emits when an instance exits a state that had an in-flight invoked service. The host stops the service registered under ID; stopping an unknown ID is a no-op. A state’s invoked services are auto-stopped when the state is exited before they complete.
type StopService struct { // ID identifies the service to stop. It matches the ID of the StartService // that began it (auto-stop-on-exit). ID string `json:"id"`}func (StopService) Kind
Section titled “func (StopService) Kind”func (StopService) Kind() stringKind reports the stop-service effect discriminant.
type TemperOption
Section titled “type TemperOption”TemperOption configures Temper.
type TemperOption func(*temperConfig)type ToJSONOption
Section titled “type ToJSONOption”ToJSONOption configures ToJSON.
type ToJSONOption func(*toJSONConfig)func WithoutSrcPos
Section titled “func WithoutSrcPos”func WithoutSrcPos() ToJSONOptionWithoutSrcPos omits the diagnostic source-position fields (srcFile/srcLine) from the serialized IR. Source positions are captured from the builder via runtime.Caller, so they carry the absolute filesystem path of the worktree that authored the machine — which makes them non-portable across checkouts. They are diagnostic-only metadata (“defined at machine.go:84” tooltips) and have no effect on loading or behavior, so stripping them yields a stable, position-independent serialization. Use it for committed goldens and any interchange that must be byte-identical regardless of where it was generated.
type Trace
Section titled “type Trace”Trace is the kernel’s canonical observability surface — pure data recorded on every Fire and surfaced live on an InspectTransition event. Consumers pattern- match and serialize it, so its field NAMES and JSON tags are stable: fields are added, never renamed or repurposed, and the per-step slices are always in emission order (the order frozen by the determinism contract; see the package overview). A field that does not apply to a given Fire is left zero/empty.
type Trace struct { // Machine names the machine the traced instance was cast from. Machine string `json:"machine,omitempty"` // Event is the human-readable label of the event that drove this Fire — the // event's string rendering — kept for diagnostics, visualization, and the // pinned emission-ordering goldens. Event string `json:"event,omitempty"` // EventPayload is the structured, JSON-serializable form of the event value // that drove this Fire, recorded so a future deterministic replay can // reconstruct the exact event rather than re-parse its label. It is the // load-bearing journal companion to Event: Event stays the human label, // EventPayload carries the machine-readable value. It is omitted when the event // has no JSON form (e.g. an internal "always"/raise microstep marker), so the // field is additive and the trace stays deterministic across a JSON round-trip. EventPayload json.RawMessage `json:"eventPayload,omitempty"` // FromState is the primary active leaf the event was fired in, before the step. FromState string `json:"fromState,omitempty"` // SelectedTransition is the transition that fired, for in-process tooling. It is // not serialized (json:"-") because behavior is bound, not embedded in the IR; // the serializable record of what happened is the other fields. SelectedTransition *Transition[any, any, any] `json:"-"` // GuardsEvaluated names each guard the step evaluated, in evaluation order. GuardsEvaluated []string `json:"guardsEvaluated,omitempty"` // PoliciesEvaluated names each policy the step evaluated, in evaluation order. PoliciesEvaluated []string `json:"policiesEvaluated,omitempty"` // EffectsEmitted names each effect the step emitted, in emission order — the // human-readable companion to FireResult.Effects (the effect data itself). EffectsEmitted []string `json:"effectsEmitted,omitempty"` // AssignsApplied names each assign reducer the step folded, in fold order. AssignsApplied []string `json:"assignsApplied,omitempty"` // Microsteps records the run-to-completion interleave — each raised internal // event and eventless ("always") step, plus per-region markers — in the order it // occurred within the macrostep. Microsteps []string `json:"microsteps,omitempty"`
// MatchedAt names the state whose transition actually fired. For a flat // machine it equals FromState; for an HSM it may be an ancestor reached by // the child-first bubble. MatchedAt string `json:"matchedAt,omitempty"` // ExitedStates and EnteredStates record the transition's exit/entry cascade // in execution order (exit innermost-first, entry outermost-first). ExitedStates []string `json:"exitedStates,omitempty"` EnteredStates []string `json:"enteredStates,omitempty"`
// Outcome classifies how the Fire settled — success or the specific failure // class that stopped it. It is always set (OutcomeSuccess on a clean step). Outcome Outcome `json:"outcome"`}type Transition
Section titled “type Transition”Transition is a directed edge.
type Transition[S comparable, E comparable, C any] struct { From S `json:"from"` To S `json:"to"` On E `json:"on"`
Guards []Ref `json:"guards,omitempty"` Effects []Ref `json:"effects,omitempty"` WaitMode WaitMode `json:"waitMode,omitempty"`
// Assigns lists the context-reducer refs run when this transition fires, folded // after the transition's effects in declaration order. Each assign sees the // context as folded by the assigns preceding it; the result becomes the // instance's context. Assigns are structurally distinct from Effects (the // assigner-vs-effector discriminator) so the cascade runs them in distinct // phases. The slice serializes and round-trips losslessly through JSON. Assigns []Ref `json:"assigns,omitempty"`
// GuardExpr is an optional composite guard: a serializable boolean // expression tree over named-ref leaves, the stateIn built-in, and the // and/or/not combinators. When set it is evaluated in // addition to every Ref in Guards — the transition is enabled only when both // the plain guards and the expression pass — so the common single-guard case // stays the plain Guards slice and composition is purely additive. The tree // serializes and round-trips losslessly through JSON. GuardExpr *GuardNode[S] `json:"guardExpr,omitempty"`
Internal bool `json:"internal,omitempty"` EventLess bool `json:"eventLess,omitempty"` After *time.Duration `json:"after,omitempty"`
// Wildcard marks a catch-all transition: it matches any event that no // specific-event transition of the same state handles. Wildcard transitions // are the lowest-priority candidates in a state, tried only after every // On-keyed match fails, and resolution still bubbles to ancestors when no // wildcard fires. On is ignored when Wildcard is set. This is the // `on: { '*': ... }`. Wildcard bool `json:"wildcard,omitempty"`
// Forbidden marks an event as explicitly blocked at this state: the event is // consumed and ignored, and — unlike "no handler declared" — it does NOT // bubble to ancestor states. To has no meaning for a forbidden transition. // This is a forbidden transition: the event is consumed and ignored. Forbidden bool `json:"forbidden,omitempty"`
// Reenter makes a transition external. By default (v5 semantics) a transition // whose target is the source itself or an ancestor of the source is internal: // its effects run but the source is not exited and re-entered. Setting Reenter // forces the external form, running the full exit/entry cascade of the target. // For an unrelated target (an ordinary state change) the cascade always runs; // Reenter only changes the self/ancestor case. This is the // `reenter: true`. Reenter bool `json:"reenter,omitempty"`
// Raise lists internal events this transition enqueues. They are appended to // the macrostep's internal queue after the transition's own effects run, and // drained by Fire's run-to-completion loop within the SAME macrostep — before // Fire returns and before any externally-sent event. This is the // `raise(...)`. The queue is local to the macrostep, so Fire stays pure. Raise []E `json:"raise,omitempty"`
SrcFile string `json:"srcFile,omitempty"` SrcLine int `json:"srcLine,omitempty"`
// Meta is the reserved extension namespace at transition (edge) granularity: // edge layout, documentation, and codegen hints live here. The kernel never // inspects it; it round-trips verbatim. Meta map[string]any `json:"meta,omitempty"` // contains filtered or unexported fields}func (Transition[S, E, C]) MarshalJSON
Section titled “func (Transition[S, E, C]) MarshalJSON”func (t Transition[S, E, C]) MarshalJSON() ([]byte, error)MarshalJSON encodes a Transition, merging its preserved unknown keys back in with stable key ordering.
func (*Transition[S, E, C]) UnmarshalJSON
Section titled “func (*Transition[S, E, C]) UnmarshalJSON”func (t *Transition[S, E, C]) UnmarshalJSON(data []byte) errorUnmarshalJSON decodes a Transition and captures any unknown keys into extra so they survive re-serialization.
type UnknownEffect
Section titled “type UnknownEffect”UnknownEffect is the preserved form of an effect whose kind the local registry does not recognize. It carries the original kind and payload verbatim so an unknown effect survives a load -> save cycle byte-for-byte (forward-compat, per the closed-enum extension policy). It implements KindedEffect, so it can be re-marshaled, but it is never dispatchable — EffectRegistry.Dispatchable rejects it with a typed *ErrUnknownEffectKind. The kernel never produces an UnknownEffect; only deserialization of a foreign envelope yields one.
type UnknownEffect struct { // EffectKind is the unrecognized discriminant, preserved verbatim. EffectKind string // Payload is the original effect body, preserved verbatim for re-emission. Payload json.RawMessage // Meta is the preserved extension namespace from the source envelope. Meta map[string]any}func (UnknownEffect) Kind
Section titled “func (UnknownEffect) Kind”func (u UnknownEffect) Kind() stringKind reports the preserved, unrecognized discriminant.
type VizOption
Section titled “type VizOption”VizOption configures the ToMermaid and ToDOT renderers.
type VizOption func(*vizConfig)func LeftToRight
Section titled “func LeftToRight”func LeftToRight() VizOptionLeftToRight lays the diagram out left-to-right (Mermaid direction LR, DOT rankdir=LR).
func TopToBottom
Section titled “func TopToBottom”func TopToBottom() VizOptionTopToBottom lays the diagram out top-to-bottom (Mermaid default, DOT rankdir=TB).
func WithoutGuards
Section titled “func WithoutGuards”func WithoutGuards() VizOptionWithoutGuards omits the bracketed guard annotations from transition labels.
func WithoutOwners
Section titled “func WithoutOwners”func WithoutOwners() VizOptionWithoutOwners omits owner color-coding (Mermaid classDef / DOT fillcolor).
type WaitMode
Section titled “type WaitMode”WaitMode tags a transition’s synchronization expectation. The kernel only stores the tag; the consumer acts on it.
type WaitMode intWait modes. SyncReply awaits a reply, FireAndForget emits and moves on, and ValidatePoll signals the consumer to poll the entity (re-running Assay) until it validates.
const ( SyncReply WaitMode = iota FireAndForget ValidatePoll)type WaitOption
Section titled “type WaitOption”WaitOption configures WaitFor.
type WaitOption[S comparable, E comparable, C any] func(*waitConfig[S, E, C])func WithWaitScheduler
Section titled “func WithWaitScheduler”func WithWaitScheduler[S comparable, E comparable, C any](sch *Scheduler[S, E, C]) WaitOption[S, E, C]WithWaitScheduler drives the wait by advancing the FakeClock the Scheduler reads and ticking it each iteration, so `after`-driven transitions fire and the instance progresses toward the predicate deterministically. It is the common driver for delayed-transition machines: cast with WithClock(fakeClock), build a Scheduler, then WaitFor(ctx, inst, pred, WithWaitScheduler(sch)). The Scheduler’s clock must be the instance’s clock (it is, by construction of NewScheduler).
func WithWaitStep
Section titled “func WithWaitStep”func WithWaitStep[S comparable, E comparable, C any](d time.Duration) WaitOption[S, E, C]WithWaitStep sets the per-iteration advance increment WaitFor applies to the driver’s clock between predicate checks. A smaller step lands closer to the exact instant a delayed transition becomes due; a larger step polls less often.
func WithWaitStepFunc
Section titled “func WithWaitStepFunc”func WithWaitStepFunc[S comparable, E comparable, C any](advance func(ctx context.Context, clock Clock, step time.Duration)) WaitOption[S, E, C]WithWaitStepFunc supplies a custom driver advance: a function WaitFor calls each iteration to move time forward and fire any due work (e.g. a ServiceRunner a test settles, or a bespoke host loop). The function should advance the supplied clock by step when it is a FakeClock so the wait budget is consumed deterministically.
func WithWaitTimeout
Section titled “func WithWaitTimeout”func WithWaitTimeout[S comparable, E comparable, C any](d time.Duration) WaitOption[S, E, C]WithWaitTimeout sets the wait budget, measured on the instance’s clock. When the budget elapses before the predicate holds, WaitFor returns a *WaitTimeoutError.
type WaitPredicate
Section titled “type WaitPredicate”WaitPredicate is the condition WaitFor waits to become true. It is evaluated against the instance’s live Snapshot after each advance (and once before any advance). It must be a pure read of the snapshot — WaitFor never mutates the instance on the predicate’s behalf.
type WaitPredicate[S comparable, E comparable, C any] func(snap Snapshot[S, E, C]) boolfunc WaitDone
Section titled “func WaitDone”func WaitDone[S comparable, E comparable, C any]() WaitPredicate[S, E, C]WaitDone returns a WaitPredicate that holds when the instance has reached completion (its whole active configuration is final), mirroring `waitFor(actor, (s) => s.status === ‘done’)`.
func WaitInState
Section titled “func WaitInState”func WaitInState[S comparable, E comparable, C any](target S) WaitPredicate[S, E, C]WaitInState returns a WaitPredicate that holds when the instance’s primary active leaf equals target — the common “wait until it reaches state X” case (waiting until the instance’s snapshot satisfies a predicate).
type WaitTimeoutError
Section titled “type WaitTimeoutError”WaitTimeoutError is returned by WaitFor when its wait budget elapses (measured on the instance’s clock) before the predicate ever held — the typed timeout returned when a WaitFor budget elapses. Machine names the instance’s machine, Timeout the budget that elapsed, and Last the primary active leaf the instance was in when the wait gave up, for diagnostics.
type WaitTimeoutError struct { Machine string Timeout time.Duration Last string}func (*WaitTimeoutError) Error
Section titled “func (*WaitTimeoutError) Error”func (e *WaitTimeoutError) Error() stringGenerated by gomarkdoc