Repository: armadaproject/armada
Pull request #2343 (2023-04-06 18:44:15)

[armada-scheduler] Avoid retrying jobs on the same node
This PR adds the functionality to avoid nodes on retrying jobs

It will only avoid a node if:

* The lease was returned
* The runAttempted flag was set on the lease return

This is so if the lease returned for reasons that didn't involve then node - we don't avoid the node

The node avoidance is implemented using affinities and setting a node anti-affinity



┆Issue is synchronized with this [Jira Task](https://gr-oss.atlassian.net/browse/BATCH-142) by [Unito](https://www.unito.io)


codecov[bot]: ## [Codecov](https://codecov.io/gh/armadaproject/armada/pull/2343?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject) Report
Patch coverage: **`70.89`**% and project coverage change: **`+0.12`** :tada:
> Comparison is base [(`201ebc3`)](https://codecov.io/gh/armadaproject/armada/commit/201ebc38dc2c2ec0e88d3682f3b303f9c4367ab3?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject) 58.81% compared to head [(`69c1903`)](https://codecov.io/gh/armadaproject/armada/pull/2343?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject) 58.94%.

> :exclamation: Current head 69c1903 differs from pull request most recent head b2a23ff. Consider uploading reports for the commit b2a23ff to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #2343      +/-   ##
==========================================
+ Coverage   58.81%   58.94%   +0.12%     
==========================================
  Files         228      229       +1     
  Lines       28748    28922     +174     
==========================================
+ Hits        16909    17047     +138     
- Misses      10517    10542      +25     
- Partials     1322     1333      +11     
```

| Flag | Coverage Δ | |
|---|---|---|
| armada-server | `58.94% <70.89%> (+0.12%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/armadaproject/armada/pull/2343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject) | Coverage Δ | |
|---|---|---|
| [...ernal/armada/repository/apimessages/conversions.go](https://codecov.io/gh/armadaproject/armada/pull/2343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject#diff-aW50ZXJuYWwvYXJtYWRhL3JlcG9zaXRvcnkvYXBpbWVzc2FnZXMvY29udmVyc2lvbnMuZ28=) | `76.90% <0.00%> (-1.82%)` | :arrow_down: |
| [internal/scheduler/schedulerapp.go](https://codecov.io/gh/armadaproject/armada/pull/2343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject#diff-aW50ZXJuYWwvc2NoZWR1bGVyL3NjaGVkdWxlcmFwcC5nbw==) | `0.00% <0.00%> (ø)` | |
| [internal/scheduleringester/dbops.go](https://codecov.io/gh/armadaproject/armada/pull/2343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject#diff-aW50ZXJuYWwvc2NoZWR1bGVyaW5nZXN0ZXIvZGJvcHMuZ28=) | `81.52% <ø> (ø)` | |
| [internal/scheduleringester/schedulerdb.go](https://codecov.io/gh/armadaproject/armada/pull/2343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject#diff-aW50ZXJuYWwvc2NoZWR1bGVyaW5nZXN0ZXIvc2NoZWR1bGVyZGIuZ28=) | `59.86% <62.50%> (+0.15%)` | :arrow_up: |
| [internal/scheduler/scheduler.go](https://codecov.io/gh/armadaproject/armada/pull/2343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject#diff-aW50ZXJuYWwvc2NoZWR1bGVyL3NjaGVkdWxlci5nbw==) | `77.82% <71.01%> (-0.40%)` | :arrow_down: |
| [internal/scheduler/jobdb/job.go](https://codecov.io/gh/armadaproject/armada/pull/2343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject#diff-aW50ZXJuYWwvc2NoZWR1bGVyL2pvYmRiL2pvYi5nbw==) | `90.85% <77.77%> (-0.71%)` | :arrow_down: |
| [...l/scheduler/kubernetesobjects/affinity/affinity.go](https://codecov.io/gh/armadaproject/armada/pull/2343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject#diff-aW50ZXJuYWwvc2NoZWR1bGVyL2t1YmVybmV0ZXNvYmplY3RzL2FmZmluaXR5L2FmZmluaXR5Lmdv) | `93.33% <93.33%> (ø)` | |
| [internal/scheduler/jobdb/job\_run.go](https://codecov.io/gh/armadaproject/armada/pull/2343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject#diff-aW50ZXJuYWwvc2NoZWR1bGVyL2pvYmRiL2pvYl9ydW4uZ28=) | `87.14% <100.00%> (+1.42%)` | :arrow_up: |
| [internal/scheduler/submitcheck.go](https://codecov.io/gh/armadaproject/armada/pull/2343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject#diff-aW50ZXJuYWwvc2NoZWR1bGVyL3N1Ym1pdGNoZWNrLmdv) | `77.01% <100.00%> (+0.82%)` | :arrow_up: |
| [internal/scheduleringester/instructions.go](https://codecov.io/gh/armadaproject/armada/pull/2343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject#diff-aW50ZXJuYWwvc2NoZWR1bGVyaW5nZXN0ZXIvaW5zdHJ1Y3Rpb25zLmdv) | `72.06% <100.00%> (+0.81%)` | :arrow_up: |

... and [2 files with indirect coverage changes](https://codecov.io/gh/armadaproject/armada/pull/2343/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject)

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject)

</details>

[:umbrella: View full report in Codecov by Sentry](https://codecov.io/gh/armadaproject/armada/pull/2343?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=armadaproject).

JamesMurkin: Hi @d80tb7 

So I've made the changes:
 - Requeue jobs with an explicit event (which can also change scheduling info and is versioned)
 - Have an explicit queued state in the jobs table - so the scheduler doesn't need to work out the queued state each time
 
Functionally this works, I know there are some points that need tidying:
 - Naming could be improved
 - The update queries are not efficient. sqlc isn't helping me out here - but I can just write an efficient native query relatively easily (luckily postgres has a nice support for this)
 - A few other minor points
 
However before I finished off - I just wanted your overall opinion on if I'm butchering things too much
d80tb7: > Hi @d80tb7
> 
> So I've made the changes:
> 
>     * Requeue jobs with an explicit event (which can also change scheduling info and is versioned)
> 
>     * Have an explicit queued state in the jobs table - so the scheduler doesn't need to work out the queued state each time
> 
> 
> Functionally this works, I know there are some points that need tidying:
> 
>     * Naming could be improved
> 
>     * The update queries are not efficient. sqlc isn't helping me out here - but I can just write an efficient native query relatively easily (luckily postgres has a nice support for this)
> 
>     * A few other minor points
> 
> 
> However before I finished off - I just wanted your overall opinion on if I'm butchering things too much
> 
> Also I've included JobSchedulingInfo in the JobRequeued event - however I've for now imported it from our internal schedulerobjects proto
> 
> We could make duplicate proto and convert them? Or move JobSchedulingInfo. It is a bit awkward

Yeah this all looks good- I made a few coments- none of them serious I don't think.  Even the comment about the reordering of events in the ingester I don't think will cause any issues.

Re the proto- I think for now that's fine.  My personal view, which  admittedly isn't quite crystallized yet, is something like:
- all the proto types in schedulerObjects should be treated as types used for serialisation only- they shouldn't be used in side the scheduler itself.
- as internal serialised types they should live alongside the proto used for armadamessages which means importing one from the other should be fine.
- The Scheduler should use it's own internal types which are probably largely mirror the proto types, but have the advantage that we can control their code so as to store things as efficiently as possible for the scheduler.
- 

Patch:
diff --git a/client/DotNet/ArmadaProject.Io.Client/ArmadaProject.Io.Client.csproj b/client/DotNet/ArmadaProject.Io.Client/ArmadaProject.Io.Client.csproj
index e1d12f41..cd3d7a4b 100644
--- a/client/DotNet/ArmadaProject.Io.Client/ArmadaProject.Io.Client.csproj
+++ b/client/DotNet/ArmadaProject.Io.Client/ArmadaProject.Io.Client.csproj
@@ -36,7 +36,7 @@
       GrpcServices="None"
     />
     <Protobuf
-      Include="../../../pkg/**/*.proto"
+      Include="../../../pkg/api/*.proto"
       AdditionalProtocArguments="--proto_path=../../../proto"
       ProtoRoot="../../../"
       GrpcServices="Client"
diff --git a/internal/armada/repository/apimessages/conversions.go b/internal/armada/repository/apimessages/conversions.go
index 4a960aaf..3cf19b24 100644
--- a/internal/armada/repository/apimessages/conversions.go
+++ b/internal/armada/repository/apimessages/conversions.go
@@ -386,6 +386,19 @@ func FromInternalJobErrors(queueName string, jobSetName string, time time.Time,
 				},
 			}
 			events = append(events, event)
+		case *armadaevents.Error_MaxRunsExceeded:
+			event := &api.EventMessage{
+				Events: &api.EventMessage_Failed{
+					Failed: &api.JobFailedEvent{
+						JobId:    jobId,
+						JobSetId: jobSetName,
+						Queue:    queueName,
+						Created:  time,
+						Reason:   reason.MaxRunsExceeded.Message,
+					},
+				},
+			}
+			events = append(events, event)
 		default:
 			log.Warnf("unknown error %T for job %s", reason, jobId)
 			event := &api.EventMessage{
diff --git a/internal/scheduler/database/job_repository.go b/internal/scheduler/database/job_repository.go
index 823c210a..3d4252ef 100644
--- a/internal/scheduler/database/job_repository.go
+++ b/internal/scheduler/database/job_repository.go
@@ -150,12 +150,15 @@ func (r *PostgresJobRepository) FetchJobUpdates(ctx context.Context, jobSerial i
 				Queue:                   row.Queue,
 				Priority:                row.Priority,
 				Submitted:               row.Submitted,
+				Queued:                  row.Queued,
+				QueuedVersion:           row.QueuedVersion,
 				CancelRequested:         row.CancelRequested,
 				Cancelled:               row.Cancelled,
 				CancelByJobsetRequested: row.CancelByJobsetRequested,
 				Succeeded:               row.Succeeded,
 				Failed:                  row.Failed,
 				SchedulingInfo:          row.SchedulingInfo,
+				SchedulingInfoVersion:   row.SchedulingInfoVersion,
 				Serial:                  row.Serial,
 			}
 		}
@@ -194,8 +197,8 @@ func (r *PostgresJobRepository) FindInactiveRuns(ctx context.Context, runIds []u
 		FROM %s as tmp
 		LEFT JOIN runs ON (tmp.run_id = runs.run_id)
 		WHERE runs.run_id IS NULL
-		OR runs.succeeded = true 
- 		OR runs.failed = true 
+		OR runs.succeeded = true
+ 		OR runs.failed = true
 		OR runs.cancelled = true;`
 
 		rows, err := tx.Query(ctx, fmt.Sprintf(query, tmpTable))
@@ -233,13 +236,13 @@ func (r *PostgresJobRepository) FetchJobRunLeases(ctx context.Context, executor
 		query := `
 				SELECT jr.run_id, jr.node, j.queue, j.job_set, j.user_id, j.groups, j.submit_message
 				FROM runs jr
-				LEFT JOIN %s as tmp ON (tmp.run_id = jr.run_id)    
+				LEFT JOIN %s as tmp ON (tmp.run_id = jr.run_id)
 			    JOIN jobs j
 			    ON jr.job_id = j.job_id
 				WHERE jr.executor = $1
 			    AND tmp.run_id IS NULL
-				AND jr.succeeded = false 
-				AND jr.failed = false 
+				AND jr.succeeded = false
+				AND jr.failed = false
 				AND jr.cancelled = false
 				LIMIT %d;
 `
diff --git a/internal/scheduler/database/migrations/001_initialize_schema.up.sql b/internal/scheduler/database/migrations/001_initialize_schema.up.sql
index 9f9fe05f..fd8903a9 100644
--- a/internal/scheduler/database/migrations/001_initialize_schema.up.sql
+++ b/internal/scheduler/database/migrations/001_initialize_schema.up.sql
@@ -12,6 +12,10 @@ CREATE TABLE jobs (
     submitted bigint NOT NULL,
     groups bytea, -- compressed
     priority bigint NOT NULL,
+    -- Indicates if the job is queued
+    queued boolean NOT NULL default false,
+    -- The current version of the queued column, used to prevent queued state going backwards on event replay
+    queued_version integer NOT NULL default 0,
     -- Indicates that the user has requested the job be cancelled
     cancel_requested boolean NOT NULL DEFAULT false,
     -- Indicates if this job has been cancelled
@@ -26,6 +30,8 @@ CREATE TABLE jobs (
     submit_message bytea NOT NULL,
     -- JobSchedulingInfo message stored as a proto buffer.
     scheduling_info bytea NOT NULL,
+    -- The current version of the JobSchedulingInfo, used to prevent JobSchedulingInfo state going backwards on even replay
+    scheduling_info_version integer NOT NULL default 0,
     serial bigserial NOT NULL,
     last_modified timestamptz NOT NULL
 );
@@ -55,6 +61,8 @@ CREATE TABLE runs (
     failed boolean NOT NULL DEFAULT false,
     -- Set to true when the lease is returned by the executor.
     returned boolean NOT NULL DEFAULT false,
+    -- Set to true when the returned job run was given a chance to run. i.e unscheduled jobs will be false, image pull failure would be true
+    run_attempted boolean NOT NULL DEFAULT false,
     serial bigserial NOT NULL,
     last_modified timestamptz NOT NULL
 );
diff --git a/internal/scheduler/database/models.go b/internal/scheduler/database/models.go
index 8c08b2db..b70ec089 100644
--- a/internal/scheduler/database/models.go
+++ b/internal/scheduler/database/models.go
@@ -24,6 +24,8 @@ type Job struct {
 	Submitted               int64     `db:"submitted"`
 	Groups                  []byte    `db:"groups"`
 	Priority                int64     `db:"priority"`
+	Queued                  bool      `db:"queued"`
+	QueuedVersion           int32     `db:"queued_version"`
 	CancelRequested         bool      `db:"cancel_requested"`
 	Cancelled               bool      `db:"cancelled"`
 	CancelByJobsetRequested bool      `db:"cancel_by_jobset_requested"`
@@ -31,6 +33,7 @@ type Job struct {
 	Failed                  bool      `db:"failed"`
 	SubmitMessage           []byte    `db:"submit_message"`
 	SchedulingInfo          []byte    `db:"scheduling_info"`
+	SchedulingInfoVersion   int32     `db:"scheduling_info_version"`
 	Serial                  int64     `db:"serial"`
 	LastModified            time.Time `db:"last_modified"`
 }
@@ -64,6 +67,7 @@ type Run struct {
 	Succeeded    bool      `db:"succeeded"`
 	Failed       bool      `db:"failed"`
 	Returned     bool      `db:"returned"`
+	RunAttempted bool      `db:"run_attempted"`
 	Serial       int64     `db:"serial"`
 	LastModified time.Time `db:"last_modified"`
 }
diff --git a/internal/scheduler/database/query.sql.go b/internal/scheduler/database/query.sql.go
index 6c2bb5a3..71a92794 100644
--- a/internal/scheduler/database/query.sql.go
+++ b/internal/scheduler/database/query.sql.go
@@ -72,6 +72,15 @@ func (q *Queries) InsertMarker(ctx context.Context, arg InsertMarkerParams) erro
 	return err
 }
 
+const markJobRunsAttemptedById = `-- name: MarkJobRunsAttemptedById :exec
+UPDATE runs SET run_attempted = true WHERE run_id = ANY($1::UUID[])
+`
+
+func (q *Queries) MarkJobRunsAttemptedById(ctx context.Context, runIds []uuid.UUID) error {
+	_, err := q.db.Exec(ctx, markJobRunsAttemptedById, runIds)
+	return err
+}
+
 const markJobRunsFailedById = `-- name: MarkJobRunsFailedById :exec
 UPDATE runs SET failed = true WHERE run_id = ANY($1::UUID[])
 `
@@ -354,7 +363,7 @@ func (q *Queries) SelectJobsForExecutor(ctx context.Context, arg SelectJobsForEx
 }
 
 const selectNewJobs = `-- name: SelectNewJobs :many
-SELECT job_id, job_set, queue, user_id, submitted, groups, priority, cancel_requested, cancelled, cancel_by_jobset_requested, succeeded, failed, submit_message, scheduling_info, serial, last_modified FROM jobs WHERE serial > $1 ORDER BY serial LIMIT $2
+SELECT job_id, job_set, queue, user_id, submitted, groups, priority, queued, queued_version, cancel_requested, cancelled, cancel_by_jobset_requested, succeeded, failed, submit_message, scheduling_info, scheduling_info_version, serial, last_modified FROM jobs WHERE serial > $1 ORDER BY serial LIMIT $2
 `
 
 type SelectNewJobsParams struct {
@@ -379,6 +388,8 @@ func (q *Queries) SelectNewJobs(ctx context.Context, arg SelectNewJobsParams) ([
 			&i.Submitted,
 			&i.Groups,
 			&i.Priority,
+			&i.Queued,
+			&i.QueuedVersion,
 			&i.CancelRequested,
 			&i.Cancelled,
 			&i.CancelByJobsetRequested,
@@ -386,6 +397,7 @@ func (q *Queries) SelectNewJobs(ctx context.Context, arg SelectNewJobsParams) ([
 			&i.Failed,
 			&i.SubmitMessage,
 			&i.SchedulingInfo,
+			&i.SchedulingInfoVersion,
 			&i.Serial,
 			&i.LastModified,
 		); err != nil {
@@ -400,7 +412,7 @@ func (q *Queries) SelectNewJobs(ctx context.Context, arg SelectNewJobsParams) ([
 }
 
 const selectNewRuns = `-- name: SelectNewRuns :many
-SELECT run_id, job_id, created, job_set, executor, node, cancelled, running, succeeded, failed, returned, serial, last_modified FROM runs WHERE serial > $1 ORDER BY serial LIMIT $2
+SELECT run_id, job_id, created, job_set, executor, node, cancelled, running, succeeded, failed, returned, run_attempted, serial, last_modified FROM runs WHERE serial > $1 ORDER BY serial LIMIT $2
 `
 
 type SelectNewRunsParams struct {
@@ -429,6 +441,7 @@ func (q *Queries) SelectNewRuns(ctx context.Context, arg SelectNewRunsParams) ([
 			&i.Succeeded,
 			&i.Failed,
 			&i.Returned,
+			&i.RunAttempted,
 			&i.Serial,
 			&i.LastModified,
 		); err != nil {
@@ -443,7 +456,7 @@ func (q *Queries) SelectNewRuns(ctx context.Context, arg SelectNewRunsParams) ([
 }
 
 const selectNewRunsForJobs = `-- name: SelectNewRunsForJobs :many
-SELECT run_id, job_id, created, job_set, executor, node, cancelled, running, succeeded, failed, returned, serial, last_modified FROM runs WHERE serial > $1 AND job_id = ANY($2::text[]) ORDER BY serial
+SELECT run_id, job_id, created, job_set, executor, node, cancelled, running, succeeded, failed, returned, run_attempted, serial, last_modified FROM runs WHERE serial > $1 AND job_id = ANY($2::text[]) ORDER BY serial
 `
 
 type SelectNewRunsForJobsParams struct {
@@ -472,6 +485,7 @@ func (q *Queries) SelectNewRunsForJobs(ctx context.Context, arg SelectNewRunsFor
 			&i.Succeeded,
 			&i.Failed,
 			&i.Returned,
+			&i.RunAttempted,
 			&i.Serial,
 			&i.LastModified,
 		); err != nil {
@@ -511,7 +525,7 @@ func (q *Queries) SelectRunErrorsById(ctx context.Context, runIds []uuid.UUID) (
 }
 
 const selectUpdatedJobs = `-- name: SelectUpdatedJobs :many
-SELECT job_id, job_set, queue, priority, submitted, cancel_requested, cancel_by_jobset_requested, cancelled, succeeded, failed, scheduling_info, serial FROM jobs WHERE serial > $1 ORDER BY serial LIMIT $2
+SELECT job_id, job_set, queue, priority, submitted, queued, queued_version, cancel_requested, cancel_by_jobset_requested, cancelled, succeeded, failed, scheduling_info, scheduling_info_version, serial FROM jobs WHERE serial > $1 ORDER BY serial LIMIT $2
 `
 
 type SelectUpdatedJobsParams struct {
@@ -525,12 +539,15 @@ type SelectUpdatedJobsRow struct {
 	Queue                   string `db:"queue"`
 	Priority                int64  `db:"priority"`
 	Submitted               int64  `db:"submitted"`
+	Queued                  bool   `db:"queued"`
+	QueuedVersion           int32  `db:"queued_version"`
 	CancelRequested         bool   `db:"cancel_requested"`
 	CancelByJobsetRequested bool   `db:"cancel_by_jobset_requested"`
 	Cancelled               bool   `db:"cancelled"`
 	Succeeded               bool   `db:"succeeded"`
 	Failed                  bool   `db:"failed"`
 	SchedulingInfo          []byte `db:"scheduling_info"`
+	SchedulingInfoVersion   int32  `db:"scheduling_info_version"`
 	Serial                  int64  `db:"serial"`
 }
 
@@ -549,12 +566,15 @@ func (q *Queries) SelectUpdatedJobs(ctx context.Context, arg SelectUpdatedJobsPa
 			&i.Queue,
 			&i.Priority,
 			&i.Submitted,
+			&i.Queued,
+			&i.QueuedVersion,
 			&i.CancelRequested,
 			&i.CancelByJobsetRequested,
 			&i.Cancelled,
 			&i.Succeeded,
 			&i.Failed,
 			&i.SchedulingInfo,
+			&i.SchedulingInfoVersion,
 			&i.Serial,
 		); err != nil {
 			return nil, err
@@ -595,6 +615,36 @@ func (q *Queries) UpdateJobPriorityByJobSet(ctx context.Context, arg UpdateJobPr
 	return err
 }
 
+const updateJobQueued = `-- name: UpdateJobQueued :exec
+UPDATE jobs SET (queued, queued_version) = ($1, $2) WHERE job_id = $3 AND queued_version < $2
+`
+
+type UpdateJobQueuedParams struct {
+	Queued        bool   `db:"queued"`
+	QueuedVersion int32  `db:"queued_version"`
+	JobID         string `db:"job_id"`
+}
+
+func (q *Queries) UpdateJobQueued(ctx context.Context, arg UpdateJobQueuedParams) error {
+	_, err := q.db.Exec(ctx, updateJobQueued, arg.Queued, arg.QueuedVersion, arg.JobID)
+	return err
+}
+
+const updateJobSchedulingInfo = `-- name: UpdateJobSchedulingInfo :exec
+UPDATE jobs SET (scheduling_info, scheduling_info_version) = ($1, $2) WHERE job_id = $3 AND scheduling_info_version < $2
+`
+
+type UpdateJobSchedulingInfoParams struct {
+	SchedulingInfo        []byte `db:"scheduling_info"`
+	SchedulingInfoVersion int32  `db:"scheduling_info_version"`
+	JobID                 string `db:"job_id"`
+}
+
+func (q *Queries) UpdateJobSchedulingInfo(ctx context.Context, arg UpdateJobSchedulingInfoParams) error {
+	_, err := q.db.Exec(ctx, updateJobSchedulingInfo, arg.SchedulingInfo, arg.SchedulingInfoVersion, arg.JobID)
+	return err
+}
+
 const upsertExecutor = `-- name: UpsertExecutor :exec
 INSERT INTO executors (executor_id, last_request, last_updated)
 VALUES($1::text, $2::bytea, $3::timestamptz)
diff --git a/internal/scheduler/database/query/query.sql b/internal/scheduler/database/query/query.sql
index 9281d431..35f97340 100644
--- a/internal/scheduler/database/query/query.sql
+++ b/internal/scheduler/database/query/query.sql
@@ -5,7 +5,7 @@ SELECT * FROM jobs WHERE serial > $1 ORDER BY serial LIMIT $2;
 SELECT job_id FROM jobs;
 
 -- name: SelectUpdatedJobs :many
-SELECT job_id, job_set, queue, priority, submitted, cancel_requested, cancel_by_jobset_requested, cancelled, succeeded, failed, scheduling_info, serial FROM jobs WHERE serial > $1 ORDER BY serial LIMIT $2;
+SELECT job_id, job_set, queue, priority, submitted, queued, queued_version, cancel_requested, cancel_by_jobset_requested, cancelled, succeeded, failed, scheduling_info, scheduling_info_version, serial FROM jobs WHERE serial > $1 ORDER BY serial LIMIT $2;
 
 -- name: UpdateJobPriorityByJobSet :exec
 UPDATE jobs SET priority = $1 WHERE job_set = $2;
@@ -25,6 +25,12 @@ UPDATE jobs SET cancelled = true WHERE job_id = ANY(sqlc.arg(job_ids)::text[]);
 -- name: MarkJobsFailedById :exec
 UPDATE jobs SET failed = true WHERE job_id = ANY(sqlc.arg(job_ids)::text[]);
 
+-- name: UpdateJobQueued :exec
+UPDATE jobs SET (queued, queued_version) = ($1, $2) WHERE job_id = $3 AND queued_version < $2;
+
+-- name: UpdateJobSchedulingInfo :exec
+UPDATE jobs SET (scheduling_info, scheduling_info_version) = ($1, $2) WHERE job_id = $3 AND scheduling_info_version < $2;
+
 -- name: UpdateJobPriorityById :exec
 UPDATE jobs SET priority = $1 WHERE job_id = $2;
 
@@ -46,6 +52,9 @@ UPDATE runs SET failed = true WHERE run_id = ANY(sqlc.arg(run_ids)::UUID[]);
 -- name: MarkJobRunsReturnedById :exec
 UPDATE runs SET returned = true WHERE run_id = ANY(sqlc.arg(run_ids)::UUID[]);
 
+-- name: MarkJobRunsAttemptedById :exec
+UPDATE runs SET run_attempted = true WHERE run_id = ANY(sqlc.arg(run_ids)::UUID[]);
+
 -- name: MarkJobRunsRunningById :exec
 UPDATE runs SET running = true WHERE run_id = ANY(sqlc.arg(run_ids)::UUID[]);
 
@@ -90,4 +99,4 @@ SELECT executor_id, last_updated FROM executors;
 -- name: UpsertExecutor :exec
 INSERT INTO executors (executor_id, last_request, last_updated)
 VALUES(sqlc.arg(executor_id)::text, sqlc.arg(last_request)::bytea, sqlc.arg(update_time)::timestamptz)
-ON CONFLICT (executor_id) DO UPDATE SET (last_request, last_updated) = (excluded.last_request,excluded.last_updated);
\ No newline at end of file
+ON CONFLICT (executor_id) DO UPDATE SET (last_request, last_updated) = (excluded.last_request,excluded.last_updated);
diff --git a/internal/scheduler/jobdb/job.go b/internal/scheduler/jobdb/job.go
index 8e1c051b..9be374f4 100644
--- a/internal/scheduler/jobdb/job.go
+++ b/internal/scheduler/jobdb/job.go
@@ -30,6 +30,8 @@ type Job struct {
 	// True if the job is currently queued.
 	// If this is set then the job will not be considered for scheduling
 	queued bool
+	// The current version of the queued state
+	queuedVersion int32
 	// Scheduling requirements of this job.
 	jobSchedulingInfo *schedulerobjects.JobSchedulingInfo
 	// True if the user has requested this job be cancelled
@@ -61,6 +63,8 @@ func NewJob(
 	queue string,
 	priority uint32,
 	schedulingInfo *schedulerobjects.JobSchedulingInfo,
+	queued bool,
+	queuedVersion int32,
 	cancelRequested bool,
 	cancelByJobsetRequested bool,
 	cancelled bool,
@@ -70,7 +74,8 @@ func NewJob(
 		id:                      jobId,
 		jobset:                  jobset,
 		queue:                   queue,
-		queued:                  true,
+		queued:                  queued,
+		queuedVersion:           queuedVersion,
 		priority:                priority,
 		requestedPriority:       priority,
 		jobSchedulingInfo:       schedulingInfo,
@@ -162,6 +167,18 @@ func (job *Job) WithQueued(queued bool) *Job {
 	return j
 }
 
+// QueuedVersion returns current queued state version.
+func (job *Job) QueuedVersion() int32 {
+	return job.queuedVersion
+}
+
+// WithQueuedVersion returns a copy of the job with the queued version updated.
+func (job *Job) WithQueuedVersion(version int32) *Job {
+	j := copyJob(*job)
+	j.queuedVersion = version
+	return j
+}
+
 // CancelRequested returns true if the user has requested this job be cancelled.
 func (job *Job) CancelRequested() bool {
 	return job.cancelRequested
@@ -287,6 +304,23 @@ func (job *Job) NumReturned() uint {
 	return returned
 }
 
+// NumAttempts returns the number of times the executors tried to run this job
+// Note that this is O(N) on Runs, but this should be fine as the number of runs should be small.
+func (job *Job) NumAttempts() uint {
+	attempts := uint(0)
+	for _, run := range job.runsById {
+		if run.runAttempted {
+			attempts++
+		}
+	}
+	return attempts
+}
+
+// AllRuns returns all runs associated with job.
+func (job *Job) AllRuns() []*JobRun {
+	return maps.Values(job.runsById)
+}
+
 // LatestRun returns the currently active job run or nil if there are no runs yet.
 // Callers should either guard against nil values explicitly or via HasRuns.
 func (job *Job) LatestRun() *JobRun {
@@ -319,7 +353,7 @@ func (job *Job) WithCreated(created int64) *Job {
 	return j
 }
 
-// WithJobSchedulingInfo returns a copy of the job with the creation time updated.
+// WithJobSchedulingInfo returns a copy of the job with the job scheduling info updated.
 func (job *Job) WithJobSchedulingInfo(jobSchedulingInfo *schedulerobjects.JobSchedulingInfo) *Job {
 	j := copyJob(*job)
 	j.jobSchedulingInfo = jobSchedulingInfo
diff --git a/internal/scheduler/jobdb/job_run.go b/internal/scheduler/jobdb/job_run.go
index 7291100c..6d53c0a5 100644
--- a/internal/scheduler/jobdb/job_run.go
+++ b/internal/scheduler/jobdb/job_run.go
@@ -26,6 +26,8 @@ type JobRun struct {
 	cancelled bool
 	// True if the job has been returned by the executor.
 	returned bool
+	// True if the job has been returned and the job was given a chance to run.
+	runAttempted bool
 }
 
 func MinimalRun(id uuid.UUID, creationTime int64) *JobRun {
@@ -47,18 +49,20 @@ func CreateRun(
 	failed bool,
 	cancelled bool,
 	returned bool,
+	runAttempted bool,
 ) *JobRun {
 	return &JobRun{
-		id:        id,
-		jobId:     jobId,
-		created:   creationTime,
-		executor:  executor,
-		node:      node,
-		running:   running,
-		succeeded: succeeded,
-		failed:    failed,
-		cancelled: cancelled,
-		returned:  returned,
+		id:           id,
+		jobId:        jobId,
+		created:      creationTime,
+		executor:     executor,
+		node:         node,
+		running:      running,
+		succeeded:    succeeded,
+		failed:       failed,
+		cancelled:    cancelled,
+		returned:     returned,
+		runAttempted: runAttempted,
 	}
 }
 
@@ -135,13 +139,24 @@ func (run *JobRun) Returned() bool {
 	return run.returned
 }
 
-// WithReturned returns a copy of the job run with the returned status updated.
 func (run *JobRun) WithReturned(returned bool) *JobRun {
 	run = run.DeepCopy()
 	run.returned = returned
 	return run
 }
 
+// RunAttempted Returns true if the executor has attempted to run the job.
+func (run *JobRun) RunAttempted() bool {
+	return run.runAttempted
+}
+
+// WithAttempted returns a copy of the job run with the runAttempted status updated.
+func (run *JobRun) WithAttempted(attempted bool) *JobRun {
+	run = run.DeepCopy()
+	run.runAttempted = attempted
+	return run
+}
+
 // Created Returns the creation time of the job run
 func (run *JobRun) Created() int64 {
 	return run.created
diff --git a/internal/scheduler/kubernetesobjects/affinity/affinity.go b/internal/scheduler/kubernetesobjects/affinity/affinity.go
new file mode 100644
index 00000000..dd8709df
--- /dev/null
+++ b/internal/scheduler/kubernetesobjects/affinity/affinity.go
@@ -0,0 +1,67 @@
+package affinity
+
+import (
+	"fmt"
+
+	v1 "k8s.io/api/core/v1"
+
+	"github.com/armadaproject/armada/internal/common/util"
+)
+
+func AddNodeAntiAffinity(affinity *v1.Affinity, labelName string, labelValue string) error {
+	if affinity == nil {
+		return fmt.Errorf("failed to add not anti affinity, as provided affinity is nil")
+	}
+	ensureAffinityHasNodeSelectorTerms(affinity)
+	addAvoidNodeAffinityToNodeSelectorTerms(affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms, labelName, labelValue)
+	return nil
+}
+
+func ensureAffinityHasNodeSelectorTerms(affinity *v1.Affinity) {
+	if affinity.NodeAffinity == nil {
+		affinity.NodeAffinity = &v1.NodeAffinity{}
+	}
+	nodeAffinity := affinity.NodeAffinity
+
+	if nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil {
+		nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution = &v1.NodeSelector{}
+	}
+	ns := nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution
+
+	if len(ns.NodeSelectorTerms) == 0 {
+		ns.NodeSelectorTerms = []v1.NodeSelectorTerm{{}}
+	}
+}
+
+func addAvoidNodeAffinityToNodeSelectorTerms(terms []v1.NodeSelectorTerm, labelName string, labelValue string) {
+	// The node NotIn needs to be added to all NodeSelectorTerms, because if any single NodeSelectorTerm matches
+	// the pod is considered to match.
+	for i := range terms {
+		addAvoidNodeAffinityToNodeSelectorTerm(&terms[i], labelName, labelValue)
+	}
+}
+
+func addAvoidNodeAffinityToNodeSelectorTerm(term *v1.NodeSelectorTerm, labelName string, labelValue string) {
+	mexp := findMatchExpression(term.MatchExpressions, labelName, v1.NodeSelectorOpNotIn)
+	if mexp == nil {
+		term.MatchExpressions = append(term.MatchExpressions, v1.NodeSelectorRequirement{
+			Key:      labelName,
+			Operator: v1.NodeSelectorOpNotIn,
+			Values:   []string{},
+		})
+		mexp = &term.MatchExpressions[len(term.MatchExpressions)-1]
+	}
+
+	if !util.ContainsString(mexp.Values, labelValue) {
+		mexp.Values = append(mexp.Values, labelValue)
+	}
+}
+
+func findMatchExpression(matchExpressions []v1.NodeSelectorRequirement, key string, operator v1.NodeSelectorOperator) *v1.NodeSelectorRequirement {
+	for i, me := range matchExpressions {
+		if me.Key == key && me.Operator == operator {
+			return &matchExpressions[i]
+		}
+	}
+	return nil
+}
diff --git a/internal/scheduler/scheduler.go b/internal/scheduler/scheduler.go
index 887b247f..e3d73c51 100644
--- a/internal/scheduler/scheduler.go
+++ b/internal/scheduler/scheduler.go
@@ -2,6 +2,7 @@ package scheduler
 
 import (
 	"context"
+	"fmt"
 	"time"
 
 	"github.com/gogo/protobuf/proto"
@@ -9,11 +10,13 @@ import (
 	"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
 	"github.com/pkg/errors"
 	"golang.org/x/exp/maps"
+	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/util/clock"
 
 	"github.com/armadaproject/armada/internal/common/stringinterner"
 	"github.com/armadaproject/armada/internal/scheduler/database"
 	"github.com/armadaproject/armada/internal/scheduler/jobdb"
+	"github.com/armadaproject/armada/internal/scheduler/kubernetesobjects/affinity"
 	"github.com/armadaproject/armada/internal/scheduler/schedulerobjects"
 	"github.com/armadaproject/armada/pkg/armadaevents"
 )
@@ -38,12 +41,17 @@ type Scheduler struct {
 	leaderController LeaderController
 	// We intern strings to save memory
 	stringInterner *stringinterner.StringInterner
+	// This is used to check if jobs are still schedulable.
+	// Useful when we are adding node anti-affinities.
+	submitChecker SubmitScheduleChecker
 	// Responsible for publishing messages to Pulsar.  Only the leader publishes.
 	publisher Publisher
 	// Minimum duration between scheduling cycles.
 	cyclePeriod time.Duration
-	// Maximum number of times a lease can be returned before the job is considered failed.
-	maxLeaseReturns uint
+	// Maximum number of times a job can be attempted before being considered failed.
+	maxAttemptedRuns uint
+	// The label used when setting node anti affinities
+	nodeIdLabel string
 	// If an executor fails to report in for this amount of time,
 	// all jobs assigne to that executor are cancelled.
 	executorTimeout time.Duration
@@ -67,9 +75,11 @@ func NewScheduler(
 	leaderController LeaderController,
 	publisher Publisher,
 	stringInterner *stringinterner.StringInterner,
+	submitChecker SubmitScheduleChecker,
 	cyclePeriod time.Duration,
 	executorTimeout time.Duration,
-	maxLeaseReturns uint,
+	maxAttemptedRuns uint,
+	nodeIdLabel string,
 ) (*Scheduler, error) {
 	jobDb := jobdb.NewJobDb()
 	return &Scheduler{
@@ -79,11 +89,13 @@ func NewScheduler(
 		leaderController:   leaderController,
 		publisher:          publisher,
 		stringInterner:     stringInterner,
+		submitChecker:      submitChecker,
 		jobDb:              jobDb,
 		clock:              clock.RealClock{},
 		cyclePeriod:        cyclePeriod,
 		executorTimeout:    executorTimeout,
-		maxLeaseReturns:    maxLeaseReturns,
+		maxAttemptedRuns:   maxAttemptedRuns,
+		nodeIdLabel:        nodeIdLabel,
 		jobsSerial:         -1,
 		runsSerial:         -1,
 	}, nil
@@ -189,7 +201,7 @@ func (s *Scheduler) cycle(ctx context.Context, updateAll bool, leaderToken Leade
 		return err
 	}
 
-	resultEvents, err := s.eventsFromSchedulerResult(overallSchedulerResult)
+	resultEvents, err := s.eventsFromSchedulerResult(txn, overallSchedulerResult)
 	if err != nil {
 		return err
 	}
@@ -240,7 +252,10 @@ func (s *Scheduler) syncState(ctx context.Context) ([]*jobdb.Job, error) {
 			}
 		} else {
 			// make the scheduler job look like the db job.
-			job = updateSchedulerJob(job, &dbJob)
+			job, err = updateSchedulerJob(job, &dbJob)
+			if err != nil {
+				return nil, err
+			}
 		}
 		jobsToUpdateById[job.Id()] = job
 	}
@@ -273,24 +288,6 @@ func (s *Scheduler) syncState(ctx context.Context) ([]*jobdb.Job, error) {
 		jobsToUpdateById[jobId] = job
 	}
 
-	// Jobs with no active runs are queued.
-	for _, job := range jobsToUpdateById {
-		// Determine if the job needs to be re-queued.
-		// TODO: If have an explicit queued message, we can remove this code.
-		run := job.LatestRun()
-		desiredQueueState := false
-		requeueJob := run != nil && run.Returned() && job.NumReturned() <= s.maxLeaseReturns
-		if run == nil || requeueJob {
-			desiredQueueState = true
-		}
-		if requeueJob {
-			job = job.WithUpdatedRun(run.WithFailed(false))
-		}
-		if desiredQueueState != job.Queued() || requeueJob {
-			jobsToUpdateById[job.Id()] = job.WithQueued(desiredQueueState)
-		}
-	}
-
 	jobsToUpdate := maps.Values(jobsToUpdateById)
 	err = s.jobDb.BatchDelete(txn, jobsToDelete)
 	if err != nil {
@@ -310,8 +307,45 @@ func (s *Scheduler) syncState(ctx context.Context) ([]*jobdb.Job, error) {
 	return jobsToUpdate, nil
 }
 
+func (s *Scheduler) createSchedulingInfoWithNodeAntiAffinityForAttemptedRuns(job *jobdb.Job) (*schedulerobjects.JobSchedulingInfo, error) {
+	newSchedulingInfo := proto.Clone(job.JobSchedulingInfo()).(*schedulerobjects.JobSchedulingInfo)
+	newSchedulingInfo.Version = job.JobSchedulingInfo().Version + 1
+	podSchedulingRequirement := PodRequirementFromJobSchedulingInfo(newSchedulingInfo)
+	if podSchedulingRequirement == nil {
+		return nil, fmt.Errorf("no pod scheduling requirement found for job %s", job.GetId())
+	}
+	newAffinity := podSchedulingRequirement.Affinity
+	if newAffinity == nil {
+		newAffinity = &v1.Affinity{}
+	}
+
+	for _, run := range job.AllRuns() {
+		if run.RunAttempted() {
+			err := affinity.AddNodeAntiAffinity(newAffinity, s.nodeIdLabel, run.Node())
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+	podSchedulingRequirement.Affinity = newAffinity
+	return newSchedulingInfo, nil
+}
+
+func (s *Scheduler) addNodeAntiAffinitiesForAttemptedRunsIfSchedulable(job *jobdb.Job) (*jobdb.Job, bool, error) {
+	schedulingInfoWithNodeAntiAffinity, err := s.createSchedulingInfoWithNodeAntiAffinityForAttemptedRuns(job)
+	if err != nil {
+		return nil, false, err
+	}
+	podSchedulingRequirement := PodRequirementFromJobSchedulingInfo(schedulingInfoWithNodeAntiAffinity)
+	if podSchedulingRequirement == nil {
+		return nil, false, fmt.Errorf("no pod scheduling requirement found for job %s", job.GetId())
+	}
+	isSchedulable, _ := s.submitChecker.CheckPodRequirements(podSchedulingRequirement)
+	return job.WithJobSchedulingInfo(schedulingInfoWithNodeAntiAffinity), isSchedulable, nil
+}
+
 // eventsFromSchedulerResult generates necessary EventSequences from the provided SchedulerResult.
-func (s *Scheduler) eventsFromSchedulerResult(result *SchedulerResult) ([]*armadaevents.EventSequence, error) {
+func (s *Scheduler) eventsFromSchedulerResult(txn *jobdb.Txn, result *SchedulerResult) ([]*armadaevents.EventSequence, error) {
 	events := make([]*armadaevents.EventSequence, 0, len(result.PreemptedJobs)+len(result.ScheduledJobs))
 	for _, job := range PreemptedJobsFromSchedulerResult[*jobdb.Job](result) {
 		jobId, err := armadaevents.ProtoUuidFromUlidString(job.Id())
@@ -377,6 +411,12 @@ func (s *Scheduler) eventsFromSchedulerResult(result *SchedulerResult) ([]*armad
 		if err != nil {
 			return nil, err
 		}
+		job = job.WithQueuedVersion(job.QueuedVersion() + 1)
+		job = job.WithQueued(false)
+		err = s.jobDb.Upsert(txn, []*jobdb.Job{job})
+		if err != nil {
+			return nil, err
+		}
 		events = append(
 			events,
 			&armadaevents.EventSequence{
@@ -387,10 +427,11 @@ func (s *Scheduler) eventsFromSchedulerResult(result *SchedulerResult) ([]*armad
 						Created: s.now(),
 						Event: &armadaevents.EventSequence_Event_JobRunLeased{
 							JobRunLeased: &armadaevents.JobRunLeased{
-								RunId:      armadaevents.ProtoUuidFromUuid(job.LatestRun().Id()),
-								JobId:      jobId,
-								ExecutorId: job.LatestRun().Executor(),
-								NodeId:     job.LatestRun().Node(),
+								RunId:                armadaevents.ProtoUuidFromUuid(job.LatestRun().Id()),
+								JobId:                jobId,
+								ExecutorId:           job.LatestRun().Executor(),
+								NodeId:               job.LatestRun().Node(),
+								UpdateSequenceNumber: job.QueuedVersion(),
 							},
 						},
 					},
@@ -398,6 +439,7 @@ func (s *Scheduler) eventsFromSchedulerResult(result *SchedulerResult) ([]*armad
 			},
 		)
 	}
+
 	return events, nil
 }
 
@@ -484,19 +526,69 @@ func (s *Scheduler) generateUpdateMessagesFromJob(job *jobdb.Job, jobRunErrors m
 				},
 			}
 			events = append(events, jobSucceeded)
-		} else if lastRun.Failed() {
-			job = job.WithFailed(true).WithQueued(false)
-			runError := jobRunErrors[lastRun.Id()]
-			jobErrors := &armadaevents.EventSequence_Event{
-				Created: s.now(),
-				Event: &armadaevents.EventSequence_Event_JobErrors{
-					JobErrors: &armadaevents.JobErrors{
-						JobId:  jobId,
-						Errors: []*armadaevents.Error{runError},
+		} else if lastRun.Failed() && !job.Queued() {
+			requeueJob := lastRun.Returned() && job.NumAttempts() < s.maxAttemptedRuns
+
+			if requeueJob && lastRun.RunAttempted() {
+				jobWithAntiAffinity, schedulable, err := s.addNodeAntiAffinitiesForAttemptedRunsIfSchedulable(job)
+				if err != nil {
+					return nil, errors.Errorf("unable to set node anti-affinity for job %s because %s", job.GetId(), err)
+				} else {
+					if schedulable {
+						job = jobWithAntiAffinity
+					} else {
+						// If job is not schedulable with anti-affinity added. Do not requeue it and let it fail
+						requeueJob = false
+					}
+				}
+			}
+
+			if requeueJob {
+				job = job.WithUpdatedRun(lastRun.WithFailed(false))
+				job = job.WithQueued(true)
+				job = job.WithQueuedVersion(job.QueuedVersion() + 1)
+
+				requeueJobEvent := &armadaevents.EventSequence_Event{
+					Created: s.now(),
+					Event: &armadaevents.EventSequence_Event_JobRequeued{
+						JobRequeued: &armadaevents.JobRequeued{
+							JobId:                jobId,
+							SchedulingInfo:       job.JobSchedulingInfo(),
+							UpdateSequenceNumber: job.QueuedVersion(),
+						},
 					},
-				},
+				}
+
+				events = append(events, requeueJobEvent)
+			} else {
+				runError := jobRunErrors[lastRun.Id()]
+				job = job.WithFailed(true).WithQueued(false)
+				if lastRun.Returned() {
+					errorMessage := fmt.Sprintf("Maximum number of attempts (%d) reached - this job will no longer be retried", s.maxAttemptedRuns)
+					if job.NumAttempts() < s.maxAttemptedRuns {
+						errorMessage = fmt.Sprintf("Job was attempeted %d times, and has been tried once on all nodes it can run on - this job will no longer be retried", job.NumAttempts())
+					}
+					runError = &armadaevents.Error{
+						Terminal: true,
+						Reason: &armadaevents.Error_MaxRunsExceeded{
+							MaxRunsExceeded: &armadaevents.MaxRunsExceeded{
+								Message: errorMessage,
+							},
+						},
+					}
+				}
+				jobErrors := &armadaevents.EventSequence_Event{
+					Created: s.now(),
+					Event: &armadaevents.EventSequence_Event_JobErrors{
+						JobErrors: &armadaevents.JobErrors{
+							JobId:  jobId,
+							Errors: []*armadaevents.Error{runError},
+						},
+					},
+				}
+
+				events = append(events, jobErrors)
 			}
-			events = append(events, jobErrors)
 		}
 	} else if job.RequestedPriority() != job.Priority() {
 		job = job.WithPriority(job.RequestedPriority())
@@ -715,6 +807,8 @@ func (s *Scheduler) createSchedulerJob(dbJob *database.Job) (*jobdb.Job, error)
 		s.stringInterner.Intern(dbJob.Queue),
 		uint32(dbJob.Priority),
 		schedulingInfo,
+		dbJob.Queued,
+		dbJob.QueuedVersion,
 		dbJob.CancelRequested,
 		dbJob.CancelByJobsetRequested,
 		dbJob.Cancelled,
@@ -735,6 +829,7 @@ func (s *Scheduler) createSchedulerRun(dbRun *database.Run) *jobdb.JobRun {
 		dbRun.Failed,
 		dbRun.Cancelled,
 		dbRun.Returned,
+		dbRun.RunAttempted,
 	)
 }
 
@@ -767,11 +862,14 @@ func updateSchedulerRun(run *jobdb.JobRun, dbRun *database.Run) *jobdb.JobRun {
 	if dbRun.Returned && !run.Returned() {
 		run = run.WithReturned(true)
 	}
+	if dbRun.RunAttempted && !run.RunAttempted() {
+		run = run.WithAttempted(true)
+	}
 	return run
 }
 
 // updateSchedulerJob updates the scheduler job  (in-place) to match the database job
-func updateSchedulerJob(job *jobdb.Job, dbJob *database.Job) *jobdb.Job {
+func updateSchedulerJob(job *jobdb.Job, dbJob *database.Job) (*jobdb.Job, error) {
 	if dbJob.CancelRequested && !job.CancelRequested() {
 		job = job.WithCancelRequested(true)
 	}
@@ -790,5 +888,17 @@ func updateSchedulerJob(job *jobdb.Job, dbJob *database.Job) *jobdb.Job {
 	if uint32(dbJob.Priority) != job.RequestedPriority() {
 		job = job.WithRequestedPriority(uint32(dbJob.Priority))
 	}
-	return job
+	if uint32(dbJob.SchedulingInfoVersion) > job.JobSchedulingInfo().Version {
+		schedulingInfo := &schedulerobjects.JobSchedulingInfo{}
+		err := proto.Unmarshal(dbJob.SchedulingInfo, schedulingInfo)
+		if err != nil {
+			return nil, errors.Wrapf(err, "error unmarshalling scheduling info for job %s", dbJob.JobID)
+		}
+		job = job.WithJobSchedulingInfo(schedulingInfo)
+	}
+	if dbJob.QueuedVersion > job.QueuedVersion() {
+		job = job.WithQueuedVersion(dbJob.QueuedVersion)
+		job = job.WithQueued(dbJob.Queued)
+	}
+	return job, nil
 }
diff --git a/internal/scheduler/schedulerapp.go b/internal/scheduler/schedulerapp.go
index 0a77eeef..80b98e27 100644
--- a/internal/scheduler/schedulerapp.go
+++ b/internal/scheduler/schedulerapp.go
@@ -145,6 +145,18 @@ func Run(config Configuration) error {
 	if err != nil {
 		return errors.WithMessage(err, "error creating string interner")
 	}
+
+	submitChecker := NewSubmitChecker(
+		30*time.Minute,
+		config.Scheduling,
+		executorRepository,
+	)
+	services = append(services, func() error {
+		return submitChecker.Run(ctx)
+	})
+	if err != nil {
+		return errors.WithMessage(err, "error creating submit checker")
+	}
 	schedulingAlgo := NewLegacySchedulingAlgo(config.Scheduling, executorRepository, queueRepository)
 	scheduler, err := NewScheduler(
 		jobRepository,
@@ -153,9 +165,11 @@ func Run(config Configuration) error {
 		leaderController,
 		pulsarPublisher,
 		stringInterner,
+		submitChecker,
 		config.CyclePeriod,
 		config.ExecutorTimeout,
-		config.Scheduling.MaxRetries,
+		config.Scheduling.MaxRetries+1,
+		config.Scheduling.Preemption.NodeIdLabel,
 	)
 	if err != nil {
 		return errors.WithMessage(err, "error creating scheduler")
diff --git a/internal/scheduler/schedulerobjects/schedulerobjects.pb.go b/internal/scheduler/schedulerobjects/schedulerobjects.pb.go
index a5e7eeeb..cac69bbf 100644
--- a/internal/scheduler/schedulerobjects/schedulerobjects.pb.go
+++ b/internal/scheduler/schedulerobjects/schedulerobjects.pb.go
@@ -593,6 +593,7 @@ type JobSchedulingInfo struct {
 	Priority uint32 `protobuf:"varint,8,opt,name=priority,proto3" json:"priority,omitempty"`
 	// Kubernetes objects that make up this job and their respective scheduling requirements.
 	ObjectRequirements []*ObjectRequirements `protobuf:"bytes,5,rep,name=object_requirements,json=objectRequirements,proto3" json:"objectRequirements,omitempty"`
+	Version            uint32                `protobuf:"varint,9,opt,name=version,proto3" json:"version,omitempty"`
 }
 
 func (m *JobSchedulingInfo) Reset()         { *m = JobSchedulingInfo{} }
@@ -684,6 +685,13 @@ func (m *JobSchedulingInfo) GetObjectRequirements() []*ObjectRequirements {
 	return nil
 }
 
+func (m *JobSchedulingInfo) GetVersion() uint32 {
+	if m != nil {
+		return m.Version
+	}
+	return 0
+}
+
 // Message capturing the scheduling requirements of a particular Kubernetes object.
 type ObjectRequirements struct {
 	// Types that are valid to be assigned to Requirements:
@@ -953,129 +961,131 @@ func init() {
 }
 
 var fileDescriptor_97dadc5fbd620721 = []byte{
-	// 1952 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x19, 0xdf, 0x6f, 0x23, 0x47,
-	0x39, 0x9b, 0x9f, 0xf6, 0xe7, 0x5c, 0xe2, 0x4c, 0x72, 0xd7, 0x8d, 0xef, 0x6a, 0x87, 0xb4, 0x54,
-	0x29, 0xbd, 0xda, 0x6a, 0x8a, 0xc4, 0xe9, 0xe0, 0x25, 0x4e, 0x02, 0xe7, 0x70, 0x75, 0x72, 0x9b,
-	0x04, 0x04, 0x12, 0x5d, 0x8d, 0x77, 0x27, 0xbe, 0x6d, 0xd6, 0x33, 0xee, 0xee, 0xec, 0x51, 0xf7,
-	0x19, 0x90, 0x10, 0x52, 0x55, 0x01, 0x42, 0x48, 0x48, 0x20, 0xde, 0xf8, 0x0b, 0x78, 0xe1, 0x1f,
-	0xe8, 0x63, 0x1f, 0x79, 0xc1, 0xa0, 0xbb, 0x37, 0x4b, 0xbc, 0xf0, 0x17, 0xa0, 0x9d, 0xd9, 0xf5,
-	0x8e, 0x77, 0xed, 0x38, 0x77, 0x70, 0xbd, 0xa7, 0x78, 0xbe, 0xdf, 0x3f, 0xf7, 0xfb, 0x66, 0x02,
-	0xf7, 0x1d, 0xca, 0x89, 0x47, 0xb1, 0x5b, 0xf3, 0xad, 0xc7, 0xc4, 0x0e, 0x5c, 0xe2, 0x25, 0xbf,
-	0x58, 0xeb, 0x23, 0x62, 0x71, 0x3f, 0x03, 0xa8, 0x76, 0x3d, 0xc6, 0x19, 0x2a, 0xa6, 0xe1, 0xa5,
-	0x4a, 0x9b, 0xb1, 0xb6, 0x4b, 0x6a, 0x02, 0xdf, 0x0a, 0x2e, 0x6a, 0xdc, 0xe9, 0x10, 0x9f, 0xe3,
-	0x4e, 0x57, 0xb2, 0x94, 0xb6, 0x2f, 0xef, 0xf9, 0x55, 0x87, 0xd5, 0x70, 0xd7, 0xa9, 0x59, 0xcc,
-	0x23, 0xb5, 0x27, 0xef, 0xd5, 0xda, 0x84, 0x12, 0x0f, 0x73, 0x62, 0x47, 0x34, 0xdf, 0x4c, 0x68,
-	0x3a, 0xd8, 0x7a, 0xec, 0x50, 0xe2, 0xf5, 0x6a, 0xdd, 0xcb, 0xb6, 0x60, 0xf2, 0x88, 0xcf, 0x02,
-	0xcf, 0x22, 0x19, 0xae, 0x77, 0xdb, 0x0e, 0x7f, 0x1c, 0xb4, 0xaa, 0x16, 0xeb, 0xd4, 0xda, 0xac,
-	0xcd, 0x12, 0x1b, 0xc2, 0x93, 0x38, 0x88, 0x5f, 0x92, 0x7c, 0xfb, 0x2f, 0x73, 0x90, 0x3b, 0xfc,
-	0x84, 0x58, 0x01, 0x67, 0x1e, 0xda, 0x82, 0x59, 0xc7, 0xd6, 0xb5, 0x2d, 0x6d, 0x27, 0x5f, 0x2f,
-	0x0e, 0xfa, 0x95, 0x65, 0xc7, 0xbe, 0xcb, 0x3a, 0x0e, 0x27, 0x9d, 0x2e, 0xef, 0x19, 0xb3, 0x8e,
-	0x8d, 0xde, 0x82, 0xf9, 0x2e, 0x63, 0xae, 0x3e, 0x2b, 0x68, 0xd0, 0xa0, 0x5f, 0x59, 0x09, 0xcf,
-	0x0a, 0x95, 0xc0, 0xa3, 0x3d, 0x58, 0xa0, 0xcc, 0x26, 0xbe, 0x3e, 0xb7, 0x35, 0xb7, 0x53, 0xd8,
-	0xbd, 0x55, 0xcd, 0x84, 0xae, 0xc9, 0x6c, 0x52, 0x5f, 0x1f, 0xf4, 0x2b, 0xab, 0x82, 0x50, 0x91,
-	0x20, 0x39, 0xd1, 0x87, 0xb0, 0xd2, 0x71, 0xa8, 0xd3, 0x09, 0x3a, 0x47, 0xac, 0x75, 0xea, 0x7c,
-	0x4a, 0xf4, 0xf9, 0x2d, 0x6d, 0xa7, 0xb0, 0x5b, 0xce, 0xca, 0x32, 0xa2, 0x60, 0x3c, 0x74, 0x7c,
-	0x5e, 0xbf, 0xf5, 0x45, 0xbf, 0x32, 0x13, 0x1a, 0x36, 0xca, 0x6d, 0xa4, 0xce, 0xa1, 0x7c, 0x17,
-	0xfb, 0xfc, 0xbc, 0x6b, 0x63, 0x4e, 0xce, 0x9c, 0x0e, 0xd1, 0x17, 0x84, 0xfc, 0x52, 0x55, 0x26,
-	0xaf, 0x1a, 0x07, 0xae, 0x7a, 0x16, 0x27, 0xaf, 0x5e, 0x8a, 0x65, 0x8f, 0x72, 0x7e, 0xfe, 0xcf,
-	0x8a, 0x66, 0xa4, 0x60, 0xe8, 0x18, 0xd6, 0x03, 0x8a, 0x7d, 0xdf, 0x69, 0x53, 0x62, 0x9b, 0x1f,
-	0xb1, 0x96, 0xe9, 0x05, 0xd4, 0xd7, 0xf3, 0x5b, 0x73, 0x3b, 0xf9, 0x7a, 0x65, 0xd0, 0xaf, 0xdc,
-	0x4e, 0xd0, 0x47, 0xac, 0x65, 0x04, 0x54, 0x0d, 0xc2, 0x5a, 0x06, 0xb9, 0xfd, 0x0b, 0x04, 0xf3,
-	0x61, 0xd4, 0xae, 0x97, 0x26, 0x8a, 0x3b, 0x44, 0x5f, 0x4e, 0xd2, 0x14, 0x9e, 0xd5, 0x34, 0x85,
-	0x67, 0xf4, 0x01, 0xe4, 0x43, 0xab, 0x4d, 0x9f, 0x10, 0x2a, 0x72, 0x7a, 0xb5, 0xfb, 0x1b, 0x91,
-	0xfb, 0xb9, 0x90, 0xe9, 0x94, 0x10, 0x2a, 0x1c, 0x1f, 0x9e, 0xd0, 0x31, 0xe4, 0xc3, 0xdc, 0x99,
-	0xbc, 0xd7, 0x25, 0xfa, 0x5c, 0x24, 0x6e, 0x6c, 0xe6, 0xcf, 0x7a, 0x5d, 0x52, 0xbf, 0x35, 0xe8,
-	0x57, 0x10, 0x8d, 0x4e, 0x8a, 0x6d, 0xb9, 0x18, 0x86, 0xee, 0xc3, 0xf2, 0x50, 0xa0, 0xe9, 0xd8,
-	0xa2, 0x02, 0xf2, 0x75, 0x7d, 0xd0, 0xaf, 0x6c, 0xc4, 0x34, 0x0d, 0xd5, 0x77, 0x48, 0xa0, 0x68,
-	0x0f, 0x16, 0x39, 0x76, 0x28, 0xf7, 0xf5, 0x05, 0x51, 0x83, 0x9b, 0x55, 0xd9, 0x4f, 0x55, 0xdc,
-	0x75, 0xaa, 0x61, 0xcf, 0x55, 0x9f, 0xbc, 0x57, 0x3d, 0x0b, 0x29, 0xea, 0x2b, 0x91, 0x5f, 0x11,
-	0x83, 0x11, 0xfd, 0x45, 0x27, 0xb0, 0xe8, 0xe2, 0x16, 0x71, 0x7d, 0x7d, 0x51, 0x88, 0xd8, 0x1e,
-	0xef, 0x4c, 0xf5, 0xa1, 0x20, 0x3a, 0xa4, 0xdc, 0xeb, 0xd5, 0x37, 0x06, 0xfd, 0x4a, 0x51, 0x72,
-	0x29, 0x86, 0x45, 0x72, 0x90, 0x09, 0xab, 0x9c, 0x71, 0xec, 0x9a, 0x71, 0xff, 0xfa, 0xfa, 0xd2,
-	0xf3, 0x55, 0xb5, 0x60, 0x8f, 0x51, 0xbe, 0x91, 0x3a, 0xa3, 0xbf, 0x6a, 0xf0, 0x26, 0x76, 0x5d,
-	0x66, 0x61, 0x8e, 0x5b, 0x2e, 0x31, 0x5b, 0x3d, 0xb3, 0xeb, 0x39, 0xcc, 0x73, 0x78, 0xcf, 0xc4,
-	0xd4, 0x1e, 0xea, 0xd5, 0x73, 0xc2, 0xa3, 0xef, 0x4c, 0xf0, 0x68, 0x2f, 0x11, 0x51, 0xef, 0x9d,
-	0x44, 0x02, 0xf6, 0xa8, 0x1d, 0x2b, 0x92, 0xbe, 0xee, 0x44, 0x46, 0x6d, 0xe1, 0x29, 0xe4, 0xc6,
-	0x54, 0x0a, 0xe4, 0xc1, 0xba, 0xcf, 0x31, 0x17, 0x16, 0x47, 0xcd, 0x12, 0x66, 0x3c, 0x2f, 0xcc,
-	0x7c, 0x67, 0x82, 0x99, 0xa7, 0x21, 0x47, 0xbd, 0x27, 0x3b, 0xa4, 0x61, 0x4b, 0xab, 0x5e, 0x8b,
-	0xac, 0x5a, 0xf5, 0x47, 0xb1, 0x46, 0x1a, 0x80, 0x02, 0x58, 0x8f, 0xec, 0x22, 0x76, 0xac, 0xd7,
-	0xb1, 0x75, 0x10, 0x3a, 0xef, 0x5e, 0x1d, 0x1a, 0x62, 0x0b, 0x41, 0xb1, 0x52, 0x3d, 0x52, 0x5a,
-	0xc4, 0x29, 0xb4, 0x91, 0x81, 0x20, 0x0e, 0x68, 0x44, 0xed, 0xc7, 0x01, 0x09, 0x88, 0x5e, 0xb8,
-	0xae, 0xd6, 0x47, 0x21, 0xf9, 0x64, 0xad, 0x02, 0x6d, 0x64, 0x20, 0xa1, 0xb3, 0xe4, 0x89, 0x63,
-	0xf1, 0xe4, 0x63, 0x64, 0x3a, 0xb6, 0xaf, 0xaf, 0x5c, 0xa9, 0xf6, 0x50, 0x72, 0xc4, 0x11, 0xf3,
-	0x53, 0x6a, 0x49, 0x0a, 0x6d, 0x64, 0x20, 0xe8, 0xcf, 0x1a, 0x94, 0x29, 0xa3, 0x26, 0xf6, 0x3a,
-	0xd8, 0xc6, 0x66, 0xe2, 0x78, 0xd2, 0x01, 0x37, 0x84, 0x09, 0xdf, 0x9a, 0x60, 0x42, 0x93, 0xd1,
-	0x3d, 0xc1, 0x3b, 0x0c, 0xc1, 0xb0, 0xda, 0xa5, 0x35, 0x6f, 0x44, 0xd6, 0xdc, 0xa6, 0x93, 0x29,
-	0x8d, 0xab, 0x90, 0x25, 0x0c, 0x05, 0xa5, 0x85, 0xd1, 0x1b, 0x30, 0x77, 0x49, 0x7a, 0xd1, 0x07,
-	0x76, 0x6d, 0xd0, 0xaf, 0xdc, 0xb8, 0x24, 0x3d, 0xa5, 0x99, 0x43, 0x2c, 0x7a, 0x1b, 0x16, 0x9e,
-	0x60, 0x37, 0x20, 0xd1, 0x28, 0x14, 0x93, 0x4c, 0x00, 0xd4, 0x49, 0x26, 0x00, 0xf7, 0x67, 0xef,
-	0x69, 0xa5, 0x3f, 0x68, 0xf0, 0xf5, 0x6b, 0x35, 0x95, 0xaa, 0x7d, 0x61, 0xa2, 0xf6, 0x86, 0xaa,
-	0x7d, 0xfa, 0xd7, 0x63, 0x9a, 0x75, 0xbf, 0xd4, 0x60, 0x63, 0x5c, 0x2f, 0x5d, 0x2f, 0x14, 0x0f,
-	0x54, 0x63, 0x56, 0x76, 0x5f, 0xcf, 0x1a, 0x23, 0x85, 0x4a, 0x0d, 0xd3, 0x6c, 0xf9, 0x95, 0x06,
-	0x37, 0xc7, 0xf6, 0xd8, 0xf5, 0x8c, 0xf9, 0x3f, 0x47, 0x26, 0x65, 0x4d, 0xd2, 0x7b, 0xaf, 0xc4,
-	0x9a, 0x4b, 0xb8, 0x39, 0xb6, 0x23, 0x5f, 0xa0, 0x64, 0x73, 0x53, 0x95, 0xfd, 0x4e, 0x83, 0xad,
-	0x69, 0xcd, 0xf7, 0x2a, 0xaa, 0x75, 0xfb, 0x8f, 0xf3, 0x90, 0x8b, 0x97, 0x88, 0x70, 0x19, 0x6a,
-	0x8c, 0x2c, 0x43, 0x23, 0x0b, 0xc1, 0xec, 0xc8, 0x22, 0x30, 0xfb, 0xa2, 0x8b, 0xc0, 0xd9, 0x70,
-	0x11, 0x90, 0xfb, 0xec, 0x5b, 0x93, 0xb7, 0x9a, 0xe7, 0x58, 0x06, 0x7e, 0xa6, 0x01, 0x0a, 0xa8,
-	0x4f, 0x78, 0x83, 0xda, 0xe4, 0x13, 0x62, 0x4b, 0x4e, 0x7d, 0x5e, 0xa8, 0xd8, 0xbd, 0x42, 0xc5,
-	0x79, 0x86, 0x49, 0xaa, 0xdb, 0x1a, 0xf4, 0x2b, 0x77, 0xb2, 0x12, 0x15, 0xd5, 0x63, 0xf4, 0x7d,
-	0x15, 0x5f, 0xbf, 0x0e, 0xbc, 0x36, 0xc1, 0xe6, 0x97, 0xa1, 0x6e, 0xfb, 0x3f, 0x73, 0xb0, 0x29,
-	0x3a, 0x75, 0xdf, 0x0d, 0x7c, 0x4e, 0xbc, 0xb8, 0xba, 0xce, 0x7d, 0xdc, 0x26, 0xa8, 0x01, 0x4b,
-	0x96, 0x47, 0xc2, 0x5a, 0x16, 0x5a, 0xaf, 0x5e, 0x79, 0xd7, 0xa3, 0x8a, 0x88, 0x59, 0xc4, 0xc6,
-	0x1b, 0x1f, 0x42, 0xbb, 0xe4, 0xf0, 0x56, 0xec, 0x12, 0x00, 0xd5, 0x2e, 0x01, 0x40, 0xf7, 0x00,
-	0x48, 0x74, 0xcf, 0x6a, 0xd8, 0x62, 0x39, 0x8e, 0x16, 0xd9, 0x04, 0xaa, 0x2e, 0xb2, 0x09, 0x14,
-	0xfd, 0x46, 0x83, 0xf5, 0xe1, 0xb0, 0x4c, 0x06, 0x47, 0x54, 0x27, 0x07, 0xd9, 0x3a, 0x99, 0xe8,
-	0xfa, 0xb0, 0xcd, 0x14, 0x31, 0xb2, 0x72, 0x6e, 0x47, 0x6e, 0x8e, 0x53, 0x64, 0x8c, 0x03, 0x96,
-	0x3e, 0xd3, 0x40, 0x9f, 0x24, 0xee, 0x95, 0x7c, 0x15, 0xfe, 0x36, 0x07, 0xa5, 0x71, 0x4e, 0x1b,
-	0xa4, 0xcb, 0x3c, 0x3e, 0xbc, 0xb9, 0x6a, 0x53, 0x6e, 0xae, 0x4a, 0x75, 0xcc, 0xfe, 0x8f, 0xd5,
-	0xf1, 0x99, 0x06, 0x45, 0x25, 0x74, 0x22, 0x2d, 0xd1, 0x07, 0xa4, 0x9e, 0x75, 0x76, 0xb2, 0xed,
-	0x6a, 0xda, 0x94, 0xe5, 0xaf, 0x3c, 0xe8, 0x57, 0x4a, 0x69, 0xf9, 0x8a, 0x3f, 0x19, 0xdd, 0xa5,
-	0xdf, 0x6b, 0x70, 0x73, 0xac, 0xac, 0xeb, 0x75, 0xe1, 0x0f, 0x46, 0x13, 0xf6, 0xce, 0x73, 0x54,
-	0xde, 0xd4, 0xec, 0xfd, 0x7c, 0x16, 0x96, 0xd5, 0x74, 0xa3, 0x0f, 0x21, 0x9f, 0x6c, 0x88, 0x9a,
-	0x08, 0xda, 0xbb, 0x57, 0x57, 0x48, 0x35, 0xb5, 0x17, 0xae, 0x45, 0xc9, 0x49, 0xe4, 0x18, 0xc9,
-	0xcf, 0xd2, 0x6f, 0x35, 0x58, 0x99, 0x3c, 0xcb, 0x26, 0x07, 0xe1, 0x47, 0xa3, 0x41, 0xa8, 0x2a,
-	0xc3, 0x64, 0xf8, 0x4a, 0x53, 0xed, 0x5e, 0xb6, 0xc5, 0x74, 0x89, 0xd5, 0x55, 0x1f, 0x05, 0x98,
-	0x72, 0x87, 0xf7, 0xa6, 0xc6, 0xe1, 0x1f, 0xf3, 0xb0, 0x76, 0xc4, 0x5a, 0xa7, 0xd2, 0x51, 0x87,
-	0xb6, 0x1b, 0xf4, 0x82, 0xa1, 0x5d, 0xc8, 0xb9, 0xce, 0x05, 0xe1, 0x4e, 0x87, 0x08, 0xf3, 0x6e,
-	0xc8, 0xbb, 0x73, 0x0c, 0x53, 0xef, 0xce, 0x31, 0x2c, 0xbc, 0x3b, 0x63, 0x6e, 0x76, 0x98, 0xcf,
-	0x4d, 0x46, 0xad, 0x78, 0xe8, 0x8b, 0x4f, 0x0e, 0xe6, 0x1f, 0x30, 0x9f, 0x1f, 0x53, 0x4b, 0xe5,
-	0x84, 0x04, 0x8a, 0xbe, 0x0d, 0x85, 0xae, 0x47, 0x42, 0xb8, 0xd3, 0x72, 0xe5, 0x55, 0x3e, 0x57,
-	0xdf, 0x1c, 0xf4, 0x2b, 0x37, 0x15, 0xb0, 0xc2, 0xab, 0x52, 0xa3, 0x07, 0x50, 0xb4, 0x18, 0xb5,
-	0x02, 0xcf, 0x23, 0xd4, 0xea, 0x99, 0x3e, 0xbe, 0x90, 0x4f, 0x37, 0xb9, 0xfa, 0xeb, 0x83, 0x7e,
-	0x65, 0x53, 0xc1, 0x9d, 0xe2, 0x0b, 0x55, 0xca, 0x6a, 0x0a, 0x85, 0x8e, 0x61, 0x7d, 0x78, 0x79,
-	0xb5, 0x5c, 0xec, 0xfb, 0xa6, 0x78, 0xd5, 0x58, 0x14, 0x09, 0x12, 0x4f, 0x28, 0x31, 0x7a, 0x3f,
-	0xc4, 0x36, 0x47, 0x9f, 0x38, 0xd6, 0x32, 0x48, 0x74, 0x0a, 0x05, 0x3f, 0x68, 0x75, 0x1c, 0x6e,
-	0x8a, 0x50, 0x2e, 0x4d, 0x6d, 0xf0, 0xf8, 0xda, 0x0d, 0x92, 0x6d, 0xf8, 0xd8, 0xa3, 0x9c, 0xc3,
-	0xe4, 0xc4, 0x9a, 0xf4, 0x5c, 0x92, 0x9c, 0x18, 0xa6, 0x26, 0x27, 0x86, 0xa1, 0x9f, 0xc2, 0xba,
-	0x2c, 0x61, 0xd3, 0x23, 0x1f, 0x07, 0x8e, 0x47, 0x3a, 0x24, 0x79, 0xa9, 0x78, 0x33, 0x5b, 0xe7,
-	0xc7, 0xe2, 0xaf, 0xa1, 0xd0, 0xca, 0x61, 0xcf, 0x32, 0x70, 0x75, 0xd8, 0x67, 0xb1, 0xdb, 0xbf,
-	0xd6, 0x00, 0x65, 0x85, 0x21, 0x17, 0x56, 0xbb, 0xcc, 0x56, 0x41, 0xd1, 0x6c, 0xfc, 0x5a, 0xd6,
-	0x96, 0x93, 0x51, 0x42, 0x99, 0xd5, 0x14, 0x77, 0x62, 0xc5, 0x83, 0x19, 0x23, 0x2d, 0xba, 0xbe,
-	0x02, 0xcb, 0xaa, 0xdb, 0xdb, 0xff, 0x5e, 0x84, 0xd5, 0x94, 0x54, 0xe4, 0xcb, 0xa7, 0x9f, 0x53,
-	0xe2, 0x12, 0x8b, 0x33, 0x2f, 0xfa, 0x04, 0xbc, 0x3f, 0xd5, 0x1c, 0xb1, 0x25, 0xc5, 0x5c, 0xf2,
-	0x43, 0x50, 0x1a, 0xf4, 0x2b, 0xb7, 0x54, 0x61, 0x4a, 0x8c, 0x46, 0x94, 0xa0, 0x13, 0xc8, 0xe1,
-	0x8b, 0x0b, 0x87, 0x86, 0xa9, 0x94, 0xfd, 0x7d, 0x67, 0xdc, 0xb2, 0xb8, 0x17, 0xd1, 0xc8, 0x44,
-	0xc7, 0x1c, 0x6a, 0xa2, 0x63, 0x18, 0x3a, 0x87, 0x02, 0x67, 0x2e, 0xf1, 0x30, 0x77, 0x18, 0x8d,
-	0xd7, 0xc7, 0xf2, 0xd8, 0x0d, 0x74, 0x48, 0x36, 0x1c, 0x2b, 0x2a, 0xab, 0xa1, 0x1e, 0x10, 0x83,
-	0x02, 0xa6, 0x94, 0xf1, 0x48, 0xec, 0xd2, 0xa4, 0x95, 0x31, 0x1d, 0x9c, 0xbd, 0x84, 0x49, 0xc6,
-	0x46, 0x34, 0xb5, 0x22, 0x4a, 0x6d, 0x6a, 0x05, 0x3c, 0x52, 0xe4, 0xf3, 0x62, 0xac, 0x4f, 0x2f,
-	0xf2, 0x23, 0x28, 0xc6, 0xdf, 0x05, 0x46, 0x4f, 0x98, 0xeb, 0x58, 0x3d, 0xf1, 0xc6, 0x9a, 0x97,
-	0xa3, 0x2b, 0x8d, 0x53, 0x47, 0x57, 0x1a, 0x87, 0x3e, 0x85, 0x8d, 0xf8, 0x63, 0x3a, 0x52, 0xa5,
-	0x8b, 0x22, 0x4b, 0x3b, 0xe3, 0x02, 0x6a, 0x8c, 0xa1, 0xaf, 0xdf, 0x89, 0x42, 0x3b, 0x56, 0x9a,
-	0x31, 0x16, 0x5a, 0x6a, 0xc3, 0x5a, 0xa6, 0xa8, 0x5e, 0xca, 0x9a, 0x7c, 0x01, 0xc5, 0x74, 0x82,
-	0x5e, 0xca, 0x7e, 0xfc, 0x27, 0x0d, 0x36, 0x4f, 0x02, 0xd7, 0xc7, 0xde, 0x69, 0x5c, 0x30, 0x47,
-	0xac, 0x75, 0x40, 0x38, 0x76, 0x5c, 0x3f, 0x14, 0x26, 0x2e, 0xdd, 0x91, 0x4e, 0x21, 0x4c, 0x00,
-	0x54, 0x61, 0xf2, 0x25, 0xeb, 0x6d, 0x58, 0x78, 0x94, 0xde, 0x7f, 0xd3, 0x6b, 0x88, 0xa4, 0x40,
-	0x77, 0x61, 0x31, 0x9c, 0x6b, 0x84, 0x47, 0xbb, 0xaf, 0xb8, 0x1a, 0x49, 0x88, 0x7a, 0x35, 0x92,
-	0x90, 0x6f, 0x1c, 0x43, 0x41, 0x79, 0x33, 0x40, 0x05, 0x58, 0x3a, 0x6f, 0x7e, 0xbf, 0x79, 0xfc,
-	0xc3, 0x66, 0x71, 0x26, 0x3c, 0x9c, 0x1c, 0x36, 0x0f, 0x1a, 0xcd, 0xef, 0x15, 0xb5, 0xf0, 0x60,
-	0x9c, 0x37, 0x9b, 0xe1, 0x61, 0x16, 0xdd, 0x80, 0xfc, 0xe9, 0xf9, 0xfe, 0xfe, 0xe1, 0xe1, 0xc1,
-	0xe1, 0x41, 0x71, 0x0e, 0x01, 0x2c, 0x7e, 0x77, 0xaf, 0xf1, 0xf0, 0xf0, 0xa0, 0x38, 0x5f, 0xff,
-	0xc9, 0x17, 0x4f, 0xcb, 0xda, 0x97, 0x4f, 0xcb, 0xda, 0xbf, 0x9e, 0x96, 0xb5, 0xcf, 0x9f, 0x95,
-	0x67, 0xbe, 0x7c, 0x56, 0x9e, 0xf9, 0xfb, 0xb3, 0xf2, 0xcc, 0x8f, 0xf7, 0x95, 0x7f, 0x98, 0xc8,
-	0x47, 0xaa, 0xae, 0xc7, 0xc2, 0xee, 0x89, 0x4e, 0xb5, 0x6b, 0xfc, 0x67, 0xa8, 0xb5, 0x28, 0x66,
-	0xc7, 0xfb, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x87, 0x9e, 0xe6, 0x45, 0x47, 0x1a, 0x00, 0x00,
+	// 1975 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x19, 0x4d, 0x6f, 0x23, 0x49,
+	0x35, 0x9d, 0x4f, 0xfb, 0x39, 0x93, 0x38, 0x95, 0x64, 0xb6, 0xe3, 0x99, 0xb5, 0x43, 0x76, 0x59,
+	0x65, 0xd9, 0x59, 0x5b, 0x9b, 0x45, 0x62, 0x34, 0x70, 0x89, 0x93, 0xc0, 0x38, 0xcc, 0x3a, 0x99,
+	0x76, 0x02, 0x02, 0x89, 0x6d, 0x95, 0xdd, 0x15, 0x4f, 0x6f, 0xda, 0x55, 0xde, 0xee, 0xea, 0xb0,
+	0xde, 0x33, 0x20, 0x21, 0xa4, 0xd1, 0x0a, 0x10, 0x42, 0x42, 0x02, 0x71, 0xe3, 0x17, 0x70, 0xe1,
+	0x0f, 0xec, 0x71, 0x8f, 0x9c, 0x0c, 0x9a, 0xb9, 0x59, 0xe2, 0xc2, 0x2f, 0x40, 0x5d, 0xd5, 0xed,
+	0x2e, 0x77, 0xdb, 0x71, 0x66, 0x60, 0x98, 0x93, 0xbb, 0xde, 0x77, 0xbd, 0x8f, 0x7a, 0xaf, 0xca,
+	0xf0, 0xc0, 0xa6, 0x9c, 0xb8, 0x14, 0x3b, 0x15, 0xaf, 0xf5, 0x84, 0x58, 0xbe, 0x43, 0xdc, 0xf8,
+	0x8b, 0x35, 0x3f, 0x21, 0x2d, 0xee, 0xa5, 0x00, 0xe5, 0xae, 0xcb, 0x38, 0x43, 0xf9, 0x24, 0xbc,
+	0x50, 0x6a, 0x33, 0xd6, 0x76, 0x48, 0x45, 0xe0, 0x9b, 0xfe, 0x45, 0x85, 0xdb, 0x1d, 0xe2, 0x71,
+	0xdc, 0xe9, 0x4a, 0x96, 0xc2, 0xce, 0xe5, 0x7d, 0xaf, 0x6c, 0xb3, 0x0a, 0xee, 0xda, 0x95, 0x16,
+	0x73, 0x49, 0xe5, 0xea, 0x83, 0x4a, 0x9b, 0x50, 0xe2, 0x62, 0x4e, 0xac, 0x90, 0xe6, 0x9b, 0x31,
+	0x4d, 0x07, 0xb7, 0x9e, 0xd8, 0x94, 0xb8, 0xbd, 0x4a, 0xf7, 0xb2, 0x2d, 0x98, 0x5c, 0xe2, 0x31,
+	0xdf, 0x6d, 0x91, 0x14, 0xd7, 0xfb, 0x6d, 0x9b, 0x3f, 0xf1, 0x9b, 0xe5, 0x16, 0xeb, 0x54, 0xda,
+	0xac, 0xcd, 0x62, 0x1b, 0x82, 0x95, 0x58, 0x88, 0x2f, 0x49, 0xbe, 0xf3, 0x97, 0x39, 0xc8, 0x1c,
+	0x7d, 0x46, 0x5a, 0x3e, 0x67, 0x2e, 0xda, 0x86, 0x59, 0xdb, 0xd2, 0xb5, 0x6d, 0x6d, 0x37, 0x5b,
+	0xcd, 0x0f, 0xfa, 0xa5, 0x65, 0xdb, 0xba, 0xc7, 0x3a, 0x36, 0x27, 0x9d, 0x2e, 0xef, 0x19, 0xb3,
+	0xb6, 0x85, 0xde, 0x81, 0xf9, 0x2e, 0x63, 0x8e, 0x3e, 0x2b, 0x68, 0xd0, 0xa0, 0x5f, 0x5a, 0x09,
+	0xd6, 0x0a, 0x95, 0xc0, 0xa3, 0x7d, 0x58, 0xa0, 0xcc, 0x22, 0x9e, 0x3e, 0xb7, 0x3d, 0xb7, 0x9b,
+	0xdb, 0xbb, 0x5d, 0x4e, 0xb9, 0xae, 0xce, 0x2c, 0x52, 0x5d, 0x1f, 0xf4, 0x4b, 0xab, 0x82, 0x50,
+	0x91, 0x20, 0x39, 0xd1, 0xc7, 0xb0, 0xd2, 0xb1, 0xa9, 0xdd, 0xf1, 0x3b, 0xc7, 0xac, 0xd9, 0xb0,
+	0x3f, 0x27, 0xfa, 0xfc, 0xb6, 0xb6, 0x9b, 0xdb, 0x2b, 0xa6, 0x65, 0x19, 0xa1, 0x33, 0x1e, 0xd9,
+	0x1e, 0xaf, 0xde, 0xfe, 0xb2, 0x5f, 0x9a, 0x09, 0x0c, 0x1b, 0xe5, 0x36, 0x12, 0xeb, 0x40, 0xbe,
+	0x83, 0x3d, 0x7e, 0xde, 0xb5, 0x30, 0x27, 0x67, 0x76, 0x87, 0xe8, 0x0b, 0x42, 0x7e, 0xa1, 0x2c,
+	0x83, 0x57, 0x8e, 0x1c, 0x57, 0x3e, 0x8b, 0x82, 0x57, 0x2d, 0x44, 0xb2, 0x47, 0x39, 0xbf, 0xf8,
+	0x47, 0x49, 0x33, 0x12, 0x30, 0x74, 0x02, 0xeb, 0x3e, 0xc5, 0x9e, 0x67, 0xb7, 0x29, 0xb1, 0xcc,
+	0x4f, 0x58, 0xd3, 0x74, 0x7d, 0xea, 0xe9, 0xd9, 0xed, 0xb9, 0xdd, 0x6c, 0xb5, 0x34, 0xe8, 0x97,
+	0xee, 0xc4, 0xe8, 0x63, 0xd6, 0x34, 0x7c, 0xaa, 0x3a, 0x61, 0x2d, 0x85, 0xdc, 0xf9, 0x05, 0x82,
+	0xf9, 0xc0, 0x6b, 0x37, 0x0b, 0x13, 0xc5, 0x1d, 0xa2, 0x2f, 0xc7, 0x61, 0x0a, 0xd6, 0x6a, 0x98,
+	0x82, 0x35, 0xfa, 0x08, 0xb2, 0x81, 0xd5, 0xa6, 0x47, 0x08, 0x15, 0x31, 0xbd, 0x7e, 0xfb, 0x1b,
+	0xe1, 0xf6, 0x33, 0x01, 0x53, 0x83, 0x10, 0x2a, 0x36, 0x3e, 0x5c, 0xa1, 0x13, 0xc8, 0x06, 0xb1,
+	0x33, 0x79, 0xaf, 0x4b, 0xf4, 0xb9, 0x50, 0xdc, 0xd8, 0xc8, 0x9f, 0xf5, 0xba, 0xa4, 0x7a, 0x7b,
+	0xd0, 0x2f, 0x21, 0x1a, 0xae, 0x14, 0xdb, 0x32, 0x11, 0x0c, 0x3d, 0x80, 0xe5, 0xa1, 0x40, 0xd3,
+	0xb6, 0x44, 0x06, 0x64, 0xab, 0xfa, 0xa0, 0x5f, 0xda, 0x88, 0x68, 0x6a, 0xea, 0xde, 0x21, 0x86,
+	0xa2, 0x7d, 0x58, 0xe4, 0xd8, 0xa6, 0xdc, 0xd3, 0x17, 0x44, 0x0e, 0x6e, 0x95, 0x65, 0x3d, 0x95,
+	0x71, 0xd7, 0x2e, 0x07, 0x35, 0x57, 0xbe, 0xfa, 0xa0, 0x7c, 0x16, 0x50, 0x54, 0x57, 0xc2, 0x7d,
+	0x85, 0x0c, 0x46, 0xf8, 0x8b, 0x4e, 0x61, 0xd1, 0xc1, 0x4d, 0xe2, 0x78, 0xfa, 0xa2, 0x10, 0xb1,
+	0x33, 0x7e, 0x33, 0xe5, 0x47, 0x82, 0xe8, 0x88, 0x72, 0xb7, 0x57, 0xdd, 0x18, 0xf4, 0x4b, 0x79,
+	0xc9, 0xa5, 0x18, 0x16, 0xca, 0x41, 0x26, 0xac, 0x72, 0xc6, 0xb1, 0x63, 0x46, 0xf5, 0xeb, 0xe9,
+	0x4b, 0x2f, 0x96, 0xd5, 0x82, 0x3d, 0x42, 0x79, 0x46, 0x62, 0x8d, 0xfe, 0xaa, 0xc1, 0xdb, 0xd8,
+	0x71, 0x58, 0x0b, 0x73, 0xdc, 0x74, 0x88, 0xd9, 0xec, 0x99, 0x5d, 0xd7, 0x66, 0xae, 0xcd, 0x7b,
+	0x26, 0xa6, 0xd6, 0x50, 0xaf, 0x9e, 0x11, 0x3b, 0xfa, 0xce, 0x84, 0x1d, 0xed, 0xc7, 0x22, 0xaa,
+	0xbd, 0xd3, 0x50, 0xc0, 0x3e, 0xb5, 0x22, 0x45, 0x72, 0xaf, 0xbb, 0xa1, 0x51, 0xdb, 0x78, 0x0a,
+	0xb9, 0x31, 0x95, 0x02, 0xb9, 0xb0, 0xee, 0x71, 0xcc, 0x85, 0xc5, 0x61, 0xb1, 0x04, 0x11, 0xcf,
+	0x0a, 0x33, 0xdf, 0x9b, 0x60, 0x66, 0x23, 0xe0, 0xa8, 0xf6, 0x64, 0x85, 0xd4, 0x2c, 0x69, 0xd5,
+	0x1b, 0xa1, 0x55, 0xab, 0xde, 0x28, 0xd6, 0x48, 0x02, 0x90, 0x0f, 0xeb, 0xa1, 0x5d, 0xc4, 0x8a,
+	0xf4, 0xda, 0x96, 0x0e, 0x42, 0xe7, 0xbd, 0xeb, 0x5d, 0x43, 0x2c, 0x21, 0x28, 0x52, 0xaa, 0x87,
+	0x4a, 0xf3, 0x38, 0x81, 0x36, 0x52, 0x10, 0xc4, 0x01, 0x8d, 0xa8, 0xfd, 0xd4, 0x27, 0x3e, 0xd1,
+	0x73, 0x37, 0xd5, 0xfa, 0x38, 0x20, 0x9f, 0xac, 0x55, 0xa0, 0x8d, 0x14, 0x24, 0xd8, 0x2c, 0xb9,
+	0xb2, 0x5b, 0x3c, 0x3e, 0x8c, 0x4c, 0xdb, 0xf2, 0xf4, 0x95, 0x6b, 0xd5, 0x1e, 0x49, 0x8e, 0xc8,
+	0x63, 0x5e, 0x42, 0x2d, 0x49, 0xa0, 0x8d, 0x14, 0x04, 0xfd, 0x59, 0x83, 0x22, 0x65, 0xd4, 0xc4,
+	0x6e, 0x07, 0x5b, 0xd8, 0x8c, 0x37, 0x1e, 0x57, 0xc0, 0x2d, 0x61, 0xc2, 0xb7, 0x26, 0x98, 0x50,
+	0x67, 0x74, 0x5f, 0xf0, 0x0e, 0x5d, 0x30, 0xcc, 0x76, 0x69, 0xcd, 0x5b, 0xa1, 0x35, 0x77, 0xe8,
+	0x64, 0x4a, 0xe3, 0x3a, 0x64, 0x01, 0x43, 0x4e, 0x29, 0x61, 0xf4, 0x16, 0xcc, 0x5d, 0x92, 0x5e,
+	0x78, 0xc0, 0xae, 0x0d, 0xfa, 0xa5, 0x5b, 0x97, 0xa4, 0xa7, 0x14, 0x73, 0x80, 0x45, 0xef, 0xc2,
+	0xc2, 0x15, 0x76, 0x7c, 0x12, 0xb6, 0x42, 0xd1, 0xc9, 0x04, 0x40, 0xed, 0x64, 0x02, 0xf0, 0x60,
+	0xf6, 0xbe, 0x56, 0xf8, 0x83, 0x06, 0x5f, 0xbf, 0x51, 0x51, 0xa9, 0xda, 0x17, 0x26, 0x6a, 0xaf,
+	0xa9, 0xda, 0xa7, 0x9f, 0x1e, 0xd3, 0xac, 0xfb, 0xa5, 0x06, 0x1b, 0xe3, 0x6a, 0xe9, 0x66, 0xae,
+	0x78, 0xa8, 0x1a, 0xb3, 0xb2, 0xf7, 0x66, 0xda, 0x18, 0x29, 0x54, 0x6a, 0x98, 0x66, 0xcb, 0xaf,
+	0x34, 0xd8, 0x1c, 0x5b, 0x63, 0x37, 0x33, 0xe6, 0x7f, 0xec, 0x99, 0x84, 0x35, 0x71, 0xed, 0xbd,
+	0x16, 0x6b, 0x2e, 0x61, 0x73, 0x6c, 0x45, 0xbe, 0x44, 0xca, 0x66, 0xa6, 0x2a, 0xfb, 0x9d, 0x06,
+	0xdb, 0xd3, 0x8a, 0xef, 0x75, 0x64, 0xeb, 0xce, 0x1f, 0xe7, 0x21, 0x13, 0x0d, 0x11, 0xc1, 0x30,
+	0x54, 0x1b, 0x19, 0x86, 0x46, 0x06, 0x82, 0xd9, 0x91, 0x41, 0x60, 0xf6, 0x65, 0x07, 0x81, 0xb3,
+	0xe1, 0x20, 0x20, 0xe7, 0xd9, 0x77, 0x26, 0x4f, 0x35, 0x2f, 0x30, 0x0c, 0xfc, 0x4c, 0x03, 0xe4,
+	0x53, 0x8f, 0xf0, 0x1a, 0xb5, 0xc8, 0x67, 0xc4, 0x92, 0x9c, 0xfa, 0xbc, 0x50, 0xb1, 0x77, 0x8d,
+	0x8a, 0xf3, 0x14, 0x93, 0x54, 0xb7, 0x3d, 0xe8, 0x97, 0xee, 0xa6, 0x25, 0x2a, 0xaa, 0xc7, 0xe8,
+	0xfb, 0x7f, 0x9c, 0x7e, 0x1d, 0x78, 0x63, 0x82, 0xcd, 0xaf, 0x42, 0xdd, 0xce, 0xbf, 0xe7, 0x60,
+	0x4b, 0x54, 0xea, 0x81, 0xe3, 0x7b, 0x9c, 0xb8, 0x51, 0x76, 0x9d, 0x7b, 0xb8, 0x4d, 0x50, 0x0d,
+	0x96, 0x5a, 0x2e, 0x09, 0x72, 0x59, 0x68, 0xbd, 0x7e, 0xe4, 0x5d, 0x0f, 0x33, 0x22, 0x62, 0x11,
+	0x13, 0x6f, 0xb4, 0x08, 0xec, 0x92, 0xcd, 0x5b, 0xb1, 0x4b, 0x00, 0x54, 0xbb, 0x04, 0x00, 0xdd,
+	0x07, 0x20, 0xe1, 0x3d, 0xab, 0x66, 0x89, 0xe1, 0x38, 0x1c, 0x64, 0x63, 0xa8, 0x3a, 0xc8, 0xc6,
+	0x50, 0xf4, 0x1b, 0x0d, 0xd6, 0x87, 0xcd, 0x32, 0x6e, 0x1c, 0x61, 0x9e, 0x1c, 0xa6, 0xf3, 0x64,
+	0xe2, 0xd6, 0x87, 0x65, 0xa6, 0x88, 0x91, 0x99, 0x73, 0x27, 0xdc, 0xe6, 0x38, 0x45, 0xc6, 0x38,
+	0x60, 0xe1, 0xa9, 0x06, 0xfa, 0x24, 0x71, 0xaf, 0xe5, 0x54, 0xf8, 0xdb, 0x1c, 0x14, 0xc6, 0x6d,
+	0xda, 0x20, 0x5d, 0xe6, 0xf2, 0xe1, 0xcd, 0x55, 0x9b, 0x72, 0x73, 0x55, 0xb2, 0x63, 0xf6, 0xbf,
+	0xcc, 0x8e, 0xa7, 0x1a, 0xe4, 0x15, 0xd7, 0x89, 0xb0, 0x84, 0x07, 0x48, 0x35, 0xbd, 0xd9, 0xc9,
+	0xb6, 0xab, 0x61, 0x53, 0x86, 0xbf, 0xe2, 0xa0, 0x5f, 0x2a, 0x24, 0xe5, 0x2b, 0xfb, 0x49, 0xe9,
+	0x2e, 0xfc, 0x5e, 0x83, 0xcd, 0xb1, 0xb2, 0x6e, 0x56, 0x85, 0x3f, 0x18, 0x0d, 0xd8, 0x7b, 0x2f,
+	0x90, 0x79, 0x53, 0xa3, 0xf7, 0xf3, 0x59, 0x58, 0x56, 0xc3, 0x8d, 0x3e, 0x86, 0x6c, 0x3c, 0x21,
+	0x6a, 0xc2, 0x69, 0xef, 0x5f, 0x9f, 0x21, 0xe5, 0xc4, 0x5c, 0xb8, 0x16, 0x06, 0x27, 0x96, 0x63,
+	0xc4, 0x9f, 0x85, 0xdf, 0x6a, 0xb0, 0x32, 0xb9, 0x97, 0x4d, 0x76, 0xc2, 0x8f, 0x46, 0x9d, 0x50,
+	0x56, 0x9a, 0xc9, 0xf0, 0x95, 0xa6, 0xdc, 0xbd, 0x6c, 0x8b, 0xee, 0x12, 0xa9, 0x2b, 0x3f, 0xf6,
+	0x31, 0xe5, 0x36, 0xef, 0x4d, 0xf5, 0xc3, 0xd3, 0x05, 0x58, 0x3b, 0x66, 0xcd, 0x86, 0xdc, 0xa8,
+	0x4d, 0xdb, 0x35, 0x7a, 0xc1, 0xd0, 0x1e, 0x64, 0x1c, 0xfb, 0x82, 0x70, 0xbb, 0x43, 0x84, 0x79,
+	0xb7, 0xe4, 0xdd, 0x39, 0x82, 0xa9, 0x77, 0xe7, 0x08, 0x16, 0xdc, 0x9d, 0x31, 0x37, 0x3b, 0xcc,
+	0xe3, 0x26, 0xa3, 0xad, 0xa8, 0xe9, 0x8b, 0x23, 0x07, 0xf3, 0x8f, 0x98, 0xc7, 0x4f, 0x68, 0x4b,
+	0xe5, 0x84, 0x18, 0x8a, 0xbe, 0x0d, 0xb9, 0xae, 0x4b, 0x02, 0xb8, 0xdd, 0x74, 0xe4, 0x55, 0x3e,
+	0x53, 0xdd, 0x1a, 0xf4, 0x4b, 0x9b, 0x0a, 0x58, 0xe1, 0x55, 0xa9, 0xd1, 0x43, 0xc8, 0xb7, 0x18,
+	0x6d, 0xf9, 0xae, 0x4b, 0x68, 0xab, 0x67, 0x7a, 0xf8, 0x42, 0x3e, 0xdd, 0x64, 0xaa, 0x6f, 0x0e,
+	0xfa, 0xa5, 0x2d, 0x05, 0xd7, 0xc0, 0x17, 0xaa, 0x94, 0xd5, 0x04, 0x0a, 0x9d, 0xc0, 0xfa, 0xf0,
+	0xf2, 0xda, 0x72, 0xb0, 0xe7, 0x99, 0xe2, 0x55, 0x63, 0x51, 0x04, 0x48, 0x3c, 0xa1, 0x44, 0xe8,
+	0x83, 0x00, 0x5b, 0x1f, 0x7d, 0xe2, 0x58, 0x4b, 0x21, 0x51, 0x03, 0x72, 0x9e, 0xdf, 0xec, 0xd8,
+	0xdc, 0x14, 0xae, 0x5c, 0x9a, 0x5a, 0xe0, 0xd1, 0xb5, 0x1b, 0x24, 0xdb, 0xf0, 0xb1, 0x47, 0x59,
+	0x07, 0xc1, 0x89, 0x34, 0xe9, 0x99, 0x38, 0x38, 0x11, 0x4c, 0x0d, 0x4e, 0x04, 0x43, 0x3f, 0x85,
+	0x75, 0x99, 0xc2, 0xa6, 0x4b, 0x3e, 0xf5, 0x6d, 0x97, 0x74, 0x48, 0xfc, 0x52, 0xf1, 0x76, 0x3a,
+	0xcf, 0x4f, 0xc4, 0xaf, 0xa1, 0xd0, 0xca, 0x66, 0xcf, 0x52, 0x70, 0xb5, 0xd9, 0xa7, 0xb1, 0xa8,
+	0x02, 0x4b, 0x57, 0xc4, 0xf5, 0x6c, 0x46, 0xf5, 0xac, 0xb0, 0x75, 0x73, 0xd0, 0x2f, 0xad, 0x85,
+	0x20, 0x85, 0x37, 0xa2, 0xda, 0xf9, 0xb5, 0x06, 0x28, 0xad, 0x1d, 0x39, 0xb0, 0xda, 0x65, 0x96,
+	0x0a, 0x0a, 0x9b, 0xe9, 0xd7, 0xd2, 0xc6, 0x9f, 0x8e, 0x12, 0xca, 0x34, 0x48, 0x70, 0xc7, 0xaa,
+	0x1f, 0xce, 0x18, 0x49, 0xd1, 0xd5, 0x15, 0x58, 0x56, 0xfd, 0xb4, 0xf3, 0xaf, 0x45, 0x58, 0x4d,
+	0x48, 0x45, 0x9e, 0x7c, 0x2b, 0x6a, 0x10, 0x87, 0xb4, 0x38, 0x73, 0xc3, 0x33, 0xe3, 0xc3, 0xa9,
+	0xe6, 0x88, 0xb1, 0x2a, 0xe2, 0x92, 0x27, 0x47, 0x61, 0xd0, 0x2f, 0xdd, 0x56, 0x85, 0x29, 0x8e,
+	0x19, 0x51, 0x82, 0x4e, 0x21, 0x83, 0x2f, 0x2e, 0x6c, 0x1a, 0xc4, 0x5e, 0x1e, 0x08, 0x77, 0xc7,
+	0x4d, 0x97, 0xfb, 0x21, 0x8d, 0xcc, 0x8c, 0x88, 0x43, 0xcd, 0x8c, 0x08, 0x86, 0xce, 0x21, 0xc7,
+	0x99, 0x43, 0x5c, 0xcc, 0x6d, 0x46, 0xa3, 0x79, 0xb3, 0x38, 0x76, 0x64, 0x1d, 0x92, 0x0d, 0xfb,
+	0x90, 0xca, 0x6a, 0xa8, 0x0b, 0xc4, 0x20, 0x87, 0x29, 0x65, 0x3c, 0x14, 0xbb, 0x34, 0x69, 0xc6,
+	0x4c, 0x3a, 0x67, 0x3f, 0x66, 0x92, 0xbe, 0x11, 0xa7, 0x80, 0x22, 0x4a, 0x3d, 0x05, 0x14, 0xf0,
+	0x48, 0x55, 0xcc, 0x8b, 0x39, 0x60, 0x7a, 0x55, 0x1c, 0x43, 0x3e, 0x3a, 0x48, 0x18, 0x3d, 0x65,
+	0x8e, 0xdd, 0xea, 0x89, 0x47, 0xd9, 0xac, 0xec, 0x75, 0x49, 0x9c, 0xda, 0xeb, 0x92, 0x38, 0xf4,
+	0x39, 0x6c, 0x44, 0xa7, 0xef, 0x48, 0x96, 0x2e, 0x8a, 0x28, 0xed, 0x8e, 0x73, 0xa8, 0x31, 0x86,
+	0xbe, 0x7a, 0x37, 0x74, 0xed, 0x58, 0x69, 0xc6, 0x58, 0x68, 0xa1, 0x0d, 0x6b, 0xa9, 0xa4, 0x7a,
+	0x25, 0x73, 0xf5, 0x05, 0xe4, 0x93, 0x01, 0x7a, 0x25, 0x03, 0xf5, 0x9f, 0x34, 0xd8, 0x3a, 0xf5,
+	0x1d, 0x0f, 0xbb, 0x8d, 0x28, 0x61, 0x8e, 0x59, 0xf3, 0x90, 0x70, 0x6c, 0x3b, 0x5e, 0x20, 0x4c,
+	0xdc, 0xd2, 0x43, 0x9d, 0x42, 0x98, 0x00, 0xa8, 0xc2, 0xe4, 0xd3, 0xd7, 0xbb, 0xb0, 0xf0, 0x38,
+	0x39, 0x30, 0x27, 0xe7, 0x16, 0x49, 0x81, 0xee, 0xc1, 0x62, 0xd0, 0x08, 0x09, 0x0f, 0x87, 0x65,
+	0x71, 0x97, 0x92, 0x10, 0xf5, 0x2e, 0x25, 0x21, 0xdf, 0x38, 0x81, 0x9c, 0xf2, 0xc8, 0x80, 0x72,
+	0xb0, 0x74, 0x5e, 0xff, 0x7e, 0xfd, 0xe4, 0x87, 0xf5, 0xfc, 0x4c, 0xb0, 0x38, 0x3d, 0xaa, 0x1f,
+	0xd6, 0xea, 0xdf, 0xcb, 0x6b, 0xc1, 0xc2, 0x38, 0xaf, 0xd7, 0x83, 0xc5, 0x2c, 0xba, 0x05, 0xd9,
+	0xc6, 0xf9, 0xc1, 0xc1, 0xd1, 0xd1, 0xe1, 0xd1, 0x61, 0x7e, 0x0e, 0x01, 0x2c, 0x7e, 0x77, 0xbf,
+	0xf6, 0xe8, 0xe8, 0x30, 0x3f, 0x5f, 0xfd, 0xc9, 0x97, 0xcf, 0x8a, 0xda, 0x57, 0xcf, 0x8a, 0xda,
+	0x3f, 0x9f, 0x15, 0xb5, 0x2f, 0x9e, 0x17, 0x67, 0xbe, 0x7a, 0x5e, 0x9c, 0xf9, 0xfb, 0xf3, 0xe2,
+	0xcc, 0x8f, 0x0f, 0x94, 0x7f, 0x58, 0xe4, 0xab, 0x56, 0xd7, 0x65, 0x41, 0xf5, 0x84, 0xab, 0xca,
+	0x0d, 0xfe, 0x4a, 0x6a, 0x2e, 0x8a, 0x66, 0xf3, 0xe1, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xf1,
+	0xbb, 0x59, 0x7d, 0x78, 0x1a, 0x00, 0x00,
 }
 
 func (m *Executor) Marshal() (dAtA []byte, err error) {
@@ -1674,6 +1684,11 @@ func (m *JobSchedulingInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	_ = i
 	var l int
 	_ = l
+	if m.Version != 0 {
+		i = encodeVarintSchedulerobjects(dAtA, i, uint64(m.Version))
+		i--
+		dAtA[i] = 0x48
+	}
 	if m.Priority != 0 {
 		i = encodeVarintSchedulerobjects(dAtA, i, uint64(m.Priority))
 		i--
@@ -2233,6 +2248,9 @@ func (m *JobSchedulingInfo) Size() (n int) {
 	if m.Priority != 0 {
 		n += 1 + sovSchedulerobjects(uint64(m.Priority))
 	}
+	if m.Version != 0 {
+		n += 1 + sovSchedulerobjects(uint64(m.Version))
+	}
 	return n
 }
 
@@ -4983,6 +5001,25 @@ func (m *JobSchedulingInfo) Unmarshal(dAtA []byte) error {
 					break
 				}
 			}
+		case 9:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
+			}
+			m.Version = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowSchedulerobjects
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Version |= uint32(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
 		default:
 			iNdEx = preIndex
 			skippy, err := skipSchedulerobjects(dAtA[iNdEx:])
diff --git a/internal/scheduler/schedulerobjects/schedulerobjects.proto b/internal/scheduler/schedulerobjects/schedulerobjects.proto
index cb97f003..d4ee18e0 100644
--- a/internal/scheduler/schedulerobjects/schedulerobjects.proto
+++ b/internal/scheduler/schedulerobjects/schedulerobjects.proto
@@ -56,7 +56,7 @@ message Node {
     // Resource allocated to queues on this node.
     map<string, ResourceList> allocated_by_queue = 11 [(gogoproto.nullable) = false];
     // Ids of all runs evicted on this node.
-    map<string, bool> evicted_job_run_ids = 14 [(gogoproto.nullable) = false];    
+    map<string, bool> evicted_job_run_ids = 14 [(gogoproto.nullable) = false];
     // Resource allocated to non-Armada pods on this node.
     map<int32, ResourceList> non_armada_allocated_resources = 13 [(gogoproto.nullable) = false];
 }
@@ -122,6 +122,7 @@ message JobSchedulingInfo {
     uint32 priority = 8;
     // Kubernetes objects that make up this job and their respective scheduling requirements.
     repeated ObjectRequirements object_requirements = 5;
+    uint32 version = 9;
 }
 
 // Message capturing the scheduling requirements of a particular Kubernetes object.
@@ -156,4 +157,4 @@ message PulsarSchedulerJobDetails  {
     string JobId = 1;
     string Queue = 2;
     string JobSet = 3;
-}
\ No newline at end of file
+}
diff --git a/internal/scheduler/submitcheck.go b/internal/scheduler/submitcheck.go
index 4adfc984..3b3afb6a 100644
--- a/internal/scheduler/submitcheck.go
+++ b/internal/scheduler/submitcheck.go
@@ -32,6 +32,11 @@ type schedulingResult struct {
 
 const maxJobSchedulingResults = 10000
 
+type SubmitScheduleChecker interface {
+	CheckPodRequirements(podRequirement *schedulerobjects.PodRequirements) (bool, string)
+	CheckApiJobs(jobs []*api.Job) (bool, string)
+}
+
 type SubmitChecker struct {
 	executorTimeout           time.Duration
 	priorityClasses           map[string]configuration.PriorityClass
@@ -112,6 +117,14 @@ func (srv *SubmitChecker) updateExecutors(ctx context.Context) {
 	srv.jobSchedulingResultsCache.Purge()
 }
 
+func (srv *SubmitChecker) CheckPodRequirements(podRequirement *schedulerobjects.PodRequirements) (bool, string) {
+	schedulingResult := srv.getSchedulingResult([]*schedulerobjects.PodRequirements{podRequirement})
+	if !schedulingResult.isSchedulable {
+		return schedulingResult.isSchedulable, fmt.Sprintf("requirements unschedulable:\n%s", schedulingResult.reason)
+	}
+	return true, ""
+}
+
 func (srv *SubmitChecker) CheckApiJobs(jobs []*api.Job) (bool, string) {
 	// First, check if all jobs can be scheduled individually.
 	for i, job := range jobs {
diff --git a/internal/scheduleringester/dbops.go b/internal/scheduleringester/dbops.go
index ad4f6825..51dbcde2 100644
--- a/internal/scheduleringester/dbops.go
+++ b/internal/scheduleringester/dbops.go
@@ -21,6 +21,17 @@ func (d *DbOperationsWithMessageIds) GetMessageIDs() []pulsar.MessageID {
 
 type JobRunFailed struct {
 	LeaseReturned bool
+	RunAttempted  bool
+}
+
+type JobSchedulingInfoUpdate struct {
+	JobSchedulingInfo        []byte
+	JobSchedulingInfoVersion int32
+}
+
+type JobQueuedStateUpdate struct {
+	Queued             bool
+	QueuedStateVersion int32
 }
 
 // DbOperation captures a generic batch database operation.
@@ -97,6 +108,8 @@ type (
 	MarkJobsSucceeded          map[string]bool
 	MarkJobsFailed             map[string]bool
 	UpdateJobPriorities        map[string]int64
+	UpdateJobSchedulingInfo    map[string]*JobSchedulingInfoUpdate
+	UpdateJobQueuedState       map[string]*JobQueuedStateUpdate
 	MarkRunsSucceeded          map[uuid.UUID]bool
 	MarkRunsFailed             map[uuid.UUID]*JobRunFailed
 	MarkRunsRunning            map[uuid.UUID]bool
@@ -140,6 +153,42 @@ func (a MarkJobsCancelRequested) Merge(b DbOperation) bool {
 	return mergeInMap(a, b)
 }
 
+func (a UpdateJobSchedulingInfo) Merge(b DbOperation) bool {
+	switch op := b.(type) {
+	case UpdateJobSchedulingInfo:
+		for key, value := range op {
+			aValue, present := a[key]
+			if !present {
+				a[key] = value
+			} else {
+				if value.JobSchedulingInfoVersion > aValue.JobSchedulingInfoVersion {
+					a[key] = value
+				}
+			}
+		}
+		return true
+	}
+	return false
+}
+
+func (a UpdateJobQueuedState) Merge(b DbOperation) bool {
+	switch op := b.(type) {
+	case UpdateJobQueuedState:
+		for key, value := range op {
+			currentValue, present := a[key]
+			if !present {
+				a[key] = value
+			} else {
+				if value.QueuedStateVersion > currentValue.QueuedStateVersion {
+					a[key] = value
+				}
+			}
+		}
+		return true
+	}
+	return false
+}
+
 func (a MarkJobsCancelled) Merge(b DbOperation) bool {
 	return mergeInMap(a, b)
 }
@@ -254,6 +303,14 @@ func (a MarkJobsCancelled) CanBeAppliedBefore(b DbOperation) bool {
 	return !definesJob(a, b)
 }
 
+func (a UpdateJobSchedulingInfo) CanBeAppliedBefore(b DbOperation) bool {
+	return !definesJob(a, b)
+}
+
+func (a UpdateJobQueuedState) CanBeAppliedBefore(b DbOperation) bool {
+	return !definesJob(a, b)
+}
+
 func (a UpdateJobPriorities) CanBeAppliedBefore(b DbOperation) bool {
 	_, isUpdateJobSetPriorities := b.(UpdateJobSetPriorities)
 	return !isUpdateJobSetPriorities && !definesJob(a, b)
diff --git a/internal/scheduleringester/instructions.go b/internal/scheduleringester/instructions.go
index 1e467cd9..c9136836 100644
--- a/internal/scheduleringester/instructions.go
+++ b/internal/scheduleringester/instructions.go
@@ -98,6 +98,8 @@ func (c *InstructionConverter) convertSequence(es *armadaevents.EventSequence) [
 			operationsFromEvent, err = c.handleCancelJobSet(meta.jobset)
 		case *armadaevents.EventSequence_Event_CancelledJob:
 			operationsFromEvent, err = c.handleCancelledJob(event.GetCancelledJob())
+		case *armadaevents.EventSequence_Event_JobRequeued:
+			operationsFromEvent, err = c.handleJobRequeued(event.GetJobRequeued())
 		case *armadaevents.EventSequence_Event_PartitionMarker:
 			operationsFromEvent, err = c.handlePartitionMarker(event.GetPartitionMarker(), *event.Created)
 		case *armadaevents.EventSequence_Event_ReprioritisedJob,
@@ -159,15 +161,18 @@ func (c *InstructionConverter) handleSubmitJob(job *armadaevents.SubmitJob, subm
 	}
 
 	return []DbOperation{InsertJobs{jobId: &schedulerdb.Job{
-		JobID:          jobId,
-		JobSet:         meta.jobset,
-		UserID:         meta.user,
-		Groups:         compressedGroups,
-		Queue:          meta.queue,
-		Submitted:      submitTime.UnixNano(),
-		Priority:       int64(job.Priority),
-		SubmitMessage:  compressedSubmitJobBytes,
-		SchedulingInfo: schedulingInfoBytes,
+		JobID:                 jobId,
+		JobSet:                meta.jobset,
+		UserID:                meta.user,
+		Groups:                compressedGroups,
+		Queue:                 meta.queue,
+		Queued:                true,
+		QueuedVersion:         0,
+		Submitted:             submitTime.UnixNano(),
+		Priority:              int64(job.Priority),
+		SubmitMessage:         compressedSubmitJobBytes,
+		SchedulingInfo:        schedulingInfoBytes,
+		SchedulingInfoVersion: int32(schedulingInfo.Version),
 	}}}, nil
 }
 
@@ -177,13 +182,40 @@ func (c *InstructionConverter) handleJobRunLeased(jobRunLeased *armadaevents.Job
 	if err != nil {
 		return nil, err
 	}
-	return []DbOperation{InsertRuns{runId: &schedulerdb.Run{
-		RunID:    runId,
-		JobID:    jobId,
-		JobSet:   meta.jobset,
-		Executor: jobRunLeased.GetExecutorId(),
-		Node:     jobRunLeased.GetNodeId(),
-	}}}, nil
+	return []DbOperation{
+		InsertRuns{runId: &schedulerdb.Run{
+			RunID:    runId,
+			JobID:    jobId,
+			JobSet:   meta.jobset,
+			Executor: jobRunLeased.GetExecutorId(),
+			Node:     jobRunLeased.GetNodeId(),
+		}},
+		UpdateJobQueuedState{jobId: &JobQueuedStateUpdate{
+			Queued:             false,
+			QueuedStateVersion: jobRunLeased.UpdateSequenceNumber,
+		}},
+	}, nil
+}
+
+func (c *InstructionConverter) handleJobRequeued(jobRequeued *armadaevents.JobRequeued) ([]DbOperation, error) {
+	schedulingInfoBytes, err := proto.Marshal(jobRequeued.SchedulingInfo)
+	if err != nil {
+		return nil, errors.WithStack(err)
+	}
+	jobId, err := armadaevents.UlidStringFromProtoUuid(jobRequeued.GetJobId())
+	if err != nil {
+		return nil, err
+	}
+	return []DbOperation{
+		UpdateJobQueuedState{jobId: &JobQueuedStateUpdate{
+			Queued:             true,
+			QueuedStateVersion: jobRequeued.UpdateSequenceNumber,
+		}},
+		UpdateJobSchedulingInfo{jobId: &JobSchedulingInfoUpdate{
+			JobSchedulingInfo:        schedulingInfoBytes,
+			JobSchedulingInfoVersion: int32(jobRequeued.SchedulingInfo.Version),
+		}},
+	}, nil
 }
 
 func (c *InstructionConverter) handleJobRunRunning(jobRunRunning *armadaevents.JobRunRunning) ([]DbOperation, error) {
@@ -216,7 +248,14 @@ func (c *InstructionConverter) handleJobRunErrors(jobRunErrors *armadaevents.Job
 				JobID: jobId,
 				Error: bytes,
 			}
-			markRunsFailed[runId] = &JobRunFailed{LeaseReturned: runError.GetPodLeaseReturned() != nil}
+			runAttempted := true
+			if runError.GetPodLeaseReturned() != nil {
+				runAttempted = runError.GetPodLeaseReturned().RunAttempted
+			}
+			markRunsFailed[runId] = &JobRunFailed{
+				LeaseReturned: runError.GetPodLeaseReturned() != nil,
+				RunAttempted:  runAttempted,
+			}
 			return []DbOperation{insertJobRunErrors, markRunsFailed}, nil
 		}
 	}
@@ -312,6 +351,7 @@ func (c *InstructionConverter) schedulingInfoFromSubmitJob(submitJob *armadaeven
 		AtMostOnce:      submitJob.AtMostOnce,
 		Preemptible:     submitJob.Preemptible,
 		ConcurrencySafe: submitJob.ConcurrencySafe,
+		Version:         0,
 	}
 
 	// Scheduling requirements specific to the objects that make up this job.
diff --git a/internal/scheduleringester/schedulerdb.go b/internal/scheduleringester/schedulerdb.go
index a2230e76..e687b3ac 100644
--- a/internal/scheduleringester/schedulerdb.go
+++ b/internal/scheduleringester/schedulerdb.go
@@ -2,6 +2,8 @@ package scheduleringester
 
 import (
 	"context"
+	"fmt"
+	"strings"
 	"time"
 
 	"github.com/google/uuid"
@@ -81,6 +83,52 @@ func (s *SchedulerDb) WriteDbOp(ctx context.Context, op DbOperation) error {
 				return errors.WithStack(err)
 			}
 		}
+	case UpdateJobSchedulingInfo:
+		args := make([]interface{}, 0, len(o)*3)
+		argMarkers := make([]string, 0, len(o))
+
+		currentIndex := 1
+		for key, value := range o {
+			args = append(args, key)
+			args = append(args, value.JobSchedulingInfo)
+			args = append(args, value.JobSchedulingInfoVersion)
+			argMarkers = append(argMarkers, fmt.Sprintf("($%d, $%d::bytea, $%d::int)", currentIndex, currentIndex+1, currentIndex+2))
+			currentIndex += 3
+		}
+
+		argMarkersString := strings.Join(argMarkers, ",")
+		updateJobInfoSqlStatement := fmt.Sprintf(
+			`update jobs as j set  scheduling_info = updated.scheduling_info, scheduling_info_version = updated.scheduling_info_version
+				 from (values %s) as updated(job_id, scheduling_info, scheduling_info_version)
+                 where j.job_id = updated.job_id and updated.scheduling_info_version > j.scheduling_info_version`, argMarkersString)
+
+		_, err := s.db.Exec(ctx, updateJobInfoSqlStatement, args...)
+		if err != nil {
+			return errors.WithStack(err)
+		}
+	case UpdateJobQueuedState:
+		args := make([]interface{}, 0, len(o)*3)
+		argMarkers := make([]string, 0, len(o))
+
+		currentIndex := 1
+		for key, value := range o {
+			args = append(args, key)
+			args = append(args, value.Queued)
+			args = append(args, value.QueuedStateVersion)
+			argMarkers = append(argMarkers, fmt.Sprintf("($%d, $%d::bool, $%d::int)", currentIndex, currentIndex+1, currentIndex+2))
+			currentIndex += 3
+		}
+
+		argMarkersString := strings.Join(argMarkers, ",")
+		updateQueuedStateSqlStatement := fmt.Sprintf(
+			`update jobs as j set  queued = updated.queued, queued_version = updated.queued_version
+				 from (values %s) as updated(job_id, queued, queued_version)
+				 where j.job_id = updated.job_id and updated.queued_version > j.queued_version`, argMarkersString)
+
+		_, err := s.db.Exec(ctx, updateQueuedStateSqlStatement, args...)
+		if err != nil {
+			return errors.WithStack(err)
+		}
 	case MarkJobSetsCancelRequested:
 		jobSets := maps.Keys(o)
 		err := queries.MarkJobsCancelRequestedBySets(ctx, jobSets)
@@ -132,10 +180,14 @@ func (s *SchedulerDb) WriteDbOp(ctx context.Context, op DbOperation) error {
 	case MarkRunsFailed:
 		runIds := maps.Keys(o)
 		returned := make([]uuid.UUID, 0, len(runIds))
+		runAttempted := make([]uuid.UUID, 0, len(runIds))
 		for k, v := range o {
 			if v.LeaseReturned {
 				returned = append(returned, k)
 			}
+			if v.RunAttempted {
+				runAttempted = append(runAttempted, k)
+			}
 		}
 		err := queries.MarkJobRunsFailedById(ctx, runIds)
 		if err != nil {
@@ -145,6 +197,10 @@ func (s *SchedulerDb) WriteDbOp(ctx context.Context, op DbOperation) error {
 		if err != nil {
 			return errors.WithStack(err)
 		}
+		err = queries.MarkJobRunsAttemptedById(ctx, runAttempted)
+		if err != nil {
+			return errors.WithStack(err)
+		}
 	case MarkRunsRunning:
 		runIds := maps.Keys(o)
 		err := queries.MarkJobRunsRunningById(ctx, runIds)
diff --git a/pkg/armadaevents/events.pb.go b/pkg/armadaevents/events.pb.go
index b220747e..1c0c1f7a 100644
--- a/pkg/armadaevents/events.pb.go
+++ b/pkg/armadaevents/events.pb.go
@@ -18,6 +18,8 @@ import (
 	v11 "k8s.io/api/core/v1"
 	v1 "k8s.io/api/networking/v1"
 	resource "k8s.io/apimachinery/pkg/api/resource"
+
+	schedulerobjects "github.com/armadaproject/armada/internal/scheduler/schedulerobjects"
 )
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -201,6 +203,7 @@ type EventSequence_Event struct {
 	//	*EventSequence_Event_JobRunPreempted
 	//	*EventSequence_Event_PartitionMarker
 	//	*EventSequence_Event_JobRunPreemptionRequested
+	//	*EventSequence_Event_JobRequeued
 	Event isEventSequence_Event_Event `protobuf_oneof:"event"`
 }
 
@@ -303,6 +306,9 @@ type EventSequence_Event_PartitionMarker struct {
 type EventSequence_Event_JobRunPreemptionRequested struct {
 	JobRunPreemptionRequested *JobRunPreemptionRequested `protobuf:"bytes,21,opt,name=jobRunPreemptionRequested,proto3,oneof" json:"jobRunPreemptionRequested,omitempty"`
 }
+type EventSequence_Event_JobRequeued struct {
+	JobRequeued *JobRequeued `protobuf:"bytes,22,opt,name=jobRequeued,proto3,oneof" json:"jobRequeued,omitempty"`
+}
 
 func (*EventSequence_Event_SubmitJob) isEventSequence_Event_Event()                 {}
 func (*EventSequence_Event_ReprioritiseJob) isEventSequence_Event_Event()           {}
@@ -324,6 +330,7 @@ func (*EventSequence_Event_ResourceUtilisation) isEventSequence_Event_Event()
 func (*EventSequence_Event_JobRunPreempted) isEventSequence_Event_Event()           {}
 func (*EventSequence_Event_PartitionMarker) isEventSequence_Event_Event()           {}
 func (*EventSequence_Event_JobRunPreemptionRequested) isEventSequence_Event_Event() {}
+func (*EventSequence_Event_JobRequeued) isEventSequence_Event_Event()               {}
 
 func (m *EventSequence_Event) GetEvent() isEventSequence_Event_Event {
 	if m != nil {
@@ -479,6 +486,13 @@ func (m *EventSequence_Event) GetJobRunPreemptionRequested() *JobRunPreemptionRe
 	return nil
 }
 
+func (m *EventSequence_Event) GetJobRequeued() *JobRequeued {
+	if x, ok := m.GetEvent().(*EventSequence_Event_JobRequeued); ok {
+		return x.JobRequeued
+	}
+	return nil
+}
+
 // XXX_OneofWrappers is for the internal use of the proto package.
 func (*EventSequence_Event) XXX_OneofWrappers() []interface{} {
 	return []interface{}{
@@ -502,6 +516,7 @@ func (*EventSequence_Event) XXX_OneofWrappers() []interface{} {
 		(*EventSequence_Event_JobRunPreempted)(nil),
 		(*EventSequence_Event_PartitionMarker)(nil),
 		(*EventSequence_Event_JobRunPreemptionRequested)(nil),
+		(*EventSequence_Event_JobRequeued)(nil),
 	}
 }
 
@@ -1199,6 +1214,67 @@ func (m *ReprioritiseJob) GetPriority() uint32 {
 	return 0
 }
 
+type JobRequeued struct {
+	JobId          *Uuid                               `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"jobId,omitempty"`
+	SchedulingInfo *schedulerobjects.JobSchedulingInfo `protobuf:"bytes,2,opt,name=scheduling_info,json=schedulingInfo,proto3" json:"schedulingInfo,omitempty"`
+	// Used by the scheduler to maintain a consistent state
+	UpdateSequenceNumber int32 `protobuf:"varint,3,opt,name=update_sequence_number,json=updateSequenceNumber,proto3" json:"updateSequenceNumber,omitempty"`
+}
+
+func (m *JobRequeued) Reset()         { *m = JobRequeued{} }
+func (m *JobRequeued) String() string { return proto.CompactTextString(m) }
+func (*JobRequeued) ProtoMessage()    {}
+func (*JobRequeued) Descriptor() ([]byte, []int) {
+	return fileDescriptor_6aab92ca59e015f8, []int{9}
+}
+func (m *JobRequeued) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *JobRequeued) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_JobRequeued.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *JobRequeued) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_JobRequeued.Merge(m, src)
+}
+func (m *JobRequeued) XXX_Size() int {
+	return m.Size()
+}
+func (m *JobRequeued) XXX_DiscardUnknown() {
+	xxx_messageInfo_JobRequeued.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_JobRequeued proto.InternalMessageInfo
+
+func (m *JobRequeued) GetJobId() *Uuid {
+	if m != nil {
+		return m.JobId
+	}
+	return nil
+}
+
+func (m *JobRequeued) GetSchedulingInfo() *schedulerobjects.JobSchedulingInfo {
+	if m != nil {
+		return m.SchedulingInfo
+	}
+	return nil
+}
+
+func (m *JobRequeued) GetUpdateSequenceNumber() int32 {
+	if m != nil {
+		return m.UpdateSequenceNumber
+	}
+	return 0
+}
+
 // Set the priority of all jobs part of a job set.
 // This sets the priority of all jobs in the job set currently in the queued state.
 type ReprioritiseJobSet struct {
@@ -1209,7 +1285,7 @@ func (m *ReprioritiseJobSet) Reset()         { *m = ReprioritiseJobSet{} }
 func (m *ReprioritiseJobSet) String() string { return proto.CompactTextString(m) }
 func (*ReprioritiseJobSet) ProtoMessage()    {}
 func (*ReprioritiseJobSet) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{9}
+	return fileDescriptor_6aab92ca59e015f8, []int{10}
 }
 func (m *ReprioritiseJobSet) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1256,7 +1332,7 @@ func (m *ReprioritisedJob) Reset()         { *m = ReprioritisedJob{} }
 func (m *ReprioritisedJob) String() string { return proto.CompactTextString(m) }
 func (*ReprioritisedJob) ProtoMessage()    {}
 func (*ReprioritisedJob) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{10}
+	return fileDescriptor_6aab92ca59e015f8, []int{11}
 }
 func (m *ReprioritisedJob) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1310,7 +1386,7 @@ func (m *CancelJob) Reset()         { *m = CancelJob{} }
 func (m *CancelJob) String() string { return proto.CompactTextString(m) }
 func (*CancelJob) ProtoMessage()    {}
 func (*CancelJob) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{11}
+	return fileDescriptor_6aab92ca59e015f8, []int{12}
 }
 func (m *CancelJob) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1356,7 +1432,7 @@ func (m *JobSetFilter) Reset()         { *m = JobSetFilter{} }
 func (m *JobSetFilter) String() string { return proto.CompactTextString(m) }
 func (*JobSetFilter) ProtoMessage()    {}
 func (*JobSetFilter) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{12}
+	return fileDescriptor_6aab92ca59e015f8, []int{13}
 }
 func (m *JobSetFilter) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1402,7 +1478,7 @@ func (m *CancelJobSet) Reset()         { *m = CancelJobSet{} }
 func (m *CancelJobSet) String() string { return proto.CompactTextString(m) }
 func (*CancelJobSet) ProtoMessage()    {}
 func (*CancelJobSet) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{13}
+	return fileDescriptor_6aab92ca59e015f8, []int{14}
 }
 func (m *CancelJobSet) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1448,7 +1524,7 @@ func (m *CancelledJob) Reset()         { *m = CancelledJob{} }
 func (m *CancelledJob) String() string { return proto.CompactTextString(m) }
 func (*CancelledJob) ProtoMessage()    {}
 func (*CancelledJob) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{14}
+	return fileDescriptor_6aab92ca59e015f8, []int{15}
 }
 func (m *CancelledJob) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1496,7 +1572,7 @@ func (m *JobSucceeded) Reset()         { *m = JobSucceeded{} }
 func (m *JobSucceeded) String() string { return proto.CompactTextString(m) }
 func (*JobSucceeded) ProtoMessage()    {}
 func (*JobSucceeded) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{15}
+	return fileDescriptor_6aab92ca59e015f8, []int{16}
 }
 func (m *JobSucceeded) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1546,13 +1622,15 @@ type JobRunLeased struct {
 	// Each cluster is represented by an executor.
 	ExecutorId string `protobuf:"bytes,3,opt,name=executor_id,json=executorId,proto3" json:"executorId,omitempty"`
 	NodeId     string `protobuf:"bytes,4,opt,name=node_id,json=nodeId,proto3" json:"nodeId,omitempty"`
+	// Used by the scheduler to maintain a consistent state
+	UpdateSequenceNumber int32 `protobuf:"varint,5,opt,name=update_sequence_number,json=updateSequenceNumber,proto3" json:"updateSequenceNumber,omitempty"`
 }
 
 func (m *JobRunLeased) Reset()         { *m = JobRunLeased{} }
 func (m *JobRunLeased) String() string { return proto.CompactTextString(m) }
 func (*JobRunLeased) ProtoMessage()    {}
 func (*JobRunLeased) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{16}
+	return fileDescriptor_6aab92ca59e015f8, []int{17}
 }
 func (m *JobRunLeased) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1609,6 +1687,13 @@ func (m *JobRunLeased) GetNodeId() string {
 	return ""
 }
 
+func (m *JobRunLeased) GetUpdateSequenceNumber() int32 {
+	if m != nil {
+		return m.UpdateSequenceNumber
+	}
+	return 0
+}
+
 // Indicates that a job has been assigned to nodes by Kubernetes.
 type JobRunAssigned struct {
 	RunId *Uuid `protobuf:"bytes,1,opt,name=run_id,json=runId,proto3" json:"runId,omitempty"`
@@ -1623,7 +1708,7 @@ func (m *JobRunAssigned) Reset()         { *m = JobRunAssigned{} }
 func (m *JobRunAssigned) String() string { return proto.CompactTextString(m) }
 func (*JobRunAssigned) ProtoMessage()    {}
 func (*JobRunAssigned) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{17}
+	return fileDescriptor_6aab92ca59e015f8, []int{18}
 }
 func (m *JobRunAssigned) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1686,7 +1771,7 @@ func (m *JobRunRunning) Reset()         { *m = JobRunRunning{} }
 func (m *JobRunRunning) String() string { return proto.CompactTextString(m) }
 func (*JobRunRunning) ProtoMessage()    {}
 func (*JobRunRunning) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{18}
+	return fileDescriptor_6aab92ca59e015f8, []int{19}
 }
 func (m *JobRunRunning) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1749,7 +1834,7 @@ func (m *KubernetesResourceInfo) Reset()         { *m = KubernetesResourceInfo{}
 func (m *KubernetesResourceInfo) String() string { return proto.CompactTextString(m) }
 func (*KubernetesResourceInfo) ProtoMessage()    {}
 func (*KubernetesResourceInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{19}
+	return fileDescriptor_6aab92ca59e015f8, []int{20}
 }
 func (m *KubernetesResourceInfo) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1840,7 +1925,7 @@ func (m *PodInfo) Reset()         { *m = PodInfo{} }
 func (m *PodInfo) String() string { return proto.CompactTextString(m) }
 func (*PodInfo) ProtoMessage()    {}
 func (*PodInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{20}
+	return fileDescriptor_6aab92ca59e015f8, []int{21}
 }
 func (m *PodInfo) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1895,7 +1980,7 @@ func (m *IngressInfo) Reset()         { *m = IngressInfo{} }
 func (m *IngressInfo) String() string { return proto.CompactTextString(m) }
 func (*IngressInfo) ProtoMessage()    {}
 func (*IngressInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{21}
+	return fileDescriptor_6aab92ca59e015f8, []int{22}
 }
 func (m *IngressInfo) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1958,7 +2043,7 @@ func (m *StandaloneIngressInfo) Reset()         { *m = StandaloneIngressInfo{} }
 func (m *StandaloneIngressInfo) String() string { return proto.CompactTextString(m) }
 func (*StandaloneIngressInfo) ProtoMessage()    {}
 func (*StandaloneIngressInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{22}
+	return fileDescriptor_6aab92ca59e015f8, []int{23}
 }
 func (m *StandaloneIngressInfo) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2057,7 +2142,7 @@ func (m *JobRunSucceeded) Reset()         { *m = JobRunSucceeded{} }
 func (m *JobRunSucceeded) String() string { return proto.CompactTextString(m) }
 func (*JobRunSucceeded) ProtoMessage()    {}
 func (*JobRunSucceeded) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{23}
+	return fileDescriptor_6aab92ca59e015f8, []int{24}
 }
 func (m *JobRunSucceeded) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2120,7 +2205,7 @@ func (m *JobErrors) Reset()         { *m = JobErrors{} }
 func (m *JobErrors) String() string { return proto.CompactTextString(m) }
 func (*JobErrors) ProtoMessage()    {}
 func (*JobErrors) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{24}
+	return fileDescriptor_6aab92ca59e015f8, []int{25}
 }
 func (m *JobErrors) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2181,7 +2266,7 @@ func (m *JobRunErrors) Reset()         { *m = JobRunErrors{} }
 func (m *JobRunErrors) String() string { return proto.CompactTextString(m) }
 func (*JobRunErrors) ProtoMessage()    {}
 func (*JobRunErrors) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{25}
+	return fileDescriptor_6aab92ca59e015f8, []int{26}
 }
 func (m *JobRunErrors) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2262,7 +2347,7 @@ func (m *Error) Reset()         { *m = Error{} }
 func (m *Error) String() string { return proto.CompactTextString(m) }
 func (*Error) ProtoMessage()    {}
 func (*Error) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{26}
+	return fileDescriptor_6aab92ca59e015f8, []int{27}
 }
 func (m *Error) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2451,7 +2536,7 @@ func (m *KubernetesError) Reset()         { *m = KubernetesError{} }
 func (m *KubernetesError) String() string { return proto.CompactTextString(m) }
 func (*KubernetesError) ProtoMessage()    {}
 func (*KubernetesError) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{27}
+	return fileDescriptor_6aab92ca59e015f8, []int{28}
 }
 func (m *KubernetesError) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2509,7 +2594,7 @@ func (m *PodError) Reset()         { *m = PodError{} }
 func (m *PodError) String() string { return proto.CompactTextString(m) }
 func (*PodError) ProtoMessage()    {}
 func (*PodError) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{28}
+	return fileDescriptor_6aab92ca59e015f8, []int{29}
 }
 func (m *PodError) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2597,7 +2682,7 @@ func (m *ContainerError) Reset()         { *m = ContainerError{} }
 func (m *ContainerError) String() string { return proto.CompactTextString(m) }
 func (*ContainerError) ProtoMessage()    {}
 func (*ContainerError) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{29}
+	return fileDescriptor_6aab92ca59e015f8, []int{30}
 }
 func (m *ContainerError) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2673,7 +2758,7 @@ func (m *PodLeaseReturned) Reset()         { *m = PodLeaseReturned{} }
 func (m *PodLeaseReturned) String() string { return proto.CompactTextString(m) }
 func (*PodLeaseReturned) ProtoMessage()    {}
 func (*PodLeaseReturned) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{30}
+	return fileDescriptor_6aab92ca59e015f8, []int{31}
 }
 func (m *PodLeaseReturned) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2744,7 +2829,7 @@ func (m *PodTerminated) Reset()         { *m = PodTerminated{} }
 func (m *PodTerminated) String() string { return proto.CompactTextString(m) }
 func (*PodTerminated) ProtoMessage()    {}
 func (*PodTerminated) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{31}
+	return fileDescriptor_6aab92ca59e015f8, []int{32}
 }
 func (m *PodTerminated) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2808,7 +2893,7 @@ func (m *ExecutorError) Reset()         { *m = ExecutorError{} }
 func (m *ExecutorError) String() string { return proto.CompactTextString(m) }
 func (*ExecutorError) ProtoMessage()    {}
 func (*ExecutorError) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{32}
+	return fileDescriptor_6aab92ca59e015f8, []int{33}
 }
 func (m *ExecutorError) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2849,7 +2934,7 @@ func (m *PodUnschedulable) Reset()         { *m = PodUnschedulable{} }
 func (m *PodUnschedulable) String() string { return proto.CompactTextString(m) }
 func (*PodUnschedulable) ProtoMessage()    {}
 func (*PodUnschedulable) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{33}
+	return fileDescriptor_6aab92ca59e015f8, []int{34}
 }
 func (m *PodUnschedulable) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2913,7 +2998,7 @@ func (m *LeaseExpired) Reset()         { *m = LeaseExpired{} }
 func (m *LeaseExpired) String() string { return proto.CompactTextString(m) }
 func (*LeaseExpired) ProtoMessage()    {}
 func (*LeaseExpired) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{34}
+	return fileDescriptor_6aab92ca59e015f8, []int{35}
 }
 func (m *LeaseExpired) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2943,13 +3028,14 @@ func (m *LeaseExpired) XXX_DiscardUnknown() {
 var xxx_messageInfo_LeaseExpired proto.InternalMessageInfo
 
 type MaxRunsExceeded struct {
+	Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
 }
 
 func (m *MaxRunsExceeded) Reset()         { *m = MaxRunsExceeded{} }
 func (m *MaxRunsExceeded) String() string { return proto.CompactTextString(m) }
 func (*MaxRunsExceeded) ProtoMessage()    {}
 func (*MaxRunsExceeded) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{35}
+	return fileDescriptor_6aab92ca59e015f8, []int{36}
 }
 func (m *MaxRunsExceeded) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2978,6 +3064,13 @@ func (m *MaxRunsExceeded) XXX_DiscardUnknown() {
 
 var xxx_messageInfo_MaxRunsExceeded proto.InternalMessageInfo
 
+func (m *MaxRunsExceeded) GetMessage() string {
+	if m != nil {
+		return m.Message
+	}
+	return ""
+}
+
 type JobRunPreemptedError struct {
 }
 
@@ -2985,7 +3078,7 @@ func (m *JobRunPreemptedError) Reset()         { *m = JobRunPreemptedError{} }
 func (m *JobRunPreemptedError) String() string { return proto.CompactTextString(m) }
 func (*JobRunPreemptedError) ProtoMessage()    {}
 func (*JobRunPreemptedError) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{36}
+	return fileDescriptor_6aab92ca59e015f8, []int{37}
 }
 func (m *JobRunPreemptedError) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -3025,7 +3118,7 @@ func (m *JobDuplicateDetected) Reset()         { *m = JobDuplicateDetected{} }
 func (m *JobDuplicateDetected) String() string { return proto.CompactTextString(m) }
 func (*JobDuplicateDetected) ProtoMessage()    {}
 func (*JobDuplicateDetected) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{37}
+	return fileDescriptor_6aab92ca59e015f8, []int{38}
 }
 func (m *JobDuplicateDetected) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -3084,7 +3177,7 @@ func (m *JobRunPreempted) Reset()         { *m = JobRunPreempted{} }
 func (m *JobRunPreempted) String() string { return proto.CompactTextString(m) }
 func (*JobRunPreempted) ProtoMessage()    {}
 func (*JobRunPreempted) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{38}
+	return fileDescriptor_6aab92ca59e015f8, []int{39}
 }
 func (m *JobRunPreempted) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -3153,7 +3246,7 @@ func (m *PartitionMarker) Reset()         { *m = PartitionMarker{} }
 func (m *PartitionMarker) String() string { return proto.CompactTextString(m) }
 func (*PartitionMarker) ProtoMessage()    {}
 func (*PartitionMarker) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{39}
+	return fileDescriptor_6aab92ca59e015f8, []int{40}
 }
 func (m *PartitionMarker) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -3206,7 +3299,7 @@ func (m *JobRunPreemptionRequested) Reset()         { *m = JobRunPreemptionReque
 func (m *JobRunPreemptionRequested) String() string { return proto.CompactTextString(m) }
 func (*JobRunPreemptionRequested) ProtoMessage()    {}
 func (*JobRunPreemptionRequested) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6aab92ca59e015f8, []int{40}
+	return fileDescriptor_6aab92ca59e015f8, []int{41}
 }
 func (m *JobRunPreemptionRequested) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -3266,6 +3359,7 @@ func init() {
 	proto.RegisterMapType((map[string]string)(nil), "armadaevents.ObjectMeta.LabelsEntry")
 	proto.RegisterType((*PodSpecWithAvoidList)(nil), "armadaevents.PodSpecWithAvoidList")
 	proto.RegisterType((*ReprioritiseJob)(nil), "armadaevents.ReprioritiseJob")
+	proto.RegisterType((*JobRequeued)(nil), "armadaevents.JobRequeued")
 	proto.RegisterType((*ReprioritiseJobSet)(nil), "armadaevents.ReprioritiseJobSet")
 	proto.RegisterType((*ReprioritisedJob)(nil), "armadaevents.ReprioritisedJob")
 	proto.RegisterType((*CancelJob)(nil), "armadaevents.CancelJob")
@@ -3305,212 +3399,221 @@ func init() {
 func init() { proto.RegisterFile("pkg/armadaevents/events.proto", fileDescriptor_6aab92ca59e015f8) }
 
 var fileDescriptor_6aab92ca59e015f8 = []byte{
-	// 3278 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x5b, 0x4b, 0x6c, 0x1b, 0xc7,
-	0x19, 0xf6, 0x92, 0x12, 0x1f, 0x3f, 0x25, 0x91, 0x1e, 0x4b, 0x0e, 0xad, 0xd8, 0xa2, 0xb3, 0x4e,
-	0x1b, 0x27, 0x48, 0xc8, 0xc4, 0x79, 0x20, 0x8f, 0x22, 0x81, 0x68, 0x2b, 0xb1, 0x1d, 0xcb, 0x76,
-	0x28, 0x2b, 0x4d, 0x83, 0x14, 0xcc, 0x72, 0x77, 0x44, 0xad, 0x45, 0xee, 0x6e, 0xf6, 0x21, 0x4b,
-	0x40, 0x0e, 0x2d, 0xd0, 0xa6, 0xb7, 0xc6, 0x87, 0x1e, 0x0a, 0xf4, 0x90, 0x5e, 0x1b, 0xa0, 0xb7,
-	0xa2, 0x3d, 0xf7, 0x96, 0x02, 0x45, 0x91, 0xde, 0x7a, 0x62, 0x8b, 0x04, 0xbd, 0xf0, 0xd0, 0x73,
-	0xdb, 0x53, 0x31, 0xaf, 0xdd, 0x99, 0xe5, 0xd2, 0x56, 0xfd, 0xa8, 0x53, 0xf8, 0x24, 0xed, 0xf7,
-	0xbf, 0x66, 0xe6, 0x9f, 0x7f, 0xe6, 0xff, 0x67, 0x86, 0x70, 0xc2, 0xdb, 0xe9, 0xb7, 0x0c, 0x7f,
-	0x68, 0x58, 0x06, 0xde, 0xc5, 0x4e, 0x18, 0xb4, 0xd8, 0x9f, 0xa6, 0xe7, 0xbb, 0xa1, 0x8b, 0xe6,
-	0x64, 0xd2, 0xb2, 0xbe, 0xf3, 0x72, 0xd0, 0xb4, 0xdd, 0x96, 0xe1, 0xd9, 0x2d, 0xd3, 0xf5, 0x71,
-	0x6b, 0xf7, 0xb9, 0x56, 0x1f, 0x3b, 0xd8, 0x37, 0x42, 0x6c, 0x31, 0x89, 0xe5, 0xd3, 0x12, 0x8f,
-	0x83, 0xc3, 0x1b, 0xae, 0xbf, 0x63, 0x3b, 0xfd, 0x2c, 0xce, 0x46, 0xdf, 0x75, 0xfb, 0x03, 0xdc,
-	0xa2, 0x5f, 0xbd, 0x68, 0xab, 0x15, 0xda, 0x43, 0x1c, 0x84, 0xc6, 0xd0, 0xe3, 0x0c, 0x2f, 0x24,
-	0xaa, 0x86, 0x86, 0xb9, 0x6d, 0x3b, 0xd8, 0xdf, 0x6f, 0xd1, 0xf6, 0x7a, 0x76, 0xcb, 0xc7, 0x81,
-	0x1b, 0xf9, 0x26, 0x9e, 0x50, 0xfb, 0x4c, 0xdf, 0x0e, 0xb7, 0xa3, 0x5e, 0xd3, 0x74, 0x87, 0xad,
-	0xbe, 0xdb, 0x77, 0x13, 0xfd, 0xe4, 0x8b, 0x7e, 0xd0, 0xff, 0x18, 0xbb, 0xfe, 0xdb, 0x23, 0x30,
-	0xbf, 0x46, 0xba, 0xb7, 0x81, 0x3f, 0x8a, 0xb0, 0x63, 0x62, 0xf4, 0x24, 0xcc, 0x7e, 0x14, 0xe1,
-	0x08, 0xd7, 0xb5, 0x93, 0xda, 0xe9, 0x72, 0xfb, 0xc8, 0x78, 0xd4, 0xa8, 0x52, 0xe0, 0x69, 0x77,
-	0x68, 0x87, 0x78, 0xe8, 0x85, 0xfb, 0x1d, 0xc6, 0x81, 0x5e, 0x85, 0xb9, 0xeb, 0x6e, 0xaf, 0x1b,
-	0xe0, 0xb0, 0xeb, 0x18, 0x43, 0x5c, 0xcf, 0x51, 0x89, 0xfa, 0x78, 0xd4, 0x58, 0xbc, 0xee, 0xf6,
-	0x36, 0x70, 0x78, 0xd9, 0x18, 0xca, 0x62, 0x90, 0xa0, 0xe8, 0x19, 0x28, 0x46, 0x01, 0xf6, 0xbb,
-	0xb6, 0x55, 0xcf, 0x53, 0xb1, 0xc5, 0xf1, 0xa8, 0x51, 0x23, 0xd0, 0x05, 0x4b, 0x12, 0x29, 0x30,
-	0x04, 0x3d, 0x0d, 0x85, 0xbe, 0xef, 0x46, 0x5e, 0x50, 0x9f, 0x39, 0x99, 0x17, 0xdc, 0x0c, 0x91,
-	0xb9, 0x19, 0x82, 0xae, 0x40, 0x81, 0xf9, 0xac, 0x3e, 0x7b, 0x32, 0x7f, 0xba, 0x72, 0xe6, 0xb1,
-	0xa6, 0xec, 0xc8, 0xa6, 0xd2, 0x61, 0xf6, 0xc5, 0x14, 0x32, 0xba, 0xac, 0x90, 0xbb, 0xfe, 0xd3,
-	0xc3, 0x30, 0x4b, 0xf9, 0xd0, 0x15, 0x28, 0x9a, 0x3e, 0x26, 0x03, 0x5e, 0x47, 0x27, 0xb5, 0xd3,
-	0x95, 0x33, 0xcb, 0x4d, 0xe6, 0xc8, 0xa6, 0x18, 0xe8, 0xe6, 0x35, 0xe1, 0xc8, 0xf6, 0xb1, 0xf1,
-	0xa8, 0x71, 0x98, 0xb3, 0x27, 0x5a, 0x6f, 0xfe, 0xb5, 0xa1, 0x75, 0x84, 0x16, 0x74, 0x15, 0xca,
-	0x41, 0xd4, 0x1b, 0xda, 0xe1, 0x45, 0xb7, 0x47, 0xc7, 0xbc, 0x72, 0xe6, 0x11, 0xb5, 0xb9, 0x1b,
-	0x82, 0xdc, 0x7e, 0x64, 0x3c, 0x6a, 0x1c, 0x89, 0xb9, 0x13, 0x8d, 0xe7, 0x0f, 0x75, 0x12, 0x25,
-	0x68, 0x1b, 0xaa, 0x3e, 0xf6, 0x7c, 0xdb, 0xf5, 0xed, 0xd0, 0x0e, 0x30, 0xd1, 0x9b, 0xa3, 0x7a,
-	0x4f, 0xa8, 0x7a, 0x3b, 0x2a, 0x53, 0xfb, 0xc4, 0x78, 0xd4, 0x38, 0x96, 0x92, 0x54, 0x6c, 0xa4,
-	0xd5, 0xa2, 0x10, 0x50, 0x0a, 0xda, 0xc0, 0x21, 0xf5, 0x67, 0xe5, 0xcc, 0xc9, 0x5b, 0x1a, 0xdb,
-	0xc0, 0x61, 0xfb, 0xe4, 0x78, 0xd4, 0x38, 0x3e, 0x29, 0xaf, 0x98, 0xcc, 0xd0, 0x8f, 0x06, 0x50,
-	0x93, 0x51, 0x8b, 0x74, 0x70, 0x86, 0xda, 0x5c, 0x99, 0x6e, 0x93, 0x70, 0xb5, 0x57, 0xc6, 0xa3,
-	0xc6, 0x72, 0x5a, 0x56, 0xb1, 0x37, 0xa1, 0x99, 0xf8, 0xc7, 0x34, 0x1c, 0x13, 0x0f, 0x88, 0x99,
-	0xd9, 0x2c, 0xff, 0x9c, 0x15, 0x64, 0xe6, 0x9f, 0x98, 0x5b, 0xf5, 0x4f, 0x0c, 0xa3, 0x0f, 0x60,
-	0x2e, 0xfe, 0x20, 0xe3, 0x55, 0xe0, 0xf3, 0x28, 0x5b, 0x29, 0x19, 0xa9, 0xe5, 0xf1, 0xa8, 0x71,
-	0x54, 0x96, 0x51, 0x54, 0x2b, 0xda, 0x12, 0xed, 0x03, 0x36, 0x32, 0xc5, 0xe9, 0xda, 0x19, 0x87,
-	0xac, 0x7d, 0x30, 0x39, 0x22, 0x8a, 0x36, 0xa2, 0x9d, 0x04, 0x71, 0x64, 0x9a, 0x18, 0x5b, 0xd8,
-	0xaa, 0x97, 0xb2, 0xb4, 0x5f, 0x94, 0x38, 0x98, 0x76, 0x59, 0x46, 0xd5, 0x2e, 0x53, 0xc8, 0x58,
-	0x5f, 0x77, 0x7b, 0x6b, 0xbe, 0xef, 0xfa, 0x41, 0xbd, 0x9c, 0x35, 0xd6, 0x17, 0x05, 0x99, 0x8d,
-	0x75, 0xcc, 0xad, 0x8e, 0x75, 0x0c, 0xf3, 0xf6, 0x76, 0x22, 0xe7, 0x12, 0x36, 0x02, 0x6c, 0xd5,
-	0x61, 0x4a, 0x7b, 0x63, 0x8e, 0xb8, 0xbd, 0x31, 0x32, 0xd1, 0xde, 0x98, 0x82, 0x2c, 0x58, 0x60,
-	0xdf, 0xab, 0x41, 0x60, 0xf7, 0x1d, 0x6c, 0xd5, 0x2b, 0x54, 0xff, 0xf1, 0x2c, 0xfd, 0x82, 0xa7,
-	0x7d, 0x7c, 0x3c, 0x6a, 0xd4, 0x55, 0x39, 0xc5, 0x46, 0x4a, 0x27, 0xfa, 0x10, 0xe6, 0x19, 0xd2,
-	0x89, 0x1c, 0xc7, 0x76, 0xfa, 0xf5, 0x39, 0x6a, 0xe4, 0xd1, 0x2c, 0x23, 0x9c, 0xa5, 0xfd, 0xe8,
-	0x78, 0xd4, 0x78, 0x44, 0x91, 0x52, 0x4c, 0xa8, 0x0a, 0xc9, 0x8a, 0xc1, 0x80, 0xc4, 0xb1, 0xf3,
-	0x59, 0x2b, 0xc6, 0x45, 0x95, 0x89, 0xad, 0x18, 0x29, 0x49, 0x75, 0xc5, 0x48, 0x11, 0x13, 0x7f,
-	0x70, 0x27, 0x2f, 0x4c, 0xf7, 0x07, 0xf7, 0xb3, 0xe4, 0x8f, 0x0c, 0x57, 0x2b, 0xda, 0xd0, 0xc7,
-	0x40, 0x36, 0x9e, 0x73, 0x91, 0x37, 0xb0, 0x4d, 0x23, 0xc4, 0xe7, 0x70, 0x88, 0x4d, 0xb2, 0x52,
-	0x57, 0xa9, 0x15, 0x7d, 0xc2, 0xca, 0x04, 0x67, 0x5b, 0x1f, 0x8f, 0x1a, 0x2b, 0x59, 0x3a, 0x14,
-	0xab, 0x99, 0x56, 0xd0, 0x0f, 0x34, 0x58, 0x0a, 0x42, 0xc3, 0xb1, 0x8c, 0x81, 0xeb, 0xe0, 0x0b,
-	0x4e, 0xdf, 0xc7, 0x41, 0x70, 0xc1, 0xd9, 0x72, 0xeb, 0x35, 0x6a, 0xff, 0x54, 0x6a, 0x59, 0xcf,
-	0x62, 0x6d, 0x9f, 0x1a, 0x8f, 0x1a, 0x8d, 0x4c, 0x2d, 0x4a, 0x0b, 0xb2, 0x0d, 0xa1, 0x3d, 0x38,
-	0x22, 0x32, 0x83, 0xcd, 0xd0, 0x1e, 0xd8, 0x81, 0x11, 0xda, 0xae, 0x53, 0x3f, 0x4c, 0xed, 0x3f,
-	0x96, 0x5e, 0x1d, 0x27, 0x18, 0xdb, 0x8f, 0x8d, 0x47, 0x8d, 0x13, 0x19, 0x1a, 0x14, 0xdb, 0x59,
-	0x26, 0x92, 0x29, 0x74, 0xd5, 0xc7, 0x84, 0x11, 0x5b, 0xf5, 0x23, 0xd3, 0xa7, 0x50, 0xcc, 0x24,
-	0x4f, 0xa1, 0x18, 0xcc, 0x9a, 0x42, 0x31, 0x91, 0x58, 0xf2, 0x0c, 0x3f, 0xb4, 0x89, 0xd9, 0x75,
-	0xc3, 0xdf, 0xc1, 0x7e, 0x7d, 0x31, 0xcb, 0xd2, 0x55, 0x95, 0x89, 0x59, 0x4a, 0x49, 0xaa, 0x96,
-	0x52, 0x44, 0x74, 0x53, 0x03, 0xb5, 0x69, 0xb6, 0xeb, 0x74, 0x48, 0xda, 0x10, 0x90, 0xee, 0x2d,
-	0x51, 0xa3, 0x4f, 0xdc, 0xa2, 0x7b, 0x32, 0x7b, 0xfb, 0x89, 0xf1, 0xa8, 0x71, 0x6a, 0xaa, 0x36,
-	0xa5, 0x21, 0xd3, 0x8d, 0xb6, 0x8b, 0x30, 0x4b, 0x2d, 0xe9, 0xe3, 0x02, 0x1c, 0xc9, 0xf0, 0x20,
-	0x7a, 0x1d, 0x0a, 0x7e, 0xe4, 0x90, 0xb4, 0x8a, 0xe5, 0x12, 0x48, 0x6d, 0xdf, 0x66, 0x64, 0x5b,
-	0x2c, 0xa7, 0xf3, 0x23, 0x47, 0xc9, 0xb4, 0x66, 0x29, 0x40, 0xe4, 0x49, 0x4e, 0x67, 0x5b, 0x3c,
-	0x67, 0x98, 0x2a, 0x7f, 0xdd, 0xed, 0xa9, 0xf2, 0x14, 0x40, 0x18, 0xe6, 0xc5, 0xf4, 0xe8, 0xda,
-	0x64, 0xee, 0xb3, 0x6c, 0xe0, 0x71, 0x55, 0xcd, 0xdb, 0x51, 0x0f, 0xfb, 0x0e, 0x0e, 0x71, 0x20,
-	0xfa, 0x40, 0x27, 0x3f, 0x8d, 0x75, 0x5f, 0x42, 0x24, 0xfd, 0x73, 0x32, 0x8e, 0x7e, 0xa6, 0x41,
-	0x7d, 0x68, 0xec, 0x75, 0x05, 0x18, 0x74, 0xb7, 0x5c, 0xbf, 0xeb, 0x61, 0xdf, 0x76, 0x2d, 0x9a,
-	0x22, 0x56, 0xce, 0x7c, 0xe7, 0xb6, 0xd3, 0xbd, 0xb9, 0x6e, 0xec, 0x09, 0x38, 0x78, 0xd3, 0xf5,
-	0xaf, 0x52, 0xf1, 0x35, 0x27, 0xf4, 0xf7, 0xdb, 0x27, 0xbe, 0x18, 0x35, 0x0e, 0x8d, 0x47, 0x8d,
-	0xa5, 0x61, 0x16, 0x4f, 0x27, 0x1b, 0x46, 0x9f, 0x6a, 0x70, 0x34, 0x74, 0x43, 0x63, 0xd0, 0x35,
-	0xa3, 0x61, 0x34, 0x30, 0x42, 0x7b, 0x17, 0x77, 0xa3, 0xc0, 0xe8, 0x63, 0x9e, 0x89, 0xbe, 0x76,
-	0xfb, 0x46, 0x5d, 0x23, 0xf2, 0x67, 0x63, 0xf1, 0x4d, 0x22, 0xcd, 0xda, 0x74, 0x9c, 0xb7, 0x69,
-	0x31, 0xcc, 0x60, 0xe9, 0x64, 0xa2, 0xcb, 0xbf, 0xd4, 0x60, 0x79, 0x7a, 0x37, 0xd1, 0x29, 0xc8,
-	0xef, 0xe0, 0x7d, 0x9e, 0xeb, 0x1f, 0x1e, 0x8f, 0x1a, 0xf3, 0x3b, 0x78, 0x5f, 0x1a, 0x75, 0x42,
-	0x45, 0xdf, 0x83, 0xd9, 0x5d, 0x63, 0x10, 0x61, 0x3e, 0x25, 0x9a, 0x4d, 0x56, 0x99, 0x34, 0xe5,
-	0xca, 0xa4, 0xe9, 0xed, 0xf4, 0x09, 0xd0, 0x14, 0x1e, 0x69, 0xbe, 0x13, 0x19, 0x4e, 0x68, 0x87,
-	0xfb, 0x6c, 0xba, 0x50, 0x05, 0xf2, 0x74, 0xa1, 0xc0, 0xab, 0xb9, 0x97, 0xb5, 0xe5, 0xcf, 0x34,
-	0x38, 0x36, 0xb5, 0xd3, 0xdf, 0x84, 0x16, 0xea, 0x5d, 0x98, 0x21, 0x13, 0x9f, 0x54, 0x21, 0xdb,
-	0x76, 0x7f, 0xfb, 0xa5, 0x17, 0x68, 0x73, 0x0a, 0xac, 0x68, 0x60, 0x88, 0x5c, 0x34, 0x30, 0x84,
-	0x54, 0x52, 0x03, 0xf7, 0xc6, 0x4b, 0x2f, 0xd0, 0x46, 0x15, 0x98, 0x11, 0x0a, 0xc8, 0x46, 0x28,
-	0xa0, 0xff, 0xa6, 0x00, 0xe5, 0x38, 0xcd, 0x97, 0x62, 0x50, 0xbb, 0xa3, 0x18, 0x3c, 0x0f, 0x35,
-	0x0b, 0x5b, 0x7c, 0x7f, 0xb2, 0x5d, 0x47, 0x44, 0x73, 0x99, 0xad, 0x81, 0x0a, 0x4d, 0x91, 0xaf,
-	0xa6, 0x48, 0xe8, 0x0c, 0x94, 0x78, 0x3a, 0xbc, 0x4f, 0x03, 0x79, 0xbe, 0x7d, 0x74, 0x3c, 0x6a,
-	0x20, 0x81, 0x49, 0xa2, 0x31, 0x1f, 0xea, 0x00, 0xb8, 0xbd, 0xeb, 0xd8, 0x0c, 0xd7, 0x71, 0x68,
-	0xf0, 0xc4, 0xbc, 0xae, 0xf6, 0xe0, 0x4a, 0x4c, 0x67, 0xd5, 0x62, 0xc2, 0x2f, 0x57, 0x8b, 0x09,
-	0x8a, 0x3e, 0x00, 0x18, 0x1a, 0xb6, 0xc3, 0xe4, 0x78, 0x16, 0xae, 0x4f, 0x5b, 0x52, 0xd6, 0x63,
-	0x4e, 0xa6, 0x3d, 0x91, 0x94, 0xb5, 0x27, 0x28, 0xa9, 0xe9, 0x98, 0xad, 0xa0, 0x5e, 0xa0, 0x51,
-	0xba, 0x32, 0x4d, 0x35, 0x57, 0xbb, 0x44, 0xea, 0x3a, 0x2e, 0x22, 0xe9, 0x14, 0x5a, 0xc8, 0xb0,
-	0x0d, 0xec, 0x2d, 0x4c, 0x2a, 0x7a, 0x9a, 0x7f, 0xf3, 0x61, 0x13, 0x98, 0x3c, 0x6c, 0x02, 0x43,
-	0x2f, 0x03, 0x18, 0xe1, 0xba, 0x1b, 0x84, 0x57, 0x1c, 0x13, 0xd3, 0xbc, 0xba, 0xc4, 0x9a, 0x9f,
-	0xa0, 0x72, 0xf3, 0x13, 0x14, 0xbd, 0x06, 0x15, 0x8f, 0x6f, 0x15, 0xbd, 0x01, 0xa6, 0x79, 0x73,
-	0x89, 0x96, 0x9e, 0x4b, 0x12, 0x2c, 0xc9, 0xca, 0xdc, 0xe8, 0x2d, 0xa8, 0x9a, 0xae, 0x63, 0x46,
-	0xbe, 0x8f, 0x1d, 0x73, 0x7f, 0xc3, 0xd8, 0xc2, 0x34, 0x47, 0x2e, 0xb1, 0xa9, 0x92, 0x22, 0xc9,
-	0x53, 0x25, 0x45, 0x42, 0x2f, 0x42, 0x39, 0x30, 0xb7, 0xb1, 0x15, 0x0d, 0xb0, 0x4f, 0xd3, 0xe0,
-	0x32, 0x2f, 0x57, 0x05, 0x28, 0x09, 0x27, 0x9c, 0xa4, 0xf1, 0x76, 0x10, 0xe7, 0x52, 0x34, 0xb5,
-	0xe5, 0x8d, 0x97, 0x60, 0xb9, 0xf1, 0x12, 0xac, 0xff, 0x51, 0x83, 0xc5, 0x2c, 0xbf, 0xa7, 0xe6,
-	0xa0, 0x76, 0x4f, 0xe6, 0xe0, 0xbb, 0x50, 0xf2, 0x5c, 0xab, 0x1b, 0x78, 0xd8, 0xe4, 0xcb, 0x4c,
-	0x6a, 0x06, 0x5e, 0x75, 0xad, 0x0d, 0x0f, 0x9b, 0xdf, 0xb5, 0xc3, 0xed, 0xd5, 0x5d, 0xd7, 0xb6,
-	0x2e, 0xd9, 0x01, 0x9f, 0x2a, 0x1e, 0xa3, 0x28, 0x9b, 0x7c, 0x91, 0x83, 0xed, 0x12, 0x14, 0x98,
-	0x15, 0xfd, 0x4f, 0x79, 0xa8, 0xa5, 0xe7, 0xda, 0xff, 0x53, 0x57, 0xd0, 0x7b, 0x50, 0xb4, 0x59,
-	0x36, 0xca, 0xb7, 0xfd, 0x6f, 0x49, 0x0b, 0x71, 0x33, 0x39, 0x0f, 0x6b, 0xee, 0x3e, 0xd7, 0xe4,
-	0x69, 0x2b, 0x1d, 0x02, 0xaa, 0x99, 0x4b, 0xaa, 0x9a, 0x39, 0x88, 0x3a, 0x50, 0x0c, 0xb0, 0xbf,
-	0x6b, 0x9b, 0x98, 0xaf, 0x28, 0x0d, 0x59, 0xb3, 0xe9, 0xfa, 0x98, 0xe8, 0xdc, 0x60, 0x2c, 0x89,
-	0x4e, 0x2e, 0xa3, 0xea, 0xe4, 0x20, 0x7a, 0x17, 0xca, 0xa6, 0xeb, 0x6c, 0xd9, 0xfd, 0x75, 0xc3,
-	0xe3, 0x6b, 0xca, 0x89, 0x2c, 0xad, 0x67, 0x05, 0x13, 0xaf, 0xef, 0xc5, 0x67, 0xaa, 0xbe, 0x8f,
-	0xb9, 0x12, 0x87, 0xfe, 0x63, 0x06, 0x20, 0x71, 0x0e, 0x7a, 0x05, 0x2a, 0x78, 0x0f, 0x9b, 0x51,
-	0xe8, 0xfa, 0x62, 0x71, 0xe7, 0xc7, 0x65, 0x02, 0x56, 0x56, 0x63, 0x48, 0x50, 0x12, 0x5d, 0x8e,
-	0x31, 0xc4, 0x81, 0x67, 0x98, 0xe2, 0x9c, 0x8d, 0x36, 0x26, 0x06, 0xe5, 0xe8, 0x8a, 0x41, 0xf4,
-	0x6d, 0x98, 0xa1, 0x27, 0x73, 0xec, 0x88, 0x0d, 0x8d, 0x47, 0x8d, 0x05, 0x47, 0x3d, 0x93, 0xa3,
-	0x74, 0xf4, 0x06, 0xcc, 0xef, 0xc4, 0x13, 0x8f, 0xb4, 0x6d, 0x86, 0x0a, 0xd0, 0x7c, 0x2c, 0x21,
-	0x28, 0xad, 0x9b, 0x93, 0x71, 0xb4, 0x05, 0x15, 0xc3, 0x71, 0xdc, 0x90, 0x6e, 0x1c, 0xe2, 0xd8,
-	0xed, 0xc9, 0x69, 0xd3, 0xb4, 0xb9, 0x9a, 0xf0, 0xb2, 0xd4, 0x86, 0x46, 0xbc, 0xa4, 0x41, 0x8e,
-	0x78, 0x09, 0x46, 0x1d, 0x28, 0x0c, 0x8c, 0x1e, 0x1e, 0x88, 0x95, 0xfa, 0xf1, 0xa9, 0x26, 0x2e,
-	0x51, 0x36, 0xa6, 0x9d, 0xee, 0xd3, 0x4c, 0x4e, 0xde, 0xa7, 0x19, 0xb2, 0xbc, 0x05, 0xb5, 0x74,
-	0x7b, 0x0e, 0x96, 0x75, 0x3c, 0x29, 0x67, 0x1d, 0xe5, 0xdb, 0xe6, 0x39, 0x06, 0x54, 0xa4, 0x46,
-	0xdd, 0x0f, 0x13, 0xfa, 0xaf, 0x34, 0x58, 0xcc, 0x8a, 0x5d, 0xb4, 0x2e, 0x45, 0xbc, 0xc6, 0x8f,
-	0x0f, 0x32, 0xa6, 0x3a, 0x97, 0x9d, 0x12, 0xea, 0x49, 0xa0, 0xb7, 0x61, 0xc1, 0x71, 0x2d, 0xdc,
-	0x35, 0x88, 0x81, 0x81, 0x1d, 0x84, 0xf5, 0x1c, 0x3d, 0x96, 0xa5, 0xc7, 0x0e, 0x84, 0xb2, 0x2a,
-	0x08, 0x92, 0xf4, 0xbc, 0x42, 0xd0, 0x7f, 0xac, 0x41, 0x35, 0x75, 0x2a, 0x78, 0xd7, 0x99, 0x8f,
-	0x9c, 0xaf, 0xe4, 0x0e, 0x96, 0xaf, 0xe8, 0xe7, 0x01, 0x4d, 0x1e, 0x4e, 0x2a, 0x9a, 0xb4, 0x03,
-	0x6a, 0xfa, 0x44, 0x83, 0x5a, 0xfa, 0xcc, 0xf1, 0x81, 0x74, 0xe9, 0x6d, 0x28, 0xc7, 0xe7, 0x87,
-	0x77, 0xdb, 0x00, 0xfd, 0x1a, 0xcc, 0xb1, 0x31, 0x79, 0xd3, 0x1e, 0x84, 0xd8, 0x47, 0xe7, 0xa0,
-	0x10, 0x84, 0x46, 0x88, 0x83, 0xba, 0x76, 0x32, 0x7f, 0x7a, 0xe1, 0xcc, 0xd1, 0xc9, 0xc3, 0x3f,
-	0x42, 0x66, 0x41, 0xc7, 0x38, 0xe5, 0xa0, 0x63, 0x08, 0xd1, 0x2a, 0x1f, 0x71, 0xde, 0x23, 0xad,
-	0x97, 0x85, 0xd6, 0xc1, 0x3d, 0x19, 0x7c, 0xfd, 0x77, 0x1a, 0xeb, 0x7c, 0x7c, 0x7e, 0x75, 0xb7,
-	0xde, 0xec, 0x27, 0xe5, 0x31, 0xa9, 0x63, 0x03, 0x1a, 0x37, 0x07, 0x2d, 0x8f, 0x69, 0x74, 0x29,
-	0xe2, 0x72, 0x74, 0x29, 0x04, 0xfd, 0x9f, 0xac, 0xe5, 0xc9, 0x59, 0xe5, 0x83, 0x3e, 0x18, 0x48,
-	0x6d, 0x7e, 0xf9, 0xff, 0x62, 0xf3, 0x7b, 0x06, 0x8a, 0x74, 0xb5, 0x89, 0xf7, 0x25, 0x3a, 0x09,
-	0x08, 0xa4, 0xde, 0x15, 0x31, 0x44, 0xff, 0x97, 0x06, 0x0b, 0xea, 0x91, 0xeb, 0x03, 0xef, 0xfc,
-	0x84, 0xdb, 0xf3, 0xf7, 0xcf, 0xed, 0xf3, 0xca, 0x49, 0xf0, 0xc3, 0xd3, 0xf5, 0x9f, 0xe7, 0xe0,
-	0x68, 0xb6, 0x9a, 0xfb, 0x92, 0x43, 0x9f, 0x07, 0xb2, 0x1b, 0xd2, 0xe3, 0x5d, 0x36, 0x30, 0x4b,
-	0x13, 0x29, 0x34, 0xed, 0x82, 0xd8, 0x4a, 0x27, 0x8e, 0x70, 0x85, 0x38, 0x7a, 0x0f, 0x2a, 0xb6,
-	0x74, 0x58, 0xcc, 0x32, 0xe7, 0x63, 0xaa, 0x36, 0xf9, 0x88, 0x98, 0x55, 0x47, 0x53, 0x0e, 0x86,
-	0x65, 0x55, 0xed, 0x02, 0xcc, 0xd8, 0xce, 0x96, 0xab, 0xef, 0x42, 0x91, 0x37, 0x07, 0x3d, 0x0f,
-	0x65, 0x1a, 0x4b, 0x34, 0x2d, 0x64, 0xb9, 0x07, 0xdd, 0x4f, 0x08, 0x98, 0xba, 0xae, 0x2d, 0x09,
-	0x0c, 0xbd, 0x04, 0x40, 0xb2, 0x07, 0x27, 0x1a, 0xf6, 0xb0, 0x4f, 0xbb, 0x3b, 0xcb, 0xd2, 0x4f,
-	0xcf, 0xb5, 0x2e, 0x53, 0x50, 0x4e, 0x3f, 0x63, 0x50, 0xff, 0x75, 0x0e, 0x2a, 0xf2, 0xf1, 0xf4,
-	0x1d, 0x19, 0xff, 0x18, 0x44, 0x69, 0xd0, 0x35, 0x2c, 0x8b, 0xfc, 0xc5, 0x62, 0xd9, 0x6c, 0x4d,
-	0x1d, 0x24, 0xf1, 0xff, 0xaa, 0x90, 0x60, 0x89, 0x20, 0xbd, 0x00, 0xb4, 0x53, 0x24, 0xc9, 0x6a,
-	0x2d, 0x4d, 0x5b, 0xde, 0x81, 0xa5, 0x4c, 0x55, 0x72, 0xfa, 0x36, 0x7b, 0xaf, 0xd2, 0xb7, 0xdf,
-	0xcf, 0xc2, 0x52, 0xe6, 0xb5, 0xc0, 0x03, 0x8f, 0x62, 0x35, 0x82, 0xf2, 0xf7, 0x24, 0x82, 0x3e,
-	0xd1, 0xb2, 0x3c, 0xcb, 0x0e, 0x6f, 0x5f, 0x39, 0xc0, 0x5d, 0xc9, 0xbd, 0xf2, 0xb1, 0x3a, 0x2d,
-	0x67, 0xef, 0x28, 0x26, 0x0a, 0x07, 0x8d, 0x09, 0xf4, 0x2c, 0xcb, 0xc4, 0xa9, 0xad, 0x22, 0xb5,
-	0x25, 0x56, 0x88, 0x94, 0xa9, 0x22, 0x87, 0x48, 0x71, 0x26, 0x24, 0x58, 0xfd, 0x57, 0x4a, 0x8a,
-	0x33, 0xce, 0x93, 0x2e, 0x01, 0xe7, 0x64, 0xfc, 0x7f, 0x3b, 0x87, 0xff, 0xad, 0x41, 0x35, 0x75,
-	0x4f, 0xf8, 0xf0, 0xec, 0x41, 0x3f, 0xd5, 0xa0, 0x1c, 0x5f, 0x51, 0xdf, 0x75, 0xb2, 0xb8, 0x0a,
-	0x05, 0xcc, 0xae, 0x49, 0xd9, 0x72, 0x77, 0x24, 0xf5, 0x8c, 0x85, 0xd0, 0xf8, 0xc3, 0x95, 0xd4,
-	0xcd, 0x68, 0x87, 0x0b, 0xea, 0x7f, 0x8e, 0xd3, 0xc0, 0xa4, 0x4d, 0x0f, 0xd4, 0x15, 0x49, 0x9f,
-	0xf2, 0x77, 0xda, 0xa7, 0x3f, 0x94, 0x60, 0x96, 0xf2, 0x91, 0xda, 0x28, 0xc4, 0xfe, 0xd0, 0x76,
-	0x8c, 0x01, 0xed, 0x4e, 0x89, 0xc5, 0xad, 0xc0, 0xe4, 0xb8, 0x15, 0x18, 0xda, 0x86, 0x6a, 0x72,
-	0x72, 0x41, 0xd5, 0x64, 0xbf, 0x8e, 0x79, 0x5b, 0x65, 0x62, 0xe7, 0xa1, 0x29, 0x49, 0xf5, 0xfa,
-	0x30, 0x45, 0x44, 0x16, 0x2c, 0x98, 0xae, 0x13, 0x1a, 0xb6, 0x83, 0x7d, 0x66, 0x28, 0x9f, 0xf5,
-	0x3a, 0xe0, 0xac, 0xc2, 0xc3, 0x5e, 0x07, 0xa8, 0x72, 0xea, 0xeb, 0x00, 0x95, 0x86, 0x3e, 0x84,
-	0x79, 0x91, 0x2a, 0x33, 0x23, 0x33, 0x59, 0xaf, 0x03, 0xd6, 0x64, 0x16, 0x36, 0xa5, 0x15, 0x29,
-	0xf5, 0x75, 0x80, 0x42, 0x42, 0x03, 0xa8, 0x79, 0xae, 0xb5, 0xe9, 0xf0, 0x43, 0x5b, 0xa3, 0x37,
-	0xc0, 0xfc, 0xb8, 0x6c, 0x65, 0x22, 0xe5, 0x51, 0xb8, 0xd8, 0x52, 0x9c, 0x96, 0x55, 0xdf, 0xdb,
-	0xa4, 0xa9, 0xe8, 0x03, 0x98, 0x1b, 0x90, 0x8a, 0x65, 0x6d, 0xcf, 0xb3, 0x7d, 0x6c, 0x65, 0xbf,
-	0x8e, 0xb9, 0x24, 0x71, 0xb0, 0x85, 0x50, 0x96, 0x51, 0x5f, 0x08, 0xc8, 0x14, 0xe2, 0xfd, 0xa1,
-	0xb1, 0xd7, 0x89, 0x9c, 0x60, 0x6d, 0x8f, 0xbf, 0x74, 0x28, 0x66, 0x79, 0x7f, 0x5d, 0x65, 0x62,
-	0xde, 0x4f, 0x49, 0xaa, 0xde, 0x4f, 0x11, 0xd1, 0x25, 0xba, 0xce, 0x33, 0x97, 0xb0, 0x57, 0x32,
-	0x47, 0x27, 0x46, 0x8b, 0x79, 0x83, 0xd5, 0xf3, 0xfc, 0x4b, 0x51, 0x1a, 0x6b, 0xe0, 0x3e, 0xa0,
-	0xdd, 0xee, 0xe0, 0x30, 0xf2, 0x1d, 0x6c, 0xf1, 0x07, 0x32, 0x93, 0x3e, 0x50, 0xb8, 0x62, 0x1f,
-	0x28, 0xe8, 0x84, 0x0f, 0x14, 0x2a, 0x99, 0x53, 0x9e, 0x6b, 0x5d, 0x63, 0x21, 0x13, 0xc6, 0xcf,
-	0x66, 0x1e, 0x9d, 0x30, 0x95, 0xb0, 0xb0, 0x39, 0xa5, 0x48, 0xa9, 0x73, 0x4a, 0x21, 0xf1, 0x97,
-	0x1a, 0xf2, 0xbd, 0x3e, 0x1b, 0xa9, 0xca, 0x94, 0x97, 0x1a, 0x13, 0x9c, 0xf1, 0x4b, 0x8d, 0x09,
-	0xca, 0xc4, 0x4b, 0x8d, 0x49, 0xd9, 0x12, 0x14, 0x7c, 0x6c, 0x04, 0xae, 0xa3, 0x7f, 0xa6, 0x41,
-	0x35, 0x15, 0xe9, 0xe8, 0x75, 0x88, 0xef, 0x9a, 0xaf, 0xed, 0x7b, 0x22, 0x51, 0x55, 0xee, 0xa6,
-	0x09, 0x9e, 0x75, 0x37, 0x4d, 0x70, 0x74, 0x09, 0x20, 0xde, 0x15, 0x6e, 0xb5, 0x4c, 0xd2, 0x2c,
-	0x29, 0xe1, 0x94, 0xb3, 0xa4, 0x04, 0xd5, 0xbf, 0xcc, 0x43, 0x49, 0x4c, 0x95, 0xfb, 0x52, 0xc8,
-	0xb4, 0xa0, 0x38, 0xc4, 0x01, 0xbd, 0xa3, 0xce, 0x25, 0xf9, 0x08, 0x87, 0xe4, 0x7c, 0x84, 0x43,
-	0x6a, 0xba, 0x94, 0xbf, 0xa3, 0x74, 0x69, 0xe6, 0xc0, 0xe9, 0x12, 0xa6, 0xf7, 0x53, 0xd2, 0x82,
-	0x27, 0x0e, 0x97, 0x6f, 0xbd, 0x8a, 0x8a, 0xdb, 0x2b, 0x59, 0x30, 0x75, 0x7b, 0x25, 0x93, 0xd0,
-	0x0e, 0x1c, 0x96, 0x0e, 0xc0, 0xd9, 0xe4, 0xa0, 0x4b, 0xcf, 0xc2, 0xf4, 0xcb, 0xc0, 0x0e, 0xe5,
-	0x62, 0x01, 0xb6, 0x93, 0x42, 0xe5, 0x7c, 0x33, 0x4d, 0xd3, 0xff, 0x9e, 0x83, 0x05, 0xb5, 0xbd,
-	0xf7, 0xc5, 0xb1, 0xcf, 0x43, 0x19, 0xef, 0xd9, 0x61, 0xd7, 0x74, 0x2d, 0xcc, 0x8b, 0x36, 0xea,
-	0x27, 0x02, 0x9e, 0x75, 0x2d, 0xc5, 0x4f, 0x02, 0x93, 0x67, 0x43, 0xfe, 0x40, 0xb3, 0xe1, 0x69,
-	0x11, 0x4b, 0xf2, 0xd9, 0x8c, 0x9f, 0x1e, 0x04, 0xce, 0x93, 0x3d, 0xce, 0xe5, 0xfb, 0x34, 0xce,
-	0x37, 0x73, 0x50, 0x4b, 0xaf, 0x87, 0xdf, 0x8c, 0x10, 0x52, 0xa3, 0x21, 0x7f, 0xe0, 0x68, 0x78,
-	0x03, 0xe6, 0x49, 0xf6, 0x66, 0x84, 0x21, 0x7f, 0x63, 0x35, 0x43, 0xb3, 0x1e, 0xb6, 0x36, 0x45,
-	0xce, 0xaa, 0xc0, 0x95, 0xb5, 0x49, 0xc2, 0xf5, 0x1f, 0xe6, 0x60, 0x5e, 0x59, 0xb7, 0x1f, 0xbe,
-	0x25, 0x45, 0xaf, 0xc2, 0xbc, 0x92, 0x0e, 0xe9, 0x3f, 0x62, 0xf3, 0x44, 0xcd, 0x43, 0x1e, 0xbe,
-	0x71, 0x59, 0x80, 0x39, 0x39, 0xaf, 0xd2, 0x0f, 0x43, 0x35, 0x95, 0x06, 0xe9, 0x47, 0x61, 0x31,
-	0x6b, 0x33, 0xd6, 0x3f, 0xd7, 0x28, 0x61, 0xf2, 0x4d, 0xe4, 0x79, 0x00, 0x07, 0xdf, 0xe8, 0xde,
-	0xb6, 0x0c, 0x62, 0xbd, 0xc2, 0x37, 0x2e, 0xa6, 0xaa, 0x86, 0x92, 0xc0, 0x88, 0x26, 0x77, 0x60,
-	0x75, 0x6f, 0x5b, 0x7c, 0x50, 0x4d, 0xee, 0xc0, 0x9a, 0xd0, 0x24, 0x30, 0xfd, 0x27, 0x79, 0x51,
-	0xa1, 0x26, 0x8f, 0x0a, 0xdf, 0x87, 0x9a, 0x27, 0x3e, 0x6e, 0xdf, 0x5a, 0x9a, 0xa3, 0xc7, 0xfc,
-	0x69, 0x4b, 0x0b, 0x2a, 0x45, 0xd5, 0xcd, 0x8b, 0xaf, 0xdc, 0x01, 0x75, 0x77, 0x52, 0x55, 0xd8,
-	0x82, 0x4a, 0x41, 0xdf, 0x87, 0xc3, 0xe2, 0x35, 0xc7, 0x2e, 0x16, 0x0d, 0xcf, 0x4f, 0x55, 0xce,
-	0xde, 0x40, 0xc6, 0x02, 0xe9, 0x96, 0x57, 0x53, 0xa4, 0x94, 0x7a, 0xde, 0xf6, 0x99, 0x83, 0xaa,
-	0x4f, 0x37, 0xbe, 0x9a, 0x22, 0x91, 0x72, 0xb9, 0x9a, 0x7a, 0xa6, 0x89, 0xce, 0x41, 0x89, 0xfe,
-	0x8a, 0xe3, 0xd6, 0x1e, 0xa0, 0x81, 0x43, 0xf9, 0x14, 0x0b, 0x45, 0x0e, 0xa1, 0x17, 0xa1, 0x1c,
-	0xbf, 0xe6, 0xe4, 0xd7, 0x66, 0x2c, 0x04, 0x04, 0xa8, 0x84, 0x80, 0x00, 0xf5, 0x5f, 0x68, 0x70,
-	0x6c, 0xea, 0x13, 0xce, 0x07, 0x5d, 0x3b, 0x3f, 0xf5, 0x2c, 0x94, 0xc4, 0x3d, 0x18, 0x02, 0x28,
-	0xbc, 0xb3, 0xb9, 0xb6, 0xb9, 0x76, 0xae, 0x76, 0x08, 0x55, 0xa0, 0x78, 0x75, 0xed, 0xf2, 0xb9,
-	0x0b, 0x97, 0xdf, 0xaa, 0x69, 0xe4, 0xa3, 0xb3, 0x79, 0xf9, 0x32, 0xf9, 0xc8, 0x3d, 0x75, 0x49,
-	0x7e, 0x50, 0xc2, 0x76, 0x45, 0x34, 0x07, 0xa5, 0x55, 0xcf, 0xa3, 0x71, 0xcb, 0x64, 0xd7, 0x76,
-	0x6d, 0x12, 0xab, 0x35, 0x0d, 0x15, 0x21, 0x7f, 0xe5, 0xca, 0x7a, 0x2d, 0x87, 0x16, 0xa1, 0x76,
-	0x0e, 0x1b, 0xd6, 0xc0, 0x76, 0xb0, 0x88, 0xfd, 0x5a, 0xbe, 0x7d, 0xfd, 0x8b, 0xaf, 0x56, 0xb4,
-	0x2f, 0xbf, 0x5a, 0xd1, 0xfe, 0xf6, 0xd5, 0x8a, 0x76, 0xf3, 0xeb, 0x95, 0x43, 0x5f, 0x7e, 0xbd,
-	0x72, 0xe8, 0x2f, 0x5f, 0xaf, 0x1c, 0x7a, 0xff, 0x59, 0xe9, 0x57, 0x47, 0xac, 0x4f, 0x9e, 0xef,
-	0x92, 0x65, 0x8f, 0x7f, 0xb5, 0xd2, 0xbf, 0xb3, 0xfa, 0x3c, 0x77, 0x62, 0x95, 0x7e, 0x5e, 0x65,
-	0x7c, 0xcd, 0x0b, 0x6e, 0x93, 0x01, 0xf4, 0x67, 0x36, 0x41, 0xaf, 0x40, 0x7f, 0x4e, 0xf3, 0xfc,
-	0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xcb, 0xad, 0xb1, 0xc8, 0xa2, 0x35, 0x00, 0x00,
+	// 3413 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x3b, 0x4b, 0x6c, 0x1b, 0xd7,
+	0xb5, 0x1e, 0x52, 0xe2, 0xe7, 0x50, 0x12, 0xe9, 0x6b, 0x59, 0xa1, 0x15, 0x5b, 0x74, 0xc6, 0x79,
+	0x2f, 0x4e, 0x90, 0x90, 0x89, 0xf3, 0x41, 0x3e, 0x0f, 0x09, 0x44, 0x5b, 0x89, 0xed, 0x58, 0xb6,
+	0x43, 0xd9, 0x79, 0x7e, 0x41, 0x1e, 0x98, 0x21, 0xe7, 0x8a, 0x1a, 0x89, 0x9c, 0x99, 0xcc, 0x47,
+	0x96, 0x80, 0x2c, 0x5a, 0xa0, 0x4d, 0x77, 0xad, 0x81, 0x76, 0x51, 0xa0, 0x8b, 0x74, 0xdb, 0x00,
+	0xdd, 0x15, 0xe8, 0xba, 0xbb, 0x14, 0x28, 0x8a, 0xb4, 0xab, 0xae, 0xd8, 0x22, 0x41, 0x17, 0xe5,
+	0xa2, 0xeb, 0xb6, 0xab, 0xe2, 0xfe, 0x66, 0xee, 0x9d, 0x19, 0xda, 0xaa, 0x3f, 0x75, 0x0a, 0xaf,
+	0xa4, 0x39, 0xdf, 0x7b, 0xef, 0xf9, 0xcc, 0x39, 0x67, 0x2e, 0xe1, 0x84, 0xbb, 0x33, 0x68, 0x19,
+	0xde, 0xc8, 0x30, 0x0d, 0xbc, 0x8b, 0xed, 0xc0, 0x6f, 0xb1, 0x3f, 0x4d, 0xd7, 0x73, 0x02, 0x07,
+	0xcd, 0xc9, 0xa8, 0x65, 0x7d, 0xe7, 0x55, 0xbf, 0x69, 0x39, 0x2d, 0xc3, 0xb5, 0x5a, 0x7d, 0xc7,
+	0xc3, 0xad, 0xdd, 0x17, 0x5a, 0x03, 0x6c, 0x63, 0xcf, 0x08, 0xb0, 0xc9, 0x38, 0x96, 0x4f, 0x4b,
+	0x34, 0x36, 0x0e, 0x6e, 0x3a, 0xde, 0x8e, 0x65, 0x0f, 0xb2, 0x28, 0x1b, 0x03, 0xc7, 0x19, 0x0c,
+	0x71, 0x8b, 0x3e, 0xf5, 0xc2, 0xcd, 0x56, 0x60, 0x8d, 0xb0, 0x1f, 0x18, 0x23, 0x97, 0x13, 0xbc,
+	0x14, 0x8b, 0x1a, 0x19, 0xfd, 0x2d, 0xcb, 0xc6, 0xde, 0x7e, 0x8b, 0xae, 0xd7, 0xb5, 0x5a, 0x1e,
+	0xf6, 0x9d, 0xd0, 0xeb, 0xe3, 0x94, 0xd8, 0xe7, 0x06, 0x56, 0xb0, 0x15, 0xf6, 0x9a, 0x7d, 0x67,
+	0xd4, 0x1a, 0x38, 0x03, 0x27, 0x96, 0x4f, 0x9e, 0xe8, 0x03, 0xfd, 0x8f, 0x93, 0xbf, 0x6e, 0xd9,
+	0x01, 0xf6, 0x6c, 0x63, 0xd8, 0xf2, 0xfb, 0x5b, 0xd8, 0x0c, 0x87, 0xd8, 0x8b, 0xff, 0x73, 0x7a,
+	0xdb, 0xb8, 0x1f, 0xf8, 0x29, 0x00, 0xe3, 0xd5, 0x6f, 0x2d, 0xc2, 0xfc, 0x1a, 0x39, 0x9a, 0x0d,
+	0xfc, 0x71, 0x88, 0xed, 0x3e, 0x46, 0x4f, 0xc3, 0xec, 0xc7, 0x21, 0x0e, 0x71, 0x5d, 0x3b, 0xa9,
+	0x9d, 0x2e, 0xb7, 0x8f, 0x4c, 0xc6, 0x8d, 0x2a, 0x05, 0x3c, 0xeb, 0x8c, 0xac, 0x00, 0x8f, 0xdc,
+	0x60, 0xbf, 0xc3, 0x28, 0xd0, 0xeb, 0x30, 0xb7, 0xed, 0xf4, 0xba, 0x3e, 0x0e, 0xba, 0xb6, 0x31,
+	0xc2, 0xf5, 0x1c, 0xe5, 0xa8, 0x4f, 0xc6, 0x8d, 0xc5, 0x6d, 0xa7, 0xb7, 0x81, 0x83, 0xcb, 0xc6,
+	0x48, 0x66, 0x83, 0x18, 0x8a, 0x9e, 0x83, 0x62, 0xe8, 0x63, 0xaf, 0x6b, 0x99, 0xf5, 0x3c, 0x65,
+	0x5b, 0x9c, 0x8c, 0x1b, 0x35, 0x02, 0xba, 0x60, 0x4a, 0x2c, 0x05, 0x06, 0x41, 0xcf, 0x42, 0x61,
+	0xe0, 0x39, 0xa1, 0xeb, 0xd7, 0x67, 0x4e, 0xe6, 0x05, 0x35, 0x83, 0xc8, 0xd4, 0x0c, 0x82, 0xae,
+	0x40, 0x81, 0xd9, 0xbb, 0x3e, 0x7b, 0x32, 0x7f, 0xba, 0x72, 0xe6, 0x89, 0xa6, 0xec, 0x04, 0x4d,
+	0x65, 0xc3, 0xec, 0x89, 0x09, 0x64, 0x78, 0x59, 0x20, 0x77, 0x9b, 0xbf, 0x1c, 0x86, 0x59, 0x4a,
+	0x87, 0xae, 0x40, 0xb1, 0xef, 0x61, 0x62, 0xac, 0x3a, 0x3a, 0xa9, 0x9d, 0xae, 0x9c, 0x59, 0x6e,
+	0x32, 0x27, 0x68, 0x0a, 0x23, 0x35, 0xaf, 0x09, 0x27, 0x68, 0x1f, 0x9b, 0x8c, 0x1b, 0x87, 0x39,
+	0x79, 0x2c, 0xf5, 0xd6, 0x1f, 0x1b, 0x5a, 0x47, 0x48, 0x41, 0x57, 0xa1, 0xec, 0x87, 0xbd, 0x91,
+	0x15, 0x5c, 0x74, 0x7a, 0xf4, 0xcc, 0x2b, 0x67, 0x1e, 0x53, 0x97, 0xbb, 0x21, 0xd0, 0xed, 0xc7,
+	0x26, 0xe3, 0xc6, 0x91, 0x88, 0x3a, 0x96, 0x78, 0xfe, 0x50, 0x27, 0x16, 0x82, 0xb6, 0xa0, 0xea,
+	0x61, 0xd7, 0xb3, 0x1c, 0xcf, 0x0a, 0x2c, 0x1f, 0x13, 0xb9, 0x39, 0x2a, 0xf7, 0x84, 0x2a, 0xb7,
+	0xa3, 0x12, 0xb5, 0x4f, 0x4c, 0xc6, 0x8d, 0x63, 0x09, 0x4e, 0x45, 0x47, 0x52, 0x2c, 0x0a, 0x00,
+	0x25, 0x40, 0x1b, 0x38, 0xa0, 0xf6, 0xac, 0x9c, 0x39, 0x79, 0x5b, 0x65, 0x1b, 0x38, 0x68, 0x9f,
+	0x9c, 0x8c, 0x1b, 0xc7, 0xd3, 0xfc, 0x8a, 0xca, 0x0c, 0xf9, 0x68, 0x08, 0x35, 0x19, 0x6a, 0x92,
+	0x0d, 0xce, 0x50, 0x9d, 0x2b, 0xd3, 0x75, 0x12, 0xaa, 0xf6, 0xca, 0x64, 0xdc, 0x58, 0x4e, 0xf2,
+	0x2a, 0xfa, 0x52, 0x92, 0x89, 0x7d, 0xfa, 0x86, 0xdd, 0xc7, 0x43, 0xa2, 0x66, 0x36, 0xcb, 0x3e,
+	0x67, 0x05, 0x9a, 0xd9, 0x27, 0xa2, 0x56, 0xed, 0x13, 0x81, 0xd1, 0x87, 0x30, 0x17, 0x3d, 0x90,
+	0xf3, 0x2a, 0x70, 0x3f, 0xca, 0x16, 0x4a, 0x4e, 0x6a, 0x79, 0x32, 0x6e, 0x2c, 0xc9, 0x3c, 0x8a,
+	0x68, 0x45, 0x5a, 0x2c, 0x7d, 0xc8, 0x4e, 0xa6, 0x38, 0x5d, 0x3a, 0xa3, 0x90, 0xa5, 0x0f, 0xd3,
+	0x27, 0xa2, 0x48, 0x23, 0xd2, 0x49, 0x10, 0x87, 0xfd, 0x3e, 0xc6, 0x26, 0x36, 0xeb, 0xa5, 0x2c,
+	0xe9, 0x17, 0x25, 0x0a, 0x26, 0x5d, 0xe6, 0x51, 0xa5, 0xcb, 0x18, 0x72, 0xd6, 0xdb, 0x4e, 0x6f,
+	0xcd, 0xf3, 0x1c, 0xcf, 0xaf, 0x97, 0xb3, 0xce, 0xfa, 0xa2, 0x40, 0xb3, 0xb3, 0x8e, 0xa8, 0xd5,
+	0xb3, 0x8e, 0xc0, 0x7c, 0xbd, 0x9d, 0xd0, 0xbe, 0x84, 0x0d, 0x1f, 0x9b, 0x75, 0x98, 0xb2, 0xde,
+	0x88, 0x22, 0x5a, 0x6f, 0x04, 0x49, 0xad, 0x37, 0xc2, 0x20, 0x13, 0x16, 0xd8, 0xf3, 0xaa, 0xef,
+	0x5b, 0x03, 0x1b, 0x9b, 0xf5, 0x0a, 0x95, 0x7f, 0x3c, 0x4b, 0xbe, 0xa0, 0x69, 0x1f, 0x9f, 0x8c,
+	0x1b, 0x75, 0x95, 0x4f, 0xd1, 0x91, 0x90, 0x89, 0x3e, 0x82, 0x79, 0x06, 0xe9, 0x84, 0xb6, 0x6d,
+	0xd9, 0x83, 0xfa, 0x1c, 0x55, 0xf2, 0x78, 0x96, 0x12, 0x4e, 0xd2, 0x7e, 0x7c, 0x32, 0x6e, 0x3c,
+	0xa6, 0x70, 0x29, 0x2a, 0x54, 0x81, 0x24, 0x63, 0x30, 0x40, 0x6c, 0xd8, 0xf9, 0xac, 0x8c, 0x71,
+	0x51, 0x25, 0x62, 0x19, 0x23, 0xc1, 0xa9, 0x66, 0x8c, 0x04, 0x32, 0xb6, 0x07, 0x37, 0xf2, 0xc2,
+	0x74, 0x7b, 0x70, 0x3b, 0x4b, 0xf6, 0xc8, 0x30, 0xb5, 0x22, 0x0d, 0x7d, 0x02, 0xe4, 0xc5, 0x73,
+	0x2e, 0x74, 0x87, 0x56, 0xdf, 0x08, 0xf0, 0x39, 0x1c, 0xe0, 0x3e, 0xc9, 0xd4, 0x55, 0xaa, 0x45,
+	0x4f, 0x69, 0x49, 0x51, 0xb6, 0xf5, 0xc9, 0xb8, 0xb1, 0x92, 0x25, 0x43, 0xd1, 0x9a, 0xa9, 0x05,
+	0x7d, 0x4b, 0x83, 0xa3, 0x7e, 0x60, 0xd8, 0xa6, 0x31, 0x74, 0x6c, 0x7c, 0xc1, 0x1e, 0x78, 0xd8,
+	0xf7, 0x2f, 0xd8, 0x9b, 0x4e, 0xbd, 0x46, 0xf5, 0x9f, 0x4a, 0xa4, 0xf5, 0x2c, 0xd2, 0xf6, 0xa9,
+	0xc9, 0xb8, 0xd1, 0xc8, 0x94, 0xa2, 0xac, 0x20, 0x5b, 0x11, 0xda, 0x83, 0x23, 0xa2, 0xaa, 0xb8,
+	0x1e, 0x58, 0x43, 0xcb, 0x37, 0x02, 0xcb, 0xb1, 0xeb, 0x87, 0xa9, 0xfe, 0x27, 0x92, 0xd9, 0x31,
+	0x45, 0xd8, 0x7e, 0x62, 0x32, 0x6e, 0x9c, 0xc8, 0x90, 0xa0, 0xe8, 0xce, 0x52, 0x11, 0xbb, 0xd0,
+	0x55, 0x0f, 0x13, 0x42, 0x6c, 0xd6, 0x8f, 0x4c, 0x77, 0xa1, 0x88, 0x48, 0x76, 0xa1, 0x08, 0x98,
+	0xe5, 0x42, 0x11, 0x92, 0x68, 0x72, 0x0d, 0x2f, 0xb0, 0x88, 0xda, 0x75, 0xc3, 0xdb, 0xc1, 0x5e,
+	0x7d, 0x31, 0x4b, 0xd3, 0x55, 0x95, 0x88, 0x69, 0x4a, 0x70, 0xaa, 0x9a, 0x12, 0x48, 0x74, 0x4b,
+	0x03, 0x75, 0x69, 0x96, 0x63, 0x77, 0x48, 0xd9, 0xe0, 0x93, 0xed, 0x1d, 0xa5, 0x4a, 0x9f, 0xba,
+	0xcd, 0xf6, 0x64, 0xf2, 0xf6, 0x53, 0x93, 0x71, 0xe3, 0xd4, 0x54, 0x69, 0xca, 0x42, 0xa6, 0x2b,
+	0x45, 0x37, 0xa0, 0x42, 0x90, 0x98, 0x16, 0x60, 0x66, 0x7d, 0x89, 0xae, 0xe1, 0x58, 0x7a, 0x0d,
+	0x9c, 0x80, 0x56, 0x20, 0x47, 0x25, 0x0e, 0x45, 0x8f, 0x2c, 0xaa, 0x5d, 0x84, 0x59, 0xca, 0xaf,
+	0x4f, 0x0a, 0x70, 0x24, 0xc3, 0x37, 0xd0, 0x9b, 0x50, 0xf0, 0x42, 0x9b, 0x14, 0x6c, 0xac, 0x4a,
+	0x41, 0xaa, 0xd6, 0xeb, 0xa1, 0x65, 0xb2, 0x6a, 0xd1, 0x0b, 0x6d, 0xa5, 0x86, 0x9b, 0xa5, 0x00,
+	0xc2, 0x4f, 0xaa, 0x45, 0xcb, 0xe4, 0xd5, 0xc8, 0x54, 0xfe, 0x6d, 0xa7, 0xa7, 0xf2, 0x53, 0x00,
+	0xc2, 0x30, 0x2f, 0x1c, 0xaf, 0x6b, 0x91, 0xa8, 0x62, 0x75, 0xc6, 0x93, 0xaa, 0x98, 0x77, 0xc3,
+	0x1e, 0xf6, 0x6c, 0x1c, 0x60, 0x5f, 0xec, 0x81, 0x86, 0x15, 0xcd, 0x22, 0x9e, 0x04, 0x91, 0xe4,
+	0xcf, 0xc9, 0x70, 0xf4, 0x23, 0x0d, 0xea, 0x23, 0x63, 0xaf, 0x2b, 0x80, 0x7e, 0x77, 0xd3, 0xf1,
+	0xba, 0x2e, 0xf6, 0x2c, 0xc7, 0xa4, 0xc5, 0x67, 0xe5, 0xcc, 0xff, 0xdc, 0x31, 0x90, 0x9a, 0xeb,
+	0xc6, 0x9e, 0x00, 0xfb, 0x6f, 0x3b, 0xde, 0x55, 0xca, 0xbe, 0x66, 0x07, 0xde, 0x7e, 0xfb, 0xc4,
+	0x17, 0xe3, 0xc6, 0x21, 0x62, 0x96, 0x51, 0x16, 0x4d, 0x27, 0x1b, 0x8c, 0x7e, 0xa0, 0xc1, 0x52,
+	0xe0, 0x04, 0xc6, 0xb0, 0xdb, 0x0f, 0x47, 0xe1, 0xd0, 0x08, 0xac, 0x5d, 0xdc, 0x0d, 0x7d, 0x63,
+	0x80, 0x79, 0x8d, 0xfb, 0xc6, 0x9d, 0x17, 0x75, 0x8d, 0xf0, 0x9f, 0x8d, 0xd8, 0xaf, 0x13, 0x6e,
+	0xb6, 0xa6, 0xe3, 0x7c, 0x4d, 0x8b, 0x41, 0x06, 0x49, 0x27, 0x13, 0xba, 0xfc, 0x53, 0x0d, 0x96,
+	0xa7, 0x6f, 0x13, 0x9d, 0x82, 0xfc, 0x0e, 0xde, 0xe7, 0x5d, 0xc4, 0xe1, 0xc9, 0xb8, 0x31, 0xbf,
+	0x83, 0xf7, 0xa5, 0x53, 0x27, 0x58, 0xf4, 0x7f, 0x30, 0xbb, 0x6b, 0x0c, 0x43, 0xcc, 0x5d, 0xa2,
+	0xd9, 0x64, 0xfd, 0x52, 0x53, 0xee, 0x97, 0x9a, 0xee, 0xce, 0x80, 0x00, 0x9a, 0xc2, 0x22, 0xcd,
+	0xf7, 0x42, 0xc3, 0x0e, 0xac, 0x60, 0x9f, 0xb9, 0x0b, 0x15, 0x20, 0xbb, 0x0b, 0x05, 0xbc, 0x9e,
+	0x7b, 0x55, 0x5b, 0xfe, 0x4c, 0x83, 0x63, 0x53, 0x37, 0xfd, 0x4d, 0x58, 0xa1, 0xde, 0x85, 0x19,
+	0xe2, 0xf8, 0xa4, 0xbf, 0xd9, 0xb2, 0x06, 0x5b, 0xaf, 0xbc, 0x44, 0x97, 0x53, 0x60, 0xed, 0x08,
+	0x83, 0xc8, 0xed, 0x08, 0x83, 0x90, 0x1e, 0x6d, 0xe8, 0xdc, 0x7c, 0xe5, 0x25, 0xba, 0xa8, 0x02,
+	0x53, 0x42, 0x01, 0xb2, 0x12, 0x0a, 0xd0, 0x7f, 0x51, 0x80, 0x72, 0xd4, 0x40, 0x48, 0x31, 0xa8,
+	0xdd, 0x55, 0x0c, 0x9e, 0x87, 0x9a, 0x89, 0x4d, 0xfe, 0xe6, 0xb3, 0x1c, 0x5b, 0x44, 0x73, 0x99,
+	0x65, 0x57, 0x05, 0xa7, 0xf0, 0x57, 0x13, 0x28, 0x74, 0x06, 0x4a, 0xbc, 0xd0, 0xde, 0xa7, 0x81,
+	0x3c, 0xdf, 0x5e, 0x9a, 0x8c, 0x1b, 0x48, 0xc0, 0x24, 0xd6, 0x88, 0x0e, 0x75, 0x00, 0x58, 0xf7,
+	0xba, 0x8e, 0x03, 0x83, 0x97, 0xfc, 0x75, 0x75, 0x07, 0x57, 0x22, 0x3c, 0xeb, 0x43, 0x63, 0x7a,
+	0xb9, 0x0f, 0x8d, 0xa1, 0xe8, 0x43, 0x80, 0x91, 0x61, 0xd9, 0x8c, 0x8f, 0xd7, 0xf7, 0xfa, 0xb4,
+	0x94, 0xb2, 0x1e, 0x51, 0x32, 0xe9, 0x31, 0xa7, 0x2c, 0x3d, 0x86, 0x92, 0x6e, 0x91, 0xf7, 0xdb,
+	0xf5, 0x02, 0x8d, 0xd2, 0x95, 0x69, 0xa2, 0xb9, 0xd8, 0xa3, 0xa4, 0x63, 0xe4, 0x2c, 0x92, 0x4c,
+	0x21, 0x85, 0x1c, 0xdb, 0xd0, 0xda, 0xc4, 0x81, 0x35, 0xc2, 0xb4, 0xb2, 0xe7, 0xc7, 0x26, 0x60,
+	0xf2, 0xb1, 0x09, 0x18, 0x7a, 0x15, 0xc0, 0x08, 0xd6, 0x1d, 0x3f, 0xb8, 0x62, 0xf7, 0x31, 0xad,
+	0xd8, 0x4b, 0x6c, 0xf9, 0x31, 0x54, 0x5e, 0x7e, 0x0c, 0x45, 0x6f, 0x40, 0xc5, 0xe5, 0x2f, 0xa1,
+	0xde, 0x10, 0xd3, 0x8a, 0xbc, 0xc4, 0x5e, 0x29, 0x12, 0x58, 0xe2, 0x95, 0xa9, 0xd1, 0x3b, 0x50,
+	0xed, 0x3b, 0x76, 0x3f, 0xf4, 0x3c, 0x6c, 0xf7, 0xf7, 0x37, 0x8c, 0x4d, 0x4c, 0xab, 0xef, 0x12,
+	0x73, 0x95, 0x04, 0x4a, 0x76, 0x95, 0x04, 0x0a, 0xbd, 0x0c, 0xe5, 0x68, 0x7a, 0x41, 0x0b, 0xec,
+	0x32, 0x6f, 0x84, 0x05, 0x50, 0x62, 0x8e, 0x29, 0xc9, 0xe2, 0x2d, 0x3f, 0xaa, 0xd2, 0x68, 0xd1,
+	0xcc, 0x17, 0x2f, 0x81, 0xe5, 0xc5, 0x4b, 0x60, 0xfd, 0x37, 0x1a, 0x2c, 0x66, 0xd9, 0x3d, 0xe1,
+	0x83, 0xda, 0x7d, 0xf1, 0xc1, 0xf7, 0xa1, 0xe4, 0x3a, 0x66, 0xd7, 0x77, 0x71, 0x9f, 0xa7, 0x99,
+	0x84, 0x07, 0x5e, 0x75, 0xcc, 0x0d, 0x17, 0xf7, 0xff, 0xd7, 0x0a, 0xb6, 0x56, 0x77, 0x1d, 0xcb,
+	0xbc, 0x64, 0xf9, 0xdc, 0x55, 0x5c, 0x86, 0x51, 0x5e, 0xeb, 0x45, 0x0e, 0x6c, 0x97, 0xa0, 0xc0,
+	0xb4, 0xe8, 0xbf, 0xcd, 0x43, 0x2d, 0xe9, 0x6b, 0xff, 0x49, 0x5b, 0x41, 0x37, 0xa0, 0x68, 0xb1,
+	0x3a, 0x97, 0xbf, 0xf6, 0xff, 0x4b, 0x4a, 0xc4, 0xcd, 0x78, 0x4a, 0xd7, 0xdc, 0x7d, 0xa1, 0xc9,
+	0x0b, 0x62, 0x7a, 0x04, 0x54, 0x32, 0xe7, 0x54, 0x25, 0x73, 0x20, 0xea, 0x40, 0xd1, 0xc7, 0xde,
+	0xae, 0xd5, 0xc7, 0x3c, 0xa3, 0x34, 0x64, 0xc9, 0x7d, 0xc7, 0xc3, 0x44, 0xe6, 0x06, 0x23, 0x89,
+	0x65, 0x72, 0x1e, 0x55, 0x26, 0x07, 0xa2, 0xf7, 0xa1, 0xdc, 0x77, 0xec, 0x4d, 0x6b, 0xb0, 0x6e,
+	0xb8, 0x3c, 0xa7, 0x9c, 0xc8, 0x92, 0x7a, 0x56, 0x10, 0xf1, 0xc9, 0x81, 0x78, 0x4c, 0x4c, 0x0e,
+	0x22, 0xaa, 0xd8, 0xa0, 0x7f, 0x9d, 0x01, 0x88, 0x8d, 0x83, 0x5e, 0x83, 0x0a, 0xde, 0xc3, 0xfd,
+	0x30, 0x70, 0x3c, 0x91, 0xdc, 0xf9, 0x20, 0x4e, 0x80, 0x95, 0x6c, 0x0c, 0x31, 0x94, 0x44, 0x97,
+	0x6d, 0x8c, 0xb0, 0xef, 0x1a, 0x7d, 0x31, 0xc1, 0xa3, 0x8b, 0x89, 0x80, 0x72, 0x74, 0x45, 0x40,
+	0xf4, 0xdf, 0x30, 0x43, 0x67, 0x7e, 0x6c, 0x78, 0x87, 0x26, 0xe3, 0xc6, 0x82, 0xad, 0x4e, 0xfb,
+	0x28, 0x1e, 0xbd, 0x05, 0xf3, 0x3b, 0x91, 0xe3, 0x91, 0xb5, 0xcd, 0x50, 0x06, 0x5a, 0x8f, 0xc5,
+	0x08, 0x65, 0x75, 0x73, 0x32, 0x1c, 0x6d, 0x42, 0xc5, 0xb0, 0x6d, 0x27, 0xa0, 0x2f, 0x0e, 0x31,
+	0xd0, 0x7b, 0x7a, 0x9a, 0x9b, 0x36, 0x57, 0x63, 0x5a, 0x56, 0xda, 0xd0, 0x88, 0x97, 0x24, 0xc8,
+	0x11, 0x2f, 0x81, 0x51, 0x07, 0x0a, 0x43, 0xa3, 0x87, 0x87, 0x22, 0x53, 0x3f, 0x39, 0x55, 0xc5,
+	0x25, 0x4a, 0xc6, 0xa4, 0xd3, 0xf7, 0x34, 0xe3, 0x93, 0xdf, 0xd3, 0x0c, 0xb2, 0xbc, 0x09, 0xb5,
+	0xe4, 0x7a, 0x0e, 0x56, 0x75, 0x3c, 0x2d, 0x57, 0x1d, 0xe5, 0x3b, 0xd6, 0x39, 0x06, 0x54, 0xa4,
+	0x45, 0x3d, 0x08, 0x15, 0xfa, 0xcf, 0x34, 0x58, 0xcc, 0x8a, 0x5d, 0xb4, 0x2e, 0x45, 0xbc, 0xc6,
+	0x07, 0x13, 0x19, 0xae, 0xce, 0x79, 0xa7, 0x84, 0x7a, 0x1c, 0xe8, 0x6d, 0x58, 0xb0, 0x1d, 0x13,
+	0x77, 0x0d, 0xa2, 0x60, 0x68, 0xf9, 0x41, 0x3d, 0x47, 0x07, 0xbe, 0x74, 0xa0, 0x41, 0x30, 0xab,
+	0x02, 0x21, 0x71, 0xcf, 0x2b, 0x08, 0xfd, 0xbb, 0x1a, 0x54, 0x13, 0xf3, 0xc6, 0x7b, 0xae, 0x7c,
+	0xe4, 0x7a, 0x25, 0x77, 0xb0, 0x7a, 0x45, 0xff, 0x61, 0x0e, 0x2a, 0x52, 0x33, 0x76, 0xcf, 0x6b,
+	0xd8, 0x86, 0x2a, 0x7f, 0xbd, 0x59, 0xf6, 0x80, 0xf5, 0x40, 0x39, 0x3e, 0x59, 0x48, 0x8d, 0xf7,
+	0x2f, 0x3a, 0xbd, 0x8d, 0x88, 0x96, 0xb6, 0x40, 0x74, 0xec, 0xe4, 0x2b, 0x30, 0x49, 0xc5, 0x82,
+	0x8a, 0x41, 0x37, 0x60, 0x29, 0x74, 0x4d, 0x23, 0xc0, 0x5d, 0x9f, 0x0f, 0xca, 0xbb, 0x76, 0x38,
+	0xea, 0x61, 0x8f, 0x46, 0xfc, 0x2c, 0x1b, 0x94, 0x30, 0x0a, 0x31, 0x49, 0xbf, 0x4c, 0xf1, 0x92,
+	0xcc, 0xc5, 0x2c, 0xbc, 0x7e, 0x1e, 0x50, 0x7a, 0x18, 0xac, 0x9c, 0xaf, 0x76, 0xc0, 0xf3, 0xfd,
+	0x54, 0x83, 0x5a, 0x72, 0xc6, 0xfb, 0x50, 0x0c, 0xfd, 0x2e, 0x94, 0xa3, 0x79, 0xed, 0xbd, 0x2e,
+	0x40, 0xbf, 0x06, 0x73, 0xec, 0x4c, 0xde, 0xb6, 0x86, 0x01, 0xf6, 0xd0, 0x39, 0x28, 0xf8, 0x81,
+	0x11, 0x60, 0xbf, 0xae, 0x9d, 0xcc, 0x9f, 0x5e, 0x38, 0xb3, 0x94, 0x1e, 0xb6, 0x12, 0x34, 0x4b,
+	0x45, 0x8c, 0x52, 0x4e, 0x45, 0x0c, 0x42, 0xa4, 0xca, 0x23, 0xe5, 0xfb, 0x24, 0xf5, 0xb2, 0x90,
+	0x3a, 0xbc, 0x2f, 0x87, 0xaf, 0xff, 0x52, 0x63, 0x9b, 0x8f, 0xe6, 0x85, 0xf7, 0x6a, 0xcd, 0x41,
+	0x3c, 0x34, 0x20, 0x6e, 0xed, 0xd3, 0x6c, 0x72, 0xd0, 0xa1, 0x01, 0xcd, 0x39, 0x0a, 0xbb, 0x9c,
+	0x73, 0x14, 0x84, 0xfe, 0xfb, 0x1c, 0x5d, 0x79, 0x3c, 0x1b, 0x7e, 0xd8, 0xe3, 0x92, 0x44, 0x49,
+	0x90, 0xff, 0x17, 0x4a, 0x82, 0xe7, 0xa0, 0x48, 0x73, 0x70, 0xf4, 0xb6, 0xa6, 0x4e, 0x40, 0x40,
+	0xea, 0xb7, 0x39, 0x06, 0xb9, 0x4d, 0xaa, 0x98, 0xbd, 0xc7, 0x54, 0xf1, 0x77, 0x0d, 0x16, 0xd4,
+	0xe1, 0xf9, 0x43, 0x3f, 0xd6, 0x94, 0x43, 0xe5, 0x1f, 0x90, 0x43, 0xfd, 0x4d, 0x83, 0x79, 0x65,
+	0xa6, 0xff, 0xe8, 0x6c, 0xfd, 0xc7, 0x39, 0x58, 0xca, 0x16, 0xf3, 0x40, 0x7a, 0x96, 0xf3, 0x40,
+	0xaa, 0x8f, 0x0b, 0xf1, 0xeb, 0xf4, 0x68, 0xaa, 0x65, 0xa1, 0x5b, 0x10, 0xa5, 0x4b, 0x6a, 0x18,
+	0x2f, 0xd8, 0xd1, 0x0d, 0xa8, 0x58, 0xd2, 0xd8, 0x3f, 0x9f, 0x35, 0x9d, 0x95, 0x87, 0xfd, 0xac,
+	0x1b, 0x9d, 0x32, 0xe2, 0x97, 0x45, 0xb5, 0x0b, 0x30, 0x43, 0xde, 0xf7, 0xfa, 0x2e, 0x14, 0xf9,
+	0x72, 0xd0, 0x8b, 0x50, 0xa6, 0x51, 0x4a, 0xcb, 0x70, 0x56, 0xeb, 0xd1, 0x37, 0x15, 0x01, 0x26,
+	0x3e, 0xbc, 0x97, 0x04, 0x0c, 0xbd, 0x02, 0x40, 0xaa, 0x35, 0x1e, 0x9f, 0x39, 0x1a, 0x9f, 0xb4,
+	0xdc, 0x77, 0x1d, 0x33, 0x15, 0x94, 0xe5, 0x08, 0xa8, 0xff, 0x3c, 0x07, 0x15, 0xf9, 0x43, 0xc3,
+	0x5d, 0x29, 0xff, 0x04, 0x44, 0x2b, 0xd6, 0x35, 0x4c, 0x93, 0xfc, 0xc5, 0x22, 0x21, 0xb7, 0xa6,
+	0x1e, 0x92, 0xf8, 0x7f, 0x55, 0x70, 0xb0, 0xc2, 0x9b, 0x7e, 0xca, 0xb5, 0x12, 0x28, 0x49, 0x6b,
+	0x2d, 0x89, 0x5b, 0xde, 0x81, 0xa3, 0x99, 0xa2, 0xe4, 0x72, 0x79, 0xf6, 0x7e, 0x95, 0xcb, 0xbf,
+	0x9a, 0x85, 0xa3, 0x99, 0x1f, 0x78, 0x1e, 0x7a, 0x14, 0xab, 0x11, 0x94, 0xbf, 0x2f, 0x11, 0xf4,
+	0xa9, 0x96, 0x65, 0x59, 0x36, 0x2c, 0x7f, 0xed, 0x00, 0x5f, 0xbd, 0xee, 0x97, 0x8d, 0x55, 0xb7,
+	0x9c, 0xbd, 0xab, 0x98, 0x28, 0x1c, 0x34, 0x26, 0xd0, 0xf3, 0xac, 0xf3, 0xa1, 0xba, 0x8a, 0x54,
+	0x97, 0xc8, 0x10, 0x09, 0x55, 0x45, 0x0e, 0x22, 0xcd, 0xb0, 0xe0, 0x60, 0xfd, 0x76, 0x29, 0x6e,
+	0x86, 0x39, 0x4d, 0xb2, 0xe5, 0x9e, 0x93, 0xe1, 0xff, 0x5e, 0x1f, 0xfe, 0x87, 0x06, 0xd5, 0xc4,
+	0x17, 0xdf, 0x47, 0xe7, 0x1d, 0xf4, 0x7d, 0x0d, 0xca, 0xd1, 0x65, 0x83, 0x7b, 0x2e, 0x43, 0x57,
+	0xa1, 0x80, 0xd9, 0x07, 0x6f, 0x96, 0xee, 0x8e, 0x24, 0x2e, 0x24, 0x11, 0x1c, 0xbf, 0x82, 0x94,
+	0xf8, 0xc6, 0xdd, 0xe1, 0x8c, 0xfa, 0xef, 0x34, 0x51, 0x60, 0xc6, 0x6b, 0x7a, 0xa8, 0xa6, 0x88,
+	0xf7, 0x94, 0xbf, 0xdb, 0x3d, 0xfd, 0xba, 0x04, 0xb3, 0x94, 0x8e, 0x74, 0x5d, 0x01, 0xf6, 0x46,
+	0x96, 0x6d, 0x0c, 0xe9, 0x76, 0x4a, 0x2c, 0x6e, 0x05, 0x4c, 0x8e, 0x5b, 0x01, 0x43, 0x5b, 0x50,
+	0x8d, 0x27, 0x45, 0x54, 0x4c, 0xf6, 0x3d, 0xa7, 0x77, 0x55, 0x22, 0x36, 0x7f, 0x4e, 0x70, 0xaa,
+	0x1f, 0x82, 0x13, 0x48, 0x64, 0xc2, 0x42, 0xdf, 0xb1, 0x03, 0xc3, 0xb2, 0xb1, 0xc7, 0x14, 0xe5,
+	0xb3, 0xee, 0x79, 0x9c, 0x55, 0x68, 0x58, 0xc3, 0xad, 0xf2, 0xa9, 0xf7, 0x3c, 0x54, 0x1c, 0xfa,
+	0x08, 0xe6, 0x45, 0x11, 0xce, 0x94, 0xcc, 0x64, 0xdd, 0xf3, 0x58, 0x93, 0x49, 0x98, 0x4b, 0x2b,
+	0x5c, 0xea, 0x3d, 0x0f, 0x05, 0x85, 0x86, 0x50, 0x73, 0x1d, 0xf3, 0xba, 0xcd, 0x7b, 0x7d, 0xa3,
+	0x37, 0xc4, 0x7c, 0x3c, 0xb9, 0x92, 0x2a, 0x79, 0x14, 0x2a, 0x96, 0x8a, 0x93, 0xbc, 0xea, 0xcd,
+	0xa9, 0x24, 0x16, 0x7d, 0x08, 0x73, 0x43, 0xd2, 0x0b, 0xad, 0xed, 0xb9, 0x96, 0x87, 0xcd, 0xec,
+	0x7b, 0x4e, 0x97, 0x24, 0x0a, 0x96, 0x08, 0x65, 0x1e, 0xf5, 0xae, 0x87, 0x8c, 0x21, 0xd6, 0x1f,
+	0x19, 0x7b, 0x9d, 0xd0, 0xf6, 0xd7, 0xf6, 0xf8, 0x9d, 0x95, 0x62, 0x96, 0xf5, 0xd7, 0x55, 0x22,
+	0x66, 0xfd, 0x04, 0xa7, 0x6a, 0xfd, 0x04, 0x12, 0x5d, 0xa2, 0x79, 0x9e, 0x99, 0x84, 0xdd, 0x77,
+	0x5a, 0x4a, 0x9d, 0x16, 0xb3, 0x06, 0x9b, 0x14, 0xf0, 0x27, 0x45, 0x68, 0x24, 0x81, 0xdb, 0x80,
+	0x6e, 0xbb, 0x83, 0x83, 0xd0, 0xb3, 0xb1, 0xc9, 0xaf, 0x3a, 0xa5, 0x6d, 0xa0, 0x50, 0x45, 0x36,
+	0x50, 0xa0, 0x29, 0x1b, 0x28, 0x58, 0xe2, 0x53, 0xae, 0x63, 0x5e, 0x63, 0x21, 0x13, 0x44, 0x17,
+	0xa0, 0x1e, 0x4f, 0xa9, 0x8a, 0x49, 0x98, 0x4f, 0x29, 0x5c, 0xaa, 0x4f, 0x29, 0x28, 0x7e, 0xe7,
+	0x46, 0xbe, 0xa1, 0xc1, 0x4e, 0xaa, 0x32, 0xe5, 0xce, 0x4d, 0x8a, 0x32, 0xba, 0x73, 0x93, 0xc2,
+	0xa4, 0xee, 0xdc, 0xa4, 0x79, 0x4b, 0x50, 0xf0, 0xb0, 0xe1, 0x3b, 0xb6, 0xfe, 0x99, 0x06, 0xd5,
+	0x44, 0xa4, 0xa3, 0x37, 0x21, 0xfa, 0xb6, 0x7f, 0x6d, 0xdf, 0x15, 0x85, 0xaa, 0x72, 0x17, 0x80,
+	0xc0, 0xb3, 0xee, 0x02, 0x10, 0x38, 0xba, 0x04, 0x10, 0xbd, 0x15, 0x6e, 0x97, 0x26, 0x69, 0x95,
+	0x14, 0x53, 0xca, 0x55, 0x52, 0x0c, 0xd5, 0xbf, 0xcc, 0x43, 0x49, 0xb8, 0xca, 0x03, 0x69, 0x64,
+	0x5a, 0x50, 0x1c, 0x61, 0x9f, 0xde, 0x09, 0xc8, 0xc5, 0xf5, 0x08, 0x07, 0xc9, 0xf5, 0x08, 0x07,
+	0xa9, 0xe5, 0x52, 0xfe, 0xae, 0xca, 0xa5, 0x99, 0x03, 0x97, 0x4b, 0x98, 0x7e, 0x0f, 0x94, 0x12,
+	0x9e, 0x18, 0xe6, 0xdf, 0x3e, 0x8b, 0x8a, 0xaf, 0x85, 0x32, 0x63, 0xe2, 0x6b, 0xa1, 0x8c, 0x42,
+	0x3b, 0x70, 0x58, 0xfa, 0xe0, 0xc0, 0x9c, 0x83, 0xa6, 0x9e, 0x85, 0xe9, 0x1f, 0x5f, 0x3b, 0x94,
+	0x8a, 0x05, 0xd8, 0x4e, 0x02, 0x2a, 0xd7, 0x9b, 0x49, 0x9c, 0xfe, 0xe7, 0x1c, 0x2c, 0xa8, 0xeb,
+	0x7d, 0x20, 0x86, 0x7d, 0x11, 0xca, 0x78, 0xcf, 0x0a, 0xba, 0x7d, 0xc7, 0xc4, 0xbc, 0x69, 0xa3,
+	0x76, 0x22, 0xc0, 0xb3, 0x8e, 0xa9, 0xd8, 0x49, 0xc0, 0x64, 0x6f, 0xc8, 0x1f, 0xc8, 0x1b, 0x9e,
+	0x15, 0xb1, 0x24, 0x4f, 0x7d, 0xbc, 0xe4, 0x21, 0x70, 0x9a, 0xec, 0x73, 0x2e, 0x3f, 0xa0, 0x73,
+	0xbe, 0x95, 0x83, 0x5a, 0x32, 0x1f, 0x7e, 0x33, 0x42, 0x48, 0x8d, 0x86, 0xfc, 0x81, 0xa3, 0xe1,
+	0x2d, 0x98, 0x27, 0xd5, 0x9b, 0x11, 0x04, 0xfc, 0xb6, 0xdc, 0x0c, 0xad, 0x7a, 0x58, 0x6e, 0x0a,
+	0xed, 0x55, 0x01, 0x57, 0x72, 0x93, 0x04, 0xd7, 0xbf, 0x9d, 0x83, 0x79, 0x25, 0x6f, 0x3f, 0x7a,
+	0x29, 0x45, 0xaf, 0xc2, 0xbc, 0x52, 0x0e, 0xe9, 0xdf, 0x61, 0x7e, 0xa2, 0xd6, 0x21, 0x8f, 0xde,
+	0xb9, 0x2c, 0xc0, 0x9c, 0x5c, 0x57, 0xe9, 0x6d, 0xa8, 0x26, 0xca, 0x20, 0x79, 0x03, 0xda, 0x41,
+	0x36, 0xa0, 0x2f, 0xc1, 0x62, 0xd6, 0xdb, 0x5b, 0xff, 0x5c, 0xa3, 0x88, 0xf4, 0x75, 0xd8, 0xf3,
+	0x00, 0x36, 0xbe, 0xd9, 0xbd, 0x63, 0xdf, 0xc4, 0x8e, 0x01, 0xdf, 0xbc, 0x98, 0x68, 0x33, 0x4a,
+	0x02, 0x46, 0x24, 0x39, 0x43, 0xb3, 0x7b, 0xc7, 0x6e, 0x85, 0x4a, 0x72, 0x86, 0x66, 0x4a, 0x92,
+	0x80, 0xe9, 0xdf, 0xcb, 0x8b, 0x96, 0x36, 0xbe, 0x4f, 0xfa, 0x01, 0xd4, 0x5c, 0xf1, 0x70, 0xe7,
+	0xd5, 0xd2, 0xa2, 0x3e, 0xa2, 0x4f, 0x6a, 0x5a, 0x50, 0x31, 0xaa, 0x6c, 0xde, 0xad, 0xe5, 0x0e,
+	0x28, 0xbb, 0x93, 0x68, 0xdb, 0x16, 0x54, 0x0c, 0xfa, 0x7f, 0x38, 0x2c, 0xae, 0xdb, 0xec, 0x62,
+	0xb1, 0xf0, 0xfc, 0x54, 0xe1, 0xec, 0xfa, 0x6b, 0xc4, 0x90, 0x5c, 0x79, 0x35, 0x81, 0x4a, 0x88,
+	0xe7, 0x6b, 0x9f, 0x39, 0xa8, 0xf8, 0xe4, 0xe2, 0xab, 0x09, 0x14, 0xe9, 0xaf, 0xab, 0x89, 0x1b,
+	0xba, 0xe8, 0x1c, 0x94, 0xe8, 0x0f, 0x78, 0x6e, 0x6f, 0x01, 0xea, 0xa8, 0x94, 0x4e, 0xd1, 0x50,
+	0xe4, 0x20, 0xf4, 0x32, 0x94, 0xa3, 0x8b, 0xbc, 0xfc, 0x0b, 0x1e, 0x8b, 0x19, 0x01, 0x54, 0x62,
+	0x46, 0x00, 0xf5, 0x9f, 0x68, 0x70, 0x6c, 0xea, 0xed, 0xdd, 0x87, 0xdd, 0x6c, 0x3f, 0xf3, 0x3c,
+	0x94, 0xc4, 0x27, 0x39, 0x04, 0x50, 0x78, 0xef, 0xfa, 0xda, 0xf5, 0xb5, 0x73, 0xb5, 0x43, 0xa8,
+	0x02, 0xc5, 0xab, 0x6b, 0x97, 0xcf, 0x5d, 0xb8, 0xfc, 0x4e, 0x4d, 0x23, 0x0f, 0x9d, 0xeb, 0x97,
+	0x2f, 0x93, 0x87, 0xdc, 0x33, 0x97, 0xe4, 0x1b, 0x3f, 0xec, 0x35, 0x8a, 0xe6, 0xa0, 0xb4, 0xea,
+	0xba, 0x34, 0x6e, 0x19, 0xef, 0xda, 0xae, 0x45, 0x62, 0xb5, 0xa6, 0xa1, 0x22, 0xe4, 0xaf, 0x5c,
+	0x59, 0xaf, 0xe5, 0xd0, 0x22, 0xd4, 0xce, 0x61, 0xc3, 0x1c, 0x5a, 0x36, 0x16, 0xc9, 0xa2, 0x96,
+	0x6f, 0x6f, 0x7f, 0xf1, 0xd5, 0x8a, 0xf6, 0xe5, 0x57, 0x2b, 0xda, 0x9f, 0xbe, 0x5a, 0xd1, 0x6e,
+	0x7d, 0xbd, 0x72, 0xe8, 0xcb, 0xaf, 0x57, 0x0e, 0xfd, 0xe1, 0xeb, 0x95, 0x43, 0x1f, 0x3c, 0x2f,
+	0xfd, 0x58, 0x8d, 0xed, 0xc9, 0xf5, 0x1c, 0x92, 0x27, 0xf9, 0x53, 0x2b, 0xf9, 0xf3, 0xbc, 0xcf,
+	0x73, 0x27, 0x56, 0xe9, 0xe3, 0x55, 0x46, 0xd7, 0xbc, 0xe0, 0x34, 0x19, 0x80, 0xfe, 0xc2, 0xca,
+	0xef, 0x15, 0xe8, 0x2f, 0xa9, 0x5e, 0xfc, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf5, 0x1d, 0x2b,
+	0xeb, 0xd9, 0x37, 0x00, 0x00,
 }
 
 func (m *EventSequence) Marshal() (dAtA []byte, err error) {
@@ -4054,6 +4157,29 @@ func (m *EventSequence_Event_JobRunPreemptionRequested) MarshalToSizedBuffer(dAt
 	}
 	return len(dAtA) - i, nil
 }
+func (m *EventSequence_Event_JobRequeued) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *EventSequence_Event_JobRequeued) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	if m.JobRequeued != nil {
+		{
+			size, err := m.JobRequeued.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintEvents(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x1
+		i--
+		dAtA[i] = 0xb2
+	}
+	return len(dAtA) - i, nil
+}
 func (m *ResourceUtilisation) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
@@ -4699,6 +4825,58 @@ func (m *ReprioritiseJob) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
+func (m *JobRequeued) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *JobRequeued) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *JobRequeued) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.UpdateSequenceNumber != 0 {
+		i = encodeVarintEvents(dAtA, i, uint64(m.UpdateSequenceNumber))
+		i--
+		dAtA[i] = 0x18
+	}
+	if m.SchedulingInfo != nil {
+		{
+			size, err := m.SchedulingInfo.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintEvents(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x12
+	}
+	if m.JobId != nil {
+		{
+			size, err := m.JobId.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintEvents(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
 func (m *ReprioritiseJobSet) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
@@ -4823,20 +5001,20 @@ func (m *JobSetFilter) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	var l int
 	_ = l
 	if len(m.States) > 0 {
-		dAtA42 := make([]byte, len(m.States)*10)
-		var j41 int
+		dAtA45 := make([]byte, len(m.States)*10)
+		var j44 int
 		for _, num := range m.States {
 			for num >= 1<<7 {
-				dAtA42[j41] = uint8(uint64(num)&0x7f | 0x80)
+				dAtA45[j44] = uint8(uint64(num)&0x7f | 0x80)
 				num >>= 7
-				j41++
+				j44++
 			}
-			dAtA42[j41] = uint8(num)
-			j41++
+			dAtA45[j44] = uint8(num)
+			j44++
 		}
-		i -= j41
-		copy(dAtA[i:], dAtA42[:j41])
-		i = encodeVarintEvents(dAtA, i, uint64(j41))
+		i -= j44
+		copy(dAtA[i:], dAtA45[:j44])
+		i = encodeVarintEvents(dAtA, i, uint64(j44))
 		i--
 		dAtA[i] = 0xa
 	}
@@ -4864,20 +5042,20 @@ func (m *CancelJobSet) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	var l int
 	_ = l
 	if len(m.States) > 0 {
-		dAtA44 := make([]byte, len(m.States)*10)
-		var j43 int
+		dAtA47 := make([]byte, len(m.States)*10)
+		var j46 int
 		for _, num := range m.States {
 			for num >= 1<<7 {
-				dAtA44[j43] = uint8(uint64(num)&0x7f | 0x80)
+				dAtA47[j46] = uint8(uint64(num)&0x7f | 0x80)
 				num >>= 7
-				j43++
+				j46++
 			}
-			dAtA44[j43] = uint8(num)
-			j43++
+			dAtA47[j46] = uint8(num)
+			j46++
 		}
-		i -= j43
-		copy(dAtA[i:], dAtA44[:j43])
-		i = encodeVarintEvents(dAtA, i, uint64(j43))
+		i -= j46
+		copy(dAtA[i:], dAtA47[:j46])
+		i = encodeVarintEvents(dAtA, i, uint64(j46))
 		i--
 		dAtA[i] = 0xa
 	}
@@ -4988,6 +5166,11 @@ func (m *JobRunLeased) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	_ = i
 	var l int
 	_ = l
+	if m.UpdateSequenceNumber != 0 {
+		i = encodeVarintEvents(dAtA, i, uint64(m.UpdateSequenceNumber))
+		i--
+		dAtA[i] = 0x28
+	}
 	if len(m.NodeId) > 0 {
 		i -= len(m.NodeId)
 		copy(dAtA[i:], m.NodeId)
@@ -6249,6 +6432,13 @@ func (m *MaxRunsExceeded) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	_ = i
 	var l int
 	_ = l
+	if len(m.Message) > 0 {
+		i -= len(m.Message)
+		copy(dAtA[i:], m.Message)
+		i = encodeVarintEvents(dAtA, i, uint64(len(m.Message)))
+		i--
+		dAtA[i] = 0xa
+	}
 	return len(dAtA) - i, nil
 }
 
@@ -6780,6 +6970,18 @@ func (m *EventSequence_Event_JobRunPreemptionRequested) Size() (n int) {
 	}
 	return n
 }
+func (m *EventSequence_Event_JobRequeued) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.JobRequeued != nil {
+		l = m.JobRequeued.Size()
+		n += 2 + l + sovEvents(uint64(l))
+	}
+	return n
+}
 func (m *ResourceUtilisation) Size() (n int) {
 	if m == nil {
 		return 0
@@ -7055,6 +7257,26 @@ func (m *ReprioritiseJob) Size() (n int) {
 	return n
 }
 
+func (m *JobRequeued) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.JobId != nil {
+		l = m.JobId.Size()
+		n += 1 + l + sovEvents(uint64(l))
+	}
+	if m.SchedulingInfo != nil {
+		l = m.SchedulingInfo.Size()
+		n += 1 + l + sovEvents(uint64(l))
+	}
+	if m.UpdateSequenceNumber != 0 {
+		n += 1 + sovEvents(uint64(m.UpdateSequenceNumber))
+	}
+	return n
+}
+
 func (m *ReprioritiseJobSet) Size() (n int) {
 	if m == nil {
 		return 0
@@ -7182,6 +7404,9 @@ func (m *JobRunLeased) Size() (n int) {
 	if l > 0 {
 		n += 1 + l + sovEvents(uint64(l))
 	}
+	if m.UpdateSequenceNumber != 0 {
+		n += 1 + sovEvents(uint64(m.UpdateSequenceNumber))
+	}
 	return n
 }
 
@@ -7724,6 +7949,10 @@ func (m *MaxRunsExceeded) Size() (n int) {
 	}
 	var l int
 	_ = l
+	l = len(m.Message)
+	if l > 0 {
+		n += 1 + l + sovEvents(uint64(l))
+	}
 	return n
 }
 
@@ -8794,6 +9023,41 @@ func (m *EventSequence_Event) Unmarshal(dAtA []byte) error {
 			}
 			m.Event = &EventSequence_Event_JobRunPreemptionRequested{v}
 			iNdEx = postIndex
+		case 22:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field JobRequeued", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEvents
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthEvents
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthEvents
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			v := &JobRequeued{}
+			if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			m.Event = &EventSequence_Event_JobRequeued{v}
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipEvents(dAtA[iNdEx:])
@@ -10677,6 +10941,147 @@ func (m *ReprioritiseJob) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *JobRequeued) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowEvents
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: JobRequeued: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: JobRequeued: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field JobId", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEvents
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthEvents
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthEvents
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.JobId == nil {
+				m.JobId = &Uuid{}
+			}
+			if err := m.JobId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field SchedulingInfo", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEvents
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthEvents
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthEvents
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.SchedulingInfo == nil {
+				m.SchedulingInfo = &schedulerobjects.JobSchedulingInfo{}
+			}
+			if err := m.SchedulingInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 3:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field UpdateSequenceNumber", wireType)
+			}
+			m.UpdateSequenceNumber = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEvents
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.UpdateSequenceNumber |= int32(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		default:
+			iNdEx = preIndex
+			skippy, err := skipEvents(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthEvents
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func (m *ReprioritiseJobSet) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
@@ -11546,6 +11951,25 @@ func (m *JobRunLeased) Unmarshal(dAtA []byte) error {
 			}
 			m.NodeId = string(dAtA[iNdEx:postIndex])
 			iNdEx = postIndex
+		case 5:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field UpdateSequenceNumber", wireType)
+			}
+			m.UpdateSequenceNumber = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEvents
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.UpdateSequenceNumber |= int32(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
 		default:
 			iNdEx = preIndex
 			skippy, err := skipEvents(dAtA[iNdEx:])
@@ -14721,6 +15145,38 @@ func (m *MaxRunsExceeded) Unmarshal(dAtA []byte) error {
 			return fmt.Errorf("proto: MaxRunsExceeded: illegal tag %d (wire type %d)", fieldNum, wire)
 		}
 		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEvents
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthEvents
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthEvents
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Message = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipEvents(dAtA[iNdEx:])
diff --git a/pkg/armadaevents/events.proto b/pkg/armadaevents/events.proto
index 74d13208..0115d27c 100644
--- a/pkg/armadaevents/events.proto
+++ b/pkg/armadaevents/events.proto
@@ -8,7 +8,7 @@ import "k8s.io/api/networking/v1/generated.proto";
 import "google/protobuf/timestamp.proto";
 import "k8s.io/apimachinery/pkg/api/resource/generated.proto";
 import "github.com/gogo/protobuf/gogoproto/gogo.proto";
-
+import "internal/scheduler/schedulerobjects/schedulerobjects.proto";
 // Armada state transition model
 //
 // The central concepts are:
@@ -88,6 +88,7 @@ message EventSequence {
             JobRunPreempted jobRunPreempted = 19;
             PartitionMarker partitionMarker = 20;
             JobRunPreemptionRequested jobRunPreemptionRequested = 21;
+            JobRequeued jobRequeued = 22;
         }
     }
     // The system is namespaced by queue, and all events are associated with a job set.
@@ -215,6 +216,13 @@ message ReprioritiseJob {
     uint32 priority = 2;
 }
 
+message JobRequeued {
+    Uuid job_id = 1;
+    schedulerobjects.JobSchedulingInfo scheduling_info = 2;
+    // Used by the scheduler to maintain a consistent state
+    int32 update_sequence_number = 3;
+}
+
 // Set the priority of all jobs part of a job set.
 // This sets the priority of all jobs in the job set currently in the queued state.
 message ReprioritiseJobSet {
@@ -275,6 +283,8 @@ message JobRunLeased {
     // Each cluster is represented by an executor.
     string executor_id = 3;
     string node_id = 4;
+    // Used by the scheduler to maintain a consistent state
+    int32 update_sequence_number = 5;
 }
 
 // Indicates that a job has been assigned to nodes by Kubernetes.
@@ -467,6 +477,7 @@ message LeaseExpired {
 }
 
 message MaxRunsExceeded {
+    string message = 1;
 }
 
 message JobRunPreemptedError{
diff --git a/pkg/armadaevents/events_util.go b/pkg/armadaevents/events_util.go
index 4fa3a03f..625035b3 100644
--- a/pkg/armadaevents/events_util.go
+++ b/pkg/armadaevents/events_util.go
@@ -105,6 +105,12 @@ func (ev *EventSequence_Event) UnmarshalJSON(data []byte) error {
 				return err
 			}
 			ev.Event = &jobErrors
+		case "jobRequeued":
+			var jobRequeued EventSequence_Event_JobRequeued
+			if err = json.Unmarshal(rawEvent.EventBytes, &jobRequeued); err != nil {
+				return err
+			}
+			ev.Event = &jobRequeued
 		case "jobRunLeased":
 			var jobRunLeased EventSequence_Event_JobRunLeased
 			if err = json.Unmarshal(rawEvent.EventBytes, &jobRunLeased); err != nil {
diff --git a/pkg/armadaevents/jobid.go b/pkg/armadaevents/jobid.go
index 65b9a8a0..4bd2d33b 100644
--- a/pkg/armadaevents/jobid.go
+++ b/pkg/armadaevents/jobid.go
@@ -38,6 +38,8 @@ func JobIdFromEvent(event *EventSequence_Event) (*Uuid, error) {
 		return e.StandaloneIngressInfo.JobId, nil
 	case *EventSequence_Event_JobRunPreempted:
 		return e.JobRunPreempted.PreemptedJobId, nil
+	case *EventSequence_Event_JobRequeued:
+		return e.JobRequeued.JobId, nil
 	default:
 		err := errors.WithStack(&armadaerrors.ErrInvalidArgument{
 			Name:    "event.Event",


Test patch:
diff --git a/internal/armada/repository/apimessages/conversions_test.go b/internal/armada/repository/apimessages/conversions_test.go
index a62aff9b..c2988494 100644
--- a/internal/armada/repository/apimessages/conversions_test.go
+++ b/internal/armada/repository/apimessages/conversions_test.go
@@ -533,6 +533,25 @@ func TestConvertJobError(t *testing.T) {
 		},
 	}
 
+	maxRunsExceeded := &armadaevents.EventSequence_Event{
+		Created: &baseTime,
+		Event: &armadaevents.EventSequence_Event_JobErrors{
+			JobErrors: &armadaevents.JobErrors{
+				JobId: jobIdProto,
+				Errors: []*armadaevents.Error{
+					{
+						Terminal: true,
+						Reason: &armadaevents.Error_MaxRunsExceeded{
+							MaxRunsExceeded: &armadaevents.MaxRunsExceeded{
+								Message: "Max runs",
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
 	expected := []*api.EventMessage{
 		{
 			Events: &api.EventMessage_Failed{
@@ -561,9 +580,21 @@ func TestConvertJobError(t *testing.T) {
 				},
 			},
 		},
+		{
+			Events: &api.EventMessage_Failed{
+				Failed: &api.JobFailedEvent{
+					JobId:    jobIdString,
+					Reason:   "Max runs",
+					JobSetId: jobSetName,
+					Queue:    queue,
+					Created:  baseTime,
+					Cause:    api.Cause_Error,
+				},
+			},
+		},
 	}
 
-	apiEvents, err := FromEventSequence(toEventSeq(errored))
+	apiEvents, err := FromEventSequence(toEventSeq(errored, maxRunsExceeded))
 	assert.NoError(t, err)
 	assert.Equal(t, expected, apiEvents)
 }
diff --git a/internal/common/ingest/testfixtures/event.go b/internal/common/ingest/testfixtures/event.go
index 150bac8b..05f85a40 100644
--- a/internal/common/ingest/testfixtures/event.go
+++ b/internal/common/ingest/testfixtures/event.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/eventutil"
+	"github.com/armadaproject/armada/internal/scheduler/schedulerobjects"
 	"github.com/armadaproject/armada/pkg/armadaevents"
 )
 
@@ -32,7 +33,24 @@ var (
 	PriorityClasses             = map[string]configuration.PriorityClass{PriorityClassName: {Priority: PriorityClassValue}}
 	Groups                      = []string{"group1", "group2"}
 	NodeSelector                = map[string]string{"foo": "bar"}
-	Tolerations                 = []v1.Toleration{{
+	Affinity                    = &v1.Affinity{
+		NodeAffinity: &v1.NodeAffinity{
+			RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
+				NodeSelectorTerms: []v1.NodeSelectorTerm{
+					{
+						MatchExpressions: []v1.NodeSelectorRequirement{
+							{
+								Key:      "noode-name",
+								Operator: v1.NodeSelectorOpNotIn,
+								Values:   []string{"node-1"},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	Tolerations = []v1.Toleration{{
 		Key:      "fish",
 		Operator: "exists",
 	}}
@@ -180,10 +198,11 @@ var Leased = &armadaevents.EventSequence_Event{
 	Created: &BaseTime,
 	Event: &armadaevents.EventSequence_Event_JobRunLeased{
 		JobRunLeased: &armadaevents.JobRunLeased{
-			RunId:      RunIdProto,
-			JobId:      JobIdProto,
-			ExecutorId: ExecutorId,
-			NodeId:     NodeName,
+			RunId:                RunIdProto,
+			JobId:                JobIdProto,
+			ExecutorId:           ExecutorId,
+			NodeId:               NodeName,
+			UpdateSequenceNumber: 1,
 		},
 	},
 }
@@ -229,7 +248,8 @@ var LeaseReturned = &armadaevents.EventSequence_Event{
 					Terminal: true,
 					Reason: &armadaevents.Error_PodLeaseReturned{
 						PodLeaseReturned: &armadaevents.PodLeaseReturned{
-							Message: LeaseReturnedMsg,
+							Message:      LeaseReturnedMsg,
+							RunAttempted: true,
 						},
 					},
 				},
@@ -263,6 +283,46 @@ var JobCancelled = &armadaevents.EventSequence_Event{
 	},
 }
 
+var JobRequeued = &armadaevents.EventSequence_Event{
+	Created: &BaseTime,
+	Event: &armadaevents.EventSequence_Event_JobRequeued{
+		JobRequeued: &armadaevents.JobRequeued{
+			JobId: JobIdProto,
+			SchedulingInfo: &schedulerobjects.JobSchedulingInfo{
+				Lifetime:        0,
+				AtMostOnce:      true,
+				Preemptible:     true,
+				ConcurrencySafe: true,
+				Version:         0,
+				ObjectRequirements: []*schedulerobjects.ObjectRequirements{
+					{
+						Requirements: &schedulerobjects.ObjectRequirements_PodRequirements{
+							PodRequirements: &schedulerobjects.PodRequirements{
+								NodeSelector:     NodeSelector,
+								Tolerations:      Tolerations,
+								PreemptionPolicy: "PreemptLowerPriority",
+								Priority:         PriorityClassValue,
+								Affinity:         Affinity,
+								ResourceRequirements: v1.ResourceRequirements{
+									Limits: map[v1.ResourceName]resource.Quantity{
+										"memory": resource.MustParse("64Mi"),
+										"cpu":    resource.MustParse("150m"),
+									},
+									Requests: map[v1.ResourceName]resource.Quantity{
+										"memory": resource.MustParse("64Mi"),
+										"cpu":    resource.MustParse("150m"),
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			UpdateSequenceNumber: 2,
+		},
+	},
+}
+
 var PartitionMarker = &armadaevents.EventSequence_Event{
 	Created: &BaseTime,
 	Event: &armadaevents.EventSequence_Event_PartitionMarker{
diff --git a/internal/scheduler/database/job_repository_test.go b/internal/scheduler/database/job_repository_test.go
index 2ccdea00..caecce46 100644
--- a/internal/scheduler/database/job_repository_test.go
+++ b/internal/scheduler/database/job_repository_test.go
@@ -262,6 +262,8 @@ func createTestJobs(numJobs int) ([]Job, []Job) {
 			Queue:                   "test-queue",
 			Submitted:               int64(i),
 			Priority:                int64(i),
+			Queued:                  false,
+			QueuedVersion:           1,
 			CancelRequested:         true,
 			Cancelled:               true,
 			CancelByJobsetRequested: true,
@@ -280,6 +282,8 @@ func createTestJobs(numJobs int) ([]Job, []Job) {
 			Submitted:               job.Submitted,
 			Priority:                job.Priority,
 			CancelRequested:         job.CancelRequested,
+			Queued:                  job.Queued,
+			QueuedVersion:           job.QueuedVersion,
 			CancelByJobsetRequested: true,
 			Cancelled:               job.Cancelled,
 			Succeeded:               job.Succeeded,
diff --git a/internal/scheduler/jobdb/job_run_test.go b/internal/scheduler/jobdb/job_run_test.go
index 5040aaa8..0a0e0669 100644
--- a/internal/scheduler/jobdb/job_run_test.go
+++ b/internal/scheduler/jobdb/job_run_test.go
@@ -18,6 +18,7 @@ var baseJobRun = CreateRun(
 	false,
 	false,
 	false,
+	false,
 )
 
 // Test methods that only have getters
@@ -53,9 +54,15 @@ func TestJobRun_TestCancelled(t *testing.T) {
 }
 
 func TestJobRun_TestReturned(t *testing.T) {
-	cancelledRun := baseJobRun.WithReturned(true)
+	returnedRun := baseJobRun.WithReturned(true)
 	assert.False(t, baseJobRun.Returned())
-	assert.True(t, cancelledRun.Returned())
+	assert.True(t, returnedRun.Returned())
+}
+
+func TestJobRun_TestRunAttempted(t *testing.T) {
+	attemptedRun := baseJobRun.WithAttempted(true)
+	assert.False(t, baseJobRun.RunAttempted())
+	assert.True(t, attemptedRun.RunAttempted())
 }
 
 func TestDeepCopy(t *testing.T) {
@@ -70,6 +77,7 @@ func TestDeepCopy(t *testing.T) {
 		true,
 		true,
 		true,
+		true,
 	)
 	expected := CreateRun(
 		run.id,
@@ -82,6 +90,7 @@ func TestDeepCopy(t *testing.T) {
 		true,
 		true,
 		true,
+		true,
 	)
 	actual := run.DeepCopy()
 	run.node = "new node"
diff --git a/internal/scheduler/jobdb/job_test.go b/internal/scheduler/jobdb/job_test.go
index 50ffb981..349cb0d9 100644
--- a/internal/scheduler/jobdb/job_test.go
+++ b/internal/scheduler/jobdb/job_test.go
@@ -29,6 +29,8 @@ var baseJob = NewJob(
 	"test-queue",
 	2,
 	schedulingInfo,
+	true,
+	0,
 	false,
 	false,
 	false,
@@ -77,6 +79,12 @@ func TestJob_TestQueued(t *testing.T) {
 	assert.Equal(t, false, newJob.Queued())
 }
 
+func TestJob_QueuedVersion(t *testing.T) {
+	newJob := baseJob.WithQueuedVersion(1)
+	assert.Equal(t, int32(0), baseJob.QueuedVersion())
+	assert.Equal(t, int32(1), newJob.QueuedVersion())
+}
+
 func TestJob_TestCancelRequested(t *testing.T) {
 	newJob := baseJob.WithCancelRequested(true)
 	assert.Equal(t, false, baseJob.CancelRequested())
@@ -217,6 +225,40 @@ func TestJob_TestNumReturned(t *testing.T) {
 	assert.Equal(t, uint(2), returned3.NumReturned())
 }
 
+func TestJob_TestNumAttempts(t *testing.T) {
+	attemptedRun := func() *JobRun {
+		return &JobRun{
+			id:           uuid.New(),
+			created:      baseRun.created,
+			returned:     true,
+			runAttempted: true,
+		}
+	}
+
+	nonAttemptedRun := func() *JobRun {
+		return &JobRun{
+			id:           uuid.New(),
+			created:      baseRun.created,
+			returned:     true,
+			runAttempted: false,
+		}
+	}
+	// initial job has no runs
+	assert.Equal(t, uint(0), baseJob.NumAttempts())
+
+	// one returned run
+	returned1 := baseJob.WithUpdatedRun(attemptedRun())
+	assert.Equal(t, uint(1), returned1.NumAttempts())
+
+	// still one returned run
+	returned2 := returned1.WithUpdatedRun(nonAttemptedRun())
+	assert.Equal(t, uint(1), returned2.NumAttempts())
+
+	// two returned runs
+	returned3 := returned2.WithUpdatedRun(attemptedRun())
+	assert.Equal(t, uint(2), returned3.NumAttempts())
+}
+
 func TestJob_TestRunsById(t *testing.T) {
 	runs := make([]*JobRun, 10)
 	job := baseJob
diff --git a/internal/scheduler/kubernetesobjects/affinity/affinity_test.go b/internal/scheduler/kubernetesobjects/affinity/affinity_test.go
new file mode 100644
index 00000000..fe19741b
--- /dev/null
+++ b/internal/scheduler/kubernetesobjects/affinity/affinity_test.go
@@ -0,0 +1,86 @@
+package affinity
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	v1 "k8s.io/api/core/v1"
+
+	"github.com/armadaproject/armada/pkg/api"
+)
+
+func TestAddNodeAntiAffinity_WhenAffinityNil_ReturnsError(t *testing.T) {
+	var affinity *v1.Affinity = nil
+	err := AddNodeAntiAffinity(affinity, "a", "b")
+	assert.Error(t, err)
+}
+
+func TestAddNodeAntiAffinity_WhenNoExistingAffinity_AddsCorrectly(t *testing.T) {
+	affinity := &v1.Affinity{}
+	expected := vanillaAvoidLabelAffinity("a", "b")
+
+	err := AddNodeAntiAffinity(affinity, "a", "b")
+	assert.NoError(t, err)
+	assert.Equal(t, expected, affinity)
+}
+
+func TestAddNodeAntiAffinity_WhenAlreadyThere_DoesNothing(t *testing.T) {
+	affinity := vanillaAvoidLabelAffinity("a", "b")
+	expected := vanillaAvoidLabelAffinity("a", "b")
+
+	err := AddNodeAntiAffinity(affinity, "a", "b")
+	assert.NoError(t, err)
+	assert.Equal(t, expected, affinity)
+}
+
+func TestAddNodeAntiAffinity_WhenSameLabelDifferentValueAlreadyThere_IncludesBothValues(t *testing.T) {
+	affinity := &v1.Affinity{}
+	expected := vanillaAvoidLabelAffinity("a", "b")
+	expected.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions[0].Values = []string{"b", "c"}
+
+	err := AddNodeAntiAffinity(affinity, "a", "b")
+	assert.NoError(t, err)
+	err = AddNodeAntiAffinity(affinity, "a", "c")
+	assert.NoError(t, err)
+	assert.Equal(t, expected, affinity)
+}
+
+func TestAddNodeAntiAffinity_WhenDifferentLabelAlreadyThere_IncludesBothLabels(t *testing.T) {
+	affinity := &v1.Affinity{}
+	expected := vanillaAvoidLabelAffinites([]*api.StringKeyValuePair{{Key: "a", Value: "b"}, {Key: "aa", Value: "bb"}})
+
+	err := AddNodeAntiAffinity(affinity, "a", "b")
+	assert.NoError(t, err)
+	err = AddNodeAntiAffinity(affinity, "aa", "bb")
+	assert.NoError(t, err)
+
+	assert.Equal(t, expected, affinity)
+}
+
+func vanillaAvoidLabelAffinity(key string, val string) *v1.Affinity {
+	return vanillaAvoidLabelAffinites([]*api.StringKeyValuePair{{Key: key, Value: val}})
+}
+
+func vanillaAvoidLabelAffinites(labels []*api.StringKeyValuePair) *v1.Affinity {
+	mexprs := []v1.NodeSelectorRequirement{}
+
+	for _, kv := range labels {
+		mexprs = append(mexprs, v1.NodeSelectorRequirement{
+			Key:      kv.Key,
+			Operator: v1.NodeSelectorOpNotIn,
+			Values:   []string{kv.Value},
+		})
+	}
+
+	return &v1.Affinity{
+		NodeAffinity: &v1.NodeAffinity{
+			RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
+				NodeSelectorTerms: []v1.NodeSelectorTerm{
+					{
+						MatchExpressions: mexprs,
+					},
+				},
+			},
+		},
+	}
+}
diff --git a/internal/scheduler/scheduler_test.go b/internal/scheduler/scheduler_test.go
index dc8f2ee1..89481db4 100644
--- a/internal/scheduler/scheduler_test.go
+++ b/internal/scheduler/scheduler_test.go
@@ -11,6 +11,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/util/clock"
 
 	protoutil "github.com/armadaproject/armada/internal/common/proto"
@@ -18,16 +19,47 @@ import (
 	"github.com/armadaproject/armada/internal/common/util"
 	"github.com/armadaproject/armada/internal/scheduler/database"
 	"github.com/armadaproject/armada/internal/scheduler/jobdb"
+	"github.com/armadaproject/armada/internal/scheduler/kubernetesobjects/affinity"
 	"github.com/armadaproject/armada/internal/scheduler/schedulerobjects"
+	"github.com/armadaproject/armada/pkg/api"
 	"github.com/armadaproject/armada/pkg/armadaevents"
 )
 
 // Data to be used in tests
-const maxLeaseReturns = 1
+const (
+	maxNumberOfAttempts = 2
+	nodeIdLabel         = "kubernetes.io/hostname"
+)
 
 var (
-	schedulingInfo      = &schedulerobjects.JobSchedulingInfo{AtMostOnce: true}
-	schedulingInfoBytes = protoutil.MustMarshall(schedulingInfo)
+	schedulingInfo = &schedulerobjects.JobSchedulingInfo{
+		AtMostOnce: true,
+		ObjectRequirements: []*schedulerobjects.ObjectRequirements{
+			{
+				Requirements: &schedulerobjects.ObjectRequirements_PodRequirements{
+					PodRequirements: &schedulerobjects.PodRequirements{
+						Priority: int32(10),
+					},
+				},
+			},
+		},
+		Version: 1,
+	}
+	schedulingInfoBytes   = protoutil.MustMarshall(schedulingInfo)
+	updatedSchedulingInfo = &schedulerobjects.JobSchedulingInfo{
+		AtMostOnce: true,
+		ObjectRequirements: []*schedulerobjects.ObjectRequirements{
+			{
+				Requirements: &schedulerobjects.ObjectRequirements_PodRequirements{
+					PodRequirements: &schedulerobjects.PodRequirements{
+						Priority: int32(10),
+					},
+				},
+			},
+		},
+		Version: 2,
+	}
+	updatedSchedulingInfoBytes = protoutil.MustMarshall(updatedSchedulingInfo)
 )
 
 var queuedJob = jobdb.NewJob(
@@ -36,6 +68,8 @@ var queuedJob = jobdb.NewJob(
 	"testQueue",
 	uint32(10),
 	schedulingInfo,
+	true,
+	1,
 	false,
 	false,
 	false,
@@ -48,97 +82,211 @@ var leasedJob = jobdb.NewJob(
 	uint32(10),
 	schedulingInfo,
 	false,
+	2,
+	false,
 	false,
 	false,
 	1).WithQueued(false).WithNewRun("testExecutor", "test-node")
 
+var (
+	requeuedJobId = util.NewULID()
+	requeuedJob   = jobdb.NewJob(
+		requeuedJobId,
+		"testJobset",
+		"testQueue",
+		uint32(10),
+		schedulingInfo,
+		true,
+		2,
+		false,
+		false,
+		false,
+		1).WithUpdatedRun(
+		jobdb.CreateRun(uuid.New(), requeuedJobId, time.Now().Unix(), "testExecutor", "test-node", false, false, true, false, true, true),
+	)
+)
+
 // Test a single scheduler cycle
 func TestScheduler_TestCycle(t *testing.T) {
 	tests := map[string]struct {
-		initialJobs              []*jobdb.Job      // jobs in the jobdb at the start of the cycle
-		jobUpdates               []database.Job    // job updates from the database
-		runUpdates               []database.Run    // run updates from the database
-		staleExecutor            bool              // if true then the executorRepository will report the executor as stale
-		fetchError               bool              // if true then the jobRepository will throw an error
-		scheduleError            bool              // if true then the schedulingalgo will throw an error
-		publishError             bool              // if true the publisher will throw an error
-		expectedJobRunLeased     []string          // ids of jobs we expect to have produced leased messages
-		expectedJobRunErrors     []string          // ids of jobs we expect to have produced jobRunErrors messages
-		expectedJobErrors        []string          // ids of jobs we expect to have produced jobErrors messages
-		expectedJobRunPreempted  []string          // ids of jobs we expect to have produced jobRunPreempted messages
-		expectedJobCancelled     []string          // ids of jobs we expect to have  produced cancelled messages
-		expectedJobReprioritised []string          // ids of jobs we expect to have  produced reprioritised messages
-		expectedJobSucceeded     []string          // ids of jobs we expect to have  produced succeeeded messages
-		expectedLeased           []string          // ids of jobs we expected to be leased in jobdb at the end of the cycle
-		expectedQueued           []string          // ids of jobs we expected to be queued in jobdb at the end of the cycle
-		expectedTerminal         []string          // ids of jobs we expected to be terminal in jobdb at the end of the cycle
-		expectedJobPriority      map[string]uint32 // expected priority of jobs at the end of the cycle
+		initialJobs                      []*jobdb.Job      // jobs in the jobdb at the start of the cycle
+		jobUpdates                       []database.Job    // job updates from the database
+		runUpdates                       []database.Run    // run updates from the database
+		staleExecutor                    bool              // if true then the executorRepository will report the executor as stale
+		fetchError                       bool              // if true then the jobRepository will throw an error
+		scheduleError                    bool              // if true then the schedulingalgo will throw an error
+		publishError                     bool              // if true the publisher will throw an error
+		submitCheckerFailure             bool              // if true the submit checker will say the job is unschedulable
+		expectedJobRunLeased             []string          // ids of jobs we expect to have produced leased messages
+		expectedJobRunErrors             []string          // ids of jobs we expect to have produced jobRunErrors messages
+		expectedJobErrors                []string          // ids of jobs we expect to have produced jobErrors messages
+		expectedJobRunPreempted          []string          // ids of jobs we expect to have produced jobRunPreempted messages
+		expectedJobCancelled             []string          // ids of jobs we expect to have  produced cancelled messages
+		expectedJobReprioritised         []string          // ids of jobs we expect to have  produced reprioritised messages
+		expectedQueued                   []string          // ids of jobs we expect to have  produced requeued messages
+		expectedJobSucceeded             []string          // ids of jobs we expect to have  produced succeeeded messages
+		expectedLeased                   []string          // ids of jobs we expected to be leased in jobdb at the end of the cycle
+		expectedRequeued                 []string          // ids of jobs we expected to be requeued in jobdb at the end of the cycle
+		expectedTerminal                 []string          // ids of jobs we expected to be terminal in jobdb at the end of the cycle
+		expectedJobPriority              map[string]uint32 // expected priority of jobs at the end of the cycle
+		expectedNodeAntiAffinities       []string          // list of nodes there is expected to be anti affinities for on job scheduling info
+		expectedJobSchedulingInfoVersion int               // expected scheduling info version of jobs at the end of the cycle
+		expectedQueuedVersion            int32             // expected queued version of jobs atthe end of the cycle
 	}{
 		"Lease a single job already in the db": {
-			initialJobs:          []*jobdb.Job{queuedJob},
-			expectedJobRunLeased: []string{queuedJob.Id()},
-			expectedLeased:       []string{queuedJob.Id()},
+			initialJobs:           []*jobdb.Job{queuedJob},
+			expectedJobRunLeased:  []string{queuedJob.Id()},
+			expectedLeased:        []string{queuedJob.Id()},
+			expectedQueuedVersion: queuedJob.QueuedVersion() + 1,
 		},
 		"Lease a single job from an update": {
 			jobUpdates: []database.Job{
 				{
-					JobID:  queuedJob.Id(),
-					JobSet: "testJobSet",
-					Queue:  "testQueue",
-					Serial: 1,
+					JobID:                 queuedJob.Id(),
+					JobSet:                "testJobSet",
+					Queue:                 "testQueue",
+					Queued:                true,
+					QueuedVersion:         1,
+					SchedulingInfo:        schedulingInfoBytes,
+					SchedulingInfoVersion: int32(schedulingInfo.Version),
+					Serial:                1,
 				},
 			},
-			expectedJobRunLeased: []string{queuedJob.Id()},
-			expectedLeased:       []string{queuedJob.Id()},
+			expectedJobRunLeased:  []string{queuedJob.Id()},
+			expectedLeased:        []string{queuedJob.Id()},
+			expectedQueuedVersion: queuedJob.QueuedVersion() + 1,
 		},
 		"Nothing leased": {
-			initialJobs:    []*jobdb.Job{queuedJob},
-			expectedQueued: []string{queuedJob.Id()},
+			initialJobs:           []*jobdb.Job{queuedJob},
+			expectedQueued:        []string{queuedJob.Id()},
+			expectedQueuedVersion: queuedJob.QueuedVersion(),
 		},
 		"No updates to an already leased job": {
-			initialJobs:    []*jobdb.Job{leasedJob},
-			expectedLeased: []string{leasedJob.Id()},
+			initialJobs:           []*jobdb.Job{leasedJob},
+			expectedLeased:        []string{leasedJob.Id()},
+			expectedQueuedVersion: leasedJob.QueuedVersion(),
 		},
-		"Lease returned and re-queued": {
+		"No updates to a requeued job already db": {
+			initialJobs:           []*jobdb.Job{requeuedJob},
+			expectedQueued:        []string{requeuedJob.Id()},
+			expectedQueuedVersion: requeuedJob.QueuedVersion(),
+		},
+		"No updates to a requeued job from update": {
+			jobUpdates: []database.Job{
+				{
+					JobID:                 requeuedJob.Id(),
+					JobSet:                "testJobSet",
+					Queue:                 "testQueue",
+					Queued:                true,
+					QueuedVersion:         2,
+					SchedulingInfo:        schedulingInfoBytes,
+					SchedulingInfoVersion: int32(schedulingInfo.Version),
+					Serial:                1,
+				},
+			},
+			runUpdates: []database.Run{
+				{
+					RunID:        requeuedJob.LatestRun().Id(),
+					JobID:        requeuedJob.Id(),
+					JobSet:       "testJobSet",
+					Executor:     "testExecutor",
+					Failed:       true,
+					Returned:     true,
+					RunAttempted: true,
+					Serial:       1,
+				},
+			},
+			expectedQueued:        []string{requeuedJob.Id()},
+			expectedQueuedVersion: requeuedJob.QueuedVersion(),
+		},
+		"Lease returned and re-queued when run attempted": {
 			initialJobs: []*jobdb.Job{leasedJob},
 			runUpdates: []database.Run{
 				{
-					RunID:    leasedJob.LatestRun().Id(),
-					JobID:    leasedJob.Id(),
-					JobSet:   "testJobSet",
-					Executor: "testExecutor",
-					Failed:   true,
-					Returned: true,
-					Serial:   1,
+					RunID:        leasedJob.LatestRun().Id(),
+					JobID:        leasedJob.Id(),
+					JobSet:       "testJobSet",
+					Executor:     "testExecutor",
+					Failed:       true,
+					Returned:     true,
+					RunAttempted: true,
+					Serial:       1,
+				},
+			},
+			expectedQueued:   []string{leasedJob.Id()},
+			expectedRequeued: []string{leasedJob.Id()},
+			// Should add node anti affinities for nodes of any attempted runs
+			expectedNodeAntiAffinities:       []string{leasedJob.LatestRun().Node()},
+			expectedJobSchedulingInfoVersion: 2,
+			expectedQueuedVersion:            leasedJob.QueuedVersion() + 1,
+		},
+		"Lease returned and re-queued when run not attempted": {
+			initialJobs: []*jobdb.Job{leasedJob},
+			runUpdates: []database.Run{
+				{
+					RunID:        leasedJob.LatestRun().Id(),
+					JobID:        leasedJob.Id(),
+					JobSet:       "testJobSet",
+					Executor:     "testExecutor",
+					Failed:       true,
+					Returned:     true,
+					RunAttempted: false,
+					Serial:       1,
+				},
+			},
+			expectedQueued:        []string{leasedJob.Id()},
+			expectedRequeued:      []string{leasedJob.Id()},
+			expectedQueuedVersion: leasedJob.QueuedVersion() + 1,
+		},
+		// When a lease is returned and the run was attempted, a node anti affinity is added
+		// If this node anti-affinity makes the job unschedulable, it should be failed
+		"Lease returned and failed": {
+			initialJobs: []*jobdb.Job{leasedJob},
+			runUpdates: []database.Run{
+				{
+					RunID:        leasedJob.LatestRun().Id(),
+					JobID:        leasedJob.Id(),
+					JobSet:       "testJobSet",
+					Executor:     "testExecutor",
+					Failed:       true,
+					Returned:     true,
+					RunAttempted: true,
+					Serial:       1,
 				},
 			},
-			expectedQueued: []string{leasedJob.Id()},
+			submitCheckerFailure:  true,
+			expectedJobErrors:     []string{leasedJob.Id()},
+			expectedTerminal:      []string{leasedJob.Id()},
+			expectedQueuedVersion: leasedJob.QueuedVersion(),
 		},
 		"Lease returned too many times": {
 			initialJobs: []*jobdb.Job{leasedJob},
 			// 2 failures here so the second one should trigger a run failure
 			runUpdates: []database.Run{
 				{
-					RunID:    leasedJob.LatestRun().Id(),
-					JobID:    leasedJob.Id(),
-					JobSet:   "testJobSet",
-					Executor: "testExecutor",
-					Failed:   true,
-					Returned: true,
-					Serial:   1,
+					RunID:        leasedJob.LatestRun().Id(),
+					JobID:        leasedJob.Id(),
+					JobSet:       "testJobSet",
+					Executor:     "testExecutor",
+					Failed:       true,
+					Returned:     true,
+					RunAttempted: true,
+					Serial:       1,
 				},
 				{
-					RunID:    uuid.New(),
-					JobID:    leasedJob.Id(),
-					JobSet:   "testJobSet",
-					Executor: "testExecutor",
-					Failed:   true,
-					Returned: true,
-					Serial:   2,
+					RunID:        uuid.New(),
+					JobID:        leasedJob.Id(),
+					JobSet:       "testJobSet",
+					Executor:     "testExecutor",
+					Failed:       true,
+					Returned:     true,
+					RunAttempted: true,
+					Serial:       2,
 				},
 			},
-			expectedJobErrors: []string{leasedJob.Id()},
-			expectedTerminal:  []string{leasedJob.Id()},
+			expectedJobErrors:     []string{leasedJob.Id()},
+			expectedTerminal:      []string{leasedJob.Id()},
+			expectedQueuedVersion: leasedJob.QueuedVersion(),
 		},
 		"Job cancelled": {
 			initialJobs: []*jobdb.Job{queuedJob},
@@ -151,8 +299,9 @@ func TestScheduler_TestCycle(t *testing.T) {
 					Serial:          1,
 				},
 			},
-			expectedJobCancelled: []string{queuedJob.Id()},
-			expectedTerminal:     []string{queuedJob.Id()},
+			expectedJobCancelled:  []string{queuedJob.Id()},
+			expectedTerminal:      []string{queuedJob.Id()},
+			expectedQueuedVersion: queuedJob.QueuedVersion(),
 		},
 		"Job reprioritised": {
 			initialJobs: []*jobdb.Job{queuedJob},
@@ -168,13 +317,15 @@ func TestScheduler_TestCycle(t *testing.T) {
 			expectedJobReprioritised: []string{queuedJob.Id()},
 			expectedQueued:           []string{queuedJob.Id()},
 			expectedJobPriority:      map[string]uint32{queuedJob.Id(): 2},
+			expectedQueuedVersion:    queuedJob.QueuedVersion(),
 		},
 		"Lease expired": {
-			initialJobs:          []*jobdb.Job{leasedJob},
-			staleExecutor:        true,
-			expectedJobRunErrors: []string{leasedJob.Id()},
-			expectedJobErrors:    []string{leasedJob.Id()},
-			expectedTerminal:     []string{leasedJob.Id()},
+			initialJobs:           []*jobdb.Job{leasedJob},
+			staleExecutor:         true,
+			expectedJobRunErrors:  []string{leasedJob.Id()},
+			expectedJobErrors:     []string{leasedJob.Id()},
+			expectedTerminal:      []string{leasedJob.Id()},
+			expectedQueuedVersion: leasedJob.QueuedVersion(),
 		},
 		"Job failed": {
 			initialJobs: []*jobdb.Job{leasedJob},
@@ -188,8 +339,9 @@ func TestScheduler_TestCycle(t *testing.T) {
 					Serial:   1,
 				},
 			},
-			expectedJobErrors: []string{leasedJob.Id()},
-			expectedTerminal:  []string{leasedJob.Id()},
+			expectedJobErrors:     []string{leasedJob.Id()},
+			expectedTerminal:      []string{leasedJob.Id()},
+			expectedQueuedVersion: leasedJob.QueuedVersion(),
 		},
 		"Job succeeded": {
 			initialJobs: []*jobdb.Job{leasedJob},
@@ -203,8 +355,9 @@ func TestScheduler_TestCycle(t *testing.T) {
 					Serial:    1,
 				},
 			},
-			expectedJobSucceeded: []string{leasedJob.Id()},
-			expectedTerminal:     []string{leasedJob.Id()},
+			expectedJobSucceeded:  []string{leasedJob.Id()},
+			expectedTerminal:      []string{leasedJob.Id()},
+			expectedQueuedVersion: leasedJob.QueuedVersion(),
 		},
 		"Job preempted": {
 			initialJobs:             []*jobdb.Job{leasedJob},
@@ -212,21 +365,25 @@ func TestScheduler_TestCycle(t *testing.T) {
 			expectedJobErrors:       []string{leasedJob.Id()},
 			expectedJobRunErrors:    []string{leasedJob.Id()},
 			expectedTerminal:        []string{leasedJob.Id()},
+			expectedQueuedVersion:   leasedJob.QueuedVersion(),
 		},
 		"Fetch fails": {
-			initialJobs:    []*jobdb.Job{leasedJob},
-			fetchError:     true,
-			expectedLeased: []string{leasedJob.Id()},
+			initialJobs:           []*jobdb.Job{leasedJob},
+			fetchError:            true,
+			expectedLeased:        []string{leasedJob.Id()},
+			expectedQueuedVersion: leasedJob.QueuedVersion(),
 		},
 		"Schedule fails": {
-			initialJobs:    []*jobdb.Job{leasedJob},
-			scheduleError:  true,
-			expectedLeased: []string{leasedJob.Id()}, // job should still be leased as error was thrown and transaction rolled back
+			initialJobs:           []*jobdb.Job{leasedJob},
+			scheduleError:         true,
+			expectedLeased:        []string{leasedJob.Id()}, // job should still be leased as error was thrown and transaction rolled back
+			expectedQueuedVersion: leasedJob.QueuedVersion(),
 		},
 		"Publish fails": {
-			initialJobs:    []*jobdb.Job{leasedJob},
-			publishError:   true,
-			expectedLeased: []string{leasedJob.Id()}, // job should still be leased as error was thrown and transaction rolled back
+			initialJobs:           []*jobdb.Job{leasedJob},
+			publishError:          true,
+			expectedLeased:        []string{leasedJob.Id()}, // job should still be leased as error was thrown and transaction rolled back
+			expectedQueuedVersion: leasedJob.QueuedVersion(),
 		},
 	}
 	for name, tc := range tests {
@@ -248,6 +405,7 @@ func TestScheduler_TestCycle(t *testing.T) {
 			publisher := &testPublisher{shouldError: tc.publishError}
 			stringInterner, err := stringinterner.New(100)
 			require.NoError(t, err)
+			submitChecker := &testSubmitChecker{checkSuccess: !tc.submitCheckerFailure}
 
 			heartbeatTime := testClock.Now()
 			if tc.staleExecutor {
@@ -263,9 +421,11 @@ func TestScheduler_TestCycle(t *testing.T) {
 				NewStandaloneLeaderController(),
 				publisher,
 				stringInterner,
+				submitChecker,
 				1*time.Second,
 				clusterTimeout,
-				maxLeaseReturns,
+				maxNumberOfAttempts,
+				nodeIdLabel,
 			)
 			require.NoError(t, err)
 
@@ -295,6 +455,7 @@ func TestScheduler_TestCycle(t *testing.T) {
 				fmt.Sprintf("%T", &armadaevents.EventSequence_Event_CancelledJob{}):     stringSet(tc.expectedJobCancelled),
 				fmt.Sprintf("%T", &armadaevents.EventSequence_Event_ReprioritisedJob{}): stringSet(tc.expectedJobReprioritised),
 				fmt.Sprintf("%T", &armadaevents.EventSequence_Event_JobSucceeded{}):     stringSet(tc.expectedJobSucceeded),
+				fmt.Sprintf("%T", &armadaevents.EventSequence_Event_JobRequeued{}):      stringSet(tc.expectedRequeued),
 			}
 			err = subtractEventsFromOutstandingEventsByType(publisher.events, outstandingEventsByType)
 			require.NoError(t, err)
@@ -336,6 +497,23 @@ func TestScheduler_TestCycle(t *testing.T) {
 				if expectedPriority, ok := tc.expectedJobPriority[job.Id()]; ok {
 					assert.Equal(t, job.Priority(), expectedPriority)
 				}
+				if len(tc.expectedNodeAntiAffinities) > 0 {
+					assert.Len(t, job.JobSchedulingInfo().ObjectRequirements, 1)
+					affinity := job.JobSchedulingInfo().ObjectRequirements[0].GetPodRequirements().Affinity
+					assert.NotNil(t, affinity)
+					expectedAffinity := createAntiAffinity(t, nodeIdLabel, tc.expectedNodeAntiAffinities)
+					assert.Equal(t, expectedAffinity, affinity)
+				}
+				expectedQueuedVersion := int32(1)
+				if tc.expectedQueuedVersion != 0 {
+					expectedQueuedVersion = tc.expectedQueuedVersion
+				}
+				assert.Equal(t, job.QueuedVersion(), expectedQueuedVersion)
+				expectedSchedulingInfoVersion := 1
+				if tc.expectedJobSchedulingInfoVersion != 0 {
+					expectedSchedulingInfoVersion = tc.expectedJobSchedulingInfoVersion
+				}
+				assert.Equal(t, job.JobSchedulingInfo().Version, uint32(expectedSchedulingInfoVersion))
 			}
 			assert.Equal(t, 0, len(remainingLeased))
 			assert.Equal(t, 0, len(remainingQueued))
@@ -345,6 +523,15 @@ func TestScheduler_TestCycle(t *testing.T) {
 	}
 }
 
+func createAntiAffinity(t *testing.T, key string, values []string) *v1.Affinity {
+	newAffinity := &v1.Affinity{}
+	for _, value := range values {
+		err := affinity.AddNodeAntiAffinity(newAffinity, key, value)
+		assert.NoError(t, err)
+	}
+	return newAffinity
+}
+
 func subtractEventsFromOutstandingEventsByType(eventSequences []*armadaevents.EventSequence, outstandingEventsByType map[string]map[string]bool) error {
 	for _, eventSequence := range eventSequences {
 		for _, event := range eventSequence.Events {
@@ -376,6 +563,7 @@ func TestRun(t *testing.T) {
 	publisher := &testPublisher{}
 	clusterRepo := &testExecutorRepository{}
 	leaderController := NewStandaloneLeaderController()
+	submitChecker := &testSubmitChecker{checkSuccess: true}
 	stringInterner, err := stringinterner.New(100)
 	require.NoError(t, err)
 
@@ -386,9 +574,11 @@ func TestRun(t *testing.T) {
 		leaderController,
 		publisher,
 		stringInterner,
+		submitChecker,
 		1*time.Second,
 		1*time.Hour,
-		maxLeaseReturns)
+		maxNumberOfAttempts,
+		nodeIdLabel)
 	require.NoError(t, err)
 
 	sched.clock = testClock
@@ -407,7 +597,7 @@ func TestRun(t *testing.T) {
 		wg.Add(1)
 		sched.onCycleCompleted = func() { wg.Done() }
 		jobId := util.NewULID()
-		jobRepo.updatedJobs = []database.Job{{JobID: jobId, Queue: "testQueue"}}
+		jobRepo.updatedJobs = []database.Job{{JobID: jobId, Queue: "testQueue", Queued: true}}
 		schedulingAlgo.jobsToSchedule = []string{jobId}
 		testClock.Step(10 * time.Second)
 		wg.Wait()
@@ -445,6 +635,8 @@ func TestScheduler_TestSyncState(t *testing.T) {
 					JobSet:         queuedJob.Jobset(),
 					Queue:          queuedJob.Queue(),
 					Submitted:      queuedJob.Created(),
+					Queued:         true,
+					QueuedVersion:  1,
 					Priority:       int64(queuedJob.Priority()),
 					SchedulingInfo: schedulingInfoBytes,
 					Serial:         1,
@@ -471,6 +663,18 @@ func TestScheduler_TestSyncState(t *testing.T) {
 		},
 		"add job run": {
 			initialJobs: []*jobdb.Job{queuedJob},
+			jobUpdates: []database.Job{
+				{
+					JobID:          queuedJob.Id(),
+					JobSet:         queuedJob.Jobset(),
+					Queue:          queuedJob.Queue(),
+					Queued:         false,
+					QueuedVersion:  2,
+					Priority:       int64(queuedJob.Priority()),
+					SchedulingInfo: schedulingInfoBytes,
+					Serial:         2,
+				},
+			},
 			runUpdates: []database.Run{
 				{
 					RunID:    uuid.UUID{},
@@ -494,8 +698,9 @@ func TestScheduler_TestSyncState(t *testing.T) {
 						false,
 						false,
 						false,
+						false,
 					),
-				).WithQueued(false),
+				).WithQueued(false).WithQueuedVersion(2),
 			},
 			expectedJobDbIds: []string{queuedJob.Id()},
 		},
@@ -516,22 +721,27 @@ func TestScheduler_TestSyncState(t *testing.T) {
 			expectedUpdatedJobs: []*jobdb.Job{},
 			expectedJobDbIds:    []string{},
 		},
-		"lease returned": {
+		"job requeued": {
 			initialJobs: []*jobdb.Job{leasedJob},
-			runUpdates: []database.Run{
+			jobUpdates: []database.Job{
 				{
-					JobID:    leasedJob.Id(),
-					JobSet:   leasedJob.Id(),
-					RunID:    leasedJob.LatestRun().Id(),
-					Failed:   true,
-					Returned: true,
-					Created:  leasedJob.LatestRun().Created(),
+					JobID:                 leasedJob.Id(),
+					JobSet:                leasedJob.Jobset(),
+					Queue:                 leasedJob.Queue(),
+					Submitted:             leasedJob.Created(),
+					Queued:                true,
+					QueuedVersion:         3,
+					Priority:              int64(leasedJob.Priority()),
+					SchedulingInfo:        updatedSchedulingInfoBytes,
+					SchedulingInfoVersion: int32(updatedSchedulingInfo.Version),
+					Serial:                1,
 				},
 			},
 			expectedUpdatedJobs: []*jobdb.Job{
 				leasedJob.
-					WithUpdatedRun(leasedJob.LatestRun().WithReturned(true)).
-					WithQueued(true),
+					WithJobSchedulingInfo(updatedSchedulingInfo).
+					WithQueued(true).
+					WithQueuedVersion(3),
 			},
 			expectedJobDbIds: []string{leasedJob.Id()},
 		},
@@ -560,9 +770,11 @@ func TestScheduler_TestSyncState(t *testing.T) {
 				leaderController,
 				publisher,
 				stringInterner,
+				nil,
 				1*time.Second,
 				1*time.Hour,
-				maxLeaseReturns)
+				maxNumberOfAttempts,
+				nodeIdLabel)
 			require.NoError(t, err)
 
 			// insert initial jobs
@@ -587,6 +799,18 @@ func TestScheduler_TestSyncState(t *testing.T) {
 	}
 }
 
+type testSubmitChecker struct {
+	checkSuccess bool
+}
+
+func (t *testSubmitChecker) CheckPodRequirements(podRequirement *schedulerobjects.PodRequirements) (bool, string) {
+	return t.checkSuccess, ""
+}
+
+func (t *testSubmitChecker) CheckApiJobs(jobs []*api.Job) (bool, string) {
+	return t.checkSuccess, "2"
+}
+
 // Test implementations of the interfaces needed by the Scheduler
 type testJobRepository struct {
 	updatedJobs           []database.Job
diff --git a/internal/scheduler/scheduling_algo_test.go b/internal/scheduler/scheduling_algo_test.go
index 1d392ef7..5fef06e8 100644
--- a/internal/scheduler/scheduling_algo_test.go
+++ b/internal/scheduler/scheduling_algo_test.go
@@ -290,6 +290,8 @@ func OneCpuJob(creationTime int64) *jobdb.Job {
 		queueName,
 		0,
 		schedulingInfo,
+		true,
+		1,
 		false,
 		false,
 		false,
diff --git a/internal/scheduler/submitcheck_test.go b/internal/scheduler/submitcheck_test.go
index 397bf08a..81eeb55a 100644
--- a/internal/scheduler/submitcheck_test.go
+++ b/internal/scheduler/submitcheck_test.go
@@ -20,6 +20,76 @@ import (
 	"github.com/armadaproject/armada/pkg/api"
 )
 
+func TestSubmitChecker_CheckPodRequirements(t *testing.T) {
+	defaultTimeout := 15 * time.Minute
+	baseTime = time.Now().UTC()
+	expiredTime := baseTime.Add(-defaultTimeout).Add(-1 * time.Second)
+
+	tests := map[string]struct {
+		executorTimout time.Duration
+		config         configuration.SchedulingConfig
+		executors      []*schedulerobjects.Executor
+		podRequirement *schedulerobjects.PodRequirements
+		expectPass     bool
+	}{
+		"one job schedules": {
+			executorTimout: defaultTimeout,
+			config:         testSchedulingConfig(),
+			executors:      []*schedulerobjects.Executor{testExecutor(baseTime)},
+			podRequirement: testSmallCpuJob("queue", 1),
+			expectPass:     true,
+		},
+		"no jobs schedule due to resources": {
+			executorTimout: defaultTimeout,
+			config:         testSchedulingConfig(),
+			executors:      []*schedulerobjects.Executor{testExecutor(baseTime)},
+			podRequirement: testLargeCpuJob("queue", 1),
+			expectPass:     false,
+		},
+		"no jobs schedule due to selector": {
+			executorTimout: defaultTimeout,
+			config:         testSchedulingConfig(),
+			executors:      []*schedulerobjects.Executor{testExecutor(baseTime)},
+			podRequirement: withNodeSelectorPodReq(map[string]string{"foo": "bar"}, testSmallCpuJob("queue", 1)),
+			expectPass:     false,
+		},
+		"no jobs schedule due to executor timeout": {
+			executorTimout: defaultTimeout,
+			config:         testSchedulingConfig(),
+			executors:      []*schedulerobjects.Executor{testExecutor(expiredTime)},
+			podRequirement: testSmallCpuJob("queue", 1),
+			expectPass:     false,
+		},
+		"multiple executors, 1 expired": {
+			executorTimout: defaultTimeout,
+			config:         testSchedulingConfig(),
+			executors:      []*schedulerobjects.Executor{testExecutor(expiredTime), testExecutor(baseTime)},
+			podRequirement: testSmallCpuJob("queue", 1),
+			expectPass:     true,
+		},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+			defer cancel()
+
+			ctrl := gomock.NewController(t)
+			mockExecutorRepo := schedulermocks.NewMockExecutorRepository(ctrl)
+			mockExecutorRepo.EXPECT().GetExecutors(ctx).Return(tc.executors, nil).AnyTimes()
+			fakeClock := clock.NewFakeClock(baseTime)
+			submitCheck := NewSubmitChecker(tc.executorTimout, tc.config, mockExecutorRepo)
+			submitCheck.clock = fakeClock
+			submitCheck.updateExecutors(ctx)
+			result, msg := submitCheck.CheckPodRequirements(tc.podRequirement)
+			assert.Equal(t, tc.expectPass, result)
+			if !tc.expectPass {
+				assert.NotEqual(t, "", msg)
+			}
+			logrus.Info(msg)
+		})
+	}
+}
+
 func TestSubmitChecker_TestCheckApiJobs(t *testing.T) {
 	defaultTimeout := 15 * time.Minute
 	baseTime = time.Now().UTC()
diff --git a/internal/scheduler/testfixtures_test.go b/internal/scheduler/testfixtures_test.go
index ac96114e..fa76d4cc 100644
--- a/internal/scheduler/testfixtures_test.go
+++ b/internal/scheduler/testfixtures_test.go
@@ -174,6 +174,11 @@ func withNodeSelectorPodReqs(selector map[string]string, reqs []*schedulerobject
 	return reqs
 }
 
+func withNodeSelectorPodReq(selector map[string]string, req *schedulerobjects.PodRequirements) *schedulerobjects.PodRequirements {
+	req.NodeSelector = maps.Clone(selector)
+	return req
+}
+
 func withNodeAffinityPodReqs(nodeSelectorTerms []v1.NodeSelectorTerm, reqs []*schedulerobjects.PodRequirements) []*schedulerobjects.PodRequirements {
 	for _, req := range reqs {
 		if req.Affinity == nil {
diff --git a/internal/scheduleringester/dbops_test.go b/internal/scheduleringester/dbops_test.go
index de1e984a..211b5046 100644
--- a/internal/scheduleringester/dbops_test.go
+++ b/internal/scheduleringester/dbops_test.go
@@ -29,6 +29,42 @@ func TestMerge(t *testing.T) {
 	assert.Equal(t, MarkJobsCancelRequested{jobId1: false, jobId2: true, jobId3: true}, markJobsCancelled1)
 }
 
+func TestMerge_UpdateJobSchedulingInfo(t *testing.T) {
+	jobId1 := util.NewULID()
+	jobId2 := util.NewULID()
+	jobId3 := util.NewULID()
+	jobId4 := util.NewULID()
+	updateSchedulingInfo1 := UpdateJobSchedulingInfo{jobId1: &JobSchedulingInfoUpdate{[]byte("job 1"), 1}, jobId2: &JobSchedulingInfoUpdate{[]byte("job 2"), 1}, jobId3: &JobSchedulingInfoUpdate{[]byte("job 3 v2"), 2}}
+	updateSchedulingInfo2 := UpdateJobSchedulingInfo{jobId2: &JobSchedulingInfoUpdate{[]byte("job 2 v2"), 2}, jobId3: &JobSchedulingInfoUpdate{[]byte("job 3"), 1}, jobId4: &JobSchedulingInfoUpdate{[]byte("job 4"), 1}}
+	expectedResult := UpdateJobSchedulingInfo{
+		jobId1: &JobSchedulingInfoUpdate{[]byte("job 1"), 1},
+		jobId2: &JobSchedulingInfoUpdate{[]byte("job 2 v2"), 2},
+		jobId3: &JobSchedulingInfoUpdate{[]byte("job 3 v2"), 2},
+		jobId4: &JobSchedulingInfoUpdate{[]byte("job 4"), 1},
+	}
+	ok := updateSchedulingInfo1.Merge(updateSchedulingInfo2)
+	assert.True(t, ok)
+	assert.Equal(t, expectedResult, updateSchedulingInfo1)
+}
+
+func TestMerge_UpdateJobQueuedState(t *testing.T) {
+	jobId1 := util.NewULID()
+	jobId2 := util.NewULID()
+	jobId3 := util.NewULID()
+	jobId4 := util.NewULID()
+	updatedJobQueuedState1 := UpdateJobQueuedState{jobId1: &JobQueuedStateUpdate{true, 1}, jobId2: &JobQueuedStateUpdate{true, 1}, jobId3: &JobQueuedStateUpdate{false, 2}}
+	updateJobQueuedState2 := UpdateJobQueuedState{jobId2: &JobQueuedStateUpdate{false, 2}, jobId3: &JobQueuedStateUpdate{true, 1}, jobId4: &JobQueuedStateUpdate{true, 1}}
+	expectedResult := UpdateJobQueuedState{
+		jobId1: &JobQueuedStateUpdate{true, 1},
+		jobId2: &JobQueuedStateUpdate{false, 2},
+		jobId3: &JobQueuedStateUpdate{false, 2},
+		jobId4: &JobQueuedStateUpdate{true, 1},
+	}
+	ok := updatedJobQueuedState1.Merge(updateJobQueuedState2)
+	assert.True(t, ok)
+	assert.Equal(t, expectedResult, updatedJobQueuedState1)
+}
+
 func TestMerge_InsertPartitionMarker(t *testing.T) {
 	marker1 := &InsertPartitionMarker{markers: []*schedulerdb.Marker{
 		{
@@ -150,10 +186,10 @@ func TestDbOperationOptimisation(t *testing.T) {
 		"MarkRunsFailed": {N: 3, Ops: []DbOperation{
 			InsertJobs{jobIds[0]: &schedulerdb.Job{JobID: jobIds[0]}},                   // 1
 			InsertRuns{runIds[0]: &schedulerdb.Run{JobID: jobIds[0], RunID: runIds[0]}}, // 2
-			MarkRunsFailed{runIds[0]: &JobRunFailed{true}},                              // 3
+			MarkRunsFailed{runIds[0]: &JobRunFailed{true, true}},                        // 3
 			InsertJobs{jobIds[1]: &schedulerdb.Job{JobID: jobIds[1]}},                   // 3
 			InsertRuns{runIds[1]: &schedulerdb.Run{JobID: jobIds[1], RunID: runIds[1]}}, // 3
-			MarkRunsFailed{runIds[1]: &JobRunFailed{true}},                              // 3
+			MarkRunsFailed{runIds[1]: &JobRunFailed{true, true}},                        // 3
 			InsertJobs{jobIds[2]: &schedulerdb.Job{JobID: jobIds[2]}},                   // 3
 		}},
 		"MarkRunsRunning": {N: 3, Ops: []DbOperation{
@@ -171,6 +207,20 @@ func TestDbOperationOptimisation(t *testing.T) {
 			InsertJobs{jobIds[1]: &schedulerdb.Job{JobID: jobIds[1]}}, // 1
 			InsertJobs{jobIds[2]: &schedulerdb.Job{JobID: jobIds[2]}}, // 1
 		}},
+		"UpdateJobSchedulingInfo": {N: 2, Ops: []DbOperation{
+			InsertJobs{jobIds[0]: &schedulerdb.Job{JobID: jobIds[0]}},                        // 1
+			UpdateJobSchedulingInfo{jobIds[0]: &JobSchedulingInfoUpdate{[]byte("job 1"), 1}}, // 2
+			InsertJobs{jobIds[1]: &schedulerdb.Job{JobID: jobIds[1]}},                        // 1
+			UpdateJobSchedulingInfo{jobIds[1]: &JobSchedulingInfoUpdate{[]byte("job 2"), 1}}, // 2
+			InsertJobs{jobIds[2]: &schedulerdb.Job{JobID: jobIds[2]}},                        // 1
+			UpdateJobSchedulingInfo{jobIds[2]: &JobSchedulingInfoUpdate{[]byte("job 3"), 1}}, // 2
+		}},
+		"UpdateJobQueuedState": {N: 2, Ops: []DbOperation{
+			UpdateJobQueuedState{jobIds[0]: &JobQueuedStateUpdate{true, 1}},  // 2
+			InsertJobs{jobIds[1]: &schedulerdb.Job{JobID: jobIds[1]}},        // 1
+			UpdateJobQueuedState{jobIds[1]: &JobQueuedStateUpdate{false, 1}}, // 2
+			UpdateJobQueuedState{jobIds[2]: &JobQueuedStateUpdate{true, 3}},  // 2
+		}},
 	}
 	for name, tc := range tests {
 		t.Run(name, func(t *testing.T) {
diff --git a/internal/scheduleringester/instructions_test.go b/internal/scheduleringester/instructions_test.go
index ae174f4c..cfa8139c 100644
--- a/internal/scheduleringester/instructions_test.go
+++ b/internal/scheduleringester/instructions_test.go
@@ -37,6 +37,8 @@ func TestConvertSequence(t *testing.T) {
 				UserID:        f.UserId,
 				Groups:        compress.MustCompressStringArray(f.Groups, compressor),
 				Queue:         f.Queue,
+				Queued:        true,
+				QueuedVersion: 0,
 				Priority:      int64(f.Priority),
 				Submitted:     f.BaseTime.UnixNano(),
 				SubmitMessage: protoutil.MustMarshallAndCompress(f.Submit.GetSubmitJob(), compressor),
@@ -45,6 +47,7 @@ func TestConvertSequence(t *testing.T) {
 					AtMostOnce:      true,
 					Preemptible:     true,
 					ConcurrencySafe: true,
+					Version:         0,
 					ObjectRequirements: []*schedulerobjects.ObjectRequirements{
 						{
 							Requirements: &schedulerobjects.ObjectRequirements_PodRequirements{
@@ -76,13 +79,19 @@ func TestConvertSequence(t *testing.T) {
 		},
 		"job run leased": {
 			events: []*armadaevents.EventSequence_Event{f.Leased},
-			expected: []DbOperation{InsertRuns{f.RunIdUuid: &schedulerdb.Run{
-				RunID:    f.RunIdUuid,
-				JobID:    f.JobIdString,
-				JobSet:   f.JobSetName,
-				Executor: f.ExecutorId,
-				Node:     f.NodeName,
-			}}},
+			expected: []DbOperation{
+				InsertRuns{f.RunIdUuid: &schedulerdb.Run{
+					RunID:    f.RunIdUuid,
+					JobID:    f.JobIdString,
+					JobSet:   f.JobSetName,
+					Executor: f.ExecutorId,
+					Node:     f.NodeName,
+				}},
+				UpdateJobQueuedState{f.JobIdString: &JobQueuedStateUpdate{
+					Queued:             false,
+					QueuedStateVersion: 1,
+				}},
+			},
 		},
 		"job run running": {
 			events:   []*armadaevents.EventSequence_Event{f.Running},
@@ -100,7 +109,7 @@ func TestConvertSequence(t *testing.T) {
 					JobID: f.JobIdString,
 					Error: protoutil.MustMarshallAndCompress(f.LeaseReturned.GetJobRunErrors().Errors[0], compressor),
 				}},
-				MarkRunsFailed{f.RunIdUuid: &JobRunFailed{LeaseReturned: true}},
+				MarkRunsFailed{f.RunIdUuid: &JobRunFailed{LeaseReturned: true, RunAttempted: true}},
 			},
 		},
 		"job failed": {
@@ -111,7 +120,7 @@ func TestConvertSequence(t *testing.T) {
 					JobID: f.JobIdString,
 					Error: protoutil.MustMarshallAndCompress(f.JobRunFailed.GetJobRunErrors().Errors[0], compressor),
 				}},
-				MarkRunsFailed{f.RunIdUuid: &JobRunFailed{LeaseReturned: false}},
+				MarkRunsFailed{f.RunIdUuid: &JobRunFailed{LeaseReturned: false, RunAttempted: true}},
 			},
 		},
 		"job errors terminal": {
@@ -156,6 +165,19 @@ func TestConvertSequence(t *testing.T) {
 				MarkJobsCancelled{f.JobIdString: true},
 			},
 		},
+		"JobRequeued": {
+			events: []*armadaevents.EventSequence_Event{f.JobRequeued},
+			expected: []DbOperation{
+				UpdateJobQueuedState{f.JobIdString: &JobQueuedStateUpdate{
+					Queued:             true,
+					QueuedStateVersion: f.JobRequeued.GetJobRequeued().UpdateSequenceNumber,
+				}},
+				UpdateJobSchedulingInfo{f.JobIdString: &JobSchedulingInfoUpdate{
+					JobSchedulingInfo:        protoutil.MustMarshall(f.JobRequeued.GetJobRequeued().SchedulingInfo),
+					JobSchedulingInfoVersion: int32(f.JobRequeued.GetJobRequeued().SchedulingInfo.Version),
+				}},
+			},
+		},
 		"PositionMarker": {
 			events: []*armadaevents.EventSequence_Event{f.PartitionMarker},
 			expected: []DbOperation{
diff --git a/internal/scheduleringester/schedulerdb_test.go b/internal/scheduleringester/schedulerdb_test.go
index 47a76194..1eef9b01 100644
--- a/internal/scheduleringester/schedulerdb_test.go
+++ b/internal/scheduleringester/schedulerdb_test.go
@@ -54,6 +54,12 @@ func TestWriteOps(t *testing.T) {
 				runIds[2]: &schedulerdb.Run{JobID: jobIds[2], RunID: runIds[2]},
 				runIds[3]: &schedulerdb.Run{JobID: jobIds[3], RunID: runIds[3]},
 			},
+			UpdateJobQueuedState{
+				jobIds[0]: &JobQueuedStateUpdate{Queued: false, QueuedStateVersion: 1},
+				jobIds[1]: &JobQueuedStateUpdate{Queued: false, QueuedStateVersion: 1},
+				jobIds[2]: &JobQueuedStateUpdate{Queued: false, QueuedStateVersion: 1},
+				jobIds[3]: &JobQueuedStateUpdate{Queued: false, QueuedStateVersion: 1},
+			},
 		}},
 		"UpdateJobSetPriorities": {Ops: []DbOperation{
 			InsertJobs{
@@ -151,6 +157,22 @@ func TestWriteOps(t *testing.T) {
 				runIds[1]: true,
 			},
 		}},
+		"UpdateJobSchedulingInfo": {Ops: []DbOperation{
+			InsertJobs{
+				jobIds[0]: &schedulerdb.Job{JobID: jobIds[0], JobSet: "set1"},
+				jobIds[1]: &schedulerdb.Job{JobID: jobIds[1], JobSet: "set2"},
+			},
+			UpdateJobSchedulingInfo{
+				jobIds[0]: &JobSchedulingInfoUpdate{
+					JobSchedulingInfo:        []byte("job-0 info update"),
+					JobSchedulingInfoVersion: 1,
+				},
+				jobIds[1]: &JobSchedulingInfoUpdate{
+					JobSchedulingInfo:        []byte("job-1 info update"),
+					JobSchedulingInfoVersion: 2,
+				},
+			},
+		}},
 		"Insert JobRunErrors": {Ops: []DbOperation{
 			InsertJobRunErrors{
 				runIds[0]: &schedulerdb.JobRunError{
@@ -180,7 +202,8 @@ func TestWriteOps(t *testing.T) {
 			},
 			MarkRunsFailed{
 				runIds[0]: &JobRunFailed{LeaseReturned: true},
-				runIds[1]: &JobRunFailed{LeaseReturned: false},
+				runIds[1]: &JobRunFailed{LeaseReturned: true, RunAttempted: true},
+				runIds[2]: &JobRunFailed{LeaseReturned: false},
 			},
 		}},
 		"MarkRunsRunning": {Ops: []DbOperation{
@@ -322,6 +345,34 @@ func assertOpSuccess(t *testing.T, schedulerDb *SchedulerDb, serials map[string]
 			}
 		}
 		assert.Equal(t, expected, actual)
+	case UpdateJobQueuedState:
+		jobs, err := selectNewJobs(ctx, serials["jobs"])
+		if err != nil {
+			return errors.WithStack(err)
+		}
+		numChanged := 0
+		for _, job := range jobs {
+			if e, ok := expected[job.JobID]; ok {
+				assert.Equal(t, e.Queued, job.Queued)
+				assert.Equal(t, e.QueuedStateVersion, job.QueuedVersion)
+				numChanged++
+			}
+		}
+		assert.Greater(t, numChanged, 0)
+	case UpdateJobSchedulingInfo:
+		jobs, err := selectNewJobs(ctx, serials["jobs"])
+		if err != nil {
+			return errors.WithStack(err)
+		}
+		numChanged := 0
+		for _, job := range jobs {
+			if e, ok := expected[job.JobID]; ok {
+				assert.Equal(t, e.JobSchedulingInfoVersion, job.SchedulingInfoVersion)
+				assert.Equal(t, e.JobSchedulingInfo, job.SchedulingInfo)
+				numChanged++
+			}
+		}
+		assert.Greater(t, numChanged, 0)
 	case UpdateJobSetPriorities:
 		jobs, err := selectNewJobs(ctx, serials["jobs"])
 		if err != nil {
@@ -465,6 +516,7 @@ func assertOpSuccess(t *testing.T, schedulerDb *SchedulerDb, serials map[string]
 			if expectedRun, ok := expected[run.RunID]; ok {
 				assert.True(t, run.Failed)
 				assert.Equal(t, expectedRun.LeaseReturned, run.Returned)
+				assert.Equal(t, expectedRun.RunAttempted, run.RunAttempted)
 				numChanged++
 			}
 		}