Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6ff3218
Add Process workflow inventory tooling
MariusStorhaug Aug 15, 2026
64b640a
Harden workflow inventory discovery
MariusStorhaug Aug 15, 2026
25b8c7e
Document Process workflow fleet standard
MariusStorhaug Aug 15, 2026
d1d05d1
Handle inventory API and shorthand edge cases
MariusStorhaug Aug 15, 2026
3cb70c1
Clarify workflow credential variants
MariusStorhaug Aug 15, 2026
26372a1
Track workflow target reference compliance
MariusStorhaug Aug 15, 2026
b3c95e6
Match workflow targets case-sensitively
MariusStorhaug Aug 15, 2026
667dbc2
Document controlled major workflow tags
MariusStorhaug Aug 15, 2026
bc50369
Prepare the v8 fleet campaign
MariusStorhaug Aug 15, 2026
d85c213
Escape cron values in Markdown inventory
MariusStorhaug Aug 15, 2026
544e240
Validate inventory test fixture path
MariusStorhaug Aug 15, 2026
925b021
Declare inventory helper contracts
MariusStorhaug Aug 15, 2026
73cd63d
Keep caller layout as a candidate
MariusStorhaug Aug 15, 2026
5a495ce
Record the caller concurrency decision
MariusStorhaug Aug 15, 2026
d7a63a5
Record the caller permission boundary
MariusStorhaug Aug 15, 2026
52f34a8
Refine the workflow token boundary
MariusStorhaug Aug 15, 2026
859d274
Move event authorization into Plan
MariusStorhaug Aug 15, 2026
5ffec40
Allow restricted fork validation
MariusStorhaug Aug 15, 2026
c18a40b
Define fork validation trust order
MariusStorhaug Aug 15, 2026
8346ad0
Record reusable workflow path constraint
MariusStorhaug Aug 15, 2026
cf7d210
Define optional test data mapping
MariusStorhaug Aug 15, 2026
0d604a3
Limit standard to caller contract
MariusStorhaug Aug 15, 2026
bd7c85f
📖 [Docs]: Workflow lifecycle behavior ready for review (#517)
MariusStorhaug Aug 15, 2026
dd7e22c
Merge branch 'main' into audit-process-workflows
MariusStorhaug Aug 15, 2026
7c856a2
Fail closed on workflow parse errors
MariusStorhaug Aug 15, 2026
f296f37
Merge updated main into workflow audit
MariusStorhaug Aug 15, 2026
946d4a6
Limit caller variation to test data
MariusStorhaug Aug 15, 2026
581bf0b
Require the canonical caller template
MariusStorhaug Aug 15, 2026
a56c70e
Merge branch 'main' into audit-process-workflows
MariusStorhaug Aug 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
961 changes: 961 additions & 0 deletions .github/scripts/Get-ProcessPSModuleWorkflowInventory.ps1

Large diffs are not rendered by default.

231 changes: 231 additions & 0 deletions .github/scripts/tests/Get-ProcessPSModuleWorkflowInventory.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
[CmdletBinding()]
param()

BeforeAll {
$scriptPath = Join-Path $PSScriptRoot '../Get-ProcessPSModuleWorkflowInventory.ps1'
Test-Path -LiteralPath $scriptPath | Should -BeTrue
$testRoot = Join-Path ([IO.Path]::GetTempPath()) "process-workflow-inventory-$([guid]::NewGuid())"
$repositoryRoot = Join-Path $testRoot 'Example'
$workflowRoot = Join-Path $repositoryRoot '.github/workflows'
& git init --quiet --initial-branch=main $repositoryRoot
& git -C $repositoryRoot config user.email 'inventory-tests@example.invalid'
& git -C $repositoryRoot config user.name 'Inventory Tests'
New-Item -ItemType Directory -Path $workflowRoot -Force | Out-Null

@'
name: Process-PSModule

on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
push:
branches:
- main
pull_request:
branches:
- main
types:
- opened
- synchronize

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: false

permissions:
contents: write
pull-requests: write

jobs:
Process-PSModule:
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8
with:
Debug: true
secrets:
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }}
GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }}
'@ | Set-Content -LiteralPath (Join-Path $workflowRoot 'Process-PSModule.yml')

@'
name: Unrelated
on:
workflow_dispatch:
jobs:
Test:
runs-on: ubuntu-latest
steps:
- run: echo test
'@ | Set-Content -LiteralPath (Join-Path $workflowRoot 'Unrelated.yml')

& git -C $repositoryRoot add .
& git -C $repositoryRoot commit --quiet -m 'Add test workflows'
& git -C $repositoryRoot update-ref refs/remotes/origin/main HEAD
& git -C $repositoryRoot symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main
& git -C $repositoryRoot switch --quiet -c feature
$featureContent = Get-Content -LiteralPath (Join-Path $workflowRoot 'Process-PSModule.yml') -Raw
$featureContent.Replace(
'workflow.yml@v8',
'workflow.yml@v9'
) |
Set-Content -LiteralPath (Join-Path $workflowRoot 'Process-PSModule.yml')
}

AfterAll {
if (Test-Path -LiteralPath $testRoot) {
Get-ChildItem -LiteralPath $testRoot -Recurse -Force |
ForEach-Object { $_.Attributes = [IO.FileAttributes]::Normal }
Remove-Item -LiteralPath $testRoot -Recurse -Force
}
}

Describe 'Get-ProcessPSModuleWorkflowInventory' {
It 'inventories matching local workflows and their compatibility dimensions' {
$result = @(
& $scriptPath `
-Path $testRoot `
-TargetReference 'v8'
)

$result.Count | Should -Be 1
$result[0].Repository | Should -Be 'Example'
$result[0].WorkflowName | Should -Be 'Process-PSModule'
$result[0].Events | Should -Be @('pull_request', 'push', 'schedule', 'workflow_dispatch')
$result[0].PushBranches | Should -Be @('main')
$result[0].PullRequestTypes | Should -Be @('opened', 'synchronize')
$result[0].ConcurrencyGroup | Should -Be '${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}'
$result[0].CancelInProgress | Should -BeFalse
$result[0].ProcessJobs[0].Reference | Should -Be 'v8'
$result[0].ProcessJobs[0].MatchesTarget | Should -BeTrue
$result[0].MatchesTarget | Should -BeTrue
$result[0].ProcessJobs[0].Condition | Should -Match 'head.repo.full_name'
$result[0].ProcessJobs[0].Inputs.Keys | Should -Contain 'Debug'
$result[0].ProcessJobs[0].SecretMappings.Keys | Should -Be @(
'PSGALLERY_API_KEY'
'GitHubAppClientId'
'GitHubAppPrivateKey'
)
}

It 'compares target references case-sensitively' {
$result = @(& $scriptPath -Path $repositoryRoot -TargetReference 'V8')

$result[0].ProcessJobs[0].MatchesTarget | Should -BeFalse
$result[0].MatchesTarget | Should -BeFalse
}

It 'reads the remote default branch instead of feature-worktree changes' {
$result = @(& $scriptPath -Path $repositoryRoot)

$result[0].DefaultBranch | Should -Be 'main'
$result[0].ProcessJobs[0].Reference | Should -Be 'v8'
}

It 'writes JSON and Markdown refresh artifacts' {
$jsonPath = Join-Path $testRoot 'inventory.json'
$markdownPath = Join-Path $testRoot 'inventory.md'

& $scriptPath `
-Path $repositoryRoot `
-TargetReference 'v8' `
-JsonPath $jsonPath `
-MarkdownPath $markdownPath |
Out-Null

Test-Path -LiteralPath $jsonPath | Should -BeTrue
Test-Path -LiteralPath $markdownPath | Should -BeTrue
(Get-Content -LiteralPath $jsonPath -Raw).TrimStart() | Should -Match '^\['
Get-Content -LiteralPath $markdownPath -Raw | Should -Match 'Example'
Get-Content -LiteralPath $markdownPath -Raw | Should -Match 'v8'
Get-Content -LiteralPath $markdownPath -Raw | Should -Match 'Matching target: 1/1'
Get-Content -LiteralPath $markdownPath -Raw | Should -Match '0 0 \\\* \\\* \\\*'
}

It 'reports and fails closed for a matching malformed workflow' {
$malformedRoot = Join-Path $testRoot 'Malformed'
$malformedWorkflowRoot = Join-Path $malformedRoot '.github/workflows'
$jsonPath = Join-Path $malformedRoot 'output/inventory.json'
$markdownPath = Join-Path $malformedRoot 'output/inventory.md'
& git init --quiet --initial-branch=main $malformedRoot
& git -C $malformedRoot config user.email 'inventory-tests@example.invalid'
& git -C $malformedRoot config user.name 'Inventory Tests'
New-Item -ItemType Directory -Path $malformedWorkflowRoot -Force | Out-Null
@'
name: Broken
jobs:
Process:
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8
invalid: [
'@ | Set-Content -LiteralPath (Join-Path $malformedWorkflowRoot 'Process.yml')
& git -C $malformedRoot add .
& git -C $malformedRoot commit --quiet -m 'Add malformed workflow'

{
& $scriptPath `
-Path $malformedRoot `
-TargetReference 'v8' `
-JsonPath $jsonPath `
-MarkdownPath $markdownPath |
Out-Null
} | Should -Throw -ExpectedMessage '*1 matching workflow file(s) could not be parsed*'

$result = @(Get-Content -LiteralPath $jsonPath -Raw | ConvertFrom-Json)
$result.Count | Should -Be 1
$result[0].Status | Should -Be 'ParseError'
$result[0].Error | Should -Not -BeNullOrEmpty
Get-Content -LiteralPath $markdownPath -Raw | Should -Match 'Parse errors: 1'
Get-Content -LiteralPath $markdownPath -Raw | Should -Match 'Matching target: 0/1'
}

It 'fails closed when no matching workflow is found' {
$emptyRoot = Join-Path $testRoot 'Empty'
& git init --quiet --initial-branch=main $emptyRoot
& git -C $emptyRoot config user.email 'inventory-tests@example.invalid'
& git -C $emptyRoot config user.name 'Inventory Tests'
New-Item -ItemType Directory -Path (Join-Path $emptyRoot '.github/workflows') -Force | Out-Null
Set-Content -LiteralPath (Join-Path $emptyRoot '.github/workflows/Unrelated.yml') -Value @'
name: Unrelated
on:
workflow_dispatch:
jobs:
Test:
runs-on: ubuntu-latest
steps:
- run: echo test
'@
& git -C $emptyRoot add .
& git -C $emptyRoot commit --quiet -m 'Add unrelated workflow'

{ & $scriptPath -Path $emptyRoot } |
Should -Throw 'No reusable workflow jobs using*'
}

It 'normalizes shorthand trigger lists' {
$shorthandRoot = Join-Path $testRoot 'Shorthand'
$shorthandWorkflowRoot = Join-Path $shorthandRoot '.github/workflows'
& git init --quiet --initial-branch=main $shorthandRoot
& git -C $shorthandRoot config user.email 'inventory-tests@example.invalid'
& git -C $shorthandRoot config user.name 'Inventory Tests'
New-Item -ItemType Directory -Path $shorthandWorkflowRoot -Force | Out-Null
Set-Content -LiteralPath (Join-Path $shorthandWorkflowRoot 'Process.yml') -Value @'
name: Shorthand
on: [push, workflow_dispatch]
permissions: read-all
concurrency: process-${{ github.ref }}
jobs:
Process:
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8
'@
& git -C $shorthandRoot add .
& git -C $shorthandRoot commit --quiet -m 'Add shorthand workflow'

$result = @(& $scriptPath -Path $shorthandRoot)

$result[0].Events | Should -Be @('push', 'workflow_dispatch')
$result[0].Permissions | Should -Be 'read-all'
$result[0].ConcurrencyGroup | Should -Be 'process-${{ github.ref }}'
$result[0].CancelInProgress | Should -BeNullOrEmpty
}
}
6 changes: 3 additions & 3 deletions docs/content/guides/calling-the-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ Notes:
- If using `secrets: inherit` in a caller workflow, remember that GitHub only forwards secrets that
already exist by name. It does not assemble a `TestData` JSON payload from individual secrets such as
`TEST_USER_PAT`; the caller must still create and pass the `TestData` value explicitly.
- Organization, repository and GitHub *Environment* secrets and variables are supported when they are
visible to the calling job. For environment-scoped values, set `environment:` on the calling job and
explicitly include those values in `TestData`; they are not exposed automatically.
- Organization and repository secrets and variables are supported when they are visible to the calling job.
GitHub Environment secrets are not supported by this caller contract because a job that calls a reusable
workflow cannot declare `environment:`.

## Important file change detection

Expand Down
Loading