An Activity can fail in two fundamentally different ways. A modeled error is an expected result named by the Activity contract. A defect is an unexpected failure that belongs to supervision.
Failure keeps its authority.
An expected failure belongs to the Activity's typed contract. A defect does not. Motive preserves that boundary so domain recovery and supervision remain different decisions.
1 · Expected failure
A modeled error returns to the domain.
ReviewOrder fails with the ReviewUnavailable value declared by its error Schema. ReviewOrder.Error carries that value back into transition selection, where the chart enters NeedsReview.
import { Activity, State, Statechart, States, Transitions } from "@motive/motive";
import { Schema } from "effect";
const ReviewUnavailable = Schema.TaggedStruct("ReviewUnavailable", {
reason: Schema.String,
});
const ReviewOrder = Activity.make("ReviewOrder", {
input: Schema.Struct({ orderId: Schema.String }),
success: Schema.Struct({ approvalCode: Schema.String }),
error: ReviewUnavailable,
});
export class Review extends State.Compound<Review>()("Review") {
static states = States.make(() => [Checking, NeedsReview]);
}
class Checking extends State.Atomic<Checking>()("Checking") {
static transitions = Transitions.make(this, ({ invoke, on }) => [
invoke(ReviewOrder, () => ({ orderId: "order-123" })),
on(ReviewOrder.Done),
on(ReviewOrder.Error, NeedsReview),
]);
}
class NeedsReview extends State.Atomic<NeedsReview>()("NeedsReview") {}
export const ReviewChart = Statechart.make(Review);
2 · Unexpected failure
A defect returns to supervision.
The same Activity implementation now dies outside its declared error contract. ReviewOrder.Defect carries an incident reference and representative defect to the supervisory listener, which isolates the order in Halted.
import { Activity, State, Statechart, States, Transitions } from "@motive/motive";
import { Schema } from "effect";
const ReviewUnavailable = Schema.TaggedStruct("ReviewUnavailable", {
reason: Schema.String,
});
const ReviewOrder = Activity.make("ReviewOrder", {
input: Schema.Struct({ orderId: Schema.String }),
success: Schema.Struct({ approvalCode: Schema.String }),
error: ReviewUnavailable,
});
export class Review extends State.Compound<Review>()("Review") {
static states = States.make(() => [Checking, NeedsReview, Halted]);
}
class Checking extends State.Atomic<Checking>()("Checking") {
static transitions = Transitions.make(this, ({ invoke, on }) => [
invoke(ReviewOrder, () => ({ orderId: "order-123" })),
on(ReviewOrder.Done),
on(ReviewOrder.Error, NeedsReview),
on(ReviewOrder.Defect, Halted),
]);
}
class NeedsReview extends State.Atomic<NeedsReview>()("NeedsReview") {}
class Halted extends State.Atomic<Halted>()("Halted") {}
export const ReviewChart = Statechart.make(Review);
Expected failure belongs to the model
ReviewOrder names an error Schema alongside its input and success Schemas:
error: Schema.TaggedStruct("ReviewUnavailable", {
reason: Schema.String,
});
This says that an unavailable review service is a result the application understands. When the
implementation fails with that decoded value, the occurrence produces ReviewOrder.Error.
Error carries a typed value and its Cause
An Error event exposes the modeled failure as event.error and preserves the complete Effect
Cause as event.cause. Most domain responses begin with the typed value: retry later, request a
manual review, decline an operation, or retain an explanation.
The first frame chooses NeedsReview:
on(ReviewOrder.Error, NeedsReview);
That transition is application policy. The Activity reports an expected outcome; it does not choose the state that should own the response.
Defect stays outside the error Schema
The same implementation can die because an invariant was violated, a dependency returned an
impossible value, or code failed somewhere the domain contract did not anticipate. Motive does not
widen ReviewUnavailable to make that failure fit.
Instead, a terminal Cause containing a defect produces ReviewOrder.Defect. The event carries an
opaque event.incident reference and one event.defect representative; it has no invented
error value. The immutable incident record retains the complete normalized Cause and its exact
provenance for operational inspection.
Supervision is explicit topology
The second frame keeps the modeled Error disposition and adds a supervisory edge:
on(ReviewOrder.Defect, Halted);
Halted is not another spelling of NeedsReview. It says the order has left its ordinary domain
recovery path and now requires a supervisory decision. Another application might escalate, isolate
a subtree, record an incident, or deliberately terminate the instance.
One Effect Cause, two authorities
Both buttons settle a real running Effect. The first uses the typed error channel; the second uses a Cause containing a defect. Motive inspects the terminal Cause and preserves the distinction at the statechart boundary:
- a Cause with a declared Activity failure and no defect returns through
Error; - a Cause containing a defect returns through
Defect.
Interruption, mixed Causes, and the exact information preserved by each channel deserve their own treatment. The important rule here is simpler: do not make an unexpected failure look expected by forcing it into the Activity's error Schema.
Modeled outcomes are total; supervision is optional
Because ReviewOrder declares success and error Schemas, an active invocation must provide an
eligible disposition for both Done and Error. Those are known branches of its contract.
Defect remains available even for an Activity whose modeled error type is impossible, but its
disposition is supervisory and optional. Whether an unhandled defect terminates, escalates, or is
required to be caught is a supervision policy—not a fabricated domain outcome.
Test the boundary
- Settle both frames and compare the selected edge, destination, and authority readout.
- Remove
on(ReviewOrder.Error, NeedsReview)and observe that the declared modeled outcome is no longer fully dispositioned. - Remove
on(ReviewOrder.Defect, Halted)and compare the resulting supervisory behavior without changing the Activity's error Schema.
The next page narrows in on Activity.Defect: its incident evidence, its lifetime, and the policies
available to the state that supervises the occurrence.