From 31564edfdf76acd67104a12877c03a9c38ab1241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Thu, 29 Oct 2026 02:10:37 -0300 Subject: [PATCH 1/6] fix: use the valid release App environment secret contract --- .changelog/valid-release-app-secret.md | 6 +++ .github/workflows/repository-changelog.yml | 4 +- .../workflows/repository-release-channels.yml | 2 +- docs/automation.rst | 46 +++++++++++++------ tests/Automation/WorkflowTest.php | 20 ++++++-- 5 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 .changelog/valid-release-app-secret.md diff --git a/.changelog/valid-release-app-secret.md b/.changelog/valid-release-app-secret.md new file mode 100644 index 0000000..b009bfd --- /dev/null +++ b/.changelog/valid-release-app-secret.md @@ -0,0 +1,6 @@ +--- +category: fixed +type: patch +--- + +Use the valid RELEASE_CHANNEL_APP_PRIVATE_KEY environment secret name for repository publication and release-channel authentication through a compatible Changelog workflow revision, while preserving publication-only App routing and channel selection. diff --git a/.github/workflows/repository-changelog.yml b/.github/workflows/repository-changelog.yml index 00a719e..389e468 100644 --- a/.github/workflows/repository-changelog.yml +++ b/.github/workflows/repository-changelog.yml @@ -18,7 +18,7 @@ jobs: issues: read statuses: write # Candidate API from Changelog PR76; adopt a verified compatible release before stable rollout. - uses: php-fast-forward/changelog/.github/workflows/changelog.yml@e8216f7d0df1e278f6da6a72936e2c7a5e1a2258 + uses: php-fast-forward/changelog/.github/workflows/changelog.yml@7892d45fef7ef3453172387692f9f72b9d28b6fb with: runs-on: '["ubuntu-latest"]' base-branch: main @@ -28,4 +28,4 @@ jobs: publish-app-client-id: 'Iv23liHqRng02H747IYa' publish-environment: release-channels secrets: - publish-app-private-key: ${{ secrets.app-private-key }} + publish-app-private-key: ${{ secrets.RELEASE_CHANNEL_APP_PRIVATE_KEY }} diff --git a/.github/workflows/repository-release-channels.yml b/.github/workflows/repository-release-channels.yml index 11406aa..9834893 100644 --- a/.github/workflows/repository-release-channels.yml +++ b/.github/workflows/repository-release-channels.yml @@ -47,7 +47,7 @@ jobs: uses: actions/create-github-app-token@v3.3.0 with: client-id: ${{ vars.RELEASE_CHANNEL_APP_CLIENT_ID }} - private-key: ${{ secrets.app-private-key }} + private-key: ${{ secrets.RELEASE_CHANNEL_APP_PRIVATE_KEY }} owner: php-fast-forward repositories: code-standards permission-contents: write diff --git a/docs/automation.rst b/docs/automation.rst index 5b05e9a..d74fdad 100644 --- a/docs/automation.rst +++ b/docs/automation.rst @@ -115,7 +115,7 @@ Publication-only App integration Code Standards v0.9.2 was published by ``github-actions[bot]`` through the old Changelog lifecycle's GITHUB_TOKEN fallback. That token cannot trigger the separate release workflow. The caller now selects immutable candidate -``e8216f7d0df1e278f6da6a72936e2c7a5e1a2258`` from Changelog PR76, whose actual +``7892d45fef7ef3453172387692f9f72b9d28b6fb`` from Changelog PR76, whose actual workflow API declares ``publish-app-client-id``, ``publish-environment`` and ``publish-app-private-key``. The existing published v0 revision ``203db95e68fc974e95ba1a921f08d01f88851be1`` does not declare them. @@ -123,8 +123,10 @@ workflow API declares ``publish-app-client-id``, ``publish-environment`` and The public Client ID is a non-empty literal, so an absent repository variable cannot silently select the job-token fallback. Only publication receives that App selection and ``release-channels`` environment. The caller explicitly -forwards ``secrets.app-private-key`` under the publication-only secret name; -the nested publication job uses its environment's ``app-private-key`` value. +forwards ``secrets.RELEASE_CHANNEL_APP_PRIVATE_KEY`` under the logical +``publish-app-private-key`` workflow-call name. The nested publication job declares +``RELEASE_CHANNEL_APP_PRIVATE_KEY`` so its environment supplies that same-named +secret, which the authentication step selects before the legacy logical key input. With a non-empty App ID and missing key, upstream authentication fails before publication. Managed PR authorship remains ``github-actions[bot]``; the publisher App is not required to be that PR's author. No global App credential input or @@ -140,9 +142,10 @@ adoption order is: #. Replace this candidate pin with that verified published revision before stable adoption, or obtain explicit acceptance of using the reviewed unreleased SHA. Never send the new inputs to the old v0 and assume compatibility. -#. Obtain the separate approvals for this repository's environment, sensitive key - transmission and protection rules, then apply them through supported access. - This caller patch does not create or configure those resources. +#. Verify the separately authorized configuration owner's staged environment, + valid secret name and protection rules through metadata; key validity and + actual publication remain separate evidence. This caller patch does not + create or configure those resources. #. Only after those gates, let the owner merge the caller and the next approved version PR. Observe that real publication/channel run separately; no rerun, edited release, manual release or ref mutation is used to manufacture evidence. @@ -156,13 +159,12 @@ token has Contents read. Read-only organization metadata on 2026-10-28 identifie App 5513989 (``fast-forward-release-channels``), installation 176263242, with all organization repositories selected and Contents/Workflows write, PR read and Metadata read. This includes Code Standards; no installation or permission -expansion is indicated by those metadata. Use in this repository still requires -separate approval. Code Standards has no ``release-channels`` environment or -available Actions secrets/variables in the checked metadata. No private key was -read, and no key's validity or token minting was tested. Creating that environment -and transmitting a key as ``app-private-key`` remain separately authorized actions; -GitHub cannot recover an existing secret value for migration. Changelog's approval -does not authorize that transmission or use here. +expansion is indicated by those metadata. The configuration owner subsequently staged the authorized environment separately. +Read-only metadata on 2026-10-29 confirms ``release-channels`` and the valid +``RELEASE_CHANNEL_APP_PRIVATE_KEY`` secret name. This code task did not read the +value, mint a token or modify settings; name metadata does not establish key +validity or end-to-end event delivery. GitHub cannot recover an existing secret +value for migration. The activation plan preserves branches, without branch-to-tag migration: @@ -224,3 +226,21 @@ and `runner access from reusable workflows `__. The fork fallback is this reusable's stricter policy, not a claim that custom runner labels themselves establish trust or isolation. + +Environment secret identifier correction +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +GitHub rejected ``app-private-key`` as a repository/environment secret name with +HTTP 422: those identifiers accept alphanumeric characters and underscores. +Hyphens remain valid in logical reusable-workflow inputs; these are separate +contracts. The compatible candidate adds the valid nested environment-secret +declaration ``RELEASE_CHANNEL_APP_PRIVATE_KEY`` while preserving the public +``publish-app-private-key`` logical input. The channel and caller now read the +valid environment identifier. This code change does not create or read any key. + +This defect is independent of the version-PR preparation failure on 2026-10-29. +The latter was reproduced before writes: provenance resolution tried to read +the original squash-merged PR94 head, absent from the main-only Git checkout. +Fetching that exact object into an isolated fixture changed the same planner +from refused to dry-run. Its provider correction and managed PR98 recovery are +separate; changing a secret identifier does not repair or retry that transaction. diff --git a/tests/Automation/WorkflowTest.php b/tests/Automation/WorkflowTest.php index 51e932c..0507bf3 100644 --- a/tests/Automation/WorkflowTest.php +++ b/tests/Automation/WorkflowTest.php @@ -42,7 +42,7 @@ public function testExternalActionsUsePublishedMajorChannels(): void 'actions/github-script@v8', 'actions/checkout@v7', 'shivammathur/setup-php@v2', - 'php-fast-forward/changelog/.github/workflows/changelog.yml@e8216f7d0df1e278f6da6a72936e2c7a5e1a2258', + 'php-fast-forward/changelog/.github/workflows/changelog.yml@7892d45fef7ef3453172387692f9f72b9d28b6fb', 'php-fast-forward/code-standards/.github/workflows/code-standards.yml@v0', ]; $files = [ @@ -63,16 +63,28 @@ public function testExternalActionsUsePublishedMajorChannels(): void public function testChangelogCallerSelectsTheCompatiblePublicationOnlyApp(): void { $workflow = file_get_contents(dirname(__DIR__, 2) . '/.github/workflows/repository-changelog.yml'); - self::assertStringContainsString('changelog.yml@e8216f7d0df1e278f6da6a72936e2c7a5e1a2258', $workflow); + self::assertStringContainsString('changelog.yml@7892d45fef7ef3453172387692f9f72b9d28b6fb', $workflow); self::assertStringContainsString("publish-app-client-id: 'Iv23liHqRng02H747IYa'", $workflow); self::assertStringContainsString('publish-environment: release-channels', $workflow); - self::assertStringContainsString('publish-app-private-key: ${{ secrets.app-private-key }}', $workflow); + self::assertStringContainsString('publish-app-private-key: ${{ secrets.RELEASE_CHANNEL_APP_PRIVATE_KEY }}', $workflow); self::assertDoesNotMatchRegularExpression('/^\s+app-client-id:/m', $workflow); self::assertDoesNotMatchRegularExpression('/^\s+app-private-key:/m', $workflow); self::assertStringNotContainsString('secrets: inherit', $workflow); self::assertStringNotContainsString('automation-actor:', $workflow); } + /** Uses one deployable environment secret identifier rather than a logical workflow-only input name. */ + public function testReleaseAppEnvironmentSecretNameIsValidAndShared(): void + { + $root = dirname(__DIR__, 2) . '/.github/workflows/'; + foreach (['repository-changelog.yml', 'repository-release-channels.yml'] as $file) { + $workflow = file_get_contents($root . $file); + self::assertStringContainsString('secrets.RELEASE_CHANNEL_APP_PRIVATE_KEY', $workflow); + self::assertStringNotContainsString('secrets.app-private-key', $workflow); + } + self::assertMatchesRegularExpression('/^[A-Z_][A-Z0-9_]*$/', 'RELEASE_CHANNEL_APP_PRIVATE_KEY'); + } + /** Reserves promotion for genuine published/edited stable release events with one serialized writer. */ public function testReleaseChannelsUseTheMajorActionOnPublishedAndEditedReleases(): void { @@ -92,7 +104,7 @@ public function testReleaseChannelsUseTheMajorActionOnPublishedAndEditedReleases self::assertStringContainsString('environment: release-channels', $workflow); self::assertStringContainsString('repositories: code-standards', $workflow); self::assertStringContainsString('token: ${{ steps.channel-app.outputs.token }}', $workflow); - self::assertStringContainsString('private-key: ${{ secrets.app-private-key }}', $workflow); + self::assertStringContainsString('private-key: ${{ secrets.RELEASE_CHANNEL_APP_PRIVATE_KEY }}', $workflow); self::assertStringContainsString('permission-contents: write', $workflow); self::assertStringContainsString('permission-workflows: write', $workflow); self::assertStringContainsString("skip-token-revoke: 'false'", $workflow); From e1a301a264541d82bd278b95cddd91545931a1dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Thu, 29 Oct 2026 09:18:42 -0300 Subject: [PATCH 2/6] fix: adopt the combined version runtime on a preserved recovery branch --- .changelog/refresh-version-runtime.md | 9 ++++ .github/workflows/repository-changelog.yml | 5 +- .../workflows/repository-release-channels.yml | 2 +- docs/automation.rst | 51 +++++++++++++++---- tests/Automation/WorkflowTest.php | 11 ++-- 5 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 .changelog/refresh-version-runtime.md diff --git a/.changelog/refresh-version-runtime.md b/.changelog/refresh-version-runtime.md new file mode 100644 index 0000000..208d1b8 --- /dev/null +++ b/.changelog/refresh-version-runtime.md @@ -0,0 +1,9 @@ +--- +category: fixed +type: patch +--- + +Refreshes version preparation with verified original implementation PR objects +and a new exclusive managed branch, preserving the human-merged PR98 head and +strict signed Bot ownership. Selects the verified +full v2.0.3 actions-tagger release tag for channel updates. diff --git a/.github/workflows/repository-changelog.yml b/.github/workflows/repository-changelog.yml index 389e468..c0a7e00 100644 --- a/.github/workflows/repository-changelog.yml +++ b/.github/workflows/repository-changelog.yml @@ -17,11 +17,12 @@ jobs: pull-requests: write issues: read statuses: write - # Candidate API from Changelog PR76; adopt a verified compatible release before stable rollout. - uses: php-fast-forward/changelog/.github/workflows/changelog.yml@7892d45fef7ef3453172387692f9f72b9d28b6fb + # Reviewed integration candidate combines publication API and original-PR provenance fixes. + uses: php-fast-forward/changelog/.github/workflows/changelog.yml@9cac29abac1f9ea77ae4f80c91de662743c7728c with: runs-on: '["ubuntu-latest"]' base-branch: main + managed-branch: changelog/version-recovery-98 source: tags prepare-release: true publish: true diff --git a/.github/workflows/repository-release-channels.yml b/.github/workflows/repository-release-channels.yml index 9834893..7cfb2e8 100644 --- a/.github/workflows/repository-release-channels.yml +++ b/.github/workflows/repository-release-channels.yml @@ -55,7 +55,7 @@ jobs: skip-token-revoke: 'false' - name: Update the released major branch if: steps.release.outputs.promote == 'true' - uses: Actions-R-Us/actions-tagger@v2 + uses: Actions-R-Us/actions-tagger@v2.0.3 with: token: ${{ steps.channel-app.outputs.token }} prefer_branch_releases: 'true' diff --git a/docs/automation.rst b/docs/automation.rst index d74fdad..93ae6c0 100644 --- a/docs/automation.rst +++ b/docs/automation.rst @@ -72,11 +72,13 @@ immutable release inputs for Composer/Packagist. ``latest`` is not a numeric stable Composer version: the installed Composer version parser rejects it as a version, unlike ``v0`` and ``v0.4.2``. No step exists solely to make latest a branch. -The candidate workflow uses ``Actions-R-Us/actions-tagger@v2`` on real -``release: published`` and ``release: edited`` events, with -``prefer_branch_releases: true``. On 2026-10-28, ``@v2`` resolved to -``330ddfac760021349fef7ff62b372f2f691c20fb`` (v2.0.3). It is a moving major channel, -not an immutable pin. The distributed bundle was tested against a local API +The candidate workflow uses the published full tag +``Actions-R-Us/actions-tagger@v2.0.3`` on real ``release: published`` and +``release: edited`` events, with ``prefer_branch_releases: true``. On 2026-10-29, +the published, non-draft, stable v2.0.3 release and its tag were verified; the tag +resolved to ``330ddfac760021349fef7ff62b372f2f691c20fb``. The workflow deliberately +selects that full version tag; an upstream tag rewrite can still replace its code. +The distributed bundle was tested against a local API returning actual branch names for branch queries. Alone, it force-downgrades ``v0`` when an older release is edited and can move ``latest`` backwards even after 1.x. Its latest ref is hardcoded as a tag in branch mode. @@ -115,7 +117,8 @@ Publication-only App integration Code Standards v0.9.2 was published by ``github-actions[bot]`` through the old Changelog lifecycle's GITHUB_TOKEN fallback. That token cannot trigger the separate release workflow. The caller now selects immutable candidate -``7892d45fef7ef3453172387692f9f72b9d28b6fb`` from Changelog PR76, whose actual +``9cac29abac1f9ea77ae4f80c91de662743c7728c`` combining Changelog PR76 and PR77 +in the review-only integration PR78. Its actual workflow API declares ``publish-app-client-id``, ``publish-environment`` and ``publish-app-private-key``. The existing published v0 revision ``203db95e68fc974e95ba1a921f08d01f88851be1`` does not declare them. @@ -126,18 +129,20 @@ App selection and ``release-channels`` environment. The caller explicitly forwards ``secrets.RELEASE_CHANNEL_APP_PRIVATE_KEY`` under the logical ``publish-app-private-key`` workflow-call name. The nested publication job declares ``RELEASE_CHANNEL_APP_PRIVATE_KEY`` so its environment supplies that same-named -secret, which the authentication step selects before the legacy logical key input. +secret. Authentication selects a key only with an App Client ID and prefers the +explicit logical key before the environment key. Token mode ignores the staged +environment key; a missing publication key never borrows the legacy App key. With a non-empty App ID and missing key, upstream authentication fails before publication. Managed PR authorship remains ``github-actions[bot]``; the publisher App is not required to be that PR's author. No global App credential input or -``secrets: inherit`` is added, and the release-channel workflow is unchanged. +``secrets: inherit`` is added, and the release-channel selector remains unchanged. **This is a blocked draft integration, not an activated release path.** The SHA is an inspectable PR candidate, not a published Changelog version. The compatible adoption order is: -#. Review and qualify Changelog PR76's publication-only API, then let the owner - merge/publish a compatible release. Verify that released revision actually +#. Review and qualify both upstream changes and the integrated candidate. + Let the owner merge/publish a compatible release. Verify that released revision actually contains the three inputs and nested publication/authentication routing. #. Replace this candidate pin with that verified published revision before stable adoption, or obtain explicit acceptance of using the reviewed unreleased SHA. @@ -244,3 +249,29 @@ the original squash-merged PR94 head, absent from the main-only Git checkout. Fetching that exact object into an isolated fixture changed the same planner from refused to dry-run. Its provider correction and managed PR98 recovery are separate; changing a secret identifier does not repair or retry that transaction. + +Version preparation recovery +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The original ``changelog/version`` branch of PR98 ends at the human merge +``48119d8d4bf3b77d488eba12b1f0515453b3f371``. Its content is the exact automatic +merge of the signed Bot head and the then-current main: the remaining managed +patch is byte-identical to the Bot patch, and the human delta is byte-identical +to the integrated main delta. This preserves the human work but does not grant +the human commit signed Bot ownership. No trailers are forged or waived. + +The caller reserves ``changelog/version-recovery-98`` for fresh automation. After +this reviewed caller and a compatible corrected runtime are integrated, the next +trusted main event plans from the fresh main and creates a new signed Bot commit +and version PR on that exclusive branch. The old branch, commit, PR body and +review history remain available. Do not reset, force-update or delete PR98's +branch. If either the fresh main or the reserved recovery branch changes during +preparation, fail closed and re-inspect before proceeding. Close the superseded +PR only after separate authorization and verification of the replacement. + +A version PR on the recovery branch still requires successful current-head +checks, ownership proof and review before an approved merge/publication. This +configuration change prepares recovery; it does not create that PR or publish a +release. A missing original squash PR object is repaired through exact verified +object acquisition, never by accepting an unproved association or fetching every +PR ref. diff --git a/tests/Automation/WorkflowTest.php b/tests/Automation/WorkflowTest.php index 0507bf3..21dd661 100644 --- a/tests/Automation/WorkflowTest.php +++ b/tests/Automation/WorkflowTest.php @@ -34,15 +34,15 @@ public function testConsumerCallerDeclaresOnlyPullRequestsWithoutRemovingQuality } /** Keeps external Actions on verified published channels or reviewed release commits. */ - public function testExternalActionsUsePublishedMajorChannels(): void + public function testExternalActionsUseVerifiedPublishedReferences(): void { $references = [ - 'Actions-R-Us/actions-tagger@v2', + 'Actions-R-Us/actions-tagger@v2.0.3', 'actions/create-github-app-token@v3.3.0', 'actions/github-script@v8', 'actions/checkout@v7', 'shivammathur/setup-php@v2', - 'php-fast-forward/changelog/.github/workflows/changelog.yml@7892d45fef7ef3453172387692f9f72b9d28b6fb', + 'php-fast-forward/changelog/.github/workflows/changelog.yml@9cac29abac1f9ea77ae4f80c91de662743c7728c', 'php-fast-forward/code-standards/.github/workflows/code-standards.yml@v0', ]; $files = [ @@ -63,9 +63,10 @@ public function testExternalActionsUsePublishedMajorChannels(): void public function testChangelogCallerSelectsTheCompatiblePublicationOnlyApp(): void { $workflow = file_get_contents(dirname(__DIR__, 2) . '/.github/workflows/repository-changelog.yml'); - self::assertStringContainsString('changelog.yml@7892d45fef7ef3453172387692f9f72b9d28b6fb', $workflow); + self::assertStringContainsString('changelog.yml@9cac29abac1f9ea77ae4f80c91de662743c7728c', $workflow); self::assertStringContainsString("publish-app-client-id: 'Iv23liHqRng02H747IYa'", $workflow); self::assertStringContainsString('publish-environment: release-channels', $workflow); + self::assertStringContainsString('managed-branch: changelog/version-recovery-98', $workflow); self::assertStringContainsString('publish-app-private-key: ${{ secrets.RELEASE_CHANNEL_APP_PRIVATE_KEY }}', $workflow); self::assertDoesNotMatchRegularExpression('/^\s+app-client-id:/m', $workflow); self::assertDoesNotMatchRegularExpression('/^\s+app-private-key:/m', $workflow); @@ -86,7 +87,7 @@ public function testReleaseAppEnvironmentSecretNameIsValidAndShared(): void } /** Reserves promotion for genuine published/edited stable release events with one serialized writer. */ - public function testReleaseChannelsUseTheMajorActionOnPublishedAndEditedReleases(): void + public function testReleaseChannelsUseTheFullVersionActionOnPublishedAndEditedReleases(): void { $root = dirname(__DIR__, 2); $workflow = file_get_contents($root . '/.github/workflows/repository-release-channels.yml'); From d5f840fdaf5a210a238f8277de8f5fdbb413c788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Thu, 29 Oct 2026 09:39:55 -0300 Subject: [PATCH 3/6] fix: qualify authenticated provenance in the combined runtime --- .changelog/refresh-version-runtime.md | 2 +- .github/workflows/repository-changelog.yml | 2 +- docs/automation.rst | 6 ++++-- tests/Automation/WorkflowTest.php | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.changelog/refresh-version-runtime.md b/.changelog/refresh-version-runtime.md index 208d1b8..7b4da9f 100644 --- a/.changelog/refresh-version-runtime.md +++ b/.changelog/refresh-version-runtime.md @@ -3,7 +3,7 @@ category: fixed type: patch --- -Refreshes version preparation with verified original implementation PR objects +Refreshes version preparation with authenticated, verified original implementation PR objects and a new exclusive managed branch, preserving the human-merged PR98 head and strict signed Bot ownership. Selects the verified full v2.0.3 actions-tagger release tag for channel updates. diff --git a/.github/workflows/repository-changelog.yml b/.github/workflows/repository-changelog.yml index c0a7e00..fe1697e 100644 --- a/.github/workflows/repository-changelog.yml +++ b/.github/workflows/repository-changelog.yml @@ -18,7 +18,7 @@ jobs: issues: read statuses: write # Reviewed integration candidate combines publication API and original-PR provenance fixes. - uses: php-fast-forward/changelog/.github/workflows/changelog.yml@9cac29abac1f9ea77ae4f80c91de662743c7728c + uses: php-fast-forward/changelog/.github/workflows/changelog.yml@3e51ef2b53c7c176353ba4ff4c7467caf24f0c59 with: runs-on: '["ubuntu-latest"]' base-branch: main diff --git a/docs/automation.rst b/docs/automation.rst index 93ae6c0..5a6d714 100644 --- a/docs/automation.rst +++ b/docs/automation.rst @@ -117,7 +117,7 @@ Publication-only App integration Code Standards v0.9.2 was published by ``github-actions[bot]`` through the old Changelog lifecycle's GITHUB_TOKEN fallback. That token cannot trigger the separate release workflow. The caller now selects immutable candidate -``9cac29abac1f9ea77ae4f80c91de662743c7728c`` combining Changelog PR76 and PR77 +``3e51ef2b53c7c176353ba4ff4c7467caf24f0c59`` combining Changelog PR76 and PR77 in the review-only integration PR78. Its actual workflow API declares ``publish-app-client-id``, ``publish-environment`` and ``publish-app-private-key``. The existing published v0 revision @@ -273,5 +273,7 @@ A version PR on the recovery branch still requires successful current-head checks, ownership proof and review before an approved merge/publication. This configuration change prepares recovery; it does not create that PR or publish a release. A missing original squash PR object is repaired through exact verified -object acquisition, never by accepting an unproved association or fetching every +object acquisition. The HTTP boundary authenticates the pack request; local Git +receives only credential-free pack bytes and never a token, credential helper +or network-fetch command. Never accept an unproved association or fetch every PR ref. diff --git a/tests/Automation/WorkflowTest.php b/tests/Automation/WorkflowTest.php index 21dd661..82aa374 100644 --- a/tests/Automation/WorkflowTest.php +++ b/tests/Automation/WorkflowTest.php @@ -42,7 +42,7 @@ public function testExternalActionsUseVerifiedPublishedReferences(): void 'actions/github-script@v8', 'actions/checkout@v7', 'shivammathur/setup-php@v2', - 'php-fast-forward/changelog/.github/workflows/changelog.yml@9cac29abac1f9ea77ae4f80c91de662743c7728c', + 'php-fast-forward/changelog/.github/workflows/changelog.yml@3e51ef2b53c7c176353ba4ff4c7467caf24f0c59', 'php-fast-forward/code-standards/.github/workflows/code-standards.yml@v0', ]; $files = [ @@ -63,7 +63,7 @@ public function testExternalActionsUseVerifiedPublishedReferences(): void public function testChangelogCallerSelectsTheCompatiblePublicationOnlyApp(): void { $workflow = file_get_contents(dirname(__DIR__, 2) . '/.github/workflows/repository-changelog.yml'); - self::assertStringContainsString('changelog.yml@9cac29abac1f9ea77ae4f80c91de662743c7728c', $workflow); + self::assertStringContainsString('changelog.yml@3e51ef2b53c7c176353ba4ff4c7467caf24f0c59', $workflow); self::assertStringContainsString("publish-app-client-id: 'Iv23liHqRng02H747IYa'", $workflow); self::assertStringContainsString('publish-environment: release-channels', $workflow); self::assertStringContainsString('managed-branch: changelog/version-recovery-98', $workflow); From 7e8587e6dfae73136592f5cdf319ccae589202f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Thu, 29 Oct 2026 10:00:38 -0300 Subject: [PATCH 4/6] docs: adopt the corrected publication key reference --- .github/workflows/repository-changelog.yml | 2 +- docs/automation.rst | 2 +- tests/Automation/WorkflowTest.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/repository-changelog.yml b/.github/workflows/repository-changelog.yml index fe1697e..a5aee18 100644 --- a/.github/workflows/repository-changelog.yml +++ b/.github/workflows/repository-changelog.yml @@ -18,7 +18,7 @@ jobs: issues: read statuses: write # Reviewed integration candidate combines publication API and original-PR provenance fixes. - uses: php-fast-forward/changelog/.github/workflows/changelog.yml@3e51ef2b53c7c176353ba4ff4c7467caf24f0c59 + uses: php-fast-forward/changelog/.github/workflows/changelog.yml@4fe3cb26a252b7c41758c50ffcfe5391bf93a60a with: runs-on: '["ubuntu-latest"]' base-branch: main diff --git a/docs/automation.rst b/docs/automation.rst index 5a6d714..b61164d 100644 --- a/docs/automation.rst +++ b/docs/automation.rst @@ -117,7 +117,7 @@ Publication-only App integration Code Standards v0.9.2 was published by ``github-actions[bot]`` through the old Changelog lifecycle's GITHUB_TOKEN fallback. That token cannot trigger the separate release workflow. The caller now selects immutable candidate -``3e51ef2b53c7c176353ba4ff4c7467caf24f0c59`` combining Changelog PR76 and PR77 +``4fe3cb26a252b7c41758c50ffcfe5391bf93a60a`` combining Changelog PR76 and PR77 in the review-only integration PR78. Its actual workflow API declares ``publish-app-client-id``, ``publish-environment`` and ``publish-app-private-key``. The existing published v0 revision diff --git a/tests/Automation/WorkflowTest.php b/tests/Automation/WorkflowTest.php index 82aa374..2699a83 100644 --- a/tests/Automation/WorkflowTest.php +++ b/tests/Automation/WorkflowTest.php @@ -42,7 +42,7 @@ public function testExternalActionsUseVerifiedPublishedReferences(): void 'actions/github-script@v8', 'actions/checkout@v7', 'shivammathur/setup-php@v2', - 'php-fast-forward/changelog/.github/workflows/changelog.yml@3e51ef2b53c7c176353ba4ff4c7467caf24f0c59', + 'php-fast-forward/changelog/.github/workflows/changelog.yml@4fe3cb26a252b7c41758c50ffcfe5391bf93a60a', 'php-fast-forward/code-standards/.github/workflows/code-standards.yml@v0', ]; $files = [ @@ -63,7 +63,7 @@ public function testExternalActionsUseVerifiedPublishedReferences(): void public function testChangelogCallerSelectsTheCompatiblePublicationOnlyApp(): void { $workflow = file_get_contents(dirname(__DIR__, 2) . '/.github/workflows/repository-changelog.yml'); - self::assertStringContainsString('changelog.yml@3e51ef2b53c7c176353ba4ff4c7467caf24f0c59', $workflow); + self::assertStringContainsString('changelog.yml@4fe3cb26a252b7c41758c50ffcfe5391bf93a60a', $workflow); self::assertStringContainsString("publish-app-client-id: 'Iv23liHqRng02H747IYa'", $workflow); self::assertStringContainsString('publish-environment: release-channels', $workflow); self::assertStringContainsString('managed-branch: changelog/version-recovery-98', $workflow); From 30cc9ac4c5333b9f7c76d72b269f5cfb12f6765d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Thu, 29 Oct 2026 11:32:28 -0300 Subject: [PATCH 5/6] fix: pin the verified release channel action commit --- .changelog/refresh-version-runtime.md | 2 +- .github/workflows/repository-changelog.yml | 2 +- .github/workflows/repository-release-channels.yml | 2 +- docs/automation.rst | 15 ++++++++------- tests/Automation/WorkflowTest.php | 6 +++--- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.changelog/refresh-version-runtime.md b/.changelog/refresh-version-runtime.md index 7b4da9f..db76fa5 100644 --- a/.changelog/refresh-version-runtime.md +++ b/.changelog/refresh-version-runtime.md @@ -6,4 +6,4 @@ type: patch Refreshes version preparation with authenticated, verified original implementation PR objects and a new exclusive managed branch, preserving the human-merged PR98 head and strict signed Bot ownership. Selects the verified -full v2.0.3 actions-tagger release tag for channel updates. +immutable actions-tagger commit verified for v2.0.3 for channel updates. diff --git a/.github/workflows/repository-changelog.yml b/.github/workflows/repository-changelog.yml index a5aee18..b8210ae 100644 --- a/.github/workflows/repository-changelog.yml +++ b/.github/workflows/repository-changelog.yml @@ -18,7 +18,7 @@ jobs: issues: read statuses: write # Reviewed integration candidate combines publication API and original-PR provenance fixes. - uses: php-fast-forward/changelog/.github/workflows/changelog.yml@4fe3cb26a252b7c41758c50ffcfe5391bf93a60a + uses: php-fast-forward/changelog/.github/workflows/changelog.yml@41020df0dcd4cb9a2b990f54077553deefbd7608 with: runs-on: '["ubuntu-latest"]' base-branch: main diff --git a/.github/workflows/repository-release-channels.yml b/.github/workflows/repository-release-channels.yml index 7cfb2e8..9c7f972 100644 --- a/.github/workflows/repository-release-channels.yml +++ b/.github/workflows/repository-release-channels.yml @@ -55,7 +55,7 @@ jobs: skip-token-revoke: 'false' - name: Update the released major branch if: steps.release.outputs.promote == 'true' - uses: Actions-R-Us/actions-tagger@v2.0.3 + uses: Actions-R-Us/actions-tagger@330ddfac760021349fef7ff62b372f2f691c20fb # v2.0.3 with: token: ${{ steps.channel-app.outputs.token }} prefer_branch_releases: 'true' diff --git a/docs/automation.rst b/docs/automation.rst index b61164d..82e7ac8 100644 --- a/docs/automation.rst +++ b/docs/automation.rst @@ -72,12 +72,13 @@ immutable release inputs for Composer/Packagist. ``latest`` is not a numeric stable Composer version: the installed Composer version parser rejects it as a version, unlike ``v0`` and ``v0.4.2``. No step exists solely to make latest a branch. -The candidate workflow uses the published full tag -``Actions-R-Us/actions-tagger@v2.0.3`` on real ``release: published`` and -``release: edited`` events, with ``prefer_branch_releases: true``. On 2026-10-29, -the published, non-draft, stable v2.0.3 release and its tag were verified; the tag -resolved to ``330ddfac760021349fef7ff62b372f2f691c20fb``. The workflow deliberately -selects that full version tag; an upstream tag rewrite can still replace its code. +The candidate workflow pins ``Actions-R-Us/actions-tagger`` to immutable commit +``330ddfac760021349fef7ff62b372f2f691c20fb`` with ``v2.0.3`` as its version comment. +The maintainer selected this pin after reviewing the moving-tag security finding. +The published, non-draft, stable v2.0.3 release and its tag were verified on +2026-10-29; the tag resolved to that exact commit. The workflow runs on real +``release: published`` and ``release: edited`` events with +``prefer_branch_releases: true``. The distributed bundle was tested against a local API returning actual branch names for branch queries. Alone, it force-downgrades ``v0`` when an older release is edited and can move ``latest`` backwards even @@ -117,7 +118,7 @@ Publication-only App integration Code Standards v0.9.2 was published by ``github-actions[bot]`` through the old Changelog lifecycle's GITHUB_TOKEN fallback. That token cannot trigger the separate release workflow. The caller now selects immutable candidate -``4fe3cb26a252b7c41758c50ffcfe5391bf93a60a`` combining Changelog PR76 and PR77 +``41020df0dcd4cb9a2b990f54077553deefbd7608`` combining Changelog PR76 and PR77 in the review-only integration PR78. Its actual workflow API declares ``publish-app-client-id``, ``publish-environment`` and ``publish-app-private-key``. The existing published v0 revision diff --git a/tests/Automation/WorkflowTest.php b/tests/Automation/WorkflowTest.php index 2699a83..0ce0d47 100644 --- a/tests/Automation/WorkflowTest.php +++ b/tests/Automation/WorkflowTest.php @@ -37,12 +37,12 @@ public function testConsumerCallerDeclaresOnlyPullRequestsWithoutRemovingQuality public function testExternalActionsUseVerifiedPublishedReferences(): void { $references = [ - 'Actions-R-Us/actions-tagger@v2.0.3', + 'Actions-R-Us/actions-tagger@330ddfac760021349fef7ff62b372f2f691c20fb', 'actions/create-github-app-token@v3.3.0', 'actions/github-script@v8', 'actions/checkout@v7', 'shivammathur/setup-php@v2', - 'php-fast-forward/changelog/.github/workflows/changelog.yml@4fe3cb26a252b7c41758c50ffcfe5391bf93a60a', + 'php-fast-forward/changelog/.github/workflows/changelog.yml@41020df0dcd4cb9a2b990f54077553deefbd7608', 'php-fast-forward/code-standards/.github/workflows/code-standards.yml@v0', ]; $files = [ @@ -63,7 +63,7 @@ public function testExternalActionsUseVerifiedPublishedReferences(): void public function testChangelogCallerSelectsTheCompatiblePublicationOnlyApp(): void { $workflow = file_get_contents(dirname(__DIR__, 2) . '/.github/workflows/repository-changelog.yml'); - self::assertStringContainsString('changelog.yml@4fe3cb26a252b7c41758c50ffcfe5391bf93a60a', $workflow); + self::assertStringContainsString('changelog.yml@41020df0dcd4cb9a2b990f54077553deefbd7608', $workflow); self::assertStringContainsString("publish-app-client-id: 'Iv23liHqRng02H747IYa'", $workflow); self::assertStringContainsString('publish-environment: release-channels', $workflow); self::assertStringContainsString('managed-branch: changelog/version-recovery-98', $workflow); From 7ea21cdefccfd105ef6e00928dae8c6155023cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Thu, 29 Oct 2026 11:39:04 -0300 Subject: [PATCH 6/6] fix: adopt the reviewed publication credential guard --- .github/workflows/repository-changelog.yml | 2 +- docs/automation.rst | 7 ++++--- tests/Automation/WorkflowTest.php | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/repository-changelog.yml b/.github/workflows/repository-changelog.yml index b8210ae..32fec81 100644 --- a/.github/workflows/repository-changelog.yml +++ b/.github/workflows/repository-changelog.yml @@ -18,7 +18,7 @@ jobs: issues: read statuses: write # Reviewed integration candidate combines publication API and original-PR provenance fixes. - uses: php-fast-forward/changelog/.github/workflows/changelog.yml@41020df0dcd4cb9a2b990f54077553deefbd7608 + uses: php-fast-forward/changelog/.github/workflows/changelog.yml@42bc58fbab55596ee03d480ebf71d2a3dfc48fb4 with: runs-on: '["ubuntu-latest"]' base-branch: main diff --git a/docs/automation.rst b/docs/automation.rst index 82e7ac8..843a2e7 100644 --- a/docs/automation.rst +++ b/docs/automation.rst @@ -118,7 +118,7 @@ Publication-only App integration Code Standards v0.9.2 was published by ``github-actions[bot]`` through the old Changelog lifecycle's GITHUB_TOKEN fallback. That token cannot trigger the separate release workflow. The caller now selects immutable candidate -``41020df0dcd4cb9a2b990f54077553deefbd7608`` combining Changelog PR76 and PR77 +``42bc58fbab55596ee03d480ebf71d2a3dfc48fb4`` combining Changelog PR76 and PR77 in the review-only integration PR78. Its actual workflow API declares ``publish-app-client-id``, ``publish-environment`` and ``publish-app-private-key``. The existing published v0 revision @@ -130,8 +130,9 @@ App selection and ``release-channels`` environment. The caller explicitly forwards ``secrets.RELEASE_CHANNEL_APP_PRIVATE_KEY`` under the logical ``publish-app-private-key`` workflow-call name. The nested publication job declares ``RELEASE_CHANNEL_APP_PRIVATE_KEY`` so its environment supplies that same-named -secret. Authentication selects a key only with an App Client ID and prefers the -explicit logical key before the environment key. Token mode ignores the staged +secret. Authentication preserves an explicit logical key so incomplete App credentials +are rejected. It considers the environment key only with a selected App Client +ID and no explicit key. Token mode ignores the staged environment key; a missing publication key never borrows the legacy App key. With a non-empty App ID and missing key, upstream authentication fails before publication. Managed PR authorship remains ``github-actions[bot]``; the publisher diff --git a/tests/Automation/WorkflowTest.php b/tests/Automation/WorkflowTest.php index 0ce0d47..262400d 100644 --- a/tests/Automation/WorkflowTest.php +++ b/tests/Automation/WorkflowTest.php @@ -42,7 +42,7 @@ public function testExternalActionsUseVerifiedPublishedReferences(): void 'actions/github-script@v8', 'actions/checkout@v7', 'shivammathur/setup-php@v2', - 'php-fast-forward/changelog/.github/workflows/changelog.yml@41020df0dcd4cb9a2b990f54077553deefbd7608', + 'php-fast-forward/changelog/.github/workflows/changelog.yml@42bc58fbab55596ee03d480ebf71d2a3dfc48fb4', 'php-fast-forward/code-standards/.github/workflows/code-standards.yml@v0', ]; $files = [ @@ -63,7 +63,7 @@ public function testExternalActionsUseVerifiedPublishedReferences(): void public function testChangelogCallerSelectsTheCompatiblePublicationOnlyApp(): void { $workflow = file_get_contents(dirname(__DIR__, 2) . '/.github/workflows/repository-changelog.yml'); - self::assertStringContainsString('changelog.yml@41020df0dcd4cb9a2b990f54077553deefbd7608', $workflow); + self::assertStringContainsString('changelog.yml@42bc58fbab55596ee03d480ebf71d2a3dfc48fb4', $workflow); self::assertStringContainsString("publish-app-client-id: 'Iv23liHqRng02H747IYa'", $workflow); self::assertStringContainsString('publish-environment: release-channels', $workflow); self::assertStringContainsString('managed-branch: changelog/version-recovery-98', $workflow);