Skip to content

chore(release): harden winget pipeline#277

Merged
thewrz merged 8 commits into
mainfrom
chore/winget-pipeline-hardening
May 2, 2026
Merged

chore(release): harden winget pipeline#277
thewrz merged 8 commits into
mainfrom
chore/winget-pipeline-hardening

Conversation

@thewrz

@thewrz thewrz commented May 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Pre-emptive hardening of the winget release pipeline driven by historical friction analysis. Five tweaks (1, 3, 4, 5, 6 from the audit) plus the shared semver refactor and a new test workflow. Each script is unit-tested before being wired into release.yml.

What's added

Script Tested by Wired in
validate-tag.sh 13 bats tests New validate-tag job (fail-fast)
tag-to-semver.sh + .ps1 8 bats + 7 Pester Build job + update-winget job
verify-arp-version.ps1 4 Pester New step on Windows build
verify-uninstall.ps1 4 Pester Replaces ad-hoc uninstall block
wait-for-asset.ps1 4 Pester Pre-wingetcreate guard

What's fixed (root causes from history)

Past blocker PR New defense
Date tags mangled into invalid NuGet versions #34 Shared script + tests pin conversion logic
--squirrel-firstrun E_ABORT #216 Fix lives in main.ts; ARP-version check now catches version-inject regressions that would shadow it
Wrong identifier / draft release / x86 mis-detect #35, #149, #174 YAML hardened via env: pattern; asset retry covers replication lag
Untested inline bash/pwsh logic winget-scripts-test.yml runs bats + Pester on PRs touching scripts or release.yml

What's deferred

  • Tweak 2 — pre-flight wingetcreate update --no-submit on PRs touching bridge-app (separate PR — needs Windows runner per PR, cost concern)
  • Tweak 7 — scheduled PAT health check (separate PR — orthogonal, simpler)

Workflow injection hardening

All ${{ github... }} and ${{ secrets... }} refs flow through env:. No untrusted-input concatenation in run: blocks.

Test plan

  • 21/21 bats tests pass locally
  • Pester suites pass on windows-latest (first run is this PR)
  • Both bats and Pester jobs in winget-scripts-test.yml go green
  • Existing ci.yml and codeql.yml jobs still pass (no source code touched)
  • After merge: cut a low-stakes patch tag (e.g. v2026.05.02) to validate full release pipeline end-to-end before relying on it for a real release

thewrz added 8 commits May 1, 2026 23:52
…ail verify

Reflects features added since last refresh:
- Cloudflare Turnstile human verification on /join + /collect
- Mandatory nickname gate, profanity filter, email verification (Resend)
- Pre-event collection flow + Tower v2 UI
- Enrich All advanced action
- HUMAN_COOKIE_SECRET, RESEND_API_KEY, EMAIL_FROM_ADDRESS, SOUNDCHARTS_*
- Security section: MultiFernet rotation, pinned image SHAs, IP-free identity
- Add server/scripts/seed_demo_event.py to populate a "demo" event with
  4 enriched requests (BPM/key/genre) and 1 upvoted by a fake guest
- Update playwright fixture to use DEMO01 when present, fall back to
  fresh-event creation. Avoids picking a stale expired event[0].
- Add Guest Collect (mobile) capture — forces collection_phase_override
  to render the Tower v2 nickname gate
- Refresh all docs/images/*.png from new run
Previous /join and /collect captures only showed the NicknameGate.
Seed script now creates a verified Guest + per-event GuestProfile so
Playwright can drop a wrzdj_guest cookie and bypass the gate, capturing
the actual Tower v2 leaderboard with rank badges, monogram tiles, vote
counts, "My Picks" section, and gradient CTAs.

Also captures the song detail sheets — tap a row to open the full
metadata view (BPM, Camelot key, queue rank, requester avatar).

- 12 screenshots total (was 11): adds gate, tower, and detail variants
- Marcus's pick (Daft Punk - One More Time) linked to demo guest_id so
  the auto-open request sheet doesn't intercept clicks
- All seed requests get submitted_during_collection=True so they show
  in the /collect leaderboard
Local DB had 25 dropped/stale events with test names ('asdasdsad',
'wrzdj are so cool uwu', repeated 'E2E-Recovery-Test', 'Abuse Test
Event', etc.) leaking into /events and /dashboard screenshots.

Cleared all events except DEMO01 (and dependent rows in requests,
request_votes, now_playing, play_history, guest_profiles), re-seeded
DEMO01, re-ran the 12-shot screenshot suite.
Unify screenshot filenames across the WrzDJ repo and the wrzdjweb
landing page so a single 'npm run screenshots' run drops finished
assets that can be rsynced verbatim to either side.

Renames (Playwright outputs in dashboard/e2e/screenshots.spec.ts):
- dj-dashboard            -> screenshot-dashboard
- events-list             -> screenshot-events-list
- event-management        -> screenshot-event-management
- event-management-tab    -> screenshot-event-management-tab
- admin-{overview,users,integrations,settings} -> screenshot-admin-*
- guest-join-gate-mobile  -> screenshot-join-gate-mobile
- guest-join-mobile       -> screenshot-join-mobile
- guest-join-detail-mobile -> screenshot-join-detail-mobile
- guest-collect-mobile    -> screenshot-collect-mobile
- guest-collect-detail-mobile -> screenshot-collect-detail-mobile
- kiosk-display           -> screenshot-kiosk

New scripts:
- scripts/png-to-webp.sh  encodes every screenshot-*.png to .webp at
  q=85 via cwebp. Wired into 'npm run screenshots' so PNG and WebP
  variants stay in sync automatically.
- scripts/sync-screenshots-to-wrzdjweb.sh  rsyncs the canonical files
  to ~/github/wrzdjweb/images/ — landing-page deploys are now a copy,
  no rename math required.

README.md updated to point at the new canonical paths.
…026-05

# Conflicts:
#	dashboard/e2e/screenshots.spec.ts
#	docs/images/admin-overview.png
#	docs/images/admin-settings.png
#	docs/images/admin-users.png
#	docs/images/dj-dashboard.png
#	docs/images/event-management-tab.png
#	docs/images/event-management.png
#	docs/images/events-list.png
#	docs/images/guest-join-mobile.png
#	docs/images/kiosk-display.png
…fy, asset retry)

Hardening tweaks driven by historical winget submission failures:

1. validate-tag.sh fails fast on malformed tags (e.g. v2026.4.08, rc suffixes)
   before any builds run. New validate-tag job is needs-by build-bridge-app.

2. tag-to-semver.{sh,ps1} extract the date-tag→semver conversion that lived
   inline in two places in release.yml. Paired bats + Pester tests cover
   leading-zero handling, revision suffixes, and edge cases.

3. wait-for-asset.ps1 HEAD-polls the release asset URL with exponential
   backoff before wingetcreate runs, eliminating CDN-replication 404 races
   that previously required manual reruns.

4. update-winget job now skips on workflow_dispatch when win32 isn't built.

5. verify-arp-version.ps1 asserts the installed ARP DisplayVersion matches
   the computed semver — catches version-injection regressions before they
   reach winget validators (which reject ARP/manifest mismatches).

6. verify-uninstall.ps1 asserts silent uninstall exits 0 — winget validation
   requires this; previously we ran it best-effort with no exit-code check.

7. winget-scripts-test.yml runs bats (ubuntu) + Pester (windows) on PRs
   touching any of the above so script regressions get caught before tag.

All values from github context flow through env: per workflow injection
hardening guidance.

21/21 bats tests pass locally. Pester suites validated on Windows runner.
…ion)

Pester v5 isolates Mock script blocks; $script:counter assignments inside
Mocks don't propagate back to the It scope. Replace manual counters with
Should -Invoke -Times -Exactly and -ParameterFilter for backoff verification.

Fixes 3/19 failures from initial CI run.
@thewrz thewrz merged commit 1d50b0f into main May 2, 2026
10 checks passed
@thewrz thewrz deleted the chore/winget-pipeline-hardening branch May 2, 2026 22:25
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.

1 participant