diff --git a/.changeset/patch-harden-safe-output-field-validation.md b/.changeset/patch-harden-safe-output-field-validation.md new file mode 100644 index 00000000000..f078e54cbee --- /dev/null +++ b/.changeset/patch-harden-safe-output-field-validation.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Harden safe-output field validation so handlers only accept declared fields, comment reuse requires trusted allowlisted IDs, and privileged patch/upload processors derive sensitive metadata from trusted runtime state. diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index c210b219f4d..8daf934552f 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -733,9 +733,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -743,6 +752,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -867,6 +886,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/agentic-token-audit.lock.yml b/.github/workflows/agentic-token-audit.lock.yml index 45cc5b6af09..b9856c92519 100644 --- a/.github/workflows/agentic-token-audit.lock.yml +++ b/.github/workflows/agentic-token-audit.lock.yml @@ -765,6 +765,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/agentic-token-optimizer.lock.yml b/.github/workflows/agentic-token-optimizer.lock.yml index 051e78ae97c..a6b05abcf00 100644 --- a/.github/workflows/agentic-token-optimizer.lock.yml +++ b/.github/workflows/agentic-token-optimizer.lock.yml @@ -688,6 +688,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/approach-validator.lock.yml b/.github/workflows/approach-validator.lock.yml index a6fe743bf87..d53ab8ce065 100644 --- a/.github/workflows/approach-validator.lock.yml +++ b/.github/workflows/approach-validator.lock.yml @@ -677,9 +677,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -687,6 +696,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -778,6 +797,21 @@ jobs: "maxLength": 1024 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index c893f426e79..3dc0ee887e7 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -635,9 +635,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -645,6 +654,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/archivx-agentic-workflows-analyzer.lock.yml b/.github/workflows/archivx-agentic-workflows-analyzer.lock.yml index 2e1a83b2c96..170fbcf2dd1 100644 --- a/.github/workflows/archivx-agentic-workflows-analyzer.lock.yml +++ b/.github/workflows/archivx-agentic-workflows-analyzer.lock.yml @@ -751,6 +751,21 @@ jobs: "maxLength": 1024 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index d26724173dd..f0850d7d831 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -839,6 +839,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/avenger.lock.yml b/.github/workflows/avenger.lock.yml index 45ab4936eeb..d3291b5f507 100644 --- a/.github/workflows/avenger.lock.yml +++ b/.github/workflows/avenger.lock.yml @@ -646,6 +646,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index 7df3931de23..91501940b31 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -702,6 +702,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, @@ -740,6 +744,12 @@ jobs: "prepend" ] }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "pull_request_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml b/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml index a0825da475d..94aa095fe77 100644 --- a/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml +++ b/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml @@ -615,6 +615,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/ci-coach.lock.yml b/.github/workflows/ci-coach.lock.yml index 9280465d65f..ef3374b7d37 100644 --- a/.github/workflows/ci-coach.lock.yml +++ b/.github/workflows/ci-coach.lock.yml @@ -697,6 +697,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 177e4041c62..c108c775fbb 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -711,9 +711,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -721,6 +730,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index e81cf63ff07..67ff78106fe 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -798,9 +798,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -808,6 +817,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -844,6 +863,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/code-scanning-fixer.lock.yml b/.github/workflows/code-scanning-fixer.lock.yml index 204c8d415dd..7fe408d4ec2 100644 --- a/.github/workflows/code-scanning-fixer.lock.yml +++ b/.github/workflows/code-scanning-fixer.lock.yml @@ -651,6 +651,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/code-simplifier.lock.yml b/.github/workflows/code-simplifier.lock.yml index e8651621bc1..56a5ef9fb11 100644 --- a/.github/workflows/code-simplifier.lock.yml +++ b/.github/workflows/code-simplifier.lock.yml @@ -628,6 +628,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/contribution-check.lock.yml b/.github/workflows/contribution-check.lock.yml index 9f9b26aef1c..5a919ffccb5 100644 --- a/.github/workflows/contribution-check.lock.yml +++ b/.github/workflows/contribution-check.lock.yml @@ -680,9 +680,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -690,6 +699,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index 8c3152758a9..ced1a26c146 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -757,6 +757,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/copilot-centralization-optimizer.lock.yml b/.github/workflows/copilot-centralization-optimizer.lock.yml index 35380a82723..01fbb840c37 100644 --- a/.github/workflows/copilot-centralization-optimizer.lock.yml +++ b/.github/workflows/copilot-centralization-optimizer.lock.yml @@ -680,6 +680,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/copilot-cli-deep-research.lock.yml b/.github/workflows/copilot-cli-deep-research.lock.yml index e4549eddd60..e2bab2441a1 100644 --- a/.github/workflows/copilot-cli-deep-research.lock.yml +++ b/.github/workflows/copilot-cli-deep-research.lock.yml @@ -681,6 +681,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index 29900d11b92..48c46fcac07 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -745,6 +745,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 31d78695db6..45f2807c4f8 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -709,6 +709,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 67f2d7d1d66..d7102b3c77f 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1a205bf3354b3cf4ede0f23f8443a2f0dd553ee0a66727371426742bf0733e68","body_hash":"2a96a21304b975c51504dd556731857febd942d37b53e1fcc5b42a29855ab23e","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.223"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1a205bf3354b3cf4ede0f23f8443a2f0dd553ee0a66727371426742bf0733e68","body_hash":"2a96a21304b975c51504dd556731857febd942d37b53e1fcc5b42a29855ab23e","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.224"}} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44","digest":"sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -755,6 +755,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index 3826033cd53..0a1a288ac42 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -640,9 +640,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -650,6 +659,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -726,6 +745,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/daily-agent-of-the-day-blog-writer.lock.yml b/.github/workflows/daily-agent-of-the-day-blog-writer.lock.yml index 1ab66c54ad1..4bc29fa003a 100644 --- a/.github/workflows/daily-agent-of-the-day-blog-writer.lock.yml +++ b/.github/workflows/daily-agent-of-the-day-blog-writer.lock.yml @@ -715,6 +715,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -780,6 +784,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/daily-architecture-diagram.lock.yml b/.github/workflows/daily-architecture-diagram.lock.yml index 31e7c119733..2d928f35f25 100644 --- a/.github/workflows/daily-architecture-diagram.lock.yml +++ b/.github/workflows/daily-architecture-diagram.lock.yml @@ -727,6 +727,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-assign-issue-to-user.lock.yml b/.github/workflows/daily-assign-issue-to-user.lock.yml index a012ce2cf59..8abbe17d4c9 100644 --- a/.github/workflows/daily-assign-issue-to-user.lock.yml +++ b/.github/workflows/daily-assign-issue-to-user.lock.yml @@ -564,9 +564,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -574,6 +583,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml b/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml index 70bf83abaae..3429149c180 100644 --- a/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml +++ b/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml @@ -647,6 +647,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml b/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml index 399b8dfd80c..02e5fa2fe27 100644 --- a/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml +++ b/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml @@ -672,6 +672,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/daily-caveman-optimizer.lock.yml b/.github/workflows/daily-caveman-optimizer.lock.yml index 035fc71590d..fa8d20ae186 100644 --- a/.github/workflows/daily-caveman-optimizer.lock.yml +++ b/.github/workflows/daily-caveman-optimizer.lock.yml @@ -666,6 +666,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-cli-performance.lock.yml b/.github/workflows/daily-cli-performance.lock.yml index 68c64b8a459..5686a8a1cd1 100644 --- a/.github/workflows/daily-cli-performance.lock.yml +++ b/.github/workflows/daily-cli-performance.lock.yml @@ -637,9 +637,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -647,6 +656,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -771,6 +790,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/daily-code-debt-aider.lock.yml b/.github/workflows/daily-code-debt-aider.lock.yml index a4dcdfaf23b..765fbda7d7f 100644 --- a/.github/workflows/daily-code-debt-aider.lock.yml +++ b/.github/workflows/daily-code-debt-aider.lock.yml @@ -595,6 +595,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-community-attribution.lock.yml b/.github/workflows/daily-community-attribution.lock.yml index 5af21a3c59c..29c593afdd6 100644 --- a/.github/workflows/daily-community-attribution.lock.yml +++ b/.github/workflows/daily-community-attribution.lock.yml @@ -726,6 +726,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -791,6 +795,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml b/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml index 2b0cdba8700..203fa298dd6 100644 --- a/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml +++ b/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml @@ -633,6 +633,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-doc-healer.lock.yml b/.github/workflows/daily-doc-healer.lock.yml index 450b663ebd2..d27b6ba5545 100644 --- a/.github/workflows/daily-doc-healer.lock.yml +++ b/.github/workflows/daily-doc-healer.lock.yml @@ -738,6 +738,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index 6c472143165..b3ea7374b26 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -670,6 +670,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-elixir-credo-snippet-audit.lock.yml b/.github/workflows/daily-elixir-credo-snippet-audit.lock.yml index 4559a1c5967..632ccd8a943 100644 --- a/.github/workflows/daily-elixir-credo-snippet-audit.lock.yml +++ b/.github/workflows/daily-elixir-credo-snippet-audit.lock.yml @@ -602,6 +602,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-experiment-report.lock.yml b/.github/workflows/daily-experiment-report.lock.yml index 2d1e3b28ae2..4dbb9cf174f 100644 --- a/.github/workflows/daily-experiment-report.lock.yml +++ b/.github/workflows/daily-experiment-report.lock.yml @@ -631,9 +631,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -641,6 +650,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/daily-fact.lock.yml b/.github/workflows/daily-fact.lock.yml index db9fafb3f31..9bc6c84726a 100644 --- a/.github/workflows/daily-fact.lock.yml +++ b/.github/workflows/daily-fact.lock.yml @@ -761,9 +761,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -771,6 +780,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/daily-formal-spec-verifier.lock.yml b/.github/workflows/daily-formal-spec-verifier.lock.yml index afc8fd501c2..5670110a303 100644 --- a/.github/workflows/daily-formal-spec-verifier.lock.yml +++ b/.github/workflows/daily-formal-spec-verifier.lock.yml @@ -713,6 +713,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/daily-go-test-parallelizer.lock.yml b/.github/workflows/daily-go-test-parallelizer.lock.yml index 9a18bd63891..d6c86d1e99f 100644 --- a/.github/workflows/daily-go-test-parallelizer.lock.yml +++ b/.github/workflows/daily-go-test-parallelizer.lock.yml @@ -606,6 +606,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-go-test-stubs-aider.lock.yml b/.github/workflows/daily-go-test-stubs-aider.lock.yml index e02fc885bc3..6cf8d46983a 100644 --- a/.github/workflows/daily-go-test-stubs-aider.lock.yml +++ b/.github/workflows/daily-go-test-stubs-aider.lock.yml @@ -595,6 +595,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index e8119bc8b63..aaaec690161 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -760,6 +760,21 @@ jobs: "maxLength": 1024 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index 09c3004aa15..41f0668ac27 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -838,6 +838,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { @@ -854,6 +864,21 @@ jobs: } } }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } + }, "upload_asset": { "defaultMax": 10, "fields": { diff --git a/.github/workflows/daily-rendering-scripts-verifier.lock.yml b/.github/workflows/daily-rendering-scripts-verifier.lock.yml index 8a970e054d0..f6e0e06187d 100644 --- a/.github/workflows/daily-rendering-scripts-verifier.lock.yml +++ b/.github/workflows/daily-rendering-scripts-verifier.lock.yml @@ -738,6 +738,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-safe-output-integrator.lock.yml b/.github/workflows/daily-safe-output-integrator.lock.yml index 6bfd31ef05a..a6edae23cd1 100644 --- a/.github/workflows/daily-safe-output-integrator.lock.yml +++ b/.github/workflows/daily-safe-output-integrator.lock.yml @@ -626,6 +626,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-safeoutputs-git-simulator.lock.yml b/.github/workflows/daily-safeoutputs-git-simulator.lock.yml index 55f2d978d29..3c2374119d0 100644 --- a/.github/workflows/daily-safeoutputs-git-simulator.lock.yml +++ b/.github/workflows/daily-safeoutputs-git-simulator.lock.yml @@ -665,6 +665,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -730,6 +734,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "push_to_pull_request_branch": { "defaultMax": 1, "fields": { @@ -746,6 +760,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/daily-sentrux-report.lock.yml b/.github/workflows/daily-sentrux-report.lock.yml index 29cb835239d..a83b31dc162 100644 --- a/.github/workflows/daily-sentrux-report.lock.yml +++ b/.github/workflows/daily-sentrux-report.lock.yml @@ -684,6 +684,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/daily-testify-uber-super-expert.lock.yml b/.github/workflows/daily-testify-uber-super-expert.lock.yml index 123e2202cc8..8197e7dc700 100644 --- a/.github/workflows/daily-testify-uber-super-expert.lock.yml +++ b/.github/workflows/daily-testify-uber-super-expert.lock.yml @@ -698,6 +698,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/daily-workflow-updater.lock.yml b/.github/workflows/daily-workflow-updater.lock.yml index 0665b81c828..08ea2265bdf 100644 --- a/.github/workflows/daily-workflow-updater.lock.yml +++ b/.github/workflows/daily-workflow-updater.lock.yml @@ -592,6 +592,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-yamllint-fixer.lock.yml b/.github/workflows/daily-yamllint-fixer.lock.yml index 4d3225ffbc3..98717fe3fc9 100644 --- a/.github/workflows/daily-yamllint-fixer.lock.yml +++ b/.github/workflows/daily-yamllint-fixer.lock.yml @@ -622,6 +622,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/dataflow-pr-discussion-dataset.lock.yml b/.github/workflows/dataflow-pr-discussion-dataset.lock.yml index d29d9f51845..e8b3255b39a 100644 --- a/.github/workflows/dataflow-pr-discussion-dataset.lock.yml +++ b/.github/workflows/dataflow-pr-discussion-dataset.lock.yml @@ -1037,6 +1037,21 @@ jobs: "maxLength": 1024 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/dead-code-remover.lock.yml b/.github/workflows/dead-code-remover.lock.yml index 816a510702e..2acfa9f35aa 100644 --- a/.github/workflows/dead-code-remover.lock.yml +++ b/.github/workflows/dead-code-remover.lock.yml @@ -646,6 +646,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index dabcf9ac2d4..94b45dc1fc0 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -958,9 +958,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -968,6 +977,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -1092,6 +1111,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { @@ -1107,6 +1136,21 @@ jobs: "maxLength": 1024 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/delight.lock.yml b/.github/workflows/delight.lock.yml index b7b811c9754..2f3538ab804 100644 --- a/.github/workflows/delight.lock.yml +++ b/.github/workflows/delight.lock.yml @@ -712,6 +712,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/dependabot-burner.lock.yml b/.github/workflows/dependabot-burner.lock.yml index d84a8fc9fc7..3374d6fd41c 100644 --- a/.github/workflows/dependabot-burner.lock.yml +++ b/.github/workflows/dependabot-burner.lock.yml @@ -664,9 +664,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -674,6 +683,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -710,6 +729,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/dependabot-go-checker.lock.yml b/.github/workflows/dependabot-go-checker.lock.yml index 87ab9274ae8..07cfa8b0d71 100644 --- a/.github/workflows/dependabot-go-checker.lock.yml +++ b/.github/workflows/dependabot-go-checker.lock.yml @@ -636,6 +636,9 @@ jobs: ], "x-strip-on-error": true }, + "duplicate_of": { + "issueOrPRNumber": true + }, "issue_number": { "optionalPositiveInteger": true }, diff --git a/.github/workflows/design-decision-gate.lock.yml b/.github/workflows/design-decision-gate.lock.yml index debda8b9005..890fcb07f90 100644 --- a/.github/workflows/design-decision-gate.lock.yml +++ b/.github/workflows/design-decision-gate.lock.yml @@ -694,9 +694,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -704,6 +713,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -780,6 +799,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index a31f935abeb..b88f235e7ca 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -672,9 +672,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -682,6 +691,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index f18f47cd3df..d6ed76a5cca 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -690,6 +690,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -755,6 +759,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index 38dadae1c6b..c1a34e3856b 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -594,6 +594,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/draft-pr-cleanup.lock.yml b/.github/workflows/draft-pr-cleanup.lock.yml index 68715e0a2be..30506e5d26d 100644 --- a/.github/workflows/draft-pr-cleanup.lock.yml +++ b/.github/workflows/draft-pr-cleanup.lock.yml @@ -567,9 +567,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -577,6 +586,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/eslint-miner.lock.yml b/.github/workflows/eslint-miner.lock.yml index d644213fe11..7639bb7975b 100644 --- a/.github/workflows/eslint-miner.lock.yml +++ b/.github/workflows/eslint-miner.lock.yml @@ -607,6 +607,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/eslint-monster.lock.yml b/.github/workflows/eslint-monster.lock.yml index 76f5253a77a..526ca5fbb0a 100644 --- a/.github/workflows/eslint-monster.lock.yml +++ b/.github/workflows/eslint-monster.lock.yml @@ -628,6 +628,9 @@ jobs: ], "x-strip-on-error": true }, + "duplicate_of": { + "issueOrPRNumber": true + }, "issue_number": { "optionalPositiveInteger": true }, diff --git a/.github/workflows/eslint-refiner.lock.yml b/.github/workflows/eslint-refiner.lock.yml index 7bcbafbaed3..4729aad0f1c 100644 --- a/.github/workflows/eslint-refiner.lock.yml +++ b/.github/workflows/eslint-refiner.lock.yml @@ -714,6 +714,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/evoskill-evolver.lock.yml b/.github/workflows/evoskill-evolver.lock.yml index 07637d5ec26..e7bf508be42 100644 --- a/.github/workflows/evoskill-evolver.lock.yml +++ b/.github/workflows/evoskill-evolver.lock.yml @@ -595,6 +595,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/firewall-escape.lock.yml b/.github/workflows/firewall-escape.lock.yml index 14d39ed4431..e65a4088e9b 100644 --- a/.github/workflows/firewall-escape.lock.yml +++ b/.github/workflows/firewall-escape.lock.yml @@ -718,6 +718,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/functional-pragmatist.lock.yml b/.github/workflows/functional-pragmatist.lock.yml index 4712bb31cb8..fd27e8bf683 100644 --- a/.github/workflows/functional-pragmatist.lock.yml +++ b/.github/workflows/functional-pragmatist.lock.yml @@ -600,6 +600,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index d63511abf02..4a97cc141a6 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -659,6 +659,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index 67f49bc39e3..bf58259f7da 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -690,6 +690,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -755,6 +759,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index 1f1bed796a8..250120d67e0 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -649,6 +649,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/grumpy-reviewer.lock.yml b/.github/workflows/grumpy-reviewer.lock.yml index eadc9e24f26..9dfb120ae41 100644 --- a/.github/workflows/grumpy-reviewer.lock.yml +++ b/.github/workflows/grumpy-reviewer.lock.yml @@ -641,6 +641,53 @@ jobs: } GH_AW_VALIDATION_JSON: | { + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_pull_request_review_comment": { "defaultMax": 1, "fields": { diff --git a/.github/workflows/hourly-ci-cleaner.lock.yml b/.github/workflows/hourly-ci-cleaner.lock.yml index ef46af381ca..78bedae174e 100644 --- a/.github/workflows/hourly-ci-cleaner.lock.yml +++ b/.github/workflows/hourly-ci-cleaner.lock.yml @@ -653,6 +653,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/impeccable-skills-reviewer.lock.yml b/.github/workflows/impeccable-skills-reviewer.lock.yml index f8810b0c7aa..6a219dd2846 100644 --- a/.github/workflows/impeccable-skills-reviewer.lock.yml +++ b/.github/workflows/impeccable-skills-reviewer.lock.yml @@ -640,9 +640,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -650,6 +659,63 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } + }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 } } }, diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index 2117f0f16a7..04b12381faa 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -625,6 +625,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index c58cc94cd8b..df338d72eb3 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -1051,9 +1051,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -1061,6 +1070,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/issue-triage-agent.lock.yml b/.github/workflows/issue-triage-agent.lock.yml index ebff1d6899b..c4e30018daa 100644 --- a/.github/workflows/issue-triage-agent.lock.yml +++ b/.github/workflows/issue-triage-agent.lock.yml @@ -718,9 +718,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -728,6 +737,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } }, "dataEnabled": true, diff --git a/.github/workflows/jsweep.lock.yml b/.github/workflows/jsweep.lock.yml index b357ba83328..f83ce2816f6 100644 --- a/.github/workflows/jsweep.lock.yml +++ b/.github/workflows/jsweep.lock.yml @@ -639,6 +639,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/layout-spec-maintainer.lock.yml b/.github/workflows/layout-spec-maintainer.lock.yml index 8514571bd18..79f84e36114 100644 --- a/.github/workflows/layout-spec-maintainer.lock.yml +++ b/.github/workflows/layout-spec-maintainer.lock.yml @@ -609,6 +609,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/lint-monster.lock.yml b/.github/workflows/lint-monster.lock.yml index f030c2caaea..1feae8e3ca8 100644 --- a/.github/workflows/lint-monster.lock.yml +++ b/.github/workflows/lint-monster.lock.yml @@ -623,6 +623,9 @@ jobs: ], "x-strip-on-error": true }, + "duplicate_of": { + "issueOrPRNumber": true + }, "issue_number": { "optionalPositiveInteger": true }, diff --git a/.github/workflows/linter-miner.lock.yml b/.github/workflows/linter-miner.lock.yml index caafa0a851b..5e6681f38c7 100644 --- a/.github/workflows/linter-miner.lock.yml +++ b/.github/workflows/linter-miner.lock.yml @@ -635,6 +635,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/mattpocock-skills-reviewer.lock.yml b/.github/workflows/mattpocock-skills-reviewer.lock.yml index 2d7662490d9..74381a0366e 100644 --- a/.github/workflows/mattpocock-skills-reviewer.lock.yml +++ b/.github/workflows/mattpocock-skills-reviewer.lock.yml @@ -788,9 +788,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -798,6 +807,63 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } + }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 } } }, diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index da1ca2238f0..fbb1f4bc3b4 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -701,6 +701,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/metrics-collector.lock.yml b/.github/workflows/metrics-collector.lock.yml index 5dc2a489c07..fe29b80dec7 100644 --- a/.github/workflows/metrics-collector.lock.yml +++ b/.github/workflows/metrics-collector.lock.yml @@ -753,6 +753,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/necromancer.lock.yml b/.github/workflows/necromancer.lock.yml index 5f8c7efffa1..28b688fa5ed 100644 --- a/.github/workflows/necromancer.lock.yml +++ b/.github/workflows/necromancer.lock.yml @@ -652,9 +652,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -662,6 +671,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -738,6 +757,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/objective-impact-report.lock.yml b/.github/workflows/objective-impact-report.lock.yml index 8d1f5d3a9a8..7ba815e4bce 100644 --- a/.github/workflows/objective-impact-report.lock.yml +++ b/.github/workflows/objective-impact-report.lock.yml @@ -591,6 +591,9 @@ jobs: ], "x-strip-on-error": true }, + "duplicate_of": { + "issueOrPRNumber": true + }, "issue_number": { "optionalPositiveInteger": true }, diff --git a/.github/workflows/org-health-report.lock.yml b/.github/workflows/org-health-report.lock.yml index a4d5acb6326..4a56857c706 100644 --- a/.github/workflows/org-health-report.lock.yml +++ b/.github/workflows/org-health-report.lock.yml @@ -735,6 +735,21 @@ jobs: } } }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } + }, "upload_asset": { "defaultMax": 10, "fields": { diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 879da447787..e0d134528fc 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -679,9 +679,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -689,6 +698,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index 2cee4fa087d..752e888d5af 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -676,9 +676,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -686,6 +695,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -835,6 +854,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -971,6 +994,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, @@ -1040,6 +1067,21 @@ jobs: } }, "customValidation": "requiresOneOf:status,title,body,labels,assignees,milestone" + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/pr-code-quality-reviewer.lock.yml b/.github/workflows/pr-code-quality-reviewer.lock.yml index 026180bf9ed..9794a78dc8b 100644 --- a/.github/workflows/pr-code-quality-reviewer.lock.yml +++ b/.github/workflows/pr-code-quality-reviewer.lock.yml @@ -681,6 +681,53 @@ jobs: } GH_AW_VALIDATION_JSON: | { + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_pull_request_review_comment": { "defaultMax": 1, "fields": { diff --git a/.github/workflows/pr-description-caveman.lock.yml b/.github/workflows/pr-description-caveman.lock.yml index 514591c3e11..5a970d9355f 100644 --- a/.github/workflows/pr-description-caveman.lock.yml +++ b/.github/workflows/pr-description-caveman.lock.yml @@ -668,6 +668,12 @@ jobs: "prepend" ] }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "pull_request_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/pr-nitpick-reviewer.lock.yml b/.github/workflows/pr-nitpick-reviewer.lock.yml index c1291e06598..f6054fe3047 100644 --- a/.github/workflows/pr-nitpick-reviewer.lock.yml +++ b/.github/workflows/pr-nitpick-reviewer.lock.yml @@ -643,6 +643,53 @@ jobs: } GH_AW_VALIDATION_JSON: | { + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_discussion": { "defaultMax": 1, "fields": { diff --git a/.github/workflows/pr-sous-chef.lock.yml b/.github/workflows/pr-sous-chef.lock.yml index 29477a28a1a..7c841b6c680 100644 --- a/.github/workflows/pr-sous-chef.lock.yml +++ b/.github/workflows/pr-sous-chef.lock.yml @@ -911,9 +911,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -921,6 +930,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -1067,6 +1086,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, @@ -1114,6 +1137,12 @@ jobs: "prepend" ] }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "pull_request_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/pr-triage-agent.lock.yml b/.github/workflows/pr-triage-agent.lock.yml index 0722de15e77..17c6df30cfd 100644 --- a/.github/workflows/pr-triage-agent.lock.yml +++ b/.github/workflows/pr-triage-agent.lock.yml @@ -777,9 +777,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -787,6 +796,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } }, "dataEnabled": true, @@ -831,6 +850,53 @@ jobs: } } }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_issue": { "defaultMax": 1, "fields": { @@ -1012,6 +1078,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/purelock.lock.yml b/.github/workflows/purelock.lock.yml index 34b87850211..eae64ce8fea 100644 --- a/.github/workflows/purelock.lock.yml +++ b/.github/workflows/purelock.lock.yml @@ -652,6 +652,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/python-data-charts.lock.yml b/.github/workflows/python-data-charts.lock.yml index 837f1a24aac..4a575d34693 100644 --- a/.github/workflows/python-data-charts.lock.yml +++ b/.github/workflows/python-data-charts.lock.yml @@ -782,6 +782,21 @@ jobs: } } }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } + }, "upload_asset": { "defaultMax": 10, "fields": { diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index cb31ad7d87d..0a566028300 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -742,9 +742,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -752,6 +761,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -804,6 +823,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/refiner.lock.yml b/.github/workflows/refiner.lock.yml index 0fdfaba033c..e505281d32f 100644 --- a/.github/workflows/refiner.lock.yml +++ b/.github/workflows/refiner.lock.yml @@ -631,9 +631,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -641,6 +650,63 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } + }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 } } }, @@ -677,6 +743,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/ruflo-backed-task.lock.yml b/.github/workflows/ruflo-backed-task.lock.yml index d8b36ef38d6..33ba4b97ad3 100644 --- a/.github/workflows/ruflo-backed-task.lock.yml +++ b/.github/workflows/ruflo-backed-task.lock.yml @@ -630,9 +630,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -640,6 +649,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -713,6 +732,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/schema-feature-coverage.lock.yml b/.github/workflows/schema-feature-coverage.lock.yml index 2b2a6ab446b..b3677f8d2dc 100644 --- a/.github/workflows/schema-feature-coverage.lock.yml +++ b/.github/workflows/schema-feature-coverage.lock.yml @@ -618,6 +618,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index 2460e917027..aa74876243e 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -706,9 +706,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -716,6 +725,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/security-compliance.lock.yml b/.github/workflows/security-compliance.lock.yml index 8bf9d5932e0..e6cbb9bc257 100644 --- a/.github/workflows/security-compliance.lock.yml +++ b/.github/workflows/security-compliance.lock.yml @@ -696,6 +696,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/security-review.lock.yml b/.github/workflows/security-review.lock.yml index e7875e02368..ff822429976 100644 --- a/.github/workflows/security-review.lock.yml +++ b/.github/workflows/security-review.lock.yml @@ -858,6 +858,53 @@ jobs: } GH_AW_VALIDATION_JSON: | { + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_pull_request_review_comment": { "defaultMax": 1, "fields": { diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index 5b1fac35db7..5f2768f95b0 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -610,6 +610,9 @@ jobs: ], "x-strip-on-error": true }, + "duplicate_of": { + "issueOrPRNumber": true + }, "issue_number": { "optionalPositiveInteger": true }, diff --git a/.github/workflows/sergo.lock.yml b/.github/workflows/sergo.lock.yml index a4c80056b28..465fa732243 100644 --- a/.github/workflows/sergo.lock.yml +++ b/.github/workflows/sergo.lock.yml @@ -724,6 +724,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/skillet.lock.yml b/.github/workflows/skillet.lock.yml index 972a5572a89..a985b9d54e6 100644 --- a/.github/workflows/skillet.lock.yml +++ b/.github/workflows/skillet.lock.yml @@ -651,6 +651,53 @@ jobs: } GH_AW_VALIDATION_JSON: | { + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_pull_request_review_comment": { "defaultMax": 1, "fields": { diff --git a/.github/workflows/slide-deck-maintainer.lock.yml b/.github/workflows/slide-deck-maintainer.lock.yml index 5bb043d95ec..0e5b9895077 100644 --- a/.github/workflows/slide-deck-maintainer.lock.yml +++ b/.github/workflows/slide-deck-maintainer.lock.yml @@ -700,6 +700,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/smoke-agent-all-merged.lock.yml b/.github/workflows/smoke-agent-all-merged.lock.yml index 559d19a1ce8..561a8330ea3 100644 --- a/.github/workflows/smoke-agent-all-merged.lock.yml +++ b/.github/workflows/smoke-agent-all-merged.lock.yml @@ -644,9 +644,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -654,6 +663,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-agent-all-none.lock.yml b/.github/workflows/smoke-agent-all-none.lock.yml index 0aad8485436..6f3679df6c8 100644 --- a/.github/workflows/smoke-agent-all-none.lock.yml +++ b/.github/workflows/smoke-agent-all-none.lock.yml @@ -644,9 +644,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -654,6 +663,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-agent-public-approved.lock.yml b/.github/workflows/smoke-agent-public-approved.lock.yml index c3953afe144..06c9f0c96f1 100644 --- a/.github/workflows/smoke-agent-public-approved.lock.yml +++ b/.github/workflows/smoke-agent-public-approved.lock.yml @@ -649,9 +649,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -659,6 +668,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-agent-public-none.lock.yml b/.github/workflows/smoke-agent-public-none.lock.yml index 9a15f5b431b..52656571fb8 100644 --- a/.github/workflows/smoke-agent-public-none.lock.yml +++ b/.github/workflows/smoke-agent-public-none.lock.yml @@ -644,9 +644,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -654,6 +663,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-agent-scoped-approved.lock.yml b/.github/workflows/smoke-agent-scoped-approved.lock.yml index 61e0c120192..a04063390e9 100644 --- a/.github/workflows/smoke-agent-scoped-approved.lock.yml +++ b/.github/workflows/smoke-agent-scoped-approved.lock.yml @@ -647,9 +647,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -657,6 +666,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-aider.lock.yml b/.github/workflows/smoke-aider.lock.yml index 3c122b37166..481ca2a0a90 100644 --- a/.github/workflows/smoke-aider.lock.yml +++ b/.github/workflows/smoke-aider.lock.yml @@ -620,9 +620,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -630,6 +639,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-checkout-pr-dispatch.lock.yml b/.github/workflows/smoke-checkout-pr-dispatch.lock.yml index 162d0275466..030db05a8f3 100644 --- a/.github/workflows/smoke-checkout-pr-dispatch.lock.yml +++ b/.github/workflows/smoke-checkout-pr-dispatch.lock.yml @@ -638,9 +638,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -648,6 +657,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-ci.lock.yml b/.github/workflows/smoke-ci.lock.yml index f40c673566c..ee5d9a9a997 100644 --- a/.github/workflows/smoke-ci.lock.yml +++ b/.github/workflows/smoke-ci.lock.yml @@ -672,9 +672,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -682,6 +691,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -820,6 +839,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "remove_labels": { "defaultMax": 5, "fields": { @@ -922,6 +951,12 @@ jobs: "prepend" ] }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "pull_request_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-claude-on-copilot.lock.yml b/.github/workflows/smoke-claude-on-copilot.lock.yml index e300fbe7212..224c93eaaa4 100644 --- a/.github/workflows/smoke-claude-on-copilot.lock.yml +++ b/.github/workflows/smoke-claude-on-copilot.lock.yml @@ -607,9 +607,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -617,6 +626,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index f23e6515584..bed1419af44 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -954,9 +954,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -964,6 +973,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -1026,6 +1045,53 @@ jobs: } } }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_code_scanning_alert": { "defaultMax": 40, "fields": { @@ -1214,6 +1280,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, @@ -1286,6 +1356,12 @@ jobs: "prepend" ] }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "pull_request_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index e19114e66a4..059c5d6fa32 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -770,9 +770,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -780,6 +789,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml index 970523d4156..db8ef4e87b5 100644 --- a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml @@ -898,9 +898,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -908,6 +917,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -952,6 +971,53 @@ jobs: } } }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_discussion": { "defaultMax": 1, "fields": { @@ -1248,6 +1314,21 @@ jobs: "maxLength": 256 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/smoke-copilot-aoai-entra.lock.yml b/.github/workflows/smoke-copilot-aoai-entra.lock.yml index 4a2f62bcd98..5d4a2d211b3 100644 --- a/.github/workflows/smoke-copilot-aoai-entra.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-entra.lock.yml @@ -915,9 +915,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -925,6 +934,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -969,6 +988,53 @@ jobs: } } }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_discussion": { "defaultMax": 1, "fields": { @@ -1265,6 +1331,21 @@ jobs: "maxLength": 256 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/smoke-copilot-arm.lock.yml b/.github/workflows/smoke-copilot-arm.lock.yml index 7e2af616adf..7b328694a32 100644 --- a/.github/workflows/smoke-copilot-arm.lock.yml +++ b/.github/workflows/smoke-copilot-arm.lock.yml @@ -815,9 +815,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -825,6 +834,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-copilot-auto.lock.yml b/.github/workflows/smoke-copilot-auto.lock.yml index 38af2165164..1f7897b3e41 100644 --- a/.github/workflows/smoke-copilot-auto.lock.yml +++ b/.github/workflows/smoke-copilot-auto.lock.yml @@ -592,9 +592,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -602,6 +611,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-copilot-mai.lock.yml b/.github/workflows/smoke-copilot-mai.lock.yml index 90fa543bbc0..75ce95dec03 100644 --- a/.github/workflows/smoke-copilot-mai.lock.yml +++ b/.github/workflows/smoke-copilot-mai.lock.yml @@ -627,9 +627,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -637,6 +646,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 32407a95727..84461f763b3 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -918,9 +918,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -928,6 +937,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -972,6 +991,53 @@ jobs: } } }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_discussion": { "defaultMax": 1, "fields": { @@ -1268,6 +1334,21 @@ jobs: "maxLength": 256 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/smoke-create-cross-repo-pr.lock.yml b/.github/workflows/smoke-create-cross-repo-pr.lock.yml index 2a2c8aa18a8..797981300ac 100644 --- a/.github/workflows/smoke-create-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-create-cross-repo-pr.lock.yml @@ -671,9 +671,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -681,6 +690,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -754,6 +773,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/smoke-crush.lock.yml b/.github/workflows/smoke-crush.lock.yml index 2c6cfa943d0..b286447cba9 100644 --- a/.github/workflows/smoke-crush.lock.yml +++ b/.github/workflows/smoke-crush.lock.yml @@ -639,9 +639,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -649,6 +658,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-cursor.lock.yml b/.github/workflows/smoke-cursor.lock.yml index d72d62c3ceb..82ea83c14a2 100644 --- a/.github/workflows/smoke-cursor.lock.yml +++ b/.github/workflows/smoke-cursor.lock.yml @@ -638,9 +638,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -648,6 +657,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-gemini.lock.yml b/.github/workflows/smoke-gemini.lock.yml index b742cc9419d..52e9c54abab 100644 --- a/.github/workflows/smoke-gemini.lock.yml +++ b/.github/workflows/smoke-gemini.lock.yml @@ -705,9 +705,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -715,6 +724,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-github-claude.lock.yml b/.github/workflows/smoke-github-claude.lock.yml index 4f92aaeac0e..61c7f6e5a48 100644 --- a/.github/workflows/smoke-github-claude.lock.yml +++ b/.github/workflows/smoke-github-claude.lock.yml @@ -607,9 +607,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -617,6 +626,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-goose.lock.yml b/.github/workflows/smoke-goose.lock.yml index 5abe748488a..c477aaad626 100644 --- a/.github/workflows/smoke-goose.lock.yml +++ b/.github/workflows/smoke-goose.lock.yml @@ -633,9 +633,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -643,6 +652,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-kiro.lock.yml b/.github/workflows/smoke-kiro.lock.yml index 98b01e6ef44..9421ea6b523 100644 --- a/.github/workflows/smoke-kiro.lock.yml +++ b/.github/workflows/smoke-kiro.lock.yml @@ -638,9 +638,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -648,6 +657,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-multi-pr.lock.yml b/.github/workflows/smoke-multi-pr.lock.yml index 0428592bcc7..8f7ad17f0e6 100644 --- a/.github/workflows/smoke-multi-pr.lock.yml +++ b/.github/workflows/smoke-multi-pr.lock.yml @@ -636,9 +636,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -646,6 +655,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -682,6 +701,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index c67155ce545..36e8644da0a 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -646,9 +646,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -656,6 +665,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-pi.lock.yml b/.github/workflows/smoke-pi.lock.yml index c3e3d0321c4..3913ce9fee0 100644 --- a/.github/workflows/smoke-pi.lock.yml +++ b/.github/workflows/smoke-pi.lock.yml @@ -669,9 +669,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -679,6 +688,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-project.lock.yml b/.github/workflows/smoke-project.lock.yml index 391f69003eb..5afc68f711a 100644 --- a/.github/workflows/smoke-project.lock.yml +++ b/.github/workflows/smoke-project.lock.yml @@ -687,9 +687,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -697,6 +706,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -825,6 +844,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -936,32 +959,60 @@ jobs: "draft_issue" ] }, + "create_if_missing": { + "type": "boolean" + }, "draft_body": { "type": "string", "sanitize": true, "maxLength": 65000 }, + "draft_issue_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "draft_title": { "type": "string", "sanitize": true, "maxLength": 256 }, + "field_definitions": { + "type": "array" + }, "fields": { "type": "object" }, "issue": { "optionalPositiveInteger": true }, + "operation": { + "type": "string", + "enum": [ + "create_fields", + "create_view" + ] + }, "project": { "required": true, "type": "string", "sanitize": true, "maxLength": 512, - "pattern": "^https://[^/]+/(orgs|users)/[^/]+/projects/\\d+", - "patternError": "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42)" + "pattern": "^(https://[^/]+/(orgs|users)/[^/]+/projects/\\d+|#?aw_[A-Za-z0-9_]{3,12})$", + "patternError": "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42) or temporary project ID (e.g., #aw_project1)" }, "pull_request": { "optionalPositiveInteger": true + }, + "target_repo": { + "type": "string", + "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, + "view": { + "type": "object" } } } diff --git a/.github/workflows/smoke-pydantic.lock.yml b/.github/workflows/smoke-pydantic.lock.yml index 06170cd9a15..934df1d8cbf 100644 --- a/.github/workflows/smoke-pydantic.lock.yml +++ b/.github/workflows/smoke-pydantic.lock.yml @@ -614,9 +614,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -624,6 +633,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-service-ports.lock.yml b/.github/workflows/smoke-service-ports.lock.yml index 768827114c8..49ec46c6f37 100644 --- a/.github/workflows/smoke-service-ports.lock.yml +++ b/.github/workflows/smoke-service-ports.lock.yml @@ -630,9 +630,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -640,6 +649,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-temporary-id.lock.yml b/.github/workflows/smoke-temporary-id.lock.yml index 8a61d4c4740..88c3744777d 100644 --- a/.github/workflows/smoke-temporary-id.lock.yml +++ b/.github/workflows/smoke-temporary-id.lock.yml @@ -675,9 +675,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -685,6 +694,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-test-tools.lock.yml b/.github/workflows/smoke-test-tools.lock.yml index 08a75659d52..7750ae2bcbf 100644 --- a/.github/workflows/smoke-test-tools.lock.yml +++ b/.github/workflows/smoke-test-tools.lock.yml @@ -662,9 +662,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -672,6 +681,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-update-cross-repo-pr.lock.yml b/.github/workflows/smoke-update-cross-repo-pr.lock.yml index 9637e1aa05c..95d50d99e69 100644 --- a/.github/workflows/smoke-update-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-update-cross-repo-pr.lock.yml @@ -705,9 +705,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -715,6 +724,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -828,6 +847,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/smoke-workflow-call.lock.yml b/.github/workflows/smoke-workflow-call.lock.yml index 8a2b8588d8f..8544bdb437c 100644 --- a/.github/workflows/smoke-workflow-call.lock.yml +++ b/.github/workflows/smoke-workflow-call.lock.yml @@ -652,9 +652,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -662,6 +671,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/spec-enforcer.lock.yml b/.github/workflows/spec-enforcer.lock.yml index 7a4a2c239ba..8b8598b2493 100644 --- a/.github/workflows/spec-enforcer.lock.yml +++ b/.github/workflows/spec-enforcer.lock.yml @@ -632,6 +632,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/spec-extractor.lock.yml b/.github/workflows/spec-extractor.lock.yml index 2bfad91543e..0d50dde77d9 100644 --- a/.github/workflows/spec-extractor.lock.yml +++ b/.github/workflows/spec-extractor.lock.yml @@ -632,6 +632,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/stale-pr-cleanup.lock.yml b/.github/workflows/stale-pr-cleanup.lock.yml index 1c56172c113..055a4803e72 100644 --- a/.github/workflows/stale-pr-cleanup.lock.yml +++ b/.github/workflows/stale-pr-cleanup.lock.yml @@ -566,9 +566,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -576,6 +585,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/stale-repo-identifier.lock.yml b/.github/workflows/stale-repo-identifier.lock.yml index 7846667f65f..7bbd21805ba 100644 --- a/.github/workflows/stale-repo-identifier.lock.yml +++ b/.github/workflows/stale-repo-identifier.lock.yml @@ -727,9 +727,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -737,6 +746,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -877,6 +896,21 @@ jobs: } } }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } + }, "upload_asset": { "defaultMax": 10, "fields": { diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index ae603e1df4f..95a256d666b 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -694,9 +694,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -704,6 +713,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/sub-issue-closer.lock.yml b/.github/workflows/sub-issue-closer.lock.yml index ead02790d74..0ff8b06c923 100644 --- a/.github/workflows/sub-issue-closer.lock.yml +++ b/.github/workflows/sub-issue-closer.lock.yml @@ -570,9 +570,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -580,6 +589,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index 3154427e1fc..f57a3b5a2f8 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -660,9 +660,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -670,6 +679,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -706,6 +725,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -771,6 +794,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { @@ -786,6 +819,21 @@ jobs: "maxLength": 1024 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/test-quality-sentinel.lock.yml b/.github/workflows/test-quality-sentinel.lock.yml index 8c795015df2..16572cdb6ca 100644 --- a/.github/workflows/test-quality-sentinel.lock.yml +++ b/.github/workflows/test-quality-sentinel.lock.yml @@ -693,9 +693,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -703,6 +712,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index f24487ee4e4..f99d70759e0 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -674,6 +674,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -755,6 +759,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/ubuntu-image-analyzer.lock.yml b/.github/workflows/ubuntu-image-analyzer.lock.yml index d5b4c62dc02..0fe9100e18f 100644 --- a/.github/workflows/ubuntu-image-analyzer.lock.yml +++ b/.github/workflows/ubuntu-image-analyzer.lock.yml @@ -611,6 +611,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 58ca88524eb..eeab7226f51 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -674,9 +674,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -684,6 +693,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -720,6 +739,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/update-astro.lock.yml b/.github/workflows/update-astro.lock.yml index 0c9ab5a3ee6..471bb7d9aab 100644 --- a/.github/workflows/update-astro.lock.yml +++ b/.github/workflows/update-astro.lock.yml @@ -616,6 +616,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/visual-regression-checker.lock.yml b/.github/workflows/visual-regression-checker.lock.yml index 66ec9959edb..7fae822653d 100644 --- a/.github/workflows/visual-regression-checker.lock.yml +++ b/.github/workflows/visual-regression-checker.lock.yml @@ -650,9 +650,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -660,6 +669,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/weekly-blog-post-writer.lock.yml b/.github/workflows/weekly-blog-post-writer.lock.yml index 997ee44c143..fcc891979f2 100644 --- a/.github/workflows/weekly-blog-post-writer.lock.yml +++ b/.github/workflows/weekly-blog-post-writer.lock.yml @@ -790,6 +790,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -855,6 +859,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/weekly-editors-health-check.lock.yml b/.github/workflows/weekly-editors-health-check.lock.yml index ecac40e0ef5..c09c4ae0aac 100644 --- a/.github/workflows/weekly-editors-health-check.lock.yml +++ b/.github/workflows/weekly-editors-health-check.lock.yml @@ -615,6 +615,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/weekly-safe-outputs-spec-review.lock.yml b/.github/workflows/weekly-safe-outputs-spec-review.lock.yml index d406c278b6e..732d233d211 100644 --- a/.github/workflows/weekly-safe-outputs-spec-review.lock.yml +++ b/.github/workflows/weekly-safe-outputs-spec-review.lock.yml @@ -607,6 +607,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/workflow-health-manager.lock.yml b/.github/workflows/workflow-health-manager.lock.yml index 45bcfee9377..efd59228c49 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -608,9 +608,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -618,6 +627,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -715,6 +734,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/actions/setup/js/add_comment.cjs b/actions/setup/js/add_comment.cjs index 00c86fde8a8..6bb76ce6e8d 100644 --- a/actions/setup/js/add_comment.cjs +++ b/actions/setup/js/add_comment.cjs @@ -34,6 +34,93 @@ const HANDLER_TYPE = "add_comment"; // pre-handled by resolveSafeOutputIssueTarget) to preserve a defensive boundary check. const WILDCARD_TARGET_FIELDS = ["item_number", "issue_number", "pull_request_number", "pr_number", "pr", "pull_number"]; +/** + * Parse trusted comment IDs supplied by workflow configuration. + * @param {unknown} value + * @returns {Set} + */ +function parseAllowedCommentIds(value) { + const values = []; + const visit = item => { + if (Array.isArray(item)) { + for (const child of item) visit(child); + return; + } + if (typeof item === "number" && Number.isInteger(item) && item > 0) { + values.push(String(item)); + return; + } + if (typeof item !== "string") { + return; + } + const trimmed = item.trim(); + if (!trimmed) { + return; + } + if (trimmed.startsWith("[") || trimmed.startsWith('"')) { + try { + visit(JSON.parse(trimmed)); + return; + } catch { + // Fall through to delimiter parsing. + } + } + for (const part of trimmed.split(/[,\s]+/)) { + if (/^[1-9]\d*$/.test(part)) { + values.push(part); + } + } + }; + visit(value); + return new Set(values); +} + +/** + * @param {unknown} value + * @returns {{success: true, commentId: number} | {success: false, error: string}} + */ +function parsePositiveCommentId(value) { + const commentId = Number(value); + if (!Number.isInteger(commentId) || commentId <= 0) { + return { success: false, error: "comment_id must be a positive integer" }; + } + return { success: true, commentId }; +} + +/** + * Validate an agent-supplied comment_id against the trusted workflow allowlist. + * @param {unknown} value + * @param {any} config + * @param {string} commentTarget + * @returns {{success: true, commentId: number} | {success: false, error: string}} + */ +function resolveAllowedCommentId(value, config, commentTarget) { + const parsed = parsePositiveCommentId(value); + if (!parsed.success) { + return parsed; + } + if (commentTarget !== "*") { + return { + success: false, + error: "comment_id is only allowed when safe-outputs.add-comment.target is '*' and the ID is listed in safe-outputs.add-comment.allows-comment-ids", + }; + } + const allowedCommentIds = parseAllowedCommentIds(config.allows_comment_ids ?? config["allows-comment-ids"]); + if (allowedCommentIds.size === 0) { + return { + success: false, + error: "comment_id requires safe-outputs.add-comment.allows-comment-ids to list trusted comment IDs", + }; + } + if (!allowedCommentIds.has(String(parsed.commentId))) { + return { + success: false, + error: "comment_id is not listed in safe-outputs.add-comment.allows-comment-ids", + }; + } + return parsed; +} + /** * Deduplicate an array of strings using case-insensitive comparison, preserving original casing and order. * @param {string[]} aliases @@ -504,17 +591,60 @@ async function main(config = {}) { // Determine target number and type let itemNumber; let isDiscussion = false; + /** @type {any} */ + let commentIdToReuse = null; + const explicitCommentIdRaw = message.comment_id ?? message.commentId ?? message["comment-id"]; + const hasExplicitCommentId = explicitCommentIdRaw !== undefined && explicitCommentIdRaw !== null && String(explicitCommentIdRaw).trim() !== ""; + if (hasExplicitCommentId) { + const allowedCommentId = resolveAllowedCommentId(explicitCommentIdRaw, config, commentTarget); + if (!allowedCommentId.success) { + return { + success: false, + error: allowedCommentId.error, + }; + } + commentIdToReuse = allowedCommentId.commentId; + } // Check if item_number or issue_number was explicitly provided in the message. // item_number takes precedence over issue_number when both are present. // pr-number is accepted as an alias for item_number for robustness. - const itemTargetResult = resolveSafeOutputIssueTarget({ message, tempIdMap: temporaryIdMap, repoParts, handlerType: HANDLER_TYPE, aliases: ["item_number", "issue_number", "pr-number"] }); - if (!itemTargetResult.success) return itemTargetResult; + /** @type {{ success: boolean, number?: number | null, deferred?: boolean, error?: string }} */ + let itemTargetResult = { success: true, number: null }; + if (hasExplicitCommentId) { + try { + const { data: existingComment } = await githubClient.rest.issues.getComment({ + owner: repoParts.owner, + repo: repoParts.repo, + comment_id: commentIdToReuse, + }); + const targetURL = existingComment?.issue_url || ""; + const match = String(targetURL).match(/\/issues\/(\d+)(?:[#/?]|$)/); + if (match) { + itemNumber = Number(match[1]); + } + if (!Number.isInteger(itemNumber) || itemNumber <= 0) { + return { + success: false, + error: `Could not derive issue or pull request number for allowed comment_id ${commentIdToReuse}`, + }; + } + core.info(`Using allowed existing comment ID: ${commentIdToReuse}`); + } catch (err) { + return { + success: false, + error: `Failed to fetch allowed comment_id ${commentIdToReuse}: ${getErrorMessage(err)}`, + }; + } + } else { + itemTargetResult = resolveSafeOutputIssueTarget({ message, tempIdMap: temporaryIdMap, repoParts, handlerType: HANDLER_TYPE, aliases: ["item_number", "issue_number", "pr-number"] }); + if (!itemTargetResult.success) return itemTargetResult; + } - if (itemTargetResult.number !== null) { + if (itemTargetResult.number != null) { itemNumber = itemTargetResult.number; core.info(`Using explicitly provided target number (item_number/issue_number/pr-number): #${itemNumber}`); - } else { + } else if (!hasExplicitCommentId) { // Check if this is a discussion context const isDiscussionContext = effectiveContext.eventName === "discussion" || effectiveContext.eventName === "discussion_comment"; @@ -609,7 +739,7 @@ async function main(config = {}) { const parentAuthors = []; if (!mentionsDisabled) { if (!isDiscussion) { - if (itemTargetResult.number !== null) { + if (itemTargetResult.number != null || hasExplicitCommentId) { // Explicit item_number/issue_number: fetch the issue/PR to get its author try { const { data: issueData } = await githubClient.rest.issues.get({ @@ -776,9 +906,6 @@ async function main(config = {}) { core.warning("Ignoring empty discussion reply_to_id after normalization"); } - // add_comment uses snake_case fields. camelCase and kebab-case aliases are - // accepted for compatibility with forwarded/legacy payload variants. - const explicitCommentIdRaw = message.comment_id ?? message.commentId ?? message["comment-id"]; const rawTarget = message.target; const allowedTargets = ["status", "issue", "discussion"]; if (rawTarget !== undefined && !allowedTargets.includes(rawTarget)) { @@ -786,17 +913,7 @@ async function main(config = {}) { } const isStatusCommentTarget = rawTarget === "status"; const statusCommentIdRaw = process.env.GH_AW_COMMENT_ID || ""; - /** @type {any} */ - let commentIdToReuse = null; - if (explicitCommentIdRaw !== undefined && explicitCommentIdRaw !== null && String(explicitCommentIdRaw).trim() !== "") { - commentIdToReuse = Number(explicitCommentIdRaw); - if (!Number.isInteger(commentIdToReuse) || commentIdToReuse <= 0) { - return { - success: false, - error: "comment_id must be a positive integer", - }; - } - } else if (isStatusCommentTarget) { + if (commentIdToReuse === null && isStatusCommentTarget) { const parsedStatusCommentId = Number(statusCommentIdRaw); if (Number.isInteger(parsedStatusCommentId) && parsedStatusCommentId > 0) { commentIdToReuse = parsedStatusCommentId; @@ -832,7 +949,7 @@ async function main(config = {}) { // reply as a threaded comment to the triggering comment instead of posting top-level. // GitHub Discussions only supports two nesting levels, so if the triggering comment is // itself a reply, we resolve the top-level parent's node ID to use as replyToId. - const hasExplicitItemNumber = itemTargetResult.number !== null; + const hasExplicitItemNumber = itemTargetResult.number != null; let replyToId; if (context.eventName === "discussion_comment" && !hasExplicitItemNumber) { // When triggered by a discussion_comment event, thread the reply under the triggering comment. @@ -850,7 +967,7 @@ async function main(config = {}) { } comment = await commentOnDiscussion(githubClient, repoParts.owner, repoParts.repo, itemNumber, processedBody, replyToId); } else { - const shouldReplyToTriggeringPRReviewComment = effectiveContext.eventName === "pull_request_review_comment" && itemTargetResult.number === null; + const shouldReplyToTriggeringPRReviewComment = !hasExplicitCommentId && effectiveContext.eventName === "pull_request_review_comment" && itemTargetResult.number == null; const triggeringReviewCommentId = Number(effectiveContext.payload?.comment?.id); if (shouldReplyToTriggeringPRReviewComment && Number.isInteger(triggeringReviewCommentId) && triggeringReviewCommentId > 0) { @@ -905,7 +1022,7 @@ async function main(config = {}) { // If 404 and item_number was explicitly provided and we tried as issue/PR, // retry as a discussion (the user may have provided a discussion number) - if (is404 && !isDiscussion && itemTargetResult.number !== null) { + if (is404 && !isDiscussion && itemTargetResult.number != null) { core.info(`Item #${itemNumber} not found as issue/PR, retrying as discussion...`); try { diff --git a/actions/setup/js/add_comment.test.cjs b/actions/setup/js/add_comment.test.cjs index 35a86a0a604..32b0d906c5c 100644 --- a/actions/setup/js/add_comment.test.cjs +++ b/actions/setup/js/add_comment.test.cjs @@ -3,9 +3,11 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest"; import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; +import { syncRuntimePromptTemplates } from "./test_prompt_templates.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +syncRuntimePromptTemplates(import.meta.url); describe("add_comment", () => { let mockCore; @@ -47,6 +49,13 @@ describe("add_comment", () => { html_url: "https://github.com/owner/repo/issues/42#issuecomment-12345", }, }), + getComment: async params => ({ + data: { + id: params.comment_id, + issue_url: "https://api.github.com/repos/owner/repo/issues/42", + html_url: `https://github.com/owner/repo/issues/42#issuecomment-${params.comment_id}`, + }, + }), listComments: async () => ({ data: [] }), }, pulls: { @@ -684,12 +693,19 @@ describe("add_comment", () => { delete process.env.GH_AW_COMMENT_ID; }); - it("should update existing comment when comment-id alias is provided", async () => { + it("should update existing comment when comment-id alias is allowed by target wildcard config", async () => { const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); /** @type {any} */ let capturedUpdateParams = null; let createCommentCalled = false; + mockGithub.rest.issues.getComment = async params => ({ + data: { + id: params.comment_id, + issue_url: "https://api.github.com/repos/owner/repo/issues/8535", + html_url: `https://github.com/owner/repo/issues/8535#issuecomment-${params.comment_id}`, + }, + }); mockGithub.rest.issues.updateComment = async params => { capturedUpdateParams = params; return { @@ -709,10 +725,11 @@ describe("add_comment", () => { }; }; - const handler = await eval(`(async () => { ${addCommentScript}; return await main({ target: 'triggering' }); })()`); + const handler = await eval(`(async () => { ${addCommentScript}; return await main({ target: '*', allows_comment_ids: ['55555'] }); })()`); const result = await handler({ type: "add_comment", body: "Updated output", "comment-id": 55555 }, {}); expect(result.success).toBe(true); + expect(result.itemNumber).toBe(8535); expect(capturedUpdateParams).toEqual( expect.objectContaining({ owner: "owner", @@ -723,6 +740,23 @@ describe("add_comment", () => { expect(createCommentCalled).toBe(false); }); + it("should reject comment_id when it is not in allows-comment-ids", async () => { + const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); + + let updateCommentCalled = false; + mockGithub.rest.issues.updateComment = async () => { + updateCommentCalled = true; + return { data: { id: 999, html_url: "https://github.com/owner/repo/issues/8535#issuecomment-999" } }; + }; + + const handler = await eval(`(async () => { ${addCommentScript}; return await main({ target: '*', allows_comment_ids: ['12345'] }); })()`); + const result = await handler({ type: "add_comment", body: "Updated output", comment_id: 55555 }, {}); + + expect(result.success).toBe(false); + expect(result.error).toContain("allows-comment-ids"); + expect(updateCommentCalled).toBe(false); + }); + it("should warn and ignore unrecognized message-level target values instead of failing", async () => { const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); diff --git a/actions/setup/js/collect_ndjson_output.cjs b/actions/setup/js/collect_ndjson_output.cjs index 0f01abf5d24..8d3d30c31e9 100644 --- a/actions/setup/js/collect_ndjson_output.cjs +++ b/actions/setup/js/collect_ndjson_output.cjs @@ -112,12 +112,12 @@ async function main() { } function validateItemWithSafeJobConfig(item, jobConfig, lineNum) { const errors = []; - const normalizedItem = { ...item }; - if (!jobConfig.inputs) { + const normalizedItem = { type: item.type }; + if (!jobConfig || typeof jobConfig !== "object" || !jobConfig.inputs) { return { isValid: true, errors: [], - normalizedItem: item, + normalizedItem, }; } for (const [fieldName, inputSchema] of Object.entries(jobConfig.inputs)) { @@ -380,16 +380,13 @@ async function main() { continue; } const safeJobConfig = jobOutputType; - if (safeJobConfig && safeJobConfig.inputs) { - const validation = validateItemWithSafeJobConfig(item, safeJobConfig, i + 1); - if (!validation.isValid) { - errors.push(...validation.errors); - continue; - } - Object.assign(item, validation.normalizedItem); + const validation = validateItemWithSafeJobConfig(item, safeJobConfig, i + 1); + if (!validation.isValid) { + errors.push(...validation.errors); + continue; } core.info(`Line ${i + 1}: Valid ${itemType} item`); - parsedItems.push(item); + parsedItems.push(validation.normalizedItem); } } catch (error) { const errorMsg = getErrorMessage(error); diff --git a/actions/setup/js/collect_ndjson_output.test.cjs b/actions/setup/js/collect_ndjson_output.test.cjs index ab3762d4b7c..169e36f79ff 100644 --- a/actions/setup/js/collect_ndjson_output.test.cjs +++ b/actions/setup/js/collect_ndjson_output.test.cjs @@ -61,6 +61,12 @@ describe("collect_ndjson_output.cjs", () => { }, }, add_comment: { defaultMax: 1, fields: { body: { required: !0, type: "string", sanitize: !0, maxLength: 65e3 }, item_number: { issueOrPRNumber: !0 } } }, + add_labels: { defaultMax: 5, fields: { labels: { required: !0, type: "array" }, item_number: { issueNumberOrTemporaryId: !0 } } }, + assign_milestone: { + defaultMax: 1, + customValidation: "requiresOneOf:milestone_number,milestone_title", + fields: { issue_number: { issueNumberOrTemporaryId: !0 }, milestone_number: { optionalPositiveInteger: !0 }, milestone_title: { type: "string", sanitize: !0, maxLength: 128 } }, + }, create_pull_request: { defaultMax: 1, fields: { @@ -258,7 +264,7 @@ describe("collect_ndjson_output.cjs", () => { it("should preserve Slack mrkdwn links in custom safe-job string inputs", async () => { const testFile = "/tmp/gh-aw/test-ndjson-output.txt"; const slackText = "Tracking issue: "; - const ndjsonContent = JSON.stringify({ type: "post_to_slack", text: slackText }); + const ndjsonContent = JSON.stringify({ type: "post_to_slack", text: slackText, channel: "security-alerts" }); fs.writeFileSync(testFile, ndjsonContent); process.env.GH_AW_SAFE_OUTPUTS = testFile; fs.writeFileSync("/tmp/gh-aw/safeoutputs/config.json", JSON.stringify({ post_to_slack: { inputs: { text: { type: "string", required: true } } } })); @@ -268,6 +274,7 @@ describe("collect_ndjson_output.cjs", () => { expect(outputCall).toBeDefined(); const parsedOutput = JSON.parse(outputCall[1]); (expect(parsedOutput.errors).toHaveLength(0), expect(parsedOutput.items).toEqual([{ type: "post_to_slack", text: slackText }])); + expect(parsedOutput.items[0]).not.toHaveProperty("channel"); }), it("should reject items with unexpected output types", async () => { const testFile = "/tmp/gh-aw/test-ndjson-output.txt", @@ -881,9 +888,9 @@ describe("collect_ndjson_output.cjs", () => { const parsedOutput = JSON.parse(outputCall[1]); (expect(parsedOutput.items).toHaveLength(1), expect(parsedOutput.items[0].type).toBe("create_issue"), - expect(parsedOutput.items[0].priority).toBe(5), - expect(parsedOutput.items[0].urgent).toBe(!0), - expect(parsedOutput.items[0].assignee).toBe(null), + expect(parsedOutput.items[0]).not.toHaveProperty("priority"), + expect(parsedOutput.items[0]).not.toHaveProperty("urgent"), + expect(parsedOutput.items[0]).not.toHaveProperty("assignee"), expect(parsedOutput.errors).toHaveLength(0)); }), it("should attempt repair but fail gracefully with excessive malformed JSON", async () => { @@ -928,12 +935,7 @@ describe("collect_ndjson_output.cjs", () => { outputCall = setOutputCalls.find(call => "output" === call[0]); expect(outputCall).toBeDefined(); const parsedOutput = JSON.parse(outputCall[1]); - (expect(parsedOutput.items).toHaveLength(1), - expect(parsedOutput.items[0].type).toBe("create_issue"), - expect(parsedOutput.items[0].metadata).toBeDefined(), - expect(parsedOutput.items[0].metadata.project).toBe("test"), - expect(parsedOutput.items[0].metadata.tags).toEqual(["important", "urgent"]), - expect(parsedOutput.errors).toHaveLength(0)); + (expect(parsedOutput.items).toHaveLength(1), expect(parsedOutput.items[0].type).toBe("create_issue"), expect(parsedOutput.items[0]).not.toHaveProperty("metadata"), expect(parsedOutput.errors).toHaveLength(0)); }), it("should handle complex backslash scenarios with graceful failure", async () => { const testFile = "/tmp/gh-aw/test-ndjson-output.txt", @@ -1052,7 +1054,7 @@ describe("collect_ndjson_output.cjs", () => { (expect(parsedOutput.items).toHaveLength(1), expect(parsedOutput.items[0].type).toBe("create_issue"), expect(parsedOutput.items[0].title).toBe("Combined issues"), - expect(parsedOutput.items[0].priority).toBe(1), + expect(parsedOutput.items[0]).not.toHaveProperty("priority"), expect(parsedOutput.errors).toHaveLength(0)); })); }), diff --git a/actions/setup/js/commit_sha_helpers.cjs b/actions/setup/js/commit_sha_helpers.cjs index f3f34c3c1b4..d477529001c 100644 --- a/actions/setup/js/commit_sha_helpers.cjs +++ b/actions/setup/js/commit_sha_helpers.cjs @@ -15,6 +15,22 @@ function normalizeCommitSHA(value) { return GIT_COMMIT_SHA_PATTERN.test(normalized) ? normalized : ""; } +/** + * Extract the trusted base commit embedded in the generated patch artifact. + * + * @param {unknown} patchContent + * @returns {string} + */ +function extractPatchBaseCommit(patchContent) { + if (typeof patchContent !== "string") { + return ""; + } + const headerBlock = patchContent.split(/\r?\n\r?\n/, 1)[0]; + const match = headerBlock.match(/^X-GH-AW-Base-Commit:\s*([0-9a-fA-F]{7,40})\s*$/m); + return normalizeCommitSHA(match?.[1]); +} + module.exports = { + extractPatchBaseCommit, normalizeCommitSHA, }; diff --git a/actions/setup/js/commit_sha_helpers.test.cjs b/actions/setup/js/commit_sha_helpers.test.cjs index 9f0d9c53089..846abeb1b05 100644 --- a/actions/setup/js/commit_sha_helpers.test.cjs +++ b/actions/setup/js/commit_sha_helpers.test.cjs @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import { createRequire } from "module"; const require = createRequire(import.meta.url); -const { normalizeCommitSHA } = require("./commit_sha_helpers.cjs"); +const { extractPatchBaseCommit, normalizeCommitSHA } = require("./commit_sha_helpers.cjs"); describe("normalizeCommitSHA", () => { it("accepts valid commit SHAs and trims whitespace", () => { @@ -11,6 +11,25 @@ describe("normalizeCommitSHA", () => { expect(normalizeCommitSHA("a".repeat(40))).toBe("a".repeat(40)); }); + describe("extractPatchBaseCommit", () => { + it("extracts a validated base commit from patch metadata", () => { + expect(extractPatchBaseCommit("From abc123 Mon Sep 17 00:00:00 2001\nX-GH-AW-Base-Commit: deadbeef\nFrom: Test\n")).toBe("deadbeef"); + }); + + it("handles CRLF line endings in patch metadata", () => { + expect(extractPatchBaseCommit("From abc123 Mon Sep 17 00:00:00 2001\r\nX-GH-AW-Base-Commit: deadbeef\r\nFrom: Test\r\n\r\nBody\r\n")).toBe("deadbeef"); + }); + + it("ignores missing or malformed patch metadata", () => { + expect(extractPatchBaseCommit("From abc123 Mon Sep 17 00:00:00 2001\nFrom: Test\n")).toBe(""); + expect(extractPatchBaseCommit("X-GH-AW-Base-Commit: main\n")).toBe(""); + }); + + it("ignores metadata-like lines outside the patch header block", () => { + expect(extractPatchBaseCommit("From abc123 Mon Sep 17 00:00:00 2001\nFrom: Test\n\nX-GH-AW-Base-Commit: deadbeef\n")).toBe(""); + }); + }); + it("rejects invalid commit references", () => { expect(normalizeCommitSHA("main")).toBe(""); expect(normalizeCommitSHA("--upload-pack=/bin/echo")).toBe(""); diff --git a/actions/setup/js/create_pull_request.cjs b/actions/setup/js/create_pull_request.cjs index 74c3336d67c..199f62dcafe 100644 --- a/actions/setup/js/create_pull_request.cjs +++ b/actions/setup/js/create_pull_request.cjs @@ -33,7 +33,7 @@ const { renderTemplateFromFile, renderFilesList, buildProtectedFileList, getProm const { withGitHubHostToken } = require("./git_auth_helpers.cjs"); const { COPILOT_REVIEWER_BOT, FAQ_CREATE_PR_PERMISSIONS_URL } = require("./constants.cjs"); const { isStagedMode } = require("./safe_output_helpers.cjs"); -const { normalizeCommitSHA } = require("./commit_sha_helpers.cjs"); +const { extractPatchBaseCommit } = require("./commit_sha_helpers.cjs"); const { withRetry, RATE_LIMIT_RETRY_CONFIG } = require("./error_recovery.cjs"); const { findAgent, getIssueDetails, assignAgentToIssue } = require("./assign_agent_helpers.cjs"); const { ensureFullHistoryForBundle, extractBundlePrerequisiteCommits, getBundlePrerequisites, isShallowOrSparseCheckout, linearizeRangeAsCommit } = require("./git_helpers.cjs"); @@ -1882,10 +1882,10 @@ gh pr create --title '${title}' --base ${baseBranch} --head ${getPullRequestHead // Handle branch creation/checkout let branchBaseRef = baseBranch; - const recordedBaseCommit = normalizeCommitSHA(pullRequestItem.base_commit); + const recordedBaseCommit = extractPatchBaseCommit(patchContent); if (recordedBaseCommit) { core.info(`Patch route base_commit resolved: ${recordedBaseCommit}`); - core.info(`Using base_commit from safe output entry for patch apply: ${recordedBaseCommit}`); + core.info(`Using base_commit embedded in the patch for patch apply: ${recordedBaseCommit}`); try { try { await exec.exec("git", ["fetch", "origin", recordedBaseCommit, "--depth=1"]); @@ -1901,8 +1901,6 @@ gh pr create --title '${title}' --base ${baseBranch} --head ${getPullRequestHead } catch (baseCommitError) { core.warning(`Recorded base_commit ${recordedBaseCommit} is not available in this checkout (${getErrorMessage(baseCommitError)}); falling back to ${baseBranch}`); } - } else if (String(pullRequestItem.base_commit ?? "").trim()) { - core.warning(`Ignoring invalid base_commit value for patch apply: ${String(pullRequestItem.base_commit).trim()}`); } core.info(`Branch should not exist locally, creating new branch from base: ${branchName} (${branchBaseRef})`); await exec.exec("git", ["checkout", "-b", branchName, branchBaseRef]); @@ -1997,9 +1995,9 @@ gh pr create --title '${title}' --base ${baseBranch} --head ${getPullRequestHead // Use the base commit recorded at patch generation time. // The From header in format-patch output contains the agent's new commit SHA // which does not exist in this checkout, so we cannot derive the base from it. - const originalBaseCommit = normalizeCommitSHA(pullRequestItem.base_commit); + const originalBaseCommit = extractPatchBaseCommit(patchContent); if (!originalBaseCommit) { - core.warning("No base_commit recorded in safe output entry - fallback not possible"); + core.warning("No base_commit embedded in patch - fallback not possible"); } else { core.info(`Original base commit from patch generation: ${originalBaseCommit}`); diff --git a/actions/setup/js/create_pull_request.test.cjs b/actions/setup/js/create_pull_request.test.cjs index e17d52ec36d..7c968fc7342 100644 --- a/actions/setup/js/create_pull_request.test.cjs +++ b/actions/setup/js/create_pull_request.test.cjs @@ -55,6 +55,7 @@ function ensureDefaultDisclosureHeaderPrompt() { beforeEach(() => { cleanupCanonicalTransports(); ensureDefaultDisclosureHeaderPrompt(); + process.env.GH_AW_PROMPTS_DIR = promptsSourceDir; }); afterEach(() => { cleanupCanonicalTransports(); @@ -2848,6 +2849,7 @@ describe("create_pull_request - patch apply fallback to original base commit", ( // Minimal valid format-patch output const PATCH_CONTENT = `From a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 Mon Sep 17 00:00:00 2001\n` + + `X-GH-AW-Base-Commit: ${MOCK_BASE_COMMIT_SHA}\n` + `From: Test Author \n` + `Date: Wed, 26 Mar 2026 12:00:00 +0000\n` + `Subject: [PATCH] Test change\n\n` + @@ -2868,7 +2870,6 @@ describe("create_pull_request - patch apply fallback to original base commit", ( process.env.GH_AW_WORKFLOW_ID = "test-workflow"; process.env.GITHUB_REPOSITORY = "test-owner/test-repo"; process.env.GITHUB_BASE_REF = "main"; - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "create-pr-fallback-test-")); patchFilePath = path.join(tempDir, "test.patch"); fs.writeFileSync(patchFilePath, PATCH_CONTENT, "utf8"); @@ -2963,7 +2964,7 @@ describe("create_pull_request - patch apply fallback to original base commit", ( return false; } - it("should create the PR branch from normalized base_commit before applying the patch when available", async () => { + it("should create the PR branch from the patch-embedded base commit when available", async () => { global.exec = { exec: vi.fn().mockResolvedValue(0), getExecOutput: vi.fn().mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" }), @@ -2979,7 +2980,7 @@ describe("create_pull_request - patch apply fallback to original base commit", ( expect(checkoutWithBaseCommit).toBeTruthy(); }); - it("should ignore invalid base_commit values when creating the branch", async () => { + it("should ignore agent-supplied base_commit values when creating the branch", async () => { global.exec = { exec: vi.fn().mockResolvedValue(0), getExecOutput: vi.fn().mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" }), @@ -2991,7 +2992,7 @@ describe("create_pull_request - patch apply fallback to original base commit", ( expect(result.success).toBe(true); expect(global.exec.exec).not.toHaveBeenCalledWith("git", ["cat-file", "-e", "not-a-sha --bad"]); - expect(global.core.warning).toHaveBeenCalledWith("Ignoring invalid base_commit value for patch apply: not-a-sha --bad"); + expect(global.core.warning).not.toHaveBeenCalledWith(expect.stringContaining("base_commit")); }); it("should fall back to base branch when base_commit is unavailable", async () => { @@ -3221,6 +3222,8 @@ describe("create_pull_request - patch apply fallback to original base commit", ( }); it("should return error when no base_commit is provided and git am --3way fails", async () => { + const patchWithoutBaseCommit = PATCH_CONTENT.replace(`X-GH-AW-Base-Commit: ${MOCK_BASE_COMMIT_SHA}\n`, ""); + fs.writeFileSync(canonicalPatchPath("test-branch"), patchWithoutBaseCommit, "utf8"); global.exec = { exec: vi.fn().mockImplementation((cmd, args) => { if (isGitAm3Way(cmd, args)) { @@ -3241,7 +3244,7 @@ describe("create_pull_request - patch apply fallback to original base commit", ( expect(result.success).toBe(false); expect(result.error).toBe("Failed to apply patch"); - expect(global.core.warning).toHaveBeenCalledWith("No base_commit recorded in safe output entry - fallback not possible"); + expect(global.core.warning).toHaveBeenCalledWith("No base_commit embedded in patch - fallback not possible"); }); it("should reuse existing remote branch when preserve-branch-name and recreate-ref are true (force-delete then recreate)", async () => { diff --git a/actions/setup/js/generate_git_patch.cjs b/actions/setup/js/generate_git_patch.cjs index edbf0cb58df..a1903e8f3e7 100644 --- a/actions/setup/js/generate_git_patch.cjs +++ b/actions/setup/js/generate_git_patch.cjs @@ -13,6 +13,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { ensureOriginRemoteTrackingRef, execGitSync } = require("./git_helpers.cjs"); const { ERR_SYSTEM } = require("./error_codes.cjs"); const { sanitizeForFilename, sanitizeBranchNameForPatch, sanitizeRepoSlugForPatch, getPatchPathForBranch, getPatchPathForBranchInRepo, buildExcludePathspecs, computeIncrementalDiffSize } = require("./git_patch_utils.cjs"); +const { normalizeCommitSHA } = require("./commit_sha_helpers.cjs"); // sanitizeForFilename is re-exported below for backward compatibility with // existing callers that imported it from this module. @@ -29,6 +30,18 @@ function debugLog(message) { } } +function embedBaseCommit(patchContent, baseCommitSha) { + const normalizedBaseCommitSha = normalizeCommitSHA(baseCommitSha); + if (!normalizedBaseCommitSha || typeof patchContent !== "string") { + return patchContent; + } + const firstNewline = patchContent.indexOf("\n"); + if (firstNewline < 0) { + return patchContent; + } + return `${patchContent.slice(0, firstNewline + 1)}X-GH-AW-Base-Commit: ${normalizedBaseCommitSha}\n${patchContent.slice(firstNewline + 1)}`; +} + /** * Generates a git patch file for the current changes * @param {string} branchName - The branch name to generate patch for @@ -290,7 +303,7 @@ async function generateGitPatch(branchName, baseBranch, options = {}) { const patchContent = execGitSync(["format-patch", `${baseRef}..${tipRef}`, "--stdout", ...excludeArgs()], { cwd }); if (patchContent && patchContent.trim()) { - fs.writeFileSync(patchPath, patchContent, "utf8"); + fs.writeFileSync(patchPath, embedBaseCommit(patchContent, baseCommitSha), "utf8"); patchGenerated = true; debugLog(`Strategy 1: SUCCESS - Generated patch with ${patchContent.split("\n").length} lines`); } @@ -407,7 +420,7 @@ async function generateGitPatch(branchName, baseBranch, options = {}) { const patchContent = execGitSync(["format-patch", `${githubSha}..HEAD`, "--stdout", ...excludeArgs()], { cwd }); if (patchContent && patchContent.trim()) { - fs.writeFileSync(patchPath, patchContent, "utf8"); + fs.writeFileSync(patchPath, embedBaseCommit(patchContent, baseCommitSha), "utf8"); patchGenerated = true; debugLog(`Strategy 2: SUCCESS - Generated patch with ${patchContent.split("\n").length} lines`); } @@ -490,7 +503,7 @@ async function generateGitPatch(branchName, baseBranch, options = {}) { const patchContent = execGitSync(["format-patch", `${bestBaseCommit}..${branchName}`, "--stdout", ...excludeArgs()], { cwd }); if (patchContent && patchContent.trim()) { - fs.writeFileSync(patchPath, patchContent, "utf8"); + fs.writeFileSync(patchPath, embedBaseCommit(patchContent, baseCommitSha), "utf8"); patchGenerated = true; debugLog(`Strategy 3: SUCCESS - Generated patch with ${patchContent.split("\n").length} lines`); } @@ -646,4 +659,5 @@ module.exports = { getPatchPathForBranchInRepo, sanitizeBranchNameForPatch, sanitizeRepoSlugForPatch, + embedBaseCommit, }; diff --git a/actions/setup/js/generate_git_patch.test.cjs b/actions/setup/js/generate_git_patch.test.cjs index 33bf67e3084..fba055db80e 100644 --- a/actions/setup/js/generate_git_patch.test.cjs +++ b/actions/setup/js/generate_git_patch.test.cjs @@ -31,6 +31,14 @@ describe("generateGitPatch", () => { }); }); + it("does not embed invalid base commit metadata", async () => { + const { embedBaseCommit } = await import("./generate_git_patch.cjs"); + + const patchContent = "From abc123 Mon Sep 17 00:00:00 2001\nFrom: Test\n"; + + expect(embedBaseCommit(patchContent, "main\nX-Injected: true")).toBe(patchContent); + }); + it("should return error when no commits can be found", async () => { delete process.env.GITHUB_SHA; process.env.GITHUB_WORKSPACE = "/tmp/test-repo"; diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs index 4f797edd3f6..bb58d5a1247 100644 --- a/actions/setup/js/push_to_pull_request_branch.cjs +++ b/actions/setup/js/push_to_pull_request_branch.cjs @@ -3,6 +3,7 @@ /** @type {typeof import("fs")} */ const fs = require("fs"); +const path = require("path"); const { generateStagedPreview } = require("./staged_preview.cjs"); const { isStagedMode } = require("./safe_output_helpers.cjs"); const { pushSignedCommits } = require("./push_signed_commits.cjs"); @@ -19,7 +20,7 @@ const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); const { renderTemplateFromFile, buildProtectedFileList, getPromptPath } = require("./messages_core.cjs"); const { withGitHubHostToken } = require("./git_auth_helpers.cjs"); const { ensureFullHistoryForBundle, extractBundlePrerequisiteCommits, isShallowOrSparseCheckout, linearizeRangeAsCommit, ensureSafeDirectoryTrust } = require("./git_helpers.cjs"); -const { normalizeCommitSHA } = require("./commit_sha_helpers.cjs"); +const { extractPatchBaseCommit } = require("./commit_sha_helpers.cjs"); const { findRepoCheckout } = require("./find_repo_checkout.cjs"); const { getThreatWarningPresentation } = require("./threat_detection_warning.cjs"); const { attachExecutionState } = require("./safe_output_execution_metadata.cjs"); @@ -113,11 +114,37 @@ function parsePositiveInteger(value) { * @returns {Promise} */ async function getBundlePreApplyFiles(exec, gitOptions, rangeBaseRef, bundleRef) { - const bundleDiffResult = await exec.getExecOutput("git", ["diff", "--name-only", "--no-renames", `${rangeBaseRef}..${bundleRef}`], gitOptions); - return bundleDiffResult.stdout - .split("\n") - .map(f => f.trim()) - .filter(Boolean); + const bundleDiffResult = await exec.getExecOutput("git", ["diff", "--name-only", "--no-renames", "-z", `${rangeBaseRef}..${bundleRef}`], gitOptions); + return bundleDiffResult.stdout.split("\0").filter(Boolean); +} + +/** + * Measure the expanded blob size of files changed by the applied agent commits. + * Deleted files contribute zero bytes because no new content is being introduced. + * + * @param {Record} gitOptions + * @param {string[]} files + * @returns {number} + */ +function getChangedBlobSizeBytes(gitOptions, files) { + let total = 0; + const cwd = typeof gitOptions.cwd === "string" && gitOptions.cwd ? gitOptions.cwd : process.cwd(); + const resolvedCwd = path.resolve(cwd); + for (const file of files) { + const filePath = path.resolve(resolvedCwd, file); + if (filePath !== resolvedCwd && !filePath.startsWith(`${resolvedCwd}${path.sep}`)) { + continue; + } + try { + const stats = fs.statSync(filePath); + if (stats.isFile()) { + total += stats.size; + } + } catch { + // Deleted files have no expanded content to add to the limit. + } + } + return total; } /** @@ -432,15 +459,8 @@ async function main(config = {}) { // Validate patch/bundle size against `max_patch_size`. // // Size-check source of truth, in order of preference: - // 1. `message.diff_size` — the incremental net diff size recorded at - // patch/bundle generation time (this is the correct quantity to cap: - // how much the PR branch will actually change as a result of the push). - // 2. For bundle transport: the on-disk bundle file size. - // 3. For patch transport: the format-patch file size. - // - // Using `diff_size` when present fixes the long-running branch case where - // the transport file accumulates per-commit metadata + per-commit diffs and - // can be many MB even when each iteration only changes a few KB. + // Use the uncompressed patch representation for both transport modes. + // Bundle size is compressed and can undercount highly compressible changes. if (!isEmpty) { const patchSizeBytes = Buffer.byteLength(patchContent, "utf8"); const patchSizeKb = Math.ceil(patchSizeBytes / 1024); @@ -455,21 +475,8 @@ async function main(config = {}) { } const bundleSizeKb = Math.ceil(bundleSizeBytes / 1024); - const diffSizeBytesRaw = message.diff_size; - const haveDiffSize = typeof diffSizeBytesRaw === "number" && diffSizeBytesRaw >= 0; - - let sizeForCheckBytes; - let sizeLabel; - if (haveDiffSize) { - sizeForCheckBytes = diffSizeBytesRaw; - sizeLabel = "Incremental diff size"; - } else if (hasBundleFile) { - sizeForCheckBytes = bundleSizeBytes; - sizeLabel = "Bundle size"; - } else { - sizeForCheckBytes = patchSizeBytes; - sizeLabel = "Patch size"; - } + const sizeForCheckBytes = patchSizeBytes; + const sizeLabel = "Patch size"; const sizeForCheckKb = Math.ceil(sizeForCheckBytes / 1024); if (hasBundleFile) { @@ -481,14 +488,7 @@ async function main(config = {}) { if (sizeForCheckKb > maxSizeKb) { let msg; - if (haveDiffSize) { - const transportLabel = hasBundleFile ? `Bundle size: ${bundleSizeKb} KB` : `Patch file size: ${patchSizeKb} KB`; - msg = `Incremental diff size (${sizeForCheckKb} KB) exceeds maximum allowed size (${maxSizeKb} KB). ${transportLabel}.`; - } else if (hasBundleFile) { - msg = `Bundle size (${sizeForCheckKb} KB) exceeds maximum allowed size (${maxSizeKb} KB)`; - } else { - msg = `Patch size (${sizeForCheckKb} KB) exceeds maximum allowed size (${maxSizeKb} KB)`; - } + msg = `Patch size (${sizeForCheckKb} KB) exceeds maximum allowed size (${maxSizeKb} KB)`; return { success: false, error: msg }; } @@ -965,8 +965,8 @@ async function main(config = {}) { // Pin patch application to the recorded base commit captured at patch-generation time. // This avoids applying a patch generated from an older branch tip onto a newer remote tip. // If the commit is unavailable (e.g. cross-repo/missing object), continue with current HEAD. - if (!hasBundleFile && message.base_commit) { - const recordedBaseCommit = normalizeCommitSHA(message.base_commit); + if (!hasBundleFile) { + const recordedBaseCommit = extractPatchBaseCommit(patchContent); if (recordedBaseCommit) { core.info(`Patch route base_commit resolved: ${recordedBaseCommit}`); try { @@ -992,8 +992,6 @@ async function main(config = {}) { } catch (baseCommitError) { core.warning(`Unable to use recorded base_commit ${recordedBaseCommit}; applying patch on current branch HEAD: ${getErrorMessage(baseCommitError)}`); } - } else if (String(message.base_commit).trim()) { - core.warning(`Ignoring invalid base_commit value for patch apply: ${String(message.base_commit).trim()}`); } } @@ -1218,11 +1216,8 @@ async function main(config = {}) { // (see github/agentic-workflows#539) let agentChangedFiles = []; { - const diffResult = await exec.getExecOutput("git", ["diff", "--name-only", "--no-renames", `${rangeBaseRef}..HEAD`], baseGitOpts); - const actualFiles = diffResult.stdout - .split("\n") - .map(f => f.trim()) - .filter(Boolean); + const diffResult = await exec.getExecOutput("git", ["diff", "--name-only", "--no-renames", "-z", `${rangeBaseRef}..HEAD`], baseGitOpts); + const actualFiles = diffResult.stdout.split("\0").filter(Boolean); agentChangedFiles = actualFiles; if (actualFiles.length > 0) { core.info(`Post-apply verification: ${actualFiles.length} file(s) actually modified`); @@ -1240,6 +1235,15 @@ async function main(config = {}) { await exec.exec("git", ["reset", "--hard", rangeBaseRef], baseGitOpts); return await createProtectedFilesFallbackIssue(postApplyProtection.files); } + + const changedBlobSizeBytes = getChangedBlobSizeBytes(baseGitOpts, actualFiles); + const changedBlobSizeKb = Math.ceil(changedBlobSizeBytes / 1024); + core.info(`Changed content size: ${changedBlobSizeKb} KB (maximum allowed: ${maxSizeKb} KB)`); + if (changedBlobSizeKb > maxSizeKb) { + const msg = `Changed content size (${changedBlobSizeKb} KB) exceeds maximum allowed size (${maxSizeKb} KB)`; + await exec.exec("git", ["reset", "--hard", rangeBaseRef], baseGitOpts); + return { success: false, error: msg }; + } } } diff --git a/actions/setup/js/push_to_pull_request_branch.integration.test.cjs b/actions/setup/js/push_to_pull_request_branch.integration.test.cjs index 5181ab8238e..f8535aa1233 100644 --- a/actions/setup/js/push_to_pull_request_branch.integration.test.cjs +++ b/actions/setup/js/push_to_pull_request_branch.integration.test.cjs @@ -139,4 +139,37 @@ describe("push_to_pull_request_branch bundle integration", () => { expect(actualFiles.sort()).toEqual(["feature.txt", "main.txt"]); }); + + it("resolves filenames that git would otherwise quote (non-ASCII characters)", async () => { + const branchName = "autoloop/quoted-path-bundle"; + const sourceRepo = createRepo("push-pr-quoted-source-"); + const targetRepo = createRepo("push-pr-quoted-target-"); + tempDirs.push(sourceRepo, targetRepo); + + writeRepoFile(sourceRepo, "README.md", "base\n"); + execGit(["add", "README.md"], { cwd: sourceRepo }); + execGit(["commit", "-m", "base"], { cwd: sourceRepo }); + execGit(["branch", "-M", "main"], { cwd: sourceRepo }); + const baseSha = execGit(["rev-parse", "HEAD"], { cwd: sourceRepo }).stdout.trim(); + + execGit(["checkout", "-b", branchName], { cwd: sourceRepo }); + const quotedFileName = "résumé.txt"; + writeRepoFile(sourceRepo, quotedFileName, "quoted path change\n"); + execGit(["add", quotedFileName], { cwd: sourceRepo }); + execGit(["commit", "-m", "quoted path change"], { cwd: sourceRepo }); + + const bundlePath = path.join(sourceRepo, "quoted.bundle"); + execGit(["bundle", "create", bundlePath, `refs/heads/${branchName}`], { cwd: sourceRepo }); + + fetchBaseCommit(targetRepo, sourceRepo, baseSha, branchName); + const bundleRef = "refs/bundles/test-quoted-bundle"; + execGit(["fetch", bundlePath, `refs/heads/${branchName}:${bundleRef}`], { cwd: targetRepo }); + + const actualFiles = await getBundlePreApplyFiles(createExecApi(targetRepo), {}, baseSha, bundleRef); + + // Without NUL-delimited (`-z`) diff output, git would quote this path (e.g. "r\303\251sum\303\251.txt"), + // causing downstream filesystem lookups keyed on the raw name to fail and silently drop the file + // from size/protection checks. + expect(actualFiles).toEqual([quotedFileName]); + }); }); diff --git a/actions/setup/js/push_to_pull_request_branch.test.cjs b/actions/setup/js/push_to_pull_request_branch.test.cjs index ab9849b8e7c..a7ffb4be824 100644 --- a/actions/setup/js/push_to_pull_request_branch.test.cjs +++ b/actions/setup/js/push_to_pull_request_branch.test.cjs @@ -5,6 +5,7 @@ import * as os from "os"; const { getPatchPathForBranch, getPatchPathForBranchInRepo } = require("./git_patch_utils.cjs"); const { getBundlePathForBranch, getBundlePathForBranchInRepo } = require("./generate_git_bundle.cjs"); +const promptsSourceDir = path.resolve(__dirname, "../md"); // The privileged handler derives patch/bundle paths from `branch` (and `repo`) // via resolveTransportPaths, so tests must write transport files at the @@ -38,6 +39,7 @@ function cleanupCanonicalTransports() { beforeEach(() => { cleanupCanonicalTransports(); + process.env.GH_AW_PROMPTS_DIR = promptsSourceDir; }); afterEach(() => { cleanupCanonicalTransports(); @@ -309,7 +311,7 @@ describe("push_to_pull_request_branch.cjs", () => { * message branch. The privileged handler always re-derives the patch path * from the validated branch, so tests must write at that canonical location. */ - function createPatchFile(branch, content = null) { + function createPatchFile(branch, content = null, baseCommit = "") { const patchPath = canonicalPatchPath(branch); const defaultPatch = `From abc123 Mon Sep 17 00:00:00 2001 From: Test Author @@ -328,7 +330,12 @@ index 0000000..abc1234 -- 2.34.1 `; - fs.writeFileSync(patchPath, content !== null ? content : defaultPatch); + let patchContent = content !== null ? content : defaultPatch; + if (baseCommit) { + const firstNewline = patchContent.indexOf("\n"); + patchContent = `${patchContent.slice(0, firstNewline + 1)}X-GH-AW-Base-Commit: ${baseCommit}\n${patchContent.slice(firstNewline + 1)}`; + } + fs.writeFileSync(patchPath, patchContent); return patchPath; } @@ -903,9 +910,9 @@ index 0000000..abc1234 expect(result.after_state).toEqual({ head_sha: "abc123" }); }); - it("should reset to message.base_commit before applying patch transport", async () => { - const patchPath = createPatchFile("should-reset-to-message-base-commit-before-applying-patch-tr"); + it("should reset to the patch-embedded base commit before applying patch transport", async () => { const recordedBaseCommit = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + const patchPath = createPatchFile("should-reset-to-message-base-commit-before-applying-patch-tr", null, recordedBaseCommit); mockExec.getExecOutput.mockResolvedValue({ exitCode: 0, stdout: "abc123\n", stderr: "" }); const pushSignedCommitsModule = require("./push_signed_commits.cjs"); const pushSignedSpy = vi.spyOn(pushSignedCommitsModule, "pushSignedCommits").mockResolvedValue("abc123"); @@ -913,7 +920,7 @@ index 0000000..abc1234 try { const module = await loadModule(); const handler = await module.main({}); - const result = await handler({ base_commit: recordedBaseCommit, branch: "should-reset-to-message-base-commit-before-applying-patch-tr" }, {}); + const result = await handler({ branch: "should-reset-to-message-base-commit-before-applying-patch-tr" }, {}); expect(result.success).toBe(true); expect(mockExec.exec).toHaveBeenCalledWith("git", ["cat-file", "-e", recordedBaseCommit], expect.any(Object)); @@ -924,9 +931,9 @@ index 0000000..abc1234 } }); - it("should fall back to current HEAD when base_commit is unavailable for patch transport", async () => { - const patchPath = createPatchFile("should-fall-back-to-current-head-when-base-commit-is-unavail"); + it("should fall back to current HEAD when the patch-embedded base commit is unavailable", async () => { const recordedBaseCommit = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + const patchPath = createPatchFile("should-fall-back-to-current-head-when-base-commit-is-unavail", null, recordedBaseCommit); mockExec.getExecOutput.mockResolvedValue({ exitCode: 0, stdout: "abc123\n", stderr: "" }); mockExec.exec.mockImplementation(async (cmd, args) => { if (cmd === "git" && Array.isArray(args) && args[0] === "cat-file" && args[1] === "-e" && args[2] === recordedBaseCommit) { @@ -941,7 +948,7 @@ index 0000000..abc1234 try { const module = await loadModule(); const handler = await module.main({}); - const result = await handler({ base_commit: recordedBaseCommit, branch: "should-fall-back-to-current-head-when-base-commit-is-unavail" }, {}); + const result = await handler({ branch: "should-fall-back-to-current-head-when-base-commit-is-unavail" }, {}); expect(result.success).toBe(true); expect(mockExec.exec).not.toHaveBeenCalledWith("git", ["reset", "--hard", recordedBaseCommit], expect.any(Object)); @@ -951,7 +958,7 @@ index 0000000..abc1234 } }); - it("should ignore invalid message.base_commit for patch transport", async () => { + it("should ignore agent-supplied base_commit for patch transport", async () => { const patchPath = createPatchFile("should-ignore-invalid-message-base-commit-for-patch-transpor"); mockExec.getExecOutput.mockResolvedValue({ exitCode: 0, stdout: "abc123\n", stderr: "" }); @@ -962,7 +969,7 @@ index 0000000..abc1234 expect(result.success).toBe(true); expect(mockExec.exec).not.toHaveBeenCalledWith("git", ["cat-file", "-e", "not-a-sha --bad"], expect.any(Object)); expect(mockExec.exec).not.toHaveBeenCalledWith("git", ["reset", "--hard", "not-a-sha --bad"], expect.any(Object)); - expect(mockCore.warning).toHaveBeenCalledWith("Ignoring invalid base_commit value for patch apply: not-a-sha --bad"); + expect(mockCore.warning).not.toHaveBeenCalledWith(expect.stringContaining("base_commit")); }); it("should use pushed commit SHA returned by pushSignedCommits for activation comment commit link", async () => { @@ -2059,11 +2066,7 @@ index 0000000..abc1234 expect(mockCore.info).toHaveBeenCalledWith("Patch size validation passed"); }); - it("should prefer message.diff_size (incremental net diff) over patch file size", async () => { - // Simulate the long-running branch case: a large format-patch file - // (e.g. 2 MB of cumulative commit metadata + per-commit diffs) but a - // tiny incremental net diff (e.g. 5 KB of actual changes since - // origin/). The size check must use diff_size and accept the push. + it("should ignore message.diff_size and enforce the patch file size", async () => { const largePatch = "x".repeat(2 * 1024 * 1024); // 2 MB format-patch file const patchPath = createPatchFile("should-prefer-message-diff-size-incremental-net-diff-over-pa", largePatch); @@ -2073,25 +2076,19 @@ index 0000000..abc1234 const handler = await module.main({ max_patch_size: 1024 }); // 1 MB max const result = await handler({ diff_size: 5 * 1024, branch: "should-prefer-message-diff-size-incremental-net-diff-over-pa" }, {}); - expect(result.success).toBe(true); - expect(mockCore.info).toHaveBeenCalledWith("Patch size validation passed"); - // Verify the size check used the incremental (diff_size) value, not the - // 2 MB file size. - expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Incremental diff size: 5 KB")); + expect(result.success).toBe(false); + expect(result.error).toContain("Patch size"); }); - it("should reject when message.diff_size exceeds max size even if file size is small", async () => { - // Inverse case: small file (defensive — shouldn't happen in practice) - // but a recorded large diff_size should still cause rejection. This - // proves diff_size is the source of truth for the size check. + it("should ignore an oversized message.diff_size when the patch is within limits", async () => { const patchPath = createPatchFile("should-reject-when-message-diff-size-exceeds-max-size-even-i"); // small valid patch const module = await loadModule(); const handler = await module.main({ max_patch_size: 1024 }); // 1 MB max const result = await handler({ diff_size: 2 * 1024 * 1024, branch: "should-reject-when-message-diff-size-exceeds-max-size-even-i" }, {}); - expect(result.success).toBe(false); - expect(result.error).toContain("exceeds maximum"); + expect(result.success).toBe(true); + expect(mockCore.info).toHaveBeenCalledWith("Patch size validation passed"); }); it("should fall back to patch file size when message.diff_size is not provided", async () => { @@ -2109,9 +2106,7 @@ index 0000000..abc1234 expect(result.error).toContain("exceeds maximum"); }); - it("should enforce max_patch_size against bundle size when bundle transport is used", async () => { - // Bundle transport still includes a patch for policy checks, but the size - // guard falls back to bundle size when diff_size is not provided. + it("should enforce max_patch_size against the uncompressed patch for bundle transport", async () => { const bundlePath = canonicalBundlePath("should-enforce-max-patch-size-against-bundle-size-when-bundl"); const patchPath = createPatchFile("should-enforce-max-patch-size-against-bundle-size-when-bundl", "small patch content"); // 2 MB dummy bundle file (contents don't matter; only size is checked) @@ -2121,15 +2116,11 @@ index 0000000..abc1234 const handler = await module.main({ max_patch_size: 1024 }); // 1 MB max const result = await handler({ branch: "should-enforce-max-patch-size-against-bundle-size-when-bundl" }, {}); - expect(result.success).toBe(false); - expect(result.error).toContain("exceeds maximum"); - expect(result.error).toMatch(/Bundle size|Incremental diff size/); + expect(result.success).toBe(true); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Patch size: 1 KB")); }); - it("should prefer diff_size over bundle file size for the limit check", async () => { - // Bundle is 2 MB on disk, but the incremental net diff is only 5 KB: - // the check must accept the push (limit reflects the real change, not the - // compressed transport size). + it("should ignore diff_size for bundle size checks", async () => { const bundlePath = canonicalBundlePath("should-prefer-diff-size-over-bundle-file-size-for-the-limit-"); const patchPath = createPatchFile("should-prefer-diff-size-over-bundle-file-size-for-the-limit-", "small patch content"); fs.writeFileSync(bundlePath, Buffer.alloc(2 * 1024 * 1024)); @@ -2142,7 +2133,32 @@ index 0000000..abc1234 expect(result.success).toBe(true); expect(mockCore.info).toHaveBeenCalledWith("Patch size validation passed"); - expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Incremental diff size: 5 KB")); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Patch size: 1 KB")); + }); + + it("should enforce max_patch_size against expanded post-apply content", async () => { + const branch = "should-reject-expanded-post-apply-content"; + const patchPath = createPatchFile(branch, "small git binary patch"); + const changedFilePath = path.join(process.cwd(), "test.txt"); + fs.writeFileSync(changedFilePath, Buffer.alloc(2 * 1024 * 1024)); + mockExec.getExecOutput.mockImplementation(async (cmd, args) => { + const argList = Array.isArray(args) ? args : []; + if (cmd === "git" && argList[0] === "diff" && argList[1] === "--name-only") { + return { exitCode: 0, stdout: "test.txt\0", stderr: "" }; + } + return { exitCode: 0, stdout: "abc123\n", stderr: "" }; + }); + + try { + const module = await loadModule(); + const handler = await module.main({ max_patch_size: 1024 }); // 1 MB max + const result = await handler({ branch }, {}); + + expect(result.success).toBe(false); + expect(result.error).toContain("Changed content size"); + } finally { + fs.rmSync(changedFilePath, { force: true }); + } }); }); @@ -2257,7 +2273,7 @@ index 0000000..abc1234 return Promise.resolve({ exitCode: 0, stdout: "", stderr: "" }); } if (cmd === "git" && args[0] === "diff" && args[1] === "--name-only" && args[2] === "--no-renames") { - return Promise.resolve({ exitCode: 0, stdout: `${actualFiles.join("\n")}\n`, stderr: "" }); + return Promise.resolve({ exitCode: 0, stdout: `${actualFiles.join("\0")}\0`, stderr: "" }); } if (cmd === "git" && args[0] === "rev-list") { return Promise.resolve({ exitCode: 0, stdout: "2\n", stderr: "" }); @@ -2273,8 +2289,8 @@ index 0000000..abc1234 expect(mockCore.info).toHaveBeenCalledWith("Pre-apply bundle verification: 4 file(s) detected from bundle transport"); const diffCalls = mockExec.getExecOutput.mock.calls.filter(([, args]) => Array.isArray(args) && args[0] === "diff" && args[1] === "--name-only" && args[2] === "--no-renames"); - expect(diffCalls.map(([, args]) => args[3])).toContain("remote-head..refs/bundles/push-feature-branch"); - expect(diffCalls.map(([, args]) => args[3])).toContain("remote-head..HEAD"); + expect(diffCalls.map(([, args]) => args[4])).toContain("remote-head..refs/bundles/push-feature-branch"); + expect(diffCalls.map(([, args]) => args[4])).toContain("remote-head..HEAD"); } finally { pushSignedSpy.mockRestore(); } @@ -2963,7 +2979,7 @@ ${diffs} const patchPath = createPatchFile("should-accept-files-that-match-the-allowed-files-pattern", createPatchWithFiles(".changeset/my-feature-fix.md")); mockExec.getExecOutput.mockImplementation(async (cmd, args) => { if (cmd === "git" && Array.isArray(args) && args[0] === "diff" && args[1] === "--name-only" && args[2] === "--no-renames") { - return { exitCode: 0, stdout: ".changeset/my-feature-fix.md\n", stderr: "" }; + return { exitCode: 0, stdout: ".changeset/my-feature-fix.md\0", stderr: "" }; } return { exitCode: 0, stdout: "abc123\n", stderr: "" }; }); @@ -2998,7 +3014,7 @@ ${diffs} const patchPath = createPatchFile("should-allow-a-protected-file-when-both-allowed-files-matche", createPatchWithFiles("package.json")); mockExec.getExecOutput.mockImplementation(async (cmd, args) => { if (cmd === "git" && Array.isArray(args) && args[0] === "diff" && args[1] === "--name-only" && args[2] === "--no-renames") { - return { exitCode: 0, stdout: "package.json\n", stderr: "" }; + return { exitCode: 0, stdout: "package.json\0", stderr: "" }; } return { exitCode: 0, stdout: "abc123\n", stderr: "" }; }); diff --git a/actions/setup/js/safe_output_type_validator.cjs b/actions/setup/js/safe_output_type_validator.cjs index 1900088f186..6e2881bdf5f 100644 --- a/actions/setup/js/safe_output_type_validator.cjs +++ b/actions/setup/js/safe_output_type_validator.cjs @@ -353,7 +353,7 @@ function validateOptionalPositiveInteger(value, fieldName, lineNum) { * @param {any} value - Value to validate * @param {string} fieldName - Field name for error messages * @param {number} lineNum - Line number for error messages - * @returns {{isValid: boolean, error?: string}} + * @returns {{isValid: boolean, normalizedValue?: number|string, error?: string}} */ function validateIssueOrPRNumber(value, fieldName, lineNum) { if (value === undefined) { @@ -365,7 +365,7 @@ function validateIssueOrPRNumber(value, fieldName, lineNum) { error: `Line ${lineNum}: ${fieldName} must be a number or string`, }; } - return { isValid: true }; + return { isValid: true, normalizedValue: value }; } /** @@ -453,6 +453,17 @@ function validateField(value, fieldName, validation, itemType, lineNum, options) return validateIssueOrPRNumber(value, `${itemType} '${fieldName}'`, lineNum); } + if (Array.isArray(validation.type)) { + const actualType = Array.isArray(value) ? "array" : typeof value; + if (!validation.type.includes(actualType)) { + return { + isValid: false, + error: `Line ${lineNum}: ${itemType} '${fieldName}' must be one of: ${validation.type.join(", ")}`, + }; + } + return { isValid: true, normalizedValue: value }; + } + // Handle type validation if (validation.type === "string") { if (typeof value !== "string") { @@ -703,7 +714,10 @@ function validateItem(item, itemType, lineNum, options) { return { isValid: true, normalizedItem: item }; } - const normalizedItem = { ...item }; + // Build the downstream payload from the declared contract. The raw item is + // agent-controlled, so forwarding undeclared fields would let consumers act + // on values that were never validated. + const normalizedItem = { type: item.type }; const errors = []; // Run custom validation first if defined @@ -729,6 +743,14 @@ function validateItem(item, itemType, lineNum, options) { } } else if (result.normalizedValue !== undefined) { normalizedItem[fieldName] = result.normalizedValue; + } else if (fieldValue !== undefined) { + let validationKind = Object.keys(validation).sort().join(",") || "unspecified"; + if (validation.type) { + const validationType = Array.isArray(validation.type) ? validation.type.join("|") : validation.type; + validationKind = `type=${validationType}`; + } + const fieldValueType = Array.isArray(fieldValue) ? "array" : typeof fieldValue; + errors.push(`Line ${lineNum}: ${itemType} '${fieldName}' validation (${validationKind}) accepted ${fieldValueType} but did not produce a normalized value`); } } diff --git a/actions/setup/js/safe_output_type_validator.test.cjs b/actions/setup/js/safe_output_type_validator.test.cjs index 3784f4844be..472267f1c20 100644 --- a/actions/setup/js/safe_output_type_validator.test.cjs +++ b/actions/setup/js/safe_output_type_validator.test.cjs @@ -27,6 +27,7 @@ const SAMPLE_VALIDATION_CONFIG = { fields: { body: { required: true, type: "string", sanitize: true, maxLength: 65000 }, item_number: { issueOrPRNumber: true }, + comment_id: { optionalPositiveInteger: true }, }, }, create_pull_request: { @@ -113,6 +114,24 @@ const SAMPLE_VALIDATION_CONFIG = { repo: { type: "string", maxLength: 256 }, }, }, + upload_asset: { + defaultMax: 10, + fields: { + path: { required: true, type: "string" }, + }, + }, + close_issue: { + defaultMax: 1, + fields: { + issue_number: { optionalPositiveInteger: true }, + }, + }, + push_to_pull_request_branch: { + defaultMax: 1, + fields: { + message: { required: true, type: "string", sanitize: true, maxLength: 65000 }, + }, + }, set_issue_type: { defaultMax: 5, fields: { @@ -1343,8 +1362,8 @@ describe("safe_output_type_validator", () => { }); }); - describe("undeclared field passthrough", () => { - it("should preserve base_commit on normalizedItem", async () => { + describe("undeclared fields", () => { + it("should preserve the normalized type and declared fields", async () => { const { validateItem } = await import("./safe_output_type_validator.cjs"); const item = { @@ -1352,31 +1371,39 @@ describe("safe_output_type_validator", () => { title: "Fix bug", body: "Fixes the thing", branch: "fix/bug", - base_commit: "abc123deadbeef", }; const result = validateItem(item, "create_pull_request", 1); expect(result.isValid).toBe(true); - expect(result.normalizedItem.base_commit).toBe("abc123deadbeef"); + expect(result.normalizedItem).toEqual(item); }); - it("should preserve diff_size on normalizedItem", async () => { + it("should preserve declared add_comment.comment_id as a positive integer", async () => { const { validateItem } = await import("./safe_output_type_validator.cjs"); - const item = { - type: "create_pull_request", - title: "Fix bug", - body: "Fixes the thing", - branch: "fix/bug", - diff_size: 1, - }; + const result = validateItem({ type: "add_comment", body: "Test comment", comment_id: "123" }, "add_comment", 1); + expect(result.isValid).toBe(true); + expect(result.normalizedItem).toEqual({ type: "add_comment", body: "Test comment", comment_id: 123 }); + }); - const result = validateItem(item, "create_pull_request", 1); + it.each([ + { itemType: "update_pull_request", item: { type: "update_pull_request", title: "Updated title", base: "release", state: "closed" }, fieldName: "base" }, + { itemType: "update_pull_request", item: { type: "update_pull_request", title: "Updated title", base: "release", state: "closed" }, fieldName: "state" }, + { itemType: "upload_asset", item: { type: "upload_asset", path: "image.png", targetFileName: "../../.git/config" }, fieldName: "targetFileName" }, + { itemType: "create_issue", item: { type: "create_issue", title: "Test", body: "Detailed issue body text.", assignees: ["octocat"] }, fieldName: "assignees" }, + { itemType: "create_discussion", item: { type: "create_discussion", title: "Test", body: "This discussion body is intentionally long enough for validation.", labels: ["security"] }, fieldName: "labels" }, + { itemType: "close_issue", item: { type: "close_issue", issue_number: 1, state_reason: "not_planned" }, fieldName: "state_reason" }, + { itemType: "push_to_pull_request_branch", item: { type: "push_to_pull_request_branch", message: "Apply changes", diff_size: 0 }, fieldName: "diff_size" }, + { itemType: "create_pull_request", item: { type: "create_pull_request", title: "Fix bug", body: "Fixes the thing", branch: "fix/bug", base_commit: "abc123deadbeef" }, fieldName: "base_commit" }, + ])("should strip undeclared $itemType.$fieldName", async ({ itemType, item, fieldName }) => { + const { validateItem } = await import("./safe_output_type_validator.cjs"); + + const result = validateItem(item, itemType, 1); expect(result.isValid).toBe(true); - expect(result.normalizedItem.diff_size).toBe(1); + expect(result.normalizedItem).not.toHaveProperty(fieldName); }); - it("should preserve undeclared fields", async () => { + it("should preserve enabled structured data", async () => { const { validateItem } = await import("./safe_output_type_validator.cjs"); const item = { @@ -1386,7 +1413,7 @@ describe("safe_output_type_validator", () => { data: { project: "test" }, }; - const result = validateItem(item, "create_issue", 1); + const result = validateItem(item, "create_issue", 1, { dataEnabled: true }); expect(result.isValid).toBe(true); expect(result.normalizedItem.data).toEqual({ project: "test" }); }); diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index 37b7b5d5be3..73ea7006a87 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -158,6 +158,75 @@ function parseAllowedBranchPatterns(value) { return []; } +/** + * Parse trusted comment IDs supplied by workflow configuration. + * @param {unknown} value + * @returns {Set} + */ +function parseAllowedCommentIds(value) { + const values = []; + const visit = item => { + if (Array.isArray(item)) { + for (const child of item) visit(child); + return; + } + if (typeof item === "number" && Number.isInteger(item) && item > 0) { + values.push(String(item)); + return; + } + if (typeof item !== "string") { + return; + } + const trimmed = item.trim(); + if (!trimmed) { + return; + } + if (trimmed.startsWith("[") || trimmed.startsWith('"')) { + try { + visit(JSON.parse(trimmed)); + return; + } catch { + // Fall through to delimiter parsing. + } + } + for (const part of trimmed.split(/[,\s]+/)) { + if (/^[1-9]\d*$/.test(part)) { + values.push(part); + } + } + }; + visit(value); + return new Set(values); +} + +/** + * Validate an agent-supplied add_comment.comment_id against the trusted workflow allowlist. + * Does not mutate the supplied entry; the caller is responsible for applying the normalized value. + * @param {Record} entry + * @param {Record} addCommentConfig + * @returns {{error: {content: Array<{type: "text", text: string}>, isError: true}} | {error: null, commentId: number | undefined}} + */ +function validateAllowedAddCommentId(entry, addCommentConfig) { + if (entry.comment_id === undefined || entry.comment_id === null || String(entry.comment_id).trim() === "") { + return { error: null, commentId: undefined }; + } + if (addCommentConfig.target !== "*") { + return { error: buildIntentErrorResponse("add_comment comment_id is only allowed when safe-outputs.add-comment.target is '*' and the ID is listed in safe-outputs.add-comment.allows-comment-ids.") }; + } + const commentId = Number(entry.comment_id); + if (!Number.isInteger(commentId) || commentId <= 0) { + return { error: buildIntentErrorResponse("add_comment comment_id must be a positive integer.") }; + } + const allowedCommentIds = parseAllowedCommentIds(addCommentConfig.allows_comment_ids ?? addCommentConfig["allows-comment-ids"]); + if (allowedCommentIds.size === 0) { + return { error: buildIntentErrorResponse("add_comment comment_id requires safe-outputs.add-comment.allows-comment-ids to list trusted comment IDs.") }; + } + if (!allowedCommentIds.has(String(commentId))) { + return { error: buildIntentErrorResponse("add_comment comment_id is not listed in safe-outputs.add-comment.allows-comment-ids.") }; + } + return { error: null, commentId }; +} + /** * @param {string} branch * @param {string[]} allowedPatterns @@ -231,6 +300,7 @@ function createHandlers(server, appendSafeOutput, config = {}) { * @type {Map} */ const operationCounts = new Map(); + const uploadedAssetPaths = new Set(); /** * Return the explicitly user-configured max for a safe-output type, or null if not set / unlimited. @@ -461,6 +531,9 @@ function createHandlers(server, appendSafeOutput, config = {}) { if (!isInWorkspace && !isInTmp) { throw new Error(`${ERR_CONFIG}: File path must be within workspace directory (${workspaceDir}) or /tmp directory. ` + `Provided path: ${filePath} (resolved to: ${absolutePath})`); } + if (uploadedAssetPaths.has(absolutePath)) { + throw new Error(`${ERR_VALIDATION}: Duplicate upload_asset source path is not allowed: ${filePath}`); + } // Validate file exists if (!fs.existsSync(filePath)) { @@ -526,8 +599,10 @@ function createHandlers(server, appendSafeOutput, config = {}) { const fileName = path.basename(filePath); const fileExt = path.extname(fileName).toLowerCase(); - // Copy file to assets directory with original name - const targetPath = path.join(assetsDir, fileName); + // Key the staged file by its declared source path so same-basename assets + // cannot overwrite each other before the privileged publishing job. + const stagedFileName = `${crypto.createHash("sha256").update(filePath).digest("hex")}${fileExt}`; + const targetPath = path.join(assetsDir, stagedFileName); try { fs.copyFileSync(filePath, targetPath); } catch (err) { @@ -564,6 +639,7 @@ function createHandlers(server, appendSafeOutput, config = {}) { }; appendSafeOutputCounted(entry); + uploadedAssetPaths.add(absolutePath); return { content: [ @@ -1955,6 +2031,18 @@ function createHandlers(server, appendSafeOutput, config = {}) { // Build the entry with a temporary_id const entry = { ...(args || {}), type: "add_comment" }; + const commentIdValidationResult = validateAllowedAddCommentId(entry, addCommentConfig); + if (commentIdValidationResult.error) { + return commentIdValidationResult.error; + } + if (commentIdValidationResult.commentId === undefined) { + // entry was spread from args, so a blank/whitespace comment_id (rather than an + // absent one) could still be sitting on entry; strip it so downstream code never + // sees an unvalidated raw value. + delete entry.comment_id; + } else { + entry.comment_id = commentIdValidationResult.commentId; + } const wildcardTargetValidationError = validateWildcardTargetRequirement(entry); if (wildcardTargetValidationError) { return wildcardTargetValidationError; diff --git a/actions/setup/js/safe_outputs_handlers.test.cjs b/actions/setup/js/safe_outputs_handlers.test.cjs index cbb607d435a..3fbb1f2b926 100644 --- a/actions/setup/js/safe_outputs_handlers.test.cjs +++ b/actions/setup/js/safe_outputs_handlers.test.cjs @@ -1,4 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import crypto from "crypto"; import fs from "fs"; import path from "path"; import { execSync } from "child_process"; @@ -351,7 +352,19 @@ describe("safe_outputs_handlers", () => { // File must be staged under RUNNER_TEMP, not hardcoded /tmp const expectedDir = path.join(testRunnerTemp, "gh-aw", "safeoutputs", "assets"); - expect(fs.existsSync(path.join(expectedDir, "chart.png"))).toBe(true); + const stagedFileName = `${crypto.createHash("sha256").update(testFile).digest("hex")}.png`; + expect(fs.existsSync(path.join(expectedDir, stagedFileName))).toBe(true); + }); + + it("should reject duplicate upload_asset source paths", () => { + process.env.GH_AW_ASSETS_BRANCH = "assets/test"; + const testFile = path.join(testWorkspaceDir, "duplicate.png"); + fs.writeFileSync(testFile, "first content"); + const args = { path: testFile }; + + handlers.uploadAssetHandler(args); + + expect(() => handlers.uploadAssetHandler(args)).toThrow("Duplicate upload_asset source path is not allowed"); }); it("should throw error if GH_AW_ASSETS_BRANCH not set", () => { @@ -2335,6 +2348,72 @@ describe("safe_outputs_handlers", () => { expect(mockAppendSafeOutput).not.toHaveBeenCalled(); }); + it("should allow comment_id from allows-comment-ids when add_comment target is '*'", () => { + const wildcardHandlers = createHandlers(mockServer, mockAppendSafeOutput, { + add_comment: { + target: "*", + allows_comment_ids: ["12345", "67890"], + }, + }); + + const result = wildcardHandlers.addCommentHandler({ body: "Update an existing status-style comment.", comment_id: "12345" }); + + expect(result).toHaveProperty("content"); + const responseData = JSON.parse(result.content[0].text); + expect(responseData.result).toBe("success"); + expect(mockAppendSafeOutput).toHaveBeenCalledWith(expect.objectContaining({ type: "add_comment", comment_id: 12345 })); + }); + + it("should reject comment_id that is not listed in allows-comment-ids", () => { + const wildcardHandlers = createHandlers(mockServer, mockAppendSafeOutput, { + add_comment: { + target: "*", + allows_comment_ids: ["12345"], + }, + }); + + const result = wildcardHandlers.addCommentHandler({ body: "Update an existing status-style comment.", comment_id: "67890" }); + + expect(result.isError).toBe(true); + const responseData = JSON.parse(result.content[0].text); + expect(responseData.result).toBe("error"); + expect(responseData.error).toContain("allows-comment-ids"); + expect(mockAppendSafeOutput).not.toHaveBeenCalled(); + }); + + it("should reject comment_id when add_comment target is not '*'", () => { + const targetingHandlers = createHandlers(mockServer, mockAppendSafeOutput, { + add_comment: { + target: "triggering", + allows_comment_ids: ["12345"], + }, + }); + + const result = targetingHandlers.addCommentHandler({ item_number: 42, body: "Update an existing status-style comment.", comment_id: "12345" }); + + expect(result.isError).toBe(true); + const responseData = JSON.parse(result.content[0].text); + expect(responseData.result).toBe("error"); + expect(responseData.error).toContain("target is '*'"); + expect(mockAppendSafeOutput).not.toHaveBeenCalled(); + }); + + it("should reject comment_id when allows-comment-ids is empty", () => { + const wildcardHandlers = createHandlers(mockServer, mockAppendSafeOutput, { + add_comment: { + target: "*", + }, + }); + + const result = wildcardHandlers.addCommentHandler({ body: "Update an existing status-style comment.", comment_id: "12345" }); + + expect(result.isError).toBe(true); + const responseData = JSON.parse(result.content[0].text); + expect(responseData.result).toBe("error"); + expect(responseData.error).toContain("allows-comment-ids"); + expect(mockAppendSafeOutput).not.toHaveBeenCalled(); + }); + it("should refuse reply_to_id when discussions are not enabled in config", () => { // Default handlers have no discussions: true in config // Discussion check precedes context check so this error surfaces regardless of event context diff --git a/actions/setup/js/safe_outputs_tools.json b/actions/setup/js/safe_outputs_tools.json index 03b10fe4553..2775fb4eb44 100644 --- a/actions/setup/js/safe_outputs_tools.json +++ b/actions/setup/js/safe_outputs_tools.json @@ -327,16 +327,16 @@ "description": "Node ID of the discussion comment to reply to, enabling threaded discussion comments. When provided, the new comment is posted as a reply to the specified top-level discussion comment. If the given node ID belongs to a nested reply, the handler automatically resolves it to the top-level parent. Only applicable for discussion comments \u2014 ignored for issue and pull request comments.", "x-synonyms": ["replyToId"] }, - "comment_id": { - "type": ["number", "string"], - "description": "Existing issue or pull request comment ID to update instead of creating a new comment.", - "x-synonyms": ["commentId"] - }, "target": { "type": "string", "enum": ["status"], "description": "When set to 'status', updates the activation status comment for this run (if available) instead of creating a new comment." }, + "comment_id": { + "type": ["number", "string"], + "description": "Existing issue or pull request comment ID to update. Only valid when the workflow config sets safe-outputs.add-comment.target to '*' and the ID is present in safe-outputs.add-comment.allows-comment-ids. Use this only for comment IDs precomputed by trusted workflow steps.", + "x-synonyms": ["commentId", "comment-id"] + }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." @@ -351,7 +351,7 @@ "x-safe-outputs-target-requirements": { "*": { "primary": "item_number", - "anyOf": ["item_number", "pr_number", "pr"] + "anyOf": ["item_number", "pr_number", "pr", "comment_id"] } } }, diff --git a/actions/setup/js/upload_assets.cjs b/actions/setup/js/upload_assets.cjs index 2e4ce07edcc..03ba8e91a54 100644 --- a/actions/setup/js/upload_assets.cjs +++ b/actions/setup/js/upload_assets.cjs @@ -10,9 +10,28 @@ const { ERR_API, ERR_CONFIG, ERR_SYSTEM, ERR_VALIDATION } = require("./error_cod const { normalizeBranchName } = require("./normalize_branch_name.cjs"); /** - * @typedef {{ type: string, fileName: string, sha: string, size: number, targetFileName: string, url?: string }} UploadAssetItem + * @typedef {{ type: string, path?: string, fileName: string, sha: string, size: number, targetFileName: string, url?: string }} UploadAssetItem */ +/** + * @param {string} githubServer + * @param {string} repo + * @param {string} branchName + * @param {string} targetFileName + * @returns {string} + */ +function buildAssetUrl(githubServer, repo, branchName, targetFileName) { + try { + const serverHostname = new URL(githubServer).hostname; + if (serverHostname === "github.com") { + return `https://github.com/${repo}/blob/${branchName}/${targetFileName}?raw=true`; + } + } catch { + // Fall through to the GHES-compatible raw URL. + } + return `${githubServer}/${repo}/raw/${branchName}/${targetFileName}`; +} + async function main() { // Check if we're in staged mode const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; @@ -58,6 +77,7 @@ async function main() { let uploadCount = 0; let missingAssetCount = 0; let hasChanges = false; + const processedAssets = []; try { // Check if orphaned branch already exists, if not create it @@ -85,15 +105,23 @@ async function main() { // Process each asset for (const asset of uploadItems) { - const { fileName, sha, size, targetFileName } = asset; + const declaredPath = typeof asset.path === "string" ? asset.path : ""; + const pathFileName = declaredPath ? path.basename(declaredPath) : ""; + const rawFileName = typeof asset.fileName === "string" ? asset.fileName : pathFileName; + const fileName = path.basename(rawFileName); - if (!fileName || !sha || !targetFileName) { + if (!fileName || (asset.fileName && asset.fileName !== fileName)) { + core.setFailed(`${ERR_VALIDATION}: Invalid asset filename: ${JSON.stringify(asset)}`); + return; + } + if (!pathFileName && (!asset.sha || !asset.targetFileName)) { core.setFailed(`${ERR_VALIDATION}: Invalid asset entry missing required fields: ${JSON.stringify(asset)}`); return; } // Check if file exists in the staged-assets directory - const assetSourcePath = path.join(assetsDir, fileName); + const stagedFileName = pathFileName ? `${crypto.createHash("sha256").update(declaredPath).digest("hex")}${path.extname(fileName).toLowerCase()}` : fileName; + const assetSourcePath = path.join(assetsDir, stagedFileName); if (!fs.existsSync(assetSourcePath)) { core.warning(`${ERR_SYSTEM}: Asset file not found: ${assetSourcePath} — skipping`); missingAssetCount++; @@ -104,14 +132,30 @@ async function main() { const fileContent = fs.readFileSync(assetSourcePath); const computedSha = crypto.createHash("sha256").update(fileContent).digest("hex"); - if (computedSha !== sha) { - core.setFailed(`${ERR_VALIDATION}: SHA mismatch for ${fileName}: expected ${sha}, got ${computedSha}`); + if (asset.sha && computedSha !== asset.sha) { + core.setFailed(`${ERR_VALIDATION}: SHA mismatch for ${fileName}: expected ${asset.sha}, got ${computedSha}`); + return; + } + + const generatedTargetFileName = `${computedSha}${path.extname(fileName).toLowerCase()}`; + // In path mode, the source path and content are re-derived from trusted staged state, + // so always compute the target filename server-side and ignore any agent-supplied value. + const targetFileName = pathFileName ? generatedTargetFileName : asset.targetFileName || generatedTargetFileName; + if (targetFileName !== path.basename(targetFileName)) { + core.setFailed(`${ERR_VALIDATION}: Invalid asset target filename: ${targetFileName}`); return; } + const size = fileContent.length; + const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com"; + const repo = process.env.GITHUB_REPOSITORY || "owner/repo"; + const url = buildAssetUrl(githubServer, repo, normalizedBranchName, targetFileName); + const processedAsset = { fileName, sha: computedSha, size, targetFileName, url }; + // Check if file already exists in the branch if (fs.existsSync(targetFileName)) { core.info(`Asset ${targetFileName} already exists, skipping`); + processedAssets.push(processedAsset); continue; } @@ -124,6 +168,7 @@ async function main() { uploadCount++; hasChanges = true; + processedAssets.push(processedAsset); core.info(`Added asset: ${targetFileName} (${size} bytes)`); } catch (error) { @@ -149,10 +194,8 @@ async function main() { core.info(`Successfully uploaded ${uploadCount} assets to branch ${normalizedBranchName}`); } - for (const asset of uploadItems) { - if (asset.fileName && asset.sha && asset.size && asset.url) { - core.summary.addRaw(`- [\`${asset.fileName}\`](${asset.url}) → \`${asset.targetFileName}\` (${asset.size} bytes)`); - } + for (const asset of processedAssets) { + core.summary.addRaw(`- [\`${asset.fileName}\`](${asset.url}) → \`${asset.targetFileName}\` (${asset.size} bytes)`); } await core.summary.write(); } else { diff --git a/actions/setup/js/upload_assets.test.cjs b/actions/setup/js/upload_assets.test.cjs index e04b43ae859..87b23d3ce1d 100644 --- a/actions/setup/js/upload_assets.test.cjs +++ b/actions/setup/js/upload_assets.test.cjs @@ -70,6 +70,8 @@ describe("upload_assets.cjs", () => { delete process.env.GH_AW_AGENT_OUTPUT; delete process.env.GH_AW_ASSETS_DIR; delete process.env.GH_AW_SAFE_OUTPUTS_STAGED; + delete process.env.GITHUB_SERVER_URL; + delete process.env.GITHUB_REPOSITORY; tempBase = fs.mkdtempSync(path.join("/tmp", "test-gh-aw-")); cwdArtifacts = new Set(); @@ -229,6 +231,58 @@ describe("upload_assets.cjs", () => { await executeScript(); expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("missing required fields")); }); + + it("should derive trusted metadata from a validated asset path", async () => { + process.env.GH_AW_ASSETS_BRANCH = "assets/test-workflow"; + process.env.GH_AW_SAFE_OUTPUTS_STAGED = "false"; + const assetDir = getAssetsDir(); + fs.mkdirSync(assetDir, { recursive: true }); + const declaredPath = "/workspace/test.png"; + const stagedFileName = `${crypto.createHash("sha256").update(declaredPath).digest("hex")}.png`; + const { sha } = makeAsset(assetDir, stagedFileName, "actual content"); + trackCwdArtifact(`${sha}.png`); + setAgentOutput({ items: [{ type: "upload_asset", path: declaredPath }] }); + mockBranchMissing(); + + await executeScript(); + expect(mockCore.setFailed).not.toHaveBeenCalled(); + expect(mockCore.setOutput).toHaveBeenCalledWith("upload_count", "1"); + }); + + it("should derive GHES raw URLs from trusted metadata", async () => { + process.env.GH_AW_ASSETS_BRANCH = "assets/test-workflow"; + process.env.GH_AW_SAFE_OUTPUTS_STAGED = "false"; + process.env.GITHUB_SERVER_URL = "https://ghe.example.com"; + process.env.GITHUB_REPOSITORY = "octo/repo"; + const assetDir = getAssetsDir(); + fs.mkdirSync(assetDir, { recursive: true }); + const declaredPath = "/workspace/test.png"; + const stagedFileName = `${crypto.createHash("sha256").update(declaredPath).digest("hex")}.png`; + const { sha } = makeAsset(assetDir, stagedFileName, "actual content"); + trackCwdArtifact(`${sha}.png`); + setAgentOutput({ items: [{ type: "upload_asset", path: declaredPath }] }); + mockBranchMissing(); + + await executeScript(); + + expect(mockCore.setFailed).not.toHaveBeenCalled(); + expect(mockCore.summary.addRaw).toHaveBeenCalledWith(expect.stringContaining(`https://ghe.example.com/octo/repo/raw/assets/test-workflow/${sha}.png`)); + }); + + it("should reject target filenames outside the checkout root", async () => { + process.env.GH_AW_ASSETS_BRANCH = "assets/test-workflow"; + process.env.GH_AW_SAFE_OUTPUTS_STAGED = "false"; + const assetDir = getAssetsDir(); + fs.mkdirSync(assetDir, { recursive: true }); + const { sha, size } = makeAsset(assetDir, "test.png", "actual content"); + setAgentOutput({ + items: [{ type: "upload_asset", fileName: "test.png", sha, size, targetFileName: "../../.git/config" }], + }); + mockBranchMissing(); + + await executeScript(); + expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("Invalid asset target filename")); + }); }); describe("missing asset handling", () => { @@ -254,6 +308,9 @@ describe("upload_assets.cjs", () => { const uploadCountCall = mockCore.setOutput.mock.calls.find(call => call[0] === "upload_count"); expect(uploadCountCall).toBeDefined(); if (uploadCountCall) expect(uploadCountCall[1]).toBe("1"); + const summary = mockCore.summary.addRaw.mock.calls.map(call => String(call[0])).join("\n"); + expect(summary).toContain("present-uploaded.png"); + expect(summary).not.toContain("missing-uploaded.png"); }); it("should fail when all declared assets are missing", async () => { diff --git a/actions/setup/setup.sh b/actions/setup/setup.sh index 3460859b4c2..5e60263d1ad 100755 --- a/actions/setup/setup.sh +++ b/actions/setup/setup.sh @@ -324,6 +324,7 @@ SAFE_OUTPUTS_FILES=( "generate_git_patch.cjs" "generate_git_bundle.cjs" "git_patch_utils.cjs" + "commit_sha_helpers.cjs" "get_base_branch.cjs" "get_current_branch.cjs" "normalize_branch_name.cjs" diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index c8ecbbaaf3c..2f5a974d597 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -283,6 +283,7 @@ safe-outputs: add-comment: max: 3 # max comments (default: 1) target: "*" # "triggering" (default), "*", or number + allows-comment-ids: ${{ needs.prepare.outputs.comment_ids }} # comment IDs the agent may update when target is "*" discussions: true # request discussions:write permission (default: false) target-repo: "owner/repo" # cross-repository allowed-repos: ["org/repo1", "org/repo2"] # additional allowed repositories @@ -297,6 +298,8 @@ safe-outputs: > [!TIP] > Use `footer: false` to suppress the "Generated by..." attribution line in posted comments. See [Footer Control](/gh-aw/reference/footers/) for global and per-handler options. +When `target: "*"` is configured, the agent may update an existing issue or pull request comment by passing `comment_id` only if that ID appears in `allows-comment-ids`. Populate `allows-comment-ids` from trusted workflow state (for example, an earlier step output) rather than agent output. + #### Normalize closing keywords Set `normalize-closing-keywords: true` to strip wrapping backticks from recognized issue-closing keywords in body text (for example, `` `Closes #123` `` becomes `Closes #123` so GitHub can process it as a closing keyword). This field is supported by `create-issue` and `add-comment` on this page, and by `create-pull-request` in [Safe Outputs (Pull Requests)](/gh-aw/reference/safe-outputs-pull-requests/#pull-request-creation-create-pull-request). diff --git a/docs/src/content/docs/specs/safe-outputs-specification.md b/docs/src/content/docs/specs/safe-outputs-specification.md index 28150ed1fa4..f3718d729b0 100644 --- a/docs/src/content/docs/specs/safe-outputs-specification.md +++ b/docs/src/content/docs/specs/safe-outputs-specification.md @@ -7,11 +7,11 @@ sidebar: # Safe Outputs MCP Gateway Specification -**Version**: 1.28.0 -**Status**: Working Draft -**Publication Date**: 2026-07-31 -**Editor**: GitHub Agentic Workflows Team -**This Version**: [safe-outputs-specification](/gh-aw/specs/safe-outputs-specification/) +**Version**: 1.28.2
+**Status**: Working Draft
+**Publication Date**: 2026-08-07
+**Editor**: GitHub Agentic Workflows Team
+**This Version**: [safe-outputs-specification](/gh-aw/specs/safe-outputs-specification/)
**Latest Published Version**: This document --- @@ -1672,6 +1672,7 @@ create-issue: ```yaml add-comment: target: "issue" | "pull_request" | "discussion" | "*" + allows-comment-ids: [12345, 67890] # Required before agents may supply comment_id with target: "*" hide-older-comments: true # Hide previous workflow comments discussions: false # Exclude discussions:write permission (optional) target-repo: owner/repo @@ -2150,6 +2151,14 @@ The following table defines the exact `createHandlers()` function used for each | `missing_data` | `defaultHandler("missing_data")` | | `report_incomplete` | `defaultHandler("report_incomplete")` | +### 7.0.3 Declared Field Payload Construction + +The MCP Gateway and Safe Output Processor MUST construct downstream safe-output payloads only from the `type` field and fields declared by the applicable MCP schema, built-in validation configuration, or custom safe-job configuration. Agent-supplied fields that are not declared by that contract MUST NOT be forwarded to handlers, privileged jobs, or API clients. + +If an optional advisory or enrichment field is declared with `x-strip-on-error: true`, implementations MAY omit that field from the normalized downstream payload when the field is invalid. Implementations MUST NOT use stripped fields for authorization, target selection, transport metadata, or other privileged decisions. + +Fields used for privileged transport metadata, including patch anchoring and upload asset file metadata, MUST be derived by trusted workflow steps or privileged processors rather than accepted from agent-controlled NDJSON. + ### 7.1 Core Issue Operations #### Type: create_issue @@ -2271,6 +2280,10 @@ The following table defines the exact `createHandlers()` function used for each "item_number": { "type": "number", "description": "Issue/PR/discussion number (auto-resolved from context if omitted)" + }, + "comment_id": { + "type": ["number", "string"], + "description": "Existing issue or pull request comment ID to update. Valid only when safe-outputs.add-comment.target is \"*\" and the ID appears in safe-outputs.add-comment.allows-comment-ids." } }, "additionalProperties": false @@ -2286,14 +2299,17 @@ The following table defines the exact `createHandlers()` function used for each 4. **Footer Injection**: Appends footer according to configuration (typically 200-500 characters). 5. **Cross-Repository**: Supports `target-repo` configuration. -**Status Comment Reuse Extension (`target: "status"`)**: +**Controlled Comment Reuse Extensions**: -This extension applies to safe-output processor messages for `add_comment` (including system-generated status updates). It is distinct from the MCP input schema in this section. +These extensions apply to safe-output processor messages for `add_comment` (including system-generated status updates). -1. When `target: "status"` is set and a reusable status comment ID is available, implementations MUST update the existing issue/PR comment instead of creating a new comment. -2. When `target: "status"` is set but no reusable status comment ID is available, implementations MUST create a new comment. -3. `target: "status"` and `comment_id` MUST be rejected for discussion comments; they are valid only for issue and pull request comments. -4. When updating an existing comment through status-comment reuse, implementations SHOULD skip hide-older-comments behavior for that operation. +1. When message-level `target: "status"` is set and a reusable status comment ID is available from trusted workflow state, implementations MUST update the existing issue/PR comment instead of creating a new comment. +2. When message-level `target: "status"` is set but no reusable status comment ID is available from trusted workflow state, implementations MUST create a new comment. +3. Message-level `target: "status"` MUST be rejected for discussion comments; status-comment reuse is valid only for issue and pull request comments. +4. The MCP input schema for `add_comment` MAY expose `comment_id` as an agent-controlled input only for workflows that configure `safe-outputs.add-comment.target: "*"`. +5. When an agent supplies `comment_id`, implementations MUST reject the operation unless `safe-outputs.add-comment.allows-comment-ids` is configured and contains that exact positive integer ID. The allowlist is trusted workflow state and MAY be computed by earlier workflow steps. +6. Agent-supplied `comment_id` MUST NOT be honored for discussion comments and MUST NOT be treated as a substitute for the trusted status comment ID used by `target: "status"`. +7. When updating an existing comment through either controlled reuse path, implementations SHOULD skip hide-older-comments behavior for that operation. **Enforced Constraints**: @@ -2309,6 +2325,7 @@ This extension applies to safe-output processor messages for `add_comment` (incl - `max`: Operation limit (default: 1) - `target`: Filter by type ("issue", "pull_request", "discussion", "*"). This configuration field applies to static workflow configuration (`safe-outputs.add-comment.target`) and is distinct from the runtime per-message `target: "status"` extension above. +- `allows-comment-ids`: Trusted allowlist of issue/PR comment IDs that the agent may update with `comment_id` when `target: "*"` is configured. This field is REQUIRED before any agent-supplied `comment_id` is honored. Accepts an array of positive integer IDs, strings containing positive integer IDs, or a GitHub Actions expression that resolves to such a list. - `hide-older-comments`: Hide previous workflow comments - `discussions`: Control `discussions:write` permission (default: false). Set to `true` to comment on discussions. - `target-repo`: Cross-repository target @@ -3248,6 +3265,8 @@ This section provides complete definitions for all remaining safe output types. - Requires `contents: write` for git push operations - Enforces maximum patch size limit (default: 10 KB, range: 1–100 KB) - Validates changes don't exceed size limits before pushing +- The handler MUST ignore agent-supplied `diff_size` values and validate patch size from the generated patch artifact. +- For patch transport, the generated patch SHOULD embed `X-GH-AW-Base-Commit` metadata derived by the trusted patch-generation step. The privileged processor MUST derive patch re-anchoring metadata from the generated patch and MUST NOT trust agent-supplied base-commit metadata. - Base-branch resolution MUST NOT depend on interactive credential prompts; git operations issued by the handler MUST run with `GIT_TERMINAL_PROMPT=0` and an enforced timeout so credential-less environments fail fast rather than hanging - When `safe-outputs.push-to-pull-request-branch.target` is `"*"`, requests MUST include `pull_request_number`. - The handler MUST refuse pushes unless the resolved pull request head repository exactly matches the configured `head-repo` (or `target-repo` when `head-repo` is omitted) @@ -3770,6 +3789,9 @@ safe-outputs: - Creates or updates orphaned branch for asset storage - Enforces maximum file size limit (default: 10 MB = 10240 KB) - Files accessible via raw.githubusercontent.com URLs +- Staged asset filenames MUST be keyed by a hash of the declared source path, with the original extension preserved where applicable, so distinct source paths with the same basename do not collide. +- The privileged upload job MUST validate that staged asset paths remain contained within the staged-assets directory and that the expected staged filename matches the declared source path. +- The privileged upload job MUST compute asset size, SHA-256 digest, target filename, and published URL from staged file contents and trusted runtime context. It MUST NOT trust agent-supplied `size`, `sha`, `path`, `targetFileName`, or URL metadata for privileged decisions. --- @@ -5388,15 +5410,32 @@ safe-outputs: ## Appendix F: Document History -### Changelog Alignment (Reviewer and Status-Comment Updates) +### Changelog Alignment (Reviewer, Status-Comment, and Hardening Updates) This specification revision aligns with directly relevant `CHANGELOG.md` entries and with the current reviewer/status-comment PR updates: +- **Commit 9d80a262**: safe-output field validation was hardened so normalized downstream payloads contain only schema/config-declared fields, unconditional agent-controlled `add_comment.comment_id` was removed, upload asset metadata is re-derived by the privileged job, and patch base metadata is embedded in the generated patch. +- **Commit 178ff313**: `add_comment.comment_id` was reintroduced only for workflows that configure `safe-outputs.add-comment.target: "*"` and provide a trusted `safe-outputs.add-comment.allows-comment-ids` allowlist containing the requested comment ID. - **v0.40.1**: `add_comment` discussion handling was updated to auto-detect discussion context without requiring a `discussion` flag. - **v0.40.1**: append-only status comment behavior was documented for smoke workflow execution. - **Earlier changelog entry**: status comments were decoupled from default AI reaction behavior; explicit `on.status-comment` configuration is required when status comments are desired. - **Earlier changelog entry**: `command` trigger was renamed to `slash_command` with deprecation compatibility. +**Version 1.28.2** (2026-08-07): + +- **Added**: Controlled `add_comment.comment_id` support for wildcard comment targets. Agent-supplied comment IDs MAY be accepted only when `safe-outputs.add-comment.target` is `"*"` and the exact positive integer ID appears in trusted `safe-outputs.add-comment.allows-comment-ids` workflow state. +- **Specified**: `allows-comment-ids` is REQUIRED before any agent-supplied `comment_id` is honored and accepts literal positive integer IDs, stringified positive integer IDs, or GitHub Actions expressions that resolve to such a list. +- **Updated**: Publication metadata to 1.28.2. + +**Version 1.28.1** (2026-08-07): + +- **Specified**: Normalized downstream safe-output payloads MUST include only `type` plus schema/config-declared fields, with undeclared agent-supplied fields stripped before handler or privileged-job consumption. +- **Removed**: Unconditional agent-controlled `add_comment.comment_id` from the default MCP input contract; status-comment reuse is limited to `target: "status"` with reusable comment IDs obtained from trusted workflow state. +- **Specified**: Optional advisory/enrichment fields marked `x-strip-on-error` MAY be omitted when invalid. +- **Specified**: Upload asset staging and publication MUST derive collision-resistant staged filenames and asset metadata from trusted staged files rather than agent-supplied metadata. +- **Specified**: Patch base metadata MUST be derived from the generated patch, and agent-supplied `diff_size` and base-commit metadata MUST NOT control privileged patch processing. +- **Updated**: Publication metadata to 1.28.1. + **Version 1.28.0** (2026-07-31): - **Added**: GP5a specifying `safe-outputs..github-app` as a per-handler GitHub App override for safe output types. @@ -5578,7 +5617,9 @@ This section maps normative specification requirements (§3–§11) to implement | §5.2 Global Parameters | `footer`, `staged`, global max limits | `actions/setup/js/safe_outputs_config.cjs`, `pkg/workflow/compiler_safe_outputs.go` | | §6 Universal Feature Interpretation | Max limit semantics (MR1–MR4), staged mode (SM1–SM4), footer attribution (FA1–FA6) | `actions/setup/js/safe_outputs_handlers.cjs`, `actions/setup/js/safe_outputs_mcp_server.cjs` | | §7 Safe Output Type Definitions | Handler implementations for each type | `actions/setup/js/safe_outputs_handlers.cjs`, `actions/setup/js/safe_outputs_tools.json` | +| §7.0.3 Declared Field Payload Construction | Normalized payload construction, undeclared field stripping, `x-strip-on-error` advisory field handling | `actions/setup/js/collect_ndjson_output.cjs`, `actions/setup/js/safe_output_type_validator.cjs`, `pkg/workflow/safe_outputs_validation_config.go` | | §7.1 Core Issue Operations | `create_issue`, `add_comment`, `hide_comment`, `close_issue` | `actions/setup/js/add_comment.cjs`, `actions/setup/js/safe_outputs_handlers.cjs` | +| §7.3 `push_to_pull_request_branch` and `upload_asset` | Trusted patch metadata derivation and upload asset staged-file metadata derivation | `actions/setup/js/generate_git_patch.cjs`, `actions/setup/js/push_to_pull_request_branch.cjs`, `actions/setup/js/upload_assets.cjs` | | §8 Protocol Exchange Patterns | stdio container transport, tool invocation, MCP server constraint enforcement | `actions/setup/js/safe_outputs_mcp_server.cjs`, `actions/setup/js/safe_outputs_mcp_server_http.cjs` | | §8.3 MCE1 Early Validation | Invocation-time validation wiring through MCP server startup | `actions/setup/js/safe_outputs_mcp_server.cjs` (`startSafeOutputsServer` → `createHandlers()`), `actions/setup/js/safe_outputs_handlers.cjs` (`addCommentHandler`, `createIssueHandler`, `updatePullRequestHandler`) | | §8.3 MCE2 Tool Description Disclosure | Tool descriptions/schemas exposed during MCP tool registration | `actions/setup/js/safe_outputs_mcp_server.cjs` (`registerPredefinedTools` call), `actions/setup/js/safe_outputs_tools_loader.cjs` (`registerPredefinedTools`) | diff --git a/pkg/cli/codemod_bash_allowlist_unsupported_engine.go b/pkg/cli/codemod_bash_allowlist_unsupported_engine.go index 05a2d4ab215..74e85a2f638 100644 --- a/pkg/cli/codemod_bash_allowlist_unsupported_engine.go +++ b/pkg/cli/codemod_bash_allowlist_unsupported_engine.go @@ -3,6 +3,7 @@ package cli import ( "encoding/json" "fmt" + "maps" "path/filepath" "strings" @@ -127,11 +128,7 @@ func resolveEffectiveBashTools(content string, frontmatter map[string]any, fileP // Merge external tools into the top-level tools map, line by line (each line is a JSON object). effective := make(map[string]any) - if topTools != nil { - for k, v := range topTools { - effective[k] = v - } - } + maps.Copy(effective, topTools) for line := range strings.SplitSeq(allExternalTools, "\n") { line = strings.TrimSpace(line) if line == "" || line == "{}" { diff --git a/pkg/workflow/add_comment.go b/pkg/workflow/add_comment.go index bad1e7b2e69..e12e0f6e223 100644 --- a/pkg/workflow/add_comment.go +++ b/pkg/workflow/add_comment.go @@ -18,6 +18,7 @@ type AddCommentsConfig struct { Target string `yaml:"target,omitempty"` // Target for comments: "triggering" (default), "*" (any issue), or explicit issue number TargetRepoSlug string `yaml:"target-repo,omitempty"` // Target repository in format "owner/repo" for cross-repository comments AllowedRepos []string `yaml:"allowed-repos,omitempty"` // List of additional repositories that comments can be added to (additionally to the target-repo) + AllowedCommentIDs []string `yaml:"allows-comment-ids,omitempty"` // Trusted allowlist of issue/PR comment IDs the agent may update when target is "*" HideOlderComments *string `yaml:"hide-older-comments,omitempty"` // When true, minimizes/hides all previous comments from the same workflow before creating the new comment HideOlderCommentsMatch []string `yaml:"hide-older-comments-match,omitempty"` // Internal list populated from hide-older-comments.match and passed to the JS handler as exact workflow ID matches AllowedReasons []string `yaml:"allowed-reasons,omitempty"` // List of allowed reasons for hiding older comments (default: all reasons allowed) @@ -63,6 +64,10 @@ func (c *Compiler) parseCommentsConfig(outputMap map[string]any) *AddCommentsCon addCommentLog.Printf("Invalid allowed-repos value: %v", err) return nil } + if err := preprocessStringArrayFieldAsTemplatable(configData, "allows-comment-ids", addCommentLog); err != nil { + addCommentLog.Printf("Invalid allows-comment-ids value: %v", err) + return nil + } config := parseConfigScaffold(outputMap, "add-comment", addCommentLog, func(err error) *AddCommentsConfig { addCommentLog.Printf("Failed to unmarshal config: %v", err) diff --git a/pkg/workflow/awf_command_builder.go b/pkg/workflow/awf_command_builder.go index 4f3673575e5..898ca786a7b 100644 --- a/pkg/workflow/awf_command_builder.go +++ b/pkg/workflow/awf_command_builder.go @@ -38,8 +38,8 @@ func BuildAWFCommand(config AWFCommandConfig) string { // Auto-detect ARC/DinD split daemon topology at runtime: probe DOCKER_HOST for a // tcp:// scheme and pass it through to AWF via --docker-host. // All behaviors avoid requiring workflow-authored sandbox.agent.args for standard ARC DinD setups. - // When AWF also supports chroot config (v0.27.1+), the Python patch body is embedded inside - // the same if-block so the script only contains one DOCKER_HOST condition check. + // When AWF also supports chroot config (v0.27.1+), the chroot patch logic is embedded + // inside the same if-block so the script only contains one DOCKER_HOST condition check. arcDindPrefixProbe := "" arcDindDockerHostProbe := fmt.Sprintf(`%s="" if [[ "${DOCKER_HOST:-}" =~ %s ]]; then diff --git a/pkg/workflow/awf_command_builder_test.go b/pkg/workflow/awf_command_builder_test.go index f116cbf9a9c..76098a935d2 100644 --- a/pkg/workflow/awf_command_builder_test.go +++ b/pkg/workflow/awf_command_builder_test.go @@ -69,7 +69,6 @@ func TestBuildAWFArgsAuditDir(t *testing.T) { // TestBuildAWFArgsAllowHostPorts tests that BuildAWFArgs includes --allow-host-ports // with port 80, 443, and the MCP gateway port so the AWF agent container can reach // the gateway through the firewall's iptables rules. - func TestBuildAWFArgsAllowHostPorts(t *testing.T) { t.Run("includes default MCP gateway port 8080", func(t *testing.T) { config := AWFCommandConfig{ @@ -190,7 +189,6 @@ func TestBuildAWFArgsAllowHostPorts(t *testing.T) { // TestBuildAWFArgsDiagnosticLogs tests that BuildAWFArgs includes --diagnostic-logs // only when features.awf-diagnostic-logs is enabled. - func TestBuildAWFArgsDiagnosticLogs(t *testing.T) { baseWorkflow := func(features map[string]any) *WorkflowData { return &WorkflowData{ @@ -236,7 +234,6 @@ func TestBuildAWFArgsDiagnosticLogs(t *testing.T) { // TestBuildAWFArgsMemoryLimit tests that BuildAWFArgs passes --memory-limit // when sandbox.agent.memory is configured in the workflow frontmatter - func TestBuildAWFArgsMemoryLimit(t *testing.T) { t.Run("includes --memory-limit flag when memory is configured", func(t *testing.T) { workflowData := &WorkflowData{ diff --git a/pkg/workflow/awf_env.go b/pkg/workflow/awf_env.go index ed9c2065e4e..b5c24c4dfd9 100644 --- a/pkg/workflow/awf_env.go +++ b/pkg/workflow/awf_env.go @@ -43,7 +43,7 @@ func applyDefaultMaxAICreditsEnvToMap(env map[string]string, workflowData *Workf // GitHub Actions runtime expression to that variable, so the ${{ }} expression // lives on one clean, dedicated line rather than being embedded inside the JSON. // -// shellEscapeArgWithVarPreserved is then used to double-quote the JSON arg while +// shellEscapeArgWithVarsPreserved is then used to double-quote the JSON arg while // preserving the ${varName} reference for bash expansion and escaping bare $ signs // (e.g. "$schema" → "\$schema"). func injectMaxAICreditsExpression(awfConfigJSON string, expr string) string { diff --git a/pkg/workflow/awf_env_test.go b/pkg/workflow/awf_env_test.go index 29b69d81bba..78118c19adc 100644 --- a/pkg/workflow/awf_env_test.go +++ b/pkg/workflow/awf_env_test.go @@ -319,6 +319,3 @@ func TestMainAgentRunUsesStandardCreditsExpressionNotDetectionExpression(t *test assert.NotContains(t, stepContent, "vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS", "main-agent run must not use detection credits expression") } - -// TestGetAWFCommandPrefixNetworkIsolation tests that GetAWFCommandPrefix returns the correct -// command based on security mode: strict (default, no sudo) or legacy (sudo -E awf). diff --git a/pkg/workflow/awf_feature_flags_test.go b/pkg/workflow/awf_feature_flags_test.go index 938279b823f..e7697afaeb5 100644 --- a/pkg/workflow/awf_feature_flags_test.go +++ b/pkg/workflow/awf_feature_flags_test.go @@ -3,8 +3,9 @@ package workflow import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestAWFSupportsExcludeEnv(t *testing.T) { @@ -125,7 +126,6 @@ func TestAWFSupportsCliProxy(t *testing.T) { } // TestAWFSupportsAllowHostPorts tests the awfSupportsAllowHostPorts version gate function. - func TestAWFSupportsAllowHostPorts(t *testing.T) { tests := []struct { name string @@ -178,7 +178,6 @@ func TestAWFSupportsAllowHostPorts(t *testing.T) { } // TestAWFSupportsDockerHostPathPrefix tests the awfSupportsDockerHostPathPrefix version gate. - func TestAWFSupportsDockerHostPathPrefix(t *testing.T) { tests := []struct { name string @@ -262,7 +261,6 @@ func TestAWFSupportsTokenSteering(t *testing.T) { } // TestAWFSupportsChrootConfig tests the awfSupportsChrootConfig version gate. - func TestAWFSupportsChrootConfig(t *testing.T) { tests := []struct { name string @@ -310,7 +308,6 @@ func TestAWFSupportsChrootConfig(t *testing.T) { } // TestAWFSupportsAPIProxyProviders tests the awfSupportsAPIProxyProviders version gate. - func TestAWFSupportsAPIProxyProviders(t *testing.T) { tests := []struct { name string @@ -356,7 +353,3 @@ func TestAWFSupportsAPIProxyProviders(t *testing.T) { }) } } - -// TestBuildAWFCommand_IncludesChrootInjectScript verifies that BuildAWFCommand -// includes the chroot injection script in the generated run step when the AWF -// version supports it. diff --git a/pkg/workflow/js/safe_outputs_tools.json b/pkg/workflow/js/safe_outputs_tools.json index 03b10fe4553..2775fb4eb44 100644 --- a/pkg/workflow/js/safe_outputs_tools.json +++ b/pkg/workflow/js/safe_outputs_tools.json @@ -327,16 +327,16 @@ "description": "Node ID of the discussion comment to reply to, enabling threaded discussion comments. When provided, the new comment is posted as a reply to the specified top-level discussion comment. If the given node ID belongs to a nested reply, the handler automatically resolves it to the top-level parent. Only applicable for discussion comments \u2014 ignored for issue and pull request comments.", "x-synonyms": ["replyToId"] }, - "comment_id": { - "type": ["number", "string"], - "description": "Existing issue or pull request comment ID to update instead of creating a new comment.", - "x-synonyms": ["commentId"] - }, "target": { "type": "string", "enum": ["status"], "description": "When set to 'status', updates the activation status comment for this run (if available) instead of creating a new comment." }, + "comment_id": { + "type": ["number", "string"], + "description": "Existing issue or pull request comment ID to update. Only valid when the workflow config sets safe-outputs.add-comment.target to '*' and the ID is present in safe-outputs.add-comment.allows-comment-ids. Use this only for comment IDs precomputed by trusted workflow steps.", + "x-synonyms": ["commentId", "comment-id"] + }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." @@ -351,7 +351,7 @@ "x-safe-outputs-target-requirements": { "*": { "primary": "item_number", - "anyOf": ["item_number", "pr_number", "pr"] + "anyOf": ["item_number", "pr_number", "pr", "comment_id"] } } }, diff --git a/pkg/workflow/safe_output_validation_config_test.go b/pkg/workflow/safe_output_validation_config_test.go index 83e5c6e1844..52452baedc3 100644 --- a/pkg/workflow/safe_output_validation_config_test.go +++ b/pkg/workflow/safe_output_validation_config_test.go @@ -4,6 +4,7 @@ package workflow import ( "encoding/json" + "strings" "testing" ) @@ -305,6 +306,28 @@ func TestUpdatePullRequestValidationConfig(t *testing.T) { } } +func TestUpdateProjectAcceptsProjectURLOrTemporaryID(t *testing.T) { + jsonStr, err := GetValidationConfigJSONWithDataSchema([]string{"update_project"}, nil, false, nil) + if err != nil { + t.Fatalf("GetValidationConfigJSON() error = %v", err) + } + + var parsed map[string]TypeValidationConfig + if err := json.Unmarshal([]byte(jsonStr), &parsed); err != nil { + t.Fatalf("Failed to parse validation config JSON: %v", err) + } + + project := parsed["update_project"].Fields["project"] + for _, expected := range []string{ + "https://[^/]+/(orgs|users)/[^/]+/projects/\\d+", + "#?aw_[A-Za-z0-9_]{3,12}", + } { + if !strings.Contains(project.Pattern, expected) { + t.Fatalf("update_project.project pattern %q does not contain %q", project.Pattern, expected) + } + } +} + func TestUpdateIssueValidationConfig(t *testing.T) { config, ok := ValidationConfig["update_issue"] if !ok { @@ -413,6 +436,35 @@ func TestValidationConfigConsistency(t *testing.T) { } } +func TestValidationConfigCoversToolInputSchemas(t *testing.T) { + var tools []struct { + Name string `json:"name"` + InputSchema struct { + Properties map[string]json.RawMessage `json:"properties"` + } `json:"inputSchema"` + } + if err := json.Unmarshal([]byte(safeOutputsToolsJSONContent), &tools); err != nil { + t.Fatalf("failed to parse safe outputs tool schema: %v", err) + } + + metadataFields := map[string]bool{"secrecy": true, "integrity": true} + for _, tool := range tools { + config, ok := ValidationConfig[tool.Name] + if !ok { + t.Errorf("%s tool is missing from ValidationConfig", tool.Name) + continue + } + for fieldName := range tool.InputSchema.Properties { + if metadataFields[fieldName] { + continue + } + if _, ok := config.Fields[fieldName]; !ok { + t.Errorf("%s tool input %q is missing from ValidationConfig", tool.Name, fieldName) + } + } + } +} + func TestCreateDiscussionBodyMinLength(t *testing.T) { config, ok := ValidationConfig["create_discussion"] if !ok { diff --git a/pkg/workflow/safe_outputs_config_generation_test.go b/pkg/workflow/safe_outputs_config_generation_test.go index 5c113bf316c..40803912435 100644 --- a/pkg/workflow/safe_outputs_config_generation_test.go +++ b/pkg/workflow/safe_outputs_config_generation_test.go @@ -238,6 +238,26 @@ func TestGenerateSafeOutputsConfigAddsDataFlagsForBodyHandlers(t *testing.T) { assert.Equal(t, true, addComment["data_enabled"]) } +func TestGenerateSafeOutputsConfigForwardsAllowedCommentIDs(t *testing.T) { + cfg := &SafeOutputsConfig{ + AddComments: &AddCommentsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("1")}, + Target: "*", + AllowedCommentIDs: []string{"${{ needs.prepare.outputs.comment_ids }}"}, + }, + } + data := &WorkflowData{SafeOutputs: cfg} + result, err := generateSafeOutputsConfig(data) + require.NoError(t, err) + + var parsed map[string]any + require.NoError(t, json.Unmarshal([]byte(result), &parsed)) + addComment, ok := parsed["add_comment"].(map[string]any) + require.True(t, ok) + assert.Equal(t, "*", addComment["target"]) + assert.Equal(t, "${{ needs.prepare.outputs.comment_ids }}", addComment["allows_comment_ids"]) +} + func TestGenerateSafeOutputsConfigAddsRuntimeDataSchemaExpression(t *testing.T) { cfg := &SafeOutputsConfig{ DataEnabled: true, diff --git a/pkg/workflow/safe_outputs_handler_registry.go b/pkg/workflow/safe_outputs_handler_registry.go index 42f29f99fb6..546c5ba813a 100644 --- a/pkg/workflow/safe_outputs_handler_registry.go +++ b/pkg/workflow/safe_outputs_handler_registry.go @@ -86,6 +86,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddBoolPtr("discussions", c.Discussions). AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddTemplatableStringSlice("allowed_repos", c.AllowedRepos). + AddTemplatableStringSlice("allows_comment_ids", c.AllowedCommentIDs). AddIfNotEmpty("github-token", resolveHandlerGitHubToken(c.GitHubApp, "add-comment", c.GitHubToken)). AddTemplatableBool("footer", getEffectiveFooterForTemplatable(c.Footer, cfg.Footer)). AddBoolPtr("normalize_closing_keywords", c.NormalizeClosingKeywords). diff --git a/pkg/workflow/safe_outputs_validation_config.go b/pkg/workflow/safe_outputs_validation_config.go index 7537adf311b..7bd7ead5082 100644 --- a/pkg/workflow/safe_outputs_validation_config.go +++ b/pkg/workflow/safe_outputs_validation_config.go @@ -81,10 +81,15 @@ var ValidationConfig = map[string]TypeValidationConfig{ "add_comment": { DefaultMax: 1, Fields: map[string]FieldValidation{ - "body": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, - "item_number": {IssueOrPRNumber: true}, - "reply_to_id": {Type: "string", MaxLength: 256}, // Optional: node ID of discussion comment to reply to (threading) - "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" + "body": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, + "item_number": {IssueOrPRNumber: true}, + "pr_number": {IssueOrPRNumber: true}, + "pr": {IssueOrPRNumber: true}, + "temporary_id": {Type: "string", Pattern: "^#?aw_[A-Za-z0-9_]{3,12}$"}, + "reply_to_id": {Type: "string", MaxLength: 256}, // Optional: node ID of discussion comment to reply to (threading) + "target": {Type: "string", Enum: []string{"status"}}, + "comment_id": {OptionalPositiveInteger: true}, + "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" }, }, "comment_memory": { @@ -99,13 +104,14 @@ var ValidationConfig = map[string]TypeValidationConfig{ "create_pull_request": { DefaultMax: 1, Fields: map[string]FieldValidation{ - "title": {Required: true, Type: "string", Sanitize: true, MaxLength: 128}, - "body": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, - "branch": {Required: true, Type: "string", Sanitize: true, MaxLength: 256}, - "base": {Type: "string", Sanitize: true, MaxLength: 128}, - "labels": {Type: "array", ItemType: "string", ItemSanitize: true, ItemMaxLength: 128}, - "draft": {Type: "boolean"}, - "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" + "title": {Required: true, Type: "string", Sanitize: true, MaxLength: 128}, + "body": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, + "branch": {Required: true, Type: "string", Sanitize: true, MaxLength: 256}, + "base": {Type: "string", Sanitize: true, MaxLength: 128}, + "labels": {Type: "array", ItemType: "string", ItemSanitize: true, ItemMaxLength: 128}, + "draft": {Type: "boolean"}, + "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" + "temporary_id": {Type: "string", Pattern: "^#?aw_[A-Za-z0-9_]{3,12}$"}, }, }, "add_labels": { @@ -212,6 +218,8 @@ var ValidationConfig = map[string]TypeValidationConfig{ "update_branch": {Type: "boolean"}, "draft": {Type: "boolean"}, "pull_request_number": {IssueOrPRNumber: true}, + "pr_number": {IssueOrPRNumber: true}, + "pr": {IssueOrPRNumber: true}, "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" }, }, @@ -231,6 +239,7 @@ var ValidationConfig = map[string]TypeValidationConfig{ "message": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, "pull_request_number": {IssueOrPRNumber: true}, "branch": {Type: "string", Sanitize: true, MaxLength: 256}, // Optional: stripped before MCP call; validated for type/length when present. + "repo": {Type: "string", MaxLength: 256}, }, }, "create_pull_request_review_comment": { @@ -293,6 +302,7 @@ var ValidationConfig = map[string]TypeValidationConfig{ Fields: map[string]FieldValidation{ "body": {Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, "issue_number": {OptionalPositiveInteger: true}, + "duplicate_of": {IssueOrPRNumber: true}, "rationale": {Type: "string", Sanitize: true, MaxLength: 280, StripOnError: true}, "confidence": {Type: "string", Enum: []string{"LOW", "MEDIUM", "HIGH"}, StripOnError: true}, "suggest": {Type: "boolean"}, @@ -337,6 +347,33 @@ var ValidationConfig = map[string]TypeValidationConfig{ "path": {Required: true, Type: "string"}, }, }, + "upload_artifact": { + DefaultMax: 10, + Fields: map[string]FieldValidation{ + "path": {Type: "string"}, + "filters": {Type: "object"}, + "temporary_id": {Type: "string", Pattern: "^#?aw_[A-Za-z0-9_]{3,12}$"}, + }, + }, + "push_repo_memory": { + DefaultMax: 1, + Fields: map[string]FieldValidation{ + "memory_id": {Type: "string", Sanitize: true, MaxLength: 128}, + }, + }, + "create_check_run": { + DefaultMax: 1, + Fields: map[string]FieldValidation{ + "conclusion": {Required: true, Type: "string", Enum: []string{"success", "failure", "neutral", "cancelled", "skipped", "timed_out", "action_required"}}, + "title": {Required: true, Type: "string", Sanitize: true, MaxLength: 256}, + "summary": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, + "text": {Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, + "pull_request_number": {IssueOrPRNumber: true}, + "pr_number": {IssueOrPRNumber: true}, + "pr": {IssueOrPRNumber: true}, + "pull_number": {IssueOrPRNumber: true}, + }, + }, "noop": { DefaultMax: 1, Fields: map[string]FieldValidation{ @@ -366,23 +403,31 @@ var ValidationConfig = map[string]TypeValidationConfig{ "update_project": { DefaultMax: 10, Fields: map[string]FieldValidation{ - "project": {Required: true, Type: "string", Sanitize: true, MaxLength: 512, Pattern: "^https://[^/]+/(orgs|users)/[^/]+/projects/\\d+", PatternError: "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42)"}, - "content_type": {Type: "string", Enum: []string{"issue", "pull_request", "draft_issue"}}, - "content_number": {IssueNumberOrTemporaryID: true}, - "issue": {OptionalPositiveInteger: true}, // Legacy - "pull_request": {OptionalPositiveInteger: true}, // Legacy - "draft_title": {Type: "string", Sanitize: true, MaxLength: 256}, - "draft_body": {Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, - "fields": {Type: "object"}, + "project": {Required: true, Type: "string", Sanitize: true, MaxLength: 512, Pattern: "^(https://[^/]+/(orgs|users)/[^/]+/projects/\\d+|#?aw_[A-Za-z0-9_]{3,12})$", PatternError: "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42) or temporary project ID (e.g., #aw_project1)"}, + "operation": {Type: "string", Enum: []string{"create_fields", "create_view"}}, + "content_type": {Type: "string", Enum: []string{"issue", "pull_request", "draft_issue"}}, + "content_number": {IssueNumberOrTemporaryID: true}, + "target_repo": {Type: "string", Pattern: "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$"}, + "issue": {OptionalPositiveInteger: true}, // Legacy + "pull_request": {OptionalPositiveInteger: true}, // Legacy + "draft_title": {Type: "string", Sanitize: true, MaxLength: 256}, + "draft_body": {Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, + "draft_issue_id": {Type: "string", Pattern: "^#?aw_[A-Za-z0-9_]{3,12}$"}, + "temporary_id": {Type: "string", Pattern: "^#?aw_[A-Za-z0-9_]{3,12}$"}, + "fields": {Type: "object"}, + "field_definitions": {Type: "array"}, + "view": {Type: "object"}, + "create_if_missing": {Type: "boolean"}, }, }, "create_project": { DefaultMax: 1, Fields: map[string]FieldValidation{ - "title": {Type: "string", Sanitize: true, MaxLength: 256}, - "owner": {Type: "string", Sanitize: true, MaxLength: 128}, - "owner_type": {Type: "string", Enum: []string{"org", "user"}}, - "item_url": {Type: "string", Sanitize: true, MaxLength: 512}, + "title": {Type: "string", Sanitize: true, MaxLength: 256}, + "owner": {Type: "string", Sanitize: true, MaxLength: 128}, + "owner_type": {Type: "string", Enum: []string{"org", "user"}}, + "item_url": {Type: "string", Sanitize: true, MaxLength: 512}, + "temporary_id": {Type: "string", Pattern: "^#?aw_[A-Za-z0-9_]{3,12}$"}, }, }, "create_project_status_update": {