safe-outputs: add merge-pull-request schema parity for samples and cross-repo targeting#39767
Conversation
Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com>
Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com>
merge-pull-request schema parity for samples and cross-repo targeting
There was a problem hiding this comment.
⚠️ Not ready to approve
The new merge-pull-request.target field is parsed/emitted but not validated in the central safe-outputs target validation, and the repo-parameter tooling metadata doesn’t yet account for merge cross-repo configuration.
Pull request overview
This PR brings safe-outputs.merge-pull-request into parity with other PR-state safe outputs by allowing samples and cross-repo targeting fields (target, target-repo, allowed-repos) in the workflow schema, and by ensuring the runtime merge config and handler-config emission carry the new target metadata through to the safe-outputs MCP configuration.
Changes:
- Expanded the main workflow JSON schema for
safe-outputs.merge-pull-requestto acceptsamplesand cross-repo targeting fields while keepingadditionalProperties: false. - Extended
MergePullRequestConfig+ parsing to includeSafeOutputTargetConfig(target,target-repo,allowed-repos). - Updated handler-config generation and added/extended tests for parsing + handler-config propagation; regenerated affected compiled
.lock.ymlartifacts.
File summaries
| File | Description |
|---|---|
| pkg/workflow/safe_outputs_handler_registry.go | Emits merge_pull_request handler config fields for target, target-repo, and allowed_repos. |
| pkg/workflow/safe_outputs_cross_repo_config_test.go | Adds coverage for merge cross-repo parsing and handler-config propagation. |
| pkg/workflow/merge_pull_request.go | Embeds/parses SafeOutputTargetConfig for merge-pull-request. |
| pkg/parser/schemas/main_workflow_schema.json | Extends merge-pull-request schema to allow target/cross-repo fields and samples. |
| pkg/parser/schema_safe_outputs_target_test.go | Extends schema regression coverage for merge-pull-request target + samples acceptance. |
| .github/workflows/test-quality-sentinel.lock.yml | Regenerated lock output reflecting updated validation schema for pull_request_number. |
| .github/workflows/smoke-copilot.lock.yml | Regenerated lock output reflecting updated validation schema for pull_request_number. |
| .github/workflows/smoke-copilot-arm.lock.yml | Regenerated lock output reflecting updated validation schema for pull_request_number. |
| .github/workflows/smoke-copilot-aoai-entra.lock.yml | Regenerated lock output reflecting updated validation schema for pull_request_number. |
| .github/workflows/smoke-copilot-aoai-apikey.lock.yml | Regenerated lock output reflecting updated validation schema for pull_request_number. |
| .github/workflows/smoke-claude.lock.yml | Regenerated lock output reflecting updated validation schema for pull_request_number. |
| .github/workflows/security-review.lock.yml | Regenerated lock output reflecting updated validation schema for pull_request_number. |
| .github/workflows/refiner.lock.yml | Regenerated lock output reflecting updated validation schema for pull_request_number. |
| .github/workflows/pr-triage-agent.lock.yml | Regenerated lock output reflecting updated validation schema for pull_request_number. |
| .github/workflows/pr-nitpick-reviewer.lock.yml | Regenerated lock output reflecting updated validation schema for pull_request_number. |
| .github/workflows/pr-code-quality-reviewer.lock.yml | Regenerated lock output reflecting updated validation schema for pull_request_number. |
| .github/workflows/mattpocock-skills-reviewer.lock.yml | Regenerated lock output reflecting updated validation schema for pull_request_number. |
| .github/workflows/grumpy-reviewer.lock.yml | Regenerated lock output reflecting updated validation schema for pull_request_number. |
Copilot's findings
- Files reviewed: 18/18 changed files
- Comments generated: 2
Note
Your feedback helps us improve the quality of this feature.
Please use 👍 or 👎 to tell us whether this assessment is correct.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| targetConfig, _ := ParseTargetConfig(configMap) | ||
| cfg.SafeOutputTargetConfig = targetConfig |
| AddTemplatableInt("max", c.Max). | ||
| AddIfNotEmpty("target", c.Target). | ||
| AddStringSlice("required_labels", c.RequiredLabels).AddIfNotEmpty("required_title_prefix", c.RequiredTitlePrefix).AddStringSlice("allowed_branches", c.AllowedBranches). | ||
| AddIfNotEmpty("target-repo", c.TargetRepoSlug). | ||
| AddStringSlice("allowed_repos", c.AllowedRepos). |
|
@copilot review all comments and address unresolved review feedback.
|
|
✅ PR Code Quality Reviewer completed the code quality review. |
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. |
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
|
❌ Test Quality Sentinel failed during test quality analysis. |
🏗️ Design Decision Gate — ADR RequiredThis PR makes significant changes to core business logic (148 new lines in
📄 Draft ADR-39767 (copy into
|
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /tdd and /grill-with-docs — approving with minor suggestions on test coverage and schema strictness.
📋 Key Themes & Highlights
Key Themes
- Test coverage gap for
targetfield emission:TestMergePullRequestCrossRepoInHandlerConfignever setsTarget: "*", so theAddIfNotEmpty("target", ...)branch in the handler registry is untested. This is the cross-repo mode selector — worth a dedicated assertion. - Degenerate test case:
allowed-repos==target-repoin both the parse test and the handler-config test. The interesting invariant is repos beyond the target being permitted. - Schema
targethas noenumconstraint: consistent with all other handlers in the file, but a typo like"Triggering"passes silently. This is a codebase-wide policy question more than a PR-specific issue.
Positive Highlights
- ✅ Tight, surgical change — only
merge-pull-requestis touched; no unrelated handlers are affected - ✅
SafeOutputTargetConfigembed follows the exact same pattern already used byclose-pull-request,mark-pull-request-as-ready-for-review, and others — no architectural drift - ✅ 106 lines of new tests (91 workflow + 15 schema) for 11 lines of production code is a healthy coverage ratio
- ✅ Handler registry builder key ordering (
target→required_labels→target-repo→allowed_repos) matchesclose-pull-requestexactly - ✅ Lock file churn is purely mechanical (
optionalPositiveInteger→issueOrPRNumberrename); all 13 files change identically — consistent recompile
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer
There was a problem hiding this comment.
🔎 Code quality review by PR Code Quality Reviewer
|
|
||
| allowedRepos, ok := mergePR["allowed_repos"] | ||
| require.True(t, ok, "allowed_repos should be present") | ||
| assert.Contains(t, allowedRepos, "githubnext/gh-aw-side-repo", "allowed_repos should contain the repo") |
There was a problem hiding this comment.
Test coverage gap: AddIfNotEmpty("target", c.Target) is a new addition in this diff, but no test ever sets Target or asserts that "target" appears in the emitted handler config — a rename or omission of that key would pass undetected.
💡 Suggested fix
Add a sub-case that sets Target: "*" and asserts the key appears in the output:
SafeOutputTargetConfig: SafeOutputTargetConfig{
Target: "*",
TargetRepoSlug: "githubnext/gh-aw-side-repo",
AllowedRepos: []string{"githubnext/gh-aw-side-repo"},
},
...
assert.Equal(t, "*", mergePR["target"], "target should be in handler config")Without this, the AddIfNotEmpty("target", c.Target) line added in safe_outputs_handler_registry.go has zero handler-config test coverage.
|
@copilot review all comments and address unresolved review feedback.
|
|
Please add the draft ADR link to the PR body so the design-decision gate can re-run.
|
|
@copilot review all comments and address unresolved review feedback. Please also summarize the remaining blockers for a faster maintainer review.
|
|
``
|
|
``
|
…tadata, strengthen tests
safe-outputs.merge-pull-requestwas the outlier: its schema rejectedsamples(breaking--use-samplesreplay) and rejectedtarget/cross-repo fields even though the tooling path supports cross-repository operations. This aligns merge behavior with other PR-state safe outputs and removes the compile-time schema mismatch.Schema:
merge-pull-requestnow accepts sample + target fieldssamplestopkg/parser/schemas/main_workflow_schema.jsontarget,target-repo, andallowed-reposto the same schema nodeadditionalProperties: falsewhile explicitly allowing the intended config surfaceRuntime config parsing: merge config now carries target metadata
MergePullRequestConfigto includeSafeOutputTargetConfigparseMergePullRequestConfignow parsestarget/target-repo/allowed-reposHandler config emission: cross-repo settings now flow to MCP config
merge_pull_requesthandler config generation now emits:targettarget-repoallowed_reposRegression coverage
merge-pull-requestwithsamplesand target/cross-repo fields