Skip to content

Build and release

The signing boundary is defined by ADR-0006: the Store applies the final Store-channel signature, while GitHub/WinGet use publicly trusted cloud-HSM signing. Production private keys are never exported into GitHub secrets. PFX handling has been removed from Release and a protected DigiCert Binary Signing simple-signing path is wired; the 1.0 signing gate remains pending until real-certificate evidence exists.

The public Code signing policy records the SignPath Foundation application status, release roles, provenance boundary, privacy statement, and the separate Partner Center MSIX identity. SignPath Foundation is not connected to the production workflow until the application is accepted and the MSIX identity decision is reviewed.

After a local build or documentation verification, run ./scripts/Clean-BuildArtifacts.ps1 to remove only the fixed generated directories: Cargo target, documentation dependencies/output, the MSIX test helper’s bin/obj, and dist. The script requires a repository marker and rejects paths outside the repository; it does not remove source files, release/ records, Stage archives, or the retained Windows runnable directory. Use -KeepDist when local installer output must be kept temporarily.

CI runs a safety-scope regression for the cleanup script, and Windows Release invokes it after architecture artifacts have been uploaded.

GitHub Release user assets contain one all-in-one MSIX bundle, one standalone portable EXE for each of x64 and ARM64, and one SHA-256 file. Internal audits, SBOMs, provenance, and WinGet YAML remain workflow artifacts rather than Release assets. GitHub Release is the first public channel. WinGet uses planned ID ZiCode.ZiFile; Microsoft Store uses MSIX.

The WinGet candidate accepts only the public all-in-one .msixbundle URL, SHA-256, and local verification path; it no longer requires or references unpublished per-architecture MSIX files. Following Microsoft’s MSIXBundle example, the manifest retains x64 and ARM64 installer nodes that must point to the same bundle URL and hash. Test-Manifests.ps1 locks that relationship, all 31 file extensions, the local bundle hash, and both nested MSIX package identities before official winget validate runs. Development identities such as ZiCode.ZiFile.Dev are rejected before submission. The ordinary unsigned GitHub evidence path may opt into -AllowDevelopmentIdentity; that explicit exception is not valid for a community submission.

GitHub main requires a pull request and seven status checks: Dependency policy, Documentation build, Fuzz targets compile, MSIX Repair helper, RAR reference interoperability, Rust performance benchmarks, and Rust quality gates. Checks must run against the latest main branch and administrators cannot bypass them. The single-maintainer workflow requires no approving review, but all review conversations must be resolved, history must remain linear, and force-pushes or deletion of main are disabled. This policy lives in GitHub repository settings; a repository migration or recreation must audit it separately rather than inferring it from source files.

The release job runs tests/smoke/portable-exe.ps1 against the x64 desktop executable in a directory that deliberately has no separate Worker. It starts the embedded --zifile-worker mode three times to create a ZIP, list and verify its entry, then extract and byte-compare the source. The public single-file asset must therefore prove a complete create-list-extract round trip rather than startup alone. The ARM64 executable runs the same PE-header and no-adjacent-Worker audit with execution skipped on the x64 runner; actual ARM64 execution remains a physical-device gate.

A v* tag builds both architectures and publishes a public GitHub Release directly. A normal push of a stable tag such as v1.0.0 publishes one unsigned all-in-one MSIX bundle, one standalone x64 EXE, one standalone ARM64 EXE, and a single SHA-256 file; audits, SBOMs, provenance, and the WinGet manifest candidate remain workflow artifacts. A manual stable-tag run with signing_provider=digicert-stm instead publishes the signed portable EXEs and a bundle rebuilt from the signed MSIX files. Release notes identify which path was used, and users should verify SHA-256 first. Hyphenated stage tags such as v0.1.0-alpha.1, v0.1.0-beta.1, and v1.0.0-rc.1 publish the same user-facing assets as GitHub pre-releases; they must never be submitted to WinGet or Store. Formal signing remains an explicit digicert-stm manual option, and Partner Center, Store screenshots, and all 1.0 readiness gates are required only when require_release_ready is explicitly enabled. Unsigned .Dev packages use Microsoft’s fixed OID publisher and require Windows 11 build 26100; formally signed/Store packages use the exact certificate or Partner Center publisher, retain the 19041 minimum, and must not contain the unsigned OID.

Before upload, publish-stage checks out the tagged commit and runs tests/smoke/public-release-assets.ps1. The audit enforces exactly these four user-facing asset classes, one checksum per payload, non-empty files, and no DLL, configuration, ZIP, SBOM, or other internal artifact in the public Release. This keeps workflow evidence separate from the user download surface.

The stage prerelease workflow automatically adds Free code signing provided by SignPath.io, certificate by SignPath Foundation. to Release Notes while retaining GitHub’s generated change summary. This is foundation attribution/application context; it does not mean the current unsigned development package has a trusted signature.

A manual run may select digicert-stm for a complete signing rehearsal and require_release_ready for formal gates. Build-time repository Variables are ZIFILE_MSIX_IDENTITY, ZIFILE_MSIX_PUBLISHER, and ZIFILE_MSIX_PUBLISHER_DISPLAY_NAME; the protected Environment provides Variables SM_HOST and SM_KEYPAIR_ALIAS plus Secrets SM_API_KEY, SM_CLIENT_CERT_FILE_B64, and SM_CLIENT_CERT_PASSWORD. Formal identity values are passed to the builder only for explicit signing/formal-gate rehearsals; ordinary public releases retain the isolated .Dev identity. The client-authentication certificate only accesses the signing service, is written to the runner temporary directory, and is removed after use; the code-signing private key remains in the cloud HSM.

A require_release_ready or digicert-stm rehearsal runs Test-PartnerCenterIdentity.ps1 -RequireConfigured before compilation. Name, Publisher, and Publisher Display Name must appear together; Name must fit the MSIX 3–50 alphanumeric/period/dash boundary, Publisher must be a valid X.500 distinguished name, and .Dev or the unsigned OID is rejected. Copy all three values exactly from Partner Center. Preflight proves structure and source constraints, not account ownership or name reservation.

See Production signing operations for provisioning, approval, rotation, emergency stop, revocation, and the minimum evidence set. Signing jobs serialize releases per architecture, have a hard 30-minute timeout, and use job-level least privilege. A timeout is a failure and cannot bypass post-signing verification.

Each MSIX is unpacked and audited for identity, publisher, publisher display name, version, minimum Windows build, PE architecture of desktop/CLI/Worker/Explorer DLL, associations, zifile.exe alias, absence of sensitive files and ZIP artifacts, and signature state. The audit also opens the desktop EXE as resource data without executing the target program, validates all 16/24/32/48/256 GROUP_ICON/ICON frames, and records embedded_desktop_icon. Audit output does not replace real install, upgrade, uninstall, or WACK testing.

tests/smoke/msix-lifecycle.ps1 provides an explicit machine gate for trusted signed baseline and upgrade packages. It audits both packages, refuses any pre-existing installation with the same identity, then checks installation, the packaged CLI, version upgrade, Reset-AppxPackage, and guaranteed cleanup with JSON evidence. Microsoft defines Reset as restoring initial configuration, so the script does not label it as a data-preserving Repair operation; formal Repair remains a separate gate. See the Appx module and Reset-AppxPackage.

The manual Trusted MSIX lifecycle workflow accepts two signed Release run IDs, reads the default package and audit JSON from each signed-windows-x64 artifact, and executes the same gate on a clean Windows Runner with 30-day evidence retention. It deliberately rejects the pre-signing windows-x64 artifacts. Real ARM64 installation remains a physical ARM64 Windows gate.

Windows Release uses pinned Rust 1.93.0, Cargo.lock, a single Cargo job, and MSVC /Brepro. Each x64/ARM64 test-and-package job has a 90-minute hard timeout; a timeout fails the job and cannot bypass package audit or artifact upload. The separate double-build gate compares five raw PE files; see Reproducible Windows builds.

Before tagging, the Release workflow can be run manually. It accepts no second version input and uses the workspace version from Cargo.toml; a pushed tag is automatically published, while none builds unsigned dual-architecture artifacts and digicert-stm additionally enters the protected environment, signs the staged artifacts, rebuilds the all-in-one bundle from the signed MSIX files, and publishes the signed portable EXEs and bundle. Use precise incrementing stage tags such as v0.1.0-alpha.1, v0.1.0-beta.1, and v1.0.0-rc.1 for milestone pre-releases; an unhyphenated v1.0.0 is also publishable as a public GitHub build, while formal gates are opt-in through require_release_ready. Normal CI and Release enforce version consistency, and a tag must exactly match v<workspace-version>. See Public contracts and version policy for the CLI, core-provider, and IPC compatibility boundaries. Manual validation and hyphenated stage tags also package the -accessible candidate; stable tags publish the default UI.

After a public Release succeeds and its four user-facing assets are verified, update release/public-release.json in a follow-up documentation commit. It records the version, tag, URL, publication time, and exact assets that actually exist, rather than the workspace version being prepared next. The documentation claim check requires the README, bilingual getting-started pages, current Stage 4 status, and roadmap to match this record. Release preparation can therefore keep pointing safely to the previous version, while a completed publication cannot silently leave stale download guidance behind.

Normal CI also requires exactly one [Unreleased] section in CHANGELOG.md. Before a tag can build, its changes must be cut into ## [<workspace-version>] - YYYY-MM-DD with at least one Keep a Changelog category and one non-placeholder entry. A missing version heading, invalid date, empty section, or remaining TODO/TBD fails before compilation. Manual Release validates only the [Unreleased] structure so it remains useful as a pre-tag rehearsal.

release/readiness.json tracks the 11 formal-channel gates for 1.0. Normal CI and public releases validate its structure and evidence format; Test-ReleaseReadiness.ps1 -RequireReleaseReady runs only when require_release_ready is explicitly enabled, so a pending gate does not block the ordinary public GitHub build. The current status is candidate; see 1.0 release readiness.

Stable releases require unit, interoperability, security, performance, install/upgrade, and documentation checks plus synchronized Stage logs. Test-WackReadiness.ps1 first checks the current interactive administrator session, WACK installation, host/package architecture, schema-v2 audit and package hash, the exact Partner Center Identity/Publisher/Publisher Display Name tuple, build 19041, forbidden files, and matching Valid signatures without installing or executing the package. It can persist failure evidence but does not run WACK. Certification still requires WACK in the current user’s interactive administrator session.