"An operation isn't a function call. It's a little creature that walks a road, and every step is written down."
Why a State Machine at All
A naive file operation is one function that either returns or throws. That model can't express the reality: an operation can be waiting on a collision decision, paused by the user, halfway through copying, blocked by a vanished volume, or recovered after a crash. So Waygate models every operation as a state machine — a defined set of states and the only legal transitions between them — and the journal records which state it's in at every moment.
The Happy Path, Named
The main road is: planned → validating → (awaitingDecision only if a collision needs a choice) → ready → running → verifying → committed. Each arrow is a barrier where the journal gains evidence. Naming the states this precisely is what lets recovery ask a sharp question after a crash: which state were we in, and is the transition out of it safe to resume?
The States That Save You
Off the happy path are the states that make the whole thing trustworthy: any active state can become paused, canceled, failed, interrupted, or needsReview. The rule that matters most: only evidence-backed transitions may resume automatically. A staged copy with a matching verification can be resumed. Anything uncertain — a move that crashed at the source-deletion barrier — lands in needsReview and waits for a human, never an automatic retry.
Every operation is a journaled state machine; only evidence-backed transitions auto-resume. The states name every situation an operation can be in — including paused, interrupted, and needsReview — so recovery is a precise question about a known state, not a guess about a mystery. Uncertainty routes to review, never to a retry.
The awaitingDecision state is not an error. When a copy hits a collision it can't resolve from the plan, it doesn't fail and it doesn't guess — it enters awaitingDecision and surfaces the choice. A state machine lets 'I need a human to pick' be a first-class, resumable state instead of a thrown exception that loses all context.
Code
The operation state machine·text
planned
|
v
validating ---> awaitingDecision (only if a collision needs a choice)
| |
| <--------------+ (decision recorded)
v
ready --> running --> verifying --> committed (the happy path)
From ANY active state:
--> paused (cooperative, at a safe checkpoint)
--> canceled (cleans only positively-owned artifacts)
--> failed (a clean, known error)
--> interrupted (crash / kill / unmount)
--> needsReview (uncertain -- waits for a human, never auto-retries)
States are explicit; transitions are the only way to move·swift
enum OperationState: String {
case planned, validating, awaitingDecision, ready
case running, verifying, committed
case paused, canceled, failed, interrupted, needsReview
}
// Recovery asks a precise question, per state:
func resumable(_ state: OperationState, evidence: Evidence) -> Bool {
switch state {
case .staged where evidence.verifiedCopyExists: return true // safe to continue
case .interrupted where evidence.destructiveBarrierPassed == false: return true
default: return false // uncertain -> needsReview, a human decides
}
}
Draw the state machine for a single cross-volume move and mark, on each arrow, what evidence the journal must hold for recovery to safely cross that arrow after a crash. Which transition is the one where 'resume automatically' would be dangerous, and why does it belong in needsReview instead?
Hint
The dangerous arrow is the source deletion after publish: if recovery can't prove the destination copy is complete AND verified, auto-deleting the source risks losing the only good copy. That transition demands deterministic evidence; without it, the operation belongs in needsReview so a human confirms before anything irreversible happens.
Progress
Progress is local-only — sign in to sync across devices.