Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 3 additions & 3 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
/.github/workflows/ @loadinglucian
/.github/CODEOWNERS @loadinglucian
/.codex/ @loadinglucian
/maintenance/ @loadinglucian
/autorelease/ @loadinglucian
/schemas/ @loadinglucian
/scripts/admit-maintenance-plan @loadinglucian
/scripts/seal-maintenance-patch @loadinglucian
/scripts/admit-autorelease-plan @loadinglucian
/scripts/seal-autorelease-patch @loadinglucian
/scripts/validate-codex-action-inputs @loadinglucian
/scripts/validate-structured-output-schemas @loadinglucian
/scripts/verify-merge-admission @loadinglucian
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Guarded PHP maintenance agent instructions
# Autorelease agent instructions

The overarching goal is one production maintenance system across
The overarching goal is one production autorelease system across
`bigpixelrocket/php-bin` and `bigpixelrocket/mise-php` that detects upstream
PHP release or lifecycle changes, prepares bounded repository work, coordinates
both repositories, and permits deterministic controls to publish immutable,
Expand Down

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions .github/workflows/protected-controls.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ steps.pr.outputs.number }}
HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
PROTECTED_REVIEWER: ${{ vars.MAINTENANCE_OWNER }}
PROTECTED_REVIEWER: ${{ vars.AUTORELEASE_OWNER }}
run: |
python3 - <<'PY'
import fnmatch
Expand All @@ -75,7 +75,7 @@ jobs:
number = os.environ["PR_NUMBER"]
head = os.environ["HEAD_SHA"]
reviewer = os.environ["PROTECTED_REVIEWER"].lower()
manifest = json.loads(pathlib.Path("maintenance/protected-paths.json").read_text())
manifest = json.loads(pathlib.Path("autorelease/protected-paths.json").read_text())
patterns = manifest["patterns"]
files = api(f"repos/{repo}/pulls/{number}/files")
protected = sorted(
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.DS_Store
__pycache__/
*.py[cod]
maintenance-run/
autorelease-run/
.test-tmp/
74 changes: 74 additions & 0 deletions AUTORELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Autorelease

How this repository consumes the accepted `php-bin` support policy,
prepares bounded repository work, and records the exact-commit readiness
that `php-bin` requires before it may publish a new branch.

The scheduled `php-bin policy consumer` captures the accepted public
`support-policy.json` and compares only its digest and incomplete-event state
with `support-snapshot.json`. It does not fetch or classify upstream PHP
lifecycle data. When the exact policy changes, the repository-scoped pinned
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Codex Action produces an evidence-bound plan. Any implementation runs offline,
without a GitHub write credential, and only against admitted paths.

```mermaid
flowchart TD
policy["Accepted php-bin policy commit and digest"] --> compare{"Snapshot differs?"}
compare -- "No" --> quiet["Quiet: no model call or mutation"]
compare -- "Yes" --> plan["Offline repository-scoped Codex plan"]
plan --> admit["Deterministic admission"]
admit --> patch["Offline admitted implementation"]
patch --> seal["Seal paths and digests"]
seal --> test["Clean macOS arm64 plugin tests"]
test --> ready["Commit exact mise_ready record"]
ready --> release["php-bin verifies both readiness records"]
```

Only maintained branches appear in `mise ls-remote` or resolve from a branch
shorthand. An exact historical stable version may still install when its
immutable `php-bin` release and checksum assets exist. New branch publication
waits for matching `php_bin_ready` and `mise_ready` records at exact commits.

Failures and lifecycle transitions use one deduplicated GitHub issue per action
key, assigned through `AUTORELEASE_OWNER`. Comments are added only for meaningful
changes, and GitHub Actions failure email remains an independent fallback.

```mermaid
flowchart TD
phase["Consumer, agent, sealing, test, or readiness phase"] --> result{"Result"}
result -- "Passed" --> state["Record exact evidence and state"]
result -- "Retryable" --> bounded["Bounded repair"]
result -- "Critical, repeated, or exhausted" --> stop["Stop mutation"]
bounded --> result
stop --> issue["Assigned autorelease issue"]
issue --> email["GitHub issue email"]
stop --> actions["Actions failure email"]
```

Pause unattended mutation in the reviewed
`php-bin/.github/autorelease-operator.json` control. Read-only capture and
investigation remain available while paused. Resume through a reviewed change;
partial events continue only through the deterministic next transition.

From a checkout containing both repositories:

```bash
(cd php-bin && ./scripts/test.sh)
(cd mise-php && ./scripts/test.sh)

./php-bin/scripts/verify-autorelease-system \
--mise-repo ./mise-php \
--php-bin-sha <exact-php-bin-sha> \
--mise-php-sha <exact-mise-php-sha> \
--output ./verification-results
```

Each repository's `scripts/test.sh` also validates every pinned Codex Action
invocation, exact CLI version, and canonical `config.toml` loading against the
reviewed offline contract in `.github/codex-action-contract.json` before
exercising autorelease behavior.

Inspect `support-snapshot.json`, `autorelease-events/`, `readiness/`, retained
workflow artifacts, and the event's GitHub issue. Recovery corrects the cause
and reruns the normal admitted path; it never disables checksum, policy,
sealing, exact-SHA, or publication gates.
74 changes: 6 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,76 +24,14 @@ that have reached end of life are intentionally not listed or installable.
Other operating systems and Intel Macs receive an explicit unsupported-target
error. Older macOS releases cannot load the published binaries.

## Guarded automatic maintenance

The scheduled `php-bin policy consumer` captures the accepted public
`support-policy.json` and compares only its digest and incomplete-event state
with `support-snapshot.json`. It does not fetch or classify upstream PHP
lifecycle data. When the exact policy changes, the repository-scoped pinned
Codex Action produces an evidence-bound plan. Any implementation runs offline,
without a GitHub write credential, and only against admitted paths.

```mermaid
flowchart TD
policy["Accepted php-bin policy commit and digest"] --> compare{"Snapshot differs?"}
compare -- "No" --> quiet["Quiet: no model call or mutation"]
compare -- "Yes" --> plan["Offline repository-scoped Codex plan"]
plan --> admit["Deterministic admission"]
admit --> patch["Offline admitted implementation"]
patch --> seal["Seal paths and digests"]
seal --> test["Clean macOS arm64 plugin tests"]
test --> ready["Commit exact mise_ready record"]
ready --> release["php-bin verifies both readiness records"]
```

Only maintained branches appear in `mise ls-remote` or resolve from a branch
shorthand. An exact historical stable version may still install when its
immutable `php-bin` release and checksum assets exist. New branch publication
waits for matching `php_bin_ready` and `mise_ready` records at exact commits.

Failures and lifecycle transitions use one deduplicated GitHub issue per action
key, assigned through `MAINTENANCE_OWNER`. Comments are added only for meaningful
changes, and GitHub Actions failure email remains an independent fallback.

```mermaid
flowchart TD
phase["Consumer, agent, sealing, test, or readiness phase"] --> result{"Result"}
result -- "Passed" --> state["Record exact evidence and state"]
result -- "Retryable" --> bounded["Bounded repair"]
result -- "Critical, repeated, or exhausted" --> stop["Stop mutation"]
bounded --> result
stop --> issue["Assigned maintenance issue"]
issue --> email["GitHub issue email"]
stop --> actions["Actions failure email"]
```

Pause unattended mutation in the reviewed
`php-bin/.github/maintenance-operator.json` control. Read-only capture and
investigation remain available while paused. Resume through a reviewed change;
partial events continue only through the deterministic next transition.

From a checkout containing both repositories:

```bash
(cd php-bin && ./scripts/test.sh)
(cd mise-php && ./scripts/test.sh)

./php-bin/scripts/verify-maintenance-system \
--mise-repo ./mise-php \
--php-bin-sha <exact-php-bin-sha> \
--mise-php-sha <exact-mise-php-sha> \
--output ./verification-results
```
## Autorelease

Each repository's `scripts/test.sh` also validates every pinned Codex Action
invocation, exact CLI version, and canonical `config.toml` loading against the
reviewed offline contract in `.github/codex-action-contract.json` before
exercising maintenance behavior.
This repository tracks `php-bin` automatically. A daily consumer captures the
accepted public support policy, and deterministic workflows admit, seal, and
merge any required change, then record exact-commit readiness.

Inspect `support-snapshot.json`, `maintenance-events/`, `readiness/`, retained
workflow artifacts, and the event's GitHub issue. Recovery corrects the cause
and reruns the normal admitted path; it never disables checksum, policy,
sealing, exact-SHA, or publication gates.
See [AUTORELEASE.md](AUTORELEASE.md) for the full contract, the operator
pause control, and maintainer commands.

## Install

Expand Down
File renamed without changes.
File renamed without changes.
6 changes: 3 additions & 3 deletions maintenance/admission.py → autorelease/admission.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,12 @@ def admit(
"no_change", "new_patch", "new_branch", "branch_eol", "repair",
"reconcile_partial", "blocked", "needs_human",
}:
raise AdmissionError("invalid maintenance action")
raise AdmissionError("invalid autorelease action")
if plan.get("action") in {"blocked", "needs_human"}:
raise AdmissionError("no-go plan cannot advance")
action_key = plan.get("actionKey", "")
if not ACTION_KEY_RE.fullmatch(action_key):
raise AdmissionError("invalid maintenance action key")
raise AdmissionError("invalid autorelease action key")
if action_key != contract.get("actionKey"):
raise AdmissionError("plan action key changed from the event contract")
if action_key.startswith("new_branch:") and plan.get("action") != "new_branch":
Expand Down Expand Up @@ -495,7 +495,7 @@ def main() -> int:
print(json.dumps(value))
return 0
except (AdmissionError, OSError, subprocess.CalledProcessError) as error:
print(f"mise maintenance admission rejected: {error}", file=sys.stderr)
print(f"mise autorelease admission rejected: {error}", file=sys.stderr)
return 1


Expand Down
6 changes: 3 additions & 3 deletions maintenance/consumer.py → autorelease/consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def fetch_url(url: str, output: pathlib.Path) -> dict[str, Any]:
raise ConsumerError("policy capture URL is outside the reviewed HTTPS allowlist")
request = urllib.request.Request(
url,
headers={"Accept": "application/json", "User-Agent": "bigpixelrocket-maintenance/1"},
headers={"Accept": "application/json", "User-Agent": "bigpixelrocket-autorelease/1"},
)
opener = urllib.request.build_opener(RestrictedRedirect)
last_error: Exception | None = None
Expand Down Expand Up @@ -112,7 +112,7 @@ def pinned_policy_urls(commit_sha: str) -> tuple[str, str]:
raise ConsumerError("php-bin main state has no exact commit")
return (
f"{RAW_ROOT}/{commit_sha}/support-policy.json",
f"{RAW_ROOT}/{commit_sha}/maintenance/policy-invariants.json",
f"{RAW_ROOT}/{commit_sha}/autorelease/policy-invariants.json",
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
)


Expand Down Expand Up @@ -350,7 +350,7 @@ def main() -> int:
print(json.dumps(result))
return 0
except (ConsumerError, OSError, json.JSONDecodeError) as error:
print(f"maintenance consumer rejected input: {error}", file=sys.stderr)
print(f"autorelease consumer rejected input: {error}", file=sys.stderr)
return 1


Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
{
"schemaVersion": 1,
"patterns": [
".github/codex/maintenance/*",
".github/codex/autorelease/*",
".github/codex-action-contract.json",
".github/workflows/*",
".codex/*",
"schemas/*",
"maintenance/*",
"scripts/admit-maintenance-plan",
"autorelease/*",
"scripts/admit-autorelease-plan",
"scripts/dispatch-pr-checks",
"scripts/seal-maintenance-patch",
"scripts/seal-autorelease-patch",
"scripts/validate-codex-action-inputs",
"scripts/validate-structured-output-schemas",
"scripts/verify-merge-admission",
"maintenance-events/*",
"autorelease-events/*",
"readiness/*",
".github/CODEOWNERS"
]
Expand Down
10 changes: 5 additions & 5 deletions docs/admin-state/mise-php-after.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@
"custom_branch_policies": false,
"protected_branches": true
},
"html_url": "https://github.com/Bigpixelrocket/mise-php/deployments/activity_log?environments_filter=php-maintenance-release",
"html_url": "https://github.com/Bigpixelrocket/mise-php/deployments/activity_log?environments_filter=php-autorelease-publish",
"id": 18804850284,
"name": "php-maintenance-release",
"name": "php-autorelease-publish",
"node_id": "EN_kwDOTj2ybM8AAAAEYNs-bA",
"protection_rules": [
{
Expand All @@ -93,7 +93,7 @@
}
],
"updated_at": "2026-07-27T10:08:07Z",
"url": "https://api.github.com/repos/Bigpixelrocket/mise-php/environments/php-maintenance-release"
"url": "https://api.github.com/repos/Bigpixelrocket/mise-php/environments/php-autorelease-publish"
}
],
"total_count": 1
Expand All @@ -113,7 +113,7 @@
"good first issue",
"help wanted",
"invalid",
"maintenance",
"autorelease",
"question",
"wontfix"
],
Expand Down Expand Up @@ -163,7 +163,7 @@
},
"snapshotDigest": "sha256:e50672433148e1054cd0436af11f47cbc7bf643db650eaed166ffcd1633fa632",
"variables": [
"MAINTENANCE_OWNER"
"AUTORELEASE_OWNER"
],
"workflowPermissions": {
"can_approve_pull_request_reviews": true,
Expand Down
10 changes: 5 additions & 5 deletions docs/repository-settings.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Repository settings

The plan executor applies this state with the protected
`php-bin/scripts/configure-github-maintenance` command and verifies it with
`php-bin/scripts/configure-github-autorelease` command and verifies it with
`php-bin/scripts/snapshot-github-admin-state`. Snapshots are redacted: only
secret names are retained.

Expand All @@ -11,7 +11,7 @@ Required repository state:
- 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 `maintenance/protected-paths.json`.
`loadinglucian` approval for any path in `autorelease/protected-paths.json`.
- Bind the required check to the GitHub Actions app, preventing another app
from satisfying the same context name.
- Require conversation resolution.
Expand All @@ -33,9 +33,9 @@ Required repository state:
- Allow GitHub-owned Actions plus only `openai/codex-action` and
`jdx/mise-action`, and require every Action reference to use a full commit
SHA.
- Create the protected `php-maintenance-release` environment.
- Create the protected `php-autorelease-publish` environment.
- Enable GitHub immutable releases for future repository releases.
- Set `MAINTENANCE_OWNER=loadinglucian`.
- Set `AUTORELEASE_OWNER=loadinglucian`.
- Keep a distinct repository-scoped `OPENAI_API_KEY` secret.

CODEOWNERS protects agent instructions, workflows, schemas, admission, sealing,
Expand All @@ -48,7 +48,7 @@ still rejects event/readiness paths as agent-authored changes.
--repo bigpixelrocket/mise-php \
--output mise-php/docs/admin-state/mise-php.json

./php-bin/scripts/configure-github-maintenance \
./php-bin/scripts/configure-github-autorelease \
--repo bigpixelrocket/mise-php \
--owner loadinglucian \
--required-check "Plugin contract"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))
from maintenance.admission import main
from autorelease.admission import main

sys.argv.insert(1, "admit")
raise SystemExit(main())
2 changes: 1 addition & 1 deletion scripts/consume-php-policy
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))
from maintenance.consumer import main
from autorelease.consumer import main

raise SystemExit(main())
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))
from maintenance.admission import main
from autorelease.admission import main

sys.argv.insert(1, "seal")
raise SystemExit(main())
2 changes: 1 addition & 1 deletion scripts/verify-merge-admission
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
exec python3 "$ROOT/maintenance/admission.py" verify-merge "$@"
exec python3 "$ROOT/autorelease/admission.py" verify-merge "$@"
Loading
Loading