Skip to content

Commit 9f5e318

Browse files
committed
refactor: name error types with the idiomatic *Error suffix
Signed-off-by: Joshua Temple <joshua.temple@stablekernel.com>
1 parent 39c2b9e commit 9f5e318

36 files changed

Lines changed: 211 additions & 195 deletions

source/statemachine/drive.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,15 @@ func emitEffects(ctx context.Context, sink Sink, effects []state.Effect) error {
167167
// errors.Is(err, source.ErrInvalidForState); otherwise it reports false and the
168168
// caller treats the error as transient.
169169
func classifyFire[K comparable, E any](err error, event E, from K) (*source.GuardRejection, bool) {
170-
var invalid *state.ErrInvalidTransition
170+
var invalid *state.InvalidTransitionError
171171
if errors.As(err, &invalid) {
172172
return &source.GuardRejection{
173173
Event: fmt.Sprint(event),
174174
State: invalid.From,
175175
Err: err,
176176
}, true
177177
}
178-
var guard *state.ErrGuardFailed
178+
var guard *state.GuardFailedError
179179
if errors.As(err, &guard) {
180180
return &source.GuardRejection{
181181
Event: fmt.Sprint(event),

source/statemachine/drivefunc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
// flows back to the binding.
1919
//
2020
// A nil error and a result whose Err is nil is a successful transition; a result
21-
// carrying a [state.ErrInvalidTransition] or [state.ErrGuardFailed] is the
21+
// carrying a [state.InvalidTransitionError] or [state.GuardFailedError] is the
2222
// state-aware rejection. Returning a non-nil error (not the FireResult.Err) is a
2323
// transient failure resolving the instance — a nak.
2424
type FireFunc[K comparable, E comparable] func(ctx context.Context, key K, event E) (state.FireResult[K], error)

state/CHANGELOG.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ representative hot-path numbers.
2929
preserves unknown fields on nested nodes (machine, state, transition, `Ref`,
3030
`GuardNode`) so a document written by a newer build round-trips losslessly
3131
through an older one, and rejects only a higher *major* schema version (the
32-
typed `*ErrUnsupportedSchema`). IR encoding is deterministic (stable key order)
32+
typed `*UnsupportedSchemaError`). IR encoding is deterministic (stable key order)
3333
so a definition hashes and diffs reproducibly.
3434
- Closed-enum extension policy. Every IR enum that may grow (guard op, state
3535
kind, param type, descriptor kind, effect kind) has a documented
@@ -96,7 +96,7 @@ representative hot-path numbers.
9696
on `NewEffectRegistry`) decodes it back to a concrete effect. Per the
9797
closed-enum extension policy, an unrecognized effect kind is preserved verbatim
9898
on load (surfaced as `UnknownEffect`) and rejected only at dispatch
99-
(`EffectRegistry.Dispatchable` returns the typed `*ErrUnknownEffectKind`), never
99+
(`EffectRegistry.Dispatchable` returns the typed `*UnknownEffectKindError`), never
100100
silently dropped or applied. Effects remain data the host applies; the kernel
101101
does not execute them. The `Effect` alias stays `any`, so bare domain effects
102102
are unaffected.
@@ -220,7 +220,7 @@ representative hot-path numbers.
220220
- **Declarative invoke + service registry.** A state declares
221221
`Invocation{ID, Src, Input, OnDone, OnError}`; service implementations bind by
222222
name through `Registry.Service` / `Builder.Service`, parallel to guards and
223-
actions. An unbound service ref fails `Quench` with the typed `*ErrUnboundRef`
223+
actions. An unbound service ref fails `Quench` with the typed `*UnboundRefError`
224224
(`Kind: "service"`), consistent with unbound guards/actions. Authored via the
225225
DSL `Invoke(src, ...InvokeOption)` whose outcomes are options —
226226
`WithInvokeOnDone` / `WithInvokeOnError` — alongside `WithInput`,
@@ -333,8 +333,8 @@ representative hot-path numbers.
333333
(e.g. `And(Or(g1, g2), Not(g3))`). Evaluation short-circuits exactly like a
334334
plain multi-guard transition: `And` stops at the first false, `Or` at the
335335
first true. A failing composite reports the failing leaf(s) when cheap, else
336-
the composite, preserving the typed `ErrGuardFailed`; a leaf panic still
337-
surfaces as `ErrGuardPanic`.
336+
the composite, preserving the typed `GuardFailedError`; a leaf panic still
337+
surfaces as `GuardPanicError`.
338338
- **`stateIn(state)`.** A first-class, config-aware built-in guard, true when
339339
the instance's active configuration includes the named state (its active
340340
leaves and their ancestor spine), so it is correct for atomic, compound, and
@@ -370,7 +370,7 @@ representative hot-path numbers.
370370
transitions until the configuration is stable, recording each as a Trace
371371
microstep. The internal queue is macrostep-local, so `Fire` stays pure. An
372372
unhandled raised event is ignored; a non-settling raise/eventless cycle fails fast
373-
with the typed `ErrMicrostepOverflow`.
373+
with the typed `MicrostepOverflowError`.
374374
- DSL also gains `Always()` to author eventless transitions directly (previously
375375
IR-only). The wildcard target, forbidden marker, reenter flag, and raised-event
376376
list serialize in the IR and round-trip losslessly through JSON; `raise` is
@@ -460,6 +460,22 @@ representative hot-path numbers.
460460
the service settled and an empty slice when the id is not in flight. A caller
461461
that checked the old `ok` bool now checks `len(results) > 0` and reads
462462
`results[0]` for the routed result.
463+
- **BREAKING: the struct error types are renamed from the `Err*` prefix to the
464+
idiomatic `*Error` suffix.** The `Err*` prefix is the Go convention for sentinel
465+
error values, not for struct types a caller inspects with `errors.As`; these are
466+
all struct types with no sentinel vars. `ErrInvalidTransition`, `ErrGuardFailed`,
467+
`ErrGuardPanic`, `ErrAssignPanic`, `ErrPolicyDenied`, `ErrUndeclaredState`,
468+
`ErrUnboundRef`, `ErrActionFailed`, `ErrMicrostepOverflow`, `ErrNoPath`,
469+
`ErrNoInitialState`, `ErrUnknownBuiltin`, `ErrUnboundActor`, `ErrUnsupportedSchema`,
470+
`ErrUnknownEffectKind`, and `MultiRegionErr` become `InvalidTransitionError`,
471+
`GuardFailedError`, `GuardPanicError`, `AssignPanicError`, `PolicyDeniedError`,
472+
`UndeclaredStateError`, `UnboundRefError`, `ActionFailedError`,
473+
`MicrostepOverflowError`, `NoPathError`, `NoInitialStateError`,
474+
`UnknownBuiltinError`, `UnboundActorError`, `UnsupportedSchemaError`,
475+
`UnknownEffectKindError`, and `MultiRegionError`, matching the already-correct
476+
`WaitTimeoutError`, `SnapshotError`, `SnapshotVersionError`, and `VerifyError`.
477+
Behavior and fields are unchanged; update the type name at each `errors.As`
478+
target, type switch, and struct literal.
463479
- The determinism and ordering contract is now explicit and frozen: emission
464480
order is exit → transition → entry across the cascade, declaration order within
465481
a set, fixed parallel-region order, and the run-to-completion interleave for
@@ -468,7 +484,7 @@ representative hot-path numbers.
468484

469485
### Fixed
470486

471-
- `Cast` returns the typed `*ErrInvalidTransition` consistently for an event that
487+
- `Cast` returns the typed `*InvalidTransitionError` consistently for an event that
472488
matches no transition, including inside parallel regions, so a caller can
473489
distinguish "no transition" from other failures uniformly.
474490
- On-entry lifecycle effects (`after` / `invoke` / actor `spawn`) are now emitted

state/actor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ func evalActorBuiltinAction(a Ref) (Effect, error) {
176176
id, _ := a.Params[stopActorIDParam].(string)
177177
return StopActor{ID: id}, nil
178178
default:
179-
return nil, &ErrUnknownBuiltin{Name: a.Name}
179+
return nil, &UnknownBuiltinError{Name: a.Name}
180180
}
181181
}
182182

state/actor_comms.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,6 @@ func evalCommBuiltinAction(a Ref) (Effect, error) {
167167
systemID, _ := a.Params[sendToSystemIDParam].(string)
168168
return ForwardEvent{TargetID: target, SystemID: systemID}, nil
169169
default:
170-
return nil, &ErrUnknownBuiltin{Name: a.Name}
170+
return nil, &UnknownBuiltinError{Name: a.Name}
171171
}
172172
}

state/actor_escalation_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,8 +409,8 @@ func TestActorEscalation_UnboundSrc_NoOnError_Escalates(t *testing.T) {
409409
if esc == nil {
410410
t.Fatal("unbound-src spawn failure was swallowed; LastEscalation = nil")
411411
}
412-
var unbound *state.ErrUnboundActor
412+
var unbound *state.UnboundActorError
413413
if !errors.As(esc, &unbound) {
414-
t.Fatalf("escalation cause is not *ErrUnboundActor: %v", esc)
414+
t.Fatalf("escalation cause is not *UnboundActorError: %v", esc)
415415
}
416416
}

state/actor_system.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ func (s *ActorSystem[S, E, C]) spawn(ctx context.Context, e SpawnActor) {
313313
s.mu.Unlock()
314314

315315
if !ok {
316-
s.routeError(ctx, e, &ErrUnboundActor{Name: e.Src.Name})
316+
s.routeError(ctx, e, &UnboundActorError{Name: e.Src.Name})
317317
return
318318
}
319319
inst, err := behavior(e.Input)

state/assign_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ func TestAssign_ParallelRegionFolds(t *testing.T) {
283283
}
284284

285285
// TestAssign_ParallelRegionPanicStopsCommit asserts a region reducer that panics
286-
// surfaces as OutcomeAssignFailed / *ErrAssignPanic and stops the commit — it
286+
// surfaces as OutcomeAssignFailed / *AssignPanicError and stops the commit — it
287287
// must not silently no-op, which was the prior behavior on the parallel path.
288288
func TestAssign_ParallelRegionPanicStopsCommit(t *testing.T) {
289289
m := state.Forge[string, string, acct]("par-assign-panic").
@@ -309,9 +309,9 @@ func TestAssign_ParallelRegionPanicStopsCommit(t *testing.T) {
309309
if res.Err == nil {
310310
t.Fatal("panicking region reducer should fail the fire")
311311
}
312-
var ap *state.ErrAssignPanic
312+
var ap *state.AssignPanicError
313313
if !errors.As(res.Err, &ap) {
314-
t.Fatalf("error = %v, want *ErrAssignPanic", res.Err)
314+
t.Fatalf("error = %v, want *AssignPanicError", res.Err)
315315
}
316316
if ap.AssignName != "boom" {
317317
t.Fatalf("assign name = %q, want boom", ap.AssignName)
@@ -386,9 +386,9 @@ func TestAssign_PanicStopsCommit(t *testing.T) {
386386
if res.Err == nil {
387387
t.Fatal("panicking reducer should fail the fire")
388388
}
389-
var ap *state.ErrAssignPanic
389+
var ap *state.AssignPanicError
390390
if !errors.As(res.Err, &ap) {
391-
t.Fatalf("error = %v, want *ErrAssignPanic", res.Err)
391+
t.Fatalf("error = %v, want *AssignPanicError", res.Err)
392392
}
393393
if ap.AssignName != "boom" {
394394
t.Fatalf("assign name = %q, want boom", ap.AssignName)
@@ -399,7 +399,7 @@ func TestAssign_PanicStopsCommit(t *testing.T) {
399399
}
400400

401401
// TestAssign_UnboundRefFailsQuench asserts a transition that wires an unregistered
402-
// assign fails Quench with the typed *ErrUnboundRef (Kind "assign"), exactly like
402+
// assign fails Quench with the typed *UnboundRefError (Kind "assign"), exactly like
403403
// an unbound guard, action, or service.
404404
func TestAssign_UnboundRefFailsQuench(t *testing.T) {
405405
defer func() {
@@ -411,9 +411,9 @@ func TestAssign_UnboundRefFailsQuench(t *testing.T) {
411411
if !ok {
412412
t.Fatalf("panic value is not an error: %T", r)
413413
}
414-
var ub *state.ErrUnboundRef
414+
var ub *state.UnboundRefError
415415
if !errors.As(err, &ub) {
416-
t.Fatalf("panic = %v, want *ErrUnboundRef", err)
416+
t.Fatalf("panic = %v, want *UnboundRefError", err)
417417
}
418418
if ub.Kind != "assign" || ub.Name != "ghost" {
419419
t.Fatalf("unbound ref = {%q, %q}, want {assign, ghost}", ub.Kind, ub.Name)

state/binding.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func (r *Registry[C]) bindGuard(name string, b GuardBinding[C]) {
171171
// The binding is wired into the same name path Guard uses, so a guard registered
172172
// this way is indistinguishable to the kernel from a Go-func guard — it resolves
173173
// by name at Provide/Quench, evaluates synchronously inside the pure Fire step, and
174-
// surfaces a panic as the same typed ErrGuardPanic. The binding's EvalGuard is
174+
// surfaces a panic as the same typed GuardPanicError. The binding's EvalGuard is
175175
// adapted to a GuardFn over the in-process context view so the fire-time fast path
176176
// (which reads r.guards) finds it; the binding is also recorded on the parallel
177177
// binding seam so a future out-of-process transport can swap it under the same name.

state/binding_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,17 +173,17 @@ func TestRegistry_AssignBindingRecorded(t *testing.T) {
173173
}
174174

175175
// TestEvalAssign_UnboundRefFailsClosed asserts the defensive fire-time guard:
176-
// an assign ref with no bound reducer surfaces as a typed *ErrAssignPanic and
176+
// an assign ref with no bound reducer surfaces as a typed *AssignPanicError and
177177
// leaves the context unchanged, rather than silently dropping the fold.
178178
func TestEvalAssign_UnboundRefFailsClosed(t *testing.T) {
179179
m := Forge[string, string, bindOrder]("x").State("a").Initial("a").Quench()
180180
next, err := m.evalAssign(Ref{Name: "ghost"}, bindOrder{Amount: 3}, nil)
181181
if err == nil {
182182
t.Fatal("unbound assign ref should fail")
183183
}
184-
var ap *ErrAssignPanic
184+
var ap *AssignPanicError
185185
if !errors.As(err, &ap) || ap.AssignName != "ghost" {
186-
t.Fatalf("error = %v, want *ErrAssignPanic{ghost}", err)
186+
t.Fatalf("error = %v, want *AssignPanicError{ghost}", err)
187187
}
188188
if next.Amount != 3 {
189189
t.Fatalf("context changed on unbound assign: %+v", next)

0 commit comments

Comments
 (0)