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
10 changes: 10 additions & 0 deletions .github/workflows/protected-controls.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ jobs:
print("No protected control path changed.")
raise SystemExit(0)

# A solo owner cannot approve their own pull request, so demanding an
# exact-head owner review of owner-authored changes was unsatisfiable
# and every such merge ended in an administrative bypass regardless.
# The gate's value is against non-owner authors: automation identities
# and admitted agent work. Owner-authored protected changes pass here
# and still owe the functional required check before merging.
if author.lower() == reviewer:
print(f"Protected paths changed by the configured owner {author}.")
raise SystemExit(0)

readiness_run = re.fullmatch(r"autorelease/readiness-(\d+)", head_ref)
if (
len(files) == 1
Expand Down
149 changes: 87 additions & 62 deletions docs/admin-state/mise-php-after.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,65 +13,8 @@
"subscribed": true,
"url": "https://api.github.com/repos/Bigpixelrocket/mise-php/subscription"
},
"branchProtection": {
"allow_deletions": {
"enabled": false
},
"allow_force_pushes": {
"enabled": false
},
"allow_fork_syncing": {
"enabled": false
},
"block_creations": {
"enabled": false
},
"enforce_admins": {
"enabled": true,
"url": "https://api.github.com/repos/Bigpixelrocket/mise-php/branches/main/protection/enforce_admins"
},
"lock_branch": {
"enabled": false
},
"required_conversation_resolution": {
"enabled": true
},
"required_linear_history": {
"enabled": true
},
"required_pull_request_reviews": {
"dismiss_stale_reviews": true,
"require_code_owner_reviews": true,
"require_last_push_approval": false,
"required_approving_review_count": 0,
"url": "https://api.github.com/repos/Bigpixelrocket/mise-php/branches/main/protection/required_pull_request_reviews"
},
"required_signatures": {
"enabled": false,
"url": "https://api.github.com/repos/Bigpixelrocket/mise-php/branches/main/protection/required_signatures"
},
"required_status_checks": {
"checks": [
{
"app_id": 15368,
"context": "Plugin contract"
},
{
"app_id": 15368,
"context": "Protected controls"
}
],
"contexts": [
"Plugin contract",
"Protected controls"
],
"contexts_url": "https://api.github.com/repos/Bigpixelrocket/mise-php/branches/main/protection/required_status_checks/contexts",
"strict": true,
"url": "https://api.github.com/repos/Bigpixelrocket/mise-php/branches/main/protection/required_status_checks"
},
"url": "https://api.github.com/repos/Bigpixelrocket/mise-php/branches/main/protection"
},
"capturedAt": "2026-08-02T19:49:28Z",
"branchProtection": null,
"capturedAt": "2026-08-04T12:48:57Z",
"environments": {
"environments": [
{
Expand Down Expand Up @@ -148,7 +91,89 @@
"use_squash_pr_title_as_default": true,
"visibility": "public"
},
"rulesets": [],
"rulesets": [
{
"_links": {
"html": {
"href": "https://github.com/Bigpixelrocket/mise-php/rules/20378767"
},
"self": {
"href": "https://api.github.com/repos/Bigpixelrocket/mise-php/rulesets/20378767"
}
},
"bypass_actors": [
{
"actor_id": 5,
"actor_type": "RepositoryRole",
"bypass_mode": "pull_request"
}
],
"conditions": {
"ref_name": {
"exclude": [],
"include": [
"~DEFAULT_BRANCH"
]
}
},
"created_at": "2026-08-04T15:12:16.343+03:00",
"current_user_can_bypass": "pull_requests_only",
"enforcement": "active",
"id": 20378767,
"name": "protect_main",
"node_id": "RRS_lACqUmVwb3NpdG9yec5OPbJszgE29I8",
"rules": [
{
"type": "deletion"
},
{
"type": "non_fast_forward"
},
{
"type": "required_linear_history"
},
{
"parameters": {
"allowed_merge_methods": [
"squash"
],
"dismiss_stale_reviews_on_push": true,
"dismissal_restriction": {
"allowed_actors": [],
"enabled": false
},
"require_code_owner_review": true,
"require_last_push_approval": false,
"required_approving_review_count": 0,
"required_review_thread_resolution": true,
"required_reviewers": []
},
"type": "pull_request"
},
{
"parameters": {
"do_not_enforce_on_create": false,
"required_status_checks": [
{
"context": "Plugin contract",
"integration_id": 15368
},
{
"context": "Protected controls",
"integration_id": 15368
}
],
"strict_required_status_checks_policy": true
},
"type": "required_status_checks"
}
],
"source": "Bigpixelrocket/mise-php",
"source_type": "Repository",
"target": "branch",
"updated_at": "2026-08-04T15:12:16.433+03:00"
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
],
"schemaVersion": 1,
"secretNames": [
"OPENAI_API_KEY"
Expand All @@ -161,12 +186,12 @@
],
"verified_allowed": false
},
"snapshotDigest": "sha256:fcdf015fdf96ade8375f29367251f35fec22e14952fd9abe46fecb63ec05f8c9",
"snapshotDigest": "sha256:3fa95b31e4ca35cae67c7bdc8186f5e2eca04d4ad6472f7358c3fed99a5afbe0",
"variables": [
"AUTORELEASE_OWNER"
],
"workflowPermissions": {
"can_approve_pull_request_reviews": true,
"can_approve_pull_request_reviews": false,
"default_workflow_permissions": "read"
}
}
24 changes: 16 additions & 8 deletions docs/repository-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,25 @@ secret names are retained.

Required repository state:

- Require a pull request before merging.
- Protect `main` with the `protect_main` repository ruleset: require a pull
request (squash merges only, CODEOWNER review, review-thread resolution),
require the status checks below on an up-to-date branch, require linear
history, and block force pushes and branch deletion. Repository
administrators are bypass actors in `pull_request` mode only: the solo
owner can merge a pull request past a failing rule but can never push,
force-push, or delete `main` directly. Classic branch protection (and its
`enforce_admins` toggle) is retired; the configure script removes it.
- Require the `Plugin contract` status check.
- Require the base-controlled `Protected controls` status check. It passes
automatically for unprotected generated paths and requires an exact-head
`loadinglucian` approval for any path in `autorelease/protected-paths.json`.
- Bind the required check to the GitHub Actions app, preventing another app
automatically for unprotected generated paths and for owner-authored pull
requests (a solo owner cannot approve their own PR, so an owner review
requirement was unsatisfiable there); any other author touching a path in
`autorelease/protected-paths.json` requires an exact-head `loadinglucian`
approval. The sole deterministic exception is a single `readiness/*.json`
record authored by `github-actions[bot]` on the trusted consumer run's own
branch, validated against the exact run that wrote it.
- Bind the required checks to the GitHub Actions app, preventing another app
from satisfying the same context name.
- Require conversation resolution.
- Require linear history; block force pushes and branch deletion.
- Enforce protection for administrators and require CODEOWNER approval for
protected control paths.
- Enable squash merge, auto-merge, update branch, and automatic head-branch
deletion; disable merge commits and rebase merge.
- Keep the default workflow token read-only; deterministic downstream jobs
Expand Down
10 changes: 10 additions & 0 deletions test/test_autorelease.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,16 @@ def test_policy_lua_diff_requires_matching_snapshot(self):
seal(repo, base, **arguments)
self.assertIn("policy.lua", str(ctx.exception))

def test_protected_controls_pass_owner_authored_changes_before_bot_exemptions(self):
# The owner short-circuit must sit after the no-protected-path exit and
# before the automation exemption, so it can never widen what a bot
# identity is allowed to merge.
root = pathlib.Path(__file__).resolve().parents[1]
protected_workflow = (root / ".github/workflows/protected-controls.yml").read_text()
owner_pass = protected_workflow.index("if author.lower() == reviewer:")
self.assertLess(protected_workflow.index("No protected control path changed."), owner_pass)
self.assertLess(owner_pass, protected_workflow.index('re.fullmatch(r"autorelease/readiness-'))

def test_token_created_prs_explicitly_dispatch_required_checks(self):
root = pathlib.Path(__file__).resolve().parents[1]
ci = (root / ".github/workflows/ci.yml").read_text()
Expand Down
Loading