A Clock returns the current time as an ISO-8601 string. Each
verb-driven operation takes ONE reading and threads it everywhere —
$now in filters, the now op-source, every history at stamp,
completedAt, enteredAt, lastChangedAt — so an eval pass agrees on
the time and a frozen clock produces a deterministic instance.
A clock is injected ONCE, never per call: production omits it entirely
(defaults to wallClock); deterministic harnesses pass
createEngine({ clock }), and tests use @sanity/workflow-engine-test's
setNow / advance (the bench wraps the same seam). It is deliberately
NOT a field on the public per-verb *Args — the raw workflow.* verbs
accept it only through the Clocked composition seam, so prod code
can't trivially override engine time by accident.
drainEffects and sweepStaleClaims read the same engine clock for
their claim / lease-expiry stamps, so lease timing is deterministic
under a pinned clock too.
The engine's single source of "now".
A Clock returns the current time as an ISO-8601 string. Each verb-driven operation takes ONE reading and threads it everywhere —
$nowin filters, thenowop-source, every historyatstamp,completedAt,enteredAt,lastChangedAt— so an eval pass agrees on the time and a frozen clock produces a deterministic instance.A clock is injected ONCE, never per call: production omits it entirely (defaults to wallClock); deterministic harnesses pass
createEngine({ clock }), and tests use@sanity/workflow-engine-test'ssetNow/advance(the bench wraps the same seam). It is deliberately NOT a field on the public per-verb*Args— the rawworkflow.*verbs accept it only through the Clocked composition seam, so prod code can't trivially override engine time by accident.drainEffectsandsweepStaleClaimsread the same engine clock for their claim / lease-expiry stamps, so lease timing is deterministic under a pinned clock too.