Skip to content

safe-outputs: add merge-pull-request schema parity for samples and cross-repo targeting#39767

Merged
dsyme merged 4 commits into
mainfrom
copilot/fix-sample-property-schema
Jun 18, 2026
Merged

safe-outputs: add merge-pull-request schema parity for samples and cross-repo targeting#39767
dsyme merged 4 commits into
mainfrom
copilot/fix-sample-property-schema

Conversation

Copilot AI commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

safe-outputs.merge-pull-request was the outlier: its schema rejected samples (breaking --use-samples replay) and rejected target/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-request now accepts sample + target fields

    • Added samples to pkg/parser/schemas/main_workflow_schema.json
    • Added target, target-repo, and allowed-repos to the same schema node
    • Keeps additionalProperties: false while explicitly allowing the intended config surface
  • Runtime config parsing: merge config now carries target metadata

    • Updated MergePullRequestConfig to include SafeOutputTargetConfig
    • parseMergePullRequestConfig now parses target / target-repo / allowed-repos
  • Handler config emission: cross-repo settings now flow to MCP config

    • merge_pull_request handler config generation now emits:
      • target
      • target-repo
      • allowed_repos
  • Regression coverage

    • Extended parser schema regression test to include merge-pull-request with samples and target/cross-repo fields
    • Added workflow cross-repo config tests for merge parsing and handler-config propagation
safe-outputs:
  merge-pull-request:
    target: "*"
    target-repo: "github/github"
    allowed-repos:
      - "github/docs"
    samples:
      - merge_method: "squash"

Copilot AI and others added 2 commits June 17, 2026 12:06
Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com>
Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix config schema to allow samples in merge-pull-request safe-outputs: add merge-pull-request schema parity for samples and cross-repo targeting Jun 17, 2026
Copilot finished work on behalf of dsyme June 17, 2026 12:21
Copilot AI requested a review from dsyme June 17, 2026 12:21
@dsyme dsyme marked this pull request as ready for review June 17, 2026 14:16
Copilot AI review requested due to automatic review settings June 17, 2026 14:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 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-request to accept samples and cross-repo targeting fields while keeping additionalProperties: false.
  • Extended MergePullRequestConfig + parsing to include SafeOutputTargetConfig (target, target-repo, allowed-repos).
  • Updated handler-config generation and added/extended tests for parsing + handler-config propagation; regenerated affected compiled .lock.yml artifacts.
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.

Comment on lines +32 to +33
targetConfig, _ := ParseTargetConfig(configMap)
cfg.SafeOutputTargetConfig = targetConfig
Comment on lines 556 to +560
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).
@github-actions

Copy link
Copy Markdown
Contributor

@copilot review all comments and address unresolved review feedback.

Generated by 👨‍🍳 PR Sous Chef ·

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel failed during test quality analysis.

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (148 new lines in pkg/, above the 100-line threshold) but does not have a linked Architecture Decision Record (ADR).

🔒 This PR cannot merge until an ADR is linked in the PR body.

⚠️ The gate generated a draft ADR but could not commit it to the branch — the push step hit a detached-HEAD condition in the runner (no PR head ref available to push_to_pull_request_branch). The full draft is provided below; please add it to your branch as docs/adr/39767-merge-pull-request-samples-and-cross-repo-parity.md.

📄 Draft ADR-39767 (copy into docs/adr/)
# ADR-39767: Schema Parity for `merge-pull-request` Samples and Cross-Repo Targeting

**Date**: 2026-06-17
**Status**: Draft

## Context

The `safe-outputs` schema defines a consistent surface across PR-state safe outputs (e.g. `create-pull-request`, `push-to-pull-request-branch`, `dispatch-workflow`): a hidden `samples` field that drives the `gh aw compile --use-samples` deterministic replay path, plus `target` / `target-repo` / `allowed-repos` fields that enable cross-repository operations. `merge-pull-request` was the outlier — its schema node kept `additionalProperties: false` but never declared `samples` or the cross-repo target fields, so a workflow author configuring them got a compile-time schema rejection even though the runtime tooling path already supports both. This left `--use-samples` replay broken for merge and made cross-repo merges unconfigurable through the documented config surface.

## Decision

We will extend the `merge-pull-request` safe-output schema and its compiler config path to accept the same `samples`, `target`, `target-repo`, and `allowed-repos` fields already supported by sibling PR-state safe outputs. The JSON schema node gains these four properties while retaining `additionalProperties: false`; `MergePullRequestConfig` embeds `SafeOutputTargetConfig` and `parseMergePullRequestConfig` parses the target fields via the shared `ParseTargetConfig` helper; and the handler-config builder emits `target`, `target-repo`, and `allowed_repos`. The driver is parity — eliminating a compile-time schema mismatch against behavior the tooling already implements — not the introduction of a new capability.

## Alternatives Considered

### Alternative 1: Leave the schema strict (status quo)
Keep `merge-pull-request` without `samples` or cross-repo fields. Rejected because it leaves `--use-samples` replay silently broken for this one handler and forces cross-repo merge users outside the validated config surface.

### Alternative 2: Relax with `additionalProperties: true`
Drop strict validation on the node so any extra keys pass. Rejected because it weakens validation for every field, hides typos, and diverges from how sibling safe outputs explicitly enumerate allowed fields.

## Consequences

### Positive
- `gh aw compile --use-samples` replay now works for `merge-pull-request`, matching every other PR-state safe output.
- Cross-repository merge targeting becomes configurable through the validated schema instead of an undocumented path.
- The safe-outputs schema is now internally consistent.

### Negative
- The `merge-pull-request` config surface grows, adding four fields to keep in sync with the shared target-config helper and handler-config emitter.
- Enabling cross-repo merges widens the security/blast-radius surface of a destructive operation, which must remain governed by existing token and `allowed-repos` gating.

### Neutral
- Regression coverage was added at both the parser-schema and workflow config/handler-config levels.
- `samples` remains an internal hidden feature; visibility is unchanged.
📋 What to do next
  1. Add the draft ADR above to docs/adr/39767-merge-pull-request-samples-and-cross-repo-parity.md on your branch.
  2. Review and complete it — refine the rationale and confirm the alternatives match what you actually considered.
  3. Reference the ADR in this PR body, e.g.:

    ADR: ADR-39767: Schema Parity for merge-pull-request

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

ADRs create a searchable, permanent record of why the codebase looks the way it does. Even a parity/consistency change like this one benefits from a recorded decision — it documents that cross-repo merge support was a deliberate alignment, and captures the security trade-off for future readers.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections: Context, Decision, Alternatives Considered, Consequences. All ADRs live in docs/adr/ numbered by PR number.

🔒 This PR cannot merge until an ADR is linked in the PR body.

References: §27695589000

🏗️ ADR gate enforced by Design Decision Gate 🏗️ ·

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 target field emission: TestMergePullRequestCrossRepoInHandlerConfig never sets Target: "*", so the AddIfNotEmpty("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-repo in both the parse test and the handler-config test. The interesting invariant is repos beyond the target being permitted.
  • Schema target has no enum constraint: 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-request is touched; no unrelated handlers are affected
  • SafeOutputTargetConfig embed follows the exact same pattern already used by close-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 (targetrequired_labelstarget-repoallowed_repos) matches close-pull-request exactly
  • ✅ Lock file churn is purely mechanical (optionalPositiveIntegerissueOrPRNumber rename); all 13 files change identically — consistent recompile

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

Comment thread pkg/workflow/safe_outputs_cross_repo_config_test.go
Comment thread pkg/workflow/safe_outputs_cross_repo_config_test.go Outdated
Comment thread pkg/workflow/safe_outputs_cross_repo_config_test.go Outdated
Comment thread pkg/parser/schemas/main_workflow_schema.json

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔎 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")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@github-actions

Copy link
Copy Markdown
Contributor

@copilot review all comments and address unresolved review feedback.

Generated by 👨‍🍳 PR Sous Chef ·

@github-actions

Copy link
Copy Markdown
Contributor

Please add the draft ADR link to the PR body so the design-decision gate can re-run.

Generated by 👨‍🍳 PR Sous Chef ·

@github-actions

Copy link
Copy Markdown
Contributor

@copilot review all comments and address unresolved review feedback. Please also summarize the remaining blockers for a faster maintainer review.

Generated by 👨‍🍳 PR Sous Chef ·

@github-actions

Copy link
Copy Markdown
Contributor

``
@copilot review all comments and address unresolved review feedback.

Generated by 👨‍🍳 PR Sous Chef ·

@github-actions

Copy link
Copy Markdown
Contributor

``
@copilot summarize the remaining blockers and refresh the branch if needed.

Generated by 👨‍🍳 PR Sous Chef ·

@dsyme dsyme merged commit 3146b06 into main Jun 18, 2026
29 checks passed
@dsyme dsyme deleted the copilot/fix-sample-property-schema branch June 18, 2026 00:55
@github-actions github-actions Bot mentioned this pull request Jun 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants