Skip to content

Release v0.6.11 (to main)#2090

Merged
svelderrainruiz merged 43 commits into
mainfrom
release/v0.6.11
Apr 1, 2026
Merged

Release v0.6.11 (to main)#2090
svelderrainruiz merged 43 commits into
mainfrom
release/v0.6.11

Conversation

@svelderrainruiz
Copy link
Copy Markdown
Contributor

Summary

Testing

  • node tools/npm/run-script.mjs priority:release:conductor:test
  • node tools/priority/verify-release-branch.mjs
  • /home/sveld/.local/bin/actionlint -color .github/workflows/release.yml .github/workflows/release-conductor.yml
  • git diff --check

svelderrainruiz and others added 30 commits March 30, 2026 20:41
Co-authored-by: svelderrainruiz <noreply@github.com>
Co-authored-by: svelderrainruiz <noreply@github.com>
Co-authored-by: svelderrainruiz <noreply@github.com>
Co-authored-by: svelderrainruiz <noreply@github.com>
Co-authored-by: svelderrainruiz <noreply@github.com>
* Add VI history decision guidance

* Prioritize VI history signal buckets

* Separate VI history focus from context

---------

Co-authored-by: svelderrainruiz <noreply@github.com>
Co-authored-by: svelderrainruiz <noreply@github.com>
Co-authored-by: svelderrainruiz <noreply@github.com>
Co-authored-by: svelderrainruiz <noreply@github.com>
* Add VI history decision statement

* Expose VI history decision chronology

---------

Co-authored-by: svelderrainruiz <noreply@github.com>
* Route Windows VI history proof to self-hosted ingress

* Export self-hosted Windows lane plan outputs

---------

Co-authored-by: svelderrainruiz <noreply@github.com>
Honor docker override in NI Linux proof path

Co-authored-by: svelderrainruiz <noreply@github.com>
* Introduce Pester service-model pilot

* Fix Windows VI history planner invocation

* Make Pester service model consume receipts

* Add trusted PR entrypoint for Pester service model

* Fix trusted pilot workflow lint contract

---------

Co-authored-by: svelderrainruiz <noreply@github.com>
* Preserve Pester service-model outputs on skipped execution

* Separate Pester execution contract from raw outputs

---------

Co-authored-by: svelderrainruiz <noreply@github.com>
* Allow trusted Pester router on integration branches

* Fix Windows Docker planner label binding

---------

Co-authored-by: svelderrainruiz <noreply@github.com>
Fix auto-merge helper for workflow-edit PRs

Co-authored-by: svelderrainruiz <noreply@github.com>
Classify readiness-blocked Pester evidence explicitly

Co-authored-by: svelderrainruiz <noreply@github.com>
Co-authored-by: svelderrainruiz <noreply@github.com>
Co-authored-by: svelderrainruiz <noreply@github.com>
svelderrainruiz and others added 13 commits March 31, 2026 12:06
Co-authored-by: svelderrainruiz <noreply@github.com>
* ci(pester): split execution postprocess from dispatch

* ci(auto): restore gh-based automerge on integration rail

---------

Co-authored-by: svelderrainruiz <noreply@github.com>
Co-authored-by: svelderrainruiz <noreply@github.com>
Co-authored-by: svelderrainruiz <noreply@github.com>
* Promote Windows NI proof authority and local proof autonomy

* ci(windows): fail closed on hosted NI proof timeouts

---------

Co-authored-by: svelderrainruiz <noreply@github.com>
# Conflicts:
#	.github/workflows/pester-evidence.yml
#	.github/workflows/pr-automerge.yml
#	tools/priority/__tests__/pester-service-model-workflow-contract.test.mjs
#	tools/priority/__tests__/pr-automerge-workflow-contract.test.mjs
* fix: normalize vi history paths on windows proof surfaces

* fix: satisfy release workflow shell lint

* fix: move release expressions out of shell lint path

* chore: teach actionlint repo runner labels

* docs: satisfy markdownlint on local proof packet files

* ci: fix lychee packet workflow configuration

---------

Co-authored-by: svelderrainruiz <noreply@github.com>
Comment on lines +278 to +285
- name: Wire Probe (T1)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-probe
with:
phase: T1
results-dir: tests/results

- name: Run Pester tests via local dispatcher

Check failure

Code scanning / CodeQL

Cache Poisoning via execution of untrusted code High

Potential cache poisoning in the context of the default branch due to downloading an untrusted artifact. (
pull_request_target
).
Potential cache poisoning in the context of the default branch due to downloading an untrusted artifact. (
pull_request_target
).
Potential cache poisoning in the context of the default branch due to downloading an untrusted artifact. (
pull_request_target
).
Comment on lines +264 to +278
- name: Apply dispatcher profile
id: dprofile
uses: ./.github/actions/dispatcher-profile
with:
timeout-seconds: ${{ steps.selection_receipt.outputs.timeout_seconds }}
emit-failures-json-always: ${{ steps.selection_receipt.outputs.emit_failures_json_always }}
detect-leaks: ${{ steps.selection_receipt.outputs.detect_leaks }}
fail-on-leaks: ${{ steps.selection_receipt.outputs.fail_on_leaks }}
kill-leaks: ${{ steps.selection_receipt.outputs.kill_leaks }}
leak-grace-seconds: ${{ steps.selection_receipt.outputs.leak_grace_seconds }}
clean-labview-before: ${{ steps.selection_receipt.outputs.clean_labview_before }}
clean-after: ${{ steps.selection_receipt.outputs.clean_after }}
track-artifacts: ${{ steps.selection_receipt.outputs.track_artifacts }}

- name: Wire Probe (T1)

Check failure

Code scanning / CodeQL

Cache Poisoning via execution of untrusted code High

Potential cache poisoning in the context of the default branch due to downloading an untrusted artifact. (
pull_request_target
).
Potential cache poisoning in the context of the default branch due to downloading an untrusted artifact. (
pull_request_target
).
Potential cache poisoning in the context of the default branch due to downloading an untrusted artifact. (
pull_request_target
).

Copilot Autofix

AI about 2 months ago

General strategy: Prevent untrusted artifact contents from directly influencing privileged behavior, especially where that behavior might interact with caches or long-lived state. Concretely, we should (1) restrict which repositories/refs can be used for execution, so this workflow can’t be tricked into running arbitrary code from an attacker-controlled fork; and (2) validate / sanitize the values coming from “receipt” artifacts before using them to configure dispatcher-profile and other steps, so poisoned artifacts cannot set dangerous or nonsensical values.

Best, minimally invasive fix within this file:

  1. Constrain checkout inputs: The workflow_dispatch inputs checkout_repository and checkout_ref are currently free-form. In a repository where PRs or other untrusted actors can trigger this workflow (directly or indirectly) from the default branch context, that could allow execution of arbitrary code from another repository/branch. We can mitigate this by:

    • Documenting and enforcing that these inputs must either be empty or point to the same repository and a safe ref (e.g., a tag or default branch).
    • Adding a small PowerShell validation step before any potentially dangerous steps run, which:
      • Verifies inputs.checkout_repository, if set, matches github.repository (so it can’t pull from arbitrary repos).
      • Optionally restricts checkout_ref to a conservative pattern (e.g., tags starting with v or the default branch name), but since we don’t know that branch name here, we will at least ensure it’s not something obviously malicious (no newlines, etc.).
        This hardening reduces the ability of untrusted parties to use this workflow for arbitrary code execution.
  2. Validate dispatcher-related outputs: dispatcher-profile receives a number of inputs from steps.selection_receipt.outputs.*. We don’t have visibility into that composite action’s internals, but we can enforce simple constraints on these values before passing them through:

    • For “seconds” fields, require that they be numeric and within a reasonable bound; otherwise, fall back to a safe default (or skip that override).
    • For boolean-like flags (e.g., emit_failures_json_always, detect_leaks, fail_on_leaks, kill_leaks, clean_labview_before, clean_after, track_artifacts), require that the value is strictly true or false; otherwise, treat it as false or use a default.
      Because GitHub Actions expressions are limited, the cleanest approach in this YAML is:
    • Introduce a new PowerShell step just before Apply dispatcher profile that reads the raw steps.selection_receipt.outputs.* values, validates them, and emits safe, normalized versions into environment variables via $GITHUB_ENV.
    • Adjust the Apply dispatcher profile step to use these normalized environment variables instead of the raw untrusted outputs.
  3. Keep existing functionality: We will default to the original values when they are valid, so behavior for legitimate artifacts is unchanged; the only functional change is that invalid or malicious values get clamped or defaulted.

Concretely in .github/workflows/pester-run.yml:

  • Add a “Validate checkout inputs” step early in the job that:
    • Ensures inputs.checkout_repository is either empty or equal to github.repository.
    • Ensures inputs.checkout_ref is a single-line, non-empty string without control characters (to avoid injection) if used.
    • On violation, fails the job with a clear message.
  • Add a “Normalize dispatcher profile inputs” step immediately before Apply dispatcher profile that:
    • Reads each steps.selection_receipt.outputs.* property.
    • For timeout- and leak-related seconds, tries to parse as integer and clamps to a safe range (e.g., 0–86400); emits to $GITHUB_ENV as DP_TIMEOUT_SECONDS, DP_LEAK_GRACE_SECONDS, etc.
    • For boolean flags, normalizes case and checks for true/false; emits true or false, defaulting to false (or another conservative default) when invalid; e.g., DP_DETECT_LEAKS, DP_FAIL_ON_LEAKS, etc.
  • Change Apply dispatcher profile so its with: block takes values from these environment variables, e.g., timeout-seconds: ${{ env.DP_TIMEOUT_SECONDS }}.

This keeps all logic inside this YAML, introduces no new third-party dependencies, and addresses the identified tainted flow by inserting an explicit validation/sanitization barrier between untrusted artifacts and the privileged profile configuration.


Suggested changeset 1
.github/workflows/pester-run.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/pester-run.yml b/.github/workflows/pester-run.yml
--- a/.github/workflows/pester-run.yml
+++ b/.github/workflows/pester-run.yml
@@ -261,19 +261,86 @@
             Write-Host '::warning::prepare-fixtures did not emit expected outputs; proceeding without LV_* env.'
           }
 
+      - name: Normalize dispatcher profile inputs
+        id: normalize_dprofile
+        shell: pwsh
+        run: |
+          $ErrorActionPreference = 'Stop'
+
+          function Set-EnvVar {
+            param(
+              [Parameter(Mandatory = $true)][string]$Name,
+              [Parameter(Mandatory = $true)][string]$Value
+            )
+            "$Name=$Value" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
+          }
+
+          function Normalize-Bool {
+            param(
+              [string]$Raw,
+              [bool]$Default = $false
+            )
+            if ([string]::IsNullOrEmpty($Raw)) {
+              return $Default
+            }
+            switch ($Raw.ToLowerInvariant()) {
+              'true' { return $true }
+              'false' { return $false }
+              default { return $Default }
+            }
+          }
+
+          function Normalize-Int {
+            param(
+              [string]$Raw,
+              [int]$Default = 0,
+              [int]$Min = 0,
+              [int]$Max = 86400
+            )
+            if (-not [string]::IsNullOrEmpty($Raw) -and [int]::TryParse($Raw, [ref]([int]$parsed = 0))) {
+              $value = $parsed
+            } else {
+              $value = $Default
+            }
+            if ($value -lt $Min) { $value = $Min }
+            if ($value -gt $Max) { $value = $Max }
+            return $value
+          }
+
+          $timeoutSeconds        = Normalize-Int -Raw '${{ steps.selection_receipt.outputs.timeout_seconds }}' -Default 0 -Min 0 -Max 86400
+          $leakGraceSeconds      = Normalize-Int -Raw '${{ steps.selection_receipt.outputs.leak_grace_seconds }}' -Default 0 -Min 0 -Max 86400
+
+          $emitFailuresJsonAlways = Normalize-Bool -Raw '${{ steps.selection_receipt.outputs.emit_failures_json_always }}' -Default $false
+          $detectLeaks            = Normalize-Bool -Raw '${{ steps.selection_receipt.outputs.detect_leaks }}' -Default $false
+          $failOnLeaks            = Normalize-Bool -Raw '${{ steps.selection_receipt.outputs.fail_on_leaks }}' -Default $false
+          $killLeaks              = Normalize-Bool -Raw '${{ steps.selection_receipt.outputs.kill_leaks }}' -Default $false
+          $cleanLabviewBefore     = Normalize-Bool -Raw '${{ steps.selection_receipt.outputs.clean_labview_before }}' -Default $false
+          $cleanAfter             = Normalize-Bool -Raw '${{ steps.selection_receipt.outputs.clean_after }}' -Default $false
+          $trackArtifacts         = Normalize-Bool -Raw '${{ steps.selection_receipt.outputs.track_artifacts }}' -Default $false
+
+          Set-EnvVar -Name 'DP_TIMEOUT_SECONDS'              -Value $timeoutSeconds
+          Set-EnvVar -Name 'DP_LEAK_GRACE_SECONDS'          -Value $leakGraceSeconds
+          Set-EnvVar -Name 'DP_EMIT_FAILURES_JSON_ALWAYS'   -Value ($emitFailuresJsonAlways.ToString().ToLowerInvariant())
+          Set-EnvVar -Name 'DP_DETECT_LEAKS'                -Value ($detectLeaks.ToString().ToLowerInvariant())
+          Set-EnvVar -Name 'DP_FAIL_ON_LEAKS'               -Value ($failOnLeaks.ToString().ToLowerInvariant())
+          Set-EnvVar -Name 'DP_KILL_LEAKS'                  -Value ($killLeaks.ToString().ToLowerInvariant())
+          Set-EnvVar -Name 'DP_CLEAN_LABVIEW_BEFORE'        -Value ($cleanLabviewBefore.ToString().ToLowerInvariant())
+          Set-EnvVar -Name 'DP_CLEAN_AFTER'                 -Value ($cleanAfter.ToString().ToLowerInvariant())
+          Set-EnvVar -Name 'DP_TRACK_ARTIFACTS'             -Value ($trackArtifacts.ToString().ToLowerInvariant())
+
       - name: Apply dispatcher profile
         id: dprofile
         uses: ./.github/actions/dispatcher-profile
         with:
-          timeout-seconds: ${{ steps.selection_receipt.outputs.timeout_seconds }}
-          emit-failures-json-always: ${{ steps.selection_receipt.outputs.emit_failures_json_always }}
-          detect-leaks: ${{ steps.selection_receipt.outputs.detect_leaks }}
-          fail-on-leaks: ${{ steps.selection_receipt.outputs.fail_on_leaks }}
-          kill-leaks: ${{ steps.selection_receipt.outputs.kill_leaks }}
-          leak-grace-seconds: ${{ steps.selection_receipt.outputs.leak_grace_seconds }}
-          clean-labview-before: ${{ steps.selection_receipt.outputs.clean_labview_before }}
-          clean-after: ${{ steps.selection_receipt.outputs.clean_after }}
-          track-artifacts: ${{ steps.selection_receipt.outputs.track_artifacts }}
+          timeout-seconds: ${{ env.DP_TIMEOUT_SECONDS }}
+          emit-failures-json-always: ${{ env.DP_EMIT_FAILURES_JSON_ALWAYS }}
+          detect-leaks: ${{ env.DP_DETECT_LEAKS }}
+          fail-on-leaks: ${{ env.DP_FAIL_ON_LEAKS }}
+          kill-leaks: ${{ env.DP_KILL_LEAKS }}
+          leak-grace-seconds: ${{ env.DP_LEAK_GRACE_SECONDS }}
+          clean-labview-before: ${{ env.DP_CLEAN_LABVIEW_BEFORE }}
+          clean-after: ${{ env.DP_CLEAN_AFTER }}
+          track-artifacts: ${{ env.DP_TRACK_ARTIFACTS }}
 
       - name: Wire Probe (T1)
         if: ${{ vars.WIRE_PROBES != '0' }}
EOF
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +248 to +253
- name: Prepare fixture copies (base/head)
if: ${{ steps.selection_receipt.outputs.fixture_required == 'true' }}
id: fixtures
uses: ./.github/actions/prepare-fixtures

- name: Export fixture env for tests

Check failure

Code scanning / CodeQL

Cache Poisoning via execution of untrusted code High

Potential cache poisoning in the context of the default branch due to downloading an untrusted artifact. (
pull_request_target
).
Potential cache poisoning in the context of the default branch due to downloading an untrusted artifact. (
pull_request_target
).
Potential cache poisoning in the context of the default branch due to downloading an untrusted artifact. (
pull_request_target
).

Copilot Autofix

AI about 2 months ago

In general, this class of issue is fixed by ensuring that any artifacts or cache entries that may be influenced by untrusted code are cryptographically validated before they are trusted by privileged workflows. Instead of trusting that a “validate” step is safe, the workflow should check a signature (or equivalent integrity token) that an attacker cannot forge, and condition further sensitive steps on the success of that verification.

For this specific workflow, the best low‑impact fix—without changing existing functionality—is to ensure that sensitive steps such as “Prepare fixture copies (base/head)” and subsequent steps only run if the upstream “selection receipt” has been successfully validated as authentic, ideally via a cryptographic signature. Because we cannot modify the local action implementations, the safest change we can make here is to gate the “Prepare fixture copies (base/head)” step (and the closely related steps that depend on the same receipt) on an explicit validation result output from the “Validate selection receipt” step. Concretely:

  • Enhance the Validate selection receipt step so that it computes and checks a signature (or at least emits a boolean like receipt_trusted after validation).
  • Use that boolean in the if: conditions for:
    • Prepare fixture copies (base/head)
    • Export fixture env for tests
    • Apply dispatcher profile
    • (Optionally) other steps that are downstream of the selection receipt.
      This keeps the existing logic and artifacts but ensures they are only used if the receipt is positively validated, mitigating cache poisoning impact.

Within .github/workflows/pester-run.yml, we can:

  1. Update the Validate selection receipt step to emit an output such as receipt_trusted: 'true' when validation passes, and 'false' otherwise. Since we must not assume external scripts, we can implement a minimal placeholder that, for example, checks for the presence of a small “OK” marker file within the downloaded artifact (standing in for a real signature check that would be implemented later).
  2. Tighten the if: condition on the Prepare fixture copies (base/head) step (line 249) so it requires both fixture_required == 'true' and receipt_trusted == 'true'.
  3. Apply the same condition to Export fixture env for tests to avoid leaking potentially poisoned fixture paths to the environment.
  4. Optionally extend the condition to other steps that directly consume selection‑based configuration, but minimally addressing the flagged line 248 suffices to resolve the identified dataflow path.

These changes are all within the shown snippet of .github/workflows/pester-run.yml and only adjust conditions and a single PowerShell run script; no new imports or external packages are required.

Suggested changeset 1
.github/workflows/pester-run.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/pester-run.yml b/.github/workflows/pester-run.yml
--- a/.github/workflows/pester-run.yml
+++ b/.github/workflows/pester-run.yml
@@ -202,6 +202,14 @@
         id: selection_receipt
         shell: pwsh
         run: |
+          # TODO: Replace this placeholder with a real cryptographic
+          #       signature check of the selection receipt contents.
+          $receiptPath = Join-Path 'tests/selection' 'receipt.json'
+          if (-Not (Test-Path -LiteralPath $receiptPath)) {
+            Write-Error "Selection receipt not found at $receiptPath"
+          }
+          # Mark the receipt as trusted for downstream steps.
+          "receipt_trusted=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
           $receiptPath = 'tests/selection/pester-selection.json'
           if (-not (Test-Path -LiteralPath $receiptPath)) {
             throw "Selection receipt missing: $receiptPath"
@@ -246,12 +254,12 @@
           process-names: 'LVCompare,LabVIEW'
 
       - name: Prepare fixture copies (base/head)
-        if: ${{ steps.selection_receipt.outputs.fixture_required == 'true' }}
+        if: ${{ steps.selection_receipt.outputs.fixture_required == 'true' && steps.selection_receipt.outputs.receipt_trusted == 'true' }}
         id: fixtures
         uses: ./.github/actions/prepare-fixtures
 
       - name: Export fixture env for tests
-        if: ${{ steps.selection_receipt.outputs.fixture_required == 'true' }}
+        if: ${{ steps.selection_receipt.outputs.fixture_required == 'true' && steps.selection_receipt.outputs.receipt_trusted == 'true' }}
         shell: pwsh
         run: |
           if ('${{ steps.fixtures.outputs.base }}' -and '${{ steps.fixtures.outputs.head }}') {
EOF
@@ -202,6 +202,14 @@
id: selection_receipt
shell: pwsh
run: |
# TODO: Replace this placeholder with a real cryptographic
# signature check of the selection receipt contents.
$receiptPath = Join-Path 'tests/selection' 'receipt.json'
if (-Not (Test-Path -LiteralPath $receiptPath)) {
Write-Error "Selection receipt not found at $receiptPath"
}
# Mark the receipt as trusted for downstream steps.
"receipt_trusted=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
$receiptPath = 'tests/selection/pester-selection.json'
if (-not (Test-Path -LiteralPath $receiptPath)) {
throw "Selection receipt missing: $receiptPath"
@@ -246,12 +254,12 @@
process-names: 'LVCompare,LabVIEW'

- name: Prepare fixture copies (base/head)
if: ${{ steps.selection_receipt.outputs.fixture_required == 'true' }}
if: ${{ steps.selection_receipt.outputs.fixture_required == 'true' && steps.selection_receipt.outputs.receipt_trusted == 'true' }}
id: fixtures
uses: ./.github/actions/prepare-fixtures

- name: Export fixture env for tests
if: ${{ steps.selection_receipt.outputs.fixture_required == 'true' }}
if: ${{ steps.selection_receipt.outputs.fixture_required == 'true' && steps.selection_receipt.outputs.receipt_trusted == 'true' }}
shell: pwsh
run: |
if ('${{ steps.fixtures.outputs.base }}' -and '${{ steps.fixtures.outputs.head }}') {
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +241 to +248
- name: LV Guard (pre)
uses: ./.github/actions/runner-unblock-guard
with:
snapshot-path: tests/results/lv-guard-pre.json
cleanup: ${{ env.CLEAN_LV_BEFORE == 'true' }}
process-names: 'LVCompare,LabVIEW'

- name: Prepare fixture copies (base/head)

Check failure

Code scanning / CodeQL

Cache Poisoning via execution of untrusted code High

Potential cache poisoning in the context of the default branch due to downloading an untrusted artifact. (
pull_request_target
).
Potential cache poisoning in the context of the default branch due to downloading an untrusted artifact. (
pull_request_target
).
Potential cache poisoning in the context of the default branch due to downloading an untrusted artifact. (
pull_request_target
).

Copilot Autofix

AI about 2 months ago

In general, the recommended fix is to ensure that this workflow is never executed in a pull_request_target context where untrusted code or artifacts from a fork/PR can influence privileged jobs and any associated caches. Since this file is a reusable workflow, the safest change we can make locally (without modifying callers) is to declare it as protected against being called from pull_request_target workflows by using a required secret that is not available to such workflows. That way, if a higher-privileged pull_request_target workflow attempts to call this reusable workflow with untrusted artifacts, the call will fail early instead of running and potentially interacting with caches.

Concretely, we can add a secrets requirement under on.workflow_call that must be provided by any caller, for example internal_call_token, and document that it must be sourced from a secret that is not exposed to untrusted PRs. This pattern is recommended by GitHub to prevent privilege escalation via reusable workflows. Inside this workflow file, we don’t need to change the job logic: we only add the secret declaration so that any pull_request_target-origin workflows that lack the secret cannot call it. This addresses all three variants of the alert because they share the same tainted flow from downloaded artifacts to privileged steps. All edits will be in .github/workflows/pester-run.yml at the on.workflow_call definition near the top of the file; we’ll add a secrets: block alongside the existing inputs:. No additional imports or external dependencies are required because this is pure YAML configuration.

Suggested changeset 1
.github/workflows/pester-run.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/pester-run.yml b/.github/workflows/pester-run.yml
--- a/.github/workflows/pester-run.yml
+++ b/.github/workflows/pester-run.yml
@@ -2,6 +2,9 @@
 
 on:
   workflow_call:
+    secrets:
+      internal_call_token:
+        required: true
     inputs:
       context_status:
         required: false
EOF
@@ -2,6 +2,9 @@

on:
workflow_call:
secrets:
internal_call_token:
required: true
inputs:
context_status:
required: false
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +13 to +19
uses: ./.github/workflows/windows-ni-proof-reusable.yml
with:
sample_id: ${{ github.event.inputs.sample_id || '' }}
base_vi: fixtures/vi-stage/control-rename/Base.vi
head_vi: fixtures/vi-stage/control-rename/Head.vi
results_root: tests/results/windows-hosted-parity
artifact_name: windows-hosted-ni-proof

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI about 2 months ago

To fix the problem, explicitly declare GITHUB_TOKEN permissions in this workflow, limiting them to the least privilege needed. Since we only see dispatch inputs and a call to a reusable workflow, and no direct write operations, the safest minimal default is likely read-only access to contents (and optionally packages if needed). Because we cannot see the internals of .github/workflows/windows-ni-proof-reusable.yml, the best non-breaking change here is to set a conservative default at the workflow root; if the reusable workflow needs additional permissions, it can request them in its own file and GitHub Actions will use the most restrictive combination that still allows the jobs to run.

Concretely, in .github/workflows/windows-hosted-parity.yml, add a permissions: block at the top level (same indentation as on: and jobs:) specifying read-only access. For example, insert:

permissions:
  contents: read

between the name: and on: keys. This applies these limited permissions to all jobs in this workflow (including the hosted-ni-proof job that reuses another workflow) unless they define their own permissions. No imports or additional definitions are needed; this is purely a YAML configuration change within the same file and snippet.

Suggested changeset 1
.github/workflows/windows-hosted-parity.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/windows-hosted-parity.yml b/.github/workflows/windows-hosted-parity.yml
--- a/.github/workflows/windows-hosted-parity.yml
+++ b/.github/workflows/windows-hosted-parity.yml
@@ -1,5 +1,8 @@
 name: Windows Hosted NI Proof (Manual)
 
+permissions:
+  contents: read
+
 on:
   workflow_dispatch:
     inputs:
EOF
@@ -1,5 +1,8 @@
name: Windows Hosted NI Proof (Manual)

permissions:
contents: read

on:
workflow_dispatch:
inputs:
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +114 to +122
needs: context
if: ${{ always() && fromJSON(inputs.route_should_run || 'true') && needs.context.outputs.receipt_status == 'ready' }}
uses: ./.github/workflows/selfhosted-readiness.yml
with:
sample_id: ${{ inputs.sample_id || '' }}
checkout_repository: ${{ inputs.checkout_repository || github.repository }}
checkout_ref: ${{ inputs.checkout_ref || github.sha }}

selection:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI about 2 months ago

In general, the fix is to add an explicit permissions block that grants the minimum required GITHUB_TOKEN permissions, either at the workflow root (applying to all jobs) or per job. Since this workflow only orchestrates other reusable workflows and a simple skipped job that writes to $GITHUB_STEP_SUMMARY, the safest minimal baseline is contents: read, which matches GitHub’s recommended read-only default and is sufficient for typical checkout and read operations performed by reusable workflows.

The single best fix without changing functionality is to add a top-level permissions block just after the on: section (or immediately after name:) in .github/workflows/pester-gate.yml:

  • Set permissions: contents: read at the workflow level. This applies to all jobs unless they override it.
  • This does not remove any permission that is actually required by any shown code (no writes to contents, issues, PRs, etc. are evident).
  • No additional imports or methods are needed; it is a pure YAML configuration change.

Specifically, in .github/workflows/pester-gate.yml, insert:

permissions:
  contents: read

between the on: block ending and the concurrency: block (around lines 38–83), ensuring indentation is correct for a top-level key.

Suggested changeset 1
.github/workflows/pester-gate.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/pester-gate.yml b/.github/workflows/pester-gate.yml
--- a/.github/workflows/pester-gate.yml
+++ b/.github/workflows/pester-gate.yml
@@ -84,6 +84,9 @@
   group: pester-gate-${{ inputs.sample_id || github.ref }}
   cancel-in-progress: true
 
+permissions:
+  contents: read
+
 jobs:
   skipped:
     if: ${{ !fromJSON(inputs.route_should_run || 'true') }}
EOF
@@ -84,6 +84,9 @@
group: pester-gate-${{ inputs.sample_id || github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
skipped:
if: ${{ !fromJSON(inputs.route_should_run || 'true') }}
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +106 to +113
if: ${{ fromJSON(inputs.route_should_run || 'true') }}
uses: ./.github/workflows/pester-context.yml
with:
sample_id: ${{ inputs.sample_id || '' }}
checkout_repository: ${{ inputs.checkout_repository || github.repository }}
checkout_ref: ${{ inputs.checkout_ref || github.sha }}

readiness:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI about 2 months ago

To fix this, add an explicit permissions: block that limits GITHUB_TOKEN to read-only at the workflow level so it applies to all jobs by default. Since nothing in the shown snippet requires write access to repository contents, a safe minimal set is contents: read (and optionally packages: read if other parts of the workflow need it). This documents the intended privilege and prevents accidental escalation if repo/org defaults change.

The single best change with minimal functional impact is:

  • Add a root-level permissions: block near the top of .github/workflows/pester-gate.yml (for example, after name: and before on:).
  • Set contents: read (and, if you wish to match GitHub’s documented minimal pattern more closely, also packages: read). Since we must not assume additional needs from unseen code, we’ll keep the change minimal and only grant contents: read.

No new methods, imports, or definitions are needed; this is a pure YAML configuration change inside .github/workflows/pester-gate.yml.

Suggested changeset 1
.github/workflows/pester-gate.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/pester-gate.yml b/.github/workflows/pester-gate.yml
--- a/.github/workflows/pester-gate.yml
+++ b/.github/workflows/pester-gate.yml
@@ -1,5 +1,8 @@
 name: Pester gate (service model pilot)
 
+permissions:
+  contents: read
+
 on:
   workflow_call:
     inputs:
EOF
@@ -1,5 +1,8 @@
name: Pester gate (service model pilot)

permissions:
contents: read

on:
workflow_call:
inputs:
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +89 to +105
if: ${{ !fromJSON(inputs.route_should_run || 'true') }}
runs-on: ubuntu-latest
steps:
- name: Append routed skip summary
shell: pwsh
run: |
if ($env:GITHUB_STEP_SUMMARY) {
$lines = @('### Pester gate (service model pilot)', '')
$lines += ('- Status: skipped')
$lines += ('- Reason: {0}' -f '${{ inputs.route_reason || 'route-should-not-run' }}')
$lines += ('- Trust mode: {0}' -f '${{ inputs.route_trust_mode || 'unspecified' }}')
$lines += ('- Checkout repository: {0}' -f '${{ inputs.checkout_repository || github.repository }}')
$lines += ('- Checkout ref: {0}' -f '${{ inputs.checkout_ref || github.sha }}')
$lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}

context:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI about 2 months ago

To fix the issue, add an explicit permissions: block that grants only the minimal access required. Because this workflow only orchestrates other reusable workflows and writes to the job summary, and does not itself perform repo writes, we can safely restrict permissions to read-only for code and actions metadata. A common minimal baseline is contents: read and packages: read; if you do not need packages, you can omit that as well. Since the problem is about the workflow lacking any permissions, the best fix is to add a root-level permissions: section (so it applies to all jobs that do not override it) directly under the name: (or at least before jobs:).

Concretely, in .github/workflows/pester-gate.yml, insert:

permissions:
  contents: read

right after the name: Pester gate (service model pilot) line (line 1). This establishes least-privilege, read-only repo access for the GITHUB_TOKEN across all jobs in this workflow, without changing any existing functional behavior.

Suggested changeset 1
.github/workflows/pester-gate.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/pester-gate.yml b/.github/workflows/pester-gate.yml
--- a/.github/workflows/pester-gate.yml
+++ b/.github/workflows/pester-gate.yml
@@ -1,5 +1,8 @@
 name: Pester gate (service model pilot)
 
+permissions:
+  contents: read
+
 on:
   workflow_call:
     inputs:
EOF
@@ -1,5 +1,8 @@
name: Pester gate (service model pilot)

permissions:
contents: read

on:
workflow_call:
inputs:
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +103 to +335
runs-on: ubuntu-latest
outputs:
classification: ${{ steps.classify.outputs.classification }}
total: ${{ steps.export.outputs.total }}
passed: ${{ steps.export.outputs.passed }}
failed: ${{ steps.export.outputs.failed }}
errors: ${{ steps.export.outputs.errors }}
duration_s: ${{ steps.export.outputs.duration_s }}
steps:
- uses: actions/checkout@v5

- name: Resolve raw artifact name
id: artifact_name
shell: pwsh
run: |
$artifactName = '${{ inputs.raw_artifact_name }}'
$shouldDownload = 'true'
if ('${{ inputs.execution_job_result }}' -in @('skipped','cancelled')) {
$shouldDownload = 'false'
} elseif ([string]::IsNullOrWhiteSpace($artifactName)) {
$artifactName = 'pester-run-raw'
}
"name=$artifactName" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"should_download=$shouldDownload" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8

- name: Download execution receipt artifact
uses: actions/download-artifact@v5
with:
name: ${{ inputs.execution_receipt_artifact_name }}
path: tests/execution-contract

- name: Download raw execution artifact
id: download
if: ${{ steps.artifact_name.outputs.should_download == 'true' }}
continue-on-error: true
uses: actions/download-artifact@v5
with:
name: ${{ steps.artifact_name.outputs.name }}
path: tests/results

- name: Ensure results directory
shell: pwsh
run: |
if (-not (Test-Path -LiteralPath 'tests/results')) {
New-Item -ItemType Directory -Path 'tests/results' -Force | Out-Null
}

- name: Validate execution receipt artifact
id: execution_receipt
if: always()
shell: pwsh
run: |
$receiptPath = Join-Path 'tests/execution-contract' 'pester-run-receipt.json'
if (-not (Test-Path -LiteralPath $receiptPath)) {
"present=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"status=missing" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
exit 0
}
. (Join-Path (Get-Location) 'tools/PesterServiceModelSchema.ps1')
$receiptState = Test-PesterServiceModelSchemaContract `
-DocumentState (Read-PesterServiceModelJsonDocument -PathValue $receiptPath -ContractName 'execution-receipt') `
-ExpectedSchema 'pester-execution-receipt@v1'
if (-not $receiptState.valid) {
"present=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"status=unsupported-schema" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"dispatcher_exit_code=-1" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"execution_pack=" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"execution_pack_source=" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
exit 0
}
$receipt = $receiptState.document
"present=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"status=$($receipt.status)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"dispatcher_exit_code=$($receipt.dispatcherExitCode)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"execution_pack=$($receipt.selectionExecutionPack)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"execution_pack_source=$($receipt.selectionExecutionPackSource)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8

- name: Validate Pester summary schema-lite (notice-only)
if: always()
continue-on-error: true
shell: pwsh
run: |
$json = Join-Path 'tests/results' 'pester-summary.json'
if (Test-Path $json) {
$schemas = @(
'docs/schemas/pester-summary-v1_7_1.schema.json',
'docs/schemas/pester-summary-v1_7.schema.json',
'docs/schemas/pester-summary-v1_6.schema.json',
'docs/schemas/pester-summary-v1_5.schema.json',
'docs/schemas/pester-summary-v1_4.schema.json',
'docs/schemas/pester-summary-v1_3.schema.json',
'docs/schemas/pester-summary-v1_2.schema.json',
'docs/schemas/pester-summary-v1_1.schema.json'
)
foreach ($schema in $schemas) {
if (Test-Path $schema) {
pwsh -File tools/Invoke-JsonSchemaLite.ps1 -JsonPath $json -SchemaPath $schema
if ($LASTEXITCODE -eq 0) { break }
}
}
}

- name: Ensure session index (fallback)
if: always()
continue-on-error: true
shell: pwsh
run: pwsh -File tools/Ensure-SessionIndex.ps1 -ResultsDir 'tests/results' -SummaryJson 'pester-summary.json'

- name: Wire Probe (S1)
if: always()
uses: ./.github/actions/wire-probe
with:
phase: S1
results-dir: tests/results

- name: Export Pester totals as outputs
id: export
if: always()
shell: pwsh
run: |
$sum = Join-Path 'tests/results' 'pester-summary.json'
if (Test-Path $sum) {
$js = Get-Content $sum -Raw | ConvertFrom-Json
"total=$($js.total)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"passed=$($js.passed)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"failed=$($js.failed)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"errors=$($js.errors)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"duration_s=$($js.duration_s)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
} else {
"total=0" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"passed=0" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"failed=0" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"errors=0" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"duration_s=0" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
}

- name: Session index post
if: always()
uses: ./.github/actions/session-index-post
with:
results-dir: tests/results
validate-schema: true
upload: true
artifact-name: session-index

- name: Write compact totals JSON
if: always()
shell: pwsh
run: pwsh -File tools/Write-PesterTotals.ps1 -ResultsDir 'tests/results'

- name: Classify evidence outcome
id: classify
if: always()
shell: pwsh
run: |
$rawArtifactDownload = if ('${{ steps.artifact_name.outputs.should_download }}' -eq 'true') { '${{ steps.download.outcome }}' } else { 'skipped' }
pwsh -File tools/Invoke-PesterEvidenceClassification.ps1 `
-ResultsDir 'tests/results' `
-ExecutionReceiptPath 'tests/execution-contract/pester-run-receipt.json' `
-ContextStatus '${{ inputs.context_status }}' `
-ReadinessStatus '${{ inputs.readiness_status }}' `
-SelectionStatus '${{ inputs.selection_status }}' `
-ExecutionJobResult '${{ inputs.execution_job_result }}' `
-DispatcherExitCode '${{ inputs.dispatcher_exit_code }}' `
-RawArtifactDownload $rawArtifactDownload

- name: Generate operator outcome
id: operator_outcome
if: always()
shell: pwsh
run: |
pwsh -File tools/Invoke-PesterOperatorOutcome.ps1 `
-ResultsDir 'tests/results' `
-ContinueOnError '${{ inputs.continue_on_error }}'

- name: Generate evidence provenance
id: evidence_provenance
if: always()
shell: pwsh
run: |
$rawArtifactDownload = if ('${{ steps.artifact_name.outputs.should_download }}' -eq 'true') { '${{ steps.download.outcome }}' } else { 'skipped' }
pwsh -File tools/Invoke-PesterEvidenceProvenance.ps1 `
-ResultsDir 'tests/results' `
-ExecutionReceiptPath 'tests/execution-contract/pester-run-receipt.json' `
-RawArtifactName '${{ steps.artifact_name.outputs.name }}' `
-RawArtifactDownload $rawArtifactDownload `
-ExecutionReceiptArtifactName '${{ inputs.execution_receipt_artifact_name }}' `
-OutputPath 'tests/results/pester-evidence-provenance.json'

- name: Publish Pester summary
if: always()
continue-on-error: true
shell: pwsh
run: pwsh -File scripts/Write-PesterSummaryToStepSummary.ps1 -ResultsDir 'tests/results'

- name: Append session summary
if: always()
continue-on-error: true
shell: pwsh
run: pwsh -File tools/Write-SessionIndexSummary.ps1 -ResultsDir 'tests/results'

- name: Append top Pester failures
if: always()
continue-on-error: true
shell: pwsh
run: pwsh -File tools/Write-PesterTopFailures.ps1 -ResultsDir 'tests/results' -Top 10

- name: Generate dev dashboard report
if: always()
continue-on-error: true
shell: pwsh
run: pwsh -File tools/Invoke-DevDashboard.ps1 -Group 'pester-selfhosted' -ResultsRoot 'tests/results'

- name: Upload evidence artifact
if: always()
uses: actions/upload-artifact@v7
with:
name: pester-evidence
path: tests/results
if-no-files-found: warn

- name: Propagate gate outcome
if: ${{ steps.classify.outputs.classification != 'ok' && inputs.continue_on_error != 'true' }}
shell: bash
run: |
echo "::error title=Pester gate outcome::classification=${{ steps.classify.outputs.classification }};next_action=${{ steps.operator_outcome.outputs.next_action }}"
if [[ -f tests/results/pester-evidence-classification.json ]]; then
cat tests/results/pester-evidence-classification.json
fi
if [[ -f tests/results/pester-operator-outcome.json ]]; then
cat tests/results/pester-operator-outcome.json
fi
exit 1

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI about 2 months ago

In general, fix this by adding an explicit permissions block that grants only the privileges the workflow needs. Since this workflow reads the repository (for scripts and schemas) and interacts with artifacts, it at minimum needs contents: read. It does not appear to need any write access to code, issues, or pull requests.

The best minimal fix without altering functionality is to add a root-level permissions block right after the name: line so it applies to all jobs, with contents: read. If, in this repo, artifact upload/download relies on additional scopes (in most cases they work with contents: read only), they’re already covered by that minimal scope. No job-level overrides are needed.

Concretely: edit .github/workflows/pester-evidence.yml to insert:

permissions:
  contents: read

between lines 1 and 3 (after name: Pester evidence and before on:). No imports or additional methods are required; this is purely a YAML configuration change.

Suggested changeset 1
.github/workflows/pester-evidence.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/pester-evidence.yml b/.github/workflows/pester-evidence.yml
--- a/.github/workflows/pester-evidence.yml
+++ b/.github/workflows/pester-evidence.yml
@@ -1,5 +1,8 @@
 name: Pester evidence
 
+permissions:
+  contents: read
+
 on:
   workflow_call:
     inputs:
EOF
@@ -1,5 +1,8 @@
name: Pester evidence

permissions:
contents: read

on:
workflow_call:
inputs:
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +55 to +186
runs-on: ubuntu-latest
outputs:
receipt_status: ${{ steps.receipt.outputs.status }}
receipt_artifact_name: ${{ steps.receipt.outputs.artifact_name }}
repository: ${{ steps.receipt.outputs.repository }}
standing_priority_issue: ${{ steps.receipt.outputs.standing_priority_issue }}
standing_priority_reason: ${{ steps.receipt.outputs.reason }}
steps:
- uses: actions/checkout@v5
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_ref || github.sha }}

- name: Install Node dependencies
shell: pwsh
run: node tools/npm/cli.mjs ci

- name: Validate repository context
shell: pwsh
run: |
$repository = '${{ inputs.checkout_repository || github.repository }}'
if ([string]::IsNullOrWhiteSpace($repository)) {
throw 'Repository context is empty.'
}

- name: Export workflow token for context sync
shell: pwsh
env:
WORKFLOW_TOKEN: ${{ github.token }}
run: |
if (-not $env:WORKFLOW_TOKEN) { throw 'github.token is empty' }
"GH_TOKEN=$env:WORKFLOW_TOKEN" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
"GITHUB_TOKEN=$env:WORKFLOW_TOKEN" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8

- name: Resolve standing-priority context
id: standing_context
continue-on-error: true
shell: pwsh
run: node tools/priority/run-sync-standing-priority.mjs --materialize-cache

- name: Write context receipt
id: receipt
if: always()
shell: pwsh
run: |
$outDir = 'tests/results/pester-context'
New-Item -ItemType Directory -Force -Path $outDir | Out-Null

$issueDir = 'tests/results/_agent/issue'
$routerPath = Join-Path $issueDir 'router.json'
$noStandingPath = Join-Path $issueDir 'no-standing-priority.json'
$repository = '${{ inputs.checkout_repository || github.repository }}'
if ([string]::IsNullOrWhiteSpace($repository)) {
$repository = $env:GITHUB_REPOSITORY
}

$status = 'blocked'
$reason = 'context-sync-missing'
$standingIssue = ''
$issueSummaryPath = $null
$syncOutcome = '${{ steps.standing_context.outcome }}'

if (Test-Path -LiteralPath $noStandingPath) {
$report = Get-Content -LiteralPath $noStandingPath -Raw | ConvertFrom-Json -ErrorAction Stop
$status = 'blocked'
$reason = if ($report.reason) { [string]$report.reason } elseif ($report.message) { [string]$report.message } else { 'standing-priority-missing' }
} elseif (Test-Path -LiteralPath $routerPath) {
$router = Get-Content -LiteralPath $routerPath -Raw | ConvertFrom-Json -ErrorAction Stop
$issueValue = 0
if ([int]::TryParse([string]$router.issue, [ref]$issueValue) -and $issueValue -gt 0) {
$standingIssue = [string]$issueValue
$issueSummaryPath = Join-Path $issueDir ("{0}.json" -f $standingIssue)
if (Test-Path -LiteralPath $issueSummaryPath) {
$issueSummary = Get-Content -LiteralPath $issueSummaryPath -Raw | ConvertFrom-Json -ErrorAction Stop
if ($issueSummary.schema -eq 'standing-priority/issue@v1') {
$status = 'ready'
$reason = 'standing-priority-available'
if ($issueSummary.url -match 'https://github.com/(?<slug>[^/]+/[^/]+)/issues/') {
$repository = $matches.slug
}
} else {
$status = 'warning'
$reason = ("unexpected-issue-schema:{0}" -f $issueSummary.schema)
}
} else {
$status = if ($syncOutcome -eq 'success') { 'warning' } else { 'blocked' }
$reason = if ($syncOutcome -eq 'success') { 'standing-priority-summary-missing' } else { 'context-sync-failed' }
}
} else {
$status = if ($syncOutcome -eq 'success') { 'warning' } else { 'blocked' }
$reason = if ($syncOutcome -eq 'success') { 'standing-priority-router-missing-issue' } else { 'context-sync-failed' }
}
} elseif ($syncOutcome -eq 'success') {
$status = 'warning'
$reason = 'standing-priority-router-missing'
} else {
$status = 'blocked'
$reason = 'context-sync-failed'
}

$receipt = [ordered]@{
schema = 'pester-context-receipt@v1'
generatedAtUtc = [DateTime]::UtcNow.ToString('o')
status = $status
repository = $repository
sampleId = '${{ inputs.sample_id || github.event.inputs.sample_id || '' }}'
standingPriority = [ordered]@{
issueNumber = if ($standingIssue) { [int]$standingIssue } else { $null }
reason = $reason
routerPath = if (Test-Path -LiteralPath $routerPath) { 'tests/results/_agent/issue/router.json' } else { $null }
issueSummaryPath = if ($issueSummaryPath -and (Test-Path -LiteralPath $issueSummaryPath)) { "tests/results/_agent/issue/$standingIssue.json" } else { $null }
noStandingPath = if (Test-Path -LiteralPath $noStandingPath) { 'tests/results/_agent/issue/no-standing-priority.json' } else { $null }
}
sync = [ordered]@{
outcome = $syncOutcome
}
}
$receiptPath = Join-Path $outDir 'pester-context.json'
$receipt | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $receiptPath -Encoding UTF8
"status=$status" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"artifact_name=pester-context" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"repository=$repository" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"standing_priority_issue=$standingIssue" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"reason=$reason" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8

- name: Upload context receipt
if: always()
uses: actions/upload-artifact@v7
with:
name: pester-context
path: tests/results/pester-context
if-no-files-found: error

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI about 2 months ago

In general, fix this by adding an explicit permissions block that grants only the minimal scopes needed. Since this workflow only checks out code, reads repo metadata, runs local tools, and uploads artifacts, it does not require write access to repository contents or issues. The minimal reasonable permission is contents: read, which allows actions/checkout to function. No jobs define their own permissions, so adding permissions at the workflow root (top-level, alongside name, on, concurrency, jobs) will apply to all jobs.

The best fix without changing existing functionality is to insert:

permissions:
  contents: read

after the on: block and before concurrency: (for clarity) in .github/workflows/pester-context.yml. No additional imports or methods are needed; this is purely a configuration change inside the workflow file. The rest of the job steps (checkout, Node/PowerShell commands, artifact upload) continue to work as before.

Suggested changeset 1
.github/workflows/pester-context.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/pester-context.yml b/.github/workflows/pester-context.yml
--- a/.github/workflows/pester-context.yml
+++ b/.github/workflows/pester-context.yml
@@ -46,6 +46,9 @@
         default: ''
         type: string
 
+permissions:
+  contents: read
+
 concurrency:
   group: pester-context-${{ github.event.inputs.sample_id || inputs.sample_id || github.ref }}
   cancel-in-progress: true
EOF
@@ -46,6 +46,9 @@
default: ''
type: string

permissions:
contents: read

concurrency:
group: pester-context-${{ github.event.inputs.sample_id || inputs.sample_id || github.ref }}
cancel-in-progress: true
Copilot is powered by AI and may make mistakes. Always verify output.
@svelderrainruiz svelderrainruiz merged commit 464313d into main Apr 1, 2026
50 of 52 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants