v0.9.5 — Seamster Extraction and Graph.Validate Race Fix

Released 2026-07-07. GitHub 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.