Skip to content

Default-branch recovery retains release intent and immutable artifacts - #531

Open
Marius Storhaug (MariusStorhaug) wants to merge 12 commits into
mainfrom
fix-dispatch-version-label
Open

Marius Storhaug (MariusStorhaug) wants to merge 12 commits into
mainfrom
fix-dispatch-version-label

Conversation

@MariusStorhaug

@MariusStorhaug Marius Storhaug (MariusStorhaug) commented Sep 2, 2026 •

Copy link
Copy Markdown
Member

Default-branch recovery retains the merged pull request's configured bump or skip intent and prevents a new dispatch from computing another version for an already-released commit. The bounded candidate rejects recognized prerelease ownership conflicts and unsafe GitHub asset replacement. This unmerged draft still has terminal-provenance and cleanup-mutation limits and depends on separately coordinated Gallery ownership/pre-publication safety work.

Fixed: Recovery retains the intended source, version, and artifact

A default-branch push and recovery dispatch use the same exact merged-PR association. Valid direct releases remain distinct. An unmatched association stops a release-capable event, not a documentation-only push; lookup errors propagate. Default-branch detection and associated PR base refs are case-sensitive full branch identities, so a similarly named tag or differently cased branch cannot select stable publication.

Normal framework callers need no configuration or invocation edit beyond selecting the eventual compatible release after the stated prerequisites are satisfied. Before retrying, retain the original run's resolved version/tag, source, and tested artifact. A tag must be unclaimed or resolve to that source, including annotated tags. Dispatch uses only the current default-branch tip; for older content or a commit that already has a GitHub release, rerun the original failed publication job rather than resolve another bump. Conflicting or unverifiable ownership requires reviewed forward-only reconciliation, never retagging or overwriting a version. This draft is not a production dependency.

The bounded GitHub publisher verifies tag/source affinity even when a release is absent and rechecks before upload. Missing uploads may resume for the original source. Existing bytes are retained only when their published SHA-256 digest matches; differing, ambiguous, or unverifiable assets fail. Uploads never use --clobber. The optional existing CommitSha input and original GITHUB_SHA context remain the source contract.

Prerelease creation and retry use the same immutable build SHA, not a mutable head branch. Complete authored or generated notes remain intact, followed by a controller-owned v1 provenance footer for the exact base repository, PR number, and source. Recognized existing footer evidence must agree with that producing context before resume/upload. Repository identity is case-insensitive; source identity is exact. Recognized conflicts stop before creation, upload, PR comment, or success output. Matching-owner missing-upload retries remain supported.

Remaining provenance limit: The frozen reader recognizes only a single-line terminal v1 comment. Wrapped or unsupported terminal comments return no evidence, and the known-owner-only guard can skip them. The exact-head owner assessment confirms this gap affects the candidate. The canonical terminal-comment correction and negative publisher coverage must come from the safety delivery or an explicitly authorized bounded correction; this PR does not claim unreadable provenance is safely handled.

Cleanup selection verifies producing-PR provenance and the actual tag commit. Another PR's records, conflicting sources, stable releases, and the currently published tag are excluded; legacy records without readable producing-PR/source proof are retained. Adopting this version does not rewrite or bulk-delete those records. WhatIf remains non-mutating.

Remaining mutation limit: Selection stores tag names, and the deletion loop does not freshly revalidate producing context, release identity, or tag/source ownership before gh release delete --cleanup-tag. A replacement claim after selection can reach deletion. Do not treat source-bound selection as concurrent-cleanup clearance. The coordinated correction must validate mutation identity and retain a tag when conditional ownership cannot be guaranteed. No live cleanup is authorized by this work.

Review disposition: Permanent matching-tag/source foreign repository/PR zero-upload tests and matching-owner/ordinary controls are committed and verified. The safety owner's exact-head assessment confirms the specific ask at review comment 4432389858 is satisfied. Its narrow thread disposition is coordinated separately from the complete review. The full summary still requests ownership/source/concurrent-cleanup assessment; no human approval, overall clearance, or clean review is inferred from Blessed=True.

Publication prerequisite: #513 must replace Gallery-only numeric-version guessing with proven same-source/same-artifact recovery, and #547 must run source-bound preflight before any Gallery mutation. The #548 module/version coordinate is a necessary prerequisite, not behavior present in this candidate. Tag/source agreement alone does not prove which same-version Gallery package/source won under another prefix. These systemic contracts remain separately owned and unimplemented here; the draft stays blocked for merge/publication.

Replacing AutoPatching with configurable DefaultBump and owned-label defaults remains the separately scoped #524. Its retained Skip finding and held test work are unchanged. No new scalar DefaultBump invocation interface, date strategy, Discord delivery, fleet migration, secret, permission, or rule change is introduced.

Technical details

Pushed and reviewed head: 50f16425797ec67375d47262c8c79d3507fb2b21. Guard commit 925cd0bb02aab31e4c110064a395bfe864f8d337 follows frozen helper/guard input d06a8775f5e37d14fc9e641ef364cb42ada260b0; comparison main is 64ffb1d87f86bd5d487891fdb140b78385a00531. Ordinary commits preserve all original/user work. The authorized source incorporation adds seven lines in Release-PSModule/src/release.ps1, with source blob exactly matching the owner's corrected patch, 9f7f1b3e03ced1dfb3e41515a29f37700dff6ec3. The authorized test-only commit adds 47 lines in Release-PSModule.WhatIf.Tests.ps1. No shared algorithm/helper or other safety-delivery change is taken.

Actual settings-controller and composed tests cover event/ref identity, important-change-before-association ordering, already-released commit rejection, and configured bump/skip decisions. Publisher behavior covers immutable source fallback, annotated/standalone/concurrently changed tags, matching asset retention, missing-upload retry, digest rejection, and preserved notes/provenance.

Permanent ownership regressions exercise the real publisher controller and footer decoder using the existing local GitHub mocks. Two cases keep tag/source matching but encode a foreign repository or PR. Each asserts zero uploads, an explicit conflict exception, and no success output. A matching-owner missing-upload case succeeds; the ordinary-retry control is unchanged. Against isolated original d06a877 source, both new conflicts fail with Expected 0, but got 1 upload while controls pass. With the actual guard all four selected cases pass. Supplemental session experiments also verify source mismatch and repository case equivalence. No real publication or guard/decoder replacement is used.

All 137 local action tests pass with actual shuffled/parallel execution; PowerShell analysis is clean. Environment-sensitive containers use a per-process mutex. Hosted parallel execution is not inferred from the discarded configuration probe. At this exact head, Test actions, Docs Build, Lint code base, and Docs Lint succeed. The real hosted suite reports 137 passed, 0 failed/skipped/not-run; the complete rollup is success with no omitted contexts.

The candidate composes with known #541 immutable 2001551566898e59efc764e68450c2238667d607 as tree 9ea691dcef75287a5d9e0d6fbaf775911e7234ed, also passing 137 tests. Neither PR was merged; #541 workflow/fixture/pipeline-doc changes were not copied into #531. This is evidence for those known immutable changes, not a later #541 head.

Full review 5723830059, submitted 2026-10-31T16:45:54Z, is explicitly bound to the current head. The runner exits 0 with zero new inline comments, but the complete summary says Needs a closer look and requests human assessment. Its retained coverage ask is now confirmed fulfilled by the exact-head owner assessment, not interpreted as whole-review clearance. Original guard thread PRRT_kwDOKhokxM6yRjac, root 4432028509, was resolved by Copilot after review of the incorporated guard. Specific coverage thread PRRT_kwDOKhokxM6ySa-k, root 4432389858, is handled only for its scoped ask, separately from the remaining review/native limits.

Remaining source limits are verified read-only: Release-PSModule.Helpers.psm1:100-102 returns no evidence on wrapped/unsupported terminal comments; cleanup.ps1:83,97-107 selects tag names then deletes without fresh release/tag mutation checks. General helper/controller/test writes remain held with the safety owner. No parallel fix, inferred cleanup authorization, draft production pin, or lifecycle framework is introduced.

Changed surface Standards checked Framework docs checked Result
Settings association, branch identity, release lookup PowerShell, Functions, Error Handling, Testing Plan, important-change detection, recovery routing Fixed in this PR
Known prerelease source/footer conflicts and asset retry PowerShell, GitHub Actions, Error Handling, Testing Existing source, ReleaseTag, artifact, notes contracts Fixed in this PR
Permanent retry regression coverage Pester, Testing Real controller and mocked transport boundaries Fixed in this PR
Terminal ownership-comment parsing PowerShell, Error Handling, Testing Canonical ownership proof Exception - coordinated canonical correction still required
Cleanup selection PowerShell, Testing Immutable tag/provenance selection Fixed in this PR
Cleanup mutation-time identity PowerShell, Error Handling, Testing Source-bound destructive mutation Exception - coordinated fresh/conditional ownership correction still required
Gallery source/package ownership and publication order Error Handling, Release Management Source-bound preflight and recovery Exception - #513 and #547

Main's effective rules were read without a required-status-check rule. The coordinator owns the repository-only enforcement proposal and approval; documentation does not create enforcement. Test actions and Docs Lint/Build are path-filtered; universal Lint code base excludes docs-owned paths, so unconditional enforcement needs an applicability/coverage decision. Keep the coordinated preflight → Gallery recovery → bounded recovery composition and #531-before-#524 integration order; no unmerged change is described as delivered.

Related references
Release decisions
  • Release: Yes - the versioned framework's supported recovery, cleanup, prerelease retry, and GitHub artifact-resume correctness changes for module-maintainer integrations.
  • Version bump: Patch - explicit compatible-fix intent through release:patch. Released Release-GHRepository v2.0.3 still uses retained legacy Patch/Fix mappings that agree on patch; owned-label caller/check adoption is a separate prerequisite.
  • Mode: Stable - the target is the native default-branch release route. Final version/base, tag, immutable source, and change baseline are resolved by the release process, not predicted here.
  • Labels: release:patch. Non-owned compatibility labels are retained. The PR stays draft while terminal-provenance/cleanup-mutation limits, full-summary assessment, Resume source-bound Gallery-only publication on retry #513/Validate source-bound ownership before module publication #547/package-coordinate prerequisites, enforcement, or production requirements remain open.

Marius Storhaug and others added 2 commits September 2, 2026 11:08
A workflow_dispatch on the default branch is the documented recovery route for
a failed or cancelled release run. Pull request association was gated on
$isPush, so a dispatch resolved no pull request, saw no version label, and
silently applied the AutoPatching patch fallback.

Widen the gate to cover a manual dispatch on the default branch, so the same
association and selection path runs for the same merge commit.

Also fail loudly when a merged default-branch pull request is associated with
the commit but does not match it, instead of falling back to a patch bump. A
wrong version published to the PowerShell Gallery cannot be reclaimed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Extract the gate, selection, and no-pull-request decision from main.ps1 into
Resolve-ReleasePullRequest, with the GitHub lookup injected as a script block.
The composed logic that produced the wrong version is now covered directly
rather than re-implemented in a test.

Add Get-DiscardedReleasePullRequest so a merged default-branch pull request that
does not match the released commit fails the run instead of silently applying a
patch bump. Open pull requests and non-default-branch merges are ignored, so a
direct push and a feature-branch push are unaffected.

Each new test was verified to fail against the specific defect it guards.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@MariusStorhaug Marius Storhaug (MariusStorhaug) changed the title 🪲 [Fix]: Manual dispatch on the default branch honours the merged pull request's version label 🪲 [Fix]: Manual dispatch on the default branch honors the merged pull request's version label Oct 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Dispatch recovery can resolve the wrong commit and delete another PR’s prereleases, while documentation-only pushes can fail unnecessarily.

5 open issues
What changed in this PR

This PR addresses #530 by making default-branch manual dispatches resolve the merged pull request associated with the dispatched commit, preserving its version-label intent in the release pipeline.

Changes:

  • Shares commit-to-PR resolution between pushes and default-branch dispatches.
  • Rejects unmatched merged-PR associations while preserving legitimate direct releases and propagating lookup failures.
  • Adds association regressions and composed version-resolution tests without changing public workflow inputs or versioning defaults.
File Description
.github/​actions/​Resolve-PSModuleVersion/​tests/​Resolve-PSModuleVersion.Helpers.Tests.ps1 Adds dispatch-context and composed version-resolution tests.
.github/​actions/​Get-PSModuleSettings/​tests/​Get-PSModuleSettings.Helpers.Tests.ps1 Covers association selection, rejection, direct releases, and lookup failures.
.github/​actions/​Get-PSModuleSettings/​src/​main.ps1 Wires event context and the association API into the shared resolver.
.github/​actions/​Get-PSModuleSettings/​src/​Get-PSModuleSettings.Helpers.psm1 Adds release-PR resolution and unmatched-association safeguards.

Review effort: Balanced

@MariusStorhaug Marius Storhaug (MariusStorhaug) added the release:patch Publish a patch release. label Oct 31, 2026
@MariusStorhaug Marius Storhaug (MariusStorhaug) changed the title 🪲 [Fix]: Manual dispatch on the default branch honors the merged pull request's version label Default-branch recovery retains the merged pull request's release intent Oct 31, 2026
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Super-linter summary

Language Validation result
CHECKOV Pass ✅
CSS Pass ✅
CSS_PRETTIER Pass ✅
GITHUB_ACTIONS Pass ✅
GITHUB_ACTIONS_ZIZMOR Pass ✅
GITLEAKS Pass ✅
GIT_MERGE_CONFLICT_MARKERS Pass ✅
HTML Pass ✅
JAVASCRIPT_ES Pass ✅
JAVASCRIPT_PRETTIER Pass ✅
MARKDOWN Pass ✅
NATURAL_LANGUAGE Pass ✅
POWERSHELL Pass ✅
PRE_COMMIT Pass ✅
SPELL_CODESPELL Pass ✅
TRIVY Pass ✅
YAML Pass ✅

All files and directories linted successfully

For more information, see the GitHub Actions workflow run

Powered by Super-linter

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MariusStorhaug Marius Storhaug (MariusStorhaug) changed the title Default-branch recovery retains the merged pull request's release intent Default-branch recovery retains release intent and immutable artifacts Oct 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved ownership checks can publish mismatched artifacts, break prerelease retries, and delete another PR’s releases, while branch matching incorrectly ignores case.

1 open issue
5 previously missed issues

In code that hasn't changed since last review

High severity Record release ownership by PR and repository

.github/​actions/​Cleanup-PSModulePrereleases/​src/​Cleanup-PSModulePrereleases.Helpers.psm1:70

The current PR's repository check does not establish who produced each candidate release. A fork PR and a base-repository PR can both use feature/a; because publication records only that short branch as target_commitish, cleanup for the base PR can delete the fork PR's release and tag. Persist and verify the producing PR/repository on each release, and retain records whose ownership cannot be established.

High severity Verify ownership before reusing a Gallery-only version

.github/​actions/​Get-PSModuleSettings/​src/​Get-PSModuleSettings.Helpers.psm1:257

This GitHub-only check allows a newer minor-labelled merge to reuse an older merge's Gallery-only version: for example, Gallery has 1.1.0 while GitHub remains at 1.0.0. Get-ResolvedModuleVersion reuses 1.1.0, Gallery publication skips the existing package, and GitHub receives the newer source and artifact under that version. Verify ownership before reusing a Gallery-only version, or reject the recovery and direct the maintainer to the original run.

High severity Check tag ownership before Gallery publication

.github/​actions/​Release-PSModule/​src/​release.ps1:230

This ownership check runs after Publish-Module.yml has already published to the PowerShell Gallery. If another source owns the tag but that Gallery version is absent, rerunning the older job permanently publishes its artifact before the conflict is rejected. Run the tag/source and existing-asset checks before Gallery publication too, retaining the release-side checks for races.

High severity Verify tag ownership when no release exists

.github/​actions/​Release-PSModule/​src/​release.ps1:235

A 404 here proves only that the release is absent, not that the tag is unclaimed. If the tag already points to another commit, gh release create reuses it despite --target, and this action uploads the wrong source's artifact. Resolve and verify existing tags even without a release, and recheck ownership before uploading to cover concurrent tag creation.

Medium severity Use case-sensitive branch comparisons

.github/​actions/​Get-PSModuleSettings/​src/​Get-PSModuleSettings.Helpers.psm1:164

Git permits distinct branches such as main and Main, but -eq treats them as equal. An associated PR merged into Main can therefore make a valid release on main fail as an unmatched merge. Use -ceq here and in Select-PullRequestForPush at line 108 so both rejection and selection respect branch identity.

4 resolved since last review

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The retry path can upload to another PR’s prerelease and report success without rejecting conflicting ownership metadata.

1 open issue
1 resolved since last review

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The ownership guard lacks retry tests for matching releases with a conflicting repository or PR footer, including assertions that no asset is uploaded.

1 open issue
1 resolved since last review

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed to evaluate release ownership, source identity, and concurrent cleanup risks across multiple workflows.

1 open issue

Review effort: Balanced

@MariusStorhaug
Marius Storhaug (MariusStorhaug) marked this pull request as ready for review October 31, 2026 22:38

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Fix Patch release:patch Publish a patch release.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🪲 [Fix]: Manual dispatch on the default branch discards the merged pull request's version label

2 participants