Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
a115bf4
Say the SignPath policy file is not enforced
erikdarlingdata Sep 25, 2026
47a9264
Merge pull request #572 from erikdarlingdata/chore/signpath-policy-no…
erikdarlingdata Sep 25, 2026
dc67258
build(deps): centralize NuGet package versions
nmummau Sep 26, 2026
5986976
Parse IF-condition query plans and MULTIPLE PLAN hashes (#580)
erikdarlingdata Sep 27, 2026
1ae7da5
Merge pull request #581 from erikdarlingdata/fix/parser-cond-multiple…
erikdarlingdata Sep 27, 2026
cc18844
Fix four analyzer rule gates (#577, #576, #579, #578)
erikdarlingdata Sep 27, 2026
dcec793
Mask comments and literals in rule 38 and the parameters panel (#579)
erikdarlingdata Sep 27, 2026
de1cac6
Drop rule 5's unreachable executions fallback
erikdarlingdata Sep 27, 2026
9984d47
Merge pull request #582 from erikdarlingdata/fix/analyzer-rules-577-5…
erikdarlingdata Sep 27, 2026
dcc06db
Key severity overrides on the rule that emitted the finding (#575)
erikdarlingdata Sep 27, 2026
126aeb3
Merge pull request #583 from erikdarlingdata/fix/severity-overrides-575
erikdarlingdata Sep 27, 2026
7f5004b
Skip the engine's own functions in rule 23
erikdarlingdata Sep 28, 2026
1424e60
Merge pull request #584 from erikdarlingdata/fix/rule23-builtin-tvf
erikdarlingdata Sep 28, 2026
5d56d3c
Keep an all-hex temp table name whole
erikdarlingdata Sep 28, 2026
1c06c6b
Cover an internal temp name with nothing before its padding
erikdarlingdata Sep 28, 2026
4d8a82b
Merge pull request #585 from erikdarlingdata/fix/temp-table-name-all-hex
erikdarlingdata Sep 28, 2026
37d2ec3
Harden HTML export, repro script header, and CLI encryption
erikdarlingdata Sep 28, 2026
ae75efc
Name Performance Studio in the repro script header
erikdarlingdata Sep 28, 2026
38ea2b6
Add review test cases and note the USE line's scope
erikdarlingdata Sep 28, 2026
85492a1
Merge pull request #586 from erikdarlingdata/fix/output-and-cli-harde…
erikdarlingdata Sep 28, 2026
d038f1c
Say on stderr which settings came from a .env file
erikdarlingdata Sep 28, 2026
2a57547
Take .env settings only when they have an effect, and reject control …
erikdarlingdata Sep 28, 2026
5b7a6c7
Merge pull request #588 from erikdarlingdata/fix/cli-env-notice
erikdarlingdata Sep 28, 2026
555d03a
Parse deep plans on a large-stack thread (#589)
erikdarlingdata Sep 28, 2026
5049cbd
Repro script: keep @0/@1 parameters, check data types by shape (#590)
erikdarlingdata Sep 28, 2026
6f66ff8
Review fixes for #589: Query Store MCP parse errors, share errors, tests
erikdarlingdata Sep 28, 2026
0283ed0
Merge pull request #591 from erikdarlingdata/fix/589-deep-plan-stack
erikdarlingdata Sep 28, 2026
940ff21
Review fixes for #590: declare each parameter once, ASCII digits only
erikdarlingdata Sep 28, 2026
68087ed
Repro script: use a later statement's value when the first has none
erikdarlingdata Sep 28, 2026
6e52102
Merge pull request #592 from erikdarlingdata/fix/590-repro-param-names
erikdarlingdata Sep 28, 2026
d9aa6bb
Harden plan loading: linear-time analyzer matching, one XML loader
erikdarlingdata Sep 28, 2026
87a02d9
Harden the app's local surfaces: pipe, About links, SSMS temp plans, …
erikdarlingdata Sep 28, 2026
426a031
PlanXml: limit namespace URI length and attributes per element
erikdarlingdata Sep 28, 2026
cff360e
Merge pull request #593 from erikdarlingdata/fix/harden-plan-loading
erikdarlingdata Sep 28, 2026
42155f9
Delete the SSMS handoff file only after the plan loads
erikdarlingdata Sep 28, 2026
027afdc
Merge pull request #596 from erikdarlingdata/fix/harden-app-surfaces
erikdarlingdata Sep 28, 2026
3c2590a
Fix #594: node label, edge color, and minimap divide by per-execution…
erikdarlingdata Sep 28, 2026
74501ce
Use the execution-aware estimate in the HTML export too
erikdarlingdata Sep 28, 2026
b34b1fb
Merge pull request #597 from erikdarlingdata/fix/594-per-execution-rows
erikdarlingdata Sep 28, 2026
6120750
Quarantine unreadable/corrupt settings files instead of overwriting t…
erikdarlingdata Sep 28, 2026
4cc0c2b
MCP host: report a failed start instead of showing Running
erikdarlingdata Sep 28, 2026
c7df535
Fix drill-down database not switching the session's toolbar (E1)
erikdarlingdata Sep 28, 2026
429d4af
Fix overlapping Query Store enabled checks on the grid's database pic…
erikdarlingdata Sep 28, 2026
573bc09
Fix overlapping database metadata fetches on the toolbar's picker (E7)
erikdarlingdata Sep 28, 2026
863a100
Report a refused settings save in the dialogs, and keep the server-fi…
erikdarlingdata Sep 28, 2026
77929e2
AtomicFile: refuse text with no UTF-8 form, as File.WriteAllText does
erikdarlingdata Sep 28, 2026
609ea43
Restart an Overview load that a tab switch cancelled mid-flight (E3)
erikdarlingdata Sep 28, 2026
0175bf4
Count a start tag's attributes in the text before XmlReader reads it
erikdarlingdata Sep 28, 2026
0017b56
Show the port and one short line for other MCP start failures
erikdarlingdata Sep 28, 2026
4d3596b
Test the unreadable-file block after the lock is released
erikdarlingdata Sep 28, 2026
829dece
Pin the count's behavior on a DOCTYPE with a quoted ">"
erikdarlingdata Sep 28, 2026
79813c5
Cancel superseded database checks without disposing their token sourc…
erikdarlingdata Sep 28, 2026
b579e45
Keep the keychain password off the command line on macOS
erikdarlingdata Sep 28, 2026
cd0dae0
Move the success test's summary back onto its test
erikdarlingdata Sep 28, 2026
f7eca0a
Show an underscore in an MCP failure reason as written
erikdarlingdata Sep 28, 2026
ddd2c4c
Merge pull request #600 from erikdarlingdata/fix/xml-attribute-prescan
erikdarlingdata Sep 28, 2026
2c7c988
Cancel a pending database metadata fetch when the session reconnects …
erikdarlingdata Sep 28, 2026
e78d02f
Block app settings saves for the rest of the process after a failed read
erikdarlingdata Sep 28, 2026
44dd389
Keep the pipe test's socket path within the macOS limit
erikdarlingdata Sep 28, 2026
c0e5700
Let a save's own read decide whether it may write
erikdarlingdata Sep 28, 2026
f4fcd0b
Merge pull request #598 from erikdarlingdata/fix/mcp-bind-failure
erikdarlingdata Sep 28, 2026
b99cfdd
Add storage limit, daily upload budget and one client key to PlanShare
erikdarlingdata Sep 28, 2026
525e05c
Point a Query Store plan's tab at the plan's own database
erikdarlingdata Sep 28, 2026
22a6849
Merge pull request #601 from erikdarlingdata/fix/keychain-password-of…
erikdarlingdata Sep 28, 2026
13ec8a1
Show share errors, send the delete token in a header, fix the dashboard
erikdarlingdata Sep 28, 2026
230b92a
Tighten the limits comment and close pooled connections in the test app
erikdarlingdata Sep 28, 2026
20bb9d9
Merge pull request #602 from erikdarlingdata/fix/drilldown-database
erikdarlingdata Sep 28, 2026
4adf56c
Use the ApiBase constant in the share service tests
erikdarlingdata Sep 28, 2026
6e7d553
Report a blocked settings save, and quarantine safely when two reader…
erikdarlingdata Sep 28, 2026
996e44e
Show a message instead of crashing on a plan with no statements
erikdarlingdata Sep 28, 2026
d6234ec
Exit nonzero from query-store when any plan fails to analyze
erikdarlingdata Sep 28, 2026
9cc0553
Refuse an unknown --output value instead of doing nothing
erikdarlingdata Sep 28, 2026
e55ded6
Unregister a plan when the tab that holds it is closed (E2)
erikdarlingdata Sep 28, 2026
f21c7b7
Refuse an unknown --order-by value in query-store
erikdarlingdata Sep 28, 2026
b152357
Merge pull request #599 from erikdarlingdata/fix/settings-read-failure
erikdarlingdata Sep 28, 2026
f9e5688
Merge pull request #603 from erikdarlingdata/fix/planshare-limits
erikdarlingdata Sep 28, 2026
c81161a
Make a loading tab's Escape and Cancel stop only its own run (E8)
erikdarlingdata Sep 28, 2026
ff57c45
Accept --output in any letter case
erikdarlingdata Sep 28, 2026
c30613e
Keep the --output values in help and completion
erikdarlingdata Sep 28, 2026
a6fafbc
deps: Bump Avalonia and 6 others
dependabot[bot] Sep 28, 2026
2f58e25
Closing a tab stops the work the tab owns (E4)
erikdarlingdata Sep 28, 2026
bbe0d5e
Escape on another tab no longer cancels a running capture (E8)
erikdarlingdata Sep 28, 2026
b428988
Merge pull request #604 from erikdarlingdata/fix/cli-exit-codes-web-g…
erikdarlingdata Sep 28, 2026
b78ed21
Merge pull request #606 from erikdarlingdata/fix/tab-close-cleanup
erikdarlingdata Sep 28, 2026
11aa46d
Rule 9: flag a grant of 1 GB or more that used none of it
erikdarlingdata Sep 28, 2026
dbdea1d
Server time display reads the connection's own offset, not a process-…
erikdarlingdata Sep 28, 2026
1964013
Rule 33: name each CE default guess for what actually produces it
erikdarlingdata Sep 28, 2026
82df0c0
Reconnect test also checks the times each document shows (E5)
erikdarlingdata Sep 28, 2026
d59ac4c
Rule 35: do not name an exchange as the expensive operator
erikdarlingdata Sep 28, 2026
fd5007f
Open the time display box on the mode in effect, and redraw the ribbo…
erikdarlingdata Sep 28, 2026
85f9aca
Merge pull request #608 from erikdarlingdata/fix/analyzer-grant-ce-la…
erikdarlingdata Sep 28, 2026
7b7ea17
Merge pull request #607 from erikdarlingdata/fix/server-offset-per-co…
erikdarlingdata Sep 28, 2026
84595ae
A second window started with --new-instance no longer duplicates or o…
erikdarlingdata Sep 28, 2026
2a63145
Check the owner's files right after a secondary starts, and note why …
erikdarlingdata Sep 28, 2026
bca4cea
Merge pull request #605 from erikdarlingdata/dependabot/nuget/src/Pla…
erikdarlingdata Sep 28, 2026
a97961c
Say what a secondary loses: session restore for its own tabs
erikdarlingdata Sep 28, 2026
1fe52b8
Pin every workflow action to a full commit SHA
erikdarlingdata Sep 28, 2026
7029b0e
Sign the SSMS extension and installer with SignPath
erikdarlingdata Sep 28, 2026
d019ef4
A secondary does not start the pipe server
erikdarlingdata Sep 28, 2026
a6636a8
Say that the secondary's settings save can still lose a concurrent ow…
erikdarlingdata Sep 28, 2026
6808925
Merge pull request #609 from erikdarlingdata/fix/new-instance-session
erikdarlingdata Sep 28, 2026
74096a2
Make the SSMS replace and release-upload steps fail safe
erikdarlingdata Sep 28, 2026
8e3147e
Merge pull request #610 from erikdarlingdata/ci/pin-actions-sign-vsix
erikdarlingdata Sep 28, 2026
2caf584
Check the VSIX signature before a signed installer installs it
erikdarlingdata Sep 29, 2026
fb89b3f
Say in the README that a signed installer checks the VSIX signature
erikdarlingdata Sep 29, 2026
33164ba
Check every ZIP entry of the VSIX against the signature
erikdarlingdata Sep 29, 2026
b22fea5
Ship the license as LICENSE.txt inside the VSIX
erikdarlingdata Sep 29, 2026
020642e
Compare the signer certificate before verifying the signature
erikdarlingdata Sep 29, 2026
3198bc7
Merge pull request #612 from erikdarlingdata/fix/installer-vsix-signa…
erikdarlingdata Sep 29, 2026
fa97cdd
Nest Early abort under Optimization and list CE model above it (#613)
erikdarlingdata Sep 29, 2026
97fd567
Make the node row label agree with its percentage (#611)
erikdarlingdata Sep 29, 2026
0c2fd6c
Merge dev into feat/central-pack
erikdarlingdata Sep 29, 2026
8b88e48
Merge pull request #574 from nmummau/feat/central-pack
erikdarlingdata Sep 29, 2026
ad0e6e4
Merge pull request #614 from erikdarlingdata/fix/613-nest-early-abort
erikdarlingdata Sep 29, 2026
82d3799
Merge pull request #615 from erikdarlingdata/fix/611-row-label-agrees
erikdarlingdata Sep 29, 2026
d69fd3b
Bump version to 1.28.0
erikdarlingdata Sep 29, 2026
85b269c
Merge pull request #619 from erikdarlingdata/release/v1.28.0
erikdarlingdata Sep 29, 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
4 changes: 2 additions & 2 deletions .github/workflows/check-version-bump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:

steps:
- name: Checkout PR branch
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Get PR version
id: pr
Expand All @@ -29,7 +29,7 @@ jobs:
Write-Host "PR version: $version (from $path)"

- name: Checkout main
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: main
path: main-branch
Expand Down
15 changes: 10 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,43 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

# What used to be the workflow's paths-ignore list. Anything NOT matched here is
# code, and only then is there anything to build.
- name: Classify changed paths
uses: dorny/paths-filter@v4
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
id: filter
with:
# Positive list, not negations. paths-filter ORs the patterns in a filter, so a
# stack of '!' patterns matches whenever a file fails ANY one of them, which for
# a docs-only change is always true. Listing what IS code keeps the OR honest.
# PlanViewer.Ssms and PlanViewer.Ssms.Installer stay out: they are not in the
# solution and ci.yml never built them.
# solution and ci.yml never built them. server/PlanShare is in: the test project
# references it, so this job builds and tests it.
filters: |
code:
- 'src/PlanViewer.App/**'
- 'src/PlanViewer.Cli/**'
- 'src/PlanViewer.Core/**'
- 'src/PlanViewer.Web/**'
- 'server/PlanShare/**'
- 'src/Directory.Build.props'
- 'Directory.Packages.props'
- 'tests/**'
- 'PlanViewer.sln'
- 'global.json'
- '.github/workflows/ci.yml'

- name: Setup .NET 10.0
if: steps.filter.outputs.code == 'true'
uses: actions/setup-dotnet@v6
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: 10.0.x
cache: true
cache-dependency-path: '**/*.csproj'
cache-dependency-path: |
**/*.csproj
Directory.Packages.props

- name: Install WASM workload
if: steps.filter.outputs.code == 'true'
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1

- uses: anthropics/claude-code-action@v1
- uses: anthropics/claude-code-action@8ce9314fa9a404564fa7e954cd84f25bcba2b829 # v1.0.236
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1

- uses: anthropics/claude-code-action@v1
- uses: anthropics/claude-code-action@8ce9314fa9a404564fa7e954cd84f25bcba2b829 # v1.0.236
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: |
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/deploy-planshare.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
branches: [main]
paths:
- 'server/PlanShare/**'
- 'Directory.Packages.props'
- '.github/workflows/deploy-planshare.yml'
workflow_dispatch:

Expand All @@ -31,10 +32,10 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Setup .NET 10.0
uses: actions/setup-dotnet@v6
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: 10.0.x

Expand Down
9 changes: 5 additions & 4 deletions .github/workflows/deploy-web.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
- 'src/PlanViewer.Core/**'
- 'src/PlanViewer.Web/**'
- 'src/Directory.Build.props'
- 'Directory.Packages.props'
- '.github/workflows/deploy-web.yml'
workflow_dispatch:

Expand All @@ -27,10 +28,10 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }}

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Setup .NET 10.0
uses: actions/setup-dotnet@v6
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: 10.0.x

Expand All @@ -50,10 +51,10 @@ jobs:
run: cp publish/wwwroot/index.html publish/wwwroot/404.html

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v5
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: publish/wwwroot

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
6 changes: 3 additions & 3 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
outputs:
has_changes: ${{ steps.check.outputs.has_changes }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: dev
fetch-depth: 0
Expand All @@ -38,12 +38,12 @@ jobs:
runs-on: windows-latest

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: dev

- name: Setup .NET 10.0
uses: actions/setup-dotnet@v6
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: 10.0.x

Expand Down
126 changes: 119 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: windows-latest

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Get version
id: version
Expand Down Expand Up @@ -44,7 +44,7 @@ jobs:
# window lapsed. Nothing before the signing step publishes anything now.

- name: Setup .NET 10.0
uses: actions/setup-dotnet@v6
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: 10.0.x

Expand All @@ -68,7 +68,7 @@ jobs:
# The build step never fails the job (it only sets an output on success),
# so a VSIX build failure can never block the cross-platform app release.
- name: Add MSBuild to PATH
uses: microsoft/setup-msbuild@v3
uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0
continue-on-error: true

- name: Build SSMS extension
Expand Down Expand Up @@ -124,7 +124,7 @@ jobs:

- name: Upload Windows build for signing
id: upload-unsigned
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: App-unsigned
path: publish/win-x64/
Expand All @@ -136,7 +136,7 @@ jobs:
# fix the cause and start a fresh run (a new dev -> main merge) instead of
# re-running the same one.
- name: Sign Windows build
uses: signpath/github-action-submit-signing-request@v3
uses: signpath/github-action-submit-signing-request@f6d04783b4569d051e0c80105fe66e82819d0092 # v3.0
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
organization-id: '7969f8b6-d946-4a74-9bac-a55856d8b8e0'
Expand All @@ -160,6 +160,107 @@ jobs:
Remove-Item -Recurse -Force publish/win-x64
Copy-Item -Recurse signed/win-x64 publish/win-x64

# ── SignPath code signing for the SSMS extension and its installer.
# Unlike the App, this is best-effort. If a step below fails, or an
# approval times out, the release still goes out with the unsigned
# files (as it did before these were signed) and a warning annotation
# says so. The one exception is in the replace step: if it cannot put
# the unsigned files back after a failed copy or a failed signature
# check, the job stops before anything is published. Each SignPath
# request needs a manual approval, so a release run now waits for two:
# the App first, then these two files. ──
- name: Stage SSMS files for signing
if: steps.ssms.outputs.BUILT == 'true'
continue-on-error: true
shell: pwsh
run: |
# Exactly these two files, at the root of one folder. The SignPath
# "Vsix" artifact configuration matches them by that root-relative path.
New-Item -ItemType Directory -Force -Path ssms-unsigned | Out-Null
Copy-Item releases/InstallSsmsExtension.exe ssms-unsigned/InstallSsmsExtension.exe
Copy-Item releases/PlanViewer.Ssms.vsix ssms-unsigned/PlanViewer.Ssms.vsix

- name: Upload SSMS files for signing
id: upload-ssms-unsigned
if: steps.ssms.outputs.BUILT == 'true'
continue-on-error: true
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Ssms-unsigned
path: ssms-unsigned/
if-no-files-found: error

# Same organization, project, policy and timeout as the App request above.
# Only the artifact configuration differs. "Vsix" is a zip whose root holds
# InstallSsmsExtension.exe (Authenticode) and PlanViewer.Ssms.vsix (OPC
# signature, plus Authenticode on PlanViewer.Ssms.dll, which sits at the
# root of the VSIX).
- name: Sign SSMS extension and installer
id: sign-ssms
if: steps.ssms.outputs.BUILT == 'true'
continue-on-error: true
uses: signpath/github-action-submit-signing-request@f6d04783b4569d051e0c80105fe66e82819d0092 # v3.0
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
organization-id: '7969f8b6-d946-4a74-9bac-a55856d8b8e0'
project-slug: 'PerformanceStudio'
signing-policy-slug: 'release-signing'
artifact-configuration-slug: 'Vsix'
github-artifact-id: '${{ steps.upload-ssms-unsigned.outputs.artifact-id }}'
wait-for-completion: true
output-artifact-directory: 'signed/ssms'
wait-for-completion-timeout-in-seconds: 1800

# Copy the signed files over the ones in releases/ only when signing
# succeeded (steps.<id>.outcome is the result before continue-on-error, so
# it is never 'success' for a failed step). Every later step reads the
# files from releases/: the release upload, the SSMS Gallery upload and the
# checksums.
- name: Replace unsigned SSMS files with signed
if: steps.ssms.outputs.BUILT == 'true' && steps.sign-ssms.outcome == 'success'
shell: pwsh
run: |
# Copy both files or neither, so one signed file never ships beside an
# unsigned one. The signed installer must also accept the signed VSIX:
# --verify-only runs the installer's signature check and installs
# nothing. If a copy or that check fails, put the unsigned pair back
# from ssms-unsigned/. If that fails too, the step fails and the job
# stops before the release is created, so a mixed pair is never
# published.
$names = 'InstallSsmsExtension.exe', 'PlanViewer.Ssms.vsix'
$missing = @($names | Where-Object { -not (Test-Path "signed/ssms/$_") })
if ($missing.Count -gt 0) {
Write-Host "::warning::SignPath reported success but these signed files are missing: $($missing -join ', '). PlanViewer.Ssms.vsix and InstallSsmsExtension.exe shipped unsigned."
} else {
try {
foreach ($name in $names) {
Copy-Item "signed/ssms/$name" "releases/$name" -Force -ErrorAction Stop
}
& releases/InstallSsmsExtension.exe --verify-only releases/PlanViewer.Ssms.vsix
if ($LASTEXITCODE -ne 0) {
throw "the installer rejected the signed VSIX with exit code $LASTEXITCODE (the reason is in the log above)"
}
Write-Host 'Replaced the SSMS extension and installer with the signed files.'
} catch {
$problem = $_.Exception.Message
foreach ($name in $names) {
Copy-Item "ssms-unsigned/$name" "releases/$name" -Force -ErrorAction Stop
}
# The failed check left a non-zero exit code, and the shell ends the
# script with `exit $LASTEXITCODE`. The failure is handled, so reset it.
$global:LASTEXITCODE = 0
Write-Host "::warning::Could not use the signed SSMS files: $problem. PlanViewer.Ssms.vsix and InstallSsmsExtension.exe shipped unsigned."
}
}

- name: Warn that SSMS files are unsigned
if: steps.ssms.outputs.BUILT == 'true' && steps.sign-ssms.outcome != 'success'
shell: pwsh
env:
SIGN_OUTCOME: ${{ steps.sign-ssms.outcome }}
run: |
Write-Host "::warning::SSMS signing did not succeed (result: $env:SIGN_OUTCOME). PlanViewer.Ssms.vsix and InstallSsmsExtension.exe shipped unsigned."

# ── Everything above this line is reversible: nothing has been published.
# The release is created only now that signed binaries exist. ──
- name: Create release
Expand All @@ -170,8 +271,12 @@ jobs:
run: |
gh release create "v${{ steps.version.outputs.VERSION }}" --title "v${{ steps.version.outputs.VERSION }}" --generate-notes --target main

# releases/ holds the signed copies here when the SSMS signing above succeeded.
# continue-on-error: the release exists by now, so a failed SSMS upload must
# not stop the App files from being uploaded below.
- name: Upload SSMS extension to release
if: steps.ssms.outputs.BUILT == 'true'
continue-on-error: true
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down Expand Up @@ -215,6 +320,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.VERSION }}
SSMS_BUILT: ${{ steps.ssms.outputs.BUILT }}
run: |
New-Item -ItemType Directory -Force -Path releases

Expand Down Expand Up @@ -271,8 +377,14 @@ jobs:
python .github/scripts/zip_with_exec.py "$wrapperDir" "releases/PerformanceStudio-$rid.zip" --exec "PerformanceStudio.app/Contents/MacOS/PlanViewer.App" --exec-optional "PerformanceStudio.app/Contents/MacOS/createdump"
}

# Checksums (zips only, Velopack has its own checksums)
$checksums = Get-ChildItem releases/*.zip | ForEach-Object {
# Checksums: the zips, plus the SSMS extension and installer when they
# were built (the signed copies when signing succeeded). Velopack has
# its own checksums.
$files = @(Get-ChildItem releases/*.zip)
if ($env:SSMS_BUILT -eq 'true') {
$files += Get-Item releases/PlanViewer.Ssms.vsix, releases/InstallSsmsExtension.exe
}
$checksums = $files | ForEach-Object {
$hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower()
"$hash $($_.Name)"
}
Expand Down
23 changes: 12 additions & 11 deletions .signpath/policies/PerformanceStudio/release-signing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
# project-slug: PerformanceStudio
# signing-policy-slug: release-signing
#
# This file only takes effect from the repository's DEFAULT branch (main). On any
# other branch it is inert, so changes here do nothing until they are merged to
# main via the usual dev -> main release merge.
#
# Since release.yml moved to v3 of signpath/github-action-submit-signing-request
# (SignPath's Pipeline Connector), SignPath reads this file only if the signing
# policy references it as a Pipeline Policy in the SignPath dashboard (SignPath
# changelog, Pipeline Connector 0.8.0, 2026-09-09). Without that reference the
# runner rule below is not enforced.
# NOT ENFORCED. Since release.yml moved to v3 of
# signpath/github-action-submit-signing-request (SignPath's Pipeline Connector),
# SignPath reads this file only if the signing policy references it as a
# Pipeline Policy (SignPath changelog, Pipeline Connector 0.8.0, 2026-09-09).
# This organization is on SignPath's OSS subscription, and its dashboard shows
# no Pipeline Policy setting (checked 2026-09-25), so nothing references this
# file and SignPath does not check the rule below. The file records the intended
# rule in case that setting becomes available. The v2 connector read this file
# automatically, but only from the repository's default branch (main).
#
# What this file does NOT do: it does not enable automatic approval. Approval mode
# lives on the signing policy in the SignPath dashboard. This file adds constraints
# that SignPath enforces on a build before it is willing to sign it.
# lives on the signing policy in the SignPath dashboard, next to the checks that
# do apply today: trusted build system, origin verification and allowed branch
# names.

github-policies:
runners:
Expand Down
Loading
Loading