diff --git a/.github/plugin/psmodule/skills/psmodule-v8-upgrade/SKILL.md b/.github/plugin/psmodule/skills/psmodule-v8-upgrade/SKILL.md index 0c27e026..337cf0af 100644 --- a/.github/plugin/psmodule/skills/psmodule-v8-upgrade/SKILL.md +++ b/.github/plugin/psmodule/skills/psmodule-v8-upgrade/SKILL.md @@ -40,10 +40,9 @@ what is present before deciding what to migrate: workflow extensions. Keep them in separate files and validate them in place. When documentation is absent, do not invent a site as part of the caller -upgrade unless the requested scope explicitly includes documentation migration. -When a legacy MkDocs configuration exists and documentation migration is in -scope, migrate its content and design deliberately to Zensical, then remove -the obsolete configuration only after the generated site validates. +upgrade. When a legacy MkDocs configuration exists, preserve it during the +caller upgrade. A documentation migration that needs consumer-authored content +or theme assets requires framework staging support before it can be delivered. ## Template-PSModule baseline @@ -125,20 +124,10 @@ Treat files and directories as follows: Preserve the consumer's local onboarding contract while updating stale links. If the template revision and the consumer's existing layout disagree, record -the difference and migrate only the requested integration surface. In -particular, the current template uses `.github/zensical.toml`, while this v8 -upgrade request uses `docs/zensical.toml`, `docs/content/`, and -`docs/overrides/` (including `docs/overrides/assets/`) as its documentation -contract; a separate `docs/assets/` directory is optional. When that requested -documentation migration is in scope, move the template's Zensical settings -and custom assets into the `docs/` contract rather than maintaining both -configurations. When it is not in scope, preserve the consumer's existing -working configuration and report the difference. - -For a documentation-only MkDocs migration, use the dedicated -[`psmodule-zensical-migration`](../psmodule-zensical-migration/SKILL.md) skill -so content, theme, assets, and link validation are handled independently from -the caller workflow upgrade. +the difference and migrate only the requested integration surface. The +template's `.github/zensical.toml` is the supported Process-PSModule consumer +configuration. Do not move its site configuration, content, or theme assets +into `docs/` as part of a v8 upgrade. ## Caller workflow contract @@ -167,10 +156,9 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + queue: ${{ github.event_name == 'pull_request' && 'single' || 'max' }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} -permissions: {} - jobs: Process-PSModule: permissions: @@ -226,24 +214,12 @@ uses. ## Documentation integration -Treat `docs/zensical.toml` as authoritative. Do not create or maintain -`mkdocs.yml`, introduce MkDocs configuration, or replace the consumer's -documentation design with a parallel theme. - -Compare the consumer's configuration with the Process-PSModule template and -preserve or migrate these surfaces deliberately: - -- `docs/zensical.toml`, including `docs_dir` and navigation. -- `docs/content/` as the documentation source directory. -- `docs/overrides/` and custom templates. -- `docs/overrides/assets/stylesheets/navigation.css`. -- `docs/overrides/assets/`, any optional `docs/assets/`, logo, favicon, palette, - fonts, and custom JavaScript/CSS. -- Existing navigation labels, page paths, and custom theme behavior. - -Do not delete existing custom CSS/assets merely because the default theme also -provides an equivalent feature. Resolve duplicate configuration in favor of -the existing consumer source of truth, then validate the generated site. +The v8 upgrade does not migrate documentation. Preserve the consumer's +existing documentation source, custom assets, and active site configuration. +For the Process-PSModule site build, keep the template's +`.github/zensical.toml` configuration. Do not create `docs/zensical.toml` or +move documentation content or theme assets into `docs/`; the module site build +does not stage that contract. ## Pester v6 migration @@ -295,15 +271,7 @@ when a targeted check reveals a broader dependency: equivalent when available. 3. Run the Pester v6 test suites with the repository's declared configuration. 4. Run the repository's existing lint and test commands. -5. When documentation exists, run: - - ```powershell - Push-Location docs - zensical build --clean - Pop-Location - ``` - -6. Review the diff for accidental workflow permissions, trigger changes, +5. Review the diff for accidental workflow permissions, trigger changes, secret exposure, fixture removal, generated files, or unrelated refactoring. Report commands and outcomes, including blocked checks and why they were diff --git a/.github/plugin/psmodule/skills/psmodule-zensical-migration/SKILL.md b/.github/plugin/psmodule/skills/psmodule-zensical-migration/SKILL.md index b6e19fef..3d5f027f 100644 --- a/.github/plugin/psmodule/skills/psmodule-zensical-migration/SKILL.md +++ b/.github/plugin/psmodule/skills/psmodule-zensical-migration/SKILL.md @@ -1,159 +1,64 @@ --- name: psmodule-zensical-migration -description: Migrate a PSModule repository from MkDocs to Zensical using the Process-PSModule documentation design as the default while preserving content, navigation, assets, and repository-specific behavior. +description: Assess a PSModule repository's documentation migration to Zensical without introducing a site layout the Process-PSModule pipeline cannot publish. --- -# Migrate PSModule documentation from MkDocs to Zensical +# Assess a PSModule Zensical migration -Use this skill when a PSModule repository has a legacy MkDocs configuration, -usually `.github/mkdocs.yml`, or needs its documentation site aligned with the -Process-PSModule Zensical design. Keep the migration limited to documentation -integration. Do not rewrite module code or tests unless a documentation build -requires a directly related fix. +Use this skill when a PSModule repository has a legacy MkDocs configuration or +needs its documentation site aligned with Zensical. Keep the assessment limited +to documentation integration. Do not rewrite module code or tests unless a +documentation build requires a directly related fix. -## Source-of-truth design +## Current Process-PSModule boundary -Use the current -[`Process-PSModule` `docs/zensical.toml`](https://github.com/PSModule/Process-PSModule/blob/main/docs/zensical.toml) -as the default style and configuration baseline. Reuse its behavior rather than -inventing a second theme: +The module site pipeline stages generated function documentation, `README.md`, +the module icon, and `.github/zensical.toml` into `outputs/site`. It does not +stage consumer-authored `docs/content/`, `docs/overrides/`, or their assets. +Consequently, a consumer configuration that sets `docs_dir = "content"` or +`custom_dir = "overrides"` cannot be published through the current framework. -- `docs_dir = "content"` with content under `docs/content/`. -- `docs/overrides/` as the custom theme directory. -- `docs/overrides/assets/stylesheets/navigation.css` for navigation styling. -- `docs/overrides/assets/` and any repository-owned `docs/assets/` directory - when present as the asset source of truth. -- Mona Sans text and Source Code Pro code fonts. -- Material-style GitHub/link icons, light/dark/system palette toggles, - black/slate/light-blue colors, and the established navigation features. -- Instant navigation, prefetch/preview/progress, tabs, tracking, top navigation, - search, code copy, tooltips, table of contents, and footer behavior. -- TOC, attribute lists, abbreviations, admonitions, definition lists, - footnotes, tables, HTML-in-Markdown, details, superfences, task lists, and - snippets extensions. -- Mermaid fenced blocks and the shared abbreviations snippet. -- Existing social links, consent configuration, site metadata, edit URI, and - custom tablesort JavaScript where those features apply. +Do not move a module consumer from `.github/zensical.toml` to +`docs/zensical.toml`, or prescribe the Process-PSModule repository's +`docs/content/` design, until the framework stages those paths. -Customize only repository identity and content-specific values such as -`site_name`, `site_url`, `repo_name`, `repo_url`, `edit_uri`, copyright, social -links, and `nav`. Do not remove a default feature merely because the old -MkDocs site did not use it. +## Assess before changing -## Inspect before changing +1. Read local guidance, the current documentation build command, and the + Process-PSModule caller workflow. +2. Inventory `.github/mkdocs.yml`, `mkdocs.yml`, `.github/zensical.toml`, and + any existing documentation sources, templates, media, and assets. +3. Identify whether documentation is published by Process-PSModule or by a + separate repository-owned workflow. +4. Preserve module source, generated help, custom assets, navigation, and + repository-owned workflows unless the requested migration requires them. -Inventory the existing repository and record: +## Supported consumer configuration -1. Local guidance, branch state, and documentation build commands. -2. `.github/mkdocs.yml`, any `mkdocs.yml`, and any existing Zensical config. -3. The Markdown content root, includes/snippets, templates, media, and assets. -4. `theme`, `theme.custom_dir`, `extra_css`, `extra_javascript`, `plugins`, - `markdown_extensions`, `nav`, `extra`, and `watch` settings. -5. Links, anchors, generated API/help pages, redirects, and CI publishing steps. -6. Existing custom CSS, JavaScript, templates, logos, favicons, and fonts. +For a Process-PSModule consumer, retain the template's +`.github/zensical.toml` configuration and the generated site layout. Do not +add a second active site configuration. -Do not assume every consumer has a `docs/` tree. If documentation is absent, -create it only when the requested scope includes documentation migration. If -the repository already uses Zensical, compare it with the Process-PSModule -baseline and make only the required alignment changes. - -## Target layout - -For the Process-PSModule documentation contract, use: - -```text -docs/ -├── content/ -├── overrides/ -│ └── assets/ -│ ├── javascripts/ -│ └── stylesheets/ -└── zensical.toml -``` - -Keep existing content under `docs/content/`, custom templates under -`docs/overrides/`, and theme assets under `docs/overrides/assets/`. A separate -`docs/assets/` directory is optional for static content assets. Do not create a -parallel MkDocs theme or leave two active site configurations. - -The current `PSModule/Template-PSModule` repository historically stores a -starter `.github/zensical.toml` and may not contain a `docs/` tree. When the -consumer upgrade explicitly requires the Process-PSModule `docs/` contract, -move the template settings and custom assets into `docs/` and remove the -obsolete active configuration only after the site builds. When that contract -is not in scope, preserve a working template layout and report the difference -instead of moving files speculatively. - -## MkDocs-to-Zensical mapping - -Translate behavior, not just filenames: - -| MkDocs | Zensical | -| --- | --- | -| `site_name`, `site_url` | `[project]` metadata | -| `docs_dir` | `[project].docs_dir` | -| `repo_name`, `repo_url`, `edit_uri` | `[project]` metadata | -| `nav` | `nav = [...]` TOML entries | -| `theme.name` | `[project.theme]` settings | -| `theme.custom_dir` | `[project.theme].custom_dir` | -| `theme.logo`, `theme.favicon` | `[project.theme]` paths | -| `theme.features` | `[project.theme].features` | -| `theme.palette` | `[[project.theme.palette]]` tables | -| `extra_css`, `extra_javascript` | `[project]` arrays | -| `markdown_extensions` | `[project.markdown_extensions.*]` tables | -| `plugins: search` | `[project.plugins.search]` | -| `extra.social` | `[[project.extra.social]]` | -| `watch` | `[project].watch` | - -Preserve Markdown semantics while checking extensions that affect formatting: -admonitions, fenced code, tables, task lists, definition lists, attributes, -snippets, Mermaid, and anchor/permalink behavior. Fix only documented -Zensical incompatibilities; do not silently change headings or links to hide -build failures. - -## Content and link migration - -- Preserve page paths and navigation labels where possible. -- Keep explicit `nav` entries for important landing pages and references. -- Resolve relative links from the new `docs/content/` root. -- Recheck fragment anchors because heading and permalink behavior can differ. -- Move includes/snippets to the configured Zensical location and update every - reference. -- Keep generated API/help inputs in their framework-owned locations. -- Preserve images and downloads; update paths rather than deleting assets. -- Keep custom templates only when they are still required by the migrated site. +When a requested migration needs authored Markdown content or a custom theme, +record that the framework must first stage those assets into `outputs/site`. +Do not claim the migration is complete until the producer supports the target +layout and a consumer build publishes it successfully. ## Validation -Run repository-native checks and the smallest targeted site checks first: - -1. Parse the new `docs/zensical.toml` and verify all configured paths exist. -2. Confirm no active `mkdocs.yml` or MkDocs workflow remains. -3. Check every navigation target, image, download, include, and fragment link. -4. Run the existing documentation lint and link checks. -5. Build the site: - - ```powershell - Push-Location docs - zensical build --clean - Pop-Location - ``` - -6. Review the generated site for navigation, search, palette toggles, fonts, - logo/favicon, custom navigation behavior, code blocks, Mermaid, and social - links. -7. Run the repository's existing Process-PSModule workflow validation when the - documentation is part of that pipeline. +1. Confirm the caller references the intended Process-PSModule version and + retains `.github/zensical.toml`. +2. Run the consumer's existing documentation and Process-PSModule validation + commands when available. +3. Review the generated site to confirm generated function documentation, + `README.md`, and module assets remain present. -Do not claim success if the site builds while links, assets, navigation, or -publishing behavior are broken. Report intentionally deferred pages, -unsupported extensions, and unrelated pre-existing failures. +Report any unsupported authored-content or custom-theme requirement as a +framework gap rather than working around it with a second active +configuration. ## References -- [Process-PSModule Zensical configuration](https://github.com/PSModule/Process-PSModule/blob/main/docs/zensical.toml) -- [Process-PSModule documentation overrides](https://github.com/PSModule/Process-PSModule/tree/main/docs/overrides) -- [Process-PSModule override assets](https://github.com/PSModule/Process-PSModule/tree/main/docs/overrides/assets) - [PSModule repository standard](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/reference/repository-standard.md) -- [PSModule documentation model](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/guides/structuring-your-module.md) +- [PSModule workflow inputs](https://github.com/PSModule/Process-PSModule/blob/main/docs/content/reference/workflow-inputs.md) - [Zensical setup basics](https://zensical.org/docs/setup/basics/) diff --git a/.github/workflows/Workflow-Test-Default.yml b/.github/workflows/Workflow-Test-Default.yml index 2bbae66e..51f6c7ad 100644 --- a/.github/workflows/Workflow-Test-Default.yml +++ b/.github/workflows/Workflow-Test-Default.yml @@ -32,16 +32,18 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: false + queue: ${{ github.event_name == 'pull_request' && 'single' || 'max' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} -permissions: - contents: read - pages: write - id-token: write +permissions: {} jobs: WorkflowTestDefault: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + permissions: + contents: read + pages: write + id-token: write uses: ./.github/workflows/workflow.yml secrets: PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} @@ -69,6 +71,8 @@ jobs: runs-on: ubuntu-latest needs: - WorkflowTestDefault + permissions: + contents: read steps: - name: Checkout repo uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/.github/workflows/Workflow-Test-WithManifest.yml b/.github/workflows/Workflow-Test-WithManifest.yml index 83829813..7b12e43f 100644 --- a/.github/workflows/Workflow-Test-WithManifest.yml +++ b/.github/workflows/Workflow-Test-WithManifest.yml @@ -32,16 +32,18 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: false + queue: ${{ github.event_name == 'pull_request' && 'single' || 'max' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} -permissions: - contents: read - pages: write - id-token: write +permissions: {} jobs: WorkflowTestWithManifest: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + permissions: + contents: read + pages: write + id-token: write uses: ./.github/workflows/workflow.yml secrets: PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} @@ -69,6 +71,8 @@ jobs: runs-on: ubuntu-latest needs: - WorkflowTestWithManifest + permissions: + contents: read steps: - name: Checkout repo uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/docs/content/get-started/index.md b/docs/content/get-started/index.md index 72cc182b..18a00b1d 100644 --- a/docs/content/get-started/index.md +++ b/docs/content/get-started/index.md @@ -11,18 +11,20 @@ Start new modules from the PSModule template repository: ## Quickstart 1. [Create a new repository from the template](https://github.com/new?template_name=Template-PSModule&template_owner=PSModule&description=Add%20a%20description%20(required)&name=%3CModule%20name%3E). -2. [Configure the repository](repository-setup.md) — GitHub Pages, `PSGALLERY_API_KEY`, and the caller workflow. -3. Replace placeholder metadata and remove scaffold sample files. +2. Complete the [module repository baseline](repository-setup.md#1-complete-the-module-repository-baseline): replace + template tokens, remove scaffolding, set `Type: Module`, and confirm required files. +3. [Configure the repository](repository-setup.md) — GitHub Pages, workflow secrets, the caller workflow, settings, and + Zensical. 4. Add your first public command and tests. -5. Validate `.github/PSModule.yml` defaults for your module. -6. [Open a draft pull request](your-first-release.md), then release from its resulting important default-branch push. +5. [Open a draft pull request](your-first-release.md), then release from its resulting important default-branch push. If the module needs several interdependent commands before it is usable at all, see [Module bootstrap](module-bootstrap.md) instead of shipping them as one command per step. ## Expected outcomes -- The repository follows the [expected structure](../guides/structuring-your-module.md). +- The repository follows the [repository standard](../reference/repository-standard.md) and + [expected structure](../guides/structuring-your-module.md). - The module can be built and tested in CI. - The release strategy is ready when functionality is implemented. @@ -30,7 +32,7 @@ If the module needs several interdependent commands before it is usable at all, | Page | Description | | --- | --- | -| [Repository setup](repository-setup.md) | GitHub Pages, `PSGALLERY_API_KEY`, permissions, and the caller workflow. | +| [Repository setup](repository-setup.md) | Repository baseline, GitHub Pages, workflow secrets, and the caller workflow. | | [Your first release](your-first-release.md) | The pull request flow, version labels, and the resulting default-branch release. | | [Module bootstrap](module-bootstrap.md) | Getting a brand-new module to its first release with an integration branch. | diff --git a/docs/content/get-started/repository-setup.md b/docs/content/get-started/repository-setup.md index 790cf5ea..fc21fb81 100644 --- a/docs/content/get-started/repository-setup.md +++ b/docs/content/get-started/repository-setup.md @@ -6,9 +6,23 @@ description: Configure GitHub Pages, `PSGALLERY_API_KEY`, permissions, and the c # Repository setup Do this once per module repository, after creating it from -[Template-PSModule](https://github.com/PSModule/Template-PSModule). +[Template-PSModule](https://github.com/PSModule/Template-PSModule). The template supplies the initial repository +files and framework wiring; this guide verifies the standard baseline and configures its external services. -## 1. Enable GitHub Pages +## 1. Complete the module repository baseline + +Before configuring the pipeline: + +1. Replace every template token, including the README and `.github/zensical.toml`. +2. Remove scaffold functions, tests, and examples that do not belong to the module. +3. Set the repository description and its `Type` custom property to `Module`; retain `main` as the default branch. +4. Confirm the required community, governance, agent, dependency-update, and workflow files are present. +5. Confirm the README follows the module start-page requirements, including `Install-PSResource` installation guidance. +6. Keep `.github/PSModule.yml` limited to settings that override the framework defaults. + +[Repository standard](../reference/repository-standard.md) defines the required files, metadata, and README shape. + +## 2. Enable GitHub Pages Enable GitHub Pages in the repository settings and set it to deploy from **GitHub Actions**. @@ -18,18 +32,23 @@ This creates an environment called `github-pages` that GitHub deploys the docume Remove the branch protection on main -## 2. Create `PSGALLERY_API_KEY` +## 3. Configure workflow secrets -1. [Create an API key on the PowerShell Gallery](https://www.powershellgallery.com/account/apikeys). Give it permission - to manage the module you are working on. -2. Create a repository or organization secret called `PSGALLERY_API_KEY` and set the API key as its value. +Create these repository or organization Actions secrets: -If you plan to create many modules, use a glob pattern for the API key permissions in the PowerShell Gallery and store -`PSGALLERY_API_KEY` on the organization instead of on each repository. +| Secret | Purpose | +| --- | --- | +| `PSGALLERY_API_KEY` | An [API key](https://www.powershellgallery.com/account/apikeys) authorized to manage the module on the PowerShell Gallery. | +| `SHELLY_CLIENT_ID` | The GitHub App client ID that the caller maps to `GitHubAppClientId`. | +| `SHELLY_PRIVATE_KEY` | The GitHub App private key that the caller maps to `GitHubAppPrivateKey`. | -## 3. Add the caller workflow +Use a glob pattern for PowerShell Gallery API-key permissions and store `PSGALLERY_API_KEY` at the organization level +when several modules share it. For Dependabot pull requests, add all three secrets to the Dependabot secret store. +[GitHub App authentication](../guides/github-app-authentication.md) defines the App permissions and token boundaries. -Create `.github/workflows/Process-PSModule.yml` in the module repository: +## 4. Verify the caller workflow + +The template supplies `.github/workflows/Process-PSModule.yml`. Replace the caller with this standard form: ```yaml name: Process-PSModule @@ -54,15 +73,15 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: false - -permissions: - contents: read - pages: write - id-token: write + queue: ${{ github.event_name == 'pull_request' && 'single' || 'max' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: Process-PSModule: + permissions: + contents: read + pages: write + id-token: write uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8 secrets: PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} @@ -70,14 +89,21 @@ jobs: GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }} ``` -Every permission in that block is required. GitHub App installation tokens perform repository writes. A push to `main` publishes a stable release after the full pipeline passes; -the pull-request trigger handles CI, prereleases, and prerelease cleanup. See +Every permission on the calling job is required. GitHub App installation tokens perform repository writes. A push to +`main` publishes a stable release after the full pipeline passes; the pull-request trigger handles CI, prereleases, +and prerelease cleanup. See [Workflow inputs](../reference/workflow-inputs.md) for what each permission is used for, and [Calling the workflow](../guides/calling-the-workflow.md) for passing test secrets and variables. -## 4. Add the settings file +The caller-level concurrency block retains production, dispatch, and scheduled work in the maximum native queue while +replacing obsolete activity for the same pull request. Its fallback expression uses the pull-request number for every +pull-request action, including `closed`; other events use their Git ref. Keep its group distinct from the reusable +workflow's prefixed group. + +## 5. Configure the settings file -Create `.github/PSModule.yml`. An empty file is valid — every setting has a default: +The template supplies `.github/PSModule.yml`. Keep only the overrides the module needs; an empty file is valid when a +hand-built repository needs no overrides: ```yaml Name: null @@ -86,10 +112,10 @@ Name: null See [Settings](../reference/settings.md) for the full contract and [Configuring the pipeline](../guides/configuring-the-pipeline.md) for worked examples. -## 5. Configure the documentation site +## 6. Configure the documentation site -Process-PSModule builds documentation with [Zensical](https://zensical.org/) from `.github/zensical.toml`. The template -ships a working file; update the site name and repository links to match the module. +The template builds documentation with [Zensical](https://zensical.org/) from `.github/zensical.toml`. Replace each +template token with the module's name, owner, and repository URL. ## Next diff --git a/docs/content/guides/calling-the-workflow.md b/docs/content/guides/calling-the-workflow.md index 8e18d7e1..6f6a5304 100644 --- a/docs/content/guides/calling-the-workflow.md +++ b/docs/content/guides/calling-the-workflow.md @@ -5,8 +5,9 @@ description: How to call the Process-PSModule reusable workflow — the caller w # Calling the workflow -To use the workflow, create a new file in the `.github/workflows` directory of the module repository and add the following content. -For documentation site generation, use `zensical.toml` as the active site contract. +Template-PSModule supplies a starter `.github/workflows/Process-PSModule.yml`. Replace it with this standard +template; repositories created without the template create the file with this content. The template's documentation +contract uses `.github/zensical.toml`. For the exact inputs, secrets, and permissions the reusable workflow declares, see [Workflow inputs](../reference/workflow-inputs.md). @@ -37,15 +38,15 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: false - -permissions: - contents: read - pages: write - id-token: write + queue: ${{ github.event_name == 'pull_request' && 'single' || 'max' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: Process-PSModule: + permissions: + contents: read + pages: write + id-token: write uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8 secrets: PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} @@ -59,11 +60,10 @@ Stable releases are evaluated from a push to the default branch. A merged pull r release notes; a direct default-branch push or a manual dispatch uses the default `Patch` bump and commit-based notes. Keep the `pull_request` trigger for CI, prereleases, and prerelease cleanup. -The concurrency key keeps a pull request distinct from a default-branch push, so the close-event cleanup and the -resulting stable release do not serialize as one run. Keep `cancel-in-progress: false`: a release-capable run mutates -the PowerShell Gallery, GitHub Releases, and tags, so later runs must queue rather than interrupt it. -The reusable workflow uses its own prefixed concurrency group, so it cannot queue behind the caller while the caller -waits for it to finish. +The concurrency group uses the pull-request number when available and the Git ref otherwise, so a close event +interrupts only its own pull-request activity and does not block the resulting stable release. Pull-request events use +the `single` queue and cancel obsolete activity; other events use the maximum native queue without cancellation. The +reusable workflow uses a distinct prefixed concurrency group. Do not give the caller that group name. ## Passing test data @@ -85,21 +85,14 @@ The reusable workflow accepts test data through `TestData` and no longer declare - `TEST_USER_USER_FG_PAT` - `TEST_USER_PAT` -If a caller passed any of these secrets directly, place them in the `secrets` map inside `TestData`. -The environment variable names used by the tests can stay the same; only the workflow-call interface -changes: +If a caller passed any of these secrets directly, place them in the `secrets` map inside `TestData`. Add the +`TestData` mapping to the calling job's `secrets` block. The environment variable names used by the tests can stay the +same; only the workflow-call interface changes: ```yaml -jobs: - Process-PSModule: - uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8 - secrets: - PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} - GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }} - GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }} - TestData: >- - { "secrets": { "TEST_USER_PAT": "${{ secrets.TEST_USER_PAT }}", - "TEST_APP_ORG_CLIENT_ID": "${{ secrets.TEST_APP_ORG_CLIENT_ID }}" } } +TestData: >- + { "secrets": { "TEST_USER_PAT": "${{ secrets.TEST_USER_PAT }}", + "TEST_APP_ORG_CLIENT_ID": "${{ secrets.TEST_APP_ORG_CLIENT_ID }}" } } ``` ### Passing test phase data (secrets and variables) @@ -112,25 +105,17 @@ workflow. It is one JSON object with two maps, so everything the tests need is v { "secrets": { "NAME": "value" }, "variables": { "NAME": "value" } } ``` -Values under `secrets` are masked in the logs; values under `variables` are not. Build it in the -calling workflow and pass it through the `secrets:` block (so the whole blob is masked). Reference each -secret directly as `"${{ secrets. }}"` and each variable as `${{ toJSON(vars.) }}`. A -folded `>-` scalar keeps the source readable while producing a single-line value, as long as the JSON -content lines stay at the same indentation level: +Values under `secrets` are masked in the logs; values under `variables` are not. Build it in the calling workflow and +pass it through the calling job's `secrets:` block so the whole blob is masked. Reference each secret directly as +`"${{ secrets. }}"` and each variable as `${{ toJSON(vars.) }}`. A folded `>-` scalar keeps the source +readable while producing a single-line value, as long as the JSON content lines stay at the same indentation level: ```yaml -jobs: - Process-PSModule: - uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8 - secrets: - PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} - GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }} - GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }} - TestData: >- - { "secrets": { "CONFLUENCE_API_TOKEN": "${{ secrets.CONFLUENCE_API_TOKEN }}" }, - "variables": { "CONFLUENCE_SITE": ${{ toJSON(vars.CONFLUENCE_SITE) }}, - "CONFLUENCE_USERNAME": ${{ toJSON(vars.CONFLUENCE_USERNAME) }}, - "CONFLUENCE_SPACE_KEY": ${{ toJSON(vars.CONFLUENCE_SPACE_KEY) }} } } +TestData: >- + { "secrets": { "CONFLUENCE_API_TOKEN": "${{ secrets.CONFLUENCE_API_TOKEN }}" }, + "variables": { "CONFLUENCE_SITE": ${{ toJSON(vars.CONFLUENCE_SITE) }}, + "CONFLUENCE_USERNAME": ${{ toJSON(vars.CONFLUENCE_USERNAME) }}, + "CONFLUENCE_SPACE_KEY": ${{ toJSON(vars.CONFLUENCE_SPACE_KEY) }} } } ``` Each entry becomes an environment variable in the test jobs, so the module's Pester tests read the @@ -228,28 +213,8 @@ settings file: ImportantFilePatterns: [] ``` -You can also pass patterns via the workflow input: - -```yaml -jobs: - Process: - uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8 - with: - ImportantFilePatterns: | - ^src/ - ^README\.md$ - ^examples/ -``` - -To disable triggering via the workflow input, pass an explicit empty string: - -```yaml -jobs: - process: - uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8 - with: - ImportantFilePatterns: '' -``` +The standard caller does not add `with:` overrides. Configure +`ImportantFilePatterns` in `.github/PSModule.yml`. Note that omitting the `ImportantFilePatterns` key entirely causes the workflow's default patterns (`^src/` and `^README\.md$`) to be used. The settings file takes priority over the workflow input, so set diff --git a/docs/content/guides/github-app-authentication.md b/docs/content/guides/github-app-authentication.md index cf35933a..cfb18c38 100644 --- a/docs/content/guides/github-app-authentication.md +++ b/docs/content/guides/github-app-authentication.md @@ -10,15 +10,16 @@ installation tokens. These workflows do not use `github.token` as a fallback for ## Caller secret contract -The reusable workflow declares two required secrets at its `workflow_call` boundary: +The reusable workflow declares three required secrets at its `workflow_call` boundary: | Name | Purpose | | --- | --- | +| `PSGALLERY_API_KEY` | The API key that publishes the module to the PowerShell Gallery. | | `GitHubAppClientId` | The GitHub App client ID passed to the token action. | | `GitHubAppPrivateKey` | The GitHub App private key passed to the token action. | The names are the reusable workflow contract, not a requirement for the caller's repository or organization secret -names. Map the caller's secrets explicitly: +names. Map the caller's secrets explicitly in the calling job: ```yaml jobs: @@ -30,13 +31,13 @@ jobs: GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }} ``` -The root reusable workflow forwards these two values to the Plan, Build-Module, and Publish-Module reusable jobs. -Do not use `secrets: inherit` as a substitute for this mapping. +The root reusable workflow forwards the GitHub App credentials to the Plan, Build-Module, and Publish-Module reusable +jobs, and passes the Gallery API key to publication. Do not use `secrets: inherit` as a substitute for this mapping. Dependabot-triggered workflows cannot read regular Actions secrets. To run Process-PSModule on Dependabot pull -requests, create `SHELLY_CLIENT_ID` and `SHELLY_PRIVATE_KEY` as Dependabot secrets in addition to Actions secrets. -This is a deliberate trust boundary: review the App's installation scope and every dependency update carefully, -because the workflow can mint a Shelly token before human review. +requests, create `PSGALLERY_API_KEY`, `SHELLY_CLIENT_ID`, and `SHELLY_PRIVATE_KEY` as Dependabot secrets in addition +to Actions secrets. This is a deliberate trust boundary: review the App's installation scope and every dependency +update carefully, because the workflow can mint a Shelly token before human review. ## GitHub App installation permissions diff --git a/docs/content/guides/structuring-your-module.md b/docs/content/guides/structuring-your-module.md index c4f36cf3..b796920e 100644 --- a/docs/content/guides/structuring-your-module.md +++ b/docs/content/guides/structuring-your-module.md @@ -27,7 +27,7 @@ The goal is a stable repository anatomy so both humans and automation know exact ```plaintext / -├── .github/ # Workflow config, doc/site templates, automation policy +├── .github/ # Workflow config, site template, and automation policy │ ├── linters/ # Rule sets applied by shared lint steps │ │ ├── .markdown-lint.yml # Markdown rules enforced via super-linter │ │ ├── .powershell-psscriptanalyzer.psd1 # Analyzer profile for test jobs @@ -36,13 +36,21 @@ The goal is a stable repository anatomy so both humans and automation know exact │ │ └── Process-PSModule.yml # Consumer hook into this workflow bundle │ ├── CODEOWNERS # Default reviewers enforced by Process-PSModule checks │ ├── dependabot.yml # Dependency update cadence handled by GitHub -│ ├── zensical.toml # Site config consumed during site builds │ ├── PSModule.yml # Settings parsed to drive matrices -│ └── release.yml # Release automation template invoked on publish +│ ├── pull_request_template.md # PR description structure +│ ├── release.yml # Release automation template invoked on publish +│ └── zensical.toml # Site config consumed during site builds +├── AGENTS.md # Agent entry point +├── CLAUDE.md # Claude Code route to AGENTS.md +├── CODE_OF_CONDUCT.md # Community conduct expectations +├── CONTRIBUTING.md # Repository contribution guidance ├── examples/ # Samples referenced in generated documentation │ └── General.ps1 # Example script processed by Process-PSModule ├── icon/ # Icon assets linked from manifest and documentation │ └── icon.png # Default module icon (PNG format) +├── LICENSE # Reuse terms +├── README.md # Module landing page rendered on GitHub and docs +├── SECURITY.md # Security reporting policy ├── src/ # Module source, see "Module source code structure" below ├── tests/ # Pester suites; the Simple layout is shown │ ├── AfterAll.ps1 (optional) # Cleanup script for ModuleLocal runs @@ -50,8 +58,7 @@ The goal is a stable repository anatomy so both humans and automation know exact │ └── .Tests.ps1 # Simple: one root-level module suite ├── .gitattributes # Normalizes line endings across platforms ├── .gitignore # Excludes build artifacts from source control -├── LICENSE # License text surfaced in manifest metadata -└── README.md # Repository overview rendered on GitHub and docs landing +└── SUPPORT.md # Support expectations ``` The tree shows the [Simple PowerShell test profile](https://msx.no/docs/Coding-Standards/PowerShell/Testing/#simple), not an exclusive test-file shape. Standard keeps one root-level `tests/.Tests.ps1` file per public function group. Advanced uses recursively discovered subdirectories, and layouts may mix across directories. Process-PSModule defines the exact [per-directory precedence and sibling suppression](writing-module-tests.md#test-discovery). diff --git a/docs/content/index.md b/docs/content/index.md index 02cc155f..c25341f1 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -26,7 +26,7 @@ New to Process-PSModule? Work through these in order. | Page | Description | | --- | --- | | [Get started](get-started/index.md) | Create a module repository from the template and get the pipeline running. | -| [Repository setup](get-started/repository-setup.md) | Configure GitHub Pages, `PSGALLERY_API_KEY`, permissions, and the caller workflow. | +| [Repository setup](get-started/repository-setup.md) | Repository baseline, GitHub Pages, workflow secrets, and the caller workflow. | | [Your first release](get-started/your-first-release.md) | The pull request flow, version labels, and the resulting default-branch release. | ## Guides diff --git a/docs/content/reference/repository-standard.md b/docs/content/reference/repository-standard.md index 6348b69b..14d2f580 100644 --- a/docs/content/reference/repository-standard.md +++ b/docs/content/reference/repository-standard.md @@ -87,6 +87,7 @@ Module repositories use the PSModule framework layout: | `AGENTS.md` | Agent onboarding entry point. Points agents to the canonical guidance at `https://psmodule.io/docs/`. | | `CLAUDE.md` | Claude Code entry point. Imports `AGENTS.md` so Claude reads the same instructions. | | `.github/PSModule.yml` | Module workflow configuration overrides. | +| `.github/zensical.toml` | Documentation site configuration consumed by the Process-PSModule site build. | | `.github/workflows/Process-PSModule.yml` | Caller workflow that runs the module's CI/CD by calling the shared Process-PSModule workflow. | | `.github/release.yml` | Release-note and changelog categorization for GitHub releases. | | `.github/linters/` | Linter configuration used by the framework's linting stage, including `.markdown-lint.yml` and `.powershell-psscriptanalyzer.psd1`. | @@ -117,19 +118,22 @@ The module repository owns a caller workflow; the framework owns the reusable wo | Caller workflow | The module repository | `.github/workflows/Process-PSModule.yml` | | Reusable workflow | [`PSModule/Process-PSModule`](https://github.com/PSModule/Process-PSModule) | `.github/workflows/workflow.yml` | -The caller workflow declares the triggers, concurrency, and permissions for the module repository, and delegates the work: +The caller workflow declares the triggers, concurrency, permissions, and explicit secret mapping for the module +repository. Name it `Process-PSModule.yml`, matching +[`PSModule/Template-PSModule`](https://github.com/PSModule/Template-PSModule) and existing module repositories. +`workflow.yml` is the reusable workflow's own filename inside `PSModule/Process-PSModule` and belongs only in the +`uses:` reference. -```yaml -jobs: - Process-PSModule: - uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@ # - secrets: - PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} - GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }} - GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }} -``` +The caller uses one `Process-PSModule` job. Its workflow-level concurrency group uses the pull-request number when +available and the Git ref otherwise. Pull-request events use `queue: single` and cancel obsolete activity; other +events use `queue: max` without cancellation. -Name the caller file `Process-PSModule.yml`, matching [`PSModule/Template-PSModule`](https://github.com/PSModule/Template-PSModule) and every existing module repository. `workflow.yml` is the reusable workflow's own filename inside `PSModule/Process-PSModule` and belongs only in the `uses:` reference. Pin the reference to a commit SHA with the version tag in a trailing comment so Dependabot can update it. +The job uses the reusable-workflow reference, required job-level permissions, and explicit `PSGALLERY_API_KEY`, +`GitHubAppClientId`, and `GitHubAppPrivateKey` mapping. The exact +[caller template](../guides/calling-the-workflow.md) is part of this standard. Use the controlled +`PSModule/Process-PSModule` `@v8` major reference; pin external Actions dependencies to a commit SHA with the version +tag in a trailing comment so Dependabot can update them. Permissions are declared only on the calling job, not at the +workflow root. ## Required common files @@ -157,6 +161,7 @@ Required baseline files for module repositories: | `.github/CODEOWNERS` | Review routing for source, docs, and GitHub workflow files. | | `.github/pull_request_template.md` | Scaffolds pull requests in the MSX PR Format (PR Manager) style — an icon + change-type + user-facing-outcome title, user-facing description sections, an optional technical-details block, and a related-issues block. | | `.github/PSModule.yml` | Module workflow defaults and overrides. | +| `.github/zensical.toml` | Documentation site configuration consumed by the Process-PSModule site build. | | `.gitattributes` | Normalizes line endings and declares text/binary handling so the module can be developed and built consistently on Linux, macOS, and Windows. | | `.gitignore` | Ignores files that must never be committed, tailored to the PowerShell-module ecosystem: operating-system files, editor and developer-tooling files, PowerShell and Pester test-harness artifacts, and all local build outputs and files created during build and test. | @@ -193,7 +198,11 @@ For PSModule module repositories, the requirements are: Every module repository must include `.github/dependabot.yml`. Dependabot is part of the repository supply-chain control, not an optional convenience. -Configure the `github-actions` ecosystem. It keeps the pinned actions current, including the pinned `PSModule/Process-PSModule` reference in the [caller workflow](#caller-workflow-and-reusable-workflow). This is what [`PSModule/Template-PSModule`](https://github.com/PSModule/Template-PSModule) ships, and it is the default for new repositories: +Configure the `github-actions` ecosystem. It keeps external action SHA pins current. The +`PSModule/Process-PSModule` `@v8` reference in the [caller workflow](#caller-workflow-and-reusable-workflow) is a +controlled floating major that advances through Process-PSModule release automation. This is what +[`PSModule/Template-PSModule`](https://github.com/PSModule/Template-PSModule) ships, and it is the default for new +repositories: ```yaml version: 2 diff --git a/docs/content/reference/workflow-inputs.md b/docs/content/reference/workflow-inputs.md index a7c0d932..c3bfdd6a 100644 --- a/docs/content/reference/workflow-inputs.md +++ b/docs/content/reference/workflow-inputs.md @@ -35,10 +35,9 @@ credentials that are exposed. `secrets: inherit` is intentionally not required. See [passing test data](../guides/calling-the-workflow.md#passing-test-data) for how to build the `TestData` value. -## Workflow `github.token` permissions +## Caller-job `github.token` permissions -The following permissions are needed by the caller workflow's default `github.token` for checkout and GitHub Pages -deployment: +Each caller job needs the following permissions for checkout and GitHub Pages deployment: ```yaml permissions: diff --git a/docs/content/specification/design.md b/docs/content/specification/design.md index 521b71b7..22330371 100644 --- a/docs/content/specification/design.md +++ b/docs/content/specification/design.md @@ -14,9 +14,16 @@ The behaviour in the [spec](spec.md) is delivered by a **single reusable GitHub The reusable workflow accepts a caller workflow and minimal caller configuration: a `pull_request`-triggered job for CI and prereleases plus a default-branch `push` trigger for stable publication. The caller calls `workflow.yml` and passes the required secrets. The full caller template is in -[Repository setup](../get-started/repository-setup.md#3-add-the-caller-workflow), and the interface it targets is +[Repository setup](../get-started/repository-setup.md#4-verify-the-caller-workflow), and the interface it targets is documented in [Workflow inputs](../reference/workflow-inputs.md). +### Trigger admission + +The [workflow trigger design](workflow-triggers/design.md) owns caller admission before processing: a retained +production queue and replaceable pull-request activity. The caller-level concurrency group covers each complete +reusable-workflow call through its final enabled stage; the reusable workflow identifies closure and performs optional +prerelease cleanup. + ### Composed reusable workflows The main workflow composes work across specialized reusable workflows, each owning a pipeline stage: @@ -110,6 +117,7 @@ independently; the main workflow pins versions explicitly. The full list is in ## Where this connects - [Spec](spec.md) — the requirements this design delivers. +- [Workflow triggers](workflow-triggers/index.md) — scheduling requirements and the event-routing design. - [Pipeline stages](../reference/pipeline-stages.md) — detailed breakdown of each job. - [Calling the workflow](../guides/calling-the-workflow.md) — how to invoke it. - [Settings](../reference/settings.md) — the settings file reference. diff --git a/docs/content/specification/index.md b/docs/content/specification/index.md index 0b90c3a1..75d9a601 100644 --- a/docs/content/specification/index.md +++ b/docs/content/specification/index.md @@ -13,4 +13,5 @@ delivered. They are aimed at people maintaining Process-PSModule itself. Module | --- | --- | | [Spec](spec.md) | Requirements — an end-to-end pipeline guaranteeing build, testing, quality gates, documentation, and versioned publication. | | [Design](design.md) | How the spec is delivered — a single reusable workflow composing sub-workflows, and the settings contract. | +| [Workflow triggers](workflow-triggers/index.md) | Production queuing, pull-request supersession, and optional closure cleanup, with a dedicated spec and design. | | [Principles and practices](principles-and-practices.md) | The versioning, branching, and colocation principles behind the design. | diff --git a/docs/content/specification/spec.md b/docs/content/specification/spec.md index 5755abf2..b5280323 100644 --- a/docs/content/specification/spec.md +++ b/docs/content/specification/spec.md @@ -61,7 +61,7 @@ Versions MUST follow [SemVer 2.0.0](https://semver.org/) (`vMAJOR.MINOR.PATCH` o ### NFR2 — Serialized releases {#nfr2} -Only one release process MUST run against a given version of the codebase at a time. Concurrent releases to the same ref MUST be prevented, so the tag, version counter, and published artifact remain consistent. +Production pipelines MUST execute serially from planning through their final enabled stage, retaining pending work within the platform's maximum queue capacity. Pull-request updates MUST supersede obsolete activity without canceling production or work for other pull requests; closing a pull request supersedes its activity. The [workflow trigger spec](workflow-triggers/spec.md) owns admission, ordering boundaries, and cleanup guarantees. ### NFR3 — Single production authority {#nfr3} @@ -73,7 +73,7 @@ Pipeline failures MUST be visible in the pull request and block merge. Contribut ### NFR5 — Reproducible and auditable {#nfr5} -The entire pipeline and its decisions MUST be stored in git, so the build is reproducible and auditable from the commit alone. No external configuration, API calls, or out-of-band decisions. +Pipeline policy and configuration MUST be version-controlled. Each run MUST record its triggering revision and the authenticated event, release, and configuration metadata used for its decisions, so queuing or later metadata changes do not erase the decision trail. ## Success Criteria @@ -151,6 +151,7 @@ Scenario: Handle documentation generation failure ## Where this connects - [Design](design.md) — how these requirements are delivered. +- [Workflow triggers](workflow-triggers/index.md) — production, pull-request, and closure scheduling. - [Pipeline stages](../reference/pipeline-stages.md) — the job-by-job breakdown of the workflow. - [Calling the workflow](../guides/calling-the-workflow.md) — how to invoke the workflow. - [Settings](../reference/settings.md) — the settings file and its options. diff --git a/docs/content/specification/workflow-triggers/design.md b/docs/content/specification/workflow-triggers/design.md new file mode 100644 index 00000000..d26699d7 --- /dev/null +++ b/docs/content/specification/workflow-triggers/design.md @@ -0,0 +1,106 @@ +--- +title: Workflow triggers - Design +description: Use one caller-owned GitHub Actions concurrency group for retained production and replaceable pull-request work. +--- + +# Workflow triggers - Design + +The caller workflow holds one concurrency slot for its complete call to `.github/workflows/workflow.yml`. It retains +non-pull-request work, replaces activity for an individual pull request, and lets the reusable workflow select its +normal processing or close behavior from the incoming event. + +## Specification + +[Workflow triggers - Spec](spec.md) defines the required admission behavior. + +## Approach + +Every caller uses this workflow-level configuration: + +```yaml +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + queue: ${{ github.event_name == 'pull_request' && 'single' || 'max' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} +``` + +The group expression is a fallback, not concatenation. A pull-request event uses its number; every other event uses +its Git ref. The caller workflow name scopes the group from other workflow names in the same repository, so no +additional prefix is needed. + +The queue expression distinguishes the two policies: + +- Pull-request events use the one-pending replacement queue and cancel active work. +- All other events use the maximum retained queue and do not cancel active work. This includes `push`, + `workflow_dispatch`, and any scheduled event. + +## Alternatives considered + +| Option | Trade-offs | Verdict | +| --- | --- | --- | +| One conditional caller workflow-level group | Covers the complete reusable-workflow call with three expressions and preserves the required event behavior. | Chosen. | +| One conditional producer workflow-level group | A live experiment failed concurrent production admission before jobs began. | Rejected. | +| One caller group with `queue: max` and no cancellation | Retains every event but does not converge pull-request activity or prioritize closure. | Rejected. | +| Per-track router jobs | Separates closure from activity but adds nested reusable workflows, receipts, and coordination beyond the required caller setting. | Rejected. | +| External dispatcher | Provides stronger retention and ordering at the cost of persistent state and operational ownership. | Rejected. | + +## Architecture + +| Component | Responsibility | +| --- | --- | +| Caller workflow | Subscribes to events and applies the concurrency group before invoking the reusable workflow. | +| GitHub Actions | Retains non-pull-request work or cancels superseded pull-request work for the matching group. | +| `workflow.yml` | Receives the original caller event and routes it through the existing processing or close path. | + +The caller-level group covers all nested reusable jobs until the calling job completes. It therefore serializes +planning, version resolution, publication, and enabled teardown rather than only a short admission step. + +## Data and contracts + +| Event | Group identity | Queue | Cancel active work | Result | +| --- | --- | --- | --- | --- | +| Default-branch `push` | `-refs/heads/` | `max` | No | Retained production work. | +| Default-branch `workflow_dispatch` | `-refs/heads/` | `max` | No | Retained manual production work. | +| Open pull-request activity | `-` | `single` | Yes | Latest activity for that pull request. | +| `pull_request.closed` | `-` | `single` | Yes | Cancels activity and invokes the close path. | +| Scheduled event | `-` | `max` | No | Retained non-pull-request work. | + +A merge's close event has the same pull-request number as its activity even when its Git ref resolves to the default +branch. It therefore cancels activity in the pull-request group, while the resulting default-branch push uses a +different group and proceeds independently. + +## Reusable workflow compatibility + +The caller group is deliberately unprefixed. The current reusable workflow uses +`Process-PSModule-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}`. The names differ, +which prevents a cancellation-enabled caller group from canceling its own caller job through the called workflow. + +Do not add `Process-PSModule-` to the caller group while that reusable group exists. The group names are +repository-local and case-insensitive. If a future reusable workflow removes its group, this caller configuration +remains valid without modification. + +## Security + +Concurrency uses only GitHub event metadata. It requires no secrets and grants no additional permissions. The existing +reusable workflow retains responsibility for credential handling and for evaluating the trusted cleanup setting. + +## Testing strategy + +Use a disposable, nonpublishing caller and producer. Verify three competing production pushes finish serially, a +manual run waits behind production, rapid updates cancel activity for one pull request without affecting another, and +a close event cancels activity before the close path runs. + +Live GitHub Actions experiments validated the configuration with a distinct prefixed group in the reusable producer: +three production runs completed serially; a manual run waited behind a push and executed the production path; three +rapid pull-request activity runs were canceled; and the closure run completed its close job. A separate experiment +showed that placing the conditional group in the producer fails under concurrent production admission. + +## Rollout and operability + +Replace the caller's existing `cancel-in-progress: false` block with the documented configuration. No new input, +secret, dispatcher, or reusable-workflow version is required. Keep the group distinct from the reusable workflow's +prefixed group. + +GitHub retains one running and up to 100 pending executions for a `max` group. It does not guarantee commit-order +execution. Overflow, manual cancellation, and external publication results remain visible in Actions; concurrency +cancellation does not undo an already accepted external operation. diff --git a/docs/content/specification/workflow-triggers/index.md b/docs/content/specification/workflow-triggers/index.md new file mode 100644 index 00000000..7ced4605 --- /dev/null +++ b/docs/content/specification/workflow-triggers/index.md @@ -0,0 +1,15 @@ +--- +title: Workflow triggers +description: The scheduling and cleanup contract for publishing runs, pull-request updates, and pull-request closure. +--- + +# Workflow triggers + +Process-PSModule separates production delivery, replaceable pull-request feedback, and prerelease cleanup. + +| Page | Owns | +| --- | --- | +| [Spec](spec.md) | Required behavior, isolation, capacity, and acceptance scenarios. | +| [Design](design.md) | Event routing, concurrency ownership, cancellation, and cleanup coordination. | + +The [framework spec](../spec.md) owns build, test, versioning, and publication requirements. diff --git a/docs/content/specification/workflow-triggers/spec.md b/docs/content/specification/workflow-triggers/spec.md new file mode 100644 index 00000000..af3b41a1 --- /dev/null +++ b/docs/content/specification/workflow-triggers/spec.md @@ -0,0 +1,155 @@ +--- +title: Workflow triggers - Spec +description: Retain production work while superseding obsolete pull-request activity with one caller concurrency group. +--- + +# Workflow triggers - Spec + +The caller workflow admits Process-PSModule work with one native GitHub Actions concurrency group. Default-branch +production and manual releases are retained; activity for each pull request converges on its latest event; a close +event supersedes that pull request's activity before the reusable workflow evaluates optional prerelease cleanup. + +## Problem + +Production publication cannot safely lose queued runs, while rapid pull-request updates should not consume resources +after they are obsolete. Both event types invoke the same reusable workflow, so admission must distinguish a +default-branch Git ref from a pull-request number without adding workflow jobs or a dispatcher. + +## Outcomes and impact + +- **Outcome:** Production work is retained, and a pull request receives feedback for its latest admitted state. +- **DORA:** Shorter pull-request feedback lead time without losing accepted production delivery intent. +- **Domain signal:** No accepted production event is replaced within the native queue capacity. + +## Users and jobs + +Module maintainers retain stable publication runs and manual releases. Contributors avoid waiting for obsolete +pull-request activity. Repository operators retain the existing close-triggered prerelease cleanup setting. + +## Scope + +**In scope:** Caller admission, production serialization, pull-request supersession, and admission of close events. + +**Out of scope:** An external queue, custom dispatcher jobs, version-label policy, build logic, and prerelease +ownership or deletion implementation. + +## Non-goals + +- Unlimited event storage or strict chronological execution when GitHub delivery order differs from push order. +- Preventing every obsolete run from starting before a later event arrives. +- Restoring external publication after cancellation. + +## Functional requirements + +### FR1 - Admit work through one caller policy {#fr1} + +The caller workflow MUST define one workflow-level concurrency group for the complete reusable-workflow call. The +group MUST use the pull-request number when available and the Git ref otherwise. It MUST retain non-pull-request work +and replace pull-request work. The reusable workflow selects processing or close behavior from the original event. + +#### Caller admission scenario + +```gherkin +Scenario: Call the workflow + Given a caller subscribes to default-branch and pull-request events + And it declares the documented concurrency group + When it calls Process-PSModule + Then its complete reusable-workflow call has one admission policy + And no dispatcher or per-track caller jobs are required +``` + +### FR2 - Retain and serialize production work {#fr2} + +Each default-branch push and manual default-branch release MUST share a retained group. A later admitted production +event MUST NOT cancel a running production pipeline or replace an accepted pending production event within supported +capacity. The complete pipeline executes at most once at a time and retains its triggering revision. + +#### Production retention scenario + +```gherkin +Scenario: Preserve a production burst + Given production run A is executing + When pushes B and C are admitted + Then A is not canceled + And B and C wait and execute serially + And each run uses its triggering revision +``` + +### FR3 - Supersede pull-request activity {#fr3} + +An `opened`, `reopened`, `synchronize`, `labeled`, or `unlabeled` event MUST cancel running activity and replace +pending activity for the same pull request. Different pull requests and production work MUST remain independent. +Once updates stop, the latest admitted pull-request activity proceeds. + +#### Pull-request activity scenario + +```gherkin +Scenario: Push three revisions rapidly + Given revision A is processing for pull request 42 + When revisions B and C arrive + Then superseded activity for pull request 42 is canceled + And only the latest admitted activity proceeds + And production and pull request 43 are unaffected +``` + +### FR4 - Supersede activity on closure {#fr4} + +A merged or abandoned pull request MUST use the same pull-request group as its activity. Its `closed` event MUST +cancel outstanding activity for that pull request and invoke the reusable workflow's close path. The close path MUST +NOT authorize build, test, stable publication, or site deployment. It evaluates prerelease cleanup through the +existing repository setting. + +#### Closure scenario + +```gherkin +Scenario: Close during pull-request activity + Given pull-request activity is running for pull request 42 + When pull request 42 closes + Then its activity is canceled + And the close path runs after cancellation + And a default-branch production run is unaffected +``` + +## Non-functional requirements + +### NFR1 - Use maximum native retention {#nfr1} + +The production group MUST use GitHub's `max` queue: one running execution and up to 100 pending executions. Overflow +remains visible as a canceled execution; it MUST NOT be reported as retained work. + +### NFR2 - Keep lifecycle identities isolated {#nfr2} + +Concurrency identity MUST distinguish every pull request from all Git refs. A closed pull request uses its number, +not the default-branch ref associated with a merge. Caller groups MUST differ from any reusable-workflow group to avoid +recursive cancellation. + +## Acceptance criteria + +```gherkin +# AC1 - Verifies: FR1, FR2, FR3, FR4, NFR1, NFR2 +Scenario: Production and pull-request lifecycle + Given a production run and pull-request activity are running + When two production pushes, two pull-request updates, and a close event arrive + Then production work completes serially + And obsolete pull-request activity is canceled + And the close path executes + And no production run is canceled by pull-request activity or closure +``` + +## Constraints and assumptions + +- **Constraint:** GitHub queues by when an execution starts waiting, not strict push or commit chronology. +- **Constraint:** Cancellation is asynchronous and does not roll back an already accepted external operation. +- **Constraint:** A later event for a reopened pull request can supersede a still-running close path; cleanup checks + the live lifecycle before mutation. +- **Assumption:** Each repository uses one named caller workflow for Process-PSModule. + +## Dependencies + +- [GitHub concurrency](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency) + - queue and cancellation semantics. +- [Settings](../../reference/settings.md) - existing prerelease cleanup setting. + +## Where this connects + +- [Design](design.md) - caller configuration and compatibility. diff --git a/docs/zensical.toml b/docs/zensical.toml index 9c9dbd78..302eba52 100644 --- a/docs/zensical.toml +++ b/docs/zensical.toml @@ -55,6 +55,11 @@ nav = [ "specification/index.md", {"Spec" = "specification/spec.md"}, {"Design" = "specification/design.md"}, + {"Workflow triggers" = [ + "specification/workflow-triggers/index.md", + {"Spec" = "specification/workflow-triggers/spec.md"}, + {"Design" = "specification/workflow-triggers/design.md"}, + ]}, {"Principles and practices" = "specification/principles-and-practices.md"}, ]}, {"Modules" = "Modules/index.md"},