Skip to content

Seamster extraction and Graph.Validate race fix

Choose a tag to compare

@bw19 bw19 released this 07 Jul 03:44
· 520 commits to main since this release

This release fixes a concurrency bug in graph validation and moves the engine's test-only instrumentation into a
standalone module. Both live in one commit (0c113c3).

Fixed

  • Data race in workflow.Graph.Validate. Validate() built the fan-out to fan-in convergence map as a side
    effect stored on the graph. Because a graph is loaded once and shared across concurrent Create calls, two
    validations (or a validation racing a MarshalJSON) wrote and read that field at the same time, which the race
    detector flagged and which could corrupt the map. Validate() is now pure over the graph's immutable definition
    and stores nothing on the graph.

Changed

  • Fan-in routing is now derived at dispatch, not persisted. The fan-out to fan-in map is a pure function of the
    graph structure, so the engine computes it per flow from the frozen graph definition (new internal package
    internal/faninmap) and caches it beside the parsed graph. It is no longer written into the stored graph JSON, so
    a workflow graph carries only its author-defined shape over the wire.

  • Test seams extracted to the seamster module. The fault-injection and checkpoint instrumentation the engine
    uses in white-box tests now lives in github.com/microbus-io/seamster v0.1.0 (a new, standard-library-only
    dependency). The engine embeds one seamster.Seamster as e.seams and consults it directly
    (IsFault, Checkpoint, Inject, Wait, Break, Resume). The seam is inert in production by construction.
    engine/debug.go is renamed engine/seams.go and reduced to the engine's fault and checkpoint names plus
    deliverFailureLost, the one consult that needs a database read.

API changes

  • Removed workflow.Graph.FanInFor and the fanOutToFanIn JSON field. Graph now exposes only its definition
    and validation. The fan-in lookup that FanInFor provided is an engine concern, served by internal/faninmap.
    A pre-existing graph JSON that still carries a fanOutToFanIn field decodes cleanly (the field is ignored), and
    the routing is recomputed from the structure, so persisted flows keep working.

    This removes an exported method, so downstream code calling graph.FanInFor(...) will not compile. If that
    matters for the version line, consider tagging v0.10.0 instead of v0.9.5.

Docs

  • Updated engine/CLAUDE.md and docs/graphs.md to describe pure validation and the dispatch-time fan-in map.

Upgrade notes

  • No schema migration. No behavior change for existing flows.
  • go get github.com/microbus-io/dwarf@v0.9.5 pulls in seamster v0.1.0 automatically.