diff --git a/.gitattributes b/.gitattributes index 33e102d50b4..c80e1a7bf05 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,7 +4,8 @@ .github/aw/github-agentic-workflows.md linguist-generated=true merge=ours pkg/cli/workflows/*.lock.yml linguist-generated=true merge=ours pkg/workflow/js/*.js linguist-generated=true +pkg/workflow/js/*.cjs linguist-generated=true +pkg/workflow/sh/*.sh linguist-generated=true actions/*/index.js linguist-generated=true -actions/setup/js/*.cjs linguist-generated=true .github/workflows/*.campaign.g.md linguist-generated=true merge=ours diff --git a/.github/aw/schemas/agentic-workflow.json b/.github/aw/schemas/agentic-workflow.json index f8f6e5c73ac..ecc885b6a4a 100644 --- a/.github/aw/schemas/agentic-workflow.json +++ b/.github/aw/schemas/agentic-workflow.json @@ -273,7 +273,41 @@ "type": "string" } } - } + }, + "oneOf": [ + { + "required": ["branches"], + "not": { "required": ["branches-ignore"] } + }, + { + "required": ["branches-ignore"], + "not": { "required": ["branches"] } + }, + { + "not": { + "anyOf": [{ "required": ["branches"] }, { "required": ["branches-ignore"] }] + } + } + ], + "allOf": [ + { + "oneOf": [ + { + "required": ["paths"], + "not": { "required": ["paths-ignore"] } + }, + { + "required": ["paths-ignore"], + "not": { "required": ["paths"] } + }, + { + "not": { + "anyOf": [{ "required": ["paths"] }, { "required": ["paths-ignore"] }] + } + } + ] + } + ] }, "pull_request": { "description": "Pull request event trigger that runs the workflow when pull requests are created, updated, or closed", @@ -374,15 +408,50 @@ "items": { "type": "string", "description": "Label name" - } + }, + "minItems": 1 } ] } }, - "additionalProperties": false + "additionalProperties": false, + "oneOf": [ + { + "required": ["branches"], + "not": { "required": ["branches-ignore"] } + }, + { + "required": ["branches-ignore"], + "not": { "required": ["branches"] } + }, + { + "not": { + "anyOf": [{ "required": ["branches"] }, { "required": ["branches-ignore"] }] + } + } + ], + "allOf": [ + { + "oneOf": [ + { + "required": ["paths"], + "not": { "required": ["paths-ignore"] } + }, + { + "required": ["paths-ignore"], + "not": { "required": ["paths"] } + }, + { + "not": { + "anyOf": [{ "required": ["paths"] }, { "required": ["paths-ignore"] }] + } + } + ] + } + ] }, "issues": { - "description": "Issues event trigger that runs the workflow when repository issues are created, updated, or managed", + "description": "Issues event trigger that runs when repository issues are created, updated, or managed", "type": "object", "additionalProperties": false, "properties": { @@ -406,7 +475,8 @@ "items": { "type": "string", "description": "Label name" - } + }, + "minItems": 1 } ] }, @@ -577,7 +647,22 @@ "type": "string" } } - } + }, + "oneOf": [ + { + "required": ["branches"], + "not": { "required": ["branches-ignore"] } + }, + { + "required": ["branches-ignore"], + "not": { "required": ["branches"] } + }, + { + "not": { + "anyOf": [{ "required": ["branches"] }, { "required": ["branches-ignore"] }] + } + } + ] }, "release": { "description": "Release event trigger", @@ -886,7 +971,41 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "oneOf": [ + { + "required": ["branches"], + "not": { "required": ["branches-ignore"] } + }, + { + "required": ["branches-ignore"], + "not": { "required": ["branches"] } + }, + { + "not": { + "anyOf": [{ "required": ["branches"] }, { "required": ["branches-ignore"] }] + } + } + ], + "allOf": [ + { + "oneOf": [ + { + "required": ["paths"], + "not": { "required": ["paths-ignore"] } + }, + { + "required": ["paths-ignore"], + "not": { "required": ["paths"] } + }, + { + "not": { + "anyOf": [{ "required": ["paths"] }, { "required": ["paths-ignore"] }] + } + } + ] + } + ] }, "pull_request_review": { "description": "Pull request review event trigger that runs when a pull request review is submitted, edited, or dismissed", @@ -1728,6 +1847,7 @@ } }, "network": { + "$comment": "Strict mode requirements: When strict=true, the 'network' field must be present (not null/undefined) and cannot contain wildcard '*' in allowed domains. This is validated in Go code (pkg/workflow/strict_mode_validation.go) via validateStrictNetwork().", "description": "Network access control for AI engines using ecosystem identifiers and domain allowlists. Controls web fetch and search capabilities.", "examples": [ "defaults", @@ -1761,7 +1881,8 @@ "items": { "type": "string", "description": "Domain name or ecosystem identifier (supports wildcards like '*.example.com' and ecosystem names like 'python', 'node')" - } + }, + "$comment": "Empty array is valid and means deny all network access. Omit the field entirely or use network: defaults to use default network permissions." }, "firewall": { "description": "AWF (Agent Workflow Firewall) configuration for network egress control. Only supported for Copilot engine.", @@ -1986,12 +2107,14 @@ "properties": { "command": { "type": "string", - "description": "Custom command to execute the MCP gateway (mutually exclusive with 'container')" + "$comment": "Mutually exclusive with 'container' - only one execution mode can be specified.", + "description": "Custom command to execute the MCP gateway" }, "container": { "type": "string", "pattern": "^[a-zA-Z0-9][a-zA-Z0-9/:_.-]*$", - "description": "Container image for the MCP gateway executable (mutually exclusive with 'command')" + "$comment": "Mutually exclusive with 'command' - only one execution mode can be specified.", + "description": "Container image for the MCP gateway executable" }, "version": { "type": ["string", "number"], @@ -2010,7 +2133,8 @@ "items": { "type": "string" }, - "description": "Arguments to add after the container image (container entrypoint arguments, only valid with 'container')" + "$comment": "Requires 'container' to be specified - entrypoint arguments only apply to container execution.", + "description": "Arguments to add after the container image (container entrypoint arguments)" }, "env": { "type": "object", @@ -2034,7 +2158,35 @@ "description": "API key for authenticating with the MCP gateway (supports ${{ secrets.* }} syntax)" } }, - "additionalProperties": false + "additionalProperties": false, + "anyOf": [ + { + "required": ["command"] + }, + { + "required": ["container"] + } + ], + "not": { + "allOf": [ + { + "required": ["command"] + }, + { + "required": ["container"] + } + ] + }, + "allOf": [ + { + "if": { + "required": ["entrypointArgs"] + }, + "then": { + "required": ["container"] + } + } + ] } }, "additionalProperties": false @@ -2268,7 +2420,9 @@ "stargazers", "users" ] - } + }, + "minItems": 1, + "$comment": "At least one toolset is required when toolsets array is specified. Use null or omit the field to use all toolsets." } }, "additionalProperties": false, @@ -3017,6 +3171,7 @@ }, "safe-outputs": { "type": "object", + "$comment": "Strict mode dependency: When strict=true AND permissions contains write values (contents:write, issues:write, or pull-requests:write), safe-outputs must be configured. This relationship is validated in Go code (pkg/workflow/strict_mode_validation.go) via validateStrictPermissions() because it requires complex logic to check if ANY permission property equals 'write', which cannot be expressed concisely in JSON Schema.", "description": "Safe output processing configuration that automatically creates GitHub issues, comments, and pull requests from AI workflow output without requiring write permissions in the main job", "$comment": "Required if workflow creates or modifies GitHub resources. Operations requiring safe-outputs: add-comment, add-labels, add-reviewer, assign-milestone, assign-to-agent, close-discussion, close-issue, close-pull-request, create-agent-task, create-code-scanning-alert, create-discussion, create-issue, create-pull-request, create-pull-request-review-comment, hide-comment, link-sub-issue, missing-tool, noop, push-to-pull-request-branch, threat-detection, update-discussion, update-issue, update-project, update-pull-request, update-release, upload-asset. See documentation for complete details.", "properties": { @@ -4723,6 +4878,7 @@ "strict": { "type": "boolean", "default": true, + "$comment": "Strict mode enforces several security constraints that are validated in Go code (pkg/workflow/strict_mode_validation.go) rather than JSON Schema: (1) Write Permissions + Safe Outputs: When strict=true AND permissions contains write values (contents:write, issues:write, pull-requests:write), safe-outputs must be configured. This relationship is too complex for JSON Schema as it requires checking if ANY permission property has a 'write' value. (2) Network Requirements: When strict=true, the 'network' field must be present and cannot contain wildcard '*'. (3) MCP Container Network: Custom MCP servers with containers require explicit network configuration. (4) Action Pinning: Actions must be pinned to commit SHAs. These are enforced during compilation via validateStrictMode().", "description": "Enable strict mode validation for enhanced security and compliance. Strict mode enforces: (1) Write Permissions - refuses contents:write, issues:write, pull-requests:write; requires safe-outputs instead, (2) Network Configuration - requires explicit network configuration with no wildcard '*' in allowed domains, (3) Action Pinning - enforces actions pinned to commit SHAs instead of tags/branches, (4) MCP Network - requires network configuration for custom MCP servers with containers, (5) Deprecated Fields - refuses deprecated frontmatter fields. Can be enabled per-workflow via 'strict: true' in frontmatter, or disabled via 'strict: false'. CLI flag takes precedence over frontmatter (gh aw compile --strict enforces strict mode). Defaults to true. See: https://githubnext.github.io/gh-aw/reference/frontmatter/#strict-mode-strict", "examples": [true, false] }, @@ -4982,6 +5138,16 @@ } }, "required": ["pull_request_review_comment"] + }, + { + "properties": { + "label": { + "not": { + "type": "null" + } + } + }, + "required": ["label"] } ] } @@ -5160,12 +5326,14 @@ "command": { "type": "string", "minLength": 1, + "$comment": "Mutually exclusive with 'container' - only one execution mode can be specified. Validated by 'not.allOf' constraint below.", "description": "Command for stdio MCP connections" }, "container": { "type": "string", "pattern": "^[a-zA-Z0-9][a-zA-Z0-9/:_.-]*$", - "description": "Container image for stdio MCP connections (alternative to command)" + "$comment": "Mutually exclusive with 'command' - only one execution mode can be specified. Validated by 'not.allOf' constraint below.", + "description": "Container image for stdio MCP connections" }, "version": { "type": ["string", "number"], @@ -5198,6 +5366,7 @@ }, "network": { "type": "object", + "$comment": "Requires 'container' to be specified - network configuration only applies to container-based MCP servers. Validated by 'if/then' constraint in 'allOf' below.", "properties": { "allowed": { "type": "array", @@ -5230,6 +5399,7 @@ } }, "additionalProperties": false, + "$comment": "Validation constraints: (1) Mutual exclusion: 'command' and 'container' cannot both be specified. (2) Requirement: Either 'command' or 'container' must be provided (via 'anyOf'). (3) Dependency: 'network' requires 'container' (validated in 'allOf'). (4) Type constraint: When 'type' is 'stdio' or 'local', either 'command' or 'container' is required.", "anyOf": [ { "required": ["type"] diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index 0621e9f0b27..0766e54f989 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -3204,7 +3204,7 @@ jobs: let previous; do { previous = s; - s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(//g, "").replace(//g, ""); + s = s.replace(/ and malformed + // Use a single regex that matches both patterns to avoid introducing comment markers + // when removing one pattern reveals another // Apply repeatedly to handle nested/overlapping patterns that could reintroduce comment markers let previous; do { previous = s; - s = s.replace(//g, "").replace(//g, ""); + // Single regex matches: OR + s = s.replace(/