From c056866b06c6f1c0ef75ad97c634d01d505cc0a3 Mon Sep 17 00:00:00 2001 From: Yash B Joshi <68319416+YJ-928@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:39:34 +0530 Subject: [PATCH 1/2] feat: add scheduleWorkflowType and deprecate scheduleWorkflowName Part of zigflow/zigflow#617. scheduleWorkflowName names a workflow type, not a name, since document.name became document.workflowType (zigflow/zigflow#340). - scheduleWorkflowType: the workflow type a schedule starts. - scheduleWorkflowName: kept, marked deprecated, so existing files still pass. - A schedule no longer requires either key in the schema. Zigflow defaults the target to document.workflowType; only the runtime can tell when that does not apply (every top-level task is a Do task), so it reports that case. Signed-off-by: Yash B Joshi <68319416+YJ-928@users.noreply.github.com> --- constants.go | 1 + definitions.go | 10 ++++++++- metadata_test.go | 20 ++++++++++++++++- schema.go | 16 ------------- schema.json | 35 ++++++++--------------------- schema.yaml | 25 ++++++++------------- schema_test.go | 58 ++++++++++++++++++++---------------------------- 7 files changed, 71 insertions(+), 94 deletions(-) diff --git a/constants.go b/constants.go index 92c1046..f3ddb9d 100644 --- a/constants.go +++ b/constants.go @@ -112,6 +112,7 @@ const ( propTaskQueue = "taskQueue" propType = "type" propScheduleWorkflowName = "scheduleWorkflowName" + propScheduleWorkflowType = "scheduleWorkflowType" propScheduleID = "scheduleId" propScheduleInput = "scheduleInput" propSeconds = "seconds" diff --git a/definitions.go b/definitions.go index e4326ad..2186942 100644 --- a/definitions.go +++ b/definitions.go @@ -413,11 +413,19 @@ var documentMetadataDefinition = &jsonschema.Schema{ Title: "ContinueAsNewMaxHistoryLength", Description: "Allows you to test the Continue-As-New functionality by specifying the max history length before triggering.", }, + propScheduleWorkflowType: { + Type: typeString, + Title: "ScheduleWorkflowType", + Description: "The workflow type the schedule starts: the document.workflowType or a top-level Do task. " + + "Defaults to document.workflowType; required when every top-level task is a Do task.", + MinLength: utils.Ptr(1), + }, propScheduleWorkflowName: { Type: typeString, Title: "ScheduleWorkflowName", - Description: "Set the workflow name to trigger - this will either be the document.workflowType or the Do task", + Description: "Deprecated: use scheduleWorkflowType.", MinLength: utils.Ptr(1), + Deprecated: true, }, propScheduleID: { Type: typeString, diff --git a/metadata_test.go b/metadata_test.go index 6b177c4..6405269 100644 --- a/metadata_test.go +++ b/metadata_test.go @@ -159,6 +159,7 @@ func TestDocumentMetadataDefinitionShape(t *testing.T) { for _, key := range []string{ propCanMaxHistory, propScheduleWorkflowName, + propScheduleWorkflowType, propScheduleID, propScheduleInput, } { @@ -168,6 +169,9 @@ func TestDocumentMetadataDefinitionShape(t *testing.T) { assert.Equal(t, typeInteger, def.Properties[propCanMaxHistory].Type) assert.Equal(t, typeString, def.Properties[propScheduleWorkflowName].Type) + assert.True(t, def.Properties[propScheduleWorkflowName].Deprecated, "scheduleWorkflowName is a deprecated alias") + assert.Equal(t, typeString, def.Properties[propScheduleWorkflowType].Type) + assert.False(t, def.Properties[propScheduleWorkflowType].Deprecated) assert.Equal(t, typeString, def.Properties[propScheduleID].Type) assert.Equal(t, typeArray, def.Properties[propScheduleInput].Type) } @@ -225,7 +229,11 @@ func TestDocumentMetadata_Validation(t *testing.T) { // ---- valid cases ---- { name: "scheduleWorkflowName string is accepted", - meta: map[string]any{"scheduleWorkflowName": "my-workflow"}, + meta: map[string]any{"scheduleWorkflowName": testWorkflowType}, + }, + { + name: "scheduleWorkflowType string is accepted", + meta: map[string]any{propScheduleWorkflowType: testWorkflowType}, }, { name: "scheduleId string is accepted", @@ -266,6 +274,16 @@ func TestDocumentMetadata_Validation(t *testing.T) { meta: map[string]any{"scheduleWorkflowName": 42}, expectError: true, }, + { + name: "scheduleWorkflowType as integer is rejected", + meta: map[string]any{propScheduleWorkflowType: 42}, + expectError: true, + }, + { + name: "empty scheduleWorkflowType is rejected", + meta: map[string]any{propScheduleWorkflowType: ""}, + expectError: true, + }, { name: "canMaxHistoryLength as string is rejected", meta: map[string]any{propCanMaxHistory: "not-an-int"}, diff --git a/schema.go b/schema.go index f2bc917..e093ee4 100644 --- a/schema.go +++ b/schema.go @@ -36,22 +36,6 @@ func BuildSchema(version, format string) (*jsonschema.Schema, error) { Required: []string{propDo, propDocument}, Properties: schemaProperties, Defs: buildDefinitions(), - If: &jsonschema.Schema{ - Required: []string{"schedule"}, - }, - Then: &jsonschema.Schema{ - Required: []string{propDocument}, - Properties: map[string]*jsonschema.Schema{ - propDocument: { - Required: []string{propMetadata}, - Properties: map[string]*jsonschema.Schema{ - propMetadata: { - Required: []string{propScheduleWorkflowName}, - }, - }, - }, - }, - }, } // Validate the generated schema diff --git a/schema.json b/schema.json index 8bc006c..24a1879 100644 --- a/schema.json +++ b/schema.json @@ -520,7 +520,14 @@ "scheduleWorkflowName": { "type": "string", "title": "ScheduleWorkflowName", - "description": "Set the workflow name to trigger - this will either be the document.workflowType or the Do task", + "description": "Deprecated: use scheduleWorkflowType.", + "deprecated": true, + "minLength": 1 + }, + "scheduleWorkflowType": { + "type": "string", + "title": "ScheduleWorkflowType", + "description": "The workflow type the schedule starts: the document.workflowType or a top-level Do task. Defaults to document.workflowType; required when every top-level task is a Do task.", "minLength": 1 } }, @@ -1834,29 +1841,5 @@ "required": [ "do", "document" - ], - "if": { - "required": [ - "schedule" - ] - }, - "then": { - "properties": { - "document": { - "properties": { - "metadata": { - "required": [ - "scheduleWorkflowName" - ] - } - }, - "required": [ - "metadata" - ] - } - }, - "required": [ - "document" - ] - } + ] } diff --git a/schema.yaml b/schema.yaml index 15944dc..22414d6 100644 --- a/schema.yaml +++ b/schema.yaml @@ -306,11 +306,18 @@ $defs: title: ScheduleInput type: array scheduleWorkflowName: - description: Set the workflow name to trigger - this will either be the document.workflowType - or the Do task + deprecated: true + description: 'Deprecated: use scheduleWorkflowType.' minLength: 1 title: ScheduleWorkflowName type: string + scheduleWorkflowType: + description: 'The workflow type the schedule starts: the document.workflowType + or a top-level Do task. Defaults to document.workflowType; required when + every top-level task is a Do task.' + minLength: 1 + title: ScheduleWorkflowType + type: string title: DocumentMetadata type: object duration: @@ -1245,9 +1252,6 @@ $defs: $id: https://zigflow.dev/schema.yaml $schema: https://json-schema.org/draft/2020-12/schema description: JSON Schema for a Zigflow workflow definition. -if: - required: - - schedule properties: do: $ref: '#/$defs/taskList' @@ -1339,16 +1343,5 @@ properties: required: - do - document -then: - properties: - document: - properties: - metadata: - required: - - scheduleWorkflowName - required: - - metadata - required: - - document title: Zigflow type: object diff --git a/schema_test.go b/schema_test.go index 6d01839..b21b7a4 100644 --- a/schema_test.go +++ b/schema_test.go @@ -23,7 +23,10 @@ import ( "github.com/stretchr/testify/require" ) -const testCronExpr = "0 0 * * *" +const ( + testCronExpr = "0 0 * * *" + testWorkflowType = "my-workflow" +) // minimalWorkflow returns a minimal structurally valid workflow document. // It is used as a baseline for validation tests so that each test only @@ -97,41 +100,28 @@ func TestSchema_DocumentFields(t *testing.T) { }) } -// TestSchema_ScheduleRequiresScheduleWorkflowName verifies the conditional -// if/then rule: when top-level schedule is present, document.metadata must -// exist and contain scheduleWorkflowName. -func TestSchema_ScheduleRequiresScheduleWorkflowName(t *testing.T) { +// TestSchema_ScheduleNeedsNoWorkflowKey verifies that a schedule validates +// without a workflow key: the runtime defaults it to document.workflowType, and +// only it can tell when that does not apply (every top-level task a Do task). +func TestSchema_ScheduleNeedsNoWorkflowKey(t *testing.T) { resolved := resolvedTestSchema(t) - t.Run("schedule present without document.metadata is rejected", func(t *testing.T) { - doc := minimalWorkflow() - doc["schedule"] = map[string]any{propCron: testCronExpr} - - err := resolved.Validate(doc) - assert.Error(t, err, "schedule without document.metadata must fail validation") - }) - - t.Run("schedule present with document.metadata but missing scheduleWorkflowName is rejected", func(t *testing.T) { - doc := minimalWorkflow() - doc["schedule"] = map[string]any{propCron: testCronExpr} - doc["document"].(map[string]any)["metadata"] = map[string]any{ - propScheduleID: "my-schedule", - } - - err := resolved.Validate(doc) - assert.Error(t, err, "schedule with document.metadata but no scheduleWorkflowName must fail validation") - }) - - t.Run("schedule present with valid document.metadata.scheduleWorkflowName is accepted", func(t *testing.T) { - doc := minimalWorkflow() - doc["schedule"] = map[string]any{propCron: testCronExpr} - doc["document"].(map[string]any)["metadata"] = map[string]any{ - propScheduleWorkflowName: "my-workflow", - } - - err := resolved.Validate(doc) - assert.NoError(t, err, "schedule with valid scheduleWorkflowName must pass validation") - }) + for name, meta := range map[string]map[string]any{ + "no document.metadata": nil, + "metadata without a key": {propScheduleID: "my-schedule"}, + "the workflow type key": {propScheduleWorkflowType: testWorkflowType}, + "the deprecated alias": {propScheduleWorkflowName: testWorkflowType}, + } { + t.Run(name, func(t *testing.T) { + doc := minimalWorkflow() + doc["schedule"] = map[string]any{propCron: testCronExpr} + if meta != nil { + doc["document"].(map[string]any)["metadata"] = meta + } + + assert.NoError(t, resolved.Validate(doc), "a schedule must validate with %s", name) + }) + } } // TestSchema_RejectsUnknownTopLevelProperties verifies that the root schema From fcb554b924959a3c5c352a2817c4387f268def10 Mon Sep 17 00:00:00 2001 From: Yash B Joshi <68319416+YJ-928@users.noreply.github.com> Date: Wed, 28 Oct 2026 16:25:36 +0530 Subject: [PATCH 2/2] refactor: drop scheduleWorkflowName from the schema No release of Studio uses it yet. document.metadata is open, so files that still set it keep validating. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Yash B Joshi <68319416+YJ-928@users.noreply.github.com> --- constants.go | 1 - definitions.go | 7 ------- metadata_test.go | 12 ------------ schema.json | 7 ------- schema.yaml | 6 ------ schema_test.go | 8 ++++---- 6 files changed, 4 insertions(+), 37 deletions(-) diff --git a/constants.go b/constants.go index f3ddb9d..5c46403 100644 --- a/constants.go +++ b/constants.go @@ -111,7 +111,6 @@ const ( propRetryPolicy = "retryPolicy" propTaskQueue = "taskQueue" propType = "type" - propScheduleWorkflowName = "scheduleWorkflowName" propScheduleWorkflowType = "scheduleWorkflowType" propScheduleID = "scheduleId" propScheduleInput = "scheduleInput" diff --git a/definitions.go b/definitions.go index 2186942..f7ead7a 100644 --- a/definitions.go +++ b/definitions.go @@ -420,13 +420,6 @@ var documentMetadataDefinition = &jsonschema.Schema{ "Defaults to document.workflowType; required when every top-level task is a Do task.", MinLength: utils.Ptr(1), }, - propScheduleWorkflowName: { - Type: typeString, - Title: "ScheduleWorkflowName", - Description: "Deprecated: use scheduleWorkflowType.", - MinLength: utils.Ptr(1), - Deprecated: true, - }, propScheduleID: { Type: typeString, Title: "ScheduleID", diff --git a/metadata_test.go b/metadata_test.go index 6405269..f9b6622 100644 --- a/metadata_test.go +++ b/metadata_test.go @@ -158,7 +158,6 @@ func TestDocumentMetadataDefinitionShape(t *testing.T) { for _, key := range []string{ propCanMaxHistory, - propScheduleWorkflowName, propScheduleWorkflowType, propScheduleID, propScheduleInput, @@ -168,8 +167,6 @@ func TestDocumentMetadataDefinitionShape(t *testing.T) { } assert.Equal(t, typeInteger, def.Properties[propCanMaxHistory].Type) - assert.Equal(t, typeString, def.Properties[propScheduleWorkflowName].Type) - assert.True(t, def.Properties[propScheduleWorkflowName].Deprecated, "scheduleWorkflowName is a deprecated alias") assert.Equal(t, typeString, def.Properties[propScheduleWorkflowType].Type) assert.False(t, def.Properties[propScheduleWorkflowType].Deprecated) assert.Equal(t, typeString, def.Properties[propScheduleID].Type) @@ -227,10 +224,6 @@ func TestDocumentMetadata_Validation(t *testing.T) { expectError bool }{ // ---- valid cases ---- - { - name: "scheduleWorkflowName string is accepted", - meta: map[string]any{"scheduleWorkflowName": testWorkflowType}, - }, { name: "scheduleWorkflowType string is accepted", meta: map[string]any{propScheduleWorkflowType: testWorkflowType}, @@ -269,11 +262,6 @@ func TestDocumentMetadata_Validation(t *testing.T) { }, // ---- invalid cases ---- - { - name: "scheduleWorkflowName as integer is rejected", - meta: map[string]any{"scheduleWorkflowName": 42}, - expectError: true, - }, { name: "scheduleWorkflowType as integer is rejected", meta: map[string]any{propScheduleWorkflowType: 42}, diff --git a/schema.json b/schema.json index 24a1879..e33add0 100644 --- a/schema.json +++ b/schema.json @@ -517,13 +517,6 @@ "title": "ScheduleInput", "description": "Set the input." }, - "scheduleWorkflowName": { - "type": "string", - "title": "ScheduleWorkflowName", - "description": "Deprecated: use scheduleWorkflowType.", - "deprecated": true, - "minLength": 1 - }, "scheduleWorkflowType": { "type": "string", "title": "ScheduleWorkflowType", diff --git a/schema.yaml b/schema.yaml index 22414d6..ed93248 100644 --- a/schema.yaml +++ b/schema.yaml @@ -305,12 +305,6 @@ $defs: description: Set the input. title: ScheduleInput type: array - scheduleWorkflowName: - deprecated: true - description: 'Deprecated: use scheduleWorkflowType.' - minLength: 1 - title: ScheduleWorkflowName - type: string scheduleWorkflowType: description: 'The workflow type the schedule starts: the document.workflowType or a top-level Do task. Defaults to document.workflowType; required when diff --git a/schema_test.go b/schema_test.go index b21b7a4..986249c 100644 --- a/schema_test.go +++ b/schema_test.go @@ -107,10 +107,10 @@ func TestSchema_ScheduleNeedsNoWorkflowKey(t *testing.T) { resolved := resolvedTestSchema(t) for name, meta := range map[string]map[string]any{ - "no document.metadata": nil, - "metadata without a key": {propScheduleID: "my-schedule"}, - "the workflow type key": {propScheduleWorkflowType: testWorkflowType}, - "the deprecated alias": {propScheduleWorkflowName: testWorkflowType}, + "no document.metadata": nil, + "metadata without a key": {propScheduleID: "my-schedule"}, + "the workflow type key": {propScheduleWorkflowType: testWorkflowType}, + "the old key, now unlisted": {"scheduleWorkflowName": testWorkflowType}, } { t.Run(name, func(t *testing.T) { doc := minimalWorkflow()