Release Profiles
Picket publishes RID-specific Native AOT release archives and RID-specific Native AOT NuGet tool packages for the scanner CLI and the TUI companion. Always pass an explicit runtime identifier with -r so each archive artifact and RID tool package is tied to the target platform.
The top-level NuGet tool packages are pointer packages. The RID-specific packages carry the native executables, and the .NET CLI selects the matching package during dotnet tool install.
dotnet publish src/Picket.Cli/Picket.Cli.csproj -p:PublishProfile=release-speed -r win-x64dotnet publish src/Picket.Tui.Cli/Picket.Tui.Cli.csproj -p:PublishProfile=release-speed -r win-x64dotnet publish src/Picket.Cli/Picket.Cli.csproj -p:PublishProfile=release-minsize -r linux-x64dotnet publish src/Picket.Tui.Cli/Picket.Tui.Cli.csproj -p:PublishProfile=release-minsize -r linux-x64dotnet publish src/Picket.Cli/Picket.Cli.csproj -p:PublishProfile=release-diagnostics -r osx-arm64dotnet publish src/Picket.Tui.Cli/Picket.Tui.Cli.csproj -p:PublishProfile=release-diagnostics -r osx-arm64Profiles
Section titled “Profiles”| Profile | Purpose | Key settings |
|---|---|---|
release-speed |
Default public CLI and TUI companion artifacts. | Native AOT, self-contained, single-file publish, speed optimization, no public debug-symbol output, diagnostics reduced. |
release-minsize |
Smallest supported artifact for package managers, containers, and constrained runners. | Native AOT, size optimization, no public debug-symbol output, stack traces off, EventSource and metrics off, resource-key exception messages. |
release-diagnostics |
Support artifact for bug reports and performance investigations. | Native AOT, speed optimization, symbols kept, debugger support, EventSource, metrics, stack traces, and HTTP activity propagation enabled. |
All profiles keep unsafe BinaryFormatter serialization, UTF-7, metadata update support, XML resolver networking by default, and HTTP/3 disabled. These switches are explicit so size and diagnostics tradeoffs are visible in review.
release-speed and release-minsize set DebugSymbols=false, DebugType=none, NativeDebugSymbols=false, and CopyOutputSymbolsToPublishDirectory=false for every RID. A post-publish target runs after the Native AOT symbol-copy target to remove residual compiler and reference symbol sidecars, and CI rejects the artifact if .pdb, .dbg, .dwarf, or .dSYM output remains. Current macOS RIDs also set StripSymbols=false until the .NET runtime pack includes the Apple Native AOT Swift module debug-info fix tracked by dotnet/runtime#123687 and dotnet/runtime#124336. That linker workaround avoids transient clang module-cache .pcm warnings without publishing debug sidecars. Use release-diagnostics when native debug information is needed.
Embeddable library packages publish .snupkg source-symbol packages. Framework-dependent tool pointer packages and RID-specific Native AOT tool packages set IncludeSymbols=false and do not publish symbol packages.
framework-dev refers to the normal framework-dependent dotnet build and dotnet run developer mode. It is not a .pubxml publish profile or a release artifact.
release-minsize must not change scanner findings, rule behavior, reports, validation states, or exit-code classification. It may reduce diagnostic richness only.
NuGet Package Validation
Section titled “NuGet Package Validation”Every CI run packs the public embeddable library packages after build and test:
Picket.RulesPicket.EnginePicket.CompatPicket.ReportPicket.Security
Every CI run also packs the Native AOT dotnet tool packages:
Picket, withToolCommandName=picketPicket.Tui.Cli, withToolCommandName=picket-tui
Every solution project commits a NuGet packages.lock.json. The lock files pin resolved versions and package content hashes, including the ZstdNet package that supplies native zstandard libraries. CI, release, container, GitHub Action, and Azure Pipelines builds restore with --locked-mode, so dependency drift or a package payload mismatch fails before compilation.
The release workflow packs each tool twice: once as a top-level pointer package, and once per supported RID with dotnet pack -r <RID>. RID-specific packages are pushed to NuGet before the pointer packages so installs can resolve immediately.
Every public NuGet package, RID release archive, container image, MSI, and package-manager installation carries LICENSE, README.md where the format supports it, the package icon where applicable, and THIRD-PARTY-NOTICES.txt. The notices cover the embedded Gitleaks rule corpus, managed dependencies compiled into Native AOT binaries, Hex1b-transitive assets, Scout packages, and bundled zstandard runtimes.
The CI pack gate runs on Windows, Linux, and macOS so package metadata, root readme packaging, icon packaging, symbol packages, project-reference dependencies, tool manifests, and cross-platform MSBuild paths are validated before release automation consumes the same projects.
Native AOT Publish Validation
Section titled “Native AOT Publish Validation”Every CI run also publishes picket and picket-tui with release-speed for the runner’s native RID, and the Linux jobs additionally validate the matching musl RID:
linux-x64onubuntu-latestlinux-arm64onubuntu-24.04-armlinux-musl-x64onubuntu-latestlinux-musl-arm64onubuntu-24.04-armwin-x64onwindows-latestwin-arm64onwindows-11-armosx-x64onmacos-26-intelosx-arm64onmacos-26
This is the analyzer gate for Native AOT, trimming, single-file compatibility, and RID-specific publish behavior. A normal dotnet build is not enough evidence that the shipped executables can be produced.
The Linux jobs install musl-tools, build the decompression-only zstandard 1.5.7 runtime from its SHA-256-pinned upstream archive, and pass it to scripts/Publish-LinuxMusl.cs. The pinned archive hash matches Meta’s published zstd-1.5.7.tar.gz.sha256 release asset. The publish app builds both executables and RID-specific tool packages in a digest-pinned Alpine AOT SDK image and rejects an executable unless its ELF interpreter is musl. Picket.Cli also fails when PICKET_ZSTANDARD_MUSL_LIBRARY does not identify the verified runtime, preventing NuGet’s generic Linux asset fallback from placing a glibc library in Alpine artifacts. CI and release jobs then run the published scanner against a generated zstandard-compressed fixture in a digest-pinned .NET runtime-deps Alpine container, so a mislabeled executable, missing library, unloadable runtime, or nonfunctional decompressor fails before packaging.
For a manual musl publish on Linux:
dotnet run --file ./scripts/Build-ZstandardMusl.cs -- -OutputDirectory ./artifacts/zstd-musldotnet run --file ./scripts/Publish-LinuxMusl.cs -- -RuntimeIdentifier linux-musl-x64 -OutputDirectory ./artifacts/picket-linux-musl-x64 -ZstandardLibrary ./artifacts/zstd-musl/libzstd.soContainer Image
Section titled “Container Image”The repository root Dockerfile builds a local Linux image from source using the same release-speed Native AOT profile as the release archives. Release jobs do not rebuild scanner code inside Docker. They extract the already tested linux-x64 and linux-arm64 release archives and build each image from packaging/container/Dockerfile on a native runner. The image includes:
picketas the entrypoint,picket-tuibeside it forpicket tui,gitfor Gitleaks-compatiblepicket githistory scans,- CA certificates for source-host, object-store, and live-verification HTTPS calls,
- Git system configuration that trusts
/workfor mounted repository scans, - a non-root default user from the .NET runtime-deps image.
Build locally for the current machine with:
docker build -t picket:dev .docker run --rm -v ${PWD}:/work picket:dev scan . --report-format jsonl --redact=100On Windows, run the WSL container CLI example from PowerShell:
wslc image build -t picket:dev .wslc run --rm -v ${PWD}:/work picket:dev scan . --report-format jsonl --redact=100wslc run --rm -v ${PWD}:/work picket:dev git . --report-format json --redact=100For release tags, .github/workflows/release.yml validates and publishes linux/amd64 on an x64 runner and linux/arm64 on an Arm64 runner, then creates the multi-architecture manifest without emulation. The published tags are:
ghcr.io/willibrandon/picket:<release-tag>, for examplev0.1.13,ghcr.io/willibrandon/picket:<semver>, for example0.1.13,ghcr.io/willibrandon/picket:latestfor non-prerelease versions only.
Each architecture image carries BuildKit SBOM and provenance attestations. The immutable version manifests are published only after the GitHub Release exists, and latest advances only after both the versioned container and NuGet publication succeed. The image is a scanner distribution surface; it does not change command defaults, compatibility behavior, reports, validation policy, or telemetry policy.
Package Manager Manifests
Section titled “Package Manager Manifests”Release automation generates package-manager submission files from the actual release archive names and SHA-256 checksums:
homebrew/picket.rbfor a Homebrew tap.scoop/picket.jsonfor a Scoop bucket.winget/willibrandon.picket/<version>/for WinGet package submission.
The generated files are packaged as picket-<tag>-package-manager-manifests.zip and uploaded to the GitHub Release with its own .sha256 sidecar. The final aggregate checksums.txt includes the manifest bundle.
Generate the same files locally from a release checksum file with:
dotnet run --file ./scripts/Generate-PackageManagerManifests.cs -- -ReleaseTag v0.1.13 -ChecksumsPath dist/checksums.txt -OutputDirectory artifacts/package-managers -CleanThe manifests install the Native AOT picket and picket-tui executables from the RID archives. Homebrew keeps the complete archive payload under libexec and writes command wrappers so the bundled zstandard library remains beside both executables. Scoop and WinGet retain the complete portable ZIP payload.
Windows portable archives contain only the supported picket.exe and picket-tui.exe entry points. CI and release jobs reject unexpected executables and invoke picket-tui without arguments under redirected input and output, matching WinGet’s post-install executable validation. In an interactive terminal, the same no-argument command opens the scan workspace.
For stable tags, the release workflow validates and commits Formula/picket.rb to willibrandon/homebrew-tap, commits bucket/picket.json to willibrandon/scoop-bucket, and updates both repository catalogs. It also validates the WinGet manifest set, pushes it to willibrandon/winget-pkgs, and opens or reuses a pull request against microsoft/winget-pkgs. Prerelease tags publish the manifest bundle with the GitHub Release but do not update package repositories.
Windows MSI Installers
Section titled “Windows MSI Installers”Release automation builds MSI installers for stable win-x64 and win-arm64 releases from the same Windows release ZIP payloads. The WiX source lives at packaging/msi/Picket.wxs; it installs picket.exe, picket-tui.exe, libzstd.dll, LICENSE, and THIRD-PARTY-NOTICES.txt under Program Files\Picket and adds that install folder to the machine PATH.
The MSI artifacts are named:
picket-<tag>-win-x64.msipicket-<tag>-win-arm64.msi
Each MSI is built, installed, and tested on a matching Windows runner architecture. The test installs into a temporary directory, exercises a zstandard scan through the installed executable, verifies the full payload, and uninstalls it before release assembly. Each artifact then receives a .sha256 sidecar and a GitHub artifact attestation. Prerelease tags skip MSI artifacts because Windows Installer ProductVersion does not support SemVer prerelease labels. MSI packaging does not rebuild scanner code and does not change CLI defaults, report behavior, validation policy, or telemetry policy.
Azure DevOps VSIX Validation
Section titled “Azure DevOps VSIX Validation”CI installs the locked Azure DevOps packaging dependencies without lifecycle scripts, fails on any npm advisory, and packages the Marketplace scaffold on Ubuntu with tfx-cli and azure-devops/vss-extension.json. This validates the extension manifest, PicketScan@1 task metadata, included files, and VSIX layout before release automation consumes the same wrapper.
Release automation also packages stable-tag VSIX artifacts with the release version applied to the extension manifest:
picket-<tag>-azure-devops.vsix
The VSIX receives a .sha256 sidecar and a GitHub artifact attestation before the GitHub Release is created or updated. Prerelease tags skip VSIX artifacts because Azure DevOps extension manifest versions do not support SemVer prerelease labels.
For a stable tag, the Release workflow automatically revalidates the VSIX checksum, identity, required package contents, and artifact attestation before publishing it with AZURE_DEVOPS_MARKETPLACE_PAT. After Azure DevOps publication succeeds, the workflow advances the GitHub Action vMAJOR tag to the immutable release commit and verifies that GitHub Marketplace exposes the exact immutable release tag. GitHub requires the repository owner to publish the Action release through the release page before that verification can pass. Reruns detect an already-published Azure DevOps extension version and continue without attempting to overwrite it. See docs/MARKETPLACES.md for release ordering, visibility, and rollback behavior.
Documentation Validation
Section titled “Documentation Validation”The docs workflow builds the static site, verifies generated docs are current, and checks local documentation links and fragments under the GitHub Pages /picket/ base path. External links are left to source review so the offline docs build stays deterministic.
CI Picket Scan Validation
Section titled “CI Picket Scan Validation”CI runs the local composite action against the repository root on Windows, Linux, and macOS with cache, annotations, and SARIF upload disabled. The scan step keeps the Action summary enabled, uses fail-on: never for the repository’s intentional test fixtures, and asserts that the action reports at least one finding and writes both picket.sarif and picket.jsonl.
Runtime Identifiers
Section titled “Runtime Identifiers”Use the target RID in the publish command. Common release RIDs are:
| Platform | RID |
|---|---|
| Windows x64 | win-x64 |
| Windows Arm64 | win-arm64 |
| Linux x64 | linux-x64 |
| Linux Arm64 | linux-arm64 |
| Linux musl x64 | linux-musl-x64 |
| Linux musl Arm64 | linux-musl-arm64 |
| macOS x64 | osx-x64 |
| macOS Arm64 | osx-arm64 |
Release automation publishes, checksums, attests, and archives each RID separately. The picket and picket-tui binaries are staged into the same RID archive so picket tui can discover the companion beside the scanner.
Release Workflow
Section titled “Release Workflow”Tags that match v*.*.* run .github/workflows/release.yml. The workflow can also be run manually for an existing tag.
The workflow validates the source tree and runs the local GitHub Action smoke test. It builds release-speed Native AOT binary archives for linux-x64, linux-arm64, linux-musl-x64, linux-musl-arm64, win-x64, win-arm64, osx-x64, and osx-arm64; builds and installs Windows MSI installers; packages the Azure DevOps VSIX; validates native container images; and packages the public NuGet libraries, top-level tool pointer packages, and RID-specific Native AOT tool packages.
The release assembly generates Homebrew, Scoop, and WinGet manifests from release checksums, writes per-asset .sha256 files, writes an aggregate checksums.txt, and creates or updates the GitHub Release for the tag. It also writes release-artifacts.json, a deterministic inventory of final payload names, exact byte counts, and SHA-256 digests. Checksum sidecars and checksums.txt are excluded from that payload-size inventory; the inventory itself is included in checksums.txt, attested separately, and published with the release. Only after that immutable release exists does the workflow publish .nupkg and .snupkg files to NuGet.org and per-architecture GHCR images. Stable releases then update the Homebrew tap and Scoop bucket, submit the WinGet manifest, publish the Azure DevOps extension, advance the GitHub Action major tag, and verify the Marketplace listing. The versioned container manifest follows its architecture images; the mutable latest tag waits for both NuGet and container publication.
GitHub’s supported Marketplace publication control is available only on the release page. For each stable Action release, edit https://github.com/willibrandon/picket/releases/edit/<tag>, select Publish this Action to the GitHub Marketplace, choose Security and Code quality, and publish the update with two-factor authentication. The release workflow’s final verifier fails until https://github.com/marketplace/actions/picket-secret-scanner reports that exact tag.
Release provenance uses GitHub artifact attestations through actions/attest@v4.2.0. GitHub’s current guidance for binary provenance requires id-token: write, contents: read, attestations: write, and a step that attests the built artifact. Consumers can verify a downloaded artifact with:
gh attestation verify <artifact-path> -R willibrandon/picketArtifact attestations prove repository and workflow provenance but are not Authenticode signatures or Apple notarization. Windows executables and MSIs are currently unsigned, and macOS executables are currently unnotarized, so operating-system reputation checks can still warn on first use. Code signing and notarization require dedicated signing identities and remain a release-channel enhancement; the workflow does not suppress or bypass platform security checks.
The release tag is the source of truth for package versions. Release jobs strip a leading v from tags such as v0.1.13, pass the resulting SemVer value to Version and PackageVersion, publish package and symbol files with --skip-duplicate, and fail clearly when NUGET_API_KEY is missing.