Skip to content

Commit ed22f0b

Browse files
docs: select resumable PR concurrency
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 7220833 commit ed22f0b

2 files changed

Lines changed: 76 additions & 48 deletions

File tree

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

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +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. The candidate also assigns an explicit mutability mode, because callers cannot safely infer it from labels alone.
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. Pull-request events may cancel their predecessors, so every pull-request route must converge to the latest pull-request state.
2323

24-
| Event | Candidate route | Mutability mode | Cancellation | Required result |
24+
| Event | Candidate route | Mutation class | Cancellation | Required result |
2525
| --- | --- | --- | --- | --- |
2626
| `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. |
27+
| `schedule` | Published-artifact validation | Validation only | Never cancel | Validate the latest published stable artifact and its documentation. |
28+
| Pull request `opened`, `reopened`, `synchronize` | Pull-request CI | Validation only | Cancels a superseded pull-request run; the later run converges state | Report configured validation on the pull request. |
29+
| Pull request `labeled`, `unlabeled` | Prerelease evaluation | Prerelease or validation only | Cancels a superseded pull-request run; the later run converges state | Re-evaluate the full label set and publish only an eligible prerelease. |
30+
| Merged pull request `closed` | Post-merge close | Validation only | Cancels a superseded pull-request run; the later run converges state | Do not clean up; the successful main-push release owns promotion cleanup. |
31+
| Abandoned pull request `closed` | Pull-request cleanup | Prerelease cleanup only | Cancels a superseded pull-request run; the later run converges state | Remove only prereleases owned by the abandoned pull request. |
3232
| 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. |
3333

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.
34+
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.
3535

3636
## Candidate artifact and version boundary
3737

@@ -64,31 +64,29 @@ A merged pull-request close performs no prerelease cleanup because the default-b
6464

6565
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.
6666

67-
## Candidate concurrency isolation
67+
## Candidate concurrency and recovery
6868

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.
69+
The candidate caller recommendation is:
7070

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. |
71+
```yaml
72+
concurrency:
73+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
74+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
75+
```
7776
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.
77+
This is a candidate, not an approved caller contract. All pull-request events for one pull request share a group and may cancel an earlier run. Push, manual-dispatch, and scheduled events serialize by ref and never cancel; a manual dispatch or schedule therefore cannot interrupt a main release.
7978
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.
81-
82-
## Why mutation-capable runs never cancel
83-
84-
Cancellation after an external mutation begins can leave the release lifecycle partially complete:
79+
Cancellation can leave only transient partial state. Every pull-request route, including prerelease publication and abandoned-close cleanup, resumes and reconciles on the next `synchronize`, `labeled`, `unlabeled`, or `closed` event:
8580

86-
| Mutation path | Cancellation hazard |
81+
| External operation | Required recovery behavior |
8782
| --- | --- |
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. |
83+
| PowerShell Gallery publication | Detect the existing resolved version and continue without duplicate publication. |
84+
| GitHub Release creation | Resume or upsert the release and replace its asset set. |
85+
| Prerelease cleanup | Repeat safely after partial deletion and converge to the latest pull-request state. |
86+
| Prerelease lifecycle | A subsequent synchronize, label, unlabel, or close reconciles obsolete prereleases. |
87+
| Production boundary | No pull-request event creates a stable or signable production artifact. |
88+
89+
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.
9290

9391
## Candidate verification strategy
9492

@@ -100,7 +98,8 @@ The lifecycle contract is exercised with event payload fixtures and publication
10098
| Version boundary | A mismatched artifact fixture that proves publication stops. |
10199
| Recovery release notes | Merged-pull-request query fixtures covering an empty range, one pull request, and multiple pull requests. |
102100
| Scheduled validation | A published-version fixture that proves no release mutation is requested. |
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. |
101+
| Pull-request convergence | Canceled prerelease-publication and cleanup fixtures followed by synchronize, label, unlabel, and close events that prove the latest pull-request state is reconciled. |
102+
| Non-pull-request serialization | Overlapping main-push, manual-dispatch, and scheduled fixtures that prove runs queue by ref and do not cancel. |
104103

105104
## Decisions requiring approval
106105

@@ -110,7 +109,7 @@ The candidate does not decide the following:
110109
- Which source is authoritative when a PowerShell Gallery publication and GitHub release disagree about the last published stable version.
111110
- Which consumer-facing checks comprise scheduled published-artifact validation.
112111
- Whether removing prerelease eligibility cleans up existing prereleases immediately or leaves them until the abandoned-close cleanup route.
113-
- The exact caller triggers and concurrency expression, which remain subject to [PSModule/Process-PSModule#514](https://github.com/PSModule/Process-PSModule/issues/514).
112+
- Approval of the selected caller concurrency expression in [PSModule/Process-PSModule#514](https://github.com/PSModule/Process-PSModule/issues/514).
114113

115114
## Related
116115

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

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -165,29 +165,23 @@ Scenario: Retry a publication after an interrupted run
165165
And it does not duplicate the package, tag, or release
166166
```
167167

168-
### NFR2 — Cancellation MUST be conditional on an explicit mutability mode {#nfr2}
168+
### NFR2 — Pull-request cancellation MUST preserve non-pull-request serialization {#nfr2}
169169

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.
170+
All pull-request events for one pull request MUST share a cancellation scope, so a newer pull-request event cancels a superseded run. Push, manual dispatch, and scheduled runs MUST share ref-based serialization and MUST NOT cancel an in-progress run.
171171

172172
#### Behavioral scenarios {#nfr2-scenarios}
173173

174174
```gherkin
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
175+
Scenario: Supersede a pull-request run
176+
Given a pull-request run is in progress
177+
When a newer synchronize, label, unlabel, or close event starts
178+
Then the older pull-request run is canceled
179+
180+
Scenario: Serialize non-pull-request runs
188181
Given a default-branch release is in progress
189182
When a manual dispatch or scheduled validation starts for the same ref
190-
Then neither event cancels the default-branch release
183+
Then the later run waits for the default-branch release
184+
And neither run cancels the other
191185
```
192186

193187
### NFR3 — Each lifecycle outcome MUST be auditable {#nfr3}
@@ -205,6 +199,40 @@ Scenario: Inspect a scheduled validation result
205199
And it identifies that no release mutation occurred
206200
```
207201

202+
### NFR4 — Pull-request mutation paths MUST resume and converge {#nfr4}
203+
204+
Every pull-request path, including prerelease publication and cleanup, MUST be idempotent and resumable after cancellation. The next `synchronize`, `labeled`, `unlabeled`, or `closed` event MUST converge release-related state to the latest pull-request state. Cancellation MAY leave transient partial state, but it MUST NOT leave a permanent duplicate or obsolete artifact.
205+
206+
#### Behavioral scenarios {#nfr4-scenarios}
207+
208+
```gherkin
209+
Scenario: Resume a canceled prerelease publication
210+
Given a canceled pull-request run published a prerelease package but did not finish its release metadata
211+
When a later pull-request event is processed
212+
Then the workflow detects the existing version
213+
And it completes or reconciles the prerelease release state without duplication
214+
215+
Scenario: Reconcile obsolete prereleases
216+
Given a canceled pull-request run left prerelease artifacts for an earlier pull-request state
217+
When a synchronize, label, unlabel, or close event is processed
218+
Then the workflow reconciles prerelease artifacts to the latest pull-request state
219+
And no obsolete prerelease artifact remains after reconciliation
220+
```
221+
222+
### NFR5 — Pull-request events MUST NOT produce production artifacts {#nfr5}
223+
224+
A pull-request event MUST NOT create a stable or signable production artifact. Pull-request events MAY create only eligible prerelease artifacts and their associated metadata.
225+
226+
#### Behavioral scenarios {#nfr5-scenarios}
227+
228+
```gherkin
229+
Scenario: Evaluate an eligible prerelease pull request
230+
Given a pull request is eligible for prerelease publication
231+
When its release-capable path completes
232+
Then it creates only prerelease artifacts and metadata
233+
And it does not create a stable or signable production artifact
234+
```
235+
208236
## Cross-cutting acceptance criteria
209237

210238
### AC1 — Verifies: [FR1](#fr1), [FR6](#fr6), [FR7](#fr7), [NFR1](#nfr1)
@@ -219,14 +247,15 @@ Scenario: Recover release notes after a missed main-push publication
219247
And a retry creates no duplicate publication
220248
```
221249

222-
### AC2 — Verifies: [FR2](#fr2), [FR5](#fr5), [FR6](#fr6), [NFR2](#nfr2), [NFR3](#nfr3)
250+
### AC2 — Verifies: [FR2](#fr2), [FR4](#fr4), [FR5](#fr5), [FR6](#fr6), [NFR2](#nfr2), [NFR3](#nfr3), [NFR4](#nfr4), [NFR5](#nfr5)
223251

224252
```gherkin
225-
Scenario: Lifecycle runs preserve release ownership
253+
Scenario: Lifecycle runs preserve release ownership after cancellation
226254
Given a scheduled validation and an abandoned pull-request cleanup overlap a main-push release
255+
And the cleanup supersedes a canceled prerelease publication
227256
When all three runs complete
228257
Then the scheduled run reports validation without a release mutation
229-
And the cleanup affects only the abandoned pull request's prereleases
258+
And the cleanup reconciles only the abandoned pull request's prereleases
230259
And none of the runs cancel the main-push release
231260
And the main-push run is the only run that publishes the stable release and performs promotion cleanup
232261
```

0 commit comments

Comments
 (0)