Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/added-contributor-lane-approve-held-runs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Contributor lane approves `action_required` workflow runs once per head for a PR author the contributor registry admitted at a `require_tiers` tier when the lane stops at `required-check-missing:<ctx>` (fork heads included), posting one `hive:contributor-held-runs-approved` audit comment; toggle `auto_merge.contributor_lane.approve_held_runs_for_registry_trusted` (default on). Claim-marker, auto-promoted and association admissions, sentinel/exclude-labelled PRs and `.github/workflows/**` diffs are never approved for (#14519).
21 changes: 11 additions & 10 deletions src/cmd/hive/merge_eligibility.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,17 @@ func contributorLanePolicyFunc(cfg *config.Config) func() automerge.ContributorL
p := cfg.AutoMerge.ContributorLane
tier := cfg.AutoMerge.ContributorTier
return automerge.ContributorLanePolicy{
Enabled: p.IsEnabled(),
HiveID: cfg.HiveID,
Soak: time.Duration(p.EffectiveSoakMinutes()) * time.Minute,
RequireReviewerVerdict: p.EffectiveRequireReviewerVerdict(),
Repos: p.RepoSet(),
ExcludeLabels: p.ExcludeLabelSet(),
RequireTiers: p.RequireTierSet(),
RegistryTiersSufficient: p.EffectiveRegistryTiersSufficient(),
AutoPromoteAfterMergedPR: tier.EffectiveAutoPromoteAfterMerged(),
AutoPromotionScope: p.EffectiveAutoPromotionScope(),
Enabled: p.IsEnabled(),
HiveID: cfg.HiveID,
Soak: time.Duration(p.EffectiveSoakMinutes()) * time.Minute,
RequireReviewerVerdict: p.EffectiveRequireReviewerVerdict(),
Repos: p.RepoSet(),
ExcludeLabels: p.ExcludeLabelSet(),
RequireTiers: p.RequireTierSet(),
RegistryTiersSufficient: p.EffectiveRegistryTiersSufficient(),
AutoPromoteAfterMergedPR: tier.EffectiveAutoPromoteAfterMerged(),
AutoPromotionScope: p.EffectiveAutoPromotionScope(),
ApproveHeldRunsForRegistryTrusted: p.EffectiveApproveHeldRunsForRegistryTrusted(),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/docs/github-app-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Fine-grained PATs are supported — the token is sent as a plain bearer, same as
| Actions | Read | Read | Read |
| Commit statuses | Read | Read | Read and write — needed when `governor.labels.automerge` is the default `lgtm` on a Prow repository, so the queue action can set the `prow/lgtm` status beside the label. With only Read, every queue action still applies the label but posts a PR comment that the status could not be set. |

Hive does not request **Actions: Read and write** for workflow-run approvals. It only needs **Actions: Read** to inspect runs. When GitHub leaves same-repository `pull_request` runs in `action_required` on trusted Dependabot/Renovate/App PRs or registry-trusted `author/clanker` PRs, Hive posts `/ok-to-test` once for that head and reports `workflow-approval-required:<ctx>` / `stage/085-needs-decision` until the repository's own self-contained `hive-ok-to-test` workflow approves the held runs. The approval is performed by that repository's `GITHUB_TOKEN` through `.github/workflows/hive-ok-to-test.yml`; no Prow and no Hive App Actions-write grant are required. Hive never approves fork heads or PRs carrying `sentinel-alert`.
Hive does not require **Actions: Read and write** for bot workflow-run approvals; **Actions: Read** is enough to inspect runs. The one direct approval Hive performs — a contributor-lane PR whose author the contributor registry admitted at a `require_tiers` tier (`auto_merge.contributor_lane.approve_held_runs_for_registry_trusted`, default on) — does need Read and write; without it that call fails, is logged once per head, and the PR waits for a person. When GitHub leaves same-repository `pull_request` runs in `action_required` on trusted Dependabot/Renovate/App PRs or registry-trusted `author/clanker` PRs, Hive posts `/ok-to-test` once for that head and reports `workflow-approval-required:<ctx>` / `stage/085-needs-decision` until the repository's own self-contained `hive-ok-to-test` workflow approves the held runs. The approval is performed by that repository's `GITHUB_TOKEN` through `.github/workflows/hive-ok-to-test.yml`; no Prow and no Hive App Actions-write grant are required. Hive never approves fork heads or PRs carrying `sentinel-alert`.

Install the workflow in each governed repository: run `hivectl ok-to-test-template > .github/workflows/hive-ok-to-test.yml` against your hive (it reads the App bot login from `/api/config`; pass `--app-login <your-app>[bot]` to override), or copy `src/docs/templates/hive-ok-to-test.yml` and replace `HIVE_APP_LOGIN[bot]` with the App bot login shown by Hive for that installation.

Expand Down Expand Up @@ -90,7 +90,7 @@ Repository permissions used by the dashboard setup UI are:
| Issues | Read/write | Enumerate, comment on, create, and close issues. |
| Pull requests | Read/write | Create, update, approve/merge, and inspect PRs. |
| Checks | Read-only | Monitor CI status. |
| Actions | Read-only | Inspect workflow runs. If GitHub holds trusted Dependabot/App/clanker runs in `action_required`, Hive posts `/ok-to-test`; the repository-local `hive-ok-to-test` workflow uses its own `GITHUB_TOKEN` with Actions write to approve those runs. |
| Actions | Read-only (Read and write to approve registry-trusted contributors' held runs) | Inspect workflow runs. If GitHub holds trusted Dependabot/App/clanker runs in `action_required`, Hive posts `/ok-to-test`; the repository-local `hive-ok-to-test` workflow uses its own `GITHUB_TOKEN` with Actions write to approve those runs. With Read and write, the contributor lane also approves held runs directly for a PR author the contributor registry admitted at a `require_tiers` tier (`auto_merge.contributor_lane.approve_held_runs_for_registry_trusted`). |
| Administration | Read-only | Read branch-protection rules before the merge-request watcher lands a PR; Hive refuses closed if it cannot verify the base branch is protected. |
| Commit statuses | Read/write | Needed when `governor.labels.automerge` is the default `lgtm` on a repository running Prow's commit-bound `lgtm` (prow-github-actions v3): the queue action sets a `prow/lgtm` success status on the approved head so Prow does not strip the label. Without the write grant the label and approval still land, but every queue action takes the failure branch and posts a PR comment saying the status could not be set. Choose a distinct label such as `hive/auto-merge` if you would rather keep this at Read. |

Expand Down
19 changes: 19 additions & 0 deletions src/docs/hive-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,24 @@ the issue had been claimed outside the relay; the registry path admits both.
Every other gate below still applies to a registry-admitted PR. Set
`registry_tiers_sufficient: false` to require the marker for everyone.

A registry-admitted author also gets their CI started. GitHub's fork-approval
policy holds a first-time contributor's `pull_request` runs in
`action_required`, and the hive otherwise never approves a human-authored
lane's held runs (the `hive-ok-to-test` workflow approves same-repo heads
only). hivecommons/docs#481 by a `trusted` registry contributor sat 17 hours
at `required-check-missing:Audit production dependencies` until an operator
clicked "Approve and run". When the registry path admitted the author and the
lane stops at `required-check-missing:<ctx>`, it now approves the head's held
runs once (`POST /repos/{owner}/{repo}/actions/runs/{id}/approve`, fork head
included), posts one `<!-- hive:contributor-held-runs-approved … -->` audit
comment naming the author and tier, and logs `contributor-lane approved held
runs` with the run ids (`approve_held_runs_for_registry_trusted`, default
`true`). Claim-marker, auto-promotion and association admissions are never
approved for, nor is a PR carrying the sentinel or an `exclude_labels` label,
nor one touching `.github/workflows/**`. The App needs **Actions: Read and
write** for the call; without it the approve fails, is logged once per head,
and the PR waits for a person as before.

A PR merges through this tier only when all of the following hold:

- The PR author is admitted by one of three paths:
Expand Down Expand Up @@ -514,6 +532,7 @@ auto_merge:
- merger
registry_tiers_sufficient: true # a require_tiers tier on the author's registry profile admits without a claim marker
auto_promotion_scope: hive # count clean merged PRs across every hive repo (repo = this repository only)
approve_held_runs_for_registry_trusted: true # approve action_required runs once per head for registry-admitted authors
contributor_tier:
auto_promote_after_merged: 5 # clean merged PRs before the lane trusts an author on its own; negative disables
```
Expand Down
8 changes: 7 additions & 1 deletion src/docs/operator-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,12 @@ runs when a required check is missing or pending:
`automerge hold`; later ticks only count the PR in the skip stats instead of
emitting `automerge skip` every tick.
- fork heads, sentinel-alert PRs, and human-authored lanes are never touched; a
first-time contributor's held run is still a person's decision.
first-time contributor's held run is still a person's decision — with one
exception: a contributor-lane PR whose author the **contributor registry**
admitted at a `require_tiers` tier has its held runs approved once per head
by the App (`auto_merge.contributor_lane.approve_held_runs_for_registry_trusted`,
default `true`; needs Actions: Read and write). The operator already trusts
that author to merge, so holding their CI is pure drag (hivecommons/docs#481).

Per-repo `project.repo_policies[].auto_merge: false` is stronger than the
hive-wide L6 setting: the merge relay refuses the repo, the self-authored sweep
Expand Down Expand Up @@ -435,6 +440,7 @@ advanced into the merge path.
| `auto_merge.contributor_lane.registry_tiers_sufficient` | `true` | Admit a PR whose author's contributor-registry profile carries a `require_tiers` tier without a linked issue or `hive:claim` marker (logged as `contributor-lane:registry-trusted`). All other gates still apply. `false` requires the claim marker for every contributor. |
| `auto_merge.contributor_lane.auto_promotion_scope` | `hive` | Where the clean-merged-PR ratchet (`auto_merge.contributor_tier.auto_promote_after_merged`) counts an author's merges: `hive` counts across every repository the hive manages (one `search/issues` query finds the repos holding the author's merges; the PR's own repo is always walked first), `repo` counts only the PR's repository. Revert and sentinel-alert evidence blocks promotion in either scope. |
| `auto_merge.contributor_tier.auto_promote_after_merged` | `5` | Clean merged PRs (no revert, no sentinel label, not undone by a later merged revert) after which the contributor lane treats an author as `trusted` without an operator grant, unless the registry marks them `revoked`. Negative disables the ratchet. |
| `auto_merge.contributor_lane.approve_held_runs_for_registry_trusted` | `true` | When the registry path admitted the PR author and the lane stops at `required-check-missing:<ctx>`, approve the head's `action_required` workflow runs once (`POST /repos/{owner}/{repo}/actions/runs/{id}/approve`, fork heads included), post one `hive:contributor-held-runs-approved` audit comment and log `contributor-lane approved held runs`. Never acts for claim-marker, auto-promoted or association admissions, sentinel/exclude-labelled PRs, or diffs touching `.github/workflows/**`. Needs the App's **Actions: Read and write** permission. See [hive-merge.md](hive-merge.md#contributor-lane-auto-merge). |
| `auto_merge.contributor_lane.repos` | empty = all watched repos | Optional repo allow-list for the contributor-lane tier (bare name or `owner/repo`). |
| `auto_merge.contributor_lane.exclude_labels` | `hold`, `do-not-merge`, `needs-human`, `blocked`, `needs-rebase` | Labels that keep a PR out of contributor-lane auto-merge (`do-not-merge/*` always excluded). |

Expand Down
15 changes: 15 additions & 0 deletions src/pkg/config/automerge_trusted_bot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,21 @@ func TestContributorLaneAutoMergeConfigRegistryTiersSufficient(t *testing.T) {
}
}

// approve_held_runs_for_registry_trusted defaults on so a registry-admitted
// contributor's held workflow runs are approved; an explicit false keeps them
// a person's decision.
func TestContributorLaneAutoMergeConfigApproveHeldRunsForRegistryTrusted(t *testing.T) {
var cfg ContributorLaneAutoMergeConfig
if !cfg.EffectiveApproveHeldRunsForRegistryTrusted() {
t.Fatal("approve_held_runs_for_registry_trusted must default to true")
}
off := false
cfg.ApproveHeldRunsForRegistryTrusted = &off
if cfg.EffectiveApproveHeldRunsForRegistryTrusted() {
t.Fatal("explicit approve_held_runs_for_registry_trusted: false must turn held-run approval off")
}
}

func TestContributorTierAutoMergeConfigAutoPromoteDefault(t *testing.T) {
var cfg ContributorTierAutoMergeConfig
if got := cfg.EffectiveAutoPromoteAfterMerged(); got != DefaultContributorTierAutoPromoteAfterMerged {
Expand Down
16 changes: 16 additions & 0 deletions src/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7735,6 +7735,16 @@ type ContributorLaneAutoMergeConfig struct {
// own repository (the pre-#14517 behaviour). Revert and sentinel-alert
// evidence is applied per PR in either scope.
AutoPromotionScope string `yaml:"auto_promotion_scope,omitempty" json:"auto_promotion_scope,omitempty"`
// ApproveHeldRunsForRegistryTrusted lets the contributor lane approve
// `pull_request` workflow runs GitHub holds for approval
// (`action_required`) on a PR whose author the registry path admitted at
// a RequireTiers tier, once per head, when the only thing between the PR
// and merge is a required check those held runs would produce. Authors
// admitted by claim marker, auto-promotion or association are never
// approved for. nil/unset means ON. Needs the App's Actions: Read and
// write permission; without it the approve call fails and the lane keeps
// waiting for a person.
ApproveHeldRunsForRegistryTrusted *bool `yaml:"approve_held_runs_for_registry_trusted,omitempty" json:"approve_held_runs_for_registry_trusted,omitempty"`
}

// Contributor-lane auto-promotion scopes
Expand Down Expand Up @@ -7823,6 +7833,12 @@ func (c ContributorLaneAutoMergeConfig) EffectiveRegistryTiersSufficient() bool
return c.RegistryTiersSufficient == nil || *c.RegistryTiersSufficient
}

// EffectiveApproveHeldRunsForRegistryTrusted reports whether the contributor
// lane may approve held workflow runs for registry-admitted authors (default on).
func (c ContributorLaneAutoMergeConfig) EffectiveApproveHeldRunsForRegistryTrusted() bool {
return c.ApproveHeldRunsForRegistryTrusted == nil || *c.ApproveHeldRunsForRegistryTrusted
}

func (c ContributorLaneAutoMergeConfig) RepoSet() map[string]bool {
return repoListSet(c.Repos)
}
Expand Down
42 changes: 24 additions & 18 deletions src/pkg/dashboard/api_config_automerge.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,15 @@ func (s *Server) handleAutoMergePut(w http.ResponseWriter, r *http.Request) {
ExcludeLabels []string `json:"exclude_labels"`
} `json:"trusted_authors"`
ContributorLane *struct {
Enabled *bool `json:"enabled"`
SoakMinutes *int `json:"soak_minutes"`
RequireReviewerVerdict *bool `json:"require_reviewer_verdict"`
Repos []string `json:"repos"`
ExcludeLabels []string `json:"exclude_labels"`
RequireTiers []string `json:"require_tiers"`
RegistryTiersSufficient *bool `json:"registry_tiers_sufficient"`
AutoPromotionScope *string `json:"auto_promotion_scope"`
Enabled *bool `json:"enabled"`
SoakMinutes *int `json:"soak_minutes"`
RequireReviewerVerdict *bool `json:"require_reviewer_verdict"`
Repos []string `json:"repos"`
ExcludeLabels []string `json:"exclude_labels"`
RequireTiers []string `json:"require_tiers"`
RegistryTiersSufficient *bool `json:"registry_tiers_sufficient"`
AutoPromotionScope *string `json:"auto_promotion_scope"`
ApproveHeldRunsForRegistryTrusted *bool `json:"approve_held_runs_for_registry_trusted"`
} `json:"contributor_lane"`
}
if err := decodeBody(r, &body); err != nil {
Expand Down Expand Up @@ -322,6 +323,10 @@ func (s *Server) handleAutoMergePut(w http.ResponseWriter, r *http.Request) {
v := *body.ContributorLane.RegistryTiersSufficient
cfg.AutoMerge.ContributorLane.RegistryTiersSufficient = &v
}
if body.ContributorLane.ApproveHeldRunsForRegistryTrusted != nil {
v := *body.ContributorLane.ApproveHeldRunsForRegistryTrusted
cfg.AutoMerge.ContributorLane.ApproveHeldRunsForRegistryTrusted = &v
}
if body.ContributorLane.Repos != nil {
cfg.AutoMerge.ContributorLane.Repos = contributorLaneRepos
}
Expand Down Expand Up @@ -410,16 +415,17 @@ func autoMergeSectionResponse(cfg *config.Config) map[string]interface{} {
"roles": []string{config.RoleMerger, config.RoleOwner},
},
"contributor_lane": map[string]interface{}{
"enabled": cl.IsEnabled(),
"soak_minutes": cl.EffectiveSoakMinutes(),
"require_reviewer_verdict": cl.EffectiveRequireReviewerVerdict(),
"repos": contributorLaneRepos,
"exclude_labels": contributorLaneLabels,
"require_tiers": cl.EffectiveRequireTiers(),
"registry_tiers_sufficient": cl.EffectiveRegistryTiersSufficient(),
"auto_promotion_scope": cl.EffectiveAutoPromotionScope(),
"auto_promotion_scopes": config.ContributorLaneAutoPromotionScopes,
"grantable_tiers": config.ContributorLaneGrantableTiers,
"enabled": cl.IsEnabled(),
"soak_minutes": cl.EffectiveSoakMinutes(),
"require_reviewer_verdict": cl.EffectiveRequireReviewerVerdict(),
"repos": contributorLaneRepos,
"exclude_labels": contributorLaneLabels,
"require_tiers": cl.EffectiveRequireTiers(),
"registry_tiers_sufficient": cl.EffectiveRegistryTiersSufficient(),
"auto_promotion_scope": cl.EffectiveAutoPromotionScope(),
"auto_promotion_scopes": config.ContributorLaneAutoPromotionScopes,
"grantable_tiers": config.ContributorLaneGrantableTiers,
"approve_held_runs_for_registry_trusted": cl.EffectiveApproveHeldRunsForRegistryTrusted(),
},
}
}
Expand Down
Loading
Loading