Reference

operations: machine queries

Package Trax.Api.GraphQLSurface GraphQL schemaSource OperationsQueries.MachineInstances.cs, OperationsQueries.Machines.cs

Fields of OperationsQueries that read persisted state machines: the machines a host registers, and their instances.

machines

Every machine the host registers, ordered by name, whether or not it has any instances, with its graph. It reads through IOperationsService.GetMachinesAsync, which reads each machine's IR by the same reading trax machine show draws it from, so the two list the same states and edges in the same order.

query {
  operations {
    machines {
      name
      version
      systemOwned
      initialState
      states { name isInitial isFinal isCommitted invokesTrain spawnsMachine }
      transitions { from command to kind guard effect after }
      timers { state command target after field min max }
    }
  }
}

Returns: [MachineDefinition!]!.

FieldTypeDescription
nameString!The machine's id
versionInt!The definition version it declares
systemOwnedBoolean!True when only the system holds its instances
schemaHashStringThe hash of its IR, which each instance row records as the definition that last wrote it
initialStateStringThe state an instance starts in
states[MachineDefinitionState!]!Every state in IR order: name, isInitial, isFinal (no transition leaves it), isCommitted, invokesTrain (the invoked train's interface full name) and spawnsMachine (the spawned machine's id)
transitions[MachineDefinitionTransition!]!Every edge: from, command, to, kind (COMMAND, TIMER or OUTCOME), guard (its message, or its rule's name), effect (the key prefix of the effect it runs once) and, for a timer, after. Commands come first, then timers, then each invoking state's outcomes (Building.done, .failed, .cancelled) in the order they are tried
timers[MachineDefinitionTimer!]!Every timed state's timer: state, command, target, and a fixed after or the context field with its min and max, all ISO-8601 durations
graphUnavailableReasonStringWhy the machine has no graph: it was declared with C# delegates, which its IR cannot carry. Its states and transitions are then empty

It carries what a machine declares and nothing about any instance: the counts and the instances are the fields below.

machineInstances

The instances of every persisted state machine on the host, system-owned and users' drafts alike, newest first by when each was last written. It reads through IOperationsService.GetMachineInstancesAsync and CountMachineInstancesAsync, which the operations console's Machines pages make.

query {
  operations {
    machineInstances(machine: "fulfilment", state: "AwaitingPayment", ownerKind: SYSTEM, take: 25) {
      items {
        rowId
        machine
        ownerKind
        id
        state
        version
        createdAt
        updatedAt
        hasLiveInvokedRun
      }
      totalCount
      isCountCapped
    }
  }
}
ParameterTypeDefaultDescription
machineStringnullOnly instances of this machine, by its id, matched exactly
stateStringnullOnly instances in this state, matched exactly
ownerKindSnapshotOwnerKindnullSYSTEM for instances created from code, USER for users' drafts; null for both
skipInt!0Offset; above 10,000 is refused with TRAX_SKIP_TOO_DEEP, and the service the dashboard reads through refuses it too
takeInt!25Page size, from 1 to 500
stuckBooleannulltrue for only the stuck instances, false for only the others; null for both

Returns: PagedResultOfMachineInstance!. There is no keyset cursor (nextCursor is always null), because the list is ordered by a time that moves as instances advance: page with skip. The total counts at most 10,000 instances; past that it reads 10,000 with isCountCapped true, and machineInstanceCounts has the exact numbers. At two million instances each page, filtered or not, reads in a few milliseconds.

MachineInstance fields

FieldTypeDescription
rowIdLong!The row's key. Pass it to machineInstance to read a user's draft
machineString!The machine's id
ownerKindSnapshotOwnerKind!SYSTEM or USER
idUUID!The instance or draft id
stateString!The state it is in
versionInt!The machine definition version it was last written under
createdAtDateTimeWhen it was created; null for one written before Trax recorded it
updatedAtDateTime!When it was last written
hasLiveInvokedRunBoolean!true while its state has invoked a train and waits for the run's outcome
dueAtDateTimeWhen its state's timer fires; null when that state has no timer
overdueBoolean!dueAt is past by more than the host's timer poll, so the timer should have fired and has not
waitingOnMachineInstanceWaitingRunThe run its state waits on: executionId (null while still queued), trainState (PENDING while queued, CANCELLED for an entry cancelled before it started) and workQueueEntryId. Null when it waits on none
inStateSinceDateTime!When it entered its state, as far as Trax records it. No transition log is kept yet, so this is updatedAt, and a write that kept the state moves it: show it as "last changed"
stuckBoolean!It cannot move on its own; see needing attention

Needing attention

An instance is stuck when its timer is overdue, or when the run it waits on failed or was cancelled and its outcome was never applied: a run that ended more than one timer poll ago, or an entry cancelled while still queued. The poll is the host's StateMachineOptions.InvokeOutcomeSweepInterval (5 seconds by default), the pace at which the reconciler fires timers and applies outcomes, so an instance is never stuck only because a sweep has not come round yet. How long an instance has sat in its state is shown (inStateSince) and never counted: a state some instances wait in for days is not a fault.

No field carries an instance's context, and none names the user who owns a draft. The context is an untyped JSON object, so nothing could mask the sensitive parts of it; an operator sees where an instance is, when it got there and which kind of owner holds it, never what it holds (ADR 0046). A user reaches their own drafts only through the stateMachine mutations, never through this list.

machineInstance

One instance, or null when none matches. The owner kind is always named, because a user can hold a draft under the same id as a system instance. A system instance is unique by machine and id. A user's draft also needs its rowId, because several users can each hold a draft under one id and an operator is not shown whose a draft is.

query {
  operations {
    system: machineInstance(machine: "fulfilment", ownerKind: SYSTEM, id: "6f9619ff-8b86-d011-b42d-00c04fc964ff") {
      state
      hasLiveInvokedRun
    }
    draft: machineInstance(machine: "checkout", ownerKind: USER, id: "6f9619ff-8b86-d011-b42d-00c04fc964ff", rowId: 42) {
      state
      updatedAt
    }
  }
}
ParameterTypeDefaultDescription
machineString!noneThe machine's id. Blank is refused with TRAX_INVALID_ARGUMENT
ownerKindSnapshotOwnerKind!noneWho owns the instance
idUUID!noneThe instance or draft id
rowIdLongnullThe row's key from the list. Required for USER (refused with TRAX_ROW_ID_REQUIRED without it); for SYSTEM, when given, it must match too

Returns: MachineInstanceDetail, with the fields of MachineInstance and the train runs the instance invoked, which only this lookup reads:

FieldTypeDescription
invokedRuns[MachineInstanceInvokedRun!]!The runs, newest first, at most 50: id, externalId, name (the train), trainState, startTime, endTime, failureClass, cancellationRequested, and isLive for the run the instance's state waits on. Never a run's input or output; executionDetail(id) reads the rest, masked
isInvokedRunsCappedBoolean!True when the instance invoked more than 50 runs
queuedInvokedRunEntryIdLongThe work queue entry of the run the state waits on while it is still queued, and so not yet a run

A system instance lists every run it invoked. A user's draft lists only its live run: a run records the machine, instance id and owner kind that queued it, not the user, and several users can each hold a draft under one id, so listing every run under the id could show another user's.

query {
  operations {
    machineInstance(machine: "fulfilment", ownerKind: SYSTEM, id: "6f9619ff-8b86-d011-b42d-00c04fc964ff") {
      state
      invokedRuns { id name trainState startTime endTime failureClass isLive }
      queuedInvokedRunEntryId
    }
  }
}

The one operator action on an instance is cancelMachineInstance.

transitions

The instance's history, newest first: one MachineInstanceTransition for every write of its snapshot Trax recorded, its creation, each command, save, timer and run outcome. It reads through IOperationsService.GetMachineInstanceTransitionsAsync, the history the console's instance page shows, and only when selected. The history is the row's (rowId), so a user's draft shows only its own, and it starts with the first write after the upgrade that added it (089).

query {
  operations {
    machineInstance(machine: "fulfilment", ownerKind: SYSTEM, id: "6f9619ff-8b86-d011-b42d-00c04fc964ff") {
      transitions(first: 25) {
        items {
          revision kind trigger actor fromState toState at previousAt
          startedRun { executionId trainState workQueueEntryId }
          startedChild { machine ownerKind id rowId }
        }
        nextCursor
      }
    }
  }
}
ParameterTypeDefaultDescription
firstInt!25The page size, clamped to 1 through 500
afterLongnullOnly transitions older than this revision: the previous page's nextCursor

Returns: MachineInstanceTransitions!: items, the transitions on this page, and nextCursor, the revision to pass as after for the next page, or null on the last. Each MachineInstanceTransition has:

FieldTypeDescription
revisionLong!The revision the write gave the instance. A write that left the snapshot alone, or one made around Trax's store, has none, so revisions can skip
kindSnapshotTransitionKind!START (created: a start, a save that created a draft, or a parent spawning it), COMMAND (an event moved it, or an effect it bound ran), SAVE (an autosave, or host code writing the snapshot), TIMER (its state's timer fired) or OUTCOME (a run it invoked ended)
triggerStringThe command: the event's name, the timer's event, or State.done, State.failed or State.cancelled for an outcome. Null for a creation, a save, and an outcome that could not be applied, which leaves the instance where it was
actorSnapshotTransitionActor!OWNER (a client acting for a user's instance), HOST (host code driving a system instance) or MACHINE (a timer, an outcome, a spawn). Never which user
fromStateStringThe state it left; null for its creation
toStateString!The state it entered
atDateTime!When it was written
previousAtDateTimeWhen the transition before it was written, so at minus previousAt is the time spent in fromState since then; null for the oldest
startedRunMachineInstanceTransitionRunThe run entering toState queued: executionId (null while queued), trainState (PENDING while queued, CANCELLED if its entry was cancelled first) and workQueueEntryId. Never its token
startedChildMachineInstanceTransitionChildThe child it spawned: machine, ownerKind, id and rowId, as machineInstance takes them

No transition carries the instance's context or its owner's key. The history is deleted with the instance.

machineInstanceCounts

How many instances each machine has in each state, for each owner kind, ordered by machine, state and owner kind. Exact. It reads through IOperationsService.GetMachineInstanceStateCountsAsync, the counts the dashboard's State Machines page shows.

query {
  operations {
    machineInstanceCounts(machine: "fulfilment") { machine state ownerKind count }
  }
}
ParameterTypeDefaultDescription
machineStringnullOnly this machine's counts; null for every machine

Returns: [MachineInstanceCount!]!, each with machine: String!, state: String!, ownerKind: SnapshotOwnerKind!, count: Long! and stuck: Long!, how many of them are stuck. On Postgres the counts are read from the listing's index alone: about 120 ms over two million instances. Each host keeps the counts it read for 5 seconds, per machine filter, and callers asking meanwhile share that read, so every dashboard and API client polling them costs one read, not one each; a count can be that old.

See also