|
| 1 | +--- |
| 2 | +title: Process-PSModule workflow lifecycle candidate specification |
| 3 | +description: Candidate behavior-driven requirements for Process-PSModule caller event routing, recovery releases, validation, and cleanup. |
| 4 | +--- |
| 5 | + |
| 6 | +# Process-PSModule workflow lifecycle candidate specification |
| 7 | + |
| 8 | +**Status:** This is a candidate for discussion. It is not an approved workflow standard and does not change the candidate caller contract in [Process-PSModule caller workflow candidate](process-workflow-fleet-standard.md). |
| 9 | + |
| 10 | +## Problem and outcome |
| 11 | + |
| 12 | +Module repositories need each workflow event to have one safe, observable lifecycle outcome. A failed or missed publication needs a recoverable path; published artifacts need continuing validation; and pull-request activity must not create an accidental stable release. |
| 13 | + |
| 14 | +This candidate defines the behavior required from a Process-PSModule workflow lifecycle. Its requirements follow [spec-driven development](https://msx.no/docs/Ways-of-Working/Spec-Driven-Development/) and use [Given / When / Then scenarios](https://msx.no/docs/Ways-of-Working/Spec-Driven-Development/#behavioral-scenarios) as the acceptance contract. |
| 15 | + |
| 16 | +## Scope |
| 17 | + |
| 18 | +The candidate covers dispatch recovery, scheduled validation, pull-request validation and prerelease evaluation, closed-pull-request cleanup, and stable publication after a default-branch push. |
| 19 | + |
| 20 | +It does not approve a caller layout, change module build or publication implementation, define label names, or prescribe release-note presentation. Those choices remain in the caller candidate, the existing versioning guidance, and the companion [candidate design](process-workflow-lifecycle-design.md). |
| 21 | + |
| 22 | +## Confirmed implementation baseline |
| 23 | + |
| 24 | +The reusable workflow has a single planning decision that enriches downstream settings with a resolved version and release decision. It also serializes runs by pull request number or ref without canceling a running workflow. |
| 25 | + |
| 26 | +The version resolver treats non-pull-request events, including `workflow_dispatch` and `schedule`, as events without a pull request and does not create a release decision. The existing workflow supports pull-request validation, prerelease publication, default-branch publication, and closed-pull-request prerelease cleanup. Scheduled published-artifact validation and manual recovery publication are not confirmed behavior. |
| 27 | + |
| 28 | +The following requirements describe desired behavior, not a claim that the confirmed implementation already satisfies it. |
| 29 | + |
| 30 | +## Functional requirements |
| 31 | + |
| 32 | +### FR1 — Manual dispatch MUST provide a safe recovery release {#fr1} |
| 33 | + |
| 34 | +A default-branch manual dispatch MUST either publish one recoverable stable release after all required validation succeeds or report that the selected commit is already covered by a stable publication. It MUST NOT create a duplicate stable publication. |
| 35 | + |
| 36 | +#### Behavioral scenarios {#fr1-scenarios} |
| 37 | + |
| 38 | +```gherkin |
| 39 | +Scenario: Recover a missing stable publication |
| 40 | + Given the default branch contains a validated commit without a stable publication |
| 41 | + When a maintainer dispatches the workflow for that commit |
| 42 | + Then the workflow publishes one stable artifact and release for the commit |
| 43 | + And the release notes identify merged pull requests since the previous published version |
| 44 | +
|
| 45 | +Scenario: Repeat a completed recovery dispatch |
| 46 | + Given a stable publication already covers the selected default-branch commit |
| 47 | + When a maintainer dispatches the workflow again |
| 48 | + Then the workflow reports that no recovery release is required |
| 49 | + And it does not create another artifact, tag, or release |
| 50 | +``` |
| 51 | + |
| 52 | +### FR2 — Scheduled runs MUST validate published artifacts without publishing {#fr2} |
| 53 | + |
| 54 | +A scheduled run MUST validate the latest published stable artifact and its published documentation against the repository's configured checks. It MUST NOT create, replace, or delete a package, tag, release, or prerelease. |
| 55 | + |
| 56 | +#### Behavioral scenarios {#fr2-scenarios} |
| 57 | + |
| 58 | +```gherkin |
| 59 | +Scenario: Validate the latest published artifact |
| 60 | + Given a stable module version and its documentation are published |
| 61 | + When the scheduled workflow runs |
| 62 | + Then the workflow validates that published version |
| 63 | + And it reports the validated version and result |
| 64 | + And it creates no release-related artifact |
| 65 | +``` |
| 66 | + |
| 67 | +### FR3 — Pull-request delivery events MUST run validation only {#fr3} |
| 68 | + |
| 69 | +An `opened`, `reopened`, or `synchronize` pull-request event targeting the default branch MUST run the configured validation for the pull request. It MUST NOT create a stable publication. |
| 70 | + |
| 71 | +#### Behavioral scenarios {#fr3-scenarios} |
| 72 | + |
| 73 | +```gherkin |
| 74 | +Scenario: Validate a synchronized pull request |
| 75 | + Given a pull request targets the default branch |
| 76 | + When a new commit synchronizes the pull request |
| 77 | + Then the workflow reports the configured validation result on that pull request |
| 78 | + And it does not publish a stable version |
| 79 | +``` |
| 80 | + |
| 81 | +### FR4 — Label changes MUST re-evaluate prerelease eligibility {#fr4} |
| 82 | + |
| 83 | +A `labeled` or `unlabeled` pull-request event targeting the default branch MUST re-evaluate prerelease eligibility from the complete current label set. A prerelease publication MUST occur only when the pull request is eligible and every required validation succeeds. |
| 84 | + |
| 85 | +#### Behavioral scenarios {#fr4-scenarios} |
| 86 | + |
| 87 | +```gherkin |
| 88 | +Scenario: Add prerelease eligibility |
| 89 | + Given a validated pull request has no prerelease eligibility |
| 90 | + When a prerelease label is added |
| 91 | + Then the workflow re-evaluates the pull request |
| 92 | + And it publishes at most one eligible prerelease version |
| 93 | +
|
| 94 | +Scenario: Remove prerelease eligibility |
| 95 | + Given a pull request has prerelease eligibility |
| 96 | + When its prerelease label is removed |
| 97 | + Then the workflow re-evaluates the pull request as ineligible |
| 98 | + And it does not create a new prerelease version |
| 99 | +``` |
| 100 | + |
| 101 | +### FR5 — Closed pull requests MUST clean up only their prereleases {#fr5} |
| 102 | + |
| 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. |
| 104 | + |
| 105 | +#### Behavioral scenarios {#fr5-scenarios} |
| 106 | + |
| 107 | +```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 |
| 112 | + And no stable artifact, tag, or release is created |
| 113 | +``` |
| 114 | + |
| 115 | +### FR6 — Default-branch pushes MUST authorize stable publication after validation {#fr6} |
| 116 | + |
| 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. |
| 118 | + |
| 119 | +#### Behavioral scenarios {#fr6-scenarios} |
| 120 | + |
| 121 | +```gherkin |
| 122 | +Scenario: Publish a merged pull request |
| 123 | + Given a merged pull request has an unambiguous release intent |
| 124 | + And its merge commit is pushed to the default branch |
| 125 | + When all required validation gates succeed |
| 126 | + Then the workflow publishes the resulting stable version |
| 127 | + And the publication is associated with the pushed commit |
| 128 | +``` |
| 129 | + |
| 130 | +### FR7 — Published artifacts MUST match the resolved version {#fr7} |
| 131 | + |
| 132 | +Every prerelease or stable publication MUST contain the version and prerelease identity resolved for its workflow run. A version mismatch MUST fail publication before the release is made visible. |
| 133 | + |
| 134 | +#### Behavioral scenarios {#fr7-scenarios} |
| 135 | + |
| 136 | +```gherkin |
| 137 | +Scenario: Reject an incorrectly stamped artifact |
| 138 | + Given a workflow resolves a release version |
| 139 | + And the built artifact reports a different version |
| 140 | + When publication is attempted |
| 141 | + Then publication fails |
| 142 | + And no release is made visible for that artifact |
| 143 | +``` |
| 144 | + |
| 145 | +## Non-functional requirements |
| 146 | + |
| 147 | +### NFR1 — Lifecycle mutations MUST be idempotent {#nfr1} |
| 148 | + |
| 149 | +Retrying the same event for the same commit and resolved version MUST produce no more than one package, tag, and release for that version. |
| 150 | + |
| 151 | +#### Behavioral scenarios {#nfr1-scenarios} |
| 152 | + |
| 153 | +```gherkin |
| 154 | +Scenario: Retry a publication after an interrupted run |
| 155 | + Given a publication for a resolved version was interrupted |
| 156 | + When the workflow retries the same event |
| 157 | + Then it completes the missing work or reports the completed work |
| 158 | + And it does not duplicate the package, tag, or release |
| 159 | +``` |
| 160 | + |
| 161 | +### NFR2 — Closed-pull-request cleanup and default-branch publication MUST be isolated {#nfr2} |
| 162 | + |
| 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. |
| 164 | + |
| 165 | +#### Behavioral scenarios {#nfr2-scenarios} |
| 166 | + |
| 167 | +```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 |
| 174 | +``` |
| 175 | + |
| 176 | +### NFR3 — Each lifecycle outcome MUST be auditable {#nfr3} |
| 177 | + |
| 178 | +Every run MUST report its event category, resolved version or validated published version, release decision, and terminal outcome before the run completes. |
| 179 | + |
| 180 | +#### Behavioral scenarios {#nfr3-scenarios} |
| 181 | + |
| 182 | +```gherkin |
| 183 | +Scenario: Inspect a scheduled validation result |
| 184 | + Given a scheduled validation has completed |
| 185 | + When a maintainer inspects the workflow result |
| 186 | + Then the result identifies the validated published version |
| 187 | + And it identifies whether validation passed or failed |
| 188 | + And it identifies that no release mutation occurred |
| 189 | +``` |
| 190 | + |
| 191 | +## Cross-cutting acceptance criteria |
| 192 | + |
| 193 | +### AC1 — Verifies: [FR1](#fr1), [FR6](#fr6), [FR7](#fr7), [NFR1](#nfr1) |
| 194 | + |
| 195 | +```gherkin |
| 196 | +Scenario: Recover release notes after a missed main-push publication |
| 197 | + Given merged pull requests exist after the last published stable version |
| 198 | + And the selected default-branch commit has no stable publication |
| 199 | + When a maintainer dispatches a recovery release |
| 200 | + Then the published artifact matches the resolved version |
| 201 | + And the release notes identify the merged pull requests in that unreleased range |
| 202 | + And a retry creates no duplicate publication |
| 203 | +``` |
| 204 | + |
| 205 | +### AC2 — Verifies: [FR2](#fr2), [FR5](#fr5), [NFR2](#nfr2), [NFR3](#nfr3) |
| 206 | + |
| 207 | +```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 |
| 210 | + When all three runs complete |
| 211 | + 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 |
| 214 | +``` |
| 215 | + |
| 216 | +## Impact |
| 217 | + |
| 218 | +This candidate aims to reduce time to restore a missed publication and reduce change failure risk by separating validation, cleanup, prerelease, and stable-release authority. Its domain signal is the count of duplicate, missing, or incorrectly stamped published versions per release cycle; the target is zero. |
| 219 | + |
| 220 | +## Dependencies and constraints |
| 221 | + |
| 222 | +Approval of the caller event and concurrency contract in [PSModule/Process-PSModule#514](https://github.com/PSModule/Process-PSModule/issues/514) is required before this candidate becomes an implementation commitment. The candidate depends on repository credentials that can query pull requests and publish module artifacts. It retains the caller candidate's default-branch and fork boundaries. |
| 223 | + |
| 224 | +## Related |
| 225 | + |
| 226 | +- [Candidate design](process-workflow-lifecycle-design.md) — proposed routing and recovery approach. |
| 227 | +- [Scenario matrix](scenario-matrix.md) — established job-level routing reference. |
| 228 | +- [Process-PSModule caller workflow candidate](process-workflow-fleet-standard.md) — candidate caller event and concurrency contract. |
0 commit comments