ConstThe actions an actor could fire on the instance's current stage, each
flagged allowed with a structured disabledReason when not, returned
with the evaluation they were projected from. Pure read.
Materialised spawned children of a parent instance. Walks history for
spawned events — the durable record, and still the one place adoption
and orphan events sit alongside spawns — strips each instanceRef's GDR
URI to a bare _id, fetches the instances, drops any not visible to this
engine's tag, and returns them sorted by startedAt ascending. Pass
activity to restrict to a single spawning activity on the parent.
Commit mid-dispatch field state from a running effect handler — the engine
verb behind ctx.commitOps. Gates on the dispatch's exact claim (token
match plus unexpired lease; a stale report throws StaleEffectClaimError
and writes nothing), validates and applies the field.* ops through the
shared op applier, records history and the mandatory idempotency key,
renews the claim's lease in the same compare-and-swap commit, refreshes
the stage's guards, then cascades — a report that satisfies a transition
moves the instance, by design.
completeEffect remains the authoritative final result and stays
claim-blind; this verb only protects the mid-dispatch write channel from
superseded handlers.
Report a queued effect's outcome. Drains it from pendingEffects and
appends an effectHistory entry whose outputs are what downstream
bindings and conditions read as $effects['<name>'].<output> — the
start-only context bag is never touched. Any ops the handler returned
(field.*) are validated and applied in the same commit, through the op
applier an action's field ops use. Cascades after. A completion that
applies always changes state, so changed is true; a bad
effectKey/status throws instead, and a keyed retry of an
already-applied completion replays as changed: false.
Completion is first-writer-wins — see CompleteEffectArgs. A
completer reporting over a retrying transport (webhook redelivery, queue,
cron) should pass idempotencyKey.
The startable half of workflow.instancesForDocument: every
deployed definition that APPLIES to document — what a start picker for
it should offer. Loads the latest deployed version of each definition
visible to the engine's tag and filters it through
applicableDefinitions: startable ∧ the subject-kind entry
accepts the doc's _type ∧ start.filter passes, evaluated in the
browse-time-pure start-filter context. Start requirements never
participate — readiness is a start-time question; pre-flight it with
workflow.evaluateStart.
Takes the LOADED candidate document, not a ref — applicability evaluates its content under whatever perspective the caller read it with. Surfaces ALL matches (name ascending) with no engine ranking: presenting a picker or auto-picking is consumer policy. Advisory like every engine-side check.
Remove a deployed definition (all versions, or one via version). Refuses
while non-terminal instances exist unless cascade aborts them first —
instances are never deleted, only aborted in place. The operation also
checks spawn referrers and cleans up guard documents.
Deploy a set of definitions as one call. Definitions are immutable and
content-addressed: the author writes no version, identical content no-ops
(unchanged), and any change mints the next version (created) — deploy
never patches a deployed version out from under the instances pinned to it.
The engine orders the batch itself (children before the parents that spawn
them). Refs may point inside the batch or at already-deployed definitions;
a ref resolving to neither, or a cycle, errors before any write. Input is
authored content or a fetched definition document — the document envelope
(_* system fields, tag, version, contentHash) is stripped at the
boundary and never fingerprinted, so a fetched document redeploys as
unchanged; any other unknown key fails loud.
Diagnose why an instance is or isn't progressing. Projects the instance
(the same read as evaluate) and classifies it — terminal,
progressing, waiting (an action is available, so healthy), or stuck
with a structured cause — returning that verdict as a
DiagnoseResult alongside the evaluation it came from, so a
consumer can render the supporting evidence without a second projection.
Pure read.
Edit a declared-editable field directly — reassign, reschedule,
claim-by-hand, append to a running log — through the generic edit seam
instead of a bespoke action per field. Soft-gates on the field's declared
editability (the same projection a UI renders), applies the edit as a
field.* op so provenance and history are stamped by the op path,
refreshes the stage's guards, then cascades — an edit to a value a
transition reads can and should move the instance. Advisory like every
engine gate.
Each call is a discrete COMMIT (a history entry, a guard refresh, a
cascade, an ifRevisionId write), not a draft patch — so an inline-field
UI must bind it to a deliberate boundary (blur, Enter, Save, debounce),
never an onChange per keystroke.
Project the instance from a given actor's perspective — a
WorkflowEvaluation with per-action verdicts (allowed plus a
structured disabledReason). Used by UIs to render
disabled-with-reason buttons and by fireAction to gate writes through
the same logic. Pure read; never writes.
Pre-flight the start gates for a definition plus candidate
initialFields — the read startInstance enforces, as a
StartEvaluation a surface can render: missingRequired mirrors
the input contract, while requirements preserves every declared node's
authored descriptor, outcome, and GROQ insight where applicable;
allowed / outcome aggregate those ordered results.
BINDABILITY-AWARE for partial mid-form inputs: when a predicate reads an
entry initialFields doesn't supply — including a singleSubject node's
implicit subject read — outcome is 'unevaluable' and unboundReads
names the entries ("fill these to decide") instead of the collapsed answer
GROQ equality would give. That is the ONE deliberate divergence from the
gate, where absence is final rather than provisional. A definition
declaring no start requirements is vacuously allowed, exactly like the
verb. Pure read; advisory under races — the enforcement moment is
startInstance itself.
Filter the instance's pending effects: claimed on claim presence,
names on specific effect names. Both filters compose (AND).
Fire an action against an active activity, then cascade and propagate to
ancestors once it commits. The universal "something happened" call —
editors fire it, and runtimes fire it in response to webhooks, effect
completions, and timer firings; external signals never bypass it. A
cascade-fired (when) action is rejected: the cascade is its only firing
path.
Fetch a workflow instance by id, scoped to the engine's tag. Throws when the instance doesn't exist or isn't visible to this engine.
Every in-flight instance whose reactive watch-set includes document —
the reverse of an instance's subscription document set. For a
content-change-driven runtime holding no instances in memory: a document
changed, which instances should it tick? The watch-set covers the
instance itself, its ancestors, live spawned children, and the docs named
by doc.ref / subject / doc.refs / release.ref entries on the
workflow scope AND the current stage, so a hand-rolled GROQ over fields[]
gets it subtly wrong. The GROQ filter narrows candidates server-side and
instanceWatchesDocument rechecks each result, keeping the reverse
in lockstep with the forward set.
document must be a resource-qualified GDR URI; a bare id is rejected,
since it can't be resource-routed and would silently mismatch — an
instance watching dataset:A:ds:doc is never matched by a change to
dataset:B:ds:doc. Sorted by startedAt ascending.
Every pending effect on the instance — the same entries the runtime would see, claimed and unclaimed alike.
Permission helpers — Sanity ACL grants evaluated against documents via
GROQ, used by workflow.evaluate to soft-gate actions when the caller
supplies grants. Deliberately namespace-only, not mirrored on Engine:
pure helpers that need none of the engine's pinned scope.
Run a caller-supplied GROQ query with the engine's tag bound as $tag.
This does NOT rewrite the query — arbitrary GROQ can't be safely
tag-scoped after the fact — so the CALLER MUST filter on $tag (e.g.
tag == $tag). To guard against accidental cross-partition reads, a query
that never references $tag is rejected before it reaches the lake. The
caller is responsible for type-narrowing the result.
Snapshot-aware GROQ — runs against the same in-memory view that filters
see for a given instance. Hydrates the instance's snapshot (instance +
ancestors + every doc declared by a doc.ref / subject / doc.refs
entry in scope), then evaluates the supplied GROQ in groq-js against that
dataset. The caller-free rendered scope cascade gates evaluate in is
auto-bound — the instance-derived vars with the open stage's overlay
merged into $fields, $assigned at its caller-free false, plus the
author's pre-evaluated $<predicate> booleans — with ids in GDR URI form
to match the snapshot's keying. Pure read; never writes.
Admin override — reset a failed (or otherwise terminal) activity in the
instance's current stage. to: 'active' (the default) re-runs it;
to: 'skipped' is the bypass that resolves it so a $allActivitiesDone-gated
exit can fire. Cascades after the reset, so an unblocked transition fires
in the same call. ACL gating is enforced upstream by the caller; this verb
performs no permission pre-flight. changed: false means the reset was a
no-op (instance terminal, or the activity already at the target status).
Admin override — force the instance into targetStage regardless of
filters or declared transitions. ACL gating is enforced upstream by the
caller; this verb performs no permission pre-flight, only the mechanical
move. changed: false means the move was a no-op (already at the target,
or terminal).
Spawn a new instance from a deployed definition: pins the snapshot, seeds
the context bag, enters the initial stage with every in-scope activity
born active, then cascades until stable. The gates run before anything is
written — supplied rows must be structurally consumable, required inputs
must be present, then every declared start requirement is evaluated in
author order and all unmet nodes are reported by one
StartNotAllowedError (no override arg; pre-flight with
evaluateStart). start.filter is NOT evaluated here — that is a
read-side visibility rule, see definitionsForDocument.
Start is three commits — create, prime, first cascade — and a supplied
instanceId is the idempotency key across them: reusing it for the SAME
start RESUMES (input gates and field resolution are skipped, those values
were pinned at create), while reusing it for a DIFFERENT start, or for an
unfinished start that was aborted, throws
ContractViolationError. changed is true on a fresh start and
rev-derived on a resume; cascaded reports how far the instance
auto-advanced.
Run the cascade until stable — triggered actions fire, transitions move.
For a runtime reacting to any event that might affect the workflow but
isn't itself an action fire: a subject doc was patched, a sibling workflow
completed, the clock crossed a deadline a when reads. The caller doesn't
need to know what changed — it nudges the affected instance and the engine
re-evaluates. changed reports whether the nudge wrote anything, derived
from the instance's _rev rather than cascaded alone, since a hop can
fire triggered actions without unlocking a transition.
Admin override — hard-stop an in-flight instance where it stands. No stage move, no transition effects, pending effects cancelled. The abort propagates through affected ancestor instances. Propagated, not cascaded — the instance is terminal, so
cascadedis always0and ancestor movement is reported on the ancestors, not here.changed: falsemeans the instance was already terminal.