An unexpected defect should not become a vague log line or an invented domain state. A durable engine preserves the complete incident, keeps the last committed Snapshot intact, and parks the ownerless root until an operator makes one explicit recovery decision.
Keep the three authorities separate
An incident can exist without Park: a chart may handle a supervisory Defect, or an owned child may
escalate to a parent that still owns policy. Park begins only when a durable root has no remaining
chart owner for an escaped defect, or when its retained checkpoint cannot be revalidated.
Snapshot + Park + Incident
Resolve the incident, not a status flag.
Follow one escaped root defect from preserved domain truth to operator evidence, then branch the same exact incident into replay or interrupt.
1 · Enumerate the operational overlay
Park does not replace the domain Snapshot.
listParked returns an exact root target, current incident pointer, and update time. The last committed domain configuration remains Processing; parked describes whether the engine may admit more work.
import { StatechartEngine } from "@motive/motive";
import { Effect } from "effect";
export const listParkedFulfillment = Effect.gen(function* () {
const ops = yield* StatechartEngine.StatechartOps;
return yield* ops.listParked("Fulfillment");
});
2 · Join the read-only authorities
The journal explains Park; the incident explains the defect.
parkJournal retains the replayable stimulus and append-only dispositions. Incidents.get resolves the same reference to the complete normalized Cause, origin, ancestry, and durable step provenance.
import type { StatechartEngine } from "@motive/motive";
import { Incidents, StatechartInspect } from "@motive/motive-storage";
import { Effect } from "effect";
export const inspectPark = Effect.fn("Fulfillment.inspectPark")(function* (
target: StatechartEngine.ParkTarget,
expectedIncidentId: StatechartEngine.ParkHead["incidentId"],
) {
const inspect = yield* StatechartInspect.StatechartInspect;
const [journal, incident] = yield* Effect.all([
inspect.parkJournal(target),
Incidents.get(expectedIncidentId),
]);
return { journal, incident };
});
3 · Resolve one exact incident
Replay and interrupt are different decisions.
Both operator commands compare expectedIncidentId with the current Park head. Replay submits the retained stimulus again; interrupt ends the root from its preserved plan. Neither command clears a generic status flag.
import { type Identity, StatechartEngine } from "@motive/motive";
import { Effect, Match } from "effect";
export type ParkDecision = "interrupt" | "replay";
export const resolvePark = Effect.fn("Fulfillment.resolvePark")(function* (
target: StatechartEngine.ParkTarget,
expectedIncidentId: Identity.IncidentId,
decision: ParkDecision,
) {
const ops = yield* StatechartEngine.StatechartOps;
const input = { target, expectedIncidentId };
return yield* Match.value(decision).pipe(
Match.when("replay", () => ops.redriveParked(input)),
Match.when("interrupt", () => ops.interruptParked(input)),
Match.exhaustive,
);
});
Park preserves the last committed domain truth
Parking changes the checkpoint's operational status to parked and stores a ParkHead beside its
existing ResumePlan. The underlying Snapshot is still the last successfully committed domain fact.
Its configuration, facets, chart hash, and sequence are not rewritten to imitate recovery.
Ordinary admission is fenced while that head is unresolved. Author-facing start, send, and read
paths can fail with StatechartEngineError.InstanceParked; storage-backed inspection remains
available because it does not enter the mailbox or fold the chart.
Keep Parked, Recovering, and Faulted out of the topology unless the business domain itself has
those states. Runtime ability and domain meaning are separate authorities.
Inspect the incident before choosing a disposition
StatechartOps.listParked(chartName) enumerates parked roots as target, head, and updatedAt.
It intentionally does not flatten the rest of the evidence into a status row.
Use the returned ParkTarget with StatechartInspect.parkJournal. The current journal head names
the sole unresolved incident. Its matching Incident entry retains:
- the exact admitted or unadmitted stimulus, or a non-replayable
Revalidationfact; - the complete chart-shaped
Causeat the Park boundary; - the last Snapshot's
chartHashandsnapshotSeq; - the step metadata, stimulus identity, provenance, and time of parking.
Resolve the same reference through Incidents.get when operator policy needs the chart-erased
immutable record: complete normalized Cause, exact occurrence origin, ancestry, ownership target,
and durable provenance. Public Defect events carry only that reference and one representative
defect; the incident service owns the full evidence.
A host exposing incident inspection also owns authentication, authorization, and redaction. A serializable Cause can still contain operationally sensitive information.
Replay the retained stimulus
redriveParked({ target, expectedIncidentId }) is a request to try the same durable stimulus again.
The engine first records a deterministic replay intent, then delivers it through the current owner.
If the fold commits, the new Snapshot and a ReplayCommitted disposition become visible together,
the Park head clears, and deferred lifecycle deliveries can drain in their captured order.
Replay is not “continue from after the failure.” It re-enters the preserved plan with the retained delivery and original step provenance. Make the underlying code, dependency, or data condition safe before requesting it.
A Revalidation incident cannot be replayed because it records an incompatible retained checkpoint,
not a refused delivery. redriveParked rejects that request with ParkRedriveRejected. Use an
explicit migration or interrupt decision instead.
If replay defects again, the journal appends Superseded followed immediately by a new Incident.
The root remains parked against the successor; the failed recovery attempt does not erase or mutate
the incident it replaced.
Interrupt the parked root
interruptParked({ target, expectedIncidentId }) folds an Interrupt activation from the preserved
ResumePlan. Its committed result changes the root to the terminal Interrupted Snapshot, appends an
Interrupted disposition, and clears the Park head in the same durable boundary.
Interrupt is not a generic acknowledgement and it does not resume the original event. Choose it when policy says the instance must end rather than try that stimulus again. Starting a replacement instance, compensating externally, or migrating retained state is separate application work.
Exact incident identity prevents accidental recovery
Both Park operations are compare-and-swap decisions. expectedIncidentId must still be the current
unresolved head for the exact root target. If another operator or failed replay changed that head,
the command refuses with ParkIncidentConflict; it never silently applies the old decision to a new
incident.
Read current truth again before issuing another command. Replacing the expected identity after a conflict is a new operator decision, not a transparent retry.
There is intentionally no clearParked operation. A Park ends only through a committed replay,
committed interrupt, or append-only supersession by a successor incident.
Park also fences descendant lifecycle delivery
While a root is parked, durable child, Activity, Resource, and send-species lifecycle requests that belong beneath it are captured once in admission order rather than folded behind the unresolved incident. After a successful replay clears the Park head, the engine drains those exact requests under current ownership. Interrupt instead terminates the root without replaying the failed stimulus.
This prevents later descendant traffic from racing ahead of the fact an operator is inspecting. It does not promise an external receiver executes exactly once; stable delivery identity and receiver idempotency still own that boundary.
Change the world
- Inspect the same parked root through
listParked,parkJournal, andIncidents.get; name what each authority adds. - Replay the retained stimulus and verify the new Snapshot and
ReplayCommitteddisposition commit together. - Reset to the same incident, interrupt it, and verify the terminal Snapshot does not claim the failed stimulus succeeded.
- Issue either command with an old incident id and handle
ParkIncidentConflictwithout retargeting it automatically.
Next, Versioning & Migrations changes durable chart meaning through an exact restoration contract rather than an operator recovery disposition.