From 7324c2c553b738646f93b677fd17dd1d2bc5ea9a Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:27:45 -0500 Subject: [PATCH 1/7] test: guard against no-op dynamic config overrides in SharedServerSuite Standalone activities (activity.enableStandalone) now default to enabled in the pinned server version, so the explicit override in SharedServerSuite was a no-op; several other overrides had also become redundant. Remove them and add a regression test that starts a bare dev server and confirms standalone activities still work without any override. Also move the remaining overrides into a const list annotated with whether each one is test-only tuning or a feature flag forced on ahead of its server default, so future server bumps that flip a default are easy to catch and clean up. Fixes #1083 --- .../temporalcli/commands.activity_test.go | 29 +++++++ internal/temporalcli/commands_test.go | 85 +++++++++++++------ 2 files changed, 86 insertions(+), 28 deletions(-) diff --git a/internal/temporalcli/commands.activity_test.go b/internal/temporalcli/commands.activity_test.go index b4552d077..62c6c6668 100644 --- a/internal/temporalcli/commands.activity_test.go +++ b/internal/temporalcli/commands.activity_test.go @@ -957,6 +957,35 @@ func (s *SharedServerSuite) TestActivity_Start() { s.Equal("default", jsonOut["namespace"]) } +// TestActivity_Start_StandaloneEnabledByServerDefault guards against +// https://github.com/temporalio/cli/issues/1083: SharedServerSuite explicitly sets dynamic +// config overrides to enable server features under test, but some of those overrides can +// become no-ops as the pinned server version's defaults change over time. This starts a +// server with no dynamic config overrides at all and confirms standalone activities still +// work, proving "activity.enableStandalone" is enabled by the server's own default and does +// not need to be set explicitly in SharedServerSuite.SetupSuite. If a future server bump +// changes that default back to disabled, this test fails, signaling that the override needs +// to be restored there instead of silently relying on a default that no longer holds. +func (s *SharedServerSuite) TestActivity_Start_StandaloneEnabledByServerDefault() { + bare := StartDevServer(s.Suite.T(), DevServerOptions{}) + defer bare.Stop() + worker := bare.StartDevWorker(s.Suite.T(), DevWorkerOptions{}) + defer worker.Stop() + worker.OnDevActivity(func(ctx context.Context, a any) (any, error) { + return "no-op-check-result", nil + }) + + res := s.Execute( + "activity", "start", + "--activity-id", "no-op-check", + "--type", "DevActivity", + "--task-queue", worker.Options.TaskQueue, + "--start-to-close-timeout", "30s", + "--address", bare.Address(), + ) + s.NoError(res.Err) +} + func (s *SharedServerSuite) TestActivity_Start_With_Headers() { s.Worker().OnDevActivity(func(ctx context.Context, a any) (any, error) { return nil, nil diff --git a/internal/temporalcli/commands_test.go b/internal/temporalcli/commands_test.go index c0dc37ff4..08de488d8 100644 --- a/internal/temporalcli/commands_test.go +++ b/internal/temporalcli/commands_test.go @@ -229,39 +229,68 @@ type SharedServerSuite struct { lazyWorkerLock sync.Mutex } +// sharedServerDynamicConfigOverride is one dynamic config value SharedServerSuite forces on. +type sharedServerDynamicConfigOverride struct { + Key string + Value any + // TestOnly is true for values that only make sense in a test environment (relaxed rate + // limits, disabled caching, shortened timeouts) and would never become a server default. + // + // It is false for feature flags that are off by default in the pinned server version today. + // Those are prod feature previews we're forcing on for test coverage, and each one should + // have a companion test (see TestActivity_Start_StandaloneEnabledByServerDefault) that + // starts a bare dev server with no overrides and confirms the gated behavior is still + // disabled by default. When a server upgrade flips one of those defaults to enabled, that + // companion test fails, which is the signal to delete the override below (see + // https://github.com/temporalio/cli/issues/1083). + TestOnly bool +} + +var sharedServerDynamicConfigOverrides = []sharedServerDynamicConfigOverride{ + { + // Allow a high rate of change to namespaces, particularly for the task-queue command + // tests. + Key: "frontend.namespaceRPS.visibility", Value: 10000, TestOnly: true, + }, + { + // Required by TestWorkflow_Show_SystemNexusOperationTransformsTypeNames to schedule a + // SignalWithStartWorkflowExecution Nexus operation against the __temporal_system + // endpoint from inside a workflow. + Key: "history.enableSignalWithStartFromWorkflow", Value: true, TestOnly: false, + }, + { + Key: "activity.startDelayEnabled", Value: true, TestOnly: false, + }, + { + Key: "history.enableStandaloneActivityOperatorCommands", Value: true, TestOnly: false, + }, + { + Key: "activity.longPollTimeout", Value: 2 * time.Second, TestOnly: true, + }, + { + Key: "nexusoperation.enableStandalone", Value: true, TestOnly: false, + }, + { + // Disable DescribeTaskQueue cache while testing versioning behavior. + Key: "matching.TaskQueueInfoByBuildIdTTL", Value: 0 * time.Second, TestOnly: true, + }, + { + // Required by TestActivity_CancelTerminateDelete_* to enable batch operations on + // standalone activities. + Key: "frontend.enableBatchOperationsForStandaloneActivities", Value: true, TestOnly: false, + }, +} + func (s *SharedServerSuite) SetupSuite() { + dynamicConfigValues := make(map[string]any, len(sharedServerDynamicConfigOverrides)) + for _, o := range sharedServerDynamicConfigOverrides { + dynamicConfigValues[o.Key] = o.Value + } s.DevServer = StartDevServer(s.Suite.T(), DevServerOptions{ StartOptions: devserver.StartOptions{ // Enable for operator cluster commands EnableGlobalNamespace: true, - DynamicConfigValues: map[string]any{ - "frontend.enableUpdateWorkflowExecutionAsyncAccepted": true, - // Allow a high rate of change to namespaces, particularly - // for the task-queue command tests. - "frontend.namespaceRPS.visibility": 10000, - // Disable DescribeTaskQueue cache. - "frontend.activityAPIsEnabled": true, - "history.enableChasm": true, - // Required by TestWorkflow_Show_SystemNexusOperationTransformsTypeNames - // to schedule a SignalWithStartWorkflowExecution Nexus operation against - // the __temporal_system endpoint from inside a workflow. - "history.enableSignalWithStartFromWorkflow": true, - "activity.enableStandalone": true, - "activity.startDelayEnabled": true, - "history.enableStandaloneActivityOperatorCommands": true, - "activity.longPollTimeout": 2 * time.Second, - "nexusoperation.enableStandalone": true, - "history.enableChasmCallbacks": true, - // this is overridden since we don't want caching to be enabled - // while testing DescribeTaskQueue behaviour related to versioning - "matching.TaskQueueInfoByBuildIdTTL": 0 * time.Second, - // worker heartbeating - "frontend.WorkerHeartbeatsEnabled": true, - "frontend.ListWorkersEnabled": true, - // Required by TestActivity_CancelTerminateDelete_* - // to enable batch operations on standalone activities. - "frontend.enableBatchOperationsForStandaloneActivities": true, - }, + DynamicConfigValues: dynamicConfigValues, }, }) // Stop server if we fail later From 9728db6b43aa4e367592d88d00783ee491abf83b Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:36:33 -0500 Subject: [PATCH 2/7] test: extend dynamic config override cleanup to StartDevServer base overrides Fold the hardcoded overrides that StartDevServer applies to every dev server into the same dynamicConfigOverride/TestOnly scheme used by SharedServerSuite, and prune the ones now redundant with server defaults (frontend.workerVersioningWorkflowAPIs, system.enableDeploymentVersions, frontend.enableUpdateWorkflowExecution). Also fix StartDevServer unconditionally overwriting caller-supplied dynamic config values with its own defaults, which was silently shadowing SharedServerSuite's frontend.namespaceRPS.visibility override (10000) with its own lower value (100). It now only fills in keys the caller hasn't already set. --- internal/temporalcli/commands_test.go | 72 ++++++++++++++++++++------- 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/internal/temporalcli/commands_test.go b/internal/temporalcli/commands_test.go index 08de488d8..6581d139d 100644 --- a/internal/temporalcli/commands_test.go +++ b/internal/temporalcli/commands_test.go @@ -229,8 +229,10 @@ type SharedServerSuite struct { lazyWorkerLock sync.Mutex } -// sharedServerDynamicConfigOverride is one dynamic config value SharedServerSuite forces on. -type sharedServerDynamicConfigOverride struct { +// dynamicConfigOverride is one dynamic config value a test dev server forces on, either as part +// of every dev server started via StartDevServer (see baseDevServerDynamicConfigOverrides) or +// just the shared one used by SharedServerSuite (see sharedServerDynamicConfigOverrides). +type dynamicConfigOverride struct { Key string Value any // TestOnly is true for values that only make sense in a test environment (relaxed rate @@ -246,7 +248,47 @@ type sharedServerDynamicConfigOverride struct { TestOnly bool } -var sharedServerDynamicConfigOverrides = []sharedServerDynamicConfigOverride{ +func dynamicConfigValues(overrides []dynamicConfigOverride) map[string]any { + values := make(map[string]any, len(overrides)) + for _, o := range overrides { + values[o.Key] = o.Value + } + return values +} + +// baseDevServerDynamicConfigOverrides are forced on for every dev server started via +// StartDevServer, applied only where the caller hasn't already set that key (see StartDevServer). +var baseDevServerDynamicConfigOverrides = []dynamicConfigOverride{ + { + Key: "system.forceSearchAttributesCacheRefreshOnRead", Value: true, TestOnly: false, + }, + { + Key: "frontend.workerVersioningRuleAPIs", Value: true, TestOnly: false, + }, + { + Key: "frontend.workerVersioningDataAPIs", Value: true, TestOnly: false, + }, + { + Key: "system.enableDeployments", Value: true, TestOnly: false, + }, + { + Key: "worker.buildIdScavengerEnabled", Value: true, TestOnly: false, + }, + { + // Raise the default per-namespace concurrent/RPS limits so batch and namespace-heavy + // tests aren't rate limited. + Key: "frontend.MaxConcurrentBatchOperationPerNamespace", Value: 1000, TestOnly: true, + }, + { + Key: "frontend.namespaceRPS.visibility", Value: 100, TestOnly: true, + }, + { + // Shorten cluster metadata refresh so multi-cluster tests don't wait a full minute. + Key: "system.clusterMetadataRefreshInterval", Value: 100 * time.Millisecond, TestOnly: true, + }, +} + +var sharedServerDynamicConfigOverrides = []dynamicConfigOverride{ { // Allow a high rate of change to namespaces, particularly for the task-queue command // tests. @@ -282,15 +324,11 @@ var sharedServerDynamicConfigOverrides = []sharedServerDynamicConfigOverride{ } func (s *SharedServerSuite) SetupSuite() { - dynamicConfigValues := make(map[string]any, len(sharedServerDynamicConfigOverrides)) - for _, o := range sharedServerDynamicConfigOverrides { - dynamicConfigValues[o.Key] = o.Value - } s.DevServer = StartDevServer(s.Suite.T(), DevServerOptions{ StartOptions: devserver.StartOptions{ // Enable for operator cluster commands EnableGlobalNamespace: true, - DynamicConfigValues: dynamicConfigValues, + DynamicConfigValues: dynamicConfigValues(sharedServerDynamicConfigOverrides), }, }) // Stop server if we fail later @@ -434,17 +472,13 @@ func StartDevServer(t *testing.T, options DevServerOptions) *DevServer { if d.Options.DynamicConfigValues == nil { d.Options.DynamicConfigValues = map[string]any{} } - d.Options.DynamicConfigValues["system.forceSearchAttributesCacheRefreshOnRead"] = true - d.Options.DynamicConfigValues["frontend.workerVersioningRuleAPIs"] = true - d.Options.DynamicConfigValues["frontend.workerVersioningDataAPIs"] = true - d.Options.DynamicConfigValues["frontend.workerVersioningWorkflowAPIs"] = true - d.Options.DynamicConfigValues["system.enableDeployments"] = true - d.Options.DynamicConfigValues["system.enableDeploymentVersions"] = true - d.Options.DynamicConfigValues["worker.buildIdScavengerEnabled"] = true - d.Options.DynamicConfigValues["frontend.enableUpdateWorkflowExecution"] = true - d.Options.DynamicConfigValues["frontend.MaxConcurrentBatchOperationPerNamespace"] = 1000 - d.Options.DynamicConfigValues["frontend.namespaceRPS.visibility"] = 100 - d.Options.DynamicConfigValues["system.clusterMetadataRefreshInterval"] = 100 * time.Millisecond + // Only fill in keys the caller hasn't already set, so e.g. SharedServerSuite's own + // dynamic config values take precedence over these defaults. + for _, o := range baseDevServerDynamicConfigOverrides { + if _, ok := d.Options.DynamicConfigValues[o.Key]; !ok { + d.Options.DynamicConfigValues[o.Key] = o.Value + } + } d.Options.GRPCInterceptors = append( d.Options.GRPCInterceptors, From d672d8b7e3243a6347f9e73ff349fe93be3a4b70 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:49:00 -0500 Subject: [PATCH 3/7] fix: remove no-op CHASM/SAA dynamic config overrides in dev server dynamicconfig.EnableChasm and activity.Enabled (SAA) are on by default as of server v1.32, matching the pinned go.temporal.io/server version, so the overrides forcing them on in devserver.Server (the 'temporal server start-dev' code path) were already no-ops. Remove them and update the stale comment. Also switch the test dynamic config override lists from raw string keys to the actual exported dynamicconfig.GenericSetting values from go.temporal.io/server (e.g. dynamicconfig.EnableSignalWithStartFromWorkflow, serveractivity.StartDelayEnabled, servernexusoperation.Enabled), so a key that gets renamed or removed upstream is a compile error instead of a silently no-op override. --- internal/devserver/server.go | 6 ++-- internal/temporalcli/commands_test.go | 50 ++++++++++++++++----------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/internal/devserver/server.go b/internal/devserver/server.go index 09d47854f..ebbd70162 100644 --- a/internal/devserver/server.go +++ b/internal/devserver/server.go @@ -243,10 +243,8 @@ func (s *StartOptions) buildServerOptions() ([]temporal.ServerOption, *slog.Leve // Up default visibility RPS dynConf[dynamicconfig.FrontendMaxNamespaceVisibilityRPSPerInstance.Key()] = 100 - // Enable CHASM and SAA. These will be on by default in server v1.32, at which point these lines - // should be removed. - dynConf[dynamicconfig.EnableChasm.Key()] = true - dynConf[activity.Enabled.Key()] = true + // CHASM (dynamicconfig.EnableChasm) and SAA (activity.Enabled) are on by default as of + // server v1.32, so they no longer need to be forced on here. dynConf[activity.EnableStandaloneActivityOperatorCommands.Key()] = true dynConf[dynamicconfig.FrontendEnableBatchOperationsForStandaloneActivities.Key()] = true diff --git a/internal/temporalcli/commands_test.go b/internal/temporalcli/commands_test.go index 6581d139d..9097a2fea 100644 --- a/internal/temporalcli/commands_test.go +++ b/internal/temporalcli/commands_test.go @@ -26,6 +26,9 @@ import ( "go.temporal.io/sdk/temporal" "go.temporal.io/sdk/worker" "go.temporal.io/sdk/workflow" + serveractivity "go.temporal.io/server/chasm/lib/activity" + servernexusoperation "go.temporal.io/server/chasm/lib/nexusoperation" + "go.temporal.io/server/common/dynamicconfig" "google.golang.org/grpc" ) @@ -232,9 +235,13 @@ type SharedServerSuite struct { // dynamicConfigOverride is one dynamic config value a test dev server forces on, either as part // of every dev server started via StartDevServer (see baseDevServerDynamicConfigOverrides) or // just the shared one used by SharedServerSuite (see sharedServerDynamicConfigOverrides). +// +// Setting references the real dynamicconfig.GenericSetting from go.temporal.io/server (e.g. +// dynamicconfig.EnableChasm, serveractivity.Enabled) rather than a raw string key, so a key that +// gets renamed or removed upstream is a compile error here instead of a silently no-op override. type dynamicConfigOverride struct { - Key string - Value any + Setting dynamicconfig.GenericSetting + Value any // TestOnly is true for values that only make sense in a test environment (relaxed rate // limits, disabled caching, shortened timeouts) and would never become a server default. // @@ -251,7 +258,7 @@ type dynamicConfigOverride struct { func dynamicConfigValues(overrides []dynamicConfigOverride) map[string]any { values := make(map[string]any, len(overrides)) for _, o := range overrides { - values[o.Key] = o.Value + values[o.Setting.Key().String()] = o.Value } return values } @@ -260,31 +267,31 @@ func dynamicConfigValues(overrides []dynamicConfigOverride) map[string]any { // StartDevServer, applied only where the caller hasn't already set that key (see StartDevServer). var baseDevServerDynamicConfigOverrides = []dynamicConfigOverride{ { - Key: "system.forceSearchAttributesCacheRefreshOnRead", Value: true, TestOnly: false, + Setting: dynamicconfig.ForceSearchAttributesCacheRefreshOnRead, Value: true, TestOnly: false, }, { - Key: "frontend.workerVersioningRuleAPIs", Value: true, TestOnly: false, + Setting: dynamicconfig.FrontendEnableWorkerVersioningRuleAPIs, Value: true, TestOnly: false, }, { - Key: "frontend.workerVersioningDataAPIs", Value: true, TestOnly: false, + Setting: dynamicconfig.FrontendEnableWorkerVersioningDataAPIs, Value: true, TestOnly: false, }, { - Key: "system.enableDeployments", Value: true, TestOnly: false, + Setting: dynamicconfig.EnableDeployments, Value: true, TestOnly: false, }, { - Key: "worker.buildIdScavengerEnabled", Value: true, TestOnly: false, + Setting: dynamicconfig.BuildIdScavengerEnabled, Value: true, TestOnly: false, }, { // Raise the default per-namespace concurrent/RPS limits so batch and namespace-heavy // tests aren't rate limited. - Key: "frontend.MaxConcurrentBatchOperationPerNamespace", Value: 1000, TestOnly: true, + Setting: dynamicconfig.FrontendMaxConcurrentBatchOperationPerNamespace, Value: 1000, TestOnly: true, }, { - Key: "frontend.namespaceRPS.visibility", Value: 100, TestOnly: true, + Setting: dynamicconfig.FrontendMaxNamespaceVisibilityRPSPerInstance, Value: 100, TestOnly: true, }, { // Shorten cluster metadata refresh so multi-cluster tests don't wait a full minute. - Key: "system.clusterMetadataRefreshInterval", Value: 100 * time.Millisecond, TestOnly: true, + Setting: dynamicconfig.ClusterMetadataRefreshInterval, Value: 100 * time.Millisecond, TestOnly: true, }, } @@ -292,34 +299,34 @@ var sharedServerDynamicConfigOverrides = []dynamicConfigOverride{ { // Allow a high rate of change to namespaces, particularly for the task-queue command // tests. - Key: "frontend.namespaceRPS.visibility", Value: 10000, TestOnly: true, + Setting: dynamicconfig.FrontendMaxNamespaceVisibilityRPSPerInstance, Value: 10000, TestOnly: true, }, { // Required by TestWorkflow_Show_SystemNexusOperationTransformsTypeNames to schedule a // SignalWithStartWorkflowExecution Nexus operation against the __temporal_system // endpoint from inside a workflow. - Key: "history.enableSignalWithStartFromWorkflow", Value: true, TestOnly: false, + Setting: dynamicconfig.EnableSignalWithStartFromWorkflow, Value: true, TestOnly: false, }, { - Key: "activity.startDelayEnabled", Value: true, TestOnly: false, + Setting: serveractivity.StartDelayEnabled, Value: true, TestOnly: false, }, { - Key: "history.enableStandaloneActivityOperatorCommands", Value: true, TestOnly: false, + Setting: serveractivity.EnableStandaloneActivityOperatorCommands, Value: true, TestOnly: false, }, { - Key: "activity.longPollTimeout", Value: 2 * time.Second, TestOnly: true, + Setting: serveractivity.LongPollTimeout, Value: 2 * time.Second, TestOnly: true, }, { - Key: "nexusoperation.enableStandalone", Value: true, TestOnly: false, + Setting: servernexusoperation.Enabled, Value: true, TestOnly: false, }, { // Disable DescribeTaskQueue cache while testing versioning behavior. - Key: "matching.TaskQueueInfoByBuildIdTTL", Value: 0 * time.Second, TestOnly: true, + Setting: dynamicconfig.TaskQueueInfoByBuildIdTTL, Value: 0 * time.Second, TestOnly: true, }, { // Required by TestActivity_CancelTerminateDelete_* to enable batch operations on // standalone activities. - Key: "frontend.enableBatchOperationsForStandaloneActivities", Value: true, TestOnly: false, + Setting: dynamicconfig.FrontendEnableBatchOperationsForStandaloneActivities, Value: true, TestOnly: false, }, } @@ -475,8 +482,9 @@ func StartDevServer(t *testing.T, options DevServerOptions) *DevServer { // Only fill in keys the caller hasn't already set, so e.g. SharedServerSuite's own // dynamic config values take precedence over these defaults. for _, o := range baseDevServerDynamicConfigOverrides { - if _, ok := d.Options.DynamicConfigValues[o.Key]; !ok { - d.Options.DynamicConfigValues[o.Key] = o.Value + key := o.Setting.Key().String() + if _, ok := d.Options.DynamicConfigValues[key]; !ok { + d.Options.DynamicConfigValues[key] = o.Value } } From 814738f5305a2cfef856f9a3bbf85c8054ac66ef Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:17:56 -0500 Subject: [PATCH 4/7] test: add instant guard for dynamic-config-override defaults TestDynamicConfigOverridesMatchServerDefaults reads each non-test-only override's real default straight from go.temporal.io/server via dynamicconfig.NewNoopCollection(), with no dev server needed. It fails the moment a server bump changes the assumption an override relies on: either a false-by-default flag we force to true now defaulting true (the override became a no-op and should be removed), or a flag we stopped forcing on because it defaults to true (dynamicconfig.EnableChasm, serveractivity.Enabled) reverting to false (the override needs to come back, in both the test lists and internal/devserver/server.go). This closes the gap on activity.EnableStandaloneActivityOperatorCommands and FrontendEnableBatchOperationsForStandaloneActivities, which had no guard at all before this. --- internal/temporalcli/commands_test.go | 97 +++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 5 deletions(-) diff --git a/internal/temporalcli/commands_test.go b/internal/temporalcli/commands_test.go index 9097a2fea..a9859ffbe 100644 --- a/internal/temporalcli/commands_test.go +++ b/internal/temporalcli/commands_test.go @@ -246,13 +246,16 @@ type dynamicConfigOverride struct { // limits, disabled caching, shortened timeouts) and would never become a server default. // // It is false for feature flags that are off by default in the pinned server version today. - // Those are prod feature previews we're forcing on for test coverage, and each one should - // have a companion test (see TestActivity_Start_StandaloneEnabledByServerDefault) that - // starts a bare dev server with no overrides and confirms the gated behavior is still - // disabled by default. When a server upgrade flips one of those defaults to enabled, that - // companion test fails, which is the signal to delete the override below (see + // Those are prod feature previews we're forcing on for test coverage, and each one must set + // Default below. TestDynamicConfigOverridesMatchServerDefaults asserts Default() still + // disagrees with Value; when a server upgrade flips one of those defaults to match Value, + // that assertion fails, which is the signal to delete the override (see // https://github.com/temporalio/cli/issues/1083). TestOnly bool + // Default returns this setting's current value straight from go.temporal.io/server with no + // overrides applied (see dynamicconfig.NewNoopCollection), i.e. the server's own default. + // Required when TestOnly is false; unused otherwise. + Default func(dc *dynamicconfig.Collection) bool } func dynamicConfigValues(overrides []dynamicConfigOverride) map[string]any { @@ -268,18 +271,33 @@ func dynamicConfigValues(overrides []dynamicConfigOverride) map[string]any { var baseDevServerDynamicConfigOverrides = []dynamicConfigOverride{ { Setting: dynamicconfig.ForceSearchAttributesCacheRefreshOnRead, Value: true, TestOnly: false, + Default: func(dc *dynamicconfig.Collection) bool { + return dynamicconfig.ForceSearchAttributesCacheRefreshOnRead.Get(dc)() + }, }, { Setting: dynamicconfig.FrontendEnableWorkerVersioningRuleAPIs, Value: true, TestOnly: false, + Default: func(dc *dynamicconfig.Collection) bool { + return dynamicconfig.FrontendEnableWorkerVersioningRuleAPIs.Get(dc)("default") + }, }, { Setting: dynamicconfig.FrontendEnableWorkerVersioningDataAPIs, Value: true, TestOnly: false, + Default: func(dc *dynamicconfig.Collection) bool { + return dynamicconfig.FrontendEnableWorkerVersioningDataAPIs.Get(dc)("default") + }, }, { Setting: dynamicconfig.EnableDeployments, Value: true, TestOnly: false, + Default: func(dc *dynamicconfig.Collection) bool { + return dynamicconfig.EnableDeployments.Get(dc)("default") + }, }, { Setting: dynamicconfig.BuildIdScavengerEnabled, Value: true, TestOnly: false, + Default: func(dc *dynamicconfig.Collection) bool { + return dynamicconfig.BuildIdScavengerEnabled.Get(dc)() + }, }, { // Raise the default per-namespace concurrent/RPS limits so batch and namespace-heavy @@ -306,18 +324,30 @@ var sharedServerDynamicConfigOverrides = []dynamicConfigOverride{ // SignalWithStartWorkflowExecution Nexus operation against the __temporal_system // endpoint from inside a workflow. Setting: dynamicconfig.EnableSignalWithStartFromWorkflow, Value: true, TestOnly: false, + Default: func(dc *dynamicconfig.Collection) bool { + return dynamicconfig.EnableSignalWithStartFromWorkflow.Get(dc)("default") + }, }, { Setting: serveractivity.StartDelayEnabled, Value: true, TestOnly: false, + Default: func(dc *dynamicconfig.Collection) bool { + return serveractivity.StartDelayEnabled.Get(dc)("default") + }, }, { Setting: serveractivity.EnableStandaloneActivityOperatorCommands, Value: true, TestOnly: false, + Default: func(dc *dynamicconfig.Collection) bool { + return serveractivity.EnableStandaloneActivityOperatorCommands.Get(dc)("default") + }, }, { Setting: serveractivity.LongPollTimeout, Value: 2 * time.Second, TestOnly: true, }, { Setting: servernexusoperation.Enabled, Value: true, TestOnly: false, + Default: func(dc *dynamicconfig.Collection) bool { + return servernexusoperation.Enabled.Get(dc)("default") + }, }, { // Disable DescribeTaskQueue cache while testing versioning behavior. @@ -327,9 +357,66 @@ var sharedServerDynamicConfigOverrides = []dynamicConfigOverride{ // Required by TestActivity_CancelTerminateDelete_* to enable batch operations on // standalone activities. Setting: dynamicconfig.FrontendEnableBatchOperationsForStandaloneActivities, Value: true, TestOnly: false, + Default: func(dc *dynamicconfig.Collection) bool { + return dynamicconfig.FrontendEnableBatchOperationsForStandaloneActivities.Get(dc)("default") + }, }, } +// dynamicConfigDefaultsAssumedEnabled are settings SharedServerSuite and StartDevServer no +// longer force on because the pinned go.temporal.io/server version already defaults them to +// true (see internal/devserver/server.go's comment on dynConf). If a server downgrade or a +// setting change ever flips one back to false, TestDynamicConfigOverridesMatchServerDefaults +// fails, signaling the override needs to be restored in both places. +var dynamicConfigDefaultsAssumedEnabled = []dynamicConfigOverride{ + { + Setting: dynamicconfig.EnableChasm, + Default: func(dc *dynamicconfig.Collection) bool { return dynamicconfig.EnableChasm.Get(dc)("default") }, + }, + { + Setting: serveractivity.Enabled, + Default: func(dc *dynamicconfig.Collection) bool { return serveractivity.Enabled.Get(dc)("default") }, + }, +} + +// TestDynamicConfigOverridesMatchServerDefaults guards against +// https://github.com/temporalio/cli/issues/1083: each non-test-only entry in +// baseDevServerDynamicConfigOverrides and sharedServerDynamicConfigOverrides forces on a server +// feature flag that's assumed to default to false in the pinned go.temporal.io/server version; +// each entry in dynamicConfigDefaultsAssumedEnabled is assumed to default to true. This reads +// those defaults directly from go.temporal.io/server with no dynamic config client and no dev +// server needed (see dynamicconfig.NewNoopCollection), so it runs instantly and fails the moment +// a server bump changes one of those assumptions — the signal to update the corresponding +// override list and internal/devserver/server.go. +func TestDynamicConfigOverridesMatchServerDefaults(t *testing.T) { + dc := dynamicconfig.NewNoopCollection() + for _, o := range slices.Concat(baseDevServerDynamicConfigOverrides, sharedServerDynamicConfigOverrides) { + if o.TestOnly { + continue + } + o := o + t.Run(o.Setting.Key().String(), func(t *testing.T) { + require.NotNilf(t, o.Default, "non-test-only override %q must set Default", o.Setting.Key().String()) + wantOverrideValue, ok := o.Value.(bool) + require.Truef(t, ok, "non-test-only override %q must be boolean, got %T", o.Setting.Key().String(), o.Value) + gotDefault := o.Default(dc) + require.NotEqualf(t, wantOverrideValue, gotDefault, + "%q now defaults to %v in the pinned server version, same as the override value — "+ + "this override is a no-op and should be removed", + o.Setting.Key().String(), gotDefault) + }) + } + for _, o := range dynamicConfigDefaultsAssumedEnabled { + o := o + t.Run(o.Setting.Key().String(), func(t *testing.T) { + require.Truef(t, o.Default(dc), + "%q no longer defaults to true in the pinned server version — "+ + "restore an explicit override in SharedServerSuite/StartDevServer and internal/devserver/server.go", + o.Setting.Key().String()) + }) + } +} + func (s *SharedServerSuite) SetupSuite() { s.DevServer = StartDevServer(s.Suite.T(), DevServerOptions{ StartOptions: devserver.StartOptions{ From 6dd98c89f30931f023f37b3dc9e572641b7c04bf Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:35:37 -0500 Subject: [PATCH 5/7] test: simplify dynamic config override tracking --- .../temporalcli/commands.activity_test.go | 29 -- internal/temporalcli/commands_test.go | 254 +++++------------- 2 files changed, 68 insertions(+), 215 deletions(-) diff --git a/internal/temporalcli/commands.activity_test.go b/internal/temporalcli/commands.activity_test.go index 62c6c6668..b4552d077 100644 --- a/internal/temporalcli/commands.activity_test.go +++ b/internal/temporalcli/commands.activity_test.go @@ -957,35 +957,6 @@ func (s *SharedServerSuite) TestActivity_Start() { s.Equal("default", jsonOut["namespace"]) } -// TestActivity_Start_StandaloneEnabledByServerDefault guards against -// https://github.com/temporalio/cli/issues/1083: SharedServerSuite explicitly sets dynamic -// config overrides to enable server features under test, but some of those overrides can -// become no-ops as the pinned server version's defaults change over time. This starts a -// server with no dynamic config overrides at all and confirms standalone activities still -// work, proving "activity.enableStandalone" is enabled by the server's own default and does -// not need to be set explicitly in SharedServerSuite.SetupSuite. If a future server bump -// changes that default back to disabled, this test fails, signaling that the override needs -// to be restored there instead of silently relying on a default that no longer holds. -func (s *SharedServerSuite) TestActivity_Start_StandaloneEnabledByServerDefault() { - bare := StartDevServer(s.Suite.T(), DevServerOptions{}) - defer bare.Stop() - worker := bare.StartDevWorker(s.Suite.T(), DevWorkerOptions{}) - defer worker.Stop() - worker.OnDevActivity(func(ctx context.Context, a any) (any, error) { - return "no-op-check-result", nil - }) - - res := s.Execute( - "activity", "start", - "--activity-id", "no-op-check", - "--type", "DevActivity", - "--task-queue", worker.Options.TaskQueue, - "--start-to-close-timeout", "30s", - "--address", bare.Address(), - ) - s.NoError(res.Err) -} - func (s *SharedServerSuite) TestActivity_Start_With_Headers() { s.Worker().OnDevActivity(func(ctx context.Context, a any) (any, error) { return nil, nil diff --git a/internal/temporalcli/commands_test.go b/internal/temporalcli/commands_test.go index a9859ffbe..3e8e3a056 100644 --- a/internal/temporalcli/commands_test.go +++ b/internal/temporalcli/commands_test.go @@ -232,197 +232,82 @@ type SharedServerSuite struct { lazyWorkerLock sync.Mutex } -// dynamicConfigOverride is one dynamic config value a test dev server forces on, either as part -// of every dev server started via StartDevServer (see baseDevServerDynamicConfigOverrides) or -// just the shared one used by SharedServerSuite (see sharedServerDynamicConfigOverrides). -// -// Setting references the real dynamicconfig.GenericSetting from go.temporal.io/server (e.g. -// dynamicconfig.EnableChasm, serveractivity.Enabled) rather than a raw string key, so a key that -// gets renamed or removed upstream is a compile error here instead of a silently no-op override. -type dynamicConfigOverride struct { - Setting dynamicconfig.GenericSetting - Value any - // TestOnly is true for values that only make sense in a test environment (relaxed rate - // limits, disabled caching, shortened timeouts) and would never become a server default. - // - // It is false for feature flags that are off by default in the pinned server version today. - // Those are prod feature previews we're forcing on for test coverage, and each one must set - // Default below. TestDynamicConfigOverridesMatchServerDefaults asserts Default() still - // disagrees with Value; when a server upgrade flips one of those defaults to match Value, - // that assertion fails, which is the signal to delete the override (see - // https://github.com/temporalio/cli/issues/1083). - TestOnly bool - // Default returns this setting's current value straight from go.temporal.io/server with no - // overrides applied (see dynamicconfig.NewNoopCollection), i.e. the server's own default. - // Required when TestOnly is false; unused otherwise. - Default func(dc *dynamicconfig.Collection) bool -} - -func dynamicConfigValues(overrides []dynamicConfigOverride) map[string]any { - values := make(map[string]any, len(overrides)) - for _, o := range overrides { - values[o.Setting.Key().String()] = o.Value - } - return values -} - -// baseDevServerDynamicConfigOverrides are forced on for every dev server started via -// StartDevServer, applied only where the caller hasn't already set that key (see StartDevServer). -var baseDevServerDynamicConfigOverrides = []dynamicConfigOverride{ - { - Setting: dynamicconfig.ForceSearchAttributesCacheRefreshOnRead, Value: true, TestOnly: false, - Default: func(dc *dynamicconfig.Collection) bool { - return dynamicconfig.ForceSearchAttributesCacheRefreshOnRead.Get(dc)() +type featureOverride struct { + key string + enabled bool + defaultEnabled func(*dynamicconfig.Collection) bool +} + +func overrideGlobalSetting(setting dynamicconfig.GlobalBoolSetting, enabled bool) featureOverride { + return featureOverride{ + key: setting.Key().String(), + enabled: enabled, + defaultEnabled: func(dc *dynamicconfig.Collection) bool { + return setting.Get(dc)() }, - }, - { - Setting: dynamicconfig.FrontendEnableWorkerVersioningRuleAPIs, Value: true, TestOnly: false, - Default: func(dc *dynamicconfig.Collection) bool { - return dynamicconfig.FrontendEnableWorkerVersioningRuleAPIs.Get(dc)("default") - }, - }, - { - Setting: dynamicconfig.FrontendEnableWorkerVersioningDataAPIs, Value: true, TestOnly: false, - Default: func(dc *dynamicconfig.Collection) bool { - return dynamicconfig.FrontendEnableWorkerVersioningDataAPIs.Get(dc)("default") - }, - }, - { - Setting: dynamicconfig.EnableDeployments, Value: true, TestOnly: false, - Default: func(dc *dynamicconfig.Collection) bool { - return dynamicconfig.EnableDeployments.Get(dc)("default") - }, - }, - { - Setting: dynamicconfig.BuildIdScavengerEnabled, Value: true, TestOnly: false, - Default: func(dc *dynamicconfig.Collection) bool { - return dynamicconfig.BuildIdScavengerEnabled.Get(dc)() - }, - }, - { - // Raise the default per-namespace concurrent/RPS limits so batch and namespace-heavy - // tests aren't rate limited. - Setting: dynamicconfig.FrontendMaxConcurrentBatchOperationPerNamespace, Value: 1000, TestOnly: true, - }, - { - Setting: dynamicconfig.FrontendMaxNamespaceVisibilityRPSPerInstance, Value: 100, TestOnly: true, - }, - { - // Shorten cluster metadata refresh so multi-cluster tests don't wait a full minute. - Setting: dynamicconfig.ClusterMetadataRefreshInterval, Value: 100 * time.Millisecond, TestOnly: true, - }, -} - -var sharedServerDynamicConfigOverrides = []dynamicConfigOverride{ - { - // Allow a high rate of change to namespaces, particularly for the task-queue command - // tests. - Setting: dynamicconfig.FrontendMaxNamespaceVisibilityRPSPerInstance, Value: 10000, TestOnly: true, - }, - { - // Required by TestWorkflow_Show_SystemNexusOperationTransformsTypeNames to schedule a - // SignalWithStartWorkflowExecution Nexus operation against the __temporal_system - // endpoint from inside a workflow. - Setting: dynamicconfig.EnableSignalWithStartFromWorkflow, Value: true, TestOnly: false, - Default: func(dc *dynamicconfig.Collection) bool { - return dynamicconfig.EnableSignalWithStartFromWorkflow.Get(dc)("default") - }, - }, - { - Setting: serveractivity.StartDelayEnabled, Value: true, TestOnly: false, - Default: func(dc *dynamicconfig.Collection) bool { - return serveractivity.StartDelayEnabled.Get(dc)("default") - }, - }, - { - Setting: serveractivity.EnableStandaloneActivityOperatorCommands, Value: true, TestOnly: false, - Default: func(dc *dynamicconfig.Collection) bool { - return serveractivity.EnableStandaloneActivityOperatorCommands.Get(dc)("default") - }, - }, - { - Setting: serveractivity.LongPollTimeout, Value: 2 * time.Second, TestOnly: true, - }, - { - Setting: servernexusoperation.Enabled, Value: true, TestOnly: false, - Default: func(dc *dynamicconfig.Collection) bool { - return servernexusoperation.Enabled.Get(dc)("default") - }, - }, - { - // Disable DescribeTaskQueue cache while testing versioning behavior. - Setting: dynamicconfig.TaskQueueInfoByBuildIdTTL, Value: 0 * time.Second, TestOnly: true, - }, - { - // Required by TestActivity_CancelTerminateDelete_* to enable batch operations on - // standalone activities. - Setting: dynamicconfig.FrontendEnableBatchOperationsForStandaloneActivities, Value: true, TestOnly: false, - Default: func(dc *dynamicconfig.Collection) bool { - return dynamicconfig.FrontendEnableBatchOperationsForStandaloneActivities.Get(dc)("default") + } +} + +// Namespace describes the setting's lookup precedence. The static CLI value is still +// unconstrained and applies to every namespace. +func overrideNamespaceSetting(setting dynamicconfig.NamespaceBoolSetting, enabled bool) featureOverride { + return featureOverride{ + key: setting.Key().String(), + enabled: enabled, + defaultEnabled: func(dc *dynamicconfig.Collection) bool { + return setting.Get(dc)("default") }, - }, -} - -// dynamicConfigDefaultsAssumedEnabled are settings SharedServerSuite and StartDevServer no -// longer force on because the pinned go.temporal.io/server version already defaults them to -// true (see internal/devserver/server.go's comment on dynConf). If a server downgrade or a -// setting change ever flips one back to false, TestDynamicConfigOverridesMatchServerDefaults -// fails, signaling the override needs to be restored in both places. -var dynamicConfigDefaultsAssumedEnabled = []dynamicConfigOverride{ - { - Setting: dynamicconfig.EnableChasm, - Default: func(dc *dynamicconfig.Collection) bool { return dynamicconfig.EnableChasm.Get(dc)("default") }, - }, - { - Setting: serveractivity.Enabled, - Default: func(dc *dynamicconfig.Collection) bool { return serveractivity.Enabled.Get(dc)("default") }, - }, -} - -// TestDynamicConfigOverridesMatchServerDefaults guards against -// https://github.com/temporalio/cli/issues/1083: each non-test-only entry in -// baseDevServerDynamicConfigOverrides and sharedServerDynamicConfigOverrides forces on a server -// feature flag that's assumed to default to false in the pinned go.temporal.io/server version; -// each entry in dynamicConfigDefaultsAssumedEnabled is assumed to default to true. This reads -// those defaults directly from go.temporal.io/server with no dynamic config client and no dev -// server needed (see dynamicconfig.NewNoopCollection), so it runs instantly and fails the moment -// a server bump changes one of those assumptions — the signal to update the corresponding -// override list and internal/devserver/server.go. + } +} + +var baseDevServerFeatureOverrides = []featureOverride{ + overrideNamespaceSetting(dynamicconfig.FrontendEnableWorkerVersioningRuleAPIs, true), + overrideNamespaceSetting(dynamicconfig.FrontendEnableWorkerVersioningDataAPIs, true), + overrideNamespaceSetting(dynamicconfig.EnableDeployments, true), + overrideGlobalSetting(dynamicconfig.BuildIdScavengerEnabled, true), +} + +var sharedServerFeatureOverrides = []featureOverride{ + // Required by TestWorkflow_Show_SystemNexusOperationTransformsTypeNames. + overrideNamespaceSetting(dynamicconfig.EnableSignalWithStartFromWorkflow, true), + overrideNamespaceSetting(serveractivity.StartDelayEnabled, true), + overrideNamespaceSetting(serveractivity.EnableStandaloneActivityOperatorCommands, true), + overrideNamespaceSetting(servernexusoperation.Enabled, true), + // Required by TestActivity_CancelTerminateDelete_*. + overrideNamespaceSetting(dynamicconfig.FrontendEnableBatchOperationsForStandaloneActivities, true), +} + +func applyFeatureOverrides(values map[string]any, overrides []featureOverride) { + for _, override := range overrides { + values[override.key] = override.enabled + } +} + +// TestDynamicConfigOverridesMatchServerDefaults guards against feature overrides becoming +// redundant when the pinned server version changes. Test-only tuning overrides are excluded. func TestDynamicConfigOverridesMatchServerDefaults(t *testing.T) { dc := dynamicconfig.NewNoopCollection() - for _, o := range slices.Concat(baseDevServerDynamicConfigOverrides, sharedServerDynamicConfigOverrides) { - if o.TestOnly { - continue - } - o := o - t.Run(o.Setting.Key().String(), func(t *testing.T) { - require.NotNilf(t, o.Default, "non-test-only override %q must set Default", o.Setting.Key().String()) - wantOverrideValue, ok := o.Value.(bool) - require.Truef(t, ok, "non-test-only override %q must be boolean, got %T", o.Setting.Key().String(), o.Value) - gotDefault := o.Default(dc) - require.NotEqualf(t, wantOverrideValue, gotDefault, - "%q now defaults to %v in the pinned server version, same as the override value — "+ - "this override is a no-op and should be removed", - o.Setting.Key().String(), gotDefault) - }) - } - for _, o := range dynamicConfigDefaultsAssumedEnabled { - o := o - t.Run(o.Setting.Key().String(), func(t *testing.T) { - require.Truef(t, o.Default(dc), - "%q no longer defaults to true in the pinned server version — "+ - "restore an explicit override in SharedServerSuite/StartDevServer and internal/devserver/server.go", - o.Setting.Key().String()) + for _, override := range slices.Concat(baseDevServerFeatureOverrides, sharedServerFeatureOverrides) { + t.Run(override.key, func(t *testing.T) { + require.NotEqualf(t, override.enabled, override.defaultEnabled(dc), + "%q now defaults to %t; remove its feature override", override.key, override.enabled) }) } } func (s *SharedServerSuite) SetupSuite() { + dynamicConfigValues := map[string]any{ + "activity.longPollTimeout": 2 * time.Second, + // Disable DescribeTaskQueue caching while testing versioning behavior. + "matching.TaskQueueInfoByBuildIdTTL": 0 * time.Second, + } + applyFeatureOverrides(dynamicConfigValues, sharedServerFeatureOverrides) + s.DevServer = StartDevServer(s.Suite.T(), DevServerOptions{ StartOptions: devserver.StartOptions{ // Enable for operator cluster commands EnableGlobalNamespace: true, - DynamicConfigValues: dynamicConfigValues(sharedServerDynamicConfigOverrides), + DynamicConfigValues: dynamicConfigValues, }, }) // Stop server if we fail later @@ -566,14 +451,11 @@ func StartDevServer(t *testing.T, options DevServerOptions) *DevServer { if d.Options.DynamicConfigValues == nil { d.Options.DynamicConfigValues = map[string]any{} } - // Only fill in keys the caller hasn't already set, so e.g. SharedServerSuite's own - // dynamic config values take precedence over these defaults. - for _, o := range baseDevServerDynamicConfigOverrides { - key := o.Setting.Key().String() - if _, ok := d.Options.DynamicConfigValues[key]; !ok { - d.Options.DynamicConfigValues[key] = o.Value - } - } + applyFeatureOverrides(d.Options.DynamicConfigValues, baseDevServerFeatureOverrides) + d.Options.DynamicConfigValues["system.forceSearchAttributesCacheRefreshOnRead"] = true + d.Options.DynamicConfigValues["frontend.MaxConcurrentBatchOperationPerNamespace"] = 1000 + d.Options.DynamicConfigValues["frontend.namespaceRPS.visibility"] = 100 + d.Options.DynamicConfigValues["system.clusterMetadataRefreshInterval"] = 100 * time.Millisecond d.Options.GRPCInterceptors = append( d.Options.GRPCInterceptors, From d5edfdf3d30746a8677f2a4d3a070028714e2c7a Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:39:43 -0500 Subject: [PATCH 6/7] test: validate dev server feature overrides --- internal/devserver/server.go | 19 +++++++++++++++++-- internal/devserver/server_test.go | 19 +++++++++++++++++++ internal/temporalcli/commands_test.go | 3 --- 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 internal/devserver/server_test.go diff --git a/internal/devserver/server.go b/internal/devserver/server.go index ebbd70162..974070ef2 100644 --- a/internal/devserver/server.go +++ b/internal/devserver/server.go @@ -59,6 +59,19 @@ const ( localhost = "127.0.0.1" ) +type namespaceFeatureOverride struct { + setting dynamicconfig.NamespaceBoolSetting + enabled bool +} + +// These values are applied without constraints and therefore affect every namespace. +// NamespaceBoolSetting describes the server setting's lookup precedence, not the scope +// of the CLI override. +var startDevServerFeatureOverrides = []namespaceFeatureOverride{ + {setting: activity.EnableStandaloneActivityOperatorCommands, enabled: true}, + {setting: dynamicconfig.FrontendEnableBatchOperationsForStandaloneActivities, enabled: true}, +} + type StartOptions struct { // Required fields FrontendIP string @@ -245,8 +258,10 @@ func (s *StartOptions) buildServerOptions() ([]temporal.ServerOption, *slog.Leve // CHASM (dynamicconfig.EnableChasm) and SAA (activity.Enabled) are on by default as of // server v1.32, so they no longer need to be forced on here. - dynConf[activity.EnableStandaloneActivityOperatorCommands.Key()] = true - dynConf[dynamicconfig.FrontendEnableBatchOperationsForStandaloneActivities.Key()] = true + // Apply the remaining temporary feature overrides from the list validated by unit tests. + for _, override := range startDevServerFeatureOverrides { + dynConf[override.setting.Key()] = override.enabled + } // Dynamic config if set for k, v := range s.DynamicConfigValues { diff --git a/internal/devserver/server_test.go b/internal/devserver/server_test.go new file mode 100644 index 000000000..4b47950b3 --- /dev/null +++ b/internal/devserver/server_test.go @@ -0,0 +1,19 @@ +package devserver + +import ( + "testing" + + "go.temporal.io/server/common/dynamicconfig" +) + +func TestStartDevServerFeatureOverridesMatchServerDefaults(t *testing.T) { + dc := dynamicconfig.NewNoopCollection() + for _, override := range startDevServerFeatureOverrides { + key := override.setting.Key().String() + t.Run(key, func(t *testing.T) { + if serverDefault := override.setting.Get(dc)("default"); serverDefault == override.enabled { + t.Fatalf("%q now defaults to %t; remove its feature override", key, override.enabled) + } + }) + } +} diff --git a/internal/temporalcli/commands_test.go b/internal/temporalcli/commands_test.go index 3e8e3a056..0824ff215 100644 --- a/internal/temporalcli/commands_test.go +++ b/internal/temporalcli/commands_test.go @@ -271,10 +271,7 @@ var sharedServerFeatureOverrides = []featureOverride{ // Required by TestWorkflow_Show_SystemNexusOperationTransformsTypeNames. overrideNamespaceSetting(dynamicconfig.EnableSignalWithStartFromWorkflow, true), overrideNamespaceSetting(serveractivity.StartDelayEnabled, true), - overrideNamespaceSetting(serveractivity.EnableStandaloneActivityOperatorCommands, true), overrideNamespaceSetting(servernexusoperation.Enabled, true), - // Required by TestActivity_CancelTerminateDelete_*. - overrideNamespaceSetting(dynamicconfig.FrontendEnableBatchOperationsForStandaloneActivities, true), } func applyFeatureOverrides(values map[string]any, overrides []featureOverride) { From a4090de9ecb8ff64755a687fc44dd890df7f569e Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:45:33 -0500 Subject: [PATCH 7/7] test: clarify dynamic config override scopes --- internal/devserver/server.go | 10 +++++++--- internal/temporalcli/commands_test.go | 17 ++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/internal/devserver/server.go b/internal/devserver/server.go index 974070ef2..5a3cf44c0 100644 --- a/internal/devserver/server.go +++ b/internal/devserver/server.go @@ -64,9 +64,13 @@ type namespaceFeatureOverride struct { enabled bool } +// startDevServerFeatureOverrides contains temporary feature-specific dynamic config for +// `temporal server start-dev`. Add boolean feature overrides here instead of assigning them +// inline in buildServerOptions so TestStartDevServerFeatureOverridesMatchServerDefaults can +// detect when an override becomes redundant. +// // These values are applied without constraints and therefore affect every namespace. -// NamespaceBoolSetting describes the server setting's lookup precedence, not the scope -// of the CLI override. +// NamespaceBoolSetting describes the server setting's lookup precedence, not override scope. var startDevServerFeatureOverrides = []namespaceFeatureOverride{ {setting: activity.EnableStandaloneActivityOperatorCommands, enabled: true}, {setting: dynamicconfig.FrontendEnableBatchOperationsForStandaloneActivities, enabled: true}, @@ -258,7 +262,7 @@ func (s *StartOptions) buildServerOptions() ([]temporal.ServerOption, *slog.Leve // CHASM (dynamicconfig.EnableChasm) and SAA (activity.Enabled) are on by default as of // server v1.32, so they no longer need to be forced on here. - // Apply the remaining temporary feature overrides from the list validated by unit tests. + // Feature-specific dynamic config must come from the validated list above, not be set inline. for _, override := range startDevServerFeatureOverrides { dynConf[override.setting.Key()] = override.enabled } diff --git a/internal/temporalcli/commands_test.go b/internal/temporalcli/commands_test.go index 0824ff215..f009da8da 100644 --- a/internal/temporalcli/commands_test.go +++ b/internal/temporalcli/commands_test.go @@ -260,14 +260,20 @@ func overrideNamespaceSetting(setting dynamicconfig.NamespaceBoolSetting, enable } } -var baseDevServerFeatureOverrides = []featureOverride{ +// allTestDevServerFeatureOverrides are applied by StartDevServer to every test server, +// including SharedServerSuite and its auxiliary standby clusters. Add broadly required +// boolean feature overrides here instead of assigning them inline in StartDevServer. +var allTestDevServerFeatureOverrides = []featureOverride{ overrideNamespaceSetting(dynamicconfig.FrontendEnableWorkerVersioningRuleAPIs, true), overrideNamespaceSetting(dynamicconfig.FrontendEnableWorkerVersioningDataAPIs, true), overrideNamespaceSetting(dynamicconfig.EnableDeployments, true), overrideGlobalSetting(dynamicconfig.BuildIdScavengerEnabled, true), } -var sharedServerFeatureOverrides = []featureOverride{ +// sharedSuiteFeatureOverrides are applied only to the singleton server used by +// SharedServerSuite. Add boolean feature overrides needed only by that suite here instead +// of assigning them inline in SetupSuite. +var sharedSuiteFeatureOverrides = []featureOverride{ // Required by TestWorkflow_Show_SystemNexusOperationTransformsTypeNames. overrideNamespaceSetting(dynamicconfig.EnableSignalWithStartFromWorkflow, true), overrideNamespaceSetting(serveractivity.StartDelayEnabled, true), @@ -284,7 +290,7 @@ func applyFeatureOverrides(values map[string]any, overrides []featureOverride) { // redundant when the pinned server version changes. Test-only tuning overrides are excluded. func TestDynamicConfigOverridesMatchServerDefaults(t *testing.T) { dc := dynamicconfig.NewNoopCollection() - for _, override := range slices.Concat(baseDevServerFeatureOverrides, sharedServerFeatureOverrides) { + for _, override := range slices.Concat(allTestDevServerFeatureOverrides, sharedSuiteFeatureOverrides) { t.Run(override.key, func(t *testing.T) { require.NotEqualf(t, override.enabled, override.defaultEnabled(dc), "%q now defaults to %t; remove its feature override", override.key, override.enabled) @@ -298,7 +304,7 @@ func (s *SharedServerSuite) SetupSuite() { // Disable DescribeTaskQueue caching while testing versioning behavior. "matching.TaskQueueInfoByBuildIdTTL": 0 * time.Second, } - applyFeatureOverrides(dynamicConfigValues, sharedServerFeatureOverrides) + applyFeatureOverrides(dynamicConfigValues, sharedSuiteFeatureOverrides) s.DevServer = StartDevServer(s.Suite.T(), DevServerOptions{ StartOptions: devserver.StartOptions{ @@ -448,7 +454,8 @@ func StartDevServer(t *testing.T, options DevServerOptions) *DevServer { if d.Options.DynamicConfigValues == nil { d.Options.DynamicConfigValues = map[string]any{} } - applyFeatureOverrides(d.Options.DynamicConfigValues, baseDevServerFeatureOverrides) + // Feature-specific dynamic config must come from the validated list above, not be set inline. + applyFeatureOverrides(d.Options.DynamicConfigValues, allTestDevServerFeatureOverrides) d.Options.DynamicConfigValues["system.forceSearchAttributesCacheRefreshOnRead"] = true d.Options.DynamicConfigValues["frontend.MaxConcurrentBatchOperationPerNamespace"] = 1000 d.Options.DynamicConfigValues["frontend.namespaceRPS.visibility"] = 100