# Mutations

Mutations are organized into two groups under the root `Mutation` type:

```graphql
type Mutation {
  dispatch: DispatchMutations!     # only when [TraxMutation] trains exist
  operations: OperationsMutations! # only when ExposeOperationMutations() is set
}
```

- **`dispatch`**: auto-generated typed mutations for trains annotated with [`[TraxMutation]`](/docs/sdk-reference/graphql-api/trax-graphql-attribute)
- **`operations`**: scheduler management operations (trigger, disable, enable, cancel manifests and groups, plus the nested `deadLetters` namespace for requeue/acknowledge). **Off by default**, opt in with [`ExposeOperationMutations()`](/docs/sdk-reference/graphql-api/add-trax-graphql) on the builder. The Trax scheduler is reachable through these mutations, so leaving them open lets any caller disrupt scheduled work.

The root `Mutation` type is omitted entirely when no source contributes to it (no `[TraxMutation]` train and `ExposeOperationMutations()` not called).

## Dispatch Mutations (Auto-Generated)

Trax auto-generates strongly-typed mutations for trains that opt in with `[TraxMutation]`. Only trains with this attribute appear under `dispatch`. Trains annotated with `[TraxQuery]` appear under `query { discover { ... } }` instead; see [Queries](/docs/sdk-reference/graphql-api/queries).

Each whitelisted train gets a single mutation field named after the train (no prefix). Trains with `Namespace` set are grouped under a sub-namespace (e.g. `dispatch { alerts { createAlert } }`). The mutation's parameters and behavior depend on the operations passed to the attribute constructor:

- **Run + Queue (default)**: when no operations are specified (or both `GraphQLOperation.Run` and `GraphQLOperation.Queue` are passed), the mutation accepts an optional `mode: ExecutionMode` parameter (`RUN` or `QUEUE`, default `RUN`) and an optional `priority: Int`.
- **Run only**: the mutation always runs synchronously. No `mode` or `priority` parameters.
- **Queue only**: the mutation always queues. Has `priority` but no `mode` parameter.

### Naming Convention

The mutation name is derived from the train's service interface name (or overridden via `[TraxMutation(Name = "...")]`):
1. Strip the `I` prefix
2. Strip the `Train` suffix
3. Use the result as the field name (camelCase)

For example, `IBanPlayerTrain` produces `banPlayer`.

### Example

Given a train annotated with `[TraxMutation]`:

```csharp
public record BanPlayerInput : IManifestProperties
{
    public required string PlayerId { get; init; }
    public required string Reason { get; init; }
}
```

The schema exposes:

```graphql
input BanPlayerInput {
  playerId: String!
  reason: String!
}

# Run synchronously (default mode)
mutation {
  dispatch {
    banPlayer(input: { playerId: "player-42", reason: "cheating" }) {
      externalId
      metadataId
      output { ... }
    }
  }
}

# Queue for async execution
mutation {
  dispatch {
    banPlayer(
      input: { playerId: "player-42", reason: "cheating" }
      mode: QUEUE
      priority: 10
    ) {
      externalId
      workQueueId
    }
  }
}
```

### Unified Response Type

Every dispatch mutation returns a single per-train response type with nullable fields. Which fields are populated depends on the execution mode:

```graphql
type BanPlayerResponse {
  externalId: String!       # always present
  metadataId: Long          # present for RUN, null for QUEUE
  output: BanPlayerOutput   # present for RUN (typed trains only), null for QUEUE
  workQueueId: Long         # present for QUEUE, null for RUN
}
```

| Field | Type | When Populated |
|-------|------|----------------|
| `externalId` | `String!` | Always present. Identifies the execution or work queue entry |
| `metadataId` | `Long` | RUN mode. Metadata ID of the completed execution |
| `output` | `{OutputType}` | RUN mode, only for trains with non-`Unit` output |
| `workQueueId` | `Long` | QUEUE mode. Database ID of the created WorkQueue entry |

The wrapper is named `{TrainName}Response` by default. If the train's output CLR class is also named `{TrainName}Response` (for example `IAddressValidationTrain` returning `AddressValidationResponse`), the wrapper falls back to `{TrainName}MutationResponse` so the schema can build without a name collision. Trains whose output type follows a different naming convention are unaffected.

### Run + Queue Mode (Default)

When no operations are specified (or both `GraphQLOperation.Run` and `GraphQLOperation.Queue` are passed), the mutation includes a `mode` parameter:

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `input` | `{TrainName}Input!` | Yes | N/A | Strongly-typed input matching the train's input record |
| `mode` | `ExecutionMode` | No | `RUN` | Whether to run synchronously (`RUN`) or queue for async execution (`QUEUE`) |
| `priority` | `Int` | No | `0` | Dispatch priority (0-31, higher runs first). Silently ignored for `RUN` mode. |

The `ExecutionMode` enum is automatically registered in the GraphQL schema when any train uses both Run and Queue operations:

```graphql
enum ExecutionMode {
  RUN
  QUEUE
}
```

#### Example: Run + Queue train with typed output

A train `ServiceTrain<LookupPlayerInput, LookupPlayerOutput>` annotated with `[TraxMutation]` (default, both modes) produces:

```graphql
type LookupPlayerResponse {
  externalId: String!
  metadataId: Long
  output: LookupPlayerOutput
  workQueueId: Long
}

type LookupPlayerOutput {
  playerId: String!
  rank: Int!
  wins: Int!
  losses: Int!
  rating: Int!
}

# Run synchronously (default)
mutation {
  dispatch {
    lookupPlayer(input: { playerId: "player-42" }) {
      externalId
      metadataId
      output {
        playerId
        rank
        wins
        losses
        rating
      }
    }
  }
}

# Queue for async execution
mutation {
  dispatch {
    lookupPlayer(
      input: { playerId: "player-42" }
      mode: QUEUE
      priority: 5
    ) {
      externalId
      workQueueId
    }
  }
}
```

The output type is automatically registered as a GraphQL `ObjectType` and deduplicated. If multiple trains share the same output type, only one GraphQL type is generated.

### Run-Only Mode

When `GraphQLOperation.Run` is the only operation passed (e.g. `[TraxMutation(GraphQLOperation.Run)]`), the mutation always runs synchronously. No `mode` or `priority` parameters are generated.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `input` | `{TrainName}Input!` | Yes | Strongly-typed input matching the train's input record |

The response type still uses the unified format, but `workQueueId` will always be `null`.

### Queue-Only Mode

When `GraphQLOperation.Queue` is the only operation passed (e.g. `[TraxMutation(GraphQLOperation.Queue)]`), the mutation always queues. No `mode` parameter is generated, but `priority` is available.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `input` | `{TrainName}Input!` | Yes | N/A | Strongly-typed input matching the train's input record |
| `priority` | `Int` | No | `0` | Dispatch priority (0-31, higher runs first) |

The response type still uses the unified format, but `metadataId` and `output` will always be `null`.

---

## Operations Mutations

The whole namespace sits behind the operations gate (`GateOperations`, `RequireAuthorization` or `AllowAnonymousOperations`; see [AddTraxGraphQL](/docs/sdk-reference/graphql-api/add-trax-graphql)). Mutations that enqueue a train and input the caller chose (`requeueExecution`, `workQueue.queueTrain`) also apply that train's `[TraxAuthorize]` requirements. Mutations that enqueue what a manifest fixed (`triggerManifest`, `triggerManifestDelayed`, `triggerGroup`, dead-letter requeues) are governed by the gate alone. See [Authorization: The Operations Surface](/docs/authorization#the-operations-surface).

### triggerManifest

Triggers an immediate execution of a manifest, bypassing its normal schedule. A manifest holds at most one queued work queue entry, so when it already has one, nothing more is queued and that entry becomes the triggered run: it runs even if the manifest is disabled, and an entry due later (a retry waiting out its backoff) is brought forward to now. The mutation still succeeds.

```graphql
mutation {
  operations {
    triggerManifest(externalId: "order-processing-daily") {
      success
      message
    }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `externalId` | `String!` | Yes | The manifest's external ID |

**Returns**: `OperationResponse`

---

### triggerManifestDelayed

Triggers a manifest execution after a specified delay.

```graphql
mutation {
  operations {
    triggerManifestDelayed(
      externalId: "order-processing-daily"
      delay: "00:05:00"
    ) {
      success
      message
    }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `externalId` | `String!` | Yes | The manifest's external ID |
| `delay` | `TimeSpan!` | Yes | How long to wait before triggering (e.g. `"00:05:00"` for 5 minutes) |

**Returns**: `OperationResponse`

---

### disableManifest

Disables a manifest. Disabled manifests are skipped during scheduling cycles.

```graphql
mutation {
  operations {
    disableManifest(externalId: "order-processing-daily") {
      success
      message
    }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `externalId` | `String!` | Yes | The manifest's external ID |

**Returns**: `OperationResponse`

---

### enableManifest

Re-enables a previously disabled manifest.

```graphql
mutation {
  operations {
    enableManifest(externalId: "order-processing-daily") {
      success
      message
    }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `externalId` | `String!` | Yes | The manifest's external ID |

**Returns**: `OperationResponse`

---

### cancelManifest

Requests cancellation of every pending and running execution of a manifest. Sets `CancellationRequested` on each, which the run observes at its next junction boundary on any host; a run on the host that received the mutation is also cancelled at once.

```graphql
mutation {
  operations {
    cancelManifest(externalId: "order-processing-daily") {
      success
      count
      message
    }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `externalId` | `String!` | Yes | The manifest's external ID |

**Returns**: `OperationResponse` (includes `count`, the number of executions marked for cancellation)

---

### triggerGroup

Triggers immediate execution of all enabled manifests in a group. A manifest that already has a queued work queue entry is not queued again and does not stop the others being queued; its entry is brought forward to now if it was due later. Every entry the trigger touches runs even if its manifest is disabled afterwards.

```graphql
mutation {
  operations {
    triggerGroup(groupId: 1) {
      success
      count
      message
    }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `groupId` | `Long!` | Yes | The manifest group's database ID |

**Returns**: `OperationResponse` (includes `count`, the number of manifests queued, which leaves out those skipped as already queued; the server logs the skipped count)

---

### cancelGroup

Requests cancellation of every pending and running execution across all manifests in a group, by the same rule as [`cancelManifest`](#cancelmanifest).

```graphql
mutation {
  operations {
    cancelGroup(groupId: 1) {
      success
      count
      message
    }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `groupId` | `Long!` | Yes | The manifest group's database ID |

**Returns**: `OperationResponse` (includes `count`, the number of executions marked for cancellation)

---

### cancelExecution

Requests cancellation of a single execution by metadata id. Sets the durable
`cancel_requested` flag on the row when it is still `PENDING` or `IN_PROGRESS`; the process
running the train observes it at its next junction boundary and records the run as `CANCELLED`,
and a run on the host that received the mutation is cancelled at once. It goes through the same
`IOperationsService.CancelExecutionsAsync` call as the dashboard's Cancel button.

```graphql
mutation {
  operations {
    cancelExecution(id: 100) { success count message }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | `Long!` | Yes | The execution's metadata id |

**Returns**: `OperationResponse`. `count` is `1` when the execution was flagged. An execution that is missing or already terminal returns `success: false` with `count` `0`.

---

### cancelExecutions

Requests cancellation of many executions at once, as [`cancelExecution`](#cancelexecution) does for one. Every id still `PENDING` or `IN_PROGRESS` is flagged in one statement; terminal and unknown ids are skipped. Backs the dashboard's bulk cancel.

```graphql
mutation {
  operations {
    cancelExecutions(ids: [100, 101, 102]) { success count message }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `ids` | `[Long!]!` | Yes | 1 to 1000 execution metadata ids |

**Returns**: `OperationResponse`. `count` is the number flagged, zero included. An empty list, or more than 1000 ids, returns `success: false` and flags nothing.

---

### setManifestsEnabled

Enables or disables many manifests at once. Only manifests whose flag differs are written. Backs the dashboard's bulk enable and disable.

```graphql
mutation {
  operations {
    setManifestsEnabled(ids: [3, 4, 5], enabled: false) { success count message }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `ids` | `[Long!]!` | Yes | 1 to 1000 manifest database ids |
| `enabled` | `Boolean!` | Yes | The flag to set |

**Returns**: `OperationResponse`. `count` is the number of manifests changed, zero included. An empty list, or more than 1000 ids, returns `success: false` and changes nothing.

---

### requeueExecution

Re-queues an execution: reads its train name + input from the metadata row and enqueues a
fresh work queue entry for the dispatcher (the GraphQL counterpart of the dashboard's Re-queue
button). Both call [`IOperationsService.RequeueExecutionAsync`](/docs/sdk-reference/scheduler-api/i-operations-service#requeueexecutionasync),
so they refuse the same runs with the same messages. The enqueue goes through the same path as
[`queueTrain`](#queuetrain), so a caller who may not
run the train gets a GraphQL error with code `TRAX_AUTHORIZATION` (`"Not authorized."`) rather
than `success: false`.

The new run replays the decisions the execution recorded with
[`AddDecisionRecording`](/docs/sdk-reference/configuration/add-decision-recording), so it takes the
[tracks](/docs/core/decisions) the execution took instead of asking its deciders again. The replay link is set only here, to
the execution being re-queued, and only when it has decisions to replay (it recorded a decision,
or was itself a replaying requeue, so re-queueing a re-queue replays too); any other execution is
re-queued as an ordinary enqueue. `queueTrain` has no way to set it. See
[Re-queued runs replay their decisions](/docs/effect/decisions#re-queued-runs-replay-their-decisions).

An execution with no saved input is refused with `success: false` and a message saying inputs are
saved only when [`SaveTrainParameters()`](/docs/sdk-reference/configuration/save-train-parameters)
is on. So is one whose input was too large to save in full and was stored as the truncation
placeholder (`{"_truncated": true, ...}`), one stored as the `_unserializable` or `_disposed`
placeholder because it could not be saved, and one whose recorded input has a
[`[TraxSensitive]`](/docs/sdk-reference/configuration/save-train-parameters#masking-sensitive-fields) member masked as
`{"_redacted": true}`: re-queueing it would run the train with the mask in place of the value.
An execution whose train is no longer registered is refused with
`"Train {name} is no longer registered, so execution {id} cannot be re-queued."`, and one whose
saved input no longer reads as the train's input type, because the type changed shape since, with
`"The saved input of run {id} no longer reads as {InputType.FullName}: "` and the parser's message,
never as an invalid `InputJson` the caller did not send.
Enqueue refusals (a throwing `OnQueue`, an unusable
subject key, a deferred entry cancelled before confirmation) come back as `success: false`, with
the message rule [`queueTrain`](#queuetrain) describes, and an infrastructure failure is a masked GraphQL error, as for `queueTrain`. An enqueue reads a missing input as `{}`, so re-queueing it would re-run the train with
defaults rather than with what it ran with. This check runs before authorization, so it answers
the same for every caller; a missing execution id also returns `success: false`.

```graphql
mutation {
  operations {
    requeueExecution(id: 100) { success message }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | `Long!` | Yes | The execution's metadata id |

**Returns**: `OperationResponse`.

---

### updateManifest

Patches mutable settings on a single manifest. Each field on `input` is independent; a `null`
value leaves it unchanged. Set `clearTimeout: true` to remove the per-execution timeout.

```graphql
mutation {
  operations {
    updateManifest(id: 42, input: {
      isEnabled: false
      maxRetries: 5
      priority: 10
      scheduleType: CRON
      cronExpression: "0 3 * * *"
    }) { success message }
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `isEnabled` | `Boolean` | Enable/disable the manifest |
| `maxRetries` | `Int` | Retry budget |
| `priority` | `Int` | Dispatch priority |
| `timeoutSeconds` / `clearTimeout` | `Int` / `Boolean` | Per-execution timeout; `clearTimeout: true` removes it |
| `scheduleType` | `ScheduleType` | Schedule type |
| `cronExpression` | `String` | Cron expression |
| `intervalSeconds` | `Int` | Interval |

**Returns**: `OperationResponse` (`success: false` when the manifest id does not exist).

---

### setEffectEnabled

Turns an observational effect on or off in the API process, through the effect registry, the same calls the dashboard's [Effects page](/docs/dashboard#effects-page) makes. The change is in memory: it does not reach the scheduler or worker processes where trains usually run, and a restart restores the configured state.

```graphql
mutation {
  operations {
    setEffectEnabled(
      fullName: "Trax.Effect.Provider.Json.Services.JsonEffectFactory.JsonEffectProviderFactory"
      enabled: false
    ) {
      success
      count
      message
    }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `fullName` | `String!` | Yes | The effect factory's full type name, as [`operations.effects`](/docs/sdk-reference/graphql-api/queries#effects) reports it in `fullName`. Matched exactly. |
| `enabled` | `Boolean!` | Yes | `true` to turn the effect on, `false` to turn it off |

**Returns**: `OperationResponse`. `success` is false, and nothing changes, when no effect has that name or the effect was registered as not toggleable. On success `count` is 1.

---

### config (nested namespace)

The `operations.config` namespace patches scheduler runtime settings. A save writes only the fields it sets to the persisted `trax.scheduler_config` row, so it never rewrites a setting it did not name, and applies them to the host that received it at once. Every running scheduler host reads the row every few seconds and applies a new or changed one without a restart, so a save made on an API-only host, or on one of several scheduler hosts, reaches all of them. A scheduler applies a change from its next polling cycle, including a new polling or cleanup interval; `localWorkerCount` is the exception and applies when the worker pool next starts. The row also survives restarts: each scheduler applies it at startup over the settings configured in code.

The row records which settings a save named, in its `overrides`. A setting no save has named is not stored, so each scheduler keeps the value configured in code for it, and a later change in code applies to it. That is why any host can make a save, the first one included, whether or not it runs the scheduler: an API-only host built with `AddTraxJobRunner()` never has to supply values for settings it did not name. Two hosts making the first save at the same time both succeed; the one that loses the race applies its patch to the row the other created.

Which fields a save stores depends on the host. A field whose setting the row already names is stored when it differs from the stored value. For a setting the row does not name, a scheduler host stores the field only when it differs from the value that host runs with, so a save from the dashboard on a scheduler host leaves unchanged fields out. A host that does not run the scheduler cannot know that value, so it stores every field the patch sets, and each field it sends becomes a saved value that replaces the code value on every scheduler. `count` counts the fields stored.

A stored value takes precedence over the value in code until it is changed or the row is deleted, and deleting the row returns every running scheduler to its configured settings. Each scheduler logs a warning when a saved value replaces a different value configured in code, because a deploy that changes that setting in code then has no effect. The two dead-letter purge settings are the exception to "the saved value wins": when code states them too, the purge runs only if both `autoPurgeDeadLetters` values allow it, and the longer of the two `deadLetterRetentionPeriod` values applies (see [Dead Letter Auto-Purge](/docs/scheduler/dead-letters-and-cleanup#dead-letter-auto-purge)).

A scheduler that cannot read the row when it starts (the database is briefly unreachable, or the table is not migrated yet) logs a warning, runs with its code values, and applies the row at its first successful read.

#### updateScheduler

Patches one or more fields. Fields left out of `input` are unchanged. The persisted row's `updatedAt` only moves on real changes (no-op patches are ignored at the DB layer).

```graphql
mutation {
  operations {
    config {
      updateScheduler(input: {
        defaultMaxRetries: 5
        defaultJobTimeout: "00:30:00"
      }) { success count message }
    }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `input` | `UpdateSchedulerConfigInput!` | Yes | Patch payload (fields below) |

#### UpdateSchedulerConfigInput fields

Every field defaults to `null` and means "no change". To clear `maxActiveJobs` (set to "no per-group cap") or `localWorkerCount` (reset to `Environment.ProcessorCount`), set the corresponding `clear*` flag to `true`.

| Field | Type | Description |
|-------|------|-------------|
| `manifestManagerEnabled` | `Boolean` | |
| `jobDispatcherEnabled` | `Boolean` | |
| `manifestManagerPollingInterval` | `TimeSpan` | 1 second to 30 days |
| `jobDispatcherPollingInterval` | `TimeSpan` | 1 second to 30 days |
| `maxActiveJobs` | `Int` | At least 1 |
| `clearMaxActiveJobs` | `Boolean` | When `true`, sets `maxActiveJobs` to null |
| `defaultMaxRetries` | `Int` | Zero or more |
| `failureCountWindow` | `TimeSpan` | 1 second to ten years. How far back failed runs count toward retry backoff and `MaxRetries` |
| `defaultRetryDelay` | `TimeSpan` | Zero to ten years |
| `retryBackoffMultiplier` | `Float` | At least 1 |
| `maxRetryDelay` | `TimeSpan` | Zero to ten years |
| `defaultJobTimeout` | `TimeSpan` | 1 second to ten years |
| `stalePendingTimeout` | `TimeSpan` | 1 second to ten years |
| `recoverStuckJobsOnStartup` | `Boolean` | |
| `deadLetterRetentionPeriod` | `TimeSpan` | Zero to ten years |
| `autoPurgeDeadLetters` | `Boolean` | |
| `localWorkerCount` | `Int` | 1 to 256. Ignored when this process runs no local worker pool (see [ConfigureLocalWorkers](/docs/sdk-reference/scheduler-api/use-local-workers)). Applies when the worker pool next starts |
| `clearLocalWorkerCount` | `Boolean` | Resets `localWorkerCount` to `Environment.ProcessorCount` |
| `metadataCleanupInterval` | `TimeSpan` | 1 second to 30 days. Ignored when metadata cleanup is not configured |
| `metadataCleanupRetention` | `TimeSpan` | 1 second to ten years. Ignored when metadata cleanup is not configured |

**Returns**: `OperationResponse`. `count` is the number of fields actually changed (zero if every supplied value already matched). A value outside its range makes `success` `false`, with a `message` naming each offending field, and nothing in the patch is applied or persisted; so does a first save on a host that does not run the scheduler. The ranges are what the scheduler can run with: an interval is the wait between two polling cycles, which a timer caps at about 49 days, and polling the database more often than once a second is load rather than responsiveness. When a scheduler applies the row, a persisted value outside its range (from a row written before these checks, or edited by hand) is skipped with a warning, and the configured value stays in effect.

---

### manifestGroups (nested namespace)

The `operations.manifestGroups` namespace patches mutable fields on a manifest group and enables or disables groups in bulk. The dashboard calls the same underlying service, so a save from either surface produces an identical write.

#### updateManifestGroup

Patches one or more fields on a manifest group. Fields left out of `input` are unchanged; `updatedAt` is bumped only when at least one field actually changed.

```graphql
mutation {
  operations {
    manifestGroups {
      updateManifestGroup(id: 7, input: {
        priority: 5
        isEnabled: false
      }) { success count message }
    }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | `Long!` | Yes | The manifest group's database ID |
| `input` | `UpdateManifestGroupInput!` | Yes | Patch payload (fields below) |

#### UpdateManifestGroupInput fields

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `maxActiveJobs` | `Int` | `null` | New per-group concurrency limit, at least 1. `null` means "no change". To clear the limit, use `clearMaxActiveJobs` instead |
| `clearMaxActiveJobs` | `Boolean` | `false` | When `true`, sets `maxActiveJobs` to `null` (removes the per-group limit). Takes precedence if both this and `maxActiveJobs` are set |
| `priority` | `Int` | `null` | New priority, 0 to 31. `null` = no change |
| `isEnabled` | `Boolean` | `null` | Whether the group is active. `null` = no change |

**Returns**: `OperationResponse`. On success, `count` is the number of fields actually changed (zero if every supplied value already matched the persisted row). On failure (the group is not found, or a value is out of range), `success` is `false`, `message` explains, and no field is written.

#### setManifestGroupsEnabled

Enables or disables the listed groups. Only groups whose flag differs are written, with `updatedAt` bumped.

```graphql
mutation {
  operations {
    manifestGroups {
      setManifestGroupsEnabled(ids: [1, 2], enabled: false) { success count message }
    }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `ids` | `[Long!]!` | Yes | 1 to 1000 manifest group ids |
| `enabled` | `Boolean!` | Yes | The flag to set |

**Returns**: `OperationResponse`. `count` is the number of groups changed, zero included. An empty list, or more than 1000 ids, returns `success: false` and changes nothing: an empty list never means "every group".

#### setAllManifestGroupsEnabled

Enables or disables every manifest group. It is a field of its own so that "all" is something a caller asks for by name.

```graphql
mutation {
  operations {
    manifestGroups {
      setAllManifestGroupsEnabled(enabled: true) { success count message }
    }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `enabled` | `Boolean!` | Yes | The flag to set |

**Returns**: `OperationResponse`. `count` is the number of groups changed, zero included.

---

### workQueue (nested namespace)

The `operations.workQueue` namespace lets the dashboard (and other API clients) put work into the queue and cancel it. Reads live under [operations.workQueue in queries.md](/docs/sdk-reference/graphql-api/queries#workqueue-nested-under-operations).

#### queueTrain

Creates a new work queue entry. The dispatcher picks it up on its next poll. An unknown `trainName`, malformed or oversized `inputJson`, or JSON that deserializes to `null` returns `OperationResponse(success: false, message: ...)` and inserts nothing. For most trains nothing is written until every check has passed. A train that sets [`DeferQueuePromotion`](/docs/core/trains-and-junctions#making-the-side-effect-durable) is the exception: its entry is committed unconfirmed (never dispatched) before its `OnQueue` hook runs, and removed again if the hook throws, so a refused enqueue of such a train does briefly write a row.

Upgrading from a version where this mutation wrote the row itself: it now authorizes, fires `OnQueue` and stamps the subject key. See [Enqueue and Outcome Changes](/docs/migration-guides/enqueue-and-outcome-changes).

The entry is created through [`ITrainExecutionService.QueueAsync`](/docs/sdk-reference/mediator-api/train-execution#queueasync), so the train's `[TraxAuthorize]` requirements apply on top of the operations gate. Authorization runs before `inputJson` is read: a caller who may not run the train gets a GraphQL error with code `TRAX_AUTHORIZATION` and message `"Not authorized."`, not `success: false`, even when the input is malformed, and nothing is inserted. See [Authorization: The Operations Surface](/docs/authorization#the-operations-surface).

Four kinds of exception propagate out of the mutation rather than becoming `success: false`. Authorization (an `UnauthorizedAccessException`, which `TrainAuthorizationException` is) surfaces as the `TRAX_AUTHORIZATION` error above. Cancellation of the request ends it. An infrastructure failure, meaning a database, EF Core, network, I/O or timeout exception anywhere in the exception's chain (a `DbException` such as `NpgsqlException`, `DbUpdateException`, `TimeoutException`, `SocketException`, `HttpRequestException` or `IOException`), is logged on the server and arrives as a GraphQL error with HotChocolate's masked `"Unexpected Execution Error"` message, so nothing about the server reaches the caller. That includes a data-layer exception caused by the train's own `OnQueue` hook; a hook that means to refuse throws its own exception. The mediator's `TrainAuthorizationNotConfiguredException`, for a `[TraxAuthorize]` train on a host with no `ITrainAuthorizationService` registered, is a host misconfiguration, so it is logged and masked the same way.

Every other exception from the enqueue is a refusal and becomes `success: false`: invalid JSON as `"Invalid InputJson: "` followed by the parser's message, an oversized input as the generic `"The train input failed validation."` (neither the cap nor the input's size is echoed), and anything else as a refusal. That last group covers the train's `OnQueue` hook throwing, `QueueSubjectKey` throwing or returning an empty key, one that is only whitespace, or one longer than 512 Unicode characters, and a deferred entry being cancelled before it was confirmed (in which case the hook's side-effect may already have landed). A refusal's message is `"The enqueue was refused: "` followed by the exception's message only when that message was written for the caller: a plain `TrainException` (not a type derived from it), whose message is the train author's, or the mediator's `QueuedWorkCancelledException` and `QueueHookTimeoutException`. For any other exception it is the fixed `"The enqueue was refused."`, and the exception is logged at Warning on the server. A hook that refuses with a reason the caller should read throws `TrainException`. `Trax.Scheduler/docs/adr/0004` records the split.

```graphql
mutation {
  operations {
    workQueue {
      queueTrain(input: {
        trainName: "Trax.Samples.GameServer.Trains.Combat.IResolveCombatTrain"
        inputJson: "{\"attackerId\":\"player-1\",\"defenderId\":\"player-2\"}"
        priority: 10
      }) {
        success
        count
        message
      }
    }
  }
}
```

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `trainName` | `String!` | Yes | N/A | Train interface FullName, the `fullName` that [`operations.trains`](/docs/sdk-reference/graphql-api/queries#trains) returns (not its `serviceTypeName`, which is a display name) |
| `inputJson` | `String` | No | `null` | JSON payload that deserializes to the train's input type. `null` or blank is read as `{}`, so a train whose input needs no values can be queued without one; an input type that needs values (a positional record whose parameters have no defaults, or a `required` member) is refused. The JSON literal `null` is refused |
| `priority` | `Int` | No | `0` | Dispatch priority 0-31. Values outside that range are clamped |
| `scheduledAt` | `DateTime` | No | `null` | Earliest UTC time the entry should be picked up. Null means dispatch immediately |

**Returns**: `OperationResponse`. On success, `count` is `1` and `id` is the new work queue entry's id, which `message` also names.

#### runTrain

Runs a train now, through the same `IOperationsService.RunTrainAsync` call as the dashboard's Run dialog. The run's `PENDING` execution row is written and handed straight to the job submitter the train is routed to (its `[TraxRemote]` or builder route, otherwise the host's default), so it skips the work queue: dispatch ordering, group limits and the subject lock do not apply. Use [`queueTrain`](#queuetrain) when they should.

What the caller sees follows the operations payload convention:

- **Refused**: `success: false` with a `message`, and no execution row is written. An unknown `trainName`, invalid JSON (`"Invalid InputJson: "` and the parser's message), an oversized input (the generic `"The train input failed validation."`), or a refusal the train itself makes.
- **Not allowed**: the train's `[TraxAuthorize]` requirements apply on top of the operations gate and are checked before `inputJson` is read. A caller who may not run the train gets a GraphQL error with code `TRAX_AUTHORIZATION` and message `"Not authorized."`, even when the input is malformed.
- **Server failure**: when the submitter fails, the execution row is marked `FAILED` with that exception and the caller gets HotChocolate's masked `"Unexpected Execution Error"`. A database failure writing the row is reported the same way.

```graphql
mutation {
  operations {
    workQueue {
      runTrain(input: {
        trainName: "Trax.Samples.GameServer.Trains.Combat.IResolveCombatTrain"
        inputJson: "{\"attackerId\":\"player-1\",\"defenderId\":\"player-2\"}"
      }) {
        success
        id
        message
      }
    }
  }
}
```

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `trainName` | `String!` | Yes | N/A | Train interface FullName, as for [`queueTrain`](#queuetrain) |
| `inputJson` | `String` | No | `null` | JSON payload that deserializes to the train's input type. Property names match whatever their case and a property given twice is refused. `null` or blank is read as `{}` |

**Returns**: `OperationResponse`. On success, `id` is the execution's metadata id (pass it to `operations.execution` or `executionDetail`), not a work queue id, and `count` is `1`.

A host that exposes the operations mutations must register an `IJobSubmitter`, or it refuses to start; see [AddTraxGraphQL](/docs/sdk-reference/graphql-api/add-trax-graphql).

#### cancelWorkQueueEntry

Cancels a queued entry. Only entries with `status: QUEUED` can be cancelled. Already-dispatched or already-cancelled entries return `OperationResponse(success: false, ...)` without modifying the row.

```graphql
mutation {
  operations {
    workQueue {
      cancelWorkQueueEntry(id: 1234) { success message }
    }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | `Long!` | Yes | The work queue entry's database ID |

**Returns**: `OperationResponse`.

#### cancelWorkQueueEntries

Cancels many queued entries in one round-trip (a single set-based `UPDATE` through the same
`IOperationsService.CancelWorkQueueEntriesAsync` call as the dashboard). Only entries still
`QUEUED` are affected; already-dispatched or already-cancelled ids in the list are skipped.
Backs the dashboard's bulk-cancel selection.

```graphql
mutation {
  operations {
    workQueue {
      cancelWorkQueueEntries(ids: [1234, 1235, 1236]) { success count message }
    }
  }
}
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `ids` | `[Long!]!` | Yes | 1 to 1000 work queue entry ids to cancel |

**Returns**: `OperationResponse`. `count` is the number actually cancelled, zero included. An empty list, or more than 1000 ids, returns `success: false` (`"No ids were given."` for an empty one) and cancels nothing.

---

### deadLetters (nested namespace)

The `operations.deadLetters` namespace exposes dead-letter requeue and acknowledge mutations: `requeueDeadLetter`, `acknowledgeDeadLetter`, batch variants (`requeueDeadLetters`, `acknowledgeDeadLetters`), and "all" variants (`requeueAllDeadLetters`, `acknowledgeAllDeadLetters`). The batch variants take 1 to 1000 ids; an empty or longer list returns `success: false` and changes nothing. See [scheduler/dead-letters-and-cleanup](/docs/scheduler/dead-letters-and-cleanup) for full details and examples.

---

## OperationResponse

Shared response type for operations mutations.

| Field | Type | Description |
|-------|------|-------------|
| `success` | `Boolean!` | Whether the operation succeeded |
| `count` | `Int` | Number of affected records, for a mutation that acts on a set or patches fields; `null` otherwise |
| `id` | `Long` | The one row the operation acted on, when there is one: the work queue entry `queueTrain` and `requeueExecution` created, the execution `runTrain` started, the group `updateManifestGroup` patched. `null` otherwise |
| `message` | `String` | Human-readable status message |
