Skip to content

Commit 7220833

Browse files
docs: refine lifecycle concurrency candidate
Co-authored-by: Copilot App <[email protected]>
1 parent 6e30367 commit 7220833

2 files changed

Lines changed: 79 additions & 40 deletions

File tree

docs/content/reference/process-workflow-lifecycle-design.md

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,19 @@ These facts establish the starting point. They do not implement the scheduled va
1919

2020
## Candidate event routing
2121

22-
The planner classifies the caller event before build and publication work begins. Each route produces one of three mutation classes: validation only, prerelease mutation, or stable-release mutation.
22+
The planner classifies the caller event before build and publication work begins. Each route produces one of three mutation classes: validation only, prerelease mutation, or stable-release mutation. The candidate also assigns an explicit mutability mode, because callers cannot safely infer it from labels alone.
2323

24-
| Event | Candidate route | Mutation class | Required result |
25-
| --- | --- | --- | --- |
26-
| `workflow_dispatch` on the default branch | Recovery release | Stable release or explicit no-op | Rebuild and validate the selected commit; reconstruct the unreleased release notes. |
27-
| `schedule` | Published-artifact validation | Validation only | Validate the latest published stable artifact and its documentation. |
28-
| Pull request `opened`, `reopened`, `synchronize` | Pull-request CI | Validation only | Report configured validation on the pull request. |
29-
| Pull request `labeled`, `unlabeled` | Prerelease evaluation | Prerelease or validation only | Re-evaluate the full label set and publish only an eligible prerelease. |
30-
| Pull request `closed` | Pull-request cleanup | Prerelease cleanup only | Remove only prereleases owned by the closed pull request. |
31-
| Push to the default branch | Stable release | Stable release | Resolve merged-pull-request intent when applicable, then publish after required gates. |
24+
| Event | Candidate route | Mutability mode | Cancellation | Required result |
25+
| --- | --- | --- | --- | --- |
26+
| `workflow_dispatch` on the default branch | Recovery release | Stable release or explicit no-op | Never cancel | Rebuild and validate the selected commit; reconstruct the unreleased release notes. |
27+
| `schedule` | Published-artifact validation | Validation only | May cancel only another explicitly read-only validation; never cancel a default-branch release | Validate the latest published stable artifact and its documentation. |
28+
| Pull request `opened`, `reopened`, `synchronize` | Pull-request CI | Explicitly read-only | May cancel a superseded run in the same read-only CI domain | Report configured validation on the pull request. |
29+
| Pull request `labeled`, `unlabeled` | Prerelease evaluation | Potential prerelease mutation | Never cancel | Re-evaluate the full label set and publish only an eligible prerelease. |
30+
| Merged pull request `closed` | Post-merge close | Validation only | May cancel only when explicitly routed as read-only | Do not clean up; the successful main-push release owns promotion cleanup. |
31+
| Abandoned pull request `closed` | Pull-request cleanup | Prerelease cleanup only | Never cancel | Remove only prereleases owned by the abandoned pull request. |
32+
| Push to the default branch | Stable release | Stable release | Never cancel | Resolve merged-pull-request intent when applicable, then publish and perform promotion cleanup after required gates. |
3233

33-
The classifier records the route, mutable resource scope, commit identity, and release decision in the plan result. Downstream jobs consume that record rather than infer the event again.
34+
The classifier records the route, mutability mode, mutable resource scope, commit identity, and release decision in the plan result. Downstream jobs consume that record rather than infer the event again.
3435

3536
## Candidate artifact and version boundary
3637

@@ -57,29 +58,49 @@ The recovery route validates the selected commit using the same release gates as
5758

5859
The scheduled route resolves the latest published stable version as an input, not as a version to create. It validates the downloaded package and its published documentation with the checks appropriate to a published consumer artifact. Its plan record sets the mutation class to validation only, so publication and cleanup stages cannot run.
5960

61+
## Candidate close behavior
62+
63+
A merged pull-request close performs no prerelease cleanup because the default-branch push is the release authority. After its stable release succeeds, that main-push route owns promotion cleanup. An abandoned pull-request close runs only pull-request-scoped prerelease cleanup because no main push will occur.
64+
65+
The alternative of never cleaning up on a close requires scheduled garbage collection and leaves abandoned prereleases available until that collection runs. This candidate selects abandoned-close cleanup instead; it does not change the existing unapproved caller contract.
66+
6067
## Candidate concurrency isolation
6168

62-
Closed-pull-request cleanup and default-branch publication use independent concurrency identities:
69+
Cancellation is conditional, not globally disabled. Only an explicit read-only mode may cancel a superseded run. A route that can mutate an external release resource never cancels and is never canceled.
70+
71+
| Lifecycle route | Concurrency domain | Cancellation policy | Mutable resources |
72+
| --- | --- | --- | --- |
73+
| Explicitly read-only pull-request CI | Pull request number and read-only mode | Superseded runs may cancel | None. |
74+
| Prerelease evaluation and abandoned-close cleanup | Pull request number and mutation mode | Never cancel | Prerelease artifacts associated with that pull request. |
75+
| Default-branch push and manual recovery | Default-branch ref and mutation mode | Never cancel | Stable package, GitHub Release, tag, uploads, Pages, and promotion cleanup. |
76+
| Scheduled validation | Published-version read domain | May cancel only another explicit scheduled validation | None. |
77+
78+
Concurrency selection occurs before the reusable workflow can produce a planner output. The candidate therefore requires either an explicit event mode at the caller boundary or separate read-only and mutation workflow domains. Caller-level label inspection is insufficient: repository settings can make a label event release-capable. Manual and scheduled runs must not share a cancellation domain that can cancel a default-branch release.
79+
80+
Cleanup receives a pull-request-scoped artifact set and MUST NOT perform a broad prerelease deletion while a stable release can be active. Any future broad cleanup needs a separately approved exclusive scope; it cannot share the abandoned-close route.
6381

64-
| Lifecycle route | Concurrency scope | Mutable resources |
65-
| --- | --- | --- |
66-
| Closed pull request | Pull request number | Prereleases associated with that pull request. |
67-
| Default-branch push or manual recovery | Default-branch ref | The stable release and its versioned artifacts. |
68-
| Scheduled validation | Published-version read scope | No release-related mutable resources. |
82+
## Why mutation-capable runs never cancel
6983

70-
The routes do not cancel one another. Cleanup receives a pull-request-scoped artifact set and MUST NOT perform a broad prerelease deletion while a stable release can be active. Any future broad cleanup needs a separately approved exclusive scope; it cannot share the closed-pull-request route.
84+
Cancellation after an external mutation begins can leave the release lifecycle partially complete:
85+
86+
| Mutation path | Cancellation hazard |
87+
| --- | --- |
88+
| PowerShell Gallery publication | The package can publish before the GitHub Release, release-asset upload, or pull-request comment completes. |
89+
| Prerelease cleanup | Cleanup can delete only part of a prerelease tag and release set. |
90+
| Default-branch release | A main release can stop between package publication, tag or GitHub Release creation, uploads, comments, Pages deployment, and promotion cleanup. |
91+
| Manual or scheduled run sharing a ref | A cancellation domain keyed only by ref can interrupt a main release with a manual dispatch or scheduled validation. |
7192

7293
## Candidate verification strategy
7394

7495
The lifecycle contract is exercised with event payload fixtures and publication fakes before credentials are used:
7596

7697
| Candidate behavior | Verification |
7798
| --- | --- |
78-
| Event routing | One fixture for each supported event and pull-request activity. |
99+
| Event routing | One fixture for each supported event and pull-request activity, including merged and abandoned close outcomes. |
79100
| Version boundary | A mismatched artifact fixture that proves publication stops. |
80101
| Recovery release notes | Merged-pull-request query fixtures covering an empty range, one pull request, and multiple pull requests. |
81102
| Scheduled validation | A published-version fixture that proves no release mutation is requested. |
82-
| Concurrency isolation | Overlapping closed-pull-request and default-branch-push runs that prove neither run cancels or broadens the other's scope. |
103+
| Conditional cancellation | A superseded read-only CI fixture plus release-capable label, cleanup, main-push, manual, and scheduled fixtures that prove mutation-capable runs do not cancel. |
83104

84105
## Decisions requiring approval
85106

@@ -88,7 +109,7 @@ The candidate does not decide the following:
88109
- Whether a recovery release always uses the normal next patch version or permits an explicit version input.
89110
- Which source is authoritative when a PowerShell Gallery publication and GitHub release disagree about the last published stable version.
90111
- Which consumer-facing checks comprise scheduled published-artifact validation.
91-
- Whether removing prerelease eligibility cleans up existing prereleases immediately or leaves cleanup to pull-request closure.
112+
- Whether removing prerelease eligibility cleans up existing prereleases immediately or leaves them until the abandoned-close cleanup route.
92113
- The exact caller triggers and concurrency expression, which remain subject to [PSModule/Process-PSModule#514](https://github.com/PSModule/Process-PSModule/issues/514).
93114

94115
## Related

docs/content/reference/process-workflow-lifecycle-specification.md

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -98,23 +98,29 @@ Scenario: Remove prerelease eligibility
9898
And it does not create a new prerelease version
9999
```
100100

101-
### FR5 — Closed pull requests MUST clean up only their prereleases {#fr5}
101+
### FR5 — Closed pull requests MUST route cleanup by close outcome {#fr5}
102102

103-
A closed pull request MUST clean up only prerelease artifacts associated with that pull request when cleanup is enabled. It MUST NOT authorize or create a stable publication.
103+
A merged pull request close MUST NOT perform prerelease cleanup. A successful default-branch release for the merge MUST own promotion cleanup. An abandoned pull request close MUST run cleanup-only behavior for prerelease artifacts associated with that pull request when cleanup is enabled. Neither close outcome MUST authorize or create a stable publication.
104104

105105
#### Behavioral scenarios {#fr5-scenarios}
106106

107107
```gherkin
108-
Scenario: Close a pull request with prereleases
109-
Given a closed pull request owns prerelease artifacts
110-
When the cleanup workflow completes
111-
Then the pull request's prerelease artifacts are removed according to configuration
108+
Scenario: Merge a pull request with prereleases
109+
Given a merged pull request owns prerelease artifacts
110+
When its close event is processed
111+
Then the close event does not clean up prerelease artifacts
112+
And the successful default-branch release owns promotion cleanup
113+
114+
Scenario: Abandon a pull request with prereleases
115+
Given an unmerged closed pull request owns prerelease artifacts
116+
When its close event is processed
117+
Then the workflow runs cleanup only for that pull request's prerelease artifacts
112118
And no stable artifact, tag, or release is created
113119
```
114120

115121
### FR6 — Default-branch pushes MUST authorize stable publication after validation {#fr6}
116122

117-
A push to the default branch MUST publish a stable version only after all required build, test, quality, and publication gates succeed. When the pushed commit is the merge commit of a pull request, the stable-release decision MUST use that pull request's release intent.
123+
A push to the default branch MUST publish a stable version only after all required build, test, quality, and publication gates succeed. When the pushed commit is the merge commit of a pull request, the stable-release decision MUST use that pull request's release intent. A successful stable release for a merged pull request MUST own promotion cleanup.
118124

119125
#### Behavioral scenarios {#fr6-scenarios}
120126

@@ -125,6 +131,7 @@ Scenario: Publish a merged pull request
125131
When all required validation gates succeed
126132
Then the workflow publishes the resulting stable version
127133
And the publication is associated with the pushed commit
134+
And the successful release performs promotion cleanup
128135
```
129136

130137
### FR7 — Published artifacts MUST match the resolved version {#fr7}
@@ -158,19 +165,29 @@ Scenario: Retry a publication after an interrupted run
158165
And it does not duplicate the package, tag, or release
159166
```
160167

161-
### NFR2 — Closed-pull-request cleanup and default-branch publication MUST be isolated {#nfr2}
168+
### NFR2 — Cancellation MUST be conditional on an explicit mutability mode {#nfr2}
162169

163-
A closed-pull-request cleanup and a default-branch push MUST use distinct concurrency identities and MUST NOT cancel each other. Cleanup MUST remain limited to its pull request's prerelease artifacts while a default-branch push publishes a stable version.
170+
A run explicitly classified as read-only pull-request CI SHOULD cancel a superseded `opened`, `reopened`, or `synchronize` run for the same pull request. A run that may publish to the PowerShell Gallery, create or upload a GitHub Release or tag, deploy Pages, or clean up prereleases MUST NOT be canceled. Caller-level label inspection MUST NOT determine mutability because repository settings can change the release decision. Manual and scheduled runs sharing a ref MUST NOT cancel a default-branch release.
164171

165172
#### Behavioral scenarios {#nfr2-scenarios}
166173

167174
```gherkin
168-
Scenario: Cleanup and stable publication overlap
169-
Given a pull request closes while another pull request is pushed to the default branch
170-
When both workflow runs start
171-
Then neither run cancels the other
172-
And cleanup does not remove artifacts outside the closed pull request
173-
And stable publication completes independently
175+
Scenario: Supersede read-only pull-request CI
176+
Given an explicitly read-only pull-request CI run is in progress
177+
When a newer synchronize event starts the same read-only route
178+
Then the older run may be canceled
179+
And no release-related resource is mutated by either run
180+
181+
Scenario: Preserve a release-capable run
182+
Given a run may publish, deploy, or clean up prereleases
183+
When a newer event starts
184+
Then the release-capable run is not canceled
185+
And the newer event does not infer safety from pull-request labels alone
186+
187+
Scenario: Keep main release independent from manual and scheduled runs
188+
Given a default-branch release is in progress
189+
When a manual dispatch or scheduled validation starts for the same ref
190+
Then neither event cancels the default-branch release
174191
```
175192

176193
### NFR3 — Each lifecycle outcome MUST be auditable {#nfr3}
@@ -202,15 +219,16 @@ Scenario: Recover release notes after a missed main-push publication
202219
And a retry creates no duplicate publication
203220
```
204221

205-
### AC2 — Verifies: [FR2](#fr2), [FR5](#fr5), [NFR2](#nfr2), [NFR3](#nfr3)
222+
### AC2 — Verifies: [FR2](#fr2), [FR5](#fr5), [FR6](#fr6), [NFR2](#nfr2), [NFR3](#nfr3)
206223

207224
```gherkin
208-
Scenario: Non-stable lifecycle events remain non-mutating
209-
Given a scheduled validation and a closed-pull-request cleanup overlap a main-push release
225+
Scenario: Lifecycle runs preserve release ownership
226+
Given a scheduled validation and an abandoned pull-request cleanup overlap a main-push release
210227
When all three runs complete
211228
Then the scheduled run reports validation without a release mutation
212-
And the cleanup affects only the closed pull request's prereleases
213-
And the main-push run is the only run that can publish the stable release
229+
And the cleanup affects only the abandoned pull request's prereleases
230+
And none of the runs cancel the main-push release
231+
And the main-push run is the only run that publishes the stable release and performs promotion cleanup
214232
```
215233

216234
## Impact

0 commit comments

Comments
 (0)