fix: autorelease unattended hardening - #27
Conversation
The action-key-to-filename mapping was written out inline in the consumer workflow, and ACTION_KEY_RE was compiled a second time in admission.py. consumer.py now owns action_filename() beside the regex it validates against, and exposes it as the action-filename subcommand the workflow calls. verify-merge-admission ran admission.py as a script, which puts autorelease/ rather than the repository root on sys.path and would break the new import; it becomes the same shim its three siblings already use, and admission.py drops its now-dead __main__ block.
GitHub runs `run:` blocks with `bash -e {0}` unless a shell is named, so a
failing command in the middle of a pipeline was silently ignored. Naming bash
explicitly switches the runner to `bash --noprofile --norc -eo pipefail {0}`,
matching what the scripts already assume.
Every pipeline in these workflows was walked first: the remaining ones either
feed a command substitution whose exit status was already checked, or read
PIPESTATUS under `set +e` and are unaffected by the new setting.
The parity gate failed on a bare `test`, which prints nothing, so an oversized or symlinked entry in the manifest produced a red run with no indication of which path caused it. The neighbouring drift failure already names the path.
mise-php never writes an event record: `autorelease-events/` held only a
.gitkeep, last touched by a repository rename, so the scan could only ever find
nothing and the `event_incomplete` trigger was unreachable. `incompleteActions`
was written into decision.json and read by nobody.
`autorelease/protected-paths.json` keeps its `autorelease-events/*` entry and
its test on purpose: it is a fail-closed guard, and it should keep refusing
writes under that prefix whether or not the directory exists.
Zero-reference proof after the change, over the whole worktree excluding .git:
$ grep -rn "event_incomplete\|incompleteActions\|autorelease-events\|--events" .
autorelease/protected-paths.json:19: "autorelease-events/*",
test/test_autorelease.py:133: self.assertTrue(protected("autorelease-events/new-patch.json"))
php-bin has its own `event_incomplete` in `control.py`'s watch decision; that
one is live, backed by a real `autorelease-events/` tree, and is untouched.
The script picked ripgrep when it was installed and grep otherwise, and the two branches disagreed about hidden files, ignore rules, and build output, so the runner's tool inventory decided what was actually checked. Scanning the tracked file list makes the scope the same everywhere. This path is in autorelease/shared-files.json, so the file is byte-identical to php-bin's copy: sha256 143cd773132b1a760da86b6fba6989fe23bd0e1d9d0b64f8a98f5b64a3cea630.
fnmatch.fnmatch runs os.path.normcase on both sides, so on a case-folding platform the protected-path and allowed-path gates would answer differently from the Linux runner that enforces them. Git tracks paths as case-sensitive bytes, so fnmatchcase is the comparison these gates meant all along.
The investigation agent was required to produce a `notification` object that
mise-php never opens: there is no notify script, no `issues: write` grant, and
no step that reads the field out of the plan. Failure notification for both
repositories is raised by php-bin's watcher, which reads its own plan.
Zero-reference proof after the change, over the whole worktree excluding .git:
$ grep -rn "notification\|suggestedSeverity\|humanActionRequired" .
(no output)
php-bin keeps its own `notification` block; autorelease-watch.yml reads
`.notification.summary` from it.
The merge job ran under `always()`, so a cancelled run still entered the job that pushes the branch, opens the pull request, and merges it. `!cancelled()` keeps the "run even though validate failed and the repair path took over" behaviour without that. It also keyed the repair path on `needs['validate-repair'].result`, which is `success` for any green job, including one that went green without producing the validated artifact this job downloads. `validate-repair` now publishes the same named `passed` output as `validate`, set by a final step that only runs after the artifact upload succeeded.
The repair validation overwrote autorelease-run/sealed in place, so the uploaded artifact ended up claiming the repaired patch had been the sealed one all along and the patch that actually failed validation was gone. Renaming it to sealed-failed keeps both in the evidence while the merge job still finds the bytes it verifies under autorelease-run/sealed. seal() writes exactly sealed.patch and patch-manifest.json, so copying the directory carries the same files the two per-file copies did.
verify_merge was tested in process, but the merge job calls it through scripts/verify-merge-admission and reads only the exit status. The script fixes the subcommand by editing sys.argv, so a wiring mistake there would let a rejected patch merge with nobody noticing. consume-php-policy already has the same kind of check.
`git ls-files -z | xargs -0 grep ... || true` needed the tolerance for xargs
exit 123, which grep produces whenever a batch matches nothing, but the same
tolerance covered a failing listing. Run outside a repository the check printed
"fatal: not a git repository" and then "Public-language check passed." with exit
0, having scanned nothing.
The listing is now produced and checked before the grep runs, in a file because
command substitution drops the NUL separators.
Probes, in order: outside a repository, a clean repository, a tracked violation,
a repository with no tracked files.
$ ./scripts/check-public-language.sh # not a repository
fatal: not a git repository (or any of the parent directories): .git
Public-language check could not list the tracked files of /tmp/probe/nogit.
rc=1
$ ./scripts/check-public-language.sh # clean
Public-language check passed.
rc=0
$ ./scripts/check-public-language.sh # tracked violation
doc.txt:1:a HERD of cows
Public-language check failed.
rc=1
$ ./scripts/check-public-language.sh # empty repository
Public-language check found no tracked files in /tmp/probe/empty.
rc=1
The same file before this change answered rc 0 with "passed" to the first probe.
This path is in autorelease/shared-files.json, so the file is byte-identical to
php-bin's copy: sha256
280d32fb10e58baa0b6b364f6e5cc1d122528a8cb7a0f4aa4e504eeb0ba76ea8.
validate-repair records passed=true as its last step on purpose: the merge job keys on that output and downloads the artifact, so claiming success before the upload would point merge at something that may not exist. validate set the same output before its uploads, so the stated invariant only held in one of the two jobs. The check step now reports a separate status output that gates the bundle and both uploads, and passed=true is recorded last. A failed run leaves passed unset, which merge and repair already read as not passed.
AUTORELEASE.md described deduplicated assigned issues as if this repository raised them. It does not: php-bin owns notify-autorelease and the jobs holding issues: write, and this consumer requests no issue permission at all, so a failure confined to it reaches the owner through Actions email until php-bin records it. Say so. The supported-branch range was hardcoded as "8.2 through 8.5"; it now points at the snapshot that tracks the accepted policy automatically, and states that EOL delists a branch without making an already-published version less installable. Also record which paths are protected harness and which stay agent-admissible, add an "Unattended lifecycle" section covering both directions, and correct the admin-state snapshot filename to the -after convention actually committed.
The sentence promising that an already-published version keeps installing used 8.2.29, which was never released. The branch's published tag is 8.2.32.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_aa0078b3-37a7-4cc3-9869-585747cc7cb5) |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe change centralizes PHP support policy, strengthens autorelease admission and readiness validation, adds shared-file parity checks, and requires explicit successful workflow validation before merge readiness. ChangesPolicy-driven version support
Admission contracts and validation
Workflow enforcement and lifecycle
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant ConsumerWorkflow
participant PhpBin
participant PrepareAgentTask
participant AdmissionChecks
participant ProtectedControls
participant MergeReadiness
ConsumerWorkflow->>PhpBin: Fetch shared files
ConsumerWorkflow->>PrepareAgentTask: Request phase criteria
ConsumerWorkflow->>AdmissionChecks: Validate checks and artifacts
ProtectedControls->>AdmissionChecks: Validate readiness record
ConsumerWorkflow->>MergeReadiness: Publish passed validation
MergeReadiness->>ConsumerWorkflow: Permit merge readiness
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (10)
.github/CODEOWNERS (1)
3-21: 🧹 Nitpick | 🔵 TrivialEvery protected control now has one owner.
All 21 rules name
@loadinglucian. The autorelease design requires owner review for each protected path, so this single account becomes a required reviewer for every admission, workflow, schema, and gate-harness change. When that account is unavailable, protected-control changes cannot merge.Consider a team handle, for example
@org/autorelease-owners, with the same effective membership. A team handle keeps the same review requirement and lets membership change without editing a protected file.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/CODEOWNERS around lines 3 - 21, Replace the individual `@loadinglucian` owner entries in CODEOWNERS with a team handle representing the autorelease/control owners, such as `@org/autorelease-owners`, while preserving coverage for every listed protected path.test/test_autorelease.py (3)
240-253: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a case for
--require-protected-controls.The test covers only the
--check-namepath.scripts/assert-admission-checkslines 22-24 add a secondjqassertion on theProtected controlsbucket when--require-protected-controlsis set. That branch decides whether a flow may touch protected paths, and no test exercises it. Two more fixtures cover it: one whereProtected controlsispass, and one where it is absent or another bucket.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/test_autorelease.py` around lines 240 - 253, Extend test_assert_admission_checks_covers_the_plugin_contract_bucket to exercise --require-protected-controls: add a passing fixture containing a pass-valued Protected controls bucket and a failing fixture where that bucket is absent or replaced by another bucket, asserting the script succeeds and fails respectively.
103-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the repository root the way the rest of the file does.
Line 108 reads
autorelease/admission.pythrough a cwd-relative path. Lines 156 and 193 resolve the root withpathlib.Path(__file__).resolve().parents[1]. If the suite runs from any directory other than the repository root, line 108 raisesFileNotFoundErrorand the test errors instead of reporting a duplicated action-key definition.♻️ Proposed fix
- source = pathlib.Path("autorelease/admission.py").read_text() + root = pathlib.Path(__file__).resolve().parents[1] + source = (root / "autorelease/admission.py").read_text()The same applies to the relative paths at lines 121 and 125.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/test_autorelease.py` around lines 103 - 111, Update test_action_key_alphabet_and_filename_have_one_definition to resolve the repository root from pathlib.Path(__file__).resolve().parents[1], matching the existing approach elsewhere in the file, and use that root for all admission.py and related file reads currently using cwd-relative paths. Preserve the existing assertions and test behavior.
405-439: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueLine 411 writes
event.jsontwice with the same content.The loop at lines 420-431 mutates
preconditionsin place, andcontractholds a reference to that same dict. Line 432 therefore writes the final contract, and line 411 is redundant. The digests at lines 435-439 read the file after line 432, so behavior is correct today.Remove line 411 and add a short note that the event file must be written after the capture loop settles the precondition digests. Without that note, a later reader may remove line 432 instead and silently break
digests["eventContract"].🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/test_autorelease.py` around lines 405 - 439, Remove the initial redundant event.write_text call before the captures loop, and retain the later write after the loop that finalizes preconditions. Add a brief comment near the retained write stating that event.json must be written after capture-derived precondition digests are settled, before eventContract is digested.scripts/validate-structured-output-schemas (1)
81-91: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winThe plan-schema parity check is skipped silently when the path does not match.
The checks at lines 82-91 run only when a discovered path equals
ROOT / "schemas/autorelease-plan.schema.json".schema_pathsis built from--output-schemastrings found in workflow files at line 68. If a workflow stops referencing the plan schema, or references it under a non-identical spelling such asschemas/../schemas/autorelease-plan.schema.json, then the loop never enters this branch. The script still prints a success line, so the action-key and required-check parity is no longer enforced and nothing reports the gap.Assert that the plan schema is present in
schema_pathsbefore the loop.🛡️ Proposed assertion
if not schema_paths: fail("no static Codex output schemas were discovered") + + plan_schema = ROOT / "schemas/autorelease-plan.schema.json" + if plan_schema not in schema_paths: + fail("the autorelease plan schema was not discovered in any workflow")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/validate-structured-output-schemas` around lines 81 - 91, Before iterating over schema_paths, assert that the expected autorelease plan schema path is present, using the same normalized path representation as schema_paths. Fail with a clear validation message when it is absent, then retain the existing actionKey and requiredChecks parity checks in the loop.scripts/check-public-language.sh (1)
29-31: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
|| truemasksxargsandgrepfailures, not only status 123.The comment names status 123, but
|| truediscards every non-zero status.xargsreturns 126 when it cannot rungrepand 127 whengrepis absent.grepreturns 2 on a read error, andxargsmaps that to 123 as well. In each casematchesis empty and the script prints "Public-language check passed." The gate then reports a pass without scanning anything.Accept only the statuses that mean "ran correctly and found nothing".
🛡️ Proposed status allowlist
-# xargs reports 123 when any grep batch matches nothing, so the finding is read -# from the output rather than from the exit status. -matches="$(cd "$PROJECT_ROOT" && xargs -0 grep -HIFni -e "$REJECTED_TERM" < "$tracked" || true)" +# xargs reports 123 when any grep batch matches nothing, so the finding is read +# from the output rather than from the exit status. Every other non-zero status +# means grep did not run correctly, so the scan is not treated as a pass. +status=0 +matches="$(cd "$PROJECT_ROOT" && xargs -0 grep -HIFni -e "$REJECTED_TERM" < "$tracked")" || status=$? +if [[ "$status" -ne 0 && "$status" -ne 123 ]]; then + echo "Public-language check could not scan the tracked files (xargs status $status)." >&2 + exit 1 +fi🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/check-public-language.sh` around lines 29 - 31, Update the xargs/grep invocation assigning matches in the public-language check to tolerate only the expected no-match status while propagating execution, missing-command, and read errors. Capture the command’s exit status explicitly and allow only successful scanning outcomes, including xargs status 123 when it represents completed grep batches, before reporting the check as passed.autorelease/admission.py (2)
111-118: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winCheck element types before you sort
evidenceDigests.If
evidenceDigestscontains mixed types, for example["sha256:...", 1], thensorted(digests)raisesTypeErrorbefore theisinstancecheck runs. The result is still fail-closed, but the caller receivesTypeErrorinstead ofAdmissionError, so the rejection message is lost.♻️ Proposed reordering
digests = record["evidenceDigests"] if ( not isinstance(digests, list) or not digests + or not all(isinstance(item, str) and SHA256_RE.fullmatch(item) for item in digests) or digests != sorted(digests) - or not all(isinstance(item, str) and SHA256_RE.fullmatch(item) for item in digests) ): raise AdmissionError("readiness record evidence digests are invalid")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@autorelease/admission.py` around lines 111 - 118, Update the evidenceDigests validation in the admission record handling so the all-elements string/SHA-256 format check runs before comparing digests with sorted(digests). Preserve the existing non-list and empty-list rejection behavior, and ensure mixed-type input raises the established AdmissionError rather than allowing sorted to raise TypeError.
417-432: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThe generated
lib/policy.lualayout is defined independently in the gate and in the test. Both sites spell out the same header comments,return {,maintained = {, and eight-space quoted branch lines.scripts/generate-policy-luaowns the same layout. Because the test does not read the gate's definition, a change to either copy alone leaves the test green while the gate rejects every real patch.
autorelease/admission.py#L417-L432: replaceexpected_policy_lineswith a call to one shared renderer, for examplerender_policy_lua(maintained).splitlines(), and compare the full text so a missing trailing newline is also rejected.test/test_autorelease.py#L490-L499: delete the localgenerated_policy_luabody and return the shared renderer's output, soseal_fixtureand the gate cannot disagree about the layout.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@autorelease/admission.py` around lines 417 - 432, Centralize generated policy rendering so the admission gate and tests use the same layout. In autorelease/admission.py lines 417-432, replace the local expected_policy_lines construction with the shared renderer (such as render_policy_lua(maintained)) and compare complete text, including the trailing newline. In test/test_autorelease.py lines 490-499, remove the local generated_policy_lua body and return the shared renderer output so seal_fixture and the gate cannot diverge.scripts/assert-admission-checks (1)
20-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
--checksomission exits without a diagnostic, and this file is parity-gated.Line 20 is a bare
[[ -n "$checks_file" ]]. If a caller omits--checks, the script exits 1 underset -eand prints nothing, so the workflow log shows a failed step with no cause. Add an explicit message.
autorelease/shared-files.jsonlists this script, so the consumer compares it byte-for-byte against php-bin at the pinned commit. Land the same edit in php-bin, or the next shared-file gate run fails.♻️ Proposed diagnostic
-[[ -n "$checks_file" ]] +if [[ -z "$checks_file" ]]; then + echo "--checks is required" >&2 + exit 2 +fi🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/assert-admission-checks` around lines 20 - 21, Replace the bare checks_file assertion in the argument-validation flow with an explicit diagnostic before exiting when --checks is omitted, while preserving the existing failure behavior. Apply the identical byte-for-byte change to the corresponding php-bin copy because this script is parity-gated through autorelease/shared-files.json.scripts/validate-codex-action-inputs (1)
18-21: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winMake destination-path quoting optional in both regexes.
Current workflows use quoted destinations, but unquoted equivalent copies are not detected. Add
"?around both destination paths.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/validate-codex-action-inputs` around lines 18 - 21, Update CANONICAL_CONFIG_RE and UNLOADED_CONFIG_RE to make the destination paths optionally double-quoted, adding optional-quote matching around each destination while preserving the existing source and destination patterns.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/protected-controls.yml:
- Around line 120-154: Replace the inline actionKey-to-filename translation in
the trusted-readiness block with the shared autorelease.consumer.action_filename
helper and import its associated error type. Update the existing exception
handler around validate_readiness_record to catch that error alongside the
current exceptions, while preserving the expected_filename comparison and
approval flow.
In `@scripts/test.sh`:
- Around line 11-12: Update the policy parity-check flow around
generate-policy-lua and the lib/policy.lua diff check so the existing policy
file is preserved when generation or comparison fails. Generate into a temporary
file for comparison, or capture and restore the original content before any
failure exit; ensure cleanup occurs without overwriting uncommitted
lib/policy.lua changes.
---
Nitpick comments:
In @.github/CODEOWNERS:
- Around line 3-21: Replace the individual `@loadinglucian` owner entries in
CODEOWNERS with a team handle representing the autorelease/control owners, such
as `@org/autorelease-owners`, while preserving coverage for every listed protected
path.
In `@autorelease/admission.py`:
- Around line 111-118: Update the evidenceDigests validation in the admission
record handling so the all-elements string/SHA-256 format check runs before
comparing digests with sorted(digests). Preserve the existing non-list and
empty-list rejection behavior, and ensure mixed-type input raises the
established AdmissionError rather than allowing sorted to raise TypeError.
- Around line 417-432: Centralize generated policy rendering so the admission
gate and tests use the same layout. In autorelease/admission.py lines 417-432,
replace the local expected_policy_lines construction with the shared renderer
(such as render_policy_lua(maintained)) and compare complete text, including the
trailing newline. In test/test_autorelease.py lines 490-499, remove the local
generated_policy_lua body and return the shared renderer output so seal_fixture
and the gate cannot diverge.
In `@scripts/assert-admission-checks`:
- Around line 20-21: Replace the bare checks_file assertion in the
argument-validation flow with an explicit diagnostic before exiting when
--checks is omitted, while preserving the existing failure behavior. Apply the
identical byte-for-byte change to the corresponding php-bin copy because this
script is parity-gated through autorelease/shared-files.json.
In `@scripts/check-public-language.sh`:
- Around line 29-31: Update the xargs/grep invocation assigning matches in the
public-language check to tolerate only the expected no-match status while
propagating execution, missing-command, and read errors. Capture the command’s
exit status explicitly and allow only successful scanning outcomes, including
xargs status 123 when it represents completed grep batches, before reporting the
check as passed.
In `@scripts/validate-codex-action-inputs`:
- Around line 18-21: Update CANONICAL_CONFIG_RE and UNLOADED_CONFIG_RE to make
the destination paths optionally double-quoted, adding optional-quote matching
around each destination while preserving the existing source and destination
patterns.
In `@scripts/validate-structured-output-schemas`:
- Around line 81-91: Before iterating over schema_paths, assert that the
expected autorelease plan schema path is present, using the same normalized path
representation as schema_paths. Fail with a clear validation message when it is
absent, then retain the existing actionKey and requiredChecks parity checks in
the loop.
In `@test/test_autorelease.py`:
- Around line 240-253: Extend
test_assert_admission_checks_covers_the_plugin_contract_bucket to exercise
--require-protected-controls: add a passing fixture containing a pass-valued
Protected controls bucket and a failing fixture where that bucket is absent or
replaced by another bucket, asserting the script succeeds and fails
respectively.
- Around line 103-111: Update
test_action_key_alphabet_and_filename_have_one_definition to resolve the
repository root from pathlib.Path(__file__).resolve().parents[1], matching the
existing approach elsewhere in the file, and use that root for all admission.py
and related file reads currently using cwd-relative paths. Preserve the existing
assertions and test behavior.
- Around line 405-439: Remove the initial redundant event.write_text call before
the captures loop, and retain the later write after the loop that finalizes
preconditions. Add a brief comment near the retained write stating that
event.json must be written after capture-derived precondition digests are
settled, before eventContract is digested.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 26bba10c-ae4a-47b6-90b1-3d613c17a43d
📒 Files selected for processing (29)
.github/CODEOWNERS.github/codex/autorelease/implementation.md.github/codex/autorelease/investigation.md.github/workflows/autorelease-consumer.yml.github/workflows/ci.yml.github/workflows/e2e.yml.github/workflows/protected-controls.ymlAUTORELEASE.mdREADME.mdautorelease-events/.gitkeepautorelease/admission.pyautorelease/consumer.pyautorelease/protected-paths.jsonautorelease/shared-files.jsondocs/repository-settings.mdhooks/parse_legacy_file.lualib/policy.lualib/releases.luaschemas/autorelease-plan.schema.jsonscripts/assert-admission-checksscripts/check-public-language.shscripts/generate-policy-luascripts/prepare-agent-taskscripts/test.shscripts/validate-codex-action-inputsscripts/validate-structured-output-schemasscripts/verify-merge-admissiontest/mock_server.pytest/test_autorelease.py
💤 Files with no reviewable changes (1)
- autorelease-events/.gitkeep
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_77b8a822-9587-4153-9cb2-e80433b0c6b1) |
Hardens the consumer side of the autorelease system so new PHP branches, patch releases, and EOL delisting flow through with zero maintainer input, staying in lockstep with php-bin.
Unattended functional gaps (Phase 1)
lib/policy.luamaintained branches are snapshot-driven from php-bin's support policy instead of hardcoded; admission requires the regenerated policy.lua to match the captured snapshot.sk-secret-scanner arm and restored validator parity with php-bin.Cross-repo integrity (Phases 3-5)
autorelease/shared-files.jsondeclares the 7 byte-identical shared files; the consumer workflow verifies parity against php-bin at the pinned commit on every run, failing closed on a missing or empty manifest.ACTION_KEY_REand the action-filename mapping are single-sourced from consumer.py;verify-merge-admissiongained a subprocess smoke test.passedoutput is only set after the artifact upload it advertises.Docs (Phase 6)
All gates green: 22 unit tests, structural workflow assertions, markdownlint.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation