Skip to content

Replace hardcoded maxCalendarYear with modified spec validation#9396

Open
zivenrokhit wants to merge 17 commits intotemporalio:mainfrom
zivenrokhit:main
Open

Replace hardcoded maxCalendarYear with modified spec validation#9396
zivenrokhit wants to merge 17 commits intotemporalio:mainfrom
zivenrokhit:main

Conversation

@zivenrokhit
Copy link
Copy Markdown

@zivenrokhit zivenrokhit commented Feb 25, 2026

Decouple Spec Year Validation from Calendar Calculation Bound

Allow schedule specs to include years up to 9999, while keeping execution-time calendar search bounded at 2100 for safety.

What changed?

  • Added a separate spec validation limit: maxSpecYear = 9999.
  • Kept maxCalendarYear = 2100, but clarified it is only a calculation safety bound during next-time lookup.
  • Updated canonicalizeSpec and checkRanges to validate years against maxSpecYear instead of maxCalendarYear.
  • Added TestSpecFarFutureYear to verify that a far-future year (for example, 2150) is accepted in the spec but resolves to zero-time when computation exceeds the 2100 bound.

Why?

The previous approach used one limit for two different concerns:

  • What input we allow users to express.
  • How far the system is willing to search at runtime.

This change separates those concerns:

  • Validation is permissive (up to 9999) so users can define far-future schedules.
  • Execution remains protected (up to 2100) to avoid unbounded or expensive calendar calculations and reduce DoS risk.

Resolves #9383

How did you test it?

  • built
  • run locally and tested manually
  • covered by existing tests
  • added new unit test(s)

Potential risks

  • Users may assume that if a far-future year is accepted, it will also produce an executable next time immediately; beyond 2100 it will not, and may return zero-time.
  • The validation-vs-execution distinction may be non-obvious without clear documentation.
  • If real execution beyond 2100 is needed later, raising maxCalendarYear will require careful performance and safety evaluation.

Copilot AI review requested due to automatic review settings February 25, 2026 04:59
@zivenrokhit zivenrokhit requested review from a team as code owners February 25, 2026 04:59
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Feb 25, 2026

CLA assistant check
All committers have signed the CLA.

@zivenrokhit zivenrokhit changed the title Replace hardcoded maxCalendarYear with dynamic config setting (worker.schedulerMaxCalendarYear, default 2100) and update all relevant tests (#9383) Replace hardcoded maxCalendarYear with dynamic config setting (worker.schedulerMaxCalendarYear, default 2100) and update all relevant tests Feb 25, 2026
@zivenrokhit zivenrokhit changed the title Replace hardcoded maxCalendarYear with dynamic config setting (worker.schedulerMaxCalendarYear, default 2100) and update all relevant tests Replace hardcoded maxCalendarYear with dynamic config Feb 25, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR makes the maxCalendarYear for schedules configurable via dynamic config instead of being hardcoded to 2100. The change addresses customer needs for scheduling workflows beyond 2100 (e.g., for payment systems).

Changes:

  • Added SchedulerMaxCalendarYear dynamic config setting (default 2100) in common/dynamicconfig/constants.go
  • Threaded maxCalendarYear parameter through SpecBuilder, CompiledSpec, compiledCalendar, and all parsing/validation functions
  • Wired dynamic config into the chasm scheduler FX module for injection at startup
  • Updated all tests to explicitly pass defaultMaxCalendarYear or 2100 as appropriate

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
common/dynamicconfig/constants.go Added new global int setting SchedulerMaxCalendarYear with default value 2100
service/worker/scheduler/calendar.go Renamed constant to defaultMaxCalendarYear, added parameter to functions, threaded through compiledCalendar
service/worker/scheduler/spec.go Added maxCalendarYear field to structs and threaded parameter through all spec processing functions
service/worker/scheduler/workflow.go Updated SchedulerWorkflow to use defaultMaxCalendarYear (for tests/replays)
service/worker/scheduler/spec_test.go Updated all test calls to pass defaultMaxCalendarYear
service/worker/scheduler/calendar_test.go Updated all test calls to pass defaultMaxCalendarYear
service/frontend/workflow_handler_test.go Updated test to pass explicit value 2100 to NewSpecBuilder
chasm/lib/scheduler/fx.go Added newSpecBuilder function that reads dynamic config value and creates SpecBuilder
chasm/lib/scheduler/spec_processor_test.go Updated test to pass explicit value 2100 to NewSpecBuilder
chasm/lib/scheduler/helper_test.go Updated tests to pass explicit value 2100 to NewSpecBuilder
chasm/lib/scheduler/handler_test.go Updated test to pass explicit value 2100 to NewSpecBuilder
chasm/lib/scheduler/generator_tasks_test.go Updated test to pass explicit value 2100 to NewSpecBuilder

Comment thread chasm/lib/scheduler/fx.go Outdated
Copy link
Copy Markdown
Contributor

@dnr dnr left a comment

Choose a reason for hiding this comment

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

I don't think this is a good solution (and the suggestion in the original bug report is misguided):

There's no problem with limiting actual execution of schedules to > 50 years in the future: the server will be updated and redeployed many times before that. The only real problem is that the execution limit is used in spec validation, when there's no need for that at all. That is, specs should be allowed to reference years farther in the future if they want. The matching times won't be calculated that far in the future (this is DoS protection, basically), but as soon as an updated server is deployed, they will be.

The fix should be to just stop imposing a maximum year validation on specs.

- Allow specs to reference years up to 9999
- Maintain DoS protection in iterator at year 2100
- Update tests to verify far-future years work
- Remove obsolete year 2112 validation error test
@zivenrokhit
Copy link
Copy Markdown
Author

Thanks for the feedback, I see what you are saying. I've scrapped the initial solution and relaxed the spec validation to allow for years beyond 2100, while keeping the match time DoS protection in place. I just want to highlight one possible edge case, if a user creates a schedule where the very next run isn't until after 2100, I believe the iterator will hit the DoS limit, fail to find a match, and immediately close the schedule as completed. Are we okay with this?

@chaptersix
Copy link
Copy Markdown
Contributor

@zivenrokhit could you update the title and description?

@zivenrokhit zivenrokhit requested a review from a team as a code owner April 20, 2026 15:59
@zivenrokhit zivenrokhit changed the title Replace hardcoded maxCalendarYear with dynamic config Replace hardcoded maxCalendarYear with modified spec validation Apr 20, 2026
@zivenrokhit
Copy link
Copy Markdown
Author

@zivenrokhit could you update the title and description?

Done

@chaptersix
Copy link
Copy Markdown
Contributor

Please add functional tests covering the RPCs impacted by relaxing spec validation beyond year 2100:

1. ListScheduleMatchingTimes with a far-future end time

Create a weekly schedule, then call ListScheduleMatchingTimes with a start time of 2026-01-01 and an end time well beyond maxCalendarYear. Verify the RPC succeeds, returned times are correct (day of week, within maxCalendarYear), and the result count hits maxListMatchingTimesCount.

2. DescribeSchedule with a far-future schedule

Create a schedule with a spec that spans beyond maxCalendarYear, then call DescribeSchedule. Verify FutureActionTimes is populated and all entries are within maxCalendarYear.

3. DescribeSchedule with a start year entirely beyond maxCalendarYear

Create a schedule where all action times are beyond the calculation bound (e.g. Year: "2150"). Verify CreateSchedule succeeds but DescribeSchedule returns empty FutureActionTimes.

4. UpdateSchedule to a far-future spec

Create a schedule with a simple spec, then update it to a calendar spec referencing years beyond maxCalendarYear. Use Calendar string form so defaults are auto-filled. Verify the update succeeds and DescribeSchedule reflects the change with FutureActionTimes within maxCalendarYear. Note: for V1, the update is async so wrap describe assertions in Eventually.

Place these on scheduleFunctionalSuiteBase so they exercise both V1 and CHASM paths.

Unit tests to add in spec_test.go:

  • Validate the maxSpecYear boundary: a spec with Year: "9999" should be accepted by canonicalizeSpec, while Year: "10000" should be rejected.
  • A spec that straddles MaxCalendarYear: e.g. Year: "2098-2150" should return action times for 2098, 2099, 2100 and then stop (zero time after that).
  • A spec spanning the full allowed range: Year: "2000-9999" (minCalendarYear to maxSpecYear) should be accepted, return action times starting from 2000, and stop computing at MaxCalendarYear.

Add tests covering the relaxed spec validation that allows years beyond
2100. Exports MaxCalendarYear and MaxListMatchingTimesCount constants
for use in test assertions.

Unit tests (spec_test.go):
- TestSpecMaxSpecYearBoundary: year 9999 accepted, 10000 rejected
- TestSpecYearStraddlesMaxCalendarYear: year range 2098-2150 computes
  through 2100 then stops
- TestSpecFullYearRange: year range 2000-9999 accepted, computation
  stops at MaxCalendarYear

Functional tests (schedule_test.go):
- testListScheduleMatchingTimesWeeklyFarFuture: weekly schedule queried
  over far-future range, results capped at MaxListMatchingTimesCount
- testDescribeScheduleFarFutureActionTimes: FutureActionTimes within
  MaxCalendarYear bound
- testDescribeScheduleBeyondMaxCalendarYear: year 2150 schedule returns
  empty FutureActionTimes
- testUpdateScheduleFarFutureSpec: update to far-future year range
  succeeds with FutureActionTimes within bound
@chaptersix chaptersix requested a review from a team as a code owner April 21, 2026 15:59
@chaptersix
Copy link
Copy Markdown
Contributor

Please add functional tests covering the RPCs impacted by relaxing spec validation beyond year 2100:

1. ListScheduleMatchingTimes with a far-future end time

Create a weekly schedule, then call ListScheduleMatchingTimes with a start time of 2026-01-01 and an end time well beyond maxCalendarYear. Verify the RPC succeeds, returned times are correct (day of week, within maxCalendarYear), and the result count hits maxListMatchingTimesCount.

2. DescribeSchedule with a far-future schedule

Create a schedule with a spec that spans beyond maxCalendarYear, then call DescribeSchedule. Verify FutureActionTimes is populated and all entries are within maxCalendarYear.

3. DescribeSchedule with a start year entirely beyond maxCalendarYear

Create a schedule where all action times are beyond the calculation bound (e.g. Year: "2150"). Verify CreateSchedule succeeds but DescribeSchedule returns empty FutureActionTimes.

4. UpdateSchedule to a far-future spec

Create a schedule with a simple spec, then update it to a calendar spec referencing years beyond maxCalendarYear. Use Calendar string form so defaults are auto-filled. Verify the update succeeds and DescribeSchedule reflects the change with FutureActionTimes within maxCalendarYear. Note: for V1, the update is async so wrap describe assertions in Eventually.

Place these on scheduleFunctionalSuiteBase so they exercise both V1 and CHASM paths.

Unit tests to add in spec_test.go:

  • Validate the maxSpecYear boundary: a spec with Year: "9999" should be accepted by canonicalizeSpec, while Year: "10000" should be rejected.
  • A spec that straddles MaxCalendarYear: e.g. Year: "2098-2150" should return action times for 2098, 2099, 2100 and then stop (zero time after that).
  • A spec spanning the full allowed range: Year: "2000-9999" (minCalendarYear to maxSpecYear) should be accepted, return action times starting from 2000, and stop computing at MaxCalendarYear.

done

Copy link
Copy Markdown
Contributor

@lina-temporal lina-temporal left a comment

Choose a reason for hiding this comment

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

some small comments on tests, otherwise LGTM. Let's get @dnr's stamp as well.

Comment thread service/worker/scheduler/spec_test.go Outdated
Comment thread tests/schedule_test.go Outdated
// testDescribeScheduleFarFutureActionTimes verifies that DescribeSchedule returns
// FutureActionTimes within the MaxCalendarYear bound for a schedule that has no
// end date and will run indefinitely.
func testDescribeScheduleFarFutureActionTimes(t *testing.T, newContext contextFactory) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't understand how this test is showing the "far future" (it doesn't appear to be really testing anything other than the immediate future), or really how it'd differentiate itself from any of the existing tests that validate FutureActionTimes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Added TestDescribeScheduleFutureActionTimesAtBoundary that should test what I was aiming at.

Comment thread tests/schedule_test.go Outdated
Comment thread tests/schedule_test.go Outdated
Remove redundant TestSpecYearStraddlesMaxCalendarYear (covered by
TestSpecFullYearRange). Rework testDescribeScheduleFarFutureActionTimes
into testDescribeScheduleFutureActionTimesAtBoundary that actually
validates the MaxCalendarYear boundary. Replace hardcoded years with
scheduler.MaxCalendarYear-relative expressions. Clean up unnecessary
comments.
@chaptersix chaptersix requested a review from dnr April 22, 2026 15:34
Comment thread service/worker/scheduler/calendar.go Outdated
// can safely increase this limit.
MaxCalendarYear = 2100
// maxSpecYear is the largest year allowed in a schedule spec. This is set to a very
// large value to effectively remove the restriction while still preventing absurd values.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the wording "remove this restriction" only makes sense if you knew there was a restriction before.. word the comment so it make sense for readers reading the current state of the code

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

good call, I've reworded it so that it doesn't reference the old restriction

Comment thread tests/schedule_test.go Outdated
})
s.NoError(err)

s.Eventually(func() bool {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

use EventuallyWithT to make this more readable

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done

Comment on lines +33 to +36
// MaxCalendarYear is the upper limit for calendar date computation. Specs can reference
// years beyond this limit, but actual calculation will stop here. Future server versions
// can safely increase this limit.
MaxCalendarYear = 2100
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

did we consider something like, stopping the calendar search at ts + 100 years instead having a hardcoded limit? that might make things more predictable all over

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I see the point about a relative limit, but I think sticking with a fixed year is the better move here. It keeps the behavior deterministic and much simpler to test since the boundary is consistent for every call. A relative limit adds another moving part that could introduce edge cases or inconsistencies

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.

Support configuring maxCalendarYear for Schedules beyond 2100

6 participants