diff --git a/.github/workflows/campaign-generator.lock.yml b/.github/workflows/campaign-generator.lock.yml index 8994565ea5..330a000343 100644 --- a/.github/workflows/campaign-generator.lock.yml +++ b/.github/workflows/campaign-generator.lock.yml @@ -525,10 +525,13 @@ jobs: "maxLength": 128 }, "issue_number": { - "required": true, - "positiveInteger": true + "optionalPositiveInteger": true + }, + "pull_number": { + "optionalPositiveInteger": true } - } + }, + "customValidation": "requiresOneOf:issue_number,pull_number" }, "create_project": { "defaultMax": 1, diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index dac2b5c813..7dc7dcc24c 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -342,10 +342,13 @@ jobs: "maxLength": 128 }, "issue_number": { - "required": true, - "positiveInteger": true + "optionalPositiveInteger": true + }, + "pull_number": { + "optionalPositiveInteger": true } - } + }, + "customValidation": "requiresOneOf:issue_number,pull_number" }, "missing_tool": { "defaultMax": 20, diff --git a/.github/workflows/workflow-generator.lock.yml b/.github/workflows/workflow-generator.lock.yml index 087fc36f6f..be74cdf4a4 100644 --- a/.github/workflows/workflow-generator.lock.yml +++ b/.github/workflows/workflow-generator.lock.yml @@ -360,10 +360,13 @@ jobs: "maxLength": 128 }, "issue_number": { - "required": true, - "positiveInteger": true + "optionalPositiveInteger": true + }, + "pull_number": { + "optionalPositiveInteger": true } - } + }, + "customValidation": "requiresOneOf:issue_number,pull_number" }, "missing_tool": { "defaultMax": 20, diff --git a/actions/setup/js/safe_output_type_validator.test.cjs b/actions/setup/js/safe_output_type_validator.test.cjs index f984515282..e723036aff 100644 --- a/actions/setup/js/safe_output_type_validator.test.cjs +++ b/actions/setup/js/safe_output_type_validator.test.cjs @@ -46,6 +46,15 @@ const SAMPLE_VALIDATION_CONFIG = { issue_number: { issueOrPRNumber: true }, }, }, + assign_to_agent: { + defaultMax: 1, + customValidation: "requiresOneOf:issue_number,pull_number", + fields: { + issue_number: { optionalPositiveInteger: true }, + pull_number: { optionalPositiveInteger: true }, + agent: { type: "string", sanitize: true, maxLength: 128 }, + }, + }, create_pull_request_review_comment: { defaultMax: 1, customValidation: "startLineLessOrEqualLine", @@ -356,6 +365,33 @@ describe("safe_output_type_validator", () => { expect(result.isValid).toBe(false); expect(result.error).toContain("requires at least one of"); }); + + it("should pass for assign_to_agent with issue_number", async () => { + const { validateItem } = await import("./safe_output_type_validator.cjs"); + + const result = validateItem({ type: "assign_to_agent", issue_number: 123 }, "assign_to_agent", 1); + + expect(result.isValid).toBe(true); + }); + + it("should pass for assign_to_agent with pull_number", async () => { + const { validateItem } = await import("./safe_output_type_validator.cjs"); + + const result = validateItem({ type: "assign_to_agent", pull_number: 456 }, "assign_to_agent", 1); + + expect(result.isValid).toBe(true); + }); + + it("should fail for assign_to_agent without issue_number or pull_number", async () => { + const { validateItem } = await import("./safe_output_type_validator.cjs"); + + const result = validateItem({ type: "assign_to_agent", agent: "copilot" }, "assign_to_agent", 1); + + expect(result.isValid).toBe(false); + expect(result.error).toContain("requires at least one of"); + expect(result.error).toContain("issue_number"); + expect(result.error).toContain("pull_number"); + }); }); describe("custom validation: startLineLessOrEqualLine", () => { diff --git a/pkg/workflow/safe_output_validation_config.go b/pkg/workflow/safe_output_validation_config.go index 0ecb965379..a7cd677f57 100644 --- a/pkg/workflow/safe_output_validation_config.go +++ b/pkg/workflow/safe_output_validation_config.go @@ -98,9 +98,11 @@ var ValidationConfig = map[string]TypeValidationConfig{ }, }, "assign_to_agent": { - DefaultMax: 1, + DefaultMax: 1, + CustomValidation: "requiresOneOf:issue_number,pull_number", Fields: map[string]FieldValidation{ - "issue_number": {Required: true, PositiveInteger: true}, + "issue_number": {OptionalPositiveInteger: true}, + "pull_number": {OptionalPositiveInteger: true}, "agent": {Type: "string", Sanitize: true, MaxLength: 128}, }, }, diff --git a/pkg/workflow/safe_output_validation_config_test.go b/pkg/workflow/safe_output_validation_config_test.go index 4335116eb6..d8c9c971fb 100644 --- a/pkg/workflow/safe_output_validation_config_test.go +++ b/pkg/workflow/safe_output_validation_config_test.go @@ -232,10 +232,11 @@ func TestFieldValidationMarshaling(t *testing.T) { func TestValidationConfigConsistency(t *testing.T) { // Verify that all types with customValidation have valid validation rules validCustomValidations := map[string]bool{ - "requiresOneOf:status,title,body": true, - "requiresOneOf:title,body": true, - "startLineLessOrEqualLine": true, - "parentAndSubDifferent": true, + "requiresOneOf:status,title,body": true, + "requiresOneOf:title,body": true, + "requiresOneOf:issue_number,pull_number": true, + "startLineLessOrEqualLine": true, + "parentAndSubDifferent": true, } for typeName, config := range ValidationConfig {