Skip to content

feat: [Scheduler] add worker.schedulerV1VersionOverride - #11856

Merged
chaptersix merged 5 commits into
sch-v1-wffrom
sched-version-target
Sep 2, 2026
Merged

feat: [Scheduler] add worker.schedulerV1VersionOverride#11856
chaptersix merged 5 commits into
sch-v1-wffrom
sched-version-target

Conversation

@chaptersix

@chaptersix chaptersix commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Stack 2/2 (base #11831): #11831 -> #11856.

Purpose

#11831 makes a ceiling dynamic, but a ceiling can only restrict a default. If the binary default is v12, min(v12, v14) is still v12. This PR adds the namespace-level promotion control required to activate a newer behavior already supported by the binary.

What changed

Adds worker.schedulerV1VersionOverride. Its default -1 retains the binary default. A value from the current default through LatestSchedulerWorkflowVersion is valid; values below the default or above the versions supported by this binary are ignored.

The override is re-read during every scheduler tweakables MutableSideEffect evaluation. It is selected before the ceiling from #11831, and the version already recorded by the current run remains a floor:

requested version = valid override, otherwise binary default
next version      = max(recorded version, min(requested version, current ceiling))

With the static default at v12 and this binary supporting v14:

override=-1, ceiling=-1 -> v12
override=14, ceiling=-1 -> v14
override=14, ceiling=12 -> v12

If a v14 override is already configured while a v12 ceiling is active, lifting the ceiling lets the workflow record v14 at its next wakeup. Lowering/removing the override, or lowering the ceiling, never downgrades a version already recorded in that run; the lower value applies to a fresh run after continue-as-new.

The frontend uses the same override for initial schedule memo/list-info construction, so version-dependent metadata agrees with the first worker task.

Determinism and coverage

The selected result is recorded by the existing tweakables MutableSideEffect; replay uses that marker rather than live dynamic config. A single dynamic-config struct supplies the workflow dependencies, and named version-selection fields make the frontend handoff explicit. The pure transition table covers promotion to the latest supported version, ceiling precedence, and invalid overrides. Workflow coverage verifies that an override advances within the same run when a prior ceiling is lifted.

@chaptersix
chaptersix marked this pull request as ready for review August 28, 2026 21:31
@chaptersix
chaptersix requested review from a team as code owners August 28, 2026 21:31
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T19:00:39.586500Z 0895ea3 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Claude finished @chaptersix's task in 3m 9s —— View job


Reviewed 6 files, 4 findings.

Comment thread service/worker/scheduler/workflow.go Outdated
Comment thread service/worker/scheduler/workflow.go
Comment thread service/worker/scheduler/workflow.go Outdated
Comment thread service/worker/scheduler/workflow.go Outdated
@chaptersix
chaptersix force-pushed the sched-version-target branch from 662eac6 to 49984ac Compare August 28, 2026 21:47
@chaptersix

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d72047da1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread service/worker/scheduler/workflow.go Outdated
Comment on lines +1892 to +1894
if override > int(LatestSchedulerWorkflowVersion) {
s.logger.Warn("worker.schedulerV1VersionOverride above the latest supported version; ignored",
"override", override, "latestSupportedVersion", LatestSchedulerWorkflowVersion)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Rate-limit warnings for an invalid override

When a namespace sets an override above the latest supported version, determineVersion runs during every tweakables MutableSideEffect evaluation for every schedule, so this warning is emitted again at every wakeup. A single invalid namespace-level value can therefore flood logs and add avoidable load across all active schedules until corrected; track the last-seen override and warn only when it changes, as the ceiling handling immediately above already does.

Useful? React with 👍 / 👎.

Comment thread service/worker/scheduler/workflow.go Outdated
Comment thread service/worker/scheduler/workflow.go Outdated
Comment on lines +1914 to +1915
if override >= int(v) && override <= int(LatestSchedulerWorkflowVersion) {
v = SchedulerWorkflowVersion(override)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fix operator migration before exposing v13

When an operator sets this override to 13 or 14, enables EnableCHASMSchedulerMigration, leaves CHASMSchedulerMigrationRolloutPercent at its default 0, and signals one schedule for migration, the v13 rollback logic clears PendingMigration before the activity runs because the workflow-side migration flag includes the rollout decision. This is already demonstrated by the skipped TestOperatorMigrateSignalSurvivesRolloutPercentZero; previously the shipped default remained v12, but accepting the override now makes the broken v13 path reachable in production and silently drops operator-requested migrations.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b5808cfe6a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread service/worker/scheduler/workflow.go Outdated
Comment thread service/worker/scheduler/workflow.go
Base automatically changed from sched-version-ceiling to sch-v1-wf September 2, 2026 16:37
chaptersix added a commit that referenced this pull request Sep 2, 2026
> **Stack 1/2** (base `sch-v1-wf`): **#11831** -> #11856.

## What changes relative to `main`

`main` records the static `CurrentTweakablePolicies.Version` in the
scheduler workflow `tweakables` `MutableSideEffect`; it is currently
v12. There is no namespace dynamic config for choosing a V1 scheduler
workflow version. A running workflow can advance only when a later
binary changes that static default.

The base branch adds `worker.schedulerV1VersionCeiling`, but selects the
capped version only on the first `tweakables` evaluation. Raising or
removing a v11 ceiling therefore leaves an in-flight workflow at v11
until continue-as-new.

This PR re-reads the ceiling on every `tweakables` evaluation. The pure
transition is:

```text
next version = max(recorded version, min(binary default, current ceiling))
```

A negative ceiling is unset. The version remains monotonic, while the
ceiling is captured for the current evaluation.

## Result

With a binary default of v12:

```text
first evaluation: ceiling=11 -> records version=11, ceiling=11
next wakeup:      ceiling=-1 -> records version=12, ceiling=-1
```

The scheduler advances at that next wakeup; it does not wait for
continue-as-new. Conversely, lowering a ceiling after a run has already
recorded a higher version does not downgrade that run. The new, lower
ceiling is recorded and applies when a fresh run starts.

## Replay compatibility

`MutableSideEffect` records each selected version/ceiling result, so
replay consumes history rather than live dynamic config. Older markers
that predate the ceiling field remain readable; a later live evaluation
records the current ceiling. The recorded version remains the monotonic
floor throughout.

## Scope

A ceiling can restrict the binary default but cannot promote beyond it.
The child PR #11856 adds the explicit override needed to select a
supported version such as v14 before a later release changes the static
default.

## Coverage

The pure transition table covers unset and zero ceilings, capping,
retaining an already-recorded version under a newly lower ceiling, and
advancement when the ceiling is raised or removed. Workflow coverage
verifies that lifting the ceiling advances and permits migration within
the same run.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 66f25ee652

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1918 to 1920
if shouldWarnForVersionCeiling(s.tweakables, resolveVersionBeforeCeiling(defaultVersion, override), ceiling) {
s.logger.Warn("worker.schedulerV1VersionCeiling above the version this binary records; no effect",
"ceiling", ceiling, "recordedVersion", defaultVersion)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Distinguish a retained version from an ineffective high ceiling

The current revision accounts for the live override, but after a run has recorded v14, removing the override and setting the ceiling to 13 still enters this branch because it compares 13 with the default v12. The warning consequently claims the ceiling is above the recorded version and reports recordedVersion=12, even though the run actually retains v14 because determineVersionTransition cannot downgrade it. This misleads operators attempting to restore rollback compatibility; compare against s.tweakables.Version and emit the retained-version diagnostic instead.

Useful? React with 👍 / 👎.

Comment thread service/worker/scheduler/workflow.go
Comment thread service/worker/scheduler/workflow.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0895ea3f8c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// TestEveryVersionIsMapped verifies that each fixture records the version it represents.
var versionFixtures = map[scheduler.SchedulerWorkflowVersion]string{
scheduler.MigrationHandoffFixes: "replay_migration_v1_to_v2.json.gz",
scheduler.RefreshCompletionDesiredTime: "replay_version_override_v14.json.gz",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exercise the v14 branch in the replay fixture

The newly added fixture only proves that a v14 marker exists: its sole LocalActivity is StartWorkflow, followed by force-continue-as-new, with no WatchWorkflow completion. Replay therefore never reaches the only v14 behavior at workflow.go:1075-1078 (RefreshCompletionDesiredTime), so a nondeterministic change in that branch would still pass both replay tests. This is fresh evidence beyond the earlier missing-fixture finding; capture a history where refresh discovers a completion while a start is buffered.

Useful? React with 👍 / 👎.

@chaptersix
chaptersix merged commit d1f76b7 into sch-v1-wf Sep 2, 2026
51 checks passed
@chaptersix
chaptersix deleted the sched-version-target branch September 2, 2026 19:21
@davidporter-id-au davidporter-id-au mentioned this pull request Sep 2, 2026
3 tasks
davidporter-id-au added a commit that referenced this pull request Sep 3, 2026
## Summary 

We held back several fixes in the V1 schedule workflow and bundled them
into a single atomic commit with the intention of avoiding having to
perform multiple increments of the workflow versioning. The changes are
broadly: Migration v1->v2 fixes + CGS version changes.

## Risk 

This touches V1 schedules workflow, and a mistake risks
nondeterminitism. This is relatively high impact and has quite a bit of
subtlety. This is therefore a commit we need to merge with care

## Testing and validation

1. Changes have been manually tested on individual PRs already
2. Changes have been manually run through CGS's validation suite (cc
@liam-lowe)

Our intention is to a) make the 163 release cut and b) continue to
manually testing a few operational scenarios manually while this is
being merged in. If we see problems we can hotfix this. Manually doing
some scenario testing will take time so we intend to do this in
parallel.

Some of the scenarios we will test manually are: 

1. Deploy and rollback to an earlier version (probably with LLM assist,
but running locally)
2. Manually lifting the the version of a schedule via dynamic config
(manually) ensuring there's no nondeterminitism risk
3. V2->v1 Rollback works as expected

Some of the tests we've already run and manually validated(cc
@liam-lowe)
1. Version floor works as expected 
2. That this change fixes v1->v2 migration 
3. That this change avoids the problematic history entries which broke a
customer using the coinbase Ruby SDK

## LLM Summary

This PR stacks a series of changes to the V1 (legacy) scheduler
workflow: two migration-correctness fixes, and dynamic-config levers to
safely roll the V1 workflow version forward/backward across a
multi-cluster deployment without requiring continue-as-new. Summary of
each PR in the stack, in order:

### #11462 — V1→V2 migration-eligibility fix and migrated-start ID
(David Porter)
Combines two previously separate fixes under one shared version bump
(`v13`), avoiding two separate version-bump deploys for the same version
number:
- `RefreshBeforeMigrationCheck`: fixes a bug that was preventing V1→V2
migration from ever succeeding under default configuration.
- `PreserveMigratedStartIDs`: preserves the request IDs workflows were
originally started with, in case of a rollback.
- Adds a guard against late migrations caused by a transient error
bouncing a migration attempt and the schedule re-attempting it after
waking again.
- Follows the existing two-phase-rollout pattern: this PR only teaches
the scheduler to *understand* v13 (gated behind `hasMinVersion(13)`);
`CurrentTweakablePolicies.Version` itself stays at v12 pending a
follow-up activation deploy.

### #11588 — Fix schedule action delay after refresh (Alex Stanfield)
- `processWatcherResult` only recorded `DesiredTime` on the long-poll
path; when a refresh instead discovered the prior action had completed,
`DesiredTime` stayed unset, inflating the reported `ScheduleActionDelay`
for back-to-back buffered actions.
- Backdates `DesiredTime` on the refresh path too, gated behind a new
version, `RefreshCompletionDesiredTime` (v14).
- Handles two follow-on correctness gaps surfaced in review: `ALLOW_ALL`
starts (never blocked by a running workflow, so shouldn't be backdated
to an unrelated close time) and multiple tracked executions in one
refresh pass (must only move the recorded close time forward, never
backward).
- Extracts the decision into a pure, unit-testable
`shouldBackdateDesiredTime` function, and a shared
`IgnoresRunningWorkflow` helper so `ProcessBuffer` and the new backdate
logic can't drift apart on what "waits for a running workflow" means.

### #11827 — Sort BufferedStarts by due time on CHASM-to-V1 rollback
(Alex Stanfield)
- `CHASMToLegacyStartScheduleArgs` (the CHASM→V1 rollback conversion)
appended trigger-derived `BufferedStarts` after the regular pending ones
unconditionally, without sorting by due time.
- V1's buffer-processing code assumes `BufferedStarts[0]` is always the
earliest-due pending start — an invariant not guaranteed across a
rollback, since manual triggers are built by iterating a Go map
(randomized order) and appended regardless of their own due time.
- Fix: sort the combined list by `ActualTime` after appending, mirroring
the sort already applied to `RecentActions` a few lines above.

### #11831 — Re-evaluate V1 version ceiling per iteration (Alex
Stanfield)
- The base PR (#10817, by liam-lowe) added
`worker.schedulerV1VersionCeiling` but only applied it on the first
`tweakables` evaluation, so raising/removing a ceiling left an in-flight
workflow stuck at the capped version until continue-as-new.
- Re-reads the ceiling on every evaluation: `next version = max(recorded
version, min(binary default, current ceiling))`. The recorded version
stays monotonic — a newly-lowered ceiling never downgrades a version
already recorded in the current run, but a raised/removed ceiling lets
the workflow advance at its next wakeup instead of waiting for
continue-as-new.
- Recorded via the existing `MutableSideEffect`, so replay consumes
history rather than re-evaluating live dynamic config.

### #11856 — Add worker.schedulerV1VersionOverride (Alex Stanfield)
- #11831 makes the ceiling dynamic, but a ceiling can only restrict a
binary default, never promote past it. Adds the namespace-level override
needed to activate a newer version already supported by the binary.
- `requested version = valid override, otherwise binary default`; `next
version = max(recorded version, min(requested version, current
ceiling))`. Default `-1` retains the binary default; values below the
binary default or above `LatestSchedulerWorkflowVersion` are ignored.
- The frontend uses the same override for initial schedule
memo/list-info construction, so version-dependent metadata agrees with
the first worker task.

Base of the stack: #10817 (liam-lowe) introduced the original
(static-per-run) `worker.schedulerV1VersionCeiling` dynamic config that
#11831/#11856 build on.

## Why?
To support safe, gradual rollout/rollback of V1 scheduler workflow
version bumps (migration fixes, action-delay-after-refresh fix) in a
cross-version multi-cluster topology, without requiring continue-as-new
to pick up config changes.

## How did you test it?
- [x] built
- [x] covered by existing tests
- [x] added new unit test(s)

---------

Co-authored-by: liam-lowe <56076876+liam-lowe@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: alex.stanfield <13949480+chaptersix@users.noreply.github.com>
Co-authored-by: Stephan Behnke <stephanos@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: michaely520 <michaely520@users.noreply.github.com>
Co-authored-by: Feiyang Xie <feiyang3cat@outlook.com>
Co-authored-by: Kannan <rkannan82@users.noreply.github.com>
Co-authored-by: Fred Tzeng <41805201+fretz12@users.noreply.github.com>
Co-authored-by: Lakshay <54310363+Lakshaymiddha@users.noreply.github.com>
Co-authored-by: samm <sam.mathis@temporal.io>
Co-authored-by: Quinn Klassen <klassenq@gmail.com>
Co-authored-by: Will Duan <xinw.duan@gmail.com>
Co-authored-by: Qian Chen <qyc5937@gmail.com>
Co-authored-by: Prathyush PV <prathyush.pv@temporal.io>
Co-authored-by: Sean Kane <sean.kane@temporal.io>
Co-authored-by: mavemuri <74267563+mavemuri@users.noreply.github.com>
Co-authored-by: Rodrigo Zhou <rodrigo.zhou@temporal.io>
Co-authored-by: Brian VanLoo <brian.vanloo@gmail.com>
Co-authored-by: akbala <akbala@gmail.com>
Co-authored-by: Dan Davison <dan.davison@temporal.io>
Co-authored-by: Chris Smith <chrsmith@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants