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
7 changes: 7 additions & 0 deletions .github/codex/maintenance/investigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,10 @@ forms enforced by the output schema: `no_change`, `new_patch`, `new_branch`,
`branch_eol`, `recipe_rebuild`, `repair`, `source_unhealthy`, `health_failed`,
`policy_failure`, or `auth_failure` with the required version, date, attempt,
or lowercase hexadecimal evidence suffix.

Every `completionAssessment.criteria[].evidence` entry is a machine-resolved
reference, never explanatory prose. Use only `evidence[N]` for an item in the
plan evidence array, `preconditions.phpBinHead`, `preconditions.misePhpHead`,
`preconditions.supportPolicyDigest`, or `researchSources[N]` for an item in the
research source array. Put explanations in the criterion status or plan summary,
not in an evidence-reference array.
1 change: 1 addition & 0 deletions .github/maintenance-pins.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"actions/checkout": "3d3c42e5aac5ba805825da76410c181273ba90b1",
"actions/cache": "55cc8345863c7cc4c66a329aec7e433d2d1c52a9",
"actions/download-artifact": "3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c",
"actions/attest": "f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6",
"actions/upload-artifact": "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a",
"jdx/mise-action": "9e7f7633ff6f6d6048a9418a68d48f288f50eb14",
"openai/codex-action": "52fe01ec70a42f454c9d2ebd47598f9fd6893d56"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/maintenance-implementation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ jobs:
needs: [validate, validate-repair]
if: always() && (needs.validate.outputs.passed == 'true' || needs['validate-repair'].result == 'success')
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 130
permissions:
contents: write
pull-requests: write
Expand Down
90 changes: 70 additions & 20 deletions .github/workflows/maintenance-watch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,15 @@ jobs:
- name: Decide whether an agent call is required
id: decision
run: |
self_update=()
if [[ "$(git diff-tree --no-commit-id --name-only -r HEAD)" == "maintenance-state/last-evidence.json" ]]; then
self_update=(--self-evidence-update)
fi
./scripts/watch-maintenance-evidence \
--manifest maintenance-run/evidence/evidence-manifest.json \
--previous maintenance-state/last-evidence.json \
--events maintenance-events \
"${self_update[@]}" \
--output maintenance-run/watch-decision.json
echo "trigger=$(jq -r .trigger maintenance-run/watch-decision.json)" >> "$GITHUB_OUTPUT"
- name: Record exact preconditions
Expand Down Expand Up @@ -139,7 +144,10 @@ jobs:
timeout-minutes: 5
permissions:
actions: write
artifact-metadata: write
attestations: write
contents: write
id-token: write
pull-requests: write
issues: write
steps:
Expand All @@ -156,15 +164,57 @@ jobs:
--repo "${{ github.repository }}" \
--name "maintenance-investigation-${{ github.run_id }}" \
--dir maintenance-plan-download
- name: Read unattended mutation state
id: operator
run: |
test "$(jq -r .unattendedMutation .github/maintenance-operator.json)" = "enabled" \
&& echo "enabled=true" >> "$GITHUB_OUTPUT" \
|| echo "enabled=false" >> "$GITHUB_OUTPUT"
- name: Prepare deterministic no-change evidence
id: evidence
if: needs.investigate.outputs.action == 'no_change' && steps.operator.outputs.enabled == 'true'
run: |
git checkout -B "maintenance/evidence-${{ github.run_id }}" origin/main
mkdir -p maintenance-state
jq -n \
--arg manifestDigest "$(jq -r .manifestDigest maintenance-plan-download/evidence/evidence-manifest.json)" \
--arg planDigest "$(jq -r .planDigest maintenance-plan-download/admission.json)" \
--argjson captureDigests "$(jq '[.captures[] | {captureId,digest,status}]' maintenance-plan-download/evidence/evidence-manifest.json)" \
'{schemaVersion:1,manifestDigest:$manifestDigest,planDigest:$planDigest,captures:$captureDigests}' \
> maintenance-state/last-evidence.json
if [[ -z "$(git status --porcelain -- maintenance-state/last-evidence.json)" ]]; then
echo "already_recorded=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "already_recorded=false" >> "$GITHUB_OUTPUT"
jq -n \
--arg runId "${{ github.run_id }}" \
--arg sourceSha "${{ needs.investigate.outputs.base_sha }}" \
--arg actionKey "${{ needs.investigate.outputs.action_key }}" \
--arg manifestDigest "$(jq -r .manifestDigest maintenance-plan-download/evidence/evidence-manifest.json)" \
'{schemaVersion:1,runId:$runId,sourceSha:$sourceSha,actionKey:$actionKey,manifestDigest:$manifestDigest}' \
> maintenance-plan-download/evidence-attestation-predicate.json
- name: Attest deterministic no-change evidence
if: needs.investigate.outputs.action == 'no_change' && steps.operator.outputs.enabled == 'true' && steps.evidence.outputs.already_recorded == 'false'
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4
with:
subject-path: maintenance-state/last-evidence.json
predicate-type: https://bigpixelrocket.dev/maintenance/evidence-state/v1
predicate-path: maintenance-plan-download/evidence-attestation-predicate.json
- name: Dispatch implementation or no-edit release
env:
EVIDENCE_ALREADY_RECORDED: ${{ steps.evidence.outputs.already_recorded }}
GH_TOKEN: ${{ github.token }}
run: |
if [[ "$(jq -r .unattendedMutation .github/maintenance-operator.json)" != "enabled" ]]; then
echo "Unattended mutation is paused; retained investigation remains read-only."
exit 0
fi
action="${{ needs.investigate.outputs.action }}"
if [[ "$action" == "no_change" && "$EVIDENCE_ALREADY_RECORDED" == "true" ]]; then
echo "The exact deterministic evidence state is already recorded."
exit 0
fi
if [[ "$action" == "blocked" || "$action" == "needs_human" ]]; then
jq -n \
--arg actionKey "${{ needs.investigate.outputs.action_key }}" \
Expand Down Expand Up @@ -198,30 +248,29 @@ jobs:
fi

if [[ "$action" == "no_change" ]]; then
:
git checkout -B "maintenance/evidence-${{ github.run_id }}" origin/main
branch="maintenance/evidence-${{ github.run_id }}"
base="$(git rev-parse HEAD)"
mkdir -p maintenance-state
jq -n \
--arg manifestDigest "$(jq -r .manifestDigest maintenance-plan-download/evidence/evidence-manifest.json)" \
--arg planDigest "$(jq -r .planDigest maintenance-plan-download/admission.json)" \
--argjson captureDigests "$(jq '[.captures[] | {captureId,digest,status}]' maintenance-plan-download/evidence/evidence-manifest.json)" \
'{schemaVersion:1,manifestDigest:$manifestDigest,planDigest:$planDigest,captures:$captureDigests}' \
> maintenance-state/last-evidence.json
git add maintenance-state/last-evidence.json
git -c user.name=maintenance-watcher -c user.email=maintenance@invalid \
commit -m "chore: record reviewed maintenance evidence"
head="$(git rev-parse HEAD)"
record_digest="sha256:$(shasum -a 256 maintenance-state/last-evidence.json | awk '{print $1}')"
gh auth setup-git
git push origin HEAD
url="$(gh pr create --base main --head "maintenance/evidence-${{ github.run_id }}" \
--title "chore: record reviewed maintenance evidence" \
--body "Opaque evidence state for a reviewed no-change result.")"
number="${url##*/}"
gh pr checks "$number" --watch --fail-fast --interval 10
gh pr checks "$number" --json name,bucket,link > maintenance-plan-download/no-change-checks.json
if git ls-remote --exit-code --heads origin "$branch" >/dev/null; then
git fetch origin "$branch:refs/remotes/origin/$branch"
fi
git push --force-with-lease origin "HEAD:refs/heads/$branch"
number="$(gh pr list --state open --head "$branch" --json number --jq '.[0].number // empty')"
if [[ -z "$number" ]]; then
url="$(gh pr create --base main --head "$branch" \
--title "chore: record reviewed maintenance evidence" \
--body "Opaque evidence state for a reviewed no-change result.")"
number="${url##*/}"
fi
gh pr checks "$number" --required --watch --fail-fast --interval 10
gh pr checks "$number" --required --json name,bucket,link > maintenance-plan-download/no-change-checks.json
jq -e '[.[] | select(.name=="Script checks") | .bucket] == ["pass"]' maintenance-plan-download/no-change-checks.json
jq -e '[.[] | select(.name=="Protected controls") | .bucket] == ["pass"]' maintenance-plan-download/no-change-checks.json
test "$(gh pr view "$number" --json headRefOid --jq .headRefOid)" = "$head"
git fetch origin main
test "$(git rev-parse origin/main)" = "$base"
Expand Down Expand Up @@ -299,9 +348,10 @@ jobs:
url="$(gh pr create --base main --head "$branch" --title "chore: complete $action_key" \
--body "Deterministic EOL completion bound to exact cross-repository readiness.")"
number="${url##*/}"
gh pr checks "$number" --watch --fail-fast --interval 10
gh pr checks "$number" --json name,bucket,link > maintenance-plan-download/eol-checks.json
gh pr checks "$number" --required --watch --fail-fast --interval 10
gh pr checks "$number" --required --json name,bucket,link > maintenance-plan-download/eol-checks.json
jq -e '[.[] | select(.name=="Script checks") | .bucket] == ["pass"]' maintenance-plan-download/eol-checks.json
jq -e '[.[] | select(.name=="Protected controls") | .bucket] == ["pass"]' maintenance-plan-download/eol-checks.json
test "$(gh pr view "$number" --json headRefOid --jq .headRefOid)" = "$head"
git fetch origin main
test "$(git rev-parse origin/main)" = "$base"
Expand All @@ -322,8 +372,8 @@ jobs:

notify-failure:
name: Notify actionable watcher failure
needs: investigate
if: always() && needs.investigate.result == 'failure'
needs: [investigate, coordinate]
if: always() && (needs.investigate.result == 'failure' || needs.coordinate.result == 'failure')
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
Expand Down
128 changes: 128 additions & 0 deletions .github/workflows/protected-controls.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
pull_request_target:

permissions:
attestations: read
contents: read
pull-requests: read

Expand All @@ -23,15 +24,29 @@ jobs:
REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PROTECTED_REVIEWER: ${{ vars.MAINTENANCE_OWNER }}
run: |
python3 - <<'PY'
import fnmatch
import base64
import json
import os
import pathlib
import re
import subprocess
import sys
import tempfile
import time

from maintenance.control import (
ControlError,
validate_evidence_attestation_predicate,
validate_evidence_state_record,
)

def api(path):
result = subprocess.run(
Expand All @@ -45,9 +60,22 @@ jobs:
raise RuntimeError("GitHub API returned an invalid paginated response")
return [item for page in pages for item in page]

def api_one(path):
result = subprocess.run(
["gh", "api", path],
check=True,
text=True,
stdout=subprocess.PIPE,
)
return json.loads(result.stdout)

repo = os.environ["REPOSITORY"]
number = os.environ["PR_NUMBER"]
head = os.environ["HEAD_SHA"]
base = os.environ["BASE_SHA"]
head_ref = os.environ["HEAD_REF"]
head_repo = os.environ["HEAD_REPOSITORY"]
author = os.environ["PR_AUTHOR"]
reviewer = os.environ["PROTECTED_REVIEWER"].lower()
manifest = json.loads(pathlib.Path("maintenance/protected-paths.json").read_text())
patterns = manifest["patterns"]
Expand All @@ -61,6 +89,106 @@ jobs:
print("No protected control path changed.")
raise SystemExit(0)

evidence_run = re.fullmatch(r"maintenance/evidence-(\d+)", head_ref)
if (
protected == ["maintenance-state/last-evidence.json"]
and evidence_run
and author == "github-actions[bot]"
and head_repo.lower() == repo.lower()
):
commit = api_one(f"repos/{repo}/commits/{head}")
run = api_one(f"repos/{repo}/actions/runs/{evidence_run.group(1)}")
content = api_one(
f"repos/{repo}/contents/maintenance-state/last-evidence.json?ref={head}"
)
try:
encoded = content["content"].replace("\n", "")
decoded = base64.b64decode(encoded, validate=True)
record = json.loads(decoded)
validate_evidence_state_record(record)
except (KeyError, ValueError, json.JSONDecodeError, ControlError) as error:
print(f"Invalid deterministic evidence state: {error}", file=sys.stderr)
raise SystemExit(1) from error
direct_parent = [parent.get("sha") for parent in commit.get("parents", [])] == [base]
trusted_run = (
run.get("path") == ".github/workflows/maintenance-watch.yml"
and run.get("event") in {"schedule", "workflow_dispatch"}
and run.get("head_branch") == "main"
and run.get("head_sha") == base
and run.get("status") == "in_progress"
)
if direct_parent and trusted_run:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
try:
with tempfile.NamedTemporaryFile() as evidence_file:
evidence_file.write(decoded)
evidence_file.flush()
command = [
"gh",
"attestation",
"verify",
evidence_file.name,
"--repo",
repo,
"--signer-workflow",
f"{repo}/.github/workflows/maintenance-watch.yml",
"--source-ref",
"refs/heads/main",
"--source-digest",
base,
"--predicate-type",
"https://bigpixelrocket.dev/maintenance/evidence-state/v1",
"--deny-self-hosted-runners",
"--format",
"json",
]
verification = None
for attempt in range(3):
candidate = subprocess.run(
command,
check=False,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if candidate.returncode == 0:
verification = candidate
break
time.sleep(2**attempt)
if verification is None:
raise RuntimeError(
f"attestation was unavailable after bounded retries: {candidate.stderr}"
)
attestations = json.loads(verification.stdout)
if not isinstance(attestations, list):
raise TypeError("attestation verifier returned a non-array result")
predicates = [
item["verificationResult"]["statement"]["predicate"]
for item in attestations
]
except (
KeyError,
RuntimeError,
TypeError,
json.JSONDecodeError,
) as error:
print(f"Evidence attestation verification failed: {error}", file=sys.stderr)
raise SystemExit(1) from error
for predicate in predicates:
try:
validate_evidence_attestation_predicate(
predicate,
run_id=evidence_run.group(1),
source_sha=base,
action_key=f"no_change:{record['manifestDigest'].removeprefix('sha256:')[:16]}",
manifest_digest=record["manifestDigest"],
)
except ControlError:
continue
print(f"Protected deterministic evidence state approved from attested watcher run {run['id']}.")
raise SystemExit(0)
print("No attestation matched the exact watcher run and evidence state.", file=sys.stderr)
raise SystemExit(1)

reviews = api(f"repos/{repo}/pulls/{number}/reviews")
approved = any(
review.get("state") == "APPROVED"
Expand Down
6 changes: 3 additions & 3 deletions docs/admin-state/php-bin-after.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
},
"url": "https://api.github.com/repos/Bigpixelrocket/php-bin/branches/main/protection"
},
"capturedAt": "2026-07-28T09:06:41Z",
"capturedAt": "2026-07-28T13:05:02Z",
"environments": {
"environments": [
{
Expand Down Expand Up @@ -182,12 +182,12 @@
],
"verified_allowed": false
},
"snapshotDigest": "sha256:3919519486d0614af629c4fba227981a104ba2668e974deb7aecc3702b808eba",
"snapshotDigest": "sha256:3d0ed7f751c408e71033bdc593f7eeba2161470658277281d1c409a7b0572797",
"variables": [
"MAINTENANCE_OWNER"
],
"workflowPermissions": {
"can_approve_pull_request_reviews": false,
"can_approve_pull_request_reviews": true,
"default_workflow_permissions": "read"
}
}
Loading
Loading