Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
46 changes: 45 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
shared:
name: Shared checks (${{ matrix.name }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -52,11 +52,13 @@ jobs:
run: >-
uv run --project backend --frozen --only-group ci ruff format --check
scripts/release_artifact.py scripts/tests/test_release_artifact.py
scripts/tests/test_windows_packaging.py

- name: Lint release tooling
run: >-
uv run --project backend --frozen --only-group ci ruff check
scripts/release_artifact.py scripts/tests/test_release_artifact.py
scripts/tests/test_windows_packaging.py

- name: Check portable Python formatting
working-directory: backend
Expand Down Expand Up @@ -152,3 +154,45 @@ jobs:
run: >-
cargo clippy --locked --workspace --all-targets
--manifest-path src-tauri/Cargo.toml -- -D warnings

- name: Install pinned Tauri packaging CLI
if: runner.os == 'Windows'
run: cargo install tauri-cli --version '=2.11.2' --locked

- name: Build older unsigned Windows installer
if: runner.os == 'Windows'
id: windows_older
shell: pwsh
run: ./scripts/build-windows-installer.ps1 -ReleaseTag v2026.08.1 -UnsignedDevelopment

- name: Build newer unsigned Windows installer
if: runner.os == 'Windows'
id: windows_newer
shell: pwsh
run: ./scripts/build-windows-installer.ps1 -ReleaseTag v2026.08.2 -UnsignedDevelopment

- name: Prove release verification rejects unsigned artifacts
if: runner.os == 'Windows'
shell: pwsh
run: >-
./scripts/assert-windows-release-rejects-unsigned.ps1
-Path '${{ steps.windows_newer.outputs.installer }}'

- name: Test Windows installer lifecycle
if: runner.os == 'Windows'
shell: pwsh
run: >-
./scripts/test-windows-installer.ps1
-OlderInstaller '${{ steps.windows_older.outputs.installer }}'
-NewerInstaller '${{ steps.windows_newer.outputs.installer }}'
-OlderVersion '${{ steps.windows_older.outputs.version }}'
-NewerVersion '${{ steps.windows_newer.outputs.version }}'

- name: Upload unsigned Windows development installer
if: runner.os == 'Windows'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: windows-x64-unsigned-development
path: ${{ steps.windows_newer.outputs.installer }}
if-no-files-found: error
retention-days: 7
105 changes: 103 additions & 2 deletions .github/workflows/macos-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Release
run-name: Release ${{ github.ref_name }} by @${{ github.actor }}

# A protected release tag starts validation. Signing credentials remain behind
# the macos-release Environment's separate human approval gate.
# each platform release Environment's separate human approval gate.
on:
push:
tags:
Expand All @@ -13,7 +13,7 @@ permissions:
contents: read

concurrency:
group: macos-release
group: release
cancel-in-progress: false

jobs:
Expand Down Expand Up @@ -248,14 +248,108 @@ jobs:
"${LSDJ_CERTIFICATE_PATH:-}" \
"${LSDJ_API_KEY_PATH:-}"

produce_windows:
name: Produce Windows x64 artifact
needs: validate
if: >-
needs.validate.result == 'success' &&
github.repository == 'protocol-works/lsdj' &&
startsWith(github.ref, 'refs/tags/v')
runs-on: windows-2025
timeout-minutes: 180

# This protected Environment must require a separate human approval. The
# provider-specific identity provisioning step cannot be selected until the
# project chooses an Authenticode provider. These public identity values and
# the credentialed wrapper path stay unavailable before approval.
environment:
name: windows-release

env:
LSDJ_WINDOWS_SIGN_COMMAND_PATH: ${{ vars.WINDOWS_SIGN_COMMAND_PATH }}
LSDJ_WINDOWS_EXPECTED_CERTIFICATE_SHA1: ${{ vars.WINDOWS_EXPECTED_CERTIFICATE_SHA1 }}
LSDJ_WINDOWS_EXPECTED_SUBJECT: ${{ vars.WINDOWS_EXPECTED_SUBJECT }}

steps:
- name: Check out the approved release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.13"

- name: Set up Rust
shell: pwsh
run: |
rustup toolchain install stable --profile minimal --no-self-update
rustup default stable

- name: Install build tools and frontend dependencies
shell: pwsh
run: |
cargo install tauri-cli --version '=2.11.2' --locked
npm ci --prefix frontend
npm run build --prefix frontend

# The selected provider must insert a protected provisioning step before
# this preflight (for example, federated identity or an HSM-backed client).
# Until that reviewed decision exists, this job intentionally fails here.
- name: Require protected Authenticode identity
shell: pwsh
run: ./scripts/sign-windows.ps1 -Preflight

- name: Build, sign, timestamp, and verify NSIS installer
id: windows_build
shell: pwsh
run: ./scripts/build-windows-installer.ps1 -ReleaseTag $env:GITHUB_REF_NAME -Release

- name: Verify installed executable payloads and uninstall behavior
shell: pwsh
run: >-
./scripts/verify-windows-release-install.ps1
-Installer '${{ steps.windows_build.outputs.installer }}'
-ExpectedVersion '${{ steps.windows_build.outputs.version }}'

- name: Package verified release artifact
shell: pwsh
env:
LSDJ_RELEASE_REVISION: ${{ needs.validate.outputs.revision }}
run: >-
python scripts/release_artifact.py create
--producer windows-x64
--release-tag "$env:GITHUB_REF_NAME"
--revision "$env:LSDJ_RELEASE_REVISION"
--asset '${{ steps.windows_build.outputs.installer }}'
--output-dir release-artifacts/windows-x64

- name: Upload verified producer bundle
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-windows-x64
path: release-artifacts/windows-x64
if-no-files-found: error
retention-days: 14

publish:
name: Verify and publish complete release
needs:
- validate
- produce_macos
- produce_windows
if: >-
needs.validate.result == 'success' &&
needs.produce_macos.result == 'success' &&
needs.produce_windows.result == 'success' &&
github.repository == 'protocol-works/lsdj' &&
startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
Expand All @@ -280,6 +374,12 @@ jobs:
name: release-macos-arm64
path: release-input/macos-arm64

- name: Download Windows producer bundle
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-windows-x64
path: release-input/windows-x64

- name: Verify complete required producer set
env:
LSDJ_RELEASE_REVISION: ${{ needs.validate.outputs.revision }}
Expand All @@ -289,6 +389,7 @@ jobs:
python scripts/release_artifact.py verify \
--input-root release-input \
--required-producer macos-arm64 \
--required-producer windows-x64 \
--release-tag "$GITHUB_REF_NAME" \
--revision "$LSDJ_RELEASE_REVISION" \
--output-dir verified-release
Expand Down
18 changes: 12 additions & 6 deletions docs/cross-platform-ci-and-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ contract. A fake result must not be reported as hardware qualification.

## Release producer/publisher boundary

The tag workflow keeps macOS as the only required release artifact initially.
It has three stages:
The tag workflow requires macOS arm64 and Windows x64 release artifacts. It has
four stages:

1. `validate` accepts only a calendar-version `v*` tag whose commit is contained
in `main`.
Expand All @@ -64,7 +64,13 @@ It has three stages:
notarizes, staples, and verifies the app and DMG. It then uploads one Actions
artifact containing the DMG, `SHA256SUMS.txt`, and metadata binding the
producer to the tag and exact source revision.
3. `publish` is the only job with `contents: write`. It downloads every required
3. `produce_windows` waits behind the protected `windows-release` Environment,
requires the selected provider's protected one-file signing interface, builds
the per-user NSIS installer, verifies the exact Authenticode identity and
timestamp on the installer and installed executable payloads, and exercises
preservation/removal before uploading its producer bundle. Until a provider
and CI identity are selected, this job intentionally fails at preflight.
4. `publish` is the only job with `contents: write`. It downloads every required
producer bundle, requires the producer set to match exactly, recomputes all
sizes and SHA-256 digests, and verifies tag/revision/platform metadata before
it creates a GitHub Release.
Expand All @@ -82,9 +88,9 @@ replacement draft. A failure before publication keeps the release private and
attempts to remove only the draft created by that run. An existing release is
never overwritten.

Signing and notarization secrets exist only in the macOS producer. The
publisher receives no signing credentials, and producers never receive
`contents: write`.
Signing and notarization secrets exist only in their protected platform
producer. The publisher receives no signing credentials, and producers never
receive `contents: write`.

## Adding a release platform

Expand Down
79 changes: 79 additions & 0 deletions docs/windows-release-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Issue #113 — Windows 11 x64 release qualification

This checklist is intentionally unchecked where physical hardware, a selected
signing provider, or current security definitions are required. Hosted CI
evidence must not be substituted for these items.

## Release identity and installer operations

- [ ] Select an Authenticode provider/certificate and record the exact subject,
leaf thumbprint, timestamp service, legal owner, and expected Explorer
publisher text.
- [ ] Configure a protected `windows-release` Environment with separate approval,
no administrator bypass, and a least-privilege CI identity.
- [ ] Document provider credential/key storage, access review, rotation, expiry,
compromise, incident, and revocation procedures; perform one revocation drill.
- [ ] Confirm the protected provider exposes the reviewed one-file signing wrapper
contract used by `scripts/sign-windows.ps1`.
- [ ] On a clean Windows 11 x64 account, verify the final NSIS installer, installed
app, uninstaller, and every executable payload have the exact signer and a
trusted timestamp.
- [ ] Install, upgrade, attempt a downgrade, uninstall with preservation, and
uninstall with explicit data removal. Record screenshots of the disclosed
`%LOCALAPPDATA%\LSDJ` path and measured size.
- [ ] Repeat installation under a profile containing spaces and non-ASCII text,
with Windows long-path support disabled.
- [ ] Confirm Start menu behavior, window/titlebar, file/folder dialogs,
notifications, opener/trash behavior, packaged resources, update/restart, and
WebView2 present/missing/offline failure cases.

## Hardware record

Record exact Windows build, CPU, RAM, GPU, VRAM, NVIDIA driver, PyTorch/CUDA
runtime, audio device/driver, WASAPI rate/format/buffer, MIDI devices, FLX4
firmware/driver, security state, LSDJ revision, and model/runtime revisions.

- [ ] Establish and document the minimum NVIDIA GPU, VRAM, driver, CPU, RAM, and
free-disk floor from measured results.
- [ ] Run both MRT2 decks for at least ten minutes at 25 frames / approximately
one second. Require zero engine-reported underruns and capture p50/p95/p99
generation latency, queue depth, temperature, and throttling.
- [ ] Run both armed decks for at least ten minutes at 5 frames / approximately
200 ms with the same zero-underrun and telemetry gate.
- [ ] Validate default-device selection/change, WASAPI shared-mode 48 kHz and
non-48 kHz devices, stereo output, FLX4 four-channel master/cue, removal,
renegotiation, and sleep/resume.
- [ ] Validate FLX4 WinMM naming, transport, mixer, jog wheels, performance pads,
LEDs, required SysEx, hotplug/reconnect, and actionable device-contention errors.
- [ ] Run Stable Audio music, SFX, audio-to-audio, continuation, inpainting,
Small/Medium, LoRA, cancellation, and long-duration validation while both decks
remain active. Record CPU/RAM impact and deck telemetry.
- [ ] Confirm normal quit, forced host exit, worker crash, update, and uninstall
leave no Python, model, or GPU worker descendants.

## Security and release response

- [ ] Test the release candidate against current Microsoft Defender definitions;
record platform, engine, intelligence versions, detection result, and submission
ID/disposition for any false-positive report.
- [ ] Exercise the documented signing-key compromise and bad-signature release
stop path without publishing a release.
- [ ] Confirm all model services bind to `127.0.0.1`, the installer creates no
firewall exception, and no public listener appears during first run or playback.
- [ ] Confirm a clean machine installs verified MRT2 and Stable Audio
runtimes/models without system Python, Git, CUDA toolkit, WSL, compiler, or shell.
- [ ] Interrupt and corrupt each runtime/model download and promotion; the prior
verified version must remain usable and diagnostics must identify recovery.
- [ ] Confirm the single publisher refuses missing, unsigned, invalid,
untimestamped, duplicate, or unexpected Windows artifacts.

## External blockers

- Authenticode provider/certificate, exact publisher subject, timestamp service,
protected CI identity, and credential lifecycle decisions.
- Physical Windows 11 x64 + supported NVIDIA host with current drivers.
- Pioneer/AlphaTheta DDJ-FLX4 plus representative WASAPI devices.
- Current Defender/SmartScreen observation on the signed release candidate.
- #110 production runtime installation and NVIDIA qualification.
- #111 TFLite Stable Audio runtime installation and parity qualification.
- #108 final notices/acknowledgement and repository licensing decisions.
Loading
Loading